X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_main.pm;h=5d6826f485e6b14c003ccb15ce0e4391e149469d;hb=1c2b820bd8d41fb9620e81faf617adffe85dbd33;hp=1c6284976dc6d81b84f5b6b501815612f026544a;hpb=3b6c827909d8cf04949b58a683d0e9e5a7e8e723;p=freeside.git diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 1c6284976..5d6826f48 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -45,6 +45,7 @@ use FS::cust_refund; use FS::part_referral; use FS::cust_main_county; use FS::cust_location; +use FS::cust_class; use FS::cust_main_exemption; use FS::cust_tax_adjustment; use FS::tax_rate; @@ -1537,6 +1538,7 @@ sub check { || $self->ut_number('agentnum') || $self->ut_textn('agent_custid') || $self->ut_number('refnum') + || $self->ut_foreign_keyn('classnum', 'cust_class', 'classnum') || $self->ut_textn('custbatch') || $self->ut_name('last') || $self->ut_name('first') @@ -2305,6 +2307,53 @@ sub agent { qsearchs( 'agent', { 'agentnum' => $self->agentnum } ); } +=item cust_class + +Returns the customer class, as an FS::cust_class object, or the empty string +if there is no customer class. + +=cut + +sub cust_class { + my $self = shift; + if ( $self->classnum ) { + qsearchs('cust_class', { 'classnum' => $self->classnum } ); + } else { + return ''; + } +} + +=item categoryname + +Returns the customer category name, or the empty string if there is no customer +category. + +=cut + +sub categoryname { + my $self = shift; + my $cust_class = $self->cust_class; + $cust_class + ? $cust_class->categoryname + : ''; +} + +=item classname + +Returns the customer class name, or the empty string if there is no customer +class. + +=cut + +sub classname { + my $self = shift; + my $cust_class = $self->cust_class; + $cust_class + ? $cust_class->classname + : ''; +} + + =item bill_and_collect Cancels and suspends any packages due, generates bills, applies payments and @@ -2623,6 +2672,141 @@ sub bill { } + my $listref_or_error = + $self->calculate_taxes( \@cust_bill_pkg, \%taxlisthash, $invoice_time); + + unless ( ref( $listref_or_error ) ) { + $dbh->rollback if $oldAutoCommit; + return $listref_or_error; + } + + foreach my $taxline ( @$listref_or_error ) { + $total_setup = sprintf('%.2f', $total_setup+$taxline->setup ); + push @cust_bill_pkg, $taxline; + } + + #add tax adjustments + warn "adding tax adjustments...\n" if $DEBUG > 2; + foreach my $cust_tax_adjustment ( + qsearch('cust_tax_adjustment', { 'custnum' => $self->custnum, + 'billpkgnum' => '', + } + ) + ) { + + my $tax = sprintf('%.2f', $cust_tax_adjustment->amount ); + + my $itemdesc = $cust_tax_adjustment->taxname; + $itemdesc = '' if $itemdesc eq 'Tax'; + + push @cust_bill_pkg, new FS::cust_bill_pkg { + 'pkgnum' => 0, + 'setup' => $tax, + 'recur' => 0, + 'sdate' => '', + 'edate' => '', + 'itemdesc' => $itemdesc, + 'itemcomment' => $cust_tax_adjustment->comment, + 'cust_tax_adjustment' => $cust_tax_adjustment, + #'cust_bill_pkg_tax_location' => \@cust_bill_pkg_tax_location, + }; + + } + + my $charged = sprintf('%.2f', $total_setup + $total_recur ); + + my @cust_bill = $self->cust_bill; + my $balance = $self->balance; + my $previous_balance = scalar(@cust_bill) + ? ( $cust_bill[$#cust_bill]->billing_balance || 0 ) + : 0; + + $previous_balance += $cust_bill[$#cust_bill]->charged + if scalar(@cust_bill); + #my $balance_adjustments = + # sprintf('%.2f', $balance - $prior_prior_balance - $prior_charged); + + #create the new invoice + my $cust_bill = new FS::cust_bill ( { + 'custnum' => $self->custnum, + '_date' => ( $invoice_time ), + 'charged' => $charged, + 'billing_balance' => $balance, + 'previous_balance' => $previous_balance, + 'invoice_terms' => $options{'invoice_terms'}, + } ); + $error = $cust_bill->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't create invoice for customer #". $self->custnum. ": $error"; + } + + foreach my $cust_bill_pkg ( @cust_bill_pkg ) { + $cust_bill_pkg->invnum($cust_bill->invnum); + my $error = $cust_bill_pkg->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't create invoice line item: $error"; + } + } + + + foreach my $hook ( @precommit_hooks ) { + eval { + &{$hook}; #($self) ? + }; + if ( $@ ) { + $dbh->rollback if $oldAutoCommit; + return "$@ running precommit hook $hook\n"; + } + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; #no error +} + +=item calculate_taxes LINEITEMREF TAXHASHREF INVOICE_TIME + +This is a weird one. Perhaps it should not even be exposed. + +Generates tax line items (see L) for this customer. +Usually used internally by bill method B. + +If there is an error, returns the error, otherwise returns reference to a +list of line items suitable for insertion. + +=over 4 + +=item LINEITEMREF + +An array ref of the line items being billed. + +=item TAXHASHREF + +A strange beast. The keys to this hash are internal identifiers consisting +of the name of the tax object type, a space, and its unique identifier ( e.g. + 'cust_main_county 23' ). The values of the hash are listrefs. The first +item in the list is the tax object. The remaining items are either line +items or floating point values (currency amounts). + +The taxes are calculated on this entity. Calculated exemption records are +transferred to the LINEITEMREF items on the assumption that they are related. + +Read the source. + +=item INVOICE_TIME + +This specifies the date appearing on the associated invoice. Some +jurisdictions (i.e. Texas) have tax exemptions which are date sensitive. + +=back + +=cut +sub calculate_taxes { + my ($self, $cust_bill_pkg, $taxlisthash, $invoice_time) = @_; + + my @tax_line_items = (); + warn "having a look at the taxes we found...\n" if $DEBUG > 2; # keys are tax names (as printed on invoices / itemdesc ) @@ -2641,20 +2825,18 @@ sub bill { # values are listrefs of cust_bill_pkg_tax_rate_location hashrefs my %tax_rate_location = (); - foreach my $tax ( keys %taxlisthash ) { - my $tax_object = shift @{ $taxlisthash{$tax} }; + foreach my $tax ( keys %$taxlisthash ) { + my $tax_object = shift @{ $taxlisthash->{$tax} }; warn "found ". $tax_object->taxname. " as $tax\n" if $DEBUG > 2; - warn " ". join('/', @{ $taxlisthash{$tax} } ). "\n" if $DEBUG > 2; + warn " ". join('/', @{ $taxlisthash->{$tax} } ). "\n" if $DEBUG > 2; my $hashref_or_error = - $tax_object->taxline( $taxlisthash{$tax}, + $tax_object->taxline( $taxlisthash->{$tax}, 'custnum' => $self->custnum, 'invoice_time' => $invoice_time ); - unless ( ref($hashref_or_error) ) { - $dbh->rollback if $oldAutoCommit; - return $hashref_or_error; - } - unshift @{ $taxlisthash{$tax} }, $tax_object; + return $hashref_or_error unless ref($hashref_or_error); + + unshift @{ $taxlisthash->{$tax} }, $tax_object; my $name = $hashref_or_error->{'name'}; my $amount = $hashref_or_error->{'amount'}; @@ -2694,9 +2876,9 @@ sub bill { } #move the cust_tax_exempt_pkg records to the cust_bill_pkgs we will commit - my %packagemap = map { $_->pkgnum => $_ } @cust_bill_pkg; - foreach my $tax ( keys %taxlisthash ) { - foreach ( @{ $taxlisthash{$tax} }[1 ... scalar(@{ $taxlisthash{$tax} })] ) { + my %packagemap = map { $_->pkgnum => $_ } @$cust_bill_pkg; + foreach my $tax ( keys %$taxlisthash ) { + foreach ( @{ $taxlisthash->{$tax} }[1 ... scalar(@{ $taxlisthash->{$tax} })] ) { next unless ref($_) eq 'FS::cust_bill_pkg'; push @{ $packagemap{$_->pkgnum}->_cust_tax_exempt_pkg }, @@ -2726,7 +2908,6 @@ sub bill { next unless $tax; $tax = sprintf('%.2f', $tax ); - $total_setup = sprintf('%.2f', $total_setup+$tax ); my $pkg_category = qsearchs( 'pkg_category', { 'categoryname' => $taxname, 'disabled' => '', @@ -2745,7 +2926,7 @@ sub bill { } - push @cust_bill_pkg, new FS::cust_bill_pkg { + push @tax_line_items, new FS::cust_bill_pkg { 'pkgnum' => 0, 'setup' => $tax, 'recur' => 0, @@ -2759,88 +2940,9 @@ sub bill { } - #add tax adjustments - warn "adding tax adjustments...\n" if $DEBUG > 2; - foreach my $cust_tax_adjustment ( - qsearch('cust_tax_adjustment', { 'custnum' => $self->custnum, - 'billpkgnum' => '', - } - ) - ) { - - my $tax = sprintf('%.2f', $cust_tax_adjustment->amount ); - $total_setup = sprintf('%.2f', $total_setup+$tax ); - - my $itemdesc = $cust_tax_adjustment->taxname; - $itemdesc = '' if $itemdesc eq 'Tax'; - - push @cust_bill_pkg, new FS::cust_bill_pkg { - 'pkgnum' => 0, - 'setup' => $tax, - 'recur' => 0, - 'sdate' => '', - 'edate' => '', - 'itemdesc' => $itemdesc, - 'itemcomment' => $cust_tax_adjustment->comment, - 'cust_tax_adjustment' => $cust_tax_adjustment, - #'cust_bill_pkg_tax_location' => \@cust_bill_pkg_tax_location, - }; - - } - - my $charged = sprintf('%.2f', $total_setup + $total_recur ); - - my @cust_bill = $self->cust_bill; - my $balance = $self->balance; - my $previous_balance = scalar(@cust_bill) - ? ( $cust_bill[$#cust_bill]->billing_balance || 0 ) - : 0; - - $previous_balance += $cust_bill[$#cust_bill]->charged - if scalar(@cust_bill); - #my $balance_adjustments = - # sprintf('%.2f', $balance - $prior_prior_balance - $prior_charged); - - #create the new invoice - my $cust_bill = new FS::cust_bill ( { - 'custnum' => $self->custnum, - '_date' => ( $invoice_time ), - 'charged' => $charged, - 'billing_balance' => $balance, - 'previous_balance' => $previous_balance, - 'invoice_terms' => $options{'invoice_terms'}, - } ); - $error = $cust_bill->insert; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "can't create invoice for customer #". $self->custnum. ": $error"; - } - - foreach my $cust_bill_pkg ( @cust_bill_pkg ) { - $cust_bill_pkg->invnum($cust_bill->invnum); - my $error = $cust_bill_pkg->insert; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "can't create invoice line item: $error"; - } - } - - - foreach my $hook ( @precommit_hooks ) { - eval { - &{$hook}; #($self) ? - }; - if ( $@ ) { - $dbh->rollback if $oldAutoCommit; - return "$@ running precommit hook $hook\n"; - } - } - - $dbh->commit or die $dbh->errstr if $oldAutoCommit; - ''; #no error + \@tax_line_items; } - sub _make_lines { my ($self, %params) = @_; @@ -3122,15 +3224,24 @@ sub _handle_taxes { $taxhash{'taxclass'} = $part_pkg->taxclass; - my @taxes = qsearch( 'cust_main_county', \%taxhash ); - + my @taxes = (); my %taxhash_elim = %taxhash; + my @elim = qw( city county state ); + do { - my @elim = qw( taxclass city county state ); - while ( !scalar(@taxes) && scalar(@elim) ) { - $taxhash_elim{ shift(@elim) } = ''; + #first try a match with taxclass @taxes = qsearch( 'cust_main_county', \%taxhash_elim ); - } + + if ( !scalar(@taxes) && $taxhash_elim{'taxclass'} ) { + #then try a match without taxclass + my %no_taxclass = %taxhash_elim; + $no_taxclass{ 'taxclass' } = ''; + @taxes = qsearch( 'cust_main_county', \%no_taxclass ); + } + + $taxhash_elim{ shift(@elim) } = ''; + + } while ( !scalar(@taxes) && scalar(@elim) ); @taxes = grep { ! $_->taxname or ! $self->tax_exemption($_->taxname) } @taxes @@ -8148,6 +8259,10 @@ listref of start date, end date listref +=item paydate_year + +=item paydate_month + =item current_balance listref (list returned by FS::UI::Web::parse_lt_gt($cgi, 'current_balance')) @@ -8226,6 +8341,21 @@ sub search_sql { } ### + # classnum + ### + + my @classnum = grep /^(\d*)$/, @{ $params->{'classnum'} }; + if ( @classnum ) { + push @where, '( '. join(' OR ', map { + $_ ? "cust_main.classnum = $_" + : "cust_main.classnum IS NULL" + } + @classnum + ). + ' )'; + } + + ### # payby ### @@ -8234,6 +8364,23 @@ sub search_sql { push @where, '( '. join(' OR ', map "cust_main.payby = '$_'", @payby). ' )'; } + ### + # paydate_year / paydate_month + ### + + if ( $params->{'paydate_year'} =~ /^(\d{4})$/ ) { + my $year = $1; + $params->{'paydate_month'} =~ /^(\d\d?)$/ + or die "paydate_year without paydate_month?"; + my $month = $1; + + push @where, + 'paydate IS NOT NULL', + "paydate != ''", + "CAST(paydate AS timestamp) < CAST('$year-$month-01' AS timestamp )" +; + } + ## # amounts ##