]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/upgrade/XXXX.lp1747542_action_item_user_circ_test.sql
LP#1747542 - Repair item/user circulation test
[Evergreen.git] / Open-ILS / src / sql / Pg / upgrade / XXXX.lp1747542_action_item_user_circ_test.sql
1 BEGIN;
2
3 SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
4
5 CREATE OR REPLACE FUNCTION action.item_user_circ_test( circ_ou INT, match_item BIGINT, match_user INT, renewal BOOL ) RETURNS SETOF action.circ_matrix_test_result AS $func$
6 DECLARE
7     user_object             actor.usr%ROWTYPE;
8     standing_penalty        config.standing_penalty%ROWTYPE;
9     item_object             asset.copy%ROWTYPE;
10     item_status_object      config.copy_status%ROWTYPE;
11     item_location_object    asset.copy_location%ROWTYPE;
12     result                  action.circ_matrix_test_result;
13     circ_test               action.found_circ_matrix_matchpoint;
14     circ_matchpoint         config.circ_matrix_matchpoint%ROWTYPE;
15     circ_limit_set          config.circ_limit_set%ROWTYPE;
16     hold_ratio              action.hold_stats%ROWTYPE;
17     penalty_type            TEXT;
18     items_out               INT;
19     context_org_list        INT[];
20     done                    BOOL := FALSE;
21     item_prox               INT;
22     home_prox               INT;
23 BEGIN
24     -- Assume success unless we hit a failure condition
25     result.success := TRUE;
26
27     -- Need user info to look up matchpoints
28     SELECT INTO user_object * FROM actor.usr WHERE id = match_user AND NOT deleted;
29
30     -- (Insta)Fail if we couldn't find the user
31     IF user_object.id IS NULL THEN
32         result.fail_part := 'no_user';
33         result.success := FALSE;
34         done := TRUE;
35         RETURN NEXT result;
36         RETURN;
37     END IF;
38
39     -- Need item info to look up matchpoints
40     SELECT INTO item_object * FROM asset.copy WHERE id = match_item AND NOT deleted;
41
42     -- (Insta)Fail if we couldn't find the item 
43     IF item_object.id IS NULL THEN
44         result.fail_part := 'no_item';
45         result.success := FALSE;
46         done := TRUE;
47         RETURN NEXT result;
48         RETURN;
49     END IF;
50
51     SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, item_object, user_object, renewal);
52
53     circ_matchpoint             := circ_test.matchpoint;
54     result.matchpoint           := circ_matchpoint.id;
55     result.circulate            := circ_matchpoint.circulate;
56     result.duration_rule        := circ_matchpoint.duration_rule;
57     result.recurring_fine_rule  := circ_matchpoint.recurring_fine_rule;
58     result.max_fine_rule        := circ_matchpoint.max_fine_rule;
59     result.hard_due_date        := circ_matchpoint.hard_due_date;
60     result.renewals             := circ_matchpoint.renewals;
61     result.grace_period         := circ_matchpoint.grace_period;
62     result.buildrows            := circ_test.buildrows;
63
64     -- (Insta)Fail if we couldn't find a matchpoint
65     IF circ_test.success = false THEN
66         result.fail_part := 'no_matchpoint';
67         result.success := FALSE;
68         done := TRUE;
69         RETURN NEXT result;
70         RETURN;
71     END IF;
72
73     -- All failures before this point are non-recoverable
74     -- Below this point are possibly overridable failures
75
76     -- Fail if the user is barred
77     IF user_object.barred IS TRUE THEN
78         result.fail_part := 'actor.usr.barred';
79         result.success := FALSE;
80         done := TRUE;
81         RETURN NEXT result;
82     END IF;
83
84     -- Fail if the item can't circulate
85     IF item_object.circulate IS FALSE THEN
86         result.fail_part := 'asset.copy.circulate';
87         result.success := FALSE;
88         done := TRUE;
89         RETURN NEXT result;
90     END IF;
91
92     -- Fail if the item isn't in a circulateable status on a non-renewal
93     IF NOT renewal AND item_object.status <> 8 AND item_object.status NOT IN (
94         (SELECT id FROM config.copy_status WHERE is_available) ) THEN 
95         result.fail_part := 'asset.copy.status';
96         result.success := FALSE;
97         done := TRUE;
98         RETURN NEXT result;
99     -- Alternately, fail if the item isn't checked out on a renewal
100     ELSIF renewal AND item_object.status <> 1 THEN
101         result.fail_part := 'asset.copy.status';
102         result.success := FALSE;
103         done := TRUE;
104         RETURN NEXT result;
105     END IF;
106
107     -- Fail if the item can't circulate because of the shelving location
108     SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
109     IF item_location_object.circulate IS FALSE THEN
110         result.fail_part := 'asset.copy_location.circulate';
111         result.success := FALSE;
112         done := TRUE;
113         RETURN NEXT result;
114     END IF;
115
116     -- Use Circ OU for penalties and such
117     SELECT INTO context_org_list ARRAY_AGG(id) FROM actor.org_unit_full_path( circ_ou );
118
119     -- Proximity of user's home_ou to circ_ou to see if penalties should be ignored.
120     SELECT INTO home_prox prox FROM actor.org_unit_proximity WHERE from_org = user_object.home_ou AND to_org = circ_ou;
121
122     -- Proximity of user's home_ou to item circ_lib to see if penalties should be ignored.
123     SELECT INTO item_prox prox FROM actor.org_unit_proximity WHERE from_org = user_object.home_ou AND to_org = item_object.circ_lib;
124
125     IF renewal THEN
126         penalty_type = '%RENEW%';
127     ELSE
128         penalty_type = '%CIRC%';
129     END IF;
130
131     FOR standing_penalty IN
132         SELECT  DISTINCT csp.*
133           FROM  actor.usr_standing_penalty usp
134                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
135           WHERE usr = match_user
136                 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
137                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
138                 AND (csp.ignore_proximity IS NULL
139                      OR csp.ignore_proximity < home_prox
140                      OR csp.ignore_proximity < item_prox)
141                 AND csp.block_list LIKE penalty_type LOOP
142
143         result.fail_part := standing_penalty.name;
144         result.success := FALSE;
145         done := TRUE;
146         RETURN NEXT result;
147     END LOOP;
148
149     -- Fail if the test is set to hard non-circulating
150     IF circ_matchpoint.circulate IS FALSE THEN
151         result.fail_part := 'config.circ_matrix_test.circulate';
152         result.success := FALSE;
153         done := TRUE;
154         RETURN NEXT result;
155     END IF;
156
157     -- Fail if the total copy-hold ratio is too low
158     IF circ_matchpoint.total_copy_hold_ratio IS NOT NULL THEN
159         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
160         IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_matchpoint.total_copy_hold_ratio THEN
161             result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
162             result.success := FALSE;
163             done := TRUE;
164             RETURN NEXT result;
165         END IF;
166     END IF;
167
168     -- Fail if the available copy-hold ratio is too low
169     IF circ_matchpoint.available_copy_hold_ratio IS NOT NULL THEN
170         IF hold_ratio.hold_count IS NULL THEN
171             SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
172         END IF;
173         IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_matchpoint.available_copy_hold_ratio THEN
174             result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
175             result.success := FALSE;
176             done := TRUE;
177             RETURN NEXT result;
178         END IF;
179     END IF;
180
181     -- Fail if the user has too many items out by defined limit sets
182     FOR circ_limit_set IN SELECT ccls.* FROM config.circ_limit_set ccls
183       JOIN config.circ_matrix_limit_set_map ccmlsm ON ccmlsm.limit_set = ccls.id
184       WHERE ccmlsm.active AND ( ccmlsm.matchpoint = circ_matchpoint.id OR
185         ( ccmlsm.matchpoint IN (SELECT * FROM unnest(result.buildrows)) AND ccmlsm.fallthrough )
186         ) LOOP
187             IF circ_limit_set.items_out > 0 AND NOT renewal THEN
188                 SELECT INTO context_org_list ARRAY_AGG(aou.id)
189                   FROM actor.org_unit_full_path( circ_ou ) aou
190                     JOIN actor.org_unit_type aout ON aou.ou_type = aout.id
191                   WHERE aout.depth >= circ_limit_set.depth;
192                 IF circ_limit_set.global THEN
193                     WITH RECURSIVE descendant_depth AS (
194                         SELECT  ou.id,
195                             ou.parent_ou
196                         FROM  actor.org_unit ou
197                         WHERE ou.id IN (SELECT * FROM unnest(context_org_list))
198                             UNION
199                         SELECT  ou.id,
200                             ou.parent_ou
201                         FROM  actor.org_unit ou
202                             JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
203                     ) SELECT INTO context_org_list ARRAY_AGG(ou.id) FROM actor.org_unit ou JOIN descendant_depth USING (id);
204                 END IF;
205                 SELECT INTO items_out COUNT(DISTINCT circ.id)
206                   FROM action.circulation circ
207                     JOIN asset.copy copy ON (copy.id = circ.target_copy)
208                     LEFT JOIN action.circulation_limit_group_map aclgm ON (circ.id = aclgm.circ)
209                   WHERE circ.usr = match_user
210                     AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
211                     AND circ.checkin_time IS NULL
212                     AND circ.xact_finish IS NULL
213                     AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
214                     AND (copy.circ_modifier IN (SELECT circ_mod FROM config.circ_limit_set_circ_mod_map WHERE limit_set = circ_limit_set.id)
215                         OR copy.location IN (SELECT copy_loc FROM config.circ_limit_set_copy_loc_map WHERE limit_set = circ_limit_set.id)
216                         OR aclgm.limit_group IN (SELECT limit_group FROM config.circ_limit_set_group_map WHERE limit_set = circ_limit_set.id)
217                     );
218                 IF items_out >= circ_limit_set.items_out THEN
219                     result.fail_part := 'config.circ_matrix_circ_mod_test';
220                     result.success := FALSE;
221                     done := TRUE;
222                     RETURN NEXT result;
223                 END IF;
224             END IF;
225             SELECT INTO result.limit_groups result.limit_groups || ARRAY_AGG(limit_group) FROM config.circ_limit_set_group_map WHERE limit_set = circ_limit_set.id AND NOT check_only;
226     END LOOP;
227
228     -- If we passed everything, return the successful matchpoint
229     IF NOT done THEN
230         RETURN NEXT result;
231     END IF;
232
233     RETURN;
234 END;
235 $func$ LANGUAGE plpgsql;
236
237 COMMIT;