name="session-name" i18n-placeholder placeholder="Session Name..."/>
</div>
<div class="col-lg-3">
+ <label for="auto-overlay-on-order-copies" i18n>
+ Auto-overlay On-order Cataloging Items
+ </label>
+ </div>
+ <div class="col-lg-3">
+ <input class="form-check-input" type="checkbox"
+ id="auto-overlay-on-order-copies"
+ [disabled]="recordType == 'authority'"
+ [(ngModel)]="autoOverlayOnOrderCopies">
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-lg-3">
<label for="marc-remove-groups" i18n>Remove MARC Field Groups</label>
</div>
<ng-container *ngIf="recordType != 'authority'">
</select>
</div>
</ng-container>
+ <div class="col-lg-3">
+ <label for="auto-overlay-org-unit-copies" i18n>
+ Use Org Unit Matching in Copy to Determine Best Match
+ </label>
+ </div>
+ <div class="col-lg-3">
+ <input class="form-check-input" type="checkbox"
+ id="auto-overlay-org-unit-copies"
+ [disabled]="recordType == 'authority'"
+ [(ngModel)]="autoOverlayOrgUnitCopies">
+ </div>
</div>
<div class="row" *ngIf="!importSelection()">
<div class="col-lg-3">
'mergeOnBestMatch',
'mergeOnSingleMatch',
'autoOverlayAcqCopies',
+ 'autoOverlayOnOrderCopies',
+ 'autoOverlayOrgUnitCopies',
'selectedHoldingsProfile',
'selectedMergeProfile',
'selectedFallThruMergeProfile',
auto_overlay_best_match?: boolean;
auto_overlay_1match?: boolean;
opp_acq_copy_overlay?: boolean;
+ opp_oo_cat_copy_overlay?: boolean;
+ auto_overlay_org_unit_copies?: boolean;
merge_profile?: any;
fall_through_merge_profile?: any;
strip_field_groups?: number[];
mergeOnBestMatch: boolean;
minQualityRatio: number;
autoOverlayAcqCopies: boolean;
+ autoOverlayOnOrderCopies: boolean;
+ autoOverlayOrgUnitCopies: boolean;
// True after the first upload, then remains true.
showProgress: boolean;
auto_overlay_best_match: this.mergeOnBestMatch,
auto_overlay_1match: this.mergeOnSingleMatch,
opp_acq_copy_overlay: this.autoOverlayAcqCopies,
+ opp_oo_cat_copy_overlay: this.autoOverlayOnOrderCopies,
+ auto_overlay_org_unit_copies: this.autoOverlayOrgUnitCopies,
merge_profile: this.selectedMergeProfile,
fall_through_merge_profile: this.selectedFallThruMergeProfile,
strip_field_groups: this.selectedTrashGroups,
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};
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';
my $record;
my $imported = 0;
+
if ($type eq 'bib') {
# strip configured / selected MARC tags from inbound records
);
}
+ 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
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);
$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
SELECT * FROM vandelay.auto_overlay_bib_queue( $1, NULL );
$$ LANGUAGE SQL;
+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;
+ scope_org INT;
+ scope_orgs 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;
+
+ -- Gather all the owning libs for our import items.
+ -- These are our initial scope_orgs.
+ SELECT ARRAY_AGG(DISTINCT owning_lib) INTO scope_orgs
+ FROM vandelay.import_item
+ WHERE queued_record = import_id;
+
+ WHILE CARDINALITY(scope_orgs) > 0 LOOP
+ FOR scope_org IN SELECT * FROM UNNEST(scope_orgs) LOOP
+ -- For each match, get a count of all copies at descendants of our scope org.
+ FOR rec IN SELECT * FROM vandelay.bib_match AS vbm
+ WHERE queued_record = import_id
+ ORDER BY vbm.eg_record DESC
+ 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 IN (SELECT id FROM
+ actor.org_unit_descendants(scope_org))
+ 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 no matching bibs had holdings, gather our next set of orgs to check, and iterate.
+ IF max_copy_count = 0 THEN
+ SELECT ARRAY_AGG(DISTINCT parent_ou) INTO scope_orgs
+ FROM actor.org_unit
+ WHERE id IN (SELECT * FROM UNNEST(scope_orgs))
+ AND parent_ou IS NOT NULL;
+ END IF;
+ END LOOP;
+
+ IF eg_id IS NULL THEN
+ -- Could not determine 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;
+
+ RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
+END;
+$$ LANGUAGE PLPGSQL;
+
CREATE OR REPLACE FUNCTION vandelay.ingest_bib_marc ( ) RETURNS TRIGGER AS $$
DECLARE
value TEXT;
( 614, 'REFRESH_CAROUSEL', oils_i18n_gettext(614,
'Allow a user to refresh carousels', 'ppl', 'description')),
( 615, 'ADMIN_REMOTEAUTH', oils_i18n_gettext( 615,
- 'Administer remote patron authentication', 'ppl', 'description' ))
+ 'Administer remote patron authentication', 'ppl', 'description' )),
+ ( 616, 'IMPORT_USE_ORG_UNIT_COPIES', oils_i18n_gettext( 616,
+ 'Allows users to import records based on the number of org unit copies attached to a record', 'ppl', 'description' )),
+ ( 617, 'IMPORT_ON_ORDER_CAT_COPY', oils_i18n_gettext( 617,
+ 'Allows users to import copies based on the on-order items attached to a record', 'ppl', 'description' ))
;
--- /dev/null
+BEGIN;
+
+INSERT INTO permission.perm_list ( id, code, description ) VALUES
+ ( 616, 'IMPORT_USE_ORG_UNIT_COPIES', oils_i18n_gettext( 616,
+ 'Allows users to import records based on the number of org unit copies attached to a record', 'ppl', 'description' )),
+ ( 617, 'IMPORT_ON_ORDER_CAT_COPY', oils_i18n_gettext( 617,
+ 'Allows users to import copies based on the on-order items attached to a record', 'ppl', 'description' ));
+
+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;
+ scope_org INT;
+ scope_orgs 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;
+
+ -- Gather all the owning libs for our import items.
+ -- These are our initial scope_orgs.
+ SELECT ARRAY_AGG(DISTINCT owning_lib) INTO scope_orgs
+ FROM vandelay.import_item
+ WHERE queued_record = import_id;
+
+ WHILE CARDINALITY(scope_orgs) > 0 LOOP
+ FOR scope_org IN SELECT * FROM UNNEST(scope_orgs) LOOP
+ -- For each match, get a count of all copies at descendants of our scope org.
+ FOR rec IN SELECT * FROM vandelay.bib_match AS vbm
+ WHERE queued_record = import_id
+ ORDER BY vbm.eg_record DESC
+ 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 IN (SELECT id FROM
+ actor.org_unit_descendants(scope_org))
+ 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 no matching bibs had holdings, gather our next set of orgs to check, and iterate.
+ IF max_copy_count = 0 THEN
+ SELECT ARRAY_AGG(DISTINCT parent_ou) INTO scope_orgs
+ FROM actor.org_unit
+ WHERE id IN (SELECT * FROM UNNEST(scope_orgs))
+ AND parent_ou IS NOT NULL;
+ END IF;
+ END LOOP;
+
+ IF eg_id IS NULL THEN
+ -- Could not determine 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;
+
+ RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
+END;
+$$ LANGUAGE PLPGSQL;
+
+COMMIT;
<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>
<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>
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
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;
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;
vlUploadQueueImportNoMatch.checked ||
vlUploadQueueAutoOverlayExact.checked ||
vlUploadQueueAutoOverlay1Match.checked ||
- vlUploadQueueAutoOverlayBestMatch.checked ) {
+ vlUploadQueueAutoOverlayBestMatch.checked ||
+ vlUploadQueueAutoOverlayOrgUnitCopies.checked ) {
vlImportRecordQueue(
currentType,
--- /dev/null
+New Options for Importing Copies
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Two new options for importing holdings have been added to MARC Batch
+Import/Export:
+
+. **Auto-overlay On-order Cataloguing Copies**: This is similar to
+ "Auto-overlay In-process Acquisitions Copies," but for copies that were not
+ created from an acquisitions workflow. Holdings information in the incoming
+ record will be used to overlay any existing On Order copies for the matching
+ record which belong to the owning library defined in the Holdings Import
+ Profile. The Holdings Import Profile is also used to match incoming to
+ existing copies, if possible; otherwise, On Order copies are overlaid in the
+ order they were created. The call number will also be overlaid if the
+ incoming record provides one.
+. **Use Org Unit Matching in Copy to Determine Best Match**: When there are
+ multiple potential matching records, this feature allows the user to
+ automatically select the record which has the most copies at libraries near
+ the importing library in the org tree. That is, starting at the importing
+ library, it climbs the org tree, gradually expanding the scope at which it
+ checks for holdings on matching records; once holdings are found, the record
+ with the most holdings at that scope is selected for overlay. If there are
+ no matching records with holdings, then the default best match overlay is
+ attempted.
+
+Permissions
++++++++++++
+
+Two new permissions control the use of these new features:
+
+* IMPORT_ON_ORDER_CAT_COPY
+* IMPORT_USE_ORG_UNIT_COPIES
+