Lazy Circ (AKA partial barcode lookup)
authorThomas Berezansky <tsbere@mvlc.org>
Fri, 13 May 2011 19:43:52 +0000 (15:43 -0400)
committerMike Rylander <mrylander@gmail.com>
Tue, 17 May 2011 17:48:51 +0000 (13:48 -0400)
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 <tsbere@mvlc.org>
Signed-off-by: Mike Rylander <mrylander@gmail.com>

21 files changed:
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm
Open-ILS/src/sql/Pg/002.schema.config.sql
Open-ILS/src/sql/Pg/020.schema.functions.sql
Open-ILS/src/sql/Pg/800.fkeys.sql
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.lazy_circ.sql [new file with mode: 0644]
Open-ILS/web/js/ui/default/conify/global/config/barcode_completion.js [new file with mode: 0644]
Open-ILS/web/opac/locale/en-US/lang.dtd
Open-ILS/web/opac/skin/default/js/holds.js
Open-ILS/web/templates/default/conify/global/config/barcode_completion.tt2 [new file with mode: 0644]
Open-ILS/xul/staff_client/chrome/content/cat/opac.js
Open-ILS/xul/staff_client/chrome/content/main/constants.js
Open-ILS/xul/staff_client/chrome/content/main/menu.js
Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties
Open-ILS/xul/staff_client/server/circ/checkin.js
Open-ILS/xul/staff_client/server/circ/checkout.js
Open-ILS/xul/staff_client/server/circ/copy_status.js
Open-ILS/xul/staff_client/server/patron/barcode_entry.xul
Open-ILS/xul/staff_client/server/patron/display.js

index 724e8f5..706ab89 100644 (file)
@@ -8809,6 +8809,31 @@ SELECT  usr,
                        </actions>
                </permacrud>
        </class>
+       <class id="cbc" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::barcode_completion" oils_persist:tablename="config.barcode_completion" reporter:label="Barcode Completions">
+               <fields oils_persist:primary="id" oils_persist:sequence="config.barcode_completion_id_seq">
+                       <field reporter:label="ID" name="id" reporter:datatype="id"/>
+                       <field reporter:label="Active" name="active" reporter:datatype="bool"/>
+                       <field reporter:label="Owner" name="org_unit" reporter:datatype="org_unit"/>
+                       <field reporter:label="Prefix" name="prefix" reporter:datatype="text"/>
+                       <field reporter:label="Suffix" name="suffix" reporter:datatype="text"/>
+                       <field reporter:label="Length" name="length" reporter:datatype="int"/>
+                       <field reporter:label="Padding" name="padding" reporter:datatype="text"/>
+                       <field reporter:label="Padding At End" name="padding_end" reporter:datatype="bool"/>
+                       <field reporter:label="Applies to Items" name="asset" reporter:datatype="bool"/>
+                       <field reporter:label="Applies to Users" name="actor" reporter:datatype="bool"/>
+               </fields>
+               <links>
+                       <link field="org_unit" reltype="has_a" key="id" map="" class="aou"/>
+               </links>
+               <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+                       <actions>
+                               <create permission="UPDATE_ORG_UNIT_SETTING_ALL" context_field="org_unit"/>
+                               <retrieve/>
+                               <update permission="UPDATE_ORG_UNIT_SETTING_ALL" context_field="org_unit"/>
+                               <delete permission="UPDATE_ORG_UNIT_SETTING_ALL" context_field="org_unit"/>
+                       </actions>
+               </permacrud>
+       </class>
 
        <!-- ********************************************************************************************************************* -->
 
index 9b9bd13..8d6659e 100644 (file)
@@ -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;
index 4b7b0ef..2f14205 100644 (file)
@@ -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;
index 981a364..f29239e 100644 (file)
@@ -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.
+$$;
index 619441d..d0ee887 100644 (file)
@@ -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;
index 38b7b71..8daf489 100644 (file)
@@ -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 (file)
index 0000000..ab4692a
--- /dev/null
@@ -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 (file)
index 0000000..0979c49
--- /dev/null
@@ -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);
+
index 9cf9140..38ae9a1 100644 (file)
 <!ENTITY staff.main.menu.admin.local_admin.conify.standing_penalty.label "Standing Penalties">
 <!ENTITY staff.main.menu.admin.local_admin.conify.grp_penalty_threshold.label "Group Penalty Thresholds">
 <!ENTITY staff.main.menu.admin.local_admin.conify.copy_location_order.label "Copy Location Order">
+<!ENTITY staff.main.menu.admin.local_admin.barcode_completion.label "Barcode Completion">
 <!ENTITY staff.main.menu.admin.local_admin.circ_matrix_matchpoint.label "Circulation Policies">
 <!ENTITY staff.main.menu.admin.local_admin.hold_matrix_matchpoint.label "Hold Policies">
 <!ENTITY staff.main.menu.admin.local_admin.work_log.label "Work Log">
index 73514c1..aaf7a2e 100644 (file)
@@ -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 (file)
index 0000000..fc81951
--- /dev/null
@@ -0,0 +1,26 @@
+[% ctx.page_title = 'Barcode Completion Configuration' %]
+[% WRAPPER default/base.tt2 %]
+<script type="text/javascript" src='[% ctx.media_prefix %]/js/ui/default/conify/global/config/barcode_completion.js'> </script>
+<div dojoType="dijit.layout.ContentPane" layoutAlign="top" class='oils-header-panel'>
+    <div>Barcode Completion Configuration</div>
+    <div><button dojoType='dijit.form.Button' onClick='cmGrid.showCreatePane()'>New</button></div>
+</div>
+<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+    <table  jsId="cmGrid"
+            style="height: 600px;"
+            dojoType="openils.widget.AutoGrid"
+            fieldOrder="['id', 'active', 'org_unit', 'prefix', 'suffix', 'length', 'padding', 'padding_end', 'asset', 'actor']"
+            defaultCellWidth='"auto"'
+            query="{id: '*'}"
+            fmClass='cbc'
+            editStyle='pane'
+            editOnEnter='true'
+            showColumnPicker='true'
+            columnPickerPrefix='"conify.config.barcode_completion"'>
+    </table>
+</div>
+
+<div class='hidden'><div dojoType='openils.widget.ProgressDialog' jsId='progressDialog'/></div>
+
+[% END %]
+
index 2fbe422..9684740 100644 (file)
@@ -105,7 +105,8 @@ function set_brief_view() {
     ["url_prefix", "new_tab", "set_tab", "close_tab", "new_patron_tab",
         "set_patron_tab", "volume_item_creator", "get_new_session",
         "holdings_maintenance_tab", "open_chrome_window", "url_prefix",
-        "network_meter", "page_meter", "set_statusbar", "set_help_context"
+        "network_meter", "page_meter", "set_statusbar", "set_help_context",
+        "get_barcode"
     ].forEach(function(k) { content_params[k] = xulG[k]; });
 
     top_pane.set_iframe( 
@@ -300,7 +301,7 @@ function open_acq_orders() {
             "set_patron_tab", "volume_item_creator", "get_new_session",
             "holdings_maintenance_tab", "set_tab_name", "open_chrome_window",
             "url_prefix", "network_meter", "page_meter", "set_statusbar",
-            "set_help_context"
+            "set_help_context", "get_barcode"
         ].forEach(function(k) { content_params[k] = xulG[k]; });
 
         var loc = urls.XUL_BROWSER + "?url=" + window.escape(
@@ -334,7 +335,7 @@ function open_alt_serial_mgmt() {
             "set_patron_tab", "volume_item_creator", "get_new_session",
             "holdings_maintenance_tab", "set_tab_name", "open_chrome_window",
             "url_prefix", "network_meter", "page_meter", "set_statusbar",
-            "set_help_context"
+            "set_help_context", "get_barcode"
         ].forEach(function(k) { content_params[k] = xulG[k]; });
 
         var loc = urls.XUL_BROWSER + "?url=" + window.escape(
@@ -370,7 +371,8 @@ function set_opac() {
                     } catch(E) {
                         g.error.standard_unexpected_error_alert('window_open',E);
                     }
-                }
+                },
+                'get_barcode' : xulG.get_barcode
             },
             'on_url_load' : function(f) {
                 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
@@ -489,6 +491,7 @@ function set_opac() {
         content_params.page_meter = xulG.page_meter;
         content_params.set_statusbar = xulG.set_statusbar;
         content_params.set_help_context = xulG.set_help_context;
+        content_params.get_barcode = xulG.get_barcode;
 
         if (opac_url) { content_params.url = opac_url; } else { content_params.url = xulG.url_prefix( urls.browser ); }
         browser_frame = bottom_pane.set_iframe( xulG.url_prefix(urls.XUL_BROWSER) + '?name=Catalog', {}, content_params);
@@ -616,6 +619,7 @@ function bib_in_new_tab() {
         content_params.page_meter = xulG.page_meter;
         content_params.set_statusbar = xulG.set_statusbar;
         content_params.set_help_context = xulG.set_help_context;
+        content_params.get_barcode = xulG.get_barcode;
 
         xulG.new_tab(xulG.url_prefix(urls.XUL_OPAC_WRAPPER), {}, content_params);
     } catch(E) {
@@ -631,7 +635,7 @@ function batch_receive_in_new_tab() {
             "set_patron_tab", "volume_item_creator", "get_new_session",
             "holdings_maintenance_tab", "set_tab_name", "open_chrome_window",
             "url_prefix", "network_meter", "page_meter", "set_statusbar",
-            "set_help_context"
+            "set_help_context", "get_barcode"
         ].forEach(function(k) { content_params[k] = xulG[k]; });
 
         xulG.new_tab(
index 56ed059..9961357 100644 (file)
@@ -357,7 +357,8 @@ var api = {
     'RECALCULATE_STANDING_PENALTIES' : { 'app' : 'open-ils.actor', 'method' : 'open-ils.actor.user.penalties.update' },
     'USER_ORG_UNIT_OPT_IN_FEATURE' : { 'app' : 'open-ils.actor', 'method' : 'open-ils.actor.user.org_unit_opt_in.enabled' },
     'USER_ORG_UNIT_OPT_IN_CHECK' : { 'app' : 'open-ils.actor', 'method' : 'open-ils.actor.user.org_unit_opt_in.check' },
-    'USER_ORG_UNIT_OPT_IN_CREATE' : { 'app' : 'open-ils.actor', 'method' : 'open-ils.actor.user.org_unit_opt_in.create' }
+    'USER_ORG_UNIT_OPT_IN_CREATE' : { 'app' : 'open-ils.actor', 'method' : 'open-ils.actor.user.org_unit_opt_in.create' },
+    'GET_BARCODES' : { 'app' : 'open-ils.actor', 'method' : 'open-ils.actor.get_barcodes' }
 }
 
 var urls = {
index bc8c11d..47fc718 100644 (file)
@@ -51,6 +51,8 @@ main.menu.prototype = {
 
         urls.remote = params['server'];
 
+        xulG.get_barcode = this.get_barcode;
+
         // Pull in local customizations
         var r = new XMLHttpRequest();
         r.open("GET", obj.url_prefix('/xul/server/skin/custom.js'), false);
@@ -762,6 +764,11 @@ main.menu.prototype = {
                 ['oncommand'],
                 function(event) { open_eg_web_page('conify/global/action/survey', null, event); }
             ],
+            'cmd_local_admin_barcode_completion' : [
+                ['oncommand'],
+                function() { open_eg_web_page('conify/global/config/barcode_completion', 
+                    'menu.local_admin.barcode_completion.tab'); }
+            ],
             'cmd_local_admin_circ_matrix_matchpoint' : [
                 ['oncommand'],
                 function() { open_eg_web_page('conify/global/config/circ_matrix_matchpoint', 
@@ -2046,6 +2053,7 @@ commands:
         content_params.url_prefix = function(url) { return obj.url_prefix(url); };
         content_params.network_meter = obj.network_meter;
         content_params.page_meter = obj.page_meter;
+        content_params.get_barcode = obj.get_barcode;
         content_params.set_statusbar = function(slot,text,tooltiptext,click_handler) {
             var e = document.getElementById('statusbarpanel'+slot);
             if (e) {
@@ -2142,8 +2150,154 @@ commands:
         }
 
         return frame;
-    }
+    },
+
+    'get_barcode' : function(window, context, barcode) {
+        JSAN.use('util.network');
+        JSAN.use('util.sound');
+
+        // Depending on where we were called from data can be found in multiple ways
+        var data;
+        if(this.data) data = this.data;
+        else if(xulG.data) data = xulG.data;        
+        else {
+            JSAN.use('util.data');
+            data = new util.data();
+        }
+        data.stash_retrieve();
+
+        var network = new util.network();
+        var sound = new util.sound();
+
+        // Should return an array. Or an error.
+        var r = network.simple_request('GET_BARCODES', [ ses(), data.list.au[0].ws_ou(), context, barcode ]);
+
+        if(!r) // Nothing?
+            return false;
 
+        // Top-level error, likely means bad session or no STAFF_LOGIN permission.
+        if(typeof r.ilsevent != 'undefined') {
+            // Hand it off to the caller.
+            return r;
+        }
+
+        // No results? Return false
+        if(r.length == 0) return false;
+
+        // One result?
+        if(r.length == 1) {
+            // Return it. If it is an error the caller should deal with it.
+            return r[0];
+        }
+
+        // At this point we have more than one result.
+        // Check to see what we got.
+        var result_filter = {};
+        var valid_r = [];
+        var unique_count = 0;
+        var found_errors = false;
+        var errors = '';
+        var len = r.length;
+
+        // Check each result.
+        for(var i = 0; i < len; ++i) {
+            // If it is an error
+            if(typeof r[i].ilsevent != 'undefined') {
+                // Make note that we found errors
+                found_errors = true;
+                // Grab the error into a string
+                errors += js2JSON(r[i]);
+            }
+            else {
+                // Otherwise, record the type/id combo for later
+                var type = r[i].type;
+                var id = r[i].id;
+                var barcode = r[i].barcode;
+                if(!result_filter[type]) result_filter[type] = {};
+                if(!result_filter[type][id]) {
+                    unique_count++;
+                    result_filter[type][id] = [];
+                }
+                result_filter[type][id].push(barcode);
+                valid_r.push(r[i]);
+            }
+        }
+
+        // Only errors? Return the first one.
+        if(unique_count == 0 && found_errors == true) {
+            return r[0];
+        }
+
+        // No errors, one (unique) result? Return it.
+        if(unique_count == 1 && found_errors == false) return valid_r[0];
+
+        // For possible debugging, dump the errors.
+        if(found_errors) dump(errors);
+
+        // Still here? Must need to have the user pick.
+        if(!xulG.url_prefix) xulG.url_prefix = url_prefix; // Make util.window happy
+        JSAN.use('util.window');
+        var win = new util.window();
+        var url = url_prefix(urls.XUL_FANCY_PROMPT);
+        var title = offlineStrings.getString('barcode_choice.title');
+        var xml = '<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" flex="1">';
+        xml += '<groupbox flex="1" style="overflow: auto; border: solid thin;"><caption label="' + title + '"/>';
+        xml += '<description style="-moz-user-select: text; -moz-user-focus: normal; font-size: large">' + offlineStrings.getString('barcode_choice.prompt') + '</description>';
+        if(found_errors) // Let the user know that one or more possible answers errored out.
+            xml += '<description style="-moz-user=select: text; -moz-user-focus: normal; font-size: large">' + offlineStrings.getString('barcode_choice.errors_found') + '</description>';
+        xml += '</groupbox><groupbox><caption label="' + offlineStrings.getString('barcode_choice.choice_label') + '"/><vbox>';
+
+        len = valid_r.length;
+        // Look at all the non-error answers we got
+        for(var i = 0; i < len; ++i) {
+            // If we still have a filtered answer, display a button.
+            if(result_filter[valid_r[i].type][valid_r[i].id]) {
+                var result_data = false;
+                var barcodes = result_filter[valid_r[i].type][valid_r[i].id];
+                var barcodes_assembled = barcodes.shift();
+                var button_label = '';
+                while(barcodes.length > 0) // Join any secondary barcodes found together
+                    barcodes_assembled = offlineStrings.getFormattedString('barcode_choice.join_barcodes', [barcodes_assembled, barcodes.shift()]);
+                switch(r[i].type) {
+                    case 'actor':
+                        result_data = network.simple_request('BLOB_AU_PARTS_RETRIEVE',
+                            [ ses() , valid_r[i].id, ['family_name', 'first_given_name', 'second_given_name', 'home_ou' ] ]);
+                        button_label = offlineStrings.getFormattedString('barcode_choice.actor',
+                            [barcodes_assembled, result_data[0], result_data[1] + (result_data[2] ? ' ' + result_data[2] : ''), data.hash.aou[ result_data[3] ].name(), data.hash.aou[ result_data[3] ].shortname()]);
+                        break;
+                    case 'booking':
+                        result_data = network.simple_request('FM_ACP_DETAILS_VIA_BARCODE', [ ses(), valid_r[i].barcode ]);
+                        // Note: This falls through intentionally.
+                    case 'asset':
+                    case 'serial':
+                        if(!result_data) // If we fell through this should be set already.
+                            result_data = network.simple_request('FM_ACP_DETAILS', [ ses(), valid_r[i].id ]);
+                        button_label = offlineStrings.getFormattedString('barcode_choice.asset',
+                            [barcodes_assembled, result_data.mvr.title(), data.hash.aou[ result_data.copy.circ_lib() ].name(), data.hash.aou[ result_data.copy.circ_lib() ].shortname()]);
+                        break;
+                }
+                r[i].data = result_data;
+
+                // This ensures we only show each unique id once
+                delete result_filter[valid_r[i].type][valid_r[i].id];
+
+                // If we have more than one context this should label each entry with where it came from
+                // Likely most useful for distinguishing assets from bookings
+                if(context != valid_r[i].type && offlineStrings.testString('barcode_choice.' + valid_r[i].type + '_label'))
+                    button_label = offlineStrings.getFormattedString('barcode_choice.' + valid_r[i].type + '_label', [button_label]);
+
+                xml += '<button label="' + button_label + '" name="fancy_submit" value="' + i + '"/>';
+            }
+        }
+        xml += '<button label="' + offlineStrings.getString('barcode_choice.none') + '" name="fancy_cancel"/>';
+        xml += '</vbox></groupbox></vbox>';
+        var fancy_prompt_data = win.open( url, 'fancy_prompt', 'chrome,resizable,modal,width=500,height=500', { 'xml' : xml, 'title' : title, 'sound' : 'bad' } );
+        if(fancy_prompt_data.fancy_status == 'complete')
+            return valid_r[fancy_prompt_data.fancy_submit];
+        else
+            // user_false is used to indicate the user said "None of the above" to avoid fall-through erroring later.
+            return "user_false";
+    }
 }
 
 dump('exiting main/menu.js\n');
index 2dc7cb5..7fb34b1 100644 (file)
     <command id="cmd_local_admin_age_overdue_circulations_to_lost" />
     <command id="cmd_local_admin_cash_reports" />
     <command id="cmd_local_admin_transit_list" />
+    <command id="cmd_local_admin_barcode_completion"
+             perm="UPDATE_ORG_UNIT_SETTING_ALL" />
     <command id="cmd_local_admin_circ_matrix_matchpoint"
              perm="ADMIN_CIRC_MATRIX_MATCHPOINT VIEW_CIRC_MATRIX_MATCHPOINT"
              />
             <menupopup id="main.menu.admin.local.popup">
                 <menuitem command="cmd_local_admin_age_overdue_circulations_to_lost" label="&staff.server.admin.index.age_overdue_circulations_to_lost.label;" accesskey="&staff.server.admin.index.age_overdue_circulations_to_lost.accesskey;"/>
                 <menuitem label="&staff.server.admin.index.cash_reports;" command="cmd_local_admin_cash_reports"/>
+                <menuitem label="&staff.main.menu.admin.local_admin.barcode_completion.label;" command="cmd_local_admin_barcode_completion"/>
                 <menuitem label="&staff.main.menu.admin.local_admin.circ_matrix_matchpoint.label;" command="cmd_local_admin_circ_matrix_matchpoint"/>
                 <menuitem label="&staff.server.admin.index.closed_dates;" command="cmd_local_admin_closed_dates"/>
                 <menuitem label="&staff.server.admin.index.copy_locations;" command="cmd_local_admin_copy_locations"/>
index 06452b0..26bf180 100644 (file)
@@ -250,6 +250,7 @@ menu.cmd_booking_reservation_pickup.tab=Reservation Pickup
 menu.cmd_booking_reservation_return.tab=Reservation Return
 menu.cmd_booking_pull_list.tab=Booking Pull List
 menu.cmd_booking_capture.tab=Booking Capture
+menu.local_admin.barcode_completion.tab=Barcode Completion
 menu.local_admin.circ_matrix_matchpoint.tab=Circulation Policies
 menu.local_admin.hold_matrix_matchpoint.tab=Hold Policies
 menu.local_admin.work_log.tab=Work Log
@@ -297,3 +298,15 @@ menu.logoff.unsaved_data_warning=This session may have unsaved data. Logoff anyw
 menu.shutdown.unsaved_data_warning=This application may have unsaved data. Exit it anyway?
 hotkeys.Default=Default
 hotkeys.None=No Hotkeys
+barcode_choice.join_barcodes=%1$s / %2$s
+barcode_choice.actor=%1$s : %2$s, %3$s from %4$s (%5$s)
+barcode_choice.asset=%1$s : %2$s from %3$s (%4$s)
+barcode_choice.none=None of the above
+barcode_choice.prompt=After auto completion multiple barcodes may match your input. Please choose the barcode you intended below.
+barcode_choice.errors_found=In addition to the options below, one or more errors were encountered on items not shown.
+barcode_choice.title=Barcode Choice
+barcode_choice.choice_label=Found Barcodes:
+barcode_choice.actor_label=Patron : %1$s
+barcode_choice.asset_label=Item : %1$s
+barcode_choice.serial_label=Serial : %1$s
+barcode_choice.booking_label=Booking : %1$s
index 9a36d7e..75c1edb 100644 (file)
@@ -509,13 +509,17 @@ circ.checkin.prototype = {
             var async_checkbox = document.getElementById('async_checkin');
             if (async_checkbox) { async = async_checkbox.getAttribute('checked') == 'true'; }
             var barcode = textbox.value;
+            // Auto-complete the barcode, items only
+            var barcode_object = xulG.get_barcode(window, 'asset', barcode);
             if (async) {
                 textbox.value = ''; textbox.focus();
             }
-            if (!barcode) return;
-            if (barcode) {
-                if ( obj.test_barcode(barcode) ) { /* good */ } else { /* bad */ return; }
-            }
+            // user_false means the user selected "None of the above", abort before other prompts/errors
+            if(barcode_object == "user_false") return;
+            // Got a barcode without an error? Use it. Otherwise fall through.
+            if(barcode_object && typeof barcode_object.ilsevent == 'undefined')
+                barcode = barcode_object.barcode;
+            if ( obj.test_barcode(barcode) ) { /* good */ } else { /* bad */ return; }
             var placeholder_item = new acp();
             placeholder_item.barcode( barcode );
             var row_params = obj.list.append( { 
index 4227858..c7d3813 100644 (file)
@@ -589,6 +589,27 @@ circ.checkout.prototype = {
         if (! (params.barcode||params.noncat)) { return; }
 
         if (params.barcode) {
+            // Default is "just items"
+            var barcode_context = 'asset';
+            // Add actor (can be any string that includes 'actor') if looking up patrons at checkout
+            if(String( obj.data.hash.aous['circ.staff_client.actor_on_checkout'] ) == 'true')
+                barcode_context += '-actor';
+            // Auto-complete the barcode
+            var in_barcode = xulG.get_barcode(window, barcode_context, params.barcode);
+            // user_false is "None of the above selected", don't error out/fall through as they already said no
+            if(in_barcode == "user_false") return;
+            // We have a barcode and there was no error?
+            if(in_barcode && typeof in_barcode.ilsevent == 'undefined') {
+                // Check if it was an actor barcode (will never happen unless actor was added above)
+                if(in_barcode.type == 'actor') {
+                    // Go to new patron (do not pass go, do not collect $200, do not prompt user)
+                    var horizontal_interface = String( obj.data.hash.aous['ui.circ.patron_summary.horizontal'] ) == 'true';
+                    var loc = xulG.url_prefix( horizontal_interface ? urls.XUL_PATRON_HORIZ_DISPLAY : urls.XUL_PATRON_DISPLAY );
+                    xulG.set_tab( loc, {}, { 'barcode' : in_barcode.barcode } );
+                    return;
+                }
+                params.barcode = in_barcode.barcode;
+            }
 
             if ( obj.test_barcode(params.barcode) ) { /* good */ } else { /* bad */ return; }
 
index bff79af..ca91ad2 100644 (file)
@@ -1049,7 +1049,15 @@ circ.copy_status.prototype = {
         try {
             try { document.getElementById('last_scanned').setAttribute('value',''); } catch(E) {}
             if (!barcode) {
+                // No barcode provided = get barcode
                 barcode = obj.controller.view.copy_status_barcode_entry_textbox.value;
+                // Complete the barcode - just items
+                var barcode_object = xulG.get_barcode(window, 'asset', barcode);
+                // user_false is user said "None of the above" - Abort before other errors/prompts can result
+                if(barcode_object == "user_false") return;
+                // Got a barcode and no error? Use the barcode. Otherwise, fall through with entered barcode.
+                if(barcode_object && typeof barcode_object.ilsevent == 'undefined')
+                    barcode = barcode_object.barcode;
             }
             if (!barcode) { return; }
             if (barcode) {
index 0de36de..557995a 100644 (file)
 
                 tb.disabled = true;
                 document.getElementById('progress').setAttribute('hidden','false');
-                net.simple_request('FM_AU_ID_RETRIEVE_VIA_BARCODE_OR_USERNAME',[ ses(), barcode, null ],
-                    function(req) {
-                        document.getElementById('progress').setAttribute('hidden','true');
-                        tb.disabled = false; tb.select(); tb.focus(); ;
-                        var robj = req.getResultObject();
-                        if (typeof robj.ilsevent != 'undefined') {
-                            sound.bad();
-                            switch(Number(robj.ilsevent)) {
-                                case 1002 /* ACTOR_USER_NOT_FOUND */: 
-                                    add_msg($("patronStrings").getFormattedString('staff.patron.barcode_entry.barcode_not_found', [barcode]));
-                                break;
-                                default:
-                                    add_msg($("patronStrings").getFormattedString('staff.patron.barcode_entry.barcode_retrieval_problem', [barcode, js2JSON(robj)]));
-                            }
-                            return;
-                        }
+                // Auto-complete the barcode, users only. Handily, looks up all we need to know in the process.
+                var barcode_object = xulG.get_barcode(window, 'actor', barcode);
+                document.getElementById('progress').setAttribute('hidden','true');
+                tb.disabled = false; tb.select(); tb.focus(); ;
+                // user_false means the user said "None of the above", so abort without further prompts/actions
+                if(barcode_object == "user_false") return;
+                if(barcode_object == false) {
+                    // Boolean false means the barcode was not found, and the user wasn't prompted.
+                    sound.bad();
+                    add_msg($("patronStrings").getFormattedString('staff.patron.barcode_entry.barcode_not_found', [barcode]));
+                    return;
+                }
+                else if(typeof barcode_object.ilsevent != 'undefined') {
+                    // Getting an ilsevent error otherwise means something, likely permissions issues, went wrong
+                    sound.bad();
+                    add_msg($("patronStrings").getFormattedString('staff.patron.barcode_entry.barcode_retrieval_problem', [barcode, js2JSON(barcode_object)]));
+                    return;
+                }
 
-                        if (g.data.user_org_unit_opt_in_enabled) {
-                            var r = net.simple_request('USER_ORG_UNIT_OPT_IN_CHECK',[ ses(), robj ]);
-                            if (typeof r.ilsevent != 'undefined') {
-                                throw(r);
-                            } else {
+                if (g.data.user_org_unit_opt_in_enabled) {
+                    var r = net.simple_request('USER_ORG_UNIT_OPT_IN_CHECK',[ ses(), barcode_object.id ]);
+                    if (typeof r.ilsevent != 'undefined') {
+                        throw(r);
+                    } else {
 
-                                if (r == 0) {
+                        if (r == 0) {
 
-                                    JSAN.use('patron.util');
-                                    var parts = patron.util.retrieve_name_via_id( ses(), robj );
+                            JSAN.use('patron.util');
+                            var parts;
+                            // Handily, if the user was prompted, we should already have the user's name information returned from autocomplete
+                            // Use it if there, as it saves us a network call.
+                            if(barcode_object.request_data) parts = barcode_object.request_data;
+                            // Otherwise, make the network call
+                            else parts = patron.util.retrieve_name_via_id( ses(), barcode_object.id );
     
-                                    if (0 != g.error.yns_alert(
-                                            $("patronStrings").getFormattedString('staff.patron.barcode_entry.consent_from_patron',
-                                                [parts[0], parts[1] + (parts[2] ? ' ' + parts[2] : ''), g.data.hash.aou[ parts[3] ].name(), g.data.hash.aou[ parts[3] ].shortname()]),
-                                            $("patronStrings").getString('staff.patron.barcode_entry.patron_consent_title'),
-                                            $("patronStrings").getString('staff.patron.barcode_entry.patron_consent_accept'),
-                                            $("patronStrings").getString('staff.patron.barcode_entry.patron_consent_deny'), null,
-                                            $("patronStrings").getString('staff.patron.barcode_entry.patron_consent_confirm')
-                                        )
-                                    ) {
-                                        tb.select(); tb.focus();
-                                        return;
-                                    } else {
-                                        var c = net.simple_request('USER_ORG_UNIT_OPT_IN_CREATE',[ ses(), robj ]);
-                                        if (typeof c.ilsevent != 'undefined') throw(r);
-                                    }
-                                }
-    
-                                sound.good();
-                                spawn(barcode);
+                            if (0 != g.error.yns_alert(
+                                    $("patronStrings").getFormattedString('staff.patron.barcode_entry.consent_from_patron',
+                                        [parts[0], parts[1] + (parts[2] ? ' ' + parts[2] : ''), g.data.hash.aou[ parts[3] ].name(), g.data.hash.aou[ parts[3] ].shortname()]),
+                                    $("patronStrings").getString('staff.patron.barcode_entry.patron_consent_title'),
+                                    $("patronStrings").getString('staff.patron.barcode_entry.patron_consent_accept'),
+                                    $("patronStrings").getString('staff.patron.barcode_entry.patron_consent_deny'), null,
+                                    $("patronStrings").getString('staff.patron.barcode_entry.patron_consent_confirm')
+                                )
+                            ) {
+                                tb.select(); tb.focus();
+                                return;
+                            } else {
+                                var c = net.simple_request('USER_ORG_UNIT_OPT_IN_CREATE',[ ses(), barcode_object.id ]);
+                                if (typeof c.ilsevent != 'undefined') throw(r);
                             }
-                        } else {
-                            sound.good();
-                            spawn(barcode);
                         }
+    
+                        sound.good();
+                        spawn(barcode_object.id, barcode_object.barcode);
                     }
-                );
+                } else {
+                    sound.good();
+                    spawn(barcode_object.id, barcode_object.barcode);
+                }
             } catch(E) {
                 tb.select(); tb.focus();
                 g.error.standard_unexpected_error_alert('barcode_entry.xul',E);
             d.setAttribute('style','color: red');
         }
 
-        function spawn(barcode) {
-            if (xul_param('perm_editor')) { spawn_perm_editor(barcode); } else { spawn_checkout(barcode); }
+        function spawn(id, barcode) {
+            if (xul_param('perm_editor')) { spawn_perm_editor(id); } else { spawn_checkout(barcode); }
         }
 
         function spawn_checkout(barcode) {
             }
         }
 
-        function spawn_perm_editor(barcode) {
+        function spawn_perm_editor(id) {
             try {
-                JSAN.use('patron.util'); var patron_obj = patron.util.retrieve_fleshed_au_via_barcode( ses(), barcode );
-                var loc = urls.XUL_USER_PERM_EDITOR + '?ses=' + window.escape(ses()) + '&usr=' + patron_obj.id();
+                var loc = urls.XUL_USER_PERM_EDITOR + '?ses=' + window.escape(ses()) + '&usr=' + id;
                 if (typeof window.xulG == 'object' && typeof window.xulG.set_tab == 'function') {
                     window.xulG.set_tab( loc, {}, {} );
                 } else {
index 0f7f9ac..f927512 100644 (file)
@@ -831,7 +831,9 @@ patron.display.prototype = {
                                 }
                             )
                         }
-                    }
+                    },
+                    'get_barcode' : xulG.get_barcode,
+                    'url_prefix' : xulG.url_prefix
                 }
             );
             netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");