From 70ccd8f7bfda225133522c16d83205a1a94d4c8a Mon Sep 17 00:00:00 2001 From: miker Date: Wed, 20 Oct 2010 13:23:09 +0000 Subject: [PATCH] Summary: Patch from Thomas Berezansky providing an alternate implementation of Hard Due Dates. Overview: * Moves hard due date configuration from the duration rule to the circ matrix * Implements backward-compatible API for circ scripts * Provides a stored procedure which updates hard due date values on demand Further extension by Mike Rylander: * Provide a srfsh script and crontab.example entry to fire the stored procedure nightly * Expose stored procedure and add to the upgrade script TODO: Create UIs for configuring hard due dates; integrate into version upgrade scripts git-svn-id: svn://svn.open-ils.org/ILS/trunk@18396 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- Open-ILS/examples/crontab.example | 10 ++-- Open-ILS/examples/fm_IDL.xml | 36 +++++++++++++-- Open-ILS/src/Makefile.am | 4 ++ .../src/perlmods/OpenILS/Application/AppUtils.pm | 11 +++++ .../perlmods/OpenILS/Application/Circ/Circulate.pm | 48 ++++++++++++++++---- Open-ILS/src/sql/Pg/002.schema.config.sql | 49 ++++++++++++++++---- Open-ILS/src/sql/Pg/100.circ_matrix.sql | 1 + .../0442.schema.due_date_ceiling_alt_impl.sql | 53 ++++++++++++++++++++++ .../support-scripts/update_hard_due_dates.srfsh | 7 +++ Open-ILS/web/templates/base.tt2 | 2 +- 10 files changed, 195 insertions(+), 26 deletions(-) create mode 100644 Open-ILS/src/sql/Pg/upgrade/0442.schema.due_date_ceiling_alt_impl.sql create mode 100755 Open-ILS/src/support-scripts/update_hard_due_dates.srfsh diff --git a/Open-ILS/examples/crontab.example b/Open-ILS/examples/crontab.example index ea1386a16c..2f39344938 100644 --- a/Open-ILS/examples/crontab.example +++ b/Open-ILS/examples/crontab.example @@ -51,14 +51,19 @@ EG_BIN_DIR = /openils/bin # 5 2 * * * . ~/.bashrc && oils_ctl.sh -d $OPENILS/var/pid -s $OPENILS/conf/oils_sip.xml -a stop_sip # 8 2 * * * . ~/.bashrc && oils_ctl.sh -d $OPENILS/var/pid -s $OPENILS/conf/oils_sip.xml -a start_sip +# Run the circ history cleanup job +# Note: destroys link between user and item +#2 0 * * * . ~/.bashrc && $EG_BIN_DIR/clear_expired_circ_history.srfsh + +# Run the hard due date updater +2 3 * * * . ~/.bashrc && $EG_BIN_DIR/update_hard_due_dates.srfsh + # Action/Trigger entries ---- # Runs all pending A/T events every half hour 0 */2 * * * . ~/.bashrc && $EG_BIN_DIR/action_trigger_runner.pl --osrf-config $SRF_CORE --run-pending # Passive A/T event generation. -# Note: the --granularity flag is not supported in 1.6 -# Note: passive event defs with no granularity will be processed regardless of any granularity flags # Note: push these back to 3am so they will run after the fine generator and spread out the start minute to reduce dogpiling 0 * * * * . ~/.bashrc && $EG_BIN_DIR/action_trigger_runner.pl --osrf-config $SRF_CORE --process-hooks --granularity hourly 5 3 * * * . ~/.bashrc && $EG_BIN_DIR/action_trigger_runner.pl --osrf-config $SRF_CORE --process-hooks --granularity daily @@ -71,6 +76,5 @@ EG_BIN_DIR = /openils/bin #XML_FILE_PREFIX = /openils/var/data/overdue/overdue #0 3 * * * . ~/.bashrc && cd $EG_BIN_DIR && ./generate_circ_notices.pl --osrf_config $SRF_CORE --notice-types overdue,predue --generate-global-templates --send-email > $XML_FILE_PREFIX.$(date +"\%F").xml - # TODO: add other entries diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index 25206b3e71..b8f71f87a1 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -1064,6 +1064,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + @@ -1080,6 +1081,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + @@ -1869,7 +1871,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - @@ -1885,15 +1886,42 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + + + + + + + + + + + + + + + + + + + + - + - + - + + + + + + + diff --git a/Open-ILS/src/Makefile.am b/Open-ILS/src/Makefile.am index e957845f56..63a8f53b30 100644 --- a/Open-ILS/src/Makefile.am +++ b/Open-ILS/src/Makefile.am @@ -72,6 +72,8 @@ core_scripts = $(examples)/oils_ctl.sh \ $(supportscr)/fine_generator.pl \ $(supportscr)/hold_targeter.pl \ $(supportscr)/reshelving_complete.srfsh \ + $(supportscr)/clear_expired_circ_history.srfsh \ + $(supportscr)/update_hard_due_dates.srfsh \ $(supportscr)/juv_to_adult.srfsh \ $(supportscr)/thaw_expired_frozen_holds.srfsh \ $(supportscr)/long-overdue-status-update.pl \ @@ -188,6 +190,8 @@ ilscore-install: sed -i 's|LOCALSTATEDIR|@localstatedir@|g' '$(DESTDIR)@bindir@/autogen.sh' sed -i 's|SYSCONFDIR|@sysconfdir@|g' '$(DESTDIR)@bindir@/autogen.sh' sed -i 's|BINDIR|@bindir@|g' '$(DESTDIR)@bindir@/reshelving_complete.srfsh' + sed -i 's|BINDIR|@bindir@|g' '$(DESTDIR)@bindir@/clear_expired_circ_history.srfsh' + sed -i 's|BINDIR|@bindir@|g' '$(DESTDIR)@bindir@/update_hard_due_dates.srfsh' sed -i 's|BINDIR|@bindir@|g' '$(DESTDIR)@bindir@/juv_to_adult.srfsh' sed -i 's|BINDIR|@bindir@|g' '$(DESTDIR)@bindir@/long-overdue-status-update.pl' sed -i 's|SYSCONFDIR|@sysconfdir@|g' '$(DESTDIR)@bindir@/long-overdue-status-update.pl' diff --git a/Open-ILS/src/perlmods/OpenILS/Application/AppUtils.pm b/Open-ILS/src/perlmods/OpenILS/Application/AppUtils.pm index 74f270f5e2..6bf3c4c28b 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/AppUtils.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/AppUtils.pm @@ -888,6 +888,17 @@ sub fetch_max_fine_by_name { return ($obj, $evt); } +sub fetch_hard_due_date_by_name { + my( $self, $name ) = @_; + my( $obj, $evt ); + $obj = $self->simplereq( + 'open-ils.cstore', + 'open-ils.cstore.direct.config.hard_due_date.search.atomic', { name => $name } ); + $obj = $obj->[0]; + $evt = OpenILS::Event->new('CONFIG_RULES_HARD_DUE_DATE_NOT_FOUND') unless $obj; + return ($obj, $evt); +} + sub storagereq { my( $self, $method, @params ) = @_; return $self->simplereq( diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Circ/Circulate.pm b/Open-ILS/src/perlmods/OpenILS/Application/Circ/Circulate.pm index 66330c9425..3620099c0a 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Circ/Circulate.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Circ/Circulate.pm @@ -473,6 +473,7 @@ my @AUTOLOAD_FIELDS = qw/ recurring_fines_rule max_fine_rule renewal_remaining + hard_due_date due_date fulfilled_holds transit @@ -1137,7 +1138,7 @@ sub run_indb_circ_test { $mp, { flesh => 1, flesh_fields => {ccmm => - ['duration_rule', 'recurring_fine_rule', 'max_fine_rule']} + ['duration_rule', 'recurring_fine_rule', 'max_fine_rule', 'hard_due_date']} } ]) ); @@ -1173,9 +1174,10 @@ sub do_inspect { my $duration_rule = $self->circ_matrix_matchpoint->duration_rule; my $recurring_fine_rule = $self->circ_matrix_matchpoint->recurring_fine_rule; my $max_fine_rule = $self->circ_matrix_matchpoint->max_fine_rule; + my $hard_due_date = $self->circ_matrix_matchpoint->hard_due_date; my $policy = $self->get_circ_policy( - $duration_rule, $recurring_fine_rule, $max_fine_rule); + $duration_rule, $recurring_fine_rule, $max_fine_rule, $hard_due_date); $$results{$_} = $$policy{$_} for keys %$policy; } @@ -1188,7 +1190,7 @@ sub do_inspect { # fine based on the current copy # --------------------------------------------------------------------- sub get_circ_policy { - my($self, $duration_rule, $recurring_fine_rule, $max_fine_rule) = @_; + my($self, $duration_rule, $recurring_fine_rule, $max_fine_rule, $hard_due_date) = @_; my $policy = { duration_rule => $duration_rule->name, @@ -1196,10 +1198,18 @@ sub get_circ_policy { max_fine_rule => $max_fine_rule->name, max_fine => $self->get_max_fine_amount($max_fine_rule), fine_interval => $recurring_fine_rule->recurrence_interval, - renewal_remaining => $duration_rule->max_renewals, - duration_date_ceiling => $duration_rule->date_ceiling + renewal_remaining => $duration_rule->max_renewals }; + if($hard_due_date) { + $policy->{duration_date_ceiling} = $hard_due_date->ceiling_date; + $policy->{duration_date_ceiling_force} = $hard_due_date->forceto; + } + else { + $policy->{duration_date_ceiling} = undef; + $policy->{duration_date_ceiling_force} = undef; + } + $policy->{duration} = $duration_rule->shrt if $self->copy->loan_duration == OILS_CIRC_DURATION_SHORT; $policy->{duration} = $duration_rule->normal @@ -1702,15 +1712,18 @@ sub run_checkout_scripts { my $duration; my $recurring; my $max_fine; + my $hard_due_date; my $duration_name; my $recurring_name; my $max_fine_name; + my $hard_due_date_name; if(!$self->legacy_script_support) { $self->run_indb_circ_test(); $duration = $self->circ_matrix_matchpoint->duration_rule; $recurring = $self->circ_matrix_matchpoint->recurring_fine_rule; $max_fine = $self->circ_matrix_matchpoint->max_fine_rule; + $hard_due_date = $self->circ_matrix_matchpoint->hard_due_date; } else { @@ -1722,6 +1735,7 @@ sub run_checkout_scripts { $duration_name = $result->{durationRule}; $recurring_name = $result->{recurringFinesRule}; $max_fine_name = $result->{maxFine}; + $hard_due_date_name = $result->{hardDueDate}; } $duration_name = $duration->name if $duration; @@ -1736,6 +1750,11 @@ sub run_checkout_scripts { ($max_fine, $evt) = $U->fetch_max_fine_by_name($max_fine_name); return $self->bail_on_events($evt) if ($evt && !$nobail); + + if($hard_due_date_name) { + ($hard_due_date, $evt) = $U->fetch_hard_due_date_by_name($hard_due_date_name); + return $self->bail_on_events($evt) if ($evt && !$nobail); + } } } else { @@ -1744,11 +1763,13 @@ sub run_checkout_scripts { $duration = undef; $recurring = undef; $max_fine = undef; + $hard_due_date = undef; } $self->duration_rule($duration); $self->recurring_fines_rule($recurring); $self->max_fine_rule($max_fine); + $self->hard_due_date($hard_due_date); } @@ -1759,21 +1780,28 @@ sub build_checkout_circ_object { my $duration = $self->duration_rule; my $max = $self->max_fine_rule; my $recurring = $self->recurring_fines_rule; + my $hard_due_date = $self->hard_due_date; my $copy = $self->copy; my $patron = $self->patron; my $duration_date_ceiling; + my $duration_date_ceiling_force; if( $duration ) { - my $policy = $self->get_circ_policy($duration, $recurring, $max); + my $policy = $self->get_circ_policy($duration, $recurring, $max, $hard_due_date); $duration_date_ceiling = $policy->{duration_date_ceiling}; + $duration_date_ceiling_force = $policy->{duration_date_ceiling_force}; my $dname = $duration->name; my $mname = $max->name; my $rname = $recurring->name; + my $hdname; + if($hard_due_date) { + $hdname = $hard_due_date->name; + } $logger->debug("circulator: building circulation ". - "with duration=$dname, maxfine=$mname, recurring=$rname"); + "with duration=$dname, maxfine=$mname, recurring=$rname, hard due date=$hdname"); $circ->duration($policy->{duration}); $circ->recurring_fine($policy->{recurring_fine}); @@ -1819,7 +1847,7 @@ sub build_checkout_circ_object { # if a patron is renewing, 'requestor' will be the patron $circ->circ_staff($self->editor->requestor->id); - $circ->due_date( $self->create_due_date($circ->duration, $duration_date_ceiling) ) if $circ->duration; + $circ->due_date( $self->create_due_date($circ->duration, $duration_date_ceiling, $duration_date_ceiling_force) ) if $circ->duration; $self->circ($circ); } @@ -2019,7 +2047,7 @@ sub apply_modified_due_date { sub create_due_date { - my( $self, $duration, $date_ceiling ) = @_; + my( $self, $duration, $date_ceiling, $force_date ) = @_; # if there is a raw time component (e.g. from postgres), # turn it into an interval that interval_to_seconds can parse @@ -2033,7 +2061,7 @@ sub create_due_date { if($date_ceiling) { my $cdate = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($date_ceiling)); - if ($cdate > DateTime->now and $cdate < $due_date) { + if ($cdate > DateTime->now and ($cdate < $due_date or $force_date)) { $logger->info("circulator: overriding due date with date ceiling: $date_ceiling"); $due_date = $cdate; } diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql index bcc7a48f4f..43cc1e6dcd 100644 --- a/Open-ILS/src/sql/Pg/002.schema.config.sql +++ b/Open-ILS/src/sql/Pg/002.schema.config.sql @@ -70,7 +70,7 @@ CREATE TABLE config.upgrade_log ( install_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() ); -INSERT INTO config.upgrade_log (version) VALUES ('0441'); -- gmc +INSERT INTO config.upgrade_log (version) VALUES ('0442'); -- tsbere via miker CREATE TABLE config.bib_source ( id SERIAL PRIMARY KEY, @@ -316,8 +316,7 @@ CREATE TABLE config.rule_circ_duration ( extended INTERVAL NOT NULL, normal INTERVAL NOT NULL, shrt INTERVAL NOT NULL, - max_renewals INT NOT NULL, - date_ceiling TIMESTAMPTZ + max_renewals INT NOT NULL ); COMMENT ON TABLE config.rule_circ_duration IS $$ /* @@ -344,13 +343,47 @@ COMMENT ON TABLE config.rule_circ_duration IS $$ $$; CREATE TABLE config.hard_due_date ( - id SERIAL PRIMARY KEY, - duration_rule INT NOT NULL REFERENCES config.rule_circ_duration (id) - DEFERRABLE INITIALLY DEFERRED, - ceiling_date TIMESTAMPTZ NOT NULL, - active_date TIMESTAMPTZ NOT NULL + id SERIAL PRIMARY KEY, + name TEXT NOT NULL UNIQUE CHECK ( name ~ E'^\\w+$' ), + ceiling_date TIMESTAMPTZ NOT NULL, + forceto BOOL NOT NULL, + owner INT NOT NULL -- REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED, ); +CREATE TABLE config.hard_due_date_values ( + id SERIAL PRIMARY KEY, + hard_due_date INT NOT NULL REFERENCES config.hard_due_date (id) + DEFERRABLE INITIALLY DEFERRED, + ceiling_date TIMESTAMPTZ NOT NULL, + active_date TIMESTAMPTZ NOT NULL +); + + +CREATE OR REPLACE FUNCTION config.update_hard_due_dates () RETURNS INT AS $func$ +DECLARE + temp_value config.hard_due_date_values%ROWTYPE; + updated INT := 0; +BEGIN + FOR temp_value IN + SELECT DISTINCT ON (hard_due_date) * + FROM config.hard_due_date_values + WHERE active_date <= NOW() -- We've passed (or are at) the rollover time + ORDER BY active_date DESC -- Latest (nearest to us) active time + LOOP + UPDATE config.hard_due_date + SET ceiling_date = temp_value.ceiling_date + WHERE id = temp_value.hard_due_date + AND ceiling_date <> temp_value.ceiling_date; -- Time is equal if we've already updated the chdd + + IF FOUND THEN + updated := updated + 1; + END IF; + END LOOP; + + RETURN updated; +END; +$func$ LANGUAGE plpgsql; + CREATE TABLE config.rule_max_fine ( id SERIAL PRIMARY KEY, name TEXT NOT NULL UNIQUE CHECK ( name ~ E'^\\w+$' ), diff --git a/Open-ILS/src/sql/Pg/100.circ_matrix.sql b/Open-ILS/src/sql/Pg/100.circ_matrix.sql index 766d47f07a..64187ec03f 100644 --- a/Open-ILS/src/sql/Pg/100.circ_matrix.sql +++ b/Open-ILS/src/sql/Pg/100.circ_matrix.sql @@ -110,6 +110,7 @@ CREATE TABLE config.circ_matrix_matchpoint ( duration_rule INT NOT NULL REFERENCES config.rule_circ_duration (id) DEFERRABLE INITIALLY DEFERRED, recurring_fine_rule INT NOT NULL REFERENCES config.rule_recurring_fine (id) DEFERRABLE INITIALLY DEFERRED, max_fine_rule INT NOT NULL REFERENCES config.rule_max_fine (id) DEFERRABLE INITIALLY DEFERRED, + hard_due_date INT REFERENCES config.hard_due_date (id) DEFERRABLE INITIALLY DEFERRED, script_test TEXT, -- javascript source total_copy_hold_ratio FLOAT, available_copy_hold_ratio FLOAT, diff --git a/Open-ILS/src/sql/Pg/upgrade/0442.schema.due_date_ceiling_alt_impl.sql b/Open-ILS/src/sql/Pg/upgrade/0442.schema.due_date_ceiling_alt_impl.sql new file mode 100644 index 0000000000..4e01f02a65 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/0442.schema.due_date_ceiling_alt_impl.sql @@ -0,0 +1,53 @@ +BEGIN; + +INSERT INTO config.upgrade_log (version) VALUES ('0442'); -- tsbere via miker + +DROP TABLE config.hard_due_date; + +CREATE TABLE config.hard_due_date ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL UNIQUE CHECK ( name ~ E'^\\w+$' ), + ceiling_date TIMESTAMPTZ NOT NULL, + forceto BOOL NOT NULL, + owner INT NOT NULL +); + +CREATE TABLE config.hard_due_date_values ( + id SERIAL PRIMARY KEY, + hard_due_date INT NOT NULL REFERENCES config.hard_due_date (id) + DEFERRABLE INITIALLY DEFERRED, + ceiling_date TIMESTAMPTZ NOT NULL, + active_date TIMESTAMPTZ NOT NULL +); + +ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN hard_due_date INT REFERENCES config.hard_due_date (id); + +ALTER TABLE config.rule_circ_duration DROP COLUMN date_ceiling; + +CREATE OR REPLACE FUNCTION config.update_hard_due_dates () RETURNS INT AS $func$ +DECLARE + temp_value config.hard_due_date_values%ROWTYPE; + updated INT := 0; +BEGIN + FOR temp_value IN + SELECT DISTINCT ON (hard_due_date) * + FROM config.hard_due_date_values + WHERE active_date <= NOW() -- We've passed (or are at) the rollover time + ORDER BY active_date DESC -- Latest (nearest to us) active time + LOOP + UPDATE config.hard_due_date + SET ceiling_date = temp_value.ceiling_date + WHERE id = temp_value.hard_due_date + AND ceiling_date <> temp_value.ceiling_date; -- Time is equal if we've already updated the chdd + + IF FOUND THEN + updated := updated + 1; + END IF; + END LOOP; + + RETURN updated; +END; +$func$ LANGUAGE plpgsql; + +COMMIT; + diff --git a/Open-ILS/src/support-scripts/update_hard_due_dates.srfsh b/Open-ILS/src/support-scripts/update_hard_due_dates.srfsh new file mode 100755 index 0000000000..f14a24ce53 --- /dev/null +++ b/Open-ILS/src/support-scripts/update_hard_due_dates.srfsh @@ -0,0 +1,7 @@ +#!/openils/bin/srfsh +open open-ils.cstore +request open-ils.cstore open-ils.cstore.transaction.begin +request open-ils.cstore open-ils.cstore.json_query {"from":["config.update_hard_due_dates"]} +request open-ils.cstore open-ils.cstore.transaction.commit +close open-ils.cstore + diff --git a/Open-ILS/web/templates/base.tt2 b/Open-ILS/web/templates/base.tt2 index 4e69f51516..19e905d852 100644 --- a/Open-ILS/web/templates/base.tt2 +++ b/Open-ILS/web/templates/base.tt2 @@ -21,7 +21,7 @@ var djConfig = {parseOnLoad:true,isDebug:false,AutoIDL:[ 'acqligad','acqliuad','acqlipad','acqphsm','acqlilad','acqedi','acqedim','acqdf','acqdfe','acqdfa','acqda','cnal', 'acqclt','acqclet','acqcl','acqcle','acqscl','acqscle','acqclp','acqclpa','acqlisum','acqft','acqftm','actsce','actscecm', 'jub','sdist','ssub','sstr','scap','bre','siss','act', 'acpl', 'ccm', 'aiit', 'atevdef', 'ath', 'atreact','atclean','atenv','atevparam','atcol','actsc','cit', -'atval','crahp','crmf','crrf','crcd','cust','coust','cgf','czs','cbt','csp','brt','brsrc','bra','bram','brav','vaq','vbq','vqar','ccmm','ccmcmtm','citm','cifm','cvrfm','chmm']}; +'atval','crahp','crmf','crrf','crcd','cust','coust','cgf','czs','cbt','csp','brt','brsrc','bra','bram','brav','vaq','vbq','vqar','ccmm','ccmcmtm','citm','cifm','cvrfm','chmm','chdd']}; -- 2.11.0