agent wholesale pricing based on actual invoices, #31234
[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 billing based on package prices, for master customers of an agent.',
21   'shortname' => 'Wholesale billing for agent (package prices)',
22   'inherit_fields' => [qw( prorate global_Mixin)],
23   'fields' => {
24     #'recur_method'  => { 'name' => 'Recurring fee method',
25     #                     #'type' => 'radio',
26     #                     #'options' => \%recur_method,
27     #                     'type' => 'select',
28     #                     'select_options' => \%recur_Common::recur_method,
29     #                   },
30     'cutoff_day'    => { 'name' => 'Billing Day (1 - 28)',
31                          'default' => '1',
32                        },
33     'add_full_period'=> { 'name' => 'When prorating first month, also bill '.
34                                     'for one full period after that',
35                           'type' => 'checkbox',
36                         },
37
38     'no_pkg_prorate'   => { 'name' => 'Disable prorating bulk packages (charge full price for packages active only a portion of the month)',
39                             'type' => 'checkbox',
40                           },
41
42     'display_separate_cust'=> { 'name' => 'Separate customer from package display on invoices',
43                                 'type' => 'checkbox',
44                               },
45
46     'cost_only' => { 'name' => 'Bill wholesale on cost only, disabling the price fallback',
47                      'type' => 'checkbox' 
48                    },
49
50   },
51
52   'fieldorder' => [qw( cutoff_day add_full_period no_pkg_prorate display_separate_cust cost_only) ],
53
54   'weight' => 52,
55
56 );
57
58 #some false laziness-ish w/bulk.pm...  not a lot
59 sub calc_recur {
60   my $self = shift;
61   my($cust_pkg, $sdate, $details, $param ) = @_;
62
63   my $last_bill = $cust_pkg->last_bill;
64
65   return sprintf("%.2f", $self->SUPER::calc_recur(@_) )
66     unless $$sdate > $last_bill;
67
68   my $conf = new FS::Conf;
69   my $money_char = $conf->config('money_char') || '$';
70   my $date_format = $conf->config('date_format') || '%m/%d/%Y';
71
72   my $total_agent_charge = 0;
73
74   warn "$me billing for agent packages from ". time2str('%x', $last_bill).
75                                        " to ". time2str('%x', $$sdate). "\n"
76     if $DEBUG;
77
78   my $prorate_ratio =   ( $$sdate                     - $last_bill )
79                       / ( $self->add_freq($last_bill) - $last_bill );
80
81   #almost always just one,
82   #unless you have multiple agents with same master customer0
83   my @agents = qsearch('agent', { 'agent_custnum' => $cust_pkg->custnum } );
84
85   foreach my $agent (@agents) {
86
87     warn "$me billing for agent ". $agent->agent. "\n"
88       if $DEBUG;
89
90     #not the most efficient to load them all into memory,
91     #but good enough for our current needs
92     my @cust_main = qsearch('cust_main', { 'agentnum' => $agent->agentnum } );
93
94     foreach my $cust_main (@cust_main) {
95
96       warn "$me billing agent charges for ". $cust_main->name_short. "\n"
97         if $DEBUG;
98
99       #make sure setup dates are filled in
100       my $error = $cust_main->bill( time => $$sdate );
101       die "Error pre-billing agent customer: $error" if $error;
102
103       my @cust_pkg = grep { my $setup  = $_->get('setup');
104                             my $cancel = $_->get('cancel');
105
106                             #$setup <= $$sdate  # ?
107                             $setup < $$sdate  # END
108                             && ( ! $cancel || $cancel > $last_bill ) #START
109                           }
110                      $cust_main->all_pkgs;
111
112       my $cust_details = 0;
113
114       foreach my $cust_pkg ( @cust_pkg ) {
115
116         warn "$me billing agent charges for pkgnum ". $cust_pkg->pkgnum. "\n"
117           if $DEBUG;
118
119         my $pkg_details = '';
120
121         my $cust_location = $cust_pkg->cust_location;
122         $pkg_details .= $cust_location->locationname. ': '
123           if $cust_location->locationname;
124
125         my $part_pkg = $cust_pkg->part_pkg;
126
127         # + something to identify package... primary service probably
128         # no... package def for now
129         $pkg_details .= $part_pkg->pkg. ': ';
130
131         my $pkg_charge = 0;
132
133         my $quantity = $cust_pkg->quantity || 1;
134
135         my $pkg_setup_fee  = $part_pkg->setup_cost;
136         $pkg_setup_fee ||= $part_pkg->option('setup_fee')
137           unless $self->option('cost_only');
138         $pkg_setup_fee ||= 0;
139
140         my $pkg_base_recur = $part_pkg->recur_cost;
141         $pkg_base_recur ||= $part_pkg->base_recur_permonth($cust_pkg)
142           unless $self->option('cost_only');
143         $pkg_base_recur ||= 0;
144
145         my $pkg_start = $cust_pkg->get('setup');
146         if ( $pkg_start < $last_bill ) {
147           $pkg_start = $last_bill;
148         } elsif ( $pkg_setup_fee ) {
149           $pkg_charge += $quantity * $pkg_setup_fee;
150           $pkg_details .= $money_char.
151                           sprintf('%.2f setup', $quantity * $pkg_setup_fee );
152           $pkg_details .= sprintf(" ($quantity \@ $money_char". '%.2f)',
153                                   $pkg_setup_fee )
154             if $quantity > 1;
155           $pkg_details .= ', ';
156         }
157
158         my $pkg_end = $cust_pkg->get('cancel');
159         $pkg_end = ( !$pkg_end || $pkg_end > $$sdate ) ? $$sdate : $pkg_end;
160
161         my $pkg_recur_charge = $prorate_ratio * $pkg_base_recur;
162         $pkg_recur_charge *= ( $pkg_end - $pkg_start )
163                            / ( $$sdate  - $last_bill )
164           unless $self->option('no_pkg_prorate');
165
166         my $recur_charge += $pkg_recur_charge;
167
168         if ( $recur_charge ) {
169           $pkg_details .= $money_char.
170                           sprintf('%.2f', $quantity * $recur_charge );
171           $pkg_details .= sprintf(" ($quantity \@ $money_char". '%.2f)',
172                                   $recur_charge )
173             if $quantity > 1;
174           $pkg_details .= ' ('.  time2str($date_format, $pkg_start).
175                           ' - '. time2str($date_format, $pkg_end  ). ')';
176         }
177
178         $pkg_charge += $quantity * $recur_charge;
179
180         if ( $pkg_charge ) {
181           if ( $self->option('display_separate_cust') ) {
182             push @$details, $cust_main->name.':' unless $cust_details++;
183             push @$details, '    '.$pkg_details;
184           } else {
185             push @$details, $cust_main->name_short.': '. $pkg_details;
186           }
187         };
188
189         $total_agent_charge += $pkg_charge;
190
191       } #foreach $cust_pkg
192
193       push @$details, ' ' if $cust_details;
194
195     } #foreach $cust_main
196
197   } #foreach $agent;
198
199   my $charges = $total_agent_charge + $self->SUPER::calc_recur(@_); #prorate
200
201   sprintf('%.2f', $charges );
202
203 }
204
205 sub can_discount { 0; }
206
207 sub hide_svc_detail {
208   1;
209 }
210
211 sub is_free {
212   0;
213 }
214
215 1;
216