1 dojo.require('dojo.date.locale');
2 dojo.require('dojo.date.stamp');
3 dojo.require('dijit.form.CheckBox');
4 dojo.require('dijit.form.CurrencyTextBox');
5 dojo.require('dijit.form.NumberTextBox');
6 dojo.require('openils.User');
7 dojo.require('openils.Util');
8 dojo.require('openils.CGI');
9 dojo.require('openils.PermaCrud');
10 dojo.require('openils.widget.EditPane');
11 dojo.require('openils.widget.AutoFieldWidget');
12 dojo.require('openils.widget.ProgressDialog');
13 dojo.require('openils.acq.Lineitem');
15 dojo.requireLocalization('openils.acq', 'acq');
16 var localeStrings = dojo.i18n.getLocalization('openils.acq', 'acq');
18 var fundLabelFormat = ['${0} (${1})', 'code', 'year'];
19 var fundSearchFormat = ['${0} (${1})', 'code', 'year'];
21 var cgi = new openils.CGI();
22 var pcrud = new openils.PermaCrud();
37 var widgetRegistry = {acqie : {}, acqii : {}};
40 function nodeByName(name, context) {
41 return dojo.query('[name='+name+']', context)[0];
46 attachLi = cgi.param('attach_li');
47 attachPo = cgi.param('attach_po');
48 focusLineitem = new openils.CGI().param('focus_li');
50 itemTypes = pcrud.retrieveAll('aiit');
52 if(cgi.param('create')) {
56 fieldmapper.standardRequest(
57 ['open-ils.acq', 'open-ils.acq.invoice.retrieve.authoritative'],
59 params : [openils.User.authtoken, invoiceId],
60 oncomplete : function(r) {
61 invoice = openils.Util.readResponse(r);
68 extraCopiesFund = new openils.widget.AutoFieldWidget({
71 searchFilter : {active : 't'},
72 labelFormat : fundLabelFormat,
73 searchFormat : fundSearchFormat,
74 dijitArgs : {required : true},
75 parentNode : dojo.byId('acq-invoice-extra-copies-fund')
77 extraCopiesFund.build();
80 function renderInvoice() {
82 // in create mode, let the LI or PO render the invoice with seed data
83 if( !(cgi.param('create') && (attachPo || attachLi)) ) {
84 invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), invoice);
87 dojo.byId('acq-invoice-new-item').onclick = function() {
88 var item = new fieldmapper.acqii();
96 if(invoice && openils.Util.isTrue(invoice.complete())) {
98 dojo.forEach( // hide widgets that should not be visible for a completed invoice
99 dojo.query('.hide-complete'),
100 function(node) { openils.Util.hide(node); }
103 new openils.User().getPermOrgList(
104 'ACQ_INVOICE_REOPEN',
106 if(orgs.indexOf(invoice.receiver()) >= 0)
107 openils.Util.show('acq-invoice-reopen-button-wrapper', 'inline');
118 addInvoiceItem(item);
125 addInvoiceEntry(entry);
130 if(attachLi) doAttachLi();
131 if(attachPo) doAttachPo();
134 function doAttachLi() {
136 //var invoiceArgs = {provider : lineitem.provider(), shipper : lineitem.provider()};
137 if(cgi.param('create')) {
139 fieldmapper.standardRequest(
140 ['open-ils.acq', 'open-ils.acq.lineitem.retrieve.authoritative'],
142 params : [openils.User.authtoken, attachLi, {clear_marc:1}],
143 oncomplete : function(r) {
144 var li = openils.Util.readResponse(r);
145 invoicePane = drawInvoicePane(
146 dojo.byId('acq-view-invoice-div'), null,
147 {provider : li.provider(), shipper : li.provider()}
154 var entry = new fieldmapper.acqie();
155 entry.id(virtualId--);
157 entry.lineitem(attachLi);
158 addInvoiceEntry(entry);
161 function doAttachPo() {
163 fieldmapper.standardRequest(
164 ['open-ils.acq', 'open-ils.acq.purchase_order.retrieve'],
167 openils.User.authtoken, attachPo,
168 {flesh_lineitem_ids : true, flesh_po_items : true}
170 oncomplete: function(r) {
171 var po = openils.Util.readResponse(r);
173 if(cgi.param('create')) {
174 // render the invoice using some seed data from the PO
175 var invoiceArgs = {provider : po.provider(), shipper : po.provider()};
176 invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), null, invoiceArgs);
179 dojo.forEach(po.lineitems(),
181 var entry = new fieldmapper.acqie();
182 entry.id(virtualId--);
184 entry.lineitem(lineitem);
185 entry.purchase_order(po);
186 addInvoiceEntry(entry);
190 dojo.forEach(po.po_items(),
192 var item = new fieldmapper.acqii();
193 item.id(virtualId--);
195 item.fund(poItem.fund());
196 item.title(poItem.title());
197 item.author(poItem.author());
198 item.note(poItem.note());
199 item.inv_item_type(poItem.inv_item_type());
200 item.purchase_order(po);
201 item.po_item(poItem);
202 addInvoiceItem(item);
210 function updateTotalCost() {
213 for(var id in widgetRegistry.acqii)
214 if(!widgetRegistry.acqii[id]._object.isdeleted())
215 totalCost += Number(widgetRegistry.acqii[id].cost_billed.getFormattedValue());
216 for(var id in widgetRegistry.acqie)
217 if(!widgetRegistry.acqie[id]._object.isdeleted())
218 totalCost += Number(widgetRegistry.acqie[id].cost_billed.getFormattedValue());
219 totalInvoicedBox.attr('value', totalCost);
222 for(var id in widgetRegistry.acqii)
223 if(!widgetRegistry.acqii[id]._object.isdeleted())
224 totalPaid += Number(widgetRegistry.acqii[id].amount_paid.getFormattedValue());
225 for(var id in widgetRegistry.acqie)
226 if(!widgetRegistry.acqie[id]._object.isdeleted())
227 totalPaid += Number(widgetRegistry.acqie[id].amount_paid.getFormattedValue());
228 totalPaidBox.attr('value', totalPaid);
230 var buttonsDisabled = false;
232 if(totalPaid > totalCost || totalPaid < 0) {
233 openils.Util.addCSSClass(totalPaidBox.domNode, 'acq-invoice-invalid-amount');
234 invoiceSaveButton.attr('disabled', true);
235 invoiceProrateButton.attr('disabled', true);
236 buttonsDisabled = true;
238 openils.Util.removeCSSClass(totalPaidBox.domNode, 'acq-invoice-invalid-amount');
239 invoiceSaveButton.attr('disabled', false);
240 invoiceProrateButton.attr('disabled', false);
244 openils.Util.addCSSClass(totalInvoicedBox.domNode, 'acq-invoice-invalid-amount');
245 invoiceSaveButton.attr('disabled', true);
246 invoiceProrateButton.attr('disabled', true);
248 openils.Util.removeCSSClass(totalInvoicedBox.domNode, 'acq-invoice-invalid-amount');
249 if(!buttonsDisabled) {
250 invoiceSaveButton.attr('disabled', false);
251 invoiceProrateButton.attr('disabled', false);
255 if(totalPaid == totalCost) { // XXX: too rigid?
256 invoiceCloseButton.attr('disabled', false);
258 invoiceCloseButton.attr('disabled', true);
261 balanceOwedBox.attr('value', (totalCost - totalPaid));
265 function registerWidget(obj, field, widget, callback) {
266 var blob = widgetRegistry[obj.classname];
268 blob[obj.id()] = {_object : obj};
269 blob[obj.id()][field] = widget;
272 dojo.connect(w, 'onChange',
278 if(callback) callback(w, ww);
284 function addInvoiceItem(item) {
285 itemTbody = dojo.byId('acq-invoice-item-tbody');
286 if(itemTemplate == null) {
287 itemTemplate = itemTbody.removeChild(dojo.byId('acq-invoice-item-template'));
290 var row = itemTemplate.cloneNode(true);
291 var itemType = itemTypes.filter(function(t) { return (t.code() == item.inv_item_type()) })[0];
294 ['title', 'author', 'cost_billed', 'amount_paid'],
298 if(field == 'title' || field == 'author') {
299 //args = {style : 'width:10em'};
300 } else if(field == 'cost_billed' || field == 'amount_paid') {
301 args = {required : true, style : 'width: 8em'};
306 new openils.widget.AutoFieldWidget({
310 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
312 parentNode : nodeByName(field, row)
319 /* ----------- fund -------------- */
324 labelFormat : fundLabelFormat,
325 searchFormat : fundSearchFormat,
326 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
327 dijitArgs : {required : true},
328 parentNode : nodeByName('fund', row)
331 if(item.fund_debit()) {
332 fundArgs.searchFilter = {'-or' : [{active : 't'}, {id : item.fund()}]};
334 fundArgs.searchFilter = {active : 't'}
335 if(itemType && openils.Util.isTrue(itemType.prorate()))
336 fundArgs.dijitArgs = {disabled : true};
339 var fundWidget = new openils.widget.AutoFieldWidget(fundArgs);
340 registerWidget(item, 'fund', fundWidget);
342 /* ---------- inv_item_type ------------- */
346 // read-only item view for items that were the result of a po-item
347 var po = item.purchase_order();
348 var po_item = item.po_item();
349 var node = nodeByName('inv_item_type', row);
350 var itemType = itemTypes.filter(function(t) { return (t.code() == item.inv_item_type()) })[0];
351 orderDate = (!po.order_date()) ? '' :
352 dojo.date.locale.format(dojo.date.stamp.fromISOString(po.order_date()), {selector:'date'});
354 node.innerHTML = dojo.string.substitute(
355 localeStrings.INVOICE_ITEM_PO_DETAILS,
362 po_item.estimated_cost()
371 new openils.widget.AutoFieldWidget({
373 fmField : 'inv_item_type',
374 parentNode : nodeByName('inv_item_type', row),
375 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
376 dijitArgs : {required : true}
379 // When the inv_item_type is set to prorate=true, don't allow the user the edit the fund
380 // since this charge will be prorated against (potentially) multiple funds
381 dojo.connect(w, 'onChange',
383 if(!item.fund_debit()) {
384 var itemType = itemTypes.filter(function(t) { return (t.code() == w.attr('value')) })[0];
385 if(!itemType) return;
386 if(openils.Util.isTrue(itemType.prorate())) {
387 fundWidget.widget.attr('disabled', true);
388 fundWidget.widget.attr('value', '');
390 fundWidget.widget.attr('disabled', false);
399 nodeByName('delete', row).onclick = function() {
400 var cost = widgetRegistry.acqii[item.id()].cost_billed.getFormattedValue();
401 var msg = dojo.string.substitute(
402 localeStrings.INVOICE_CONFIRM_ITEM_DELETE, [
404 widgetRegistry.acqii[item.id()].inv_item_type.getFormattedValue() || ''
407 if(!confirm(msg)) return;
408 itemTbody.removeChild(row);
409 item.isdeleted(true);
411 delete widgetRegistry.acqii[item.id()];
415 itemTbody.appendChild(row);
419 function updateReceiveLink(li) {
421 return; /* can't do this with unsaved invoices */
423 var link = dojo.byId("acq-view-invoice-receive-link");
424 if (link.onclick) return; /* only need to do this once */
426 /* don't do this if there's nothing receivable on the lineitem */
427 if (li.order_summary().recv_count() + li.order_summary().cancel_count() >=
428 li.order_summary().item_count())
431 openils.Util.show("acq-view-invoice-receive");
432 link.onclick = function() { location.href = oilsBasePath + '/acq/invoice/receive/' + invoiceId; };
436 * Ensures focusLineitem is in view and causes a brief
437 * border around the lineitem to come to life then fade.
440 if (!focusLineitem) return;
442 // set during addLineitem()
443 var node = dojo.byId('li-title-ref-' + focusLineitem);
445 console.log('focus: li-title-ref-' + focusLineitem + ' : ' + node);
447 // LI may not yet be rendered
450 console.log('focusing ' + focusLineitem);
452 // prevent numerous re-focuses
453 focusLineitem = null;
455 // causes the full row to be visible
456 dijit.scrollIntoView(node);
458 dojo.require('dojox.fx');
462 dojox.fx.highlight({color : '#BB4433', node : node, duration : 2000}).play();
468 function addInvoiceEntry(entry) {
470 openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-header'), 'hidden');
471 openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-thead'), 'hidden');
472 openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-tbody'), 'hidden');
474 entryTbody = dojo.byId('acq-invoice-entry-tbody');
475 if(entryTemplate == null) {
476 entryTemplate = entryTbody.removeChild(dojo.byId('acq-invoice-entry-template'));
479 if(dojo.query('[lineitem=' + entry.lineitem() +']', entryTbody)[0])
480 // Is it ever valid to have multiple entries for 1 lineitem in a single invoice?
483 var row = entryTemplate.cloneNode(true);
484 row.setAttribute('lineitem', entry.lineitem());
486 openils.acq.Lineitem.fetchAndRender(
487 entry.lineitem(), {},
490 entry.purchase_order(li.purchase_order());
491 nodeByName('title_details', row).innerHTML = html;
493 nodeByName('title_details', row).parentNode.id = 'li-title-ref-' + li.id();
494 console.log(dojo.byId('li-title-ref-' + li.id()));
496 updateReceiveLink(li);
499 ['inv_item_count', 'phys_item_count', 'cost_billed', 'amount_paid'],
501 var dijitArgs = {required : true, constraints : {min: 0}, style : 'width:6em'};
502 if(!field.match(/count/)) dijitArgs.style = 'width:9em';
503 if(entry.isnew() && field == 'phys_item_count') {
504 // by default, attempt to pay for all non-canceled and as-of-yet-un-invoiced items
505 var count = Number(li.order_summary().item_count() || 0) -
506 Number(li.order_summary().cancel_count() || 0) -
507 Number(li.order_summary().invoice_count() || 0);
508 if(count < 0) count = 0;
509 dijitArgs.value = count;
514 new openils.widget.AutoFieldWidget({
518 dijitArgs : dijitArgs,
519 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
520 parentNode : nodeByName(field, row)
523 if(field == 'phys_item_count') {
524 dojo.connect(w, 'onChange',
526 // staff entered a higher number in the receive field than was originally ordered
527 // taking into account already invoiced items
528 var extra = Number(this.attr('value')) -
529 (Number(entry.lineitem().item_count()) - Number(entry.lineitem().order_summary().invoice_count()));
531 storeExtraCopies(entry, extra);
541 if (focusLineitem == li.id())
546 nodeByName('detach', row).onclick = function() {
547 var cost = widgetRegistry.acqie[entry.id()].cost_billed.getFormattedValue();
549 dojo.forEach(['isbn', 'upc', 'issn'],
551 var val = liMarcAttr(entry.lineitem(), ident);
552 if(val) idents.push(val);
556 var msg = dojo.string.substitute(
557 localeStrings.INVOICE_CONFIRM_ENTRY_DETACH, [
559 liMarcAttr(entry.lineitem(), 'title'),
560 liMarcAttr(entry.lineitem(), 'author'),
564 if(!confirm(msg)) return;
565 entryTbody.removeChild(row);
566 entry.isdeleted(true);
568 delete widgetRegistry.acqie[entry.id()];
572 entryTbody.appendChild(row);
576 function liMarcAttr(lineitem, name) {
577 var attr = lineitem.attributes().filter(
580 attr.attr_type() == 'lineitem_marc_attr_definition' &&
581 attr.attr_name() == name)
585 return (attr) ? attr.attr_value() : '';
588 function saveChanges(doProrate, doClose, doReopen) {
591 saveChangesPartTwo(doProrate, doClose, doReopen);
596 function saveChangesPartTwo(doProrate, doClose, doReopen) {
598 progressDialog.show(true);
601 invoice.complete('f');
606 var updateItems = [];
607 for(var id in widgetRegistry.acqii) {
608 var reg = widgetRegistry.acqii[id];
609 var item = reg._object;
610 if(item.ischanged() || item.isnew() || item.isdeleted()) {
611 updateItems.push(item);
612 if(item.isnew()) item.id(null);
613 for(var field in reg) {
614 if(field != '_object')
615 item[field]( reg[field].getFormattedValue() );
619 if(item.purchase_order() != null && typeof item.purchase_order() == 'object')
620 item.purchase_order( item.purchase_order().id() );
624 var updateEntries = [];
625 for(var id in widgetRegistry.acqie) {
626 var reg = widgetRegistry.acqie[id];
627 var entry = reg._object;
628 if(entry.ischanged() || entry.isnew() || entry.isdeleted()) {
629 entry.lineitem(entry.lineitem().id());
630 entry.purchase_order(entry.purchase_order().id());
631 updateEntries.push(entry);
632 if(entry.isnew()) entry.id(null);
634 for(var field in reg) {
635 if(field != '_object')
636 entry[field]( reg[field].getFormattedValue() );
640 dojo.forEach(['purchase_order', 'lineitem'],
642 if(entry[field]() != null && typeof entry[field]() == 'object')
643 entry[field]( entry[field]().id() );
650 invoice = new fieldmapper.acqinv();
653 invoice.ischanged(true); // for now, just always update
656 dojo.forEach(invoicePane.fieldList,
658 invoice[field.name]( field.widget.getFormattedValue() );
663 invoice.complete('t');
666 fieldmapper.standardRequest(
667 ['open-ils.acq', 'open-ils.acq.invoice.update'],
669 params : [openils.User.authtoken, invoice, updateEntries, updateItems],
670 oncomplete : function(r) {
671 progressDialog.hide();
672 var invoice = openils.Util.readResponse(r);
675 return prorateInvoice(invoice);
676 location.href = oilsBasePath + '/acq/invoice/view/' + invoice.id();
683 function prorateInvoice(invoice) {
684 if(!confirm(localeStrings.INVOICE_CONFIRM_PRORATE)) return;
685 progressDialog.show(true);
687 fieldmapper.standardRequest(
688 ['open-ils.acq', 'open-ils.acq.invoice.apply_prorate'],
690 params : [openils.User.authtoken, invoice.id()],
691 oncomplete : function(r) {
692 progressDialog.hide();
693 var invoice = openils.Util.readResponse(r);
695 location.href = oilsBasePath + '/acq/invoice/view/' + invoice.id();
702 function storeExtraCopies(entry, numExtra) {
704 dojo.byId('acq-invoice-extra-copies-message').innerHTML =
705 dojo.string.substitute(
706 localeStrings.INVOICE_EXTRA_COPIES, [numExtra]);
709 addCopyHandler = dojo.connect(
713 extraCopies[entry.lineitem().id()] = {
715 fund : extraCopiesFund.widget.attr('value')
717 extraItemsDialog.hide();
718 dojo.disconnect(addCopyHandler);
726 widgetRegistry.acqie[entry.id()].phys_item_count.widget.attr('value', '');
727 extraItemsDialog.hide()
731 extraItemsDialog.show();
734 function createExtraCopies(oncomplete) {
737 for(var liId in extraCopies) {
738 var data = extraCopies[liId];
739 for(var i = 0; i < data.numExtra; i++) {
740 var lid = new fieldmapper.acqlid();
744 lid.recv_time('now');
752 fieldmapper.standardRequest(
753 ['open-ils.acq', 'open-ils.acq.lineitem_detail.cud.batch'],
755 params : [openils.User.authtoken, lids, true],
756 oncomplete : function(r) {
757 if(openils.Util.readResponse(r))
766 openils.Util.addOnLoad(init);