]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/acq/invoice/view.js
fixed bug in save+prorate action (passing null ID)
[working/Evergreen.git] / Open-ILS / web / js / ui / default / acq / invoice / view.js
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
14 dojo.requireLocalization('openils.acq', 'acq');
15 var localeStrings = dojo.i18n.getLocalization('openils.acq', 'acq');
16
17 var fundLabelFormat = ['${0} (${1})', 'code', 'year'];
18 var fundSearchFormat = ['${0} (${1})', 'code', 'year'];
19
20 var cgi = new openils.CGI();
21 var pcrud = new openils.PermaCrud();
22 var attachLi;
23 var attachPo;
24 var invoice;
25 var itemTbody;
26 var itemTemplate;
27 var entryTemplate;
28 var totalInvoicedBox;
29 var totalPaidBox;
30 var invoicePane;
31 var itemTypes;
32 var virtualId = -1;
33 var widgetRegistry = {acqie : {}, acqii : {}};
34
35 function nodeByName(name, context) {
36     return dojo.query('[name='+name+']', context)[0];
37 }
38
39 function init() {
40
41     attachLi = cgi.param('attach_li');
42     attachPo = cgi.param('attach_po');
43
44     itemTypes = pcrud.retrieveAll('aiit');
45
46     if(cgi.param('create')) {
47         renderInvoice();
48
49     } else {
50         fieldmapper.standardRequest(
51             ['open-ils.acq', 'open-ils.acq.invoice.retrieve'],
52             {
53                 params : [openils.User.authtoken, invoiceId],
54                 oncomplete : function(r) {
55                     invoice = openils.Util.readResponse(r);     
56                     renderInvoice();
57                 }
58             }
59         );
60     }
61 }
62
63 function renderInvoice() {
64
65     // in create mode, let the LI or PO render the invoice with seed data
66     if( !(cgi.param('create') && (attachPo || attachLi)) ) {
67         invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), invoice);
68     }
69
70     dojo.byId('acq-invoice-new-item').onclick = function() {
71         var item = new fieldmapper.acqii();
72         item.id(virtualId--);
73         item.isnew(true);
74         addInvoiceItem(item);
75     }
76
77     updateTotalCost();
78
79     if(invoice) {
80         dojo.forEach(
81             invoice.items(),
82             function(item) {
83                 addInvoiceItem(item);
84             }
85         );
86
87         dojo.forEach(
88             invoice.entries(),
89             function(entry) {
90                 addInvoiceEntry(entry);
91             }
92         );
93     }
94
95     if(attachLi) doAttachLi();
96     if(attachPo) doAttachPo();
97 }
98
99 function doAttachLi() {
100
101     fieldmapper.standardRequest(
102         ["open-ils.acq", "open-ils.acq.lineitem.retrieve"], {
103             async: true,
104             params: [openils.User.authtoken, attachLi, {
105                 clear_marc : true,
106                 flesh_attrs : true,
107                 flesh_po : true,
108                 flesh_li_details : true,
109                 flesh_fund_debit : true
110             }],
111             oncomplete: function(r) { 
112                 lineitem = openils.Util.readResponse(r);
113
114                 if(cgi.param('create')) {
115                     // render the invoice using some seed data from the Lineitem
116                     var invoiceArgs = {provider : lineitem.provider(), shipper : lineitem.provider()}; 
117                     invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), null, invoiceArgs);
118                 }
119
120                 var entry = new fieldmapper.acqie();
121                 entry.id(virtualId--);
122                 entry.isnew(true);
123                 entry.lineitem(lineitem);
124                 entry.purchase_order(lineitem.purchase_order());
125                 addInvoiceEntry(entry);
126             }
127         }
128     );
129 }
130
131 function doAttachPo() {
132     fieldmapper.standardRequest(
133         ['open-ils.acq', 'open-ils.acq.purchase_order.retrieve'],
134         {   async: true,
135             params: [openils.User.authtoken, attachPo, {
136                 flesh_lineitems : true,
137                 clear_marc : true,
138                 flesh_lineitem_details : true,
139                 flesh_fund_debit : true
140             }],
141             oncomplete: function(r) {
142                 var po = openils.Util.readResponse(r);
143
144                 if(cgi.param('create')) {
145                     // render the invoice using some seed data from the PO
146                     var invoiceArgs = {provider : po.provider(), shipper : po.provider()}; 
147                     invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), null, invoiceArgs);
148                 }
149
150                 dojo.forEach(po.lineitems(), 
151                     function(lineitem) {
152                         var entry = new fieldmapper.acqie();
153                         entry.id(virtualId--);
154                         entry.isnew(true);
155                         entry.lineitem(lineitem);
156                         entry.purchase_order(po);
157                         lineitem.purchase_order(po);
158                         addInvoiceEntry(entry);
159                     }
160                 );
161             }
162         }
163     );
164 }
165
166 function updateTotalCost() {
167
168     var totalCost = 0;    
169     if(!totalInvoicedBox) {
170         totalInvoicedBox = new dijit.form.CurrencyTextBox(
171             {style : 'width: 6em'}, dojo.byId('acq-invoice-total-invoiced'));
172     }
173     for(var id in widgetRegistry.acqii) 
174         if(!widgetRegistry.acqii[id]._object.isdeleted())
175             totalCost += widgetRegistry.acqii[id].cost_billed.getFormattedValue();
176     for(var id in widgetRegistry.acqie) 
177         if(!widgetRegistry.acqie[id]._object.isdeleted())
178             totalCost += widgetRegistry.acqie[id].cost_billed.getFormattedValue();
179     totalInvoicedBox.attr('value', totalCost);
180
181     totalPaid = 0;    
182     if(!totalPaidBox) {
183         totalPaidBox = new dijit.form.CurrencyTextBox(
184             {style : 'width: 6em'}, dojo.byId('acq-invoice-total-paid'));
185     }
186     for(var id in widgetRegistry.acqii) 
187         if(!widgetRegistry.acqii[id]._object.isdeleted())
188             totalPaid += widgetRegistry.acqii[id].amount_paid.getFormattedValue();
189     for(var id in widgetRegistry.acqie) 
190         if(!widgetRegistry.acqie[id]._object.isdeleted())
191             totalPaid += widgetRegistry.acqie[id].amount_paid.getFormattedValue();
192     totalPaidBox.attr('value', totalPaid);
193
194     var buttonsDisabled = false;
195     if(totalPaid > totalCost || totalPaid < 0) {
196         openils.Util.addCSSClass(totalPaidBox.domNode, 'acq-invoice-invalid-amount');
197         invoiceSaveButton.attr('disabled', true);
198         invoiceProrateButton.attr('disabled', true);
199         buttonsDisabled = true;
200     } else {
201         openils.Util.removeCSSClass(totalPaidBox.domNode, 'acq-invoice-invalid-amount');
202         invoiceSaveButton.attr('disabled', false);
203         invoiceProrateButton.attr('disabled', false);
204     }
205
206     if(totalCost < 0) {
207         openils.Util.addCSSClass(totalInvoicedBox.domNode, 'acq-invoice-invalid-amount');
208         invoiceSaveButton.attr('disabled', true);
209         invoiceProrateButton.attr('disabled', true);
210     } else {
211         openils.Util.removeCSSClass(totalInvoicedBox.domNode, 'acq-invoice-invalid-amount');
212         if(!buttonsDisabled) {
213             invoiceSaveButton.attr('disabled', false);
214             invoiceProrateButton.attr('disabled', false);
215         }
216     }
217 }
218
219
220 function registerWidget(obj, field, widget, callback) {
221     var blob = widgetRegistry[obj.classname];
222     if(!blob[obj.id()]) 
223         blob[obj.id()] = {_object : obj};
224     blob[obj.id()][field] = widget;
225     widget.build(
226         function(w, ww) {
227             dojo.connect(w, 'onChange', 
228                 function(newVal) { 
229                     obj.ischanged(true); 
230                     updateTotalCost();
231                 }
232             );
233             if(callback) callback(w, ww);
234         }
235     );
236     return widget;
237 }
238
239 function addInvoiceItem(item) {
240     itemTbody = dojo.byId('acq-invoice-item-tbody');
241     if(itemTemplate == null) {
242         itemTemplate = itemTbody.removeChild(dojo.byId('acq-invoice-item-template'));
243     }
244
245     var row = itemTemplate.cloneNode(true);
246     var itemType = itemTypes.filter(function(t) { return (t.code() == item.inv_item_type()) })[0];
247
248     dojo.forEach(
249         ['title', 'author', 'cost_billed', 'amount_paid'], 
250         function(field) {
251             registerWidget(
252                 item,
253                 field,
254                 new openils.widget.AutoFieldWidget({
255                     fmClass : 'acqii',
256                     fmObject : item,
257                     fmField : field,
258                     dijitArgs : (field == 'cost_billed' || field == 'amount_paid') ? {required : true, style : 'width: 6em'} : null,
259                     parentNode : nodeByName(field, row)
260                 })
261             )
262         }
263     );
264
265
266     /* ----------- fund -------------- */
267     var fundArgs = {
268         fmClass : 'acqii',
269         fmObject : item,
270         fmField : 'fund',
271         labelFormat : fundLabelFormat,
272         searchFormat : fundSearchFormat,
273         parentNode : nodeByName('fund', row)
274     }
275
276     if(item.fund_debit()) {
277         fundArgs.searchFilter = {'-or' : [{active : 't'}, {id : item.fund()}]};
278     } else {
279         fundArgs.searchFilter = {active : 't'}
280         if(itemType && openils.Util.isTrue(itemType.prorate()))
281             fundArgs.dijitArgs = {disabled : true};
282     }
283
284     var fundWidget = new openils.widget.AutoFieldWidget(fundArgs);
285     registerWidget(item, 'fund', fundWidget);
286
287     /* ---------- inv_item_type ------------- */
288
289     registerWidget(
290         item,
291         'inv_item_type',
292         new openils.widget.AutoFieldWidget({
293             fmObject : item,
294             fmField : 'inv_item_type',
295             parentNode : nodeByName('inv_item_type', row),
296             dijitArgs : {required : true}
297         }),
298         function(w, ww) {
299             // When the inv_item_type is set to prorate=true, don't allow the user the edit the fund
300             // since this charge will be prorated against (potentially) multiple funds
301             dojo.connect(w, 'onChange', 
302                 function() {
303                     if(!item.fund_debit()) {
304                         var itemType = itemTypes.filter(function(t) { return (t.code() == w.attr('value')) })[0];
305                         if(!itemType) return;
306                         if(openils.Util.isTrue(itemType.prorate())) {
307                             fundWidget.widget.attr('disabled', true);
308                             fundWidget.widget.attr('value', '');
309                         } else {
310                             fundWidget.widget.attr('disabled', false);
311                         }
312                     }
313                 }
314             );
315         }
316     );
317
318     nodeByName('delete', row).onclick = function() {
319         var cost = widgetRegistry.acqii[item.id()].cost_billed.getFormattedValue();
320         var msg = dojo.string.substitute(
321             localeStrings.INVOICE_CONFIRM_ITEM_DELETE, [
322                 cost,
323                 widgetRegistry.acqii[item.id()].inv_item_type.getFormattedValue()
324             ]
325         );
326         if(!confirm(msg)) return;
327         itemTbody.removeChild(row);
328         item.isdeleted(true);
329         if(item.isnew())
330             delete widgetRegistry.acqii[item.id()];
331         updateTotalCost();
332     }
333
334     itemTbody.appendChild(row);
335     updateTotalCost();
336 }
337
338 function addInvoiceEntry(entry) {
339     entryTbody = dojo.byId('acq-invoice-entry-tbody');
340     if(entryTemplate == null) {
341         entryTemplate = entryTbody.removeChild(dojo.byId('acq-invoice-entry-template'));
342     }
343
344     if(dojo.query('[lineitem=' + entry.lineitem().id() +']', entryTbody)[0])
345         // Is it ever valid to have multiple entries for 1 lineitem in a single invoice?
346         return;
347
348     var row = entryTemplate.cloneNode(true);
349     row.setAttribute('lineitem', entry.lineitem().id());
350     var lineitem = entry.lineitem();
351
352     var idents = [];
353     if(liMarcAttr(lineitem, 'isbn')) idents.push(liMarcAttr(lineitem, 'isbn'));
354     if(liMarcAttr(lineitem, 'upc')) idents.push(liMarcAttr(lineitem, 'upc'));
355     if(liMarcAttr(lineitem, 'issn')) idents.push(liMarcAttr(lineitem, 'issn'));
356
357     var lids = lineitem.lineitem_details();
358     var numOrdered = lids.length;
359     var numReceived = lids.filter(function(lid) { return (lid.recv_time() != null) }).length;
360     var numInvoiced = lids.filter(function(lid) { return !openils.Util.isTrue(lid.fund_debit().encumbrance()) }).length;
361
362     var poName = '';
363     var poId = '';
364     var po = entry.purchase_order();
365     if(po) {
366         poName = po.name();
367         poId = po.id();
368     }
369
370     nodeByName('title_details', row).innerHTML = 
371         dojo.string.substitute(
372             localeStrings.INVOICE_TITLE_DETAILS, [
373                 liMarcAttr(lineitem, 'title'),
374                 liMarcAttr(lineitem, 'author'),
375                 idents.join(','),
376                 numOrdered,
377                 numReceived,
378                 Number(lineitem.estimated_unit_price()).toFixed(2),
379                 (Number(lineitem.estimated_unit_price()) * numOrdered).toFixed(2),
380                 numInvoiced,
381                 lineitem.id(),
382                 oilsBasePath,
383                 poId,
384                 poName
385             ]
386         );
387
388
389     dojo.forEach(
390         ['inv_item_count', 'phys_item_count', 'cost_billed', 'amount_paid'],
391         function(field) {
392             var dijitArgs = {required : true, constraints : {min: 0}, style : 'width:6em'};
393             if(entry.isnew() && field == 'phys_item_count') dijitArgs.value = numReceived;
394             registerWidget(
395                 entry, 
396                 field,
397                 new openils.widget.AutoFieldWidget({
398                     fmObject : entry,
399                     fmClass : 'acqie',
400                     fmField : field,
401                     dijitArgs : dijitArgs,
402                     parentNode : nodeByName(field, row)
403                 })
404             );
405         }
406     );
407
408     nodeByName('detach', row).onclick = function() {
409         var cost = widgetRegistry.acqie[entry.id()].cost_billed.getFormattedValue();
410         var msg = dojo.string.substitute(
411             localeStrings.INVOICE_CONFIRM_ENTRY_DETACH, [
412                 cost || 0,
413                 liMarcAttr(lineitem, 'title'),
414                 liMarcAttr(lineitem, 'author'),
415                 idents.join(',')
416             ]
417         );
418         if(!confirm(msg)) return;
419         entryTbody.removeChild(row);
420         entry.isdeleted(true);
421         if(entry.isnew())
422             delete widgetRegistry.acqie[entry.id()];
423         updateTotalCost();
424     }
425
426     entryTbody.appendChild(row);
427     updateTotalCost();
428 }
429
430 function liMarcAttr(lineitem, name) {
431     var attr = lineitem.attributes().filter(
432         function(attr) { 
433             if(
434                 attr.attr_type() == 'lineitem_marc_attr_definition' && 
435                 attr.attr_name() == name) 
436                     return attr 
437         } 
438     )[0];
439     return (attr) ? attr.attr_value() : '';
440 }
441
442 function saveChanges(doProrate) {
443     
444     progressDialog.show(true);
445
446     var updateItems = [];
447     for(var id in widgetRegistry.acqii) {
448         var reg = widgetRegistry.acqii[id];
449         var item = reg._object;
450         if(item.ischanged() || item.isnew() || item.isdeleted()) {
451             updateItems.push(item);
452             if(item.isnew()) item.id(null);
453             for(var field in reg) {
454                 if(field != '_object')
455                     item[field]( reg[field].getFormattedValue() );
456             }
457             
458             // unflesh
459             if(item.purchase_order() != null && typeof item.purchase_order() == 'object')
460                 item.purchase_order( item.purchase_order().id() );
461         }
462     }
463
464     var updateEntries = [];
465     for(var id in widgetRegistry.acqie) {
466         var reg = widgetRegistry.acqie[id];
467         var entry = reg._object;
468         if(entry.ischanged() || entry.isnew() || entry.isdeleted()) {
469             entry.lineitem(entry.lineitem().id());
470             entry.purchase_order(entry.purchase_order().id());
471             updateEntries.push(entry);
472             if(entry.isnew()) entry.id(null);
473
474             for(var field in reg) {
475                 if(field != '_object')
476                     entry[field]( reg[field].getFormattedValue() );
477             }
478             
479             // unflesh
480             dojo.forEach(['purchase_order', 'lineitem'],
481                 function(field) {
482                     if(entry[field]() != null && typeof entry[field]() == 'object')
483                         entry[field]( entry[field]().id() );
484                 }
485             );
486         }
487     }
488
489     if(!invoice) {
490         invoice = new fieldmapper.acqinv();
491         invoice.isnew(true);
492     } else {
493         invoice.ischanged(true); // for now, just always update
494     }
495
496     dojo.forEach(invoicePane.fieldList, 
497         function(field) {
498             invoice[field.name]( field.widget.getFormattedValue() );
499         }
500     );
501
502     fieldmapper.standardRequest(
503         ['open-ils.acq', 'open-ils.acq.invoice.update'],
504         {
505             params : [openils.User.authtoken, invoice, updateEntries, updateItems],
506             oncomplete : function(r) {
507                 progressDialog.hide();
508                 var invoice = openils.Util.readResponse(r);
509                 if(invoice) {
510                     if(doProrate)
511                         return prorateInvoice(invoice);
512                     location.href = oilsBasePath + '/acq/invoice/view/' + invoice.id();
513                 }
514             }
515         }
516     );
517 }
518
519 function prorateInvoice(invoice) {
520     if(!confirm(localeStrings.INVOICE_CONFIRM_PRORATE)) return;
521     progressDialog.show(true);
522
523     fieldmapper.standardRequest(
524         ['open-ils.acq', 'open-ils.acq.invoice.apply_prorate'],
525         {
526             params : [openils.User.authtoken, invoice.id()],
527             oncomplete : function(r) {
528                 progressDialog.hide();
529                 var invoice = openils.Util.readResponse(r);
530                 if(invoice) {
531                     location.href = oilsBasePath + '/acq/invoice/view/' + invoice.id();
532                 }
533             }
534         }
535     );
536
537 }
538
539
540
541 openils.Util.addOnLoad(init);
542
543