fix the prorating for the package's first month (whew!)
[freeside.git] / FS / FS / part_pkg / agent.pm
1 package FS::part_pkg::agent;
2
3 use strict;
4 use vars qw(@ISA $DEBUG $me %info);
5 use Date::Format;
6 use FS::Record qw( qsearch );
7 use FS::agent;
8 use FS::cust_main;
9
10 #use FS::part_pkg::recur_Common;;
11 #@ISA = qw(FS::part_pkg::recur_Common);
12 use FS::part_pkg::prorate;
13 @ISA = qw(FS::part_pkg::prorate);
14
15 $DEBUG = 0;
16
17 $me = '[FS::part_pkg::agent]';
18
19 %info = (
20   'name'      => 'Wholesale bulk billing, for master customers of an agent.',
21   'shortname' => 'Wholesale bulk billing for agent.',
22
23   'fields' => {
24     'setup_fee'     => { 'name' => 'Setup fee for this package',
25                          'default' => 0,
26                        },
27     'recur_fee'     => { 'name' => 'Base recurring fee for this package',
28                          'default' => 0,
29                        },
30
31
32     #'recur_method'  => { 'name' => 'Recurring fee method',
33     #                     #'type' => 'radio',
34     #                     #'options' => \%recur_method,
35     #                     'type' => 'select',
36     #                     'select_options' => \%recur_Common::recur_method,
37     #                   },
38     'cutoff_day'    => { 'name' => 'Billing Day (1 - 28)',
39                          'default' => '1',
40                        },
41
42     'no_pkg_prorate'   => { 'name' => 'Disable prorating bulk packages (charge full price for packages active only a portion of the month)',
43                             'type' => 'checkbox',
44                           },
45
46   },
47
48   #'fieldorder' => [qw( setup_fee recur_fee recur_method cutoff_day ) ],
49   'fieldorder' => [qw( setup_fee recur_fee cutoff_day no_pkg_prorate ) ],
50
51   'weight' => 51,
52
53 );
54
55 #some false laziness-ish w/bulk.pm...  not a lot
56 sub calc_recur {
57   my $self = shift;
58   my($cust_pkg, $sdate, $details, $param ) = @_;
59
60   my $last_bill = $cust_pkg->last_bill;
61
62   return sprintf("%.2f", $self->SUPER::calc_recur(@_) )
63     unless $$sdate > $last_bill;
64
65   my $conf = new FS::Conf;
66   my $money_char = $conf->config('money_char') || '$';
67
68   my $total_agent_charge = 0;
69
70   warn "$me billing for agent packages from ". time2str('%x', $last_bill).
71                                        " to ". time2str('%x', $$sdate);
72        "\n"
73     if $DEBUG;
74
75   my $prorate_ratio =   ( $$sdate                     - $last_bill )
76                       / ( $self->add_freq($last_bill) - $last_bill );
77
78   #almost always just one,
79   #unless you have multiple agents with same master customer0
80   my @agents = qsearch('agent', { 'agent_custnum' => $cust_pkg->custnum } );
81
82   foreach my $agent (@agents) {
83
84     warn "$me billing for agent ". $agent->agent. "\n"
85       if $DEBUG;
86
87     #not the most efficient to load them all into memory,
88     #but good enough for our current needs
89     my @cust_main = qsearch('cust_main', { 'agentnum' => $agent->agentnum } );
90
91     foreach my $cust_main (@cust_main) {
92
93       warn "$me billing agent charges for ". $cust_main->name_short. "\n"
94         if $DEBUG;
95
96       #make sure setup dates are filled in
97       my $error = $cust_main->bill; #options don't propogate from freeside-daily
98       die "Error pre-billing agent customer: $error" if $error;
99
100       my @cust_pkg = grep { my $setup  = $_->get('setup');
101                             my $cancel = $_->get('cancel');
102
103                             $setup < $$sdate  # END
104                             && ( ! $cancel || $cancel > $last_bill ) #START
105                           }
106                      $cust_main->all_pkgs;
107
108       foreach my $cust_pkg ( @cust_pkg ) {
109
110         warn "$me billing agent charges for pkgnum ". $cust_pkg->pkgnum. "\n"
111           if $DEBUG;
112
113         my $pkg_details = $cust_main->name_short. ': '; #name?
114         # + something to identify package... primary service probably
115
116         my $pkg_charge = 0;
117
118         my $part_pkg = $cust_pkg->part_pkg;
119         #option to not fallback? via options above
120         my $pkg_setup_fee  =
121           $part_pkg->setup_cost || $part_pkg->option('setup_fee');
122         my $pkg_base_recur =
123           $part_pkg->recur_cost || $part_pkg->base_recur_permonth;
124
125         my $pkg_start = $cust_pkg->get('setup');
126         if ( $pkg_start < $last_bill ) {
127           $pkg_start = $last_bill;
128         } elsif ( $pkg_setup_fee ) {
129           $pkg_charge += $pkg_setup_fee;
130           $pkg_details .= $money_char. sprintf('%.2f setup, ', $pkg_setup_fee );
131         }
132
133         my $pkg_end = $cust_pkg->get('cancel');
134         $pkg_end = ( !$pkg_end || $pkg_end > $$sdate ) ? $$sdate : $pkg_end;
135
136
137         my $pkg_recur_charge = $prorate_ratio * $pkg_base_recur;
138         $pkg_recur_charge *= ( $pkg_end - $pkg_start )
139                            / ( $$sdate  - $last_bill )
140           unless $self->option('no_pkg_prorate');
141
142         my $recur_charge += $pkg_recur_charge;
143
144         $pkg_details .= $money_char. sprintf('%.2f', $recur_charge ).
145                         ' ('.  time2str('%x', $pkg_start).
146                         ' - '. time2str('%x', $pkg_end  ). ')'
147           if $recur_charge;
148
149         $pkg_charge += $recur_charge;
150
151         push @$details, $pkg_details
152           if $pkg_charge;
153         $total_agent_charge += $pkg_charge;
154
155       } #foreach $cust_pkg
156
157     } #foreach $cust_main
158
159   } #foreach $agent;
160
161   my $charges = $total_agent_charge + $self->SUPER::calc_recur(@_); #prorate
162
163   sprintf('%.2f', $charges );
164
165 }
166
167 sub hide_svc_detail {
168   1;
169 }
170
171 sub is_free {
172   0;
173 }
174
175 1;
176