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