LP1965446 Option to Disable Title-Level Holds on Bib Records with Parts collab/phasefx/lp1965446-eparts
authorJason Etheridge <jason@EquinoxOLI.org>
Thu, 12 Jan 2023 06:16:29 +0000 (01:16 -0500)
committerJason Etheridge <jason@EquinoxOLI.org>
Thu, 12 Jan 2023 16:48:10 +0000 (11:48 -0500)
This feature adds one global flag and one library setting, respectively:

    * circ.holds.allow_require_monographic_part_when_present
      Holds: Enable the Require Monographic Part When Present library setting.
    * circ.holds.require_monographic_part_when_present
      Require Monographic Part when Present

Normally the selection of a monographic part during hold placement is optional if there is at least one copy
on the bib without a monographic part.  A true value for this setting and for the global flag will require
part selection even under this condition.  This essentially removes the All/Any Parts option from the part
selection drop-down, for both versions of the public catalog (TPAC and BOOPAC), and for the Angular staff
catalog interface.

We also test for this at the API level, which will catch situations where the UI may have stale information
on the state of the flag and setting, or when the API is being invoked by third parties under this condition.
We will throw a TITLE_HOLD_WHEN_MONOGRAPHIC_PART_REQUIRED event if a title hold is effectively disallowed
based on the presence of monographic parts and lack of part selection.

Signed-off-by: Jason Etheridge <jason@EquinoxOLI.org>
Open-ILS/src/eg2/src/app/staff/catalog/hold/hold.component.html
Open-ILS/src/eg2/src/app/staff/catalog/hold/hold.component.ts
Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Holds.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.eparts.sql [new file with mode: 0644]
Open-ILS/src/templates-bootstrap/opac/parts/hold_error_messages.tt2
Open-ILS/src/templates/opac/parts/hold_error_messages.tt2
Open-ILS/web/js/dojo/openils/circ/nls/selfcheck.js
docs/RELEASE_NOTES_NEXT/miscellaneous.adoc

index d447492..932d042 100644 (file)
         <div class="col-lg-2">
           <ng-container *ngIf="ctx.holdMeta.parts.length">
             <select class="form-control"  (change)="setPart(ctx, $event)"
-              [ngModel]="ctx.holdMeta.part ? ctx.holdMeta.part.id() : ''">
-              <option value="" i18n>Any Part</option>
+              [ngModel]="ctx.holdMeta.part ? ctx.holdMeta.part.id() : (ctx.holdMeta.part_required ? ctx.holdMeta.parts[0].id() : '')">
+              <option *ngIf="!ctx.holdMeta.part_required" value="" i18n>Any Part</option>
               <option *ngFor="let part of ctx.holdMeta.parts"
                 value="{{part.id()}}">{{part.label()}}</option>
             </select>
index a74bc37..9b7d1ff 100644 (file)
@@ -245,7 +245,7 @@ export class HoldComponent implements OnInit {
     getTargetMeta(): Promise<any> {
 
         return new Promise(resolve => {
-            this.holds.getHoldTargetMeta(this.holdType, this.holdTargets)
+            this.holds.getHoldTargetMeta(this.holdType, this.holdTargets, this.auth.user().ws_ou())
             .subscribe(
                 meta => {
                     this.holdContexts.filter(ctx => ctx.holdTarget === meta.target)
index 6e9878b..bd61588 100644 (file)
@@ -2877,6 +2877,48 @@ sub _check_title_hold_is_possible {
     # $holdable_formats is now unused. We pre-filter the MR's records.
 
     my $e = new_editor();
+
+    # T holds on records that have parts are normally OK, but if the record has
+    # no non-part copies, the hold will ultimately fail, so let's test for that.
+    #
+    # If the global flag circ.holds.allow_require_monographic_part_when_present is
+    # enabled, and the library setting circ.holds.require_monographic_part_when_present
+    # is active, then any configured parts for the bib is enough to disallow title holds.
+    my $part_required = 0;
+    my $parts = $e->search_biblio_monograph_part(
+        {
+            record => $titleid
+        }, {idlist=>1} );
+
+    if ($parts) {
+        my $part_required_flag = $e->retrieve_config_global_flag('circ.holds.allow_require_monographic_part_when_present');
+        $part_required_flag = ($part_required_flag and $U->is_true($part_required_flag->enabled));
+        $part_required = $part_required_flag && $U->ou_ancestor_setting_value($request_lib->id, 'circ.holds.require_monographic_part_when_present');
+        if (!$part_required) {
+            my $np_copies = $e->json_query({
+                select => { acp => [{column => 'id', transform => 'count', alias => 'count'}]},
+                from => {acp => {acn => {}, acpm => {type => 'left'}}},
+                where => {
+                    '+acp' => {deleted => 'f'},
+                    '+acn' => {deleted => 'f', record => $titleid},
+                    '+acpm' => {id => undef}
+                }
+            });
+            $part_required = 1 if $np_copies->[0]->{count} == 0;
+        }
+    }
+    if ($part_required) {
+        $logger->info("title hold when monographic part required");
+        return (
+            0, 0, [
+                new OpenILS::Event(
+                    "TITLE_HOLD_WHEN_MONOGRAPHIC_PART_REQUIRED",
+                    "payload" => {"fail_part" => "monographic_part_required"}
+                )
+            ]
+        );
+    }
+
     my %org_filter = create_ranged_org_filter($e, $selection_ou, $depth);
 
     # this monster will grab the id and circ_lib of all of the "holdable" copies for the given record
@@ -5153,6 +5195,7 @@ sub hold_metadata {
             issuance => $issuance,
             part => $part,
             parts => [],
+            part_required => 'f',
             bibrecord => $bre,
             metarecord => $metarecord,
             metarecord_filters => {}
@@ -5178,6 +5221,35 @@ sub hold_metadata {
                     {order_by => {bmp => 'label_sortkey'}}
                 ]
             );
+
+            # T holds on records that have parts are normally OK, but if the record has
+            # no non-part copies, the hold will ultimately fail.  When that happens,
+            # require the user to select a part.
+            #
+            # If the global flag circ.holds.allow_require_monographic_part_when_present is
+            # enabled, and the library setting circ.holds.require_monographic_part_when_present
+            # is active, then any configured parts for the bib is enough to disallow title holds.
+            my $part_required = 0;
+            if ($meta->{parts}) {
+                my $part_required_flag = $e->retrieve_config_global_flag('circ.holds.allow_require_monographic_part_when_present');
+                $part_required_flag = ($part_required_flag and $U->is_true($part_required_flag->enabled));
+                my $part_required_org = $org_id || $U->get_org_tree->id;
+                $part_required = $part_required_flag
+                    && $U->ou_ancestor_setting_value($part_required_org, 'circ.holds.require_monographic_part_when_present');
+                if (!$part_required) {
+                    my $np_copies = $e->json_query({
+                        select => { acp => [{column => 'id', transform => 'count', alias => 'count'}]},
+                        from => {acp => {acn => {}, acpm => {type => 'left'}}},
+                        where => {
+                            '+acp' => {deleted => 'f'},
+                            '+acn' => {deleted => 'f', record => $bre->id},
+                            '+acpm' => {id => undef}
+                        }
+                    });
+                    $part_required = 1 if $np_copies->[0]->{count} == 0;
+                }
+            }
+            $meta->{part_required} = $part_required;
         }
 
         if ($meta->{metarecord}) {
index fab9dd6..367f096 100644 (file)
@@ -374,6 +374,12 @@ sub load_common {
     my $geo_org = $ctx->{physical_loc} || $self->cgi->param('loc') || $ctx->{aou_tree}->()->id;
     my $geo_sort_for_org = $ctx->{get_org_setting}->($geo_org, 'opac.holdings_sort_by_geographic_proximity');
     $ctx->{geo_sort} = $geo_sort && $U->is_true($geo_sort_for_org);
+    my $part_required_flag = $e->retrieve_config_global_flag('circ.holds.allow_require_monographic_part_when_present');
+    $part_required_flag = ($part_required_flag and $U->is_true($part_required_flag->enabled));
+    my $part_required_org = $ctx->{physical_loc} || $self->cgi->param('locg') || $self->cgi->param('loc') || $ctx->{aou_tree}->()->id;
+    my $part_required_setting = $ctx->{get_org_setting}->($part_required_org, 'circ.holds.require_monographic_part_when_present');
+    my $part_required = $part_required_flag && $part_required_setting;
+    $ctx->{part_required_when_present} = $part_required;
 
     # capture some commonly accessed pages
     $ctx->{home_page} = $ctx->{proto} . '://' . $ctx->{hostname} . $self->ctx->{opac_root} . "/home";
index 78bba4a..49d72ba 100644 (file)
@@ -1619,21 +1619,28 @@ sub load_place_hold {
                     {record => $rec->id}
                 );
 
-                # T holds on records that have parts are OK, but if the record has
-                # no non-part copies, the hold will ultimately fail.  When that
-                # happens, require the user to select a part.
+                # T holds on records that have parts are normally OK, but if the record has
+                # no non-part copies, the hold will ultimately fail.  When that happens,
+                # require the user to select a part.
+                #
+                # If the global flag circ.holds.allow_require_monographic_part_when_present is
+                # enabled, and the library setting circ.holds.require_monographic_part_when_present
+                # is active, then any configured parts for the bib is enough to disallow title holds.
                 my $part_required = 0;
                 if (@$parts) {
-                    my $np_copies = $e->json_query({
-                        select => { acp => [{column => 'id', transform => 'count', alias => 'count'}]},
-                        from => {acp => {acn => {}, acpm => {type => 'left'}}},
-                        where => {
-                            '+acp' => {deleted => 'f'},
-                            '+acn' => {deleted => 'f', record => $rec->id},
-                            '+acpm' => {id => undef}
-                        }
-                    });
-                    $part_required = 1 if $np_copies->[0]->{count} == 0;
+                    $part_required = $ctx->{part_required_when_present};
+                    if (!$part_required) {
+                        my $np_copies = $e->json_query({
+                            select => { acp => [{column => 'id', transform => 'count', alias => 'count'}]},
+                            from => {acp => {acn => {}, acpm => {type => 'left'}}},
+                            where => {
+                                '+acp' => {deleted => 'f'},
+                                '+acn' => {deleted => 'f', record => $rec->id},
+                                '+acpm' => {id => undef}
+                            }
+                        });
+                        $part_required = 1 if $np_copies->[0]->{count} == 0;
+                    }
                 }
 
                 push(@hold_data, $data_filler->({
index ddfabe4..f8238c3 100644 (file)
@@ -21503,6 +21503,33 @@ VALUES (
     'integer'
 );
 
+-- eparts
+
+INSERT INTO config.global_flag (name, value, enabled, label)
+VALUES (
+    'circ.holds.allow_require_monographic_part_when_present',
+    NULL,
+    FALSE,
+    oils_i18n_gettext(
+        'circ.holds.allow_require_monographic_part_when_present',
+        'Holds: Enable the Require Monographic Part When Present library setting.',
+        'cgf', 'label'
+    )
+);
+
+INSERT INTO config.org_unit_setting_type (name, label, grp, description, datatype)
+VALUES (
+    'circ.holds.require_monographic_part_when_present',
+    oils_i18n_gettext('circ.holds.require_monographic_part_when_present',
+        'Require Monographic Part when Present',
+        'coust', 'label'),
+    'circ',
+    oils_i18n_gettext('circ.holds.require_monographic_part_when_present',
+        'Normally the selection of a monographic part during hold placement is optional if there is at least one copy on the bib without a monographic part.  A true value for this setting will require part selection even under this condition.',
+        'coust', 'description'),
+    'bool'
+);
+
 ------------------- Disabled example A/T defintions ------------------------------
 
 -- Create a "dummy" slot when applicable, and trigger the "offer curbside" events
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.eparts.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.eparts.sql
new file mode 100644 (file)
index 0000000..98dd366
--- /dev/null
@@ -0,0 +1,33 @@
+BEGIN;
+
+-- check whether patch can be applied
+SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+-- 950.data.seed-values.sql
+
+INSERT INTO config.global_flag (name, value, enabled, label)
+VALUES (
+    'circ.holds.allow_require_monographic_part_when_present',
+    NULL,
+    FALSE,
+    oils_i18n_gettext(
+        'circ.holds.allow_require_monographic_part_when_present',
+        'Holds: Enable the Require Monographic Part When Present library setting.',
+        'cgf', 'label'
+    )
+);
+
+INSERT INTO config.org_unit_setting_type (name, label, grp, description, datatype)
+VALUES (
+    'circ.holds.require_monographic_part_when_present',
+    oils_i18n_gettext('circ.holds.require_monographic_part_when_present',
+        'Require Monographic Part when Present',
+        'coust', 'label'),
+    'circ',
+    oils_i18n_gettext('circ.holds.require_monographic_part_when_present',
+        'Normally the selection of a monographic part during hold placement is optional if there is at least one copy on the bib without a monographic part.  A true value for this setting will require part selection even under this condition.',
+        'coust', 'description'),
+    'bool'
+);
+
+COMMIT;
index 407ea52..f36a0ac 100755 (executable)
@@ -23,6 +23,7 @@
         "status.holdable" => l("The item is not in a holdable status"),
         "no_item" => l("The system could not find this item"),
         "no_ultimate_items" => l("The system could not find any items to match this hold request"),
+        "monographic_part_required" => l("Title hold request invalid when monographic part required"),
         "no_matchpoint" => l("System rules do not define how to handle this item"),
         "no_user" => l("The system could not find this patron"),
         "transit_range" => l("The item cannot transit this far")
index 407ea52..f36a0ac 100644 (file)
@@ -23,6 +23,7 @@
         "status.holdable" => l("The item is not in a holdable status"),
         "no_item" => l("The system could not find this item"),
         "no_ultimate_items" => l("The system could not find any items to match this hold request"),
+        "monographic_part_required" => l("Title hold request invalid when monographic part required"),
         "no_matchpoint" => l("System rules do not define how to handle this item"),
         "no_user" => l("The system could not find this patron"),
         "transit_range" => l("The item cannot transit this far")
index 438d556..e9e8e60 100644 (file)
@@ -41,6 +41,7 @@
     "FAIL_PART_config_rule_age_hold_protect_prox": "The item is too new to transit this far",
     "FAIL_PART_no_item": "The system could not find this item",
     "FAIL_PART_no_ultimate_items": "The system could not find any items to match this hold request",
+    "FAIL_PART_monographic_part_required": "Title hold request invalid when monographic part required",
     "FAIL_PART_no_matchpoint": "System rules do not define how to handle this item",
     "FAIL_PART_no_user": "The system could not find this patron",
     "FAIL_PART_transit_range": "The item cannot transit this far",
index f650dfb..9df3bf8 100644 (file)
@@ -1 +1,21 @@
 * Add patron home library code as a column to the View Holds grid in the staff catalog record details page (LP#1991726)
+
+* LP1965446 Option to Disable Title-Level Holds on Bib Records with Parts
+
+    This feature adds one global flag and one library setting, respectively:
+
+        * circ.holds.allow_require_monographic_part_when_present
+          Holds: Enable the Require Monographic Part When Present library setting.
+        * circ.holds.require_monographic_part_when_present
+          Require Monographic Part when Present
+
+    Normally the selection of a monographic part during hold placement is optional if there is at least one copy
+    on the bib without a monographic part.  A true value for this setting and for the global flag will require
+    part selection even under this condition.  This essentially removes the All/Any Parts option from the part
+    selection drop-down, for both versions of the public catalog (TPAC and BOOPAC), and for the Angular staff
+    catalog interface.
+
+    We also test for this at the API level, which will catch situations where the UI may have stale information
+    on the state of the flag and setting, or when the API is being invoked by third parties under this condition.
+    We will throw a TITLE_HOLD_WHEN_MONOGRAPHIC_PART_REQUIRED event if a title hold is effectively disallowed
+    based on the presence of monographic parts and lack of part selection.