6 <& elements/ApplicationCommon.html,
7 'form_action' => 'process/cust_bill_pay.cgi,
8 'src_table' => 'cust_pay',
9 'src_thing' => 'payment',
10 'dst_table' => 'cust_bill',
11 'dst_thing' => 'invoice',
15 <& elements/ApplicationCommon.html',
16 'form_action' => 'process/cust_credit_bill.cgi,
17 'src_table' => 'cust_credit',
18 'src_thing' => 'credit',
19 'dst_table' => 'cust_bill',
20 'dst_thing' => 'invoice',
24 <& elements/ApplicationCommon.html',
25 'form_action' => 'process/cust_pay_refund.cgi,
26 'src_table' => 'cust_pay',
27 'src_thing' => 'payment',
28 'dst_table' => 'cust_refund',
29 'dst_thing' => 'refund',
33 <& elements/ApplicationCommon.html,
34 'form_action' => 'process/cust_credit_refund.cgi',
35 'src_table' => 'cust_credit',
36 'src_thing' => 'credit',
37 'dst_table' => 'cust_refund',
38 'dst_thing' => 'refund',
42 <& /elements/header-popup.html, "Apply $src_thing$to", '', 'onLoad="myOnLoadFunction();"' &>
44 <& /elements/error.html &>
46 <P ID="ErrorMessage"></P>
47 <FORM ACTION="<% $p1. $opt{'form_action'} %>" NAME="ApplicationForm" ID="ApplicationForm" METHOD=POST>
49 <% $src_thing %> #<B><% $src_pkeyvalue %></B><BR>
50 <INPUT TYPE="hidden" NAME="<% $src_pkey %>" VALUE="<% $src_pkeyvalue %>">
52 <TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
55 <TD ALIGN="right">Date: </TD>
56 <TD><B><% time2str($date_format, $src->_date) %></B></TD>
60 <TD ALIGN="right">Amount: </TD>
61 <TD ID="original_amount"><B><% $money_char %><% $src_amount %></B>
64 % if ($use_sub_dst_thing && $can_change_credit) {
65 <INPUT TYPE="hidden" NAME="src_amount" VALUE="<% $src_amount %>" >
66 <BUTTON TYPE="button" NAME="expand_button" ID="expand_button" onClick="do_change_amount(this);">Change</BUTTON>
73 <TD ALIGN="right">Unapplied amount: </TD>
74 <TD ID="unapplied_amount"><B><% $money_char %><% $unapplied %></B></TD>
77 % if ( $src_table eq 'cust_credit' ) {
79 <TD ALIGN="right">Reason: </TD>
80 <TD COLSPAN=2><B><% $src->reason %></B></TD>
87 <SCRIPT TYPE="text/javascript">
88 function clear_amounts() {
90 var table = document.getElementById('ApplicationTable');
91 for (var row = 2; table.rows[row]; row++)
93 var inputs = table.rows[row].getElementsByTagName('input');
94 if ( !inputs.length ) {
97 inputs.item(0).value = ''; // amount
102 function changed(what) {
103 dst = what.options[what.selectedIndex].value;
106 what.form.submit.disabled=true;
107 %if ($use_sub_dst_thing && $src_pkey eq 'crednum') {
108 what.form.tax_button.disabled=true;
109 what.form.clear_button.disabled=true;
114 what.form.submit.disabled=false;
115 %if ($use_sub_dst_thing && $src_pkey eq 'crednum') {
116 what.form.tax_button.disabled=false;
117 what.form.clear_button.disabled=false;
120 % foreach my $dst ( @dst ) {
122 if ( dst == <% $dst->$dst_pkey %> ) {
123 what.form.amount.value = "<% min($dst->$dst_unapplied, $unapplied) %>";
124 % if ($use_sub_dst_thing) {
125 what.form.display_amount.value = "<% min($dst->$dst_unapplied, $unapplied) %>";
128 var table = document.getElementById('ApplicationTable');
129 while(table.rows[2]) {
132 % my $app_class = "FS::$link_table";
133 % my $temp_app = $app_class->new(
134 % { $src_pkey => $src_pkeyvalue,
135 % $dst_pkey => $dst->$dst_pkey,
136 % 'amount' => min($dst->$dst_unapplied, $unapplied),
140 % my $listref_or_error = $temp_app->calculate_applications;
141 % %apphash = map { &{$key_generator}($_), $_ } @$listref_or_error
142 % if ref($listref_or_error);
143 % foreach my $cbp ( $dst->open_cust_bill_pkg ) {
144 % my $desc = $cbp->desc;
145 % my $total_owed = $cbp->owed_setup + $cbp->owed_recur;
146 % my $key = &{$key_generator}([ $cbp, 0, {} ]);
147 % my $amount = exists($apphash{ $key }) ? $apphash{ $key }->[1] : 0;
148 % unless ( $cbp->pkgnum ) {
149 % foreach my $taxX ( $cbp->cust_bill_pkg_tax_Xlocation ) {
150 % my $pkey = $taxX->primary_key;
151 % my $owed = $taxX->owed;
152 % my $key = &{$key_generator}([ $cbp, 0, { $pkey => $taxX->$pkey } ]);
153 % my $toapp = exists($apphash{ $key }) ? $apphash{ $key }->[1] : 0;
154 <% &{$row_generator}( $key, $cbp, $taxX->desc, $owed, $toapp, $taxX->$pkey ) %>
155 % $total_owed -= $owed;
158 % $desc .= ' (default)';
160 % $total_owed = sprintf('%.2f', $total_owed + 0.00000001 ); #so 1.005 rounds to 1.01
161 % if ( $total_owed > 0 ) {
162 <% &{$row_generator}($key, $cbp, $desc, $total_owed, $amount, '') %>
172 function sub_changed(what) {
175 var table = document.getElementById('ApplicationTable');
176 var i = table.rows.length;
178 var inputs = table.rows[i].getElementsByTagName('input');
179 if (! inputs.length) {
182 amount += parseFloat( inputs.item(0).value ) || 0;
184 what.form.amount.value = parseFloat(amount).toFixed(2);
185 what.form.display_amount.value = parseFloat(amount).toFixed(2);
186 set_amount_color(what);
190 function set_amount_color(what) {
191 if (what.form.src_amount.value < what.form.amount.value) {
192 what.form.display_amount.style.color = '#ff0000';
194 what.form.display_amount.style.color = '#00ff00';
202 % if ($use_sub_dst_thing && $src_pkey eq 'crednum') {
207 <BUTTON TYPE="button" NAME="tax_button" ID="tax_button" onClick="do_calculate_tax(this);" DISABLED>Calculate Tax</BUTTON>
210 <BUTTON TYPE="button" NAME="clear_button" ID="clear_button" onClick="clear_amounts(this);" DISABLED>Clear Amounts</BUTTON>
215 <& /elements/xmlhttp.html,
216 'url' => $p.'misc/xmlhttp-calculate_taxes.html',
217 'subs' => [ 'calculate_taxes' ],
219 <SCRIPT TYPE="text/javascript">
221 function show_taxes(arg) {
222 var argsHash = eval('(' + arg + ')');
224 var button = document.getElementById('tax_button');
225 button.disabled = false;
226 button.innerHTML = 'Calculate Tax';
227 button = document.getElementById('clear_button');
228 button.disabled = false;
230 var error = argsHash['error'];
232 var paragraph = document.getElementById('ErrorMessage');
234 paragraph.innerHTML = 'Error: ' + error;
235 paragraph.style.color = '#ff0000';
237 paragraph.innerHTML = '';
239 var taxlines = argsHash['taxlines'];
241 var table = document.getElementById('ApplicationTable');
244 for (i = 0; taxlines[i]; i++) {
245 var itemdesc = taxlines[i][0];
246 var locnum = taxlines[i][2];
247 if (taxlines[i][3]) {
248 locnum = taxlines[i][3];
252 for (var row = 2; table.rows[row]; row++) {
253 var inputs = table.rows[row].getElementsByTagName('input');
254 if (! inputs.length) {
255 while ( table.rows[row] ) {
256 table.deleteRow(row);
260 if ( inputs.item(4).value == itemdesc && inputs.item(2).value == locnum )
262 inputs.item(0).value = taxlines[i][1];
263 aFoundRow = found = row;
268 var row = table.insertRow(table.rows.length);
269 var warning_cell = document.createElement('TD');
270 warning_cell.style.color = '#ff0000';
271 warning_cell.colSpan = 2;
272 warning_cell.innerHTML = 'Calculated Tax - ' + itemdesc + ' - ' +
273 taxlines[i][1] + ' will not be applied';
274 row.appendChild(warning_cell);
279 sub_changed(table.rows[aFoundRow].getElementsByTagName('input').item(0));
284 function do_calculate_tax (what) {
285 what.innerHTML = 'Calculating....';
286 what.disabled = true;
287 var button = document.getElementById('clear_button');
288 button.disabled = true;
289 var taxed_items = new Array();
290 var table = document.getElementById('ApplicationTable');
291 for (var row = 2; table.rows[row]; row++)
293 var inputs = table.rows[row].getElementsByTagName('input');
294 if ( !inputs.length ) {
297 var taxed_item = new Array(
298 inputs.item(1).value, // billpkgnum
299 inputs.item(3).value, // s_or_r
300 inputs.item(0).value || 0 // amount
302 taxed_items.push(taxed_item);
305 var args = new Array(
306 'crednum', '<% $src_pkeyvalue %>',
309 calculate_taxes ( args, show_taxes );
312 function do_change_amount (what) {
313 var amount_cell = document.getElementById('original_amount');
314 var inputs = amount_cell.getElementsByTagName('input');
316 src_amount_changed();
317 amount_cell.innerHTML = '<B><% $money_char %></B>' + inputs.item(0).value;
319 amount_cell.innerHTML = '<% $money_char %>';
320 var amount_input = document.createElement('INPUT');
321 amount_input.setAttribute('name', 'entered_amount');
322 amount_input.setAttribute('id', 'entered_amount');
323 amount_input.style.textAlign = 'right';
324 amount_input.setAttribute('size', 8);
325 amount_input.setAttribute('maxlength', 8);
326 amount_input.setAttribute('value', what.form.src_amount.value);
327 amount_input.setAttribute('onChange', "src_amount_changed(this);");
328 amount_cell.appendChild(amount_input);
332 function src_amount_changed () {
333 //alert('src_amount_changed called');
334 var entered_amount = document.getElementById('entered_amount');
335 if ( entered_amount ) {
336 entered_amount.form.src_amount.value = entered_amount.value;
337 var unapplied_cell = document.getElementById('unapplied_amount');
338 unapplied_cell.innerHTML = '<B><% $money_char %>' + entered_amount.value + '</B>';
339 set_amount_color(entered_amount);
348 <TABLE ID="ApplicationTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
351 <TD ALIGN="right"><% $dst_thing %>: </TD>
352 <TD><SELECT NAME="<% $dst_pkey %>" SIZE=1 onChange="changed(this)">
353 <OPTION VALUE="">Select <% $dst_thing %>
355 % foreach my $dst ( @dst ) {
356 <OPTION<% $dst->$dst_pkey eq $dst_pkeyvalue ? ' SELECTED' : '' %> VALUE="<% $dst->$dst_pkey %>">#<% $dst->$dst_pkey %> - <% time2str($date_format, $dst->_date) %> - $<% $dst->$dst_unapplied %>
364 <TD ALIGN="right">Amount: </TD>
365 <TD><% $money_char %><INPUT TYPE="text" NAME="<% $use_sub_dst_thing ? 'display_' : '' %>amount" VALUE="<% $amount %>" SIZE=8 MAXLENGTH=8 <% $use_sub_dst_thing ? 'DISABLED' : '' %> STYLE="text-align:right;"></TD>
366 % if ($use_sub_dst_thing) {
367 <INPUT TYPE="hidden" NAME="amount" VALUE="<% $amount %>" >
374 <CENTER><INPUT TYPE="submit"
378 % if ($use_sub_dst_thing && $can_change_credit) {
379 onClick="src_amount_changed()"
386 <SCRIPT TYPE="text/javascript">
388 function myOnLoadFunction () {
394 <& /elements/footer-popup.html &>
399 my $conf = new FS::Conf;
400 my $money_char = $conf->config('money_char') || '$';
401 my $date_format = $conf->config('date_format') || '%m/%d/%Y';
403 my $src_thing = ucfirst($opt{'src_thing'});
404 my $src_table = $opt{'src_table'};
405 my $src_pkey = dbdef->table($src_table)->primary_key;
407 my $dst_thing = ucfirst($opt{'dst_thing'});
408 my $dst_table = $opt{'dst_table'};
409 my $dst_pkey = dbdef->table($dst_table)->primary_key;
410 my $dst_unapplied = $dst_table eq 'cust_bill' ? 'owed' : 'unapplied';
412 $opt{form_action} =~ /^process\/(.*)\./ or die "bad form action";
415 my $use_sub_dst_thing = 0;
416 $use_sub_dst_thing = 1
417 if ( $dst_table eq 'cust_bill' && $conf->exists("${link_table}_pkg-manual") );
419 my $can_change_credit = 0;
420 $can_change_credit = 1
421 if ( $src_table eq 'cust_credit' &&
422 $FS::CurrentUser::CurrentUser->access_right('Post credit') &&
423 $FS::CurrentUser::CurrentUser->access_right('Delete credit')
426 my $to = $dst_table eq 'cust_refund' ? ' to Refund' : '';
428 $m->comp('/elements/handle_uri_query');
430 my($src_pkeyvalue, $amount, $dst_pkeyvalue, $src_amount);
431 if ( $cgi->param('error') ) {
432 $src_pkeyvalue = $cgi->param($src_pkey);
433 $amount = $cgi->param('amount');
434 $dst_pkeyvalue = $cgi->param($dst_pkey);
435 $src_amount = $cgi->param('src_amount');
437 my($query) = $cgi->keywords;
446 my $src = qsearchs($src_table, { $src_pkey => $src_pkeyvalue } );
447 die "$src_thing $src_pkeyvalue not found!" unless $src;
449 $src_amount = $src->amount unless $cgi->param('error');
451 my $unapplied = $src->unapplied;
453 my @dst = sort { $a->_date <=> $b->_date
454 or $a->$dst_pkey <=> $b->$dst_pkey
456 grep { $_->$dst_unapplied != 0 }
457 qsearch($dst_table, { 'custnum' => $src->custnum } );
459 my $row_generator = sub {
460 my ($key, $cust_bill_pkg, $desc, $owed, $amount, $taxXnum) = @_;
461 my ($num, $s_or_r, $taxlinenum) = split(':', $key);
462 my $id = $cust_bill_pkg->pkgnum || 'Tax';
463 my $billpkgnum = $cust_bill_pkg->billpkgnum;
464 my $s_or_r = $cust_bill_pkg->setup > 0 ? 'setup' : 'recur';
466 $amount = sprintf("%.2f", $amount);
468 var tablebody = document.getElementsByTagName('tbody').item(0);
469 var row = table.insertRow(rownum+2);
470 var pkg_cell = document.createElement('TD');
471 pkg_cell.style.textAlign = 'right';
472 pkg_cell.innerHTML = "$id - $desc - $owed:";
473 var amount_cell = document.createElement('TD');
474 amount_cell.innerHTML = "$money_char";
475 var amount_input = document.createElement('INPUT');
476 amount_input.setAttribute('name', 'subamount'+rownum);
477 amount_input.setAttribute('id', 'subamount'+rownum);
478 amount_input.style.textAlign = 'right';
479 amount_input.setAttribute('size', 8);
480 amount_input.setAttribute('maxlength', 8);
481 amount_input.setAttribute('rownum', rownum);
482 amount_input.setAttribute('value', "$amount");
483 amount_input.setAttribute('onChange', "sub_changed(this);");
484 amount_cell.appendChild(amount_input);
485 var subnum_input = document.createElement('INPUT');
486 subnum_input.setAttribute('name', 'subnum'+rownum);
487 subnum_input.setAttribute('id', 'subnum'+rownum);
488 subnum_input.setAttribute('type', 'hidden');
489 subnum_input.setAttribute('rownum', rownum);
490 subnum_input.setAttribute('value', "$billpkgnum");
491 amount_cell.appendChild(subnum_input);
492 var taxnum_input = document.createElement('INPUT');
493 taxnum_input.setAttribute('name', 'taxXlocationnum'+rownum);
494 taxnum_input.setAttribute('id', 'taxXlocationnum'+rownum);
495 taxnum_input.setAttribute('type', 'hidden');
496 taxnum_input.setAttribute('rownum', rownum);
497 taxnum_input.setAttribute('value', "$taxXnum");
498 amount_cell.appendChild(taxnum_input);
499 var s_or_r_input = document.createElement('INPUT');
500 s_or_r_input.setAttribute('name', 's_or_r'+rownum);
501 s_or_r_input.setAttribute('id', 's_or_r'+rownum);
502 s_or_r_input.setAttribute('type', 'hidden');
503 s_or_r_input.setAttribute('rownum', rownum);
504 s_or_r_input.setAttribute('value', "$s_or_r");
505 amount_cell.appendChild(s_or_r_input);
506 var itemdesc_input = document.createElement('INPUT');
507 itemdesc_input.setAttribute('name', 'itemdesc'+rownum);
508 itemdesc_input.setAttribute('id', 'itemdesc'+rownum);
509 itemdesc_input.setAttribute('type', 'hidden');
510 itemdesc_input.setAttribute('rownum', rownum);
511 itemdesc_input.setAttribute('value', "$desc");
512 amount_cell.appendChild(itemdesc_input);
513 row.appendChild(pkg_cell);
514 row.appendChild(amount_cell);
519 my $key_generator = sub {
521 my ($cust_bill_pkg, $amount, $hashref) = @$listref;
522 my $setup_or_recur = $cust_bill_pkg->setup > 0 ? 'setup' : 'recur';
523 my $taxlinenum = $hashref->{billpkgtaxlocationnum} ||
524 $hashref->{billpkgtaxratelocationnum} ||
527 join(':', $cust_bill_pkg->billpkgnum, $setup_or_recur, $taxlinenum);
530 my $onload = 'return true;';
532 if ($cgi->param('error')) {
534 my $set_sub_amounts =
535 join(';', map { "myform.subamount$_.value = ". $cgi->param("subamount$_") }
537 map { /^subnum(\d+)$/ ? $1 : '' }
540 $set_sub_amounts &&= "$set_sub_amounts;sub_changed(myform.subamount0)";
543 var myform = document.getElementById('ApplicationForm');
544 changed(myform.elements['$dst_pkey']);