<div class="col-lg-2">
<ng-container *ngIf="ctx.holdMeta.parts.length">
<select class="form-select" (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>
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)
let hType = this.holdType;
let hTarget = ctx.holdTarget;
+
+ if (ctx.holdMeta.parts && !ctx.holdMeta.part) {
+ ctx.holdMeta.part = (ctx.holdMeta.part_required ? ctx.holdMeta.parts[0] : null);
+ }
+
if (hType === 'T' && ctx.holdMeta.part) {
// A Title hold morphs into a Part hold at hold placement time
// if a part is selected. This can happen on a per-hold basis
copy?: IdlObject;
issuance?: IdlObject;
metarecord_filters?: any;
+ part_required?: boolean;
}
/** Service for performing various hold-related actions */
# $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.api_require_monographic_part_when_present is
+ # enabled, 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.api_require_monographic_part_when_present');
+ $part_required = ($part_required_flag and $U->is_true($part_required_flag->enabled));
+ 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
issuance => $issuance,
part => $part,
parts => [],
+ part_required => 'f',
bibrecord => $bre,
metarecord => $metarecord,
metarecord_filters => {}
{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.api_require_monographic_part_when_present is
+ # enabled, or the library setting circ.holds.ui_require_monographic_part_when_present
+ # is true for any involved owning_library, then also require part selection.
+ my $part_required = 0;
+ if ($meta->{parts}) {
+ my $part_required_flag = $e->retrieve_config_global_flag('circ.holds.api_require_monographic_part_when_present');
+ $part_required = ($part_required_flag and $U->is_true($part_required_flag->enabled));
+ if (!$part_required) {
+ my $resp = $e->json_query({
+ select => {
+ acn => ['owning_lib']
+ },
+ from => {acn => {acp => {type => 'left'}}},
+ where => {
+ '+acp' => {
+ '-or' => [
+ {deleted => 'f'},
+ {id => undef} # left join
+ ]
+ },
+ '+acn' => {deleted => 'f', record => $bre->id}
+ },
+ distinct => 't'
+ });
+ my $org_ids = [map {$_->{owning_lib}} @$resp];
+ foreach my $org (@$org_ids) { # FIXME: worth shortcutting/optimizing?
+ if ($U->ou_ancestor_setting_value($org, 'circ.holds.ui_require_monographic_part_when_present')) {
+ $part_required = 1;
+ }
+ }
+ }
+ 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}) {
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.api_require_monographic_part_when_present');
+ $part_required_flag = ($part_required_flag and $U->is_true($part_required_flag->enabled));
+ $ctx->{part_required_when_present_global_flag} = $part_required_flag;
# capture some commonly accessed pages
$ctx->{home_page} = $ctx->{proto} . '://' . $ctx->{hostname} . $self->ctx->{opac_root} . "/home";
{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.api_require_monographic_part_when_present is
+ # enabled, or the library setting circ.holds.ui_require_monographic_part_when_present
+ # is active for any item owning library associated with the bib, then any configured
+ # parts for the bib is enough to disallow title holds (aka require part selection).
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 = $ctx->{part_required_when_present_global_flag};
+ if (!$part_required) {
+ my $resp = $e->json_query({
+ select => {
+ acn => ['owning_lib']
+ },
+ from => {acn => {acp => {type => 'left'}}},
+ where => {
+ '+acp' => {
+ '-or' => [
+ {deleted => 'f'},
+ {id => undef} # left join
+ ]
+ },
+ '+acn' => {deleted => 'f', record => $rec->id}
+ },
+ distinct => 't'
+ });
+ my $org_ids = [map {$_->{owning_lib}} @$resp];
+ foreach my $org (@$org_ids) { # FIXME: worth shortcutting/optimizing?
+ if ($self->ctx->{get_org_setting}->($org, 'circ.holds.ui_require_monographic_part_when_present')) {
+ $part_required = 1;
+ }
}
- });
- $part_required = 1 if $np_copies->[0]->{count} == 0;
+ }
+ 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->({
'integer'
);
+-- eparts
+
+INSERT INTO config.global_flag (name, value, enabled, label)
+VALUES (
+ 'circ.holds.api_require_monographic_part_when_present',
+ NULL,
+ FALSE,
+ oils_i18n_gettext(
+ 'circ.holds.api_require_monographic_part_when_present',
+ 'Holds: Require Monographic Part When Present for hold check.',
+ 'cgf', 'label'
+ )
+);
+
+INSERT INTO config.org_unit_setting_type (name, label, grp, description, datatype)
+VALUES (
+ 'circ.holds.ui_require_monographic_part_when_present',
+ oils_i18n_gettext('circ.holds.ui_require_monographic_part_when_present',
+ 'Require Monographic Part when Present',
+ 'coust', 'label'),
+ 'circ',
+ oils_i18n_gettext('circ.holds.ui_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
--- /dev/null
+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.api_require_monographic_part_when_present',
+ NULL,
+ FALSE,
+ oils_i18n_gettext(
+ 'circ.holds.api_require_monographic_part_when_present',
+ 'Holds: Require Monographic Part When Present for hold check.',
+ 'cgf', 'label'
+ )
+);
+
+INSERT INTO config.org_unit_setting_type (name, label, grp, description, datatype)
+VALUES (
+ 'circ.holds.ui_require_monographic_part_when_present',
+ oils_i18n_gettext('circ.holds.ui_require_monographic_part_when_present',
+ 'Require Monographic Part when Present',
+ 'coust', 'label'),
+ 'circ',
+ oils_i18n_gettext('circ.holds.ui_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;
"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")
"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")
"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",
* Adds new Local Administration entries for Item Statistical Categories Editor and Patron Statistical Categories Editor, which are angularized interfaces.
* Tweaks eg-grids to underline hyperlinks within cells. This potentially affects multiple interfaces.
* eg-org-family-select now supports persistKey
+* 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.api_require_monographic_part_when_present
+ Holds: Require Monographic Part When Present for hold check.
+ * circ.holds.ui_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 for any involved owning library for the
+ bib or 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. It should be noted that if the library setting is set
+ below the consortium level, Title level holds may be allowed for some libraries and not others.
+
+ At the API level, we consider just the global flag and will throw a TITLE_HOLD_WHEN_MONOGRAPHIC_PART_REQUIRED
+ event for a title hold request when there are items with monographic parts on the bib. It is possible for
+ the library settings and the global flag to differ, but the global flag will catch every instance of hold
+ placement including those by third party callers, SIP, etc.