From 84ee4e7e178f57ebed80003178cc2817a8e9d836 Mon Sep 17 00:00:00 2001 From: erickson Date: Wed, 28 Apr 2010 21:48:21 +0000 Subject: [PATCH] added pcrud entries for po_items, repaired pcrud entries for po notes added logic to create invoice_item's from po_item's. added po_item fund debit creation on po activation added po_items to po money summary added support for handling po_item debits during invoiceing. showing po_item estimated cost summary data in invoice table git-svn-id: svn://svn.open-ils.org/ILS/trunk@16341 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- Open-ILS/examples/fm_IDL.xml | 42 +++++-- .../perlmods/OpenILS/Application/Acq/Financials.pm | 14 ++- .../perlmods/OpenILS/Application/Acq/Invoice.pm | 52 +++++++-- .../src/perlmods/OpenILS/Application/Acq/Order.pm | 20 +++- Open-ILS/web/js/dojo/openils/acq/Lineitem.js | 8 +- Open-ILS/web/js/dojo/openils/acq/nls/acq.js | 4 +- Open-ILS/web/js/ui/default/acq/common/li_table.js | 130 +++------------------ Open-ILS/web/js/ui/default/acq/invoice/view.js | 108 ++++++++++++----- Open-ILS/web/js/ui/default/acq/po/view_po.js | 1 + .../web/templates/default/acq/invoice/view.tt2 | 17 +-- 10 files changed, 210 insertions(+), 186 deletions(-) diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index d1da75aa3e..bd4886dc57 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -5340,6 +5340,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + @@ -5348,6 +5349,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + @@ -5414,18 +5416,20 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - - - - - - - - - - - - + + + + + + + + + + + + + + @@ -5448,6 +5452,20 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + + + + + + + + + + + + + diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Financials.pm b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Financials.pm index bfff04b8c3..db3538e736 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Financials.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Financials.pm @@ -825,7 +825,8 @@ __PACKAGE__->register_method( clear_marc, to clear the MARC data from the lineitem (for reduced bandwidth) li_limit : number of lineitems to return if fleshing line items; li_offset : lineitem offset if fleshing line items - li_order_by : lineitem sort definition if fleshing line items + li_order_by : lineitem sort definition if fleshing line items, + flesh_po_items : po_item objects /, type => 'hash'} ], @@ -884,6 +885,15 @@ sub build_price_summary { "where" => {"+acqpo" => {"id" => $po_id}} }); + # add any debits for non-bib po_items + push(@$debits, @{ + $e->json_query({ + "select" => {"acqfdeb" => [qw/encumbrance amount/]}, + "from" => {acqpoi => 'acqfdeb'}, + "where" => {"+acqpoi" => {"purchase_order" => $po_id}} + }) + }); + my ($enc, $spent) = (0, 0); for my $deb (@$debits) { if($U->is_true($deb->{encumbrance})) { @@ -912,6 +922,8 @@ sub retrieve_purchase_order_impl { push @{$flesh->{"flesh_fields"}->{"acqpo"}}, "provider"; } + push (@{$flesh->{flesh_fields}->{acqpo}}, 'po_items') if $options->{flesh_po_items}; + my $args = (@{$flesh->{"flesh_fields"}->{"acqpo"}}) ? [$po_id, $flesh] : $po_id; diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Invoice.pm b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Invoice.pm index 7842494f37..69e7430208 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Invoice.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Invoice.pm @@ -95,14 +95,26 @@ sub build_invoice_api { # prorated items are handled separately unless($U->is_true($item_type->prorate)) { - my $debit = Fieldmapper::acq::fund_debit->new; + my $debit; + if($item->po_item) { + my $po_item = $e->retrieve_acq_po_item($item->po_item) or return $e->die_event; + $debit = $e->retrieve_acq_fund_debit($po_item->fund_debit) or return $e->die_event; + } else { + $debit = Fieldmapper::acq::fund_debit->new; + $debit->isnew(1); + } $debit->fund($item->fund); $debit->amount($item->amount_paid); $debit->origin_amount($item->amount_paid); $debit->origin_currency_type($e->retrieve_acq_fund($item->fund)->currency_type); # future: cache funds locally $debit->encumbrance('f'); $debit->debit_type('direct_charge'); - $e->create_acq_fund_debit($debit) or return $e->die_event; + + if($debit->isnew) { + $e->create_acq_fund_debit($debit) or return $e->die_event; + } else { + $e->update_acq_fund_debit($debit) or return $e->die_event; + } $item->fund_debit($debit->id); $e->update_acq_invoice_item($item) or return $e->die_event; @@ -112,10 +124,19 @@ sub build_invoice_api { $e->delete_acq_invoice_item($item) or return $e->die_event; - # kill the debit - $e->delete_acq_fund_debit( - $e->retrieve_acq_fund_debit($item->fund_debit) - ) or return $e->die_event; + if($item->po_item and $e->retrieve_acq_po_item($item->po_item)->fund_debit == $item->fund_debit) { + # the debit is attached to the po_item. instead of deleting it, roll it back + # to being an encumbrance. Note: a prorated invoice_item that points to a po_item + # could point to a different fund_debit. We can't go back in time to collect all the + # prorated invoice_items (nor is the caller asking us too), so when that happens, + # just delete the extraneous debit (in the else block). + my $debit = $e->retrieve_acq_fund_debit($item->fund_debit); + $debit->encumbrance('t'); + $e->update_acq_fund_debit($debit) or return $e->die_event; + } else { + $e->delete_acq_fund_debit($e->retrieve_acq_fund_debit($item->fund_debit)) + or return $e->die_event; + } } elsif($item->ischanged) { @@ -257,7 +278,7 @@ sub fetch_invoice_impl { "flesh" => 6, "flesh_fields" => { "acqinv" => ["entries", "items"], - "acqii" => ["fund_debit"], + "acqii" => ["fund_debit", "purchase_order", "po_item"] } } ]; @@ -330,7 +351,16 @@ sub prorate_invoice { my $prorated_cost = ($spent_for_fund / $total_entry_paid) * $full_item_cost; $logger->info("invoice: attaching prorated amount $prorated_amount to fund $fund_id for invoice $invoice_id"); - my $debit = Fieldmapper::acq::fund_debit->new; + my $debit; + if($first_round and $item->po_item) { + # if this item is the result of a PO item, repurpose the original debit + # for the first chunk of the prorated amount + $debit = $e->retrieve_acq_fund_debit($item->po_item->fund_debit); + } else { + $debit = Fieldmapper::acq::fund_debit->new; + $debit->isnew(1); + } + $debit->fund($fund_id); $debit->amount($prorated_amount); $debit->origin_amount($prorated_amount); @@ -338,7 +368,11 @@ sub prorate_invoice { $debit->encumbrance('f'); $debit->debit_type('prorated_charge'); - $e->create_acq_fund_debit($debit) or return $e->die_event; + if($debit->isnew) { + $e->create_acq_fund_debit($debit) or return $e->die_event; + } else { + $e->update_acq_fund_debit($debit) or return $e->die_event; + } $total_debited += $prorated_amount; $total_costed += $prorated_cost; diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Order.pm b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Order.pm index 6217933fa0..d815d341ea 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Order.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Order.pm @@ -2114,6 +2114,22 @@ sub activate_purchase_order_impl { $mgr->respond; } + for my $po_item (@{$e->search_acq_po_item({purchase_order => $po_id})}) { + + my $debit = create_fund_debit( + $mgr, + $dry_run, + debit_type => 'direct_charge', # to match invoicing + origin_amount => $po_item->estimated_cost, + origin_currency_type => $e->retrieve_acq_fund($po_item->fund)->currency_type, + amount => $po_item->estimated_cost, + fund => $po_item->fund + ) or return $e->die_event; + $po_item->fund_debit($debit->id); + $e->update_acq_po_item($po_item) or return $e->die_event; + $mgr->respond; + } + return undef; } @@ -2991,9 +3007,9 @@ sub fetch_and_check_li { my $perms = ($perm_mode eq 'read') ? 'VIEW_PURCHASE_ORDER' : 'CREATE_PURCHASE_ORDER'; return ($li, $e->die_event) unless $e->allowed($perms, $po->ordering_agency); - } elsif(my $li = $li->picklist) { + } elsif(my $pl = $li->picklist) { my $perms = ($perm_mode eq 'read') ? 'VIEW_PICKLIST' : 'CREATE_PICKLIST'; - return ($li, $e->die_event) unless $e->allowed($perms, $li->picklist->org_unit); + return ($li, $e->die_event) unless $e->allowed($perms, $pl->org_unit); } return ($li); diff --git a/Open-ILS/web/js/dojo/openils/acq/Lineitem.js b/Open-ILS/web/js/dojo/openils/acq/Lineitem.js index 877ba546f3..3ce519a2bd 100644 --- a/Open-ILS/web/js/dojo/openils/acq/Lineitem.js +++ b/Open-ILS/web/js/dojo/openils/acq/Lineitem.js @@ -146,9 +146,11 @@ openils.acq.Lineitem.fetchAndRender = function(liId, args, callback) { if(po) { liLink = oilsBasePath + '/acq/po/view/' + po.id() + '/' + lineitem.id(); - var date = dojo.date.stamp.fromISOString(po.order_date()); - if(date) { - orderDate = dojo.date.locale.format(date, {selector:'date'}); + if(po.order_date()) { + var date = dojo.date.stamp.fromISOString(po.order_date()); + if(date) { + orderDate = dojo.date.locale.format(date, {selector:'date'}); + } } } 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 63d60545ca..cc51639815 100644 --- a/Open-ILS/web/js/dojo/openils/acq/nls/acq.js +++ b/Open-ILS/web/js/dojo/openils/acq/nls/acq.js @@ -64,12 +64,12 @@ "${3} Ordered, ${4} Received, ${7} Invoiced, ${8} Claimed, ${9} Cancelled" + "
Estimated $${6}, Encumbered $${16}, Paid $${17}
" + "", '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 ${0} more copies than originally ordered.

To add these items to the original order, " + "select a fund and choose 'Add New Items' below.
After saving the invoice, you may finish editing and importing the new copies from the lineitem details page.", - //'INVOICE_EXTRA_COPIES_CATALOG' : "Add ${0} new copies to the catalog?", + 'INVOICE_ITEM_PO_DETAILS' : "${0}
PO #${3} ${4}
Total Estimated Cost: $${5}", 'UNNAMED': "Unnamed", 'NO_FIND_INVOICE': "Could not find that invoice.\nNote that the Invoice # field is case-sensitive.", 'NO_LI_TO_CLAIM': "You have not selected any lineitems to claim.", diff --git a/Open-ILS/web/js/ui/default/acq/common/li_table.js b/Open-ILS/web/js/ui/default/acq/common/li_table.js index 6563eba0c6..4961b3a3fd 100644 --- a/Open-ILS/web/js/ui/default/acq/common/li_table.js +++ b/Open-ILS/web/js/ui/default/acq/common/li_table.js @@ -1373,6 +1373,21 @@ function AcqLiTable() { } else { searchFilter = null; } + + var readOnly = false; + + // TODO: Add support for changing the owning_lib after real copies have been made. + // owning_lib is order data as much as its item data + if(copy.eg_copy_id() && ['owning_lib', 'location', 'circ_modifier', 'cn_label', 'barcode'].indexOf(field) >= 0) { + readOnly = true; + } + + // TODO: add support for changing the fund after debits have been created + // Note: invoicing allows the change + if(copy.fund_debit() && field == 'fund') { + readOnly = true; + } + var widget = new openils.widget.AutoFieldWidget({ fmObject : copy, fmField : field, @@ -1384,7 +1399,7 @@ function AcqLiTable() { fmClass : 'acqlid', parentNode : dojo.query('[name='+field+']', row)[0], orgLimitPerms : ['CREATE_PICKLIST', 'CREATE_PURCHASE_ORDER'], - readOnly : Boolean(copy.eg_copy_id()) + readOnly : readOnly, }); widget.build( // make sure we capture the value from any async widgets @@ -2325,117 +2340,4 @@ function AcqLiTable() { } ); } - - - /* - this.saveRealCopies = function() { - progressDialog.show(true); - var list = this.realCopyList.filter(function(copy) { return copy.ischanged(); }); - this.pcrud.update(list, {oncomplete: function() { - progressDialog.hide(); - self.show('list'); - }}); - } - - // grab the li-details for this lineitem, grab the linked copies and volumes, add them to the table - this.showRealCopies = function(li) { - while(this.realCopiesTbody.childNodes[0]) - this.realCopiesTbody.removeChild(this.realCopiesTbody.childNodes[0]); - this.show('real-copies'); - - this.realCopyList = []; - this.volCache = {}; - var tabIndex = 1000; - var self = this; - - acqLitSaveRealCopies.onClick = function() { - self.saveRealCopies(); - } - - this._fetchLineitem(li.id(), - function(fullLi) { - li = self.liCache[li.id()] = fullLi; - - self.pcrud.search( - 'acp', { - id : li.lineitem_details().map( - function(item) { return item.eg_copy_id() } - ) - }, { - async : true, - streaming : true, - onresponse : function(r) { - var copy = openils.Util.readResponse(r); - var volId = copy.call_number(); - var volume = self.volCache[volId]; - if(!volume) { - volume = self.volCache[volId] = self.pcrud.retrieve('acn', volId); - } - self.addRealCopy(volume, copy, tabIndex++); - } - } - ); - } - ); - } - - this.addRealCopy = function(volume, copy, tabIndex) { - var row = this.realCopiesRow.cloneNode(true); - this.realCopyList.push(copy); - - var selectNode; - dojo.forEach( - ['owning_lib', 'location', 'circ_modifier', 'label', 'barcode'], - - function(field) { - var isvol = (field == 'owning_lib' || field == 'label'); - var widget = new openils.widget.AutoFieldWidget({ - fmField : field, - fmObject : isvol ? volume : copy, - parentNode : nodeByName(field, row), - readOnly : (field != 'barcode'), - }); - - var widgetDrawn = null; - - if(field == 'barcode') { - - widgetDrawn = function(w, ww) { - var node = w.domNode; - node.setAttribute('tabindex', ''+tabIndex); - - // on enter, select the next barcode input - dojo.connect(w, 'onKeyDown', - function(e) { - if(e.keyCode == dojo.keys.ENTER) { - var ti = node.getAttribute('tabindex'); - var nextNode = dojo.query('[tabindex=' + String(Number(ti) + 1) + ']', self.realCopiesTbody)[0]; - if(nextNode) nextNode.select(); - } - } - ); - - dojo.connect(w, 'onChange', - function(val) { - if(!val || val == copy.barcode()) return; - copy.ischanged(true); - copy.barcode(val); - } - ); - - - if(self.realCopiesTbody.getElementsByTagName('TR').length == 0) - selectNode = node; - } - } - - widget.build(widgetDrawn); - } - ); - - this.realCopiesTbody.appendChild(row); - if(selectNode) selectNode.select(); - }; - */ - } 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 c4770c22df..da87e49dca 100644 --- a/Open-ILS/web/js/ui/default/acq/invoice/view.js +++ b/Open-ILS/web/js/ui/default/acq/invoice/view.js @@ -148,7 +148,10 @@ function doAttachPo() { fieldmapper.standardRequest( ['open-ils.acq', 'open-ils.acq.purchase_order.retrieve'], { async: true, - params: [openils.User.authtoken, attachPo, {flesh_lineitem_ids : true}], + params: [ + openils.User.authtoken, attachPo, + {flesh_lineitem_ids : true, flesh_po_items : true} + ], oncomplete: function(r) { var po = openils.Util.readResponse(r); @@ -168,6 +171,22 @@ function doAttachPo() { addInvoiceEntry(entry); } ); + + dojo.forEach(po.po_items(), + function(poItem) { + var item = new fieldmapper.acqii(); + item.id(virtualId--); + item.isnew(true); + item.fund(poItem.fund()); + item.title(poItem.title()); + item.author(poItem.author()); + item.note(poItem.note()); + item.inv_item_type(poItem.inv_item_type()); + item.purchase_order(po); + item.po_item(poItem); + addInvoiceItem(item); + } + ); } } ); @@ -262,7 +281,7 @@ function addInvoiceItem(item) { var args; if(field == 'title' || field == 'author') { - args = {style : 'width:10em'}; + //args = {style : 'width:10em'}; } else if(field == 'cost_billed' || field == 'amount_paid') { args = {required : true, style : 'width: 6em'}; } @@ -307,35 +326,58 @@ function addInvoiceItem(item) { /* ---------- inv_item_type ------------- */ - registerWidget( - item, - 'inv_item_type', - new openils.widget.AutoFieldWidget({ - fmObject : item, - fmField : 'inv_item_type', - parentNode : nodeByName('inv_item_type', row), - readOnly : invoice && openils.Util.isTrue(invoice.complete()), - dijitArgs : {required : true} - }), - function(w, ww) { - // When the inv_item_type is set to prorate=true, don't allow the user the edit the fund - // since this charge will be prorated against (potentially) multiple funds - dojo.connect(w, 'onChange', - function() { - if(!item.fund_debit()) { - var itemType = itemTypes.filter(function(t) { return (t.code() == w.attr('value')) })[0]; - if(!itemType) return; - if(openils.Util.isTrue(itemType.prorate())) { - fundWidget.widget.attr('disabled', true); - fundWidget.widget.attr('value', ''); - } else { - fundWidget.widget.attr('disabled', false); + if(item.po_item()) { + + // read-only item view for items that were the result of a po-item + var po = item.purchase_order(); + var po_item = item.po_item(); + var node = nodeByName('inv_item_type', row); + var itemType = itemTypes.filter(function(t) { return (t.code() == item.inv_item_type()) })[0]; + + node.innerHTML = dojo.string.substitute( + localeStrings.INVOICE_ITEM_PO_DETAILS, + [ + itemType.name(), + oilsBasePath, + po.id(), + po.name(), + dojo.date.locale.format(dojo.date.stamp.fromISOString(po.order_date()), {selector:'date'}), + po_item.estimated_cost() + ] + ); + + } else { + + registerWidget( + item, + 'inv_item_type', + new openils.widget.AutoFieldWidget({ + fmObject : item, + fmField : 'inv_item_type', + parentNode : nodeByName('inv_item_type', row), + readOnly : invoice && openils.Util.isTrue(invoice.complete()), + dijitArgs : {required : true} + }), + function(w, ww) { + // When the inv_item_type is set to prorate=true, don't allow the user the edit the fund + // since this charge will be prorated against (potentially) multiple funds + dojo.connect(w, 'onChange', + function() { + if(!item.fund_debit()) { + var itemType = itemTypes.filter(function(t) { return (t.code() == w.attr('value')) })[0]; + if(!itemType) return; + if(openils.Util.isTrue(itemType.prorate())) { + fundWidget.widget.attr('disabled', true); + fundWidget.widget.attr('value', ''); + } else { + fundWidget.widget.attr('disabled', false); + } } } - } - ); - } - ); + ); + } + ); + } nodeByName('delete', row).onclick = function() { var cost = widgetRegistry.acqii[item.id()].cost_billed.getFormattedValue(); @@ -387,8 +429,12 @@ function addInvoiceEntry(entry) { function(field) { var dijitArgs = {required : true, constraints : {min: 0}, style : 'width:6em'}; if(entry.isnew() && field == 'phys_item_count') { - // by default, attempt to pay for all received and as-of-yet-un-invoiced items - dijitArgs.value = (Number(li.order_summary().recv_count()) - Number(li.order_summary().invoice_count())) || 0; + // by default, attempt to pay for all non-canceled and as-of-yet-un-invoiced items + var count = Number(li.order_summary().item_count() || 0) - + Number(li.order_summary().cancel_count() || 0) - + Number(li.order_summary().invoice_count() || 0); + if(count < 0) count = 0; + dijitArgs.value = count; } registerWidget( entry, 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 8183cf7d82..c68f1c6368 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 @@ -364,6 +364,7 @@ params: [openils.User.authtoken, {purchase_order:poId}, {flesh_attrs:true, flesh onresponse: function(r) { liTable.show('list'); var li = openils.Util.readResponse(r); + // TODO: Add po_item's to total estimated amount totalEstimated += (Number(li.item_count() || 0) * Number(li.estimated_unit_price() || 0)); liTable.addLineitem(li); }, diff --git a/Open-ILS/web/templates/default/acq/invoice/view.tt2 b/Open-ILS/web/templates/default/acq/invoice/view.tt2 index 6ad0e9a3a4..fffbfb7b0d 100644 --- a/Open-ILS/web/templates/default/acq/invoice/view.tt2 +++ b/Open-ILS/web/templates/default/acq/invoice/view.tt2 @@ -28,8 +28,7 @@ Title Details - # Invoiced - # Paid + # Invoiced / # Paid Billed Paid Detach @@ -42,11 +41,7 @@
- - - - - +  / 
Detach @@ -55,7 +50,7 @@ -

Taxes, Fees, Misc.

+

Direct Charges, Taxes, Fees, etc.

@@ -63,9 +58,8 @@ Charge Type - Title/Description - Author Fund + Title/Description Billed Paid Delete @@ -74,9 +68,8 @@
-
-
+
Delete -- 2.11.0