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