add batch forgive fines script
authorChris Sharp <csharp@georgialibraries.org>
Thu, 9 Apr 2020 17:35:35 +0000 (13:35 -0400)
committerChris Sharp <csharp@georgialibraries.org>
Thu, 9 Apr 2020 17:35:35 +0000 (13:35 -0400)
batch_void_fines.sh [deleted file]
overdues/README.md [new file with mode: 0644]
overdues/batch_forgive_fines.pl [new file with mode: 0755]
overdues/legacy/batch_void_fines.sh [new file with mode: 0755]

diff --git a/batch_void_fines.sh b/batch_void_fines.sh
deleted file mode 100755 (executable)
index 9d63302..0000000
+++ /dev/null
@@ -1,183 +0,0 @@
-#!/bin/bash
-
-# Copyright (C) 2014 Georgia Public Library Service
-# Chris Sharp <csharp@georgialibraries.org>
-#
-# A program that takes a library system or branch name and a date or set of dates
-# on the command line to apply a batch void for billings applied on that day.
-# This is particularly useful if closed dates are set after the fine generator has 
-# run for that day.
-#
-# This script assumes that you are on a server with direct access to your master
-# database, and that you have set the credentials for that database in the .pgpass 
-# file in your user's home directory. 
-# 
-# See http://www.postgresql.org/docs/9.1/static/libpq-pgpass.html for more information.
-#
-# Usage Notes:
-#
-# You'll want to set the $EGUSER/$EGPASS variables to the username/password of an Evergreen
-# user (e.g., your administrative user in Evergreen).  Adjust the $SRFSH variable to suit 
-# your environment.  As of this writing, we're still installing Evergreen in /openils.
-# The $VOID_NOTE variable should be something informative to your staff users as to why these
-# bills were voided.  Our use case was closings due to inclement weather.
-#
-# The $BACKUP_SCHEMA variable should be set to a schema in your postgresql database that is
-# for administrative use.  You should NOT use an existing Evergreen schema name (e.g. "action", 
-# "actor", etc. for this purpose.  I use "csharp" for that in our case.
-#
-# My hope is that this can be re-implemented in Perl or something with actual OpenSRF bindings, 
-# but my current skill level and available time prevented me from doing so now.
-
-EGUSER="myuser"
-EGPASS="mypass"
-SRFSH="/openils/bin/srfsh"
-DBHOST="mydbhost"
-DBUSER="mydbuser"
-BACKUP_SCHEMA="myschema"
-
-Usage() { echo "Usage: ./batch_void_fines.sh -d YYYY-MM-DD,YYYY-MM-DD,... -s systemname OR -b branchname." 
-}
-
-while getopts s:b:d:n:h OPTIONS
-do  case "$OPTIONS" in
-    s)  SYSTEM="$OPTARG";;
-    b)  BRANCH="$OPTARG";;
-    d)  DATE="$OPTARG";;
-    n)  VOID_NOTE="$OPTARG";;
-    h)  Usage ; exit 1;;
-    *)  Usage ; exit 2;;
-    esac
-done
-
-if [ -n "$SYSTEM" ] && [ -n "$BRANCH" ]; then
-    echo "Please only specify either system OR branch.  Not both."
-       Usage
-    exit 1;
-fi
-
-if [ -n "$VOID_NOTE" ]; then
-VOID_NOTE="VOIDED BY PINES STAFF BY REQUEST FROM LIBRARY/SYSTEM"
-fi
-
-if [ -z "$DATE" ]; then
-       echo "Date is required."
-    Usage
-    exit 1;
-fi
-
-if [ ! $(echo $DATE | egrep '20[0-9][0-9]-[01][0-9]-[0-3][0-9](,20[0-9][0-9]-[01][0-9]-[0-3][0-9])?') ]; then
-       echo "Date must be in YYYY-MM-DD format.  Please check."
-       Usage
-       exit 1;
-fi
-
-if [ $(echo $DATE | grep ",") ]; then
-    SPLITDATE=`echo $DATE | sed "s/,/', '/g"`
-    DATE=$SPLITDATE
-fi
-
-if [ -n "$SYSTEM" ]; then
-       if [ $(echo $SYSTEM | grep "-") ]; then
-               echo "System names do not have hyphens.  Did you mean -b?"
-               Usage
-               exit 1;
-       fi
-       BACKUP_TABLE="$BACKUP_SCHEMA.`echo $SYSTEM | tr '[:upper:]' '[:lower:]'`_batch_voided_fines_`echo $DATE | tr '-' '_' | sed "s/'//g" | sed "s/, /_/g"`"
-else           
-       if [ ! $(echo $BRANCH | grep "-") ]; then
-        echo "Branch names have hyphens.  Did you mean -s?"
-        Usage
-        exit 1;
-    fi
-       BACKUP_TABLE="$BACKUP_SCHEMA.`echo $BRANCH | tr '[:upper:]' '[:lower:]' | tr '-' '_'`_batch_voided_fines_`echo $DATE | tr '-' '_' | sed "s/'//g" | sed "s/, /_/g"`"
-fi
-read -d '' SYSTEM_Q <<END_OF_Q
-SELECT * INTO $BACKUP_TABLE
-FROM money.billing mb
-WHERE btype = 1
-AND date(billing_ts) IN (
-    '$DATE'
-) AND voided = FALSE
-       AND EXISTS ( 
-    SELECT 1
-    FROM action.circulation ac
-    WHERE mb.xact = ac.id
-    AND ac.circ_lib IN (
-        SELECT id
-        FROM actor.org_unit
-        WHERE parent_ou IN (
-            SELECT id
-            FROM actor.org_unit
-            WHERE shortname = '$SYSTEM')));
-END_OF_Q
-read -d '' BRANCH_Q <<END_OF_Q
-SELECT * INTO $BACKUP_TABLE
-FROM money.billing mb
-WHERE btype = 1 
-AND date(billing_ts) IN (
-    '$DATE'
-) AND voided = FALSE
-       AND EXISTS ( 
-    SELECT 1
-    FROM action.circulation ac
-    WHERE mb.xact = ac.id
-    AND ac.circ_lib IN (
-        SELECT id
-        FROM actor.org_unit
-        WHERE shortname = '$BRANCH'));
-END_OF_Q
-
-if [ -n "$SYSTEM" ]; then
-    echo "$SYSTEM_Q"
-       psql -U "$DBUSER" -h "$DBHOST" -c "$SYSTEM_Q"
-fi
-
-if [ -n "$BRANCH" ]; then
-    echo "$BRANCH_Q"
-       psql -U "$DBUSER" -h "$DBHOST" -c "$BRANCH_Q"
-fi
-
-read -d '' BILLS_SQL <<END_OF_Q
-SELECT id FROM $BACKUP_TABLE;
-END_OF_Q
-
-read -d '' COUNT_SQL <<END_OF_Q
-SELECT count(*)
-FROM money.billing
-WHERE NOT voided
-AND id IN (
-       SELECT id
-       FROM $BACKUP_TABLE
-);
-END_OF_Q
-
-read -d '' UPDATE_SQL <<END_OF_Q
-BEGIN;
-UPDATE money.billing
-SET note = '$VOID_NOTE'
-WHERE id IN (
-       SELECT id 
-       FROM $BACKUP_TABLE
-);
-COMMIT;
-END_OF_Q
-
-BILLS=`psql -A -t -U "$DBUSER" -h "$DBHOST" -c "$BILLS_SQL" | sed 's/^/"/g' | sed 's/$/", /g' | tr '\n' ' '`
-
-AUTHTOKEN=`echo "login $EGUSER $EGPASS" | $SRFSH | grep "Login Session" | cut -d':' -f 2 | cut -d'.' -f1 | sed 's/ //g'`
-
-SRFSH_COMMAND="request open-ils.circ open-ils.circ.money.billing.void \"$AUTHTOKEN\" $BILLS"
-
-echo "$SRFSH_COMMAND" >> "${BACKUP_TABLE}_void.srfsh"
-
-echo "$SRFSH_COMMAND" | $SRFSH
-
-until [ $(psql -A -t -U "$DBUSER" -h "$DBHOST" -c "$COUNT_SQL") == "0" ]; do
-       echo "Waiting for srfsh command to complete..."
-       sleep 10
-done
-
-psql -U "$DBUSER" -h "$DBHOST" -c "$UPDATE_SQL"
-
-echo "Update complete"
diff --git a/overdues/README.md b/overdues/README.md
new file mode 100644 (file)
index 0000000..e24f0a2
--- /dev/null
@@ -0,0 +1,31 @@
+Batch Forgive Fines
+===================
+
+Create a table to work from
+---------------------------
+
+- Example 1: Forgive all fines/fees resulting from circulations at Thomas County Public Library:
+
+`select x.id as xact_id, u.last_xact_id as usr_last_xact_id, u.id as usr_id, x.* into myschema.my_forgiven_fines_and_fees from money.materialized_billable_xact_summary x join action.circulation c on (x.id = c.id) join actor.usr u on (c.usr = u.id) join actor.org_unit cl on (c.circ_lib = cl.id) where cl.shortname ~ '^TCPLS' and x.balance_owed > '0.00';`
+
+- Example 2: Forgive just fines, excluding fees and lost/longoverdue charges:
+
+`select x.id as xact_id, u.last_xact_id as usr_last_xact_id, u.id as usr_id, x.* into myschema.my_forgiven_fines_only from money.materialized_billable_xact_summary x join action.circulation c on (x.id = c.id) join actor.usr u on (c.usr = u.id) join actor.org_unit cl on (c.circ_lib = cl.id) where cl.shortname ~ '^TCPLS' and x.balance_owed > '0.00' and x.last_billing_type ~* '^overdue';`
+
+- Example 3: Forgive "grocery" (staff-created) bills:
+
+`select x.id as xact_id, u.last_xact_id as usr_last_xact_id, u.id as usr_id, x.* into myschema.my_forgiven_grocery from money.materialized_billable_xact_summary x join money.grocery g on (x.id = g.id) join actor.usr u on (g.usr = u.id) join actor.org_unit gl on (g.billing_location = gl.id) where cl.shortname ~ '^TCPLS' and x.balance_owed > '0.00';`
+
+Adjust the Perl to include the table
+------------------------------------
+
+Edit `batch_forgive_fines.pl` and change the assignment of `$source_table` to be the name of the table you created above.
+
+Run the script
+--------------
+
+You need the username and password of a privileged Evergreen user (ex. "admin") and the name of a workstation (ex. "STATELIB-A-csharp-firefox").  Then run:
+
+`./batch_forgive_fines.pl <username> <password> <workstation>`
+
+substituting the actual username, password, and workstation name (no brackets).
diff --git a/overdues/batch_forgive_fines.pl b/overdues/batch_forgive_fines.pl
new file mode 100755 (executable)
index 0000000..a9eefdc
--- /dev/null
@@ -0,0 +1,100 @@
+#!/usr/bin/perl
+# ---------------------------------------------------------------
+# Copyright © 2014 Merrimack Valley Library Consortium
+# Jason Stephenson <jason@sigio.com>
+# Copyright (C) 2018 Georgia Public Library Service (Modifications)
+# Chris Sharp <csharp@georgialibraries.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# ---------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use OpenILS::Utils::Cronscript;
+use Data::Dumper;
+use DBI;
+
+my $source_table = "csharp.tcpls_circ_fines_purge";
+
+my $apputils = 'OpenILS::Application::AppUtils';
+
+my $script = OpenILS::Utils::Cronscript->new({nolockfile=>1});
+
+my $login = {
+    username => $ARGV[0],
+    password => $ARGV[1],
+    workstation => $ARGV[2],
+    type => 'staff'
+};
+
+my $authtoken = $script->authenticate($login);
+
+die "failed to authenticate" unless($authtoken);
+
+END {
+    $script->logout();
+}
+
+sub retrieve_usr_last_xact_id {
+    my $usr_id = shift;
+    my $e = $script->editor(authtoken=>$authtoken);
+    my $user = $e->retrieve_actor_user($usr_id) if ($usr_id);
+    return $e->event unless($user);
+    my $last_xact_id = $user->last_xact_id;
+    return $last_xact_id;
+}
+
+
+my $settings = {
+     host => "db01",
+     db => "evergreen",
+     user => "evergreen"
+};
+
+my $dbh = DBI->connect('DBI:Pg:dbname=' . $settings->{'db'} . ';host=' . $settings->{'host'}, $settings->{'user'}, 
+            undef,
+            {
+                RaiseError => 1,
+                ShowErrorStatement => 0,
+                AutoCommit => 0
+            }
+) or die DBI->errstr;
+
+my $query = "select xact_id, usr_last_xact_id, usr_id, sum(balance_owed) as balance_owed from $source_table where balance_owed > '0.00' group by 1, 2, 3";
+
+my ($xact_id, $usr_last_xact_id, $usr_id, $balance_owed);
+
+my $sth = $dbh->prepare($query);
+$sth->execute();
+my $ref = $sth->bind_columns(\($xact_id, $usr_last_xact_id, $usr_id, $balance_owed));
+
+while ($sth->fetch) {
+    print "applying forgive payment of $balance_owed to xact_id: $xact_id for usr_id: $usr_id\n";
+    my @payments = [$xact_id, $balance_owed];
+    $usr_last_xact_id = retrieve_usr_last_xact_id($usr_id);
+    my $result = $apputils->simplereq(
+        'open-ils.circ',
+        'open-ils.circ.money.payment',
+        $authtoken,
+        {
+            payment_type => "forgive_payment",
+            userid => $usr_id,
+            note => "Bills forgiven per TCPLS System Director - 4/9/2020",
+            payments => \@payments
+        },
+        $usr_last_xact_id
+    );
+    print Dumper $result;
+}
+
+$sth->finish();
+$dbh->disconnect();
diff --git a/overdues/legacy/batch_void_fines.sh b/overdues/legacy/batch_void_fines.sh
new file mode 100755 (executable)
index 0000000..9d63302
--- /dev/null
@@ -0,0 +1,183 @@
+#!/bin/bash
+
+# Copyright (C) 2014 Georgia Public Library Service
+# Chris Sharp <csharp@georgialibraries.org>
+#
+# A program that takes a library system or branch name and a date or set of dates
+# on the command line to apply a batch void for billings applied on that day.
+# This is particularly useful if closed dates are set after the fine generator has 
+# run for that day.
+#
+# This script assumes that you are on a server with direct access to your master
+# database, and that you have set the credentials for that database in the .pgpass 
+# file in your user's home directory. 
+# 
+# See http://www.postgresql.org/docs/9.1/static/libpq-pgpass.html for more information.
+#
+# Usage Notes:
+#
+# You'll want to set the $EGUSER/$EGPASS variables to the username/password of an Evergreen
+# user (e.g., your administrative user in Evergreen).  Adjust the $SRFSH variable to suit 
+# your environment.  As of this writing, we're still installing Evergreen in /openils.
+# The $VOID_NOTE variable should be something informative to your staff users as to why these
+# bills were voided.  Our use case was closings due to inclement weather.
+#
+# The $BACKUP_SCHEMA variable should be set to a schema in your postgresql database that is
+# for administrative use.  You should NOT use an existing Evergreen schema name (e.g. "action", 
+# "actor", etc. for this purpose.  I use "csharp" for that in our case.
+#
+# My hope is that this can be re-implemented in Perl or something with actual OpenSRF bindings, 
+# but my current skill level and available time prevented me from doing so now.
+
+EGUSER="myuser"
+EGPASS="mypass"
+SRFSH="/openils/bin/srfsh"
+DBHOST="mydbhost"
+DBUSER="mydbuser"
+BACKUP_SCHEMA="myschema"
+
+Usage() { echo "Usage: ./batch_void_fines.sh -d YYYY-MM-DD,YYYY-MM-DD,... -s systemname OR -b branchname." 
+}
+
+while getopts s:b:d:n:h OPTIONS
+do  case "$OPTIONS" in
+    s)  SYSTEM="$OPTARG";;
+    b)  BRANCH="$OPTARG";;
+    d)  DATE="$OPTARG";;
+    n)  VOID_NOTE="$OPTARG";;
+    h)  Usage ; exit 1;;
+    *)  Usage ; exit 2;;
+    esac
+done
+
+if [ -n "$SYSTEM" ] && [ -n "$BRANCH" ]; then
+    echo "Please only specify either system OR branch.  Not both."
+       Usage
+    exit 1;
+fi
+
+if [ -n "$VOID_NOTE" ]; then
+VOID_NOTE="VOIDED BY PINES STAFF BY REQUEST FROM LIBRARY/SYSTEM"
+fi
+
+if [ -z "$DATE" ]; then
+       echo "Date is required."
+    Usage
+    exit 1;
+fi
+
+if [ ! $(echo $DATE | egrep '20[0-9][0-9]-[01][0-9]-[0-3][0-9](,20[0-9][0-9]-[01][0-9]-[0-3][0-9])?') ]; then
+       echo "Date must be in YYYY-MM-DD format.  Please check."
+       Usage
+       exit 1;
+fi
+
+if [ $(echo $DATE | grep ",") ]; then
+    SPLITDATE=`echo $DATE | sed "s/,/', '/g"`
+    DATE=$SPLITDATE
+fi
+
+if [ -n "$SYSTEM" ]; then
+       if [ $(echo $SYSTEM | grep "-") ]; then
+               echo "System names do not have hyphens.  Did you mean -b?"
+               Usage
+               exit 1;
+       fi
+       BACKUP_TABLE="$BACKUP_SCHEMA.`echo $SYSTEM | tr '[:upper:]' '[:lower:]'`_batch_voided_fines_`echo $DATE | tr '-' '_' | sed "s/'//g" | sed "s/, /_/g"`"
+else           
+       if [ ! $(echo $BRANCH | grep "-") ]; then
+        echo "Branch names have hyphens.  Did you mean -s?"
+        Usage
+        exit 1;
+    fi
+       BACKUP_TABLE="$BACKUP_SCHEMA.`echo $BRANCH | tr '[:upper:]' '[:lower:]' | tr '-' '_'`_batch_voided_fines_`echo $DATE | tr '-' '_' | sed "s/'//g" | sed "s/, /_/g"`"
+fi
+read -d '' SYSTEM_Q <<END_OF_Q
+SELECT * INTO $BACKUP_TABLE
+FROM money.billing mb
+WHERE btype = 1
+AND date(billing_ts) IN (
+    '$DATE'
+) AND voided = FALSE
+       AND EXISTS ( 
+    SELECT 1
+    FROM action.circulation ac
+    WHERE mb.xact = ac.id
+    AND ac.circ_lib IN (
+        SELECT id
+        FROM actor.org_unit
+        WHERE parent_ou IN (
+            SELECT id
+            FROM actor.org_unit
+            WHERE shortname = '$SYSTEM')));
+END_OF_Q
+read -d '' BRANCH_Q <<END_OF_Q
+SELECT * INTO $BACKUP_TABLE
+FROM money.billing mb
+WHERE btype = 1 
+AND date(billing_ts) IN (
+    '$DATE'
+) AND voided = FALSE
+       AND EXISTS ( 
+    SELECT 1
+    FROM action.circulation ac
+    WHERE mb.xact = ac.id
+    AND ac.circ_lib IN (
+        SELECT id
+        FROM actor.org_unit
+        WHERE shortname = '$BRANCH'));
+END_OF_Q
+
+if [ -n "$SYSTEM" ]; then
+    echo "$SYSTEM_Q"
+       psql -U "$DBUSER" -h "$DBHOST" -c "$SYSTEM_Q"
+fi
+
+if [ -n "$BRANCH" ]; then
+    echo "$BRANCH_Q"
+       psql -U "$DBUSER" -h "$DBHOST" -c "$BRANCH_Q"
+fi
+
+read -d '' BILLS_SQL <<END_OF_Q
+SELECT id FROM $BACKUP_TABLE;
+END_OF_Q
+
+read -d '' COUNT_SQL <<END_OF_Q
+SELECT count(*)
+FROM money.billing
+WHERE NOT voided
+AND id IN (
+       SELECT id
+       FROM $BACKUP_TABLE
+);
+END_OF_Q
+
+read -d '' UPDATE_SQL <<END_OF_Q
+BEGIN;
+UPDATE money.billing
+SET note = '$VOID_NOTE'
+WHERE id IN (
+       SELECT id 
+       FROM $BACKUP_TABLE
+);
+COMMIT;
+END_OF_Q
+
+BILLS=`psql -A -t -U "$DBUSER" -h "$DBHOST" -c "$BILLS_SQL" | sed 's/^/"/g' | sed 's/$/", /g' | tr '\n' ' '`
+
+AUTHTOKEN=`echo "login $EGUSER $EGPASS" | $SRFSH | grep "Login Session" | cut -d':' -f 2 | cut -d'.' -f1 | sed 's/ //g'`
+
+SRFSH_COMMAND="request open-ils.circ open-ils.circ.money.billing.void \"$AUTHTOKEN\" $BILLS"
+
+echo "$SRFSH_COMMAND" >> "${BACKUP_TABLE}_void.srfsh"
+
+echo "$SRFSH_COMMAND" | $SRFSH
+
+until [ $(psql -A -t -U "$DBUSER" -h "$DBHOST" -c "$COUNT_SQL") == "0" ]; do
+       echo "Waiting for srfsh command to complete..."
+       sleep 10
+done
+
+psql -U "$DBUSER" -h "$DBHOST" -c "$UPDATE_SQL"
+
+echo "Update complete"