1 <& elements/search.html,
2 'title' => emt('Line items'),
3 'name' => emt('line items'),
5 'count_query' => $count_query,
6 'count_addl' => \@total_desc,
17 FS::UI::Web::cust_header(),
21 sub { $_[0]->pkgnum > 0
22 ? $_[0]->get('pkgpart')
25 'itemdesc', # is part_pkg.pkg if applicable
27 #strikethrough or "N/A ($amount)" or something these when
28 # they're not applicable to pkg_tax search
31 sub { time2str('%b %d %Y', shift->_date ) },
32 sub { sprintf($money_char.'%.2f', shift->get('pay_amount')) },
33 sub { sprintf($money_char.'%.2f', shift->get('credit_amount')) },
34 \&FS::UI::Web::cust_fields,
46 FS::UI::Web::cust_sort_fields(),
58 ( map { $_ ne 'Cust. Status' ? $clink : '' }
59 FS::UI::Web::cust_header()
62 #'align' => 'rlrrrc'.FS::UI::Web::cust_aligns(),
63 'align' => $pkgnum_align.
68 FS::UI::Web::cust_aligns(),
79 FS::UI::Web::cust_colors(),
91 FS::UI::Web::cust_styles(),
96 Output control parameters:
97 - distribute: Boolean. If true, recurring fees will be "prorated" for the
98 portion of the package date range (sdate-edate) that falls within the date
99 range of the report. Line items will be limited to those for which this
100 portion is > 0. This disables filtering on invoice date.
102 - usage: Separate usage (cust_bill_pkg_detail records) from
103 recurring charges. If set to "usage", will show usage instead of
104 recurring charges. If set to "recurring", will deduct usage and only
105 show the flat rate charge. If not passed, the "recurring charge" column
106 will include usage charges also.
108 Filtering parameters:
109 - begin, end: Date range. Applies to invoice date, not necessarily package
110 date range. But see "distribute".
112 - status: Customer status (active, suspended, etc.). This will filter on
113 _current_ customer status, not status at the time the invoice was generated.
115 - agentnum: Filter on customer agent.
117 - refnum: Filter on customer reference source.
119 - cust_classnum: Filter on customer class.
121 - classnum: Filter on package class.
123 - report_optionnum: Filter on package report class. Can be a single report
124 class number or a comma-separated list (where 0 is "no report class"), or the
127 - use_override: Apply "classnum" and "taxclass" filtering based on the
128 override (bundle) pkgpart, rather than always using the true pkgpart.
130 - nottax: Limit to items that are not taxes (pkgnum > 0 or feepart > 0).
132 - istax: Limit to items that are taxes (pkgnum == 0 and feepart = null).
134 - taxnum: Limit to items whose tax definition matches this taxnum.
135 With "nottax" that means items that are subject to that tax;
136 with "istax" it's the tax charges themselves. Can be specified
137 more than once to include multiple taxes.
139 - country, state, county, city: Limit to items whose tax location
140 matches these fields. If "nottax" it's the tax location of the package;
141 if "istax" the location of the tax.
143 - taxname, taxnameNULL: With "nottax", limit to items whose tax location
144 matches a tax with this name. With "istax", limit to items that have
145 this tax name. taxnameNULL is equivalent to "taxname = '' OR taxname
148 - out: With "nottax", limit to items that don't match any tax definition.
149 With "istax", find tax items that are unlinked to their tax definitions.
150 Current Freeside (> July 2012) always creates tax links, but unlinked
151 items may result from an incomplete upgrade of legacy data.
153 - locationtaxid: With "nottax", limit to packages matching this
154 tax_rate_location ID; with "tax", limit to taxes generated from that
157 - taxclass: Filter on package taxclass.
159 - taxclassNULL: With "nottax", limit to items that would be subject to the
160 tax with taxclass = NULL. This doesn't necessarily mean part_pkg.taxclass
161 is NULL; it also includes taxclasses that don't have a tax in this region.
163 - itemdesc: Limit to line items with this description. Note that non-tax
164 packages usually have a description of NULL. (Deprecated.)
166 - report_group: Can contain '=' or '!=' followed by a string to limit to
167 line items where itemdesc starts with, or doesn't start with, the string.
169 - cust_tax: Limit to customers who are tax-exempt. If "taxname" is also
170 specified, limit to customers who are also specifically exempt from that
173 - pkg_tax: Limit to packages that are tax-exempt, and only include the
174 exempt portion (setup, recurring, or both) when calculating totals.
176 - taxable: Limit to packages that are subject to tax, i.e. where a
177 cust_bill_pkg_tax_location record exists.
179 - credit: Limit to line items that received a credit application. The
180 amount of the credit will also be shown.
185 my $curuser = $FS::CurrentUser::CurrentUser;
187 die "access denied" unless $curuser->access_right('Financial reports');
189 my $conf = new FS::Conf;
190 my $money_char = $conf->config('money_char') || '$';
192 my @select = ( 'cust_bill_pkg.*', 'cust_bill._date' );
193 my @total = ( 'COUNT(*)', 'SUM(cust_bill_pkg.setup + cust_bill_pkg.recur)');
194 my @total_desc = ( $money_char.'%.2f total' ); # sprintf strings
196 my @peritem = ( 'setup', 'recur' );
197 my @peritem_desc = ( 'Setup charge', 'Recurring charge' );
199 my @pkgnum_header = ();
202 my $pkgnum_align = '';
203 if ( $curuser->option('show_pkgnum') ) {
204 push @select, 'cust_bill_pkg.pkgnum';
205 push @pkgnum_header, 'Pkg Num';
206 push @pkgnum, sub { $_[0]->pkgnum > 0 ? $_[0]->pkgnum : '' };
207 push @pkgnum_null, '';
208 $pkgnum_align .= 'r';
211 my @post_desc_header = ();
213 my @post_desc_null = ();
214 my $post_desc_align = '';
215 if ( $conf->exists('enable_taxclasses') ) {
216 push @post_desc_header, 'Tax class';
217 push @post_desc, 'taxclass';
218 push @post_desc_null, '';
219 $post_desc_align .= 'l';
222 # used in several places
223 my $itemdesc = 'COALESCE(part_fee.itemdesc, part_pkg.pkg, cust_bill_pkg.itemdesc)';
225 # valid in both the tax and non-tax cases
227 " LEFT JOIN cust_bill ON (cust_bill_pkg.invnum = cust_bill.invnum)".
228 # use cust_pkg.locationnum if it exists
229 FS::UI::Web::join_cust_main('cust_bill', 'cust_pkg');
231 #agent virtualization
233 $FS::CurrentUser::CurrentUser->agentnums_sql( 'table' => 'cust_main' );
235 my @where = ( $agentnums_sql );
238 my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
240 if ( $cgi->param('distribute') == 1 ) {
241 push @where, "sdate <= $ending",
242 "edate > $beginning",
245 push @where, "cust_bill._date >= $beginning",
246 "cust_bill._date <= $ending";
250 if ( $cgi->param('status') =~ /^([a-z]+)$/ ) {
251 push @where, FS::cust_main->cust_status_sql . " = '$1'";
255 if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
256 push @where, "cust_main.agentnum = $1";
259 # salesnum--see below
261 if ( $cgi->param('refnum') =~ /^(\d+)$/ ) {
262 push @where, "cust_main.refnum = $1";
265 # 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_referral.html, unearned_detail.html, cust_credit.html, cust_credit_refund.html, cust_main::Search::search_sql)
266 if ( grep { $_ eq 'cust_classnum' } $cgi->param ) {
267 my @classnums = grep /^\d*$/, $cgi->param('cust_classnum');
268 push @where, 'COALESCE( cust_main.classnum, 0) IN ( '.
269 join(',', map { $_ || '0' } @classnums ).
276 if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
277 push @where, "cust_main.custnum = $1";
280 # we want the package and its definition if available
282 ' LEFT JOIN cust_pkg USING (pkgnum)
283 LEFT JOIN part_pkg USING (pkgpart)
284 LEFT JOIN part_fee USING (feepart)';
286 my $part_pkg = 'part_pkg';
287 # "Separate sub-packages from parents"
288 my $use_override = $cgi->param('use_override') ? 1 : 0;
289 if ( $use_override ) {
290 # still need the real part_pkg for tax applicability,
292 $join_pkg .= " LEFT JOIN part_pkg AS override ON (
293 COALESCE(cust_bill_pkg.pkgpart_override, cust_pkg.pkgpart, 0) = override.pkgpart
295 $part_pkg = 'override';
297 push @select, "$part_pkg.pkgpart", "$part_pkg.pkg";
298 push @select, "COALESCE($part_pkg.taxclass, part_fee.taxclass) AS taxclass"
299 if $conf->exists('enable_taxclasses');
302 if ( $cgi->param('nottax') ) {
304 push @select, "($itemdesc) AS itemdesc";
307 '(cust_bill_pkg.pkgnum > 0 OR cust_bill_pkg.feepart IS NOT NULL)';
309 my @tax_where; # will go into a subquery
310 my @exempt_where; # will also go into a subquery
312 # classnum (of override pkgpart if applicable)
313 # not specified: all classes
316 if ( grep { $_ eq 'classnum' } $cgi->param ) {
317 my @classnums = grep /^\d+$/, $cgi->param('classnum');
318 push @where, "COALESCE(part_fee.classnum, $part_pkg.classnum, 0) IN ( ".
319 join(',', @classnums ).
324 if ( grep { $_ eq 'report_optionnum' } $cgi->param ) {
325 my $num = join(',', grep /^[\d,]+$/, $cgi->param('report_optionnum'));
326 my $not_num = join(',', grep /^[\d,]+$/, $cgi->param('not_report_optionnum'));
327 my $all = $cgi->param('all_report_options') ? 1 : 0;
328 push @where, # code reuse FTW
329 FS::Report::Table->with_report_option(
330 report_optionnum => $num,
331 not_report_optionnum => $not_num,
332 use_override => $use_override,
333 all_report_options => $all,
338 if ( $cgi->param('taxclassNULL') ) {
339 # a little different from 'taxclass' in that it applies to the
340 # effective taxclass, not the real one
341 push @tax_where, 'cust_main_county.taxclass IS NULL'
342 } elsif ( $cgi->param('taxclass') ) {
343 push @tax_where, "COALESCE(part_fee.taxclass, $part_pkg.taxclass) IN (" .
344 join(', ', map {dbh->quote($_)} $cgi->param('taxclass') ).
348 if ( $cgi->param('exempt_cust') eq 'Y' ) {
349 # tax-exempt customers
350 push @exempt_where, "(exempt_cust = 'Y' OR exempt_cust_taxname = 'Y')";
352 } elsif ( $cgi->param('exempt_pkg') eq 'Y' ) { # non-taxable package
353 # non-taxable package charges
354 push @exempt_where, "(exempt_setup = 'Y' OR exempt_recur = 'Y')";
356 # we don't handle exempt_monthly here
358 if ( $cgi->param('taxname') ) { # specific taxname
359 push @tax_where, 'cust_main_county.taxname = '.
360 dbh->quote($cgi->param('taxname'));
361 } elsif ( $cgi->param('taxnameNULL') ) {
362 push @tax_where, 'cust_main_county.taxname IS NULL OR '.
363 'cust_main_county.taxname = \'Tax\'';
366 # country:state:county:city:district (may be repeated)
367 # You can also pass a big list of taxnums but that leads to huge URLs.
368 # Note that this means "packages whose tax is in this region", not
369 # "packages in this region". It's meant for links from the tax report.
370 if ( $cgi->param('region') ) {
372 foreach ( $cgi->param('region') ) {
374 @loc{qw(country state county city district)} =
375 split(':', $cgi->param('region'));
376 my $string = join(' AND ',
379 "$_ = ".dbh->quote($loc{$_});
385 push @orwhere, "($string)";
387 push @tax_where, '(' . join(' OR ', @orwhere) . ')' if @orwhere;
391 if ( $cgi->param('taxnum') =~ /^([0-9,]+)$/ ) {
392 push @tax_where, "cust_main_county.taxnum IN ($1)";
395 # If we're showing exempt items, we need to find those with
396 # cust_tax_exempt_pkg records matching the selected taxes.
397 # If we're showing taxable items, we need to find those with
398 # cust_bill_pkg_tax_location records. We also need to find the
399 # exemption records so that we can show the taxable amount.
400 # If we're showing all items, we need the union of those.
401 # If we're showing 'out' (items that aren't region/class taxable),
402 # then we need the set of all items minus the union of those.
406 if ( @exempt_where or @tax_where
407 or $cgi->param('taxable') or $cgi->param('out') )
409 # process exemption restrictions, including @tax_where
410 my $exempt_sub = 'SELECT SUM(amount) as exempt_amount, billpkgnum
411 FROM cust_tax_exempt_pkg JOIN cust_main_county USING (taxnum)';
413 $exempt_sub .= ' WHERE '.join(' AND ', @tax_where, @exempt_where)
414 if (@tax_where or @exempt_where);
416 $exempt_sub .= ' GROUP BY billpkgnum';
418 $join_pkg .= " LEFT JOIN ($exempt_sub) AS item_exempt
422 if ( @tax_where or $cgi->param('taxable') or $cgi->param('out') ) {
423 # process tax restrictions
425 'cust_main_county.tax > 0';
427 my $tax_sub = "SELECT invnum, cust_bill_pkg_tax_location.pkgnum
428 FROM cust_bill_pkg_tax_location
429 JOIN cust_bill_pkg AS tax_item USING (billpkgnum)
430 JOIN cust_main_county USING (taxnum)
431 WHERE ". join(' AND ', @tax_where).
432 " GROUP BY invnum, cust_bill_pkg_tax_location.pkgnum";
434 $join_pkg .= " LEFT JOIN ($tax_sub) AS item_tax
435 ON (item_tax.invnum = cust_bill_pkg.invnum AND
436 item_tax.pkgnum = cust_bill_pkg.pkgnum)";
439 # now do something with that
440 if ( @exempt_where ) {
442 push @where, 'item_exempt.billpkgnum IS NOT NULL';
443 push @select, 'item_exempt.exempt_amount';
444 push @peritem, 'exempt_amount';
445 push @peritem_desc, 'Exempt';
446 push @total, 'SUM(exempt_amount)';
447 push @total_desc, "$money_char%.2f tax-exempt";
449 } elsif ( $cgi->param('taxable') ) {
451 my $taxable = 'cust_bill_pkg.setup + cust_bill_pkg.recur '.
452 '- COALESCE(item_exempt.exempt_amount, 0)';
454 push @where, 'item_tax.invnum IS NOT NULL';
455 push @select, "($taxable) AS taxable_amount";
456 push @peritem, 'taxable_amount';
457 push @peritem_desc, 'Taxable';
458 push @total, "SUM($taxable)";
459 push @total_desc, "$money_char%.2f taxable";
461 } elsif ( $cgi->param('out') ) {
463 push @where, 'item_tax.invnum IS NULL',
464 'item_exempt.billpkgnum IS NULL';
466 } elsif ( @tax_where ) {
468 # union of taxable + all exempt_ cases
470 '(item_tax.invnum IS NOT NULL OR item_exempt.billpkgnum IS NOT NULL)';
474 # recur/usage separation
475 if ( $cgi->param('usage') eq 'recurring' ) {
477 my $recur_no_usage = FS::cust_bill_pkg->charged_sql('', '', no_usage => 1);
478 push @select, "($recur_no_usage) AS recur_no_usage";
479 $peritem[1] = 'recur_no_usage';
480 $total[1] = "SUM(cust_bill_pkg.setup + $recur_no_usage)";
481 $total_desc[0] .= ' (excluding usage)';
483 } elsif ( $cgi->param('usage') eq 'usage' ) {
485 my $usage = FS::cust_bill_pkg->usage_sql();
486 push @select, "($usage) AS _usage";
487 # there's already a method named 'usage'
488 $peritem[1] = '_usage';
489 $peritem_desc[1] = 'Usage charge';
490 $total[1] = "SUM($usage)";
491 $total_desc[0] .= ' usage charges';
494 } elsif ( $cgi->param('istax') ) {
496 @peritem = ( 'setup' ); # taxes only have setup
497 @peritem_desc = ( 'Tax charge' );
499 push @where, 'cust_bill_pkg.pkgnum = 0';
501 # tax location when using tax_rate_location
502 if ( $cgi->param('vendortax') ) {
504 $join_pkg .= ' LEFT JOIN cust_bill_pkg_tax_rate_location USING ( billpkgnum ) '.
505 ' LEFT JOIN tax_rate_location USING ( taxratelocationnum )';
506 foreach (qw( state county city locationtaxid)) {
507 if ( scalar($cgi->param($_)) ) {
508 my $place = dbh->quote( $cgi->param($_) );
509 push @where, "tax_rate_location.$_ = $place";
514 COALESCE(cust_bill_pkg_tax_rate_location.amount,
515 cust_bill_pkg.setup + cust_bill_pkg.recur)
518 } elsif ( $cgi->param('out') ) {
521 LEFT JOIN cust_bill_pkg_tax_location USING (billpkgnum)
523 push @where, 'cust_bill_pkg_tax_location.billpkgnum IS NULL';
525 # each billpkgnum should appear only once
526 $total[0] = 'COUNT(*)';
527 $total[1] = 'SUM(cust_bill_pkg.setup)';
529 } else { # not locationtaxid or 'out'--the normal case
532 LEFT JOIN cust_bill_pkg_tax_location USING (billpkgnum)
533 JOIN cust_main_county USING (taxnum)
536 # don't double-count the components of consolidated taxes
537 $total[0] = 'COUNT(DISTINCT cust_bill_pkg.billpkgnum)';
538 $total[1] = 'SUM(cust_bill_pkg_tax_location.amount)';
542 if ( $cgi->param('taxclassNULL') ) {
543 push @where, 'cust_main_county.taxclass IS NULL';
547 if ( $cgi->param('taxnameNULL') ) {
548 push @where, 'cust_main_county.taxname IS NULL OR '.
549 'cust_main_county.taxname = \'Tax\'';
550 } elsif ( $cgi->param('taxname') ) {
551 push @where, 'cust_main_county.taxname = '.
552 dbh->quote($cgi->param('taxname'));
556 if ( $cgi->param('taxnum') =~ /^([0-9,]+)$/ ) {
557 push @where, "cust_main_county.taxnum IN ($1)";
560 # report group (itemdesc)
561 if ( $cgi->param('report_group') =~ /^(=|!=) (.*)$/ ) {
562 my ( $group_op, $group_value ) = ( $1, $2 );
563 if ( $group_op eq '=' ) {
564 #push @where, 'itemdesc LIKE '. dbh->quote($group_value.'%');
565 push @where, 'itemdesc = '. dbh->quote($group_value);
566 } elsif ( $group_op eq '!=' ) {
567 push @where, '( itemdesc != '. dbh->quote($group_value) .' OR itemdesc IS NULL )';
569 die "guru meditation #00de: group_op $group_op\n";
573 # itemdesc, for breakdown from the vendor tax report
574 if ( $cgi->param('itemdesc') ) {
575 if ( $cgi->param('itemdesc') eq 'Tax' ) {
576 push @where, "($itemdesc = 'Tax' OR $itemdesc is null)";
578 push @where, "$itemdesc = ". dbh->quote($cgi->param('itemdesc'));
586 my $pay_sub = "SELECT SUM(cust_bill_pay_pkg.amount)
587 FROM cust_bill_pay_pkg
588 WHERE cust_bill_pkg.billpkgnum = cust_bill_pay_pkg.billpkgnum
590 push @select, "($pay_sub) AS pay_amount";
594 if ( $cgi->param('credit') ) {
598 my($cr_begin, $cr_end) = FS::UI::Web::parse_beginning_ending($cgi, 'credit');
599 $credit_where = "WHERE cust_credit_bill._date >= $cr_begin " .
600 "AND cust_credit_bill._date <= $cr_end";
604 if ( $cgi->param('istax') ) {
605 # then we need to group/join by billpkgtaxlocationnum, to get only the
606 # relevant part of partial taxes
607 my $credit_sub = "SELECT SUM(cust_credit_bill_pkg.amount) AS credit_amount,
608 reason.reason as reason_text, access_user.username AS username_text,
609 billpkgtaxlocationnum, billpkgnum
610 FROM cust_credit_bill_pkg
611 JOIN cust_credit_bill USING (creditbillnum)
612 JOIN cust_credit USING (crednum)
613 LEFT JOIN reason USING (reasonnum)
614 LEFT JOIN access_user USING (usernum)
616 GROUP BY billpkgnum, billpkgtaxlocationnum, reason.reason,
617 access_user.username";
619 if ( $cgi->param('out') ) {
621 # find credits that are applied to the line items, but not to
622 # a cust_bill_pkg_tax_location link
623 $join_pkg .= " LEFT JOIN ($credit_sub) AS item_credit
625 push @where, 'item_credit.billpkgtaxlocationnum IS NULL';
629 # find credits that are applied to the CBPTL links that are
630 # considered "interesting" by the report criteria
631 $join_pkg .= " LEFT JOIN ($credit_sub) AS item_credit
632 USING (billpkgtaxlocationnum)";
637 # then only group by billpkgnum
638 my $credit_sub = "SELECT SUM(cust_credit_bill_pkg.amount) AS credit_amount,
639 reason.reason as reason_text, access_user.username AS username_text,
641 FROM cust_credit_bill_pkg
642 JOIN cust_credit_bill USING (creditbillnum)
643 JOIN cust_credit USING (crednum)
644 LEFT JOIN reason USING (reasonnum)
645 LEFT JOIN access_user USING (usernum)
647 GROUP BY billpkgnum, reason.reason, access_user.username";
648 $join_pkg .= " LEFT JOIN ($credit_sub) AS item_credit USING (billpkgnum)";
651 push @where, 'item_credit.billpkgnum IS NOT NULL';
652 push @select, 'item_credit.credit_amount',
653 'item_credit.username_text',
654 'item_credit.reason_text';
655 push @peritem, 'credit_amount', 'username_text', 'reason_text';
656 push @peritem_desc, 'Credited', 'By', 'Reason';
657 push @total, 'SUM(credit_amount)';
658 push @total_desc, "$money_char%.2f credited";
662 #still want a credit total column
665 SELECT SUM(cust_credit_bill_pkg.amount)
666 FROM cust_credit_bill_pkg
667 WHERE cust_bill_pkg.billpkgnum = cust_credit_bill_pkg.billpkgnum
669 push @select, "($credit_sub) AS credit_amount";
673 push @select, 'cust_main.custnum', FS::UI::Web::cust_sql_fields();
676 if ( $cgi->param('salesnum') =~ /^(\d+)$/ ) {
679 my $sales = FS::sales->by_key($salesnum)
680 or die "salesnum $salesnum not found";
682 my $subsearch = $sales->cust_bill_pkg_search('', '',
683 'cust_main_sales' => ($cgi->param('cust_main_sales') ? 1 : 0),
684 'paid' => ($cgi->param('paid') ? 1 : 0),
685 'classnum' => scalar($cgi->param('classnum'))
687 $join_pkg .= " JOIN sales_pkg_class ON ( COALESCE(sales_pkg_class.classnum, 0) = COALESCE( part_fee.classnum, part_pkg.classnum, 0) )";
689 my $extra_sql = $subsearch->{extra_sql};
690 $extra_sql =~ s/^WHERE//;
691 push @where, $extra_sql;
693 $cgi->param('classnum', 0) unless $cgi->param('classnum');
697 my $where = join(' AND ', @where);
698 $where &&= "WHERE $where";
701 'table' => 'cust_bill_pkg',
702 'addl_from' => "$join_pkg $join_cust",
704 'select' => join(",\n", @select ),
705 'extra_sql' => $where,
706 'order_by' => 'ORDER BY cust_bill._date, cust_bill_pkg.billpkgnum',
710 'SELECT ' . join(',', @total) .
711 " FROM cust_bill_pkg $join_pkg $join_cust
714 @peritem_desc = map {emt($_)} @peritem_desc;
715 my @peritem_sub = map {
717 if ($field =~ /_text$/) { # kludge for credit reason/username fields
718 sub {$_[0]->get($field)};
720 sub { sprintf($money_char.'%.2f', $_[0]->get($field)) }
723 my @peritem_null = map { '' } @peritem; # placeholders
724 my $peritem_align = 'r' x scalar(@peritem);
726 my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ];
727 my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ];
729 my $pay_link = ''; #[, 'billpkgnum', ];
730 my $credit_link = [ "${p}search/cust_credit_bill_pkg.html?billpkgnum=", 'billpkgnum', ];
732 warn "\n\nQUERY:\n".Dumper($query)."\n\nCOUNT_QUERY:\n$count_query\n\n"
733 if $cgi->param('debug');