1 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$
3 user_object actor.usr%ROWTYPE;
4 standing_penalty config.standing_penalty%ROWTYPE;
5 item_object asset.copy%ROWTYPE;
6 item_status_object config.copy_status%ROWTYPE;
7 item_location_object asset.copy_location%ROWTYPE;
8 result action.circ_matrix_test_result;
9 circ_test action.found_circ_matrix_matchpoint;
10 circ_matchpoint config.circ_matrix_matchpoint%ROWTYPE;
11 out_by_circ_mod config.circ_matrix_circ_mod_test%ROWTYPE;
12 circ_mod_map config.circ_matrix_circ_mod_test_map%ROWTYPE;
13 hold_ratio action.hold_stats%ROWTYPE;
16 context_org_list INT[];
19 -- Assume success unless we hit a failure condition
20 result.success := TRUE;
22 -- Need user info to look up matchpoints
23 SELECT INTO user_object * FROM actor.usr WHERE id = match_user AND NOT deleted;
25 -- (Insta)Fail if we couldn't find the user
26 IF user_object.id IS NULL THEN
27 result.fail_part := 'no_user';
28 result.success := FALSE;
34 -- Need item info to look up matchpoints
35 SELECT INTO item_object * FROM asset.copy WHERE id = match_item AND NOT deleted;
37 -- (Insta)Fail if we couldn't find the item
38 IF item_object.id IS NULL THEN
39 result.fail_part := 'no_item';
40 result.success := FALSE;
46 SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, item_object, user_object, renewal);
48 circ_matchpoint := circ_test.matchpoint;
49 result.matchpoint := circ_matchpoint.id;
50 result.circulate := circ_matchpoint.circulate;
51 result.duration_rule := circ_matchpoint.duration_rule;
52 result.recurring_fine_rule := circ_matchpoint.recurring_fine_rule;
53 result.max_fine_rule := circ_matchpoint.max_fine_rule;
54 result.hard_due_date := circ_matchpoint.hard_due_date;
55 result.renewals := circ_matchpoint.renewals;
56 result.grace_period := circ_matchpoint.grace_period;
57 result.buildrows := circ_test.buildrows;
59 -- (Insta)Fail if we couldn't find a matchpoint
60 IF circ_test.success = false THEN
61 result.fail_part := 'no_matchpoint';
62 result.success := FALSE;
68 -- All failures before this point are non-recoverable
69 -- Below this point are possibly overridable failures
71 -- Fail if the user is barred
72 IF user_object.barred IS TRUE THEN
73 result.fail_part := 'actor.usr.barred';
74 result.success := FALSE;
79 -- Fail if the item can't circulate
80 IF item_object.circulate IS FALSE THEN
81 result.fail_part := 'asset.copy.circulate';
82 result.success := FALSE;
87 -- Fail if the item isn't in a circulateable status on a non-renewal
88 IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN
89 result.fail_part := 'asset.copy.status';
90 result.success := FALSE;
93 -- Alternately, fail if the item isn't checked out on a renewal
94 ELSIF renewal AND item_object.status <> 1 THEN
95 result.fail_part := 'asset.copy.status';
96 result.success := FALSE;
101 -- Fail if the item can't circulate because of the shelving location
102 SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
103 IF item_location_object.circulate IS FALSE THEN
104 result.fail_part := 'asset.copy_location.circulate';
105 result.success := FALSE;
110 -- Use Circ OU for penalties and such
111 SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_ou );
114 penalty_type = '%RENEW%';
116 penalty_type = '%CIRC%';
119 FOR standing_penalty IN
120 SELECT DISTINCT csp.*
121 FROM actor.usr_standing_penalty usp
122 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
123 WHERE usr = match_user
124 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
125 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
126 AND csp.block_list LIKE penalty_type LOOP
128 result.fail_part := standing_penalty.name;
129 result.success := FALSE;
134 -- Fail if the test is set to hard non-circulating
135 IF circ_matchpoint.circulate IS FALSE THEN
136 result.fail_part := 'config.circ_matrix_test.circulate';
137 result.success := FALSE;
142 -- Fail if the total copy-hold ratio is too low
143 IF circ_matchpoint.total_copy_hold_ratio IS NOT NULL THEN
144 SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
145 IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_matchpoint.total_copy_hold_ratio THEN
146 result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
147 result.success := FALSE;
153 -- Fail if the available copy-hold ratio is too low
154 IF circ_matchpoint.available_copy_hold_ratio IS NOT NULL THEN
155 IF hold_ratio.hold_count IS NULL THEN
156 SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
158 IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_matchpoint.available_copy_hold_ratio THEN
159 result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
160 result.success := FALSE;
166 -- Fail if the user has too many items with specific circ_modifiers checked out
167 FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_matchpoint.id LOOP
168 SELECT INTO items_out COUNT(*)
169 FROM action.circulation circ
170 JOIN asset.copy cp ON (cp.id = circ.target_copy)
171 WHERE circ.usr = match_user
172 AND circ.circ_lib IN ( SELECT * FROM unnest(context_org_list) )
173 AND circ.checkin_time IS NULL
174 AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
175 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);
176 IF items_out >= out_by_circ_mod.items_out THEN
177 result.fail_part := 'config.circ_matrix_circ_mod_test';
178 result.success := FALSE;
184 -- If we passed everything, return the successful matchpoint
191 $func$ LANGUAGE plpgsql;