From ff7bb858b57f4b0ec73d25e33b26d0c03b8d3235 Mon Sep 17 00:00:00 2001 From: senator <senator@dcc99617-32d9-48b4-a31d-7c20da2025e4> Date: Sat, 27 Feb 2010 00:07:40 +0000 Subject: [PATCH] Acq: View and manage distrib formula applications for a given lineitem When distribution formulas are applied, each application is represented by an icon in a new table in the lineitem copies interface. git-svn-id: svn://svn.open-ils.org/ILS/trunk@15644 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- .../perlmods/OpenILS/Application/Acq/Picklist.pm | 53 ++++++ Open-ILS/web/css/skin/default/acq.css | 3 + Open-ILS/web/js/dojo/openils/User.js | 16 ++ Open-ILS/web/js/dojo/openils/User/nls/User.js | 3 + Open-ILS/web/js/dojo/openils/acq/nls/acq.js | 6 +- Open-ILS/web/js/ui/default/acq/common/li_table.js | 202 +++++++++++++++++++-- .../web/templates/default/acq/common/li_table.tt2 | 13 ++ 7 files changed, 277 insertions(+), 19 deletions(-) create mode 100644 Open-ILS/web/js/dojo/openils/User/nls/User.js diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Picklist.pm b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Picklist.pm index b3e9aae14e..3004982bb5 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Picklist.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Picklist.pm @@ -474,4 +474,57 @@ sub ranged_distrib_formulas { return undef; } +__PACKAGE__->register_method( + method => "ranged_distrib_formula_applications", + api_name => "open-ils.acq.distribution_formula_application.ranged.retrieve", + stream => 1, + signature => { + desc => "Ranged distribution formulas applications, fleshed with formulas and users", + params => [ + {desc => "Authentication token", type => "string"}, + {desc => "Lineitem Id", type => "number"} + ], + return => {desc => "List of distribution formula applications"} + } +); + +sub ranged_distrib_formula_applications { + my ($self, $conn, $auth, $li_id) = @_; + + my $e = new_editor("authtoken" => $auth); + return $e->event unless $e->checkauth; + + my $li = $e->retrieve_acq_lineitem([ + $li_id, { + "flesh" => 1, + "flesh_fields" => {"jub" => [qw/purchase_order picklist/]} + } + ]) or return $e->die_event; + + if ($li->picklist) { + return $e->die_event unless $e->allowed( + "VIEW_PICKLIST", $li->picklist->org_unit + ); + } elsif ($li->purchase_order) { + return $e->die_event unless $e->allowed( + "VIEW_PURCHASE_ORDER", $li->purchase_order->ordering_agency + ); + } else { + # For the moment no use cases are forseen for using this + # method with LIs that don't belong to a PL or a PO. + $e->disconnect; + return new OpenILS::Event("BAD_PARAMS", "note" => "Freestanding LI"); + } + + my $dfa = $e->search_acq_distribution_formula_application([ + {"lineitem" => $li_id}, + {"flesh" => 1, "flesh_fields" => {"acqdfa" => [qw/formula creator/]}} + ]); + + $conn->respond($_) foreach (@$dfa); + + $e->disconnect; + undef; +} + 1; diff --git a/Open-ILS/web/css/skin/default/acq.css b/Open-ILS/web/css/skin/default/acq.css index 00a61597df..0aa12ab71e 100644 --- a/Open-ILS/web/css/skin/default/acq.css +++ b/Open-ILS/web/css/skin/default/acq.css @@ -148,6 +148,9 @@ span[name="alert_code"] {color: #c00;padding-right: 1em;font-weight: bold;} .acq-lit-li-menu-left {text-align:left; width:300px;} .acq-lit-li-menu-right {text-align:left;} .acq-lit-distrib-form-use-count { color: #999; font-weight: bold; } +#acq-lit-distrib-applied-heading { font-size: 110%; font-weight: bold; } +.acq-lit-distrib-applied-row td { border-right: 1px #999 solid;border-bottom: 1px #999 solid; } +.acq-lit-distrib-applied-row th { border-bottom: 1px #999 solid;padding-bottom: 4px;} .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/User.js b/Open-ILS/web/js/dojo/openils/User.js index 11ca1a5df7..ca3c62a579 100644 --- a/Open-ILS/web/js/dojo/openils/User.js +++ b/Open-ILS/web/js/dojo/openils/User.js @@ -24,6 +24,7 @@ if(!dojo._hasResource["openils.User"]) { dojo.require('fieldmapper.Fieldmapper'); dojo.require('fieldmapper.OrgUtils'); dojo.require('openils.Util'); + dojo.requireLocalization("openils.User", "User"); dojo.declare('openils.User', null, { @@ -282,12 +283,27 @@ if(!dojo._hasResource["openils.User"]) { else _u.getPermOrgList(perm, buildTreePicker, true); }, + }); openils.User.user = null; openils.User.authtoken = null; openils.User.authtime = null; openils.User.authcookie = null; + openils.User.localeStrings = + dojo.i18n.getLocalization("openils.User", "User"); + + openils.User.formalName = function(u) { + if (!u) u = openils.User.user; + return dojo.string.substitute( + openils.User.localeStrings.FULL_NAME, [ + u.family_name(), u.first_given_name(), + u.second_given_name() ? u.second_given_name() : "", + u.prefix() ? u.prefix() : "", + u.suffix() ? u.suffix() : "" + ] + ).replace(/\s{2,}/g, " ").replace(/\s$/, ""); + }; } diff --git a/Open-ILS/web/js/dojo/openils/User/nls/User.js b/Open-ILS/web/js/dojo/openils/User/nls/User.js new file mode 100644 index 0000000000..c2041cffd0 --- /dev/null +++ b/Open-ILS/web/js/dojo/openils/User/nls/User.js @@ -0,0 +1,3 @@ +{ + 'FULL_NAME': "${0}, ${3} ${1} ${2} ${4}" +} 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 ed859228de..31d1cdb3a2 100644 --- a/Open-ILS/web/js/dojo/openils/acq/nls/acq.js +++ b/Open-ILS/web/js/dojo/openils/acq/nls/acq.js @@ -22,5 +22,9 @@ 'UNRECEIVE_LID': "Are you sure you want to mark this copy as UN-received?", 'CONFIRM_LI_ALERT': "An alert has been placed on the lineitem titled,\n\"${0}\":\n\n${1}\n${2}\n${3}\nChoose OK if you wish to acknowledge this alert.", - 'ALERT_UNSELECTED': "You must choose an alert code." + 'ALERT_UNSELECTED': "You must choose an alert code.", + 'DFA_TIP': "<strong>Applied by</strong>: ${0}<br /><strong>When</strong>: ${1}", + 'ITS_YOU': "You", + 'JUST_NOW': "Just now", + 'EXPLAIN_DFA_MGMT': "Remove record of this distribution formula usage?" } 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 c4bbf289ba..4702426b8e 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 @@ -4,6 +4,7 @@ dojo.require('dijit.form.Button'); dojo.require('dijit.form.TextBox'); dojo.require('dijit.form.FilteringSelect'); dojo.require('dijit.form.Textarea'); +dojo.require('dijit.Tooltip'); dojo.require('dijit.ProgressBar'); dojo.require('openils.User'); dojo.require('openils.Util'); @@ -35,13 +36,16 @@ function AcqLiTable() { this.liCache = {}; this.plCache = {}; this.poCache = {}; - this.dfaCache = {}; + this.realDfaCache = {}; + this.virtDfaCounts = {}; + this.virtDfaId = -1; this.dfeOffset = 0; this.toggleState = false; this.tbody = dojo.byId('acq-lit-tbody'); this.selectors = []; this.noteAcks = {}; this.authtoken = openils.User.authtoken; + this.pcrud = new openils.PermaCrud(); this.rowTemplate = this.tbody.removeChild(dojo.byId('acq-lit-row')); this.copyTbody = dojo.byId('acq-lit-li-details-tbody'); this.copyRow = this.copyTbody.removeChild(dojo.byId('acq-lit-li-details-row')); @@ -322,7 +326,7 @@ function AcqLiTable() { acqLitAlertAlertText.store = new dojo.data.ItemFileReadStore( { "data": acqliat.toStoreData( - (new openils.PermaCrud()).search( + this.pcrud.search( "acqliat", {"id": {"!=": null}} ) ) @@ -625,7 +629,8 @@ function AcqLiTable() { this.copyCache = {}; this.copyWidgetCache = {}; this.oldCopyWidgetCache = {}; - this.dfaCache = {}; + this.virtDfaCounts = {}; + this.realDfaCache = {}; this.dfeOffset = 0; acqLitSaveCopies.onClick = function() { self.saveCopyChanges(liId) }; @@ -637,6 +642,8 @@ function AcqLiTable() { this._drawBatchCopyWidgets(); + this._drawDistribApplied(liId); + this._fetchDistribFormulas( function() { openils.acq.Lineitem.fetchAttrDefs( @@ -648,6 +655,158 @@ function AcqLiTable() { ); }; + this._saveDistribAppliedTemplates = function() { + if (!this._appliedDistribTemplate) { + this._appliedDistribTemplate = + dojo.byId("acq-lit-distrib-applied-tbody"). + removeChild(dojo.byId("acq-lit-distrib-applied-row")); + dojo.attr(this._appliedDistribTemplate, "id"); + } + }; + + this._drawDistribApplied = function(liId) { + /* Build this table while hidden to prevent rendering artifacts */ + openils.Util.hide("acq-lit-distrib-applied-tbody"); + + this._saveDistribAppliedTemplates(); + + /* Remove any rows in the table from previous populations */ + dojo.query("tr[formula]", "acq-lit-distrib-applied-tbody"). + forEach(dojo.destroy); + + /* Unregister all dijits previously created (for some reason this isn't + * covered by the above destroy calls). */ + dijit.registry.forEach( + function(w) { if (/^dfa-/.test(w.id)) w.destroyRecursive(); } + ); + + /* Populate the table with our liId */ + var total = 0; + fieldmapper.standardRequest( + ["open-ils.acq", + "open-ils.acq.distribution_formula_application.ranged.retrieve"], + { + "async": true, + "params": [self.authtoken, liId], + "onresponse": function(r) { + var dfa = openils.Util.readResponse(r); + if (dfa) { + total++; + self.realDfaCache[dfa.id()] = dfa; + self._drawDistribAppliedUnit(dfa); + } + }, + "oncomplete": function() { + /* Reveal built table */ + if (total) { + openils.Util.show( + "acq-lit-distrib-applied-tbody", "table-row-group" + ); + } + } + } + ); + }; + + this._drawDistribAppliedUnit = function(dfa) { + var new_row = false; + var row = dojo.query( + 'tr[formula="' + dfa.formula().id() + '"]', + "acq-lit-distrib-applied-tbody" + )[0]; + + if (!row) { + new_row = true; + row = dojo.clone(this._appliedDistribTemplate); + dojo.attr(row, "formula", dfa.formula().id()); + dojo.query("th", row)[0].innerHTML = dfa.formula().name(); + } + + var td = dojo.query("td", row)[0]; + + dojo.create("span", {"id": "dfa-button-" + dfa.id()}, td, "last"); + dojo.create("span", {"id": "dfa-tip-" + dfa.id()}, td, "last"); + + if (new_row) + dojo.place(row, "acq-lit-distrib-applied-tbody", "last"); + + new dijit.form.Button( + { + "onClick": function() { + if (confirm(localeStrings.EXPLAIN_DFA_MGMT)) + self.deleteDfa(dfa); + }, + "label": "X", + /* XXX I /cannot/ make the following work in as a CSS class + * for some reason. So frustrating... */ + "style": function(id) { + return (id > 0 ? + "font-weight: bold; color: #c00;" : + "color: #666;"); + }(dfa.id()) + "margin: 0 6px;display: inline;" + }, "dfa-button-" + dfa.id() + ); + new dijit.Tooltip( + { + "connectId": ["dfa-button-" + dfa.id()], + "label": dojo.string.substitute( + localeStrings.DFA_TIP, dfa.id() > 0 ? [ + openils.User.formalName(dfa.creator()), + dojo.date.locale.format( + dojo.date.stamp.fromISOString(dfa.create_time()), + {"formatLength":"short"} + ) + ] : [localeStrings.ITS_YOU, localeStrings.JUST_NOW] + ) + }, "dfa-tip-" + dfa.id() + ); + } + + this.deleteDfa = function(dfa) { + if (dfa.id() > 0) { /* real */ + this.pcrud.delete( + dfa, { + "async": true, + "oncomplete": function() { + self._removeDistribApplied(dfa.id()); + delete self.realDfaCache[dfa.id()]; + } + } + ); + } else { /* virtual */ + if (--(this.virtDfaCounts[dfa.formula().id()]) < 0) + this.virtDfaCounts[dfa.formula().id()] = 0; + /* hasn't been saved yet, so no need to do anything server side */ + this._removeDistribApplied(dfa.id()); + } + + }; + + this._removeDistribApplied = function(dfaId) { + var re = new RegExp("^dfa-\\w+-" + String(dfaId)); + dijit.registry.forEach( + function(w) { if (re.test(w.id)) w.destroyRecursive(); } + ); + this._removeDistribAppliedEmptyRows(); + }; + + this._removeAllDistribAppliedVirtual = function() { + /* Unregister dijits */ + dijit.registry.forEach( + function(w) { if (/^dfa-\w+--/.test(w.id)) w.destroyRecursive(); } + ); + this._removeDistribAppliedEmptyRows(); + }; + + this._removeDistribAppliedEmptyRows = function() { + /* Remove any rows with no DFA at all */ + dojo.query("tr[formula] td", "acq-lit-distrib-applied-tbody").forEach( + function(o) { + if (o.childNodes.length < 1) dojo.destroy(o.parentNode); + } + ); + }; + /** * Insert a new row into the distribution formula selection form */ @@ -697,9 +856,11 @@ function AcqLiTable() { dojo.connect(reset, 'onClick', function() { self.restoreCopyFieldsBeforeDF(); - self.dfaCache = {}; + self.virtDfaCounts = {}; + self.virtDfaId = -1; self.dfeOffset = 0; self._updateFormulaStore(); + self._removeAllDistribAppliedVirtual(); reset.attr("disabled", "true"); } ); @@ -765,8 +926,15 @@ function AcqLiTable() { } if (entries_applied) { - this.dfaCache[formula.id()] = ++(this.dfaCache[formula.id()]) || 1; + this.virtDfaCounts[formula.id()] = + ++(this.virtDfaCounts[formula.id()]) || 1; this._updateFormulaStore(); + this._drawDistribAppliedUnit( + function(df) { + var dfa = new acqdfa(); + dfa.formula(df); dfa.id(self.virtDfaId--); return dfa; + }(formula) + ); this.dfeOffset += entries_applied; }; }; @@ -819,8 +987,8 @@ function AcqLiTable() { var obj = store_data.items[key]; obj.use_count = Number(obj.use_count); /* needed for sorting */ - if (this.dfaCache[obj.id]) - obj.use_count = obj.use_count + Number(this.dfaCache[obj.id]); + if (this.virtDfaCounts[obj.id]) + obj.use_count = obj.use_count + Number(this.virtDfaCounts[obj.id]); obj.dynLabel = "<span class='acq-lit-distrib-form-use-count'>[" + obj.use_count + "]</span> " + obj.name; @@ -1071,10 +1239,10 @@ function AcqLiTable() { this.copyTbody.removeChild(row); } - this._dfaCacheAsList = function() { + this._virtDfaCountsAsList = function() { var L = []; - for (var key in this.dfaCache) { - for (var i = 0; i < this.dfaCache[key]; i++) + for (var key in this.virtDfaCounts) { + for (var i = 0; i < this.virtDfaCounts[key]; i++) L.push(key); } return L; @@ -1120,7 +1288,7 @@ function AcqLiTable() { ); } - var dfa_list = this._dfaCacheAsList(); + var dfa_list = this._virtDfaCountsAsList(); if (dfa_list.length > 0) { fieldmapper.standardRequest( ["open-ils.acq", @@ -1135,7 +1303,7 @@ function AcqLiTable() { } } ); - this.dfaCache = {}; + this.virtDfaCounts = {}; } } @@ -1239,7 +1407,7 @@ function AcqLiTable() { acqLitExportAttrSelector.store = new dojo.data.ItemFileReadStore( { "data": acqliad.toStoreData( - (new openils.PermaCrud()).search( + this.pcrud.search( "acqliad", {"code": li_exportable_attrs} ) ) @@ -1573,7 +1741,6 @@ function AcqLiTable() { this.realCopiesTbody.removeChild(this.realCopiesTbody.childNodes[0]); this.show('real-copies'); - var pcrud = new openils.PermaCrud({authtoken : this.authtoken}); this.realCopyList = []; this.volCache = {}; var tabIndex = 1000; @@ -1587,7 +1754,7 @@ function AcqLiTable() { function(fullLi) { li = self.liCache[li.id()] = fullLi; - pcrud.search( + self.pcrud.search( 'acp', { id : li.lineitem_details().map( function(item) { return item.eg_copy_id() } @@ -1600,7 +1767,7 @@ function AcqLiTable() { var volId = copy.call_number(); var volume = self.volCache[volId]; if(!volume) { - volume = self.volCache[volId] = pcrud.retrieve('acn', volId); + volume = self.volCache[volId] = self.pcrud.retrieve('acn', volId); } self.addRealCopy(volume, copy, tabIndex++); } @@ -1669,10 +1836,9 @@ function AcqLiTable() { }; this.saveRealCopies = function() { - var pcrud = new openils.PermaCrud({authtoken : this.authtoken}); progressDialog.show(true); var list = this.realCopyList.filter(function(copy) { return copy.ischanged(); }); - pcrud.update(list, {oncomplete: function() { + this.pcrud.update(list, {oncomplete: function() { progressDialog.hide(); self.show('list'); }}); 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 ec3caf827e..c5891d7ead 100644 --- a/Open-ILS/web/templates/default/acq/common/li_table.tt2 +++ b/Open-ILS/web/templates/default/acq/common/li_table.tt2 @@ -239,6 +239,19 @@ </tbody> <tbody><tr><td class='acq-lit-table-spacer' colspan='0'/></tr></tbody> + <tbody id="acq-lit-distrib-applied-tbody" class="hidden"> + <tr> + <td colspan="5" id="acq-lit-distrib-applied-heading"> + Distribution formulas applied to this lineitem: + </td> + </tr> + <tr id="acq-lit-distrib-applied-row" class="acq-lit-distrib-applied-row"> + <th></th> + <td colspan="4"></td> + </tr> + </tbody> + + <tbody><tr><td class='acq-lit-table-spacer' colspan='0'/></tr></tbody> <tbody style='font-weight:bold;'> <tr> <td style='margin-top:30px;'>Owning Branch</td> -- 2.11.0