LP1211473 YAOUS : disable patron credit payments
[working/Evergreen.git] / Open-ILS / xul / staff_client / server / patron / bill2.js
1 function my_init() {
2     try {
3         if (typeof JSAN == 'undefined') { throw( $("commonStrings").getString('common.jsan.missing') ); }
4         JSAN.errorLevel = "die"; // none, warn, or die
5         JSAN.addRepository('/xul/server/');
6
7         JSAN.use('util.error'); g.error = new util.error();
8         JSAN.use('util.network'); g.network = new util.network();
9         JSAN.use('util.date');
10         JSAN.use('util.money');
11         JSAN.use('util.widgets');
12         JSAN.use('patron.util');
13         JSAN.use('OpenILS.data'); g.data = new OpenILS.data(); g.data.init({'via':'stash'});
14         g.data.voided_billings = []; g.data.stash('voided_billings');
15
16         g.error.sdump('D_TRACE','my_init() for bill2.xul');
17         window.bill_event_listeners = new EventListenerList();
18
19         document.title = $("patronStrings").getString('staff.patron.bill_history.my_init.current_bills');
20
21         g.funcs = []; g.bill_map = {}; g.row_map = {}; g.check_map = {};
22
23         g.patron_id = xul_param('patron_id');
24
25         $('circulating_hint').hidden = true;
26
27         init_lists();
28
29         retrieve_mbts_for_list();
30
31         event_listeners();
32
33         JSAN.use('util.exec'); var exec = new util.exec(20); 
34         exec.on_error = function(E) { alert(E); return true; }
35         exec.timer(g.funcs,100);
36
37         $('credit_forward').setAttribute('value','???');
38         if (!g.patron) {
39             refresh_patron();
40         } else {
41             $('credit_forward').setAttribute('value',util.money.sanitize( g.patron.credit_forward_balance() ));
42         }
43
44         if (g.data.hash.aous['circ.disable_patron_credit']) {
45             var nodes = document.getElementsByClassName('hide_patron_credit');
46             for (var i = 0; i < nodes.length; i++) 
47                 nodes[i].setAttribute('hidden', true);
48         }
49
50         if (g.data.hash.aous['ui.circ.billing.uncheck_bills_and_unfocus_payment_box']) {
51             g.funcs.push(
52                 function() {
53                     $('uncheck_all').focus();
54                     tally_all();
55                 }
56             );
57         } else {
58             g.funcs.push(
59                 function() {
60                     default_focus();
61                     tally_all();
62                 }
63             );
64         }
65
66     } catch(E) {
67         var err_msg = $("commonStrings").getFormattedString('common.exception', ['patron/bill2.xul', E]);
68         try { g.error.sdump('D_ERROR',err_msg); } catch(E) { dump(err_msg); }
69         alert(err_msg);
70     }
71 }
72
73 function my_cleanup() {
74     try {
75         window.bill_event_listeners.removeAll();
76         g.bill_list.cleanup();
77         g.bill_list.clear();
78     } catch(E) {
79         var err_msg = $("commonStrings").getFormattedString('common.exception', ['patron/bill2.xul', E]);
80         try { g.error.sdump('D_ERROR',err_msg); } catch(E) { dump(err_msg); }
81         alert(err_msg);
82     }
83 }
84
85 function event_listeners() {
86     try {
87         window.bill_event_listeners.add($('details'), 
88             'command',
89             handle_details,
90             false
91         );
92
93         window.bill_event_listeners.add($('add'), 
94             'command',
95             handle_add,
96             false
97         );
98
99         window.bill_event_listeners.add($('voidall'), 
100             'command',
101             handle_void_all,
102             false
103         );
104
105         window.bill_event_listeners.add($('refund'), 
106             'command',
107             handle_refund,
108             false
109         );
110
111         window.bill_event_listeners.add($('opac'), 
112             'command',
113             handle_opac,
114             false
115         );
116
117         window.bill_event_listeners.add($('copy_details'), 
118             'command',
119             handle_copy_details,
120             false
121         );
122
123         window.bill_event_listeners.add($('payment'), 
124             'change',
125             function(ev) {
126                 if ($('payment_type').value == 'credit_payment') {
127                     JSAN.use('util.money');
128                     JSAN.use('patron.util'); g.patron = patron.util.retrieve_fleshed_au_via_id(ses(),g.patron_id,null);
129                     var proposed = util.money.dollars_float_to_cents_integer(ev.target.value);
130                     var available = util.money.dollars_float_to_cents_integer(g.patron.credit_forward_balance());
131                     if (proposed > available) {
132                         alert($("patronStrings").getFormattedString('staff.patron.bills.bill_payment_amount.credit_amount', [g.patron.credit_forward_balance()]));
133                         ev.target.value = util.money.cents_as_dollars( available );
134                         ev.target.setAttribute('value',ev.target.value);
135                     }
136                 }
137                 distribute_payment(); 
138             },
139             false
140         );
141
142         window.bill_event_listeners.add($('payment'), 
143             'focus',
144             function(ev) { ev.target.select(); },
145             false
146         );
147
148         window.bill_event_listeners.add($('payment'), 
149             'keypress',
150             function(ev) {
151                 if (! (ev.keyCode == 13 /* enter */ || ev.keyCode == 77 /* mac enter */) ) { return; }
152                 distribute_payment();
153                 $('apply_payment_btn').focus();
154             },
155             false
156         );
157
158         window.bill_event_listeners.add($('bill_patron_btn'), 
159             'command',
160             function() {
161                 JSAN.use('util.window'); var win = new util.window();
162                 var my_xulG = win.open(
163                     urls.XUL_PATRON_BILL_WIZARD,
164                     'billwizard',
165                     'chrome,resizable,modal',
166                     { 'patron_id' : g.patron_id }
167                 );
168                 if (my_xulG.xact_id) {
169                     g.funcs.push( gen_list_append_func( my_xulG.xact_id ) );
170                     if (typeof window.xulG == 'object' && typeof window.xulG.on_money_change == 'function') window.xulG.on_money_change();
171                 }
172             },
173             false
174         );
175
176         window.bill_event_listeners.add($('bill_history_btn'), 
177             'command',
178             function() {
179                 xulG.display_window.g.patron.right_deck.reset_iframe( 
180                     urls.XUL_PATRON_BILL_HISTORY,
181                     {},
182                     {
183                         'patron_id' : g.patron_id,
184                         'refresh' : function() { refresh(); },
185                         'new_tab' : xulG.new_tab,
186                         'url_prefix' : xulG.url_prefix
187                     }
188                 );
189             },
190             false
191         );
192
193         window.bill_event_listeners.add($('convert_change_to_credit'), 
194             'command',
195             function(ev) {
196                 if (ev.target.checked) {
197                     addCSSClass( $('change_due'), 'change_to_credit' );
198                 } else {
199                     removeCSSClass( $('change_due'), 'change_to_credit' );
200                 }
201             },
202             false
203         );
204
205         window.bill_event_listeners.add($('apply_payment_btn'), 
206             'command',
207             function(ev) {
208                 try {
209                     $('apply_payment_btn').disabled = true;
210                     apply_payment();
211                     tally_all();
212                     $('apply_payment_btn').disabled = false;
213                 } catch(E) {
214                     alert('Error in bill2.js, apply_payment_btn: ' + E);
215                 }
216             },
217             false
218         );
219
220     } catch(E) {
221         alert('Error in bill2.js, event_listeners(): ' + E);
222     }
223 }
224
225 function $(id) { return document.getElementById(id); }
226
227 function default_focus() {
228     try { $('payment').focus(); } catch(E) { alert('Error in default_focus(): ' + E); }
229 }
230
231 function tally_pending() {
232     try {
233         var payments = [];
234         JSAN.use('util.money');
235         var tb = $('payment');
236         var payment_tendered = util.money.dollars_float_to_cents_integer( tb.value );
237         var payment_pending = 0;
238         var retrieve_ids = g.bill_list.dump_retrieve_ids();
239         for (var i = 0; i < retrieve_ids.length; i++) {
240             var row_params = g.row_map[retrieve_ids[i]];
241             if (g.check_map[retrieve_ids[i]]) { 
242                 var value = util.money.dollars_float_to_cents_integer( row_params.row.my.payment_pending );
243                 payment_pending += value;
244                 if (value != '0.00') { payments.push( [ retrieve_ids[i], util.money.cents_as_dollars(value) ] ); }
245             }
246         }
247         var change_pending = payment_tendered - payment_pending;
248         $('pending_payment').value = util.money.cents_as_dollars( payment_pending );
249         $('pending_change').value = util.money.cents_as_dollars( change_pending );
250         $('change_due').value = util.money.cents_as_dollars( change_pending );
251         return { 'payments' : payments, 'change' : util.money.cents_as_dollars( change_pending ) };
252     } catch(E) {
253         alert('Error in bill2.js, tally_pending(): ' + E);
254     }
255 }
256
257 function tally_selected() {
258     try {
259         JSAN.use('util.money');
260         var selected_billed = 0;
261         var selected_paid = 0;
262         var selected_balance = 0;
263
264         for (var i = 0; i < g.bill_list_selection.length; i++) {
265             var bill = g.bill_map[g.bill_list_selection[i]];
266             if (!bill) {
267                 //$('checked_owed').setAttribute('value', '???');
268                 //$('checked_billed').setAttribute('value', '???');
269                 //$('checked_paid').setAttribute('value', '???');
270                 return;
271             }
272             var to = util.money.dollars_float_to_cents_integer( bill.transaction.total_owed() );
273             var tp = util.money.dollars_float_to_cents_integer( bill.transaction.total_paid() );
274             var bo = util.money.dollars_float_to_cents_integer( bill.transaction.balance_owed() );
275             selected_billed += to;
276             selected_paid += tp;
277             selected_balance += bo;
278         }
279         //$('checked_billed').setAttribute('value', util.money.cents_as_dollars( selected_billed ) );
280         //$('checked_paid').setAttribute('value', util.money.cents_as_dollars( selected_paid ) );
281         //$('checked_owed').setAttribute('value', util.money.cents_as_dollars( selected_balance ) );
282     } catch(E) {
283         alert('Error in bill2.js, tally_selected(): ' + E);
284     }
285 }
286
287 function tally_voided() {
288     try {
289         JSAN.use('util.money');
290         var voided_total = 0;
291
292         g.data.stash_retrieve();
293
294         for (var i = 0; i < g.data.voided_billings.length; i++) {
295             var billing = g.data.voided_billings[i];
296             var bv = util.money.dollars_float_to_cents_integer( billing.amount() );
297             voided_total += bv;
298         }
299         $('currently_voided').setAttribute('value', util.money.cents_as_dollars( voided_total ) );
300     } catch(E) {
301         alert('Error in bill2.js, tally_voided(): ' + E);
302     }
303 }
304
305 function tally_all() {
306     try {
307         JSAN.use('util.money');
308         var checked_billed = 0;
309         var checked_paid = 0;
310         var checked_balance = 0;
311         var total_billed = 0;
312         var total_paid = 0;
313         var total_balance = 0;
314         var refunds_owed = 0;
315
316         var retrieve_ids = g.bill_list.dump_retrieve_ids();
317         for (var i = 0; i < retrieve_ids.length; i++) {
318             var bill = g.bill_map[retrieve_ids[i]];
319             if (!bill) {
320                 $('checked_owed').value = '???';
321                 $('checked_owed2').setAttribute('value', '???');
322                 $('checked_billed').value = '???';
323                 $('checked_paid').value = '???';
324                 $('tb_total_owed').value = '???';
325                 $('total_owed2').setAttribute('value', '???');
326                 $('total_billed').value = '???';
327                 $('tb_total_paid').value = '???';
328                 $('refunds_owed').setAttribute('value', '???');
329                 return;
330             }
331             var to = util.money.dollars_float_to_cents_integer( bill.transaction.total_owed() );
332             var tp = util.money.dollars_float_to_cents_integer( bill.transaction.total_paid() );
333             var bo = util.money.dollars_float_to_cents_integer( bill.transaction.balance_owed() );
334             total_billed += to;
335             total_paid += tp;
336             total_balance += bo;
337             if ( bo < 0 ) refunds_owed += bo;
338             if (g.check_map[retrieve_ids[i]]) {
339                 checked_billed += to;
340                 checked_paid += tp;
341                 checked_balance += bo;
342             }
343         }
344         $('checked_billed').value = util.money.cents_as_dollars( checked_billed );
345         $('checked_paid').value = util.money.cents_as_dollars( checked_paid );
346         $('checked_owed').value = util.money.cents_as_dollars( checked_balance );
347         $('checked_owed2').setAttribute('value', util.money.cents_as_dollars( checked_balance ) );
348         $('total_billed').value = util.money.cents_as_dollars( total_billed );
349         $('tb_total_paid').value = util.money.cents_as_dollars( total_paid );
350         $('tb_total_owed').value = util.money.cents_as_dollars( total_balance );
351         $('total_owed2').setAttribute('value', util.money.cents_as_dollars( total_balance ) );
352         $('refunds_owed').setAttribute('value', util.money.cents_as_dollars( Math.abs( refunds_owed ) ) );
353         // tally_selected();
354     } catch(E) {
355         alert('Error in bill2.js, tally_all(): ' + E);
356     }
357 }
358
359 function handle_refund() {
360     if(g.bill_list_selection.length > 1) {
361         var msg = $("patronStrings").getFormattedString('staff.patron.bills.handle_refund.message_plural', [g.bill_list_selection]);
362     } else {
363         var msg = $("patronStrings").getFormattedString('staff.patron.bills.handle_refund.message_singular', [g.bill_list_selection]);
364     }
365         
366     var r = g.error.yns_alert(msg,
367         $("patronStrings").getString('staff.patron.bills.handle_refund.title'),
368         $("patronStrings").getString('staff.patron.bills.handle_refund.btn_yes'),
369         $("patronStrings").getString('staff.patron.bills.handle_refund.btn_no'),null,
370         $("patronStrings").getString('staff.patron.bills.handle_refund.confirm_message'));
371     if (r == 0) {
372         for (var i = 0; i < g.bill_list_selection.length; i++) {
373             var bill_id = g.bill_list_selection[i];
374             //alert('g.check_map['+bill_id+'] = '+g.check_map[bill_id]+' bill_map['+bill_id+'] = ' + js2JSON(g.bill_map[bill_id]));
375             g.check_map[bill_id] = true;
376             var row_params = g.row_map[bill_id];
377             row_params.row.my.checked = true;
378             g.bill_list.refresh_row(row_params);
379         }
380     }
381     tally_all();
382     distribute_payment();
383 }
384
385
386 function check_all() {
387     try {
388         for (var i in g.bill_map) {
389             g.check_map[i] = true;
390             var row_params = g.row_map[i];
391             row_params.row.my.checked = true;
392             g.bill_list.refresh_row(row_params);
393         }
394         tally_all();
395         distribute_payment();
396     } catch(E) {
397         alert('Error in bill2.js, check_all(): ' + E);
398     }
399
400 }
401
402 function uncheck_all() {
403     try {
404         for (var i in g.bill_map) {
405             g.check_map[i] = false;
406             var row_params = g.row_map[i];
407             row_params.row.my.checked = false;
408             g.bill_list.refresh_row(row_params);
409         }
410         tally_all();
411         distribute_payment();
412     } catch(E) {
413         alert('Error in bill2.js, check_all(): ' + E);
414     }
415
416 }
417
418 function check_all_refunds() {
419     try {
420         for (var i in g.bill_map) {
421             if ( Number( g.bill_map[i].transaction.balance_owed() ) < 0 ) {
422                 g.check_map[i] = true;
423                 var row_params = g.row_map[i];
424                 row_params.row.my.checked = true;
425                 g.bill_list.refresh_row(row_params);
426             }
427         }
428         tally_all();
429         distribute_payment();
430     } catch(E) {
431         alert('Error in bill2.js, check_all_refunds(): ' + E);
432     }
433 }
434
435 function gen_list_append_func(r) {
436     return function() {
437         var default_check_state = g.data.hash.aous[
438             'ui.circ.billing.uncheck_bills_and_unfocus_payment_box'
439         ] ? false : true;
440         if (typeof r == 'object') {
441             g.row_map[ r.id() ] = g.bill_list.append( {
442                 'retrieve_id' : r.id(),
443                 'flesh_immediately' : true,
444                 'row' : {
445                     'my' : {
446                         'checked' : default_check_state,
447                         'mbts' : r
448                     }
449                 }
450             } );
451         } else {
452             g.row_map[r] = g.bill_list.append( {
453                 'retrieve_id' : r,
454                 'flesh_immediately' : true,
455                 'row' : {
456                     'my' : {
457                         'checked' : default_check_state
458                     }
459                 }
460             } );
461         }
462     }
463 }
464
465 function retrieve_mbts_for_list() {
466     var method = 'FM_MBTS_IDS_RETRIEVE_ALL_HAVING_BALANCE.authoritative';
467     g.mbts_ids = g.network.simple_request(method,[ses(),g.patron_id]);
468     if (g.mbts_ids.ilsevent) {
469         switch(Number(g.mbts_ids.ilsevent)) {
470             case -1: g.error.standard_network_error_alert($("patronStrings").getString('staff.patron.bill_history.retrieve_mbts_for_list.close_win_try_again')); break;
471             default: g.error.standard_unexpected_error_alert($("patronStrings").getString('staff.patron.bill_history.retrieve_mbts_for_list.close_win_try_again'),g.mbts_ids); break;
472         }
473     } else if (g.mbts_ids == null) {
474         g.error.standard_unexpected_error_alert($("patronStrings").getString('staff.patron.bill_history.retrieve_mbts_for_list.close_win_try_again'),null);
475     } else {
476    
477         g.mbts_ids.reverse();
478  
479         for (var i = 0; i < g.mbts_ids.length; i++) {
480             dump('i = ' + i + ' g.mbts_ids[i] = ' + g.mbts_ids[i] + '\n');
481             g.funcs.push( gen_list_append_func(g.mbts_ids[i]) );
482         }
483     }
484 }
485
486 function init_lists() {
487     JSAN.use('util.list'); JSAN.use('circ.util'); 
488
489     g.bill_list_selection = [];
490
491     g.bill_list = new util.list('bill_tree');
492
493     g.bill_list.init( {
494         'columns' : 
495             [
496                 {
497                     'id' : 'select', 'primary' : true, 'type' : 'checkbox', 'editable' : true, 'label' : '', 'style' : 'min-width: 3em;',
498                     'render' : function(my) { return String( my.checked ) == 'true'; }, 
499                 }
500             ].concat(
501                 patron.util.mbts_columns({
502                     'mbts_xact_finish' : { 'hidden' : true }
503                 }
504             ).concat( 
505                 circ.util.columns({ 
506                     'title' : { 'hidden' : false, 'flex' : '3' }
507                 }
508             ).concat( 
509                 [
510                     {
511                         'id' : 'payment_pending', 'editable' : false, 'sort_type' : 'money', 
512                         'label' : $('patronStrings').getString('staff.patron.bill_interface.payment_pending.column_header'),
513                         'render' : function(my) { return my.payment_pending || '0.00'; }, 
514                     }
515                 ]
516             ))),
517         'on_select' : function(ev) {
518             JSAN.use('util.functional');
519             g.bill_list_selection = util.functional.map_list(
520                 g.bill_list.retrieve_selection(),
521                 function(o) { return o.getAttribute('retrieve_id'); }
522             );
523             //tally_selected();
524             $('details').setAttribute('disabled', g.bill_list_selection.length == 0);
525             $('add').setAttribute('disabled', g.bill_list_selection.length == 0);
526             $('voidall').setAttribute('disabled', g.bill_list_selection.length == 0);
527             $('refund').setAttribute('disabled', g.bill_list_selection.length == 0);
528             $('opac').setAttribute('disabled', g.bill_list_selection.length == 0);
529             $('copy_details').setAttribute('disabled', g.bill_list_selection.length == 0);
530         },
531         'on_click' : function(ev) {
532             var row = {}; var col = {}; var nobj = {};
533             g.bill_list.node.treeBoxObject.getCellAt(ev.clientX,ev.clientY,row,col,nobj);
534             if (row.value == -1) return;
535             var treeItem = g.bill_list.node.contentView.getItemAtIndex(row.value);
536             if (treeItem.nodeName != 'treeitem') return;
537             var treeRow = treeItem.firstChild;
538             var treeCell = treeRow.firstChild.nextSibling;
539             if (g.check_map[ treeItem.getAttribute('retrieve_id') ] != (treeCell.getAttribute('value') == 'true')) {
540                 g.check_map[ treeItem.getAttribute('retrieve_id') ] = treeCell.getAttribute('value') == 'true';
541                 g.row_map[ treeItem.getAttribute('retrieve_id') ].row.my.checked = treeCell.getAttribute('value') == 'true';
542                 tally_all();
543                 distribute_payment();
544             }
545         },
546         'on_sort' : function() {
547             tally_all();
548         },
549         'on_checkbox_toggle' : function(toggle) {
550             try {
551                 var retrieve_ids = g.bill_list.dump_retrieve_ids();
552                 for (var i = 0; i < retrieve_ids.length; i++) {
553                     g.check_map[ retrieve_ids[i] ] = (toggle=='on');
554                     g.row_map[ retrieve_ids[i] ].row.my.checked = (toggle=='on');
555                 }
556                 tally_all();
557             } catch(E) {
558                 alert('error in on_checkbox_toggle(): ' + E);
559             }
560         },
561         'retrieve_row' : function(params) {
562             try {
563                 var id = params.retrieve_id;
564                 var row = params.row;
565
566                 function handle_props(row) {
567                     try {
568                         if ( row && row.my && row.my.mbts && Number( row.my.mbts.balance_owed() ) < 0 ) {
569                             util.widgets.addProperty(params.treeitem_node.firstChild,'refundable');
570                             util.widgets.addProperty(params.treeitem_node.firstChild.childNodes[ g.payment_pending_column_idx ],'refundable');
571                         }
572                         if ( row && row.my && row.my.circ && ! row.my.circ.checkin_time() ) {
573                             var style_type = 'circulating';
574                             var stop_fines = row.my.circ.stop_fines() || '';
575
576                             // we have custom syling for these stop-fines reasons
577                             if (stop_fines.match(/LOST|LONGOVERDUE/)) 
578                                 style_type = stop_fines.toLowerCase();
579
580                             $(style_type + '_hint').hidden = false;
581
582                             // style every cell in the row
583                             for (var n in params.treeitem_node.firstChild.childNodes) {
584                                 try {
585                                     util.widgets.addProperty(
586                                         params.treeitem_node.firstChild.childNodes[n], 
587                                         style_type
588                                     );
589                                 } catch(E) {}
590                             }
591                         }
592                     } catch(E) {
593                         g.error.sdump('D_WARN','Error setting list properties in bill2.js: ' + E);
594                         alert('Error setting list properties in bill2.js: ' + E);
595                     }
596                 }
597
598                 if (id) {
599                     if (typeof row.my == 'undefined') row.my = {};
600                     if (typeof row.my.mbts == 'undefined' ) {
601                         g.network.simple_request('BLOB_MBTS_DETAILS_RETRIEVE',[ses(),id], function(req) {
602                             var blob = req.getResultObject();
603                             row.my.mbts = blob.transaction;
604                             row.my.circ = blob.circ;
605                             row.my.acp = blob.copy;
606                             row.my.mvr = blob.record;
607                             if (typeof params.on_retrieve == 'function') {
608                                 if ( row.my.mbts && Number( row.my.mbts.balance_owed() ) < 0 ) {
609                                     row.my.checked = false;
610                                 }
611                                 handle_props(row);
612                                 params.on_retrieve(row);
613                             };
614                             g.bill_map[ id ] = blob;
615                             g.check_map[ id ] = row.my.checked;
616                             tally_all();
617                         } );
618                     } else {
619                         if (typeof params.on_retrieve == 'function') { 
620                             handle_props(row);
621                             params.on_retrieve(row); 
622                         }
623                     }
624                 } else {
625                     if (typeof params.on_retrieve == 'function') { 
626                         params.on_retrieve(row); 
627                     }
628                 }
629
630                 return row;
631             } catch(E) {
632                 alert('Error in bill2.js, retrieve_row(): ' + E);
633             }
634         }
635     } );
636
637     g.title_column_idx = util.functional.map_list( g.bill_list.columns, function(o) { return o.id; } ).indexOf( 'title' );
638     g.payment_pending_column_idx = util.functional.map_list( g.bill_list.columns, function(o) { return o.id; } ).indexOf( 'payment_pending' );
639     $('bill_list_actions').appendChild( g.bill_list.render_list_actions() );
640     g.bill_list.set_list_actions();
641 }
642
643 function handle_add() {
644     if(g.bill_list_selection.length > 1) {
645         var msg = $("patronStrings").getFormattedString('staff.patron.bill_history.handle_add.message_plural', [g.bill_list_selection]);
646     } else {
647         var msg = $("patronStrings").getFormattedString('staff.patron.bill_history.handle_add.message_singular', [g.bill_list_selection]);
648     }
649         
650     var r = g.error.yns_alert(msg,
651         $("patronStrings").getString('staff.patron.bill_history.handle_add.title'),
652         $("patronStrings").getString('staff.patron.bill_history.handle_add.btn_yes'),
653         $("patronStrings").getString('staff.patron.bill_history.handle_add.btn_no'),null,
654         $("patronStrings").getString('staff.patron.bill_history.handle_add.confirm_message'));
655     if (r == 0) {
656         JSAN.use('util.window');
657         var win = new util.window();
658         for (var i = 0; i < g.bill_list_selection.length; i++) {
659             var w = win.open(
660                 urls.XUL_PATRON_BILL_WIZARD,
661                 'billwizard',
662                 'chrome,resizable,modal',
663                 { 'patron_id' : g.patron_id, 'xact_id' : g.bill_list_selection[i] }
664             );
665         }
666         refresh();
667         if (typeof window.xulG == 'object' && typeof window.xulG.refresh == 'function') window.xulG.refresh();
668     }
669 }
670
671 function handle_void_all() {
672     if(g.bill_list_selection.length > 1) {
673         var msg = $("patronStrings").getFormattedString('staff.patron.bill_history.handle_void.message_plural', [g.bill_list_selection]);
674     } else {
675         var msg = $("patronStrings").getFormattedString('staff.patron.bill_history.handle_void.message_singular', [g.bill_list_selection]);
676     }
677         
678     var r = g.error.yns_alert(msg,
679         $("patronStrings").getString('staff.patron.bill_history.handle_void.title'),
680         $("patronStrings").getString('staff.patron.bill_history.handle_void.btn_yes'),
681         $("patronStrings").getString('staff.patron.bill_history.handle_void.btn_no'),null,
682         $("patronStrings").getString('staff.patron.bill_history.handle_void.confirm_message'));
683     if (r == 0) {
684         for (var i = 0; i < g.bill_list_selection.length; i++) {
685             void_all_billings( g.bill_list_selection[i] );
686         }
687         refresh();
688         if (typeof window.xulG == 'object' && typeof window.xulG.refresh == 'function') window.xulG.refresh();
689         if (typeof window.xulG == 'object' && typeof window.xulG.on_money_change == 'function') window.xulG.on_money_change();
690     }
691 }
692
693 function handle_opac() {
694     try {
695         var ids = [];
696         for (var i = 0; i < g.bill_list_selection.length; i++) {
697             var my_mvr = g.bill_map[ g.bill_list_selection[i] ].record;
698             var my_acp = g.bill_map[ g.bill_list_selection[i] ].copy;
699             if (typeof my_mvr != 'undefined' && my_mvr != null) {
700                 ids.push( { 'barcode' : my_acp.barcode(), 'doc_id' : my_mvr.doc_id() } );
701             }
702         }
703         JSAN.use('cat.util');
704         cat.util.show_in_opac( ids );
705     } catch(E) {
706         alert('Error in bill2.js, handle_opac: ' + E);
707     }
708 }
709
710 function handle_copy_details() {
711     try {
712         var ids = [];
713         for (var i = 0; i < g.bill_list_selection.length; i++) {
714             var my_acp = g.bill_map[ g.bill_list_selection[i] ].copy;
715             if (typeof my_acp != 'undefined' && my_acp != null) {
716                 ids.push( my_acp.barcode() );
717             }
718         }
719         JSAN.use('circ.util');
720         circ.util.item_details_new( ids );
721     } catch(E) {
722         alert('Error in bill2.js, handle_opac: ' + E);
723     }
724 }
725
726 function handle_details() {
727     JSAN.use('util.window'); var win = new util.window();
728     for (var i = 0; i < g.bill_list_selection.length; i++) {
729         var my_xulG = win.open(
730             urls.XUL_PATRON_BILL_DETAILS,
731             'test_billdetails_' + g.bill_list_selection[i],
732             'chrome,resizable',
733             {
734                 'patron_id' : g.patron_id,
735                 'mbts_id' : g.bill_list_selection[i],
736                 'refresh' : function() {
737                     refresh(); 
738                     if (typeof window.xulG == 'object' && typeof window.xulG.refresh == 'function') window.xulG.refresh();
739                 }, 
740                 'new_tab' : xulG.new_tab,
741                 'url_prefix' : xulG.url_prefix
742             }
743         );
744     }
745 }
746
747 function print_bills() {
748     try {
749         var template = 'bills_current';
750         JSAN.use('patron.util');
751         g.patron = patron.util.retrieve_fleshed_au_via_id(ses(),g.patron_id,null); 
752         g.bill_list.print({ 
753               'patron' : g.patron
754             , 'printer_context' : 'receipt'
755             , 'template' : template
756             , 'data' : {
757                   grand_total_owed:   $('tb_total_owed').value
758                 , grand_total_billed: $('total_billed').value
759                 , grand_total_paid:   $('tb_total_paid').value
760             }
761          });
762     } catch(E) {
763         g.error.standard_unexpected_error_alert($("patronStrings").getString('staff.patron.bill_history.print_bills.print_error'), E);
764     }
765 }
766
767 function distribute_payment() {
768     try {
769         JSAN.use('util.money');
770         var tb = $('payment');
771         tb.value = util.money.cents_as_dollars( util.money.dollars_float_to_cents_integer( tb.value ) );
772         tb.setAttribute('value', tb.value );
773         var total = util.money.dollars_float_to_cents_integer( tb.value );
774         if (total < 0) { tb.value = '0.00'; tb.setAttribute('value','0.00'); total = 0; }
775         var retrieve_ids = g.bill_list.dump_retrieve_ids();
776         for (var i = 0; i < retrieve_ids.length; i++) {
777             var row_params = g.row_map[retrieve_ids[i]];
778             if (g.check_map[retrieve_ids[i]]) { 
779                 var bill = g.bill_map[retrieve_ids[i]].transaction;
780                 var bo = util.money.dollars_float_to_cents_integer( bill.balance_owed() );
781                 if ( bo > total ) {
782                     row_params.row.my.payment_pending = util.money.cents_as_dollars( total );
783                     total = 0;
784                 } else {
785                     row_params.row.my.payment_pending = util.money.cents_as_dollars( bo );
786                     total = total - bo;
787                 }
788             } else {
789                 row_params.row.my.payment_pending = '0.00';
790             }
791             g.bill_list.refresh_row(row_params);
792         }
793         tally_pending();
794     } catch(E) {
795         alert('Error in bill2.js, distribute_payment(): ' + E);
796     }
797 }
798
799 function apply_payment() {
800     try {
801         var payment_blob = {};
802         JSAN.use('util.window');
803         var win = new util.window();
804         switch($('payment_type').value) {
805             case 'credit_card_payment' :
806                 g.data.temp = '';
807                 g.data.stash('temp');
808                 var my_xulG = win.open(
809                     urls.XUL_PATRON_BILL_CC_INFO,
810                     'billccinfo',
811                     'chrome,resizable,modal',
812                     {'patron_id': g.patron_id}
813                 );
814                 g.data.stash_retrieve();
815                 payment_blob = JSON2js( g.data.temp ); // FIXME - replace with my_xulG and update_modal_xulG, though it looks like we were using that before and moved away from it
816             break;
817             case 'check_payment' :
818                 g.data.temp = '';
819                 g.data.stash('temp');
820                 var my_xulG = win.open(
821                     urls.XUL_PATRON_BILL_CHECK_INFO,
822                     'billcheckinfo',
823                     'chrome,resizable,modal'
824                 );
825                 g.data.stash_retrieve();
826                 payment_blob = JSON2js( g.data.temp );
827             break;
828         }
829         if (
830             (typeof payment_blob == 'undefined') || 
831             payment_blob=='' || 
832             payment_blob.cancelled=='true'
833         ) { 
834             alert( $('commonStrings').getString('common.cancelled') ); 
835             return; 
836         }
837         payment_blob.userid = g.patron_id;
838         payment_blob.note = payment_blob.note || '';
839         //payment_blob.cash_drawer = 1; // FIXME: get new Config() to work
840         payment_blob.payment_type = $('payment_type').value;
841         var tally_blob = tally_pending();
842         payment_blob.payments = tally_blob.payments;
843         // Handle patron credit
844         if ( payment_blob.payment_type == 'credit_payment' ) { // paying with patron credit
845             if ( $('convert_change_to_credit').checked ) {
846                 // No need to convert credit into credit, handled automatically
847                 payment_blob.patron_credit = '0.00';
848             } else {
849                 // Cashing out extra credit as change
850                 payment_blob.patron_credit = 0 - tally_blob.change;
851             }
852         } else if ( $('convert_change_to_credit').checked ) {
853             // Saving change from a non-credit payment as patron credit on server
854             payment_blob.patron_credit = tally_blob.change;
855         } else {
856             payment_blob.patron_credit = '0.00';
857         }
858         if ( payment_blob.payments.length == 0 && payment_blob.patron_credit == '0.00' ) {
859             alert($("patronStrings").getString('staff.patron.bills.apply_payment.nothing_applied'));
860             return;
861         }
862         if ( pay( payment_blob ) ) {
863
864             $('payment').value = ''; $('payment').select(); $('payment').focus();
865             refresh({'clear_voided_summary':true});
866             if (typeof window.xulG == 'object' && typeof window.xulG.refresh == 'function') window.xulG.refresh();
867             if (typeof window.xulG == 'object' && typeof window.xulG.on_money_change == 'function') window.xulG.on_money_change();
868             if ( $('payment_type').value == 'credit_payment' || $('convert_change_to_credit').checked ) {
869                 refresh_patron();
870             }
871             try {
872                 if ( ! $('receipt_upon_payment').hasAttribute('checked') ) { return; } // Skip print attempt
873                 if ( ! $('receipt_upon_payment').getAttribute('checked') ) { return; } // Skip print attempt
874                 var no_print_prompting = g.data.hash.aous['circ.staff_client.do_not_auto_attempt_print'];
875                 if (no_print_prompting) {
876                     if (no_print_prompting.indexOf( "Bill Pay" ) > -1) { return; } // Skip print attempt
877                 }
878                 g.data.stash_retrieve();
879                 var template = 'bill_payment';
880                 JSAN.use('patron.util'); JSAN.use('util.functional');
881                 var params = { 
882                     'patron' : g.patron,
883                     'lib' : g.data.hash.aou[ ses('ws_ou') ],
884                     'staff' : ses('staff'),
885                     'header' : g.data.print_list_templates[template].header,
886                     'line_item' : g.data.print_list_templates[template].line_item,
887                     'footer' : g.data.print_list_templates[template].footer,
888                     'type' : g.data.print_list_templates[template].type,
889                     'list' : util.functional.map_list(
890                         payment_blob.payments,
891                         function(o) {
892                             return {
893                                 'bill_id' : o[0],
894                                 'payment' : o[1],
895                                 'last_billing_type' : g.bill_map[ o[0] ].transaction.last_billing_type(),
896                                 'last_billing_note' : g.bill_map[ o[0] ].transaction.last_billing_note(),
897                                 'title' : typeof g.bill_map[ o[0] ].record != 'undefined' ? g.bill_map[ o[0] ].record.title() : '', 
898                                 'barcode' : typeof g.bill_map[ o[0] ].copy != 'undefined' ? g.bill_map[ o[0] ].copy.barcode() : ''
899                             };
900                         }
901                     ),
902                     'data' : g.previous_summary,
903                     'context' : g.data.print_list_templates[template].context,
904                 };
905                 g.error.sdump('D_DEBUG',js2JSON(params));
906                 if ($('printer_prompt').hasAttribute('checked')) {
907                     if ($('printer_prompt').getAttribute('checked')) {
908                             params.no_prompt = false;
909                     } else {
910                             params.no_prompt = true;
911                     }
912                 } else {
913                     params.no_prompt = true;
914                 }
915                 JSAN.use('util.print'); var print = new util.print('receipt');
916                 for (var i = 0; i < $('num_of_receipts').value; i++) {
917                     print.tree_list( params );
918                 }
919             } catch(E) {
920                 g.error.standard_unexpected_error_alert('bill receipt', E);
921             }
922         }
923     } catch(E) {
924         alert('Error in bill2.js, apply_payment(): ' + E);
925     }
926 }
927
928 function pay(payment_blob) {
929     try {
930         var x = $('annotate_payment');
931         if (x && x.checked && (! payment_blob.note)) {
932             payment_blob.note = window.prompt(
933                 $("patronStrings").getString('staff.patron.bills.pay.annotate_payment'),
934                 '', 
935                 $("patronStrings").getString('staff.patron.bills.pay.annotate_payment.title')
936             );
937         }
938         g.previous_summary = {
939             original_balance : $('tb_total_owed').value,
940             voided_balance : $('currently_voided').value,
941             payment_received : $('payment').value,
942             payment_applied : $('pending_payment').value,
943             change_given : $('convert_change_to_credit').checked ? 0 : $('pending_change').value,
944             credit_given : $('convert_change_to_credit').checked ? $('pending_change').value : 0,
945             new_balance : util.money.cents_as_dollars( 
946                 util.money.dollars_float_to_cents_integer( $('tb_total_owed').value ) - 
947                 util.money.dollars_float_to_cents_integer( $('pending_payment').value )
948             ),
949             payment_type : $('payment_type').getAttribute('label'),
950             note : payment_blob.note
951         }
952         var robj = g.network.simple_request( 'BILL_PAY', [ ses(), payment_blob, g.patron.last_xact_id() ]);
953
954         try {
955             g.error.work_log(
956                 $('circStrings').getFormattedString(
957                     robj && robj.payments
958                         ? 'staff.circ.work_log_payment_attempt.success.message'
959                         : 'staff.circ.work_log_payment_attempt.failure.message',
960                     [
961                         ses('staff_usrname'), // 1 - Staff Username
962                         g.patron.family_name(), // 2 - Patron Family
963                         g.patron.card().barcode(), // 3 - Patron Barcode
964                         g.previous_summary.original_balance, // 4 - Original Balance
965                         g.previous_summary.voided_balance, // 5 - Voided Balance
966                         g.previous_summary.payment_received, // 6 - Payment Received
967                         g.previous_summary.payment_applied, // 7 - Payment Applied
968                         g.previous_summary.change_given, // 8 - Change Given
969                         g.previous_summary.credit_given, // 9 - Credit Given
970                         g.previous_summary.new_balance, // 10 - New Balance
971                         g.previous_summary.payment_type, // 11 - Payment Type
972                         g.previous_summary.note, // 12 - Note
973                         robj && robj.textcode ? robj.textcode : robj // 13 - API call result
974                     ]
975                 ), {
976                     'au_id' : g.patron.id(),
977                     'au_family_name' : g.patron.family_name(),
978                     'au_barcode' : g.patron.card().barcode()
979                 }
980             );
981         } catch(E) {
982             alert('Error logging payment in bill2.js: ' + E);
983         }
984
985         if (typeof robj.ilsevent != 'undefined') {
986             switch(robj.textcode) {
987                 case 'SUCCESS' : return true; break;
988                 case 'REFUND_EXCEEDS_DESK_PAYMENTS' : alert($("patronStrings").getFormattedString('staff.patron.bills.pay.refund_exceeds_desk_payment', [robj.desc])); return false; break;
989                 case 'INVALID_USER_XACT_ID' :
990                     refresh(); default_focus();
991                     alert($("patronStrings").getFormattedString('staff.patron.bills.pay.invalid_user_xact_id', [robj.desc])); return false; break;
992                 case 'PATRON_CREDIT_DISABLED' :
993                     refresh(); 
994                     default_focus();
995                     alert(robj.desc);
996                     return false;
997                     break;
998
999                 default: throw(robj); break;
1000             }
1001         }
1002         return true;
1003     } catch(E) {
1004         g.error.standard_unexpected_error_alert($("patronStrings").getString('staff.patron.bills.pay.payment_failed'),E);
1005         return false;
1006     }
1007 }
1008
1009 function refresh(params) {
1010     try {
1011         if (params && params.clear_voided_summary) {
1012             g.data.voided_billings = []; g.data.stash('voided_billings');
1013         }
1014         refresh_patron();
1015         g.bill_list.clear();
1016         retrieve_mbts_for_list();
1017         tally_voided();
1018         distribute_payment(); 
1019     } catch(E) {
1020         alert('Error in bill2.js, refresh(): ' + E);
1021     }
1022 }
1023
1024 function void_all_billings(mobts_id) {
1025     try {
1026         JSAN.use('util.functional');
1027         
1028         var mb_list = g.network.simple_request( 'FM_MB_RETRIEVE_VIA_MBTS_ID.authoritative', [ ses(), mobts_id ] );
1029         if (typeof mb_list.ilsevent != 'undefined') throw(mb_list);
1030
1031         mb_list = util.functional.filter_list( mb_list, function(o) { return ! get_bool( o.voided() ) });
1032
1033         if (mb_list.length == 0) { alert($("patronStrings").getString('staff.patron.bills.void_all_billings.all_voided')); return; }
1034
1035         var sum = 0;
1036         for (var i = 0; i < mb_list.length; i++) sum += util.money.dollars_float_to_cents_integer( mb_list[i].amount() );
1037         sum = util.money.cents_as_dollars( sum );
1038
1039         var msg = $("patronStrings").getFormattedString('staff.patron.bills.void_all_billings.void.message', [sum]);
1040         var r = g.error.yns_alert(msg,
1041             $("patronStrings").getString('staff.patron.bills.void_all_billings.void.title'),
1042             $("patronStrings").getString('staff.patron.bills.void_all_billings.void.yes'),
1043             $("patronStrings").getString('staff.patron.bills.void_all_billings.void.no'), null,
1044             $("patronStrings").getString('staff.patron.bills.void_all_billings.void.confirm_message'));
1045         if (r == 0) {
1046             var robj = g.network.simple_request('FM_MB_VOID',[ses()].concat(util.functional.map_list(mb_list,function(o){return o.id();})));
1047             if (robj.ilsevent) {
1048                 switch(Number(robj.ilsevent)) {
1049                     case 5000 /* PERM_FAILURE */:
1050                         return;
1051                     break;
1052                     default: 
1053                         g.error.standard_unexpected_error_alert($("patronStrings").getString('staff.patron.bills.void_all_billings.error_voiding_bills'),robj); 
1054                         return; 
1055                     break;
1056                 }
1057             }
1058
1059             g.data.stash_retrieve(); if (! g.data.voided_billings ) g.data.voided_billings = []; 
1060             for (var i = 0; i < mb_list.length; i++) {
1061                     g.data.voided_billings.push( mb_list[i] );
1062             }
1063             g.data.stash('voided_billings');
1064         }
1065     } catch(E) {
1066         try { g.error.standard_unexpected_error_alert('bill2.js, void_all_billings():',E); } catch(F) { alert(E); }
1067     }
1068 }
1069
1070 function refresh_patron() {
1071     JSAN.use('patron.util'); JSAN.use('util.money');
1072     patron.util.retrieve_fleshed_au_via_id(ses(),g.patron_id,null,function(req) {
1073         var au_obj = req.getResultObject();
1074         if (typeof au_obj.ilsevent == 'undefined') {
1075             g.patron = au_obj;
1076             $('credit_forward').setAttribute('value',util.money.sanitize( g.patron.credit_forward_balance() ));
1077         }
1078     });
1079 }