fix application of things when there's lots of CCH data and an error, RT#41610
[freeside.git] / httemplate / edit / elements / ApplicationCommon.html
1 <%doc>
2
3 Examples:
4
5   #cust_bill_pay
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',
12   &>
13
14   #cust_credit_bill
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',
21   &>
22
23   #cust_pay_refund
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',
30   &>
31
32   #cust_credit_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',
39   &>
40
41 </%doc>
42 <& /elements/header-popup.html, "Apply $src_thing$to", '', 'onLoad="myOnLoadFunction();"' &>
43
44 <& /elements/error.html &>
45
46 <P ID="ErrorMessage"></P>
47 <FORM ACTION="<% $p1. $opt{'form_action'} %>" NAME="ApplicationForm" ID="ApplicationForm" METHOD=POST>
48
49 <% $src_thing %> #<B><% $src_pkeyvalue %></B><BR>
50 <INPUT TYPE="hidden" NAME="<% $src_pkey %>" VALUE="<% $src_pkeyvalue %>">
51
52 <TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
53
54 <TR>
55   <TD ALIGN="right">Date: </TD>
56   <TD><B><% time2str($date_format, $src->_date) %></B></TD>
57 </TR>
58
59 <TR>
60   <TD ALIGN="right">Amount: </TD>
61   <TD ID="original_amount"><B><% $money_char %><% $src_amount %></B>
62   </TD>
63   <TD>
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>
67 % }
68   </TD>
69
70 </TR>
71
72 <TR>
73   <TD ALIGN="right">Unapplied amount: </TD>
74   <TD ID="unapplied_amount"><B><% $money_char %><% $unapplied %></B></TD>
75 </TR>
76
77 % if ( $src_table eq 'cust_credit' ) {
78     <TR>
79       <TD ALIGN="right">Reason: </TD>
80       <TD COLSPAN=2><B><% $src->reason %></B></TD>
81     </TR>
82 % }
83
84 </TABLE>
85 <BR>
86
87 <SCRIPT TYPE="text/javascript">
88 function clear_amounts() {
89   var rownum=0
90   var table = document.getElementById('ApplicationTable');
91   for (var row = 2; table.rows[row]; row++)
92   {
93     var inputs = table.rows[row].getElementsByTagName('input');
94     if ( !inputs.length ) {
95       break;
96     }
97     inputs.item(0).value = ''; // amount
98   }
99
100 }
101
102 function changed(what) {
103   dst = what.options[what.selectedIndex].value;
104
105   if ( dst == '' ) {
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;
110 %}
111     return true;
112   }
113
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;
118 %}
119
120 % foreach my $dst ( @dst ) {
121
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) %>";
126
127         var rownum=0
128         var table = document.getElementById('ApplicationTable');
129         while(table.rows[2]) {
130           table.deleteRow(2);
131         }
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),
137 %         }
138 %       );
139 %       my %apphash = ();
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;
156 %             $amount -= $toapp;
157 %           }
158 %           $desc .= ' (default)';
159 %         }
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, '') %>
163 %         }
164 %       }
165 %     }
166     }
167
168 % } 
169
170 }
171
172 function sub_changed(what) {
173
174   var amount = 0;
175   var table = document.getElementById('ApplicationTable');
176   var i = table.rows.length;
177   while(i-- > 2) {
178     var inputs = table.rows[i].getElementsByTagName('input');
179     if (! inputs.length) {
180       continue;
181     }
182     amount += parseFloat( inputs.item(0).value ) || 0; 
183   }
184   what.form.amount.value = parseFloat(amount).toFixed(2);
185   what.form.display_amount.value = parseFloat(amount).toFixed(2);
186   set_amount_color(what);
187
188 }
189
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';
193   } else {
194     what.form.display_amount.style.color = '#00ff00';
195   }
196 }
197
198 </SCRIPT>
199
200 Apply to:
201
202 % if ($use_sub_dst_thing && $src_pkey eq 'crednum') {
203 <CENTER>
204   <TABLE>
205     <TR>
206       <TD>
207         <BUTTON TYPE="button" NAME="tax_button" ID="tax_button" onClick="do_calculate_tax(this);" DISABLED>Calculate Tax</BUTTON>
208       </TD>
209       <TD>
210         <BUTTON TYPE="button" NAME="clear_button" ID="clear_button" onClick="clear_amounts(this);" DISABLED>Clear Amounts</BUTTON>
211       </TD>
212     </TR>
213   </TABLE>
214 </CENTER>
215 <& /elements/xmlhttp.html,
216             'url' =>  $p.'misc/xmlhttp-calculate_taxes.html',
217             'subs' => [ 'calculate_taxes' ],
218 &>
219 <SCRIPT TYPE="text/javascript">
220
221 function show_taxes(arg) {
222   var argsHash = eval('(' + arg + ')');
223
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;
229
230   var error = argsHash['error'];
231
232   var paragraph = document.getElementById('ErrorMessage');
233   if (error) {
234     paragraph.innerHTML = 'Error: ' + error;
235     paragraph.style.color = '#ff0000';
236   } else {
237     paragraph.innerHTML = '';
238   }
239   var taxlines = argsHash['taxlines'];
240
241   var table = document.getElementById('ApplicationTable');
242
243   var aFoundRow = 0;
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];
249     }
250
251     var found = 0;
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);
257         }
258         break;
259       }
260       if ( inputs.item(4).value == itemdesc && inputs.item(2).value == locnum )
261       {
262         inputs.item(0).value = taxlines[i][1];
263         aFoundRow = found = row;
264         break;
265       }
266     }
267     if (! found) {
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);
275     }
276   }
277
278   if (aFoundRow) {
279     sub_changed(table.rows[aFoundRow].getElementsByTagName('input').item(0));
280   }
281     
282 }
283
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++)
292   {
293     var inputs = table.rows[row].getElementsByTagName('input');
294     if ( !inputs.length ) {
295       break;
296     }
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
301     );
302     taxed_items.push(taxed_item);
303   }
304
305   var args = new Array(
306     'crednum', '<% $src_pkeyvalue %>',
307     'items', taxed_items
308   );
309   calculate_taxes ( args, show_taxes );
310 }
311
312 function do_change_amount (what) {
313   var amount_cell = document.getElementById('original_amount');
314   var inputs = amount_cell.getElementsByTagName('input');
315   if (inputs.length) {
316     src_amount_changed();
317     amount_cell.innerHTML = '<B><% $money_char %></B>' + inputs.item(0).value;
318   } else {
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);
329   }
330 }
331
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);
340   }
341   return true;
342 }
343
344 </SCRIPT>
345
346 %}
347
348 <TABLE ID="ApplicationTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
349
350 <TR>
351   <TD ALIGN="right"><% $dst_thing %>: </TD>
352   <TD><SELECT NAME="<% $dst_pkey %>" SIZE=1 onChange="changed(this)">
353 <OPTION VALUE="">Select <% $dst_thing %>
354
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 %>
357 % } 
358
359 </SELECT>
360   </TD>
361 </TR>
362
363 <TR>
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 %>" >
368 % }
369 </TR>
370
371 </TABLE>
372
373 <BR>
374 <CENTER><INPUT TYPE="submit"
375                VALUE="Apply"
376                NAME="submit"
377                ID="submit"
378 % if ($use_sub_dst_thing && $can_change_credit) {
379                onClick="src_amount_changed()"
380 % }
381                DISABLED
382 ></CENTER>
383
384 </FORM>
385
386 <SCRIPT TYPE="text/javascript">
387
388 function myOnLoadFunction () {
389   <% $onload %>
390 }
391
392 </SCRIPT>
393
394 <& /elements/footer-popup.html &>
395 <%init>
396
397 my %opt = @_;
398
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';
402
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;
406
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';
411
412 $opt{form_action} =~ /^process\/(.*)\./ or die "bad form action";
413 my $link_table = $1;
414
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") );
418
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')
424      );
425
426 my $to = $dst_table eq 'cust_refund' ? ' to Refund' : '';
427
428 $m->comp('/elements/handle_uri_query');
429
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');
436 } else {
437   my($query) = $cgi->keywords;
438   $query =~ /^(\d+)$/;
439   $src_pkeyvalue = $1;
440   $amount = '';
441   $dst_pkeyvalue = '';
442 }
443
444 my $p1 = popurl(1);
445
446 my $src = qsearchs($src_table, { $src_pkey => $src_pkeyvalue } );
447 die "$src_thing $src_pkeyvalue not found!" unless $src;
448
449 $src_amount = $src->amount unless $cgi->param('error');
450
451 my $unapplied = $src->unapplied;
452
453 my @dst = sort {    $a->_date     <=> $b->_date
454                  or $a->$dst_pkey <=> $b->$dst_pkey
455                }
456           grep { $_->$dst_unapplied != 0 }
457           qsearch($dst_table, { 'custnum' => $src->custnum } );
458
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';
465
466   $amount = sprintf("%.2f", $amount);
467   qq!
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);
515       rownum++;
516     !;
517 };
518
519 my $key_generator = sub {
520   my $listref = shift;
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} ||
525                    '';
526                    
527   join(':', $cust_bill_pkg->billpkgnum, $setup_or_recur, $taxlinenum);
528 };
529
530 my $onload = 'return true;';
531
532 if ($cgi->param('error')) {
533
534   my $set_sub_amounts =
535     join(';', map { "myform.subamount$_.value = ". $cgi->param("subamount$_") }
536                grep { /.+/ }
537                map { /^subnum(\d+)$/ ? $1 : '' }
538                $cgi->param
539   );
540   $set_sub_amounts &&= "$set_sub_amounts;sub_changed(myform.subamount0)";
541
542   $onload = qq!
543     var myform = document.getElementById('ApplicationForm');
544     changed(myform.elements['$dst_pkey']);
545     $set_sub_amounts;
546     return true;
547   !;
548 }
549
550 </%init>