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;
47 function nodeByName(name, context) {
48 return dojo.query('[name='+name+']', context)[0];
52 // before rendering any fund selectors, limit the funds to
53 // attempt to retrieve to those the user can actually use.
54 new openils.User().getPermOrgList(
55 ['ADMIN_INVOICE','CREATE_INVOICE','MANAGE_FUND'],
57 fundSearchFilter.org = orgs;
60 true, true // descendants, id_list
66 attachLi = cgi.param('attach_li') || [];
67 if (!dojo.isArray(attachLi))
68 attachLi = [attachLi];
70 attachPo = cgi.param('attach_po') || [];
71 if (!dojo.isArray(attachPo))
72 attachPo = [attachPo];
74 focusLineitem = new openils.CGI().param('focus_li');
76 totalInvoicedBox = dojo.byId('acq-total-invoiced-box');
77 totalPaidBox = dojo.byId('acq-total-paid-box');
78 balanceOwedBox = dojo.byId('acq-total-balance-box');
80 itemTypes = pcrud.retrieveAll('aiit');
82 dojo.byId('acq-invoice-summary-toggle-off').onclick = function() {
83 openils.Util.hide(dojo.byId('acq-invoice-summary'));
84 openils.Util.show(dojo.byId('acq-invoice-summary-small'));
87 dojo.byId('acq-invoice-summary-toggle-on').onclick = function() {
88 openils.Util.show(dojo.byId('acq-invoice-summary'));
89 openils.Util.hide(dojo.byId('acq-invoice-summary-small'));
92 if(cgi.param('create')) {
95 // show summary info by default for new invoices
96 dojo.byId('acq-invoice-summary-toggle-on').onclick();
99 dojo.byId('acq-invoice-summary-toggle-off').onclick();
100 fieldmapper.standardRequest(
101 ['open-ils.acq', 'open-ils.acq.invoice.retrieve.authoritative'],
103 params : [openils.User.authtoken, invoiceId],
104 oncomplete : function(r) {
105 invoice = openils.Util.readResponse(r);
112 extraCopiesFund = new openils.widget.AutoFieldWidget({
115 labelFormat : fundLabelFormat,
116 searchFormat : fundSearchFormat,
117 searchFilter : fundSearchFilter,
118 dijitArgs : {required : true},
119 parentNode : dojo.byId('acq-invoice-extra-copies-fund')
121 extraCopiesFund.build();
124 function renderInvoice() {
126 // in create mode, let the LI or PO render the invoice with seed data
127 if( !(cgi.param('create') && (attachPo.length || attachLi.length)) ) {
128 invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), invoice);
131 dojo.byId('acq-invoice-new-item').onclick = function() {
132 var item = new fieldmapper.acqii();
133 item.id(virtualId--);
135 addInvoiceItem(item);
140 if(invoice && openils.Util.isTrue(invoice.complete())) {
142 dojo.forEach( // hide widgets that should not be visible for a completed invoice
143 dojo.query('.hide-complete'),
144 function(node) { openils.Util.hide(node); }
147 new openils.User().getPermOrgList(
148 'ACQ_INVOICE_REOPEN',
150 if(orgs.indexOf(invoice.receiver()) >= 0)
151 openils.Util.show('acq-invoice-reopen-button-wrapper', 'inline');
158 // display items and entries in ID order
159 // which effectively equates to add order.
160 function idsort(a, b) { return a.id() < b.id() ? -1 : 1 }
164 invoice.items().sort(idsort),
166 addInvoiceItem(item);
171 invoice.entries().sort(idsort),
173 addInvoiceEntry(entry);
178 if(attachLi.length) doAttachLi();
179 if(attachPo.length) doAttachPo(0);
182 function doAttachLi(skipInit) {
184 //var invoiceArgs = {provider : lineitem.provider(), shipper : lineitem.provider()};
185 if(cgi.param('create') && !skipInit) {
187 // use the first LI in the list to determine the default provider
188 fieldmapper.standardRequest(
189 ['open-ils.acq', 'open-ils.acq.lineitem.retrieve.authoritative'],
191 params : [openils.User.authtoken, attachLi[0], {clear_marc:1}],
192 oncomplete : function(r) {
193 var li = openils.Util.readResponse(r);
194 invoicePane = drawInvoicePane(
195 dojo.byId('acq-view-invoice-div'), null,
196 {provider : li.provider(), shipper : li.provider()}
203 dojo.forEach(attachLi,
205 var entry = new fieldmapper.acqie();
206 entry.id(virtualId--);
209 addInvoiceEntry(entry);
214 function doAttachPo(idx) {
216 if (idx == attachPo.length) return;
217 var poId = attachPo[idx];
219 fieldmapper.standardRequest(
220 ['open-ils.acq', 'open-ils.acq.purchase_order.retrieve'],
223 openils.User.authtoken, poId,
224 {flesh_lineitem_ids : true, flesh_po_items : true}
226 oncomplete: function(r) {
227 var po = openils.Util.readResponse(r);
229 if(cgi.param('create') && idx == 0) {
230 // render the invoice using some seed data from the first PO
231 var invoiceArgs = {provider : po.provider(), shipper : po.provider()};
232 invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), null, invoiceArgs);
235 dojo.forEach(po.lineitems(),
237 var entry = new fieldmapper.acqie();
238 entry.id(virtualId--);
240 entry.lineitem(lineitem);
241 entry.purchase_order(po);
242 addInvoiceEntry(entry);
246 dojo.forEach(po.po_items(),
248 var item = new fieldmapper.acqii();
249 item.id(virtualId--);
251 item.fund(poItem.fund());
252 item.title(poItem.title());
253 item.author(poItem.author());
254 item.note(poItem.note());
255 item.inv_item_type(poItem.inv_item_type());
256 item.purchase_order(po);
257 item.po_item(poItem);
258 addInvoiceItem(item);
269 var cookieUriSSL, cookieSvc, cookieMgr;
271 function performSearch(pageDir, clearFirst) {
273 clearSearchResTable();
275 var searchObject = termManager.buildSearchObject();
277 if (openils.XUL.isXUL()) {
279 cookieSvc.setCookieString(cookieUriSSL, null,
280 "invs=" + base64Encode(searchObject) + ';max-age=2592000', null);
282 cookieSvc.setCookieString(cookieUriSSL, null,
283 "invc=" + dojo.byId("acq-unified-conjunction").getValue() +
284 ';max-age=2592000', null);
288 dojo.cookie('invs', base64Encode(searchObject));
289 dojo.cookie('invc', dojo.byId("acq-unified-conjunction").getValue());
292 if (pageDir == 0) { // new search
293 resultsLoader.displayOffset = 0;
295 resultsLoader.displayOffset += pageDir * resultsLoader.displayLimit;
298 if (resultsLoader.displayOffset == 0) {
299 openils.Util.hide('acq-inv-search-prev');
301 openils.Util.show('acq-inv-search-prev', 'inline');
304 if (dojo.byId('acq-invoice-search-limit-invoiceable').checked) {
305 if (!searchObject.jub)
306 searchObject.jub = [];
308 // exclude lineitems that are "cancelled" (sidebar: 'Mericans spell it 'canceled')
309 searchObject.jub.push({state : 'cancelled', '__not' : true});
311 // exclude lineitems already linked to this invoice
312 if (invoice && invoice.id() > 0) {
313 if (!searchObject.acqinv)
314 searchObject.acqinv = [];
315 searchObject.acqinv.push({id : invoice.id(), '__not' : true});
318 // limit to lineitems that have invoiceable copies
319 searchObject.acqlisumi = [{item_count : 1, '_gte' : true}];
321 // limit to provider if a provider is selected
322 var provider = invoicePane.getFieldValue('provider');
324 if (!searchObject.jub.filter(function(i) { return i.provider != null }).length)
325 searchObject.jub.push({provider : provider});
329 if (dojo.byId('acq-invoice-search-sort-title').checked) {
330 uriManager.order_by =
331 [ {"class": "acqlia", "field":"attr_value", "transform":"first"} ];
334 resultsLoader.lastSearch = searchObject;
335 resultManager.go(searchObject)
336 console.log('Lineitem Search: ' + js2JSON(searchObject));
337 focusLastSearchInput();
341 function renderUnifiedSearch() {
343 if (!searchInitDone) {
345 searchInitDone = true;
346 termManager = new TermManager();
347 resultManager = new ResultManager();
348 resultsLoader = new searchResultsLoader();
349 uriManager = new URIManager();
351 // define custom lineitem result handler
352 resultManager.result_types = {
354 "search_options": { "id_list": true },
355 "revealer": function() { },
356 "finisher": function() {
357 resultsLoader.batch_length = resultManager.count_results;
359 "adder": function(li) {
360 resultsLoader.addLineitem(li);
362 "interface": resultsLoader
365 "revealer": function() { }
370 resultManager.no_results_popup = true;
371 resultManager.submitter = smartSearchSubmitter;
373 var searchObject, searchConjunction;
375 if (openils.XUL.isXUL()) {
379 var ios = Components.classes["@mozilla.org/network/io-service;1"]
380 .getService(Components.interfaces.nsIIOService);
382 cookieUriSSL = ios.newURI("https://" + location.hostname, null, null);
384 cookieSvc = Components.classes["@mozilla.org/cookieService;1"]
385 .getService(Components.interfaces.nsICookieService);
388 cookieManager = Components.classes["@mozilla.org/cookiemanager;1"]
389 .getService(Components.interfaces.nsICookieManager);
392 var iter = cookieManager.enumerator;
393 while (iter.hasMoreElements()) {
394 var cookie = iter.getNext();
395 if (cookie instanceof Components.interfaces.nsICookie) {
396 if (cookie.name == 'invs')
397 searchObject = cookie.value;
398 if (cookie.name == 'invc')
399 searchConjunction = cookie.value;
404 // useful for web-based testing
405 searchObject = dojo.cookie('invs');
406 searchConjunction = dojo.cookie('invc');
411 // if there is a search object cookie, populate the search form
412 termManager.reflect(base64Decode(searchObject));
413 dojo.byId("acq-unified-conjunction").setValue(searchConjunction);
416 console.log('adding row');
417 termManager.addRow();
421 dojo.addClass(dojo.byId('oils-acq-invoice-table'), 'hidden');
422 dojo.removeClass(dojo.byId('oils-acq-invoice-search'), 'hidden');
423 focusLastSearchInput();
426 function focusLastSearchInput() {
427 // TODO: see about making this better and moving it into search/unified.js
428 var wnodes = dojo.query('[name=widget]');
429 var inputNode = wnodes.item(wnodes.length - 1).firstChild;
439 var resultsTbody, resultsRow;
440 function searchResultsLoader() {
441 this.displayOffset = 0;
442 this.displayLimit = 10;
445 resultsTbody = dojo.byId('acq-invoice-search-results-tbody');
446 resultsRow = resultsTbody.removeChild(dojo.byId('acq-invoice-search-results-tr'));
449 this.addLineitem = function(li_id) {
450 console.log('Adding search result lineitem ' + li_id);
451 var row = resultsRow.cloneNode(true);
452 resultsTbody.appendChild(row);
453 var checkbox = dojo.query('[name=search-results-checkbox]', row)[0];
454 checkbox.setAttribute('lineitem', li_id);
456 // this lineitem is already part of the invoice
457 if (dojo.query('[entry_lineitem_row=' + li_id + ']')[0]) {
458 checkbox.disabled = true;
459 dojo.addClass(checkbox.parentNode, 'search-results-already-invoiced');
462 openils.acq.Lineitem.fetchAndRender(
465 dojo.query('[name=search-results-content-div]', row)[0].innerHTML = html;
471 function addSelectedToInvoice() {
472 var inputs = dojo.query('[name=search-results-checkbox]');
476 if (checkbox.checked) {
477 attachLi.push(checkbox.getAttribute('lineitem'));
478 checkbox.disabled = true;
479 checkbox.checked = false;
480 dojo.addClass(checkbox.parentNode, 'search-results-already-invoiced');
487 allResSelected = false;
488 function clearSearchResTable() {
489 allResSelected = false;
490 while (resultsTbody.childNodes[0])
491 resultsTbody.removeChild(resultsTbody.childNodes[0]);
494 function selectSearchResults() {
495 allResSelected = !allResSelected;
496 dojo.query('[name=search-results-checkbox]').forEach(
497 function(input) { input.checked = allResSelected });
500 function updateTotalCost() {
503 for(var id in widgetRegistry.acqii)
504 if(!widgetRegistry.acqii[id]._object.isdeleted())
505 totalCost += Number(widgetRegistry.acqii[id].cost_billed.getFormattedValue());
506 for(var id in widgetRegistry.acqie)
507 if(!widgetRegistry.acqie[id]._object.isdeleted())
508 totalCost += Number(widgetRegistry.acqie[id].cost_billed.getFormattedValue());
509 totalInvoicedBox.innerHTML = totalCost.toFixed(2);
512 for(var id in widgetRegistry.acqii)
513 if(!widgetRegistry.acqii[id]._object.isdeleted())
514 totalPaid += Number(widgetRegistry.acqii[id].amount_paid.getFormattedValue());
515 for(var id in widgetRegistry.acqie)
516 if(!widgetRegistry.acqie[id]._object.isdeleted())
517 totalPaid += Number(widgetRegistry.acqie[id].amount_paid.getFormattedValue());
518 totalPaidBox.innerHTML = totalPaid.toFixed(2);
520 var buttonsDisabled = false;
522 if(totalPaid > totalCost || totalPaid < 0) {
523 openils.Util.addCSSClass(totalPaidBox, 'acq-invoice-invalid-amount');
524 invoiceSaveButton.attr('disabled', true);
525 invoiceProrateButton.attr('disabled', true);
526 buttonsDisabled = true;
528 openils.Util.removeCSSClass(totalPaidBox, 'acq-invoice-invalid-amount');
529 invoiceSaveButton.attr('disabled', false);
530 invoiceProrateButton.attr('disabled', false);
534 openils.Util.addCSSClass(totalInvoicedBox, 'acq-invoice-invalid-amount');
535 invoiceSaveButton.attr('disabled', true);
536 invoiceProrateButton.attr('disabled', true);
538 openils.Util.removeCSSClass(totalInvoicedBox, 'acq-invoice-invalid-amount');
539 if(!buttonsDisabled) {
540 invoiceSaveButton.attr('disabled', false);
541 invoiceProrateButton.attr('disabled', false);
545 if(totalPaid == totalCost) { // XXX: too rigid?
546 invoiceCloseButton.attr('disabled', false);
548 invoiceCloseButton.attr('disabled', true);
551 balanceOwedBox.innerHTML = (totalCost - totalPaid).toFixed(2);
553 updateExpectedCost();
557 function registerWidget(obj, field, widget, callback) {
558 var blob = widgetRegistry[obj.classname];
560 blob[obj.id()] = {_object : obj};
561 blob[obj.id()][field] = widget;
564 dojo.connect(w, 'onChange',
570 if(callback) callback(w, ww);
576 function addInvoiceItem(item) {
577 itemTbody = dojo.byId('acq-invoice-item-tbody');
578 if(itemTemplate == null) {
579 itemTemplate = itemTbody.removeChild(dojo.byId('acq-invoice-item-template'));
582 var row = itemTemplate.cloneNode(true);
583 var itemType = itemTypes.filter(function(t) { return (t.code() == item.inv_item_type()) })[0];
586 ['title', 'author', 'cost_billed', 'amount_paid'],
590 if(field == 'title' || field == 'author') {
591 //args = {style : 'width:10em'};
592 } else if(field == 'cost_billed' || field == 'amount_paid') {
593 args = {required : true, style : 'width: 8em'};
599 new openils.widget.AutoFieldWidget({
603 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
605 parentNode : nodeByName(field, row)
608 if (field == "cost_billed") {
610 w, "onChange", function(value) {
611 var paid = widgetRegistry.acqii[item.id()].amount_paid.widget;
612 if (value && isNaN(paid.attr("value")))
613 paid.attr("value", value);
623 /* ----------- fund -------------- */
628 labelFormat : fundLabelFormat,
629 searchFormat : fundSearchFormat,
630 searchFilter : fundSearchFilter,
631 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
632 dijitArgs : {required : true},
633 parentNode : nodeByName('fund', row)
636 if(item.fund_debit()) {
637 fundArgs.searchFilter = {'-or' : [{ "-and": fundSearchFilter }, {id : item.fund()}]};
639 if(itemType && openils.Util.isTrue(itemType.prorate()))
640 fundArgs.dijitArgs = {disabled : true};
643 var fundWidget = new openils.widget.AutoFieldWidget(fundArgs);
644 registerWidget(item, 'fund', fundWidget);
646 /* ---------- inv_item_type ------------- */
650 // read-only item view for items that were the result of a po-item
651 var po = item.purchase_order();
652 var po_item = item.po_item();
653 var node = nodeByName('inv_item_type', row);
654 var itemType = itemTypes.filter(function(t) { return (t.code() == item.inv_item_type()) })[0];
655 orderDate = (!po.order_date()) ? '' :
656 dojo.date.locale.format(dojo.date.stamp.fromISOString(po.order_date()), {selector:'date'});
658 node.innerHTML = dojo.string.substitute(
659 localeStrings.INVOICE_ITEM_PO_DETAILS,
666 po_item.estimated_cost()
675 new openils.widget.AutoFieldWidget({
677 fmField : 'inv_item_type',
678 parentNode : nodeByName('inv_item_type', row),
679 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
680 dijitArgs : {required : true}
683 // When the inv_item_type is set to prorate=true, don't allow the user the edit the fund
684 // since this charge will be prorated against (potentially) multiple funds
685 dojo.connect(w, 'onChange',
687 if(!item.fund_debit()) {
688 var itemType = itemTypes.filter(function(t) { return (t.code() == w.attr('value')) })[0];
689 if(!itemType) return;
690 if(openils.Util.isTrue(itemType.prorate())) {
691 fundWidget.widget.attr('disabled', true);
692 fundWidget.widget.attr('value', '');
694 fundWidget.widget.attr('disabled', false);
703 nodeByName('delete', row).onclick = function() {
704 var cost = widgetRegistry.acqii[item.id()].cost_billed.getFormattedValue();
707 if (widgetRegistry.acqii[item.id()].inv_item_type) {
708 iTypeName = widgetRegistry.acqii[item.id()]
709 .inv_item_type.getFormattedValue()
711 // if the invoice_item came from a po_item, the type is
712 // read-only, hence no widget in the registry. Look up
713 // the name in the cached types list.
714 var itype = itemTypes.filter(
715 function(t) { return (t.code() == item.inv_item_type()) })[0];
716 iTypeName = itype.name();
719 var msg = dojo.string.substitute(
720 localeStrings.INVOICE_CONFIRM_ITEM_DELETE,
721 [cost || 0, iTypeName || '']
723 if(!confirm(msg)) return;
724 itemTbody.removeChild(row);
725 item.isdeleted(true);
727 delete widgetRegistry.acqii[item.id()];
731 itemTbody.appendChild(row);
735 function updateReceiveLink(li) {
737 return; /* can't do this with unsaved invoices */
739 var link = dojo.byId("acq-view-invoice-receive-link");
740 if (link.onclick) return; /* only need to do this once */
742 /* don't do this if there's nothing receivable on the lineitem */
743 if (li.order_summary().recv_count() + li.order_summary().cancel_count() >=
744 li.order_summary().item_count())
747 openils.Util.show("acq-view-invoice-receive");
748 link.onclick = function() { location.href = oilsBasePath + '/acq/invoice/receive/' + invoiceId; };
752 * Ensures focusLineitem is in view and causes a brief
753 * border around the lineitem to come to life then fade.
756 if (!focusLineitem) return;
758 // set during addLineitem()
759 var node = dojo.byId('li-title-ref-' + focusLineitem);
761 console.log('focus: li-title-ref-' + focusLineitem + ' : ' + node);
763 // LI may not yet be rendered
766 console.log('focusing ' + focusLineitem);
768 // prevent numerous re-focuses
769 focusLineitem = null;
771 // causes the full row to be visible
772 dijit.scrollIntoView(node);
774 dojo.require('dojox.fx');
778 dojox.fx.highlight({color : '#BB4433', node : node, duration : 2000}).play();
784 // expected cost is totalCostInvoiced + totalCostNotYetInvoiced
785 function updateExpectedCost() {
787 var cost = Number(totalInvoicedBox.innerHTML || 0);
789 // for any LI's that are not yet billed (i.e. filled in)
790 // use the total expected cost for that lineitem.
791 for(var id in widgetRegistry.acqie) {
792 var entry = widgetRegistry.acqie[id]._object;
793 if(!entry.isdeleted()) {
794 if (Number(widgetRegistry.acqie[id].cost_billed.getFormattedValue()) == 0) {
795 var li = entry.lineitem();
797 Number(li.order_summary().estimated_amount()) -
798 Number(li.order_summary().paid_amount());
803 dojo.byId('acq-invoice-summary-cost').innerHTML = cost.toFixed(2);
806 var invoicEntryWidgets = {};
807 function addInvoiceEntry(entry) {
808 console.log('Adding new entry for lineitem ' + entry.lineitem());
810 openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-header'), 'hidden');
811 openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-thead'), 'hidden');
812 openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-tbody'), 'hidden');
814 dojo.byId('acq-invoice-summary-count').innerHTML =
815 Number(dojo.byId('acq-invoice-summary-count').innerHTML) + 1;
817 entryTbody = dojo.byId('acq-invoice-entry-tbody');
818 if(entryTemplate == null) {
819 entryTemplate = entryTbody.removeChild(dojo.byId('acq-invoice-entry-template'));
822 if(dojo.query('[lineitem=' + entry.lineitem() +']', entryTbody)[0])
823 // Is it ever valid to have multiple entries for 1 lineitem in a single invoice?
826 var row = entryTemplate.cloneNode(true);
827 row.setAttribute('lineitem', entry.lineitem());
828 row.setAttribute('entry_lineitem_row', entry.lineitem());
830 openils.acq.Lineitem.fetchAndRender(
831 entry.lineitem(), {},
834 entry.purchase_order(li.purchase_order());
835 nodeByName('title_details', row).innerHTML = html;
837 nodeByName('title_details', row).parentNode.id = 'li-title-ref-' + li.id();
838 console.log(dojo.byId('li-title-ref-' + li.id()));
840 updateReceiveLink(li);
842 // set some default values if otherwise unset
843 if (!invoicePane.getFieldValue('receiver')) {
844 invoicePane.setFieldValue('receiver', li.purchase_order().ordering_agency());
846 if (!invoicePane.getFieldValue('provider')) {
847 invoicePane.setFieldValue('provider', li.purchase_order().provider());
851 ['inv_item_count', 'phys_item_count', 'cost_billed', 'amount_paid'],
853 var dijitArgs = {required : true, constraints : {min: 0}, style : 'width:6em'};
854 if(field.match(/count/)) {
855 dijitArgs.style = 'width:4em;';
857 dijitArgs.style = 'width:9em;';
859 if (entry.isnew() && (field == 'phys_item_count' || field == 'inv_item_count')) {
860 // by default, attempt to pay for all non-canceled and as-of-yet-un-invoiced items
861 var count = Number(li.order_summary().item_count() || 0) -
862 Number(li.order_summary().cancel_count() || 0) -
863 Number(li.order_summary().invoice_count() || 0);
864 if(count < 0) count = 0;
865 dijitArgs.value = count;
870 new openils.widget.AutoFieldWidget({
874 dijitArgs : dijitArgs,
875 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
876 parentNode : nodeByName(field, row)
880 if(field == 'phys_item_count') {
881 dojo.connect(w, 'onChange',
883 // staff entered a higher number in the receive field than was originally ordered
884 // taking into account already invoiced items
885 var extra = Number(this.attr('value')) -
886 (Number(entry.lineitem().item_count()) - Number(entry.lineitem().order_summary().invoice_count()));
888 storeExtraCopies(entry, extra);
894 if (field == "cost_billed") {
895 // hooks applied with dojo.connect to dijit events are additive, so there's no conflict between this and what comes next
897 w, "onChange", function(value) {
898 var paid = widgetRegistry.acqie[entry.id()].amount_paid.widget;
899 if (value && isNaN(paid.attr("value")))
900 paid.attr("value", value);
904 if(field == 'inv_item_count' || field == 'cost_billed') {
905 setPerCopyPrice(row, entry);
906 // update the per-copy count as invoice count and cost billed change
907 dojo.connect(w, 'onChange', function() { setPerCopyPrice(row, entry) } );
916 if (focusLineitem == li.id())
921 nodeByName('detach', row).onclick = function() {
922 var cost = widgetRegistry.acqie[entry.id()].cost_billed.getFormattedValue();
924 dojo.forEach(['isbn', 'upc', 'issn'],
926 var val = liMarcAttr(entry.lineitem(), ident);
927 if(val) idents.push(val);
931 var msg = dojo.string.substitute(
932 localeStrings.INVOICE_CONFIRM_ENTRY_DETACH, [
934 liMarcAttr(entry.lineitem(), 'title'),
935 liMarcAttr(entry.lineitem(), 'author'),
939 if(!confirm(msg)) return;
940 entryTbody.removeChild(row);
941 entry.isdeleted(true);
943 delete widgetRegistry.acqie[entry.id()];
947 entryTbody.appendChild(row);
950 function setPerCopyPrice(row, entry) {
951 var inv_w = widgetRegistry.acqie[entry.id()].inv_item_count;
952 var bill_w = widgetRegistry.acqie[entry.id()].cost_billed;
954 if (inv_w && bill_w) {
955 var invoiced = Number(inv_w.getFormattedValue());
956 var billed = Number(bill_w.getFormattedValue());
957 console.log(invoiced + ' : ' + billed);
959 nodeByName('amount_paid_per_copy', row).innerHTML = (billed / invoiced).toFixed(2);
961 nodeByName('amount_paid_per_copy', row).innerHTML = '0.00';
966 function liMarcAttr(lineitem, name) {
967 var attr = lineitem.attributes().filter(
970 attr.attr_type() == 'lineitem_marc_attr_definition' &&
971 attr.attr_name() == name)
975 return (attr) ? attr.attr_value() : '';
978 function saveChanges(args) {
980 createExtraCopies(function() { saveChangesPartTwo(args); });
983 // Define a helper function to 'unflesh' sub-objects from an fmclass object.
984 // 'this' specifies the object; the arguments specify a list of names of
988 dojo.forEach(arguments, function (n) {
990 if (_ !== null && typeof _ === 'object')
995 function saveChangesPartTwo(args) {
999 invoice.complete('f');
1003 // Prepare an invoice for submission
1005 invoice = new fieldmapper.acqinv();
1006 invoice.isnew(true);
1008 invoice.ischanged(true); // for now, just always update
1011 var e = invoicePane.mapValues(function (n, v) { invoice[n](v); });
1012 if (e instanceof Error) {
1018 invoice.complete('t');
1021 // Prepare any charge items
1022 var updateItems = [];
1023 for(var id in widgetRegistry.acqii) {
1024 var reg = widgetRegistry.acqii[id];
1025 var item = reg._object;
1026 if(item.ischanged() || item.isnew() || item.isdeleted()) {
1027 updateItems.push(item);
1028 if(item.isnew()) item.id(null);
1029 for(var field in reg) {
1030 if(field != '_object')
1031 item[field]( reg[field].getFormattedValue() );
1034 unflesh.call(item, 'purchase_order');
1039 // Prepare any line items
1040 var updateEntries = [];
1041 for(var id in widgetRegistry.acqie) {
1042 var reg = widgetRegistry.acqie[id];
1043 var entry = reg._object;
1044 if(entry.ischanged() || entry.isnew() || entry.isdeleted()) {
1045 updateEntries.push(entry);
1046 if(entry.isnew()) entry.id(null);
1048 for(var field in reg) {
1049 if(field != '_object')
1050 entry[field]( reg[field].getFormattedValue() );
1053 unflesh.call(entry, 'purchase_order', 'lineitem');
1058 progressDialog.show(true);
1059 fieldmapper.standardRequest(
1060 ['open-ils.acq', 'open-ils.acq.invoice.update'],
1062 params : [openils.User.authtoken, invoice, updateEntries, updateItems],
1063 oncomplete : function(r) {
1064 progressDialog.hide();
1065 var invoice = openils.Util.readResponse(r);
1068 return prorateInvoice(invoice);
1070 location.href = oilsBasePath + '/acq/invoice/view?create=1';
1072 location.href = oilsBasePath + '/acq/invoice/view/' + invoice.id();
1080 function prorateInvoice(invoice) {
1081 if(!confirm(localeStrings.INVOICE_CONFIRM_PRORATE)) return;
1082 progressDialog.show(true);
1084 fieldmapper.standardRequest(
1085 ['open-ils.acq', 'open-ils.acq.invoice.apply_prorate'],
1087 params : [openils.User.authtoken, invoice.id()],
1088 oncomplete : function(r) {
1089 progressDialog.hide();
1090 var invoice = openils.Util.readResponse(r);
1092 location.href = oilsBasePath + '/acq/invoice/view/' + invoice.id();
1099 function storeExtraCopies(entry, numExtra) {
1101 dojo.byId('acq-invoice-extra-copies-message').innerHTML =
1102 dojo.string.substitute(
1103 localeStrings.INVOICE_EXTRA_COPIES, [numExtra]);
1106 addCopyHandler = dojo.connect(
1110 extraCopies[entry.lineitem().id()] = {
1111 numExtra : numExtra,
1112 fund : extraCopiesFund.widget.attr('value')
1114 extraItemsDialog.hide();
1115 dojo.disconnect(addCopyHandler);
1123 widgetRegistry.acqie[entry.id()].phys_item_count.widget.attr('value', '');
1124 extraItemsDialog.hide()
1128 extraItemsDialog.show();
1131 function validateInvIdent(inv_ident, provider, receiver) {
1132 if (!(inv_ident && provider && receiver)) {
1133 console.info("not enough information to pre-validate inv_ident");
1137 openils.Util.show("ident-validation-spinner", "inline");
1138 var pcrud = new openils.PermaCrud();
1140 "acqinv", {"inv_ident": inv_ident, "provider": provider}, {
1141 "oncomplete": function(r) {
1142 openils.Util.hide("ident-validation-spinner");
1144 /* This could throw an event about the user not having perms,
1145 * but in such a case the whole interface is already busted
1147 r = openils.Util.readResponse(r);
1149 var w = invoicePane.getFieldWidget("inv_ident").widget;
1151 alert(localeStrings.INVOICE_IDENT_COLLIDE);
1152 w.validator = function() { return false; };
1155 w.validator = function() { return true; };
1165 function drawInvoicePane(parentNode, inv, args) {
1172 recv_date : {widgetValue : dojo.date.stamp.toISOString(new Date())},
1173 //receiver : {widgetValue : openils.User.user.ws_ou()},
1176 "onChange": function(val) {
1178 invoicePane && invoicePane.getFieldValue("inv_ident"),
1179 invoicePane && invoicePane.getFieldValue("provider"),
1185 recv_method : {widgetValue : 'PPR'}
1189 dojo.mixin(override, {
1192 store_options : { base_filter : { active :"t" } },
1193 onChange : function(val) {
1194 pane.setFieldValue('shipper', val);
1196 invoicePane && invoicePane.getFieldValue("inv_ident"),
1198 invoicePane && invoicePane.getFieldValue("receiver")
1203 shipper : { dijitArgs : { store_options : { base_filter : { active :"t" } } } }
1206 for(var field in args) {
1207 override[field] = {widgetValue : args[field]};
1210 // push the name of the invoice into the name display field after update
1211 override.inv_ident = dojo.mixin(
1213 {dijitArgs : {onChange :
1217 invoicePane && invoicePane.getFieldValue("provider"),
1218 invoicePane && invoicePane.getFieldValue("receiver")
1221 if (dojo.byId('acq-invoice-summary-name'))
1222 dojo.byId('acq-invoice-summary-name').innerHTML = newVal;
1228 pane = new openils.widget.EditPane({
1232 mode : (inv) ? 'edit' : 'create',
1233 hideActionButtons : true,
1234 overrideWidgetArgs : override,
1235 readOnly : (inv) && openils.Util.isTrue(inv.complete()),
1250 suppressFields : ['id', 'complete']
1254 parentNode.appendChild(pane.domNode);
1259 function createExtraCopies(oncomplete) {
1262 for(var liId in extraCopies) {
1263 var data = extraCopies[liId];
1264 for(var i = 0; i < data.numExtra; i++) {
1265 var lid = new fieldmapper.acqlid();
1268 lid.fund(data.fund);
1269 lid.recv_time('now');
1274 if(lids.length == 0)
1275 return oncomplete();
1277 fieldmapper.standardRequest(
1278 ['open-ils.acq', 'open-ils.acq.lineitem_detail.cud.batch'],
1280 params : [openils.User.authtoken, lids, true],
1281 oncomplete : function(r) {
1282 if(openils.Util.readResponse(r))
1290 function smartSearchSubmitter() {
1291 performSearch(0, !dojo.byId('acq-unified-build-progressively').checked);
1295 openils.Util.addOnLoad(init);