From d4918fa6a9b433b35dcc1c186902a7235fb928fd Mon Sep 17 00:00:00 2001 From: Kyle Tomita Date: Thu, 14 Mar 2013 15:01:14 -0700 Subject: [PATCH] Patron Blocking by lost items and include lost as items out This feature has two main parts, patron blocking by number of lost items and include lost items as items out. A group penalty threshold will be added for lost items. This will restrict patrons who have lost too many items. The inclusion of lost items as items out will be an optional setting, like claimed returned items. This will allow libraries to set what type of item statuses count toward the total items out. Signed-off-by: Kyle Tomita Signed-off-by: Justin Douma Signed-off-by: Ben Shum --- Open-ILS/src/extras/ils_events.xml | 7 +- Open-ILS/src/sql/Pg/100.circ_matrix.sql | 82 ++- Open-ILS/src/sql/Pg/950.data.seed-values.sql | 17 + ..._items_out_and_patron_blocking_by_lost.sql | 483 ++++++++++++++++++ Open-ILS/web/opac/locale/en-US/lang.dtd | 2 + .../xul/staff_client/server/circ/checkout.js | 9 + Open-ILS/xul/staff_client/server/circ/util.js | 3 + .../xul/staff_client/server/patron/display.js | 3 + .../server/patron/display_horiz_overlay.xul | 2 + .../server/patron/display_overlay.xul | 2 + .../xul/staff_client/server/patron/summary.js | 3 + .../xul/staff_client/server/patron/util.js | 13 + .../server/skin/patron_display.css | 12 + .../lost_items_modifications.txt | 14 + 14 files changed, 647 insertions(+), 5 deletions(-) create mode 100644 Open-ILS/src/sql/Pg/upgrade/XXXX.add_option_for_including_lost_in_items_out_and_patron_blocking_by_lost.sql create mode 100644 docs/RELEASE_NOTES_NEXT/lost_items_modifications.txt diff --git a/Open-ILS/src/extras/ils_events.xml b/Open-ILS/src/extras/ils_events.xml index 84ba694854..9706603d5a 100644 --- a/Open-ILS/src/extras/ils_events.xml +++ b/Open-ILS/src/extras/ils_events.xml @@ -170,10 +170,9 @@ While you were trying to make payments, this account's transaction history changed. Please go back and try again. - - - - + + The patron has too many lost items. + Someone attempted to retrieve a circulation object from the system and diff --git a/Open-ILS/src/sql/Pg/100.circ_matrix.sql b/Open-ILS/src/sql/Pg/100.circ_matrix.sql index 3dbb328ecc..643dd4e097 100644 --- a/Open-ILS/src/sql/Pg/100.circ_matrix.sql +++ b/Open-ILS/src/sql/Pg/100.circ_matrix.sql @@ -648,9 +648,11 @@ DECLARE max_fines permission.grp_penalty_threshold%ROWTYPE; max_overdue permission.grp_penalty_threshold%ROWTYPE; max_items_out permission.grp_penalty_threshold%ROWTYPE; + max_lost permission.grp_penalty_threshold%ROWTYPE; tmp_grp INT; items_overdue INT; items_out INT; + items_lost INT; context_org_list INT[]; current_fines NUMERIC(8,2) := 0.0; tmp_fines NUMERIC(8,2); @@ -831,7 +833,30 @@ BEGIN JOIN actor.org_unit_full_path( max_items_out.org_unit ) fp ON (circ.circ_lib = fp.id) WHERE circ.usr = match_user AND circ.checkin_time IS NULL - AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL); + AND (circ.stop_fines IN ( + SELECT 'MAXFINES'::TEXT + UNION ALL + SELECT 'LONGOVERDUE'::TEXT + UNION ALL + SELECT 'LOST'::TEXT + WHERE 'true' ILIKE + ( + SELECT CASE + WHEN (SELECT value FROM actor.org_unit_ancestor_setting('circ.tally_lost', circ.circ_lib)) ILIKE 'true' THEN 'true' + ELSE 'false' + END + ) + UNION ALL + SELECT 'CLAIMSRETURNED'::TEXT + WHERE 'false' ILIKE + ( + SELECT CASE + WHEN (SELECT value FROM actor.org_unit_ancestor_setting('circ.do_not_tally_claims_returned', circ.circ_lib)) ILIKE 'true' THEN 'true' + ELSE 'false' + END + ) + ) OR circ.stop_fines IS NULL) + AND xact_finish IS NULL; IF items_out >= max_items_out.threshold::INT THEN new_sp_row.usr := match_user; @@ -841,6 +866,61 @@ BEGIN END IF; END IF; + -- Start over for max lost + SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org; + + -- Fail if the user has too many lost items + LOOP + tmp_grp := user_object.profile; + LOOP + + SELECT * INTO max_lost FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 5 AND org_unit = tmp_org.id; + + IF max_lost.threshold IS NULL THEN + SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp; + ELSE + EXIT; + END IF; + + IF tmp_grp IS NULL THEN + EXIT; + END IF; + END LOOP; + + IF max_lost.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN + EXIT; + END IF; + + SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou; + + END LOOP; + + IF max_lost.threshold IS NOT NULL THEN + + RETURN QUERY + SELECT * + FROM actor.usr_standing_penalty + WHERE usr = match_user + AND org_unit = max_lost.org_unit + AND (stop_date IS NULL or stop_date > NOW()) + AND standing_penalty = 5; + + SELECT INTO items_lost COUNT(*) + FROM action.circulation circ + JOIN actor.org_unit_full_path( max_lost.org_unit ) fp ON (circ.circ_lib = fp.id) + WHERE circ.usr = match_user + AND circ.checkin_time IS NULL + AND (circ.stop_fines = 'LOST') + AND xact_finish IS NULL; + + IF items_lost >= max_lost.threshold::INT AND 0 < max_lost.threshold::INT THEN + new_sp_row.usr := match_user; + new_sp_row.org_unit := max_lost.org_unit; + new_sp_row.standing_penalty := 5; + RETURN NEXT new_sp_row; + END IF; + END IF; + -- Start over for collections warning SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org; diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql index a8ddf6a09f..7f401cd5f9 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -27,6 +27,8 @@ INSERT INTO config.standing_penalty (id,name,label,block_list,staff_alert) VALUES (3,'PATRON_EXCEEDS_CHECKOUT_COUNT',oils_i18n_gettext(3, 'Patron exceeds max checked out item threshold', 'csp', 'label'),'CIRC|FULFILL', TRUE); INSERT INTO config.standing_penalty (id,name,label,block_list,staff_alert) VALUES (4,'PATRON_EXCEEDS_COLLECTIONS_WARNING',oils_i18n_gettext(4, 'Patron exceeds pre-collections warning fine threshold', 'csp', 'label'),'CIRC|FULFILL|HOLD|CAPTURE|RENEW', TRUE); +INSERT INTO config.standing_penalty (id,name,label,block_list,staff_alert) + VALUES (5,'PATRON_EXCEEDS_LOST_COUNT',oils_i18n_gettext(5, 'Patron exceeds max lost item threshold', 'csp', 'label'),'CIRC|FULFILL|HOLD|CAPTURE|RENEW', TRUE); INSERT INTO config.standing_penalty (id,name,label,staff_alert) VALUES (20,'ALERT_NOTE',oils_i18n_gettext(20, 'Alerting Note, no blocks', 'csp', 'label'),TRUE); INSERT INTO config.standing_penalty (id,name,label) VALUES (21,'SILENT_NOTE',oils_i18n_gettext(21, 'Note, no blocks', 'csp', 'label')); @@ -1637,6 +1639,8 @@ INSERT INTO permission.grp_penalty_threshold (grp,org_unit,penalty,threshold) VALUES (1,1,2,10.0); INSERT INTO permission.grp_penalty_threshold (grp,org_unit,penalty,threshold) VALUES (1,1,3,10.0); +INSERT INTO permission.grp_penalty_threshold (grp,org_unit,penalty,threshold) + VALUES (1,1,5,10.0); SELECT SETVAL('permission.grp_penalty_threshold_id_seq'::TEXT, (SELECT MAX(id) FROM permission.grp_penalty_threshold)); @@ -2886,6 +2890,19 @@ INSERT into config.org_unit_setting_type 'coust', 'description'), 'bool', null) +,('circ.tally_lost', 'circ', + oils_i18n_gettext( + 'circ.tally_lost', + 'Include Lost circulations in lump sum tallies in Patron Display.', + 'coust', + 'label'), + oils_i18n_gettext( + 'circ.tally_lost', + '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.', + 'coust', + 'description'), + 'bool', null) + ,( 'circ.grace.extend', 'circ', oils_i18n_gettext('circ.grace.extend', 'Auto-Extend Grace Periods', diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.add_option_for_including_lost_in_items_out_and_patron_blocking_by_lost.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.add_option_for_including_lost_in_items_out_and_patron_blocking_by_lost.sql new file mode 100644 index 0000000000..22b60fde77 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.add_option_for_including_lost_in_items_out_and_patron_blocking_by_lost.sql @@ -0,0 +1,483 @@ +BEGIN; + +SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version); + +INSERT INTO config.standing_penalty (id,name,label,block_list,staff_alert) + VALUES (5,'PATRON_EXCEEDS_LOST_COUNT',oils_i18n_gettext(5, 'Patron exceeds max lost item threshold', 'csp', 'label'),'CIRC|FULFILL|HOLD|CAPTURE|RENEW', TRUE); + +INSERT INTO config.org_unit_setting_type ( name, grp, label, description, datatype ) VALUES ( + 'circ.tally_lost', 'circ', + oils_i18n_gettext( + 'circ.tally_lost', + 'Include Lost circulations in lump sum tallies in Patron Display.', + 'coust', + 'label'), + oils_i18n_gettext( + 'circ.tally_lost', + '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.', + 'coust', + 'description'), + 'bool' +); + +-- Function: actor.calculate_system_penalties(integer, integer) +-- DROP FUNCTION actor.calculate_system_penalties(integer, integer); + +CREATE OR REPLACE FUNCTION actor.calculate_system_penalties(match_user integer, context_org integer) + RETURNS SETOF actor.usr_standing_penalty AS +$BODY$ +DECLARE + user_object actor.usr%ROWTYPE; + new_sp_row actor.usr_standing_penalty%ROWTYPE; + existing_sp_row actor.usr_standing_penalty%ROWTYPE; + collections_fines permission.grp_penalty_threshold%ROWTYPE; + max_fines permission.grp_penalty_threshold%ROWTYPE; + max_overdue permission.grp_penalty_threshold%ROWTYPE; + max_items_out permission.grp_penalty_threshold%ROWTYPE; + max_lost permission.grp_penalty_threshold%ROWTYPE; + tmp_grp INT; + items_overdue INT; + items_out INT; + items_lost INT; + context_org_list INT[]; + current_fines NUMERIC(8,2) := 0.0; + tmp_fines NUMERIC(8,2); + tmp_groc RECORD; + tmp_circ RECORD; + tmp_org actor.org_unit%ROWTYPE; + tmp_penalty config.standing_penalty%ROWTYPE; + tmp_depth INTEGER; +BEGIN + SELECT INTO user_object * FROM actor.usr WHERE id = match_user; + + -- Max fines + SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org; + + -- Fail if the user has a high fine balance + LOOP + tmp_grp := user_object.profile; + LOOP + SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 1 AND org_unit = tmp_org.id; + + IF max_fines.threshold IS NULL THEN + SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp; + ELSE + EXIT; + END IF; + + IF tmp_grp IS NULL THEN + EXIT; + END IF; + END LOOP; + + IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN + EXIT; + END IF; + + SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou; + + END LOOP; + + IF max_fines.threshold IS NOT NULL THEN + + RETURN QUERY + SELECT * + FROM actor.usr_standing_penalty + WHERE usr = match_user + AND org_unit = max_fines.org_unit + AND (stop_date IS NULL or stop_date > NOW()) + AND standing_penalty = 1; + + SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit ); + + SELECT SUM(f.balance_owed) INTO current_fines + FROM money.materialized_billable_xact_summary f + JOIN ( + SELECT r.id + FROM booking.reservation r + WHERE r.usr = match_user + AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list)) + AND xact_finish IS NULL + UNION ALL + SELECT g.id + FROM money.grocery g + WHERE g.usr = match_user + AND g.billing_location IN (SELECT * FROM unnest(context_org_list)) + AND xact_finish IS NULL + UNION ALL + SELECT circ.id + FROM action.circulation circ + WHERE circ.usr = match_user + AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list)) + AND xact_finish IS NULL ) l USING (id); + + IF current_fines >= max_fines.threshold THEN + new_sp_row.usr := match_user; + new_sp_row.org_unit := max_fines.org_unit; + new_sp_row.standing_penalty := 1; + RETURN NEXT new_sp_row; + END IF; + END IF; + + -- Start over for max overdue + SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org; + + -- Fail if the user has too many overdue items + LOOP + tmp_grp := user_object.profile; + LOOP + + SELECT * INTO max_overdue FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 2 AND org_unit = tmp_org.id; + + IF max_overdue.threshold IS NULL THEN + SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp; + ELSE + EXIT; + END IF; + + IF tmp_grp IS NULL THEN + EXIT; + END IF; + END LOOP; + + IF max_overdue.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN + EXIT; + END IF; + + SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou; + + END LOOP; + + IF max_overdue.threshold IS NOT NULL THEN + + RETURN QUERY + SELECT * + FROM actor.usr_standing_penalty + WHERE usr = match_user + AND org_unit = max_overdue.org_unit + AND (stop_date IS NULL or stop_date > NOW()) + AND standing_penalty = 2; + + SELECT INTO items_overdue COUNT(*) + FROM action.circulation circ + JOIN actor.org_unit_full_path( max_overdue.org_unit ) fp ON (circ.circ_lib = fp.id) + WHERE circ.usr = match_user + AND circ.checkin_time IS NULL + AND circ.due_date < NOW() + AND (circ.stop_fines = 'MAXFINES' OR circ.stop_fines IS NULL); + + IF items_overdue >= max_overdue.threshold::INT THEN + new_sp_row.usr := match_user; + new_sp_row.org_unit := max_overdue.org_unit; + new_sp_row.standing_penalty := 2; + RETURN NEXT new_sp_row; + END IF; + END IF; + + -- Start over for max out + SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org; + + -- Fail if the user has too many checked out items + LOOP + tmp_grp := user_object.profile; + LOOP + SELECT * INTO max_items_out FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 3 AND org_unit = tmp_org.id; + + IF max_items_out.threshold IS NULL THEN + SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp; + ELSE + EXIT; + END IF; + + IF tmp_grp IS NULL THEN + EXIT; + END IF; + END LOOP; + + IF max_items_out.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN + EXIT; + END IF; + + SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou; + + END LOOP; + + -- Fail if the user has too many items checked out + IF max_items_out.threshold IS NOT NULL THEN + RETURN QUERY + SELECT * + FROM actor.usr_standing_penalty + WHERE usr = match_user + AND org_unit = max_items_out.org_unit + AND (stop_date IS NULL or stop_date > NOW()) + AND standing_penalty = 3; + SELECT INTO items_out COUNT(*) + FROM action.circulation circ + JOIN actor.org_unit_full_path( max_items_out.org_unit ) fp ON (circ.circ_lib = fp.id) + WHERE circ.usr = match_user + AND circ.checkin_time IS NULL + AND (circ.stop_fines IN ( + SELECT 'MAXFINES'::TEXT + UNION ALL + SELECT 'LONGOVERDUE'::TEXT + UNION ALL + SELECT 'LOST'::TEXT + WHERE 'true' ILIKE + ( + SELECT CASE + WHEN (SELECT value FROM actor.org_unit_ancestor_setting('circ.tally_lost', circ.circ_lib)) ILIKE 'true' THEN 'true' + ELSE 'false' + END + ) + UNION ALL + SELECT 'CLAIMSRETURNED'::TEXT + WHERE 'false' ILIKE + ( + SELECT CASE + WHEN (SELECT value FROM actor.org_unit_ancestor_setting('circ.do_not_tally_claims_returned', circ.circ_lib)) ILIKE 'true' THEN 'true' + ELSE 'false' + END + ) + ) OR circ.stop_fines IS NULL) + AND xact_finish IS NULL; + + IF items_out >= max_items_out.threshold::INT THEN + new_sp_row.usr := match_user; + new_sp_row.org_unit := max_items_out.org_unit; + new_sp_row.standing_penalty := 3; + RETURN NEXT new_sp_row; + END IF; +END IF; + + -- Start over for max lost + SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org; + + -- Fail if the user has too many lost items + LOOP + tmp_grp := user_object.profile; + LOOP + SELECT * INTO max_lost FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 5 AND org_unit = tmp_org.id; + IF max_lost.threshold IS NULL THEN + SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp; + ELSE + EXIT; + END IF; + + IF tmp_grp IS NULL THEN + EXIT; + END IF; + END LOOP; + + IF max_lost.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN + EXIT; + END IF; + + SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou; + + END LOOP; + + IF max_lost.threshold IS NOT NULL THEN + RETURN QUERY + SELECT * + FROM actor.usr_standing_penalty + WHERE usr = match_user + AND org_unit = max_lost.org_unit + AND (stop_date IS NULL or stop_date > NOW()) + AND standing_penalty = 5; + + SELECT INTO items_lost COUNT(*) + FROM action.circulation circ + JOIN actor.org_unit_full_path( max_lost.org_unit ) fp ON (circ.circ_lib = fp.id) + WHERE circ.usr = match_user + AND circ.checkin_time IS NULL + AND (circ.stop_fines = 'LOST') + AND xact_finish IS NULL; + + IF items_lost >= max_lost.threshold::INT AND 0 < max_lost.threshold::INT THEN + new_sp_row.usr := match_user; + new_sp_row.org_unit := max_lost.org_unit; + new_sp_row.standing_penalty := 5; + RETURN NEXT new_sp_row; + END IF; + END IF; + + -- Start over for collections warning + SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org; + + -- Fail if the user has a collections-level fine balance + LOOP + tmp_grp := user_object.profile; + LOOP + SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 4 AND org_unit = tmp_org.id; + IF max_fines.threshold IS NULL THEN + SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp; + ELSE + EXIT; + END IF; + + IF tmp_grp IS NULL THEN + EXIT; + END IF; + END LOOP; + + IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN + EXIT; + END IF; + + SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou; + + END LOOP; + + IF max_fines.threshold IS NOT NULL THEN + + RETURN QUERY + SELECT * + FROM actor.usr_standing_penalty + WHERE usr = match_user + AND org_unit = max_fines.org_unit + AND (stop_date IS NULL or stop_date > NOW()) + AND standing_penalty = 4; + + SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit ); + + SELECT SUM(f.balance_owed) INTO current_fines + FROM money.materialized_billable_xact_summary f + JOIN ( + SELECT r.id + FROM booking.reservation r + WHERE r.usr = match_user + AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list)) + AND r.xact_finish IS NULL + UNION ALL + SELECT g.id + FROM money.grocery g + WHERE g.usr = match_user + AND g.billing_location IN (SELECT * FROM unnest(context_org_list)) + AND g.xact_finish IS NULL + UNION ALL + SELECT circ.id + FROM action.circulation circ + WHERE circ.usr = match_user + AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list)) + AND circ.xact_finish IS NULL ) l USING (id); + + IF current_fines >= max_fines.threshold THEN + new_sp_row.usr := match_user; + new_sp_row.org_unit := max_fines.org_unit; + new_sp_row.standing_penalty := 4; + RETURN NEXT new_sp_row; + END IF; + END IF; + + -- Start over for in collections + SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org; + + -- Remove the in-collections penalty if the user has paid down enough + -- This penalty is different, because this code is not responsible for creating + -- new in-collections penalties, only for removing them + LOOP + tmp_grp := user_object.profile; + LOOP + SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 30 AND org_unit = tmp_org.id; + + IF max_fines.threshold IS NULL THEN + SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp; + ELSE + EXIT; + END IF; + + IF tmp_grp IS NULL THEN + EXIT; + END IF; + END LOOP; + + IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN + EXIT; + END IF; + + SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou; + + END LOOP; + + IF max_fines.threshold IS NOT NULL THEN + + SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit ); + + -- first, see if the user had paid down to the threshold + SELECT SUM(f.balance_owed) INTO current_fines + FROM money.materialized_billable_xact_summary f + JOIN ( + SELECT r.id + FROM booking.reservation r + WHERE r.usr = match_user + AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list)) + AND r.xact_finish IS NULL + UNION ALL + SELECT g.id + FROM money.grocery g + WHERE g.usr = match_user + AND g.billing_location IN (SELECT * FROM unnest(context_org_list)) + AND g.xact_finish IS NULL + UNION ALL + SELECT circ.id + FROM action.circulation circ + WHERE circ.usr = match_user + AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list)) + AND circ.xact_finish IS NULL ) l USING (id); + + IF current_fines IS NULL OR current_fines <= max_fines.threshold THEN + -- patron has paid down enough + + SELECT INTO tmp_penalty * FROM config.standing_penalty WHERE id = 30; + + IF tmp_penalty.org_depth IS NOT NULL THEN + + -- since this code is not responsible for applying the penalty, it can't + -- guarantee the current context org will match the org at which the penalty + --- was applied. search up the org tree until we hit the configured penalty depth + SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org; + SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type; + + WHILE tmp_depth >= tmp_penalty.org_depth LOOP + + RETURN QUERY + SELECT * + FROM actor.usr_standing_penalty + WHERE usr = match_user + AND org_unit = tmp_org.id + AND (stop_date IS NULL or stop_date > NOW()) + AND standing_penalty = 30; + + IF tmp_org.parent_ou IS NULL THEN + EXIT; + END IF; + + SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou; + SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type; + END LOOP; + + ELSE + + -- no penalty depth is defined, look for exact matches + + RETURN QUERY + SELECT * + FROM actor.usr_standing_penalty + WHERE usr = match_user + AND org_unit = max_fines.org_unit + AND (stop_date IS NULL or stop_date > NOW()) + AND standing_penalty = 30; + END IF; + + END IF; + + END IF; + + RETURN; +END; +$BODY$ + LANGUAGE plpgsql VOLATILE + COST 100 + ROWS 1000; + +COMMIT; diff --git a/Open-ILS/web/opac/locale/en-US/lang.dtd b/Open-ILS/web/opac/locale/en-US/lang.dtd index d52ae7833f..f2fcb14ff7 100644 --- a/Open-ILS/web/opac/locale/en-US/lang.dtd +++ b/Open-ILS/web/opac/locale/en-US/lang.dtd @@ -3242,9 +3242,11 @@ + + diff --git a/Open-ILS/xul/staff_client/server/circ/checkout.js b/Open-ILS/xul/staff_client/server/circ/checkout.js index ea6afa606c..56d5ef8650 100644 --- a/Open-ILS/xul/staff_client/server/circ/checkout.js +++ b/Open-ILS/xul/staff_client/server/circ/checkout.js @@ -501,6 +501,7 @@ circ.checkout.prototype = { case 1232 /* ITEM_DEPOSIT_REQUIRED */ : case 1233 /* ITEM_RENTAL_FEE_REQUIRED */ : case 1234 /* ITEM_DEPOSIT_PAID */ : + case 1236 /* PATRON_EXCEEDS_LOST_COUNT */ : case 1500 /* ACTION_CIRCULATION_NOT_FOUND */ : case 7002 /* PATRON_EXCEEDS_CHECKOUT_COUNT */ : case 7003 /* COPY_CIRC_NOT_ALLOWED */ : @@ -673,6 +674,7 @@ circ.checkout.prototype = { 1215 /* CIRC_EXCEEDS_COPY_RANGE */, 1232 /* ITEM_DEPOSIT_REQUIRED */, 1233 /* ITEM_RENTAL_FEE_REQUIRED */, + 1236 /* PATRON_EXCEEDS_LOST_COUNT */, 7002 /* PATRON_EXCEEDS_CHECKOUT_COUNT */, 7003 /* COPY_CIRC_NOT_ALLOWED */, 7004 /* COPY_NOT_AVAILABLE */, @@ -684,6 +686,7 @@ circ.checkout.prototype = { 'report_override_on_events' : [ /* Allow auto-override of Patron overrides only */ 1212 /* PATRON_EXCEEDS_OVERDUE_COUNT */, 1213 /* PATRON_BARRED */, + 1236 /* PATRON_EXCEEDS_LOST_COUNT */, 7002 /* PATRON_EXCEEDS_CHECKOUT_COUNT */, 7013 /* PATRON_EXCEEDS_FINES */ ], @@ -701,6 +704,9 @@ circ.checkout.prototype = { '1233' : function(r) { return document.getElementById('circStrings').getString('staff.circ.checkout.override.item_rental_fee_required.warning'); }, + '1236' : function(r) { + return document.getElementById('circStrings').getString('staff.circ.checkout.override.will_auto'); + }, '7002' : function(r) { return document.getElementById('circStrings').getString('staff.circ.checkout.override.will_auto'); }, @@ -861,6 +867,9 @@ circ.checkout.prototype = { break; case 1232 /* ITEM_DEPOSIT_REQUIRED */ : case 1233 /* ITEM_RENTAL_FEE_REQUIRED */ : + case 1236 /* PATRON_EXCEEDS_LOST_COUNT */ : + found_handled = true; + break; case 7013 /* PATRON_EXCEEDS_FINES */ : found_handled = true; break; diff --git a/Open-ILS/xul/staff_client/server/circ/util.js b/Open-ILS/xul/staff_client/server/circ/util.js index af7442f41d..4a3b2a47ab 100644 --- a/Open-ILS/xul/staff_client/server/circ/util.js +++ b/Open-ILS/xul/staff_client/server/circ/util.js @@ -3755,6 +3755,7 @@ circ.util.renew_via_barcode = function ( params, async ) { case 1232 /* ITEM_DEPOSIT_REQUIRED */ : break; case 1233 /* ITEM_RENTAL_FEE_REQUIRED */ : break; case 1234 /* ITEM_DEPOSIT_PAID */ : break; + case 1236 /* PATRON_EXCEEDS_LOST_COUNT */ : break; case 1500 /* ACTION_CIRCULATION_NOT_FOUND */ : break; case 1502 /* ASSET_COPY_NOT_FOUND */ : var mis_scan_msg = document.getElementById('circStrings').getFormattedString('staff.circ.copy_status.status.copy_not_found', [params.barcode]); @@ -3856,6 +3857,7 @@ circ.util.renew_via_barcode = function ( params, async ) { 1232 /* ITEM_DEPOSIT_REQUIRED */, 1233 /* ITEM_RENTAL_FEE_REQUIRED */, 1234 /* ITEM_DEPOSIT_PAID */, + 1236 /* PATRON_EXCEEDS_LOST_COUNT */, 7002 /* PATRON_EXCEEDS_CHECKOUT_COUNT */, 7003 /* COPY_CIRC_NOT_ALLOWED */, 7004 /* COPY_NOT_AVAILABLE */, @@ -3879,6 +3881,7 @@ circ.util.renew_via_barcode = function ( params, async ) { '1234' : function(r) { return document.getElementById('circStrings').getFormattedString('staff.circ.utils.checkin.override.item_deposit_paid.warning'); }, + '1236' : function(r) { return document.getElementById('circStrings').getFormattedString('staff.circ.renew.barcode', [params.barcode]); }, '7002' : function(r) { return document.getElementById('circStrings').getFormattedString('staff.circ.renew.barcode', [params.barcode]); }, '7003' : function(r) { return document.getElementById('circStrings').getFormattedString('staff.circ.renew.barcode', [params.barcode]); }, '7004' : function(r) { diff --git a/Open-ILS/xul/staff_client/server/patron/display.js b/Open-ILS/xul/staff_client/server/patron/display.js index ffac5d1893..4728790146 100644 --- a/Open-ILS/xul/staff_client/server/patron/display.js +++ b/Open-ILS/xul/staff_client/server/patron/display.js @@ -161,8 +161,11 @@ patron.display.prototype = { removeCSSClass(document.documentElement,'PATRON_HAS_BILLS'); removeCSSClass(document.documentElement,'PATRON_HAS_OVERDUES'); removeCSSClass(document.documentElement,'PATRON_HAS_NOTES'); + removeCSSClass(document.documentElement,'PATRON_HAS_LOST'); + removeCSSClass(document.documentElement,'PATRON_HAS_LOST_AND_COUNTED'); removeCSSClass(document.documentElement,'PATRON_EXCEEDS_CHECKOUT_COUNT'); removeCSSClass(document.documentElement,'PATRON_EXCEEDS_OVERDUE_COUNT'); + removeCSSClass(document.documentElement,'PATRON_EXCEEDS_LOST_COUNT'); removeCSSClass(document.documentElement,'PATRON_EXCEEDS_FINES'); removeCSSClass(document.documentElement,'NO_PENALTIES'); removeCSSClass(document.documentElement,'ONE_PENALTY'); diff --git a/Open-ILS/xul/staff_client/server/patron/display_horiz_overlay.xul b/Open-ILS/xul/staff_client/server/patron/display_horiz_overlay.xul index 81fc680208..a73aa4681e 100644 --- a/Open-ILS/xul/staff_client/server/patron/display_horiz_overlay.xul +++ b/Open-ILS/xul/staff_client/server/patron/display_horiz_overlay.xul @@ -29,6 +29,8 @@