backup the schema for tables we don't need the data from. RT#85959
[freeside.git] / FS / FS / part_pkg / agent_invoice.pm
1 package FS::part_pkg::agent_invoice;
2 use base qw(FS::part_pkg::recur_Common);
3
4 use strict;
5 use FS::Record qw( qsearch );
6 use FS::agent;
7 use FS::cust_main;
8 use FS::cust_bill_pkg_detail;
9 use Date::Format 'time2str';
10 use Text::CSV;
11
12 our $DEBUG = 0;
13
14 our $me = '[FS::part_pkg::agent_invoice]';
15
16 tie my %itemize_options, 'Tie::IxHash',
17   'cust_bill' => 'one line per invoice',
18   'cust_main' => 'one line per customer',
19   'agent'     => 'one line per agent',
20 ;
21
22 # use detail_formats for this?
23 my %itemize_header = (
24   'cust_bill' => '"Inv #","Customer","Date","Charge"',
25   'cust_main' => '"Cust #","Customer","Charge"',
26   'agent'     => '',
27 );
28
29 our %info = (
30   'name'      => 'Wholesale bulk billing based on actual invoice amounts, for master customers of an agent.',
31   'shortname' => 'Wholesale billing for agent (invoice amounts)',
32   'inherit_fields' => [qw( prorate_Mixin global_Mixin) ],
33   'fields' => {
34     'cutoff_day'    => { 'name' => 'Billing Day (1 - 28) for prorating or '.
35                                    'subscription',
36                          'default' => '1',
37                        },
38
39     'recur_method'  => { 'name' => 'Recurring fee method',
40                          #'type' => 'radio',
41                          #'options' => \%recur_method,
42                          'type' => 'select',
43                          'select_options' => \%FS::part_pkg::recur_Common::recur_method,
44                        },
45     'multiplier'    => { 'name' => 'Percentage of billed amount to charge' },
46     'itemize'       => { 'name' => 'Display on the wholesale invoice',
47                          'type' => 'select',
48                          'select_options' => \%itemize_options,
49                        },
50   },
51   'fieldorder' => [ qw( recur_method cutoff_day multiplier itemize ),
52                     FS::part_pkg::prorate_Mixin::fieldorder,
53                   ],
54
55   'weight' => 53,
56
57 );
58
59 #some false laziness-ish w/ the other agent plan
60 sub calc_recur {
61   my $self = shift;
62   my($cust_pkg, $sdate, $details, $param ) = @_;
63
64   my $csv = Text::CSV->new({ binary => 1 });
65
66   my $itemize = $self->option('itemize') || 'cust_bill';
67   my $last_bill = $cust_pkg->last_bill;
68
69   my $conf = new FS::Conf;
70 #  my $money_char = $conf->config('money_char') || '$';
71
72   warn "$me billing for agent packages from ". time2str('%x', $last_bill).
73                                        " to ". time2str('%x', $$sdate). "\n"
74     if $DEBUG;
75
76   # only invoices dated after $last_bill, but before or on $$sdate, will be
77   # included in this wholesale bundle.
78   # $last_bill is the last date the wholesale package was billed, unless
79   # it has never been billed before, in which case it's the current time.
80   # $$sdate is the date of the invoice we are now generating. It is one of:
81   # - the bill date we are now billing, if there is one.
82   # - or the wholesale package's setup date, if there is one
83   # - or the current time
84   # It will usually not be _after_ the current time. This can still happen
85   # if this package's bill date is later in the current day than right now,
86   # and next-bill-ignore-time is on.
87   my $date_range = " AND _date <= $$sdate";
88   if ( $last_bill ) {
89     $date_range .= " AND _date > $last_bill";
90   }
91
92   my $percent = $self->option('multiplier') || 100;
93
94   my $charged_cents = 0;
95   my $wholesale_cents = 0;
96   my $total = 0;
97
98   my @agents = qsearch('agent', { 'agent_custnum' => $cust_pkg->custnum } );
99
100   # The existing "agent" plan (based on package defined charges/costs) has to
101   # ensure that all the agent's customers are billed before calculating its
102   # fee, so that it can tell which packages were charged within the period.
103   # _This_ plan has to do it because it actually uses the invoices. Either way
104   # this behavior is not ideal, especially when using freeside-daily in 
105   # multiprocess mode, since it can lead to billing all of the agent's 
106   # customers within the master customer's billing job. If this becomes a
107   # problem, one option is to use "freeside-daily -a" to bill the agent's
108   # customers _first_, and master customers later.
109   foreach my $agent (@agents) {
110
111     warn "$me billing for agent ". $agent->agent. "\n"
112       if $DEBUG;
113
114     # cursor this if memory usage becomes a problem
115     my @cust_main = qsearch('cust_main', { 'agentnum' => $agent->agentnum } );
116
117     foreach my $cust_main (@cust_main) {
118
119       warn "$me billing agent charges for ". $cust_main->name_short. "\n"
120         if $DEBUG;
121
122       # this option at least should propagate, or we risk generating invoices
123       # in the apparent future and then leaving them out of this group
124       my $error = $cust_main->bill( 'time' => $$sdate );
125
126       die "Error pre-billing agent customer: $error" if $error;
127
128       my @cust_bill = qsearch({
129           table     => 'cust_bill',
130           hashref   => { 'custnum' => $cust_main->custnum },
131           extra_sql => $date_range,
132           order_by  => ' ORDER BY _date ASC',
133       });
134
135       foreach my $cust_bill (@cust_bill) {
136
137         # do we want the itemize setting to be purely cosmetic, or to actually
138         # change how the calculation is done? for now let's make it purely
139         # cosmetic, and round at the level of the individual invoice. can
140         # change this if needed.
141         $charged_cents += $cust_bill->charged * 100;
142         $wholesale_cents += sprintf('%.0f', $cust_bill->charged * $percent);
143
144         if ( $itemize eq 'cust_bill' ) {
145           $csv->combine(
146             $cust_bill->invnum,
147             $cust_main->name_short,
148             $cust_main->time2str_local('short', $cust_bill->_date),
149             sprintf('%.2f', $wholesale_cents / 100),
150           );
151           my $detail = FS::cust_bill_pkg_detail->new({
152               format    => 'C',
153               startdate => $cust_bill->_date,
154               amount    => sprintf('%.2f', $wholesale_cents / 100),
155               detail    => $csv->string,
156           });
157           push @$details, $detail;
158
159           $total += $wholesale_cents;
160           $charged_cents = $wholesale_cents = 0;
161         }
162
163       }
164
165       if ( $itemize eq 'cust_main' ) {
166
167         $csv->combine(
168           $cust_main->custnum,
169           $cust_main->name_short,
170           sprintf('%.2f', $wholesale_cents / 100),
171         );
172         my $detail = FS::cust_bill_pkg_detail->new({
173             format => 'C',
174             amount => sprintf('%.2f', $wholesale_cents / 100),
175             detail => $csv->string,
176         });
177         push @$details, $detail;
178
179         $total += $wholesale_cents;
180         $charged_cents = $wholesale_cents = 0;
181       }
182
183     } # foreach $cust_main
184
185     if ( $itemize eq 'agent' ) {
186       $csv->combine(
187         $cust_pkg->mt('[_1] customers', $agent->agent),
188         sprintf('%.2f', $wholesale_cents / 100),
189       );
190       my $detail = FS::cust_bill_pkg_detail->new({
191           format => 'C',
192           amount => sprintf('%.2f', $wholesale_cents / 100),
193           detail => $csv->string,
194       });
195       push @$details, $detail;
196
197       $total += $wholesale_cents;
198       $charged_cents = $wholesale_cents = 0;
199     }
200
201   } # foreach $agent
202
203   if ( @$details and $itemize_header{$itemize} ) {
204     unshift @$details, FS::cust_bill_pkg_detail->new({
205         format => 'C',
206         detail => $itemize_header{$itemize},
207     });
208   }
209
210   my $charges = ($total / 100) + $self->calc_recur_Common(@_);
211
212   sprintf('%.2f', $charges );
213
214 }
215
216 sub can_discount { 0; }
217
218 sub hide_svc_detail { 1; }
219
220 sub is_free { 0; }
221
222 sub can_usageprice { 0; }
223
224 1;
225