# -----------------------------------------------------------------
# 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) = @_;
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);
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;
use DateTime::Format::ISO8601;
use OpenILS::Utils::Penalty;
use POSIX qw(ceil);
+use OpenILS::Application::Circ::CircCommon;
sub isTrue {
my $v = shift;
$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);
'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',
,(1, 'cat.label.font.family', '"monospace"')
,(1, 'cat.label.font.size', 10)
,(1, 'cat.label.font.weight', '"normal"')
+ ,(1, 'circ.grace.extend', 'true')
;
--- /dev/null
+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);