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;
|| $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')
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
}
+ 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<FS::cust_bill_pkg>) for this customer.
+Usually used internally by bill method B<bill>.
+
+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 )
# 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'};
}
#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 },
next unless $tax;
$tax = sprintf('%.2f', $tax );
- $total_setup = sprintf('%.2f', $total_setup+$tax );
my $pkg_category = qsearchs( 'pkg_category', { 'categoryname' => $taxname,
'disabled' => '',
}
- push @cust_bill_pkg, new FS::cust_bill_pkg {
+ push @tax_line_items, new FS::cust_bill_pkg {
'pkgnum' => 0,
'setup' => $tax,
'recur' => 0,
}
- #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) = @_;
$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
listref
+=item paydate_year
+
+=item paydate_month
+
=item current_balance
listref (list returned by FS::UI::Web::parse_lt_gt($cgi, 'current_balance'))
}
###
+ # classnum
+ ###
+
+ my @classnum = grep /^(\d*)$/, @{ $params->{'classnum'} };
+ if ( @classnum ) {
+ push @where, '( '. join(' OR ', map {
+ $_ ? "cust_main.classnum = $_"
+ : "cust_main.classnum IS NULL"
+ }
+ @classnum
+ ).
+ ' )';
+ }
+
+ ###
# payby
###
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
##