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