Bug 22540: Add ability to place holds using SIP CLI emulator
[koha.git] / misc / sip_cli_emulator.pl
blobfa440bc4cdf166c8a74c2dc93412fd12343b4540
1 #!/usr/bin/perl
3 # This file is part of Koha.
5 # Copyright (C) 2012-2013 ByWater Solutions
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.
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.
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>.
20 use Modern::Perl;
22 use Socket qw(:crlf);
23 use IO::Socket::INET;
24 use Getopt::Long;
26 use C4::SIP::Sip::Constants qw(:all);
27 use C4::SIP::Sip;
29 use constant { LANGUAGE => '001' };
31 my $help = 0;
33 my $host;
34 my $port = '6001';
36 my $login_user_id;
37 my $login_password;
38 my $location_code;
40 my $patron_identifier;
41 my $patron_password;
43 my $summary;
45 my $item_identifier;
47 my $fee_acknowledged = 0;
49 my $fee_type;
50 my $payment_type;
51 my $currency_type;
52 my $fee_amount;
53 my $fee_identifier;
54 my $transaction_id;
56 my $terminator = q{};
58 my @messages;
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
67 "patron=s" => \$patron_identifier, # patron cardnumber or login
68 "password=s" => \$patron_password, # patron's password
70 "i|item=s" => \$item_identifier,
72 "fa|fee-acknowledged" => \$fee_acknowledged,
74 "s|summary=s" => \$summary,
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,
83 "t|terminator=s" => \$terminator,
85 "m|message=s" => \@messages,
87 'h|help|?' => \$help
90 if ( $help
91 || !$host
92 || !$login_user_id
93 || !$login_password
94 || !$location_code )
96 say &help();
97 exit();
100 $terminator = ( $terminator eq 'CR' ) ? $CR : $CRLF;
102 # Set perl to expect the same record terminator it is sending
103 $/ = $terminator;
105 my $transaction_date = C4::SIP::Sip::timestamp();
107 my $terminal_password = $login_password;
109 $| = 1;
110 print "Attempting socket connection to $host:$port...";
112 my $socket = IO::Socket::INET->new("$host:$port")
113 or die "failed! : $!\n";
114 say "connected!";
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,
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,
136 optional => [ 'patron_password', ],
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,
149 optional => [ 'patron_password', 'summary' ],
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,
160 optional => [],
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,
179 optional => [
180 'nb_due_date', # defaults to transaction date
181 'item_properties',
182 'patron_password',
183 'fee_acknowledged',
184 'cancel',
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,
201 optional => [
202 'return_date', # defaults to transaction date
203 'item_properties',
204 'patron_password',
205 'cancel',
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,
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',
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,
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',
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,
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',
291 my $data = run_command_message('login');
293 if ( $data =~ '^941' ) { ## we are logged in
294 foreach my $m (@messages) {
295 say "Trying '$m'";
297 my $data = run_command_message($m);
301 else {
302 say "Login Failed!";
305 sub build_command_message {
306 my ($message) = @_;
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;
314 my $subroutine = $handlers->{$message}->{subroutine};
315 my $parameters = $handlers->{$message}->{parameters};
316 my %optional = map { $_ => 1 } @{ $handlers->{$message}->{optional} };
318 foreach my $key ( keys %$parameters ) {
319 unless ( $parameters->{$key} ) {
320 unless ( $optional{$key} ) {
321 say "$key is required for $message";
322 return;
327 return &$subroutine($parameters);
330 sub run_command_message {
331 my ($message) = @_;
333 my $command_message = build_command_message($message);
335 return unless $command_message;
337 say "SEND: $command_message";
338 print $socket $command_message . $terminator;
340 my $data = <$socket>;
342 say "READ: $data";
344 return $data;
347 sub build_login_command_message {
348 my ($params) = @_;
350 my $login_user_id = $params->{login_user_id};
351 my $login_password = $params->{login_password};
352 my $location_code = $params->{location_code};
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 );
361 sub build_patron_status_request_command_message {
362 my ($params) = @_;
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};
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 );
380 sub build_patron_information_command_message {
381 my ($params) = @_;
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};
390 $summary //= " ";
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 } );
403 sub build_item_information_command_message {
404 my ($params) = @_;
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};
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 );
420 sub build_checkout_command_message {
421 my ($params) = @_;
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';
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';
441 $nb_due_date ||= $transaction_date;
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 } );
459 sub build_checkin_command_message {
460 my ($params) = @_;
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';
472 $no_block = $no_block eq 'Y' ? 'Y' : 'N';
473 $cancel = $cancel eq 'Y' ? 'Y' : 'N';
475 $return_date ||= $transaction_date;
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 } );
490 sub build_hold_command_message {
491 my ($params) = @_;
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';
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 } );
522 sub build_renew_command_message {
523 my ($params) = @_;
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';
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';
542 $nb_due_date ||= $transaction_date;
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 } );
560 sub build_fee_paid_command_message {
561 my ($params) = @_;
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};
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 } );
590 sub build_field {
591 my ( $field_identifier, $value, $params ) = @_;
593 $params //= {};
595 return q{} if ( $params->{optional} && !$value );
597 return $field_identifier . (($value) ? $value : '') . '|';
600 sub help {
601 say q/sip_cli_emulator.pl - SIP command line emulator
603 Test a SIP2 service by sending patron status and patron
604 information requests.
606 Usage:
607 sip_cli_emulator.pl [OPTIONS]
609 Options:
610 --help display help message
612 -a --address SIP server ip address or host name
613 -p --port SIP server port
615 -su --sip_user SIP server login username
616 -sp --sip_pass SIP server login password
618 -l --location SIP location code
620 --patron ILS patron cardnumber or username
621 --password ILS patron password
623 -s --summary Optionally define the patron information request summary field.
624 Please refer to the SIP2 protocol specification for details
626 --item ILS item identifier ( item barcode )
628 -t --terminator SIP2 message terminator, either CR, or CRLF
629 (defaults to CRLF)
631 -fa --fee-acknowledged Sends a confirmation of checkout fee
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
640 -m --message SIP2 message to execute
642 Implemented Messages:
643 checkin
644 checkout
645 fee_paid
646 hold
647 item_information
648 patron_information
649 patron_status_request
650 renew