1 package FS::cust_credit_bill_pkg;
5 use FS::Record qw( qsearch qsearchs dbh );
6 use FS::cust_main_Mixin;
7 use FS::cust_credit_bill;
9 use FS::cust_bill_pkg_tax_location;
10 use FS::cust_bill_pkg_tax_rate_location;
11 use FS::cust_tax_exempt_pkg;
13 @ISA = qw( FS::cust_main_Mixin FS::Record );
17 FS::cust_credit_bill_pkg - Object methods for cust_credit_bill_pkg records
21 use FS::cust_credit_bill_pkg;
23 $record = new FS::cust_credit_bill_pkg \%hash;
24 $record = new FS::cust_credit_bill_pkg { 'column' => 'value' };
26 $error = $record->insert;
28 $error = $new_record->replace($old_record);
30 $error = $record->delete;
32 $error = $record->check;
36 An FS::cust_credit_bill_pkg object represents application of a credit (see
37 L<FS::cust_credit_bill>) to a specific line item within an invoice
38 (see L<FS::cust_bill_pkg>). FS::cust_credit_bill_pkg inherits from FS::Record.
39 The following fields are currently supported:
43 =item creditbillpkgnum - primary key
45 =item creditbillnum - Credit application to the overall invoice (see L<FS::cust_credit::bill>)
47 =item billpkgnum - Line item to which credit is applied (see L<FS::cust_bill_pkg>)
49 =item amount - Amount of the credit applied to this line item.
51 =item setuprecur - 'setup' or 'recur', designates whether the payment was applied to the setup or recurring portion of the line item.
53 =item sdate - starting date of recurring fee
55 =item edate - ending date of recurring fee
59 sdate and edate are specified as UNIX timestamps; see L<perlfunc/"time">. Also
60 see L<Time::Local> and L<Date::Parse> for conversion functions.
68 Creates a new example. To add the example to the database, see L<"insert">.
70 Note that this stores the hash reference, not a distinct copy of the hash it
71 points to. You can ask the object for a copy with the I<hash> method.
75 # the new method can be inherited from FS::Record, if a table method is defined
77 sub table { 'cust_credit_bill_pkg'; }
81 Adds this record to the database. If there is an error, returns the error,
82 otherwise returns false.
89 local $SIG{HUP} = 'IGNORE';
90 local $SIG{INT} = 'IGNORE';
91 local $SIG{QUIT} = 'IGNORE';
92 local $SIG{TERM} = 'IGNORE';
93 local $SIG{TSTP} = 'IGNORE';
94 local $SIG{PIPE} = 'IGNORE';
96 my $oldAutoCommit = $FS::UID::AutoCommit;
97 local $FS::UID::AutoCommit = 0;
100 my $error = $self->SUPER::insert;
102 $dbh->rollback if $oldAutoCommit;
106 my $payable = $self->cust_bill_pkg->payable($self->setuprecur);
107 my $taxable = $self->_is_taxable ? $payable : 0;
108 my $part_pkg = $self->cust_bill_pkg->part_pkg;
109 my $freq = $self->cust_bill_pkg->freq;
111 $freq = $part_pkg ? ($part_pkg->freq || 1) : 1;#fallback.. assumes unchanged
113 my $taxable_per_month = sprintf("%.2f", $taxable / $freq );
114 my $credit_per_month = sprintf("%.2f", $self->amount / $freq ); #pennies?
116 if ($taxable_per_month >= 0) { #panic if its subzero?
117 my $groupby = 'taxnum,year,month';
118 my $sum = 'SUM(amount)';
119 my @exemptions = qsearch(
121 'select' => "$groupby, $sum AS amount",
122 'table' => 'cust_tax_exempt_pkg',
123 'hashref' => { billpkgnum => $self->billpkgnum },
124 'extra_sql' => "GROUP BY $groupby HAVING $sum > 0",
127 foreach my $exemption ( @exemptions ) {
128 next if $taxable_per_month >= $exemption->amount;
129 my $amount = $exemption->amount - $taxable_per_month;
130 if ($amount > $credit_per_month) {
131 "cust_bill_pkg ". $self->billpkgnum. " Reducing.\n";
132 $amount = $credit_per_month;
134 my $cust_tax_exempt_pkg = new FS::cust_tax_exempt_pkg {
135 'billpkgnum' => $self->billpkgnum,
136 'creditbillpkgnum' => $self->creditbillpkgnum,
137 'amount' => sprintf('%.2f', 0-$amount),
138 map { $_ => $exemption->$_ } split(',', $groupby)
140 my $error = $cust_tax_exempt_pkg->insert;
142 $dbh->rollback if $oldAutoCommit;
143 return "error inserting cust_tax_exempt_pkg: $error";
148 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
153 #helper functions for above
156 my $part_pkg = $self->cust_bill_pkg->part_pkg;
158 return 0 unless $part_pkg; #XXX fails for tax on tax
160 my $method = $self->setuprecur. 'tax';
161 return 0 if $part_pkg->$method =~ /^Y$/i;
163 if ($self->billpkgtaxlocationnum) {
164 my $location_object = $self->cust_bill_pkg_tax_Xlocation;
165 my $tax_object = $location_object->cust_main_county;
166 return 0 if $tax_object && $self->tax_object->$method =~ /^Y$/i;
167 } #elsif ($self->billpkgtaxratelocationnum) { ... }
174 Delete this record from the database.
181 local $SIG{HUP} = 'IGNORE';
182 local $SIG{INT} = 'IGNORE';
183 local $SIG{QUIT} = 'IGNORE';
184 local $SIG{TERM} = 'IGNORE';
185 local $SIG{TSTP} = 'IGNORE';
186 local $SIG{PIPE} = 'IGNORE';
188 my $oldAutoCommit = $FS::UID::AutoCommit;
189 local $FS::UID::AutoCommit = 0;
192 my $original_cust_bill_pkg = $self->cust_bill_pkg;
193 my $cust_bill = $original_cust_bill_pkg->cust_bill;
195 my %hash = $original_cust_bill_pkg->hash;
196 delete $hash{$_} for qw( billpkgnum setup recur );
197 $hash{$self->setuprecur} = $self->amount;
198 my $cust_bill_pkg = new FS::cust_bill_pkg { %hash };
201 my @exemptions = qsearch( 'cust_tax_exempt_pkg',
202 { creditbillpkgnum => $self->creditbillpkgnum }
205 my @generated_exemptions = ();
206 my @unseen_exemptions = ();
207 foreach my $exemption ( @exemptions ) {
208 my $error = $exemption->delete;
210 $dbh->rollback if $oldAutoCommit;
211 return "error deleting cust_tax_exempt_pkg: $error";
214 next if $seen{$exemption->taxnum};
215 $seen{$exemption->taxnum} = 1;
216 push @unseen_exemptions, $exemption;
219 foreach my $exemption ( @unseen_exemptions ) {
220 my $tax_object = $exemption->cust_main_county;
221 unless ($tax_object) {
222 $dbh->rollback if $oldAutoCommit;
223 return "can't find exempted tax";
226 my $hashref_or_error =
227 $tax_object->taxline( [ $cust_bill_pkg ],
228 'custnum' => $cust_bill->custnum,
229 'invoice_time' => $cust_bill->_date,
231 unless (ref($hashref_or_error)) {
232 $dbh->rollback if $oldAutoCommit;
233 return "error calculating taxes: $hashref_or_error";
236 push @generated_exemptions, @{ $cust_bill_pkg->_cust_tax_exempt_pkg || [] };
239 foreach my $taxnum ( keys %seen ) {
241 $sum += $_->amount for grep {$_->taxnum == $taxnum} @exemptions;
242 $sum -= $_->amount for grep {$_->taxnum == $taxnum} @generated_exemptions;
243 $sum = sprintf("%.2f", $sum);
244 unless ($sum eq '0.00' || $sum eq '-0.00') {
245 $dbh->rollback if $oldAutoCommit;
246 return "Can't unapply credit without charging tax";
250 my $error = $self->SUPER::delete(@_);
252 $dbh->rollback if $oldAutoCommit;
256 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
262 =item replace OLD_RECORD
264 Replaces the OLD_RECORD with this one in the database. If there is an error,
265 returns the error, otherwise returns false.
269 # the replace method can be inherited from FS::Record
273 Checks all fields to make sure this is a valid credit applicaiton. If there is
274 an error, returns the error, otherwise returns false. Called by the insert
279 # the check method should currently be supplied - FS::Record contains some
280 # data checking routines
286 $self->ut_numbern('creditbillpkgnum')
287 || $self->ut_foreign_key('creditbillnum', 'cust_credit_bill', 'creditbillnum')
288 || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum' )
289 || $self->ut_foreign_keyn('billpkgtaxlocationnum',
290 'cust_bill_pkg_tax_location',
291 'billpkgtaxlocationnum')
292 || $self->ut_foreign_keyn('billpkgtaxratelocationnum',
293 'cust_bill_pkg_tax_rate_location',
294 'billpkgtaxratelocationnum')
295 || $self->ut_money('amount')
296 || $self->ut_enum('setuprecur', [ 'setup', 'recur' ] )
297 || $self->ut_numbern('sdate')
298 || $self->ut_numbern('edate')
300 return $error if $error;
305 sub cust_credit_bill {
307 qsearchs('cust_credit_bill', { 'creditbillnum' => $self->creditbillnum } );
312 qsearchs('cust_bill_pkg', { 'billpkgnum' => $self->billpkgnum } );
315 sub cust_bill_pkg_tax_Xlocation {
317 if ($self->billpkg_tax_locationnum) {
319 'cust_bill_pkg_tax_location',
320 { 'billpkgtaxlocationnum' => $self->billpkgtaxlocationnum },
323 } elsif ($self->billpkg_tax_rate_locationnum) {
325 'cust_bill_pkg_tax_rate_location',
326 { 'billpkgtaxratelocationnum' => $self->billpkgtaxratelocationnum },
337 B<setuprecur> field is a kludge to compensate for cust_bill_pkg having separate
338 setup and recur fields. It should be removed once that's fixed.
340 B<insert> method used to assume that the frequency of the package associated
341 with the associated line item remained unchanged during the lifetime of the
342 system. That is still used as a fallback. It may get the tax exemption
343 adjustments wrong if package definitions change frequency. The presense of
344 delete methods in FS::cust_main_county and FS::tax_rate makes crediting of
345 old "texas tax" unreliable in the presense of changing taxes. Explicit tax
346 credit requests? Carry 'taxable' onto line items?
350 L<FS::Record>, schema.html from the base documentation.