1 dojo.require("dojo.cookie");
2 dojo.require("dojo.date.locale");
3 dojo.require("dojo.date.stamp");
4 dojo.require("dojo.string");
5 dojo.require("openils.Util");
6 dojo.require("openils.User");
7 dojo.require("openils.CGI");
8 dojo.require("openils.PermaCrud");
12 String.prototype.trim = function() {return this.replace(/^\s*(.+)\s*$/,"$1");}
15 * hard_empty() is needed because dojo.empty() doesn't seem to work on
16 * XUL nodes. This also means that dojo.place() with a position argument of
17 * "only" doesn't do what it should, but calling hard_empty() on the refnode
18 * first will do the trick.
20 function hard_empty(node) {
21 if (typeof(node) == "string")
22 node = dojo.byId(node);
24 if (node && node.childNodes.length > 0) {
29 if (c.childNodes.length > 0)
30 dojo.forEach(c.childNodes, hard_empty);
39 if (typeof(e) == "string") e = dojo.byId(e);
40 openils.Util.addCSSClass(e, "hideme");
44 if (typeof(e) == "string") e = dojo.byId(e);
45 openils.Util.removeCSSClass(e, "hideme");
48 function hide_table_cell(e) {
49 if (typeof(e) == "string") e = dojo.byId(e);
51 e.style.display = "none";
52 e.style.visibility = "hidden";
55 function show_table_cell(e) {
56 if (typeof(e) == "string") e = dojo.byId(e);
57 e.style.display = "table-cell";
58 e.style.visibility = "visible";
62 if (typeof(busy._window) == "undefined")
63 busy._window = dojo.query("window")[0];
64 busy._window.style.cursor = on ? "wait" : "auto";
68 return dojo.byId("serialStrings").getString("batch_receive." + k).
73 return dojo.byId("serialStrings").
74 getFormattedString("batch_receive." + k, args).replace("\\n", "\n");
77 function T(s) { return document.createTextNode(s); }
78 function D(s) {return s ? openils.Util.timeStamp(s,{"selector":"date"}) : "";}
79 function node_by_name(s, ctx) {return dojo.query("[name='"+ s +"']",ctx)[0];}
81 function num_sort(a, b) {
82 [a, b] = [Number(a), Number(b)];
83 return a > b ? 1 : (a < b ? -1 : 0);
86 function BatchReceiver() {
89 this.init = function(authtoken, bib_id) {
91 this.user = new openils.User({"authtoken": authtoken});
92 this.pcrud = new openils.PermaCrud({"authtoken": authtoken});
93 this.authtoken = authtoken;
96 hide("batch_receive_sub");
97 hide("batch_receive_entry");
98 hide("batch_receive_bibdata_bits");
99 hide("batch_receive_sub_bits");
100 hide("batch_receive_issuance_bits");
101 hide("batch_receive_issuance");
103 dojo.byId("bib_lookup_submit").disabled = false;
104 dojo.byId("bib_search_term").value = "";
107 show("batch_receive_bib");
108 dojo.byId("bib_search_term").focus();
111 if (!this.entry_tbody) {
112 this.entry_tbody = dojo.byId("entry_tbody");
113 this.template = this.entry_tbody.removeChild(
114 dojo.byId("entry_template")
118 this._clear_entry_batch_row();
120 this._call_number_cache = null;
121 this._prepared_call_number_controls = {};
122 this._location_by_lib = {};
124 /* empty the entry receiving table if we're starting over */
125 if (this.item_cache) {
126 for (var id in this.item_cache) {
127 this.finish_receipt(this.item_cache[id]);
128 hard_empty(this.entry_tbody);
130 /* XXX incredibly, running hard_empty() more than once seems to be
131 * good and necessary. There's a bug under the covers somewhere,
132 * but this keeps it out of sight for the moment. */
133 hard_empty(this.entry_tbody);
135 hard_empty(this.entry_tbody);
138 this.item_cache = {};
141 this.bib_lookup(bib_id, null, true);
146 this._clear_entry_batch_row = function() {
148 dojo.byId("entry_batch_row").childNodes,
150 if (node.nodeType == 1 &&
151 node.getAttribute("name") != "barcode")
157 this._show_bibdata_bits = function() {
158 hard_empty("title_here");
159 dojo.byId("title_here").appendChild(T(this.bibdata.mvr.title()));
160 hard_empty("author_here");
162 if (this.bibdata.mvr.author()) {
163 dojo.byId("author_here").appendChild(T(this.bibdata.mvr.author()));
164 show("author_here_holder");
166 hide("author_here_holder");
169 show("batch_receive_bibdata_bits");
172 this._sub_label = function(sub) {
173 /* XXX use a formatting string from serial.properties */
174 return sub.id() + ": (" + sub.owning_lib().shortname() + ") " +
175 D(sub.start_date()) + " - " + D(sub.end_date());
178 this._show_sub_bits = function() {
179 hard_empty("sublabel_here");
181 T(this._sub_label(this.sub)),
185 hide("batch_receive_sub");
186 show("batch_receive_sub_bits");
189 this._show_issuance_bits = function() {
190 hide("batch_receive_issuance");
191 hard_empty("issuance_label_here");
193 T(this.issuance.label()),
194 "issuance_label_here",
197 show("batch_receive_issuance_bits");
200 this._get_receivable_issuances = function() {
205 fieldmapper.standardRequest(
206 ["open-ils.serial", "open-ils.serial.issuances.receivable"], {
207 "params": [this.authtoken, this.sub.id()],
209 "onresponse": function(r) {
210 if (r = openils.Util.readResponse(r))
223 this._build_circ_modifier_dropdown = function() {
224 if (!this._built_circ_modifier_dropdown) {
225 var menulist = dojo.create("menulist");
226 var menupopup = dojo.create("menupopup", null, menulist, "only");
228 "menuitem", {"value": 0, "label": S("none")},
233 fieldmapper.standardRequest(
234 ["open-ils.circ", "open-ils.circ.circ_modifier.retrieve.all"],{
235 "params": [{"full": true}],
237 "onresponse": function(r) {
238 if (mods = openils.Util.readResponse(r)) {
241 return a.code() > b.code() ? 1 :
242 b.code() > a.code() ? -1 :
250 /* XXX use format string */
251 "label": mod.code()+" "+mod.name()
261 /* in this case, discard menulist and menupopup */
262 this._built_circ_modifier_dropdown =
263 dojo.create("description", {"value": "-"});
265 this._built_circ_modifier_dropdown = menulist;
269 return dojo.clone(this._built_circ_modifier_dropdown);
272 this._extend_circ_modifier_for_batch = function(control) {
274 "menuitem", {"value": -1, "label": "---"},
275 dojo.query("menupopup", control)[0],
281 this._build_location_dropdown = function(locs, add_unset_value) {
282 var menulist = dojo.create("menulist");
283 var menupopup = dojo.create("menupopup", null, menulist, "only");
285 if (add_unset_value) {
287 "menuitem", {"value": -1, "label": "---"}, menupopup, "first"
296 "label": "(" + loc.owning_lib().shortname() + ") " +
297 loc.name() /* XXX i18n */
306 this._get_locations_for_lib = function(lib) {
307 if (!this._location_by_lib[lib]) {
308 fieldmapper.standardRequest(
309 ["open-ils.circ", "open-ils.circ.copy_location.retrieve.all"],{
310 "params": [lib, false, true],
312 "onresponse": function(r) {
313 if (locs = openils.Util.readResponse(r))
314 self._location_by_lib[lib] = locs;
320 return this._location_by_lib[lib];
323 this._build_call_number_control = function(item) {
324 /* In any case, give a dropdown of call numbers related to the
325 * same bre as the subscription relates to. */
326 if (!this._call_number_cache) {
327 this._call_number_cache = this.pcrud.search(
329 "record": this.sub.record_entry()
331 "order_by": {"acn": "label"}, /* XXX wrong sorting? */
336 if (typeof item == "undefined") {
337 /* In this case, no further limiting of call numbers for now,
338 * although ideally it might be nice to limit to call numbers
339 * with owning_lib matching the holding_lib of the distribs
340 * that ultimately relate to the items. */
342 var menulist = dojo.create("menulist", {
343 "editable": "true", "className": "cn"
345 var menupopup = dojo.create("menupopup", null, menulist, "only");
346 this._call_number_cache.forEach(
350 "value": cn.id(), "label": cn.label()
357 /* In this case, limit call numbers by owning_lib matching
358 * distributions's holding_lib. */
360 var lib = item.stream().distribution().holding_lib().id();
361 if (!this._prepared_call_number_controls[lib]) {
362 var menulist = dojo.create("menulist", {
363 "editable": "true", "className": "cn"
365 var menupopup = dojo.create("menupopup", null, menulist,"only");
366 this._call_number_cache.filter(
367 function(cn) { return cn.owning_lib() == lib; }
372 "value": cn.id(), "label": cn.label()
377 this._prepared_call_number_controls[lib] = menulist;
379 return dojo.clone(this._prepared_call_number_controls[lib]);
383 this._build_receive_toggle = function(item) {
386 "oncommand": function(ev) {
387 self._disable_row(item.id(), !ev.target.checked);
394 this._disable_row = function(item_id, disabled) {
395 var row = this.rows[item_id];
396 dojo.query("textbox,menulist", row).forEach(
397 function(element) { element.disabled = disabled; }
399 this._row_disabled(row, disabled);
402 this._row_disabled = function(row, disabled) {
403 if (typeof(row) == "string") row = this.rows[row];
405 var checkbox = dojo.query("checkbox", row)[0];
407 if (typeof(disabled) != "undefined")
408 checkbox.checked = !disabled;
410 return !checkbox.checked;
413 this._row_field_value = function(row, field, value) {
414 if (typeof(row) == "string") row = this.rows[row];
416 var node = dojo.query("*", node_by_name(field, row))[0];
418 if (typeof(value) == "undefined")
424 this._user_wants_autogen = function() {
425 return dojo.byId("autogen_barcodes").checked;
428 this._get_autogen_potentials = function(item_id) {
429 var hit_a_wall = false;
431 return [openils.Util.objectProperties(this.rows).sort(num_sort).filter(
435 } else if (id <= item_id || self._row_disabled(id)) {
437 } else if (self._row_field_value(id, "barcode")) {
447 this._prepare_autogen_control = function() {
448 dojo.attr("autogen_barcodes",
449 "command", function(ev) {
450 if (!ev.target.checked) {
451 var list = self._have_autogen_barcodes();
452 if (list.length && confirm(S("autogen_barcodes.remove"))) {
455 self._row_field_value(id, "barcode", "");
456 self.rows[id]._has_autogen_barcode = false;
465 this._have_autogen_barcodes = function() {
467 for (var id in this.rows)
468 if (this.rows[id]._has_autogen_barcode) list.push(id);
472 this._cn_exists_but_not_for_lib = function(lib, value) {
473 var exists = this._call_number_cache.filter(
474 function(cn) { return cn.label() == value }
476 var for_lib = exists.filter(
477 function(cn) { return cn.owning_lib() == lib; }
479 return (exists.length && !for_lib.length);
482 this._call_number_confirm_for_lib = function(lib, value) {
483 if (!this._has_confirmed_cn_for)
484 this._has_confirmed_cn_for = {};
486 if (typeof(this._has_confirmed_cn_for[lib.id()]) == "undefined") {
487 if (this._cn_exists_but_not_for_lib(lib.id(), value)) {
488 this._has_confirmed_cn_for[lib.id()] = confirm(
489 F("cn_for_lib", [lib.shortname()])
492 this._has_confirmed_cn_for[lib.id()] = true;
496 return this._has_confirmed_cn_for[lib.id()];
499 this._confirm_row_field_application = function(id, key, value) {
500 if (key == "call_number") { /* XXX make a dispatch table so we can do
501 this for other fields too */
502 return this._call_number_confirm_for_lib(
503 this.item_cache[id].stream().distribution().holding_lib(),
511 this._set_all_enabled_rows = function(key, value) {
512 /* do NOT do trimming here, set whitespace as is. */
513 for (var id in this.rows) {
514 if (!this._row_disabled(id)) {
515 if (this._confirm_row_field_application(id, key, value)) {
516 this._row_field_value(id, key, value);
522 this.bib_lookup = function(bib_search_term, evt, is_actual_id) {
523 if (evt && evt.keyCode != 13) return;
525 if (!bib_search_term) {
526 var bib_search_term = dojo.byId("bib_search_term").value.trim();
527 if (!bib_search_term.length) {
528 alert(S("bib_lookup.empty"));
533 hide("batch_receive_sub");
534 hide("batch_receive_entry");
537 dojo.byId("bib_lookup_submit").disabled = true;
538 fieldmapper.standardRequest(
540 "open-ils.serial.biblio.record_entry.by_identifier.atomic"], {
543 "require_subscriptions": true,
545 "is_actual_id": is_actual_id
549 "oncomplete": function(r) {
550 /* These two things better come before readResponse(),
551 * which can throw exceptions. */
553 dojo.byId("bib_lookup_submit").disabled = false;
555 var list = openils.Util.readResponse(r, false, true);
556 if (list && list.length) {
557 if (list.length > 1) {
558 /* XXX TODO just let the user pick one from a list,
559 * although this circumstance seems really
560 * unlikely. It just can't happen for TCN, and
561 * wouldn't be likely for ISxN or UPC... ? */
562 alert(S("bib_lookup.multiple"));
564 self.bibdata = list[0];
565 self._show_bibdata_bits();
566 self.choose_subscription();
569 alert(S("bib_lookup.not_found"));
573 dojo.byId("bib_search_term").reset();
574 dojo.byId("bib_search_term").focus();
582 this.choose_subscription = function() {
583 hide("batch_receive_bib");
584 hide("batch_receive_entry");
585 hide("batch_receive_sub_bits");
586 hide("batch_receive_issuance");
588 var subs = this.bibdata.bre.subscriptions();
590 if (subs.length > 1) {
591 var menulist = dojo.create("menulist", {"id": "sub_chooser"});
592 var menupopup = dojo.create("menupopup", {}, menulist, "only");
594 this.bibdata.bre.subscriptions().forEach(
598 "label": self._sub_label(sub),
605 hard_empty(dojo.byId("sub_chooser_here"));
607 dojo.place(menulist, dojo.byId("sub_chooser_here"), "only");
608 show("batch_receive_sub");
610 this.choose_issuance(subs[0]);
614 this.choose_issuance = function(sub) {
615 hide("batch_receive_bib");
616 hide("batch_receive_entry");
617 hide("batch_receive_sub");
619 if (typeof(sub) == "undefined") { /* sub chosen from menu */
620 var sub_id = dojo.byId("sub_chooser").value;
621 this.sub = this.bibdata.bre.subscriptions().filter(
622 function(o) { return o.id() == sub_id; }
624 } else { /* only one sub possible, passed in directly */
628 this._show_sub_bits();
630 this.issuances = this._get_receivable_issuances(); /* sync */
632 if (this.issuances.length > 1) {
633 var menulist = dojo.create("menulist", {"id": "issuance_chooser"});
634 var menupopup = dojo.create("menupopup", {}, menulist, "only");
638 if (a.date_published()>b.date_published()) return 1;
639 else if (b.date_published()>a.date_published()) return -1;
646 "label": issuance.label(),
647 "value": issuance.id()
653 hard_empty("issuance_chooser_here");
654 dojo.place(menulist, dojo.byId("issuance_chooser_here"), "only");
656 show("batch_receive_issuance");
657 } else if (this.issuances.length) {
658 this.load_entry_form(this.issuances[0]);
660 alert(S("issuance_lookup.none"));
666 this.load_entry_form = function(issuance) {
667 if (typeof(issuance) == "undefined") {
668 var issuance_id = dojo.byId("issuance_chooser").value;
669 this.issuance = this.issuances.filter(
670 function(o) { return o.id() == issuance_id; }
673 this.issuance = issuance;
676 this._show_issuance_bits();
677 this._prepare_autogen_control();
681 fieldmapper.standardRequest(
683 "open-ils.serial.items.receivable.by_issuance.atomic"], {
684 "params": [this.authtoken, this.issuance.id()],
686 "onresponse": function(r) {
689 if (list = openils.Util.readResponse(r, false, true)) {
695 list.forEach(function(o) {self.add_entry_row(o);});
697 self.build_batch_entry_row();
698 dojo.byId("batch_receive_with_units").doCommand();
700 show("batch_receive_entry");
703 alert(S("item_lookup.none"));
704 if (self.issuances.length) self.choose_issuance();
713 this.toggle_receive_with_units = function(ev) {
714 var head_row = dojo.byId("batch_receive_entry_thead");
715 var batch_row = dojo.byId("entry_batch_row");
718 "barcode", "call_number", "price", "location", "circ_modifier"
721 var table_cell_func = ev.target.checked ?
722 show_table_cell : hide_table_cell;
725 if (batch_row) table_cell_func(node_by_name(key, batch_row));
726 if (head_row) table_cell_func(node_by_name(key, head_row));
728 for (var id in self.rows) {
729 table_cell_func(node_by_name(key, self.rows[id]));
734 if (!ev.target.checked) {
735 /* XXX As of the time of this writing, a blank barcode field will
736 * avoid unit creation */
737 this._set_all_enabled_rows("barcode", "");
741 this.toggle_all_receive = function(checked) {
742 for (var id in this.rows) {
743 this._disable_row(id, !checked);
747 this.build_batch_entry_row = function() {
748 var row = dojo.byId("entry_batch_row");
750 this.batch_controls = {};
752 node_by_name("note", row).appendChild(
753 this.batch_controls.note = dojo.create("textbox", {"size": 20})
756 node_by_name("location", row).appendChild(
757 this.batch_controls.location = this._build_location_dropdown(
758 /* XXX TODO build a smarter list. rather than all copy locs
759 * under OU #1, try building a list of copy locs available to
760 * all OUs represented in actual items */
761 this._get_locations_for_lib(1),
762 true /* add_unset_value */
766 node_by_name("circ_modifier", row).appendChild(
767 this.batch_controls.circ_modifier =
768 this._extend_circ_modifier_for_batch(
769 this._build_circ_modifier_dropdown() /* for all OUs */
773 node_by_name("call_number", row).appendChild(
774 this.batch_controls.call_number = this._build_call_number_control()
777 node_by_name("price", row).appendChild(
778 this.batch_controls.price = dojo.create("textbox", {"size": 9})
781 node_by_name("receive", row).appendChild(
784 "oncommand": function(ev) {
785 self.toggle_all_receive(ev.target.checked);
792 node_by_name("apply", row).appendChild(
793 dojo.create("button", {
795 "oncommand": function() { self.apply_batch_values(); }
800 this.apply_batch_values = function() {
801 var row = dojo.byId("entry_batch_row");
803 for (var key in this.batch_controls) {
804 var value = this.batch_controls[key].value;
805 if (value != "" && value != -1)
806 this._set_all_enabled_rows(key, value);
809 /* XXX genericize for all fields? */
810 delete this._has_confirmed_cn_for;
813 this.add_entry_row = function(item) {
814 this.item_cache[item.id()] = item;
815 var row = this.rows[item.id()] = dojo.clone(this.template);
817 function n(s) { return node_by_name(s, row); } /* typing saver */
819 n("holding_lib").appendChild(
820 T(item.stream().distribution().holding_lib().shortname())
823 n("barcode").appendChild(
827 "tabindex": 10000 + Number(item.id()), /* is this right? */
828 "onchange": function() {
829 self.autogen_if_appropriate(this, item.id());
835 n("location").appendChild(
836 this._build_location_dropdown(
837 this._get_locations_for_lib(
838 item.stream().distribution().holding_lib().id()
843 n("note").appendChild(dojo.create("textbox", {"size": 20}));
844 n("circ_modifier").appendChild(this._build_circ_modifier_dropdown());
845 n("call_number").appendChild(this._build_call_number_control(item));
846 n("price").appendChild(dojo.create("textbox", {"size": 9}));
847 n("receive").appendChild(this._build_receive_toggle(item));
849 this.entry_tbody.appendChild(row);
852 this.receive = function() {
854 var confirmed_missing_units = false;
856 for (var id in this.rows) {
857 if (this._row_disabled(id))
860 var item = this.item_cache[id];
862 /* Don't trim() call_number field, as existing call numbers
863 * are yielded by their label field, not by id, and if
864 * they start or end in spaces, we'll unintentionally create
865 * a new, different CN if we trim that */
866 var cn_string = this._row_field_value(id, "call_number");
867 var barcode = this._row_field_value(id, "barcode").trim();
869 if (barcode && cn_string.length) {
870 var unit = new sunit();
871 unit.barcode(barcode);
873 ["price", "location", "circ_modifier"].forEach(
875 var value = self._row_field_value(id, field).trim();
876 if (value) unit[field](value);
880 unit.call_number(cn_string);
882 } else if (barcode && !cn_string.length) {
883 alert(S("missing_cn"));
885 } else if (!confirmed_missing_units) {
887 (!dojo.byId("batch_receive_with_units").checked) ||
888 confirm(S("missing_units"))
890 confirmed_missing_units = true;
896 var note_value = this._row_field_value(id, "note").trim();
898 var note = new sin();
901 note.title(S("receive_time_note"));
902 note.value(note_value);
911 fieldmapper.standardRequest(
912 ["open-ils.serial", "open-ils.serial.receive_items.one_unit_per"],{
913 "params": [this.authtoken, items, this.sub.record_entry()],
915 "oncomplete": function(r) {
917 while (item_id = openils.Util.readResponse(r))
918 self.finish_receipt(item_id);
928 this.finish_receipt = function(item_id) {
929 hard_empty(this.rows[item_id]);
930 dojo.destroy(this.rows[item_id]);
931 delete this.rows[item_id];
932 delete this.item_cache[item_id];
935 this.autogen_if_appropriate = function(textbox, item_id) {
936 if (this._user_wants_autogen() && textbox.value) {
937 var [list, question] = this._get_autogen_potentials(item_id);
939 if (question && !confirm(S("autogen_barcodes.questionable")))
944 fieldmapper.standardRequest(
945 ["open-ils.cat", "open-ils.cat.item.barcode.autogen"], {
947 this.authtoken, textbox.value, list.length
950 "onresponse": function(r) {
951 r = openils.Util.readResponse(r, false, true);
953 for (var i = 0; i < r.length; i++) {
954 var row = self.rows[list[i]];
955 self._row_field_value(
958 row._has_autogen_barcode = true;
968 } /* do nothing for empty list */
972 this.init.apply(this, arguments);
976 var cgi = new openils.CGI();
978 batch_receiver = new BatchReceiver(
979 (typeof ses == "function" ? ses() : 0) ||
980 cgi.param("ses") || dojo.cookie("ses"),
981 cgi.param("docid") || null