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