Wholesale CDR cost re-billing, RT#27555
[freeside.git] / FS / FS / part_pkg / agent_cdr.pm
1 package FS::part_pkg::agent_cdr;
2 use base qw( FS::part_pkg::recur_Common );
3
4 #kind of glommed together from cdr_termination, agent, voip_cdr
5 # some false laziness w/ all of them
6
7 use strict;
8 use vars qw( $DEBUG $me %info );
9 use FS::Record qw( qsearch );
10 use FS::PagedSearch qw( psearch );
11 use FS::agent;
12 use FS::cust_main;
13 use FS::cdr;
14
15 $DEBUG = 0;
16
17 $me = '[FS::part_pkg::agent_cdr]';
18
19 %info = (
20   'name'      => 'Wholesale CDR cost billing, for master customers of an agent.',
21   'shortname' => 'Whilesale CDR cost billing for agent.',
22   'inherit_fields' => [ 'prorate_Mixin', 'global_Mixin' ],
23   'fields' => { #false laziness w/cdr_termination
24
25     #false laziness w/flat.pm
26     'recur_temporality' => { 'name' => 'Charge recurring fee for period',
27                              'type' => 'select',
28                              'select_options' => \%temporalities,
29                            },
30
31     'cutoff_day'    => { 'name' => 'Billing Day (1 - 28) for prorating or '.
32                                    'subscription',
33                          'default' => '1',
34                        },
35     'recur_method'  => { 'name' => 'Recurring fee method',
36                          #'type' => 'radio',
37                          #'options' => \%recur_method,
38                          'type' => 'select',
39                          'select_options' => \%FS::part_pkg::recur_Common::recur_method,
40                        },
41
42     #false laziness w/voip_cdr.pm
43     'output_format' => { 'name' => 'CDR invoice display format',
44                          'type' => 'select',
45                          'select_options' => { FS::cdr::invoice_formats() },
46                          'default'        => 'simple2', #XXX test
47                        },
48
49     'usage_section' => { 'name' => 'Section in which to place separate usage charges',
50                        },
51
52     'summarize_usage' => { 'name' => 'Include usage summary with recurring charges when usage is in separate section',
53                           'type' => 'checkbox',
54                         },
55
56     'usage_mandate' => { 'name' => 'Always put usage details in separate section',
57                           'type' => 'checkbox',
58                        },
59     #eofalse
60
61   },
62
63   'fieldorder' => [ qw( recur_temporality recur_method cutoff_day ),
64                     FS::part_pkg::prorate_Mixin::fieldorder, 
65                     qw(
66                        output_format usage_section summarize_usage usage_mandate
67                     )
68                   ],
69
70   'weight' => 53,
71
72 );
73
74 sub calc_recur {
75   my $self, $cust_pkg, $sdate, $details, $param ) = @_;
76
77   #my $last_bill = $cust_pkg->last_bill;
78   my $last_bill = $cust_pkg->get('last_bill'); #->last_bill falls back to setup
79
80   return 0
81     if $self->recur_temporality eq 'preceding'
82     && ( $last_bill eq '' || $last_bill == 0 );
83
84   my $charges = 0;
85
86   #CDR calculations
87
88   #false laziness w/agent.pm
89   #almost always just one,
90   #unless you have multiple agents with same master customer0
91   my @agents = qsearch('agent', { 'agent_custnum' => $cust_pkg->custnum } );
92
93   foreach my $agent (@agents) {
94
95     warn "$me billing wholesale CDRs for agent ". $agent->agent. "\n"
96       if $DEBUG;
97
98     #not the most efficient to load them all into memory,
99     #but good enough for our current needs
100     my @cust_main = qsearch('cust_main', { 'agentnum' => $agent->agentnum } );
101
102     foreach my $cust_main (@cust_main) {
103
104       warn "$me billing agent wholesale CDRs for ". $cust_main->name_short. "\n"
105         if $DEBUG;
106
107       #eofalse laziness w/agent.pm
108
109       my @svcnum = ();
110       foreach my $cust_pkg ( $cust_main->cust_pkg ) {
111         push @svcnum, map $_->svcnum, $cust_pkg->cust_svc( svcdb=>'svc_phone' );
112       }
113
114       next unless @svcnum;
115
116       #false laziness w/cdr_termination
117
118       my $termpart = 1; #or from an option -- we're not termination, we're wholesale?  for now, use one or the other
119
120       #false lazienss w/search/cdr.html (i should be a part_termination method)
121       my $where_term =
122         "( cdr.acctid = cdr_termination.acctid AND termpart = $termpart ) ";
123       #my $join_term = "LEFT JOIN cdr_termination ON ( $where_term )";
124       my $extra_sql =
125         "AND NOT EXISTS ( SELECT 1 FROM cdr_termination WHERE $where_term )";
126
127       #eofalse laziness w/cdr_termination.pm
128
129       #false laziness w/ svc_phone->psearch_cdrs, kinda
130       my $cdr_search = psearch({
131         'table'     => 'cdr',
132         #'addl_from' => $join_term,
133         'hashref'   => {},
134         'extra_sql' => " WHERE freesidestatus IN ( 'rated', 'done' ) ".
135                        "   AND svcnum IN (". join(',', @svcnum). ") ".
136                        $extra_sql,
137         'order_by'  => 'ORDER BY startdate FOR UPDATE ',
138
139       });
140
141       #false laziness w/voip_cdr
142       $cdr_search->limit(1000);
143       $cdr_search->increment(0); #because we're adding cdr_termination as we go?
144       while ( my $cdr = $cdr_search->fetch ) {
145
146         my $cost = $cdr->rate_cost;
147         #XXX exception handling?  return undef? (and err?) ref to a scalar err?
148
149         #false laziness w/cdr_termination
150
151         #add a cdr_termination record and the charges
152
153         my $cdr_termination = new FS::cdr_termination {
154           'acctid'      => $cdr->acctid,
155           'termpart'    => $termpart,
156           'rated_price' => $cost,
157           'status'      => 'done',
158         };
159
160         my $error = $cdr_termination->insert;
161         die $error if $error; #next if $error; #or just skip this one???  why?
162
163         $charges += $cost;
164
165         # and add a line to the invoice
166
167         my $call_details = $cdr->downstream_csv( 'format' => $output_format,
168                                                  'charge' => $cost,
169                                                );
170         my $classnum = ''; #usage class?
171
172        #option to turn off?  or just use squelch_cdr for the customer probably
173         push @$details, [ 'C', $call_details, $cost, $classnum ];
174
175         #eofalse laziness w/cdr_termination
176
177       }
178
179     }
180
181   }
182
183   #eo CDR calculations
184
185   $charges += ($cust_pkg->quantity || 1) * $self->calc_recur_Common(@_);
186
187   $charges;
188 }
189
190 sub can_discount { 0; }
191
192 #?  sub hide_svc_detail { 1; }
193
194 sub is_free { 0; }
195
196 sub can_usageprice { 0; }
197
198 1;