]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/upgrade/0527.schema.matrix-bib_level.sql
LP#1117808: release notes for New Access points for MARC Overlay
[Evergreen.git] / Open-ILS / src / sql / Pg / upgrade / 0527.schema.matrix-bib_level.sql
1 BEGIN;
2
3 INSERT INTO config.upgrade_log (version) VALUES ('0527');
4
5 ALTER TABLE config.circ_matrix_weights ADD COLUMN marc_bib_level NUMERIC(6,2) NOT NULL DEFAULT 0.0;
6
7 UPDATE config.circ_matrix_weights
8 SET marc_bib_level = marc_vr_format;
9
10 ALTER TABLE config.hold_matrix_weights ADD COLUMN marc_bib_level NUMERIC(6, 2) NOT NULL DEFAULT 0.0;
11
12 UPDATE config.hold_matrix_weights
13 SET marc_bib_level = marc_vr_format;
14
15 ALTER TABLE config.circ_matrix_weights ALTER COLUMN marc_bib_level DROP DEFAULT;
16
17 ALTER TABLE config.hold_matrix_weights ALTER COLUMN marc_bib_level DROP DEFAULT;
18
19 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN marc_bib_level text;
20
21 ALTER TABLE config.hold_matrix_matchpoint ADD COLUMN marc_bib_level text;
22
23 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$
24 DECLARE
25     cn_object       asset.call_number%ROWTYPE;
26     rec_descriptor  metabib.rec_descriptor%ROWTYPE;
27     cur_matchpoint  config.circ_matrix_matchpoint%ROWTYPE;
28     matchpoint      config.circ_matrix_matchpoint%ROWTYPE;
29     weights         config.circ_matrix_weights%ROWTYPE;
30     user_age        INTERVAL;
31     denominator     NUMERIC(6,2);
32     row_list        INT[];
33     result          action.found_circ_matrix_matchpoint;
34 BEGIN
35     -- Assume failure
36     result.success = false;
37
38     -- Fetch useful data
39     SELECT INTO cn_object       * FROM asset.call_number        WHERE id = item_object.call_number;
40     SELECT INTO rec_descriptor  * FROM metabib.rec_descriptor   WHERE record = cn_object.record;
41
42     -- Pre-generate this so we only calc it once
43     IF user_object.dob IS NOT NULL THEN
44         SELECT INTO user_age age(user_object.dob);
45     END IF;
46
47     -- Grab the closest set circ weight setting.
48     SELECT INTO weights cw.*
49       FROM config.weight_assoc wa
50            JOIN config.circ_matrix_weights cw ON (cw.id = wa.circ_weights)
51            JOIN actor.org_unit_ancestors_distance( context_ou ) d ON (wa.org_unit = d.id)
52       WHERE active
53       ORDER BY d.distance
54       LIMIT 1;
55
56     -- No weights? Bad admin! Defaults to handle that anyway.
57     IF weights.id IS NULL THEN
58         weights.grp                 := 11.0;
59         weights.org_unit            := 10.0;
60         weights.circ_modifier       := 5.0;
61         weights.marc_type           := 4.0;
62         weights.marc_form           := 3.0;
63         weights.marc_bib_level      := 2.0;
64         weights.marc_vr_format      := 2.0;
65         weights.copy_circ_lib       := 8.0;
66         weights.copy_owning_lib     := 8.0;
67         weights.user_home_ou        := 8.0;
68         weights.ref_flag            := 1.0;
69         weights.juvenile_flag       := 6.0;
70         weights.is_renewal          := 7.0;
71         weights.usr_age_lower_bound := 0.0;
72         weights.usr_age_upper_bound := 0.0;
73     END IF;
74
75     -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
76     -- If you break your org tree with funky parenting this may be wrong
77     -- 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
78     -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting
79     WITH all_distance(distance) AS (
80             SELECT depth AS distance FROM actor.org_unit_type
81         UNION
82             SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
83         )
84     SELECT INTO denominator MAX(distance) + 1 FROM all_distance;
85
86     -- Loop over all the potential matchpoints
87     FOR cur_matchpoint IN
88         SELECT m.*
89           FROM  config.circ_matrix_matchpoint m
90                 /*LEFT*/ JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.grp = upgad.id
91                 /*LEFT*/ JOIN actor.org_unit_ancestors_distance( context_ou ) ctoua ON m.org_unit = ctoua.id
92                 LEFT JOIN actor.org_unit_ancestors_distance( cn_object.owning_lib ) cnoua ON m.copy_owning_lib = cnoua.id
93                 LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.copy_circ_lib = iooua.id
94                 LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou  ) uhoua ON m.user_home_ou = uhoua.id
95           WHERE m.active
96                 -- Permission Groups
97              -- AND (m.grp                      IS NULL OR upgad.id IS NOT NULL) -- Optional Permission Group?
98                 -- Org Units
99              -- AND (m.org_unit                 IS NULL OR ctoua.id IS NOT NULL) -- Optional Org Unit?
100                 AND (m.copy_owning_lib          IS NULL OR cnoua.id IS NOT NULL)
101                 AND (m.copy_circ_lib            IS NULL OR iooua.id IS NOT NULL)
102                 AND (m.user_home_ou             IS NULL OR uhoua.id IS NOT NULL)
103                 -- Circ Type
104                 AND (m.is_renewal               IS NULL OR m.is_renewal = renewal)
105                 -- Static User Checks
106                 AND (m.juvenile_flag            IS NULL OR m.juvenile_flag = user_object.juvenile)
107                 AND (m.usr_age_lower_bound      IS NULL OR (user_age IS NOT NULL AND m.usr_age_lower_bound < user_age))
108                 AND (m.usr_age_upper_bound      IS NULL OR (user_age IS NOT NULL AND m.usr_age_upper_bound > user_age))
109                 -- Static Item Checks
110                 AND (m.circ_modifier            IS NULL OR m.circ_modifier = item_object.circ_modifier)
111                 AND (m.marc_type                IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type))
112                 AND (m.marc_form                IS NULL OR m.marc_form = rec_descriptor.item_form)
113                 AND (m.marc_bib_level           IS NULL OR m.marc_bib_level = rec_descriptor.bib_level)
114                 AND (m.marc_vr_format           IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
115                 AND (m.ref_flag                 IS NULL OR m.ref_flag = item_object.ref)
116           ORDER BY
117                 -- Permission Groups
118                 CASE WHEN upgad.distance        IS NOT NULL THEN 2^(2*weights.grp - (upgad.distance/denominator)) ELSE 0.0 END +
119                 -- Org Units
120                 CASE WHEN ctoua.distance        IS NOT NULL THEN 2^(2*weights.org_unit - (ctoua.distance/denominator)) ELSE 0.0 END +
121                 CASE WHEN cnoua.distance        IS NOT NULL THEN 2^(2*weights.copy_owning_lib - (cnoua.distance/denominator)) ELSE 0.0 END +
122                 CASE WHEN iooua.distance        IS NOT NULL THEN 2^(2*weights.copy_circ_lib - (iooua.distance/denominator)) ELSE 0.0 END +
123                 CASE WHEN uhoua.distance        IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0.0 END +
124                 -- Circ Type                    -- Note: 4^x is equiv to 2^(2*x)
125                 CASE WHEN m.is_renewal          IS NOT NULL THEN 4^weights.is_renewal ELSE 0.0 END +
126                 -- Static User Checks
127                 CASE WHEN m.juvenile_flag       IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0.0 END +
128                 CASE WHEN m.usr_age_lower_bound IS NOT NULL THEN 4^weights.usr_age_lower_bound ELSE 0.0 END +
129                 CASE WHEN m.usr_age_upper_bound IS NOT NULL THEN 4^weights.usr_age_upper_bound ELSE 0.0 END +
130                 -- Static Item Checks
131                 CASE WHEN m.circ_modifier       IS NOT NULL THEN 4^weights.circ_modifier ELSE 0.0 END +
132                 CASE WHEN m.marc_type           IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END +
133                 CASE WHEN m.marc_form           IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END +
134                 CASE WHEN m.marc_vr_format      IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END +
135                 CASE WHEN m.ref_flag            IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END DESC,
136                 -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
137                 -- This prevents "we changed the table order by updating a rule, and we started getting different results"
138                 m.id LOOP
139
140         -- Record the full matching row list
141         row_list := row_list || cur_matchpoint.id;
142
143         -- No matchpoint yet?
144         IF matchpoint.id IS NULL THEN
145             -- Take the entire matchpoint as a starting point
146             matchpoint := cur_matchpoint;
147             CONTINUE; -- No need to look at this row any more.
148         END IF;
149
150         -- Incomplete matchpoint?
151         IF matchpoint.circulate IS NULL THEN
152             matchpoint.circulate := cur_matchpoint.circulate;
153         END IF;
154         IF matchpoint.duration_rule IS NULL THEN
155             matchpoint.duration_rule := cur_matchpoint.duration_rule;
156         END IF;
157         IF matchpoint.recurring_fine_rule IS NULL THEN
158             matchpoint.recurring_fine_rule := cur_matchpoint.recurring_fine_rule;
159         END IF;
160         IF matchpoint.max_fine_rule IS NULL THEN
161             matchpoint.max_fine_rule := cur_matchpoint.max_fine_rule;
162         END IF;
163         IF matchpoint.hard_due_date IS NULL THEN
164             matchpoint.hard_due_date := cur_matchpoint.hard_due_date;
165         END IF;
166         IF matchpoint.total_copy_hold_ratio IS NULL THEN
167             matchpoint.total_copy_hold_ratio := cur_matchpoint.total_copy_hold_ratio;
168         END IF;
169         IF matchpoint.available_copy_hold_ratio IS NULL THEN
170             matchpoint.available_copy_hold_ratio := cur_matchpoint.available_copy_hold_ratio;
171         END IF;
172         IF matchpoint.renewals IS NULL THEN
173             matchpoint.renewals := cur_matchpoint.renewals;
174         END IF;
175         IF matchpoint.grace_period IS NULL THEN
176             matchpoint.grace_period := cur_matchpoint.grace_period;
177         END IF;
178     END LOOP;
179
180     -- Check required fields
181     IF matchpoint.circulate             IS NOT NULL AND
182        matchpoint.duration_rule         IS NOT NULL AND
183        matchpoint.recurring_fine_rule   IS NOT NULL AND
184        matchpoint.max_fine_rule         IS NOT NULL THEN
185         -- All there? We have a completed match.
186         result.success := true;
187     END IF;
188
189     -- Include the assembled matchpoint, even if it isn't complete
190     result.matchpoint := matchpoint;
191
192     -- Include (for debugging) the full list of matching rows
193     result.buildrows := row_list;
194
195     -- Hand the result back to caller
196     RETURN result;
197 END;
198 $func$ LANGUAGE plpgsql;
199
200 CREATE OR REPLACE FUNCTION action.find_hold_matrix_matchpoint(pickup_ou integer, request_ou integer, match_item bigint, match_user integer, match_requestor integer)
201   RETURNS integer AS
202 $func$
203 DECLARE
204     requestor_object    actor.usr%ROWTYPE;
205     user_object         actor.usr%ROWTYPE;
206     item_object         asset.copy%ROWTYPE;
207     item_cn_object      asset.call_number%ROWTYPE;
208     rec_descriptor      metabib.rec_descriptor%ROWTYPE;
209     matchpoint          config.hold_matrix_matchpoint%ROWTYPE;
210     weights             config.hold_matrix_weights%ROWTYPE;
211     denominator         NUMERIC(6,2);
212 BEGIN
213     SELECT INTO user_object         * FROM actor.usr                WHERE id = match_user;
214     SELECT INTO requestor_object    * FROM actor.usr                WHERE id = match_requestor;
215     SELECT INTO item_object         * FROM asset.copy               WHERE id = match_item;
216     SELECT INTO item_cn_object      * FROM asset.call_number        WHERE id = item_object.call_number;
217     SELECT INTO rec_descriptor      * FROM metabib.rec_descriptor   WHERE record = item_cn_object.record;
218
219     -- The item's owner should probably be the one determining if the item is holdable
220     -- How to decide that is debatable. Decided to default to the circ library (where the item lives)
221     -- This flag will allow for setting it to the owning library (where the call number "lives")
222     PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.weight_owner_not_circ' AND enabled;
223
224     -- Grab the closest set circ weight setting.
225     IF NOT FOUND THEN
226         -- Default to circ library
227         SELECT INTO weights hw.*
228           FROM config.weight_assoc wa
229                JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
230                JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) d ON (wa.org_unit = d.id)
231           WHERE active
232           ORDER BY d.distance
233           LIMIT 1;
234     ELSE
235         -- Flag is set, use owning library
236         SELECT INTO weights hw.*
237           FROM config.weight_assoc wa
238                JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
239                JOIN actor.org_unit_ancestors_distance( cn_object.owning_lib ) d ON (wa.org_unit = d.id)
240           WHERE active
241           ORDER BY d.distance
242           LIMIT 1;
243     END IF;
244
245     -- No weights? Bad admin! Defaults to handle that anyway.
246     IF weights.id IS NULL THEN
247         weights.user_home_ou    := 5.0;
248         weights.request_ou      := 5.0;
249         weights.pickup_ou       := 5.0;
250         weights.item_owning_ou  := 5.0;
251         weights.item_circ_ou    := 5.0;
252         weights.usr_grp         := 7.0;
253         weights.requestor_grp   := 8.0;
254         weights.circ_modifier   := 4.0;
255         weights.marc_type       := 3.0;
256         weights.marc_form       := 2.0;
257         weights.marc_bib_level  := 1.0;
258         weights.marc_vr_format  := 1.0;
259         weights.juvenile_flag   := 4.0;
260         weights.ref_flag        := 0.0;
261     END IF;
262
263     -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
264     -- If you break your org tree with funky parenting this may be wrong
265     -- 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
266     -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting
267     WITH all_distance(distance) AS (
268             SELECT depth AS distance FROM actor.org_unit_type
269         UNION
270             SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
271         )
272     SELECT INTO denominator MAX(distance) + 1 FROM all_distance;
273
274     -- To ATTEMPT to make this work like it used to, make it reverse the user/requestor profile ids.
275     -- This may be better implemented as part of the upgrade script?
276     -- Set usr_grp = requestor_grp, requestor_grp = 1 or something when this flag is already set
277     -- Then remove this flag, of course.
278     PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
279
280     IF FOUND THEN
281         -- Note: This, to me, is REALLY hacky. I put it in anyway.
282         -- If you can't tell, this is a single call swap on two variables.
283         SELECT INTO user_object.profile, requestor_object.profile
284                     requestor_object.profile, user_object.profile;
285     END IF;
286
287     -- Select the winning matchpoint into the matchpoint variable for returning
288     SELECT INTO matchpoint m.*
289       FROM  config.hold_matrix_matchpoint m
290             /*LEFT*/ JOIN permission.grp_ancestors_distance( requestor_object.profile ) rpgad ON m.requestor_grp = rpgad.id
291             LEFT JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.usr_grp = upgad.id
292             LEFT JOIN actor.org_unit_ancestors_distance( pickup_ou ) puoua ON m.pickup_ou = puoua.id
293             LEFT JOIN actor.org_unit_ancestors_distance( request_ou ) rqoua ON m.request_ou = rqoua.id
294             LEFT JOIN actor.org_unit_ancestors_distance( item_cn_object.owning_lib ) cnoua ON m.item_owning_ou = cnoua.id
295             LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.item_circ_ou = iooua.id
296             LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou  ) uhoua ON m.user_home_ou = uhoua.id
297       WHERE m.active
298             -- Permission Groups
299          -- AND (m.requestor_grp        IS NULL OR upgad.id IS NOT NULL) -- Optional Requestor Group?
300             AND (m.usr_grp              IS NULL OR upgad.id IS NOT NULL)
301             -- Org Units
302             AND (m.pickup_ou            IS NULL OR (puoua.id IS NOT NULL AND (puoua.distance = 0 OR NOT m.strict_ou_match)))
303             AND (m.request_ou           IS NULL OR (rqoua.id IS NOT NULL AND (rqoua.distance = 0 OR NOT m.strict_ou_match)))
304             AND (m.item_owning_ou       IS NULL OR (cnoua.id IS NOT NULL AND (cnoua.distance = 0 OR NOT m.strict_ou_match)))
305             AND (m.item_circ_ou         IS NULL OR (iooua.id IS NOT NULL AND (iooua.distance = 0 OR NOT m.strict_ou_match)))
306             AND (m.user_home_ou         IS NULL OR (uhoua.id IS NOT NULL AND (uhoua.distance = 0 OR NOT m.strict_ou_match)))
307             -- Static User Checks
308             AND (m.juvenile_flag        IS NULL OR m.juvenile_flag = user_object.juvenile)
309             -- Static Item Checks
310             AND (m.circ_modifier        IS NULL OR m.circ_modifier = item_object.circ_modifier)
311             AND (m.marc_type            IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type))
312             AND (m.marc_form            IS NULL OR m.marc_form = rec_descriptor.item_form)
313             AND (m.marc_bib_level       IS NULL OR m.marc_bib_level = rec_descriptor.bib_level)
314             AND (m.marc_vr_format       IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
315             AND (m.ref_flag             IS NULL OR m.ref_flag = item_object.ref)
316       ORDER BY
317             -- Permission Groups
318             CASE WHEN rpgad.distance    IS NOT NULL THEN 2^(2*weights.requestor_grp - (rpgad.distance/denominator)) ELSE 0.0 END +
319             CASE WHEN upgad.distance    IS NOT NULL THEN 2^(2*weights.usr_grp - (upgad.distance/denominator)) ELSE 0.0 END +
320             -- Org Units
321             CASE WHEN puoua.distance    IS NOT NULL THEN 2^(2*weights.pickup_ou - (puoua.distance/denominator)) ELSE 0.0 END +
322             CASE WHEN rqoua.distance    IS NOT NULL THEN 2^(2*weights.request_ou - (rqoua.distance/denominator)) ELSE 0.0 END +
323             CASE WHEN cnoua.distance    IS NOT NULL THEN 2^(2*weights.item_owning_ou - (cnoua.distance/denominator)) ELSE 0.0 END +
324             CASE WHEN iooua.distance    IS NOT NULL THEN 2^(2*weights.item_circ_ou - (iooua.distance/denominator)) ELSE 0.0 END +
325             CASE WHEN uhoua.distance    IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0.0 END +
326             -- Static User Checks       -- Note: 4^x is equiv to 2^(2*x)
327             CASE WHEN m.juvenile_flag   IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0.0 END +
328             -- Static Item Checks
329             CASE WHEN m.circ_modifier   IS NOT NULL THEN 4^weights.circ_modifier ELSE 0.0 END +
330             CASE WHEN m.marc_type       IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END +
331             CASE WHEN m.marc_form       IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END +
332             CASE WHEN m.marc_vr_format  IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END +
333             CASE WHEN m.ref_flag        IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END DESC,
334             -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
335             -- This prevents "we changed the table order by updating a rule, and we started getting different results"
336             m.id;
337
338     -- Return just the ID for now
339     RETURN matchpoint.id;
340 END;
341 $func$ LANGUAGE 'plpgsql';
342
343 COMMIT;