1 package FS::part_pkg_taxrate;
7 use DateTime::Format::Strptime;
9 use FS::Record qw( qsearch qsearchs );
10 use FS::part_pkg_taxproduct;
11 use FS::Misc qw(csv_from_fixed);
13 @ISA = qw(FS::Record);
17 FS::part_pkg_taxrate - Object methods for part_pkg_taxrate records
21 use FS::part_pkg_taxrate;
23 $record = new FS::part_pkg_taxrate \%hash;
24 $record = new FS::part_pkg_taxrate { 'column' => 'value' };
26 $error = $record->insert;
28 $error = $new_record->replace($old_record);
30 $error = $record->delete;
32 $error = $record->check;
36 An FS::part_pkg_taxrate object maps packages onto tax rates.
37 FS::part_pkg_taxrate inherits from FS::Record. The following fields are
52 Tax vendor location code
56 Class of package for tax purposes, Index into FS::part_pkg_taxproduct
80 Class of tax index into FS::tax_taxclass and FS::tax_rate
82 =item taxclassnumtaxed
84 Class of tax taxed by this entry.
102 Creates a new customer (location), package, tax rate mapping. To add the
103 mapping to the database, see L<"insert">.
105 Note that this stores the hash reference, not a distinct copy of the hash it
106 points to. You can ask the object for a copy with the I<hash> method.
110 sub table { 'part_pkg_taxrate'; }
114 Adds this record to the database. If there is an error, returns the error,
115 otherwise returns false.
121 Delete this record from the database.
125 =item replace OLD_RECORD
127 Replaces the OLD_RECORD with this one in the database. If there is an error,
128 returns the error, otherwise returns false.
134 Checks all fields to make sure this is a valid tax rate mapping. If there is
135 an error, returns the error, otherwise returns false. Called by the insert
144 $self->ut_numbern('pkgtaxratenum')
145 || $self->ut_textn('data_vendor')
146 || $self->ut_textn('geocode')
148 ut_foreign_key('taxproductnum', 'part_pkg_taxproduct', 'taxproductnum')
149 || $self->ut_textn('city')
150 || $self->ut_textn('county')
151 || $self->ut_textn('state')
152 || $self->ut_textn('local')
153 || $self->ut_text('country')
154 || $self->ut_foreign_keyn('taxclassnumtaxed', 'tax_class', 'taxclassnum')
155 || $self->ut_foreign_key('taxclassnum', 'tax_class', 'taxclassnum')
156 || $self->ut_snumbern('effdate')
157 || $self->ut_enum('taxable', [ 'Y', '' ])
159 return $error if $error;
166 Loads part_pkg_taxrate records from an external CSV file. If there is
167 an error, returns the error, otherwise returns false.
172 my ($param, $job) = @_;
174 my $fh = $param->{filehandle};
175 my $format = $param->{'format'};
181 my @column_lengths = ();
182 my @column_callbacks = ();
183 if ( $format eq 'cch-fixed' || $format eq 'cch-fixed-update' ) {
184 $format =~ s/-fixed//;
185 my $date_format = sub { my $r='';
186 /^(\d{4})(\d{2})(\d{2})$/ && ($r="$3/$2/$1");
189 $column_callbacks[16] = $date_format;
190 push @column_lengths, qw( 28 25 2 1 10 4 30 3 100 2 2 2 2 1 2 2 8 1 );
191 push @column_lengths, 1 if $format eq 'cch-update';
195 my ( $count, $last, $min_sec ) = (0, time, 5); #progressbar
196 if ( $job || scalar(@column_callbacks) ) {
198 csv_from_fixed(\$fh, \$count, \@column_lengths, \@column_callbacks);
199 return $error if $error;
202 if ( $format eq 'cch' || $format eq 'cch-update' ) {
203 @fields = qw( city county state local geocode group groupdesc item
204 itemdesc provider customer taxtypetaxed taxcattaxed
205 taxable taxtype taxcat effdate rectype );
206 push @fields, 'actionflag' if $format eq 'cch-update';
208 $imported++ if $format eq 'cch-update'; #empty file ok
213 unless ( $hash->{'rectype'} eq 'R' or $hash->{'rectype'} eq 'T' ) {
214 delete($hash->{$_}) for (keys %$hash);
218 $hash->{'data_vendor'} = 'cch';
220 my %providers = ( '00' => 'Regulated LEC',
221 '01' => 'Regulated IXC',
222 '02' => 'Unregulated LEC',
223 '03' => 'Unregulated IXC',
228 my %customers = ( '00' => 'Residential',
229 '01' => 'Commercial',
230 '02' => 'Industrial',
232 '10' => 'Senior Citizen',
236 join(':', map{ $hash->{$_} } qw(group item provider customer ) );
238 my %part_pkg_taxproduct = ( 'data_vendor' => 'cch',
239 'taxproduct' => $taxproduct,
242 my $part_pkg_taxproduct = qsearchs( 'part_pkg_taxproduct',
243 { %part_pkg_taxproduct }
246 unless ($part_pkg_taxproduct) {
247 return "Can't find part_pkg_taxproduct for txmatrix deletion: ".
248 join(" ", map { "$_ => ". $hash->{$_} } @fields)
249 if ($hash->{'actionfield'} && $hash->{'actionflag'} eq 'D');
251 $part_pkg_taxproduct{'description'} =
252 join(' : ', (map{ $hash->{$_} } qw(groupdesc itemdesc)),
253 $providers{$hash->{'provider'}} || '',
254 $customers{$hash->{'customer'}} || '',
256 $part_pkg_taxproduct = new FS::part_pkg_taxproduct \%part_pkg_taxproduct;
257 my $error = $part_pkg_taxproduct->insert;
258 return "Error inserting tax product (part_pkg_taxproduct): $error"
262 $hash->{'taxproductnum'} = $part_pkg_taxproduct->taxproductnum;
265 for qw(group groupdesc item itemdesc provider customer rectype );
267 my %map = ( 'taxclassnum' => [ 'taxtype', 'taxcat' ],
268 'taxclassnumtaxed' => [ 'taxtypetaxed', 'taxcattaxed' ],
271 for my $item (keys %map) {
272 my $class = join(':', map($hash->{$_}, @{$map{$item}}));
274 qsearchs( 'tax_class',
275 { data_vendor => 'cch',
276 'taxclass' => $class,
279 $hash->{$item} = $tax_class->taxclassnum
282 return "Can't find tax class for txmatrix deletion: ".
283 join(" ", map { "$_ => ". $hash->{$_} } @fields)
284 if ( $hash->{'actionflag'} && $hash->{'actionflag'} eq 'D' &&
285 !$tax_class && $class ne ':'
288 delete($hash->{$_}) foreach @{$map{$item}};
291 my $parser = new DateTime::Format::Strptime( pattern => "%m/%d/%Y",
292 time_zone => 'floating',
294 my $dt = $parser->parse_datetime( $hash->{'effdate'} );
295 return "Can't parse effdate ". $hash->{'effdate'}. ': '. $parser->errstr
297 $hash->{'effdate'} = $dt->epoch;
299 $hash->{'country'} = 'US'; # CA is available
301 $hash->{'taxable'} = '' if ($hash->{'taxable'} eq 'N');
303 if (exists($hash->{actionflag}) && $hash->{actionflag} eq 'D') {
304 delete($hash->{actionflag});
306 foreach my $intfield (qw( taxproductnum taxclassnum effdate )) {
307 if ( $hash->{$intfield} eq '' ) {
308 return "$intfield is empty in search! -- ".
309 join(" ", map { "$_ => *". $hash->{$_}. '*' } keys(%$hash) );
313 my $part_pkg_taxrate = qsearchs('part_pkg_taxrate', $hash);
314 unless ( $part_pkg_taxrate ) {
315 if ( $hash->{taxproductnum} ) {
317 qsearchs( 'part_pkg_taxproduct',
318 { 'taxproductnum' => $hash->{taxproductnum} }
320 $hash->{taxproductnum} .= ' ( '. $taxproduct->taxproduct. ' )'
323 return "Can't find part_pkg_taxrate to delete: ".
324 join(" ", map { "$_ => *". $hash->{$_}. '*' } keys(%$hash) );
327 my $error = $part_pkg_taxrate->delete;
328 return $error if $error;
330 delete($hash->{$_}) foreach (keys %$hash);
333 delete($hash->{actionflag});
338 } elsif ( $format eq 'extended' ) {
339 die "unimplemented\n";
343 die "unknown format $format";
346 eval "use Text::CSV_XS;";
349 my $csv = new Text::CSV_XS;
351 local $SIG{HUP} = 'IGNORE';
352 local $SIG{INT} = 'IGNORE';
353 local $SIG{QUIT} = 'IGNORE';
354 local $SIG{TERM} = 'IGNORE';
355 local $SIG{TSTP} = 'IGNORE';
356 local $SIG{PIPE} = 'IGNORE';
358 my $oldAutoCommit = $FS::UID::AutoCommit;
359 local $FS::UID::AutoCommit = 0;
362 while ( defined($line=<$fh>) ) {
363 $csv->parse($line) or do {
364 $dbh->rollback if $oldAutoCommit;
365 return "can't parse: ". $csv->error_input();
369 if ( $job ) { # progress bar
370 if ( time - $min_sec > $last ) {
371 my $error = $job->update_statustext(
372 int( 100 * $imported / $count ). ",Importing tax matrix"
374 die $error if $error;
379 my @columns = $csv->fields();
381 my %part_pkg_taxrate = ( 'data_vendor' => $format );
382 foreach my $field ( @fields ) {
383 $part_pkg_taxrate{$field} = shift @columns;
385 if ( scalar( @columns ) ) {
386 $dbh->rollback if $oldAutoCommit;
387 return "Unexpected trailing columns in line (wrong format?): $line";
390 my $error = &{$hook}(\%part_pkg_taxrate);
392 $dbh->rollback if $oldAutoCommit;
395 next unless scalar(keys %part_pkg_taxrate);
398 my $part_pkg_taxrate = new FS::part_pkg_taxrate( \%part_pkg_taxrate );
399 $error = $part_pkg_taxrate->insert;
402 $dbh->rollback if $oldAutoCommit;
403 return "can't insert part_pkg_taxrate for $line: $error";
409 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
411 return "Empty file!" unless ( $imported || $format eq 'cch-update' );
423 L<FS::Record>, schema.html from the base documentation.