[freeside-commits] branch master updated. 0cd91e6625848b1ccf72b4b80bd18b3a1c66bbee

Mark Wells mark at 420.am
Mon May 16 22:45:53 PDT 2016


The branch, master has been updated
       via  0cd91e6625848b1ccf72b4b80bd18b3a1c66bbee (commit)
      from  5c87e7c09b01281104c39e1af0d5e5129b9804a6 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 0cd91e6625848b1ccf72b4b80bd18b3a1c66bbee
Author: Mark Wells <mark at freeside.biz>
Date:   Mon May 16 20:58:12 2016 -0700

    prorate_round_day options to round up / down only, #42108

diff --git a/FS/FS/part_pkg/flat.pm b/FS/FS/part_pkg/flat.pm
index 3e63242..84599ea 100644
--- a/FS/FS/part_pkg/flat.pm
+++ b/FS/FS/part_pkg/flat.pm
@@ -58,8 +58,9 @@ tie my %contract_years, 'Tie::IxHash', (
                         },
     'prorate_round_day' => {
                           'name' => 'When synchronizing, round the prorated '.
-                                    'period to the nearest full day',
-                          'type' => 'checkbox',
+                                    'period',
+                          'type' => 'select',
+                          'select_options' => \%FS::part_pkg::prorate_Mixin::prorate_round_day_opts,
                         },
     'add_full_period' => { 'disabled' => 1 }, # doesn't make sense with sync?
 
diff --git a/FS/FS/part_pkg/prorate.pm b/FS/FS/part_pkg/prorate.pm
index a81bfda..4cdc3f1 100644
--- a/FS/FS/part_pkg/prorate.pm
+++ b/FS/FS/part_pkg/prorate.pm
@@ -22,9 +22,9 @@ use Time::Local qw(timelocal);
                           'type' => 'checkbox',
                         },
     'prorate_round_day'=> {
-                          'name' => 'Round the prorated period to the nearest '.
-                                    'full day',
-                          'type' => 'checkbox',
+                          'name' => 'Round the prorated period',
+                          'type' => 'select',
+                          'select_options' => \%FS::part_pkg::prorate_Mixin::prorate_round_day_opts,
                         },
     'prorate_defer_bill'=> {
                         'name' => 'Defer the first bill until the billing day',
diff --git a/FS/FS/part_pkg/prorate_Mixin.pm b/FS/FS/part_pkg/prorate_Mixin.pm
index e8d42b9..26fdc35 100644
--- a/FS/FS/part_pkg/prorate_Mixin.pm
+++ b/FS/FS/part_pkg/prorate_Mixin.pm
@@ -6,6 +6,13 @@ use Time::Local qw( timelocal timelocal_nocheck );
 use Date::Format qw( time2str );
 use List::Util qw( min );
 
+tie our %prorate_round_day_opts, 'Tie::IxHash',
+  0   => 'no',
+  1   => 'to the nearest day',
+  2   => 'up to a full day',
+  3   => 'down to a full day',
+;
+
 %info = ( 
   'disabled'  => 1,
   # define all fields that are referenced in this code
@@ -16,8 +23,9 @@ use List::Util qw( min );
                 'type' => 'checkbox',
     },
     'prorate_round_day' => { 
-                'name' => 'When prorating, round to the nearest full day',
-                'type' => 'checkbox',
+                'name' => 'When prorating, round the prorated period',
+                'type' => 'select',
+                'select_options' => \%prorate_round_day_opts,
     },
     'prorate_defer_bill' => {
                 'name' => 'When prorating, defer the first bill until the '.
@@ -219,7 +227,8 @@ sub _endpoints {
 
   # only works for freq >= 1 month; probably can't be fixed
   my ($sec, $min, $hour, $mday, $mon, $year) = (localtime($mnow))[0..5];
-  if( $self->option('prorate_round_day',1) ) {
+  my $rounding_mode = $self->option('prorate_round_day',1);
+  if ( $rounding_mode == 1 ) {
     # If the time is 12:00-23:59, move to the next day by adding 18 
     # hours to $mnow.  Because of DST this can end up from 05:00 to 18:59
     # but it's always within the next day.
@@ -228,6 +237,19 @@ sub _endpoints {
     ($mday,$mon,$year) = (localtime($mnow))[3..5];
     # Then set $mnow to midnight on that day.
     $mnow = timelocal(0,0,0,$mday,$mon,$year);
+  } elsif ( $rounding_mode == 2 ) {
+    # Move the time back to midnight. This increases the length of the
+    # prorate interval.
+    $mnow = timelocal(0,0,0,$mday,$mon,$year);
+    ($mday,$mon,$year) = (localtime($mnow))[3..5];
+  } elsif ( $rounding_mode == 3 ) {
+    # If the time is after midnight, move it forward to the next midnight.
+    # This decreases the length of the prorate interval.
+    if ( $sec > 0 or $min > 0 or $hour > 0 ) {
+      # move to one second before midnight, then tick forward
+      $mnow = timelocal(59,59,23,$mday,$mon,$year) + 1;
+      ($mday,$mon,$year) = (localtime($mnow))[3..5];
+    }
   }
   my $mend;
   my $mstart;
diff --git a/FS/t/suite/05-prorate_sync_same_day.t b/FS/t/suite/05-prorate_sync_same_day.t
new file mode 100755
index 0000000..91a8efa
--- /dev/null
+++ b/FS/t/suite/05-prorate_sync_same_day.t
@@ -0,0 +1,97 @@
+#!/usr/bin/perl
+
+=head2 DESCRIPTION
+
+Tests the effect of ordering and activating two sync_bill_date packages on
+the same day. Ref RT#42108.
+
+Correct: If the packages have prorate_round_day = 1 (round nearest), or 3
+(round down) then the second package should be prorated one day short. If
+they have prorate_round_day = 2 (round up), they should be billed
+for the same amount. In both cases they should have the same next bill date.
+
+=cut
+
+use strict;
+use Test::More tests => 9;
+use FS::Test;
+use Date::Parse 'str2time';
+use Date::Format 'time2str';
+use Test::MockTime qw(set_fixed_time);
+use FS::cust_main;
+use FS::cust_pkg;
+use FS::Conf;
+my $FS= FS::Test->new;
+
+foreach my $prorate_mode (1, 2, 3) {
+  diag("prorate_round_day = $prorate_mode");
+  # Create a package def with the sync_bill_date option.
+  my $error;
+  my $old_part_pkg = $FS->qsearchs('part_pkg', { pkgpart => 5 });
+  my $part_pkg = $old_part_pkg->clone;
+  BAIL_OUT("existing pkgpart 5 is not a flat monthly package")
+    unless $part_pkg->freq eq '1' and $part_pkg->plan eq 'flat';
+  $error = $part_pkg->insert(
+    options => {  $old_part_pkg->options,
+                  'sync_bill_date' => 1,
+                  'prorate_round_day' => $prorate_mode, }
+  );
+
+  BAIL_OUT("can't configure package: $error") if $error;
+
+  my $pkgpart = $part_pkg->pkgpart;
+  # Create a clean customer with no other packages.
+  my $location = FS::cust_location->new({
+      address1  => '123 Example Street',
+      city      => 'Sacramento',
+      state     => 'CA',
+      country   => 'US',
+      zip       => '94901',
+  });
+  my $cust = FS::cust_main->new({
+      agentnum      => 1,
+      refnum        => 1,
+      last          => 'Customer',
+      first         => 'Sync bill date',
+      invoice_email => 'newcustomer at fake.freeside.biz',
+      bill_location => $location,
+      ship_location => $location,
+  });
+  $error = $cust->insert;
+  BAIL_OUT("can't create test customer: $error") if $error;
+
+  my @pkgs;
+  # Create and bill the first package.
+  set_fixed_time(str2time('2016-03-10 08:00'));
+  $pkgs[0] = FS::cust_pkg->new({ pkgpart => $pkgpart });
+  $error = $cust->order_pkg({ 'cust_pkg' => $pkgs[0] });
+  BAIL_OUT("can't order package: $error") if $error;
+  $error = $cust->bill_and_collect;
+  # Check the amount billed.
+  my ($cust_bill_pkg) = $pkgs[0]->cust_bill_pkg;
+  my $recur = $part_pkg->base_recur;
+  ok( $cust_bill_pkg->recur == $recur, "first package recur is $recur" )
+    or diag("first package recur is ".$cust_bill_pkg->recur);
+
+  # Create and bill the second package.
+  set_fixed_time(str2time('2016-03-10 16:00'));
+  $pkgs[1] = FS::cust_pkg->new({ pkgpart => $pkgpart });
+  $error = $cust->order_pkg({ 'cust_pkg' => $pkgs[1] });
+  BAIL_OUT("can't order package: $error") if $error;
+  $error = $cust->bill_and_collect;
+
+  # Check the amount billed.
+  if ( $prorate_mode == 1 or $prorate_mode == 3 ) {
+    # it should be one day short, in March
+    $recur = sprintf('%.2f', $recur * 30/31);
+  }
+  ($cust_bill_pkg) = $pkgs[1]->cust_bill_pkg;
+  ok( $cust_bill_pkg->recur == $recur, "second package recur is $recur" )
+    or diag("second package recur is ".$cust_bill_pkg->recur);
+
+  my @next_bill = map { time2str('%Y-%m-%d', $_->replace_old->get('bill')) } @pkgs;
+
+  ok( $next_bill[0] eq $next_bill[1],
+    "both packages will bill again on $next_bill[0]" )
+    or diag("first package bill date is $next_bill[0], second package is $next_bill[1]");
+}

-----------------------------------------------------------------------

Summary of changes:
 FS/FS/part_pkg/flat.pm                |    5 +-
 FS/FS/part_pkg/prorate.pm             |    6 +-
 FS/FS/part_pkg/prorate_Mixin.pm       |   28 +++++++++-
 FS/t/suite/05-prorate_sync_same_day.t |   97 +++++++++++++++++++++++++++++++++
 4 files changed, 128 insertions(+), 8 deletions(-)
 create mode 100755 FS/t/suite/05-prorate_sync_same_day.t




More information about the freeside-commits mailing list