]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/web/js/ui/default/acq/common/li_table.js
Acq: complete improvements of receive/unreceive for lineitems and copies
[Evergreen.git] / Open-ILS / web / js / ui / default / acq / common / li_table.js
1 dojo.require('dojo.date.locale');
2 dojo.require('dojo.date.stamp');
3 dojo.require('dijit.form.Button');
4 dojo.require('dijit.form.TextBox');
5 dojo.require('dijit.form.FilteringSelect');
6 dojo.require('dijit.form.Textarea');
7 dojo.require('dijit.ProgressBar');
8 dojo.require('openils.User');
9 dojo.require('openils.Util');
10 dojo.require('openils.acq.Lineitem');
11 dojo.require('openils.acq.PO');
12 dojo.require('openils.acq.Picklist');
13 dojo.require('openils.widget.AutoFieldWidget');
14 dojo.require('dojo.data.ItemFileReadStore');
15 dojo.require('openils.widget.ProgressDialog');
16 dojo.require('openils.PermaCrud');
17 dojo.require('openils.XUL');
18
19 dojo.requireLocalization('openils.acq', 'acq');
20 var localeStrings = dojo.i18n.getLocalization('openils.acq', 'acq');
21 const XUL_OPAC_WRAPPER = 'chrome://open_ils_staff_client/content/cat/opac.xul';
22 var li_exportable_attrs = ["issn", "isbn", "upc"];
23
24 function nodeByName(name, context) {
25     return dojo.query('[name='+name+']', context)[0];
26 }
27
28
29 var liDetailBatchFields = ['fund', 'owning_lib', 'location', 'collection_code', 'circ_modifier', 'cn_label'];
30 var liDetailFields = liDetailBatchFields.concat(['barcode', 'note']);
31
32 function AcqLiTable() {
33
34     var self = this;
35     this.liCache = {};
36     this.plCache = {};
37     this.poCache = {};
38     this.dfaCache = [];
39     this.dfeOffset = 0;
40     this.toggleState = false;
41     this.tbody = dojo.byId('acq-lit-tbody');
42     this.selectors = [];
43     this.authtoken = openils.User.authtoken;
44     this.rowTemplate = this.tbody.removeChild(dojo.byId('acq-lit-row'));
45     this.copyTbody = dojo.byId('acq-lit-li-details-tbody');
46     this.copyRow = this.copyTbody.removeChild(dojo.byId('acq-lit-li-details-row'));
47     this.copyBatchRow = dojo.byId('acq-lit-li-details-batch-row');
48     this.copyBatchWidgets = {};
49     this.liNotesTbody = dojo.byId('acq-lit-notes-tbody');
50     this.liNotesRow = this.liNotesTbody.removeChild(dojo.byId('acq-lit-notes-row'));
51     this.realCopiesTbody = dojo.byId('acq-lit-real-copies-tbody');
52     this.realCopiesRow = this.realCopiesTbody.removeChild(dojo.byId('acq-lit-real-copies-row'));
53     this._copy_fields_for_acqdf = ['owning_lib', 'location'];
54
55     dojo.connect(acqLitLiActionsSelector, 'onChange', 
56         function() { 
57             self.applySelectedLiAction(this.attr('value')) 
58             acqLitLiActionsSelector.attr('value', '_');
59         });
60
61     acqLitCreatePoSubmit.onClick = function() {
62         acqLitPoCreateDialog.hide();
63         self._createPO(acqLitPoCreateDialog.getValues());
64     }
65
66     acqLitSavePlButton.onClick = function() {
67         acqLitSavePlDialog.hide();
68         self._savePl(acqLitSavePlDialog.getValues());
69     }
70
71     acqLitCancelLiStateButton.onClick = function() {
72         acqLitChangeLiStateDialog.hide();
73     }
74     acqLitSaveLiStateButton.onClick = function() {
75         acqLitChangeLiStateDialog.hide();
76         self._updateLiState(acqLitChangeLiStateDialog.getValues(), acqLitChangeLiStateDialog.attr('state'));
77     }
78
79
80     //dojo.byId('acq-lit-notes-new-button').onclick = function(){acqLitCreateLiNoteDialog.show();}
81
82     dojo.byId('acq-lit-select-toggle').onclick = function(){self.toggleSelect()};
83     dojo.byId('acq-lit-info-back-button').onclick = function(){self.show('list')};
84     dojo.byId('acq-lit-copies-back-button').onclick = function(){self.show('list')};
85     dojo.byId('acq-lit-notes-back-button').onclick = function(){self.show('list')};
86     dojo.byId('acq-lit-real-copies-back-button').onclick = function(){self.show('list')};
87
88     this.reset = function() {
89         while(self.tbody.childNodes[0])
90             self.tbody.removeChild(self.tbody.childNodes[0]);
91         self.selectors = [];
92     };
93     
94     this.setNext = function(handler) {
95         var link = dojo.byId('acq-lit-next');
96         if(handler) {
97             dojo.style(link, 'visibility', 'visible');
98             link.onclick = handler;
99         } else {
100             dojo.style(link, 'visibility', 'hidden');
101         }
102     };
103
104     this.setPrev = function(handler) {
105         var link = dojo.byId('acq-lit-prev');
106         if(handler) {
107             dojo.style(link, 'visibility', 'visible'); 
108             link.onclick = handler; 
109         } else {
110             dojo.style(link, 'visibility', 'hidden');
111         }
112     };
113
114     this.show = function(div) {
115         openils.Util.hide('acq-lit-table-div');
116         openils.Util.hide('acq-lit-info-div');
117         openils.Util.hide('acq-lit-li-details');
118         openils.Util.hide('acq-lit-notes-div');
119         openils.Util.hide('acq-lit-real-copies-div');
120         switch(div) {
121             case 'list':
122                 openils.Util.show('acq-lit-table-div');
123                 break;
124             case 'info':
125                 openils.Util.show('acq-lit-info-div');
126                 break;
127             case 'copies':
128                 openils.Util.show('acq-lit-li-details');
129                 break;
130             case 'real-copies':
131                 openils.Util.show('acq-lit-real-copies-div');
132                 break;
133             case 'notes':
134                 openils.Util.show('acq-lit-notes-div');
135                 break;
136             default:
137                 if(div) 
138                     openils.Util.show(div);
139         }
140     }
141
142     this.hide = function() {
143         this.show(null);
144     }
145
146     this.toggleSelect = function() {
147         if(self.toggleState) 
148             dojo.forEach(self.selectors, function(i){i.checked = false});
149         else 
150             dojo.forEach(self.selectors, function(i){i.checked = true});
151         self.toggleState = !self.toggleState;
152     };
153
154
155     /** @param all If true, assume all are selected */
156     this.getSelected = function(all) {
157         var selected = [];
158         dojo.forEach(self.selectors, 
159             function(i) { 
160                 if(i.checked || all)
161                     selected.push(self.liCache[i.parentNode.parentNode.getAttribute('li')]);
162             }
163         );
164         return selected;
165     };
166
167     this.setRowAttr = function(td, liWrapper, field, type) {
168         var val = liWrapper.findAttr(field, type || 'lineitem_marc_attr_definition') || '';
169         td.appendChild(document.createTextNode(val));
170     };
171
172     /**
173      * Inserts a single lineitem into the growing table of lineitems
174      * @param {Object} li The lineitem object to insert
175      */
176     this.addLineitem = function(li, skip_final_placement) {
177         this.liCache[li.id()] = li;
178
179         // sort the lineitem notes on edit_time
180         if(!li.lineitem_notes()) li.lineitem_notes([]);
181
182         var liWrapper = new openils.acq.Lineitem({lineitem:li});
183         var row = self.rowTemplate.cloneNode(true);
184         row.setAttribute('li', li.id());
185         var tds = dojo.query('[attr]', row);
186         dojo.forEach(tds, function(td) {self.setRowAttr(td, liWrapper, td.getAttribute('attr'), td.getAttribute('attr_type'));});
187         dojo.query('[name=source_label]', row)[0].appendChild(document.createTextNode(li.source_label()));
188
189         var isbn = liWrapper.findAttr('isbn', 'lineitem_marc_attr_definition');
190         if(isbn) {
191             // XXX media prefix for added content
192             dojo.query('[name=jacket]', row)[0].setAttribute('src', '/opac/extras/ac/jacket/small/' + isbn);
193         }
194
195         dojo.query('[attr=title]', row)[0].onclick = function() {self.drawInfo(li.id())};
196         dojo.query('[name=copieslink]', row)[0].onclick = function() {self.drawCopies(li.id())};
197         dojo.query('[name=notes_count]', row)[0].innerHTML = li.lineitem_notes().length;
198         dojo.query('[name=noteslink]', row)[0].onclick = function() {self.drawLiNotes(li)};
199
200         // show which PO this lineitem is a member of
201         if(li.purchase_order() && !this.isPO) {
202             var po = 
203                 this.poCache[li.purchase_order()] =
204                 this.poCache[li.purchase_order()] ||
205                 fieldmapper.standardRequest(
206                     ['open-ils.acq', 'open-ils.acq.purchase_order.retrieve'],
207                     {params: [
208                         this.authtoken, li.purchase_order(), {
209                             "flesh_price_summary": true,
210                             "flesh_lineitem_count": true
211                         }
212                     ]});
213             if(po && !this.isMeta) {
214                 openils.Util.show(nodeByName('po', row), 'inline');
215                 var link = nodeByName('po_link', row);
216                 link.setAttribute('href', oilsBasePath + '/acq/po/view/' + li.purchase_order());
217                 link.innerHTML = 'PO: ' + po.name(); // TODO i18n
218             }
219         }
220
221         // show which picklist this lineitem is a member of
222         if(li.picklist() && (this.isPO || this.isMeta)) {
223             var pl = 
224                 this.plCache[li.picklist()] = 
225                 this.plCache[li.picklist()] || 
226                 fieldmapper.standardRequest(
227                     ['open-ils.acq', 'open-ils.acq.picklist.retrieve'],
228                     {params: [this.authtoken, li.picklist()]});
229             if(pl) {
230                 openils.Util.show(nodeByName('pl', row), 'inline');
231                 var link = nodeByName('pl_link', row);
232                 link.setAttribute('href', oilsBasePath + '/acq/picklist/view/' + li.picklist());
233                 link.innerHTML = 'PL: '+pl.name(); // TODO i18n
234             }
235         }
236
237         var countNode = nodeByName('count', row);
238         var count = li.item_count() || 0;
239         if (typeof(this._copy_count_cb) == "function") {
240             this._copy_count_cb(li.id(), count);
241         }
242         countNode.innerHTML = count;
243         countNode.id = 'acq-lit-copy-count-label-' + li.id();
244
245         // lineitem state
246         nodeByName('li_state', row).innerHTML = li.state(); // TODO i18n state labels
247         // lineitem price
248         var priceInput = dojo.query('[name=price]', row)[0];
249         var priceData = liWrapper.getPrice();
250         priceInput.value = (priceData) ? priceData.price : '';
251         priceInput.onchange = function() { self.updateLiPrice(priceInput, li) };
252
253         // show either "mark received" or "unreceive" as appropriate
254         this.updateLiReceivedness(li, row);
255
256         if (!skip_final_placement) {
257             self.tbody.appendChild(row);
258             self.selectors.push(dojo.query('[name=selectbox]', row)[0]);
259         } else {
260             return row;
261         }
262     };
263
264     this.updateLiReceivedness = function(li, row) {
265         if (typeof(row) == "undefined")
266             row = dojo.query('tr[li="' + li.id() + '"]', "acq-lit-tbody")[0];
267
268         var recv_link = nodeByName("receive_link", row);
269         var unrecv_link = nodeByName("unreceive_link", row);
270         var real_copies_link = nodeByName("real_copies_link", row);
271
272         /* handle row coloring for based on LI state */
273         openils.Util.removeCSSClass(row, /^oils-acq-li-state-/);
274         openils.Util.addCSSClass(row, "oils-acq-li-state-" + li.state());
275
276         /* handle links that appear/disappear based on whether LI is received */
277         if (this.isPO) {
278             var self = this;
279             switch(li.state()) {
280                 case "on-order":
281                     openils.Util.hide(real_copies_link);
282                     openils.Util.hide(unrecv_link);
283                     openils.Util.show(recv_link, "inline");
284                     recv_link.onclick = function() { self.issueReceive(li); };
285                     return;
286                 case "received":
287                     openils.Util.hide(recv_link);
288                     openils.Util.show(unrecv_link, "inline");
289                     unrecv_link.onclick = function() {
290                         if (confirm(localeStrings.UNRECEIVE_LI))
291                             self.issueReceive(li, /* rollback */ true);
292                     };
293                     // TODO we should allow editing before receipt, in which case the
294                     // test should be "if 1 or more real (acp) copies exist
295                     openils.Util.show(real_copies_link);
296                     real_copies_link.onclick = function() {
297                         self.showRealCopies(li);
298                     }
299                     return;
300             }
301         }
302
303         openils.Util.hide(recv_link);
304         openils.Util.hide(unrecv_link);
305         openils.Util.hide(real_copies_link);
306     };
307
308
309     /**
310      * Draws and shows the lineitem notes pane
311      */
312     this.drawLiNotes = function(li) {
313         var self = this;
314
315         li.lineitem_notes(
316             li.lineitem_notes().sort(
317                 function(a, b) { 
318                     if(a.edit_time() < b.edit_time()) return 1;
319                     return -1;
320                 }
321             )
322         );
323
324         while(this.liNotesTbody.childNodes[0])
325             this.liNotesTbody.removeChild(this.liNotesTbody.childNodes[0]);
326         this.show('notes');
327
328         acqLitCreateLiNoteSubmit.onClick = function() {
329             var value = acqLitCreateNoteText.attr('value');
330             if(!value) return;
331             var note = new fieldmapper.acqlin();
332             note.isnew(true);
333             note.value(value);
334             note.lineitem(li.id());
335             self.updateLiNotes(li, note);
336         }
337
338         dojo.byId('acq-lit-notes-save-button').onclick = function() {
339             self.updateLiNotes(li);
340         }
341
342         dojo.forEach(li.lineitem_notes(), function(note) { self.addLiNote(li, note) });
343     }
344
345     /**
346      * Draws a single lineitem note in the notes pane
347      */
348     this.addLiNote = function(li, note) {
349         if(note.isdeleted()) return;
350         var self = this;
351         var row = self.liNotesRow.cloneNode(true);
352         dojo.query('[name=value]', row)[0].innerHTML = note.value();
353
354         dojo.query('[name=delete]', row)[0].onclick = function() {
355             note.isdeleted(true);
356             self.liNotesTbody.removeChild(row);
357         };
358
359         if(note.edit_time()) {
360             dojo.query('[name=edit_time]', row)[0].innerHTML = 
361                 dojo.date.locale.format(
362                     dojo.date.stamp.fromISOString(note.edit_time()), 
363                     {formatLength:'short'});
364         }
365
366         self.liNotesTbody.appendChild(row);
367     }
368
369     /**
370      * Updates any new/changed/deleted notes on the server
371      */
372     this.updateLiNotes = function(li, newNote) {
373
374         var notes;
375         if(newNote) {
376             notes = [newNote];
377         } else {
378             notes = li.lineitem_notes().filter(
379                 function(note) {
380                     if(note.ischanged() || note.isnew() || note.isdeleted())
381                         return note;
382                 }
383             );
384         }
385
386         if(notes.length == 0) return;
387         progressDialog.show();
388
389         fieldmapper.standardRequest(
390             ['open-ils.acq', 'open-ils.acq.lineitem_note.cud.batch'],
391             {   async : true,
392                 params : [this.authtoken, notes],
393                 onresponse : function(r) {
394                     var resp = openils.Util.readResponse(r);
395
396                     if(resp.complete) {
397
398                         if(!newNote) {
399                             // remove the old changed notes
400                             var list = [];
401                             dojo.forEach(li.lineitem_notes(), 
402                                 function(note) {
403                                     if(!(note.ischanged() || note.isnew() || note.isdeleted()))
404                                         list.push(note);
405                                 }
406                             );
407                             li.lineitem_notes(list);
408                         }
409
410                         progressDialog.hide();
411                         self.drawLiNotes(li);
412                         return;
413                     }
414
415                     progressDialog.update(resp);
416                     var newnote = resp.note;
417
418                     if(!newnote.isdeleted()) {
419                         newnote.isnew(false);
420                         newnote.ischanged(false);
421                         li.lineitem_notes().push(newnote);
422                     }
423                 },
424             }
425         );
426     }
427
428     this.updateLiPrice = function(input, li) {
429
430         var price = input.value;
431         var liWrapper = new openils.acq.Lineitem({lineitem:li});
432         var oldPrice = liWrapper.getPrice() || null;
433
434         if(oldPrice) oldPrice = oldPrice.price;
435         if(price == oldPrice) return;
436
437         fieldmapper.standardRequest(
438             ['open-ils.acq', 'open-ils.acq.lineitem.price.set'],
439             {   async : true,
440                 params : [this.authtoken, li.id(), price],
441                 oncomplete : function(r) {
442                     openils.Util.readResponse(r);
443                 }
444             }
445         );
446     }
447
448     this.removeLineitem = function(liId) {
449         this.tbody.removeChild(dojo.query('[li='+liId+']', this.tbody)[0]);
450         delete this.liCache[liId];
451         //selected.push(self.liCache[i.parentNode.parentNode.getAttribute('li')]);
452     }
453
454     this.drawInfo = function(liId) {
455         this.show('info');
456         openils.acq.Lineitem.fetchAttrDefs(
457             function() { 
458                 self._fetchLineitem(liId, function(li){self._drawInfo(li);}); 
459             } 
460         );
461     };
462
463     this._fetchLineitem = function(liId, handler) {
464
465         var li = this.liCache[liId];
466         if(li && li.marc() && li.lineitem_details())
467             return handler(li);
468         
469         fieldmapper.standardRequest(
470             ['open-ils.acq', 'open-ils.acq.lineitem.retrieve'],
471             {   async: true,
472
473                 params: [self.authtoken, liId, {
474                     flesh_attrs: true,
475                     flesh_li_details: true,
476                     flesh_fund_debit: true }],
477
478                 oncomplete: function(r) {
479                     var li = openils.Util.readResponse(r);
480                     handler(li)
481                 }
482             }
483         );
484     };
485
486     this._drawInfo = function(li) {
487
488         acqLitEditOrderMarc.onClick = function() { self.editOrderMarc(li); }
489
490         if(li.eg_bib_id()) {
491             openils.Util.hide('acq-lit-marc-order-record-label');
492             openils.Util.hide(acqLitEditOrderMarc.domNode);
493             openils.Util.show('acq-lit-marc-real-record-label');
494         } else {
495             openils.Util.show('acq-lit-marc-order-record-label');
496             openils.Util.show(acqLitEditOrderMarc.domNode);
497             openils.Util.hide('acq-lit-marc-real-record-label');
498         }
499
500         this.drawMarcHTML(li);
501         this.infoTbody = dojo.byId('acq-lit-info-tbody');
502
503         if(!this.infoRow)
504             this.infoRow = this.infoTbody.removeChild(dojo.byId('acq-lit-info-row'));
505         while(this.infoTbody.childNodes[0])
506             this.infoTbody.removeChild(this.infoTbody.childNodes[0]);
507
508         for(var i = 0; i < li.attributes().length; i++) {
509             var attr = li.attributes()[i];
510             var row = this.infoRow.cloneNode(true);
511
512             var type = attr.attr_type().replace(/lineitem_(.*)_attr_definition/, '$1');
513             var name = openils.acq.Lineitem.attrDefs[type].filter(
514                 function(a) {
515                     return (a.code() == attr.attr_name());
516                 }
517             ).pop().description();
518
519             dojo.query('[name=label]', row)[0].appendChild(document.createTextNode(name));
520             dojo.query('[name=value]', row)[0].appendChild(document.createTextNode(attr.attr_value()));
521             this.infoTbody.appendChild(row);
522         }
523
524         if(li.eg_bib_id()) {
525             openils.Util.show('acq-lit-info-cat-link');
526             var link = dojo.byId('acq-lit-info-cat-link').getElementsByTagName('a')[0];
527
528             if(openils.XUL.isXUL()) {
529
530                 var makeRecTab = function() {
531                                     xulG.new_tab(
532                         XUL_OPAC_WRAPPER,
533                                             {tab_name: localeStrings.XUL_RECORD_DETAIL_PAGE, browser:false},
534                                             {
535                             no_xulG : false, 
536                             show_nav_buttons : true, 
537                             show_print_button : true, 
538                             opac_url : xulG.url_prefix(xulG.urls.opac_rdetail + '?r=' + li.eg_bib_id())
539                         }
540                     );
541                 }
542                 link.setAttribute('href', 'javascript:void(0);');
543                 link.onclick = makeRecTab;
544
545             } else {
546                 var href = link.getAttribute('href');
547                 if(href.match(/=$/))
548                     link.setAttribute('href',  href + li.eg_bib_id());
549             }
550         } else {
551             openils.Util.hide('acq-lit-info-cat-link');
552         }
553     };
554
555     this.drawMarcHTML = function(li) {
556         var params = [null, true, li.marc()];
557         if(li.eg_bib_id()) 
558             params = [li.eg_bib_id(), true];
559
560         fieldmapper.standardRequest(
561             ['open-ils.search', 'open-ils.search.biblio.record.html'],
562             {   async: true,
563                 params: params,
564                 oncomplete: function(r) {
565                     dojo.byId('acq-lit-marc-div').innerHTML = 
566                         openils.Util.readResponse(r);
567                 }
568             }
569         );
570     }
571
572     this.drawCopies = function(liId) {
573         this.show('copies');
574         var self = this;
575         this.copyCache = {};
576         this.copyWidgetCache = {};
577         this.oldCopyWidgetCache = {};
578         this.dfaCache = [];
579         this.dfeOffset = 0;
580
581         acqLitSaveCopies.onClick = function() { self.saveCopyChanges(liId) };
582         acqLitBatchUpdateCopies.onClick = function() { self.batchCopyUpdate() };
583         acqLitCopyCountInput.attr('value', '0');
584
585         while(this.copyTbody.childNodes[0])
586             this.copyTbody.removeChild(this.copyTbody.childNodes[0]);
587
588         this._drawBatchCopyWidgets();
589
590         this._fetchDistribFormulas(
591             function() {
592                 openils.acq.Lineitem.fetchAttrDefs(
593                     function() { 
594                         self._fetchLineitem(liId, function(li){self._drawCopies(li);}); 
595                     } 
596                 );
597             }
598         );
599     };
600
601     /**
602      * Insert a new row into the distribution formula selection form
603      */
604     this._addDistribFormulaRow = function() {
605         var self = this;
606
607         if(!self.distribFormulaStore) {
608             // no formulas, hide the form
609             openils.Util.hide('acq-lit-distrib-formula-tbody');
610             return;
611         }
612
613         if(!this.distribFormulaTemplate) 
614             this.distribFormulaTemplate = 
615                 dojo.byId('acq-lit-distrib-formula-tbody').removeChild(dojo.byId('acq-lit-distrib-form-row'));
616
617         var row = dojo.byId('acq-lit-distrib-formula-tbody').appendChild(this.distribFormulaTemplate.cloneNode(true));
618
619         var selector = new dijit.form.FilteringSelect(
620             {store : self.distribFormulaStore}, 
621             nodeByName('selector', row)
622         );
623
624         var apply = new dijit.form.Button(
625             {"label": localeStrings.APPLY},
626             nodeByName('set_button', row)
627         ); 
628
629         var reset = new dijit.form.Button(
630             {"label": localeStrings.RESET_FORMULAE, "disabled": true},
631             nodeByName("reset_button", row)  
632         );
633
634         dojo.connect(apply, 'onClick', 
635             function() {
636                 var form_id = selector.attr('value');
637                 if(!form_id) return;
638                 self._applyDistribFormula(form_id);
639                 reset.attr("disabled", false);
640             }
641         );
642
643         dojo.connect(reset, 'onClick', 
644             function() {
645                 self.restoreCopyFieldsBeforeDF();
646                 self.dfaCache = [];
647                 self.dfeOffset = 0;
648                 reset.attr("disabled", "true");
649             }
650         );
651
652     };
653
654     /**
655      * Applies a distrib formula to the current set of copies
656      */
657     this._applyDistribFormula = function(formula) {
658         if(!formula) return;
659
660         formula = this.distribForms.filter(
661             function(form) { return form.id() == formula; }
662         )[0];
663
664         var copyRows = dojo.query('tr', self.copyTbody);
665
666         if (this.dfeOffset >= copyRows.length) {
667             alert(localeStrings.OUT_OF_COPIES);
668             return;
669         }
670
671         var entries_applied = 0;
672         for(
673             var rowIndex = this.dfeOffset;
674             rowIndex < copyRows.length;
675             rowIndex++
676         ) {
677             
678             var row = copyRows[rowIndex];
679             var copy_id = row.getAttribute('copy_id');
680             var copyWidgets = this.copyWidgetCache[copy_id];
681             var entryIndex = this.dfeOffset;
682             var entry = null;
683
684             // find the correct entry for the current row
685             dojo.forEach(formula.entries(), 
686                 function(e) {
687                     if(!entry) {
688                         entryIndex += e.item_count();
689                         if(entryIndex > rowIndex)
690                             entry = e;
691                     }
692                 }
693             );
694
695             if(entry) {
696                 
697                 //console.log("rowIndex = " + rowIndex + ", entry = " + entry.id() + ", entryIndex=" + 
698                 //  entryIndex + ", owning_lib = " + entry.owning_lib() + ", location = " + entry.location());
699     
700                 entries_applied++;
701                 this.saveCopyFieldsBeforeDF(copy_id);
702                 this._copy_fields_for_acqdf.forEach(
703                     function(field) {
704                         if(entry[field]()) {
705                             copyWidgets[field].attr('value', (entry[field]()));
706                         }
707                     }
708                 );
709             }
710         }
711
712         if (entries_applied) {
713             this.dfaCache.push(formula.id());
714             this.dfeOffset += entries_applied;
715         };
716     };
717
718     this.saveCopyFieldsBeforeDF = function(copy_id) {
719         var self = this;
720         if (!this.oldCopyWidgetCache[copy_id]) {
721             var copyWidgets = this.copyWidgetCache[copy_id];
722
723             this.oldCopyWidgetCache[copy_id] = {};
724             this._copy_fields_for_acqdf.forEach(
725                 function(f) {
726                     self.oldCopyWidgetCache[copy_id][f] =
727                         copyWidgets[f].attr("value");
728                 }
729             );
730         }
731     };
732
733     this.restoreCopyFieldsBeforeDF = function() {
734         var self = this;
735         for (var copy_id in this.oldCopyWidgetCache) {
736             this._copy_fields_for_acqdf.forEach(
737                 function(f) {
738                     self.copyWidgetCache[copy_id][f].attr(
739                         "value", self.oldCopyWidgetCache[copy_id][f]
740                     );
741                 }
742             );
743         }
744     };
745
746     this._fetchDistribFormulas = function(onload) {
747         if(this.distribForms) {
748             onload();
749         } else {
750             var self = this;
751             fieldmapper.standardRequest(
752                 ['open-ils.acq', 'open-ils.acq.distribution_formula.ranged.retrieve.atomic'],
753                 {   async: true,
754                     params: [openils.User.authtoken],
755                     oncomplete: function(r) {
756                         self.distribForms = openils.Util.readResponse(r);
757                         if(!self.distribForms || self.distribForms.length == 0) {
758                             self.distribForms = [];
759                         } else {
760                             self.distribFormulaStore =
761                                 new dojo.data.ItemFileReadStore(
762                                     {data:acqdf.toStoreData(self.distribForms)}
763                                 );
764                         }
765                         self._addDistribFormulaRow();
766                         onload();
767                     }
768                 }
769             );
770         }
771     }
772
773     this._drawBatchCopyWidgets = function() {
774         var row = this.copyBatchRow;
775         dojo.forEach(liDetailBatchFields, 
776             function(field) {
777                 if(self.copyBatchRowDrawn) {
778                     self.copyBatchWidgets[field].attr('value', null);
779                 } else {
780                     var widget = new openils.widget.AutoFieldWidget({
781                         fmField : field,
782                         fmClass : 'acqlid',
783                         parentNode : dojo.query('[name='+field+']', row)[0],
784                         orgLimitPerms : ['CREATE_PICKLIST'],
785                         dijitArgs : {required:false},
786                         forceSync : true
787                     });
788                     widget.build(
789                         function(w, ww) {
790                             self.copyBatchWidgets[field] = w;
791                         }
792                     );
793                 }
794             }
795         );
796         this.copyBatchRowDrawn = true;
797     };
798
799     this.batchCopyUpdate = function() {
800         var self = this;
801         for(var k in this.copyWidgetCache) {
802             var cache = this.copyWidgetCache[k];
803             dojo.forEach(liDetailBatchFields, function(f) {
804                 var newval = self.copyBatchWidgets[f].attr('value');
805                 if(newval) cache[f].attr('value', newval);
806             });
807         }
808     };
809
810     this._drawCopies = function(li) {
811         var self = this;
812
813         // this button sets the total number of copies for a given lineitem
814         acqLitAddCopyCount.onClick = function() { 
815             var count = acqLitCopyCountInput.attr('value');
816
817             // add new rows
818             while(self.copyCount() < count)
819                 self.addCopy(li); 
820             
821             // delete rows if necessary
822             var diff = self.copyCount() - count;
823             if(diff > 0) {
824                 var rows = dojo.query('tr', self.copyTbody).reverse().slice(0, diff);
825                 if(confirm(dojo.string.substitute(localeStrings.DELETE_LI_COPIES_CONFIRM, [diff]))) {
826                     dojo.forEach(rows, function(row) {self.deleteCopy(row); });
827                 } else {
828                     acqLitCopyCountInput.attr('value', self.copyCount()+'');
829                 }
830             }
831         }
832
833
834         if(li.lineitem_details().length > 0) {
835             dojo.forEach(li.lineitem_details(),
836                 function(copy) {
837                     self.addCopy(li, copy);
838                 }
839             );
840         } else {
841             self.addCopy(li);
842         }
843     };
844
845     this.copyCount = function() {
846         var count = 0;
847         for(var id in this.copyCache) {
848             if(!this.copyCache[id].isdeleted())
849                 count++;
850         }
851         return count;
852     }
853
854     this.virtCopyId = -1;
855     this.addCopy = function(li, copy) {
856         var row = this.copyRow.cloneNode(true);
857         this.copyTbody.appendChild(row);
858         var self = this;
859
860         if(!copy) {
861             copy = new fieldmapper.acqlid();
862             copy.isnew(true);
863             copy.id(this.virtCopyId--);
864             copy.lineitem(li.id());
865         }
866
867         this.copyCache[copy.id()] = copy;
868         row.setAttribute('copy_id', copy.id());
869         self.copyWidgetCache[copy.id()] = {};
870
871         acqLitCopyCountInput.attr('value', self.copyCount()+'');
872
873         dojo.forEach(liDetailFields,
874             function(field) {
875                 var widget = new openils.widget.AutoFieldWidget({
876                     fmObject : copy,
877                     fmField : field,
878                     fmClass : 'acqlid',
879                     parentNode : dojo.query('[name='+field+']', row)[0],
880                     orgLimitPerms : ['CREATE_PICKLIST', 'CREATE_PURCHASE_ORDER'],
881                     readOnly : Boolean(copy.eg_copy_id())
882                 });
883                 widget.build(
884                     // make sure we capture the value from any async widgets
885                     function(w, ww) { 
886                         copy[field](ww.getFormattedValue()) 
887                         self.copyWidgetCache[copy.id()][field] = w;
888                     }
889                 );
890                 dojo.connect(widget.widget, 'onChange', 
891                     function(val) { 
892                         if(copy.isnew() || val != copy[field]()) {
893                             // prevent setting ischanged() automatically on widget load for existing copies
894                             copy[field](widget.getFormattedValue()) 
895                             copy.ischanged(true);
896                         }
897                     }
898                 );
899             }
900         );
901
902         this.updateLidReceivedness(copy, row);
903     };
904
905     this.updateLidReceivedness = function(copy, row) {
906         if (typeof(row) == "undefined") {
907             row = dojo.query(
908                 'tr[copy_id="' + copy.id() + '"]', this.copyTbody
909             )[0];
910         }
911
912         var self = this;
913         var recv_link = nodeByName("receive", row);
914         var unrecv_link = nodeByName("unreceive", row);
915         var del_link = nodeByName("delete", row);
916
917         if (this.isPO) {
918             openils.Util.hide(del_link.parentNode);
919
920             /* Avoid showing (un)receive links for virtual copies */
921             if (copy.id() > 0) {
922                 if(copy.recv_time()) {
923                     openils.Util.hide(recv_link);
924                     openils.Util.show(unrecv_link);
925                     unrecv_link.onclick = function() {
926                         if (confirm(localeStrings.UNRECEIVE_LID))
927                             self.issueReceive(copy, /* rollback */ true);
928                     };
929                 } else {
930                     openils.Util.hide(unrecv_link);
931                     openils.Util.show(recv_link);
932                     recv_link.onclick = function() { self.issueReceive(copy); };
933                 }
934             } else {
935                 openils.Util.hide(unrecv_link);
936                 openils.Util.hide(recv_link);
937             }
938         } else {
939             openils.Util.hide(unrecv_link);
940             openils.Util.hide(recv_link);
941
942             del_link.onclick = function() { self.deleteCopy(row) };
943             openils.Util.show(del_link.parentNode);
944         }
945     }
946
947     this.deleteCopy = function(row) {
948         var copy = this.copyCache[row.getAttribute('copy_id')];
949         copy.isdeleted(true);
950         if(copy.isnew())
951             delete this.copyCache[copy.id()];
952         this.copyTbody.removeChild(row);
953     }
954
955     this.saveCopyChanges = function(liId) {
956         var self = this;
957         var copies = [];
958
959
960         var total = 0;
961         for(var id in this.copyCache) {
962             var c = this.copyCache[id];
963             if(!c.isdeleted()) total++;
964             if(c.isnew() || c.ischanged() || c.isdeleted()) {
965                 if(c.id() < 0) c.id(null);
966                 copies.push(c);
967             }
968         }
969
970         if (typeof(this._copy_count_cb) == "function") {
971             this._copy_count_cb(liId, total);
972         }
973
974         dojo.byId('acq-lit-copy-count-label-' + liId).innerHTML = total;
975
976
977         if (copies.length > 0) {
978             openils.Util.show("acq-lit-update-copies-progress");
979             fieldmapper.standardRequest(
980                 ['open-ils.acq', 'open-ils.acq.lineitem_detail.cud.batch'],
981                 {   async: true,
982                     params: [openils.User.authtoken, copies],
983                     onresponse: function(r) {
984                         var res = openils.Util.readResponse(r);
985                         litUpdateCopiesProgress.update(res);
986                     },
987                     oncomplete: function() {
988                         self.drawCopies(liId);
989                         openils.Util.hide("acq-lit-update-copies-progress");
990                     }
991                 }
992             );
993         }
994
995         if (this.dfaCache.length > 0) {
996             var oldlength = this.dfaCache.length;
997
998             fieldmapper.standardRequest(
999                 ["open-ils.acq",
1000                 "open-ils.acq.distribution_formula.record_application"],
1001                 {
1002                     "async": true,
1003                     "params": [openils.User.authtoken, this.dfaCache, liId],
1004                     "onresponse": function(r) {
1005                         var res = openils.Util.readResponse(r);
1006                         if (res && res.length != oldlength)
1007                             alert(localeStrings.DFA_NOT_ALL);
1008                     }
1009                 }
1010             );
1011             this.dfaCache = [];
1012         }
1013     }
1014
1015     this.applySelectedLiAction = function(action) {
1016         var self = this;
1017         switch(action) {
1018
1019             case 'delete_selected':
1020                 this._deleteLiList(self.getSelected());
1021                 break;
1022
1023             case 'create_order':
1024
1025                 if(!this.createPoProviderSelector) {
1026                     var widget = new openils.widget.AutoFieldWidget({
1027                         fmField : 'provider',
1028                         fmClass : 'acqpo',
1029                         searchFilter: {"active": "t"},
1030                         parentNode : dojo.byId('acq-lit-po-provider'),
1031                     });
1032                     widget.build(
1033                         function(w) { self.createPoProviderSelector = w; }
1034                     );
1035                 }
1036
1037                 if(!this.createPoAgencySelector) {
1038                     var widget = new openils.widget.AutoFieldWidget({
1039                         fmField : 'ordering_agency',
1040                         fmClass : 'acqpo',
1041                         parentNode : dojo.byId('acq-lit-po-agency'),
1042                         orgLimitPerms : ['CREATE_PURCHASE_ORDER'],
1043                     });
1044                     widget.build(
1045                         function(w) { self.createPoAgencySelector = w; }
1046                     );
1047                 }
1048
1049          
1050                 acqLitPoCreateDialog.show();
1051                 break;
1052
1053             case 'save_picklist':
1054                 this._loadPLSelect();
1055                 acqLitSavePlDialog.show();
1056                 break;
1057
1058             case 'selector_ready':
1059             case 'order_ready':
1060                 acqLitChangeLiStateDialog.attr('state', action.replace('_', '-'));
1061                 acqLitChangeLiStateDialog.show();
1062                 break;
1063
1064             case 'print_po':
1065                 this.printPO();
1066                 break;
1067
1068             case 'receive_po':
1069                 this.receivePO();
1070                 break;
1071
1072             case 'rollback_receive_po':
1073                 this.rollbackPoReceive();
1074                 break;
1075
1076             case 'create_assets':
1077                 this.createAssets();
1078                 break;
1079
1080             case 'export_attr_list':
1081                 this.chooseExportAttr();
1082                 break;
1083
1084             case 'add_brief_record':
1085                 if(this.isPO)
1086                     location.href = oilsBasePath + '/acq/picklist/brief_record?po=' + this.isPO;
1087                 else
1088                     location.href = oilsBasePath + '/acq/picklist/brief_record?pl=' + this.isPL;
1089         }
1090     }
1091
1092     this.createAssets = function() {
1093         if(!this.isPO) return;
1094         if(!confirm(localeStrings.CREATE_PO_ASSETS_CONFIRM)) return;
1095         this.show('acq-lit-progress-numbers');
1096         var self = this;
1097         fieldmapper.standardRequest(
1098             ['open-ils.acq', 'open-ils.acq.purchase_order.assets.create'],
1099             {   async: true,
1100                 params: [this.authtoken, this.isPO],
1101                 onresponse: function(r) {
1102                     var resp = openils.Util.readResponse(r);
1103                     self._updateProgressNumbers(resp, true);
1104                 }
1105             }
1106         );
1107     }
1108
1109     this.chooseExportAttr = function() {
1110         if (!acqLitExportAttrSelector._li_setup) {
1111             var self = this;
1112             acqLitExportAttrSelector.store = new dojo.data.ItemFileReadStore(
1113                 {
1114                     "data": acqliad.toStoreData(
1115                         (new openils.PermaCrud()).search(
1116                             "acqliad", {"code": li_exportable_attrs}
1117                         )
1118                     )
1119                 }
1120             );
1121             acqLitExportAttrSelector.setValue();
1122             acqLitExportAttrButton.onClick = function(){self.exportAttrList();};
1123             acqLitExportAttrSelector._li_setup = true;
1124         }
1125         openils.Util.show("acq-lit-export-attr-holder", "inline");
1126     };
1127
1128     this.exportAttrList = function() {
1129         var attr_def = acqLitExportAttrSelector.item;
1130         var li_list = this.getSelected();
1131         var value_list = li_list.map(
1132             function(li) {
1133                 return (new openils.acq.Lineitem({"lineitem": li})).findAttr(
1134                     attr_def.code, "lineitem_marc_attr_definition"
1135                 );
1136             }
1137         ).filter(function(attr) { return Boolean(attr); });
1138
1139         if (value_list.length > 0) {
1140             if (value_list.length < li_list.length) {
1141                 if (!confirm(
1142                     dojo.string.substitute(
1143                         localeStrings.EXPORT_SHORT_LIST, [attr_def.description]
1144                     )
1145                 )) {
1146                     return;
1147                 }
1148             }
1149             try {
1150                 openils.XUL.contentToFileSaveDialog(
1151                     value_list.join("\n"),
1152                     localeStrings.EXPORT_SAVE_DIALOG_TITLE
1153                 );
1154             } catch (E) {
1155                 alert(E);
1156             }
1157         } else {
1158             alert(dojo.string.substitute(
1159                 localeStrings.EXPORT_EMPTY_LIST, [attr_def.description]
1160             ));
1161         }
1162
1163         openils.Util.hide("acq-lit-export-attr-holder");
1164     };
1165
1166     this.printPO = function() {
1167         if(!this.isPO) return;
1168         progressDialog.show(true);
1169         fieldmapper.standardRequest(
1170             ['open-ils.acq', 'open-ils.acq.purchase_order.format'],
1171             {   async: true,
1172                 params: [this.authtoken, this.isPO, 'html'],
1173                 oncomplete: function(r) {
1174                     progressDialog.hide();
1175                     var evt = openils.Util.readResponse(r);
1176                     if(evt && evt.template_output()) {
1177                         win = window.open('','', 'resizable,width=800,height=600,scrollbars=1');
1178                         win.document.body.innerHTML = evt.template_output().data();
1179                     }
1180                 }
1181             }
1182         );
1183     }
1184
1185
1186     this.receivePO = function() {
1187         if(!this.isPO) return;
1188         this.show('acq-lit-progress-numbers');
1189         var self = this;
1190         fieldmapper.standardRequest(
1191             ['open-ils.acq', 'open-ils.acq.purchase_order.receive'],
1192             {   async: true,
1193                 params: [this.authtoken, this.isPO],
1194                 onresponse : function(r) {
1195                     var resp = openils.Util.readResponse(r);
1196                     self._updateProgressNumbers(resp, true);
1197                 },
1198             }
1199         );
1200     }
1201
1202     this.issueReceive = function(obj, rollback) {
1203         /* (For now) there shall be no marking LI or LIDs (un)received
1204          * except from the actual "view PO" interface. */
1205         if (!this.isPO) return;
1206
1207         var part =
1208             {"jub": "lineitem", "acqlid": "lineitem_detail"}[obj.classname];
1209         var method =
1210             "open-ils.acq." + part + ".receive" + (rollback ? ".rollback" : "");
1211
1212         progressDialog.show(true);
1213         fieldmapper.standardRequest(
1214             ["open-ils.acq", method], {
1215                 "async": true,
1216                 "params": [this.authtoken, obj.id()],
1217                 "onresponse": function(r) {
1218                     self.handleReceive(openils.Util.readResponse(r));
1219                 },
1220                 "oncomplete": function() { progressDialog.hide(); }
1221             }
1222         );
1223     };
1224
1225     /**
1226      * Handles the responses from receive and rollback ML calls.
1227      */
1228     this.handleReceive = function(resp) {
1229         if (resp) {
1230             if (resp.li) {
1231                 for (var li_id in resp.li) {
1232                     for (var key in resp.li[li_id])
1233                         self.liCache[li_id][key](resp.li[li_id][key]);
1234                     self.updateLiReceivedness(self.liCache[li_id]);
1235                 }
1236             }
1237             if (resp.po) {
1238                 if (typeof(self.poUpdateCallback) == "function")
1239                     self.poUpdateCallback(resp.po);
1240             }
1241             if (resp.lid) {
1242                 for (var lid_id in resp.lid) {
1243                     for (var key in resp.lid[lid_id])
1244                         self.copyCache[lid_id][key](resp.lid[lid_id][key]);
1245                     self.updateLidReceivedness(self.copyCache[lid_id]);
1246                 }
1247             }
1248         }
1249     };
1250
1251     this.rollbackPoReceive = function() {
1252         if(!this.isPO) return;
1253         if(!confirm(localeStrings.ROLLBACK_PO_RECEIVE_CONFIRM)) return;
1254         this.show('acq-lit-progress-numbers');
1255         var self = this;
1256         fieldmapper.standardRequest(
1257             ['open-ils.acq', 'open-ils.acq.purchase_order.receive.rollback'],
1258             {   async: true,
1259                 params: [this.authtoken, this.isPO],
1260                 onresponse : function(r) {
1261                     var resp = openils.Util.readResponse(r);
1262                     self._updateProgressNumbers(resp, true);
1263                 },
1264             }
1265         );
1266     }
1267
1268     this._updateProgressNumbers = function(resp, reloadOnComplete) {
1269         if(!resp) return;
1270         dojo.byId('acq-pl-lit-li-processed').innerHTML = resp.li;
1271         dojo.byId('acq-pl-lit-lid-processed').innerHTML = resp.lid;
1272         dojo.byId('acq-pl-lit-debits-processed').innerHTML = resp.debits_accrued;
1273         dojo.byId('acq-pl-lit-bibs-processed').innerHTML = resp.bibs;
1274         dojo.byId('acq-pl-lit-indexed-processed').innerHTML = resp.indexed;
1275         dojo.byId('acq-pl-lit-copies-processed').innerHTML = resp.copies;
1276         if(resp.complete && reloadOnComplete) 
1277             location.href = location.href;
1278     }
1279
1280
1281     this._createPO = function(fields) {
1282         this.show('acq-lit-progress-numbers');
1283         var po = new fieldmapper.acqpo();
1284         po.provider(this.createPoProviderSelector.attr('value'));
1285         po.ordering_agency(this.createPoAgencySelector.attr('value'));
1286
1287         var selected = this.getSelected( (fields.create_from == 'all') );
1288         if(selected.length == 0) return;
1289
1290         var max = selected.length * 3;
1291
1292         var self = this;
1293         fieldmapper.standardRequest(
1294             ['open-ils.acq', 'open-ils.acq.purchase_order.create'],
1295             {   async: true,
1296                 params: [
1297                     openils.User.authtoken, 
1298                     po, 
1299                     {
1300                         lineitems : selected.map(function(li) { return li.id() }),
1301                         create_assets : fields.create_assets[0],
1302                     }
1303                 ],
1304
1305                 onresponse : function(r) {
1306                     var resp = openils.Util.readResponse(r);
1307                     self._updateProgressNumbers(resp);
1308                     if(resp.complete) 
1309                         location.href = oilsBasePath + '/eg/acq/po/view/' + resp.purchase_order.id();
1310                 }
1311             }
1312         );
1313     }
1314
1315     this._deleteLiList = function(list, idx) {
1316         if(idx == null) idx = 0;
1317         if(idx >= list.length) return;
1318         var liId = list[idx].id();
1319         fieldmapper.standardRequest(
1320             ['open-ils.acq', 'open-ils.acq.lineitem.delete'],
1321             {   async: true,
1322                 params: [openils.User.authtoken, liId],
1323                 oncomplete: function(r) {
1324                     self.removeLineitem(liId);
1325                     self._deleteLiList(list, ++idx);
1326                 }
1327             }
1328         );
1329     }
1330
1331     this.editOrderMarc = function(li) {
1332
1333         /*  To run in Firefox directly, must set signed.applets.codebase_principal_support
1334             to true in about:config */
1335
1336         if(!openils.XUL.enableXPConnect()) return;
1337
1338         if(openils.XUL.isXUL()) {
1339             win = window.open('/xul/' + openils.XUL.buildId() + '/server/cat/marcedit.xul');
1340         } else {
1341             win = window.open('/xul/server/cat/marcedit.xul'); 
1342         }
1343         var self = this;
1344         win.xulG = {
1345             record : {marc : li.marc()},
1346             save : {
1347                 label: 'Save Record', // XXX I18N
1348                 func: function(xmlString) {
1349                     li.marc(xmlString);
1350                     fieldmapper.standardRequest(
1351                         ['open-ils.acq', 'open-ils.acq.lineitem.update'],
1352                         {   async: true,
1353                             params: [openils.User.authtoken, li],
1354                             oncomplete: function(r) {
1355                                 openils.Util.readResponse(r);
1356                                 win.close();
1357                                 self.drawInfo(li.id())
1358                             }
1359                         }
1360                     );
1361                 },
1362             }
1363         };
1364     }
1365
1366     this._savePl = function(values) {
1367         var self = this;
1368         var selected = this.getSelected( (values.which == 'all') );
1369         openils.Util.show('acq-lit-generic-progress');
1370
1371         if(values.new_name) {
1372             openils.acq.Picklist.create(
1373                 {name: values.new_name}, 
1374                 function(id) {
1375                     self._updateLiList(id, selected, 0, 
1376                         function(){
1377                             location.href = oilsBasePath + '/eg/acq/picklist/view/' + id;
1378                         });
1379                 }
1380             );
1381         } else if(values.existing_pl) {
1382             // update lineitems to use an existing picklist
1383             self._updateLiList(values.existing_pl, selected, 0, 
1384                 function(){
1385                     location.href = oilsBasePath + '/eg/acq/picklist/view/' + values.existing_pl;
1386                 });
1387         }
1388     }
1389
1390     this._updateLiState = function(values, state) {
1391         var self = this;
1392         var selected = this.getSelected( (values.which == 'all') );
1393         if(!selected.length) return;
1394         dojo.forEach(selected, function(li) {li.state(state);});
1395         self._updateLiList(null, selected, 0, 
1396             // TODO consider inline updates for efficiency
1397             function() { location.href = location.href }
1398         );
1399     }
1400
1401     this._updateLiList = function(pl, list, idx, oncomplete) {
1402         if(idx >= list.length) return oncomplete();
1403         var li = list[idx];
1404         if(pl != null) li.picklist(pl);
1405         litGenericProgress.update({maximum: list.length, progress: idx});
1406         new openils.acq.Lineitem({lineitem:li}).update(
1407             function(r) {
1408                 self._updateLiList(pl, list, ++idx, oncomplete);
1409             }
1410         );
1411     }
1412
1413     this._loadPLSelect = function() {
1414         if(this._plSelectLoaded) return;
1415         var plList = [];
1416         function handleResponse(r) {
1417             plList.push(r.recv().content());
1418         }
1419         var method = 'open-ils.acq.picklist.user.retrieve';
1420         fieldmapper.standardRequest(
1421             ['open-ils.acq', method],
1422             {   async: true,
1423                 params: [this.authtoken],
1424                 onresponse: handleResponse,
1425                 oncomplete: function() {
1426                     self._plSelectLoaded = true;
1427                     acqLitAddExistingSelect.store = 
1428                         new dojo.data.ItemFileReadStore({data:acqpl.toStoreData(plList)});
1429                     acqLitAddExistingSelect.setValue();
1430                 }
1431             }
1432         );
1433     }
1434
1435     // grab the li-details for this lineitem, grab the linked copies and volumes, add them to the table
1436     this.showRealCopies = function(li) {
1437         while(this.realCopiesTbody.childNodes[0])
1438             this.realCopiesTbody.removeChild(this.realCopiesTbody.childNodes[0]);
1439         this.show('real-copies');
1440
1441         var pcrud = new openils.PermaCrud({authtoken : this.authtoken});
1442         this.realCopyList = [];
1443         this.volCache = {};
1444         var tabIndex = 1000;
1445         var self = this;
1446
1447         acqLitSaveRealCopies.onClick = function() {
1448             self.saveRealCopies();
1449         }
1450
1451         this._fetchLineitem(li.id(), 
1452             function(fullLi) {
1453                 li = self.liCache[li.id()] = fullLi;
1454
1455                 pcrud.search(
1456                     'acp', {
1457                         id : li.lineitem_details().map(
1458                             function(item) { return item.eg_copy_id() }
1459                         )
1460                     }, {
1461                         async : true,
1462                         streaming : true,
1463                         onresponse : function(r) {
1464                             var copy = openils.Util.readResponse(r);
1465                             var volId = copy.call_number();
1466                             var volume = self.volCache[volId];
1467                             if(!volume) {
1468                                 volume = self.volCache[volId] = pcrud.retrieve('acn', volId);
1469                             }
1470                             self.addRealCopy(volume, copy, tabIndex++);
1471                         }
1472                     }
1473                 );
1474             }
1475         );
1476     }
1477
1478     this.addRealCopy = function(volume, copy, tabIndex) {
1479         var row = this.realCopiesRow.cloneNode(true);
1480         this.realCopyList.push(copy);
1481
1482         var selectNode;
1483         dojo.forEach(
1484             ['owning_lib', 'location', 'circ_modifier', 'label', 'barcode'],
1485
1486             function(field) {
1487                 var isvol = (field == 'owning_lib' || field == 'label');
1488                 var widget = new openils.widget.AutoFieldWidget({
1489                     fmField : field,
1490                     fmObject : isvol ? volume : copy,
1491                     parentNode : nodeByName(field, row),
1492                     readOnly : (field != 'barcode'),
1493                 });
1494
1495                 var widgetDrawn = null;
1496
1497                 if(field == 'barcode') {
1498
1499                     widgetDrawn = function(w, ww) {
1500                         var node = w.domNode;
1501                         node.setAttribute('tabindex', ''+tabIndex);
1502
1503                         // on enter, select the next barcode input
1504                         dojo.connect(w, 'onKeyDown',
1505                             function(e) {
1506                                 if(e.keyCode == dojo.keys.ENTER) {
1507                                     var ti = node.getAttribute('tabindex');
1508                                     var nextNode = dojo.query('[tabindex=' + String(Number(ti) + 1) + ']', self.realCopiesTbody)[0];
1509                                     if(nextNode) nextNode.select();
1510                                 }
1511                             }
1512                         );
1513
1514                         dojo.connect(w, 'onChange', 
1515                             function(val) { 
1516                                 if(!val || val == copy.barcode()) return;
1517                                 copy.ischanged(true);
1518                                 copy.barcode(val);
1519                             }
1520                         );
1521
1522
1523                         if(self.realCopiesTbody.getElementsByTagName('TR').length == 0)
1524                             selectNode = node;
1525                     }
1526                 }
1527
1528                 widget.build(widgetDrawn);
1529             }
1530         );
1531
1532         this.realCopiesTbody.appendChild(row);
1533         if(selectNode) selectNode.select();
1534     };
1535
1536     this.saveRealCopies = function() {
1537         var pcrud = new openils.PermaCrud({authtoken : this.authtoken});
1538         progressDialog.show(true);
1539         var list = this.realCopyList.filter(function(copy) { return copy.ischanged(); });
1540         pcrud.update(list, {oncomplete: function() { 
1541             progressDialog.hide();
1542             self.show('list');
1543         }});
1544     }
1545 }