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