]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/sql/Pg/upgrade/0168.schema.return_matchpoint_with_fails_where_possible.sql
Stamping upgrade scripts for LP#818740
[working/Evergreen.git] / Open-ILS / src / sql / Pg / upgrade / 0168.schema.return_matchpoint_with_fails_where_possible.sql
1
2 BEGIN;
3
4 INSERT INTO config.upgrade_log (version) VALUES ('0168'); -- miker
5
6 CREATE OR REPLACE FUNCTION action.item_user_circ_test( circ_ou INT, match_item BIGINT, match_user INT, renewal BOOL ) RETURNS SETOF action.matrix_test_result AS $func$
7 DECLARE
8     user_object        actor.usr%ROWTYPE;
9     standing_penalty    config.standing_penalty%ROWTYPE;
10     item_object        asset.copy%ROWTYPE;
11     item_status_object    config.copy_status%ROWTYPE;
12     item_location_object    asset.copy_location%ROWTYPE;
13     result            action.matrix_test_result;
14     circ_test        config.circ_matrix_matchpoint%ROWTYPE;
15     out_by_circ_mod        config.circ_matrix_circ_mod_test%ROWTYPE;
16     circ_mod_map        config.circ_matrix_circ_mod_test_map%ROWTYPE;
17     hold_ratio          action.hold_stats%ROWTYPE;
18     penalty_type         TEXT;
19     tmp_grp         INT;
20     items_out        INT;
21     context_org_list        INT[];
22     done            BOOL := FALSE;
23 BEGIN
24     result.success := TRUE;
25
26     -- Fail if the user is BARRED
27     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
28
29     -- Fail if we couldn't find the user 
30     IF user_object.id IS NULL THEN
31         result.fail_part := 'no_user';
32         result.success := FALSE;
33         done := TRUE;
34         RETURN NEXT result;
35         RETURN;
36     END IF;
37
38     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
39
40     -- Fail if we couldn't find the item 
41     IF item_object.id IS NULL THEN
42         result.fail_part := 'no_user';
43         result.success := FALSE;
44         done := TRUE;
45         RETURN NEXT result;
46         RETURN;
47     END IF;
48
49     SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, match_item, match_user, renewal);
50     result.matchpoint := circ_test.id;
51
52     -- Fail if we couldn't find a matchpoint
53     IF result.matchpoint IS NULL THEN
54         result.fail_part := 'no_matchpoint';
55         result.success := FALSE;
56         done := TRUE;
57         RETURN NEXT result;
58     END IF;
59
60     IF user_object.barred IS TRUE THEN
61         result.fail_part := 'actor.usr.barred';
62         result.success := FALSE;
63         done := TRUE;
64         RETURN NEXT result;
65     END IF;
66
67     -- Fail if the item can't circulate
68     IF item_object.circulate IS FALSE THEN
69         result.fail_part := 'asset.copy.circulate';
70         result.success := FALSE;
71         done := TRUE;
72         RETURN NEXT result;
73     END IF;
74
75     -- Fail if the item isn't in a circulateable status on a non-renewal
76     IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN 
77         result.fail_part := 'asset.copy.status';
78         result.success := FALSE;
79         done := TRUE;
80         RETURN NEXT result;
81     ELSIF renewal AND item_object.status <> 1 THEN
82         result.fail_part := 'asset.copy.status';
83         result.success := FALSE;
84         done := TRUE;
85         RETURN NEXT result;
86     END IF;
87
88     -- Fail if the item can't circulate because of the shelving location
89     SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
90     IF item_location_object.circulate IS FALSE THEN
91         result.fail_part := 'asset.copy_location.circulate';
92         result.success := FALSE;
93         done := TRUE;
94         RETURN NEXT result;
95     END IF;
96
97     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_test.org_unit );
98
99     -- Fail if the test is set to hard non-circulating
100     IF circ_test.circulate IS FALSE THEN
101         result.fail_part := 'config.circ_matrix_test.circulate';
102         result.success := FALSE;
103         done := TRUE;
104         RETURN NEXT result;
105     END IF;
106
107     -- Fail if the total copy-hold ratio is too low
108     IF circ_test.total_copy_hold_ratio IS NOT NULL THEN
109         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
110         IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_test.total_copy_hold_ratio THEN
111             result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
112             result.success := FALSE;
113             done := TRUE;
114             RETURN NEXT result;
115         END IF;
116     END IF;
117
118     -- Fail if the available copy-hold ratio is too low
119     IF circ_test.available_copy_hold_ratio IS NOT NULL THEN
120         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
121         IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_test.available_copy_hold_ratio THEN
122             result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
123             result.success := FALSE;
124             done := TRUE;
125             RETURN NEXT result;
126         END IF;
127     END IF;
128
129     IF renewal THEN
130         penalty_type = '%RENEW%';
131     ELSE
132         penalty_type = '%CIRC%';
133     END IF;
134
135     FOR standing_penalty IN
136         SELECT  DISTINCT csp.*
137           FROM  actor.usr_standing_penalty usp
138                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
139           WHERE usr = match_user
140                 AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
141                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
142                 AND csp.block_list LIKE penalty_type LOOP
143
144         result.fail_part := standing_penalty.name;
145         result.success := FALSE;
146         done := TRUE;
147         RETURN NEXT result;
148     END LOOP;
149
150     -- Fail if the user has too many items with specific circ_modifiers checked out
151     FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_test.id LOOP
152         SELECT  INTO items_out COUNT(*)
153           FROM  action.circulation circ
154             JOIN asset.copy cp ON (cp.id = circ.target_copy)
155           WHERE circ.usr = match_user
156                AND circ.circ_lib IN ( SELECT * FROM explode_array(context_org_list) )
157             AND circ.checkin_time IS NULL
158             AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
159             AND cp.circ_modifier IN (SELECT circ_mod FROM config.circ_matrix_circ_mod_test_map WHERE circ_mod_test = out_by_circ_mod.id);
160         IF items_out >= out_by_circ_mod.items_out THEN
161             result.fail_part := 'config.circ_matrix_circ_mod_test';
162             result.success := FALSE;
163             done := TRUE;
164             RETURN NEXT result;
165         END IF;
166     END LOOP;
167
168     -- If we passed everything, return the successful matchpoint id
169     IF NOT done THEN
170         RETURN NEXT result;
171     END IF;
172
173     RETURN;
174 END;
175 $func$ LANGUAGE plpgsql;
176
177 CREATE OR REPLACE FUNCTION action.hold_request_permit_test( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT ) RETURNS SETOF action.matrix_test_result AS $func$
178 DECLARE
179     matchpoint_id        INT;
180     user_object        actor.usr%ROWTYPE;
181     age_protect_object    config.rule_age_hold_protect%ROWTYPE;
182     standing_penalty    config.standing_penalty%ROWTYPE;
183     transit_range_ou_type    actor.org_unit_type%ROWTYPE;
184     transit_source        actor.org_unit%ROWTYPE;
185     item_object        asset.copy%ROWTYPE;
186     ou_skip              actor.org_unit_setting%ROWTYPE;
187     result            action.matrix_test_result;
188     hold_test        config.hold_matrix_matchpoint%ROWTYPE;
189     hold_count        INT;
190     hold_transit_prox    INT;
191     frozen_hold_count    INT;
192     context_org_list    INT[];
193     done            BOOL := FALSE;
194 BEGIN
195     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
196     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou );
197
198     result.success := TRUE;
199
200     -- Fail if we couldn't find a user
201     IF user_object.id IS NULL THEN
202         result.fail_part := 'no_user';
203         result.success := FALSE;
204         done := TRUE;
205         RETURN NEXT result;
206         RETURN;
207     END IF;
208
209     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
210
211     -- Fail if we couldn't find a copy
212     IF item_object.id IS NULL THEN
213         result.fail_part := 'no_item';
214         result.success := FALSE;
215         done := TRUE;
216         RETURN NEXT result;
217         RETURN;
218     END IF;
219
220     SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor);
221     result.matchpoint := matchpoint_id;
222
223     SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
224
225     -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
226     IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
227         result.fail_part := 'circ.holds.target_skip_me';
228         result.success := FALSE;
229         done := TRUE;
230         RETURN NEXT result;
231         RETURN;
232     END IF;
233
234     -- Fail if user is barred
235     IF user_object.barred IS TRUE THEN
236         result.fail_part := 'actor.usr.barred';
237         result.success := FALSE;
238         done := TRUE;
239         RETURN NEXT result;
240         RETURN;
241     END IF;
242
243     -- Fail if we couldn't find any matchpoint (requires a default)
244     IF matchpoint_id IS NULL THEN
245         result.fail_part := 'no_matchpoint';
246         result.success := FALSE;
247         done := TRUE;
248         RETURN NEXT result;
249         RETURN;
250     END IF;
251
252     SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
253
254     IF hold_test.holdable IS FALSE THEN
255         result.fail_part := 'config.hold_matrix_test.holdable';
256         result.success := FALSE;
257         done := TRUE;
258         RETURN NEXT result;
259     END IF;
260
261     IF hold_test.transit_range IS NOT NULL THEN
262         SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
263         IF hold_test.distance_is_from_owner THEN
264             SELECT INTO transit_source ou.* FROM actor.org_unit ou JOIN asset.call_number cn ON (cn.owning_lib = ou.id) WHERE cn.id = item_object.call_number;
265         ELSE
266             SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
267         END IF;
268
269         PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou;
270
271         IF NOT FOUND THEN
272             result.fail_part := 'transit_range';
273             result.success := FALSE;
274             done := TRUE;
275             RETURN NEXT result;
276         END IF;
277     END IF;
278  
279     FOR standing_penalty IN
280         SELECT  DISTINCT csp.*
281           FROM  actor.usr_standing_penalty usp
282                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
283           WHERE usr = match_user
284                 AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
285                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
286                 AND csp.block_list LIKE '%HOLD%' LOOP
287
288         result.fail_part := standing_penalty.name;
289         result.success := FALSE;
290         done := TRUE;
291         RETURN NEXT result;
292     END LOOP;
293
294     IF hold_test.stop_blocked_user IS TRUE THEN
295         FOR standing_penalty IN
296             SELECT  DISTINCT csp.*
297               FROM  actor.usr_standing_penalty usp
298                     JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
299               WHERE usr = match_user
300                     AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
301                     AND (usp.stop_date IS NULL or usp.stop_date > NOW())
302                     AND csp.block_list LIKE '%CIRC%' LOOP
303     
304             result.fail_part := standing_penalty.name;
305             result.success := FALSE;
306             done := TRUE;
307             RETURN NEXT result;
308         END LOOP;
309     END IF;
310
311     IF hold_test.max_holds IS NOT NULL THEN
312         SELECT    INTO hold_count COUNT(*)
313           FROM    action.hold_request
314           WHERE    usr = match_user
315             AND fulfillment_time IS NULL
316             AND cancel_time IS NULL
317             AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
318
319         IF hold_count >= hold_test.max_holds THEN
320             result.fail_part := 'config.hold_matrix_test.max_holds';
321             result.success := FALSE;
322             done := TRUE;
323             RETURN NEXT result;
324         END IF;
325     END IF;
326
327     IF item_object.age_protect IS NOT NULL THEN
328         SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
329
330         IF item_object.create_date + age_protect_object.age > NOW() THEN
331             IF hold_test.distance_is_from_owner THEN
332                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_prox WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
333             ELSE
334                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_prox WHERE from_org = item_object.circ_lib AND to_org = pickup_ou;
335             END IF;
336
337             IF hold_transit_prox > age_protect_object.prox THEN
338                 result.fail_part := 'config.rule_age_hold_protect.prox';
339                 result.success := FALSE;
340                 done := TRUE;
341                 RETURN NEXT result;
342             END IF;
343         END IF;
344     END IF;
345
346     IF NOT done THEN
347         RETURN NEXT result;
348     END IF;
349
350     RETURN;
351 END;
352 $func$ LANGUAGE plpgsql;
353
354 COMMIT;
355