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 : {}};
39 function nodeByName(name, context) {
40 return dojo.query('[name='+name+']', context)[0];
45 attachLi = cgi.param('attach_li');
46 attachPo = cgi.param('attach_po');
48 itemTypes = pcrud.retrieveAll('aiit');
50 if(cgi.param('create')) {
54 fieldmapper.standardRequest(
55 ['open-ils.acq', 'open-ils.acq.invoice.retrieve.authoritative'],
57 params : [openils.User.authtoken, invoiceId],
58 oncomplete : function(r) {
59 invoice = openils.Util.readResponse(r);
66 extraCopiesFund = new openils.widget.AutoFieldWidget({
69 searchFilter : {active : 't'},
70 labelFormat : fundLabelFormat,
71 searchFormat : fundSearchFormat,
72 dijitArgs : {required : true},
73 parentNode : dojo.byId('acq-invoice-extra-copies-fund')
75 extraCopiesFund.build();
78 function renderInvoice() {
80 // in create mode, let the LI or PO render the invoice with seed data
81 if( !(cgi.param('create') && (attachPo || attachLi)) ) {
82 invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), invoice);
85 dojo.byId('acq-invoice-new-item').onclick = function() {
86 var item = new fieldmapper.acqii();
94 if(invoice && openils.Util.isTrue(invoice.complete())) {
96 dojo.forEach( // hide widgets that should not be visible for a completed invoice
97 dojo.query('.hide-complete'),
98 function(node) { openils.Util.hide(node); }
101 new openils.User().getPermOrgList(
102 'ACQ_INVOICE_REOPEN',
104 if(orgs.indexOf(invoice.receiver()) >= 0)
105 openils.Util.show('acq-invoice-reopen-button-wrapper', 'inline');
116 addInvoiceItem(item);
123 addInvoiceEntry(entry);
128 if(attachLi) doAttachLi();
129 if(attachPo) doAttachPo();
132 function doAttachLi() {
134 //var invoiceArgs = {provider : lineitem.provider(), shipper : lineitem.provider()};
135 if(cgi.param('create')) {
137 fieldmapper.standardRequest(
138 ['open-ils.acq', 'open-ils.acq.lineitem.retrieve.authoritative'],
140 params : [openils.User.authtoken, attachLi, {clear_marc:1}],
141 oncomplete : function(r) {
142 var li = openils.Util.readResponse(r);
143 invoicePane = drawInvoicePane(
144 dojo.byId('acq-view-invoice-div'), null,
145 {provider : li.provider(), shipper : li.provider()}
152 var entry = new fieldmapper.acqie();
153 entry.id(virtualId--);
155 entry.lineitem(attachLi);
156 addInvoiceEntry(entry);
159 function doAttachPo() {
161 fieldmapper.standardRequest(
162 ['open-ils.acq', 'open-ils.acq.purchase_order.retrieve'],
165 openils.User.authtoken, attachPo,
166 {flesh_lineitem_ids : true, flesh_po_items : true}
168 oncomplete: function(r) {
169 var po = openils.Util.readResponse(r);
171 if(cgi.param('create')) {
172 // render the invoice using some seed data from the PO
173 var invoiceArgs = {provider : po.provider(), shipper : po.provider()};
174 invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), null, invoiceArgs);
177 dojo.forEach(po.lineitems(),
179 var entry = new fieldmapper.acqie();
180 entry.id(virtualId--);
182 entry.lineitem(lineitem);
183 entry.purchase_order(po);
184 addInvoiceEntry(entry);
188 dojo.forEach(po.po_items(),
190 var item = new fieldmapper.acqii();
191 item.id(virtualId--);
193 item.fund(poItem.fund());
194 item.title(poItem.title());
195 item.author(poItem.author());
196 item.note(poItem.note());
197 item.inv_item_type(poItem.inv_item_type());
198 item.purchase_order(po);
199 item.po_item(poItem);
200 addInvoiceItem(item);
208 function updateTotalCost() {
211 for(var id in widgetRegistry.acqii)
212 if(!widgetRegistry.acqii[id]._object.isdeleted())
213 totalCost += Number(widgetRegistry.acqii[id].cost_billed.getFormattedValue());
214 for(var id in widgetRegistry.acqie)
215 if(!widgetRegistry.acqie[id]._object.isdeleted())
216 totalCost += Number(widgetRegistry.acqie[id].cost_billed.getFormattedValue());
217 totalInvoicedBox.attr('value', totalCost);
220 for(var id in widgetRegistry.acqii)
221 if(!widgetRegistry.acqii[id]._object.isdeleted())
222 totalPaid += Number(widgetRegistry.acqii[id].amount_paid.getFormattedValue());
223 for(var id in widgetRegistry.acqie)
224 if(!widgetRegistry.acqie[id]._object.isdeleted())
225 totalPaid += Number(widgetRegistry.acqie[id].amount_paid.getFormattedValue());
226 totalPaidBox.attr('value', totalPaid);
228 var buttonsDisabled = false;
230 if(totalPaid > totalCost || totalPaid < 0) {
231 openils.Util.addCSSClass(totalPaidBox.domNode, 'acq-invoice-invalid-amount');
232 invoiceSaveButton.attr('disabled', true);
233 invoiceProrateButton.attr('disabled', true);
234 buttonsDisabled = true;
236 openils.Util.removeCSSClass(totalPaidBox.domNode, 'acq-invoice-invalid-amount');
237 invoiceSaveButton.attr('disabled', false);
238 invoiceProrateButton.attr('disabled', false);
242 openils.Util.addCSSClass(totalInvoicedBox.domNode, 'acq-invoice-invalid-amount');
243 invoiceSaveButton.attr('disabled', true);
244 invoiceProrateButton.attr('disabled', true);
246 openils.Util.removeCSSClass(totalInvoicedBox.domNode, 'acq-invoice-invalid-amount');
247 if(!buttonsDisabled) {
248 invoiceSaveButton.attr('disabled', false);
249 invoiceProrateButton.attr('disabled', false);
253 if(totalPaid == totalCost) { // XXX: too rigid?
254 invoiceCloseButton.attr('disabled', false);
256 invoiceCloseButton.attr('disabled', true);
259 balanceOwedBox.attr('value', (totalCost - totalPaid));
263 function registerWidget(obj, field, widget, callback) {
264 var blob = widgetRegistry[obj.classname];
266 blob[obj.id()] = {_object : obj};
267 blob[obj.id()][field] = widget;
270 dojo.connect(w, 'onChange',
276 if(callback) callback(w, ww);
282 function addInvoiceItem(item) {
283 itemTbody = dojo.byId('acq-invoice-item-tbody');
284 if(itemTemplate == null) {
285 itemTemplate = itemTbody.removeChild(dojo.byId('acq-invoice-item-template'));
288 var row = itemTemplate.cloneNode(true);
289 var itemType = itemTypes.filter(function(t) { return (t.code() == item.inv_item_type()) })[0];
292 ['title', 'author', 'cost_billed', 'amount_paid'],
296 if(field == 'title' || field == 'author') {
297 //args = {style : 'width:10em'};
298 } else if(field == 'cost_billed' || field == 'amount_paid') {
299 args = {required : true, style : 'width: 8em'};
304 new openils.widget.AutoFieldWidget({
308 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
310 parentNode : nodeByName(field, row)
317 /* ----------- fund -------------- */
322 labelFormat : fundLabelFormat,
323 searchFormat : fundSearchFormat,
324 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
325 dijitArgs : {required : true},
326 parentNode : nodeByName('fund', row)
329 if(item.fund_debit()) {
330 fundArgs.searchFilter = {'-or' : [{active : 't'}, {id : item.fund()}]};
332 fundArgs.searchFilter = {active : 't'}
333 if(itemType && openils.Util.isTrue(itemType.prorate()))
334 fundArgs.dijitArgs = {disabled : true};
337 var fundWidget = new openils.widget.AutoFieldWidget(fundArgs);
338 registerWidget(item, 'fund', fundWidget);
340 /* ---------- inv_item_type ------------- */
344 // read-only item view for items that were the result of a po-item
345 var po = item.purchase_order();
346 var po_item = item.po_item();
347 var node = nodeByName('inv_item_type', row);
348 var itemType = itemTypes.filter(function(t) { return (t.code() == item.inv_item_type()) })[0];
349 orderDate = (!po.order_date()) ? '' :
350 dojo.date.locale.format(dojo.date.stamp.fromISOString(po.order_date()), {selector:'date'});
352 node.innerHTML = dojo.string.substitute(
353 localeStrings.INVOICE_ITEM_PO_DETAILS,
360 po_item.estimated_cost()
369 new openils.widget.AutoFieldWidget({
371 fmField : 'inv_item_type',
372 parentNode : nodeByName('inv_item_type', row),
373 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
374 dijitArgs : {required : true}
377 // When the inv_item_type is set to prorate=true, don't allow the user the edit the fund
378 // since this charge will be prorated against (potentially) multiple funds
379 dojo.connect(w, 'onChange',
381 if(!item.fund_debit()) {
382 var itemType = itemTypes.filter(function(t) { return (t.code() == w.attr('value')) })[0];
383 if(!itemType) return;
384 if(openils.Util.isTrue(itemType.prorate())) {
385 fundWidget.widget.attr('disabled', true);
386 fundWidget.widget.attr('value', '');
388 fundWidget.widget.attr('disabled', false);
397 nodeByName('delete', row).onclick = function() {
398 var cost = widgetRegistry.acqii[item.id()].cost_billed.getFormattedValue();
399 var msg = dojo.string.substitute(
400 localeStrings.INVOICE_CONFIRM_ITEM_DELETE, [
402 widgetRegistry.acqii[item.id()].inv_item_type.getFormattedValue() || ''
405 if(!confirm(msg)) return;
406 itemTbody.removeChild(row);
407 item.isdeleted(true);
409 delete widgetRegistry.acqii[item.id()];
413 itemTbody.appendChild(row);
417 function updateReceiveLink(li) {
419 return; /* can't do this with unsaved invoices */
421 var link = dojo.byId("acq-view-invoice-receive-link");
422 if (link.onclick) return; /* only need to do this once */
424 /* don't do this if there's nothing receivable on the lineitem */
425 if (li.order_summary().recv_count() + li.order_summary().cancel_count() >=
426 li.order_summary().item_count())
429 openils.Util.show("acq-view-invoice-receive");
430 link.onclick = function() { location.href = oilsBasePath + '/acq/invoice/receive/' + invoiceId; };
433 function addInvoiceEntry(entry) {
435 openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-header'), 'hidden');
436 openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-thead'), 'hidden');
437 openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-tbody'), 'hidden');
439 entryTbody = dojo.byId('acq-invoice-entry-tbody');
440 if(entryTemplate == null) {
441 entryTemplate = entryTbody.removeChild(dojo.byId('acq-invoice-entry-template'));
444 if(dojo.query('[lineitem=' + entry.lineitem() +']', entryTbody)[0])
445 // Is it ever valid to have multiple entries for 1 lineitem in a single invoice?
448 var row = entryTemplate.cloneNode(true);
449 row.setAttribute('lineitem', entry.lineitem());
451 openils.acq.Lineitem.fetchAndRender(
452 entry.lineitem(), {},
455 entry.purchase_order(li.purchase_order());
456 nodeByName('title_details', row).innerHTML = html;
458 updateReceiveLink(li);
461 ['inv_item_count', 'phys_item_count', 'cost_billed', 'amount_paid'],
463 var dijitArgs = {required : true, constraints : {min: 0}, style : 'width:6em'};
464 if(!field.match(/count/)) dijitArgs.style = 'width:9em';
465 if(entry.isnew() && field == 'phys_item_count') {
466 // by default, attempt to pay for all non-canceled and as-of-yet-un-invoiced items
467 var count = Number(li.order_summary().item_count() || 0) -
468 Number(li.order_summary().cancel_count() || 0) -
469 Number(li.order_summary().invoice_count() || 0);
470 if(count < 0) count = 0;
471 dijitArgs.value = count;
476 new openils.widget.AutoFieldWidget({
480 dijitArgs : dijitArgs,
481 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
482 parentNode : nodeByName(field, row)
485 if(field == 'phys_item_count') {
486 dojo.connect(w, 'onChange',
488 // staff entered a higher number in the receive field than was originally ordered
489 // taking into account already invoiced items
490 var extra = Number(this.attr('value')) -
491 (Number(entry.lineitem().item_count()) - Number(entry.lineitem().order_summary().invoice_count()));
493 storeExtraCopies(entry, extra);
505 nodeByName('detach', row).onclick = function() {
506 var cost = widgetRegistry.acqie[entry.id()].cost_billed.getFormattedValue();
508 dojo.forEach(['isbn', 'upc', 'issn'],
510 var val = liMarcAttr(entry.lineitem(), ident);
511 if(val) idents.push(val);
515 var msg = dojo.string.substitute(
516 localeStrings.INVOICE_CONFIRM_ENTRY_DETACH, [
518 liMarcAttr(entry.lineitem(), 'title'),
519 liMarcAttr(entry.lineitem(), 'author'),
523 if(!confirm(msg)) return;
524 entryTbody.removeChild(row);
525 entry.isdeleted(true);
527 delete widgetRegistry.acqie[entry.id()];
531 entryTbody.appendChild(row);
535 function liMarcAttr(lineitem, name) {
536 var attr = lineitem.attributes().filter(
539 attr.attr_type() == 'lineitem_marc_attr_definition' &&
540 attr.attr_name() == name)
544 return (attr) ? attr.attr_value() : '';
547 function saveChanges(doProrate, doClose, doReopen) {
550 saveChangesPartTwo(doProrate, doClose, doReopen);
555 function saveChangesPartTwo(doProrate, doClose, doReopen) {
557 progressDialog.show(true);
560 invoice.complete('f');
565 var updateItems = [];
566 for(var id in widgetRegistry.acqii) {
567 var reg = widgetRegistry.acqii[id];
568 var item = reg._object;
569 if(item.ischanged() || item.isnew() || item.isdeleted()) {
570 updateItems.push(item);
571 if(item.isnew()) item.id(null);
572 for(var field in reg) {
573 if(field != '_object')
574 item[field]( reg[field].getFormattedValue() );
578 if(item.purchase_order() != null && typeof item.purchase_order() == 'object')
579 item.purchase_order( item.purchase_order().id() );
583 var updateEntries = [];
584 for(var id in widgetRegistry.acqie) {
585 var reg = widgetRegistry.acqie[id];
586 var entry = reg._object;
587 if(entry.ischanged() || entry.isnew() || entry.isdeleted()) {
588 entry.lineitem(entry.lineitem().id());
589 entry.purchase_order(entry.purchase_order().id());
590 updateEntries.push(entry);
591 if(entry.isnew()) entry.id(null);
593 for(var field in reg) {
594 if(field != '_object')
595 entry[field]( reg[field].getFormattedValue() );
599 dojo.forEach(['purchase_order', 'lineitem'],
601 if(entry[field]() != null && typeof entry[field]() == 'object')
602 entry[field]( entry[field]().id() );
609 invoice = new fieldmapper.acqinv();
612 invoice.ischanged(true); // for now, just always update
615 dojo.forEach(invoicePane.fieldList,
617 invoice[field.name]( field.widget.getFormattedValue() );
622 invoice.complete('t');
625 fieldmapper.standardRequest(
626 ['open-ils.acq', 'open-ils.acq.invoice.update'],
628 params : [openils.User.authtoken, invoice, updateEntries, updateItems],
629 oncomplete : function(r) {
630 progressDialog.hide();
631 var invoice = openils.Util.readResponse(r);
634 return prorateInvoice(invoice);
635 location.href = oilsBasePath + '/acq/invoice/view/' + invoice.id();
642 function prorateInvoice(invoice) {
643 if(!confirm(localeStrings.INVOICE_CONFIRM_PRORATE)) return;
644 progressDialog.show(true);
646 fieldmapper.standardRequest(
647 ['open-ils.acq', 'open-ils.acq.invoice.apply_prorate'],
649 params : [openils.User.authtoken, invoice.id()],
650 oncomplete : function(r) {
651 progressDialog.hide();
652 var invoice = openils.Util.readResponse(r);
654 location.href = oilsBasePath + '/acq/invoice/view/' + invoice.id();
661 function storeExtraCopies(entry, numExtra) {
663 dojo.byId('acq-invoice-extra-copies-message').innerHTML =
664 dojo.string.substitute(
665 localeStrings.INVOICE_EXTRA_COPIES, [numExtra]);
668 addCopyHandler = dojo.connect(
672 extraCopies[entry.lineitem().id()] = {
674 fund : extraCopiesFund.widget.attr('value')
676 extraItemsDialog.hide();
677 dojo.disconnect(addCopyHandler);
685 widgetRegistry.acqie[entry.id()].phys_item_count.widget.attr('value', '');
686 extraItemsDialog.hide()
690 extraItemsDialog.show();
693 function createExtraCopies(oncomplete) {
696 for(var liId in extraCopies) {
697 var data = extraCopies[liId];
698 for(var i = 0; i < data.numExtra; i++) {
699 var lid = new fieldmapper.acqlid();
703 lid.recv_time('now');
711 fieldmapper.standardRequest(
712 ['open-ils.acq', 'open-ils.acq.lineitem_detail.cud.batch'],
714 params : [openils.User.authtoken, lids, true],
715 oncomplete : function(r) {
716 if(openils.Util.readResponse(r))
725 openils.Util.addOnLoad(init);