option to force one-time charges to be billed separately, #32866
authorMark Wells <mark@freeside.biz>
Tue, 24 Feb 2015 03:17:29 +0000 (19:17 -0800)
committerMark Wells <mark@freeside.biz>
Tue, 24 Feb 2015 03:17:29 +0000 (19:17 -0800)
FS/FS/Schema.pm
FS/FS/cust_main.pm
FS/FS/cust_main/Billing.pm
FS/FS/cust_pkg.pm
httemplate/edit/process/quick-charge.cgi
httemplate/edit/quick-charge.html
httemplate/view/cust_main/packages/package.html
httemplate/view/cust_main/packages/status.html

index db2d3cf..44f09d6 100644 (file)
@@ -2659,6 +2659,7 @@ sub tables_hashref {
         'recur_show_zero',    'char', 'NULL',  1, '', '',
         'setup_show_zero',    'char', 'NULL',  1, '', '',
         'change_to_pkgnum',    'int', 'NULL', '', '', '',
+        'separate_bill',      'char', 'NULL',  1, '', '',
       ],
       'primary_key'  => 'pkgnum',
       'unique'       => [],
index c3b141e..7594fa8 100644 (file)
@@ -3131,6 +3131,7 @@ sub charge {
   my ( $setuptax, $taxclass );   #internal taxes
   my ( $taxproduct, $override ); #vendor (CCH) taxes
   my $no_auto = '';
+  my $separate_bill = '';
   my $cust_pkg_ref = '';
   my ( $bill_now, $invoice_terms ) = ( 0, '' );
   my $locationnum;
@@ -3153,7 +3154,8 @@ sub charge {
     $bill_now = exists($_[0]->{bill_now}) ? $_[0]->{bill_now} : '';
     $invoice_terms = exists($_[0]->{invoice_terms}) ? $_[0]->{invoice_terms} : '';
     $locationnum = $_[0]->{locationnum} || $self->ship_locationnum;
-  } else {
+    $separate_bill = $_[0]->{separate_bill} || '';
+  } else { # yuck
     $amount     = shift;
     $setup_cost = '';
     $quantity   = 1;
@@ -3221,6 +3223,7 @@ sub charge {
     'quantity'   => $quantity,
     'start_date' => $start_date,
     'no_auto'    => $no_auto,
+    'separate_bill' => $separate_bill,
     'locationnum'=> $locationnum,
   } );
 
index b3d4e70..9305eb3 100644 (file)
@@ -518,13 +518,31 @@ sub bill {
       push @{ $cust_bill_pkg{$pass} }, @transfer_items;
       # treating this as recur, just because most charges are recur...
       ${$total_recur{$pass}} += $_->recur foreach @transfer_items;
+
+      # currently not considering separate_bill here, as it's for 
+      # one-time charges only
     }
 
     foreach my $part_pkg ( @part_pkg ) {
 
       $cust_pkg->set($_, $hash{$_}) foreach qw ( setup last_bill bill );
 
-      my $pass = ($cust_pkg->no_auto || $part_pkg->no_auto) ? 'no_auto' : '';
+      my $pass = '';
+      if ( $cust_pkg->separate_bill ) {
+        # if no_auto is also set, that's fine. we just need to not have
+        # invoices that are both auto and no_auto, and since the package
+        # gets an invoice all to itself, it will only be one or the other.
+        $pass = $cust_pkg->pkgnum;
+        if (!exists $cust_bill_pkg{$pass}) { # it may not exist yet
+          push @passes, $pass;
+          $total_setup{$pass} = do { my $z = 0; \$z };
+          $total_recur{$pass} = do { my $z = 0; \$z };
+          $taxlisthash{$pass} = {};
+          $cust_bill_pkg{$pass} = [];
+        }
+      } elsif ( ($cust_pkg->no_auto || $part_pkg->no_auto) ) {
+        $pass = 'no_auto';
+      }
 
       my $next_bill = $cust_pkg->getfield('bill') || 0;
       my $error;
@@ -566,13 +584,7 @@ sub bill {
 
   } #foreach my $cust_pkg
 
-  #if the customer isn't on an automatic payby, everything can go on a single
-  #invoice anyway?
-  #if ( $cust_main->payby !~ /^(CARD|CHEK)$/ ) {
-    #merge everything into one list
-  #}
-
-  foreach my $pass (@passes) { # keys %cust_bill_pkg ) {
+  foreach my $pass (@passes) { # keys %cust_bill_pkg )
 
     my @cust_bill_pkg = _omit_zero_value_bundles(@{ $cust_bill_pkg{$pass} });
 
index b64d4dc..ae86ca0 100644 (file)
@@ -667,8 +667,9 @@ sub check {
     || $self->ut_numbern('resume')
     || $self->ut_numbern('expire')
     || $self->ut_numbern('dundate')
-    || $self->ut_enum('no_auto', [ '', 'Y' ])
-    || $self->ut_enum('waive_setup', [ '', 'Y' ])
+    || $self->ut_flag('no_auto', [ '', 'Y' ])
+    || $self->ut_flag('waive_setup', [ '', 'Y' ])
+    || $self->ut_flag('separate_bill')
     || $self->ut_textn('agent_pkgid')
     || $self->ut_enum('recur_show_zero', [ '', 'Y', 'N', ])
     || $self->ut_enum('setup_show_zero', [ '', 'Y', 'N', ])
@@ -1065,7 +1066,8 @@ sub uncancel {
       setup
       susp adjourn resume expire start_date contract_end dundate
       change_date change_pkgpart change_locationnum
-      manual_flag no_auto quantity agent_pkgid recur_show_zero setup_show_zero
+      manual_flag no_auto separate_bill quantity agent_pkgid 
+      recur_show_zero setup_show_zero
     ),
   };
 
@@ -2408,6 +2410,7 @@ and, I<if the charge has not yet been billed>:
 - start_date: the date when it will be billed
 - amount: the setup fee to be charged
 - quantity: the multiplier for the setup fee
+- separate_bill: whether to put the charge on a separate invoice
 
 If you pass 'adjust_commission' => 1, and the classnum changes, and there are
 commission credits linked to this charge, they will be recalculated.
@@ -2463,7 +2466,8 @@ sub modify_charge {
   }
 
   if ( !$self->get('setup') ) {
-    # not yet billed, so allow amount, setup_cost, quantity and start_date
+    # not yet billed, so allow amount, setup_cost, quantity, start_date,
+    # and separate_bill
 
     if ( exists($opt{'amount'}) 
           and $part_pkg->option('setup_fee') != $opt{'amount'}
@@ -2493,6 +2497,12 @@ sub modify_charge {
       $self->set('start_date', $opt{'start_date'});
     }
 
+    if ( exists($opt{'separate_bill'})
+          and $opt{'separate_bill'} ne $self->separate_bill ) {
+
+      $self->set('separate_bill', $opt{'separate_bill'});
+    }
+
 
   } # else simply ignore them; the UI shouldn't allow editing the fields
 
index aa6010e..c1e7fc1 100644 (file)
@@ -93,6 +93,7 @@ if ( $param->{'pkgnum'} =~ /^(\d+)$/ ) { #modifying an existing one-time charge
       'tax_override'      => $override,
       'quantity'          => $quantity,
       'start_date'        => $start_date,
+      'separate_bill'     => scalar($cgi->param('separate_bill')),
   );
 
 } else { # the usual case: new one-time charge
@@ -138,6 +139,7 @@ if ( $param->{'pkgnum'} =~ /^(\d+)$/ ) { #modifying an existing one-time charge
                            : ''
                        ),
     'no_auto'       => scalar($cgi->param('no_auto')),
+    'separate_bill' => scalar($cgi->param('separate_bill')),
     'pkg'           => scalar($cgi->param('pkg')),
     'setuptax'      => scalar($cgi->param('setuptax')),
     'taxclass'      => scalar($cgi->param('taxclass')),
index 58c1b0a..78752c0 100644 (file)
@@ -169,18 +169,22 @@ function bill_now_changed (what) {
           noinit  => 1,
         }
       &>
-%   }
 
-%              unless ($billed) {
-<TR>
-  <TD ALIGN="right"><% mt('Tax exempt') |h %> </TD>
-  <TD><INPUT TYPE="checkbox" NAME="setuptax" VALUE="Y" <% $cgi->param('setuptax') ? 'CHECKED' : '' %>></TD>
-</TR>
+      <& /elements/tr-checkbox.html,
+        label => emt('Invoice this charge separately'),
+        field => 'separate_bill',
+        value => 'Y',
+        curr_value => $cust_pkg->get('separate_bill'),
+      &>
+      <TR>
+        <TD ALIGN="right"><% mt('Tax exempt') |h %> </TD>
+        <TD><INPUT TYPE="checkbox" NAME="setuptax" VALUE="Y" <% $cgi->param('setuptax') ? 'CHECKED' : '' %>></TD>
+      </TR>
 
-<& /elements/tr-select-taxclass.html, 'curr_value' => $part_pkg->get('taxclass')  &>
+      <& /elements/tr-select-taxclass.html, 'curr_value' => $part_pkg->get('taxclass')  &>
 
-<& /elements/tr-select-taxproduct.html, 'label' => emt('Tax product'), 'onclick' => 'parent.taxproductmagic(this);', 'curr_value' => $part_pkg->get('taxproductnum')  &>
-%              }
+      <& /elements/tr-select-taxproduct.html, 'label' => emt('Tax product'), 'onclick' => 'parent.taxproductmagic(this);', 'curr_value' => $part_pkg->get('taxproductnum')  &>
+% }
 
 % } else { # new one-time charge
 
@@ -280,6 +284,12 @@ function bill_now_changed (what) {
       });
     </SCRIPT>
 
+<& /elements/tr-checkbox.html,
+  label => emt('Invoice this charge separately'),
+  field => 'separate_bill',
+  value => 'Y'
+&>
+
 % }
 
 % if ( ! $quotationnum && $cust_main->payby =~ /^(CARD|CHEK)$/ ) {
index e47d891..3036f2e 100644 (file)
@@ -370,7 +370,7 @@ sub onetime_change_link {
     'actionlabel' => emt('Modify'),
     'cust_pkg'    => $cust_pkg,
     'width'       => 690,
-    'height'      => 380,
+    'height'      => 440,
   );
 }
 
index 3641964..2bb4bef 100644 (file)
@@ -69,6 +69,8 @@
 
     <% pkg_status_row_noauto( $cust_pkg, %opt ) %>
 
+    <% pkg_status_row_separate_bill( $cust_pkg, %opt ) %>
+
     <% pkg_status_row_discount( $cust_pkg, %opt ) %>
 
 %   unless ( $cust_pkg->order_date eq $cust_pkg->get('susp') ) { #on hold
 
           <% pkg_status_row_noauto( $cust_pkg, %opt ) %>
 
+          <% pkg_status_row_separate_bill( $cust_pkg, %opt ) %>
+
           <% pkg_status_row_discount( $cust_pkg, %opt ) %>
 
           <% pkg_status_row_if(
 
           <% pkg_status_row_noauto( $cust_pkg, %opt ) %>
 
+          <% pkg_status_row_separate_bill( $cust_pkg, %opt ) %>
+
           <% pkg_status_row_discount( $cust_pkg, %opt ) %>
 
           <% pkg_status_row_if($cust_pkg, emt('Start billing'), 'start_date', %opt) %>
 
           <% pkg_status_row_noauto( $cust_pkg, %opt ) %>
 
+          <% pkg_status_row_separate_bill( $cust_pkg, %opt ) %>
+
           <% pkg_status_row_discount( $cust_pkg, %opt ) %>
 
           <% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %>
 
           <% pkg_status_row_noauto( $cust_pkg, %opt ) %>
 
+          <% pkg_status_row_separate_bill( $cust_pkg, %opt ) %>
+
           <% pkg_status_row_discount( $cust_pkg, %opt ) %>
 
           <% pkg_status_row($cust_pkg, emt('Setup'), 'setup', %opt) %>
@@ -496,6 +506,12 @@ sub pkg_status_row_noauto {
   pkg_status_row_colspan( $cust_pkg, emt("No automatic $what charge"), '');
 }
 
+sub pkg_status_row_separate_bill {
+  my $cust_pkg = shift;
+  return '' unless $cust_pkg->separate_bill;
+  pkg_status_row_colspan( $cust_pkg, emt("Invoiced separately") );
+}
+
 sub pkg_status_row_discount {
   my( $cust_pkg, %opt ) = @_;