LP#1440114 Direct charge blanket orders
authorBill Erickson <berickxx@gmail.com>
Tue, 7 Apr 2015 19:47:10 +0000 (15:47 -0400)
committerBill Erickson <berickxx@gmail.com>
Wed, 19 Aug 2015 16:33:27 +0000 (12:33 -0400)
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 <berickxx@gmail.com>
Signed-off-by: Kathy Lussier <klussier@masslnc.org>
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Financials.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Invoice.pm
Open-ILS/src/sql/Pg/200.schema.acq.sql
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq_blanket_orders.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/YYYY.data.acq_blanket_order.sql [new file with mode: 0644]

index 4686e7f..e345393 100644 (file)
@@ -1437,6 +1437,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                        <field reporter:label="Code" name="code" reporter:selector="name" reporter:datatype="id"/>
                        <field reporter:label="Label" name="name" reporter:datatype="text" oils_persist:i18n="true"/>
                        <field reporter:label="Prorate?" name="prorate" reporter:datatype="bool"/>
+                       <field reporter:label="Blanket?" name="blanket" reporter:datatype="bool"/>
                </fields>
                <links/>
         <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
index 5d74b11..28b4f51 100644 (file)
@@ -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
             );
         }
     }
index b14b9db..aa3bf49 100644 (file)
@@ -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);
 
index c909f89..3405ad2 100644 (file)
@@ -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 (
index 3e30e7c..8aee29f 100644 (file)
@@ -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 (file)
index 0000000..4e9df8c
--- /dev/null
@@ -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 (file)
index 0000000..b5a2f6d
--- /dev/null
@@ -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;