2b9cc4b7d276fd8ca867a99ddb050614360c276e
[working/Evergreen.git] / Open-ILS / web / js / ui / default / acq / po / view_po.js
1 dojo.require("dijit.form.Button");
2 dojo.require("dojo.string");
3 dojo.require('dijit.layout.ContentPane');
4 dojo.require('openils.PermaCrud');
5
6 var pcrud = new openils.PermaCrud();
7 var PO = null;
8 var liTable;
9 var poItemTable;
10 var poNoteTable;
11 var invoiceLinkDialogManager;
12
13 function AcqPoNoteTable() {
14     var self = this;
15
16     this.notesTbody = dojo.byId("acq-po-notes-tbody");
17     this.notesRow = this.notesTbody.removeChild(dojo.byId("acq-po-notes-row"));
18
19     dojo.byId("acq-po-notes-back-button").onclick = function() { self.hide(); };
20     dojo.byId("acq-po-view-notes").onclick = function() { self.show(); };
21
22     /* widgets' event properties are cased likeThis */
23     acqPoCreateNoteSubmit.onClick = function() {
24         if (!acqPoCreateNoteText.attr("value")) return;
25
26         /* prep new note */
27         var note = new acqpon();
28         note.vendor_public(
29             Boolean(acqPoCreateNoteVendorPublic.attr('checked'))
30         );
31         note.value(acqPoCreateNoteText.attr("value"));
32         note.purchase_order(PO.id());
33         note.isnew(true);
34
35         /* save it */
36         self.updatePoNotes(note);
37
38         /* reset fields for next use */
39         acqPoCreateNoteText.attr("value", "");
40         acqPoCreateNoteVendorPublic.attr("checked", false);
41     };
42
43     this.drawPoNote = function(note) {
44         if (note.isdeleted())
45             return;
46
47         var row = dojo.clone(this.notesRow);
48
49         nodeByName("value", row).innerHTML = note.value();
50
51         if (openils.Util.isTrue(note.vendor_public()))
52             nodeByName("vendor_public", row).innerHTML =
53                 localeStrings.VENDOR_PUBLIC;
54
55         nodeByName("delete", row).onclick = function() {
56             note.isdeleted(true);
57             self.notesTbody.removeChild(row);
58             self.updatePoNotes();
59         };
60
61         if (note.edit_time()) {
62             nodeByName("edit_time", row).innerHTML =
63                 dojo.date.locale.format(
64                     dojo.date.stamp.fromISOString(note.edit_time()),
65                     {"formatLength": "short"}
66                 );
67         }
68
69         self.notesTbody.appendChild(row);
70     };
71
72     this.drawPoNotes = function() {
73         /* sort */
74         PO.notes(
75             PO.notes().sort(
76                 function(a, b) {
77                     return (a.edit_time() < b.edit_time()) ? 1 : -1;
78                 }
79             )
80         );
81
82         /* remove old renderings of notes */
83         dojo.empty(this.notesTbody);
84
85         PO.notes().forEach(function(o) { self.drawPoNote(o); });
86     };
87
88     this.updatePoNotesCount = function() {
89         dojo.byId("acq-po-view-notes").innerHTML =
90             "(" + PO.notes().length + ")";
91     };
92
93     this.updatePoNotes = function(newNote) {
94         var notes = newNote ?
95             [newNote] :
96             PO.notes().filter(
97                 function(o) {
98                     if (o.ischanged() || o.isnew() || o.isdeleted())
99                         return o;
100                 }
101             );
102
103         if (notes.length < 1)
104             return;
105
106         progressDialog.show();
107
108         fieldmapper.standardRequest(
109             ["open-ils.acq", "open-ils.acq.po_note.cud.batch"], {
110                 "async": true,
111                 "params": [openils.User.authtoken, notes],
112                 "onresponse": function(r) {
113                     var resp = openils.Util.readResponse(r);
114                     if (resp) {
115                         progressDialog.update(resp);
116
117                         if (!resp.note.isdeleted()) {
118                             resp.note.isnew(false);
119                             resp.note.ischanged(false);
120                             PO.notes().push(resp.note);
121                         }
122                     }
123                 },
124                 "oncomplete": function() {
125                     if (!newNote) {
126                         /* remove the old changed notes */
127                         var list = [];
128                         PO.notes(
129                             PO.notes().filter(
130                                 function(o) {
131                                     return (!(
132                                         o.ischanged() || o.isnew() ||
133                                         o.isdeleted()
134                                     ));
135                                 }
136                             )
137                         );
138                     }
139
140                     progressDialog.hide();
141                     self.updatePoNotesCount();
142                     self.drawPoNotes();
143                 }
144             }
145         );
146     };
147
148     this.hide = function() {
149         openils.Util.hide("acq-po-notes-div");
150         liTable.show("list");
151         poItemTable.show();
152     };
153
154     this.show = function() {
155         liTable.hide();
156         poItemTable.hide();
157         self.drawPoNotes();
158         openils.Util.show("acq-po-notes-div");
159     };
160 }
161
162 function updatePoState(po_info) {
163     var data = po_info[PO.id()];
164     if (data) {
165         for (var key in data)
166             PO[key](data[key]);
167         renderPo();
168     }
169 }
170
171 function cancellationUpdater(r) {
172     var r = openils.Util.readResponse(r);
173     if (r) {
174         if (r.po) updatePoState(r.po);
175         if (r.li) {
176             for (var id in r.li) {
177                 liTable.liCache[id].state(r.li[id].state);
178                 liTable.liCache[id].cancel_reason(r.li[id].cancel_reason);
179                 liTable.updateLiState(liTable.liCache[id]);
180             }
181         }
182         if (r.lid && liTable.copyCache) {
183             for (var id in r.lid) {
184                 if (liTable.copyCache[id]) {
185                     liTable.copyCache[id].cancel_reason(
186                         r.lid[id].cancel_reason
187                     );
188                     liTable.updateLidState(liTable.copyCache[id]);
189                 }
190             }
191         }
192     }
193 }
194
195 function makeProviderLink(node, provider) {
196     return dojo.create(
197         "a", {
198             "href": oilsBasePath + "/conify/global/acq/provider/" + provider.id(),
199             "innerHTML": provider.name() + " (" + provider.code() + ")",
200         },
201         node,
202         "only"
203     );
204 }
205 function makePrepayWidget(node, prepay) {
206     if (prepay) {
207         openils.Util.addCSSClass(node, "oils-acq-po-prepay");
208         node.innerHTML = localeStrings.YES;
209     } else {
210         openils.Util.removeCSSClass(node, "oils-acq-po-prepay");
211         node.innerHTML = localeStrings.NO;
212     }
213 }
214
215 function makeCancelWidget(node, labelnode) {
216     openils.Util.hide("acq-po-choose-cancel-reason");
217
218     if (PO.cancel_reason()) {
219         labelnode.innerHTML = localeStrings.CANCEL_REASON;
220         node.innerHTML = PO.cancel_reason().description() + " (" +
221             PO.cancel_reason().label() + ")";
222     } else if (["on-order", "pending"].indexOf(PO.state()) == -1) {
223         dojo.destroy(this.oldTip);
224         labelnode.innerHTML = "";
225         node.innerHTML = "";
226     } else {
227         dojo.destroy(this.oldTip);
228         labelnode.innerHTML = localeStrings.CANCEL;
229         node.innerHTML = "";
230         if (!acqPoCancelReasonSubmit._prepared) {
231             var widget = new openils.widget.AutoFieldWidget({
232                 "fmField": "cancel_reason",
233                 "fmClass": "acqpo",
234                 "parentNode": dojo.byId("acq-po-cancel-reason"),
235                 "orgLimitPerms": ["CREATE_PURCHASE_ORDER"],
236                 "forceSync": true
237             });
238             widget.build(
239                 function(w, ww) {
240                     acqPoCancelReasonSubmit.onClick = function() {
241                         if (w.attr("value")) {
242                             if (confirm(localeStrings.PO_CANCEL_CONFIRM)) {
243                                 fieldmapper.standardRequest(
244                                     ["open-ils.acq",
245                                         "open-ils.acq.purchase_order.cancel"],
246                                     {
247                                         "params": [
248                                             openils.User.authtoken,
249                                             PO.id(), 
250                                             w.attr("value")
251                                         ],
252                                         "async": true,
253                                         "oncomplete": cancellationUpdater
254                                     }
255                                 );
256                             }
257                         }
258                     };
259                     acqPoCancelReasonSubmit._prepared = true;
260                 }
261             );
262         }
263         openils.Util.show("acq-po-choose-cancel-reason", "inline");
264     }
265 }
266
267 function prepareInvoiceFeatures() {
268     /* show the count of related invoices on the "view invoices" button */
269     fieldmapper.standardRequest(
270         ["open-ils.acq", "open-ils.acq.invoice.unified_search.atomic"], {
271             "params": [
272                 openils.User.authtoken,
273                 {"acqpo":[{"id": PO.id()}]},
274                 null,
275                 null,
276                 {"id_list": true}
277             ],
278             "async": true,
279             "oncomplete": function(r) {
280                 dojo.byId("acq-po-view-invoice-count").innerHTML =
281                     openils.Util.readResponse(r).length;
282             }
283         }
284     );
285
286     /* view invoices button */
287     dijit.byId("acq-po-view-invoice-link").onClick = function() {
288         location.href = oilsBasePath + "/acq/search/unified?so=" +
289             base64Encode({"acqpo":[{"id": PO.id()}]}) +
290             "&rt=invoice";
291     };
292
293     /* create invoice button */
294     dijit.byId("acq-po-create-invoice-link").onClick = function() {
295         location.href = oilsBasePath +
296             "/acq/invoice/view?create=1&attach_po=" + PO.id();
297     };
298
299     openils.Util.show("acq-po-invoice-stuff", "table-cell");
300 }
301
302 function renderPo() {
303     var po_state = PO.state();
304     dojo.byId("acq-po-view-id").innerHTML = PO.id();
305     dojo.byId("acq-po-view-name").innerHTML = PO.name();
306     makeProviderLink(
307         dojo.byId("acq-po-view-provider"),
308         PO.provider()
309     );
310     dojo.byId("acq-po-view-total-li").innerHTML = PO.lineitem_count();
311     dojo.byId("acq-po-view-total-enc").innerHTML = PO.amount_encumbered().toFixed(2);
312     dojo.byId("acq-po-view-total-spent").innerHTML = PO.amount_spent().toFixed(2);
313     dojo.byId("acq-po-view-state").innerHTML = po_state; // TODO i18n
314
315     if(PO.order_date()) {
316         openils.Util.show('acq-po-activated-on', 'inline');
317         dojo.byId('acq-po-activated-on').innerHTML = 
318             dojo.string.substitute(
319                 localeStrings.PO_ACTIVATED_ON, [
320                     openils.Util.timeStamp(PO.order_date(), {formatLength:'short'})
321                 ]
322             );
323         if(po_state == "on-order" || po_state == "cancelled") {
324             dojo.removeAttr('receive_po', 'disabled');
325         } else if(po_state == "received") {
326             dojo.removeAttr('rollback_receive_po', 'disabled');
327         }
328     }
329
330     makePrepayWidget(
331         dojo.byId("acq-po-view-prepay"),
332         openils.Util.isTrue(PO.prepayment_required())
333     );
334     makeCancelWidget(
335         dojo.byId("acq-po-view-cancel-reason"),
336         dojo.byId("acq-po-cancel-label")
337     );
338     // dojo.byId("acq-po-view-notes").innerHTML = PO.notes().length;
339     poNoteTable.updatePoNotesCount();
340
341     if (po_state == "pending") {
342         checkCouldActivatePo();
343         if (PO.lineitem_count() > 1)
344             openils.Util.show("acq-po-split");
345     } else {
346         dojo.byId("acq-po-activate-checking").innerHTML = localeStrings.NO;
347     }
348
349     // XXX we probably don't *always* need to do this...
350     poItemTable.reset();
351     PO.po_items().forEach(
352         function(po_item) { poItemTable.addItem(po_item); }
353     );
354     poItemTable.show();
355
356     dojo.attr(
357         "acq-po-view-history", "href",
358         oilsBasePath + "/acq/po/history/" + PO.id()
359     );
360     openils.Util.show("acq-po-view-history", "inline");
361
362     
363     /* if we got here from the search/invoice page with a focused LI,
364      * return to the previous page with the same LI focused */
365     var cgi = new openils.CGI();
366     var source = cgi.param('source');
367     var focus_li = cgi.param('focus_li');
368     if (focus_li && source) {
369         dojo.forEach(
370             ['search', 'invoice'], // perhaps a wee bit too loose
371             function(srcType) {
372                 if (source.match(new RegExp(srcType))) {
373                     openils.Util.show('acq-po-return-to-' + srcType);
374                     var newCgi = new openils.CGI({url : source});
375                     newCgi.param('focus_li', focus_li);
376                     dojo.byId('acq-po-return-to-' + srcType + '-button').onclick = function() {
377                         location.href = newCgi.url();
378                     }
379                 }
380             }
381         );
382     }
383
384     prepareInvoiceFeatures();
385 }
386
387
388 function init() {
389     /* set up li table */
390     liTable = new AcqLiTable();
391     liTable.reset();
392     liTable.isPO = poId;
393     liTable.poUpdateCallback = updatePoState;
394
395     /* set up po notes table */
396     poNoteTable = new AcqPoNoteTable();
397
398     /* retrieve data and populate */
399     fieldmapper.standardRequest(
400         ['open-ils.acq', 'open-ils.acq.purchase_order.retrieve'],
401         {   async: true,
402             params: [openils.User.authtoken, poId, {
403                 "flesh_provider": true,
404                 "flesh_price_summary": true,
405                 "flesh_lineitem_count": true,
406                 "flesh_notes": true,
407                 "flesh_po_items": true
408             }],
409             oncomplete: function(r) {
410                 PO = openils.Util.readResponse(r); /* save PO globally */
411
412                 /* po item table */
413                 poItemTable = new PoItemTable(PO, pcrud);
414
415                 liTable.testOrderIdentPerms( PO.ordering_agency(), init2);
416
417                 renderPo();
418             }
419         }
420     );
421
422 }
423
424 function init2() {
425
426     var totalEstimated = 0;
427     var zeroLi = true;
428     fieldmapper.standardRequest(
429         ['open-ils.acq', 'open-ils.acq.lineitem.search'],
430         {   async: true,
431             params: [
432                 openils.User.authtoken, 
433                 [{purchase_order:poId}, {"order_by": {"jub": "id ASC"}}], 
434                 {flesh_attrs:true, flesh_notes:true, flesh_cancel_reason:true, clear_marc:true}
435             ],
436             onresponse: function(r) {
437                 zeroLi = false;
438                 liTable.show('list');
439                 var li = openils.Util.readResponse(r);
440                 // TODO: Add po_item's to total estimated amount
441                 totalEstimated += (Number(li.item_count() || 0) * Number(li.estimated_unit_price() || 0));
442                 liTable.addLineitem(li);
443             },
444
445             oncomplete : function() {
446                 dojo.byId("acq-po-view-total-estimated").innerHTML = totalEstimated.toFixed(2);
447                 if (liFocus) liTable.drawCopies(liFocus);
448                 if(zeroLi) openils.Util.show('acq-po-no-lineitems');
449             }
450         }
451     );
452
453     pcrud.search(
454         'acqedim', 
455         {purchase_order : poId}, 
456         {
457             id_list : true,
458             oncomplete : function(r) {
459                 var resp = openils.Util.readResponse(r);
460                 // TODO: I18n
461                 if(resp) {
462                     dojo.byId('acq-po-view-edi-messages').innerHTML = '(' + resp.length + ')';
463                     dojo.byId('acq-po-view-edi-messages').setAttribute('href', oilsBasePath + '/acq/po/edi_messages/' + poId);
464                 } else {
465                     dojo.byId('acq-po-view-edi-messages').innerHTML = '0';
466                     dojo.byId('acq-po-view-edi-messages').setAttribute('href', '');
467                 }
468             }
469         }
470     );
471 }
472
473 function checkCouldActivatePo() {
474     var d = dojo.byId("acq-po-activate-checking");
475     var a = dojo.byId("acq-po-activate-link");
476     d.innerHTML = localeStrings.PO_CHECKING;
477     var warnings = [];
478     var stops = [];
479     var other = [];
480
481     fieldmapper.standardRequest(
482         ["open-ils.acq", "open-ils.acq.purchase_order.activate.dry_run"], {
483             "params": [
484                 openils.User.authtoken,
485                 PO.id(),
486                 null,  // vandelay options
487                 {zero_copy_activate : dojo.byId('acq-po-activate-zero-copies').checked}
488             ],
489             "async": true,
490             "onresponse": function(r) {
491                 if ((r = openils.Util.readResponse(r, true /* eventOk */))) {
492                     if (typeof(r.textcode) != "undefined") {
493                         switch(r.textcode) {
494                             case "ACQ_FUND_EXCEEDS_STOP_PERCENT":
495                                 stops.push(r);
496                                 break;
497                             case "ACQ_FUND_EXCEEDS_WARN_PERCENT":
498                                 warnings.push(r);
499                                 break;
500                             default:
501                                 other.push(r);
502                         }
503                     }
504                 }
505             },
506             "oncomplete": function() {
507                 /* XXX in the future, this might be tweaked to display info
508                  * about more than one stop or warning event from the ML. */
509                 if (!(warnings.length || stops.length || other.length)) {
510                     d.innerHTML = localeStrings.PO_COULD_ACTIVATE;
511                     openils.Util.show(a, "inline");
512                 } else {
513                     if (other.length) {
514                         /* XXX make the textcode part a tooltip one day */
515                         d.innerHTML = localeStrings.NO + ": " +
516                             other[0].desc + " (" + other[0].textcode + ")";
517                         openils.Util.hide(a);
518                         
519                         if (other[0].textcode == 'ACQ_LINEITEM_NO_COPIES') {
520                             // when LIs w/ zero LIDs are present, list them
521                             fieldmapper.standardRequest(
522                                 [   'open-ils.acq', 
523                                     'open-ils.acq.purchase_order.no_copy_lineitems.id_list.authoritative.atomic' ],
524                                 {   async : true, 
525                                     params : [openils.User.authtoken, poId],
526                                     oncomplete : function(r) {
527                                         var ids = openils.Util.readResponse(r);
528                                         d.innerHTML += ' (' + ids + ')';
529                                     }
530                                 }
531                             );
532                         }
533                     } else if (stops.length) {
534                         d.innerHTML =
535                             dojo.string.substitute(
536                                 localeStrings.PO_STOP_BLOCKS_ACTIVATION, [
537                                     stops[0].payload.fund.code(),
538                                     stops[0].payload.fund.year()
539                                 ]
540                             );
541                         openils.Util.hide(a);
542                     } else {
543                         PO._warning_hack = true;
544                         d.innerHTML =
545                             dojo.string.substitute(
546                                 localeStrings.PO_WARNING_NO_BLOCK_ACTIVATION, [
547                                     warnings[0].payload.fund.code(),
548                                     warnings[0].payload.fund.year()
549                                 ]
550                             );
551                         openils.Util.show(a, "inline");
552                     }
553                 }
554             }
555         }
556     );
557 }
558
559 function activatePo() {
560     if (openils.Util.isTrue(PO.prepayment_required())) {
561         if (!confirm(localeStrings.PREPAYMENT_REQUIRED_REMINDER))
562             return false;
563     }
564
565     if (PO._warning_hack) {
566         if (!confirm(localeStrings.PO_FUND_WARNING_CONFIRM))
567             return false;
568     }
569
570     liTable.showAssetCreator(activatePoStage2);
571 }
572
573 function activatePoStage2() {
574
575     var want_refresh = false;
576     progressDialog.show(true);
577     fieldmapper.standardRequest(
578         ["open-ils.acq", "open-ils.acq.purchase_order.activate"], {
579             "async": true,
580             "params": [
581                 openils.User.authtoken,
582                 PO.id(),
583                 null,  // vandelay options
584                 {zero_copy_activate : dojo.byId('acq-po-activate-zero-copies').checked}
585             ],
586             "onresponse": function(r) {
587                 want_refresh = Boolean(openils.Util.readResponse(r));
588             },
589             "oncomplete": function() {
590                 progressDialog.hide();
591                 if (want_refresh)
592                     location.href = location.href;
593             }
594         }
595     );
596 }
597
598 function splitPo() {
599     progressDialog.show(true);
600     try {
601         var list;
602         fieldmapper.standardRequest(
603             ['open-ils.acq', 'open-ils.acq.purchase_order.split_by_lineitems'],
604             {   async: true,
605                 params: [openils.User.authtoken, PO.id()],
606                 onresponse : function(r) {
607                     list = openils.Util.readResponse(r);
608                 },
609                 oncomplete : function() {
610                     progressDialog.hide();
611                     if (list) {
612                         location.href = oilsBasePath + '/acq/po/search/' +
613                             list.join(",");
614                     }
615                 }
616             }
617         );
618     } catch(E) {
619         progressDialog.hide();
620         alert(E);
621     }
622 }
623
624 function updatePoName() {
625     var value = prompt('Enter new purchase order name:', PO.name()); // TODO i18n
626     if(!value || value == PO.name()) return;
627     PO.name(value);
628     pcrud.update(PO, {
629         oncomplete : function(r, cudResults) {
630             var stat = cudResults[0];
631             if(stat)
632                 dojo.byId('acq-po-view-name').innerHTML = value;
633         }
634     });
635 }
636
637 openils.Util.addOnLoad(init);