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