fix A/R report
[freeside.git] / httemplate / search / cust_bill_pkg_referral.html
1 <& elements/search.html,
2   'title'       => emt('Sales with advertising source'),
3   'name'        => emt('line items'),
4   'query'       => $query,
5   'count_query' => $count_query,
6   'count_addl'  => [ 
7                      ($setup ? $money_char. '%.2f setup' : ()),
8                      ($recur ? $money_char. '%.2f recurring' : ()),
9                      ($usage ? $money_char. '%.2f usage' : ()),
10                    ],
11   'header'      => [
12     emt('Description'),
13     ($setup ? emt('Setup') : ()),
14     ($recur ? emt('Recurring') : ()),
15     ($usage ? emt('Usage') : ()),
16     emt('Invoice'),
17     emt('Invoice date'),
18     emt('Paid'),
19     emt('Payment date'),
20     emt('Pkg. status'),
21     emt('Pkg. class'),
22     '', #report class
23     emt('Cust#'),
24     emt('Customer'),
25     emt('Ad source'),
26     emt('Agent'),
27   ],
28   'fields'      => [
29     'pkg',
30     ($setup ? money_sub('setup') : ()),
31     ($recur ? money_sub('recur_no_usage') : ()),
32     ($usage ? money_sub('recur_usage') : ()),
33     'invnum',
34     date_sub('_date'),
35     money_sub('paid'),
36     date_sub('last_pay'),
37     sub {
38       my $cust_pkg = shift->cust_pkg;
39       $cust_pkg ? ucfirst($cust_pkg->status) : '';
40     },
41     'classname',
42     sub { # report_option
43       my $cust_bill_pkg = shift;
44       my $pkgpart = $cust_bill_pkg->pkgpart_override;
45       unless ( $pkgpart ) {
46         my $cust_pkg = $cust_bill_pkg->cust_pkg or return '';
47         $pkgpart = $cust_pkg->pkgpart;
48       }
49       if ( !exists($report_classes{$pkgpart}) ) {
50         my $part_pkg = FS::part_pkg->by_key($pkgpart);
51         my %opts = $part_pkg->options;
52         $report_classes{$pkgpart} = [
53           map { /^report_option_(\d+)/ ? 
54                 $report_option_name{$1} :
55                 () }
56           keys %opts
57         ];
58       }
59       join( '<BR>', @{ $report_classes{$pkgpart} });
60     },
61     'custnum',
62     'name',
63     'referral', # from query
64     'agent',
65   ],
66   'sort_fields' => [
67     '',
68     ($setup ? 'setup' : ()),
69     ($recur ? 'recur_no_usage' : ()),
70     ($usage ? 'recur_usage' : ()),
71     'invnum',
72     '_date',
73     'paid',
74     'last_pay',
75     '', #package status
76     'classname',
77     '', #report_option
78     'custnum',
79     '',
80     'referral',
81     'agent',
82   ],
83   'links'       => [
84     '', #package/item desc
85     ('') x $x, #setup/recur/usage
86     $ilink, #invnum
87     $ilink, #invoice date
88     '', #paid amt
89     '', #payment date
90     '', #pkg status
91     '', #classnum
92     '', #report class
93     $clink, #custnum
94     $clink, #customer name
95     '', #referral
96     '', #agent
97   ],
98   #'align' => 'rlrrrc'.FS::UI::Web::cust_aligns(),
99   'align' => 'l' . ('r' x $x) . 'rcrccccrlll',
100   'color' => [ ('') x (5 + $x),
101                 sub {
102                   my $cust_pkg = shift->cust_pkg;
103                   $cust_pkg ? $cust_pkg->statuscolor : '';
104                 },
105                ('') x 6,
106              ],
107   'style' => [
108                ('') x (5 + $x),
109                'b',
110                ('') x 6
111              ],
112 &>
113 <%init>
114
115 die "access denied"
116   unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
117
118 my $conf = new FS::Conf;
119
120 my $setup = $cgi->param('setup') ? 1 : 0;
121 my $recur = $cgi->param('recur') ? 1 : 0;
122 my $usage = $cgi->param('usage') ? 1 : 0;
123
124 my $x = $setup + $recur + $usage;
125
126 my @select = ( 'cust_bill_pkg.*', 'cust_bill._date' );
127 my ($join_cust, $join_pkg ) = ('', '');
128
129 #here is the agent virtualization
130 my $agentnums_sql =
131   $FS::CurrentUser::CurrentUser->agentnums_sql( 'table' => 'cust_main' );
132
133 my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
134
135 my @where = ( $agentnums_sql,
136               # exclude taxes
137               '(cust_bill_pkg.pkgnum != 0 OR '.
138                 'cust_bill_pkg.feepart IS NOT NULL)',
139               "cust_bill._date >= $beginning",
140               "cust_bill._date <= $ending",
141             );
142
143 my @status_where;
144 foreach my $status ($cgi->param('status')) {
145   if ( $status =~ /^([- a-z]+)$/ ) { #"one-time charge"
146     push @status_where, "'$status'";
147   }
148 }
149 if ( @status_where ) {
150   push @where, '('. FS::cust_pkg->status_sql.
151     ') IN (' . join(',', @status_where) .')';
152 }
153
154 my @refnum;
155 foreach my $refnum ($cgi->param('refnum')) {
156   if ( $refnum =~ /^\d+$/ ) {
157     push @refnum, $refnum;
158   }
159 }
160 if ( @refnum ) {
161   push @where, 'cust_main.refnum IN ('.join(',', @refnum).')';
162 }
163
164 # cust_classnum (false laziness w/ elements/cust_main_dayranges.html, elements/cust_pay_or_refund.html, prepaid_income.html, cust_bill_pay.html, cust_bill_pkg.html, unearned_detail.html, cust_credit.html, cust_credit_refund.html, cust_main::Search::search_sql)
165 if ( grep { $_ eq 'cust_classnum' } $cgi->param ) {
166   my @classnums = grep /^\d*$/, $cgi->param('cust_classnum');
167   push @where, 'COALESCE( cust_main.classnum, 0) IN ( '.
168                    join(',', map { $_ || '0' } @classnums ).
169                ' )'
170     if @classnums;
171 }
172
173 if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
174   push @where, "cust_main.agentnum = $1";
175 }
176
177 #classnum
178 # not specified: all classes
179 # 0: empty class
180 # N: classnum
181 my $use_override = 1; #$cgi->param('use_override');
182 if ( $cgi->param('classnum') =~ /^(\d+)$/ ) {
183   my $comparison = '';
184   if ( $1 == 0 ) {
185     $comparison = "IS NULL";
186   } else {
187     $comparison = "= $1";
188   }
189
190   if ( $use_override ) {
191     push @where, "(
192       (part_pkg.classnum $comparison AND pkgpart_override IS NULL) OR
193       (override.classnum $comparison AND pkgpart_override IS NOT NULL) OR
194       (part_fee.classnum $comparison AND feepart IS NOT NULL)
195     )";
196   } else {
197     push @where, 
198     "(part_pkg.classnum $comparison) OR (part_fee.classnum $comparison)";
199   }
200 }
201
202 # report option
203 my @report_option = grep /^\d+$/, ( $cgi->param('report_option') );
204 if ( @report_option ) {
205   @report_option = map { "'report_option_$_'" } @report_option;
206   push @where, "EXISTS( 
207     SELECT 1 FROM part_pkg_option WHERE optionname IN (".
208     join(',', @report_option).") AND (
209       part_pkg_option.pkgpart = cust_pkg.pkgpart AND pkgpart_override IS NULL
210       OR part_pkg_option.pkgpart = pkgpart_override
211     )
212   )";
213 }
214
215 my $setup_sql =
216   FS::cust_bill_pkg->charged_sql('', '', setuprecur => 'setup');
217 my $recur_sql =
218   FS::cust_bill_pkg->charged_sql('', '', setuprecur => 'recur', no_usage => 1);
219 my $usage_sql = FS::cust_bill_pkg->usage_sql;
220
221 # exclude zero-amount items
222 my @orwhere;
223 push @orwhere, "(cust_bill_pkg.setup > 0)" if $setup;
224 push @orwhere, "($recur_sql > 0)"          if $recur;
225 push @orwhere, "($usage_sql > 0)"          if $usage;
226 push @where, '('.join(' OR ', @orwhere).')' if @orwhere;
227
228 $join_cust =  '        JOIN cust_bill     USING ( invnum )
229                   LEFT JOIN cust_main     USING ( custnum )
230                   LEFT JOIN part_referral USING ( refnum )
231                   LEFT JOIN agent ON cust_main.agentnum = agent.agentnum
232               ';
233
234 $join_pkg .=  ' LEFT JOIN cust_pkg USING ( pkgnum )
235                 LEFT JOIN part_pkg USING ( pkgpart )
236                 LEFT JOIN part_pkg AS override
237                   ON pkgpart_override = override.pkgpart 
238                 LEFT JOIN part_fee USING ( feepart )
239                 LEFT JOIN pkg_class ON '; #...
240
241 if ( $use_override ) {
242   # join to whichever pkgpart is appropriate
243   $join_pkg .= '
244       ( pkgpart_override IS NULL     AND part_pkg.classnum = pkg_class.classnum )
245    OR ( pkgpart_override IS NOT NULL AND override.classnum = pkg_class.classnum )
246    OR ( feepart IS NOT NULL AND part_fee.classnum = pkg_class.classnum )';
247 } else {
248   $join_pkg .= '
249       ( part_pkg.classnum = pkg_class.classnum )
250    OR ( part_fee.classnum = pkg_class.classnum )';
251 }
252
253 my $where = ' WHERE '. join(' AND ', @where);
254
255 # setup and recurring only
256 my $count_query = "SELECT 
257   COUNT(billpkgnum)".
258   ($setup ? ", SUM($setup_sql)" : '').
259   ($recur ? ", SUM($recur_sql)" : '').
260   ($usage ? ", SUM($usage_sql)" : '').
261   " FROM cust_bill_pkg
262   $join_cust
263   $join_pkg
264   $where
265   ";
266
267 my $paid_sql = FS::cust_bill_pkg->paid_sql('', '');
268 my $last_pay_sql = "SELECT MAX(_date)
269   FROM cust_bill_pay JOIN cust_bill_pay_pkg USING (billpaynum)
270   WHERE cust_bill_pay_pkg.billpkgnum = cust_bill_pkg.billpkgnum";
271
272 push @select, 'COALESCE(part_pkg.pkg, part_fee.itemdesc) AS pkg',
273               'part_pkg.freq',
274               'cust_main.custnum',
275               'cust_main.first',
276               'cust_main.last',
277               'cust_main.company',
278               'part_referral.referral',
279               "($paid_sql) AS paid",
280               "($last_pay_sql) AS last_pay",
281               "($recur_sql) AS recur_no_usage",
282               "($usage_sql) AS recur_usage",
283               'pkg_class.classname',
284               'agent.agent',
285               ;
286
287 my $query = {
288   'table'     => 'cust_bill_pkg',
289   'addl_from' => "$join_cust $join_pkg",
290   'hashref'   => {},
291   'select'    => join(",\n", @select ),
292   'extra_sql' => $where,
293   'order_by'  => 'ORDER BY cust_bill._date, billpkgnum',
294 };
295
296 my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ];
297 my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ];
298
299 my $conf = new FS::Conf;
300 my $money_char = $conf->config('money_char') || '$';
301
302 my %report_classes; #cache
303 my %report_option_name = 
304   map { $_->num => $_->name } qsearch('part_pkg_report_option', {});
305
306 # should this be in Mason.pm or something?
307 sub money_sub {
308   $conf ||= new FS::Conf;
309   $money_char ||= $conf->config('money_char') || '$';
310   my $field = shift;
311   sub {
312     $money_char . sprintf('%.2f', $_[0]->get($field));
313   };
314 }
315
316 sub date_sub {
317   my $field = shift;
318   sub {
319     my $value = $_[0]->get($field);
320     $value ? time2str('%b %d %Y', $value) : '';
321   };
322 }
323
324 </%init>