checkpoint of new tax rating system
[freeside.git] / FS / FS / tax_class.pm
1 package FS::tax_class;
2
3 use strict;
4 use vars qw( @ISA );
5 use FS::UID qw(dbh);
6 use FS::Record qw( qsearch qsearchs );
7
8 @ISA = qw(FS::Record);
9
10 =head1 NAME
11
12 FS::tax_class - Object methods for tax_class records
13
14 =head1 SYNOPSIS
15
16   use FS::tax_class;
17
18   $record = new FS::tax_class \%hash;
19   $record = new FS::tax_class { 'column' => 'value' };
20
21   $error = $record->insert;
22
23   $error = $new_record->replace($old_record);
24
25   $error = $record->delete;
26
27   $error = $record->check;
28
29 =head1 DESCRIPTION
30
31 An FS::tax_class object represents a tax class.  FS::tax_class
32 inherits from FS::Record.  The following fields are currently supported:
33
34 =over 4
35
36 =item taxclassnum
37
38 Primary key
39
40 =item data_vendor
41
42 Vendor of the tax data
43
44 =item taxclass
45
46 Tax class
47
48 =item description
49
50 Human readable description of the tax class
51
52 =back
53
54 =head1 METHODS
55
56 =over 4
57
58 =item new HASHREF
59
60 Creates a new tax class.  To add the tax class to the database, see L<"insert">.
61
62 Note that this stores the hash reference, not a distinct copy of the hash it
63 points to.  You can ask the object for a copy with the I<hash> method.
64
65 =cut
66
67 sub table { 'tax_class'; }
68
69 =item insert
70
71 Adds this record to the database.  If there is an error, returns the error,
72 otherwise returns false.
73
74 =cut
75
76 =item delete
77
78 Delete this record from the database.
79
80 =cut
81
82 =item replace OLD_RECORD
83
84 Replaces the OLD_RECORD with this one in the database.  If there is an error,
85 returns the error, otherwise returns false.
86
87 =cut
88
89 =item check
90
91 Checks all fields to make sure this is a valid tax class.  If there is
92 an error, returns the error, otherwise returns false.  Called by the insert
93 and replace methods.
94
95 =cut
96
97 sub check {
98   my $self = shift;
99
100   my $error = 
101     $self->ut_numbern('taxclassnum')
102     || $self->ut_text('taxclass')
103     || $self->ut_textn('data_vendor')
104     || $self->ut_textn('description')
105   ;
106   return $error if $error;
107
108   $self->SUPER::check;
109 }
110
111 =item batch_import
112
113 Loads part_pkg_taxrate records from an external CSV file.  If there is
114 an error, returns the error, otherwise returns false. 
115
116 =cut 
117
118 sub batch_import {
119   my $param = shift;
120
121   my $fh = $param->{filehandle};
122   my $format = $param->{'format'};
123
124   my @fields;
125   my $hook;
126   my $endhook;
127   my $data = {};
128   my $imported = 0;
129
130   if ( $format eq 'cch' ) {
131     @fields = qw( table name pos number length value description );
132
133     $hook = sub { 
134       my $hash = shift;
135
136       if ($hash->{'table'} eq 'DETAIL') {
137         push @{$data->{'taxcat'}}, [ $hash->{'value'}, $hash->{'description'} ]
138           if $hash->{'name'} eq 'TAXCAT';
139
140         push @{$data->{'taxtype'}}, [ $hash->{'value'}, $hash->{'description'} ]
141           if $hash->{'name'} eq 'TAXTYPE';
142       }
143
144       delete($hash->{$_})
145         for qw( data_vendor table name pos number length value description );
146
147       '';
148
149     };
150
151     $endhook = sub { 
152       foreach my $type (@{$data->{'taxtype'}}) {
153         foreach my $cat (@{$data->{'taxcat'}}) {
154           my $tax_class =
155             new FS::tax_class( { 'data_vendor' => 'cch',
156                                  'taxclass'    => $type->[0].':'.$cat->[0],
157                                  'description' => $type->[1].':'.$cat->[1],
158                              } );
159           my $error = $tax_class->insert;
160           return $error if $error;
161           $imported++;
162         }
163       }
164       '';
165     };
166
167   } elsif ( $format eq 'extended' ) {
168     die "unimplemented\n";
169     @fields = qw( );
170     $hook = sub {};
171   } else {
172     die "unknown format $format";
173   }
174
175   eval "use Text::CSV_XS;";
176   die $@ if $@;
177
178   my $csv = new Text::CSV_XS;
179
180   local $SIG{HUP} = 'IGNORE';
181   local $SIG{INT} = 'IGNORE';
182   local $SIG{QUIT} = 'IGNORE';
183   local $SIG{TERM} = 'IGNORE';
184   local $SIG{TSTP} = 'IGNORE';
185   local $SIG{PIPE} = 'IGNORE';
186
187   my $oldAutoCommit = $FS::UID::AutoCommit;
188   local $FS::UID::AutoCommit = 0;
189   my $dbh = dbh;
190   
191   my $line;
192   while ( defined($line=<$fh>) ) {
193     $csv->parse($line) or do {
194       $dbh->rollback if $oldAutoCommit;
195       return "can't parse: ". $csv->error_input();
196     };
197
198     my @columns = $csv->fields();
199
200     my %tax_class = ( 'data_vendor' => $format );
201     foreach my $field ( @fields ) {
202       $tax_class{$field} = shift @columns; 
203     }
204     my $error = &{$hook}(\%tax_class);
205     if ( $error ) {
206       $dbh->rollback if $oldAutoCommit;
207       return $error;
208     }
209     next unless scalar(keys %tax_class);
210
211     my $tax_class = new FS::tax_class( \%tax_class );
212     $error = $tax_class->insert;
213
214     if ( $error ) {
215       $dbh->rollback if $oldAutoCommit;
216       return "can't insert tax_class for $line: $error";
217     }
218
219     $imported++;
220   }
221
222   my $error = &{$endhook}();
223   if ( $error ) {
224     $dbh->rollback if $oldAutoCommit;
225     return "can't insert tax_class for $line: $error";
226   }
227
228   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
229
230   return "Empty file!" unless $imported;
231
232   ''; #no error
233
234 }
235
236
237 =back
238
239 =head1 BUGS
240
241 =head1 SEE ALSO
242
243 L<FS::Record>, schema.html from the base documentation.
244
245 =cut
246
247 1;
248
249