View | Details | Raw Unified | Return to bug 21068
Collapse All | Expand All

(-)a/C4/Installer/PerlDependencies.pm (-20 lines)
Lines 737-762 our $PERL_DEPS = { Link Here
737
        'required' => '0',
737
        'required' => '0',
738
        'min_ver'  => '5.836',
738
        'min_ver'  => '5.836',
739
    },
739
    },
740
    'SOAP::Lite' => {
741
        'usage'    => 'Norwegian national library card',
742
        'required' => '0',
743
        'min_ver'  => '0.712',
744
    },
745
    'Crypt::GCrypt' => {
746
        'usage'    => 'Norwegian national library card',
747
        'required' => '0',
748
        'min_ver'  => '1.24',
749
    },
750
    'Convert::BaseN' => {
751
        'usage'    => 'Norwegian national library card',
752
        'required' => '0',
753
        'min_ver'  => '0.01',
754
    },
755
    'Digest::SHA' => {
756
        'usage'    => 'Norwegian national library card',
757
        'required' => '0',
758
        'min_ver'  => '5.61',
759
    },
760
    'PDF::FromHTML' => {
740
    'PDF::FromHTML' => {
761
        'usage'    => 'Discharge generation',
741
        'usage'    => 'Discharge generation',
762
        'required' => '0',
742
        'required' => '0',
(-)a/C4/Members.pm (-6 lines)
Lines 50-61 use Koha::Schema; Link Here
50
50
51
our (@ISA,@EXPORT,@EXPORT_OK,$debug);
51
our (@ISA,@EXPORT,@EXPORT_OK,$debug);
52
52
53
use Module::Load::Conditional qw( can_load );
54
if ( ! can_load( modules => { 'Koha::NorwegianPatronDB' => undef } ) ) {
55
   $debug && warn "Unable to load Koha::NorwegianPatronDB";
56
}
57
58
59
BEGIN {
53
BEGIN {
60
    $debug = $ENV{DEBUG} || 0;
54
    $debug = $ENV{DEBUG} || 0;
61
    require Exporter;
55
    require Exporter;
(-)a/Koha/NorwegianPatronDB.pm (-676 lines)
Lines 1-676 Link Here
1
package Koha::NorwegianPatronDB;
2
3
# Copyright 2014 Oslo Public Library
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 3 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
=head1 NAME
21
22
Koha::NorwegianPatronDB
23
24
=head1 SYNOPSIS
25
26
  use Koha::NorwegianPatronDB;
27
28
=head1 CONDITIONAL LOADING
29
30
This module depends on some Perl modules that have not been marked as required.
31
This is because the module only will be of interest to Norwegian libraries, and
32
it seems polite not to bother the rest of the world with these modules. It is
33
also good practice to check that the module is actually needed before loading
34
it. So in a NorwegianPatronDB page or script it will be OK to just do:
35
36
  use Koha::NorwegianPatronDB qw(...);
37
38
But in scripts that are also used by others (like e.g. moremember.pl), it will
39
be polite to only load the module at runtime, if it is needed:
40
41
  use Module::Load;
42
  if ( C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
43
      load Koha::NorwegianPatronDB, qw( NLGetSyncDataFromBorrowernumber );
44
  }
45
46
(Module::Load::Conditional is used for this in other parts of Koha, but it does
47
not seem to allow for a list of subroutines to import, so Module::Load looks
48
like a better candidate.)
49
50
=head1 FUNCTIONS
51
52
=cut
53
54
use Modern::Perl;
55
use C4::Context;
56
use C4::Members::Attributes qw( UpdateBorrowerAttribute );
57
use SOAP::Lite;
58
use Crypt::GCrypt;
59
use Digest::SHA qw( sha256_hex );
60
use Convert::BaseN;
61
use DateTime;
62
63
use base 'Exporter';
64
65
our %EXPORT_TAGS = ( all => [qw(
66
        NLCheckSysprefs
67
        NLSearch
68
        NLSync
69
        NLGetChanged
70
        NLMarkForDeletion
71
        NLDecodePin
72
        NLEncryptPIN
73
        NLUpdateHashedPIN
74
        NLGetFirstname
75
        NLGetSurname
76
        NLGetSyncDataFromBorrowernumber
77
)] );
78
Exporter::export_ok_tags('all');
79
80
my $nl_uri   = 'http://lanekortet.no';
81
82
=head2 SOAP::Transport::HTTP::Client::get_basic_credentials
83
84
This is included to set the username and password used by SOAP::Lite.
85
86
=cut
87
88
sub SOAP::Transport::HTTP::Client::get_basic_credentials {
89
    # Library username and password from Base Bibliotek (stored as system preferences)
90
    my $library_username = C4::Context->preference("NorwegianPatronDBUsername");
91
    my $library_password = C4::Context->preference("NorwegianPatronDBPassword");
92
    # Vendor username and password (stored in koha-conf.xml)
93
    my $vendor_username = C4::Context->config( 'nlvendoruser' );
94
    my $vendor_password = C4::Context->config( 'nlvendorpass' );
95
    # Combine usernames and passwords, and encrypt with SHA256
96
    my $combined_username = "$vendor_username-$library_username";
97
    my $combined_password = sha256_hex( "$library_password-$vendor_password" );
98
    return $combined_username => $combined_password;
99
}
100
101
=head2 NLCheckSysprefs
102
103
Check that sysprefs relevant to NL are set.
104
105
=cut
106
107
sub NLCheckSysprefs {
108
109
    my $response = {
110
        'error'     => 0,
111
        'nlenabled' => 0,
112
        'endpoint'  => 0,
113
        'userpass'  => 0,
114
    };
115
116
    # Check that the Norwegian national paron database is enabled
117
    if ( C4::Context->preference("NorwegianPatronDBEnable") == 1 ) {
118
        $response->{ 'nlenabled' } = 1;
119
    } else {
120
        $response->{ 'error' } = 1;
121
    }
122
123
    # Check that an endpoint is specified
124
    if ( C4::Context->preference("NorwegianPatronDBEndpoint") ne '' ) {
125
        $response->{ 'endpoint' } = 1;
126
    } else {
127
        $response->{ 'error' } = 1;
128
    }
129
130
    # Check that the username and password for the patron database is set
131
    if ( C4::Context->preference("NorwegianPatronDBUsername") ne '' && C4::Context->preference("NorwegianPatronDBPassword") ne '' ) {
132
        $response->{ 'userpass' } = 1;
133
    } else {
134
        $response->{ 'error' } = 1;
135
    }
136
137
    return $response;
138
139
}
140
141
=head2 NLSearch
142
143
Search the NL patron database.
144
145
SOAP call: "hent" (fetch)
146
147
=cut
148
149
sub NLSearch {
150
151
    my ( $identifier ) = @_;
152
153
    my $client = SOAP::Lite
154
        ->on_action( sub { return '""';})
155
        ->uri( $nl_uri )
156
        ->proxy( C4::Context->preference("NorwegianPatronDBEndpoint") );
157
158
    my $id = SOAP::Data->type('string');
159
    $id->name('identifikator');
160
    $id->value( $identifier );
161
    my $som = $client->hent( $id );
162
163
    return $som;
164
165
}
166
167
=head2 NLSync
168
169
Sync a patron that has been changed or created in Koha "upstream" to NL.
170
171
Input is a hashref with one of two possible elements, either a patron retrieved
172
from the database:
173
174
    my $result = NLSync({ 'patron' => $borrower_from_dbic });
175
176
or a plain old borrowernumber:
177
178
    my $result = NLSync({ 'borrowernumber' => $borrowernumber });
179
180
In the latter case, this function will retrieve the patron record from the
181
database using DBIC.
182
183
Which part of the API is called depends on the value of the "syncstatus" column:
184
185
=over 4
186
187
=item * B<new> = The I<nyPost> ("new record") method is called.
188
189
=item * B<edited> = The I<endre> ("change/update") method is called.
190
191
=item * B<delete> = The I<slett> ("delete") method is called.
192
193
=back
194
195
Required values for B<new> and B<edited>:
196
197
=over 4
198
199
=item * sist_endret (last updated)
200
201
=item * adresse, postnr eller sted (address, zip or city)
202
203
=item * fdato (birthdate)
204
205
=item * fnr_hash (social security number, but not hashed...)
206
207
=item * kjonn (gender, M/F)
208
209
=back
210
211
=cut
212
213
sub NLSync {
214
215
    my ( $input ) = @_;
216
217
    my $patron;
218
    if ( defined $input->{'borrowernumber'} ) {
219
        $patron = Koha::Database->new->schema->resultset('Borrower')->find( $input->{'borrowernumber'} );
220
    } elsif ( defined $input->{'patron'} ) {
221
        $patron = $input->{'patron'};
222
    }
223
224
    # There should only be one sync, so we use the first one
225
    my @syncs = $patron->borrower_syncs;
226
    my $sync;
227
    foreach my $this_sync ( @syncs ) {
228
        if ( $this_sync->synctype eq 'norwegianpatrondb' ) {
229
            $sync = $this_sync;
230
        }
231
    }
232
233
    my $client = SOAP::Lite
234
        ->on_action( sub { return '""';})
235
        ->uri( $nl_uri )
236
        ->proxy( C4::Context->preference("NorwegianPatronDBEndpoint") );
237
238
    my $cardnumber = SOAP::Data->name( 'lnr' => $patron->cardnumber );
239
240
    # Call the appropriate method based on syncstatus
241
    my $response;
242
    if ( $sync->syncstatus eq 'edited' || $sync->syncstatus eq 'new' ) {
243
        my $soap_patron = _koha_patron_to_soap( $patron );
244
        if ( $sync->syncstatus eq 'edited' ) {
245
            $response = $client->endre( $cardnumber, $soap_patron );
246
        } elsif ( $sync->syncstatus eq 'new' ) {
247
            $response = $client->nyPost( $soap_patron );
248
        }
249
    }
250
    if ( $sync->syncstatus eq 'delete' ) {
251
        $response = $client->slett( $cardnumber );
252
    }
253
254
    # Update the sync data according to the results
255
    if ( $response->{'status'} && $response->{'status'} == 1 ) {
256
        if ( $sync->syncstatus eq 'delete' ) {
257
            # Turn off any further syncing
258
            $sync->update( { 'sync' => 0 } );
259
        }
260
        # Update the syncstatus to 'synced'
261
        $sync->update( { 'syncstatus' => 'synced' } );
262
        # Update the 'synclast' attribute with the "server time" ("server_tid") returned by the method
263
        $sync->update( { 'lastsync' => $response->{'server_tid'} } );
264
    }
265
    return $response;
266
267
}
268
269
=head2 NLGetChanged
270
271
Fetches patrons from NL that have been changed since a given timestamp. This includes
272
patrons that have been changed by the library that runs the sync, so we have to
273
check which library was the last one to change a patron, before we update patrons
274
locally.
275
276
This is supposed to be executed once per night.
277
278
SOAP call: soekEndret
279
280
=cut
281
282
sub NLGetChanged {
283
284
    my ( $from_arg ) = @_;
285
286
    my $client = SOAP::Lite
287
        ->on_action( sub { return '""';})
288
        ->uri( $nl_uri )
289
        ->proxy( C4::Context->preference("NorwegianPatronDBEndpoint") );
290
291
    my $from_string;
292
    if ( $from_arg && $from_arg ne '' ) {
293
        $from_string = $from_arg;
294
    } else {
295
        # Calculate 1 second past midnight of the day before
296
        my $dt = DateTime->now( time_zone => 'Europe/Oslo' );
297
        $dt->subtract( days => 1 );
298
        my $from = DateTime->new(
299
            year       => $dt->year(),
300
            month      => $dt->month(),
301
            day        => $dt->day(),
302
            hour       => 0,
303
            minute     => 0,
304
            second     => 1,
305
            time_zone  => 'Europe/Oslo',
306
        );
307
        $from_string = $from->ymd . "T" . $from->hms;
308
    }
309
310
    my $timestamp   = SOAP::Data->name( 'tidspunkt'    => $from_string );
311
    my $max_results = SOAP::Data->name( 'max_antall'   => 0 ); # 0 = no limit
312
    my $start_index = SOAP::Data->name( 'start_indeks' => 0 ); # 1 is the first record
313
314
    # Call the appropriate method based on syncstatus
315
    my $som = $client->soekEndret( $timestamp, $max_results, $start_index );
316
317
    # Extract and massage patron data
318
    my $result = $som->result;
319
    foreach my $patron ( @{ $result->{'respons_poster'} } ) {
320
        # Only handle patrons that have lnr (barcode) and fnr_hash (social security number)
321
        # Patrons that lack these two have been deleted from NL
322
        if ( $patron->{'lnr'} && $patron->{'fnr_hash'} ) {
323
            push @{ $result->{'kohapatrons'} }, _soap_to_kohapatron( $patron );
324
        }
325
    }
326
    return $result;
327
328
}
329
330
=head2 NLMarkForDeletion
331
332
Mark a borrower for deletion, but do not do the actual deletion. Deleting the
333
borrower from NL will be done later by the nl-sync-from-koha.pl script.
334
335
=cut
336
337
sub NLMarkForDeletion {
338
339
    my ( $borrowernumber ) = @_;
340
341
    my $borrowersync = Koha::Database->new->schema->resultset('BorrowerSync')->find({
342
        'synctype'       => 'norwegianpatrondb',
343
        'borrowernumber' => $borrowernumber,
344
    });
345
    return $borrowersync->update( { 'syncstatus' => 'delete' } );
346
347
}
348
349
=head2 NLDecodePin
350
351
Takes a string encoded with AES/ECB/PKCS5PADDING and a 128-bits key, and returns
352
the decoded string as plain text.
353
354
The key needs to be stored in koha-conf.xml, like so:
355
356
<yazgfs>
357
  ...
358
  <config>
359
    ...
360
    <nlkey>xyz</nlkey>
361
  </config>
362
</yazgfs>
363
364
=cut
365
366
sub NLDecodePin {
367
368
    my ( $hash ) = @_;
369
    my $key = C4::Context->config( 'nlkey' );
370
371
    # Convert the hash from Base16
372
    my $cb = Convert::BaseN->new( base => 16 );
373
    my $decoded_hash = $cb->decode( $hash );
374
375
    # Do the decryption
376
    my $cipher = Crypt::GCrypt->new(
377
        type      => 'cipher',
378
        algorithm => 'aes',
379
        mode      => 'ecb',
380
        padding   => 'standard', # "This is also known as PKCS#5"
381
    );
382
    $cipher->start( 'decrypting' );
383
    $cipher->setkey( $key ); # Must be called after start()
384
    my $plaintext  = $cipher->decrypt( $decoded_hash );
385
    $plaintext .= $cipher->finish;
386
387
    return $plaintext;
388
389
}
390
391
=head2 NLEncryptPIN
392
393
Takes a plain text PIN as argument, returns the encrypted PIN, according to the
394
NL specs.
395
396
    my $encrypted_pin = NLEncryptPIN( $plain_text_pin );
397
398
=cut
399
400
sub NLEncryptPIN {
401
402
    my ( $pin ) = @_;
403
    return _encrypt_pin( $pin );
404
405
}
406
407
=head2 NLUpdateHashedPIN
408
409
Takes two arguments:
410
411
=over 4
412
413
=item * Borrowernumber
414
415
=item * Clear text PIN code
416
417
=back
418
419
Hashes the password and saves it in borrower_sync.hashed_pin.
420
421
=cut
422
423
sub NLUpdateHashedPIN {
424
425
    my ( $borrowernumber, $pin ) = @_;
426
    my $borrowersync = Koha::Database->new->schema->resultset('BorrowerSync')->find({
427
        'synctype'       => 'norwegianpatrondb',
428
        'borrowernumber' => $borrowernumber,
429
        });
430
    return $borrowersync->update({ 'hashed_pin', _encrypt_pin( $pin ) });
431
432
}
433
434
=head2 _encrypt_pin
435
436
Takes a plain text PIN and returns the encrypted version, according to the NL specs.
437
438
=cut
439
440
sub _encrypt_pin {
441
442
    my ( $pin ) = @_;
443
    my $key = C4::Context->config( 'nlkey' );
444
445
    # Do the encryption
446
    my $cipher = Crypt::GCrypt->new(
447
        type      => 'cipher',
448
        algorithm => 'aes',
449
        mode      => 'ecb',
450
        padding   => 'standard', # "This is also known as PKCS#5"
451
    );
452
    $cipher->start( 'encrypting' );
453
    $cipher->setkey( $key ); # Must be called after start()
454
    my $ciphertext  = $cipher->encrypt( $pin );
455
    $ciphertext .= $cipher->finish;
456
457
    # Encode as Bas16
458
    my $cb = Convert::BaseN->new( base => 16 );
459
    my $encoded_ciphertext = $cb->encode( $ciphertext );
460
461
    return $encoded_ciphertext;
462
463
}
464
465
=head2 NLGetSyncDataFromBorrowernumber
466
467
Takes a borrowernumber as argument, returns a Koha::Schema::Result::BorrowerSync
468
object.
469
470
    my $syncdata = NLGetSyncDataFromBorrowernumber( $borrowernumber );
471
472
=cut
473
474
sub NLGetSyncDataFromBorrowernumber {
475
476
    my ( $borrowernumber ) = @_;
477
    my $data = Koha::Database->new->schema->resultset('BorrowerSync')->find({
478
        'synctype'       => 'norwegianpatrondb',
479
        'borrowernumber' => $borrowernumber,
480
    });
481
    return $data;
482
483
}
484
485
=head2 NLGetFirstname
486
487
Takes a string like "Surname, Firstname" and returns the "Firstname" part.
488
489
If there is no comma, the string is returned unaltered.
490
491
    my $firstname = NLGetFirstname( $name );
492
493
=cut
494
495
sub NLGetFirstname {
496
497
    my ( $s ) = @_;
498
    my ( $surname, $firstname ) = _split_name( $s );
499
    if ( $surname eq $s ) {
500
        return $s;
501
    } else {
502
        return $firstname;
503
    }
504
505
}
506
507
=head2 NLGetSurname
508
509
Takes a string like "Surname, Firstname" and returns the "Surname" part.
510
511
If there is no comma, the string is returned unaltered.
512
513
    my $surname = NLGetSurname( $name );
514
515
=cut
516
517
sub NLGetSurname {
518
519
    my ( $s ) = @_;
520
    my ( $surname, $firstname ) = _split_name( $s );
521
    return $surname;
522
523
}
524
525
=head2 _split_name
526
527
Takes a string like "Surname, Firstname" and returns a list of surname and firstname.
528
529
If there is no comma, the string is returned unaltered.
530
531
    my ( $surname, $firstname ) = _split_name( $name );
532
533
=cut
534
535
sub _split_name {
536
537
    my ( $s ) = @_;
538
539
    # Return the string if there is no comma
540
    unless ( $s =~ m/,/ ) {
541
        return $s;
542
    }
543
544
    my ( $surname, $firstname ) = split /, /, $s;
545
546
    return ( $surname, $firstname );
547
548
}
549
550
=head2 _format_soap_error
551
552
Takes a soap result object as input and returns a formatted string containing SOAP error data.
553
554
=cut
555
556
sub _format_soap_error {
557
558
    my ( $result ) = @_;
559
    if ( $result ) {
560
        return join ', ', $result->faultcode, $result->faultstring, $result->faultdetail;
561
    } else {
562
        return 'No result';
563
    }
564
565
}
566
567
=head2 _soap_to_koha_patron
568
569
Convert a SOAP object of type "Laaner" into a hash that can be sent to Koha::Patron
570
571
=cut
572
573
sub _soap_to_kohapatron {
574
575
    my ( $soap ) = @_;
576
577
    return {
578
        'cardnumber'      => $soap->{ 'lnr' },
579
        'surname'         => NLGetSurname(   $soap->{ 'navn' } ),
580
        'firstname'       => NLGetFirstname( $soap->{ 'navn' } ),
581
        'sex'             => $soap->{ 'kjonn' },
582
        'dateofbirth'     => $soap->{ 'fdato' },
583
        'address'         => $soap->{ 'p_adresse1' },
584
        'address2'        => $soap->{ 'p_adresse2' },
585
        'zipcode'         => $soap->{ 'p_postnr' },
586
        'city'            => $soap->{ 'p_sted' },
587
        'country'         => $soap->{ 'p_land' },
588
        'b_address'       => $soap->{ 'm_adresse1' },
589
        'b_address2'      => $soap->{ 'm_adresse2' },
590
        'b_zipcode'       => $soap->{ 'm_postnr' },
591
        'b_city'          => $soap->{ 'm_sted' },
592
        'b_country'       => $soap->{ 'm_land' },
593
        'password'        => $soap->{ 'pin' },
594
        'dateexpiry'      => $soap->{ 'gyldig_til' },
595
        'email'           => $soap->{ 'epost' },
596
        'mobile'          => $soap->{ 'tlf_mobil' },
597
        'phone'           => $soap->{ 'tlf_hjemme' },
598
        'phonepro'        => $soap->{ 'tlf_jobb' },
599
        '_extra'          => { # Data that should not go in the borrowers table
600
            'socsec'         => $soap->{ 'fnr_hash' },
601
            'created'        => $soap->{ 'opprettet' },
602
            'created_by'     => $soap->{ 'opprettet_av' },
603
            'last_change'    => $soap->{ 'sist_endret' },
604
            'last_change_by' => $soap->{ 'sist_endret_av' },
605
        },
606
    };
607
608
}
609
610
=head2 _koha_patron_to_soap
611
612
Convert a patron (in the form of a Koha::Schema::Result::Borrower) into a SOAP
613
object that can be sent to NL.
614
615
=cut
616
617
sub _koha_patron_to_soap {
618
619
    my ( $patron ) = @_;
620
621
    # Extract attributes
622
    my $patron_attributes = {};
623
    foreach my $attribute ( $patron->borrower_attributes ) {
624
        $patron_attributes->{ $attribute->code->code } = $attribute->attribute;
625
    }
626
627
    # There should only be one sync, so we use the first one
628
    my @syncs = $patron->borrower_syncs;
629
    my $sync = $syncs[0];
630
631
    # Create SOAP::Data object
632
    my $soap_patron = SOAP::Data->name(
633
        'post' => \SOAP::Data->value(
634
            SOAP::Data->name( 'lnr'         => $patron->cardnumber ),
635
            SOAP::Data->name( 'fnr_hash'    => $patron_attributes->{ 'fnr' } )->type( 'string' )->type( 'string' ),
636
            SOAP::Data->name( 'navn'        => $patron->surname . ', ' . $patron->firstname    )->type( 'string' ),
637
            SOAP::Data->name( 'sist_endret' => $sync->lastsync      )->type( 'string' ),
638
            SOAP::Data->name( 'kjonn'       => $patron->sex         )->type( 'string' ),
639
            SOAP::Data->name( 'fdato'       => $patron->dateofbirth )->type( 'string' ),
640
            SOAP::Data->name( 'p_adresse1'  => $patron->address     )->type( 'string' ),
641
            SOAP::Data->name( 'p_adresse2'  => $patron->address2    )->type( 'string' ),
642
            SOAP::Data->name( 'p_postnr'    => $patron->zipcode     )->type( 'string' ),
643
            SOAP::Data->name( 'p_sted'      => $patron->city        )->type( 'string' ),
644
            SOAP::Data->name( 'p_land'      => $patron->country     )->type( 'string' ),
645
            SOAP::Data->name( 'm_adresse1'  => $patron->b_address   )->type( 'string' ),
646
            SOAP::Data->name( 'm_adresse2'  => $patron->b_address2  )->type( 'string' ),
647
            SOAP::Data->name( 'm_postnr'    => $patron->b_zipcode   )->type( 'string' ),
648
            SOAP::Data->name( 'm_sted'      => $patron->b_city      )->type( 'string' ),
649
            SOAP::Data->name( 'm_land'      => $patron->b_country   )->type( 'string' ),
650
            # Do not send the PIN code as it has been hashed by Koha, but use the version hashed according to NL
651
            SOAP::Data->name( 'pin'         => $sync->hashed_pin    )->type( 'string' ),
652
            SOAP::Data->name( 'gyldig_til'  => $patron->dateexpiry  )->type( 'string' ),
653
            SOAP::Data->name( 'epost'       => $patron->email       )->type( 'string' ),
654
            SOAP::Data->name( 'tlf_mobil'   => $patron->mobile      )->type( 'string' ),
655
            SOAP::Data->name( 'tlf_hjemme'  => $patron->phone       )->type( 'string' ),
656
            SOAP::Data->name( 'tlf_jobb'    => $patron->phonepro    )->type( 'string' ),
657
        ),
658
    )->type("Laaner");
659
660
    return $soap_patron;
661
662
}
663
664
=head1 EXPORT
665
666
None by default.
667
668
=head1 AUTHOR
669
670
Magnus Enger <digitalutvikling@gmail.com>
671
672
=cut
673
674
1;
675
676
__END__
(-)a/Koha/Patron.pm (-50 lines)
Lines 23-29 use Modern::Perl; Link Here
23
use Carp;
23
use Carp;
24
use List::MoreUtils qw( uniq );
24
use List::MoreUtils qw( uniq );
25
use JSON qw( to_json );
25
use JSON qw( to_json );
26
use Module::Load::Conditional qw( can_load );
27
use Text::Unaccent qw( unac_string );
26
use Text::Unaccent qw( unac_string );
28
27
29
use C4::Accounts;
28
use C4::Accounts;
Lines 45-54 use Koha::Club::Enrollments; Link Here
45
use Koha::Account;
44
use Koha::Account;
46
use Koha::Subscription::Routinglists;
45
use Koha::Subscription::Routinglists;
47
46
48
if ( ! can_load( modules => { 'Koha::NorwegianPatronDB' => undef } ) ) {
49
   warn "Unable to load Koha::NorwegianPatronDB";
50
}
51
52
use base qw(Koha::Object);
47
use base qw(Koha::Object);
53
48
54
our $RESULTSET_PATRON_ID_MAPPING = {
49
our $RESULTSET_PATRON_ID_MAPPING = {
Lines 245-268 sub store { Link Here
245
240
246
                $self = $self->SUPER::store;
241
                $self = $self->SUPER::store;
247
242
248
                # If NorwegianPatronDBEnable is enabled, we set syncstatus to something that a
249
                # cronjob will use for syncing with NL
250
                if (   C4::Context->preference('NorwegianPatronDBEnable')
251
                    && C4::Context->preference('NorwegianPatronDBEnable') == 1 )
252
                {
253
                    Koha::Database->new->schema->resultset('BorrowerSync')
254
                      ->create(
255
                        {
256
                            'borrowernumber' => $self->borrowernumber,
257
                            'synctype'       => 'norwegianpatrondb',
258
                            'sync'           => 1,
259
                            'syncstatus'     => 'new',
260
                            'hashed_pin' =>
261
                              Koha::NorwegianPatronDB::NLEncryptPIN($self->plain_text_password),
262
                        }
263
                      );
264
                }
265
266
                $self->add_enrolment_fee_if_needed;
243
                $self->add_enrolment_fee_if_needed;
267
244
268
                logaction( "MEMBERS", "CREATE", $self->borrowernumber, "" )
245
                logaction( "MEMBERS", "CREATE", $self->borrowernumber, "" )
Lines 293-320 sub store { Link Here
293
                    $self->add_enrolment_fee_if_needed;
270
                    $self->add_enrolment_fee_if_needed;
294
                }
271
                }
295
272
296
                # If NorwegianPatronDBEnable is enabled, we set syncstatus to something that a
297
                # cronjob will use for syncing with NL
298
                if (   C4::Context->preference('NorwegianPatronDBEnable')
299
                    && C4::Context->preference('NorwegianPatronDBEnable') == 1 )
300
                {
301
                    my $borrowersync = Koha::Database->new->schema->resultset('BorrowerSync')->find({
302
                        'synctype'       => 'norwegianpatrondb',
303
                        'borrowernumber' => $self->borrowernumber,
304
                    });
305
                    # Do not set to "edited" if syncstatus is "new". We need to sync as new before
306
                    # we can sync as changed. And the "new sync" will pick up all changes since
307
                    # the patron was created anyway.
308
                    if ( $borrowersync->syncstatus ne 'new' && $borrowersync->syncstatus ne 'delete' ) {
309
                        $borrowersync->update( { 'syncstatus' => 'edited' } );
310
                    }
311
                    # Set the value of 'sync'
312
                    # FIXME THIS IS BROKEN # $borrowersync->update( { 'sync' => $data{'sync'} } );
313
314
                    # Try to do the live sync
315
                    Koha::NorwegianPatronDB::NLSync({ 'borrowernumber' => $self->borrowernumber });
316
                }
317
318
                my $borrowers_log = C4::Context->preference("BorrowersLog");
273
                my $borrowers_log = C4::Context->preference("BorrowersLog");
319
                my $previous_cardnumber = $self_from_storage->cardnumber;
274
                my $previous_cardnumber = $self_from_storage->cardnumber;
320
                if ($borrowers_log
275
                if ($borrowers_log
Lines 685-695 sub update_password { Link Here
685
640
686
    return 0 if $password eq '****' or $password eq '';
641
    return 0 if $password eq '****' or $password eq '';
687
642
688
    if ( C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
689
        # Update the hashed PIN in borrower_sync.hashed_pin, before Koha hashes it
690
        Koha::NorwegianPatronDB::NLUpdateHashedPIN( $self->borrowernumber, $password );
691
    }
692
693
    my $digest = Koha::AuthUtils::hash_password($password);
643
    my $digest = Koha::AuthUtils::hash_password($password);
694
    $self->update(
644
    $self->update(
695
        {
645
        {
(-)a/installer/data/mysql/kohastructure.sql (-18 lines)
Lines 1696-1719 CREATE TABLE borrower_debarments ( -- tracks restrictions on the patron's record Link Here
1696
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
1696
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
1697
1697
1698
--
1698
--
1699
-- Table structure for table borrower_sync
1700
--
1701
1702
DROP TABLE IF EXISTS `borrower_sync`;
1703
CREATE TABLE borrower_sync (
1704
  borrowersyncid int(11) NOT NULL AUTO_INCREMENT, -- Primary key, unique identifier
1705
  borrowernumber int(11) NOT NULL, -- Connects data about synchronisations to a borrower
1706
  synctype varchar(32) NOT NULL, -- There could potentially be more than one kind of syncing going on, a text string here can be used to tell them apart. E.g.: The Norwegian national patron database uses 'norwegianpatrondb' in this column
1707
  sync tinyint(1) NOT NULL DEFAULT '0', -- A boolean (1/0) for turning syncing off and on for individual borrowers
1708
  syncstatus varchar(10) DEFAULT NULL, -- The sync status for any given borrower. Could be text strings like 'new', 'edited', 'synced' etc. The values used here will depend on the actual syncing being done.
1709
  lastsync varchar(50) DEFAULT NULL, -- Date of the last successfull sync. The date format might be different depending on the service that is being used, so no special date format is being enforced here.
1710
  hashed_pin varchar(64) DEFAULT NULL, -- Perhaps specific to The Norwegian national patron database, this column holds a hashed PIN code
1711
  PRIMARY KEY (borrowersyncid),
1712
  KEY borrowernumber (borrowernumber),
1713
  CONSTRAINT borrower_sync_ibfk_1 FOREIGN KEY (borrowernumber) REFERENCES borrowers (borrowernumber) ON DELETE CASCADE ON UPDATE CASCADE
1714
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
1715
1716
--
1717
-- Table structure for table api_keys
1699
-- Table structure for table api_keys
1718
--
1700
--
1719
1701
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/members-toolbar.inc (-8 / +1 lines)
Lines 4-10 Link Here
4
[% USE Branches %]
4
[% USE Branches %]
5
[% USE Categories %]
5
[% USE Categories %]
6
[% USE AuthorisedValues %]
6
[% USE AuthorisedValues %]
7
[% SET NorwegianPatronDBEnable = Koha.Preference( 'NorwegianPatronDBEnable' ) %]
8
<div id="toolbar" class="btn-toolbar">
7
<div id="toolbar" class="btn-toolbar">
9
    [% IF CAN_user_borrowers_edit_borrowers %]
8
    [% IF CAN_user_borrowers_edit_borrowers %]
10
        [% IF ( guarantor ) %]
9
        [% IF ( guarantor ) %]
Lines 72-84 Link Here
72
                [% END %]
71
                [% END %]
73
72
74
                [% IF CAN_user_borrowers_edit_borrowers %]
73
                [% IF CAN_user_borrowers_edit_borrowers %]
75
                    [% IF ( NorwegianPatronDBEnable == 1 ) %]
74
                    <li><a id="deletepatron" href="#">Delete</a></li>
76
                        <li><a id="deletepatronlocal" href="#">Delete local</a></li>
77
                        <li><a id="deletepatronremote" href="#">Delete remote</a></li>
78
                        <li><a id="deletepatronboth" href="#">Delete local and remote</a></li>
79
                    [% ELSE %]
80
                        <li><a id="deletepatron" href="#">Delete</a></li>
81
                    [% END %]
82
                [% ELSE %]
75
                [% ELSE %]
83
                    <li class="disabled"><a data-toggle="tooltip" data-placement="left" title="You are not authorized to delete patrons" id="deletepatron" href="#">Delete</a></li>
76
                    <li class="disabled"><a data-toggle="tooltip" data-placement="left" title="You are not authorized to delete patrons" id="deletepatron" href="#">Delete</a></li>
84
                [% END %]
77
                [% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/nl-search-form.tt (-9 lines)
Lines 1-9 Link Here
1
<form method="POST" action="nl-search.pl" autocomplete="off"> <!-- This form will be used for things like social security numbers, so it makes sense not to remember them. -->
2
<input type="hidden" name="op" value="search" />
3
    <fieldset>
4
        <legend>[% nl_search_form_title | html %]</legend>
5
        <label for="q">Social security or card number: </label>
6
        <input type="text" name="q" value="[% q | html %]">
7
        <input type="submit" value="Search">
8
    </fieldset>
9
</form>
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/str/members-menu.inc (-1 lines)
Lines 9-15 Link Here
9
    var CAN_user_borrowers_edit_borrowers = "[% CAN_user_borrowers_edit_borrowers | html %]";
9
    var CAN_user_borrowers_edit_borrowers = "[% CAN_user_borrowers_edit_borrowers | html %]";
10
10
11
    var dateformat = "[% Koha.Preference('dateformat') | html %]";
11
    var dateformat = "[% Koha.Preference('dateformat') | html %]";
12
    var NorwegianPatronDBEnable = "[% Koha.Preference('NorwegianPatronDBEnable') | html %]";
13
12
14
    var borrowernumber;
13
    var borrowernumber;
15
    var number_of_adult_categories = 0;
14
    var number_of_adult_categories = 0;
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/members/deletemem.tt (-6 lines)
Lines 49-60 Link Here
49
            </form>
49
            </form>
50
        </div>
50
        </div>
51
    [% END %]
51
    [% END %]
52
    [% IF ( keeplocal ) %]
53
        <div class="dialog message">
54
        <h3>Remote record deleted, local record kept</h3>
55
        <p>Patron was marked for deletion from Norwegian national patron database, but the local record was kept.</p>
56
        </div>
57
    [% END %]
58
</div>
52
</div>
59
</div>
53
</div>
60
54
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/members/member.tt (-5 lines)
Lines 28-38 Link Here
28
            </div>
28
            </div>
29
          [% END %]
29
          [% END %]
30
30
31
          [% IF Koha.Preference( 'NorwegianPatronDBEnable' ) == 1 %]
32
            [% SET nl_search_form_title='Search the Norwegian national patron database' %]
33
            [% INCLUDE 'nl-search-form.tt' %]
34
          [% END %]
35
36
          [% INCLUDE 'patron-toolbar.inc' %]
31
          [% INCLUDE 'patron-toolbar.inc' %]
37
          [% INCLUDE 'noadd-warnings.inc' %]
32
          [% INCLUDE 'noadd-warnings.inc' %]
38
33
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt (-12 lines)
Lines 635-652 Link Here
635
    [% IF ( mandatorysort2 ) %]<span class="required">Required</span>[% END %]
635
    [% IF ( mandatorysort2 ) %]<span class="required">Required</span>[% END %]
636
    </li>
636
    </li>
637
        [% END %]
637
        [% END %]
638
    [% IF ( Koha.Preference( 'NorwegianPatronDBEnable' ) == 1 ) %]
639
        <li>
640
            <label for="sort2">Sync with the Norwegian national patron database:</label>
641
            [% IF ( sync == 0 ) %]
642
                <input type="radio" id="sync" name="sync" value="1"> Yes
643
                <input type="radio" id="sync" name="sync" value="0" checked> No
644
            [% ELSE %]
645
                <input type="radio" id="sync" name="sync" value="1" checked> Yes
646
                <input type="radio" id="sync" name="sync" value="0"> No
647
            [% END %]
648
        </li>
649
    [% END %]
650
    [% IF ( Koha.Preference('CheckPrevCheckout') == 'softyes' || Koha.Preference('CheckPrevCheckout') == 'softno' ) %]
638
    [% IF ( Koha.Preference('CheckPrevCheckout') == 'softyes' || Koha.Preference('CheckPrevCheckout') == 'softno' ) %]
651
      <li><label for="checkprevcheckout">Check for previous checkouts: </label>
639
      <li><label for="checkprevcheckout">Check for previous checkouts: </label>
652
        <select name="checkprevcheckout" id="checkprevcheckout">
640
        <select name="checkprevcheckout" id="checkprevcheckout">
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt (-26 lines)
Lines 494-525 Link Here
494
                                            </li>
494
                                            </li>
495
                                        [% END %]
495
                                        [% END %]
496
496
497
                                        [% IF Koha.Preference( 'NorwegianPatronDBEnable' ) == 1 %]
498
                                            [% IF ( sync == 1 ) %]
499
                                                <li>
500
                                                    <span class="label">Activate sync: </span>
501
                                                    Yes
502
                                                </li>
503
                                                [% IF ( syncstatus ) %]
504
                                                    <li>
505
                                                        <span class="label">Sync status: </span>
506
                                                        [% syncstatus | html %]
507
                                                    </li>
508
                                                [% END %]
509
                                                [% IF ( lastsync ) %]
510
                                                    <li>
511
                                                        <span class="label">Last sync: </span>
512
                                                        [% lastsync | $KohaDates %]
513
                                                    </li>
514
                                                [% END %]
515
                                            [% ELSE %]
516
                                                <li>
517
                                                    <span class="label">Activate sync: </span>
518
                                                    No
519
                                                </li>
520
                                            [% END %]
521
                                        [% END %]
522
523
                                        [% IF ( Koha.Preference('CheckPrevCheckout') == 'softyes' || Koha.Preference('CheckPrevCheckout') == 'softno' ) %]
497
                                        [% IF ( Koha.Preference('CheckPrevCheckout') == 'softyes' || Koha.Preference('CheckPrevCheckout') == 'softno' ) %]
524
                                            <li>
498
                                            <li>
525
                                                <span class="label">Check previous checkouts: </span>
499
                                                <span class="label">Check previous checkouts: </span>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/members/nl-search.tt (-180 lines)
Lines 1-180 Link Here
1
[% USE raw %]
2
[% USE Asset %]
3
[% USE KohaDates %]
4
[% USE Branches %]
5
[% SET footerjs = 1 %]
6
[% INCLUDE 'doc-head-open.inc' %]
7
<title>Search the Norwegian national patron database</title>
8
[% INCLUDE 'doc-head-close.inc' %]
9
</head>
10
<body id="pat_nl_search" class="pat">
11
[% INCLUDE 'header.inc' %]
12
[% INCLUDE 'patron-search.inc' %]
13
14
<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/members/members-home.pl">Patrons</a>  &rsaquo; Search the Norwegian national patron database</div>
15
16
<div id="doc3" class="yui-t2">
17
    <div id="bd">
18
        <div id="yui-main">
19
            <div class="yui-b">
20
21
                <h1>Search the Norwegian national patron database</h1>
22
23
                [% IF (error) %]
24
                    <div class="dialog alert">
25
                    [% IF ( error.nlenabled == 0 ) %]<p>You need to activate this function with the NorwegianPatronDBEnable system preference in order to use it.</p>[% END %]
26
                    [% IF ( error.endpoint  == 0 ) %]<p>You need to specify an endpoint with the NorwegianPatronDBEndpoint system preference.</p>[% END %]
27
                    [% IF ( error.userpass  == 0 ) %]<p>You need to fill in the NorwegianPatronDBUsername and NorwegianPatronDBPassword system preferences in order to use this function.</p>[% END %]
28
                    [% IF ( error == 'COULD_NOT_ADD_PATRON' ) %]<p>Could not add a new patron.</p>[% END %]
29
                    </div>
30
                [% ELSE %]
31
                    [% SET nl_search_form_title='Search' %]
32
                    [% INCLUDE 'nl-search-form.tt' %]
33
                [% END %]
34
35
                [% IF ( local_result ) %]
36
                    <h3>Existing patrons</h3>
37
                    <ul>
38
                        [% FOREACH patron IN local_result %]
39
                            <li>[% patron.firstname | html %] [% patron.surname | html %] [% patron.cardnumber | html %] |
40
                                <a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% patron.borrowernumber | html %]">Details</a> |
41
                                <a href="/cgi-bin/koha/members/memberentry.pl?op=modify&destination=circ&borrowernumber=[% patron.borrowernumber | html %]">Edit</a> |
42
                                <a href="/cgi-bin/koha/circ/circulation.pl?borrowernumber=[% patron.borrowernumber | html %]">Check out</a>
43
                            </li>
44
                        [% END %]
45
                    </ul>
46
                [% END %]
47
48
                [% IF ( result ) %]
49
50
                    [% IF result.antall_poster_returnert == 0 %]
51
52
                        <div class="dialog alert">
53
                            <p>No results found in the Norwegian national patron database. Message: "[% result.melding | html %]"</p>
54
                        </div>
55
56
                    [% ELSE %]
57
58
                        <h3>Results from the Norwegian national patron database</h3>
59
                        <div class="yui-g">
60
                        <div class="yui-u first">
61
                        [% PROCESS patron_detail p=result.respons_poster.0 %]
62
                        </div>
63
                        [% IF ( result.respons_poster.1 ) %]
64
                            <div class="yui-u">
65
                            [% PROCESS patron_detail p=result.respons_poster.1 %]
66
                            </div>
67
                        [% END %]
68
                        </div>
69
70
                    [% END %]
71
72
                [% END %]
73
74
            </div>
75
        </div>
76
77
        <div class="yui-b">
78
            [% INCLUDE 'circ-menu.inc' %]
79
        </div>
80
    </div>
81
82
[% MACRO jsinclude BLOCK %]
83
    [% INCLUDE 'str/members-menu.inc' %]
84
    [% Asset.js("js/members-menu.js") | $raw %]
85
[% END %]
86
87
[% INCLUDE 'intranet-bottom.inc' %]
88
89
[% BLOCK patron_detail %]
90
<div class="rows">
91
<h4>[% p.navn | html_entity %]</h4>
92
<ol>
93
[% IF ( p.kjonn ) %]<li><span class="label">Gender:</span> [% p.kjonn | html_entity %]</li>[% END %]
94
[% IF ( p.fdato ) %]<li><span class="label">Date of birth:</span> [% p.fdato | html_entity %]</li>[% END %]
95
[% IF ( p.lnr ) %]<li><span class="label">Card number:</span> [% p.lnr | html_entity %]</li>[% END %]
96
[% IF ( p.fnr_hash ) %]<li><span class="label">Social security number hash:</span> [% p.fnr_hash | html_entity %]</li>[% END %]
97
98
[% IF ( p.epost ) %]<li><span class="label">Email:</span> [% p.epost | html_entity %]</li>[% END %]
99
[% IF ( p.epost_sjekk ) %]<li><span class="label">Email check:</span> [% p.epost_sjekk | html_entity %]</li>[% END %]
100
101
[% IF ( p.tlf_mobil ) %]<li><span class="label">Phone - mobile:</span> [% p.tlf_mobil | html_entity %]</li>[% END %]
102
[% IF ( p.tlf_hjemme ) %]<li><span class="label">Phone - home:</span> [% p.tlf_hjemme | html_entity %]</li>[% END %]
103
[% IF ( p.tlf_jobb ) %]<li><span class="label">Phone - work:</span> [% p.tlf_jobb | html_entity %]</li>[% END %]
104
105
[% IF ( p.p_adresse1 ) %]<li><span class="label">Address:</span> [% p.p_adresse1 | html_entity %]</li>[% END %]
106
[% IF ( p.p_adresse2 ) %]<li><span class="label">Address 2:</span> [% p.p_adresse2 | html_entity %]</li>[% END %]
107
[% IF ( p.p_postnr ) %]<li><span class="label">Zip/Postal code:</span> [% p.p_postnr | html_entity %]</li>[% END %]
108
[% IF ( p.p_sted ) %]<li><span class="label">City:</span> [% p.p_sted | html_entity %]</li>[% END %]
109
[% IF ( p.p_land ) %]<li><span class="label">Country:</span> [% p.p_land | html_entity %]</li>[% END %]
110
[% IF ( p.p_sjekk ) %]<li><span class="label">Check:</span> [% p.p_sjekk | html_entity %]</li>[% END %]
111
112
[% IF ( p.m_adresse1 ) %]<li><span class="label">Address:</span> [% p.m_adresse1 | html_entity %]</li>[% END %]
113
[% IF ( p.m_adresse2 ) %]<li><span class="label">Address 2:</span> [% p.m_adresse2 | html_entity %]</li>[% END %]
114
[% IF ( p.m_postnr ) %]<li><span class="label">Zip/Postal code:</span> [% p.m_postnr | html_entity %]</li>[% END %]
115
[% IF ( p.m_sted ) %]<li><span class="label">City:</span> [% p.m_sted | html_entity %]</li>[% END %]
116
[% IF ( p.m_land ) %]<li><span class="label">Country:</span> [% p.m_land | html_entity %]</li>[% END %]
117
[% IF ( p.m_sjek ) %]<li><span class="label">Check:</span> [% p.m_sjekk | html_entity %]</li>[% END %]
118
[% IF ( p.m_gyldig_til ) %]<li><span class="label">Valid until:</span> [% p.m_gyldig_til | html_entity %]</li>[% END %]
119
120
[% IF ( p.pin ) %]<li><span class="label">PIN:</span> [% p.pin | html_entity %]</li>[% END %]
121
[% IF ( p.passord ) %]<li><span class="label">Password:</span> [% p.passord | html_entity %]</li>[% END %]
122
[% IF ( p.feide ) %]<li><span class="label">FEIDE:</span> [% p.feide | html_entity %]</li>[% END %]
123
[% IF ( p.folkeregsjekk_dato ) %]<li><span class="label">Population registry date check:</span> [% p.folkeregsjekk_dato | html_entity %]</li>[% END %]
124
125
[% IF ( p.hjemmebibliotek ) %]<li><span class="label">Home library:</span> [% p.hjemmebibliotek | html_entity %]</li>[% END %]
126
[% IF ( p.opprettet ) %]<li><span class="label">Created:</span> [% p.opprettet | html_entity %]</li>[% END %]
127
[% IF ( p.opprettet_av ) %]<li><span class="label">Created by:</span> [% p.opprettet_av | html_entity %]</li>[% END %]
128
[% IF ( p.sist_endret ) %]<li><span class="label">Last changed:</span> [% p.sist_endret | html_entity %]</li>[% END %]
129
[% IF ( p.sist_endret_av ) %]<li><span class="label">Last changed by:</span> [% p.sist_endret_av | html_entity %]</li>[% END %]
130
[% IF ( p.gyldig_til ) %]<li><span class="label">Valid until:</span> [% p.gyldig_til | html_entity %]</li>[% END %]
131
132
[% IF ( p.prim_kontakt ) %]<li><span class="label">Primary contact:</span> [% p.prim_kontakt | html_entity %]</li>[% END %]
133
</ol>
134
135
<form action="nl-search.pl" method="POST">
136
<input type="hidden" name="op" value="save" />
137
<input type="hidden" name="navn" value="[% p.navn | html_entity %]" />
138
<input type="hidden" name="kjonn" value="[% p.kjonn | html_entity %]" />
139
<input type="hidden" name="fdato" value="[% p.fdato | html_entity %]" />
140
<input type="hidden" name="lnr" value="[% p.lnr | html_entity %]" />
141
<input type="hidden" name="fnr_hash" value="[% p.fnr_hash | html_entity %]" />
142
<input type="hidden" name="p_adresse1" value="[% p.p_adresse1 | html_entity %]" />
143
<input type="hidden" name="p_adresse2" value="[% p.p_adresse2 | html_entity %]" />
144
<input type="hidden" name="p_postnr" value="[% p.p_postnr | html_entity %]" />
145
<input type="hidden" name="p_sted" value="[% p.p_sted | html_entity %]" />
146
<input type="hidden" name="p_land" value="[% p.p_land | html_entity %]" />
147
<input type="hidden" name="p_sjekk" value="[% p.p_sjekk | html_entity %]" />
148
<input type="hidden" name="m_adresse1" value="[% p.m_adresse1 | html_entity %]" />
149
<input type="hidden" name="m_adresse2" value="[% p.m_adresse2 | html_entity %]" />
150
<input type="hidden" name="m_postnr" value="[% p.m_postnr | html_entity %]" />
151
<input type="hidden" name="m_sted" value="[% p.m_sted | html_entity %]" />
152
<input type="hidden" name="m_land" value="[% p.m_land | html_entity %]" />
153
<input type="hidden" name="m_sjekk" value="[% p.m_sjekk | html_entity %]" />
154
<input type="hidden" name="m_gyldig_til" value="[% p.m_gyldig_til | html_entity %]" />
155
<input type="hidden" name="pin" value="[% p.pin | html %]" />
156
<input type="hidden" name="passord" value="[% p.passord | html_entity %]" />
157
<input type="hidden" name="feide" value="[% p.feide | html_entity %]" />
158
<input type="hidden" name="folkeregsjekk_dato" value="[% p.folkeregsjekk_dato | html_entity %]" />
159
<input type="hidden" name="hjemmebibliotek" value="[% p.hjemmebibliotek | html_entity %]" />
160
<input type="hidden" name="opprettet" value="[% p.opprettet | html_entity %]" />
161
<input type="hidden" name="opprettet_av" value="[% p.opprettet_av | html_entity %]" />
162
<input type="hidden" name="sist_endret" value="[% p.sist_endret | html_entity %]" />
163
<input type="hidden" name="sist_endret_av" value="[% p.sist_endret_av | html_entity %]" />
164
<input type="hidden" name="gyldig_til" value="[% p.gyldig_til | html_entity %]" />
165
<input type="hidden" name="epost" value="[% p.epost | html_entity %]" />
166
<input type="hidden" name="epost_sjekk" value="[% p.epost_sjekk | html_entity %]" />
167
<input type="hidden" name="tlf_mobil" value="[% p.tlf_mobil | html_entity %]" />
168
<input type="hidden" name="tlf_hjemme" value="[% p.tlf_hjemme | html_entity %]" />
169
<input type="hidden" name="tlf_jobb" value="[% p.tlf_jobb | html_entity %]" />
170
<input type="hidden" name="prim_kontakt" value="[% p.prim_kontakt | html_entity %]" />
171
<input type="submit" value="Import this patron" />
172
as
173
<select name="categorycode">
174
[% FOREACH c IN categories %]
175
    <option value="[% c.categorycode | html %]">[% c.description | html %]</option>
176
[% END %]
177
</select>
178
</form>
179
</div>
180
[% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/js/members-menu.js (-40 / +4 lines)
Lines 1-4 Link Here
1
/* global borrowernumber advsearch dateformat _ CAN_user_borrowers_edit_borrowers NorwegianPatronDBEnable number_of_adult_categories destination */
1
/* global borrowernumber advsearch dateformat _ CAN_user_borrowers_edit_borrowers number_of_adult_categories destination */
2
2
3
$(document).ready(function(){
3
$(document).ready(function(){
4
    $("#filteraction_off, #filteraction_on").on('click', function(e) {
4
    $("#filteraction_off, #filteraction_on").on('click', function(e) {
Lines 31-57 $(document).ready(function(){ Link Here
31
    });
31
    });
32
32
33
    if( CAN_user_borrowers_edit_borrowers ){
33
    if( CAN_user_borrowers_edit_borrowers ){
34
        if( NorwegianPatronDBEnable == 1 ){
34
        $("#deletepatron").click(function(){
35
            $("#deletepatronlocal").click(function(){
35
            window.location='/cgi-bin/koha/members/deletemem.pl?member=' + borrowernumber;
36
                confirm_local_deletion();
36
        });
37
                $(".btn-group").removeClass("open");
38
                return false;
39
            });
40
            $("#deletepatronremote").click(function(){
41
                confirm_remote_deletion();
42
                $(".btn-group").removeClass("open");
43
                return false;
44
            });
45
            $("#deletepatronboth").click(function(){
46
                confirm_both_deletion();
47
                $(".btn-group").removeClass("open");
48
                return false;
49
            });
50
        } else {
51
            $("#deletepatron").click(function(){
52
                window.location='/cgi-bin/koha/members/deletemem.pl?member=' + borrowernumber;
53
            });
54
        }
55
        $("#renewpatron").click(function(){
37
        $("#renewpatron").click(function(){
56
            confirm_reregistration();
38
            confirm_reregistration();
57
            $(".btn-group").removeClass("open");
39
            $(".btn-group").removeClass("open");
Lines 101-124 $(document).ready(function(){ Link Here
101
        $("#borrower_message").val( $(this).val() );
83
        $("#borrower_message").val( $(this).val() );
102
    });
84
    });
103
});
85
});
104
function confirm_local_deletion() {
105
    var is_confirmed = window.confirm(_("Are you sure you want to delete this patron from the local database? This cannot be undone."));
106
    if (is_confirmed) {
107
        window.location='/cgi-bin/koha/members/deletemem.pl?member=' + borrowernumber + '&deletelocal=true&deleteremote=false';
108
    }
109
}
110
function confirm_remote_deletion() {
111
    var is_confirmed = window.confirm(_("Are you sure you want to delete this patron from the Norwegian national patron database? This cannot be undone."));
112
    if (is_confirmed) {
113
        window.location='/cgi-bin/koha/members/deletemem.pl?member=' + borrowernumber + '&deletelocal=false&deleteremote=true';
114
    }
115
}
116
function confirm_both_deletion() {
117
    var is_confirmed = window.confirm(_("Are you sure you want to delete this patron both from the local database and from the Norwegian national patron database? This cannot be undone."));
118
    if (is_confirmed) {
119
        window.location='/cgi-bin/koha/members/deletemem.pl?member=' + borrowernumber + '&deletelocal=true&deleteremote=true';
120
    }
121
}
122
86
123
function confirm_updatechild() {
87
function confirm_updatechild() {
124
    var is_confirmed = window.confirm(_("Are you sure you want to update this child to an Adult category?  This cannot be undone."));
88
    var is_confirmed = window.confirm(_("Are you sure you want to update this child to an Adult category?  This cannot be undone."));
(-)a/members/deletemem.pl (-24 / +3 lines)
Lines 28-42 use C4::Context; Link Here
28
use C4::Output;
28
use C4::Output;
29
use C4::Auth;
29
use C4::Auth;
30
use C4::Members;
30
use C4::Members;
31
use Module::Load;
32
use Koha::Patrons;
31
use Koha::Patrons;
33
use Koha::Token;
32
use Koha::Token;
34
use Koha::Patron::Categories;
33
use Koha::Patron::Categories;
35
34
36
if ( C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
37
    load Koha::NorwegianPatronDB, qw( NLMarkForDeletion NLSync );
38
}
39
40
my $input = new CGI;
35
my $input = new CGI;
41
36
42
my ($template, $loggedinuser, $cookie)
37
my ($template, $loggedinuser, $cookie)
Lines 61-80 my $logged_in_user = Koha::Patrons->find( $loggedinuser ) or die "Not logged in" Link Here
61
my $patron         = Koha::Patrons->find( $member );
56
my $patron         = Koha::Patrons->find( $member );
62
output_and_exit_if_error( $input, $cookie, $template, { module => 'members', logged_in_user => $logged_in_user, current_patron => $patron } );
57
output_and_exit_if_error( $input, $cookie, $template, { module => 'members', logged_in_user => $logged_in_user, current_patron => $patron } );
63
58
64
# Handle deletion from the Norwegian national patron database, if it is enabled
65
# If the "deletelocal" parameter is set to "false", the regular deletion will be
66
# short circuited, and only a deletion from the national database can be carried
67
# out. If "deletelocal" is set to "true", or not set to anything normal
68
# deletion will be done.
69
my $deletelocal  = $input->param('deletelocal')  eq 'false' ? 0 : 1; # Deleting locally is the default
70
if ( C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
71
    if ( $input->param('deleteremote') eq 'true' ) {
72
        # Mark for deletion, then try a live sync
73
        NLMarkForDeletion( $member );
74
        NLSync({ 'borrowernumber' => $member });
75
    }
76
}
77
78
my $charges = $patron->account->non_issues_charges;
59
my $charges = $patron->account->non_issues_charges;
79
my $countissues = $patron->checkouts->count;
60
my $countissues = $patron->checkouts->count;
80
my $userenv = C4::Context->userenv;
61
my $userenv = C4::Context->userenv;
Lines 104-110 if (C4::Context->preference("IndependentBranches")) { Link Here
104
my $op = $input->param('op') || 'delete_confirm';
85
my $op = $input->param('op') || 'delete_confirm';
105
my $dbh = C4::Context->dbh;
86
my $dbh = C4::Context->dbh;
106
my $is_guarantor = $dbh->selectrow_array("SELECT COUNT(*) FROM borrowers WHERE guarantorid=?", undef, $member);
87
my $is_guarantor = $dbh->selectrow_array("SELECT COUNT(*) FROM borrowers WHERE guarantorid=?", undef, $member);
107
if ( $op eq 'delete_confirm' or $countissues > 0 or $charges or $is_guarantor or $deletelocal == 0) {
88
if ( $op eq 'delete_confirm' or $countissues > 0 or $charges or $is_guarantor ) {
108
89
109
    $template->param(
90
    $template->param(
110
        patron => $patron,
91
        patron => $patron,
Lines 118-128 if ( $op eq 'delete_confirm' or $countissues > 0 or $charges or $is_guarantor or Link Here
118
    if ($is_guarantor) {
99
    if ($is_guarantor) {
119
        $template->param(guarantees => 1);
100
        $template->param(guarantees => 1);
120
    }
101
    }
121
    if ($deletelocal == 0) {
102
122
        $template->param(keeplocal => 1);
123
    }
124
    # This is silly written but reflect the same conditions as above
103
    # This is silly written but reflect the same conditions as above
125
    if ( not $countissues > 0 and not $charges and not $is_guarantor and not $deletelocal == 0 ) {
104
    if ( not $countissues > 0 and not $charges and not $is_guarantor ) {
126
        $template->param(
105
        $template->param(
127
            op         => 'delete_confirm',
106
            op         => 'delete_confirm',
128
            csrf_token => Koha::Token->new->generate_csrf({ session_id => scalar $input->cookie('CGISESSID') }),
107
            csrf_token => Koha::Token->new->generate_csrf({ session_id => scalar $input->cookie('CGISESSID') }),
(-)a/members/memberentry.pl (-17 lines)
Lines 48-57 use Koha::Patron::HouseboundRole; Link Here
48
use Koha::Patron::HouseboundRoles;
48
use Koha::Patron::HouseboundRoles;
49
use Koha::Token;
49
use Koha::Token;
50
use Email::Valid;
50
use Email::Valid;
51
use Module::Load;
52
if ( C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
53
    load Koha::NorwegianPatronDB, qw( NLGetSyncDataFromBorrowernumber );
54
}
55
use Koha::SMS::Providers;
51
use Koha::SMS::Providers;
56
52
57
use vars qw($debug);
53
use vars qw($debug);
Lines 483-492 if ((!$nok) and $nodouble and ($op eq 'insert' or $op eq 'save')){ Link Here
483
        if (C4::Context->preference('EnhancedMessagingPreferences') and $input->param('setting_messaging_prefs')) {
479
        if (C4::Context->preference('EnhancedMessagingPreferences') and $input->param('setting_messaging_prefs')) {
484
            C4::Form::MessagingPreferences::handle_form_action($input, { borrowernumber => $borrowernumber }, $template, 1, $newdata{'categorycode'});
480
            C4::Form::MessagingPreferences::handle_form_action($input, { borrowernumber => $borrowernumber }, $template, 1, $newdata{'categorycode'});
485
        }
481
        }
486
        # Try to do the live sync with the Norwegian national patron database, if it is enabled
487
        if ( exists $data{'borrowernumber'} && C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
488
            NLSync({ 'borrowernumber' => $borrowernumber });
489
        }
490
482
491
        # Create HouseboundRole if necessary.
483
        # Create HouseboundRole if necessary.
492
        # Borrower did not exist, so HouseboundRole *cannot* yet exist.
484
        # Borrower did not exist, so HouseboundRole *cannot* yet exist.
Lines 599-613 if ($op eq "modify") { Link Here
599
    if ( $step == 4 ) {
591
    if ( $step == 4 ) {
600
        $template->param( categorycode => $borrower_data->{'categorycode'} );
592
        $template->param( categorycode => $borrower_data->{'categorycode'} );
601
    }
593
    }
602
    # Add sync data to the user data
603
    if ( C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
604
        my $sync = NLGetSyncDataFromBorrowernumber( $borrowernumber );
605
        if ( $sync ) {
606
            $template->param(
607
                sync => $sync->sync,
608
            );
609
        }
610
    }
611
}
594
}
612
if ( $op eq "duplicate" ) {
595
if ( $op eq "duplicate" ) {
613
    $template->param( updtype => 'I' );
596
    $template->param( updtype => 'I' );
(-)a/members/moremember.pl (-14 lines)
Lines 55-64 use Koha::AuthorisedValues; Link Here
55
use Koha::CsvProfiles;
55
use Koha::CsvProfiles;
56
use Koha::Patron::Debarments qw(GetDebarments);
56
use Koha::Patron::Debarments qw(GetDebarments);
57
use Koha::Patron::Messages;
57
use Koha::Patron::Messages;
58
use Module::Load;
59
if ( C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
60
    load Koha::NorwegianPatronDB, qw( NLGetSyncDataFromBorrowernumber );
61
}
62
#use Smart::Comments;
58
#use Smart::Comments;
63
#use Data::Dumper;
59
#use Data::Dumper;
64
use DateTime;
60
use DateTime;
Lines 241-256 if ($borrowernumber) { Link Here
241
          ->count( { borrowernumber => $borrowernumber } ) );
237
          ->count( { borrowernumber => $borrowernumber } ) );
242
}
238
}
243
239
244
# Add sync data to the user data
245
if ( C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
246
    my $sync = NLGetSyncDataFromBorrowernumber( $borrowernumber );
247
    if ( $sync ) {
248
        $data->{'sync'}       = $sync->sync;
249
        $data->{'syncstatus'} = $sync->syncstatus;
250
        $data->{'lastsync'}   = $sync->lastsync;
251
    }
252
}
253
254
# Generate CSRF token for upload and delete image buttons
240
# Generate CSRF token for upload and delete image buttons
255
$template->param(
241
$template->param(
256
    csrf_token => Koha::Token->new->generate_csrf({ session_id => $input->cookie('CGISESSID'),}),
242
    csrf_token => Koha::Token->new->generate_csrf({ session_id => $input->cookie('CGISESSID'),}),
(-)a/members/nl-search.pl (-167 lines)
Lines 1-167 Link Here
1
#!/usr/bin/perl
2
3
# Copyright 2013 Oslo Public Library
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
=head1 NAME
21
22
nl-search.pl - Script for searching the Norwegian national patron database
23
24
=head1 DESCRIPTION
25
26
This script will search the Norwegian national patron database, and let staff
27
import patrons from the natial database into the local database.
28
29
In order to use this, a username/password from the Norwegian national database
30
of libraries ("Base Bibliotek") is needed. A special key is also needed, in
31
order to decrypt and encrypt PIN-codes/passwords.
32
33
See http://www.lanekortet.no/ for more information (in Norwegian).
34
35
=cut
36
37
use Modern::Perl;
38
use CGI;
39
use C4::Auth;
40
use C4::Context;
41
use C4::Output;
42
use C4::Members::Attributes qw( SetBorrowerAttributes );
43
use C4::Utils::DataTables::Members;
44
use Koha::NorwegianPatronDB qw( NLCheckSysprefs NLSearch NLDecodePin NLGetFirstname NLGetSurname NLSync );
45
use Koha::Database;
46
use Koha::DateUtils;
47
use Koha::Patrons;
48
use Koha::Patron::Categories;
49
50
my $cgi = CGI->new;
51
my $dbh = C4::Context->dbh;
52
my $op  = $cgi->param('op');
53
54
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
55
    {
56
        template_name   => "members/nl-search.tt",
57
        query           => $cgi,
58
        type            => "intranet",
59
        authnotrequired => 0,
60
        flagsrequired   => { borrowers => 'edit_borrowers' },
61
        debug           => 1,
62
    }
63
);
64
65
my $userenv = C4::Context->userenv;
66
67
# Check sysprefs
68
my $check_result = NLCheckSysprefs();
69
if ( $check_result->{'error'} == 1 ) {
70
    $template->param( 'error' => $check_result );
71
    output_html_with_http_headers $cgi, $cookie, $template->output;
72
    exit 0;
73
}
74
75
if ( $op && $op eq 'search' ) {
76
77
    # Get the string we are searching for
78
    my $identifier = $cgi->param('q');
79
    if ( $identifier ) {
80
        # Local search
81
        my $local_results = C4::Utils::DataTables::Members::search(
82
            {
83
                searchmember => $identifier,
84
                dt_params => { iDisplayLength => -1 },
85
            }
86
        )->{patrons};
87
        $template->param( 'local_result' => $local_results );
88
        # Search NL, unless we got at least one hit and further searching is
89
        # disabled
90
        if ( scalar @{ $local_results } == 0 || C4::Context->preference("NorwegianPatronDBSearchNLAfterLocalHit") == 1 ) {
91
            # TODO Check the format of the identifier before searching NL
92
            my $result = NLSearch( $identifier );
93
            unless ($result->fault) {
94
                my $r = $result->result();
95
                my $categories = Koha::Patron::Categories->search_limited;
96
                $template->param(
97
                    'result'     => $r,
98
                    'categories' => $categories,
99
                );
100
            } else {
101
                $template->param( 'error' => join ', ', $result->faultcode, $result->faultstring, $result->faultdetail );
102
            }
103
        }
104
        $template->param( 'q' => $identifier );
105
    }
106
107
} elsif ( $op && $op eq 'save' ) {
108
109
    # This is where we map from fields in NL to fields in Koha
110
    my %borrower = (
111
        'surname'      => NLGetSurname( $cgi->param('navn') ),
112
        'firstname'    => NLGetFirstname( $cgi->param('navn') ),
113
        'sex'          => scalar $cgi->param('kjonn'),
114
        'dateofbirth'  => scalar $cgi->param('fdato'),
115
        'cardnumber'   => scalar $cgi->param('lnr'),
116
        'userid'       => scalar $cgi->param('lnr'),
117
        'address'      => scalar $cgi->param('p_adresse1'),
118
        'address2'     => scalar $cgi->param('p_adresse2'),
119
        'zipcode'      => scalar $cgi->param('p_postnr'),
120
        'city'         => scalar $cgi->param('p_sted'),
121
        'country'      => scalar $cgi->param('p_land'),
122
        'B_address'    => scalar $cgi->param('m_adresse1'),
123
        'B_address2'   => scalar $cgi->param('m_adresse2'),
124
        'B_zipcode'    => scalar $cgi->param('m_postnr'),
125
        'B_city'       => scalar $cgi->param('m_sted'),
126
        'B_country'    => scalar $cgi->param('m_land'),
127
        'password'     => NLDecodePin( $cgi->param('pin') ),
128
        'dateexpiry'   => scalar $cgi->param('gyldig_til'),
129
        'email'        => scalar $cgi->param('epost'),
130
        'mobile'       => scalar $cgi->param('tlf_mobil'),
131
        'phone'        => scalar $cgi->param('tlf_hjemme'),
132
        'phonepro'     => scalar $cgi->param('tlf_jobb'),
133
        'branchcode'   => $userenv->{'branch'},
134
        'categorycode' => scalar $cgi->param('categorycode'),
135
    );
136
    # Add the new patron
137
    my $patron = eval { Koha::Patron->new(\%borrower)->store; };
138
    unless ( $@) {
139
        my $borrowernumber = $patron->borrowernumber;
140
        # Add extended patron attributes
141
        SetBorrowerAttributes($borrowernumber, [
142
            { code => 'fnr', value => scalar $cgi->param('fnr_hash') },
143
        ], 'no_branch_limit' );
144
        # Override the default sync data created by Koha::Patron->store
145
        my $borrowersync = Koha::Database->new->schema->resultset('BorrowerSync')->find({
146
            'synctype'       => 'norwegianpatrondb',
147
            'borrowernumber' => $borrowernumber,
148
        });
149
        $borrowersync->update({ 'syncstatus', 'synced' });
150
        $borrowersync->update({ 'lastsync',   $cgi->param('sist_endret') });
151
        $borrowersync->update({ 'hashed_pin', $cgi->param('pin') });
152
        # Try to sync in real time. If this fails it will be picked up by the cronjob
153
        NLSync({ 'borrowernumber' => $borrowernumber });
154
        # Redirect to the edit screen
155
        print $cgi->redirect( "/cgi-bin/koha/members/memberentry.pl?op=modify&destination=circ&borrowernumber=$borrowernumber" );
156
    } else {
157
        $template->param( 'error' => 'COULD_NOT_ADD_PATRON' );
158
    }
159
}
160
161
output_html_with_http_headers $cgi, $cookie, $template->output;
162
163
=head1 AUTHOR
164
165
Magnus Enger <digitalutvikling@gmail.com>
166
167
=cut
(-)a/misc/cronjobs/nl-sync-from-koha.pl (-202 lines)
Lines 1-202 Link Here
1
#!/usr/bin/perl
2
3
# Copyright 2014 Oslo Public Library
4
5
=head1 NAME
6
7
nl-sync-from-koha.pl - Sync patrons from Koha to the Norwegian national patron database (NL).
8
9
=head1 SYNOPSIS
10
11
 perl nl-sync-from-koha.pl -v --run
12
13
=cut
14
15
use Koha::NorwegianPatronDB qw( NLCheckSysprefs NLSync );
16
use Koha::Database;
17
use Getopt::Long;
18
use Pod::Usage;
19
use Modern::Perl;
20
21
# Get options
22
my ( $run, $verbose, $debug ) = get_options();
23
24
=head1 ACTIONS
25
26
=head2
27
28
Find local patrons that have been changed and need to be sent upstream to NL.
29
These patrons will be distinguished by two borrower attributes:
30
31
=over 4
32
33
=item * The "nlstatus" attribute will have a value of "needsync". (Which means
34
that the patron has been changed in Koha, but not yet successfully synced
35
upstream.)
36
37
=item * The "nlsync" attribute will have a value of 1. (Which means that this
38
patron has accepted to be synced with NL, as opposed to a value of 0 which
39
would indicate that the patron has asked not to be synced with NL.)
40
41
=back
42
43
=head1 STEPS
44
45
This script performs the following steps:
46
47
=head2 Check sysprefs
48
49
Check that the necessary sysprefs are set before proceeding.
50
51
=cut
52
53
my $check_result = NLCheckSysprefs();
54
if ( $check_result->{'error'} == 1 ) {
55
    if ( $check_result->{'nlenabled'} == 0 ) { say "* Please activate this function with the NorwegianPatronDBEnable system preference." };
56
    if ( $check_result->{'endpoint'}  == 0 ) { say "* Please specify an endpoint with the NorwegianPatronDBEndpoint system preference." };
57
    if ( $check_result->{'userpass'}  == 0 ) { say "* Please fill in the NorwegianPatronDBUsername and NorwegianPatronDBPassword system preferences." };
58
    exit 0;
59
}
60
61
unless ( $run ) {
62
    say "* You have not specified --run, no real syncing will be done.";
63
}
64
65
=head2 Find patrons that need to be synced
66
67
Patrons with either of these statuses:
68
69
=over 4
70
71
=item * edited
72
73
=item * new
74
75
=item * deleted
76
77
=back
78
79
=cut
80
81
my @needs_sync = Koha::Database->new->schema->resultset('BorrowerSync')->search({
82
    -and => [
83
      sync     => 1,
84
      synctype => 'norwegianpatrondb',
85
      -or => [
86
        syncstatus => 'edited',
87
        syncstatus => 'new',
88
        syncstatus => 'delete',
89
      ],
90
    ],
91
});
92
93
=head2 Do the actual sync
94
95
Data is synced to NL with NLSync.
96
97
=cut
98
99
my $sync_success = 0;
100
my $sync_failed  = 0;
101
foreach my $borrower ( @needs_sync ) {
102
    my $cardnumber = $borrower->borrowernumber->cardnumber;
103
    my $firstname  = $borrower->borrowernumber->firstname;
104
    my $surname    = $borrower->borrowernumber->surname;
105
    my $syncstatus = $borrower->syncstatus;
106
    say "*** Syncing patron: $cardnumber - $firstname $surname ($syncstatus)" if $verbose;
107
    if ( $run ) {
108
        my $response = NLSync({ 'patron' => $borrower->borrowernumber });
109
        if ( $response ) {
110
            my $result = $response->result;
111
            if ( $result->{'status'} && $result->{'status'} == 1 ) {
112
                $sync_success++;
113
            } else {
114
                $sync_failed++;
115
            }
116
            if ( $result->{'melding'} && $verbose ) {
117
                say $result->{'melding'};
118
            }
119
        }
120
    }
121
}
122
123
=head2 Summarize if verbose mode is enabled
124
125
Specify -v on the command line to get a summary of the syncing operations.
126
127
=cut
128
129
if ( $verbose ) {
130
    say "-----------------------------";
131
    say "Sync succeeded: $sync_success";
132
    say "Sync failed   : $sync_failed";
133
}
134
135
=head1 OPTIONS
136
137
=over 4
138
139
=item B<-r, --run>
140
141
Actually carry out syncing operations. Without this option, the script will
142
only report what it would have done, but not change any data, locally or
143
remotely.
144
145
=item B<-v --verbose>
146
147
Report on the progress of the script.
148
149
=item B<-d --debug>
150
151
Even more output.
152
153
=item B<-h, -?, --help>
154
155
Prints this help message and exits.
156
157
=back
158
159
=cut
160
161
sub get_options {
162
163
  # Options
164
  my $run     = '',
165
  my $verbose = '';
166
  my $debug   = '';
167
  my $help    = '';
168
169
  GetOptions (
170
    'r|run'     => \$run,
171
    'v|verbose' => \$verbose,
172
    'd|debug'   => \$debug,
173
    'h|?|help'  => \$help
174
  );
175
176
  pod2usage( -exitval => 0 ) if $help;
177
178
  return ( $run, $verbose, $debug );
179
180
}
181
182
=head1 AUTHOR
183
184
Magnus Enger <digitalutvikling@gmail.com>
185
186
=head1 COPYRIGHT
187
188
Copyright 2014 Oslo Public Library
189
190
=head1 LICENSE
191
192
This file is part of Koha.
193
194
Koha is free software; you can redistribute it and/or modify it under the terms
195
of the GNU General Public License as published by the Free Software Foundation;
196
either version 3 of the License, or (at your option) any later version.
197
198
You should have received a copy of the GNU General Public License along with
199
Koha; if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
200
Fifth Floor, Boston, MA 02110-1301 USA.
201
202
=cut
(-)a/misc/cronjobs/nl-sync-to-koha.pl (-186 lines)
Lines 1-186 Link Here
1
#!/usr/bin/perl
2
3
# Copyright 2014 Oslo Public Library
4
5
=head1 NAME
6
7
nl-sync-to-koha.pl - Sync patrons from the Norwegian national patron database (NL) to Koha.
8
9
=head1 SYNOPSIS
10
11
 perl nl-sync-to-koha.pl -v --run
12
13
=cut
14
15
use C4::Members::Attributes qw( UpdateBorrowerAttribute );
16
use Koha::NorwegianPatronDB qw( NLCheckSysprefs NLGetChanged );
17
use Koha::Patrons;
18
use Koha::Database;
19
use Getopt::Long;
20
use Pod::Usage;
21
use Modern::Perl;
22
23
# Get options
24
my ( $run, $from, $verbose, $debug ) = get_options();
25
26
my $check_result = NLCheckSysprefs();
27
if ( $check_result->{'error'} == 1 ) {
28
    if ( $check_result->{'nlenabled'} == 0 ) { say "* Please activate this function with the NorwegianPatronDBEnable system preference." };
29
    if ( $check_result->{'endpoint'}  == 0 ) { say "* Please specify an endpoint with the NorwegianPatronDBEndpoint system preference." };
30
    if ( $check_result->{'userpass'}  == 0 ) { say "* Please fill in the NorwegianPatronDBUsername and NorwegianPatronDBPassword system preferences." };
31
    exit 0;
32
}
33
34
unless ( $run ) {
35
    say "* You have not specified --run, no real syncing will be done.";
36
}
37
38
# Do the sync
39
my $sync_success         = 0;
40
my $sync_failed          = 0;
41
my $skipped_local_change = 0;
42
43
# Get the borrowers that have been changed
44
my $result = NLGetChanged( $from );
45
46
if ( $verbose ) {
47
    say 'Number of records: ' . $result->{'antall_poster_returnert'};
48
    say 'Number of hits:    ' . $result->{'antall_treff'};
49
    say 'Message:           ' . $result->{'melding'};
50
    say 'Status:            ' . $result->{'status'};
51
    say 'Server time:       ' . $result->{'server_tid'};
52
    say "-----------------------------";
53
}
54
55
# Loop through the patrons
56
foreach my $patron ( @{ $result->{'kohapatrons'} } ) {
57
    if ( $verbose ) {
58
        if ( $patron->{'surname'} ) {
59
            say "*** Name: " . $patron->{'surname'};
60
        } else {
61
            say "*** No name";
62
        }
63
        say 'Created by:     ' . $patron->{'_extra'}->{'created_by'};
64
        say 'Last change by: ' . $patron->{'_extra'}->{'last_change_by'};
65
    }
66
    # Only sync in changes made by other libraries
67
    if ( C4::Context->preference("NorwegianPatronDBUsername") ne $patron->{'_extra'}->{'last_change_by'} ) {
68
        # Make a copy of the data in the hashref and store it as a hash
69
        my %clean_patron = %$patron;
70
        # Delete the extra data from the copy of the hashref
71
        delete $clean_patron{'_extra'};
72
        # Find the borrowernumber based on cardnumber
73
        my $stored_patron = Koha::Patrons->find({ cardnumber => $patron->{cardnumber} });
74
        my $borrowernumber = $stored_patron->borrowernumber;
75
        if ( $run ) {
76
            # FIXME Exceptions must be caught here
77
            if ( $stored_patron->set(\%clean_patron)->store ) {
78
                # Get the sync object
79
                my $sync = Koha::Database->new->schema->resultset('BorrowerSync')->find({
80
                    'synctype'       => 'norwegianpatrondb',
81
                    'borrowernumber' => $borrowernumber,
82
                });
83
                # Update the syncstatus to 'synced'
84
                $sync->update( { 'syncstatus' => 'synced' } );
85
                # Update the 'synclast' attribute with the "server time" ("server_tid") returned by the method
86
                $sync->update( { 'lastsync' => $result->{'result'}->{'server_tid'} } );
87
                # Save social security number as attribute
88
                UpdateBorrowerAttribute(
89
                    $borrowernumber,
90
                    { code => 'fnr', attribute => $patron->{'_extra'}->{'socsec'} },
91
                );
92
                $sync_success++;
93
            } else {
94
                $sync_failed++;
95
            }
96
        }
97
    } else {
98
        say "Skipped, local change" if $verbose;
99
        $skipped_local_change++;
100
    }
101
}
102
103
if ( $verbose ) {
104
    say "-----------------------------";
105
    say "Sync succeeded:       $sync_success";
106
    say "Sync failed   :       $sync_failed";
107
    say "Skipped local change: $skipped_local_change";
108
}
109
110
=head1 OPTIONS
111
112
=over 4
113
114
=item B<-r, --run>
115
116
Actually carry out syncing operations. Without this option, the script will
117
only report what it would have done, but not change any data, locally or
118
remotely.
119
120
=item B<-v --verbose>
121
122
Report on the progress of the script.
123
124
=item B<-f --from>
125
126
Date and time to sync from, if this should be different from "1 second past
127
midnight of the day before". The date should be in this format:
128
129
    2014-06-03T00:00:01
130
131
=item B<-d --debug>
132
133
Even more output.
134
135
=item B<-h, -?, --help>
136
137
Prints this help message and exits.
138
139
=back
140
141
=cut
142
143
sub get_options {
144
145
  # Options
146
  my $run     = '',
147
  my $from    = '',
148
  my $verbose = '';
149
  my $debug   = '';
150
  my $help    = '';
151
152
  GetOptions (
153
    'r|run'     => \$run,
154
    'f|from=s'  => \$from,
155
    'v|verbose' => \$verbose,
156
    'd|debug'   => \$debug,
157
    'h|?|help'  => \$help
158
  );
159
160
  pod2usage( -exitval => 0 ) if $help;
161
162
  return ( $run, $from, $verbose, $debug );
163
164
}
165
166
=head1 AUTHOR
167
168
Magnus Enger <digitalutvikling@gmail.com>
169
170
=head1 COPYRIGHT
171
172
Copyright 2014 Oslo Public Library
173
174
=head1 LICENSE
175
176
This file is part of Koha.
177
178
Koha is free software; you can redistribute it and/or modify it under the terms
179
of the GNU General Public License as published by the Free Software Foundation;
180
either version 3 of the License, or (at your option) any later version.
181
182
You should have received a copy of the GNU General Public License along with
183
Koha; if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
184
Fifth Floor, Boston, MA 02110-1301 USA.
185
186
=cut
(-)a/t/00-load.t (-5 / +1 lines)
Lines 75-85 sub is_testable { Link Here
75
    my ($module_name) = @_;
75
    my ($module_name) = @_;
76
    my @needed_module_names;
76
    my @needed_module_names;
77
    my $return_value = 1;
77
    my $return_value = 1;
78
    if ( $module_name =~ /Koha::NorwegianPatronDB/xsm ) {
78
    if ( $module_name =~ /Koha::SearchEngine::Elasticsearch::Indexer/xsm ) {
79
        @needed_module_names =
80
          ( 'SOAP::Lite', 'Crypt::GCrypt', 'Digest::SHA', 'Convert::BaseN' );
81
    }
82
    elsif ( $module_name =~ /Koha::SearchEngine::Elasticsearch::Indexer/xsm ) {
83
        @needed_module_names =
79
        @needed_module_names =
84
          ( 'Catmandu::Importer::MARC', 'Catmandu::Store::ElasticSearch' );
80
          ( 'Catmandu::Importer::MARC', 'Catmandu::Store::ElasticSearch' );
85
    }
81
    }
(-)a/t/NorwegianPatronDB.t (-600 lines)
Lines 1-599 Link Here
1
#!/usr/bin/perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
use Test::More;
20
use Test::MockModule;
21
use t::lib::Mocks;
22
use Data::Dumper;
23
24
# Check that all the modules we need are installed, or bail out
25
BEGIN {
26
    my $missing_lib;
27
    eval {
28
        require Test::DBIx::Class;
29
        1;
30
    } or do {
31
        $missing_lib = "Test::DBIx::Class";
32
    };
33
34
    eval {
35
        require SOAP::Lite;
36
        1;
37
    } or do {
38
        $missing_lib = "SOAP::Lite";
39
    };
40
41
    eval {
42
        require Crypt::GCrypt;
43
        1;
44
    } or do {
45
        $missing_lib = "Crypt::GCrypt";
46
    };
47
48
    eval {
49
        require Convert::BaseN;
50
        1;
51
    } or do {
52
        $missing_lib = "Convert::BaseN";
53
    };
54
55
    if ( $missing_lib ) {
56
        plan skip_all => $missing_lib . " is not available.";
57
    } else {
58
        # Everything good
59
        plan tests => 73;
60
    }
61
}
62
63
use Test::DBIx::Class {}, 'Borrower', 'BorrowerSync'; #Also loads those modules.
64
65
# Make the code in the module use our mocked Koha::Schema/Koha::Database
66
my $db = Test::MockModule->new('Koha::Database');
67
$db->mock(
68
    # Schema() gives us the DB connection set up by Test::DBIx::Class
69
    _new_schema => sub { return Schema(); }
70
);
71
72
fixtures_ok [
73
    'Borrower' => [
74
        [qw/firstname surname borrowernumber address city/],
75
        ['Test', 'Borrower', 1, 'Test road', 'Test city'],
76
        ['Test', 'Borrower', 2, 'Test road', 'Test city'],
77
        ['Test', 'Borrower', 3, 'Test road', 'Test city'],
78
        ['Test', 'Borrower', 4, 'Test road', 'Test city'],
79
    ],
80
    'BorrowerSync' => [
81
        [qw/borrowernumber sync syncstatus lastsync hashed_pin synctype/],
82
        [1, 1, 'new',    '2014-03-31T12:35:14', 'abc', 'norwegianpatrondb' ],
83
        [2, 1, 'edited', '2014-03-31T12:35:14', 'abc', 'norwegianpatrondb' ],
84
        [3, 1, 'new',    '2014-03-31T12:35:14', 'abc', 'norwegianpatrondb' ],
85
        [4, 1, 'new',    '2014-03-31T12:35:14', 'abc', 'norwegianpatrondb' ],
86
    ],
87
], 'installed some fixtures';
88
89
=head1 LOADING THE MODULE
90
91
=cut
92
93
BEGIN { use_ok( 'Koha::NorwegianPatronDB', ':all' ) }
94
95
96
=head1 UTILITY SUBROUTINES
97
98
=head2 NLCheckSysprefs
99
100
Relevant sysprefs:
101
102
=over 4
103
104
=item * NorwegianPatronDBEnable
105
106
=item * NorwegianPatronDBEndpoint
107
108
=item * NorwegianPatronDBUsername
109
110
=item * NorwegianPatronDBPassword
111
112
=back
113
114
=cut
115
116
BEGIN {
117
    t::lib::Mocks::mock_config('nlkey',        'key');
118
    t::lib::Mocks::mock_config('nlvendoruser', 'user');
119
    t::lib::Mocks::mock_config('nlvendorpass', 'pass');
120
}
121
t::lib::Mocks::mock_preference('NorwegianPatronDBEnable',   0);
122
t::lib::Mocks::mock_preference('NorwegianPatronDBEndpoint', '');
123
t::lib::Mocks::mock_preference('NorwegianPatronDBUsername', '');
124
t::lib::Mocks::mock_preference('NorwegianPatronDBPassword', '');
125
126
ok( my $result = NLCheckSysprefs(), 'call NLCheckSysprefs() ok' );
127
is( $result->{ 'error' },     1, 'error detected' );
128
is( $result->{ 'nlenabled' }, 0, 'NL is not enabled' );
129
is( $result->{ 'endpoint' },  0, 'an endpoint is not specified' );
130
is( $result->{ 'userpass' },  0, 'username and/or password is missing' );
131
132
t::lib::Mocks::mock_preference('NorwegianPatronDBEnable',   1);
133
ok( $result = NLCheckSysprefs(), 'call NLCheckSysprefs() ok' );
134
is( $result->{ 'error' },     1, 'error detected' );
135
is( $result->{ 'nlenabled' }, 1, 'NL is enabled' );
136
is( $result->{ 'endpoint' },  0, 'an endpoint is not specified' );
137
is( $result->{ 'userpass' },  0, 'username and/or password is missing' );
138
139
t::lib::Mocks::mock_preference('NorwegianPatronDBEnable',   0);
140
t::lib::Mocks::mock_preference('NorwegianPatronDBUsername', 'user');
141
t::lib::Mocks::mock_preference('NorwegianPatronDBPassword', 'pass');
142
ok( $result = NLCheckSysprefs(), 'call NLCheckSysprefs() ok' );
143
is( $result->{ 'error' },     1, 'error detected' );
144
is( $result->{ 'nlenabled' }, 0, 'NL is not enabled' );
145
is( $result->{ 'endpoint' },  0, 'an endpoint is not specified' );
146
is( $result->{ 'userpass' },  1, 'username and/or password is present' );
147
148
t::lib::Mocks::mock_preference('NorwegianPatronDBEnable',   1);
149
t::lib::Mocks::mock_preference('NorwegianPatronDBEndpoint', 'http://example.com/');
150
ok( $result = NLCheckSysprefs(), 'call NLCheckSysprefs() ok' );
151
is( $result->{ 'error' },     0, 'no error detected' );
152
is( $result->{ 'nlenabled' }, 1, 'NL is enabled' );
153
is( $result->{ 'endpoint' },  1, 'an endpoint is specified' );
154
is( $result->{ 'userpass' },  1, 'username and/or password is present' );
155
156
=head2 NLGetFirstname and NLGetSurname
157
158
=cut
159
160
my $firstname = 'Firstname';
161
my $surname   = 'Surname';
162
my $fullname  = "$surname, $firstname";
163
my $wrongname = "$surname $firstname";
164
165
is( NLGetFirstname( $fullname  ), $firstname, 'can get firstname from name' );
166
is( NLGetSurname(   $fullname  ), $surname,   'can get surname from name' );
167
is( NLGetFirstname( $wrongname ), $wrongname, 'returns full string when name misses comma' );
168
is( NLGetSurname(   $wrongname ), $wrongname, 'returns full string when name misses comma' );
169
170
=head2 NLDecodePin and NLEncryptPIN
171
172
=cut
173
174
my $pin  = '1234';
175
my $hash = NLEncryptPIN( $pin );
176
177
is( NLEncryptPIN( $pin ), $hash, 'NLEncryptPIN works' );
178
is( NLDecodePin( $hash ), $pin, 'NLDecodePin works' );
179
180
=head2 NLUpdateHashedPIN
181
182
=cut
183
184
is ( BorrowerSync->find({ 'borrowernumber' => 1 })->get_column('hashed_pin'), 'abc', 'hashed_pin is "abc"' );
185
# Set a new pin
186
my $new_pin = 'bcd';
187
ok( NLUpdateHashedPIN( 1, $new_pin ), 'NLUpdateHashedPIN runs ok' );
188
# Hash the new pin and compare it to the one stored in the database
189
my $hashed_pin = Koha::NorwegianPatronDB::_encrypt_pin( $new_pin );
190
is ( BorrowerSync->find({ 'borrowernumber' => 1 })->get_column('hashed_pin'), $hashed_pin, 'hashed_pin was updated' );
191
192
=head2 NLMarkForDeletion
193
194
=cut
195
196
is ( BorrowerSync->find({ 'borrowernumber' => 3 })->get_column('syncstatus'), 'new', 'syncstatus is "new"' );
197
ok( NLMarkForDeletion( 3 ), 'NLMarkForDeletion runs ok' );
198
# Check that the syncstatus was updated. Note: We will use this status later, to check syncing of deleted borrowers
199
is ( BorrowerSync->find({ 'borrowernumber' => 3 })->get_column('syncstatus'), 'delete', 'syncstatus is "delete"' );
200
201
=head2 NLGetSyncDataFromBorrowernumber
202
203
=cut
204
205
ok( my $sync_data = NLGetSyncDataFromBorrowernumber( 1 ), 'NLGetSyncDataFromBorrowernumber runs ok' );
206
isa_ok( $sync_data, 'Koha::Schema::Result::BorrowerSync' );
207
is( $sync_data->sync, 1, 'the sync is on' );
208
is( $sync_data->syncstatus, 'new', 'syncstatus is "new"' );
209
is( $sync_data->lastsync, '2014-03-31T12:35:14', 'lastsync is ok' );
210
is( $sync_data->hashed_pin, $hashed_pin, 'hashed_pin is ok' );
211
212
=head1 SUBROUTINES THAT TALK TO SOAP
213
214
=head2 NLSearch
215
216
=cut
217
218
my $lite = Test::MockModule->new('SOAP::Lite');
219
220
# Mock a successfull call to the "hent" method
221
$lite->mock(
222
    hent => sub { return SOAP::Deserializer->deserialize( hent_success() )->result; }
223
);
224
ok( my $res = NLSearch( '12345678910' ), 'successfull call to NLSearch' );
225
is( $res->{'antall_poster_returnert'}, 1, 'got 1 record' );
226
isa_ok( $res, "Resultat" );
227
isa_ok( $res->{'respons_poster'}, "LaanerListe" );
228
isa_ok( $res->{'respons_poster'}[0], "Laaner" );
229
230
# Mock an unsuccessfull call to the "hent" method
231
$lite->mock(
232
    hent => sub { return SOAP::Deserializer->deserialize( hent_failure() )->result; }
233
);
234
ok( $res = NLSearch( '12345678910' ), 'call to NLSearch with an illegal argument' );
235
is( $res->{'antall_poster_returnert'}, 0, 'got 0 records' );
236
isa_ok( $res, "Resultat" );
237
like( $res->{'melding'}, qr/Ulovlig argument: hverken LNR eller FNR_HASH/, "got expected error message for an illegal identifier" );
238
239
=head2 NLSync
240
241
=head3 New patron
242
243
=cut
244
245
my $borrower = Borrower->find({ 'borrowernumber' => 1 });
246
$lite->mock(
247
    nyPost => sub { return SOAP::Deserializer->deserialize( nyPost_success() )->result; }
248
);
249
is ( BorrowerSync->find({ 'borrowernumber' => 1 })->get_column('syncstatus'), 'new', 'patron is new' );
250
ok ( $result = NLSync({ 'patron' => $borrower }), 'successfull call to NLSync via patron ("nyPost")' );
251
is ( BorrowerSync->find({ 'borrowernumber' => 1 })->get_column('syncstatus'), 'synced', 'patron is synced' );
252
253
# Now do the same test, but pass in a borrowernumber, not a Koha::Schema::Result::Borrower
254
is ( BorrowerSync->find({ 'borrowernumber' => 4 })->get_column('syncstatus'), 'new', 'patron is new' );
255
ok ( $result = NLSync({ 'borrowernumber' => 4 }), 'successfull call to NLSync via borrowernumber ("nyPost")' );
256
is ( BorrowerSync->find({ 'borrowernumber' => 4 })->get_column('syncstatus'), 'synced', 'patron is synced' );
257
258
=head3 Edited patron
259
260
=cut
261
262
ok ( $borrower = Borrower->find({ 'borrowernumber' => 2 }), 'find our "edited" mock patron' );
263
$lite->mock(
264
    endre => sub { return SOAP::Deserializer->deserialize( endre_success() )->result; }
265
);
266
is ( BorrowerSync->find({ 'borrowernumber' => 2 })->get_column('syncstatus'), 'edited', 'patron is edited' );
267
ok ( $result = NLSync({ 'patron' => $borrower }), 'successfull call to NLSync ("endre")' );
268
is ( BorrowerSync->find({ 'borrowernumber' => 2 })->get_column('syncstatus'), 'synced', 'patron is synced' );
269
270
=head3 Deleted patron
271
272
=cut
273
274
ok ( $borrower = Borrower->find({ 'borrowernumber' => 3 }), 'find our "deleted" mock patron' );
275
$lite->mock(
276
    slett => sub { return SOAP::Deserializer->deserialize( endre_success() )->result; }
277
);
278
is ( BorrowerSync->find({ 'borrowernumber' => 3 })->get_column('syncstatus'), 'delete', 'patron is marked for deletion' );
279
ok ( $result = NLSync({ 'patron' => $borrower }), 'successfull call to NLSync ("slett")' );
280
is ( BorrowerSync->find({ 'borrowernumber' => 3 })->get_column('sync'), 0, 'sync is now disabled' );
281
282
=head2 NLGetChanged
283
284
=cut
285
286
# Mock a successfull call to the "soekEndret" method
287
$lite->mock(
288
    soekEndret => sub { return SOAP::Deserializer->deserialize( soekEndret_success() ); }
289
);
290
ok( $res = NLGetChanged(), 'successfull call to NLGetChanged - 2 results' );
291
is( $res->{'melding'}, 'OK', 'got "OK"' );
292
is( $res->{'antall_poster_returnert'}, 2, 'got 2 records' );
293
isa_ok( $res, "Resultat" );
294
isa_ok( $res->{'respons_poster'}, "LaanerListe" );
295
isa_ok( $res->{'respons_poster'}[0], "Laaner" );
296
297
298
# Mock a successfull call to the "soekEndret" method, but with zero new records
299
$lite->mock(
300
    soekEndret => sub { return SOAP::Deserializer->deserialize( soekEndret_zero_new() ); }
301
);
302
ok( $res = NLGetChanged(), 'successfull call to NLGetChanged - 0 results' );
303
is( $res->{'melding'}, 'ingen treff', 'got "ingen treff"' );
304
is( $res->{'antall_poster_returnert'}, 0, 'got 0 records' );
305
is( $res->{'antall_treff'}, 0, 'got 0 records' );
306
307
=head1 SAMPLE SOAP XML RESPONSES
308
309
These responses can be gathered by setting "outputxml()" to true on the SOAP
310
client:
311
312
    my $client = SOAP::Lite
313
        ->on_action( sub { return '""';})
314
        ->uri('http://lanekortet.no')
315
        ->proxy('https://fl.lanekortet.no/laanekort/fl_test.php')
316
        ->outputxml(1);
317
    my $response = $client->slett( $x );
318
    say $response;
319
320
Pretty formatting can be achieved by piping the output from a test script
321
through xmllint:
322
323
    perl my_test_script.pl > xmllint --format -
324
325
=cut
326
327
sub slett_success {
328
329
    return <<'ENDRESPONSE';
330
<?xml version="1.0" encoding="UTF-8"?>
331
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://lanekortet.no" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
332
  <SOAP-ENV:Body>
333
    <ns1:slettResponse>
334
      <return xsi:type="ns1:Svar">
335
        <status xsi:type="xsd:boolean">true</status>
336
        <melding xsi:type="xsd:string">Test Testersen (1973-08-11) er slettet fra registeret</melding>
337
        <lnr xsi:type="xsd:string">N000106188</lnr>
338
        <server_tid xsi:type="xsd:string">2014-06-02T16:51:58</server_tid>
339
      </return>
340
    </ns1:slettResponse>
341
  </SOAP-ENV:Body>
342
</SOAP-ENV:Envelope>
343
ENDRESPONSE
344
345
}
346
347
sub endre_success {
348
349
    return <<'ENDRESPONSE';
350
<?xml version="1.0" encoding="UTF-8"?>
351
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://lanekortet.no" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
352
  <SOAP-ENV:Body>
353
    <ns1:endreResponse>
354
      <return xsi:type="ns1:Svar">
355
        <status xsi:type="xsd:boolean">true</status>
356
        <melding xsi:type="xsd:string">Oppdaterte felt: navn, p_adresse1, p_postnr, p_sted, p_land, fdato, fnr_hash, kjonn, pin, sist_endret, sist_endret_av</melding>
357
        <lnr xsi:type="xsd:string">N000106188</lnr>
358
        <server_tid xsi:type="xsd:string">2014-06-02T16:42:32</server_tid>
359
      </return>
360
    </ns1:endreResponse>
361
  </SOAP-ENV:Body>
362
</SOAP-ENV:Envelope>
363
ENDRESPONSE
364
365
}
366
367
sub nyPost_success {
368
369
    return <<'ENDRESPONSE';
370
<?xml version="1.0" encoding="UTF-8"?>
371
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://lanekortet.no" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
372
  <SOAP-ENV:Body>
373
    <ns1:nyPostResponse>
374
      <return xsi:type="ns1:Svar">
375
        <status xsi:type="xsd:boolean">true</status>
376
        <melding xsi:type="xsd:string">Ny post er opprettet</melding>
377
        <lnr xsi:type="xsd:string">N000106188</lnr>
378
        <server_tid xsi:type="xsd:string">2014-06-02T14:10:09</server_tid>
379
      </return>
380
    </ns1:nyPostResponse>
381
  </SOAP-ENV:Body>
382
</SOAP-ENV:Envelope>
383
ENDRESPONSE
384
385
}
386
387
sub soekEndret_success {
388
389
return <<'ENDRESPONSE';
390
<?xml version="1.0" encoding="UTF-8"?>
391
<SOAP-ENV:Envelope
392
    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
393
    xmlns:ns1="http://lanekortet.no"
394
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
395
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
396
    xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
397
    SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
398
  <SOAP-ENV:Body>
399
    <ns1:soekEndretResponse>
400
      <return xsi:type="ns1:Resultat">
401
        <status xsi:type="xsd:boolean">true</status>
402
        <melding xsi:type="xsd:string">OK</melding>
403
        <antall_treff xsi:type="xsd:int">2</antall_treff>
404
        <antall_poster_returnert xsi:type="xsd:int">2</antall_poster_returnert>
405
        <neste_indeks xsi:type="xsd:int">0</neste_indeks>
406
        <respons_poster SOAP-ENC:arrayType="ns1:Laaner[2]" xsi:type="ns1:LaanerListe">
407
          <item xsi:type="ns1:Laaner">
408
            <lnr xsi:type="xsd:string">N000106186</lnr>
409
            <navn xsi:type="xsd:string">Hansen, Hanne</navn>
410
            <p_adresse1 xsi:type="xsd:string"/>
411
            <p_adresse2 xsi:type="xsd:string"/>
412
            <p_postnr xsi:type="xsd:string"/>
413
            <p_sted xsi:type="xsd:string">BØDØ</p_sted>
414
            <p_land xsi:type="xsd:string">no</p_land>
415
            <p_sjekk xsi:type="xsd:string">0</p_sjekk>
416
            <m_adresse1 xsi:type="xsd:string"/>
417
            <m_adresse2 xsi:type="xsd:string"/>
418
            <m_postnr xsi:type="xsd:string"/>
419
            <m_sted xsi:type="xsd:string"/>
420
            <m_land xsi:type="xsd:string"/>
421
            <m_sjekk xsi:type="xsd:string">0</m_sjekk>
422
            <m_gyldig_til xsi:type="xsd:string">0000-00-00</m_gyldig_til>
423
            <tlf_hjemme xsi:type="xsd:string"/>
424
            <tlf_jobb xsi:type="xsd:string"/>
425
            <tlf_mobil xsi:type="xsd:string"/>
426
            <epost xsi:type="xsd:string"/>
427
            <epost_sjekk xsi:type="xsd:string"/>
428
            <prim_kontakt xsi:type="xsd:string"/>
429
            <hjemmebibliotek xsi:type="xsd:string">5180401</hjemmebibliotek>
430
            <fdato xsi:type="xsd:string">1994-04-08</fdato>
431
            <fnr_hash xsi:type="xsd:string">11087395628</fnr_hash>
432
            <kjonn xsi:type="xsd:string">F</kjonn>
433
            <pin xsi:type="xsd:string">89308dfc85ee7a5826ae14e2d8efad1e</pin>
434
            <passord xsi:type="xsd:string"/>
435
            <feide xsi:type="xsd:string">0</feide>
436
            <opprettet xsi:type="xsd:string">2014-04-28T15:20:38</opprettet>
437
            <opprettet_av xsi:type="xsd:string">5180401</opprettet_av>
438
            <sist_endret xsi:type="xsd:string">2014-04-28T15:20:38</sist_endret>
439
            <sist_endret_av xsi:type="xsd:string">5180401</sist_endret_av>
440
            <folkeregsjekk_dato xsi:type="xsd:string">0000-00-00</folkeregsjekk_dato>
441
          </item>
442
          <item xsi:type="ns1:Laaner">
443
            <lnr xsi:type="xsd:string">N000106184</lnr>
444
            <navn xsi:type="xsd:string">Enger, Magnus</navn>
445
            <p_adresse1 xsi:type="xsd:string">Svarthammarveien 633333</p_adresse1>
446
            <p_adresse2 xsi:type="xsd:string"/>
447
            <p_postnr xsi:type="xsd:string">8015</p_postnr>
448
            <p_sted xsi:type="xsd:string">Bodø</p_sted>
449
            <p_land xsi:type="xsd:string">no</p_land>
450
            <p_sjekk xsi:type="xsd:string">0</p_sjekk>
451
            <m_adresse1 xsi:type="xsd:string"/>
452
            <m_adresse2 xsi:type="xsd:string"/>
453
            <m_postnr xsi:type="xsd:string"/>
454
            <m_sted xsi:type="xsd:string"/>
455
            <m_land xsi:type="xsd:string">no</m_land>
456
            <m_sjekk xsi:type="xsd:string">0</m_sjekk>
457
            <m_gyldig_til xsi:type="xsd:string">0000-00-00</m_gyldig_til>
458
            <tlf_hjemme xsi:type="xsd:string">95158548</tlf_hjemme>
459
            <tlf_jobb xsi:type="xsd:string"/>
460
            <tlf_mobil xsi:type="xsd:string"/>
461
            <epost xsi:type="xsd:string">magnus@enger.priv.no</epost>
462
            <epost_sjekk xsi:type="xsd:string"/>
463
            <prim_kontakt xsi:type="xsd:string"/>
464
            <hjemmebibliotek xsi:type="xsd:string">5180401</hjemmebibliotek>
465
            <fdato xsi:type="xsd:string">1973-08-11</fdato>
466
            <fnr_hash xsi:type="xsd:string">11087345795</fnr_hash>
467
            <kjonn xsi:type="xsd:string">M</kjonn>
468
            <pin xsi:type="xsd:string">a632c504b8c4fba3149115cb07e0796c</pin>
469
            <passord xsi:type="xsd:string"/>
470
            <feide xsi:type="xsd:string">0</feide>
471
            <opprettet xsi:type="xsd:string">2014-04-28T14:52:02</opprettet>
472
            <opprettet_av xsi:type="xsd:string">5180401</opprettet_av>
473
            <sist_endret xsi:type="xsd:string">2014-05-13T11:01:33</sist_endret>
474
            <sist_endret_av xsi:type="xsd:string">5180401</sist_endret_av>
475
            <folkeregsjekk_dato xsi:type="xsd:string">0000-00-00</folkeregsjekk_dato>
476
          </item>
477
        </respons_poster>
478
        <server_tid xsi:type="xsd:string">2014-05-16T14:44:44</server_tid>
479
      </return>
480
    </ns1:soekEndretResponse>
481
  </SOAP-ENV:Body>
482
</SOAP-ENV:Envelope>
483
ENDRESPONSE
484
}
485
486
sub soekEndret_zero_new {
487
    return <<'ENDRESPONSE';
488
<?xml version="1.0" encoding="UTF-8"?>
489
    <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://lanekortet.no" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
490
      <SOAP-ENV:Body>
491
        <ns1:soekEndretResponse>
492
          <return xsi:type="ns1:Resultat">
493
            <status xsi:type="xsd:boolean">false</status>
494
            <melding xsi:type="xsd:string">ingen treff</melding>
495
            <antall_treff xsi:type="xsd:int">0</antall_treff>
496
            <antall_poster_returnert xsi:type="xsd:int">0</antall_poster_returnert>
497
            <neste_indeks xsi:type="xsd:int">0</neste_indeks>
498
            <respons_poster SOAP-ENC:arrayType="ns1:Laaner[0]" xsi:type="ns1:LaanerListe"/>
499
            <server_tid xsi:type="xsd:string">2014-05-20T13:02:02</server_tid>
500
          </return>
501
        </ns1:soekEndretResponse>
502
      </SOAP-ENV:Body>
503
    </SOAP-ENV:Envelope>
504
ENDRESPONSE
505
}
506
507
sub hent_failure {
508
    return <<'ENDRESPONSE';
509
<?xml version="1.0" encoding="UTF-8"?>
510
<SOAP-ENV:Envelope
511
    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
512
    xmlns:ns1="http://lanekortet.no"
513
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
514
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
515
    xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
516
    SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
517
  <SOAP-ENV:Body>
518
    <ns1:hentResponse>
519
      <return xsi:type="ns1:Resultat">
520
        <status xsi:type="xsd:boolean">false</status>
521
        <melding xsi:type="xsd:string">hent: Ulovlig argument: hverken LNR eller FNR_HASH</melding>
522
        <antall_treff xsi:type="xsd:int">0</antall_treff>
523
        <antall_poster_returnert xsi:type="xsd:int">0</antall_poster_returnert>
524
        <neste_indeks xsi:type="xsd:int">0</neste_indeks>
525
        <respons_poster SOAP-ENC:arrayType="ns1:Laaner[0]" xsi:type="ns1:LaanerListe"/>
526
        <server_tid xsi:type="xsd:string">2014-05-15T10:56:24</server_tid>
527
      </return>
528
    </ns1:hentResponse>
529
  </SOAP-ENV:Body>
530
</SOAP-ENV:Envelope>
531
ENDRESPONSE
532
533
}
534
535
sub hent_success {
536
537
return <<'ENDRESPONSE';
538
<?xml version="1.0" encoding="UTF-8"?>
539
<SOAP-ENV:Envelope
540
    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
541
    xmlns:ns1="http://lanekortet.no"
542
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
543
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
544
    xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
545
    SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
546
  <SOAP-ENV:Body>
547
    <ns1:hentResponse>
548
      <return xsi:type="ns1:Resultat">
549
        <status xsi:type="xsd:boolean">true</status>
550
        <melding xsi:type="xsd:string">OK</melding>
551
        <antall_treff xsi:type="xsd:int">1</antall_treff>
552
        <antall_poster_returnert xsi:type="xsd:int">1</antall_poster_returnert>
553
        <neste_indeks xsi:type="xsd:int">0</neste_indeks>
554
        <respons_poster SOAP-ENC:arrayType="ns1:Laaner[1]" xsi:type="ns1:LaanerListe">
555
          <item xsi:type="ns1:Laaner">
556
            <lnr xsi:type="xsd:string">N000123456</lnr>
557
            <navn xsi:type="xsd:string">Test, Testersen</navn>
558
            <p_adresse1 xsi:type="xsd:string">Bibliotekveien 6</p_adresse1>
559
            <p_adresse2 xsi:type="xsd:string"/>
560
            <p_postnr xsi:type="xsd:string">1234</p_postnr>
561
            <p_sted xsi:type="xsd:string">Lillevik</p_sted>
562
            <p_land xsi:type="xsd:string">no</p_land>
563
            <p_sjekk xsi:type="xsd:string">0</p_sjekk>
564
            <m_adresse1 xsi:type="xsd:string"/>
565
            <m_adresse2 xsi:type="xsd:string"/>
566
            <m_postnr xsi:type="xsd:string"/>
567
            <m_sted xsi:type="xsd:string"/>
568
            <m_land xsi:type="xsd:string">no</m_land>
569
            <m_sjekk xsi:type="xsd:string">0</m_sjekk>
570
            <m_gyldig_til xsi:type="xsd:string">0000-00-00</m_gyldig_til>
571
            <tlf_hjemme xsi:type="xsd:string"/>
572
            <tlf_jobb xsi:type="xsd:string"/>
573
            <tlf_mobil xsi:type="xsd:string">12345678</tlf_mobil>
574
            <epost xsi:type="xsd:string">test@example.com</epost>
575
            <epost_sjekk xsi:type="xsd:string">0</epost_sjekk>
576
            <prim_kontakt xsi:type="xsd:string"/>
577
            <hjemmebibliotek xsi:type="xsd:string">2060000</hjemmebibliotek>
578
            <fdato xsi:type="xsd:string">1964-05-22</fdato>
579
            <fnr_hash xsi:type="xsd:string">22056412345</fnr_hash>
580
            <kjonn xsi:type="xsd:string">F</kjonn>
581
            <pin xsi:type="xsd:string">g345abc123dab567abc78900abc123ab</pin>
582
            <passord xsi:type="xsd:string"/>
583
            <feide xsi:type="xsd:string"/>
584
            <opprettet xsi:type="xsd:string">2005-10-20</opprettet>
585
            <opprettet_av xsi:type="xsd:string">2060000</opprettet_av>
586
            <sist_endret xsi:type="xsd:string">2013-05-13T13:51:24</sist_endret>
587
            <sist_endret_av xsi:type="xsd:string">2060000</sist_endret_av>
588
            <gyldig_til xsi:type="xsd:string"/>
589
            <folkeregsjekk_dato xsi:type="xsd:string">0000-00-00</folkeregsjekk_dato>
590
          </item>
591
        </respons_poster>
592
        <server_tid xsi:type="xsd:string">2014-01-07T14:43:18</server_tid>
593
      </return>
594
    </ns1:hentResponse>
595
  </SOAP-ENV:Body>
596
</SOAP-ENV:Envelope>
597
ENDRESPONSE
598
599
}
600
- 

Return to bug 21068