age-circs.sql: move circ exception testing to separate function
authorJeff Davis <jdavis@sitka.bclibraries.ca>
Fri, 4 Dec 2015 23:10:56 +0000 (15:10 -0800)
committerJeff Davis <jdavis@sitka.bclibraries.ca>
Fri, 4 Dec 2015 23:10:56 +0000 (15:10 -0800)
maintenance/age-circs.sql

index b31a4d4..6772352 100644 (file)
@@ -60,6 +60,89 @@ BEGIN
 END;
 $$ LANGUAGE plpgsql;
 
+
+-- Function for testing whether circ should be aged.
+-- If circ CANNOT be aged based on exceptions and user settings, function returns NULL.
+-- If circ CAN be aged, function returns xact_finish timestamp of last circ in chain.
+CREATE OR REPLACE FUNCTION sitka.age_circ_test(circ_id BIGINT, cutoff DATE, skip_basic_tests BOOL DEFAULT FALSE) RETURNS TIMESTAMPTZ AS $$
+DECLARE
+  potential_aged_circ  action.circulation%ROWTYPE;
+  balance              NUMERIC;
+  circ_chain_finish    TIMESTAMP WITH TIME ZONE;
+  usr_keep_start       TEXT;
+  usr_keep_age         TEXT;
+  usr_keep_date        DATE;
+  circ_cutoff          DATE;
+  has_exception        BOOL;
+BEGIN
+
+  -- use the cutoff param as the default cutoff date for the current circ
+  circ_cutoff := cutoff;
+
+  -- grab our circ
+  SELECT * INTO potential_aged_circ FROM action.circulation WHERE id = circ_id;
+
+  -- When you are batch-deleting old circs, you would normally gather a list of
+  -- closed circs with no outstanding balance, and then pass each of those
+  -- circs through the age_circ_test function to check for further exceptions.
+  -- In that situation, we don't want to waste time and processing power
+  -- re-checking whether the circs are closed or have outstanding balances,
+  -- so we provide an option to skip those basic tests.
+  IF skip_basic_tests IS FALSE THEN
+    IF potential_aged_circ.xact_finish IS NULL THEN
+      --RAISE NOTICE 'Cannot age circ %, circ is not closed', circ_id;
+      RETURN NULL;
+    END IF;
+    SELECT balance_owed INTO balance FROM money.materialized_billable_xact_summary WHERE id = circ_id;
+    IF balance != 0 THEN
+      --RAISE NOTICE 'Cannot age circ %, non-zero balance', circ_id;
+      RETURN NULL;
+    END IF;
+  END IF;
+    
+  -- renewals create a "chain" of circs; find the last circ in the current chain
+  SELECT MAX(xact_finish) INTO circ_chain_finish FROM action.circ_chain(potential_aged_circ.id);
+
+  -- user's history.circ.retention_start setting, if any, overrides default cutoff date
+  SELECT value INTO usr_keep_start FROM actor.usr_setting WHERE usr = potential_aged_circ.usr AND name = 'history.circ.retention_start'; IF usr_keep_start IS NOT NULL THEN
+    circ_cutoff := oils_json_to_text(usr_keep_start)::DATE;
+    --RAISE NOTICE 'circ cutoff adjusted to % for circ %', circ_cutoff, circ_id;
+  END IF;
+
+  -- trim cutoff to user's history.circ.retention_age, if set
+  SELECT oils_json_to_text(value) INTO usr_keep_age FROM actor.usr_setting WHERE usr = potential_aged_circ.usr AND name = 'history.circ.retention_age';
+  IF usr_keep_age IS NOT NULL AND sitka.is_valid_interval(usr_keep_age) IS TRUE THEN
+    SELECT (now() - usr_keep_age::INTERVAL)::DATE INTO usr_keep_date;
+    IF usr_keep_date IS NOT NULL AND usr_keep_date < circ_cutoff THEN
+      circ_cutoff := usr_keep_date;
+      --RAISE NOTICE 'circ cutoff adjusted to % for circ %', circ_cutoff, circ_id;
+    END IF;
+  END IF;
+
+  -- don't age the chain if the last circ is later than the cutoff
+  IF circ_chain_finish IS NULL OR circ_chain_finish > circ_cutoff THEN
+    --RAISE NOTICE 'Cannot age circ %, circ chain is still open or was open on cutoff date', circ_id;
+    RETURN NULL;
+  END IF;
+
+  -- don't age the circ if it matches a defined exception
+  has_exception := FALSE;
+  SELECT * INTO has_exception FROM sitka.has_age_circ_exception(potential_aged_circ.id);
+  IF has_exception IS TRUE THEN
+    --RAISE NOTICE 'Cannot age circ %, has exceptions', circ_id;
+    RETURN NULL;
+  END IF;
+
+  -- this circ passes all our tests; return the timestamp when the circ chain was closed
+  RETURN circ_chain_finish;
+
+EXCEPTION WHEN OTHERS THEN
+  RAISE WARNING 'Cannot age circ %, error during age circ test', circ_id;
+  RETURN NULL;
+END;
+$$ LANGUAGE plpgsql;
+
+
 \echo 'table for aged circs'
 CREATE TABLE sitka.aged_circs (
   id BIGINT NOT NULL,
@@ -79,12 +162,8 @@ CREATE TABLE sitka.aged_circs_by_user (
 CREATE OR REPLACE FUNCTION sitka.age_circs(cutoff DATE, max_circ_count BIGINT) RETURNS INT AS $$
 DECLARE
   today                DATE;
-  potential_aged_circ  action.circulation%ROWTYPE;
-  circ_chain_finish    TIMESTAMP WITH TIME ZONE;
-  usr_keep_start       TEXT;
-  usr_keep_age         TEXT;
-  circ_cutoff          DATE;
-  has_exception        BOOL;
+  potential_aged_circ  BIGINT;
+  circ_chain_finish    TIMESTAMPTZ;
   circs_aged           INT;
 BEGIN
   today := now()::DATE;
@@ -101,49 +180,17 @@ BEGIN
 
   -- for each circ that we might want to age...
   FOR potential_aged_circ IN
-    SELECT b.* FROM circs_to_age a
-    JOIN action.circulation b ON a.id = b.id
+    SELECT id FROM circs_to_age
   LOOP
 
-    -- reset variables
     circ_chain_finish := NULL;
-    usr_keep_start    := NULL;
-    usr_keep_age      := NULL;
-
-    -- use the cutoff param as the default cutoff date for the current circ
-    circ_cutoff := cutoff;
-
-    -- renewals create a "chain" of circs; find the last circ in the current chain
-    SELECT MAX(xact_finish) INTO circ_chain_finish FROM action.circ_chain(potential_aged_circ.id);
-    UPDATE circs_to_age SET circ_chain_tail_xact_finish = circ_chain_finish WHERE id = potential_aged_circ.id;
-
-    -- user's history.circ.retention_start setting, if any, overrides default cutoff date
-    SELECT value INTO usr_keep_start FROM actor.usr_setting WHERE usr = potential_aged_circ.usr AND name = 'history.circ.retention_start';
-    IF usr_keep_start IS NOT NULL THEN
-        circ_cutoff := oils_json_to_text(usr_keep_start)::DATE;
-    END IF;
-
-    -- trim cutoff to user's history.circ.retention_age, if set
-    SELECT oils_json_to_text(value) INTO usr_keep_age FROM actor.usr_setting WHERE usr = potential_aged_circ.usr AND name = 'history.circ.retention_age';
-    IF usr_keep_age IS NOT NULL AND sitka.is_valid_interval(usr_keep_age) IS TRUE THEN
-        IF (now() - usr_keep_age::INTERVAL)::DATE > circ_cutoff THEN
-        circ_cutoff := (now() - oils_json_to_text(usr_keep_age)::INTERVAL)::DATE;
-        END IF;
-    END IF;
-
-    -- don't age the chain if the last circ is later than the cutoff
-    IF circ_chain_finish IS NULL OR circ_chain_finish > circ_cutoff THEN
-      DELETE FROM circs_to_age WHERE id IN (SELECT id FROM action.circ_chain(potential_aged_circ.id));
-      CONTINUE; -- proceed to next potential_aged_circ
-    END IF;
 
-    -- don't age the circ if it matches a defined exception
-    -- TODO: If there's a matching exception, we skip only the current circ; should we skip the entire chain?
-    has_exception := FALSE;
-    SELECT * INTO has_exception FROM sitka.has_age_circ_exception(potential_aged_circ.id);
-    IF has_exception IS TRUE THEN
-      DELETE FROM circs_to_age WHERE id = potential_aged_circ.id;
-      CONTINUE; -- proceed to next potential_aged_circ
+    -- test whether we can age each circ
+    SELECT * INTO circ_chain_finish FROM sitka.age_circ_test(potential_aged_circ, cutoff, TRUE);
+    IF circ_chain_finish IS NOT NULL THEN
+      UPDATE circs_to_age SET circ_chain_tail_xact_finish = circ_chain_finish WHERE id = potential_aged_circ;
+    ELSE
+      DELETE FROM circs_to_age WHERE id = potential_aged_circ;
     END IF;
 
   END LOOP;