fix mixin inheritence preventing prorate_delayed packages from billing, RT#14372
[freeside.git] / FS / FS / part_pkg / prorate_Mixin.pm
1 package FS::part_pkg::prorate_Mixin;
2
3 use strict;
4 use vars qw( %info );
5 use Time::Local qw( timelocal );
6
7 %info = ( 
8   'disabled'  => 1,
9 );
10
11 =head1 NAME
12
13 FS::part_pkg::prorate_Mixin - Mixin class for part_pkg:: classes that 
14 need to prorate partial months
15
16 =head1 SYNOPSIS
17
18 package FS::part_pkg::...;
19 use base qw( FS::part_pkg::prorate_Mixin );
20
21 sub calc_recur {
22   ...
23   if( conditions that trigger prorate ) {
24     # sets $$sdate and $param->{'months'}, returns the prorated charge
25     $charges = $self->calc_prorate($cust_pkg, $sdate, $param, $cutoff_day);
26   } 
27   ...
28 }
29
30 =head METHODS
31
32 =item calc_prorate CUST_PKG
33
34 Takes all the arguments of calc_recur, followed by a day of the month 
35 to prorate to (which must be <= 28).  Calculates a prorated charge from 
36 the $sdate to that day, and sets the $sdate and $param->{months} accordingly.
37 base_recur() will be called to determine the base price per billing cycle.
38
39 Options:
40 - add_full_period: Bill for the time up to the prorate day plus one full
41 billing period after that.
42 - prorate_round_day: Round the current time to the nearest full day, 
43 instead of using the exact time.
44
45 =cut
46
47 sub calc_prorate {
48   my $self  = shift;
49   my ($cust_pkg, $sdate, $details, $param, $cutoff_day) = @_;
50  
51   my $charge = $self->base_recur($cust_pkg, $sdate) || 0;
52   if($cutoff_day) {
53     # only works for freq >= 1 month; probably can't be fixed
54     my $mnow = $$sdate;
55     my ($sec, $min, $hour, $mday, $mon, $year) = (localtime($mnow))[0..5];
56     if( $self->option('prorate_round_day',1) ) {
57       # If the time is 12:00-23:59, move to the next day by adding 18 
58       # hours to $mnow.  Because of DST this can end up from 05:00 to 18:59
59       # but it's always within the next day.
60       $mnow += 64800 if $hour >= 12;
61       # Get the new day, month, and year.
62       ($mday,$mon,$year) = (localtime($mnow))[3..5];
63       # Then set $mnow to midnight on that date.
64       $mnow = timelocal(0,0,0,$mday,$mon,$year);
65     }
66     my $mend;
67     my $mstart;
68     # if cutoff day > 28, force it to the 1st of next month
69     if ( $cutoff_day > 28 ) {
70       $cutoff_day = 1;
71       # and if we are currently after the 28th, roll the current day 
72       # forward to that day
73       if ( $mday > 28 ) {
74         $mday = 1;
75         #set $mnow = $mend so the amount billed will be zero
76         $mnow = timelocal(0,0,0,1,$mon == 11 ? 0 : $mon + 1,$year+($mon==11));
77       }
78     }
79     if ( $mday >= $cutoff_day ) {
80       $mend = 
81         timelocal(0,0,0,$cutoff_day,$mon == 11 ? 0 : $mon + 1,$year+($mon==11));
82       $mstart =
83         timelocal(0,0,0,$cutoff_day,$mon,$year);
84     }
85     else {
86       $mend = 
87         timelocal(0,0,0,$cutoff_day,$mon,$year);
88       $mstart = 
89         timelocal(0,0,0,$cutoff_day,$mon == 0 ? 11 : $mon - 1,$year-($mon==0));
90     }
91    
92     # next bill date will be figured as $$sdate + one period
93     $$sdate = $mstart;
94
95     my $permonth = $charge / $self->freq;
96     my $months = ( ( $self->freq - 1 ) + ($mend-$mnow) / ($mend-$mstart) );
97
98     # add a full period if currently billing for a partial period
99     if ( $self->option('add_full_period',1) and $months < $self->freq ) {
100       $months += $self->freq;
101       $$sdate = $self->add_freq($mstart);
102     }
103
104     $param->{'months'} = $months;
105     $charge = sprintf('%.2f', $permonth * $months);
106   }
107   return $charge;
108 }
109
110 1;