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