specify billing/out/etc range by depth, not specific OU (trac ticket #21)
[Evergreen.git] / Open-ILS / src / sql / Pg / 100.circ_matrix.sql
index bb21ae4..33eecbb 100644 (file)
@@ -1,6 +1,11 @@
 
 BEGIN;
 
+CREATE OR REPLACE FUNCTION explode_array(anyarray) RETURNS SETOF anyelement AS $BODY$
+    SELECT ($1)[s] FROM generate_series(1, array_upper($1, 1)) AS s;
+$BODY$
+LANGUAGE 'sql' IMMUTABLE;
+
 -- NOTE: current config.item_type should get sip2_media_type and magnetic_media columns
 
 -- New table needed to handle circ modifiers inside the DB.  Will still require
@@ -100,20 +105,22 @@ CREATE TABLE config.circ_matrix_matchpoint (
        marc_form               TEXT    REFERENCES config.item_form_map (code) DEFERRABLE INITIALLY DEFERRED,
        marc_vr_format  TEXT    REFERENCES config.videorecording_format_map (code) DEFERRABLE INITIALLY DEFERRED,
        ref_flag                BOOL,
+       is_renewal      BOOL,
        usr_age_lower_bound     INTERVAL,
        usr_age_upper_bound     INTERVAL,
-       CONSTRAINT ep_once_per_grp_loc_mod_marc UNIQUE (grp, org_unit, circ_modifier, marc_type, marc_form, marc_vr_format, ref_flag, usr_age_lower_bound, usr_age_upper_bound)
+       CONSTRAINT ep_once_per_grp_loc_mod_marc UNIQUE (grp, org_unit, circ_modifier, marc_type, marc_form, marc_vr_format, ref_flag, usr_age_lower_bound, usr_age_upper_bound, is_renewal)
 );
 
 
 -- Tests to determine if circ can occur for this item at this location for this patron
 CREATE TABLE config.circ_matrix_test (
-       matchpoint                      INT     PRIMARY KEY NOT NULL REFERENCES config.circ_matrix_matchpoint (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
-       circulate                       BOOL    NOT NULL DEFAULT TRUE,  -- Hard "can't circ" flag requiring an override
-       max_items_out           INT,                            -- Total current active circulations must be less than this, NULL means skip (always pass)
-       max_overdue                     INT,                                            -- Total overdue active circulations must be less than this, NULL means skip (always pass)
-       max_fines                       NUMERIC(8,2),                           -- Total fines owed must be less than this, NULL means skip (always pass)
-       script_test                     TEXT                                    -- filename or javascript source ??
+       matchpoint      INT     PRIMARY KEY NOT NULL REFERENCES config.circ_matrix_matchpoint (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+       circulate       BOOL    NOT NULL DEFAULT TRUE,  -- Hard "can't circ" flag requiring an override
+       max_items_out   INT,                            -- Total current active circulations must be less than this, NULL means skip (always pass)
+       max_overdue     INT,                            -- Total overdue active circulations must be less than this, NULL means skip (always pass)
+       max_fines       NUMERIC(8,2),                   -- Total fines owed must be less than this, NULL means skip (always pass)
+       org_depth       INT,                            -- Set to the top OU for the max-out applicability range
+       script_test     TEXT                            -- filename or javascript source ??
 );
 
 -- Tests for max items out by circ_modifier
@@ -133,7 +140,7 @@ CREATE TABLE config.circ_matrix_ruleset (
        max_fine_rule           INT     NOT NULL REFERENCES config.rule_max_fine (id) DEFERRABLE INITIALLY DEFERRED
 );
 
-CREATE OR REPLACE FUNCTION action.find_circ_matrix_matchpoint( context_ou INT, match_item BIGINT, match_user INT ) RETURNS INT AS $func$
+CREATE OR REPLACE FUNCTION action.find_circ_matrix_matchpoint( context_ou INT, match_item BIGINT, match_user INT, renewal BOOL ) RETURNS INT AS $func$
 DECLARE
        current_group   permission.grp_tree%ROWTYPE;
        user_object     actor.usr%ROWTYPE;
@@ -156,6 +163,7 @@ BEGIN
                                LEFT JOIN actor.org_unit_proximity p ON (p.from_org = context_ou AND p.to_org = d.id)
                          WHERE m.grp = current_group.id AND m.active
                          ORDER BY      CASE WHEN p.prox                IS NULL THEN 999 ELSE p.prox END,
+                                       CASE WHEN m.is_renewal = renewal        THEN 64 ELSE 0 END +
                                        CASE WHEN m.circ_modifier       IS NOT NULL THEN 32 ELSE 0 END +
                                        CASE WHEN m.marc_type           IS NOT NULL THEN 16 ELSE 0 END +
                                        CASE WHEN m.marc_form           IS NOT NULL THEN 8 ELSE 0 END +
@@ -226,9 +234,11 @@ DECLARE
        out_by_circ_mod         config.circ_matrix_circ_mod_test%ROWTYPE;
        items_out               INT;
        items_overdue           INT;
+       overdue_orgs            INT[];
        current_fines           NUMERIC(8,2) := 0.0;
        tmp_fines               NUMERIC(8,2);
-       tmp_xact                BIGINT;
+       tmp_groc                RECORD;
+       tmp_circ                RECORD;
        done                    BOOL := FALSE;
 BEGIN
        result.success := TRUE;
@@ -283,9 +293,15 @@ BEGIN
                RETURN NEXT result;
        END IF;
 
-       SELECT INTO matchpoint_id action.find_circ_matrix_matchpoint(circ_ou, match_item, match_user);
+       SELECT INTO matchpoint_id action.find_circ_matrix_matchpoint(circ_ou, match_item, match_user, renewal);
        result.matchpoint := matchpoint_id;
 
+       SELECT INTO circ_test * from config.circ_matrix_test WHERE matchpoint = result.matchpoint;
+
+       IF circ_test.org_depth IS NOT NULL THEN
+               SELECT INTO overdue_orgs ARRAY_ACCUM(id) FROM actor.org_unit_descendants( circ_ou, circ_test.org_depth );
+       END IF; 
+
        -- Fail if we couldn't find a set of tests
        IF result.matchpoint IS NULL THEN
                result.fail_part := 'no_matchpoint';
@@ -307,6 +323,7 @@ BEGIN
        SELECT  INTO items_out COUNT(*)
           FROM  action.circulation
           WHERE usr = match_user
+                AND (circ_test.org_depth IS NULL OR (circ_test.org_depth IS NOT NULL AND circ_lib IN ( SELECT * FROM explode_array(overdue_orgs) )))
                 AND checkin_time IS NULL
                 AND (stop_fines NOT IN ('LOST','CLAIMSRETURNED','LONGOVERDUE') OR stop_fines IS NULL);
                IF items_out >= circ_test.max_items_out THEN
@@ -323,6 +340,7 @@ BEGIN
                  FROM  action.circulation circ
                        JOIN asset.copy cp ON (cp.id = circ.target_copy)
                  WHERE circ.usr = match_user
+                       AND (circ_test.org_depth IS NULL OR (circ_test.org_depth IS NOT NULL AND circ_lib IN ( SELECT * FROM explode_array(overdue_orgs) )))
                        AND circ.checkin_time IS NULL
                        AND (circ.stop_fines NOT IN ('LOST','CLAIMSRETURNED','LONGOVERDUE') OR circ.stop_fines IS NULL)
                        AND cp.circ_modifier = out_by_circ_mod.circ_mod;
@@ -339,6 +357,7 @@ BEGIN
                SELECT  INTO items_overdue COUNT(*)
                  FROM  action.circulation
                  WHERE usr = match_user
+                       AND (circ_test.org_depth IS NULL OR (circ_test.org_depth IS NOT NULL AND circ_lib IN ( SELECT * FROM explode_array(overdue_orgs) )))
                        AND checkin_time IS NULL
                        AND due_date < NOW()
                        AND (stop_fines NOT IN ('LOST','CLAIMSRETURNED','LONGOVERDUE') OR stop_fines IS NULL);
@@ -352,14 +371,21 @@ BEGIN
 
        -- Fail if the user has a high fine balance
        IF circ_test.max_fines IS NOT NULL THEN
-               FOR tmp_xact IN SELECT id FROM money.billable_xact WHERE usr = match_usr AND xact_finish IS NULL LOOP
-                       SELECT INTO tmp_fines SUM( amount ) FROM money.billing WHERE xact = tmp_xact AND NOT voided;
+               FOR tmp_groc IN SELECT * FROM money.grocery WHERE usr = match_usr AND xact_finish IS NULL AND (circ_test.org_depth IS NULL OR (circ_test.org_depth IS NOT NULL AND billing_location IN ( SELECT * FROM explode_array(overdue_orgs) ))) LOOP
+                       SELECT INTO tmp_fines SUM( amount ) FROM money.billing WHERE xact = tmp_groc.id AND NOT voided;
+                       current_fines = current_fines + COALESCE(tmp_fines, 0.0);
+                       SELECT INTO tmp_fines SUM( amount ) FROM money.payment WHERE xact = tmp_groc.id AND NOT voided;
+                       current_fines = current_fines - COALESCE(tmp_fines, 0.0);
+               END LOOP;
+
+               FOR tmp_circ IN SELECT * FROM action.circulation WHERE usr = match_usr AND xact_finish IS NULL AND (circ_test.org_depth IS NULL OR (circ_test.org_depth IS NOT NULL AND circ_lib IN ( SELECT * FROM explode_array(overdue_orgs) ))) LOOP
+                       SELECT INTO tmp_fines SUM( amount ) FROM money.billing WHERE xact = tmp_circ.id AND NOT voided;
                        current_fines = current_fines + COALESCE(tmp_fines, 0.0);
-                       SELECT INTO tmp_fines SUM( amount ) FROM money.payment WHERE xact = tmp_xact AND NOT voided;
+                       SELECT INTO tmp_fines SUM( amount ) FROM money.payment WHERE xact = tmp_circ.id AND NOT voided;
                        current_fines = current_fines - COALESCE(tmp_fines, 0.0);
                END LOOP;
 
-               IF current_fines >= circ_test.max_overdue THEN
+               IF current_fines >= circ_test.max_fines THEN
                        result.fail_part := 'config.circ_matrix_test.max_fines';
                        result.success := FALSE;
                        RETURN NEXT result;