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');
14 dojo.requireLocalization('openils.acq', 'acq');
15 var localeStrings = dojo.i18n.getLocalization('openils.acq', 'acq');
17 var fundLabelFormat = ['${0} (${1})', 'code', 'year'];
18 var fundSearchFormat = ['${0} (${1})', 'code', 'year'];
20 var cgi = new openils.CGI();
21 var pcrud = new openils.PermaCrud();
34 var widgetRegistry = {acqie : {}, acqii : {}};
36 function nodeByName(name, context) {
37 return dojo.query('[name='+name+']', context)[0];
42 attachLi = cgi.param('attach_li');
43 attachPo = cgi.param('attach_po');
45 itemTypes = pcrud.retrieveAll('aiit');
47 if(cgi.param('create')) {
51 fieldmapper.standardRequest(
52 ['open-ils.acq', 'open-ils.acq.invoice.retrieve'],
54 params : [openils.User.authtoken, invoiceId],
55 oncomplete : function(r) {
56 invoice = openils.Util.readResponse(r);
64 function renderInvoice() {
66 // in create mode, let the LI or PO render the invoice with seed data
67 if( !(cgi.param('create') && (attachPo || attachLi)) ) {
68 invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), invoice);
71 dojo.byId('acq-invoice-new-item').onclick = function() {
72 var item = new fieldmapper.acqii();
80 if(invoice && openils.Util.isTrue(invoice.complete())) {
82 dojo.forEach( // hide widgets that should not be visible for a completed invoice
83 dojo.query('.hide-complete'),
84 function(node) { openils.Util.hide(node); }
87 new openils.User().getPermOrgList(
90 if(orgs.indexOf(invoice.receiver()) >= 0)
91 openils.Util.show('acq-invoice-reopen-button-wrapper', 'inline');
102 addInvoiceItem(item);
109 addInvoiceEntry(entry);
114 if(attachLi) doAttachLi();
115 if(attachPo) doAttachPo();
118 function doAttachLi() {
120 fieldmapper.standardRequest(
121 ["open-ils.acq", "open-ils.acq.lineitem.retrieve"], {
123 params: [openils.User.authtoken, attachLi, {
127 flesh_li_details : true,
128 flesh_fund_debit : true
130 oncomplete: function(r) {
131 lineitem = openils.Util.readResponse(r);
133 if(cgi.param('create')) {
134 // render the invoice using some seed data from the Lineitem
135 var invoiceArgs = {provider : lineitem.provider(), shipper : lineitem.provider()};
136 invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), null, invoiceArgs);
139 var entry = new fieldmapper.acqie();
140 entry.id(virtualId--);
142 entry.lineitem(lineitem);
143 entry.purchase_order(lineitem.purchase_order());
144 addInvoiceEntry(entry);
150 function doAttachPo() {
151 fieldmapper.standardRequest(
152 ['open-ils.acq', 'open-ils.acq.purchase_order.retrieve'],
154 params: [openils.User.authtoken, attachPo, {
155 flesh_lineitems : true,
157 flesh_lineitem_details : true,
158 flesh_fund_debit : true
160 oncomplete: function(r) {
161 var po = openils.Util.readResponse(r);
163 if(cgi.param('create')) {
164 // render the invoice using some seed data from the PO
165 var invoiceArgs = {provider : po.provider(), shipper : po.provider()};
166 invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), null, invoiceArgs);
169 dojo.forEach(po.lineitems(),
171 var entry = new fieldmapper.acqie();
172 entry.id(virtualId--);
174 entry.lineitem(lineitem);
175 entry.purchase_order(po);
176 lineitem.purchase_order(po);
177 addInvoiceEntry(entry);
185 function updateTotalCost() {
188 for(var id in widgetRegistry.acqii)
189 if(!widgetRegistry.acqii[id]._object.isdeleted())
190 totalCost += Number(widgetRegistry.acqii[id].cost_billed.getFormattedValue());
191 for(var id in widgetRegistry.acqie)
192 if(!widgetRegistry.acqie[id]._object.isdeleted())
193 totalCost += Number(widgetRegistry.acqie[id].cost_billed.getFormattedValue());
194 totalInvoicedBox.attr('value', totalCost);
197 for(var id in widgetRegistry.acqii)
198 if(!widgetRegistry.acqii[id]._object.isdeleted())
199 totalPaid += Number(widgetRegistry.acqii[id].amount_paid.getFormattedValue());
200 for(var id in widgetRegistry.acqie)
201 if(!widgetRegistry.acqie[id]._object.isdeleted())
202 totalPaid += Number(widgetRegistry.acqie[id].amount_paid.getFormattedValue());
203 totalPaidBox.attr('value', totalPaid);
205 var buttonsDisabled = false;
207 if(totalPaid > totalCost || totalPaid < 0) {
208 openils.Util.addCSSClass(totalPaidBox.domNode, 'acq-invoice-invalid-amount');
209 invoiceSaveButton.attr('disabled', true);
210 invoiceProrateButton.attr('disabled', true);
211 buttonsDisabled = true;
213 openils.Util.removeCSSClass(totalPaidBox.domNode, 'acq-invoice-invalid-amount');
214 invoiceSaveButton.attr('disabled', false);
215 invoiceProrateButton.attr('disabled', false);
219 openils.Util.addCSSClass(totalInvoicedBox.domNode, 'acq-invoice-invalid-amount');
220 invoiceSaveButton.attr('disabled', true);
221 invoiceProrateButton.attr('disabled', true);
223 openils.Util.removeCSSClass(totalInvoicedBox.domNode, 'acq-invoice-invalid-amount');
224 if(!buttonsDisabled) {
225 invoiceSaveButton.attr('disabled', false);
226 invoiceProrateButton.attr('disabled', false);
230 if(totalPaid == totalCost) { // XXX: too rigid?
231 invoiceCloseButton.attr('disabled', false);
233 invoiceCloseButton.attr('disabled', true);
236 balanceOwedBox.attr('value', (totalCost - totalPaid));
240 function registerWidget(obj, field, widget, callback) {
241 var blob = widgetRegistry[obj.classname];
243 blob[obj.id()] = {_object : obj};
244 blob[obj.id()][field] = widget;
247 dojo.connect(w, 'onChange',
253 if(callback) callback(w, ww);
259 function addInvoiceItem(item) {
260 itemTbody = dojo.byId('acq-invoice-item-tbody');
261 if(itemTemplate == null) {
262 itemTemplate = itemTbody.removeChild(dojo.byId('acq-invoice-item-template'));
265 var row = itemTemplate.cloneNode(true);
266 var itemType = itemTypes.filter(function(t) { return (t.code() == item.inv_item_type()) })[0];
269 ['title', 'author', 'cost_billed', 'amount_paid'],
273 if(field == 'title' || field == 'author') {
274 args = {style : 'width:10em'};
275 } else if(field == 'cost_billed' || field == 'amount_paid') {
276 args = {required : true, style : 'width: 6em'};
281 new openils.widget.AutoFieldWidget({
285 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
287 parentNode : nodeByName(field, row)
294 /* ----------- fund -------------- */
299 labelFormat : fundLabelFormat,
300 searchFormat : fundSearchFormat,
301 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
302 parentNode : nodeByName('fund', row)
305 if(item.fund_debit()) {
306 fundArgs.searchFilter = {'-or' : [{active : 't'}, {id : item.fund()}]};
308 fundArgs.searchFilter = {active : 't'}
309 if(itemType && openils.Util.isTrue(itemType.prorate()))
310 fundArgs.dijitArgs = {disabled : true};
313 var fundWidget = new openils.widget.AutoFieldWidget(fundArgs);
314 registerWidget(item, 'fund', fundWidget);
316 /* ---------- inv_item_type ------------- */
321 new openils.widget.AutoFieldWidget({
323 fmField : 'inv_item_type',
324 parentNode : nodeByName('inv_item_type', row),
325 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
326 dijitArgs : {required : true}
329 // When the inv_item_type is set to prorate=true, don't allow the user the edit the fund
330 // since this charge will be prorated against (potentially) multiple funds
331 dojo.connect(w, 'onChange',
333 if(!item.fund_debit()) {
334 var itemType = itemTypes.filter(function(t) { return (t.code() == w.attr('value')) })[0];
335 if(!itemType) return;
336 if(openils.Util.isTrue(itemType.prorate())) {
337 fundWidget.widget.attr('disabled', true);
338 fundWidget.widget.attr('value', '');
340 fundWidget.widget.attr('disabled', false);
348 nodeByName('delete', row).onclick = function() {
349 var cost = widgetRegistry.acqii[item.id()].cost_billed.getFormattedValue();
350 var msg = dojo.string.substitute(
351 localeStrings.INVOICE_CONFIRM_ITEM_DELETE, [
353 widgetRegistry.acqii[item.id()].inv_item_type.getFormattedValue()
356 if(!confirm(msg)) return;
357 itemTbody.removeChild(row);
358 item.isdeleted(true);
360 delete widgetRegistry.acqii[item.id()];
364 itemTbody.appendChild(row);
368 function addInvoiceEntry(entry) {
370 openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-header'), 'hidden');
371 openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-thead'), 'hidden');
372 openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-tbody'), 'hidden');
374 entryTbody = dojo.byId('acq-invoice-entry-tbody');
375 if(entryTemplate == null) {
376 entryTemplate = entryTbody.removeChild(dojo.byId('acq-invoice-entry-template'));
379 if(dojo.query('[lineitem=' + entry.lineitem().id() +']', entryTbody)[0])
380 // Is it ever valid to have multiple entries for 1 lineitem in a single invoice?
383 var row = entryTemplate.cloneNode(true);
384 row.setAttribute('lineitem', entry.lineitem().id());
385 var lineitem = entry.lineitem();
388 if(liMarcAttr(lineitem, 'isbn')) idents.push(liMarcAttr(lineitem, 'isbn'));
389 if(liMarcAttr(lineitem, 'upc')) idents.push(liMarcAttr(lineitem, 'upc'));
390 if(liMarcAttr(lineitem, 'issn')) idents.push(liMarcAttr(lineitem, 'issn'));
392 var lids = lineitem.lineitem_details();
393 var numOrdered = lids.length;
394 var numReceived = lids.filter(function(lid) { return (lid.recv_time() != null) }).length;
395 var numInvoiced = lids.filter(function(lid) { return !openils.Util.isTrue(lid.fund_debit().encumbrance()) }).length;
399 var po = entry.purchase_order();
405 nodeByName('title_details', row).innerHTML =
406 dojo.string.substitute(
407 localeStrings.INVOICE_TITLE_DETAILS, [
408 liMarcAttr(lineitem, 'title'),
409 liMarcAttr(lineitem, 'author'),
413 Number(lineitem.estimated_unit_price()).toFixed(2),
414 (Number(lineitem.estimated_unit_price()) * numOrdered).toFixed(2),
425 ['inv_item_count', 'phys_item_count', 'cost_billed', 'amount_paid'],
427 var dijitArgs = {required : true, constraints : {min: 0}, style : 'width:6em'};
428 if(entry.isnew() && field == 'phys_item_count') dijitArgs.value = numReceived;
432 new openils.widget.AutoFieldWidget({
436 dijitArgs : dijitArgs,
437 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
438 parentNode : nodeByName(field, row)
444 nodeByName('detach', row).onclick = function() {
445 var cost = widgetRegistry.acqie[entry.id()].cost_billed.getFormattedValue();
446 var msg = dojo.string.substitute(
447 localeStrings.INVOICE_CONFIRM_ENTRY_DETACH, [
449 liMarcAttr(lineitem, 'title'),
450 liMarcAttr(lineitem, 'author'),
454 if(!confirm(msg)) return;
455 entryTbody.removeChild(row);
456 entry.isdeleted(true);
458 delete widgetRegistry.acqie[entry.id()];
462 entryTbody.appendChild(row);
466 function liMarcAttr(lineitem, name) {
467 var attr = lineitem.attributes().filter(
470 attr.attr_type() == 'lineitem_marc_attr_definition' &&
471 attr.attr_name() == name)
475 return (attr) ? attr.attr_value() : '';
478 function saveChanges(doProrate, doClose, doReopen) {
480 progressDialog.show(true);
483 invoice.complete('f');
488 var updateItems = [];
489 for(var id in widgetRegistry.acqii) {
490 var reg = widgetRegistry.acqii[id];
491 var item = reg._object;
492 if(item.ischanged() || item.isnew() || item.isdeleted()) {
493 updateItems.push(item);
494 if(item.isnew()) item.id(null);
495 for(var field in reg) {
496 if(field != '_object')
497 item[field]( reg[field].getFormattedValue() );
501 if(item.purchase_order() != null && typeof item.purchase_order() == 'object')
502 item.purchase_order( item.purchase_order().id() );
506 var updateEntries = [];
507 for(var id in widgetRegistry.acqie) {
508 var reg = widgetRegistry.acqie[id];
509 var entry = reg._object;
510 if(entry.ischanged() || entry.isnew() || entry.isdeleted()) {
511 entry.lineitem(entry.lineitem().id());
512 entry.purchase_order(entry.purchase_order().id());
513 updateEntries.push(entry);
514 if(entry.isnew()) entry.id(null);
516 for(var field in reg) {
517 if(field != '_object')
518 entry[field]( reg[field].getFormattedValue() );
522 dojo.forEach(['purchase_order', 'lineitem'],
524 if(entry[field]() != null && typeof entry[field]() == 'object')
525 entry[field]( entry[field]().id() );
532 invoice = new fieldmapper.acqinv();
535 invoice.ischanged(true); // for now, just always update
538 dojo.forEach(invoicePane.fieldList,
540 invoice[field.name]( field.widget.getFormattedValue() );
545 invoice.complete('t');
548 fieldmapper.standardRequest(
549 ['open-ils.acq', 'open-ils.acq.invoice.update'],
551 params : [openils.User.authtoken, invoice, updateEntries, updateItems],
552 oncomplete : function(r) {
553 progressDialog.hide();
554 var invoice = openils.Util.readResponse(r);
557 return prorateInvoice(invoice);
558 location.href = oilsBasePath + '/acq/invoice/view/' + invoice.id();
565 function prorateInvoice(invoice) {
566 if(!confirm(localeStrings.INVOICE_CONFIRM_PRORATE)) return;
567 progressDialog.show(true);
569 fieldmapper.standardRequest(
570 ['open-ils.acq', 'open-ils.acq.invoice.apply_prorate'],
572 params : [openils.User.authtoken, invoice.id()],
573 oncomplete : function(r) {
574 progressDialog.hide();
575 var invoice = openils.Util.readResponse(r);
577 location.href = oilsBasePath + '/acq/invoice/view/' + invoice.id();
587 openils.Util.addOnLoad(init);