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 if ( $cust_tax_exempt_pkg->cust_main_county ) {
176 my $error = $cust_tax_exempt_pkg->insert;
178 $dbh->rollback if $oldAutoCommit;
179 return "error inserting cust_tax_exempt_pkg: $error";
184 } #foreach $exemption
187 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
192 #helper functions for above
195 my $part_pkg = $self->cust_bill_pkg->part_pkg;
197 return 0 unless $part_pkg; #XXX fails for tax on tax
199 my $method = $self->setuprecur. 'tax';
200 return 0 if $part_pkg->$method =~ /^Y$/i;
202 if ($self->billpkgtaxlocationnum) {
203 my $location_object = $self->cust_bill_pkg_tax_Xlocation;
204 my $tax_object = $location_object->cust_main_county;
205 return 0 if $tax_object && $self->tax_object->$method =~ /^Y$/i;
206 } #elsif ($self->billpkgtaxratelocationnum) { ... }
213 Delete this record from the database.
220 local $SIG{HUP} = 'IGNORE';
221 local $SIG{INT} = 'IGNORE';
222 local $SIG{QUIT} = 'IGNORE';
223 local $SIG{TERM} = 'IGNORE';
224 local $SIG{TSTP} = 'IGNORE';
225 local $SIG{PIPE} = 'IGNORE';
227 my $oldAutoCommit = $FS::UID::AutoCommit;
228 local $FS::UID::AutoCommit = 0;
231 my @negative_exemptions = qsearch('cust_tax_exempt_pkg', {
232 'creditbillpkgnum' => $self->creditbillpkgnum
235 # de-anti-exempt those negative exemptions
237 foreach (@negative_exemptions) {
240 $dbh->rollback if $oldAutoCommit;
245 $error = $self->SUPER::delete(@_);
247 $dbh->rollback if $oldAutoCommit;
251 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
257 =item replace OLD_RECORD
259 Replaces the OLD_RECORD with this one in the database. If there is an error,
260 returns the error, otherwise returns false.
264 # the replace method can be inherited from FS::Record
268 Checks all fields to make sure this is a valid credit applicaiton. If there is
269 an error, returns the error, otherwise returns false. Called by the insert
274 # the check method should currently be supplied - FS::Record contains some
275 # data checking routines
281 $self->ut_numbern('creditbillpkgnum')
282 || $self->ut_foreign_key('creditbillnum', 'cust_credit_bill', 'creditbillnum')
283 || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum' )
284 || $self->ut_foreign_keyn('billpkgtaxlocationnum',
285 'cust_bill_pkg_tax_location',
286 'billpkgtaxlocationnum')
287 || $self->ut_foreign_keyn('billpkgtaxratelocationnum',
288 'cust_bill_pkg_tax_rate_location',
289 'billpkgtaxratelocationnum')
290 || $self->ut_money('amount')
291 || $self->ut_enum('setuprecur', [ 'setup', 'recur' ] )
292 || $self->ut_numbern('sdate')
293 || $self->ut_numbern('edate')
295 return $error if $error;
300 sub cust_credit_bill {
302 qsearchs('cust_credit_bill', { 'creditbillnum' => $self->creditbillnum } );
307 qsearchs('cust_bill_pkg', { 'billpkgnum' => $self->billpkgnum } );
310 sub cust_bill_pkg_tax_Xlocation {
312 if ($self->billpkgtaxlocationnum) {
314 'cust_bill_pkg_tax_location',
315 { 'billpkgtaxlocationnum' => $self->billpkgtaxlocationnum },
318 } elsif ($self->billpkgtaxratelocationnum) {
320 'cust_bill_pkg_tax_rate_location',
321 { 'billpkgtaxratelocationnum' => $self->billpkgtaxratelocationnum },
332 B<setuprecur> field is a kludge to compensate for cust_bill_pkg having separate
333 setup and recur fields. It should be removed once that's fixed.
335 B<insert> method used to assume that the frequency of the package associated
336 with the associated line item remained unchanged during the lifetime of the
337 system. That is still used as a fallback. It may get the tax exemption
338 adjustments wrong if package definitions change frequency. The presense of
339 delete methods in FS::cust_main_county and FS::tax_rate makes crediting of
340 old "texas tax" unreliable in the presense of changing taxes. Explicit tax
341 credit requests? Carry 'taxable' onto line items?
345 L<FS::Record>, schema.html from the base documentation.