]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/acq/common/li_table.js
Acq: fix inaccurate word choice in "related lineitems" link; hide when only 1
[working/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.Tooltip');
8 dojo.require('dijit.ProgressBar');
9 dojo.require('openils.User');
10 dojo.require('openils.Util');
11 dojo.require('openils.acq.Lineitem');
12 dojo.require('openils.acq.PO');
13 dojo.require('openils.acq.Picklist');
14 dojo.require('openils.widget.AutoFieldWidget');
15 dojo.require('dojo.data.ItemFileReadStore');
16 dojo.require('openils.widget.ProgressDialog');
17 dojo.require('openils.PermaCrud');
18 dojo.require('openils.XUL');
19
20 dojo.requireLocalization('openils.acq', 'acq');
21 var localeStrings = dojo.i18n.getLocalization('openils.acq', 'acq');
22 const XUL_OPAC_WRAPPER = 'chrome://open_ils_staff_client/content/cat/opac.xul';
23 var li_exportable_attrs = ["issn", "isbn", "upc"];
24
25 var fundLabelFormat = ['${0} (${1})', 'code', 'year'];
26 var fundSearchFormat = ['${0} (${1})', 'code', 'year'];
27
28 function nodeByName(name, context) {
29     return dojo.query('[name='+name+']', context)[0];
30 }
31
32
33 var liDetailBatchFields = ['fund', 'owning_lib', 'location', 'collection_code', 'circ_modifier', 'cn_label'];
34 var liDetailFields = liDetailBatchFields.concat(['barcode', 'note']);
35
36 function AcqLiTable() {
37
38     var self = this;
39     this.liCache = {};
40     this.plCache = {};
41     this.poCache = {};
42     this.relCache = {};
43     this.realDfaCache = {};
44     this.virtDfaCounts = {};
45     this.virtDfaId = -1;
46     this.dfeOffset = 0;
47     this.toggleState = false;
48     this.tbody = dojo.byId('acq-lit-tbody');
49     this.selectors = [];
50     this.noteAcks = {};
51     this.authtoken = openils.User.authtoken;
52     this.pcrud = new openils.PermaCrud();
53     this.rowTemplate = this.tbody.removeChild(dojo.byId('acq-lit-row'));
54     this.copyTbody = dojo.byId('acq-lit-li-details-tbody');
55     this.copyRow = this.copyTbody.removeChild(dojo.byId('acq-lit-li-details-row'));
56     this.copyBatchRow = dojo.byId('acq-lit-li-details-batch-row');
57     this.copyBatchWidgets = {};
58     this.liNotesTbody = dojo.byId('acq-lit-notes-tbody');
59     this.liNotesRow = this.liNotesTbody.removeChild(dojo.byId('acq-lit-notes-row'));
60     this.realCopiesTbody = dojo.byId('acq-lit-real-copies-tbody');
61     this.realCopiesRow = this.realCopiesTbody.removeChild(dojo.byId('acq-lit-real-copies-row'));
62     this._copy_fields_for_acqdf = ['owning_lib', 'location'];
63
64     dojo.connect(acqLitLiActionsSelector, 'onChange', 
65         function() { 
66             self.applySelectedLiAction(this.attr('value')) 
67             acqLitLiActionsSelector.attr('value', '_');
68         });
69
70     acqLitCreatePoSubmit.onClick = function() {
71         acqLitPoCreateDialog.hide();
72         self._createPO(acqLitPoCreateDialog.getValues());
73     }
74
75     acqLitSavePlButton.onClick = function() {
76         acqLitSavePlDialog.hide();
77         self._savePl(acqLitSavePlDialog.getValues());
78     }
79
80     acqLitCancelLiStateButton.onClick = function() {
81         acqLitChangeLiStateDialog.hide();
82     }
83     acqLitSaveLiStateButton.onClick = function() {
84         acqLitChangeLiStateDialog.hide();
85         self._updateLiState(acqLitChangeLiStateDialog.getValues(), acqLitChangeLiStateDialog.attr('state'));
86     }
87
88
89     dojo.byId('acq-lit-select-toggle').onclick = function(){self.toggleSelect()};
90     dojo.byId('acq-lit-info-back-button').onclick = function(){self.show('list')};
91     dojo.byId('acq-lit-copies-back-button').onclick = function(){self.show('list')};
92     dojo.byId('acq-lit-notes-back-button').onclick = function(){self.show('list')};
93     dojo.byId('acq-lit-real-copies-back-button').onclick = function(){self.show('list')};
94
95     this.reset = function() {
96         while(self.tbody.childNodes[0])
97             self.tbody.removeChild(self.tbody.childNodes[0]);
98         self.selectors = [];
99         self.noteAcks = {};
100         self.relCache = {};
101     };
102     
103     this.setNext = function(handler) {
104         var link = dojo.byId('acq-lit-next');
105         if(handler) {
106             dojo.style(link, 'visibility', 'visible');
107             link.onclick = handler;
108         } else {
109             dojo.style(link, 'visibility', 'hidden');
110         }
111     };
112
113     this.setPrev = function(handler) {
114         var link = dojo.byId('acq-lit-prev');
115         if(handler) {
116             dojo.style(link, 'visibility', 'visible'); 
117             link.onclick = handler; 
118         } else {
119             dojo.style(link, 'visibility', 'hidden');
120         }
121     };
122
123     this.show = function(div) {
124         openils.Util.hide('acq-lit-table-div');
125         openils.Util.hide('acq-lit-info-div');
126         openils.Util.hide('acq-lit-li-details');
127         openils.Util.hide('acq-lit-notes-div');
128         openils.Util.hide('acq-lit-real-copies-div');
129         switch(div) {
130             case 'list':
131                 openils.Util.show('acq-lit-table-div');
132                 break;
133             case 'info':
134                 openils.Util.show('acq-lit-info-div');
135                 break;
136             case 'copies':
137                 openils.Util.show('acq-lit-li-details');
138                 break;
139             case 'real-copies':
140                 openils.Util.show('acq-lit-real-copies-div');
141                 break;
142             case 'notes':
143                 openils.Util.show('acq-lit-notes-div');
144                 break;
145             default:
146                 if(div) 
147                     openils.Util.show(div);
148         }
149     }
150
151     this.hide = function() {
152         this.show(null);
153     }
154
155     this.toggleSelect = function() {
156         if(self.toggleState) 
157             dojo.forEach(self.selectors, function(i){i.checked = false});
158         else 
159             dojo.forEach(self.selectors, function(i){i.checked = true});
160         self.toggleState = !self.toggleState;
161     };
162
163
164     /** @param all If true, assume all are selected */
165     this.getSelected = function(all) {
166         var selected = [];
167         dojo.forEach(self.selectors, 
168             function(i) { 
169                 if(i.checked || all)
170                     selected.push(self.liCache[i.parentNode.parentNode.getAttribute('li')]);
171             }
172         );
173         return selected;
174     };
175
176     this.setRowAttr = function(td, liWrapper, field, type) {
177         var val = liWrapper.findAttr(field, type || 'lineitem_marc_attr_definition') || '';
178         td.appendChild(document.createTextNode(val));
179     };
180
181     /**
182      * Inserts a single lineitem into the growing table of lineitems
183      * @param {Object} li The lineitem object to insert
184      */
185     this.addLineitem = function(li, skip_final_placement) {
186         this.liCache[li.id()] = li;
187
188         // sort the lineitem notes on edit_time
189         if(!li.lineitem_notes()) li.lineitem_notes([]);
190
191         var liWrapper = new openils.acq.Lineitem({lineitem:li});
192         var row = self.rowTemplate.cloneNode(true);
193         row.setAttribute('li', li.id());
194         var tds = dojo.query('[attr]', row);
195         dojo.forEach(tds, function(td) {self.setRowAttr(td, liWrapper, td.getAttribute('attr'), td.getAttribute('attr_type'));});
196         dojo.query('[name=source_label]', row)[0].appendChild(document.createTextNode(li.source_label()));
197
198         var isbn = liWrapper.findAttr('isbn', 'lineitem_marc_attr_definition');
199         if(isbn) {
200             // XXX media prefix for added content
201             dojo.query('[name=jacket]', row)[0].setAttribute('src', '/opac/extras/ac/jacket/small/' + isbn);
202         }
203
204         dojo.query('[attr=title]', row)[0].onclick = function() {self.drawInfo(li.id())};
205         dojo.query('[name=copieslink]', row)[0].onclick = function() {self.drawCopies(li.id())};
206         dojo.query('[name=noteslink]', row)[0].onclick = function() {self.drawLiNotes(li)};
207
208         this.updateLiNotesCount(li, row);
209
210         // show which PO this lineitem is a member of
211         if(li.purchase_order() && !this.isPO) {
212             var po = 
213                 this.poCache[li.purchase_order()] =
214                 this.poCache[li.purchase_order()] ||
215                 fieldmapper.standardRequest(
216                     ['open-ils.acq', 'open-ils.acq.purchase_order.retrieve'],
217                     {params: [
218                         this.authtoken, li.purchase_order(), {
219                             "flesh_price_summary": true,
220                             "flesh_lineitem_count": true
221                         }
222                     ]});
223             if(po && !this.isMeta) {
224                 openils.Util.show(nodeByName('po', row), 'inline');
225                 var link = nodeByName('po_link', row);
226                 link.setAttribute('href', oilsBasePath + '/acq/po/view/' + li.purchase_order());
227                 link.innerHTML = 'PO: ' + po.name(); // TODO i18n
228             }
229         }
230
231         // show which picklist this lineitem is a member of
232         if(li.picklist() && (this.isPO || this.isMeta)) {
233             var pl = 
234                 this.plCache[li.picklist()] = 
235                 this.plCache[li.picklist()] || 
236                 fieldmapper.standardRequest(
237                     ['open-ils.acq', 'open-ils.acq.picklist.retrieve'],
238                     {params: [this.authtoken, li.picklist()]});
239             if(pl) {
240                 openils.Util.show(nodeByName('pl', row), 'inline');
241                 var link = nodeByName('pl_link', row);
242                 link.setAttribute('href', oilsBasePath + '/acq/picklist/view/' + li.picklist());
243                 link.innerHTML = 'PL: '+pl.name(); // TODO i18n
244             }
245         }
246
247         var countNode = nodeByName('count', row);
248         var count = li.item_count() || 0;
249         if (typeof(this._copy_count_cb) == "function") {
250             this._copy_count_cb(li.id(), count);
251         }
252         countNode.innerHTML = count;
253         countNode.id = 'acq-lit-copy-count-label-' + li.id();
254
255         // lineitem state
256         nodeByName('li_state', row).innerHTML = li.state(); // TODO i18n state labels
257         
258         // lineitem price
259         var priceInput = dojo.query('[name=price]', row)[0];
260         priceInput.value = li.estimated_unit_price() || '';
261         priceInput.onchange = function() { self.updateLiPrice(priceInput, li) };
262
263         // show either "mark received" or "unreceive" as appropriate
264         this.updateLiReceivedness(li, row);
265
266         if (!skip_final_placement) {
267             self.tbody.appendChild(row);
268             self.selectors.push(dojo.query('[name=selectbox]', row)[0]);
269         } else {
270             return row;
271         }
272     };
273
274     this.updateLiNotesCount = function(li, row) {
275         if (typeof(row) == "undefined")
276             row = dojo.query('tr[li="' + li.id() + '"]', "acq-lit-tbody")[0];
277
278         var has_notes = (li.lineitem_notes().filter(
279                 function(o) { return Boolean (o.alert_text()); }
280             ).length > 0);
281
282         /* U+2691 is the code point for a filled-in flag character */
283         nodeByName("notes_alert_flag", row).innerHTML =
284              has_notes ? "⚑" : "";
285         nodeByName("noteslink", row).style.fontStyle =
286             has_notes ? "italic" : "normal";
287         nodeByName("notes_count", row).innerHTML = li.lineitem_notes().length;
288     };
289
290     this.updateLiReceivedness = function(li, row) {
291         if (typeof(row) == "undefined")
292             row = dojo.query('tr[li="' + li.id() + '"]', "acq-lit-tbody")[0];
293
294         var recv_link = nodeByName("receive_link", row);
295         var unrecv_link = nodeByName("unreceive_link", row);
296         var real_copies_link = nodeByName("real_copies_link", row);
297         var holdings_maintenance_link = nodeByName("holdings_maintenance_link", row);
298
299         /* handle row coloring for based on LI state */
300         openils.Util.removeCSSClass(row, /^oils-acq-li-state-/);
301         openils.Util.addCSSClass(row, "oils-acq-li-state-" + li.state());
302
303         /* handle links that appear/disappear based on whether LI is received */
304         if (this.isPO) {
305             var self = this;
306             switch(li.state()) {
307                 case "on-order":
308                     openils.Util.hide(real_copies_link);
309                     openils.Util.hide(unrecv_link);
310                     openils.Util.show(recv_link, "inline");
311                     recv_link.onclick = function() {
312                         if (self.checkLiAlerts(li.id()))
313                             self.issueReceive(li);
314                     };
315                     return;
316                 case "received":
317                     openils.Util.hide(recv_link);
318                     openils.Util.show(unrecv_link, "inline");
319                     unrecv_link.onclick = function() {
320                         if (confirm(localeStrings.UNRECEIVE_LI))
321                             self.issueReceive(li, /* rollback */ true);
322                     };
323                     // TODO we should allow editing before receipt, in which case the
324                     // test should be "if 1 or more real (acp) copies exist
325                     openils.Util.show(real_copies_link);
326                     real_copies_link.onclick = function() {
327                         self.showRealCopyEditUI(li);
328                     }
329                     openils.Util.show(holdings_maintenance_link);
330                     holdings_maintenance_link.onclick = self.generateMakeRecTab( li.eg_bib_id(), 'copy_browser' );
331                     return;
332             }
333         }
334
335         openils.Util.hide(recv_link);
336         openils.Util.hide(unrecv_link);
337         openils.Util.hide(real_copies_link);
338     };
339
340
341     this._setAlertStore = function() {
342         acqLitAlertAlertText.store = new dojo.data.ItemFileReadStore(
343             {
344                 "data": acqliat.toStoreData(
345                     this.pcrud.search(
346                         "acqliat", {"id": {"!=": null}}
347                     )
348                 )
349             }
350         );
351         acqLitAlertAlertText.setValue(); /* make the store "live" */
352         acqLitAlertAlertText._store_ready = true;
353     };
354
355     /**
356      * Draws and shows the lineitem notes pane
357      */
358     this.drawLiNotes = function(li) {
359         var self = this;
360
361         if (!acqLitAlertAlertText._store_ready)
362             this._setAlertStore();
363
364         li.lineitem_notes(
365             li.lineitem_notes().sort(
366                 function(a, b) { 
367                     if(a.edit_time() < b.edit_time()) return 1;
368                     return -1;
369                 }
370             )
371         );
372
373         while(this.liNotesTbody.childNodes[0])
374             this.liNotesTbody.removeChild(this.liNotesTbody.childNodes[0]);
375         this.show('notes');
376
377         acqLitCreateNoteSubmit.onClick = function() {
378             var value = acqLitCreateNoteText.attr('value');
379             if(!value) return;
380             var note = new fieldmapper.acqlin();
381             note.isnew(true);
382             note.vendor_public(
383                 Boolean(acqLitCreateNoteVendorPublic.attr('checked'))
384             );
385             note.value(value);
386             note.lineitem(li.id());
387
388             self.updateLiNotes(li, note);
389             acqLitCreateNoteVendorPublic.attr("checked", false);
390             acqLitCreateNoteText.attr("value", "");
391         }
392
393         acqLitCreateAlertSubmit.onClick = function() {
394             if (!acqLitAlertAlertText.item) {
395                 alert(localeStrings.ALERT_UNSELECTED);
396                 return;
397             }
398
399             var alert_text = new fieldmapper.acqliat().fromStoreItem(
400                 acqLitAlertAlertText.item
401             );
402             var value = acqLitAlertNoteValue.attr("value") || "";
403
404             var note = new fieldmapper.acqlin();
405             note.isnew(true);
406             note.lineitem(li.id());
407             note.value(value);
408             note.alert_text(alert_text);
409
410             self.updateLiNotes(li, note);
411         }
412
413         dojo.forEach(li.lineitem_notes(), function(note) { self.addLiNote(li, note) });
414     }
415
416     /**
417      * Draws a single lineitem note in the notes pane
418      */
419     this.addLiNote = function(li, note) {
420         if(note.isdeleted()) return;
421         var self = this;
422         var row = self.liNotesRow.cloneNode(true);
423         nodeByName("value", row).innerHTML = note.value();
424         var alert_node = nodeByName("alert_code", row);
425         if (note.alert_text()) {
426             alert_node.innerHTML = note.alert_text().code();
427             if (note.alert_text().description()) {
428                 new dijit.Tooltip(
429                     {
430                         "connectId": [alert_node],
431                         "label": note.alert_text().description()
432                     }, dojo.create("span", null, alert_node, "after")
433                 );
434             }
435         }
436
437         if (note.vendor_public() == "t")
438             nodeByName("vendor_public", row).innerHTML =
439                 localeStrings.VENDOR_PUBLIC;
440
441         nodeByName("delete", row).onclick = function() {
442             note.isdeleted(true);
443             self.liNotesTbody.removeChild(row);
444             self.updateLiNotes(li);
445         };
446
447         if(note.edit_time()) {
448             nodeByName("edit_time", row).innerHTML =
449                 dojo.date.locale.format(
450                     dojo.date.stamp.fromISOString(note.edit_time()), 
451                     {formatLength:'short'});
452         }
453
454         self.liNotesTbody.appendChild(row);
455     }
456
457     /**
458      * Updates any new/changed/deleted notes on the server
459      */
460     this.updateLiNotes = function(li, newNote) {
461
462         var notes;
463         if(newNote) {
464             notes = [newNote];
465         } else {
466             notes = li.lineitem_notes().filter(
467                 function(note) {
468                     if(note.ischanged() || note.isnew() || note.isdeleted())
469                         return note;
470                 }
471             );
472         }
473
474         if(notes.length == 0) return;
475         progressDialog.show();
476
477         fieldmapper.standardRequest(
478             ['open-ils.acq', 'open-ils.acq.lineitem_note.cud.batch'],
479             {   async : true,
480                 params : [this.authtoken, notes],
481                 onresponse : function(r) {
482                     var resp = openils.Util.readResponse(r);
483
484                     if(resp.complete) {
485
486                         if(!newNote) {
487                             // remove the old changed notes
488                             var list = [];
489                             dojo.forEach(li.lineitem_notes(), 
490                                 function(note) {
491                                     if(!(note.ischanged() || note.isnew() || note.isdeleted()))
492                                         list.push(note);
493                                 }
494                             );
495                             li.lineitem_notes(list);
496                         }
497
498                         progressDialog.hide();
499                         self.updateLiNotesCount(li);
500                         self.drawLiNotes(li);
501                         return;
502                     }
503
504                     progressDialog.update(resp);
505                     var newnote = resp.note;
506
507                     if(!newnote.isdeleted()) {
508                         newnote.isnew(false);
509                         newnote.ischanged(false);
510                         li.lineitem_notes().push(newnote);
511                     }
512                 },
513             }
514         );
515     }
516
517     this.updateLiPrice = function(input, li) {
518
519         var price = input.value;
520         if(Number(price) == Number(li.estimated_unit_price())) return;
521
522         fieldmapper.standardRequest(
523             ['open-ils.acq', 'open-ils.acq.lineitem.price.set'],
524             {   async : true,
525                 params : [this.authtoken, li.id(), price],
526                 oncomplete : function(r) {
527                     openils.Util.readResponse(r);
528                 }
529             }
530         );
531     }
532
533     this.removeLineitem = function(liId) {
534         this.tbody.removeChild(dojo.query('[li='+liId+']', this.tbody)[0]);
535         delete this.liCache[liId];
536         //selected.push(self.liCache[i.parentNode.parentNode.getAttribute('li')]);
537     }
538
539     this.drawInfo = function(liId) {
540         if (!this._isRelatedViewer) {
541             var d = dojo.byId("acq-lit-info-related");
542             if (!this.relCache[liId]) {
543                 fieldmapper.standardRequest(
544                     [
545                         "open-ils.acq",
546                         "open-ils.acq.lineitems_for_bib.by_lineitem_id.count"
547                     ], {
548                         "async": true,
549                         "params": [openils.User.authtoken, liId],
550                         "onresponse": function(r) {
551                             self.relCache[liId] = openils.Util.readResponse(r);
552                             nodeByName("related_number", d).innerHTML =
553                                 self.relCache[liId];
554                             openils.Util[
555                                 self.relCache[liId] >1 ? "show" : "hide"
556                             ](d);
557                         }
558                     }
559                 );
560             } else {
561                 nodeByName("related_number", d).innerHTML = this.relCache[liId];
562                 openils.Util[this.relCache[liId] > 1 ? "show" : "hide"](d);
563             }
564         }
565
566         this.show('info');
567         openils.acq.Lineitem.fetchAttrDefs(
568             function() { 
569                 self._fetchLineitem(liId, function(li){self._drawInfo(li);}); 
570             } 
571         );
572     };
573
574     this._fetchLineitem = function(liId, handler) {
575
576         var li = this.liCache[liId];
577         if(li && li.marc() && li.lineitem_details())
578             return handler(li);
579         
580         fieldmapper.standardRequest(
581             ['open-ils.acq', 'open-ils.acq.lineitem.retrieve'],
582             {   async: true,
583
584                 params: [self.authtoken, liId, {
585                     flesh_attrs: true,
586                     flesh_li_details: true,
587                     flesh_fund_debit: true }],
588
589                 oncomplete: function(r) {
590                     var li = openils.Util.readResponse(r);
591                     handler(li)
592                 }
593             }
594         );
595     };
596
597     this._drawInfo = function(li) {
598
599         acqLitEditOrderMarc.onClick = function() { self.editOrderMarc(li); }
600
601         if(li.eg_bib_id()) {
602             openils.Util.hide('acq-lit-marc-order-record-label');
603             openils.Util.hide(acqLitEditOrderMarc.domNode);
604             openils.Util.show('acq-lit-marc-real-record-label');
605         } else {
606             openils.Util.show('acq-lit-marc-order-record-label');
607             openils.Util.show(acqLitEditOrderMarc.domNode);
608             openils.Util.hide('acq-lit-marc-real-record-label');
609         }
610
611         this.drawMarcHTML(li);
612         this.infoTbody = dojo.byId('acq-lit-info-tbody');
613
614         if(!this.infoRow)
615             this.infoRow = this.infoTbody.removeChild(dojo.byId('acq-lit-info-row'));
616         while(this.infoTbody.childNodes[0])
617             this.infoTbody.removeChild(this.infoTbody.childNodes[0]);
618
619         if (!this._isRelatedViewer) {
620             for(var i = 0; i < li.attributes().length; i++) {
621                 var attr = li.attributes()[i];
622                 var row = this.infoRow.cloneNode(true);
623
624                 var type = attr.attr_type().replace(/lineitem_(.*)_attr_definition/, '$1');
625                 var name = openils.acq.Lineitem.attrDefs[type].filter(
626                     function(a) {
627                         return (a.code() == attr.attr_name());
628                     }
629                 ).pop().description();
630
631                 dojo.query('[name=label]', row)[0].appendChild(document.createTextNode(name));
632                 dojo.query('[name=value]', row)[0].appendChild(document.createTextNode(attr.attr_value()));
633                 this.infoTbody.appendChild(row);
634             }
635
636             nodeByName("rel_link", dojo.byId("acq-lit-info-related")).href =
637                 "/eg/acq/lineitem/related/" + li.id();
638         }
639
640         if(li.eg_bib_id()) {
641
642             openils.Util.show('acq-lit-info-cat-link');
643             var link = dojo.byId('acq-lit-info-cat-link').getElementsByTagName('a')[0];
644
645             if(openils.XUL.isXUL()) {
646
647                 link.setAttribute('href', 'javascript:void(0);');
648                 link.onclick = this.generateMakeRecTab( li.eg_bib_id() );
649
650             } else {
651                 var href = link.getAttribute('href');
652                 if(href.match(/=$/))
653                     link.setAttribute('href',  href + li.eg_bib_id());
654             }
655         } else {
656             openils.Util.hide('acq-lit-info-cat-link');
657         }
658     };
659
660     this.generateMakeRecTab = function(bib_id,default_view) {
661         return function() {
662             xulG.new_tab(
663                 XUL_OPAC_WRAPPER,
664                 {tab_name: localeStrings.XUL_RECORD_DETAIL_PAGE, browser:false},
665                 {
666                     no_xulG : false, 
667                     show_nav_buttons : true, 
668                     show_print_button : true, 
669                     opac_url : xulG.url_prefix(xulG.urls.opac_rdetail + '?r=' + bib_id),
670                     default_view : default_view
671                 }
672             );
673         }
674     };
675
676     this.drawMarcHTML = function(li) {
677         var params = [null, true, li.marc()];
678         if(li.eg_bib_id()) 
679             params = [li.eg_bib_id(), true];
680
681         fieldmapper.standardRequest(
682             ['open-ils.search', 'open-ils.search.biblio.record.html'],
683             {   async: true,
684                 params: params,
685                 oncomplete: function(r) {
686                     dojo.byId('acq-lit-marc-div').innerHTML = 
687                         openils.Util.readResponse(r);
688                 }
689             }
690         );
691     }
692
693     this.drawCopies = function(liId) {
694         this.show('copies');
695         var self = this;
696         this.copyCache = {};
697         this.copyWidgetCache = {};
698         this.oldCopyWidgetCache = {};
699         this.virtDfaCounts = {};
700         this.realDfaCache = {};
701         this.dfeOffset = 0;
702
703         acqLitSaveCopies.onClick = function() { self.saveCopyChanges(liId) };
704         acqLitBatchUpdateCopies.onClick = function() { self.batchCopyUpdate() };
705         acqLitCopyCountInput.attr('value', '0');
706
707         while(this.copyTbody.childNodes[0])
708             this.copyTbody.removeChild(this.copyTbody.childNodes[0]);
709
710         this._drawBatchCopyWidgets();
711
712         this._drawDistribApplied(liId);
713
714         this._fetchDistribFormulas(
715             function() {
716                 openils.acq.Lineitem.fetchAttrDefs(
717                     function() { 
718                         self._fetchLineitem(liId, function(li){self._drawCopies(li);}); 
719                     } 
720                 );
721             }
722         );
723     };
724
725     this._saveDistribAppliedTemplates = function() {
726         if (!this._appliedDistribTemplate) {
727             this._appliedDistribTemplate =
728                 dojo.byId("acq-lit-distrib-applied-tbody").
729                     removeChild(dojo.byId("acq-lit-distrib-applied-row"));
730             dojo.attr(this._appliedDistribTemplate, "id");
731         }
732     };
733
734     this._drawDistribApplied = function(liId) {
735         /* Build this table while hidden to prevent rendering artifacts */
736         openils.Util.hide("acq-lit-distrib-applied-tbody");
737
738         this._saveDistribAppliedTemplates();
739
740         /* Remove any rows in the table from previous populations */
741         dojo.query("tr[formula]", "acq-lit-distrib-applied-tbody").
742             forEach(dojo.destroy);
743
744         /* Unregister all dijits previously created (for some reason this isn't
745          * covered by the above destroy calls). */
746         dijit.registry.forEach(
747             function(w) { if (/^dfa-/.test(w.id)) w.destroyRecursive(); }
748         );
749
750         /* Populate the table with our liId */
751         var total = 0;
752         fieldmapper.standardRequest(
753             ["open-ils.acq",
754             "open-ils.acq.distribution_formula_application.ranged.retrieve"],
755             {
756                 "async": true,
757                 "params": [self.authtoken, liId],
758                 "onresponse": function(r) {
759                     var dfa = openils.Util.readResponse(r);
760                     if (dfa) {
761                         total++;
762                         self.realDfaCache[dfa.id()] = dfa;
763                         self._drawDistribAppliedUnit(dfa);
764                     }
765                 },
766                 "oncomplete": function() {
767                     /* Reveal built table */
768                     if (total) {
769                         openils.Util.show(
770                             "acq-lit-distrib-applied-tbody", "table-row-group"
771                         );
772                     }
773                 }
774             }
775         );
776     };
777
778     this._drawDistribAppliedUnit = function(dfa) {
779         var new_row = false;
780         var row = dojo.query(
781             'tr[formula="' + dfa.formula().id() + '"]',
782             "acq-lit-distrib-applied-tbody"
783         )[0];
784
785         if (!row) {
786             new_row = true;
787             row = dojo.clone(this._appliedDistribTemplate);
788             dojo.attr(row, "formula", dfa.formula().id());
789             dojo.query("th", row)[0].innerHTML = dfa.formula().name();
790         }
791
792         var td = dojo.query("td", row)[0];
793
794         dojo.create("span", {"id": "dfa-button-" + dfa.id()}, td, "last");
795         dojo.create("span", {"id": "dfa-tip-" + dfa.id()}, td, "last");
796
797         if (new_row)
798             dojo.place(row, "acq-lit-distrib-applied-tbody", "last");
799
800         new dijit.form.Button(
801             {
802                 "onClick": function() {
803                     if (confirm(localeStrings.EXPLAIN_DFA_MGMT))
804                         self.deleteDfa(dfa);
805                 },
806                 "label": "X",
807                 /* XXX I /cannot/ make the following work in as a CSS class
808                  * for some reason. So frustrating... */
809                 "style": function(id) {
810                      return (id > 0 ?
811                         "font-weight: bold; color: #c00;" :
812                         "color: #666;");
813                      }(dfa.id()) + "margin: 0 6px;display: inline;"
814             }, "dfa-button-" + dfa.id()
815         );
816         new dijit.Tooltip(
817             {
818                 "connectId": ["dfa-button-" + dfa.id()],
819                 "label": dojo.string.substitute(
820                     localeStrings.DFA_TIP, dfa.id() > 0 ? [
821                         openils.User.formalName(dfa.creator()),
822                         dojo.date.locale.format(
823                             dojo.date.stamp.fromISOString(dfa.create_time()),
824                             {"formatLength":"short"}
825                         )
826                     ] : [localeStrings.ITS_YOU, localeStrings.JUST_NOW]
827                 )
828             }, "dfa-tip-" + dfa.id()
829         );
830     }
831
832     this.deleteDfa = function(dfa) {
833         if (dfa.id() > 0) { /* real */
834             this.pcrud.eliminate(
835                 dfa, {
836                     "async": true,
837                     "oncomplete": function() {
838                         self._removeDistribApplied(dfa.id());
839                         delete self.realDfaCache[dfa.id()];
840                     }
841                 }
842             );
843         } else { /* virtual */
844             if (--(this.virtDfaCounts[dfa.formula().id()]) < 0)
845             this.virtDfaCounts[dfa.formula().id()] = 0;
846             /* hasn't been saved yet, so no need to do anything server side */
847             this._removeDistribApplied(dfa.id());
848         }
849
850     };
851
852     this._removeDistribApplied = function(dfaId) {
853         var re = new RegExp("^dfa-\\w+-" + String(dfaId));
854         dijit.registry.forEach(
855             function(w) { if (re.test(w.id)) w.destroyRecursive(); }
856         );
857         this._removeDistribAppliedEmptyRows();
858     };
859
860     this._removeAllDistribAppliedVirtual = function() {
861         /* Unregister dijits */
862         dijit.registry.forEach(
863             function(w) { if (/^dfa-\w+--/.test(w.id)) w.destroyRecursive(); }
864         );
865         this._removeDistribAppliedEmptyRows();
866     };
867
868     this._removeDistribAppliedEmptyRows = function() {
869         /* Remove any rows with no DFA at all */
870         dojo.query("tr[formula] td", "acq-lit-distrib-applied-tbody").forEach(
871             function(o) {
872                 if (o.childNodes.length < 1) dojo.destroy(o.parentNode);
873             }
874         );
875     };
876
877     /**
878      * Insert a new row into the distribution formula selection form
879      */
880     this._addDistribFormulaRow = function() {
881         var self = this;
882
883         if (!self.distribForms) {
884             // no formulas, hide the form
885             openils.Util.hide('acq-lit-distrib-formula-tbody');
886             return;
887         }
888
889         if(!this.distribFormulaTemplate) 
890             this.distribFormulaTemplate = 
891                 dojo.byId('acq-lit-distrib-formula-tbody').removeChild(dojo.byId('acq-lit-distrib-form-row'));
892
893         var row = this.distribFormulaTemplate.cloneNode(true);
894         dojo.place(row, "acq-lit-distrib-formula-tbody", "only");
895
896         this.dfSelector = new dijit.form.FilteringSelect(
897             {"labelAttr": "dynLabel", "labelType": "html"},
898             nodeByName("selector", row)
899         );
900         this._updateFormulaStore();
901         this.dfSelector.fetchProperties =
902             {"sort": [{"attribute": "use_count", "descending": true}]};
903
904         var apply = new dijit.form.Button(
905             {"label": localeStrings.APPLY},
906             nodeByName('set_button', row)
907         ); 
908
909         var reset = new dijit.form.Button(
910             {"label": localeStrings.RESET_FORMULAE, "disabled": true},
911             nodeByName("reset_button", row)  
912         );
913
914         dojo.connect(apply, 'onClick', 
915             function() {
916                 var form_id = self.dfSelector.attr("value");
917                 if(!form_id) return;
918                 self._applyDistribFormula(form_id);
919                 reset.attr("disabled", false);
920             }
921         );
922
923         dojo.connect(reset, 'onClick', 
924             function() {
925                 self.restoreCopyFieldsBeforeDF();
926                 self.virtDfaCounts = {};
927                 self.virtDfaId = -1;
928                 self.dfeOffset = 0;
929                 self._updateFormulaStore();
930                 self._removeAllDistribAppliedVirtual();
931                 reset.attr("disabled", "true");
932             }
933         );
934
935     };
936
937     /**
938      * Applies a distrib formula to the current set of copies
939      */
940     this._applyDistribFormula = function(formula) {
941         if(!formula) return;
942
943         formula = this.distribForms.filter(
944             function(form) { return form.id() == formula; }
945         )[0];
946
947         var copyRows = dojo.query('tr', self.copyTbody);
948
949         if (this.dfeOffset >= copyRows.length) {
950             alert(localeStrings.OUT_OF_COPIES);
951             return;
952         }
953
954         var entries_applied = 0;
955         for(
956             var rowIndex = this.dfeOffset;
957             rowIndex < copyRows.length;
958             rowIndex++
959         ) {
960             
961             var row = copyRows[rowIndex];
962             var copy_id = row.getAttribute('copy_id');
963             var copyWidgets = this.copyWidgetCache[copy_id];
964             var entryIndex = this.dfeOffset;
965             var entry = null;
966
967             // find the correct entry for the current row
968             dojo.forEach(formula.entries(), 
969                 function(e) {
970                     if(!entry) {
971                         entryIndex += e.item_count();
972                         if(entryIndex > rowIndex)
973                             entry = e;
974                     }
975                 }
976             );
977
978             if(entry) {
979                 
980                 //console.log("rowIndex = " + rowIndex + ", entry = " + entry.id() + ", entryIndex=" + 
981                 //  entryIndex + ", owning_lib = " + entry.owning_lib() + ", location = " + entry.location());
982     
983                 entries_applied++;
984                 this.saveCopyFieldsBeforeDF(copy_id);
985                 this._copy_fields_for_acqdf.forEach(
986                     function(field) {
987                         if(entry[field]()) {
988                             copyWidgets[field].attr('value', (entry[field]()));
989                         }
990                     }
991                 );
992             }
993         }
994
995         if (entries_applied) {
996             this.virtDfaCounts[formula.id()] =
997                 ++(this.virtDfaCounts[formula.id()]) || 1;
998             this._updateFormulaStore();
999             this._drawDistribAppliedUnit(
1000                 function(df) {
1001                     var dfa = new acqdfa();
1002                     dfa.formula(df); dfa.id(self.virtDfaId--); return dfa;
1003                 }(formula)
1004             );
1005             this.dfeOffset += entries_applied;
1006         };
1007     };
1008
1009     /**
1010      * This function updates the DF store for the dropdown so that use_counts
1011      * can reflect DF applications from this session before they're saved
1012      * server-side.
1013      */
1014     this._updateFormulaStore = function() {
1015         this.dfSelector.store = new dojo.data.ItemFileReadStore(
1016             {
1017                 "data": self._labelFormulasWithCounts(
1018                     acqdf.toStoreData(self.distribForms)
1019                 )
1020             }
1021         );
1022     };
1023
1024     this.saveCopyFieldsBeforeDF = function(copy_id) {
1025         var self = this;
1026         if (!this.oldCopyWidgetCache[copy_id]) {
1027             var copyWidgets = this.copyWidgetCache[copy_id];
1028
1029             this.oldCopyWidgetCache[copy_id] = {};
1030             this._copy_fields_for_acqdf.forEach(
1031                 function(f) {
1032                     self.oldCopyWidgetCache[copy_id][f] =
1033                         copyWidgets[f].attr("value");
1034                 }
1035             );
1036         }
1037     };
1038
1039     this.restoreCopyFieldsBeforeDF = function() {
1040         var self = this;
1041         for (var copy_id in this.oldCopyWidgetCache) {
1042             this._copy_fields_for_acqdf.forEach(
1043                 function(f) {
1044                     self.copyWidgetCache[copy_id][f].attr(
1045                         "value", self.oldCopyWidgetCache[copy_id][f]
1046                     );
1047                 }
1048             );
1049         }
1050     };
1051
1052     this._labelFormulasWithCounts = function(store_data) {
1053         for (var key in store_data.items) {
1054             var obj = store_data.items[key];
1055             obj.use_count = Number(obj.use_count); /* needed for sorting */
1056
1057             if (this.virtDfaCounts[obj.id])
1058                 obj.use_count = obj.use_count + Number(this.virtDfaCounts[obj.id]);
1059
1060             obj.dynLabel = "<span class='acq-lit-distrib-form-use-count'>[" +
1061                 obj.use_count + "]</span>&nbsp; " + obj.name;
1062         }
1063         return store_data;
1064     };
1065
1066     /**
1067      * This method formerly would not refetch the DF formulas if they'd been
1068      * loaded already, but now it always re-fetches, since use_count changes.
1069      */
1070     this._fetchDistribFormulas = function(onload) {
1071         fieldmapper.standardRequest(
1072             ["open-ils.acq",
1073                 "open-ils.acq.distribution_formula.ranged.retrieve.atomic"],
1074             {
1075                 "async": true,
1076                 "params": [openils.User.authtoken],
1077                 "oncomplete": function(r) {
1078                     self.distribForms = openils.Util.readResponse(r);
1079                     if(!self.distribForms || self.distribForms.length == 0) {
1080                         self.distribForms = [];
1081                     }
1082                     self._addDistribFormulaRow();
1083                     onload();
1084                 }
1085             }
1086         );
1087     }
1088
1089     this._drawBatchCopyWidgets = function() {
1090         var row = this.copyBatchRow;
1091         dojo.forEach(liDetailBatchFields, 
1092             function(field) {
1093                 if(self.copyBatchRowDrawn) {
1094                     self.copyBatchWidgets[field].attr('value', null);
1095                 } else {
1096                     var widget = new openils.widget.AutoFieldWidget({
1097                         fmField : field,
1098                         fmClass : 'acqlid',
1099                         labelFormat : (field == 'fund') ? fundLabelFormat : null,
1100                         searchFormat : (field == 'fund') ? fundSearchFormat : null,
1101                         parentNode : dojo.query('[name='+field+']', row)[0],
1102                         orgLimitPerms : ['CREATE_PICKLIST'],
1103                         dijitArgs : {required:false},
1104                         forceSync : true
1105                     });
1106                     widget.build(
1107                         function(w, ww) {
1108                             self.copyBatchWidgets[field] = w;
1109                         }
1110                     );
1111                 }
1112             }
1113         );
1114         this.copyBatchRowDrawn = true;
1115     };
1116
1117     this.batchCopyUpdate = function() {
1118         var self = this;
1119         for(var k in this.copyWidgetCache) {
1120             var cache = this.copyWidgetCache[k];
1121             dojo.forEach(liDetailBatchFields, function(f) {
1122                 var newval = self.copyBatchWidgets[f].attr('value');
1123                 if(newval) cache[f].attr('value', newval);
1124             });
1125         }
1126     };
1127
1128     this._drawCopies = function(li) {
1129         var self = this;
1130
1131         // this button sets the total number of copies for a given lineitem
1132         acqLitAddCopyCount.onClick = function() { 
1133             var count = acqLitCopyCountInput.attr('value');
1134
1135             // add new rows
1136             while(self.copyCount() < count)
1137                 self.addCopy(li); 
1138             
1139             // delete rows if necessary
1140             var diff = self.copyCount() - count;
1141             if(diff > 0) {
1142                 var rows = dojo.query('tr', self.copyTbody).reverse().slice(0, diff);
1143                 if(confirm(dojo.string.substitute(localeStrings.DELETE_LI_COPIES_CONFIRM, [diff]))) {
1144                     dojo.forEach(rows, function(row) {self.deleteCopy(row); });
1145                 } else {
1146                     acqLitCopyCountInput.attr('value', self.copyCount()+'');
1147                 }
1148             }
1149         }
1150
1151
1152         if(li.lineitem_details().length > 0) {
1153             dojo.forEach(li.lineitem_details(),
1154                 function(copy) {
1155                     self.addCopy(li, copy);
1156                 }
1157             );
1158         } else {
1159             self.addCopy(li);
1160         }
1161     };
1162
1163     this.copyCount = function() {
1164         var count = 0;
1165         for(var id in this.copyCache) {
1166             if(!this.copyCache[id].isdeleted())
1167                 count++;
1168         }
1169         return count;
1170     }
1171
1172     this.virtCopyId = -1;
1173     this.addCopy = function(li, copy) {
1174         var row = this.copyRow.cloneNode(true);
1175         this.copyTbody.appendChild(row);
1176         var self = this;
1177
1178         if(!copy) {
1179             copy = new fieldmapper.acqlid();
1180             copy.isnew(true);
1181             copy.id(this.virtCopyId--);
1182             copy.lineitem(li.id());
1183         }
1184
1185         this.copyCache[copy.id()] = copy;
1186         row.setAttribute('copy_id', copy.id());
1187         self.copyWidgetCache[copy.id()] = {};
1188
1189         acqLitCopyCountInput.attr('value', self.copyCount()+'');
1190
1191         dojo.forEach(liDetailFields,
1192             function(field) {
1193                 var widget = new openils.widget.AutoFieldWidget({
1194                     fmObject : copy,
1195                     fmField : field,
1196                     labelFormat : (field == 'fund') ? fundLabelFormat : null,
1197                     searchFormat : (field == 'fund') ? fundSearchFormat : null,
1198                     fmClass : 'acqlid',
1199                     parentNode : dojo.query('[name='+field+']', row)[0],
1200                     orgLimitPerms : ['CREATE_PICKLIST', 'CREATE_PURCHASE_ORDER'],
1201                     readOnly : Boolean(copy.eg_copy_id())
1202                 });
1203                 widget.build(
1204                     // make sure we capture the value from any async widgets
1205                     function(w, ww) { 
1206                         copy[field](ww.getFormattedValue()) 
1207                         self.copyWidgetCache[copy.id()][field] = w;
1208                     }
1209                 );
1210                 dojo.connect(widget.widget, 'onChange', 
1211                     function(val) { 
1212                         if(copy.isnew() || val != copy[field]()) {
1213                             // prevent setting ischanged() automatically on widget load for existing copies
1214                             copy[field](widget.getFormattedValue()) 
1215                             copy.ischanged(true);
1216                         }
1217                     }
1218                 );
1219             }
1220         );
1221
1222         this.updateLidReceivedness(copy, row);
1223     };
1224
1225     this.updateLidReceivedness = function(copy, row) {
1226         if (typeof(row) == "undefined") {
1227             row = dojo.query(
1228                 'tr[copy_id="' + copy.id() + '"]', this.copyTbody
1229             )[0];
1230         }
1231
1232         var self = this;
1233         var recv_link = nodeByName("receive", row);
1234         var unrecv_link = nodeByName("unreceive", row);
1235         var del_link = nodeByName("delete", row);
1236
1237         if (this.isPO) {
1238             openils.Util.hide(del_link.parentNode);
1239
1240             /* Avoid showing (un)receive links for virtual copies */
1241             if (copy.id() > 0) {
1242                 if(copy.recv_time()) {
1243                     openils.Util.hide(recv_link);
1244                     openils.Util.show(unrecv_link);
1245                     unrecv_link.onclick = function() {
1246                         if (confirm(localeStrings.UNRECEIVE_LID))
1247                             self.issueReceive(copy, /* rollback */ true);
1248                     };
1249                 } else {
1250                     openils.Util.hide(unrecv_link);
1251                     openils.Util.show(recv_link);
1252                     recv_link.onclick = function() {
1253                         if (self.checkLiAlerts(copy.lineitem()))
1254                             self.issueReceive(copy);
1255                     };
1256                 }
1257             } else {
1258                 openils.Util.hide(unrecv_link);
1259                 openils.Util.hide(recv_link);
1260             }
1261         } else {
1262             openils.Util.hide(unrecv_link);
1263             openils.Util.hide(recv_link);
1264
1265             del_link.onclick = function() { self.deleteCopy(row) };
1266             openils.Util.show(del_link.parentNode);
1267         }
1268     }
1269
1270     this._confirmAlert = function(li, lin) {
1271         return confirm(
1272             dojo.string.substitute(
1273                 localeStrings.CONFIRM_LI_ALERT, [
1274                     (new openils.acq.Lineitem({"lineitem": li})).findAttr(
1275                         "title", "lineitem_marc_attr_definition"
1276                     ),
1277                     lin.alert_text().code(),
1278                     lin.alert_text().description() || "",
1279                     lin.value()
1280                 ]
1281             )
1282         );
1283     };
1284
1285     this.checkLiAlerts = function(li_id) {
1286         var li = this.liCache[li_id];
1287
1288         var alert_notes = li.lineitem_notes().filter(
1289             function(o) { return Boolean(o.alert_text()); }
1290         );
1291
1292         /* this is _intentionally_ not done in a call to forEach() ... */
1293         for (var i = 0; i < alert_notes.length; i++) {
1294             if (this.noteAcks[alert_notes[i].id()])
1295                 continue;
1296             else if (!this._confirmAlert(li, alert_notes[i]))
1297                 return false;
1298             else
1299                 this.noteAcks[alert_notes[i].id()] = true;
1300         }
1301
1302         return true;
1303     };
1304
1305     this.deleteCopy = function(row) {
1306         var copy = this.copyCache[row.getAttribute('copy_id')];
1307         copy.isdeleted(true);
1308         if(copy.isnew())
1309             delete this.copyCache[copy.id()];
1310         this.copyTbody.removeChild(row);
1311     }
1312
1313     this._virtDfaCountsAsList = function() {
1314         var L = [];
1315         for (var key in this.virtDfaCounts) {
1316             for (var i = 0; i < this.virtDfaCounts[key]; i++)
1317                 L.push(key);
1318         }
1319         return L;
1320     }
1321
1322     this.saveCopyChanges = function(liId) {
1323         var self = this;
1324         var copies = [];
1325
1326
1327         var total = 0;
1328         for(var id in this.copyCache) {
1329             var c = this.copyCache[id];
1330             if(!c.isdeleted()) total++;
1331             if(c.isnew() || c.ischanged() || c.isdeleted()) {
1332                 if(c.id() < 0) c.id(null);
1333                 copies.push(c);
1334             }
1335         }
1336
1337         if (typeof(this._copy_count_cb) == "function") {
1338             this._copy_count_cb(liId, total);
1339         }
1340
1341         dojo.byId('acq-lit-copy-count-label-' + liId).innerHTML = total;
1342
1343
1344         if (copies.length > 0) {
1345             openils.Util.show("acq-lit-update-copies-progress");
1346             fieldmapper.standardRequest(
1347                 ['open-ils.acq', 'open-ils.acq.lineitem_detail.cud.batch'],
1348                 {   async: true,
1349                     params: [openils.User.authtoken, copies],
1350                     onresponse: function(r) {
1351                         var res = openils.Util.readResponse(r);
1352                         litUpdateCopiesProgress.update(res);
1353                     },
1354                     oncomplete: function() {
1355                         self.drawCopies(liId);
1356                         openils.Util.hide("acq-lit-update-copies-progress");
1357                     }
1358                 }
1359             );
1360         }
1361
1362         var dfa_list = this._virtDfaCountsAsList();
1363         if (dfa_list.length > 0) {
1364             fieldmapper.standardRequest(
1365                 ["open-ils.acq",
1366                 "open-ils.acq.distribution_formula.record_application"],
1367                 {
1368                     "async": true,
1369                     "params": [openils.User.authtoken, dfa_list, liId],
1370                     "onresponse": function(r) {
1371                         var res = openils.Util.readResponse(r);
1372                         if (res && res.length < dfa_list.length)
1373                             alert(localeStrings.DFA_NOT_ALL);
1374                     }
1375                 }
1376             );
1377             this.virtDfaCounts = {};
1378         }
1379     }
1380
1381     this.applySelectedLiAction = function(action) {
1382         var self = this;
1383         switch(action) {
1384
1385             case 'delete_selected':
1386                 this._deleteLiList(self.getSelected());
1387                 break;
1388
1389             case 'create_order':
1390
1391                 if(!this.createPoProviderSelector) {
1392                     var widget = new openils.widget.AutoFieldWidget({
1393                         fmField : 'provider',
1394                         fmClass : 'acqpo',
1395                         searchFilter: {"active": "t"},
1396                         parentNode : dojo.byId('acq-lit-po-provider'),
1397                     });
1398                     widget.build(
1399                         function(w) { self.createPoProviderSelector = w; }
1400                     );
1401                 }
1402
1403                 if(!this.createPoAgencySelector) {
1404                     var widget = new openils.widget.AutoFieldWidget({
1405                         fmField : 'ordering_agency',
1406                         fmClass : 'acqpo',
1407                         parentNode : dojo.byId('acq-lit-po-agency'),
1408                         orgLimitPerms : ['CREATE_PURCHASE_ORDER'],
1409                     });
1410                     widget.build(
1411                         function(w) { self.createPoAgencySelector = w; }
1412                     );
1413                 }
1414
1415          
1416                 acqLitPoCreateDialog.show();
1417                 break;
1418
1419             case 'save_picklist':
1420                 this._loadPLSelect();
1421                 acqLitSavePlDialog.show();
1422                 break;
1423
1424             case 'selector_ready':
1425             case 'order_ready':
1426                 acqLitChangeLiStateDialog.attr('state', action.replace('_', '-'));
1427                 acqLitChangeLiStateDialog.show();
1428                 break;
1429
1430             case 'print_po':
1431                 this.printPO();
1432                 break;
1433
1434             case 'receive_po':
1435                 this.receivePO();
1436                 break;
1437
1438             case 'rollback_receive_po':
1439                 this.rollbackPoReceive();
1440                 break;
1441
1442             case 'create_assets':
1443                 this.createAssets();
1444                 break;
1445
1446             case 'export_attr_list':
1447                 this.chooseExportAttr();
1448                 break;
1449
1450             case 'add_brief_record':
1451                 if(this.isPO)
1452                     location.href = oilsBasePath + '/acq/picklist/brief_record?po=' + this.isPO;
1453                 else
1454                     location.href = oilsBasePath + '/acq/picklist/brief_record?pl=' + this.isPL;
1455         }
1456     }
1457
1458     this.createAssets = function() {
1459         if(!this.isPO) return;
1460         if(!confirm(localeStrings.CREATE_PO_ASSETS_CONFIRM)) return;
1461         this.show('acq-lit-progress-numbers');
1462         var self = this;
1463         fieldmapper.standardRequest(
1464             ['open-ils.acq', 'open-ils.acq.purchase_order.assets.create'],
1465             {   async: true,
1466                 params: [this.authtoken, this.isPO],
1467                 onresponse: function(r) {
1468                     var resp = openils.Util.readResponse(r);
1469                     self._updateProgressNumbers(resp, true);
1470                 }
1471             }
1472         );
1473     }
1474
1475     this.chooseExportAttr = function() {
1476         if (!acqLitExportAttrSelector._li_setup) {
1477             var self = this;
1478             acqLitExportAttrSelector.store = new dojo.data.ItemFileReadStore(
1479                 {
1480                     "data": acqliad.toStoreData(
1481                         this.pcrud.search(
1482                             "acqliad", {"code": li_exportable_attrs}
1483                         )
1484                     )
1485                 }
1486             );
1487             acqLitExportAttrSelector.setValue();
1488             acqLitExportAttrButton.onClick = function(){self.exportAttrList();};
1489             acqLitExportAttrSelector._li_setup = true;
1490         }
1491         openils.Util.show("acq-lit-export-attr-holder", "inline");
1492     };
1493
1494     this.exportAttrList = function() {
1495         var attr_def = acqLitExportAttrSelector.item;
1496         var li_list = this.getSelected();
1497         var value_list = li_list.map(
1498             function(li) {
1499                 return (new openils.acq.Lineitem({"lineitem": li})).findAttr(
1500                     attr_def.code, "lineitem_marc_attr_definition"
1501                 );
1502             }
1503         ).filter(function(attr) { return Boolean(attr); });
1504
1505         if (value_list.length > 0) {
1506             if (value_list.length < li_list.length) {
1507                 if (!confirm(
1508                     dojo.string.substitute(
1509                         localeStrings.EXPORT_SHORT_LIST, [attr_def.description]
1510                     )
1511                 )) {
1512                     return;
1513                 }
1514             }
1515             try {
1516                 openils.XUL.contentToFileSaveDialog(
1517                     value_list.join("\n"),
1518                     localeStrings.EXPORT_SAVE_DIALOG_TITLE
1519                 );
1520             } catch (E) {
1521                 alert(E);
1522             }
1523         } else {
1524             alert(dojo.string.substitute(
1525                 localeStrings.EXPORT_EMPTY_LIST, [attr_def.description]
1526             ));
1527         }
1528
1529         openils.Util.hide("acq-lit-export-attr-holder");
1530     };
1531
1532     this.printPO = function() {
1533         if(!this.isPO) return;
1534         progressDialog.show(true);
1535         fieldmapper.standardRequest(
1536             ['open-ils.acq', 'open-ils.acq.purchase_order.format'],
1537             {   async: true,
1538                 params: [this.authtoken, this.isPO, 'html'],
1539                 oncomplete: function(r) {
1540                     progressDialog.hide();
1541                     var evt = openils.Util.readResponse(r);
1542                     if(evt && evt.template_output()) {
1543                         win = window.open('','', 'resizable,width=800,height=600,scrollbars=1');
1544                         win.document.body.innerHTML = evt.template_output().data();
1545                     }
1546                 }
1547             }
1548         );
1549     }
1550
1551
1552     this.receivePO = function() {
1553         if (!this.isPO) return;
1554
1555         for (var id in this.liCache) {
1556             /* assumption: liCache reflects exactly the
1557              * set of LIs that belong to our PO */
1558             if (this.liCache[id].state() != "received" &&
1559                 !this.checkLiAlerts(id)) return;
1560         }
1561
1562         this.show('acq-lit-progress-numbers');
1563         var self = this;
1564         fieldmapper.standardRequest(
1565             ['open-ils.acq', 'open-ils.acq.purchase_order.receive'],
1566             {   async: true,
1567                 params: [this.authtoken, this.isPO],
1568                 onresponse : function(r) {
1569                     var resp = openils.Util.readResponse(r);
1570                     self._updateProgressNumbers(resp, true);
1571                 },
1572             }
1573         );
1574     }
1575
1576     this.issueReceive = function(obj, rollback) {
1577         /* (For now) there shall be no marking LI or LIDs (un)received
1578          * except from the actual "view PO" interface. */
1579         if (!this.isPO) return;
1580
1581         var part =
1582             {"jub": "lineitem", "acqlid": "lineitem_detail"}[obj.classname];
1583         var method =
1584             "open-ils.acq." + part + ".receive" + (rollback ? ".rollback" : "");
1585
1586         progressDialog.show(true);
1587         fieldmapper.standardRequest(
1588             ["open-ils.acq", method], {
1589                 "async": true,
1590                 "params": [this.authtoken, obj.id()],
1591                 "onresponse": function(r) {
1592                     self.handleReceive(openils.Util.readResponse(r));
1593                 },
1594                 "oncomplete": function() { progressDialog.hide(); }
1595             }
1596         );
1597     };
1598
1599     /**
1600      * Handles the responses from receive and rollback ML calls.
1601      */
1602     this.handleReceive = function(resp) {
1603         if (resp) {
1604             if (resp.li) {
1605                 for (var li_id in resp.li) {
1606                     for (var key in resp.li[li_id])
1607                         self.liCache[li_id][key](resp.li[li_id][key]);
1608                     self.updateLiReceivedness(self.liCache[li_id]);
1609                 }
1610             }
1611             if (resp.po) {
1612                 if (typeof(self.poUpdateCallback) == "function")
1613                     self.poUpdateCallback(resp.po);
1614             }
1615             if (resp.lid) {
1616                 for (var lid_id in resp.lid) {
1617                     for (var key in resp.lid[lid_id])
1618                         self.copyCache[lid_id][key](resp.lid[lid_id][key]);
1619                     self.updateLidReceivedness(self.copyCache[lid_id]);
1620                 }
1621             }
1622         }
1623     };
1624
1625     this.rollbackPoReceive = function() {
1626         if(!this.isPO) return;
1627         if(!confirm(localeStrings.ROLLBACK_PO_RECEIVE_CONFIRM)) return;
1628         this.show('acq-lit-progress-numbers');
1629         var self = this;
1630         fieldmapper.standardRequest(
1631             ['open-ils.acq', 'open-ils.acq.purchase_order.receive.rollback'],
1632             {   async: true,
1633                 params: [this.authtoken, this.isPO],
1634                 onresponse : function(r) {
1635                     var resp = openils.Util.readResponse(r);
1636                     self._updateProgressNumbers(resp, true);
1637                 },
1638             }
1639         );
1640     }
1641
1642     this._updateProgressNumbers = function(resp, reloadOnComplete) {
1643         if(!resp) return;
1644         dojo.byId('acq-pl-lit-li-processed').innerHTML = resp.li;
1645         dojo.byId('acq-pl-lit-lid-processed').innerHTML = resp.lid;
1646         dojo.byId('acq-pl-lit-debits-processed').innerHTML = resp.debits_accrued;
1647         dojo.byId('acq-pl-lit-bibs-processed').innerHTML = resp.bibs;
1648         dojo.byId('acq-pl-lit-indexed-processed').innerHTML = resp.indexed;
1649         dojo.byId('acq-pl-lit-copies-processed').innerHTML = resp.copies;
1650         if(resp.complete && reloadOnComplete) 
1651             location.href = location.href;
1652     }
1653
1654
1655     this._createPO = function(fields) {
1656         this.show('acq-lit-progress-numbers');
1657         var po = new fieldmapper.acqpo();
1658         po.provider(this.createPoProviderSelector.attr('value'));
1659         po.ordering_agency(this.createPoAgencySelector.attr('value'));
1660
1661         var selected = this.getSelected( (fields.create_from == 'all') );
1662         if(selected.length == 0) return;
1663
1664         var max = selected.length * 3;
1665
1666         var self = this;
1667         fieldmapper.standardRequest(
1668             ['open-ils.acq', 'open-ils.acq.purchase_order.create'],
1669             {   async: true,
1670                 params: [
1671                     openils.User.authtoken, 
1672                     po, 
1673                     {
1674                         lineitems : selected.map(function(li) { return li.id() }),
1675                         create_assets : fields.create_assets[0],
1676                     }
1677                 ],
1678
1679                 onresponse : function(r) {
1680                     var resp = openils.Util.readResponse(r);
1681                     self._updateProgressNumbers(resp);
1682                     if(resp.complete) 
1683                         location.href = oilsBasePath + '/eg/acq/po/view/' + resp.purchase_order.id();
1684                 }
1685             }
1686         );
1687     }
1688
1689     this._deleteLiList = function(list, idx) {
1690         if(idx == null) idx = 0;
1691         if(idx >= list.length) return;
1692         var liId = list[idx].id();
1693         fieldmapper.standardRequest(
1694             ['open-ils.acq', 'open-ils.acq.lineitem.delete'],
1695             {   async: true,
1696                 params: [openils.User.authtoken, liId],
1697                 oncomplete: function(r) {
1698                     self.removeLineitem(liId);
1699                     self._deleteLiList(list, ++idx);
1700                 }
1701             }
1702         );
1703     }
1704
1705     this.editOrderMarc = function(li) {
1706
1707         /*  To run in Firefox directly, must set signed.applets.codebase_principal_support
1708             to true in about:config */
1709
1710         if(!openils.XUL.enableXPConnect()) return;
1711
1712         if(openils.XUL.isXUL()) {
1713             win = window.open('/xul/' + openils.XUL.buildId() + '/server/cat/marcedit.xul');
1714         } else {
1715             win = window.open('/xul/server/cat/marcedit.xul'); 
1716         }
1717         var self = this;
1718         win.xulG = {
1719             record : {marc : li.marc()},
1720             save : {
1721                 label: 'Save Record', // XXX I18N
1722                 func: function(xmlString) {
1723                     li.marc(xmlString);
1724                     fieldmapper.standardRequest(
1725                         ['open-ils.acq', 'open-ils.acq.lineitem.update'],
1726                         {   async: true,
1727                             params: [openils.User.authtoken, li],
1728                             oncomplete: function(r) {
1729                                 openils.Util.readResponse(r);
1730                                 win.close();
1731                                 self.drawInfo(li.id())
1732                             }
1733                         }
1734                     );
1735                 },
1736             }
1737         };
1738     }
1739
1740     this._savePl = function(values) {
1741         var self = this;
1742         var selected = this.getSelected( (values.which == 'all') );
1743         openils.Util.show('acq-lit-generic-progress');
1744
1745         if(values.new_name) {
1746             openils.acq.Picklist.create(
1747                 {name: values.new_name}, 
1748                 function(id) {
1749                     self._updateLiList(id, selected, 0, 
1750                         function(){
1751                             location.href = oilsBasePath + '/eg/acq/picklist/view/' + id;
1752                         });
1753                 }
1754             );
1755         } else if(values.existing_pl) {
1756             // update lineitems to use an existing picklist
1757             self._updateLiList(values.existing_pl, selected, 0, 
1758                 function(){
1759                     location.href = oilsBasePath + '/eg/acq/picklist/view/' + values.existing_pl;
1760                 });
1761         }
1762     }
1763
1764     this._updateLiState = function(values, state) {
1765         var self = this;
1766         var selected = this.getSelected( (values.which == 'all') );
1767         if(!selected.length) return;
1768         dojo.forEach(selected, function(li) {li.state(state);});
1769         self._updateLiList(null, selected, 0, 
1770             // TODO consider inline updates for efficiency
1771             function() { location.href = location.href }
1772         );
1773     }
1774
1775     this._updateLiList = function(pl, list, idx, oncomplete) {
1776         if(idx >= list.length) return oncomplete();
1777         var li = list[idx];
1778         if(pl != null) li.picklist(pl);
1779         litGenericProgress.update({maximum: list.length, progress: idx});
1780         new openils.acq.Lineitem({lineitem:li}).update(
1781             function(r) {
1782                 self._updateLiList(pl, list, ++idx, oncomplete);
1783             }
1784         );
1785     }
1786
1787     this._loadPLSelect = function() {
1788         if(this._plSelectLoaded) return;
1789         var plList = [];
1790         function handleResponse(r) {
1791             plList.push(r.recv().content());
1792         }
1793         var method = 'open-ils.acq.picklist.user.retrieve';
1794         fieldmapper.standardRequest(
1795             ['open-ils.acq', method],
1796             {   async: true,
1797                 params: [this.authtoken],
1798                 onresponse: handleResponse,
1799                 oncomplete: function() {
1800                     self._plSelectLoaded = true;
1801                     acqLitAddExistingSelect.store = 
1802                         new dojo.data.ItemFileReadStore({data:acqpl.toStoreData(plList)});
1803                     acqLitAddExistingSelect.setValue();
1804                 }
1805             }
1806         );
1807     }
1808
1809     this.showRealCopyEditUI = function(li) {
1810         copyList = [];
1811         var self = this;
1812         this.volCache = {};
1813
1814         this._fetchLineitem(li.id(), 
1815             function(fullLi) {
1816                 li = self.liCache[li.id()] = fullLi;
1817
1818                 self.pcrud.search(
1819                     'acp', {
1820                         id : li.lineitem_details().map(
1821                             function(item) { return item.eg_copy_id() }
1822                         )
1823                     }, {
1824                         async : true,
1825                         oncomplete : function(r) {
1826                             try {
1827                                 var r_list = openils.Util.readResponse( r );
1828                                 for (var i = 0; i < r_list.length; i++) {
1829                                     var copy = r_list[i];
1830                                     var volId = copy.call_number();
1831                                     var volume = self.volCache[volId];
1832                                     if(!volume) {
1833                                         volume = self.volCache[volId] = self.pcrud.retrieve('acn', volId);
1834                                     }
1835                                     copy.call_number(volume);
1836                                     copyList.push(copy);
1837                                 }
1838                                 if (xulG) {
1839                                     // If we need to, we can pass in an update_copy function to handle the update instead of volume_item_creator
1840                                     xulG.volume_item_creator( { 'existing_copies' : copyList } );
1841                                 }
1842                             } catch(E) {
1843                                 alert('error in oncomplete: ' + E);
1844                             }
1845                         }
1846                     }
1847                 );
1848             }
1849         );
1850     }
1851
1852     
1853     /*
1854     this.saveRealCopies = function() {
1855         progressDialog.show(true);
1856         var list = this.realCopyList.filter(function(copy) { return copy.ischanged(); });
1857         this.pcrud.update(list, {oncomplete: function() { 
1858             progressDialog.hide();
1859             self.show('list');
1860         }});
1861     }
1862
1863     // grab the li-details for this lineitem, grab the linked copies and volumes, add them to the table
1864     this.showRealCopies = function(li) {
1865         while(this.realCopiesTbody.childNodes[0])
1866             this.realCopiesTbody.removeChild(this.realCopiesTbody.childNodes[0]);
1867         this.show('real-copies');
1868
1869         this.realCopyList = [];
1870         this.volCache = {};
1871         var tabIndex = 1000;
1872         var self = this;
1873
1874         acqLitSaveRealCopies.onClick = function() {
1875             self.saveRealCopies();
1876         }
1877
1878         this._fetchLineitem(li.id(), 
1879             function(fullLi) {
1880                 li = self.liCache[li.id()] = fullLi;
1881
1882                 self.pcrud.search(
1883                     'acp', {
1884                         id : li.lineitem_details().map(
1885                             function(item) { return item.eg_copy_id() }
1886                         )
1887                     }, {
1888                         async : true,
1889                         streaming : true,
1890                         onresponse : function(r) {
1891                             var copy = openils.Util.readResponse(r);
1892                             var volId = copy.call_number();
1893                             var volume = self.volCache[volId];
1894                             if(!volume) {
1895                                 volume = self.volCache[volId] = self.pcrud.retrieve('acn', volId);
1896                             }
1897                             self.addRealCopy(volume, copy, tabIndex++);
1898                         }
1899                     }
1900                 );
1901             }
1902         );
1903     }
1904
1905     this.addRealCopy = function(volume, copy, tabIndex) {
1906         var row = this.realCopiesRow.cloneNode(true);
1907         this.realCopyList.push(copy);
1908
1909         var selectNode;
1910         dojo.forEach(
1911             ['owning_lib', 'location', 'circ_modifier', 'label', 'barcode'],
1912
1913             function(field) {
1914                 var isvol = (field == 'owning_lib' || field == 'label');
1915                 var widget = new openils.widget.AutoFieldWidget({
1916                     fmField : field,
1917                     fmObject : isvol ? volume : copy,
1918                     parentNode : nodeByName(field, row),
1919                     readOnly : (field != 'barcode'),
1920                 });
1921
1922                 var widgetDrawn = null;
1923
1924                 if(field == 'barcode') {
1925
1926                     widgetDrawn = function(w, ww) {
1927                         var node = w.domNode;
1928                         node.setAttribute('tabindex', ''+tabIndex);
1929
1930                         // on enter, select the next barcode input
1931                         dojo.connect(w, 'onKeyDown',
1932                             function(e) {
1933                                 if(e.keyCode == dojo.keys.ENTER) {
1934                                     var ti = node.getAttribute('tabindex');
1935                                     var nextNode = dojo.query('[tabindex=' + String(Number(ti) + 1) + ']', self.realCopiesTbody)[0];
1936                                     if(nextNode) nextNode.select();
1937                                 }
1938                             }
1939                         );
1940
1941                         dojo.connect(w, 'onChange', 
1942                             function(val) { 
1943                                 if(!val || val == copy.barcode()) return;
1944                                 copy.ischanged(true);
1945                                 copy.barcode(val);
1946                             }
1947                         );
1948
1949
1950                         if(self.realCopiesTbody.getElementsByTagName('TR').length == 0)
1951                             selectNode = node;
1952                     }
1953                 }
1954
1955                 widget.build(widgetDrawn);
1956             }
1957         );
1958
1959         this.realCopiesTbody.appendChild(row);
1960         if(selectNode) selectNode.select();
1961     };
1962     */
1963
1964 }