3 INSERT INTO config.upgrade_log (version) VALUES ('0483');
5 CREATE OR REPLACE FUNCTION action.find_circ_matrix_matchpoint( context_ou INT, match_item BIGINT, match_user INT, renewal BOOL ) RETURNS config.circ_matrix_matchpoint AS $func$
7 user_object actor.usr%ROWTYPE;
8 item_object asset.copy%ROWTYPE;
9 cn_object asset.call_number%ROWTYPE;
10 rec_descriptor metabib.rec_descriptor%ROWTYPE;
11 matchpoint config.circ_matrix_matchpoint%ROWTYPE;
12 weights config.circ_matrix_weights%ROWTYPE;
14 denominator NUMERIC(6,2);
16 SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
17 SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
18 SELECT INTO cn_object * FROM asset.call_number WHERE id = item_object.call_number;
19 SELECT INTO rec_descriptor * FROM metabib.rec_descriptor WHERE record = cn_object.record;
21 -- Pre-generate this so we only calc it once
22 IF user_object.dob IS NOT NULL THEN
23 SELECT INTO user_age age(user_object.dob);
26 -- Grab the closest set circ weight setting.
27 SELECT INTO weights cw.*
28 FROM config.weight_assoc wa
29 JOIN config.circ_matrix_weights cw ON (cw.id = wa.circ_weights)
30 JOIN actor.org_unit_ancestors_distance( context_ou ) d ON (wa.org_unit = d.id)
35 -- No weights? Bad admin! Defaults to handle that anyway.
36 IF weights.id IS NULL THEN
38 weights.org_unit := 10.0;
39 weights.circ_modifier := 5.0;
40 weights.marc_type := 4.0;
41 weights.marc_form := 3.0;
42 weights.marc_vr_format := 2.0;
43 weights.copy_circ_lib := 8.0;
44 weights.copy_owning_lib := 8.0;
45 weights.user_home_ou := 8.0;
46 weights.ref_flag := 1.0;
47 weights.juvenile_flag := 6.0;
48 weights.is_renewal := 7.0;
49 weights.usr_age_lower_bound := 0.0;
50 weights.usr_age_upper_bound := 0.0;
53 -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
54 -- If you break your org tree with funky parenting this may be wrong
55 -- 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
56 -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting
57 WITH all_distance(distance) AS (
58 SELECT depth AS distance FROM actor.org_unit_type
60 SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
62 SELECT INTO denominator MAX(distance) + 1 FROM all_distance;
64 -- Select the winning matchpoint into the matchpoint variable for returning
65 SELECT INTO matchpoint m.*
66 FROM config.circ_matrix_matchpoint m
67 /*LEFT*/ JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.grp = upgad.id
68 /*LEFT*/ JOIN actor.org_unit_ancestors_distance( context_ou ) ctoua ON m.org_unit = ctoua.id
69 LEFT JOIN actor.org_unit_ancestors_distance( cn_object.owning_lib ) cnoua ON m.copy_owning_lib = cnoua.id
70 LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.copy_circ_lib = iooua.id
71 LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou ) uhoua ON m.user_home_ou = uhoua.id
74 -- AND (m.grp IS NULL OR upgad.id IS NOT NULL) -- Optional Permission Group?
76 -- AND (m.org_unit IS NULL OR ctoua.id IS NOT NULL) -- Optional Org Unit?
77 AND (m.copy_owning_lib IS NULL OR cnoua.id IS NOT NULL)
78 AND (m.copy_circ_lib IS NULL OR iooua.id IS NOT NULL)
79 AND (m.user_home_ou IS NULL OR uhoua.id IS NOT NULL)
81 AND (m.is_renewal IS NULL OR m.is_renewal = renewal)
83 AND (m.juvenile_flag IS NULL OR m.juvenile_flag = user_object.juvenile)
84 AND (m.usr_age_lower_bound IS NULL OR (user_age IS NOT NULL AND m.usr_age_lower_bound < user_age))
85 AND (m.usr_age_upper_bound IS NULL OR (user_age IS NOT NULL AND m.usr_age_upper_bound > user_age))
87 AND (m.circ_modifier IS NULL OR m.circ_modifier = item_object.circ_modifier)
88 AND (m.marc_type IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type))
89 AND (m.marc_form IS NULL OR m.marc_form = rec_descriptor.item_form)
90 AND (m.marc_vr_format IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
91 AND (m.ref_flag IS NULL OR m.ref_flag = item_object.ref)
94 CASE WHEN upgad.distance IS NOT NULL THEN 2^(2*weights.grp - (upgad.distance/denominator)) ELSE 0.0 END +
96 CASE WHEN ctoua.distance IS NOT NULL THEN 2^(2*weights.org_unit - (ctoua.distance/denominator)) ELSE 0.0 END +
97 CASE WHEN cnoua.distance IS NOT NULL THEN 2^(2*weights.copy_owning_lib - (cnoua.distance/denominator)) ELSE 0.0 END +
98 CASE WHEN iooua.distance IS NOT NULL THEN 2^(2*weights.copy_circ_lib - (iooua.distance/denominator)) ELSE 0.0 END +
99 CASE WHEN uhoua.distance IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0.0 END +
100 -- Circ Type -- Note: 4^x is equiv to 2^(2*x)
101 CASE WHEN m.is_renewal IS NOT NULL THEN 4^weights.is_renewal ELSE 0.0 END +
102 -- Static User Checks
103 CASE WHEN m.juvenile_flag IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0.0 END +
104 CASE WHEN m.usr_age_lower_bound IS NOT NULL THEN 4^weights.usr_age_lower_bound ELSE 0.0 END +
105 CASE WHEN m.usr_age_upper_bound IS NOT NULL THEN 4^weights.usr_age_upper_bound ELSE 0.0 END +
106 -- Static Item Checks
107 CASE WHEN m.circ_modifier IS NOT NULL THEN 4^weights.circ_modifier ELSE 0.0 END +
108 CASE WHEN m.marc_type IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END +
109 CASE WHEN m.marc_form IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END +
110 CASE WHEN m.marc_vr_format IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END +
111 CASE WHEN m.ref_flag IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END DESC,
112 -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
113 -- This prevents "we changed the table order by updating a rule, and we started getting different results"
116 -- Return the entire matchpoint
119 $func$ LANGUAGE plpgsql;
121 CREATE OR REPLACE FUNCTION action.find_hold_matrix_matchpoint(pickup_ou integer, request_ou integer, match_item bigint, match_user integer, match_requestor integer)
125 requestor_object actor.usr%ROWTYPE;
126 user_object actor.usr%ROWTYPE;
127 item_object asset.copy%ROWTYPE;
128 item_cn_object asset.call_number%ROWTYPE;
129 rec_descriptor metabib.rec_descriptor%ROWTYPE;
130 matchpoint config.hold_matrix_matchpoint%ROWTYPE;
131 weights config.hold_matrix_weights%ROWTYPE;
132 denominator NUMERIC(6,2);
134 SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
135 SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor;
136 SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
137 SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
138 SELECT INTO rec_descriptor * FROM metabib.rec_descriptor WHERE record = item_cn_object.record;
140 -- The item's owner should probably be the one determining if the item is holdable
141 -- How to decide that is debatable. Decided to default to the circ library (where the item lives)
142 -- This flag will allow for setting it to the owning library (where the call number "lives")
143 PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.weight_owner_not_circ' AND enabled;
145 -- Grab the closest set circ weight setting.
147 -- Default to circ library
148 SELECT INTO weights hw.*
149 FROM config.weight_assoc wa
150 JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
151 JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) d ON (wa.org_unit = d.id)
156 -- Flag is set, use owning library
157 SELECT INTO weights hw.*
158 FROM config.weight_assoc wa
159 JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
160 JOIN actor.org_unit_ancestors_distance( cn_object.owning_lib ) d ON (wa.org_unit = d.id)
166 -- No weights? Bad admin! Defaults to handle that anyway.
167 IF weights.id IS NULL THEN
168 weights.user_home_ou := 5.0;
169 weights.request_ou := 5.0;
170 weights.pickup_ou := 5.0;
171 weights.item_owning_ou := 5.0;
172 weights.item_circ_ou := 5.0;
173 weights.usr_grp := 7.0;
174 weights.requestor_grp := 8.0;
175 weights.circ_modifier := 4.0;
176 weights.marc_type := 3.0;
177 weights.marc_form := 2.0;
178 weights.marc_vr_format := 1.0;
179 weights.juvenile_flag := 4.0;
180 weights.ref_flag := 0.0;
183 -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
184 -- If you break your org tree with funky parenting this may be wrong
185 -- 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
186 -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting
187 WITH all_distance(distance) AS (
188 SELECT depth AS distance FROM actor.org_unit_type
190 SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
192 SELECT INTO denominator MAX(distance) + 1 FROM all_distance;
194 -- To ATTEMPT to make this work like it used to, make it reverse the user/requestor profile ids.
195 -- This may be better implemented as part of the upgrade script?
196 -- Set usr_grp = requestor_grp, requestor_grp = 1 or something when this flag is already set
197 -- Then remove this flag, of course.
198 PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
201 -- Note: This, to me, is REALLY hacky. I put it in anyway.
202 -- If you can't tell, this is a single call swap on two variables.
203 SELECT INTO user_object.profile, requestor_object.profile
204 requestor_object.profile, user_object.profile;
207 -- Select the winning matchpoint into the matchpoint variable for returning
208 SELECT INTO matchpoint m.*
209 FROM config.hold_matrix_matchpoint m
210 /*LEFT*/ JOIN permission.grp_ancestors_distance( requestor_object.profile ) rpgad ON m.requestor_grp = rpgad.id
211 LEFT JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.usr_grp = upgad.id
212 LEFT JOIN actor.org_unit_ancestors_distance( pickup_ou ) puoua ON m.pickup_ou = puoua.id
213 LEFT JOIN actor.org_unit_ancestors_distance( request_ou ) rqoua ON m.request_ou = rqoua.id
214 LEFT JOIN actor.org_unit_ancestors_distance( item_cn_object.owning_lib ) cnoua ON m.item_owning_ou = cnoua.id
215 LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.item_circ_ou = iooua.id
216 LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou ) uhoua ON m.user_home_ou = uhoua.id
219 -- AND (m.requestor_grp IS NULL OR upgad.id IS NOT NULL) -- Optional Requestor Group?
220 AND (m.usr_grp IS NULL OR upgad.id IS NOT NULL)
222 AND (m.pickup_ou IS NULL OR (puoua.id IS NOT NULL AND (puoua.distance = 0 OR NOT m.strict_ou_match)))
223 AND (m.request_ou IS NULL OR (rqoua.id IS NOT NULL AND (rqoua.distance = 0 OR NOT m.strict_ou_match)))
224 AND (m.item_owning_ou IS NULL OR (cnoua.id IS NOT NULL AND (cnoua.distance = 0 OR NOT m.strict_ou_match)))
225 AND (m.item_circ_ou IS NULL OR (iooua.id IS NOT NULL AND (iooua.distance = 0 OR NOT m.strict_ou_match)))
226 AND (m.user_home_ou IS NULL OR (uhoua.id IS NOT NULL AND (uhoua.distance = 0 OR NOT m.strict_ou_match)))
227 -- Static User Checks
228 AND (m.juvenile_flag IS NULL OR m.juvenile_flag = user_object.juvenile)
229 -- Static Item Checks
230 AND (m.circ_modifier IS NULL OR m.circ_modifier = item_object.circ_modifier)
231 AND (m.marc_type IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type))
232 AND (m.marc_form IS NULL OR m.marc_form = rec_descriptor.item_form)
233 AND (m.marc_vr_format IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
234 AND (m.ref_flag IS NULL OR m.ref_flag = item_object.ref)
237 CASE WHEN rpgad.distance IS NOT NULL THEN 2^(2*weights.requestor_grp - (rpgad.distance/denominator)) ELSE 0.0 END +
238 CASE WHEN upgad.distance IS NOT NULL THEN 2^(2*weights.usr_grp - (upgad.distance/denominator)) ELSE 0.0 END +
240 CASE WHEN puoua.distance IS NOT NULL THEN 2^(2*weights.pickup_ou - (puoua.distance/denominator)) ELSE 0.0 END +
241 CASE WHEN rqoua.distance IS NOT NULL THEN 2^(2*weights.request_ou - (rqoua.distance/denominator)) ELSE 0.0 END +
242 CASE WHEN cnoua.distance IS NOT NULL THEN 2^(2*weights.item_owning_ou - (cnoua.distance/denominator)) ELSE 0.0 END +
243 CASE WHEN iooua.distance IS NOT NULL THEN 2^(2*weights.item_circ_ou - (iooua.distance/denominator)) ELSE 0.0 END +
244 CASE WHEN uhoua.distance IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0.0 END +
245 -- Static User Checks -- Note: 4^x is equiv to 2^(2*x)
246 CASE WHEN m.juvenile_flag IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0.0 END +
247 -- Static Item Checks
248 CASE WHEN m.circ_modifier IS NOT NULL THEN 4^weights.circ_modifier ELSE 0.0 END +
249 CASE WHEN m.marc_type IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END +
250 CASE WHEN m.marc_form IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END +
251 CASE WHEN m.marc_vr_format IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END +
252 CASE WHEN m.ref_flag IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END DESC,
253 -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
254 -- This prevents "we changed the table order by updating a rule, and we started getting different results"
257 -- Return just the ID for now
258 RETURN matchpoint.id;
260 $func$ LANGUAGE 'plpgsql';