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