1 dojo.require('dojo.date.locale');
2 dojo.require('dojo.date.stamp');
3 dojo.require('dojo.cookie');
4 dojo.require('dijit.form.CheckBox');
5 dojo.require('dijit.form.Button');
6 dojo.require('dijit.form.CurrencyTextBox');
7 dojo.require('dijit.form.NumberTextBox');
8 dojo.require('openils.User');
9 dojo.require('openils.Util');
10 dojo.require('openils.CGI');
11 dojo.require('openils.PermaCrud');
12 dojo.require('openils.widget.EditPane');
13 dojo.require('openils.widget.AutoFieldWidget');
14 dojo.require('openils.widget.ProgressDialog');
15 dojo.require('openils.acq.Lineitem');
16 dojo.require('openils.XUL');
18 dojo.requireLocalization('openils.acq', 'acq');
19 var localeStrings = dojo.i18n.getLocalization('openils.acq', 'acq');
21 var fundLabelFormat = ['${0} (${1})', 'code', 'year'];
22 var fundSearchFormat = ['${0} (${1})', 'code', 'year'];
23 var fundSearchFilter = {active : 't'};
25 var cgi = new openils.CGI();
26 var pcrud = new openils.PermaCrud();
41 var widgetRegistry = {acqie : {}, acqii : {}};
43 var searchInitDone = false;
48 function nodeByName(name, context) {
49 return dojo.query('[name='+name+']', context)[0];
53 // before rendering any fund selectors, limit the funds to
54 // attempt to retrieve to those the user can actually use.
55 new openils.User().getPermOrgList(
56 ['ADMIN_INVOICE','CREATE_INVOICE','MANAGE_FUND'],
58 fundSearchFilter.org = orgs;
61 true, true // descendants, id_list
67 attachLi = cgi.param('attach_li') || [];
68 if (!dojo.isArray(attachLi))
69 attachLi = [attachLi];
71 attachPo = cgi.param('attach_po') || [];
72 if (!dojo.isArray(attachPo))
73 attachPo = [attachPo];
75 focusLineitem = new openils.CGI().param('focus_li');
77 totalInvoicedBox = dojo.byId('acq-total-invoiced-box');
78 totalPaidBox = dojo.byId('acq-total-paid-box');
79 balanceOwedBox = dojo.byId('acq-total-balance-box');
81 itemTypes = pcrud.retrieveAll('aiit');
83 dojo.byId('acq-invoice-summary-toggle-off').onclick = function() {
84 openils.Util.hide(dojo.byId('acq-invoice-summary'));
85 openils.Util.show(dojo.byId('acq-invoice-summary-small'));
88 dojo.byId('acq-invoice-summary-toggle-on').onclick = function() {
89 openils.Util.show(dojo.byId('acq-invoice-summary'));
90 openils.Util.hide(dojo.byId('acq-invoice-summary-small'));
93 if(cgi.param('create')) {
96 // show summary info by default for new invoices
97 dojo.byId('acq-invoice-summary-toggle-on').onclick();
100 dojo.byId('acq-invoice-summary-toggle-off').onclick();
101 fieldmapper.standardRequest(
102 ['open-ils.acq', 'open-ils.acq.invoice.retrieve.authoritative'],
104 params : [openils.User.authtoken, invoiceId],
105 oncomplete : function(r) {
106 invoice = openils.Util.readResponse(r);
113 extraCopiesFund = new openils.widget.AutoFieldWidget({
116 labelFormat : fundLabelFormat,
117 searchFormat : fundSearchFormat,
118 searchFilter : fundSearchFilter,
119 dijitArgs : {required : true},
120 parentNode : dojo.byId('acq-invoice-extra-copies-fund')
122 extraCopiesFund.build();
125 function renderInvoice() {
127 // in create mode, let the LI or PO render the invoice with seed data
128 if( !(cgi.param('create') && (attachPo.length || attachLi.length)) ) {
129 invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), invoice);
132 dojo.byId('acq-invoice-new-item').onclick = function() {
133 var item = new fieldmapper.acqii();
134 item.id(virtualId--);
136 addInvoiceItem(item);
141 if(invoice && openils.Util.isTrue(invoice.complete())) {
143 dojo.forEach( // hide widgets that should not be visible for a completed invoice
144 dojo.query('.hide-complete'),
145 function(node) { openils.Util.hide(node); }
148 new openils.User().getPermOrgList(
149 'ACQ_INVOICE_REOPEN',
151 if(orgs.indexOf(invoice.receiver()) >= 0)
152 openils.Util.show('acq-invoice-reopen-button-wrapper', 'inline');
159 // display items and entries in ID order
160 // which effectively equates to add order.
161 function idsort(a, b) { return a.id() < b.id() ? -1 : 1 }
165 invoice.items().sort(idsort),
167 addInvoiceItem(item);
172 invoice.entries().sort(idsort),
174 addInvoiceEntry(entry);
179 if(attachLi.length) doAttachLi();
180 if(attachPo.length) doAttachPo(0);
183 function doAttachLi(skipInit) {
185 //var invoiceArgs = {provider : lineitem.provider(), shipper : lineitem.provider()};
186 if(cgi.param('create') && !skipInit) {
188 // use the first LI in the list to determine the default provider
189 fieldmapper.standardRequest(
190 ['open-ils.acq', 'open-ils.acq.lineitem.retrieve.authoritative'],
192 params : [openils.User.authtoken, attachLi[0], {clear_marc:1}],
193 oncomplete : function(r) {
194 var li = openils.Util.readResponse(r);
195 invoicePane = drawInvoicePane(
196 dojo.byId('acq-view-invoice-div'), null,
197 {provider : li.provider(), shipper : li.provider()}
204 dojo.forEach(attachLi,
206 var entry = new fieldmapper.acqie();
207 entry.id(virtualId--);
210 addInvoiceEntry(entry);
215 function doAttachPo(idx) {
217 if (idx == attachPo.length) return;
218 var poId = attachPo[idx];
220 fieldmapper.standardRequest(
221 ['open-ils.acq', 'open-ils.acq.purchase_order.retrieve'],
224 openils.User.authtoken, poId,
225 {flesh_lineitem_ids : true, flesh_po_items : true}
227 oncomplete: function(r) {
228 var po = openils.Util.readResponse(r);
230 if(cgi.param('create') && idx == 0) {
231 // render the invoice using some seed data from the first PO
232 var invoiceArgs = {provider : po.provider(), shipper : po.provider()};
233 invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), null, invoiceArgs);
236 dojo.forEach(po.lineitems(),
238 var entry = new fieldmapper.acqie();
239 entry.id(virtualId--);
241 entry.lineitem(lineitem);
242 entry.purchase_order(po);
243 addInvoiceEntry(entry);
247 dojo.forEach(po.po_items(),
249 var item = new fieldmapper.acqii();
250 item.id(virtualId--);
252 item.fund(poItem.fund());
253 item.title(poItem.title());
254 item.author(poItem.author());
255 item.note(poItem.note());
256 item.inv_item_type(poItem.inv_item_type());
257 item.purchase_order(po);
258 item.po_item(poItem);
259 addInvoiceItem(item);
270 var cookieUriSSL, cookieSvc, cookieMgr;
272 function performSearch(pageDir, clearFirst) {
274 clearSearchResTable();
276 var searchObject = termManager.buildSearchObject();
278 if (openils.XUL.isXUL()) {
280 cookieSvc.setCookieString(cookieUriSSL, null,
281 "invs=" + base64Encode(searchObject) + ';max-age=2592000', null);
283 cookieSvc.setCookieString(cookieUriSSL, null,
284 "invc=" + dojo.byId("acq-unified-conjunction").getValue() +
285 ';max-age=2592000', null);
289 dojo.cookie('invs', base64Encode(searchObject));
290 dojo.cookie('invc', dojo.byId("acq-unified-conjunction").getValue());
293 if (pageDir == 0) { // new search
294 resultsLoader.displayOffset = 0;
296 resultsLoader.displayOffset += pageDir * resultsLoader.displayLimit;
299 if (resultsLoader.displayOffset == 0) {
300 openils.Util.hide('acq-inv-search-prev');
302 openils.Util.show('acq-inv-search-prev', 'inline');
305 if (dojo.byId('acq-invoice-search-limit-invoiceable').checked) {
306 if (!searchObject.jub)
307 searchObject.jub = [];
309 // exclude lineitems that are "cancelled" (sidebar: 'Mericans spell it 'canceled')
310 searchObject.jub.push({state : 'cancelled', '__not' : true});
312 // exclude lineitems already linked to this invoice
313 if (invoice && invoice.id() > 0) {
314 if (!searchObject.acqinv)
315 searchObject.acqinv = [];
316 searchObject.acqinv.push({id : invoice.id(), '__not' : true});
319 // limit to lineitems that have invoiceable copies
320 searchObject.acqlisumi = [{item_count : 1, '_gte' : true}];
322 // limit to provider if a provider is selected
323 var provider = invoicePane.getFieldValue('provider');
325 if (!searchObject.jub.filter(function(i) { return i.provider != null }).length)
326 searchObject.jub.push({provider : provider});
330 if (dojo.byId('acq-invoice-search-sort-title').checked) {
331 uriManager.order_by =
332 [ {"class": "acqlia", "field":"attr_value", "transform":"first"} ];
335 resultsLoader.lastSearch = searchObject;
336 resultManager.go(searchObject)
337 console.log('Lineitem Search: ' + js2JSON(searchObject));
338 focusLastSearchInput();
342 function renderUnifiedSearch() {
344 if (!searchInitDone) {
346 searchInitDone = true;
347 termManager = new TermManager();
348 resultManager = new ResultManager();
349 resultsLoader = new searchResultsLoader();
350 uriManager = new URIManager();
352 // define custom lineitem result handler
353 resultManager.result_types = {
355 "search_options": { "id_list": true },
356 "revealer": function() { },
357 "finisher": function() {
358 resultsLoader.batch_length = resultManager.count_results;
360 "adder": function(li) {
361 resultsLoader.addLineitem(li);
363 "interface": resultsLoader
366 "revealer": function() { }
371 resultManager.no_results_popup = true;
372 resultManager.submitter = smartSearchSubmitter;
374 var searchObject, searchConjunction;
376 if (openils.XUL.isXUL()) {
380 var ios = Components.classes["@mozilla.org/network/io-service;1"]
381 .getService(Components.interfaces.nsIIOService);
383 cookieUriSSL = ios.newURI("https://" + location.hostname, null, null);
385 cookieSvc = Components.classes["@mozilla.org/cookieService;1"]
386 .getService(Components.interfaces.nsICookieService);
389 cookieManager = Components.classes["@mozilla.org/cookiemanager;1"]
390 .getService(Components.interfaces.nsICookieManager);
393 var iter = cookieManager.enumerator;
394 while (iter.hasMoreElements()) {
395 var cookie = iter.getNext();
396 if (cookie instanceof Components.interfaces.nsICookie) {
397 if (cookie.name == 'invs')
398 searchObject = cookie.value;
399 if (cookie.name == 'invc')
400 searchConjunction = cookie.value;
405 // useful for web-based testing
406 searchObject = dojo.cookie('invs');
407 searchConjunction = dojo.cookie('invc');
412 // if there is a search object cookie, populate the search form
413 termManager.reflect(base64Decode(searchObject));
414 dojo.byId("acq-unified-conjunction").setValue(searchConjunction);
417 console.log('adding row');
418 termManager.addRow();
422 dojo.addClass(dojo.byId('oils-acq-invoice-table'), 'hidden');
423 dojo.removeClass(dojo.byId('oils-acq-invoice-search'), 'hidden');
424 focusLastSearchInput();
427 function focusLastSearchInput() {
428 // TODO: see about making this better and moving it into search/unified.js
429 var wnodes = dojo.query('[name=widget]');
430 var inputNode = wnodes.item(wnodes.length - 1).firstChild;
440 var resultsTbody, resultsRow;
441 function searchResultsLoader() {
442 this.displayOffset = 0;
443 this.displayLimit = 10;
446 resultsTbody = dojo.byId('acq-invoice-search-results-tbody');
447 resultsRow = resultsTbody.removeChild(dojo.byId('acq-invoice-search-results-tr'));
450 this.addLineitem = function(li_id) {
451 console.log('Adding search result lineitem ' + li_id);
452 var row = resultsRow.cloneNode(true);
453 resultsTbody.appendChild(row);
454 var checkbox = dojo.query('[name=search-results-checkbox]', row)[0];
455 checkbox.setAttribute('lineitem', li_id);
457 // this lineitem is already part of the invoice
458 if (dojo.query('[entry_lineitem_row=' + li_id + ']')[0]) {
459 checkbox.disabled = true;
460 dojo.addClass(checkbox.parentNode, 'search-results-already-invoiced');
463 openils.acq.Lineitem.fetchAndRender(
466 dojo.query('[name=search-results-content-div]', row)[0].innerHTML = html;
472 function addSelectedToInvoice() {
473 var inputs = dojo.query('[name=search-results-checkbox]');
477 if (checkbox.checked) {
478 attachLi.push(checkbox.getAttribute('lineitem'));
479 checkbox.disabled = true;
480 checkbox.checked = false;
481 dojo.addClass(checkbox.parentNode, 'search-results-already-invoiced');
488 allResSelected = false;
489 function clearSearchResTable() {
490 allResSelected = false;
491 while (resultsTbody.childNodes[0])
492 resultsTbody.removeChild(resultsTbody.childNodes[0]);
495 function selectSearchResults() {
496 allResSelected = !allResSelected;
497 dojo.query('[name=search-results-checkbox]').forEach(
498 function(input) { input.checked = allResSelected });
501 function updateTotalCost() {
504 for(var id in widgetRegistry.acqii)
505 if(!widgetRegistry.acqii[id]._object.isdeleted())
506 totalCost += Number(widgetRegistry.acqii[id].cost_billed.getFormattedValue());
507 for(var id in widgetRegistry.acqie)
508 if(!widgetRegistry.acqie[id]._object.isdeleted())
509 totalCost += Number(widgetRegistry.acqie[id].cost_billed.getFormattedValue());
510 totalInvoicedBox.innerHTML = totalCost.toFixed(2);
513 for(var id in widgetRegistry.acqii)
514 if(!widgetRegistry.acqii[id]._object.isdeleted())
515 totalPaid += Number(widgetRegistry.acqii[id].amount_paid.getFormattedValue());
516 for(var id in widgetRegistry.acqie)
517 if(!widgetRegistry.acqie[id]._object.isdeleted())
518 totalPaid += Number(widgetRegistry.acqie[id].amount_paid.getFormattedValue());
519 totalPaidBox.innerHTML = totalPaid.toFixed(2);
521 var buttonsDisabled = false;
523 if(totalPaid > totalCost || totalPaid < 0) {
524 openils.Util.addCSSClass(totalPaidBox, 'acq-invoice-invalid-amount');
525 invoiceSaveButton.attr('disabled', true);
526 invoiceProrateButton.attr('disabled', true);
527 buttonsDisabled = true;
529 openils.Util.removeCSSClass(totalPaidBox, 'acq-invoice-invalid-amount');
530 invoiceSaveButton.attr('disabled', false);
531 invoiceProrateButton.attr('disabled', false);
535 openils.Util.addCSSClass(totalInvoicedBox, 'acq-invoice-invalid-amount');
536 invoiceSaveButton.attr('disabled', true);
537 invoiceProrateButton.attr('disabled', true);
539 openils.Util.removeCSSClass(totalInvoicedBox, 'acq-invoice-invalid-amount');
540 if(!buttonsDisabled) {
541 invoiceSaveButton.attr('disabled', false);
542 invoiceProrateButton.attr('disabled', false);
546 if(totalPaid == totalCost) { // XXX: too rigid?
547 invoiceCloseButton.attr('disabled', false);
549 invoiceCloseButton.attr('disabled', true);
552 balanceOwedBox.innerHTML = (totalCost - totalPaid).toFixed(2);
554 updateExpectedCost();
558 function registerWidget(obj, field, widget, callback) {
559 var blob = widgetRegistry[obj.classname];
561 blob[obj.id()] = {_object : obj};
562 blob[obj.id()][field] = widget;
565 dojo.connect(w, 'onChange',
571 if(callback) callback(w, ww);
577 var finalInvTbody, finalInvRow;
578 var finalInvPoSeen = {};
579 function addMarkFinalPO(item, po_item, po_label) {
581 if (finalInvPoSeen[po_item.purchase_order()]) return;
582 finalInvPoSeen[po_item.purchase_order()] = true;
584 openils.Util.show(dojo.byId('oils-acq-final-invoice-pane'));
586 if (!finalInvTbody) {
587 finalInvTbody = dojo.byId('acq-final-invoice-tbody');
588 finalInvRow = finalInvTbody.removeChild(
589 dojo.byId('acq-final-invoice-row'));
592 var row = finalInvRow.cloneNode(true);
593 nodeByName('po-label', row).innerHTML = po_label;
594 var cbox = new dijit.form.CheckBox({}, nodeByName('checkbox', row));
596 dojo.connect(cbox, 'onChange', function(set) {
597 if (set) { // add to finalize list
598 finalizePos.push(Number(po_item.purchase_order()));
599 } else { // remove from finalize list
600 finalizePos = finalizePos.filter(
601 function(id) {return id != po_item.purchase_order()});
605 finalInvTbody.appendChild(row);
608 function addInvoiceItem(item) {
609 itemTbody = dojo.byId('acq-invoice-item-tbody');
610 if(itemTemplate == null) {
611 itemTemplate = itemTbody.removeChild(dojo.byId('acq-invoice-item-template'));
614 var row = itemTemplate.cloneNode(true);
615 var itemType = itemTypes.filter(function(t) { return (t.code() == item.inv_item_type()) })[0];
618 ['title', 'author', 'cost_billed', 'amount_paid'],
622 if(field == 'title' || field == 'author') {
623 //args = {style : 'width:10em'};
624 } else if(field == 'cost_billed' || field == 'amount_paid') {
625 args = {required : true, style : 'width: 8em'};
631 new openils.widget.AutoFieldWidget({
635 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
637 parentNode : nodeByName(field, row)
640 if (field == "cost_billed") {
642 w, "onChange", function(value) {
643 var paid = widgetRegistry.acqii[item.id()].amount_paid.widget;
644 if (value && isNaN(paid.attr("value")))
645 paid.attr("value", value);
655 /* ----------- fund -------------- */
660 labelFormat : fundLabelFormat,
661 searchFormat : fundSearchFormat,
662 searchFilter : fundSearchFilter,
663 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
664 dijitArgs : {required : true},
665 parentNode : nodeByName('fund', row)
668 if(item.fund_debit()) {
669 fundArgs.searchFilter = {'-or' : [{ "-and": fundSearchFilter }, {id : item.fund()}]};
671 if(itemType && openils.Util.isTrue(itemType.prorate()))
672 fundArgs.dijitArgs = {disabled : true};
675 var fundWidget = new openils.widget.AutoFieldWidget(fundArgs);
676 registerWidget(item, 'fund', fundWidget);
678 /* ---------- inv_item_type ------------- */
682 // read-only item view for items that were the result of a po-item
683 var po = item.purchase_order();
684 var po_item = item.po_item();
685 var node = nodeByName('inv_item_type', row);
686 var itemType = itemTypes.filter(function(t) { return (t.code() == item.inv_item_type()) })[0];
687 orderDate = (!po.order_date()) ? '' :
688 dojo.date.locale.format(dojo.date.stamp.fromISOString(po.order_date()), {selector:'date'});
690 node.innerHTML = dojo.string.substitute(
691 localeStrings.INVOICE_ITEM_PO_DETAILS,
698 po_item.estimated_cost()
702 if (openils.Util.isTrue(itemType.blanket())
703 && po.state() != 'received') {
705 fieldmapper.standardRequest(
707 'open-ils.acq.purchase_order.retrieve.authoritative'],
709 params: [openils.User.authtoken, po.id(), {
710 "flesh_price_summary": true
712 oncomplete: function(r) {
713 // update the global PO instead of replacing it, since other
714 // code outside our control may be referencing it.
715 var po2 = openils.Util.readResponse(r);
717 var po_label = dojo.string.substitute(
718 localeStrings.INVOICE_ITEM_PO_LABEL,
719 [ oilsBasePath, po2.id(), po2.name(),
720 orderDate, po2.amount_estimated().toFixed(2)
724 addMarkFinalPO(item, po_item, po_label);
735 new openils.widget.AutoFieldWidget({
737 fmField : 'inv_item_type',
738 parentNode : nodeByName('inv_item_type', row),
739 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
740 dijitArgs : {required : true}
743 // When the inv_item_type is set to prorate=true, don't allow the user the edit the fund
744 // since this charge will be prorated against (potentially) multiple funds
745 dojo.connect(w, 'onChange',
747 if(!item.fund_debit()) {
748 var itemType = itemTypes.filter(function(t) { return (t.code() == w.attr('value')) })[0];
749 if(!itemType) return;
750 if(openils.Util.isTrue(itemType.prorate())) {
751 fundWidget.widget.attr('disabled', true);
752 fundWidget.widget.attr('value', '');
754 fundWidget.widget.attr('disabled', false);
763 nodeByName('delete', row).onclick = function() {
764 var cost = widgetRegistry.acqii[item.id()].cost_billed.getFormattedValue();
767 if (widgetRegistry.acqii[item.id()].inv_item_type) {
768 iTypeName = widgetRegistry.acqii[item.id()]
769 .inv_item_type.getFormattedValue()
771 // if the invoice_item came from a po_item, the type is
772 // read-only, hence no widget in the registry. Look up
773 // the name in the cached types list.
774 var itype = itemTypes.filter(
775 function(t) { return (t.code() == item.inv_item_type()) })[0];
776 iTypeName = itype.name();
779 var msg = dojo.string.substitute(
780 localeStrings.INVOICE_CONFIRM_ITEM_DELETE,
781 [cost || 0, iTypeName || '']
783 if(!confirm(msg)) return;
784 itemTbody.removeChild(row);
785 item.isdeleted(true);
787 delete widgetRegistry.acqii[item.id()];
791 itemTbody.appendChild(row);
795 function updateReceiveLink(li) {
797 return; /* can't do this with unsaved invoices */
799 var link = dojo.byId("acq-view-invoice-receive-link");
800 if (link.onclick) return; /* only need to do this once */
802 /* don't do this if there's nothing receivable on the lineitem */
803 if (li.order_summary().recv_count() + li.order_summary().cancel_count() >=
804 li.order_summary().item_count())
807 openils.Util.show("acq-view-invoice-receive");
808 link.onclick = function() { location.href = oilsBasePath + '/acq/invoice/receive/' + invoiceId; };
812 * Ensures focusLineitem is in view and causes a brief
813 * border around the lineitem to come to life then fade.
816 if (!focusLineitem) return;
818 // set during addLineitem()
819 var node = dojo.byId('li-title-ref-' + focusLineitem);
821 console.log('focus: li-title-ref-' + focusLineitem + ' : ' + node);
823 // LI may not yet be rendered
826 console.log('focusing ' + focusLineitem);
828 // prevent numerous re-focuses
829 focusLineitem = null;
831 // causes the full row to be visible
832 dijit.scrollIntoView(node);
834 dojo.require('dojox.fx');
838 dojox.fx.highlight({color : '#BB4433', node : node, duration : 2000}).play();
844 // expected cost is totalCostInvoiced + totalCostNotYetInvoiced
845 function updateExpectedCost() {
847 var cost = Number(totalInvoicedBox.innerHTML || 0);
849 // for any LI's that are not yet billed (i.e. filled in)
850 // use the total expected cost for that lineitem.
851 for(var id in widgetRegistry.acqie) {
852 var entry = widgetRegistry.acqie[id]._object;
853 if(!entry.isdeleted()) {
854 if (Number(widgetRegistry.acqie[id].cost_billed.getFormattedValue()) == 0) {
855 var li = entry.lineitem();
857 Number(li.order_summary().estimated_amount()) -
858 Number(li.order_summary().paid_amount());
863 dojo.byId('acq-invoice-summary-cost').innerHTML = cost.toFixed(2);
866 var invoicEntryWidgets = {};
867 function addInvoiceEntry(entry) {
868 console.log('Adding new entry for lineitem ' + entry.lineitem());
870 openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-header'), 'hidden');
871 openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-thead'), 'hidden');
872 openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-tbody'), 'hidden');
874 dojo.byId('acq-invoice-summary-count').innerHTML =
875 Number(dojo.byId('acq-invoice-summary-count').innerHTML) + 1;
877 entryTbody = dojo.byId('acq-invoice-entry-tbody');
878 if(entryTemplate == null) {
879 entryTemplate = entryTbody.removeChild(dojo.byId('acq-invoice-entry-template'));
882 if(dojo.query('[lineitem=' + entry.lineitem() +']', entryTbody)[0])
883 // Is it ever valid to have multiple entries for 1 lineitem in a single invoice?
886 var row = entryTemplate.cloneNode(true);
887 row.setAttribute('lineitem', entry.lineitem());
888 row.setAttribute('entry_lineitem_row', entry.lineitem());
890 openils.acq.Lineitem.fetchAndRender(
891 entry.lineitem(), {},
894 entry.purchase_order(li.purchase_order());
895 nodeByName('title_details', row).innerHTML = html;
897 nodeByName('title_details', row).parentNode.id = 'li-title-ref-' + li.id();
898 console.log(dojo.byId('li-title-ref-' + li.id()));
900 updateReceiveLink(li);
902 // set some default values if otherwise unset
903 if (!invoicePane.getFieldValue('receiver')) {
904 invoicePane.setFieldValue('receiver', li.purchase_order().ordering_agency());
906 if (!invoicePane.getFieldValue('provider')) {
907 invoicePane.setFieldValue('provider', li.purchase_order().provider());
911 ['inv_item_count', 'phys_item_count', 'cost_billed', 'amount_paid'],
913 var dijitArgs = {required : true, constraints : {min: 0}, style : 'width:6em'};
914 if(field.match(/count/)) {
915 dijitArgs.style = 'width:4em;';
917 dijitArgs.style = 'width:9em;';
919 if (entry.isnew() && (field == 'phys_item_count' || field == 'inv_item_count')) {
920 // by default, attempt to pay for all non-canceled and as-of-yet-un-invoiced items
921 var count = Number(li.order_summary().item_count() || 0) -
922 Number(li.order_summary().cancel_count() || 0) -
923 Number(li.order_summary().invoice_count() || 0);
924 if(count < 0) count = 0;
925 dijitArgs.value = count;
930 new openils.widget.AutoFieldWidget({
934 dijitArgs : dijitArgs,
935 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
936 parentNode : nodeByName(field, row)
940 if(field == 'phys_item_count') {
941 dojo.connect(w, 'onChange',
943 // staff entered a higher number in the receive field than was originally ordered
944 // taking into account already invoiced items
945 var extra = Number(this.attr('value')) -
946 (Number(entry.lineitem().item_count()) - Number(entry.lineitem().order_summary().invoice_count()));
948 storeExtraCopies(entry, extra);
954 if (field == "cost_billed") {
955 // hooks applied with dojo.connect to dijit events are additive, so there's no conflict between this and what comes next
957 w, "onChange", function(value) {
958 var paid = widgetRegistry.acqie[entry.id()].amount_paid.widget;
959 if (value && isNaN(paid.attr("value")))
960 paid.attr("value", value);
964 if(field == 'inv_item_count' || field == 'cost_billed') {
965 setPerCopyPrice(row, entry);
966 // update the per-copy count as invoice count and cost billed change
967 dojo.connect(w, 'onChange', function() { setPerCopyPrice(row, entry) } );
976 if (focusLineitem == li.id())
981 nodeByName('detach', row).onclick = function() {
982 var cost = widgetRegistry.acqie[entry.id()].cost_billed.getFormattedValue();
984 dojo.forEach(['isbn', 'upc', 'issn'],
986 var val = liMarcAttr(entry.lineitem(), ident);
987 if(val) idents.push(val);
991 var msg = dojo.string.substitute(
992 localeStrings.INVOICE_CONFIRM_ENTRY_DETACH, [
994 liMarcAttr(entry.lineitem(), 'title'),
995 liMarcAttr(entry.lineitem(), 'author'),
999 if(!confirm(msg)) return;
1000 entryTbody.removeChild(row);
1001 entry.isdeleted(true);
1003 delete widgetRegistry.acqie[entry.id()];
1007 entryTbody.appendChild(row);
1010 function setPerCopyPrice(row, entry) {
1011 var inv_w = widgetRegistry.acqie[entry.id()].inv_item_count;
1012 var bill_w = widgetRegistry.acqie[entry.id()].cost_billed;
1014 if (inv_w && bill_w) {
1015 var invoiced = Number(inv_w.getFormattedValue());
1016 var billed = Number(bill_w.getFormattedValue());
1017 console.log(invoiced + ' : ' + billed);
1019 nodeByName('amount_paid_per_copy', row).innerHTML = (billed / invoiced).toFixed(2);
1021 nodeByName('amount_paid_per_copy', row).innerHTML = '0.00';
1026 function liMarcAttr(lineitem, name) {
1027 var attr = lineitem.attributes().filter(
1030 attr.attr_type() == 'lineitem_marc_attr_definition' &&
1031 attr.attr_name() == name)
1035 return (attr) ? attr.attr_value() : '';
1038 function saveChanges(args) {
1040 createExtraCopies(function() { saveChangesPartTwo(args); });
1043 // Define a helper function to 'unflesh' sub-objects from an fmclass object.
1044 // 'this' specifies the object; the arguments specify a list of names of
1046 function unflesh() {
1048 dojo.forEach(arguments, function (n) {
1050 if (_ !== null && typeof _ === 'object')
1055 function saveChangesPartTwo(args) {
1059 invoice.complete('f');
1063 // Prepare an invoice for submission
1065 invoice = new fieldmapper.acqinv();
1066 invoice.isnew(true);
1068 invoice.ischanged(true); // for now, just always update
1071 var e = invoicePane.mapValues(function (n, v) { invoice[n](v); });
1072 if (e instanceof Error) {
1078 invoice.complete('t');
1081 // Prepare any charge items
1082 var updateItems = [];
1083 for(var id in widgetRegistry.acqii) {
1084 var reg = widgetRegistry.acqii[id];
1085 var item = reg._object;
1086 if(item.ischanged() || item.isnew() || item.isdeleted()) {
1087 updateItems.push(item);
1088 if(item.isnew()) item.id(null);
1089 for(var field in reg) {
1090 if(field != '_object')
1091 item[field]( reg[field].getFormattedValue() );
1094 unflesh.call(item, 'purchase_order');
1099 // Prepare any line items
1100 var updateEntries = [];
1101 for(var id in widgetRegistry.acqie) {
1102 var reg = widgetRegistry.acqie[id];
1103 var entry = reg._object;
1104 if(entry.ischanged() || entry.isnew() || entry.isdeleted()) {
1105 updateEntries.push(entry);
1106 if(entry.isnew()) entry.id(null);
1108 for(var field in reg) {
1109 if(field != '_object')
1110 entry[field]( reg[field].getFormattedValue() );
1113 unflesh.call(entry, 'purchase_order', 'lineitem');
1118 progressDialog.show(true);
1119 fieldmapper.standardRequest(
1120 ['open-ils.acq', 'open-ils.acq.invoice.update'],
1122 params : [openils.User.authtoken,
1123 invoice, updateEntries, updateItems, finalizePos],
1124 oncomplete : function(r) {
1125 progressDialog.hide();
1126 var invoice = openils.Util.readResponse(r);
1129 return prorateInvoice(invoice);
1131 location.href = oilsBasePath + '/acq/invoice/view?create=1';
1133 location.href = oilsBasePath + '/acq/invoice/view/' + invoice.id();
1141 function prorateInvoice(invoice) {
1142 if(!confirm(localeStrings.INVOICE_CONFIRM_PRORATE)) return;
1143 progressDialog.show(true);
1145 fieldmapper.standardRequest(
1146 ['open-ils.acq', 'open-ils.acq.invoice.apply_prorate'],
1148 params : [openils.User.authtoken, invoice.id()],
1149 oncomplete : function(r) {
1150 progressDialog.hide();
1151 var invoice = openils.Util.readResponse(r);
1153 location.href = oilsBasePath + '/acq/invoice/view/' + invoice.id();
1160 function storeExtraCopies(entry, numExtra) {
1162 dojo.byId('acq-invoice-extra-copies-message').innerHTML =
1163 dojo.string.substitute(
1164 localeStrings.INVOICE_EXTRA_COPIES, [numExtra]);
1167 addCopyHandler = dojo.connect(
1171 extraCopies[entry.lineitem().id()] = {
1172 numExtra : numExtra,
1173 fund : extraCopiesFund.widget.attr('value')
1175 extraItemsDialog.hide();
1176 dojo.disconnect(addCopyHandler);
1184 widgetRegistry.acqie[entry.id()].phys_item_count.widget.attr('value', '');
1185 extraItemsDialog.hide()
1189 extraItemsDialog.show();
1192 function validateInvIdent(inv_ident, provider, receiver) {
1193 if (!(inv_ident && provider && receiver)) {
1194 console.info("not enough information to pre-validate inv_ident");
1198 openils.Util.show("ident-validation-spinner", "inline");
1199 var pcrud = new openils.PermaCrud();
1201 "acqinv", {"inv_ident": inv_ident, "provider": provider}, {
1202 "oncomplete": function(r) {
1203 openils.Util.hide("ident-validation-spinner");
1205 /* This could throw an event about the user not having perms,
1206 * but in such a case the whole interface is already busted
1208 r = openils.Util.readResponse(r);
1210 var w = invoicePane.getFieldWidget("inv_ident").widget;
1212 alert(localeStrings.INVOICE_IDENT_COLLIDE);
1213 w.validator = function() { return false; };
1216 w.validator = function() { return true; };
1226 function drawInvoicePane(parentNode, inv, args) {
1233 recv_date : {widgetValue : dojo.date.stamp.toISOString(new Date())},
1234 //receiver : {widgetValue : openils.User.user.ws_ou()},
1237 "onChange": function(val) {
1239 invoicePane && invoicePane.getFieldValue("inv_ident"),
1240 invoicePane && invoicePane.getFieldValue("provider"),
1246 recv_method : {widgetValue : 'PPR'}
1250 dojo.mixin(override, {
1253 store_options : { base_filter : { active :"t" } },
1254 onChange : function(val) {
1255 pane.setFieldValue('shipper', val);
1257 invoicePane && invoicePane.getFieldValue("inv_ident"),
1259 invoicePane && invoicePane.getFieldValue("receiver")
1264 shipper : { dijitArgs : { store_options : { base_filter : { active :"t" } } } }
1267 for(var field in args) {
1268 override[field] = {widgetValue : args[field]};
1271 // push the name of the invoice into the name display field after update
1272 override.inv_ident = dojo.mixin(
1274 {dijitArgs : {onChange :
1278 invoicePane && invoicePane.getFieldValue("provider"),
1279 invoicePane && invoicePane.getFieldValue("receiver")
1282 if (dojo.byId('acq-invoice-summary-name'))
1283 dojo.byId('acq-invoice-summary-name').innerHTML = newVal;
1289 pane = new openils.widget.EditPane({
1293 mode : (inv) ? 'edit' : 'create',
1294 hideActionButtons : true,
1295 overrideWidgetArgs : override,
1296 readOnly : (inv) && openils.Util.isTrue(inv.complete()),
1311 suppressFields : ['id', 'complete']
1315 parentNode.appendChild(pane.domNode);
1320 function createExtraCopies(oncomplete) {
1323 for(var liId in extraCopies) {
1324 var data = extraCopies[liId];
1325 for(var i = 0; i < data.numExtra; i++) {
1326 var lid = new fieldmapper.acqlid();
1329 lid.fund(data.fund);
1330 lid.recv_time('now');
1335 if(lids.length == 0)
1336 return oncomplete();
1338 fieldmapper.standardRequest(
1339 ['open-ils.acq', 'open-ils.acq.lineitem_detail.cud.batch'],
1341 params : [openils.User.authtoken, lids, true],
1342 oncomplete : function(r) {
1343 if(openils.Util.readResponse(r))
1351 function smartSearchSubmitter() {
1352 performSearch(0, !dojo.byId('acq-unified-build-progressively').checked);
1356 openils.Util.addOnLoad(init);