]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/upgrade/0483.dynamic_weights_fix.sql
LP#1947173: Clean up bad cataloging pot hole
[Evergreen.git] / Open-ILS / src / sql / Pg / upgrade / 0483.dynamic_weights_fix.sql
1 BEGIN;
2
3 INSERT INTO config.upgrade_log (version) VALUES ('0483');
4
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$
6 DECLARE
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;
13     user_age        INTERVAL;
14     denominator     NUMERIC(6,2);
15 BEGIN
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;
20
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);
24     END IF;
25
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)
31       WHERE active
32       ORDER BY d.distance
33       LIMIT 1;
34
35     -- No weights? Bad admin! Defaults to handle that anyway.
36     IF weights.id IS NULL THEN
37         weights.grp                 := 11.0;
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;
51     END IF;
52
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
59         UNION
60             SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
61         )
62     SELECT INTO denominator MAX(distance) + 1 FROM all_distance;
63
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
72       WHERE m.active
73             -- Permission Groups
74          -- AND (m.grp                      IS NULL OR upgad.id IS NOT NULL) -- Optional Permission Group?
75             -- Org Units
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)
80             -- Circ Type
81             AND (m.is_renewal               IS NULL OR m.is_renewal = renewal)
82             -- Static User Checks
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))
86             -- Static Item Checks
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)
92       ORDER BY
93             -- Permission Groups
94             CASE WHEN upgad.distance        IS NOT NULL THEN 2^(2*weights.grp - (upgad.distance/denominator)) ELSE 0.0 END +
95             -- Org Units
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"
114             m.id;
115
116     -- Return the entire matchpoint
117     RETURN matchpoint;
118 END;
119 $func$ LANGUAGE plpgsql;
120
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)
122   RETURNS integer AS
123 $func$
124 DECLARE
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);
133 BEGIN
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;
139
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;
144
145     -- Grab the closest set circ weight setting.
146     IF NOT FOUND THEN
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)
152           WHERE active
153           ORDER BY d.distance
154           LIMIT 1;
155     ELSE
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)
161           WHERE active
162           ORDER BY d.distance
163           LIMIT 1;
164     END IF;
165
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;
181     END IF;
182
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
189         UNION
190             SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
191         )
192     SELECT INTO denominator MAX(distance) + 1 FROM all_distance;
193
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;
199
200     IF FOUND THEN
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;
205     END IF;
206
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
217       WHERE m.active
218             -- Permission Groups
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)
221             -- Org Units
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)
235       ORDER BY
236             -- Permission Groups
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 +
239             -- Org Units
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"
255             m.id;
256
257     -- Return just the ID for now
258     RETURN matchpoint.id;
259 END;
260 $func$ LANGUAGE 'plpgsql';
261
262 COMMIT;