From e4bb24703fe6a73a649691761b2df308dd21bbbf Mon Sep 17 00:00:00 2001 From: Thomas Berezansky Date: Thu, 15 Sep 2011 12:10:12 -0400 Subject: [PATCH] Grace period auto extension and backdate awareness Grace period auto extension: Default is "grace periods don't auto extend". OU setting turns on grace period auto extension. By default they only do so when the grace period ends on a closed date, but there are two modifiers to change that. The first modifier causes grace periods to extend for all closed dates that they intersect. This is "grace periods are only consumed by open days." The second modifier causes a grace period that ends just before a closed day, with or without extension having happened, to include the closed day (and any following it) as well. This is mainly so that a backdate into the closed period following the grace period will assume the "best case" of the item having been returned after hours on the last day of the closed date. Backdate grace period awareness: By moving grace period calculations into a shared location backdating can check the grace period, including extensions. If it finds that the backdate is within the grace period (before or after extension) then instead of voiding the fines for after the backdate it instead voids all fines for the circulation. This emulates what would have happened (no fines) if the item had been checked in at the time the backdate was made to take effect. Signed-off-by: Thomas Berezansky Signed-off-by: Mike Rylander --- .../OpenILS/Application/Circ/CircCommon.pm | 111 +++++++++++++++++- .../Application/Storage/Publisher/action.pm | 34 +----- Open-ILS/src/sql/Pg/950.data.seed-values.sql | 28 +++++ .../upgrade/XXXX.data.grace_period_extend.sql | 28 +++++ 4 files changed, 165 insertions(+), 36 deletions(-) create mode 100644 Open-ILS/src/sql/Pg/upgrade/XXXX.data.grace_period_extend.sql diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm index 1319201554..9daa10cc5d 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm @@ -18,7 +18,9 @@ my $U = "OpenILS::Application::AppUtils"; # ----------------------------------------------------------------- # Voids overdue fines on the given circ. if a backdate is -# provided, then we only void back to the backdate +# provided, then we only void back to the backdate, unless the +# backdate is to within the grace period, in which case we void all +# overdue fines. # ----------------------------------------------------------------- sub void_overdues { my($class, $e, $circ, $backdate, $note) = @_; @@ -43,9 +45,15 @@ sub void_overdues { my $interval = OpenSRF::Utils->interval_to_seconds($duration); my $date = DateTime::Format::ISO8601->parse_datetime($backdate); - $backdate = $U->epoch2ISO8601($date->epoch + $interval); - $logger->info("applying backdate $backdate in overdue voiding"); - $$bill_search{billing_ts} = {'>=' => $backdate}; + my $due_date = DateTime::Format::ISO8601->parse_datetime(cleanse_ISO8601($circ->due_date))->epoch; + my $grace_period = extend_grace_period( $class, $circ->circ_lib, $circ->due_date, OpenSRF::Utils->interval_to_seconds($circ->grace_period), $e); + if($date->epoch < $due_date + $grace_period) { + $logger->info("backdate $backdate is within grace period, voiding all"); + } else { + $backdate = $U->epoch2ISO8601($date->epoch + $interval); + $logger->info("applying backdate $backdate in overdue voiding"); + $$bill_search{billing_ts} = {'>=' => $backdate}; + } } my $bills = $e->search_money_billing($bill_search); @@ -106,4 +114,99 @@ sub create_bill { return undef; } +sub extend_grace_period { + my($class, $circ_lib, $due_date, $grace_period, $e, $h) = @_; + if ($grace_period >= 86400) { # Only extend grace periods greater than or equal to a full day + my $parser = DateTime::Format::ISO8601->new; + my $due_dt = $parser->parse_datetime( cleanse_ISO8601( $due_date ) ); + my $due = $due_dt->epoch; + + my $grace_extend = $U->ou_ancestor_setting_value($circ_lib, 'circ.grace.extend'); + $e = new_editor() if (!$e); + $h = $e->retrieve_actor_org_unit_hours_of_operation($circ_lib) if (!$h); + if ($grace_extend and $h) { + my $new_grace_period = $grace_period; + + $logger->info( "Circ lib has an hours-of-operation entry and grace period extension is enabled." ); + + my $closed = 0; + my %h_closed = {}; + for my $i (0 .. 6) { + my $dow_open = "dow_${i}_open"; + my $dow_close = "dow_${i}_close"; + if($h->$dow_open() eq '00:00:00' and $h->$dow_close() eq '00:00:00') { + $closed++; + $h_closed{$i} = 1; + } else { + $h_closed{$i} = 0; + } + } + + if($closed == 7) { + $logger->info("Circ lib is closed all week according to hours-of-operation entry. Skipping grace period extension checks."); + } else { + # Extra nice grace periods + # AKA, merge closed dates trailing the grace period into the grace period + my $grace_extend_into_closed = $U->ou_ancestor_setting_value($circ_lib, 'circ.grace.extend.into_closed'); + $due += 86400 if $grace_extend_into_closed; + + my $grace_extend_all = $U->ou_ancestor_setting_value($circ_lib, 'circ.grace.extend.all'); + + if ( $grace_extend_all ) { + # Start checking the day after the item was due + # This is "The grace period only counts open days" + # NOTE: Adding 86400 seconds is not the same as adding one day. This uses seconds intentionally. + $due_dt = $due_dt->add( seconds => 86400 ); + } else { + # Jump to the end of the grace period + # This is "If the grace period ends on a closed day extend it" + # NOTE: This adds grace period as a number of seconds intentionally + $due_dt = $due_dt->add( seconds => $grace_period ); + } + + my $count = 0; # Infinite loop protection + do { + $closed = 0; # Starting assumption for day: We are not closed + $count++; # We limit the number of loops below. + + # get the day of the week for the day we are looking at + my $dow = $due_dt->day_of_week_0; + + # Check hours of operation first. + if ($h_closed{$dow}) { + $closed = 1; + $new_grace_period += 86400; + $due_dt->add( seconds => 86400 ); + } else { + # Check for closed dates for this period + my $timestamptz = $due_dt->strftime('%FT%T%z'); + my $cl = $e->search_actor_org_unit_closed_date( + { close_start => { '<=' => $timestamptz }, + close_end => { '>=' => $timestamptz }, + org_unit => $circ_lib } + ); + if ($cl and @$cl) { + $closed = 1; + foreach (@$cl) { + my $cl_dt = $parser->parse_datetime( cleanse_ISO8601( $_->close_end ) ); + while ($due_dt <= $cl_dt) { + $due_dt->add( seconds => 86400 ); + $new_grace_period += 86400; + } + } + } else { + $due_dt->add( seconds => 86400 ); + } + } + } while ( $count <= 366 and ( $closed or $due_dt->epoch <= $due + $new_grace_period ) ); + if ($new_grace_period > $grace_period) { + $grace_period = $new_grace_period; + $logger->info( "Grace period for circ extended to $grace_period [" . seconds_to_interval( $grace_period ) . "]" ); + } + } + } + } + return $grace_period; +} + 1; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm index 6a2b6d6d4d..eff589c35b 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm @@ -13,6 +13,7 @@ use DateTime; use DateTime::Format::ISO8601; use OpenILS::Utils::Penalty; use POSIX qw(ceil); +use OpenILS::Application::Circ::CircCommon; sub isTrue { my $v = shift; @@ -886,38 +887,7 @@ sub generate_fines { $log->info( "Potential first billing for circ ".$c->id ); $last_fine = $due; - if (0) { - if (my $h = $hoo{$c->$circ_lib_method}) { - - $log->info( "Circ lib has an hours-of-operation entry" ); - # find the day after the due date... - $due_dt = $due_dt->add( days => 1 ); - - # get the day of the week for that day... - my $dow = $due_dt->day_of_week_0; - my $dow_open = "dow_${dow}_open"; - my $dow_close = "dow_${dow}_close"; - - my $count = 0; - while ( $h->$dow_open eq '00:00:00' and $h->$dow_close eq '00:00:00' ) { - # if the circ lib is closed, add a day to the grace period... - - $grace_period+=86400; - $log->info( "Grace period for circ ".$c->id." extended to $grace_period [" . seconds_to_interval( $grace_period ) . "]" ); - $log->info( "Day of week $dow open $dow_open, close $dow_close" ); - - $due_dt = $due_dt->add( days => 1 ); - $dow = $due_dt->day_of_week_0; - $dow_open = "dow_${dow}_open"; - $dow_close = "dow_${dow}_close"; - - $count++; - - # and check for up to a week - last if ($count > 6); - } - } - } + $grace_period = OpenILS::Application::Circ::CircCommon->extend_grace_period($c->$circ_lib_method->to_fieldmapper->id,$c->$due_date_method,$grace_period,undef,$hoo{$c->$circ_lib_method}); } next if ($last_fine > $now); 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 09466eb983..7f0263b400 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -2724,6 +2724,33 @@ INSERT into config.org_unit_setting_type 'coust', 'description'), 'bool', null) +,( 'circ.grace.extend', 'circ', + oils_i18n_gettext('circ.grace.extend', + 'Auto-Extend Grace Periods', + 'coust', 'label'), + oils_i18n_gettext('circ.grace.extend', + 'When enabled grace periods will auto-extend. By default this will be only when they are a full day or more and end on a closed date, though other options can alter this.', + 'coust', 'description'), + 'bool', null) + +,( 'circ.grace.extend.all', 'circ', + oils_i18n_gettext('circ.grace.extend.all', + 'Auto-Extending Grace Periods extend for all closed dates', + 'coust', 'label'), + oils_i18n_gettext('circ.grace.extend.all', + 'If enabled and Grace Periods auto-extending is turned on grace periods will extend past all closed dates they intersect, within hard-coded limits. This basically becomes "grace periods can only be consumed by closed dates".', + 'coust', 'description'), + 'bool', null) + +,( 'circ.grace.extend.into_closed', 'circ', + oils_i18n_gettext('circ.grace.extend.into_closed', + 'Auto-Extending Grace Periods include trailing closed dates', + 'coust', 'label'), + oils_i18n_gettext('circ.grace.extend.into_closed', + 'If enabled and Grace Periods auto-extending is turned on grace periods will include closed dates that directly follow the last day of the grace period, to allow a backdate into the closed dates to assume "returned after hours on the last day of the grace period, and thus still within it" automatically.', + 'coust', 'description'), + 'bool', null) + ,( 'circ.hold_boundary.hard', 'holds', oils_i18n_gettext('circ.hold_boundary.hard', 'Hard boundary', @@ -4475,6 +4502,7 @@ INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES ( ,(1, 'cat.label.font.family', '"monospace"') ,(1, 'cat.label.font.size', 10) ,(1, 'cat.label.font.weight', '"normal"') + ,(1, 'circ.grace.extend', 'true') ; diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.grace_period_extend.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.grace_period_extend.sql new file mode 100644 index 0000000000..84ed345873 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.grace_period_extend.sql @@ -0,0 +1,28 @@ +INSERT INTO config.org_unit_setting_type(name, grp, label, description, datatype) VALUES + +( 'circ.grace.extend', 'circ', + oils_i18n_gettext('circ.grace.extend', + 'Auto-Extend Grace Periods', + 'coust', 'label'), + oils_i18n_gettext('circ.grace.extend', + 'When enabled grace periods will auto-extend. By default this will be only when they are a full day or more and end on a closed date, though other options can alter this.', + 'coust', 'description'), + 'bool', null) + +,( 'circ.grace.extend.all', 'circ', + oils_i18n_gettext('circ.grace.extend.all', + 'Auto-Extending Grace Periods extend for all closed dates', + 'coust', 'label'), + oils_i18n_gettext('circ.grace.extend.all', + 'If enabled and Grace Periods auto-extending is turned on grace periods will extend past all closed dates they intersect, within hard-coded limits. This basically becomes "grace periods can only be consumed by closed dates".', + 'coust', 'description'), + 'bool', null) + +,( 'circ.grace.extend.into_closed', 'circ', + oils_i18n_gettext('circ.grace.extend.into_closed', + 'Auto-Extending Grace Periods include trailing closed dates', + 'coust', 'label'), + oils_i18n_gettext('circ.grace.extend.into_closed', + 'If enabled and Grace Periods auto-extending is turned on grace periods will include closed dates that directly follow the last day of the grace period, to allow a backdate into the closed dates to assume "returned after hours on the last day of the grace period, and thus still within it" automatically.', + 'coust', 'description'), + 'bool', null); -- 2.43.2