From 605a9aa2bef153ebe5376ba7500e5a6b567a8b63 Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Sat, 15 Oct 2016 21:03:17 -0700 Subject: [PATCH] improve testing of prorate-sync behavior, #72928, #42108, and #34622 --- FS/t/suite/05-prorate_sync_same_day.t | 15 +++-- FS/t/suite/10-prorate_sync_same_hour.t | 102 ++++++++++++++++++++++++++++++++ FS/t/suite/11-prorate_sync_single_pkg.t | 89 ++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+), 5 deletions(-) create mode 100755 FS/t/suite/10-prorate_sync_same_hour.t create mode 100755 FS/t/suite/11-prorate_sync_single_pkg.t diff --git a/FS/t/suite/05-prorate_sync_same_day.t b/FS/t/suite/05-prorate_sync_same_day.t index 91a8efa74..d08752ef1 100755 --- a/FS/t/suite/05-prorate_sync_same_day.t +++ b/FS/t/suite/05-prorate_sync_same_day.t @@ -5,10 +5,15 @@ 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. +Formerly 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. + +Revised RT#72928: The second package should be prorated one day short only +if the rounding mode is 1 (round nearest), as the nearest day is different +for the two packages. =cut @@ -81,7 +86,7 @@ foreach my $prorate_mode (1, 2, 3) { $error = $cust->bill_and_collect; # Check the amount billed. - if ( $prorate_mode == 1 or $prorate_mode == 3 ) { + if ( $prorate_mode == 1 ) { # it should be one day short, in March $recur = sprintf('%.2f', $recur * 30/31); } diff --git a/FS/t/suite/10-prorate_sync_same_hour.t b/FS/t/suite/10-prorate_sync_same_hour.t new file mode 100755 index 000000000..f1e31851a --- /dev/null +++ b/FS/t/suite/10-prorate_sync_same_hour.t @@ -0,0 +1,102 @@ +#!/usr/bin/perl + +=head2 DESCRIPTION + +Tests the effect of ordering and activating two sync_bill_date packages +either both before or both after noon, less than an hour apart. Ref RT#42108 +and #72928. + +Correct: The packages should always end up with the same next bill date, +and should be billed for a full period, except in the case where the first +package starts at midnight and the rounding mode is "always round down". + +=cut + +use strict; +use Test::More tests => 27; +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. + foreach my $hour (0, 8, 16) { + diag("$hour:00"); + 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@fake.freeside.biz', + payby => 'BILL', + 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 $hour: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 $hour:01")); + $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 == 3 and $hour == 0 ) { + # special case: a start date of midnight won't be rounded down but any + # later start date will, so the second package will be one day short. + $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]"); + } +} diff --git a/FS/t/suite/11-prorate_sync_single_pkg.t b/FS/t/suite/11-prorate_sync_single_pkg.t new file mode 100755 index 000000000..83308f5f8 --- /dev/null +++ b/FS/t/suite/11-prorate_sync_single_pkg.t @@ -0,0 +1,89 @@ +#!/usr/bin/perl + +=head2 DESCRIPTION + +Tests the effect of ordering a sync_bill_date package either before or +after noon and billing it for two consecutive cycles, in all three prorate +rounding modes (round nearest, round up, and round down). Ref RT#34622. + +Correct: It should be charged full price in both cycles regardless of +the prorate rounding mode, as long as prorate rounding is enabled. + +=cut + +use strict; +use Test::More tests => 18; +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. + foreach my $hour (0, 8, 16) { + diag("$hour:00"); + 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@fake.freeside.biz', + payby => 'BILL', + bill_location => $location, + ship_location => $location, + }); + $error = $cust->insert; + BAIL_OUT("can't create test customer: $error") if $error; + + my $pkg; + # Create and bill the package. + set_fixed_time(str2time("2016-03-10 $hour:00")); + $pkg = FS::cust_pkg->new({ pkgpart => $pkgpart }); + $error = $cust->order_pkg({ 'cust_pkg' => $pkg }); + BAIL_OUT("can't order package: $error") if $error; + $error = $cust->bill_and_collect; + BAIL_OUT("can't bill package: $error") if $error; + + # Bill it a second time. + $pkg = $pkg->replace_old; + set_fixed_time($pkg->bill); + $error = $cust->bill_and_collect; + BAIL_OUT("can't bill package: $error") if $error; + + # Check the amount billed. + my $recur = $part_pkg->base_recur; + my @cust_bill = $cust->cust_bill; + ok( $cust_bill[0]->charged == $recur, "first bill is $recur" ) + or diag("first bill is ".$cust_bill[0]->charged); + ok( $cust_bill[1]->charged == $recur, "second bill is $recur" ) + or diag("second bill is ".$cust_bill[1]->charged); + + } +} -- 2.11.0