From 4144e25e10351cf4fd397a6dd3010b93e5d98c8d Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Tue, 7 Apr 2015 15:47:10 -0400 Subject: [PATCH] LP#1440114 Direct charge blanket orders Support "blanket" (long-lived, multi-invoice) orders via a new "blanket" boolean on invoice item types. Blanket charges can be invoiced multiple times by creating a new fund_debit for each invoice item linked to a blanket po_item. This change also adds the amounts paid over time for blanket charges to the Amount Paid summary information for purchase orders containing the charges. Adds a new Invoice Item Type of "Blanket Order". Signed-off-by: Bill Erickson Signed-off-by: Kathy Lussier --- Open-ILS/examples/fm_IDL.xml | 1 + .../lib/OpenILS/Application/Acq/Financials.pm | 38 ++++++++++++- .../lib/OpenILS/Application/Acq/Invoice.pm | 57 +++++++++++++++++-- Open-ILS/src/sql/Pg/200.schema.acq.sql | 5 +- Open-ILS/src/sql/Pg/950.data.seed-values.sql | 3 + .../XXXX.schema.acq_blanket_orders.sql | 10 ++++ .../upgrade/YYYY.data.acq_blanket_order.sql | 8 +++ 7 files changed, 113 insertions(+), 9 deletions(-) create mode 100644 Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq_blanket_orders.sql create mode 100644 Open-ILS/src/sql/Pg/upgrade/YYYY.data.acq_blanket_order.sql diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index 4686e7febe..e3453934a7 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -1437,6 +1437,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Financials.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Financials.pm index 5d74b11ce3..28b4f512f8 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Financials.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Financials.pm @@ -927,13 +927,45 @@ sub build_price_summary { }, where => {'+acqpoi' => {purchase_order => $po_id}} }); + + # debits for invoice items linked to "blanket" po_items are + # considered part of the PO. We are not duplicating debits + # here with po_item debits, because blanket po_item debits + # plus related invoice_item debits are cumulitive. + my $inv_data = $e->json_query({ + select => { + acqii => [ + 'amount_paid', + {column => 'id', alias => 'item_id'} + ], + aiit => ['blanket'], + acqfdeb => [ + 'encumbrance', + {column => 'amount', alias => 'debit_amount'} + ] + }, + from => { + acqii => { + acqfdeb => { + type => 'left', + fkey => 'fund_debit', + field => 'id' + }, + aiit => {} + } + }, + where => { + '+acqii' => {purchase_order => $po_id}, + '+aiit' => {blanket => 't'} + } + }); # sum amounts debited (for activated PO's) and amounts estimated # (for pending PO's) for all lineitem_details and po_items. my ($enc, $spent, $estimated) = (0, 0, 0); - for my $deb (@$li_data, @$item_data) { + for my $deb (@$li_data, @$item_data, @$inv_data) { if (defined $deb->{debit_amount}) { # could be $0 # we have a debit, treat it as authoritative. @@ -954,7 +986,9 @@ sub build_price_summary { # us the total esimated amount. $estimated += ( - $deb->{estimated_unit_price} || $deb->{estimated_cost} || 0 + $deb->{estimated_unit_price} || + $deb->{estimated_cost} || + $deb->{amount_paid} || 0 ); } } diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Invoice.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Invoice.pm index b14b9dbd8f..aa3bf49c7f 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Invoice.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Invoice.pm @@ -96,13 +96,14 @@ sub build_invoice_impl { if ($items) { for my $item (@$items) { $item->invoice($invoice->id); + + # future: cache item types + my $item_type = $e->retrieve_acq_invoice_item_type( + $item->inv_item_type) or return $e->die_event; if ($item->isnew) { $e->create_acq_invoice_item($item) or return $e->die_event; - # future: cache item types - my $item_type = $e->retrieve_acq_invoice_item_type( - $item->inv_item_type) or return $e->die_event; # This following complex conditional statement effecively means: # 1) Items with item_types that are prorate are handled @@ -124,7 +125,20 @@ sub build_invoice_impl { or return $e->die_event; $debit = $e->retrieve_acq_fund_debit($po_item->fund_debit) or return $e->die_event; - } else { + + if ($U->is_true($item_type->blanket)) { + # Each payment toward a blanket charge results + # in a new debit to track the payment and + # decreasing the (encumbered) amount on the + # origin po-item debit by the amount paid. + + $debit->amount($debit->amount - $item->amount_paid); + $e->update_acq_fund_debit($debit) or return $e->die_event; + $debit = undef; + } + } + + if (!$debit) { $debit = Fieldmapper::acq::fund_debit->new; $debit->isnew(1); } @@ -159,10 +173,26 @@ sub build_invoice_impl { my $debit = $e->retrieve_acq_fund_debit($item->fund_debit); $debit->encumbrance('t'); $e->update_acq_fund_debit($debit) or return $e->die_event; + } elsif ($item->fund_debit) { - $e->delete_acq_fund_debit($e->retrieve_acq_fund_debit($item->fund_debit)) - or return $e->die_event; + + my $inv_debit = $e->retrieve_acq_fund_debit($item->fund_debit); + + if ($U->is_true($item_type->blanket)) { + # deleting a payment against a blanket charge means + # we have to re-encumber the paid amount by adding + # it back to the debit linked to the source po_item. + + my $po_debit = $e->retrieve_acq_fund_debit($item->po_item->fund_debit); + $po_debit->amount($po_debit->amount + $inv_debit->amount); + + $e->update_acq_fund_debit($po_debit) + or return $e->die_event; + } + + $e->delete_acq_fund_debit($inv_debit) or return $e->die_event; } + } elsif ($item->ischanged) { my $debit; @@ -178,6 +208,21 @@ sub build_invoice_impl { return $e->die_event; } + if ($U->is_true($item_type->blanket)) { + # modifying a payment against a blanket charge means + # also modifying the amount encumbered on the source + # debit from the blanket po_item to keep things balanced. + + my $po_debit = $e->retrieve_acq_fund_debit( + $item->po_item->fund_debit); + my $delta = $debit->amount - $item->amount_paid; + $po_debit->amount($po_debit->amount + $delta); + + $e->update_acq_fund_debit($po_debit) + or return $e->die_event; + } + + $debit->amount($item->amount_paid); $debit->fund($item->fund); diff --git a/Open-ILS/src/sql/Pg/200.schema.acq.sql b/Open-ILS/src/sql/Pg/200.schema.acq.sql index c909f89b6a..3405ad253b 100644 --- a/Open-ILS/src/sql/Pg/200.schema.acq.sql +++ b/Open-ILS/src/sql/Pg/200.schema.acq.sql @@ -850,7 +850,10 @@ CREATE INDEX ie_li_idx on acq.invoice_entry (lineitem); CREATE TABLE acq.invoice_item_type ( code TEXT PRIMARY KEY, name TEXT NOT NULL, -- i18n-ize - prorate BOOL NOT NULL DEFAULT FALSE + prorate BOOL NOT NULL DEFAULT FALSE, + blanket BOOL NOT NULL DEFAULT FALSE, + CONSTRAINT aiit_not_blanket_and_prorate + CHECK (blanket IS FALSE OR prorate IS FALSE) ); CREATE TABLE acq.po_item ( 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 3e30e7c8cf..8aee29f6a1 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -9663,6 +9663,9 @@ INSERT INTO acq.invoice_item_type (code,name) VALUES ('SHP',oils_i18n_gettext('S INSERT INTO acq.invoice_item_type (code,name) VALUES ('HND',oils_i18n_gettext('HND', 'Handling Charge', 'aiit', 'name')); INSERT INTO acq.invoice_item_type (code,name) VALUES ('ITM',oils_i18n_gettext('ITM', 'Non-library Item', 'aiit', 'name')); INSERT INTO acq.invoice_item_type (code,name) VALUES ('SUB',oils_i18n_gettext('SUB', 'Serial Subscription', 'aiit', 'name')); +INSERT INTO acq.invoice_item_type (code, blanket, name) VALUES ( + 'BLA', TRUE, oils_i18n_gettext('BLA', 'Blanket Order', 'aiit', 'name')); + INSERT INTO acq.invoice_method (code,name) VALUES ('EDI',oils_i18n_gettext('EDI', 'EDI', 'acqim', 'name')); INSERT INTO acq.invoice_method (code,name) VALUES ('PPR',oils_i18n_gettext('PPR', 'Paper', 'acqit', 'name')); diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq_blanket_orders.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq_blanket_orders.sql new file mode 100644 index 0000000000..4e9df8cd54 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq_blanket_orders.sql @@ -0,0 +1,10 @@ +BEGIN; + +--SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version); + +ALTER TABLE acq.invoice_item_type + ADD COLUMN blanket BOOLEAN NOT NULL DEFAULT FALSE, + ADD CONSTRAINT aiit_not_blanket_and_prorate + CHECK (blanket IS FALSE OR prorate IS FALSE); + +COMMIT; diff --git a/Open-ILS/src/sql/Pg/upgrade/YYYY.data.acq_blanket_order.sql b/Open-ILS/src/sql/Pg/upgrade/YYYY.data.acq_blanket_order.sql new file mode 100644 index 0000000000..b5a2f6dad8 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/YYYY.data.acq_blanket_order.sql @@ -0,0 +1,8 @@ +BEGIN; + +--SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version); + +INSERT INTO acq.invoice_item_type (code, blanket, name) VALUES ( + 'BLA', TRUE, oils_i18n_gettext('BLA', 'Blanket Order', 'aiit', 'name')); + +COMMIT; -- 2.43.2