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