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