]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/xul/staff_client/server/serial/batch_receive.js
Invoke skull-and-crossbones popup in batch receive for server-side errors
[working/Evergreen.git] / Open-ILS / xul / staff_client / server / serial / batch_receive.js
1 /* The code in this file relies on common.js */
2
3 dojo.require("dojo.cookie");
4 dojo.require("openils.Util");
5 dojo.require("openils.User");
6 dojo.require("openils.CGI");
7 dojo.require("openils.XUL");
8 dojo.require("openils.PermaCrud");
9
10 JSAN.use("util.error");
11 var _error = new util.error();
12 var batch_receiver;
13
14 function _generic_onmethoderror(r, stat, stat_text) {
15     _error.standard_unexpected_error_alert(
16         dojo.byId("commonStrings").getString("common.error"),
17         stat + ":" + stat_text
18     );
19     busy(false);
20 }
21
22 function S(k) {
23     return dojo.byId("serialStrings").getString("batch_receive." + k).
24         replace("\\n", "\n");
25 }
26
27 function F(k, args) {
28     return dojo.byId("serialStrings").
29         getFormattedString("batch_receive." + k, args).replace("\\n", "\n");
30 }
31
32 function BatchReceiver() {
33     var self = this;
34
35     this.init = function(authtoken, bib_id, sub_id) {
36         if (authtoken) {
37             this.user = new openils.User({"authtoken": authtoken});
38             this.pcrud = new openils.PermaCrud({"authtoken": authtoken});
39             this.authtoken = authtoken;
40         }
41
42         hide("batch_receive_sub");
43         hide("batch_receive_entry");
44         hide("batch_receive_bibdata_bits");
45         hide("batch_receive_sub_bits");
46         hide("batch_receive_issuance_bits");
47         hide("batch_receive_issuance");
48
49         dojo.byId("bib_lookup_submit").disabled = false;
50         dojo.byId("bib_search_term").value = "";
51
52         if (!bib_id) {
53             show("batch_receive_bib");
54             dojo.byId("bib_search_term").focus();
55         }
56
57         if (!this.entry_tbody) {
58             this.entry_tbody = dojo.byId("entry_tbody");
59             this.template = this.entry_tbody.removeChild(
60                 dojo.byId("entry_template")
61             );
62         }
63
64         this._clear_entry_batch_row();
65
66         this._call_number_cache = null;
67         this._prepared_call_number_controls = {};
68         this._location_by_lib = {};
69         this._copy_template_cache = {};
70         this._wants_print_routing = {};
71
72         /* empty the entry receiving table if we're starting over */
73         if (this.item_cache) {
74             for (var id in this.item_cache) {
75                 this.finish_receipt(this.item_cache[id]);
76                 hard_empty(this.entry_tbody);
77             }
78             /* XXX incredibly, running hard_empty() more than once seems to be
79              * good and necessary.  There's a bug under the covers somewhere,
80              * but this keeps it out of sight for the moment. */
81              hard_empty(this.entry_tbody);
82         }
83         hard_empty(this.entry_tbody);
84
85         this.rows = {};
86         this.item_cache = {};
87
88         if (bib_id)
89             this.bib_lookup(bib_id, null, true, sub_id);
90
91         busy(false);
92     };
93
94     this._clear_entry_batch_row = function() {
95         dojo.forEach(
96             dojo.byId("entry_batch_row").childNodes,
97             function(node) {
98                 if (node.nodeType == 1 &&
99                     node.getAttribute("name") != "barcode")
100                     hard_empty(node);
101             }
102         );
103     };
104
105     this._show_bibdata_bits = function() {
106         hard_empty("title_here");
107         dojo.byId("title_here").appendChild(T(this.bibdata.mvr.title()));
108         hard_empty("author_here");
109
110         if (this.bibdata.mvr.author()) {
111             dojo.byId("author_here").appendChild(T(this.bibdata.mvr.author()));
112             show("author_here_holder");
113         } else {
114             hide("author_here_holder");
115         }
116
117         show("batch_receive_bibdata_bits");
118     };
119
120     this._sub_label = function(sub) {
121         /* XXX use a formatting string from serial.properties */
122         return sub.id() + ": (" + sub.owning_lib().shortname() + ") " +
123             D(sub.start_date()) + " - " + D(sub.end_date());
124     };
125
126     this._show_sub_bits = function() {
127         hard_empty("sublabel_here");
128         dojo.place(
129             T(this._sub_label(this.sub)),
130             "sublabel_here",
131             "only"
132         );
133         hide("batch_receive_sub");
134         show("batch_receive_sub_bits");
135     };
136
137     this._show_issuance_bits = function() {
138         hide("batch_receive_issuance");
139         hard_empty("issuance_label_here");
140         dojo.place(
141             T(this.issuance.label()),
142             "issuance_label_here",
143             "only"
144         );
145         show("batch_receive_issuance_bits");
146     }
147
148     this._get_receivable_issuances = function() {
149         var issuances = [];
150
151         busy(true);
152         try {
153             fieldmapper.standardRequest(
154                 ["open-ils.serial", "open-ils.serial.issuances.receivable"], {
155                     "params": [this.authtoken, this.sub.id()],
156                     "async": false,
157                     "onresponse": function(r) {
158                         if (r = openils.Util.readResponse(r))
159                             issuances.push(r);
160                     },
161                     "onmethoderror": _generic_onmethoderror
162                 }
163             );
164         } catch (E) {
165             alert(E);
166         }
167         busy(false);
168
169         return issuances;
170     };
171
172     this._build_circ_modifier_dropdown = function() {
173         if (!this._built_circ_modifier_dropdown) {
174             var menulist = dojo.create("menulist");
175             var menupopup = dojo.create("menupopup", null, menulist, "only");
176             dojo.create(
177                 "menuitem", {"value": 0, "label": S("none")},
178                 menupopup, "first"
179             );
180
181             var mods = [];
182             fieldmapper.standardRequest(
183                 ["open-ils.circ", "open-ils.circ.circ_modifier.retrieve.all"],{
184                     "params": [{"full": true}],
185                     "async": false,
186                     "onresponse": function(r) {
187                         if (mods = openils.Util.readResponse(r)) {
188                             mods.sort(
189                                 function(a,b) {
190                                     return a.code() > b.code() ? 1 :
191                                         b.code() > a.code() ? -1 :
192                                         0;
193                                 }
194                             ).forEach(
195                                 function(mod) {
196                                     dojo.create(
197                                         "menuitem", {
198                                             "value": mod.code(),
199                                             "label": document.getElementById('commonStrings').getFormattedString('staff.circ_modifier.display',[mod.code(),mod.name(),mod.description()]) 
200                                         }, menupopup, "last"
201                                     );
202                                 }
203                             );
204                         }
205                     },
206                     "onmethoderror": _generic_onmethoderror
207                 }
208             );
209             if (!mods.length) {
210                 /* in this case, discard menulist and menupopup */
211                 this._built_circ_modifier_dropdown =
212                     dojo.create("description", {"value": "-"});
213             } else {
214                 this._built_circ_modifier_dropdown = menulist;
215             }
216         }
217
218         return dojo.clone(this._built_circ_modifier_dropdown);
219     };
220
221     this._extend_circ_modifier_for_batch = function(control) {
222         dojo.create(
223             "menuitem", {"value": -1, "label": "---"},
224             dojo.query("menupopup", control)[0],
225             "first"
226         );
227         control.value = -1;
228         return control;
229     };
230
231     this._build_location_dropdown = function(locs, add_unset_value) {
232         var menulist = dojo.create("menulist");
233         var menupopup = dojo.create("menupopup", null, menulist, "only");
234
235         if (add_unset_value) {
236             dojo.create(
237                 "menuitem", {"value": -1, "label": "---"}, menupopup, "first"
238             );
239         }
240
241         locs.forEach(
242             function(loc) {
243                 dojo.create(
244                     "menuitem", {
245                         "value": loc.id(), "label": loc.name()
246                     }, menupopup, "last"
247                 );
248             }
249         );
250
251         return menulist;
252     };
253
254     this._get_locations_for_lib = function(lib) {
255         if (!this._location_by_lib[lib]) {
256             fieldmapper.standardRequest(
257                 ["open-ils.circ", "open-ils.circ.copy_location.retrieve.all"],{
258                     "params": [lib, false, true],
259                     "async": false,
260                     "onresponse": function(r) {
261                         if (locs = openils.Util.readResponse(r))
262                             self._location_by_lib[lib] = locs;
263                     },
264                     "onmethoderror": _generic_onmethoderror
265                 }
266             );
267         }
268
269         return this._location_by_lib[lib];
270     };
271
272     this._build_call_number_control = function(item) {
273         /* In any case, give a dropdown of call numbers related to the
274          * same bre as the subscription relates to. */
275         if (!this._call_number_cache) {
276             this._call_number_cache = this.pcrud.search(
277                 "acn", {
278                     "record": this.sub.record_entry()
279                 }, {
280                     "order_by": {"acn": "label"},   /* XXX wrong sorting? */
281                 }
282             );
283         }
284
285         if (typeof item == "undefined") {
286             /* In this case, no further limiting of call numbers for now,
287              * although ideally it might be nice to limit to call numbers
288              * with owning_lib matching the holding_lib of the distribs
289              * that ultimately relate to the items. */
290
291             var menulist = dojo.create("menulist", {
292                 "editable": "true", "className": "cn"
293             });
294             var menupopup = dojo.create("menupopup", null, menulist, "only");
295
296             openils.Util.uniqueObjects(this._call_number_cache, "label").
297                 forEach(
298                     function(cn) {
299                         dojo.create(
300                             "menuitem", {
301                                 "value": cn.label(), "label": cn.label()
302                             }, menupopup, "last"
303                         );
304                     }
305                 );
306
307             return menulist;
308         } else {
309             /* In this case, limit call numbers by owning_lib matching
310              * distributions's holding_lib. */
311
312             var lib = item.stream().distribution().holding_lib().id();
313             if (!this._prepared_call_number_controls[lib]) {
314                 var menulist = dojo.create("menulist", {
315                     "editable": "true", "className": "cn"
316                 });
317                 var menupopup = dojo.create("menupopup", null, menulist,"only");
318                 this._call_number_cache.filter(
319                     function(cn) { return cn.owning_lib() == lib; }
320                 ).forEach(
321                     function(cn) {
322                         dojo.create(
323                             "menuitem", {
324                                 "value": cn.label(), "label": cn.label()
325                             }, menupopup, "last"
326                         );
327                     }
328                 );
329                 this._prepared_call_number_controls[lib] = menulist;
330             }
331             return dojo.clone(this._prepared_call_number_controls[lib]);
332         }
333     };
334
335     this._build_batch_location_dropdown = function() {
336         var menulist = dojo.create("menulist");
337         var menupopup = dojo.create("menupopup",null,menulist);
338         dojo.create("menuitem", {"value": -1, "label": "---"}, menupopup);
339
340         fieldmapper.standardRequest(
341             ["open-ils.circ",
342                 "open-ils.circ.copy_location.retrieve.distinct.atomic"],{
343                 "params": [],
344                 "async": false,
345                 "onresponse": function(r) {
346                     if (list = openils.Util.readResponse(r)) {
347                         list.forEach(
348                             function(locname) {
349                                 dojo.create(
350                                     "menuitem", {
351                                         "value": locname, "label": locname
352                                     }, menupopup
353                                 );
354                             }
355                         );
356                     }
357                 },
358                 "onmethoderror": _generic_onmethoderror
359             }
360         );
361
362         return menulist;
363     };
364
365     this._build_print_routing_toggle = function(item) {
366         var start = true;
367         var checkbox = dojo.create(
368             "checkbox", {
369                 "oncommand": function(ev) {
370                         self._print_routing(item.id(), ev.target.checked);
371                 },
372                 "checked": start.toString()
373             }
374         );
375         this._print_routing(item.id(), start);
376         return checkbox;
377     }
378
379     this._build_receive_toggle = function(item) {
380         return dojo.create(
381             "checkbox", {
382                 "oncommand": function(ev) {
383                         self._disable_row(item.id(), !ev.target.checked);
384                 },
385                 "checked": "true",
386                 "name": "receive_" + item.id()
387             }
388         );
389     }
390
391     this._disable_row = function(item_id, disabled) {
392         var row = this.rows[item_id];
393         dojo.query(
394             "textbox,menulist,checkbox:not([name^='receive_'])", row
395         ).forEach(
396             function(element) { element.disabled = disabled; }
397         );
398         this._row_disabled(row, disabled);
399     };
400
401     this._row_disabled = function(row, disabled) {
402         if (typeof(row) == "string") row = this.rows[row];
403
404         var checkbox = dojo.query("checkbox", row)[1];
405
406         if (typeof(disabled) != "undefined")
407             checkbox.checked = !disabled;
408
409         return !checkbox.checked;
410     };
411
412     this._row_print_routing_disabled = function(row, disabled) {
413         if (typeof(row) == "string") row = this.rows[row];
414
415         var checkbox = dojo.query("checkbox", row)[0];
416
417         if (typeof(disabled) != "undefined") {
418             checkbox.checked = !disabled;
419             checkbox.doCommand();
420         }
421
422         return !checkbox.checked;
423     };
424
425     this._row_field_value = function(row, field, value) {
426         if (typeof(row) == "string") row = this.rows[row];
427
428         var node = dojo.query("*", node_by_name(field, row))[0];
429
430         if (typeof(value) == "undefined") {
431             return node.value;
432         } else {
433             /* XXX The new two lines /should/ each do the same thing, but
434              * apparently they don't.  With only one or the other, I get
435              * skipped fields when this is called by the code that
436              * pre-populates fields based on copy templates.  This may
437              * have something to do with Dojo and XUL not getting along
438              * completely? */
439             dojo.attr(node, "value", value);
440             node.value = value;
441         }
442     }
443
444     this._print_routing = function(id, value) {
445         this._wants_print_routing[id] = value;
446     };
447
448         this._user_wants_autogen = function() {
449         return dojo.byId("autogen_barcodes").checked;
450     };
451
452     this._get_autogen_potentials = function(item_id) {
453         var hit_a_wall = false;
454
455         return [openils.Util.objectProperties(this.rows).sort(num_sort).filter(
456             function(id) {
457                 if (hit_a_wall) {
458                     return false;
459                 } else if (id <= item_id || self._row_disabled(id)) {
460                     return false;
461                 } else if (self._row_field_value(id, "barcode")) {
462                     hit_a_wall = true;
463                     return false;
464                 } else {
465                     return true;
466                 }
467             }
468         ), hit_a_wall];
469     };
470
471     this._prepare_autogen_control = function() {
472         dojo.attr("autogen_barcodes",
473             "command", function(ev) {
474                 if (!ev.target.checked) {
475                     var list = self._have_autogen_barcodes();
476                     if (list.length && confirm(S("autogen_barcodes.remove"))) {
477                         list.forEach(
478                             function(id) {
479                                 self._row_field_value(id, "barcode", "");
480                                 self.rows[id]._has_autogen_barcode = false;
481                             }
482                         );
483                     }
484                 }
485             }
486         );
487     };
488
489     this._have_autogen_barcodes = function() {
490         var list = [];
491         for (var id in this.rows)
492             if (this.rows[id]._has_autogen_barcode) list.push(id);
493         return list;
494     };
495
496     this._cn_exists_but_not_for_lib = function(lib, value) {
497         var exists = this._call_number_cache.filter(
498             function(cn) { return cn.label() == value }
499         );
500         var for_lib = exists.filter(
501             function(cn) { return cn.owning_lib() == lib; }
502         );
503         return (exists.length && !for_lib.length);
504     };
505
506     this._call_number_confirm_for_lib = function(lib, value) {
507         if (!this._has_confirmed_cn_for)
508             this._has_confirmed_cn_for = {};
509
510         if (typeof(this._has_confirmed_cn_for[lib.id()]) == "undefined") {
511             if (this._cn_exists_but_not_for_lib(lib.id(), value)) {
512                 this._has_confirmed_cn_for[lib.id()] = confirm(
513                     F("cn_for_lib", [lib.shortname()])
514                 );
515             } else {
516                 this._has_confirmed_cn_for[lib.id()] = true;
517             }
518         }
519
520         return this._has_confirmed_cn_for[lib.id()];
521     }
522
523     this._confirm_row_field_application = function(id, key, value) {
524         if (key == "call_number") { /* XXX make a dispatch table so we can do
525                                        this for other fields too */
526             return this._call_number_confirm_for_lib(
527                 this.item_cache[id].stream().distribution().holding_lib(),
528                 value
529             );
530         } else {
531             return true;
532         }
533     };
534
535     this._location_by_name = function(id, value) {
536         var lib = this.item_cache[id].stream().distribution().
537             holding_lib().id();
538         var winners = this._location_by_lib[lib].filter(
539             function(loc) { return loc.name() == value; }
540         );
541         if (winners.length) {
542             return winners[0].id();
543         } else {
544             return null;
545         }
546     };
547
548     this._set_all_enabled_rows = function(key, value) {
549         /* do NOT do trimming here, set whitespace as is. */
550         for (var id in this.rows) {
551             if (!this._row_disabled(id)) {
552                 if (this._confirm_row_field_application(id, key, value)) {
553                     if (key == "location") /* kludge for this field */ {
554                         if (actual = this._location_by_name(id, value))
555                             this._row_field_value(id, key, actual);
556                     } else {
557                         this._row_field_value(id, key, value);
558                     }
559                 }
560             }
561         }
562     };
563
564     this.print_routing_lists = function(streams) {
565         fieldmapper.standardRequest(
566             ["open-ils.serial",
567                 "open-ils.serial.routing_list_users.fleshed_and_ordered.atomic"],{
568                 "params": [
569                     this.authtoken, streams.map(function(o) { return o.id(); })
570                 ],
571                 "async": false,
572                 "oncomplete": function(r) {
573                     if ((r = openils.Util.readResponse(r)) && r.length) {
574                         openils.XUL.newTabEasy(
575                             "SERIAL_PRINT_ROUTING_LIST_USERS",
576                             S("print_routing_list_users"), {
577                                 "show_print_button": false, /* we supply one */
578                                 "routing_list_data": {
579                                     "streams": streams, "mvr": self.bibdata.mvr,
580                                     "issuance": self.issuance, "users": r
581                                 }
582                             }, true /* wrap_in_browser */
583                         );
584                     }
585                 },
586                 "onmethoderror": _generic_onmethoderror
587             }
588         );
589     };
590
591     this.bib_lookup = function(bib_search_term, evt, is_actual_id, sub_id) {
592         if (evt && evt.keyCode != 13) return;
593
594         if (!bib_search_term) {
595             var bib_search_term = dojo.byId("bib_search_term").value.trim();
596             if (!bib_search_term.length) {
597                 alert(S("bib_lookup.empty"));
598                 return;
599             }
600         }
601
602         hide("batch_receive_sub");
603         hide("batch_receive_entry");
604
605         busy(true);
606         dojo.byId("bib_lookup_submit").disabled = true;
607         fieldmapper.standardRequest(
608             ["open-ils.serial",
609                 "open-ils.serial.biblio.record_entry.by_identifier.atomic"], {
610                 "params": [
611                     bib_search_term, {
612                         "require_subscriptions": true,
613                         "add_mvr": true,
614                         "is_actual_id": is_actual_id
615                     }
616                 ],
617                 "async": false,
618                 "oncomplete": function(r) {
619                     /* These two things better come before readResponse(),
620                      * which can throw exceptions. */
621                     busy(false);
622                     dojo.byId("bib_lookup_submit").disabled = false;
623
624                     var list = openils.Util.readResponse(r, false, true);
625                     if (list && list.length) {
626                         if (list.length > 1) {
627                             /* XXX TODO just let the user pick one from a list,
628                              * although this circumstance seems really
629                              * unlikely.  It just can't happen for TCN, and
630                              * wouldn't be likely for ISxN or UPC... ? */
631                             alert(S("bib_lookup.multiple"));
632                         } else {
633                             self.bibdata = list[0];
634                             self._show_bibdata_bits();
635                             self.choose_subscription(sub_id);
636                         }
637                     } else {
638                         alert(S("bib_lookup.not_found"));
639                         if (is_actual_id) {
640                             self.init();
641                         } else {
642                             dojo.byId("bib_search_term").reset();
643                             dojo.byId("bib_search_term").focus();
644                         }
645                     }
646                 },
647                 "onmethoderror": _generic_onmethoderror
648             }
649         );
650     };
651
652     this.choose_subscription = function(sub_id) {
653         hide("batch_receive_bib");
654         hide("batch_receive_entry");
655         hide("batch_receive_sub_bits");
656         hide("batch_receive_issuance");
657
658         var subs = this.bibdata.bre.subscriptions();
659
660         if (sub_id) {
661             this.choose_issuance(
662                 subs.filter(function(o) { return o.id() == sub_id; })[0]
663             );
664         } else if (subs.length > 1) {
665             var menulist = dojo.create("menulist", {"id": "sub_chooser"});
666             var menupopup = dojo.create("menupopup", {}, menulist, "only");
667
668             this.bibdata.bre.subscriptions().forEach(
669                 function(sub) {
670                     dojo.create(
671                         "menuitem", {
672                             "label": self._sub_label(sub),
673                             "value": sub.id()
674                         }, menupopup, "last"
675                     );
676                 }
677             );
678
679             hard_empty(dojo.byId("sub_chooser_here"));
680
681             dojo.place(menulist, dojo.byId("sub_chooser_here"), "only");
682             show("batch_receive_sub");
683         } else {
684             this.choose_issuance(subs[0]);
685         }
686     };
687
688     this.choose_issuance = function(sub) {
689         hide("batch_receive_bib");
690         hide("batch_receive_entry");
691         hide("batch_receive_sub");
692
693         if (typeof(sub) == "undefined") {   /* sub chosen from menu */
694             var sub_id = dojo.byId("sub_chooser").value;
695             this.sub = this.bibdata.bre.subscriptions().filter(
696                 function(o) { return o.id() == sub_id; }
697             )[0];
698         } else {    /* only one sub possible, passed in directly */
699             this.sub = sub;
700         }
701
702         this._show_sub_bits();
703
704         this.issuances = this._get_receivable_issuances();   /* sync */
705
706         if (this.issuances.length > 1) {
707             var menulist = dojo.create("menulist", {"id": "issuance_chooser"});
708             var menupopup = dojo.create("menupopup", {}, menulist, "only");
709
710             this.issuances.sort(
711                 function(a, b) {
712                     if (a.date_published()>b.date_published()) return 1;
713                     else if (b.date_published()>a.date_published()) return -1;
714                     else return 0;
715                 }
716             ).forEach(
717                 function(issuance) {
718                     dojo.create(
719                         "menuitem", {
720                             "label": issuance.label(),
721                             "value": issuance.id()
722                         }, menupopup, "last"
723                     );
724                 }
725             );
726
727             hard_empty("issuance_chooser_here");
728             dojo.place(menulist, dojo.byId("issuance_chooser_here"), "only");
729
730             show("batch_receive_issuance");
731         } else if (this.issuances.length) {
732             this.load_entry_form(this.issuances[0]);
733         } else {
734             alert(S("issuance_lookup.none"));
735             this.init();
736         }
737
738     };
739
740     this._update_copy_template_cache = function() {
741         var templates_needed = openils.Util.uniqueElements(
742             openils.Util.objectProperties(this.item_cache).map(
743                 function(id) {
744                     return self.item_cache[id].stream().distribution().
745                         receive_unit_template();
746                 }
747             )
748         ).filter(
749             function(id) { return !self._copy_template_cache[id]; }
750         );
751
752         if (templates_needed.length) {
753             this.pcrud.search("act", {"id": templates_needed}).forEach(
754                 function(tmpl) {
755                     self._copy_template_cache[tmpl.id()] = tmpl;
756                 }
757             );
758         }
759     }
760
761     this.apply_copy_templates = function() {
762         this._update_copy_template_cache(); /* sync */
763
764         for (var id in this.item_cache) {
765             var item = this.item_cache[id];
766             var template_id =
767                 item.stream().distribution().receive_unit_template();
768             var template = this._copy_template_cache[template_id];
769
770             var row = this.rows[id];
771
772             var tmpl_mod = template.circ_modifier();
773             var tmpl_loc = template.location();
774             var tmpl_price = template.price();
775             if (tmpl_mod != null) {
776                 this._row_field_value(
777                     row, "circ_modifier", tmpl_mod == "" ? 0 : tmpl_mod
778                 );
779             }
780             if (tmpl_loc)
781                 this._row_field_value(row, "location", tmpl_loc);
782             if (tmpl_price > 0)
783                 this._row_field_value(row, "price", tmpl_price);
784         }
785     };
786
787     this.load_entry_form = function(issuance) {
788         if (typeof(issuance) == "undefined") {
789             var issuance_id = dojo.byId("issuance_chooser").value;
790             this.issuance = this.issuances.filter(
791                 function(o) { return o.id() == issuance_id; }
792             )[0];
793         } else {
794             this.issuance = issuance;
795         }
796
797         this._show_issuance_bits();
798         this._prepare_autogen_control();
799
800         busy(true);
801
802         fieldmapper.standardRequest(
803             ["open-ils.serial",
804                 "open-ils.serial.items.receivable.by_issuance.atomic"], {
805                 "params": [this.authtoken, this.issuance.id()],
806                 "async": true,
807                 "onresponse": function(r) {
808                     busy(false);
809
810                     if (list = openils.Util.readResponse(r, false, true)) {
811                         if (list.length) {
812                             busy(true);
813                             show("form_holder");
814
815                             list.forEach(function(o) {self.add_entry_row(o);});
816
817                             self.build_batch_entry_row();
818
819                             var recv_with_units =
820                                 dojo.byId("batch_receive_with_units");
821                             recv_with_units.doCommand();
822                             if (recv_with_units.checked)
823                                 self.apply_copy_templates();
824
825                             show("batch_receive_entry");
826                             busy(false);
827                         } else {
828                             alert(S("item_lookup.none"));
829                             if (self.issuances.length) self.choose_issuance();
830                             else self.init();
831                         }
832                     }
833                 },
834                 "onmethoderror": _generic_onmethoderror
835             }
836         );
837     };
838
839     this.toggle_receive_with_units = function(ev) {
840         var head_row = dojo.byId("batch_receive_entry_thead");
841         var batch_row = dojo.byId("entry_batch_row");
842
843         var fields = [
844             "barcode", "call_number", "price", "location", "circ_modifier"
845         ];
846
847         var table_cell_func = ev.target.checked ?
848             show_table_cell : hide_table_cell;
849         fields.forEach(
850             function(key) {
851                 if (batch_row) table_cell_func(node_by_name(key, batch_row));
852                 if (head_row) table_cell_func(node_by_name(key, head_row));
853
854                 for (var id in self.rows) {
855                     table_cell_func(node_by_name(key, self.rows[id]));
856                 }
857             }
858         );
859
860         if (!ev.target.checked) {
861             /* XXX As of the time of this writing, a blank barcode field will
862              * avoid unit creation */
863             this._set_all_enabled_rows("barcode", "");
864         }
865     };
866
867     this.toggle_all_receive = function(checked) {
868         for (var id in this.rows) {
869             this._disable_row(id, !checked);
870         }
871     };
872
873     this.toggle_all_print_routing = function(checked) {
874         for (var id in this.rows) {
875             this._row_print_routing_disabled(id, !checked);
876         }
877     };
878
879     this.build_batch_entry_row = function() {
880         var row = dojo.byId("entry_batch_row");
881
882         this.batch_controls = {};
883
884         node_by_name("note", row).appendChild(
885             this.batch_controls.note = dojo.create("textbox", {"size": 20})
886         );
887
888         node_by_name("location", row).appendChild(
889             this.batch_controls.location =
890                 this._build_batch_location_dropdown()
891         );
892
893         node_by_name("circ_modifier", row).appendChild(
894             this.batch_controls.circ_modifier =
895                 this._extend_circ_modifier_for_batch(
896                     this._build_circ_modifier_dropdown() /* for all OUs */
897                 )
898         );
899
900         node_by_name("call_number", row).appendChild(
901             this.batch_controls.call_number = this._build_call_number_control()
902         );
903
904         node_by_name("price", row).appendChild(
905             this.batch_controls.price = dojo.create("textbox", {"size": 9})
906         );
907
908         node_by_name("print_routing", row).appendChild(
909             dojo.create(
910                 "checkbox", {
911                     "oncommand": function(ev) {
912                         self.toggle_all_print_routing(ev.target.checked);
913                     },
914                     "checked": "true"
915                 }
916             )
917         );
918
919         node_by_name("receive", row).appendChild(
920             dojo.create(
921                 "checkbox", {
922                     "oncommand": function(ev) {
923                         self.toggle_all_receive(ev.target.checked);
924                     },
925                     "checked": "true"
926                 }
927             )
928         );
929
930         node_by_name("apply", row).appendChild(
931             dojo.create("button", {
932                 "label": S("apply"),
933                 "oncommand": function() { self.apply_batch_values(); }
934             })
935         );
936     };
937
938     this.apply_batch_values = function() {
939         var row = dojo.byId("entry_batch_row");
940
941         for (var key in this.batch_controls) {
942             var value = this.batch_controls[key].value;
943             if (value != "" && value != -1)
944                 this._set_all_enabled_rows(key, value);
945         }
946
947         /* XXX genericize for all fields? */
948         delete this._has_confirmed_cn_for;
949     };
950
951     this.add_entry_row = function(item) {
952         this.item_cache[item.id()] = item;
953         var row = this.rows[item.id()] = dojo.clone(this.template);
954
955         function n(s) { return node_by_name(s, row); }    /* typing saver */
956
957         var stream_dist_label = item.stream().distribution().label();
958         if (item.stream().routing_label())
959             stream_dist_label += " / " + item.stream().routing_label();
960
961         n("holding_lib").appendChild(
962             dojo.create(
963                 "description", {
964                     "value": item.stream().distribution().
965                         holding_lib().shortname(),
966                     "tooltiptext": stream_dist_label
967                 }
968             )
969         );
970
971         n("barcode").appendChild(
972             dojo.create(
973                 "textbox", {
974                     "size": 15,
975                     "tabindex": 10000 + Number(item.id()), /* is this right? */
976                     "onchange": function() {
977                         self.autogen_if_appropriate(this, item.id());
978                     }
979                 }
980             )
981         );
982
983         n("location").appendChild(
984             this._build_location_dropdown(
985                 this._get_locations_for_lib(
986                     item.stream().distribution().holding_lib().id()
987                 )
988             )
989         );
990
991         n("note").appendChild(dojo.create("textbox", {"size": 20}));
992         n("circ_modifier").appendChild(this._build_circ_modifier_dropdown());
993         n("call_number").appendChild(this._build_call_number_control(item));
994         n("price").appendChild(dojo.create("textbox", {"size": 9}));
995         n("print_routing").appendChild(this._build_print_routing_toggle(item));
996         n("receive").appendChild(this._build_receive_toggle(item));
997
998         this.entry_tbody.appendChild(row);
999     };
1000
1001     this.receive = function() {
1002         var items = [];
1003         var confirmed_missing_units = false;
1004
1005         for (var id in this.rows) {
1006             if (this._row_disabled(id))
1007                 continue;
1008
1009             var item = this.item_cache[id];
1010
1011             /* Don't trim() call_number field, as existing call numbers
1012              * are yielded by their label field, not by id, and if
1013              * they start or end in spaces, we'll unintentionally create
1014              * a new, different CN if we trim that */
1015             var cn_string = this._row_field_value(id, "call_number");
1016             var barcode = this._row_field_value(id, "barcode");
1017             if (barcode && barcode.trim) barcode = barcode.trim();
1018
1019             if (barcode && cn_string.length) {
1020                 var unit = new sunit();
1021                 unit.barcode(barcode);
1022
1023                 ["price", "location", "circ_modifier"].forEach(
1024                     function(field) {
1025                         var value = self._row_field_value(id, field);
1026                         if (value)
1027                             unit[field](value.trim ? value.trim() : value);
1028                     }
1029                 );
1030
1031                 unit.call_number(cn_string);
1032                 item.unit(unit);
1033             } else if (barcode && !cn_string.length) {
1034                 alert(S("missing_cn"));
1035                 return;
1036             } else if (!confirmed_missing_units) {
1037                 if (
1038                     (!dojo.byId("batch_receive_with_units").checked) ||
1039                     confirm(S("missing_units"))
1040                 ) {
1041                     confirmed_missing_units = true;
1042                 } else {
1043                     return;
1044                 }
1045             }
1046
1047             var note_value = this._row_field_value(id, "note").trim();
1048             if (note_value) {
1049                 var note = new sin();
1050                 note.item(id);
1051                 note.pub(false);
1052                 note.title(S("receive_time_note"));
1053                 note.value(note_value);
1054
1055                 item.notes([note]);
1056             }
1057
1058             items.push(item);
1059         }
1060
1061         busy(true);
1062         fieldmapper.standardRequest(
1063             ["open-ils.serial", "open-ils.serial.receive_items.one_unit_per"],{
1064                 "params": [this.authtoken, items, this.sub.record_entry()],
1065                 "async": true,
1066                 "oncomplete": function(r) {
1067                     try {
1068                         var streams_for_printing = [];
1069                         while (item_id = openils.Util.readResponse(r)) {
1070                             if (self._wants_print_routing[item_id]) {
1071                                 streams_for_printing.push(
1072                                     self.item_cache[item_id].stream()
1073                                 );
1074                             }
1075                             self.finish_receipt(item_id);
1076                         }
1077                         if (streams_for_printing.length)
1078                             self.print_routing_lists(streams_for_printing);
1079                     } catch (E) {
1080                         alert(E);
1081                     }
1082                     busy(false);
1083                     try {
1084                         xulG.reload_opac();
1085                     } catch(E) {
1086                         (dump ? dump : console.log)(E);
1087                     }
1088                 },
1089                 "onmethoderror": _generic_onmethoderror
1090             }
1091         );
1092     };
1093
1094     this.finish_receipt = function(item_id) {
1095         hard_empty(this.rows[item_id]);
1096         dojo.destroy(this.rows[item_id]);
1097         delete this.rows[item_id];
1098         delete this.item_cache[item_id];
1099     };
1100
1101     this.autogen_if_appropriate = function(textbox, item_id) {
1102         if (this._user_wants_autogen() && textbox.value) {
1103             var kvlist = this._get_autogen_potentials(item_id);
1104             var list = kvlist[0];
1105             var question = kvlist[1];
1106             if (list.length) {
1107                 if (question && !confirm(S("autogen_barcodes.questionable")))
1108                     return;
1109
1110                 busy(true);
1111                 try {
1112                     fieldmapper.standardRequest(
1113                         ["open-ils.cat", "open-ils.cat.item.barcode.autogen"], {
1114                             "params": [
1115                                 this.authtoken, textbox.value, list.length
1116                             ],
1117                             "async": false,
1118                             "onresponse": function(r) {
1119                                 r = openils.Util.readResponse(r, false, true);
1120                                 if (r) {
1121                                     for (var i = 0; i < r.length; i++) {
1122                                         var row = self.rows[list[i]];
1123                                         self._row_field_value(
1124                                             row, "barcode", r[i]
1125                                         );
1126                                         row._has_autogen_barcode = true;
1127                                     }
1128                                 }
1129                             },
1130                             "onmethoderror": _generic_onmethoderror
1131                         }
1132                     );
1133                 } catch (E) {
1134                     alert(E);
1135                 }
1136                 busy(false);
1137             } /* do nothing for empty list */
1138         }
1139     };
1140
1141     this.init.apply(this, arguments);
1142 }
1143
1144 function my_init() {
1145     var cgi = new openils.CGI();
1146     var authtoken = (typeof ses == "function" ? ses() : 0) ||
1147             cgi.param("ses") || dojo.cookie("ses");
1148     if(!authtoken && openils.XUL.isXUL()) {
1149         var stash = openils.XUL.getStash();
1150         authtoken = stash.session.key;
1151     }
1152     batch_receiver = new BatchReceiver(
1153         authtoken,
1154         cgi.param("docid") || null, cgi.param("subid") || null
1155     );
1156 }