From 686e7a7fbda43628cbefeb11cc6deb533a7ffed1 Mon Sep 17 00:00:00 2001 From: miker Date: Tue, 3 May 2011 14:55:37 +0000 Subject: [PATCH] Patch from Jason Stephenson ( LP#770261, https://bugs.launchpad.net/evergreen/+bug/770261 ) adding support for bibliographic level inspection in the hold and circ matrix matchpoint tests. git-svn-id: svn://svn.open-ils.org/ILS/trunk@20380 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- Open-ILS/examples/fm_IDL.xml | 4 + Open-ILS/src/sql/Pg/099.matrix_weights.sql | 2 + Open-ILS/src/sql/Pg/100.circ_matrix.sql | 5 +- Open-ILS/src/sql/Pg/110.hold_matrix.sql | 5 +- Open-ILS/src/sql/Pg/950.data.seed-values.sql | 20 +- .../Pg/upgrade/0527.schema.matrix-bib_level.sql | 341 ++++++++++++++++++++ .../web/opac/skin/default/xml/common/js_common.xml | 18 +- .../global/config/circ_matrix_matchpoint.tt2 | 2 +- .../global/config/hold_matrix_matchpoint.tt2 | 2 +- 9 files changed, 376 insertions(+), 23 deletions(-) create mode 100644 Open-ILS/src/sql/Pg/upgrade/0527.schema.matrix-bib_level.sql diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index e9250ef..3df1fdb 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -1232,6 +1232,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + @@ -1252,6 +1253,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + @@ -1279,6 +1281,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + @@ -1304,6 +1307,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + diff --git a/Open-ILS/src/sql/Pg/099.matrix_weights.sql b/Open-ILS/src/sql/Pg/099.matrix_weights.sql index 521171c..febf569 100644 --- a/Open-ILS/src/sql/Pg/099.matrix_weights.sql +++ b/Open-ILS/src/sql/Pg/099.matrix_weights.sql @@ -10,6 +10,7 @@ CREATE TABLE config.circ_matrix_weights ( circ_modifier NUMERIC(6,2) NOT NULL, marc_type NUMERIC(6,2) NOT NULL, marc_form NUMERIC(6,2) NOT NULL, + marc_bib_level NUMERIC(6,2) NOT NULL, marc_vr_format NUMERIC(6,2) NOT NULL, copy_circ_lib NUMERIC(6,2) NOT NULL, copy_owning_lib NUMERIC(6,2) NOT NULL, @@ -35,6 +36,7 @@ CREATE TABLE config.hold_matrix_weights ( circ_modifier NUMERIC(6,2) NOT NULL, marc_type NUMERIC(6,2) NOT NULL, marc_form NUMERIC(6,2) NOT NULL, + marc_bib_level NUMERIC(6,2) NOT NULL, marc_vr_format NUMERIC(6,2) NOT NULL, juvenile_flag NUMERIC(6,2) NOT NULL, ref_flag NUMERIC(6,2) NOT NULL diff --git a/Open-ILS/src/sql/Pg/100.circ_matrix.sql b/Open-ILS/src/sql/Pg/100.circ_matrix.sql index a026d26..fadc392 100644 --- a/Open-ILS/src/sql/Pg/100.circ_matrix.sql +++ b/Open-ILS/src/sql/Pg/100.circ_matrix.sql @@ -59,6 +59,7 @@ CREATE TABLE config.circ_matrix_matchpoint ( circ_modifier TEXT REFERENCES config.circ_modifier (code) DEFERRABLE INITIALLY DEFERRED, marc_type TEXT, marc_form TEXT, + marc_bib_level TEXT, marc_vr_format TEXT, copy_circ_lib INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED, copy_owning_lib INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED, @@ -82,7 +83,7 @@ CREATE TABLE config.circ_matrix_matchpoint ( ); -- Nulls don't count for a constraint match, so we have to coalesce them into something that does. -CREATE UNIQUE INDEX ccmm_once_per_paramset ON config.circ_matrix_matchpoint (org_unit, grp, COALESCE(circ_modifier, ''), COALESCE(marc_type, ''), COALESCE(marc_form, ''), COALESCE(marc_vr_format, ''), COALESCE(copy_circ_lib::TEXT, ''), COALESCE(copy_owning_lib::TEXT, ''), COALESCE(user_home_ou::TEXT, ''), COALESCE(ref_flag::TEXT, ''), COALESCE(juvenile_flag::TEXT, ''), COALESCE(is_renewal::TEXT, ''), COALESCE(usr_age_lower_bound::TEXT, ''), COALESCE(usr_age_upper_bound::TEXT, '')) WHERE active; +CREATE UNIQUE INDEX ccmm_once_per_paramset ON config.circ_matrix_matchpoint (org_unit, grp, COALESCE(circ_modifier, ''), COALESCE(marc_type, ''), COALESCE(marc_form, ''), COALESCE(marc_bib_level,''), COALESCE(marc_vr_format, ''), COALESCE(copy_circ_lib::TEXT, ''), COALESCE(copy_owning_lib::TEXT, ''), COALESCE(user_home_ou::TEXT, ''), COALESCE(ref_flag::TEXT, ''), COALESCE(juvenile_flag::TEXT, ''), COALESCE(is_renewal::TEXT, ''), COALESCE(usr_age_lower_bound::TEXT, ''), COALESCE(usr_age_upper_bound::TEXT, '')) WHERE active; -- Tests for max items out by circ_modifier CREATE TABLE config.circ_matrix_circ_mod_test ( @@ -140,6 +141,7 @@ BEGIN weights.circ_modifier := 5.0; weights.marc_type := 4.0; weights.marc_form := 3.0; + weights.marc_bib_level := 2.0; weights.marc_vr_format := 2.0; weights.copy_circ_lib := 8.0; weights.copy_owning_lib := 8.0; @@ -189,6 +191,7 @@ BEGIN AND (m.circ_modifier IS NULL OR m.circ_modifier = item_object.circ_modifier) AND (m.marc_type IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type)) AND (m.marc_form IS NULL OR m.marc_form = rec_descriptor.item_form) + AND (m.marc_bib_level IS NULL OR m.marc_bib_level = rec_descriptor.bib_level) AND (m.marc_vr_format IS NULL OR m.marc_vr_format = rec_descriptor.vr_format) AND (m.ref_flag IS NULL OR m.ref_flag = item_object.ref) ORDER BY diff --git a/Open-ILS/src/sql/Pg/110.hold_matrix.sql b/Open-ILS/src/sql/Pg/110.hold_matrix.sql index 3b954af..b914874 100644 --- a/Open-ILS/src/sql/Pg/110.hold_matrix.sql +++ b/Open-ILS/src/sql/Pg/110.hold_matrix.sql @@ -43,6 +43,7 @@ CREATE TABLE config.hold_matrix_matchpoint ( circ_modifier TEXT REFERENCES config.circ_modifier (code) DEFERRABLE INITIALLY DEFERRED, marc_type TEXT, marc_form TEXT, + marc_bib_level TEXT, marc_vr_format TEXT, juvenile_flag BOOL, ref_flag BOOL, @@ -57,7 +58,7 @@ CREATE TABLE config.hold_matrix_matchpoint ( ); -- Nulls don't count for a constraint match, so we have to coalesce them into something that does. -CREATE UNIQUE INDEX chmm_once_per_paramset ON config.hold_matrix_matchpoint (COALESCE(user_home_ou::TEXT, ''), COALESCE(request_ou::TEXT, ''), COALESCE(pickup_ou::TEXT, ''), COALESCE(item_owning_ou::TEXT, ''), COALESCE(item_circ_ou::TEXT, ''), COALESCE(usr_grp::TEXT, ''), COALESCE(requestor_grp::TEXT, ''), COALESCE(circ_modifier, ''), COALESCE(marc_type, ''), COALESCE(marc_form, ''), COALESCE(marc_vr_format, ''), COALESCE(juvenile_flag::TEXT, ''), COALESCE(ref_flag::TEXT, '')) WHERE active; +CREATE UNIQUE INDEX chmm_once_per_paramset ON config.hold_matrix_matchpoint (COALESCE(user_home_ou::TEXT, ''), COALESCE(request_ou::TEXT, ''), COALESCE(pickup_ou::TEXT, ''), COALESCE(item_owning_ou::TEXT, ''), COALESCE(item_circ_ou::TEXT, ''), COALESCE(usr_grp::TEXT, ''), COALESCE(requestor_grp::TEXT, ''), COALESCE(circ_modifier, ''), COALESCE(marc_type, ''), COALESCE(marc_form, ''), COALESCE(marc_bib_level, ''), COALESCE(marc_vr_format, ''), COALESCE(juvenile_flag::TEXT, ''), COALESCE(ref_flag::TEXT, '')) WHERE active; CREATE OR REPLACE FUNCTION action.find_hold_matrix_matchpoint(pickup_ou integer, request_ou integer, match_item bigint, match_user integer, match_requestor integer) RETURNS integer AS @@ -116,6 +117,7 @@ BEGIN weights.circ_modifier := 4.0; weights.marc_type := 3.0; weights.marc_form := 2.0; + weights.marc_bib_level := 1.0; weights.marc_vr_format := 1.0; weights.juvenile_flag := 4.0; weights.ref_flag := 0.0; @@ -171,6 +173,7 @@ BEGIN AND (m.circ_modifier IS NULL OR m.circ_modifier = item_object.circ_modifier) AND (m.marc_type IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type)) AND (m.marc_form IS NULL OR m.marc_form = rec_descriptor.item_form) + AND (m.marc_bib_level IS NULL OR m.marc_bib_level = rec_descriptor.bib_level) AND (m.marc_vr_format IS NULL OR m.marc_vr_format = rec_descriptor.vr_format) AND (m.ref_flag IS NULL OR m.ref_flag = item_object.ref) ORDER BY 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 83c8547..1af2c96 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -1603,20 +1603,20 @@ INSERT INTO asset.call_number VALUES (-1,1,NOW(),1,NOW(),-1,1,'UNCATALOGED'); -- circ matrix INSERT INTO config.circ_matrix_matchpoint (org_unit,grp,circulate,duration_rule,recurring_fine_rule,max_fine_rule) VALUES (1,1,true,11,1,1); -INSERT INTO config.circ_matrix_weights(name, org_unit, grp, circ_modifier, marc_type, marc_form, marc_vr_format, copy_circ_lib, copy_owning_lib, user_home_ou, ref_flag, juvenile_flag, is_renewal, usr_age_upper_bound, usr_age_lower_bound) VALUES - ('Default', 10.0, 11.0, 5.0, 4.0, 3.0, 2.0, 8.0, 8.0, 8.0, 1.0, 6.0, 7.0, 0.0, 0.0), - ('Org_Unit_First', 11.0, 10.0, 5.0, 4.0, 3.0, 2.0, 8.0, 8.0, 8.0, 1.0, 6.0, 7.0, 0.0, 0.0), - ('Item_Owner_First', 8.0, 8.0, 5.0, 4.0, 3.0, 2.0, 10.0, 11.0, 8.0, 1.0, 6.0, 7.0, 0.0, 0.0), - ('All_Equal', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); +INSERT INTO config.circ_matrix_weights(name, org_unit, grp, circ_modifier, marc_type, marc_form, marc_bib_level, marc_vr_format, copy_circ_lib, copy_owning_lib, user_home_ou, ref_flag, juvenile_flag, is_renewal, usr_age_upper_bound, usr_age_lower_bound) VALUES + ('Default', 10.0, 11.0, 5.0, 4.0, 3.0, 2.0, 2.0, 8.0, 8.0, 8.0, 1.0, 6.0, 7.0, 0.0, 0.0), + ('Org_Unit_First', 11.0, 10.0, 5.0, 4.0, 3.0, 2.0, 2.0, 8.0, 8.0, 8.0, 1.0, 6.0, 7.0, 0.0, 0.0), + ('Item_Owner_First', 8.0, 8.0, 5.0, 4.0, 3.0, 2.0, 2.0, 10.0, 11.0, 8.0, 1.0, 6.0, 7.0, 0.0, 0.0), + ('All_Equal', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); -- hold matrix - 110.hold_matrix.sql: INSERT INTO config.hold_matrix_matchpoint (requestor_grp) VALUES (1); -INSERT INTO config.hold_matrix_weights(name, user_home_ou, request_ou, pickup_ou, item_owning_ou, item_circ_ou, usr_grp, requestor_grp, circ_modifier, marc_type, marc_form, marc_vr_format, juvenile_flag, ref_flag) VALUES - ('Default', 5.0, 5.0, 5.0, 5.0, 5.0, 7.0, 8.0, 4.0, 3.0, 2.0, 1.0, 4.0, 0.0), - ('Item_Owner_First', 5.0, 5.0, 5.0, 8.0, 7.0, 5.0, 5.0, 4.0, 3.0, 2.0, 1.0, 4.0, 0.0), - ('User_Before_Requestor', 5.0, 5.0, 5.0, 5.0, 5.0, 8.0, 7.0, 4.0, 3.0, 2.0, 1.0, 4.0, 0.0), - ('All_Equal', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); +INSERT INTO config.hold_matrix_weights(name, user_home_ou, request_ou, pickup_ou, item_owning_ou, item_circ_ou, usr_grp, requestor_grp, circ_modifier, marc_type, marc_form, marc_bib_level, marc_vr_format, juvenile_flag, ref_flag) VALUES + ('Default', 5.0, 5.0, 5.0, 5.0, 5.0, 7.0, 8.0, 4.0, 3.0, 2.0, 1.0, 1.0, 4.0, 0.0), + ('Item_Owner_First', 5.0, 5.0, 5.0, 8.0, 7.0, 5.0, 5.0, 4.0, 3.0, 2.0, 1.0, 1.0, 4.0, 0.0), + ('User_Before_Requestor', 5.0, 5.0, 5.0, 5.0, 5.0, 8.0, 7.0, 4.0, 3.0, 2.0, 1.0, 1.0, 4.0, 0.0), + ('All_Equal', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); -- dynamic weight associations INSERT INTO config.weight_assoc(active, org_unit, circ_weights, hold_weights) VALUES diff --git a/Open-ILS/src/sql/Pg/upgrade/0527.schema.matrix-bib_level.sql b/Open-ILS/src/sql/Pg/upgrade/0527.schema.matrix-bib_level.sql new file mode 100644 index 0000000..ffe5269 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/0527.schema.matrix-bib_level.sql @@ -0,0 +1,341 @@ +BEGIN; + +ALTER TABLE config.circ_matrix_weights ADD COLUMN marc_bib_level NUMERIC(6,2) NOT NULL DEFAULT 0.0; + +UPDATE config.circ_matrix_weights +SET marc_bib_level = marc_vr_format; + +ALTER TABLE config.hold_matrix_weights ADD COLUMN marc_bib_level NUMERIC(6, 2) NOT NULL DEFAULT 0.0; + +UPDATE config.hold_matrix_weights +SET marc_bib_level = marc_vr_format; + +ALTER TABLE config.circ_matrix_weights ALTER COLUMN marc_bib_level DROP DEFAULT; + +ALTER TABLE config.hold_matrix_weights ALTER COLUMN marc_bib_level DROP DEFAULT; + +ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN marc_bib_level text; + +ALTER TABLE config.hold_matrix_matchpoint ADD COLUMN marc_bib_level text; + +CREATE OR REPLACE FUNCTION action.find_circ_matrix_matchpoint( context_ou INT, item_object asset.copy, user_object actor.usr, renewal BOOL ) RETURNS action.found_circ_matrix_matchpoint AS $func$ +DECLARE + cn_object asset.call_number%ROWTYPE; + rec_descriptor metabib.rec_descriptor%ROWTYPE; + cur_matchpoint config.circ_matrix_matchpoint%ROWTYPE; + matchpoint config.circ_matrix_matchpoint%ROWTYPE; + weights config.circ_matrix_weights%ROWTYPE; + user_age INTERVAL; + denominator NUMERIC(6,2); + row_list INT[]; + result action.found_circ_matrix_matchpoint; +BEGIN + -- Assume failure + result.success = false; + + -- Fetch useful data + SELECT INTO cn_object * FROM asset.call_number WHERE id = item_object.call_number; + SELECT INTO rec_descriptor * FROM metabib.rec_descriptor WHERE record = cn_object.record; + + -- Pre-generate this so we only calc it once + IF user_object.dob IS NOT NULL THEN + SELECT INTO user_age age(user_object.dob); + END IF; + + -- Grab the closest set circ weight setting. + SELECT INTO weights cw.* + FROM config.weight_assoc wa + JOIN config.circ_matrix_weights cw ON (cw.id = wa.circ_weights) + JOIN actor.org_unit_ancestors_distance( context_ou ) d ON (wa.org_unit = d.id) + WHERE active + ORDER BY d.distance + LIMIT 1; + + -- No weights? Bad admin! Defaults to handle that anyway. + IF weights.id IS NULL THEN + weights.grp := 11.0; + weights.org_unit := 10.0; + weights.circ_modifier := 5.0; + weights.marc_type := 4.0; + weights.marc_form := 3.0; + weights.marc_bib_level := 2.0; + weights.marc_vr_format := 2.0; + weights.copy_circ_lib := 8.0; + weights.copy_owning_lib := 8.0; + weights.user_home_ou := 8.0; + weights.ref_flag := 1.0; + weights.juvenile_flag := 6.0; + weights.is_renewal := 7.0; + weights.usr_age_lower_bound := 0.0; + weights.usr_age_upper_bound := 0.0; + END IF; + + -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree + -- If you break your org tree with funky parenting this may be wrong + -- Note: This CTE is duplicated in the find_hold_matrix_matchpoint function, and it may be a good idea to split it off to a function + -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting + WITH all_distance(distance) AS ( + SELECT depth AS distance FROM actor.org_unit_type + UNION + SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL)) + ) + SELECT INTO denominator MAX(distance) + 1 FROM all_distance; + + -- Loop over all the potential matchpoints + FOR cur_matchpoint IN + SELECT m.* + FROM config.circ_matrix_matchpoint m + /*LEFT*/ JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.grp = upgad.id + /*LEFT*/ JOIN actor.org_unit_ancestors_distance( context_ou ) ctoua ON m.org_unit = ctoua.id + LEFT JOIN actor.org_unit_ancestors_distance( cn_object.owning_lib ) cnoua ON m.copy_owning_lib = cnoua.id + LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.copy_circ_lib = iooua.id + LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou ) uhoua ON m.user_home_ou = uhoua.id + WHERE m.active + -- Permission Groups + -- AND (m.grp IS NULL OR upgad.id IS NOT NULL) -- Optional Permission Group? + -- Org Units + -- AND (m.org_unit IS NULL OR ctoua.id IS NOT NULL) -- Optional Org Unit? + AND (m.copy_owning_lib IS NULL OR cnoua.id IS NOT NULL) + AND (m.copy_circ_lib IS NULL OR iooua.id IS NOT NULL) + AND (m.user_home_ou IS NULL OR uhoua.id IS NOT NULL) + -- Circ Type + AND (m.is_renewal IS NULL OR m.is_renewal = renewal) + -- Static User Checks + AND (m.juvenile_flag IS NULL OR m.juvenile_flag = user_object.juvenile) + AND (m.usr_age_lower_bound IS NULL OR (user_age IS NOT NULL AND m.usr_age_lower_bound < user_age)) + AND (m.usr_age_upper_bound IS NULL OR (user_age IS NOT NULL AND m.usr_age_upper_bound > user_age)) + -- Static Item Checks + AND (m.circ_modifier IS NULL OR m.circ_modifier = item_object.circ_modifier) + AND (m.marc_type IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type)) + AND (m.marc_form IS NULL OR m.marc_form = rec_descriptor.item_form) + AND (m.marc_bib_level IS NULL OR m.marc_bib_level = rec_descriptor.bib_level) + AND (m.marc_vr_format IS NULL OR m.marc_vr_format = rec_descriptor.vr_format) + AND (m.ref_flag IS NULL OR m.ref_flag = item_object.ref) + ORDER BY + -- Permission Groups + CASE WHEN upgad.distance IS NOT NULL THEN 2^(2*weights.grp - (upgad.distance/denominator)) ELSE 0.0 END + + -- Org Units + CASE WHEN ctoua.distance IS NOT NULL THEN 2^(2*weights.org_unit - (ctoua.distance/denominator)) ELSE 0.0 END + + CASE WHEN cnoua.distance IS NOT NULL THEN 2^(2*weights.copy_owning_lib - (cnoua.distance/denominator)) ELSE 0.0 END + + CASE WHEN iooua.distance IS NOT NULL THEN 2^(2*weights.copy_circ_lib - (iooua.distance/denominator)) ELSE 0.0 END + + CASE WHEN uhoua.distance IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0.0 END + + -- Circ Type -- Note: 4^x is equiv to 2^(2*x) + CASE WHEN m.is_renewal IS NOT NULL THEN 4^weights.is_renewal ELSE 0.0 END + + -- Static User Checks + CASE WHEN m.juvenile_flag IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0.0 END + + CASE WHEN m.usr_age_lower_bound IS NOT NULL THEN 4^weights.usr_age_lower_bound ELSE 0.0 END + + CASE WHEN m.usr_age_upper_bound IS NOT NULL THEN 4^weights.usr_age_upper_bound ELSE 0.0 END + + -- Static Item Checks + CASE WHEN m.circ_modifier IS NOT NULL THEN 4^weights.circ_modifier ELSE 0.0 END + + CASE WHEN m.marc_type IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END + + CASE WHEN m.marc_form IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END + + CASE WHEN m.marc_vr_format IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END + + CASE WHEN m.ref_flag IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END DESC, + -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order + -- This prevents "we changed the table order by updating a rule, and we started getting different results" + m.id LOOP + + -- Record the full matching row list + row_list := row_list || cur_matchpoint.id; + + -- No matchpoint yet? + IF matchpoint.id IS NULL THEN + -- Take the entire matchpoint as a starting point + matchpoint := cur_matchpoint; + CONTINUE; -- No need to look at this row any more. + END IF; + + -- Incomplete matchpoint? + IF matchpoint.circulate IS NULL THEN + matchpoint.circulate := cur_matchpoint.circulate; + END IF; + IF matchpoint.duration_rule IS NULL THEN + matchpoint.duration_rule := cur_matchpoint.duration_rule; + END IF; + IF matchpoint.recurring_fine_rule IS NULL THEN + matchpoint.recurring_fine_rule := cur_matchpoint.recurring_fine_rule; + END IF; + IF matchpoint.max_fine_rule IS NULL THEN + matchpoint.max_fine_rule := cur_matchpoint.max_fine_rule; + END IF; + IF matchpoint.hard_due_date IS NULL THEN + matchpoint.hard_due_date := cur_matchpoint.hard_due_date; + END IF; + IF matchpoint.total_copy_hold_ratio IS NULL THEN + matchpoint.total_copy_hold_ratio := cur_matchpoint.total_copy_hold_ratio; + END IF; + IF matchpoint.available_copy_hold_ratio IS NULL THEN + matchpoint.available_copy_hold_ratio := cur_matchpoint.available_copy_hold_ratio; + END IF; + IF matchpoint.renewals IS NULL THEN + matchpoint.renewals := cur_matchpoint.renewals; + END IF; + IF matchpoint.grace_period IS NULL THEN + matchpoint.grace_period := cur_matchpoint.grace_period; + END IF; + END LOOP; + + -- Check required fields + IF matchpoint.circulate IS NOT NULL AND + matchpoint.duration_rule IS NOT NULL AND + matchpoint.recurring_fine_rule IS NOT NULL AND + matchpoint.max_fine_rule IS NOT NULL THEN + -- All there? We have a completed match. + result.success := true; + END IF; + + -- Include the assembled matchpoint, even if it isn't complete + result.matchpoint := matchpoint; + + -- Include (for debugging) the full list of matching rows + result.buildrows := row_list; + + -- Hand the result back to caller + RETURN result; +END; +$func$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION action.find_hold_matrix_matchpoint(pickup_ou integer, request_ou integer, match_item bigint, match_user integer, match_requestor integer) + RETURNS integer AS +$func$ +DECLARE + requestor_object actor.usr%ROWTYPE; + user_object actor.usr%ROWTYPE; + item_object asset.copy%ROWTYPE; + item_cn_object asset.call_number%ROWTYPE; + rec_descriptor metabib.rec_descriptor%ROWTYPE; + matchpoint config.hold_matrix_matchpoint%ROWTYPE; + weights config.hold_matrix_weights%ROWTYPE; + denominator NUMERIC(6,2); +BEGIN + SELECT INTO user_object * FROM actor.usr WHERE id = match_user; + SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor; + SELECT INTO item_object * FROM asset.copy WHERE id = match_item; + SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number; + SELECT INTO rec_descriptor * FROM metabib.rec_descriptor WHERE record = item_cn_object.record; + + -- The item's owner should probably be the one determining if the item is holdable + -- How to decide that is debatable. Decided to default to the circ library (where the item lives) + -- This flag will allow for setting it to the owning library (where the call number "lives") + PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.weight_owner_not_circ' AND enabled; + + -- Grab the closest set circ weight setting. + IF NOT FOUND THEN + -- Default to circ library + SELECT INTO weights hw.* + FROM config.weight_assoc wa + JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights) + JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) d ON (wa.org_unit = d.id) + WHERE active + ORDER BY d.distance + LIMIT 1; + ELSE + -- Flag is set, use owning library + SELECT INTO weights hw.* + FROM config.weight_assoc wa + JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights) + JOIN actor.org_unit_ancestors_distance( cn_object.owning_lib ) d ON (wa.org_unit = d.id) + WHERE active + ORDER BY d.distance + LIMIT 1; + END IF; + + -- No weights? Bad admin! Defaults to handle that anyway. + IF weights.id IS NULL THEN + weights.user_home_ou := 5.0; + weights.request_ou := 5.0; + weights.pickup_ou := 5.0; + weights.item_owning_ou := 5.0; + weights.item_circ_ou := 5.0; + weights.usr_grp := 7.0; + weights.requestor_grp := 8.0; + weights.circ_modifier := 4.0; + weights.marc_type := 3.0; + weights.marc_form := 2.0; + weights.marc_bib_level := 1.0; + weights.marc_vr_format := 1.0; + weights.juvenile_flag := 4.0; + weights.ref_flag := 0.0; + END IF; + + -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree + -- If you break your org tree with funky parenting this may be wrong + -- Note: This CTE is duplicated in the find_circ_matrix_matchpoint function, and it may be a good idea to split it off to a function + -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting + WITH all_distance(distance) AS ( + SELECT depth AS distance FROM actor.org_unit_type + UNION + SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL)) + ) + SELECT INTO denominator MAX(distance) + 1 FROM all_distance; + + -- To ATTEMPT to make this work like it used to, make it reverse the user/requestor profile ids. + -- This may be better implemented as part of the upgrade script? + -- Set usr_grp = requestor_grp, requestor_grp = 1 or something when this flag is already set + -- Then remove this flag, of course. + PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled; + + IF FOUND THEN + -- Note: This, to me, is REALLY hacky. I put it in anyway. + -- If you can't tell, this is a single call swap on two variables. + SELECT INTO user_object.profile, requestor_object.profile + requestor_object.profile, user_object.profile; + END IF; + + -- Select the winning matchpoint into the matchpoint variable for returning + SELECT INTO matchpoint m.* + FROM config.hold_matrix_matchpoint m + /*LEFT*/ JOIN permission.grp_ancestors_distance( requestor_object.profile ) rpgad ON m.requestor_grp = rpgad.id + LEFT JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.usr_grp = upgad.id + LEFT JOIN actor.org_unit_ancestors_distance( pickup_ou ) puoua ON m.pickup_ou = puoua.id + LEFT JOIN actor.org_unit_ancestors_distance( request_ou ) rqoua ON m.request_ou = rqoua.id + LEFT JOIN actor.org_unit_ancestors_distance( item_cn_object.owning_lib ) cnoua ON m.item_owning_ou = cnoua.id + LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.item_circ_ou = iooua.id + LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou ) uhoua ON m.user_home_ou = uhoua.id + WHERE m.active + -- Permission Groups + -- AND (m.requestor_grp IS NULL OR upgad.id IS NOT NULL) -- Optional Requestor Group? + AND (m.usr_grp IS NULL OR upgad.id IS NOT NULL) + -- Org Units + AND (m.pickup_ou IS NULL OR (puoua.id IS NOT NULL AND (puoua.distance = 0 OR NOT m.strict_ou_match))) + AND (m.request_ou IS NULL OR (rqoua.id IS NOT NULL AND (rqoua.distance = 0 OR NOT m.strict_ou_match))) + AND (m.item_owning_ou IS NULL OR (cnoua.id IS NOT NULL AND (cnoua.distance = 0 OR NOT m.strict_ou_match))) + AND (m.item_circ_ou IS NULL OR (iooua.id IS NOT NULL AND (iooua.distance = 0 OR NOT m.strict_ou_match))) + AND (m.user_home_ou IS NULL OR (uhoua.id IS NOT NULL AND (uhoua.distance = 0 OR NOT m.strict_ou_match))) + -- Static User Checks + AND (m.juvenile_flag IS NULL OR m.juvenile_flag = user_object.juvenile) + -- Static Item Checks + AND (m.circ_modifier IS NULL OR m.circ_modifier = item_object.circ_modifier) + AND (m.marc_type IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type)) + AND (m.marc_form IS NULL OR m.marc_form = rec_descriptor.item_form) + AND (m.marc_bib_level IS NULL OR m.marc_bib_level = rec_descriptor.bib_level) + AND (m.marc_vr_format IS NULL OR m.marc_vr_format = rec_descriptor.vr_format) + AND (m.ref_flag IS NULL OR m.ref_flag = item_object.ref) + ORDER BY + -- Permission Groups + CASE WHEN rpgad.distance IS NOT NULL THEN 2^(2*weights.requestor_grp - (rpgad.distance/denominator)) ELSE 0.0 END + + CASE WHEN upgad.distance IS NOT NULL THEN 2^(2*weights.usr_grp - (upgad.distance/denominator)) ELSE 0.0 END + + -- Org Units + CASE WHEN puoua.distance IS NOT NULL THEN 2^(2*weights.pickup_ou - (puoua.distance/denominator)) ELSE 0.0 END + + CASE WHEN rqoua.distance IS NOT NULL THEN 2^(2*weights.request_ou - (rqoua.distance/denominator)) ELSE 0.0 END + + CASE WHEN cnoua.distance IS NOT NULL THEN 2^(2*weights.item_owning_ou - (cnoua.distance/denominator)) ELSE 0.0 END + + CASE WHEN iooua.distance IS NOT NULL THEN 2^(2*weights.item_circ_ou - (iooua.distance/denominator)) ELSE 0.0 END + + CASE WHEN uhoua.distance IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0.0 END + + -- Static User Checks -- Note: 4^x is equiv to 2^(2*x) + CASE WHEN m.juvenile_flag IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0.0 END + + -- Static Item Checks + CASE WHEN m.circ_modifier IS NOT NULL THEN 4^weights.circ_modifier ELSE 0.0 END + + CASE WHEN m.marc_type IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END + + CASE WHEN m.marc_form IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END + + CASE WHEN m.marc_vr_format IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END + + CASE WHEN m.ref_flag IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END DESC, + -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order + -- This prevents "we changed the table order by updating a rule, and we started getting different results" + m.id; + + -- Return just the ID for now + RETURN matchpoint.id; +END; +$func$ LANGUAGE 'plpgsql'; + +COMMIT; diff --git a/Open-ILS/web/opac/skin/default/xml/common/js_common.xml b/Open-ILS/web/opac/skin/default/xml/common/js_common.xml index fd7d5ad..ab8edbb 100644 --- a/Open-ILS/web/opac/skin/default/xml/common/js_common.xml +++ b/Open-ILS/web/opac/skin/default/xml/common/js_common.xml @@ -19,18 +19,18 @@ }, lang, bidi; - - - - - + + + + + - - - + + + - +