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