From: senator Date: Fri, 19 Feb 2010 20:00:35 +0000 (+0000) Subject: Acq: complete improvements of receive/unreceive for lineitems and copies X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=66746ae2504375e28a18b33e9a626c5868980263;p=evergreen%2Ftadl.git Acq: complete improvements of receive/unreceive for lineitems and copies When viewing a purchase order, you can now recieve and un-receive individual lineitems or even individual copies, and the PO viewing interface will properly reflect all changes (to the states of the PO, the lineitems, and the copies; to the amounts spent and encumbered; and to the state of widgets that should appear or disappear based on PO/LI/copy state). git-svn-id: svn://svn.open-ils.org/ILS/trunk@15600 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Financials.pm b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Financials.pm index 7b07a9e298..bf298a1296 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Financials.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Financials.pm @@ -1003,6 +1003,41 @@ sub po_perm_failure { return undef; } +sub build_price_summary { + my ($e, $po_id) = @_; + + # fetch the fund debits for this purchase order + my $debits = $e->json_query({ + "select" => {"acqfdeb" => [qw/encumbrance amount/]}, + "from" => { + "acqlid" => { + "jub" => { + "fkey" => "lineitem", + "field" => "id", + "join" => { + "acqpo" => { + "fkey" => "purchase_order", "field" => "id" + } + } + }, + "acqfdeb" => {"fkey" => "fund_debit", "field" => "id"} + } + }, + "where" => {"+acqpo" => {"id" => $po_id}} + }); + + my ($enc, $spent) = (0, 0); + for my $deb (@$debits) { + if($U->is_true($deb->{encumbrance})) { + $enc += $deb->{amount}; + } else { + $spent += $deb->{amount}; + } + } + ($enc, $spent); +} + + sub retrieve_purchase_order_impl { my($e, $po_id, $options) = @_; @@ -1038,31 +1073,7 @@ sub retrieve_purchase_order_impl { } if($$options{flesh_price_summary}) { - - # fetch the fund debits for this purchase order - my $debits = $e->json_query({ - select => {acqfdeb => ["encumbrance", "amount"]}, - from => { - acqlid => { - jub => {fkey => "lineitem", field => "id", - join => {acqpo => {fkey => "purchase_order", field => "id"}} - }, - acqfdeb => {fkey => "fund_debit", field =>"id"} - } - }, - where => {'+acqpo' => {id => $po_id}} - }); - - my $enc = 0; - my $spent = 0; - for my $deb (@$debits) { - if($U->is_true($deb->{encumbrance})) { - $enc += $deb->{amount}; - } else { - $spent += $deb->{amount}; - } - } - + my ($enc, $spent) = build_price_summary($e, $po_id); $po->amount_encumbered($enc); $po->amount_spent($spent); } diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Order.pm b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Order.pm index 7f4847f6a9..41dae23a7d 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Order.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Order.pm @@ -1,4 +1,5 @@ package OpenILS::Application::Acq::BatchManager; +use OpenILS::Application::Acq::Financials; use OpenSRF::AppSession; use OpenSRF::EX qw/:try/; use strict; use warnings; @@ -328,6 +329,23 @@ sub check_import_li_marc_perms { # been received, mark the lineitem as received # returns 1 on non-received, li on received, 0 on error # ---------------------------------------------------------------------------- + +sub describe_affected_po { + my ($e, $po) = @_; + + my ($enc, $spent) = + OpenILS::Application::Acq::Financials::build_price_summary( + $e, $po->id + ); + + +{$po->id => { + "state" => $po->state, + "amount_encumbered" => $enc, + "amount_spent" => $spent + } + }; +} + sub check_lineitem_received { my($mgr, $li_id) = @_; @@ -359,15 +377,13 @@ sub receive_lineitem { $mgr->post_process( sub { create_lineitem_status_events($mgr, $li_id, 'aur.received'); }); my $po; - my $result = {"li" => {$li->id => {"state" => $li->state}}}; return 0 unless $skip_complete_check or ( $po = check_purchase_order_received($mgr, $li->purchase_order) ); - if (ref $po) { - $result->{"po"} = {$po->id => {"state" => $li->state}}; - } + my $result = {"li" => {$li->id => {"state" => $li->state}}}; + $result->{"po"} = describe_affected_po($mgr->editor, $po) if ref $po; return $result; } @@ -497,9 +513,7 @@ sub receive_lineitem_detail { my $li = check_lineitem_received($mgr, $lid->lineitem) or return 0; return 1 if $li == 1; # li not received - my $po = check_purchase_order_received($mgr, $li->purchase_order) or return 0; - return $li if $po == 1; - return $po; + return check_purchase_order_received($mgr, $li->purchase_order) or return 0; } @@ -844,9 +858,9 @@ sub check_purchase_order_received { state => {'!=' => 'received'} }, {idlist=>1}); - return 1 if @$non_recv_li; - my $po = $mgr->editor->retrieve_acq_purchase_order($po_id); + return $po if @$non_recv_li; + $po->state('received'); return update_purchase_order($mgr, $po); } @@ -1555,6 +1569,22 @@ sub receive_po_api { } +# At the moment there's a lack of parallelism between the receive and unreceive +# API methods for POs and the API methods for LIs and LIDs. The methods for +# POs stream back objects as they act, whereas the methods for LIs and LIDs +# atomically return an object that describes only what changed (in LIs and LIDs +# themselves or in the objects to which to LIs and LIDs belong). +# +# The methods for LIs and LIDs work the way they do to faciliate the UI's +# maintaining correct information about the state of these things when a user +# wants to receive or unreceive these objects without refreshing their whole +# display. The UI feature for receiving and un-receiving a whole PO just +# refreshes the whole display, so this absence of parallelism in the UI is also +# relected in this module. +# +# This could be neatened in the future by making POs receive and unreceive in +# the same way the LIs and LIDs do. + __PACKAGE__->register_method( method => 'receive_lineitem_detail_api', api_name => 'open-ils.acq.lineitem_detail.receive', @@ -1564,7 +1594,10 @@ __PACKAGE__->register_method( {desc => 'Authentication token', type => 'string'}, {desc => 'lineitem detail ID', type => 'number'} ], - return => {desc => '1 on success, Event on error'} + return => {desc => + "on success, object describing changes to LID and possibly " . + "to LI and PO; on error, Event" + } } ); @@ -1575,34 +1608,29 @@ sub receive_lineitem_detail_api { return $e->die_event unless $e->checkauth; my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn); - my $lid = $e->retrieve_acq_lineitem_detail([ - $lid_id, { - flesh => 2, - flesh_fields => { - acqlid => ['lineitem'], - jub => ['purchase_order'] - } + my $fleshing = { + "flesh" => 2, "flesh_fields" => { + "acqlid" => ["lineitem"], "jub" => ["purchase_order"] } - ]); + }; + + my $lid = $e->retrieve_acq_lineitem_detail([$lid_id, $fleshing]); return $e->die_event unless $e->allowed( 'RECEIVE_PURCHASE_ORDER', $lid->lineitem->purchase_order->ordering_agency); + # update ... my $recvd = receive_lineitem_detail($mgr, $lid_id) or return $e->die_event; - # What's this business, you ask? We basically want to return a minimal - # set of information about what has changed as a result of the "receive - # lineitem detail" operation; remember: not only does the lineitem detail - # change state, but so might an LI and even a PO, and a good UI will want - # to reflect those changes. - $lid = $e->retrieve_acq_lineitem_detail( - [$lid_id, {"flesh" => 1, "flesh_fields" => {"acqlid" => ["lineitem"]}}] - ); + # .. and re-retrieve + $lid = $e->retrieve_acq_lineitem_detail([$lid_id, $fleshing]); + + # Now build result data structure. my $result = {"lid" => {$lid->id => {"recv_time" => $lid->recv_time}}}; if (ref $recvd) { if ($recvd->class_name =~ /::purchase_order/) { - $result->{"po"} = {"id" => $recvd->id, "state" => $recvd->state}; + $result->{"po"} = describe_affected_po($e, $recvd); $result->{"li"} = { $lid->lineitem->id => {"state" => $lid->lineitem->state} }; @@ -1610,6 +1638,8 @@ sub receive_lineitem_detail_api { $result->{"li"} = {$recvd->id => {"state" => $recvd->state}}; } } + $result->{"po"} ||= + describe_affected_po($e, $lid->lineitem->purchase_order); $e->commit; return $result; @@ -1622,11 +1652,11 @@ __PACKAGE__->register_method( desc => 'Mark a lineitem as received', params => [ {desc => 'Authentication token', type => 'string'}, - {desc => 'lineitem detail ID', type => 'number'} + {desc => 'lineitem ID', type => 'number'} ], return => {desc => - "on success, object containing an LI and possibly a PO; " . - "on error, event" + "on success, object describing changes to LI and possibly PO; " . + "on error, Event" } } ); @@ -1695,7 +1725,10 @@ __PACKAGE__->register_method( {desc => 'Authentication token', type => 'string'}, {desc => 'lineitem detail ID', type => 'number'} ], - return => {desc => '1 on success, Event on error'} + return => {desc => + "on success, object describing changes to LID and possibly " . + "to LI and PO; on error, Event" + } } ); @@ -1740,8 +1773,8 @@ sub rollback_receive_lineitem_detail_api { if ($po->state eq "received") { $po->state("on-order"); $po = update_purchase_order($mgr, $po) or return $e->die_event; - $result->{"po"} = {$po->id => {"state" => $po->state}}; } + $result->{"po"} = describe_affected_po($e, $po); $e->commit and return $result or return $e->die_event; } @@ -1750,12 +1783,15 @@ __PACKAGE__->register_method( method => 'rollback_receive_lineitem_api', api_name => 'open-ils.acq.lineitem.receive.rollback', signature => { - desc => 'Mark a lineitem as received', + desc => 'Mark a lineitem as Un-received', params => [ {desc => 'Authentication token', type => 'string'}, - {desc => 'lineitem detail ID', type => 'number'} + {desc => 'lineitem ID', type => 'number'} ], - return => {desc => 'altered objects on success, event on error'} + return => {desc => + "on success, object describing changes to LI and possibly PO; " . + "on error, Event" + } } ); @@ -1781,8 +1817,8 @@ sub rollback_receive_lineitem_api { if ($po->state eq "received") { $po->state("on-order"); $po = update_purchase_order($mgr, $po) or return $e->die_event; - $result->{"po"} = {$po->id => {"state" => $po->state}}; } + $result->{"po"} = describe_affected_po($e, $po); $e->commit and return $result or return $e->die_event; } 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 969ca48049..586f007228 100644 --- a/Open-ILS/web/js/dojo/openils/acq/nls/acq.js +++ b/Open-ILS/web/js/dojo/openils/acq/nls/acq.js @@ -18,5 +18,7 @@ 'EXPORT_SAVE_DIALOG_TITLE': "Save field values to a file", 'EXPORT_SHORT_LIST': "Not all of the selected items had the attribute '${0}'.\nChoose OK to save those values that could be found.", 'EXPORT_EMPTY_LIST': "No values for attribute '${0}' found.", - 'UNRECEIVE_LI': "Are you sure you want to mark this lineitem as UN-received?" + 'UNRECEIVE_LI': "Are you sure you want to mark this lineitem as UN-received?", + + 'UNRECEIVE_LID': "Are you sure you want to mark this copy as UN-received?", } 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 f728ed9180..873cdffa79 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 @@ -251,7 +251,7 @@ function AcqLiTable() { priceInput.onchange = function() { self.updateLiPrice(priceInput, li) }; // show either "mark received" or "unreceive" as appropriate - this.updateReceivedness(li, row); + this.updateLiReceivedness(li, row); if (!skip_final_placement) { self.tbody.appendChild(row); @@ -261,7 +261,7 @@ function AcqLiTable() { } }; - this.updateReceivedness = function(li, row) { + this.updateLiReceivedness = function(li, row) { if (typeof(row) == "undefined") row = dojo.query('tr[li="' + li.id() + '"]', "acq-lit-tbody")[0]; @@ -281,18 +281,15 @@ function AcqLiTable() { openils.Util.hide(real_copies_link); openils.Util.hide(unrecv_link); openils.Util.show(recv_link, "inline"); - if (typeof(recv_link.onclick) != "function") - recv_link.onclick = function() { self.receiveLi(li); }; + recv_link.onclick = function() { self.issueReceive(li); }; return; case "received": openils.Util.hide(recv_link); openils.Util.show(unrecv_link, "inline"); - if (typeof(unrecv_link.onclick) != "function") { - unrecv_link.onclick = function() { - if (confirm(localeStrings.UNRECEIVE_LI)) - self.unReceiveLi(li); - }; - } + unrecv_link.onclick = function() { + if (confirm(localeStrings.UNRECEIVE_LI)) + self.issueReceive(li, /* rollback */ true); + }; // TODO we should allow editing before receipt, in which case the // test should be "if 1 or more real (acp) copies exist openils.Util.show(real_copies_link); @@ -902,23 +899,50 @@ function AcqLiTable() { } ); - var recv_link = dojo.query('[name=receive]', row)[0]; - if(copy.recv_time()) { - openils.Util.hide(recv_link); - } else { - recv_link.onclick = function() { - self.receiveLid(copy); - openils.Util.hide(recv_link); - } + this.updateLidReceivedness(copy, row); + }; + + this.updateLidReceivedness = function(copy, row) { + if (typeof(row) == "undefined") { + row = dojo.query( + 'tr[copy_id="' + copy.id() + '"]', this.copyTbody + )[0]; } - if(this.isPO) { - openils.Util.hide(dojo.query('[name=delete]', row)[0].parentNode); + var self = this; + var recv_link = nodeByName("receive", row); + var unrecv_link = nodeByName("unreceive", row); + var del_link = nodeByName("delete", row); + + if (this.isPO) { + openils.Util.hide(del_link.parentNode); + + /* Avoid showing (un)receive links for virtual copies */ + if (copy.id() > 0) { + if(copy.recv_time()) { + openils.Util.hide(recv_link); + openils.Util.show(unrecv_link); + unrecv_link.onclick = function() { + if (confirm(localeStrings.UNRECEIVE_LID)) + self.issueReceive(copy, /* rollback */ true); + }; + } else { + openils.Util.hide(unrecv_link); + openils.Util.show(recv_link); + recv_link.onclick = function() { self.issueReceive(copy); }; + } + } else { + openils.Util.hide(unrecv_link); + openils.Util.hide(recv_link); + } } else { - dojo.query('[name=delete]', row)[0].onclick = - function() { self.deleteCopy(row) }; + openils.Util.hide(unrecv_link); + openils.Util.hide(recv_link); + + del_link.onclick = function() { self.deleteCopy(row) }; + openils.Util.show(del_link.parentNode); } - }; + } this.deleteCopy = function(row) { var copy = this.copyCache[row.getAttribute('copy_id')]; @@ -1175,78 +1199,54 @@ function AcqLiTable() { ); } - this.receiveLi = function(li) { - /* (For now) there shall be no marking LIs received except from the - * actual "view PO" interface. */ + this.issueReceive = function(obj, rollback) { + /* (For now) there shall be no marking LI or LIDs (un)received + * except from the actual "view PO" interface. */ if (!this.isPO) return; - var self = this; + var part = + {"jub": "lineitem", "acqlid": "lineitem_detail"}[obj.classname]; + var method = + "open-ils.acq." + part + ".receive" + (rollback ? ".rollback" : ""); + progressDialog.show(true); fieldmapper.standardRequest( - ["open-ils.acq", "open-ils.acq.lineitem.receive"], { + ["open-ils.acq", method], { "async": true, - "params": [this.authtoken, li.id()], + "params": [this.authtoken, obj.id()], "onresponse": function(r) { - self.handleReceiveOrRollback(openils.Util.readResponse(r)); + self.handleReceive(openils.Util.readResponse(r)); }, - "oncomplete": function() { - progressDialog.hide(); - } + "oncomplete": function() { progressDialog.hide(); } } ); - } + }; - this.handleReceiveOrRollback = function(resp) { + /** + * Handles the responses from receive and rollback ML calls. + */ + this.handleReceive = function(resp) { if (resp) { if (resp.li) { for (var li_id in resp.li) { for (var key in resp.li[li_id]) self.liCache[li_id][key](resp.li[li_id][key]); - self.updateReceivedness(self.liCache[li_id]); + self.updateLiReceivedness(self.liCache[li_id]); } } if (resp.po) { if (typeof(self.poUpdateCallback) == "function") self.poUpdateCallback(resp.po); } - } - } - - this.unReceiveLi = function(li) { - /* (For now) there shall be no marking LIs un-received except from the - * actual "view PO" interface. */ - if (!this.isPO) return; - - var self = this; - progressDialog.show(true); - fieldmapper.standardRequest( - ["open-ils.acq", "open-ils.acq.lineitem.receive.rollback"], { - "async": true, - "params": [this.authtoken, li.id()], - "onresponse": function(r) { - self.handleReceiveOrRollback(openils.Util.readResponse(r)); - }, - "oncomplete": function() { - progressDialog.hide(); + if (resp.lid) { + for (var lid_id in resp.lid) { + for (var key in resp.lid[lid_id]) + self.copyCache[lid_id][key](resp.lid[lid_id][key]); + self.updateLidReceivedness(self.copyCache[lid_id]); } } - ); - } - - this.receiveLid = function(li) { - if(!this.isPO) return; - progressDialog.show(true); - fieldmapper.standardRequest( - ['open-ils.acq', 'open-ils.acq.lineitem_detail.receive'], - { async: true, - params: [this.authtoken, li.id()], - onresponse : function(r) { - var resp = openils.Util.readResponse(r); - progressDialog.hide(); - }, - } - ); - } + } + }; this.rollbackPoReceive = function() { if(!this.isPO) return; 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 7075fa1c2c..48426a6da8 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 @@ -8,9 +8,25 @@ var liTable; function updatePoState(po_info) { var data = po_info[PO.id()]; - if (data && data.state) { - PO.state(data.state); - dojo.byId("acq-po-view-state").innerHTML = PO.state(); // TODO i18n + if (data) { + for (var key in data) + PO[key](data[key]); + renderPo(); + } +} + +function renderPo() { + dojo.byId("acq-po-view-id").innerHTML = PO.id(); + dojo.byId("acq-po-view-name").innerHTML = PO.name(); + dojo.byId("acq-po-view-total-li").innerHTML = PO.lineitem_count(); + dojo.byId("acq-po-view-total-enc").innerHTML = PO.amount_encumbered(); + dojo.byId("acq-po-view-total-spent").innerHTML = PO.amount_spent(); + dojo.byId("acq-po-view-state").innerHTML = PO.state(); // TODO i18n + + if(PO.state() == "pending") { + openils.Util.show("acq-po-activate"); + if (PO.lineitem_count() > 1) + openils.Util.show("acq-po-split"); } } @@ -25,20 +41,8 @@ function init() { { async: true, params: [openils.User.authtoken, poId, {flesh_price_summary:true, flesh_lineitem_count:true}], oncomplete: function(r) { - PO = openils.Util.readResponse(r); - dojo.byId('acq-po-view-id').innerHTML = PO.id(); - dojo.byId('acq-po-view-name').innerHTML = PO.name(); - dojo.byId('acq-po-view-total-li').innerHTML = PO.lineitem_count(); - dojo.byId('acq-po-view-total-enc').innerHTML = PO.amount_encumbered(); - dojo.byId('acq-po-view-total-spent').innerHTML = PO.amount_spent(); - dojo.byId('acq-po-view-state').innerHTML = PO.state(); // TODO i18n - - if(PO.state() == 'pending') { - openils.Util.show('acq-po-activate'); - if (PO.lineitem_count() > 1) { - openils.Util.show('acq-po-split'); - } - } + PO = openils.Util.readResponse(r); /* save PO globally */ + renderPo(); } } ); diff --git a/Open-ILS/web/templates/default/acq/common/li_table.tt2 b/Open-ILS/web/templates/default/acq/common/li_table.tt2 index e66406415c..0e3f878fbd 100644 --- a/Open-ILS/web/templates/default/acq/common/li_table.tt2 +++ b/Open-ILS/web/templates/default/acq/common/li_table.tt2 @@ -92,7 +92,7 @@ - Mark ReceivedUnreceive + Mark ReceivedUn-Receive Copies(0) @@ -249,7 +249,7 @@
- Mark Received + Mark ReceivedUn-Receive
X