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

(-)a/C4/Installer/PerlDependencies.pm (+25 lines)
Lines 742-747 our $PERL_DEPS = { Link Here
742
        'required' => '0',
742
        'required' => '0',
743
        'min_ver'  => '5.836',
743
        'min_ver'  => '5.836',
744
    },
744
    },
745
    'SOAP::Lite' => {
746
        'usage'    => 'Norwegian national library card',
747
        'required' => '0',
748
        'min_ver'  => '0.712',
749
    },
750
    'Crypt::GCrypt' => {
751
        'usage'    => 'Norwegian national library card',
752
        'required' => '0',
753
        'min_ver'  => '1.24',
754
    },
755
    'Convert::BaseN' => {
756
        'usage'    => 'Norwegian national library card',
757
        'required' => '0',
758
        'min_ver'  => '0.01',
759
    },
760
    'Test::DBIx::Class' => {
761
        'usage'    => 'Testing suite',
762
        'required' => '0',
763
        'min_ver'  => '0.41',
764
    },
765
    'Digest::SHA' => {
766
        'usage'    => 'Norwegian national library card',
767
        'required' => '0',
768
        'min_ver'  => '5.84_01',
769
    },
745
};
770
};
746
771
747
1;
772
1;
(-)a/C4/Members.pm (-1 / +40 lines)
Lines 33-47 use C4::Accounts; Link Here
33
use C4::Biblio;
33
use C4::Biblio;
34
use C4::Letters;
34
use C4::Letters;
35
use C4::SQLHelper qw(InsertInTable UpdateInTable SearchInTable);
35
use C4::SQLHelper qw(InsertInTable UpdateInTable SearchInTable);
36
use C4::Members::Attributes qw(SearchIdMatchingAttribute);
36
use C4::Members::Attributes qw(SearchIdMatchingAttribute UpdateBorrowerAttribute);
37
use C4::NewsChannels; #get slip news
37
use C4::NewsChannels; #get slip news
38
use DateTime;
38
use DateTime;
39
use DateTime::Format::DateParse;
39
use DateTime::Format::DateParse;
40
use Koha::Database;
40
use Koha::DateUtils;
41
use Koha::DateUtils;
41
use Koha::Borrower::Debarments qw(IsDebarred);
42
use Koha::Borrower::Debarments qw(IsDebarred);
42
use Text::Unaccent qw( unac_string );
43
use Text::Unaccent qw( unac_string );
43
use Koha::AuthUtils qw(hash_password);
44
use Koha::AuthUtils qw(hash_password);
44
use Koha::Database;
45
use Koha::Database;
46
use Koha::NorwegianPatronDB qw( NLUpdateHashedPIN NLEncryptPIN NLSync );
45
47
46
our ($VERSION,@ISA,@EXPORT,@EXPORT_OK,$debug);
48
our ($VERSION,@ISA,@EXPORT,@EXPORT_OK,$debug);
47
49
Lines 783-788 sub ModMember { Link Here
783
        if ($data{password} eq '****' or $data{password} eq '') {
785
        if ($data{password} eq '****' or $data{password} eq '') {
784
            delete $data{password};
786
            delete $data{password};
785
        } else {
787
        } else {
788
            if ( C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
789
                # Update the hashed PIN in borrower_sync.hashed_pin, before Koha hashes it
790
                NLUpdateHashedPIN( $data{'borrowernumber'}, $data{password} );
791
            }
786
            $data{password} = hash_password($data{password});
792
            $data{password} = hash_password($data{password});
787
        }
793
        }
788
    }
794
    }
Lines 803-808 sub ModMember { Link Here
803
            AddEnrolmentFeeIfNeeded( $data{categorycode}, $data{borrowernumber} );
809
            AddEnrolmentFeeIfNeeded( $data{categorycode}, $data{borrowernumber} );
804
        }
810
        }
805
811
812
        # If NorwegianPatronDBEnable is enabled, we set syncstatus to something that a
813
        # cronjob will use for syncing with NL
814
        if ( C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
815
            my $borrowersync = Koha::Database->new->schema->resultset('BorrowerSync')->find({
816
                'synctype'       => 'norwegianpatrondb',
817
                'borrowernumber' => $data{'borrowernumber'}
818
            });
819
            # Do not set to "edited" if syncstatus is "new". We need to sync as new before
820
            # we can sync as changed. And the "new sync" will pick up all changes since
821
            # the patron was created anyway.
822
            if ( $borrowersync->syncstatus ne 'new' && $borrowersync->syncstatus ne 'delete' ) {
823
                $borrowersync->update( { 'syncstatus' => 'edited' } );
824
            }
825
            # Set the value of 'sync'
826
            $borrowersync->update( { 'sync' => $data{'sync'} } );
827
            # Try to do the live sync
828
            NLSync({ 'borrowernumber' => $data{'borrowernumber'} });
829
        }
830
806
        logaction("MEMBERS", "MODIFY", $data{'borrowernumber'}, "UPDATE (executed w/ arg: $data{'borrowernumber'})") if C4::Context->preference("BorrowersLog");
831
        logaction("MEMBERS", "MODIFY", $data{'borrowernumber'}, "UPDATE (executed w/ arg: $data{'borrowernumber'})") if C4::Context->preference("BorrowersLog");
807
    }
832
    }
808
    return $execute_success;
833
    return $execute_success;
Lines 845-855 sub AddMember { Link Here
845
      : $patron_category->default_privacy() eq 'never'   ? 2
870
      : $patron_category->default_privacy() eq 'never'   ? 2
846
      : $patron_category->default_privacy() eq 'forever' ? 0
871
      : $patron_category->default_privacy() eq 'forever' ? 0
847
      :                                                    undef;
872
      :                                                    undef;
873
    # Make a copy of the plain text password for later use
874
    my $plain_text_password = $data{'password'};
848
875
849
    # create a disabled account if no password provided
876
    # create a disabled account if no password provided
850
    $data{'password'} = ($data{'password'})? hash_password($data{'password'}) : '!';
877
    $data{'password'} = ($data{'password'})? hash_password($data{'password'}) : '!';
851
    $data{'borrowernumber'}=InsertInTable("borrowers",\%data);
878
    $data{'borrowernumber'}=InsertInTable("borrowers",\%data);
852
879
880
    # If NorwegianPatronDBEnable is enabled, we set syncstatus to something that a
881
    # cronjob will use for syncing with NL
882
    if ( exists $data{'borrowernumber'} && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
883
        Koha::Database->new->schema->resultset('BorrowerSync')->create({
884
            'borrowernumber' => $data{'borrowernumber'},
885
            'synctype'       => 'norwegianpatrondb',
886
            'sync'           => 1,
887
            'syncstatus'     => 'new',
888
            'hashed_pin'     => NLEncryptPIN( $plain_text_password ),
889
        });
890
    }
891
853
    # mysql_insertid is probably bad.  not necessarily accurate and mysql-specific at best.
892
    # mysql_insertid is probably bad.  not necessarily accurate and mysql-specific at best.
854
    logaction("MEMBERS", "CREATE", $data{'borrowernumber'}, "") if C4::Context->preference("BorrowersLog");
893
    logaction("MEMBERS", "CREATE", $data{'borrowernumber'}, "") if C4::Context->preference("BorrowersLog");
855
894
(-)a/Koha/NorwegianPatronDB.pm (+657 lines)
Line 0 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 FUNCTIONS
29
30
=cut
31
32
use Modern::Perl;
33
use C4::Context;
34
use C4::Members::Attributes qw( UpdateBorrowerAttribute );
35
use SOAP::Lite;
36
use Crypt::GCrypt;
37
use Digest::SHA qw( sha256_hex );
38
use Convert::BaseN;
39
use DateTime;
40
41
use base 'Exporter';
42
use version; our $VERSION = qv('1.0.0');
43
44
our %EXPORT_TAGS = ( all => [qw(
45
        NLCheckSysprefs
46
        NLSearch
47
        NLSync
48
        NLGetChanged
49
        NLMarkForDeletion
50
        NLDecodePin
51
        NLEncryptPIN
52
        NLUpdateHashedPIN
53
        NLGetFirstname
54
        NLGetSurname
55
        NLGetSyncDataFromBorrowernumber
56
)] );
57
Exporter::export_ok_tags('all');
58
59
my $nl_uri   = 'http://lanekortet.no';
60
my $nl_proxy =  C4::Context->preference("NorwegianPatronDBEndpoint");
61
62
=head2 SOAP::Transport::HTTP::Client::get_basic_credentials
63
64
This is included to set the username and password used by SOAP::Lite.
65
66
=cut
67
68
sub SOAP::Transport::HTTP::Client::get_basic_credentials {
69
    # Library username and password from Base Bibliotek (stored as system preferences)
70
    my $library_username = C4::Context->preference("NorwegianPatronDBUsername");
71
    my $library_password = C4::Context->preference("NorwegianPatronDBPassword");
72
    # Vendor username and password (stored in koha-conf.xml)
73
    my $vendor_username = C4::Context->config( 'nlvendoruser' );
74
    my $vendor_password = C4::Context->config( 'nlvendorpass' );
75
    # Combine usernames and passwords, and encrypt with SHA256
76
    my $combined_username = "$vendor_username-$library_username";
77
    my $combined_password = sha256_hex( "$library_password-$vendor_password" );
78
    warn "$combined_username => $combined_password";
79
    return $combined_username => $combined_password;
80
}
81
82
=head2 NLCheckSysprefs
83
84
Check that sysprefs relevant to NL are set.
85
86
=cut
87
88
sub NLCheckSysprefs {
89
90
    my $response = {
91
        'error'     => 0,
92
        'nlenabled' => 0,
93
        'endpoint'  => 0,
94
        'userpass'  => 0,
95
    };
96
97
    # Check that the Norwegian national paron database is enabled
98
    if ( C4::Context->preference("NorwegianPatronDBEnable") == 1 ) {
99
        $response->{ 'nlenabled' } = 1;
100
    } else {
101
        $response->{ 'error' } = 1;
102
    }
103
104
    # Check that an endpoint is specified
105
    if ( C4::Context->preference("NorwegianPatronDBEndpoint") ne '' ) {
106
        $response->{ 'endpoint' } = 1;
107
    } else {
108
        $response->{ 'error' } = 1;
109
    }
110
111
    # Check that the username and password for the patron database is set
112
    if ( C4::Context->preference("NorwegianPatronDBUsername") ne '' && C4::Context->preference("NorwegianPatronDBPassword") ne '' ) {
113
        $response->{ 'userpass' } = 1;
114
    } else {
115
        $response->{ 'error' } = 1;
116
    }
117
118
    return $response;
119
120
}
121
122
=head2 NLSearch
123
124
Search the NL patron database.
125
126
SOAP call: "hent" (fetch)
127
128
=cut
129
130
sub NLSearch {
131
132
    my ( $identifier ) = @_;
133
134
    my $client = SOAP::Lite
135
        ->on_action( sub { return '""';})
136
        ->uri( $nl_uri )
137
        ->proxy( $nl_proxy );
138
139
    my $id = SOAP::Data->type('string');
140
    $id->name('identifikator');
141
    $id->value( $identifier );
142
    my $som = $client->hent( $id );
143
144
    return $som;
145
146
}
147
148
=head2 NLSync
149
150
Sync a patron that has been changed or created in Koha "upstream" to NL.
151
152
Input is a hashref with one of two possible elements, either a patron retrieved
153
from the database:
154
155
    my $result = NLSync({ 'patron' => $borrower_from_dbic });
156
157
or a plain old borrowernumber:
158
159
    my $result = NLSync({ 'borrowernumber' => $borrowernumber });
160
161
In the latter case, this function will retrieve the patron record from the
162
database using DBIC.
163
164
Which part of the API is called depends on the value of the "syncstatus" column:
165
166
=over 4
167
168
=item * B<new> = The I<nyPost> ("new record") method is called.
169
170
=item * B<edited> = The I<endre> ("change/update") method is called.
171
172
=item * B<delete> = The I<slett> ("delete") method is called.
173
174
=back
175
176
Required values for B<new> and B<edited>:
177
178
=over 4
179
180
=item * sist_endret (last updated)
181
182
=item * adresse, postnr eller sted (address, zip or city)
183
184
=item * fdato (birthdate)
185
186
=item * fnr_hash (social security number, but not hashed...)
187
188
=item * kjonn (gender, M/F)
189
190
=back
191
192
=cut
193
194
sub NLSync {
195
196
    my ( $input ) = @_;
197
198
    my $patron;
199
    if ( defined $input->{'borrowernumber'} ) {
200
        $patron = Koha::Database->new->schema->resultset('Borrower')->find( $input->{'borrowernumber'} );
201
    } elsif ( defined $input->{'patron'} ) {
202
        $patron = $input->{'patron'};
203
    }
204
205
    # There should only be one sync, so we use the first one
206
    my @syncs = $patron->borrower_syncs;
207
    my $sync;
208
    foreach my $this_sync ( @syncs ) {
209
        if ( $this_sync->synctype eq 'norwegianpatrondb' ) {
210
            $sync = $this_sync;
211
        }
212
    }
213
214
    my $client = SOAP::Lite
215
        ->on_action( sub { return '""';})
216
        ->uri( $nl_uri )
217
        ->proxy( $nl_proxy );
218
219
    my $cardnumber = SOAP::Data->name( 'lnr' => $patron->cardnumber );
220
221
    # Call the appropriate method based on syncstatus
222
    my $response;
223
    if ( $sync->syncstatus eq 'edited' || $sync->syncstatus eq 'new' ) {
224
        my $soap_patron = _koha_patron_to_soap( $patron );
225
        if ( $sync->syncstatus eq 'edited' ) {
226
            $response = $client->endre( $cardnumber, $soap_patron );
227
        } elsif ( $sync->syncstatus eq 'new' ) {
228
            $response = $client->nyPost( $soap_patron );
229
        }
230
    }
231
    if ( $sync->syncstatus eq 'delete' ) {
232
        $response = $client->slett( $cardnumber );
233
    }
234
235
    # Update the sync data according to the results
236
    if ( $response->{'status'} && $response->{'status'} == 1 ) {
237
        if ( $sync->syncstatus eq 'delete' ) {
238
            # Turn off any further syncing
239
            $sync->update( { 'sync' => 0 } );
240
        }
241
        # Update the syncstatus to 'synced'
242
        $sync->update( { 'syncstatus' => 'synced' } );
243
        # Update the 'synclast' attribute with the "server time" ("server_tid") returned by the method
244
        $sync->update( { 'lastsync' => $response->{'server_tid'} } );
245
    }
246
    return $response;
247
248
}
249
250
=head2 NLGetChanged
251
252
Fetches patrons from NL that have been changed since a given timestamp. This includes
253
patrons that have been changed by the library that runs the sync, so we have to
254
check which library was the last one to change a patron, before we update patrons
255
locally.
256
257
This is supposed to be executed once per night.
258
259
SOAP call: soekEndret
260
261
=cut
262
263
sub NLGetChanged {
264
265
    my ( $from_arg ) = @_;
266
267
    my $client = SOAP::Lite
268
        ->on_action( sub { return '""';})
269
        ->uri( $nl_uri )
270
        ->proxy( $nl_proxy );
271
272
    my $from_string;
273
    if ( $from_arg && $from_arg ne '' ) {
274
        $from_string = $from_arg;
275
    } else {
276
        # Calculate 1 second past midnight of the day before
277
        my $dt = DateTime->now( time_zone => 'Europe/Oslo' );
278
        $dt->subtract( days => 1 );
279
        my $from = DateTime->new(
280
            year       => $dt->year(),
281
            month      => $dt->month(),
282
            day        => $dt->day(),
283
            hour       => 00,
284
            minute     => 00,
285
            second     => 01,
286
            time_zone  => 'Europe/Oslo',
287
        );
288
        $from_string = $from->ymd . "T" . $from->hms;
289
    }
290
291
    my $timestamp   = SOAP::Data->name( 'tidspunkt'    => $from_string );
292
    my $max_results = SOAP::Data->name( 'max_antall'   => 0 ); # 0 = no limit
293
    my $start_index = SOAP::Data->name( 'start_indeks' => 0 ); # 1 is the first record
294
295
    # Call the appropriate method based on syncstatus
296
    my $som = $client->soekEndret( $timestamp, $max_results, $start_index );
297
298
    # Extract and massage patron data
299
    my $result = $som->result;
300
    foreach my $patron ( @{ $result->{'respons_poster'} } ) {
301
        # Only handle patrons that have lnr (barcode) and fnr_hash (social security number)
302
        # Patrons that lack these two have been deleted from NL
303
        if ( $patron->{'lnr'} && $patron->{'fnr_hash'} ) {
304
            push @{ $result->{'kohapatrons'} }, _soap_to_kohapatron( $patron );
305
        }
306
    }
307
    return $result;
308
309
}
310
311
=head2 NLMarkForDeletion
312
313
Mark a borrower for deletion, but do not do the actual deletion. Deleting the
314
borrower from NL will be done later by the nl-sync-from-koha.pl script.
315
316
=cut
317
318
sub NLMarkForDeletion {
319
320
    my ( $borrowernumber ) = @_;
321
322
    my $borrowersync = Koha::Database->new->schema->resultset('BorrowerSync')->find({
323
        'synctype'       => 'norwegianpatrondb',
324
        'borrowernumber' => $borrowernumber,
325
    });
326
    return $borrowersync->update( { 'syncstatus' => 'delete' } );
327
328
}
329
330
=head2 NLDecodePin
331
332
Takes a string encoded with AES/ECB/PKCS5PADDING and a 128-bits key, and returns
333
the decoded string as plain text.
334
335
The key needs to be stored in koha-conf.xml, like so:
336
337
<yazgfs>
338
  ...
339
  <config>
340
    ...
341
    <nlkey>xyz</nlkey>
342
  </config>
343
</yazgfs>
344
345
=cut
346
347
sub NLDecodePin {
348
349
    my ( $hash ) = @_;
350
    my $key = C4::Context->config( 'nlkey' );
351
352
    # Convert the hash from Base16
353
    my $cb = Convert::BaseN->new( base => 16 );
354
    my $decoded_hash = $cb->decode( $hash );
355
356
    # Do the decryption
357
    my $cipher = Crypt::GCrypt->new(
358
        type      => 'cipher',
359
        algorithm => 'aes',
360
        mode      => 'ecb',
361
        padding   => 'standard', # "This is also known as PKCS#5"
362
    );
363
    $cipher->start( 'decrypting' );
364
    $cipher->setkey( $key ); # Must be called after start()
365
    my $plaintext  = $cipher->decrypt( $decoded_hash );
366
    $plaintext .= $cipher->finish;
367
368
    return $plaintext;
369
370
}
371
372
=head2 NLEncryptPIN
373
374
Takes a plain text PIN as argument, returns the encrypted PIN, according to the
375
NL specs.
376
377
    my $encrypted_pin = NLEncryptPIN( $plain_text_pin );
378
379
=cut
380
381
sub NLEncryptPIN {
382
383
    my ( $pin ) = @_;
384
    return _encrypt_pin( $pin );
385
386
}
387
388
=head2 NLUpdateHashedPIN
389
390
Takes two arguments:
391
392
=over 4
393
394
=item * Borrowernumber
395
396
=item * Clear text PIN code
397
398
=back
399
400
Hashes the password and saves it in borrower_sync.hashed_pin.
401
402
=cut
403
404
sub NLUpdateHashedPIN {
405
406
    my ( $borrowernumber, $pin ) = @_;
407
    my $borrowersync = Koha::Database->new->schema->resultset('BorrowerSync')->find({
408
        'synctype'       => 'norwegianpatrondb',
409
        'borrowernumber' => $borrowernumber,
410
        });
411
    return $borrowersync->update({ 'hashed_pin', _encrypt_pin( $pin ) });
412
413
}
414
415
=head2 _encrypt_pin
416
417
Takes a plain text PIN and returns the encrypted version, according to the NL specs.
418
419
=cut
420
421
sub _encrypt_pin {
422
423
    my ( $pin ) = @_;
424
    my $key = C4::Context->config( 'nlkey' );
425
426
    # Do the encryption
427
    my $cipher = Crypt::GCrypt->new(
428
        type      => 'cipher',
429
        algorithm => 'aes',
430
        mode      => 'ecb',
431
        padding   => 'standard', # "This is also known as PKCS#5"
432
    );
433
    $cipher->start( 'encrypting' );
434
    $cipher->setkey( $key ); # Must be called after start()
435
    my $ciphertext  = $cipher->encrypt( $pin );
436
    $ciphertext .= $cipher->finish;
437
438
    # Encode as Bas16
439
    my $cb = Convert::BaseN->new( base => 16 );
440
    my $encoded_ciphertext = $cb->encode( $ciphertext );
441
442
    return $encoded_ciphertext;
443
444
}
445
446
=head2 NLGetSyncDataFromBorrowernumber
447
448
Takes a borrowernumber as argument, returns a Koha::Schema::Result::BorrowerSync
449
object.
450
451
    my $syncdata = NLGetSyncDataFromBorrowernumber( $borrowernumber );
452
453
=cut
454
455
sub NLGetSyncDataFromBorrowernumber {
456
457
    my ( $borrowernumber ) = @_;
458
    my $data = Koha::Database->new->schema->resultset('BorrowerSync')->find({
459
        'synctype'       => 'norwegianpatrondb',
460
        'borrowernumber' => $borrowernumber,
461
    });
462
    return $data;
463
464
}
465
466
=head2 NLGetFirstname
467
468
Takes a string like "Surname, Firstname" and returns the "Firstname" part.
469
470
If there is no comma, the string is returned unaltered.
471
472
    my $firstname = NLGetFirstname( $name );
473
474
=cut
475
476
sub NLGetFirstname {
477
478
    my ( $s ) = @_;
479
    my ( $surname, $firstname ) = _split_name( $s );
480
    if ( $surname eq $s ) {
481
        return $s;
482
    } else {
483
        return $firstname;
484
    }
485
486
}
487
488
=head2 NLGetSurname
489
490
Takes a string like "Surname, Firstname" and returns the "Surname" part.
491
492
If there is no comma, the string is returned unaltered.
493
494
    my $surname = NLGetSurname( $name );
495
496
=cut
497
498
sub NLGetSurname {
499
500
    my ( $s ) = @_;
501
    my ( $surname, $firstname ) = _split_name( $s );
502
    return $surname;
503
504
}
505
506
=head2 _split_name
507
508
Takes a string like "Surname, Firstname" and returns a list of surname and firstname.
509
510
If there is no comma, the string is returned unaltered.
511
512
    my ( $surname, $firstname ) = _split_name( $name );
513
514
=cut
515
516
sub _split_name {
517
518
    my ( $s ) = @_;
519
520
    # Return the string if there is no comma
521
    unless ( $s =~ m/,/ ) {
522
        return $s;
523
    }
524
525
    my ( $surname, $firstname ) = split /, /, $s;
526
527
    return ( $surname, $firstname );
528
529
}
530
531
=head2 _format_soap_error
532
533
Takes a soap result object as input and returns a formatted string containing SOAP error data.
534
535
=cut
536
537
sub _format_soap_error {
538
539
    my ( $result ) = @_;
540
    if ( $result ) {
541
        return join ', ', $result->faultcode, $result->faultstring, $result->faultdetail;
542
    } else {
543
        return 'No result';
544
    }
545
546
}
547
548
=head2 _soap_to_koha_patron
549
550
Convert a SOAP object of type "Laaner" into a hash that can be sent to AddMember or ModMember.
551
552
=cut
553
554
sub _soap_to_kohapatron {
555
556
    my ( $soap ) = @_;
557
558
    return {
559
        'cardnumber'      => $soap->{ 'lnr' },
560
        'surname'         => NLGetSurname(   $soap->{ 'navn' } ),
561
        'firstname'       => NLGetFirstname( $soap->{ 'navn' } ),
562
        'sex'             => $soap->{ 'kjonn' },
563
        'dateofbirth'     => $soap->{ 'fdato' },
564
        'address'         => $soap->{ 'p_adresse1' },
565
        'address2'        => $soap->{ 'p_adresse2' },
566
        'zipcode'         => $soap->{ 'p_postnr' },
567
        'city'            => $soap->{ 'p_sted' },
568
        'country'         => $soap->{ 'p_land' },
569
        'b_address'       => $soap->{ 'm_adresse1' },
570
        'b_address2'      => $soap->{ 'm_adresse2' },
571
        'b_zipcode'       => $soap->{ 'm_postnr' },
572
        'b_city'          => $soap->{ 'm_sted' },
573
        'b_country'       => $soap->{ 'm_land' },
574
        'password'        => $soap->{ 'pin' },
575
        'dateexpiry'      => $soap->{ 'gyldig_til' },
576
        'email'           => $soap->{ 'epost' },
577
        'mobile'          => $soap->{ 'tlf_mobil' },
578
        'phone'           => $soap->{ 'tlf_hjemme' },
579
        'phonepro'        => $soap->{ 'tlf_jobb' },
580
        '_extra'          => { # Data that should not go in the borrowers table
581
            'socsec'         => $soap->{ 'fnr_hash' },
582
            'created'        => $soap->{ 'opprettet' },
583
            'created_by'     => $soap->{ 'opprettet_av' },
584
            'last_change'    => $soap->{ 'sist_endret' },
585
            'last_change_by' => $soap->{ 'sist_endret_av' },
586
        },
587
    };
588
589
}
590
591
=head2 _koha_patron_to_soap
592
593
Convert a patron (in the form of a Koha::Schema::Result::Borrower) into a SOAP
594
object that can be sent to NL.
595
596
=cut
597
598
sub _koha_patron_to_soap {
599
600
    my ( $patron ) = @_;
601
602
    # Extract attributes
603
    my $patron_attributes = {};
604
    foreach my $attribute ( $patron->borrower_attributes ) {
605
        $patron_attributes->{ $attribute->code->code } = $attribute->attribute;
606
    }
607
608
    # There should only be one sync, so we use the first one
609
    my @syncs = $patron->borrower_syncs;
610
    my $sync = $syncs[0];
611
612
    # Create SOAP::Data object
613
    my $soap_patron = SOAP::Data->name(
614
        'post' => \SOAP::Data->value(
615
            SOAP::Data->name( 'lnr'         => $patron->cardnumber ),
616
            SOAP::Data->name( 'fnr_hash'    => $patron_attributes->{ 'fnr' } )->type( 'string' )->type( 'string' ),
617
            SOAP::Data->name( 'navn'        => $patron->surname . ', ' . $patron->firstname    )->type( 'string' ),
618
            SOAP::Data->name( 'sist_endret' => $sync->lastsync      )->type( 'string' ),
619
            SOAP::Data->name( 'kjonn'       => $patron->sex         )->type( 'string' ),
620
            SOAP::Data->name( 'fdato'       => $patron->dateofbirth )->type( 'string' ),
621
            SOAP::Data->name( 'p_adresse1'  => $patron->address     )->type( 'string' ),
622
            SOAP::Data->name( 'p_adresse2'  => $patron->address2    )->type( 'string' ),
623
            SOAP::Data->name( 'p_postnr'    => $patron->zipcode     )->type( 'string' ),
624
            SOAP::Data->name( 'p_sted'      => $patron->city        )->type( 'string' ),
625
            SOAP::Data->name( 'p_land'      => $patron->country     )->type( 'string' ),
626
            SOAP::Data->name( 'm_adresse1'  => $patron->b_address   )->type( 'string' ),
627
            SOAP::Data->name( 'm_adresse2'  => $patron->b_address2  )->type( 'string' ),
628
            SOAP::Data->name( 'm_postnr'    => $patron->b_zipcode   )->type( 'string' ),
629
            SOAP::Data->name( 'm_sted'      => $patron->b_city      )->type( 'string' ),
630
            SOAP::Data->name( 'm_land'      => $patron->b_country   )->type( 'string' ),
631
            # Do not send the PIN code as it has been hashed by Koha, but use the version hashed according to NL
632
            SOAP::Data->name( 'pin'         => $sync->hashed_pin    )->type( 'string' ),
633
            SOAP::Data->name( 'gyldig_til'  => $patron->dateexpiry  )->type( 'string' ),
634
            SOAP::Data->name( 'epost'       => $patron->email       )->type( 'string' ),
635
            SOAP::Data->name( 'tlf_mobil'   => $patron->mobile      )->type( 'string' ),
636
            SOAP::Data->name( 'tlf_hjemme'  => $patron->phone       )->type( 'string' ),
637
            SOAP::Data->name( 'tlf_jobb'    => $patron->phonepro    )->type( 'string' ),
638
        ),
639
    )->type("Laaner");
640
641
    return $soap_patron;
642
643
}
644
645
=head1 EXPORT
646
647
None by default.
648
649
=head1 AUTHOR
650
651
Magnus Enger <digitalutvikling@gmail.com>
652
653
=cut
654
655
1;
656
657
__END__
(-)a/installer/data/mysql/kohastructure.sql (+18 lines)
Lines 357-362 CREATE TABLE `branch_item_rules` ( -- information entered in the circulation and Link Here
357
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
357
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
358
358
359
--
359
--
360
-- Table structure for table borrower_sync
361
--
362
363
CREATE TABLE borrower_sync (
364
  borrowersyncid int(11) NOT NULL AUTO_INCREMENT, -- Primary key, unique identifier
365
  borrowernumber int(11) NOT NULL, -- Connects data about synchronisations to a borrower
366
  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
367
  sync tinyint(1) NOT NULL DEFAULT '0', -- A boolean (1/0) for turning syncing off and on for individual borrowers
368
  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.
369
  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.
370
  hashed_pin varchar(64) DEFAULT NULL, -- Perhaps specific to The Norwegian national patron database, this column holds a hashed PIN code
371
  PRIMARY KEY (borrowersyncid),
372
  KEY borrowernumber (borrowernumber),
373
  CONSTRAINT borrower_sync_ibfk_1 FOREIGN KEY (borrowernumber) REFERENCES borrowers (borrowernumber) ON DELETE CASCADE ON UPDATE CASCADE
374
) ENGINE=InnoDB DEFAULT CHARSET=utf8
375
376
377
--
360
-- Table structure for table `branchcategories`
378
-- Table structure for table `branchcategories`
361
--
379
--
362
380
(-)a/installer/data/mysql/sysprefs.sql (+5 lines)
Lines 197-202 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
197
('noissuescharge','5','','Define maximum amount withstanding before check outs are blocked','Integer'),
197
('noissuescharge','5','','Define maximum amount withstanding before check outs are blocked','Integer'),
198
('noItemTypeImages','0',NULL,'If ON, disables item-type images','YesNo'),
198
('noItemTypeImages','0',NULL,'If ON, disables item-type images','YesNo'),
199
('NoLoginInstructions', '', '60|10', 'Instructions to display on the OPAC login form when a patron is not logged in', 'Textarea'),
199
('NoLoginInstructions', '', '60|10', 'Instructions to display on the OPAC login form when a patron is not logged in', 'Textarea'),
200
('NorwegianPatronDBEnable','0',NULL,'Enable communication with the Norwegian national patron database.', 'YesNo'),
201
('NorwegianPatronDBEndpoint','',NULL,'Which NL endpoint to use.', 'Free'),
202
('NorwegianPatronDBUsername','',NULL,'Username for communication with the Norwegian national patron database.','Free'),
203
('NorwegianPatronDBPassword','',NULL,'Password for communication with the Norwegian national patron database.','Free'),
204
('NorwegianPatronDBSearchNLAfterLocalHit','0',NULL,'Search NL if a search has already given one or more local hits?.','YesNo'),
200
('NotesBlacklist','',NULL,'List of notes fields that should not appear in the title notes/description separator of details','free'),
205
('NotesBlacklist','',NULL,'List of notes fields that should not appear in the title notes/description separator of details','free'),
201
('NotHighlightedWords','and|or|not',NULL,'List of words to NOT highlight when OpacHitHighlight is enabled','free'),
206
('NotHighlightedWords','and|or|not',NULL,'List of words to NOT highlight when OpacHitHighlight is enabled','free'),
202
('NoticeCSS','',NULL,'Notices CSS url.','free'),
207
('NoticeCSS','',NULL,'Notices CSS url.','free'),
(-)a/installer/data/mysql/updatedatabase.pl (+25 lines)
Lines 8776-8781 if ( CheckVersion($DBversion) ) { Link Here
8776
    SetVersion($DBversion);
8776
    SetVersion($DBversion);
8777
}
8777
}
8778
8778
8779
$DBversion = "3.17.00.XXX";
8780
if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
8781
    $dbh->do("INSERT IGNORE INTO systempreferences (variable,value,options,explanation,type) VALUES ('NorwegianPatronDBEnable', '0', NULL, 'Enable communication with the Norwegian national patron database.', 'YesNo')");
8782
    $dbh->do("INSERT IGNORE INTO systempreferences (variable,value,options,explanation,type) VALUES ('NorwegianPatronDBEndpoint', '', NULL, 'Which NL endpoint to use.', 'Free')");
8783
    $dbh->do("INSERT IGNORE INTO systempreferences (variable,value,options,explanation,type) VALUES ('NorwegianPatronDBUsername', '', NULL, 'Username for communication with the Norwegian national patron database.', 'Free')");
8784
    $dbh->do("INSERT IGNORE INTO systempreferences (variable,value,options,explanation,type) VALUES ('NorwegianPatronDBPassword', '', NULL, 'Password for communication with the Norwegian national patron database.', 'Free')");
8785
    $dbh->do("INSERT IGNORE INTO systempreferences (variable,value,options,explanation,type) VALUES ('NorwegianPatronDBSearchNLAfterLocalHit','0',NULL,'Search NL if a search has already given one or more local hits?.','YesNo')");
8786
    $dbh->do("
8787
CREATE TABLE borrower_sync (
8788
    borrowersyncid int(11) NOT NULL AUTO_INCREMENT,
8789
    borrowernumber int(11) NOT NULL,
8790
    synctype varchar(32) NOT NULL,
8791
    sync tinyint(1) NOT NULL DEFAULT '0',
8792
    syncstatus varchar(10) DEFAULT NULL,
8793
    lastsync varchar(50) DEFAULT NULL,
8794
    hashed_pin varchar(64) DEFAULT NULL,
8795
    PRIMARY KEY (borrowersyncid),
8796
    KEY borrowernumber (borrowernumber),
8797
    CONSTRAINT borrower_sync_ibfk_1 FOREIGN KEY (borrowernumber) REFERENCES borrowers (borrowernumber) ON DELETE CASCADE ON UPDATE CASCADE
8798
) ENGINE=InnoDB DEFAULT CHARSET=utf8"
8799
);
8800
    print "Upgrade to $DBversion done (Bug 11401 - Add support for Norwegian national library card)\n";
8801
    SetVersion($DBversion);
8802
}
8803
8779
=head1 FUNCTIONS
8804
=head1 FUNCTIONS
8780
8805
8781
=head2 TableExists($table)
8806
=head2 TableExists($table)
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/members-toolbar.inc (-2 / +46 lines)
Lines 1-12 Link Here
1
[% USE Koha %]
2
[% SET NorwegianPatronDBEnable = Koha.Preference( 'NorwegianPatronDBEnable' ) %]
1
<script type="text/javascript">
3
<script type="text/javascript">
2
//<![CDATA[
4
//<![CDATA[
3
$(document).ready(function(){
5
$(document).ready(function(){
4
    [% IF ( CAN_user_borrowers ) %]
6
    [% IF ( CAN_user_borrowers ) %]
5
        $("#deletepatron").click(function(){
7
        [% IF ( NorwegianPatronDBEnable == 1 ) %]
8
            $("#deletepatronlocal").click(function(){
9
                confirm_local_deletion();
10
                $(".btn-group").removeClass("open");
11
                return false;
12
            });
13
            $("#deletepatronremote").click(function(){
14
                confirm_remote_deletion();
15
                $(".btn-group").removeClass("open");
16
                return false;
17
            });
18
            $("#deletepatronboth").click(function(){
19
                confirm_both_deletion();
20
                $(".btn-group").removeClass("open");
21
                return false;
22
            });
23
        [% ELSE %]
24
            $("#deletepatron").click(function(){
6
                confirm_deletion();
25
                confirm_deletion();
7
                $(".btn-group").removeClass("open");
26
                $(".btn-group").removeClass("open");
8
                return false;
27
                return false;
9
            });
28
            });
29
        [% END %]
10
        $("#renewpatron").click(function(){
30
        $("#renewpatron").click(function(){
11
            confirm_reregistration();
31
            confirm_reregistration();
12
            $(".btn-group").removeClass("open");
32
            $(".btn-group").removeClass("open");
Lines 49-54 function confirm_deletion() { Link Here
49
        window.location='/cgi-bin/koha/members/deletemem.pl?member=[% borrowernumber %]';
69
        window.location='/cgi-bin/koha/members/deletemem.pl?member=[% borrowernumber %]';
50
    }
70
    }
51
}
71
}
72
function confirm_local_deletion() {
73
    var is_confirmed = window.confirm(_("Are you sure you want to delete this patron from the local database? This cannot be undone."));
74
    if (is_confirmed) {
75
        window.location='/cgi-bin/koha/members/deletemem.pl?member=[% borrowernumber %]&deletelocal=true&deleteremote=false';
76
    }
77
}
78
function confirm_remote_deletion() {
79
    var is_confirmed = window.confirm(_("Are you sure you want to delete this patron from the Norwegian national patron database? This cannot be undone."));
80
    if (is_confirmed) {
81
        window.location='/cgi-bin/koha/members/deletemem.pl?member=[% borrowernumber %]&deletelocal=false&deleteremote=true';
82
    }
83
}
84
function confirm_both_deletion() {
85
    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."));
86
    if (is_confirmed) {
87
        window.location='/cgi-bin/koha/members/deletemem.pl?member=[% borrowernumber %]&deletelocal=true&deleteremote=true';
88
    }
89
}
52
90
53
[% IF ( is_child ) %]function confirm_updatechild() {
91
[% IF ( is_child ) %]function confirm_updatechild() {
54
    var is_confirmed = window.confirm(_("Are you sure you want to update this child to an Adult category?  This cannot be undone."));
92
    var is_confirmed = window.confirm(_("Are you sure you want to update this child to an Adult category?  This cannot be undone."));
Lines 141-147 function searchToHold(){ Link Here
141
                    <li class="disabled"><a data-toggle="tooltip" data-placement="left" title="You are not authorized to set permissions" id="patronflags" href="#">Set permissions</a></li>
179
                    <li class="disabled"><a data-toggle="tooltip" data-placement="left" title="You are not authorized to set permissions" id="patronflags" href="#">Set permissions</a></li>
142
                [% END %]
180
                [% END %]
143
                [% IF ( CAN_user_borrowers ) %]
181
                [% IF ( CAN_user_borrowers ) %]
144
                    <li><a id="deletepatron" href="#">Delete</a></li>
182
                    [% IF ( NorwegianPatronDBEnable == 1 ) %]
183
                        <li><a id="deletepatronlocal" href="#">Delete local</a></li>
184
                        <li><a id="deletepatronremote" href="#">Delete remote</a></li>
185
                        <li><a id="deletepatronboth" href="#">Delete local and remote</a></li>
186
                    [% ELSE %]
187
                        <li><a id="deletepatron" href="#">Delete</a></li>
188
                    [% END %]
145
                [% ELSE %]
189
                [% ELSE %]
146
                    <li class="disabled"><a data-toggle="tooltip" data-placement="left" title="You are not authorized to delete patrons" id="deletepatron" href="#">Delete</a></li>
190
                    <li class="disabled"><a data-toggle="tooltip" data-placement="left" title="You are not authorized to delete patrons" id="deletepatron" href="#">Delete</a></li>
147
                [% END %]
191
                [% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/nl-search-form.tt (+9 lines)
Line 0 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 %]</legend>
5
        <label for="q">Social security or card number: </label>
6
        <input type="text" name="q" value="[% q %]">
7
        <input type="submit" value="Search">
8
    </fieldset>
9
</form>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref (+22 lines)
Lines 1-4 Link Here
1
Patrons:
1
Patrons:
2
    General:
2
     -
3
     -
3
         - List
4
         - List
4
         - pref: AddPatronLists
5
         - pref: AddPatronLists
Lines 138-140 Patrons: Link Here
138
         - pref: CardnumberLength
139
         - pref: CardnumberLength
139
         - "characters long. The length can be a single number to specify an exact length, a range separated by a comma (i.e., 'Min,Max'), or a maximum with no minimum (i.e., ',Max')."
140
         - "characters long. The length can be a single number to specify an exact length, a range separated by a comma (i.e., 'Min,Max'), or a maximum with no minimum (i.e., ',Max')."
140
         - "If 'cardnumber' is included in the BorrowerMandatoryField list, the minimum length, if not specified here, defaults to one."
141
         - "If 'cardnumber' is included in the BorrowerMandatoryField list, the minimum length, if not specified here, defaults to one."
142
    Norwegian patron database:
143
    -
144
         - pref: NorwegianPatronDBEnable
145
           choices:
146
               yes: Enable
147
               no: Disable
148
         - the ability to communicate with the Norwegian national patron database via the
149
         - pref: NorwegianPatronDBEndpoint
150
         - endpoint.
151
    -
152
         - Communicate with the Norwegian national patron database using the username
153
         - pref: NorwegianPatronDBUsername
154
         - and the password
155
         - pref: NorwegianPatronDBPassword
156
         - . You can get these from "Base Bibliotek", which is maintained by the Norwegian National Library.
157
    -
158
         - pref: NorwegianPatronDBSearchNLAfterLocalHit
159
           choices:
160
               yes: Do
161
               no: "Don't"
162
         - search the Norwegian national patron database after a local search result was found.
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/members/deletemem.tt (-12 / +22 lines)
Lines 13-30 Link Here
13
	<div id="yui-main">
13
	<div id="yui-main">
14
	<div class="yui-b">
14
	<div class="yui-b">
15
	[% INCLUDE 'members-toolbar.inc' %]
15
	[% INCLUDE 'members-toolbar.inc' %]
16
	<div class="dialog alert">
16
	[% IF ( ItemsOnIssues || charges || guarantees ) %]
17
    <h3>Cannot delete patron</h3>
17
        <div class="dialog alert">
18
		<ul>[% IF ( ItemsOnIssues ) %]
18
        <h3>Cannot delete patron</h3>
19
			<li>Patron has [% ItemsOnIssues %] item(s) checked out.</li>
19
		    <ul>
20
		[% END %]
20
		    [% IF ( ItemsOnIssues ) %]
21
		[% IF ( charges ) %]
21
			    <li>Patron has [% ItemsOnIssues %] item(s) checked out.</li>
22
			<li>Patron has [% charges %] in fines.</li>
22
		    [% END %]
23
		[% END %]
23
		    [% IF ( charges ) %]
24
		[% IF ( guarantees ) %]
24
			    <li>Patron has [% charges %] in fines.</li>
25
			<li>Patron's record has guaranteed accounts attached.</li>
25
		    [% END %]
26
		[% END %]</ul>
26
		    [% IF ( guarantees ) %]
27
			    <li>Patron's record has guaranteed accounts attached.</li>
28
		    [% END %]
29
		    </ul>
27
	</div>
30
	</div>
31
    [% END %]
32
    [% IF ( keeplocal ) %]
33
        <div class="dialog message">
34
        <h3>Remote record deleted, local record kept</h3>
35
        <p>Patron was marked for deletion from Norwegian national patron database, but the local record was kept.</p>
36
        </div>
37
    [% END %]
28
</div>
38
</div>
29
</div>
39
</div>
30
40
Lines 32-35 Link Here
32
[% INCLUDE 'circ-menu.inc' %]
42
[% INCLUDE 'circ-menu.inc' %]
33
</div>
43
</div>
34
</div>
44
</div>
35
[% INCLUDE 'intranet-bottom.inc' %]
45
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/members/member.tt (+6 lines)
Lines 1-3 Link Here
1
[% USE Koha %]
1
[% INCLUDE 'doc-head-open.inc' %]
2
[% INCLUDE 'doc-head-open.inc' %]
2
<title>Koha &rsaquo; Patrons [% IF ( searching ) %]&rsaquo; Search results[% END %]</title>
3
<title>Koha &rsaquo; Patrons [% IF ( searching ) %]&rsaquo; Search results[% END %]</title>
3
[% INCLUDE 'doc-head-close.inc' %]
4
[% INCLUDE 'doc-head-close.inc' %]
Lines 293-298 function filterByFirstLetterSurname(letter) { Link Here
293
            </div>
294
            </div>
294
          [% END %]
295
          [% END %]
295
296
297
          [% IF Koha.Preference( 'NorwegianPatronDBEnable' ) == 1 %]
298
            [% SET nl_search_form_title='Search the Norwegian national patron database' %]
299
            [% INCLUDE 'nl-search-form.tt' %]
300
          [% END %]
301
296
          [% INCLUDE 'patron-toolbar.inc' %]
302
          [% INCLUDE 'patron-toolbar.inc' %]
297
          [% IF ( no_add ) %]
303
          [% IF ( no_add ) %]
298
            <div class="dialog alert">
304
            <div class="dialog alert">
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/members/memberentrygen.tt (+13 lines)
Lines 1-4 Link Here
1
[% IF ( opduplicate ) %][% SET focusAction = "clearDupe" %][% END %]
1
[% IF ( opduplicate ) %][% SET focusAction = "clearDupe" %][% END %]
2
[% USE Koha %]
2
[% USE KohaDates %]
3
[% USE KohaDates %]
3
[% INCLUDE 'doc-head-open.inc' %]
4
[% INCLUDE 'doc-head-open.inc' %]
4
<title>Koha &rsaquo; Patrons &rsaquo; 
5
<title>Koha &rsaquo; Patrons &rsaquo; 
Lines 973-978 Link Here
973
    [% IF ( mandatorysort2 ) %]<span class="required">Required</span>[% END %]
974
    [% IF ( mandatorysort2 ) %]<span class="required">Required</span>[% END %]
974
    </li>
975
    </li>
975
        [% END %]
976
        [% END %]
977
    [% IF ( Koha.Preference( 'NorwegianPatronDBEnable' ) == 1 ) %]
978
        <li>
979
            <label for="sort2">Sync with the Norwegian national patron database:</label>
980
            [% IF ( sync == 0 ) %]
981
                <input type="radio" id="sync" name="sync" value="1"> Yes
982
                <input type="radio" id="sync" name="sync" value="0" checked> No
983
            [% ELSE %]
984
                <input type="radio" id="sync" name="sync" value="1" checked> Yes
985
                <input type="radio" id="sync" name="sync" value="0"> No
986
            [% END %]
987
        </li>
988
    [% END %]
976
	</ol>
989
	</ol>
977
  </fieldset>
990
  </fieldset>
978
    [% UNLESS nodateenrolled &&  noopacnote && noborrowernotes %]
991
    [% UNLESS nodateenrolled &&  noopacnote && noborrowernotes %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt (+10 lines)
Lines 1-3 Link Here
1
[% USE Koha %]
1
[% USE KohaDates %]
2
[% USE KohaDates %]
2
[% INCLUDE 'doc-head-open.inc' %]
3
[% INCLUDE 'doc-head-open.inc' %]
3
<title>Koha &rsaquo; Patrons &rsaquo;
4
<title>Koha &rsaquo; Patrons &rsaquo;
Lines 350-355 function validate1(date) { Link Here
350
    </li>
351
    </li>
351
    [% IF ( borrowernotes ) %]<li><span class="label">Circulation note: </span>[% borrowernotes %]</li>[% END %]
352
    [% IF ( borrowernotes ) %]<li><span class="label">Circulation note: </span>[% borrowernotes %]</li>[% END %]
352
    [% IF ( opacnote ) %]<li><span class="label">OPAC note:</span>[% opacnote %]</li>[% END %]
353
    [% IF ( opacnote ) %]<li><span class="label">OPAC note:</span>[% opacnote %]</li>[% END %]
354
    [% IF Koha.Preference( 'NorwegianPatronDBEnable' ) == 1 %]
355
        [% IF ( sync == 1 ) %]
356
            <li><span class="label">Activate sync: </span>Yes</li>
357
            [% IF ( syncstatus ) %]<li><span class="label">Sync status: </span>[% syncstatus %]</li>[% END %]
358
            [% IF ( lastsync ) %]<li><span class="label">Last sync: </span>[% lastsync | $KohaDates %]</li>[% END %]
359
        [% ELSE %]
360
            <li><span class="label">Activate sync: </span>No</li>
361
        [% END %]
362
    [% END %]
353
	</ol>
363
	</ol>
354
	</div>
364
	</div>
355
 </div>
365
 </div>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/members/nl-search.tt (+176 lines)
Line 0 Link Here
1
[% USE KohaDates %]
2
[% INCLUDE 'doc-head-open.inc' %]
3
<title>Search the Norwegian national patron database</title>
4
[% INCLUDE 'doc-head-close.inc' %]
5
</head>
6
<body>
7
[% INCLUDE 'header.inc' %]
8
[% INCLUDE 'patron-search.inc' %]
9
10
<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>
11
12
<div id="doc3" class="yui-t2">
13
    <div id="bd">
14
        <div id="yui-main">
15
            <div class="yui-b">
16
17
                <h1>Search the Norwegian national patron database</h1>
18
19
                [% IF (error) %]
20
                    <div class="dialog alert">
21
                    [% IF ( error.nlenabled == 0 ) %]<p>You need to activate this function with the NorwegianPatronDBEnable system preference in order to use it.</p>[% END %]
22
                    [% IF ( error.endpoint  == 0 ) %]<p>You need to specify an endpoint with the NorwegianPatronDBEndpoint system preference.</p>[% END %]
23
                    [% IF ( error.userpass  == 0 ) %]<p>You need to fill in the NorwegianPatronDBUsername and NorwegianPatronDBPassword system preferences in order to use this function.</p>[% END %]
24
                    [% IF ( error == 'COULD_NOT_ADD_PATRON' ) %]<p>Could not add a new patron.</p>[% END %]
25
                    </div>
26
                [% ELSE %]
27
                    [% SET nl_search_form_title='Search' %]
28
                    [% INCLUDE 'nl-search-form.tt' %]
29
                [% END %]
30
31
                [% IF ( local_result ) %]
32
                    <h3>Existing patrons</h3>
33
                    <ul>
34
                        [% FOREACH patron IN local_result %]
35
                            [%# Find the category_type %]
36
                            [% FOREACH category IN categories %]
37
                                [% IF category.categorycode == patron.categorycode %]
38
                                    [% patron.category_type = category.category_type %]
39
                                [% END %]
40
                            [% END %]
41
                            <li>[% patron.firstname %] [% patron.surname %] [% patron.cardnumber %] |
42
                                <a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% patron.borrowernumber %]">Details</a> |
43
                                <a href="/cgi-bin/koha/members/memberentry.pl?op=modify&destination=circ&borrowernumber=[% patron.borrowernumber %]&category_type=[% patron.category_type %]">Edit</a> |
44
                                <a href="/cgi-bin/koha/circ/circulation.pl?borrowernumber=[% patron.borrowernumber %]">Check out</a>
45
                            </li>
46
                        [% END %]
47
                    </ul>
48
                [% END %]
49
50
                [% IF ( result ) %]
51
52
                    [% IF result.antall_poster_returnert == 0 %]
53
54
                        <div class="dialog alert">
55
                            <p>No results found in the Norwegian national patron database. Message: "[% result.melding %]"</p>
56
                        </div>
57
58
                    [% ELSE %]
59
60
                        <h3>Results from the Norwegian national patron database</h3>
61
                        <div class="yui-g">
62
                        <div class="yui-u first">
63
                        [% PROCESS patron_detail p=result.respons_poster.0 %]
64
                        </div>
65
                        [% IF ( result.respons_poster.1 ) %]
66
                            <div class="yui-u">
67
                            [% PROCESS patron_detail p=result.respons_poster.1 %]
68
                            </div>
69
                        [% END %]
70
                        </div>
71
72
                    [% END %]
73
74
                [% END %]
75
76
            </div>
77
        </div>
78
79
        <div class="yui-b">
80
            [% INCLUDE 'circ-menu.inc' %]
81
        </div>
82
    </div>
83
[% INCLUDE 'intranet-bottom.inc' %]
84
85
[% BLOCK patron_detail %]
86
<div class="rows">
87
<h4>[% p.navn | html_entity %]</h4>
88
<ol>
89
[% IF ( p.kjonn ) %]<li><span class="label">kjonn: </span>[% p.kjonn | html_entity %]</li>[% END %]
90
[% IF ( p.fdato ) %]<li><span class="label">fdato: </span>[% p.fdato | html_entity %]</li>[% END %]
91
[% IF ( p.lnr ) %]<li><span class="label">lnr: </span>[% p.lnr | html_entity %]</li>[% END %]
92
[% IF ( p.fnr_hash ) %]<li><span class="label">fnr_hash: </span>[% p.fnr_hash | html_entity %]</li>[% END %]
93
94
[% IF ( p.epost ) %]<li><span class="label">epost: </span>[% p.epost | html_entity %]</li>[% END %]
95
[% IF ( p.epost_sjekk ) %]<li><span class="label">epost_sjekk: </span>[% p.epost_sjekk | html_entity %]</li>[% END %]
96
97
[% IF ( p.tlf_mobil ) %]<li><span class="label">tlf_mobil: </span>[% p.tlf_mobil | html_entity %]</li>[% END %]
98
[% IF ( p.tlf_hjemme ) %]<li><span class="label">tlf_hjemme: </span>[% p.tlf_hjemme | html_entity %]</li>[% END %]
99
[% IF ( p.tlf_jobb ) %]<li><span class="label">tlf_jobb: </span>[% p.tlf_jobb | html_entity %]</li>[% END %]
100
101
[% IF ( p.p_adresse1 ) %]<li><span class="label">p_adresse1: </span>[% p.p_adresse1 | html_entity %]</li>[% END %]
102
[% IF ( p.p_adresse2 ) %]<li><span class="label">p_adresse2: </span>[% p.p_adresse2 | html_entity %]</li>[% END %]
103
[% IF ( p.p_postnr ) %]<li><span class="label">p_postnr: </span>[% p.p_postnr | html_entity %]</li>[% END %]
104
[% IF ( p.p_sted ) %]<li><span class="label">p_sted: </span>[% p.p_sted | html_entity %]</li>[% END %]
105
[% IF ( p.p_land ) %]<li><span class="label">p_land: </span>[% p.p_land | html_entity %]</li>[% END %]
106
[% IF ( p.p_sjekk ) %]<li><span class="label">p_sjekk: </span>[% p.p_sjekk | html_entity %]</li>[% END %]
107
108
[% IF ( p.m_adresse1 ) %]<li><span class="label">m_adresse1: </span>[% p.m_adresse1 | html_entity %]</li>[% END %]
109
[% IF ( p.m_adresse2 ) %]<li><span class="label">m_adresse2: </span>[% p.m_adresse2 | html_entity %]</li>[% END %]
110
[% IF ( p.m_postnr ) %]<li><span class="label">m_postnr: </span>[% p.m_postnr | html_entity %]</li>[% END %]
111
[% IF ( p.m_sted ) %]<li><span class="label">m_sted: </span>[% p.m_sted | html_entity %]</li>[% END %]
112
[% IF ( p.m_land ) %]<li><span class="label">m_land: </span>[% p.m_land | html_entity %]</li>[% END %]
113
[% IF ( p.m_sjek ) %]<li><span class="label">m_sjekk: </span>[% p.m_sjekk | html_entity %]</li>[% END %]
114
[% IF ( p.m_gyldig_til ) %]<li><span class="label">m_gyldig_til: </span>[% p.m_gyldig_til | html_entity %]</li>[% END %]
115
116
[% IF ( p.pin ) %]<li><span class="label">pin: </span>[% p.pin | html_entity %]</li>[% END %]
117
[% IF ( p.passord ) %]<li><span class="label">passsord: </span>[% p.passord | html_entity %]</li>[% END %]
118
[% IF ( p.feide ) %]<li><span class="label">feide: </span>[% p.feide | html_entity %]</li>[% END %]
119
[% IF ( p.folkeregsjekk_dato ) %]<li><span class="label">folkeregsjekk_dato: </span>[% p.folkeregsjekk_dato | html_entity %]</li>[% END %]
120
121
[% IF ( p.hjemmebibliotek ) %]<li><span class="label">hjemmebibliotek: </span>[% p.hjemmebibliotek | html_entity %]</li>[% END %]
122
[% IF ( p.opprettet ) %]<li><span class="label">opprettet: </span>[% p.opprettet | html_entity %]</li>[% END %]
123
[% IF ( p.opprettet_av ) %]<li><span class="label">opprettet_av: </span>[% p.opprettet_av | html_entity %]</li>[% END %]
124
[% IF ( p.sist_endret ) %]<li><span class="label">sist_endret: </span>[% p.sist_endret | html_entity %]</li>[% END %]
125
[% IF ( p.sist_endret_av ) %]<li><span class="label">sist_endret_av: </span>[% p.sist_endret_av | html_entity %]</li>[% END %]
126
[% IF ( p.gyldig_til ) %]<li><span class="label">gyldig_til: </span>[% p.gyldig_til | html_entity %]</li>[% END %]
127
128
[% IF ( p.prim_kontakt ) %]<li><span class="label">prim_kontakt: </span>[% p.prim_kontakt | html_entity %]</li>[% END %]
129
</ol>
130
131
<form action="nl-search.pl" method="POST">
132
<input type="hidden" name="op" value="save" />
133
<input type="hidden" name="navn" value="[% p.navn | html_entity %]" />
134
<input type="hidden" name="kjonn" value="[% p.kjonn | html_entity %]" />
135
<input type="hidden" name="fdato" value="[% p.fdato | html_entity %]" />
136
<input type="hidden" name="lnr" value="[% p.lnr | html_entity %]" />
137
<input type="hidden" name="fnr_hash" value="[% p.fnr_hash | html_entity %]" />
138
<input type="hidden" name="p_adresse1" value="[% p.p_adresse1 | html_entity %]" />
139
<input type="hidden" name="p_adresse2" value="[% p.p_adresse2 | html_entity %]" />
140
<input type="hidden" name="p_postnr" value="[% p.p_postnr | html_entity %]" />
141
<input type="hidden" name="p_sted" value="[% p.p_sted | html_entity %]" />
142
<input type="hidden" name="p_land" value="[% p.p_land | html_entity %]" />
143
<input type="hidden" name="p_sjekk" value="[% p.p_sjekk | html_entity %]" />
144
<input type="hidden" name="m_adresse1" value="[% p.m_adresse1 | html_entity %]" />
145
<input type="hidden" name="m_adresse2" value="[% p.m_adresse2 | html_entity %]" />
146
<input type="hidden" name="m_postnr" value="[% p.m_postnr | html_entity %]" />
147
<input type="hidden" name="m_sted" value="[% p.m_sted | html_entity %]" />
148
<input type="hidden" name="m_land" value="[% p.m_land | html_entity %]" />
149
<input type="hidden" name="m_sjekk" value="[% p.m_sjekk | html_entity %]" />
150
<input type="hidden" name="m_gyldig_til" value="[% p.m_gyldig_til | html_entity %]" />
151
<input type="hidden" name="pin" value="[% p.pin %]" />
152
<input type="hidden" name="passord" value="[% p.passord | html_entity %]" />
153
<input type="hidden" name="feide" value="[% p.feide | html_entity %]" />
154
<input type="hidden" name="folkeregsjekk_dato" value="[% p.folkeregsjekk_dato | html_entity %]" />
155
<input type="hidden" name="hjemmebibliotek" value="[% p.hjemmebibliotek | html_entity %]" />
156
<input type="hidden" name="opprettet" value="[% p.opprettet | html_entity %]" />
157
<input type="hidden" name="opprettet_av" value="[% p.opprettet_av | html_entity %]" />
158
<input type="hidden" name="sist_endret" value="[% p.sist_endret | html_entity %]" />
159
<input type="hidden" name="sist_endret_av" value="[% p.sist_endret_av | html_entity %]" />
160
<input type="hidden" name="gyldig_til" value="[% p.gyldig_til | html_entity %]" />
161
<input type="hidden" name="epost" value="[% p.epost | html_entity %]" />
162
<input type="hidden" name="epost_sjekk" value="[% p.epost_sjekk | html_entity %]" />
163
<input type="hidden" name="tlf_mobil" value="[% p.tlf_mobil | html_entity %]" />
164
<input type="hidden" name="tlf_hjemme" value="[% p.tlf_hjemme | html_entity %]" />
165
<input type="hidden" name="tlf_jobb" value="[% p.tlf_jobb | html_entity %]" />
166
<input type="hidden" name="prim_kontakt" value="[% p.prim_kontakt | html_entity %]" />
167
<input type="submit" value="Import this patron" />
168
as
169
<select name="categorycode">
170
[% FOREACH c IN categories %]
171
    <option value="[% c.categorycode %]">[% c.description %]</option>
172
[% END %]
173
</select>
174
</form>
175
</div>
176
[% END %]
(-)a/members/deletemem.pl (-3 / +22 lines)
Lines 31-36 use C4::Auth; Link Here
31
use C4::Members;
31
use C4::Members;
32
use C4::Branch; # GetBranches
32
use C4::Branch; # GetBranches
33
use C4::VirtualShelves (); #no import
33
use C4::VirtualShelves (); #no import
34
use Koha::NorwegianPatronDB qw( NLMarkForDeletion NLSync );
34
35
35
my $input = new CGI;
36
my $input = new CGI;
36
37
Lines 44-50 my ($template, $borrowernumber, $cookie) Link Here
44
                                        });
45
                                        });
45
46
46
#print $input->header;
47
#print $input->header;
47
my $member=$input->param('member');
48
my $member       = $input->param('member');
49
50
# Handle deletion from the Norwegian national patron database, if it is enabled
51
# If the "deletelocal" parameter is set to "false", the regular deletion will be
52
# short circuited, and only a deletion from the national database can be carried
53
# out. If "deletelocal" is set to "true", or not set to anything normal
54
# deletion will be done.
55
my $deletelocal  = $input->param('deletelocal')  eq 'false' ? 0 : 1; # Deleting locally is the default
56
if ( C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
57
    if ( $input->param('deleteremote') eq 'true' ) {
58
        # Mark for deletion, then try a live sync
59
        NLMarkForDeletion( $member );
60
        NLSync({ 'borrowernumber' => $member });
61
    }
62
}
63
48
my $issues = GetPendingIssues($member);     # FIXME: wasteful call when really, we only want the count
64
my $issues = GetPendingIssues($member);     # FIXME: wasteful call when really, we only want the count
49
my $countissues = scalar(@$issues);
65
my $countissues = scalar(@$issues);
50
66
Lines 80-86 my $dbh = C4::Context->dbh; Link Here
80
my $sth=$dbh->prepare("Select * from borrowers where guarantorid=?");
96
my $sth=$dbh->prepare("Select * from borrowers where guarantorid=?");
81
$sth->execute($member);
97
$sth->execute($member);
82
my $data=$sth->fetchrow_hashref;
98
my $data=$sth->fetchrow_hashref;
83
if ($countissues > 0 or $flags->{'CHARGES'}  or $data->{'borrowernumber'}){
99
if ($countissues > 0 or $flags->{'CHARGES'}  or $data->{'borrowernumber'} or $deletelocal == 0){
84
    #   print $input->header;
100
    #   print $input->header;
85
101
86
    my ($picture, $dberror) = GetPatronImage($bor->{'borrowernumber'});
102
    my ($picture, $dberror) = GetPatronImage($bor->{'borrowernumber'});
Lines 112-120 if ($countissues > 0 or $flags->{'CHARGES'} or $data->{'borrowernumber'}){ Link Here
112
    if ($flags->{'CHARGES'} ne '') {
128
    if ($flags->{'CHARGES'} ne '') {
113
        $template->param(charges => $flags->{'CHARGES'}->{'amount'});
129
        $template->param(charges => $flags->{'CHARGES'}->{'amount'});
114
    }
130
    }
115
    if ($data) {
131
    if ($data->{'borrowernumber'}) {
116
        $template->param(guarantees => 1);
132
        $template->param(guarantees => 1);
117
    }
133
    }
134
    if ($deletelocal == 0) {
135
        $template->param(keeplocal => 1);
136
    }
118
output_html_with_http_headers $input, $cookie, $template->output;
137
output_html_with_http_headers $input, $cookie, $template->output;
119
138
120
} else {
139
} else {
(-)a/members/memberentry.pl (-1 / +15 lines)
Lines 43-48 use C4::Branch; # GetBranches Link Here
43
use C4::Form::MessagingPreferences;
43
use C4::Form::MessagingPreferences;
44
use Koha::Borrower::Debarments;
44
use Koha::Borrower::Debarments;
45
use Koha::DateUtils;
45
use Koha::DateUtils;
46
use Koha::NorwegianPatronDB qw(NLGetSyncDataFromBorrowernumber);
46
47
47
use vars qw($debug);
48
use vars qw($debug);
48
49
Lines 416-427 if ((!$nok) and $nodouble and ($op eq 'insert' or $op eq 'save')){ Link Here
416
        if (C4::Context->preference('EnhancedMessagingPreferences') and $input->param('setting_messaging_prefs')) {
417
        if (C4::Context->preference('EnhancedMessagingPreferences') and $input->param('setting_messaging_prefs')) {
417
            C4::Form::MessagingPreferences::handle_form_action($input, { borrowernumber => $borrowernumber }, $template, 1, $newdata{'categorycode'});
418
            C4::Form::MessagingPreferences::handle_form_action($input, { borrowernumber => $borrowernumber }, $template, 1, $newdata{'categorycode'});
418
        }
419
        }
420
        # Try to do the live sync with the Norwegian national patron database, if it is enabled
421
        if ( exists $data{'borrowernumber'} && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
422
            NLSync({ 'borrowernumber' => $borrowernumber });
423
        }
419
	} elsif ($op eq 'save'){ 
424
	} elsif ($op eq 'save'){ 
420
		if ($NoUpdateLogin) {
425
		if ($NoUpdateLogin) {
421
			delete $newdata{'password'};
426
			delete $newdata{'password'};
422
			delete $newdata{'userid'};
427
			delete $newdata{'userid'};
423
		}
428
		}
424
		&ModMember(%newdata) unless scalar(keys %newdata) <= 1; # bug 4508 - avoid crash if we're not
429
        &ModMember(%newdata) unless scalar(keys %newdata) <= 1; # bug 4508 - avoid crash if we're not
425
                                                                # updating any columns in the borrowers table,
430
                                                                # updating any columns in the borrowers table,
426
                                                                # which can happen if we're only editing the
431
                                                                # which can happen if we're only editing the
427
                                                                # patron attributes or messaging preferences sections
432
                                                                # patron attributes or messaging preferences sections
Lines 470-475 if ($op eq "modify") { Link Here
470
    if ( $step == 4 ) {
475
    if ( $step == 4 ) {
471
        $template->param( categorycode => $borrower_data->{'categorycode'} );
476
        $template->param( categorycode => $borrower_data->{'categorycode'} );
472
    }
477
    }
478
    # Add sync data to the user data
479
    if ( C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
480
        my $sync = NLGetSyncDataFromBorrowernumber( $borrowernumber );
481
        if ( $sync ) {
482
            $template->param(
483
                sync => $sync->sync,
484
            );
485
        }
486
    }
473
}
487
}
474
if ( $op eq "duplicate" ) {
488
if ( $op eq "duplicate" ) {
475
    $template->param( updtype => 'I' );
489
    $template->param( updtype => 'I' );
(-)a/members/moremember.pl (+11 lines)
Lines 53-58 use C4::Form::MessagingPreferences; Link Here
53
use List::MoreUtils qw/uniq/;
53
use List::MoreUtils qw/uniq/;
54
use C4::Members::Attributes qw(GetBorrowerAttributes);
54
use C4::Members::Attributes qw(GetBorrowerAttributes);
55
use Koha::Borrower::Debarments qw(GetDebarments IsDebarred);
55
use Koha::Borrower::Debarments qw(GetDebarments IsDebarred);
56
use Koha::NorwegianPatronDB qw(NLGetSyncDataFromBorrowernumber);
56
#use Smart::Comments;
57
#use Smart::Comments;
57
#use Data::Dumper;
58
#use Data::Dumper;
58
use DateTime;
59
use DateTime;
Lines 290-295 else { Link Here
290
    }
291
    }
291
}
292
}
292
293
294
# Add sync data to the user data
295
if ( C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
296
    my $sync = NLGetSyncDataFromBorrowernumber( $borrowernumber );
297
    if ( $sync ) {
298
        $data->{'sync'}       = $sync->sync;
299
        $data->{'syncstatus'} = $sync->syncstatus;
300
        $data->{'lastsync'}   = $sync->lastsync;
301
    }
302
}
303
293
# check to see if patron's image exists in the database
304
# check to see if patron's image exists in the database
294
# basically this gives us a template var to condition the display of
305
# basically this gives us a template var to condition the display of
295
# patronimage related interface on
306
# patronimage related interface on
(-)a/members/nl-search.pl (+161 lines)
Line 0 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::Category;
41
use C4::Context;
42
use C4::Output;
43
use C4::Members;
44
use C4::Members::Attributes qw( SetBorrowerAttributes );
45
use Koha::NorwegianPatronDB qw( NLCheckSysprefs NLSearch NLDecodePin NLGetFirstname NLGetSurname NLSync );
46
use Koha::Database;
47
use Koha::DateUtils;
48
49
my $cgi = CGI->new;
50
my $dbh = C4::Context->dbh;
51
my $op  = $cgi->param('op');
52
53
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
54
    {
55
        template_name   => "members/nl-search.tt",
56
        query           => $cgi,
57
        type            => "intranet",
58
        authnotrequired => 0,
59
        flagsrequired   => { borrowers => 1 },
60
        debug           => 1,
61
    }
62
);
63
64
my $userenv = C4::Context->userenv;
65
66
# Check sysprefs
67
my $check_result = NLCheckSysprefs();
68
if ( $check_result->{'error'} == 1 ) {
69
    $template->param( 'error' => $check_result );
70
    output_html_with_http_headers $cgi, $cookie, $template->output;
71
    exit 0;
72
}
73
74
if ( $op && $op eq 'search' ) {
75
76
    # Get the string we are searching for
77
    my $identifier = $cgi->param('q');
78
    if ( $identifier ) {
79
        # Local search
80
        my @local_results = Search( $identifier );
81
        $template->param( 'local_result' => @local_results );
82
        # Search NL, unless we got at least one hit and further searching is
83
        # disabled
84
        if ( scalar @local_results == 0 || C4::Context->preference("NorwegianPatronDBSearchNLAfterLocalHit") == 1 ) {
85
            # TODO Check the format of the identifier before searching NL
86
            my $result = NLSearch( $identifier );
87
            unless ($result->fault) {
88
                my $r = $result->result();
89
                # Send the data to the template
90
                my @categories = C4::Category->all;
91
                $template->param(
92
                    'result'     => $r,
93
                    'categories' => \@categories,
94
                );
95
            } else {
96
                $template->param( 'error' => join ', ', $result->faultcode, $result->faultstring, $result->faultdetail );
97
            }
98
        }
99
        $template->param( 'q' => $identifier );
100
    }
101
102
} elsif ( $op && $op eq 'save' ) {
103
104
    # This is where we map from fields in NL to fields in Koha
105
    my %borrower = (
106
        'surname'      => NLGetSurname( $cgi->param('navn') ),
107
        'firstname'    => NLGetFirstname( $cgi->param('navn') ),
108
        'sex'          => $cgi->param('kjonn'),
109
        'dateofbirth'  => $cgi->param('fdato'),
110
        'cardnumber'   => $cgi->param('lnr'),
111
        'userid'       => $cgi->param('lnr'),
112
        'address'      => $cgi->param('p_adresse1'),
113
        'address2'     => $cgi->param('p_adresse2'),
114
        'zipcode'      => $cgi->param('p_postnr'),
115
        'city'         => $cgi->param('p_sted'),
116
        'country'      => $cgi->param('p_land'),
117
        'B_address'    => $cgi->param('m_adresse1'),
118
        'B_address2'   => $cgi->param('m_adresse2'),
119
        'B_zipcode'    => $cgi->param('m_postnr'),
120
        'B_city'       => $cgi->param('m_sted'),
121
        'B_country'    => $cgi->param('m_land'),
122
        'password'     => NLDecodePin( $cgi->param('pin') ),
123
        'dateexpiry'   => $cgi->param('gyldig_til'),
124
        'email'        => $cgi->param('epost'),
125
        'mobile'       => $cgi->param('tlf_mobil'),
126
        'phone'        => $cgi->param('tlf_hjemme'),
127
        'phonepro'     => $cgi->param('tlf_jobb'),
128
        'branchcode'   => $userenv->{'branch'},
129
        'categorycode' => $cgi->param('categorycode'),
130
    );
131
    # Add the new patron
132
    my $borrowernumber = &AddMember(%borrower);
133
    if ( $borrowernumber ) {
134
        # Add extended patron attributes
135
        SetBorrowerAttributes($borrowernumber, [
136
            { code => 'fnr', value => $cgi->param('fnr_hash') },
137
        ] );
138
        # Override the default sync data created by AddMember
139
        my $borrowersync = Koha::Database->new->schema->resultset('BorrowerSync')->find({
140
            'synctype'       => 'norwegianpatrondb',
141
            'borrowernumber' => $borrowernumber,
142
        });
143
        $borrowersync->update({ 'syncstatus', 'synced' });
144
        $borrowersync->update({ 'lastsync',   $cgi->param('sist_endret') });
145
        $borrowersync->update({ 'hashed_pin', $cgi->param('pin') });
146
        # Try to sync in real time. If this fails it will be picked up by the cronjob
147
        NLSync({ 'borrowernumber' => $borrowernumber });
148
        # Redirect to the edit screen
149
        print $cgi->redirect( "/cgi-bin/koha/members/memberentry.pl?op=modify&destination=circ&borrowernumber=$borrowernumber" );
150
    } else {
151
        $template->param( 'error' => 'COULD_NOT_ADD_PATRON' );
152
    }
153
}
154
155
output_html_with_http_headers $cgi, $cookie, $template->output;
156
157
=head1 AUTHOR
158
159
Magnus Enger <digitalutvikling@gmail.com>
160
161
=cut
(-)a/misc/cronjobs/nl-sync-from-koha.pl (+184 lines)
Line 0 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
=cut
50
51
my $check_result = NLCheckSysprefs();
52
if ( $check_result->{'error'} == 1 ) {
53
    if ( $check_result->{'nlenabled'} == 0 ) { say "* Please activate this function with the NorwegianPatronDBEnable system preference." };
54
    if ( $check_result->{'endpoint'}  == 0 ) { say "* Please specify an endpoint with the NorwegianPatronDBEndpoint system preference." };
55
    if ( $check_result->{'userpass'}  == 0 ) { say "* Please fill in the NorwegianPatronDBUsername and NorwegianPatronDBPassword system preferences." };
56
    exit 0;
57
}
58
59
unless ( $run ) {
60
    say "* You have not specified --run, no real syncing will be done.";
61
}
62
63
=head2 Find patrons that need to be synced
64
65
=cut
66
67
my @needs_sync = Koha::Database->new->schema->resultset('BorrowerSync')->search({
68
    -and => [
69
      sync     => 1,
70
      synctype => 'norwegianpatrondb',
71
      -or => [
72
        syncstatus => 'edited',
73
        syncstatus => 'new',
74
        syncstatus => 'delete',
75
      ],
76
    ],
77
});
78
79
=head2 Do the actual sync
80
81
=cut
82
83
my $sync_success = 0;
84
my $sync_failed  = 0;
85
foreach my $borrower ( @needs_sync ) {
86
    my $cardnumber = $borrower->borrowernumber->cardnumber;
87
    my $firstname  = $borrower->borrowernumber->firstname;
88
    my $surname    = $borrower->borrowernumber->surname;
89
    my $syncstatus = $borrower->syncstatus;
90
    say "*** Syncing patron: $cardnumber - $firstname $surname ($syncstatus)" if $verbose;
91
    if ( $run ) {
92
        my $response = NLSync({ 'patron' => $borrower->borrowernumber });
93
        if ( $response ) {
94
            my $result = $response->result;
95
            if ( $result->{'status'} && $result->{'status'} == 1 ) {
96
                $sync_success++;
97
            } else {
98
                $sync_failed++;
99
            }
100
            if ( $result->{'melding'} && $verbose ) {
101
                say $result->{'melding'};
102
            }
103
        }
104
    }
105
}
106
107
=head2 Summarize if verbose mode is enabled
108
109
=cut
110
111
if ( $verbose ) {
112
    say "-----------------------------";
113
    say "Sync succeeded: $sync_success";
114
    say "Sync failed   : $sync_failed";
115
}
116
117
=head1 OPTIONS
118
119
=over 4
120
121
=item B<-r, --run>
122
123
Actually carry out syncing operations. Without this option, the script will
124
only report what it would have done, but not change any data, locally or
125
remotely.
126
127
=item B<-v --verbose>
128
129
Report on the progress of the script.
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 $verbose = '';
148
  my $debug   = '';
149
  my $help    = '';
150
151
  GetOptions (
152
    'r|run'     => \$run,
153
    'v|verbose' => \$verbose,
154
    'd|debug'   => \$debug,
155
    'h|?|help'  => \$help
156
  );
157
158
  pod2usage( -exitval => 0 ) if $help;
159
160
  return ( $run, $verbose, $debug );
161
162
}
163
164
=head1 AUTHOR
165
166
Magnus Enger <digitalutvikling@gmail.com>
167
168
=head1 COPYRIGHT
169
170
Copyright 2014 Oslo Public Library
171
172
=head1 LICENSE
173
174
This file is part of Koha.
175
176
Koha is free software; you can redistribute it and/or modify it under the terms
177
of the GNU General Public License as published by the Free Software Foundation;
178
either version 3 of the License, or (at your option) any later version.
179
180
You should have received a copy of the GNU General Public License along with
181
Koha; if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
182
Fifth Floor, Boston, MA 02110-1301 USA.
183
184
=cut
(-)a/misc/cronjobs/nl-sync-to-koha.pl (+192 lines)
Line 0 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;
16
use C4::Members::Attributes qw( UpdateBorrowerAttribute );
17
use Koha::NorwegianPatronDB qw( NLCheckSysprefs NLGetChanged );
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::Database->new->schema->resultset('Borrower')->find({
74
            'cardnumber' => $patron->{'cardnumber'}
75
        });
76
        my $borrowernumber = $stored_patron->borrowernumber;
77
        if ( $run ) {
78
            # Call ModMember
79
            my $success = ModMember(
80
                'borrowernumber' => $borrowernumber,
81
                %clean_patron,
82
            );
83
            if ( $success ) {
84
                # Get the sync object
85
                my $sync = Koha::Database->new->schema->resultset('BorrowerSync')->find({
86
                    'synctype'       => 'norwegianpatrondb',
87
                    'borrowernumber' => $borrowernumber,
88
                });
89
                # Update the syncstatus to 'synced'
90
                $sync->update( { 'syncstatus' => 'synced' } );
91
                # Update the 'synclast' attribute with the "server time" ("server_tid") returned by the method
92
                $sync->update( { 'lastsync' => $result->{'result'}->{'server_tid'} } );
93
                # Save social security number as attribute
94
                UpdateBorrowerAttribute(
95
                    $borrowernumber,
96
                    { code => 'fnr', attribute => $patron->{'_extra'}->{'socsec'} },
97
                );
98
                $sync_success++;
99
            } else {
100
                $sync_failed++;
101
            }
102
        }
103
    } else {
104
        say "Skipped, local change" if $verbose;
105
        $skipped_local_change++;
106
    }
107
}
108
109
if ( $verbose ) {
110
    say "-----------------------------";
111
    say "Sync succeeded:       $sync_success";
112
    say "Sync failed   :       $sync_failed";
113
    say "Skipped local change: $skipped_local_change";
114
}
115
116
=head1 OPTIONS
117
118
=over 4
119
120
=item B<-r, --run>
121
122
Actually carry out syncing operations. Without this option, the script will
123
only report what it would have done, but not change any data, locally or
124
remotely.
125
126
=item B<-v --verbose>
127
128
Report on the progress of the script.
129
130
=item B<-f --from>
131
132
Date and time to sync from, if this should be different from "1 second past
133
midnight of the day before". The date should be in this format:
134
135
    2014-06-03T00:00:01
136
137
=item B<-d --debug>
138
139
Even more output.
140
141
=item B<-h, -?, --help>
142
143
Prints this help message and exits.
144
145
=back
146
147
=cut
148
149
sub get_options {
150
151
  # Options
152
  my $run     = '',
153
  my $from    = '',
154
  my $verbose = '';
155
  my $debug   = '';
156
  my $help    = '';
157
158
  GetOptions (
159
    'r|run'     => \$run,
160
    'f|from=s'  => \$from,
161
    'v|verbose' => \$verbose,
162
    'd|debug'   => \$debug,
163
    'h|?|help'  => \$help
164
  );
165
166
  pod2usage( -exitval => 0 ) if $help;
167
168
  return ( $run, $from, $verbose, $debug );
169
170
}
171
172
=head1 AUTHOR
173
174
Magnus Enger <digitalutvikling@gmail.com>
175
176
=head1 COPYRIGHT
177
178
Copyright 2014 Oslo Public Library
179
180
=head1 LICENSE
181
182
This file is part of Koha.
183
184
Koha is free software; you can redistribute it and/or modify it under the terms
185
of the GNU General Public License as published by the Free Software Foundation;
186
either version 3 of the License, or (at your option) any later version.
187
188
You should have received a copy of the GNU General Public License along with
189
Koha; if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
190
Fifth Floor, Boston, MA 02110-1301 USA.
191
192
=cut
(-)a/t/NorwegianPatronDB.t (-1 / +583 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
use Modern::Perl;
4
use Test::More;
5
use Test::MockModule;
6
use t::lib::Mocks;
7
use Data::Dumper;
8
use SOAP::Lite;
9
10
# Check that all the modules we need are installed, or bail out
11
BEGIN {
12
    eval {
13
        require Test::DBIx::Class;
14
        1;
15
    } or do {
16
        plan skip_all => "Test::DBIx::Class is not available";
17
    };
18
}
19
BEGIN {
20
    eval {
21
        require SOAP::Lite;
22
        1;
23
    } or do {
24
        plan skip_all => "SOAP::Lite is not available";
25
    };
26
}
27
BEGIN {
28
    eval {
29
        require Crypt::GCrypt;
30
        1;
31
    } or do {
32
        plan skip_all => "Crypt::GCrypt is not available";
33
    };
34
}
35
BEGIN {
36
    eval {
37
        require Convert::BaseN;
38
        1;
39
    } or do {
40
        plan skip_all => "Convert::BaseN is not available";
41
    };
42
}
43
44
use Test::DBIx::Class {
45
    schema_class => 'Koha::Schema',
46
    connect_info => ['dbi:SQLite:dbname=:memory:','',''],
47
    connect_opts => { name_sep => '.', quote_char => '`', },
48
    fixture_class => '::Populate',
49
}, 'Borrower', 'BorrowerSync';
50
51
# Make the code in the module use our mocked Koha::Schema/Koha::Database
52
my $db = Test::MockModule->new('Koha::Database');
53
$db->mock(
54
    # Schema() gives us the DB connection set up by Test::DBIx::Class
55
    _new_schema => sub { return Schema(); }
56
);
57
58
fixtures_ok [
59
    'Borrower' => [
60
        [qw/firstname surname borrowernumber address city/],
61
        ['Test', 'Borrower', 1, 'Test road', 'Test city'],
62
        ['Test', 'Borrower', 2, 'Test road', 'Test city'],
63
        ['Test', 'Borrower', 3, 'Test road', 'Test city'],
64
        ['Test', 'Borrower', 4, 'Test road', 'Test city'],
65
    ],
66
    'BorrowerSync' => [
67
        [qw/borrowernumber sync syncstatus lastsync hashed_pin synctype/],
68
        [1, 1, 'new',    '2014-03-31T12:35:14', 'abc', 'norwegianpatrondb' ],
69
        [2, 1, 'edited', '2014-03-31T12:35:14', 'abc', 'norwegianpatrondb' ],
70
        [3, 1, 'new',    '2014-03-31T12:35:14', 'abc', 'norwegianpatrondb' ],
71
        [4, 1, 'new',    '2014-03-31T12:35:14', 'abc', 'norwegianpatrondb' ],
72
    ],
73
], 'installed some fixtures';
74
75
=head1 LOADING THE MODULE
76
77
=cut
78
79
BEGIN { use_ok( 'Koha::NorwegianPatronDB', ':all' ) }
80
81
82
=head1 UTILITY SUBROUTINES
83
84
=head2 NLCheckSysprefs
85
86
Relevant sysprefs:
87
88
=over 4
89
90
=item * NorwegianPatronDBEnable
91
92
=item * NorwegianPatronDBEndpoint
93
94
=item * NorwegianPatronDBUsername
95
96
=item * NorwegianPatronDBPassword
97
98
=back
99
100
=cut
101
102
t::lib::Mocks::mock_preference('NorwegianPatronDBEnable',   0);
103
t::lib::Mocks::mock_preference('NorwegianPatronDBEndpoint', '');
104
t::lib::Mocks::mock_preference('NorwegianPatronDBUsername', '');
105
t::lib::Mocks::mock_preference('NorwegianPatronDBPassword', '');
106
ok( my $result = NLCheckSysprefs(), 'call NLCheckSysprefs() ok' );
107
is( $result->{ 'error' },     1, 'error detected' );
108
is( $result->{ 'nlenabled' }, 0, 'NL is not enabled' );
109
is( $result->{ 'endpoint' },  0, 'an endpoint is not specified' );
110
is( $result->{ 'userpass' },  0, 'username and/or password is missing' );
111
112
t::lib::Mocks::mock_preference('NorwegianPatronDBEnable',   1);
113
ok( $result = NLCheckSysprefs(), 'call NLCheckSysprefs() ok' );
114
is( $result->{ 'error' },     1, 'error detected' );
115
is( $result->{ 'nlenabled' }, 1, 'NL is enabled' );
116
is( $result->{ 'endpoint' },  0, 'an endpoint is not specified' );
117
is( $result->{ 'userpass' },  0, 'username and/or password is missing' );
118
119
t::lib::Mocks::mock_preference('NorwegianPatronDBEnable',   0);
120
t::lib::Mocks::mock_preference('NorwegianPatronDBUsername', 'user');
121
t::lib::Mocks::mock_preference('NorwegianPatronDBPassword', 'pass');
122
ok( $result = NLCheckSysprefs(), 'call NLCheckSysprefs() ok' );
123
is( $result->{ 'error' },     1, 'error detected' );
124
is( $result->{ 'nlenabled' }, 0, 'NL is not enabled' );
125
is( $result->{ 'endpoint' },  0, 'an endpoint is not specified' );
126
is( $result->{ 'userpass' },  1, 'username and/or password is present' );
127
128
t::lib::Mocks::mock_preference('NorwegianPatronDBEnable',   1);
129
t::lib::Mocks::mock_preference('NorwegianPatronDBEndpoint', 'http://example.com/');
130
ok( $result = NLCheckSysprefs(), 'call NLCheckSysprefs() ok' );
131
is( $result->{ 'error' },     0, 'no error detected' );
132
is( $result->{ 'nlenabled' }, 1, 'NL is enabled' );
133
is( $result->{ 'endpoint' },  1, 'an endpoint is specified' );
134
is( $result->{ 'userpass' },  1, 'username and/or password is present' );
135
136
=head2 NLGetFirstname and NLGetSurname
137
138
=cut
139
140
my $firstname = 'Firstname';
141
my $surname   = 'Surname';
142
my $fullname  = "$surname, $firstname";
143
my $wrongname = "$surname $firstname";
144
145
is( NLGetFirstname( $fullname  ), $firstname, 'can get firstname from name' );
146
is( NLGetSurname(   $fullname  ), $surname,   'can get surname from name' );
147
is( NLGetFirstname( $wrongname ), $wrongname, 'returns full string when name misses comma' );
148
is( NLGetSurname(   $wrongname ), $wrongname, 'returns full string when name misses comma' );
149
150
=head2 NLDecodePin and NLEncryptPIN
151
152
=cut
153
154
my $pin  = '1234';
155
my $hash = NLEncryptPIN( $pin );
156
157
is( NLEncryptPIN( $pin ), $hash, 'NLEncryptPIN works' );
158
is( NLDecodePin( $hash ), $pin, 'NLDecodePin works' );
159
160
=head2 NLUpdateHashedPIN
161
162
TODO
163
164
=cut
165
166
is ( BorrowerSync->find({ 'borrowernumber' => 1 })->get_column('hashed_pin'), 'abc', 'hashed_pin is "abc"' );
167
# Set a new pin
168
my $new_pin = 'bcd';
169
ok( NLUpdateHashedPIN( 1, $new_pin ), 'NLUpdateHashedPIN runs ok' );
170
# Hash the new pin and compare it to the one stored in the database
171
my $hashed_pin = Koha::NorwegianPatronDB::_encrypt_pin( $new_pin );
172
is ( BorrowerSync->find({ 'borrowernumber' => 1 })->get_column('hashed_pin'), $hashed_pin, 'hashed_pin was updated' );
173
174
=head2 NLMarkForDeletion
175
176
=cut
177
178
is ( BorrowerSync->find({ 'borrowernumber' => 3 })->get_column('syncstatus'), 'new', 'syncstatus is "new"' );
179
ok( NLMarkForDeletion( 3 ), 'NLMarkForDeletion runs ok' );
180
# Check that the syncstatus was updated. Note: We will use this status later, to check syncing of deleted borrowers
181
is ( BorrowerSync->find({ 'borrowernumber' => 3 })->get_column('syncstatus'), 'delete', 'syncstatus is "delete"' );
182
183
=head2 NLGetSyncDataFromBorrowernumber
184
185
=cut
186
187
ok( my $sync_data = NLGetSyncDataFromBorrowernumber( 1 ), 'NLGetSyncDataFromBorrowernumber runs ok' );
188
isa_ok( $sync_data, 'Koha::Schema::Result::BorrowerSync' );
189
is( $sync_data->sync, 1, 'the sync is on' );
190
is( $sync_data->syncstatus, 'new', 'syncstatus is "new"' );
191
is( $sync_data->lastsync, '2014-03-31T12:35:14', 'lastsync is ok' );
192
is( $sync_data->hashed_pin, $hashed_pin, 'hashed_pin is ok' );
193
194
=head1 SUBROUTINES THAT TALK TO SOAP
195
196
=head2 NLSearch
197
198
=cut
199
200
my $lite = Test::MockModule->new('SOAP::Lite');
201
202
# Mock a successfull call to the "hent" method
203
$lite->mock(
204
    hent => sub { return SOAP::Deserializer->deserialize( hent_success() )->result; }
205
);
206
ok( my $res = NLSearch( '12345678910' ), 'successfull call to NLSearch' );
207
is( $res->{'antall_poster_returnert'}, 1, 'got 1 record' );
208
isa_ok( $res, "Resultat" );
209
isa_ok( $res->{'respons_poster'}, "LaanerListe" );
210
isa_ok( $res->{'respons_poster'}[0], "Laaner" );
211
212
# Mock an unsuccessfull call to the "hent" method
213
$lite->mock(
214
    hent => sub { return SOAP::Deserializer->deserialize( hent_failure() )->result; }
215
);
216
ok( $res = NLSearch( '12345678910' ), 'call to NLSearch with an illegal argument' );
217
is( $res->{'antall_poster_returnert'}, 0, 'got 0 records' );
218
isa_ok( $res, "Resultat" );
219
like( $res->{'melding'}, qr/Ulovlig argument: hverken LNR eller FNR_HASH/, "got expected error message for an illegal identifier" );
220
221
=head2 NLSync
222
223
=head3 New patron
224
225
=cut
226
227
my $borrower = Borrower->find({ 'borrowernumber' => 1 });
228
$lite->mock(
229
    nyPost => sub { return SOAP::Deserializer->deserialize( nyPost_success() )->result; }
230
);
231
is ( BorrowerSync->find({ 'borrowernumber' => 1 })->get_column('syncstatus'), 'new', 'patron is new' );
232
ok ( $result = NLSync({ 'patron' => $borrower }), 'successfull call to NLSync via patron ("nyPost")' );
233
is ( BorrowerSync->find({ 'borrowernumber' => 1 })->get_column('syncstatus'), 'synced', 'patron is synced' );
234
235
# Now do the same test, but pass in a borrowernumber, not a Koha::Schema::Result::Borrower
236
is ( BorrowerSync->find({ 'borrowernumber' => 4 })->get_column('syncstatus'), 'new', 'patron is new' );
237
ok ( $result = NLSync({ 'borrowernumber' => 4 }), 'successfull call to NLSync via borrowernumber ("nyPost")' );
238
is ( BorrowerSync->find({ 'borrowernumber' => 4 })->get_column('syncstatus'), 'synced', 'patron is synced' );
239
240
=head3 Edited patron
241
242
=cut
243
244
ok ( $borrower = Borrower->find({ 'borrowernumber' => 2 }), 'find our "edited" mock patron' );
245
$lite->mock(
246
    endre => sub { return SOAP::Deserializer->deserialize( endre_success() )->result; }
247
);
248
is ( BorrowerSync->find({ 'borrowernumber' => 2 })->get_column('syncstatus'), 'edited', 'patron is edited' );
249
ok ( $result = NLSync({ 'patron' => $borrower }), 'successfull call to NLSync ("endre")' );
250
is ( BorrowerSync->find({ 'borrowernumber' => 2 })->get_column('syncstatus'), 'synced', 'patron is synced' );
251
252
=head3 Deleted patron
253
254
=cut
255
256
ok ( $borrower = Borrower->find({ 'borrowernumber' => 3 }), 'find our "deleted" mock patron' );
257
$lite->mock(
258
    slett => sub { return SOAP::Deserializer->deserialize( endre_success() )->result; }
259
);
260
is ( BorrowerSync->find({ 'borrowernumber' => 3 })->get_column('syncstatus'), 'delete', 'patron is marked for deletion' );
261
ok ( $result = NLSync({ 'patron' => $borrower }), 'successfull call to NLSync ("slett")' );
262
is ( BorrowerSync->find({ 'borrowernumber' => 3 })->get_column('sync'), 0, 'sync is now disabled' );
263
264
=head2 NLGetChanged
265
266
=cut
267
268
# Mock a successfull call to the "soekEndret" method
269
$lite->mock(
270
    soekEndret => sub { return SOAP::Deserializer->deserialize( soekEndret_success() ); }
271
);
272
ok( $res = NLGetChanged(), 'successfull call to NLGetChanged - 2 results' );
273
is( $res->{'melding'}, 'OK', 'got "OK"' );
274
is( $res->{'antall_poster_returnert'}, 2, 'got 2 records' );
275
isa_ok( $res, "Resultat" );
276
isa_ok( $res->{'respons_poster'}, "LaanerListe" );
277
isa_ok( $res->{'respons_poster'}[0], "Laaner" );
278
279
280
# Mock a successfull call to the "soekEndret" method, but with zero new records
281
$lite->mock(
282
    soekEndret => sub { return SOAP::Deserializer->deserialize( soekEndret_zero_new() ); }
283
);
284
ok( $res = NLGetChanged(), 'successfull call to NLGetChanged - 0 results' );
285
is( $res->{'melding'}, 'ingen treff', 'got "ingen treff"' );
286
is( $res->{'antall_poster_returnert'}, 0, 'got 0 records' );
287
is( $res->{'antall_treff'}, 0, 'got 0 records' );
288
289
done_testing();
290
291
=head1 SAMPLE SOAP XML RESPONSES
292
293
These responses can be gathered by setting "outputxml()" to true on the SOAP
294
client:
295
296
    my $client = SOAP::Lite
297
        ->on_action( sub { return '""';})
298
        ->uri('http://lanekortet.no')
299
        ->proxy('https://fl.lanekortet.no/laanekort/fl_test.php')
300
        ->outputxml(1);
301
    my $response = $client->slett( $x );
302
    say $response;
303
304
Pretty formatting can be achieved by piping the output from a test script
305
through xmllint:
306
307
    perl my_test_script.pl > xmllint --format -
308
309
=cut
310
311
sub slett_success {
312
313
    return <<'ENDRESPONSE';
314
<?xml version="1.0" encoding="UTF-8"?>
315
<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/">
316
  <SOAP-ENV:Body>
317
    <ns1:slettResponse>
318
      <return xsi:type="ns1:Svar">
319
        <status xsi:type="xsd:boolean">true</status>
320
        <melding xsi:type="xsd:string">Test Testersen (1973-08-11) er slettet fra registeret</melding>
321
        <lnr xsi:type="xsd:string">N000106188</lnr>
322
        <server_tid xsi:type="xsd:string">2014-06-02T16:51:58</server_tid>
323
      </return>
324
    </ns1:slettResponse>
325
  </SOAP-ENV:Body>
326
</SOAP-ENV:Envelope>
327
ENDRESPONSE
328
329
}
330
331
sub endre_success {
332
333
    return <<'ENDRESPONSE';
334
<?xml version="1.0" encoding="UTF-8"?>
335
<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/">
336
  <SOAP-ENV:Body>
337
    <ns1:endreResponse>
338
      <return xsi:type="ns1:Svar">
339
        <status xsi:type="xsd:boolean">true</status>
340
        <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>
341
        <lnr xsi:type="xsd:string">N000106188</lnr>
342
        <server_tid xsi:type="xsd:string">2014-06-02T16:42:32</server_tid>
343
      </return>
344
    </ns1:endreResponse>
345
  </SOAP-ENV:Body>
346
</SOAP-ENV:Envelope>
347
ENDRESPONSE
348
349
}
350
351
sub nyPost_success {
352
353
    return <<'ENDRESPONSE';
354
<?xml version="1.0" encoding="UTF-8"?>
355
<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/">
356
  <SOAP-ENV:Body>
357
    <ns1:nyPostResponse>
358
      <return xsi:type="ns1:Svar">
359
        <status xsi:type="xsd:boolean">true</status>
360
        <melding xsi:type="xsd:string">Ny post er opprettet</melding>
361
        <lnr xsi:type="xsd:string">N000106188</lnr>
362
        <server_tid xsi:type="xsd:string">2014-06-02T14:10:09</server_tid>
363
      </return>
364
    </ns1:nyPostResponse>
365
  </SOAP-ENV:Body>
366
</SOAP-ENV:Envelope>
367
ENDRESPONSE
368
369
}
370
371
sub soekEndret_success {
372
373
return <<'ENDRESPONSE';
374
<?xml version="1.0" encoding="UTF-8"?>
375
<SOAP-ENV:Envelope
376
    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
377
    xmlns:ns1="http://lanekortet.no"
378
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
379
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
380
    xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
381
    SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
382
  <SOAP-ENV:Body>
383
    <ns1:soekEndretResponse>
384
      <return xsi:type="ns1:Resultat">
385
        <status xsi:type="xsd:boolean">true</status>
386
        <melding xsi:type="xsd:string">OK</melding>
387
        <antall_treff xsi:type="xsd:int">2</antall_treff>
388
        <antall_poster_returnert xsi:type="xsd:int">2</antall_poster_returnert>
389
        <neste_indeks xsi:type="xsd:int">0</neste_indeks>
390
        <respons_poster SOAP-ENC:arrayType="ns1:Laaner[2]" xsi:type="ns1:LaanerListe">
391
          <item xsi:type="ns1:Laaner">
392
            <lnr xsi:type="xsd:string">N000106186</lnr>
393
            <navn xsi:type="xsd:string">Hansen, Hanne</navn>
394
            <p_adresse1 xsi:type="xsd:string"/>
395
            <p_adresse2 xsi:type="xsd:string"/>
396
            <p_postnr xsi:type="xsd:string"/>
397
            <p_sted xsi:type="xsd:string">BØDØ</p_sted>
398
            <p_land xsi:type="xsd:string">no</p_land>
399
            <p_sjekk xsi:type="xsd:string">0</p_sjekk>
400
            <m_adresse1 xsi:type="xsd:string"/>
401
            <m_adresse2 xsi:type="xsd:string"/>
402
            <m_postnr xsi:type="xsd:string"/>
403
            <m_sted xsi:type="xsd:string"/>
404
            <m_land xsi:type="xsd:string"/>
405
            <m_sjekk xsi:type="xsd:string">0</m_sjekk>
406
            <m_gyldig_til xsi:type="xsd:string">0000-00-00</m_gyldig_til>
407
            <tlf_hjemme xsi:type="xsd:string"/>
408
            <tlf_jobb xsi:type="xsd:string"/>
409
            <tlf_mobil xsi:type="xsd:string"/>
410
            <epost xsi:type="xsd:string"/>
411
            <epost_sjekk xsi:type="xsd:string"/>
412
            <prim_kontakt xsi:type="xsd:string"/>
413
            <hjemmebibliotek xsi:type="xsd:string">5180401</hjemmebibliotek>
414
            <fdato xsi:type="xsd:string">1994-04-08</fdato>
415
            <fnr_hash xsi:type="xsd:string">11087395628</fnr_hash>
416
            <kjonn xsi:type="xsd:string">F</kjonn>
417
            <pin xsi:type="xsd:string">89308dfc85ee7a5826ae14e2d8efad1e</pin>
418
            <passord xsi:type="xsd:string"/>
419
            <feide xsi:type="xsd:string">0</feide>
420
            <opprettet xsi:type="xsd:string">2014-04-28T15:20:38</opprettet>
421
            <opprettet_av xsi:type="xsd:string">5180401</opprettet_av>
422
            <sist_endret xsi:type="xsd:string">2014-04-28T15:20:38</sist_endret>
423
            <sist_endret_av xsi:type="xsd:string">5180401</sist_endret_av>
424
            <folkeregsjekk_dato xsi:type="xsd:string">0000-00-00</folkeregsjekk_dato>
425
          </item>
426
          <item xsi:type="ns1:Laaner">
427
            <lnr xsi:type="xsd:string">N000106184</lnr>
428
            <navn xsi:type="xsd:string">Enger, Magnus</navn>
429
            <p_adresse1 xsi:type="xsd:string">Svarthammarveien 633333</p_adresse1>
430
            <p_adresse2 xsi:type="xsd:string"/>
431
            <p_postnr xsi:type="xsd:string">8015</p_postnr>
432
            <p_sted xsi:type="xsd:string">Bodø</p_sted>
433
            <p_land xsi:type="xsd:string">no</p_land>
434
            <p_sjekk xsi:type="xsd:string">0</p_sjekk>
435
            <m_adresse1 xsi:type="xsd:string"/>
436
            <m_adresse2 xsi:type="xsd:string"/>
437
            <m_postnr xsi:type="xsd:string"/>
438
            <m_sted xsi:type="xsd:string"/>
439
            <m_land xsi:type="xsd:string">no</m_land>
440
            <m_sjekk xsi:type="xsd:string">0</m_sjekk>
441
            <m_gyldig_til xsi:type="xsd:string">0000-00-00</m_gyldig_til>
442
            <tlf_hjemme xsi:type="xsd:string">95158548</tlf_hjemme>
443
            <tlf_jobb xsi:type="xsd:string"/>
444
            <tlf_mobil xsi:type="xsd:string"/>
445
            <epost xsi:type="xsd:string">magnus@enger.priv.no</epost>
446
            <epost_sjekk xsi:type="xsd:string"/>
447
            <prim_kontakt xsi:type="xsd:string"/>
448
            <hjemmebibliotek xsi:type="xsd:string">5180401</hjemmebibliotek>
449
            <fdato xsi:type="xsd:string">1973-08-11</fdato>
450
            <fnr_hash xsi:type="xsd:string">11087345795</fnr_hash>
451
            <kjonn xsi:type="xsd:string">M</kjonn>
452
            <pin xsi:type="xsd:string">a632c504b8c4fba3149115cb07e0796c</pin>
453
            <passord xsi:type="xsd:string"/>
454
            <feide xsi:type="xsd:string">0</feide>
455
            <opprettet xsi:type="xsd:string">2014-04-28T14:52:02</opprettet>
456
            <opprettet_av xsi:type="xsd:string">5180401</opprettet_av>
457
            <sist_endret xsi:type="xsd:string">2014-05-13T11:01:33</sist_endret>
458
            <sist_endret_av xsi:type="xsd:string">5180401</sist_endret_av>
459
            <folkeregsjekk_dato xsi:type="xsd:string">0000-00-00</folkeregsjekk_dato>
460
          </item>
461
        </respons_poster>
462
        <server_tid xsi:type="xsd:string">2014-05-16T14:44:44</server_tid>
463
      </return>
464
    </ns1:soekEndretResponse>
465
  </SOAP-ENV:Body>
466
</SOAP-ENV:Envelope>
467
ENDRESPONSE
468
}
469
470
sub soekEndret_zero_new {
471
    return <<'ENDRESPONSE';
472
<?xml version="1.0" encoding="UTF-8"?>
473
    <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/">
474
      <SOAP-ENV:Body>
475
        <ns1:soekEndretResponse>
476
          <return xsi:type="ns1:Resultat">
477
            <status xsi:type="xsd:boolean">false</status>
478
            <melding xsi:type="xsd:string">ingen treff</melding>
479
            <antall_treff xsi:type="xsd:int">0</antall_treff>
480
            <antall_poster_returnert xsi:type="xsd:int">0</antall_poster_returnert>
481
            <neste_indeks xsi:type="xsd:int">0</neste_indeks>
482
            <respons_poster SOAP-ENC:arrayType="ns1:Laaner[0]" xsi:type="ns1:LaanerListe"/>
483
            <server_tid xsi:type="xsd:string">2014-05-20T13:02:02</server_tid>
484
          </return>
485
        </ns1:soekEndretResponse>
486
      </SOAP-ENV:Body>
487
    </SOAP-ENV:Envelope>
488
ENDRESPONSE
489
}
490
491
sub hent_failure {
492
    return <<'ENDRESPONSE';
493
<?xml version="1.0" encoding="UTF-8"?>
494
<SOAP-ENV:Envelope
495
    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
496
    xmlns:ns1="http://lanekortet.no"
497
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
498
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
499
    xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
500
    SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
501
  <SOAP-ENV:Body>
502
    <ns1:hentResponse>
503
      <return xsi:type="ns1:Resultat">
504
        <status xsi:type="xsd:boolean">false</status>
505
        <melding xsi:type="xsd:string">hent: Ulovlig argument: hverken LNR eller FNR_HASH</melding>
506
        <antall_treff xsi:type="xsd:int">0</antall_treff>
507
        <antall_poster_returnert xsi:type="xsd:int">0</antall_poster_returnert>
508
        <neste_indeks xsi:type="xsd:int">0</neste_indeks>
509
        <respons_poster SOAP-ENC:arrayType="ns1:Laaner[0]" xsi:type="ns1:LaanerListe"/>
510
        <server_tid xsi:type="xsd:string">2014-05-15T10:56:24</server_tid>
511
      </return>
512
    </ns1:hentResponse>
513
  </SOAP-ENV:Body>
514
</SOAP-ENV:Envelope>
515
ENDRESPONSE
516
517
}
518
519
sub hent_success {
520
521
return <<'ENDRESPONSE';
522
<?xml version="1.0" encoding="UTF-8"?>
523
<SOAP-ENV:Envelope
524
    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
525
    xmlns:ns1="http://lanekortet.no"
526
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
527
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
528
    xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
529
    SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
530
  <SOAP-ENV:Body>
531
    <ns1:hentResponse>
532
      <return xsi:type="ns1:Resultat">
533
        <status xsi:type="xsd:boolean">true</status>
534
        <melding xsi:type="xsd:string">OK</melding>
535
        <antall_treff xsi:type="xsd:int">1</antall_treff>
536
        <antall_poster_returnert xsi:type="xsd:int">1</antall_poster_returnert>
537
        <neste_indeks xsi:type="xsd:int">0</neste_indeks>
538
        <respons_poster SOAP-ENC:arrayType="ns1:Laaner[1]" xsi:type="ns1:LaanerListe">
539
          <item xsi:type="ns1:Laaner">
540
            <lnr xsi:type="xsd:string">N000123456</lnr>
541
            <navn xsi:type="xsd:string">Test, Testersen</navn>
542
            <p_adresse1 xsi:type="xsd:string">Bibliotekveien 6</p_adresse1>
543
            <p_adresse2 xsi:type="xsd:string"/>
544
            <p_postnr xsi:type="xsd:string">1234</p_postnr>
545
            <p_sted xsi:type="xsd:string">Lillevik</p_sted>
546
            <p_land xsi:type="xsd:string">no</p_land>
547
            <p_sjekk xsi:type="xsd:string">0</p_sjekk>
548
            <m_adresse1 xsi:type="xsd:string"/>
549
            <m_adresse2 xsi:type="xsd:string"/>
550
            <m_postnr xsi:type="xsd:string"/>
551
            <m_sted xsi:type="xsd:string"/>
552
            <m_land xsi:type="xsd:string">no</m_land>
553
            <m_sjekk xsi:type="xsd:string">0</m_sjekk>
554
            <m_gyldig_til xsi:type="xsd:string">0000-00-00</m_gyldig_til>
555
            <tlf_hjemme xsi:type="xsd:string"/>
556
            <tlf_jobb xsi:type="xsd:string"/>
557
            <tlf_mobil xsi:type="xsd:string">12345678</tlf_mobil>
558
            <epost xsi:type="xsd:string">test@example.com</epost>
559
            <epost_sjekk xsi:type="xsd:string">0</epost_sjekk>
560
            <prim_kontakt xsi:type="xsd:string"/>
561
            <hjemmebibliotek xsi:type="xsd:string">2060000</hjemmebibliotek>
562
            <fdato xsi:type="xsd:string">1964-05-22</fdato>
563
            <fnr_hash xsi:type="xsd:string">22056412345</fnr_hash>
564
            <kjonn xsi:type="xsd:string">F</kjonn>
565
            <pin xsi:type="xsd:string">g345abc123dab567abc78900abc123ab</pin>
566
            <passord xsi:type="xsd:string"/>
567
            <feide xsi:type="xsd:string"/>
568
            <opprettet xsi:type="xsd:string">2005-10-20</opprettet>
569
            <opprettet_av xsi:type="xsd:string">2060000</opprettet_av>
570
            <sist_endret xsi:type="xsd:string">2013-05-13T13:51:24</sist_endret>
571
            <sist_endret_av xsi:type="xsd:string">2060000</sist_endret_av>
572
            <gyldig_til xsi:type="xsd:string"/>
573
            <folkeregsjekk_dato xsi:type="xsd:string">0000-00-00</folkeregsjekk_dato>
574
          </item>
575
        </respons_poster>
576
        <server_tid xsi:type="xsd:string">2014-01-07T14:43:18</server_tid>
577
      </return>
578
    </ns1:hentResponse>
579
  </SOAP-ENV:Body>
580
</SOAP-ENV:Envelope>
581
ENDRESPONSE
582
583
}

Return to bug 11401