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'],
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')) {
136 var invoiceArgs = {};
137 invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), null, invoiceArgs);
139 var entry = new fieldmapper.acqie();
140 entry.id(virtualId--);
142 entry.lineitem(attachLi);
143 addInvoiceEntry(entry);
146 function doAttachPo() {
148 fieldmapper.standardRequest(
149 ['open-ils.acq', 'open-ils.acq.purchase_order.retrieve'],
151 params: [openils.User.authtoken, attachPo, {flesh_lineitem_ids : true}],
152 oncomplete: function(r) {
153 var po = openils.Util.readResponse(r);
155 if(cgi.param('create')) {
156 // render the invoice using some seed data from the PO
157 var invoiceArgs = {provider : po.provider(), shipper : po.provider()};
158 invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), null, invoiceArgs);
161 dojo.forEach(po.lineitems(),
163 var entry = new fieldmapper.acqie();
164 entry.id(virtualId--);
166 entry.lineitem(lineitem);
167 entry.purchase_order(po);
168 addInvoiceEntry(entry);
176 function updateTotalCost() {
179 for(var id in widgetRegistry.acqii)
180 if(!widgetRegistry.acqii[id]._object.isdeleted())
181 totalCost += Number(widgetRegistry.acqii[id].cost_billed.getFormattedValue());
182 for(var id in widgetRegistry.acqie)
183 if(!widgetRegistry.acqie[id]._object.isdeleted())
184 totalCost += Number(widgetRegistry.acqie[id].cost_billed.getFormattedValue());
185 totalInvoicedBox.attr('value', totalCost);
188 for(var id in widgetRegistry.acqii)
189 if(!widgetRegistry.acqii[id]._object.isdeleted())
190 totalPaid += Number(widgetRegistry.acqii[id].amount_paid.getFormattedValue());
191 for(var id in widgetRegistry.acqie)
192 if(!widgetRegistry.acqie[id]._object.isdeleted())
193 totalPaid += Number(widgetRegistry.acqie[id].amount_paid.getFormattedValue());
194 totalPaidBox.attr('value', totalPaid);
196 var buttonsDisabled = false;
198 if(totalPaid > totalCost || totalPaid < 0) {
199 openils.Util.addCSSClass(totalPaidBox.domNode, 'acq-invoice-invalid-amount');
200 invoiceSaveButton.attr('disabled', true);
201 invoiceProrateButton.attr('disabled', true);
202 buttonsDisabled = true;
204 openils.Util.removeCSSClass(totalPaidBox.domNode, 'acq-invoice-invalid-amount');
205 invoiceSaveButton.attr('disabled', false);
206 invoiceProrateButton.attr('disabled', false);
210 openils.Util.addCSSClass(totalInvoicedBox.domNode, 'acq-invoice-invalid-amount');
211 invoiceSaveButton.attr('disabled', true);
212 invoiceProrateButton.attr('disabled', true);
214 openils.Util.removeCSSClass(totalInvoicedBox.domNode, 'acq-invoice-invalid-amount');
215 if(!buttonsDisabled) {
216 invoiceSaveButton.attr('disabled', false);
217 invoiceProrateButton.attr('disabled', false);
221 if(totalPaid == totalCost) { // XXX: too rigid?
222 invoiceCloseButton.attr('disabled', false);
224 invoiceCloseButton.attr('disabled', true);
227 balanceOwedBox.attr('value', (totalCost - totalPaid));
231 function registerWidget(obj, field, widget, callback) {
232 var blob = widgetRegistry[obj.classname];
234 blob[obj.id()] = {_object : obj};
235 blob[obj.id()][field] = widget;
238 dojo.connect(w, 'onChange',
244 if(callback) callback(w, ww);
250 function addInvoiceItem(item) {
251 itemTbody = dojo.byId('acq-invoice-item-tbody');
252 if(itemTemplate == null) {
253 itemTemplate = itemTbody.removeChild(dojo.byId('acq-invoice-item-template'));
256 var row = itemTemplate.cloneNode(true);
257 var itemType = itemTypes.filter(function(t) { return (t.code() == item.inv_item_type()) })[0];
260 ['title', 'author', 'cost_billed', 'amount_paid'],
264 if(field == 'title' || field == 'author') {
265 args = {style : 'width:10em'};
266 } else if(field == 'cost_billed' || field == 'amount_paid') {
267 args = {required : true, style : 'width: 6em'};
272 new openils.widget.AutoFieldWidget({
276 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
278 parentNode : nodeByName(field, row)
285 /* ----------- fund -------------- */
290 labelFormat : fundLabelFormat,
291 searchFormat : fundSearchFormat,
292 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
293 dijitArgs : {required : true},
294 parentNode : nodeByName('fund', row)
297 if(item.fund_debit()) {
298 fundArgs.searchFilter = {'-or' : [{active : 't'}, {id : item.fund()}]};
300 fundArgs.searchFilter = {active : 't'}
301 if(itemType && openils.Util.isTrue(itemType.prorate()))
302 fundArgs.dijitArgs = {disabled : true};
305 var fundWidget = new openils.widget.AutoFieldWidget(fundArgs);
306 registerWidget(item, 'fund', fundWidget);
308 /* ---------- inv_item_type ------------- */
313 new openils.widget.AutoFieldWidget({
315 fmField : 'inv_item_type',
316 parentNode : nodeByName('inv_item_type', row),
317 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
318 dijitArgs : {required : true}
321 // When the inv_item_type is set to prorate=true, don't allow the user the edit the fund
322 // since this charge will be prorated against (potentially) multiple funds
323 dojo.connect(w, 'onChange',
325 if(!item.fund_debit()) {
326 var itemType = itemTypes.filter(function(t) { return (t.code() == w.attr('value')) })[0];
327 if(!itemType) return;
328 if(openils.Util.isTrue(itemType.prorate())) {
329 fundWidget.widget.attr('disabled', true);
330 fundWidget.widget.attr('value', '');
332 fundWidget.widget.attr('disabled', false);
340 nodeByName('delete', row).onclick = function() {
341 var cost = widgetRegistry.acqii[item.id()].cost_billed.getFormattedValue();
342 var msg = dojo.string.substitute(
343 localeStrings.INVOICE_CONFIRM_ITEM_DELETE, [
345 widgetRegistry.acqii[item.id()].inv_item_type.getFormattedValue()
348 if(!confirm(msg)) return;
349 itemTbody.removeChild(row);
350 item.isdeleted(true);
352 delete widgetRegistry.acqii[item.id()];
356 itemTbody.appendChild(row);
360 function addInvoiceEntry(entry) {
362 openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-header'), 'hidden');
363 openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-thead'), 'hidden');
364 openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-tbody'), 'hidden');
366 entryTbody = dojo.byId('acq-invoice-entry-tbody');
367 if(entryTemplate == null) {
368 entryTemplate = entryTbody.removeChild(dojo.byId('acq-invoice-entry-template'));
371 if(dojo.query('[lineitem=' + entry.lineitem() +']', entryTbody)[0])
372 // Is it ever valid to have multiple entries for 1 lineitem in a single invoice?
375 var row = entryTemplate.cloneNode(true);
376 row.setAttribute('lineitem', entry.lineitem());
378 openils.acq.Lineitem.fetchAndRender(
379 entry.lineitem(), {},
382 entry.purchase_order(li.purchase_order());
383 nodeByName('title_details', row).innerHTML = html;
386 ['inv_item_count', 'phys_item_count', 'cost_billed', 'amount_paid'],
388 var dijitArgs = {required : true, constraints : {min: 0}, style : 'width:6em'};
389 if(entry.isnew() && field == 'phys_item_count') {
390 // by default, attempt to pay for all received and as-of-yet-un-invoiced items
391 dijitArgs.value = (Number(li.order_summary().recv_count()) - Number(li.order_summary().invoice_count())) || 0;
396 new openils.widget.AutoFieldWidget({
400 dijitArgs : dijitArgs,
401 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
402 parentNode : nodeByName(field, row)
405 if(field == 'phys_item_count') {
406 dojo.connect(w, 'onChange',
408 // staff entered a higher number in the receive field than was originally ordered
409 // taking into account already invoiced items
410 var extra = Number(this.attr('value')) -
411 (Number(entry.lineitem().item_count()) - Number(entry.lineitem().order_summary().invoice_count()));
413 storeExtraCopies(entry, extra);
425 nodeByName('detach', row).onclick = function() {
426 var cost = widgetRegistry.acqie[entry.id()].cost_billed.getFormattedValue();
427 var msg = dojo.string.substitute(
428 localeStrings.INVOICE_CONFIRM_ENTRY_DETACH, [
430 liMarcAttr(lineitem, 'title'),
431 liMarcAttr(lineitem, 'author'),
435 if(!confirm(msg)) return;
436 entryTbody.removeChild(row);
437 entry.isdeleted(true);
439 delete widgetRegistry.acqie[entry.id()];
443 entryTbody.appendChild(row);
447 function liMarcAttr(lineitem, name) {
448 var attr = lineitem.attributes().filter(
451 attr.attr_type() == 'lineitem_marc_attr_definition' &&
452 attr.attr_name() == name)
456 return (attr) ? attr.attr_value() : '';
459 function saveChanges(doProrate, doClose, doReopen) {
462 saveChangesPartTwo(doProrate, doClose, doReopen);
467 function saveChangesPartTwo(doProrate, doClose, doReopen) {
469 progressDialog.show(true);
472 invoice.complete('f');
477 var updateItems = [];
478 for(var id in widgetRegistry.acqii) {
479 var reg = widgetRegistry.acqii[id];
480 var item = reg._object;
481 if(item.ischanged() || item.isnew() || item.isdeleted()) {
482 updateItems.push(item);
483 if(item.isnew()) item.id(null);
484 for(var field in reg) {
485 if(field != '_object')
486 item[field]( reg[field].getFormattedValue() );
490 if(item.purchase_order() != null && typeof item.purchase_order() == 'object')
491 item.purchase_order( item.purchase_order().id() );
495 var updateEntries = [];
496 for(var id in widgetRegistry.acqie) {
497 var reg = widgetRegistry.acqie[id];
498 var entry = reg._object;
499 if(entry.ischanged() || entry.isnew() || entry.isdeleted()) {
500 entry.lineitem(entry.lineitem().id());
501 entry.purchase_order(entry.purchase_order().id());
502 updateEntries.push(entry);
503 if(entry.isnew()) entry.id(null);
505 for(var field in reg) {
506 if(field != '_object')
507 entry[field]( reg[field].getFormattedValue() );
511 dojo.forEach(['purchase_order', 'lineitem'],
513 if(entry[field]() != null && typeof entry[field]() == 'object')
514 entry[field]( entry[field]().id() );
521 invoice = new fieldmapper.acqinv();
524 invoice.ischanged(true); // for now, just always update
527 dojo.forEach(invoicePane.fieldList,
529 invoice[field.name]( field.widget.getFormattedValue() );
534 invoice.complete('t');
537 fieldmapper.standardRequest(
538 ['open-ils.acq', 'open-ils.acq.invoice.update'],
540 params : [openils.User.authtoken, invoice, updateEntries, updateItems],
541 oncomplete : function(r) {
542 progressDialog.hide();
543 var invoice = openils.Util.readResponse(r);
546 return prorateInvoice(invoice);
547 location.href = oilsBasePath + '/acq/invoice/view/' + invoice.id();
554 function prorateInvoice(invoice) {
555 if(!confirm(localeStrings.INVOICE_CONFIRM_PRORATE)) return;
556 progressDialog.show(true);
558 fieldmapper.standardRequest(
559 ['open-ils.acq', 'open-ils.acq.invoice.apply_prorate'],
561 params : [openils.User.authtoken, invoice.id()],
562 oncomplete : function(r) {
563 progressDialog.hide();
564 var invoice = openils.Util.readResponse(r);
566 location.href = oilsBasePath + '/acq/invoice/view/' + invoice.id();
573 function storeExtraCopies(entry, numExtra) {
575 dojo.byId('acq-invoice-extra-copies-message').innerHTML =
576 dojo.string.substitute(
577 localeStrings.INVOICE_EXTRA_COPIES, [numExtra]);
580 addCopyHandler = dojo.connect(
584 extraCopies[entry.lineitem().id()] = {
586 fund : extraCopiesFund.widget.attr('value')
588 extraItemsDialog.hide();
589 dojo.disconnect(addCopyHandler);
597 widgetRegistry.acqie[entry.id()].phys_item_count.widget.attr('value', '');
598 extraItemsDialog.hide()
602 extraItemsDialog.show();
605 function createExtraCopies(oncomplete) {
608 for(var liId in extraCopies) {
609 var data = extraCopies[liId];
610 for(var i = 0; i < data.numExtra; i++) {
611 var lid = new fieldmapper.acqlid();
615 lid.recv_time('now');
623 fieldmapper.standardRequest(
624 ['open-ils.acq', 'open-ils.acq.lineitem_detail.cud.batch'],
626 params : [openils.User.authtoken, lids, true],
627 oncomplete : function(r) {
628 if(openils.Util.readResponse(r))
637 openils.Util.addOnLoad(init);