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'];
24 var cgi = new openils.CGI();
25 var pcrud = new openils.PermaCrud();
40 var widgetRegistry = {acqie : {}, acqii : {}};
42 var searchInitDone = false;
46 function nodeByName(name, context) {
47 return dojo.query('[name='+name+']', context)[0];
52 attachLi = cgi.param('attach_li') || [];
53 if (!dojo.isArray(attachLi))
54 attachLi = [attachLi];
56 attachPo = cgi.param('attach_po') || [];
57 if (!dojo.isArray(attachPo))
58 attachPo = [attachPo];
60 focusLineitem = new openils.CGI().param('focus_li');
62 totalInvoicedBox = dojo.byId('acq-total-invoiced-box');
63 totalPaidBox = dojo.byId('acq-total-paid-box');
64 balanceOwedBox = dojo.byId('acq-total-balance-box');
66 itemTypes = pcrud.retrieveAll('aiit');
68 dojo.byId('acq-invoice-summary-toggle-off').onclick = function() {
69 openils.Util.hide(dojo.byId('acq-invoice-summary'));
70 openils.Util.show(dojo.byId('acq-invoice-summary-small'));
73 dojo.byId('acq-invoice-summary-toggle-on').onclick = function() {
74 openils.Util.show(dojo.byId('acq-invoice-summary'));
75 openils.Util.hide(dojo.byId('acq-invoice-summary-small'));
78 if(cgi.param('create')) {
81 // show summary info by default for new invoices
82 dojo.byId('acq-invoice-summary-toggle-on').onclick();
85 dojo.byId('acq-invoice-summary-toggle-off').onclick();
86 fieldmapper.standardRequest(
87 ['open-ils.acq', 'open-ils.acq.invoice.retrieve.authoritative'],
89 params : [openils.User.authtoken, invoiceId],
90 oncomplete : function(r) {
91 invoice = openils.Util.readResponse(r);
98 extraCopiesFund = new openils.widget.AutoFieldWidget({
101 searchFilter : {active : 't'},
102 labelFormat : fundLabelFormat,
103 searchFormat : fundSearchFormat,
104 dijitArgs : {required : true},
105 parentNode : dojo.byId('acq-invoice-extra-copies-fund')
107 extraCopiesFund.build();
110 function renderInvoice() {
112 // in create mode, let the LI or PO render the invoice with seed data
113 if( !(cgi.param('create') && (attachPo.length || attachLi.length)) ) {
114 invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), invoice);
117 dojo.byId('acq-invoice-new-item').onclick = function() {
118 var item = new fieldmapper.acqii();
119 item.id(virtualId--);
121 addInvoiceItem(item);
126 if(invoice && openils.Util.isTrue(invoice.complete())) {
128 dojo.forEach( // hide widgets that should not be visible for a completed invoice
129 dojo.query('.hide-complete'),
130 function(node) { openils.Util.hide(node); }
133 new openils.User().getPermOrgList(
134 'ACQ_INVOICE_REOPEN',
136 if(orgs.indexOf(invoice.receiver()) >= 0)
137 openils.Util.show('acq-invoice-reopen-button-wrapper', 'inline');
144 // display items and entries in ID order
145 // which effectively equates to add order.
146 function idsort(a, b) { return a.id() < b.id() ? -1 : 1 }
150 invoice.items().sort(idsort),
152 addInvoiceItem(item);
157 invoice.entries().sort(idsort),
159 addInvoiceEntry(entry);
164 if(attachLi.length) doAttachLi();
165 if(attachPo.length) doAttachPo(0);
168 function doAttachLi(skipInit) {
170 //var invoiceArgs = {provider : lineitem.provider(), shipper : lineitem.provider()};
171 if(cgi.param('create') && !skipInit) {
173 // use the first LI in the list to determine the default provider
174 fieldmapper.standardRequest(
175 ['open-ils.acq', 'open-ils.acq.lineitem.retrieve.authoritative'],
177 params : [openils.User.authtoken, attachLi[0], {clear_marc:1}],
178 oncomplete : function(r) {
179 var li = openils.Util.readResponse(r);
180 invoicePane = drawInvoicePane(
181 dojo.byId('acq-view-invoice-div'), null,
182 {provider : li.provider(), shipper : li.provider()}
189 dojo.forEach(attachLi,
191 var entry = new fieldmapper.acqie();
192 entry.id(virtualId--);
195 addInvoiceEntry(entry);
200 function doAttachPo(idx) {
202 if (idx == attachPo.length) return;
203 var poId = attachPo[idx];
205 fieldmapper.standardRequest(
206 ['open-ils.acq', 'open-ils.acq.purchase_order.retrieve'],
209 openils.User.authtoken, poId,
210 {flesh_lineitem_ids : true, flesh_po_items : true}
212 oncomplete: function(r) {
213 var po = openils.Util.readResponse(r);
215 if(cgi.param('create') && idx == 0) {
216 // render the invoice using some seed data from the first PO
217 var invoiceArgs = {provider : po.provider(), shipper : po.provider()};
218 invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), null, invoiceArgs);
221 dojo.forEach(po.lineitems(),
223 var entry = new fieldmapper.acqie();
224 entry.id(virtualId--);
226 entry.lineitem(lineitem);
227 entry.purchase_order(po);
228 addInvoiceEntry(entry);
232 dojo.forEach(po.po_items(),
234 var item = new fieldmapper.acqii();
235 item.id(virtualId--);
237 item.fund(poItem.fund());
238 item.title(poItem.title());
239 item.author(poItem.author());
240 item.note(poItem.note());
241 item.inv_item_type(poItem.inv_item_type());
242 item.purchase_order(po);
243 item.po_item(poItem);
244 addInvoiceItem(item);
255 var cookieUriSSL, cookieSvc, cookieMgr;
257 function performSearch(pageDir, clearFirst) {
259 clearSearchResTable();
261 var searchObject = termManager.buildSearchObject();
263 if (openils.XUL.isXUL()) {
265 cookieSvc.setCookieString(cookieUriSSL, null,
266 "invs=" + base64Encode(searchObject) + ';max-age=2592000', null);
268 cookieSvc.setCookieString(cookieUriSSL, null,
269 "invc=" + dojo.byId("acq-unified-conjunction").getValue() +
270 ';max-age=2592000', null);
274 dojo.cookie('invs', base64Encode(searchObject));
275 dojo.cookie('invc', dojo.byId("acq-unified-conjunction").getValue());
278 if (pageDir == 0) { // new search
279 resultsLoader.displayOffset = 0;
281 resultsLoader.displayOffset += pageDir * resultsLoader.displayLimit;
284 if (resultsLoader.displayOffset == 0) {
285 openils.Util.hide('acq-inv-search-prev');
287 openils.Util.show('acq-inv-search-prev', 'inline');
290 if (dojo.byId('acq-invoice-search-limit-invoiceable').checked) {
291 if (!searchObject.jub)
292 searchObject.jub = [];
294 // exclude lineitems that are "cancelled" (sidebar: 'Mericans spell it 'canceled')
295 searchObject.jub.push({state : 'cancelled', '__not' : true});
297 // exclude lineitems already linked to this invoice
298 if (invoice && invoice.id() > 0) {
299 if (!searchObject.acqinv)
300 searchObject.acqinv = [];
301 searchObject.acqinv.push({id : invoice.id(), '__not' : true});
304 // limit to lineitems that have invoiceable copies
305 searchObject.acqlisumi = [{item_count : 1, '_gte' : true}];
307 // limit to provider if a provider is selected
308 var provider = invoicePane.getFieldValue('provider');
310 if (!searchObject.jub.filter(function(i) { return i.provider != null }).length)
311 searchObject.jub.push({provider : provider});
315 if (dojo.byId('acq-invoice-search-sort-title').checked) {
316 uriManager.order_by =
317 [ {"class": "acqlia", "field":"attr_value", "transform":"first"} ];
320 resultsLoader.lastSearch = searchObject;
321 resultManager.go(searchObject)
322 console.log('Lineitem Search: ' + js2JSON(searchObject));
323 focusLastSearchInput();
327 function renderUnifiedSearch() {
329 if (!searchInitDone) {
331 searchInitDone = true;
332 termManager = new TermManager();
333 resultManager = new ResultManager();
334 resultsLoader = new searchResultsLoader();
335 uriManager = new URIManager();
337 // define custom lineitem result handler
338 resultManager.result_types = {
340 "search_options": { "id_list": true },
341 "revealer": function() { },
342 "finisher": function() {
343 resultsLoader.batch_length = resultManager.count_results;
345 "adder": function(li) {
346 resultsLoader.addLineitem(li);
348 "interface": resultsLoader
351 "revealer": function() { }
356 resultManager.no_results_popup = true;
357 resultManager.submitter = smartSearchSubmitter;
359 var searchObject, searchConjunction;
361 if (openils.XUL.isXUL()) {
365 var ios = Components.classes["@mozilla.org/network/io-service;1"]
366 .getService(Components.interfaces.nsIIOService);
368 cookieUriSSL = ios.newURI("https://" + location.hostname, null, null);
370 cookieSvc = Components.classes["@mozilla.org/cookieService;1"]
371 .getService(Components.interfaces.nsICookieService);
374 cookieManager = Components.classes["@mozilla.org/cookiemanager;1"]
375 .getService(Components.interfaces.nsICookieManager);
378 var iter = cookieManager.enumerator;
379 while (iter.hasMoreElements()) {
380 var cookie = iter.getNext();
381 if (cookie instanceof Components.interfaces.nsICookie) {
382 if (cookie.name == 'invs')
383 searchObject = cookie.value;
384 if (cookie.name == 'invc')
385 searchConjunction = cookie.value;
390 // useful for web-based testing
391 searchObject = dojo.cookie('invs');
392 searchConjunction = dojo.cookie('invc');
397 // if there is a search object cookie, populate the search form
398 termManager.reflect(base64Decode(searchObject));
399 dojo.byId("acq-unified-conjunction").setValue(searchConjunction);
402 console.log('adding row');
403 termManager.addRow();
407 dojo.addClass(dojo.byId('oils-acq-invoice-table'), 'hidden');
408 dojo.removeClass(dojo.byId('oils-acq-invoice-search'), 'hidden');
409 focusLastSearchInput();
412 function focusLastSearchInput() {
413 // TODO: see about making this better and moving it into search/unified.js
414 var wnodes = dojo.query('[name=widget]');
415 var inputNode = wnodes.item(wnodes.length - 1).firstChild;
425 var resultsTbody, resultsRow;
426 function searchResultsLoader() {
427 this.displayOffset = 0;
428 this.displayLimit = 10;
431 resultsTbody = dojo.byId('acq-invoice-search-results-tbody');
432 resultsRow = resultsTbody.removeChild(dojo.byId('acq-invoice-search-results-tr'));
435 this.addLineitem = function(li_id) {
436 console.log('Adding search result lineitem ' + li_id);
437 var row = resultsRow.cloneNode(true);
438 resultsTbody.appendChild(row);
439 var checkbox = dojo.query('[name=search-results-checkbox]', row)[0];
440 checkbox.setAttribute('lineitem', li_id);
442 // this lineitem is already part of the invoice
443 if (dojo.query('[entry_lineitem_row=' + li_id + ']')[0]) {
444 checkbox.disabled = true;
445 dojo.addClass(checkbox.parentNode, 'search-results-already-invoiced');
448 openils.acq.Lineitem.fetchAndRender(
451 dojo.query('[name=search-results-content-div]', row)[0].innerHTML = html;
457 function addSelectedToInvoice() {
458 var inputs = dojo.query('[name=search-results-checkbox]');
462 if (checkbox.checked) {
463 attachLi.push(checkbox.getAttribute('lineitem'));
464 checkbox.disabled = true;
465 checkbox.checked = false;
466 dojo.addClass(checkbox.parentNode, 'search-results-already-invoiced');
473 allResSelected = false;
474 function clearSearchResTable() {
475 allResSelected = false;
476 while (resultsTbody.childNodes[0])
477 resultsTbody.removeChild(resultsTbody.childNodes[0]);
480 function selectSearchResults() {
481 allResSelected = !allResSelected;
482 dojo.query('[name=search-results-checkbox]').forEach(
483 function(input) { input.checked = allResSelected });
486 function updateTotalCost() {
489 for(var id in widgetRegistry.acqii)
490 if(!widgetRegistry.acqii[id]._object.isdeleted())
491 totalCost += Number(widgetRegistry.acqii[id].cost_billed.getFormattedValue());
492 for(var id in widgetRegistry.acqie)
493 if(!widgetRegistry.acqie[id]._object.isdeleted())
494 totalCost += Number(widgetRegistry.acqie[id].cost_billed.getFormattedValue());
495 totalInvoicedBox.innerHTML = totalCost.toFixed(2);
498 for(var id in widgetRegistry.acqii)
499 if(!widgetRegistry.acqii[id]._object.isdeleted())
500 totalPaid += Number(widgetRegistry.acqii[id].amount_paid.getFormattedValue());
501 for(var id in widgetRegistry.acqie)
502 if(!widgetRegistry.acqie[id]._object.isdeleted())
503 totalPaid += Number(widgetRegistry.acqie[id].amount_paid.getFormattedValue());
504 totalPaidBox.innerHTML = totalPaid.toFixed(2);
506 var buttonsDisabled = false;
508 if(totalPaid > totalCost || totalPaid < 0) {
509 openils.Util.addCSSClass(totalPaidBox, 'acq-invoice-invalid-amount');
510 invoiceSaveButton.attr('disabled', true);
511 invoiceProrateButton.attr('disabled', true);
512 buttonsDisabled = true;
514 openils.Util.removeCSSClass(totalPaidBox, 'acq-invoice-invalid-amount');
515 invoiceSaveButton.attr('disabled', false);
516 invoiceProrateButton.attr('disabled', false);
520 openils.Util.addCSSClass(totalInvoicedBox, 'acq-invoice-invalid-amount');
521 invoiceSaveButton.attr('disabled', true);
522 invoiceProrateButton.attr('disabled', true);
524 openils.Util.removeCSSClass(totalInvoicedBox, 'acq-invoice-invalid-amount');
525 if(!buttonsDisabled) {
526 invoiceSaveButton.attr('disabled', false);
527 invoiceProrateButton.attr('disabled', false);
531 if(totalPaid == totalCost) { // XXX: too rigid?
532 invoiceCloseButton.attr('disabled', false);
534 invoiceCloseButton.attr('disabled', true);
537 balanceOwedBox.innerHTML = (totalCost - totalPaid).toFixed(2);
539 updateExpectedCost();
543 function registerWidget(obj, field, widget, callback) {
544 var blob = widgetRegistry[obj.classname];
546 blob[obj.id()] = {_object : obj};
547 blob[obj.id()][field] = widget;
550 dojo.connect(w, 'onChange',
556 if(callback) callback(w, ww);
562 function addInvoiceItem(item) {
563 itemTbody = dojo.byId('acq-invoice-item-tbody');
564 if(itemTemplate == null) {
565 itemTemplate = itemTbody.removeChild(dojo.byId('acq-invoice-item-template'));
568 var row = itemTemplate.cloneNode(true);
569 var itemType = itemTypes.filter(function(t) { return (t.code() == item.inv_item_type()) })[0];
572 ['title', 'author', 'cost_billed', 'amount_paid'],
576 if(field == 'title' || field == 'author') {
577 //args = {style : 'width:10em'};
578 } else if(field == 'cost_billed' || field == 'amount_paid') {
579 args = {required : true, style : 'width: 8em'};
585 new openils.widget.AutoFieldWidget({
589 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
591 parentNode : nodeByName(field, row)
594 if (field == "cost_billed") {
596 w, "onChange", function(value) {
597 var paid = widgetRegistry.acqii[item.id()].amount_paid.widget;
598 if (value && isNaN(paid.attr("value")))
599 paid.attr("value", value);
609 /* ----------- fund -------------- */
614 labelFormat : fundLabelFormat,
615 searchFormat : fundSearchFormat,
616 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
617 dijitArgs : {required : true},
618 parentNode : nodeByName('fund', row)
621 if(item.fund_debit()) {
622 fundArgs.searchFilter = {'-or' : [{active : 't'}, {id : item.fund()}]};
624 fundArgs.searchFilter = {active : 't'}
625 if(itemType && openils.Util.isTrue(itemType.prorate()))
626 fundArgs.dijitArgs = {disabled : true};
629 var fundWidget = new openils.widget.AutoFieldWidget(fundArgs);
630 registerWidget(item, 'fund', fundWidget);
632 /* ---------- inv_item_type ------------- */
636 // read-only item view for items that were the result of a po-item
637 var po = item.purchase_order();
638 var po_item = item.po_item();
639 var node = nodeByName('inv_item_type', row);
640 var itemType = itemTypes.filter(function(t) { return (t.code() == item.inv_item_type()) })[0];
641 orderDate = (!po.order_date()) ? '' :
642 dojo.date.locale.format(dojo.date.stamp.fromISOString(po.order_date()), {selector:'date'});
644 node.innerHTML = dojo.string.substitute(
645 localeStrings.INVOICE_ITEM_PO_DETAILS,
652 po_item.estimated_cost()
661 new openils.widget.AutoFieldWidget({
663 fmField : 'inv_item_type',
664 parentNode : nodeByName('inv_item_type', row),
665 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
666 dijitArgs : {required : true}
669 // When the inv_item_type is set to prorate=true, don't allow the user the edit the fund
670 // since this charge will be prorated against (potentially) multiple funds
671 dojo.connect(w, 'onChange',
673 if(!item.fund_debit()) {
674 var itemType = itemTypes.filter(function(t) { return (t.code() == w.attr('value')) })[0];
675 if(!itemType) return;
676 if(openils.Util.isTrue(itemType.prorate())) {
677 fundWidget.widget.attr('disabled', true);
678 fundWidget.widget.attr('value', '');
680 fundWidget.widget.attr('disabled', false);
689 nodeByName('delete', row).onclick = function() {
690 var cost = widgetRegistry.acqii[item.id()].cost_billed.getFormattedValue();
691 var msg = dojo.string.substitute(
692 localeStrings.INVOICE_CONFIRM_ITEM_DELETE, [
694 widgetRegistry.acqii[item.id()].inv_item_type.getFormattedValue() || ''
697 if(!confirm(msg)) return;
698 itemTbody.removeChild(row);
699 item.isdeleted(true);
701 delete widgetRegistry.acqii[item.id()];
705 itemTbody.appendChild(row);
709 function updateReceiveLink(li) {
711 return; /* can't do this with unsaved invoices */
713 var link = dojo.byId("acq-view-invoice-receive-link");
714 if (link.onclick) return; /* only need to do this once */
716 /* don't do this if there's nothing receivable on the lineitem */
717 if (li.order_summary().recv_count() + li.order_summary().cancel_count() >=
718 li.order_summary().item_count())
721 openils.Util.show("acq-view-invoice-receive");
722 link.onclick = function() { location.href = oilsBasePath + '/acq/invoice/receive/' + invoiceId; };
726 * Ensures focusLineitem is in view and causes a brief
727 * border around the lineitem to come to life then fade.
730 if (!focusLineitem) return;
732 // set during addLineitem()
733 var node = dojo.byId('li-title-ref-' + focusLineitem);
735 console.log('focus: li-title-ref-' + focusLineitem + ' : ' + node);
737 // LI may not yet be rendered
740 console.log('focusing ' + focusLineitem);
742 // prevent numerous re-focuses
743 focusLineitem = null;
745 // causes the full row to be visible
746 dijit.scrollIntoView(node);
748 dojo.require('dojox.fx');
752 dojox.fx.highlight({color : '#BB4433', node : node, duration : 2000}).play();
758 // expected cost is totalCostInvoiced + totalCostNotYetInvoiced
759 function updateExpectedCost() {
761 var cost = Number(totalInvoicedBox.innerHTML || 0);
763 // for any LI's that are not yet billed (i.e. filled in)
764 // use the total expected cost for that lineitem.
765 for(var id in widgetRegistry.acqie) {
766 var entry = widgetRegistry.acqie[id]._object;
767 if(!entry.isdeleted()) {
768 if (Number(widgetRegistry.acqie[id].cost_billed.getFormattedValue()) == 0) {
769 var li = entry.lineitem();
771 Number(li.order_summary().estimated_amount()) -
772 Number(li.order_summary().paid_amount());
777 dojo.byId('acq-invoice-summary-cost').innerHTML = cost.toFixed(2);
780 var invoicEntryWidgets = {};
781 function addInvoiceEntry(entry) {
782 console.log('Adding new entry for lineitem ' + entry.lineitem());
784 openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-header'), 'hidden');
785 openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-thead'), 'hidden');
786 openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-tbody'), 'hidden');
788 dojo.byId('acq-invoice-summary-count').innerHTML =
789 Number(dojo.byId('acq-invoice-summary-count').innerHTML) + 1;
791 entryTbody = dojo.byId('acq-invoice-entry-tbody');
792 if(entryTemplate == null) {
793 entryTemplate = entryTbody.removeChild(dojo.byId('acq-invoice-entry-template'));
796 if(dojo.query('[lineitem=' + entry.lineitem() +']', entryTbody)[0])
797 // Is it ever valid to have multiple entries for 1 lineitem in a single invoice?
800 var row = entryTemplate.cloneNode(true);
801 row.setAttribute('lineitem', entry.lineitem());
802 row.setAttribute('entry_lineitem_row', entry.lineitem());
804 openils.acq.Lineitem.fetchAndRender(
805 entry.lineitem(), {},
808 entry.purchase_order(li.purchase_order());
809 nodeByName('title_details', row).innerHTML = html;
811 nodeByName('title_details', row).parentNode.id = 'li-title-ref-' + li.id();
812 console.log(dojo.byId('li-title-ref-' + li.id()));
814 updateReceiveLink(li);
816 // set some default values if otherwise unset
817 if (!invoicePane.getFieldValue('receiver')) {
818 invoicePane.setFieldValue('receiver', li.purchase_order().ordering_agency());
820 if (!invoicePane.getFieldValue('provider')) {
821 invoicePane.setFieldValue('provider', li.purchase_order().provider());
825 ['inv_item_count', 'phys_item_count', 'cost_billed', 'amount_paid'],
827 var dijitArgs = {required : true, constraints : {min: 0}, style : 'width:6em'};
828 if(field.match(/count/)) {
829 dijitArgs.style = 'width:4em;';
831 dijitArgs.style = 'width:9em;';
833 if (entry.isnew() && (field == 'phys_item_count' || field == 'inv_item_count')) {
834 // by default, attempt to pay for all non-canceled and as-of-yet-un-invoiced items
835 var count = Number(li.order_summary().item_count() || 0) -
836 Number(li.order_summary().cancel_count() || 0) -
837 Number(li.order_summary().invoice_count() || 0);
838 if(count < 0) count = 0;
839 dijitArgs.value = count;
844 new openils.widget.AutoFieldWidget({
848 dijitArgs : dijitArgs,
849 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
850 parentNode : nodeByName(field, row)
854 if(field == 'phys_item_count') {
855 dojo.connect(w, 'onChange',
857 // staff entered a higher number in the receive field than was originally ordered
858 // taking into account already invoiced items
859 var extra = Number(this.attr('value')) -
860 (Number(entry.lineitem().item_count()) - Number(entry.lineitem().order_summary().invoice_count()));
862 storeExtraCopies(entry, extra);
868 if (field == "cost_billed") {
869 // hooks applied with dojo.connect to dijit events are additive, so there's no conflict between this and what comes next
871 w, "onChange", function(value) {
872 var paid = widgetRegistry.acqie[entry.id()].amount_paid.widget;
873 if (value && isNaN(paid.attr("value")))
874 paid.attr("value", value);
878 if(field == 'inv_item_count' || field == 'cost_billed') {
879 setPerCopyPrice(row, entry);
880 // update the per-copy count as invoice count and cost billed change
881 dojo.connect(w, 'onChange', function() { setPerCopyPrice(row, entry) } );
890 if (focusLineitem == li.id())
895 nodeByName('detach', row).onclick = function() {
896 var cost = widgetRegistry.acqie[entry.id()].cost_billed.getFormattedValue();
898 dojo.forEach(['isbn', 'upc', 'issn'],
900 var val = liMarcAttr(entry.lineitem(), ident);
901 if(val) idents.push(val);
905 var msg = dojo.string.substitute(
906 localeStrings.INVOICE_CONFIRM_ENTRY_DETACH, [
908 liMarcAttr(entry.lineitem(), 'title'),
909 liMarcAttr(entry.lineitem(), 'author'),
913 if(!confirm(msg)) return;
914 entryTbody.removeChild(row);
915 entry.isdeleted(true);
917 delete widgetRegistry.acqie[entry.id()];
921 entryTbody.appendChild(row);
924 function setPerCopyPrice(row, entry) {
925 var inv_w = widgetRegistry.acqie[entry.id()].inv_item_count;
926 var bill_w = widgetRegistry.acqie[entry.id()].cost_billed;
928 if (inv_w && bill_w) {
929 var invoiced = Number(inv_w.getFormattedValue());
930 var billed = Number(bill_w.getFormattedValue());
931 console.log(invoiced + ' : ' + billed);
933 nodeByName('amount_paid_per_copy', row).innerHTML = (billed / invoiced).toFixed(2);
935 nodeByName('amount_paid_per_copy', row).innerHTML = '0.00';
940 function liMarcAttr(lineitem, name) {
941 var attr = lineitem.attributes().filter(
944 attr.attr_type() == 'lineitem_marc_attr_definition' &&
945 attr.attr_name() == name)
949 return (attr) ? attr.attr_value() : '';
952 function saveChanges(args) {
954 createExtraCopies(function() { saveChangesPartTwo(args); });
957 // Define a helper function to 'unflesh' sub-objects from an fmclass object.
958 // 'this' specifies the object; the arguments specify a list of names of
962 dojo.forEach(arguments, function (n) {
964 if (_ !== null && typeof _ === 'object')
969 function saveChangesPartTwo(args) {
973 invoice.complete('f');
977 // Prepare an invoice for submission
979 invoice = new fieldmapper.acqinv();
982 invoice.ischanged(true); // for now, just always update
985 var e = invoicePane.mapValues(function (n, v) { invoice[n](v); });
986 if (e instanceof Error) {
992 invoice.complete('t');
995 // Prepare any charge items
996 var updateItems = [];
997 for(var id in widgetRegistry.acqii) {
998 var reg = widgetRegistry.acqii[id];
999 var item = reg._object;
1000 if(item.ischanged() || item.isnew() || item.isdeleted()) {
1001 updateItems.push(item);
1002 if(item.isnew()) item.id(null);
1003 for(var field in reg) {
1004 if(field != '_object')
1005 item[field]( reg[field].getFormattedValue() );
1008 unflesh.call(item, 'purchase_order');
1013 // Prepare any line items
1014 var updateEntries = [];
1015 for(var id in widgetRegistry.acqie) {
1016 var reg = widgetRegistry.acqie[id];
1017 var entry = reg._object;
1018 if(entry.ischanged() || entry.isnew() || entry.isdeleted()) {
1019 updateEntries.push(entry);
1020 if(entry.isnew()) entry.id(null);
1022 for(var field in reg) {
1023 if(field != '_object')
1024 entry[field]( reg[field].getFormattedValue() );
1027 unflesh.call(entry, 'purchase_order', 'lineitem');
1032 progressDialog.show(true);
1033 fieldmapper.standardRequest(
1034 ['open-ils.acq', 'open-ils.acq.invoice.update'],
1036 params : [openils.User.authtoken, invoice, updateEntries, updateItems],
1037 oncomplete : function(r) {
1038 progressDialog.hide();
1039 var invoice = openils.Util.readResponse(r);
1042 return prorateInvoice(invoice);
1044 location.href = oilsBasePath + '/acq/invoice/view?create=1';
1046 location.href = oilsBasePath + '/acq/invoice/view/' + invoice.id();
1054 function prorateInvoice(invoice) {
1055 if(!confirm(localeStrings.INVOICE_CONFIRM_PRORATE)) return;
1056 progressDialog.show(true);
1058 fieldmapper.standardRequest(
1059 ['open-ils.acq', 'open-ils.acq.invoice.apply_prorate'],
1061 params : [openils.User.authtoken, invoice.id()],
1062 oncomplete : function(r) {
1063 progressDialog.hide();
1064 var invoice = openils.Util.readResponse(r);
1066 location.href = oilsBasePath + '/acq/invoice/view/' + invoice.id();
1073 function storeExtraCopies(entry, numExtra) {
1075 dojo.byId('acq-invoice-extra-copies-message').innerHTML =
1076 dojo.string.substitute(
1077 localeStrings.INVOICE_EXTRA_COPIES, [numExtra]);
1080 addCopyHandler = dojo.connect(
1084 extraCopies[entry.lineitem().id()] = {
1085 numExtra : numExtra,
1086 fund : extraCopiesFund.widget.attr('value')
1088 extraItemsDialog.hide();
1089 dojo.disconnect(addCopyHandler);
1097 widgetRegistry.acqie[entry.id()].phys_item_count.widget.attr('value', '');
1098 extraItemsDialog.hide()
1102 extraItemsDialog.show();
1105 function validateInvIdent(inv_ident, provider, receiver) {
1106 if (!(inv_ident && provider && receiver)) {
1107 console.info("not enough information to pre-validate inv_ident");
1111 openils.Util.show("ident-validation-spinner", "inline");
1112 var pcrud = new openils.PermaCrud();
1114 "acqinv", {"inv_ident": inv_ident, "provider": provider}, {
1115 "oncomplete": function(r) {
1116 openils.Util.hide("ident-validation-spinner");
1118 /* This could throw an event about the user not having perms,
1119 * but in such a case the whole interface is already busted
1121 r = openils.Util.readResponse(r);
1123 var w = invoicePane.getFieldWidget("inv_ident").widget;
1125 alert(localeStrings.INVOICE_IDENT_COLLIDE);
1126 w.validator = function() { return false; };
1129 w.validator = function() { return true; };
1139 function drawInvoicePane(parentNode, inv, args) {
1146 recv_date : {widgetValue : dojo.date.stamp.toISOString(new Date())},
1147 //receiver : {widgetValue : openils.User.user.ws_ou()},
1150 "onChange": function(val) {
1152 invoicePane && invoicePane.getFieldValue("inv_ident"),
1153 invoicePane && invoicePane.getFieldValue("provider"),
1159 recv_method : {widgetValue : 'PPR'}
1163 dojo.mixin(override, {
1166 store_options : { base_filter : { active :"t" } },
1167 onChange : function(val) {
1168 pane.setFieldValue('shipper', val);
1170 invoicePane && invoicePane.getFieldValue("inv_ident"),
1172 invoicePane && invoicePane.getFieldValue("receiver")
1177 shipper : { dijitArgs : { store_options : { base_filter : { active :"t" } } } }
1180 for(var field in args) {
1181 override[field] = {widgetValue : args[field]};
1184 // push the name of the invoice into the name display field after update
1185 override.inv_ident = dojo.mixin(
1187 {dijitArgs : {onChange :
1191 invoicePane && invoicePane.getFieldValue("provider"),
1192 invoicePane && invoicePane.getFieldValue("receiver")
1195 if (dojo.byId('acq-invoice-summary-name'))
1196 dojo.byId('acq-invoice-summary-name').innerHTML = newVal;
1202 pane = new openils.widget.EditPane({
1206 mode : (inv) ? 'edit' : 'create',
1207 hideActionButtons : true,
1208 overrideWidgetArgs : override,
1209 readOnly : (inv) && openils.Util.isTrue(inv.complete()),
1224 suppressFields : ['id', 'complete']
1228 parentNode.appendChild(pane.domNode);
1233 function createExtraCopies(oncomplete) {
1236 for(var liId in extraCopies) {
1237 var data = extraCopies[liId];
1238 for(var i = 0; i < data.numExtra; i++) {
1239 var lid = new fieldmapper.acqlid();
1242 lid.fund(data.fund);
1243 lid.recv_time('now');
1248 if(lids.length == 0)
1249 return oncomplete();
1251 fieldmapper.standardRequest(
1252 ['open-ils.acq', 'open-ils.acq.lineitem_detail.cud.batch'],
1254 params : [openils.User.authtoken, lids, true],
1255 oncomplete : function(r) {
1256 if(openils.Util.readResponse(r))
1264 function smartSearchSubmitter() {
1265 performSearch(0, !dojo.byId('acq-unified-build-progressively').checked);
1269 openils.Util.addOnLoad(init);