package FS::cust_bill;
use strict;
-use vars qw( @ISA $DEBUG $me $conf $money_char $date_format $rdate_format );
+use vars qw( @ISA $DEBUG $me $conf
+ $money_char $date_format $rdate_format $date_format_long );
use vars qw( $invoice_lines @buf ); #yuck
use Fcntl qw(:flock); #for spool_csv
use List::Util qw(min max);
#ask FS::UID to run this stuff for us later
FS::UID->install_callback( sub {
$conf = new FS::Conf;
- $money_char = $conf->config('money_char') || '$';
- $date_format = $conf->config('date_format') || '%x';
- $rdate_format = $conf->config('date_format') || '%m/%d/%Y';
+ $money_char = $conf->config('money_char') || '$';
+ $date_format = $conf->config('date_format') || '%x'; #/YY
+ $rdate_format = $conf->config('date_format') || '%m/%d/%Y'; #/YYYY
+ $date_format_long = $conf->config('date_format_long') || '%b %o, %Y';
} );
=head1 NAME
#return "Can't change _date!" unless $old->_date eq $new->_date;
return "Can't change _date" unless $old->_date == $new->_date;
return "Can't change charged" unless $old->charged == $new->charged
- || $old->charged == 0;
+ || $old->charged == 0
+ || $new->{'Hash'}{'cc_surcharge_replace_hack'};
'';
}
+
+=item add_cc_surcharge
+
+Giant hack
+
+=cut
+
+sub add_cc_surcharge {
+ my ($self, $pkgnum, $amount) = (shift, shift, shift);
+
+ my $error;
+ my $cust_bill_pkg = new FS::cust_bill_pkg({
+ 'invnum' => $self->invnum,
+ 'pkgnum' => $pkgnum,
+ 'setup' => $amount,
+ });
+ $error = $cust_bill_pkg->insert;
+ return $error if $error;
+
+ $self->{'Hash'}{'cc_surcharge_replace_hack'} = 1;
+ $self->charged($self->charged+$amount);
+ $error = $self->replace;
+ return $error if $error;
+
+ $self->apply_payments_and_credits;
+}
+
+
=item check
Checks all fields to make sure this is a valid invoice. If there is an error,
my @invoicing_list = grep { $_ !~ /^(POST|FAX)$/ }
$self->cust_main->invoicing_list;
- #better to notify this person than silence
- @invoicing_list = ($invoice_from) unless @invoicing_list;
+ if ( ! @invoicing_list ) { #no recipients
+ if ( $conf->exists('cust_bill-no_recipients-error') ) {
+ die 'No recipients for customer #'. $self->custnum;
+ } else {
+ #default: better to notify this person than silence
+ @invoicing_list = ($invoice_from);
+ }
+ }
my $subject = $self->email_subject($template);
}
sub realtime_bop {
- my( $self, $method ) = @_;
+ my( $self, $method ) = (shift,shift);
+ my %opt = @_;
my $cust_main = $self->cust_main;
my $balance = $cust_main->balance;
#this didn't do what we want, it just calls apply_payments_and_credits
# 'apply' => 1,
'apply_to_invoice' => 1,
+ %opt,
#what we want:
#this changes application behavior: auto payments
#triggered against a specific invoice are now applied
'template' => [ '{', '}' ],
);
+ warn "$me print_generic creating template\n"
+ if $DEBUG > 1;
+
#create the template
my $template = $params{template} ? $params{template} : $self->_agent_template;
my $templatefile = "invoice_$format";
@invoice_template = _translate_old_latex_format(@invoice_template);
}
+ warn "$me print_generic creating T:T object\n"
+ if $DEBUG > 1;
+
my $text_template = new Text::Template(
TYPE => 'ARRAY',
SOURCE => \@invoice_template,
DELIMITERS => $delimiters{$format},
);
+ warn "$me print_generic compiling T:T object\n"
+ if $DEBUG > 1;
+
$text_template->compile()
or die "Can't compile $templatefile: $Text::Template::ERROR\n";
my $escape_function_nonbsp = ($format eq 'html')
? \&_html_escape : $escape_function;
- my %date_formats = ( 'latex' => '%b %o, %Y',
- 'html' => '%b %o, %Y',
+ my %date_formats = ( 'latex' => $date_format_long,
+ 'html' => $date_format_long,
'template' => '%s',
);
+ $date_formats{'html'} =~ s/ / /g;
+
my $date_format = $date_formats{$format};
my %embolden_functions = ( 'latex' => sub { return '\textbf{'. shift(). '}'
);
my $embolden_function = $embolden_functions{$format};
+ warn "$me generating template variables\n"
+ if $DEBUG > 1;
# generate template variables
my $returnaddress;
}
+ warn "$me generating invoice data\n"
+ if $DEBUG > 1;
+
my $agentnum = $self->cust_main->agentnum;
my %invoice_data = (
#invoice info
'invnum' => $self->invnum,
'date' => time2str($date_format, $self->_date),
- 'today' => time2str('%b %o, %Y', $today),
+ 'today' => time2str($date_format_long, $today),
'terms' => $self->terms,
'template' => $template, #params{'template'},
'notice_name' => ($params{'notice_name'} || 'Invoice'),#escape_function?
}
$invoice_data{'summarypage'} = $summarypage;
- #do variable substitution in notes, footer, smallfooter
+ warn "$me substituting variables in notes, footer, smallfooter\n"
+ if $DEBUG > 1;
+
foreach my $include (qw( notes footer smallfooter coupon )) {
my $inc_file = $conf->key_orbase("invoice_${format}$include", $template);
$invoice_data{'buf'} = \@buf;
$invoice_data{'sections'} = \@sections;
+ warn "$me generating sections\n"
+ if $DEBUG > 1;
+
my $previous_section = { 'description' => 'Previous Charges',
'subtotal' => $other_money_char.
sprintf('%.2f', $pr_total),
)
{
+ warn "$me adding previous balances\n"
+ if $DEBUG > 1;
+
foreach my $line_item ( $self->_items_previous ) {
my $detail = {
}
if ( $conf->exists('svc_phone-did-summary') ) {
+ warn "$me adding DID summary\n"
+ if $DEBUG > 1;
+
my ($didsummary,$minutes) = $self->_did_summary;
my $didsummary_desc = 'DID Activity Summary (Past 30 days)';
push @detail_items,
foreach my $section (@sections, @$late_sections) {
+ warn "$me adding section \n". Dumper($section)
+ if $DEBUG > 1;
+
# begin some normalization
$section->{'subtotal'} = $section->{'amount'}
if $multisection
);
}
+ warn "$me setting options\n"
+ if $DEBUG > 1;
+
my $multilocation = scalar($cust_main->cust_location); #too expensive?
my %options = ();
$options{'section'} = $section if $multisection;
$options{'multilocation'} = $multilocation;
$options{'multisection'} = $multisection;
+ warn "$me searching for line items\n"
+ if $DEBUG > 1;
+
foreach my $line_item ( $self->_items_pkg(%options) ) {
+
+ warn "$me adding line item $line_item\n"
+ if $DEBUG > 1;
+
my $detail = {
ext_description => [],
};
unshift @sections, $previous_section if $pr_total;
}
+ warn "$me adding taxes\n"
+ if $DEBUG > 1;
+
foreach my $tax ( $self->_items_tax ) {
$taxtotal += $tax->{'amount'};
my ($file, $lfile) = $self->print_latex(@_);
my $ps = generate_ps($file);
- unlink($file.'.tex');
unlink($lfile);
$ps;
my ($file, $lfile) = $self->print_latex(@_);
my $pdf = generate_pdf($file);
- unlink($file.'.tex');
unlink($lfile);
$pdf;
sub _items_pkg {
my $self = shift;
my %options = @_;
+
+ warn "$me _items_pkg searching for all package line items\n"
+ if $DEBUG > 1;
+
my @cust_bill_pkg = grep { $_->pkgnum } $self->cust_bill_pkg;
+
+ warn "$me _items_pkg filtering line items\n"
+ if $DEBUG > 1;
my @items = $self->_items_cust_bill_pkg(\@cust_bill_pkg, @_);
+
if ($options{section} && $options{section}->{condensed}) {
+
+ warn "$me _items_pkg condensing section\n"
+ if $DEBUG > 1;
+
my %itemshash = ();
local $Storable::canonical = 1;
foreach ( @items ) {
}
keys %itemshash;
}
+
+ warn "$me _items_pkg returning ". scalar(@items). " items\n"
+ if $DEBUG > 1;
+
@items;
}
sub _items_cust_bill_pkg {
my $self = shift;
- my $cust_bill_pkg = shift;
+ my $cust_bill_pkgs = shift;
my %opt = @_;
my $format = $opt{format} || '';
my $summary_page = $opt{summary_page} || '';
my $multilocation = $opt{multilocation} || '';
my $multisection = $opt{multisection} || '';
+ my $discount_show_always = 0;
my @b = ();
my ($s, $r, $u) = ( undef, undef, undef );
- foreach my $cust_bill_pkg ( @$cust_bill_pkg )
+ foreach my $cust_bill_pkg ( @$cust_bill_pkgs )
{
+ warn "$me _items_cust_bill_pkg considering cust_bill_pkg $cust_bill_pkg\n"
+ if $DEBUG > 1;
+
+ $discount_show_always = ($cust_bill_pkg->cust_bill_pkg_discount
+ && $conf->exists('discount-show-always'));
+
foreach ( $s, $r, ($opt{skip_usage} ? () : $u ) ) {
if ( $_ && !$cust_bill_pkg->hidden ) {
$_->{amount} = sprintf( "%.2f", $_->{amount} ),
$_->{amount} =~ s/^\-0\.00$/0.00/;
$_->{unit_amount} = sprintf( "%.2f", $_->{unit_amount} ),
push @b, { %$_ }
- unless $_->{amount} == 0;
+ unless ( $_->{amount} == 0 && !$discount_show_always );
$_ = undef;
}
}
)
{
+ warn "$me _items_cust_bill_pkg considering display item $display\n"
+ if $DEBUG > 1;
+
my $type = $display->type;
my $desc = $cust_bill_pkg->desc;
if ( $cust_bill_pkg->pkgnum > 0 ) {
+ warn "$me _items_cust_bill_pkg cust_bill_pkg is non-tax\n"
+ if $DEBUG > 1;
+
my $cust_pkg = $cust_bill_pkg->cust_pkg;
if ( $cust_bill_pkg->setup != 0 && (!$type || $type eq 'S') ) {
+ warn "$me _items_cust_bill_pkg adding setup\n"
+ if $DEBUG > 1;
+
my $description = $desc;
$description .= ' Setup' if $cust_bill_pkg->recur != 0;
}
- if ( ( $cust_bill_pkg->recur != 0 || $cust_bill_pkg->setup == 0 ) &&
+ if ( ( $cust_bill_pkg->recur != 0 || $cust_bill_pkg->setup == 0 ||
+ ($discount_show_always && $cust_bill_pkg->recur == 0) ) &&
( !$type || $type eq 'R' || $type eq 'U' )
)
{
+ warn "$me _items_cust_bill_pkg adding recur/usage\n"
+ if $DEBUG > 1;
+
my $is_summary = $display->summary;
my $description = ($is_summary && $type && $type eq 'U')
? "Usage charges" : $desc;
- unless ( $conf->exists('disable_line_item_date_ranges') ) {
- $description .= " (" . time2str($date_format, $cust_bill_pkg->sdate).
- " - ". time2str($date_format, $cust_bill_pkg->edate). ")";
- }
+ $description .= " (" . time2str($date_format, $cust_bill_pkg->sdate).
+ " - ". time2str($date_format, $cust_bill_pkg->edate).
+ ")"
+ unless $conf->exists('disable_line_item_date_ranges');
my @d = ();
|| $is_summary && $type && $type eq 'U' )
{
+ warn "$me _items_cust_bill_pkg adding service details\n"
+ if $DEBUG > 1;
+
push @d, map &{$escape_function}($_),
$cust_pkg->h_labels_short(@dates, 'I')
#$cust_bill_pkg->edate,
#$cust_bill_pkg->sdate)
unless $cust_bill_pkg->pkgpart_override; #don't redisplay services
+ warn "$me _items_cust_bill_pkg done adding service details\n"
+ if $DEBUG > 1;
+
if ( $multilocation ) {
my $loc = $cust_pkg->location_label;
$loc = substr($loc, 0, 50). '...'
}
+ warn "$me _items_cust_bill_pkg adding details\n"
+ if $DEBUG > 1;
+
push @d, $cust_bill_pkg->details(%details_opt)
unless ($is_summary || $type && $type eq 'R');
+
+ warn "$me _items_cust_bill_pkg calculating amount\n"
+ if $DEBUG > 1;
my $amount = 0;
if (!$type) {
if ( !$type || $type eq 'R' ) {
+ warn "$me _items_cust_bill_pkg adding recur\n"
+ if $DEBUG > 1;
+
if ( $cust_bill_pkg->hidden ) {
$r->{amount} += $amount;
$r->{unit_amount} += $cust_bill_pkg->unitrecur;
} else { # $type eq 'U'
+ warn "$me _items_cust_bill_pkg adding usage\n"
+ if $DEBUG > 1;
+
if ( $cust_bill_pkg->hidden ) {
$u->{amount} += $amount;
$u->{unit_amount} += $cust_bill_pkg->unitrecur;
} else { #pkgnum tax or one-shot line item (??)
+ warn "$me _items_cust_bill_pkg cust_bill_pkg is tax\n"
+ if $DEBUG > 1;
+
if ( $cust_bill_pkg->setup != 0 ) {
push @b, {
'description' => $desc,
}
+ warn "$me _items_cust_bill_pkg done considering cust_bill_pkgs\n"
+ if $DEBUG > 1;
+
foreach ( $s, $r, ($opt{skip_usage} ? () : $u ) ) {
if ( $_ ) {
$_->{amount} = sprintf( "%.2f", $_->{amount} ),
$_->{amount} =~ s/^\-0\.00$/0.00/;
$_->{unit_amount} = sprintf( "%.2f", $_->{unit_amount} ),
push @b, { %$_ }
- unless $_->{amount} == 0;
+ unless ( $_->{amount} == 0 && !$discount_show_always );
}
}