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, sub_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 = {};
58 this._copy_template_cache = {};
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);
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);
71 hard_empty(this.entry_tbody);
77 this.bib_lookup(bib_id, null, true, sub_id);
82 this._clear_entry_batch_row = function() {
84 dojo.byId("entry_batch_row").childNodes,
86 if (node.nodeType == 1 &&
87 node.getAttribute("name") != "barcode")
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");
98 if (this.bibdata.mvr.author()) {
99 dojo.byId("author_here").appendChild(T(this.bibdata.mvr.author()));
100 show("author_here_holder");
102 hide("author_here_holder");
105 show("batch_receive_bibdata_bits");
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());
114 this._show_sub_bits = function() {
115 hard_empty("sublabel_here");
117 T(this._sub_label(this.sub)),
121 hide("batch_receive_sub");
122 show("batch_receive_sub_bits");
125 this._show_issuance_bits = function() {
126 hide("batch_receive_issuance");
127 hard_empty("issuance_label_here");
129 T(this.issuance.label()),
130 "issuance_label_here",
133 show("batch_receive_issuance_bits");
136 this._get_receivable_issuances = function() {
141 fieldmapper.standardRequest(
142 ["open-ils.serial", "open-ils.serial.issuances.receivable"], {
143 "params": [this.authtoken, this.sub.id()],
145 "onresponse": function(r) {
146 if (r = openils.Util.readResponse(r))
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");
164 "menuitem", {"value": 0, "label": S("none")},
169 fieldmapper.standardRequest(
170 ["open-ils.circ", "open-ils.circ.circ_modifier.retrieve.all"],{
171 "params": [{"full": true}],
173 "onresponse": function(r) {
174 if (mods = openils.Util.readResponse(r)) {
177 return a.code() > b.code() ? 1 :
178 b.code() > a.code() ? -1 :
186 "label": document.getElementById('commonStrings').getFormattedString('staff.circ_modifier.display',[mod.code(),mod.name(),mod.description()])
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],
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");
221 if (add_unset_value) {
223 "menuitem", {"value": -1, "label": "---"}, menupopup, "first"
231 "value": loc.id(), "label": loc.name()
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],
246 "onresponse": function(r) {
247 if (locs = openils.Util.readResponse(r))
248 self._location_by_lib[lib] = locs;
254 return this._location_by_lib[lib];
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(
263 "record": this.sub.record_entry()
265 "order_by": {"acn": "label"}, /* XXX wrong sorting? */
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. */
276 var menulist = dojo.create("menulist", {
277 "editable": "true", "className": "cn"
279 var menupopup = dojo.create("menupopup", null, menulist, "only");
281 openils.Util.uniqueObjects(this._call_number_cache, "label").
286 "value": cn.label(), "label": cn.label()
294 /* In this case, limit call numbers by owning_lib matching
295 * distributions's holding_lib. */
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"
302 var menupopup = dojo.create("menupopup", null, menulist,"only");
303 this._call_number_cache.filter(
304 function(cn) { return cn.owning_lib() == lib; }
309 "value": cn.label(), "label": cn.label()
314 this._prepared_call_number_controls[lib] = menulist;
316 return dojo.clone(this._prepared_call_number_controls[lib]);
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);
325 fieldmapper.standardRequest(
327 "open-ils.circ.copy_location.retrieve.distinct.atomic"],{
330 "onresponse": function(r) {
331 if (list = openils.Util.readResponse(r)) {
336 "value": locname, "label": locname
349 this._build_receive_toggle = function(item) {
352 "oncommand": function(ev) {
353 self._disable_row(item.id(), !ev.target.checked);
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; }
365 this._row_disabled(row, disabled);
368 this._row_disabled = function(row, disabled) {
369 if (typeof(row) == "string") row = this.rows[row];
371 var checkbox = dojo.query("checkbox", row)[0];
373 if (typeof(disabled) != "undefined")
374 checkbox.checked = !disabled;
376 return !checkbox.checked;
379 this._row_field_value = function(row, field, value) {
380 if (typeof(row) == "string") row = this.rows[row];
382 var node = dojo.query("*", node_by_name(field, row))[0];
384 if (typeof(value) == "undefined") {
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
393 dojo.attr(node, "value", value);
398 this._user_wants_autogen = function() {
399 return dojo.byId("autogen_barcodes").checked;
402 this._get_autogen_potentials = function(item_id) {
403 var hit_a_wall = false;
405 return [openils.Util.objectProperties(this.rows).sort(num_sort).filter(
409 } else if (id <= item_id || self._row_disabled(id)) {
411 } else if (self._row_field_value(id, "barcode")) {
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"))) {
429 self._row_field_value(id, "barcode", "");
430 self.rows[id]._has_autogen_barcode = false;
439 this._have_autogen_barcodes = function() {
441 for (var id in this.rows)
442 if (this.rows[id]._has_autogen_barcode) list.push(id);
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 }
450 var for_lib = exists.filter(
451 function(cn) { return cn.owning_lib() == lib; }
453 return (exists.length && !for_lib.length);
456 this._call_number_confirm_for_lib = function(lib, value) {
457 if (!this._has_confirmed_cn_for)
458 this._has_confirmed_cn_for = {};
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()])
466 this._has_confirmed_cn_for[lib.id()] = true;
470 return this._has_confirmed_cn_for[lib.id()];
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(),
485 this._location_by_name = function(id, value) {
486 var lib = this.item_cache[id].stream().distribution().
488 var winners = this._location_by_lib[lib].filter(
489 function(loc) { return loc.name() == value; }
491 if (winners.length) {
492 return winners[0].id();
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);
507 this._row_field_value(id, key, value);
514 this.bib_lookup = function(bib_search_term, evt, is_actual_id, sub_id) {
515 if (evt && evt.keyCode != 13) return;
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"));
525 hide("batch_receive_sub");
526 hide("batch_receive_entry");
529 dojo.byId("bib_lookup_submit").disabled = true;
530 fieldmapper.standardRequest(
532 "open-ils.serial.biblio.record_entry.by_identifier.atomic"], {
535 "require_subscriptions": true,
537 "is_actual_id": is_actual_id
541 "oncomplete": function(r) {
542 /* These two things better come before readResponse(),
543 * which can throw exceptions. */
545 dojo.byId("bib_lookup_submit").disabled = false;
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"));
556 self.bibdata = list[0];
557 self._show_bibdata_bits();
558 self.choose_subscription(sub_id);
561 alert(S("bib_lookup.not_found"));
565 dojo.byId("bib_search_term").reset();
566 dojo.byId("bib_search_term").focus();
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");
580 var subs = this.bibdata.bre.subscriptions();
583 this.choose_issuance(
584 subs.filter(function(o) { return o.id() == sub_id; })[0]
586 } else if (subs.length > 1) {
587 var menulist = dojo.create("menulist", {"id": "sub_chooser"});
588 var menupopup = dojo.create("menupopup", {}, menulist, "only");
590 this.bibdata.bre.subscriptions().forEach(
594 "label": self._sub_label(sub),
601 hard_empty(dojo.byId("sub_chooser_here"));
603 dojo.place(menulist, dojo.byId("sub_chooser_here"), "only");
604 show("batch_receive_sub");
606 this.choose_issuance(subs[0]);
610 this.choose_issuance = function(sub) {
611 hide("batch_receive_bib");
612 hide("batch_receive_entry");
613 hide("batch_receive_sub");
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; }
620 } else { /* only one sub possible, passed in directly */
624 this._show_sub_bits();
626 this.issuances = this._get_receivable_issuances(); /* sync */
628 if (this.issuances.length > 1) {
629 var menulist = dojo.create("menulist", {"id": "issuance_chooser"});
630 var menupopup = dojo.create("menupopup", {}, menulist, "only");
634 if (a.date_published()>b.date_published()) return 1;
635 else if (b.date_published()>a.date_published()) return -1;
642 "label": issuance.label(),
643 "value": issuance.id()
649 hard_empty("issuance_chooser_here");
650 dojo.place(menulist, dojo.byId("issuance_chooser_here"), "only");
652 show("batch_receive_issuance");
653 } else if (this.issuances.length) {
654 this.load_entry_form(this.issuances[0]);
656 alert(S("issuance_lookup.none"));
662 this._update_copy_template_cache = function() {
663 var templates_needed = openils.Util.uniqueElements(
664 openils.Util.objectProperties(this.item_cache).map(
666 return self.item_cache[id].stream().distribution().
667 receive_unit_template();
671 function(id) { return !self._copy_template_cache[id]; }
674 if (templates_needed.length) {
675 this.pcrud.search("act", {"id": templates_needed}).forEach(
677 self._copy_template_cache[tmpl.id()] = tmpl;
683 this.apply_copy_templates = function() {
684 this._update_copy_template_cache(); /* sync */
686 for (var id in this.item_cache) {
687 var item = this.item_cache[id];
689 item.stream().distribution().receive_unit_template();
690 var template = this._copy_template_cache[template_id];
692 var row = this.rows[id];
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
703 this._row_field_value(row, "location", tmpl_loc);
705 this._row_field_value(row, "price", tmpl_price);
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; }
716 this.issuance = issuance;
719 this._show_issuance_bits();
720 this._prepare_autogen_control();
724 fieldmapper.standardRequest(
726 "open-ils.serial.items.receivable.by_issuance.atomic"], {
727 "params": [this.authtoken, this.issuance.id()],
729 "onresponse": function(r) {
732 if (list = openils.Util.readResponse(r, false, true)) {
737 list.forEach(function(o) {self.add_entry_row(o);});
739 self.build_batch_entry_row();
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();
747 show("batch_receive_entry");
750 alert(S("item_lookup.none"));
751 if (self.issuances.length) self.choose_issuance();
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");
765 "barcode", "call_number", "price", "location", "circ_modifier"
768 var table_cell_func = ev.target.checked ?
769 show_table_cell : hide_table_cell;
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));
775 for (var id in self.rows) {
776 table_cell_func(node_by_name(key, self.rows[id]));
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", "");
788 this.toggle_all_receive = function(checked) {
789 for (var id in this.rows) {
790 this._disable_row(id, !checked);
794 this.build_batch_entry_row = function() {
795 var row = dojo.byId("entry_batch_row");
797 this.batch_controls = {};
799 node_by_name("note", row).appendChild(
800 this.batch_controls.note = dojo.create("textbox", {"size": 20})
803 node_by_name("location", row).appendChild(
804 this.batch_controls.location =
805 this._build_batch_location_dropdown()
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 */
815 node_by_name("call_number", row).appendChild(
816 this.batch_controls.call_number = this._build_call_number_control()
819 node_by_name("price", row).appendChild(
820 this.batch_controls.price = dojo.create("textbox", {"size": 9})
823 node_by_name("receive", row).appendChild(
826 "oncommand": function(ev) {
827 self.toggle_all_receive(ev.target.checked);
834 node_by_name("apply", row).appendChild(
835 dojo.create("button", {
837 "oncommand": function() { self.apply_batch_values(); }
842 this.apply_batch_values = function() {
843 var row = dojo.byId("entry_batch_row");
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);
851 /* XXX genericize for all fields? */
852 delete this._has_confirmed_cn_for;
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);
859 function n(s) { return node_by_name(s, row); } /* typing saver */
861 var stream_dist_label = item.stream().distribution().label();
862 if (item.stream().routing_label())
863 stream_dist_label += " / " + item.stream().routing_label();
865 n("holding_lib").appendChild(
868 "value": item.stream().distribution().
869 holding_lib().shortname(),
870 "tooltiptext": stream_dist_label
875 n("barcode").appendChild(
879 "tabindex": 10000 + Number(item.id()), /* is this right? */
880 "onchange": function() {
881 self.autogen_if_appropriate(this, item.id());
887 n("location").appendChild(
888 this._build_location_dropdown(
889 this._get_locations_for_lib(
890 item.stream().distribution().holding_lib().id()
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));
901 this.entry_tbody.appendChild(row);
904 this.receive = function() {
906 var confirmed_missing_units = false;
908 for (var id in this.rows) {
909 if (this._row_disabled(id))
912 var item = this.item_cache[id];
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();
921 if (barcode && cn_string.length) {
922 var unit = new sunit();
923 unit.barcode(barcode);
925 ["price", "location", "circ_modifier"].forEach(
927 var value = self._row_field_value(id, field).trim();
928 if (value) unit[field](value);
932 unit.call_number(cn_string);
934 } else if (barcode && !cn_string.length) {
935 alert(S("missing_cn"));
937 } else if (!confirmed_missing_units) {
939 (!dojo.byId("batch_receive_with_units").checked) ||
940 confirm(S("missing_units"))
942 confirmed_missing_units = true;
948 var note_value = this._row_field_value(id, "note").trim();
950 var note = new sin();
953 note.title(S("receive_time_note"));
954 note.value(note_value);
963 fieldmapper.standardRequest(
964 ["open-ils.serial", "open-ils.serial.receive_items.one_unit_per"],{
965 "params": [this.authtoken, items, this.sub.record_entry()],
967 "oncomplete": function(r) {
969 while (item_id = openils.Util.readResponse(r))
970 self.finish_receipt(item_id);
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];
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];
993 if (question && !confirm(S("autogen_barcodes.questionable")))
998 fieldmapper.standardRequest(
999 ["open-ils.cat", "open-ils.cat.item.barcode.autogen"], {
1001 this.authtoken, textbox.value, list.length
1004 "onresponse": function(r) {
1005 r = openils.Util.readResponse(r, false, true);
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]
1012 row._has_autogen_barcode = true;
1022 } /* do nothing for empty list */
1026 this.init.apply(this, arguments);
1029 function my_init() {
1030 var cgi = new openils.CGI();
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