X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_bill.pm;h=bcfbbc73fb318c40f7933941f3c65d5d104083e5;hb=7924a4bd53059d7ba5088d1b6c7662775c82a660;hp=9a971dd3ab20e927bc0bbf0d6a4cf97f50edb0af;hpb=429a48b18a54917b73b3add468d930f60bcdb2fe;p=freeside.git diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 9a971dd3a..bcfbbc73f 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -1,8 +1,10 @@ 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; @@ -13,7 +15,7 @@ use HTML::Entities; use Storable qw( freeze thaw ); use GD::Barcode; use FS::UID qw( datasrc ); -use FS::Misc qw( send_email send_fax do_print ); +use FS::Misc qw( send_fax do_print ); use FS::Record qw( qsearch qsearchs dbh ); use FS::cust_statement; use FS::cust_bill_pkg; @@ -41,12 +43,6 @@ 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 @@ -103,23 +99,19 @@ L and L for conversion functions. =back -Customer info at invoice generation time +Deprecated fields =over 4 -=item billing_balance - the customer's balance at the time the invoice was -generated (not including charges on this invoice) +=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 previous_balance - the billing_balance of this customer's previous -invoice plus the charges on that invoice +=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 @@ -344,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'}; @@ -398,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; @@ -417,8 +413,8 @@ 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; @@ -633,6 +629,23 @@ 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 suspend { + my $self = shift; + + grep { $_->suspend(@_) } + grep {! $_->getfield('cancel') } + $self->cust_pkg; + +} + =item cust_suspend_if_balance_over AMOUNT Suspends the customer associated with this invoice if the total amount owed on @@ -652,45 +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; -} +sub cancel { + my( $self, %opt ) = @_; -=item cust_pay + warn "$me cancel called on cust_bill ". $self->invnum . " with options ". + join(', ', map { "$_: $opt{$_}" } keys %opt ). "\n" + if $DEBUG; -Depreciated. See the cust_bill_pay method. + return ( 'Access denied' ) + unless $FS::CurrentUser::CurrentUser->access_right('Cancel customer'); -#Returns all payments (see L) for this invoice. + my @pkgs = $self->cust_pkg; -=cut + 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_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 } ) - #; + grep { $_ } + map { $_->cancel(%opt) } + grep { ! $_->getfield('cancel') } + @pkgs; } =item cust_bill_pay @@ -1021,298 +1024,6 @@ sub apply_payments_and_credits { } -=item generate_email OPTION => VALUE ... - -Options: - -=over 4 - -=item from - -sender address, required - -=item template - -alternate template name, optional - -=item print_text - -text attachment arrayref, optional - -=item subject - -email subject, optional - -=item notice_name - -notice name instead of "Invoice", optional - -=back - -Returns an argument list to be passed to L. - -=cut - -use MIME::Entity; - -sub generate_email { - - my $self = shift; - my %args = @_; - my $conf = $self->conf; - - my $me = '[FS::cust_bill::generate_email]'; - - my %return = ( - 'from' => $args{'from'}, - 'subject' => ($args{'subject'} || $self->email_subject), - ); - - $args{'unsquelch_cdr'} = $conf->exists('voip-cdr_email'); - - my $cust_main = $self->cust_main; - - if (ref($args{'to'}) eq 'ARRAY') { - $return{'to'} = $args{'to'}; - } else { - $return{'to'} = [ grep { $_ !~ /^(POST|FAX)$/ } - $cust_main->invoicing_list - ]; - } - - if ( $conf->exists('invoice_html') ) { - - warn "$me creating HTML/text multipart message" - if $DEBUG; - - $return{'nobody'} = 1; - - my $alternative = build MIME::Entity - 'Type' => 'multipart/alternative', - #'Encoding' => '7bit', - 'Disposition' => 'inline' - ; - - my $data; - if ( $conf->exists('invoice_email_pdf') - and scalar($conf->config('invoice_email_pdf_note')) ) { - - warn "$me using 'invoice_email_pdf_note' in multipart message" - if $DEBUG; - $data = [ map { $_ . "\n" } - $conf->config('invoice_email_pdf_note') - ]; - - } else { - - warn "$me not using 'invoice_email_pdf_note' in multipart message" - if $DEBUG; - if ( ref($args{'print_text'}) eq 'ARRAY' ) { - $data = $args{'print_text'}; - } else { - $data = [ $self->print_text(\%args) ]; - } - - } - - $alternative->attach( - 'Type' => 'text/plain', - 'Encoding' => 'quoted-printable', - #'Encoding' => '7bit', - 'Data' => $data, - 'Disposition' => 'inline', - ); - - - my $htmldata; - my $image = ''; - my $barcode = ''; - if ( $conf->exists('invoice_email_pdf') - and scalar($conf->config('invoice_email_pdf_note')) ) { - - $htmldata = join('
', $conf->config('invoice_email_pdf_note') ); - - } else { - - $args{'from'} =~ /\@([\w\.\-]+)/; - my $from = $1 || 'example.com'; - my $content_id = join('.', rand()*(2**32), $$, time). "\@$from"; - - my $logo; - my $agentnum = $cust_main->agentnum; - if ( defined($args{'template'}) && length($args{'template'}) - && $conf->exists( 'logo_'. $args{'template'}. '.png', $agentnum ) - ) - { - $logo = 'logo_'. $args{'template'}. '.png'; - } else { - $logo = "logo.png"; - } - my $image_data = $conf->config_binary( $logo, $agentnum); - - $image = build MIME::Entity - 'Type' => 'image/png', - 'Encoding' => 'base64', - 'Data' => $image_data, - 'Filename' => 'logo.png', - 'Content-ID' => "<$content_id>", - ; - - if ($conf->exists('invoice-barcode')) { - my $barcode_content_id = join('.', rand()*(2**32), $$, time). "\@$from"; - $barcode = build MIME::Entity - 'Type' => 'image/png', - 'Encoding' => 'base64', - 'Data' => $self->invoice_barcode(0), - 'Filename' => 'barcode.png', - 'Content-ID' => "<$barcode_content_id>", - ; - $args{'barcode_cid'} = $barcode_content_id; - } - - $htmldata = $self->print_html({ 'cid'=>$content_id, %args }); - } - - $alternative->attach( - 'Type' => 'text/html', - 'Encoding' => 'quoted-printable', - 'Data' => [ '', - ' ', - ' ', - ' '. encode_entities($return{'subject'}), - ' ', - ' ', - ' ', - $htmldata, - ' ', - '', - ], - 'Disposition' => 'inline', - #'Filename' => 'invoice.pdf', - ); - - - my @otherparts = (); - if ( $cust_main->email_csv_cdr ) { - - push @otherparts, build MIME::Entity - 'Type' => 'text/csv', - 'Encoding' => '7bit', - 'Data' => [ map { "$_\n" } - $self->call_details('prepend_billed_number' => 1) - ], - 'Disposition' => 'attachment', - 'Filename' => 'usage-'. $self->invnum. '.csv', - ; - - } - - if ( $conf->exists('invoice_email_pdf') ) { - - #attaching pdf too: - # multipart/mixed - # multipart/related - # multipart/alternative - # text/plain - # text/html - # image/png - # application/pdf - - my $related = build MIME::Entity 'Type' => 'multipart/related', - 'Encoding' => '7bit'; - - #false laziness w/Misc::send_email - $related->head->replace('Content-type', - $related->mime_type. - '; boundary="'. $related->head->multipart_boundary. '"'. - '; type=multipart/alternative' - ); - - $related->add_part($alternative); - - $related->add_part($image) if $image; - - my $pdf = build MIME::Entity $self->mimebuild_pdf(\%args); - - $return{'mimeparts'} = [ $related, $pdf, @otherparts ]; - - } else { - - #no other attachment: - # multipart/related - # multipart/alternative - # text/plain - # text/html - # image/png - - $return{'content-type'} = 'multipart/related'; - if ($conf->exists('invoice-barcode') && $barcode) { - $return{'mimeparts'} = [ $alternative, $image, $barcode, @otherparts ]; - } else { - $return{'mimeparts'} = [ $alternative, $image, @otherparts ]; - } - $return{'type'} = 'multipart/alternative'; #Content-Type of first part... - #$return{'disposition'} = 'inline'; - - } - - } else { - - if ( $conf->exists('invoice_email_pdf') ) { - warn "$me creating PDF attachment" - if $DEBUG; - - #mime parts arguments a la MIME::Entity->build(). - $return{'mimeparts'} = [ - { $self->mimebuild_pdf(\%args) } - ]; - } - - if ( $conf->exists('invoice_email_pdf') - and scalar($conf->config('invoice_email_pdf_note')) ) { - - warn "$me using 'invoice_email_pdf_note'" - if $DEBUG; - $return{'body'} = [ map { $_ . "\n" } - $conf->config('invoice_email_pdf_note') - ]; - - } else { - - warn "$me not using 'invoice_email_pdf_note'" - if $DEBUG; - if ( ref($args{'print_text'}) eq 'ARRAY' ) { - $return{'body'} = $args{'print_text'}; - } else { - $return{'body'} = [ $self->print_text(\%args) ]; - } - - } - - } - - %return; - -} - -=item mimebuild_pdf - -Returns a list suitable for passing to MIME::Entity->build(), representing -this invoice as PDF attachment. - -=cut - -sub mimebuild_pdf { - my $self = shift; - ( - 'Type' => 'application/pdf', - 'Encoding' => 'base64', - 'Data' => [ $self->print_pdf(@_) ], - 'Disposition' => 'attachment', - 'Filename' => 'invoice-'. $self->invnum. '.pdf', - ); -} - =item send HASHREF Sends this invoice to the destinations configured for this customer: sends @@ -1325,7 +1036,7 @@ I