From: Mike Rylander Date: Mon, 2 Mar 2020 20:49:05 +0000 (-0500) Subject: LP#1841974: Allow adjusted proximity for age-protect X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=93811f4d7e54d92aa679f4415adf257a2459d8fa;p=evergreen%2Fmasslnc.git LP#1841974: Allow adjusted proximity for age-protect When testing prospective transit distance for age-protected items at hold placement time we currently use the direct, baseline proximity between the item circ (or owning) lib and the pickup lib. With this commit, setting a new YAOUS will allow the circ library of the item to specify that a potentially-adjusted, calculated proximity should be used instead. Signed-off-by: Mike Rylander Signed-off-by: Diane Disbro Signed-off-by: Nelson Appell Signed-off-by: Jason Boyer --- diff --git a/Open-ILS/src/sql/Pg/090.schema.action.sql b/Open-ILS/src/sql/Pg/090.schema.action.sql index 685a4c3db5..0a4bb2c6ad 100644 --- a/Open-ILS/src/sql/Pg/090.schema.action.sql +++ b/Open-ILS/src/sql/Pg/090.schema.action.sql @@ -1477,53 +1477,31 @@ query-based fieldsets. Returns NULL if successful, or an error message if not. $$; -CREATE OR REPLACE FUNCTION action.hold_copy_calculated_proximity( - ahr_id INT, - acp_id BIGINT, - copy_context_ou INT DEFAULT NULL - -- TODO maybe? hold_context_ou INT DEFAULT NULL. This would optionally - -- support an "ahprox" measurement: adjust prox between copy circ lib and - -- hold request lib, but I'm unsure whether to use this theoretical - -- argument only in the baseline calculation or later in the other - -- queries in this function. +CREATE OR REPLACE FUNCTION action.copy_calculated_proximity( + pickup INT, + request INT, + vacp_cl INT, + vacp_cm TEXT, + vacn_ol INT, + vacl_ol INT ) RETURNS NUMERIC AS $f$ DECLARE - aoupa actor.org_unit_proximity_adjustment%ROWTYPE; - ahr action.hold_request%ROWTYPE; - acp asset.copy%ROWTYPE; - acn asset.call_number%ROWTYPE; - acl asset.copy_location%ROWTYPE; baseline_prox NUMERIC; - - icl_list INT[]; - iol_list INT[]; - isl_list INT[]; - hpl_list INT[]; - hrl_list INT[]; - + aoupa actor.org_unit_proximity_adjustment%ROWTYPE; BEGIN - SELECT * INTO ahr FROM action.hold_request WHERE id = ahr_id; - SELECT * INTO acp FROM asset.copy WHERE id = acp_id; - SELECT * INTO acn FROM asset.call_number WHERE id = acp.call_number; - SELECT * INTO acl FROM asset.copy_location WHERE id = acp.location; - - IF copy_context_ou IS NULL THEN - copy_context_ou := acp.circ_lib; - END IF; - -- First, gather the baseline proximity of "here" to pickup lib - SELECT prox INTO baseline_prox FROM actor.org_unit_proximity WHERE from_org = copy_context_ou AND to_org = ahr.pickup_lib; + SELECT prox INTO baseline_prox FROM actor.org_unit_proximity WHERE from_org = vacp_cl AND to_org = pickup; -- Find any absolute adjustments, and set the baseline prox to that SELECT adj.* INTO aoupa FROM actor.org_unit_proximity_adjustment adj - LEFT JOIN actor.org_unit_ancestors_distance(copy_context_ou) acp_cl ON (acp_cl.id = adj.item_circ_lib) - LEFT JOIN actor.org_unit_ancestors_distance(acn.owning_lib) acn_ol ON (acn_ol.id = adj.item_owning_lib) - LEFT JOIN actor.org_unit_ancestors_distance(acl.owning_lib) acl_ol ON (acl_ol.id = adj.copy_location) - LEFT JOIN actor.org_unit_ancestors_distance(ahr.pickup_lib) ahr_pl ON (ahr_pl.id = adj.hold_pickup_lib) - LEFT JOIN actor.org_unit_ancestors_distance(ahr.request_lib) ahr_rl ON (ahr_rl.id = adj.hold_request_lib) - WHERE (adj.circ_mod IS NULL OR adj.circ_mod = acp.circ_modifier) AND + LEFT JOIN actor.org_unit_ancestors_distance(vacp_cl) acp_cl ON (acp_cl.id = adj.item_circ_lib) + LEFT JOIN actor.org_unit_ancestors_distance(vacn_ol) acn_ol ON (acn_ol.id = adj.item_owning_lib) + LEFT JOIN actor.org_unit_ancestors_distance(vacl_ol) acl_ol ON (acl_ol.id = adj.copy_location) + LEFT JOIN actor.org_unit_ancestors_distance(pickup) ahr_pl ON (ahr_pl.id = adj.hold_pickup_lib) + LEFT JOIN actor.org_unit_ancestors_distance(request) ahr_rl ON (ahr_rl.id = adj.hold_request_lib) + WHERE (adj.circ_mod IS NULL OR adj.circ_mod = vacp_cm) AND (adj.item_circ_lib IS NULL OR adj.item_circ_lib = acp_cl.id) AND (adj.item_owning_lib IS NULL OR adj.item_owning_lib = acn_ol.id) AND (adj.copy_location IS NULL OR adj.copy_location = acl_ol.id) AND @@ -1546,14 +1524,14 @@ BEGIN -- Now find any relative adjustments, and change the baseline prox based on them FOR aoupa IN - SELECT adj.* + SELECT adj.* FROM actor.org_unit_proximity_adjustment adj - LEFT JOIN actor.org_unit_ancestors_distance(copy_context_ou) acp_cl ON (acp_cl.id = adj.item_circ_lib) - LEFT JOIN actor.org_unit_ancestors_distance(acn.owning_lib) acn_ol ON (acn_ol.id = adj.item_owning_lib) - LEFT JOIN actor.org_unit_ancestors_distance(acl.owning_lib) acl_ol ON (acn_ol.id = adj.copy_location) - LEFT JOIN actor.org_unit_ancestors_distance(ahr.pickup_lib) ahr_pl ON (ahr_pl.id = adj.hold_pickup_lib) - LEFT JOIN actor.org_unit_ancestors_distance(ahr.request_lib) ahr_rl ON (ahr_rl.id = adj.hold_request_lib) - WHERE (adj.circ_mod IS NULL OR adj.circ_mod = acp.circ_modifier) AND + LEFT JOIN actor.org_unit_ancestors_distance(vacp_cl) acp_cl ON (acp_cl.id = adj.item_circ_lib) + LEFT JOIN actor.org_unit_ancestors_distance(vacn_ol) acn_ol ON (acn_ol.id = adj.item_owning_lib) + LEFT JOIN actor.org_unit_ancestors_distance(vacl_ol) acl_ol ON (acn_ol.id = adj.copy_location) + LEFT JOIN actor.org_unit_ancestors_distance(pickup) ahr_pl ON (ahr_pl.id = adj.hold_pickup_lib) + LEFT JOIN actor.org_unit_ancestors_distance(request) ahr_rl ON (ahr_rl.id = adj.hold_request_lib) + WHERE (adj.circ_mod IS NULL OR adj.circ_mod = vacp_cm) AND (adj.item_circ_lib IS NULL OR adj.item_circ_lib = acp_cl.id) AND (adj.item_owning_lib IS NULL OR adj.item_owning_lib = acn_ol.id) AND (adj.copy_location IS NULL OR adj.copy_location = acl_ol.id) AND @@ -1569,6 +1547,47 @@ BEGIN END; $f$ LANGUAGE PLPGSQL; +CREATE OR REPLACE FUNCTION action.hold_copy_calculated_proximity( + ahr_id INT, + acp_id BIGINT, + copy_context_ou INT DEFAULT NULL + -- TODO maybe? hold_context_ou INT DEFAULT NULL. This would optionally + -- support an "ahprox" measurement: adjust prox between copy circ lib and + -- hold request lib, but I'm unsure whether to use this theoretical + -- argument only in the baseline calculation or later in the other + -- queries in this function. +) RETURNS NUMERIC AS $f$ +DECLARE + ahr action.hold_request%ROWTYPE; + acp asset.copy%ROWTYPE; + acn asset.call_number%ROWTYPE; + acl asset.copy_location%ROWTYPE; + + prox NUMERIC; +BEGIN + + SELECT * INTO ahr FROM action.hold_request WHERE id = ahr_id; + SELECT * INTO acp FROM asset.copy WHERE id = acp_id; + SELECT * INTO acn FROM asset.call_number WHERE id = acp.call_number; + SELECT * INTO acl FROM asset.copy_location WHERE id = acp.location; + + IF copy_context_ou IS NULL THEN + copy_context_ou := acp.circ_lib; + END IF; + + SELECT action.copy_calculated_proximity( + ahr.pickup_lib, + ahr.request_lib, + copy_context_ou, + acp.circ_modifier, + acn.owning_lib, + acl.owning_lib + ) INTO prox; + + RETURN prox; +END; +$f$ LANGUAGE PLPGSQL; + CREATE OR REPLACE FUNCTION action.hold_copy_calculated_proximity_update () RETURNS TRIGGER AS $f$ BEGIN NEW.proximity := action.hold_copy_calculated_proximity(NEW.hold,NEW.target_copy); diff --git a/Open-ILS/src/sql/Pg/110.hold_matrix.sql b/Open-ILS/src/sql/Pg/110.hold_matrix.sql index 3b47b62ebf..248460aad1 100644 --- a/Open-ILS/src/sql/Pg/110.hold_matrix.sql +++ b/Open-ILS/src/sql/Pg/110.hold_matrix.sql @@ -230,12 +230,14 @@ DECLARE item_status_object config.copy_status%ROWTYPE; item_location_object asset.copy_location%ROWTYPE; ou_skip actor.org_unit_setting%ROWTYPE; + calc_age_prox actor.org_unit_setting%ROWTYPE; result action.matrix_test_result; hold_test config.hold_matrix_matchpoint%ROWTYPE; use_active_date TEXT; + prox_ou INT; age_protect_date TIMESTAMP WITH TIME ZONE; hold_count INT; - hold_transit_prox INT; + hold_transit_prox NUMERIC; frozen_hold_count INT; context_org_list INT[]; done BOOL := FALSE; @@ -360,7 +362,7 @@ BEGIN RETURN NEXT result; END IF; END IF; - + -- Proximity of user's home_ou to the pickup_lib to see if penalty should be ignored. SELECT INTO pickup_prox prox FROM actor.org_unit_proximity WHERE from_org = user_object.home_ou AND to_org = v_pickup_ou; -- Proximity of user's home_ou to the items' lib to see if penalty should be ignored. @@ -396,7 +398,7 @@ BEGIN AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) ) AND (usp.stop_date IS NULL or usp.stop_date > NOW()) AND csp.block_list LIKE '%CIRC%' LOOP - + result.fail_part := standing_penalty.name; result.success := FALSE; done := TRUE; @@ -433,14 +435,26 @@ BEGIN age_protect_date := item_object.create_date; END IF; IF age_protect_date + age_protect_object.age > NOW() THEN + SELECT INTO calc_age_prox * FROM actor.org_unit_setting WHERE name = 'circ.holds.calculated_age_proximity' AND org_unit = item_object.circ_lib; IF hold_test.distance_is_from_owner THEN - SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number; - SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = v_pickup_ou; + prox_ou := item_cn_object.owning_lib; + ELSE + prox_ou := item_object.circ_lib; + END IF; + IF calc_age_prox.id IS NOT NULL AND calc_age_prox.value = 'true' THEN + SELECT INTO hold_transit_prox action.copy_calculated_proximity( + v_pickup_ou, + v_request_ou, + prox_ou, + item_object.circ_modifier, + item_cn_object.owning_lib, + item_location_object.owning_lib + ); ELSE - SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = v_pickup_ou; + SELECT INTO hold_transit_prox prox::NUMERIC FROM actor.org_unit_proximity WHERE from_org = prox_ou AND to_org = v_pickup_ou; END IF; - IF hold_transit_prox > age_protect_object.prox THEN + IF hold_transit_prox > age_protect_object.prox::NUMERIC THEN result.fail_part := 'config.rule_age_hold_protect.prox'; result.success := FALSE; done := TRUE; diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql index 2543707732..822c6233cf 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -20075,6 +20075,17 @@ INSERT INTO config.org_unit_setting_type 'bool' ); +INSERT into config.org_unit_setting_type +( name, grp, label, description, datatype, fm_class ) VALUES +( 'circ.holds.calculated_age_proximity', 'circ', + oils_i18n_gettext('circ.holds.calculated_age_proximity', + 'Use calculated proximity for age-protection check', + 'coust', 'label'), + oils_i18n_gettext('circ.holds.calculated_age_proximity', + 'When checking whether a copy is viable for a hold based on transit distance, use calculated proximity with adjustments rather than baseline Org Unit proximity.', + 'coust', 'description'), + 'bool', null); + INSERT INTO config.workstation_setting_type (name, grp, datatype, label) VALUES ( diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.calc-prox-for-age_protect-YAOUS.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.calc-prox-for-age_protect-YAOUS.sql new file mode 100644 index 0000000000..84fb089d60 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.calc-prox-for-age_protect-YAOUS.sql @@ -0,0 +1,17 @@ +BEGIN; + +SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version); + +INSERT into config.org_unit_setting_type +( name, grp, label, description, datatype, fm_class ) VALUES +( 'circ.holds.calculated_age_proximity', 'circ', + oils_i18n_gettext('circ.holds.calculated_age_proximity', + 'Use calculated proximity for age-protection check', + 'coust', 'label'), + oils_i18n_gettext('circ.holds.calculated_age_proximity', + 'When checking whether a copy is viable for a hold based on transit distance, use calculated proximity with adjustments rather than baseline Org Unit proximity.', + 'coust', 'description'), + 'bool', null); + +COMMIT; + diff --git a/Open-ILS/src/sql/Pg/upgrade/YYYY.function.calc-prox-and-hold-permit.sql b/Open-ILS/src/sql/Pg/upgrade/YYYY.function.calc-prox-and-hold-permit.sql new file mode 100644 index 0000000000..bfbbacc388 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/YYYY.function.calc-prox-and-hold-permit.sql @@ -0,0 +1,371 @@ +BEGIN; + +SELECT evergreen.upgrade_deps_block_check('YYYY', :eg_version); + +CREATE OR REPLACE FUNCTION action.copy_calculated_proximity( + pickup INT, + request INT, + vacp_cl INT, + vacp_cm TEXT, + vacn_ol INT, + vacl_ol INT +) RETURNS NUMERIC AS $f$ +DECLARE + baseline_prox NUMERIC; + aoupa actor.org_unit_proximity_adjustment%ROWTYPE; +BEGIN + + -- First, gather the baseline proximity of "here" to pickup lib + SELECT prox INTO baseline_prox FROM actor.org_unit_proximity WHERE from_org = vacp_cl AND to_org = pickup; + + -- Find any absolute adjustments, and set the baseline prox to that + SELECT adj.* INTO aoupa + FROM actor.org_unit_proximity_adjustment adj + LEFT JOIN actor.org_unit_ancestors_distance(vacp_cl) acp_cl ON (acp_cl.id = adj.item_circ_lib) + LEFT JOIN actor.org_unit_ancestors_distance(vacn_ol) acn_ol ON (acn_ol.id = adj.item_owning_lib) + LEFT JOIN actor.org_unit_ancestors_distance(vacl_ol) acl_ol ON (acl_ol.id = adj.copy_location) + LEFT JOIN actor.org_unit_ancestors_distance(pickup) ahr_pl ON (ahr_pl.id = adj.hold_pickup_lib) + LEFT JOIN actor.org_unit_ancestors_distance(request) ahr_rl ON (ahr_rl.id = adj.hold_request_lib) + WHERE (adj.circ_mod IS NULL OR adj.circ_mod = vacp_cm) AND + (adj.item_circ_lib IS NULL OR adj.item_circ_lib = acp_cl.id) AND + (adj.item_owning_lib IS NULL OR adj.item_owning_lib = acn_ol.id) AND + (adj.copy_location IS NULL OR adj.copy_location = acl_ol.id) AND + (adj.hold_pickup_lib IS NULL OR adj.hold_pickup_lib = ahr_pl.id) AND + (adj.hold_request_lib IS NULL OR adj.hold_request_lib = ahr_rl.id) AND + absolute_adjustment AND + COALESCE(acp_cl.id, acn_ol.id, acl_ol.id, ahr_pl.id, ahr_rl.id) IS NOT NULL + ORDER BY + COALESCE(acp_cl.distance,999) + + COALESCE(acn_ol.distance,999) + + COALESCE(acl_ol.distance,999) + + COALESCE(ahr_pl.distance,999) + + COALESCE(ahr_rl.distance,999), + adj.pos + LIMIT 1; + + IF FOUND THEN + baseline_prox := aoupa.prox_adjustment; + END IF; + + -- Now find any relative adjustments, and change the baseline prox based on them + FOR aoupa IN + SELECT adj.* + FROM actor.org_unit_proximity_adjustment adj + LEFT JOIN actor.org_unit_ancestors_distance(vacp_cl) acp_cl ON (acp_cl.id = adj.item_circ_lib) + LEFT JOIN actor.org_unit_ancestors_distance(vacn_ol) acn_ol ON (acn_ol.id = adj.item_owning_lib) + LEFT JOIN actor.org_unit_ancestors_distance(vacl_ol) acl_ol ON (acn_ol.id = adj.copy_location) + LEFT JOIN actor.org_unit_ancestors_distance(pickup) ahr_pl ON (ahr_pl.id = adj.hold_pickup_lib) + LEFT JOIN actor.org_unit_ancestors_distance(request) ahr_rl ON (ahr_rl.id = adj.hold_request_lib) + WHERE (adj.circ_mod IS NULL OR adj.circ_mod = vacp_cm) AND + (adj.item_circ_lib IS NULL OR adj.item_circ_lib = acp_cl.id) AND + (adj.item_owning_lib IS NULL OR adj.item_owning_lib = acn_ol.id) AND + (adj.copy_location IS NULL OR adj.copy_location = acl_ol.id) AND + (adj.hold_pickup_lib IS NULL OR adj.hold_pickup_lib = ahr_pl.id) AND + (adj.hold_request_lib IS NULL OR adj.hold_request_lib = ahr_rl.id) AND + NOT absolute_adjustment AND + COALESCE(acp_cl.id, acn_ol.id, acl_ol.id, ahr_pl.id, ahr_rl.id) IS NOT NULL + LOOP + baseline_prox := baseline_prox + aoupa.prox_adjustment; + END LOOP; + + RETURN baseline_prox; +END; +$F$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION action.hold_copy_calculated_proximity( + ahr_id INT, + acp_id BIGINT, + copy_context_ou INT DEFAULT NULL + -- TODO maybe? hold_context_ou INT DEFAULT NULL. This would optionally + -- support an "ahprox" measurement: adjust prox between copy circ lib and + -- hold request lib, but I'm unsure whether to use this theoretical + -- argument only in the baseline calculation or later in the other + -- queries in this function. +) RETURNS NUMERIC AS $f$ +DECLARE + ahr action.hold_request%ROWTYPE; + acp asset.copy%ROWTYPE; + acn asset.call_number%ROWTYPE; + acl asset.copy_location%ROWTYPE; + + prox NUMERIC; +BEGIN + + SELECT * INTO ahr FROM action.hold_request WHERE id = ahr_id; + SELECT * INTO acp FROM asset.copy WHERE id = acp_id; + SELECT * INTO acn FROM asset.call_number WHERE id = acp.call_number; + SELECT * INTO acl FROM asset.copy_location WHERE id = acp.location; + + IF copy_context_ou IS NULL THEN + copy_context_ou := acp.circ_lib; + END IF; + + SELECT action.copy_calculated_proximity( + ahr.pickup_lib, + ahr.request_lib, + copy_context_ou, + acp.circ_modifier, + acn.owning_lib, + acl.owning_lib + ) INTO prox; + + RETURN prox; +END; +$f$ LANGUAGE PLPGSQL; + +CREATE OR REPLACE FUNCTION action.hold_request_permit_test( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT, retargetting BOOL ) RETURNS SETOF action.matrix_test_result AS $func$ +DECLARE + matchpoint_id INT; + user_object actor.usr%ROWTYPE; + age_protect_object config.rule_age_hold_protect%ROWTYPE; + standing_penalty config.standing_penalty%ROWTYPE; + transit_range_ou_type actor.org_unit_type%ROWTYPE; + transit_source actor.org_unit%ROWTYPE; + item_object asset.copy%ROWTYPE; + item_cn_object asset.call_number%ROWTYPE; + item_status_object config.copy_status%ROWTYPE; + item_location_object asset.copy_location%ROWTYPE; + ou_skip actor.org_unit_setting%ROWTYPE; + calc_age_prox actor.org_unit_setting%ROWTYPE; + result action.matrix_test_result; + hold_test config.hold_matrix_matchpoint%ROWTYPE; + use_active_date TEXT; + prox_ou INT; + age_protect_date TIMESTAMP WITH TIME ZONE; + hold_count INT; + hold_transit_prox NUMERIC; + frozen_hold_count INT; + context_org_list INT[]; + done BOOL := FALSE; + hold_penalty TEXT; + v_pickup_ou ALIAS FOR pickup_ou; + v_request_ou ALIAS FOR request_ou; + item_prox INT; + pickup_prox INT; +BEGIN + SELECT INTO user_object * FROM actor.usr WHERE id = match_user; + SELECT INTO context_org_list ARRAY_AGG(id) FROM actor.org_unit_full_path( v_pickup_ou ); + + result.success := TRUE; + + -- The HOLD penalty block only applies to new holds. + -- The CAPTURE penalty block applies to existing holds. + hold_penalty := 'HOLD'; + IF retargetting THEN + hold_penalty := 'CAPTURE'; + END IF; + + -- Fail if we couldn't find a user + IF user_object.id IS NULL THEN + result.fail_part := 'no_user'; + result.success := FALSE; + done := TRUE; + RETURN NEXT result; + RETURN; + END IF; + + SELECT INTO item_object * FROM asset.copy WHERE id = match_item; + + -- Fail if we couldn't find a copy + IF item_object.id IS NULL THEN + result.fail_part := 'no_item'; + result.success := FALSE; + done := TRUE; + RETURN NEXT result; + RETURN; + END IF; + + SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(v_pickup_ou, v_request_ou, match_item, match_user, match_requestor); + result.matchpoint := matchpoint_id; + + SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib; + + -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true + IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN + result.fail_part := 'circ.holds.target_skip_me'; + result.success := FALSE; + done := TRUE; + RETURN NEXT result; + RETURN; + END IF; + + -- Fail if user is barred + IF user_object.barred IS TRUE THEN + result.fail_part := 'actor.usr.barred'; + result.success := FALSE; + done := TRUE; + RETURN NEXT result; + RETURN; + END IF; + + SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number; + SELECT INTO item_status_object * FROM config.copy_status WHERE id = item_object.status; + SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location; + + -- Fail if we couldn't find any matchpoint (requires a default) + IF matchpoint_id IS NULL THEN + result.fail_part := 'no_matchpoint'; + result.success := FALSE; + done := TRUE; + RETURN NEXT result; + RETURN; + END IF; + + SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id; + + IF hold_test.holdable IS FALSE THEN + result.fail_part := 'config.hold_matrix_test.holdable'; + result.success := FALSE; + done := TRUE; + RETURN NEXT result; + END IF; + + IF item_object.holdable IS FALSE THEN + result.fail_part := 'item.holdable'; + result.success := FALSE; + done := TRUE; + RETURN NEXT result; + END IF; + + IF item_status_object.holdable IS FALSE THEN + result.fail_part := 'status.holdable'; + result.success := FALSE; + done := TRUE; + RETURN NEXT result; + END IF; + + IF item_location_object.holdable IS FALSE THEN + result.fail_part := 'location.holdable'; + result.success := FALSE; + done := TRUE; + RETURN NEXT result; + END IF; + + IF hold_test.transit_range IS NOT NULL THEN + SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range; + IF hold_test.distance_is_from_owner THEN + SELECT INTO transit_source ou.* FROM actor.org_unit ou JOIN asset.call_number cn ON (cn.owning_lib = ou.id) WHERE cn.id = item_object.call_number; + ELSE + SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib; + END IF; + + PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = v_pickup_ou; + + IF NOT FOUND THEN + result.fail_part := 'transit_range'; + result.success := FALSE; + done := TRUE; + RETURN NEXT result; + END IF; + END IF; + + -- Proximity of user's home_ou to the pickup_lib to see if penalty should be ignored. + SELECT INTO pickup_prox prox FROM actor.org_unit_proximity WHERE from_org = user_object.home_ou AND to_org = v_pickup_ou; + -- Proximity of user's home_ou to the items' lib to see if penalty should be ignored. + IF hold_test.distance_is_from_owner THEN + SELECT INTO item_prox prox FROM actor.org_unit_proximity WHERE from_org = user_object.home_ou AND to_org = item_cn_object.owning_lib; + ELSE + SELECT INTO item_prox prox FROM actor.org_unit_proximity WHERE from_org = user_object.home_ou AND to_org = item_object.circ_lib; + END IF; + + FOR standing_penalty IN + SELECT DISTINCT csp.* + FROM actor.usr_standing_penalty usp + JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty) + WHERE usr = match_user + AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) ) + AND (usp.stop_date IS NULL or usp.stop_date > NOW()) + AND (csp.ignore_proximity IS NULL OR csp.ignore_proximity < item_prox + OR csp.ignore_proximity < pickup_prox) + AND csp.block_list LIKE '%' || hold_penalty || '%' LOOP + + result.fail_part := standing_penalty.name; + result.success := FALSE; + done := TRUE; + RETURN NEXT result; + END LOOP; + + IF hold_test.stop_blocked_user IS TRUE THEN + FOR standing_penalty IN + SELECT DISTINCT csp.* + FROM actor.usr_standing_penalty usp + JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty) + WHERE usr = match_user + AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) ) + AND (usp.stop_date IS NULL or usp.stop_date > NOW()) + AND csp.block_list LIKE '%CIRC%' LOOP + + result.fail_part := standing_penalty.name; + result.success := FALSE; + done := TRUE; + RETURN NEXT result; + END LOOP; + END IF; + + IF hold_test.max_holds IS NOT NULL AND NOT retargetting THEN + SELECT INTO hold_count COUNT(*) + FROM action.hold_request + WHERE usr = match_user + AND fulfillment_time IS NULL + AND cancel_time IS NULL + AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END; + + IF hold_count >= hold_test.max_holds THEN + result.fail_part := 'config.hold_matrix_test.max_holds'; + result.success := FALSE; + done := TRUE; + RETURN NEXT result; + END IF; + END IF; + + IF item_object.age_protect IS NOT NULL THEN + SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect; + IF hold_test.distance_is_from_owner THEN + SELECT INTO use_active_date value FROM actor.org_unit_ancestor_setting('circ.holds.age_protect.active_date', item_cn_object.owning_lib); + ELSE + SELECT INTO use_active_date value FROM actor.org_unit_ancestor_setting('circ.holds.age_protect.active_date', item_object.circ_lib); + END IF; + IF use_active_date = 'true' THEN + age_protect_date := COALESCE(item_object.active_date, NOW()); + ELSE + age_protect_date := item_object.create_date; + END IF; + IF age_protect_date + age_protect_object.age > NOW() THEN + SELECT INTO calc_age_prox * FROM actor.org_unit_setting WHERE name = 'circ.holds.calculated_age_proximity' AND org_unit = item_object.circ_lib; + IF hold_test.distance_is_from_owner THEN + prox_ou := item_cn_object.owning_lib; + ELSE + prox_ou := item_object.circ_lib; + END IF; + IF calc_age_prox.id IS NOT NULL AND calc_age_prox.value = 'true' THEN + SELECT INTO hold_transit_prox action.copy_calculated_proximity( + v_pickup_ou, + v_request_ou, + prox_ou, + item_object.circ_modifier, + item_cn_object.owning_lib, + item_location_object.owning_lib + ); + ELSE + SELECT INTO hold_transit_prox prox::NUMERIC FROM actor.org_unit_proximity WHERE from_org = prox_ou AND to_org = v_pickup_ou; + END IF; + + IF hold_transit_prox > age_protect_object.prox::NUMERIC THEN + result.fail_part := 'config.rule_age_hold_protect.prox'; + result.success := FALSE; + done := TRUE; + RETURN NEXT result; + END IF; + END IF; + END IF; + + IF NOT done THEN + RETURN NEXT result; + END IF; + + RETURN; +END; +$func$ LANGUAGE plpgsql; + +COMMIT; +