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 @negative_exemptions = qsearch('cust_tax_exempt_pkg', {
227 'creditbillpkgnum' => $self->creditbillpkgnum
230 # de-anti-exempt those negative exemptions
232 foreach (@negative_exemptions) {
235 $dbh->rollback if $oldAutoCommit;
240 $error = $self->SUPER::delete(@_);
242 $dbh->rollback if $oldAutoCommit;
246 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
252 =item replace OLD_RECORD
254 Replaces the OLD_RECORD with this one in the database. If there is an error,
255 returns the error, otherwise returns false.
259 # the replace method can be inherited from FS::Record
263 Checks all fields to make sure this is a valid credit applicaiton. If there is
264 an error, returns the error, otherwise returns false. Called by the insert
269 # the check method should currently be supplied - FS::Record contains some
270 # data checking routines
276 $self->ut_numbern('creditbillpkgnum')
277 || $self->ut_foreign_key('creditbillnum', 'cust_credit_bill', 'creditbillnum')
278 || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum' )
279 || $self->ut_foreign_keyn('billpkgtaxlocationnum',
280 'cust_bill_pkg_tax_location',
281 'billpkgtaxlocationnum')
282 || $self->ut_foreign_keyn('billpkgtaxratelocationnum',
283 'cust_bill_pkg_tax_rate_location',
284 'billpkgtaxratelocationnum')
285 || $self->ut_money('amount')
286 || $self->ut_enum('setuprecur', [ 'setup', 'recur' ] )
287 || $self->ut_numbern('sdate')
288 || $self->ut_numbern('edate')
290 return $error if $error;
295 sub cust_credit_bill {
297 qsearchs('cust_credit_bill', { 'creditbillnum' => $self->creditbillnum } );
302 qsearchs('cust_bill_pkg', { 'billpkgnum' => $self->billpkgnum } );
305 sub cust_bill_pkg_tax_Xlocation {
307 if ($self->billpkgtaxlocationnum) {
309 'cust_bill_pkg_tax_location',
310 { 'billpkgtaxlocationnum' => $self->billpkgtaxlocationnum },
313 } elsif ($self->billpkgtaxratelocationnum) {
315 'cust_bill_pkg_tax_rate_location',
316 { 'billpkgtaxratelocationnum' => $self->billpkgtaxratelocationnum },
327 B<setuprecur> field is a kludge to compensate for cust_bill_pkg having separate
328 setup and recur fields. It should be removed once that's fixed.
330 B<insert> method used to assume that the frequency of the package associated
331 with the associated line item remained unchanged during the lifetime of the
332 system. That is still used as a fallback. It may get the tax exemption
333 adjustments wrong if package definitions change frequency. The presense of
334 delete methods in FS::cust_main_county and FS::tax_rate makes crediting of
335 old "texas tax" unreliable in the presense of changing taxes. Explicit tax
336 credit requests? Carry 'taxable' onto line items?
340 L<FS::Record>, schema.html from the base documentation.