Bug 22770: DBRev 19.05.00.003
[koha.git] / misc / sip_cli_emulator.pl
1 #!/usr/bin/perl
2
3 # This file is part of Koha.
4 #
5 # Copyright (C) 2012-2013 ByWater Solutions
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 use Modern::Perl;
21
22 use Socket qw(:crlf);
23 use IO::Socket::INET;
24 use Getopt::Long;
25
26 use C4::SIP::Sip::Constants qw(:all);
27 use C4::SIP::Sip;
28
29 use constant { LANGUAGE => '001' };
30
31 my $help = 0;
32
33 my $host;
34 my $port = '6001';
35
36 my $login_user_id;
37 my $login_password;
38 my $location_code;
39
40 my $patron_identifier;
41 my $patron_password;
42
43 my $summary;
44
45 my $item_identifier;
46
47 my $fee_acknowledged = 0;
48
49 my $fee_type;
50 my $payment_type;
51 my $currency_type;
52 my $fee_amount;
53 my $fee_identifier;
54 my $transaction_id;
55
56 my $terminator = q{};
57
58 my @messages;
59
60 GetOptions(
61     "a|address|host|hostaddress=s" => \$host,              # sip server ip
62     "p|port=s"                     => \$port,              # sip server port
63     "su|sip_user=s"                => \$login_user_id,     # sip user
64     "sp|sip_pass=s"                => \$login_password,    # sip password
65     "l|location|location_code=s"   => \$location_code,     # sip location code
66
67     "patron=s"   => \$patron_identifier,                   # patron cardnumber or login
68     "password=s" => \$patron_password,                     # patron's password
69
70     "i|item=s" => \$item_identifier,
71
72     "fa|fee-acknowledged" => \$fee_acknowledged,
73
74     "s|summary=s" => \$summary,
75
76     "fee-type=s"       => \$fee_type,
77     "payment-type=s"   => \$payment_type,
78     "currency-type=s"  => \$currency_type,
79     "fee-amount=s"     => \$fee_amount,
80     "fee-identifier=s" => \$fee_identifier,
81     "transaction-id=s" => \$transaction_id,
82
83     "t|terminator=s" => \$terminator,
84
85     "m|message=s" => \@messages,
86
87     'h|help|?' => \$help
88 );
89
90 if (   $help
91     || !$host
92     || !$login_user_id
93     || !$login_password
94     || !$location_code )
95 {
96     say &help();
97     exit();
98 }
99
100 $terminator = ( $terminator eq 'CR' ) ? $CR : $CRLF;
101
102 # Set perl to expect the same record terminator it is sending
103 $/ = $terminator;
104
105 my $transaction_date = C4::SIP::Sip::timestamp();
106
107 my $terminal_password = $login_password;
108
109 $| = 1;
110 print "Attempting socket connection to $host:$port...";
111
112 my $socket = IO::Socket::INET->new("$host:$port")
113   or die "failed! : $!\n";
114 say "connected!";
115
116 my $handlers = {
117     login => {
118         name       => 'Login',
119         subroutine => \&build_login_command_message,
120         parameters => {
121             login_user_id  => $login_user_id,
122             login_password => $login_password,
123             location_code  => $location_code,
124         },
125     },
126     patron_status_request => {
127         name       => 'Patron Status Request',
128         subroutine => \&build_patron_status_request_command_message,
129         parameters => {
130             transaction_date  => $transaction_date,
131             institution_id    => $location_code,
132             patron_identifier => $patron_identifier,
133             terminal_password => $terminal_password,
134             patron_password   => $patron_password,
135         },
136         optional => [ 'patron_password', ],
137     },
138     patron_information => {
139         name       => 'Patron Information',
140         subroutine => \&build_patron_information_command_message,
141         parameters => {
142             transaction_date  => $transaction_date,
143             institution_id    => $location_code,
144             patron_identifier => $patron_identifier,
145             terminal_password => $terminal_password,
146             patron_password   => $patron_password,
147             summary           => $summary,
148         },
149         optional => [ 'patron_password', 'summary' ],
150     },
151     item_information => {
152         name       => 'Item Information',
153         subroutine => \&build_item_information_command_message,
154         parameters => {
155             transaction_date  => $transaction_date,
156             institution_id    => $location_code,
157             item_identifier   => $item_identifier,
158             terminal_password => $terminal_password,
159         },
160         optional => [],
161     },
162     checkout => {
163         name       => 'Checkout',
164         subroutine => \&build_checkout_command_message,
165         parameters => {
166             SC_renewal_policy => 'Y',
167             no_block          => 'N',
168             transaction_date  => $transaction_date,
169             nb_due_date       => undef,
170             institution_id    => $location_code,
171             patron_identifier => $patron_identifier,
172             item_identifier   => $item_identifier,
173             terminal_password => $terminal_password,
174             item_properties   => undef,
175             patron_password   => $patron_password,
176             fee_acknowledged  => $fee_acknowledged,
177             cancel            => undef,
178         },
179         optional => [
180             'nb_due_date',    # defaults to transaction date
181             'item_properties',
182             'patron_password',
183             'fee_acknowledged',
184             'cancel',
185         ],
186     },
187     checkin => {
188         name       => 'Checkin',
189         subroutine => \&build_checkin_command_message,
190         parameters => {
191             no_block          => 'N',
192             transaction_date  => $transaction_date,
193             return_date       => $transaction_date,
194             current_location  => $location_code,
195             institution_id    => $location_code,
196             item_identifier   => $item_identifier,
197             terminal_password => $terminal_password,
198             item_properties   => undef,
199             cancel            => undef,
200         },
201         optional => [
202             'return_date',    # defaults to transaction date
203             'item_properties',
204             'patron_password',
205             'cancel',
206         ],
207     },
208     renew => {
209         name       => 'Renew',
210         subroutine => \&build_renew_command_message,
211         parameters => {
212             third_party_allowed => 'N',
213             no_block            => 'N',
214             transaction_date    => $transaction_date,
215             nb_due_date         => undef,
216             institution_id      => $location_code,
217             patron_identifier   => $patron_identifier,
218             patron_password     => $patron_password,
219             item_identifier     => $item_identifier,
220             title_identifier    => undef,
221             terminal_password   => $terminal_password,
222             item_properties     => undef,
223             fee_acknowledged    => $fee_acknowledged,
224         },
225         optional => [
226             'nb_due_date',    # defaults to transaction date
227             'patron_password',
228             'item_identifier',
229             'title_identifier',
230             'terminal_password',
231             'item_properties',
232             'fee_acknowledged',
233         ],
234     },
235     fee_paid => {
236         name       => 'Fee Paid',
237         subroutine => \&build_fee_paid_command_message,
238         parameters => {
239             transaction_date  => $transaction_date,
240             fee_type          => $fee_type,
241             payment_type      => $payment_type,
242             currency_type     => $currency_type,
243             fee_amount        => $fee_amount,
244             institution_id    => $location_code,
245             patron_identifier => $patron_identifier,
246             terminal_password => $terminal_password,
247             patron_password   => $patron_password,
248             fee_identifier    => $fee_identifier,
249             transaction_id    => $transaction_id,
250         },
251         optional => [
252             'fee_type', # has default
253             'payment_type', # has default
254             'currency_type', #has default
255             'terminal_password',
256             'patron_password',
257             'fee_identifier',
258             'transaction_id',
259         ],
260     },
261 };
262
263 my $data = run_command_message('login');
264
265 if ( $data =~ '^941' ) {    ## we are logged in
266     foreach my $m (@messages) {
267         say "Trying '$m'";
268
269         my $data = run_command_message($m);
270
271     }
272 }
273 else {
274     say "Login Failed!";
275 }
276
277 sub build_command_message {
278     my ($message) = @_;
279
280     ##FIXME It would be much better to use exception handling so we aren't priting from subs
281     unless ( $handlers->{$message} ) {
282         say "$message is an unsupported command!";
283         return;
284     }
285
286     my $subroutine = $handlers->{$message}->{subroutine};
287     my $parameters = $handlers->{$message}->{parameters};
288     my %optional   = map { $_ => 1 } @{ $handlers->{$message}->{optional} };
289
290     foreach my $key ( keys %$parameters ) {
291         unless ( $parameters->{$key} ) {
292             unless ( $optional{$key} ) {
293                 say "$key is required for $message";
294                 return;
295             }
296         }
297     }
298
299     return &$subroutine($parameters);
300 }
301
302 sub run_command_message {
303     my ($message) = @_;
304
305     my $command_message = build_command_message($message);
306
307     return unless $command_message;
308
309     say "SEND: $command_message";
310     print $socket $command_message . $terminator;
311
312     my $data = <$socket>;
313
314     say "READ: $data";
315
316     return $data;
317 }
318
319 sub build_login_command_message {
320     my ($params) = @_;
321
322     my $login_user_id  = $params->{login_user_id};
323     my $login_password = $params->{login_password};
324     my $location_code  = $params->{location_code};
325
326     return
327         LOGIN . "00"
328       . build_field( FID_LOGIN_UID,     $login_user_id )
329       . build_field( FID_LOGIN_PWD,     $login_password )
330       . build_field( FID_LOCATION_CODE, $location_code );
331 }
332
333 sub build_patron_status_request_command_message {
334     my ($params) = @_;
335
336     my $transaction_date  = $params->{transaction_date};
337     my $institution_id    = $params->{institution_id};
338     my $patron_identifier = $params->{patron_identifier};
339     my $terminal_password = $params->{terminal_password};
340     my $patron_password   = $params->{patron_password};
341
342     return
343         PATRON_STATUS_REQ
344       . LANGUAGE
345       . $transaction_date
346       . build_field( FID_INST_ID,      $institution_id )
347       . build_field( FID_PATRON_ID,    $patron_identifier )
348       . build_field( FID_TERMINAL_PWD, $terminal_password )
349       . build_field( FID_PATRON_PWD,   $patron_password );
350 }
351
352 sub build_patron_information_command_message {
353     my ($params) = @_;
354
355     my $transaction_date  = $params->{transaction_date};
356     my $institution_id    = $params->{institution_id};
357     my $patron_identifier = $params->{patron_identifier};
358     my $terminal_password = $params->{terminal_password};
359     my $patron_password   = $params->{patron_password};
360     my $summary           = $params->{summary};
361
362     $summary //= "          ";
363
364     return
365         PATRON_INFO
366       . LANGUAGE
367       . $transaction_date
368       . $summary
369       . build_field( FID_INST_ID,      $institution_id )
370       . build_field( FID_PATRON_ID,    $patron_identifier )
371       . build_field( FID_TERMINAL_PWD, $terminal_password )
372       . build_field( FID_PATRON_PWD,   $patron_password, { optional => 1 } );
373 }
374
375 sub build_item_information_command_message {
376     my ($params) = @_;
377
378     my $transaction_date  = $params->{transaction_date};
379     my $institution_id    = $params->{institution_id};
380     my $item_identifier   = $params->{item_identifier};
381     my $terminal_password = $params->{terminal_password};
382
383     return
384         ITEM_INFORMATION
385       . LANGUAGE
386       . $transaction_date
387       . build_field( FID_INST_ID,      $institution_id )
388       . build_field( FID_ITEM_ID,      $item_identifier )
389       . build_field( FID_TERMINAL_PWD, $terminal_password );
390 }
391
392 sub build_checkout_command_message {
393     my ($params) = @_;
394
395     my $SC_renewal_policy = $params->{SC_renewal_policy} || 'N';
396     my $no_block          = $params->{no_block} || 'N';
397     my $transaction_date  = $params->{transaction_date};
398     my $nb_due_date       = $params->{nb_due_date};
399     my $institution_id    = $params->{institution_id};
400     my $patron_identifier = $params->{patron_identifier};
401     my $item_identifier   = $params->{item_identifier};
402     my $terminal_password = $params->{terminal_password};
403     my $item_properties   = $params->{item_properties};
404     my $patron_password   = $params->{patron_password};
405     my $fee_acknowledged  = $params->{fee_acknowledged} || 'N';
406     my $cancel            = $params->{cancel} || 'N';
407
408     $SC_renewal_policy = $SC_renewal_policy eq 'Y' ? 'Y' : 'N';
409     $no_block          = $no_block          eq 'Y' ? 'Y' : 'N';
410     $fee_acknowledged  = $fee_acknowledged  eq 'Y' ? 'Y' : 'N';
411     $cancel            = $cancel            eq 'Y' ? 'Y' : 'N';
412
413     $nb_due_date ||= $transaction_date;
414
415     return
416         CHECKOUT
417       . $SC_renewal_policy
418       . $no_block
419       . $transaction_date
420       . $nb_due_date
421       . build_field( FID_INST_ID,      $institution_id )
422       . build_field( FID_PATRON_ID,    $patron_identifier )
423       . build_field( FID_ITEM_ID,      $item_identifier )
424       . build_field( FID_TERMINAL_PWD, $terminal_password )
425       . build_field( FID_ITEM_PROPS,   $item_properties, { optional => 1 } )
426       . build_field( FID_PATRON_PWD,   $patron_password, { optional => 1 } )
427       . build_field( FID_FEE_ACK,      $fee_acknowledged, { optional => 1 } )
428       . build_field( FID_CANCEL,       $cancel, { optional => 1 } );
429 }
430
431 sub build_checkin_command_message {
432     my ($params) = @_;
433
434     my $no_block          = $params->{no_block} || 'N';
435     my $transaction_date  = $params->{transaction_date};
436     my $return_date       = $params->{return_date};
437     my $current_location  = $params->{current_location};
438     my $institution_id    = $params->{institution_id};
439     my $item_identifier   = $params->{item_identifier};
440     my $terminal_password = $params->{terminal_password};
441     my $item_properties   = $params->{item_properties};
442     my $cancel            = $params->{cancel} || 'N';
443
444     $no_block = $no_block eq 'Y' ? 'Y' : 'N';
445     $cancel   = $cancel   eq 'Y' ? 'Y' : 'N';
446
447     $return_date ||= $transaction_date;
448
449     return
450         CHECKIN
451       . $no_block
452       . $transaction_date
453       . $return_date
454       . build_field( FID_CURRENT_LOCN, $current_location )
455       . build_field( FID_INST_ID,      $institution_id )
456       . build_field( FID_ITEM_ID,      $item_identifier )
457       . build_field( FID_TERMINAL_PWD, $terminal_password )
458       . build_field( FID_ITEM_PROPS,   $item_properties, { optional => 1 } )
459       . build_field( FID_CANCEL,       $cancel, { optional => 1 } );
460 }
461
462 sub build_renew_command_message {
463     my ($params) = @_;
464
465     my $third_party_allowed = $params->{third_party_allowed} || 'N';
466     my $no_block            = $params->{no_block}            || 'N';
467     my $transaction_date    = $params->{transaction_date};
468     my $nb_due_date         = $params->{nb_due_date};
469     my $institution_id      = $params->{institution_id};
470     my $patron_identifier   = $params->{patron_identifier};
471     my $patron_password     = $params->{patron_password};
472     my $item_identifier     = $params->{item_identifier};
473     my $title_identifier    = $params->{title_identifier};
474     my $terminal_password   = $params->{terminal_password};
475     my $item_properties     = $params->{item_properties};
476     my $fee_acknowledged    = $params->{fee_acknowledged}    || 'N';
477
478     $third_party_allowed = $third_party_allowed eq 'Y' ? 'Y' : 'N';
479     $no_block            = $no_block            eq 'Y' ? 'Y' : 'N';
480     $fee_acknowledged    = $fee_acknowledged    eq 'Y' ? 'Y' : 'N';
481
482     $nb_due_date ||= $transaction_date;
483
484     return
485         RENEW
486       . $third_party_allowed
487       . $no_block
488       . $transaction_date
489       . $nb_due_date
490       . build_field( FID_INST_ID,      $institution_id )
491       . build_field( FID_PATRON_ID,    $patron_identifier )
492       . build_field( FID_PATRON_PWD,   $patron_password, { optional => 1 } )
493       . build_field( FID_ITEM_ID,      $item_identifier )
494       . build_field( FID_TITLE_ID,     $title_identifier )
495       . build_field( FID_TERMINAL_PWD, $terminal_password )
496       . build_field( FID_ITEM_PROPS,   $item_properties, { optional => 1 } )
497       . build_field( FID_FEE_ACK,      $fee_acknowledged, { optional => 1 } );
498 }
499
500 sub build_fee_paid_command_message {
501     my ($params) = @_;
502
503     my $transaction_date  = $params->{transaction_date};
504     my $fee_type          = $params->{fee_type} || '01';
505     my $payment_type      = $params->{payment_type} || '00';
506     my $currency_type     = $params->{currency_type} || 'USD';
507     my $fee_amount        = $params->{fee_amount};
508     my $institution_id    = $params->{location_code};
509     my $patron_identifier = $params->{patron_identifier};
510     my $terminal_password = $params->{terminal_password};
511     my $patron_password   = $params->{patron_password};
512     my $fee_identifier    = $params->{fee_identifier};
513     my $transaction_id    = $params->{transaction_id};
514
515     return
516         FEE_PAID
517       . $transaction_date
518       . $fee_type
519       . $payment_type
520       . $currency_type
521       . build_field( FID_FEE_AMT,        $fee_amount )
522       . build_field( FID_INST_ID,        $institution_id )
523       . build_field( FID_PATRON_ID,      $patron_identifier )
524       . build_field( FID_TERMINAL_PWD,   $terminal_password, { optional => 1 } )
525       . build_field( FID_PATRON_PWD,     $patron_password, { optional => 1 } )
526       . build_field( FID_FEE_ID,         $fee_identifier, { optional => 1 } )
527       . build_field( FID_TRANSACTION_ID, $transaction_id, { optional => 1 } );
528 }
529
530 sub build_field {
531     my ( $field_identifier, $value, $params ) = @_;
532
533     $params //= {};
534
535     return q{} if ( $params->{optional} && !$value );
536
537     return $field_identifier . (($value) ? $value : '') . '|';
538 }
539
540 sub help {
541     say q/sip_cli_emulator.pl - SIP command line emulator
542
543 Test a SIP2 service by sending patron status and patron
544 information requests.
545
546 Usage:
547   sip_cli_emulator.pl [OPTIONS]
548
549 Options:
550   --help           display help message
551
552   -a --address     SIP server ip address or host name
553   -p --port        SIP server port
554
555   -su --sip_user   SIP server login username
556   -sp --sip_pass   SIP server login password
557
558   -l --location    SIP location code
559
560   --patron         ILS patron cardnumber or username
561   --password       ILS patron password
562
563   -s --summary     Optionally define the patron information request summary field.
564                    Please refer to the SIP2 protocol specification for details
565
566   --item           ILS item identifier ( item barcode )
567
568   -t --terminator  SIP2 message terminator, either CR, or CRLF
569                    (defaults to CRLF)
570
571   -fa --fee-acknowledged Sends a confirmation of checkout fee
572
573   --fee-type        Fee type for Fee Paid message, defaults to '01'
574   --payment-type    Payment type for Fee Paid message, default to '00'
575   --currency-type   Currency type for Fee Paid message, defaults to 'USD'
576   --fee-amount      Fee amount for Fee Paid message, required
577   --fee-identifier  Fee identifier for Fee Paid message, optional
578   --transaction-id  Transaction id for Fee Paid message, optional
579
580   -m --message     SIP2 message to execute
581
582   Implemented Messages:
583     patron_status_request
584     patron_information
585     item_information
586     checkout
587     checkin
588     renew
589     fee_paid
590 /
591 }