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