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

Evergreen Git git at git.evergreen-ils.org
Wed Aug 19 12:42:36 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  c6a391d74432353548b6fb750cdf422e20b6ea49 (commit)
       via  ae9f8cb239be42d3b3efb870ef449eda8a998fbd (commit)
       via  b393a14dc1e06aff87e30ad4e18b5f5a9b1cbb6b (commit)
       via  2fb0fb38e213177f47156437ccefc90174009d0b (commit)
       via  0938e29d7327ba66d1781275c1f6b9c3716b4cca (commit)
       via  cc61c578b7ebcfdf20d6f83cd91206e3c803d70b (commit)
       via  a58aeba14ecb30e36acee531efc3599648dd3d4d (commit)
       via  4144e25e10351cf4fd397a6dd3010b93e5d98c8d (commit)
       via  b34abf2a95296882452dbe5ed3df2fc1067f395a (commit)
      from  3cd206a5e61f6665420602dd2e9da15270a5b26c (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 c6a391d74432353548b6fb750cdf422e20b6ea49
Author: Bill Erickson <berickxx at gmail.com>
Date:   Wed Aug 19 12:35:55 2015 -0400

    LP#1440114 Stamping upgrade for Blanket PO
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql
index 209df2d..07aa790 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 ('0929', :eg_version); -- dbwells/remington/bshum
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0931', :eg_version); -- berick/kmlussier
 
 CREATE TABLE config.bib_source (
 	id		SERIAL	PRIMARY KEY,
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq_blanket_orders.sql b/Open-ILS/src/sql/Pg/upgrade/0930.schema.acq_blanket_orders.sql
similarity index 76%
rename from Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq_blanket_orders.sql
rename to Open-ILS/src/sql/Pg/upgrade/0930.schema.acq_blanket_orders.sql
index 4e9df8c..1592e51 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq_blanket_orders.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/0930.schema.acq_blanket_orders.sql
@@ -1,6 +1,6 @@
 BEGIN;
 
---SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+SELECT evergreen.upgrade_deps_block_check('0930', :eg_version);
 
 ALTER TABLE acq.invoice_item_type
     ADD COLUMN blanket BOOLEAN NOT NULL DEFAULT FALSE,
diff --git a/Open-ILS/src/sql/Pg/upgrade/YYYY.data.acq_blanket_order.sql b/Open-ILS/src/sql/Pg/upgrade/0931.data.acq_blanket_order.sql
similarity index 70%
rename from Open-ILS/src/sql/Pg/upgrade/YYYY.data.acq_blanket_order.sql
rename to Open-ILS/src/sql/Pg/upgrade/0931.data.acq_blanket_order.sql
index b5a2f6d..355151a 100644
--- a/Open-ILS/src/sql/Pg/upgrade/YYYY.data.acq_blanket_order.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/0931.data.acq_blanket_order.sql
@@ -1,6 +1,6 @@
 BEGIN;
 
---SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+SELECT evergreen.upgrade_deps_block_check('0931', :eg_version);
 
 INSERT INTO acq.invoice_item_type (code, blanket, name) VALUES (
     'BLA', TRUE, oils_i18n_gettext('BLA', 'Blanket Order', 'aiit', 'name'));

commit ae9f8cb239be42d3b3efb870ef449eda8a998fbd
Author: Bill Erickson <berickxx at gmail.com>
Date:   Tue Aug 18 14:34:16 2015 -0400

    LP#1440114 PO stays open with active blanket charges
    
    When marking the final lineitem on a PO as received, a PO will remain
    open (on-order) if blanket charges which are still encumbered link to
    the PO.
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Order.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Order.pm
index 69be3d7..d3a106e 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Order.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Order.pm
@@ -1180,8 +1180,8 @@ sub create_purchase_order {
 }
 
 # ----------------------------------------------------------------------------
-# if all of the lineitems for this PO are received,
-# mark the PO as received
+# if all of the lineitems for this PO are received and no 
+# blanket charges are still encumbered, mark the PO as received.
 # ----------------------------------------------------------------------------
 sub check_purchase_order_received {
     my($mgr, $po_id) = @_;
@@ -1194,6 +1194,21 @@ sub check_purchase_order_received {
     my $po = $mgr->editor->retrieve_acq_purchase_order($po_id);
     return $po if @$non_recv_li;
 
+    # avoid marking the PO as received if any blanket charges
+    # are still encumbered.
+    my $blankets = $mgr->editor->json_query({
+        select => {acqpoi => ['id']},
+        from => {
+            acqpoi => {
+                aiit => {filter => {blanket=>'t'}},
+                acqfdeb => {filter => {encumbrance => 't'}}
+            }
+        },
+        where => {'+acqpoi' => {purchase_order => $po_id}}
+    });
+
+    return $po if @$blankets;
+
     $po->state('received');
     return update_purchase_order($mgr, $po);
 }

commit b393a14dc1e06aff87e30ad4e18b5f5a9b1cbb6b
Author: Bill Erickson <berickxx at gmail.com>
Date:   Tue Aug 18 12:42:14 2015 -0400

    LP#1440114 invoice item type prorate/blanket warning
    
    Warn the user that only one of 'prorate' or 'blanket' may be selected
    when creating/editing an invoice item type.  When this happens, the save
    operation is aborted and the user is returned to the edit dialog.
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/conify/global/acq/invoice_item_type.tt2 b/Open-ILS/src/templates/conify/global/acq/invoice_item_type.tt2
index 58fd203..91aba6e 100644
--- a/Open-ILS/src/templates/conify/global/acq/invoice_item_type.tt2
+++ b/Open-ILS/src/templates/conify/global/acq/invoice_item_type.tt2
@@ -21,8 +21,29 @@
 <script type="text/javascript">
     dojo.require("openils.widget.AutoGrid");
     dojo.require("dijit.form.FilteringSelect");
+    dojo.require('openils.PermaCrud');
     openils.Util.addOnLoad(
-        function() { aiitGrid.loadAll({"order_by": {"aiit": "name"}}); }
+        function() { 
+          aiitGrid.loadAll({"order_by": {"aiit": "name"}}); 
+
+          // If either "prorate" or "blanket" are selected, 
+          // the other must be deselected.  
+          // "For neither can live while the other survives...."
+          aiitGrid.onEditPane = function(pane) {
+
+            pane.onSubmit = function(inv_type, ops) {
+
+              if (inv_type.blanket() == 't') {
+                if (inv_type.prorate() == 't') {
+                  alert('[% l('Only one of "prorate" and "blanket" may be selected') %]');
+                  return;
+                }
+              }
+
+              (new openils.PermaCrud())[pane.mode](inv_type, ops);
+            }
+          }
+        }
     );
 </script>
 [% END %]

commit 2fb0fb38e213177f47156437ccefc90174009d0b
Author: Bill Erickson <berickxx at gmail.com>
Date:   Tue Aug 18 10:31:50 2015 -0400

    LP#1440114 Blanket order pgtap tests
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/sql/Pg/t/blanket_order.pg b/Open-ILS/src/sql/Pg/t/blanket_order.pg
new file mode 100644
index 0000000..6f536dc
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/t/blanket_order.pg
@@ -0,0 +1,28 @@
+BEGIN;
+
+SELECT plan(3);
+
+SELECT has_column('acq', 'invoice_item_type', 
+    'blanket', '"blanket" column created');
+
+-- A type cannot be both blanket and prorate
+
+SELECT throws_ok(
+    'INSERT INTO acq.invoice_item_type 
+        (code, name, prorate, blanket) VALUES (''foo'', ''foo'', TRUE, TRUE)',
+    23514 -- check_violation
+);
+
+SELECT is(
+    (SELECT blanket FROM acq.invoice_item_type WHERE code = 'BLA'),
+    TRUE,
+    'New item type is set to blanket=TRUE'
+);
+
+
+SELECT * FROM finish();
+
+ROLLBACK;
+
+
+

commit 0938e29d7327ba66d1781275c1f6b9c3716b4cca
Author: Bill Erickson <berickxx at gmail.com>
Date:   Mon Apr 13 11:11:47 2015 -0400

    LP#1440114 Blanket order release notes
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/docs/RELEASE_NOTES_NEXT/Acquisitions/lp1440114-blanket-orders.txt b/docs/RELEASE_NOTES_NEXT/Acquisitions/lp1440114-blanket-orders.txt
new file mode 100644
index 0000000..8effc61
--- /dev/null
+++ b/docs/RELEASE_NOTES_NEXT/Acquisitions/lp1440114-blanket-orders.txt
@@ -0,0 +1,53 @@
+==== "Blanket" Orders
+
+"Blanket" orders allow staff to invoice an encumbered amount multiple times,
+paying off the charge over a period of time.  The work flow supported by this 
+development assumes staff does not need to track the individual contents of 
+the order, only the amounts encumbered and invoiced in bulk.
+
+===== Example
+
+ . Staff creates PO with a Direct Charge of "Popular Fiction 2015" and
+   a charge type of "Blanket Order".
+ . The amount entered for the charge equals the total amount expected
+   to be charged over the duration of the order.
+ . When a shipment of "Popular Fiction" items arrive, staff creates an 
+   invoice from the "Popular Fiction 2015" PO page and enters the amount 
+   billed/paid for the received shipment under the "Popular Fiction 2015" 
+   charge in the invoice.
+ . When the final shipment arrives, staff select the 'Final invoice
+   for Blanket Order' option on the invoice screen to mark the PO as
+   'received' and drop any remaining encumbrances to $0.
+  .. Alternatively, if the PO needs to be finalized without creating
+     a final invoice, staff can use the new 'Finalize Blanket Order'
+     option on the PO page.
+
+===== New Components/Terminology/Concepts
+
+ * Invoice Item Types have a new flag called 'blanket', available under
+   Admin -> Server Administration -> Acq -> Invoice Item Types in the
+   staff client.
+ * Any direct charge using a 'blanket' item type will create a long-lived
+   charge that can be invoiced multiple times.
+ * Such a charge is considered open until its purchase order is "finalized" 
+   (received).
+ * "Finalizing" a PO changes the PO's state to 'received' (assuming there are
+   no pending lineitems on the PO) and fully dis-encumbers all blanket charges
+   on the PO by setting the fund_debit amount to $0 on the original fund_debit
+   for the charge.
+ * Invoicing a 'blanket' charge does the following under the covers:
+  .. Create an invoice_item to track the payment
+  .. Create a new fund_debit to implement the payment whose amount matches the
+     invoiced amount.
+  .. Subtract the invoiced amount from the fund_debit linked to the original
+     'blanket' po_item, thus reducing the amount encumbered on the charge as 
+     a whole by the invoiced amount.
+ * A PO can have multiple blanket charges.  E.g. you could have a blanket
+   order for "Popular Fiction 2015" and a second charge for "Pop Fiction 
+   20156 Taxes" to track / pay taxes over time on a blanket charge.
+ * A PO can have a mix of lineitems, non-blanket charges, and blanket charges.  
+ * A 'blanket' Invoice Item Type cannot also be a 'prorate' type, since it's
+   nonsensical.  Blanket items are encumbered, whereas prorated items are 
+   only paid at invoice time and never encumbered.
+
+

commit cc61c578b7ebcfdf20d6f83cd91206e3c803d70b
Author: Bill Erickson <berickxx at gmail.com>
Date:   Wed Apr 15 17:44:37 2015 -0400

    LP#1440114 Blanket PO finalize from PO view
    
    Adds new "Finalize Blanket Order" button to purchase page for PO's that
    are activated and have at least on blanket charge.  Finalizing the PO
    disencumbers all blanket charges and marks the PO as complete.
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/acq/po/view.tt2 b/Open-ILS/src/templates/acq/po/view.tt2
index 00a8543..9aa8b11 100644
--- a/Open-ILS/src/templates/acq/po/view.tt2
+++ b/Open-ILS/src/templates/acq/po/view.tt2
@@ -35,6 +35,11 @@
                                 [% l('Activate Without Loading Items') %]
                             </span>
                         </span>
+                        <span class="hidden" id="acq-po-finalize-links" style='padding: 5px'>
+                            <span dojoType="dijit.form.Button" onClick="finalizePo()" jsId="finalizePoButton">
+                                [% l('Finalize Blanket Order') %]
+                            </span>
+                        </span>
                     </td>
                 </tr>
                 <tr>
diff --git a/Open-ILS/web/js/dojo/openils/acq/nls/acq.js b/Open-ILS/web/js/dojo/openils/acq/nls/acq.js
index 9eaeabb..0947f21 100644
--- a/Open-ILS/web/js/dojo/openils/acq/nls/acq.js
+++ b/Open-ILS/web/js/dojo/openils/acq/nls/acq.js
@@ -104,6 +104,7 @@
     "DUPE_PO_NAME_MSG" : "This name is already in use by another PO",
     "DUPE_PO_NAME_LINK" : "View PO",
     "PO_NAME_OPTIONAL" : "${0} (optional)",
+    "FINALIZE_PO" : "Finalize this blanket PO?\nThis will disencumber all blanket charges and mark the PO as received",
     "LI_EXISTING_COPIES" : "There are ${0} existing copies for this bibliographic record at this location",
     "LI_CREATING_ASSETS" : "Creating bib, call number, and copy records...",
     "PO_ACTIVATING" : "Activating purchase order...",
diff --git a/Open-ILS/web/js/ui/default/acq/po/view_po.js b/Open-ILS/web/js/ui/default/acq/po/view_po.js
index 9b76c08..f9f633d 100644
--- a/Open-ILS/web/js/ui/default/acq/po/view_po.js
+++ b/Open-ILS/web/js/ui/default/acq/po/view_po.js
@@ -396,6 +396,7 @@ function renderPo() {
     } else {
         if (PO.order_date()) {
             dojo.byId("acq-po-activate-checking").innerHTML = localeStrings.PO_ALREADY_ACTIVATED;
+            checkCouldBlanketFinalize();
         } else {
             dojo.byId("acq-po-activate-checking").innerHTML = localeStrings.NO;
         }
@@ -519,6 +520,30 @@ function init2() {
     );
 }
 
+function checkCouldBlanketFinalize() {
+
+    if (PO.state() == 'received') return;
+
+    var inv_types = [];
+
+    // get the unique set of invoice item type IDs
+    PO.po_items().forEach(function(item) { 
+        if (inv_types.indexOf(item.inv_item_type()) == -1)
+            inv_types.push(item.inv_item_type());
+    });
+
+    if (inv_types.length == 0) return;
+
+    pcrud.search('aiit', 
+        {code : inv_types, blanket : 't'}, {
+        oncomplete : function(r) {
+            r = openils.Util.readResponse(r);
+            if (r.length == 0) return;
+            openils.Util.show(dojo.byId('acq-po-finalize-links'), 'inline');
+        }
+    });
+}
+
 function checkCouldActivatePo() {
     var d = dojo.byId("acq-po-activate-checking");
     var a = dojo.byId("acq-po-activate-links");  /* <span> not <a> now, but no diff */
@@ -607,6 +632,24 @@ function checkCouldActivatePo() {
     );
 }
 
+function finalizePo() {
+
+    if (!confirm(localeStrings.FINALIZE_PO)) return;
+
+    finalizePoButton.attr('disabled', true);
+
+    fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.purchase_order.blanket.finalize'],
+        {   async : true,
+            params : [openils.User.authtoken, PO.id()],
+            oncomplete : function(r) {
+                if (openils.Util.readResponse(r) == 1) 
+                    location.href = location.href;
+            }
+        }
+    );
+}
+
 function activatePo(noAssets) {
     activatePoButton.attr("disabled", true);
     activatePoNoAssetsButton.attr("disabled", true);

commit a58aeba14ecb30e36acee531efc3599648dd3d4d
Author: Bill Erickson <berickxx at gmail.com>
Date:   Fri Apr 10 12:30:22 2015 -0400

    LP#1440114 Blanket order PO "finalize"
    
    When invoicing a PO that has at least one blanket charge, a new option is
    present which allows staff to indicate that an invoice is the final invoice
    for the PO.  Finalizing a PO results in the following:
    
    1. Encumbrances for all blanket charges on the PO are dropped to $0.
       This is done by setting the amount paid in the original fund_debit
       (linked the blanket po_item) to $0.
    
    2. If no pending lineitems exist on the PO, the PO is marked as received.
       If there are pending lineitems, the state is left untouched.
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 aa3bf49..3980e62 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Invoice.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Invoice.pm
@@ -38,6 +38,7 @@ __PACKAGE__->register_method(
             {desc => q/Invoice/, type => 'number'},
             {desc => q/Entries.  Array of 'acqie' objects/, type => 'array'},
             {desc => q/Items.  Array of 'acqii' objects/, type => 'array'},
+            {desc => q/Finalize PO's.  Array of 'acqpo' ID's/, type => 'array'},
         ],
         return => {desc => 'The invoice w/ entries and items attached', type => 'object', class => 'acqinv'}
     }
@@ -45,7 +46,9 @@ __PACKAGE__->register_method(
 
 
 sub build_invoice_impl {
-    my ($e, $invoice, $entries, $items, $do_commit) = @_;
+    my ($e, $invoice, $entries, $items, $do_commit, $finalize_pos) = @_;
+
+    $finalize_pos ||= [];
 
     if ($invoice->isnew) {
         $invoice->recv_method('PPR') unless $invoice->recv_method;
@@ -128,13 +131,12 @@ sub build_invoice_impl {
 
                         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.
-
+                            # in a new debit to track the payment and a 
+                            # decrease in the original encumbrance by 
+                            # the amount paid on this invoice item
                             $debit->amount($debit->amount - $item->amount_paid);
                             $e->update_acq_fund_debit($debit) or return $e->die_event;
-                            $debit = undef;
+                            $debit = undef; # new debit created below
                         }
                     }
 
@@ -210,16 +212,15 @@ sub build_invoice_impl {
 
                 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.
+                    # modifying the amount encumbered on the source debit
+                    # by the same (but opposite) amount.
 
                     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;
+                    $e->update_acq_fund_debit($po_debit) or return $e->die_event;
                 }
 
 
@@ -239,6 +240,14 @@ sub build_invoice_impl {
         }
     }
 
+    for my $po_id (@$finalize_pos) {
+        my $po = $e->retrieve_acq_purchase_order($po_id) 
+            or return $e->die_event;
+        
+        my $evt = finalize_blanket_po($e, $po);
+        return $evt if $evt;
+    }
+
     $invoice = fetch_invoice_impl($e, $invoice->id);
     if ($do_commit) {
         $e->commit or return $e->die_event;
@@ -248,7 +257,7 @@ sub build_invoice_impl {
 }
 
 sub build_invoice_api {
-    my($self, $conn, $auth, $invoice, $entries, $items) = @_;
+    my($self, $conn, $auth, $invoice, $entries, $items, $finalize_pos) = @_;
 
     my $e = new_editor(xact => 1, authtoken=>$auth);
     return $e->die_event unless $e->checkauth;
@@ -265,7 +274,7 @@ sub build_invoice_api {
     return $e->die_event unless
         $e->allowed('CREATE_INVOICE', $invoice->receiver);
 
-    return build_invoice_impl($e, $invoice, $entries, $items, 1);
+    return build_invoice_impl($e, $invoice, $entries, $items, 1, $finalize_pos);
 }
 
 
@@ -748,4 +757,103 @@ sub print_html_invoice {
     undef;
 }
 
+__PACKAGE__->register_method(
+    method => 'finalize_blanket_po_api',
+    api_name    => 'open-ils.acq.purchase_order.blanket.finalize',
+    signature => {
+        desc => q/
+            1. Set encumbered amount to zero for all blanket po_item's
+            2. If the PO does not have any outstanding lineitems, mark
+               the PO as 'received'.
+        /,
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => q/PO ID/, type => 'number'}
+        ],
+        return => {desc => '1 on success, event on error'}
+    }
+);
+
+sub finalize_blanket_po_api {
+    my ($self, $client, $auth, $po_id) = @_;
+
+    my $e = new_editor(xact => 1, authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
+
+    my $po = $e->retrieve_acq_purchase_order($po_id) or return $e->die_event;
+
+    return $e->die_event unless
+        $e->allowed('CREATE_PURCHASE_ORDER', $po->ordering_agency);
+
+    my $evt = finalize_blanket_po($e, $po);
+    return $evt if $evt;
+
+    $e->commit;
+    return 1;
+}
+
+
+# 1. set any remaining blanket encumbrances to $0.
+# 2. mark the PO as received if there are no pending lineitems.
+sub finalize_blanket_po {
+    my ($e, $po) = @_;
+
+    my $po_id = $po->id;
+
+    # blanket po_items on this PO
+    my $blanket_items = $e->json_query({
+        select => {acqpoi => ['id']},
+        from => {acqpoi => {aiit => {}}},
+        where => {
+            '+aiit' => {blanket => 't'},
+            '+acqpoi' => {purchase_order => $po_id}
+        }
+    });
+
+    for my $item_id (map { $_->{id} } @$blanket_items) {
+
+        my $item = $e->retrieve_acq_po_item([
+            $item_id, {
+                flesh => 1,
+                flesh_fields => {acqpoi => ['fund_debit']}
+            }
+        ]); 
+
+        my $debit = $item->fund_debit or next;
+
+        next unless $U->is_true($debit->encumbrance);
+
+        $debit->amount(0);
+        $debit->encumbrance('f');
+        $e->update_acq_fund_debit($debit) or return $e->die_event;
+    }
+
+    # Number of pending lineitems on this PO. 
+    # If there are any, we don't mark 'received'
+    my $li_count = $e->json_query({
+        select => {jub => [{column => 'id', transform => 'count'}]},
+        from => 'jub',
+        where => {
+            '+jub' => {
+                purchase_order => $po_id,
+                state => 'on-order'
+            }
+        }
+    })->[0];
+    
+    if ($li_count->{count} > 0) {
+        $logger->info("skipping 'received' state change for po $po_id ".
+            "during finalization, because PO has pending lineitems");
+        return undef;
+    }
+
+    $po->state('received');
+    $po->edit_time('now');
+    $po->editor($e->requestor->id);
+
+    $e->update_acq_purchase_order($po) or return $e->die_event;
+
+    return undef;
+}
+
 1;
diff --git a/Open-ILS/src/templates/acq/invoice/view.tt2 b/Open-ILS/src/templates/acq/invoice/view.tt2
index 678eec5..69b09bc 100644
--- a/Open-ILS/src/templates/acq/invoice/view.tt2
+++ b/Open-ILS/src/templates/acq/invoice/view.tt2
@@ -45,6 +45,20 @@
         </div>
     </div>
 
+    <div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+        <div class='hidden' id='oils-acq-final-invoice-pane'>
+            <table>
+                <tbody id='acq-final-invoice-tbody'>
+                    <tr id='acq-final-invoice-row'>
+                        <td>[% l('Final invoice for Blanket order?') %]</td>
+                        <td><div name='po-label'></td>
+                        <td><div name='checkbox'></div></td>
+                    </tr>
+                </tbody>
+            </table>
+        </div>
+    </div>
+
     <!--
     <div dojoType="dijit.layout.ContentPane" layoutAlign="client">
     -->
diff --git a/Open-ILS/web/css/skin/default/acq.css b/Open-ILS/web/css/skin/default/acq.css
index 1887983..1dd1ccb 100644
--- a/Open-ILS/web/css/skin/default/acq.css
+++ b/Open-ILS/web/css/skin/default/acq.css
@@ -284,3 +284,14 @@ span[name="cancel_reason"] { text-decoration: underline; font-weight: bold; }
     font-weight: bold;
     color: red;
 }
+
+#oils-acq-final-invoice-pane {
+    margin-top: 10px;
+    margin-bottom: 10px;
+}
+
+#acq-final-invoice-tbody td {
+    padding: 6px;
+    border: 1px solid #AAA;
+}
+
diff --git a/Open-ILS/web/js/dojo/openils/acq/nls/acq.js b/Open-ILS/web/js/dojo/openils/acq/nls/acq.js
index 0248ad6..9eaeabb 100644
--- a/Open-ILS/web/js/dojo/openils/acq/nls/acq.js
+++ b/Open-ILS/web/js/dojo/openils/acq/nls/acq.js
@@ -68,6 +68,7 @@
     "INVOICE_CONFIRM_PRORATE" : "Prorate charges?\n\nAny subsequent changes to the invoice that would affect prorated amounts should be resolved manually.",
     "INVOICE_EXTRA_COPIES" : "You are attempting to invoice <b>${0}</b> more copies than originally ordered.  <br/><br/>To add these items to the original order, select a fund and choose 'Add New Items' below.  <br/>After saving the invoice, you may finish editing and importing the new copies from the lineitem details page.",
     "INVOICE_ITEM_PO_DETAILS" : "<b>${0}</b><br/><a href='${1}/acq/po/view/${2}'>PO #${3} ${4}</a><br/>Total Estimated Cost: $${5}",
+    "INVOICE_ITEM_PO_LABEL" : "<a href='${0}/acq/po/view/${1}'>PO #${2} ${3}</a><br/>Total Estimated Cost: $${4}",
     "UNNAMED" : "Unnamed",
     "NO_FIND_INVOICE" : "Could not find that invoice.\nNote that the Invoice # field is case-sensitive.",
     "LI_BATCH_UPDATE": "Line item batch update",
diff --git a/Open-ILS/web/js/ui/default/acq/invoice/view.js b/Open-ILS/web/js/ui/default/acq/invoice/view.js
index 313e4d7..4fbc16a 100644
--- a/Open-ILS/web/js/ui/default/acq/invoice/view.js
+++ b/Open-ILS/web/js/ui/default/acq/invoice/view.js
@@ -43,6 +43,7 @@ var focusLineitem;
 var searchInitDone = false;
 var termManager;
 var resultManager;
+var finalizePos = [];
 
 function nodeByName(name, context) {
     return dojo.query('[name='+name+']', context)[0];
@@ -573,6 +574,37 @@ function registerWidget(obj, field, widget, callback) {
     return widget;
 }
 
+var finalInvTbody, finalInvRow;
+var finalInvPoSeen = {};
+function addMarkFinalPO(item, po_item, po_label) {
+
+    if (finalInvPoSeen[po_item.purchase_order()]) return;
+    finalInvPoSeen[po_item.purchase_order()] = true;
+
+    openils.Util.show(dojo.byId('oils-acq-final-invoice-pane'));
+
+    if (!finalInvTbody) {
+        finalInvTbody = dojo.byId('acq-final-invoice-tbody');
+        finalInvRow = finalInvTbody.removeChild(
+            dojo.byId('acq-final-invoice-row'));
+    }
+
+    var row = finalInvRow.cloneNode(true);
+    nodeByName('po-label', row).innerHTML = po_label;
+    var cbox = new dijit.form.CheckBox({}, nodeByName('checkbox', row));
+
+    dojo.connect(cbox, 'onChange', function(set) {
+        if (set) { // add to finalize list
+            finalizePos.push(Number(po_item.purchase_order()));
+        } else { // remove from finalize list
+            finalizePos = finalizePos.filter(
+                function(id) {return id != po_item.purchase_order()});
+        }
+    });
+
+    finalInvTbody.appendChild(row);
+}
+
 function addInvoiceItem(item) {
     itemTbody = dojo.byId('acq-invoice-item-tbody');
     if(itemTemplate == null) {
@@ -667,6 +699,34 @@ function addInvoiceItem(item) {
             ]
         );
 
+        if (openils.Util.isTrue(itemType.blanket()) 
+                && po.state() != 'received') {
+
+            fieldmapper.standardRequest(
+                ['open-ils.acq', 
+                    'open-ils.acq.purchase_order.retrieve.authoritative'],
+                {   async: true,
+                    params: [openils.User.authtoken, po.id(), {
+                        "flesh_price_summary": true
+                    }],
+                    oncomplete: function(r) {
+                        // update the global PO instead of replacing it, since other 
+                        // code outside our control may be referencing it.
+                        var po2 = openils.Util.readResponse(r);
+
+                        var po_label = dojo.string.substitute(
+                            localeStrings.INVOICE_ITEM_PO_LABEL,
+                            [ oilsBasePath, po2.id(), po2.name(), 
+                              orderDate, po2.amount_estimated().toFixed(2)
+                            ]
+                        );
+
+                        addMarkFinalPO(item, po_item, po_label);
+                    }
+                }
+            );
+        }
+
     } else {
 
         registerWidget(
@@ -1059,7 +1119,8 @@ function saveChangesPartTwo(args) {
     fieldmapper.standardRequest(
         ['open-ils.acq', 'open-ils.acq.invoice.update'],
         {
-            params : [openils.User.authtoken, invoice, updateEntries, updateItems],
+            params : [openils.User.authtoken,
+                invoice, updateEntries, updateItems, finalizePos],
             oncomplete : function(r) {
                 progressDialog.hide();
                 var invoice = openils.Util.readResponse(r);

commit 4144e25e10351cf4fd397a6dd3010b93e5d98c8d
Author: Bill Erickson <berickxx at gmail.com>
Date:   Tue Apr 7 15:47:10 2015 -0400

    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 <berickxx at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml
index 4686e7f..e345393 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
 			<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">
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 5d74b11..28b4f51 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 b14b9db..aa3bf49 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 c909f89..3405ad2 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 3e30e7c..8aee29f 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 0000000..4e9df8c
--- /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 0000000..b5a2f6d
--- /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;

commit b34abf2a95296882452dbe5ed3df2fc1067f395a
Author: Bill Erickson <berickxx at gmail.com>
Date:   Wed Apr 8 15:17:01 2015 -0400

    LP#1440114 Remove inv_item via po_item from invoice
    
    Allow invoice_items which were descended from po_item's (direct charges)
    to be removed from invoices.  Prior to this, a JS exception prevented
    removal of these items.
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/acq/invoice/view.js b/Open-ILS/web/js/ui/default/acq/invoice/view.js
index d5cf6f1..313e4d7 100644
--- a/Open-ILS/web/js/ui/default/acq/invoice/view.js
+++ b/Open-ILS/web/js/ui/default/acq/invoice/view.js
@@ -702,11 +702,23 @@ function addInvoiceItem(item) {
 
     nodeByName('delete', row).onclick = function() {
         var cost = widgetRegistry.acqii[item.id()].cost_billed.getFormattedValue();
+
+        var iTypeName = '';
+        if (widgetRegistry.acqii[item.id()].inv_item_type) {
+            iTypeName = widgetRegistry.acqii[item.id()]
+                .inv_item_type.getFormattedValue()
+        } else {
+            // if the invoice_item came from a po_item, the type is
+            // read-only, hence no widget in the registry.  Look up
+            // the name in the cached types list.
+            var itype = itemTypes.filter(
+                function(t) { return (t.code() == item.inv_item_type()) })[0];
+            iTypeName = itype.name();
+        }
+
         var msg = dojo.string.substitute(
-            localeStrings.INVOICE_CONFIRM_ITEM_DELETE, [
-                cost || 0,
-                widgetRegistry.acqii[item.id()].inv_item_type.getFormattedValue() || ''
-            ]
+            localeStrings.INVOICE_CONFIRM_ITEM_DELETE, 
+            [cost || 0, iTypeName || '']
         );
         if(!confirm(msg)) return;
         itemTbody.removeChild(row);

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

Summary of changes:
 Open-ILS/examples/fm_IDL.xml                       |    1 +
 .../lib/OpenILS/Application/Acq/Financials.pm      |   38 ++++-
 .../lib/OpenILS/Application/Acq/Invoice.pm         |  171 ++++++++++++++++++-
 .../perlmods/lib/OpenILS/Application/Acq/Order.pm  |   19 ++-
 Open-ILS/src/sql/Pg/002.schema.config.sql          |    2 +-
 Open-ILS/src/sql/Pg/200.schema.acq.sql             |    5 +-
 Open-ILS/src/sql/Pg/950.data.seed-values.sql       |    3 +
 Open-ILS/src/sql/Pg/t/blanket_order.pg             |   28 ++++
 .../Pg/upgrade/0930.schema.acq_blanket_orders.sql  |   10 ++
 .../sql/Pg/upgrade/0931.data.acq_blanket_order.sql |    8 +
 Open-ILS/src/templates/acq/invoice/view.tt2        |   14 ++
 Open-ILS/src/templates/acq/po/view.tt2             |    5 +
 .../conify/global/acq/invoice_item_type.tt2        |   23 +++-
 Open-ILS/web/css/skin/default/acq.css              |   11 ++
 Open-ILS/web/js/dojo/openils/acq/nls/acq.js        |    2 +
 Open-ILS/web/js/ui/default/acq/invoice/view.js     |   83 +++++++++-
 Open-ILS/web/js/ui/default/acq/po/view_po.js       |   43 +++++
 .../Acquisitions/lp1440114-blanket-orders.txt      |   53 ++++++
 18 files changed, 498 insertions(+), 21 deletions(-)
 create mode 100644 Open-ILS/src/sql/Pg/t/blanket_order.pg
 create mode 100644 Open-ILS/src/sql/Pg/upgrade/0930.schema.acq_blanket_orders.sql
 create mode 100644 Open-ILS/src/sql/Pg/upgrade/0931.data.acq_blanket_order.sql
 create mode 100644 docs/RELEASE_NOTES_NEXT/Acquisitions/lp1440114-blanket-orders.txt


hooks/post-receive
-- 
Evergreen ILS




More information about the open-ils-commits mailing list