sub delete {
my $self = shift;
return "Can't delete closed invoice" if $self->closed =~ /^Y/i;
- $self->SUPER::delete(@_);
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ foreach my $table (qw(
+ cust_bill_event
+ cust_credit_bill
+ cust_bill_pay
+ cust_bill_pay
+ cust_credit_bill
+ cust_pay_batch
+ cust_bill_pay_batch
+ cust_bill_pkg
+ )) {
+
+ foreach my $linked ( $self->$table() ) {
+ my $error = $linked->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ }
+
+ my $error = $self->SUPER::delete(@_);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+
}
=item replace OLD_RECORD
#;
}
+sub cust_pay_batch {
+ my $self = shift;
+ qsearch('cust_pay_batch', { 'invnum' => $self->invnum } );
+}
+
+sub cust_bill_pay_batch {
+ my $self = shift;
+ qsearch('cust_bill_pay_batch', { 'invnum' => $self->invnum } );
+}
+
=item cust_bill_pay
Returns all payment applications (see L<FS::cust_bill_pay>) for this invoice.
=item cust_credited
+=item cust_credit_bill
+
Returns all applied credits (see L<FS::cust_credit_bill>) for this invoice.
=cut
;
}
+sub cust_credit_bill {
+ shift->cust_credited(@_);
+}
+
=item tax
Returns the tax amount (see L<FS::cust_bill_pkg>) for this invoice.
'Encoding' => 'base64',
'Data' => [ $self->print_pdf(@_) ],
'Disposition' => 'attachment',
- 'Filename' => 'invoice.pdf',
+ 'Filename' => 'invoice-'. $self->invnum. '.pdf',
);
}
my @invoicing_list = $self->cust_main->invoicing_list;
+ #$self->email_invoice($template, $invoice_from)
$self->email($template, $invoice_from)
if grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list or !@invoicing_list;
+ #$self->print_invoice($template)
$self->print($template)
if grep { $_ eq 'POST' } @invoicing_list; #postal
- $self->fax($template)
+ $self->fax_invoice($template)
if grep { $_ eq 'FAX' } @invoicing_list; #fax
'';
}
+#sub email_invoice {
sub email {
my $self = shift;
my $template = scalar(@_) ? shift : '';
#better to notify this person than silence
@invoicing_list = ($invoice_from) unless @invoicing_list;
+ my $subject = $self->email_subject($template);
+
my $error = send_email(
$self->generate_email(
'from' => $invoice_from,
'to' => [ grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ],
+ 'subject' => $subject,
'template' => $template,
)
);
}
+sub email_subject {
+ my $self = shift;
+
+ #my $template = scalar(@_) ? shift : '';
+ #per-template?
+
+ my $subject = $conf->config('invoice_subject') || 'Invoice';
+
+ my $cust_main = $self->cust_main;
+ my $name = $cust_main->name;
+ my $name_short = $cust_main->name_short;
+ my $invoice_number = $self->invnum;
+ my $invoice_date = $self->_date_pretty;
+
+ eval qq("$subject");
+}
+
=item lpr_data [ TEMPLATENAME ]
Returns the postscript or plaintext for this invoice as an arrayref.
=cut
+#sub print_invoice {
sub print {
my $self = shift;
my $template = scalar(@_) ? shift : '';
do_print $self->lpr_data($template);
}
-=item fax [ TEMPLATENAME ]
+=item fax_invoice [ TEMPLATENAME ]
Faxes this invoice.
=cut
-sub fax {
+sub fax_invoice {
my $self = shift;
my $template = scalar(@_) ? shift : '';
}
+=item ftp_invoice [ TEMPLATENAME ]
+
+Sends this invoice data via FTP.
+
+TEMPLATENAME is unused?
+
+=cut
+
+sub ftp_invoice {
+ my $self = shift;
+ my $template = scalar(@_) ? shift : '';
+
+ $self->send_csv(
+ 'protocol' => 'ftp',
+ 'server' => $conf->config('cust_bill-ftpserver'),
+ 'username' => $conf->config('cust_bill-ftpusername'),
+ 'password' => $conf->config('cust_bill-ftppassword'),
+ 'dir' => $conf->config('cust_bill-ftpdir'),
+ 'format' => $conf->config('cust_bill-ftpformat'),
+ );
+}
+
+=item spool_invoice [ TEMPLATENAME ]
+
+Spools this invoice data (see L<FS::spool_csv>)
+
+TEMPLATENAME is unused?
+
+=cut
+
+sub spool_invoice {
+ my $self = shift;
+ my $template = scalar(@_) ? shift : '';
+
+ $self->spool_csv(
+ 'format' => $conf->config('cust_bill-spoolformat'),
+ 'agent_spools' => $conf->exists('cust_bill-spoolagent'),
+ );
+}
+
=item send_if_newest [ TEMPLATENAME [ , AGENTNUM [ , INVOICE_FROM ] ] ]
Like B<send>, but only sends the invoice if it is the newest open invoice for
my $taxtotal = 0;
$taxtotal += $_->{'amount'} foreach $self->_items_tax;
- 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) );
- }
+ my $duedate = $self->balance_due_date;
my( $previous_balance, @unused ) = $self->previous; #previous balance
'date' => time2str('%b %o, %Y', $self->_date),
'today' => time2str('%b %o, %Y', $today),
'agent' => _latex_escape($cust_main->agent->agent),
+ 'agent_custid' => _latex_escape($cust_main->agent_custid),
'payname' => _latex_escape($cust_main->payname),
'company' => _latex_escape($cust_main->company),
'address1' => _latex_escape($cust_main->address1),
'state' => _latex_escape($cust_main->state),
#'quantity' => 1,
'zip' => _latex_escape($cust_main->zip),
+ 'fax' => _latex_escape($cust_main->fax),
'footer' => join("\n", $conf->config_orbase('invoice_latexfooter', $template) ),
'smallfooter' => join("\n", $conf->config_orbase('invoice_latexsmallfooter', $template) ),
'returnaddress' => $returnaddress,
'terms' => $conf->config('invoice_default_terms') || 'Payable upon receipt',
#'notes' => join("\n", $conf->config('invoice_latexnotes') ),
'conf_dir' => "$FS::UID::conf_dir/conf.$FS::UID::datasrc",
- 'balance' => $balance_due,
+ 'current_charges' => sprintf('%.2f', $self->charged ),
+ 'previous_balance' => sprintf("%.2f", $pr_total),
+ 'balance' => sprintf("%.2f", $balance_due),
+ 'duedate' => $self->balance_due_date,
+ '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 );
+
if ( $cust_main->country eq $countrydefault ) {
$invoice_data{'country'} = '';
} else {
!~ /^%%EndDetail\s*$/ ) {
push @line_item, $line_item_line;
}
- foreach my $line_item ( $self->_items ) {
+ foreach my $line_item ( $self->_items ) { #( 'format'=>'latex' ) ) {
#foreach my $line_item ( $self->_items_pkg ) {
$invoice_data{'ref'} = $line_item->{'pkgnum'};
$invoice_data{'description'} =
}
if ( $taxtotal ) {
+ $invoice_data{'taxtotal'} = sprintf('%.2f', $taxtotal);
my $total = {};
$total->{'total_item'} = 'Sub-total';
$total->{'total_amount'} =
'\dollar '. sprintf('%.2f', $self->charged - $taxtotal );
unshift @total_items, $total;
+ }else{
+ $invoice_data{'taxtotal'} = '0.00';
}
{
#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'} = _latex_escape($credit->{'description'});
- #$credittotal
+ $credittotal += $credit->{'amount'};
$total->{'total_amount'} = '-\dollar '. $credit->{'amount'};
push @total_items, $total;
}
+ $invoice_data{'credittotal'} = sprintf('%.2f', $credittotal);
# payments
+ my $paymenttotal = 0;
foreach my $payment ( $self->_items_payments ) {
my $total = {};
$total->{'total_item'} = _latex_escape($payment->{'description'});
- #$paymenttotal
+ $paymenttotal += $payment->{'amount'};
$total->{'total_amount'} = '-\dollar '. $payment->{'amount'};
push @total_items, $total;
}
+ $invoice_data{'paymenttotal'} = sprintf('%.2f', $paymenttotal);
{
my $total;
'date' => time2str('%b %o, %Y', $self->_date),
'today' => time2str('%b %o, %Y', $today),
'agent' => encode_entities($cust_main->agent->agent),
+ 'agent_custid' => encode_entities($cust_main->agent_custid),
'payname' => encode_entities($cust_main->payname),
'company' => encode_entities($cust_main->company),
'address1' => encode_entities($cust_main->address1),
'city' => encode_entities($cust_main->city),
'state' => encode_entities($cust_main->state),
'zip' => encode_entities($cust_main->zip),
+ 'fax' => encode_entities($cust_main->fax),
'terms' => $conf->config('invoice_default_terms')
|| 'Payable upon receipt',
'cid' => $cid,
'template' => $template,
+ 'ship_enable' => $conf->exists('invoice-ship_address'),
+ 'unitprices' => $conf->exists('invoice-unitprice'),
# 'conf_dir' => "$FS::UID::conf_dir/conf.$FS::UID::datasrc",
);
+ 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_$_"} = encode_entities($cust_main->$method);
+ }
+
if (
defined( $conf->config_orbase('invoice_htmlreturnaddress', $template) )
&& length( $conf->config_orbase('invoice_htmlreturnaddress', $template) )
s/~/ /g;
s/\\\\\*?\s*$/<BR>/;
s/\\hyphenation\{[\w\s\-]+\}//;
+ s/\\([&])/$1/g;
$_;
}
$conf->config_orbase( 'invoice_latexreturnaddress',
$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 invnum_date_pretty {
my $self = shift;
- 'Invoice #'. $self->invnum. ' ('. time2str('%x', $self->_date). ')';
+ 'Invoice #'. $self->invnum. ' ('. $self->_date_pretty. ')';
+}
+
+=item _date_pretty
+
+Returns a string with the date, for example: "3/20/2008"
+
+=cut
+
+sub _date_pretty {
+ my $self = shift;
+ time2str('%x', $self->_date);
}
sub _items {
my $cust_pkg = $cust_bill_pkg->cust_pkg;
my $desc = $cust_bill_pkg->desc;
+ $desc = substr($desc, 0, 50). '...'
+ if $format eq 'latex' && length($desc) > 50;
my %details_opt = ( 'format' => $format,
'escape_function' => $escape_function,
my $description = $desc;
$description .= ' Setup' if $cust_bill_pkg->recur != 0;
- 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)
+ unless $cust_pkg->part_pkg->hide_svc_detail;
+
push @d, $cust_bill_pkg->details(%details_opt)
if $cust_bill_pkg->recur == 0;
my $description = $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). ")";
}
+ my @d = ();
+
#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);
- #$cust_bill_pkg->edate,
- #$cust_bill_pkg->sdate),
+ push @d, map &{$escape_function}($_),
+ $cust_pkg->h_labels_short($self->_date)
+ #$cust_bill_pkg->edate,
+ #$cust_bill_pkg->sdate),
+ unless $cust_pkg->part_pkg->hide_svc_detail
+ || $cust_bill_pkg->itemdesc;
+
push @d, $cust_bill_pkg->details(%details_opt);
push @b, {
=over 4
-=item reprint
+=item process_reprint
=cut
process_re_X('print', @_);
}
-=item reemail
+=item process_reemail
=cut
process_re_X('email', @_);
}
-=item refax
+=item process_refax
=cut
process_re_X('fax', @_);
}
+=item process_reftp
+
+=cut
+
+sub process_reftp {
+ process_re_X('ftp', @_);
+}
+
+=item respool
+
+=cut
+
+sub process_respool {
+ process_re_X('spool', @_);
+}
+
use Storable qw(thaw);
use Data::Dumper;
use MIME::Base64;
'debug' => 1,
} );
+ $method .= '_invoice' unless $method eq 'email' || $method eq 'print';
+
warn " $me re_X $method: ". scalar(@cust_bill). " invoices found\n"
if $DEBUG;