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