return undef;
}
+# -----------------------------------------------------------------
+# Given an editor and a circ, return a reference to an array of
+# billing objects which are outstanding (unpaid, not voided).
+# If a bill is partially paid, change the amount of the bill
+# to reflect the unpaid amount, not the original amount.
+# -----------------------------------------------------------------
+sub outstanding_bills_for_circ {
+ my ($self, $e, $circ) = @_;
+
+ # find all unvoided bills in order
+ my $bill_search = [
+ { xact => $circ->id, voided=>'f' },
+ { order_by => { mb => { billing_ts => { direction => 'asc' } } } },
+ ];
+
+ # find all unvoided payments in order
+ my $payment_search = [
+ { xact => $circ->id, voided=>'f' },
+ { order_by => { mp => { payment_ts => { direction => 'asc' } } } },
+ ];
+
+ my $bills = $e->search_money_billing($bill_search);
+
+ my $payments = $e->search_money_payment($payment_search);
+
+ # "Pay" the bills, removing fully paid bills and
+ # adjusting the amount for partially paid bills
+ map {
+ my $payment = $_;
+ my $paybal = $payment->amount * 100;
+
+ while ($paybal > 0) {
+ # get next billing
+ my $bill = shift @{$bills};
+ my $newbal = $paybal - $bill->amount*100;
+ if ($newbal < 0) {
+ $newbal = 0;
+ my $new_bill_amount = $bill->amount*100 - $paybal;
+ $bill->amount($new_bill_amount/100);
+ unshift(@{$bills}, $bill); # put the partially-paid bill back on top of the stack
+ }
+
+ $paybal = $newbal;
+
+ }
+
+ } @$payments;
+
+ return $bills;
+
+}
+
1;
# fetch the related org settings
my $proc_fee = $U->ou_ancestor_setting_value(
$owning_lib, $args{ous_proc_fee}, $e) || 0;
+ my $forgive_overdue = 0;
+ if (!$args{is_longoverdue}) {
+ $forgive_overdue = $U->ou_ancestor_setting_value(
+ $owning_lib, OILS_SETTING_FORGIVE_OVERDUE_ON_LOST, $e) || 0;
+ };
my $void_overdue = $U->ou_ancestor_setting_value(
$owning_lib, $args{ous_void_od}, $e) || 0;
$e->update_action_circulation($circ) or return $e->die_event;
# ---------------------------------------------------------------------
- # void all overdue fines on this circ if configured
- if( $void_overdue ) {
+ # forgive outstanding overdue fines or void all overdue fines on this circ if configured
+ if( $forgive_overdue ) {
+ my $evt = OpenILS::Application::Circ::CircCommon->forgive_overdues($e, $circ, "System: OVERDUES FORGIVEN ON LOST");
+ return $evt if $evt;
+
+ } elsif( $void_overdue ) {
my $evt = OpenILS::Application::Circ::CircCommon->void_overdues($e, $circ);
return $evt if $evt;
}
# -----------------------------------------------------------------
+# Forgive (don't void) unpaid overdue fines on the given circ.
+# This is different from void_overdues in a few ways:
+# * only deals with 'unpaid' overdue billings
+# * does not accept a backdate argument
+# * only forgives if the first unpaid billing is of type 1,
+# and stops when it gets to a billing type other than 1
+# -----------------------------------------------------------------
+sub forgive_overdues {
+ my ($class, $e, $circ, $note) = @_;
+
+
+ $logger->info("attempting to forgive overdues on circ " . $circ->id . " with note " . $note);
+
+ # get outstanding bills for the circ in question
+ my $bills = $U->outstanding_bills_for_circ($e, $circ);
+
+ # Sum any outstanding overdue billings, stopping at the first non-overdue billing
+
+ my $outstanding_overdues = 0;
+
+ foreach (@$bills) {
+ my $bill = $_;
+ if ($bill->btype == 1) {
+ $logger->debug("forgive_overdues found a btype 1 bill id " . $bill->id . " amount " . $bill->amount);
+ $outstanding_overdues = ($outstanding_overdues*100 + $bill->amount*100)/100;
+ } else {
+ # We found a billing type other than 1 -- Overdue Fines
+ $logger->info("forgive_overdues found a bill id " . $bill->id . " with btype " . $bill->btype);
+ last; # stop looking for bills to forgive
+ }
+
+ }
+
+ $logger->debug("forgive_overdues outstanding balance to forgive is: " . $outstanding_overdues);
+ my $amount = $outstanding_overdues;
+
+ if ($amount >= 0.01) {
+ # pay with forgive payment
+ my $payobj = Fieldmapper::money::forgive_payment->new;
+ $payobj->amount($amount);
+ $payobj->amount_collected($amount);
+ $payobj->xact($circ->id);
+ $payobj->note($note);
+ # do we need an accepting user? who should be the accepting user?
+ $payobj->accepting_usr($e->requestor->id); # or 1?
+
+ $logger->info("forgive_overdues about to create the forgive payment... ");
+ $e->create_money_forgive_payment($payobj) or return $e->die_event;
+
+ return undef;
+ } else {
+ $logger->info("forgive_overdues found no outstanding overdues, or found outstanding billings of another type first. No forgive payment made.");
+ return undef;
+ }
+}
+
+# -----------------------------------------------------------------
# Voids overdue fines on the given circ. if a backdate is
# provided, then we only void back to the backdate, unless the
# backdate is to within the grace period, in which case we void all
}
# ------------------------------------------------------------------
-# See if we need to void billings, etc. for lost checkin
+# See if we need to void or forgive billings, etc. for lost checkin
# ------------------------------------------------------------------
sub checkin_handle_lost {
my $self = shift;
$circ_lib, $args{ous_void_item_cost}, $self->editor) || 0;
my $void_proc_fee = $U->ou_ancestor_setting_value(
$circ_lib, $args{ous_void_proc_fee}, $self->editor) || 0;
+ my $forgive_lost = 0;
+ if (!$args{is_longoverdue}) {
+ $forgive_lost = $U->ou_ancestor_setting_value(
+ $circ_lib, OILS_SETTING_FORGIVE_LOST_ON_CHECKIN, $self->editor) || 0;
+ }
my $restore_od = $U->ou_ancestor_setting_value(
$circ_lib, $args{ous_restore_overdue}, $self->editor) || 0;
}
$self->checkin_handle_lost_or_lo_now_found(
- $args{void_cost_btype}, $args{is_longoverdue}) if $void_cost;
+ $args{void_cost_btype}, $args{is_longoverdue}, $forgive_lost) if $forgive_lost || $void_cost;
$self->checkin_handle_lost_or_lo_now_found(
$args{void_fee_btype}, $args{is_longoverdue}) if $void_proc_fee;
$self->checkin_handle_lost_or_lo_now_found_restore_od($circ_lib)
sub checkin_handle_lost_or_lo_now_found {
- my ($self, $bill_type, $is_longoverdue) = @_;
+ my ($self, $bill_type, $is_longoverdue, $forgive) = @_;
# ------------------------------------------------------------------
# remove charge from patron's account if lost item is returned
# ------------------------------------------------------------------
- my $bills = $self->editor->search_money_billing(
- {
- xact => $self->circ->id,
- btype => $bill_type
+ if ($forgive) {
+ my $bills = $U->outstanding_bills_for_circ($self->editor, $self->circ);
+
+ $logger->debug("forgiving lost item charge of ".scalar(@$bills));
+ my $total_to_forgive = 0;
+ for my $bill (@$bills) {
+ if ($bill->btype == $bill_type) {
+ $logger->info("lost item returned - will make payment to forgive bill ".$bill->id);
+ # add this bill's amount to the total to forgive
+ $total_to_forgive = (($total_to_forgive*100) + ($bill->amount*100)) / 100;
+ } else {
+ last;
+ }
+ }
+ if ($total_to_forgive >= 0.01) {
+ # pay with forgive payment
+ my $payobj = Fieldmapper::money::forgive_payment->new;
+ $payobj->amount($total_to_forgive);
+ $payobj->amount_collected($total_to_forgive);
+ $payobj->xact($self->circ->id);
+ $payobj->note("System: FORGIVEN FOR LOST ITEM RETURNED");
+ $payobj->accepting_usr($self->editor->requestor->id);
+
+ $logger->info("checkin_handle_lost_now_found about to create forgive payment... ");
+ $self->bail_on_events($self->editor->event)
+ unless $self->editor->create_money_forgive_payment($payobj);
+ } else {
+ $logger->info("checkin_handle_lost_now_found found no billings of type $bill_type to forgive.");
}
- );
- my $tag = $is_longoverdue ? "LONGOVERDUE" : "LOST";
+ } else {
+ my $bills = $self->editor->search_money_billing(
+ {
+ xact => $self->circ->id,
+ btype => $bill_type
+ }
+ );
- $logger->debug("voiding ".scalar(@$bills)." $tag item billings");
- for my $bill (@$bills) {
- if( !$U->is_true($bill->voided) ) {
- $logger->info("$tag item returned - voiding bill ".$bill->id);
- $bill->voided('t');
- $bill->void_time('now');
- $bill->voider($self->editor->requestor->id);
- my $note = ($bill->note) ? $bill->note . "\n" : '';
- $bill->note("${note}System: VOIDED FOR $tag ITEM RETURNED");
+ my $tag = $is_longoverdue ? "LONGOVERDUE" : "LOST";
+
+ $logger->debug("voiding ".scalar(@$bills)." $tag item billings");
+ for my $bill (@$bills) {
+ if( !$U->is_true($bill->voided) ) {
+ $logger->info("$tag item returned - voiding bill ".$bill->id);
+ $bill->voided('t');
+ $bill->void_time('now');
+ $bill->voider($self->editor->requestor->id);
+ my $note = ($bill->note) ? $bill->note . "\n" : '';
+ $bill->note("${note}System: VOIDED FOR $tag ITEM RETURNED");
- $self->bail_on_events($self->editor->event)
- unless $self->editor->update_money_billing($bill);
+ $self->bail_on_events($self->editor->event)
+ unless $self->editor->update_money_billing($bill);
+ }
}
}
}
econst OILS_SETTING_MAX_ITEM_PRICE => 'circ.max_item_price';
econst OILS_SETTING_ORG_BOUNCED_EMAIL => 'org.bounced_emails';
econst OILS_SETTING_CHARGE_LOST_ON_ZERO => 'circ.charge_lost_on_zero';
+econst OILS_SETTING_FORGIVE_OVERDUE_ON_LOST => 'circ.forgive_overdue_on_lost';
econst OILS_SETTING_VOID_OVERDUE_ON_LOST => 'circ.void_overdue_on_lost';
econst OILS_SETTING_HOLD_SOFT_STALL => 'circ.hold_stalling.soft';
econst OILS_SETTING_HOLD_HARD_STALL => 'circ.hold_stalling.hard';
econst OILS_SETTING_HOLD_HARD_BOUNDARY => 'circ.hold_boundary.hard';
econst OILS_SETTING_HOLD_EXPIRE => 'circ.hold_expire_interval';
econst OILS_SETTING_HOLD_ESIMATE_WAIT_INTERVAL => 'circ.holds.default_estimated_wait_interval';
+econst OILS_SETTING_FORGIVE_LOST_ON_CHECKIN => 'circ.forgive_lost_on_checkin';
econst OILS_SETTING_VOID_LOST_ON_CHECKIN => 'circ.void_lost_on_checkin';
econst OILS_SETTING_MAX_ACCEPT_RETURN_OF_LOST => 'circ.max_accept_return_of_lost';
+econst OILS_SETTING_FORGIVE_LOST_PROCESS_FEE_ON_CHECKIN => 'circ.forgive_lost_proc_fee_on_checkin';
econst OILS_SETTING_VOID_LOST_PROCESS_FEE_ON_CHECKIN => 'circ.void_lost_proc_fee_on_checkin';
econst OILS_SETTING_RESTORE_OVERDUE_ON_LOST_RETURN => 'circ.restore_overdue_on_lost_return';
econst OILS_SETTING_LOST_IMMEDIATELY_AVAILABLE => 'circ.lost_immediately_available';
<!ENTITY staff.server.admin.org_settings.global.credit.allow "Allow Credit Card Payments">
<!ENTITY staff.server.admin.org_settings.global.credit.allow.desc "If enabled, patrons will be able to pay fines accrued at this location via credit card">
<!ENTITY staff.server.admin.org_settings.global.default_locale "Default Locale">
+<!ENTITY staff.server.admin.org_settings.circ.forgive_overdue_on_lost "Forgive overdue fines when items are marked lost">
+<!ENTITY staff.server.admin.org_settings.circ.forgive_overdue_on_lost.desc "Forgive overdue fines when items are marked lost -- makes a Forgive payment">
<!ENTITY staff.server.admin.org_settings.circ.void_overdue_on_lost "Void overdue fines when items are marked lost">
<!ENTITY staff.server.admin.org_settings.circ.hold_stalling.soft 'Holds: Soft stalling interval'>
<!ENTITY staff.server.admin.org_settings.circ.hold_stalling.soft.desc 'How long to wait before allowing remote items to be opportunisticaly captured for a hold. Example "5 days"'>
<!ENTITY staff.server.admin.org_settings.circ.void_item_billing_on_lost_return_before_interval "Void lost item fine when returned before interval">
<!ENTITY staff.server.admin.org_settings.circ.void_item_billing_on_lost_return_before_interval.desc "Void lost item fine when returned before interval">
+<!ENTITY staff.server.admin.org_settings.circ.forgive_lost_on_checkin "Circ: Forgive lost item billing when returned">
+<!ENTITY staff.server.admin.org_settings.circ.forgive_lost_on_checkin.desc "Forgive lost item billing when returned -- makes a Forgive payment">
<!ENTITY staff.server.admin.org_settings.circ.void_lost_on_checkin "Circ: Void lost item billing when returned">
<!ENTITY staff.server.admin.org_settings.circ.void_lost_on_checkin.desc "Void lost item billing when returned">
<!ENTITY staff.server.admin.org_settings.circ.max_accept_return_of_lost "Circ: Void lost max interval">
<!ENTITY staff.server.admin.org_settings.circ.max_accept_return_of_lost.desc "Items that have been lost this long will not result in voided billings when returned. E.g. \'6 months\'">
+<!ENTITY staff.server.admin.org_settings.circ.forgive_lost_proc_fee_on_checkin "Circ: Forgive processing fee on lost item return">
+<!ENTITY staff.server.admin.org_settings.circ.forgive_lost_proc_fee_on_checkin.desc "Forgive processing fee when lost item returned -- makes a Forgive payment">
<!ENTITY staff.server.admin.org_settings.circ.void_lost_proc_fee_on_checkin "Circ: Void processing fee on lost item return">
<!ENTITY staff.server.admin.org_settings.circ.void_lost_proc_fee_on_checkin.desc "Void processing fee when lost item returned">
<!ENTITY staff.server.admin.org_settings.circ.restore_overdue_on_lost_return "Circ: Restore overdues on lost item return">