1 /* The code in this file relies on common.js */
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");
12 return dojo.byId("serialStrings").getString("batch_receive." + k).
17 return dojo.byId("serialStrings").
18 getFormattedString("batch_receive." + k, args).replace("\\n", "\n");
21 function BatchReceiver() {
24 this.init = function(authtoken, bib_id) {
26 this.user = new openils.User({"authtoken": authtoken});
27 this.pcrud = new openils.PermaCrud({"authtoken": authtoken});
28 this.authtoken = authtoken;
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");
38 dojo.byId("bib_lookup_submit").disabled = false;
39 dojo.byId("bib_search_term").value = "";
42 show("batch_receive_bib");
43 dojo.byId("bib_search_term").focus();
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")
53 this._clear_entry_batch_row();
55 this._call_number_cache = null;
56 this._prepared_call_number_controls = {};
57 this._location_by_lib = {};
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);
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);
70 hard_empty(this.entry_tbody);
76 this.bib_lookup(bib_id, null, true);
81 this._clear_entry_batch_row = function() {
83 dojo.byId("entry_batch_row").childNodes,
85 if (node.nodeType == 1 &&
86 node.getAttribute("name") != "barcode")
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");
97 if (this.bibdata.mvr.author()) {
98 dojo.byId("author_here").appendChild(T(this.bibdata.mvr.author()));
99 show("author_here_holder");
101 hide("author_here_holder");
104 show("batch_receive_bibdata_bits");
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());
113 this._show_sub_bits = function() {
114 hard_empty("sublabel_here");
116 T(this._sub_label(this.sub)),
120 hide("batch_receive_sub");
121 show("batch_receive_sub_bits");
124 this._show_issuance_bits = function() {
125 hide("batch_receive_issuance");
126 hard_empty("issuance_label_here");
128 T(this.issuance.label()),
129 "issuance_label_here",
132 show("batch_receive_issuance_bits");
135 this._get_receivable_issuances = function() {
140 fieldmapper.standardRequest(
141 ["open-ils.serial", "open-ils.serial.issuances.receivable"], {
142 "params": [this.authtoken, this.sub.id()],
144 "onresponse": function(r) {
145 if (r = openils.Util.readResponse(r))
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");
163 "menuitem", {"value": 0, "label": S("none")},
168 fieldmapper.standardRequest(
169 ["open-ils.circ", "open-ils.circ.circ_modifier.retrieve.all"],{
170 "params": [{"full": true}],
172 "onresponse": function(r) {
173 if (mods = openils.Util.readResponse(r)) {
176 return a.code() > b.code() ? 1 :
177 b.code() > a.code() ? -1 :
185 /* XXX use format string */
186 "label": mod.code()+" "+mod.name()
196 /* in this case, discard menulist and menupopup */
197 this._built_circ_modifier_dropdown =
198 dojo.create("description", {"value": "-"});
200 this._built_circ_modifier_dropdown = menulist;
204 return dojo.clone(this._built_circ_modifier_dropdown);
207 this._extend_circ_modifier_for_batch = function(control) {
209 "menuitem", {"value": -1, "label": "---"},
210 dojo.query("menupopup", control)[0],
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");
220 if (add_unset_value) {
222 "menuitem", {"value": -1, "label": "---"}, menupopup, "first"
231 "label": "(" + loc.owning_lib().shortname() + ") " +
232 loc.name() /* XXX i18n */
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],
247 "onresponse": function(r) {
248 if (locs = openils.Util.readResponse(r))
249 self._location_by_lib[lib] = locs;
255 return this._location_by_lib[lib];
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(
264 "record": this.sub.record_entry()
266 "order_by": {"acn": "label"}, /* XXX wrong sorting? */
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. */
277 var menulist = dojo.create("menulist", {
278 "editable": "true", "className": "cn"
280 var menupopup = dojo.create("menupopup", null, menulist, "only");
281 this._call_number_cache.forEach(
285 "value": cn.id(), "label": cn.label()
292 /* In this case, limit call numbers by owning_lib matching
293 * distributions's holding_lib. */
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"
300 var menupopup = dojo.create("menupopup", null, menulist,"only");
301 this._call_number_cache.filter(
302 function(cn) { return cn.owning_lib() == lib; }
307 "value": cn.id(), "label": cn.label()
312 this._prepared_call_number_controls[lib] = menulist;
314 return dojo.clone(this._prepared_call_number_controls[lib]);
318 this._build_receive_toggle = function(item) {
321 "oncommand": function(ev) {
322 self._disable_row(item.id(), !ev.target.checked);
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; }
334 this._row_disabled(row, disabled);
337 this._row_disabled = function(row, disabled) {
338 if (typeof(row) == "string") row = this.rows[row];
340 var checkbox = dojo.query("checkbox", row)[0];
342 if (typeof(disabled) != "undefined")
343 checkbox.checked = !disabled;
345 return !checkbox.checked;
348 this._row_field_value = function(row, field, value) {
349 if (typeof(row) == "string") row = this.rows[row];
351 var node = dojo.query("*", node_by_name(field, row))[0];
353 if (typeof(value) == "undefined")
359 this._user_wants_autogen = function() {
360 return dojo.byId("autogen_barcodes").checked;
363 this._get_autogen_potentials = function(item_id) {
364 var hit_a_wall = false;
366 return [openils.Util.objectProperties(this.rows).sort(num_sort).filter(
370 } else if (id <= item_id || self._row_disabled(id)) {
372 } else if (self._row_field_value(id, "barcode")) {
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"))) {
390 self._row_field_value(id, "barcode", "");
391 self.rows[id]._has_autogen_barcode = false;
400 this._have_autogen_barcodes = function() {
402 for (var id in this.rows)
403 if (this.rows[id]._has_autogen_barcode) list.push(id);
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 }
411 var for_lib = exists.filter(
412 function(cn) { return cn.owning_lib() == lib; }
414 return (exists.length && !for_lib.length);
417 this._call_number_confirm_for_lib = function(lib, value) {
418 if (!this._has_confirmed_cn_for)
419 this._has_confirmed_cn_for = {};
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()])
427 this._has_confirmed_cn_for[lib.id()] = true;
431 return this._has_confirmed_cn_for[lib.id()];
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(),
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);
457 this.bib_lookup = function(bib_search_term, evt, is_actual_id) {
458 if (evt && evt.keyCode != 13) return;
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"));
468 hide("batch_receive_sub");
469 hide("batch_receive_entry");
472 dojo.byId("bib_lookup_submit").disabled = true;
473 fieldmapper.standardRequest(
475 "open-ils.serial.biblio.record_entry.by_identifier.atomic"], {
478 "require_subscriptions": true,
480 "is_actual_id": is_actual_id
484 "oncomplete": function(r) {
485 /* These two things better come before readResponse(),
486 * which can throw exceptions. */
488 dojo.byId("bib_lookup_submit").disabled = false;
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"));
499 self.bibdata = list[0];
500 self._show_bibdata_bits();
501 self.choose_subscription();
504 alert(S("bib_lookup.not_found"));
508 dojo.byId("bib_search_term").reset();
509 dojo.byId("bib_search_term").focus();
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");
523 var subs = this.bibdata.bre.subscriptions();
525 if (subs.length > 1) {
526 var menulist = dojo.create("menulist", {"id": "sub_chooser"});
527 var menupopup = dojo.create("menupopup", {}, menulist, "only");
529 this.bibdata.bre.subscriptions().forEach(
533 "label": self._sub_label(sub),
540 hard_empty(dojo.byId("sub_chooser_here"));
542 dojo.place(menulist, dojo.byId("sub_chooser_here"), "only");
543 show("batch_receive_sub");
545 this.choose_issuance(subs[0]);
549 this.choose_issuance = function(sub) {
550 hide("batch_receive_bib");
551 hide("batch_receive_entry");
552 hide("batch_receive_sub");
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; }
559 } else { /* only one sub possible, passed in directly */
563 this._show_sub_bits();
565 this.issuances = this._get_receivable_issuances(); /* sync */
567 if (this.issuances.length > 1) {
568 var menulist = dojo.create("menulist", {"id": "issuance_chooser"});
569 var menupopup = dojo.create("menupopup", {}, menulist, "only");
573 if (a.date_published()>b.date_published()) return 1;
574 else if (b.date_published()>a.date_published()) return -1;
581 "label": issuance.label(),
582 "value": issuance.id()
588 hard_empty("issuance_chooser_here");
589 dojo.place(menulist, dojo.byId("issuance_chooser_here"), "only");
591 show("batch_receive_issuance");
592 } else if (this.issuances.length) {
593 this.load_entry_form(this.issuances[0]);
595 alert(S("issuance_lookup.none"));
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; }
608 this.issuance = issuance;
611 this._show_issuance_bits();
612 this._prepare_autogen_control();
616 fieldmapper.standardRequest(
618 "open-ils.serial.items.receivable.by_issuance.atomic"], {
619 "params": [this.authtoken, this.issuance.id()],
621 "onresponse": function(r) {
624 if (list = openils.Util.readResponse(r, false, true)) {
630 list.forEach(function(o) {self.add_entry_row(o);});
632 self.build_batch_entry_row();
633 dojo.byId("batch_receive_with_units").doCommand();
635 show("batch_receive_entry");
638 alert(S("item_lookup.none"));
639 if (self.issuances.length) self.choose_issuance();
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");
653 "barcode", "call_number", "price", "location", "circ_modifier"
656 var table_cell_func = ev.target.checked ?
657 show_table_cell : hide_table_cell;
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));
663 for (var id in self.rows) {
664 table_cell_func(node_by_name(key, self.rows[id]));
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", "");
676 this.toggle_all_receive = function(checked) {
677 for (var id in this.rows) {
678 this._disable_row(id, !checked);
682 this.build_batch_entry_row = function() {
683 var row = dojo.byId("entry_batch_row");
685 this.batch_controls = {};
687 node_by_name("note", row).appendChild(
688 this.batch_controls.note = dojo.create("textbox", {"size": 20})
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 */
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 */
708 node_by_name("call_number", row).appendChild(
709 this.batch_controls.call_number = this._build_call_number_control()
712 node_by_name("price", row).appendChild(
713 this.batch_controls.price = dojo.create("textbox", {"size": 9})
716 node_by_name("receive", row).appendChild(
719 "oncommand": function(ev) {
720 self.toggle_all_receive(ev.target.checked);
727 node_by_name("apply", row).appendChild(
728 dojo.create("button", {
730 "oncommand": function() { self.apply_batch_values(); }
735 this.apply_batch_values = function() {
736 var row = dojo.byId("entry_batch_row");
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);
744 /* XXX genericize for all fields? */
745 delete this._has_confirmed_cn_for;
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);
752 function n(s) { return node_by_name(s, row); } /* typing saver */
754 n("holding_lib").appendChild(
755 T(item.stream().distribution().holding_lib().shortname())
758 n("barcode").appendChild(
762 "tabindex": 10000 + Number(item.id()), /* is this right? */
763 "onchange": function() {
764 self.autogen_if_appropriate(this, item.id());
770 n("location").appendChild(
771 this._build_location_dropdown(
772 this._get_locations_for_lib(
773 item.stream().distribution().holding_lib().id()
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));
784 this.entry_tbody.appendChild(row);
787 this.receive = function() {
789 var confirmed_missing_units = false;
791 for (var id in this.rows) {
792 if (this._row_disabled(id))
795 var item = this.item_cache[id];
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();
804 if (barcode && cn_string.length) {
805 var unit = new sunit();
806 unit.barcode(barcode);
808 ["price", "location", "circ_modifier"].forEach(
810 var value = self._row_field_value(id, field).trim();
811 if (value) unit[field](value);
815 unit.call_number(cn_string);
817 } else if (barcode && !cn_string.length) {
818 alert(S("missing_cn"));
820 } else if (!confirmed_missing_units) {
822 (!dojo.byId("batch_receive_with_units").checked) ||
823 confirm(S("missing_units"))
825 confirmed_missing_units = true;
831 var note_value = this._row_field_value(id, "note").trim();
833 var note = new sin();
836 note.title(S("receive_time_note"));
837 note.value(note_value);
846 fieldmapper.standardRequest(
847 ["open-ils.serial", "open-ils.serial.receive_items.one_unit_per"],{
848 "params": [this.authtoken, items, this.sub.record_entry()],
850 "oncomplete": function(r) {
852 while (item_id = openils.Util.readResponse(r))
853 self.finish_receipt(item_id);
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];
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);
874 if (question && !confirm(S("autogen_barcodes.questionable")))
879 fieldmapper.standardRequest(
880 ["open-ils.cat", "open-ils.cat.item.barcode.autogen"], {
882 this.authtoken, textbox.value, list.length
885 "onresponse": function(r) {
886 r = openils.Util.readResponse(r, false, true);
888 for (var i = 0; i < r.length; i++) {
889 var row = self.rows[list[i]];
890 self._row_field_value(
893 row._has_autogen_barcode = true;
903 } /* do nothing for empty list */
907 this.init.apply(this, arguments);
911 var cgi = new openils.CGI();
913 batch_receiver = new BatchReceiver(
914 (typeof ses == "function" ? ses() : 0) ||
915 cgi.param("ses") || dojo.cookie("ses"),
916 cgi.param("docid") || null