part_pkg prorate mixin and sync_bill_date option, RT#9554
[freeside.git] / FS / FS / part_pkg / prorate_Mixin.pm
1 package FS::part_pkg::prorate_Mixin;
2
3 use strict;
4 use vars qw(@ISA %info);
5 use Time::Local qw(timelocal);
6
7 @ISA = qw(FS::part_pkg);
8 %info = ( 'disabled' => 1 );
9
10 =head1 NAME
11
12 FS::part_pkg::prorate_Mixin - Mixin class for part_pkg:: classes that 
13 need to prorate partial months
14
15 =head1 SYNOPSIS
16
17 package FS::part_pkg::...;
18 use base qw( FS::part_pkg::prorate_Mixin );
19
20 sub calc_recur {
21   ...
22   if( conditions that trigger prorate ) {
23     # sets $$sdate and $param->{'months'}, returns the prorated charge
24     $charges = $self->calc_prorate($cust_pkg, $sdate, $param, $cutoff_day);
25   } 
26   ...
27 }
28
29 =head METHODS
30
31 =item calc_prorate
32
33 Takes all the arguments of calc_recur, and calculates a prorated charge 
34 in one of two ways:
35
36 - If 'sync_bill_date' is set: Charge for a number of days to synchronize 
37   this package to the customer's next bill date.  If this is their only 
38   package (or they're already synchronized), that will take them through 
39   one billing cycle.
40 - If 'cutoff_day' is set: Prorate this package so that its next bill date 
41   falls on that day of the month.
42
43 =cut
44
45 sub calc_prorate {
46   my $self  = shift;
47   my ($cust_pkg, $sdate, $details, $param) = @_;
48  
49   my $charge = $self->option('recur_fee') || 0;
50   my $cutoff_day;
51   if( $self->option('sync_bill_date') ) {
52     my $next_bill = $cust_pkg->cust_main->next_bill_date;
53     if( defined($next_bill) and $next_bill != $$sdate ) {
54       $cutoff_day = (localtime($next_bill))[3];
55     }
56     else {
57       # don't prorate, assume a full month
58       $param->{'months'} = $self->freq;
59     }
60   }
61   else { # no sync, use cutoff_day or day 1
62     $cutoff_day = $self->option('cutoff_day') || 1;
63   }
64
65   if($cutoff_day) {
66     # only works for freq >= 1 month; probably can't be fixed
67     my $mnow = $$sdate;
68     my ($sec, $min, $hour, $mday, $mon, $year) = (localtime($mnow))[0..5];
69     my $mend;
70     my $mstart;
71     if ( $mday >= $cutoff_day ) {
72       $mend = 
73         timelocal(0,0,0,$cutoff_day,$mon == 11 ? 0 : $mon + 1,$year+($mon==11));
74       $mstart =
75         timelocal(0,0,0,$cutoff_day,$mon,$year);
76     }
77     else {
78       $mend = 
79         timelocal(0,0,0,$cutoff_day,$mon,$year);
80       $mstart = 
81         timelocal(0,0,0,$cutoff_day,$mon == 0 ? 11 : $mon - 1,$year-($mon==11));
82     }
83     
84     $$sdate = $mstart;
85
86     my $permonth = $self->option('recur_fee', 1) / $self->freq;
87     my $months = ( ( $self->freq - 1 ) + ($mend-$mnow) / ($mend-$mstart) );
88     
89     $param->{'months'} = $months;
90     $charge = sprintf('%.2f', $permonth * $months);
91   }
92   my $discount =  $self->calc_discount(@_);
93   return ($charge - $discount);
94 }
95
96 1;