[freeside-commits] freeside/FS/FS cust_tax_location.pm, NONE, 1.1 part_pkg_taxoverride.pm, NONE, 1.1 part_pkg_taxproduct.pm, NONE, 1.1 part_pkg_taxrate.pm, NONE, 1.1 tax_class.pm, NONE, 1.1 tax_rate.pm, NONE, 1.1 Conf.pm, 1.223, 1.224 Schema.pm, 1.80, 1.81 part_pkg.pm, 1.61, 1.62
Jeff Finucane,420,,
jeff at wavetail.420.am
Mon Mar 31 17:54:44 PDT 2008
- Previous message: [freeside-commits] freeside/FS/t cust_tax_location.t, NONE, 1.1 part_pkg_taxoverride.t, NONE, 1.1 part_pkg_taxproduct.t, NONE, 1.1 part_pkg_taxrate.t, NONE, 1.1 tax_class.t, NONE, 1.1 tax_rate.t, NONE, 1.1
- Next message: [freeside-commits] freeside/httemplate/browse tax_rate.cgi, NONE, 1.1
- Messages sorted by:
[ date ]
[ thread ]
[ subject ]
[ author ]
Update of /home/cvs/cvsroot/freeside/FS/FS
In directory wavetail.420.am:/tmp/cvs-serv19578/FS/FS
Modified Files:
Conf.pm Schema.pm part_pkg.pm
Added Files:
cust_tax_location.pm part_pkg_taxoverride.pm
part_pkg_taxproduct.pm part_pkg_taxrate.pm tax_class.pm
tax_rate.pm
Log Message:
checkpoint of new tax rating system
Index: Conf.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/Conf.pm,v
retrieving revision 1.223
retrieving revision 1.224
diff -u -d -r1.223 -r1.224
--- Conf.pm 30 Mar 2008 01:26:13 -0000 1.223
+++ Conf.pm 1 Apr 2008 00:54:41 -0000 1.224
@@ -1406,6 +1406,13 @@
},
{
+ 'key' => 'enable_taxproducts',
+ 'section' => 'billing',
+ 'description' => 'Enable per-package mapping to new style tax classes',
+ 'type' => 'checkbox',
+ },
+
+ {
'key' => 'welcome_email',
'section' => '',
'description' => 'Template file for welcome email. Welcome emails are sent to the customer email invoice destination(s) each time a svc_acct record is created. See the <a href="http://search.cpan.org/~mjd/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation for details on the template substitution language. The following variables are available<ul><li><code>$username</code> <li><code>$password</code> <li><code>$first</code> <li><code>$last</code> <li><code>$pkg</code></ul>',
--- NEW FILE: part_pkg_taxproduct.pm ---
package FS::part_pkg_taxproduct;
use strict;
use vars qw( @ISA );
use FS::Record;
@ISA = qw(FS::Record);
=head1 NAME
FS::part_pkg_taxproduct - Object methods for part_pkg_taxproduct records
=head1 SYNOPSIS
use FS::part_pkg_taxproduct;
$record = new FS::part_pkg_taxproduct \%hash;
$record = new FS::part_pkg_taxproduct { 'column' => 'value' };
$error = $record->insert;
$error = $new_record->replace($old_record);
$error = $record->delete;
$error = $record->check;
=head1 DESCRIPTION
An FS::part_pkg_taxproduct object represents a tax product.
FS::part_pkg_taxproduct inherits from FS::Record. The following fields are
currently supported:
=over 4
=item taxproductnum
Primary key
=item data_vendor
Tax data vendor
=item taxproduct
Tax product id from the vendor
=item description
A human readable description of the id in taxproduct
=back
=head1 METHODS
=over 4
=item new HASHREF
Creates a new tax product. To add the tax product to the database, see L<"insert">.
Note that this stores the hash reference, not a distinct copy of the hash it
points to. You can ask the object for a copy with the I<hash> method.
=cut
sub table { 'part_pkg_taxproduct'; }
=item insert
Adds this record to the database. If there is an error, returns the error,
otherwise returns false.
=cut
=item delete
Delete this record from the database.
=cut
=item replace OLD_RECORD
Replaces the OLD_RECORD with this one in the database. If there is an error,
returns the error, otherwise returns false.
=cut
=item check
Checks all fields to make sure this is a valid tax product. If there is
an error, returns the error, otherwise returns false. Called by the insert
and replace methods.
=cut
sub check {
my $self = shift;
my $error =
$self->ut_numbern('taxproductnum')
|| $self->ut_textn('data_vendor')
|| $self->ut_text('taxproduct')
|| $self->ut_textn('description')
;
return $error if $error;
$self->SUPER::check;
}
=back
=cut
=head1 BUGS
=head1 SEE ALSO
L<FS::Record>, schema.html from the base documentation.
=cut
1;
--- NEW FILE: part_pkg_taxrate.pm ---
package FS::part_pkg_taxrate;
use strict;
use vars qw( @ISA );
use Date::Parse;
use FS::UID qw(dbh);
use FS::Record qw( qsearch qsearchs );
use FS::part_pkg_taxproduct;
@ISA = qw(FS::Record);
=head1 NAME
FS::part_pkg_taxrate - Object methods for part_pkg_taxrate records
=head1 SYNOPSIS
use FS::part_pkg_taxrate;
$record = new FS::part_pkg_taxrate \%hash;
$record = new FS::part_pkg_taxrate { 'column' => 'value' };
$error = $record->insert;
$error = $new_record->replace($old_record);
$error = $record->delete;
$error = $record->check;
=head1 DESCRIPTION
An FS::part_pkg_taxrate object maps packages onto tax rates.
FS::part_pkg_taxrate inherits from FS::Record. The following fields are
currently supported:
=over 4
=item pkgtaxratenum
Primary key
=item data_vendor
Tax data vendor
=item geocode
Tax vendor location code
=item taxproductnum
Class of package for tax purposes, Index into FS::part_pkg_taxproduct
=item city
city
=item county
county
=item state
state
=item local
local
=item country
country
=item taxclassnum
Class of tax index into FS::tax_taxclass and FS::tax_rate
=item taxclassnumtaxed
Class of tax taxed by this entry.
=item taxable
taxable
=item effdate
effdate
=back
=head1 METHODS
=over 4
=item new HASHREF
Creates a new customer (location), package, tax rate mapping. To add the
mapping to the database, see L<"insert">.
Note that this stores the hash reference, not a distinct copy of the hash it
points to. You can ask the object for a copy with the I<hash> method.
=cut
sub table { 'part_pkg_taxrate'; }
=item insert
Adds this record to the database. If there is an error, returns the error,
otherwise returns false.
=cut
=item delete
Delete this record from the database.
=cut
=item replace OLD_RECORD
Replaces the OLD_RECORD with this one in the database. If there is an error,
returns the error, otherwise returns false.
=cut
=item check
Checks all fields to make sure this is a valid tax rate mapping. If there is
an error, returns the error, otherwise returns false. Called by the insert
and replace methods.
=cut
sub check {
my $self = shift;
my $error =
$self->ut_numbern('pkgtaxratenum')
|| $self->ut_textn('data_vendor')
|| $self->ut_textn('geocode')
|| $self->
ut_foreign_key('taxproductnum', 'part_pkg_taxproduct', 'taxproductnum')
|| $self->ut_textn('city')
|| $self->ut_textn('county')
|| $self->ut_textn('state')
|| $self->ut_textn('local')
|| $self->ut_text('country')
|| $self->ut_foreign_keyn('taxclassnumtaxed', 'tax_class', 'taxclassnum')
|| $self->ut_foreign_key('taxclassnum', 'tax_class', 'taxclassnum')
|| $self->ut_numbern('effective_date')
|| $self->ut_enum('taxable', [ 'Y', '' ])
;
return $error if $error;
$self->SUPER::check;
}
=item batch_import
Loads part_pkg_taxrate records from an external CSV file. If there is
an error, returns the error, otherwise returns false.
=cut
sub batch_import {
my $param = shift;
my $fh = $param->{filehandle};
my $format = $param->{'format'};
my @fields;
my $hook;
if ( $format eq 'cch' ) {
@fields = qw( city county state local geocode group groupdesc item
itemdesc provider customer taxtypetaxed taxcattaxed
taxable taxtype taxcat effdate rectype );
$hook = sub {
my $hash = shift;
unless ( $hash->{'rectype'} eq 'R' or $hash->{'rectype'} eq 'T' ) {
delete($hash->{$_}) for (keys %$hash);
return;
}
my %providers = ( '00' => 'Regulated LEC',
'01' => 'Regulated IXC',
'02' => 'Unregulated LEC',
'03' => 'Unregulated IXC',
'04' => 'ISP',
'05' => 'Wireless',
);
my %customers = ( '00' => 'Residential',
'01' => 'Commercial',
'02' => 'Industrial',
'09' => 'Lifeline',
'10' => 'Senior Citizen',
);
my $taxproduct =
join(':', map{ $hash->{$_} } qw(group item provider customer ) );
my %part_pkg_taxproduct = ( 'data_vendor' => 'cch',
'taxproduct' => $taxproduct,
);
my $part_pkg_taxproduct = qsearchs( 'part_pkg_taxproduct',
{ %part_pkg_taxproduct }
);
unless ($part_pkg_taxproduct) {
$part_pkg_taxproduct{'description'} =
join(' : ', map{ $hash->{$_} } qw(groupdesc itemdesc),
$providers{$hash->{'provider'}} || 'Unknown',
$customers{$hash->{'customer'}} || 'Unknown',
);
$part_pkg_taxproduct = new FS::part_pkg_taxproduct \%part_pkg_taxproduct;
my $error = $part_pkg_taxproduct->insert;
return "Error inserting tax product (part_pkg_taxproduct): $error"
if $error;
}
$hash->{'taxproductnum'} = $part_pkg_taxproduct->taxproductnum;
delete($hash->{$_})
for qw(group groupdesc item itemdesc provider customer rectype );
my %map = ( 'taxclassnum' => [ 'taxtype', 'taxcat' ],
'taxclassnumtaxed' => [ 'taxtypetaxed', 'taxcattaxed' ],
);
for my $item (keys %map) {
my $tax_class =
qsearchs( 'tax_class',
{ data_vendor => 'cch',
'taxclass' => join(':', map($hash->{$_}, @{$map{$item}})),
}
);
$hash->{$item} = $tax_class->taxclassnum
if $tax_class;
delete($hash->{$_}) foreach @{$map{$item}};
}
$hash->{'effdate'} = str2time($hash->{'effdate'});
$hash->{'effdate'} = str2time($hash->{'effdate'});
$hash->{'country'} = 'US'; # CA is available
delete($hash->{'taxable'}) if ($hash->{'taxable'} eq 'N');
'';
};
} elsif ( $format eq 'extended' ) {
die "unimplemented\n";
@fields = qw( );
$hook = sub {};
} else {
die "unknown format $format";
}
eval "use Text::CSV_XS;";
die $@ if $@;
my $csv = new Text::CSV_XS;
my $imported = 0;
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 $line;
while ( defined($line=<$fh>) ) {
$csv->parse($line) or do {
$dbh->rollback if $oldAutoCommit;
return "can't parse: ". $csv->error_input();
};
my @columns = $csv->fields();
my %part_pkg_taxrate = ( 'data_vendor' => $format );
foreach my $field ( @fields ) {
$part_pkg_taxrate{$field} = shift @columns;
}
my $error = &{$hook}(\%part_pkg_taxrate);
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
}
next unless scalar(keys %part_pkg_taxrate);
my $part_pkg_taxrate = new FS::part_pkg_taxrate( \%part_pkg_taxrate );
$error = $part_pkg_taxrate->insert;
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return "can't insert part_pkg_taxrate for $line: $error";
}
$imported++;
}
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
return "Empty file!" unless $imported;
''; #no error
}
=back
=head1 BUGS
=head1 SEE ALSO
L<FS::Record>, schema.html from the base documentation.
=cut
1;
--- NEW FILE: part_pkg_taxoverride.pm ---
package FS::part_pkg_taxoverride;
use strict;
use vars qw( @ISA );
use FS::Record;
@ISA = qw(FS::Record);
=head1 NAME
FS::part_pkg_taxoverride - Object methods for part_pkg_taxoverride records
=head1 SYNOPSIS
use FS::part_pkg_taxoverride;
$record = new FS::part_pkg_taxoverride \%hash;
$record = new FS::part_pkg_taxoverride { 'column' => 'value' };
$error = $record->insert;
$error = $new_record->replace($old_record);
$error = $record->delete;
$error = $record->check;
=head1 DESCRIPTION
An FS::part_pkg_taxoverride object represents a manual mapping of a
package to tax rates. FS::part_pkg_taxoverride inherits from FS::Record.
The following fields are currently supported:
=over 4
=item taxoverridenum
Primary key
=item pkgpart
The package definition id
=item taxnum
The tax rate definition id
=back
=head1 METHODS
=over 4
=item new HASHREF
Creates a new tax override. To add the tax product to the database, see L<"insert">.
Note that this stores the hash reference, not a distinct copy of the hash it
points to. You can ask the object for a copy with the I<hash> method.
=cut
sub table { 'part_pkg_taxoverride'; }
=item insert
Adds this record to the database. If there is an error, returns the error,
otherwise returns false.
=cut
=item delete
Delete this record from the database.
=cut
=item replace OLD_RECORD
Replaces the OLD_RECORD with this one in the database. If there is an error,
returns the error, otherwise returns false.
=cut
=item check
Checks all fields to make sure this is a valid tax product. If there is
an error, returns the error, otherwise returns false. Called by the insert
and replace methods.
=cut
sub check {
my $self = shift;
my $error =
$self->ut_numbern('taxoverridenum')
|| $self->ut_foreign_key('pkgpart', 'part_pkg', 'pkgpart')
|| $self->ut_foreign_key('taxnum', 'tax_rate', 'taxnum')
;
return $error if $error;
$self->SUPER::check;
}
=back
=cut
=head1 BUGS
=head1 SEE ALSO
L<FS::Record>, schema.html from the base documentation.
=cut
1;
Index: part_pkg.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/part_pkg.pm,v
retrieving revision 1.61
retrieving revision 1.62
diff -u -d -r1.61 -r1.62
--- part_pkg.pm 24 Sep 2007 00:56:50 -0000 1.61
+++ part_pkg.pm 1 Apr 2008 00:54:42 -0000 1.62
@@ -14,6 +14,8 @@
use FS::part_pkg_option;
use FS::pkg_class;
use FS::agent;
+use FS::part_pkg_taxoverride;
+use FS::part_pkg_taxproduct;
@ISA = qw( FS::m2m_Common FS::Record ); # FS::option_Common ); # this can use option_Common
# when all the plandata bs is
@@ -726,6 +728,34 @@
'';
}
+=item part_pkg_taxoverride
+
+Returns all options as FS::part_pkg_taxoverride objects (see
+L<FS::part_pkg_taxoverride>).
+
+=cut
+
+sub part_pkg_taxoverride {
+ my $self = shift;
+ qsearch('part_pkg_taxoverride', { 'pkgpart' => $self->pkgpart } );
+}
+
+=item taxproduct_description
+
+Returns the description of the associated tax product for this package
+definition (see L<FS::part_pkg_taxproduct>).
+
+=cut
+
+sub taxproduct_description {
+ my $self = shift;
+ my $part_pkg_taxproduct =
+ qsearchs( 'part_pkg_taxproduct',
+ { 'taxproductnum' => $self->taxproductnum }
+ );
+ $part_pkg_taxproduct ? $part_pkg_taxproduct->description : '';
+}
+
=item _rebless
Reblesses the object into the FS::part_pkg::PLAN class (if available), where
--- NEW FILE: cust_tax_location.pm ---
package FS::cust_tax_location;
use strict;
use vars qw( @ISA );
use FS::Record qw( qsearch qsearchs dbh );
@ISA = qw(FS::Record);
=head1 NAME
FS::cust_tax_location - Object methods for cust_tax_location records
=head1 SYNOPSIS
use FS::cust_tax_location;
$record = new FS::cust_tax_location \%hash;
$record = new FS::cust_tax_location { 'column' => 'value' };
$error = $record->insert;
$error = $new_record->replace($old_record);
$error = $record->delete;
$error = $record->check;
=head1 DESCRIPTION
An FS::cust_tax_location object represents a mapping between a customer and
a tax location. FS::cust_tax_location inherits from FS::Record. The
following fields are currently supported:
=over 4
=item custlocationnum
primary key
=item data_vendor
a tax data vendor
=item zip
=item state
=item plus4hi
the upper bound of the last 4 zip code digits
=item plus4lo
the lower bound of the last 4 zip code digits
=item default_location
'Y' when this record represents the default for zip
=item geocode - the foreign key into FS::part_pkg_tax_rate and FS::tax_rate
=back
=head1 METHODS
=over 4
=item new HASHREF
Creates a new cust_tax_location. To add the cust_tax_location to the database,
see L<"insert">.
Note that this stores the hash reference, not a distinct copy of the hash it
points to. You can ask the object for a copy with the I<hash> method.
=cut
sub table { 'cust_tax_location'; }
=item insert
Adds this record to the database. If there is an error, returns the error,
otherwise returns false.
=cut
=item delete
Delete this record from the database.
=cut
=item replace OLD_RECORD
Replaces the OLD_RECORD with this one in the database. If there is an error,
returns the error, otherwise returns false.
=cut
=item check
Checks all fields to make sure this is a valid cust_tax_location. If there is
an error, returns the error, otherwise returns false. Called by the insert
and replace methods.
=cut
sub check {
my $self = shift;
my $error =
$self->ut_numbern('custlocationnum')
|| $self->ut_text('data_vendor')
|| $self->ut_number('zip')
|| $self->ut_text('state')
|| $self->ut_number('plus4hi')
|| $self->ut_number('plus4lo')
|| $self->ut_enum('default', [ '', ' ', 'Y' ] )
|| $self->ut_number('geocode')
;
return $error if $error;
$self->SUPER::check;
}
sub batch_import {
my $param = shift;
my $fh = $param->{filehandle};
my $format = $param->{'format'};
my @fields;
if ( $format eq 'cch' ) {
@fields = qw( zip state plus4lo plus4hi geocode default );
} elsif ( $format eq 'extended' ) {
die "unimplemented\n";
@fields = qw( );
} else {
die "unknown format $format";
}
eval "use Text::CSV_XS;";
die $@ if $@;
my $csv = new Text::CSV_XS;
my $imported = 0;
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 $line;
while ( defined($line=<$fh>) ) {
$csv->parse($line) or do {
$dbh->rollback if $oldAutoCommit;
return "can't parse: ". $csv->error_input();
};
my @columns = $csv->fields();
my %cust_tax_location = ( 'data_vendor' => $format );;
foreach my $field ( @fields ) {
$cust_tax_location{$field} = shift @columns;
}
my $cust_tax_location = new FS::cust_tax_location( \%cust_tax_location );
my $error = $cust_tax_location->insert;
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return "can't insert cust_tax_location for $line: $error";
}
$imported++;
}
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
return "Empty file!" unless $imported;
''; #no error
}
=back
=head1 BUGS
The author should be informed of any you find.
=head1 SEE ALSO
L<FS::Record>, schema.html from the base documentation.
=cut
1;
--- NEW FILE: tax_class.pm ---
package FS::tax_class;
use strict;
use vars qw( @ISA );
use FS::UID qw(dbh);
use FS::Record qw( qsearch qsearchs );
@ISA = qw(FS::Record);
=head1 NAME
FS::tax_class - Object methods for tax_class records
=head1 SYNOPSIS
use FS::tax_class;
$record = new FS::tax_class \%hash;
$record = new FS::tax_class { 'column' => 'value' };
$error = $record->insert;
$error = $new_record->replace($old_record);
$error = $record->delete;
$error = $record->check;
=head1 DESCRIPTION
An FS::tax_class object represents a tax class. FS::tax_class
inherits from FS::Record. The following fields are currently supported:
=over 4
=item taxclassnum
Primary key
=item data_vendor
Vendor of the tax data
=item taxclass
Tax class
=item description
Human readable description of the tax class
=back
=head1 METHODS
=over 4
=item new HASHREF
Creates a new tax class. To add the tax class to the database, see L<"insert">.
Note that this stores the hash reference, not a distinct copy of the hash it
points to. You can ask the object for a copy with the I<hash> method.
=cut
sub table { 'tax_class'; }
=item insert
Adds this record to the database. If there is an error, returns the error,
otherwise returns false.
=cut
=item delete
Delete this record from the database.
=cut
=item replace OLD_RECORD
Replaces the OLD_RECORD with this one in the database. If there is an error,
returns the error, otherwise returns false.
=cut
=item check
Checks all fields to make sure this is a valid tax class. If there is
an error, returns the error, otherwise returns false. Called by the insert
and replace methods.
=cut
sub check {
my $self = shift;
my $error =
$self->ut_numbern('taxclassnum')
|| $self->ut_text('taxclass')
|| $self->ut_textn('data_vendor')
|| $self->ut_textn('description')
;
return $error if $error;
$self->SUPER::check;
}
=item batch_import
Loads part_pkg_taxrate records from an external CSV file. If there is
an error, returns the error, otherwise returns false.
=cut
sub batch_import {
my $param = shift;
my $fh = $param->{filehandle};
my $format = $param->{'format'};
my @fields;
my $hook;
my $endhook;
my $data = {};
my $imported = 0;
if ( $format eq 'cch' ) {
@fields = qw( table name pos number length value description );
$hook = sub {
my $hash = shift;
if ($hash->{'table'} eq 'DETAIL') {
push @{$data->{'taxcat'}}, [ $hash->{'value'}, $hash->{'description'} ]
if $hash->{'name'} eq 'TAXCAT';
push @{$data->{'taxtype'}}, [ $hash->{'value'}, $hash->{'description'} ]
if $hash->{'name'} eq 'TAXTYPE';
}
delete($hash->{$_})
for qw( data_vendor table name pos number length value description );
'';
};
$endhook = sub {
foreach my $type (@{$data->{'taxtype'}}) {
foreach my $cat (@{$data->{'taxcat'}}) {
my $tax_class =
new FS::tax_class( { 'data_vendor' => 'cch',
'taxclass' => $type->[0].':'.$cat->[0],
'description' => $type->[1].':'.$cat->[1],
} );
my $error = $tax_class->insert;
return $error if $error;
$imported++;
}
}
'';
};
} elsif ( $format eq 'extended' ) {
die "unimplemented\n";
@fields = qw( );
$hook = sub {};
} else {
die "unknown format $format";
}
eval "use Text::CSV_XS;";
die $@ if $@;
my $csv = new Text::CSV_XS;
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 $line;
while ( defined($line=<$fh>) ) {
$csv->parse($line) or do {
$dbh->rollback if $oldAutoCommit;
return "can't parse: ". $csv->error_input();
};
my @columns = $csv->fields();
my %tax_class = ( 'data_vendor' => $format );
foreach my $field ( @fields ) {
$tax_class{$field} = shift @columns;
}
my $error = &{$hook}(\%tax_class);
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
}
next unless scalar(keys %tax_class);
my $tax_class = new FS::tax_class( \%tax_class );
$error = $tax_class->insert;
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return "can't insert tax_class for $line: $error";
}
$imported++;
}
my $error = &{$endhook}();
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return "can't insert tax_class for $line: $error";
}
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
return "Empty file!" unless $imported;
''; #no error
}
=back
=head1 BUGS
=head1 SEE ALSO
L<FS::Record>, schema.html from the base documentation.
=cut
1;
Index: Schema.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/Schema.pm,v
retrieving revision 1.80
retrieving revision 1.81
diff -u -d -r1.80 -r1.81
--- Schema.pm 20 Feb 2008 01:21:15 -0000 1.80
+++ Schema.pm 1 Apr 2008 00:54:41 -0000 1.81
@@ -303,6 +303,7 @@
my @date_type = ( 'int', 'NULL', '' );
my @perl_type = ( 'text', 'NULL', '' );
my @money_type = ( 'decimal', '', '10,2' );
+ my @money_typen = ( 'decimal', 'NULL', '10,2' );
my $username_len = 32; #usernamemax config file
@@ -665,6 +666,68 @@
'index' => [ [ 'county' ], [ 'state' ], [ 'country' ] ],
},
+ 'tax_rate' => {
+ 'columns' => [
+ 'taxnum', 'serial', '', '', '', '',
+ 'geocode', 'varchar', 'NULL', $char_d, '', '',#cch provides 10 char
+ 'data_vendor', 'varchar', 'NULL', $char_d, '', '',#auto update source
+ 'location', 'varchar', 'NULL', $char_d, '', '',#provided by tax authority
+ 'taxclassnum', 'int', '', '', '', '',
+ 'effective_date', @date_type, '', '',
+ 'tax', 'real', '', '', '', '', # tax %
+ 'excessrate', 'real', 'NULL','', '', '', # second tax %
+ 'taxbase', @money_typen, '', '', # amount at first tax rate
+ 'taxmax', @money_typen, '', '', # maximum about at both rates
+ 'usetax', 'real', 'NULL', '', '', '', # tax % when non-local
+ 'useexcessrate', 'real', 'NULL', '', '', '', # second tax % when non-local
+ 'unittype', 'int', 'NULL', '', '', '', # for fee
+ 'fee', 'real', 'NULL', '', '', '', # amount tax per unit
+ 'excessfee', 'real', 'NULL', '', '', '', # second amount tax per unit
+ 'feebase', 'real', 'NULL', '', '', '', # units taxed at first rate
+ 'feemax', 'real', 'NULL', '', '', '', # maximum number of unit taxed
+ 'maxtype', 'int', 'NULL', '', '', '', # indicator of how thresholds accumulate
+ 'taxname', 'varchar', 'NULL', $char_d, '', '', # may appear on invoice
+ 'taxauth', 'int', 'NULL', '', '', '', # tax authority
+ 'basetype', 'int', 'NULL', '', '', '', # indicator of basis for tax
+ 'passtype', 'int', 'NULL', '', '', '', # indicator declaring how item should be shown
+ 'passflag', 'char', 'NULL', 1, '', '', # Y = required to list as line item, N = Prohibited
+ 'setuptax', 'char', 'NULL', 1, '', '', # Y = setup tax exempt
+ 'recurtax', 'char', 'NULL', 1, '', '', # Y = recur tax exempt
+ 'manual', 'char', 'NULL', 1, '', '', # Y = manually edited
+ ],
+ 'primary_key' => 'taxnum',
+ 'unique' => [],
+ 'index' => [ ['taxclassnum'], ['data_vendor', 'geocode'] ],
+ },
+
+ 'cust_tax_location' => {
+ 'columns' => [
+ 'custlocationnum', 'serial', '', '', '', '',
+ 'data_vendor', 'varchar', 'NULL', $char_d, '', '', # update source
+ 'zip', 'char', '', 5, '', '',
+ 'state', 'char', '', 2, '', '',
+ 'plus4hi', 'char', '', 4, '', '',
+ 'plus4lo', 'char', '', 4, '', '',
+ 'default_location','char', 'NULL', 1, '', '', # Y = default for zip
+ 'geocode', 'varchar', '', 20, '', '',
+ ],
+ 'primary_key' => 'custlocationnum',
+ 'unique' => [],
+ 'index' => [ [ 'zip', 'plus4lo', 'plus4hi' ] ],
+ },
+
+ 'tax_class' => {
+ 'columns' => [
+ 'taxclassnum', 'serial', '', '', '', '',
+ 'data_vendor', 'varchar', 'NULL', $char_d, '', '',
+ 'taxclass', 'varchar', '', $char_d, '', '',
+ 'description', 'varchar', '', 2*$char_d, '', '',
+ ],
+ 'primary_key' => 'taxclassnum',
+ 'unique' => [ [ 'data_vendor', 'taxclass' ] ],
+ 'index' => [],
+ },
+
'cust_pay_pending' => {
'columns' => [
'paypendingnum','serial', '', '', '', '',
@@ -933,6 +996,7 @@
'disabled', 'char', 'NULL', 1, '', '',
'taxclass', 'varchar', 'NULL', $char_d, '', '',
'classnum', 'int', 'NULL', '', '', '',
+ 'taxproductnum', 'int', 'NULL', '', '', '',
'pay_weight', 'real', 'NULL', '', '', '',
'credit_weight', 'real', 'NULL', '', '', '',
'agentnum', 'int', 'NULL', '', '', '',
@@ -953,6 +1017,51 @@
'index' => [],
},
+ 'part_pkg_taxproduct' => {
+ 'columns' => [
+ 'taxproductnum', 'serial', '', '', '', '',
+ 'data_vendor', 'varchar', 'NULL', $char_d, '', '',
+ 'taxproduct', 'varchar', '', $char_d, '', '',
+ 'description', 'varchar', '', 2*$char_d, '', '',
+ ],
+ 'primary_key' => 'taxproductnum',
+ 'unique' => [ [ 'data_vendor', 'taxproduct' ] ],
+ 'index' => [],
+ },
+
+ 'part_pkg_taxrate' => {
+ 'columns' => [
+ 'pkgtaxratenum', 'serial', '', '', '', '',
+ 'data_vendor', 'varchar', 'NULL', $char_d, '', '', # update source
+ 'geocode', 'varchar', 'NULL', $char_d, '', '', # cch provides 10
+ 'taxproductnum', 'int', '', '', '', '',
+ 'city', 'varchar', 'NULL', $char_d, '', '', # tax_location?
+ 'county', 'varchar', 'NULL', $char_d, '', '',
+ 'state', 'varchar', 'NULL', $char_d, '', '',
+ 'local', 'varchar', 'NULL', $char_d, '', '',
+ 'country', 'char', 'NULL', 2, '', '',
+ 'taxclassnumtaxed', 'int', 'NULL', '', '', '',
+ 'taxcattaxed', 'varchar', 'NULL', $char_d, '', '',
+ 'taxclassnum', 'int', 'NULL', '', '', '',
+ 'effdate', @date_type, '', '',
+ 'taxable', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'pkgtaxratenum',
+ 'unique' => [],
+ 'index' => [ [ 'data_vendor', 'geocode', 'taxproductnum' ] ],
+ },
+
+ 'part_pkg_taxoverride' => {
+ 'columns' => [
+ 'taxoverridenum', 'serial', '', '', '', '',
+ 'pkgpart', 'serial', '', '', '', '',
+ 'taxnum', 'serial', '', '', '', '',
+ ],
+ 'primary_key' => 'taxoverridenum',
+ 'unique' => [],
+ 'index' => [ [ 'pkgpart' ], [ 'taxnum' ] ],
+ },
+
# 'part_title' => {
# 'columns' => [
# 'titlenum', 'int', '', '',
@@ -1366,6 +1475,7 @@
'routernum', 'serial', '', '', '', '',
'routername', 'varchar', '', $char_d, '', '',
'svcnum', 'int', 'NULL', '', '', '',
+ 'agentnum', 'int', 'NULL', '', '', '',
],
'primary_key' => 'routernum',
'unique' => [],
@@ -1389,6 +1499,7 @@
'routernum', 'int', '', '', '', '',
'ip_gateway', 'varchar', '', 15, '', '',
'ip_netmask', 'int', '', '', '', '',
+ 'agentnum', 'int', 'NULL', '', '', '',
],
'primary_key' => 'blocknum',
'unique' => [ [ 'blocknum', 'routernum' ] ],
--- NEW FILE: tax_rate.pm ---
package FS::tax_rate;
use strict;
use vars qw( @ISA @EXPORT_OK $conf $DEBUG $me
%tax_unittypes %tax_maxtypes %tax_basetypes %tax_authorities
%tax_passtypes
@tax_rate %tax_rate $countyflag );
use Exporter;
use Date::Parse;
use Tie::IxHash;
use FS::Record qw( qsearchs qsearch dbh );
use FS::tax_class;
@ISA = qw( FS::Record );
@EXPORT_OK = qw( regionselector );
$DEBUG = 1;
$me = '[FS::tax_rate]';
@tax_rate = ();
$countyflag = '';
#ask FS::UID to run this stuff for us later
$FS::UID::callback{'FS::tax_rate'} = sub {
$conf = new FS::Conf;
};
=head1 NAME
FS::tax_rate - Object methods for tax_rate objects
=head1 SYNOPSIS
use FS::tax_rate;
$record = new FS::tax_rate \%hash;
$record = new FS::tax_rate { 'column' => 'value' };
$error = $record->insert;
$error = $new_record->replace($old_record);
$error = $record->delete;
$error = $record->check;
($county_html, $state_html, $country_html) =
FS::tax_rate::regionselector( $county, $state, $country );
=head1 DESCRIPTION
An FS::tax_rate object represents a tax rate, defined by locale.
FS::tax_rate inherits from FS::Record. The following fields are
currently supported:
=over 4
=item taxnum
primary key (assigned automatically for new tax rates)
=item geocode
a geographic location code provided by a tax data vendor
=item data_vendor
the tax data vendor
=item location
a location code provided by a tax authority
=item taxclassnum
a foreign key into FS::tax_class - the type of tax
referenced but FS::part_pkg_taxrate
=item effective_date
the time after which the tax applies
=item tax
percentage
=item excessrate
second bracket percentage
=item taxbase
the amount to which the tax applies (first bracket)
=item taxmax
a cap on the amount of tax if a cap exists
=item usetax
percentage on out of jurisdiction purchases
=item useexcessrate
second bracket percentage on out of jurisdiction purchases
=item unittype
one of the values in %tax_unittypes
=item fee
amount of tax per unit
=item excessfee
second bracket amount of tax per unit
=item feebase
the number of units to which the fee applies (first bracket)
=item feemax
the most units to which fees apply (first and second brackets)
=item maxtype
a value from %tax_maxtypes indicating how brackets accumulate (i.e. monthly, per invoice, etc)
=item taxname
if defined, printed on invoices instead of "Tax"
=item taxauth
a value from %tax_authorities
=item basetype
a value from %tax_basetypes indicating the tax basis
=item passtype
a value from %tax_passtypes indicating how the tax should displayed to the customer
=item passflag
'Y', 'N', or blank indicating the tax can be passed to the customer
=item setuptax
if 'Y', this tax does not apply to setup fees
=item recurtax
if 'Y', this tax does not apply to recurring fees
=item manual
if 'Y', has been manually edited
=back
=head1 METHODS
=over 4
=item new HASHREF
Creates a new tax rate. To add the tax rate to the database, see L<"insert">.
=cut
sub table { 'tax_rate'; }
=item insert
Adds this tax rate to the database. If there is an error, returns the error,
otherwise returns false.
=item delete
Deletes this tax rate from the database. If there is an error, returns the
error, otherwise returns false.
=item replace OLD_RECORD
Replaces the OLD_RECORD with this one in the database. If there is an error,
returns the error, otherwise returns false.
=item check
Checks all fields to make sure this is a valid tax rate. If there is an error,
returns the error, otherwise returns false. Called by the insert and replace
methods.
=cut
sub check {
my $self = shift;
foreach (qw( taxbase taxmax )) {
$self->$_(0) unless $self->$_;
}
$self->ut_numbern('taxnum')
|| $self->ut_text('geocode')
|| $self->ut_textn('data_vendor')
|| $self->ut_textn('location')
|| $self->ut_foreign_key('taxclassnum', 'tax_class', 'taxclassnum')
|| $self->ut_numbern('effective_date')
|| $self->ut_float('tax')
|| $self->ut_floatn('excessrate')
|| $self->ut_money('taxbase')
|| $self->ut_money('taxmax')
|| $self->ut_floatn('usetax')
|| $self->ut_floatn('useexcessrate')
|| $self->ut_numbern('unittype')
|| $self->ut_floatn('fee')
|| $self->ut_floatn('excessfee')
|| $self->ut_floatn('feemax')
|| $self->ut_numbern('maxtype')
|| $self->ut_textn('taxname')
|| $self->ut_numbern('taxauth')
|| $self->ut_numbern('basetype')
|| $self->ut_numbern('passtype')
|| $self->ut_enum('passflag', [ '', 'Y', 'N' ])
|| $self->ut_enum('setuptax', [ '', 'Y' ] )
|| $self->ut_enum('recurtax', [ '', 'Y' ] )
|| $self->ut_enum('manual', [ '', 'Y' ] )
|| $self->SUPER::check
;
}
=item taxclass_description
Returns the human understandable value associated with the related
FS::tax_class.
=cut
sub taxclass_description {
my $self = shift;
my $tax_class = qsearchs('tax_class', {'taxclassnum' => $self->taxclassnum });
$tax_class ? $tax_class->description : '';
}
=item unittype_name
Returns the human understandable value associated with the unittype column
=cut
%tax_unittypes = ( '0' => 'access line',
'1' => 'minute',
'2' => 'account',
);
sub unittype_name {
my $self = shift;
$tax_unittypes{$self->unittype};
}
=item maxtype_name
Returns the human understandable value associated with the maxtype column
=cut
%tax_maxtypes = ( '0' => 'receipts per invoice',
'1' => 'receipts per item',
'2' => 'total utility charges per utility tax year',
'3' => 'total charges per utility tax year',
'4' => 'receipts per access line',
'9' => 'monthly receipts per location',
);
sub maxtype_name {
my $self = shift;
$tax_maxtypes{$self->maxtype};
}
=item basetype_name
Returns the human understandable value associated with the basetype column
=cut
%tax_basetypes = ( '0' => 'sale price',
'1' => 'gross receipts',
'2' => 'sales taxable telecom revenue',
'3' => 'minutes carried',
'4' => 'minutes billed',
'5' => 'gross operating revenue',
'6' => 'access line',
'7' => 'account',
'8' => 'gross revenue',
'9' => 'portion gross receipts attributable to interstate service',
'10' => 'access line',
'11' => 'gross profits',
'12' => 'tariff rate',
'14' => 'account',
);
sub basetype_name {
my $self = shift;
$tax_basetypes{$self->basetype};
}
=item taxauth_name
Returns the human understandable value associated with the taxauth column
=cut
%tax_authorities = ( '0' => 'federal',
'1' => 'state',
'2' => 'county',
'3' => 'city',
'4' => 'local',
'5' => 'county administered by state',
'6' => 'city administered by state',
'7' => 'city administered by county',
'8' => 'local administered by state',
'9' => 'local administered by county',
);
sub taxauth_name {
my $self = shift;
$tax_authorities{$self->taxauth};
}
=item passtype_name
Returns the human understandable value associated with the passtype column
=cut
%tax_passtypes = ( '0' => 'separate tax line',
'1' => 'separate surcharge line',
'2' => 'surcharge not separated',
'3' => 'included in base rate',
);
sub passtype_name {
my $self = shift;
$tax_passtypes{$self->passtype};
}
=back
=head1 SUBROUTINES
=over 4
=item regionselector [ COUNTY STATE COUNTRY [ PREFIX [ ONCHANGE [ DISABLED ] ] ] ]
=cut
sub regionselector {
my ( $selected_county, $selected_state, $selected_country,
$prefix, $onchange, $disabled ) = @_;
$prefix = '' unless defined $prefix;
$countyflag = 0;
# unless ( @tax_rate ) { #cache
@tax_rate = qsearch('tax_rate', {} );
foreach my $c ( @tax_rate ) {
$countyflag=1 if $c->county;
#push @{$tax_rate{$c->country}{$c->state}}, $c->county;
$tax_rate{$c->country}{$c->state}{$c->county} = 1;
}
# }
$countyflag=1 if $selected_county;
my $script_html = <<END;
<SCRIPT>
function opt(what,value,text) {
var optionName = new Option(text, value, false, false);
var length = what.length;
what.options[length] = optionName;
}
function ${prefix}country_changed(what) {
country = what.options[what.selectedIndex].text;
for ( var i = what.form.${prefix}state.length; i >= 0; i-- )
what.form.${prefix}state.options[i] = null;
END
#what.form.${prefix}state.options[0] = new Option('', '', false, true);
foreach my $country ( sort keys %tax_rate ) {
$script_html .= "\nif ( country == \"$country\" ) {\n";
foreach my $state ( sort keys %{$tax_rate{$country}} ) {
( my $dstate = $state ) =~ s/[\n\r]//g;
my $text = $dstate || '(n/a)';
$script_html .= qq!opt(what.form.${prefix}state, "$dstate", "$text");\n!;
}
$script_html .= "}\n";
}
$script_html .= <<END;
}
function ${prefix}state_changed(what) {
END
if ( $countyflag ) {
$script_html .= <<END;
state = what.options[what.selectedIndex].text;
country = what.form.${prefix}country.options[what.form.${prefix}country.selectedIndex].text;
for ( var i = what.form.${prefix}county.length; i >= 0; i-- )
what.form.${prefix}county.options[i] = null;
END
foreach my $country ( sort keys %tax_rate ) {
$script_html .= "\nif ( country == \"$country\" ) {\n";
foreach my $state ( sort keys %{$tax_rate{$country}} ) {
$script_html .= "\nif ( state == \"$state\" ) {\n";
#foreach my $county ( sort @{$tax_rate{$country}{$state}} ) {
foreach my $county ( sort keys %{$tax_rate{$country}{$state}} ) {
my $text = $county || '(n/a)';
$script_html .=
qq!opt(what.form.${prefix}county, "$county", "$text");\n!;
}
$script_html .= "}\n";
}
$script_html .= "}\n";
}
}
$script_html .= <<END;
}
</SCRIPT>
END
my $county_html = $script_html;
if ( $countyflag ) {
$county_html .= qq!<SELECT NAME="${prefix}county" onChange="$onchange" $disabled>!;
$county_html .= '</SELECT>';
} else {
$county_html .=
qq!<INPUT TYPE="hidden" NAME="${prefix}county" VALUE="$selected_county">!;
}
my $state_html = qq!<SELECT NAME="${prefix}state" !.
qq!onChange="${prefix}state_changed(this); $onchange" $disabled>!;
foreach my $state ( sort keys %{ $tax_rate{$selected_country} } ) {
my $text = $state || '(n/a)';
my $selected = $state eq $selected_state ? 'SELECTED' : '';
$state_html .= qq(\n<OPTION $selected VALUE="$state">$text</OPTION>);
}
$state_html .= '</SELECT>';
$state_html .= '</SELECT>';
my $country_html = qq!<SELECT NAME="${prefix}country" !.
qq!onChange="${prefix}country_changed(this); $onchange" $disabled>!;
my $countrydefault = $conf->config('countrydefault') || 'US';
foreach my $country (
sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) or $a cmp $b }
keys %tax_rate
) {
my $selected = $country eq $selected_country ? ' SELECTED' : '';
$country_html .= qq(\n<OPTION$selected VALUE="$country">$country</OPTION>");
}
$country_html .= '</SELECT>';
($county_html, $state_html, $country_html);
}
sub batch_import {
my $param = shift;
my $fh = $param->{filehandle};
my $format = $param->{'format'};
my @fields;
my $hook;
if ( $format eq 'cch' ) {
@fields = qw( geocode inoutcity inoutlocal tax location taxbase taxmax
excessrate effective_date taxauth taxtype taxcat taxname
usetax useexcessrate fee unittype feemax maxtype passflag
passtype basetype );
$hook = sub {
my $hash = shift;
$hash->{'effective_date'} = str2time($hash->{'effective_date'});
my $taxclassid =
join(':', map{ $hash->{$_} } qw(taxtype taxcat) );
my %tax_class = ( 'data_vendor' => 'cch',
'taxclass' => $taxclassid,
);
my $tax_class = qsearchs( 'tax_class', \%tax_class );
return "Error inserting tax rate: no tax class $taxclassid"
unless $tax_class;
$hash->{'taxclassnum'} = $tax_class->taxclassnum;
foreach (qw( inoutcity inoutlocal taxtype taxcat )) {
delete($hash->{$_});
}
my %passflagmap = ( '0' => '',
'1' => 'Y',
'2' => 'N',
);
$hash->{'passflag'} = $passflagmap{$hash->{'passflag'}}
if exists $passflagmap{$hash->{'passflag'}};
foreach (keys %$hash) {
$hash->{$_} = substr($hash->{$_}, 0, 80)
if length($hash->{$_}) > 80;
}
};
} elsif ( $format eq 'extended' ) {
die "unimplemented\n";
@fields = qw( );
$hook = sub {};
} else {
die "unknown format $format";
}
eval "use Text::CSV_XS;";
die $@ if $@;
my $csv = new Text::CSV_XS;
my $imported = 0;
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 $line;
while ( defined($line=<$fh>) ) {
$csv->parse($line) or do {
$dbh->rollback if $oldAutoCommit;
return "can't parse: ". $csv->error_input();
};
warn "$me batch_import: $imported\n"
if (!($imported % 100) && $DEBUG);
my @columns = $csv->fields();
my %tax_rate = ( 'data_vendor' => $format );
foreach my $field ( @fields ) {
$tax_rate{$field} = shift @columns;
}
my $error = &{$hook}(\%tax_rate);
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
}
my $tax_rate = new FS::tax_rate( \%tax_rate );
$error = $tax_rate->insert;
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return "can't insert tax_rate for $line: $error";
}
$imported++;
}
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
return "Empty file!" unless $imported;
''; #no error
}
=back
=head1 BUGS
regionselector? putting web ui components in here? they should probably live
somewhere else...
=head1 SEE ALSO
L<FS::Record>, L<FS::cust_main>, L<FS::cust_bill>, schema.html from the base
documentation.
=cut
1;
- Previous message: [freeside-commits] freeside/FS/t cust_tax_location.t, NONE, 1.1 part_pkg_taxoverride.t, NONE, 1.1 part_pkg_taxproduct.t, NONE, 1.1 part_pkg_taxrate.t, NONE, 1.1 tax_class.t, NONE, 1.1 tax_rate.t, NONE, 1.1
- Next message: [freeside-commits] freeside/httemplate/browse tax_rate.cgi, NONE, 1.1
- Messages sorted by:
[ date ]
[ thread ]
[ subject ]
[ author ]
More information about the freeside-commits
mailing list