]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/acq/invoice/view.js
Added support to invoice page for adding extra copies to an invoice than
[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 extraCopies = {};
36 var extraCopiesFund;
37 var widgetRegistry = {acqie : {}, acqii : {}};
38
39 function nodeByName(name, context) {
40     return dojo.query('[name='+name+']', context)[0];
41 }
42
43 function init() {
44
45     attachLi = cgi.param('attach_li');
46     attachPo = cgi.param('attach_po');
47
48     itemTypes = pcrud.retrieveAll('aiit');
49
50     if(cgi.param('create')) {
51         renderInvoice();
52
53     } else {
54         fieldmapper.standardRequest(
55             ['open-ils.acq', 'open-ils.acq.invoice.retrieve'],
56             {
57                 params : [openils.User.authtoken, invoiceId],
58                 oncomplete : function(r) {
59                     invoice = openils.Util.readResponse(r);     
60                     renderInvoice();
61                 }
62             }
63         );
64     }
65
66     extraCopiesFund = new openils.widget.AutoFieldWidget({
67         fmField : 'fund',
68         fmClass : 'acqlid',
69         searchFilter : {active : 't'},
70         labelFormat : fundLabelFormat,
71         searchFormat : fundSearchFormat,
72         parentNode : dojo.byId('acq-invoice-extra-copies-fund')
73     });
74     extraCopiesFund.build();
75 }
76
77 function renderInvoice() {
78
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);
82     }
83
84     dojo.byId('acq-invoice-new-item').onclick = function() {
85         var item = new fieldmapper.acqii();
86         item.id(virtualId--);
87         item.isnew(true);
88         addInvoiceItem(item);
89     }
90
91     updateTotalCost();
92
93     if(invoice && openils.Util.isTrue(invoice.complete())) {
94
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); }
98         );
99
100         new openils.User().getPermOrgList(
101             'ACQ_INVOICE_REOPEN', 
102             function (orgs) {
103                 if(orgs.indexOf(invoice.receiver()) >= 0)
104                     openils.Util.show('acq-invoice-reopen-button-wrapper', 'inline');
105             }, 
106             true, 
107             true
108         );
109     }
110
111     if(invoice) {
112         dojo.forEach(
113             invoice.items(),
114             function(item) {
115                 addInvoiceItem(item);
116             }
117         );
118
119         dojo.forEach(
120             invoice.entries(),
121             function(entry) {
122                 addInvoiceEntry(entry);
123             }
124         );
125     }
126
127     if(attachLi) doAttachLi();
128     if(attachPo) doAttachPo();
129 }
130
131 function doAttachLi() {
132
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);
137     }
138     var entry = new fieldmapper.acqie();
139     entry.id(virtualId--);
140     entry.isnew(true);
141     entry.lineitem(attachLi);
142     addInvoiceEntry(entry);
143 }
144
145 function doAttachPo() {
146
147     fieldmapper.standardRequest(
148         ['open-ils.acq', 'open-ils.acq.purchase_order.retrieve'],
149         {   async: true,
150             params: [openils.User.authtoken, attachPo, {flesh_lineitem_ids : true}],
151             oncomplete: function(r) {
152                 var po = openils.Util.readResponse(r);
153
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);
158                 }
159
160                 dojo.forEach(po.lineitems(), 
161                     function(lineitem) {
162                         var entry = new fieldmapper.acqie();
163                         entry.id(virtualId--);
164                         entry.isnew(true);
165                         entry.lineitem(lineitem);
166                         entry.purchase_order(po);
167                         addInvoiceEntry(entry);
168                     }
169                 );
170             }
171         }
172     );
173 }
174
175 function updateTotalCost() {
176
177     var totalCost = 0;    
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);
185
186     totalPaid = 0;    
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);
194
195     var buttonsDisabled = false;
196
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;
202     } else {
203         openils.Util.removeCSSClass(totalPaidBox.domNode, 'acq-invoice-invalid-amount');
204         invoiceSaveButton.attr('disabled', false);
205         invoiceProrateButton.attr('disabled', false);
206     }
207
208     if(totalCost < 0) {
209         openils.Util.addCSSClass(totalInvoicedBox.domNode, 'acq-invoice-invalid-amount');
210         invoiceSaveButton.attr('disabled', true);
211         invoiceProrateButton.attr('disabled', true);
212     } else {
213         openils.Util.removeCSSClass(totalInvoicedBox.domNode, 'acq-invoice-invalid-amount');
214         if(!buttonsDisabled) {
215             invoiceSaveButton.attr('disabled', false);
216             invoiceProrateButton.attr('disabled', false);
217         }
218     }
219
220     if(totalPaid == totalCost) { // XXX: too rigid?
221         invoiceCloseButton.attr('disabled', false);
222     } else {
223         invoiceCloseButton.attr('disabled', true);
224     }
225
226     balanceOwedBox.attr('value', (totalCost - totalPaid));
227 }
228
229
230 function registerWidget(obj, field, widget, callback) {
231     var blob = widgetRegistry[obj.classname];
232     if(!blob[obj.id()]) 
233         blob[obj.id()] = {_object : obj};
234     blob[obj.id()][field] = widget;
235     widget.build(
236         function(w, ww) {
237             dojo.connect(w, 'onChange', 
238                 function(newVal) { 
239                     obj.ischanged(true); 
240                     updateTotalCost();
241                 }
242             );
243             if(callback) callback(w, ww);
244         }
245     );
246     return widget;
247 }
248
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'));
253     }
254
255     var row = itemTemplate.cloneNode(true);
256     var itemType = itemTypes.filter(function(t) { return (t.code() == item.inv_item_type()) })[0];
257
258     dojo.forEach(
259         ['title', 'author', 'cost_billed', 'amount_paid'], 
260         function(field) {
261             
262             var args;
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'};
267             }
268             registerWidget(
269                 item,
270                 field,
271                 new openils.widget.AutoFieldWidget({
272                     fmClass : 'acqii',
273                     fmObject : item,
274                     fmField : field,
275                     readOnly : invoice && openils.Util.isTrue(invoice.complete()),
276                     dijitArgs : args,
277                     parentNode : nodeByName(field, row)
278                 })
279             )
280         }
281     );
282
283
284     /* ----------- fund -------------- */
285     var fundArgs = {
286         fmClass : 'acqii',
287         fmObject : item,
288         fmField : 'fund',
289         labelFormat : fundLabelFormat,
290         searchFormat : fundSearchFormat,
291         readOnly : invoice && openils.Util.isTrue(invoice.complete()),
292         parentNode : nodeByName('fund', row)
293     }
294
295     if(item.fund_debit()) {
296         fundArgs.searchFilter = {'-or' : [{active : 't'}, {id : item.fund()}]};
297     } else {
298         fundArgs.searchFilter = {active : 't'}
299         if(itemType && openils.Util.isTrue(itemType.prorate()))
300             fundArgs.dijitArgs = {disabled : true};
301     }
302
303     var fundWidget = new openils.widget.AutoFieldWidget(fundArgs);
304     registerWidget(item, 'fund', fundWidget);
305
306     /* ---------- inv_item_type ------------- */
307
308     registerWidget(
309         item,
310         'inv_item_type',
311         new openils.widget.AutoFieldWidget({
312             fmObject : item,
313             fmField : 'inv_item_type',
314             parentNode : nodeByName('inv_item_type', row),
315             readOnly : invoice && openils.Util.isTrue(invoice.complete()),
316             dijitArgs : {required : true}
317         }),
318         function(w, ww) {
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', 
322                 function() {
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', '');
329                         } else {
330                             fundWidget.widget.attr('disabled', false);
331                         }
332                     }
333                 }
334             );
335         }
336     );
337
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, [
342                 cost,
343                 widgetRegistry.acqii[item.id()].inv_item_type.getFormattedValue()
344             ]
345         );
346         if(!confirm(msg)) return;
347         itemTbody.removeChild(row);
348         item.isdeleted(true);
349         if(item.isnew())
350             delete widgetRegistry.acqii[item.id()];
351         updateTotalCost();
352     }
353
354     itemTbody.appendChild(row);
355     updateTotalCost();
356 }
357
358 function addInvoiceEntry(entry) {
359
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');
363
364     entryTbody = dojo.byId('acq-invoice-entry-tbody');
365     if(entryTemplate == null) {
366         entryTemplate = entryTbody.removeChild(dojo.byId('acq-invoice-entry-template'));
367     }
368
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?
371         return;
372
373     var row = entryTemplate.cloneNode(true);
374     row.setAttribute('lineitem', entry.lineitem());
375
376     openils.acq.Lineitem.fetchAndRender(
377         entry.lineitem(), {}, 
378         function(li, html) { 
379             entry.lineitem(li);
380             entry.purchase_order(li.purchase_order());
381             nodeByName('title_details', row).innerHTML = html;
382         }
383     );
384
385     dojo.forEach(
386         ['inv_item_count', 'phys_item_count', 'cost_billed', 'amount_paid'],
387         function(field) {
388             var dijitArgs = {required : true, constraints : {min: 0}, style : 'width:6em'};
389             if(entry.isnew() && field == 'phys_item_count') dijitArgs.value = numReceived;
390             registerWidget(
391                 entry, 
392                 field,
393                 new openils.widget.AutoFieldWidget({
394                     fmObject : entry,
395                     fmClass : 'acqie',
396                     fmField : field,
397                     dijitArgs : dijitArgs,
398                     readOnly : invoice && openils.Util.isTrue(invoice.complete()),
399                     parentNode : nodeByName(field, row)
400                 }),
401                 function(w) {    
402                     if(field == 'phys_item_count') {
403                         dojo.connect(w, 'onChange', 
404                             function() {
405                                 // staff entered a higher number in the receive field than was originally ordered
406                                 if(Number(this.attr('value')) > entry.lineitem().item_count()) {
407                                     storeExtraCopies(
408                                         entry.lineitem().id(), 
409                                         Number(this.attr('value')) - entry.lineitem().item_count()
410                                     );
411                                 }
412                             }
413                         )
414                     }
415                 }
416             );
417         }
418     );
419
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, [
424                 cost || 0,
425                 liMarcAttr(lineitem, 'title'),
426                 liMarcAttr(lineitem, 'author'),
427                 idents.join(',')
428             ]
429         );
430         if(!confirm(msg)) return;
431         entryTbody.removeChild(row);
432         entry.isdeleted(true);
433         if(entry.isnew())
434             delete widgetRegistry.acqie[entry.id()];
435         updateTotalCost();
436     }
437
438     entryTbody.appendChild(row);
439     updateTotalCost();
440 }
441
442 function liMarcAttr(lineitem, name) {
443     var attr = lineitem.attributes().filter(
444         function(attr) { 
445             if(
446                 attr.attr_type() == 'lineitem_marc_attr_definition' && 
447                 attr.attr_name() == name) 
448                     return attr 
449         } 
450     )[0];
451     return (attr) ? attr.attr_value() : '';
452 }
453
454 function saveChanges(doProrate, doClose, doReopen) {
455     createExtraCopies(
456         function() {
457             saveChangesPartTwo(doProrate, doClose, doReopen);
458         }
459     );
460 }
461
462 function saveChangesPartTwo(doProrate, doClose, doReopen) {
463     
464     progressDialog.show(true);
465
466     if(doReopen) {
467         invoice.complete('f');
468
469     } else {
470
471
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() );
482                 }
483                 
484                 // unflesh
485                 if(item.purchase_order() != null && typeof item.purchase_order() == 'object')
486                     item.purchase_order( item.purchase_order().id() );
487             }
488         }
489
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);
499
500                 for(var field in reg) {
501                     if(field != '_object')
502                         entry[field]( reg[field].getFormattedValue() );
503                 }
504                 
505                 // unflesh
506                 dojo.forEach(['purchase_order', 'lineitem'],
507                     function(field) {
508                         if(entry[field]() != null && typeof entry[field]() == 'object')
509                             entry[field]( entry[field]().id() );
510                     }
511                 );
512             }
513         }
514
515         if(!invoice) {
516             invoice = new fieldmapper.acqinv();
517             invoice.isnew(true);
518         } else {
519             invoice.ischanged(true); // for now, just always update
520         }
521
522         dojo.forEach(invoicePane.fieldList, 
523             function(field) {
524                 invoice[field.name]( field.widget.getFormattedValue() );
525             }
526         );
527
528         if(doClose) 
529             invoice.complete('t');
530     }
531
532     fieldmapper.standardRequest(
533         ['open-ils.acq', 'open-ils.acq.invoice.update'],
534         {
535             params : [openils.User.authtoken, invoice, updateEntries, updateItems],
536             oncomplete : function(r) {
537                 progressDialog.hide();
538                 var invoice = openils.Util.readResponse(r);
539                 if(invoice) {
540                     if(doProrate)
541                         return prorateInvoice(invoice);
542                     location.href = oilsBasePath + '/acq/invoice/view/' + invoice.id();
543                 }
544             }
545         }
546     );
547 }
548
549 function prorateInvoice(invoice) {
550     if(!confirm(localeStrings.INVOICE_CONFIRM_PRORATE)) return;
551     progressDialog.show(true);
552
553     fieldmapper.standardRequest(
554         ['open-ils.acq', 'open-ils.acq.invoice.apply_prorate'],
555         {
556             params : [openils.User.authtoken, invoice.id()],
557             oncomplete : function(r) {
558                 progressDialog.hide();
559                 var invoice = openils.Util.readResponse(r);
560                 if(invoice) {
561                     location.href = oilsBasePath + '/acq/invoice/view/' + invoice.id();
562                 }
563             }
564         }
565     );
566 }
567
568 function storeExtraCopies(liId, numExtra) {
569
570     dojo.byId('acq-invoice-extra-copies-message').innerHTML = 
571         dojo.string.substitute(
572             localeStrings.INVOICE_EXTRA_COPIES, [numExtra]);
573
574     var addCopyHandler;
575     addCopyHandler = dojo.connect(
576         extraCopiesGo, 
577         'onClick',
578         function() {
579             extraCopies[liId] = {
580                 numExtra : numExtra, 
581                 fund : extraCopiesFund.widget.attr('value')
582             }
583             extraItemsDialog.hide();
584             dojo.disconnect(addCopyHandler);
585         }
586     );
587
588     dojo.connect(
589        extraCopiesCancel, 
590        'onClick',
591        function() { extraItemsDialog.hide() }
592     );
593
594     extraItemsDialog.show();
595 }
596
597 function createExtraCopies(oncomplete) {
598
599     var lids = [];
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();
604             lid.isnew(true);
605             lid.lineitem(liId);
606             lid.fund(data.fund);
607             lid.recv_time('now');
608             lids.push(lid);
609         }
610     }
611
612     if(lids.length == 0) 
613         return oncomplete();
614
615     fieldmapper.standardRequest(
616         ['open-ils.acq', 'open-ils.acq.lineitem_detail.cud.batch'],
617         {
618             params : [openils.User.authtoken, lids, true],
619             oncomplete : function(r) {
620                 if(openils.Util.readResponse(r))
621                     oncomplete();
622             }
623         }
624     );
625
626 }
627
628
629 openils.Util.addOnLoad(init);
630
631