LP 1198465: Initial tests for conditional negative balances
authorRemington Steed <rjs7@calvin.edu>
Fri, 17 Jul 2015 19:42:37 +0000 (15:42 -0400)
committerBen Shum <bshum@biblio.org>
Tue, 28 Jul 2015 20:24:57 +0000 (16:24 -0400)
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@calvin.edu>
Signed-off-by: Kathy Lussier <klussier@masslnc.org>
Signed-off-by: Ben Shum <bshum@biblio.org>
Open-ILS/src/perlmods/live_t/09-lp1198465_neg_balances.t [new file with mode: 0644]
Open-ILS/src/sql/Pg/live_t/lp1198465_run_this_before_livetests.sql [new file with mode: 0644]

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 (file)
index 0000000..01c4fed
--- /dev/null
@@ -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 (file)
index 0000000..b257b85
--- /dev/null
@@ -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;