6 use FS::Record qw( qsearch qsearchs );
7 use FS::Misc qw( csv_from_fixed );
13 FS::tax_class - Object methods for tax_class records
19 $record = new FS::tax_class \%hash;
20 $record = new FS::tax_class { 'column' => 'value' };
22 $error = $record->insert;
24 $error = $new_record->replace($old_record);
26 $error = $record->delete;
28 $error = $record->check;
32 An FS::tax_class object represents a tax class. FS::tax_class
33 inherits from FS::Record. The following fields are currently supported:
43 Vendor of the tax data
51 Human readable description of the tax class
61 Creates a new tax class. To add the tax class to the database, see L<"insert">.
63 Note that this stores the hash reference, not a distinct copy of the hash it
64 points to. You can ask the object for a copy with the I<hash> method.
68 sub table { 'tax_class'; }
72 Adds this record to the database. If there is an error, returns the error,
73 otherwise returns false.
79 Delete this record from the database.
86 return "Can't delete a tax class which has tax rates!"
87 if qsearch( 'tax_rate', { 'taxclassnum' => $self->taxclassnum } );
89 return "Can't delete a tax class which has package tax rates!"
90 if qsearch( 'part_pkg_taxrate', { 'taxclassnum' => $self->taxclassnum } );
92 return "Can't delete a tax class which has package tax rates!"
93 if qsearch( 'part_pkg_taxrate', { 'taxclassnumtaxed' => $self->taxclassnum } );
95 return "Can't delete a tax class which has package tax overrides!"
96 if qsearch( 'part_pkg_taxoverride', { 'taxclassnum' => $self->taxclassnum } );
98 $self->SUPER::delete(@_);
102 =item replace OLD_RECORD
104 Replaces the OLD_RECORD with this one in the database. If there is an error,
105 returns the error, otherwise returns false.
111 Checks all fields to make sure this is a valid tax class. If there is
112 an error, returns the error, otherwise returns false. Called by the insert
121 $self->ut_numbern('taxclassnum')
122 || $self->ut_text('taxclass')
123 || $self->ut_textn('data_vendor')
124 || $self->ut_textn('description')
126 return $error if $error;
133 Loads part_pkg_taxrate records from an external CSV file. If there is
134 an error, returns the error, otherwise returns false.
139 my ($param, $job) = @_;
141 my $fh = $param->{filehandle};
142 my $format = $param->{'format'};
151 my @column_lengths = ();
152 my @column_callbacks = ();
153 if ( $format eq 'cch-fixed' || $format eq 'cch-fixed-update' ) {
154 $format =~ s/-fixed//;
155 push @column_lengths, qw( 8 10 3 2 2 10 100 );
156 push @column_lengths, 1 if $format eq 'cch-update';
160 my ( $count, $last, $min_sec ) = (0, time, 5); #progressbar
161 if ( $job || scalar(@column_lengths) ) {
162 my $error = csv_from_fixed(\$fh, \$count, \@column_lengths);
163 return $error if $error;
166 if ( $format eq 'cch' || $format eq 'cch-update' ) {
167 @fields = qw( table name pos length number value description );
168 push @fields, 'actionflag' if $format eq 'cch-update';
173 if ($hash->{'table'} eq 'DETAIL') {
174 push @{$data->{'taxcat'}}, [ $hash->{'value'}, $hash->{'description'} ]
175 if ($hash->{'name'} eq 'TAXCAT' &&
176 (!exists($hash->{actionflag}) || $hash->{actionflag} eq 'I') );
178 push @{$data->{'taxtype'}}, [ $hash->{'value'}, $hash->{'description'} ]
179 if ($hash->{'name'} eq 'TAXTYPE' &&
180 (!exists($hash->{actionflag}) || $hash->{actionflag} eq 'I') );
182 if (exists($hash->{actionflag}) && $hash->{actionflag} eq 'D') {
183 my $name = $hash->{'name'};
184 my $value = $hash->{'value'};
185 return "Bad value for $name: $value"
186 unless $value =~ /^\d+$/;
188 if ($name eq 'TAXCAT' || $name eq 'TAXTYPE') {
189 my @tax_class = qsearch( 'tax_class',
190 { 'data_vendor' => 'cch' },
192 "AND taxclass LIKE '".
193 ($name eq 'TAXTYPE' ? $value : '%').":".
194 ($name eq 'TAXCAT' ? $value : '%')."'",
196 foreach (@tax_class) {
197 my $error = $_->delete;
198 return $error if $error;
206 for qw( data_vendor table name pos length number value description );
207 delete($hash->{actionflag}) if exists($hash->{actionflag});
215 my $sql = "SELECT DISTINCT ".
216 "substring(taxclass from 1 for position(':' in taxclass)-1),".
217 "substring(description from 1 for position(':' in description)-1) ".
218 "FROM tax_class WHERE data_vendor='cch'";
220 my $sth = $dbh->prepare($sql) or die $dbh->errstr;
221 $sth->execute or die $sth->errstr;
222 my @old_types = @{$sth->fetchall_arrayref};
224 $sql = "SELECT DISTINCT ".
225 "substring(taxclass from position(':' in taxclass)+1),".
226 "substring(description from position(':' in description)+1) ".
227 "FROM tax_class WHERE data_vendor='cch'";
229 $sth = $dbh->prepare($sql) or die $dbh->errstr;
230 $sth->execute or die $sth->errstr;
231 my @old_cats = @{$sth->fetchall_arrayref};
233 my $catcount = exists($data->{'taxcat'}) ? scalar(@{$data->{'taxcat'}})
235 my $typecount = exists($data->{'taxtype'}) ? scalar(@{$data->{'taxtype'}})
238 my $count = scalar(@old_types) * $catcount
239 + $typecount * (scalar(@old_cats) + $catcount);
241 $imported = 1 if $format eq 'cch-update'; #empty file ok
243 foreach my $type (@old_types) {
244 foreach my $cat (@{$data->{'taxcat'}}) {
246 if ( $job ) { # progress bar
247 if ( time - $min_sec > $last ) {
248 my $error = $job->update_statustext(
249 int( 100 * $imported / $count ). ",Importing tax classes"
251 die $error if $error;
257 new FS::tax_class( { 'data_vendor' => 'cch',
258 'taxclass' => $type->[0].':'.$cat->[0],
259 'description' => $type->[1].':'.$cat->[1],
261 my $error = $tax_class->insert;
262 return $error if $error;
267 foreach my $type (@{$data->{'taxtype'}}) {
268 foreach my $cat (@old_cats, @{$data->{'taxcat'}}) {
270 if ( $job ) { # progress bar
271 if ( time - $min_sec > $last ) {
272 my $error = $job->update_statustext(
273 int( 100 * $imported / $count ). ",Importing tax classes"
275 die $error if $error;
281 new FS::tax_class( { 'data_vendor' => 'cch',
282 'taxclass' => $type->[0].':'.$cat->[0],
283 'description' => $type->[1].':'.$cat->[1],
285 my $error = $tax_class->insert;
286 return $error if $error;
294 } elsif ( $format eq 'extended' ) {
295 die "unimplemented\n";
299 die "unknown format $format";
302 eval "use Text::CSV_XS;";
305 my $csv = new Text::CSV_XS;
307 local $SIG{HUP} = 'IGNORE';
308 local $SIG{INT} = 'IGNORE';
309 local $SIG{QUIT} = 'IGNORE';
310 local $SIG{TERM} = 'IGNORE';
311 local $SIG{TSTP} = 'IGNORE';
312 local $SIG{PIPE} = 'IGNORE';
314 my $oldAutoCommit = $FS::UID::AutoCommit;
315 local $FS::UID::AutoCommit = 0;
317 while ( defined($line=<$fh>) ) {
319 if ( $job ) { # progress bar
320 if ( time - $min_sec > $last ) {
321 my $error = $job->update_statustext(
322 int( 100 * $imported / $count ). ",Importing tax classes"
324 die $error if $error;
329 $csv->parse($line) or do {
330 $dbh->rollback if $oldAutoCommit;
331 return "can't parse: ". $csv->error_input();
334 my @columns = $csv->fields();
336 my %tax_class = ( 'data_vendor' => $format );
337 foreach my $field ( @fields ) {
338 $tax_class{$field} = shift @columns;
340 if ( scalar( @columns ) ) {
341 $dbh->rollback if $oldAutoCommit;
342 return "Unexpected trailing columns in line (wrong format?): $line";
345 my $error = &{$hook}(\%tax_class);
347 $dbh->rollback if $oldAutoCommit;
351 next unless scalar(keys %tax_class);
353 my $tax_class = new FS::tax_class( \%tax_class );
354 $error = $tax_class->insert;
356 $dbh->rollback if $oldAutoCommit;
357 return "can't insert tax_class for $line: $error";
363 my $error = &{$endhook}();
365 $dbh->rollback if $oldAutoCommit;
366 return "can't insert tax_class for $line: $error";
369 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
371 return "Empty File!" unless ($imported || $format eq 'cch-update');
381 batch_import does not handle mixed I and D records in the same file for
386 L<FS::Record>, schema.html from the base documentation.