From c83b04e310677502b27c4e95b22fc588acb88d51 Mon Sep 17 00:00:00 2001 From: senator Date: Fri, 19 Feb 2010 00:41:38 +0000 Subject: [PATCH] Acq: Towards slick integration of granular un-receive in the PO interface. This adds good UI support for lineitem unreceive. The bulk of this commit, however, is in middle-layer rearrangements to make it easier to keep track of changing lineitems and the POs that they belong to in a long-lived client side interface. In other words, when you receive an LI in a PO, the LI table doesn't only hide the receive button, but keeps track of the LI's state (received or something else) and shows you the proper controls in any case. Same for unreceive. Similar thing coming very soon for lineitem details (copies). git-svn-id: svn://svn.open-ils.org/ILS/trunk@15593 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- .../src/perlmods/OpenILS/Application/Acq/Order.pm | 115 +++++++++++++------ Open-ILS/web/js/dojo/openils/Util.js | 28 ++++- Open-ILS/web/js/dojo/openils/acq/nls/acq.js | 3 +- Open-ILS/web/js/ui/default/acq/common/li_table.js | 127 ++++++++++++++++----- Open-ILS/web/js/ui/default/acq/po/view_po.js | 9 ++ .../web/templates/default/acq/common/li_table.tt2 | 3 +- 6 files changed, 218 insertions(+), 67 deletions(-) diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Order.pm b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Order.pm index a0ffcc4c0..7f4847f6a 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Order.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Order.pm @@ -214,7 +214,8 @@ sub update_lineitem { $li->edit_time('now'); $li->editor($mgr->editor->requestor->id); $mgr->add_li; - return $li if $mgr->editor->update_acq_lineitem($li); + return $mgr->editor->retrieve_acq_lineitem($mgr->editor->data) if + $mgr->editor->update_acq_lineitem($li); return undef; } @@ -353,13 +354,21 @@ sub receive_lineitem { $mgr->add_li; $li->state('received'); - update_lineitem($mgr, $li) or return 0; + $li = update_lineitem($mgr, $li) or return 0; $mgr->post_process( sub { create_lineitem_status_events($mgr, $li_id, 'aur.received'); }); - return 1 if $skip_complete_check; + 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) + ); - return check_purchase_order_received($mgr, $li->purchase_order); + if (ref $po) { + $result->{"po"} = {$po->id => {"state" => $li->state}}; + } + return $result; } sub rollback_receive_lineitem { @@ -488,7 +497,9 @@ sub receive_lineitem_detail { my $li = check_lineitem_received($mgr, $lid->lineitem) or return 0; return 1 if $li == 1; # li not received - return check_purchase_order_received($mgr, $li->purchase_order); + my $po = check_purchase_order_received($mgr, $li->purchase_order) or return 0; + return $li if $po == 1; + return $po; } @@ -791,7 +802,8 @@ sub update_purchase_order { $po->editor($mgr->editor->requestor->id); $po->edit_time('now'); $mgr->purchase_order($po); - return $po if $mgr->editor->update_acq_purchase_order($po); + return $mgr->editor->retrieve_acq_purchase_order($mgr->editor->data) + if $mgr->editor->update_acq_purchase_order($po); return undef; } @@ -1576,9 +1588,31 @@ sub receive_lineitem_detail_api { return $e->die_event unless $e->allowed( 'RECEIVE_PURCHASE_ORDER', $lid->lineitem->purchase_order->ordering_agency); - receive_lineitem_detail($mgr, $lid_id) or return $e->die_event; + 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"]}}] + ); + 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->{"li"} = { + $lid->lineitem->id => {"state" => $lid->lineitem->state} + }; + } elsif ($recvd->class_name =~ /::lineitem/) { + $result->{"li"} = {$recvd->id => {"state" => $recvd->state}}; + } + } + $e->commit; - return 1; + return $result; } __PACKAGE__->register_method( @@ -1590,7 +1624,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 containing an LI and possibly a PO; " . + "on error, event" + } } ); @@ -1613,12 +1650,10 @@ sub receive_lineitem_api { return $e->die_event unless $e->allowed( 'RECEIVE_PURCHASE_ORDER', $li->purchase_order->ordering_agency); - receive_lineitem($mgr, $li_id) or return $e->die_event; - + my $res = receive_lineitem($mgr, $li_id) or return $e->die_event; $e->commit; - $conn->respond_complete(1); + $conn->respond_complete($res); $mgr->run_post_response_hooks; - return undef; } @@ -1655,7 +1690,7 @@ __PACKAGE__->register_method( method => 'rollback_receive_lineitem_detail_api', api_name => 'open-ils.acq.lineitem_detail.receive.rollback', signature => { - desc => 'Mark a lineitem_detail as received', + desc => 'Mark a lineitem_detail as Un-received', params => [ {desc => 'Authentication token', type => 'string'}, {desc => 'lineitem detail ID', type => 'number'} @@ -1684,15 +1719,31 @@ sub rollback_receive_lineitem_detail_api { my $po = $li->purchase_order; return $e->die_event unless $e->allowed('RECEIVE_PURCHASE_ORDER', $po->ordering_agency); - rollback_receive_lineitem_detail($mgr, $lid_id) or return $e->die_event; - $li->state('on-order'); - $po->state('on-order'); - udpate_lineitem($mgr, $li) or return $e->die_event; - udpate_purchase_order($mgr, $po) or return $e->die_event; + my $result = {}; - $e->commit; - return 1; + my $recvd = rollback_receive_lineitem_detail($mgr, $lid_id) + or return $e->die_event; + + if (ref $recvd) { + $result->{"lid"} = {$recvd->id => {"recv_time" => $recvd->recv_time}}; + } else { + $result->{"lid"} = {$lid->id => {"recv_time" => $lid->recv_time}}; + } + + if ($li->state eq "received") { + $li->state("on-order"); + $li = update_lineitem($mgr, $li) or return $e->die_event; + $result->{"li"} = {$li->id => {"state" => $li->state}}; + } + + 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}}; + } + + $e->commit and return $result or return $e->die_event; } __PACKAGE__->register_method( @@ -1704,7 +1755,7 @@ __PACKAGE__->register_method( {desc => 'Authentication token', type => 'string'}, {desc => 'lineitem detail ID', type => 'number'} ], - return => {desc => '1 on success, Event on error'} + return => {desc => 'altered objects on success, event on error'} } ); @@ -1715,25 +1766,25 @@ sub rollback_receive_lineitem_api { return $e->die_event unless $e->checkauth; my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn); - my $li = $e->retrieve_acq_lineitem_detail([ + my $li = $e->retrieve_acq_lineitem([ $li_id, { - flesh => 1, - flesh_fields => { - jub => ['purchase_order'] - } + "flesh" => 1, "flesh_fields" => {"jub" => ["purchase_order"]} } ]); my $po = $li->purchase_order; return $e->die_event unless $e->allowed('RECEIVE_PURCHASE_ORDER', $po->ordering_agency); - rollback_receive_lineitem($mgr, $li_id) or return $e->die_event; + $li = rollback_receive_lineitem($mgr, $li_id) or return $e->die_event; - $po->state('on-order'); - update_purchase_order($mgr, $po) or return $e->die_event; + my $result = {"li" => {$li->id => {"state" => $li->state}}}; + 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}}; + } - $e->commit; - return 1; + $e->commit and return $result or return $e->die_event; } diff --git a/Open-ILS/web/js/dojo/openils/Util.js b/Open-ILS/web/js/dojo/openils/Util.js index ce89904ed..912edc55b 100644 --- a/Open-ILS/web/js/dojo/openils/Util.js +++ b/Open-ILS/web/js/dojo/openils/Util.js @@ -158,11 +158,20 @@ if(!dojo._hasResource["openils.Util"]) { var classList = node.className.split(/\s+/); var className = ''; for(var i = 0; i < classList.length; i++) { - if (classList[i] != cls) { - if(i == 0) - className = classList[i]; - else - className += ' ' + classList[i]; + if (typeof(cls) == "object") { /* assume regex */ + if (!cls.test(classList[i])) { + if(i == 0) + className = classList[i]; + else + className += ' ' + classList[i]; + } + } else { + if (classList[i] != cls) { + if(i == 0) + className = classList[i]; + else + className += ' ' + classList[i]; + } } } node.className = className; @@ -264,5 +273,14 @@ if(!dojo._hasResource["openils.Util"]) { document.body.removeChild(audio); } + /** + * Return the properties of an object as a list. Saves typing. + */ + openils.Util.objectProperties = function(obj) { + var K = []; + for (var k in obj) K.push(k); + return K; + } + } 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 5db9e1461..969ca4804 100644 --- a/Open-ILS/web/js/dojo/openils/acq/nls/acq.js +++ b/Open-ILS/web/js/dojo/openils/acq/nls/acq.js @@ -17,5 +17,6 @@ 'NO_RESULTS': "No results.", '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." + 'EXPORT_EMPTY_LIST': "No values for attribute '${0}' found.", + 'UNRECEIVE_LI': "Are you sure you want to mark this lineitem 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 9213115c1..f728ed918 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 @@ -244,34 +244,14 @@ function AcqLiTable() { // lineitem state nodeByName('li_state', row).innerHTML = li.state(); // TODO i18n state labels - openils.Util.addCSSClass(row, 'oils-acq-li-state-' + li.state()); - // lineitem price var priceInput = dojo.query('[name=price]', row)[0]; var priceData = liWrapper.getPrice(); priceInput.value = (priceData) ? priceData.price : ''; priceInput.onchange = function() { self.updateLiPrice(priceInput, li) }; - var recv_link = dojo.query('[name=receive_link]', row)[0]; - - if(li.state() == 'on-order') { - recv_link.onclick = function() { - self.receiveLi(li); - openils.Util.hide(recv_link) - } - } else { - openils.Util.hide(recv_link); - } - - // TODO we should allow editing before receipt, in which case the - // test should be "if 1 or more real (acp) copies exist - if(li.state() == 'received') { - var real_copies_link = dojo.query('[name=real_copies_link]', row)[0]; - openils.Util.show(real_copies_link); - real_copies_link.onclick = function() { - self.showRealCopies(li); - } - } + // show either "mark received" or "unreceive" as appropriate + this.updateReceivedness(li, row); if (!skip_final_placement) { self.tbody.appendChild(row); @@ -281,6 +261,54 @@ function AcqLiTable() { } }; + this.updateReceivedness = function(li, row) { + if (typeof(row) == "undefined") + row = dojo.query('tr[li="' + li.id() + '"]', "acq-lit-tbody")[0]; + + var recv_link = nodeByName("receive_link", row); + var unrecv_link = nodeByName("unreceive_link", row); + var real_copies_link = nodeByName("real_copies_link", row); + + /* handle row coloring for based on LI state */ + openils.Util.removeCSSClass(row, /^oils-acq-li-state-/); + openils.Util.addCSSClass(row, "oils-acq-li-state-" + li.state()); + + /* handle links that appear/disappear based on whether LI is received */ + if (this.isPO) { + var self = this; + switch(li.state()) { + case "on-order": + 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); }; + 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); + }; + } + // 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); + real_copies_link.onclick = function() { + self.showRealCopies(li); + } + return; + } + } + + openils.Util.hide(recv_link); + openils.Util.hide(unrecv_link); + openils.Util.hide(real_copies_link); + }; + + /** * Draws and shows the lineitem notes pane */ @@ -1148,16 +1176,59 @@ function AcqLiTable() { } this.receiveLi = function(li) { - if(!this.isPO) return; + /* (For now) there shall be no marking LIs 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'], - { async: true, - params: [this.authtoken, li.id()], - onresponse : function(r) { - var resp = openils.Util.readResponse(r); + ["open-ils.acq", "open-ils.acq.lineitem.receive"], { + "async": true, + "params": [this.authtoken, li.id()], + "onresponse": function(r) { + self.handleReceiveOrRollback(openils.Util.readResponse(r)); + }, + "oncomplete": function() { progressDialog.hide(); + } + } + ); + } + + this.handleReceiveOrRollback = 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]); + } + } + 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(); + } } ); } 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 76294bc7b..7075fa1c2 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 @@ -6,10 +6,19 @@ dojo.require('openils.PermaCrud'); var PO = null; 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 + } +} + function init() { liTable = new AcqLiTable(); liTable.reset(); liTable.isPO = poId; + liTable.poUpdateCallback = updatePoState; fieldmapper.standardRequest( ['open-ils.acq', 'open-ils.acq.purchase_order.retrieve'], 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 f01d90fd7..e66406415 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,8 @@ - Mark Received + Mark ReceivedUnreceive + Copies(0)