]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/web/js/ui/default/acq/invoice/receive.js
Fix LP800480, ACQ - Vendor Invoice Won't Save
[Evergreen.git] / Open-ILS / web / js / ui / default / acq / invoice / receive.js
1 dojo.require("dijit.form.Button");
2 dojo.require("dijit.form.NumberSpinner");
3 dojo.require("openils.PermaCrud");
4 dojo.require("openils.acq.Lineitem");
5 dojo.require("openils.widget.AutoFieldWidget");
6 dojo.require("openils.widget.ProgressDialog");
7 dojo.requireLocalization("openils.acq", "acq");
8
9 var copy_table;
10 var localeStrings = dojo.i18n.getLocalization("openils.acq", "acq");
11
12 function ReceivableCopyTable() {
13     var self = this;
14
15     this._init = function() {
16         this.columns = ["owning_lib", "location", "collection_code",
17             "circ_modifier", "fund", "cn_label", "barcode"];
18
19         this.tbody = dojo.byId("rows-here");
20         this.pcrud = new openils.PermaCrud();
21
22         this.mode = "number";   /* can be "number" or "list" */
23         this.some_receiving_done = false;
24
25         this._init_select_all();
26     };
27
28     this._init_select_all = function() {
29         dojo.byId("select_all").onchange = function() {
30             var checked = this.checked;
31             dojo.query("input[type='checkbox']", self.tbody).forEach(
32                 function(cb) { cb.checked = checked; }
33             );
34         };
35     };
36
37     this._set_invoice_header = function() {
38         dojo.byId("inv-header").innerHTML = dojo.string.substitute(
39             localeStrings.INVOICE_NUMBER, [this.invoice.inv_ident()]
40         );
41     };
42
43     this._configure_for_mode = function() {
44         if (this.mode == "list") {
45             openils.Util.show("list-mode-headings", "table-header-group");
46             openils.Util.hide("set-list-mode");
47             openils.Util.show("set-number-mode");
48             dojo.byId("set-number-mode-link").onclick = function() {
49                 self.reset("number");
50                 self.load();
51             };
52         } else { /* number */
53             openils.Util.hide("list-mode-headings");
54             openils.Util.show("set-list-mode");
55             openils.Util.hide("set-number-mode");
56             dojo.byId("set-list-mode-link").onclick = function() {
57                 self.reset("list");
58                 self.load();
59             };
60         }
61     };
62
63     this._get_receivable_details = function(li) {
64         return li.lineitem_details().filter(
65             function(lid) { return (!lid.recv_time() && !lid.cancel_reason()); }
66         );
67     };
68
69     this._create_receiver = function(lid, tr, precheck) {
70         var args = {
71             "type": "checkbox",
72             "name": "receive",
73             "value": lid.id()
74         };
75
76         if (precheck) args.checked = "checked";
77
78         dojo.create("input", args, dojo.create("td", null, tr));
79     };
80
81     this._get_selected_list_mode = function() {
82         return dojo.query("input[type=checkbox]", this.tbody).filter(
83             function(cb) { return cb.checked; }
84         ).map(
85             function(cb) { return cb.value; }
86         );
87     };
88
89     this._get_selected_number_mode = function() {
90         var list = [];
91         for (var li_id in this.spinners) {
92             var spinner = this.spinners[li_id];
93             var li = spinner._li;
94
95             var number = spinner.attr("value");
96             list = list.concat(
97                 this._get_receivable_details(li).slice(0, number)
98             );
99         }
100         return list.map(function(lid) { return lid.id(); });
101     };
102
103     /* The first time this interface is loaded, use the phys_item_count field
104      * (the "# paid" column on an invoice) to determing how man items to
105      * preselect.  Otherwise use 0.
106      */
107     this._number_to_preselect = function(ie, li) {
108         return (this.some_receiving_done) ? 0 :
109             Number(ie.phys_item_count() || 0);
110
111 //        var n = Number(ie.phys_item_count() || 0) -
112 //            li.lineitem_details().filter(
113 //                function(lid) {
114 //                    return lid.recv_time() || lid.cancel_reason()
115 //                }
116 //            ).length;
117 //
118 //        return n > 0 ? n : 0;
119     };
120
121     this._render_copy_count_info = function() {
122         dojo.byId("inv-copy-count-info").innerHTML =
123             dojo.string.substitute(
124                 localeStrings.INVOICE_COPY_COUNT_INFO,
125                 [this.copy_number_received, this.copy_number_total]
126             );
127     };
128
129     this._increment_copy_count_info = function(li) {
130         var all_uncanceled = li.lineitem_details().filter(
131             function(lid) { return !lid.cancel_reason(); }
132         );
133         this.copy_number_total += all_uncanceled.length;
134         this.copy_number_received += all_uncanceled.filter(
135             function(lid) { return Boolean(lid.recv_time()); }
136         ).length;
137     };
138
139     this._add_lineitem_number_mode = function(details, li, preselect_count) {
140         var tr = dojo.create("tr", null, this.tbody);
141         var td = dojo.create("td", {
142             "colspan": 1 + this.columns.length,
143             "className": "spinner-cell"
144         }, tr);
145
146         var span_id = "number-mode-li-" + li.id();
147
148         td.innerHTML = localeStrings.COPIES_TO_RECEIVE;
149         dojo.create("span", {"id": span_id}, td);
150
151         var max = details.length;
152         var value = (preselect_count <= max ? preselect_count : max);
153
154         this.spinners[li.id()] = new dijit.form.NumberSpinner({
155             "constraints": {"min": 0, "max": max},
156             "value": value
157         }, span_id);
158         this.spinners[li.id()]._li = li;
159     };
160
161     this._add_lineitem_list_mode = function(details, li, preselect_count) {
162         details.forEach(
163             function(lid) {
164                 //dump("preselect_count "+ preselect_count+"\n");
165                 self.add_lineitem_detail(
166                     lid, li, Boolean(preselect_count-- > 0)
167                 );
168             }
169         );
170     };
171
172     this.add_lineitem_detail = function(lid, li, precheck) {
173         var tr = dojo.create(
174             "tr", {"className": "copy-row"}, this.tbody
175         );
176
177         /* Make receive checkbox cell. */
178         this._create_receiver(lid, tr, precheck);
179
180         /* Make cells for all the other columns.  Using a read-only
181          * AutoFieldWidget to show the value of each field on a lineitem
182          * detail is much easier than worrying about fleshing enough
183          * information to do the same ourselves. */
184         this.columns.forEach(
185             function(column) {
186                 var td = dojo.create("td", null, tr);
187                 new openils.widget.AutoFieldWidget({
188                     "parentNode": dojo.create("div", null, td),
189                     "fmField": column,
190                     "fmObject": lid,
191                     "readOnly": true,
192                     "dijitArgs": {"labelType": (column=='fund') ? "html" : null}
193                 }).build();
194             }
195         );
196     };
197
198     /* /maybe/ add a lineitem to the table, if it has any lineitem details
199      * that are still receivable, and preselect lineitem details up to the
200      * number specified in ie.phys_item_count() */
201     this.add_lineitem = function(ie, li, displayHTML) {
202         /* This call only affects the blurb about received vs. total copies
203          * on the invoice near the top of the display. */
204         this._increment_copy_count_info(li);
205
206         var receivable_details = this._get_receivable_details(li);
207         if (!receivable_details.length) return;
208
209         /* show lineitem overall description */
210         /* add rows for copies (lineitem details) */
211         dojo.create(
212             "td", {
213                 "colspan": 1 + this.columns.length,
214                 "innerHTML": displayHTML
215             }, dojo.create("tr", null, this.tbody)
216         );
217
218         /* build look-up table */
219         receivable_details.forEach(
220             function(lid) { self.li_by_lid[lid.id()] = li; }
221         );
222
223         /* Render something for receiving the lineitem details, depending
224          * on mode. */
225         this["_add_lineitem_" + this.mode + "_mode"](
226             receivable_details, li, this._number_to_preselect(ie, li)
227         );
228     };
229
230     this.reset = function(mode) {
231         if (mode)
232             this.mode = mode;
233
234         this.user_has_acked = [];
235         this.li_by_lid = {};
236         this.copy_number_received = 0;
237         this.copy_number_total = 0;
238
239         if (this.spinners) {
240             for (var key in this.spinners)
241                 this.spinners[key].destroy();
242         }
243
244         this.spinners = {};
245
246         this._configure_for_mode();
247
248         dojo.empty(this.tbody);
249     };
250
251     /* It's important to remember that an invoice doesn't actually have
252      * lineitems, but rather is made up of invoice entries and invoice items.
253      * Invoice entries usually link to lineitems, though (invoice items
254      * usually link to po_items).
255      */
256     this.load = function(inv_id) {
257         if (inv_id)
258             this.inv_id = inv_id;
259
260         this.reset();
261         progress_dialog.show(true);
262
263         if (!this.invoice) {
264             this.invoice = this.pcrud.retrieve("acqinv", this.inv_id);
265             this._set_invoice_header();
266         }
267
268         this.pcrud.search("acqie", {"invoice": this.inv_id}).forEach(
269             function(entry) {
270                 if (entry.lineitem()) {
271                     openils.acq.Lineitem.fetchAndRender(
272                         entry.lineitem(),
273                         {"flesh_li_details": true, "flesh_notes": true},
274                         function(li, str) { self.add_lineitem(entry, li, str); }
275                     );
276                 }
277             }
278         );
279
280         this._render_copy_count_info();
281
282         if (openils.Util.objectProperties(this.li_by_lid).length) {
283             openils.Util.show("non-empty");
284             openils.Util.hide("empty");
285         } else {
286             openils.Util.hide("non-empty");
287             openils.Util.show("empty");
288         }
289         progress_dialog.hide();
290     };
291
292     /* returns an array of lineitem_detail IDs */
293     this.get_selected = function() {
294         return this["_get_selected_" + this.mode + "_mode"]();
295     };
296
297     this.receive_lineitem_detail = function(id_list, index) {
298         if (index >= id_list.length) {
299             progress_dialog.hide();
300             this.load();
301
302             return;
303         }
304
305         var lid_id = id_list[index];
306         var li = this.li_by_lid[lid_id];
307
308         if (!this.check_lineitem_alerts(li)) {
309             self.receive_lineitem_detail(id_list, ++index);
310             return;
311         }
312
313         fieldmapper.standardRequest(
314             ["open-ils.acq", "open-ils.acq.lineitem_detail.receive"], {
315                 "async": false,
316                 "params": [openils.User.authtoken, lid_id],
317                 "oncomplete": function(r) {
318                     if (r = openils.Util.readResponse(r)) {
319                         self.some_receiving_done = true;
320                         /* receive the next lid in our list */
321                         self.receive_lineitem_detail(id_list, ++index);
322                     }
323                 }
324             }
325         );
326     };
327
328     this.receive_selected = function() {
329         var lid_ids = this.get_selected();
330
331         progress_dialog.show(true);
332
333         this.receive_lineitem_detail(lid_ids, 0);
334     };
335
336     /* 1st of 2 functions all but copied from li_table.js. Refactor this and
337      * that to share code from a 3rd place.
338      */
339     this.check_lineitem_alerts = function(lineitem) {
340         var alert_notes = lineitem.lineitem_notes().filter(
341             function(o) { return Boolean(o.alert_text()); }
342         );
343
344         for (var i = 0; i < alert_notes.length; i++) {
345             if (this.user_has_acked[alert_notes[i].id()])
346                 continue;
347             else if (!this.confirm_alert(li, alert_notes[i]))
348                 return false;
349             else
350                 this.user_has_acked[alert_notes[i].id()] = true;
351         }
352
353         return true;
354     };
355
356     /* 2nd of 2 functions all but copied from li_table.js. Refactor this and
357      * that to share code from a 3rd place.
358      */
359     this.confirm_alert = function(lineitem, note) {
360         return confirm(
361             dojo.string.substitute(
362                 localeStrings.CONFIRM_LI_ALERT, [
363                     (new openils.acq.Lineitem({"lineitem": lineitem})).findAttr(
364                         "title", "lineitem_marc_attr_definition"
365                     ),
366                     note.alert_text().code(),
367                     note.alert_text().description() || "",
368                     note.value()
369                 ]
370             )
371         );
372     };
373
374     this.back_to_invoice = function() {
375         location.href = oilsBasePath + "/acq/invoice/view/" + this.inv_id;
376     };
377
378     this._init.apply(this, arguments);
379 }
380
381 function my_init() {
382     copy_table = new ReceivableCopyTable();
383     copy_table.load(inv_id);
384 }
385
386 openils.Util.addOnLoad(my_init);