From 70c5adc11235b492340c035421dfd6f92a30b610 Mon Sep 17 00:00:00 2001 From: senator Date: Thu, 4 Feb 2010 17:21:37 +0000 Subject: [PATCH] Add an "virtual PO" view of PO search results in the Acquisitions module. This extends the li_table primitive and uses it to display lineitems belonging to many POs as if they were part of one large PO. git-svn-id: svn://svn.open-ils.org/ILS/trunk@15448 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- Open-ILS/web/css/skin/default/acq.css | 20 +- Open-ILS/web/js/dojo/openils/acq/nls/acq.js | 4 +- Open-ILS/web/js/ui/default/acq/common/li_table.js | 31 ++- Open-ILS/web/js/ui/default/acq/po/search.js | 277 +++++++++++++++++++++- Open-ILS/web/templates/default/acq/po/search.tt2 | 57 ++++- 5 files changed, 373 insertions(+), 16 deletions(-) diff --git a/Open-ILS/web/css/skin/default/acq.css b/Open-ILS/web/css/skin/default/acq.css index 08c7bfe973..211128bef7 100644 --- a/Open-ILS/web/css/skin/default/acq.css +++ b/Open-ILS/web/css/skin/default/acq.css @@ -90,13 +90,32 @@ .oils-acq-lineitem-attr-name {} .oils-acq-lineitem-attr-value {} #oils-acq-lineitem-marc-block { margin-top: 10px; padding: 6px; } +.oils-acq-po-heading-po { font-weight: bold; } +#oils-acq-metapo-summary { + border: 1px inset #ccc; + width: 400px; + margin-bottom: 8px; + margin-left: 8px; +} +#oils-acq-metapo-summary th, #oils-acq-metapo-summary td { padding: 3px; } +#oils-acq-metapo-summary tbody th { font-weight: bold; } +#oils-acq-metapo-summary thead th { + font-size: 110%; + font-weight: bold; + text-align: center; +} +#oils-acq-metapo-summary td { text-align: right; } #acq-lit-table {width:100%} #acq-lit-table th {padding:5px; font-weight: bold; text-align:left;} #acq-lit-table td {padding:2px;} .acq-lit-row { border-bottom: 1px solid #AAA; } .acq-lit-alt-row td { padding-left:30px; } +.acq-lit-po-heading td { background-color: #ccc; border: 1px solid #000; } +.acq-lit-po-heading td span { padding-left: 6px; padding-right: 6px; } +.acq-lit-po-heading td span span { padding: 0; } +.acq-lit-po-heading td span a[attr="name"] { font-weight: bold; } #acq-lit-info-tbody td {padding:5px;} #acq-lit-li-details-table {margin-top:20px;} #acq-lit-li-details-table td {padding:0px 3px 1px 3px;} @@ -111,4 +130,3 @@ .acq-lit-table-spacer { height:20px; } .acq-lit-row td[name="selector"] { width:1.5em; font-weight:bold; color:blue; font-size:110%;} #acq-lit-notes-tbody li { margin-bottom:10px; border:1px solid #aaa; -moz-border-radius: 5px 5px 5px 5px; } - 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 c688a5b26d..d6a9d517e4 100644 --- a/Open-ILS/web/js/dojo/openils/acq/nls/acq.js +++ b/Open-ILS/web/js/dojo/openils/acq/nls/acq.js @@ -2,5 +2,7 @@ 'CREATE_PO_ASSETS_CONFIRM' : "This will create bibliographic, call number, and copy records for this purchase order in the ILS.\n\nContinue?", 'ROLLBACK_PO_RECEIVE_CONFIRM' : "This will rollback receipt of all copies for this purchase order.\n\nContinue?", 'XUL_RECORD_DETAIL_PAGE' : 'Record Details', - 'DELETE_LI_COPIES_CONFIRM' : 'This will delete the last ${0} copies in the table. Proceed?' + 'DELETE_LI_COPIES_CONFIRM' : 'This will delete the last ${0} copies in the table. Proceed?', + 'NO_PO_RESULTS': "No results", + 'PO_HEADING_ERROR' : "Unexpected problem building virtual combined PO" } 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 5bf469f16b..9d97a2e1d8 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 @@ -169,7 +169,7 @@ function AcqLiTable() { * Inserts a single lineitem into the growing table of lineitems * @param {Object} li The lineitem object to insert */ - this.addLineitem = function(li) { + this.addLineitem = function(li, skip_final_placement) { this.liCache[li.id()] = li; // sort the lineitem notes on edit_time @@ -200,8 +200,13 @@ function AcqLiTable() { this.poCache[li.purchase_order()] || fieldmapper.standardRequest( ['open-ils.acq', 'open-ils.acq.purchase_order.retrieve'], - {params: [this.authtoken, li.purchase_order()]}); - if(po) { + {params: [ + this.authtoken, li.purchase_order(), { + "flesh_price_summary": true, + "flesh_lineitem_count": true + } + ]}); + if(po && !this.isMeta) { openils.Util.show(nodeByName('po', row), 'inline'); var link = nodeByName('po_link', row); link.setAttribute('href', oilsBasePath + '/acq/po/view/' + li.purchase_order()); @@ -210,7 +215,7 @@ function AcqLiTable() { } // show which picklist this lineitem is a member of - if(li.picklist() && this.isPO) { + if(li.picklist() && (this.isPO || this.isMeta)) { var pl = this.plCache[li.picklist()] = this.plCache[li.picklist()] || @@ -226,7 +231,11 @@ function AcqLiTable() { } var countNode = nodeByName('count', row); - countNode.innerHTML = li.item_count() || 0; + var count = li.item_count() || 0; + if (typeof(this._copy_count_cb) == "function") { + this._copy_count_cb(li.id(), count); + } + countNode.innerHTML = count; countNode.id = 'acq-lit-copy-count-label-' + li.id(); // lineitem state @@ -260,8 +269,12 @@ function AcqLiTable() { } } - self.tbody.appendChild(row); - self.selectors.push(dojo.query('[name=selectbox]', row)[0]); + if (!skip_final_placement) { + self.tbody.appendChild(row); + self.selectors.push(dojo.query('[name=selectbox]', row)[0]); + } else { + return row; + } }; /** @@ -851,6 +864,10 @@ function AcqLiTable() { } } + if (typeof(this._copy_count_cb) == "function") { + this._copy_count_cb(liId, total); + } + dojo.byId('acq-lit-copy-count-label-' + liId).innerHTML = total; if(copies.length == 0) diff --git a/Open-ILS/web/js/ui/default/acq/po/search.js b/Open-ILS/web/js/ui/default/acq/po/search.js index 8b11201183..30aa6ebc39 100644 --- a/Open-ILS/web/js/ui/default/acq/po/search.js +++ b/Open-ILS/web/js/ui/default/acq/po/search.js @@ -1,5 +1,6 @@ dojo.require('dijit.form.Form'); dojo.require('dijit.form.Button'); +dojo.require('dijit.form.CheckBox'); dojo.require('dijit.form.FilteringSelect'); dojo.require('dijit.form.NumberTextBox'); dojo.require('dojo.data.ItemFileWriteStore'); @@ -11,6 +12,9 @@ dojo.require('openils.widget.AutoGrid'); dojo.require('openils.widget.AutoFieldWidget'); dojo.require('openils.PermaCrud'); +var metaPO; +var _last_fields; +var general_po_search_opts = {"order_by": {"acqpo": "edit_time DESC"}}; function getPOOwner(rowIndex, item) { if(!item) return ''; @@ -19,7 +23,15 @@ function getPOOwner(rowIndex, item) { } function doSearch(fields) { - + _last_fields = dojo.clone(fields); /* Save for re-use */ + var metapo_view = false; + + /* Remove the metapo_view field from 'fields'... we'll use it later */ + if (fields.metapo_view && fields.metapo_view[0]) { + metapo_view = true; + delete fields.metapo_view; + } + if(isNaN(fields.id)) { delete fields.id; for(var k in fields) { @@ -36,8 +48,15 @@ function doSearch(fields) { for(var k in fields) some = true; if(!some) fields.id = {'!=' : null}; - poGrid.resetStore(); - poGrid.loadAll({order_by:{acqpo : 'edit_time DESC'}}, fields); + if (metapo_view) { + openils.Util.hide("holds_po_grid"); + loadMetaPO(fields); + } else { + if (metaPO) metaPO.myHide(); + openils.Util.show("holds_po_grid"); + poGrid.resetStore(); + poGrid.loadAll(general_po_search_opts, fields); + } } function loadForm() { @@ -61,4 +80,256 @@ function loadForm() { doSearch({ordering_agency : openils.User.user.ws_ou()}); } +function loadMetaPO(fields) { + var pcrud = new openils.PermaCrud(); + var po_list = pcrud.search("acqpo", fields, general_po_search_opts); + if (!po_list || !po_list.length) { + alert(localeStrings.NO_PO_RESULTS); + } else { + if (!metaPO) { + metaPO = new AcqLiTable(); + + /* We need to know the width (in cells) of the template row for + * the LI table, and we don't want to hardcode it here. */ + metaPO.n_cells = dojo.query("> td", metaPO.rowTemplate).length; + + metaPO._copy_count_cb = function(liId, count) { + var poId = this.liCache[liId].purchase_order(); + + if (this.copy_counts[poId] == undefined) + this.copy_counts[poId] = {}; + this.copy_counts[poId][liId] = count; + + this.renderCopyCounts(poId); + this.renderSummary("copies"); + }; + metaPO.myHide = function() { + this.hide(); + openils.Util.hide("oils-acq-holds-metapo-summary"); + }; + metaPO.renderSummary = function(part) { + var self = this; + /* The idea here will be that if "part" is defined, we'll + * just update that part of the metaPO summary, otherwise, + * the whole thing. */ + if (part != undefined) { + var target = dojo.byId("oils-acq-metapo-summary-" + part); + switch (part) { + case "copies": + target.innerHTML = self.copiesTotal(); + break; + case "po": + target.innerHTML = self.working_po_list.length; + break; + default: + /* assume a field on the acqpo's themselves */ + target.innerHTML = self.anyFieldTotal(part); + break; + } + } else { + openils.Util.show("oils-acq-holds-metapo-summary"); + self.totalable_fields.forEach( + function(f) { self.renderSummary(f); } + ); + } + }; + metaPO.anyFieldTotal = function(field) { + var self = this; + return self.working_po_list.reduce( + /* working_po_list contains unfleshed, acqpo's, so we must + * find the same PO in the poCache */ + function(p, c) { + c = self.poCache[c.id()][field](); + return p + Number(c); + }, 0 + ); + }; + metaPO.renderCopyCounts = function(poId) { + try { + dojo.query("td#oils-acq-po-heading-" + poId + + ' span span[attr="copies"]')[0].innerHTML = + this.copiesByPOId(poId); + } catch (E) { + ; + } + }; + metaPO.sectionHeadingById = function(id) { + var headings = dojo.query("#po-heading-" + id, this.tbody); + if (headings.length != 1) { + alert(localeStrings.PO_HEADING_ERROR); + return undefined; + } else { + return headings[0]; + } + }; + metaPO.sectionHeadingByPOId = function(poId) { + return this.sectionHeadingById(this.sections_by_poid[poId]); + }; + metaPO.addSection = function(po) { + var s = this.sections_by_poid[po.id()] = this.sections++; + + this.tbody.appendChild( + dojo.create("tr", { + "class": "acq-lit-po-heading", "id": "po-heading-" + s + }) + ); + + return s; + }; + metaPO.addLineitemToSection = function(li, section) { + dojo.place( + this.addLineitem(li, true /* skip_final_placement */), + this.sectionHeadingById(section), + "after" + ); + }; + metaPO.generateActivator = function(id) { + return function() { + progressDialog.show(true); + try { + fieldmapper.standardRequest( + ["open-ils.acq", + "open-ils.acq.purchase_order.activate"], { + "async": true, + "params": [openils.User.authtoken, id], + "oncomplete": function() { + progressDialog.hide(); + doSearch(_last_fields); + } + } + ); + } catch (E) { + progressDialog.hide(); + alert(E); /* XXX */ + } + }; + }; + metaPO.renderHeading = function(poId) { + var self = this; + var td = dojo.create("td", {"colspan": self.n_cells}); + td.id = "oils-acq-po-heading-" + poId; + + /* Build our HTML structure from the template... */ + dojo.query("> span", "oils-acq-po-heading-template").forEach( + function(s) { td.appendChild(s.cloneNode(true)); } + ); + + /* Some fields straight from the PO object... */ + self.po_fields_for_display.forEach( + function(f) { + dojo.query('[attr="' + f + '"]', td)[0].innerHTML = + self.poCache[poId][f](); + } + ); + + /* The name field needs special treatment: it's a link */ + dojo.attr( + dojo.query('a[attr="name"]', td)[0], + "href", + oilsBasePath + '/acq/po/view/' + poId + ); + + /* Show an "activate" link, or not, based on "state"... */ + var a = dojo.query('a[attr="activator"]', td)[0]; + if (self.poCache[poId].state() == "pending") { + a.onclick = self.generateActivator(poId); + openils.Util.show(a, "inline"); + } else { + openils.Util.hide(a); + } + + /* Put the new heading cell in place... */ + dojo.place(td, self.sectionHeadingByPOId(poId), "only"); + + /* And finally, render copy info (must happen _after_ heading + * is attached to the DOM tree */ + this.renderCopyCounts(poId); + }; + metaPO.copiesByPOId = function(poId) { + if (!this.copy_counts[poId]) return undefined; + var total = 0; + for (var liId in this.copy_counts[poId]) { + total += this.copy_counts[poId][liId]; + } + return total; + }; + metaPO.copiesTotal = function() { + var total = 0; + for (var poId in this.copy_counts) + total += this.copiesByPOId(poId); + return total; + }; + metaPO.myReset = function() { + this.isMeta = true; + this.sections = 0; + this.sections_by_poid = {}; + this.copy_counts = {}; + this.po_fields_for_display = [ + "name", "lineitem_count", "amount_encumbered", + "amount_spent", "state" + ]; + this.totalable_fields = [ + "po", "lineitem_count", "copies", + "amount_encumbered", "amount_spent" + ]; + openils.Util.hide("oils-acq-holds-metapo-summary"); + }; + metaPO.populate = function(list) { + var self = this; + var done = 0; + + self.working_po_list = []; + + progressDialog.show(true); + list.forEach(function(po) { + var sec = self.addSection(po); + fieldmapper.standardRequest( + ["open-ils.acq", "open-ils.acq.lineitem.search"], { + "async": true, + "params": [ + openils.User.authtoken, + {"purchase_order": po.id()}, + {"flesh_attrs": true, "flesh_notes": true} + ], + "onresponse": function(r) { + var li = openils.Util.readResponse(r); + if (li) /* sometimes empty string: disregard */ + self.addLineitemToSection(li, sec); + }, + "oncomplete": function(r) { + self.working_po_list.push(po); + self.renderHeading(po.id()); + self.renderSummary(); + /* This mechanism avoids calling .show() too + * often or before results are ready, and + * thus smooths out DOM rendering glitches. */ + if (++done >= list.length) { + done = -1; + self.show("list"); + progressDialog.hide(); + } + } + } + ); + }); + /* This mechanism sees to it that we call .show() at least once + * even if the search result population seems to be timing + * out or failing. */ + setTimeout( + function() { + if (done != -1) { + self.show("list"); + progressDialog.hide(); + } + }, 10000 /* 10 seconds: make this configurable? */ + ); + }; + } + + metaPO.reset(); + metaPO.myReset(); + metaPO.populate(po_list); + } +} + openils.Util.addOnLoad(loadForm); diff --git a/Open-ILS/web/templates/default/acq/po/search.tt2 b/Open-ILS/web/templates/default/acq/po/search.tt2 index 1a8d8111eb..d152efbd0e 100644 --- a/Open-ILS/web/templates/default/acq/po/search.tt2 +++ b/Open-ILS/web/templates/default/acq/po/search.tt2 @@ -4,6 +4,15 @@
PO Search
+ @@ -36,8 +45,12 @@ identifier:"value", label: "name", items: [ + /* FIXME This is probably not the correct final list of + possible states */ {name:"New", value:'new'}, - {name:"In Process", value:'in-process'} + {name:"In Process", value:'in-process'}, + {name:"Pending", value:'pending'}, + {name:"On order", value:'on-order'} ] } }); @@ -55,9 +68,14 @@ Search +
+ + +

-
+
-
+
+ +[% INCLUDE 'default/acq/common/li_table.tt2' %] [% END %] - -- 2.11.0