]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/xul/staff_client/server/patron/bill2.js
LP#1479107 adjust to zero UI
[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         $("patronStrings").getString('staff.patron.bills.handle_refund.confirm_message'));
377     if (r == 0) {
378         for (var i = 0; i < g.bill_list_selection.length; i++) {
379             var bill_id = g.bill_list_selection[i];
380             //alert('g.check_map['+bill_id+'] = '+g.check_map[bill_id]+' bill_map['+bill_id+'] = ' + js2JSON(g.bill_map[bill_id]));
381             g.check_map[bill_id] = true;
382             var row_params = g.row_map[bill_id];
383             row_params.row.my.checked = true;
384             g.bill_list.refresh_row(row_params);
385         }
386     }
387     tally_all();
388     distribute_payment();
389 }
390
391 /**
392  * Calls open-ils.circ.money.billable_xact.adjust_to_zero on selected
393  * transactions to produce a zero-balance transaction.
394  * Successfully cleared transactions will disappear from the billing list.
395  */
396 function handle_adjust_to_zero() {
397
398     var msgkey = g.bill_list_selection.length > 1 ?
399         'staff.patron.bills.handle_adjust_to_zero.message_plural' :
400         'staff.patron.bills.handle_adjust_to_zero.message_singular';
401
402     var msg = $("patronStrings").getFormattedString(
403         msgkey, [g.bill_list_selection]);
404
405     var r = g.error.yns_alert(msg,
406         $("patronStrings").getString(
407             'staff.patron.bills.handle_adjust_to_zero.title'),
408         $("patronStrings").getString(
409             'staff.patron.bills.handle_adjust_to_zero.btn_yes'),
410         $("patronStrings").getString(
411             'staff.patron.bills.handle_adjust_to_zero.btn_no'),null,
412         $("patronStrings").getString(
413             'staff.patron.bills.handle_adjust_to_zero.confirm_message'));
414
415     if (r == 0) {
416         var xact_ids = [];
417         for (var i = 0; i < g.bill_list_selection.length; i++) {
418             var bill_id = g.bill_list_selection[i];
419             xact_ids.push(bill_id);
420         }
421
422         var mod_ids = g.network.simple_request(
423             'ADJUST_BILLS_TO_ZERO', [ses(), xact_ids]);
424
425         g.error.sdump('D_DEBUG', 'adjusted to zero transactions ' + mod_ids);
426
427         refresh();
428         tally_all();
429         distribute_payment();
430     }
431 }
432
433
434 function check_all() {
435     try {
436         for (var i in g.bill_map) {
437             g.check_map[i] = true;
438             var row_params = g.row_map[i];
439             row_params.row.my.checked = true;
440             g.bill_list.refresh_row(row_params);
441         }
442         tally_all();
443         distribute_payment();
444     } catch(E) {
445         alert('Error in bill2.js, check_all(): ' + E);
446     }
447
448 }
449
450 function uncheck_all() {
451     try {
452         for (var i in g.bill_map) {
453             g.check_map[i] = false;
454             var row_params = g.row_map[i];
455             row_params.row.my.checked = false;
456             g.bill_list.refresh_row(row_params);
457         }
458         tally_all();
459         distribute_payment();
460     } catch(E) {
461         alert('Error in bill2.js, check_all(): ' + E);
462     }
463
464 }
465
466 function check_all_refunds() {
467     try {
468         for (var i in g.bill_map) {
469             if ( Number( g.bill_map[i].transaction.balance_owed() ) < 0 ) {
470                 g.check_map[i] = true;
471                 var row_params = g.row_map[i];
472                 row_params.row.my.checked = true;
473                 g.bill_list.refresh_row(row_params);
474             }
475         }
476         tally_all();
477         distribute_payment();
478     } catch(E) {
479         alert('Error in bill2.js, check_all_refunds(): ' + E);
480     }
481 }
482
483 function gen_list_append_func(r) {
484     return function() {
485         var default_check_state = g.data.hash.aous[
486             'ui.circ.billing.uncheck_bills_and_unfocus_payment_box'
487         ] ? false : true;
488         if (typeof r == 'object') {
489             g.row_map[ r.id() ] = g.bill_list.append( {
490                 'retrieve_id' : r.id(),
491                 'flesh_immediately' : true,
492                 'row' : {
493                     'my' : {
494                         'checked' : default_check_state,
495                         'mbts' : r
496                     }
497                 }
498             } );
499         } else {
500             g.row_map[r] = g.bill_list.append( {
501                 'retrieve_id' : r,
502                 'flesh_immediately' : true,
503                 'row' : {
504                     'my' : {
505                         'checked' : default_check_state
506                     }
507                 }
508             } );
509         }
510     }
511 }
512
513 function retrieve_mbts_for_list() {
514     var method = 'FM_MBTS_IDS_RETRIEVE_ALL_HAVING_BALANCE.authoritative';
515     g.mbts_ids = g.network.simple_request(method,[ses(),g.patron_id]);
516     if (g.mbts_ids.ilsevent) {
517         switch(Number(g.mbts_ids.ilsevent)) {
518             case -1: g.error.standard_network_error_alert($("patronStrings").getString('staff.patron.bill_history.retrieve_mbts_for_list.close_win_try_again')); break;
519             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;
520         }
521     } else if (g.mbts_ids == null) {
522         g.error.standard_unexpected_error_alert($("patronStrings").getString('staff.patron.bill_history.retrieve_mbts_for_list.close_win_try_again'),null);
523     } else {
524    
525         g.mbts_ids.reverse();
526  
527         for (var i = 0; i < g.mbts_ids.length; i++) {
528             dump('i = ' + i + ' g.mbts_ids[i] = ' + g.mbts_ids[i] + '\n');
529             g.funcs.push( gen_list_append_func(g.mbts_ids[i]) );
530         }
531     }
532 }
533
534 function init_lists() {
535     JSAN.use('util.list'); JSAN.use('circ.util'); 
536
537     g.bill_list_selection = [];
538
539     g.bill_list = new util.list('bill_tree');
540
541     g.bill_list.init( {
542         'columns' : 
543             [
544                 {
545                     'id' : 'select', 'primary' : true, 'type' : 'checkbox', 'editable' : true, 'label' : '', 'style' : 'min-width: 3em;',
546                     'render' : function(my) { return String( my.checked ) == 'true'; }, 
547                 }
548             ].concat(
549                 patron.util.mbts_columns({
550                     'mbts_xact_finish' : { 'hidden' : true }
551                 }
552             ).concat( 
553                 circ.util.columns({ 
554                     'title' : { 'hidden' : false, 'flex' : '3' }
555                 }
556             ).concat( 
557                 [
558                     {
559                         'id' : 'payment_pending', 'editable' : false, 'sort_type' : 'money', 
560                         'label' : $('patronStrings').getString('staff.patron.bill_interface.payment_pending.column_header'),
561                         'render' : function(my) { return my.payment_pending || '0.00'; }, 
562                     }
563                 ]
564             ))),
565         'on_select' : function(ev) {
566             JSAN.use('util.functional');
567             g.bill_list_selection = util.functional.map_list(
568                 g.bill_list.retrieve_selection(),
569                 function(o) { return o.getAttribute('retrieve_id'); }
570             );
571             //tally_selected();
572             $('details').setAttribute('disabled', g.bill_list_selection.length == 0);
573             $('add').setAttribute('disabled', g.bill_list_selection.length == 0);
574             $('voidall').setAttribute('disabled', g.bill_list_selection.length == 0);
575             $('adjust_to_zero').setAttribute('disabled', g.bill_list_selection.length == 0);
576             $('refund').setAttribute('disabled', g.bill_list_selection.length == 0);
577             $('opac').setAttribute('disabled', g.bill_list_selection.length == 0);
578             $('copy_details').setAttribute('disabled', g.bill_list_selection.length == 0);
579         },
580         'on_click' : function(ev) {
581             var row = {}; var col = {}; var nobj = {};
582             g.bill_list.node.treeBoxObject.getCellAt(ev.clientX,ev.clientY,row,col,nobj);
583             if (row.value == -1) return;
584             var treeItem = g.bill_list.node.contentView.getItemAtIndex(row.value);
585             if (treeItem.nodeName != 'treeitem') return;
586             var treeRow = treeItem.firstChild;
587             var treeCell = treeRow.firstChild.nextSibling;
588             if (g.check_map[ treeItem.getAttribute('retrieve_id') ] != (treeCell.getAttribute('value') == 'true')) {
589                 g.check_map[ treeItem.getAttribute('retrieve_id') ] = treeCell.getAttribute('value') == 'true';
590                 g.row_map[ treeItem.getAttribute('retrieve_id') ].row.my.checked = treeCell.getAttribute('value') == 'true';
591                 tally_all();
592                 distribute_payment();
593             }
594         },
595         'on_sort' : function() {
596             tally_all();
597         },
598         'on_checkbox_toggle' : function(toggle) {
599             try {
600                 var retrieve_ids = g.bill_list.dump_retrieve_ids();
601                 for (var i = 0; i < retrieve_ids.length; i++) {
602                     g.check_map[ retrieve_ids[i] ] = (toggle=='on');
603                     g.row_map[ retrieve_ids[i] ].row.my.checked = (toggle=='on');
604                 }
605                 tally_all();
606             } catch(E) {
607                 alert('error in on_checkbox_toggle(): ' + E);
608             }
609         },
610         'retrieve_row' : function(params) {
611             try {
612                 var id = params.retrieve_id;
613                 var row = params.row;
614
615                 function handle_props(row) {
616                     try {
617                         if ( row && row.my && row.my.mbts && Number( row.my.mbts.balance_owed() ) < 0 ) {
618                             util.widgets.addProperty(params.treeitem_node.firstChild,'refundable');
619                             util.widgets.addProperty(params.treeitem_node.firstChild.childNodes[ g.payment_pending_column_idx ],'refundable');
620                         }
621                         if ( row && row.my && row.my.circ && ! row.my.circ.checkin_time() ) {
622                             var style_type = 'circulating';
623                             var stop_fines = row.my.circ.stop_fines() || '';
624
625                             // we have custom syling for these stop-fines reasons
626                             if (stop_fines.match(/LOST|LONGOVERDUE/)) 
627                                 style_type = stop_fines.toLowerCase();
628
629                             $(style_type + '_hint').hidden = false;
630
631                             // style every cell in the row
632                             for (var n in params.treeitem_node.firstChild.childNodes) {
633                                 try {
634                                     util.widgets.addProperty(
635                                         params.treeitem_node.firstChild.childNodes[n], 
636                                         style_type
637                                     );
638                                 } catch(E) {}
639                             }
640                         }
641                     } catch(E) {
642                         g.error.sdump('D_WARN','Error setting list properties in bill2.js: ' + E);
643                         alert('Error setting list properties in bill2.js: ' + E);
644                     }
645                 }
646
647                 if (id) {
648                     if (typeof row.my == 'undefined') row.my = {};
649                     if (typeof row.my.mbts == 'undefined' ) {
650                         g.network.simple_request('BLOB_MBTS_DETAILS_RETRIEVE',[ses(),id], function(req) {
651                             var blob = req.getResultObject();
652                             row.my.mbts = blob.transaction;
653                             row.my.circ = blob.circ;
654                             row.my.acp = blob.copy;
655                             row.my.mvr = blob.record;
656                             if (typeof params.on_retrieve == 'function') {
657                                 if ( row.my.mbts && Number( row.my.mbts.balance_owed() ) < 0 ) {
658                                     row.my.checked = false;
659                                 }
660                                 handle_props(row);
661                                 params.on_retrieve(row);
662                             };
663                             g.bill_map[ id ] = blob;
664                             g.check_map[ id ] = row.my.checked;
665                             tally_all();
666                         } );
667                     } else {
668                         if (typeof params.on_retrieve == 'function') { 
669                             handle_props(row);
670                             params.on_retrieve(row); 
671                         }
672                     }
673                 } else {
674                     if (typeof params.on_retrieve == 'function') { 
675                         params.on_retrieve(row); 
676                     }
677                 }
678
679                 return row;
680             } catch(E) {
681                 alert('Error in bill2.js, retrieve_row(): ' + E);
682             }
683         }
684     } );
685
686     g.title_column_idx = util.functional.map_list( g.bill_list.columns, function(o) { return o.id; } ).indexOf( 'title' );
687     g.payment_pending_column_idx = util.functional.map_list( g.bill_list.columns, function(o) { return o.id; } ).indexOf( 'payment_pending' );
688     $('bill_list_actions').appendChild( g.bill_list.render_list_actions() );
689     g.bill_list.set_list_actions();
690 }
691
692 function handle_add() {
693     if(g.bill_list_selection.length > 1) {
694         var msg = $("patronStrings").getFormattedString('staff.patron.bill_history.handle_add.message_plural', [g.bill_list_selection]);
695     } else {
696         var msg = $("patronStrings").getFormattedString('staff.patron.bill_history.handle_add.message_singular', [g.bill_list_selection]);
697     }
698         
699     var r = g.error.yns_alert(msg,
700         $("patronStrings").getString('staff.patron.bill_history.handle_add.title'),
701         $("patronStrings").getString('staff.patron.bill_history.handle_add.btn_yes'),
702         $("patronStrings").getString('staff.patron.bill_history.handle_add.btn_no'),null,
703         $("patronStrings").getString('staff.patron.bill_history.handle_add.confirm_message'));
704     if (r == 0) {
705         JSAN.use('util.window');
706         var win = new util.window();
707         for (var i = 0; i < g.bill_list_selection.length; i++) {
708             var w = win.open(
709                 urls.XUL_PATRON_BILL_WIZARD,
710                 'billwizard',
711                 'chrome,resizable,modal',
712                 { 'patron_id' : g.patron_id, 'xact_id' : g.bill_list_selection[i] }
713             );
714         }
715         refresh();
716         if (typeof window.xulG == 'object' && typeof window.xulG.refresh == 'function') window.xulG.refresh();
717     }
718 }
719
720 function handle_void_all() {
721     if(g.bill_list_selection.length > 1) {
722         var msg = $("patronStrings").getFormattedString('staff.patron.bill_history.handle_void.message_plural', [g.bill_list_selection]);
723     } else {
724         var msg = $("patronStrings").getFormattedString('staff.patron.bill_history.handle_void.message_singular', [g.bill_list_selection]);
725     }
726         
727     var r = g.error.yns_alert(msg,
728         $("patronStrings").getString('staff.patron.bill_history.handle_void.title'),
729         $("patronStrings").getString('staff.patron.bill_history.handle_void.btn_yes'),
730         $("patronStrings").getString('staff.patron.bill_history.handle_void.btn_no'),null,
731         $("patronStrings").getString('staff.patron.bill_history.handle_void.confirm_message'));
732     if (r == 0) {
733         for (var i = 0; i < g.bill_list_selection.length; i++) {
734             void_all_billings( g.bill_list_selection[i] );
735         }
736         refresh();
737         if (typeof window.xulG == 'object' && typeof window.xulG.refresh == 'function') window.xulG.refresh();
738         if (typeof window.xulG == 'object' && typeof window.xulG.on_money_change == 'function') window.xulG.on_money_change();
739     }
740 }
741
742 function handle_opac() {
743     try {
744         var ids = [];
745         for (var i = 0; i < g.bill_list_selection.length; i++) {
746             var my_mvr = g.bill_map[ g.bill_list_selection[i] ].record;
747             var my_acp = g.bill_map[ g.bill_list_selection[i] ].copy;
748             if (typeof my_mvr != 'undefined' && my_mvr != null) {
749                 ids.push( { 'barcode' : my_acp.barcode(), 'doc_id' : my_mvr.doc_id() } );
750             }
751         }
752         JSAN.use('cat.util');
753         cat.util.show_in_opac( ids );
754     } catch(E) {
755         alert('Error in bill2.js, handle_opac: ' + E);
756     }
757 }
758
759 function handle_copy_details() {
760     try {
761         var ids = [];
762         for (var i = 0; i < g.bill_list_selection.length; i++) {
763             var my_acp = g.bill_map[ g.bill_list_selection[i] ].copy;
764             if (typeof my_acp != 'undefined' && my_acp != null) {
765                 ids.push( my_acp.barcode() );
766             }
767         }
768         JSAN.use('circ.util');
769         circ.util.item_details_new( ids );
770     } catch(E) {
771         alert('Error in bill2.js, handle_opac: ' + E);
772     }
773 }
774
775 function handle_details() {
776     JSAN.use('util.window'); var win = new util.window();
777     for (var i = 0; i < g.bill_list_selection.length; i++) {
778         var my_xulG = win.open(
779             urls.XUL_PATRON_BILL_DETAILS,
780             'test_billdetails_' + g.bill_list_selection[i],
781             'chrome,resizable',
782             {
783                 'patron_id' : g.patron_id,
784                 'mbts_id' : g.bill_list_selection[i],
785                 'refresh' : function() {
786                     refresh(); 
787                     if (typeof window.xulG == 'object' && typeof window.xulG.refresh == 'function') window.xulG.refresh();
788                 }, 
789                 'new_tab' : xulG.new_tab,
790                 'url_prefix' : xulG.url_prefix
791             }
792         );
793     }
794 }
795
796 function print_bills() {
797     try {
798         var template = 'bills_current';
799         JSAN.use('patron.util');
800         g.patron = patron.util.retrieve_fleshed_au_via_id(ses(),g.patron_id,null); 
801         g.bill_list.print({ 
802               'patron' : g.patron
803             , 'printer_context' : 'receipt'
804             , 'template' : template
805             , 'data' : {
806                   grand_total_owed:   $('tb_total_owed').value
807                 , grand_total_billed: $('total_billed').value
808                 , grand_total_paid:   $('tb_total_paid').value
809             }
810          });
811     } catch(E) {
812         g.error.standard_unexpected_error_alert($("patronStrings").getString('staff.patron.bill_history.print_bills.print_error'), E);
813     }
814 }
815
816 function distribute_payment() {
817     try {
818         JSAN.use('util.money');
819         var tb = $('payment');
820         tb.value = util.money.cents_as_dollars( util.money.dollars_float_to_cents_integer( tb.value ) );
821         tb.setAttribute('value', tb.value );
822         var total = util.money.dollars_float_to_cents_integer( tb.value );
823         if (total < 0) { tb.value = '0.00'; tb.setAttribute('value','0.00'); total = 0; }
824         var retrieve_ids = g.bill_list.dump_retrieve_ids();
825         for (var i = 0; i < retrieve_ids.length; i++) {
826             var row_params = g.row_map[retrieve_ids[i]];
827             if (g.check_map[retrieve_ids[i]]) { 
828                 var bill = g.bill_map[retrieve_ids[i]].transaction;
829                 var bo = util.money.dollars_float_to_cents_integer( bill.balance_owed() );
830                 if ( bo > total ) {
831                     row_params.row.my.payment_pending = util.money.cents_as_dollars( total );
832                     total = 0;
833                 } else {
834                     row_params.row.my.payment_pending = util.money.cents_as_dollars( bo );
835                     total = total - bo;
836                 }
837             } else {
838                 row_params.row.my.payment_pending = '0.00';
839             }
840             g.bill_list.refresh_row(row_params);
841         }
842         tally_pending();
843     } catch(E) {
844         alert('Error in bill2.js, distribute_payment(): ' + E);
845     }
846 }
847
848 function apply_payment() {
849     try {
850         var payment_blob = {};
851         JSAN.use('util.window');
852         var win = new util.window();
853         switch($('payment_type').value) {
854             case 'credit_card_payment' :
855                 g.data.temp = '';
856                 g.data.stash('temp');
857                 var my_xulG = win.open(
858                     urls.XUL_PATRON_BILL_CC_INFO,
859                     'billccinfo',
860                     'chrome,resizable,modal',
861                     {'patron_id': g.patron_id}
862                 );
863                 g.data.stash_retrieve();
864                 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
865             break;
866             case 'check_payment' :
867                 g.data.temp = '';
868                 g.data.stash('temp');
869                 var my_xulG = win.open(
870                     urls.XUL_PATRON_BILL_CHECK_INFO,
871                     'billcheckinfo',
872                     'chrome,resizable,modal'
873                 );
874                 g.data.stash_retrieve();
875                 payment_blob = JSON2js( g.data.temp );
876             break;
877         }
878         if (
879             (typeof payment_blob == 'undefined') || 
880             payment_blob=='' || 
881             payment_blob.cancelled=='true'
882         ) { 
883             alert( $('commonStrings').getString('common.cancelled') ); 
884             return; 
885         }
886         payment_blob.userid = g.patron_id;
887         payment_blob.note = payment_blob.note || '';
888         //payment_blob.cash_drawer = 1; // FIXME: get new Config() to work
889         payment_blob.payment_type = $('payment_type').value;
890         var tally_blob = tally_pending();
891         payment_blob.payments = tally_blob.payments;
892         // Handle patron credit
893         if ( payment_blob.payment_type == 'credit_payment' ) { // paying with patron credit
894             if ( $('convert_change_to_credit').checked ) {
895                 // No need to convert credit into credit, handled automatically
896                 payment_blob.patron_credit = '0.00';
897             } else {
898                 // Cashing out extra credit as change
899                 payment_blob.patron_credit = 0 - tally_blob.change;
900             }
901         } else if ( $('convert_change_to_credit').checked ) {
902             // Saving change from a non-credit payment as patron credit on server
903             payment_blob.patron_credit = tally_blob.change;
904         } else {
905             payment_blob.patron_credit = '0.00';
906         }
907         if ( payment_blob.payments.length == 0 && payment_blob.patron_credit == '0.00' ) {
908             alert($("patronStrings").getString('staff.patron.bills.apply_payment.nothing_applied'));
909             return;
910         }
911         if ( pay( payment_blob ) ) {
912
913             $('payment').value = ''; $('payment').select(); $('payment').focus();
914             refresh({'clear_voided_summary':true});
915             if (typeof window.xulG == 'object' && typeof window.xulG.refresh == 'function') window.xulG.refresh();
916             if (typeof window.xulG == 'object' && typeof window.xulG.on_money_change == 'function') window.xulG.on_money_change();
917             if ( $('payment_type').value == 'credit_payment' || $('convert_change_to_credit').checked ) {
918                 refresh_patron();
919             }
920             try {
921                 if ( ! $('receipt_upon_payment').hasAttribute('checked') ) { return; } // Skip print attempt
922                 if ( ! $('receipt_upon_payment').getAttribute('checked') ) { return; } // Skip print attempt
923                 var no_print_prompting = g.data.hash.aous['circ.staff_client.do_not_auto_attempt_print'];
924                 if (no_print_prompting) {
925                     if (no_print_prompting.indexOf( "Bill Pay" ) > -1) { return; } // Skip print attempt
926                 }
927                 g.data.stash_retrieve();
928                 var template = 'bill_payment';
929                 JSAN.use('patron.util'); JSAN.use('util.functional');
930                 var params = { 
931                     'patron' : g.patron,
932                     'lib' : g.data.hash.aou[ ses('ws_ou') ],
933                     'staff' : ses('staff'),
934                     'header' : g.data.print_list_templates[template].header,
935                     'line_item' : g.data.print_list_templates[template].line_item,
936                     'footer' : g.data.print_list_templates[template].footer,
937                     'type' : g.data.print_list_templates[template].type,
938                     'list' : util.functional.map_list(
939                         payment_blob.payments,
940                         function(o) {
941                             return {
942                                 'bill_id' : o[0],
943                                 'payment' : o[1],
944                                 'last_billing_type' : g.bill_map[ o[0] ].transaction.last_billing_type(),
945                                 'last_billing_note' : g.bill_map[ o[0] ].transaction.last_billing_note(),
946                                 'title' : typeof g.bill_map[ o[0] ].record != 'undefined' ? g.bill_map[ o[0] ].record.title() : '', 
947                                 'barcode' : typeof g.bill_map[ o[0] ].copy != 'undefined' ? g.bill_map[ o[0] ].copy.barcode() : ''
948                             };
949                         }
950                     ),
951                     'data' : g.previous_summary,
952                     'context' : g.data.print_list_templates[template].context,
953                 };
954                 g.error.sdump('D_DEBUG',js2JSON(params));
955                 if ($('printer_prompt').hasAttribute('checked')) {
956                     if ($('printer_prompt').getAttribute('checked')) {
957                             params.no_prompt = false;
958                     } else {
959                             params.no_prompt = true;
960                     }
961                 } else {
962                     params.no_prompt = true;
963                 }
964                 JSAN.use('util.print'); var print = new util.print('receipt');
965                 for (var i = 0; i < $('num_of_receipts').value; i++) {
966                     print.tree_list( params );
967                 }
968             } catch(E) {
969                 g.error.standard_unexpected_error_alert('bill receipt', E);
970             }
971         }
972     } catch(E) {
973         alert('Error in bill2.js, apply_payment(): ' + E);
974     }
975 }
976
977 function pay(payment_blob) {
978     try {
979         var x = $('annotate_payment');
980         if (x && x.checked && (! payment_blob.note)) {
981             payment_blob.note = window.prompt(
982                 $("patronStrings").getString('staff.patron.bills.pay.annotate_payment'),
983                 '', 
984                 $("patronStrings").getString('staff.patron.bills.pay.annotate_payment.title')
985             );
986         }
987         g.previous_summary = {
988             original_balance : $('tb_total_owed').value,
989             voided_balance : $('currently_voided').value,
990             payment_received : $('payment').value,
991             payment_applied : $('pending_payment').value,
992             change_given : $('convert_change_to_credit').checked ? 0 : $('pending_change').value,
993             credit_given : $('convert_change_to_credit').checked ? $('pending_change').value : 0,
994             new_balance : util.money.cents_as_dollars( 
995                 util.money.dollars_float_to_cents_integer( $('tb_total_owed').value ) - 
996                 util.money.dollars_float_to_cents_integer( $('pending_payment').value )
997             ),
998             payment_type : $('payment_type').getAttribute('label'),
999             note : payment_blob.note
1000         }
1001         var robj = g.network.simple_request( 'BILL_PAY', [ ses(), payment_blob, g.patron.last_xact_id() ]);
1002
1003         try {
1004             g.error.work_log(
1005                 $('circStrings').getFormattedString(
1006                     robj && robj.payments
1007                         ? 'staff.circ.work_log_payment_attempt.success.message'
1008                         : 'staff.circ.work_log_payment_attempt.failure.message',
1009                     [
1010                         ses('staff_usrname'), // 1 - Staff Username
1011                         g.patron.family_name(), // 2 - Patron Family
1012                         g.patron.card().barcode(), // 3 - Patron Barcode
1013                         g.previous_summary.original_balance, // 4 - Original Balance
1014                         g.previous_summary.voided_balance, // 5 - Voided Balance
1015                         g.previous_summary.payment_received, // 6 - Payment Received
1016                         g.previous_summary.payment_applied, // 7 - Payment Applied
1017                         g.previous_summary.change_given, // 8 - Change Given
1018                         g.previous_summary.credit_given, // 9 - Credit Given
1019                         g.previous_summary.new_balance, // 10 - New Balance
1020                         g.previous_summary.payment_type, // 11 - Payment Type
1021                         g.previous_summary.note, // 12 - Note
1022                         robj && robj.textcode ? robj.textcode : robj // 13 - API call result
1023                     ]
1024                 ), {
1025                     'au_id' : g.patron.id(),
1026                     'au_family_name' : g.patron.family_name(),
1027                     'au_barcode' : g.patron.card().barcode()
1028                 }
1029             );
1030         } catch(E) {
1031             alert('Error logging payment in bill2.js: ' + E);
1032         }
1033
1034         if (typeof robj.ilsevent != 'undefined') {
1035             switch(robj.textcode) {
1036                 case 'SUCCESS' : return true; break;
1037                 case 'REFUND_EXCEEDS_DESK_PAYMENTS' : alert($("patronStrings").getFormattedString('staff.patron.bills.pay.refund_exceeds_desk_payment', [robj.desc])); return false; break;
1038                 case 'INVALID_USER_XACT_ID' :
1039                     refresh(); default_focus();
1040                     alert($("patronStrings").getFormattedString('staff.patron.bills.pay.invalid_user_xact_id', [robj.desc])); return false; break;
1041                 case 'PATRON_CREDIT_DISABLED' :
1042                     refresh(); 
1043                     default_focus();
1044                     alert(robj.desc);
1045                     return false;
1046                     break;
1047
1048                 default: throw(robj); break;
1049             }
1050         }
1051         return true;
1052     } catch(E) {
1053         g.error.standard_unexpected_error_alert($("patronStrings").getString('staff.patron.bills.pay.payment_failed'),E);
1054         return false;
1055     }
1056 }
1057
1058 function refresh(params) {
1059     try {
1060         if (params && params.clear_voided_summary) {
1061             g.data.voided_billings = []; g.data.stash('voided_billings');
1062         }
1063         refresh_patron();
1064         g.bill_list.clear();
1065         retrieve_mbts_for_list();
1066         tally_voided();
1067         distribute_payment(); 
1068     } catch(E) {
1069         alert('Error in bill2.js, refresh(): ' + E);
1070     }
1071 }
1072
1073 function void_all_billings(mobts_id) {
1074     try {
1075         JSAN.use('util.functional');
1076         
1077         var mb_list = g.network.simple_request( 'FM_MB_RETRIEVE_VIA_MBTS_ID.authoritative', [ ses(), mobts_id ] );
1078         if (typeof mb_list.ilsevent != 'undefined') throw(mb_list);
1079
1080         mb_list = util.functional.filter_list( mb_list, function(o) { return ! get_bool( o.voided() ) });
1081
1082         if (mb_list.length == 0) { alert($("patronStrings").getString('staff.patron.bills.void_all_billings.all_voided')); return; }
1083
1084         var sum = 0;
1085         for (var i = 0; i < mb_list.length; i++) sum += util.money.dollars_float_to_cents_integer( mb_list[i].amount() );
1086         sum = util.money.cents_as_dollars( sum );
1087
1088         var msg = $("patronStrings").getFormattedString('staff.patron.bills.void_all_billings.void.message', [sum]);
1089         var r = g.error.yns_alert(msg,
1090             $("patronStrings").getString('staff.patron.bills.void_all_billings.void.title'),
1091             $("patronStrings").getString('staff.patron.bills.void_all_billings.void.yes'),
1092             $("patronStrings").getString('staff.patron.bills.void_all_billings.void.no'), null,
1093             $("patronStrings").getString('staff.patron.bills.void_all_billings.void.confirm_message'));
1094         if (r == 0) {
1095             var robj = g.network.simple_request('FM_MB_VOID',[ses()].concat(util.functional.map_list(mb_list,function(o){return o.id();})));
1096             if (robj.ilsevent) {
1097                 switch(Number(robj.ilsevent)) {
1098                     case 5000 /* PERM_FAILURE */:
1099                         return;
1100                     break;
1101                     default: 
1102                         g.error.standard_unexpected_error_alert($("patronStrings").getString('staff.patron.bills.void_all_billings.error_voiding_bills'),robj); 
1103                         return; 
1104                     break;
1105                 }
1106             }
1107
1108             g.data.stash_retrieve(); if (! g.data.voided_billings ) g.data.voided_billings = []; 
1109             for (var i = 0; i < mb_list.length; i++) {
1110                     g.data.voided_billings.push( mb_list[i] );
1111             }
1112             g.data.stash('voided_billings');
1113         }
1114     } catch(E) {
1115         try { g.error.standard_unexpected_error_alert('bill2.js, void_all_billings():',E); } catch(F) { alert(E); }
1116     }
1117 }
1118
1119 function refresh_patron() {
1120     JSAN.use('patron.util'); JSAN.use('util.money');
1121     patron.util.retrieve_fleshed_au_via_id(ses(),g.patron_id,null,function(req) {
1122         var au_obj = req.getResultObject();
1123         if (typeof au_obj.ilsevent == 'undefined') {
1124             g.patron = au_obj;
1125             $('credit_forward').setAttribute('value',util.money.sanitize( g.patron.credit_forward_balance() ));
1126         }
1127     });
1128 }