1 <& elements/search.html,
2 'title' => emt("Unearned revenue - ".ucfirst($unearned_mode)),
3 'name' => emt('line items'),
5 'count_query' => $count_query,
6 'count_addl' => [ $money_char. '%.2f total',
7 $money_char. '%.2f unearned revenue'
9 'header' => [ map( {emt $_}
11 'Unearned', # depends on mode
12 'Recurring charge', #recur - usage
13 'Owed', #recur - usage - credits - payments
15 'Payment date', #of last payment
16 'Credit date', #of last credit
22 FS::UI::Web::cust_header(),
26 sub { $_[0]->pkgnum > 0
27 ? $_[0]->get('pkg') # possibly use override.pkg
28 : $_[0]->get('itemdesc') # but i think this correct
31 money_sub('unearned_revenue'),
33 money_sub('recur_no_usage'),
35 money_sub('owed_no_usage'),
37 money_sub('paid_no_usage'),
41 date_sub('last_credit'),
50 \&FS::UI::Web::cust_fields,
54 # SQL expressions work as sort keys...
70 ( map { $_ ne 'Cust. Status' ? $clink : '' }
71 FS::UI::Web::cust_header()
74 'align' => 'lrrcrccrc'. FS::UI::Web::cust_aligns(),
77 FS::UI::Web::cust_colors(),
81 FS::UI::Web::cust_styles(),
86 # Separated from cust_bill_pkg.cgi to simplify things.
89 unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
91 my $conf = new FS::Conf;
94 my $unearned_mode = '';
95 my $unearned_base = '';
96 my $unearned_sql = '';
98 my @select = ( 'cust_bill_pkg.*', 'cust_bill._date' );
99 my ($join_cust, $join_pkg ) = ('', '');
101 #here is the agent virtualization
103 $FS::CurrentUser::CurrentUser->agentnums_sql( 'table' => 'cust_main' );
105 my @where = ( $agentnums_sql );
107 my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
109 if ( $cgi->param('status') =~ /^([a-z]+)$/ ) {
110 push @where, FS::cust_main->cust_status_sql . " = '$1'";
113 push @where, "cust_bill._date >= $beginning",
114 "cust_bill._date <= $ending";
116 if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
117 push @where, "cust_main.agentnum = $1";
120 # no pkgclass, no taxclass, no tax location...
122 # unearned revenue mode
123 $cgi->param('date') =~ /^(\d+)$/
124 or die "date required";
127 $unearned_mode = $cgi->param('mode');
129 push @where, "cust_bill_pkg.sdate < $unearned",
130 "cust_bill_pkg.edate > $unearned",
131 "cust_bill_pkg.recur != 0",
132 "part_pkg.freq != '0'";
134 if ( !$cgi->param('include_monthly') ) {
136 "part_pkg.freq != '1'",
137 "part_pkg.freq NOT LIKE '%h'",
138 "part_pkg.freq NOT LIKE '%d'",
139 "part_pkg.freq NOT LIKE '%w'";
143 $unearned, #before this date
145 setuprecur => 'recur',
149 my $charged = FS::cust_bill_pkg->charged_sql(@opt);
150 push @select, "($charged) AS recur_no_usage";
152 my $owed_sql = FS::cust_bill_pkg->owed_sql(@opt);
153 push @select, "($owed_sql) AS owed_no_usage";
155 my $paid_sql = FS::cust_bill_pkg->paid_sql(@opt);
156 push @select, "$paid_sql AS paid_no_usage";
158 if ( $unearned_mode eq 'paid' ) {
159 # then use the amount paid, minus usage charges
160 $unearned_base = $paid_sql;
163 # use the amount billed, minus usage charges and credits
164 $unearned_base = "( $charged - " .
165 FS::cust_bill_pkg->credited_sql(@opt) . ' )';
167 # whatever we're using as the base, only show rows where it's positive
168 push @where, "$unearned_base > 0";
170 my $period = "CAST(cust_bill_pkg.edate - cust_bill_pkg.sdate AS REAL)";
171 my $elapsed = "GREATEST( $unearned - cust_bill_pkg.sdate, 0 )";
172 my $remaining = "(1 - $elapsed/$period)";
174 $unearned_sql = "CAST( $unearned_base * $remaining AS DECIMAL(10,2) )";
175 push @select, "$unearned_sql AS unearned_revenue";
177 # last payment/credit date
178 my %t = (pay => 'cust_bill_pay', credit => 'cust_credit_bill');
179 foreach my $x (qw(pay credit)) {
181 my $link = $table.'_pkg';
182 my $pkey = dbdef->table($table)->primary_key;
183 my $last_date_sql = "SELECT MAX(_date)
184 FROM $table JOIN $link USING ($pkey)
185 WHERE $link.billpkgnum = cust_bill_pkg.billpkgnum
186 AND $table._date <= $unearned";
187 push @select, "($last_date_sql) AS last_$x";
191 #no tax report group kludge
193 #usage always excluded
195 # always 'nottax', not 'istax'
196 $join_cust = ' JOIN cust_bill USING ( invnum )
197 LEFT JOIN cust_main USING ( custnum ) ';
199 $join_pkg .= ' LEFT JOIN cust_pkg USING ( pkgnum )
200 LEFT JOIN part_pkg USING ( pkgpart )
201 LEFT JOIN part_pkg AS override
202 ON pkgpart_override = override.pkgpart ';
204 my $where = ' WHERE '. join(' AND ', @where);
206 my $count_query = "SELECT COUNT(DISTINCT billpkgnum),
207 SUM( $unearned_base ), SUM( $unearned_sql )
208 FROM cust_bill_pkg $join_cust $join_pkg $where";
210 push @select, 'part_pkg.pkg',
213 FS::UI::Web::cust_sql_fields();
216 'table' => 'cust_bill_pkg',
217 'addl_from' => "$join_cust $join_pkg",
219 'select' => join(",\n", @select ),
220 'extra_sql' => $where,
221 'order_by' => 'ORDER BY cust_bill._date, billpkgnum',
224 my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ];
225 my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ];
230 $conf ||= new FS::Conf;
231 $money_char ||= $conf->config('money_char') || '$';
234 $money_char . sprintf('%.2f', $_[0]->get($field));
241 my $value = $_[0]->get($field);
242 $value ? time2str('%b %d %Y', $value) : '';