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