X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_bill.pm;h=4fa876bb972bf8bd7cd35b059721a823b0ab62fc;hb=096f208ab7e91fdf6391001a2e28c0a80374ee39;hp=1f837de84aa9c742be4566c78cc169269a6b757c;hpb=f163fa1c9f616fbbb5413e6fad09bd08957b0c3a;p=freeside.git diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 1f837de84..4fa876bb9 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -226,7 +226,12 @@ Returns the line items (see L) for this invoice. 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 @@ -557,19 +562,27 @@ sub apply_payments_and_credits { } -=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 + +alternate template name, optional + +=item print_text -=item tempate => alternate template name, optional +text attachment arrayref, optional -=item print_text => text attachment arrayref, optional +=item subject -=item subject => email subject, optional +email subject, optional =back @@ -838,13 +851,15 @@ sub send { 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 ''; @@ -876,6 +891,7 @@ sub queueable_email { } +#sub email_invoice { sub email { my $self = shift; my $template = scalar(@_) ? shift : ''; @@ -925,6 +941,7 @@ TEMPLATENAME, if specified, is the name of a suffix for alternate invoices. =cut +#sub print_invoice { sub print { my $self = shift; my $template = scalar(@_) ? shift : ''; @@ -932,7 +949,7 @@ sub print { do_print $self->lpr_data($template); } -=item fax [ TEMPLATENAME ] +=item fax_invoice [ TEMPLATENAME ] Faxes this invoice. @@ -940,7 +957,7 @@ TEMPLATENAME, if specified, is the name of a suffix for alternate invoices. =cut -sub fax { +sub fax_invoice { my $self = shift; my $template = scalar(@_) ? shift : ''; @@ -957,6 +974,28 @@ sub fax { } +=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 send_if_newest [ TEMPLATENAME [ , AGENTNUM [ , INVOICE_FROM ] ] ] Like B, but only sends the invoice if it is the newest open invoice for @@ -1614,6 +1653,8 @@ L and L for conversion functions. cid - +unsquelch_cdr - overrides any per customer cdr squelching when true + =cut sub print_generic { @@ -1671,6 +1712,7 @@ sub print_generic { 'footer' => sub { map "$_", @_ }, 'smallfooter' => sub { map "$_", @_ }, 'returnaddress' => sub { map "$_", @_ }, + 'coupon' => sub { map "$_", @_ }, }, 'html' => { 'notes' => @@ -1702,6 +1744,7 @@ sub print_generic { $_; } @_ }, + 'coupon' => sub { "" }, }, 'template' => { 'notes' => @@ -1731,6 +1774,7 @@ sub print_generic { $_; } @_ }, + 'coupon' => sub { "" }, }, ); @@ -1823,6 +1867,7 @@ 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), 'payname' => &$escape_function($cust_main->payname), 'company' => &$escape_function($cust_main->company), 'address1' => &$escape_function($cust_main->address1), @@ -1831,7 +1876,7 @@ sub print_generic { 'state' => &$escape_function($cust_main->state), 'zip' => &$escape_function($cust_main->zip), 'returnaddress' => $returnaddress, - 'quantity' => 1, + #'quantity' => 1, 'terms' => $self->terms, 'template' => $params{'template'}, #'notes' => join("\n", $conf->config('invoice_latexnotes') ), @@ -1839,12 +1884,24 @@ sub print_generic { '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 { @@ -1872,8 +1929,18 @@ sub print_generic { push @address, '' while (scalar(@address) < 5); + $invoice_data{'logo_file'} = $params{'logo_file'} + if $params{'logo_file'}; + + my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance +# 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{'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 )) { + foreach my $include (qw( notes footer smallfooter coupon )) { my $inc_file = $conf->key_orbase("invoice_${format}$include", $template); my @inc_src; @@ -1919,11 +1986,6 @@ sub print_generic { ? &$escape_function("Purchase Order #". $cust_main->payinfo) : $nbsp; - my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance -# 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; - my %money_chars = ( 'latex' => '', 'html' => $conf->config('money_char') || '$', 'template' => '', @@ -1959,9 +2021,11 @@ sub print_generic { 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' => '' }; } @@ -1983,10 +2047,8 @@ sub print_generic { &$escape_function($_); } @{$line_item->{'ext_description'}}; } - { - my $money = $old_latex ? '' : $money_char; - $detail->{'amount'} = $money. $line_item->{'amount'}; - } + $detail->{'amount'} = ( $old_latex ? '' : $money_char). + $line_item->{'amount'}; $detail->{'product_code'} = $line_item->{'pkgpart'} || 'N/A'; push @detail_items, $detail; @@ -2002,7 +2064,7 @@ sub print_generic { push @buf, ['','']; } - foreach my $section (@sections) { + foreach my $section (@sections, @$late_sections) { $section->{'subtotal'} = $other_money_char. sprintf('%.2f', $section->{'subtotal'}) @@ -2018,22 +2080,24 @@ sub print_generic { $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 = { ext_description => [], }; $detail->{'ref'} = $line_item->{'pkgnum'}; - $detail->{'quantity'} = 1; + $detail->{'quantity'} = $line_item->{'quantity'}; $detail->{'section'} = $section; $detail->{'description'} = &$escape_function($line_item->{'description'}); if ( exists $line_item->{'ext_description'} ) { @{$detail->{'ext_description'}} = @{$line_item->{'ext_description'}}; } - { - my $money = $old_latex ? '' : $money_char; - $detail->{'amount'} = $money. $line_item->{'amount'}; - } + $detail->{'amount'} = ( $old_latex ? '' : $money_char ). + $line_item->{'amount'}; + $detail->{'unit_amount'} = ( $old_latex ? '' : $money_char ). + $line_item->{'unit_amount'}; $detail->{'product_code'} = $line_item->{'pkgpart'} || 'N/A'; push @detail_items, $detail; @@ -2101,6 +2165,7 @@ sub print_generic { unshift @total_items, $total; } } + $invoice_data{'taxtotal'} = sprintf('%.2f', $taxtotal); push @buf,['','-----------']; push @buf,[( $conf->exists('disable_previous_balance') @@ -2124,8 +2189,8 @@ sub print_generic { ) ); 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; } @@ -2146,10 +2211,11 @@ sub print_generic { #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 ) { @@ -2167,6 +2233,7 @@ sub print_generic { push @total_items, $total; } } + $invoice_data{'credittotal'} = sprintf('%.2f', $credittotal); # credits (again) foreach ( $self->cust_credited ) { @@ -2183,10 +2250,11 @@ sub print_generic { } # 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 ) { @@ -2207,6 +2275,7 @@ sub print_generic { $money_char. sprintf("%10.2f", $payment->{'amount'}), ]; } + $invoice_data{'paymenttotal'} = sprintf('%.2f', $paymenttotal); if ( $multisection ) { $adjust_section->{'subtotal'} = $other_money_char. @@ -2233,8 +2302,10 @@ sub print_generic { } } - $invoice_data{'logo_file'} = $params{'logo_file'} - if $params{'logo_file'}; + if ( $multisection ) { + push @sections, @$late_sections + if $unsquelched; + } $invoice_lines = 0; my $wasfunc = 0; @@ -2343,12 +2414,17 @@ when emailing the invoice as part of a multipart/related MIME email. =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 ); } @@ -2471,6 +2547,16 @@ sub balance_due_msg { $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: @@ -2485,24 +2571,51 @@ sub invnum_date_pretty { 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; - $s{$desc} += $cust_bill_pkg->setup - if ( $cust_bill_pkg->setup != 0 ); + if ($cust_bill_pkg->duplicate) { + $s{$dup_desc} += $cust_bill_pkg->setup + if ( $cust_bill_pkg->setup != 0 ); - $s{$desc} += $cust_bill_pkg->recur - if ( $cust_bill_pkg->recur != 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 ); + + } 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 ); + } } } + push @$late, map { { 'description' => $_, + 'subtotal' => $l{$_}, + 'post_total' => 1, + } } sort keys %l; + map { {'description' => $_, 'subtotal' => $s{$_}} } sort keys %s; } @@ -2557,11 +2670,12 @@ sub _items_previous { 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; @@ -2590,9 +2704,13 @@ sub _items_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; @@ -2600,6 +2718,7 @@ sub _items_cust_bill_pkg { my %details_opt = ( 'format' => $format, 'escape_function' => $escape_function, + 'format_function' => $format_function, ); if ( $cust_bill_pkg->pkgnum > 0 ) { @@ -2619,35 +2738,66 @@ sub _items_cust_bill_pkg { #pkgpart => $part_pkg->pkgpart, pkgnum => $cust_bill_pkg->pkgnum, amount => sprintf("%.2f", $cust_bill_pkg->setup), + unit_amount => sprintf("%.2f", $cust_bill_pkg->unitsetup), + 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), - 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 (??) @@ -2667,6 +2817,8 @@ sub _items_cust_bill_pkg { }; } + $last_pkgnum = ''; + } } @@ -2759,6 +2911,14 @@ sub process_refax { process_re_X('fax', @_); } +=item reftp + +=cut + +sub process_reftp { + process_re_X('ftp', @_); +} + use Storable qw(thaw); use Data::Dumper; use MIME::Base64; @@ -2802,6 +2962,8 @@ sub re_X { 'debug' => 1, } ); + $method .= '_invoice' unless $method eq 'email' || $method eq 'print'; + warn " $me re_X $method: ". scalar(@cust_bill). " invoices found\n" if $DEBUG;