6 use FS::Record qw( qsearch qsearchs );
7 use FS::Misc qw( csv_from_fixed );
8 use FS::part_pkg_taxrate;
9 use FS::part_pkg_taxoverride;
11 @ISA = qw(FS::Record);
15 FS::tax_class - Object methods for tax_class records
21 $record = new FS::tax_class \%hash;
22 $record = new FS::tax_class { 'column' => 'value' };
24 $error = $record->insert;
26 $error = $new_record->replace($old_record);
28 $error = $record->delete;
30 $error = $record->check;
34 An FS::tax_class object represents a tax class. FS::tax_class
35 inherits from FS::Record. The following fields are currently supported:
45 Vendor of the tax data
53 Human readable description of the tax class
63 Creates a new tax class. To add the tax class to the database, see L<"insert">.
65 Note that this stores the hash reference, not a distinct copy of the hash it
66 points to. You can ask the object for a copy with the I<hash> method.
70 sub table { 'tax_class'; }
74 Adds this record to the database. If there is an error, returns the error,
75 otherwise returns false.
81 Delete this record from the database.
88 return "Can't delete a tax class which has package tax rates!"
89 if qsearch( 'part_pkg_taxrate', { 'taxclassnumtaxed' => $self->taxclassnum } );
91 return "Can't delete a tax class which has package tax overrides!"
92 if qsearch( 'part_pkg_taxoverride', { 'taxclassnum' => $self->taxclassnum } );
94 local $SIG{HUP} = 'IGNORE';
95 local $SIG{INT} = 'IGNORE';
96 local $SIG{QUIT} = 'IGNORE';
97 local $SIG{TERM} = 'IGNORE';
98 local $SIG{TSTP} = 'IGNORE';
99 local $SIG{PIPE} = 'IGNORE';
101 my $oldAutoCommit = $FS::UID::AutoCommit;
102 local $FS::UID::AutoCommit = 0;
105 foreach my $tax_rate (
106 qsearch( 'tax_rate', { taxclassnum=>$self->taxclassnum } )
108 my $error = $tax_rate->delete;
110 $dbh->rollback if $oldAutoCommit;
115 foreach my $part_pkg_taxrate (
116 qsearch( 'part_pkg_taxrate', { taxclassnum=>$self->taxclassnum } )
118 my $error = $part_pkg_taxrate->delete;
120 $dbh->rollback if $oldAutoCommit;
125 my $error = $self->SUPER::delete(@_);
127 $dbh->rollback if $oldAutoCommit;
131 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
137 =item replace OLD_RECORD
139 Replaces the OLD_RECORD with this one in the database. If there is an error,
140 returns the error, otherwise returns false.
146 Checks all fields to make sure this is a valid tax class. If there is
147 an error, returns the error, otherwise returns false. Called by the insert
156 $self->ut_numbern('taxclassnum')
157 || $self->ut_text('taxclass')
158 || $self->ut_textn('data_vendor')
159 || $self->ut_textn('description')
161 return $error if $error;
168 Loads part_pkg_taxrate records from an external CSV file. If there is
169 an error, returns the error, otherwise returns false.
174 my ($param, $job) = @_;
176 my $fh = $param->{filehandle};
177 my $format = $param->{'format'};
186 my @column_lengths = ();
187 my @column_callbacks = ();
188 if ( $format eq 'cch-fixed' || $format eq 'cch-fixed-update' ) {
189 $format =~ s/-fixed//;
190 push @column_lengths, qw( 8 10 3 2 2 10 100 );
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_lengths) ) {
197 my $error = csv_from_fixed(\$fh, \$count, \@column_lengths);
198 return $error if $error;
201 if ( $format eq 'cch' || $format eq 'cch-update' ) {
202 @fields = qw( table name pos length number value description );
203 push @fields, 'actionflag' if $format eq 'cch-update';
208 if ($hash->{'table'} eq 'DETAIL') {
209 push @{$data->{'taxcat'}}, [ $hash->{'value'}, $hash->{'description'} ]
210 if ($hash->{'name'} eq 'TAXCAT' &&
211 (!exists($hash->{actionflag}) || $hash->{actionflag} eq 'I') );
213 push @{$data->{'taxtype'}}, [ $hash->{'value'}, $hash->{'description'} ]
214 if ($hash->{'name'} eq 'TAXTYPE' &&
215 (!exists($hash->{actionflag}) || $hash->{actionflag} eq 'I') );
217 if (exists($hash->{actionflag}) && $hash->{actionflag} eq 'D') {
218 my $name = $hash->{'name'};
219 my $value = $hash->{'value'};
220 return "Bad value for $name: $value"
221 unless $value =~ /^\d+$/;
223 if ($name eq 'TAXCAT' || $name eq 'TAXTYPE') {
224 my @tax_class = qsearch( 'tax_class',
225 { 'data_vendor' => 'cch' },
227 "AND taxclass LIKE '".
228 ($name eq 'TAXTYPE' ? $value : '%').":".
229 ($name eq 'TAXCAT' ? $value : '%')."'",
231 foreach (@tax_class) {
232 my $error = $_->delete;
233 return $error if $error;
241 for qw( data_vendor table name pos length number value description );
242 delete($hash->{actionflag}) if exists($hash->{actionflag});
250 my $sql = "SELECT DISTINCT ".
251 "substring(taxclass from 1 for position(':' in taxclass)-1),".
252 "substring(description from 1 for position(':' in description)-1) ".
253 "FROM tax_class WHERE data_vendor='cch'";
255 my $sth = $dbh->prepare($sql) or die $dbh->errstr;
256 $sth->execute or die $sth->errstr;
257 my @old_types = @{$sth->fetchall_arrayref};
259 $sql = "SELECT DISTINCT ".
260 "substring(taxclass from position(':' in taxclass)+1),".
261 "substring(description from position(':' in description)+1) ".
262 "FROM tax_class WHERE data_vendor='cch'";
264 $sth = $dbh->prepare($sql) or die $dbh->errstr;
265 $sth->execute or die $sth->errstr;
266 my @old_cats = @{$sth->fetchall_arrayref};
268 my $catcount = exists($data->{'taxcat'}) ? scalar(@{$data->{'taxcat'}})
270 my $typecount = exists($data->{'taxtype'}) ? scalar(@{$data->{'taxtype'}})
273 my $count = scalar(@old_types) * $catcount
274 + $typecount * (scalar(@old_cats) + $catcount);
276 $imported = 1 if $format eq 'cch-update'; #empty file ok
278 foreach my $type (@old_types) {
279 foreach my $cat (@{$data->{'taxcat'}}) {
281 if ( $job ) { # progress bar
282 if ( time - $min_sec > $last ) {
283 my $error = $job->update_statustext(
284 int( 100 * $imported / $count ). ",Importing tax classes"
286 die $error if $error;
291 my %hash = ( 'data_vendor' => 'cch',
292 'taxclass' => $type->[0].':'.$cat->[0],
293 'description' => $type->[1].':'.$cat->[1],
295 unless ( qsearchs('tax_class', \%hash) ) {
296 my $tax_class = new FS::tax_class \%hash;
297 my $error = $tax_class->insert;
299 return "can't insert tax_class for ".
300 " old TAXTYPE ". $type->[0].':'.$type->[1].
301 " and new TAXCAT ". $cat->[0].':'. $cat->[1].
311 foreach my $type (@{$data->{'taxtype'}}) {
312 foreach my $cat (@old_cats, @{$data->{'taxcat'}}) {
314 if ( $job ) { # progress bar
315 if ( time - $min_sec > $last ) {
316 my $error = $job->update_statustext(
317 int( 100 * $imported / $count ). ",Importing tax classes"
319 die $error if $error;
325 new FS::tax_class( { 'data_vendor' => 'cch',
326 'taxclass' => $type->[0].':'.$cat->[0],
327 'description' => $type->[1].':'.$cat->[1],
329 my $error = $tax_class->insert;
330 return "can't insert tax_class for new TAXTYPE $type and TAXCAT $cat: $error" if $error;
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;
361 while ( defined($line=<$fh>) ) {
363 if ( $job ) { # progress bar
364 if ( time - $min_sec > $last ) {
365 my $error = $job->update_statustext(
366 int( 100 * $imported / $count ). ",Importing tax classes"
368 die $error if $error;
373 $csv->parse($line) or do {
374 $dbh->rollback if $oldAutoCommit;
375 return "can't parse: ". $csv->error_input();
378 my @columns = $csv->fields();
380 my %tax_class = ( 'data_vendor' => $format );
381 foreach my $field ( @fields ) {
382 $tax_class{$field} = shift @columns;
384 if ( scalar( @columns ) ) {
385 $dbh->rollback if $oldAutoCommit;
386 return "Unexpected trailing columns in line (wrong format?) importing tax_class: $line";
389 my $error = &{$hook}(\%tax_class);
391 $dbh->rollback if $oldAutoCommit;
395 next unless scalar(keys %tax_class);
397 my $tax_class = new FS::tax_class( \%tax_class );
398 $error = $tax_class->insert;
400 $dbh->rollback if $oldAutoCommit;
401 return "can't insert tax_class for $line: $error";
407 my $error = &{$endhook}();
409 $dbh->rollback if $oldAutoCommit;
410 return "can't run end hook: $error";
413 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
415 return "Empty File!" unless ($imported || $format eq 'cch-update');
427 L<FS::Record>, schema.html from the base documentation.