LP#1444644 Copy Import Development Work user/ldw/RT21151_to_LP1444644
authorLiam Whalen <liam.whalen@bc.libraries.coop>
Thu, 2 Oct 2014 02:17:34 +0000 (19:17 -0700)
committerLiam Whalen <liam.whalen@bc.libraries.coop>
Wed, 15 Apr 2015 20:51:26 +0000 (13:51 -0700)
This is the first commit in the copy import development work outlined
here: https://bugs.launchpad.net/evergreen/+bug/1444644

It copies the Overlay In-process Acquisition Copie code, with
modifications to fit the specificiations outlined on Launchpad.

This process first checks the number of copies belonging to a record,
whose volumes' owning_lib matches the OU specified in the Holdings
Import Profile.  If a match is not found, then it searches the OU's
federation for copies.  The record with the largest number of copies
is chosen as the record to be overlaid.  If there are two records
with the same number of copies, then the record with the largest
biblio.record_entry.id is chosen for overlay.

Finally, if no records have copies relevant to the OU or its
Federation, then the default best match overlay is attempted.

The vandelay.js and its corresponding upload.tt2 file needed
modifications in order to trigger the code that attempts to Auto
Overlay according the the number of copies per OU.

Two new perms were added to control the use of Auto Overlaying
On-order copies and Using Org Copies to Determine Best Match.

The UI modifications to the Vandelay Onorder Overlay Copy Import
Development work are also included in this commit.

Signed-off-by: Liam Whalen <liam.whalen@bc.libraries.coop>
Conflicts:
Open-ILS/src/perlmods/lib/OpenILS/Application/Vandelay.pm

Open-ILS/src/perlmods/lib/OpenILS/Application/Vandelay.pm
Open-ILS/src/sql/Pg/upgrade/XXXX.data.onorder-overlay-perms.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.vandelay.auto_overlay_org_unit_copies.sql [new file with mode: 0644]
Open-ILS/src/templates/vandelay/inc/queue.tt2
Open-ILS/src/templates/vandelay/inc/upload.tt2
Open-ILS/web/js/ui/default/vandelay/vandelay.js

index 588642d..84f0197 100644 (file)
@@ -906,6 +906,7 @@ sub import_record_list_impl {
     my $auto_overlay_exact = $$args{auto_overlay_exact};
     my $auto_overlay_1match = $$args{auto_overlay_1match};
     my $auto_overlay_best = $$args{auto_overlay_best_match};
+    my $auto_overlay_org_unit_copies = $$args{auto_overlay_org_unit_copies};
     my $match_quality_ratio = $$args{match_quality_ratio};
     my $merge_profile = $$args{merge_profile};
     my $ft_merge_profile = $$args{fall_through_merge_profile};
@@ -916,6 +917,7 @@ sub import_record_list_impl {
     my $overlay_func = 'vandelay.overlay_bib_record';
     my $auto_overlay_func = 'vandelay.auto_overlay_bib_record';
     my $auto_overlay_best_func = 'vandelay.auto_overlay_bib_record_with_best';
+    my $auto_overlay_org_unit_copies_func = 'vandelay.auto_overlay_org_unit_copies';
     my $retrieve_func = 'retrieve_vandelay_queued_bib_record';
     my $update_func = 'update_vandelay_queued_bib_record';
     my $search_func = 'search_vandelay_queued_bib_record';
@@ -985,6 +987,7 @@ sub import_record_list_impl {
         my $record;
         my $imported = 0;
 
+
         if ($type eq 'bib') {
             # strip configured / selected MARC tags from inbound records
 
@@ -1104,6 +1107,30 @@ sub import_record_list_impl {
                 );
             }
 
+            if(!$imported and !$error and $auto_overlay_org_unit_copies and scalar(@{$rec->matches}) > 0 ) {
+                # caller says to overlay depending on the number of copies attached to a record, whose OU
+                # matches the OU of the import record's Holding's Import Profile.
+                
+                my $perm = 'IMPORT_USE_ORG_UNIT_COPIES';
+                my $rec_ou = $e->requestor->ws_ou;
+
+                if (!$e->allowed($perm, $rec_ou)) {
+                    return $e->die_event;
+                }
+
+                ($imported, $error, $rec) = try_auto_overlay(
+                    $e, $type,
+                    $report_args, 
+                    $auto_overlay_org_unit_copies_func,
+                    $retrieve_func,
+                    $rec_class,
+                    $rec_id, 
+                    $match_quality_ratio, 
+                    $merge_profile, 
+                    $ft_merge_profile
+                );
+            }
+
             if(!$imported and !$error and $import_no_match and scalar(@{$rec->matches}) == 0) {
             
                 # No overlay / merge occurred.  Do a traditional record import by creating a new record
@@ -1624,6 +1651,7 @@ sub import_record_asset_list_impl {
         my $auto_callnumber = {};
 
         my $opp_acq_copy_overlay = $args->{opp_acq_copy_overlay};
+        my $opp_oo_cat_copy_overlay = $args->{opp_oo_cat_copy_overlay};
         my @overlaid_copy_ids;
         for my $item_id (@$item_ids) {
             my $e = new_editor(requestor => $requestor, xact => 1);
@@ -1713,6 +1741,58 @@ sub import_record_asset_list_impl {
                     $copy = $acqlid->eg_copy_id;
                     push(@overlaid_copy_ids, $copy->id);
                 }
+            } elsif ($opp_oo_cat_copy_overlay) { # we are going to "opportunistically" overlay received, On-order catalogue copies
+                
+                my $perm = 'IMPORT_ON_ORDER_CAT_COPY';
+                my $rec_ou = $e->requestor->ws_ou;
+
+                if (!$e->allowed($perm, $rec_ou)) {
+                    return $e->die_event;
+                }
+
+                my $query; 
+                if ($item->copy_number) {
+                    $query = [
+                        {
+                            "status" => OILS_COPY_STATUS_ON_ORDER,
+                            "copy_number" => $item->copy_number,
+                            "+acn" => [{"owning_lib" => $item->owning_lib}, 
+                                {"record" => $rec->imported_as}]
+                        },
+                        {
+                            "join" => "acn",
+                            "flesh" => 1,
+                            "flesh_fields" => {
+                                "acp" => ["call_number"]
+                            }
+                        }
+                    ];
+                } else {
+                    #see if we have copies in vandelay.import_item that
+                    #belong to the current record, but do not have $item->copy_number defined
+                    #in their on-order records.  Retrieve them by create date from oldest to
+                    #newest ORDER BY acp.create_date ASC
+                    $query = [
+                        {
+                            "status" => OILS_COPY_STATUS_ON_ORDER,
+                            "+acn" => [{"record" => $rec->imported_as}, 
+                                {"owning_lib" => $item->owning_lib}]
+                        },
+                        {
+                            "join" => "acn",
+                            "flesh" => 1,
+                            "flesh_fields" => {
+                                "acp" => ["call_number"]
+                            },
+                            "order_by" => { "acp" => "create_date" }
+                        }
+                    ];
+                }
+                # don't overlay the same copy twice
+                $query->[0]{"+acp"}{"id"} = {"not in" => \@overlaid_copy_ids} if @overlaid_copy_ids;
+                if ($copy = $e->search_asset_copy($query)->[0]) {
+                    push(@overlaid_copy_ids, $copy->id);
+                } 
             }
 
             if ($copy) { # we found a copy to overlay
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.onorder-overlay-perms.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.onorder-overlay-perms.sql
new file mode 100644 (file)
index 0000000..e1953aa
--- /dev/null
@@ -0,0 +1,24 @@
+BEGIN;
+
+INSERT INTO permission.perm_list ( code, description ) VALUES
+       ('IMPORT_USE_ORG_UNIT_COPIES', 'Allows users to import records based on the number of org unit copies attached toa record');
+
+INSERT INTO permission.perm_list ( code, description ) VALUES
+       ('IMPORT_ON_ORDER_CAT_COPY', 'Allows users to import copies based on the on-order items attached to a record');
+
+INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
+       SELECT
+               pgt.id, perm.id, aout.depth, FALSE 
+       FROM  
+               permission.grp_tree pgt,
+               permission.perm_list perm, 
+               actor.org_unit_type aout
+       WHERE 
+               pgt.name = 'SITKA Staff' AND
+               aout.name = 'Consortium' AND
+               perm.code IN (
+                       'IMPORT_USE_ORG_UNIT_COPIES', 
+                       'IMPORT_ON_ORDER_CAT_COPY'
+               );
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.vandelay.auto_overlay_org_unit_copies.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.vandelay.auto_overlay_org_unit_copies.sql
new file mode 100644 (file)
index 0000000..c24386b
--- /dev/null
@@ -0,0 +1,96 @@
+BEGIN;
+
+CREATE OR REPLACE FUNCTION vandelay.auto_overlay_org_unit_copies ( import_id BIGINT, merge_profile_id INT, lwm_ratio_value_p NUMERIC ) RETURNS BOOL AS $$
+DECLARE
+    eg_id           BIGINT;
+    match_count     INT;
+    rec             vandelay.bib_match%ROWTYPE;
+    v_owning_lib    INT;
+    owning_fed      INT;
+    copy_count      INT := 0;
+    max_copy_count  INT := 0;
+BEGIN
+
+    PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
+
+    IF FOUND THEN
+        -- RAISE NOTICE 'already imported, cannot auto-overlay'
+        RETURN FALSE;
+    END IF;
+
+    FOR rec IN SELECT * FROM vandelay.bib_match AS vbm
+        WHERE queued_record = import_id
+        ORDER BY vbm.eg_record DESC
+    LOOP
+        FOR v_owning_lib IN SELECT DISTINCT owning_lib
+            FROM vandelay.import_item AS vii
+            INNER JOIN vandelay.queued_bib_record AS vqbr
+                ON vii.record = vqbr.id
+            WHERE vqbr.id = rec.queued_record
+        LOOP
+            SELECT COUNT(acp.id) INTO copy_count
+                FROM asset.copy AS acp
+                INNER JOIN asset.call_number AS acn
+                    ON acp.call_number = acn.id
+                WHERE acn.owning_lib = v_owning_lib 
+                AND acn.record = rec.eg_record
+                AND acp.deleted = FALSE;
+
+            IF copy_count > max_copy_count THEN
+                max_copy_count := copy_count;
+                eg_id = rec.eg_record;
+            END IF;
+        END LOOP;
+    END LOOP;
+
+    IF max_copy_count = 0 THEN
+        -- No copies for the owing lib, check the federation
+        FOR rec IN SELECT * FROM vandelay.bib_match AS vbm
+            WHERE queued_record = import_id
+            ORDER BY vbm.eg_record DESC
+        LOOP
+            FOR v_owning_lib IN SELECT DISTINCT owning_lib
+                FROM vandelay.import_item AS vii
+                INNER JOIN vandelay.queued_bib_record AS vqbr
+                    ON vii.record = vqbr.id
+                WHERE vqbr.id = rec.queued_record
+            LOOP
+                SELECT id INTO owning_fed
+                FROM actor.org_unit AS aou
+                WHERE parent_ou = (SELECT id FROM
+                    actor.org_unit WHERE parent_ou IS NULL)
+                AND v_owning_lib IN (SELECT id FROM
+                    actor.org_unit_descendants(aou.id));
+
+                SELECT COUNT(acp.id) INTO copy_count
+                    FROM asset.copy AS acp
+                    INNER JOIN asset.call_number AS acn
+                        ON acp.call_number = acn.id
+                    WHERE acn.owning_lib IN (SELECT id FROM
+                        actor.org_unit_descendants(owning_fed))
+                    AND acn.record = rec.eg_record
+                    AND acp.deleted = FALSE;
+
+                IF copy_count > max_copy_count THEN
+                    max_copy_count := copy_count;
+                    eg_id = rec.eg_record;
+                END IF;
+            END LOOP;
+        END LOOP;
+
+        IF max_copy_count = 0 THEN
+            -- Could not detrmine best match via copy count
+            -- fall back to default best match
+            IF (SELECT * FROM vandelay.auto_overlay_bib_record_with_best( import_id, merge_profile_id, lwm_ratio_value_p )) THEN
+                RETURN TRUE;
+            ELSE
+                RETURN FALSE;
+            END IF;
+        END IF;
+    END IF;
+
+    RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
+END;
+$$ LANGUAGE PLPGSQL;
+
+COMMIT;
index e3e2035..c96723f 100644 (file)
                     <td>[% l('Auto-overlay In-process Acquisition Copies') %]</td>
                     <td colspan='4'><input jsId='vlUploadQueueAutoOverlayInprocessAcqCopies2' dojoType='dijit.form.CheckBox'/></td>
                 </tr>
+                <tr>
+                    <td>[% l('Auto-overlay On-order Cataloguing Copies') %]</td>
+                    <td colspan='4'><input jsId='vlUploadQueueAutoOverlayOnorderCatCopies2' dojoType='dijit.form.CheckBox'/></td>
+                </tr>
+
+                <tr>
+                    <td>[% l('Use Org Unit Matching in Copy to Determine Best Match') %]</td>
+                    <td colspan='4'>
+                        <input jsId='vlUploadQueueAutoOverlayOrgUnitCopies2' dojoType='dijit.form.CheckBox'/>
+                    </td>
+                </tr>
 
                 <tr>
                     <td>
index 4db970d..1337304 100644 (file)
             <td>[% l('Auto-overlay In-process Acquisitions Copies') %]</td>
             <td colspan='4'><input jsId='vlUploadQueueAutoOverlayInprocessAcqCopies' dojoType='dijit.form.CheckBox'/></td>
         </tr>
+        <tr>
+            <td>[% l('Auto-overlay On-order Cataloguing  Copies') %]</td>
+            <td colspan='4'><input jsId='vlUploadQueueAutoOverlayOnorderCatCopies' dojoType='dijit.form.CheckBox'/></td>
+        </tr>
+        <tr>
+            <td>[% l('Use Org Unit Matching in Copy to Determine Best Match') %]</td>
+            <td colspan='4'>
+                <input jsId='vlUploadQueueAutoOverlayOrgUnitCopies' dojoType='dijit.form.CheckBox'/>
+            </td>
+        </tr>
 
         <tr><td colspan='2' style='border-bottom:2px solid #888;'></td></tr>
         <tr><td colspan='2' style='padding-bottom: 10px;'></td></tr>
index 64bef54..8ff8874 100644 (file)
@@ -1252,6 +1252,8 @@ function vlHandleQueueItemsAction(action) {
             vlUploadQueueAutoOverlayBestMatch.attr('value',  vlUploadQueueAutoOverlayBestMatch2.attr('value'));
             vlUploadQueueAutoOverlayBestMatchRatio.attr('value',  vlUploadQueueAutoOverlayBestMatchRatio2.attr('value'));
             vlUploadQueueAutoOverlayInprocessAcqCopies.attr('value',  vlUploadQueueAutoOverlayInprocessAcqCopies2.attr('value'));
+            vlUploadQueueAutoOverlayOnorderCatCopies.attr('value',  vlUploadQueueAutoOverlayOnorderCatCopies2.attr('value'));
+            vlUploadQueueAutoOverlayOrgUnitCopies.attr('value',  vlUploadQueueAutoOverlayOrgUnitCopies2.attr('value'));
 
             // attr('value') and various other incantations won't let me set 
             // the value on the checkedmultiselect, so we temporarily swap 
@@ -1284,6 +1286,10 @@ function vlHandleQueueItemsAction(action) {
             vlUploadQueueAutoOverlayBestMatchRatio2.attr('value', '0.0');
             vlUploadQueueAutoOverlayInprocessAcqCopies.attr('value', false);
             vlUploadQueueAutoOverlayInprocessAcqCopies2.attr('value', false);
+            vlUploadQueueAutoOverlayOnorderCatCopies.attr('value', false);
+            vlUploadQueueAutoOverlayOnorderCatCopies2.attr('value', false);
+            vlUploadQueueAutoOverlayOrgUnitCopies.attr('value', false);
+            vlUploadQueueAutoOverlayOrgUnitCopies2.attr('value', false);
 
             // and... swap them back
             vlUploadTrashGroups2 = vlUploadTrashGroups;
@@ -1369,6 +1375,17 @@ function vlImportRecordQueue(type, queueId, recList, onload) {
         vlUploadQueueAutoOverlayInprocessAcqCopies.checked = false;
     }
 
+    if(vlUploadQueueAutoOverlayOnorderCatCopies.checked) {
+        options.opp_oo_cat_copy_overlay = true; //"opp" for opportunistic "oo" for on order
+        vlUploadQueueAutoOverlayOnorderCatCopies.checked = false;
+    }
+
+    if(vlUploadQueueAutoOverlayOrgUnitCopies.checked) {
+        options.auto_overlay_org_unit_copies = true;
+        vlUploadQueueAutoOverlayOrgUnitCopies.checked = false;
+        options.match_quality_ratio = vlUploadQueueAutoOverlayBestMatchRatio.attr('value');
+    }
+
     var profile = vlUploadMergeProfile.attr('value');
     if(profile != null && profile != '') {
         options.merge_profile = profile;
@@ -1427,7 +1444,8 @@ function batchUpload() {
             vlUploadQueueImportNoMatch.checked || 
             vlUploadQueueAutoOverlayExact.checked || 
             vlUploadQueueAutoOverlay1Match.checked ||
-            vlUploadQueueAutoOverlayBestMatch.checked ) {
+            vlUploadQueueAutoOverlayBestMatch.checked ||
+            vlUploadQueueAutoOverlayOrgUnitCopies.checked ) {
 
                 vlImportRecordQueue(
                     currentType,