Acq Batch update UI
authorLebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Fri, 8 Mar 2013 19:10:12 +0000 (14:10 -0500)
committerLebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Mon, 11 Mar 2013 18:00:53 +0000 (14:00 -0400)
First UI to use new middle layer batch updater.  On success, this batch
updater just reload the whole page, but it can be made smarter later.
The UI only appears on POs for now, and is partly disabled if the PO has
already been activated.

The API call behind it returns the ID of each line item updated, so we
could really just update the current page, especially if in the future
we simplify the copies interface behind the li_table.

Signed-off-by: Lebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Conflicts:
Open-ILS/src/templates/acq/common/li_table.tt2

Open-ILS/examples/fm_IDL.xml
Open-ILS/src/templates/acq/common/li_table.tt2
Open-ILS/web/css/skin/default/acq.css
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

index 76aad44..247ee60 100644 (file)
@@ -8463,7 +8463,7 @@ SELECT  usr,
        </class>
        <class id="acqdf" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="acq::distribution_formula" oils_persist:tablename="acq.distribution_formula" reporter:label="Distribution Formula">
                <fields oils_persist:primary="id" oils_persist:sequence="acq.distribution_formula_id_seq">
-                       <field reporter:label="Formula ID" name="id" reporter:datatype="id"/>
+                       <field reporter:label="Formula ID" name="id" reporter:datatype="id" reporter:selector="name" />
                        <field reporter:label="Formula Owner" name="owner" reporter:datatype="org_unit"/>
                        <field reporter:label="Formula Name" name="name" reporter:datatype="text"/>
                        <field reporter:label="Skip Count" name="skip_count" reporter:datatype="int"/>
index 157d60c..789c5e9 100644 (file)
     <div id='acq-lit-table-div' class='hidden'>
 
         <!-- Line Item (bib record) list -->
-        <table id='acq-lit-table' class='oils-generic-table'>
-            <thead>
-                <tr>
-                    <th colspan='0'>
-                        <table style='width:100%;'>
-                            <tr>
-                                <td>
-                                    <span>
-                                        <select id="acq-lit-li-actions-selector">
-                                    <!-- mask meanings:
-                                        pl: selection list
-                                        po: pending purchase order
-                                        ao: activated purchase order
-                                        gs: general search
-                                        vp: view/place orders
-                                        fs: MARC federated search
+        <div style="float: left;">
+            <span>
+                <select id="acq-lit-li-actions-selector">
+            <!-- mask meanings:
+                pl: selection list
+                po: pending purchase order
+                ao: activated purchase order
+                gs: general search
+                vp: view/place orders
+                fs: MARC federated search
 
-                                        * for all, otherwise combine with |
-                                    -->
-                                            <option mask='*'  value='_'>[% l('--Actions--') %]</option>
-                                            <option mask='pl|gs|vp|fs' value='save_picklist'>[% l('Save Items To Selection List') %]</option>
-                                            <option mask='pl|gs|vp' value='selector_ready'>[% l('Mark Ready for Selector') %]</option>
-                                            <option mask='pl|gs|vp' value='order_ready'>[% l('Mark Ready for Order') %]</option>
-                                            <option mask='pl|po|gs|vp'  value='delete_selected'>[% l('Delete Selected Items') %]</option>
-                                            <option mask='pl|po'  value='add_brief_record'>[% l('Add Brief Record') %]</option>
-                                            <option mask='pl|po|ao|gs'  value='export_attr_list'>[% l('Export Single Attribute List') %]</option>
-                                            <option mask='*' value='' disabled='disabled'>[% l('----PO----') %]</option>
-                                            <option mask='pl|gs|vp|fs' value='create_order'>[% l('Create Purchase Order') %]</option>
-                                            <option mask='pl|gs|vp|fs' value='add_to_order'>[% l('Add to Purchase Order') %]</option>
-                                            <option mask='po|ao' value='print_po'>[% l('Print Purchase Order') %]</option>
-                                            <option mask='po|ao' value='po_history'>[% l('View PO History') %]</option>
-                                            <option mask='po' value='create_assets'>[% l('Load Bibs and Items') %]</option>
-                                            <!-- <option mask=''  value='batch_apply_funds'>[% l('Apply Funds to Selected Items') %]</option> XXX moving to batch updater -->
-                                            <option mask='ao|gs|vp' value='cancel_lineitems'>[% l('Cancel Selected Line Items') %]</option>
-                                            <option mask='po|ao|gs|vp' value='apply_claim_policy'>[% l('Apply Claim Policy to Selected Line Items') %]</option><!-- can the functionality desired here be covered by the next thing? -->
-                                            <option mask='ao|gs|vp' value='receive_lineitems' id='receive_lineitems' disabled='disabled'>[% l('Mark Selected Line Items as Received') %]</option>
-                                            <option mask='ao|gs|vp' value='rollback_receive_lineitems' id='rollback_receive_lineitems' disabled='disabled'>[% l('Un-Receive Selected Line Items') %]</option>
-                                            <option mask='ao|gs|vp' value='batch_create_invoice'>[% l('Create Invoice From Selected Line Items') %]</option>
-                                            <option mask='ao|gs|vp' value='batch_link_invoice'>[% l('Link Selected Line Items to Invoice') %]</option>
-                                        </select>
-                                        <span id="acq-lit-export-attr-holder" class="hidden">
-                                            <input dojoType="dijit.form.FilteringSelect" id="acq-lit-export-attr" jsId="acqLitExportAttrSelector" labelAttr="description" searchAttr="description" />
-                                            <span dojoType="dijit.form.Button" jsId="acqLitExportAttrButton">[% l('Export List') %]</span>
-                                        </span>
-                                        <span id="acq-lit-cancel-reason" class="hidden">
-                                            <span id="acq-lit-cancel-reason-selector"></span>
-                                            <span dojoType="dijit.form.Button" jsId="acqLitCancelLineitemsButton">[% l('Cancel Line Items') %]</span>
-                                        </span>
-                                    </span>
-                                    <!-- XXX TESTING XXX -->
-                                    <span><a href='javascript:;'  id='acq-lit-apply-changes'>Apply Changes</a></span>
-                                    <!-- XXX TESTING XXX -->
-                                    <span id='acq-lit-generic-progress' class='hidden'>
-                                        <span dojoType="dijit.ProgressBar" style="width:300px" jsId="litGenericProgress"></span>
-                                    </span>
-                                </td>
-                                <td>
-                                    <div style='width:100%;text-align:right;'>
-                                        <span style='padding-right:15px;'>
-                                            <a href='javascript:void(0);' id='acq-lit-prev' style='visibility:hidden'>[% l('&#171; Previous') %]</a>
-                                            <a href='javascript:void(0);' id='acq-lit-next' style='visibility:hidden'>[% l('Next &#187;') %]</a>
-                                        </span>
-                                    </div>
-                                </td>
-                            </tr>
-                        </table>
-                    </th>
-                </tr>
-            </thead>
-            <tbody><tr><td colspan='0' style='height:20px;'/></tr></tbody>
+                * for all, otherwise combine with |
+            -->
+                    <option mask='*'  value='_'>[% l('--Actions--') %]</option>
+                    <option mask='pl|gs|vp|fs' value='save_picklist'>[% l('Save Items To Selection List') %]</option>
+                    <option mask='pl|gs|vp' value='selector_ready'>[% l('Mark Ready for Selector') %]</option>
+                    <option mask='pl|gs|vp' value='order_ready'>[% l('Mark Ready for Order') %]</option>
+                    <option mask='pl|po|gs|vp'  value='delete_selected'>[% l('Delete Selected Items') %]</option>
+                    <option mask='pl|po'  value='add_brief_record'>[% l('Add Brief Record') %]</option>
+                    <option mask='pl|po|ao|gs'  value='export_attr_list'>[% l('Export Single Attribute List') %]</option>
+                    <option mask='*' value='' disabled='disabled'>[% l('----PO----') %]</option>
+                    <option mask='pl|gs|vp|fs' value='create_order'>[% l('Create Purchase Order') %]</option>
+                    <option mask='pl|gs|vp|fs' value='add_to_order'>[% l('Add to Purchase Order') %]</option>
+                    <option mask='po|ao' value='print_po'>[% l('Print Purchase Order') %]</option>
+                    <option mask='po|ao' value='po_history'>[% l('View PO History') %]</option>
+                    <option mask='po' value='create_assets'>[% l('Load Bibs and Items') %]</option>
+                    <option mask='ao|gs|vp' value='cancel_lineitems'>[% l('Cancel Selected Line Items') %]</option>
+                    <option mask='po|ao|gs|vp' value='apply_claim_policy'>[% l('Apply Claim Policy to Selected Line Items') %]</option><!-- can the functionality desired here be covered by the next thing? -->
+                    <option mask='ao|gs|vp' value='receive_lineitems' id='receive_lineitems' disabled='disabled'>[% l('Mark Selected Line Items as Received') %]</option>
+                    <option mask='ao|gs|vp' value='rollback_receive_lineitems' id='rollback_receive_lineitems' disabled='disabled'>[% l('Un-Receive Selected Line Items') %]</option>
+                    <option mask='ao|gs|vp' value='batch_create_invoice'>[% l('Create Invoice From Selected Line Items') %]</option>
+                    <option mask='ao|gs|vp' value='batch_link_invoice'>[% l('Link Selected Line Items to Invoice') %]</option>
+                </select>
+                <span id="acq-lit-export-attr-holder" class="hidden">
+                    <input dojoType="dijit.form.FilteringSelect" id="acq-lit-export-attr" jsId="acqLitExportAttrSelector" labelAttr="description" searchAttr="description" />
+                    <span dojoType="dijit.form.Button" jsId="acqLitExportAttrButton">[% l('Export List') %]</span>
+                </span>
+                <span id="acq-lit-cancel-reason" class="hidden">
+                    <span id="acq-lit-cancel-reason-selector"></span>
+                    <span dojoType="dijit.form.Button" jsId="acqLitCancelLineitemsButton">[% l('Cancel Line Items') %]</span>
+                </span>
+            </span>
+            <span id='acq-lit-generic-progress' class='hidden'>
+                <span dojoType="dijit.ProgressBar" style="width:300px" jsId="litGenericProgress"></span>
+            </span>
+        </div>
+        <div style="float: right;">
+            <div style='width:100%;text-align:right;'>
+                <span style='padding-right:15px;'>
+                    <a href='javascript:void(0);' id='acq-lit-prev' style='visibility:hidden'>[% l('&#171; Previous') %]</a>
+                    <a href='javascript:void(0);' id='acq-lit-next' style='visibility:hidden'>[% l('Next &#187;') %]</a>
+                </span>
+            </div>
+        </div>
+        <div style="clear: both;">&nbsp;</div><!-- XXX better way to end effects of float: left/right ? -->
+
+        <table id="acq-batch-update" class="hidden"><!-- XXX freeze in place w/ CSS? -->
+            <tr>
+                <th>
+                    <label for="acq-bu-item_count">[% l("Copies") %]</label>
+                </th>
+                <th>
+                    <label for="acq-bu-owning_lib">[% l("Owning Branch") %]</label>
+                </th>
+                <th>
+                    <label for="acq-bu-location">[% l("Copy Location") %]</label>
+                </th>
+                <th>
+                    <label for="acq-bu-collection_code">[% l("Collection Code") %]</label>
+                </th>
+                <th>
+                    <label for="acq-bu-fund">[% l("Fund") %]</label>
+                </th>
+                <th>
+                    <label for="acq-bu-circ_modifier">[% l("Circ Modifier") %]</label>
+                </th>
+                <th>
+                    <label for="acq-bu-distribution_formula">[% l("Distribution Formula") %]</label>
+                </th>
+            </tr>
+            <tr>
+                <td>
+                    <span id="acq-bu-item_count"></span>
+                </td>
+                <td>
+                    <span id="acq-bu-owning_lib"></span>
+                </td>
+                <td>
+                    <span id="acq-bu-location"></span>
+                </td>
+                <td>
+                    <span id="acq-bu-collection_code"></span>
+                </td>
+                <td>
+                    <span id="acq-bu-fund"></span>
+                </td>
+                <td>
+                    <span id="acq-bu-circ_modifier"></span> OR
+                </td>
+                <td>
+                    <span id="acq-bu-distribution_formula"></span>
+                    <span dojoType="dijit.form.Button" jsId="acqBatchUpdateApply">[% l("Apply to Selected") %]</span>
+                </td>
+            </tr>
+        </table>
+        <table id='acq-lit-table' class='oils-generic-table'>
+            <tbody><tr><td colspan='0' style='height:20px;'></td></tr></tbody>
             <tbody style='font-weight:bold;border:1px solid #aaa;'>
                 <tr>
                     <td><span><a id='acq-lit-select-toggle' href='javascript:void(0);'>&#x2713</a></span></td>
index 2fb317b..771a31d 100644 (file)
@@ -166,6 +166,10 @@ label[for="attr_search_type_scalar"] { vertical-align: top; }
 span[name="worksheet"] { padding: 0 6px; }
 #acq-lit-li-claim-dia-lid-list-init { margin-left: 8px; }
 
+#acq-batch-update { padding: 20px 0; }
+#acq-batch-update th { font-weight: bold; }
+#acq-batch-update td { white-space: nowrap; padding-right: 0.5em; }
+
 #acq-worksheet-contents thead th { font-weight: bold; background-color: #ccc; text-align: center; border-bottom: 1px #000 solid; border-right: 1px #000 solid; padding: 0 6px; }
 #acq-worksheet-contents tbody td { text-align: left; vertical-align: top; border: 1px #999 inset; padding: 0 2px; }
 
index ad82f7b..0e77867 100644 (file)
     "INVOICE_ITEM_PO_DETAILS" : "<b>${0}</b><br/><a href='${1}/acq/po/view/${2}'>PO #${3} ${4}</a><br/>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.",
-    "NO_LID_TO_CLAIM" : "You have not selected any lineitem details to claim.",
+    "LI_BATCH_UPDATE": "Line item batch update",
+    "NO_LI_TO_UPDATE" : "You have not selected any line items to update.",
+    "NO_LI_TO_CLAIM" : "You have not selected any line items to claim.",
+    "NO_LID_TO_CLAIM" : "You have not selected any line item details to claim.",
     "CHANGE_CLAIM_POLICY" : "Change claim policy",
     "CANCELED" : "Canceled",
     "RECVD" : "Recv'd",
index 303883a..7dd7776 100644 (file)
@@ -143,6 +143,169 @@ function AcqLiTable() {
     dojo.byId('acq-lit-notes-back-button').onclick = function(){self.show('list')};
     dojo.byId('acq-lit-real-copies-back-button').onclick = function(){self.show('list')};
 
+    this.afwCopyFieldArgs = function(field, perms) {
+        return {
+                "fmField" : field,
+                "fmClass": 'acqlid',
+                "labelFormat": (field == 'fund') ? fundLabelFormat : null,
+                "searchFormat": (field == 'fund') ? fundSearchFormat : null,
+                "searchFilter": (field == 'fund') ? {"active": "t"} : null,
+                "orgLimitPerms": [perms],
+                "dijitArgs": {
+                    "required": false,
+                    "labelType": (field == "fund") ? "html" : null
+                },
+                "noCache": (field == "fund"),
+                "forceSync": true
+            };
+    };
+
+    /* This is the "new" batch updater that sits atop all lineitems. It does
+     * use this.afwCopyFieldArgs() to borrow a little common code  from the
+     * "old" batch updater atop the copy details view. */
+    this.initBatchUpdater = function(disabled_fields) {
+        openils.Util.show("acq-batch-update", "table");
+
+        if (!dojo.isArray(disabled_fields)) disabled_fields = [];
+
+        /* Note that this will directly contain dijits, not the AutoWidget
+         * wrapper object. */
+        this.batchUpdateWidgets = {};
+
+        this.batchUpdateWidgets.item_count = new dijit.form.TextBox(
+            {
+                "style": {"width": "3em"},
+                "disabled": Boolean(
+                    dojo.indexOf(disabled_fields, "item_count") != -1
+                )
+            },
+            "acq-bu-item_count"
+        );
+
+        (new openils.widget.AutoFieldWidget({
+            "fmClass": "acqdf",
+            "selfReference": true,
+            "dijitArgs": { "required": false },
+            "forceSync": true,
+            "parentNode": "acq-bu-distribution_formula"
+        })).build(
+            function(w) {
+                dojo.style(w.domNode, {"width": "12em"});
+                /* dijitArgs to AutoFieldWidget won't work for 'disabled' */
+                w.attr(
+                    "disabled",
+                    dojo.indexOf(disabled_fields, "distribution_formula") != -1
+                );
+                self.batchUpdateWidgets.distribution_formula = w;
+            }
+        );
+
+        dojo.forEach(
+            ["owning_lib","location","collection_code","circ_modifier","fund"],
+            function(field) {
+                var args = self.afwCopyFieldArgs(field,"CREATE_PURCHASE_ORDER");
+                args.parentNode = dojo.byId("acq-bu-" + field);
+
+                (new openils.widget.AutoFieldWidget(args)).build(
+                    function(w, aw) {
+                        if (field == "fund") {
+                            dojo.connect(
+                                w, "onChange", function(val) {
+                                    self._updateFundSelectorStyle(aw, val);
+                                }
+                            );
+                            if (w.store)
+                                self._ensureCSSFundClasses(w.store);
+                        }
+
+                        dojo.style(w.domNode, {"width": "10em"});
+                        w.attr(
+                            "disabled",
+                            dojo.indexOf(disabled_fields, field) != -1
+                        );
+                        self.batchUpdateWidgets[field] = w;
+                    }
+                );
+            }
+        );
+
+        acqBatchUpdateApply.onClick = function() {
+            var li_id_list = self.getSelected(false, null, true /* id list */);
+            if (!li_id_list.length) {
+                alert(localeStrings.NO_LI_TO_UPDATE);
+                return;
+            }
+
+            progressDialog.show(true);
+            progressDialog.attr("title", localeStrings.LI_BATCH_UPDATE);
+            progressDialog.update({"maximum": li_id_list.length,"progress": 0});
+
+            var count = 0;
+
+            var params = [ self.authtoken, {"lineitems": li_id_list},
+                        self.batchUpdateChanges(), self.batchUpdateFormula() ];
+            console.log("batch update params: " + dojo.toJson(params));
+
+            fieldmapper.standardRequest(
+                ["open-ils.acq", "open-ils.acq.lineitem.batch_update"], {
+                    "async": true,
+                    "params": params,
+                    "onresponse": function(r) {
+                        if ((r = openils.Util.readResponse(r))) { // assignment
+                            progressDialog.update({"progress": ++count});
+                        } else {
+                            progressDialog.hide();
+                            progressDialog.attr("title", "");
+                        }
+                    },
+                    "oncomplete": function() {
+                        /* XXX Is the last call to onresponse guaranteed to
+                         * finish before oncomplete is fired? */
+                        if (count != li_id_list.length) {
+                            console.error("lineitem batch update operation failed");
+                            progressDialog.hide();
+                            progressDialog.attr("title", "");
+                        } else {
+                            location.href = location.href;
+                        }
+                    }
+                }
+            );
+        };
+    };
+
+    this.batchUpdateChanges = function() {
+        var o = {};
+
+        dojo.forEach(
+            openils.Util.objectProperties(this.batchUpdateWidgets),
+            function(k) {
+                if (k == "distribution_formula") return; /* handled elsewhere */
+                if (self.batchUpdateWidgets[k].attr("disabled")) return;
+
+                /* It's important that a value of "" should mean that a field
+                 * doesn't get used in the arguments to the batch updater API,
+                 * but 0 should mean an actual 0. */
+                var value = self.batchUpdateWidgets[k].attr("value");
+                if (value !== "")
+                    o[k] = value;
+            }
+        );
+
+        return o;
+    };
+
+    this.batchUpdateFormula = function() {
+        if (this.batchUpdateWidgets.distribution_formula.attr("disabled")) {
+            return null;
+        } else {
+            return (
+                this.batchUpdateWidgets.distribution_formula.attr("value") ||
+                null
+            );
+        }
+    };
+
     this.reset = function(keep_selectors) {
         while(self.tbody.childNodes[0])
             self.tbody.removeChild(self.tbody.childNodes[0]);
@@ -174,7 +337,7 @@ function AcqLiTable() {
     };
 
     this.enableActionsDropdownOptions = function(mask) {
-        /* 'mask' is probably a minomer the way I'm using it, but it needs to
+        /* 'mask' is probably a misnomer the way I'm using it, but it needs to
          * be one of pl,po,ao,gs,vp, or fs. */
         dojo.query("option", "acq-lit-li-actions-selector").forEach(
             function(option) {
@@ -1928,21 +2091,10 @@ function AcqLiTable() {
                 if(self.copyBatchRowDrawn) {
                     self.copyBatchWidgets[field].attr('value', null);
                 } else {
-                    var widget = new openils.widget.AutoFieldWidget({
-                        fmField : field,
-                        fmClass : 'acqlid',
-                        labelFormat : (field == 'fund') ? fundLabelFormat : null,
-                        searchFormat : (field == 'fund') ? fundSearchFormat : null,
-                        searchFilter : (field == 'fund') ? {"active": "t"} : null,
-                        parentNode : dojo.query('[name='+field+']', row)[0],
-                        orgLimitPerms : ['CREATE_PICKLIST'],
-                        dijitArgs : {
-                            "required": false,
-                            "labelType": (field == "fund") ? "html" : null
-                        },
-                        noCache: (field == "fund"),
-                        forceSync : true
-                    });
+                    var args = self.afwCopyFieldArgs(field, "CREATE_PICKLIST");
+                    args.parentNode = dojo.query('[name='+field+']', row)[0];
+
+                    var widget = new openils.widget.AutoFieldWidget(args);
                     widget.build(
                         function(w, ww) {
                             if (field == "fund" && w.store)
index 2a9b44c..0159b5b 100644 (file)
@@ -318,6 +318,7 @@ function renderPo() {
     if(PO.order_date()) {
         openils.Util.show('acq-po-activated-on', 'inline');
         liTable.enableActionsDropdownOptions("ao"); /* activated */
+        liTable.initBatchUpdater(["item_count", "distribution_formula"]);
 
         dojo.byId('acq-po-activated-on').innerHTML = 
             dojo.string.substitute(
@@ -344,6 +345,7 @@ function renderPo() {
     } else {
         /* These things only make sense for not-ordered-yet POs */
 
+        liTable.initBatchUpdater();
         liTable.enableActionsDropdownOptions("po");
 
         openils.Util.show("acq-po-zero-activate-label", "table-cell");