},
{
+ 'key' => 'order_pkg-no_start_date',
+ 'section' => 'UI',
+ 'description' => 'Don\'t set a default start date for new packages.',
+ 'type' => 'checkbox',
+ },
+
+ {
'key' => 'mcp_svcpart',
'section' => '',
'description' => 'Master Control Program svcpart. Leave this blank.',
use FS::part_pkg;
use FS::cust_bill_pkg_discount;
-@ISA = qw(FS::part_pkg);
+@ISA = qw(FS::part_pkg FS::part_pkg::prorate_Mixin);
tie my %temporalities, 'Tie::IxHash',
'upcoming' => "Upcoming (future)",
'start_1st' => { 'name' => 'Auto-add a start date to the 1st, ignoring the current month.',
'type' => 'checkbox',
},
+ 'sync_bill_date' => { 'name' => 'Prorate first month to synchronize '.
+ 'with the customer\'s other packages',
+ 'type' => 'checkbox',
+ },
%usage_fields,
%usage_recharge_fields,
},
'fieldorder' => [ qw( setup_fee recur_fee
recur_temporality unused_credit
- expire_months start_1st
+ expire_months start_1st sync_bill_date
),
@usage_fieldorder, @usage_recharge_fieldorder,
qw( externalid ),
}
sub calc_recur {
- my($self, $cust_pkg, $sdate, $details, $param ) = @_;
+ my $self = shift;
+ my($cust_pkg, $sdate, $details, $param ) = @_;
#my $last_bill = $cust_pkg->last_bill;
my $last_bill = $cust_pkg->get('last_bill'); #->last_bill falls back to setup
return 0
if $self->option('recur_temporality', 1) eq 'preceding' && $last_bill == 0;
- my $br = $self->base_recur($cust_pkg);
-
- my $discount = $self->calc_discount($cust_pkg, $sdate, $details, $param);
+ if( $self->option('sync_bill_date') ) {
+ return $self->calc_prorate(@_);
+ }
+ else {
+ my $charge = $self->base_recur($cust_pkg);
+ my $discount = $self->calc_discount($cust_pkg, $sdate, $details, $param);
- sprintf('%.2f', $br - $discount);
+ return sprintf('%.2f', $charge - $discount);
+ }
}
sub calc_discount {
);
sub calc_recur {
- my($self, $cust_pkg, $sdate, $details, $param ) = @_;
- my $cutoff_day = $self->option('cutoff_day', 1) || 1;
- my $mnow = $$sdate;
- my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($mnow) )[0,1,2,3,4,5];
- my $mend;
- my $mstart;
-
- if ( $mday >= $cutoff_day ) {
- $mend =
- timelocal(0,0,0,$cutoff_day, $mon == 11 ? 0 : $mon+1, $year+($mon==11));
- $mstart =
- timelocal(0,0,0,$cutoff_day,$mon,$year);
-
- } else {
- $mend = timelocal(0,0,0,$cutoff_day, $mon, $year);
- if ($mon==0) {$mon=11;$year--;} else {$mon--;}
- $mstart= timelocal(0,0,0,$cutoff_day,$mon,$year);
- }
-
- $$sdate = $mstart;
- my $permonth = $self->option('recur_fee') / $self->freq;
-
- my $months = ( ( $self->freq - 1 ) + ($mend-$mnow) / ($mend-$mstart) );
-
- $param->{'months'} = $months;
- my $discount = $self->calc_discount( $cust_pkg, $sdate, $details, $param);
-
- sprintf('%.2f', $permonth * $months - $discount);
+ my $self = shift;
+ $self->calc_prorate(@_);
}
1;
--- /dev/null
+package FS::part_pkg::prorate_Mixin;
+
+use strict;
+use vars qw(@ISA %info);
+use Time::Local qw(timelocal);
+
+@ISA = qw(FS::part_pkg);
+%info = ( 'disabled' => 1 );
+
+=head1 NAME
+
+FS::part_pkg::prorate_Mixin - Mixin class for part_pkg:: classes that
+need to prorate partial months
+
+=head1 SYNOPSIS
+
+package FS::part_pkg::...;
+use base qw( FS::part_pkg::prorate_Mixin );
+
+sub calc_recur {
+ ...
+ if( conditions that trigger prorate ) {
+ # sets $$sdate and $param->{'months'}, returns the prorated charge
+ $charges = $self->calc_prorate($cust_pkg, $sdate, $param, $cutoff_day);
+ }
+ ...
+}
+
+=head METHODS
+
+=item calc_prorate
+
+Takes all the arguments of calc_recur, and calculates a prorated charge
+in one of two ways:
+
+- If 'sync_bill_date' is set: Charge for a number of days to synchronize
+ this package to the customer's next bill date. If this is their only
+ package (or they're already synchronized), that will take them through
+ one billing cycle.
+- If 'cutoff_day' is set: Prorate this package so that its next bill date
+ falls on that day of the month.
+
+=cut
+
+sub calc_prorate {
+ my $self = shift;
+ my ($cust_pkg, $sdate, $details, $param) = @_;
+
+ my $charge = $self->option('recur_fee') || 0;
+ my $cutoff_day;
+ if( $self->option('sync_bill_date') ) {
+ my $next_bill = $cust_pkg->cust_main->next_bill_date;
+ if( defined($next_bill) and $next_bill != $$sdate ) {
+ $cutoff_day = (localtime($next_bill))[3];
+ }
+ else {
+ # don't prorate, assume a full month
+ $param->{'months'} = $self->freq;
+ }
+ }
+ else { # no sync, use cutoff_day or day 1
+ $cutoff_day = $self->option('cutoff_day') || 1;
+ }
+
+ if($cutoff_day) {
+ # only works for freq >= 1 month; probably can't be fixed
+ my $mnow = $$sdate;
+ my ($sec, $min, $hour, $mday, $mon, $year) = (localtime($mnow))[0..5];
+ my $mend;
+ my $mstart;
+ if ( $mday >= $cutoff_day ) {
+ $mend =
+ timelocal(0,0,0,$cutoff_day,$mon == 11 ? 0 : $mon + 1,$year+($mon==11));
+ $mstart =
+ timelocal(0,0,0,$cutoff_day,$mon,$year);
+ }
+ else {
+ $mend =
+ timelocal(0,0,0,$cutoff_day,$mon,$year);
+ $mstart =
+ timelocal(0,0,0,$cutoff_day,$mon == 0 ? 11 : $mon - 1,$year-($mon==11));
+ }
+
+ $$sdate = $mstart;
+
+ my $permonth = $self->option('recur_fee', 1) / $self->freq;
+ my $months = ( ( $self->freq - 1 ) + ($mend-$mnow) / ($mend-$mstart) );
+
+ $param->{'months'} = $months;
+ $charge = sprintf('%.2f', $permonth * $months);
+ }
+ my $discount = $self->calc_discount(@_);
+ return ($charge - $discount);
+}
+
+1;
use vars qw( @ISA %info %recur_method );
use Tie::IxHash;
use Time::Local;
-use FS::part_pkg::prorate;
+use FS::part_pkg::prorate_Mixin;
-@ISA = qw(FS::part_pkg::prorate);
+@ISA = qw(FS::part_pkg::prorate_Mixin);
%info = ( 'disabled' => 1 ); #recur_Common not a usable price plan directly
my $recur_method = $self->option('recur_method', 1) || 'anniversary';
- if ( $recur_method eq 'prorate' ) {
-
- $charges = $self->SUPER::calc_recur(@_);
-
- } else {
+ if ( $recur_method eq 'prorate'
+ or ($recur_method eq 'anniversary' and $self->option('sync_bill_date'))
+ ) {
+ $charges = $self->calc_prorate(@_);
+ }
+ else {
$charges = $self->option('recur_fee');
$$sdate = timelocal(0, 0, 0, $cutoff_day, $mon, $year);
}#$recur_method eq 'subscription'
+ $charges -= $self->calc_discount( $cust_pkg, $sdate, $details, $param );
- $charges -= $self->calc_discount( $cust_pkg, $sdate, $details, $param );
-
- }#$recur_method eq 'prorate'
-
+ }#$recur_method eq 'prorate' or ...
}#increment_next_bill
- $charges;
+ return $charges;
}
my $pkgpart = scalar($cgi->param('pkgpart'));
my $format = $date_format. ' %T %z (%Z)'; #false laziness w/REAL_cust_pkg.cgi?
-my $start_date = $cust_main->next_bill_date;
-$start_date = $start_date ? time2str($format, $start_date) : '';
+my $start_date = '';
+if(! $conf->exists('order_pkg-no_start_date') ) {
+ warn "foo";
+ $cust_main->next_bill_date;
+ $start_date = $start_date ? time2str($format, $start_date) : '';
+}
</%init>