From 29a874a1f9c99d2eb69b048631d4e5b0dc51a813 Mon Sep 17 00:00:00 2001 From: Thomas Berezansky Date: Fri, 13 May 2011 15:43:52 -0400 Subject: [PATCH] Lazy Circ (AKA partial barcode lookup) Database tables/access functions for prefix/suffix info Table: config.barcode_completion Function: evergreen.get_barcodes Takes org unit, context, and input barcode Context is a string and can contain: asset - asset.copy barcodes serial - serial.unit barcodes actor - actor.usr (via actor.card) barcodes booking - booking.resource barcodes Special case: asset and serial both in context Returns non-serial asset entries as asset Returns serial entries as serial Type for function return: evergreen.barcode_set With editing interface: Admin->Local Admin->Barcode Completion OpenSRF Call: open-ils.actor.get_barcodes Basically a passthrough to the database function Checks for permissions: STAFF_LOGIN - To do anything VIEW_USER - At home_ou of the user owning the returned barcode when in actor context Add get_barcode to menu.js and to xulG in a number of places Takes a window handle, a context, and an input barcode Passes the current OU, the context, and the input barcode to the db If multiple results come back it pops up a dialog Returns boolean false on no results Returns "user_false" on dialog cancel Returns an object with type, id, barcode on success If dialog was brought up, returns data used to populate button Add option for looking up actors at checkout Org unit setting: Load patron from Checkout. Default: Don't. Call sites for get_barcode: Lookup Patron By Barcode (actor only) OPAC's Staff Client Place Hold (actor only) Checkout (asset only by default, with actor if above option set) Checkin (asset only) Item Status (asset only) Signed-off-by: Thomas Berezansky Signed-off-by: Mike Rylander --- Open-ILS/examples/fm_IDL.xml | 25 +++ .../src/perlmods/lib/OpenILS/Application/Actor.pm | 49 ++++++ Open-ILS/src/sql/Pg/002.schema.config.sql | 15 ++ Open-ILS/src/sql/Pg/020.schema.functions.sql | 89 +++++++++++ Open-ILS/src/sql/Pg/800.fkeys.sql | 2 + Open-ILS/src/sql/Pg/950.data.seed-values.sql | 5 + Open-ILS/src/sql/Pg/upgrade/XXXX.lazy_circ.sql | 113 ++++++++++++++ .../conify/global/config/barcode_completion.js | 15 ++ Open-ILS/web/opac/locale/en-US/lang.dtd | 1 + Open-ILS/web/opac/skin/default/js/holds.js | 19 +++- .../conify/global/config/barcode_completion.tt2 | 26 ++++ .../xul/staff_client/chrome/content/cat/opac.js | 14 +- .../staff_client/chrome/content/main/constants.js | 3 +- .../xul/staff_client/chrome/content/main/menu.js | 156 +++++++++++++++++++- .../chrome/content/main/menu_frame_menus.xul | 3 + .../chrome/locale/en-US/offline.properties | 13 ++ Open-ILS/xul/staff_client/server/circ/checkin.js | 12 +- Open-ILS/xul/staff_client/server/circ/checkout.js | 21 +++ .../xul/staff_client/server/circ/copy_status.js | 8 + .../staff_client/server/patron/barcode_entry.xul | 108 +++++++------- Open-ILS/xul/staff_client/server/patron/display.js | 4 +- 21 files changed, 636 insertions(+), 65 deletions(-) create mode 100644 Open-ILS/src/sql/Pg/upgrade/XXXX.lazy_circ.sql create mode 100644 Open-ILS/web/js/ui/default/conify/global/config/barcode_completion.js create mode 100644 Open-ILS/web/templates/default/conify/global/config/barcode_completion.tt2 diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index 724e8f5..706ab89 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -8809,6 +8809,31 @@ SELECT usr, + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm index 9b9bd13..8d6659e 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm @@ -4283,6 +4283,55 @@ sub user_saved_search_cud { return $res; } +__PACKAGE__->register_method( + method => "get_barcodes", + api_name => "open-ils.actor.get_barcodes" +); + +sub get_barcodes { + my( $self, $client, $auth, $org_id, $context, $barcode ) = @_; + my $e = new_editor(authtoken => $auth); + return $e->event unless $e->checkauth; + return $e->event unless $e->allowed('STAFF_LOGIN', $org_id); + my $db_result = $e->json_query( + { from => [ + 'evergreen.get_barcodes', + $org_id, $context, $barcode, + ] + } + ); + if($context =~ /actor/) { + my $filter_result = (); + my $patron; + foreach my $result (@$db_result) { + if($result->{type} eq 'actor') { + if($e->requestor->id != $result->{id}) { + $patron = $e->retrieve_actor_user($result->{id}); + if(!$patron) { + push(@$filter_result, $e->event); + next; + } + if($e->allowed('VIEW_USER', $patron->home_ou)) { + push(@$filter_result, $result); + } + else { + push(@$filter_result, $e->event); + } + } + else { + push(@$filter_result, $result); + } + } + else { + push(@$filter_result, $result); + } + } + return $filter_result; + } + else { + return $db_result; + } +} 1; diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql index 4b7b0ef..2f14205 100644 --- a/Open-ILS/src/sql/Pg/002.schema.config.sql +++ b/Open-ILS/src/sql/Pg/002.schema.config.sql @@ -887,4 +887,19 @@ Upgrade script % can not be applied: END; $$ LANGUAGE PLPGSQL; +CREATE TABLE config.barcode_completion ( + id SERIAL PRIMARY KEY, + active BOOL NOT NULL DEFAULT true, + org_unit INT NOT NULL, -- REFERENCES actor.org_unit(id) DEFERRABLE INITIALLY DEFERRED, + prefix TEXT, + suffix TEXT, + length INT NOT NULL DEFAULT 0, + padding TEXT, + padding_end BOOL NOT NULL DEFAULT false, + asset BOOL NOT NULL DEFAULT true, + actor BOOL NOT NULL DEFAULT true +); + +CREATE TYPE evergreen.barcode_set AS (type TEXT, id BIGINT, barcode TEXT); + COMMIT; diff --git a/Open-ILS/src/sql/Pg/020.schema.functions.sql b/Open-ILS/src/sql/Pg/020.schema.functions.sql index 981a364..f29239e 100644 --- a/Open-ILS/src/sql/Pg/020.schema.functions.sql +++ b/Open-ILS/src/sql/Pg/020.schema.functions.sql @@ -416,3 +416,92 @@ from an authority record. The primary purpose is to build a unique index to defend against duplicated authority records from the same thesaurus. $$; + +CREATE OR REPLACE FUNCTION evergreen.get_barcodes(select_ou INT, type TEXT, in_barcode TEXT) RETURNS SETOF evergreen.barcode_set AS $$ +DECLARE + cur_barcode TEXT; + barcode_len INT; + completion_len INT; + asset_barcodes TEXT[]; + actor_barcodes TEXT[]; + do_asset BOOL = false; + do_serial BOOL = false; + do_booking BOOL = false; + do_actor BOOL = false; + completion_set config.barcode_completion%ROWTYPE; +BEGIN + + IF position('asset' in type) > 0 THEN + do_asset = true; + END IF; + IF position('serial' in type) > 0 THEN + do_serial = true; + END IF; + IF position('booking' in type) > 0 THEN + do_booking = true; + END IF; + IF do_asset OR do_serial OR do_booking THEN + asset_barcodes = asset_barcodes || in_barcode; + END IF; + IF position('actor' in type) > 0 THEN + do_actor = true; + actor_barcodes = actor_barcodes || in_barcode; + END IF; + + barcode_len := length(in_barcode); + + FOR completion_set IN + SELECT * FROM config.barcode_completion + WHERE active + AND org_unit IN (SELECT aou.id FROM actor.org_unit_ancestors(select_ou) aou) + LOOP + IF completion_set.prefix IS NULL THEN + completion_set.prefix := ''; + END IF; + IF completion_set.suffix IS NULL THEN + completion_set.suffix := ''; + END IF; + IF completion_set.length = 0 OR completion_set.padding IS NULL OR length(completion_set.padding) = 0 THEN + cur_barcode = completion_set.prefix || in_barcode || completion_set.suffix; + ELSE + completion_len = completion_set.length - length(completion_set.prefix) - length(completion_set.suffix); + IF completion_len >= barcode_len THEN + IF completion_set.padding_end THEN + cur_barcode = rpad(in_barcode, completion_len, completion_set.padding); + ELSE + cur_barcode = lpad(in_barcode, completion_len, completion_set.padding); + END IF; + cur_barcode = completion_set.prefix || cur_barcode || completion_set.suffix; + END IF; + END IF; + IF completion_set.actor THEN + actor_barcodes = actor_barcodes || cur_barcode; + END IF; + IF completion_set.asset THEN + asset_barcodes = asset_barcodes || cur_barcode; + END IF; + END LOOP; + + IF do_asset AND do_serial THEN + RETURN QUERY SELECT 'asset'::TEXT, id, barcode FROM ONLY asset.copy WHERE barcode = ANY(asset_barcodes) AND deleted = false; + RETURN QUERY SELECT 'serial'::TEXT, id, barcode FROM serial.unit WHERE barcode = ANY(asset_barcodes) AND deleted = false; + ELSIF do_asset THEN + RETURN QUERY SELECT 'asset'::TEXT, id, barcode FROM asset.copy WHERE barcode = ANY(asset_barcodes) AND deleted = false; + ELSIF do_serial THEN + RETURN QUERY SELECT 'serial'::TEXT, id, barcode FROM serial.unit WHERE barcode = ANY(asset_barcodes) AND deleted = false; + END IF; + IF do_booking THEN + RETURN QUERY SELECT 'booking'::TEXT, id::BIGINT, barcode FROM booking.resource WHERE barcode = ANY(asset_barcodes); + END IF; + IF do_actor THEN + RETURN QUERY SELECT 'actor'::TEXT, c.usr::BIGINT, c.barcode FROM actor.card c JOIN actor.usr u ON c.usr = u.id WHERE c.barcode = ANY(actor_barcodes) AND c.active AND NOT u.deleted ORDER BY usr; + END IF; + RETURN; +END; +$$ LANGUAGE plpgsql; + +COMMENT ON FUNCTION evergreen.get_barcodes(INT, TEXT, TEXT) IS $$ +Given user input, find an appropriate barcode in the proper class. + +Will add prefix/suffix information to do so, and return all results. +$$; diff --git a/Open-ILS/src/sql/Pg/800.fkeys.sql b/Open-ILS/src/sql/Pg/800.fkeys.sql index 619441d..d0ee887 100644 --- a/Open-ILS/src/sql/Pg/800.fkeys.sql +++ b/Open-ILS/src/sql/Pg/800.fkeys.sql @@ -115,6 +115,8 @@ ALTER TABLE config.remote_account ADD CONSTRAINT config_remote_account_owner_fke ALTER TABLE config.org_unit_setting_type ADD CONSTRAINT view_perm_fkey FOREIGN KEY (view_perm) REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED; ALTER TABLE config.org_unit_setting_type ADD CONSTRAINT update_perm_fkey FOREIGN KEY (update_perm) REFERENCES permission.perm_list (id) ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE config.barcode_completion ADD CONSTRAINT config_barcode_completion_org_unit_fkey FOREIGN KEY (org_unit) REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; + CREATE INDEX by_heading_and_thesaurus ON authority.record_entry (authority.normalize_heading(marc)) WHERE deleted IS FALSE or deleted = FALSE; COMMIT; diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql index 38b7b71..8daf489 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -2508,6 +2508,11 @@ INSERT into config.org_unit_setting_type oils_i18n_gettext('circ.selfcheck.auto_override_checkout_events', 'List of checkout/renewal events that the selfcheck interface should automatically override instead instead of alerting and stopping the transaction', 'coust', 'description'), 'array'), +( 'circ.staff_client.actor_on_checkout', + oils_i18n_gettext('circ.staff_client.actor_on_checkout', 'Load patron from Checkout', 'coust', 'label'), + oils_i18n_gettext('circ.staff_client.actor_on_checkout', 'When scanning barcodes into Checkout auto-detect if a new patron barcode is scanned and auto-load the new patron.', 'coust', 'description'), + 'bool'), + ( 'circ.staff_client.do_not_auto_attempt_print', oils_i18n_gettext('circ.staff_client.do_not_auto_attempt_print', 'Disable Automatic Print Attempt Type List', 'coust', 'label'), oils_i18n_gettext('circ.staff_client.do_not_auto_attempt_print', 'Disable automatic print attempts from staff client interfaces for the receipt types in this list. Possible values: "Checkout", "Bill Pay", "Hold Slip", "Transit Slip", and "Hold/Transit Slip". This is different from the Auto-Print checkbox in the pertinent interfaces in that it disables automatic print attempts altogether, rather than encouraging silent printing by suppressing the print dialog. The Auto-Print checkbox in these interfaces have no effect on the behavior for this setting. In the case of the Hold, Transit, and Hold/Transit slips, this also suppresses the alert dialogs that precede the print dialog (the ones that offer Print and Do Not Print as options).', 'coust', 'description'), diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.lazy_circ.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.lazy_circ.sql new file mode 100644 index 0000000..ab4692a --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.lazy_circ.sql @@ -0,0 +1,113 @@ +BEGIN; + +INSERT INTO config.upgrade_log (version) VALUES ('XXXX'); + +INSERT INTO config.org_unit_setting_type ( name, label, description, datatype) VALUES ( 'circ.staff_client.actor_on_checkout', 'Load patron from Checkout', 'When scanning barcodes into Checkout auto-detect if a new patron barcode is scanned and auto-load the new patron.', 'bool'); + +CREATE TABLE config.barcode_completion ( + id SERIAL PRIMARY KEY, + active BOOL NOT NULL DEFAULT true, + org_unit INT NOT NULL, -- REFERENCES actor.org_unit(id) DEFERRABLE INITIALLY DEFERRED, + prefix TEXT, + suffix TEXT, + length INT NOT NULL DEFAULT 0, + padding TEXT, + padding_end BOOL NOT NULL DEFAULT false, + asset BOOL NOT NULL DEFAULT true, + actor BOOL NOT NULL DEFAULT true +); + +CREATE TYPE evergreen.barcode_set AS (type TEXT, id BIGINT, barcode TEXT); + +CREATE OR REPLACE FUNCTION evergreen.get_barcodes(select_ou INT, type TEXT, in_barcode TEXT) RETURNS SETOF evergreen.barcode_set AS $$ +DECLARE + cur_barcode TEXT; + barcode_len INT; + completion_len INT; + asset_barcodes TEXT[]; + actor_barcodes TEXT[]; + do_asset BOOL = false; + do_serial BOOL = false; + do_booking BOOL = false; + do_actor BOOL = false; + completion_set config.barcode_completion%ROWTYPE; +BEGIN + + IF position('asset' in type) > 0 THEN + do_asset = true; + END IF; + IF position('serial' in type) > 0 THEN + do_serial = true; + END IF; + IF position('booking' in type) > 0 THEN + do_booking = true; + END IF; + IF do_asset OR do_serial OR do_booking THEN + asset_barcodes = asset_barcodes || in_barcode; + END IF; + IF position('actor' in type) > 0 THEN + do_actor = true; + actor_barcodes = actor_barcodes || in_barcode; + END IF; + + barcode_len := length(in_barcode); + + FOR completion_set IN + SELECT * FROM config.barcode_completion + WHERE active + AND org_unit IN (SELECT aou.id FROM actor.org_unit_ancestors(select_ou) aou) + LOOP + IF completion_set.prefix IS NULL THEN + completion_set.prefix := ''; + END IF; + IF completion_set.suffix IS NULL THEN + completion_set.suffix := ''; + END IF; + IF completion_set.length = 0 OR completion_set.padding IS NULL OR length(completion_set.padding) = 0 THEN + cur_barcode = completion_set.prefix || in_barcode || completion_set.suffix; + ELSE + completion_len = completion_set.length - length(completion_set.prefix) - length(completion_set.suffix); + IF completion_len >= barcode_len THEN + IF completion_set.padding_end THEN + cur_barcode = rpad(in_barcode, completion_len, completion_set.padding); + ELSE + cur_barcode = lpad(in_barcode, completion_len, completion_set.padding); + END IF; + cur_barcode = completion_set.prefix || cur_barcode || completion_set.suffix; + END IF; + END IF; + IF completion_set.actor THEN + actor_barcodes = actor_barcodes || cur_barcode; + END IF; + IF completion_set.asset THEN + asset_barcodes = asset_barcodes || cur_barcode; + END IF; + END LOOP; + + IF do_asset AND do_serial THEN + RETURN QUERY SELECT 'asset'::TEXT, id, barcode FROM ONLY asset.copy WHERE barcode = ANY(asset_barcodes) AND deleted = false; + RETURN QUERY SELECT 'serial'::TEXT, id, barcode FROM serial.unit WHERE barcode = ANY(asset_barcodes) AND deleted = false; + ELSIF do_asset THEN + RETURN QUERY SELECT 'asset'::TEXT, id, barcode FROM asset.copy WHERE barcode = ANY(asset_barcodes) AND deleted = false; + ELSIF do_serial THEN + RETURN QUERY SELECT 'serial'::TEXT, id, barcode FROM serial.unit WHERE barcode = ANY(asset_barcodes) AND deleted = false; + END IF; + IF do_booking THEN + RETURN QUERY SELECT 'booking'::TEXT, id::BIGINT, barcode FROM booking.resource WHERE barcode = ANY(asset_barcodes); + END IF; + IF do_actor THEN + RETURN QUERY SELECT 'actor'::TEXT, c.usr::BIGINT, c.barcode FROM actor.card c JOIN actor.usr u ON c.usr = u.id WHERE c.barcode = ANY(actor_barcodes) AND c.active AND NOT u.deleted ORDER BY usr; + END IF; + RETURN; +END; +$$ LANGUAGE plpgsql; + +COMMENT ON FUNCTION evergreen.get_barcodes(INT, TEXT, TEXT) IS $$ +Given user input, find an appropriate barcode in the proper class. + +Will add prefix/suffix information to do so, and return all results. +$$; + +ALTER TABLE config.barcode_completion ADD CONSTRAINT config_barcode_completion_org_unit_fkey FOREIGN KEY (org_unit) REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED; + +COMMIT; diff --git a/Open-ILS/web/js/ui/default/conify/global/config/barcode_completion.js b/Open-ILS/web/js/ui/default/conify/global/config/barcode_completion.js new file mode 100644 index 0000000..0979c49 --- /dev/null +++ b/Open-ILS/web/js/ui/default/conify/global/config/barcode_completion.js @@ -0,0 +1,15 @@ +dojo.require('dijit.layout.ContentPane'); +dojo.require('dijit.form.Button'); +dojo.require('openils.widget.AutoGrid'); +dojo.require('openils.widget.AutoFieldWidget'); +dojo.require('openils.PermaCrud'); +dojo.require('openils.widget.ProgressDialog'); + +function load(){ + cmGrid.overrideWidgetArgs.prefix = {hrbefore : true}; + cmGrid.overrideWidgetArgs.asset = {hrbefore: true}; + cmGrid.loadAll({order_by:{cbc:'org_unit'}}); +} + +openils.Util.addOnLoad(load); + diff --git a/Open-ILS/web/opac/locale/en-US/lang.dtd b/Open-ILS/web/opac/locale/en-US/lang.dtd index 9cf9140..38ae9a1 100644 --- a/Open-ILS/web/opac/locale/en-US/lang.dtd +++ b/Open-ILS/web/opac/locale/en-US/lang.dtd @@ -704,6 +704,7 @@ + diff --git a/Open-ILS/web/opac/skin/default/js/holds.js b/Open-ILS/web/opac/skin/default/js/holds.js index 73514c1..aaf7a2e 100644 --- a/Open-ILS/web/opac/skin/default/js/holds.js +++ b/Open-ILS/web/opac/skin/default/js/holds.js @@ -53,7 +53,24 @@ function _holdsHandleStaffMe() { } function _holdsHandleStaff() { - var barcode = xulG.patron_barcode || $('xul_recipient_barcode').value; + var barcode = xulG.patron_barcode; + if(!barcode) { + barcode = $('xul_recipient_barcode').value; + if(xulG.get_barcode) { + // We have a "complete the barcode" function, call it (actor = users only) + var new_barcode = xulG.get_barcode(window, 'actor', barcode); + // If we got a result (boolean false is "no result") check it + if(new_barcode) { + // user_false string means they picked "None of the above" + // Abort before any other events can fire + if(new_barcode == "user_false") return; + // No error means we have a (hopefully valid) completed barcode to use. + // Otherwise, fall through to other methods of checking + if(typeof new_barcode.ilsevent == 'undefined') + barcode = new_barcode.barcode; + } + } + } var user = grabUserByBarcode( G.user.session, barcode ); var evt; diff --git a/Open-ILS/web/templates/default/conify/global/config/barcode_completion.tt2 b/Open-ILS/web/templates/default/conify/global/config/barcode_completion.tt2 new file mode 100644 index 0000000..fc81951 --- /dev/null +++ b/Open-ILS/web/templates/default/conify/global/config/barcode_completion.tt2 @@ -0,0 +1,26 @@ +[% ctx.page_title = 'Barcode Completion Configuration' %] +[% WRAPPER default/base.tt2 %] + +
+
Barcode Completion Configuration
+
+
+
+ +
+
+ +