Bug 22540: Add ability to place holds using 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
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     hold => {
262         name       => 'Hold',
263         subroutine => \&build_hold_command_message,
264         parameters => {
265             hold_mode           => '+',
266             transaction_date    => $transaction_date,
267             expiration_date     => undef,
268             pickup_location     => undef,
269             hold_type           => undef,
270             institution_id      => $location_code,
271             patron_identifier   => $patron_identifier,
272             patron_password     => $patron_password,
273             item_identifier     => $item_identifier,
274             title_identifier    => undef,
275             terminal_password   => $terminal_password,
276             fee_acknowledged    => $fee_acknowledged,
277         },
278         optional => [
279             'expiration_date',
280             'pickup_location',
281             'hold_type',
282             'patron_password',
283             'item_identifier',
284             'title_identifier',
285             'terminal_password',
286             'fee_acknowledged',
287         ],
288     },
289 };
290
291 my $data = run_command_message('login');
292
293 if ( $data =~ '^941' ) {    ## we are logged in
294     foreach my $m (@messages) {
295         say "Trying '$m'";
296
297         my $data = run_command_message($m);
298
299     }
300 }
301 else {
302     say "Login Failed!";
303 }
304
305 sub build_command_message {
306     my ($message) = @_;
307
308     ##FIXME It would be much better to use exception handling so we aren't priting from subs
309     unless ( $handlers->{$message} ) {
310         say "$message is an unsupported command!";
311         return;
312     }
313
314     my $subroutine = $handlers->{$message}->{subroutine};
315     my $parameters = $handlers->{$message}->{parameters};
316     my %optional   = map { $_ => 1 } @{ $handlers->{$message}->{optional} };
317
318     foreach my $key ( keys %$parameters ) {
319         unless ( $parameters->{$key} ) {
320             unless ( $optional{$key} ) {
321                 say "$key is required for $message";
322                 return;
323             }
324         }
325     }
326
327     return &$subroutine($parameters);
328 }
329
330 sub run_command_message {
331     my ($message) = @_;
332
333     my $command_message = build_command_message($message);
334
335     return unless $command_message;
336
337     say "SEND: $command_message";
338     print $socket $command_message . $terminator;
339
340     my $data = <$socket>;
341
342     say "READ: $data";
343
344     return $data;
345 }
346
347 sub build_login_command_message {
348     my ($params) = @_;
349
350     my $login_user_id  = $params->{login_user_id};
351     my $login_password = $params->{login_password};
352     my $location_code  = $params->{location_code};
353
354     return
355         LOGIN . "00"
356       . build_field( FID_LOGIN_UID,     $login_user_id )
357       . build_field( FID_LOGIN_PWD,     $login_password )
358       . build_field( FID_LOCATION_CODE, $location_code );
359 }
360
361 sub build_patron_status_request_command_message {
362     my ($params) = @_;
363
364     my $transaction_date  = $params->{transaction_date};
365     my $institution_id    = $params->{institution_id};
366     my $patron_identifier = $params->{patron_identifier};
367     my $terminal_password = $params->{terminal_password};
368     my $patron_password   = $params->{patron_password};
369
370     return
371         PATRON_STATUS_REQ
372       . LANGUAGE
373       . $transaction_date
374       . build_field( FID_INST_ID,      $institution_id )
375       . build_field( FID_PATRON_ID,    $patron_identifier )
376       . build_field( FID_TERMINAL_PWD, $terminal_password )
377       . build_field( FID_PATRON_PWD,   $patron_password );
378 }
379
380 sub build_patron_information_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     my $summary           = $params->{summary};
389
390     $summary //= "          ";
391
392     return
393         PATRON_INFO
394       . LANGUAGE
395       . $transaction_date
396       . $summary
397       . build_field( FID_INST_ID,      $institution_id )
398       . build_field( FID_PATRON_ID,    $patron_identifier )
399       . build_field( FID_TERMINAL_PWD, $terminal_password )
400       . build_field( FID_PATRON_PWD,   $patron_password, { optional => 1 } );
401 }
402
403 sub build_item_information_command_message {
404     my ($params) = @_;
405
406     my $transaction_date  = $params->{transaction_date};
407     my $institution_id    = $params->{institution_id};
408     my $item_identifier   = $params->{item_identifier};
409     my $terminal_password = $params->{terminal_password};
410
411     return
412         ITEM_INFORMATION
413       . LANGUAGE
414       . $transaction_date
415       . build_field( FID_INST_ID,      $institution_id )
416       . build_field( FID_ITEM_ID,      $item_identifier )
417       . build_field( FID_TERMINAL_PWD, $terminal_password );
418 }
419
420 sub build_checkout_command_message {
421     my ($params) = @_;
422
423     my $SC_renewal_policy = $params->{SC_renewal_policy} || 'N';
424     my $no_block          = $params->{no_block} || 'N';
425     my $transaction_date  = $params->{transaction_date};
426     my $nb_due_date       = $params->{nb_due_date};
427     my $institution_id    = $params->{institution_id};
428     my $patron_identifier = $params->{patron_identifier};
429     my $item_identifier   = $params->{item_identifier};
430     my $terminal_password = $params->{terminal_password};
431     my $item_properties   = $params->{item_properties};
432     my $patron_password   = $params->{patron_password};
433     my $fee_acknowledged  = $params->{fee_acknowledged} || 'N';
434     my $cancel            = $params->{cancel} || 'N';
435
436     $SC_renewal_policy = $SC_renewal_policy eq 'Y' ? 'Y' : 'N';
437     $no_block          = $no_block          eq 'Y' ? 'Y' : 'N';
438     $fee_acknowledged  = $fee_acknowledged  eq 'Y' ? 'Y' : 'N';
439     $cancel            = $cancel            eq 'Y' ? 'Y' : 'N';
440
441     $nb_due_date ||= $transaction_date;
442
443     return
444         CHECKOUT
445       . $SC_renewal_policy
446       . $no_block
447       . $transaction_date
448       . $nb_due_date
449       . build_field( FID_INST_ID,      $institution_id )
450       . build_field( FID_PATRON_ID,    $patron_identifier )
451       . build_field( FID_ITEM_ID,      $item_identifier )
452       . build_field( FID_TERMINAL_PWD, $terminal_password )
453       . build_field( FID_ITEM_PROPS,   $item_properties, { optional => 1 } )
454       . build_field( FID_PATRON_PWD,   $patron_password, { optional => 1 } )
455       . build_field( FID_FEE_ACK,      $fee_acknowledged, { optional => 1 } )
456       . build_field( FID_CANCEL,       $cancel, { optional => 1 } );
457 }
458
459 sub build_checkin_command_message {
460     my ($params) = @_;
461
462     my $no_block          = $params->{no_block} || 'N';
463     my $transaction_date  = $params->{transaction_date};
464     my $return_date       = $params->{return_date};
465     my $current_location  = $params->{current_location};
466     my $institution_id    = $params->{institution_id};
467     my $item_identifier   = $params->{item_identifier};
468     my $terminal_password = $params->{terminal_password};
469     my $item_properties   = $params->{item_properties};
470     my $cancel            = $params->{cancel} || 'N';
471
472     $no_block = $no_block eq 'Y' ? 'Y' : 'N';
473     $cancel   = $cancel   eq 'Y' ? 'Y' : 'N';
474
475     $return_date ||= $transaction_date;
476
477     return
478         CHECKIN
479       . $no_block
480       . $transaction_date
481       . $return_date
482       . build_field( FID_CURRENT_LOCN, $current_location )
483       . build_field( FID_INST_ID,      $institution_id )
484       . build_field( FID_ITEM_ID,      $item_identifier )
485       . build_field( FID_TERMINAL_PWD, $terminal_password )
486       . build_field( FID_ITEM_PROPS,   $item_properties, { optional => 1 } )
487       . build_field( FID_CANCEL,       $cancel, { optional => 1 } );
488 }
489
490 sub build_hold_command_message {
491     my ($params) = @_;
492
493     my $hold_mode         = $params->{hold_mode} || '+';
494     my $transaction_date  = $params->{transaction_date};
495     my $expiration_date   = $params->{expiration_date};
496     my $pickup_location   = $params->{pickup_location};
497     my $hold_type         = $params->{hold_type};
498     my $institution_id    = $params->{institution_id};
499     my $patron_identifier = $params->{patron_identifier};
500     my $patron_password   = $params->{patron_password};
501     my $item_identifier   = $params->{item_identifier};
502     my $title_identifier  = $params->{title_identifier};
503     my $terminal_password = $params->{terminal_password};
504     my $fee_acknowledged  = $params->{fee_acknowledged} || 'N';
505
506     return
507         HOLD
508       . $hold_mode
509       . $transaction_date
510       . build_field( FID_EXPIRATION,   $expiration_date,   { optional => 1 } )
511       . build_field( FID_PICKUP_LOCN,  $pickup_location,   { optional => 1 } )
512       . build_field( FID_HOLD_TYPE,    $hold_type,         { optional => 1 } )
513       . build_field( FID_INST_ID,      $institution_id                       )
514       . build_field( FID_PATRON_ID,    $patron_identifier                    )
515       . build_field( FID_PATRON_PWD,   $patron_password,   { optional => 1 } )
516       . build_field( FID_ITEM_ID,      $item_identifier,   { optional => 1 } )
517       . build_field( FID_TITLE_ID,     $title_identifier,  { optional => 1 } )
518       . build_field( FID_TERMINAL_PWD, $terminal_password, { optional => 1 } )
519       . build_field( FID_FEE_ACK,      $fee_acknowledged,  { optional => 1 } );
520 }
521
522 sub build_renew_command_message {
523     my ($params) = @_;
524
525     my $third_party_allowed = $params->{third_party_allowed} || 'N';
526     my $no_block            = $params->{no_block}            || 'N';
527     my $transaction_date    = $params->{transaction_date};
528     my $nb_due_date         = $params->{nb_due_date};
529     my $institution_id      = $params->{institution_id};
530     my $patron_identifier   = $params->{patron_identifier};
531     my $patron_password     = $params->{patron_password};
532     my $item_identifier     = $params->{item_identifier};
533     my $title_identifier    = $params->{title_identifier};
534     my $terminal_password   = $params->{terminal_password};
535     my $item_properties     = $params->{item_properties};
536     my $fee_acknowledged    = $params->{fee_acknowledged}    || 'N';
537
538     $third_party_allowed = $third_party_allowed eq 'Y' ? 'Y' : 'N';
539     $no_block            = $no_block            eq 'Y' ? 'Y' : 'N';
540     $fee_acknowledged    = $fee_acknowledged    eq 'Y' ? 'Y' : 'N';
541
542     $nb_due_date ||= $transaction_date;
543
544     return
545         RENEW
546       . $third_party_allowed
547       . $no_block
548       . $transaction_date
549       . $nb_due_date
550       . build_field( FID_INST_ID,      $institution_id )
551       . build_field( FID_PATRON_ID,    $patron_identifier )
552       . build_field( FID_PATRON_PWD,   $patron_password, { optional => 1 } )
553       . build_field( FID_ITEM_ID,      $item_identifier )
554       . build_field( FID_TITLE_ID,     $title_identifier )
555       . build_field( FID_TERMINAL_PWD, $terminal_password )
556       . build_field( FID_ITEM_PROPS,   $item_properties, { optional => 1 } )
557       . build_field( FID_FEE_ACK,      $fee_acknowledged, { optional => 1 } );
558 }
559
560 sub build_fee_paid_command_message {
561     my ($params) = @_;
562
563     my $transaction_date  = $params->{transaction_date};
564     my $fee_type          = $params->{fee_type} || '01';
565     my $payment_type      = $params->{payment_type} || '00';
566     my $currency_type     = $params->{currency_type} || 'USD';
567     my $fee_amount        = $params->{fee_amount};
568     my $institution_id    = $params->{location_code};
569     my $patron_identifier = $params->{patron_identifier};
570     my $terminal_password = $params->{terminal_password};
571     my $patron_password   = $params->{patron_password};
572     my $fee_identifier    = $params->{fee_identifier};
573     my $transaction_id    = $params->{transaction_id};
574
575     return
576         FEE_PAID
577       . $transaction_date
578       . $fee_type
579       . $payment_type
580       . $currency_type
581       . build_field( FID_FEE_AMT,        $fee_amount )
582       . build_field( FID_INST_ID,        $institution_id )
583       . build_field( FID_PATRON_ID,      $patron_identifier )
584       . build_field( FID_TERMINAL_PWD,   $terminal_password, { optional => 1 } )
585       . build_field( FID_PATRON_PWD,     $patron_password, { optional => 1 } )
586       . build_field( FID_FEE_ID,         $fee_identifier, { optional => 1 } )
587       . build_field( FID_TRANSACTION_ID, $transaction_id, { optional => 1 } );
588 }
589
590 sub build_field {
591     my ( $field_identifier, $value, $params ) = @_;
592
593     $params //= {};
594
595     return q{} if ( $params->{optional} && !$value );
596
597     return $field_identifier . (($value) ? $value : '') . '|';
598 }
599
600 sub help {
601     say q/sip_cli_emulator.pl - SIP command line emulator
602
603 Test a SIP2 service by sending patron status and patron
604 information requests.
605
606 Usage:
607   sip_cli_emulator.pl [OPTIONS]
608
609 Options:
610   --help           display help message
611
612   -a --address     SIP server ip address or host name
613   -p --port        SIP server port
614
615   -su --sip_user   SIP server login username
616   -sp --sip_pass   SIP server login password
617
618   -l --location    SIP location code
619
620   --patron         ILS patron cardnumber or username
621   --password       ILS patron password
622
623   -s --summary     Optionally define the patron information request summary field.
624                    Please refer to the SIP2 protocol specification for details
625
626   --item           ILS item identifier ( item barcode )
627
628   -t --terminator  SIP2 message terminator, either CR, or CRLF
629                    (defaults to CRLF)
630
631   -fa --fee-acknowledged Sends a confirmation of checkout fee
632
633   --fee-type        Fee type for Fee Paid message, defaults to '01'
634   --payment-type    Payment type for Fee Paid message, default to '00'
635   --currency-type   Currency type for Fee Paid message, defaults to 'USD'
636   --fee-amount      Fee amount for Fee Paid message, required
637   --fee-identifier  Fee identifier for Fee Paid message, optional
638   --transaction-id  Transaction id for Fee Paid message, optional
639
640   -m --message     SIP2 message to execute
641
642   Implemented Messages:
643     checkin
644     checkout
645     fee_paid
646     hold
647     item_information
648     patron_information
649     patron_status_request
650     renew
651 /
652 }