From ba5113a62b0c52dc8466e0c569e66bda60171d81 Mon Sep 17 00:00:00 2001 From: Jeff Godin Date: Tue, 6 Dec 2011 13:27:09 -0500 Subject: [PATCH] Support forgive-vs void on Lost/Lost-checkin Constants and strings for new OU settings: circ.forgive_overdue_on_lost circ.forgive_lost_on_checkin circ.forgive_lost_proc_fee_on_checkin These are intended for use as an alternative to their "void" counterparts in situations where the library does not refund fees and fines, and wishes funds taken in for one purpose or billing type to not be re-applied toward a different billing type as a result of voiding bills after they have been paid. When circ.forgive_overdue_on_lost is set, attempt to make a payment of type "Forgive" on the transaction for the amount of outstanding overdue billings. Only outstanding bills of type 1 (Overdue materials) will be paid, and only the first contiguous grouping. If the system finds an outstanding billing of type other than 1, it will pay what it has found up to that point. circ.forgive_overdue_on_lost has priority over the "void" version of the same setting, if both happen to be set. When circ.forgive_lost_on_checkin and/or circ.forgive_lost_proc_fee_on_checkin is set, make a Forgive payment on outstanding Lost and Lost Processing Fee bills on checkin of a lost item. New utility function: outstanding_bills_for_circ (in AppUtils) accepts an editor and a circ object, and will return a reference to an array of outstanding billing objects. this has been broken out of the forgive_overdues sub, as it will be useful elsewhere. Signed-off-by: Jeff Godin Conflicts: Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm Open-ILS/src/perlmods/lib/OpenILS/Application/Cat/AssetCommon.pm Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm --- .../perlmods/lib/OpenILS/Application/AppUtils.pm | 52 +++++++++++++++ .../lib/OpenILS/Application/Cat/AssetCommon.pm | 13 +++- .../lib/OpenILS/Application/Circ/CircCommon.pm | 57 ++++++++++++++++ .../lib/OpenILS/Application/Circ/Circulate.pm | 77 ++++++++++++++++------ Open-ILS/src/perlmods/lib/OpenILS/Const.pm | 3 + Open-ILS/web/opac/locale/en-US/lang.dtd | 6 ++ 6 files changed, 186 insertions(+), 22 deletions(-) diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm index 5a6d38582d..9cc5708c5d 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm @@ -2181,5 +2181,57 @@ sub check_open_xact { 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; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat/AssetCommon.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat/AssetCommon.pm index 1cf8cc8bed..5b7f79dca1 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat/AssetCommon.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat/AssetCommon.pm @@ -706,6 +706,11 @@ sub set_item_lost_or_lod { # 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; @@ -739,8 +744,12 @@ sub set_item_lost_or_lod { $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; } 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 2901ec5ae3..9a20a868ef 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm @@ -17,6 +17,63 @@ my $U = "OpenILS::Application::AppUtils"; # ----------------------------------------------------------------- +# 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 diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm index b39068891d..8a88b93cc3 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm @@ -3453,7 +3453,7 @@ sub checkin_handle_circ { } # ------------------------------------------------------------------ -# 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; @@ -3577,6 +3577,11 @@ sub checkin_handle_lost_or_longoverdue { $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; @@ -3591,7 +3596,7 @@ sub checkin_handle_lost_or_longoverdue { } $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) @@ -3972,33 +3977,65 @@ sub make_trigger_events { 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); + } } } } diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Const.pm b/Open-ILS/src/perlmods/lib/OpenILS/Const.pm index 9bbcdf24a7..3f0827aab7 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Const.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Const.pm @@ -79,6 +79,7 @@ econst OILS_SETTING_LOST_PROCESSING_FEE => 'circ.lost_materials_processing_fee'; econst OILS_SETTING_DEF_ITEM_PRICE => 'cat.default_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'; @@ -86,8 +87,10 @@ econst OILS_SETTING_HOLD_SOFT_BOUNDARY => 'circ.hold_boundary.soft'; 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'; diff --git a/Open-ILS/web/opac/locale/en-US/lang.dtd b/Open-ILS/web/opac/locale/en-US/lang.dtd index c3c6cf020d..45b6c6d4d2 100644 --- a/Open-ILS/web/opac/locale/en-US/lang.dtd +++ b/Open-ILS/web/opac/locale/en-US/lang.dtd @@ -2319,6 +2319,8 @@ + + @@ -2360,10 +2362,14 @@ + + + + -- 2.11.0