From 537a3cffee447bde85bf2a7ff716a371e6f4259d Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Fri, 30 Aug 2013 16:21:05 -0400 Subject: [PATCH] Make FF a child of Evergreen; experiment Signed-off-by: Bill Erickson --- .../lib/FulfILLment/AT/Reactor/BibRefresh.pm | 57 + .../lib/FulfILLment/AT/Reactor/ItemLoad.pm | 119 ++ .../lib/FulfILLment/AT/Reactor/ItemRefresh.pm | 86 ++ .../lib/FulfILLment/Application/LAICore.pm | 375 +++++++ .../src/perlmods/lib/FulfILLment/LAIConnector.pm | 611 +++++++++++ .../perlmods/lib/FulfILLment/LAIConnector/Aleph.pm | 6 + .../lib/FulfILLment/LAIConnector/Evergreen.pm | 396 +++++++ .../LAIConnector/Evergreen/FulfILLment_EGAPP.pm | 489 +++++++++ .../lib/FulfILLment/LAIConnector/Horizon.pm | 318 ++++++ .../perlmods/lib/FulfILLment/LAIConnector/III.pm | 144 +++ .../lib/FulfILLment/LAIConnector/III/2009B_1_2.pm | 1154 +++++++++++++++++++ .../lib/FulfILLment/LAIConnector/III/2011_1_3.pm | 1156 ++++++++++++++++++++ .../perlmods/lib/FulfILLment/LAIConnector/Koha.pm | 254 +++++ .../lib/FulfILLment/LAIConnector/Polaris.pm | 6 + .../lib/FulfILLment/LAIConnector/Symphony.pm | 6 + Open-ILS/src/perlmods/lib/FulfILLment/Util/NCIP.pm | 151 +++ .../perlmods/lib/FulfILLment/Util/SIP2Client.pm | 459 ++++++++ .../src/perlmods/lib/FulfILLment/Util/Z3950.pm | 244 +++++ .../src/perlmods/lib/FulfILLment/WWW/FastImport.pm | 186 ++++ Open-ILS/web/staff/js/circ_tracker.js | 603 ++++++++++ Open-ILS/web/staff/js/hold_tracker.js | 776 +++++++++++++ Open-ILS/web/staff/js/record_mgmt.js | 41 + Open-ILS/web/staff/php/barcode/barcode.php | 174 +++ Open-ILS/web/staff/php/barcode/c128aobject.php | 320 ++++++ Open-ILS/web/staff/php/barcode/c128bobject.php | 321 ++++++ Open-ILS/web/staff/php/barcode/c128cobject.php | 344 ++++++ Open-ILS/web/staff/php/barcode/c39object.php | 228 ++++ Open-ILS/web/staff/php/barcode/debug.php | 64 ++ Open-ILS/web/staff/php/barcode/download.php | 75 ++ Open-ILS/web/staff/php/barcode/download.png | Bin 0 -> 4859 bytes Open-ILS/web/staff/php/barcode/home.php | 30 + Open-ILS/web/staff/php/barcode/home.png | Bin 0 -> 4238 bytes Open-ILS/web/staff/php/barcode/i25object.php | 169 +++ Open-ILS/web/staff/php/barcode/image.php | 73 ++ Open-ILS/web/staff/php/barcode/image.png | Bin 0 -> 450 bytes Open-ILS/web/staff/php/barcode/index.php | 3 + Open-ILS/web/staff/php/barcode/linux.gif | Bin 0 -> 6969 bytes Open-ILS/web/staff/php/barcode/php_logo.gif | Bin 0 -> 3872 bytes Open-ILS/web/staff/php/barcode/sample.php | 139 +++ Open-ILS/web/staff/php/barcode/sample.png | Bin 0 -> 4653 bytes Open-ILS/web/staff/php/barcode/spain.png | Bin 0 -> 529 bytes Open-ILS/web/staff/xml/circ_tracker.xml | 267 +++++ Open-ILS/web/staff/xml/hold_tracker.xml | 276 +++++ Open-ILS/web/staff/xml/record_mgmt.xml | 154 +++ 44 files changed, 10274 insertions(+) create mode 100644 Open-ILS/src/perlmods/lib/FulfILLment/AT/Reactor/BibRefresh.pm create mode 100644 Open-ILS/src/perlmods/lib/FulfILLment/AT/Reactor/ItemLoad.pm create mode 100644 Open-ILS/src/perlmods/lib/FulfILLment/AT/Reactor/ItemRefresh.pm create mode 100644 Open-ILS/src/perlmods/lib/FulfILLment/Application/LAICore.pm create mode 100644 Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector.pm create mode 100644 Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Aleph.pm create mode 100644 Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Evergreen.pm create mode 100644 Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Evergreen/FulfILLment_EGAPP.pm create mode 100644 Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Horizon.pm create mode 100644 Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/III.pm create mode 100644 Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/III/2009B_1_2.pm create mode 100644 Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/III/2011_1_3.pm create mode 100644 Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Koha.pm create mode 100644 Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Polaris.pm create mode 100644 Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Symphony.pm create mode 100644 Open-ILS/src/perlmods/lib/FulfILLment/Util/NCIP.pm create mode 100644 Open-ILS/src/perlmods/lib/FulfILLment/Util/SIP2Client.pm create mode 100644 Open-ILS/src/perlmods/lib/FulfILLment/Util/Z3950.pm create mode 100755 Open-ILS/src/perlmods/lib/FulfILLment/WWW/FastImport.pm create mode 100644 Open-ILS/web/staff/js/circ_tracker.js create mode 100644 Open-ILS/web/staff/js/hold_tracker.js create mode 100644 Open-ILS/web/staff/js/record_mgmt.js create mode 100755 Open-ILS/web/staff/php/barcode/barcode.php create mode 100755 Open-ILS/web/staff/php/barcode/c128aobject.php create mode 100755 Open-ILS/web/staff/php/barcode/c128bobject.php create mode 100755 Open-ILS/web/staff/php/barcode/c128cobject.php create mode 100755 Open-ILS/web/staff/php/barcode/c39object.php create mode 100755 Open-ILS/web/staff/php/barcode/debug.php create mode 100755 Open-ILS/web/staff/php/barcode/download.php create mode 100755 Open-ILS/web/staff/php/barcode/download.png create mode 100755 Open-ILS/web/staff/php/barcode/home.php create mode 100755 Open-ILS/web/staff/php/barcode/home.png create mode 100755 Open-ILS/web/staff/php/barcode/i25object.php create mode 100755 Open-ILS/web/staff/php/barcode/image.php create mode 100755 Open-ILS/web/staff/php/barcode/image.png create mode 100755 Open-ILS/web/staff/php/barcode/index.php create mode 100755 Open-ILS/web/staff/php/barcode/linux.gif create mode 100755 Open-ILS/web/staff/php/barcode/php_logo.gif create mode 100755 Open-ILS/web/staff/php/barcode/sample.php create mode 100755 Open-ILS/web/staff/php/barcode/sample.png create mode 100755 Open-ILS/web/staff/php/barcode/spain.png create mode 100644 Open-ILS/web/staff/xml/circ_tracker.xml create mode 100644 Open-ILS/web/staff/xml/hold_tracker.xml create mode 100644 Open-ILS/web/staff/xml/record_mgmt.xml diff --git a/Open-ILS/src/perlmods/lib/FulfILLment/AT/Reactor/BibRefresh.pm b/Open-ILS/src/perlmods/lib/FulfILLment/AT/Reactor/BibRefresh.pm new file mode 100644 index 0000000000..20eb10dab0 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/FulfILLment/AT/Reactor/BibRefresh.pm @@ -0,0 +1,57 @@ +package FulfILLment::AT::Reactor::BibRefresh; +use base 'OpenILS::Application::Trigger::Reactor'; +use OpenSRF::Utils::Logger qw($logger); +use OpenILS::Application::AppUtils; +use OpenILS::Utils::CStoreEditor qw/:funcs/; + +use strict; use warnings; +use Error qw/:try/; +use OpenSRF::Utils::SettingsClient; + +my $U = 'OpenILS::Application::AppUtils'; +my $FF = OpenSRF::AppSession->create('fulfillment.laicore'); + +sub handler { + my $self = shift; + my $env = shift; + + update_bib($_) for @{$env->{target}}; + + return 1; +} + +sub update_bib { + my $self = shift; + my $b = shift || $self; + my $e = new_editor(); + + my $owner = $b->owner; + my ($error, $new_bibs); + + try { + $new_bibs = $FF->request( 'fulfillment.laicore.record_by_id', $owner, $b->remote_id)->gather(1); + } otherwise { + $error = 1; + }; + + unless ($error) { + if (@$new_bibs) { + + my $bib = shift @$new_bibs; + (my $id = $bib->{id}) =~ s#^/resources/##; + $b->ischanged(1); + $b->cache_time('edit_time'); + $b->marc($bib->{content}); + $b->remote_id($id); # just in case + + $e->xact_begin; + $e->update_biblio_record_entry($b) or return $e->die_event; + return $e->xact_commit; + } + + return undef; + } + + return 0; +} + diff --git a/Open-ILS/src/perlmods/lib/FulfILLment/AT/Reactor/ItemLoad.pm b/Open-ILS/src/perlmods/lib/FulfILLment/AT/Reactor/ItemLoad.pm new file mode 100644 index 0000000000..b22bc947d1 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/FulfILLment/AT/Reactor/ItemLoad.pm @@ -0,0 +1,119 @@ +package FulfILLment::AT::Reactor::ItemLoad; +use base 'OpenILS::Application::Trigger::Reactor'; +use OpenSRF::Utils::Logger qw($logger); +use OpenILS::Application::AppUtils; +use OpenILS::Utils::CStoreEditor qw/:funcs/; + +use strict; use warnings; +use Error qw/:try/; +use OpenSRF::Utils::SettingsClient; + +my $U = 'OpenILS::Application::AppUtils'; + +=comment +$item_template = { + + 'isbn_issn_code' => '', + 'call_number' => '', + 'edit_date' => '', + 'create_date' => '', + 'fingerprint' => '', + 'barcode' => '', + 'holdable' => 't', + 'call_number' => '', + 'agency_id' => '', + 'error' => 0, + 'error_message' => '' +}; +=cut + +sub ByBib { + my $self = shift; + my $env = shift; + + my $owner = $env->{target}->owner; + my $remote_bibid = $env->{target}->remote_id; + my $bibid = $env->{target}->id; + + my $FF = OpenSRF::AppSession->create('fulfillment.laicore'); + + my ($error, $new_items); + try { + $new_items = $FF->request( 'fulfillment.laicore.items_by_record', $owner, $remote_bibid)->gather(1); + } otherwise { + $error = 1; + }; + + if ($error or !$new_items or !@$new_items) { + $FF->disconnect; + return 0; + } + + my $e = new_editor(); + for my $remote_cp (@$new_items) { + try { + $e->xact_begin; + + $$remote_cp{call_number} ||= 'UNKNOWN'; + + $logger->info("Remote copy data: " . join(', ', map { "$_ => $$remote_cp{$_}" } keys %$remote_cp)); + + my $existing_cp = $e->search_asset_copy( + { source_lib => $owner, barcode => $$remote_cp{barcode} } + )->[0]; + + if (!$existing_cp) { + $existing_cp = Fieldmapper::asset::copy->new; + $existing_cp->isnew(1); + $existing_cp->creator(1); + $existing_cp->editor(1); + $existing_cp->loan_duration(2); + $existing_cp->fine_level(2); + $existing_cp->source_lib($owner); + $existing_cp->circ_lib($owner); + $existing_cp->barcode($$remote_cp{barcode}); + } + + $existing_cp->ischanged( 1 ); + $existing_cp->remote_id( $remote_cp->{bib_id} ); + $existing_cp->holdable( defined($remote_cp->{holdable}) ? $remote_cp->{holdable} : 1 ); + my $due = $remote_cp->{due_date} || ''; # avoid warnings + $existing_cp->status( $due =~ /^\d+-\d+-\d+$/ ? 1 : 0 ); + + + my $existing_cn = $e->search_asset_call_number( + { record => $bibid, owning_lib => $owner, label => $$remote_cp{call_number} } + )->[0]; + + if (!$existing_cn) { + $existing_cn = Fieldmapper::asset::call_number->new; + $existing_cn->isnew(1); + $existing_cn->creator(1); + $existing_cn->editor(1); + $existing_cn->label($$remote_cp{call_number}); + $existing_cn->owning_lib($owner); + $existing_cn->record($bibid); + + $existing_cn = $e->create_asset_call_number( $existing_cn ); + } + + $existing_cp->call_number( $existing_cn->id ); + + if ($existing_cp->isnew) { + $e->create_asset_copy( $existing_cp ); + } else { + $e->update_asset_copy( $existing_cp ); + } + + $e->xact_commit; + } otherwise { + $e->xact_rollback; + }; + } + $e->disconnect; + $FF->disconnect; + + return 1; +} + +1; diff --git a/Open-ILS/src/perlmods/lib/FulfILLment/AT/Reactor/ItemRefresh.pm b/Open-ILS/src/perlmods/lib/FulfILLment/AT/Reactor/ItemRefresh.pm new file mode 100644 index 0000000000..95ef382c42 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/FulfILLment/AT/Reactor/ItemRefresh.pm @@ -0,0 +1,86 @@ +package FulfILLment::AT::Reactor::ItemRefresh; +use base 'OpenILS::Application::Trigger::Reactor'; +use OpenSRF::Utils::Logger qw($logger); +use OpenILS::Application::AppUtils; +use OpenILS::Utils::CStoreEditor qw/:funcs/; + +use strict; use warnings; +use Error qw/:try/; +use OpenSRF::Utils::SettingsClient; + +my $U = 'OpenILS::Application::AppUtils'; + + +sub ByItem { + my $self = shift; + my $env = shift; + + update_item($_) for @{$env->{target}}; + + return 1; +} + +sub ByBib { + my $self = shift; + my $env = shift; + + my $e = new_editor(); + for my $cn (@{ $e->search_asset_call_number( { record => $env->{target}->id } ) }) { + update_item($_) for @{ $e->search_asset_copy( { call_number => $cn->id } ) }; + } + + return 1; +} + +sub update_item { + my $self = shift; + my $i = shift; + + $i = $self if (!$i); + + my $owner = $i->source_lib; + my $e = new_editor(); + my $FF = OpenSRF::AppSession->create('fulfillment.laicore'); + + my ($error, $new_items); + try { + $new_items = $FF->request( 'fulfillment.laicore.item_by_barcode', $owner, $i->barcode)->gather(1); + } otherwise { + $error = 1; + }; + + unless ($error) { + if (@$new_items) { + my $item = shift @$new_items; + + $i->ischanged(1); + $i->cache_time('now'); + $i->remote_id($item->{bib_id}); + $i->holdable($item->{holdable}); + $i->status( + $item->{due_date} =~ /^\d+-\d+-\d+$/ ? 1 : 0 + ); + + my $bib = $e->search_biblio_record_entry( + {remote_id => $item->{bib_id}, owner => $owner} + )->[0]; + + if ($bib) { + my $cn = $e->search_asset_call_number( + {label => $item->{call_number}, record => $bib->id} + )->[0]; + $i->call_number($cn->id) if ($cn); + } + + $e->xact_begin; + $e->update_asset_copy($i) or return $e->die_event; + return $e->xact_commit; + } + + return undef; + } + + return 0; +} + +1; diff --git a/Open-ILS/src/perlmods/lib/FulfILLment/Application/LAICore.pm b/Open-ILS/src/perlmods/lib/FulfILLment/Application/LAICore.pm new file mode 100644 index 0000000000..fc3edfee10 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/FulfILLment/Application/LAICore.pm @@ -0,0 +1,375 @@ +package FulfILLment::Application::LAICore; +use strict; use warnings; +use OpenILS::Application; +use base qw/OpenILS::Application/; +use OpenSRF::AppSession; +use OpenSRF::EX qw(:try); +use OpenSRF::Utils::SettingsClient; +use OpenSRF::Utils::Logger qw($logger); +use OpenSRF::Utils::JSON; +use OpenILS::Utils::Fieldmapper; +use OpenILS::Utils::CStoreEditor q/:funcs/; +use FulfILLment::LAIConnector; +use FulfILLment::AT::Reactor::ItemLoad; + +sub killme_wrapper { + + my $self = shift; + my $client = shift; + my $method = $self->{real_method}; + die "killme_wrapper called without method\n" unless $method; + + $logger->info("FF executing LAICore::$method()"); + + my @final = (); + eval { + local $SIG{ALRM} = sub { die 'killme' }; + alarm($self->{killme}); + my $this = bless($self,$self->{package}); + @final = $this->$method($client,@_); + alarm(0); + }; + alarm(0); + + if ($@) { + $logger->error("Error executing $method : $@"); + return undef; + } + + $client->respond($_) for (@final); + return undef; +} + +# TODO : If/when API calls return virtual Fieldmapper +# objects (or arrays thereof), a wrapper to hash-ify +# all outbound responses may be in order. + +sub register_method { + my $class = shift; + my %args = @_; + + $class = ref($class) || $class; + $args{package} = $class; + + if (exists($args{killme})) { + $args{real_method} = $args{method}; + $args{method} = 'killme_wrapper'; + return $class->SUPER::register_method( %args ); + } + + $class->SUPER::register_method( %args ); +} + + +__PACKAGE__->register_method( + method => "get_connector_info", + api_name => "fulfillment.laicore.connector_info.retrieve", + signature => { params => [ {desc => 'Org Unit ID', type => 'number'} ] }, + argc => 1, + api_level => 1 +); + +sub get_connector_info { + my ($self, $client, $ou) = @_; + return undef if ($ou !~ /^\d+$/); + return FulfILLment::LAIConnector->load($ou); +} + +__PACKAGE__->register_method( + method => "items_by_barcode", + api_name => "fulfillment.laicore.item_by_barcode", + killme => 120 +); +__PACKAGE__->register_method( + method => "items_by_barcode", + api_name => "fulfillment.laicore.item_by_barcode.batch", + killme => 120 +); + +sub items_by_barcode { + my ($self, $client, $ou, $ids) = @_; + + my $connector = FulfILLment::LAIConnector->load($ou) or return; + + return $connector->get_item_batch($ids) + if $self->api_name =~ /batch/; + + return $connector->get_item($ids); +} + +__PACKAGE__->register_method( + method => "items_by_record", + api_name => "fulfillment.laicore.items_by_record", + killme => 120 +); +__PACKAGE__->register_method( + method => "items_by_record", + api_name => "fulfillment.laicore.items_by_record.batch", + killme => 120 +); +sub items_by_record { + my ($self, $client, $ou, $ids) = @_; + + my $connector = FulfILLment::LAIConnector->load($ou) or return; + + return $connector->get_items_by_record_batch($ids) + if $self->api_name =~ /batch/; + + return $connector->get_items_by_record($ids); +} + +__PACKAGE__->register_method( + method => "records_by_item", + api_name => "fulfillment.laicore.record_by_item", + killme => 120 +); +__PACKAGE__->register_method( + method => "items_by_record", + api_name => "fulfillment.laicore.record_by_item.batch", + killme => 120 +); +sub records_by_item { + my ($self, $client, $ou, $ids) = @_; + + my $connector = FulfILLment::LAIConnector->load($ou) or return; + + return $connector->get_record_by_item_batch($ids) + if $self->api_name =~ /batch/; + + return $connector->get_record_by_item($ids); +} + +__PACKAGE__->register_method( + method => "get_holds", + api_name => "fulfillment.laicore.holds_by_item", + killme => 120 +); +__PACKAGE__->register_method( + method => "get_holds", + api_name => "fulfillment.laicore.holds_by_record", + killme => 120 +); +# target is copy_barcode or record_id +sub get_holds { + my ($self, $client, $ou, $target, $user_barcode) = @_; + + my $connector = FulfILLment::LAIConnector->load($ou) or return; + + return $connector->get_item_holds($target, $user_barcode) + if $self->api_name =~ /by_item/; + + return $connector->get_record_holds($target, $user_barcode); +} + +__PACKAGE__->register_method( + method => "lender_holds", + api_name => "fulfillment.laicore.hold.lender.place", + killme => 120 +); +__PACKAGE__->register_method( + method => "lender_holds", + api_name => "fulfillment.laicore.hold.lender.delete_earliest", + killme => 120 +); + +sub lender_holds { + my ($self, $client, $ou, $copy_barcode) = @_; + + my $connector = FulfILLment::LAIConnector->load($ou) or return; + my $e = new_editor(); + + # for lending library holds, we use the configured hold user + my $user_barcode = $connector->{'user.hold'} || $connector->{'user'}; + + if (!$user_barcode) { + $logger->error( + "FF no hold recipient defined for ou=$ou copy=$copy_barcode"); + return; + } + + # TODO: proxy user pickup lib setting? + return $connector->place_lender_hold($copy_barcode, $user_barcode) + if $self->api_name =~ /place/; + + return $connector->delete_lender_hold($copy_barcode, $user_barcode); +} + +__PACKAGE__->register_method( + method => "create_borrower_copy", + api_name => "fulfillment.laicore.item.create_for_borrower", + killme => 120 +); +sub create_borrower_copy { + my ($self, $client, $ou, $src_copy_id) = @_; + + my $connector = FulfILLment::LAIConnector->load($ou) or return; + my $e = new_editor(); + + my $src_copy = $e->retrieve_asset_copy([ + $src_copy_id, + { flesh => 3, + flesh_fields => { + acp => ['call_number'], + acn => ['record'], + bre => ['simple_record'] + } + } + ]); + + my $circ_lib = $e->retrieve_actor_org_unit($ou)->shortname; + + return $connector->create_borrower_copy($src_copy, $circ_lib); +} + + +__PACKAGE__->register_method( + method => "borrower_holds", + api_name => "fulfillment.laicore.hold.borrower.place", + killme => 120 +); +__PACKAGE__->register_method( + method => "borrower_holds", + api_name => "fulfillment.laicore.hold.borrower.delete_earliest", + killme => 120 +); +# --------------------------------------------------------------------------- +# Borrower Library Holds: +# Create a hold against the temporary copy for the borrowing user +# at the borrowing library. +# --------------------------------------------------------------------------- +sub borrower_holds { + my ($self, $client, $ou, $copy_barcode, $user_barcode) = @_; + + # TODO: should be a pickup_lib here based on the pickup_lib + # of the FF hold + + my $connector = FulfILLment::LAIConnector->load($ou) or return; + my $e = new_editor(); + my $pickup_lib = $e->retrieve_actor_org_unit($ou)->shortname; + + return $connector->place_borrower_hold( + $copy_barcode, $user_barcode, $pickup_lib) + if $self->api_name =~ /place/; + + return $connector->deletel_borrower_hold($copy_barcode, $user_barcode); +} + + +__PACKAGE__->register_method( + method => "circulation", + api_name => "fulfillment.laicore.circ.retrieve", + killme => 120 +); +__PACKAGE__->register_method( + method => "circulation", + api_name => "fulfillment.laicore.circ.lender.checkout", + killme => 120 +); +__PACKAGE__->register_method( + method => "circulation", + api_name => "fulfillment.laicore.circ.lender.checkin", + killme => 120 +); +__PACKAGE__->register_method( + method => "circulation", + api_name => "fulfillment.laicore.circ.borrower.checkout", + killme => 120 +); +__PACKAGE__->register_method( + method => "circulation", + api_name => "fulfillment.laicore.circ.borrower.checkin", + killme => 120 +); +sub circulation { + my ($self, $client, $ou, $item_ident, $user_barcode) = @_; + + my $connector = FulfILLment::LAIConnector->load($ou) or return; + + if ($self->api_name =~ /lender/) { + # the circulation on the lender side is always checked + # out to the circ proxy user. + + $user_barcode = $connector->{'user.circ'} || $connector->{'user'}; + if (!$user_barcode) { + $logger->error("FF proxy circ user defined for $ou"); + return; + } + } + + if ($self->api_name =~ /checkout/) { + + return $connector->checkout_lender($item_ident, $user_barcode) + if $self->api_name =~ /lender/; + + return $connector->checkout_borrower($item_ident, $user_barcode); + } + + if ($self->api_name =~ /checkin/) { + + return $connector->checkin_lender($item_ident, $user_barcode) + if $self->api_name =~ /lender/; + + return $connector->checkin_borrower($item_ident, $user_barcode); + } + + return $connector->get_circulation($item_ident, $user_barcode); +} + +__PACKAGE__->register_method( + method => "records_by_id", + api_name => "fulfillment.laicore.record_by_id", + killme => 120 +); +__PACKAGE__->register_method( + method => "records_by_id", + api_name => "fulfillment.laicore.record_by_id.batch", + killme => 120 +); +sub records_by_id { + my ($self, $client, $ou, $ids) = @_; + + my $connector = FulfILLment::LAIConnector->load($ou) or return; + + return $connector->get_record_by_id_batch($ids) + if $self->api_name =~ /batch/; + + return $connector->get_record_by_id($ids); +} + +__PACKAGE__->register_method( + method => "lookup_user", + api_name => "fulfillment.laicore.lookup_user", + killme => 120 +); + +sub lookup_user { + my ($self, $client, $ou, $user_barcode, $user_pass) = @_; + my $connector = FulfILLment::LAIConnector->load($ou) or return; + return $connector->get_user($user_barcode, $user_pass); +} + +__PACKAGE__->register_method( + method => "import_items_by_record", + api_name => "fulfillment.laicore.import_items_by_record", + killme => 120, + signature => q/ + Import items from the remote site via remote record ID. + Returns true on success, false on failure. + / +); +sub import_items_by_record { + my ($self, $client, $ou, $record_id) = @_; + FulfILLment::LAIConnector->load($ou) or return; + my $e = new_editor(); + + # record_id param refers to the remote_id + my $rec = $e->search_biblio_record_entry( + {owner => $ou, remote_id => $record_id} + )->[0] or return; + + return FulfILLment::AT::Reactor::ItemLoad->ByBib({target => $rec}); +} + + +1; + diff --git a/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector.pm b/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector.pm new file mode 100644 index 0000000000..095be8b54b --- /dev/null +++ b/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector.pm @@ -0,0 +1,611 @@ +package FulfILLment::LAIConnector; +use strict; use warnings; +use OpenSRF::Utils::Logger qw/$logger/; +use OpenILS::Application::AppUtils; +use OpenILS::Utils::CStoreEditor qw/:funcs/; +use FulfILLment::Util::Z3950; +use FulfILLment::Util::SIP2Client; +use FulfILLment::Util::NCIP; +my $U = 'OpenILS::Application::AppUtils'; + +# Determines the correct connector to load for the provided org +# unit and returns a ref to a new instance of the connector. +# This is the main sub called by external modules. +sub load { + my ($class, $org_id) = @_; + + # collect all of the FF org unit settings for this org. + my $settings = new_editor()->search_config_org_unit_setting_type( + {name => {'like' => 'ff.remote.connector%'}} + ); + + my $cdata = { + map { + $_->name => $U->ou_ancestor_setting($org_id, $_->name) + } @$settings + }; + + my %args = (org_id => $org_id, extra => {}); + + for my $key (keys %$cdata) { + my $setting = $cdata->{$key}; + my $value = $setting->{value}; + (my $newkey = $key) =~ s/^ff\.remote\.connector\.//; + if ($newkey =~ /^extra\./) { + $newkey =~ s/^extra\.//; + $args{extra}{$newkey} = $value; + } else { + $args{$newkey} = $value; + } + } + + if (!$args{type}) { + $logger->error("No ILS type specifed for org unit $org_id"); + return undef; + } + + if ($args{disabled}) { + $logger->info("FF connector for ".$args{type}." disabled"); + return; + } + + return $class->_load_connector($args{type}, $args{version}, \%args); +} + +# Returns a new LAIConnector for the specified type +# and (optional) version. All additional args are +# passed through to the connector constructor. +sub _load_connector { + my ($class, $type, $version, $args) = @_; + + my $module = "FulfILLment::LAIConnector::$type"; + $module .= "::$version" if $version; + + $logger->info("FF loading ILS module org=$$args{org_id} $module"); + + # note: do not wrap in eval {}. $module->use + # uses eval internally and nested evals clobber $@ + $module->use; + + if ($@) { + $logger->error("Unable to load $module : $@"); + return undef; + } + + my $connector; + eval { $connector = $module->new($args) }; + + if ($@) { + $logger->error("Unable to create $module object : $@"); + return undef; + } + + if (!$connector->init) { + $logger->error("Error initializing connector $module"); + return undef; + } + + return $connector; +} + +sub new { + my ($class, $args) = @_; + $args ||= {}; + return bless($args, $class); +} + +sub z39_client { + my $self = shift; + + if (!$self->{z39_client}) { + + my $host = $self->{extra}{'z3950.host'} || $self->{host}; + my $port = $self->{extra}{'z3950.port'}; + my $database = $self->{extra}{'z3950.database'}; + + unless ($host and $port and $database) { + $logger->info("FF Z39 not configured for $self, skipping..."); + return; + } + + $self->{z39_client} = + FulfILLment::Util::Z3950->new( + $host, $port, $database, + $self->{extra}{'z3950.username'}, + $self->{extra}{'z3950.password'} + ); + } + + return $self->{z39_client}; +} + +sub sip_client { + my $self = shift; + + if (!$self->{sip_client}) { + + my $host = $self->{extra}{'sip2.host'} || $self->{host}; + my $port = $self->{extra}{'sip2.port'}; + + unless ($host and $port) { + $logger->info("FF SIP not configured for $self, skipping..."); + return; + } + + $self->{sip_client} = + FulfILLment::Util::SIP2Client->new( + $host, + $self->{extra}{'sip2.username'}, + $self->{extra}{'sip2.password'}, + $port, + $self->{extra}{'sip2.protocol'}, # undef == SOCK_STREAM + $self->{extra}{'sip2.institution'} + ); + } + + return $self->{sip_client}; +} + +sub ncip_client { + my $self = shift; + + if (!$self->{ncip_client}) { + my $host = $self->{extra}{'ncip.host'} || $self->{host}; + my $port = $self->{extra}{'ncip.port'}; + + unless ($host and $port) { + $logger->info("FF NCIP not configured for $self, skipping..."); + return; + } + + $self->{ncip_client} = FulfILLment::Util::NCIP->new( + protocol => $self->{extra}->{'ncip.protocol'}, + host => $host, + port => $port, + path => $self->{extra}->{'ncip.path'}, + template_paths => ['/openils/var/ncip/v1'], # TODO + ils_agency_name => $self->{extra}->{'ncip.ils_agency.name'}, + ils_agency_uri => $self->{extra}->{'ncip.ils_agency.uri'}, + ff_agency_name => 'FulfILLment', + ff_agency_uri => 'http://fulfillment-ill.org/ncip/schemes/agency.scm' + ); + } + + return $self->{ncip_client}; +} + + +# override with connector-specific initialization as needed +# return true on success, false on failure +sub init { + my $self = shift; + return 1; +} + +# commonly accessed data + +# retursn the connector type (ff.remote.connector.type) +sub type { + my $self = shift; + return $self->{type}; +} +# returns the connector ILS version string (ff.remote.connector.version) +sub version { + my $self = shift; + return $self->{version}; +} +# returns the actor.org_unit.id for our current context org unit +sub org_id { + my $self = shift; + return $self->{org_id}; +} +# returns the actor.org_unit.shortname value for our current context org unit +sub org_code { + my $self = shift; + return $self->{org_code} ? $self->{org_code} : + $self->{org_code} = + new_editor()->retrieve_actor_org_unit($self->org_id)->shortname; +} + + +# ---------------------------------------------------------------------------- +# Below are methods responsible for communicating with remote ILSes. In some +# cases, default implementations are provided. This is only done when the +# implementation could reasonably by used by multiple connectors and only when +# using SIP2 or Z39.50 as the communication layer. +# +# Connectors should override each method as needed. +# ---------------------------------------------------------------------------- + +# returns one item +# Default implementation uses SIP2 +sub get_item { + my ($self, $copy_barcode) = @_; + + my $item = + $self->sip_client->item_information_request($copy_barcode) + or return; + + $item->{barcode} = $item->{item_identifier}; + $item->{status} = $item->{circulation_status}; + $item->{location_code} = $item->{permanent_location}; + + return $item; +} + +# returns a list of items +sub get_item_batch { + my ($self, $item_idents) = @_; + return [map {$self->get_item($_)} @$item_idents]; +} + +# returns a list of items +sub get_items_by_record { + my ($self, $record_id) = @_; + return []; +} + +# returns a list of items +sub get_items_by_record_batch { + my ($self, $record_ids) = @_; + return [map {$self->get_items_by_record($_)} @$record_ids]; +} + +# returns one record +sub get_record_by_id { + my ($self, $rec_id) = @_; + return; +} + +# returns one record +sub get_record_by_id_batch { + my ($self, $rec_ids) = @_; + return []; +} + +# returns one record +sub get_record_by_item { + my ($self, $item_ident) = @_; + return []; +} + +# returns a list of records +sub get_record_by_item_batch { + my ($self, $item_idents) = @_; + return []; +} + +# returns 1 user. +# Default implementation uses SIP2 +sub get_user { + my ($self, $user_barcode, $user_pass) = @_; + + my $user = $self->sip_client->lookup_user({ + patron_id => $user_barcode, + patron_pass => $user_pass + }); + + return unless $user; + + $user->{user_barcode} = $user->{patron_identifier}; + $user->{loaned_items} = $user->{charged_items}; + $user->{loaned_items_count} = $user->{charged_items_count}; + $user->{loaned_items_limit} = $user->{charged_items_limit}; + $user->{lang_pref} = $user->{language}; + $user->{phone} = $user->{home_phone_number}; + + $user->{billing_address} = $user->{home_address}; + $user->{mailing_address} = $user->{home_address}; + + return $user; +} + +# returns a list of holds +sub get_item_holds { + my ($self, $item_ident) = @_; + return []; +} + +# TODO: docs +sub place_borrower_hold { + my ($self, $item_barcode, $user_barcode, $pickup_lib) = @_; +} + +# TODO: docs +sub delete_borrower_hold { + my ($self, $item_barcode, $user_barcode) = @_; +} + +# TODO: docs +sub place_lender_hold { + my ($self, $item_barcode, $user_barcode, $pickup_lib) = @_; +} + +# TODO: docs +sub delete_lender_hold { + my ($self, $item_barcode, $user_barcode) = @_; +} + + +# --------------------------------------------------------------------------- +# Provide a default hold placement via SIP +# --------------------------------------------------------------------------- +sub place_hold_via_sip { + my $self = shift; + my $bib_id = shift || ''; + my $copy_barcode = shift || ''; + my $user_barcode = shift || ''; + my $pickup_lib = shift || ''; + my $expire_date = shift; + my $hold_type = shift; + + if (!$hold_type) { + # if no hold type is provided, assume passing + # a barcode implies a copy-level hold + + # 2 == bib hold + # 3 == copy hold + $hold_type = $copy_barcode ? 3 : 2; + } + + if (!$expire_date) { + $expire_date = # interval should be a setting? + DateTime->now->add({months => 6})->strftime("%Y%m%d 000000"); + } + + if (!$pickup_lib) { + # use the home org unit of the requesting user + # as the pickup lib if none is provided + my $user = $self->flesh_user($user_barcode); + $pickup_lib = $user->home_ou->shortname if $user; + } + + $logger->warn("FF has no pickup lib for $user_barcode") if !$pickup_lib; + + $logger->info("FF placing hold copy=$copy_barcode; ". + "pickup_lib=$pickup_lib; bib=$bib_id; ". + "user=$user_barcode; expire=$expire_date"); + + my $resp = $self->sip_client->place_hold($user_barcode, undef, + $expire_date, $pickup_lib, $hold_type, $copy_barcode, $bib_id); + + return undef unless $resp; + + my $blob = $self->translate_sip_hold($resp); + $blob->{bibID} = $bib_id; + $blob->{hold_type} = $bib_id ? 'T' : 'C'; + + return undef if $blob->{error}; + return $blob; +} + +sub translate_sip_hold { + my ($self, $sip_msg) = @_; + + my $fields = $sip_msg->{fields}; + my $fixed_fields = $sip_msg->{fixed_fields}; + + # TODO: verify returned format is sane + + return { + error => !$fixed_fields->[0], + error_message => $fields->{AF}, + success_message => $fields->{AF}, + expire_time => $fields->{BW}, + expires => $fields->{BW}, + placed_on => $fixed_fields->[2], + request_time => $fixed_fields->[2], + status => $fixed_fields->[1] eq 'Y' ? 'Available' : 'Pending', + title => $fields->{AJ}, + barcode => $fields->{AB}, + itemid => $fields->{AB}, + pickup_lib => $fields->{BS} + }; +} + +# given a copy barcode, this will return the copy whose source lib +# matches my org unit fleshed with its call number and bib record +sub flesh_copy { + my ($self, $copy_barcode) = @_; + + # find the FF copy so we can get the copy's record_id + my $copy = new_editor()->search_asset_copy([ + { barcode => $copy_barcode, source_lib => $self->org_id}, + { flesh => 2, + flesh_fields => { + acp => ['call_number'], + acn => ['record'] + } + } + ])->[0]; + + return $copy ? $copy : undef; +} + + +# given a user barcode, this will return the use whose home lib +# is at or below my org unit, fleshed with home_ou +sub flesh_user { + my ($self, $user_barcode) = @_; + + my $cards = new_editor()->search_actor_card([ + { barcode => $user_barcode, + org => $U->get_org_descendants($self->org_id) + }, { + flesh => 2, + flesh_fields => {ac => ['usr'], au => ['home_ou']} + } + ]); + + return @$cards ? $cards->[0]->usr : undef; +} + + + + +# returns one hold +# pickup_lib is the library code (org_unit.shortname) +sub place_item_hold { + my ($self, $item_ident, $user_barcode, $pickup_lib) = @_; + return; +} + +# returns one hold +# pickup_lib is the library code (org_unit.shortname) +sub place_record_hold { + my ($self, $rec_id, $user_barcode, $pickup_lib) = @_; + return; +} + +# returns one hold +# pickup_lib is the library code (org_unit.shortname) +sub delete_item_hold { + my ($self, $item_ident, $user_barcode, $pickup_lib) = @_; + return; +} + +# returns one hold +# pickup_lib is the library code (org_unit.shortname) +sub delete_record_hold { + my ($self, $rec_id, $user_barcode, $pickup_lib) = @_; + return; +} + +# returns a list of holds +sub get_record_holds { + my ($self, $rec_id) = @_; + return []; +} + +# --------------------------------------------------------------------------- +# Allow connectors to provide lender vs. borrower checkout and checkin +# handling. Call the stock checkout/checkin methods by default. +# --------------------------------------------------------------------------- +sub checkout_lender { + my $self = shift; + return $self->checkout(@_); +} +sub checkout_borrower { + my $self = shift; + return $self->checkout(@_); +} +sub checkin_lender { + my $self = shift; + return $self->checkin(@_); +} +sub checkin_borrower { + my $self = shift; + return $self->checkin(@_); +} + +# --------------------------------------------------------------------------- +# Provide default checkout and checkin routines via SIP. +# Override with connector-specific behavior as needed. +# --------------------------------------------------------------------------- +sub checkout { + my ($self, $item_barcode, $user_barcode) = @_; + return unless $self->sip_client; + my $resp = $self->sip_client->checkout($user_barcode, undef, $item_barcode); + return $self->sip_client->sip_msg_to_circ($resp, 'checkout'); +} + +sub checkin { + my ($self, $item_barcode, $user_barcode) = @_; + return unless $self->sip_client; + my $resp = $self->sip_client->checkin($user_barcode, undef, $item_barcode); + return $self->sip_client->sip_msg_to_circ($resp); +} + + +# --------------------------------------------------------------------------- + +# returns one circulation +sub get_circulation { + my ($self, $item_ident, $user_barcode) = @_; + return; +} + +# --------------------------------------------------------------------------- +# Reference copy is the asset.copy hold target for the lender hold, +# fleshed with ->call_number->record. The borrower copy is a temporary / +# dummy copy created at the borrowing library for the purposes of +# hold placement and circulation at the borrowing library. +# --------------------------------------------------------------------------- +sub create_borrower_copy { + my ($self, $reference_copy, $circ_lib_code) = @_; +} + +# -------------------------------------------------- +# -- these method have no corresponding API calls -- +# -- their purpose is unclear -- + +sub item_get_page { + # TODO: params? + return []; +} +sub item_get_range { + # TODO: params? + return []; +} +sub resource_get_page { + # TODO: params + return []; +} +sub resource_get_range { + # TODO: params + return []; +} +sub resource_get_actor_relation { + # TODO: params + return []; +} +sub resource_get_total_pages { + # TODO: params + return []; +} +sub resource_get_on_date { + # TODO: params + return []; +} +sub resource_get_after_date { + # TODO: params + return []; +} +sub resource_get_before_date { + # TODO: params + return []; +} +sub actor_get_range { + # TODO: params + return []; +} +sub actor_get_page { + # TODO: params + return []; +} +sub actor_get_total_pages { + # TODO: params + return []; +} +sub actor_list_holds { + my ($self, $user_barcode) = @_; + return []; +} +sub get_results_per_page { + # TODO: params + return []; +} +sub get_host_ills { + # TODO: params + return; +} +sub item_get_total_pages { + # TODO: params? + return []; +} +sub item_get_all { + # TODO: params? + return []; +} + +1; diff --git a/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Aleph.pm b/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Aleph.pm new file mode 100644 index 0000000000..fba7816b0a --- /dev/null +++ b/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Aleph.pm @@ -0,0 +1,6 @@ +package FulfILLment::LAIConnector::Aleph; +use base FulfILLment::LAIConnector; +use strict; use warnings; +use OpenSRF::Utils::Logger qw/$logger/; + +1; diff --git a/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Evergreen.pm b/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Evergreen.pm new file mode 100644 index 0000000000..4abbca9268 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Evergreen.pm @@ -0,0 +1,396 @@ +package FulfILLment::LAIConnector::Evergreen; +use base FulfILLment::LAIConnector; +use strict; use warnings; +use OpenSRF::Utils::Logger qw/$logger/; +use Digest::MD5 qw(md5_hex); +use LWP::UserAgent; +use HTTP::Request; +use JSON::XS; +use Data::Dumper; + +my $json = JSON::XS->new; +$json->allow_nonref(1); + +my $ua = LWP::UserAgent->new; +$ua->agent("FulfILLment/1.0"); + +sub gateway { + my ($self, $service, $method, @args) = @_; + + my $url = sprintf( + 'https://%s/osrf-gateway-v1?service=%s&method=%s', + $self->{host}, $service, $method + ); + $url .= '¶m=' . $json->encode($_) for (@args); + + $logger->debug("FF Evergreen gateway request => $url"); + + my $req = HTTP::Request->new('GET' => $url); + my $res = $ua->request($req); + + if (!$res->is_success) { + $logger->error( + "FF Evergreen gateway request error [HTTP ".$res->code."] $url"); + return undef; + } + + my $value = decode_json($res->content); + return $$value{payload} if $$value{status} == 200; +} + +# -------------------------------------------------------------------- +# Login +# TODO: support authtoken re-use (and graceful recovery) for +# faster batches of actions +# Always assumes barcode-based login. +# -------------------------------------------------------------------- +sub login { + my $self = shift; + my $username = shift || $self->{user}; + my $password = shift || $self->{passwd}; + my $type = shift; + + my $json = $self->gateway( + 'open-ils.fulfillment', + 'fulfillment.connector.login', + undef, $password, $type, $username + ); + + my $auth = $$json[0]; + $logger->info("EG: login failed for $username") unless $auth; + return $self->{authtoken} = $auth; +} + +sub get_user { + my $self = shift; + my $user_barcode = shift; + my $user_password = shift; + + my $resp = $self->gateway( + 'open-ils.fulfillment', + 'fulfillment.connector.verify_user_by_barcode', + $user_barcode, $user_password + ); + + # TODO: we always assume barcode logins in FF, but it would be + # nice if the EG connector could safey fall-through to username + # logins. Care must be taken to prevent multiple accounts, one + # for the barcode and one for the username. + + unless ($resp and $resp->[0]) { + $logger->info("EG: unable to verify user $user_barcode"); + return []; + } + + my $data = $resp->[0]; + + $logger->info("Evergreen retreived user " . Dumper($data)); + + $data->{surname} = $data->{family_name}; + $data->{user_id} = $data->{id}; + $data->{given_name} = $data->{first_given_name}; + $data->{exp_date} = $data->{expire_date}; + $data->{user_barcode} = ref($data->{card}) ? + $data->{card}->{barcode} : $user_barcode; + + return $data; +} + +sub get_items_by_record { + my ($self, $record_id) = @_; + + my $auth = $self->login or return []; + + my $resp = $self->gateway( + 'open-ils.fulfillment', + 'fulfillment.connector.copy_tree', + $auth, $record_id + ); + + my $cns = $resp->[0]; + my @copies = map {@{$_->{copies}}} @$cns; + $_->{bib_id} = $_->{record_id} = $record_id for @copies; + return \@copies; +} + +sub get_record_by_id { + my ($self, $record_id) = @_; + + my $url = sprintf( + "http://%s/opac/extras/supercat/retrieve/marcxml/record/%s", + $self->{host}, $record_id + ); + + $logger->info("FF EG get_record_by_id() => $url"); + + my $req = HTTP::Request->new("GET" => $url); + my $res = $ua->request($req); + + if (!$res->is_success) { + $logger->error( + "FF Evergreen gateway request error [HTTP ".$res->code."] $url"); + return undef; + } + + return { + marc => $res->content, + error => 0, + id => $record_id + }; +} + +sub get_item { + my ($self, $barcode) = @_; + + my $auth = $self->login; + + # TODO add fields as needed + my %fields; + for my $field (qw/ + id circ_lib barcode location status holdable circulate/) { + $fields{$field} = {path => $field, display => 1}; + } + + $fields{call_number} = {path => 'call_number.label', display => 1}; + $fields{record_id} = {path => 'call_number.record', display => 1}; + + my $resp = $self->gateway( + 'open-ils.fielder', + 'open-ils.fielder.flattened_search', + $auth, 'acp', \%fields, {barcode => $barcode} + ); + + my $copy = $resp->[0]; + $copy->{bib_id} = $copy->{record_id}; + + return $resp->[0]; +} + +sub get_record_holds { + my ($self, $record_id) = @_; + my $auth = $self->login; + + # TODO: xmlrpc is dead + #my $resp = $self->request( + #'open-ils.circ', + #'open-ils.circ.holds.retrieve_all_from_title', + #$key, + #$bibID, + #)->value; +} + +sub place_lender_hold { + my $self = shift; + return $self->place_borrower_hold(@_); +} + +sub place_borrower_hold { + my ($self, $copy_barcode, $user_barcode, $pickup_lib) = @_; + + my $auth = $self->login or return; + + my $resp = $self->gateway( + 'open-ils.fulfillment', + 'fulfillment.connector.create_hold', + $auth, $copy_barcode, $user_barcode + ); + + + $logger->debug("FF Evergreen item hold for copy=$copy_barcode ". + "user=$user_barcode resulted in ".Dumper($resp)); + + # NOTE: fulfillment.connector.create_hold only returns + # the hold ID and not the hold object. is that enough? + return $$resp[0] if $resp and @$resp; + return; +} + +sub delete_lender_hold { + my $self = shift; + return $self->delete_borrower_hold(@_); +} + +sub delete_borrower_hold { + my ($self, $copy_barcode, $user_barcode) = @_; + + my $auth = $self->login or return; + + my $resp = $self->gateway( + 'open-ils.fulfillment', + 'fulfillment.connector.cancel_oldest_hold', + $auth, $copy_barcode + ); + + # NOTE: fulfillment.connector.cancel_oldest_hold only + # returns success or failure. is that enough? + return $resp and @$resp and $$resp[0]; +} + +sub create_borrower_copy { + my ($self, $ref_copy, $ou_code) = @_; + + my $auth = $self->login or return; + + my $resp = $self->gateway( + 'open-ils.fulfillment', + 'fulfillment.connector.create_borrower_copy', + $auth, $ou_code, $ref_copy->barcode, { + title => $ref_copy->call_number->record->simple_record->title, + author => $ref_copy->call_number->record->simple_record->author, + isbn => $ref_copy->call_number->record->simple_record->isbn, + } + ); + + my $copy = $resp->[0] if $resp; + + unless ($copy) { + $logger->error( + "FF unable to create borrower copy for ".$ref_copy->barcode); + return; + } + + $logger->debug("FF created borrower copy " . Dumper($copy)); + + return $copy; +} + +# borrower checkout uses a precat copy +sub checkout_borrower { + my ($self, $copy_barcode, $user_barcode) = @_; + + my $args = { + copy_barcode => $copy_barcode, + patron_barcode => $user_barcode, + request_precat => 1 + }; + + return $self->_perform_checkout($args); +} + +# to date, lender checkout requires no special handling +sub checkout_lender { + my ($self, $copy_barcode, $user_barcode) = @_; + + my $args = { + copy_barcode => $copy_barcode, + patron_barcode => $user_barcode + }; + + return $self->_perform_checkout($args); +} + +# --------------------------------------------------------------------------- +# attempts a checkout. +# if the checkout fails with a COPY_IN_TRANSIT event, abort the transit and +# attempt the checkout again. +# --------------------------------------------------------------------------- +sub _perform_checkout { + my ($self, $args) = @_; + my $auth = $self->login or return; + my $copy_barcode = $args->{copy_barcode}; + + my $resp = $self->_send_checkout($auth, $args) or return; + + if ($resp->{textcode} eq 'COPY_IN_TRANSIT') { + # checkout of in-transit copy attempted. We really want this + # copy, so let's abort the transit, then try again. + + $logger->info("FF EG attempting to abort ". + "transit on $copy_barcode for checkout"); + + my $resp2 = $self->gateway( + 'open-ils.circ', + 'open-ils.circ.transit.abort', + $auth, {barcode => $copy_barcode} + ); + + if ($resp2 and $resp2->[0] eq '1') { + $logger->info( + "FF EG successfully aborted transit for $copy_barcode"); + + # re-do the checkout + $resp = $self->_send_checkout($auth, $args); + + } else { + $logger->warn("FF EG unable to abort transit on checkout"); + return; + } + + } + + return $resp; +} + +sub _send_checkout { + my ($self, $auth, $args) = @_; + + my $resp = $self->gateway( + 'open-ils.circ', + 'open-ils.circ.checkout.full.override', + $auth, $args + ); + + if ($resp) { + # gateway returns an array + if ($resp = $resp->[0]) { + # circ may return an array of events; use the first. + $resp = $resp->[0] if ref($resp) eq 'ARRAY'; + $logger->info("FF EG checkout returned event ".$resp->{textcode}); + return $resp; + } + } + + $logger->error("FF EG checkout failed to return a response"); + return; +} + + +sub checkin { + my ($self, $copy_barcode, $user_barcode) = @_; + my $auth = $self->login or return; + + # we want to check the item in at the + # correct location or it will go into transit. + my $fields = { + id => {path => 'id', display => 1}, + shortname => {path => 'shortname', display => 1} + }; + + my $resp = $self->gateway( + 'open-ils.fielder', + 'open-ils.fielder.flattened_search', + $auth, 'aou', $fields, {shortname => $self->org_code} + ); + + my $checkin_args = {copy_barcode => $copy_barcode}; + + if ($resp and $resp->[0]) { + $logger->debug("FF EG found org ".Dumper($resp)); + $checkin_args->{circ_lib} = $resp->[0]->{id}; + } else { + $logger->warn("FF EG unable to locate org unit ". + $self->org_code.", passing no circ_lib on checkin"); + } + + $resp = $self->gateway( + 'open-ils.circ', + 'open-ils.circ.checkin.override', + $auth, $checkin_args + ); + + if ($resp) { + # gateway returns an array + if ($resp = $resp->[0]) { + # circ may return an array of events; use the first. + $resp = $resp->[0] if ref($resp) eq 'ARRAY'; + $logger->info("FF EG checkin returned event ".$resp->{textcode}); + return $resp; + } + } + + $logger->error("FF EG checkin failed to return a response"); + return; +} + + +1; diff --git a/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Evergreen/FulfILLment_EGAPP.pm b/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Evergreen/FulfILLment_EGAPP.pm new file mode 100644 index 0000000000..25d5242183 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Evergreen/FulfILLment_EGAPP.pm @@ -0,0 +1,489 @@ +# +#=============================================================================== +# +# FILE: FF-evergreen-app.pm +# +# DESCRIPTION: +# +# FILES: --- +# BUGS: --- +# NOTES: --- +# AUTHOR: Michael Davadrian Smith (), msmith@esilibrary.com, Mike Rylander, miker@esilibrary.com +# COMPANY: Equinox Software +# VERSION: 1.0 +# CREATED: 01/29/2013 03:27:04 PM +# REVISION: --- +#=============================================================================== + +use strict; +use warnings; + +package OpenILS::Application::FulfILLment_EGAPP; + +use OpenILS::Application; +use base qw/OpenILS::Application/; + +use OpenSRF::EX qw(:try); +use OpenSRF::Utils::SettingsClient; +use OpenSRF::Utils::Logger qw($logger); + +use OpenILS::Utils::Fieldmapper; +use OpenILS::Const qw/:const/; +use OpenILS::Application::AppUtils; +my $U = 'OpenILS::Application::AppUtils'; + +use OpenILS::Utils::CStoreEditor q/:funcs/; +use Digest::MD5 qw(md5_hex); + +sub login { + my( $self, $client, $username, $password, $type, $barcode ) = @_; + + $type |= "staff"; + + my $seed = $U->simplereq( + 'open-ils.auth', + 'open-ils.auth.authenticate.init', + $username || $barcode + ); + + $logger->info("No auth seed. Couldn't talk to the auth server") unless $seed; + + my $args = { + username => $username, + agent => 'fulfillment', + password => md5_hex($seed . md5_hex($password)), + type => $type + }; + + if ($barcode) { + delete $args->{username}; + $args->{barcode} = $barcode; + } + + my $response = $U->simplereq( + 'open-ils.auth', + 'open-ils.auth.authenticate.complete', + $args + ); + + $logger->info("No auth response returned on login.") unless $response; + + my $authtime = $response->{payload}->{authtime}; + my $authtoken = $response->{payload}->{authtoken}; + + my $ident = $username || $barcode; + $logger->info("Login failed for user $ident!") unless $authtoken; + + return $authtoken || ''; +} + +__PACKAGE__->register_method( + method => "login", + api_name => "fulfillment.connector.login", + signature => { + desc => "Authenticate the requesting user", + params => [ + { name => 'username', type => 'string' }, + { name => 'passwd', type => 'string' }, + { name => 'type', type => 'string' } + ], + 'return' => { + desc => 'Returns an authentication token on success, or an empty string on failure' + } + } +); + +sub lookup_user { + my ($self, $client, $authtoken, $keys, $value) = @_; + + $keys = [$keys] if (!ref($keys)); + + ($authtoken) = $self + ->method_lookup( 'fulfillment.connector.login' ) + ->run(@$authtoken) + if (ref $authtoken); + + my $e = new_editor(authtoken => $authtoken); + + return undef unless $e->checkauth; + + for my $k ( @$keys ) { + my $users = $e->search_actor_user([ + { $k => $value }, + {flesh => 1, flesh_fields => {au => ['card']}} + ]); + + if ($users->[0]) { + + # user's are allowed to retrieve their own accounts + # regardless of proxy permissions + return recursive_hash($users->[0]) + if $users->[0]->id eq $e->requestor->id; + + # all other user retrievals require proxy user permissions + return undef unless $e->allowed('fulfillment.proxy_user'); + return recursive_hash($users->[0]); + } + } + + return undef; +} + +__PACKAGE__->register_method( + method => "lookup_user", + api_name => "fulfillment.connector.lookup_user", + signature => { + desc => "Retrieve a user hash", + params => [ + { name => 'authtoken', type => 'string', desc => 'Either a valid auth token OR an arrayref containing a username and password to log in as' }, + { name => 'keys', type => 'string', 'One or more fields against which to attempt matching the retrieval value, such as "id" or "usrname"' }, + { name => 'lookup_value', type => 'string' } + ], + 'return' => { + desc => 'Returns a user hash on success, or nothing on failure' + } + } +); + +sub verify_user_by_barcode { + my ($self, $client, $user_barcode, $user_password) = @_; + + my ($authtoken) = $self + ->method_lookup( 'fulfillment.connector.login' ) + ->run(undef, $user_password, 'temp', $user_barcode); + + return undef unless $authtoken; + + my $user = $U->simplereq( + 'open-ils.auth', + 'open-ils.auth.session.retrieve', + $authtoken + ); + + return recursive_hash($user); +} + +__PACKAGE__->register_method( + method => "verify_user_by_barcode", + api_name => "fulfillment.connector.verify_user_by_barcode", + signature => { + desc => q/Given a user barcode and password, returns the + user hash if the barcode+password combo is valid/, + params => [ + {name => 'barcode', type => 'string', desc => 'User barcode'}, + {name => 'password', type => 'string', desc => 'User password'} + ], + 'return' => { + desc => 'Returns a user hash on success, or nothing on failure' + } + } +); + + +sub lookup_holds { + my ($self, $client, $authtoken, $uid) = @_; + + ($authtoken) = $self + ->method_lookup( 'fulfillment.connector.login' ) + ->run(@$authtoken) + if (ref $authtoken); + + my $e = new_editor(authtoken => $authtoken); + + return undef unless $e->checkauth; + return undef unless $e->allowed('fulfillment.proxy_user'); + + $uid ||= $e->requestor; + + my $holds = $e->search_action_hold_request([ + { usr => $uid, capture_time => undef, cancel_time => undef }, + { order_by => { ahr => 'request_time' } } + ]); + + return recursive_hash($holds); +} + +__PACKAGE__->register_method( + method => "lookup_holds", + api_name => "fulfillment.connector.lookup_holds", + signature => { + desc => "Retrieve a list of open holds for a user", + params => [ + { name => 'authtoken', type => 'string', desc => 'Either a valid auth token OR an arrayref containing a username and password to log in as' } + ], + 'return' => { + desc => 'Returns an array of hold hashes on success, or nothing on failure' + } + } +); + +sub copy_detail { + my ($self, $client, $authtoken, $barcode ) = @_; + + ($authtoken) = $self + ->method_lookup( 'fulfillment.connector.login' ) + ->run(@$authtoken) + if (ref $authtoken); + + my $e = new_editor(authtoken => $authtoken); + return undef unless $e->checkauth; + return undef unless $e->allowed('fulfillment.proxy_user'); + + my $tree = $U->simplereq('open-ils.circ', 'open-ils.circ.copy_details.retrieve.barcode', $authtoken, $barcode); + + return recursive_hash($tree); +} + +__PACKAGE__->register_method( + method => "copy_detail", + api_name => "fulfillment.connector.copy_detail", + signature => { + desc => "Fetch a copy tree by bib id, optionally org-scoped", + params => [ + { name => 'authtoken', type => 'string', desc => 'Either a valid auth token OR an arrayref containing a username and password to log in as' }, + { name => 'barcode', type => 'string', desc => 'Copy barcode' }, + ], + 'return' => { + desc => 'Returns a fleshed copy on success, or nothing on failure' + } + } +); + +sub copy_tree { + my ($self, $client, $authtoken, $bib, @orgs ) = @_; + + ($authtoken) = $self + ->method_lookup( 'fulfillment.connector.login' ) + ->run(@$authtoken) + if (ref $authtoken); + + my $e = new_editor(authtoken => $authtoken); + return undef unless $e->checkauth; + return undef unless $e->allowed('fulfillment.proxy_user'); + + my $tree; + if (@orgs) { + $tree = $U->simplereq('open-ils.cat', 'open-ils.cat.asset.copy_tree.retrieve', $authtoken, $bib, @orgs); + } else { + $tree = $U->simplereq('open-ils.cat', 'open-ils.cat.asset.copy_tree.global.retrieve', $authtoken, $bib); + } + + return recursive_hash($tree); +} + +__PACKAGE__->register_method( + method => "copy_tree", + api_name => "fulfillment.connector.copy_tree", + signature => { + desc => "Fetch a copy tree by bib id, optionally org-scoped", + params => [ + { name => 'authtoken', type => 'string', desc => 'Either a valid auth token OR an arrayref containing a username and password to log in as' }, + { name => 'bib_id', type => 'string', desc => 'Bib id to fetch copies from' }, + { name => 'org', type => 'string', desc => 'Org id for copy scoping; repeatable' }, + ], + 'return' => { + desc => 'Returns a CN-CP tree on success, or nothing on failure' + } + } +); + +sub recursive_hash { + my $obj = shift; + + if (ref($obj)) { + if (ref($obj) =~ /Fieldmapper/) { + $obj = $obj->to_bare_hash; + $$obj{$_} = recursive_hash($$obj{$_}) for (keys %$obj); + } elsif (ref($obj) =~ /ARRAY/) { + $obj = [ map { recursive_hash($_) } @$obj ]; + } else { + $$obj{$_} = recursive_hash($$obj{$_}) for (keys %$obj); + } + } + + return $obj; +} + + +sub create_hold { + my ($self, $client, $authtoken, $copy_bc, $patron_bc) = @_; + + ($authtoken) = $self + ->method_lookup( 'fulfillment.connector.login' ) + ->run(@$authtoken) + if (ref $authtoken); + + my $e = new_editor(authtoken => $authtoken); + return undef unless $e->checkauth; + return undef unless $e->allowed('fulfillment.proxy_user'); + + my $patron = $e->requestor->id; + + if ($patron_bc) { + my $p = $e->search_actor_card({barcode => $patron_bc})->[0]; + $patron = $p->id if $p; + } + + my $copy = $e->search_asset_copy({barcode => $copy_bc, deleted => 'f'})->[0]; + return undef unless ($copy); + + my $hold = new Fieldmapper::action::hold_request; + $hold->usr($patron); + $hold->target($copy->id); + $hold->hold_type('C'); + $hold->pickup_lib($copy->circ_lib); + + my $resp = $U->simplereq('open-ils.circ', 'open-ils.circ.holds.create.override', $authtoken, $hold); + + return undef if (ref $resp); + return $resp; +} + +__PACKAGE__->register_method( + method => "create_hold", + api_name => "fulfillment.connector.create_hold", + signature => { + desc => "Create a new hold", + params => [ + { name => 'authtoken', type => 'string', desc => 'Either a valid auth token OR an arrayref containing a username and password to log in as' }, + { name => 'copy_bc', type => 'string', desc => 'Copy barcode on which to place a hold' }, + { name => 'patron_bc', type => 'string', desc => 'Patron barcode as which to place a hold, if different from calling user' }, + ], + 'return' => { + desc => 'Returns a hold id on success, or nothing on failure' + } + } +); + +sub cancel_proxy_hold { + my ($self, $client, $authtoken, $copy_bc) = @_; + + ($authtoken) = $self + ->method_lookup( 'fulfillment.connector.login' ) + ->run(@$authtoken) + if (ref $authtoken); + + my $e = new_editor(authtoken => $authtoken); + return undef unless $e->checkauth; + return undef unless $e->allowed('fulfillment.proxy_user'); + + my ($holds) = $self + ->method_lookup( 'fulfillment.connector.lookup_holds' ) + ->run($authtoken); + + my $copy = $e->search_asset_copy({barcode => $copy_bc, deleted => 'f'})->[0]; + return undef unless ($copy); + + $holds = [ grep { $_->{target} == $copy->id } @$holds ]; + + my $resp = $U->simplereq('open-ils.circ', 'open-ils.circ.hold.cancel', $authtoken, $holds->[-1]->{id}) if (@$holds); + + return undef if (ref $resp); + return $resp; +} + +__PACKAGE__->register_method( + method => "cancel_proxy_hold", + api_name => "fulfillment.connector.cancel_oldest_hold", + signature => { + desc => "Retrieve a list of open holds for a user", + params => [ + { name => 'authtoken', type => 'string', desc => 'Either a valid auth token OR an arrayref containing a username and password to log in as' }, + { name => 'copy_bc', type => 'string', desc => 'Copy barcode against which to cancel the oldest hold' }, + ], + 'return' => { + desc => 'Returns 1 on success, or nothing on failure' + } + } +); + + +__PACKAGE__->register_method( + method => "create_borrower_copy", + api_name => "fulfillment.connector.create_borrower_copy", + signature => { + desc => "Creates a pre-cat copy for borrower holds/circs", + params => [ + { name => 'authtoken', + type => 'string', + desc => q/Either a valid auth token OR an arrayref + containing a username and password to log in as/ + }, + { name => 'ou_code', + type => 'string', + desc => 'org_unit shortname to use as the copy circ lib' + }, + { name => 'barcode', + type => 'string', + desc => q/copy barcode. Note, if a barcode collision + occurs the barcode of the final copy may be different/, + }, + { name => 'args', + type => 'hash', + desc => 'Hash of optional extra information: title, author, isbn' + } + ], + 'return' => { + desc => 'Copy object (hash) or nothing on failure' + } + } +); + +sub create_borrower_copy { + my ($self, $client, $auth, $ou_code, $barcode, $args) = @_; + $args ||= {}; + + ($auth) = $self + ->method_lookup('fulfillment.connector.login') + ->run(@$auth) if ref $auth; + + my $e = new_editor(authtoken => $auth, xact => 1); + + my $e_copy = $e->search_asset_copy( + {deleted => 'f', barcode => $barcode}); + + # copy with the requested barcode already exists. + # Add a prefix to the barcode. + # TODO: make the prefix a setting + # TODO: maybe all such copies should be given a prefix for consistency + $barcode = "FF$barcode" if @$e_copy; + + my $circ_lib = $e->search_actor_org_unit({shortname => $ou_code}); + if (!@$circ_lib) { + $logger->error("Unable to locate org unit '$ou_code'"); + $e->rollback; + return; + } + + my $copy = Fieldmapper::asset::copy->new; + $copy->barcode($barcode); + $copy->circ_lib($circ_lib->[0]->id); + $copy->creator($e->requestor->id); + $copy->editor($e->requestor->id); + + $copy->call_number(OILS_PRECAT_CALL_NUMBER); + $copy->loan_duration(OILS_PRECAT_COPY_LOAN_DURATION); + $copy->fine_level(OILS_PRECAT_COPY_FINE_LEVEL); + + # if the caller provided any additional metadata on the + # item we're creating, capture it in the dummy fields + $copy->dummy_title($args->{title} || ""); + $copy->dummy_author($args->{author} || ""); + $copy->dummy_isbn($args->{isbn} || ""); + + unless ($e->create_asset_copy($copy)) { + $logger->error("error creating FF precat copy"); + $e->rollback; + return; + } + + # fetch from DB to ensure updated values (dates, etc.) + $copy = $e->retrieve_asset_copy($copy->id); + $e->commit; + return recursive_hash($copy); +} + + + +1; + diff --git a/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Horizon.pm b/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Horizon.pm new file mode 100644 index 0000000000..85f167feb0 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Horizon.pm @@ -0,0 +1,318 @@ +package FulfILLment::LAIConnector::Horizon; +use base FulfILLment::LAIConnector; +use strict; use warnings; +use OpenSRF::Utils::Logger qw/$logger/; +use OpenILS::Utils::Fieldmapper; +use FulfILLment::Util::Z3950; +use FulfILLment::Util::SIP2Client; +use XML::LibXML; +use Data::Dumper; +use Encode; +use Unicode::Normalize; +use DateTime; +use MARC::Record; +use MARC::File::XML ( BinaryEncoding => 'utf8' ); +use OpenILS::Utils::CStoreEditor qw/:funcs/; +use OpenILS::Application::AppUtils; +my $U = 'OpenILS::Application::AppUtils'; + +sub get_item { + my ($self, $item_ident) = @_; + + my $sip_item = $self->sip_client->item_information_request($item_ident); + return unless $sip_item; + + my $item = $sip_item; + $item->{barcode} = $sip_item->{item_identifier}; + $item->{status} = $sip_item->{circulation_status}; + $item->{location_code} = $sip_item->{permanent_location}; + + return $item; +} + +sub get_item_batch { + my ($self, $item_barcodes) = @_; + return [map {$self->get_item($_)} @$item_barcodes]; +} + +sub get_record_by_id { + my ($self, $record_id) = @_; + + my $bib = {}; + my $attr = $self->{args}{extra}{'z3950.search_attr'}; + my $xml = $self->z39_client->get_record_by_id($record_id, $attr); + + # TODO: clean this up + if ($xml =~ /something went wrong/) { + $bib->{'error'} = 1; + $bib->{'error_message'} = $xml; + } else { + $bib->{'marc'} = $xml; + $bib->{'id'} = $record_id; + } + + return $bib; +} + + + +=comment Format for holdings via Z39.50 + + + + LIB NAME + Juvenile Fiction + J ROWLING + + + + LIB NAME + 1234567890 + + + Checked In + + + + ... +=cut + +sub get_items_by_record { + my ($self, $record_id) = @_; + + my $attr = $self->{args}{extra}{'z3950.search_attr'}; + my $xml = $self->z39_client->get_record_by_id($record_id, $attr, undef, 'opac', 1); + + # entityize() + $xml = decode_utf8($xml); + NFC($xml); + $xml =~ s/&(?!\S+;)/&/gso; + $xml =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe; + + # strip control chars, etc. + # silence 'uninitialized value in substitution iterator' + no warnings; + $xml =~ s/([\x{0000}-\x{001F}])//sgoe; + use warnings; + + my $doc = XML::LibXML->new->parse_string($xml); + + my %map = ( + circ_lib => 'localLocation', + location => 'shelvingLocation', + call_number => 'callNumber', + barcode => 'itemId', + status => 'temporaryLocation' + ); + + my @copies; + for my $node ($doc->findnodes('//holding')) { + my $copy = {}; + + for my $key (keys %map) { + my $fnode = $node->getElementsByTagName($map{$key})->[0]; + $copy->{$key} = $fnode->textContent if $fnode; + } + + push(@copies, $copy); + } + + return \@copies; +} + +sub get_user { + my ($self, $user_barcode, $user_pass) = @_; + + # fetch the user using the default implementation + my $user = $self->SUPER::get_user($user_barcode, $user_pass); + return unless $user; + + # munge the names... + # personal_name is delivered => SURNAME, GIVEN NAME + my @names = split(',', $user->{personal_name} || $user->{full_name}); + $user->{full_name} = $user->{personal_name}; + $user->{given_name} = $names[1]; + $user->{surname} = $names[0]; + + return $user; +} + +# copy hold +sub place_borrower_hold { + my ($self, $item_barcode, $user_barcode, $pickup_lib) = @_; + return $self->place_hold_via_sip( + undef, $item_barcode, $user_barcode, $pickup_lib); +} + +# bib hold +sub place_lender_hold { + my ($self, $item_barcode, $user_barcode, $pickup_lib) = @_; + + my $copy = $self->flesh_copy($item_barcode) or return; + + return $self->place_hold_via_sip( + $copy->call_number->record->id, + undef, # item_barcode + $user_barcode, + $pickup_lib + ); +} + +sub delete_borrower_hold { + my ($self, $item_barcode, $user_barcode) = @_; + + my $resp = $self->sip_client->delete_hold( + $user_barcode, + undef, undef, undef, undef, + $item_barcode + ); + + return $resp ? $self->translate_sip_hold($resp) : undef; +} + +sub delete_lender_hold { + my ($self, $item_barcode, $user_barcode) = @_; + my $copy = $self->flesh_copy($item_barcode) or return; + + my $resp = $self->sip_client->delete_hold( + $user_barcode, + undef, undef, undef, undef, undef, + $copy->call_number->record->id + ); + + return $resp ? $self->translate_sip_hold($resp) : undef; +} + +sub delete_item_hold { + my ($self, $item_ident, $user_barcode) = @_; + + my $resp = $self->sip_client->delete_hold( + $user_barcode, undef, undef, undef, undef, $item_ident); + + return unless $resp; + return $self->translate_sip_hold($resp); +} + +sub delete_record_hold { + my ($self, $record_id, $user_barcode) = @_; + + my $resp = $self->sip_client->delete_hold( + $user_barcode, undef, undef, undef, undef, undef, $record_id); + + return [] unless $resp; + + my $blob = $self->translate_sip_hold($resp); + $blob->{bibID} = $record_id; + return $blob; +} + + +# --------------------------------------------------------- +# BELOW NEEDS RE-TESTING +# --------------------------------------------------------- + +sub get_user_holds { + my $self = shift; + my $user_barcode = shift; + my $user_pass = shift; + my @holds; + + # TODO: requiring user_pass may be problematic... + + # Horizon provides available holds and unavailable holds in + # separate lists, which requires multiple patron info requests. + # FF does not differentiate, though, so collect them all + # into one patron holds list. + + # available holds + my $user = $self->sip_client->lookup_user({ + patron_id => $user_barcode, + patron_pass => $user_pass + }); + + # TODO: fix the Available/Pending statuses? + + if ($user) { + $logger->debug("User hold items = @{$user->{hold_items}}"); + push(@holds, translate_patron_info_hold($_, 'Available')) + for @{$user->{hold_items}}; + } + + # unavailable holds + $user = $self->sip_client->lookup_user({ + patron_id => $user_barcode, + patron_pass => $user_pass, + enable_summary_pos => 5 + }); + + if ($user) { + $logger->debug("User pending hold items = @{$user->{hold_items}}"); + push(@holds, translate_patron_info_hold($_, 'Pending')) + for @{$user->{hold_items}}; + } + + return \@holds; +} + +sub translate_patron_info_hold { + my ($txt, $status) = @_; + + # Horizon SIP2 patron info hold format + # |CDSOFFTESTB12 CENT 06/05/13 $0.00 b SO FF Test Book 1| + # |CDSOFFTESTB22 CENT 06/05/13 $0.00 b SO FF Test Book 2| + + my ($barcode, undef, $xact_start, undef, undef, $title) = split(' ', $txt); + + return { + placed_on => $xact_start, + status => $status, + title => $title, + barcode => $barcode, + itemid => $barcode + }; +} + +sub create_borrower_copy { + my ($self, $ref_copy, $ou_code) = @_; + + return unless $self->ncip_client; + + my $simple_rec = $ref_copy->call_number->record->simple_record; + + my ($doc, @errs) = $self->ncip_client->request( + 'CreateItem', + item => { + barcode => $ref_copy->barcode, + call_number => $ref_copy->call_number->label, + title => $simple_rec->title, + author => $simple_rec->author, + owning_lib => $ou_code + } + ); + + + @errs = ('See Error Log') unless @errs or $doc; + + if (@errs) { + $logger->error( + "FF unable to create borrower copy ". + $ref_copy->barcode." : @errs"); + return; + } + + my $barcode = $doc->findnodes( + '//CreateItemResponse/UniqueItemId/ItemIdentifierValue' + )->string_value; + + if (!$barcode) { + $logger->error("FF unable to create borrower copy : ".$doc->toString); + return; + } + + $logger->info("FF created borrower copy $barcode"); + + return {barcode => $barcode}; +} + + +1; diff --git a/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/III.pm b/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/III.pm new file mode 100644 index 0000000000..a26b0e0684 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/III.pm @@ -0,0 +1,144 @@ +package FulfILLment::LAIConnector::III; +use base FulfILLment::LAIConnector; +use strict; use warnings; +use Net::Telnet; +use Net::OpenSSH; +use OpenSRF::Utils::Logger qw/$logger/; + +# connect to SSH / Millenium terminal app +sub ssh_connect { + my $self = shift; + + my $host = $self->{'ssh.host'} || $self->{'host.item'} || $self->{host}; + my $user = $self->{'ssh.user'} || $self->{'user.item'} || $self->{user}; + my $pass = $self->{'ssh.passwd'} || $self->{'passwd.item'} || $self->{passwd}; + + # creating a pty spawns a child process make sure + # we're not picking up any existing sigchld handlers + $SIG{CHLD} = 'DEFAULT'; + + $logger->info("FF III SSH connecting to $user\@$host"); + + my $ssh = Net::OpenSSH->new( + $host, user => $user, password => $pass); + + if ($ssh->error) { + $logger->error("FF III SSH connect error " . $ssh->error); + return; + } + + my ($fh, $pid) = $ssh->open2pty(); + my $term = Net::Telnet->new(Fhopen => $fh); + + # keep these around for later cleanup and to ensure $ssh stays in scope + $self->{ssh_parent} = $ssh; + $self->{ssh_pty} = $fh; + $self->{ssh_pid} = $pid; + $self->{ssh_term} = $term; + + return 1 if $term->waitfor(-match => '/SEARCH/', -errmode => "return"); + + $logger->error("FF III never received SSH menu prompt"); + $self->ssh_disconnect; + return; +} + +sub ssh_disconnect { + my $self = shift; + return unless $self->{ssh_parent}; + + $logger->debug("FF III SSH disconnecting"); + + $self->{ssh_pty}->close if $self->{ssh_pty}; + $self->{ssh_term}->close if $self->{ssh_term}; + $self->{ssh_term} = undef; + $self->{ssh_parent} = undef; + + # required to avoid SSH processes + if (my $pid = $self->{ssh_pid}) { # assignment + $logger->debug("FF III SSH waiting on child $pid"); + waitpid($pid, 0); + } +} + +# send command to SSH term and wait for a response +sub send_wait { + my ($self, $send, $wait, $timeout) = @_; + + if ($send) { + $logger->debug("FF III sending SSH command '$send'"); + $self->{ssh_term}->print($send); + } + + my @response; + + if ($wait) { + $logger->debug("FF III SSH waiting for '$wait'..."); + + @response = $self->{ssh_term}->waitfor( + -match => "/$wait/", + -errmode => 'return', + -timeout => $timeout || 10 + ); + + if (@response) { + my $txt = join('', @response); + $txt =~ s/[[:cntrl:]]//mg; + $logger->debug("FF III SSH wait received text: $txt"); + warn "==\n$send ==> \n$txt\n"; + + } else { + $logger->warn( + "FF III SSH timed out waiting for '$wait' :". + $self->{ssh_term}->errmsg); + } + } + + return @response; +} + + +sub get_user { + my ($self, $user_barcode, $user_pass) = @_; + + return $self->SUPER::get_user($user_barcode, $user_pass) + if $self->sip_client; + + # no SIP, use SSH instead.. + $self->ssh_connect or return; + + my $user; + eval { $user = $self->get_user_guts($user_barcode, $user_pass) }; + $logger->error("FF III error getting user $user_barcode : $@"); + + $self->ssh_disconnect; + return $user; +} + +sub get_items_by_record { + my ($self, $record_id) = @_; + $self->ssh_connect or return; + + my @items; + eval { @items = $self->get_items_by_record_guts($record_id) }; + $logger->error("FF III get_items_by_record() died : $@") if $@; + + $self->ssh_disconnect; + return @items ? \@items : []; +} + +# TODO: test with 2011 +sub get_record_by_id { + my ($self, $rec_id) = @_; + + if ($self->z39_client) { + $logger->info("FF III fetching record from Z39: $rec_id"); + chop($rec_id); # z39 does not want the final checksum char. + return { marc => $self->z39_client->get_record_by_id($rec_id) }; + } else { + $logger->info("FF III fetching record from SSH: $rec_id"); + return $self->get_record_by_id_ssh($rec_id); + } +} + +1; diff --git a/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/III/2009B_1_2.pm b/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/III/2009B_1_2.pm new file mode 100644 index 0000000000..671d59638e --- /dev/null +++ b/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/III/2009B_1_2.pm @@ -0,0 +1,1154 @@ +package FulfILLment::LAIConnector::III::2009B_1_2; +use base FulfILLment::LAIConnector::III; +use strict; use warnings; +use OpenSRF::Utils::Logger qw/$logger/; + +use MARC::Record; +use MARC::Batch; +use MARC::File::XML (BinaryEncoding => 'utf8'); +use LWP::UserAgent; +use HTTP::Request; +use WWW::Mechanize; +use XML::Simple; +use Sip; +use FulfILLment::Util::Z3950; +use FulfILLment::Util::SIP2Client; +use Data::Dumper; + +my $ua = LWP::UserAgent->new; +$ua->agent("FulfILLment/1.0"); + +sub get_items_by_record_guts { + my ($self, $record_id) = @_; + my @items; + + $self->send_wait('S', 'prominently') or return; + $self->send_wait('B', 'RECORD NO') or return; + $self->send_wait('R', 'Type Record') or return; + $self->send_wait($record_id, 'Record SUMMARY') or return; + + my ($prematch, $match) = $self->send_wait('S', + 'To see a particular|Copy Type:\w+') or return; + + if ($match =~ /Copy Type/) { + # single-copy bib jumps right to copy details. + + # we need the Copy Type from the match as well, + # so push it back into the main text + $prematch .= $match; + + # TODO: see why parse_item_screen() works fine for + # single copies in 2011_1_3 but not here. there's + # an opportunity for more consolidation w/i the superclass + my @barcodes = ($prematch =~ /BARCODE\s+(\d+)/g); + my @location_code = ($prematch =~ /LOCATION:\s(\w+)\s+/); + $prematch =~ s/\e\[\d+(?>(;\d+)*)[mh]//gi; + $prematch =~ s/\e\[k//gi; + $prematch =~ s/[[:cntrl:]]//g; + my @fstring = ($prematch =~ /TITLE(.*)IMPRINT/); + my @fingerprint = split(/IMPRINT|\s{2,}/,$fstring[0]); + my $item_fields = $self->parse_item_screen($prematch, $barcodes[0]); + + unless ($item_fields and $barcodes[0]) { + $logger->warn( + "FF III unable to parse single-item screen for $record_id"); + return; + } + + my $item = { + fingerprint => $fingerprint[1], + call_number => $item_fields->{call_number}, + due_date => $item_fields->{due_date}, + barcode => $barcodes[0], + holdable => "t", + location_code => $location_code[0], + error => 0, + error_message => '', + item_id => $item_fields->{item_id}, + }; + + push(@items, $item); + + } else { + # multi-copy bib + + my @item_indexes = ($prematch =~ /ITEM\s+(\d+)\s>/g); + + for my $index (@item_indexes) { + my @response = $self->send_wait($index, 'Record SUMMARY') or last; + my $screen = $response[0]; + + my @barcodes = ($screen =~ /BARCODE\s+(\d+)/g); + + $logger->debug("FF III item screen contains ". + length($screen)." characters"); + + my $item; + $logger->debug("FF III parsing item screen for entry $index"); + $item = $self->parse_item_screen($screen, $barcodes[0]); + + unless ($item and $item->{barcode}) { + $logger->warn( + "FF III unable to parse item screen for $record_id"); + last; + } + + push(@items, $item); + + # return to the summary screen + $self->send_wait('S', 'To see a particular') or last; + } + } + + return @items; +} + +sub get_user_guts { + my ($self, $user_barcode, $user_pass) = @_; + + $self->send_wait('S', 'prominently') or return; + $self->send_wait('P', 'RECORDS') or return; + $self->send_wait('B', 'BARCODE') or return; + + my ($txt, $match) = $self->send_wait( + $user_barcode, + 'Record|BARCODE not found' + ) or return; + + if ($match =~ /BARCODE not found/) { + $logger->info("FF III user '$user_barcode' not found"); + return; + } + + $txt =~ s/\[\d+;\d+(;\d+)?[Hm]//g; + $txt =~ s/^\d\d\d\s+.+//g; + $txt =~ s/\x1b//g; + $txt =~ s/\[0m//g; + $txt =~ s/\[0xF\]//g; + $txt =~ s/[[:cntrl:]]//g; + + my $user = { + exp_date => qr/EXP DATE:\s(\d+\-\d+\-\d+)/, + user_id => qr/PIN\s+([A-Za-z0-9]+)INPUT/, + notice_pref => qr/NOTICE PREF:\s(.*)TOT/, + lang_pref => qr/LANG PREF:\s+(\w+)/, + mblock => qr/MBLOCK:\s+([A-Za-z0-9\-])\s+/, + patron_agency => qr/PAT AGENCY:\s+(\d+)\s+/, + overdue_items_count => qr/HLODUES:\s+(\d+)/, + notes => qr/PMESSAGE:\s+([A-Za-z0-9\-])/, + home_ou => qr/HOME LIBR:\s+(\w+)/, + total_renewals => qr/TOT RENWAL:\s+(\d+)/, + email_address => qr/EMAIL ADDR\s+([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,3})/, + claims_returned_count => qr/CL RTRND:\s(\d+)/, + loaned_items_count => qr/CUR CHKOUT:\s(\d+)/, + money_owed => qr/MONEY OWED:\s(\$\d+\.\d+)/, + overdue_penalty => qr/OD PENALTY:\s(\d+)/, + institution_code => qr/HOME LIBR:\s+(\w+)/, + block_until => qr/BLK UNTIL:\s(\d+\-\d+\-\d+)/, + full_name => qr/PATRN NAME\s+(.*)ADDRESS/, + street_address => qr/ADDRESS\s+(.*)SSN/, + ssn => qr/SSN #\s+(\d+)P BARCODE/, + photocopy_count => qr/PIUSE:\s+(\d+)/, + patron_code1 => qr/PCODE1:\s+(\d+)/, + patron_code2 => qr/PCODE2:\s+(\d+)/, + patron_code3 => qr/PCODE3:\s+(\d+)/, + patron_code4 => qr/PCODE4:\s+(\d+)/, + notes => qr/PMESSAGE:\s+([A-Za-z0-9\-])/, + ill_request => qr/ILL REQUES:\s+(\d+)/, + last_circ_date => qr/CIRCACTIV:\s(\d+\-\d+\-\d+)/, + patron_type => qr/P TYPE:\s(\d+)/, + census => qr/CENSUS:\s(\d+)\s+/, + total_checkouts => qr/TOT CHKOUT:\s(\d+)/, + checkouts => qr/CUR CHKOUT:\s+(\d+)/, + phone => qr/TELEPHONE\s+(\d\d\d\-\d\d\d\-\d\d\d\d)/, + input_by => qr/INPUT BY\s+([A-Za-z\/]+)/, + birth_date => qr/BIRTH DAT:(\d\d\-\d\d-\d\d)/, + cur_item_A => qr/CUR ITEMA:\s(\d+)/, + cur_item_B => qr/CUR ITEMB:\s(\d+)/, + cur_item_C => qr/CUR ITEMC:\s(\d+)/, + cur_item_D => qr/CUR ITEMD:\s(\d+)/, + error => 0, + }; + + for my $key (keys %$user) { + my ($val) = ($txt =~ $user->{$key}); + $user->{$key} = $val; + } + + my %prefs = ( + z => 'email', + t => 'phone', + p => 'secondary phone' + ); + + $user->{notice_pref} = $prefs{$user->{notice_pref}}; + $user->{barcode} = $user_barcode; + + return $user; +} + + +sub get_item { + my ($self, $item_barcode) = @_; + + $self->ssh_connect or return; + + $self->send_wait('S', 'prominently') or return; + $self->send_wait('I', 'SEARCHING RECORDS') or return; + $self->send_wait('B', 'BARCODE') or return; # NOTE: 2011 uses 'D' + my ($screen) = $self->send_wait($item_barcode, 'NEW Search') or return; + + $self->ssh_disconnect; + + if ($screen =~ /not found|PATRON Information/g) { + $logger->info("FF III unable to locate item $item_barcode"); + return; + } + + my $item = $self->parse_item_screen($screen, $item_barcode); + + $logger->error("FF III error parsing item screen for $item_barcode") + unless $item; + + return $item; +} + +# --------------------------------------------------------------------------- +# Everything below here needs modification and testing +# --------------------------------------------------------------------------- + +sub get_item_fields{ + my $self=$_[0]; + my $itemData=$_[1]; + #print Dumper $itemData; + $itemData =~ s/\e\[\d+(?>(;\d+)*)[mh]//gi; + $itemData =~ s/\e\[k//gi; + $itemData =~ s/[[:cntrl:]]//g; + $itemData =~ s/VOLUME/VOLUME:/g; + $itemData =~ s/CALL #/CALL #:/g; + $itemData =~ s/R > Browse Nearby EntriesI > Show similar ITEMSN >//g; + $itemData =~ s/R > RETURN to BrowsingZ > Show Items Nearby on ShelfF > FORWARD browseI > Show similar ITEMSN >//g; + $itemData =~ s/U > Show BIBLIOGRAPHIC RecordZ > Show Items Nearby on Shelf//g; + $itemData =~ s/\+ > ADDITIONAL options1-2,N,A,Z,I,U,T,E,\+\).*ITEM Information//g; + $itemData =~ s/I > Show similar ITEMSA > ANOTHER Search by RECORD #//g; + $itemData =~ s/U > Show similar BIBLIOGRAPHIC RecordS > Record SUMMARY//g; + $itemData =~ s/U > Show BIBLIOGRAPHIC RecordS > Record SUMMARY//g; + $itemData =~ s/T > Display MARC RecordE > Mark item for//g; + $itemData =~ s/U > Show BIBLIOGRAPHIC Record//g; + $itemData =~ s/NEW Search//g; + $itemData =~ s/N,A,S,Z,I,U,T,E\)//g; + $itemData =~ s/N >//g; + + if(my @l = ($itemData =~ m/(\d+)BARCODE/g)){ + $itemData =~ s/VOLUME:/VOLUME: $l[0]/g; + } + + + $itemData =~ s/(\d+)BARCODE/BARCODE:/g; + #$itemData =~ s/1BARCODE/BARCODE/g; + my @fields = grep {defined and not /^\s*$/} split /(\s{2,})|(- -)/, $itemData; + my $i=0; + my @newfields = []; + + foreach (@fields){ + #$_.=':' if $_ eq 'VOLUME'; + $_.=':' if $_ eq 'BARCODE'; + + if((/^[ \-]+$/ or $newfields[$#newfields] eq 'BARCODE:') and @newfields){ + $newfields[$#newfields] .=$_; + }else{ + push @newfields, $_; + } + + $i++; + } + + return \@newfields; +} + + + + + + + +sub get_item_call_and_bib_number{ + my $self = $_[0]; + my $item_data = $_[1]; + #print Dumper $item_data; + $item_data =~ s/\e\[\d+(?>(;\d+)*)[mh]//gi; + $item_data =~ s/\e\[k//gi; + $item_data =~ s/[[:cntrl:]]//g; + + #get Call Number + my @c = ($item_data =~ /BIBLIOGRAPHIC Information\s+CALL #\s+(.*?)AUTHOR/); + + if(not $c[0]){ + @c = ($item_data =~ /BIBLIOGRAPHIC Information\s+CALL #\s(.*?)TITLE/); + } + + #get Barcode + my @b = ($item_data =~ /BARCODE:.*(B.*)\s+BIBLIOGRAPHIC Information/); + + if($b[0]){ + $b[0] =~ s/\s//g; + } + + #get title + my @f = split("TITLE",$item_data) ; + my @fingerprint = ($f[0] =~ /\s+(.*)\s{5,}/); + + my @out = ($b[0],$c[0],$fingerprint[0]); + #print Dumper @out; + return \@out; + +} + + + + + +sub parse_item_screen{ + my $self = $_[0]; + my $screen = $_[1]; + my $barcode = $_[2]; + my $jhash={}; + my @nvp; + my $label; + my $value; + my @screen_split; + my $rs = $screen; + my @record_number = ($rs =~ /(I\d.*)\s+ITEM Information/g); + my $e = "ESC[7;2H"; + $e = ord($e); + + $record_number[0] =~ s/\s+//g; + push @screen_split, split(/ITEM Information/,$screen); + #print Dumper @screen_split; + my $fields = $self->get_item_fields($screen_split[1]); + my $call_and_bib = $self->get_item_call_and_bib_number($screen_split[0]); + + unless ($call_and_bib->[1]) { + # in multi-copy records, the call number is embedded in the + # copy data and has no predictable terminating string. + # capture it all and chop it off at the first occurence + # of 2 consecutive spaces + my $scr = $screen_split[1]; + $scr =~ s/[[:cntrl:]]//mg; + my @c = ($scr =~ /CALL #\s+(.*)/); + $c[0] =~ s/(.*?)\s{2}.*/$1/mg if $c[0]; + $call_and_bib->[1] = $c[0] || 'UNKNOWN'; + } + + # remove pesky trailing junk + $call_and_bib->[1] =~ s/^\s+//g; + $call_and_bib->[1] =~ s/\s+$//g; + + $logger->warn("FF III unble to find callnumber") + unless $call_and_bib->[1]; + + #print Dumper $fields; + + foreach(@$fields){ + @nvp=split(":",$_); + $label=$nvp[0]; + $value=$nvp[1]; + if($label eq "DUE DATE"){ + $label="due_date"; + }elsif($label eq "BARCODE"){ + $label="barcode"; + }elsif($label eq "STATUS"){ + $label="holdable"; + }elsif($label eq "LOCATION"){ + $label="location"; + } + + if($label eq "holdable" and $value eq " -"){ + $value="t"; + }elsif($label eq "holdable" and $value eq "e"){ + $value="t"; + }elsif($label eq "holdable" ){ + $value="f"; + } + + $jhash->{$label}=$value; + } + + #print Dumper $call_and_bib; + if($jhash->{due_date}){ + if($jhash->{'due_date'} eq "- -"){ + $jhash->{'due_date'} = ''; + } + } + $jhash->{'call_number'} = $call_and_bib->[1]; + $jhash->{'bib_id'}=$call_and_bib->[0]; + $jhash->{'item_id'} = $record_number[0]; + $jhash->{'barcode'} = $barcode; + $jhash->{'error_message'} = ''; + $jhash->{'fingerprint'} = ''; + return $jhash; +} + + + +sub get_item_by_call_number{ + my $self = $_[0]; + my $ssh = $self->initialize; + my $item_id = $_[1]; + my $call_number_type = $_[2]; + my @out; + my @entries; #If there are multiple entries for an item, the entries are stored here. + #print "preparing to search the catalog\n"; + $ssh->print("S"); + $ssh->waitfor(-match => '/prominently\?/', + -errmode => "return") or die "search failed;", $ssh->lastline; + #print "ok\n"; + #print "selecting option to search for items\n"; + $ssh->print("I"); + $ssh->waitfor(-match => '/SEARCHING RECORDS/', + -errmode => "return")or die "search failed;", $ssh->lastline; + #print "ok\n"; + #select attribute to search by, i.e title, barcode etc... + $ssh->print("C"); + $ssh->waitfor(-match => '/CALL NUMBER SEARCHES/', + -errmode => "return") or die "search failed;", $ssh->lastline; + + if(lc($call_number_type) eq "dewey"){ + + $ssh->print("D"); + $ssh->waitfor(-match => '/DEWEY CALL NO :/', + -errmode => "return") or die "search failed;", $ssh->lastline; + + }elsif(lc($call_number_type) eq "lc"){ + $ssh->print("C"); + $ssh->waitfor(-match => '/LC CALL NO :/', + -errmode => "return") or die "search failed;", $ssh->lastline; + + }elsif(lc($call_number_type) eq "local"){ + $ssh->print("L"); + $ssh->waitfor(-match => '/LOCAL CALL NO :/', + -errmode => "return") or die "search failed;", $ssh->lastline; + } + + $ssh->print($item_id); + push @out,$ssh->waitfor(-match => '/NEW Search/', + -errmode => "return") or die "search failed;", $ssh->lastline; + #print "Done.\n"; + + if(my @num_entries = ($out[0] =~ /(\d+)\sentries found/)){ + #my $i = 0; + #$i++; + $ssh->print(1); + push @out,$ssh->waitfor(-match => '/NEW Search/', + -errmode => "return") or die "search failed;", $ssh->lastline; + #print "Done.\n"; + } + + $self->ssh_disconnect; + my @items; + push @items,$self->parse_item_screen($out[2]); + return \@items; +} + + +#Under construction + +sub get_item_by_bib_number_z3950{ + my $self = $_[0]; + my $bibID = $_[1]; + my $marc = $self->get_bib_records_by_record_number_z3950($bibID); + my $batch = MARC::Batch->new('XML', $marc ); + while (my $m = $batch->next ){ + print $m->subfield(650,"a"),"\n"; + + } + #my $record = $batch->next(); + #print Dumper $record; + +} + + + + + +# Method: get_bib_records_by_record_num +# Params: +# idList => list of record numbers + +#BIBLIOGRAPHIC RECORDS +#Notes: Section contains methods to retrieve and parse bibliographic records +#================================================================================== + + +sub get_bib_records_by_record_number{ + my $self = $_[0]; + my $ssh = $self->initialize; + my $id = $_[1]; + my $json=JSON::XS->new(); + my $count=0; + my @out; + my @screen; + my @marc; + my @bib = (); + my $jhash={}; + + eval{ + if(ref($ssh) eq "ARRAY"){ + return $ssh; + } + + #select SEARCH the catalog + #print "getBibRecords\n"; + #print "preparing to search the catalog\n"; + $ssh->print("S"); + + $ssh->waitfor(-match => '/prominently\?/', + -errmode => "return") or die "Search failed. Could not retrieve ", $id; + + #print "ok\n"; + #print "selecting option to search for bibliographic records\n"; + #select item record option + $ssh->print("B"); + $ssh->waitfor(-match => '/SEARCHING RECORDS/', + -errmode => "return") or die "Search failed. Could not retrieve ", $id; + + #print "ok\n"; + #print "selecting option to search by record number\n"; + #select attribute to search by, i.e title, barcode etc... + $ssh->print("R"); #search by record number + #print "ok\n"; + #print "searching for id=$id\n"; + #search for id + $ssh->print($id); + my $first = 1; + #print "searching more of the document\n"; + my @bid; + + my $get_more = sub { + my @temp_screen = (); + my @lines; + my $last = 0; + my $get_more = shift; + + if($first == 1){ + $ssh->print("T"); + $ssh->waitfor(-match => '/BIBLIOGRAPHIC Information/', + -errmode => "return") or die "Search failed. Could not retrieve ", $id; + #-errmode => "return") or die "search failed;", $ssh->lastline; + $ssh->print("M"); + #$first = 0; + } + + $ssh->print("M"); + $ssh->print("T"); + #delete the following two lines + + push @temp_screen ,$ssh->waitfor(-match => '/Regular Display/', + -errmode => "return") or die "Search failed. Could not retrieve ", $id; + #-errmode => "return") or die "search failed;", $ssh->lastline; + #print Dumper $temp_screen[0]; + + @bid = ($temp_screen[0] =~ /([Bb].*)\s+BIBLIOGRAPHIC Information/); + if($temp_screen[0] =~ /COUNTRY:/g and $first == 0 ){ + #print Dumper $temp_screen[0]; + #print "Reached end of record... I think\n"; + return 1; + } + push @lines,split(/\[\d+;\d+(;\d+)?[Hm]/,$temp_screen[0]); + + foreach (@lines){ + if($_){ + $_ =~ s/^(\s+)//g; + if($_ =~/^\d\d\d\s+.+/g){ + $_ =~ s/\x1b//g; + $_ =~ s/\[0m//g; + $_ =~ s/\[0xF\]//g; + $_ =~ s/[[:cntrl:]]//g; + push @marc,$_; + #print Dumper $_; + } + } + } + #print Dumper @lines; + + $first = 0; + #print Dumper @marc; + $ssh->print("M"); + $ssh->print("T"); + @temp_screen = (); + $get_more->($get_more); + }; + + + $get_more->($get_more); + + my @id = ($bid[0]) ? ($bid[0] =~ /([Bb][0-9]+)\s+/) : "" ; + #print Dumper @marc; + + #print "bid = ".$id[0]."\n"; + #print "ok\n"; + #push @out, $self->parse_bib_records( $self->grab_bib_screen($ssh,$id), $id ); + #print "logging out\n"; + $ssh->print("N"); + $ssh->print("Q"); + $ssh->print("Q"); + $ssh->print("Q"); + $ssh->print("X"); + $ssh->waitfor(-match => '/closed/', + #-errmode => "return")or die "log out failed;", $ssh->lastline; + -errmode => "return") or die "Search failed. Could not retrieve ", $id; + + #print "logged out\n"; + my $rec = breaker2marc(\@marc); + $rec->insert_fields_ordered( + MARC::Field->new( + '907', + ' ', + ' ', + 'a' => $id[0] + ) + ) if (!$rec->subfield( '907' => 'a' )); + + my $x = $rec->as_xml_record; + $x =~ s/^<\?.+?\?>.//sm; + #my $jhash={}; + $jhash->{'marc'}=$x; + #$bid =~ s/\[/$bid/g; + $jhash->{'id'}=$id[0]; + $jhash->{'format'}="marcxml"; + $jhash->{error} = 0; + $jhash->{error_message} = ''; + #print "xml = $x\n"; + #my @bib = (); + push @bib,$jhash; + #warn Dumper \@bib; + return \@bib; + + 1; + }or do { + $jhash->{error} = 1; + $jhash->{error_message} = $@; + push @bib,$jhash; + return \@bib; + } + +} + + + + +sub get_range_of_records{ + my $self = $_[0]; + my $list = $_[1]; + my $dir = `pwd`; + my $file = "/openils/lib/perl5/FulfILLment/WWW/LAIConnector/conf/III_2009B_1_2/marc/marc_dump.mrc"; + open FILE, ">$file" or die &!; + + foreach my $r (@$list){ + my $record = $self->get_bib_records_by_record_number($r); + + print FILE $record->[0]->{marc}; + + } + + close FILE; + my @out; + my $jhash; + push @out,$jhash; + return \@out; + +} + + + + + + + +sub breaker2marc { + my $lines = shift; + my $delim = quotemeta(shift() || '|'); + my $rec = new MARC::Record; + for my $line (@$lines) { + + chomp($line); + + if ($line =~ /^=?(\d{3})\s{2}(.)(.)\s(.+)$/) { + + my ($tag, $i1, $i2, $rest) = ($1, $2, $3, $4); + if ($tag < 10) { + $rec->insert_fields_ordered( MARC::Field->new( $tag => $rest ) ); + + } else { + + my @subfield_data = split $delim, $rest; + if ($subfield_data[0]) { + $subfield_data[0] = 'a' . $subfield_data[0]; + } else { + shift @subfield_data; + } + + my @subfields; + for my $sfd (@subfield_data) { + if ($sfd =~ /^(.)(.+)$/) { + push @subfields, $1, $2; + } + } + + $rec->insert_fields_ordered( + MARC::Field->new( + $tag, + $i1, + $i2, + @subfields + ) + ) if @subfields; + } + } + } + + return $rec; +} + + +#END BIBLIOGRAPHIC RECORDS +#===================================================================================== + + + + + +#Places hold on a III server through the web interface + + + +sub placeHold{ + #use WWW::Curl::Easy; + my $self = shift; + my $host = $self->{host}; + my $port = $self->{port}; + my $name = $self->{login}; + my $user_barcode = $self->{password}; + my $bib_id = shift; + + if(length($bib_id) > 8){ + chop($bib_id); + } + + my $patron_sys_num = shift; + my $url = "http://$host/search~SO?/.$bib_id/.$bib_id/1%2C1%2C1%CB/request~$bib_id?name=$name&code=$user_barcode"; + my @out; + my $response_body; + my $mech = WWW::Mechanize->new(); + my $error_msg; + my $response; + my @radioInputs; + my $content; + my $hold = {}; + + eval{ + $mech->post($url); + $mech->form_name('patform'); + $mech->field('name',$name); + $mech->field('code',$user_barcode); + $response = $mech->submit(); + $mech->submit(); + $content = $mech->content; + }; + + if($@){ + $hold->{error} = 1; + $hold->{error_message} = "check username and password , error : $@"; + push @out,$hold; + return \@out; + } + + my @title = ($content =~ /

Requesting (.+)<\/strong>

/); + my @err_response_msg = ($content =~ /(.+)<\/font>/g); + my @success_response_msg = ($content =~ /Your request for .* was successful./g); + my @delivered_to = ($content =~ /Your request will be delivered to .* when it is available./); + + if($content =~ /No Such Record/){ + $hold->{error} = 1; + $hold->{error_message} = "A hold could not be placed on $bib_id, no such record\n"; + push @out,$hold; + return \@out; + } + + + if($content =~ /Request denied/){ + $err_response_msg[0] =~ s///; + $err_response_msg[0] =~ s/<\/strong>//; + $hold->{error} = 1; + $hold->{error_message} = $err_response_msg[0]; + $hold->{title} = $title[0]; + push @out,$hold; + return \@out; + }elsif($content =~ /Your request for .* was successful./g){ + $success_response_msg[0] =~ s///; + $success_response_msg[0] =~ s/<\/strong>//; + $hold->{error} = 0; + $hold->{success_message} = $success_response_msg[0]; + $hold->{title} = $title[0]; + push @out,$hold; + return \@out; + } + + @title = ($content =~ /class="bibInfoLabel">Title<\/td>\n\n(.*)<\/strong>/g); + + if($title[0]){ + $hold->{error} = 0; + $hold->{success_message} = "Your request for $title[0] was successful."; + $hold->{title} = $title[0]; + push @out,$hold; + return \@out; + } +} + + + + +sub parse_hold_response{ + my $self = $_[0]; + my $txt = $_[1]; + my @resp = split("END SEARCH WIDGET -->",$txt); + my $success = ($resp[1] =~ /denied/) ? "false" : "true"; + if(!defined($resp[1])){$success = "false"} + my $msg = $resp[1]; + $msg =~ s/

//g; + $msg =~ s/<\/p>//g; + $msg =~ s/
//g; + $msg =~ s/ //g; + $msg =~ s///g; + $msg =~ s/<\/body>//g; + $msg =~ s/<\/html>//g; + $msg =~ s/<\/strong\>//g; + $msg =~ s///g; + $msg =~ s/\./\. /g; + $msg =~ s/\n//g; + $msg =~ s//\. /g; + $msg =~ s/<\/font>//g; + my $data = {}; + $data->{'success'} = $success; + $data->{'message'} = $msg; + my $out = []; + $out->[0] = $data; + return $out; +} + + + +sub list_holds{ + my $self = $_[0]; + my $base_url = $self->{host}; + $base_url = "https://".$base_url; + my $port = $self->{port}; + my $username = $_[1]; + my $user_barcode = $_[2]; + my $patron_sys_num = $_[3]; + my $action = $_[4] || "list_holds"; + my $response_body; + my $request_parameters; + $request_parameters = "name=$username&code=$user_barcode"; + my $get_string = "/patroninfo~SO/$patron_sys_num/holds"; + my $url = $base_url.$get_string."?".$request_parameters; + my $out; + my $mech = WWW::Mechanize->new(); + $mech->post($url); + + + if($mech->success){ + $out = $self->parse_hold_list($mech->content,$action); + }else{ + $out = [{error => 1, error_message => 'There was a problem with the request' }]; + } + + if( not defined($out->[0]) ){ + $out = [{error => 0, error_message => 'No holds were found for this user' }]; + } + + #print Dumper $out; + return $out; + +} + + + +sub list_holds_by_bib{ + my $self = shift; + my $host = shift; + my $port = shift; + my $login = shift; + my $user_barcode = shift; + my $bibID = shift; + my $patron_sys_num = shift; + my $holds = $self->list_holds($host,$port,$login,$user_barcode,$patron_sys_num); + my @out; + + foreach(@$holds){ + if($_->{'bibid'} eq $bibID){ + push @out,$_; + } + } + + return \@out; +} + + + +sub list_holds_by_item{ + my $self = shift; + my $host = shift; + my $port = shift; + my $login = shift; + my $user_barcode = shift; + my $itemID = shift; + my $patron_sys_num = shift; + my $holds = $self->list_holds($host,$port,$login,$user_barcode,$patron_sys_num); + my @out; + + foreach(@$holds){ + if($_->{'itemid'} eq $itemID){ + push @out,$_; + } + } + + return \@out; +} + + + + + + + +sub delete_hold{ + + my $self = shift; + my $host = $self->{host}; + my $port = $self->{port}; + my $userid = $self->{login}; + $userid =~ s/\s/%20/g; + my $user_barcode = $self->{password}; + my $search_id = shift; + my $id_type = shift; + my $patron_sys_num = shift; + my $response_body; + my $holds = $self->list_holds($userid,$user_barcode,$patron_sys_num); + my $itemid; + my $linkid; + my $num_holds_before = @$holds; + my $num_holds_after; + + if($id_type eq "bib"){ + + if(length($search_id) > 8){ + chop($search_id); + } + + foreach(@$holds){ + #print "bibid = $_->{bibid} search_id = $search_id\n"; + if($_->{'bibid'} eq $search_id){ + #print "item id = ".$_->{'itemid'}."\n"; + $itemid = $_->{'itemid'}; + $linkid = $_->{'linkid'}; + } + } + }elsif($id_type eq "item"){ + foreach(@$holds){ + if($_->{'itemid'} eq $search_id){ + #print "item id = ".$_->{'itemid'}."\n"; + $itemid = $_->{'itemid'}; + $linkid = $_->{'linkid'}; + } + } + } + + $itemid = (defined($itemid)) ? $itemid : ""; + $linkid = (defined($linkid)) ? $linkid : ""; + my $url = "http://$host/patroninfo~SO/$patron_sys_num/holds?name=$userid&code=$user_barcode&$linkid=on¤tsortorder=current_pickup&updateholdssome=YES&loc$itemid="; + my @out; + my $msg = {}; + my $mech = WWW::Mechanize->new(); + $response_body = $mech->post($url); + + if($mech->success){ + my $nha = $self->list_holds($userid,$user_barcode,$patron_sys_num); + $num_holds_after = @$nha; + #check to see whether the user was successfully authenticated + my $auth = $self->loggedIn($response_body); + + if($auth ne "t"){ + $msg->{"error"} = 1; + $msg->{"error_message"} = "The user $userid could not be authenticated"; + push @out, $msg; + return \@out; + } + + if($num_holds_before == $num_holds_after){ + $msg->{"error"} = 1; + $msg->{"error_message"} = "The hold for $search_id either does not exist or could not be deleted"; + push @out, $msg; + return \@out; + }elsif($num_holds_before > $num_holds_after){ + $msg->{"error"} = 0; + $msg->{"success_message"} = "The hold for $search_id has been deleted"; + push @out, $msg; + return \@out; + } + + }else{ + $msg->{"error"} = 1; + $msg->{"error_message"} = "An error occured: code $mech->status"; + push @out, $msg; + return \@out; + } +} + + + + +sub parse_hold_list{ + my $self = $_[0]; + my $in = $_[1]; + my $action = $_[2]; + $in =~ s/\n//g; + my @holds; + my @holdEntries = split(/()/ ,$in); + my $user = {}; + shift @holdEntries; + pop @holdEntries; + my @userSurname = ($in =~ /

Patron Record for<\/h4>(\w+),.*<\/strong>
/); + my @userGivenName = ($in =~ /

Patron Record for<\/h4>(\w+),.*<\/strong>
/); + my @expDate = ($in =~ /EXP DATE:(\d\d\-\d\d\-\d\d\d\d)
/); + + + $user->{surname} = $userSurname[0]; + $user->{given_name} = $userGivenName[0]; + $user->{exp_date} = $expDate[0]; + + if($action eq "lookup_user"){ + push @holds,$user; + }elsif($action eq "list_holds"){ + + foreach(@holdEntries){ + my $hold = {}; + my @bibid = ($_ =~ /record=(.*)~/); + my @pickup = ($_ =~ /"patFuncPickup">(\w+)<\/td>/); + my @title = ($_ =~ /record=.*>\s(.*)<\/a>/); + my @status = ($_ =~ /"patFuncStatus">\s(\w+)\s<\/td>/); + my @itemid = ($_ =~ /id="cancel(\w+)x/); + my @linkid = ($_ =~ /id="(cancel.+x\d+)"\s+\/>/); + + $hold->{'bibid'} = $bibid[0] if(defined $bibid[0]); + $hold->{'pickup'} = $pickup[0] if(defined $pickup[0]); + $hold->{'title'} = $title[0] if(defined $title[0]); + $hold->{title} =~ s/<.+>.*<\/.+>//g if(defined $hold->{title}); + $hold->{'status'} = $status[0] if(defined $status[0]); + $hold->{'itemid'} = $itemid[0] if(defined $status[0]); + $hold->{'linkid'} = $linkid[0] if(defined $status[0]); + push @holds, $hold if (defined $hold->{'bibid'}); + } + + if($user->{surname}){ + $user->{error} = 0; + $user->{error_meessage} = ''; + }else{ + $user->{error} = 1; + $user->{error_message} = 'supplied user could not be looked up'; + } + } + + return \@holds; +} + + +sub checkout{ + my $self = $_[0]; + my $user_barcode = $_[1]; + my $ssh = $self->initialize; + my @temp_screen; + my @screenData; + my @lines; + my $circ; + + eval{ + $ssh->print("S"); + $ssh->waitfor(-match => '/prominently\?/', + -errmode => "return") or die "Search failed. Could not retrieve user ", $user_barcode; + $ssh->print("P"); + $ssh->waitfor(-match => '/RECORDS/', + -errmode => "return") or die "Search failed. Could not retrieve user ", $user_barcode; + + $ssh->print("D"); + $ssh->waitfor(-match => '/BARCODE/', + -errmode => "return") or die "Search failed. Could not retrieve user ", $user_barcode; + + $ssh->print($user_barcode); + + }or do { + $circ = {error => 1, error_message => $@}; + + }; + + +} + + + +sub checkin{ + my $self = $_[0]; + my $user_barcode = $_[1]; + my $ssh = $self->initialize; + my @temp_screen; + my @screenData; + my @lines; + my $circ; + + eval{ + $ssh->print("S"); + + + }or do{ + + + } + +} + + + + + + + + + + + + + + + + + + + + +sub loggedIn{ + my $self = $_[0]; + my $response = $_[1]; + if($response =~ /Please enter the following information/g){ + return "f"; + } + + return "t"; +} + + + + + +1; + + diff --git a/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/III/2011_1_3.pm b/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/III/2011_1_3.pm new file mode 100644 index 0000000000..e0cbdfdf88 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/III/2011_1_3.pm @@ -0,0 +1,1156 @@ +package FulfILLment::LAIConnector::III::2011_1_3; +use base FulfILLment::LAIConnector::III; +use strict; use warnings; +use OpenSRF::Utils::Logger qw/$logger/; + +use MARC::Record; +use MARC::Batch; +use MARC::File::XML (BinaryEncoding => 'utf8'); +use LWP::UserAgent; +use HTTP::Request; +use WWW::Mechanize; +use XML::Simple; +use Data::Dumper; + +my $ua = LWP::UserAgent->new; +$ua->agent("FulfILLment/1.0"); + +sub get_record_by_id_ssh { + my ($self, $record_id) = @_; + + $self->ssh_connect or return; + $self->send_wait('S', 'Choose one') or return; + $self->send_wait('B', 'Choose one') or return; + $self->send_wait('R', 'Type Record') or return; + $self->send_wait("$record_id", 'Choose one') or return; + $self->send_wait('T', 'BIBLIOGRAPHIC Information') or return; + $self->send_wait('M'); + my ($pre, $post) = $self->send_wait('T', 'Regular Display') or return; + $self->ssh_disconnect or return; + + my @lines = split(/\[\d+;\d+(;\d+)?[Hm]/, $pre); + + my @marc; + foreach (@lines){ + next unless $_; + s/^(\s+)//g; + next unless /^\d{3}/; + s/\x1b//g; + s/\[0m//g; + s/\[0xF\]//g; + s/[[:cntrl:]]//g; + push @marc,$_; + } + + my $rec = breaker2marc(\@marc); + $rec->insert_fields_ordered( + MARC::Field->new('907', ' ', ' ', a => $record_id) + ) unless $rec->subfield('907' => 'a'); + + my $x = $rec->as_xml_record; + $x =~ s/^<\?.+?\?>.//sm; + return {marc => $x}; +} + +sub get_items_by_record_guts { + my ($self, $record_id) = @_; + my @items; + + $self->send_wait('S', 'prominently') or return; + $self->send_wait('B', 'SEARCHING RECORDS') or return; + $self->send_wait('R', 'RECORD') or return; + $self->send_wait($record_id, 'Choose one') or return; + + my ($prematch, $match) = $self->send_wait( + 'S', 'To see a particular|BARCODE\s*[^\s]+') or return; + + if ($match =~ /BARCODE/) { + # single-copy bib jumps right to copy details. + + + # we need the Copy Type from the match as well, + # so push it back into the main text + $prematch .= $match; + my $item = $self->parse_item_screen($prematch); + + unless ($item and $item->{barcode}) { + $logger->warn( + "FF III unable to parse single-item screen for $record_id"); + return; + } + + push(@items, $item); + + } else { + # multi-copy bib + + my @item_indexes = ($prematch =~ /ITEM\s+(\d+)\s>/g); + + for my $index (@item_indexes) { + my @response = $self->send_wait($index, 'Record SUMMARY') or last; + my $screen = $response[0]; + + $logger->debug("FF III item screen contains ". + length($screen)." characters"); + + my $item; + $logger->debug("FF III parsing item screen for entry $index"); + $item = $self->parse_item_screen($screen); + + unless ($item and $item->{barcode}) { + $logger->warn( + "FF III unable to parse item screen for $record_id"); + last; + } + + push(@items, $item); + + # return to the summary screen + $self->send_wait('S', 'To see a particular') or last; + } + } + + return @items; +} + +sub get_user_guts { + my ($self, $user_barcode, $user_pass) = @_; + + $self->send_wait('S', 'prominently') or return; + $self->send_wait('P', 'RECORDS') or return; + $self->send_wait('D', 'BARCODE') or return; # 2009 uses 'B' + + my ($txt, $match) = $self->send_wait( + $user_barcode, + 'Record|BARCODE not found' + ) or return; + + if ($match =~ /BARCODE not found/) { + $logger->info("FF III user '$user_barcode' not found"); + return; + } + + $txt =~ s/\[\d+;\d+(;\d+)?[Hm]//g; + $txt =~ s/^\d\d\d\s+.+//g; + $txt =~ s/\x1b//g; + $txt =~ s/\[0m//g; + $txt =~ s/\[0xF\]//g; + $txt =~ s/[[:cntrl:]]//g; + + my $user = { + exp_date => qr/EXP DATE:\s(\d+\-\d+\-\d+)/, + user_id => qr/PIN\s+([A-Za-z0-9]+)INPUT/, + notice_pref => qr/NOTICE PREF:\s(.*)TOT/, + lang_pref => qr/LANG PREF:\s+(\w+)/, + mblock => qr/MBLOCK:\s+([A-Za-z0-9\-])\s+/, + patron_agency => qr/PAT AGENCY:\s+(\d+)\s+/, + overdue_items_count => qr/HLODUES:\s+(\d+)/, + notes => qr/PMESSAGE:\s+([A-Za-z0-9\-])/, + home_ou => qr/HOME LIBR:\s+(\w+)/, + total_renewals => qr/TOT RENWAL:\s+(\d+)/, + email_address => qr/EMAIL ADDR\s+([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,3})/, + claims_returned_count => qr/CL RTRND:\s(\d+)/, + loaned_items_count => qr/CUR CHKOUT:\s(\d+)/, + money_owed => qr/MONEY OWED:\s(\$\d+\.\d+)/, + overdue_penalty => qr/OD PENALTY:\s(\d+)/, + institution_code => qr/HOME LIBR:\s+(\w+)/, + block_until => qr/BLK UNTIL:\s(\d+\-\d+\-\d+)/, + full_name => qr/PATRN NAME\s+(.*)ADDRESS/, + street_address => qr/ADDRESS\s+(.*)SSN/, + ssn => qr/SSN #\s+(\d+)P BARCODE/, + photocopy_count => qr/PIUSE:\s+(\d+)/, + patron_code1 => qr/PCODE1:\s+(\d+)/, + patron_code2 => qr/PCODE2:\s+(\d+)/, + patron_code3 => qr/PCODE3:\s+(\d+)/, + patron_code4 => qr/PCODE4:\s+(\d+)/, + notes => qr/PMESSAGE:\s+([A-Za-z0-9\-])/, + ill_request => qr/ILL REQUES:\s+(\d+)/, + last_circ_date => qr/CIRCACTIV:\s(\d+\-\d+\-\d+)/, + patron_type => qr/P TYPE:\s(\d+)/, + census => qr/CENSUS:\s(\d+)\s+/, + total_checkouts => qr/TOT CHKOUT:\s(\d+)/, + checkouts => qr/CUR CHKOUT:\s+(\d+)/, + phone => qr/TELEPHONE\s+(\d\d\d\-\d\d\d\-\d\d\d\d)/, + input_by => qr/INPUT BY\s+([A-Za-z\/]+)/, + birth_date => qr/BIRTH DAT:(\d\d\-\d\d-\d\d)/, + cur_item_A => qr/CUR ITEMA:\s(\d+)/, + cur_item_B => qr/CUR ITEMB:\s(\d+)/, + cur_item_C => qr/CUR ITEMC:\s(\d+)/, + cur_item_D => qr/CUR ITEMD:\s(\d+)/, + error => 0, + }; + + for my $key (keys %$user) { + my ($val) = ($txt =~ $user->{$key}); + $user->{$key} = $val; + } + + my %prefs = ( + z => 'email', + t => 'phone', + p => 'secondary phone' + ); + + $user->{notice_pref} = $prefs{$user->{notice_pref}}; + $user->{barcode} = $user_barcode; + + return $user; +} + +# not needed if SIP is used. +sub get_item_via_ssh { + my ($self, $item_barcode) = @_; + + $self->ssh_connect or return; + + $self->send_wait('S', 'prominently') or return; + $self->send_wait('I', 'SEARCHING RECORDS') or return; + $self->send_wait('D', 'BARCODE') or return; # NOTE: 2009 uses 'B' + my ($screen) = $self->send_wait($item_barcode, 'NEW Search') or return; + + $self->ssh_disconnect; + + if ($screen =~ /not found|PATRON Information/g) { + $logger->info("FF III unable to locate item $item_barcode"); + return; + } + + my $item = $self->parse_item_screen($screen, $item_barcode); + + $logger->error("FF III error parsing item screen for $item_barcode") + unless $item; + + return $item; +} + + +sub place_record_hold { + my ($self, $record_id, $user_barcode) = @_; + + my $host = $self->{host}; + my $password = $self->{'passwd.hold'} || $self->{passwd}; + + # $self->{port} usually == 80; need a separate SSL port config + my $port = 443; + + # XXX do we need an org setting for default lender hold pickup location? + # TODO: web form pads the pickup lib values. add padding via sprintf to match. + my $pickup_lib = 'pla '; + + chop($record_id); # strip check digit + $record_id = substr($record_id, 1); # strip the initial '.' + + my $url = "https://$host/search~S1?/.$record_id/". + ".$record_id/1%2C1%2C1%2CB/request~$record_id"; + + $logger->info("FF III title hold URL: $url"); + + my $mech = WWW::Mechanize->new(); + my $content; + + eval { + $mech->post($url); + $mech->form_name('patform'); + $mech->set_fields('pin', $password, 'code', $user_barcode); + $mech->select('locx00', $pickup_lib); + my $response = $mech->submit(); + $content = $mech->content if $response; + }; + + $logger->info($content); # XXX + + if ($@ or !$content){ + my $msg = $@ || 'no content'; + $logger->info("FF III error placing title hold on $record_id : $msg"); + return {error => 1, error_message => $@}; + } + + my @title = ($content =~ /

Requesting (.+)<\/strong>

/); + my @err_response_msg = ($content =~ /(.+)<\/font>/g); + my @success_response_msg = ($content =~ /Your request for .* was successful./g); + my @delivered_to = ($content =~ /Your request will be delivered to .* when it is available./); + + if($content =~ /No Such Record/){ + $logger->info("FF III no such record $record_id in title hold"); + return { + error => 1, + error_message => # TODO: i18n? + "A hold could not be placed on $record_id, no such record" + } + } + + if ($content =~ /Request denied/){ + $logger->info("FF III title hold for $record_id denied"); + $err_response_msg[0] =~ s///; + $err_response_msg[0] =~ s/<\/strong>//; + + return { + error => 1, + error_message => $err_response_msg[0], + title => $title[0] + }; + + } elsif ($content =~ /Your request for .* was successful./g){ + $success_response_msg[0] =~ s///; + $success_response_msg[0] =~ s/<\/strong>//; + + return { + error => 0, + success_message => $success_response_msg[0], + title => $title[0] + }; + } + + @title = ($content =~ + /class="bibInfoLabel">Title<\/td>\n\n(.*)<\/strong>/g); + + if ($title[0]){ + return { + error => 0, + success_message => "Your request for $title[0] was successful.", + title => $title[0] + }; + } +} + + +# --------------------------------------------------------------------------- +# Everything below here needs modification and testing +# --------------------------------------------------------------------------- + + +sub get_item_fields{ + my $self=$_[0]; + my $itemData=$_[1]; + return [] unless $itemData; + $logger->debug("FF III get_item_fields parsing " .length($itemData)." characters"); + #print Dumper $itemData; + $itemData =~ s/\e\[\d+(?>(;\d+)*)[mh]//gi; + $itemData =~ s/\e\[k//gi; + $itemData =~ s/[[:cntrl:]]//g; + $itemData =~ s/VOLUME/VOLUME:/g; + $itemData =~ s/CALL #/CALL #:/g; + $itemData =~ s/R > Browse Nearby EntriesI > Show similar ITEMSN >//g; + $itemData =~ s/R > RETURN to BrowsingZ > Show Items Nearby on ShelfF > FORWARD browseI > Show similar ITEMSN >//g; + $itemData =~ s/U > Show BIBLIOGRAPHIC RecordZ > Show Items Nearby on Shelf//g; + $itemData =~ s/\+ > ADDITIONAL options1-2,N,A,Z,I,U,T,E,\+\).*ITEM Information//g; + $itemData =~ s/I > Show similar ITEMSA > ANOTHER Search by RECORD #//g; + $itemData =~ s/U > Show similar BIBLIOGRAPHIC RecordS > Record SUMMARY//g; + $itemData =~ s/U > Show BIBLIOGRAPHIC RecordS > Record SUMMARY//g; + $itemData =~ s/T > Display MARC RecordE > Mark item for//g; + $itemData =~ s/U > Show BIBLIOGRAPHIC Record//g; + $itemData =~ s/NEW Search//g; + $itemData =~ s/N,A,S,Z,I,U,T,E\)//g; + $itemData =~ s/N >//g; + + if(my @l = ($itemData =~ m/(\d+)BARCODE/g)){ + $itemData =~ s/VOLUME:/VOLUME: $l[0]/g; + } + + $itemData =~ s/(\d+)BARCODE/BARCODE:/g; + #$itemData =~ s/1BARCODE/BARCODE/g; + my @fields = grep {defined and not /^\s*$/} split /(\s{2,})|(- -)/, $itemData; + #my $i=0; + my @newfields = []; + + foreach (@fields){ + #$_.=':' if $_ eq 'VOLUME'; + $_.=':' if $_ eq 'BARCODE'; + + if((/^[ \-]+$/ or $newfields[$#newfields] eq 'BARCODE:') and @newfields){ + $newfields[$#newfields] .=$_; + }else{ + unless(ref($_) eq 'ARRAY'){ + push @newfields, $_; + } + } + + #$i++; + } + + return \@newfields; +} + + + + + + + +sub get_item_call_and_bib_number { + $logger->debug("In get_item_call_and_bib_number"); + my $self = $_[0]; + my $item_data = $_[1]; + $item_data =~ s/\e\[\d+(?>(;\d+)*)[mh]//gi; + $item_data =~ s/\e\[k//gi; + $item_data =~ s/[[:cntrl:]]//g; + + my @c = ($item_data =~ /BIBLIOGRAPHIC Information\s+CALL #\s+(.*?)AUTHOR/); + + if(not defined($c[0])){ + @c = ($item_data =~ /BIBLIOGRAPHIC Information\s+CALL #\s(.*?)TITLE/); + } + + if (!$c[0]) { + # in multi-copy records, the call number is embedded in the + # copy data and has no predictable terminating string. + # capture it all and chop it off at the first occurence + # of 2 consecutive spaces + + @c = ($item_data =~ /CALL #\s+(.*)/); + if ($c[0]) { + $c[0] =~ s/(.*?)\s{2}.*/$1/mg; + $c[0] =~ s/\s+$//; + } else { + $logger->warn("FF III unable to parse callnumber"); + $c[0] = 'UNKNOWN'; + } + } + + #get Barcode + my @b = ($item_data =~ /BARCODE:.*(B.*)\s+BIBLIOGRAPHIC Information/); + + if($b[0]){ + $b[0] =~ s/\s//g; + }else{ + @b = ($item_data =~ /BARCODE\s+([^\s]+)/); + } + + #get title + my @f = split("TITLE",$item_data) ; + my @fingerprint = ($f[0] =~ /\s+(.*)\s{5,}/); + my @out; + + if(not defined $fingerprint[0]){ + @fingerprint = ($item_data =~ /TITLE\s+(.*)\s{5,}/); + } + + @out = ($b[0],$c[0],$fingerprint[0]); + $logger->debug("Exiting get_item_call_and_bib_number"); + return \@out; +} + + + + + +sub parse_item_screen{ + $logger->debug("In parse_item_screen"); + my $self = $_[0]; + my $screen = $_[1]; + my $jhash={}; + my @nvp; + my $label; + my $value; + my @screen_split; + my $rs = $screen; + my @record_number = ($rs =~ /(I\d.*)\s+ITEM Information/g); + my $e = "ESC[7;2H"; + $e = ord($e); + $record_number[0] =~ s/\s+//g; + push @screen_split, split(/ITEM Information/,$screen); + #print Dumper @screen_split; + my $fields = $self->get_item_fields($screen_split[1]); + + + my $call_and_bib = $self->get_item_call_and_bib_number($screen_split[1]); + + my $barcode = (defined($_[2])) ? $_[2] : $call_and_bib->[0]; + my @fingerprint = ($screen =~ /TITLE\s+([^.!?\s][^.!?]*)\s+/); + $logger->debug("setting fields to their FulfILLment equivalents"); + + foreach(@$fields){ + @nvp=split(":",$_); + $label=$nvp[0]; + $value=$nvp[1]; + if($label eq "DUE DATE"){ + $label="due_date"; + }elsif($label eq "BARCODE"){ + $label="barcode"; + }elsif($label eq "STATUS"){ + $label="holdable"; + }elsif($label eq "LOCATION"){ + $label="location"; + }elsif($label eq "PRICE"){ + $label="price"; + }elsif($label eq "COPY #"){ + $label = "copy_number"; + }elsif($label eq "I TYPE"){ + $label = "item_type"; + }elsif($label eq "IMESSAGE"){ + $label = "note"; + }elsif($label eq "AGENCY"){ + $label = "agency"; + }elsif($label eq "IN LOC"){ + $label = "in_location"; + }elsif($label eq "LOU"){ + $label = "last_checkout_date"; + }elsif($label eq "ICODE1"){ + $label = "item_code1"; + }elsif($label eq "ICODE2"){ + $label = "item_code2"; + }elsif($label eq "ICODE3"){ + $label = "item_code3"; + }elsif($label eq "IUSE1"){ + $label = "item_use1"; + }elsif($label eq "IUSE2"){ + $label = "item_use2"; + }elsif($label eq "IUSE3"){ + $label = "item_use3"; + }elsif($label eq "OPACMSG"){ + $label = "opac_msg"; + }elsif($label eq "OUT LOC"){ + $label = "out_location"; + }elsif($label eq "# RENEWALS"){ + $label = "num_renewals"; + }elsif($label eq "# OVERDUE"){ + $label = "num_overdue"; + }elsif($label eq "LOANRULE"){ + $label = "loanrule"; + }elsif($label eq "LYRCIRC"){ + $label = "last_year_circ_stats"; + } + + if($label eq "holdable" and $value eq " -"){ + $value="t"; + }elsif($label eq "holdable" and $value eq "e"){ + $value="t"; + }elsif($label eq "holdable" ){ + $value="f"; + } + + $jhash->{$label}=$value; + } + + #print Dumper $call_and_bib; + if($jhash->{due_date}){ + if($jhash->{'due_date'} eq "- -"){ + $jhash->{'due_date'} = ''; + } + } + + # avoid leading/trailing spaces in cn + $call_and_bib->[1] =~ s/^\s+//g; + $call_and_bib->[1] =~ s/\s+$//g; + + $jhash->{'call_number'} = $call_and_bib->[1]; + #$jhash->{'bib_id'}=$call_and_bib->[0]; + $jhash->{'item_id'} = $record_number[0]; + $jhash->{'barcode'} = $barcode; + $jhash->{'error_message'} = ''; + #$jhash->{'fingerprint'} = $fingerprint[0]; + $jhash->{'fingerprint'} = ''; + #print Dumper $jhash; + $logger->debug("Exiting parse_item_screen"); + #print Dumper $jhash; + return $jhash; +} + + +sub get_item_by_call_number{ + my $self = $_[0]; + my $ssh = $self->initialize; + my $item_id = $_[1]; + my $call_number_type = $_[2]; + my @out; + my @entries; #If there are multiple entries for an item, the entries are stored here. + #print "preparing to search the catalog\n"; + $ssh->print("S"); + $ssh->waitfor(-match => '/prominently\?/', + -errmode => "return") or die "search failed;", $ssh->lastline; + #print "ok\n"; + #print "selecting option to search for items\n"; + $ssh->print("I"); + $ssh->waitfor(-match => '/SEARCHING RECORDS/', + -errmode => "return")or die "search failed;", $ssh->lastline; + #print "ok\n"; + #select attribute to search by, i.e title, barcode etc... + $ssh->print("C"); + $ssh->waitfor(-match => '/CALL NUMBER SEARCHES/', + -errmode => "return") or die "search failed;", $ssh->lastline; + + if(lc($call_number_type) eq "dewey"){ + + $ssh->print("D"); + $ssh->waitfor(-match => '/DEWEY CALL NO :/', + -errmode => "return") or die "search failed;", $ssh->lastline; + + }elsif(lc($call_number_type) eq "lc"){ + $ssh->print("C"); + $ssh->waitfor(-match => '/LC CALL NO :/', + -errmode => "return") or die "search failed;", $ssh->lastline; + + }elsif(lc($call_number_type) eq "local"){ + $ssh->print("L"); + $ssh->waitfor(-match => '/LOCAL CALL NO :/', + -errmode => "return") or die "search failed;", $ssh->lastline; + } + + $ssh->print($item_id); + push @out,$ssh->waitfor(-match => '/NEW Search/', + -errmode => "return") or die "search failed;", $ssh->lastline; + #print "Done.\n"; + + if(my @num_entries = ($out[0] =~ /(\d+)\sentries found/)){ + #my $i = 0; + #$i++; + $ssh->print(1); + push @out,$ssh->waitfor(-match => '/NEW Search/', + -errmode => "return") or die "search failed;", $ssh->lastline; + #print "Done.\n"; + } + + $self->ssh_disconnect; + my @items; + push @items,$self->parse_item_screen($out[2]); + return \@items; +} + + +#Under construction + +sub get_item_by_bib_number_z3950{ + my $self = $_[0]; + my $bibID = $_[1]; + my $marc = $self->get_bib_records_by_record_number_z3950($bibID); + my $batch = MARC::Batch->new('XML', $marc ); + while (my $m = $batch->next ){ + print $m->subfield(650,"a"),"\n"; + + } + #my $record = $batch->next(); + #print Dumper $record; + +} + + + + + +# Method: get_bib_records_by_record_num +# Params: +# idList => list of record numbers + +#BIBLIOGRAPHIC RECORDS +#Notes: Section contains methods to retrieve and parse bibliographic records +#================================================================================== + + +sub get_bib_records_by_record_number{ + my $self = $_[0]; + my $ssh = $self->initialize; + my $id = $_[1]; + my $count=0; + my @out; + my @screen; + my @marc; + my @bib = (); + my $jhash={}; + + eval{ + if(ref($ssh) eq "ARRAY"){ + return $ssh; + } + + #select SEARCH the catalog + #print "getBibRecords\n"; + #print "preparing to search the catalog\n"; + $ssh->print("S"); + + $ssh->waitfor(-match => '/prominently\?/', + -errmode => "return") or die "Search failed. Could not retrieve ", $id; + + #print "ok\n"; + #print "selecting option to search for bibliographic records\n"; + #select item record option + $ssh->print("B"); + $ssh->waitfor(-match => '/SEARCHING RECORDS/', + -errmode => "return") or die "Search failed. Could not retrieve ", $id; + + #print "ok\n"; + #print "selecting option to search by record number\n"; + #select attribute to search by, i.e title, barcode etc... + $ssh->print("R"); #search by record number + #print "ok\n"; + #print "searching for id=$id\n"; + #search for id + $ssh->print($id); + my $first = 1; + #print "searching more of the document\n"; + my @bid; + + my $get_more = sub { + my @temp_screen = (); + my @lines; + my $last = 0; + my $get_more = shift; + + if($first == 1){ + $ssh->print("T"); + $ssh->waitfor(-match => '/BIBLIOGRAPHIC Information/', + -errmode => "return") or die "Search failed. Could not retrieve ", $id; + #-errmode => "return") or die "search failed;", $ssh->lastline; + $ssh->print("M"); + #$first = 0; + } + + $ssh->print("M"); + $ssh->print("T"); + #delete the following two lines + + push @temp_screen ,$ssh->waitfor(-match => '/Regular Display/', + -errmode => "return") or die "Search failed. Could not retrieve ", $id; + #-errmode => "return") or die "search failed;", $ssh->lastline; + #print Dumper $temp_screen[0]; + + @bid = ($temp_screen[0] =~ /([Bb].*)\s+BIBLIOGRAPHIC Information/); + if($temp_screen[0] =~ /COUNTRY:/g and $first == 0 ){ + #print Dumper $temp_screen[0]; + #print "Reached end of record... I think\n"; + return 1; + } + push @lines,split(/\[\d+;\d+(;\d+)?[Hm]/,$temp_screen[0]); + + foreach (@lines){ + if($_){ + $_ =~ s/^(\s+)//g; + if($_ =~/^\d\d\d\s+.+/g){ + $_ =~ s/\x1b//g; + $_ =~ s/\[0m//g; + $_ =~ s/\[0xF\]//g; + $_ =~ s/[[:cntrl:]]//g; + push @marc,$_; + #print Dumper $_; + } + } + } + #print Dumper @lines; + + $first = 0; + #print Dumper @marc; + $ssh->print("M"); + $ssh->print("T"); + @temp_screen = (); + $get_more->($get_more); + }; + + + $get_more->($get_more); + + my @id = ($bid[0]) ? ($bid[0] =~ /([Bb][0-9]+)\s+/) : "" ; + #print Dumper @marc; + + #print "bid = ".$id[0]."\n"; + #print "ok\n"; + #push @out, $self->parse_bib_records( $self->grab_bib_screen($ssh,$id), $id ); + #print "logging out\n"; + $ssh->print("N"); + $ssh->print("Q"); + $ssh->print("Q"); + $ssh->print("X"); + $ssh->waitfor(-match => '/closed/', + #-errmode => "return")or die "log out failed;", $ssh->lastline; + -errmode => "return") or die "Search failed. Could not retrieve ", $id; + + #print "logged out\n"; + my $rec = breaker2marc(\@marc); + $rec->insert_fields_ordered( + MARC::Field->new( + '907', + ' ', + ' ', + 'a' => $id[0] + ) + ) if (!$rec->subfield( '907' => 'a' )); + + my $x = $rec->as_xml_record; + $x =~ s/^<\?.+?\?>.//sm; + #my $jhash={}; + $jhash->{'marc'}=$x; + #$bid =~ s/\[/$bid/g; + $jhash->{'id'}=$id[0]; + $jhash->{'format'}="marcxml"; + $jhash->{error} = 0; + $jhash->{error_message} = ''; + #print "xml = $x\n"; + #my @bib = (); + push @bib,$jhash; + #warn Dumper \@bib; + return \@bib; + + 1; + }or do { + $jhash->{error} = 1; + $jhash->{error_message} = $@; + push @bib,$jhash; + return \@bib; + } + +} + + + + +sub get_range_of_records{ + my $self = $_[0]; + my $list = $_[1]; + my $dir = `pwd`; + my $file = "/openils/lib/perl5/FulfILLment/WWW/LAIConnector/conf/III_2009B_1_2/marc/marc_dump.mrc"; + open FILE, ">$file" or die &!; + + foreach my $r (@$list){ + my $record = $self->get_bib_records_by_record_number($r); + + print FILE $record->[0]->{marc}; + + } + + close FILE; + my @out; + my $jhash; + push @out,$jhash; + return \@out; + +} + + + + + + + +sub breaker2marc { + my $lines = shift; + my $delim = quotemeta(shift() || '|'); + my $rec = new MARC::Record; + for my $line (@$lines) { + + chomp($line); + + if ($line =~ /^=?(\d{3})\s{2}(.)(.)\s(.+)$/) { + + my ($tag, $i1, $i2, $rest) = ($1, $2, $3, $4); + if ($tag < 10) { + $rec->insert_fields_ordered( MARC::Field->new( $tag => $rest ) ); + + } else { + + my @subfield_data = split $delim, $rest; + if ($subfield_data[0]) { + $subfield_data[0] = 'a' . $subfield_data[0]; + } else { + shift @subfield_data; + } + + my @subfields; + for my $sfd (@subfield_data) { + if ($sfd =~ /^(.)(.+)$/) { + push @subfields, $1, $2; + } + } + + $rec->insert_fields_ordered( + MARC::Field->new( + $tag, + $i1, + $i2, + @subfields + ) + ) if @subfields; + } + } + } + + return $rec; +} + + +#END BIBLIOGRAPHIC RECORDS +#===================================================================================== + + + + + +#Places hold on a III server through the web interface + + + +sub parse_hold_response{ + my $self = $_[0]; + my $txt = $_[1]; + my @resp = split("END SEARCH WIDGET -->",$txt); + my $success = ($resp[1] =~ /denied/) ? "false" : "true"; + if(!defined($resp[1])){$success = "false"} + my $msg = $resp[1]; + $msg =~ s/

//g; + $msg =~ s/<\/p>//g; + $msg =~ s/
//g; + $msg =~ s/ //g; + $msg =~ s///g; + $msg =~ s/<\/body>//g; + $msg =~ s/<\/html>//g; + $msg =~ s/<\/strong\>//g; + $msg =~ s///g; + $msg =~ s/\./\. /g; + $msg =~ s/\n//g; + $msg =~ s//\. /g; + $msg =~ s/<\/font>//g; + my $data = {}; + $data->{'success'} = $success; + $data->{'message'} = $msg; + my $out = []; + $out->[0] = $data; + return $out; +} + + + +sub list_holds{ + $logger->debug("In list_holds"); + my $self = shift; + my $host = $self->{host}; + my $port = $self->{port}; + my $user_barcode = shift; + my $passwd = shift; + my $patron_sys_num = shift; + my $action = shift || "list_holds"; + my $response; + my $content; + $logger->debug("params are host=$host\n port=$port\n user_barcode=$user_barcode\n passwd=$passwd\n patron_sys_number=$patron_sys_num"); + my $url = "https://$host:$port/patroninfo~S0/$patron_sys_num/holds/?name=$user_barcode&code=$passwd"; + my $out; + my $mech = WWW::Mechanize->new(); + + eval{ + $mech->post($url); + $mech->form_name('patform'); + $mech->set_fields('pin',$passwd, + 'code',$user_barcode + + ); + + $response = $mech->submit(); + $content = $mech->content; + }; + + if($@){ + my $hold; + my @out; + $hold->{error} = 1; + $hold->{error_message} = "There was an error looking up holds for the user $user_barcode : $@"; + push @out,$hold; + $logger->debug("Exiting list_holds"); + return \@out; + } + + + $logger->debug("Exiting list_holds"); + return $self->parse_hold_list($content,"list_holds"); + +} + + + +sub list_holds_by_bib{ + my $self = shift; + my $host = shift; + my $port = shift; + my $login = shift; + my $user_barcode = shift; + my $bibID = shift; + my $patron_sys_num = shift; + my $holds = $self->list_holds($host,$port,$login,$user_barcode,$patron_sys_num); + my @out; + + foreach(@$holds){ + if($_->{'bibid'} eq $bibID){ + push @out,$_; + } + } + + return \@out; +} + + + +sub list_holds_by_item{ + my $self = shift; + my $host = shift; + my $port = shift; + my $login = shift; + my $user_barcode = shift; + my $itemID = shift; + my $patron_sys_num = shift; + my $holds = $self->list_holds($host,$port,$login,$user_barcode,$patron_sys_num); + my @out; + + foreach(@$holds){ + if($_->{'itemid'} eq $itemID){ + push @out,$_; + } + } + + return \@out; +} + + +sub delete_hold{ + $logger->debug("In delete_hold"); + my $self = shift; + my $host = $self->{host}; + my $port = $self->{port}; + my $userid = $self->{login}; + $userid =~ s/\s/%20/g; + my $search_id = shift; + my $id_type = shift; + my $patron_sys_num = shift; + my $user_barcode = shift; + + $logger->debug("fields are host=$host\n port=$port \n userid=$userid \n search_id=$search_id \n id_type=$id_type \n patron_sys_num=$patron_sys_num \n user_barcode=$user_barcode\n"); + + my $response_body; + my $holds = $self->list_holds($userid,$user_barcode,$patron_sys_num); + my $itemid; + my $linkid; + my $num_holds_before = @$holds; + my $num_holds_after; + + if($id_type eq "bib"){ + + if(length($search_id) > 8){ + chop($search_id); + } + + foreach(@$holds){ + #print "bibid = $_->{bibid} search_id = $search_id\n"; + if($_->{'bibid'} eq $search_id){ + #print "item id = ".$_->{'itemid'}."\n"; + $itemid = $_->{'itemid'}; + $linkid = $_->{'linkid'}; + } + } + }elsif($id_type eq "item"){ + foreach(@$holds){ + if($_->{'itemid'} eq $search_id){ + #print "item id = ".$_->{'itemid'}."\n"; + $itemid = $_->{'itemid'}; + $linkid = $_->{'linkid'}; + } + } + } + + $itemid = (defined($itemid)) ? $itemid : ""; + $linkid = (defined($linkid)) ? $linkid : ""; + my $url = "https://$host:$port/patroninfo~SO/$patron_sys_num/holds?name=$userid&code=$user_barcode&$linkid=on¤tsortorder=current_pickup&updateholdssome=YES&loc$itemid="; + my @out; + my $msg = {}; + my $mech = WWW::Mechanize->new(); + $response_body = $mech->post($url); + + if($mech->success){ + my $nha = $self->list_holds($userid,$user_barcode,$patron_sys_num); + $num_holds_after = @$nha; + #check to see whether the user was successfully authenticated + my $auth = $self->loggedIn($response_body); + + if($auth ne "t"){ + $msg->{"error"} = 1; + $msg->{"error_message"} = "The user $userid could not be authenticated"; + push @out, $msg; + return \@out; + } + + if($num_holds_before == $num_holds_after){ + $msg->{"error"} = 1; + $msg->{"error_message"} = "The hold for $search_id either does not exist or could not be deleted"; + push @out, $msg; + return \@out; + }elsif($num_holds_before > $num_holds_after){ + $msg->{"error"} = 0; + $msg->{"success_message"} = "The hold for $search_id has been deleted"; + push @out, $msg; + return \@out; + } + + }else{ + $msg->{"error"} = 1; + $msg->{"error_message"} = "An error occured: code $mech->status"; + push @out, $msg; + return \@out; + } +} + + + + +sub parse_hold_list{ + my $self = shift; + my $in = shift; + my $action = shift; + $in =~ s/\n//g; + my @holds; + my @holdEntries = split(/()/ ,$in); + my $user = {}; + shift @holdEntries; + pop @holdEntries; + my @userSurname = ($in =~ /

Patron Record for<\/h4>(\w+),.*<\/strong>
/); + my @userGivenName = ($in =~ /

Patron Record for<\/h4>(\w+),.*<\/strong>
/); + my @expDate = ($in =~ /EXP DATE:(\d\d\-\d\d\-\d\d\d\d)
/); + + + $user->{surname} = $userSurname[0]; + $user->{given_name} = $userGivenName[0]; + $user->{exp_date} = $expDate[0]; + + if($action eq "lookup_user"){ + push @holds,$user; + }elsif($action eq "list_holds"){ + + foreach(@holdEntries){ + my $hold = {}; + my @bibid = ($_ =~ /record=(.*)~/); + my @pickup = ($_ =~ /"patFuncPickup">(\w+)<\/td>/); + my @title = ($_ =~ /record=.*>\s(.*)<\/a>/); + my @status = ($_ =~ /"patFuncStatus">\s(\w+)\s<\/td>/); + my @itemid = ($_ =~ /id="cancel(\w+)x/); + my @linkid = ($_ =~ /id="(cancel.+x\d+)"\s+\/>/); + + $hold->{'bibid'} = $bibid[0] if(defined $bibid[0]); + $hold->{'pickup'} = $pickup[0] if(defined $pickup[0]); + $hold->{'title'} = $title[0] if(defined $title[0]); + $hold->{title} =~ s/<.+>.*<\/.+>//g if(defined $hold->{title}); + $hold->{'status'} = $status[0] if(defined $status[0]); + $hold->{'itemid'} = $itemid[0] if(defined $status[0]); + $hold->{'linkid'} = $linkid[0] if(defined $status[0]); + push @holds, $hold if (defined $hold->{'bibid'}); + } + + if($user->{surname}){ + $user->{error} = 0; + $user->{error_meessage} = ''; + }else{ + $user->{error} = 1; + $user->{error_message} = 'supplied user could not be looked up'; + } + } + + return \@holds; +} + + +sub loggedIn{ + my $self = $_[0]; + my $response = $_[1]; + if($response =~ /Please enter the following information/g){ + return "f"; + } + + return "t"; +} + + + + + +1; diff --git a/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Koha.pm b/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Koha.pm new file mode 100644 index 0000000000..76e7282e8c --- /dev/null +++ b/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Koha.pm @@ -0,0 +1,254 @@ +package FulfILLment::LAIConnector::Koha; +use base FulfILLment::LAIConnector; +use strict; use warnings; +use XML::LibXML; +use LWP::UserAgent; +use OpenSRF::Utils::Logger qw/$logger/; + +# TODO: for holds +use DateTime; +my $U = 'OpenILS::Application::AppUtils'; +use OpenILS::Utils::CStoreEditor qw/:funcs/; + +# special thanks to Koha => misc/migration_tools/koha-svc.pl +sub svc_login { + my $self = shift; + return $self->{svc_agent} if $self->{svc_agent}; + + my $username = $self->{extra}->{'svc.user'} || $self->{user}; + my $password = $self->{extra}->{'svc.password'} || $self->{passwd}; + + # TODO: https (setting) + my $url = sprintf( + "http://%s/cgi-bin/koha/svc", + $self->{extra}->{'svc.host'} || $self->{host} + ); + + my $ua = LWP::UserAgent->new(); + $ua->cookie_jar({}); + + $logger->info("FF Koha logging in at $url/authentication"); + + my $resp = $ua->post( + "$url/authentication", + {userid => $username, password => $password} + ); + + if (!$resp->is_success) { + $logger->error("FF Koha svc login failed " . $resp->status_line); + return; + } + + $self->{svc_url} = $url; + $self->{svc_agent} = $ua; + + return 1; +} + +sub escape_xml { + my $str = shift; + $str =~ s/&/&/sog; + $str =~ s//>/sog; + return $str; +} + +# sends a MARCXML stub record w/ a single embedded copy +sub create_borrower_copy { + my ($self, $ref_copy, $circ_lib_code) = @_; + return unless $self->svc_login; + + my $marc = < + + AUTHOR + + + TITLE + + + BARCODE + CALLNUMBER + LOCATION + + +XML + + my $title = escape_xml($ref_copy->call_number->record->simple_record->title); + my $author = escape_xml($ref_copy->call_number->record->simple_record->author); + my $barcode = escape_xml($ref_copy->barcode); # TODO: setting for leading org id + my $callnumber = escape_xml($ref_copy->call_number->label); + + $marc =~ s/TITLE/$title/g; + $marc =~ s/AUTHOR/$author/g; + $marc =~ s/BARCODE/$barcode/g; + $marc =~ s/CALLNUMBER/$callnumber/g; + $marc =~ s/LOCATION/$circ_lib_code/g; + + $logger->info("FF Koha borrower rec/copy: $marc"); + + my $resp = $self->{svc_agent}->post( + $self->{svc_url} . "/new_bib?items=1", + {POSTDATA => $marc} + # note: passing Content => $marc fails + ); + + if (!$resp->is_success) { + $logger->error("FF Koha create_borrower_copy " . $resp->status_line); + return; + } + + $logger->info($resp->decoded_content); + + my $resp_xml = XML::LibXML->new->parse_string($resp->decoded_content); + $logger->info($resp_xml); + $logger->info($resp_xml->toString); + + my $error = $resp_xml->getElementsByTagName('error')->string_value; + my $marcxml = $resp_xml->getElementsByTagName('record')->shift; + + return { + error => $error, + barcode => $error ? '' : $barcode, # return bc on success + title => $title, + author => $author, + location => $circ_lib_code, + call_number => $callnumber, + remote_id => $resp_xml->getElementsByTagName('biblionumber')->string_value, + status => $resp_xml->getElementsByTagName('status')->string_value, + marcxml => $marcxml ? $marcxml->toString : '' + }; +} + +sub get_record_by_id { + my ($self, $record_id, $with_items) = @_; + return unless $self->svc_login; + + $with_items = '?items=1' if $with_items; + + my $url = $self->{svc_url}."/bib/$record_id$with_items"; + my $resp = $self->{svc_agent}->get($url); + + if (!$resp->is_success) { + $logger->error("FF Koha record_by_id failed " . $resp->status_line); + return; + } + + return $resp->decoded_content +} + +# NOTE: unused, but kept for reference +sub get_record_by_id_z3950 { + my ($self, $record_id) = @_; + + my $attr = $self->{args}{extra}{'z3950.search_attr'}; + + # Koha returns holdings by default, which is useful + # for get_items_by_record (below). + + my $xml = $self->z39_client->get_record_by_id( + $record_id, $attr, undef, 'xml', 1) or return; + + return {marc => $xml, id => $record_id}; +} + +sub get_items_by_record { + my ($self, $record_id) = @_; + + my $rec = $self->get_record_by_id($record_id, 1) or return []; + + # when calling get_record_by_id_z3950 + # my $doc = XML::LibXML->new->parse_string($rec->{marc}) or return []; + + my $doc = XML::LibXML->new->parse_string($rec) or return []; + + # marc code to copy field map + my %map = ( + o => 'call_number', + p => 'barcode', + a => 'location_code' + ); + + my @items; + for my $node ($doc->findnodes('//*[@tag="952"]')) { + + my $item = {bib_id => $record_id}; + + for my $key (keys %map) { + my $val = $node->findnodes("./*[\@code='$key']")->string_value; + next unless $val; + $val =~ s/^\s+|\s+$//g; # cleanup + $item->{$map{$key}} = $val; + } + + push (@items, $item); + } + + return \@items; +} + +# NOTE: initial code review suggests Koha only supports bib-level +# holds via SIP, but they are created via copy barcode (not bib id). +# Needs more research + +sub place_borrower_hold { + my ($self, $item_barcode, $user_barcode, $pickup_lib) = @_; + + # NOTE: i believe koha ignores (but requires) the hold type + my $hold = $self->place_hold_via_sip( + undef, $item_barcode, $user_barcode, $pickup_lib, 3) + or return; + + $hold->{hold_type} = 'T'; + return $hold; +} + +sub place_lender_hold { + my ($self, $item_barcode, $user_barcode, $pickup_lib) = @_; + + # NOTE: i believe koha ignores (but requires) the hold type + my $hold = $self->place_hold_via_sip( + undef, $item_barcode, $user_barcode, $pickup_lib, 2) + or return; + + $hold->{hold_type} = 'T'; + return $hold; +} + +sub delete_borrower_hold { + my ($self, $item_barcode, $user_barcode) = @_; + + # TODO: find the hold in the FF db to determine the pickup_lib + # for now, assume pickup lib matches the user's home lib + my $user = $self->flesh_user($user_barcode); + my $pickup_lib = $user->home_ou->shortname if $user; + + my $resp = $self->sip_client->delete_hold( + $user_barcode, undef, undef, + $pickup_lib, 3, $item_barcode) + or return; + + return unless $resp; + return $self->translate_sip_hold($resp); +} + +sub delete_lender_hold { + my ($self, $item_barcode, $user_barcode) = @_; + + my $user = $self->flesh_user($user_barcode); + my $pickup_lib = $user->home_ou->shortname if $user; + + my $resp = $self->sip_client->delete_hold( + $user_barcode, undef, undef, + $pickup_lib, 2, $item_barcode) + or return; + + return unless $resp; + return $self->translate_sip_hold($resp); +} + + +1; diff --git a/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Polaris.pm b/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Polaris.pm new file mode 100644 index 0000000000..c740554388 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Polaris.pm @@ -0,0 +1,6 @@ +package FulfILLment::LAIConnector::Polaris; +use base FulfILLment::LAIConnector; +use strict; use warnings; +use OpenSRF::Utils::Logger qw/$logger/; + +1; diff --git a/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Symphony.pm b/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Symphony.pm new file mode 100644 index 0000000000..ba0debe963 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/FulfILLment/LAIConnector/Symphony.pm @@ -0,0 +1,6 @@ +package FulfILLment::LAIConnector::Symphony; +use base FulfILLment::LAIConnector; +use strict; use warnings; +use OpenSRF::Utils::Logger qw/$logger/; + +1; diff --git a/Open-ILS/src/perlmods/lib/FulfILLment/Util/NCIP.pm b/Open-ILS/src/perlmods/lib/FulfILLment/Util/NCIP.pm new file mode 100644 index 0000000000..a9c8b45639 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/FulfILLment/Util/NCIP.pm @@ -0,0 +1,151 @@ +package FulfILLment::Util::NCIP; +use strict; +use warnings; +use IO::Socket; +use Data::Dumper; +use XML::LibXML; +use LWP::UserAgent; +use HTTP::Request; +use Template; +use OpenSRF::Utils::Logger qw/$logger/; + +my $ua = LWP::UserAgent->new; +$ua->agent("FulfILLment/1.0"); +$ua->default_header('Content-Type' => 'application/xml; charset="utf-8"'); + +sub new { + my ($class, %args) = @_; + return bless(\%args, $class); +} + +sub request { + my ($self, $type, %params) = @_; + + $logger->info("FF NCIP sending message $type"); + + my $xml = $self->compile_xml($type, %params); + return unless $xml; + + my $proto = $self->{protocol} || ''; + my $resp_xml; + if ($proto =~ /http/i) { + $resp_xml = $self->send_via_http($xml); + } elsif ($proto =~ /tcp/i) { + $resp_xml = $self->send_via_tcp($xml); + } else { + $logger->error("FF Invalid NCIP protocol '$proto'"); + return; + } + + my $doc = $self->parse_xml($resp_xml) or return; + return ($doc, $self->extract_ncip_errors($doc)); +} + +# parses/verifies XML and returns an XML doc +sub parse_xml { + my ($self, $xml) = @_; + + my $parser = XML::LibXML->new; + $parser->keep_blanks(0); + + my $doc; + eval { $doc = $parser->parse_string($xml) }; + + if (!$doc) { + $logger->error("FF invalid XML for NCIP message $@ : $xml"); + return; + } + + my $log_xml = $doc->toString; + $log_xml =~ s/\n/ /g; + $logger->debug("FF NCIP XML : $log_xml"); + + return $doc; +} + + +# extract all //Problem/* text values +sub extract_ncip_errors { + my ($self, $doc) = @_; + my @errors; + my $prob_xpath = '//Problem//Value'; + push(@errors, $_->textContent) for $doc->findnodes($prob_xpath); + return @errors; +} + +# sends the xml template for the requested message type +# through TT to generate the final XML message. +sub compile_xml { + my ($self, $type, %params) = @_; + + # insert the agency info into the template environment + $params{ff_agency_name} = $self->{ff_agency_name}; + $params{ff_agency_uri} = $self->{ff_agency_uri}; + $params{ils_agency_name} = $self->{ils_agency_name}; + $params{ils_agency_uri} = $self->{ils_agency_uri}; + + my $template = "$type.tt2"; + + my $tt = Template->new({ + ENCODING => 'utf-8', + INCLUDE_PATH => $self->{template_paths} + }); + + my $xml = ''; + if ($tt->process($template, \%params, \$xml)) { + + my $doc = $self->parse_xml($xml) or return; + return $doc->toString; + + } else { + $logger->error("FF NCIP XML template error : ".$tt->error); + return; + } +} + +sub send_via_http { + my ($self, $xml) = @_; + + my $url = sprintf( + '%s://%s:%s%s', + $self->{protocol}, + $self->{host}, + $self->{port}, + $self->{path} + ); + + $logger->debug("FF NCIP url = $url"); + + my $r = HTTP::Request->new('POST', $url); + $r->content($xml); + my $resp = $ua->request($r); + + return $resp->decoded_content if $resp->is_success; + + $logger->error("FF NCIP HTTP(S) Error : " . $resp->status_line); + return; +} + +sub send_via_tcp { + my ($self, $xml) = @_; + + my $sock = IO::Socket::INET->new( + PeerAddr => $self->{host}, + PeerPort => $self->{port}, + Proto => 'tcp', + Timeout => 10, + ); + + if (!$sock) { + $logger->error("FF NCIP TCP connection error $!"); + return; + } + + $sock->send($xml); + my $resp_xml = <$sock>; + + $sock->close or $logger->warn("FF error closing socket $!"); + return $resp_xml; +} + +1; diff --git a/Open-ILS/src/perlmods/lib/FulfILLment/Util/SIP2Client.pm b/Open-ILS/src/perlmods/lib/FulfILLment/Util/SIP2Client.pm new file mode 100644 index 0000000000..d7f68a1cc4 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/FulfILLment/Util/SIP2Client.pm @@ -0,0 +1,459 @@ +# +#=============================================================================== +# +# FILE: SIP2client.pm +# +# DESCRIPTION: +# +# FILES: --- +# BUGS: --- +# NOTES: --- +# AUTHOR: Michael Davadrian Smith (), msmith@esilibrary.com +# COMPANY: Equinox Software +# VERSION: 1.0 +# CREATED: 05/14/2012 03:27:10 PM +# REVISION: --- +#=============================================================================== + +package FulfILLment::Util::SIP2Client; + +use strict; +use warnings; +use Data::Dumper; +use IO::Socket::INET; +use Encode; +use Sip qw(:all); +use Sip::Checksum qw(checksum); +use Sip::Constants qw(:all); +use Sip::MsgType; +use OpenSRF::Utils::Logger qw/:logger/; + +$Sip::protocol_version = 2; +$Sip::error_detection = 1; +$/ = "\r"; + +sub new { + my ($type) = $_[0]; + my $self = {}; + $self->{host} = $_[1]; + $self->{login_username} = $_[2]; + $self->{login_passwd} = $_[3]; + $self->{port} = $_[4]; + $self->{protocol} = $_[5]; + $self->{location} = $_[6]; + bless($self,$type); +} + +sub socket{ + my $self = shift; + + if ($self->{socket}) { + # logout may cause disconnect + return $self->{socket} if $self->{socket}->connected; + + # probably excessive, but can't hurt to clean up + $self->{socket}->shutdown(2); + $self->{socket}->close; + } + + $logger->debug("FF creating SIP socket to ".$self->{host}); + + $self->{socket} = IO::Socket::INET->new( + PeerAddr => $self->{host}, + Proto => $self->{protocol}, + PeerPort => $self->{port} + ) or die "Cannot connect to host $self->{host} $@"; + + return $self->{socket}; +} + +sub sendMsg{ + my $self = $_[0]; + my $msg = $_[1]; + my $seqno = $_[2] || 1; + my $resp; + my %fields; + my $sock = $self->socket; + $sock->autoflush(1); + $logger->info("FF SIP request => $msg"); + write_msg({seqno => $seqno},$msg,$sock); + $resp = <$sock>; + # SIP Msg hates leading spaces especially + $resp =~ s/^\s+|\s+$//mg; + $logger->info("FF SIP response => $resp"); + return $resp; +} + +sub login{ + my $self = $_[0]; + return $self->{login_resp} if ($self->{logged_in}); + my $userid = $self->{login_username}; + my $userpasswd = $self->{login_passwd}; + my $locationCode = $self->{location}; + + # some SIP servers do not require login + return $self->{logged_in} = 1 unless $userid and $userpasswd; + + my $msg = "93 CN$userid|CO$userpasswd|"; + $self->{login_resp} = $self->sendMsg($msg); + my $u = Sip::MsgType->new($self->{login_resp},0); + my ($ok) = @{$u->{fixed_fields}}; + $self->{logged_in} = ($ok eq 'Y'); + return $ok; + +} + +sub logout{ + my $self = $_[0]; + $self->{login_resp} = $self->{logged_in} = undef; + if ($self->{socket}) { + $self->{socket}->shutdown(2); + $self->{socket}->close; + $self->{socket} = undef; + } +} + +# args: +# patron_id +# patron_pass - optional +# start_index - optional +# end_index - optional +sub lookup_user { + my $self = shift; + my $args = shift; + + $args->{enable_summary_pos} = 0 # backwards compat + unless $args->{enable_summary_pos}; + + my $msg = '63001'; # message 63, language english (001) + $msg .= Sip::timestamp(); + + # summary field is 10 slots, all spaces, one Y allowed. + $msg .= $args->{enable_summary_pos} == $_ ? 'Y' : ' ' for (0..9); + + $msg .= add_field(FID_INST_ID, $self->{location}); + $msg .= add_field(FID_PATRON_ID, $args->{patron_id}); + + # optional fields + $msg .= maybe_add(FID_TERMINAL_PWD, $self->{login_passwd}); + $msg .= maybe_add(FID_PATRON_PWD, $args->{patron_pass}); + $msg .= maybe_add(FID_START_ITEM, $args->{start_index}); + $msg .= maybe_add(FID_END_ITEM, $args->{end_index}); + + $self->login; + + my $resp = $self->sendMsg($msg, 5); + my $u = Sip::MsgType->new($resp, 0, 1); + my $out = $self->lookup_user_handler($u); + + $self->logout; + return $out; +} + + +# deprecated, start using lookup_user() instead +sub lookupUser{ + my $self = $_[0]; + my $terminalPwd = $self->{login_passwd}; + my $location = $self->{location}; #institution id + my $patronId = $_[1]; + my $patronPasswd = $_[2]; + my $start_item = $_[3] || 1; + my $end_item = $_[4] || 10; + my $msg = '63001'; #sets message id to 63 and the language to english, 001 + $msg .= Sip::timestamp()."Y"; + $msg .= ' ' x 9; #Adds an empty 10 spaces for the summary field + $msg .= add_field(FID_INST_ID,$location); + $msg .= add_field(FID_PATRON_ID,$patronId); + $msg .= maybe_add(FID_TERMINAL_PWD,$terminalPwd); + $msg .= maybe_add(FID_PATRON_PWD,$patronPasswd); + $msg .= maybe_add(FID_START_ITEM,$start_item); + $msg .= maybe_add(FID_END_ITEM,$end_item); + $self->login; + my $resp = $self->sendMsg($msg,5); + #$self->logout; + my $u = Sip::MsgType->new($resp,0); + my $out = $self->lookup_user_handler($u); + $self->logout; + return $out; +} + +sub lookup_user_handler{ + my $self = shift; + my $data = shift; + + if (!$data) { + $logger->error("FF SIP lookup_user returned no response"); + return; + } + + my $fields = $data->{fields}; + my $fixed_fields = $data->{fixed_fields}; + my @wholename = split(',',$fields->{AE}); + my $surname = $wholename[0]; + my $given_name = $wholename[1]; + my $valid_patron = $fields->{BL}; + my $error = 0; + my $error_message = ""; + $fields->{AF} ||= ''; + + if($valid_patron eq "N" || $fields->{AF} eq "User not found"){ + $error = 1; + $error_message = "User is not valid"; + } + + my $out = { + #user_id => $fields->{AA}, + patron_status => $fixed_fields->[0], + langauge => $fixed_fields->[1], + transaction_date => $fixed_fields->[2], + hold_items_count => $fixed_fields->[3], + overdue_items_count => $data->{fixed_fields}->[4], + charged_items_count => $data->{fixed_fields}->[5], + fine_items_count => $data->{fixed_fields}->[6], + recall_items_count => $data->{fixed_fields}->[7], + unavailable_holds_count => $data->{fixed_fields}->[8], + institution_id => $fields->{AO}, + patron_identifier => $fields->{AA}, + personal_name => $fields->{AE}, + holds_items_limit => $fields->{BZ}, + overdue_items_limit => $fields->{CA}, + charged_items_limit => $fields->{CB}, + valid_patron => $fields->{BL}, + valid_patron_password => $fields->{CQ}, + currency_type => $fields->{BH}, + fee_amount => $fields->{BV}, + fee_limit => $fields->{CC}, + hold_items => $fields->{AS}, + overdue_items => $fields->{AT}, + charged_items => $fields->{AU}, + fine_items => $fields->{AV}, + recall_items => $fields->{BU}, + unavailable_hold_items => $fields->{CD}, + home_address => $fields->{BD}, + email_address => $fields->{BE}, + home_phone_number => $fields->{BF}, + screen_message => $fields->{AF}, + print_line => $fields->{AG}, + }; + + return $out; +} + +sub checkout{ + my $self = $_[0]; + my $terminalPwd = $self->{login_passwd}; + my $location = $self->{location}; #institution id + my $patron_id = $_[1]; + my $patron_passwd =$_[2]; + my $item_id = $_[3]; + my $fee_ack = $_[4] || "N"; #value should be Y or N + my $cancel = $_[5] || "N"; #value should be Y or N + my $due_date_epoch = $_[6]; + my $msg = '11NN'; #sets message id to 11, no blocking, no autorenew + $msg .= Sip::timestamp(); + $msg .= Sip::timestamp($due_date_epoch); + $msg .= maybe_add(FID_INST_ID,$location); + $msg .= add_field(FID_PATRON_ID,$patron_id); + $msg .= add_field(FID_ITEM_ID,$item_id); + $msg .= add_field(FID_TERMINAL_PWD,$terminalPwd); + $msg .= maybe_add(FID_PATRON_PWD,$patron_passwd); + $msg .= maybe_add(FID_FEE_ACK,$fee_ack); + $msg .= maybe_add(FID_CANCEL,$cancel); + $self->login; + my $resp = $self->sendMsg($msg,5); + my $co = Sip::MsgType->new($resp,0); + $self->logout; + return $co; + +} + +sub checkin{ + my $self = $_[0]; + my $terminalPwd = $self->{login_passwd}; + my $location = $self->{location}; #institution id + my $patron_id = $_[1]; + my $patron_passwd =$_[2]; + my $item_id = $_[3]; + my $item_properties = $_[4]; + my $cancel = $_[5] || "N"; #value should be Y or N + my $fee_ack = $_[6] || "N"; #value should be Y or N + my $msg = '09N'; #sets message id to 09, no blocking + $msg .= Sip::timestamp(); + $msg .= Sip::timestamp(); + $msg .= add_field(FID_CURRENT_LOCN,$location); + $msg .= add_field(FID_INST_ID,$location); + $msg .= add_field(FID_PATRON_ID,$patron_id); + $msg .= add_field(FID_ITEM_ID,$item_id); + $msg .= add_field(FID_TERMINAL_PWD,$terminalPwd); + $msg .= add_field(FID_ITEM_PROPS,$item_properties); + $msg .= add_field(FID_CANCEL,$cancel); + $msg .= add_field(FID_FEE_ACK,$fee_ack); + $self->login; + my $resp = $self->sendMsg($msg,5); + my $ci = Sip::MsgType->new($resp,0); + $self->logout; + return $ci; + +} + +# translates a SIP checkout or checkin response to a human-friendlier hash +sub sip_msg_to_circ { + my ($self, $msg, $type) = @_; + $type |= ''; + + my $fields = $msg->{fields}; + my $fixed_fields = $msg->{fixed_fields}; + + $logger->debug("FF mapping SIP to circ for " . Dumper($msg)); + + my $circ = { + error => !$fixed_fields->[0], + magnetic_media => $fixed_fields->[2], # Y N U + transaction_date => $fixed_fields->[4], + institution_id => $fields->{AO}, + patron_id => $fields->{AA}, + item_id => $fields->{AB}, + due_date => $fields->{AH}, + fee_type => $fields->{BT}, + security_inhibit => $fields->{BI}, + currency_type => $fields->{BH}, + fee_amount => $fields->{BV}, + media_type => $fields->{BV}, + item_properties => $fields->{CH}, + transaction_id => $fields->{BK}, + screen_message => $fields->{AF}, + print_line => $fields->{AG}, + permanent_location => $fields->{AQ} + #title => + #call_number => + #price => + }; + + if ($type eq 'checkout') { + $circ->{renewal_ok} = $fixed_fields->[1]; + $circ->{desensitize} = $fixed_fields->[3]; + } else { + $circ->{resensitize} = $fixed_fields->[1]; + $circ->{alert} = $fixed_fields->[3]; + } + + return $circ; +} + + + +sub item_information_request{ + my ($self,$item_id) = @_; + $Sip::protocol_version = 2; + my $msg = "17".Sip::timestamp(); + $msg .= add_field(FID_INST_ID,$self->{location}); + $msg .= add_field(FID_ITEM_ID,$item_id); + $msg .= add_field(FID_TERMINAL_PWD,$self->{login_passwd}); + $self->login; + my $resp = $self->sendMsg($msg,6); + my $u = Sip::MsgType->new($resp,0); + $self->logout; + return unless $u; + my $out = $self->item_information_request_handler($u); + return $out; +} + +sub item_information_request_handler{ + my $self = $_[0]; + my $response = $_[1]; + my $fixed_fields = $response->{fixed_fields}; + my $fields = $response->{fields}; + + my $out = { + circulation_status => $fixed_fields->[0], + security_marker => $fixed_fields->[1], + fee_type => $fixed_fields->[2], + transaction_date => $fixed_fields->[3], + hold_queue_length => $fields->{CF}, + due_date => $fields->{AH}, + recall_date => $fields->{CJ}, + hold_pickup_date => $fields->{CM}, + item_identifier => $fields->{AB}, + title_identifier => $fields->{AJ}, + owner => $fields->{BG}, + currency_type => $fields->{BH}, + fee_amount => $fields->{BV}, + media_type => $fields->{CK}, + permanent_location => $fields->{AQ}, + current_location => $fields->{AP}, + item_properties => $fields->{CH}, + screen_message => $fields->{AF}, + print_line => $fields->{AG}, + }; + + return $out; +} + + +sub build_hold_msg{ + my ($self,$patron_id, $patron_pwd ,$holdMode, + $expiration_date, $pickup_location, $hold_type, + $item_id, $title_id, $fee_acknowledged + ) = @_; + + my $location = $self->{location}; + my $msg = "15"; + my $terminal_pwd = $self->{login_passwd} ; + + if($holdMode eq "add"){ + $holdMode = "+"; + }elsif($holdMode eq "delete"){ + $holdMode = "-"; + }elsif($holdMode eq "change"){ + $holdMode = "*"; + } + + $msg .= $holdMode . Sip::timestamp(); + $msg .= maybe_add(FID_EXPIRATION,$expiration_date); + $msg .= maybe_add(FID_PICKUP_LOCN,$pickup_location); + $msg .= maybe_add(FID_HOLD_TYPE,$hold_type); + $msg .= maybe_add(FID_INST_ID,$location); + $msg .= add_field(FID_PATRON_ID,$patron_id); + $msg .= maybe_add(FID_PATRON_PWD,$patron_pwd); + $msg .= maybe_add(FID_ITEM_ID,$item_id); + $msg .= maybe_add(FID_TITLE_ID,$title_id); + $msg .= maybe_add(FID_TERMINAL_PWD,$terminal_pwd); + #$msg .= maybe_add(FID_FEE_ACK,$fee_acknowledged); #This field did not work when tested with the Sirsi-dynex implementation + + return $msg; + +} + +sub place_hold{ + + my ($self,$patron_id, $patron_pwd ,$expiration_date, $pickup_location, $hold_type, + $item_id, $title_id, $fee_acknowledged + ) = @_; + + my $hold_mode = "add"; + my $msg = $self->build_hold_msg($patron_id, $patron_pwd,$hold_mode, + $expiration_date ,$pickup_location,$hold_type,$item_id,$title_id,$fee_acknowledged); + $self->login; + my $resp = $self->sendMsg($msg,5); + my $u = Sip::MsgType->new($resp,0); + $self->logout; + return $u; +} + +sub delete_hold{ + my ($self,$patron_id, $patron_pwd ,$expiration_date, $pickup_location, $hold_type, + $item_id, $title_id, $fee_acknowledged + ) = @_; + + my $hold_mode = "delete"; + my $msg = $self->build_hold_msg($patron_id, $patron_pwd,$hold_mode, + $expiration_date ,$pickup_location,$hold_type,$item_id,$title_id,$fee_acknowledged); + $self->login; + my $resp = $self->sendMsg($msg,5); + my $u = Sip::MsgType->new($resp,0); + return $u; + $self->logout; +} + +1; diff --git a/Open-ILS/src/perlmods/lib/FulfILLment/Util/Z3950.pm b/Open-ILS/src/perlmods/lib/FulfILLment/Util/Z3950.pm new file mode 100644 index 0000000000..2b5fb7b183 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/FulfILLment/Util/Z3950.pm @@ -0,0 +1,244 @@ +# +#=============================================================================== +# +# FILE: z39.50.pm +# +# DESCRIPTION: +# +# FILES: --- +# BUGS: --- +# NOTES: --- +# AUTHOR: Michael Davadrian Smith (), msmith@esilibrary.com +# COMPANY: Equinox Software +# VERSION: 1.0 +# CREATED: 12/12/2011 01:12:32 PM +# REVISION: --- +#=============================================================================== + +package FulfILLment::Util::Z3950; +use strict; +use warnings; +use Data::Dumper; +use JSON::XS; +use ZOOM; +use MARC::Record; +use MARC::File::XML ( BinaryEncoding => 'utf8' ); +use MARC::Charset; +use OpenSRF::Utils::Logger qw/$logger/; + +MARC::Charset->ignore_errors(1); + +sub new{ + my($type) = shift; + my $host = shift; + #$host =~ s/http:\/\///; + my $port = shift; + my $database = shift; + my $login= shift; + my $password= shift; + my ($self)={ + 'host'=>$host, + 'login' => $login, + 'password' => $password, + 'database' => $database, + 'port' => $port, + 'out'=>'',#will hold output of query + }; + + bless($self,$type); + +} + + +sub breaker2marc { + my $lines = shift; + my $delim = quotemeta(shift() || '$'); + + my $rec = new MARC::Record; + + my $first = 1; + for my $line (@$lines) { + + chomp($line); + + if ($first) { + if ($line =~ /^\d/) { + $rec->leader($line); + $first--; + } + } elsif ($line =~ /^=?(\d{3}) (.)(.) (.+)$/) { + + my ($tag, $i1, $i2, $rest) = ($1, $2, $3, $4); + + if ($tag < 10) { + $rec->insert_fields_ordered( MARC::Field->new( $tag => $rest ) ); + + } else { + + my @subfield_data = split $delim, $rest; + if ($subfield_data[0]) { + $subfield_data[0] = 'a ' . $subfield_data[0]; + } else { + shift @subfield_data; + } + + my @subfields; + for my $sfd (@subfield_data) { + if ($sfd =~ /^(.) (.+)$/) { + push @subfields, $1, $2; + } + } + + $rec->insert_fields_ordered( + MARC::Field->new( + $tag, + $i1, + $i2, + @subfields + ) + ) if @subfields; + } + } + } + + return $rec; +} + + +sub get_record_by_id { + my $self = shift; + my $recid = shift; + my $attr = shift || '12'; + my $asxml = shift || 1; + my $syntax = shift || 'usmarc'; + my $return_raw = shift || 0; + + my $conn = new ZOOM::Connection( + $self->{'host'}, + $self->{'port'}, + databaseName => $self->{'database'}, + preferredRecordSyntax => $syntax + ); + + my $query = "\@attr 1=$attr \"$recid\""; + + $logger->info(sprintf( + "FF Z3950 sending query to %s/%s => %s", + $self->{host}, $self->{database}, $query)); + + my $rs = $conn->search_pqf($query); + + if ($conn->errcode() != 0) { + $logger->error("Z39 bib-by-id failed: " . $conn->errmsg()); + return; + } + + $logger->info("Z39 bib-by-id returned ".$rs->size." hit(s)"); + + return unless $rs->size; + + return $rs->record(0)->raw if $return_raw; + + # warning: render() is highly subjective and may + # not behave as expected on all Z servers and formats + my $m = $rs->record(0)->render(); + + my $rec = breaker2marc([ split /\n/, $m ]); + + my $x = $rec->as_xml_record; + $x =~ s/^<\?.+?\?>.//sm; + + my @out; + $conn->destroy(); + if($asxml == 1){ + return $x; + }elsif($asxml == 0){ + return $m; + } +} + + + +sub getBibByTitle{ + my $self = shift; + my $title = shift; + my $attr = "4"; + my $asxml = shift || 1; + + my $conn = new ZOOM::Connection( + $self->{'host'}, + $self->{'port'}, + databaseName => $self->{'database'}, + preferredRecordSyntax => "usmarc" + ); + + my $rs = $conn->search_pqf("\@attr 1=$attr \"$title\""); + + if ($conn->errcode() != 0) { + die("something went wrong: " . $conn->errmsg()) + } + + my $m = $rs->record(0)->render(); + my $rec = breaker2marc([ split /\n/, $m ]); + my $x = $rec->as_xml_record; + $x =~ s/^<\?.+?\?>.//sm; + + my @out; + $conn->destroy(); + if($asxml == 1){ + return $x; + }elsif($asxml == 0){ + return $m; + } +} + + + + + +sub queryServer{ + my $self = shift; + my $query = shift; + my $asxml = shift || 1; + my $conn = new ZOOM::Connection( + $self->{host}, + $self->{port}, + databaseName => $self->{database}, + preferredRecordSyntax => "usmarc" + ); + my $rs = $conn->search_pqf($query); + + if($conn->errcode() != 0){ + die("something went wrong: ".$conn->errmsg()); + } + + my $m = $rs->record(0)->render(); + my $rec = breaker2marc([ split /\n/, $m ]); + my $x = $rec->as_xml_record; + $x =~ s/^<\?.+?\?>.//sm; + my @out; + $conn->destroy(); + if($asxml == 1){ + return $x; + }elsif($asxml == 0){ + return $m; + } + + +} + + + + + + + + + + + + + + + +1; diff --git a/Open-ILS/src/perlmods/lib/FulfILLment/WWW/FastImport.pm b/Open-ILS/src/perlmods/lib/FulfILLment/WWW/FastImport.pm new file mode 100755 index 0000000000..486cc260a0 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/FulfILLment/WWW/FastImport.pm @@ -0,0 +1,186 @@ +package FulfILLment::WWW::FastImport; +use strict; +use warnings; +use bytes; + +use Apache2::Log; +use Apache2::Const -compile => qw(OK REDIRECT DECLINED NOT_FOUND FORBIDDEN :log); +use APR::Const -compile => qw(:error SUCCESS); +use APR::Table; + +use Apache2::RequestRec (); +use Apache2::RequestIO (); +use Apache2::RequestUtil; +use CGI; +use Data::Dumper; + +use OpenSRF::EX qw(:try); +use OpenSRF::Utils::Cache; +use OpenSRF::System; +use OpenSRF::Utils::SettingsClient; +use OpenSRF::AppSession; +use OpenSRF::MultiSession; +use OpenSRF::Utils::JSON; +use XML::LibXML; + +use OpenILS::Utils::Fieldmapper; +use OpenSRF::Utils::Logger qw/$logger/; + +use MARC::Batch; +use MARC::File::XML ( BinaryEncoding => 'utf-8' ); +use MARC::Charset; +use Getopt::Long; +use Unicode::Normalize; +use Encode; + +use UNIVERSAL::require; + +MARC::Charset->ignore_errors(1); + +our @formats = qw/USMARC UNIMARC XML BRE/; +my $MAX_FILE_SIZE = 1073741824; # 1GB +my $FILE_READ_SIZE = 4096; + +# set the bootstrap config and template include directory when +# this module is loaded +my $bootstrap; + +sub import { + my $self = shift; + $bootstrap = shift; +} + + +sub child_init { + OpenSRF::System->bootstrap_client( config_file => $bootstrap ); +} + +sub handler { + my $r = shift; + my $cgi = new CGI; + + my $auth = $cgi->param('ses') || $cgi->cookie('ses'); + + unless(verify_login($auth)) { + $logger->error("authentication failed on vandelay record import: $auth"); + return Apache2::Const::FORBIDDEN; + } + + my $fh = $cgi->param('loadFile'); + my $x; + my $mtype = (sysread($fh,$x,1) && $x =~ /^\D/o) ? 'XML' : 'USMARC'; + + sysseek($fh,0,0); + + $r->content_type('html'); + print '
'; + + my $conf = OpenSRF::Utils::SettingsClient->new; + my $parallel = $conf->config_value( + apps => 'fulfillment.www.fast-import' => app_settings => 'parallel' + ) || 1; + + my $owner = $cgi->param('uploadLocation'); + + my $multi = OpenSRF::MultiSession->new( + app => 'open-ils.cstore', + cap => $parallel, + api_level => 1 + ); + + my $batch = new MARC::Batch ($mtype, $fh); + $batch->strict_off; + + my $count = 0; + my $rec = -1; + while (try { $rec = $batch->next } otherwise { $rec = -1 }) { + $count++; + warn "record $count\n"; + if ($rec == -1) { + print "
Processing of record $count in set $fh failed. Skipping this record
"; + next; + } + + try { + # Avoid an over-eager MARC::File::XML that may try to convert + # our record from MARC8 to UTF8 and break because the record + # is obviously already UTF8 + my $ldr = $rec->leader(); + if (($mtype eq 'XML') && (substr($ldr, 9, 1) ne 'a')) { + print "
MARCXML record LDR/09 was not 'a'; record leader may be corrupt
"; + substr($ldr,9,1,'a'); + $rec->leader($ldr); + } + + (my $xml = $rec->as_xml_record()) =~ s/\n//sog; + $xml =~ s/^<\?xml.+\?\s*>//go; + $xml =~ s/>\s+request( + 'open-ils.cstore.json_query', + { from => [ 'biblio.fast_import', $owner, $xml ] } + ); + + } catch Error with { + my $error = shift; + print "
Encountered a bad record during fast import: $error
"; + }; + + } + + print "
Completed processing of $count records from $fh
"; + $multi->session_wait(1); + $multi->disconnect; + + print '
'; + + return Apache2::Const::OK; +} + +# xml-escape non-ascii characters +sub entityize { + my($string, $form) = @_; + $form ||= ""; + + # If we're going to convert non-ASCII characters to XML entities, + # we had better be dealing with a UTF8 string to begin with + $string = decode_utf8($string); + + if ($form eq 'D') { + $string = NFD($string); + } else { + $string = NFC($string); + } + + # Convert raw ampersands to entities + $string =~ s/&(?!\S+;)/&/gso; + + # Convert Unicode characters to entities + $string =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe; + + return $string; +} + +sub verify_login { + my $auth_token = shift; + return undef unless $auth_token; + + my $user = OpenSRF::AppSession + ->create("open-ils.auth") + ->request( "open-ils.auth.session.retrieve", $auth_token ) + ->gather(1); + + if (ref($user) eq 'HASH' && $user->{ilsevent} == 1001) { + return undef; + } + + return $user if ref($user); + return undef; +} + +1; + diff --git a/Open-ILS/web/staff/js/circ_tracker.js b/Open-ILS/web/staff/js/circ_tracker.js new file mode 100644 index 0000000000..406596eaa1 --- /dev/null +++ b/Open-ILS/web/staff/js/circ_tracker.js @@ -0,0 +1,603 @@ +dojo.require("fieldmapper.AutoIDL"); +dojo.require("dijit.layout.ContentPane"); +dojo.require("dijit.layout.TabContainer"); +dojo.require("dijit.form.TextBox"); +dojo.require("openils.widget.AutoGrid"); +dojo.require("openils.User"); +dojo.require("openils.PermaCrud"); +dojo.require("openils.widget.OrgUnitFilteringSelect"); + +function objSize (obj) { + var size = 0, key; + for (key in obj) { + if (obj.hasOwnProperty(key)) size++; + } + return size; +} + + +var user = new openils.User ({authcookie:'ses'}); +openils.User.authtoken = user.authtoken; +var pcrud = new openils.PermaCrud ({authtoken:user.authtoken}); +var cgi = new CGI (); + +var cache = {acp:{}, au:{}, ac:{}, acn:{}, circ:{}, atc:{}}; +var firstrun = true; + +function resetLocation(loc,which) { + + if (!dojo.isObject(which)) which = {}; + var hasSome = objSize(which) > 0; + + if (!firstrun) toMeGrid.resetStore(); + if (!hasSome || which.tmg) toMeGrid.loadAll( + {order_by:{"circ":"xact_start"}}, + {circ_lib : loc, checkin_time : null} + ); + + if (!firstrun) fromMeGrid.resetStore(); + if (!hasSome || which.fmg) fromMeGrid.loadAll( + {order_by:{"circ":"xact_start"}}, + { checkin_time : null, + target_copy : { + "in" : { + select: { acp : ['id'] }, + from : 'acp', + where: { circ_lib : loc, id : { '=' : {'+circ' : 'target_copy'} } } + } + } + } + ); + + + if(!firstrun) nonHoldTransitsFromMeGrid.resetStore(); + if (!hasSome || which.nhtfmg) nonHoldTransitsFromMeGrid.loadAll( + {order_by:{"atc":"source_send_time DESC"}}, + {source:loc, dest_recv_time:null, + id:{"not in":{ + select : {'ahtc' : ['id']}, + from : 'ahtc', + where :{source:loc, dest_recv_time:null} + }} + } + ); + + + if(!firstrun) nonHoldTransitsToMeGrid.resetStore(); + if (!hasSome || which.nhttmg) nonHoldTransitsToMeGrid.loadAll( + {order_by:{"atc":"source_send_time DESC"}}, + {dest:loc, dest_recv_time:null, + id:{"not in":{ + select : {'ahtc' : ['id']}, + from : 'ahtc', + where :{dest:loc, dest_recv_time:null} + }} + } + ); + + + + + + firstrun = false; +} + + +function processCopyBarcodeAction (bc, resetPart) { + var c = pcrud.search( + "circ", + { checkin_time : null, + circ_lib : circLocation.getValue(), + target_copy : { + "in" : { + select: { acp : ['id'] }, + from : 'acp', + where: { + deleted : 'f', + circ_lib : { '<>' : circLocation.getValue() }, + barcode : bc, + id : { '=' : {'+circ' : 'target_copy'} } + } + } + } + } + )[0]; + + var stat = { + barcode: bc, // barcode we were passed + copy: null, // item with the barcode we were passed + patron: null, // patron FM object from circ.usr() + card: null, // item with the barcode we were passed + circ: null, // circ FM object + transit: null, // circ FM object + action: null, // Check In, Recall + }; + + if (c) { // circ found, it's leaving + checkinAction(c.id(),resetPart); + + } else { // going out ... maybe + c = pcrud.search( + "circ", + { checkin_time : null, + circ_lib : { '<>' : circLocation.getValue() }, + target_copy : { + "in" : { + select: { acp : ['id'] }, + from : 'acp', + where: { + deleted : 'f', + circ_lib : circLocation.getValue(), + barcode : bc, + id : { '=' : {'+circ' : 'target_copy'} } + } + } + } + } + )[0]; + + if (c) { // found a remote circ + recallAction(c.id(),{x:false}); + + } else { // return transit, perhaps? + stat.transit = pcrud.search( + "atc", + { target_copy : { + "in" : { + select: { acp : ['id'] }, + from : 'acp', + where: { + deleted : 'f', + circ_lib : circLocation.getValue(), + barcode : bc, + id : { '=' : {'+atc' : 'target_copy'} } + } + } + }, + dest: circLocation.getValue(), + dest_recv_time: null + }, {flesh: 2, flesh_fields: {aou:['ill_address'], atc: ['source','dest']}} + )[0]; // grab any transit + + if (stat.transit) { + if (!cache.acp[stat.transit.target_copy()]) cache.acp[stat.transit.target_copy()] = pcrud.retrieve("acp", stat.transit.target_copy()); + stat.copy = cache.acp[stat.transit.target_copy()]; + if (stat.copy) stat.barcode = stat.copy.barcode(); + + fieldmapper.standardRequest( + ['open-ils.circ','open-ils.circ.checkin.override'], + [user.authtoken, {noop: 1, force: 1, barcode: bc}] + ); + + stat.action = 'Incoming'; + stat.direction = 'Item Received'; + } else { + stat.action = 'None'; + stat.direction = 'Unknown'; + } + + return renderScanStatusRow(stat); + + } + } + +} + +function truncate_date (ts) { return ts.substring(0,10) } + +var stat_list = []; + +function renderScanStatusRow(stat) { + stat_list.push(stat); + var stat_pos = stat_list.length - 1; + + var row = dojo.clone( dojo.query('.statusTemplate')[0] ); + row.setAttribute('stat_pos', stat_pos); + + dojo.query('.action', row)[0].innerHTML = stat.action; + + if (stat.card) dojo.query('.patron', row)[0].innerHTML = stat.card.barcode(); + + if (stat.circ || stat.transit) { + dojo.query('.receipt', row)[0].setAttribute('href','javascript:printCircStatDetail(' + stat_pos + ')'); + } + + if (stat.copy) { + dojo.query('.item', row)[0].innerHTML = formatCopyBarcode(stat.copy); + } else { + dojo.query('.item', row)[0].innerHTML = stat.barcode; + } + + if (stat.transit) { + if (stat.transit.dest() == circLocation.getValue()) { + dojo.query('.location', row)[0].innerHTML = formatCirclib( + fieldmapper.aou.findOrgUnit(stat.transit.source()) + ); + } else { + dojo.query('.location', row)[0].innerHTML = formatCirclib( + fieldmapper.aou.findOrgUnit(stat.transit.dest()) + ); + } + } + + dojo.byId('statusTarget').appendChild(row); +} + +function printAllCircStatDetail() { + for (var i = 0; i < stat_list.length; i++) { + printCircStatDetail(i); + } +} + +function printCircStatDetail(stat_pos) { + var stat = stat_list[stat_pos]; + var template = fieldmapper.standardRequest( + ['open-ils.actor','open-ils.actor.web_action_print_template.fetch'], + [circLocation.getValue(), 'circ', stat.action, stat.direction ] + ); + + var f_list = [ 'copy', 'patron', 'card', 'circ', 'transit' ]; + + var stat_copy = { action : stat.action, barcode: stat.barcode, direction: stat.direction }; + for (var i = 0; i < f_list.length; i++) { + if (stat[f_list[i]]) stat_copy[f_list[i]] = stat[f_list[i]].toHash(true, [], true) + } + + var w = window.open(); + w.document.write( dojo.string.substitute( template.template(), stat_copy, function (v) { if (!v) return ''; return v; } ) ); + w.document.close(); + w.print(); + w.close(); +} + +function fetchCopy(rowIndex, item) { + if (!item) return null; + + var acp_id = this.grid.store.getValue(item, "target_copy"); + if (!cache.acp[acp_id]) + cache.acp[acp_id] = pcrud.retrieve("acp", this.grid.store.getValue(item, "target_copy")); + + return cache.acp[acp_id] +} + +function formatBarcode(value) { + if (!value) return ""; // XXX + return value.barcode(); +} + +function fetchSource(rowIndex, item) { + if (!item) return null; + + return fieldmapper.aou.findOrgUnit(this.grid.store.getValue(item, "source")); +} + +function fetchDest(rowIndex, item) { + if (!item) return null; + + return fieldmapper.aou.findOrgUnit(this.grid.store.getValue(item, "dest")); +} + +function fetchCirclib(rowIndex, item) { + if (!item) return null; + + return fieldmapper.aou.findOrgUnit(this.grid.store.getValue(item, "circ_lib")); +} + +function formatCirclib(value) { + if (!value) return ""; // XXX + return value.name(); +} + +function fetchCirc(rowIndex, item) { + if (!item) return null; + return item; +} + +function fetchTransit(rowIndex, item) { + if (!item) return null; + return item; +} + +function formatIncomingTransitActions(item) { + if (!item) return ""; + + var c_id = this.grid.store.getValue(item, "id"); + + var link_text = '' + + 'Receive Item' + + ''; + + return link_text; +} + +function formatOutgoingTransitActions(item) { return ""; } + +function formatLocalActions(item) { + if (!item) return ""; // XXX + var circ_id = this.grid.store.getValue(item, "id"); + return '' + + 'Check In' + + '
' + + 'Renew' + + '
' + + 'Mark Lost' + + '
' + + 'Mark Claims Returned' + + '
'; +} + +function formatRemoteActions(item) { + if (!item) return ""; // XXX + return '' + + 'Recall' + + ''; +} + +function formatCopyBarcode(value) { + if (!value) return ""; // XXX + if (!cache.acn[value.call_number()]) + cache.acn[value.call_number()] = pcrud.retrieve("acn", value.call_number()); + var cn = cache.acn[value.call_number()]; + return '' + value.barcode() + ''; +} + +function checkinAction(circid, resetPart) { + if (!cache.circ[circid]) + cache.circ[circid] = pcrud.retrieve("circ", circid); + + var c = cache.circ[circid]; + fieldmapper.standardRequest( + ['open-ils.circ','open-ils.circ.checkin.override'], + [user.authtoken, {force: 1, copy_id: c.target_copy(), patron: c.usr()}] + ); + resetLocation(circLocation.getValue(),resetPart); + + var stat = { + barcode: null, // barcode we were passed + copy: null, // item with the barcode we were passed + patron: null, // patron FM object from circ.usr() + card: null, // item with the barcode we were passed + circ: null, // circ FM object + transit: null, // circ FM object + action: 'Checked In', // Check In, Recall + }; + + c = pcrud.retrieve("circ", c.id()); // refetch the circ + cache.circ[c.id()] = c; + stat.circ = c; + + if (!cache.acp[c.target_copy()]) cache.acp[c.target_copy()] = pcrud.retrieve("acp", stat.circ.target_copy()); // fetch the copy + stat.copy = cache.acp[c.target_copy()]; + stat.barcode = stat.copy.barcode(); // barcode we would have been passed + + if (!cache.au[c.usr()]) cache.au[c.usr()] = pcrud.retrieve("au", stat.circ.usr()); // fetch the copy + stat.patron = cache.au[c.usr()]; + + if (!cache.ac[stat.patron.card()]) cache.ac[stat.patron.card()] = pcrud.retrieve("ac", stat.patron.card()); // fetch the patron card + stat.card = cache.ac[stat.patron.card()]; + + stat.transit = pcrud.search( + "atc", + { target_copy: stat.copy.id(), + source: circLocation.getValue(), + dest_recv_time: null + }, {flesh: 2, flesh_fields: {aou:['ill_address'], atc: ['source','dest']}} + )[0]; // grab any transit + + if (stat.transit) cache.atc[stat.transit.id()] = stat.transit; + + return renderScanStatusRow(stat); +} + +function renewAction(circid,resetPart) { + if (!resetPart) resetPart = {tmg:true}; + if (!cache.circ[circid]) + cache.circ[circid] = pcrud.retrieve("circ", circid); + + var c = cache.circ[circid]; + fieldmapper.standardRequest( + ['open-ils.circ','open-ils.circ.renew.override'], + [user.authtoken, {copy_id: c.target_copy(), patron: c.usr()}] + ); + resetLocation(circLocation.getValue(),resetPart); + + var stat = { + barcode: null, // barcode we were passed + copy: null, // item with the barcode we were passed + patron: null, // patron FM object from circ.usr() + card: null, // item with the barcode we were passed + circ: null, // circ FM object + transit: null, // circ FM object + action: 'Item Renewed', // Check In, Recall + }; + + var new_c = pcrud.search( // Fetch the new circ + "circ", + { checkin_time : null, + circ_lib : circLocation.getValue(), + target_copy : c.target_copy() + } + )[0]; + + if (!new_c) { + stat.action = 'Item Renewal Failed'; + } else if (c.id() != new_c.id()) { + c = new_c; + cache.circ[c.id()] = c; + stat.circ = c; + + } else if (c.id() == new_c.id()) { + c = pcrud.retrieve("circ", c.id()); // refetch the circ + cache.circ[c.id()] = c; + stat.circ = c; + stat.action = 'Item Renewal Failed'; + } + + if (!cache.acp[c.target_copy()]) cache.acp[c.target_copy()] = pcrud.retrieve("acp", stat.circ.target_copy()); // fetch the copy + stat.copy = cache.acp[c.target_copy()]; + stat.barcode = stat.copy.barcode(); // barcode we would have been passed + + if (!cache.au[c.usr()]) cache.au[c.usr()] = pcrud.retrieve("au", stat.circ.usr()); // fetch the copy + stat.patron = cache.au[c.usr()]; + + if (!cache.ac[stat.patron.card()]) cache.ac[stat.patron.card()] = pcrud.retrieve("ac", stat.patron.card()); // fetch the patron card + stat.card = cache.ac[stat.patron.card()]; + + return renderScanStatusRow(stat); +} + +function lostAction(circid,resetPart) { + if (!resetPart) resetPart = {tmg:true}; + if (!cache.circ[circid]) + cache.circ[circid] = pcrud.retrieve("circ", circid); + + var c = cache.circ[circid]; + + if (!cache.acp[c.target_copy()]) + cache.acp[c.target_copy()] = pcrud.retrieve("acp", c.target_copy()); + + var cp = cache.acp[c.target_copy()]; + + fieldmapper.standardRequest( + ['open-ils.circ','open-ils.circ.circulation.set_lost'], + [user.authtoken, {barcode: cp.barcode()}] + ); + resetLocation(circLocation.getValue(),resetPart); + + var stat = { + barcode: null, // barcode we were passed + copy: null, // item with the barcode we were passed + patron: null, // patron FM object from circ.usr() + card: null, // item with the barcode we were passed + circ: null, // circ FM object + transit: null, // circ FM object + action: 'Item Marked Lost', // Check In, Recall + }; + + c = pcrud.retrieve("circ", c.id()); // refetch the circ + cache.circ[c.id()] = c; + stat.circ = c; + + if (!cache.acp[c.target_copy()]) cache.acp[c.target_copy()] = pcrud.retrieve("acp", stat.circ.target_copy()); // fetch the copy + stat.copy = cache.acp[c.target_copy()]; + stat.barcode = stat.copy.barcode(); // barcode we would have been passed + + if (!cache.au[c.usr()]) cache.au[c.usr()] = pcrud.retrieve("au", stat.circ.usr()); // fetch the copy + stat.patron = cache.au[c.usr()]; + + if (!cache.ac[stat.patron.card()]) cache.ac[stat.patron.card()] = pcrud.retrieve("ac", stat.patron.card()); // fetch the patron card + stat.card = cache.ac[stat.patron.card()]; + + return renderScanStatusRow(stat); +} + +function CRAction(circid,resetPart) { + if (!resetPart) resetPart = {tmg:true}; + if (!cache.circ[circid]) + cache.circ[circid] = pcrud.retrieve("circ", circid); + + var c = cache.circ[circid]; + + if (!cache.acp[c.target_copy()]) + cache.acp[c.target_copy()] = pcrud.retrieve("acp", c.target_copy()); + + var cp = cache.acp[c.target_copy()]; + + fieldmapper.standardRequest( + ['open-ils.circ','open-ils.circ.circulation.set_claims_returned.override'], + [user.authtoken, {barcode: cp.barcode(), use_due_date: 1}] + ); + resetLocation(circLocation.getValue(),resetPart); + + var stat = { + barcode: null, // barcode we were passed + copy: null, // item with the barcode we were passed + patron: null, // patron FM object from circ.usr() + card: null, // item with the barcode we were passed + circ: null, // circ FM object + transit: null, // circ FM object + action: 'Item Marked Claims-Returned', // Check In, Recall + }; + + c = pcrud.retrieve("circ", c.id()); // refetch the circ + cache.circ[c.id()] = c; + stat.circ = c; + + if (!cache.acp[c.target_copy()]) cache.acp[c.target_copy()] = pcrud.retrieve("acp", stat.circ.target_copy()); // fetch the copy + stat.copy = cache.acp[c.target_copy()]; + stat.barcode = stat.copy.barcode(); // barcode we would have been passed + + if (!cache.au[c.usr()]) cache.au[c.usr()] = pcrud.retrieve("au", stat.circ.usr()); // fetch the copy + stat.patron = cache.au[c.usr()]; + + if (!cache.ac[stat.patron.card()]) cache.ac[stat.patron.card()] = pcrud.retrieve("ac", stat.patron.card()); // fetch the patron card + stat.card = cache.ac[stat.patron.card()]; + + return renderScanStatusRow(stat); +} + +function recallAction(circid,resetPart) { + if (!resetPart) resetPart = {fmg:true}; + alert('recalling ' + circid); + resetLocation(circLocation.getValue(),resetPart); + + var stat = { + barcode: null, // barcode we were passed + copy: null, // item with the barcode we were passed + card: null, // item with the barcode we were passed + patron: null, // patron FM object from circ.usr() + circ: null, // circ FM object + transit: null, // circ FM object + action: 'Item Recalled', // Check In, Recall + }; + + c = pcrud.retrieve("circ", c.id()); // refetch the circ + cache.circ[c.id()] = c; + stat.circ = c; + + if (!cache.acp[c.target_copy()]) cache.acp[c.target_copy()] = pcrud.retrieve("acp", stat.circ.target_copy()); // fetch the copy + stat.copy = cache.acp[c.target_copy()]; + stat.barcode = stat.copy.barcode(); // barcode we would have been passed + + if (!cache.au[c.usr()]) cache.au[c.usr()] = pcrud.retrieve("au", stat.circ.usr()); // fetch the copy + stat.patron = cache.au[c.usr()]; + + if (!cache.ac[stat.patron.card()]) cache.ac[stat.patron.card()] = pcrud.retrieve("ac", stat.patron.card()); // fetch the patron card + stat.card = cache.ac[stat.patron.card()]; + + stat.transit = pcrud.search( + "atc", + { target_copy: stat.copy.id(), + source: circLocation.getValue(), + dest_recv_time: null + }, {flesh: 2, flesh_fields: {aou:['ill_address'], atc: ['source','dest']}} + )[0]; // grab any transit + + if (stat.transit) cache.atc[stat.transit.id()] = stat.transit; + + return renderScanStatusRow(stat); +} + +function fetchCard(rowIndex, item) { + if (!item) return null; + + var auid = this.grid.store.getValue(item, "usr"); + if (!cache.au[auid]) + cache.au[auid] = pcrud.retrieve("au", auid); + + var acid = cache.au[auid].card(); + if (!cache.ac[acid]) + cache.ac[acid] = pcrud.retrieve("ac", acid); + + return cache.ac[acid] +} + +dojo.addOnLoad(function(){ + user.buildPermOrgSelector( + 'STAFF_LOGIN', + circLocation, + cgi.param('l') || user.user.home_ou() + ); +}); + + diff --git a/Open-ILS/web/staff/js/hold_tracker.js b/Open-ILS/web/staff/js/hold_tracker.js new file mode 100644 index 0000000000..247cc0befe --- /dev/null +++ b/Open-ILS/web/staff/js/hold_tracker.js @@ -0,0 +1,776 @@ +dojo.require("fieldmapper.AutoIDL"); +dojo.require("dijit.layout.ContentPane"); +dojo.require("dijit.layout.TabContainer"); +dojo.require("dijit.form.TextBox"); +dojo.require("openils.widget.AutoGrid"); +dojo.require("openils.User"); +dojo.require("openils.PermaCrud"); +dojo.require("openils.widget.OrgUnitFilteringSelect"); + +function objSize (x) { + var size = 0, key; + for (key in x) { + if (x.hasOwnProperty(key)) size++; + } + return size; +} + +var user = new openils.User ({authcookie:'ses'}); +openils.User.authtoken = user.authtoken; + +var pcrud = new openils.PermaCrud ({authtoken:user.authtoken}); +var cgi = new CGI (); + +var cache = { acp : {}, acn : {}, ahr : {}, au : {}, ac : {}, ahtc_by_hold : {}, atc : {}, circ : {} }; +var firstrun = true; + +function resetLocation(loc, which) { + + if (!dojo.isObject(which)) which = {}; + + var hasSome = objSize(which) > 0; + + if (!firstrun) toMeGrid.resetStore(); + if (!hasSome || which.tmg) toMeGrid.loadAll( + {order_by:{"ahr": "request_time DESC"}}, + {request_lib : loc, fulfillment_time : null, cancel_time : null} + ); + + + if (!firstrun) fromMeGrid.resetStore(); + if (!hasSome || which.fmg) fromMeGrid.loadAll( + {order_by:{"ahr": "request_time DESC"}}, + { fulfillment_time : null, + cancel_time : null, + current_copy : { + "in" : { + select: { acp : ['id'] }, + from : 'acp', + where: { circ_lib : loc, id : { '=' : {'+ahr' : 'current_copy'} } } + } + } + } + ); + + + + if (!firstrun) holdTransitFromMeGrid.resetStore(); + if (!hasSome || which.htfmg) holdTransitFromMeGrid.loadAll( + {order_by:{"ahtc":"source_send_time DESC"}}, + {source:loc, dest_recv_time:null} + ); + + + if(!firstrun) holdTransitToMeGrid.resetStore(); + if (!hasSome || which.httmg) holdTransitToMeGrid.loadAll( + {order_by:{"ahtc":"source_send_time DESC"}}, + {dest:loc, dest_recv_time:null} + ); + + firstrun = false; +} + +function processCopyBarcodeAction (bc) { + console.log("processing barcode " + bc); + + var h = pcrud.search( + "ahr", + { fulfillment_time : null, + cancel_time : null, + frozen : 'f', + request_lib : holdLocation.getValue(), + current_copy : { + "in" : { + select: { acp : ['id'] }, + from : 'acp', + where: { + deleted : 'f', + circ_lib : { '<>' : holdLocation.getValue() }, + barcode : bc, + id : { '=' : {'+ahr' : 'current_copy'} } + } + } + } + } + )[0]; + + var stat = { + barcode: bc, // barcode we were passed + copy: null, // item with the barcode we were passed + hold: h, // hold FM object + patron: null, // patron FM object + card: null, // card FM object + transit: null, // transit FM object + action: null, // Receive, Check Out, Capture + direction: null, // Incoming, Outgoing + }; + + if (h) { // hold coming here + stat.direction = 'Incoming'; + + if (h.shelf_time()) { // captured, ready for circ + stat.action = 'Check Out'; + checkoutAction(h.id(),stat, null, 'ill-foreign-checkout'); + } else { // receiving, check it in to capture + stat.action = 'Check In'; + checkinAction(h.id(),stat, null, 'ill-foreign-receive'); + } + + } else { // going out ... maybe + h = pcrud.search( + "ahr", + { fulfillment_time : null, + cancel_time : null, + frozen : 'f', + request_lib : { '<>' : holdLocation.getValue() }, + current_copy : { + in : { + select: { acp : ['id'] }, + from : 'acp', + where: { + deleted : 'f', + circ_lib : holdLocation.getValue(), + barcode : bc, + id : { '=' : {'+ahr' : 'current_copy'} } + } + } + } + } + )[0]; + + if (h) { // yep ... going out + stat.direction = 'Outgoing'; + + if (!h.capture_time()) { // time to capture it + stat.action = 'Capture'; + checkinAction(h.id(),stat,null,'ill-home-capture'); + } else { // else, already captured, just grab the transit + stat.hold = h; + cache.ahr[stat.hold.id()] = stat.hold; + + if (!cache.acp[stat.hold.current_copy()]) cache.acp[stat.hold.current_copy()] = pcrud.retrieve("acp", stat.hold.current_copy()); + stat.copy = cache.acp[stat.hold.current_copy()]; + if (stat.copy) stat.barcode = stat.copy.barcode(); + + if (!cache.au[stat.hold.usr()]) cache.au[stat.hold.usr()] = pcrud.retrieve("au", stat.hold.usr()); + stat.patron = cache.au[stat.hold.usr()]; + + if (!cache.ac[stat.patron.card()]) cache.ac[stat.patron.card()] = pcrud.retrieve("ac", stat.patron.card()); + stat.card = cache.ac[stat.patron.card()]; + + // Grab the transit again, with stuff fleshed for printing + stat.transit = pcrud.search("ahtc", { hold: stat.hold.id() }, {flesh: 2, flesh_fields: {aou:['ill_address'], ahtc: ['source','dest']}})[0]; + + return renderScanStatusRow(stat); + } + + + } else { // return transit, perhaps? + stat.transit = pcrud.search( + "atc", + { target_copy : { + "in" : { + select: { acp : ['id'] }, + from : 'acp', + where: { + deleted : 'f', + circ_lib : holdLocation.getValue(), + barcode : bc, + id : { '=' : {'+atc' : 'target_copy'} } + } + } + }, + dest: holdLocation.getValue(), + dest_recv_time: null + }, {flesh: 2, flesh_fields: {aou:['ill_address'], atc: ['source','dest']}} + )[0]; // grab any transit + + if (stat.transit) { // coming home + if (!cache.acp[stat.transit.target_copy()]) + cache.acp[stat.transit.target_copy()] = + pcrud.retrieve("acp", stat.transit.target_copy()); + + stat.copy = cache.acp[stat.transit.target_copy()]; + if (stat.copy) stat.barcode = stat.copy.barcode(); + + fieldmapper.standardRequest( + ['open-ils.circ','open-ils.circ.checkin.override'], + [user.authtoken, {noop: 1, force: 1, barcode: bc, ff_action: 'transit-home-receive'}] + ); + + stat.direction = 'Incoming'; + stat.action = 'Check In'; + } else { // maybe it's checked out here + var c = pcrud.search( + "circ", + { checkin_time : null, + circ_lib : holdLocation.getValue(), + target_copy : { + "in" : { + select: { acp : ['id'] }, + from : 'acp', + where: { + deleted : 'f', + circ_lib : { '<>' : holdLocation.getValue() }, + barcode : bc, + id : { '=' : {'+circ' : 'target_copy'} } + } + } + } + } + )[0]; + + if (c) { + stat.action = 'Check In'; + stat.direction = 'Outgoing'; + return checkinILLAction(c, stat); + + } else { + + stat.direction = 'Unknown'; + + // maybe it's already checked in here and transiting home + // TODO + // all the commented-out stuff below needs re-thinking + // with borrowing tmp copy barcode in mind + + /* + stat.transit = pcrud.search( + "atc", + { target_copy : { + "in" : { + select: { acp : ['id'] }, + from : 'acp', + where: { + deleted : 'f', + circ_lib : holdLocation.getValue(), + barcode : bc, + id : { '=' : {'+atc' : 'target_copy'} } + } + } + }, + dest: holdLocation.getValue(), + dest_recv_time: null + }, {flesh: 2, flesh_fields: {aou:['ill_address'], atc: ['source','dest']}} + )[0]; // grab any transit + + + // this copy means nothing to us, but show it in the UI so the user + // has some conformation it was entered. + + + + // find the real copy if we can + for (var id in cache) { + if (cache[id].barcode() == stat.barcode) { + stat.copy = cache[id]; + } + } + if (!stat.copy) { + stat.copy = pcrud.search( + 'acp', { + barcode : stat.barcode, + deleted : 'f' + } + )[0]; + if (stat.copy) + cache.acp[stat.copy.id()] = stat.copy; + } + + if (stat.copy) { + // find the last transit this copy took part in + // to provide some context to the user. + stat.transit = pcrud.search("atc", + {target_copy : stat.copy.id()}, + { flesh: 2, + flesh_fields: {aou:['ill_address'], atc: ['source','dest']}, + order_by : {atc : "source_send_time DESC"}, + limit : 1 + } + )[0]; + + if (stat.transit && !stat.transit.dest_recv_time && + stat.dest.id() != holdLocation.getValue() ) { + + // copy has already been checked in at the + // borrowing library and wants to get back home. + stat.direction = 'Outgoing'; + + // report on the most recent circulation + stat.circ = pcrud.search( + "circ", + { circ_lib : holdLocation.getValue(), + target_copy : + "in" : { + select: { acp : ['id'] }, + from : 'acp', + where: { + deleted : 'f', + circ_lib : { '<>' : holdLocation.getValue() }, + barcode : bc, + id : { '=' : {'+circ' : 'target_copy'} } + } + } + } + } + )[0]; + + } + + } + */ + + } + } + + return renderScanStatusRow(stat); + } + } +} + +function checkinILLAction(c, stat) { + fieldmapper.standardRequest( + ['open-ils.circ','open-ils.circ.checkin.override'], + [user.authtoken, {force: 1, copy_id: c.target_copy(), patron: c.usr(), ff_action: 'ill-foreign-checkin'}] + ); + + c = pcrud.retrieve("circ", c.id()); // refetch the circ + cache.circ[c.id()] = c; + stat.circ = c; + + if (!cache.acp[c.target_copy()]) cache.acp[c.target_copy()] = pcrud.retrieve("acp", stat.circ.target_copy()); // fetch the copy + stat.copy = cache.acp[c.target_copy()]; + stat.barcode = stat.copy.barcode(); // barcode we would have been passed + + if (!cache.au[c.usr()]) cache.au[c.usr()] = pcrud.retrieve("au", stat.circ.usr()); // fetch the copy + stat.patron = cache.au[c.usr()]; + + if (!cache.ac[stat.patron.card()]) cache.ac[stat.patron.card()] = pcrud.retrieve("ac", stat.patron.card()); // fetch the patron card + stat.card = cache.ac[stat.patron.card()]; + + stat.transit = pcrud.search( + "atc", + { target_copy: stat.copy.id(), + source: holdLocation.getValue(), + dest_recv_time: null + }, {flesh: 2, flesh_fields: {aou:['ill_address'], atc: ['source','dest']}} + )[0]; // grab any transit + + if (stat.transit) cache.atc[stat.transit.id()] = stat.transit; + + return renderScanStatusRow(stat); +} + +function truncate_date (ts) { return ts.substring(0,10) } + +var stat_list = []; + +function renderScanStatusRow(stat) { + stat_list.push(stat); + var stat_pos = stat_list.length - 1; + + var row = dojo.clone( dojo.query('.statusTemplate')[0] ); + row.setAttribute('stat_pos', stat_pos); + + dojo.query('.action', row)[0].innerHTML = stat.action; // TODO i18n + dojo.query('.direction', row)[0].innerHTML = stat.direction; // TODO i18n + + if (stat.card) dojo.query('.patron', row)[0].innerHTML = stat.card.barcode(); + + if (stat.circ) { + dojo.query('.receipt', row)[0].setAttribute('href','javascript:printCircStatDetail(' + stat_pos + ')'); + } else if (stat.hold || stat.transit) { + dojo.query('.receipt', row)[0].setAttribute('href','javascript:printHoldStatDetail(' + stat_pos + ')'); + } + + if (stat.copy) { + dojo.query('.item', row)[0].innerHTML = formatCopyBarcode(stat.copy); + } else { + dojo.query('.item', row)[0].innerHTML = stat.barcode; + } + + if (stat.transit) { + if (stat.transit.dest().id() == holdLocation.getValue()) { + dojo.query('.location', row)[0].innerHTML = formatCirclib( stat.transit.source() ); + } else { + dojo.query('.location', row)[0].innerHTML = formatCirclib( stat.transit.dest() ); + } + } + + dojo.byId('statusTarget').appendChild(row); +} + + +function printAllHoldStatDetail() { + for (var i = 0; i < stat_list.length; i++) { + console.log('stat list: ' + js2JSON(stat_list)); + if (stat_list[i].circ) printCircStatDetail(i); + else printHoldStatDetail(i); + } +} + +function printHoldStatDetail(stat_pos) { + var stat = stat_list[stat_pos]; + var template = fieldmapper.standardRequest( + ['open-ils.actor','open-ils.actor.web_action_print_template.fetch'], + [holdLocation.getValue(), 'hold', stat.action, stat.direction ] + ); + + var f_list = [ 'copy', 'patron', 'card', 'hold', 'transit' ]; + + var stat_copy = { action : stat.action, barcode: stat.barcode, direction: stat.direction }; + for (var i = 0; i < f_list.length; i++) { + if (stat[f_list[i]]) stat_copy[f_list[i]] = stat[f_list[i]].toHash(true, [], true) + } + + var w = window.open(); + w.document.write( dojo.string.substitute( template.template(), stat_copy, function (v) { if (!v) return ''; return v; } ) ); + w.document.close(); + w.print(); + w.close(); +} + +function printCircStatDetail(stat_pos) { + var stat = stat_list[stat_pos]; + var template = fieldmapper.standardRequest( + ['open-ils.actor','open-ils.actor.web_action_print_template.fetch'], + [holdLocation.getValue(), 'circ', stat.action, stat.direction ] + ); + + var f_list = [ 'copy', 'patron', 'card', 'circ', 'transit' ]; + + var stat_copy = { action : stat.action, barcode: stat.barcode, direction: stat.direction }; + for (var i = 0; i < f_list.length; i++) { + if (stat[f_list[i]]) stat_copy[f_list[i]] = stat[f_list[i]].toHash(true, [], true) + } + + var w = window.open(); + w.document.write( dojo.string.substitute( template.template(), stat_copy, function (v) { if (!v) return ''; return v; } ) ); + w.document.close(); + w.print(); + w.close(); +} + +function fetchCopy(rowIndex, item) { + if (!item) return null; + + var acp_id = this.grid.store.getValue(item, "current_copy"); + if (!cache.acp[acp_id]) cache.acp[acp_id] = pcrud.retrieve("acp", this.grid.store.getValue(item, "current_copy")); + return cache.acp[acp_id]; +} + +function fetchTargetCopy(rowIndex, item) { + if (!item) return null; + + var acp_id = this.grid.store.getValue(item, "target_copy"); + if (!cache.acp[acp_id]) cache.acp[acp_id] = pcrud.retrieve("acp", this.grid.store.getValue(item, "target_copy")); + return cache.acp[acp_id]; +} + +function formatBarcode(value) { + if (!value) return ""; // XXX + return value.barcode(); +} + +function fetchSource(rowIndex, item) { + if (!item) return null; + + return fieldmapper.aou.findOrgUnit(this.grid.store.getValue(item, "source")); +} + +function fetchDest(rowIndex, item) { + if (!item) return null; + + return fieldmapper.aou.findOrgUnit(this.grid.store.getValue(item, "dest")); +} + +function fetchCirclib(rowIndex, item) { + if (!item) return null; + + return fieldmapper.aou.findOrgUnit(this.grid.store.getValue(item, "pickup_lib")); +} + +function formatCirclib(value) { + if (!value) return ""; // XXX + return value.name(); +} + +function fetchCard(rowIndex, item) { + if (!item) return null; + + var u_id = this.grid.store.getValue(item, "usr"); + if (!cache.au[u_id]) cache.au[u_id] = pcrud.retrieve("au", u_id); + + if (!cache.ac[cache.au[u_id].card()]) cache.ac[cache.au[u_id].card()] = pcrud.retrieve("ac", cache.au[u_id].card()); + + return cache.ac[cache.au[u_id].card()]; +} + +function fetchHold(rowIndex, item) { + if (!item) return null; + return item; +} + +function fetchTransit(rowIndex, item) { + if (!item) return null; + return item; +} + +function formatLocalActions(item) { + if (!item) return ""; // XXX + + var h_id = this.grid.store.getValue(item, "id"); + + var f_text = 'Freeze Request
'; + if (this.grid.store.getValue(item, "frozen") == 't') + f_text = 'Thaw Request
'; + + var cc_text = 'Target Request
'; + if (this.grid.store.getValue(item, "current_copy")) cc_text = ''; + + var cap_text = 'Receive Item
'; + if (this.grid.store.getValue(item, "shelf_time")) cap_text = 'Circulate Item
'; + + var link_text = '' + + cap_text + f_text + cc_text + + 'Cancel Request' + + ''; + return link_text; +} + +function formatRemoteActions(item) { + if (!item) return ""; // XXX + + var h_id = this.grid.store.getValue(item, "id"); + + var cap_text = 'Capture Item
'; + if (this.grid.store.getValue(item, "capture_time")) cap_text = ''; + + var link_text = '' + + cap_text + + 'Reject Request' + + ''; + return link_text; +} + +function formatIncomingTransitActions(item) { + if (!item) return ""; // XXX + + var h_id = this.grid.store.getValue(item, "hold"); + + var link_text = '' + + 'Receive Item
' + + 'Cancel Request' + + '
'; + return link_text; +} + +function formatOutgoingTransitActions(item) { + if (!item) return ""; // XXX + + var h_id = this.grid.store.getValue(item, "hold"); + + var link_text = '' + + 'Cancel Request' + + ''; + return link_text; +} + +function formatCopyBarcode(value) { + if (!value) return ""; // XXX + if (!cache.acn[value.call_number()]) cache.acn[value.call_number()] = pcrud.retrieve("acn", value.call_number()); + return '' + value.barcode() + ''; +} + +function checkinAction(holdid, stat, resetPart, ff_a) { + if (!resetPart) resetPart = {tmg : true}; + var hold = pcrud.retrieve("ahr", holdid); + fieldmapper.standardRequest( + ['open-ils.circ','open-ils.circ.checkin.override'], + [user.authtoken, {force: 1, copy_id: hold.current_copy(), patron: hold.usr(), ff_action: ff_a}] + ); + resetLocation(holdLocation.getValue(), resetPart); + + stat.hold = pcrud.retrieve("ahr", holdid); // refetch the hold + cache.ahr[stat.hold.id()] = stat.hold; + + if (!cache.acp[stat.hold.current_copy()]) cache.acp[stat.hold.current_copy()] = pcrud.retrieve("acp", stat.hold.current_copy()); + stat.copy = cache.acp[stat.hold.current_copy()]; + + if (!cache.au[stat.hold.usr()]) cache.au[stat.hold.usr()] = pcrud.retrieve("au", stat.hold.usr()); + stat.patron = cache.au[stat.hold.usr()]; + + if (!cache.ac[stat.patron.card()]) cache.ac[stat.patron.card()] = pcrud.retrieve("ac", stat.patron.card()); + stat.card = cache.ac[stat.patron.card()]; + + // Grab the transit again, with stuff fleshed for printing + stat.transit = pcrud.search("ahtc", { hold: stat.hold.id() }, {flesh: 2, flesh_fields: {aou:['ill_address'], ahtc: ['source','dest']}})[0]; + + return renderScanStatusRow(stat); + +} + +function checkoutAction(holdid, stat, resetPart, ff_a) { + if (!resetPart) resetPart = {tmg : true}; + var hold = pcrud.retrieve("ahr", holdid); + fieldmapper.standardRequest( + ['open-ils.circ','open-ils.circ.checkout.full'], + [user.authtoken, {copy_id: hold.current_copy(), patron: hold.usr(), ff_action: ff_a}] + ); + resetLocation(holdLocation.getValue(), resetPart); + + stat.hold = pcrud.retrieve("ahr", holdid); // refetch the hold + cache.ahr[stat.hold.id()] = stat.hold; + + var c = pcrud.search( + "circ", + { checkin_time : null, + circ_lib : holdLocation.getValue(), + target_copy : hold.current_copy() + } + )[0]; + + cache.circ[c.id()] = c; + stat.circ = cache.circ[c.id()]; + + if (!cache.acp[stat.hold.current_copy()]) cache.acp[stat.hold.current_copy()] = pcrud.retrieve("acp", stat.hold.current_copy()); + stat.copy = cache.acp[stat.hold.current_copy()]; + + if (!cache.au[stat.hold.usr()]) cache.au[stat.hold.usr()] = pcrud.retrieve("au", stat.hold.usr()); + stat.patron = cache.au[stat.hold.usr()]; + + if (!cache.ac[stat.patron.card()]) cache.ac[stat.patron.card()] = pcrud.retrieve("ac", stat.patron.card()); + stat.card = cache.ac[stat.patron.card()]; + + // Grab the transit again, with stuff fleshed for printing + stat.transit = pcrud.search("ahtc", { hold: stat.hold.id() }, {flesh: 2, flesh_fields: {aou:['ill_address'], ahtc: ['source','dest']}})[0]; + + return renderScanStatusRow(stat); + +} + +function cancelAction(holdid, resetPart) { + if (!resetPart) resetPart = {tmg : true}; + fieldmapper.standardRequest( + ['open-ils.circ','open-ils.circ.hold.cancel'], + [user.authtoken, holdid] + ); + resetLocation(holdLocation.getValue(), resetPart); + + var stat = { + barcode: '', // barcode we were passed + copy: null, // item with the barcode we were passed + hold: null, // hold FM object + patron: null, // patron FM object + card: null, // card FM object + transit: null, // transit FM object + action: 'Cancel', // Receive, Check Out, Capture + direction: 'Incoming', // Incoming, Outgoing + }; + + stat.hold = pcrud.retrieve("ahr", holdid); // refetch the hold + cache.ahr[stat.hold.id()] = stat.hold; + + if (!cache.acp[stat.hold.current_copy()]) cache.acp[stat.hold.current_copy()] = pcrud.retrieve("acp", stat.hold.current_copy()); + stat.copy = cache.acp[stat.hold.current_copy()]; + if (stat.copy) stat.barcode = stat.copy.barcode(); + + if (!cache.au[stat.hold.usr()]) cache.au[stat.hold.usr()] = pcrud.retrieve("au", stat.hold.usr()); + stat.patron = cache.au[stat.hold.usr()]; + + if (!cache.ac[stat.patron.card()]) cache.ac[stat.patron.card()] = pcrud.retrieve("ac", stat.patron.card()); + stat.card = cache.ac[stat.patron.card()]; + + // Grab the transit again, with stuff fleshed for printing + stat.transit = pcrud.search("ahtc", { hold: stat.hold.id() }, {flesh: 2, flesh_fields: {aou:['ill_address'], ahtc: ['source','dest']}})[0]; + + return renderScanStatusRow(stat); + +} + +function freezeAction(holdid, setting, resetPart) { + if (!resetPart) resetPart = {tmg : true}; + fieldmapper.standardRequest( + ['open-ils.circ','open-ils.circ.hold.update'], + [user.authtoken, null, { id : holdid, frozen : setting }] + ); + resetLocation(holdLocation.getValue(), resetPart); + + var stat = { + barcode: '', // barcode we were passed + copy: null, // item with the barcode we were passed + hold: null, // hold FM object + patron: null, // patron FM object + card: null, // card FM object + transit: null, // transit FM object + action: setting == 't' ? 'Freeze' : 'Thaw', // Receive, Check Out, Capture + direction: 'Incoming', // Incoming, Outgoing + }; + + stat.hold = pcrud.retrieve("ahr", holdid); // refetch the hold + cache.ahr[stat.hold.id()] = stat.hold; + + if (!cache.acp[stat.hold.current_copy()]) cache.acp[stat.hold.current_copy()] = pcrud.retrieve("acp", stat.hold.current_copy()); + stat.copy = cache.acp[stat.hold.current_copy()]; + if (stat.copy) stat.barcode = stat.copy.barcode(); + + if (!cache.au[stat.hold.usr()]) cache.au[stat.hold.usr()] = pcrud.retrieve("au", stat.hold.usr()); + stat.patron = cache.au[stat.hold.usr()]; + + if (!cache.ac[stat.patron.card()]) cache.ac[stat.patron.card()] = pcrud.retrieve("ac", stat.patron.card()); + stat.card = cache.ac[stat.patron.card()]; + + // Grab the transit again, with stuff fleshed for printing + stat.transit = pcrud.search("ahtc", { hold: stat.hold.id() }, {flesh: 2, flesh_fields: {aou:['ill_address'], ahtc: ['source','dest']}})[0]; + + return renderScanStatusRow(stat); + +} + +function rejectAction(holdid, resetPart) { + if (!resetPart) resetPart = {fmg : true}; + fieldmapper.standardRequest( + ['open-ils.circ','open-ils.circ.hold.reset'], + [user.authtoken, holdid] + ); + resetLocation(holdLocation.getValue(), resetPart); + + var stat = { + barcode: '', // barcode we were passed + copy: null, // item with the barcode we were passed + hold: null, // hold FM object + patron: null, // patron FM object + card: null, // card FM object + transit: null, // transit FM object + action: 'Reject', // Receive, Check Out, Capture + direction: 'Outgoing', // Incoming, Outgoing + }; + + stat.hold = pcrud.retrieve("ahr", holdid); // refetch the hold + cache.ahr[stat.hold.id()] = stat.hold; + + if (!cache.acp[stat.hold.current_copy()]) cache.acp[stat.hold.current_copy()] = pcrud.retrieve("acp", stat.hold.current_copy()); + stat.copy = cache.acp[stat.hold.current_copy()]; + if (stat.copy) stat.barcode = stat.copy.barcode(); + + if (!cache.au[stat.hold.usr()]) cache.au[stat.hold.usr()] = pcrud.retrieve("au", stat.hold.usr()); + stat.patron = cache.au[stat.hold.usr()]; + + if (!cache.ac[stat.patron.card()]) cache.ac[stat.patron.card()] = pcrud.retrieve("ac", stat.patron.card()); + stat.card = cache.ac[stat.patron.card()]; + + // Grab the transit again, with stuff fleshed for printing + stat.transit = pcrud.search("ahtc", { hold: stat.hold.id() }, {flesh: 2, flesh_fields: {aou:['ill_address'], ahtc: ['source','dest']}})[0]; + + return renderScanStatusRow(stat); + +} + +dojo.addOnLoad(function(){ + user.buildPermOrgSelector( + 'STAFF_LOGIN', + holdLocation, + cgi.param('l') || user.user.home_ou() + ); +}); + diff --git a/Open-ILS/web/staff/js/record_mgmt.js b/Open-ILS/web/staff/js/record_mgmt.js new file mode 100644 index 0000000000..30e4521f65 --- /dev/null +++ b/Open-ILS/web/staff/js/record_mgmt.js @@ -0,0 +1,41 @@ +dojo.require("fieldmapper.AutoIDL"); +dojo.require("dijit.layout.ContentPane"); +dojo.require("dijit.layout.TabContainer"); +dojo.require("openils.widget.AutoGrid"); +dojo.require("openils.User"); +dojo.require("openils.PermaCrud"); +dojo.require("openils.widget.OrgUnitFilteringSelect"); +dojo.require("dojo.io.iframe"); + +var user = new openils.User ({authcookie:'ses'}); +openils.User.authtoken = user.authtoken; +var pcrud = new openils.PermaCrud ({authtoken:user.authtoken}); +var cgi = new CGI (); + +function wireForm(){ + var canvas=dojo.byId("response"); + dojo.connect(dojo.byId('localMarcForm'),"onsubmit",function(event){ + dojo.stopEvent(event); + + dojo.byId("response").innerHTML="  Sending data, please wait..."; + dojo.io.iframe.send({ + form: "localMarcForm", + handleAs: "html", + load: function(data){ canvas.innerHTML = data.innerHTML }, + error: function(error){ canvas.innerHTML = error.innerHTML } + }); + + }); +} + +dojo.addOnLoad(function(){ + user.buildPermOrgSelector( + 'STAFF_LOGIN', + loc, + cgi.param('l') || user.user.home_ou() + ); + + wireForm(); +}); + + diff --git a/Open-ILS/web/staff/php/barcode/barcode.php b/Open-ILS/web/staff/php/barcode/barcode.php new file mode 100755 index 0000000000..67eae11082 --- /dev/null +++ b/Open-ILS/web/staff/php/barcode/barcode.php @@ -0,0 +1,174 @@ +mWidth = $Width; + $this->mHeight = $Height; + $this->mStyle = $Style; + $this->mFont = BCD_DEFAULT_FONT; + $this->mImg = ImageCreate($this->mWidth, $this->mHeight); + $dbColor = $this->mStyle & BCS_REVERSE_COLOR ? BCD_DEFAULT_FOREGROUND_COLOR : BCD_DEFAULT_BACKGROUND_COLOR; + $dfColor = $this->mStyle & BCS_REVERSE_COLOR ? BCD_DEFAULT_BACKGROUND_COLOR : BCD_DEFAULT_FOREGROUND_COLOR; + $this->mBgcolor = ImageColorAllocate($this->mImg, ($dbColor & 0xFF0000) >> 16, + ($dbColor & 0x00FF00) >> 8 , $dbColor & 0x0000FF); + $this->mBrush = ImageColorAllocate($this->mImg, ($dfColor & 0xFF0000) >> 16, + ($dfColor & 0x00FF00) >> 8 , $dfColor & 0x0000FF); + if (!($this->mStyle & BCS_TRANSPARENT)) { + ImageFill($this->mImg, $this->mWidth, $this->mHeight, $this->mBgcolor); + } + __TRACE__("OBJECT CONSTRUCTION: ".$this->mWidth." ".$this->mHeight." ".$this->mStyle); + } + + function DrawObject ($xres) { + /* there is not implementation neded, is simply the asbsract function. */ + __TRACE__("OBJECT DRAW: NEED VIRTUAL FUNCTION IMPLEMENTATION"); + return false; + } + + function DrawBorder () { + ImageRectangle($this->mImg, 0, 0, $this->mWidth-1, $this->mHeight-1, $this->mBrush); + __TRACE__("DRAWING BORDER"); + } + + function DrawChar ($Font, $xPos, $yPos, $Char) { + ImageString($this->mImg,$Font,$xPos,$yPos,$Char,$this->mBrush); + } + + function DrawText ($Font, $xPos, $yPos, $Char) { + ImageString($this->mImg,$Font,$xPos,$yPos,$Char,$this->mBrush); + } + + function DrawSingleBar($xPos, $yPos, $xSize, $ySize) { + if ($xPos>=0 && $xPos<=$this->mWidth && ($xPos+$xSize)<=$this->mWidth && + $yPos>=0 && $yPos<=$this->mHeight && ($yPos+$ySize)<=$this->mHeight) { + for ($i=0;$i<$xSize;$i++) { + ImageLine($this->mImg, $xPos+$i, $yPos, $xPos+$i, $yPos+$ySize, $this->mBrush); + } + return true; + } + __DEBUG__("DrawSingleBar: Out of range"); + return false; + } + + function GetError() { + return $this->mError; + } + + function GetFontHeight($font) { + return ImageFontHeight($font); + } + + function GetFontWidth($font) { + return ImageFontWidth($font); + } + + function SetFont($font) { + $this->mFont = $font; + } + + function GetStyle () { + return $this->mStyle; + } + + function SetStyle ($Style) { + __TRACE__("CHANGING STYLE"); + $this->mStyle = $Style; + } + + function FlushObject () { + if (($this->mStyle & BCS_BORDER)) { + $this->DrawBorder(); + } + if ($this->mStyle & BCS_IMAGE_PNG) { + Header("Content-Type: image/png"); + ImagePng($this->mImg); + } else if ($this->mStyle & BCS_IMAGE_JPEG) { + Header("Content-Type: image/jpeg"); + ImageJpeg($this->mImg); + } else __DEBUG__("FlushObject: No output type"); + } + + function DestroyObject () { + ImageDestroy($obj->mImg); + } + } +?> diff --git a/Open-ILS/web/staff/php/barcode/c128aobject.php b/Open-ILS/web/staff/php/barcode/c128aobject.php new file mode 100755 index 0000000000..af4c130d01 --- /dev/null +++ b/Open-ILS/web/staff/php/barcode/c128aobject.php @@ -0,0 +1,320 @@ +BarcodeObject($Width, $Height, $Style); + $this->mValue = $Value; + $this->mChars = " !\"#$%&'()*+´-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"; + $this->mCharSet = array + ( + "212222", /* 00 */ + "222122", /* 01 */ + "222221", /* 02 */ + "121223", /* 03 */ + "121322", /* 04 */ + "131222", /* 05 */ + "122213", /* 06 */ + "122312", /* 07 */ + "132212", /* 08 */ + "221213", /* 09 */ + "221312", /* 10 */ + "231212", /* 11 */ + "112232", /* 12 */ + "122132", /* 13 */ + "122231", /* 14 */ + "113222", /* 15 */ + "123122", /* 16 */ + "123221", /* 17 */ + "223211", /* 18 */ + "221132", /* 19 */ + "221231", /* 20 */ + "213212", /* 21 */ + "223112", /* 22 */ + "312131", /* 23 */ + "311222", /* 24 */ + "321122", /* 25 */ + "321221", /* 26 */ + "312212", /* 27 */ + "322112", /* 28 */ + "322211", /* 29 */ + "212123", /* 30 */ + "212321", /* 31 */ + "232121", /* 32 */ + "111323", /* 33 */ + "131123", /* 34 */ + "131321", /* 35 */ + "112313", /* 36 */ + "132113", /* 37 */ + "132311", /* 38 */ + "211313", /* 39 */ + "231113", /* 40 */ + "231311", /* 41 */ + "112133", /* 42 */ + "112331", /* 43 */ + "132131", /* 44 */ + "113123", /* 45 */ + "113321", /* 46 */ + "133121", /* 47 */ + "313121", /* 48 */ + "211331", /* 49 */ + "231131", /* 50 */ + "213113", /* 51 */ + "213311", /* 52 */ + "213131", /* 53 */ + "311123", /* 54 */ + "311321", /* 55 */ + "331121", /* 56 */ + "312113", /* 57 */ + "312311", /* 58 */ + "332111", /* 59 */ + "314111", /* 60 */ + "221411", /* 61 */ + "431111", /* 62 */ + "111224", /* 63 */ + "111422", /* 64 */ + "121124", /* 65 */ + "121421", /* 66 */ + "141122", /* 67 */ + "141221", /* 68 */ + "112214", /* 69 */ + "112412", /* 70 */ + "122114", /* 71 */ + "122411", /* 72 */ + "142112", /* 73 */ + "142211", /* 74 */ + "241211", /* 75 */ + "221114", /* 76 */ + "413111", /* 77 */ + "241112", /* 78 */ + "134111", /* 79 */ + "111242", /* 80 */ + "121142", /* 81 */ + "121241", /* 82 */ + "114212", /* 83 */ + "124112", /* 84 */ + "124211", /* 85 */ + "411212", /* 86 */ + "421112", /* 87 */ + "421211", /* 88 */ + "212141", /* 89 */ + "214121", /* 90 */ + "412121", /* 91 */ + "111143", /* 92 */ + "111341", /* 93 */ + "131141", /* 94 */ + "114113", /* 95 */ + "114311", /* 96 */ + "411113", /* 97 */ + "411311", /* 98 */ + "113141", /* 99 */ + "114131", /* 100 */ + "311141", /* 101 */ + "411131" /* 102 */ + ); + } + + function GetCharIndex ($char) { + for ($i=0;$i<64;$i++) { + if ($this->mChars[$i] == $char) + return $i; + } + return -1; + } + + function GetBarSize ($xres, $char) { + switch ($char) + { + case '1': + $cVal = BCD_C128_BAR_1; + break; + case '2': + $cVal = BCD_C128_BAR_2; + break; + case '3': + $cVal = BCD_C128_BAR_3; + break; + case '4': + $cVal = BCD_C128_BAR_4; + break; + default: + $cVal = 0; + } + return $cVal * $xres; + } + + + function GetSize($xres) { + $len = strlen($this->mValue); + + if ($len == 0) { + $this->mError = "Null value"; + __DEBUG__("GetRealSize: null barcode value"); + return false; + } + $ret = 0; + for ($i=0;$i<$len;$i++) { + if (($id = $this->GetCharIndex($this->mValue[$i])) == -1) { + $this->mError = "C128A not include the char '".$this->mValue[$i]."'"; + return false; + } else { + $cset = $this->mCharSet[$id]; + $ret += $this->GetBarSize($xres, $cset[0]); + $ret += $this->GetBarSize($xres, $cset[1]); + $ret += $this->GetBarSize($xres, $cset[2]); + $ret += $this->GetBarSize($xres, $cset[3]); + $ret += $this->GetBarSize($xres, $cset[4]); + $ret += $this->GetBarSize($xres, $cset[5]); + } + } + + /* length of Check character */ + $cset = $this->GetCheckCharValue(); + for ($i=0;$i<6;$i++) { + $CheckSize += $this->GetBarSize($cset[$i], $xres); + } + $StartSize = 2*BCD_C128_BAR_2*$xres + 3*BCD_C128_BAR_1*$xres + BCD_C128_BAR_4*$xres; + $StopSize = 2*BCD_C128_BAR_2*$xres + 3*BCD_C128_BAR_1*$xres + 2*BCD_C128_BAR_3*$xres; + return $StartSize + $ret + $CheckSize + $StopSize; + } + + function GetCheckCharValue() + { + $len = strlen($this->mValue); + $sum = 103; // 'A' type; + for ($i=0;$i<$len;$i++) { + $sum += $this->GetCharIndex($this->mValue[$i]) * ($i+1); + } + $check = $sum % 103; + return $this->mCharSet[$check]; + } + + function DrawStart($DrawPos, $yPos, $ySize, $xres) + { /* Start code is '211412' */ + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('2', $xres) , $ySize); + $DrawPos += $this->GetBarSize('2', $xres); + $DrawPos += $this->GetBarSize('1', $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('1', $xres) , $ySize); + $DrawPos += $this->GetBarSize('1', $xres); + $DrawPos += $this->GetBarSize('4', $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('1', $xres) , $ySize); + $DrawPos += $this->GetBarSize('1', $xres); + $DrawPos += $this->GetBarSize('2', $xres); + return $DrawPos; + } + + function DrawStop($DrawPos, $yPos, $ySize, $xres) + { /* Stop code is '2331112' */ + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('2', $xres) , $ySize); + $DrawPos += $this->GetBarSize('2', $xres); + $DrawPos += $this->GetBarSize('3', $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('3', $xres) , $ySize); + $DrawPos += $this->GetBarSize('3', $xres); + $DrawPos += $this->GetBarSize('1', $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('1', $xres) , $ySize); + $DrawPos += $this->GetBarSize('1', $xres); + $DrawPos += $this->GetBarSize('1', $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('2', $xres) , $ySize); + $DrawPos += $this->GetBarSize('2', $xres); + return $DrawPos; + } + + function DrawCheckChar($DrawPos, $yPos, $ySize, $xres) + { + $cset = $this->GetCheckCharValue(); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[0], $xres) , $ySize); + $DrawPos += $this->GetBarSize($cset[0], $xres); + $DrawPos += $this->GetBarSize($cset[1], $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[2], $xres) , $ySize); + $DrawPos += $this->GetBarSize($cset[2], $xres); + $DrawPos += $this->GetBarSize($cset[3], $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[4], $xres) , $ySize); + $DrawPos += $this->GetBarSize($cset[4], $xres); + $DrawPos += $this->GetBarSize($cset[5], $xres); + return $DrawPos; + } + + function DrawObject ($xres) + { + $len = strlen($this->mValue); + if (($size = $this->GetSize($xres))==0) { + __DEBUG__("GetSize: failed"); + return false; + } + + if ($this->mStyle & BCS_ALIGN_CENTER) $sPos = (integer)(($this->mWidth - $size ) / 2); + else if ($this->mStyle & BCS_ALIGN_RIGHT) $sPos = $this->mWidth - $size; + else $sPos = 0; + + /* Total height of bar code -Bars only- */ + if ($this->mStyle & BCS_DRAW_TEXT) $ysize = $this->mHeight - BCD_DEFAULT_MAR_Y1 - BCD_DEFAULT_MAR_Y2 - $this->GetFontHeight($this->mFont); + else $ysize = $this->mHeight - BCD_DEFAULT_MAR_Y1 - BCD_DEFAULT_MAR_Y2; + + /* Draw text */ + if ($this->mStyle & BCS_DRAW_TEXT) { + if ($this->mStyle & BCS_STRETCH_TEXT) { + for ($i=0;$i<$len;$i++) { + $this->DrawChar($this->mFont, $sPos+(2*BCD_C128_BAR_2*$xres + 3*BCD_C128_BAR_1*$xres + BCD_C128_BAR_4*$xres)+($size/$len)*$i, + $ysize + BCD_DEFAULT_MAR_Y1 + BCD_DEFAULT_TEXT_OFFSET, $this->mValue[$i]); + } + } else {/* Center */ + $text_width = $this->GetFontWidth($this->mFont) * strlen($this->mValue); + $this->DrawText($this->mFont, $sPos+(($size-$text_width)/2)+(2*BCD_C128_BAR_2*$xres + 3*BCD_C128_BAR_1*$xres + BCD_C128_BAR_4*$xres), + $ysize + BCD_DEFAULT_MAR_Y1 + BCD_DEFAULT_TEXT_OFFSET, $this->mValue); + } + } + + $cPos = 0; + $DrawPos = $this->DrawStart($sPos, BCD_DEFAULT_MAR_Y1 , $ysize, $xres); + do { + $c = $this->GetCharIndex($this->mValue[$cPos]); + $cset = $this->mCharSet[$c]; + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[0], $xres) , $ysize); + $DrawPos += $this->GetBarSize($cset[0], $xres); + $DrawPos += $this->GetBarSize($cset[1], $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[2], $xres) , $ysize); + $DrawPos += $this->GetBarSize($cset[2], $xres); + $DrawPos += $this->GetBarSize($cset[3], $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[4], $xres) , $ysize); + $DrawPos += $this->GetBarSize($cset[4], $xres); + $DrawPos += $this->GetBarSize($cset[5], $xres); + $cPos++; + } while ($cPos<$len); + $DrawPos = $this->DrawCheckChar($DrawPos, BCD_DEFAULT_MAR_Y1 , $ysize, $xres); + $DrawPos = $this->DrawStop($DrawPos, BCD_DEFAULT_MAR_Y1 , $ysize, $xres); + return true; + } + } +?> diff --git a/Open-ILS/web/staff/php/barcode/c128bobject.php b/Open-ILS/web/staff/php/barcode/c128bobject.php new file mode 100755 index 0000000000..b6ec95b983 --- /dev/null +++ b/Open-ILS/web/staff/php/barcode/c128bobject.php @@ -0,0 +1,321 @@ +BarcodeObject($Width, $Height, $Style); + $this->mValue = $Value; + $this->mChars = " !\"#$%&'()*+´-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{ }~"; + $this->mCharSet = array + ( + "212222", /* 00 */ + "222122", /* 01 */ + "222221", /* 02 */ + "121223", /* 03 */ + "121322", /* 04 */ + "131222", /* 05 */ + "122213", /* 06 */ + "122312", /* 07 */ + "132212", /* 08 */ + "221213", /* 09 */ + "221312", /* 10 */ + "231212", /* 11 */ + "112232", /* 12 */ + "122132", /* 13 */ + "122231", /* 14 */ + "113222", /* 15 */ + "123122", /* 16 */ + "123221", /* 17 */ + "223211", /* 18 */ + "221132", /* 19 */ + "221231", /* 20 */ + "213212", /* 21 */ + "223112", /* 22 */ + "312131", /* 23 */ + "311222", /* 24 */ + "321122", /* 25 */ + "321221", /* 26 */ + "312212", /* 27 */ + "322112", /* 28 */ + "322211", /* 29 */ + "212123", /* 30 */ + "212321", /* 31 */ + "232121", /* 32 */ + "111323", /* 33 */ + "131123", /* 34 */ + "131321", /* 35 */ + "112313", /* 36 */ + "132113", /* 37 */ + "132311", /* 38 */ + "211313", /* 39 */ + "231113", /* 40 */ + "231311", /* 41 */ + "112133", /* 42 */ + "112331", /* 43 */ + "132131", /* 44 */ + "113123", /* 45 */ + "113321", /* 46 */ + "133121", /* 47 */ + "313121", /* 48 */ + "211331", /* 49 */ + "231131", /* 50 */ + "213113", /* 51 */ + "213311", /* 52 */ + "213131", /* 53 */ + "311123", /* 54 */ + "311321", /* 55 */ + "331121", /* 56 */ + "312113", /* 57 */ + "312311", /* 58 */ + "332111", /* 59 */ + "314111", /* 60 */ + "221411", /* 61 */ + "431111", /* 62 */ + "111224", /* 63 */ + "111422", /* 64 */ + "121124", /* 65 */ + "121421", /* 66 */ + "141122", /* 67 */ + "141221", /* 68 */ + "112214", /* 69 */ + "112412", /* 70 */ + "122114", /* 71 */ + "122411", /* 72 */ + "142112", /* 73 */ + "142211", /* 74 */ + "241211", /* 75 */ + "221114", /* 76 */ + "413111", /* 77 */ + "241112", /* 78 */ + "134111", /* 79 */ + "111242", /* 80 */ + "121142", /* 81 */ + "121241", /* 82 */ + "114212", /* 83 */ + "124112", /* 84 */ + "124211", /* 85 */ + "411212", /* 86 */ + "421112", /* 87 */ + "421211", /* 88 */ + "212141", /* 89 */ + "214121", /* 90 */ + "412121", /* 91 */ + "111143", /* 92 */ + "111341", /* 93 */ + "131141", /* 94 */ + "114113", /* 95 */ + "114311", /* 96 */ + "411113", /* 97 */ + "411311", /* 98 */ + "113141", /* 99 */ + "114131", /* 100 */ + "311141", /* 101 */ + "411131" /* 102 */ + ); + } + + function GetCharIndex ($char) { + for ($i=0;$i<95;$i++) { + if ($this->mChars[$i] == $char) + return $i; + } + return -1; + } + + function GetBarSize ($xres, $char) { + switch ($char) + { + case '1': + $cVal = BCD_C128_BAR_1; + break; + case '2': + $cVal = BCD_C128_BAR_2; + break; + case '3': + $cVal = BCD_C128_BAR_3; + break; + case '4': + $cVal = BCD_C128_BAR_4; + break; + default: + $cVal = 0; + } + return $cVal * $xres; + } + + + function GetSize($xres) { + $len = strlen($this->mValue); + + if ($len == 0) { + $this->mError = "Null value"; + __DEBUG__("GetRealSize: null barcode value"); + return false; + } + $ret = 0; + for ($i=0;$i<$len;$i++) { + if (($id = $this->GetCharIndex($this->mValue[$i])) == -1) { + $this->mError = "C128B not include the char '".$this->mValue[$i]."'"; + return false; + } else { + $cset = $this->mCharSet[$id]; + $ret += $this->GetBarSize($xres, $cset[0]); + $ret += $this->GetBarSize($xres, $cset[1]); + $ret += $this->GetBarSize($xres, $cset[2]); + $ret += $this->GetBarSize($xres, $cset[3]); + $ret += $this->GetBarSize($xres, $cset[4]); + $ret += $this->GetBarSize($xres, $cset[5]); + } + } + /* length of Check character */ + $cset = $this->GetCheckCharValue(); + for ($i=0;$i<6;$i++) { + $CheckSize += $this->GetBarSize($cset[$i], $xres); + } + + $StartSize = 2*BCD_C128_BAR_2*$xres + 3*BCD_C128_BAR_1*$xres + BCD_C128_BAR_4*$xres; + $StopSize = 2*BCD_C128_BAR_2*$xres + 3*BCD_C128_BAR_1*$xres + 2*BCD_C128_BAR_3*$xres; + + return $StartSize + $ret + $CheckSize + $StopSize; + } + + function GetCheckCharValue() + { + $len = strlen($this->mValue); + $sum = 104; // 'B' type; + for ($i=0;$i<$len;$i++) { + $sum += $this->GetCharIndex($this->mValue[$i]) * ($i+1); + } + $check = $sum % 103; + return $this->mCharSet[$check]; + } + + function DrawStart($DrawPos, $yPos, $ySize, $xres) + { /* Start code is '211214' */ + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('2', $xres) , $ySize); + $DrawPos += $this->GetBarSize('2', $xres); + $DrawPos += $this->GetBarSize('1', $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('1', $xres) , $ySize); + $DrawPos += $this->GetBarSize('1', $xres); + $DrawPos += $this->GetBarSize('2', $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('1', $xres) , $ySize); + $DrawPos += $this->GetBarSize('1', $xres); + $DrawPos += $this->GetBarSize('4', $xres); + return $DrawPos; + } + + function DrawStop($DrawPos, $yPos, $ySize, $xres) + { /* Stop code is '2331112' */ + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('2', $xres) , $ySize); + $DrawPos += $this->GetBarSize('2', $xres); + $DrawPos += $this->GetBarSize('3', $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('3', $xres) , $ySize); + $DrawPos += $this->GetBarSize('3', $xres); + $DrawPos += $this->GetBarSize('1', $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('1', $xres) , $ySize); + $DrawPos += $this->GetBarSize('1', $xres); + $DrawPos += $this->GetBarSize('1', $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('2', $xres) , $ySize); + $DrawPos += $this->GetBarSize('2', $xres); + return $DrawPos; + } + + function DrawCheckChar($DrawPos, $yPos, $ySize, $xres) + { + $cset = $this->GetCheckCharValue(); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[0], $xres) , $ySize); + $DrawPos += $this->GetBarSize($cset[0], $xres); + $DrawPos += $this->GetBarSize($cset[1], $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[2], $xres) , $ySize); + $DrawPos += $this->GetBarSize($cset[2], $xres); + $DrawPos += $this->GetBarSize($cset[3], $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[4], $xres) , $ySize); + $DrawPos += $this->GetBarSize($cset[4], $xres); + $DrawPos += $this->GetBarSize($cset[5], $xres); + return $DrawPos; + } + + function DrawObject ($xres) + { + $len = strlen($this->mValue); + if (($size = $this->GetSize($xres))==0) { + __DEBUG__("GetSize: failed"); + return false; + } + + if ($this->mStyle & BCS_ALIGN_CENTER) $sPos = (integer)(($this->mWidth - $size ) / 2); + else if ($this->mStyle & BCS_ALIGN_RIGHT) $sPos = $this->mWidth - $size; + else $sPos = 0; + + /* Total height of bar code -Bars only- */ + if ($this->mStyle & BCS_DRAW_TEXT) $ysize = $this->mHeight - BCD_DEFAULT_MAR_Y1 - BCD_DEFAULT_MAR_Y2 - $this->GetFontHeight($this->mFont); + else $ysize = $this->mHeight - BCD_DEFAULT_MAR_Y1 - BCD_DEFAULT_MAR_Y2; + + /* Draw text */ + if ($this->mStyle & BCS_DRAW_TEXT) { + if ($this->mStyle & BCS_STRETCH_TEXT) { + for ($i=0;$i<$len;$i++) { + $this->DrawChar($this->mFont, $sPos+(2*BCD_C128_BAR_2*$xres + 3*BCD_C128_BAR_1*$xres + BCD_C128_BAR_4*$xres)+($size/$len)*$i, + $ysize + BCD_DEFAULT_MAR_Y1 + BCD_DEFAULT_TEXT_OFFSET, $this->mValue[$i]); + } + } else {/* Center */ + $text_width = $this->GetFontWidth($this->mFont) * strlen($this->mValue); + $this->DrawText($this->mFont, $sPos+(($size-$text_width)/2)+(2*BCD_C128_BAR_2*$xres + 3*BCD_C128_BAR_1*$xres + BCD_C128_BAR_4*$xres), + $ysize + BCD_DEFAULT_MAR_Y1 + BCD_DEFAULT_TEXT_OFFSET, $this->mValue); + } + } + + $cPos = 0; + $DrawPos = $this->DrawStart($sPos, BCD_DEFAULT_MAR_Y1 , $ysize, $xres); + do { + $c = $this->GetCharIndex($this->mValue[$cPos]); + $cset = $this->mCharSet[$c]; + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[0], $xres) , $ysize); + $DrawPos += $this->GetBarSize($cset[0], $xres); + $DrawPos += $this->GetBarSize($cset[1], $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[2], $xres) , $ysize); + $DrawPos += $this->GetBarSize($cset[2], $xres); + $DrawPos += $this->GetBarSize($cset[3], $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[4], $xres) , $ysize); + $DrawPos += $this->GetBarSize($cset[4], $xres); + $DrawPos += $this->GetBarSize($cset[5], $xres); + $cPos++; + } while ($cPos<$len); + $DrawPos = $this->DrawCheckChar($DrawPos, BCD_DEFAULT_MAR_Y1 , $ysize, $xres); + $DrawPos = $this->DrawStop($DrawPos, BCD_DEFAULT_MAR_Y1 , $ysize, $xres); + return true; + } + } +?> diff --git a/Open-ILS/web/staff/php/barcode/c128cobject.php b/Open-ILS/web/staff/php/barcode/c128cobject.php new file mode 100755 index 0000000000..dc602cd3ab --- /dev/null +++ b/Open-ILS/web/staff/php/barcode/c128cobject.php @@ -0,0 +1,344 @@ +BarcodeObject($Width, $Height, $Style); + $this->mValue = $Value; + $this->mChars = array + ( + "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", + "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", + "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", + "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", + "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", + "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", + "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", + "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", + "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", + "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", + ); + $this->mCharSet = array + ( + "212222", /* 00 */ + "222122", /* 01 */ + "222221", /* 02 */ + "121223", /* 03 */ + "121322", /* 04 */ + "131222", /* 05 */ + "122213", /* 06 */ + "122312", /* 07 */ + "132212", /* 08 */ + "221213", /* 09 */ + "221312", /* 10 */ + "231212", /* 11 */ + "112232", /* 12 */ + "122132", /* 13 */ + "122231", /* 14 */ + "113222", /* 15 */ + "123122", /* 16 */ + "123221", /* 17 */ + "223211", /* 18 */ + "221132", /* 19 */ + "221231", /* 20 */ + "213212", /* 21 */ + "223112", /* 22 */ + "312131", /* 23 */ + "311222", /* 24 */ + "321122", /* 25 */ + "321221", /* 26 */ + "312212", /* 27 */ + "322112", /* 28 */ + "322211", /* 29 */ + "212123", /* 30 */ + "212321", /* 31 */ + "232121", /* 32 */ + "111323", /* 33 */ + "131123", /* 34 */ + "131321", /* 35 */ + "112313", /* 36 */ + "132113", /* 37 */ + "132311", /* 38 */ + "211313", /* 39 */ + "231113", /* 40 */ + "231311", /* 41 */ + "112133", /* 42 */ + "112331", /* 43 */ + "132131", /* 44 */ + "113123", /* 45 */ + "113321", /* 46 */ + "133121", /* 47 */ + "313121", /* 48 */ + "211331", /* 49 */ + "231131", /* 50 */ + "213113", /* 51 */ + "213311", /* 52 */ + "213131", /* 53 */ + "311123", /* 54 */ + "311321", /* 55 */ + "331121", /* 56 */ + "312113", /* 57 */ + "312311", /* 58 */ + "332111", /* 59 */ + "314111", /* 60 */ + "221411", /* 61 */ + "431111", /* 62 */ + "111224", /* 63 */ + "111422", /* 64 */ + "121124", /* 65 */ + "121421", /* 66 */ + "141122", /* 67 */ + "141221", /* 68 */ + "112214", /* 69 */ + "112412", /* 70 */ + "122114", /* 71 */ + "122411", /* 72 */ + "142112", /* 73 */ + "142211", /* 74 */ + "241211", /* 75 */ + "221114", /* 76 */ + "413111", /* 77 */ + "241112", /* 78 */ + "134111", /* 79 */ + "111242", /* 80 */ + "121142", /* 81 */ + "121241", /* 82 */ + "114212", /* 83 */ + "124112", /* 84 */ + "124211", /* 85 */ + "411212", /* 86 */ + "421112", /* 87 */ + "421211", /* 88 */ + "212141", /* 89 */ + "214121", /* 90 */ + "412121", /* 91 */ + "111143", /* 92 */ + "111341", /* 93 */ + "131141", /* 94 */ + "114113", /* 95 */ + "114311", /* 96 */ + "411113", /* 97 */ + "411311", /* 98 */ + "113141", /* 99 */ + ); + } + + function GetCharIndex ($char) { + for ($i=0;$i<100;$i++) { + if ($this->mChars[$i] == $char) + return $i; + } + return -1; + } + + function GetBarSize ($xres, $char) { + switch ($char) + { + case '1': + $cVal = BCD_C128_BAR_1; + break; + case '2': + $cVal = BCD_C128_BAR_2; + break; + case '3': + $cVal = BCD_C128_BAR_3; + break; + case '4': + $cVal = BCD_C128_BAR_4; + break; + default: + $cVal = 0; + } + return $cVal * $xres; + } + + + function GetSize($xres) { + $len = strlen($this->mValue); + + if ($len == 0) { + $this->mError = "Null value"; + __DEBUG__("GetRealSize: null barcode value"); + return false; + } + $ret = 0; + + for ($i=0;$i<$len;$i++) { + if ((ord($this->mValue[$i])<48) || (ord($this->mValue[$i])>57)) { + $this->mError = "Code-128C is numeric only"; + return false; + } + } + + if (($len%2) != 0) { + $this->mError = "The length of barcode value must be even. You must pad the number with zeros."; + __DEBUG__("GetSize: failed C128-C requiremente"); + return false; + } + + for ($i=0;$i<$len;$i+=2) { + $id = $this->GetCharIndex($this->mValue[$i].$this->mValue[$i+1]); + $cset = $this->mCharSet[$id]; + $ret += $this->GetBarSize($xres, $cset[0]); + $ret += $this->GetBarSize($xres, $cset[1]); + $ret += $this->GetBarSize($xres, $cset[2]); + $ret += $this->GetBarSize($xres, $cset[3]); + $ret += $this->GetBarSize($xres, $cset[4]); + $ret += $this->GetBarSize($xres, $cset[5]); + } + /* length of Check character */ + $cset = $this->GetCheckCharValue(); + for ($i=0;$i<6;$i++) { + $CheckSize += $this->GetBarSize($cset[$i], $xres); + } + + $StartSize = 2*BCD_C128_BAR_2*$xres + 3*BCD_C128_BAR_1*$xres + BCD_C128_BAR_4*$xres; + $StopSize = 2*BCD_C128_BAR_2*$xres + 3*BCD_C128_BAR_1*$xres + 2*BCD_C128_BAR_3*$xres; + return $StartSize + $ret + $CheckSize + $StopSize; + } + + function GetCheckCharValue() + { + $len = strlen($this->mValue); + $sum = 105; // 'C' type; + $m = 0; + for ($i=0;$i<$len;$i+=2) { + $m++; + $sum += $this->GetCharIndex($this->mValue[$i].$this->mValue[$i+1]) * $m; + } + $check = $sum % 103; + return $this->mCharSet[$check]; + } + + function DrawStart($DrawPos, $yPos, $ySize, $xres) + { /* Start code is '211232' */ + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('2', $xres) , $ySize); + $DrawPos += $this->GetBarSize('2', $xres); + $DrawPos += $this->GetBarSize('1', $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('1', $xres) , $ySize); + $DrawPos += $this->GetBarSize('1', $xres); + $DrawPos += $this->GetBarSize('2', $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('3', $xres) , $ySize); + $DrawPos += $this->GetBarSize('3', $xres); + $DrawPos += $this->GetBarSize('2', $xres); + return $DrawPos; + } + + function DrawStop($DrawPos, $yPos, $ySize, $xres) + { /* Stop code is '2331112' */ + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('2', $xres) , $ySize); + $DrawPos += $this->GetBarSize('2', $xres); + $DrawPos += $this->GetBarSize('3', $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('3', $xres) , $ySize); + $DrawPos += $this->GetBarSize('3', $xres); + $DrawPos += $this->GetBarSize('1', $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('1', $xres) , $ySize); + $DrawPos += $this->GetBarSize('1', $xres); + $DrawPos += $this->GetBarSize('1', $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize('2', $xres) , $ySize); + $DrawPos += $this->GetBarSize('2', $xres); + return $DrawPos; + } + + function DrawCheckChar($DrawPos, $yPos, $ySize, $xres) + { + $cset = $this->GetCheckCharValue(); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[0], $xres) , $ySize); + $DrawPos += $this->GetBarSize($cset[0], $xres); + $DrawPos += $this->GetBarSize($cset[1], $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[2], $xres) , $ySize); + $DrawPos += $this->GetBarSize($cset[2], $xres); + $DrawPos += $this->GetBarSize($cset[3], $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[4], $xres) , $ySize); + $DrawPos += $this->GetBarSize($cset[4], $xres); + $DrawPos += $this->GetBarSize($cset[5], $xres); + return $DrawPos; + } + + function DrawObject ($xres) + { + $len = strlen($this->mValue); + if (($size = $this->GetSize($xres))==0) { + __DEBUG__("GetSize: failed"); + return false; + } + + if ($this->mStyle & BCS_ALIGN_CENTER) $sPos = (integer)(($this->mWidth - $size ) / 2); + else if ($this->mStyle & BCS_ALIGN_RIGHT) $sPos = $this->mWidth - $size; + else $sPos = 0; + + /* Total height of bar code -Bars only- */ + if ($this->mStyle & BCS_DRAW_TEXT) $ysize = $this->mHeight - BCD_DEFAULT_MAR_Y1 - BCD_DEFAULT_MAR_Y2 - $this->GetFontHeight($this->mFont); + else $ysize = $this->mHeight - BCD_DEFAULT_MAR_Y1 - BCD_DEFAULT_MAR_Y2; + + /* Draw text */ + if ($this->mStyle & BCS_DRAW_TEXT) { + if ($this->mStyle & BCS_STRETCH_TEXT) { + for ($i=0;$i<$len;$i++) { + $this->DrawChar($this->mFont, $sPos+(2*BCD_C128_BAR_2*$xres + 3*BCD_C128_BAR_1*$xres + BCD_C128_BAR_4*$xres)+($size/$len)*$i, + $ysize + BCD_DEFAULT_MAR_Y1 + BCD_DEFAULT_TEXT_OFFSET, $this->mValue[$i]); + } + } else {/* Center */ + $text_width = $this->GetFontWidth($this->mFont) * strlen($this->mValue); + $this->DrawText($this->mFont, $sPos+(($size-$text_width)/2)+(2*BCD_C128_BAR_2*$xres + 3*BCD_C128_BAR_1*$xres + BCD_C128_BAR_4*$xres), + $ysize + BCD_DEFAULT_MAR_Y1 + BCD_DEFAULT_TEXT_OFFSET, $this->mValue); + } + } + + $cPos = 0; + $DrawPos = $this->DrawStart($sPos, BCD_DEFAULT_MAR_Y1 , $ysize, $xres); + do { + $c = $this->GetCharIndex($this->mValue[$cPos].$this->mValue[$cPos+1]); + $cset = $this->mCharSet[$c]; + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[0], $xres) , $ysize); + $DrawPos += $this->GetBarSize($cset[0], $xres); + $DrawPos += $this->GetBarSize($cset[1], $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[2], $xres) , $ysize); + $DrawPos += $this->GetBarSize($cset[2], $xres); + $DrawPos += $this->GetBarSize($cset[3], $xres); + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, $this->GetBarSize($cset[4], $xres) , $ysize); + $DrawPos += $this->GetBarSize($cset[4], $xres); + $DrawPos += $this->GetBarSize($cset[5], $xres); + $cPos += 2; + } while ($cPos<$len); + $DrawPos = $this->DrawCheckChar($DrawPos, BCD_DEFAULT_MAR_Y1 , $ysize, $xres); + $DrawPos = $this->DrawStop($DrawPos, BCD_DEFAULT_MAR_Y1 , $ysize, $xres); + return true; + } + } +?> diff --git a/Open-ILS/web/staff/php/barcode/c39object.php b/Open-ILS/web/staff/php/barcode/c39object.php new file mode 100755 index 0000000000..726f656fc3 --- /dev/null +++ b/Open-ILS/web/staff/php/barcode/c39object.php @@ -0,0 +1,228 @@ +BarcodeObject($Width, $Height, $Style); + $this->mValue = $Value; + $this->mChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. *$/+%"; + $this->mCharSet = array + ( + /* 0 */ "000110100", + /* 1 */ "100100001", + /* 2 */ "001100001", + /* 3 */ "101100000", + /* 4 */ "000110001", + /* 5 */ "100110000", + /* 6 */ "001110000", + /* 7 */ "000100101", + /* 8 */ "100100100", + /* 9 */ "001100100", + /* A */ "100001001", + /* B */ "001001001", + /* C */ "101001000", + /* D */ "000011001", + /* E */ "100011000", + /* F */ "001011000", + /* G */ "000001101", + /* H */ "100001100", + /* I */ "001001100", + /* J */ "000011100", + /* K */ "100000011", + /* L */ "001000011", + /* M */ "101000010", + /* N */ "000010011", + /* O */ "100010010", + /* P */ "001010010", + /* Q */ "000000111", + /* R */ "100000110", + /* S */ "001000110", + /* T */ "000010110", + /* U */ "110000001", + /* V */ "011000001", + /* W */ "111000000", + /* X */ "010010001", + /* Y */ "110010000", + /* Z */ "011010000", + /* - */ "010000101", + /* . */ "110000100", + /* SP */ "011000100", + /* * */ "010010100", + /* $ */ "010101000", + /* / */ "010100010", + /* + */ "010001010", + /* % */ "000101010" + ); + } + + function GetCharIndex ($char) + { + for ($i=0;$i<44;$i++) { + if ($this->mChars[$i] == $char) + return $i; + } + return -1; + } + + function GetSize($xres) + { + $len = strlen($this->mValue); + + if ($len == 0) { + $this->mError = "Null value"; + __DEBUG__("GetRealSize: null barcode value"); + return false; + } + + for ($i=0;$i<$len;$i++) { + if ($this->GetCharIndex($this->mValue[$i]) == -1 || $this->mValue[$i] == '*') { + /* The asterisk is only used as a start and stop code */ + $this->mError = "C39 not include the char '".$this->mValue[$i]."'"; + return false; + } + } + + /* Start, Stop is 010010100 == '*' */ + $StartSize = BCD_C39_NARROW_BAR * $xres * 6 + BCD_C39_WIDE_BAR * $xres * 3; + $StopSize = BCD_C39_NARROW_BAR * $xres * 6 + BCD_C39_WIDE_BAR * $xres * 3; + $CharSize = BCD_C39_NARROW_BAR * $xres * 6 + BCD_C39_WIDE_BAR * $xres * 3; /* Same for all chars */ + + return $CharSize * $len + $StarSize + $StopSize + /* Space between chars */ BCD_C39_NARROW_BAR * $xres * ($len-1); + } + + function DrawStart($DrawPos, $yPos, $ySize, $xres) + { /* Start code is '*' */ + $narrow = BCD_C39_NARROW_BAR * $xres; + $wide = BCD_C39_WIDE_BAR * $xres; + $this->DrawSingleBar($DrawPos, $yPos, $narrow , $ySize); + $DrawPos += $narrow; + $DrawPos += $wide; + $this->DrawSingleBar($DrawPos, $yPos, $narrow , $ySize); + $DrawPos += $narrow; + $DrawPos += $narrow; + $this->DrawSingleBar($DrawPos, $yPos, $wide , $ySize); + $DrawPos += $wide; + $DrawPos += $narrow; + $this->DrawSingleBar($DrawPos, $yPos, $wide , $ySize); + $DrawPos += $wide; + $DrawPos += $narrow; + $this->DrawSingleBar($DrawPos, $yPos, $narrow, $ySize); + $DrawPos += $narrow; + $DrawPos += $narrow; /* Space between chars */ + return $DrawPos; + } + + function DrawStop($DrawPos, $yPos, $ySize, $xres) + { /* Stop code is '*' */ + $narrow = BCD_C39_NARROW_BAR * $xres; + $wide = BCD_C39_WIDE_BAR * $xres; + $this->DrawSingleBar($DrawPos, $yPos, $narrow , $ySize); + $DrawPos += $narrow; + $DrawPos += $wide; + $this->DrawSingleBar($DrawPos, $yPos, $narrow , $ySize); + $DrawPos += $narrow; + $DrawPos += $narrow; + $this->DrawSingleBar($DrawPos, $yPos, $wide , $ySize); + $DrawPos += $wide; + $DrawPos += $narrow; + $this->DrawSingleBar($DrawPos, $yPos, $wide , $ySize); + $DrawPos += $wide; + $DrawPos += $narrow; + $this->DrawSingleBar($DrawPos, $yPos, $narrow, $ySize); + $DrawPos += $narrow; + return $DrawPos; + } + + function DrawObject ($xres) + { + $len = strlen($this->mValue); + + $narrow = BCD_C39_NARROW_BAR * $xres; + $wide = BCD_C39_WIDE_BAR * $xres; + + if (($size = $this->GetSize($xres))==0) { + __DEBUG__("GetSize: failed"); + return false; + } + + $cPos = 0; + if ($this->mStyle & BCS_ALIGN_CENTER) $sPos = (integer)(($this->mWidth - $size ) / 2); + else if ($this->mStyle & BCS_ALIGN_RIGHT) $sPos = $this->mWidth - $size; + else $sPos = 0; + + /* Total height of bar code -Bars only- */ + if ($this->mStyle & BCS_DRAW_TEXT) $ysize = $this->mHeight - BCD_DEFAULT_MAR_Y1 - BCD_DEFAULT_MAR_Y2 - $this->GetFontHeight($this->mFont); + else $ysize = $this->mHeight - BCD_DEFAULT_MAR_Y1 - BCD_DEFAULT_MAR_Y2; + + /* Draw text */ + if ($this->mStyle & BCS_DRAW_TEXT) { + if ($this->mStyle & BCS_STRETCH_TEXT) { + for ($i=0;$i<$len;$i++) { + $this->DrawChar($this->mFont, $sPos+($narrow*6+$wide*3)+($size/$len)*$i, + $ysize + BCD_DEFAULT_MAR_Y1 + BCD_DEFAULT_TEXT_OFFSET, $this->mValue[$i]); + } + } else {/* Center */ + $text_width = $this->GetFontWidth($this->mFont) * strlen($this->mValue); + $this->DrawText($this->mFont, $sPos+(($size-$text_width)/2)+($narrow*6+$wide*3), + $ysize + BCD_DEFAULT_MAR_Y1 + BCD_DEFAULT_TEXT_OFFSET, $this->mValue); + } + } + + $DrawPos = $this->DrawStart($sPos, BCD_DEFAULT_MAR_Y1 , $ysize, $xres); + do { + $c = $this->GetCharIndex($this->mValue[$cPos]); + $cset = $this->mCharSet[$c]; + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, ($cset[0] == '0') ? $narrow : $wide , $ysize); + $DrawPos += ($cset[0] == '0') ? $narrow : $wide; + $DrawPos += ($cset[1] == '0') ? $narrow : $wide; + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, ($cset[2] == '0') ? $narrow : $wide , $ysize); + $DrawPos += ($cset[2] == '0') ? $narrow : $wide; + $DrawPos += ($cset[3] == '0') ? $narrow : $wide; + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, ($cset[4] == '0') ? $narrow : $wide , $ysize); + $DrawPos += ($cset[4] == '0') ? $narrow : $wide; + $DrawPos += ($cset[5] == '0') ? $narrow : $wide; + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, ($cset[6] == '0') ? $narrow : $wide , $ysize); + $DrawPos += ($cset[6] == '0') ? $narrow : $wide; + $DrawPos += ($cset[7] == '0') ? $narrow : $wide; + $this->DrawSingleBar($DrawPos, BCD_DEFAULT_MAR_Y1, ($cset[8] == '0') ? $narrow : $wide , $ysize); + $DrawPos += ($cset[8] == '0') ? $narrow : $wide; + $DrawPos += $narrow; /* Space between chars */ + $cPos++; + } while ($cPos<$len); + $DrawPos = $this->DrawStop($DrawPos, BCD_DEFAULT_MAR_Y1 , $ysize, $xres); + return true; + } + } +?> diff --git a/Open-ILS/web/staff/php/barcode/debug.php b/Open-ILS/web/staff/php/barcode/debug.php new file mode 100755 index 0000000000..3337a9ccc3 --- /dev/null +++ b/Open-ILS/web/staff/php/barcode/debug.php @@ -0,0 +1,64 @@ + + + Barcode Download + + + + + + + + +
+

+ + + + + + + + + + +
This is alpha release, report any problem to barcode@mribti.com

Main site
barcode-0.0.8a.tar.gz(45kb)
barcode-0.0.8a.zip(47kb)
Mirror site (fast)
barcode-0.0.8a.tar.gz(45kb)
barcode-0.0.8a.zip(47kb)

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CHANGES LOG

2001-03-25v0.0.1aInitial release.
2001-03-26v0.0.2aError checking has been added, and there are minor bugfixes
2001-03-26v0.0.3aAdd Code 39 support
2001-03-27v0.0.4aMinor feature enhancements, new output styles.
2001-03-28v0.0.5aAdd font control, minor bugfixes.
2001-03-29v0.0.6aBugfix in Code 39 render, thanks to Henry Bland.
2001-04-01v0.0.7aAdd support for Code 128-A and Code 128-B
2001-08-03v0.0.8aNow support Code 128-C, thanks to Sam Michaels.
+ + diff --git a/Open-ILS/web/staff/php/barcode/download.png b/Open-ILS/web/staff/php/barcode/download.png new file mode 100755 index 0000000000000000000000000000000000000000..ace23c411f51d5010d6b808db1dd7b7ece4f368f GIT binary patch literal 4859 zcmV45Ab;p12y?L|GkTb*KIwB=%Uo6{E>`Jv$uMJ{Vahe*A zEu$#9pa$9^K${ru-l9m6e5joUX^Z*Yt?=I(_d;j+=cX^U!ow4|T`aesyTP?uNkY!mIhKXgG zfB_hohK^;LSf++$Sy+~aWEp_8qV5V_NFb?zjI7v@WEn|P#D7TvNk&l=WVw;gPYT_3 z02_vhX=vzL0m;l`lk=E*21CnWle4ID5y?y;=_wR5gCy&K3RVUvfmr~l0OSNit60Z< z9K&dAPd7l0r zvDp+>PN-W=(ycPlx~^mDc`(wTFQHfo6qdk9fhB>ZgSiZ{A0!uGf~A#fYhb0oN&^M3 z;$WS|Fa%%#h5?4z*yc)bp;4n{86-)h$+Cb-k_4YhI)G_{rbDR|MpCu{VRSQuW$QzA zhOs$9*c<^=hYyF-gKD=UNw)_0j|JdTsf1)^u(Skr76gc9QH&H4NM4|fhLsb5nK7_t zF^#t=X;9KY(*%HqA*vdN81%{m(5f?5g>{FXT9yE@LJj}RV9T<2D*5&Al=Cf1$W|(W zW*kCRM?i_-@C+h5`f+=Eusi*@+^&xW^i2U+D;2S{1gbfMWF*1V#3)NXp^a8iXrP$@ z^D>t48o4|aOEn-(Q#>nys~?ie_;du08o*6?s{me4<|?BkmjRdL<}}L^R>06f(?>zx zgY4Xa!@Cu4_ZB?XGTQ*VTD`U8w*$OYvQ$o&5TKh@O*UNzorkfx4&V*!#2*~N z*A>FybaWPAjdarho2H3gNMJW6%H~!AsY^tp<}w)9K>q=`+-b6TC=_ZYRgxNgS}R%) z0G+gU)w-P~%xW@q&TcGQmapQ^D{e+BMKEng@Q3fFXJ8M3o<1Con+N-b0WKAD*z{}2 z`XZQikOP1W<^pJAXvM!}ISu)IEvTsgTJ4HV2Cd0k&y@BtOW$d!J(e!Mkuc)aH8i z02_u$ap^tW%z+XSn-1E$#9}AOW^audhT6d1Yyh>EGMnvd*CInl9_6}{Bz*K@0;cDL z3D-2(y7zav|KZ2*`@IeIssL-6hPnI}4sxLMf*D88{Sni1HOB24wk$}^MaZY@xcns) zdl64Cwx&+42iW!Uht9y78pN-T(z9!#Gn000-vF@j%cB4|d?f(9ebE}97WE}b_~uI! zkX#mNp@CoH(~mzzI2^2%v#c|gVVIN>r*KJgAoqZ|NOt+F#Nw^Bwz|8tqT$43gt4#p zQ94yi>-nt$09_+Gj(|qbXc~tncH07*nTrs=4jD-y7@9tX2DK2%_rA z(BSMh4w8RenA!W@{E^_(frliCNc;RaOwWQ%g~;e{a_osG>FV+U<Ts#%1rY*kY6u-4n?0ebA;2o{CVm+G_Ysqg1tf@ib+9=n) z>m~Pk&6hd1+_>Ys8DFcY;N4|Z0bWZ#Eel*u*u5Q68Hg>y__=?kXJD9zAAJy2wILUa zMa;|(KnjDo2wEe!l|{I@%8ab!*<6k3hyH24s>K!h@Yw(}=SCarQ*NV!pIxo})dHwa z=2%i8v~!{fQ17tbzS7DzjP(Ulo=AR8X4<&TZn|Fk-y(AKzm-DtxOgpN`?T z{X3Y4z{(LzG@9TW4V--8Qp3BV!rsp>@INmNH>6+wy8r`!DFB^1J<7rvFR5>f+1vkI zfyj|%{QXf1nJ}ptAG1HK%^WSQ5dLI_zP)J_HHM->F&k#)$3bH6ILN&w__&X0?07nX zUbHj!Ll=5lK~KT{U!P_2zk6AD(TRE(20oi%43I>i_7OQQ~L3q)rO{yD?8J3cW9YGr=?>=uESrk~={_+^GihLCgFC1kk=%nvZM?@=t)fNjOFQyzfBS8RCq zgrYb+v8HmRQ;>;=x%9?vF8_7_cfY}bSCatDz2M}`6Me)dBRD)U{DW!YC!EzM^=*|c zhbFMQW6Yc#B{?O^*<4x`T!6^Y4Bf*~dUgtTkUFVi^TYdJ8)o4};X1TaF!hIC7B7xA z$q3LL5fgUejon=RQUK?G!QNMdoC_~HdG8B-Bqt-{#7Kh+uMCiUS@6!ku5$T{0hX`S zCTI6>6hng#{-##W7S_1_$6l_#+0}YbSzXa=3iS4fcUgv+=_rO_AX^sb+S?S1W8@3# zU+UQ1jbJLOh_GAH%;_N4|IEvQSCj1f{CT1}5tzwyR$cRx4A?w6BQ!0TzP`wC)B+?*%d>>iTouvky&jc2SYpLUI} z%=bL5Reuc)KKgF3xiYYPMVykvK5#L4dX)FSIna==or3hd(2nq-OhcZb!AGwOzVQ=I z3YjqR6SV4ZBb2vtsbn!*KVPgjT)B!79kXJ{rV&eB!5np5c)p0A!Hp8QhI-e?lD07sY;a{ROpE zO$BvJDH9ZDv%jp6dKeCTD@kxT%J6-Oj(kVHnn9_UB5q7)wKFH zx`(3;X%#EtyR(372iaa*d_~9^+TVJa<2u?n9{mR=@EvbI zMy;ro$(Cht#cAwAxOQE<_eGxMfkz&|?G_V82C&;zw(NQkO?i%DNz8~;M$CxScAQqO zU6WH0md2X{me6k$*mEMu;jjIOa5<`Vcw*dna=r;55a4dBTe_GQI0Cgn(H2#TnJ}(E zjBUS^Y^duRZ5+IOB3%8SKI;@Wib^%D?t4_&V6z0-Xrt_hwNY_Q_g_NB_3Y>nbd&qO=M)8){ z=k8z>*)Bj|p7vGKEvsC289d<=_3mgRInU|Sk{izVRVrO_&%?%^m) zQ$jwQqsFVg|GA|wX0g^_Dr#r&&_vrkXv>0F9Ns@)^SeV^pW%t)$BArdj76G`3w7P# z%J^BP$G?MQy@6?f-41~+aFr+bW}fQ6>L)v}{-ZnCm{XnxejC*PcECy!WO8ujYR%_L za)cuf{VESV@*shrzqww!1OLQgjEm>qWMS%Cs5UW6J?=8lPO;{?kpWukAbyYevUlIEwl_OEUfI!gotdd?T>a?3Se*F|k`zOh z1<=86@Of&7|LxYvtI=8jg^d9fHV)X0SWdPpSJ76}6tU3MG+dn&$AYp9p}u3>_t0lJ zc-I~H{he1LYr1q-D(TEdCz-hLF0+&Wie-)?$>4SgjqdV;+X;%THh}9rV6V{a25gnz z25W5n;Z80u4)m{2i@oK1`K)Ge+mjr*|6%s+-$PfoZ%uOE(6w2^Fj<(JV{+m=(-ZHq zocKPL2{xMu!~)&Wrn3;o_c}Qo(&Z6Ic zm^}yY=iuFUv2}C=kEi{XYRfim_TqaslVf)JI@d0rCwlD+nWZ-XP*w1{#SvDZ8@wKH zIKWotA}V{QE9rLCR#?Brt~`Ft7a)^`#1hOdKs+fx)^t!^huF2}lkB_WE_UwPPN*kv z)1S3$$fqmmREGJvS*E8Zn3=r5;@lX8!WcHC47f)CUio!9LA8TY-|5N` z>2#WSEKWQ&&)m!u3v<)N7bhuXPGPeN;A9D$PVl${_-?0oce}*0OjT>(+mz-n`JncZ z*LtYl3RGhUv-a7lt_w4oFF-CQWDSSJq^;De@+$wIUxar~3m<#Web4j2qd!Aemw!_L zT}hk%g}=k0;&24%=?TK#omkfYwsST;O$AXHetJ9YZ3``-1;vOVF5H+vG#KMTR%+Bl z^xIq%uUvLuzA3Z`jNEELIYZBfkSDHSsm^C{L-XD zCd3An&*2llswGE;;mRbu{s4u1mQW}}e}AakTd&qXPEA;(w5e3;)N56$)fz_GWXCLH z8YX4KpkkKUF%0T8lX|_1teI452GWY-YkLmuq~=hmw9f0dIs&*3AVvx3QG%f;x*ou< zhY9vY2uH$1A_GJQ2GIQ)h14f<=~;XtM=+pnb0n+|Q+o&mDk1Q80Vb!OaOd7F{Ql0_ zw!^})BKROUj*H_s*tU(dY^0P(X=7OywzO2?e#2sSSIyFrdw!s<>!R%|71cBkfnWfy z*NYGWpAcwXfv)=zzUC}MbEt+a?Fv83l4R0p)|MApU!KRY=ZN&fP)zw3UTtrfnD`D0 zOB39me#C`~=exdV{}sW<9oyR_lB+8u;_oSCUl1MKAleKSTTY|93-PazF&;2IGea!a zxen_v&?C4Duq;V7lV&x(NHQ@`pMQ>#6UuS)cvKGe^B0JXJ>&M>>jx?X{{}sRyNuFy znYF|U8{gjH)@C_5lp_>Wj@;tX1edNoW_KHy?J*RQCuz0u%}*HrL`9xi`qlbNtb#T)ukaw^jRpf*!#~h + + Barcode home page + + + + + + + + +
+

+ + + + + +
Barcode is a small implementation of a barcode rendering class using the PHP language and GD graphics library.


For any question, please send an email to barcode@mribti.com
+

+ + + + + + + +
SourceForge Logo
+ + diff --git a/Open-ILS/web/staff/php/barcode/home.png b/Open-ILS/web/staff/php/barcode/home.png new file mode 100755 index 0000000000000000000000000000000000000000..2f2fcbede261430750f1236f7568e7c47672b205 GIT binary patch literal 4238 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, $Height, $Style); + $this->mValue = $Value; + $this->mCharSet = array + ( + /* 0 */ "00110", + /* 1 */ "10001", + /* 2 */ "01001", + /* 3 */ "11000", + /* 4 */ "00101", + /* 5 */ "10100", + /* 6 */ "01100", + /* 7 */ "00011", + /* 8 */ "10010", + /* 9 */ "01010" + ); + } + + function GetSize($xres) + { + $len = strlen($this->mValue); + + if ($len == 0) { + $this->mError = "Null value"; + __DEBUG__("GetRealSize: null barcode value"); + return false; + } + + for ($i=0;$i<$len;$i++) { + if ((ord($this->mValue[$i])<48) || (ord($this->mValue[$i])>57)) { + $this->mError = "I25 is numeric only"; + return false; + } + } + + if (($len%2) != 0) { + $this->mError = "The length of barcode value must be even"; + __DEBUG__("GetSize: failed I25 requiremente"); + return false; + } + $StartSize = BCD_I25_NARROW_BAR * 4 * $xres; + $StopSize = BCD_I25_WIDE_BAR * $xres + 2 * BCD_I25_NARROW_BAR * $xres; + $cPos = 0; + $sPos = 0; + do { + $c1 = $this->mValue[$cPos]; + $c2 = $this->mValue[$cPos+1]; + $cset1 = $this->mCharSet[$c1]; + $cset2 = $this->mCharSet[$c2]; + + for ($i=0;$i<5;$i++) { + $type1 = ($cset1[$i]==0) ? (BCD_I25_NARROW_BAR * $xres) : (BCD_I25_WIDE_BAR * $xres); + $type2 = ($cset2[$i]==0) ? (BCD_I25_NARROW_BAR * $xres) : (BCD_I25_WIDE_BAR * $xres); + $sPos += ($type1 + $type2); + } + $cPos+=2; + } while ($cPos<$len); + + return $sPos + $StarSize + $StopSize; + } + + function DrawStart($DrawPos, $yPos, $ySize, $xres) + { /* Start code is "0000" */ + $this->DrawSingleBar($DrawPos, $yPos, BCD_I25_NARROW_BAR * $xres , $ySize); + $DrawPos += BCD_I25_NARROW_BAR * $xres; + $DrawPos += BCD_I25_NARROW_BAR * $xres; + $this->DrawSingleBar($DrawPos, $yPos, BCD_I25_NARROW_BAR * $xres , $ySize); + $DrawPos += BCD_I25_NARROW_BAR * $xres; + $DrawPos += BCD_I25_NARROW_BAR * $xres; + return $DrawPos; + } + + function DrawStop($DrawPos, $yPos, $ySize, $xres) + { /* Stop code is "100" */ + $this->DrawSingleBar($DrawPos, $yPos, BCD_I25_WIDE_BAR * $xres , $ySize); + $DrawPos += BCD_I25_WIDE_BAR * $xres; + $DrawPos += BCD_I25_NARROW_BAR * $xres; + $this->DrawSingleBar($DrawPos, $yPos, BCD_I25_NARROW_BAR * $xres , $ySize); + $DrawPos += BCD_I25_NARROW_BAR * $xres; + return $DrawPos; + } + + function DrawObject ($xres) + { + $len = strlen($this->mValue); + + if (($size = $this->GetSize($xres))==0) { + __DEBUG__("GetSize: failed"); + return false; + } + + $cPos = 0; + + if ($this->mStyle & BCS_DRAW_TEXT) $ysize = $this->mHeight - BCD_DEFAULT_MAR_Y1 - BCD_DEFAULT_MAR_Y2 - $this->GetFontHeight($this->mFont); + else $ysize = $this->mHeight - BCD_DEFAULT_MAR_Y1 - BCD_DEFAULT_MAR_Y2; + + if ($this->mStyle & BCS_ALIGN_CENTER) $sPos = (integer)(($this->mWidth - $size ) / 2); + else if ($this->mStyle & BCS_ALIGN_RIGHT) $sPos = $this->mWidth - $size; + else $sPos = 0; + + if ($this->mStyle & BCS_DRAW_TEXT) { + if ($this->mStyle & BCS_STRETCH_TEXT) { + /* Stretch */ + for ($i=0;$i<$len;$i++) { + $this->DrawChar($this->mFont, $sPos+BCD_I25_NARROW_BAR*4*$xres+($size/$len)*$i, + $ysize + BCD_DEFAULT_MAR_Y1 + BCD_DEFAULT_TEXT_OFFSET , $this->mValue[$i]); + } + }else {/* Center */ + $text_width = $this->GetFontWidth($this->mFont) * strlen($this->mValue); + $this->DrawText($this->mFont, $sPos+(($size-$text_width)/2)+(BCD_I25_NARROW_BAR*4*$xres), + $ysize + BCD_DEFAULT_MAR_Y1 + BCD_DEFAULT_TEXT_OFFSET, $this->mValue); + } + } + + $sPos = $this->DrawStart($sPos, BCD_DEFAULT_MAR_Y1, $ysize, $xres); + do { + $c1 = $this->mValue[$cPos]; + $c2 = $this->mValue[$cPos+1]; + $cset1 = $this->mCharSet[$c1]; + $cset2 = $this->mCharSet[$c2]; + + for ($i=0;$i<5;$i++) { + $type1 = ($cset1[$i]==0) ? (BCD_I25_NARROW_BAR * $xres) : (BCD_I25_WIDE_BAR * $xres); + $type2 = ($cset2[$i]==0) ? (BCD_I25_NARROW_BAR * $xres) : (BCD_I25_WIDE_BAR * $xres); + $this->DrawSingleBar($sPos, BCD_DEFAULT_MAR_Y1, $type1 , $ysize); + $sPos += ($type1 + $type2); + } + $cPos+=2; + } while ($cPos<$len); + $sPos = $this->DrawStop($sPos, BCD_DEFAULT_MAR_Y1, $ysize, $xres); + return true; + } + } +?> diff --git a/Open-ILS/web/staff/php/barcode/image.php b/Open-ILS/web/staff/php/barcode/image.php new file mode 100755 index 0000000000..028419527a --- /dev/null +++ b/Open-ILS/web/staff/php/barcode/image.php @@ -0,0 +1,73 @@ +SetFont($font); + $obj->DrawObject($xres); + $obj->FlushObject(); + $obj->DestroyObject(); + unset($obj); /* clean */ + } +?> diff --git a/Open-ILS/web/staff/php/barcode/image.png b/Open-ILS/web/staff/php/barcode/image.png new file mode 100755 index 0000000000000000000000000000000000000000..36c4fabea25f98f84287bc1dc8d24216ca475550 GIT binary patch literal 450 zcmeAS@N?(olHy`uVBq!ia0vp^zks-ckr_xnjH+D=q}T#{LR|m<{|{t_9=coyq~3YD zIEGZrd3)WOi^))c?SVZ@lCXTL)?TM0ZUHVIlr~1LPf(N+*mEo{RqQtF`VHeg%x!%$Cuh@@9KV(P)Bm1{Tg1S~#KIw<;LyOp zc%O0oSK6}Wppij!HKQ^L+w^D0e#Nk z>FVdQ&MBeECaK7d%O=S#Jh3P*GcVmnKgrHYK_R%bpdcqRHANw@D7`c{HLpY=vsfW7 zzeFKXp)4^cGeyBOH!(d`p(wRDzqBYhRUtD^0ceJPSz?iXd3m{hMoDgteo|sla(+rG djMgj2C;*wRke{cJlbM$a(qL$81QG(f0RRiUtOo!9 literal 0 HcmV?d00001 diff --git a/Open-ILS/web/staff/php/barcode/index.php b/Open-ILS/web/staff/php/barcode/index.php new file mode 100755 index 0000000000..0e8f4db74f --- /dev/null +++ b/Open-ILS/web/staff/php/barcode/index.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/Open-ILS/web/staff/php/barcode/linux.gif b/Open-ILS/web/staff/php/barcode/linux.gif new file mode 100755 index 0000000000000000000000000000000000000000..2540ad36586c26bac20a0699e30e4fafe82033a6 GIT binary patch literal 6969 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 literal 0 HcmV?d00001 diff --git a/Open-ILS/web/staff/php/barcode/sample.php b/Open-ILS/web/staff/php/barcode/sample.php new file mode 100755 index 0000000000..f82132ce13 --- /dev/null +++ b/Open-ILS/web/staff/php/barcode/sample.php @@ -0,0 +1,139 @@ + + + Barcode Sample + + + + + + + + +
+

+0) { + $style = BCS_ALIGN_CENTER; + $style |= ($output == "png" ) ? BCS_IMAGE_PNG : 0; + $style |= ($output == "jpeg") ? BCS_IMAGE_JPEG : 0; + $style |= ($border == "on" ) ? BCS_BORDER : 0; + $style |= ($drawtext== "on" ) ? BCS_DRAW_TEXT : 0; + $style |= ($stretchtext== "on" ) ? BCS_STRETCH_TEXT : 0; + $style |= ($negative== "on" ) ? BCS_REVERSE_COLOR : 0; + + switch ($type) + { + case "I25": + $obj = new I25Object(250, 120, $style, $barcode); + break; + case "C39": + $obj = new C39Object(250, 120, $style, $barcode); + break; + case "C128A": + $obj = new C128AObject(250, 120, $style, $barcode); + break; + case "C128B": + $obj = new C128BObject(250, 120, $style, $barcode); + break; + case "C128C": + $obj = new C128CObject(250, 120, $style, $barcode); + break; + default: + $obj = false; + } + if ($obj) { + if ($obj->DrawObject($xres)) { + echo "
"; + } else echo "
".($obj->GetError())."
"; + } +} +?> +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Type
Output
Styles>Draw border
>Draw value text
>Stretch text
>Negative (White on black)
SizeWidth:
Height:
Xres + >1       + >2       + >3       +
Text Font + >1       + >2       + >3       + >4       + >5       +
Value
+
+ + diff --git a/Open-ILS/web/staff/php/barcode/sample.png b/Open-ILS/web/staff/php/barcode/sample.png new file mode 100755 index 0000000000000000000000000000000000000000..1fdbbf6d0cdf840d90bdca2bc404a809ef2eae24 GIT binary patch literal 4653 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 literal 0 HcmV?d00001 diff --git a/Open-ILS/web/staff/php/barcode/spain.png b/Open-ILS/web/staff/php/barcode/spain.png new file mode 100755 index 0000000000000000000000000000000000000000..d50f1be99c5b14f31832811b6482916eff276d89 GIT binary patch literal 529 zcmV+s0`C2ZP)<2m%6&-=FV@*x}sFb45ZX!s` zEchV(y!!#H^#!`SzCZvlIfu!)UE3W*HC3+Vp%ZiUH9h(reS^|V7ztSwISRq#rlrVQ zX?WyX)Z^gr$hBzu;%We^fD$u!)44Z_;hC0ux(f-Zc4*saV#YMRzEP`U3{~fMe^`%T zh=3ULwpAMDiaWrdF%$_9$uJ`M*k3F?B}cBp0E2+M03bln4pRf6)fKsI+#c~FV0kt# zf{b<^KMNSZbaE=t1yAqCnM6bo<9RPVFn}cx5qv;)2WucN0+2dqquNS#re>gBbrZ=k zo5zdyb34&o9n?7o(d%^wFkTPox38o1*j)yZ==0P?25JUMiPsH8&q9FgP+~?5`4|8o zvWuLb8?-w;AL_OR-sFmrd+tAho#VA5Vc(zkVK=1$P;7gps@b-d)pTbYIqeiPSC6aq z4{OJiW~Gofr3Y^*63zbqSkGTNYd`PHkPWgaQ6(#kxTFL9S?Z z0VU?5OGjfPF~gy)9AA~0LnVkK7e=`Q7yvzfU{tL8N6O5hcY`eY{;a>dJ`U&)G&!s# Tu%&>f00000NkvXXu0mjfh9=_c literal 0 HcmV?d00001 diff --git a/Open-ILS/web/staff/xml/circ_tracker.xml b/Open-ILS/web/staff/xml/circ_tracker.xml new file mode 100644 index 0000000000..0f35a2b54b --- /dev/null +++ b/Open-ILS/web/staff/xml/circ_tracker.xml @@ -0,0 +1,267 @@ + + + + + + + + + + + + + + + + +
+
+ Open Source Integrated Interlibrary Lending System +
+ + +
+ + Choose a location +
+
+
+
+
+
+
+
+ + +
+

Check In or Recall ILLs

+
+ Scan Item: + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + +
Print ALL
ActionItemPatronLocationPrint...
+ ILL Receipt +
+ +
+
+ +
+ + +
+ + + + + + + + +
+
+
+ +
+ + +
+ + + + + + + + +
+
+
+ +
+ + +
+ + + + + + + + +
+
+
+ +
+ + +
+ + + + + + + + +
+
+
+ + +
+
+ +
+
+
+ Log Out + + diff --git a/Open-ILS/web/staff/xml/hold_tracker.xml b/Open-ILS/web/staff/xml/hold_tracker.xml new file mode 100644 index 0000000000..beb5ee0bfd --- /dev/null +++ b/Open-ILS/web/staff/xml/hold_tracker.xml @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + +
+
+ Open Source Integrated Interlibrary Lending System +
+ + +
+ + Choose a location +
+
+
+
+
+ +
+
+
+ + +
+

Capture, Receive or Circulate ILL Requests

+
+ Scan Item: + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Print ALL
DirectionActionItemPatronLocationPrint...
+ ILL Request Receipt +
+ +
+
+ +
+ + +
+ + + + + + + + + +
+
+
+ + + +
+ + +
+ + + + + + + + + + +
+
+
+ + +
+ + +
+ + + + + + + + +
+
+
+ + +
+ + +
+ + + + + + + + +
+
+
+ +
+
+ +
+
+
+ Log Out + + diff --git a/Open-ILS/web/staff/xml/record_mgmt.xml b/Open-ILS/web/staff/xml/record_mgmt.xml new file mode 100644 index 0000000000..c1c9a298ca --- /dev/null +++ b/Open-ILS/web/staff/xml/record_mgmt.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + +
+
+ Open Source Integrated Interlibrary Lending System +
+ + +
+ + Choose a location + +
+
+
+
+
+ +
+ +
+ +
+ + + +
+
+
Status:
+
+
+ + + +
+
+ +
+ +
+ + + +
+
+ +
+
+ +
+
+
+ Log Out + + + -- 2.11.0