Acq: Towards slick integration of granular un-receive in the PO interface.
authorsenator <senator@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Fri, 19 Feb 2010 00:41:38 +0000 (00:41 +0000)
committersenator <senator@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Fri, 19 Feb 2010 00:41:38 +0000 (00:41 +0000)
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

Open-ILS/src/perlmods/OpenILS/Application/Acq/Order.pm
Open-ILS/web/js/dojo/openils/Util.js
Open-ILS/web/js/dojo/openils/acq/nls/acq.js
Open-ILS/web/js/ui/default/acq/common/li_table.js
Open-ILS/web/js/ui/default/acq/po/view_po.js
Open-ILS/web/templates/default/acq/common/li_table.tt2

index a0ffcc4..7f4847f 100644 (file)
@@ -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;
 }
 
 
index ce89904..912edc5 100644 (file)
@@ -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;
+    }
+
 }
 
index 5db9e14..969ca48 100644 (file)
@@ -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?"
 }
index 9213115..f728ed9 100644 (file)
@@ -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();
+                }
             }
         );
     }
index 76294bc..7075fa1 100644 (file)
@@ -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'],
index f01d90f..e664064 100644 (file)
@@ -92,7 +92,8 @@
                         </table>
                     </td>
                     <td><a class='hidden' name='real_copies_link' href='javascript:void(0);'>Update&nbsp;Barcodes</a></td>
-                    <td><a name='receive_link' href='javascript:void(0);'>Mark&nbsp;Received</a></td>
+                    <td><a name='receive_link' href='javascript:void(0);'>Mark&nbsp;Received</a><a name='unreceive_link' href='javascript:void(0);'>Unreceive</a></td>
+                    </td>
                     <td><a name='copieslink' href='javascript:void(0);'>Copies(<span name='count'>0</span>)</a></td>
                     <td>
                         <a name='noteslink' href='javascript:void(0);'