3 SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
5 INSERT INTO config.standing_penalty (id,name,label,block_list,staff_alert)
6 VALUES (5,'PATRON_EXCEEDS_LOST_COUNT',oils_i18n_gettext(5, 'Patron exceeds max lost item threshold', 'csp', 'label'),'CIRC|FULFILL|HOLD|CAPTURE|RENEW', TRUE);
8 INSERT INTO config.org_unit_setting_type ( name, grp, label, description, datatype ) VALUES (
9 'circ.tally_lost', 'circ',
12 'Include Lost circulations in lump sum tallies in Patron Display.',
17 'In the Patron Display interface, the number of total active circulations for a given patron is presented in the Summary sidebar and underneath the Items Out navigation button. This setting will include Lost circulations as counting toward these tallies.',
23 -- Function: actor.calculate_system_penalties(integer, integer)
24 -- DROP FUNCTION actor.calculate_system_penalties(integer, integer);
26 CREATE OR REPLACE FUNCTION actor.calculate_system_penalties(match_user integer, context_org integer)
27 RETURNS SETOF actor.usr_standing_penalty AS
30 user_object actor.usr%ROWTYPE;
31 new_sp_row actor.usr_standing_penalty%ROWTYPE;
32 existing_sp_row actor.usr_standing_penalty%ROWTYPE;
33 collections_fines permission.grp_penalty_threshold%ROWTYPE;
34 max_fines permission.grp_penalty_threshold%ROWTYPE;
35 max_overdue permission.grp_penalty_threshold%ROWTYPE;
36 max_items_out permission.grp_penalty_threshold%ROWTYPE;
37 max_lost permission.grp_penalty_threshold%ROWTYPE;
42 context_org_list INT[];
43 current_fines NUMERIC(8,2) := 0.0;
44 tmp_fines NUMERIC(8,2);
47 tmp_org actor.org_unit%ROWTYPE;
48 tmp_penalty config.standing_penalty%ROWTYPE;
51 SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
54 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
56 -- Fail if the user has a high fine balance
58 tmp_grp := user_object.profile;
60 SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 1 AND org_unit = tmp_org.id;
62 IF max_fines.threshold IS NULL THEN
63 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
68 IF tmp_grp IS NULL THEN
73 IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
77 SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
81 IF max_fines.threshold IS NOT NULL THEN
85 FROM actor.usr_standing_penalty
86 WHERE usr = match_user
87 AND org_unit = max_fines.org_unit
88 AND (stop_date IS NULL or stop_date > NOW())
89 AND standing_penalty = 1;
91 SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
93 SELECT SUM(f.balance_owed) INTO current_fines
94 FROM money.materialized_billable_xact_summary f
97 FROM booking.reservation r
98 WHERE r.usr = match_user
99 AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
100 AND xact_finish IS NULL
104 WHERE g.usr = match_user
105 AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
106 AND xact_finish IS NULL
109 FROM action.circulation circ
110 WHERE circ.usr = match_user
111 AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
112 AND xact_finish IS NULL ) l USING (id);
114 IF current_fines >= max_fines.threshold THEN
115 new_sp_row.usr := match_user;
116 new_sp_row.org_unit := max_fines.org_unit;
117 new_sp_row.standing_penalty := 1;
118 RETURN NEXT new_sp_row;
122 -- Start over for max overdue
123 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
125 -- Fail if the user has too many overdue items
127 tmp_grp := user_object.profile;
130 SELECT * INTO max_overdue FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 2 AND org_unit = tmp_org.id;
132 IF max_overdue.threshold IS NULL THEN
133 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
138 IF tmp_grp IS NULL THEN
143 IF max_overdue.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
147 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
151 IF max_overdue.threshold IS NOT NULL THEN
155 FROM actor.usr_standing_penalty
156 WHERE usr = match_user
157 AND org_unit = max_overdue.org_unit
158 AND (stop_date IS NULL or stop_date > NOW())
159 AND standing_penalty = 2;
161 SELECT INTO items_overdue COUNT(*)
162 FROM action.circulation circ
163 JOIN actor.org_unit_full_path( max_overdue.org_unit ) fp ON (circ.circ_lib = fp.id)
164 WHERE circ.usr = match_user
165 AND circ.checkin_time IS NULL
166 AND circ.due_date < NOW()
167 AND (circ.stop_fines = 'MAXFINES' OR circ.stop_fines IS NULL);
169 IF items_overdue >= max_overdue.threshold::INT THEN
170 new_sp_row.usr := match_user;
171 new_sp_row.org_unit := max_overdue.org_unit;
172 new_sp_row.standing_penalty := 2;
173 RETURN NEXT new_sp_row;
177 -- Start over for max out
178 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
180 -- Fail if the user has too many checked out items
182 tmp_grp := user_object.profile;
184 SELECT * INTO max_items_out FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 3 AND org_unit = tmp_org.id;
186 IF max_items_out.threshold IS NULL THEN
187 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
192 IF tmp_grp IS NULL THEN
197 IF max_items_out.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
201 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
205 -- Fail if the user has too many items checked out
206 IF max_items_out.threshold IS NOT NULL THEN
209 FROM actor.usr_standing_penalty
210 WHERE usr = match_user
211 AND org_unit = max_items_out.org_unit
212 AND (stop_date IS NULL or stop_date > NOW())
213 AND standing_penalty = 3;
214 SELECT INTO items_out COUNT(*)
215 FROM action.circulation circ
216 JOIN actor.org_unit_full_path( max_items_out.org_unit ) fp ON (circ.circ_lib = fp.id)
217 WHERE circ.usr = match_user
218 AND circ.checkin_time IS NULL
219 AND (circ.stop_fines IN (
220 SELECT 'MAXFINES'::TEXT
222 SELECT 'LONGOVERDUE'::TEXT
228 WHEN (SELECT value FROM actor.org_unit_ancestor_setting('circ.tally_lost', circ.circ_lib)) ILIKE 'true' THEN 'true'
233 SELECT 'CLAIMSRETURNED'::TEXT
237 WHEN (SELECT value FROM actor.org_unit_ancestor_setting('circ.do_not_tally_claims_returned', circ.circ_lib)) ILIKE 'true' THEN 'true'
241 ) OR circ.stop_fines IS NULL)
242 AND xact_finish IS NULL;
244 IF items_out >= max_items_out.threshold::INT THEN
245 new_sp_row.usr := match_user;
246 new_sp_row.org_unit := max_items_out.org_unit;
247 new_sp_row.standing_penalty := 3;
248 RETURN NEXT new_sp_row;
252 -- Start over for max lost
253 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
255 -- Fail if the user has too many lost items
257 tmp_grp := user_object.profile;
259 SELECT * INTO max_lost FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 5 AND org_unit = tmp_org.id;
260 IF max_lost.threshold IS NULL THEN
261 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
266 IF tmp_grp IS NULL THEN
271 IF max_lost.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
275 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
279 IF max_lost.threshold IS NOT NULL THEN
282 FROM actor.usr_standing_penalty
283 WHERE usr = match_user
284 AND org_unit = max_lost.org_unit
285 AND (stop_date IS NULL or stop_date > NOW())
286 AND standing_penalty = 5;
288 SELECT INTO items_lost COUNT(*)
289 FROM action.circulation circ
290 JOIN actor.org_unit_full_path( max_lost.org_unit ) fp ON (circ.circ_lib = fp.id)
291 WHERE circ.usr = match_user
292 AND circ.checkin_time IS NULL
293 AND (circ.stop_fines = 'LOST')
294 AND xact_finish IS NULL;
296 IF items_lost >= max_lost.threshold::INT AND 0 < max_lost.threshold::INT THEN
297 new_sp_row.usr := match_user;
298 new_sp_row.org_unit := max_lost.org_unit;
299 new_sp_row.standing_penalty := 5;
300 RETURN NEXT new_sp_row;
304 -- Start over for collections warning
305 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
307 -- Fail if the user has a collections-level fine balance
309 tmp_grp := user_object.profile;
311 SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 4 AND org_unit = tmp_org.id;
312 IF max_fines.threshold IS NULL THEN
313 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
318 IF tmp_grp IS NULL THEN
323 IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
327 SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
331 IF max_fines.threshold IS NOT NULL THEN
335 FROM actor.usr_standing_penalty
336 WHERE usr = match_user
337 AND org_unit = max_fines.org_unit
338 AND (stop_date IS NULL or stop_date > NOW())
339 AND standing_penalty = 4;
341 SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
343 SELECT SUM(f.balance_owed) INTO current_fines
344 FROM money.materialized_billable_xact_summary f
347 FROM booking.reservation r
348 WHERE r.usr = match_user
349 AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
350 AND r.xact_finish IS NULL
354 WHERE g.usr = match_user
355 AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
356 AND g.xact_finish IS NULL
359 FROM action.circulation circ
360 WHERE circ.usr = match_user
361 AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
362 AND circ.xact_finish IS NULL ) l USING (id);
364 IF current_fines >= max_fines.threshold THEN
365 new_sp_row.usr := match_user;
366 new_sp_row.org_unit := max_fines.org_unit;
367 new_sp_row.standing_penalty := 4;
368 RETURN NEXT new_sp_row;
372 -- Start over for in collections
373 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
375 -- Remove the in-collections penalty if the user has paid down enough
376 -- This penalty is different, because this code is not responsible for creating
377 -- new in-collections penalties, only for removing them
379 tmp_grp := user_object.profile;
381 SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 30 AND org_unit = tmp_org.id;
383 IF max_fines.threshold IS NULL THEN
384 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
389 IF tmp_grp IS NULL THEN
394 IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
398 SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
402 IF max_fines.threshold IS NOT NULL THEN
404 SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
406 -- first, see if the user had paid down to the threshold
407 SELECT SUM(f.balance_owed) INTO current_fines
408 FROM money.materialized_billable_xact_summary f
411 FROM booking.reservation r
412 WHERE r.usr = match_user
413 AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
414 AND r.xact_finish IS NULL
418 WHERE g.usr = match_user
419 AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
420 AND g.xact_finish IS NULL
423 FROM action.circulation circ
424 WHERE circ.usr = match_user
425 AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
426 AND circ.xact_finish IS NULL ) l USING (id);
428 IF current_fines IS NULL OR current_fines <= max_fines.threshold THEN
429 -- patron has paid down enough
431 SELECT INTO tmp_penalty * FROM config.standing_penalty WHERE id = 30;
433 IF tmp_penalty.org_depth IS NOT NULL THEN
435 -- since this code is not responsible for applying the penalty, it can't
436 -- guarantee the current context org will match the org at which the penalty
437 --- was applied. search up the org tree until we hit the configured penalty depth
438 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
439 SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
441 WHILE tmp_depth >= tmp_penalty.org_depth LOOP
445 FROM actor.usr_standing_penalty
446 WHERE usr = match_user
447 AND org_unit = tmp_org.id
448 AND (stop_date IS NULL or stop_date > NOW())
449 AND standing_penalty = 30;
451 IF tmp_org.parent_ou IS NULL THEN
455 SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
456 SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
461 -- no penalty depth is defined, look for exact matches
465 FROM actor.usr_standing_penalty
466 WHERE usr = match_user
467 AND org_unit = max_fines.org_unit
468 AND (stop_date IS NULL or stop_date > NOW())
469 AND standing_penalty = 30;
479 LANGUAGE plpgsql VOLATILE