X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_bill.pm;h=d811c1af4e4ac2e3f542042c012acb06e55e6c87;hb=f5a77514d6816225b378ced1d598ba745d0c22b4;hp=c3d48a61c208d28dd69710ab2544b11b01a9d898;hpb=8c450aab9bae89373c2c1b35c85597bb52299de3;p=freeside.git diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index c3d48a61c..d811c1af4 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -1,12 +1,14 @@ package FS::cust_bill; -use base qw( FS::Template_Mixin FS::cust_main_Mixin FS::Record ); +use base qw( FS::cust_bill::Search 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; -use List::Util qw(min max); +use List::Util qw(min max sum); use Date::Format; use File::Temp 0.14; use HTML::Entities; @@ -15,7 +17,6 @@ use GD::Barcode; use FS::UID qw( datasrc ); use FS::Misc qw( send_email send_fax do_print ); use FS::Record qw( qsearch qsearchs dbh ); -use FS::cust_main; use FS::cust_statement; use FS::cust_bill_pkg; use FS::cust_bill_pkg_display; @@ -25,12 +26,10 @@ use FS::cust_pay; use FS::cust_pkg; use FS::cust_credit_bill; use FS::pay_batch; -use FS::cust_pay_batch; use FS::cust_bill_event; use FS::cust_event; use FS::part_pkg; use FS::cust_bill_pay; -use FS::cust_bill_pay_batch; use FS::part_bill_event; use FS::payby; use FS::bill_batch; @@ -38,17 +37,12 @@ use FS::cust_bill_batch; use FS::cust_bill_pay_pkg; use FS::cust_credit_bill_pkg; use FS::discount_plan; +use FS::cust_bill_void; use FS::L10N; $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 @@ -79,7 +73,7 @@ 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 @@ -105,21 +99,19 @@ L and L for conversion functions. =back -Customer info at invoice generation time +Deprecated fields =over 4 -=item previous_balance +=item billing_balance - the customer's balance immediately before generating +this invoice. DEPRECATED. Use the L method +to determine the customer's balance at a specific time. -=item billing_balance +=item previous_balance - the customer's balance immediately after generating +the invoice before this one. DEPRECATED. -=back - -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 @@ -135,6 +127,8 @@ Specific use cases =item promised_date - customer promised payment date, for collection +=item pending - invoice is still being generated, empty or 'Y' + =back =head1 METHODS @@ -150,9 +144,15 @@ Invoices are normally created by calling the bill method of a customer object =cut sub table { 'cust_bill'; } -sub notice_name { 'Invoice'; } -sub cust_linked { $_[0]->cust_main_custnum; } +# 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 || $_[0]->custnum } sub cust_unlinked_msg { my $self = shift; "WARNING: can't find cust_main.custnum ". $self->custnum. @@ -203,10 +203,63 @@ sub insert { } +=item void + +Voids this invoice: deletes the invoice and adds a record of the voided invoice +to the FS::cust_bill_void table (and related tables starting from +FS::cust_bill_pkg_void). + +=cut + +sub void { + my $self = shift; + my $reason = scalar(@_) ? shift : ''; + + 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; + + my $cust_bill_void = new FS::cust_bill_void ( { + map { $_ => $self->get($_) } $self->fields + } ); + $cust_bill_void->reason($reason); + my $error = $cust_bill_void->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + + foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) { + my $error = $cust_bill_pkg->void($reason); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + + $error = $self->delete; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + + ''; + +} + =item delete This method now works but you probably shouldn't use it. Instead, apply a -credit against the invoice. +credit against the invoice, or use the new void method. Using this method to delete invoices outright is really, really bad. There would be no record you ever posted this invoice, and there are no check to @@ -236,11 +289,10 @@ sub delete { cust_event cust_credit_bill cust_bill_pay - cust_credit_bill cust_pay_batch cust_bill_pay_batch - cust_bill_pkg cust_bill_batch + cust_bill_pkg )) { foreach my $linked ( $self->$table() ) { @@ -286,6 +338,7 @@ sub replace_check { #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->pending eq 'Y' || $old->charged == 0 || $new->{'Hash'}{'cc_surcharge_replace_hack'}; @@ -340,6 +393,7 @@ sub check { || $self->ut_enum('closed', [ '', 'Y' ]) || $self->ut_foreign_keyn('statementnum', 'cust_statement', 'statementnum' ) || $self->ut_numbern('agent_invid') #varchar? + || $self->ut_flag('pending') ; return $error if $error; @@ -359,14 +413,33 @@ cust_bill-default_agent_invid is set and it has a value, invnum otherwise. 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, @@ -380,7 +453,8 @@ sub previous { my @cust_bill = sort { $a->_date <=> $b->_date } grep { $_->owed != 0 } qsearch( 'cust_bill', { 'custnum' => $self->custnum, - '_date' => { op=>'<', value=>$self->_date }, + #'_date' => { op=>'<', value=>$self->_date }, + 'invnum' => { op=>'<', value=>$self->invnum }, } ) ; foreach ( @cust_bill ) { $total += $_->owed; } @@ -411,7 +485,9 @@ sub cust_bill_pkg { qsearch( { 'table' => 'cust_bill_pkg', 'hashref' => { 'invnum' => $self->invnum }, - 'order_by' => 'ORDER BY billpkgnum', + 'order_by' => 'ORDER BY billpkgnum', #important? otherwise we could use + # the AUTLOADED FK search. or should + # that default to ORDER by the pkey? } ); } @@ -553,11 +629,21 @@ sub num_cust_event { Returns the customer (see L) for this invoice. +=item suspend + +Suspends all unsuspended packages (see L) for this invoice + +Returns a list: an empty list on success or a list of errors. + =cut -sub cust_main { +sub suspend { my $self = shift; - qsearchs( 'cust_main', { 'custnum' => $self->custnum } ); + + grep { $_->suspend(@_) } + grep {! $_->getfield('cancel') } + $self->cust_pkg; + } =item cust_suspend_if_balance_over AMOUNT @@ -579,55 +665,35 @@ sub cust_suspend_if_balance_over { } } -=item cust_credit - -Depreciated. See the cust_credited method. +=item cancel - #Returns a list consisting of the total previous credited (see - #L) and unapplied for this customer, followed by the previous - #outstanding credits (FS::cust_credit objects). +Cancel the packages on this invoice. Largely similar to the cust_main version, but does not bother yet with banned payment options =cut -sub cust_credit { - use Carp; - croak "FS::cust_bill->cust_credit depreciated; see ". - "FS::cust_bill->cust_credit_bill"; - #my $self = shift; - #my $total = 0; - #my @cust_credit = sort { $a->_date <=> $b->_date } - # grep { $_->credited != 0 && $_->_date < $self->_date } - # qsearch('cust_credit', { 'custnum' => $self->custnum } ) - #; - #foreach (@cust_credit) { $total += $_->credited; } - #$total, @cust_credit; -} - -=item cust_pay - -Depreciated. See the cust_bill_pay method. +sub cancel { + my( $self, %opt ) = @_; -#Returns all payments (see L) for this invoice. + warn "$me cancel called on cust_bill ". $self->invnum . " with options ". + join(', ', map { "$_: $opt{$_}" } keys %opt ). "\n" + if $DEBUG; -=cut + return ( 'Access denied' ) + unless $FS::CurrentUser::CurrentUser->access_right('Cancel customer'); -sub cust_pay { - use Carp; - croak "FS::cust_bill->cust_pay depreciated; see FS::cust_bill->cust_bill_pay"; - #my $self = shift; - #sort { $a->_date <=> $b->_date } - # qsearch( 'cust_pay', { 'invnum' => $self->invnum } ) - #; -} + my @pkgs = $self->cust_pkg; -sub cust_pay_batch { - my $self = shift; - qsearch('cust_pay_batch', { 'invnum' => $self->invnum } ); -} + if ( !$opt{nobill} && $self->conf->exists('bill_usage_on_cancel') ) { + $opt{nobill} = 1; + my $error = $self->cust_main->bill( pkg_list => [ @pkgs ], cancel => 1 ); + warn "Error billing during cancel, custnum ". $self->custnum. ": $error" + if $error; + } -sub cust_bill_pay_batch { - my $self = shift; - qsearch('cust_bill_pay_batch', { 'invnum' => $self->invnum } ); + grep { $_ } + map { $_->cancel(%opt) } + grep { ! $_->getfield('cancel') } + @pkgs; } =item cust_bill_pay @@ -968,7 +1034,7 @@ Options: sender address, required -=item tempate +=item template alternate template name, optional @@ -1002,15 +1068,12 @@ sub generate_email { 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; @@ -1052,7 +1115,7 @@ sub generate_email { if ( ref($args{'print_text'}) eq 'ARRAY' ) { $data = $args{'print_text'}; } else { - $data = [ $self->print_text(\%opt) ]; + $data = [ $self->print_text(\%args) ]; } } @@ -1060,6 +1123,7 @@ sub generate_email { $alternative->attach( 'Type' => 'text/plain', 'Encoding' => 'quoted-printable', + 'Charset' => 'UTF-8', #'Encoding' => '7bit', 'Data' => $data, 'Disposition' => 'inline', @@ -1109,10 +1173,10 @@ sub generate_email { '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( @@ -1174,7 +1238,7 @@ sub generate_email { $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 ]; @@ -1206,7 +1270,7 @@ sub generate_email { #mime parts arguments a la MIME::Entity->build(). $return{'mimeparts'} = [ - { $self->mimebuild_pdf(\%opt) } + { $self->mimebuild_pdf(\%args) } ]; } @@ -1226,7 +1290,7 @@ sub generate_email { 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) ]; } } @@ -1255,99 +1319,48 @@ sub mimebuild_pdf { ); } -=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. -Options can be passed as a hashref (recommended) or as a list of up to -four values for templatename, agentnum, invoice_from and amount. +Options can be passed as a hashref. Positional parameters are no longer +allowed. -I