X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2Fcust_main.pm;h=efe94027dcbec702cde1e7fc0c2d40e14f9cb82d;hp=445c6951a0e4e5c776ba35af445ef33b88196663;hb=4329b50ca24073d39e16ce808ee49286309150e9;hpb=98a73bb080f55f4f5d850102bcec6da2807e3d4f diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 445c6951a..efe94027d 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -26,6 +26,8 @@ use FS::queue; use FS::part_pkg; use FS::part_bill_event; use FS::cust_bill_event; +use FS::cust_tax_exempt; +use FS::type_pkgs; use FS::Msgcat qw(gettext); @ISA = qw( FS::Record ); @@ -219,7 +221,8 @@ invoicing_list destination to the newly-created svc_acct. Here's an example: sub insert { my $self = shift; - my @param = @_; + my $cust_pkgs = @_ ? shift : {}; + my $invoicing_list = @_ ? shift : ''; local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -256,29 +259,39 @@ sub insert { my $error = $self->SUPER::insert; if ( $error ) { $dbh->rollback if $oldAutoCommit; - return "inserting cust_main record (transaction rolled back): $error"; + #return "inserting cust_main record (transaction rolled back): $error"; + return $error; } - if ( @param ) { # CUST_PKG_HASHREF - my $cust_pkgs = shift @param; - foreach my $cust_pkg ( keys %$cust_pkgs ) { - $cust_pkg->custnum( $self->custnum ); - $error = $cust_pkg->insert; + # invoicing list + if ( $invoicing_list ) { + $error = $self->check_invoicing_list( $invoicing_list ); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "checking invoicing_list (transaction rolled back): $error"; + } + $self->invoicing_list( $invoicing_list ); + } + + # packages + foreach my $cust_pkg ( keys %$cust_pkgs ) { + $cust_pkg->custnum( $self->custnum ); + $error = $cust_pkg->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "inserting cust_pkg (transaction rolled back): $error"; + } + foreach my $svc_something ( @{$cust_pkgs->{$cust_pkg}} ) { + $svc_something->pkgnum( $cust_pkg->pkgnum ); + if ( $seconds && $svc_something->isa('FS::svc_acct') ) { + $svc_something->seconds( $svc_something->seconds + $seconds ); + $seconds = 0; + } + $error = $svc_something->insert; if ( $error ) { $dbh->rollback if $oldAutoCommit; - return "inserting cust_pkg (transaction rolled back): $error"; - } - foreach my $svc_something ( @{$cust_pkgs->{$cust_pkg}} ) { - $svc_something->pkgnum( $cust_pkg->pkgnum ); - if ( $seconds && $svc_something->isa('FS::svc_acct') ) { - $svc_something->seconds( $svc_something->seconds + $seconds ); - $seconds = 0; - } - $error = $svc_something->insert; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "inserting svc_ (transaction rolled back): $error"; - } + #return "inserting svc_ (transaction rolled back): $error"; + return $error; } } } @@ -288,16 +301,6 @@ sub insert { return "No svc_acct record to apply pre-paid time"; } - if ( @param ) { # INVOICING_LIST_ARYREF - my $invoicing_list = shift @param; - $error = $self->check_invoicing_list( $invoicing_list ); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "checking invoicing_list (transaction rolled back): $error"; - } - $self->invoicing_list( $invoicing_list ); - } - if ( $amount ) { my $cust_credit = new FS::cust_credit { 'custnum' => $self->custnum, @@ -479,6 +482,32 @@ sub replace { $self->invoicing_list( $invoicing_list ); } + if ( $self->payby eq 'CARD' && + grep { $self->get($_) ne $old->get($_) } qw(payinfo paydate payname) ) { + # card info has changed, want to retry realtime_card invoice events + #false laziness w/collect + foreach my $cust_bill_event ( + grep { + #$_->part_bill_event->plan eq 'realtime-card' + $_->part_bill_event->eventcode eq '$cust_bill->realtime_card();' + && $_->status eq 'done' + && $_->statustext + } + map { $_->cust_bill_event } + grep { $_->cust_bill_event } + $self->open_cust_bill + + ) { + my $error = $cust_bill_event->retry; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "error scheduling invoice events for retry: $error"; + } + } + #eslaf + + } + #false laziness with sub insert my $queue = new FS::queue { 'job' => 'FS::cust_main::append_fuzzyfiles' }; $error = $queue->insert($self->getfield('last'), $self->company); @@ -681,14 +710,14 @@ sub check { } else { $self->paydate =~ /^(\d{1,2})[\/\-](\d{2}(\d{2})?)$/ or return "Illegal expiration date: ". $self->paydate; - if ( length($2) == 4 ) { - $self->paydate("$2-$1-01"); - } else { - $self->paydate("20$2-$1-01"); - } + my $y = length($2) == 4 ? $2 : "20$2"; + $self->paydate("$y-$1-01"); + my($nowm,$nowy)=(localtime(time))[4,5]; $nowm++; $nowy+=1900; + return gettext('expired_card') if $y<$nowy || ( $y==$nowy && $1<$nowm ); } - if ( $self->payname eq '' ) { + if ( $self->payname eq '' && + ( ! $conf->exists('require_cardname') || $self->payby ne 'CARD' ) ) { $self->payname( $self->first. " ". $self->getfield('last') ); } else { $self->payname =~ /^([\w \,\.\-\']+)$/ @@ -872,8 +901,11 @@ sub bill { # & generate invoice database. my( $total_setup, $total_recur ) = ( 0, 0 ); - my( $taxable_setup, $taxable_recur ) = ( 0, 0 ); + #my( $taxable_setup, $taxable_recur ) = ( 0, 0 ); my @cust_bill_pkg = (); + my $tax = 0;## + #my $taxable_charged = 0;## + #my $charged = 0;## foreach my $cust_pkg ( qsearch('cust_pkg', { 'custnum' => $self->custnum } ) @@ -886,7 +918,7 @@ sub bill { $cust_pkg->setfield('bill', '') unless defined($cust_pkg->bill); - my $part_pkg = qsearchs( 'part_pkg', { 'pkgpart' => $cust_pkg->pkgpart } ); + my $part_pkg = $cust_pkg->part_pkg; #so we don't modify cust_pkg record unnecessarily my $cust_pkg_mod_flag = 0; @@ -956,7 +988,7 @@ sub bill { # here $sdate = $cust_pkg->bill || $cust_pkg->setup || $time; - $mon += $part_pkg->getfield('freq'); + $mon += $part_pkg->freq; until ( $mon < 12 ) { $mon -= 12; $year++; } $cust_pkg->setfield('bill', timelocal($sec,$min,$hour,$mday,$mon,$year)); @@ -967,6 +999,7 @@ sub bill { warn "\$recur is undefined" unless defined($recur); warn "\$cust_pkg->bill is undefined" unless defined($cust_pkg->bill); + my $taxable_charged = 0; if ( $cust_pkg_mod_flag ) { $error=$cust_pkg->replace($old_cust_pkg); if ( $error ) { #just in case @@ -994,51 +1027,126 @@ sub bill { push @cust_bill_pkg, $cust_bill_pkg; $total_setup += $setup; $total_recur += $recur; - $taxable_setup += $setup - unless $part_pkg->dbdef_table->column('setuptax') - && $part_pkg->setuptax =~ /^Y$/i; - $taxable_recur += $recur - unless $part_pkg->dbdef_table->column('recurtax') - && $part_pkg->recurtax =~ /^Y$/i; - } - } - - } + $taxable_charged += $setup + unless $part_pkg->setuptax =~ /^Y$/i; + $taxable_charged += $recur + unless $part_pkg->recurtax =~ /^Y$/i; + + unless ( $self->tax =~ /Y/i + || $self->payby eq 'COMP' + || $taxable_charged == 0 ) { + + my $cust_main_county = + qsearchs('cust_main_county',{ + 'state' => $self->state, + 'county' => $self->county, + 'country' => $self->country, + 'taxclass' => $part_pkg->taxclass, + } ) + or qsearchs('cust_main_county',{ + 'state' => $self->state, + 'county' => $self->county, + 'country' => $self->country, + 'taxclass' => '', + } ) + or do { + $dbh->rollback if $oldAutoCommit; + return + "fatal: can't find tax rate for state/county/country/taxclass ". + join('/', map $self->$_(), qw(state county country taxclass) ). + "\n"; + }; + + if ( $cust_main_county->exempt_amount ) { + my ($mon,$year) = (localtime($sdate) )[4,5]; + $mon++; + my $freq = $part_pkg->freq || 1; + my $taxable_per_month = sprintf("%.2f", $taxable_charged / $freq ); + foreach my $which_month ( 1 .. $freq ) { + my %hash = ( + 'custnum' => $self->custnum, + 'taxnum' => $cust_main_county->taxnum, + 'year' => 1900+$year, + 'month' => $mon++, + ); + #until ( $mon < 12 ) { $mon -= 12; $year++; } + until ( $mon < 13 ) { $mon -= 12; $year++; } + my $cust_tax_exempt = + qsearchs('cust_tax_exempt', \%hash) + || new FS::cust_tax_exempt( { %hash, 'amount' => 0 } ); + my $remaining_exemption = sprintf("%.2f", + $cust_main_county->exempt_amount - $cust_tax_exempt->amount ); + if ( $remaining_exemption > 0 ) { + my $addl = $remaining_exemption > $taxable_per_month + ? $taxable_per_month + : $remaining_exemption; + $taxable_charged -= $addl; + my $new_cust_tax_exempt = new FS::cust_tax_exempt ( { + $cust_tax_exempt->hash, + 'amount' => sprintf("%.2f", $cust_tax_exempt->amount + $addl), + } ); + $error = $new_cust_tax_exempt->exemptnum + ? $new_cust_tax_exempt->replace($cust_tax_exempt) + : $new_cust_tax_exempt->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "fatal: can't update cust_tax_exempt: $error"; + } + + } # if $remaining_exemption > 0 + + } #foreach $which_month + + } #if $cust_main_county->exempt_amount + + $taxable_charged = sprintf( "%.2f", $taxable_charged); + $tax += $taxable_charged * $cust_main_county->tax / 100 + + } #unless $self->tax =~ /Y/i + # || $self->payby eq 'COMP' + # || $taxable_charged == 0 + + } #if $setup > 0 || $recur > 0 + + } #if $cust_pkg_mod_flag + + } #foreach my $cust_pkg my $charged = sprintf( "%.2f", $total_setup + $total_recur ); - my $taxable_charged = sprintf( "%.2f", $taxable_setup + $taxable_recur ); +# my $taxable_charged = sprintf( "%.2f", $taxable_setup + $taxable_recur ); - unless ( @cust_bill_pkg ) { + unless ( @cust_bill_pkg ) { #don't create invoices with no line items $dbh->commit or die $dbh->errstr if $oldAutoCommit; return ''; } - unless ( $self->tax =~ /Y/i - || $self->payby eq 'COMP' - || $taxable_charged == 0 ) { - my $cust_main_county = qsearchs('cust_main_county',{ - 'state' => $self->state, - 'county' => $self->county, - 'country' => $self->country, - } ) or die "fatal: can't find tax rate for state/county/country ". - $self->state. "/". $self->county. "/". $self->country. "\n"; - my $tax = sprintf( "%.2f", - $taxable_charged * ( $cust_main_county->getfield('tax') / 100 ) - ); - - if ( $tax > 0 ) { - $charged = sprintf( "%.2f", $charged+$tax ); - - my $cust_bill_pkg = new FS::cust_bill_pkg ({ - 'pkgnum' => 0, - 'setup' => $tax, - 'recur' => 0, - 'sdate' => '', - 'edate' => '', - }); - push @cust_bill_pkg, $cust_bill_pkg; - } +# unless ( $self->tax =~ /Y/i +# || $self->payby eq 'COMP' +# || $taxable_charged == 0 ) { +# my $cust_main_county = qsearchs('cust_main_county',{ +# 'state' => $self->state, +# 'county' => $self->county, +# 'country' => $self->country, +# } ) or die "fatal: can't find tax rate for state/county/country ". +# $self->state. "/". $self->county. "/". $self->country. "\n"; +# my $tax = sprintf( "%.2f", +# $taxable_charged * ( $cust_main_county->getfield('tax') / 100 ) +# ); + + $tax = sprintf("%.2f", $tax); + if ( $tax > 0 ) { + $charged = sprintf( "%.2f", $charged+$tax ); + + my $cust_bill_pkg = new FS::cust_bill_pkg ({ + 'pkgnum' => 0, + 'setup' => $tax, + 'recur' => 0, + 'sdate' => '', + 'edate' => '', + }); + push @cust_bill_pkg, $cust_bill_pkg; } +# } my $cust_bill = new FS::cust_bill ( { 'custnum' => $self->custnum, @@ -1089,6 +1197,8 @@ invoice_time - Use this time when deciding when to print invoices and late notices on those invoices. The default is now. It is specified as a UNIX timestamp; see L). Also see L and L for conversion functions. +retry_card - Retry cards even when not scheduled by invoice events. + batch_card - This option is deprecated. See the invoice events web interface to control whether cards are batched or run against a realtime gateway. @@ -1121,9 +1231,29 @@ sub collect { return ''; } - foreach my $cust_bill ( - qsearch('cust_bill', { 'custnum' => $self->custnum, } ) - ) { + if ( exists($options{'retry_card'}) && $options{'retry_card'} ) { + #false laziness w/replace + foreach my $cust_bill_event ( + grep { + #$_->part_bill_event->plan eq 'realtime-card' + $_->part_bill_event->eventcode eq '$cust_bill->realtime_card();' + && $_->status eq 'done' + && $_->statustext + } + map { $_->cust_bill_event } + grep { $_->cust_bill_event } + $self->open_cust_bill + ) { + my $error = $cust_bill_event->retry; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "error scheduling invoice events for retry: $error"; + } + } + #eslaf + } + + foreach my $cust_bill ( $self->cust_bill ) { #this has to be before next's my $amount = sprintf( "%.2f", $balance < $cust_bill->owed @@ -1141,6 +1271,7 @@ sub collect { next unless $amount > 0; + foreach my $part_bill_event ( sort { $a->seconds <=> $b->seconds || $a->weight <=> $b->weight @@ -1613,17 +1744,80 @@ the error, otherwise returns false. sub charge { my ( $self, $amount, $pkg, $comment ) = @_; + 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 $part_pkg = new FS::part_pkg ( { 'pkg' => $pkg || 'One-time charge', - 'comment' => $comment || '$'. sprintf("%.2f".$amount), + 'comment' => $comment || '$'. sprintf("%.2f",$amount), 'setup' => $amount, 'freq' => 0, 'recur' => '0', 'disabled' => 'Y', } ); - $part_pkg->insert; + my $error = $part_pkg->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + + my $pkgpart = $part_pkg->pkgpart; + my %type_pkgs = ( 'typenum' => $self->agent->typenum, 'pkgpart' => $pkgpart ); + unless ( qsearchs('type_pkgs', \%type_pkgs ) ) { + my $type_pkgs = new FS::type_pkgs \%type_pkgs; + $error = $type_pkgs->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + my $cust_pkg = new FS::cust_pkg ( { + 'custnum' => $self->custnum, + 'pkgpart' => $pkgpart, + } ); + + $error = $cust_pkg->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; + +} + +=item cust_bill + +Returns all the invoices (see L) for this customer. + +=cut + +sub cust_bill { + my $self = shift; + qsearch('cust_bill', { 'custnum' => $self->custnum, } ) +} + +=item open_cust_bill + +Returns all the open (owed > 0) invoices (see L) for this +customer. + +=cut + +sub open_cust_bill { + my $self = shift; + grep { $_->owed > 0 } $self->cust_bill; } =back