1 package FS::part_pkg::prorate_Mixin;
4 use vars qw(@ISA %info);
5 use Time::Local qw(timelocal);
7 @ISA = qw(FS::part_pkg);
14 FS::part_pkg::prorate_Mixin - Mixin class for part_pkg:: classes that
15 need to prorate partial months
19 package FS::part_pkg::...;
20 use base qw( FS::part_pkg::prorate_Mixin );
24 if( conditions that trigger prorate ) {
25 # sets $$sdate and $param->{'months'}, returns the prorated charge
26 $charges = $self->calc_prorate($cust_pkg, $sdate, $param);
33 =item calc_prorate CUST_PKG SDATE DETAILS PARAM
35 Takes all the arguments of calc_recur. Calculates a prorated charge from
36 the $sdate to the cutoff day for this package definition, and sets the $sdate
37 and $param->{months} accordingly. base_recur() will be called to determine
38 the base price per billing cycle.
41 - add_full_period: Bill for the time up to the prorate day plus one full
42 billing period after that.
43 - prorate_round_day: Round the current time to the nearest full day,
44 instead of using the exact time.
45 - prorate_defer_bill: Don't bill the prorate interval until the prorate
52 my ($cust_pkg, $sdate, $details, $param) = @_;
53 my $cutoff_day = $self->cutoff_day or die "no cutoff_day"; #($cust_pkg)
55 my $charge = $self->base_recur($cust_pkg, $sdate) || 0;
59 # if this is the first bill but the bill date has been set
60 # (by prorate_defer_bill), calculate from the setup date,
61 # and append the setup fee to @$details.
62 if ( $self->option('prorate_defer_bill')
63 and ! $cust_pkg->getfield('last_bill')
64 and $cust_pkg->setup ) {
65 warn "[calc_prorate] #".$cust_pkg->pkgnum.": running deferred setup\n";
66 $param->{'setup_fee'} = $self->calc_setup($cust_pkg, $$sdate, $details);
67 $mnow = $cust_pkg->setup;
71 ($mnow, $mend, $mstart) = $self->_endpoints($mnow, $cutoff_day);
73 # next bill date will be figured as $$sdate + one period
76 my $permonth = $charge / $self->freq;
77 my $months = ( ( $self->freq - 1 ) + ($mend-$mnow) / ($mend-$mstart) );
79 # add a full period if currently billing for a partial period
80 if ( ( $self->option('add_full_period',1)
81 or $self->option('prorate_defer_bill',1) ) # necessary
82 and $months < $self->freq ) {
83 $months += $self->freq;
84 $$sdate = $self->add_freq($mstart);
87 $param->{'months'} = $months;
88 $charge = sprintf('%.2f', $permonth * $months);
95 Returns the value of the "cutoff_day" option, or 1.
101 $self->option('cutoff_day', 1) || 1;
104 =item prorate_setup CUST_PKG SDATE
106 Set up the package. This only has an effect if prorate_defer_bill is
107 set, in which case it postpones the next bill to the cutoff day.
113 my ($cust_pkg, $sdate) = @_;
114 my $cutoff_day = $self->cutoff_day($cust_pkg);
115 if ( ! $cust_pkg->bill
116 and $self->option('prorate_defer_bill',1)
119 my ($mnow, $mend, $mstart) = $self->_endpoints($sdate, $cutoff_day);
120 # if today is the cutoff day, set the next bill to right now instead
121 # of waiting a month.
122 if ( $mnow - $mstart < 86400 ) {
123 $cust_pkg->bill($mnow);
126 $cust_pkg->bill($mend);
133 =item _endpoints TIME CUTOFF_DAY
135 Given a current time and a day of the month to prorate to, return three
136 times: the start of the prorate interval (usually the current time), the
137 end of the prorate interval (i.e. the cutoff date), and the time one month
138 before the end of the prorate interval.
143 my ($self, $mnow, $cutoff_day) = @_;
145 # only works for freq >= 1 month; probably can't be fixed
146 my ($sec, $min, $hour, $mday, $mon, $year) = (localtime($mnow))[0..5];
147 if( $self->option('prorate_round_day',1) ) {
148 $mday++ if $hour >= 12;
149 $mnow = timelocal(0,0,0,$mday,$mon,$year);
153 # if cutoff day > 28, force it to the 1st of next month
154 if ( $cutoff_day > 28 ) {
156 # and if we are currently after the 28th, roll the current day
157 # forward to that day
160 #set $mnow = $mend so the amount billed will be zero
161 $mnow = timelocal(0,0,0,1,$mon == 11 ? 0 : $mon + 1,$year+($mon==11));
164 if ( $mday >= $cutoff_day ) {
166 timelocal(0,0,0,$cutoff_day,$mon == 11 ? 0 : $mon + 1,$year+($mon==11));
168 timelocal(0,0,0,$cutoff_day,$mon,$year);
172 timelocal(0,0,0,$cutoff_day,$mon,$year);
174 timelocal(0,0,0,$cutoff_day,$mon == 0 ? 11 : $mon - 1,$year-($mon==0));
176 return ($mnow, $mend, $mstart);