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 parentNode : dojo.byId('acq-invoice-extra-copies-fund')
74 extraCopiesFund.build();
77 function renderInvoice() {
79 // in create mode, let the LI or PO render the invoice with seed data
80 if( !(cgi.param('create') && (attachPo || attachLi)) ) {
81 invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), invoice);
84 dojo.byId('acq-invoice-new-item').onclick = function() {
85 var item = new fieldmapper.acqii();
93 if(invoice && openils.Util.isTrue(invoice.complete())) {
95 dojo.forEach( // hide widgets that should not be visible for a completed invoice
96 dojo.query('.hide-complete'),
97 function(node) { openils.Util.hide(node); }
100 new openils.User().getPermOrgList(
101 'ACQ_INVOICE_REOPEN',
103 if(orgs.indexOf(invoice.receiver()) >= 0)
104 openils.Util.show('acq-invoice-reopen-button-wrapper', 'inline');
115 addInvoiceItem(item);
122 addInvoiceEntry(entry);
127 if(attachLi) doAttachLi();
128 if(attachPo) doAttachPo();
131 function doAttachLi() {
133 //var invoiceArgs = {provider : lineitem.provider(), shipper : lineitem.provider()};
134 if(cgi.param('create')) {
135 var invoiceArgs = {};
136 invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), null, invoiceArgs);
138 var entry = new fieldmapper.acqie();
139 entry.id(virtualId--);
141 entry.lineitem(attachLi);
142 addInvoiceEntry(entry);
145 function doAttachPo() {
147 fieldmapper.standardRequest(
148 ['open-ils.acq', 'open-ils.acq.purchase_order.retrieve'],
150 params: [openils.User.authtoken, attachPo, {flesh_lineitem_ids : true}],
151 oncomplete: function(r) {
152 var po = openils.Util.readResponse(r);
154 if(cgi.param('create')) {
155 // render the invoice using some seed data from the PO
156 var invoiceArgs = {provider : po.provider(), shipper : po.provider()};
157 invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), null, invoiceArgs);
160 dojo.forEach(po.lineitems(),
162 var entry = new fieldmapper.acqie();
163 entry.id(virtualId--);
165 entry.lineitem(lineitem);
166 entry.purchase_order(po);
167 addInvoiceEntry(entry);
175 function updateTotalCost() {
178 for(var id in widgetRegistry.acqii)
179 if(!widgetRegistry.acqii[id]._object.isdeleted())
180 totalCost += Number(widgetRegistry.acqii[id].cost_billed.getFormattedValue());
181 for(var id in widgetRegistry.acqie)
182 if(!widgetRegistry.acqie[id]._object.isdeleted())
183 totalCost += Number(widgetRegistry.acqie[id].cost_billed.getFormattedValue());
184 totalInvoicedBox.attr('value', totalCost);
187 for(var id in widgetRegistry.acqii)
188 if(!widgetRegistry.acqii[id]._object.isdeleted())
189 totalPaid += Number(widgetRegistry.acqii[id].amount_paid.getFormattedValue());
190 for(var id in widgetRegistry.acqie)
191 if(!widgetRegistry.acqie[id]._object.isdeleted())
192 totalPaid += Number(widgetRegistry.acqie[id].amount_paid.getFormattedValue());
193 totalPaidBox.attr('value', totalPaid);
195 var buttonsDisabled = false;
197 if(totalPaid > totalCost || totalPaid < 0) {
198 openils.Util.addCSSClass(totalPaidBox.domNode, 'acq-invoice-invalid-amount');
199 invoiceSaveButton.attr('disabled', true);
200 invoiceProrateButton.attr('disabled', true);
201 buttonsDisabled = true;
203 openils.Util.removeCSSClass(totalPaidBox.domNode, 'acq-invoice-invalid-amount');
204 invoiceSaveButton.attr('disabled', false);
205 invoiceProrateButton.attr('disabled', false);
209 openils.Util.addCSSClass(totalInvoicedBox.domNode, 'acq-invoice-invalid-amount');
210 invoiceSaveButton.attr('disabled', true);
211 invoiceProrateButton.attr('disabled', true);
213 openils.Util.removeCSSClass(totalInvoicedBox.domNode, 'acq-invoice-invalid-amount');
214 if(!buttonsDisabled) {
215 invoiceSaveButton.attr('disabled', false);
216 invoiceProrateButton.attr('disabled', false);
220 if(totalPaid == totalCost) { // XXX: too rigid?
221 invoiceCloseButton.attr('disabled', false);
223 invoiceCloseButton.attr('disabled', true);
226 balanceOwedBox.attr('value', (totalCost - totalPaid));
230 function registerWidget(obj, field, widget, callback) {
231 var blob = widgetRegistry[obj.classname];
233 blob[obj.id()] = {_object : obj};
234 blob[obj.id()][field] = widget;
237 dojo.connect(w, 'onChange',
243 if(callback) callback(w, ww);
249 function addInvoiceItem(item) {
250 itemTbody = dojo.byId('acq-invoice-item-tbody');
251 if(itemTemplate == null) {
252 itemTemplate = itemTbody.removeChild(dojo.byId('acq-invoice-item-template'));
255 var row = itemTemplate.cloneNode(true);
256 var itemType = itemTypes.filter(function(t) { return (t.code() == item.inv_item_type()) })[0];
259 ['title', 'author', 'cost_billed', 'amount_paid'],
263 if(field == 'title' || field == 'author') {
264 args = {style : 'width:10em'};
265 } else if(field == 'cost_billed' || field == 'amount_paid') {
266 args = {required : true, style : 'width: 6em'};
271 new openils.widget.AutoFieldWidget({
275 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
277 parentNode : nodeByName(field, row)
284 /* ----------- fund -------------- */
289 labelFormat : fundLabelFormat,
290 searchFormat : fundSearchFormat,
291 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
292 parentNode : nodeByName('fund', row)
295 if(item.fund_debit()) {
296 fundArgs.searchFilter = {'-or' : [{active : 't'}, {id : item.fund()}]};
298 fundArgs.searchFilter = {active : 't'}
299 if(itemType && openils.Util.isTrue(itemType.prorate()))
300 fundArgs.dijitArgs = {disabled : true};
303 var fundWidget = new openils.widget.AutoFieldWidget(fundArgs);
304 registerWidget(item, 'fund', fundWidget);
306 /* ---------- inv_item_type ------------- */
311 new openils.widget.AutoFieldWidget({
313 fmField : 'inv_item_type',
314 parentNode : nodeByName('inv_item_type', row),
315 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
316 dijitArgs : {required : true}
319 // When the inv_item_type is set to prorate=true, don't allow the user the edit the fund
320 // since this charge will be prorated against (potentially) multiple funds
321 dojo.connect(w, 'onChange',
323 if(!item.fund_debit()) {
324 var itemType = itemTypes.filter(function(t) { return (t.code() == w.attr('value')) })[0];
325 if(!itemType) return;
326 if(openils.Util.isTrue(itemType.prorate())) {
327 fundWidget.widget.attr('disabled', true);
328 fundWidget.widget.attr('value', '');
330 fundWidget.widget.attr('disabled', false);
338 nodeByName('delete', row).onclick = function() {
339 var cost = widgetRegistry.acqii[item.id()].cost_billed.getFormattedValue();
340 var msg = dojo.string.substitute(
341 localeStrings.INVOICE_CONFIRM_ITEM_DELETE, [
343 widgetRegistry.acqii[item.id()].inv_item_type.getFormattedValue()
346 if(!confirm(msg)) return;
347 itemTbody.removeChild(row);
348 item.isdeleted(true);
350 delete widgetRegistry.acqii[item.id()];
354 itemTbody.appendChild(row);
358 function addInvoiceEntry(entry) {
360 openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-header'), 'hidden');
361 openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-thead'), 'hidden');
362 openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-tbody'), 'hidden');
364 entryTbody = dojo.byId('acq-invoice-entry-tbody');
365 if(entryTemplate == null) {
366 entryTemplate = entryTbody.removeChild(dojo.byId('acq-invoice-entry-template'));
369 if(dojo.query('[lineitem=' + entry.lineitem() +']', entryTbody)[0])
370 // Is it ever valid to have multiple entries for 1 lineitem in a single invoice?
373 var row = entryTemplate.cloneNode(true);
374 row.setAttribute('lineitem', entry.lineitem());
376 openils.acq.Lineitem.fetchAndRender(
377 entry.lineitem(), {},
380 entry.purchase_order(li.purchase_order());
381 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') dijitArgs.value = numReceived;
393 new openils.widget.AutoFieldWidget({
397 dijitArgs : dijitArgs,
398 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
399 parentNode : nodeByName(field, row)
402 if(field == 'phys_item_count') {
403 dojo.connect(w, 'onChange',
405 // staff entered a higher number in the receive field than was originally ordered
406 if(Number(this.attr('value')) > entry.lineitem().item_count()) {
408 entry.lineitem().id(),
409 Number(this.attr('value')) - entry.lineitem().item_count()
420 nodeByName('detach', row).onclick = function() {
421 var cost = widgetRegistry.acqie[entry.id()].cost_billed.getFormattedValue();
422 var msg = dojo.string.substitute(
423 localeStrings.INVOICE_CONFIRM_ENTRY_DETACH, [
425 liMarcAttr(lineitem, 'title'),
426 liMarcAttr(lineitem, 'author'),
430 if(!confirm(msg)) return;
431 entryTbody.removeChild(row);
432 entry.isdeleted(true);
434 delete widgetRegistry.acqie[entry.id()];
438 entryTbody.appendChild(row);
442 function liMarcAttr(lineitem, name) {
443 var attr = lineitem.attributes().filter(
446 attr.attr_type() == 'lineitem_marc_attr_definition' &&
447 attr.attr_name() == name)
451 return (attr) ? attr.attr_value() : '';
454 function saveChanges(doProrate, doClose, doReopen) {
457 saveChangesPartTwo(doProrate, doClose, doReopen);
462 function saveChangesPartTwo(doProrate, doClose, doReopen) {
464 progressDialog.show(true);
467 invoice.complete('f');
472 var updateItems = [];
473 for(var id in widgetRegistry.acqii) {
474 var reg = widgetRegistry.acqii[id];
475 var item = reg._object;
476 if(item.ischanged() || item.isnew() || item.isdeleted()) {
477 updateItems.push(item);
478 if(item.isnew()) item.id(null);
479 for(var field in reg) {
480 if(field != '_object')
481 item[field]( reg[field].getFormattedValue() );
485 if(item.purchase_order() != null && typeof item.purchase_order() == 'object')
486 item.purchase_order( item.purchase_order().id() );
490 var updateEntries = [];
491 for(var id in widgetRegistry.acqie) {
492 var reg = widgetRegistry.acqie[id];
493 var entry = reg._object;
494 if(entry.ischanged() || entry.isnew() || entry.isdeleted()) {
495 entry.lineitem(entry.lineitem().id());
496 entry.purchase_order(entry.purchase_order().id());
497 updateEntries.push(entry);
498 if(entry.isnew()) entry.id(null);
500 for(var field in reg) {
501 if(field != '_object')
502 entry[field]( reg[field].getFormattedValue() );
506 dojo.forEach(['purchase_order', 'lineitem'],
508 if(entry[field]() != null && typeof entry[field]() == 'object')
509 entry[field]( entry[field]().id() );
516 invoice = new fieldmapper.acqinv();
519 invoice.ischanged(true); // for now, just always update
522 dojo.forEach(invoicePane.fieldList,
524 invoice[field.name]( field.widget.getFormattedValue() );
529 invoice.complete('t');
532 fieldmapper.standardRequest(
533 ['open-ils.acq', 'open-ils.acq.invoice.update'],
535 params : [openils.User.authtoken, invoice, updateEntries, updateItems],
536 oncomplete : function(r) {
537 progressDialog.hide();
538 var invoice = openils.Util.readResponse(r);
541 return prorateInvoice(invoice);
542 location.href = oilsBasePath + '/acq/invoice/view/' + invoice.id();
549 function prorateInvoice(invoice) {
550 if(!confirm(localeStrings.INVOICE_CONFIRM_PRORATE)) return;
551 progressDialog.show(true);
553 fieldmapper.standardRequest(
554 ['open-ils.acq', 'open-ils.acq.invoice.apply_prorate'],
556 params : [openils.User.authtoken, invoice.id()],
557 oncomplete : function(r) {
558 progressDialog.hide();
559 var invoice = openils.Util.readResponse(r);
561 location.href = oilsBasePath + '/acq/invoice/view/' + invoice.id();
568 function storeExtraCopies(liId, numExtra) {
570 dojo.byId('acq-invoice-extra-copies-message').innerHTML =
571 dojo.string.substitute(
572 localeStrings.INVOICE_EXTRA_COPIES, [numExtra]);
575 addCopyHandler = dojo.connect(
579 extraCopies[liId] = {
581 fund : extraCopiesFund.widget.attr('value')
583 extraItemsDialog.hide();
584 dojo.disconnect(addCopyHandler);
591 function() { extraItemsDialog.hide() }
594 extraItemsDialog.show();
597 function createExtraCopies(oncomplete) {
600 for(var liId in extraCopies) {
601 var data = extraCopies[liId];
602 for(var i = 0; i < data.numExtra; i++) {
603 var lid = new fieldmapper.acqlid();
607 lid.recv_time('now');
615 fieldmapper.standardRequest(
616 ['open-ils.acq', 'open-ils.acq.lineitem_detail.cud.batch'],
618 params : [openils.User.authtoken, lids, true],
619 oncomplete : function(r) {
620 if(openils.Util.readResponse(r))
629 openils.Util.addOnLoad(init);