[open-ils-commits] [GIT] Evergreen ILS branch master updated. ea55537955c4f632615f9104d53ed76f4a6a0263

Evergreen Git git at git.evergreen-ils.org
Tue Jul 28 16:32:40 EDT 2015


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Evergreen ILS".

The branch, master has been updated
       via  ea55537955c4f632615f9104d53ed76f4a6a0263 (commit)
       via  d5ee86b2e17b04aa9797e618d5de9358f8b1bdf4 (commit)
       via  cde1573c3273d4b180a319387700596f7c526407 (commit)
       via  14836cc346774182dc187a24356c392c1cd942fa (commit)
       via  336f8a60f9e46a8b4956c2b46694cc93da1b11d9 (commit)
       via  7c8e241b3c169536ab25485988c0efabde420782 (commit)
       via  75b8b2b0a5690e6362fb51970ae3b0f0e8910b8f (commit)
       via  57b4f77ff2cecee2b40d59caa98069a486cc4814 (commit)
       via  b933439a93e3465fc36a685d261e97a44d8cef7b (commit)
       via  8bd2ba53bf0ba7fb4de2a6c84cebdce6b91c0841 (commit)
       via  7d8686135e65942b3a474e9bff6925f026a2023b (commit)
       via  b6d4f96caa22923f84124e825427407489e821a0 (commit)
       via  f89b707fe016b978ef0de0b4616237d2574a42b6 (commit)
       via  f9f9ce0107ef1023f201acc4c89d77dc642ce524 (commit)
       via  027837553e3cfdf179ce63a9adaff5fb3eb29991 (commit)
       via  f8af9fc2759b2c01e038836c9934663e238ee884 (commit)
       via  9d6ed22ec04bd900990c60e853f8f45197feb6d0 (commit)
       via  48fce9688dd1cd67ca083c3ec985f86c144d13f8 (commit)
      from  001bb572c81e3f5930ea09d95e014a8cbf3a9ed4 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit ea55537955c4f632615f9104d53ed76f4a6a0263
Author: Ben Shum <bshum at biblio.org>
Date:   Tue Jul 28 16:28:45 2015 -0400

    LP#1198465: Stamping upgrade script for conditional negative balance
    
    Signed-off-by: Ben Shum <bshum at biblio.org>

diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql
index fe2ceeb..d08026e 100644
--- a/Open-ILS/src/sql/Pg/002.schema.config.sql
+++ b/Open-ILS/src/sql/Pg/002.schema.config.sql
@@ -91,7 +91,7 @@ CREATE TRIGGER no_overlapping_deps
     BEFORE INSERT OR UPDATE ON config.db_patch_dependencies
     FOR EACH ROW EXECUTE PROCEDURE evergreen.array_overlap_check ('deprecates');
 
-INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0920', :eg_version); -- miker/klussier
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0921', :eg_version); -- dyrcona/dbwells/remingtron/kmlussier/bshum
 
 CREATE TABLE config.bib_source (
 	id		SERIAL	PRIMARY KEY,
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.conditional_negative_balance.sql b/Open-ILS/src/sql/Pg/upgrade/0921.data.conditional_negative_balance.sql
similarity index 98%
rename from Open-ILS/src/sql/Pg/upgrade/XXXX.data.conditional_negative_balance.sql
rename to Open-ILS/src/sql/Pg/upgrade/0921.data.conditional_negative_balance.sql
index 0bf8ec1..8085a89 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.conditional_negative_balance.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/0921.data.conditional_negative_balance.sql
@@ -1,6 +1,6 @@
 BEGIN;
 
--- SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+SELECT evergreen.upgrade_deps_block_check('0921', :eg_version);
 
 CREATE TABLE money.account_adjustment (
     billing BIGINT REFERENCES money.billing (id) ON DELETE SET NULL

commit d5ee86b2e17b04aa9797e618d5de9358f8b1bdf4
Author: Kathy Lussier <klussier at masslnc.org>
Date:   Thu Jul 23 20:53:33 2015 -0400

    LP 1198465: Adapt some language in the negative balance branch
    
    End users may see the term 'adjustment payment' and think that an actual
    payment was made. Let's use 'account adjustment' instead. Also, remove any
    references to credits in the description for the OU settings since it could
    be confused with patron credits, which aren't prohibited by the code.
    
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Ben Shum <bshum at biblio.org>

diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml
index cd9d697..f5f25d4 100644
--- a/Open-ILS/examples/fm_IDL.xml
+++ b/Open-ILS/examples/fm_IDL.xml
@@ -81,7 +81,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 			<field name="work_payment" oils_persist:virtual="true" />
 			<field name="credit_payment" oils_persist:virtual="true" />
 			<field name="goods_payment" oils_persist:virtual="true" />
-			<field name="adjustment_payment" oils_persist:virtual="true" />
+			<field name="account_adjustment" oils_persist:virtual="true" />
 		</fields>
 		<links>
 			<link field="usr" reltype="has_a" key="id" map="" class="au"/>
@@ -3622,7 +3622,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 			</actions>
 		</permacrud>
 	</class>
-	<class id="map" controller="open-ils.cstore" oils_obj:fieldmapper="money::adjustment_payment" oils_persist:tablename="money.adjustment_payment" reporter:label="Adjustment Payment">
+	<class id="map" controller="open-ils.cstore" oils_obj:fieldmapper="money::account_adjustment" oils_persist:tablename="money.account_adjustment" reporter:label="Account Adjustment">
 		<fields oils_persist:primary="id" oils_persist:sequence="money.payment_id_seq">
 			<field name="accepting_usr" reporter:datatype="link"/>
 			<field name="amount" reporter:datatype="money" />
@@ -6949,7 +6949,7 @@ SELECT  usr,
 			<field reporter:label="Work Payment Detail" name="work_payment" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Forgive Payment Detail" name="forgive_payment" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Goods Payment Detail" name="goods_payment" oils_persist:virtual="true" reporter:datatype="link"/>
-			<field reporter:label="Adjustment Payment Detail" name="adjustment_payment" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Account Adjustment Detail" name="account_adjustment" oils_persist:virtual="true" reporter:datatype="link"/>
 		</fields>
 		<links>
 			<link field="cash_payment" reltype="might_have" key="id" map="" class="mcp"/>
@@ -6959,7 +6959,7 @@ SELECT  usr,
 			<link field="work_payment" reltype="might_have" key="id" map="" class="mwp"/>
 			<link field="forgive_payment" reltype="might_have" key="id" map="" class="mfp"/>
 			<link field="goods_payment" reltype="might_have" key="id" map="" class="mgp"/>
-			<link field="adjustment_payment" reltype="might_have" key="id" map="" class="map"/>
+			<link field="account_adjustment" reltype="might_have" key="id" map="" class="map"/>
 			<link field="xact" reltype="has_a" key="id" map="" class="mbt"/>
 		</links>
         <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
@@ -6987,7 +6987,7 @@ SELECT  usr,
 			<field reporter:label="Work Payment Detail" name="work_payment" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Forgive Payment Detail" name="forgive_payment" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Goods Payment Detail" name="goods_payment" oils_persist:virtual="true" reporter:datatype="link"/>
-			<field reporter:label="Adjustment Payment Detail" name="adjustment_payment" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Account Adjustment Detail" name="account_adjustment" oils_persist:virtual="true" reporter:datatype="link"/>
 		</fields>
 		<links>
 			<link field="cash_payment" reltype="might_have" key="id" map="" class="mcp"/>
@@ -6997,7 +6997,7 @@ SELECT  usr,
 			<link field="work_payment" reltype="might_have" key="id" map="" class="mwp"/>
 			<link field="forgive_payment" reltype="might_have" key="id" map="" class="mfp"/>
 			<link field="goods_payment" reltype="might_have" key="id" map="" class="mgp"/>
-			<link field="adjustment_payment" reltype="might_have" key="id" map="" class="mvp"/>
+			<link field="account_adjustment" reltype="might_have" key="id" map="" class="mvp"/>
 			<link field="xact" reltype="has_a" key="id" map="" class="mbt"/>
 			<link field="accepting_usr" reltype="has_a" key="id" map="" class="au"/>
 		</links>
@@ -7015,14 +7015,14 @@ SELECT  usr,
 			<field reporter:label="Forgive Payment Detail" name="forgive_payment" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Goods Payment Detail" name="goods_payment" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Credit Payment Detail" name="credit_payment" oils_persist:virtual="true" reporter:datatype="link"/>
-			<field reporter:label="Adjustment Payment Detail" name="adjustment_payment" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Account Adjustment Detail" name="account_adjustment" oils_persist:virtual="true" reporter:datatype="link"/>
 		</fields>
 		<links>
 			<link field="work_payment" reltype="might_have" key="id" map="" class="mwp"/>
 			<link field="forgive_payment" reltype="might_have" key="id" map="" class="mfp"/>
 			<link field="goods_payment" reltype="might_have" key="id" map="" class="mgp"/>
 			<link field="credit_payment" reltype="might_have" key="id" map="" class="mcrp"/>
-			<link field="adjustment_payment" reltype="might_have" key="id" map="" class="mvp"/>
+			<link field="account_adjustment" reltype="might_have" key="id" map="" class="mvp"/>
 			<link field="xact" reltype="has_a" key="id" map="" class="mbt"/>
 		</links>
 	</class>
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 572f5d9..113e47c 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm
@@ -680,11 +680,11 @@ sub generate_fines {
 # following fields:
 #
 # bill => the adjusted bill object
-# adjustments => an arrayref of adjustment payments that apply directly
+# adjustments => an arrayref of account adjustments that apply directly
 #                to the bill
 # payments => an arrayref of payment objects applied to the bill
 # bill_amount => original amount from the billing object
-# adjustment_amount => total of the adjustment payments that apply
+# adjustment_amount => total of the account adjustments that apply
 #                      directly to the bill
 #
 # Each bill is only mapped to payments one time.  However, a single
@@ -720,7 +720,7 @@ sub bill_payment_map_for_xact {
     # the fieldmapper, only the mp object, based on the money.payment
     # view, does.  However, I want to leave that complication for
     # later.  I wonder if I'm not slowing things down too much with
-    # the current adjustment_payment logic.  It would probably be faster if
+    # the current account_adjustment logic.  It would probably be faster if
     # we had direct Pg access at this layer.  I can probably wrangle
     # something via the drivers or store interfaces, but I haven't
     # really figured those out, yet.
@@ -741,7 +741,7 @@ sub bill_payment_map_for_xact {
         }
     } @$bills;
 
-    # Find all unvoided payments in order.  Flesh adjustment payments
+    # Find all unvoided payments in order.  Flesh account adjustments
     # so that we don't have to retrieve them later.
     my $payments = $e->search_money_payment(
         [
@@ -749,7 +749,7 @@ sub bill_payment_map_for_xact {
             {
                 order_by => { mp => { payment_ts => { direction => 'asc' } } },
                 flesh => 1,
-                flesh_fields => { mp => ['adjustment_payment'] }
+                flesh_fields => { mp => ['account_adjustment'] }
             }
         ]
     );
@@ -764,7 +764,7 @@ sub bill_payment_map_for_xact {
     foreach my $entry (@entries) {
         my $bill = $entry->{bill};
         # Find only the adjustments that apply to individual bills.
-        my @adjustments = map {$_->adjustment_payment()} grep {$_->payment_type() eq 'adjustment_payment' && $_->adjustment_payment()->billing() == $bill->id()} @$payments;
+        my @adjustments = map {$_->account_adjustment()} grep {$_->payment_type() eq 'account_adjustment' && $_->account_adjustment()->billing() == $bill->id()} @$payments;
         if (@adjustments) {
             foreach my $adjustment (@adjustments) {
                 my $new_amount = $U->fpdiff($bill->amount(),$adjustment->amount());
@@ -982,8 +982,8 @@ sub adjust_bills_to_zero {
                 $amount_to_adjust = $xact_total;
             }
 
-            # Create the adjustment payment
-            my $payobj = Fieldmapper::money::adjustment_payment->new;
+            # Create the account adjustment
+            my $payobj = Fieldmapper::money::account_adjustment->new;
             $payobj->amount($amount_to_adjust);
             $payobj->amount_collected($amount_to_adjust);
             $payobj->xact($xactid);
@@ -991,7 +991,7 @@ sub adjust_bills_to_zero {
             $payobj->payment_ts('now');
             $payobj->billing($bill->id());
             $payobj->note($note) if ($note);
-            $e->create_money_adjustment_payment($payobj) or return $e->die_event;
+            $e->create_money_account_adjustment($payobj) or return $e->die_event;
             # Adjust our bill_payment_map
             $bpentry->{adjustment_amount} += $amount_to_adjust;
             push @{$bpentry->{adjustments}}, $payobj;
@@ -1031,7 +1031,7 @@ sub _has_refundable_payments {
     my $last_payment = $e->search_money_payment(
         {
             xact => $xactid,
-            payment_type => {"!=" => 'adjustment_payment'}
+            payment_type => {"!=" => 'account_adjustment'}
         },{
             limit => 1,
             order_by => { mp => "payment_ts DESC" }
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 9b20a97..61ef659 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm
@@ -3834,7 +3834,7 @@ sub checkin_handle_lost_or_lo_now_found_restore_od {
         my $void_max = $self->circ->max_fine();
         # search for overdues voided the new way (aka "adjusted")
         my @billings = map {$_->id()} @$ods;
-        my $voids = $self->editor->search_money_adjustment_payment(
+        my $voids = $self->editor->search_money_account_adjustment(
             {
                 billing => \@billings
             }
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/money.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/money.pm
index 21b986f..c3e65fc 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/money.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/money.pm
@@ -144,9 +144,9 @@ __PACKAGE__->table('money_credit_payment');
 
 #-------------------------------------------------------------------------------
 
-package money::adjustment_payment;
+package money::account_adjustment;
 use base qw/money/;
-__PACKAGE__->table('money_adjustment_payment');
+__PACKAGE__->table('money_account_adjustment');
 __PACKAGE__->columns(Primary => 'id');
 __PACKAGE__->columns(Essential => qw/xact amount payment_ts note accepting_usr
                                      amount_collected voided billing/);
diff --git a/Open-ILS/src/sql/Pg/080.schema.money.sql b/Open-ILS/src/sql/Pg/080.schema.money.sql
index 2becfb5..41c7c86 100644
--- a/Open-ILS/src/sql/Pg/080.schema.money.sql
+++ b/Open-ILS/src/sql/Pg/080.schema.money.sql
@@ -540,19 +540,19 @@ CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.forgive_payment FOR EAC
 CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.forgive_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_update ('forgive_payment');
 CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.forgive_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_del ('forgive_payment');
 
-CREATE TABLE money.adjustment_payment (
+CREATE TABLE money.account_adjustment (
     billing BIGINT REFERENCES money.billing (id) ON DELETE SET NULL
 ) INHERITS (money.bnm_payment);
-ALTER TABLE money.adjustment_payment ADD PRIMARY KEY (id);
-CREATE INDEX money_adjustment_id_idx ON money.adjustment_payment (id);
-CREATE INDEX money_adjustment_payment_xact_idx ON money.adjustment_payment (xact);
-CREATE INDEX money_adjustment_payment_bill_idx ON money.adjustment_payment (billing);
-CREATE INDEX money_adjustment_payment_payment_ts_idx ON money.adjustment_payment (payment_ts);
-CREATE INDEX money_adjustment_payment_accepting_usr_idx ON money.adjustment_payment (accepting_usr);
-
-CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.adjustment_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_add ('adjustment_payment');
-CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.adjustment_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_update ('adjustment_payment');
-CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.adjustment_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_del ('adjustment_payment');
+ALTER TABLE money.account_adjustment ADD PRIMARY KEY (id);
+CREATE INDEX money_adjustment_id_idx ON money.account_adjustment (id);
+CREATE INDEX money_account_adjustment_xact_idx ON money.account_adjustment (xact);
+CREATE INDEX money_account_adjustment_bill_idx ON money.account_adjustment (billing);
+CREATE INDEX money_account_adjustment_payment_ts_idx ON money.account_adjustment (payment_ts);
+CREATE INDEX money_account_adjustment_accepting_usr_idx ON money.account_adjustment (accepting_usr);
+
+CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.account_adjustment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_add ('account_adjustment');
+CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.account_adjustment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_update ('account_adjustment');
+CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.account_adjustment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_del ('account_adjustment');
 
 
 CREATE TABLE money.work_payment () INHERITS (money.bnm_payment);
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 97b9537..0ce75a4 100644
--- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql
+++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
@@ -5014,7 +5014,7 @@ INSERT into config.org_unit_setting_type
         'coust', 'label'),
     oils_i18n_gettext(
         'bill.prohibit_negative_balance_default',
-        'Default setting to prevent credits on circulation related bills',
+        'Default setting to prevent negative balances (refunds) on circulation related bills',
         'coust', 'description'),
     'bool', null)
 ,(  'bill.prohibit_negative_balance_on_overdues', 'finance',
@@ -5024,7 +5024,7 @@ INSERT into config.org_unit_setting_type
         'coust', 'label'),
     oils_i18n_gettext(
         'bill.prohibit_negative_balance_on_overdues',
-        'Prevent credits on bills for overdue materials',
+        'Prevent negative balances (refunds) on bills for overdue materials',
         'coust', 'description'),
     'bool', null)
 ,(  'bill.prohibit_negative_balance_on_lost', 'finance',
@@ -5034,7 +5034,7 @@ INSERT into config.org_unit_setting_type
         'coust', 'label'),
     oils_i18n_gettext(
         'bill.prohibit_negative_balance_on_lost',
-        'Prevent credits on bills for lost/long overdue materials',
+        'Prevent negative balances (refunds) on bills for lost/long overdue materials',
         'coust', 'description'),
     'bool', null)
 ,(  'bill.negative_balance_interval_default', 'finance',
@@ -5044,7 +5044,7 @@ INSERT into config.org_unit_setting_type
         'coust', 'label'),
     oils_i18n_gettext(
         'bill.negative_balance_interval_default',
-        'Amount of time after which no negative balances or credits are allowed on circulation bills',
+        'Amount of time after which no negative balances (refunds) are allowed on circulation bills',
         'coust', 'description'),
     'interval', null)
 ,(  'bill.negative_balance_interval_on_overdues', 'finance',
@@ -5054,7 +5054,7 @@ INSERT into config.org_unit_setting_type
         'coust', 'label'),
     oils_i18n_gettext(
         'bill.negative_balance_interval_on_overdues',
-        'Amount of time after which no negative balances or credits are allowed on bills for overdue materials',
+        'Amount of time after which no negative balances (refunds) are allowed on bills for overdue materials',
         'coust', 'description'),
     'interval', null)
 ,(  'bill.negative_balance_interval_on_lost', 'finance',
@@ -5064,7 +5064,7 @@ INSERT into config.org_unit_setting_type
         'coust', 'label'),
     oils_i18n_gettext(
         'bill.negative_balance_interval_on_lost',
-        'Amount of time after which no negative balances or credits are allowed on bills for lost/long overdue materials',
+        'Amount of time after which no negative balances (refunds) are allowed on bills for lost/long overdue materials',
         'coust', 'description'),
     'interval', null)
 ;
diff --git a/Open-ILS/src/sql/Pg/live_t/lp1198465_run_this_before_livetests.sql b/Open-ILS/src/sql/Pg/live_t/lp1198465_run_this_before_livetests.sql
index 194963b..23588d1 100644
--- a/Open-ILS/src/sql/Pg/live_t/lp1198465_run_this_before_livetests.sql
+++ b/Open-ILS/src/sql/Pg/live_t/lp1198465_run_this_before_livetests.sql
@@ -155,7 +155,7 @@ INSERT INTO money.billing (id, xact, billing_ts, voided, voider, void_time, amou
     (DEFAULT, 16, '2014-05-26 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
     (DEFAULT, 16, '2014-05-27 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
     (DEFAULT, 16, '2014-05-28 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
-    -- XACT 10 must be last, because we use CURRVAL() to put their IDs in the adjustment payments
+    -- XACT 10 must be last, because we use CURRVAL() to put their IDs in the account adjustments
     (DEFAULT, 10, (DATE(NOW() - '9 days'::interval) || ' 23:59:59')::TIMESTAMP, false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
     (DEFAULT, 10, (DATE(NOW() - '8 days'::interval) || ' 23:59:59')::TIMESTAMP, false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
     (DEFAULT, 10, (DATE(NOW() - '7 days'::interval) || ' 23:59:59')::TIMESTAMP, false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
@@ -166,7 +166,7 @@ INSERT INTO money.billing (id, xact, billing_ts, voided, voider, void_time, amou
     (DEFAULT, 10, NOW() - '3 days'::interval, false, NULL, NULL, 50.00, 'Lost Materials', 3, 'SYSTEM GENERATED');
 
 
-INSERT INTO money.adjustment_payment (id, xact, payment_ts, voided, amount, note, amount_collected, accepting_usr, billing) VALUES
+INSERT INTO money.account_adjustment (id, xact, payment_ts, voided, amount, note, amount_collected, accepting_usr, billing) VALUES
     (DEFAULT, 10, NOW() - '3 days'::interval, false, 0.10, '', 0.10, 1, CURRVAL('money.billing_id_seq') - 7),
     (DEFAULT, 10, NOW() - '3 days'::interval, false, 0.10, '', 0.10, 1, CURRVAL('money.billing_id_seq') - 6),
     (DEFAULT, 10, NOW() - '3 days'::interval, false, 0.10, '', 0.10, 1, CURRVAL('money.billing_id_seq') - 5),
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.conditional_negative_balance.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.conditional_negative_balance.sql
index 1d03ec9..0bf8ec1 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.conditional_negative_balance.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.conditional_negative_balance.sql
@@ -2,19 +2,19 @@ BEGIN;
 
 -- SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
 
-CREATE TABLE money.adjustment_payment (
+CREATE TABLE money.account_adjustment (
     billing BIGINT REFERENCES money.billing (id) ON DELETE SET NULL
 ) INHERITS (money.bnm_payment);
-ALTER TABLE money.adjustment_payment ADD PRIMARY KEY (id);
-CREATE INDEX money_adjustment_id_idx ON money.adjustment_payment (id);
-CREATE INDEX money_adjustment_payment_xact_idx ON money.adjustment_payment (xact);
-CREATE INDEX money_adjustment_payment_bill_idx ON money.adjustment_payment (billing);
-CREATE INDEX money_adjustment_payment_payment_ts_idx ON money.adjustment_payment (payment_ts);
-CREATE INDEX money_adjustment_payment_accepting_usr_idx ON money.adjustment_payment (accepting_usr);
+ALTER TABLE money.account_adjustment ADD PRIMARY KEY (id);
+CREATE INDEX money_adjustment_id_idx ON money.account_adjustment (id);
+CREATE INDEX money_account_adjustment_xact_idx ON money.account_adjustment (xact);
+CREATE INDEX money_account_adjustment_bill_idx ON money.account_adjustment (billing);
+CREATE INDEX money_account_adjustment_payment_ts_idx ON money.account_adjustment (payment_ts);
+CREATE INDEX money_account_adjustment_accepting_usr_idx ON money.account_adjustment (accepting_usr);
 
-CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.adjustment_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_add ('adjustment_payment');
-CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.adjustment_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_update ('adjustment_payment');
-CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.adjustment_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_del ('adjustment_payment');
+CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.account_adjustment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_add ('account_adjustment');
+CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.account_adjustment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_update ('account_adjustment');
+CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.account_adjustment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_del ('account_adjustment');
 
 -- Insert new org. unit settings.
 INSERT INTO config.org_unit_setting_type 
@@ -28,7 +28,7 @@ VALUES
             'coust', 'label'),
         oils_i18n_gettext(
             'bill.prohibit_negative_balance_default',
-            'Default setting to prevent credits on circulation related bills',
+            'Default setting to prevent negative balances (refunds) on circulation related bills',
             'coust', 'description')
        ),
        ('bill.prohibit_negative_balance_on_overdues',
@@ -39,7 +39,7 @@ VALUES
             'coust', 'label'),
         oils_i18n_gettext(
             'bill.prohibit_negative_balance_on_overdues',
-            'Prevent credits on bills for overdue materials',
+            'Prevent negative balances (refunds) on bills for overdue materials',
             'coust', 'description')
        ),
        ('bill.prohibit_negative_balance_on_lost',
@@ -50,7 +50,7 @@ VALUES
             'coust', 'label'),
         oils_i18n_gettext(
             'bill.prohibit_negative_balance_on_lost',
-            'Prevent credits on bills for lost/long-overde materials',
+            'Prevent negative balances (refunds) on bills for lost/long-overdue materials',
             'coust', 'description')
        ),
        ('bill.negative_balance_interval_default',
@@ -61,7 +61,7 @@ VALUES
             'coust', 'label'),
         oils_i18n_gettext(
             'bill.negative_balance_interval_default',
-            'Amount of time after which no negative balances or credits are allowed on circulation bills',
+            'Amount of time after which no negative balances (refunds) are allowed on circulation bills',
             'coust', 'description')
        ),
        ('bill.negative_balance_interval_on_overdues',
@@ -72,7 +72,7 @@ VALUES
             'coust', 'label'),
         oils_i18n_gettext(
             'bill.negative_balance_interval_on_overdues',
-            'Amount of time after which no negative balances or credits are allowed on bills for overdue materials',
+            'Amount of time after which no negative balances (refunds) are allowed on bills for overdue materials',
             'coust', 'description')
        ),
        ('bill.negative_balance_interval_on_lost',
@@ -83,7 +83,7 @@ VALUES
             'coust', 'label'),
         oils_i18n_gettext(
             'bill.negative_balance_interval_on_lost',
-            'Amount of time after which no negative balances or credits are allowed on bills for lost/long overdue materials',
+            'Amount of time after which no negative balances (refunds) are allowed on bills for lost/long overdue materials',
             'coust', 'description')
        );
 

commit cde1573c3273d4b180a319387700596f7c526407
Author: Remington Steed <rjs7 at calvin.edu>
Date:   Tue Jul 21 13:21:15 2015 -0400

    LP 1198465: More tests for conditional negative balances
    
    This commit adds the remaining test cases documented by Kathy Lussier on
    this wiki page:
    
        http://evergreen-ils.org/dokuwiki/doku.php?id=qa:billing_test_cases
    
    Test cases included in this commit are:
    
        6. Restores Overdue Fines Appropriately, No Previous "Voids", Patron
           Will Not Owe On Lost Item Return
        7. Restores Overdue Fines Appropriately, No Previous "Voids", Patron
           Will Still Owe On Lost Item Return
        9. Restore Overdue Fines Appropriately, Previous Voids, Negative
           Balance Allowed
       13. Prohibit negative balances on lost materials bills ONLY
       14. Prohibit negative balances on overdue bills ONLY
    
    Note that test case 5 is omitted because it is a duplicate of case 2,
    and case 11 is included but commented out because it is unclear how best
    to handle the situation.
    
    Signed-off-by: Remington Steed <rjs7 at calvin.edu>
    Signed-off-by: Dan Wells <dbw2 at calvin.edu>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Ben Shum <bshum at biblio.org>

diff --git a/Open-ILS/src/perlmods/live_t/09-lp1198465_neg_balances.t b/Open-ILS/src/perlmods/live_t/09-lp1198465_neg_balances.t
index 01c4fed..3ee0576 100644
--- a/Open-ILS/src/perlmods/live_t/09-lp1198465_neg_balances.t
+++ b/Open-ILS/src/perlmods/live_t/09-lp1198465_neg_balances.t
@@ -1,6 +1,6 @@
 #!perl
 
-use Test::More tests => 64;
+use Test::More tests => 127;
 
 diag("Test features of Conditional Negative Balances code.");
 
@@ -149,7 +149,6 @@ if ($user_obj = retrieve_patron($patron_id)) {
 $xact_id = 1;
 $item_id = 2;
 $item_barcode = 'CONC4000037';
-$org_id = 1; #CONS
 
 $summary = fetch_billable_xact_summary($xact_id);
 ok( $summary, 'CASE 1: Found the transaction summary');
@@ -236,7 +235,6 @@ is(
 $xact_id = 2;
 $item_id = 3;
 $item_barcode = 'CONC4000038';
-$org_id = 1; #CONS
 
 $summary = fetch_billable_xact_summary($xact_id);
 ok( $summary, 'CASE 2: Found the transaction summary');
@@ -288,18 +286,36 @@ is(
 
 
 ##############################
-# 3. Basic No Negative Balance Test
+# 13. RERUN of Case 1. No Prohibit Negative Balance Settings Are Enabled, Payment Made
+# SETTINGS: Prohibit negative balances on bills for lost materials
 ##############################
 
+# Setup next patron
+$patron_id = 6;
+$patron_usrname = '99999335859';
+
+# Look up the patron
+if ($user_obj = retrieve_patron($patron_id)) {
+    is(
+        ref $user_obj,
+        'Fieldmapper::actor::user',
+        'open-ils.storage.direct.actor.user.retrieve returned aou object'
+    );
+    is(
+        $user_obj->usrname,
+        $patron_usrname,
+        'Patron with id = ' . $patron_id . ' has username ' . $patron_usrname
+    );
+}
+
 ### Setup use case variables
-$xact_id = 3;
-$item_id = 4;
-$item_barcode = 'CONC4000039';
-$org_id = 1; #CONS
+$xact_id = 13;
+$item_id = 14;
+$item_barcode = 'CONC4000049';
 
 # Setup Org Unit Settings
 $settings = {
-    'bill.prohibit_negative_balance_default' => 1
+    'bill.prohibit_negative_balance_on_lost' => 1
 };
 $apputils->simplereq(
     'open-ils.actor',
@@ -310,19 +326,47 @@ $apputils->simplereq(
 );
 
 $summary = fetch_billable_xact_summary($xact_id);
-ok( $summary, 'CASE 3: Found the transaction summary');
+ok( $summary, 'CASE 13a: Found the transaction summary');
 is(
     $summary->balance_owed,
     '50.00',
     'Starting balance owed is 50.00 for lost item'
 );
 
+### pay the whole bill
+$payment_blob = {
+    userid => $patron_id,
+    note => '09-lp1198465_neg_balances.t',
+    payment_type => 'cash_payment',
+    patron_credit => '0.00',
+    payments => [ [ $xact_id, '50.00' ] ]
+};
+$pay_resp = pay_bills($payment_blob);
+
+is(
+    scalar( @{ $pay_resp->{payments} } ),
+    1,
+    'Payment response included one payment id'
+);
+
+$summary = fetch_billable_xact_summary($xact_id);
+is(
+    $summary->balance_owed,
+    '0.00',
+    'Remaining balance of 0.00 after payment'
+);
+
 ### check-in the lost copy
 
 $item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
 if (my $item_resp = $item_req->recv) {
     if (my $item = $item_resp->content) {
         is(
+            ref $item,
+            'Fieldmapper::asset::copy',
+            'open-ils.storage.direct.asset.copy.retrieve returned acp object'
+        );
+        is(
             $item->status,
             3,
             'Item with id = ' . $item_id . ' has status of LOST'
@@ -354,49 +398,39 @@ $summary = fetch_billable_xact_summary($xact_id);
 is(
     $summary->balance_owed,
     '0.00',
-    'Patron has a balance of 0.00 (negative balance prohibited)'
+    'Patron has a balance of 0.00 (negative balance prevented)'
 );
 
+
 ##############################
-# 4. Prohibit Negative Balances with Partial Payment
+# 13. RERUN of Case 12. Test negative balance settings on fines
+# SETTINGS: Prohibit negative balances on bills for lost materials
 ##############################
 
 ### Setup use case variables
-$xact_id = 4;
-$item_id = 5;
-$item_barcode = 'CONC4000040';
-$org_id = 1; #CONS
+$xact_id = 14;
+$item_id = 15;
+$item_barcode = 'CONC4000050';
 
 # Setup Org Unit Settings
-# already set: 'bill.prohibit_negative_balance_default' => 1
+# ALREADY SET:
+#    'bill.prohibit_negative_balance_on_lost' => 1
 
 $summary = fetch_billable_xact_summary($xact_id);
-ok( $summary, 'CASE 4: Found the transaction summary');
+ok( $summary, 'CASE 13b: Found the transaction summary');
 is(
     $summary->balance_owed,
-    '50.00',
-    'Starting balance owed is 50.00 for lost item'
+    '0.70',
+    'Starting balance owed is 0.70 for overdue fines'
 );
 
-### confirm the copy is lost
-$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
-if (my $item_resp = $item_req->recv) {
-    if (my $item = $item_resp->content) {
-        is(
-            $item->status,
-            3,
-            'Item with id = ' . $item_id . ' has status of LOST'
-        );
-    }
-}
-
 ### partially pay the bill
 $payment_blob = {
     userid => $patron_id,
     note => '09-lp1198465_neg_balances.t',
     payment_type => 'cash_payment',
     patron_credit => '0.00',
-    payments => [ [ $xact_id, '10.00' ] ]
+    payments => [ [ $xact_id, '0.20' ] ]
 };
 $pay_resp = pay_bills($payment_blob);
 
@@ -409,13 +443,15 @@ is(
 $summary = fetch_billable_xact_summary($xact_id);
 is(
     $summary->balance_owed,
-    '40.00',
-    'Remaining balance of 40.00 after payment'
+    '0.50',
+    'Remaining balance of 0.50 after payment'
 );
 
-### check-in the lost copy
+### Check in using Amnesty Mode
 $checkin_resp = $script->do_checkin_override({
-    barcode => $item_barcode});
+    barcode => $item_barcode,
+    void_overdues => 1
+});
 is(
     $checkin_resp->{ilsevent},
     0,
@@ -433,138 +469,52 @@ if (my $item_resp = $item_req->recv) {
 }
 
 ### verify ending state
-
 $summary = fetch_billable_xact_summary($xact_id);
 is(
     $summary->balance_owed,
-    '0.00',
-    'Patron has a balance of 0.00 (negative balance prohibited)'
+    '-0.20',
+    'Patron has a negative balance of -0.20 (refund of overdue fine payment)'
 );
 
 
-###############################
-## 11. Manually voiding lost book fee does not result in negative balances
-###############################
-#
-#### Setup use case variables
-#$xact_id = 5;
-#$item_id = 6;
-#$item_barcode = 'CONC4000040';
-#$org_id = 1; #CONS
-#
-## Setup Org Unit Settings
-## already set: 'bill.prohibit_negative_balance_default' => 1
-#
-#$summary = fetch_billable_xact_summary($xact_id);
-#ok( $summary, 'CASE 11: Found the transaction summary');
-#is(
-#    $summary->balance_owed,
-#    '50.00',
-#    'Starting balance owed is 50.00 for lost item'
-#);
-#
-#### confirm the copy is lost
-#$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
-#if (my $item_resp = $item_req->recv) {
-#    if (my $item = $item_resp->content) {
-#        is(
-#            $item->status,
-#            3,
-#            'Item with id = ' . $item_id . ' has status of LOST'
-#        );
-#    }
-#}
-#
-#### partially pay the bill
-#$payment_blob = {
-#    userid => $patron_id,
-#    note => '09-lp1198465_neg_balances.t',
-#    payment_type => 'cash_payment',
-#    patron_credit => '0.00',
-#    payments => [ [ $xact_id, '10.00' ] ]
-#};
-#$pay_resp = pay_bills($payment_blob);
-#
-#is(
-#    scalar( @{ $pay_resp->{payments} } ),
-#    1,
-#    'Payment response included one payment id'
-#);
-#
-#$summary = fetch_billable_xact_summary($xact_id);
-#is(
-#    $summary->balance_owed,
-#    '40.00',
-#    'Remaining balance of 40.00 after payment'
-#);
-#
-#### TODO: manually void "the rest" of the bill (i.e. prevent neg bal)
-#### XXX: HARDCODING billing id for now; should look up the LOST bill for this xact?
-#my @billing_ids = (6);
-#my $void_resp = void_bills(\@billing_ids);
-#
-#is(
-#    $void_resp,
-#    '1',
-#    'Voiding was successful'
-#);
-#
-#### verify ending state
-#
-#$summary = fetch_billable_xact_summary($xact_id);
-#is(
-#    $summary->balance_owed,
-#    '0.00',
-#    'Patron has a balance of 0.00 (negative balance prohibited)'
-#);
-
-
 ##############################
-# 12. Test negative balance settings on fines
+# 14. RERUN of Case 1. No Prohibit Negative Balance Settings Are Enabled, Payment Made
+# SETTINGS: Prohibit negative balances on bills for overdue materials
 ##############################
 
-# Setup next patron
-$patron_id = 5;
-$patron_usrname = '99999387993';
-
-# Look up the patron
-if ($user_obj = retrieve_patron($patron_id)) {
-    is(
-        ref $user_obj,
-        'Fieldmapper::actor::user',
-        'open-ils.storage.direct.actor.user.retrieve returned aou object'
-    );
-    is(
-        $user_obj->usrname,
-        $patron_usrname,
-        'Patron with id = ' . $patron_id . ' has username ' . $patron_usrname
-    );
-}
-
 ### Setup use case variables
-$xact_id = 7;
-$item_id = 8;
-$item_barcode = 'CONC4000043';
-$org_id = 1; #CONS
+$xact_id = 15;
+$item_id = 16;
+$item_barcode = 'CONC4000051';
 
 # Setup Org Unit Settings
-# already set: 'bill.prohibit_negative_balance_default' => 1
+$settings = {
+    'bill.prohibit_negative_balance_on_lost' => 0, #unset from previous test
+    'bill.prohibit_negative_balance_on_overdues' => 1
+};
+$apputils->simplereq(
+    'open-ils.actor',
+    'open-ils.actor.org_unit.settings.update',
+    $script->authtoken,
+    $org_id,
+    $settings
+);
 
 $summary = fetch_billable_xact_summary($xact_id);
-ok( $summary, 'CASE 12: Found the transaction summary');
+ok( $summary, 'CASE 14a: Found the transaction summary');
 is(
     $summary->balance_owed,
-    '0.70',
-    'Starting balance owed is 0.70 for overdue fines'
+    '50.00',
+    'Starting balance owed is 50.00 for lost item'
 );
 
-### partially pay the bill
+### pay the whole bill
 $payment_blob = {
     userid => $patron_id,
     note => '09-lp1198465_neg_balances.t',
     payment_type => 'cash_payment',
     patron_credit => '0.00',
-    payments => [ [ $xact_id, '0.20' ] ]
+    payments => [ [ $xact_id, '50.00' ] ]
 };
 $pay_resp = pay_bills($payment_blob);
 
@@ -577,15 +527,30 @@ is(
 $summary = fetch_billable_xact_summary($xact_id);
 is(
     $summary->balance_owed,
-    '0.50',
-    'Remaining balance of 0.50 after payment'
+    '0.00',
+    'Remaining balance of 0.00 after payment'
 );
 
-### Check in using Amnesty Mode
+### check-in the lost copy
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        is(
+            ref $item,
+            'Fieldmapper::asset::copy',
+            'open-ils.storage.direct.asset.copy.retrieve returned acp object'
+        );
+        is(
+            $item->status,
+            3,
+            'Item with id = ' . $item_id . ' has status of LOST'
+        );
+    }
+}
+
 $checkin_resp = $script->do_checkin_override({
-    barcode => $item_barcode,
-    void_overdues => 1
-});
+    barcode => $item_barcode});
 is(
     $checkin_resp->{ilsevent},
     0,
@@ -603,49 +568,509 @@ if (my $item_resp = $item_req->recv) {
 }
 
 ### verify ending state
+
 $summary = fetch_billable_xact_summary($xact_id);
 is(
     $summary->balance_owed,
-    '0.00',
-    'Patron has a balance of 0.00 (remaining fines forgiven)'
+    '-50.00',
+    'Patron has a negative balance (credit) of 50.00 due to overpayment'
 );
 
 
 ##############################
-# 10. Interval Testing
+# 14. RERUN of Case 12. Test negative balance settings on fines
+# SETTINGS: Prohibit negative balances on bills for overdue materials
 ##############################
 
-# Setup Org Unit Settings
-# already set: 'bill.prohibit_negative_balance_default' => 1
+### Setup use case variables
+$xact_id = 16;
+$item_id = 17;
+$item_barcode = 'CONC4000052';
 
 # Setup Org Unit Settings
-$org_id = 1; #CONS
-$settings = {
-    'bill.negative_balance_interval_default' => '1 hour'
-};
-
-$apputils->simplereq(
-    'open-ils.actor',
-    'open-ils.actor.org_unit.settings.update',
-    $script->authtoken,
-    $org_id,
-    $settings
-);
-
-### Setup use case variables
-$xact_id = 8;
-$item_id = 9;
-$item_barcode = 'CONC4000044';
+# ALREADY SET:
+#    'bill.prohibit_negative_balance_on_overdues' => 1
 
 $summary = fetch_billable_xact_summary($xact_id);
-ok( $summary, 'CASE 10.1: Found the transaction summary');
+ok( $summary, 'CASE 14b: Found the transaction summary');
 is(
     $summary->balance_owed,
-    '0.00',
-    'Starting balance owed is 0.00 (LOST fee paid)'
-);
+    '0.70',
+    'Starting balance owed is 0.70 for overdue fines'
+);
+
+### partially pay the bill
+$payment_blob = {
+    userid => $patron_id,
+    note => '09-lp1198465_neg_balances.t',
+    payment_type => 'cash_payment',
+    patron_credit => '0.00',
+    payments => [ [ $xact_id, '0.20' ] ]
+};
+$pay_resp = pay_bills($payment_blob);
+
+is(
+    scalar( @{ $pay_resp->{payments} } ),
+    1,
+    'Payment response included one payment id'
+);
+
+$summary = fetch_billable_xact_summary($xact_id);
+is(
+    $summary->balance_owed,
+    '0.50',
+    'Remaining balance of 0.50 after payment'
+);
+
+### Check in using Amnesty Mode
+$checkin_resp = $script->do_checkin_override({
+    barcode => $item_barcode,
+    void_overdues => 1
+});
+is(
+    $checkin_resp->{ilsevent},
+    0,
+    'Checkin returned a SUCCESS event'
+);
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        ok(
+            $item->status == 7 || $item->status == 0,
+            'Item with id = ' . $item_id . ' has status of Reshelving or Available after fresh Storage request'
+        );
+    }
+}
+
+### verify ending state
+$summary = fetch_billable_xact_summary($xact_id);
+is(
+    $summary->balance_owed,
+    '0.00',
+    'Patron has a balance of 0.00 (negative balance prevented)'
+);
+
+
+##############################
+# 3. Basic No Negative Balance Test
+##############################
+
+# Re-setup first patron
+$patron_id = 4;
+$patron_usrname = '99999355250';
+
+# Look up the patron
+if ($user_obj = retrieve_patron($patron_id)) {
+    is(
+        ref $user_obj,
+        'Fieldmapper::actor::user',
+        'open-ils.storage.direct.actor.user.retrieve returned aou object'
+    );
+    is(
+        $user_obj->usrname,
+        $patron_usrname,
+        'Patron with id = ' . $patron_id . ' has username ' . $patron_usrname
+    );
+}
+
+
+### Setup use case variables
+$xact_id = 3;
+$item_id = 4;
+$item_barcode = 'CONC4000039';
+
+# Setup Org Unit Settings
+$settings = {
+    'bill.prohibit_negative_balance_on_overdues' => 0, #unset from previous test
+    'bill.prohibit_negative_balance_default' => 1
+};
+$apputils->simplereq(
+    'open-ils.actor',
+    'open-ils.actor.org_unit.settings.update',
+    $script->authtoken,
+    $org_id,
+    $settings
+);
+
+$summary = fetch_billable_xact_summary($xact_id);
+ok( $summary, 'CASE 3: Found the transaction summary');
+is(
+    $summary->balance_owed,
+    '50.00',
+    'Starting balance owed is 50.00 for lost item'
+);
+
+### check-in the lost copy
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        is(
+            $item->status,
+            3,
+            'Item with id = ' . $item_id . ' has status of LOST'
+        );
+    }
+}
+
+$checkin_resp = $script->do_checkin_override({
+    barcode => $item_barcode});
+is(
+    $checkin_resp->{ilsevent},
+    0,
+    'Checkin returned a SUCCESS event'
+);
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        ok(
+            $item->status == 7 || $item->status == 0,
+            'Item with id = ' . $item_id . ' has status of Reshelving or Available after fresh Storage request'
+        );
+    }
+}
+
+### verify ending state
+
+$summary = fetch_billable_xact_summary($xact_id);
+is(
+    $summary->balance_owed,
+    '0.00',
+    'Patron has a balance of 0.00 (negative balance prevented)'
+);
+
+##############################
+# 4. Prohibit Negative Balances with Partial Payment
+##############################
+
+### Setup use case variables
+$xact_id = 4;
+$item_id = 5;
+$item_barcode = 'CONC4000040';
+
+# Setup Org Unit Settings
+# ALREADY SET:
+#     'bill.prohibit_negative_balance_default' => 1
+
+$summary = fetch_billable_xact_summary($xact_id);
+ok( $summary, 'CASE 4: Found the transaction summary');
+is(
+    $summary->balance_owed,
+    '50.00',
+    'Starting balance owed is 50.00 for lost item'
+);
+
+### confirm the copy is lost
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        is(
+            $item->status,
+            3,
+            'Item with id = ' . $item_id . ' has status of LOST'
+        );
+    }
+}
+
+### partially pay the bill
+$payment_blob = {
+    userid => $patron_id,
+    note => '09-lp1198465_neg_balances.t',
+    payment_type => 'cash_payment',
+    patron_credit => '0.00',
+    payments => [ [ $xact_id, '10.00' ] ]
+};
+$pay_resp = pay_bills($payment_blob);
+
+is(
+    scalar( @{ $pay_resp->{payments} } ),
+    1,
+    'Payment response included one payment id'
+);
+
+$summary = fetch_billable_xact_summary($xact_id);
+is(
+    $summary->balance_owed,
+    '40.00',
+    'Remaining balance of 40.00 after payment'
+);
+
+### check-in the lost copy
+$checkin_resp = $script->do_checkin_override({
+    barcode => $item_barcode});
+is(
+    $checkin_resp->{ilsevent},
+    0,
+    'Checkin returned a SUCCESS event'
+);
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        ok(
+            $item->status == 7 || $item->status == 0,
+            'Item with id = ' . $item_id . ' has status of Reshelving or Available after fresh Storage request'
+        );
+    }
+}
+
+### verify ending state
+
+$summary = fetch_billable_xact_summary($xact_id);
+is(
+    $summary->balance_owed,
+    '0.00',
+    'Patron has a balance of 0.00 (negative balance prevented)'
+);
+
+
+###############################
+## 11. Manually voiding lost book fee does not result in negative balances
+###############################
+#
+#### Setup use case variables
+#$xact_id = 5;
+#$item_id = 6;
+#$item_barcode = 'CONC4000040';
+#
+## Setup Org Unit Settings
+# ALREADY SET:
+#     'bill.prohibit_negative_balance_default' => 1
+#
+#$summary = fetch_billable_xact_summary($xact_id);
+#ok( $summary, 'CASE 11: Found the transaction summary');
+#is(
+#    $summary->balance_owed,
+#    '50.00',
+#    'Starting balance owed is 50.00 for lost item'
+#);
+#
+#### confirm the copy is lost
+#$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+#if (my $item_resp = $item_req->recv) {
+#    if (my $item = $item_resp->content) {
+#        is(
+#            $item->status,
+#            3,
+#            'Item with id = ' . $item_id . ' has status of LOST'
+#        );
+#    }
+#}
+#
+#### partially pay the bill
+#$payment_blob = {
+#    userid => $patron_id,
+#    note => '09-lp1198465_neg_balances.t',
+#    payment_type => 'cash_payment',
+#    patron_credit => '0.00',
+#    payments => [ [ $xact_id, '10.00' ] ]
+#};
+#$pay_resp = pay_bills($payment_blob);
+#
+#is(
+#    scalar( @{ $pay_resp->{payments} } ),
+#    1,
+#    'Payment response included one payment id'
+#);
+#
+#$summary = fetch_billable_xact_summary($xact_id);
+#is(
+#    $summary->balance_owed,
+#    '40.00',
+#    'Remaining balance of 40.00 after payment'
+#);
+#
+#### TODO: manually void "the rest" of the bill (i.e. prevent neg bal)
+#### XXX: HARDCODING billing id for now; should look up the LOST bill for this xact?
+#my @billing_ids = (6);
+#my $void_resp = void_bills(\@billing_ids);
+#
+#is(
+#    $void_resp,
+#    '1',
+#    'Voiding was successful'
+#);
+#
+#### verify ending state
+#
+#$summary = fetch_billable_xact_summary($xact_id);
+#is(
+#    $summary->balance_owed,
+#    '0.00',
+#    'Patron has a balance of 0.00 (negative balance prohibited)'
+#);
+
+
+##############################
+# 12. Test negative balance settings on fines
+##############################
+
+# Setup next patron
+$patron_id = 5;
+$patron_usrname = '99999387993';
+
+# Look up the patron
+if ($user_obj = retrieve_patron($patron_id)) {
+    is(
+        ref $user_obj,
+        'Fieldmapper::actor::user',
+        'open-ils.storage.direct.actor.user.retrieve returned aou object'
+    );
+    is(
+        $user_obj->usrname,
+        $patron_usrname,
+        'Patron with id = ' . $patron_id . ' has username ' . $patron_usrname
+    );
+}
+
+### Setup use case variables
+$xact_id = 7;
+$item_id = 8;
+$item_barcode = 'CONC4000043';
+
+# Setup Org Unit Settings
+# ALREADY SET:
+#     'bill.prohibit_negative_balance_default' => 1
+
+$summary = fetch_billable_xact_summary($xact_id);
+ok( $summary, 'CASE 12: Found the transaction summary');
+is(
+    $summary->balance_owed,
+    '0.70',
+    'Starting balance owed is 0.70 for overdue fines'
+);
+
+### partially pay the bill
+$payment_blob = {
+    userid => $patron_id,
+    note => '09-lp1198465_neg_balances.t',
+    payment_type => 'cash_payment',
+    patron_credit => '0.00',
+    payments => [ [ $xact_id, '0.20' ] ]
+};
+$pay_resp = pay_bills($payment_blob);
+
+is(
+    scalar( @{ $pay_resp->{payments} } ),
+    1,
+    'Payment response included one payment id'
+);
+
+$summary = fetch_billable_xact_summary($xact_id);
+is(
+    $summary->balance_owed,
+    '0.50',
+    'Remaining balance of 0.50 after payment'
+);
+
+### Check in using Amnesty Mode
+$checkin_resp = $script->do_checkin_override({
+    barcode => $item_barcode,
+    void_overdues => 1
+});
+is(
+    $checkin_resp->{ilsevent},
+    0,
+    'Checkin returned a SUCCESS event'
+);
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        ok(
+            $item->status == 7 || $item->status == 0,
+            'Item with id = ' . $item_id . ' has status of Reshelving or Available after fresh Storage request'
+        );
+    }
+}
+
+### verify ending state
+$summary = fetch_billable_xact_summary($xact_id);
+is(
+    $summary->balance_owed,
+    '0.00',
+    'Patron has a balance of 0.00 (remaining fines forgiven)'
+);
+
+
+##############################
+# 10. Interval Testing
+##############################
+
+# Setup Org Unit Settings
+# ALREADY SET:
+#     'bill.prohibit_negative_balance_default' => 1
+
+# Setup Org Unit Settings
+$settings = {
+    'bill.negative_balance_interval_default' => '1 hour'
+};
+
+$apputils->simplereq(
+    'open-ils.actor',
+    'open-ils.actor.org_unit.settings.update',
+    $script->authtoken,
+    $org_id,
+    $settings
+);
+
+### Setup use case variables
+$xact_id = 8;
+$item_id = 9;
+$item_barcode = 'CONC4000044';
+
+$summary = fetch_billable_xact_summary($xact_id);
+ok( $summary, 'CASE 10.1: Found the transaction summary');
+is(
+    $summary->balance_owed,
+    '0.00',
+    'Starting balance owed is 0.00 (LOST fee paid)'
+);
+
+### Check in first item (right after its payment)
+$checkin_resp = $script->do_checkin_override({
+    barcode => $item_barcode,
+});
+is(
+    $checkin_resp->{ilsevent},
+    0,
+    'Checkin returned a SUCCESS event'
+);
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        ok(
+            $item->status == 7 || $item->status == 0,
+            'Item with id = ' . $item_id . ' has status of Reshelving or Available after fresh Storage request'
+        );
+    }
+}
+
+### verify ending state for 10.1
+$summary = fetch_billable_xact_summary($xact_id);
+is(
+    $summary->balance_owed,
+    '-50.00',
+    'Patron has a balance of -50.00 (lost item returned during interval)'
+);
 
-### Check in first item (right after its payment)
+### Setup use case variables
+$xact_id = 9;
+$item_id = 10;
+$item_barcode = 'CONC4000045';
+
+$summary = fetch_billable_xact_summary($xact_id);
+ok( $summary, 'CASE 10.2: Found the transaction summary');
+is(
+    $summary->balance_owed,
+    '0.00',
+    'Starting balance owed is 0.00 (LOST fee paid)'
+);
+
+### Check in second item (2 hours after its payment)
 $checkin_resp = $script->do_checkin_override({
     barcode => $item_barcode,
 });
@@ -665,31 +1090,286 @@ if (my $item_resp = $item_req->recv) {
     }
 }
 
-### verify ending state for 10.1
+### verify ending state
 $summary = fetch_billable_xact_summary($xact_id);
 is(
     $summary->balance_owed,
-    '-50.00',
-    'Patron has a balance of -50.00 (lost item returned during interval)'
+    '0.00',
+    'Patron has a balance of 0.00 (lost item returned after interval)'
 );
 
+
+#############################
+# 6. Restores Overdue Fines Appropriately, No Previous "Voids", Patron Will Not Owe On Lost Item Return
+#############################
+
 ### Setup use case variables
-$xact_id = 9;
-$item_id = 10;
-$item_barcode = 'CONC4000045';
+$xact_id = 10;
+$item_id = 11;
+$item_barcode = 'CONC4000046';
+
+# Setup Org Unit Settings
+$settings = {
+    'bill.negative_balance_interval_default' => 0, #unset previous setting
+    'circ.void_overdue_on_lost' => 1,
+    'circ.restore_overdue_on_lost_return' => 1,
+    'circ.lost.generate_overdue_on_checkin' => 1
+};
+
+$apputils->simplereq(
+    'open-ils.actor',
+    'open-ils.actor.org_unit.settings.update',
+    $script->authtoken,
+    $org_id,
+    $settings
+);
+
+$summary = fetch_billable_xact_summary($xact_id);
+ok( $summary, 'CASE 6: Found the transaction summary');
+is(
+    $summary->balance_owed,
+    '40.00',
+    'Starting balance owed is 40.00 for partially paid lost item'
+);
+
+### check-in the lost copy
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        is(
+            ref $item,
+            'Fieldmapper::asset::copy',
+            'open-ils.storage.direct.asset.copy.retrieve returned acp object'
+        );
+        is(
+            $item->status,
+            3,
+            'Item with id = ' . $item_id . ' has status of LOST'
+        );
+    }
+}
+
+$checkin_resp = $script->do_checkin_override({
+    barcode => $item_barcode});
+is(
+    $checkin_resp->{ilsevent},
+    0,
+    'Checkin returned a SUCCESS event'
+);
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        ok(
+            $item->status == 7 || $item->status == 0,
+            'Item with id = ' . $item_id . ' has status of Reshelving or Available after fresh Storage request'
+        );
+    }
+}
+
+### verify ending state
 
 $summary = fetch_billable_xact_summary($xact_id);
-ok( $summary, 'CASE 10.2: Found the transaction summary');
 is(
     $summary->balance_owed,
     '0.00',
-    'Starting balance owed is 0.00 (LOST fee paid)'
+    'Patron has a balance of 0.00 (negative balance prevented)'
 );
 
-### Check in second item (2 hours after its payment)
+
+#############################
+# 7. Restores Overdue Fines Appropriately, No Previous "Voids", Patron Will Still Owe On Lost Item Return
+#############################
+
+### Setup use case variables
+$xact_id = 11;
+$item_id = 12;
+$item_barcode = 'CONC4000047';
+
+# Setup Org Unit Settings
+# ALREADY SET:
+#     'bill.prohibit_negative_balance_default' => 1
+#     'circ.void_overdue_on_lost' => 1,
+#     'circ.restore_overdue_on_lost_return' => 1,
+#     'circ.lost.generate_overdue_on_checkin' => 1
+
+$apputils->simplereq(
+    'open-ils.actor',
+    'open-ils.actor.org_unit.settings.update',
+    $script->authtoken,
+    $org_id,
+    $settings
+);
+
+$summary = fetch_billable_xact_summary($xact_id);
+ok( $summary, 'CASE 7: Found the transaction summary');
+is(
+    $summary->balance_owed,
+    '0.70',
+    'Starting balance owed is 0.70 for overdues'
+);
+
+### mark item as LOST
+$apputils->simplereq(
+    'open-ils.circ',
+    'open-ils.circ.circulation.set_lost',
+    $script->authtoken,
+    {barcode => $item_barcode}
+);
+
+$summary = fetch_billable_xact_summary($xact_id);
+ok( $summary, 'Found the transaction summary');
+is(
+    $summary->balance_owed,
+    '50.00',
+    'New balance owed is 50.00 for LOST fee'
+);
+
+### partially pay the bill
+$payment_blob = {
+    userid => $patron_id,
+    note => '09-lp1198465_neg_balances.t',
+    payment_type => 'cash_payment',
+    patron_credit => '0.00',
+    payments => [ [ $xact_id, '0.10' ] ]
+};
+$pay_resp = pay_bills($payment_blob);
+
+is(
+    scalar( @{ $pay_resp->{payments} } ),
+    1,
+    'Payment response included one payment id'
+);
+
+$summary = fetch_billable_xact_summary($xact_id);
+is(
+    $summary->balance_owed,
+    '49.90',
+    'Remaining balance of 49.90 after payment'
+);
+
+### check-in the lost copy
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        is(
+            ref $item,
+            'Fieldmapper::asset::copy',
+            'open-ils.storage.direct.asset.copy.retrieve returned acp object'
+        );
+        is(
+            $item->status,
+            3,
+            'Item with id = ' . $item_id . ' has status of LOST'
+        );
+    }
+}
+
 $checkin_resp = $script->do_checkin_override({
-    barcode => $item_barcode,
-});
+    barcode => $item_barcode});
+is(
+    $checkin_resp->{ilsevent},
+    0,
+    'Checkin returned a SUCCESS event'
+);
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        ok(
+            $item->status == 7 || $item->status == 0,
+            'Item with id = ' . $item_id . ' has status of Reshelving or Available after fresh Storage request'
+        );
+    }
+}
+
+### verify ending state
+
+$summary = fetch_billable_xact_summary($xact_id);
+is(
+    $summary->balance_owed,
+    '0.60',
+    'Patron has a balance of 0.60 due to reinstated overdue fines'
+);
+
+
+#############################
+# 9. Restore Overdue Fines Appropriately, Previous Voids, Negative Balance Allowed
+#############################
+
+### Setup use case variables
+$xact_id = 12;
+$item_id = 13;
+$item_barcode = 'CONC4000048';
+
+# Setup Org Unit Settings
+# ALREADY SET:
+#     'bill.prohibit_negative_balance_default' => 1
+#     'circ.void_overdue_on_lost' => 1,
+#     'circ.restore_overdue_on_lost_return' => 1,
+#     'circ.lost.generate_overdue_on_checkin' => 1
+
+$apputils->simplereq(
+    'open-ils.actor',
+    'open-ils.actor.org_unit.settings.update',
+    $script->authtoken,
+    $org_id,
+    $settings
+);
+
+$summary = fetch_billable_xact_summary($xact_id);
+ok( $summary, 'CASE 9: Found the transaction summary');
+is(
+    $summary->balance_owed,
+    '50.00',
+    'Starting balance owed is 50.00 for lost item'
+);
+
+### partially pay the bill
+$payment_blob = {
+    userid => $patron_id,
+    note => '09-lp1198465_neg_balances.t',
+    payment_type => 'cash_payment',
+    patron_credit => '0.00',
+    payments => [ [ $xact_id, '10.00' ] ]
+};
+$pay_resp = pay_bills($payment_blob);
+
+is(
+    scalar( @{ $pay_resp->{payments} } ),
+    1,
+    'Payment response included one payment id'
+);
+
+$summary = fetch_billable_xact_summary($xact_id);
+is(
+    $summary->balance_owed,
+    '40.00',
+    'Remaining balance of 40.00 after payment'
+);
+
+### check-in the lost copy
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        is(
+            ref $item,
+            'Fieldmapper::asset::copy',
+            'open-ils.storage.direct.asset.copy.retrieve returned acp object'
+        );
+        is(
+            $item->status,
+            3,
+            'Item with id = ' . $item_id . ' has status of LOST'
+        );
+    }
+}
+
+$checkin_resp = $script->do_checkin_override({
+    barcode => $item_barcode});
 is(
     $checkin_resp->{ilsevent},
     0,
@@ -707,11 +1387,12 @@ if (my $item_resp = $item_req->recv) {
 }
 
 ### verify ending state
+
 $summary = fetch_billable_xact_summary($xact_id);
 is(
     $summary->balance_owed,
     '0.00',
-    'Patron has a balance of 0.00 (lost item returned after interval)'
+    'Patron has a balance of 0.00 (negative balance prevented)'
 );
 
 
@@ -743,13 +1424,14 @@ if ($user_obj = retrieve_patron($patron_id)) {
 $xact_id = 6;
 $item_id = 7;
 $item_barcode = 'CONC4000042';
-$org_id = 1; #CONS
 
 # Setup Org Unit Settings
+# ALREADY SET:
+#     'circ.void_overdue_on_lost' => 1,
+#     'circ.restore_overdue_on_lost_return' => 1,
+#     'circ.lost.generate_overdue_on_checkin' => 1
 $settings = {
-    'bill.prohibit_negative_balance_default' => 0,
-    'circ.restore_overdue_on_lost_return' => 1,
-    'circ.lost.generate_overdue_on_checkin' => 1
+    'bill.prohibit_negative_balance_default' => 0
 };
 
 $apputils->simplereq(
diff --git a/Open-ILS/src/sql/Pg/live_t/lp1198465_run_this_before_livetests.sql b/Open-ILS/src/sql/Pg/live_t/lp1198465_run_this_before_livetests.sql
index c578cad..194963b 100644
--- a/Open-ILS/src/sql/Pg/live_t/lp1198465_run_this_before_livetests.sql
+++ b/Open-ILS/src/sql/Pg/live_t/lp1198465_run_this_before_livetests.sql
@@ -9,11 +9,9 @@ BEGIN;
 -- NOTE: Org unit settings will be handled in the perl code
 
 
--- user id: 4, name: Gregory Jones
-
 -- clear bills and payments for our test circs
-DELETE FROM money.billing WHERE xact <= 9;
-DELETE FROM money.payment WHERE xact <= 9;
+DELETE FROM money.billing WHERE xact <= 16;
+DELETE FROM money.payment WHERE xact <= 16;
 
 -- clear any non-stock settings
 -- XXX This will need adjusting if new stock settings are added, so
@@ -24,33 +22,85 @@ DELETE FROM actor.org_unit_setting WHERE id >= 14;
 -- clear out the test workstation (just in case)
 DELETE FROM actor.workstation WHERE name = 'BR1-test-09-lp1198465_neg_balances.t';
 
--- Setup all LOST circs
+-- Setup some LOST circs, and change copy status to LOST
+UPDATE action.circulation SET
+    xact_start = '2014-05-14 08:39:13.070326-04',
+    due_date = '2014-05-21 23:59:59-04',
+    stop_fines_time = '2014-05-28 08:39:13.070326-04',
+    create_time = '2014-05-14 08:39:13.070326-04',
+    max_fine = '3.00',
+    stop_fines = 'LOST',
+    checkin_staff = NULL,
+    checkin_lib = NULL,
+    checkin_time = NULL,
+    checkin_scan_time = NULL
+WHERE id IN (1,2,3,4,5,6,12,13,15);
+UPDATE asset.copy SET status = 3 WHERE id IN (2,3,4,5,6,7,13,14,16);
+
+-- relative LOST circ
+UPDATE action.circulation SET
+    xact_start = NOW() - '24 days'::interval,
+    due_date = (DATE(NOW() - '10 days'::interval) || ' 23:59:59')::TIMESTAMP,
+    stop_fines_time = NOW() - '3 days'::interval,
+    create_time = NOW() - '24 days'::interval,
+    max_fine = '5.00',
+    stop_fines = 'LOST',
+    checkin_staff = NULL,
+    checkin_lib = NULL,
+    checkin_time = NULL,
+    checkin_scan_time = NULL
+WHERE id = 10;
+UPDATE asset.copy SET status = 3 WHERE id = 11;
+
+-- Two recently LOST items for Case 10: Interval Testing (1 hour interval)
+-- - Item 1: Lost, paid more than 1 hour later, to be returned LESS than 1 hour after payment (via perl test)
+-- - Item 2: Lost, paid more than 1 hour later, to be returned MORE than 1 hour after payment (via perl test)
+UPDATE action.circulation SET
+    create_time = NOW() - '1 week'::interval,
+    xact_start = NOW() - '1 week'::interval,
+    due_date = NOW() + '1 day'::interval,
+    stop_fines_time = NOW() - '3 hours'::interval,
+    max_fine = '3.00',
+    stop_fines = 'LOST',
+    checkin_staff = NULL,
+    checkin_lib = NULL,
+    checkin_time = NULL,
+    checkin_scan_time = NULL
+WHERE id IN (8, 9);
+UPDATE asset.copy SET status = 3 WHERE id IN (9, 10);
+
+-- non-lost circs, used for Amnesty Mode check-ins
 UPDATE action.circulation SET
     xact_start = '2014-05-14 08:39:13.070326-04',
-	due_date = '2014-05-21 23:59:59-04',
-	stop_fines_time = '2014-05-28 08:39:13.070326-04',
-	create_time = '2014-05-14 08:39:13.070326-04',
-	max_fine = '3.00',
-	stop_fines = 'LOST',
-	checkin_staff = NULL,
-	checkin_lib = NULL,
-	checkin_time = NULL,
-	checkin_scan_time = NULL
-WHERE id >= 1 AND id <= 6;
-UPDATE asset.copy SET status = 3 WHERE id >= 2 AND id <= 7;
-
--- Setup non-lost circ
+    due_date = '2014-05-21 23:59:59-04',
+    stop_fines_time = '2014-05-28 08:39:13.070326-04',
+    create_time = '2014-05-14 08:39:13.070326-04',
+    max_fine = '0.70',
+    stop_fines = 'MAXFINES',
+    checkin_staff = NULL,
+    checkin_lib = NULL,
+    checkin_time = NULL,
+    checkin_scan_time = NULL
+WHERE id IN (7, 14, 16);
+UPDATE asset.copy SET status = 1 WHERE id IN (8, 15, 17);
+
+-- Setup a non-lost, maxfines circ
 UPDATE action.circulation SET
-	checkin_staff = NULL,
-	checkin_lib = NULL,
-	checkin_time = NULL,
-	checkin_scan_time = NULL,
-	stop_fines = NULL,
-	stop_fines_time = NULL
-WHERE id = 7;
-UPDATE asset.copy SET status = 1 WHERE id = 8;
-
--- Setup other LOST and overdue fines
+    xact_start = '2014-05-14 08:39:13.070326-04',
+    due_date = '2014-05-21 23:59:59-04',
+    stop_fines_time = '2014-05-28 08:39:13.070326-04',
+    create_time = '2014-05-14 08:39:13.070326-04',
+    max_fine = '0.70',
+    stop_fines = 'MAXFINES',
+    checkin_staff = NULL,
+    checkin_lib = NULL,
+    checkin_time = NULL,
+    checkin_scan_time = NULL
+WHERE id = 11;
+UPDATE asset.copy SET status = 1 WHERE id = 12;
+
+
+-- Create LOST and overdue fines
 INSERT INTO money.billing (id, xact, billing_ts, voided, voider, void_time, amount, billing_type, btype, note) VALUES
     (DEFAULT, 1, '2014-05-28 08:39:13.070326-04', false, NULL, NULL, 50.00, 'Lost Materials', 3, 'SYSTEM GENERATED'),
     (DEFAULT, 2, '2014-05-28 08:39:13.070326-04', false, NULL, NULL, 50.00, 'Lost Materials', 3, 'SYSTEM GENERATED'),
@@ -71,39 +121,74 @@ INSERT INTO money.billing (id, xact, billing_ts, voided, voider, void_time, amou
     (DEFAULT, 7, '2014-05-25 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
     (DEFAULT, 7, '2014-05-26 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
     (DEFAULT, 7, '2014-05-27 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
-    (DEFAULT, 7, '2014-05-28 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine');
+    (DEFAULT, 7, '2014-05-28 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 8, NOW() - '2 hours'::interval, false, NULL, NULL, 50.00, 'Lost Materials', 3, 'SYSTEM GENERATED'),
+    (DEFAULT, 9, NOW() - '4 hours'::interval, false, NULL, NULL, 50.00, 'Lost Materials', 3, 'SYSTEM GENERATED'),
+    (DEFAULT, 11, '2014-05-22 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 11, '2014-05-23 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 11, '2014-05-24 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 11, '2014-05-25 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 11, '2014-05-26 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 11, '2014-05-27 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 11, '2014-05-28 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 12, '2014-05-22 23:59:59-04', true, 1, '2014-05-28 08:39:13.070326-04', 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 12, '2014-05-23 23:59:59-04', true, 1, '2014-05-28 08:39:13.070326-04', 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 12, '2014-05-24 23:59:59-04', true, 1, '2014-05-28 08:39:13.070326-04', 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 12, '2014-05-25 23:59:59-04', true, 1, '2014-05-28 08:39:13.070326-04', 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 12, '2014-05-26 23:59:59-04', true, 1, '2014-05-28 08:39:13.070326-04', 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 12, '2014-05-27 23:59:59-04', true, 1, '2014-05-28 08:39:13.070326-04', 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 12, '2014-05-28 23:59:59-04', true, 1, '2014-05-28 08:39:13.070326-04', 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 12, '2014-05-28 08:39:13.070326-04', false, NULL, NULL, 50.00, 'Lost Materials', 3, 'SYSTEM GENERATED'),
+    (DEFAULT, 13, '2014-05-28 08:39:13.070326-04', false, NULL, NULL, 50.00, 'Lost Materials', 3, 'SYSTEM GENERATED'),
+    (DEFAULT, 14, '2014-05-22 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 14, '2014-05-23 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 14, '2014-05-24 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 14, '2014-05-25 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 14, '2014-05-26 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 14, '2014-05-27 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 14, '2014-05-28 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 15, '2014-05-28 08:39:13.070326-04', false, NULL, NULL, 50.00, 'Lost Materials', 3, 'SYSTEM GENERATED'),
+    (DEFAULT, 16, '2014-05-22 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 16, '2014-05-23 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 16, '2014-05-24 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 16, '2014-05-25 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 16, '2014-05-26 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 16, '2014-05-27 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 16, '2014-05-28 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    -- XACT 10 must be last, because we use CURRVAL() to put their IDs in the adjustment payments
+    (DEFAULT, 10, (DATE(NOW() - '9 days'::interval) || ' 23:59:59')::TIMESTAMP, false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 10, (DATE(NOW() - '8 days'::interval) || ' 23:59:59')::TIMESTAMP, false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 10, (DATE(NOW() - '7 days'::interval) || ' 23:59:59')::TIMESTAMP, false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 10, (DATE(NOW() - '6 days'::interval) || ' 23:59:59')::TIMESTAMP, false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 10, (DATE(NOW() - '5 days'::interval) || ' 23:59:59')::TIMESTAMP, false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 10, (DATE(NOW() - '4 days'::interval) || ' 23:59:59')::TIMESTAMP, false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 10, (DATE(NOW() - '3 days'::interval) || ' 23:59:59')::TIMESTAMP, false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 10, NOW() - '3 days'::interval, false, NULL, NULL, 50.00, 'Lost Materials', 3, 'SYSTEM GENERATED');
 
--- Setup two recently LOST items for Case 10: Interval Testing
--- - Item 1: Lost, paid more than 1 hour later, returned less than 1 hour after payment (via perl test)
--- - Item 2: Lost, paid more than 1 hour later, returned more than 1 hour after payment (via perl test)
-UPDATE action.circulation SET
-	create_time = NOW() - '1 week'::interval,
-    xact_start = NOW() - '1 week'::interval,
-	due_date = NOW() + '1 day'::interval,
-	stop_fines_time = NOW() - '3 hours'::interval,
-	max_fine = '3.00',
-	stop_fines = 'LOST',
-	checkin_staff = NULL,
-	checkin_lib = NULL,
-	checkin_time = NULL,
-	checkin_scan_time = NULL
-WHERE id IN (8, 9);
-UPDATE asset.copy SET status = 3 WHERE id IN (9, 10);
 
-INSERT INTO money.billing (id, xact, billing_ts, voided, voider, void_time, amount, billing_type, btype, note) VALUES
-    (DEFAULT, 8, NOW() - '2 hours'::interval, false, NULL, NULL, 50.00, 'Lost Materials', 3, 'SYSTEM GENERATED'),
-    (DEFAULT, 9, NOW() - '4 hours'::interval, false, NULL, NULL, 50.00, 'Lost Materials', 3, 'SYSTEM GENERATED');
+INSERT INTO money.adjustment_payment (id, xact, payment_ts, voided, amount, note, amount_collected, accepting_usr, billing) VALUES
+    (DEFAULT, 10, NOW() - '3 days'::interval, false, 0.10, '', 0.10, 1, CURRVAL('money.billing_id_seq') - 7),
+    (DEFAULT, 10, NOW() - '3 days'::interval, false, 0.10, '', 0.10, 1, CURRVAL('money.billing_id_seq') - 6),
+    (DEFAULT, 10, NOW() - '3 days'::interval, false, 0.10, '', 0.10, 1, CURRVAL('money.billing_id_seq') - 5),
+    (DEFAULT, 10, NOW() - '3 days'::interval, false, 0.10, '', 0.10, 1, CURRVAL('money.billing_id_seq') - 4),
+    (DEFAULT, 10, NOW() - '3 days'::interval, false, 0.10, '', 0.10, 1, CURRVAL('money.billing_id_seq') - 3),
+    (DEFAULT, 10, NOW() - '3 days'::interval, false, 0.10, '', 0.10, 1, CURRVAL('money.billing_id_seq') - 2),
+    (DEFAULT, 10, NOW() - '3 days'::interval, false, 0.10, '', 0.10, 1, CURRVAL('money.billing_id_seq') - 1);
 
-INSERT INTO money.payment (id, xact, payment_ts, voided, amount, note) VALUES
-	(DEFAULT, 8, NOW() - '30 minutes'::interval, false, 50.00, 'LOST payment'),
-	(DEFAULT, 9, NOW() - '2 hours'::interval, false, 50.00, 'LOST payment');
+INSERT INTO money.cash_payment (id, xact, payment_ts, voided, amount, note, amount_collected, accepting_usr, cash_drawer) VALUES
+    (DEFAULT, 8, NOW() - '30 minutes'::interval, false, 50.00, 'LOST payment', 50.00, 1, 51),
+    (DEFAULT, 9, NOW() - '2 hours'::interval, false, 50.00, 'LOST payment', 50.00, 1, 51),
+    (DEFAULT, 10, NOW() - '2 days'::interval, false, 10.00, 'Partial LOST payment', 10.00, 1, 51);
 
 -- if rerunning, make sure our mangled bills have the right total in the summary
 UPDATE money.materialized_billable_xact_summary SET balance_owed = 50.00
-	WHERE id >=1 AND id <= 6;
+    WHERE id IN (1,2,3,4,5,6,12,13,15);
 UPDATE money.materialized_billable_xact_summary SET balance_owed = 0.70
-	WHERE id = 7;
+    WHERE id IN (7, 14, 16);
 UPDATE money.materialized_billable_xact_summary SET balance_owed = 0.00
-	WHERE id IN (8, 9);
-
+    WHERE id IN (8, 9);
+UPDATE money.materialized_billable_xact_summary SET balance_owed = 40.00
+    WHERE id = 10;
+UPDATE money.materialized_billable_xact_summary SET balance_owed = 0.70
+    WHERE id = 11;
 COMMIT;

commit 14836cc346774182dc187a24356c392c1cd942fa
Author: Dan Wells <dbw2 at calvin.edu>
Date:   Fri Jul 17 16:45:16 2015 -0400

    LP 1198465: Make conditional negative balances test sql re-runnable
    
    Add some DELETEs and some more explicit setting of a few DB pieces such
    that running the test SQL a second time will get everything back to
    state where the tests will again proceed as expected (hopefully
    successfully!).
    
    Signed-off-by: Dan Wells <dbw2 at calvin.edu>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Ben Shum <bshum at biblio.org>

diff --git a/Open-ILS/src/sql/Pg/live_t/lp1198465_run_this_before_livetests.sql b/Open-ILS/src/sql/Pg/live_t/lp1198465_run_this_before_livetests.sql
index b257b85..c578cad 100644
--- a/Open-ILS/src/sql/Pg/live_t/lp1198465_run_this_before_livetests.sql
+++ b/Open-ILS/src/sql/Pg/live_t/lp1198465_run_this_before_livetests.sql
@@ -11,6 +11,19 @@ BEGIN;
 
 -- user id: 4, name: Gregory Jones
 
+-- clear bills and payments for our test circs
+DELETE FROM money.billing WHERE xact <= 9;
+DELETE FROM money.payment WHERE xact <= 9;
+
+-- clear any non-stock settings
+-- XXX This will need adjusting if new stock settings are added, so
+-- TODO: Pad out org_unit_settings with a SETVAL like we do for other
+-- settings
+DELETE FROM actor.org_unit_setting WHERE id >= 14;
+
+-- clear out the test workstation (just in case)
+DELETE FROM actor.workstation WHERE name = 'BR1-test-09-lp1198465_neg_balances.t';
+
 -- Setup all LOST circs
 UPDATE action.circulation SET
     xact_start = '2014-05-14 08:39:13.070326-04',
@@ -18,7 +31,11 @@ UPDATE action.circulation SET
 	stop_fines_time = '2014-05-28 08:39:13.070326-04',
 	create_time = '2014-05-14 08:39:13.070326-04',
 	max_fine = '3.00',
-	stop_fines = 'LOST'
+	stop_fines = 'LOST',
+	checkin_staff = NULL,
+	checkin_lib = NULL,
+	checkin_time = NULL,
+	checkin_scan_time = NULL
 WHERE id >= 1 AND id <= 6;
 UPDATE asset.copy SET status = 3 WHERE id >= 2 AND id <= 7;
 
@@ -65,7 +82,11 @@ UPDATE action.circulation SET
 	due_date = NOW() + '1 day'::interval,
 	stop_fines_time = NOW() - '3 hours'::interval,
 	max_fine = '3.00',
-	stop_fines = 'LOST'
+	stop_fines = 'LOST',
+	checkin_staff = NULL,
+	checkin_lib = NULL,
+	checkin_time = NULL,
+	checkin_scan_time = NULL
 WHERE id IN (8, 9);
 UPDATE asset.copy SET status = 3 WHERE id IN (9, 10);
 
@@ -77,5 +98,12 @@ INSERT INTO money.payment (id, xact, payment_ts, voided, amount, note) VALUES
 	(DEFAULT, 8, NOW() - '30 minutes'::interval, false, 50.00, 'LOST payment'),
 	(DEFAULT, 9, NOW() - '2 hours'::interval, false, 50.00, 'LOST payment');
 
+-- if rerunning, make sure our mangled bills have the right total in the summary
+UPDATE money.materialized_billable_xact_summary SET balance_owed = 50.00
+	WHERE id >=1 AND id <= 6;
+UPDATE money.materialized_billable_xact_summary SET balance_owed = 0.70
+	WHERE id = 7;
+UPDATE money.materialized_billable_xact_summary SET balance_owed = 0.00
+	WHERE id IN (8, 9);
 
 COMMIT;

commit 336f8a60f9e46a8b4956c2b46694cc93da1b11d9
Author: Remington Steed <rjs7 at calvin.edu>
Date:   Fri Jul 17 15:42:37 2015 -0400

    LP 1198465: Initial tests for conditional negative balances
    
    This is a first commit of work-in-progress for testing the conditional
    negative balances features.  It covers 9 of the 14 test cases listed
    here (as of today):
    
    http://evergreen-ils.org/dokuwiki/doku.php?id=qa:billing_test_cases
    
    TODO:
    - The test is currently an SQL setup file plus a Perl live test file.
      One simple improvement would be to switch the setup process to
      cstore calls within the Perl test file.  This would be both more
      contained and more robust.
    - A second step to more advanced and useful tests would be to use
      higher-level API calls to create portions of the setup rather than
      doing everthing manually.  However, some test conditions cannot be
      reasonably setup with the normal API calls (e.g. bills of a specific
      age, or bills using a legacy format no longer generated by current
      code), so certain areas will likely always require direct
      manipulation.
    
    Signed-off-by: Daniel Wells <dbw2 at calvin.edu>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Ben Shum <bshum at biblio.org>

diff --git a/Open-ILS/src/perlmods/live_t/09-lp1198465_neg_balances.t b/Open-ILS/src/perlmods/live_t/09-lp1198465_neg_balances.t
new file mode 100644
index 0000000..01c4fed
--- /dev/null
+++ b/Open-ILS/src/perlmods/live_t/09-lp1198465_neg_balances.t
@@ -0,0 +1,843 @@
+#!perl
+
+use Test::More tests => 64;
+
+diag("Test features of Conditional Negative Balances code.");
+
+use constant WORKSTATION_NAME => 'BR1-test-09-lp1198465_neg_balances.t';
+use constant WORKSTATION_LIB => 4;
+
+use strict; use warnings;
+
+use DateTime;
+use DateTime::Format::ISO8601;
+use OpenSRF::Utils qw/cleanse_ISO8601/;
+use OpenILS::Utils::TestUtils;
+my $script = OpenILS::Utils::TestUtils->new();
+use Data::Dumper;
+
+our $apputils   = "OpenILS::Application::AppUtils";
+
+my ($patron_id, $patron_usrname, $xact_id, $item_id, $item_barcode);
+my ($summary, $payment_blob, $pay_resp, $item_req, $checkin_resp);
+my $user_obj;
+my $storage_ses = $script->session('open-ils.storage');
+
+
+sub retrieve_patron {
+    my $patron_id = shift;
+
+    my $user_req = $storage_ses->request('open-ils.storage.direct.actor.user.retrieve', $patron_id);
+    if (my $user_resp = $user_req->recv) {
+        if (my $patron_obj = $user_resp->content) {
+            return $patron_obj;
+        }
+    }
+    return 0;
+}
+
+sub fetch_billable_xact_summary {
+    my $xact_id = shift;
+    my $ses = $script->session('open-ils.cstore');
+    my $req = $ses->request(
+        'open-ils.cstore.direct.money.billable_transaction_summary.retrieve',
+        $xact_id);
+
+    if (my $resp = $req->recv) {
+        return $resp->content;
+    } else {
+        return 0;
+    }
+}
+
+sub pay_bills {
+    my $payment_blob = shift;
+    my $resp = $apputils->simplereq(
+        'open-ils.circ',
+        'open-ils.circ.money.payment',
+        $script->authtoken,
+        $payment_blob,
+        $user_obj->last_xact_id
+    );
+
+    #refetch user_obj to get latest last_xact_id
+    $user_obj = retrieve_patron($patron_id)
+        or die 'Could not refetch patron';
+
+    return $resp;
+}
+
+sub void_bills {
+    my $billing_ids = shift; #array ref
+    my $resp = $apputils->simplereq(
+        'open-ils.circ',
+        'open-ils.circ.money.billing.void',
+        $script->authtoken,
+        @$billing_ids
+    );
+
+    return $resp;
+}
+
+#----------------------------------------------------------------
+# The tests...  assumes stock sample data, full-auto install by
+# eg_wheezy_installer.sh, etc.
+#----------------------------------------------------------------
+
+# Connect to Evergreen
+$script->authenticate({
+    username => 'admin',
+    password => 'demo123',
+    type => 'staff'});
+ok( $script->authtoken, 'Have an authtoken');
+
+my $ws = $script->register_workstation(WORKSTATION_NAME,WORKSTATION_LIB);
+ok( ! ref $ws, 'Registered a new workstation');
+
+$script->logout();
+$script->authenticate({
+    username => 'admin',
+    password => 'demo123',
+    type => 'staff',
+    workstation => WORKSTATION_NAME});
+ok( $script->authtoken, 'Have an authtoken associated with the workstation');
+
+
+### TODO: verify that stock data is ready for testing
+
+### Setup Org Unit Settings that apply to all test cases
+
+my $org_id = 1; #CONS
+my $settings = {
+    'circ.max_item_price' => 50,
+    'circ.min_item_price' => 50,
+    'circ.void_lost_on_checkin' => 1
+};
+
+$apputils->simplereq(
+    'open-ils.actor',
+    'open-ils.actor.org_unit.settings.update',
+    $script->authtoken,
+    $org_id,
+    $settings
+);
+
+# Setup first patron
+$patron_id = 4;
+$patron_usrname = '99999355250';
+
+# Look up the patron
+if ($user_obj = retrieve_patron($patron_id)) {
+    is(
+        ref $user_obj,
+        'Fieldmapper::actor::user',
+        'open-ils.storage.direct.actor.user.retrieve returned aou object'
+    );
+    is(
+        $user_obj->usrname,
+        $patron_usrname,
+        'Patron with id = ' . $patron_id . ' has username ' . $patron_usrname
+    );
+}
+
+
+##############################
+# 1. No Prohibit Negative Balance Settings Are Enabled, Payment Made
+##############################
+
+### Setup use case variables
+$xact_id = 1;
+$item_id = 2;
+$item_barcode = 'CONC4000037';
+$org_id = 1; #CONS
+
+$summary = fetch_billable_xact_summary($xact_id);
+ok( $summary, 'CASE 1: Found the transaction summary');
+is(
+    $summary->balance_owed,
+    '50.00',
+    'Starting balance owed is 50.00 for lost item'
+);
+
+### pay the whole bill
+$payment_blob = {
+    userid => $patron_id,
+    note => '09-lp1198465_neg_balances.t',
+    payment_type => 'cash_payment',
+    patron_credit => '0.00',
+    payments => [ [ $xact_id, '50.00' ] ]
+};
+$pay_resp = pay_bills($payment_blob);
+
+is(
+    scalar( @{ $pay_resp->{payments} } ),
+    1,
+    'Payment response included one payment id'
+);
+
+$summary = fetch_billable_xact_summary($xact_id);
+is(
+    $summary->balance_owed,
+    '0.00',
+    'Remaining balance of 0.00 after payment'
+);
+
+### check-in the lost copy
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        is(
+            ref $item,
+            'Fieldmapper::asset::copy',
+            'open-ils.storage.direct.asset.copy.retrieve returned acp object'
+        );
+        is(
+            $item->status,
+            3,
+            'Item with id = ' . $item_id . ' has status of LOST'
+        );
+    }
+}
+
+$checkin_resp = $script->do_checkin_override({
+    barcode => $item_barcode});
+is(
+    $checkin_resp->{ilsevent},
+    0,
+    'Checkin returned a SUCCESS event'
+);
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        ok(
+            $item->status == 7 || $item->status == 0,
+            'Item with id = ' . $item_id . ' has status of Reshelving or Available after fresh Storage request'
+        );
+    }
+}
+
+### verify ending state
+
+$summary = fetch_billable_xact_summary($xact_id);
+is(
+    $summary->balance_owed,
+    '-50.00',
+    'Patron has a negative balance (credit) of 50.00 due to overpayment'
+);
+
+
+##############################
+# 2. Negative Balance Settings Are Unset, No Payment Made
+##############################
+
+### Setup use case variables
+$xact_id = 2;
+$item_id = 3;
+$item_barcode = 'CONC4000038';
+$org_id = 1; #CONS
+
+$summary = fetch_billable_xact_summary($xact_id);
+ok( $summary, 'CASE 2: Found the transaction summary');
+is(
+    $summary->balance_owed,
+    '50.00',
+    'Starting balance owed is 50.00 for lost item'
+);
+
+### check-in the lost copy
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        is(
+            $item->status,
+            3,
+            'Item with id = ' . $item_id . ' has status of LOST'
+        );
+    }
+}
+
+$checkin_resp = $script->do_checkin_override({
+    barcode => $item_barcode});
+is(
+    $checkin_resp->{ilsevent},
+    0,
+    'Checkin returned a SUCCESS event'
+);
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        ok(
+            $item->status == 7 || $item->status == 0,
+            'Item with id = ' . $item_id . ' has status of Reshelving or Available after fresh Storage request'
+        );
+    }
+}
+
+### verify ending state
+
+$summary = fetch_billable_xact_summary($xact_id);
+is(
+    $summary->balance_owed,
+    '0.00',
+    'Patron has a balance of 0.00'
+);
+
+
+##############################
+# 3. Basic No Negative Balance Test
+##############################
+
+### Setup use case variables
+$xact_id = 3;
+$item_id = 4;
+$item_barcode = 'CONC4000039';
+$org_id = 1; #CONS
+
+# Setup Org Unit Settings
+$settings = {
+    'bill.prohibit_negative_balance_default' => 1
+};
+$apputils->simplereq(
+    'open-ils.actor',
+    'open-ils.actor.org_unit.settings.update',
+    $script->authtoken,
+    $org_id,
+    $settings
+);
+
+$summary = fetch_billable_xact_summary($xact_id);
+ok( $summary, 'CASE 3: Found the transaction summary');
+is(
+    $summary->balance_owed,
+    '50.00',
+    'Starting balance owed is 50.00 for lost item'
+);
+
+### check-in the lost copy
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        is(
+            $item->status,
+            3,
+            'Item with id = ' . $item_id . ' has status of LOST'
+        );
+    }
+}
+
+$checkin_resp = $script->do_checkin_override({
+    barcode => $item_barcode});
+is(
+    $checkin_resp->{ilsevent},
+    0,
+    'Checkin returned a SUCCESS event'
+);
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        ok(
+            $item->status == 7 || $item->status == 0,
+            'Item with id = ' . $item_id . ' has status of Reshelving or Available after fresh Storage request'
+        );
+    }
+}
+
+### verify ending state
+
+$summary = fetch_billable_xact_summary($xact_id);
+is(
+    $summary->balance_owed,
+    '0.00',
+    'Patron has a balance of 0.00 (negative balance prohibited)'
+);
+
+##############################
+# 4. Prohibit Negative Balances with Partial Payment
+##############################
+
+### Setup use case variables
+$xact_id = 4;
+$item_id = 5;
+$item_barcode = 'CONC4000040';
+$org_id = 1; #CONS
+
+# Setup Org Unit Settings
+# already set: 'bill.prohibit_negative_balance_default' => 1
+
+$summary = fetch_billable_xact_summary($xact_id);
+ok( $summary, 'CASE 4: Found the transaction summary');
+is(
+    $summary->balance_owed,
+    '50.00',
+    'Starting balance owed is 50.00 for lost item'
+);
+
+### confirm the copy is lost
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        is(
+            $item->status,
+            3,
+            'Item with id = ' . $item_id . ' has status of LOST'
+        );
+    }
+}
+
+### partially pay the bill
+$payment_blob = {
+    userid => $patron_id,
+    note => '09-lp1198465_neg_balances.t',
+    payment_type => 'cash_payment',
+    patron_credit => '0.00',
+    payments => [ [ $xact_id, '10.00' ] ]
+};
+$pay_resp = pay_bills($payment_blob);
+
+is(
+    scalar( @{ $pay_resp->{payments} } ),
+    1,
+    'Payment response included one payment id'
+);
+
+$summary = fetch_billable_xact_summary($xact_id);
+is(
+    $summary->balance_owed,
+    '40.00',
+    'Remaining balance of 40.00 after payment'
+);
+
+### check-in the lost copy
+$checkin_resp = $script->do_checkin_override({
+    barcode => $item_barcode});
+is(
+    $checkin_resp->{ilsevent},
+    0,
+    'Checkin returned a SUCCESS event'
+);
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        ok(
+            $item->status == 7 || $item->status == 0,
+            'Item with id = ' . $item_id . ' has status of Reshelving or Available after fresh Storage request'
+        );
+    }
+}
+
+### verify ending state
+
+$summary = fetch_billable_xact_summary($xact_id);
+is(
+    $summary->balance_owed,
+    '0.00',
+    'Patron has a balance of 0.00 (negative balance prohibited)'
+);
+
+
+###############################
+## 11. Manually voiding lost book fee does not result in negative balances
+###############################
+#
+#### Setup use case variables
+#$xact_id = 5;
+#$item_id = 6;
+#$item_barcode = 'CONC4000040';
+#$org_id = 1; #CONS
+#
+## Setup Org Unit Settings
+## already set: 'bill.prohibit_negative_balance_default' => 1
+#
+#$summary = fetch_billable_xact_summary($xact_id);
+#ok( $summary, 'CASE 11: Found the transaction summary');
+#is(
+#    $summary->balance_owed,
+#    '50.00',
+#    'Starting balance owed is 50.00 for lost item'
+#);
+#
+#### confirm the copy is lost
+#$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+#if (my $item_resp = $item_req->recv) {
+#    if (my $item = $item_resp->content) {
+#        is(
+#            $item->status,
+#            3,
+#            'Item with id = ' . $item_id . ' has status of LOST'
+#        );
+#    }
+#}
+#
+#### partially pay the bill
+#$payment_blob = {
+#    userid => $patron_id,
+#    note => '09-lp1198465_neg_balances.t',
+#    payment_type => 'cash_payment',
+#    patron_credit => '0.00',
+#    payments => [ [ $xact_id, '10.00' ] ]
+#};
+#$pay_resp = pay_bills($payment_blob);
+#
+#is(
+#    scalar( @{ $pay_resp->{payments} } ),
+#    1,
+#    'Payment response included one payment id'
+#);
+#
+#$summary = fetch_billable_xact_summary($xact_id);
+#is(
+#    $summary->balance_owed,
+#    '40.00',
+#    'Remaining balance of 40.00 after payment'
+#);
+#
+#### TODO: manually void "the rest" of the bill (i.e. prevent neg bal)
+#### XXX: HARDCODING billing id for now; should look up the LOST bill for this xact?
+#my @billing_ids = (6);
+#my $void_resp = void_bills(\@billing_ids);
+#
+#is(
+#    $void_resp,
+#    '1',
+#    'Voiding was successful'
+#);
+#
+#### verify ending state
+#
+#$summary = fetch_billable_xact_summary($xact_id);
+#is(
+#    $summary->balance_owed,
+#    '0.00',
+#    'Patron has a balance of 0.00 (negative balance prohibited)'
+#);
+
+
+##############################
+# 12. Test negative balance settings on fines
+##############################
+
+# Setup next patron
+$patron_id = 5;
+$patron_usrname = '99999387993';
+
+# Look up the patron
+if ($user_obj = retrieve_patron($patron_id)) {
+    is(
+        ref $user_obj,
+        'Fieldmapper::actor::user',
+        'open-ils.storage.direct.actor.user.retrieve returned aou object'
+    );
+    is(
+        $user_obj->usrname,
+        $patron_usrname,
+        'Patron with id = ' . $patron_id . ' has username ' . $patron_usrname
+    );
+}
+
+### Setup use case variables
+$xact_id = 7;
+$item_id = 8;
+$item_barcode = 'CONC4000043';
+$org_id = 1; #CONS
+
+# Setup Org Unit Settings
+# already set: 'bill.prohibit_negative_balance_default' => 1
+
+$summary = fetch_billable_xact_summary($xact_id);
+ok( $summary, 'CASE 12: Found the transaction summary');
+is(
+    $summary->balance_owed,
+    '0.70',
+    'Starting balance owed is 0.70 for overdue fines'
+);
+
+### partially pay the bill
+$payment_blob = {
+    userid => $patron_id,
+    note => '09-lp1198465_neg_balances.t',
+    payment_type => 'cash_payment',
+    patron_credit => '0.00',
+    payments => [ [ $xact_id, '0.20' ] ]
+};
+$pay_resp = pay_bills($payment_blob);
+
+is(
+    scalar( @{ $pay_resp->{payments} } ),
+    1,
+    'Payment response included one payment id'
+);
+
+$summary = fetch_billable_xact_summary($xact_id);
+is(
+    $summary->balance_owed,
+    '0.50',
+    'Remaining balance of 0.50 after payment'
+);
+
+### Check in using Amnesty Mode
+$checkin_resp = $script->do_checkin_override({
+    barcode => $item_barcode,
+    void_overdues => 1
+});
+is(
+    $checkin_resp->{ilsevent},
+    0,
+    'Checkin returned a SUCCESS event'
+);
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        ok(
+            $item->status == 7 || $item->status == 0,
+            'Item with id = ' . $item_id . ' has status of Reshelving or Available after fresh Storage request'
+        );
+    }
+}
+
+### verify ending state
+$summary = fetch_billable_xact_summary($xact_id);
+is(
+    $summary->balance_owed,
+    '0.00',
+    'Patron has a balance of 0.00 (remaining fines forgiven)'
+);
+
+
+##############################
+# 10. Interval Testing
+##############################
+
+# Setup Org Unit Settings
+# already set: 'bill.prohibit_negative_balance_default' => 1
+
+# Setup Org Unit Settings
+$org_id = 1; #CONS
+$settings = {
+    'bill.negative_balance_interval_default' => '1 hour'
+};
+
+$apputils->simplereq(
+    'open-ils.actor',
+    'open-ils.actor.org_unit.settings.update',
+    $script->authtoken,
+    $org_id,
+    $settings
+);
+
+### Setup use case variables
+$xact_id = 8;
+$item_id = 9;
+$item_barcode = 'CONC4000044';
+
+$summary = fetch_billable_xact_summary($xact_id);
+ok( $summary, 'CASE 10.1: Found the transaction summary');
+is(
+    $summary->balance_owed,
+    '0.00',
+    'Starting balance owed is 0.00 (LOST fee paid)'
+);
+
+### Check in first item (right after its payment)
+$checkin_resp = $script->do_checkin_override({
+    barcode => $item_barcode,
+});
+is(
+    $checkin_resp->{ilsevent},
+    0,
+    'Checkin returned a SUCCESS event'
+);
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        ok(
+            $item->status == 7 || $item->status == 0,
+            'Item with id = ' . $item_id . ' has status of Reshelving or Available after fresh Storage request'
+        );
+    }
+}
+
+### verify ending state for 10.1
+$summary = fetch_billable_xact_summary($xact_id);
+is(
+    $summary->balance_owed,
+    '-50.00',
+    'Patron has a balance of -50.00 (lost item returned during interval)'
+);
+
+### Setup use case variables
+$xact_id = 9;
+$item_id = 10;
+$item_barcode = 'CONC4000045';
+
+$summary = fetch_billable_xact_summary($xact_id);
+ok( $summary, 'CASE 10.2: Found the transaction summary');
+is(
+    $summary->balance_owed,
+    '0.00',
+    'Starting balance owed is 0.00 (LOST fee paid)'
+);
+
+### Check in second item (2 hours after its payment)
+$checkin_resp = $script->do_checkin_override({
+    barcode => $item_barcode,
+});
+is(
+    $checkin_resp->{ilsevent},
+    0,
+    'Checkin returned a SUCCESS event'
+);
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        ok(
+            $item->status == 7 || $item->status == 0,
+            'Item with id = ' . $item_id . ' has status of Reshelving or Available after fresh Storage request'
+        );
+    }
+}
+
+### verify ending state
+$summary = fetch_billable_xact_summary($xact_id);
+is(
+    $summary->balance_owed,
+    '0.00',
+    'Patron has a balance of 0.00 (lost item returned after interval)'
+);
+
+
+#############################
+# 8. Restore Overdue Fines Appropriately, Previous Voids, Negative Balance Allowed
+#############################
+
+## TODO: consider using a later xact_id/item_id, instead of reverting back to user 4
+
+# Setup first patron (again)
+$patron_id = 4;
+$patron_usrname = '99999355250';
+
+# Look up the patron
+if ($user_obj = retrieve_patron($patron_id)) {
+    is(
+        ref $user_obj,
+        'Fieldmapper::actor::user',
+        'open-ils.storage.direct.actor.user.retrieve returned aou object'
+    );
+    is(
+        $user_obj->usrname,
+        $patron_usrname,
+        'Patron with id = ' . $patron_id . ' has username ' . $patron_usrname
+    );
+}
+
+### Setup use case variables
+$xact_id = 6;
+$item_id = 7;
+$item_barcode = 'CONC4000042';
+$org_id = 1; #CONS
+
+# Setup Org Unit Settings
+$settings = {
+    'bill.prohibit_negative_balance_default' => 0,
+    'circ.restore_overdue_on_lost_return' => 1,
+    'circ.lost.generate_overdue_on_checkin' => 1
+};
+
+$apputils->simplereq(
+    'open-ils.actor',
+    'open-ils.actor.org_unit.settings.update',
+    $script->authtoken,
+    $org_id,
+    $settings
+);
+
+$summary = fetch_billable_xact_summary($xact_id);
+ok( $summary, 'CASE 8: Found the transaction summary');
+is(
+    $summary->balance_owed,
+    '50.00',
+    'Starting balance owed is 50.00 for lost item'
+);
+
+### partially pay the bill
+$payment_blob = {
+    userid => $patron_id,
+    note => '09-lp1198465_neg_balances.t',
+    payment_type => 'cash_payment',
+    patron_credit => '0.00',
+    payments => [ [ $xact_id, '10.00' ] ]
+};
+$pay_resp = pay_bills($payment_blob);
+
+is(
+    scalar( @{ $pay_resp->{payments} } ),
+    1,
+    'Payment response included one payment id'
+);
+
+$summary = fetch_billable_xact_summary($xact_id);
+is(
+    $summary->balance_owed,
+    '40.00',
+    'Remaining balance of 40.00 after payment'
+);
+
+### check-in the lost copy
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        is(
+            ref $item,
+            'Fieldmapper::asset::copy',
+            'open-ils.storage.direct.asset.copy.retrieve returned acp object'
+        );
+        is(
+            $item->status,
+            3,
+            'Item with id = ' . $item_id . ' has status of LOST'
+        );
+    }
+}
+
+$checkin_resp = $script->do_checkin_override({
+    barcode => $item_barcode});
+is(
+    $checkin_resp->{ilsevent},
+    0,
+    'Checkin returned a SUCCESS event'
+);
+
+$item_req = $storage_ses->request('open-ils.storage.direct.asset.copy.retrieve', $item_id);
+if (my $item_resp = $item_req->recv) {
+    if (my $item = $item_resp->content) {
+        ok(
+            $item->status == 7 || $item->status == 0,
+            'Item with id = ' . $item_id . ' has status of Reshelving or Available after fresh Storage request'
+        );
+    }
+}
+
+### verify ending state
+
+$summary = fetch_billable_xact_summary($xact_id);
+is(
+    $summary->balance_owed,
+    '-7.00',
+    'Patron has a negative balance of 7.00 due to overpayment'
+);
+
+
+
+$script->logout();
+
+
diff --git a/Open-ILS/src/sql/Pg/live_t/lp1198465_run_this_before_livetests.sql b/Open-ILS/src/sql/Pg/live_t/lp1198465_run_this_before_livetests.sql
new file mode 100644
index 0000000..b257b85
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/live_t/lp1198465_run_this_before_livetests.sql
@@ -0,0 +1,81 @@
+BEGIN;
+
+-- DATA FOR LIVE TESTING LP#1198465:
+--   Support for Conditional Negative Balances
+--
+-- Assume stock data has been loaded.
+--
+-- Dates are relative when necessary; otherwise they may be hardcoded.
+-- NOTE: Org unit settings will be handled in the perl code
+
+
+-- user id: 4, name: Gregory Jones
+
+-- Setup all LOST circs
+UPDATE action.circulation SET
+    xact_start = '2014-05-14 08:39:13.070326-04',
+	due_date = '2014-05-21 23:59:59-04',
+	stop_fines_time = '2014-05-28 08:39:13.070326-04',
+	create_time = '2014-05-14 08:39:13.070326-04',
+	max_fine = '3.00',
+	stop_fines = 'LOST'
+WHERE id >= 1 AND id <= 6;
+UPDATE asset.copy SET status = 3 WHERE id >= 2 AND id <= 7;
+
+-- Setup non-lost circ
+UPDATE action.circulation SET
+	checkin_staff = NULL,
+	checkin_lib = NULL,
+	checkin_time = NULL,
+	checkin_scan_time = NULL,
+	stop_fines = NULL,
+	stop_fines_time = NULL
+WHERE id = 7;
+UPDATE asset.copy SET status = 1 WHERE id = 8;
+
+-- Setup other LOST and overdue fines
+INSERT INTO money.billing (id, xact, billing_ts, voided, voider, void_time, amount, billing_type, btype, note) VALUES
+    (DEFAULT, 1, '2014-05-28 08:39:13.070326-04', false, NULL, NULL, 50.00, 'Lost Materials', 3, 'SYSTEM GENERATED'),
+    (DEFAULT, 2, '2014-05-28 08:39:13.070326-04', false, NULL, NULL, 50.00, 'Lost Materials', 3, 'SYSTEM GENERATED'),
+    (DEFAULT, 3, '2014-05-28 08:39:13.070326-04', false, NULL, NULL, 50.00, 'Lost Materials', 3, 'SYSTEM GENERATED'),
+    (DEFAULT, 4, '2014-05-28 08:39:13.070326-04', false, NULL, NULL, 50.00, 'Lost Materials', 3, 'SYSTEM GENERATED'),
+    (DEFAULT, 5, '2014-05-28 08:39:13.070326-04', false, NULL, NULL, 50.00, 'Lost Materials', 3, 'SYSTEM GENERATED'),
+    (DEFAULT, 6, '2014-05-22 23:59:59-04', true, 1, '2014-05-28 08:39:13.070326-04', 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 6, '2014-05-23 23:59:59-04', true, 1, '2014-05-28 08:39:13.070326-04', 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 6, '2014-05-24 23:59:59-04', true, 1, '2014-05-28 08:39:13.070326-04', 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 6, '2014-05-25 23:59:59-04', true, 1, '2014-05-28 08:39:13.070326-04', 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 6, '2014-05-26 23:59:59-04', true, 1, '2014-05-28 08:39:13.070326-04', 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 6, '2014-05-27 23:59:59-04', true, 1, '2014-05-28 08:39:13.070326-04', 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 6, '2014-05-28 23:59:59-04', true, 1, '2014-05-28 08:39:13.070326-04', 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 6, '2014-05-28 08:39:13.070326-04', false, NULL, NULL, 50.00, 'Lost Materials', 3, 'SYSTEM GENERATED'),
+    (DEFAULT, 7, '2014-05-22 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 7, '2014-05-23 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 7, '2014-05-24 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 7, '2014-05-25 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 7, '2014-05-26 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 7, '2014-05-27 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine'),
+    (DEFAULT, 7, '2014-05-28 23:59:59-04', false, NULL, NULL, 0.10, 'Overdue materials', 1, 'System Generated Overdue Fine');
+
+-- Setup two recently LOST items for Case 10: Interval Testing
+-- - Item 1: Lost, paid more than 1 hour later, returned less than 1 hour after payment (via perl test)
+-- - Item 2: Lost, paid more than 1 hour later, returned more than 1 hour after payment (via perl test)
+UPDATE action.circulation SET
+	create_time = NOW() - '1 week'::interval,
+    xact_start = NOW() - '1 week'::interval,
+	due_date = NOW() + '1 day'::interval,
+	stop_fines_time = NOW() - '3 hours'::interval,
+	max_fine = '3.00',
+	stop_fines = 'LOST'
+WHERE id IN (8, 9);
+UPDATE asset.copy SET status = 3 WHERE id IN (9, 10);
+
+INSERT INTO money.billing (id, xact, billing_ts, voided, voider, void_time, amount, billing_type, btype, note) VALUES
+    (DEFAULT, 8, NOW() - '2 hours'::interval, false, NULL, NULL, 50.00, 'Lost Materials', 3, 'SYSTEM GENERATED'),
+    (DEFAULT, 9, NOW() - '4 hours'::interval, false, NULL, NULL, 50.00, 'Lost Materials', 3, 'SYSTEM GENERATED');
+
+INSERT INTO money.payment (id, xact, payment_ts, voided, amount, note) VALUES
+	(DEFAULT, 8, NOW() - '30 minutes'::interval, false, 50.00, 'LOST payment'),
+	(DEFAULT, 9, NOW() - '2 hours'::interval, false, 50.00, 'LOST payment');
+
+
+COMMIT;

commit 7c8e241b3c169536ab25485988c0efabde420782
Author: Dan Wells <dbw2 at calvin.edu>
Date:   Thu May 28 14:10:50 2015 -0400

    LP 1198465: Clean up terminology
    
    Replace the "void payment" language with "adjustment" in variable names
    and comments.  This commit should not make any functional difference.
    
    Signed-off-by: Dan Wells <dbw2 at calvin.edu>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Ben Shum <bshum at biblio.org>

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 541a5a8..572f5d9 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm
@@ -680,12 +680,12 @@ sub generate_fines {
 # following fields:
 #
 # bill => the adjusted bill object
-# voids => an arrayref of void payments that apply directly to the
-#          bill
+# adjustments => an arrayref of adjustment payments that apply directly
+#                to the bill
 # payments => an arrayref of payment objects applied to the bill
 # bill_amount => original amount from the billing object
-# void_amount => total of the void payments that apply directly to the
-#                bill
+# adjustment_amount => total of the adjustment payments that apply
+#                      directly to the bill
 #
 # Each bill is only mapped to payments one time.  However, a single
 # payment may be mapped to more than one bill if the payment amount is
@@ -736,13 +736,13 @@ sub bill_payment_map_for_xact {
             bill => $_,
             bill_amount => $_->amount(),
             payments => [],
-            voids => [],
-            void_amount => 0
+            adjustments => [],
+            adjustment_amount => 0
         }
     } @$bills;
 
-    # Find all unvoided payments in order.  Flesh voids so that we
-    # don't have to retrieve them later.
+    # Find all unvoided payments in order.  Flesh adjustment payments
+    # so that we don't have to retrieve them later.
     my $payments = $e->search_money_payment(
         [
             { xact => $xact->id, voided=>'f' },
@@ -760,39 +760,39 @@ sub bill_payment_map_for_xact {
     # Now, we go through the rigmarole of mapping payments to bills
     # and adjusting the bill balances.
 
-    # Apply the voids before "paying" other bills.
+    # Apply the adjustments before "paying" other bills.
     foreach my $entry (@entries) {
         my $bill = $entry->{bill};
-        # Find only the voids that apply to individual bills.
-        my @voids = map {$_->adjustment_payment()} grep {$_->payment_type() eq 'adjustment_payment' && $_->adjustment_payment()->billing() == $bill->id()} @$payments;
-        if (@voids) {
-            foreach my $void (@voids) {
-                my $new_amount = $U->fpdiff($bill->amount(),$void->amount());
+        # Find only the adjustments that apply to individual bills.
+        my @adjustments = map {$_->adjustment_payment()} grep {$_->payment_type() eq 'adjustment_payment' && $_->adjustment_payment()->billing() == $bill->id()} @$payments;
+        if (@adjustments) {
+            foreach my $adjustment (@adjustments) {
+                my $new_amount = $U->fpdiff($bill->amount(),$adjustment->amount());
                 if ($new_amount >= 0) {
-                    push @{$entry->{voids}}, $void;
-                    $entry->{void_amount} += $void->amount();
+                    push @{$entry->{adjustments}}, $adjustment;
+                    $entry->{adjustment_amount} += $adjustment->amount();
                     $bill->amount($new_amount);
-                    # Remove the used up void from list of payments:
-                    my @p = grep {$_->id() != $void->id()} @$payments;
+                    # Remove the used up adjustment from list of payments:
+                    my @p = grep {$_->id() != $adjustment->id()} @$payments;
                     $payments = \@p;
                 } else {
-                    # It should never happen that we have more void
+                    # It should never happen that we have more adjustment
                     # payments on a single bill than the amount of the
                     # bill.  However, experience shows that the things
                     # that should never happen actually do happen with
                     # surprising regularity in a library setting.
 
-                    # Clone the void to say how much of it actually
+                    # Clone the adjustment to say how much of it actually
                     # applied to this bill.
-                    my $new_void = $void->clone();
-                    $new_void->amount($bill->amount());
-                    $new_void->amount_collected($bill->amount());
-                    push (@{$entry->{voids}}, $new_void);
-                    $entry->{void_amount} += $new_void->amount();
+                    my $new_adjustment = $adjustment->clone();
+                    $new_adjustment->amount($bill->amount());
+                    $new_adjustment->amount_collected($bill->amount());
+                    push (@{$entry->{adjustments}}, $new_adjustment);
+                    $entry->{adjustment_amount} += $new_adjustment->amount();
                     $bill->amount(0);
-                    $void->amount(-$new_amount);
+                    $adjustment->amount(-$new_amount);
                     # Could be a candidate for YAOUS about what to do
-                    # with excess void amounts on a bill.
+                    # with excess adjustment amounts on a bill.
                 }
                 last if ($bill->amount() == 0);
             }
@@ -952,7 +952,7 @@ sub adjust_bills_to_zero {
         # Handle each bill in turn.
         foreach my $bill (@xact_bills) {
             # As the total open amount on the transaction will change
-            # as each bill is voided, we'll just recalculate it for
+            # as each bill is adjusted, we'll just recalculate it for
             # each bill.
             my $xact_total = 0;
             map {$xact_total += $_->{bill}->amount()} @$bpmap;
@@ -965,27 +965,27 @@ sub adjust_bills_to_zero {
             # payment map entry.
             $bill = $bpentry->{bill};
 
-            # The amount to void is the non-voided balance on the
+            # The amount to adjust is the non-adjusted balance on the
             # bill. It should never be less than zero.
-            my $amount_to_void = $U->fpdiff($bpentry->{bill_amount},$bpentry->{void_amount});
+            my $amount_to_adjust = $U->fpdiff($bpentry->{bill_amount},$bpentry->{adjustment_amount});
 
-            # Check if this bill is already voided.  We don't allow
-            # "double" voids regardless of settings.
-            if ($amount_to_void <= 0) {
+            # Check if this bill is already adjusted.  We don't allow
+            # "double" adjustments regardless of settings.
+            if ($amount_to_adjust <= 0) {
                 #my $event = OpenILS::Event->new('BILL_ALREADY_VOIDED', payload => $bill);
                 #$e->event($event);
                 #return $event;
                 next;
             }
 
-            if ($amount_to_void > $xact_total) {
-                $amount_to_void = $xact_total;
+            if ($amount_to_adjust > $xact_total) {
+                $amount_to_adjust = $xact_total;
             }
 
             # Create the adjustment payment
             my $payobj = Fieldmapper::money::adjustment_payment->new;
-            $payobj->amount($amount_to_void);
-            $payobj->amount_collected($amount_to_void);
+            $payobj->amount($amount_to_adjust);
+            $payobj->amount_collected($amount_to_adjust);
             $payobj->xact($xactid);
             $payobj->accepting_usr($e->requestor->id);
             $payobj->payment_ts('now');
@@ -993,10 +993,10 @@ sub adjust_bills_to_zero {
             $payobj->note($note) if ($note);
             $e->create_money_adjustment_payment($payobj) or return $e->die_event;
             # Adjust our bill_payment_map
-            $bpentry->{void_amount} += $amount_to_void;
-            push @{$bpentry->{voids}}, $payobj;
+            $bpentry->{adjustment_amount} += $amount_to_adjust;
+            push @{$bpentry->{adjustments}}, $payobj;
             # Should come to zero:
-            my $new_bill_amount = $U->fpdiff($bill->amount(),$amount_to_void);
+            my $new_bill_amount = $U->fpdiff($bill->amount(),$amount_to_adjust);
             $bill->amount($new_bill_amount);
         }
 

commit 75b8b2b0a5690e6362fb51970ae3b0f0e8910b8f
Author: Dan Wells <dbw2 at calvin.edu>
Date:   Thu May 28 13:54:26 2015 -0400

    LP 1198465: Set restored overdue timestamp to time of last overdue
    
    When we have our settings configured to generate new overdues on lost
    item return, we start generation after the most recent overdue fine.
    Because of this, we need the restored fine to be dated in the past,
    which in turn allows the fine generator to apply catch-up fines as
    expected.
    
    Signed-off-by: Dan Wells <dbw2 at calvin.edu>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Ben Shum <bshum at biblio.org>

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 4dac75e..541a5a8 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm
@@ -198,7 +198,7 @@ sub reopen_xact {
 
 
 sub create_bill {
-    my($class, $e, $amount, $btype, $type, $xactid, $note) = @_;
+    my($class, $e, $amount, $btype, $type, $xactid, $note, $billing_ts) = @_;
 
     $logger->info("The system is charging $amount [$type] on xact $xactid");
     $note ||= 'SYSTEM GENERATED';
@@ -208,6 +208,7 @@ sub create_bill {
     my $bill = Fieldmapper::money::billing->new;
     $bill->xact($xactid);
     $bill->amount($amount);
+    $bill->billing_ts($billing_ts);
     $bill->billing_type($type); 
     $bill->btype($btype); 
     $bill->note($note);
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 44719ef..9b20a97 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm
@@ -3813,12 +3813,15 @@ sub checkin_handle_lost_or_lo_now_found_restore_od {
     # restore those overdue charges voided when item was set to lost
     # ------------------------------------------------------------------
 
-    my $ods = $self->editor->search_money_billing(
+    my $ods = $self->editor->search_money_billing([
         {
             xact => $self->circ->id,
             btype => 1
+        },
+        {
+            order_by => {mb => 'billing_ts desc'}
         }
-    );
+    ]);
 
     $logger->debug("returning ".scalar(@$ods)." overdue charges pre-$tag");
     # Because actual users get up to all kinds of unexpectedness, we
@@ -3852,7 +3855,8 @@ sub checkin_handle_lost_or_lo_now_found_restore_od {
             $ods->[0]->btype(),
             $ods->[0]->billing_type(),
             $self->circ->id(),
-            "System: $tag RETURNED - OVERDUES REINSTATED"
+            "System: $tag RETURNED - OVERDUES REINSTATED",
+            $ods->[0]->billing_ts() # date this restoration the same as the last overdue (for possible subsequent fine generation)
         );
     }
 }

commit 57b4f77ff2cecee2b40d59caa98069a486cc4814
Author: Remington Steed <rjs7 at calvin.edu>
Date:   Wed Jun 18 09:34:13 2014 -0400

    LP 1198465: Account for overdues voided the old way
    
    The code for the particular case of "void"-overdues-for-lost is incomplete.
    This is the one case in the new code where it now always adjusts rather
    than voids, but the new restore code still needs to recognize cases where
    the voiding happened under the old code.
    
    Signed-off-by: Remington Steed <rjs7 at calvin.edu>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Ben Shum <bshum at biblio.org>

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 20ff28d..4dac75e 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm
@@ -708,7 +708,7 @@ sub bill_payment_map_for_xact {
 
     # find all bills in order
     my $bill_search = [
-        {xact => $xact->id()},
+        { xact => $xact->id(), voided => 'f' },
         { order_by => { mb => { billing_ts => { direction => 'asc' } } } },
     ];
 
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 6809d68..44719ef 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm
@@ -3829,13 +3829,23 @@ sub checkin_handle_lost_or_lo_now_found_restore_od {
     if ($ods && @$ods) {
         my $void_amount = 0;
         my $void_max = $self->circ->max_fine();
+        # search for overdues voided the new way (aka "adjusted")
         my @billings = map {$_->id()} @$ods;
         my $voids = $self->editor->search_money_adjustment_payment(
             {
                 billing => \@billings
             }
         );
-        map {$void_amount += $_->amount()} @$voids;
+        if (@$voids) {
+            map {$void_amount += $_->amount()} @$voids;
+        } else {
+            # if no adjustments found, assume they were voided the old way (aka "voided")
+            for my $bill (@$ods) {
+                if( $U->is_true($bill->voided) ) {
+                    $void_amount += $bill->amount();
+                }
+            }
+        }
         $CC->create_bill(
             $self->editor,
             ($void_amount < $void_max ? $void_amount : $void_max),

commit b933439a93e3465fc36a685d261e97a44d8cef7b
Author: Dan Wells <dbw2 at calvin.edu>
Date:   Tue Apr 1 15:13:00 2014 -0400

    LP 1198465: Delay creation of bill map for special bill handling
    
    We were making the map, then deciding whether we wanted to void or
    adjust.  Since we only want the map if we adjust (and it creates havoc
    when voiding), let's just get the bill IDs, then let the adjustment
    code create the map it needs.
    
    Signed-off-by: Dan Wells <dbw2 at calvin.edu>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Ben Shum <bshum at biblio.org>

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 faa6651..20ff28d 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm
@@ -142,35 +142,33 @@ sub void_lost {
 sub void_or_zero_bills_of_type {
     my ($class, $e, $circ, $copy, $btype, $for_note) = @_;
 
-    # Get a bill payment map.
-    my $bpmap = $class->bill_payment_map_for_xact($e, $circ);
-    if ($bpmap && @$bpmap) {
-        # Filter out the unvoided bills of the type we're looking for:
-        my @bills = map {$_->{bill}} grep { $_->{bill}->btype() == $btype && $_->{bill_amount} > $_->{void_amount} } @$bpmap;
-        if (@bills) {
-            # settings for lost come from copy circlib.
-            my $prohibit_neg_balance_lost = (
-                $U->ou_ancestor_setting_value($copy->circ_lib(), 'bill.prohibit_negative_balance_on_lost')
-                ||
-                $U->ou_ancestor_setting_value($copy->circ_lib(), 'bill.prohibit_negative_balance_default')
-            );
-            my $neg_balance_interval_lost = (
-                $U->ou_ancestor_setting_value($copy->circ_lib(), 'bill.negative_balance_interval_on_lost')
-                ||
-                $U->ou_ancestor_setting_value($copy->circ_lib(), 'bill.negative_balance_interval_default')
-            );
-            my $result;
-            if (
-                $U->is_true($prohibit_neg_balance_lost)
-                and !_has_refundable_payments($e, $circ->id, $neg_balance_interval_lost)
-            ) {
-                $result = $class->adjust_bills_to_zero($e, \@bills, "System: ADJUSTED $for_note");
-            } else {
-                $result = $class->void_bills($e, \@bills, "System: VOIDED $for_note");
-            }
-            if (ref($result)) {
-                return $result;
-            }
+    my $billids = $e->search_money_billing(
+        {xact => $circ->id(), btype => $btype},
+        {idlist=>1}
+    );
+    if ($billids && @$billids) {
+        # settings for lost come from copy circlib.
+        my $prohibit_neg_balance_lost = (
+            $U->ou_ancestor_setting_value($copy->circ_lib(), 'bill.prohibit_negative_balance_on_lost')
+            ||
+            $U->ou_ancestor_setting_value($copy->circ_lib(), 'bill.prohibit_negative_balance_default')
+        );
+        my $neg_balance_interval_lost = (
+            $U->ou_ancestor_setting_value($copy->circ_lib(), 'bill.negative_balance_interval_on_lost')
+            ||
+            $U->ou_ancestor_setting_value($copy->circ_lib(), 'bill.negative_balance_interval_default')
+        );
+        my $result;
+        if (
+            $U->is_true($prohibit_neg_balance_lost)
+            and !_has_refundable_payments($e, $circ->id, $neg_balance_interval_lost)
+        ) {
+            $result = $class->adjust_bills_to_zero($e, $billids, "System: ADJUSTED $for_note");
+        } else {
+            $result = $class->void_bills($e, $billids, "System: VOIDED $for_note");
+        }
+        if (ref($result)) {
+            return $result;
         }
     }
 

commit 8bd2ba53bf0ba7fb4de2a6c84cebdce6b91c0841
Author: Dan Wells <dbw2 at calvin.edu>
Date:   Wed Feb 26 17:44:45 2014 -0500

    LP 1198465: Fix and improve void/adjustment code
    
    This commit does three things:
    
    - Replace ou_ancestor_setting() with ou_ancestor_setting_value() calls
      This also fixed a bug where we were expecting just the setting, not
      a HASH
    
    - Reword interval checking
      This fix is two part.  First, we simplify the check to not require
      the whole payment map.  Second, we use this newfound simplicity to
      push this check up into the gatekeeper functions, further clarifying
      the code paths.
    
    - make $note into $for_note for void_or_zero_bills_of_type()
      Because the function can both void and adjust, we can't supply a
      complete note, so let's just supply text of what the void/adjustment
      is for.
    
    Signed-off-by: Dan Wells <dbw2 at calvin.edu>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Ben Shum <bshum at biblio.org>

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 23ac20a..faa6651 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm
@@ -67,22 +67,26 @@ sub void_or_zero_overdues {
     if ($billids && @$billids) {
         # overdue settings come from transaction org unit
         my $prohibit_neg_balance_overdues = (
-            $U->ou_ancestor_setting($circ->circ_lib(), 'bill.prohibit_negative_balance_on_overdues')
+            $U->ou_ancestor_setting_value($circ->circ_lib(), 'bill.prohibit_negative_balance_on_overdues')
             ||
-            $U->ou_ancestor_setting($circ->circ_lib(), 'bill.prohibit_negative_balance_default')
+            $U->ou_ancestor_setting_value($circ->circ_lib(), 'bill.prohibit_negative_balance_default')
         );
         my $neg_balance_interval_overdues = (
-            $U->ou_ancestor_setting($circ->circ_lib(), 'bill.negative_balance_interval_on_overdues')
+            $U->ou_ancestor_setting_value($circ->circ_lib(), 'bill.negative_balance_interval_on_overdues')
             ||
-            $U->ou_ancestor_setting($circ->circ_lib(), 'bill.negative_balance_interval_default')
+            $U->ou_ancestor_setting_value($circ->circ_lib(), 'bill.negative_balance_interval_default')
         );
         my $result;
-        # if we prohibit negative overdue balances outright, OR we have an
-        # interval setting which determines what we do, let
-        # adjust_bills_to_zero() do the heavy lifting
+        # if we prohibit negative overdue balances and all payments
+        # are outside the refund interval (if given), zero the transaction
         if ($opts->{force_zero}
-            or (!$opts->{force_void} and ($U->is_true($prohibit_neg_balance_overdues) or $neg_balance_interval_overdues))
-            ) {
+            or (!$opts->{force_void}
+                and (
+                    $U->is_true($prohibit_neg_balance_overdues)
+                    and !_has_refundable_payments($e, $circ->id, $neg_balance_interval_overdues)
+                )
+            )
+        ) {
             $result = $class->adjust_bills_to_zero($e, $billids, $opts->{note}, $neg_balance_interval_overdues);
         } else {
             # otherwise, just void the usual way
@@ -136,7 +140,7 @@ sub void_lost {
 # Returns undef on success or the result from void_bills.
 # ------------------------------------------------------------------
 sub void_or_zero_bills_of_type {
-    my ($class, $e, $circ, $copy, $btype, $note) = @_;
+    my ($class, $e, $circ, $copy, $btype, $for_note) = @_;
 
     # Get a bill payment map.
     my $bpmap = $class->bill_payment_map_for_xact($e, $circ);
@@ -146,16 +150,24 @@ sub void_or_zero_bills_of_type {
         if (@bills) {
             # settings for lost come from copy circlib.
             my $prohibit_neg_balance_lost = (
-                $U->ou_ancestor_setting($copy->circ_lib(), 'bill.prohibit_negative_balance_on_lost')
+                $U->ou_ancestor_setting_value($copy->circ_lib(), 'bill.prohibit_negative_balance_on_lost')
                 ||
-                $U->ou_ancestor_setting($copy->circ_lib(), 'bill.prohibit_negative_balance_default')
+                $U->ou_ancestor_setting_value($copy->circ_lib(), 'bill.prohibit_negative_balance_default')
             );
             my $neg_balance_interval_lost = (
-                $U->ou_ancestor_setting($copy->circ_lib(), 'bill.negative_balance_interval_on_lost')
+                $U->ou_ancestor_setting_value($copy->circ_lib(), 'bill.negative_balance_interval_on_lost')
                 ||
-                $U->ou_ancestor_setting($copy->circ_lib(), 'bill.negative_balance_interval_default')
+                $U->ou_ancestor_setting_value($copy->circ_lib(), 'bill.negative_balance_interval_default')
             );
-            my $result = $class->void_bills($e, \@bills, $note);
+            my $result;
+            if (
+                $U->is_true($prohibit_neg_balance_lost)
+                and !_has_refundable_payments($e, $circ->id, $neg_balance_interval_lost)
+            ) {
+                $result = $class->adjust_bills_to_zero($e, \@bills, "System: ADJUSTED $for_note");
+            } else {
+                $result = $class->void_bills($e, \@bills, "System: VOIDED $for_note");
+            }
             if (ref($result)) {
                 return $result;
             }
@@ -889,10 +901,9 @@ sub void_bills {
 
 
 # This subroutine actually handles "adjusting" bills to zero.  It takes a
-# CStoreEditor, an arrayref of bill ids or bills, an optional note, and an
-# optional interval.
+# CStoreEditor, an arrayref of bill ids or bills, and an optional note.
 sub adjust_bills_to_zero {
-    my ($class, $e, $billids, $note, $interval) = @_;
+    my ($class, $e, $billids, $note) = @_;
 
     # Get with the editor to see if we have permission to void bills.
     return $e->die_event unless $e->checkauth;
@@ -950,10 +961,6 @@ sub adjust_bills_to_zero {
 
             # Get the bill_payment_map entry for this bill:
             my ($bpentry) = grep {$_->{bill}->id() == $bill->id()} @$bpmap;
-            if (_within_refund_interval($bpentry, $interval)) {
-#TODO
-                next;
-            }
 
             # From here on out, use the bill object from the bill
             # payment map entry.
@@ -1012,38 +1019,34 @@ sub adjust_bills_to_zero {
     return 1;
 }
 
-# A helper function to check if the payments on a bill are within the
-# range of a given interval.  The first argument is the entry hash
-# from the bill payment map for the bill to check and the second
-# argument is the interval.  It returns true (1) if any of the bills
-# are within range of the interval, or false (0) otherwise.  It also
-# returns true if the interval argument is undefined or empty, or if
-# the bill has no payments whatsoever.  It will return false if the
-# entry has no payments other than voids.
-sub _within_refund_interval {
-    my ($entry, $interval) = @_;
-    my $result = ($interval ? 0 : 1);
-
-    # A check to see if we were given the settings hash or the value:
-    if (ref($interval) eq 'HASH') {
-        $interval = $interval->{value};
-    }
+# A helper function to check if the payments on a bill are inside the
+# range of a given interval.
+# TODO: here is one simple place we could do voids in the absence
+# of any payments
+sub _has_refundable_payments {
+    my ($e, $xactid, $interval) = @_;
 
-    if ($interval && $entry && $entry->{payments} && @{$entry->{payments}}) {
-        my $interval_secs = interval_to_seconds($interval);
-        my @pay_dates = map {$_->payment_ts()} sort {$b->payment_ts() cmp $a->payment_ts()}  grep {$_->payment_type() ne 'adjustment_payment'} @{$entry->{payments}};
-        if (@pay_dates) {
-            # Since we've sorted the payment dates from highest to
-            # lowest, we really only need to check the 0th one.
-            my $payment_date = DateTime::Format::ISO8601->parse_datetime(cleanse_ISO8601($pay_dates[0]))->epoch;
-            my $now = time;
-            $result = 1 if ($payment_date + $interval_secs >= $now);
+    # for now, just short-circuit with no interval
+    return 0 if (!$interval);
+
+    my $last_payment = $e->search_money_payment(
+        {
+            xact => $xactid,
+            payment_type => {"!=" => 'adjustment_payment'}
+        },{
+            limit => 1,
+            order_by => { mp => "payment_ts DESC" }
         }
-    } elsif ($interval && (!$entry->{payments} || !@{$entry->{payments}})) {
-        $result = 1;
+    );
+
+    if ($last_payment->[0]) {
+        my $interval_secs = interval_to_seconds($interval);
+        my $payment_ts = DateTime::Format::ISO8601->parse_datetime(cleanse_ISO8601($last_payment->[0]->payment_ts))->epoch;
+        my $now = time;
+        return 1 if ($payment_ts + $interval_secs >= $now);
     }
 
-    return $result;
+    return 0;
 }
 
 1;
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 60a0b5c..6809d68 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm
@@ -3799,7 +3799,7 @@ sub checkin_handle_lost_or_lo_now_found {
     my $tag = $is_longoverdue ? "LONGOVERDUE" : "LOST";
 
     $logger->debug("voiding $tag item billings");
-    my $result = $CC->void_or_zero_bills_of_type($self->editor, $self->circ, $self->copy, $bill_type, "System: VOIDED FOR $tag ITEM RETURNED");
+    my $result = $CC->void_or_zero_bills_of_type($self->editor, $self->circ, $self->copy, $bill_type, "$tag ITEM RETURNED");
     $self->bail_on_events($self->editor->event) if ($result);
 }
 

commit 7d8686135e65942b3a474e9bff6925f026a2023b
Author: Dan Wells <dbw2 at calvin.edu>
Date:   Wed Feb 26 11:12:21 2014 -0500

    LP 1198465: Refactor logic into gatekeeper functions
    
    The bulk of this commits take the logic from adjust_bills_to_zero() and
    moves it up a layer into the "gatekeeper" void_or_zero* functions.
    This move also allows us to simplify the logic, since some facts are
    already known based on our function path.
    
    Also:
    - give void_or_zero_overdues() a new signature to better support
      multiple options
    - add new 'force_void' and 'force_zero' options to this function
    - rename real_void_bills() to simply void_bills() (since there is no
      other void_bills(), the "real" was redundant)
    
    Signed-off-by: Dan Wells <dbw2 at calvin.edu>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Ben Shum <bshum at biblio.org>

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 0de7fd7..2dcc62d 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat/AssetCommon.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat/AssetCommon.pm
@@ -743,9 +743,9 @@ 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
+    # zero out overdue fines on this circ if configured
     if( $void_overdue ) {
-        my $evt = OpenILS::Application::Circ::CircCommon->void_or_zero_overdues($e, $circ);
+        my $evt = OpenILS::Application::Circ::CircCommon->void_or_zero_overdues($e, $circ, {force_zero => 1});
         return $evt if $evt;
     }
 
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm
index 1aca1de..3e438ce 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm
@@ -461,7 +461,7 @@ sub set_circ_claims_returned {
 
         # make it look like the circ stopped at the cliams returned time
         $circ->stop_fines_time($backdate);
-        my $evt = OpenILS::Application::Circ::CircCommon->void_or_zero_overdues($e, $circ, $backdate);
+        my $evt = OpenILS::Application::Circ::CircCommon->void_or_zero_overdues($e, $circ, {backdate => $backdate, note => 'System: OVERDUE REVERSED FOR CLAIMS-RETURNED', force_zero => 1});
         return $evt if $evt;
     }
 
@@ -604,7 +604,7 @@ sub post_checkin_backdate_circ_impl {
     $e->update_action_circulation($circ) or return $e->die_event;
 
     # now void the overdues "erased" by the back-dating
-    my $evt = OpenILS::Application::Circ::CircCommon->void_or_zero_overdues($e, $circ, $backdate);
+    my $evt = OpenILS::Application::Circ::CircCommon->void_or_zero_overdues($e, $circ, {backdate => $backdate});
     return $evt if $evt;
 
     # If the circ was closed before and the balance owned !=0, re-open the transaction
@@ -1331,7 +1331,7 @@ sub handle_mark_damaged {
         # the assumption is that you would not void the overdues unless you 
         # were also charging for the item and/or applying a processing fee
         if($void_overdue) {
-            my $evt = OpenILS::Application::Circ::CircCommon->void_or_zero_overdues($e, $circ);
+            my $evt = OpenILS::Application::Circ::CircCommon->void_or_zero_overdues($e, $circ, {note => 'System: OVERDUE REVERSED FOR DAMAGE CHARGE'});
             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 9a00513..23ac20a 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm
@@ -25,15 +25,19 @@ my $parser = DateTime::Format::ISO8601->new;
 # backdate is to within the grace period, in which case we void all
 # overdue fines.
 # -----------------------------------------------------------------
+sub void_overdues {
+#compatibility layer - TODO
+}
 sub void_or_zero_overdues {
-    my($class, $e, $circ, $backdate, $note) = @_;
+    my($class, $e, $circ, $opts) = @_;
 
     my $bill_search = { 
         xact => $circ->id, 
         btype => 1 
     };
 
-    if( $backdate ) {
+    if( $opts->{backdate} ) {
+        my $backdate = $opts->{backdate};
         # ------------------------------------------------------------------
         # Fines for overdue materials are assessed up to, but not including,
         # one fine interval after the fines are applicable.  Here, we add
@@ -61,7 +65,29 @@ sub void_or_zero_overdues {
 
     my $billids = $e->search_money_billing([$bill_search, {idlist=>1}]);
     if ($billids && @$billids) {
-        my $result = $class->real_void_bills($e, $billids, $note);
+        # overdue settings come from transaction org unit
+        my $prohibit_neg_balance_overdues = (
+            $U->ou_ancestor_setting($circ->circ_lib(), 'bill.prohibit_negative_balance_on_overdues')
+            ||
+            $U->ou_ancestor_setting($circ->circ_lib(), 'bill.prohibit_negative_balance_default')
+        );
+        my $neg_balance_interval_overdues = (
+            $U->ou_ancestor_setting($circ->circ_lib(), 'bill.negative_balance_interval_on_overdues')
+            ||
+            $U->ou_ancestor_setting($circ->circ_lib(), 'bill.negative_balance_interval_default')
+        );
+        my $result;
+        # if we prohibit negative overdue balances outright, OR we have an
+        # interval setting which determines what we do, let
+        # adjust_bills_to_zero() do the heavy lifting
+        if ($opts->{force_zero}
+            or (!$opts->{force_void} and ($U->is_true($prohibit_neg_balance_overdues) or $neg_balance_interval_overdues))
+            ) {
+            $result = $class->adjust_bills_to_zero($e, $billids, $opts->{note}, $neg_balance_interval_overdues);
+        } else {
+            # otherwise, just void the usual way
+            $result = $class->void_bills($e, $billids, $opts->{note});
+        }
         if (ref($result)) {
             return $result;
         }
@@ -107,10 +133,10 @@ sub void_lost {
 # Takes an editor, a circ object, the btype number for the bills you
 # want to void, and an optional note.
 #
-# Returns undef on success or the result from real_void_bills.
+# Returns undef on success or the result from void_bills.
 # ------------------------------------------------------------------
 sub void_or_zero_bills_of_type {
-    my ($class, $e, $circ, $btype, $note) = @_;
+    my ($class, $e, $circ, $copy, $btype, $note) = @_;
 
     # Get a bill payment map.
     my $bpmap = $class->bill_payment_map_for_xact($e, $circ);
@@ -118,7 +144,18 @@ sub void_or_zero_bills_of_type {
         # Filter out the unvoided bills of the type we're looking for:
         my @bills = map {$_->{bill}} grep { $_->{bill}->btype() == $btype && $_->{bill_amount} > $_->{void_amount} } @$bpmap;
         if (@bills) {
-            my $result = $class->real_void_bills($e, \@bills, $note);
+            # settings for lost come from copy circlib.
+            my $prohibit_neg_balance_lost = (
+                $U->ou_ancestor_setting($copy->circ_lib(), 'bill.prohibit_negative_balance_on_lost')
+                ||
+                $U->ou_ancestor_setting($copy->circ_lib(), 'bill.prohibit_negative_balance_default')
+            );
+            my $neg_balance_interval_lost = (
+                $U->ou_ancestor_setting($copy->circ_lib(), 'bill.negative_balance_interval_on_lost')
+                ||
+                $U->ou_ancestor_setting($copy->circ_lib(), 'bill.negative_balance_interval_default')
+            );
+            my $result = $class->void_bills($e, \@bills, $note);
             if (ref($result)) {
                 return $result;
             }
@@ -799,16 +836,20 @@ sub bill_payment_map_for_xact {
 
 # This subroutine actually handles voiding of bills.  It takes a
 # CStoreEditor, an arrayref of bill ids or bills, and an optional note.
-sub real_void_bills {
+sub void_bills {
     my ($class, $e, $billids, $note) = @_;
     return $e->die_event unless $e->checkauth;
     return $e->die_event unless $e->allowed('VOID_BILLING');
 
     my %users;
-    for my $billid (@$billids) {
-
-        my $bill = $e->retrieve_money_billing($billid)
+    my $bills;
+    if (ref($billids->[0])) {
+        $bills = $billids;
+    } else {
+        $bills = $e->search_money_billing([{id => $billids}])
             or return $e->die_event;
+    }
+    for my $bill (@$bills) {
 
         my $xact = $e->retrieve_money_billable_transaction($bill->xact)
             or return $e->die_event;
@@ -848,9 +889,10 @@ sub real_void_bills {
 
 
 # This subroutine actually handles "adjusting" bills to zero.  It takes a
-# CStoreEditor, an arrayref of bill ids or bills, and an optional note.
+# CStoreEditor, an arrayref of bill ids or bills, an optional note, and an
+# optional interval.
 sub adjust_bills_to_zero {
-    my ($class, $e, $billids, $note) = @_;
+    my ($class, $e, $billids, $note, $interval) = @_;
 
     # Get with the editor to see if we have permission to void bills.
     return $e->die_event unless $e->checkauth;
@@ -890,46 +932,7 @@ sub adjust_bills_to_zero {
         $circ = $mbt->circulation();
         $copy = $circ->target_copy() if ($circ);
 
-        # Retrieve settings based on transaction location and copy
-        # location if we have a circulation.
-        my ($prohibit_neg_balance_default, $prohibit_neg_balance_overdues,
-            $prohibit_neg_balance_lost, $neg_balance_interval_default,
-            $neg_balance_interval_overdues, $neg_balance_interval_lost);
-        if ($circ) {
-            # defaults and overdue settings come from transaction org unit.
-            $prohibit_neg_balance_default = $U->ou_ancestor_setting(
-                $circ->circ_lib(), 'bill.prohibit_negative_balance_default');
-            $prohibit_neg_balance_overdues = (
-                $U->ou_ancestor_setting($circ->circ_lib(), 'bill.prohibit_negative_balance_on_overdues')
-                ||
-                $U->ou_ancestor_setting($circ->circ_lib(), 'bill.prohibit_netgative_balance_default')
-            );
-            $neg_balance_interval_default = $U->ou_ancestor_setting(
-                $circ->circ_lib(), 'bill.negative_balance_interval_default');
-            $neg_balance_interval_overdues = (
-                $U->ou_ancestor_setting($circ->circ_lib(), 'bill.negative_balance_interval_on_overdues')
-                ||
-                $U->ou_ancestor_setting($circ->circ_lib(), 'bill.negative_balance_interval_default')
-            );
-            # settings for lost come from copy circlib.
-            $prohibit_neg_balance_lost = (
-                $U->ou_ancestor_setting($copy->circ_lib(), 'bill.prohibit_negative_balance_on_lost')
-                ||
-                $U->ou_ancestor_setting($copy->circ_lib(), 'bill.prohibit_negative_balance_default')
-            );
-            $neg_balance_interval_lost = (
-                $U->ou_ancestor_setting($copy->circ_lib(), 'bill.negative_balance_interval_on_lost')
-                ||
-                $U->ou_ancestor_setting($copy->circ_lib(), 'bill.negative_balance_interval_default')
-            );
-        } else {
-            # We only care about defaults, and they come from the
-            # billing location.
-            $prohibit_neg_balance_default = $U->ou_ancestor_setting(
-                $grocery->billing_location(), 'bill.prohibit_negative_balance_default');
-            $neg_balance_interval_default = $U->ou_ancestor_setting(
-            $grocery->billing_location(), 'bill.negative_balance_interval_default');
-        }
+
 
         # Get the bill_payment_map for the transaction.
         my $bpmap = $class->bill_payment_map_for_xact($e, $mbt);
@@ -943,9 +946,14 @@ sub adjust_bills_to_zero {
             # each bill.
             my $xact_total = 0;
             map {$xact_total += $_->{bill}->amount()} @$bpmap;
+            last if $xact_total == 0;
 
             # Get the bill_payment_map entry for this bill:
             my ($bpentry) = grep {$_->{bill}->id() == $bill->id()} @$bpmap;
+            if (_within_refund_interval($bpentry, $interval)) {
+#TODO
+                next;
+            }
 
             # From here on out, use the bill object from the bill
             # payment map entry.
@@ -956,60 +964,34 @@ sub adjust_bills_to_zero {
             my $amount_to_void = $U->fpdiff($bpentry->{bill_amount},$bpentry->{void_amount});
 
             # Check if this bill is already voided.  We don't allow
-            # "double" voids regardless of settings.  The old code
-            # made it impossible to void an already voided bill, so
-            # we're doing the same.
+            # "double" voids regardless of settings.
             if ($amount_to_void <= 0) {
-                my $event = OpenILS::Event->new('BILL_ALREADY_VOIDED', payload => $bill);
-                $e->event($event);
-                return $event;
+                #my $event = OpenILS::Event->new('BILL_ALREADY_VOIDED', payload => $bill);
+                #$e->event($event);
+                #return $event;
+                next;
             }
 
-            # If we're voiding a circulation-related bill we have
-            # stuff to check.
-            if ($circ) {
-                if ($amount_to_void > $xact_total) {
-                    my $btype = $bill->btype();
-                    if ($btype == 1) {
-                        # Overdues
-                        $amount_to_void = $xact_total unless(_check_payment_interval($bpentry, $neg_balance_interval_overdues));
-                        $amount_to_void = $xact_total if ($U->is_true($prohibit_neg_balance_overdues));
-                    } elsif ($btype == 3 || $btype == 10) {
-                        # Lost or Long Overdue
-                        $amount_to_void = $xact_total unless(_check_payment_interval($bpentry, $neg_balance_interval_lost));
-                        $amount_to_void = $xact_total if ($U->is_true($prohibit_neg_balance_lost));
-                    } else {
-                        # Any other bill that we're trying to void.
-                        $amount_to_void = $xact_total unless(_check_payment_interval($bpentry, $neg_balance_interval_default));
-                        $amount_to_void = $xact_total if ($U->is_true($prohibit_neg_balance_default));
-                    }
-                }
-            } else {
-                # Grocery bills are simple by comparison.
-                if ($amount_to_void > $xact_total) {
-                    $amount_to_void = $xact_total unless(_check_payment_interval($bpentry, $neg_balance_interval_default));
-                    $amount_to_void = $xact_total if ($U->is_true($prohibit_neg_balance_default));
-                }
+            if ($amount_to_void > $xact_total) {
+                $amount_to_void = $xact_total;
             }
 
-            # Create the void payment if necessary:
-            if ($amount_to_void > 0) {
-                my $payobj = Fieldmapper::money::adjustment_payment->new;
-                $payobj->amount($amount_to_void);
-                $payobj->amount_collected($amount_to_void);
-                $payobj->xact($xactid);
-                $payobj->accepting_usr($e->requestor->id);
-                $payobj->payment_ts('now');
-                $payobj->billing($bill->id());
-                $payobj->note($note) if ($note);
-                $e->create_money_adjustment_payment($payobj) or return $e->die_event;
-                # Adjust our bill_payment_map
-                $bpentry->{void_amount} += $amount_to_void;
-                push @{$bpentry->{voids}}, $payobj;
-                # Should come to zero:
-                my $new_bill_amount = $U->fpdiff($bill->amount(),$amount_to_void);
-                $bill->amount($new_bill_amount);
-            }
+            # Create the adjustment payment
+            my $payobj = Fieldmapper::money::adjustment_payment->new;
+            $payobj->amount($amount_to_void);
+            $payobj->amount_collected($amount_to_void);
+            $payobj->xact($xactid);
+            $payobj->accepting_usr($e->requestor->id);
+            $payobj->payment_ts('now');
+            $payobj->billing($bill->id());
+            $payobj->note($note) if ($note);
+            $e->create_money_adjustment_payment($payobj) or return $e->die_event;
+            # Adjust our bill_payment_map
+            $bpentry->{void_amount} += $amount_to_void;
+            push @{$bpentry->{voids}}, $payobj;
+            # Should come to zero:
+            my $new_bill_amount = $U->fpdiff($bill->amount(),$amount_to_void);
+            $bill->amount($new_bill_amount);
         }
 
         my $org = $U->xact_org($xactid, $e);
@@ -1038,7 +1020,7 @@ sub adjust_bills_to_zero {
 # returns true if the interval argument is undefined or empty, or if
 # the bill has no payments whatsoever.  It will return false if the
 # entry has no payments other than voids.
-sub _check_payment_interval {
+sub _within_refund_interval {
     my ($entry, $interval) = @_;
     my $result = ($interval ? 0 : 1);
 
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 19f3134..60a0b5c 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm
@@ -2635,7 +2635,7 @@ sub finish_fines_and_voiding {
     my $note = 'System: Amnesty Checkin' if $self->void_overdues;
 
     my $evt = $CC->void_or_zero_overdues(
-        $self->editor, $self->circ, $self->backdate, $note);
+        $self->editor, $self->circ, {backdate => $self->backdate, note => $note});
 
     return $self->bail_on_events($evt) if $evt;
 
@@ -3799,7 +3799,7 @@ sub checkin_handle_lost_or_lo_now_found {
     my $tag = $is_longoverdue ? "LONGOVERDUE" : "LOST";
 
     $logger->debug("voiding $tag item billings");
-    my $result = $CC->void_or_zero_bills_of_type($self->editor, $self->circ, $bill_type, "System: VOIDED FOR $tag ITEM RETURNED");
+    my $result = $CC->void_or_zero_bills_of_type($self->editor, $self->circ, $self->copy, $bill_type, "System: VOIDED FOR $tag ITEM RETURNED");
     $self->bail_on_events($self->editor->event) if ($result);
 }
 
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Money.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Money.pm
index 05cc461..30e0ca9 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Money.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Money.pm
@@ -907,7 +907,7 @@ __PACKAGE__->register_method(
 sub void_bill {
     my( $s, $c, $authtoken, @billids ) = @_;
     my $editor = new_editor(authtoken=>$authtoken, xact=>1);
-    my $rv = $CC->real_void_bills($editor, \@billids);
+    my $rv = $CC->void_bills($editor, \@billids);
     if (ref($rv) eq 'HASH') {
         # We got an event.
         $editor->rollback();

commit b6d4f96caa22923f84124e825427407489e821a0
Author: Dan Wells <dbw2 at calvin.edu>
Date:   Fri Feb 20 17:19:42 2015 -0500

    LP 1198465: Rename internal functions for clarity
    
    These methods will now be the splitting point for void vs. adjustment,
    so let's give them broader names.
    
    Signed-off-by: Dan Wells <dbw2 at calvin.edu>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Ben Shum <bshum at biblio.org>

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 07357ea..0de7fd7 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat/AssetCommon.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat/AssetCommon.pm
@@ -745,7 +745,7 @@ sub set_item_lost_or_lod {
     # ---------------------------------------------------------------------
     # void all overdue fines on this circ if configured
     if( $void_overdue ) {
-        my $evt = OpenILS::Application::Circ::CircCommon->void_overdues($e, $circ);
+        my $evt = OpenILS::Application::Circ::CircCommon->void_or_zero_overdues($e, $circ);
         return $evt if $evt;
     }
 
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm
index adf6af2..1aca1de 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm
@@ -461,7 +461,7 @@ sub set_circ_claims_returned {
 
         # make it look like the circ stopped at the cliams returned time
         $circ->stop_fines_time($backdate);
-        my $evt = OpenILS::Application::Circ::CircCommon->void_overdues($e, $circ, $backdate);
+        my $evt = OpenILS::Application::Circ::CircCommon->void_or_zero_overdues($e, $circ, $backdate);
         return $evt if $evt;
     }
 
@@ -604,7 +604,7 @@ sub post_checkin_backdate_circ_impl {
     $e->update_action_circulation($circ) or return $e->die_event;
 
     # now void the overdues "erased" by the back-dating
-    my $evt = OpenILS::Application::Circ::CircCommon->void_overdues($e, $circ, $backdate);
+    my $evt = OpenILS::Application::Circ::CircCommon->void_or_zero_overdues($e, $circ, $backdate);
     return $evt if $evt;
 
     # If the circ was closed before and the balance owned !=0, re-open the transaction
@@ -1331,7 +1331,7 @@ sub handle_mark_damaged {
         # the assumption is that you would not void the overdues unless you 
         # were also charging for the item and/or applying a processing fee
         if($void_overdue) {
-            my $evt = OpenILS::Application::Circ::CircCommon->void_overdues($e, $circ);
+            my $evt = OpenILS::Application::Circ::CircCommon->void_or_zero_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 8496e3f..9a00513 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm
@@ -20,12 +20,12 @@ my $parser = DateTime::Format::ISO8601->new;
 
 
 # -----------------------------------------------------------------
-# Voids overdue fines on the given circ.  if a backdate is 
+# Voids (or zeros) 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
 # overdue fines.
 # -----------------------------------------------------------------
-sub void_overdues {
+sub void_or_zero_overdues {
     my($class, $e, $circ, $backdate, $note) = @_;
 
     my $bill_search = { 
@@ -102,14 +102,14 @@ sub void_lost {
 }
 
 # ------------------------------------------------------------------
-# Void all bills of a given type on a circulation.
+# Void (or zero) all bills of a given type on a circulation.
 #
 # Takes an editor, a circ object, the btype number for the bills you
 # want to void, and an optional note.
 #
 # Returns undef on success or the result from real_void_bills.
 # ------------------------------------------------------------------
-sub void_bills_of_type {
+sub void_or_zero_bills_of_type {
     my ($class, $e, $circ, $btype, $note) = @_;
 
     # Get a bill payment map.
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 93c288e..19f3134 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm
@@ -2634,7 +2634,7 @@ sub finish_fines_and_voiding {
     # void overdues after fine generation to prevent concurrent DB access to overdue billings
     my $note = 'System: Amnesty Checkin' if $self->void_overdues;
 
-    my $evt = $CC->void_overdues(
+    my $evt = $CC->void_or_zero_overdues(
         $self->editor, $self->circ, $self->backdate, $note);
 
     return $self->bail_on_events($evt) if $evt;
@@ -3799,7 +3799,7 @@ sub checkin_handle_lost_or_lo_now_found {
     my $tag = $is_longoverdue ? "LONGOVERDUE" : "LOST";
 
     $logger->debug("voiding $tag item billings");
-    my $result = $CC->void_bills_of_type($self->editor, $self->circ, $bill_type, "System: VOIDED FOR $tag ITEM RETURNED");
+    my $result = $CC->void_or_zero_bills_of_type($self->editor, $self->circ, $bill_type, "System: VOIDED FOR $tag ITEM RETURNED");
     $self->bail_on_events($self->editor->event) if ($result);
 }
 

commit f89b707fe016b978ef0de0b4616237d2574a42b6
Author: Dan Wells <dbw2 at calvin.edu>
Date:   Tue Feb 25 17:37:43 2014 -0500

    LP 1198465: Restore voiding code, rename adjustment function
    
    This commit restores the code we removed a few commits earlier, and
    also renames "real_void_bills()" "adjust_bills_to_zero()" in order to
    better delineate the split functionality.
    
    Signed-off-by: Dan Wells <dbw2 at calvin.edu>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Ben Shum <bshum at biblio.org>

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 c27df39..8496e3f 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm
@@ -801,6 +801,56 @@ sub bill_payment_map_for_xact {
 # CStoreEditor, an arrayref of bill ids or bills, and an optional note.
 sub real_void_bills {
     my ($class, $e, $billids, $note) = @_;
+    return $e->die_event unless $e->checkauth;
+    return $e->die_event unless $e->allowed('VOID_BILLING');
+
+    my %users;
+    for my $billid (@$billids) {
+
+        my $bill = $e->retrieve_money_billing($billid)
+            or return $e->die_event;
+
+        my $xact = $e->retrieve_money_billable_transaction($bill->xact)
+            or return $e->die_event;
+
+        if($U->is_true($bill->voided)) {
+            # For now, it is not an error to attempt to re-void a bill, but
+            # don't actually do anything
+            #$e->rollback;
+            #return OpenILS::Event->new('BILL_ALREADY_VOIDED', payload => $bill)
+            next;
+        }
+
+        my $org = $U->xact_org($bill->xact, $e);
+        $users{$xact->usr} = {} unless $users{$xact->usr};
+        $users{$xact->usr}->{$org} = 1;
+
+        $bill->voided('t');
+        $bill->voider($e->requestor->id);
+        $bill->void_time('now');
+        my $n = ($bill->note) ? sprintf("%s\n", $bill->note) : "";
+        $bill->note(sprintf("$n%s", ($note) ? $note : "System: VOIDED FOR BACKDATE"));
+
+        $e->update_money_billing($bill) or return $e->die_event;
+        my $evt = $U->check_open_xact($e, $bill->xact, $xact);
+        return $evt if $evt;
+    }
+
+    # calculate penalties for all user/org combinations
+    for my $user_id (keys %users) {
+        for my $org_id (keys %{$users{$user_id}}) {
+            OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $org_id)
+        }
+    }
+
+    return 1;
+}
+
+
+# This subroutine actually handles "adjusting" bills to zero.  It takes a
+# CStoreEditor, an arrayref of bill ids or bills, and an optional note.
+sub adjust_bills_to_zero {
+    my ($class, $e, $billids, $note) = @_;
 
     # Get with the editor to see if we have permission to void bills.
     return $e->die_event unless $e->checkauth;

commit f9f9ce0107ef1023f201acc4c89d77dc642ce524
Author: Jason Stephenson <jason at sigio.com>
Date:   Tue Feb 25 14:47:47 2014 -0500

    LP 1198465: Fix typo in BILL_ALREADY_VOIDED event
    
    Signed-off-by: Dan Wells <dbw2 at calvin.edu>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Ben Shum <bshum at biblio.org>

diff --git a/Open-ILS/src/extras/ils_events.xml b/Open-ILS/src/extras/ils_events.xml
index cb860d4..2ecd618 100644
--- a/Open-ILS/src/extras/ils_events.xml
+++ b/Open-ILS/src/extras/ils_events.xml
@@ -78,7 +78,7 @@
 		<desc xml:lang="en-US">The provided password is not correct</desc>
 	</event>
 	<event code='1211' textcode='BILL_ALREADY_VOIDED'>
-		<desc xml:lang="en-US">The selecte bill has already been voided</desc>
+		<desc xml:lang="en-US">The bill is already voided</desc>
 	</event>
 	<event code='1212' textcode='PATRON_EXCEEDS_OVERDUE_COUNT'>
 		<desc xml:lang="en-US">The patron has too many overdue items</desc>

commit 027837553e3cfdf179ce63a9adaff5fb3eb29991
Author: Jason Stephenson <jason at sigio.com>
Date:   Tue Jan 7 20:44:47 2014 -0500

    LP 1198465: Update CDBI/money.pm for adjustment_payment
    
    Signed-off-by: Dan Wells <dbw2 at calvin.edu>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Ben Shum <bshum at biblio.org>

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/money.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/money.pm
index ddf366e..21b986f 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/money.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/money.pm
@@ -144,5 +144,13 @@ __PACKAGE__->table('money_credit_payment');
 
 #-------------------------------------------------------------------------------
 
+package money::adjustment_payment;
+use base qw/money/;
+__PACKAGE__->table('money_adjustment_payment');
+__PACKAGE__->columns(Primary => 'id');
+__PACKAGE__->columns(Essential => qw/xact amount payment_ts note accepting_usr
+                                     amount_collected voided billing/);
+#-------------------------------------------------------------------------------
+
 1;
 

commit f8af9fc2759b2c01e038836c9934663e238ee884
Author: Jason Stephenson <jason at sigio.com>
Date:   Sun Oct 6 17:13:01 2013 -0400

    LP 1198465: Update logic in checkin_handle_lost_or_lo_now_found_restore_od
    
    Signed-off-by: Dan Wells <dbw2 at calvin.edu>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Ben Shum <bshum at biblio.org>

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 c11b213..93c288e 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm
@@ -3821,18 +3821,29 @@ sub checkin_handle_lost_or_lo_now_found_restore_od {
     );
 
     $logger->debug("returning ".scalar(@$ods)." overdue charges pre-$tag");
-    for my $bill (@$ods) {
-        if( $U->is_true($bill->voided) ) {
-                $logger->info("$tag item returned - restoring overdue ".$bill->id);
-                $bill->voided('f');
-                $bill->clear_void_time;
-                $bill->voider($self->editor->requestor->id);
-                my $note = ($bill->note) ? $bill->note . "\n" : '';
-                $bill->note("${note}System: $tag RETURNED - OVERDUES REINSTATED");
-
-                $self->bail_on_events($self->editor->event)
-                        unless $self->editor->update_money_billing($bill);
-        }
+    # Because actual users get up to all kinds of unexpectedness, we
+    # only recreate up to $circ->max_fine in bills.  I know you think
+    # it wouldn't happen that bills could get created, voided, and
+    # recreated more than once, but I guaran-damn-tee you that it will
+    # happen.
+    if ($ods && @$ods) {
+        my $void_amount = 0;
+        my $void_max = $self->circ->max_fine();
+        my @billings = map {$_->id()} @$ods;
+        my $voids = $self->editor->search_money_adjustment_payment(
+            {
+                billing => \@billings
+            }
+        );
+        map {$void_amount += $_->amount()} @$voids;
+        $CC->create_bill(
+            $self->editor,
+            ($void_amount < $void_max ? $void_amount : $void_max),
+            $ods->[0]->btype(),
+            $ods->[0]->billing_type(),
+            $self->circ->id(),
+            "System: $tag RETURNED - OVERDUES REINSTATED"
+        );
     }
 }
 

commit 9d6ed22ec04bd900990c60e853f8f45197feb6d0
Author: Jason Stephenson <jason at sigio.com>
Date:   Sun Oct 6 11:09:10 2013 -0400

    LP 1198465: Modify O::A::Circ::Circulator->checkin_handle_lost_or_lo_now_found()
    
    Simplify it to use the new, CircCommon->void_bills_of_type() method.
    
    Signed-off-by: Dan Wells <dbw2 at calvin.edu>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Ben Shum <bshum at biblio.org>

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 b74c8c3..c11b213 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm
@@ -2634,7 +2634,7 @@ sub finish_fines_and_voiding {
     # void overdues after fine generation to prevent concurrent DB access to overdue billings
     my $note = 'System: Amnesty Checkin' if $self->void_overdues;
 
-    my $evt = OpenILS::Application::Circ::CircCommon->void_overdues(
+    my $evt = $CC->void_overdues(
         $self->editor, $self->circ, $self->backdate, $note);
 
     return $self->bail_on_events($evt) if $evt;
@@ -3796,33 +3796,11 @@ sub make_trigger_events {
 sub checkin_handle_lost_or_lo_now_found {
     my ($self, $bill_type, $is_longoverdue) = @_;
 
-    # ------------------------------------------------------------------
-    # 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
-        }
-    );
-
     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);
-        }
-    }
+    $logger->debug("voiding $tag item billings");
+    my $result = $CC->void_bills_of_type($self->editor, $self->circ, $bill_type, "System: VOIDED FOR $tag ITEM RETURNED");
+    $self->bail_on_events($self->editor->event) if ($result);
 }
 
 sub checkin_handle_lost_or_lo_now_found_restore_od {

commit 48fce9688dd1cd67ca083c3ec985f86c144d13f8
Author: Jason Stephenson <jason at sigio.com>
Date:   Fri Feb 20 17:17:46 2015 -0500

    LP 1198465: Add code for adjustment payments
    
    This new payment type complements the current void logic that flags
    bills as voided.
    
    This new payment type is needed because the current way that Evergreen
    voids bills requires that all voids happen in the same increment as the
    bills themselves.  This prevents voiding of a partial bill or a bill
    that has had a partial payment applied.
    
    This commit also adds the org. unit setting types for the conditional
    negative balances enhancements:
    
    bill.prohibit_negative_balance_default
    bill.prohibit_negative_balance_on_overdues
    bill.prohibit_negative_balance_on_lost
    bill.negative_balance_interval_default
    bill.negative_balance_interval_on_overdues
    bill.negative_balance_interval_on_lost
    
    Finally, create a helper function for checking intervals along the way,
    and a handy little subroutine to void all bills of a given type on a
    circulation transaction.
    
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    
    Conflicts:
    	Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm
    	Open-ILS/src/sql/Pg/950.data.seed-values.sql
    
    Signed-off-by: Dan Wells <dbw2 at calvin.edu>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    Signed-off-by: Ben Shum <bshum at biblio.org>

diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml
index b847d16..cd9d697 100644
--- a/Open-ILS/examples/fm_IDL.xml
+++ b/Open-ILS/examples/fm_IDL.xml
@@ -81,6 +81,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 			<field name="work_payment" oils_persist:virtual="true" />
 			<field name="credit_payment" oils_persist:virtual="true" />
 			<field name="goods_payment" oils_persist:virtual="true" />
+			<field name="adjustment_payment" oils_persist:virtual="true" />
 		</fields>
 		<links>
 			<link field="usr" reltype="has_a" key="id" map="" class="au"/>
@@ -3621,6 +3622,26 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 			</actions>
 		</permacrud>
 	</class>
+	<class id="map" controller="open-ils.cstore" oils_obj:fieldmapper="money::adjustment_payment" oils_persist:tablename="money.adjustment_payment" reporter:label="Adjustment Payment">
+		<fields oils_persist:primary="id" oils_persist:sequence="money.payment_id_seq">
+			<field name="accepting_usr" reporter:datatype="link"/>
+			<field name="amount" reporter:datatype="money" />
+			<field name="amount_collected" reporter:datatype="money" />
+			<field name="id" reporter:datatype="id" />
+			<field name="note"  reporter:datatype="text"/>
+			<field name="payment_ts" reporter:datatype="timestamp"/>
+			<field name="xact" reporter:datatype="link"/>
+			<field name="billing" reporter:datatype="link"/>
+			<field name="payment_type" oils_persist:virtual="true"  reporter:datatype="text"/>
+			<field name="payment" oils_persist:virtual="true" reporter:datatype="link"/>
+		</fields>
+		<links>
+			<link field="payment" reltype="might_have" key="id" map="" class="mp"/>
+			<link field="accepting_usr" reltype="has_a" key="id" map="" class="au"/>
+			<link field="xact" reltype="has_a" key="id" map="" class="mbt"/>
+			<link field="billing" reltype="might_have" key="id" class="mb"/>
+		</links>
+	</class>
 	<class id="mrd" controller="open-ils.cstore" oils_obj:fieldmapper="metabib::record_descriptor" oils_persist:tablename="metabib.rec_descriptor" reporter:label="Basic Record Descriptor">
 		<fields oils_persist:primary="id" oils_persist:sequence="metabib.rec_descriptor_id_seq">
 			<field reporter:label="Audn" name="audience" oils_persist:primitive="string"  reporter:datatype="text"/>
@@ -6928,6 +6949,7 @@ SELECT  usr,
 			<field reporter:label="Work Payment Detail" name="work_payment" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Forgive Payment Detail" name="forgive_payment" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Goods Payment Detail" name="goods_payment" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Adjustment Payment Detail" name="adjustment_payment" oils_persist:virtual="true" reporter:datatype="link"/>
 		</fields>
 		<links>
 			<link field="cash_payment" reltype="might_have" key="id" map="" class="mcp"/>
@@ -6937,6 +6959,7 @@ SELECT  usr,
 			<link field="work_payment" reltype="might_have" key="id" map="" class="mwp"/>
 			<link field="forgive_payment" reltype="might_have" key="id" map="" class="mfp"/>
 			<link field="goods_payment" reltype="might_have" key="id" map="" class="mgp"/>
+			<link field="adjustment_payment" reltype="might_have" key="id" map="" class="map"/>
 			<link field="xact" reltype="has_a" key="id" map="" class="mbt"/>
 		</links>
         <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
@@ -6964,6 +6987,7 @@ SELECT  usr,
 			<field reporter:label="Work Payment Detail" name="work_payment" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Forgive Payment Detail" name="forgive_payment" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Goods Payment Detail" name="goods_payment" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Adjustment Payment Detail" name="adjustment_payment" oils_persist:virtual="true" reporter:datatype="link"/>
 		</fields>
 		<links>
 			<link field="cash_payment" reltype="might_have" key="id" map="" class="mcp"/>
@@ -6973,6 +6997,7 @@ SELECT  usr,
 			<link field="work_payment" reltype="might_have" key="id" map="" class="mwp"/>
 			<link field="forgive_payment" reltype="might_have" key="id" map="" class="mfp"/>
 			<link field="goods_payment" reltype="might_have" key="id" map="" class="mgp"/>
+			<link field="adjustment_payment" reltype="might_have" key="id" map="" class="mvp"/>
 			<link field="xact" reltype="has_a" key="id" map="" class="mbt"/>
 			<link field="accepting_usr" reltype="has_a" key="id" map="" class="au"/>
 		</links>
@@ -6990,12 +7015,14 @@ SELECT  usr,
 			<field reporter:label="Forgive Payment Detail" name="forgive_payment" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Goods Payment Detail" name="goods_payment" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Credit Payment Detail" name="credit_payment" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Adjustment Payment Detail" name="adjustment_payment" oils_persist:virtual="true" reporter:datatype="link"/>
 		</fields>
 		<links>
 			<link field="work_payment" reltype="might_have" key="id" map="" class="mwp"/>
 			<link field="forgive_payment" reltype="might_have" key="id" map="" class="mfp"/>
 			<link field="goods_payment" reltype="might_have" key="id" map="" class="mgp"/>
 			<link field="credit_payment" reltype="might_have" key="id" map="" class="mcrp"/>
+			<link field="adjustment_payment" reltype="might_have" key="id" map="" class="mvp"/>
 			<link field="xact" reltype="has_a" key="id" map="" class="mbt"/>
 		</links>
 	</class>
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm
index 2a4e184..8c92ee5 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm
@@ -2219,5 +2219,26 @@ sub check_open_xact {
     return undef;
 }
 
+# Because floating point math has rounding issues, and Dyrcona gets
+# tired of typing out the code to multiply floating point numbers
+# before adding and subtracting them and then dividing the result by
+# 100 each time, he wrote this little subroutine for subtracting
+# floating point values.  It can serve as a model for the other
+# operations if you like.
+#
+# It takes a list of floating point values as arguments.  The rest are
+# all subtracted from the first and the result is returned.  The
+# values are all multiplied by 100 before being used, and the result
+# is divided by 100 in order to avoid decimal rounding errors inherent
+# in floating point math.
+sub fpdiff {
+    my ($class, @args) = @_;
+    my $result = shift(@args) * 100;
+    while (my $arg = shift(@args)) {
+        $result -= $arg * 100;
+    }
+    return $result / 100;
+}
+
 1;
 
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 19b2787..c27df39 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/CircCommon.pm
@@ -9,6 +9,7 @@ use OpenSRF::Utils::Logger qw(:logger);
 use OpenILS::Utils::CStoreEditor q/:funcs/;
 use OpenILS::Const qw/:const/;
 use POSIX qw(ceil);
+use List::MoreUtils qw(uniq);
 
 my $U = "OpenILS::Application::AppUtils";
 my $parser = DateTime::Format::ISO8601->new;
@@ -58,17 +59,12 @@ sub void_overdues {
         }
     }
 
-    my $bills = $e->search_money_billing($bill_search);
-    
-    for my $bill (@$bills) {
-        next if $U->is_true($bill->voided);
-        $logger->info("voiding overdue bill ".$bill->id);
-        $bill->voided('t');
-        $bill->void_time('now');
-        $bill->voider($e->requestor->id);
-        my $n = ($bill->note) ? sprintf("%s\n", $bill->note) : "";
-        $bill->note(sprintf("$n%s", ($note) ? $note : "System: VOIDED FOR BACKDATE"));
-        $e->update_money_billing($bill) or return $e->die_event;
+    my $billids = $e->search_money_billing([$bill_search, {idlist=>1}]);
+    if ($billids && @$billids) {
+        my $result = $class->real_void_bills($e, $billids, $note);
+        if (ref($result)) {
+            return $result;
+        }
     }
 
     return undef;
@@ -105,6 +101,33 @@ sub void_lost {
     return undef;
 }
 
+# ------------------------------------------------------------------
+# Void all bills of a given type on a circulation.
+#
+# Takes an editor, a circ object, the btype number for the bills you
+# want to void, and an optional note.
+#
+# Returns undef on success or the result from real_void_bills.
+# ------------------------------------------------------------------
+sub void_bills_of_type {
+    my ($class, $e, $circ, $btype, $note) = @_;
+
+    # Get a bill payment map.
+    my $bpmap = $class->bill_payment_map_for_xact($e, $circ);
+    if ($bpmap && @$bpmap) {
+        # Filter out the unvoided bills of the type we're looking for:
+        my @bills = map {$_->{bill}} grep { $_->{bill}->btype() == $btype && $_->{bill_amount} > $_->{void_amount} } @$bpmap;
+        if (@bills) {
+            my $result = $class->real_void_bills($e, \@bills, $note);
+            if (ref($result)) {
+                return $result;
+            }
+        }
+    }
+
+    return undef;
+}
+
 sub reopen_xact {
     my($class, $e, $xactid) = @_;
 
@@ -591,4 +614,404 @@ sub generate_fines {
     return undef;
 }
 
+# -----------------------------------------------------------------
+# Given an editor and a xact, return a reference to an array of
+# hashrefs that map billing objects to payment objects.  Returns undef
+# if no bills are found for the given transaction.
+#
+# The bill amounts are adjusted to reflect the application of the
+# payments to the bills.  The original bill amounts are retained in
+# the mapping.
+#
+# The payment objects may or may not have their amounts adjusted
+# depending on whether or not they apply to more than one bill.  We
+# could really use a better logic here, perhaps, but if it was
+# consistent, it wouldn't be Evergreen.
+#
+# The data structure used in the array is a hashref that has the
+# following fields:
+#
+# bill => the adjusted bill object
+# voids => an arrayref of void payments that apply directly to the
+#          bill
+# payments => an arrayref of payment objects applied to the bill
+# bill_amount => original amount from the billing object
+# void_amount => total of the void payments that apply directly to the
+#                bill
+#
+# Each bill is only mapped to payments one time.  However, a single
+# payment may be mapped to more than one bill if the payment amount is
+# greater than the amount of each individual bill, such as a $3.00
+# payment for 30 $0.10 overdue bills.  There is an attempt made to
+# first pay bills with payments that match the billing amount.  This
+# is intended to catch payments for lost and/or long overdue bills so
+# that they will match up.
+#
+# This function is heavily adapted from code written by Jeff Godin of
+# Traverse Area District Library and submitted on LaunchPad bug
+# #1009049.
+# -----------------------------------------------------------------
+sub bill_payment_map_for_xact {
+    my ($class, $e, $xact) = @_;
+
+    # Check for CStoreEditor and make a new one if we have to. This
+    # allows one-off calls to this subroutine to pass undef as the
+    # CStoreEditor and not have to create one of their own.
+    $e = OpenILS::Utils::CStoreEditor->new unless ($e);
+
+    # find all bills in order
+    my $bill_search = [
+        {xact => $xact->id()},
+        { order_by => { mb => { billing_ts => { direction => 'asc' } } } },
+    ];
+
+    # At some point, we should get rid of the voided column on
+    # money.payment and family.  It is not exposed in the client at
+    # the moment, and should be replaced with a void_bill type.  The
+    # descendants of money.payment don't expose the voided field in
+    # the fieldmapper, only the mp object, based on the money.payment
+    # view, does.  However, I want to leave that complication for
+    # later.  I wonder if I'm not slowing things down too much with
+    # the current adjustment_payment logic.  It would probably be faster if
+    # we had direct Pg access at this layer.  I can probably wrangle
+    # something via the drivers or store interfaces, but I haven't
+    # really figured those out, yet.
+
+    my $bills = $e->search_money_billing($bill_search);
+
+    # return undef if there are no bills.
+    return undef unless ($bills && @$bills);
+
+    # map the bills into our bill_payment_map entry format:
+    my @entries = map {
+        {
+            bill => $_,
+            bill_amount => $_->amount(),
+            payments => [],
+            voids => [],
+            void_amount => 0
+        }
+    } @$bills;
+
+    # Find all unvoided payments in order.  Flesh voids so that we
+    # don't have to retrieve them later.
+    my $payments = $e->search_money_payment(
+        [
+            { xact => $xact->id, voided=>'f' },
+            {
+                order_by => { mp => { payment_ts => { direction => 'asc' } } },
+                flesh => 1,
+                flesh_fields => { mp => ['adjustment_payment'] }
+            }
+        ]
+    );
+
+    # If there were no payments, then we just return the bills.
+    return \@entries unless ($payments && @$payments);
+
+    # Now, we go through the rigmarole of mapping payments to bills
+    # and adjusting the bill balances.
+
+    # Apply the voids before "paying" other bills.
+    foreach my $entry (@entries) {
+        my $bill = $entry->{bill};
+        # Find only the voids that apply to individual bills.
+        my @voids = map {$_->adjustment_payment()} grep {$_->payment_type() eq 'adjustment_payment' && $_->adjustment_payment()->billing() == $bill->id()} @$payments;
+        if (@voids) {
+            foreach my $void (@voids) {
+                my $new_amount = $U->fpdiff($bill->amount(),$void->amount());
+                if ($new_amount >= 0) {
+                    push @{$entry->{voids}}, $void;
+                    $entry->{void_amount} += $void->amount();
+                    $bill->amount($new_amount);
+                    # Remove the used up void from list of payments:
+                    my @p = grep {$_->id() != $void->id()} @$payments;
+                    $payments = \@p;
+                } else {
+                    # It should never happen that we have more void
+                    # payments on a single bill than the amount of the
+                    # bill.  However, experience shows that the things
+                    # that should never happen actually do happen with
+                    # surprising regularity in a library setting.
+
+                    # Clone the void to say how much of it actually
+                    # applied to this bill.
+                    my $new_void = $void->clone();
+                    $new_void->amount($bill->amount());
+                    $new_void->amount_collected($bill->amount());
+                    push (@{$entry->{voids}}, $new_void);
+                    $entry->{void_amount} += $new_void->amount();
+                    $bill->amount(0);
+                    $void->amount(-$new_amount);
+                    # Could be a candidate for YAOUS about what to do
+                    # with excess void amounts on a bill.
+                }
+                last if ($bill->amount() == 0);
+            }
+        }
+    }
+
+    # Try to map payments to bills by amounts starting with the
+    # largest payments:
+    foreach my $payment (sort {$b->amount() <=> $a->amount()} @$payments) {
+        my @bills2pay = grep {$_->{bill}->amount() == $payment->amount()} @entries;
+        if (@bills2pay) {
+            my $entry = $bills2pay[0];
+            $entry->{bill}->amount(0);
+            push @{$entry->{payments}}, $payment;
+            # Remove the payment from the master list.
+            my @p = grep {$_->id() != $payment->id()} @$payments;
+            $payments = \@p;
+        }
+    }
+
+    # Map remaining bills to payments in whatever order.
+    foreach  my $entry (grep {$_->{bill}->amount() > 0} @entries) {
+        my $bill = $entry->{bill};
+        # We could run out of payments before bills.
+        if ($payments && @$payments) {
+            while ($bill->amount() > 0) {
+                my $payment = shift @$payments;
+                last unless $payment;
+                my $new_amount = $U->fpdiff($bill->amount(),$payment->amount());
+                if ($new_amount < 0) {
+                    # Clone the payment so we can say how much applied
+                    # to this bill.
+                    my $new_payment = $payment->clone();
+                    $new_payment->amount($bill->amount());
+                    $bill->amount(0);
+                    push @{$entry->{payments}}, $new_payment;
+                    # Reset the payment amount and put it back on the
+                    # list for later use.
+                    $payment->amount(-$new_amount);
+                    unshift @$payments, $payment;
+                } else {
+                    $bill->amount($new_amount);
+                    push @{$entry->{payments}}, $payment;
+                }
+            }
+        }
+    }
+
+    return \@entries;
+}
+
+
+# This subroutine actually handles voiding of bills.  It takes a
+# CStoreEditor, an arrayref of bill ids or bills, and an optional note.
+sub real_void_bills {
+    my ($class, $e, $billids, $note) = @_;
+
+    # Get with the editor to see if we have permission to void bills.
+    return $e->die_event unless $e->checkauth;
+    return $e->die_event unless $e->allowed('VOID_BILLING');
+
+    my %users;
+
+    # Let's get all the billing objects and handle them by
+    # transaction.
+    my $bills;
+    if (ref($billids->[0])) {
+        $bills = $billids;
+    } else {
+        $bills = $e->search_money_billing([{id => $billids}])
+            or return $e->die_event;
+    }
+
+    my @xactids = uniq map {$_->xact()} @$bills;
+
+    foreach my $xactid (@xactids) {
+        my $mbt = $e->retrieve_money_billable_transaction(
+            [
+                $xactid,
+                {
+                    flesh=> 2,
+                    flesh_fields=> {
+                        mbt=>['grocery','circulation'],
+                        circ=>['target_copy']
+                    }
+                }
+            ]
+        ) or return $e->die_event;
+        # Flesh grocery bills and circulations so we don't have to
+        # retrieve them later.
+        my ($circ, $grocery, $copy);
+        $grocery = $mbt->grocery();
+        $circ = $mbt->circulation();
+        $copy = $circ->target_copy() if ($circ);
+
+        # Retrieve settings based on transaction location and copy
+        # location if we have a circulation.
+        my ($prohibit_neg_balance_default, $prohibit_neg_balance_overdues,
+            $prohibit_neg_balance_lost, $neg_balance_interval_default,
+            $neg_balance_interval_overdues, $neg_balance_interval_lost);
+        if ($circ) {
+            # defaults and overdue settings come from transaction org unit.
+            $prohibit_neg_balance_default = $U->ou_ancestor_setting(
+                $circ->circ_lib(), 'bill.prohibit_negative_balance_default');
+            $prohibit_neg_balance_overdues = (
+                $U->ou_ancestor_setting($circ->circ_lib(), 'bill.prohibit_negative_balance_on_overdues')
+                ||
+                $U->ou_ancestor_setting($circ->circ_lib(), 'bill.prohibit_netgative_balance_default')
+            );
+            $neg_balance_interval_default = $U->ou_ancestor_setting(
+                $circ->circ_lib(), 'bill.negative_balance_interval_default');
+            $neg_balance_interval_overdues = (
+                $U->ou_ancestor_setting($circ->circ_lib(), 'bill.negative_balance_interval_on_overdues')
+                ||
+                $U->ou_ancestor_setting($circ->circ_lib(), 'bill.negative_balance_interval_default')
+            );
+            # settings for lost come from copy circlib.
+            $prohibit_neg_balance_lost = (
+                $U->ou_ancestor_setting($copy->circ_lib(), 'bill.prohibit_negative_balance_on_lost')
+                ||
+                $U->ou_ancestor_setting($copy->circ_lib(), 'bill.prohibit_negative_balance_default')
+            );
+            $neg_balance_interval_lost = (
+                $U->ou_ancestor_setting($copy->circ_lib(), 'bill.negative_balance_interval_on_lost')
+                ||
+                $U->ou_ancestor_setting($copy->circ_lib(), 'bill.negative_balance_interval_default')
+            );
+        } else {
+            # We only care about defaults, and they come from the
+            # billing location.
+            $prohibit_neg_balance_default = $U->ou_ancestor_setting(
+                $grocery->billing_location(), 'bill.prohibit_negative_balance_default');
+            $neg_balance_interval_default = $U->ou_ancestor_setting(
+            $grocery->billing_location(), 'bill.negative_balance_interval_default');
+        }
+
+        # Get the bill_payment_map for the transaction.
+        my $bpmap = $class->bill_payment_map_for_xact($e, $mbt);
+
+        # Get the bills for this transaction from the main list of bills.
+        my @xact_bills = grep {$_->xact() == $xactid} @$bills;
+        # Handle each bill in turn.
+        foreach my $bill (@xact_bills) {
+            # As the total open amount on the transaction will change
+            # as each bill is voided, we'll just recalculate it for
+            # each bill.
+            my $xact_total = 0;
+            map {$xact_total += $_->{bill}->amount()} @$bpmap;
+
+            # Get the bill_payment_map entry for this bill:
+            my ($bpentry) = grep {$_->{bill}->id() == $bill->id()} @$bpmap;
+
+            # From here on out, use the bill object from the bill
+            # payment map entry.
+            $bill = $bpentry->{bill};
+
+            # The amount to void is the non-voided balance on the
+            # bill. It should never be less than zero.
+            my $amount_to_void = $U->fpdiff($bpentry->{bill_amount},$bpentry->{void_amount});
+
+            # Check if this bill is already voided.  We don't allow
+            # "double" voids regardless of settings.  The old code
+            # made it impossible to void an already voided bill, so
+            # we're doing the same.
+            if ($amount_to_void <= 0) {
+                my $event = OpenILS::Event->new('BILL_ALREADY_VOIDED', payload => $bill);
+                $e->event($event);
+                return $event;
+            }
+
+            # If we're voiding a circulation-related bill we have
+            # stuff to check.
+            if ($circ) {
+                if ($amount_to_void > $xact_total) {
+                    my $btype = $bill->btype();
+                    if ($btype == 1) {
+                        # Overdues
+                        $amount_to_void = $xact_total unless(_check_payment_interval($bpentry, $neg_balance_interval_overdues));
+                        $amount_to_void = $xact_total if ($U->is_true($prohibit_neg_balance_overdues));
+                    } elsif ($btype == 3 || $btype == 10) {
+                        # Lost or Long Overdue
+                        $amount_to_void = $xact_total unless(_check_payment_interval($bpentry, $neg_balance_interval_lost));
+                        $amount_to_void = $xact_total if ($U->is_true($prohibit_neg_balance_lost));
+                    } else {
+                        # Any other bill that we're trying to void.
+                        $amount_to_void = $xact_total unless(_check_payment_interval($bpentry, $neg_balance_interval_default));
+                        $amount_to_void = $xact_total if ($U->is_true($prohibit_neg_balance_default));
+                    }
+                }
+            } else {
+                # Grocery bills are simple by comparison.
+                if ($amount_to_void > $xact_total) {
+                    $amount_to_void = $xact_total unless(_check_payment_interval($bpentry, $neg_balance_interval_default));
+                    $amount_to_void = $xact_total if ($U->is_true($prohibit_neg_balance_default));
+                }
+            }
+
+            # Create the void payment if necessary:
+            if ($amount_to_void > 0) {
+                my $payobj = Fieldmapper::money::adjustment_payment->new;
+                $payobj->amount($amount_to_void);
+                $payobj->amount_collected($amount_to_void);
+                $payobj->xact($xactid);
+                $payobj->accepting_usr($e->requestor->id);
+                $payobj->payment_ts('now');
+                $payobj->billing($bill->id());
+                $payobj->note($note) if ($note);
+                $e->create_money_adjustment_payment($payobj) or return $e->die_event;
+                # Adjust our bill_payment_map
+                $bpentry->{void_amount} += $amount_to_void;
+                push @{$bpentry->{voids}}, $payobj;
+                # Should come to zero:
+                my $new_bill_amount = $U->fpdiff($bill->amount(),$amount_to_void);
+                $bill->amount($new_bill_amount);
+            }
+        }
+
+        my $org = $U->xact_org($xactid, $e);
+        $users{$mbt->usr} = {} unless $users{$mbt->usr};
+        $users{$mbt->usr}->{$org} = 1;
+
+        my $evt = $U->check_open_xact($e, $xactid, $mbt);
+        return $evt if $evt;
+    }
+
+    # calculate penalties for all user/org combinations
+    for my $user_id (keys %users) {
+        for my $org_id (keys %{$users{$user_id}}) {
+            OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $org_id);
+        }
+    }
+
+    return 1;
+}
+
+# A helper function to check if the payments on a bill are within the
+# range of a given interval.  The first argument is the entry hash
+# from the bill payment map for the bill to check and the second
+# argument is the interval.  It returns true (1) if any of the bills
+# are within range of the interval, or false (0) otherwise.  It also
+# returns true if the interval argument is undefined or empty, or if
+# the bill has no payments whatsoever.  It will return false if the
+# entry has no payments other than voids.
+sub _check_payment_interval {
+    my ($entry, $interval) = @_;
+    my $result = ($interval ? 0 : 1);
+
+    # A check to see if we were given the settings hash or the value:
+    if (ref($interval) eq 'HASH') {
+        $interval = $interval->{value};
+    }
+
+    if ($interval && $entry && $entry->{payments} && @{$entry->{payments}}) {
+        my $interval_secs = interval_to_seconds($interval);
+        my @pay_dates = map {$_->payment_ts()} sort {$b->payment_ts() cmp $a->payment_ts()}  grep {$_->payment_type() ne 'adjustment_payment'} @{$entry->{payments}};
+        if (@pay_dates) {
+            # Since we've sorted the payment dates from highest to
+            # lowest, we really only need to check the 0th one.
+            my $payment_date = DateTime::Format::ISO8601->parse_datetime(cleanse_ISO8601($pay_dates[0]))->epoch;
+            my $now = time;
+            $result = 1 if ($payment_date + $interval_secs >= $now);
+        }
+    } elsif ($interval && (!$entry->{payments} || !@{$entry->{payments}})) {
+        $result = 1;
+    }
+
+    return $result;
+}
+
 1;
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Money.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Money.pm
index 6c59ad1..05cc461 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Money.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Money.pm
@@ -17,8 +17,10 @@ package OpenILS::Application::Circ::Money;
 use base qw/OpenILS::Application/;
 use strict; use warnings;
 use OpenILS::Application::AppUtils;
+use OpenILS::Application::Circ::CircCommon;
 my $apputils = "OpenILS::Application::AppUtils";
 my $U = "OpenILS::Application::AppUtils";
+my $CC = "OpenILS::Application::Circ::CircCommon";
 
 use OpenSRF::EX qw(:try);
 use OpenILS::Perm;
@@ -482,7 +484,7 @@ sub make_payments {
             # close if no circulation transaction is present,
             # otherwise we check if the circulation is in a state that
             # allows itself to be closed.
-            if (!$circ || OpenILS::Application::Circ::CircCommon->can_close_circ($e, $circ)) {
+            if (!$circ || $CC->can_close_circ($e, $circ)) {
                 $trans = $e->retrieve_money_billable_transaction($transid);
                 $trans->xact_finish("now");
                 if (!$e->update_money_billable_transaction($trans)) {
@@ -904,46 +906,16 @@ __PACKAGE__->register_method(
 );
 sub void_bill {
     my( $s, $c, $authtoken, @billids ) = @_;
-
-    my $e = new_editor( authtoken => $authtoken, xact => 1 );
-    return $e->die_event unless $e->checkauth;
-    return $e->die_event unless $e->allowed('VOID_BILLING');
-
-    my %users;
-    for my $billid (@billids) {
-
-        my $bill = $e->retrieve_money_billing($billid)
-            or return $e->die_event;
-
-        my $xact = $e->retrieve_money_billable_transaction($bill->xact)
-            or return $e->die_event;
-
-        if($U->is_true($bill->voided)) {
-            $e->rollback;
-            return OpenILS::Event->new('BILL_ALREADY_VOIDED', payload => $bill);
-        }
-
-        my $org = $U->xact_org($bill->xact, $e);
-        $users{$xact->usr} = {} unless $users{$xact->usr};
-        $users{$xact->usr}->{$org} = 1;
-
-        $bill->voided('t');
-        $bill->voider($e->requestor->id);
-        $bill->void_time('now');
-    
-        $e->update_money_billing($bill) or return $e->die_event;
-        my $evt = $U->check_open_xact($e, $bill->xact, $xact);
-        return $evt if $evt;
-    }
-
-    # calculate penalties for all user/org combinations
-    for my $user_id (keys %users) {
-        for my $org_id (keys %{$users{$user_id}}) {
-            OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $org_id);
-        }
+    my $editor = new_editor(authtoken=>$authtoken, xact=>1);
+    my $rv = $CC->real_void_bills($editor, \@billids);
+    if (ref($rv) eq 'HASH') {
+        # We got an event.
+        $editor->rollback();
+    } else {
+        # We should have gotten 1.
+        $editor->commit();
     }
-    $e->commit;
-    return 1;
+    return $rv;
 }
 
 
diff --git a/Open-ILS/src/sql/Pg/080.schema.money.sql b/Open-ILS/src/sql/Pg/080.schema.money.sql
index fc0cfe3..2becfb5 100644
--- a/Open-ILS/src/sql/Pg/080.schema.money.sql
+++ b/Open-ILS/src/sql/Pg/080.schema.money.sql
@@ -540,6 +540,20 @@ CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.forgive_payment FOR EAC
 CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.forgive_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_update ('forgive_payment');
 CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.forgive_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_del ('forgive_payment');
 
+CREATE TABLE money.adjustment_payment (
+    billing BIGINT REFERENCES money.billing (id) ON DELETE SET NULL
+) INHERITS (money.bnm_payment);
+ALTER TABLE money.adjustment_payment ADD PRIMARY KEY (id);
+CREATE INDEX money_adjustment_id_idx ON money.adjustment_payment (id);
+CREATE INDEX money_adjustment_payment_xact_idx ON money.adjustment_payment (xact);
+CREATE INDEX money_adjustment_payment_bill_idx ON money.adjustment_payment (billing);
+CREATE INDEX money_adjustment_payment_payment_ts_idx ON money.adjustment_payment (payment_ts);
+CREATE INDEX money_adjustment_payment_accepting_usr_idx ON money.adjustment_payment (accepting_usr);
+
+CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.adjustment_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_add ('adjustment_payment');
+CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.adjustment_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_update ('adjustment_payment');
+CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.adjustment_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_del ('adjustment_payment');
+
 
 CREATE TABLE money.work_payment () INHERITS (money.bnm_payment);
 ALTER TABLE money.work_payment ADD PRIMARY KEY (id);
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 bac8cfc..97b9537 100644
--- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql
+++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
@@ -5007,6 +5007,66 @@ INSERT into config.org_unit_setting_type
         'coust', 'description'),
     'bool', null)
 
+,(  'bill.prohibit_negative_balance_default', 'finance',
+    oils_i18n_gettext(
+        'bill.prohibit_negative_balance_default',
+        'Prohibit negative balance on bills (DEFAULT)',
+        'coust', 'label'),
+    oils_i18n_gettext(
+        'bill.prohibit_negative_balance_default',
+        'Default setting to prevent credits on circulation related bills',
+        'coust', 'description'),
+    'bool', null)
+,(  'bill.prohibit_negative_balance_on_overdues', 'finance',
+    oils_i18n_gettext(
+        'bill.prohibit_negative_balance_on_overdues',
+        'Prohibit negative balance on bills for overdue materials',
+        'coust', 'label'),
+    oils_i18n_gettext(
+        'bill.prohibit_negative_balance_on_overdues',
+        'Prevent credits on bills for overdue materials',
+        'coust', 'description'),
+    'bool', null)
+,(  'bill.prohibit_negative_balance_on_lost', 'finance',
+    oils_i18n_gettext(
+        'bill.prohibit_negative_balance_on_lost',
+        'Prohibit negative balance on bills for lost materials',
+        'coust', 'label'),
+    oils_i18n_gettext(
+        'bill.prohibit_negative_balance_on_lost',
+        'Prevent credits on bills for lost/long overdue materials',
+        'coust', 'description'),
+    'bool', null)
+,(  'bill.negative_balance_interval_default', 'finance',
+    oils_i18n_gettext(
+        'bill.negative_balance_interval_default',
+        'Negative Balance Interval (DEFAULT)',
+        'coust', 'label'),
+    oils_i18n_gettext(
+        'bill.negative_balance_interval_default',
+        'Amount of time after which no negative balances or credits are allowed on circulation bills',
+        'coust', 'description'),
+    'interval', null)
+,(  'bill.negative_balance_interval_on_overdues', 'finance',
+    oils_i18n_gettext(
+        'bill.negative_balance_interval_on_overdues',
+        'Negative Balance Interval for Overdues',
+        'coust', 'label'),
+    oils_i18n_gettext(
+        'bill.negative_balance_interval_on_overdues',
+        'Amount of time after which no negative balances or credits are allowed on bills for overdue materials',
+        'coust', 'description'),
+    'interval', null)
+,(  'bill.negative_balance_interval_on_lost', 'finance',
+    oils_i18n_gettext(
+        'bill.negative_balance_interval_on_lost',
+        'Negative Balance Interval for Lost',
+        'coust', 'label'),
+    oils_i18n_gettext(
+        'bill.negative_balance_interval_on_lost',
+        'Amount of time after which no negative balances or credits are allowed on bills for lost/long overdue materials',
+        'coust', 'description'),
+    'interval', null)
 ;
 
 UPDATE config.org_unit_setting_type
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.conditional_negative_balance.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.conditional_negative_balance.sql
new file mode 100644
index 0000000..1d03ec9
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.conditional_negative_balance.sql
@@ -0,0 +1,90 @@
+BEGIN;
+
+-- SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+CREATE TABLE money.adjustment_payment (
+    billing BIGINT REFERENCES money.billing (id) ON DELETE SET NULL
+) INHERITS (money.bnm_payment);
+ALTER TABLE money.adjustment_payment ADD PRIMARY KEY (id);
+CREATE INDEX money_adjustment_id_idx ON money.adjustment_payment (id);
+CREATE INDEX money_adjustment_payment_xact_idx ON money.adjustment_payment (xact);
+CREATE INDEX money_adjustment_payment_bill_idx ON money.adjustment_payment (billing);
+CREATE INDEX money_adjustment_payment_payment_ts_idx ON money.adjustment_payment (payment_ts);
+CREATE INDEX money_adjustment_payment_accepting_usr_idx ON money.adjustment_payment (accepting_usr);
+
+CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.adjustment_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_add ('adjustment_payment');
+CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.adjustment_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_update ('adjustment_payment');
+CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.adjustment_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_del ('adjustment_payment');
+
+-- Insert new org. unit settings.
+INSERT INTO config.org_unit_setting_type 
+       (name, grp, datatype, label, description)
+VALUES
+       ('bill.prohibit_negative_balance_default',
+        'finance', 'bool',
+        oils_i18n_gettext(
+            'bill.prohibit_negative_balance_default',
+            'Prohibit negative balance on bills (DEFAULT)',
+            'coust', 'label'),
+        oils_i18n_gettext(
+            'bill.prohibit_negative_balance_default',
+            'Default setting to prevent credits on circulation related bills',
+            'coust', 'description')
+       ),
+       ('bill.prohibit_negative_balance_on_overdues',
+        'finance', 'bool',
+        oils_i18n_gettext(
+            'bill.prohibit_negative_balance_on_overdues',
+            'Prohibit negative balance on bills for overdue materials',
+            'coust', 'label'),
+        oils_i18n_gettext(
+            'bill.prohibit_negative_balance_on_overdues',
+            'Prevent credits on bills for overdue materials',
+            'coust', 'description')
+       ),
+       ('bill.prohibit_negative_balance_on_lost',
+        'finance', 'bool',
+        oils_i18n_gettext(
+            'bill.prohibit_negative_balance_on_lost',
+            'Prohibit negative balance on bills for lost materials',
+            'coust', 'label'),
+        oils_i18n_gettext(
+            'bill.prohibit_negative_balance_on_lost',
+            'Prevent credits on bills for lost/long-overde materials',
+            'coust', 'description')
+       ),
+       ('bill.negative_balance_interval_default',
+        'finance', 'interval',
+        oils_i18n_gettext(
+            'bill.negative_balance_interval_default',
+            'Negative Balance Interval (DEFAULT)',
+            'coust', 'label'),
+        oils_i18n_gettext(
+            'bill.negative_balance_interval_default',
+            'Amount of time after which no negative balances or credits are allowed on circulation bills',
+            'coust', 'description')
+       ),
+       ('bill.negative_balance_interval_on_overdues',
+        'finance', 'interval',
+        oils_i18n_gettext(
+            'bill.negative_balance_interval_on_overdues',
+            'Negative Balance Interval for Overdues',
+            'coust', 'label'),
+        oils_i18n_gettext(
+            'bill.negative_balance_interval_on_overdues',
+            'Amount of time after which no negative balances or credits are allowed on bills for overdue materials',
+            'coust', 'description')
+       ),
+       ('bill.negative_balance_interval_on_lost',
+        'finance', 'interval',
+        oils_i18n_gettext(
+            'bill.negative_balance_interval_on_lost',
+            'Negative Balance Interval for Lost',
+            'coust', 'label'),
+        oils_i18n_gettext(
+            'bill.negative_balance_interval_on_lost',
+            'Amount of time after which no negative balances or credits are allowed on bills for lost/long overdue materials',
+            'coust', 'description')
+       );
+
+COMMIT;

-----------------------------------------------------------------------

Summary of changes:
 Open-ILS/examples/fm_IDL.xml                       |   27 +
 Open-ILS/src/extras/ils_events.xml                 |    2 +-
 .../perlmods/lib/OpenILS/Application/AppUtils.pm   |   21 +
 .../lib/OpenILS/Application/Cat/AssetCommon.pm     |    4 +-
 .../src/perlmods/lib/OpenILS/Application/Circ.pm   |    6 +-
 .../lib/OpenILS/Application/Circ/CircCommon.pm     |  487 ++++++-
 .../lib/OpenILS/Application/Circ/Circulate.pm      |   83 +-
 .../perlmods/lib/OpenILS/Application/Circ/Money.pm |   52 +-
 .../lib/OpenILS/Application/Storage/CDBI/money.pm  |    8 +
 .../perlmods/live_t/09-lp1198465_neg_balances.t    | 1525 ++++++++++++++++++++
 Open-ILS/src/sql/Pg/002.schema.config.sql          |    2 +-
 Open-ILS/src/sql/Pg/080.schema.money.sql           |   14 +
 Open-ILS/src/sql/Pg/950.data.seed-values.sql       |   60 +
 .../live_t/lp1198465_run_this_before_livetests.sql |  194 +++
 .../0921.data.conditional_negative_balance.sql     |   90 ++
 15 files changed, 2473 insertions(+), 102 deletions(-)
 create mode 100644 Open-ILS/src/perlmods/live_t/09-lp1198465_neg_balances.t
 create mode 100644 Open-ILS/src/sql/Pg/live_t/lp1198465_run_this_before_livetests.sql
 create mode 100644 Open-ILS/src/sql/Pg/upgrade/0921.data.conditional_negative_balance.sql


hooks/post-receive
-- 
Evergreen ILS


More information about the open-ils-commits mailing list