]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/acq/invoice/view.js
Merge branch 'master' of git.evergreen-ils.org:Evergreen-DocBook into doc_consolidati...
[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.authoritative'],
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         dijitArgs : {required : true},
73         parentNode : dojo.byId('acq-invoice-extra-copies-fund')
74     });
75     extraCopiesFund.build();
76 }
77
78 function renderInvoice() {
79
80     // in create mode, let the LI or PO render the invoice with seed data
81     if( !(cgi.param('create') && (attachPo || attachLi)) ) {
82         invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), invoice);
83     }
84
85     dojo.byId('acq-invoice-new-item').onclick = function() {
86         var item = new fieldmapper.acqii();
87         item.id(virtualId--);
88         item.isnew(true);
89         addInvoiceItem(item);
90     }
91
92     updateTotalCost();
93
94     if(invoice && openils.Util.isTrue(invoice.complete())) {
95
96         dojo.forEach( // hide widgets that should not be visible for a completed invoice
97             dojo.query('.hide-complete'), 
98             function(node) { openils.Util.hide(node); }
99         );
100
101         new openils.User().getPermOrgList(
102             'ACQ_INVOICE_REOPEN', 
103             function (orgs) {
104                 if(orgs.indexOf(invoice.receiver()) >= 0)
105                     openils.Util.show('acq-invoice-reopen-button-wrapper', 'inline');
106             }, 
107             true, 
108             true
109         );
110     }
111
112     if(invoice) {
113         dojo.forEach(
114             invoice.items(),
115             function(item) {
116                 addInvoiceItem(item);
117             }
118         );
119
120         dojo.forEach(
121             invoice.entries(),
122             function(entry) {
123                 addInvoiceEntry(entry);
124             }
125         );
126     }
127
128     if(attachLi) doAttachLi();
129     if(attachPo) doAttachPo();
130 }
131
132 function doAttachLi() {
133
134     //var invoiceArgs = {provider : lineitem.provider(), shipper : lineitem.provider()}; 
135     if(cgi.param('create')) {
136
137         fieldmapper.standardRequest(
138             ['open-ils.acq', 'open-ils.acq.lineitem.retrieve.authoritative'],
139             {
140                 params : [openils.User.authtoken, attachLi, {clear_marc:1}],
141                 oncomplete : function(r) {
142                     var li = openils.Util.readResponse(r);
143                     invoicePane = drawInvoicePane(
144                         dojo.byId('acq-view-invoice-div'), null, 
145                         {provider : li.provider(), shipper : li.provider()}
146                     );
147                 }
148             }
149         );
150     }
151
152     var entry = new fieldmapper.acqie();
153     entry.id(virtualId--);
154     entry.isnew(true);
155     entry.lineitem(attachLi);
156     addInvoiceEntry(entry);
157 }
158
159 function doAttachPo() {
160
161     fieldmapper.standardRequest(
162         ['open-ils.acq', 'open-ils.acq.purchase_order.retrieve'],
163         {   async: true,
164             params: [
165                 openils.User.authtoken, attachPo, 
166                 {flesh_lineitem_ids : true, flesh_po_items : true}
167             ],
168             oncomplete: function(r) {
169                 var po = openils.Util.readResponse(r);
170
171                 if(cgi.param('create')) {
172                     // render the invoice using some seed data from the PO
173                     var invoiceArgs = {provider : po.provider(), shipper : po.provider()}; 
174                     invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), null, invoiceArgs);
175                 }
176
177                 dojo.forEach(po.lineitems(), 
178                     function(lineitem) {
179                         var entry = new fieldmapper.acqie();
180                         entry.id(virtualId--);
181                         entry.isnew(true);
182                         entry.lineitem(lineitem);
183                         entry.purchase_order(po);
184                         addInvoiceEntry(entry);
185                     }
186                 );
187
188                 dojo.forEach(po.po_items(),
189                     function(poItem) {
190                         var item = new fieldmapper.acqii();
191                         item.id(virtualId--);
192                         item.isnew(true);
193                         item.fund(poItem.fund());
194                         item.title(poItem.title());
195                         item.author(poItem.author());
196                         item.note(poItem.note());
197                         item.inv_item_type(poItem.inv_item_type());
198                         item.purchase_order(po);
199                         item.po_item(poItem);
200                         addInvoiceItem(item);
201                     }
202                 );
203             }
204         }
205     );
206 }
207
208 function updateTotalCost() {
209
210     var totalCost = 0;    
211     for(var id in widgetRegistry.acqii) 
212         if(!widgetRegistry.acqii[id]._object.isdeleted())
213             totalCost += Number(widgetRegistry.acqii[id].cost_billed.getFormattedValue());
214     for(var id in widgetRegistry.acqie) 
215         if(!widgetRegistry.acqie[id]._object.isdeleted())
216             totalCost += Number(widgetRegistry.acqie[id].cost_billed.getFormattedValue());
217     totalInvoicedBox.attr('value', totalCost);
218
219     totalPaid = 0;    
220     for(var id in widgetRegistry.acqii) 
221         if(!widgetRegistry.acqii[id]._object.isdeleted())
222             totalPaid += Number(widgetRegistry.acqii[id].amount_paid.getFormattedValue());
223     for(var id in widgetRegistry.acqie) 
224         if(!widgetRegistry.acqie[id]._object.isdeleted())
225             totalPaid += Number(widgetRegistry.acqie[id].amount_paid.getFormattedValue());
226     totalPaidBox.attr('value', totalPaid);
227
228     var buttonsDisabled = false;
229
230     if(totalPaid > totalCost || totalPaid < 0) {
231         openils.Util.addCSSClass(totalPaidBox.domNode, 'acq-invoice-invalid-amount');
232         invoiceSaveButton.attr('disabled', true);
233         invoiceProrateButton.attr('disabled', true);
234         buttonsDisabled = true;
235     } else {
236         openils.Util.removeCSSClass(totalPaidBox.domNode, 'acq-invoice-invalid-amount');
237         invoiceSaveButton.attr('disabled', false);
238         invoiceProrateButton.attr('disabled', false);
239     }
240
241     if(totalCost < 0) {
242         openils.Util.addCSSClass(totalInvoicedBox.domNode, 'acq-invoice-invalid-amount');
243         invoiceSaveButton.attr('disabled', true);
244         invoiceProrateButton.attr('disabled', true);
245     } else {
246         openils.Util.removeCSSClass(totalInvoicedBox.domNode, 'acq-invoice-invalid-amount');
247         if(!buttonsDisabled) {
248             invoiceSaveButton.attr('disabled', false);
249             invoiceProrateButton.attr('disabled', false);
250         }
251     }
252
253     if(totalPaid == totalCost) { // XXX: too rigid?
254         invoiceCloseButton.attr('disabled', false);
255     } else {
256         invoiceCloseButton.attr('disabled', true);
257     }
258
259     balanceOwedBox.attr('value', (totalCost - totalPaid));
260 }
261
262
263 function registerWidget(obj, field, widget, callback) {
264     var blob = widgetRegistry[obj.classname];
265     if(!blob[obj.id()]) 
266         blob[obj.id()] = {_object : obj};
267     blob[obj.id()][field] = widget;
268     widget.build(
269         function(w, ww) {
270             dojo.connect(w, 'onChange', 
271                 function(newVal) { 
272                     obj.ischanged(true); 
273                     updateTotalCost();
274                 }
275             );
276             if(callback) callback(w, ww);
277         }
278     );
279     return widget;
280 }
281
282 function addInvoiceItem(item) {
283     itemTbody = dojo.byId('acq-invoice-item-tbody');
284     if(itemTemplate == null) {
285         itemTemplate = itemTbody.removeChild(dojo.byId('acq-invoice-item-template'));
286     }
287
288     var row = itemTemplate.cloneNode(true);
289     var itemType = itemTypes.filter(function(t) { return (t.code() == item.inv_item_type()) })[0];
290
291     dojo.forEach(
292         ['title', 'author', 'cost_billed', 'amount_paid'], 
293         function(field) {
294             
295             var args;
296             if(field == 'title' || field == 'author') {
297                 //args = {style : 'width:10em'};
298             } else if(field == 'cost_billed' || field == 'amount_paid') {
299                 args = {required : true, style : 'width: 8em'};
300             }
301             registerWidget(
302                 item,
303                 field,
304                 new openils.widget.AutoFieldWidget({
305                     fmClass : 'acqii',
306                     fmObject : item,
307                     fmField : field,
308                     readOnly : invoice && openils.Util.isTrue(invoice.complete()),
309                     dijitArgs : args,
310                     parentNode : nodeByName(field, row)
311                 })
312             )
313         }
314     );
315
316
317     /* ----------- fund -------------- */
318     var fundArgs = {
319         fmClass : 'acqii',
320         fmObject : item,
321         fmField : 'fund',
322         labelFormat : fundLabelFormat,
323         searchFormat : fundSearchFormat,
324         readOnly : invoice && openils.Util.isTrue(invoice.complete()),
325         dijitArgs : {required : true},
326         parentNode : nodeByName('fund', row)
327     }
328
329     if(item.fund_debit()) {
330         fundArgs.searchFilter = {'-or' : [{active : 't'}, {id : item.fund()}]};
331     } else {
332         fundArgs.searchFilter = {active : 't'}
333         if(itemType && openils.Util.isTrue(itemType.prorate()))
334             fundArgs.dijitArgs = {disabled : true};
335     }
336
337     var fundWidget = new openils.widget.AutoFieldWidget(fundArgs);
338     registerWidget(item, 'fund', fundWidget);
339
340     /* ---------- inv_item_type ------------- */
341
342     if(item.po_item()) {
343
344         // read-only item view for items that were the result of a po-item
345         var po = item.purchase_order();
346         var po_item = item.po_item();
347         var node = nodeByName('inv_item_type', row);
348         var itemType = itemTypes.filter(function(t) { return (t.code() == item.inv_item_type()) })[0];
349         orderDate = (!po.order_date()) ? '' : 
350                 dojo.date.locale.format(dojo.date.stamp.fromISOString(po.order_date()), {selector:'date'});
351
352         node.innerHTML = dojo.string.substitute(
353             localeStrings.INVOICE_ITEM_PO_DETAILS, 
354             [ 
355                 itemType.name(),
356                 oilsBasePath, 
357                 po.id(), 
358                 po.name(), 
359                 orderDate,
360                 po_item.estimated_cost() 
361             ]
362         );
363
364     } else {
365
366         registerWidget(
367             item,
368             'inv_item_type',
369             new openils.widget.AutoFieldWidget({
370                 fmObject : item,
371                 fmField : 'inv_item_type',
372                 parentNode : nodeByName('inv_item_type', row),
373                 readOnly : invoice && openils.Util.isTrue(invoice.complete()),
374                 dijitArgs : {required : true}
375             }),
376             function(w, ww) {
377                 // When the inv_item_type is set to prorate=true, don't allow the user the edit the fund
378                 // since this charge will be prorated against (potentially) multiple funds
379                 dojo.connect(w, 'onChange', 
380                     function() {
381                         if(!item.fund_debit()) {
382                             var itemType = itemTypes.filter(function(t) { return (t.code() == w.attr('value')) })[0];
383                             if(!itemType) return;
384                             if(openils.Util.isTrue(itemType.prorate())) {
385                                 fundWidget.widget.attr('disabled', true);
386                                 fundWidget.widget.attr('value', '');
387                             } else {
388                                 fundWidget.widget.attr('disabled', false);
389                             }
390                         }
391                     }
392                 );
393             }
394         );
395     }
396
397     nodeByName('delete', row).onclick = function() {
398         var cost = widgetRegistry.acqii[item.id()].cost_billed.getFormattedValue();
399         var msg = dojo.string.substitute(
400             localeStrings.INVOICE_CONFIRM_ITEM_DELETE, [
401                 cost || 0,
402                 widgetRegistry.acqii[item.id()].inv_item_type.getFormattedValue() || ''
403             ]
404         );
405         if(!confirm(msg)) return;
406         itemTbody.removeChild(row);
407         item.isdeleted(true);
408         if(item.isnew())
409             delete widgetRegistry.acqii[item.id()];
410         updateTotalCost();
411     }
412
413     itemTbody.appendChild(row);
414     updateTotalCost();
415 }
416
417 function updateReceiveLink(li) {
418     if (!invoiceId)
419         return; /* can't do this with unsaved invoices */
420
421     var link = dojo.byId("acq-view-invoice-receive-link");
422     if (link.onclick) return; /* only need to do this once */
423
424     /* don't do this if there's nothing receivable on the lineitem */
425     if (li.order_summary().recv_count() + li.order_summary().cancel_count() >=
426         li.order_summary().item_count())
427         return;
428
429     openils.Util.show("acq-view-invoice-receive");
430     link.onclick = function() { location.href =  oilsBasePath + '/acq/invoice/receive/' + invoiceId; };
431 }
432
433 function addInvoiceEntry(entry) {
434
435     openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-header'), 'hidden');
436     openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-thead'), 'hidden');
437     openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-tbody'), 'hidden');
438
439     entryTbody = dojo.byId('acq-invoice-entry-tbody');
440     if(entryTemplate == null) {
441         entryTemplate = entryTbody.removeChild(dojo.byId('acq-invoice-entry-template'));
442     }
443
444     if(dojo.query('[lineitem=' + entry.lineitem() +']', entryTbody)[0])
445         // Is it ever valid to have multiple entries for 1 lineitem in a single invoice?
446         return;
447
448     var row = entryTemplate.cloneNode(true);
449     row.setAttribute('lineitem', entry.lineitem());
450
451     openils.acq.Lineitem.fetchAndRender(
452         entry.lineitem(), {}, 
453         function(li, html) { 
454             entry.lineitem(li);
455             entry.purchase_order(li.purchase_order());
456             nodeByName('title_details', row).innerHTML = html;
457
458             updateReceiveLink(li);
459
460             dojo.forEach(
461                 ['inv_item_count', 'phys_item_count', 'cost_billed', 'amount_paid'],
462                 function(field) {
463                     var dijitArgs = {required : true, constraints : {min: 0}, style : 'width:6em'};
464                     if(!field.match(/count/)) dijitArgs.style = 'width:9em';
465                     if(entry.isnew() && field == 'phys_item_count') {
466                         // by default, attempt to pay for all non-canceled and as-of-yet-un-invoiced items
467                         var count = Number(li.order_summary().item_count() || 0) - 
468                                     Number(li.order_summary().cancel_count() || 0) -
469                                     Number(li.order_summary().invoice_count() || 0);
470                         if(count < 0) count = 0;
471                         dijitArgs.value = count;
472                     }
473                     registerWidget(
474                         entry, 
475                         field,
476                         new openils.widget.AutoFieldWidget({
477                             fmObject : entry,
478                             fmClass : 'acqie',
479                             fmField : field,
480                             dijitArgs : dijitArgs,
481                             readOnly : invoice && openils.Util.isTrue(invoice.complete()),
482                             parentNode : nodeByName(field, row)
483                         }),
484                         function(w) {    
485                             if(field == 'phys_item_count') {
486                                 dojo.connect(w, 'onChange', 
487                                     function() {
488                                         // staff entered a higher number in the receive field than was originally ordered
489                                         // taking into account already invoiced items
490                                         var extra = Number(this.attr('value')) - 
491                                             (Number(entry.lineitem().item_count()) - Number(entry.lineitem().order_summary().invoice_count()));
492                                         if(extra > 0) {
493                                             storeExtraCopies(entry, extra);
494                                         }
495                                     }
496                                 )
497                             }
498                         }
499                     );
500                 }
501             );
502         }
503     );
504
505     nodeByName('detach', row).onclick = function() {
506         var cost = widgetRegistry.acqie[entry.id()].cost_billed.getFormattedValue();
507         var idents = [];
508         dojo.forEach(['isbn', 'upc', 'issn'], 
509             function(ident) { 
510                 var val = liMarcAttr(entry.lineitem(), ident);
511                 if(val) idents.push(val); 
512             }
513         );
514
515         var msg = dojo.string.substitute(
516             localeStrings.INVOICE_CONFIRM_ENTRY_DETACH, [
517                 cost || 0,
518                 liMarcAttr(entry.lineitem(), 'title'),
519                 liMarcAttr(entry.lineitem(), 'author'),
520                 idents.join(',')
521             ]
522         );
523         if(!confirm(msg)) return;
524         entryTbody.removeChild(row);
525         entry.isdeleted(true);
526         if(entry.isnew())
527             delete widgetRegistry.acqie[entry.id()];
528         updateTotalCost();
529     }
530
531     entryTbody.appendChild(row);
532     updateTotalCost();
533 }
534
535 function liMarcAttr(lineitem, name) {
536     var attr = lineitem.attributes().filter(
537         function(attr) { 
538             if(
539                 attr.attr_type() == 'lineitem_marc_attr_definition' && 
540                 attr.attr_name() == name) 
541                     return attr 
542         } 
543     )[0];
544     return (attr) ? attr.attr_value() : '';
545 }
546
547 function saveChanges(doProrate, doClose, doReopen) {
548     createExtraCopies(
549         function() {
550             saveChangesPartTwo(doProrate, doClose, doReopen);
551         }
552     );
553 }
554
555 function saveChangesPartTwo(doProrate, doClose, doReopen) {
556     
557     progressDialog.show(true);
558
559     if(doReopen) {
560         invoice.complete('f');
561
562     } else {
563
564
565         var updateItems = [];
566         for(var id in widgetRegistry.acqii) {
567             var reg = widgetRegistry.acqii[id];
568             var item = reg._object;
569             if(item.ischanged() || item.isnew() || item.isdeleted()) {
570                 updateItems.push(item);
571                 if(item.isnew()) item.id(null);
572                 for(var field in reg) {
573                     if(field != '_object')
574                         item[field]( reg[field].getFormattedValue() );
575                 }
576                 
577                 // unflesh
578                 if(item.purchase_order() != null && typeof item.purchase_order() == 'object')
579                     item.purchase_order( item.purchase_order().id() );
580             }
581         }
582
583         var updateEntries = [];
584         for(var id in widgetRegistry.acqie) {
585             var reg = widgetRegistry.acqie[id];
586             var entry = reg._object;
587             if(entry.ischanged() || entry.isnew() || entry.isdeleted()) {
588                 entry.lineitem(entry.lineitem().id());
589                 entry.purchase_order(entry.purchase_order().id());
590                 updateEntries.push(entry);
591                 if(entry.isnew()) entry.id(null);
592
593                 for(var field in reg) {
594                     if(field != '_object')
595                         entry[field]( reg[field].getFormattedValue() );
596                 }
597                 
598                 // unflesh
599                 dojo.forEach(['purchase_order', 'lineitem'],
600                     function(field) {
601                         if(entry[field]() != null && typeof entry[field]() == 'object')
602                             entry[field]( entry[field]().id() );
603                     }
604                 );
605             }
606         }
607
608         if(!invoice) {
609             invoice = new fieldmapper.acqinv();
610             invoice.isnew(true);
611         } else {
612             invoice.ischanged(true); // for now, just always update
613         }
614
615         dojo.forEach(invoicePane.fieldList, 
616             function(field) {
617                 invoice[field.name]( field.widget.getFormattedValue() );
618             }
619         );
620
621         if(doClose) 
622             invoice.complete('t');
623     }
624
625     fieldmapper.standardRequest(
626         ['open-ils.acq', 'open-ils.acq.invoice.update'],
627         {
628             params : [openils.User.authtoken, invoice, updateEntries, updateItems],
629             oncomplete : function(r) {
630                 progressDialog.hide();
631                 var invoice = openils.Util.readResponse(r);
632                 if(invoice) {
633                     if(doProrate)
634                         return prorateInvoice(invoice);
635                     location.href = oilsBasePath + '/acq/invoice/view/' + invoice.id();
636                 }
637             }
638         }
639     );
640 }
641
642 function prorateInvoice(invoice) {
643     if(!confirm(localeStrings.INVOICE_CONFIRM_PRORATE)) return;
644     progressDialog.show(true);
645
646     fieldmapper.standardRequest(
647         ['open-ils.acq', 'open-ils.acq.invoice.apply_prorate'],
648         {
649             params : [openils.User.authtoken, invoice.id()],
650             oncomplete : function(r) {
651                 progressDialog.hide();
652                 var invoice = openils.Util.readResponse(r);
653                 if(invoice) {
654                     location.href = oilsBasePath + '/acq/invoice/view/' + invoice.id();
655                 }
656             }
657         }
658     );
659 }
660
661 function storeExtraCopies(entry, numExtra) {
662
663     dojo.byId('acq-invoice-extra-copies-message').innerHTML = 
664         dojo.string.substitute(
665             localeStrings.INVOICE_EXTRA_COPIES, [numExtra]);
666
667     var addCopyHandler;
668     addCopyHandler = dojo.connect(
669         extraCopiesGo, 
670         'onClick',
671         function() {
672             extraCopies[entry.lineitem().id()] = {
673                 numExtra : numExtra, 
674                 fund : extraCopiesFund.widget.attr('value')
675             }
676             extraItemsDialog.hide();
677             dojo.disconnect(addCopyHandler);
678         }
679     );
680
681     dojo.connect(
682         extraCopiesCancel, 
683         'onClick',
684         function() { 
685             widgetRegistry.acqie[entry.id()].phys_item_count.widget.attr('value', '');
686             extraItemsDialog.hide() 
687         }
688     );
689
690     extraItemsDialog.show();
691 }
692
693 function createExtraCopies(oncomplete) {
694
695     var lids = [];
696     for(var liId in extraCopies) {
697         var data = extraCopies[liId];
698         for(var i = 0; i < data.numExtra; i++) {
699             var lid = new fieldmapper.acqlid();
700             lid.isnew(true);
701             lid.lineitem(liId);
702             lid.fund(data.fund);
703             lid.recv_time('now');
704             lids.push(lid);
705         }
706     }
707
708     if(lids.length == 0) 
709         return oncomplete();
710
711     fieldmapper.standardRequest(
712         ['open-ils.acq', 'open-ils.acq.lineitem_detail.cud.batch'],
713         {
714             params : [openils.User.authtoken, lids, true],
715             oncomplete : function(r) {
716                 if(openils.Util.readResponse(r))
717                     oncomplete();
718             }
719         }
720     );
721
722 }
723
724
725 openils.Util.addOnLoad(init);
726
727