X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_main.pm;h=ed16e1b9e21cc5e5e719cc0db8eb334a9c6cded6;hb=74cb9e1c3974d8899bf9745564d0dfce5875454c;hp=72b84504f06744b7c1d8410bb51fe317c31abf03;hpb=15e57a4859d967a13113602b112c4aa197ca6002;p=freeside.git diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 72b84504f..ed16e1b9e 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -40,6 +40,7 @@ use FS::cust_refund; use FS::part_referral; use FS::cust_main_county; use FS::cust_location; +use FS::cust_main_exemption; use FS::tax_rate; use FS::tax_rate_location; use FS::cust_tax_location; @@ -363,7 +364,7 @@ invoicing_list destination to the newly-created svc_acct. Here's an example: $cust_main->insert( {}, [ $email, 'POST' ] ); -Currently available options are: I and I. +Currently available options are: I, I and I. If I is set, all provisioning jobs will have a dependancy on the supplied jobnum (they will not run until the specific job completes). @@ -374,6 +375,9 @@ The I option is deprecated. If I is set true, no provisioning jobs (exports) are scheduled. (You can schedule them later with the B method.) +The I option can be set to an arrayref of tax names. +FS::cust_main_exemption records will be created and inserted. + =cut sub insert { @@ -459,6 +463,24 @@ sub insert { $self->invoicing_list( $invoicing_list ); } + warn " setting cust_main_exemption\n" + if $DEBUG > 1; + + my $tax_exemption = delete $options{'tax_exemption'}; + if ( $tax_exemption ) { + foreach my $taxname ( @$tax_exemption ) { + my $cust_main_exemption = new FS::cust_main_exemption { + 'custnum' => $self->custnum, + 'taxname' => $taxname, + }; + my $error = $cust_main_exemption->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "inserting cust_main_exemption (transaction rolled back): $error"; + } + } + } + if ( $conf->config('cust_main-skeleton_tables') && $conf->config('cust_main-skeleton_custnum') ) { @@ -1295,6 +1317,16 @@ sub delete { } } + foreach my $cust_main_exemption ( + qsearch( 'cust_main_exemption', { 'custnum' => $self->custnum } ) + ) { + my $error = $cust_main_exemption->delete; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + my $error = $self->SUPER::delete; if ( $error ) { $dbh->rollback if $oldAutoCommit; @@ -1306,7 +1338,8 @@ sub delete { } -=item replace [ OLD_RECORD ] [ INVOICING_LIST_ARYREF ] +=item replace [ OLD_RECORD ] [ INVOICING_LIST_ARYREF ] [ , OPTION => VALUE ... ] ] + Replaces the OLD_RECORD with this one in the database. If there is an error, returns the error, otherwise returns false. @@ -1318,6 +1351,11 @@ check_invoicing_list first. Here's an example: $new_cust_main->replace( $old_cust_main, [ $email, 'POST' ] ); +Currently available options are: I. + +The I option can be set to an arrayref of tax names. +FS::cust_main_exemption records will be deleted and inserted as appropriate. + =cut sub replace { @@ -1364,7 +1402,7 @@ sub replace { return $error; } - if ( @param ) { # INVOICING_LIST_ARYREF + if ( @param && ref($param[0]) eq 'ARRAY' ) { # INVOICING_LIST_ARYREF my $invoicing_list = shift @param; $error = $self->check_invoicing_list( $invoicing_list ); if ( $error ) { @@ -1374,6 +1412,40 @@ sub replace { $self->invoicing_list( $invoicing_list ); } + my %options = @param; + + my $tax_exemption = delete $options{'tax_exemption'}; + if ( $tax_exemption ) { + + my %cust_main_exemption = + map { $_->taxname => $_ } + qsearch('cust_main_exemption', { 'custnum' => $old->custnum } ); + + foreach my $taxname ( @$tax_exemption ) { + + next if delete $cust_main_exemption{$taxname}; + + my $cust_main_exemption = new FS::cust_main_exemption { + 'custnum' => $self->custnum, + 'taxname' => $taxname, + }; + my $error = $cust_main_exemption->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "inserting cust_main_exemption (transaction rolled back): $error"; + } + } + + foreach my $cust_main_exemption ( values %cust_main_exemption ) { + my $error = $cust_main_exemption->delete; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "deleting cust_main_exemption (transaction rolled back): $error"; + } + } + + } + if ( $self->payby =~ /^(CARD|CHEK|LECB)$/ && grep { $self->get($_) ne $old->get($_) } qw(payinfo paydate payname) ) { # card/check/lec info has changed, want to retry realtime_ invoice events @@ -2240,7 +2312,7 @@ sub bill_and_collect { #$options{actual_time} not $options{time} because freeside-daily -d is for #pre-printing invoices - $self->cancel_expired_pkgs( $options{actual_time} ); + $self->cancel_expired_pkgs( $options{actual_time} ); $self->suspend_adjourned_pkgs( $options{actual_time} ); my $error = $self->bill( %options ); @@ -2262,8 +2334,9 @@ sub bill_and_collect { sub cancel_expired_pkgs { my ( $self, $time ) = @_; - my @cancel_pkgs = grep { $_->expire && $_->expire <= $time } - $self->ncancelled_pkgs; + my @cancel_pkgs = $self->ncancelled_pkgs( { + 'extra_sql' => " AND expire IS NOT NULL AND expire > 0 AND expire <= $time " + } ); foreach my $cust_pkg ( @cancel_pkgs ) { my $cpr = $cust_pkg->last_cust_pkg_reason('expire'); @@ -2282,18 +2355,27 @@ sub cancel_expired_pkgs { sub suspend_adjourned_pkgs { my ( $self, $time ) = @_; - my @susp_pkgs = - grep { ! $_->susp - && ( ( $_->part_pkg->is_prepaid - && $_->bill - && $_->bill < $time - ) - || ( $_->adjourn - && $_->adjourn <= $time - ) - ) + my @susp_pkgs = $self->ncancelled_pkgs( { + 'extra_sql' => + " AND ( susp IS NULL OR susp = 0 ) + AND ( ( bill IS NOT NULL AND bill != 0 AND bill < $time ) + OR ( adjourn IS NOT NULL AND adjourn != 0 AND adjourn <= $time ) + ) + ", + } ); + + #only because there's no SQL test for is_prepaid :/ + @susp_pkgs = + grep { ( $_->part_pkg->is_prepaid + && $_->bill + && $_->bill < $time + ) + || ( $_->adjourn + && $_->adjourn <= $time + ) + } - $self->ncancelled_pkgs; + @susp_pkgs; foreach my $cust_pkg ( @susp_pkgs ) { my $cpr = $cust_pkg->last_cust_pkg_reason('adjourn') @@ -2382,11 +2464,7 @@ sub bill { my %taxlisthash; my @precommit_hooks = (); - my @cust_pkgs = qsearch('cust_pkg', { 'custnum' => $self->custnum } ); - foreach my $cust_pkg (@cust_pkgs) { - - #NO!! next if $cust_pkg->cancel; - next if $cust_pkg->getfield('cancel'); + foreach my $cust_pkg ( $self->ncancelled_pkgs ) { warn " bill package ". $cust_pkg->pkgnum. "\n" if $DEBUG > 1; @@ -2429,35 +2507,40 @@ sub bill { return ''; } - my $postal_pkg = $self->charge_postal_fee(); - if ( $postal_pkg && !ref( $postal_pkg ) ) { - $dbh->rollback if $oldAutoCommit; - return "can't charge postal invoice fee for customer ". - $self->custnum. ": $postal_pkg"; - } - if ( $postal_pkg && - ( scalar( grep { $_->recur && $_->recur > 0 } @cust_bill_pkg) || + if ( scalar( grep { $_->recur && $_->recur > 0 } @cust_bill_pkg) || !$conf->exists('postal_invoice-recurring_only') - ) ) { - foreach my $part_pkg ( $postal_pkg->part_pkg->self_and_bill_linked ) { - my $error = - $self->_make_lines( 'part_pkg' => $part_pkg, - 'cust_pkg' => $postal_pkg, - 'precommit_hooks' => \@precommit_hooks, - 'line_items' => \@cust_bill_pkg, - 'setup' => \$total_setup, - 'recur' => \$total_recur, - 'tax_matrix' => \%taxlisthash, - 'time' => $time, - 'options' => \%options, - ); - if ($error) { - $dbh->rollback if $oldAutoCommit; - return $error; + + my $postal_pkg = $self->charge_postal_fee(); + if ( $postal_pkg && !ref( $postal_pkg ) ) { + + $dbh->rollback if $oldAutoCommit; + return "can't charge postal invoice fee for customer ". + $self->custnum. ": $postal_pkg"; + + } elsif ( $postal_pkg ) { + + foreach my $part_pkg ( $postal_pkg->part_pkg->self_and_bill_linked ) { + my $error = + $self->_make_lines( 'part_pkg' => $part_pkg, + 'cust_pkg' => $postal_pkg, + 'precommit_hooks' => \@precommit_hooks, + 'line_items' => \@cust_bill_pkg, + 'setup' => \$total_setup, + 'recur' => \$total_recur, + 'tax_matrix' => \%taxlisthash, + 'time' => $time, + 'options' => \%options, + ); + if ($error) { + $dbh->rollback if $oldAutoCommit; + return $error; + } } + } + } warn "having a look at the taxes we found...\n" if $DEBUG > 2; @@ -2889,6 +2972,10 @@ sub _handle_taxes { @taxes = qsearch( 'cust_main_county', \%taxhash_elim ); } + @taxes = grep { ! $_->taxname or ! $self->tax_exemption($_->taxname) } + @taxes + if $self->cust_main_exemption; #just to be safe + if ( $conf->exists('tax-pkg_address') && $cust_pkg->locationnum ) { foreach (@taxes) { $_->set('pkgnum', $cust_pkg->pkgnum ); @@ -2901,12 +2988,12 @@ sub _handle_taxes { $taxes{'recur'} = [ @taxes ]; $taxes{$_} = [ @taxes ] foreach (@classes); - # maybe eliminate this entirely, along with all the 0% records - unless ( @taxes ) { - return - "fatal: can't find tax rate for state/county/country/taxclass ". - join('/', map $taxhash{$_}, qw(state county country taxclass) ); - } + # # maybe eliminate this entirely, along with all the 0% records + # unless ( @taxes ) { + # return + # "fatal: can't find tax rate for state/county/country/taxclass ". + # join('/', map $taxhash{$_}, qw(state county country taxclass) ); + # } } #if $conf->exists('enable_taxproducts') ... @@ -6043,6 +6130,22 @@ see L and L for conversion functions. sub total_owed_date { my $self = shift; my $time = shift; + +# my $custnum = $self->custnum; +# +# my $owed_sql = FS::cust_bill->owed_sql; +# +# my $sql = " +# SELECT SUM($owed_sql) FROM cust_bill +# WHERE custnum = $custnum +# AND _date <= $time +# "; +# +# my $sth = dbh->prepare($sql) or die dbh->errstr; +# $sth->execute() or die $sth->errstr; +# +# return sprintf( '%.2f', $sth->fetchrow_arrayref->[0] ); + my $total_bill = 0; foreach my $cust_bill ( grep { $_->_date <= $time } @@ -6051,6 +6154,7 @@ sub total_owed_date { $total_bill += $cust_bill->owed; } sprintf( "%.2f", $total_bill ); + } =item total_paid @@ -6276,6 +6380,28 @@ sub paydate_monthyear { } } +=item tax_exemption TAXNAME + +=cut + +sub tax_exemption { + my( $self, $taxname ) = @_; + + qsearchs( 'cust_main_exemption', { 'custnum' => $self->custnum, + 'taxname' => $taxname, + }, + ); +} + +=item cust_main_exemption + +=cut + +sub cust_main_exemption { + my $self = shift; + qsearch( 'cust_main_exemption', { 'custnum' => $self->custnum } ); +} + =item invoicing_list [ ARRAYREF ] If an arguement is given, sets these email addresses as invoice recipients @@ -6709,7 +6835,14 @@ customer. sub open_cust_bill { my $self = shift; - grep { $_->owed > 0 } $self->cust_bill; + + qsearch({ + 'table' => 'cust_bill', + 'hashref' => { 'custnum' => $self->custnum, }, + 'extra_sql' => ' AND '. FS::cust_bill->owed_sql. ' > 0', + 'order_by' => 'ORDER BY _date ASC', + }); + } =item cust_credit @@ -6756,7 +6889,7 @@ Returns all batched payments (see L) for this customer. sub cust_pay_batch { my $self = shift; - sort { $a->_date <=> $b->_date } + sort { $a->paybatchnum <=> $b->paybatchnum } qsearch( 'cust_pay_batch', { 'custnum' => $self->custnum } ) }