4 use vars qw( @ISA $DEBUG $me
5 %tax_unittypes %tax_maxtypes %tax_basetypes %tax_authorities
8 use FS::Record qw( qsearchs dbh );
10 use FS::cust_bill_pkg;
12 @ISA = qw( FS::Record );
15 $me = '[FS::tax_rate]';
19 FS::tax_rate - Object methods for tax_rate objects
25 $record = new FS::tax_rate \%hash;
26 $record = new FS::tax_rate { 'column' => 'value' };
28 $error = $record->insert;
30 $error = $new_record->replace($old_record);
32 $error = $record->delete;
34 $error = $record->check;
38 An FS::tax_rate object represents a tax rate, defined by locale.
39 FS::tax_rate inherits from FS::Record. The following fields are
46 primary key (assigned automatically for new tax rates)
50 a geographic location code provided by a tax data vendor
58 a location code provided by a tax authority
62 a foreign key into FS::tax_class - the type of tax
63 referenced but FS::part_pkg_taxrate
66 the time after which the tax applies
74 second bracket percentage
78 the amount to which the tax applies (first bracket)
82 a cap on the amount of tax if a cap exists
86 percentage on out of jurisdiction purchases
90 second bracket percentage on out of jurisdiction purchases
94 one of the values in %tax_unittypes
98 amount of tax per unit
102 second bracket amount of tax per unit
106 the number of units to which the fee applies (first bracket)
110 the most units to which fees apply (first and second brackets)
114 a value from %tax_maxtypes indicating how brackets accumulate (i.e. monthly, per invoice, etc)
118 if defined, printed on invoices instead of "Tax"
122 a value from %tax_authorities
126 a value from %tax_basetypes indicating the tax basis
130 a value from %tax_passtypes indicating how the tax should displayed to the customer
134 'Y', 'N', or blank indicating the tax can be passed to the customer
138 if 'Y', this tax does not apply to setup fees
142 if 'Y', this tax does not apply to recurring fees
146 if 'Y', has been manually edited
156 Creates a new tax rate. To add the tax rate to the database, see L<"insert">.
160 sub table { 'tax_rate'; }
164 Adds this tax rate to the database. If there is an error, returns the error,
165 otherwise returns false.
169 Deletes this tax rate from the database. If there is an error, returns the
170 error, otherwise returns false.
172 =item replace OLD_RECORD
174 Replaces the OLD_RECORD with this one in the database. If there is an error,
175 returns the error, otherwise returns false.
179 Checks all fields to make sure this is a valid tax rate. If there is an error,
180 returns the error, otherwise returns false. Called by the insert and replace
188 foreach (qw( taxbase taxmax )) {
189 $self->$_(0) unless $self->$_;
192 $self->ut_numbern('taxnum')
193 || $self->ut_text('geocode')
194 || $self->ut_textn('data_vendor')
195 || $self->ut_textn('location')
196 || $self->ut_foreign_key('taxclassnum', 'tax_class', 'taxclassnum')
197 || $self->ut_numbern('effective_date')
198 || $self->ut_float('tax')
199 || $self->ut_floatn('excessrate')
200 || $self->ut_money('taxbase')
201 || $self->ut_money('taxmax')
202 || $self->ut_floatn('usetax')
203 || $self->ut_floatn('useexcessrate')
204 || $self->ut_numbern('unittype')
205 || $self->ut_floatn('fee')
206 || $self->ut_floatn('excessfee')
207 || $self->ut_floatn('feemax')
208 || $self->ut_numbern('maxtype')
209 || $self->ut_textn('taxname')
210 || $self->ut_numbern('taxauth')
211 || $self->ut_numbern('basetype')
212 || $self->ut_numbern('passtype')
213 || $self->ut_enum('passflag', [ '', 'Y', 'N' ])
214 || $self->ut_enum('setuptax', [ '', 'Y' ] )
215 || $self->ut_enum('recurtax', [ '', 'Y' ] )
216 || $self->ut_enum('manual', [ '', 'Y' ] )
217 || $self->SUPER::check
222 =item taxclass_description
224 Returns the human understandable value associated with the related
229 sub taxclass_description {
231 my $tax_class = qsearchs('tax_class', {'taxclassnum' => $self->taxclassnum });
232 $tax_class ? $tax_class->description : '';
237 Returns the human understandable value associated with the unittype column
241 %tax_unittypes = ( '0' => 'access line',
248 $tax_unittypes{$self->unittype};
253 Returns the human understandable value associated with the maxtype column
257 %tax_maxtypes = ( '0' => 'receipts per invoice',
258 '1' => 'receipts per item',
259 '2' => 'total utility charges per utility tax year',
260 '3' => 'total charges per utility tax year',
261 '4' => 'receipts per access line',
262 '9' => 'monthly receipts per location',
267 $tax_maxtypes{$self->maxtype};
272 Returns the human understandable value associated with the basetype column
276 %tax_basetypes = ( '0' => 'sale price',
277 '1' => 'gross receipts',
278 '2' => 'sales taxable telecom revenue',
279 '3' => 'minutes carried',
280 '4' => 'minutes billed',
281 '5' => 'gross operating revenue',
282 '6' => 'access line',
284 '8' => 'gross revenue',
285 '9' => 'portion gross receipts attributable to interstate service',
286 '10' => 'access line',
287 '11' => 'gross profits',
288 '12' => 'tariff rate',
294 $tax_basetypes{$self->basetype};
299 Returns the human understandable value associated with the taxauth column
303 %tax_authorities = ( '0' => 'federal',
308 '5' => 'county administered by state',
309 '6' => 'city administered by state',
310 '7' => 'city administered by county',
311 '8' => 'local administered by state',
312 '9' => 'local administered by county',
317 $tax_authorities{$self->taxauth};
322 Returns the human understandable value associated with the passtype column
326 %tax_passtypes = ( '0' => 'separate tax line',
327 '1' => 'separate surcharge line',
328 '2' => 'surcharge not separated',
329 '3' => 'included in base rate',
334 $tax_passtypes{$self->passtype};
337 =item taxline CUST_BILL_PKG, ...
339 Returns a listref of a name and an amount of tax calculated for the list
340 of packages. If an error occurs, a message is returned as a scalar.
346 my @cust_bill_pkg = @_;
348 if ($self->passflag eq 'N') {
349 return "fatal: can't (yet) handle taxes not passed to the customer";
352 if ($self->maxtype != 0 && $self->maxtype != 9) {
353 return qq!fatal: can't (yet) handle tax with "!. $self->maxtype_name.
357 if ($self->maxtype == 9) {
358 return qq!fatal: can't (yet) handle tax with "!. $self->maxtype_name.
359 '" threshold'; # "texas" tax
362 if ($self->basetype != 0 && $self->basetype != 1 &&
363 $self->basetype != 6 && $self->basetype != 7 &&
364 $self->basetype != 14
366 return qq!fatal: can't (yet) handle tax with "!. $self->basetype_name.
370 my $name = $self->taxname;
371 $name = 'Other surcharges'
372 if ($self->passtype == 2);
375 my $taxable_charged = 0;
376 unless ($self->setuptax =~ /^Y$/i) {
377 $taxable_charged += $_->setup foreach @cust_bill_pkg;
379 unless ($self->recurtax =~ /^Y$/i) {
380 $taxable_charged += $_->recur foreach @cust_bill_pkg;
383 my $taxable_units = 0;
384 unless ($self->recurtax =~ /^Y$/i) {
385 $taxable_units += $_->units foreach @cust_bill_pkg;
389 # XXX insert exemption handling here
391 # the tax or fee is applied to taxbase or feebase and then
392 # the excessrate or excess fee is applied to taxmax or feemax
395 $amount += $taxable_charged * $self->tax;
396 $amount += $taxable_units * $self->fee;
398 return [$name, $amount];
415 my $fh = $param->{filehandle};
416 my $format = $param->{'format'};
420 if ( $format eq 'cch' ) {
421 @fields = qw( geocode inoutcity inoutlocal tax location taxbase taxmax
422 excessrate effective_date taxauth taxtype taxcat taxname
423 usetax useexcessrate fee unittype feemax maxtype passflag
428 $hash->{'effective_date'} = str2time($hash->{'effective_date'});
431 join(':', map{ $hash->{$_} } qw(taxtype taxcat) );
433 my %tax_class = ( 'data_vendor' => 'cch',
434 'taxclass' => $taxclassid,
437 my $tax_class = qsearchs( 'tax_class', \%tax_class );
438 return "Error inserting tax rate: no tax class $taxclassid"
441 $hash->{'taxclassnum'} = $tax_class->taxclassnum;
443 foreach (qw( inoutcity inoutlocal taxtype taxcat )) {
447 my %passflagmap = ( '0' => '',
451 $hash->{'passflag'} = $passflagmap{$hash->{'passflag'}}
452 if exists $passflagmap{$hash->{'passflag'}};
454 foreach (keys %$hash) {
455 $hash->{$_} = substr($hash->{$_}, 0, 80)
456 if length($hash->{$_}) > 80;
463 } elsif ( $format eq 'extended' ) {
464 die "unimplemented\n";
468 die "unknown format $format";
471 eval "use Text::CSV_XS;";
474 my $csv = new Text::CSV_XS;
478 local $SIG{HUP} = 'IGNORE';
479 local $SIG{INT} = 'IGNORE';
480 local $SIG{QUIT} = 'IGNORE';
481 local $SIG{TERM} = 'IGNORE';
482 local $SIG{TSTP} = 'IGNORE';
483 local $SIG{PIPE} = 'IGNORE';
485 my $oldAutoCommit = $FS::UID::AutoCommit;
486 local $FS::UID::AutoCommit = 0;
490 while ( defined($line=<$fh>) ) {
491 $csv->parse($line) or do {
492 $dbh->rollback if $oldAutoCommit;
493 return "can't parse: ". $csv->error_input();
496 warn "$me batch_import: $imported\n"
497 if (!($imported % 100) && $DEBUG);
499 my @columns = $csv->fields();
501 my %tax_rate = ( 'data_vendor' => $format );
502 foreach my $field ( @fields ) {
503 $tax_rate{$field} = shift @columns;
505 my $error = &{$hook}(\%tax_rate);
507 $dbh->rollback if $oldAutoCommit;
511 my $tax_rate = new FS::tax_rate( \%tax_rate );
512 $error = $tax_rate->insert;
515 $dbh->rollback if $oldAutoCommit;
516 return "can't insert tax_rate for $line: $error";
522 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
524 return "Empty file!" unless $imported;
534 regionselector? putting web ui components in here? they should probably live
539 L<FS::Record>, L<FS::cust_main>, L<FS::cust_bill>, schema.html from the base