use base qw( FS::Template_Mixin FS::cust_main_Mixin FS::Record );
use strict;
-use vars qw( $DEBUG $me $date_format );
+use vars qw( $DEBUG $me );
# but NOT $conf
use Fcntl qw(:flock); #for spool_csv
use Cwd;
$DEBUG = 0;
$me = '[FS::cust_bill]';
-#ask FS::UID to run this stuff for us later
-FS::UID->install_callback( sub {
- my $conf = new FS::Conf; #global
- $date_format = $conf->config('date_format') || '%x'; #/YY
-} );
-
=head1 NAME
FS::cust_bill - Object methods for cust_bill records
$tax_amount = $record->tax;
@lines = $cust_bill->print_text;
- @lines = $cust_bill->print_text $time;
+ @lines = $cust_bill->print_text('time' => $time);
=head1 DESCRIPTION
=back
-Customer info at invoice generation time
+Deprecated fields
=over 4
-=item previous_balance
-
-=item billing_balance
+=item billing_balance - the customer's balance immediately before generating
+this invoice. DEPRECATED. Use the L<FS::cust_main/balance_date> method
+to determine the customer's balance at a specific time.
-=back
+=item previous_balance - the customer's balance immediately after generating
+the invoice before this one. DEPRECATED.
-Deprecated
-
-=over 4
-
-=item printed - deprecated
+=item printed - formerly used to track the number of times an invoice had
+been printed; no longer used.
=back
=cut
sub table { 'cust_bill'; }
-sub notice_name { 'Invoice'; }
+
+# should be the ONLY occurrence of "Invoice" in invoice rendering code.
+# (except email_subject and invnum_date_pretty)
+sub notice_name {
+ my $self = shift;
+ $self->conf->config('notice_name') || 'Invoice'
+}
sub cust_linked { $_[0]->cust_main_custnum; }
sub cust_unlinked_msg {
sub display_invnum {
my $self = shift;
- my $conf = $self->conf;
- if ( $conf->exists('cust_bill-default_agent_invid') && $self->agent_invid ){
+ if ( $self->agent_invid
+ && FS::Conf->new->exists('cust_bill-default_agent_invid') ) {
return $self->agent_invid;
} else {
return $self->invnum;
}
}
+=item previous_bill
+
+Returns the customer's last invoice before this one.
+
+=cut
+
+sub previous_bill {
+ my $self = shift;
+ if ( !$self->get('previous_bill') ) {
+ $self->set('previous_bill', qsearchs({
+ 'table' => 'cust_bill',
+ 'hashref' => { 'custnum' => $self->custnum,
+ '_date' => { op=>'<', value=>$self->_date } },
+ 'order_by' => 'ORDER BY _date DESC LIMIT 1',
+ }) );
+ }
+ $self->get('previous_bill');
+}
+
=item previous
Returns a list consisting of the total previous balance for this customer,
sender address, required
-=item tempate
+=item template
alternate template name, optional
my %return = (
'from' => $args{'from'},
- 'subject' => (($args{'subject'}) ? $args{'subject'} : 'Invoice'),
+ 'subject' => ($args{'subject'} || $self->email_subject),
+ 'custnum' => $self->custnum,
+ 'msgtype' => 'invoice',
);
- my %opt = (
- 'unsquelch_cdr' => $conf->exists('voip-cdr_email'),
- 'template' => $args{'template'},
- 'notice_name' => ( $args{'notice_name'} || 'Invoice' ),
- 'no_coupon' => $args{'no_coupon'},
- );
+ $args{'unsquelch_cdr'} = $conf->exists('voip-cdr_email');
my $cust_main = $self->cust_main;
if ( ref($args{'print_text'}) eq 'ARRAY' ) {
$data = $args{'print_text'};
} else {
- $data = [ $self->print_text(\%opt) ];
+ $data = [ $self->print_text(\%args) ];
}
}
'Filename' => 'barcode.png',
'Content-ID' => "<$barcode_content_id>",
;
- $opt{'barcode_cid'} = $barcode_content_id;
+ $args{'barcode_cid'} = $barcode_content_id;
}
- $htmldata = $self->print_html({ 'cid'=>$content_id, %opt });
+ $htmldata = $self->print_html({ 'cid'=>$content_id, %args });
}
$alternative->attach(
$related->add_part($image) if $image;
- my $pdf = build MIME::Entity $self->mimebuild_pdf(\%opt);
+ my $pdf = build MIME::Entity $self->mimebuild_pdf(\%args);
$return{'mimeparts'} = [ $related, $pdf, @otherparts ];
#mime parts arguments a la MIME::Entity->build().
$return{'mimeparts'} = [
- { $self->mimebuild_pdf(\%opt) }
+ { $self->mimebuild_pdf(\%args) }
];
}
if ( ref($args{'print_text'}) eq 'ARRAY' ) {
$return{'body'} = $args{'print_text'};
} else {
- $return{'body'} = [ $self->print_text(\%opt) ];
+ $return{'body'} = [ $self->print_text(\%args) ];
}
}
);
}
-=item send HASHREF | [ TEMPLATE [ , AGENTNUM [ , INVOICE_FROM [ , AMOUNT ] ] ] ]
+=item send HASHREF
Sends this invoice to the destinations configured for this customer: sends
email, prints and/or faxes. See L<FS::cust_main_invoice>.
-Options can be passed as a hashref (recommended) or as a list of up to
-four values for templatename, agentnum, invoice_from and amount.
-
-I<template>, if specified, is the name of a suffix for alternate invoices.
-
-I<agentnum>, if specified, means that this invoice will only be sent for customers
-of the specified agent or agent(s). AGENTNUM can be a scalar agentnum (for a
-single agent) or an arrayref of agentnums.
-
-I<invoice_from>, if specified, overrides the default email invoice From: address.
+Options can be passed as a hashref. Positional parameters are no longer
+allowed.
-I<amount>, if specified, only sends the invoice if the total amount owed on this
-invoice and all older invoices is greater than the specified amount.
+I<template>: a suffix for alternate invoices
-I<notice_name>, if specified, overrides "Invoice" as the name of the sent document (templates from 10/2009 or newer required)
+I<agentnum>: obsolete, now does nothing.
-=cut
+I<invoice_from> overrides the default email invoice From: address.
-sub queueable_send {
- my %opt = @_;
-
- my $self = qsearchs('cust_bill', { 'invnum' => $opt{invnum} } )
- or die "invalid invoice number: " . $opt{invnum};
+I<amount>: obsolete, does nothing
- my @args = ( $opt{template}, $opt{agentnum} );
- push @args, $opt{invoice_from}
- if exists($opt{invoice_from}) && $opt{invoice_from};
+I<notice_name> overrides "Invoice" as the name of the sent document
+(templates from 10/2009 or newer required).
- my $error = $self->send( @args );
- die $error if $error;
+I<lpr> overrides the system 'lpr' option as the command to print a document
+from standard input.
-}
+=cut
sub send {
my $self = shift;
+ my $opt = ref($_[0]) ? $_[0] : +{ @_ };
my $conf = $self->conf;
- my( $template, $invoice_from, $notice_name );
- my $agentnums = '';
- my $balance_over = 0;
-
- if ( ref($_[0]) ) {
- my $opt = shift;
- $template = $opt->{'template'} || '';
- if ( $agentnums = $opt->{'agentnum'} ) {
- $agentnums = [ $agentnums ] unless ref($agentnums);
- }
- $invoice_from = $opt->{'invoice_from'};
- $balance_over = $opt->{'balance_over'} if $opt->{'balance_over'};
- $notice_name = $opt->{'notice_name'};
- } else {
- $template = scalar(@_) ? shift : '';
- if ( scalar(@_) && $_[0] ) {
- $agentnums = ref($_[0]) ? shift : [ shift ];
- }
- $invoice_from = shift if scalar(@_);
- $balance_over = shift if scalar(@_) && $_[0] !~ /^\s*$/;
- }
-
my $cust_main = $self->cust_main;
- return 'N/A' unless ! $agentnums
- or grep { $_ == $cust_main->agentnum } @$agentnums;
-
- return ''
- unless $cust_main->total_owed_date($self->_date) > $balance_over;
-
- $invoice_from ||= $self->_agent_invoice_from || #XXX should go away
- $conf->config('invoice_from', $cust_main->agentnum );
-
- my %opt = (
- 'template' => $template,
- 'invoice_from' => $invoice_from,
- 'notice_name' => ( $notice_name || 'Invoice' ),
- );
-
my @invoicing_list = $cust_main->invoicing_list;
- #$self->email_invoice(\%opt)
- $self->email(\%opt)
+ $self->email($opt)
if ( grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list or !@invoicing_list )
&& ! $self->invoice_noemail;
- #$self->print_invoice(\%opt)
- $self->print(\%opt)
+ $self->print($opt)
if grep { $_ eq 'POST' } @invoicing_list; #postal
- $self->fax_invoice(\%opt)
+ #this has never been used post-$ORIGINAL_ISP afaik
+ $self->fax_invoice($opt)
if grep { $_ eq 'FAX' } @invoicing_list; #fax
'';
=item email HASHREF | [ TEMPLATE [ , INVOICE_FROM ] ]
-Emails this invoice.
+Sends this invoice to the customer's email destination(s).
-Options can be passed as a hashref (recommended) or as a list of up to
-two values for templatename and invoice_from.
+Options must be passed as a hashref. Positional parameters are no longer
+allowed.
I<template>, if specified, is the name of a suffix for alternate invoices.
-I<invoice_from>, if specified, overrides the default email invoice From: address.
+I<invoice_from>, if specified, overrides the default email invoice From:
+address.
-I<notice_name>, if specified, overrides "Invoice" as the name of the sent document (templates from 10/2009 or newer required)
+I<notice_name> is the name of the sent document.
=cut
my $self = qsearchs('cust_bill', { 'invnum' => $opt{invnum} } )
or die "invalid invoice number: " . $opt{invnum};
- my %args = ( 'template' => $opt{template} );
- $args{$_} = $opt{$_}
- foreach grep { exists($opt{$_}) && $opt{$_} }
- qw( invoice_from notice_name no_coupon );
+ my %args = map {$_ => $opt{$_}}
+ grep { $opt{$_} }
+ qw( invoice_from notice_name no_coupon template );
my $error = $self->email( \%args );
die $error if $error;
}
-#sub email_invoice {
sub email {
my $self = shift;
return if $self->hide;
my $conf = $self->conf;
-
- my( $template, $invoice_from, $notice_name, $no_coupon );
- if ( ref($_[0]) ) {
- my $opt = shift;
- $template = $opt->{'template'} || '';
- $invoice_from = $opt->{'invoice_from'};
- $notice_name = $opt->{'notice_name'} || 'Invoice';
- $no_coupon = $opt->{'no_coupon'} || 0;
- } else {
- $template = scalar(@_) ? shift : '';
- $invoice_from = shift if scalar(@_);
- $notice_name = 'Invoice';
- $no_coupon = 0;
+ my $opt = shift || {};
+ if ($opt and !ref($opt)) {
+ die "FS::cust_bill::email called with positional parameters";
}
- $invoice_from ||= $self->_agent_invoice_from || #XXX should go away
- $conf->config('invoice_from', $self->cust_main->agentnum );
+ my $template = $opt->{template};
+ my $from = delete $opt->{invoice_from};
+
+ # this is where we set the From: address
+ $from ||= $self->_agent_invoice_from || #XXX should go away
+ $conf->config('invoice_from', $self->cust_main->agentnum );
my @invoicing_list = grep { $_ !~ /^(POST|FAX)$/ }
$self->cust_main->invoicing_list;
die 'No recipients for customer #'. $self->custnum;
} else {
#default: better to notify this person than silence
- @invoicing_list = ($invoice_from);
+ @invoicing_list = ($from);
}
}
+ # this is where we set the Subject:
my $subject = $self->email_subject($template);
my $error = send_email(
$self->generate_email(
- 'from' => $invoice_from,
+ 'from' => $from,
'to' => [ grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ],
'subject' => $subject,
- 'template' => $template,
- 'notice_name' => $notice_name,
- 'no_coupon' => $no_coupon,
+ %$opt, # template, etc.
)
);
die "can't email invoice: $error\n" if $error;
eval qq("$subject");
}
-=item lpr_data HASHREF | [ TEMPLATE ]
+=item lpr_data HASHREF
Returns the postscript or plaintext for this invoice as an arrayref.
-Options can be passed as a hashref (recommended) or as a single optional value
-for template.
+Options must be passed as a hashref. Positional parameters are no longer
+allowed.
I<template>, if specified, is the name of a suffix for alternate invoices.
sub lpr_data {
my $self = shift;
my $conf = $self->conf;
- my( $template, $notice_name );
- if ( ref($_[0]) ) {
- my $opt = shift;
- $template = $opt->{'template'} || '';
- $notice_name = $opt->{'notice_name'} || 'Invoice';
- } else {
- $template = scalar(@_) ? shift : '';
- $notice_name = 'Invoice';
+ my $opt = shift || {};
+ if ($opt and !ref($opt)) {
+ # nobody does this anyway
+ die "FS::cust_bill::lpr_data called with positional parameters";
}
- my %opt = (
- 'template' => $template,
- 'notice_name' => $notice_name,
- );
-
my $method = $conf->exists('invoice_latex') ? 'print_ps' : 'print_text';
- [ $self->$method( \%opt ) ];
+ [ $self->$method( $opt ) ];
}
-=item print HASHREF | [ TEMPLATE ]
+=item print HASHREF
Prints this invoice.
-Options can be passed as a hashref (recommended) or as a single optional
-value for template.
+Options must be passed as a hashref.
I<template>, if specified, is the name of a suffix for alternate invoices.
=cut
-#sub print_invoice {
sub print {
my $self = shift;
return if $self->hide;
my $conf = $self->conf;
-
- my( $template, $notice_name );
- if ( ref($_[0]) ) {
- my $opt = shift;
- $template = $opt->{'template'} || '';
- $notice_name = $opt->{'notice_name'} || 'Invoice';
- } else {
- $template = scalar(@_) ? shift : '';
- $notice_name = 'Invoice';
+ my $opt = shift || {};
+ if ($opt and !ref($opt)) {
+ die "FS::cust_bill::print called with positional parameters";
}
- my %opt = (
- 'template' => $template,
- 'notice_name' => $notice_name,
- );
-
+ my $lpr = delete $opt->{lpr};
if($conf->exists('invoice_print_pdf')) {
# Add the invoice to the current batch.
- $self->batch_invoice(\%opt);
+ $self->batch_invoice($opt);
}
else {
- do_print $self->lpr_data(\%opt);
+ do_print(
+ $self->lpr_data($opt),
+ 'agentnum' => $self->cust_main->agentnum,
+ 'lpr' => $lpr,
+ );
}
}
-=item fax_invoice HASHREF | [ TEMPLATE ]
+=item fax_invoice HASHREF
Faxes this invoice.
-Options can be passed as a hashref (recommended) or as a single optional
-value for template.
+Options must be passed as a hashref.
I<template>, if specified, is the name of a suffix for alternate invoices.
my $self = shift;
return if $self->hide;
my $conf = $self->conf;
-
- my( $template, $notice_name );
- if ( ref($_[0]) ) {
- my $opt = shift;
- $template = $opt->{'template'} || '';
- $notice_name = $opt->{'notice_name'} || 'Invoice';
- } else {
- $template = scalar(@_) ? shift : '';
- $notice_name = 'Invoice';
+ my $opt = shift || {};
+ if ($opt and !ref($opt)) {
+ die "FS::cust_bill::fax_invoice called with positional parameters";
}
die 'FAX invoice destination not (yet?) supported with plain text invoices.'
my $dialstring = $self->cust_main->getfield('fax');
#Check $dialstring?
- my %opt = (
- 'template' => $template,
- 'notice_name' => $notice_name,
- );
-
- my $error = send_fax( 'docdata' => $self->lpr_data(\%opt),
+ my $error = send_fax( 'docdata' => $self->lpr_data($opt),
'dialstring' => $dialstring,
);
die $error if $error;
);
}
-=item send_if_newest [ TEMPLATENAME [ , AGENTNUM [ , INVOICE_FROM ] ] ]
-
-Like B<send>, but only sends the invoice if it is the newest open invoice for
-this customer.
-
-=cut
-
-sub send_if_newest {
- my $self = shift;
-
- return ''
- if scalar(
- grep { $_->owed > 0 }
- qsearch('cust_bill', {
- 'custnum' => $self->custnum,
- #'_date' => { op=>'>', value=>$self->_date },
- 'invnum' => { op=>'>', value=>$self->invnum },
- } )
- );
-
- $self->send(@_);
-}
-
=item send_csv OPTION => VALUE, ...
Sends invoice as a CSV data-file to a remote host with the specified protocol.
my $spooldir = "/usr/local/etc/freeside/export.". datasrc. "/cust_bill";
mkdir $spooldir, 0700 unless -d $spooldir;
+ # don't localize dates here, they're a defined format
my $tracctnum = $self->invnum. time2str('-%Y%m%d%H%M%S', time);
my $file = "$spooldir/$tracctnum.csv";
Agent number, agent name, customer number, first name, last name, address
line 1, address line 2, city, state, zip, invoice date, invoice number,
-amount charged, amount due,
+amount charged, amount due, previous balance, due date.
and then, for each line item, three columns containing the package number,
description, and amount.
my $time = $opt{'time'} || time;
+ my $tracctnum = ''; #leaking out from billco-specific sections :/
if ( $format eq 'billco' ) {
+ my $account_num =
+ $self->conf->config('billco-account_num', $cust_main->agentnum);
+
+ $tracctnum = $account_num eq 'display_custnum'
+ ? $cust_main->display_custnum
+ : $opt{'tracctnum'};
+
my $taxtotal = 0;
$taxtotal += $_->{'amount'} foreach $self->_items_tax;
- my $duedate = $self->due_date2str('%m/%d/%Y'); #date_format?
+ my $duedate = $self->due_date2str('%m/%d/%Y'); # hardcoded, NOT date_format
my( $previous_balance, @unused ) = $self->previous; #previous balance
my $pmt_cr_applied = 0;
$pmt_cr_applied += $_->{'amount'}
- foreach ( $self->_items_payments, $self->_items_credits ) ;
+ foreach ( $self->_items_payments(%opt), $self->_items_credits(%opt) ) ;
my $totaldue = sprintf('%.2f', $self->owed + $previous_balance);
$csv->combine(
'', # 1 | N/A-Leave Empty CHAR 2
'', # 2 | N/A-Leave Empty CHAR 15
- $opt{'tracctnum'}, # 3 | Transaction Account No CHAR 15
+ $tracctnum, # 3 | Transaction Account No CHAR 15
$self->invnum, # 4 | Transaction Invoice No CHAR 15
$cust_main->zip, # 5 | Transaction Zip Code CHAR 5
$cust_main->company, # 6 | Transaction Company Bill To CHAR 30
} elsif ( $format eq 'oneline' ) { #name
my ($previous_balance) = $self->previous;
+ $previous_balance = sprintf('%.2f', $previous_balance);
my $totaldue = sprintf('%.2f', $self->owed + $previous_balance);
my @items = map {
- ($_->{pkgnum} || ''),
- $_->{description},
- $_->{amount}
- } $self->_items_pkg;
+ $_->{pkgnum},
+ $_->{description},
+ $_->{amount}
+ }
+ $self->_items_pkg, #_items_nontax? no sections or anything
+ # with this format
+ $self->_items_tax;
$csv->combine(
$cust_main->agentnum,
$self->custnum,
$cust_main->first,
$cust_main->last,
+ $cust_main->company,
$cust_main->address1,
$cust_main->address2,
$cust_main->city,
$self->invnum,
$self->charged,
$totaldue,
+ $previous_balance,
+ $self->due_date2str("%x"),
@items,
);
'';
}
- my $svc_x = $cust_svc->svc_x or
- warn "missing svc_x record for svc#".$cust_svc->svcnum."\n";
+ my @h_label = $cust_svc->label(@dates, 'I');
push @details, sprintf('01%-9s%-20s%-47s',
$cust_svc->svcnum,
$svc_class{$svcpart},
- ($svc_x ? $svc_x->label : ''),
+ $h_label[1],
);
} #foreach $cust_svc
} #if $cust_pkg
$csv->combine(
'', # 1 | N/A-Leave Empty CHAR 2
'', # 2 | N/A-Leave Empty CHAR 15
- $opt{'tracctnum'}, # 3 | Account Number CHAR 15
+ $tracctnum, # 3 | Account Number CHAR 15
$self->invnum, # 4 | Invoice Number CHAR 15
$lineseq++, # 5 | Line Sequence (sort order) NUM 6
$item->{'description'}, # 6 | Transaction Detail CHAR 100
? time2str("%x", $cust_bill_pkg->sdate)
: '' ),
($cust_bill_pkg->edate
- ?time2str("%x", $cust_bill_pkg->edate)
+ ? time2str("%x", $cust_bill_pkg->edate)
: '' ),
);
=item invnum_date_pretty
Returns a string with the invoice number and date, for example:
-"Invoice #54 (3/20/2008)"
+"Invoice #54 (3/20/2008)".
+
+Intended for back-end context, with regard to translation and date formatting.
=cut
+#note: this uses _date_pretty_unlocalized because _date_pretty is too expensive
+# for backend use (and also does the wrong thing, localizing for end customer
+# instead of backoffice configured date format)
sub invnum_date_pretty {
my $self = shift;
- $self->mt('Invoice #'). $self->invnum. ' ('. $self->_date_pretty. ')';
+ #$self->mt('Invoice #').
+ 'Invoice #'. #XXX should be translated ala web UI user (not invoice customer)
+ $self->invnum. ' ('. $self->_date_pretty_unlocalized. ')';
}
#sub _items_extra_usage_sections {
}
+=sub _items_usage_class_summary OPTIONS
+
+Returns a list of detail items summarizing the usage charges on this
+invoice. Each one will have 'amount', 'description' (the usage charge name),
+and 'usage_classnum'.
+
+OPTIONS can include 'escape' (a function to escape the descriptions).
+
+=cut
+
+sub _items_usage_class_summary {
+ my $self = shift;
+ my %opt = @_;
+
+ my $escape = $opt{escape} || sub { $_[0] };
+ my $invnum = $self->invnum;
+ my @classes = qsearch({
+ 'table' => 'usage_class',
+ 'select' => 'classnum, classname, SUM(amount) AS amount',
+ 'addl_from' => ' LEFT JOIN cust_bill_pkg_detail USING (classnum)' .
+ ' LEFT JOIN cust_bill_pkg USING (billpkgnum)',
+ 'extra_sql' => " WHERE cust_bill_pkg.invnum = $invnum".
+ ' GROUP BY classnum, classname, weight'.
+ ' HAVING (usage_class.disabled IS NULL OR SUM(amount) > 0)'.
+ ' ORDER BY weight ASC',
+ });
+ my @l;
+ my $section = {
+ description => &{$escape}($self->mt('Usage Summary')),
+ no_subtotal => 1,
+ usage_section => 1,
+ };
+ foreach my $class (@classes) {
+ push @l, {
+ 'description' => &{$escape}($class->classname),
+ 'amount' => sprintf('%.2f', $class->amount),
+ 'usage_classnum' => $class->classnum,
+ 'section' => $section,
+ };
+ }
+ return @l;
+}
+
sub _items_previous {
my $self = shift;
my $conf = $self->conf;
my @b = ();
foreach ( @pr_cust_bill ) {
my $date = $conf->exists('invoice_show_prior_due_date')
- ? 'due '. $_->due_date2str($date_format)
- : time2str($date_format, $_->_date);
+ ? 'due '. $_->due_date2str('short')
+ : $self->time2str_local('short', $_->_date);
push @b, {
'description' => $self->mt('Previous Balance, Invoice #'). $_->invnum. " ($date)",
#'pkgpart' => 'N/A',
my @b;
#credits
- foreach ( $self->cust_credited ) {
+ my @objects;
+ if ( $self->conf->exists('previous_balance-payments_since') ) {
+ if ( $opt{'template'} eq 'statement' ) {
+ # then the current bill is a "statement" (i.e. an invoice sent as
+ # a payment receipt)
+ # and in that case we want to see payments on or after THIS invoice
+ @objects = qsearch('cust_credit', {
+ 'custnum' => $self->custnum,
+ '_date' => {op => '>=', value => $self->_date},
+ });
+ } else {
+ my $date = 0;
+ $date = $self->previous_bill->_date if $self->previous_bill;
+ @objects = qsearch('cust_credit', {
+ 'custnum' => $self->custnum,
+ '_date' => {op => '>=', value => $date},
+ });
+ }
+ } else {
+ @objects = $self->cust_credited;
+ }
- #something more elaborate if $_->amount ne $_->cust_credit->credited ?
+ foreach my $obj ( @objects ) {
+ my $cust_credit = $obj->isa('FS::cust_credit') ? $obj : $obj->cust_credit;
- my $reason = substr($_->cust_credit->reason, 0, $trim_len);
- $reason .= '...' if length($reason) < length($_->cust_credit->reason);
+ my $reason = substr($cust_credit->reason, 0, $trim_len);
+ $reason .= '...' if length($reason) < length($cust_credit->reason);
$reason = " ($reason) " if $reason;
push @b, {
# " (". time2str("%x",$_->cust_credit->_date) .")".
# $reason,
'description' => $self->mt('Credit applied').' '.
- time2str($date_format,$_->cust_credit->_date). $reason,
- 'amount' => sprintf("%.2f",$_->amount),
+ $self->time2str_local('short', $obj->_date). $reason,
+ 'amount' => sprintf("%.2f",$obj->amount),
};
}
sub _items_payments {
my $self = shift;
+ my %opt = @_;
my @b;
- #get & print payments
- foreach ( $self->cust_bill_pay ) {
+ my $detailed = $self->conf->exists('invoice_payment_details');
+ my @objects;
+ if ( $self->conf->exists('previous_balance-payments_since') ) {
+ # then show payments dated on/after the previous bill...
+ if ( $opt{'template'} eq 'statement' ) {
+ # then the current bill is a "statement" (i.e. an invoice sent as
+ # a payment receipt)
+ # and in that case we want to see payments on or after THIS invoice
+ @objects = qsearch('cust_pay', {
+ 'custnum' => $self->custnum,
+ '_date' => {op => '>=', value => $self->_date},
+ });
+ } else {
+ # the normal case: payments on or after the previous invoice
+ my $date = 0;
+ $date = $self->previous_bill->_date if $self->previous_bill;
+ @objects = qsearch('cust_pay', {
+ 'custnum' => $self->custnum,
+ '_date' => {op => '>=', value => $date},
+ });
+ # and before the current bill...
+ @objects = grep { $_->_date < $self->_date } @objects;
+ }
+ } else {
+ @objects = $self->cust_bill_pay;
+ }
- #something more elaborate if $_->amount ne ->cust_pay->paid ?
+ foreach my $obj (@objects) {
+ my $cust_pay = $obj->isa('FS::cust_pay') ? $obj : $obj->cust_pay;
+ my $desc = $self->mt('Payment received').' '.
+ $self->time2str_local('short', $cust_pay->_date );
+ $desc .= $self->mt(' via ') .
+ $cust_pay->payby_payinfo_pretty( $self->cust_main->locale )
+ if $detailed;
push @b, {
- 'description' => $self->mt('Payment received').' '.
- time2str($date_format,$_->cust_pay->_date ),
- 'amount' => sprintf("%.2f", $_->amount )
+ 'description' => $desc,
+ 'amount' => sprintf("%.2f", $obj->amount )
};
}
=item newest_percust
+=item custnum
+
+Return only invoices belonging to that customer.
+
+=item cust_classnum
+
+Limit to that customer class (single value or arrayref).
+
+=item payby
+
+Limit to customers with that payment method (single value or arrayref).
+
+=item refnum
+
+Limit to customers with that advertising source.
+
=back
Note: validates all passed-in data; i.e. safe to use with unchecked CGI params.
push @search, "cust_bill.custnum = $1";
}
+ #customer classnum (false laziness w/ cust_main/Search.pm)
+ if ( $param->{'cust_classnum'} ) {
+
+ my @classnum = ref( $param->{'cust_classnum'} )
+ ? @{ $param->{'cust_classnum'} }
+ : ( $param->{'cust_classnum'} );
+
+ @classnum = grep /^(\d*)$/, @classnum;
+
+ if ( @classnum ) {
+ push @search, '( '. join(' OR ', map {
+ $_ ? "cust_main.classnum = $_"
+ : "cust_main.classnum IS NULL"
+ }
+ @classnum
+ ).
+ ' )';
+ }
+
+ }
+
+ #payby
+ if ( $param->{payby} ) {
+ my $payby = $param->{payby};
+ $payby = [ $payby ] unless ref $payby;
+ my $payby_in = join(',', map {dbh->quote($_)} @$payby);
+ push @search, "cust_main.payby IN($payby_in)" if length($payby_in);
+ }
+
#_date
if ( $param->{_date} ) {
my($beginning, $ending) = @{$param->{_date}};