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