RT#31594: Unapplied payment issues
authorJonathan Prykop <jonathan@freeside.biz>
Mon, 13 Jul 2015 23:33:52 +0000 (18:33 -0500)
committerJonathan Prykop <jonathan@freeside.biz>
Mon, 13 Jul 2015 23:33:52 +0000 (18:33 -0500)
FS/FS/Schema.pm
FS/FS/cust_bill.pm
FS/FS/cust_main/Billing.pm
FS/FS/cust_pay.pm
httemplate/edit/cust_pay.cgi
httemplate/edit/process/cust_pay-no_auto_apply.cgi [new file with mode: 0644]
httemplate/edit/process/cust_pay.cgi
httemplate/misc/batch-cust_pay.html
httemplate/misc/process/batch-cust_pay.cgi
httemplate/view/cust_main/payment_history/payment.html

index 24ca858..eb5f1d3 100644 (file)
@@ -2431,6 +2431,7 @@ sub tables_hashref {
         'payunique',   'varchar', 'NULL', $char_d, '', '',#separate paybatch "unique" functions from current usage
         'closed',         'char', 'NULL',       1, '', '', 
         'pkgnum', 'int', 'NULL', '', '', '', #desired pkgnum for pkg-balances
+        'no_auto_apply',  'char', 'NULL',       1, '', '', 
 
         # cash/check deposit info fields
         'bank',        'varchar', 'NULL', $char_d, '', '',
index db90930..5052ed1 100644 (file)
@@ -888,6 +888,7 @@ sub hide {
 =item apply_payments_and_credits [ OPTION => VALUE ... ]
 
 Applies unapplied payments and credits to this invoice.
+Payments with the no_auto_apply flag set will not be applied.
 
 A hash of optional arguments may be passed.  Currently "manual" is supported.
 If true, a payment receipt is sent instead of a statement when
@@ -914,7 +915,9 @@ sub apply_payments_and_credits {
 
   $self->select_for_update; #mutex
 
-  my @payments = grep { $_->unapplied > 0 } $self->cust_main->cust_pay;
+  my @payments = grep { $_->unapplied > 0 } 
+                   grep { !$_->no_auto_apply }
+                     $self->cust_main->cust_pay;
   my @credits  = grep { $_->credited > 0 } $self->cust_main->cust_credit;
 
   if ( $conf->exists('pkg-balances') ) {
index 87be4e6..df7e17f 100644 (file)
@@ -2177,6 +2177,7 @@ sub due_cust_event {
 =item apply_payments_and_credits [ OPTION => VALUE ... ]
 
 Applies unapplied payments and credits.
+Payments with the no_auto_apply flag set will not be applied.
 
 In most cases, this new method should be used in place of sequential
 apply_payments and apply_credits methods.
@@ -2319,6 +2320,7 @@ sub apply_credits {
 
 Applies (see L<FS::cust_bill_pay>) unapplied payments (see L<FS::cust_pay>)
 to outstanding invoice balances in chronological order.
+Payments with the no_auto_apply flag set will not be applied.
 
  #and returns the value of any remaining unapplied payments.
 
@@ -2348,7 +2350,7 @@ sub apply_payments {
 
   #return 0 unless
 
-  my @payments = $self->unapplied_cust_pay;
+  my @payments = grep { !$_->no_auto_apply } $self->unapplied_cust_pay;
 
   my @invoices = $self->open_cust_bill;
 
index 8b4c98a..d135599 100644 (file)
@@ -116,6 +116,10 @@ books closed flag, empty or `Y'
 
 Desired pkgnum when using experimental package balances.
 
+=item no_auto_apply
+
+Flag to only allow manual application of payment, empty or 'Y'
+
 =item bank
 
 The bank where the payment was deposited.
@@ -539,6 +543,7 @@ sub check {
     || $self->ut_textn('paybatch')
     || $self->ut_textn('payunique')
     || $self->ut_enum('closed', [ '', 'Y' ])
+    || $self->ut_flag('no_auto_apply')
     || $self->ut_foreign_keyn('pkgnum', 'cust_pkg', 'pkgnum')
     || $self->ut_textn('bank')
     || $self->ut_alphan('depositor')
index 888335f..5d74365 100755 (executable)
   <TD ALIGN="right"><% mt('Auto-apply to invoices') |h %></TD>
   <TD COLSPAN=2>
     <SELECT NAME="apply">
-      <OPTION VALUE="yes" SELECTED><% mt('yes') |h %> 
-      <OPTION><% mt('no') |h %></SELECT>
-    </TD>
+      <OPTION VALUE="yes" SELECTED><% mt('yes') |h %></OPTION> 
+      <OPTION VALUE=""><% mt('not now') |h %></OPTION>
+      <OPTION VALUE="never"><% mt('never') |h %></OPTION>
+    </SELECT>
+  </TD>
 
 % } elsif ( $link eq 'invnum' ) { 
 
   <TD ALIGN="right"><% mt('Apply to') |h %></TD>
   <TD COLSPAN=2 BGCOLOR="#ffffff">Invoice #<B><% $linknum %></B> only</TD>
-  <INPUT TYPE="hidden" NAME="apply" VALUE="no">
+  <INPUT TYPE="hidden" NAME="apply" VALUE="">
 
 % } 
 </TR>
diff --git a/httemplate/edit/process/cust_pay-no_auto_apply.cgi b/httemplate/edit/process/cust_pay-no_auto_apply.cgi
new file mode 100644 (file)
index 0000000..ccbd2d7
--- /dev/null
@@ -0,0 +1,48 @@
+<%doc>
+Quick process for toggling no_auto_apply field in cust_pay.
+
+Requires paynum and no_auto_apply ('Y' or '') in cgi.
+
+Requires 'Apply payment' acl.
+</%doc>
+
+% if ($error) {
+
+<P STYLE="color: #FF0000"><% emt($error) %></P>
+
+% } else {
+
+<P STYLE="font-weight: bold;"><% emt($message) %></P>
+<P><% emt('Please wait while the page reloads.') %></P>
+<SCRIPT TYPE="text/javascript">
+window.top.location.reload();
+</SCRIPT>
+
+% }
+
+<%init>
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Apply payment');
+
+my $paynum = $cgi->param('paynum');
+my $noauto = $cgi->param('no_auto_apply');
+
+my $error = '';
+my $message = '';
+my $cust_pay = qsearchs('cust_pay',{ paynum => $paynum });
+if ($cust_pay) {
+  if (($noauto eq 'Y') || (defined($noauto) && (length($noauto) == 0))) {
+    $cust_pay->no_auto_apply($noauto);
+    $error = $cust_pay->replace;
+    $message = $noauto ?
+               q(Payment will not be automatically applied to open invoices, must be applied manually) :
+               q(Payment will be automatically applied to open invoices the next time this customer's payments are processed);
+  } else {
+    $error = 'no_auto_apply not specified';
+  }
+} else {
+  $error .= 'Payment could not be found in database';
+}
+
+
+</%init>
index a002fa1..56d3f2f 100755 (executable)
@@ -50,6 +50,7 @@ else {
 my $new = new FS::cust_pay ( {
   $field => $linknum,
   _date  => $_date,
+  no_auto_apply => ($cgi->param('apply') eq 'never') ? 'Y' : '',
   map {
     $_, scalar($cgi->param($_));
   } qw( paid payby payinfo paybatch
index 3b0ebc1..9f2540c 100644 (file)
@@ -461,6 +461,16 @@ push @footer, '';
 push @footer_align, '';
 push @onchange, 'toggle_application_row';
 
+push @header, 'No Auto Allocate';
+push @fields, 'no_auto_apply';
+push @types, 'checkbox';
+push @align, 'c';
+push @sizes, '0';
+push @colors, '';
+push @footer, '';
+push @footer_align, '';
+push @onchange, '';
+
 #push @header, 'Error';
 push @header, '';
 push @fields, 'error';
index bb4b973..ff78862 100644 (file)
@@ -40,6 +40,7 @@ foreach my $row ( map /^custnum(\d+)$/, keys %$param ) {
                     'payinfo'        => $param->{"payinfo$row"},
                     'discount_term'  => $param->{"discount_term$row"},
                     'paybatch'       => $paybatch,
+                    'no_auto_apply'  => exists($param->{"no_auto_apply$row"}) ? 'Y' : '',
                   }
     if    $param->{"custnum$row"}
        || $param->{"paid$row"}
index 4ec9271..bf88a66 100644 (file)
@@ -9,6 +9,7 @@ my $date_format = $opt{'date_format'} || '%m/%d/%Y';
 
 my @cust_bill_pay = $cust_pay->cust_bill_pay;
 my @cust_pay_refund = $cust_pay->cust_pay_refund;
+my $unapplied = $cust_pay->unapplied;
 
 my ($payby,$payinfo) = translate_payinfo($cust_pay);
 my $target = "$payby$payinfo";
@@ -50,39 +51,14 @@ if (    scalar(@cust_bill_pay)   == 0
   $payment = emt("Unapplied Payment by [_1]",$otaker);
   $payment =~ s/$otaker/<i>$otaker<\/i>/ if $italicize_otaker;
   $payment = '<B><FONT COLOR="#FF0000">'.$payment.'</FONT></B>';
-  if ( $opt{'Apply payment'} ) {
-    if ( $opt{total_owed} > 0 ) {
-      $apply = ' ('.
-               include( '/elements/popup_link.html',
-                          'label'       => emt('apply'),
-                          'action'      => "${p}edit/cust_bill_pay.cgi?".
-                                           $cust_pay->paynum,
-                          'actionlabel' => emt('Apply payment'),
-                          %cust_bill_pay_width,
-                          %cust_bill_pay_height,
-                      ).
-                ')';
-    }
-    if ( $opt{total_unapplied_refunds} > 0 ) {
-      $apply.= ' ('.
-               include( '/elements/popup_link.html',
-                          'label'       => emt('apply to refund'),
-                          'action'      => "${p}edit/cust_pay_refund.cgi?".
-                                           $cust_pay->paynum,
-                          'actionlabel' => emt('Apply payment to refund'),
-                          'width'       => 392,
-                      ).
-               ')';
-    }
-  }
 } elsif (    scalar(@cust_bill_pay)   == 1
           && scalar(@cust_pay_refund) == 0
-          && $cust_pay->unapplied == 0     ) {
+          && $unapplied == 0     ) {
   #applied to one invoice, the usual situation
   $desc .= ' '. $cust_bill_pay[0]->applied_to_invoice;
 } elsif (    scalar(@cust_bill_pay)   == 0
           && scalar(@cust_pay_refund) == 1
-          && $cust_pay->unapplied == 0     ) {
+          && $unapplied == 0     ) {
   #applied to one refund
   $desc .= emt(" refunded on [_1]", time2str($date_format, $cust_pay_refund[0]->_date) );
 } else {
@@ -101,40 +77,59 @@ if (    scalar(@cust_bill_pay)   == 0
       die "$app is not a FS::cust_bill_pay or FS::cust_pay_refund";
     }
   }
-  if ( $cust_pay->unapplied > 0 ) {
+  if ( $unapplied > 0 ) {
     $desc .= '&nbsp;&nbsp;'.
              '<B><FONT COLOR="#FF0000">'.
-             emt("[_1][_2] unapplied", $opt{money_char}, $cust_pay->unapplied).
+             emt("[_1][_2] unapplied", $opt{money_char}, $unapplied).
              '</FONT></B>';
-    if ( $opt{'Apply payment'} ) {
-      if ( $opt{total_owed} > 0 ) {
-        $apply = ' ('.
-                 include( '/elements/popup_link.html',
-                            'label'      => emt('apply'),
-                            'action'     => "${p}edit/cust_bill_pay.cgi?".
-                                            $cust_pay->paynum,
-                            'actionlabel' => emt('Apply payment'),
-                            %cust_bill_pay_width,
-                            %cust_bill_pay_height,
-                        ).
-                 ')';
-      }
-      if ( $opt{total_unapplied_refunds} > 0 ) {
-        $apply.= ' ('.
-                 include( '/elements/popup_link.html',
-                            'label'      => emt('apply to refund'),
-                            'action'     => "${p}edit/cust_pay_refund.cgi?".
-                                            $cust_pay->paynum,
-                            'actionlabel' => emt('Apply payment to refund'),
-                            'width'      => 392,
-                        ).
-                 ')';
-      }
-    }
     $desc .= '<BR>';
   }
 }
 
+if ($unapplied > 0) {
+  if ( $opt{'Apply payment'} ) {
+    if ( $opt{total_owed} > 0 ) {
+      $apply = ' ('.
+               include( '/elements/popup_link.html',
+                          'label'       => emt('apply'),
+                          'action'      => "${p}edit/cust_bill_pay.cgi?".
+                                           $cust_pay->paynum,
+                          'actionlabel' => emt('Apply payment'),
+                          %cust_bill_pay_width,
+                          %cust_bill_pay_height,
+                      ).
+                ')';
+    }
+    if ( $opt{total_unapplied_refunds} > 0 ) {
+      $apply.= ' ('.
+               include( '/elements/popup_link.html',
+                          'label'       => emt('apply to refund'),
+                          'action'      => "${p}edit/cust_pay_refund.cgi?".
+                                           $cust_pay->paynum,
+                          'actionlabel' => emt('Apply payment to refund'),
+                          'width'       => 392,
+                      ).
+               ')';
+    }
+    $apply .= ' (auto&#8209;apply:&nbsp;'
+           .  ($cust_pay->no_auto_apply ? 'no' : 'yes')
+           .  '&nbsp;|&nbsp;'
+           .  include( '/elements/popup_link.html',
+                          'label'       => emt($cust_pay->no_auto_apply ? 'yes' : 'no'),
+                          'action'      => "${p}edit/process/cust_pay-no_auto_apply.cgi?paynum="
+                                           . $cust_pay->paynum
+                                           . '&no_auto_apply='
+                                           . ($cust_pay->no_auto_apply ? '' : 'Y'),
+                          'actionlabel' => emt('Toggle Auto-Apply'),
+                          'width'       => 392,
+                          'height'      => 200,
+                      )
+           . ')';
+  } else { # end if $opt('Apply payment')
+    $apply .= ' (no auto-apply)' if $cust_pay->no_auto_apply;
+  }
+} # end if $unapplied > 0
+
 my $view =
   ' ('. include('/elements/popup_link.html',
                   'label'     => emt('view receipt'),