1 dojo.require('dojo.date.locale');
2 dojo.require('dojo.date.stamp');
3 dojo.require('dijit.form.Button');
4 dojo.require('dijit.form.TextBox');
5 dojo.require('dijit.form.FilteringSelect');
6 dojo.require('dijit.form.Textarea');
7 dojo.require('dijit.Tooltip');
8 dojo.require('dijit.ProgressBar');
9 dojo.require('openils.acq.Lineitem');
10 dojo.require('openils.acq.PO');
11 dojo.require('openils.acq.Picklist');
12 dojo.require('openils.widget.AutoFieldWidget');
13 dojo.require('dojo.data.ItemFileReadStore');
14 dojo.require('openils.widget.ProgressDialog');
15 dojo.require('openils.PermaCrud');
16 dojo.require("openils.widget.PCrudAutocompleteBox");
18 dojo.requireLocalization('openils.acq', 'acq');
19 var localeStrings = dojo.i18n.getLocalization('openils.acq', 'acq');
20 const XUL_OPAC_WRAPPER = 'chrome://open_ils_staff_client/content/cat/opac.xul';
21 var li_exportable_attrs = ["issn", "isbn", "upc"];
23 var fundLabelFormat = [
24 '<span class="fund_${0}">${1} (${2})</span>', 'id', 'code', 'year'
26 var fundSearchFormat = ['${0} (${1})', 'code', 'year'];
28 function nodeByName(name, context) {
29 return dojo.query('[name='+name+']', context)[0];
32 // for caching linked users. e.g. lineitem_detail.receiver
35 var liDetailBatchFields = ['fund', 'owning_lib', 'location', 'collection_code', 'circ_modifier', 'cn_label'];
36 var liDetailFields = liDetailBatchFields.concat(['barcode', 'note']);
38 "stop": "color: #c00; font-weight: bold;",
39 "warning": "color: #c93;"
42 function AcqLiTable() {
49 this.haveFundClass = {}
50 this.fundBalanceState = {};
51 this.realDfaCache = {};
52 this.virtDfaCounts = {};
55 this.claimEligibleLidByLi = {};
56 this.claimEligibleLid = {};
57 this.toggleState = false;
58 this.tbody = dojo.byId('acq-lit-tbody');
61 this.authtoken = openils.User.authtoken;
62 this.pcrud = new openils.PermaCrud();
63 this.rowTemplate = this.tbody.removeChild(dojo.byId('acq-lit-row'));
64 this.copyTbody = dojo.byId('acq-lit-li-details-tbody');
65 this.copyRow = this.copyTbody.removeChild(dojo.byId('acq-lit-li-details-row'));
66 this.copyBatchRow = dojo.byId('acq-lit-li-details-batch-row');
67 this.copyBatchWidgets = {};
68 this.liNotesTbody = dojo.byId('acq-lit-notes-tbody');
69 this.liNotesRow = this.liNotesTbody.removeChild(dojo.byId('acq-lit-notes-row'));
70 this.realCopiesTbody = dojo.byId('acq-lit-real-copies-tbody');
71 this.realCopiesRow = this.realCopiesTbody.removeChild(dojo.byId('acq-lit-real-copies-row'));
72 this._copy_fields_for_acqdf = ['owning_lib', 'location'];
73 this.skipInitialEligibilityCheck = false;
74 this.claimDialog = new ClaimDialogManager(
75 liClaimDialog, finalClaimDialog, this.claimEligibleLidByLi,
76 function(li) { /* callback that fires when claims are made */
77 self.fetchClaimInfo(li.id(), /* force update */ true);
81 dojo.byId("acq-lit-li-actions-selector").onchange = function() {
82 self.applySelectedLiAction(this.options[this.selectedIndex].value);
83 this.selectedIndex = 0;
86 acqLitCreatePoSubmit.onClick = function() {
87 if (self._confirmPoPrepaySituation()) {
88 acqLitPoCreateDialog.hide();
89 self._createPO(acqLitPoCreateDialog.getValues());
95 acqLitSavePlButton.onClick = function() {
96 acqLitSavePlDialog.hide();
97 self._savePl(acqLitSavePlDialog.getValues());
100 acqLitCancelLiStateButton.onClick = function() {
101 acqLitChangeLiStateDialog.hide();
103 acqLitSaveLiStateButton.onClick = function() {
104 acqLitChangeLiStateDialog.hide();
105 self._updateLiState(acqLitChangeLiStateDialog.getValues(), acqLitChangeLiStateDialog.attr('state'));
109 dojo.byId('acq-lit-select-toggle').onclick = function(){self.toggleSelect()};
110 dojo.byId('acq-lit-info-back-button').onclick = function(){self.show('list')};
111 dojo.byId('acq-lit-copies-back-button').onclick = function(){self.show('list')};
112 dojo.byId('acq-lit-notes-back-button').onclick = function(){self.show('list')};
113 dojo.byId('acq-lit-real-copies-back-button').onclick = function(){self.show('list')};
115 this.reset = function(keep_selectors) {
116 while(self.tbody.childNodes[0])
117 self.tbody.removeChild(self.tbody.childNodes[0]);
125 this.setNext = function(handler) {
126 var link = dojo.byId('acq-lit-next');
128 dojo.style(link, 'visibility', 'visible');
129 link.onclick = handler;
131 dojo.style(link, 'visibility', 'hidden');
135 this.setPrev = function(handler) {
136 var link = dojo.byId('acq-lit-prev');
138 dojo.style(link, 'visibility', 'visible');
139 link.onclick = handler;
141 dojo.style(link, 'visibility', 'hidden');
145 this.show = function(div) {
146 openils.Util.hide('acq-lit-table-div');
147 openils.Util.hide('acq-lit-info-div');
148 openils.Util.hide('acq-lit-li-details');
149 openils.Util.hide('acq-lit-notes-div');
150 openils.Util.hide('acq-lit-real-copies-div');
153 openils.Util.show('acq-lit-table-div');
156 openils.Util.show('acq-lit-info-div');
159 openils.Util.show('acq-lit-li-details');
162 openils.Util.show('acq-lit-real-copies-div');
165 openils.Util.show('acq-lit-notes-div');
169 openils.Util.show(div);
173 this.hide = function() {
177 this.toggleSelect = function() {
179 dojo.forEach(self.selectors, function(i){i.checked = false});
181 dojo.forEach(self.selectors, function(i){i.checked = true});
182 self.toggleState = !self.toggleState;
186 /** @param all If true, assume all are selected */
187 this.getSelected = function(all) {
189 var indices = {}; /* use to uniqify. needed in paging situations. */
190 dojo.forEach(self.selectors,
193 indices[i.parentNode.parentNode.getAttribute('li')] = true;
196 return openils.Util.objectProperties(indices).map(
197 function(liId) { return self.liCache[liId]; }
201 this.setRowAttr = function(td, liWrapper, field, type) {
202 var val = liWrapper.findAttr(field, type || 'lineitem_marc_attr_definition') || '';
203 td.appendChild(document.createTextNode(val));
206 this.setClaimPolicyControl = function(li, row) {
207 if (!self.claimPolicyPicker) {
208 self.claimPolicyPicker = true; /* prevents a race condition */
209 new openils.widget.AutoFieldWidget({
210 "parentNode": "acq-lit-li-claim-policy",
212 "selfReference": true,
213 "dijitArgs": {"required": true}
214 }).build(function(w) { self.claimPolicyPicker = w; });
217 if (!row) row = this._findLiRow(li);
219 var actViewPolicy = nodeByName("action_view_claim_policy", row);
220 if (li.claim_policy())
221 actViewPolicy.innerHTML = localeStrings.CHANGE_CLAIM_POLICY;
223 if (!actViewPolicy.onclick) {
224 actViewPolicy.onclick = function() {
225 if (li.claim_policy())
226 self.claimPolicyPicker.attr("value", li.claim_policy());
227 liClaimPolicyDialog.show();
228 liClaimPolicySave.onClick = function() {
229 self.changeClaimPolicy(
230 [li], self.claimPolicyPicker.attr("value"),
232 self.setClaimPolicyControl(li, row);
233 self.reconsiderClaimControl(li, row);
234 liClaimPolicyDialog.hide();
242 this.fetchClaimInfo = function(liId, force, callback, row) {
244 liId, function(full) {
245 self.liCache[full.id()] = full;
246 self.checkClaimEligibility(full, callback, row);
252 * Inserts a single lineitem into the growing table of lineitems
253 * @param {Object} li The lineitem object to insert
255 this.addLineitem = function(li, skip_final_placement) {
256 this.liCache[li.id()] = li;
258 // insert the row right away so that final order isn't
259 // dependent on how long subsequent async request take
260 // for a given line item
261 var row = self.rowTemplate.cloneNode(true);
262 if (!skip_final_placement) {
263 self.tbody.appendChild(row);
264 self.selectors.push(dojo.query('[name=selectbox]', row)[0]);
267 // sort the lineitem notes on edit_time
268 if(!li.lineitem_notes()) li.lineitem_notes([]);
270 var liWrapper = new openils.acq.Lineitem({lineitem:li});
271 row.setAttribute('li', li.id());
272 var tds = dojo.query('[attr]', row);
273 dojo.forEach(tds, function(td) {self.setRowAttr(td, liWrapper, td.getAttribute('attr'), td.getAttribute('attr_type'));});
274 dojo.query('[name=source_label]', row)[0].appendChild(document.createTextNode(li.source_label()));
277 liWrapper.findAttr("isbn", "lineitem_marc_attr_definition") ||
278 liWrapper.findAttr("upc", "lineitem_marc_attr_definition");
280 // XXX media prefix for added content
282 nodeByName("jacket", row).setAttribute(
283 "src", "/opac/extras/ac/jacket/small/" + identifier
287 nodeByName("liid", row).innerHTML += li.id();
290 openils.Util.show(nodeByName('catalog', row), 'inline');
291 nodeByName("catalog_link", row).onclick = this.generateMakeRecTab(li.eg_bib_id());
293 openils.Util.show(nodeByName('link_to_catalog', row), 'inline');
294 nodeByName("link_to_catalog_link", row).onclick = function() { self.drawBibFinder(li) };
297 nodeByName("worksheet_link", row).href =
298 oilsBasePath + "/acq/lineitem/worksheet/" + li.id();
300 nodeByName("show_requests_link", row).href =
301 oilsBasePath + "/acq/picklist/user_request?lineitem=" + li.id();
303 dojo.query('[attr=title]', row)[0].onclick = function() {self.drawInfo(li.id())};
304 dojo.query('[name=copieslink]', row)[0].onclick = function() {self.drawCopies(li.id())};
305 dojo.query('[name=noteslink]', row)[0].onclick = function() {self.drawLiNotes(li)};
307 if (!this.skipInitialEligibilityCheck)
311 function(full) { self.setClaimPolicyControl(full, row) },
315 this.updateLiNotesCount(li, row);
317 // show which PO this lineitem is a member of
318 if(li.purchase_order() && !this.isPO) {
320 this.poCache[li.purchase_order()] =
321 this.poCache[li.purchase_order()] ||
322 fieldmapper.standardRequest(
323 ['open-ils.acq', 'open-ils.acq.purchase_order.retrieve'],
325 this.authtoken, li.purchase_order(), {
326 "flesh_price_summary": true,
327 "flesh_lineitem_count": true
330 if(po && !this.isMeta) {
331 openils.Util.show(nodeByName('po', row), 'inline');
332 var link = nodeByName('po_link', row);
333 link.setAttribute('href', oilsBasePath + '/acq/po/view/' + li.purchase_order());
334 link.innerHTML += po.name();
338 // show which picklist this lineitem is a member of
339 if(li.picklist() && (this.isPO || this.isMeta || this.isUni)) {
341 this.plCache[li.picklist()] =
342 this.plCache[li.picklist()] ||
343 fieldmapper.standardRequest(
344 ['open-ils.acq', 'open-ils.acq.picklist.retrieve.authoritative'],
345 {params: [this.authtoken, li.picklist()]});
347 if (pl.name() == "") {
348 openils.Util.show(nodeByName("bib_origin", row), "inline");
352 openils.Util.show(nodeByName('pl', row), 'inline');
353 var link = nodeByName('pl_link', row);
354 link.setAttribute('href', oilsBasePath + '/acq/picklist/view/' + li.picklist());
355 link.innerHTML += pl.name();
360 var countNode = nodeByName('count', row);
361 var count = li.item_count() || 0;
362 if (typeof(this._copy_count_cb) == "function") {
363 this._copy_count_cb(li.id(), count);
365 countNode.innerHTML = count;
366 countNode.id = 'acq-lit-copy-count-label-' + li.id();
369 var priceInput = dojo.query('[name=price]', row)[0];
370 priceInput.value = li.estimated_unit_price() || '';
371 priceInput.onchange = function() { self.updateLiPrice(priceInput, li) };
373 // show either "mark received" or "unreceive" as appropriate
374 this.updateLiState(li, row);
376 if (skip_final_placement) {
381 this._liCountClaims = function(li) {
383 for (var i = 0; i < li.lineitem_details().length; i++)
384 total += li.lineitem_details()[i].claims().length;
388 this._findLiRow = function(li) {
389 return dojo.query('tr[li="' + li.id() + '"]', "acq-lit-tbody")[0];
392 this.reconsiderClaimControl = function(li, row) {
393 if (!row) row = this._findLiRow(li);
394 var option = nodeByName("action_manage_claims", row);
395 var eligible = this.claimEligibleLidByLi[li.id()].length;
396 var count = this._liCountClaims(li);
398 option.disabled = !(count || eligible);
400 dojo.string.substitute(localeStrings.NUM_CLAIMS_EXISTING, [count]);
401 option.onclick = function() { self.claimDialog.show(li); };
404 this.clearEligibility = function(li) {
405 this.claimEligibleLidByLi[li.id()] = [];
407 if (li.lineitem_details()) {
408 li.lineitem_details().forEach(
409 function(lid) { delete self.claimEligibleLid[lid.id()]; }
413 if (this.copyCache) {
415 for (var k in this.copyCache) {
416 if (this.copyCache[k].lineitem() == li.id())
420 function(k) { delete self.claimEligibleLid[k]; }
425 this.checkClaimEligibility = function(li, callback, row) {
426 /* Assume always eligible, i.e. from this interface we don't care about
427 * claim eligibility any more. this is where the user would force a
429 this.clearEligibility(li);
430 this.claimEligibleLidByLi[li.id()] = li.lineitem_details().map(
431 function(lid) { return lid.id(); }
433 li.lineitem_details().forEach(
434 function(lid) { self.claimEligibleLid[lid.id()] = true; }
436 this.reconsiderClaimControl(li, row);
437 if (callback) callback(li);
439 this.clearEligibility(li);
440 fieldmapper.standardRequest(
441 ["open-ils.acq", "open-ils.acq.claim.eligible.lineitem_detail"], {
442 "params": [openils.User.authtoken, {"lineitem": li.id()}],
444 "onresponse": function(r) {
445 if (r = openils.Util.readResponse(r)) {
446 self.claimEligibleLidByLi[li.id()].push(
449 self.claimEligibleLid[r.lineitem_detail()] = true;
452 "oncomplete": function() {
453 self.reconsiderClaimControl(li, row);
454 if (typeof(callback) == "function")
462 this.updateLiNotesCount = function(li, row) {
463 if (!row) row = this._findLiRow(li);
465 var has_notes = (li.lineitem_notes().filter(
466 function(o) { return Boolean (o.alert_text()); }
469 /* U+2691 is the code point for a filled-in flag character */
470 nodeByName("notes_alert_flag", row).innerHTML =
471 has_notes ? "⚑" : "";
472 nodeByName("noteslink", row).style.fontStyle =
473 has_notes ? "italic" : "normal";
474 nodeByName("notes_count", row).innerHTML = li.lineitem_notes().length;
477 /* XXX NOT related to _updateLiState(). rethink */
478 this.updateLiState = function(li, row) {
479 if (!row) row = this._findLiRow(li);
481 var actReceive = nodeByName("action_mark_recv", row);
482 var actUnRecv = nodeByName("action_mark_unrecv", row);
483 var actUpdateBarcodes = nodeByName("action_update_barcodes", row);
484 var actHoldingsMaint = nodeByName("action_holdings_maint", row);
486 var actNewInvoice = nodeByName('action_new_invoice', row);
487 var actLinkInvoice = nodeByName('action_link_invoice', row);
488 var actViewInvoice = nodeByName('action_view_invoice', row);
490 nodeByName('action_view_history', row).onclick =
491 function() { location.href = oilsBasePath + '/acq/lineitem/history/' + li.id(); };
493 var state_cell = nodeByName("li_state", row);
495 if (li.state() == "cancelled") {
496 if (typeof li.cancel_reason() == "object") {
497 var holds_state = dojo.create(
499 "style": "border-bottom: 1px dashed #000;",
500 "innerHTML": li.state()
501 }, state_cell, "only"
505 "label": "<em>" + li.cancel_reason().label() +
506 "</em><br />" + li.cancel_reason().description(),
507 "connectId": [holds_state]
508 }, dojo.create("span", null, state_cell, "last")
511 state_cell.innerHTML = li.state(); // TODO i18n state labels
514 state_cell.innerHTML = li.state(); // TODO i18n state labels
518 /* handle row coloring for based on LI state */
519 openils.Util.removeCSSClass(row, /^oils-acq-li-state-/);
520 openils.Util.addCSSClass(row, "oils-acq-li-state-" + li.state());
522 /* handle links that appear/disappear based on whether LI is received */
526 actNewInvoice.onclick = function() {
527 location.href = oilsBasePath + '/acq/invoice/view?create=1&attach_li=' + li.id();
528 nodeByName("action_none", row).selected = true;
530 actLinkInvoice.onclick = function() {
531 if (!self.invoiceLinkDialogManager) {
532 self.invoiceLinkDialogManager =
533 new InvoiceLinkDialogManager("li");
535 self.invoiceLinkDialogManager.target = li;
536 acqLitLinkInvoiceDialog.show();
537 nodeByName("action_none", row).selected = true;
539 actViewInvoice.onclick = function() {
540 location.href = oilsBasePath +
541 "/acq/search/unified?so=" +
542 base64Encode({"jub":[{"id": li.id()}]}) +
544 nodeByName("action_none", row).selected = true;
547 actNewInvoice.disabled = false;
548 actLinkInvoice.disabled = false;
549 actViewInvoice.disabled = false;
553 actReceive.disabled = false;
554 actReceive.onclick = function() {
555 if (self.checkLiAlerts(li.id()))
556 self.issueReceive(li);
557 nodeByName("action_none", row).selected = true;
562 actUnRecv.disabled = false;
563 actUnRecv.onclick = function() {
564 if (confirm(localeStrings.UNRECEIVE_LI))
565 self.issueReceive(li, /* rollback */ true);
566 nodeByName("action_none", row).selected = true;
568 // TODO we should allow editing before receipt, in which case the
569 // test should be "if 1 or more real (acp) copies exist
570 actUpdateBarcodes.disabled = false;
571 actUpdateBarcodes.onclick = function() {
572 self.showRealCopyEditUI(li);
573 nodeByName("action_none", row).selected = true;
575 actHoldingsMaint.disabled = false;
576 actHoldingsMaint.onclick = self.generateMakeRecTab( li.eg_bib_id(), 'copy_browser', row );
584 this._setAlertStore = function() {
585 acqLitAlertAlertText.store = new dojo.data.ItemFileReadStore(
587 "data": acqliat.toStoreData(
589 "acqliat", {"id": {"!=": null}}
594 acqLitAlertAlertText.setValue(); /* make the store "live" */
595 acqLitAlertAlertText._store_ready = true;
599 * Draws and shows the lineitem notes pane
601 this.drawLiNotes = function(li) {
604 if (!acqLitAlertAlertText._store_ready)
605 this._setAlertStore();
608 li.lineitem_notes().sort(
610 if(a.edit_time() < b.edit_time()) return 1;
616 while(this.liNotesTbody.childNodes[0])
617 this.liNotesTbody.removeChild(this.liNotesTbody.childNodes[0]);
620 acqLitCreateNoteSubmit.onClick = function() {
621 var value = acqLitCreateNoteText.attr('value');
623 var note = new fieldmapper.acqlin();
626 Boolean(acqLitCreateNoteVendorPublic.attr('checked'))
629 note.lineitem(li.id());
631 self.updateLiNotes(li, note);
632 acqLitCreateNoteVendorPublic.attr("checked", false);
633 acqLitCreateNoteText.attr("value", "");
636 acqLitCreateAlertSubmit.onClick = function() {
637 if (!acqLitAlertAlertText.item) {
638 alert(localeStrings.ALERT_UNSELECTED);
642 var alert_text = new fieldmapper.acqliat().fromStoreItem(
643 acqLitAlertAlertText.item
645 var value = acqLitAlertNoteValue.attr("value") || "";
647 var note = new fieldmapper.acqlin();
649 note.lineitem(li.id());
651 note.alert_text(alert_text);
653 self.updateLiNotes(li, note);
656 dojo.forEach(li.lineitem_notes(), function(note) { self.addLiNote(li, note) });
660 * Draws a single lineitem note in the notes pane
662 this.addLiNote = function(li, note) {
663 if(note.isdeleted()) return;
665 var row = self.liNotesRow.cloneNode(true);
666 nodeByName("value", row).innerHTML = note.value();
667 var alert_node = nodeByName("alert_code", row);
668 if (note.alert_text()) {
669 alert_node.innerHTML = note.alert_text().code();
670 if (note.alert_text().description()) {
673 "connectId": [alert_node],
674 "label": note.alert_text().description()
675 }, dojo.create("span", null, alert_node, "after")
680 if (openils.Util.isTrue(note.vendor_public()))
681 nodeByName("vendor_public", row).innerHTML =
682 localeStrings.VENDOR_PUBLIC;
684 nodeByName("delete", row).onclick = function() {
685 note.isdeleted(true);
686 self.liNotesTbody.removeChild(row);
687 self.updateLiNotes(li);
690 if(note.edit_time()) {
691 nodeByName("edit_time", row).innerHTML =
692 dojo.date.locale.format(
693 dojo.date.stamp.fromISOString(note.edit_time()),
694 {formatLength:'short'});
697 self.liNotesTbody.appendChild(row);
701 * Updates any new/changed/deleted notes on the server
703 this.updateLiNotes = function(li, newNote) {
709 notes = li.lineitem_notes().filter(
711 if(note.ischanged() || note.isnew() || note.isdeleted())
717 if(notes.length == 0) return;
718 progressDialog.show();
720 fieldmapper.standardRequest(
721 ['open-ils.acq', 'open-ils.acq.lineitem_note.cud.batch'],
723 params : [this.authtoken, notes],
724 onresponse : function(r) {
725 var resp = openils.Util.readResponse(r);
730 // remove the old changed notes
732 dojo.forEach(li.lineitem_notes(),
734 if(!(note.ischanged() || note.isnew() || note.isdeleted()))
738 li.lineitem_notes(list);
741 progressDialog.hide();
742 self.updateLiNotesCount(li);
743 self.drawLiNotes(li);
747 progressDialog.update(resp);
748 var newnote = resp.note;
750 if(!newnote.isdeleted()) {
751 newnote.isnew(false);
752 newnote.ischanged(false);
753 li.lineitem_notes().push(newnote);
760 this.updateLiPrice = function(input, li) {
762 var price = input.value;
763 if(Number(price) == Number(li.estimated_unit_price())) return;
765 fieldmapper.standardRequest(
766 ['open-ils.acq', 'open-ils.acq.lineitem.price.set'],
767 { async : false, // redundant w/ timeout
769 params : [this.authtoken, li.id(), price],
770 oncomplete : function(r) {
771 openils.Util.readResponse(r);
772 li.estimated_unit_price(price); // update local copy
778 this.removeLineitem = function(liId) {
779 this.tbody.removeChild(dojo.query('[li='+liId+']', this.tbody)[0]);
780 delete this.liCache[liId];
781 //selected.push(self.liCache[i.parentNode.parentNode.getAttribute('li')]);
784 this.drawInfo = function(liId) {
785 if (!this._isRelatedViewer) {
786 var d = dojo.byId("acq-lit-info-related");
787 if (!this.relCache[liId]) {
788 fieldmapper.standardRequest(
791 "open-ils.acq.lineitems_for_bib.by_lineitem_id.count"
794 "params": [openils.User.authtoken, liId],
795 "onresponse": function(r) {
796 self.relCache[liId] = openils.Util.readResponse(r);
797 nodeByName("related_number", d).innerHTML =
800 self.relCache[liId] >1 ? "show" : "hide"
806 nodeByName("related_number", d).innerHTML = this.relCache[liId];
807 openils.Util[this.relCache[liId] > 1 ? "show" : "hide"](d);
812 openils.acq.Lineitem.fetchAttrDefs(
814 self._fetchLineitem(liId, function(li){self._drawInfo(li);});
819 this._fetchLineitem = function(liId, handler, force) {
821 var li = this.liCache[liId];
822 if(li && li.marc() && li.lineitem_details() && !force)
825 fieldmapper.standardRequest(
826 ['open-ils.acq', 'open-ils.acq.lineitem.retrieve.authoritative'],
829 params: [self.authtoken, liId, {
831 flesh_cancel_reason: true,
832 flesh_li_details: true,
834 flesh_fund_debit: true }],
836 oncomplete: function(r) {
837 var li = openils.Util.readResponse(r);
838 self.liCache[liId] = li;
845 this._drawInfo = function(li) {
847 acqLitEditOrderMarc.onClick = function() { self.editOrderMarc(li); }
850 openils.Util.hide('acq-lit-marc-order-record-label');
851 openils.Util.hide(acqLitEditOrderMarc.domNode);
852 openils.Util.show('acq-lit-marc-real-record-label');
854 openils.Util.show('acq-lit-marc-order-record-label');
855 openils.Util.show(acqLitEditOrderMarc.domNode);
856 openils.Util.hide('acq-lit-marc-real-record-label');
859 this.drawMarcHTML(li);
860 this.infoTbody = dojo.byId('acq-lit-info-tbody');
863 this.infoRow = this.infoTbody.removeChild(dojo.byId('acq-lit-info-row'));
864 while(this.infoTbody.childNodes[0])
865 this.infoTbody.removeChild(this.infoTbody.childNodes[0]);
867 for(var i = 0; i < li.attributes().length; i++) {
868 var attr = li.attributes()[i];
869 var row = this.infoRow.cloneNode(true);
871 var type = attr.attr_type().replace(/lineitem_(.*)_attr_definition/, '$1');
872 var name = openils.acq.Lineitem.attrDefs[type].filter(
874 return (a.code() == attr.attr_name());
876 ).pop().description();
878 dojo.query('[name=label]', row)[0].appendChild(document.createTextNode(name));
879 dojo.query('[name=value]', row)[0].appendChild(document.createTextNode(attr.attr_value()));
880 this.infoTbody.appendChild(row);
883 if (!this._isRelatedViewer) {
884 nodeByName("rel_link", dojo.byId("acq-lit-info-related")).href =
885 oilsBasePath + "/acq/lineitem/related/" + li.id();
890 this.generateMakeRecTab = function(bib_id,default_view, row) {
894 {tab_name: localeStrings.XUL_RECORD_DETAIL_PAGE, browser:false},
897 show_nav_buttons : true,
898 show_print_button : true,
899 opac_url : xulG.url_prefix(xulG.urls.opac_rdetail + bib_id),
900 default_view : default_view
904 if(row) nodeByName("action_none", row).selected = true;
908 this.drawMarcHTML = function(li) {
909 var params = [null, true, li.marc()];
911 params = [li.eg_bib_id(), true];
913 fieldmapper.standardRequest(
914 ['open-ils.search', 'open-ils.search.biblio.record.html'],
917 oncomplete: function(r) {
918 dojo.byId('acq-lit-marc-div').innerHTML =
919 openils.Util.readResponse(r);
925 this.drawCopies = function(liId, force_fetch) {
926 if (typeof force_fetch == "undefined")
929 openils.acq.Lineitem.fetchAndRender(liId, {},
931 dojo.byId('acq-lit-copies-li-summary').innerHTML = html;
938 this.copyWidgetCache = {};
939 this.oldCopyWidgetCache = {};
940 this.virtDfaCounts = {};
941 this.realDfaCache = {};
944 acqLitSaveCopies.onClick = function() { self.saveCopyChanges(liId) };
945 acqLitBatchUpdateCopies.onClick = function() { self.batchCopyUpdate() };
946 acqLitCopyCountInput.attr('value', '0');
948 while(this.copyTbody.childNodes[0])
949 this.copyTbody.removeChild(this.copyTbody.childNodes[0]);
951 this._drawBatchCopyWidgets();
953 this._drawDistribApplied(liId);
955 this._fetchDistribFormulas(
957 openils.acq.Lineitem.fetchAttrDefs(
959 self._fetchLineitem(liId, function(li){self._drawCopies(li);}, force_fetch);
966 this._saveDistribAppliedTemplates = function() {
967 if (!this._appliedDistribTemplate) {
968 this._appliedDistribTemplate =
969 dojo.byId("acq-lit-distrib-applied-tbody").
970 removeChild(dojo.byId("acq-lit-distrib-applied-row"));
971 dojo.attr(this._appliedDistribTemplate, "id");
975 this._drawDistribApplied = function(liId) {
976 /* Build this table while hidden to prevent rendering artifacts */
977 openils.Util.hide("acq-lit-distrib-applied-tbody");
979 this._saveDistribAppliedTemplates();
981 /* Remove any rows in the table from previous populations */
982 dojo.query("tr[formula]", "acq-lit-distrib-applied-tbody").
983 forEach(dojo.destroy);
985 /* Unregister all dijits previously created (for some reason this isn't
986 * covered by the above destroy calls). */
987 dijit.registry.forEach(
988 function(w) { if (/^dfa-/.test(w.id)) w.destroyRecursive(); }
991 /* Populate the table with our liId */
993 fieldmapper.standardRequest(
995 "open-ils.acq.distribution_formula_application.ranged.retrieve"],
998 "params": [self.authtoken, liId],
999 "onresponse": function(r) {
1000 var dfa = openils.Util.readResponse(r);
1003 self.realDfaCache[dfa.id()] = dfa;
1004 self._drawDistribAppliedUnit(dfa);
1007 "oncomplete": function() {
1008 /* Reveal built table */
1011 "acq-lit-distrib-applied-tbody", "table-row-group"
1019 this._drawDistribAppliedUnit = function(dfa) {
1020 var new_row = false;
1021 var row = dojo.query(
1022 'tr[formula="' + dfa.formula().id() + '"]',
1023 "acq-lit-distrib-applied-tbody"
1028 row = dojo.clone(this._appliedDistribTemplate);
1029 dojo.attr(row, "formula", dfa.formula().id());
1030 dojo.query("th", row)[0].innerHTML = dfa.formula().name();
1033 var td = dojo.query("td", row)[0];
1035 dojo.create("span", {"id": "dfa-button-" + dfa.id()}, td, "last");
1036 dojo.create("span", {"id": "dfa-tip-" + dfa.id()}, td, "last");
1039 dojo.place(row, "acq-lit-distrib-applied-tbody", "last");
1041 new dijit.form.Button(
1043 "onClick": function() {
1044 if (confirm(localeStrings.EXPLAIN_DFA_MGMT))
1045 self.deleteDfa(dfa);
1048 /* XXX I /cannot/ make the following work in as a CSS class
1049 * for some reason. So frustrating... */
1050 "style": function(id) {
1052 "font-weight: bold; color: #c00;" :
1054 }(dfa.id()) + "margin: 0 6px;display: inline;"
1055 }, "dfa-button-" + dfa.id()
1059 "connectId": ["dfa-button-" + dfa.id()],
1060 "label": dojo.string.substitute(
1061 localeStrings.DFA_TIP, dfa.id() > 0 ? [
1062 openils.User.formalName(dfa.creator()),
1063 dojo.date.locale.format(
1064 dojo.date.stamp.fromISOString(dfa.create_time()),
1065 {"formatLength":"short"}
1067 ] : [localeStrings.ITS_YOU, localeStrings.JUST_NOW]
1069 }, "dfa-tip-" + dfa.id()
1073 this.deleteDfa = function(dfa) {
1074 if (dfa.id() > 0) { /* real */
1075 this.pcrud.eliminate(
1078 "oncomplete": function() {
1079 self._removeDistribApplied(dfa.id());
1080 delete self.realDfaCache[dfa.id()];
1084 } else { /* virtual */
1085 if (--(this.virtDfaCounts[dfa.formula().id()]) < 0)
1086 this.virtDfaCounts[dfa.formula().id()] = 0;
1087 /* hasn't been saved yet, so no need to do anything server side */
1088 this._removeDistribApplied(dfa.id());
1093 this._removeDistribApplied = function(dfaId) {
1094 var re = new RegExp("^dfa-\\w+-" + String(dfaId));
1095 dijit.registry.forEach(
1096 function(w) { if (re.test(w.id)) w.destroyRecursive(); }
1098 this._removeDistribAppliedEmptyRows();
1101 this._removeAllDistribAppliedVirtual = function() {
1102 /* Unregister dijits */
1103 dijit.registry.forEach(
1104 function(w) { if (/^dfa-\w+--/.test(w.id)) w.destroyRecursive(); }
1106 this._removeDistribAppliedEmptyRows();
1109 this._removeDistribAppliedEmptyRows = function() {
1110 /* Remove any rows with no DFA at all */
1111 dojo.query("tr[formula] td", "acq-lit-distrib-applied-tbody").forEach(
1113 if (o.childNodes.length < 1) dojo.destroy(o.parentNode);
1119 * Insert a new row into the distribution formula selection form
1121 this._addDistribFormulaRow = function() {
1124 if (!self.distribForms) {
1125 // no formulas, hide the form
1126 openils.Util.hide('acq-lit-distrib-formula-table');
1130 if(!this.distribFormulaTemplate)
1131 this.distribFormulaTemplate =
1132 dojo.byId('acq-lit-distrib-formula-tbody').removeChild(dojo.byId('acq-lit-distrib-form-row'));
1134 var row = this.distribFormulaTemplate.cloneNode(true);
1135 dojo.place(row, "acq-lit-distrib-formula-tbody", "only");
1137 this.dfSelector = new dijit.form.FilteringSelect(
1138 {"labelAttr": "dynLabel", "labelType": "html"},
1139 nodeByName("selector", row)
1141 this._updateFormulaStore();
1142 this.dfSelector.fetchProperties =
1143 {"sort": [{"attribute": "use_count", "descending": true}]};
1145 var apply = new dijit.form.Button(
1146 {"label": localeStrings.APPLY},
1147 nodeByName('set_button', row)
1150 var reset = new dijit.form.Button(
1151 {"label": localeStrings.RESET_FORMULAE, "disabled": true},
1152 nodeByName("reset_button", row)
1155 dojo.connect(apply, 'onClick',
1157 var form_id = self.dfSelector.attr("value");
1158 if(!form_id) return;
1159 self._applyDistribFormula(form_id);
1160 reset.attr("disabled", false);
1164 dojo.connect(reset, 'onClick',
1166 self.restoreCopyFieldsBeforeDF();
1167 self.virtDfaCounts = {};
1168 self.virtDfaId = -1;
1170 self._updateFormulaStore();
1171 self._removeAllDistribAppliedVirtual();
1172 reset.attr("disabled", "true");
1179 * Applies a distrib formula to the current set of copies
1181 this._applyDistribFormula = function(formula) {
1182 if(!formula) return;
1184 formula = this.distribForms.filter(
1185 function(form) { return form.id() == formula; }
1188 var copyRows = dojo.query('tr', self.copyTbody);
1190 if (this.dfeOffset >= copyRows.length) {
1191 alert(localeStrings.OUT_OF_COPIES);
1195 var entries_applied = 0;
1197 var rowIndex = this.dfeOffset;
1198 rowIndex < copyRows.length;
1202 var row = copyRows[rowIndex];
1203 var copy_id = row.getAttribute('copy_id');
1204 var copyWidgets = this.copyWidgetCache[copy_id];
1205 var entryIndex = this.dfeOffset;
1208 // find the correct entry for the current row
1209 dojo.forEach(formula.entries(),
1212 entryIndex += e.item_count();
1213 if(entryIndex > rowIndex)
1221 //console.log("rowIndex = " + rowIndex + ", entry = " + entry.id() + ", entryIndex=" +
1222 // entryIndex + ", owning_lib = " + entry.owning_lib() + ", location = " + entry.location());
1225 this.saveCopyFieldsBeforeDF(copy_id);
1226 this._copy_fields_for_acqdf.forEach(
1228 if(entry[field]()) {
1229 copyWidgets[field].attr('value', (entry[field]()));
1236 if (entries_applied) {
1237 this.virtDfaCounts[formula.id()] =
1238 ++(this.virtDfaCounts[formula.id()]) || 1;
1239 this._updateFormulaStore();
1240 this._drawDistribAppliedUnit(
1242 var dfa = new acqdfa();
1243 dfa.formula(df); dfa.id(self.virtDfaId--); return dfa;
1246 this.dfeOffset += entries_applied;
1251 * This function updates the DF store for the dropdown so that use_counts
1252 * can reflect DF applications from this session before they're saved
1255 this._updateFormulaStore = function() {
1256 this.dfSelector.store = new dojo.data.ItemFileReadStore(
1258 "data": self._labelFormulasWithCounts(
1259 acqdf.toStoreData(self.distribForms)
1265 this.saveCopyFieldsBeforeDF = function(copy_id) {
1267 if (!this.oldCopyWidgetCache[copy_id]) {
1268 var copyWidgets = this.copyWidgetCache[copy_id];
1270 this.oldCopyWidgetCache[copy_id] = {};
1271 this._copy_fields_for_acqdf.forEach(
1273 self.oldCopyWidgetCache[copy_id][f] =
1274 copyWidgets[f].attr("value");
1280 this.restoreCopyFieldsBeforeDF = function() {
1282 for (var copy_id in this.oldCopyWidgetCache) {
1283 this._copy_fields_for_acqdf.forEach(
1285 self.copyWidgetCache[copy_id][f].attr(
1286 "value", self.oldCopyWidgetCache[copy_id][f]
1293 this._labelFormulasWithCounts = function(store_data) {
1294 for (var key in store_data.items) {
1295 var obj = store_data.items[key];
1296 obj.use_count = Number(obj.use_count); /* needed for sorting */
1298 if (this.virtDfaCounts[obj.id])
1299 obj.use_count = obj.use_count + Number(this.virtDfaCounts[obj.id]);
1301 obj.dynLabel = "<span class='acq-lit-distrib-form-use-count'>[" +
1302 obj.use_count + "]</span> " + obj.name;
1308 * This method formerly would not refetch the DF formulas if they'd been
1309 * loaded already, but now it always re-fetches, since use_count changes.
1311 /** TODO: port distrib-formula selector to autofieldwidget+pcrud/dojo store */
1312 this._fetchDistribFormulas = function(onload) {
1313 fieldmapper.standardRequest(
1315 "open-ils.acq.distribution_formula.ranged.retrieve.atomic"],
1318 "params": [openils.User.authtoken, 0, 500],
1319 "oncomplete": function(r) {
1320 self.distribForms = openils.Util.readResponse(r);
1321 if(!self.distribForms || self.distribForms.length == 0) {
1322 self.distribForms = [];
1324 self._addDistribFormulaRow();
1331 this._drawBatchCopyWidgets = function() {
1332 var row = this.copyBatchRow;
1333 dojo.forEach(liDetailBatchFields,
1335 if(self.copyBatchRowDrawn) {
1336 self.copyBatchWidgets[field].attr('value', null);
1338 var widget = new openils.widget.AutoFieldWidget({
1341 labelFormat : (field == 'fund') ? fundLabelFormat : null,
1342 searchFormat : (field == 'fund') ? fundSearchFormat : null,
1343 searchFilter : (field == 'fund') ? {"active": "t"} : null,
1344 parentNode : dojo.query('[name='+field+']', row)[0],
1345 orgLimitPerms : ['CREATE_PICKLIST'],
1348 "labelType": (field == "fund") ? "html" : null
1350 noCache: (field == "fund"),
1355 if (field == "fund" && w.store)
1356 self._ensureCSSFundClasses(w.store);
1357 self.copyBatchWidgets[field] = w;
1360 if (field == "fund") {
1362 widget.widget, "onChange", function(val) {
1363 self._updateFundSelectorStyle(widget, val);
1370 this.copyBatchRowDrawn = true;
1373 this.batchCopyUpdate = function() {
1375 for(var k in this.copyWidgetCache) {
1376 var cache = this.copyWidgetCache[k];
1377 dojo.forEach(liDetailBatchFields, function(f) {
1378 var newval = self.copyBatchWidgets[f].attr('value');
1379 if(newval) cache[f].attr('value', newval);
1384 this._drawCopies = function(li) {
1387 // this button sets the total number of copies for a given lineitem
1388 acqLitAddCopyCount.onClick = function() {
1389 var count = acqLitCopyCountInput.attr('value');
1392 while(self.copyCount() < count)
1395 // delete rows if necessary
1396 var diff = self.copyCount() - count;
1398 var rows = dojo.query('tr', self.copyTbody).reverse().slice(0, diff);
1399 if(confirm(dojo.string.substitute(localeStrings.DELETE_LI_COPIES_CONFIRM, [diff]))) {
1400 dojo.forEach(rows, function(row) {self.deleteCopy(row); });
1402 acqLitCopyCountInput.attr('value', self.copyCount()+'');
1408 if(li.lineitem_details().length > 0) {
1409 dojo.forEach(li.lineitem_details(),
1411 self.addCopy(li, copy);
1419 this.copyCount = function() {
1421 for(var id in this.copyCache) {
1422 if(!this.copyCache[id].isdeleted())
1428 this.virtCopyId = -1;
1429 this.addCopy = function(li, copy) {
1430 var row = this.copyRow.cloneNode(true);
1431 this.copyTbody.appendChild(row);
1435 copy = new fieldmapper.acqlid();
1437 copy.id(this.virtCopyId--);
1438 copy.lineitem(li.id());
1441 this.copyCache[copy.id()] = copy;
1442 row.setAttribute('copy_id', copy.id());
1443 self.copyWidgetCache[copy.id()] = {};
1445 acqLitCopyCountInput.attr('value', self.copyCount()+'');
1447 var rcvr = copy.receiver();
1449 if (!userCache[rcvr]) {
1450 if(rcvr == openils.User.user.id()) {
1451 userCache[rcvr] = openils.User.user;
1453 userCache[rcvr] = fieldmapper.standardRequest(
1454 ['open-ils.actor', 'open-ils.actor.user.retrieve'],
1455 {params: [openils.User.authtoken, rcvr]}
1459 dojo.query('[name=receiver]', row)[0].innerHTML = userCache[rcvr].usrname();
1462 dojo.forEach(liDetailFields,
1465 if (field == "fund") {
1466 searchFilter = (copy.fund() ?
1467 {"-or": {"active": "t", "id": copy.fund()}} :
1470 searchFilter = null;
1473 var readOnly = false;
1475 // TODO: Add support for changing the owning_lib after real copies have been made.
1476 // owning_lib is order data as much as its item data
1477 if(copy.eg_copy_id() && ['owning_lib', 'location', 'circ_modifier', 'cn_label', 'barcode'].indexOf(field) >= 0) {
1481 // TODO: add support for changing the fund after debits have been created
1482 // Note: invoicing allows the change
1483 if(copy.fund_debit() && field == 'fund') {
1488 var widget = new openils.widget.AutoFieldWidget({
1491 labelFormat : (field == 'fund') ? fundLabelFormat : null,
1492 searchFormat : (field == 'fund') ? fundSearchFormat : null,
1493 dijitArgs: {"labelType": (field == 'fund') ? "html" : null},
1494 searchFilter : searchFilter,
1495 noCache: (field == "fund"),
1497 parentNode : dojo.query('[name='+field+']', row)[0],
1498 orgLimitPerms : ['CREATE_PICKLIST', 'CREATE_PURCHASE_ORDER'],
1499 readOnly : readOnly,
1500 orgDefaultsToWs : true
1504 // make sure we capture the value from any async widgets
1507 if (field == "fund" && w.store)
1508 self._ensureCSSFundClasses(w.store);
1511 copy[field](ww.getFormattedValue())
1513 self.copyWidgetCache[copy.id()][field] = w;
1515 dojo.connect(w, 'onChange',
1517 if (field == "fund")
1518 self._updateFundSelectorStyle(widget, val);
1520 if (!readOnly && (copy.isnew() || val != copy[field]())) {
1521 // prevent setting ischanged() automatically on widget load for existing copies
1522 copy[field](widget.getFormattedValue())
1523 copy.ischanged(true);
1532 this.updateLidState(copy, row);
1535 this._ensureCSSFundClass = function(id) {
1536 if (!this.fundStyleSheet) {
1538 "style", {"type": "text/css"},
1539 document.getElementsByTagName("head")[0], "last"
1541 this.fundStyleSheet = document.styleSheets[
1542 document.styleSheets.length - 1
1546 var cn = "fund_" + id;
1547 if (!this.haveFundClass[cn]) {
1548 fieldmapper.standardRequest(
1549 ["open-ils.acq", "open-ils.acq.fund.check_balance_percentages"],
1551 "params": [openils.User.authtoken, id],
1553 "oncomplete": function(r) {
1554 r = openils.Util.readResponse(r);
1555 self.fundBalanceState[id] = r;
1557 if (r[0] /* stop */)
1558 style = fundStyles.stop;
1559 else if (r[1] /* warning */)
1560 style = fundStyles.warning;
1561 self.fundStyleSheet.insertRule(
1562 "." + cn + " { " + style + " }",
1563 self.fundStyleSheet.cssRules.length
1565 self.haveFundClass[cn] = true;
1572 this._ensureCSSFundClasses = function(store) {
1574 "query": {"id": "*"},
1575 "onItem": function(o) { self._ensureCSSFundClass(o.id[0]); }
1579 this._updateFundSelectorStyle = function(widget, fund_id) {
1580 openils.Util.removeCSSClass(widget.widget.domNode, /fund_\d+/);
1581 openils.Util.addCSSClass(widget.widget.domNode, "fund_" + fund_id);
1584 this.updateLidState = function(copy, row) {
1585 if (typeof(row) == "undefined") {
1587 'tr[copy_id="' + copy.id() + '"]', this.copyTbody
1592 var recv_link = nodeByName("receive", row);
1593 var unrecv_link = nodeByName("unreceive", row);
1594 var del_link = nodeByName("delete", row);
1595 var cxl_link = nodeByName("cancel", row);
1596 var claim_link = nodeByName("claim", row);
1597 var cxl_reason_link = nodeByName("cancel_reason", row);
1599 if (copy.cancel_reason()) {
1600 openils.Util.hide(del_link.parentNode);
1601 openils.Util.hide(recv_link);
1602 openils.Util.hide(unrecv_link);
1603 openils.Util.hide(cxl_link);
1604 openils.Util.hide(claim_link);
1606 /* XXX the following may leak memory in a long lived table: dijits may not get destroyed... not positive. revisit. */
1607 var holds_reason = dojo.create(
1609 "style": "border-bottom: 1px dashed #000;",
1610 "innerHTML": "Cancelled" /* XXX [sic] and i18n */
1611 }, cxl_reason_link, "only"
1615 "label": "<em>" + copy.cancel_reason().label() +
1616 "</em><br />" + copy.cancel_reason().description(),
1617 "connectId": [holds_reason]
1618 }, dojo.create("span", null, cxl_reason_link, "last")
1620 openils.Util.show(cxl_reason_link, "inline");
1621 } else if (this.isPO) {
1622 /* Only using this in one place so far, but may want it for better
1623 * decisions on when to display certain controls. */
1624 var li_state = this.liCache[copy.lineitem()].state();
1626 openils.Util.hide(del_link.parentNode);
1627 openils.Util.hide(cxl_reason_link);
1629 /* Avoid showing (un)receive links, cancel links, for virt copies */
1630 if (copy.id() > 0) {
1631 if (copy.recv_time()) {
1632 openils.Util.hide(cxl_link);
1633 openils.Util.hide(recv_link);
1634 openils.Util.hide(claim_link);
1636 openils.Util.show(unrecv_link, "inline");
1637 unrecv_link.onclick = function() {
1638 if (confirm(localeStrings.UNRECEIVE_LID))
1639 self.issueReceive(copy, /* rollback */ true);
1642 openils.Util.hide(unrecv_link);
1644 if (this.claimEligibleLid[copy.id()]) {
1645 openils.Util.show(claim_link, "inline");
1646 claim_link.onclick = function() {
1647 self.claimDialog.show(
1648 self.liCache[copy.lineitem()], copy.id()
1652 openils.Util.hide(claim_link);
1655 openils.Util[li_state == "on-order" ? "show" : "hide"](
1658 openils.Util.show(cxl_link, "inline");
1659 recv_link.onclick = function() {
1660 if (self.checkLiAlerts(copy.lineitem()))
1661 self.issueReceive(copy);
1663 cxl_link.onclick = function() {
1664 self.cancelLid(copy.id());
1668 openils.Util.hide(cxl_link);
1669 openils.Util.hide(unrecv_link);
1670 openils.Util.hide(recv_link);
1671 openils.Util.hide(claim_link);
1674 openils.Util.hide(unrecv_link);
1675 openils.Util.hide(recv_link);
1676 openils.Util.hide(cxl_reason_link);
1677 openils.Util.hide(claim_link);
1679 del_link.onclick = function() { self.deleteCopy(row) };
1680 openils.Util.show(del_link.parentNode);
1684 this.cancelLid = function(lid_id) {
1685 lidCancelDialog._lid_id = lid_id;
1686 openils.Util.show(lidCancelDialog.domNode.parentNode);
1687 lidCancelDialog.show();
1688 if (!lidCancelDialog._prepared) {
1689 var widget = new openils.widget.AutoFieldWidget({
1690 "fmField": "cancel_reason",
1691 "fmClass": "acqlid",
1692 "parentNode": dojo.byId("acq-lit-lid-cancel-reason"),
1693 "orgLimitPerms": ["CREATE_PURCHASE_ORDER"],
1698 acqLidCancelButton.onClick = function() {
1699 if (w.attr("value")) {
1700 if (confirm(localeStrings.LID_CANCEL_CONFIRM)) {
1702 lidCancelDialog._lid_id,
1706 lidCancelDialog.hide();
1709 lidCancelDialog._prepared = true;
1715 this._cancelLid = function(lid_id, reason) {
1716 fieldmapper.standardRequest(
1717 ["open-ils.acq", "open-ils.acq.lineitem_detail.cancel"], {
1718 "params": [openils.User.authtoken, lid_id, reason],
1720 "onresponse": function(r) {
1721 if (r = openils.Util.readResponse(r)) {
1723 for (var id in r.lid) {
1724 /* actually this should only iterate once */
1725 self.copyCache[id].cancel_reason(
1726 r.lid[id].cancel_reason
1728 self.updateLidState(self.copyCache[id]);
1737 this._confirmAlert = function(li, lin) {
1739 dojo.string.substitute(
1740 localeStrings.CONFIRM_LI_ALERT, [
1741 (new openils.acq.Lineitem({"lineitem": li})).findAttr(
1742 "title", "lineitem_marc_attr_definition"
1744 lin.alert_text().code(),
1745 lin.alert_text().description() || "",
1752 this.checkLiAlerts = function(li_id) {
1753 var li = this.liCache[li_id];
1755 var alert_notes = li.lineitem_notes().filter(
1756 function(o) { return Boolean(o.alert_text()); }
1759 /* this is _intentionally_ not done in a call to forEach() ... */
1760 for (var i = 0; i < alert_notes.length; i++) {
1761 if (this.noteAcks[alert_notes[i].id()])
1763 else if (!this._confirmAlert(li, alert_notes[i]))
1766 this.noteAcks[alert_notes[i].id()] = true;
1772 this.deleteCopy = function(row) {
1773 var copy = this.copyCache[row.getAttribute('copy_id')];
1774 copy.isdeleted(true);
1776 delete this.copyCache[copy.id()];
1777 this.copyTbody.removeChild(row);
1780 this._virtDfaCountsAsList = function() {
1782 for (var key in this.virtDfaCounts) {
1783 for (var i = 0; i < this.virtDfaCounts[key]; i++)
1789 this.confirmBreachedCopyFunds = function(copies) {
1790 var stop = 0, warning = 0;
1794 var state = self.fundBalanceState[o.fund()];
1795 if (state[0] /* stop */)
1797 else if (state[1] /* warning */)
1804 return confirm(localeStrings.CONFIRM_FUNDS_AT_STOP);
1805 } else if (warning) {
1806 return confirm(localeStrings.CONFIRM_FUNDS_AT_WARNING);
1811 this.saveCopyChanges = function(liId) {
1817 for(var id in this.copyCache) {
1818 var c = this.copyCache[id];
1819 if(!c.isdeleted()) total++;
1820 if(c.isnew() || c.ischanged() || c.isdeleted()) {
1821 if(c.id() < 0) c.id(null);
1827 dojo.byId('acq-lit-copy-count-label-' + liId).innerHTML = total;
1830 if (copies.length > 0) {
1831 if (!this.confirmBreachedCopyFunds(copies))
1834 if (typeof(this._copy_count_cb) == "function")
1835 this._copy_count_cb(liId, total);
1837 openils.Util.show("acq-lit-update-copies-progress");
1838 fieldmapper.standardRequest(
1839 ['open-ils.acq', 'open-ils.acq.lineitem_detail.cud.batch'],
1841 params: [openils.User.authtoken, copies],
1842 onresponse: function(r) {
1843 var res = openils.Util.readResponse(r);
1844 litUpdateCopiesProgress.update(res);
1846 oncomplete: function() {
1847 self.drawCopies(liId, true /* force_fetch */);
1848 openils.Util.hide("acq-lit-update-copies-progress");
1854 var dfa_list = this._virtDfaCountsAsList();
1855 if (dfa_list.length > 0) {
1856 fieldmapper.standardRequest(
1858 "open-ils.acq.distribution_formula.record_application"],
1861 "params": [openils.User.authtoken, dfa_list, liId],
1862 "onresponse": function(r) {
1863 var res = openils.Util.readResponse(r);
1864 if (res && res.length < dfa_list.length)
1865 alert(localeStrings.DFA_NOT_ALL);
1869 this.virtDfaCounts = {};
1873 this._updateCreatePoPrepayCheckbox = function(prepay) {
1874 var prepay = openils.Util.isTrue(prepay);
1875 this._prepayRequiredByVendor = prepay;
1876 dijit.byId("acq-lit-po-prepay").attr("checked", prepay);
1879 this._confirmPoPrepaySituation = function() {
1880 var want_prepay = dijit.byId("acq-lit-po-prepay").attr("checked");
1881 if (want_prepay != this._prepayRequiredByVendor) {
1884 localeStrings.VENDOR_SAYS_PREPAY_NOT_NEEDED :
1885 localeStrings.VENDOR_SAYS_PREPAY_NEEDED
1892 this.applySelectedLiAction = function(action) {
1896 case 'delete_selected':
1897 this._deleteLiList(self.getSelected());
1900 case 'create_order':
1901 this._loadPOSelect();
1902 acqLitPoCreateDialog.show();
1905 case 'save_picklist':
1906 acqLitSavePlDialog.show();
1909 case 'selector_ready':
1911 acqLitChangeLiStateDialog.attr('state', action.replace('_', '-'));
1912 acqLitChangeLiStateDialog.show();
1920 location.href = oilsBasePath + '/acq/po/history/' + this.isPO;
1927 case 'rollback_receive_po':
1928 this.rollbackPoReceive();
1931 case 'create_assets':
1932 this.createAssets();
1935 case 'export_attr_list':
1936 this.chooseExportAttr();
1939 case 'batch_apply_funds':
1940 this.applyBatchLiFunds();
1943 case 'add_brief_record':
1945 location.href = oilsBasePath + '/acq/picklist/brief_record?po=' + this.isPO;
1947 location.href = oilsBasePath + '/acq/picklist/brief_record?pl=' + this.isPL;
1951 case "cancel_lineitems":
1952 this.maybeCancelLineitems();
1955 case "change_claim_policy":
1956 var li_list = this.getSelected();
1957 this.claimPolicyPicker.attr("value", null);
1958 liClaimPolicyDialog.show();
1959 liClaimPolicySave.onClick = function() {
1960 self.changeClaimPolicy(
1962 self.claimPolicyPicker.attr("value"),
1966 self.setClaimPolicyControl(li);
1967 self.reconsiderClaimControl(li);
1970 liClaimPolicyDialog.hide();
1978 this.changeClaimPolicy = function(li_list, value, callback) {
1980 function(li) { li.claim_policy(value); }
1982 fieldmapper.standardRequest(
1983 ["open-ils.acq", "open-ils.acq.lineitem.update"], {
1984 "params": [openils.User.authtoken, li_list],
1986 "oncomplete": function(r) {
1987 r = openils.Util.readResponse(r);
1988 if (callback) callback(r);
1994 this.createAssets = function() {
1995 if(!this.isPO) return;
1996 if(!confirm(localeStrings.CREATE_PO_ASSETS_CONFIRM)) return;
1997 this.show('acq-lit-progress-numbers');
1999 fieldmapper.standardRequest(
2000 ['open-ils.acq', 'open-ils.acq.purchase_order.assets.create'],
2002 params: [this.authtoken, this.isPO],
2003 onresponse: function(r) {
2004 var resp = openils.Util.readResponse(r);
2005 self._updateProgressNumbers(resp, true);
2011 this.maybeCancelLineitems = function() {
2012 openils.Util.show("acq-lit-cancel-reason", "inline");
2013 if (!acqLitCancelLineitemsButton._prepared) {
2014 var widget = new openils.widget.AutoFieldWidget({
2015 "fmField": "cancel_reason",
2017 "parentNode": dojo.byId("acq-lit-cancel-reason-selector"),
2018 "orgLimitPerms": ["CREATE_PURCHASE_ORDER"],
2023 acqLitCancelLineitemsButton.onClick = function() {
2024 if (w.attr("value")) {
2025 if (confirm(localeStrings.LI_CANCEL_CONFIRM)) {
2026 self._cancelLineitems(w.attr("value"));
2028 openils.Util.hide("acq-lit-cancel-reason");
2031 acqLitCancelLineitemsButton._prepared = true;
2037 this._cancelLineitems = function(reason) {
2038 var id_list = this.getSelected().map(function(o) { return o.id(); });
2039 fieldmapper.standardRequest(
2040 ["open-ils.acq", "open-ils.acq.lineitem.cancel.batch"], {
2041 "params": [openils.User.authtoken, id_list, reason],
2043 "onresponse": function(r) {
2044 if (r = openils.Util.readResponse(r)) {
2046 for (var id in r.li) {
2047 self.liCache[id].state(r.li[id].state);
2048 self.liCache[id].cancel_reason(
2049 r.li[id].cancel_reason
2051 self.updateLiState(self.liCache[id]);
2054 if (r.lid && self.copyCache) {
2055 for (var id in r.lid) {
2056 if (self.copyCache[id]) {
2057 self.copyCache[id].cancel_reason(
2058 r.lid[id].cancel_reason
2060 self.updateLidState(self.copyCache[id]);
2070 this.chooseExportAttr = function() {
2071 if (!acqLitExportAttrSelector._li_setup) {
2073 acqLitExportAttrSelector.store = new dojo.data.ItemFileReadStore(
2075 "data": acqlimad.toStoreData(
2077 "acqlimad", {"code": li_exportable_attrs}
2082 acqLitExportAttrSelector.setValue();
2083 acqLitExportAttrButton.onClick = function(){self.exportAttrList();};
2084 acqLitExportAttrSelector._li_setup = true;
2086 openils.Util.show("acq-lit-export-attr-holder", "inline");
2089 this.exportAttrList = function() {
2090 var attr_def = acqLitExportAttrSelector.item;
2091 var li_list = this.getSelected();
2092 var value_list = li_list.map(
2094 return (new openils.acq.Lineitem({"lineitem": li})).findAttr(
2095 attr_def.code, "lineitem_marc_attr_definition"
2098 ).filter(function(attr) { return Boolean(attr); });
2100 if (value_list.length > 0) {
2101 if (value_list.length < li_list.length) {
2103 dojo.string.substitute(
2104 localeStrings.EXPORT_SHORT_LIST, [attr_def.description]
2111 openils.XUL.contentToFileSaveDialog(
2112 value_list.join("\n"),
2113 localeStrings.EXPORT_SAVE_DIALOG_TITLE
2119 alert(dojo.string.substitute(
2120 localeStrings.EXPORT_EMPTY_LIST, [attr_def.description]
2124 openils.Util.hide("acq-lit-export-attr-holder");
2127 this.printPO = function() {
2128 if(!this.isPO) return;
2129 progressDialog.show(true);
2130 fieldmapper.standardRequest(
2131 ['open-ils.acq', 'open-ils.acq.purchase_order.format'],
2133 params: [this.authtoken, this.isPO, 'html'],
2134 oncomplete: function(r) {
2135 progressDialog.hide();
2136 var evt = openils.Util.readResponse(r);
2137 if(evt && evt.template_output()) {
2138 openils.Util.printHtmlString(evt.template_output().data());
2146 this.receivePO = function() {
2147 if (!this.isPO) return;
2149 for (var id in this.liCache) {
2150 /* assumption: liCache reflects exactly the
2151 * set of LIs that belong to our PO */
2152 if (this.liCache[id].state() != "received" &&
2153 !this.checkLiAlerts(id)) return;
2156 this.show('acq-lit-progress-numbers');
2158 fieldmapper.standardRequest(
2159 ['open-ils.acq', 'open-ils.acq.purchase_order.receive'],
2161 params: [this.authtoken, this.isPO],
2162 onresponse : function(r) {
2163 var resp = openils.Util.readResponse(r);
2164 self._updateProgressNumbers(resp, true);
2170 this.issueReceive = function(obj, rollback) {
2171 /* (For now) there shall be no marking LI or LIDs (un)received
2172 * except from the actual "view PO" interface. */
2173 if (!this.isPO) return;
2176 {"jub": "lineitem", "acqlid": "lineitem_detail"}[obj.classname];
2178 "open-ils.acq." + part + ".receive" + (rollback ? ".rollback" : "");
2180 progressDialog.show(true);
2181 fieldmapper.standardRequest(
2182 ["open-ils.acq", method], {
2184 "params": [this.authtoken, obj.id()],
2185 "onresponse": function(r) {
2186 if (r = openils.Util.readResponse(r)) {
2187 self.fetchClaimInfo(
2188 part == "lineitem" ? obj.id() : obj.lineitem(),
2190 function() { self.handleReceive(r); }
2192 progressDialog.hide();
2200 * Handles the responses from receive and rollback ML calls.
2202 this.handleReceive = function(resp) {
2205 for (var li_id in resp.li) {
2206 for (var key in resp.li[li_id])
2207 self.liCache[li_id][key](resp.li[li_id][key]);
2208 self.updateLiState(self.liCache[li_id]);
2212 if (typeof(self.poUpdateCallback) == "function")
2213 self.poUpdateCallback(resp.po);
2216 for (var lid_id in resp.lid) {
2217 for (var key in resp.lid[lid_id])
2218 self.copyCache[lid_id][key](resp.lid[lid_id][key]);
2219 self.updateLidState(self.copyCache[lid_id]);
2225 this.rollbackPoReceive = function() {
2226 if(!this.isPO) return;
2227 if(!confirm(localeStrings.ROLLBACK_PO_RECEIVE_CONFIRM)) return;
2228 this.show('acq-lit-progress-numbers');
2230 fieldmapper.standardRequest(
2231 ['open-ils.acq', 'open-ils.acq.purchase_order.receive.rollback'],
2233 params: [this.authtoken, this.isPO],
2234 onresponse : function(r) {
2235 var resp = openils.Util.readResponse(r);
2236 self._updateProgressNumbers(resp, true);
2242 this._updateProgressNumbers = function(resp, reloadOnComplete) {
2244 dojo.byId('acq-pl-lit-li-processed').innerHTML = resp.li;
2245 dojo.byId('acq-pl-lit-lid-processed').innerHTML = resp.lid;
2246 dojo.byId('acq-pl-lit-debits-processed').innerHTML = resp.debits_accrued;
2247 dojo.byId('acq-pl-lit-bibs-processed').innerHTML = resp.bibs;
2248 dojo.byId('acq-pl-lit-indexed-processed').innerHTML = resp.indexed;
2249 dojo.byId('acq-pl-lit-copies-processed').innerHTML = resp.copies;
2250 if(resp.complete && reloadOnComplete)
2251 location.href = location.href;
2255 this._createPO = function(fields) {
2256 this.show('acq-lit-progress-numbers');
2257 var po = new fieldmapper.acqpo();
2258 po.provider(this.createPoProviderSelector.attr('value'));
2259 po.ordering_agency(this.createPoAgencySelector.attr('value'));
2260 po.prepayment_required(fields.prepayment_required[0] ? true : false);
2262 var selected = this.getSelected( (fields.create_from == 'all') );
2263 if(selected.length == 0) return;
2265 var max = selected.length * 3;
2268 fieldmapper.standardRequest(
2269 ['open-ils.acq', 'open-ils.acq.purchase_order.create'],
2272 openils.User.authtoken,
2275 lineitems : selected.map(function(li) { return li.id() }),
2276 create_assets : fields.create_assets[0],
2280 onresponse : function(r) {
2281 var resp = openils.Util.readResponse(r);
2282 self._updateProgressNumbers(resp);
2284 location.href = oilsBasePath + '/acq/po/view/' + resp.purchase_order.id();
2290 this.batchFundWidget = null;
2292 this.applyBatchLiFunds = function() {
2294 var liIds = this.getSelected().map(function(li) { return li.id(); });
2295 if(liIds.length == 0) return; // warn?
2298 batchFundUpdateDialog.show();
2300 if(!this.batchFundWidget) {
2301 this.batchFundWidget = new openils.widget.AutoFieldWidget({
2303 selfReference : true,
2304 labelFormat : fundLabelFormat,
2305 searchFormat : fundSearchFormat,
2306 searchFilter : {"active": "t"},
2307 parentNode : dojo.byId('acq-lit-batch-fund-selector'),
2308 orgLimitPerms : ['CREATE_PICKLIST', 'CREATE_PURCHASE_ORDER'],
2309 dijitArgs : { "required": true, "labelType": "html" },
2312 this.batchFundWidget.build();
2315 dojo.connect(batchFundUpdateCancel, 'onClick', function() { batchFundUpdateDialog.hide(); });
2316 dojo.connect(batchFundUpdateSubmit, 'onClick',
2319 // TODO: call .dry_run first to test thresholds
2320 fieldmapper.standardRequest(
2321 ['open-ils.acq', 'open-ils.acq.lineitem.fund.update.batch'],
2324 openils.User.authtoken,
2326 self.batchFundWidget.widget.attr('value')
2328 oncomplete : function(r) {
2329 var resp = openils.Util.readResponse(r);
2331 location.href = location.href;
2340 this._deleteLiList = function(list, idx) {
2341 if(idx == null) idx = 0;
2342 if(idx >= list.length) return;
2347 if (this.isPO && (li.state() == "on-order" || li.state() == "received")) {
2348 /* It makes little sense to delete a lineitem from a PO that has
2349 * already been marked 'on-order'. Especially if EDI is in use,
2350 * such a purchase order will probably have already been shipped
2351 * off to a vendor, and mucking with it at this point could leave
2352 * your data in a bad state that doesn't jive with reality.
2354 * I could see making this restriction even firmer.
2356 * I could also see adjusting the li state comparisons, extending
2357 * the comparison to the PO's state, and/or providing functions
2358 * that house the logic for comparing states in a single location.
2360 * Yes, this will be really annoying if you have selected a lot
2361 * of lineitems to cancel that have been ordered. You'll get a
2362 * confirm dialog for each one.
2365 if (!confirm(localeStrings.DEL_LI_FROM_PO)) {
2366 self._deleteLiList(list, ++idx); /* move on to next in list */
2371 fieldmapper.standardRequest(
2373 this.isPO ? 'open-ils.acq.purchase_order.lineitem.delete' : 'open-ils.acq.picklist.lineitem.delete'],
2375 params: [openils.User.authtoken, liId],
2376 oncomplete: function(r) {
2377 self.removeLineitem(liId);
2378 self._deleteLiList(list, ++idx);
2384 this.editOrderMarc = function(li) {
2386 /* To run in Firefox directly, must set signed.applets.codebase_principal_support
2387 to true in about:config */
2389 if(!openils.XUL.enableXPConnect()) return;
2391 if(openils.XUL.isXUL()) {
2392 win = window.open('/xul/' + openils.XUL.buildId() + '/server/cat/marcedit.xul');
2394 win = window.open('/xul/server/cat/marcedit.xul');
2398 record : {marc : li.marc(), "rtype": "bre"},
2400 label: 'Save Record', // XXX I18N
2401 func: function(xmlString) {
2403 fieldmapper.standardRequest(
2404 ['open-ils.acq', 'open-ils.acq.lineitem.update'],
2406 params: [openils.User.authtoken, li],
2407 oncomplete: function(r) {
2408 openils.Util.readResponse(r);
2410 self.drawInfo(li.id())
2416 'lock_tab' : typeof xulG != 'undefined' ? (typeof xulG['lock_tab'] != 'undefined' ? xulG.lock_tab : undefined) : undefined,
2417 'unlock_tab' : typeof xulG != 'undefined' ? (typeof xulG['unlock_tab'] != 'undefined' ? xulG.unlock_tab : undefined) : undefined
2421 this._savePl = function(values) {
2423 var selected = this.getSelected( (values.which == 'all') );
2424 openils.Util.show('acq-lit-generic-progress');
2426 if(values.new_name) {
2427 openils.acq.Picklist.create(
2428 {name: values.new_name},
2430 self._updateLiList(id, selected, 0,
2432 location.href = oilsBasePath + '/acq/picklist/view/' + id;
2436 } else if(values.existing_pl) {
2437 // update lineitems to use an existing picklist
2438 self._updateLiList(values.existing_pl, selected, 0,
2440 location.href = oilsBasePath + '/acq/picklist/view/' + values.existing_pl;
2445 this._updateLiState = function(values, state) {
2447 var selected = this.getSelected( (values.which == 'all') );
2448 if(!selected.length) return;
2449 dojo.forEach(selected, function(li) {li.state(state);});
2450 self._updateLiList(null, selected, 0,
2451 // TODO consider inline updates for efficiency
2452 function() { location.href = location.href }
2456 this._updateLiList = function(pl, list, idx, oncomplete) {
2457 if(idx >= list.length) return oncomplete();
2459 if(pl != null) li.picklist(pl);
2460 litGenericProgress.update({maximum: list.length, progress: idx});
2461 new openils.acq.Lineitem({lineitem:li}).update(
2463 self._updateLiList(pl, list, ++idx, oncomplete);
2468 this._loadPOSelect = function() {
2469 if (!this.createPoProviderSelector) {
2470 var widget = new openils.widget.AutoFieldWidget({
2471 "fmField": "provider",
2473 "searchFilter": {"active": "t"},
2474 "parentNode": dojo.byId("acq-lit-po-provider"),
2476 "onChange": function() {
2478 self._updateCreatePoPrepayCheckbox(
2479 this.item.prepayment_required()
2485 widget.build(function(w) { self.createPoProviderSelector = w; });
2488 if (!this.createPoAgencySelector) {
2489 var widget = new openils.widget.AutoFieldWidget({
2490 "fmField": "ordering_agency",
2492 "parentNode": dojo.byId("acq-lit-po-agency"),
2493 "orgLimitPerms": ["CREATE_PURCHASE_ORDER"],
2495 widget.build(function(w) { self.createPoAgencySelector = w; });
2499 this.showRealCopyEditUI = function(li) {
2504 this._fetchLineitem(li.id(),
2506 li = self.liCache[li.id()] = fullLi;
2510 id : li.lineitem_details().map(
2511 function(item) { return item.eg_copy_id() }
2515 oncomplete : function(r) {
2517 var r_list = openils.Util.readResponse( r );
2518 for (var i = 0; i < r_list.length; i++) {
2519 var copy = r_list[i];
2520 var volId = copy.call_number();
2521 var volume = self.volCache[volId];
2523 volume = self.volCache[volId] = self.pcrud.retrieve('acn', volId);
2525 copy.call_number(volume);
2526 copyList.push(copy);
2529 xulG.volume_item_creator( { 'existing_copies' : copyList } );
2532 alert('error in oncomplete: ' + E);
2541 this.drawBibFinder = function(li) {
2544 var liWrapper = new openils.acq.Lineitem({lineitem:li});
2547 ['isbn', 'upc', 'issn', 'title', 'author'],
2549 var val = liWrapper.findAttr(field, 'lineitem_marc_attr_definition');
2551 if(field == 'title' || field == 'author') {
2552 query += field +':' + val + ' ';
2554 query += 'identifier|' + field + ':' + val + ' ';
2561 oilsBasePath + '/acq/lineitem/findbib?query=' + escape(query),
2562 '', 'resizable,scrollbars=1');
2564 win.window.recordFound = function(bibId) {
2567 var attrs = li.attributes();
2568 li.attributes(null);
2569 li.eg_bib_id(bibId);
2571 fieldmapper.standardRequest(
2572 ["open-ils.acq", "open-ils.acq.lineitem.update"],
2574 "params": [openils.User.authtoken, li],
2576 "oncomplete": function(r) {
2577 if(openils.Util.readResponse(r)) {
2578 location.href = location.href;