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 $cust_bill_pkg = $self->cust_bill_pkg;
107 #'payable' is the amount charged (either setup or recur)
108 # minus any credit applications, including this one
109 my $payable = $cust_bill_pkg->payable($self->setuprecur);
110 my $part_pkg = $cust_bill_pkg->part_pkg;
111 my $freq = $cust_bill_pkg->freq;
113 $freq = $part_pkg ? ($part_pkg->freq || 1) : 1;#fallback.. assumes unchanged
115 my $taxable_per_month = sprintf("%.2f", $payable / $freq );
116 my $credit_per_month = sprintf("%.2f", $self->amount / $freq ); #pennies?
118 if ($taxable_per_month >= 0) { #panic if its subzero?
119 my $groupby = join(',',
120 qw(taxnum year month exempt_monthly exempt_cust
121 exempt_cust_taxname exempt_setup exempt_recur));
122 my $sum = 'SUM(amount)';
123 my @exemptions = qsearch(
125 'select' => "$groupby, $sum AS amount",
126 'table' => 'cust_tax_exempt_pkg',
127 'hashref' => { billpkgnum => $self->billpkgnum },
128 'extra_sql' => "GROUP BY $groupby HAVING $sum > 0",
131 # each $exemption is now the sum of all monthly exemptions applied to
132 # this line item for a particular taxnum and month.
133 foreach my $exemption ( @exemptions ) {
135 if ( $exemption->exempt_monthly ) {
137 # $taxable_per_month is AFTER inserting the credit application, so
138 # if it's still larger than the exemption, we don't need to adjust
139 next if $taxable_per_month >= $exemption->amount;
140 # the amount of 'excess' exemption already in place (above the
141 # remaining charged amount). We'll de-exempt that much, or the
142 # amount of the new credit, whichever is smaller.
143 $amount = $exemption->amount - $taxable_per_month;
144 # $amount is the amount of 'excess' exemption already existing
145 # (above the remaining taxable charge amount). We'll "de-exempt"
146 # that much, or the amount of the new credit, whichever is smaller.
147 if ($amount > $credit_per_month) {
148 "cust_bill_pkg ". $self->billpkgnum. " Reducing.\n";
149 $amount = $credit_per_month;
151 } elsif ( $exemption->exempt_setup or $exemption->exempt_recur ) {
152 # package defined exemptions: may be setup only, recur only, or both
153 my $method = 'exempt_'.$self->setuprecur;
154 if ( $exemption->$method ) {
155 # then it's exempt from the portion of the charge that this
156 # credit is being applied to
157 $amount = $self->amount;
160 # other types of exemptions: always equal to the amount of
162 $amount = $self->amount;
164 next if $amount == 0;
166 # create a negative exemption
167 my $cust_tax_exempt_pkg = new FS::cust_tax_exempt_pkg {
168 $exemption->hash, # for exempt_ flags, taxnum, month/year
169 'billpkgnum' => $self->billpkgnum,
170 'creditbillpkgnum' => $self->creditbillpkgnum,
171 'amount' => sprintf('%.2f', 0-$amount),
174 my $error = $cust_tax_exempt_pkg->insert;
176 $dbh->rollback if $oldAutoCommit;
177 return "error inserting cust_tax_exempt_pkg: $error";
179 } #foreach $exemption
182 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
187 #helper functions for above
190 my $part_pkg = $self->cust_bill_pkg->part_pkg;
192 return 0 unless $part_pkg; #XXX fails for tax on tax
194 my $method = $self->setuprecur. 'tax';
195 return 0 if $part_pkg->$method =~ /^Y$/i;
197 if ($self->billpkgtaxlocationnum) {
198 my $location_object = $self->cust_bill_pkg_tax_Xlocation;
199 my $tax_object = $location_object->cust_main_county;
200 return 0 if $tax_object && $self->tax_object->$method =~ /^Y$/i;
201 } #elsif ($self->billpkgtaxratelocationnum) { ... }
208 Delete this record from the database.
215 local $SIG{HUP} = 'IGNORE';
216 local $SIG{INT} = 'IGNORE';
217 local $SIG{QUIT} = 'IGNORE';
218 local $SIG{TERM} = 'IGNORE';
219 local $SIG{TSTP} = 'IGNORE';
220 local $SIG{PIPE} = 'IGNORE';
222 my $oldAutoCommit = $FS::UID::AutoCommit;
223 local $FS::UID::AutoCommit = 0;
226 my $original_cust_bill_pkg = $self->cust_bill_pkg;
227 my $cust_bill = $original_cust_bill_pkg->cust_bill;
229 my %hash = $original_cust_bill_pkg->hash;
230 delete $hash{$_} for qw( billpkgnum setup recur );
231 $hash{$self->setuprecur} = $self->amount;
232 my $cust_bill_pkg = new FS::cust_bill_pkg { %hash };
235 my @exemptions = qsearch( 'cust_tax_exempt_pkg',
236 { creditbillpkgnum => $self->creditbillpkgnum }
239 my @generated_exemptions = ();
240 my @unseen_exemptions = ();
241 foreach my $exemption ( @exemptions ) {
242 my $error = $exemption->delete;
244 $dbh->rollback if $oldAutoCommit;
245 return "error deleting cust_tax_exempt_pkg: $error";
248 next if $seen{$exemption->taxnum};
249 $seen{$exemption->taxnum} = 1;
250 push @unseen_exemptions, $exemption;
253 foreach my $exemption ( @unseen_exemptions ) {
254 my $tax_object = $exemption->cust_main_county;
255 unless ($tax_object) {
256 $dbh->rollback if $oldAutoCommit;
257 return "can't find exempted tax";
260 my $hashref_or_error =
261 $tax_object->taxline( [ $cust_bill_pkg ],
262 'custnum' => $cust_bill->custnum,
263 'invoice_time' => $cust_bill->_date,
265 unless (ref($hashref_or_error)) {
266 $dbh->rollback if $oldAutoCommit;
267 return "error calculating taxes: $hashref_or_error";
270 push @generated_exemptions, @{ $cust_bill_pkg->cust_tax_exempt_pkg };
273 foreach my $taxnum ( keys %seen ) {
275 $sum += $_->amount for grep {$_->taxnum == $taxnum} @exemptions;
276 $sum -= $_->amount for grep {$_->taxnum == $taxnum} @generated_exemptions;
277 $sum = sprintf("%.2f", $sum);
278 unless ($sum eq '0.00' || $sum eq '-0.00') {
279 $dbh->rollback if $oldAutoCommit;
280 return "Can't unapply credit without charging tax of $sum";
284 my $error = $self->SUPER::delete(@_);
286 $dbh->rollback if $oldAutoCommit;
290 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
296 =item replace OLD_RECORD
298 Replaces the OLD_RECORD with this one in the database. If there is an error,
299 returns the error, otherwise returns false.
303 # the replace method can be inherited from FS::Record
307 Checks all fields to make sure this is a valid credit applicaiton. If there is
308 an error, returns the error, otherwise returns false. Called by the insert
313 # the check method should currently be supplied - FS::Record contains some
314 # data checking routines
320 $self->ut_numbern('creditbillpkgnum')
321 || $self->ut_foreign_key('creditbillnum', 'cust_credit_bill', 'creditbillnum')
322 || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum' )
323 || $self->ut_foreign_keyn('billpkgtaxlocationnum',
324 'cust_bill_pkg_tax_location',
325 'billpkgtaxlocationnum')
326 || $self->ut_foreign_keyn('billpkgtaxratelocationnum',
327 'cust_bill_pkg_tax_rate_location',
328 'billpkgtaxratelocationnum')
329 || $self->ut_money('amount')
330 || $self->ut_enum('setuprecur', [ 'setup', 'recur' ] )
331 || $self->ut_numbern('sdate')
332 || $self->ut_numbern('edate')
334 return $error if $error;
339 sub cust_credit_bill {
341 qsearchs('cust_credit_bill', { 'creditbillnum' => $self->creditbillnum } );
346 qsearchs('cust_bill_pkg', { 'billpkgnum' => $self->billpkgnum } );
349 sub cust_bill_pkg_tax_Xlocation {
351 if ($self->billpkg_tax_locationnum) {
353 'cust_bill_pkg_tax_location',
354 { 'billpkgtaxlocationnum' => $self->billpkgtaxlocationnum },
357 } elsif ($self->billpkg_tax_rate_locationnum) {
359 'cust_bill_pkg_tax_rate_location',
360 { 'billpkgtaxratelocationnum' => $self->billpkgtaxratelocationnum },
371 B<setuprecur> field is a kludge to compensate for cust_bill_pkg having separate
372 setup and recur fields. It should be removed once that's fixed.
374 B<insert> method used to assume that the frequency of the package associated
375 with the associated line item remained unchanged during the lifetime of the
376 system. That is still used as a fallback. It may get the tax exemption
377 adjustments wrong if package definitions change frequency. The presense of
378 delete methods in FS::cust_main_county and FS::tax_rate makes crediting of
379 old "texas tax" unreliable in the presense of changing taxes. Explicit tax
380 credit requests? Carry 'taxable' onto line items?
384 L<FS::Record>, schema.html from the base documentation.