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