sub cust_bill_pkg {
my $self = shift;
- qsearch( 'cust_bill_pkg', { 'invnum' => $self->invnum } );
+ qsearch(
+ { 'table' => 'cust_bill_pkg',
+ 'hashref' => { 'invnum' => $self->invnum },
+ 'order_by' => 'ORDER BY billpkgnum',
+ }
+ );
}
=item cust_pkg
}
-=item generate_email PARAMHASH
+=item generate_email OPTION => VALUE ...
-PARAMHASH can contain the following:
+Options:
=over 4
-=item from => sender address, required
+=item from
+
+sender address, required
+
+=item tempate
-=item tempate => alternate template name, optional
+alternate template name, optional
-=item print_text => text attachment arrayref, optional
+=item print_text
-=item subject => email subject, optional
+text attachment arrayref, optional
+
+=item subject
+
+email subject, optional
=back
cid -
+unsquelch_cdr - overrides any per customer cdr squelching when true
+
=cut
sub print_generic {
'date' => time2str($date_format, $self->_date),
'today' => time2str('%b %o, %Y', $today),
'agent' => &$escape_function($cust_main->agent->agent),
- 'agent_custid => &$escape_function($cust_main->agent_custid),
+ 'agent_custid' => &$escape_function($cust_main->agent_custid),
'payname' => &$escape_function($cust_main->payname),
'company' => &$escape_function($cust_main->company),
'address1' => &$escape_function($cust_main->address1),
'conf_dir' => "$FS::UID::conf_dir/conf.$FS::UID::datasrc",
'page' => 1,
'total_pages' => 1,
+ 'current_charges' => sprintf("%.2f", $self->charged),
+ 'duedate' => $self->due_date2str('%m/%d/%Y'), #date_format?
'ship_enable' => $conf->exists('invoice-ship_address'),
'unitprices' => $conf->exists('invoice-unitprice'),
);
+ my $countrydefault = $conf->config('countrydefault') || 'US';
my $prefix = $cust_main->has_ship_address ? 'ship_' : '';
foreach ( qw( contact company address1 address2 city state zip country fax) ){
my $method = $prefix.$_;
$invoice_data{"ship_$_"} = _latex_escape($cust_main->$method);
}
+ $invoice_data{'ship_country'} = ''
+ if ( $invoice_data{'ship_country'} eq $countrydefault );
$invoice_data{'cid'} = $params{'cid'}
if $params{'cid'};
- my $countrydefault = $conf->config('countrydefault') || 'US';
if ( $cust_main->country eq $countrydefault ) {
$invoice_data{'country'} = '';
} else {
# my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits
#my $balance_due = $self->owed + $pr_total - $cr_total;
my $balance_due = $self->owed + $pr_total;
- $invoice_data{'balance'} = $balance_due;
+ $invoice_data{'previous_balance'} = sprintf("%.2f", $pr_total);
+ $invoice_data{'balance'} = sprintf("%.2f", $balance_due);
#do variable substitution in notes, footer, smallfooter
foreach my $include (qw( notes footer smallfooter coupon )) {
my $adjust_section = { 'description' => 'Credits, Payments, and Adjustments',
'subtotal' => 0 }; # adjusted below
+ my $unsquelched = $params{unsquelch_cdr} || $cust_main->squelch_cdr ne 'Y';
my $multisection = $conf->exists('invoice_sections', $cust_main->agentnum);
+ my $late_sections = [];
if ( $multisection ) {
- push @sections, $self->_items_sections;
+ push @sections, $self->_items_sections( $late_sections );
}else{
push @sections, { 'description' => '', 'subtotal' => '' };
}
push @buf, ['',''];
}
- foreach my $section (@sections) {
+ foreach my $section (@sections, @$late_sections) {
$section->{'subtotal'} = $other_money_char.
sprintf('%.2f', $section->{'subtotal'})
$options{'section'} = $section if $multisection;
$options{'format'} = $format;
$options{'escape_function'} = $escape_function;
+ $options{'format_function'} = sub { () } unless $unsquelched;
+ $options{'unsquelched'} = $unsquelched;
foreach my $line_item ( $self->_items_pkg(%options) ) {
my $detail = {
unshift @total_items, $total;
}
}
+ $invoice_data{'taxtotal'} = sprintf('%.2f', $taxtotal);
push @buf,['','-----------'];
push @buf,[( $conf->exists('disable_previous_balance')
)
);
if ( $multisection ) {
- $adjust_section->{'pretotal'} = 'New charges total '.
- $total->{'total_amount'};
+ $adjust_section->{'pretotal'} = 'New charges total '. $other_money_char.
+ sprintf('%.2f', $self->charged );
}else{
push @total_items, $total;
}
#foreach my $thing ( sort { $a->_date <=> $b->_date } $self->_items_credits, $self->_items_payments
# credits
+ my $credittotal = 0;
foreach my $credit ( $self->_items_credits ) {
my $total;
$total->{'total_item'} = &$escape_function($credit->{'description'});
- #$credittotal
+ $credittotal += $credit->{'amount'};
$total->{'total_amount'} = '-'. $other_money_char. $credit->{'amount'};
$adjusttotal += $credit->{'amount'};
if ( $multisection ) {
push @total_items, $total;
}
}
+ $invoice_data{'credittotal'} = sprintf('%.2f', $credittotal);
# credits (again)
foreach ( $self->cust_credited ) {
}
# payments
+ my $paymenttotal = 0;
foreach my $payment ( $self->_items_payments ) {
my $total = {};
$total->{'total_item'} = &$escape_function($payment->{'description'});
- #$paymenttotal
+ $paymenttotal += $payment->{'amount'};
$total->{'total_amount'} = '-'. $other_money_char. $payment->{'amount'};
$adjusttotal += $payment->{'amount'};
if ( $multisection ) {
$money_char. sprintf("%10.2f", $payment->{'amount'}),
];
}
+ $invoice_data{'paymenttotal'} = sprintf('%.2f', $paymenttotal);
if ( $multisection ) {
$adjust_section->{'subtotal'} = $other_money_char.
}
}
+ if ( $multisection ) {
+ push @sections, @$late_sections
+ if $unsquelched;
+ }
+
$invoice_lines = 0;
my $wasfunc = 0;
foreach ( grep /invoice_lines\(\d*\)/, @invoice_template ) { #kludgy
=cut
sub print_html {
- my( $self, $today, $template, $cid ) = @_;
+ my $self = shift;
+ my %params;
+ if ( ref $_[0] ) {
+ %params = %{ shift() };
+ }else{
+ $params{'time'} = shift;
+ $params{'template'} = shift;
+ $params{'cid'} = shift;
+ }
- my %params = ( 'format' => 'html' );
- $params{'time'} = $today if $today;
- $params{'template'} = $template if $template;
- $params{'cid'} = $cid if $cid;
+ $params{'format'} = 'html';
$self->print_generic( %params );
}
$msg;
}
+sub balance_due_date {
+ my $self = shift;
+ my $duedate = '';
+ if ( $conf->exists('invoice_default_terms')
+ && $conf->config('invoice_default_terms')=~ /^\s*Net\s*(\d+)\s*$/ ) {
+ $duedate = time2str("%m/%d/%Y", $self->_date + ($1*86400) );
+ }
+ $duedate;
+}
+
=item invnum_date_pretty
Returns a string with the invoice number and date, for example:
sub _items_sections {
my $self = shift;
+ my $late = shift;
my %s = ();
- foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) {
+ my %l = ();
+
+ foreach my $cust_bill_pkg ( $self->cust_bill_pkg )
+ {
if ( $cust_bill_pkg->pkgnum > 0 ) {
- my $desc = $cust_bill_pkg->part_pkg->classname;
+ my $desc = $cust_bill_pkg->section;
+ my $dup_desc = $cust_bill_pkg->duplicate_section;
+
+ if ($cust_bill_pkg->duplicate) {
+ $s{$dup_desc} += $cust_bill_pkg->setup
+ if ( $cust_bill_pkg->setup != 0 );
+
+ $s{$dup_desc} += $cust_bill_pkg->recur
+ if ( $cust_bill_pkg->recur != 0 );
+ }
+
+ if ( $cust_bill_pkg->post_total ) {
+ $l{$desc} += $cust_bill_pkg->setup
+ if ( $cust_bill_pkg->setup != 0 );
+
+ $l{$desc} += $cust_bill_pkg->recur
+ if ( $cust_bill_pkg->recur != 0 );
- $s{$desc} += $cust_bill_pkg->setup
- if ( $cust_bill_pkg->setup != 0 );
+ } else {
+ $s{$desc} += $cust_bill_pkg->setup
+ if ( $cust_bill_pkg->setup != 0 );
- $s{$desc} += $cust_bill_pkg->recur
- if ( $cust_bill_pkg->recur != 0 );
+ $s{$desc} += $cust_bill_pkg->recur
+ if ( $cust_bill_pkg->recur != 0 );
+ }
}
}
+ push @$late, map { { 'description' => $_,
+ 'subtotal' => $l{$_},
+ 'post_total' => 1,
+ } } sort keys %l;
+
map { {'description' => $_, 'subtotal' => $s{$_}} } sort keys %s;
}
sub _items_pkg {
my $self = shift;
my %options = @_;
- my $section = delete $options{'section'};
+ my $section = $options{'section'};
+ my $desc = $section->{'description'};
my @cust_bill_pkg =
grep { $_->pkgnum &&
( defined($section)
- ? $_->part_pkg->classname eq $section->{'description'}
+ ? ( $_->section eq $desc || $_->duplicate_section eq $desc )
: 1
)
} $self->cust_bill_pkg;
my $format = $opt{format} || '';
my $escape_function = $opt{escape_function} || sub { shift };
+ my $format_function = $opt{format_function} || '';
+ my $unsquelched = $opt{unsquelched} || '';
my @b = ();
- foreach my $cust_bill_pkg ( @$cust_bill_pkg ) {
+ my $last_pkgnum = '';
+ foreach my $cust_bill_pkg ( @$cust_bill_pkg )
+ {
my $cust_pkg = $cust_bill_pkg->cust_pkg;
my %details_opt = ( 'format' => $format,
'escape_function' => $escape_function,
+ 'format_function' => $format_function,
);
if ( $cust_bill_pkg->pkgnum > 0 ) {
quantity => $cust_bill_pkg->quantity,
ext_description => \@d,
};
+
+ $last_pkgnum = '';
+
}
if ( $cust_bill_pkg->recur != 0 ) {
- my $description = $desc;
+ my $is_summary =
+ ( $cust_bill_pkg->duplicate &&
+ $opt{section}->{description} ne $cust_bill_pkg->section
+ );
+ my $description = $is_summary ? "Usage charges" : $desc;
+
unless ( $conf->exists('disable_line_item_date_ranges') ) {
- $desc .= " (" . time2str("%x", $cust_bill_pkg->sdate).
- " - ". time2str("%x", $cust_bill_pkg->edate). ")";
+ $description .= " (" . time2str("%x", $cust_bill_pkg->sdate).
+ " - ". time2str("%x", $cust_bill_pkg->edate). ")";
}
#at least until cust_bill_pkg has "past" ranges in addition to
#the "future" sdate/edate ones... see #3032
- my @d = map &{$escape_function}($_),
- $cust_pkg->h_labels_short($self->_date);
+ my @d = ();
+ push @d, map &{$escape_function}($_),
+ $cust_pkg->h_labels_short($self->_date)
#$cust_bill_pkg->edate,
#$cust_bill_pkg->sdate),
- push @d, $cust_bill_pkg->details(%details_opt);
+ unless ($cust_bill_pkg->pkgnum eq $last_pkgnum);
- push @b, {
- description => $description,
- #pkgpart => $part_pkg->pkgpart,
- pkgnum => $cust_bill_pkg->pkgnum,
- amount => sprintf("%.2f", $cust_bill_pkg->recur),
- unit_amount => sprintf("%.2f", $cust_bill_pkg->unitrecur),
- quantity => $cust_bill_pkg->quantity,
- ext_description => \@d,
- };
+ @d = () if ($cust_bill_pkg->itemdesc || $is_summary);
+ push @d, $cust_bill_pkg->details(%details_opt)
+ unless $is_summary;
+
+ if ($cust_bill_pkg->pkgnum eq $last_pkgnum) {
+ $b[$#b]->{amount} =
+ sprintf("%.2f", $b[$#b]->{amount} + $cust_bill_pkg->recur);
+ push @{$b[$#b]->{ext_description}}, @d;
+
+ }else{
+
+ push @b, {
+ description => $description,
+ #pkgpart => $part_pkg->pkgpart,
+ pkgnum => $cust_bill_pkg->pkgnum,
+ amount => sprintf("%.2f", $cust_bill_pkg->recur),
+ unit_amount => sprintf("%.2f", $cust_bill_pkg->unitrecur),
+ quantity => $cust_bill_pkg->quantity,
+ ext_description => \@d,
+ };
+
+ }
+
+ if ($conf->exists('separate_usage') && $cust_bill_pkg->type ne 'U') {
+ $last_pkgnum = '';
+ }else{
+ $last_pkgnum = $cust_bill_pkg->pkgnum;
+ }
}
} else { #pkgnum tax or one-shot line item (??)
};
}
+ $last_pkgnum = '';
+
}
}