From 8f37f0b3f6a946839132120984ec62b6f7ad7594 Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Tue, 16 Aug 2016 13:10:41 -0700 Subject: [PATCH] optionally show introductory rates as discounts, #72097 --- FS/FS/cust_bill_pkg.pm | 73 +++++++++++++++++++++++++--------------- FS/FS/part_pkg.pm | 12 +++++++ FS/FS/part_pkg/flat_introrate.pm | 57 ++++++++++++++++++++++++++----- 3 files changed, 106 insertions(+), 36 deletions(-) diff --git a/FS/FS/cust_bill_pkg.pm b/FS/FS/cust_bill_pkg.pm index df67f3d3e..a1762e471 100644 --- a/FS/FS/cust_bill_pkg.pm +++ b/FS/FS/cust_bill_pkg.pm @@ -832,34 +832,53 @@ sub _item_discount { my $self = shift; my %options = @_; + my $d; # this will be returned. + my @pkg_discounts = $self->pkg_discount; - return if @pkg_discounts == 0; - # special case: if there are old "discount details" on this line item, don't - # show discount line items - if ( FS::cust_bill_pkg_detail->count("detail LIKE 'Includes discount%' AND billpkgnum = ?", $self->billpkgnum || 0) > 0 ) { - return; - } - - my @ext; - my $d = { - _is_discount => 1, - description => $self->mt('Discount'), - setup_amount => 0, - recur_amount => 0, - ext_description => \@ext, - pkgpart => $self->pkgpart, - feepart => $self->feepart, - # maybe should show quantity/unit discount? - }; - foreach my $pkg_discount (@pkg_discounts) { - push @ext, $pkg_discount->description; - my $setuprecur = $pkg_discount->cust_pkg_discount->setuprecur; - $d->{$setuprecur.'_amount'} -= $pkg_discount->amount; - } - $d->{setup_amount} *= $self->quantity || 1; # ?? - $d->{recur_amount} *= $self->quantity || 1; # ?? - - return $d; + if (@pkg_discounts) { + # special case: if there are old "discount details" on this line item, + # don't show discount line items + if ( FS::cust_bill_pkg_detail->count("detail LIKE 'Includes discount%' AND billpkgnum = ?", $self->billpkgnum || 0) > 0 ) { + return; + } + + my @ext; + $d = { + _is_discount => 1, + description => $self->mt('Discount'), + setup_amount => 0, + recur_amount => 0, + ext_description => \@ext, + pkgpart => $self->pkgpart, + feepart => $self->feepart, + # maybe should show quantity/unit discount? + }; + foreach my $pkg_discount (@pkg_discounts) { + push @ext, $pkg_discount->description; + my $setuprecur = $pkg_discount->cust_pkg_discount->setuprecur; + $d->{$setuprecur.'_amount'} -= $pkg_discount->amount; + } + } + + # show introductory rate as a pseudo-discount + if (!$d) { # this will conflict with showing real discounts + my $part_pkg = $self->part_pkg; + if ( $part_pkg and $part_pkg->option('show_as_discount') ) { + my $cust_pkg = $self->cust_pkg; + my $intro_end = $part_pkg->intro_end($cust_pkg); + my $_date = $self->cust_bill->_date; + if ( $intro_end > $_date ) { + $d = $part_pkg->item_discount($cust_pkg); + } + } + } + + if ( $d ) { + $d->{setup_amount} *= $self->quantity || 1; # ?? + $d->{recur_amount} *= $self->quantity || 1; # ?? + } + + $d; } =item set_display OPTION => VALUE ... diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm index 92943f25c..008ba8a86 100644 --- a/FS/FS/part_pkg.pm +++ b/FS/FS/part_pkg.pm @@ -2015,6 +2015,18 @@ sub recur_margin_permonth { $self->base_recur_permonth(@_) - $self->recur_cost_permonth(@_); } +=item intro_end PACKAGE + +Takes an L object. If this plan has an introductory rate, +returns the expected date the intro period will end. If there is no intro +rate, returns zero. + +=cut + +sub intro_end { + 0; +} + =item format OPTION DATA Returns data formatted according to the function 'format' described diff --git a/FS/FS/part_pkg/flat_introrate.pm b/FS/FS/part_pkg/flat_introrate.pm index 786841bff..e43a525d2 100644 --- a/FS/FS/part_pkg/flat_introrate.pm +++ b/FS/FS/part_pkg/flat_introrate.pm @@ -46,17 +46,17 @@ sub validate_number { 'default' => 0, 'validate' => \&validate_number, }, + 'show_as_discount' => + { 'name' => 'Show the introductory rate on the invoice as if it\'s a discount', + 'type' => 'checkbox', + }, }, - 'fieldorder' => [ qw(intro_duration intro_fee) ], + 'fieldorder' => [ qw(intro_duration intro_fee show_as_discount) ], 'weight' => 14, ); -sub base_recur { - my($self, $cust_pkg, $time ) = @_; - - warn "flat_introrate base_recur requires date!" if !$time; - my $now = $time ? $$time : time; - +sub intro_end { + my($self, $cust_pkg) = @_; my ($duration) = ($self->option('intro_duration') =~ /^\s*(\d+)\s*$/); unless (length($duration)) { my $log = FS::Log->new('FS::part_pkg'); @@ -64,9 +64,27 @@ sub base_recur { .", defaulting to 0, check package definition"); $duration = 0; } - my $intro_end = $self->add_freq($cust_pkg->setup, $duration); - if ($now < $intro_end) { + # no setup or start_date means "start billing the package ASAP", so assume + # it would start billing right now. + my $start = $cust_pkg->setup || $cust_pkg->start_date || time; + + $self->add_freq($start, $duration); +} + +sub base_recur { + my($self, $cust_pkg, $time ) = @_; + + my $now; + if (!$time) { # the "$sdate" from _make_lines + my $log = FS::Log->new('FS::part_pkg'); + $log->warning("flat_introrate base_recur requires date!"); + $now = time; + } else { + $now = $$time; + } + + if ($now < $self->intro_end($cust_pkg)) { return $self->option('intro_fee'); } else { return $self->option('recur_fee'); @@ -74,5 +92,26 @@ sub base_recur { } +sub item_discount { + my ($self, $cust_pkg) = @_; + return unless $self->option('show_as_discount'); + my $intro_end = $self->intro_end($cust_pkg); + my $amount = sprintf('%.2f', + $self->option('intro_fee') - $self->option('recur_fee') + ); + return unless $amount < 0; + # otherwise it's an "introductory surcharge"? not the intended use of + # the feature. + + { '_is_discount' => 1, + 'description' => $cust_pkg->mt('Introductory discount until') . ' ' . + $cust_pkg->time2str_local('short', $intro_end), + 'setup_amount' => 0, + 'recur_amount' => $amount, + 'ext_description' => [], + 'pkgpart' => $self->pkgpart, + 'feepart' => '', + } +} 1; -- 2.11.0