3 SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
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$
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;
19 context_org_list INT[];
24 -- Assume success unless we hit a failure condition
25 result.success := TRUE;
27 -- Need user info to look up matchpoints
28 SELECT INTO user_object * FROM actor.usr WHERE id = match_user AND NOT deleted;
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;
39 -- Need item info to look up matchpoints
40 SELECT INTO item_object * FROM asset.copy WHERE id = match_item AND NOT deleted;
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;
51 SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, item_object, user_object, renewal);
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;
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;
73 -- All failures before this point are non-recoverable
74 -- Below this point are possibly overridable failures
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;
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;
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;
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;
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;
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 );
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;
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;
126 penalty_type = '%RENEW%';
128 penalty_type = '%CIRC%';
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
143 result.fail_part := standing_penalty.name;
144 result.success := FALSE;
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;
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;
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);
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;
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 )
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 (
196 FROM actor.org_unit ou
197 WHERE ou.id IN (SELECT * FROM unnest(context_org_list))
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);
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)
218 IF items_out >= circ_limit_set.items_out THEN
219 result.fail_part := 'config.circ_matrix_circ_mod_test';
220 result.success := FALSE;
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;
228 -- If we passed everything, return the successful matchpoint
235 $func$ LANGUAGE plpgsql;