1 package FS::cust_credit_bill_pkg;
2 use base qw( FS::cust_main_Mixin FS::Record );
5 use FS::Record qw( qsearch qsearchs dbh );
6 use FS::cust_bill_pkg_tax_location;
7 use FS::cust_bill_pkg_tax_rate_location;
8 use FS::cust_tax_exempt_pkg;
12 FS::cust_credit_bill_pkg - Object methods for cust_credit_bill_pkg records
16 use FS::cust_credit_bill_pkg;
18 $record = new FS::cust_credit_bill_pkg \%hash;
19 $record = new FS::cust_credit_bill_pkg { 'column' => 'value' };
21 $error = $record->insert;
23 $error = $new_record->replace($old_record);
25 $error = $record->delete;
27 $error = $record->check;
31 An FS::cust_credit_bill_pkg object represents application of a credit (see
32 L<FS::cust_credit_bill>) to a specific line item within an invoice
33 (see L<FS::cust_bill_pkg>). FS::cust_credit_bill_pkg inherits from FS::Record.
34 The following fields are currently supported:
38 =item creditbillpkgnum - primary key
40 =item creditbillnum - Credit application to the overall invoice (see L<FS::cust_credit::bill>)
42 =item billpkgnum - Line item to which credit is applied (see L<FS::cust_bill_pkg>)
44 =item amount - Amount of the credit applied to this line item.
46 =item setuprecur - 'setup' or 'recur', designates whether the payment was applied to the setup or recurring portion of the line item.
48 =item sdate - starting date of recurring fee
50 =item edate - ending date of recurring fee
54 sdate and edate are specified as UNIX timestamps; see L<perlfunc/"time">. Also
55 see L<Time::Local> and L<Date::Parse> for conversion functions.
63 Creates a new example. To add the example to the database, see L<"insert">.
65 Note that this stores the hash reference, not a distinct copy of the hash it
66 points to. You can ask the object for a copy with the I<hash> method.
70 # the new method can be inherited from FS::Record, if a table method is defined
72 sub table { 'cust_credit_bill_pkg'; }
76 Adds this record to the database. If there is an error, returns the error,
77 otherwise returns false.
84 local $SIG{HUP} = 'IGNORE';
85 local $SIG{INT} = 'IGNORE';
86 local $SIG{QUIT} = 'IGNORE';
87 local $SIG{TERM} = 'IGNORE';
88 local $SIG{TSTP} = 'IGNORE';
89 local $SIG{PIPE} = 'IGNORE';
91 my $oldAutoCommit = $FS::UID::AutoCommit;
92 local $FS::UID::AutoCommit = 0;
95 my $error = $self->SUPER::insert;
97 $dbh->rollback if $oldAutoCommit;
101 my $cust_bill_pkg = $self->cust_bill_pkg;
102 #'payable' is the amount charged (either setup or recur)
103 # minus any credit applications, including this one
104 my $payable = $cust_bill_pkg->payable($self->setuprecur);
105 my $part_pkg = $cust_bill_pkg->part_pkg;
106 my $freq = $cust_bill_pkg->freq;
108 $freq = $part_pkg ? ($part_pkg->freq || 1) : 1;#fallback.. assumes unchanged
110 my $taxable_per_month = sprintf("%.2f", $payable / $freq );
111 my $credit_per_month = sprintf("%.2f", $self->amount / $freq ); #pennies?
113 if ($taxable_per_month >= 0) { #panic if its subzero?
114 my $groupby = join(',',
115 qw(taxnum year month exempt_monthly exempt_cust
116 exempt_cust_taxname exempt_setup exempt_recur));
117 my $sum = 'SUM(amount)';
118 my @exemptions = qsearch(
120 'select' => "$groupby, $sum AS amount",
121 'table' => 'cust_tax_exempt_pkg',
122 'hashref' => { billpkgnum => $self->billpkgnum },
123 'extra_sql' => "GROUP BY $groupby HAVING $sum > 0",
126 # each $exemption is now the sum of all monthly exemptions applied to
127 # this line item for a particular taxnum and month.
128 foreach my $exemption ( @exemptions ) {
130 if ( $exemption->exempt_monthly ) {
132 # $taxable_per_month is AFTER inserting the credit application, so
133 # if it's still larger than the exemption, we don't need to adjust
134 next if $taxable_per_month >= $exemption->amount;
135 # the amount of 'excess' exemption already in place (above the
136 # remaining charged amount). We'll de-exempt that much, or the
137 # amount of the new credit, whichever is smaller.
138 $amount = $exemption->amount - $taxable_per_month;
139 # $amount is the amount of 'excess' exemption already existing
140 # (above the remaining taxable charge amount). We'll "de-exempt"
141 # that much, or the amount of the new credit, whichever is smaller.
142 if ($amount > $credit_per_month) {
143 "cust_bill_pkg ". $self->billpkgnum. " Reducing.\n";
144 $amount = $credit_per_month;
146 } elsif ( $exemption->exempt_setup or $exemption->exempt_recur ) {
147 # package defined exemptions: may be setup only, recur only, or both
148 my $method = 'exempt_'.$self->setuprecur;
149 if ( $exemption->$method ) {
150 # then it's exempt from the portion of the charge that this
151 # credit is being applied to
152 $amount = $self->amount;
155 # other types of exemptions: always equal to the amount of
157 $amount = $self->amount;
159 next if $amount == 0;
161 # create a negative exemption
162 my $cust_tax_exempt_pkg = new FS::cust_tax_exempt_pkg {
163 $exemption->hash, # for exempt_ flags, taxnum, month/year
164 'billpkgnum' => $self->billpkgnum,
165 'creditbillpkgnum' => $self->creditbillpkgnum,
166 'amount' => sprintf('%.2f', 0-$amount),
169 my $error = $cust_tax_exempt_pkg->insert;
171 $dbh->rollback if $oldAutoCommit;
172 return "error inserting cust_tax_exempt_pkg: $error";
174 } #foreach $exemption
177 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
182 #helper functions for above
185 my $part_pkg = $self->cust_bill_pkg->part_pkg;
187 return 0 unless $part_pkg; #XXX fails for tax on tax
189 my $method = $self->setuprecur. 'tax';
190 return 0 if $part_pkg->$method =~ /^Y$/i;
192 if ($self->billpkgtaxlocationnum) {
193 my $location_object = $self->cust_bill_pkg_tax_Xlocation;
194 my $tax_object = $location_object->cust_main_county;
195 return 0 if $tax_object && $self->tax_object->$method =~ /^Y$/i;
196 } #elsif ($self->billpkgtaxratelocationnum) { ... }
203 Delete this record from the database.
210 local $SIG{HUP} = 'IGNORE';
211 local $SIG{INT} = 'IGNORE';
212 local $SIG{QUIT} = 'IGNORE';
213 local $SIG{TERM} = 'IGNORE';
214 local $SIG{TSTP} = 'IGNORE';
215 local $SIG{PIPE} = 'IGNORE';
217 my $oldAutoCommit = $FS::UID::AutoCommit;
218 local $FS::UID::AutoCommit = 0;
221 my @negative_exemptions = qsearch('cust_tax_exempt_pkg', {
222 'creditbillpkgnum' => $self->creditbillpkgnum
225 # de-anti-exempt those negative exemptions
227 foreach (@negative_exemptions) {
230 $dbh->rollback if $oldAutoCommit;
235 $error = $self->SUPER::delete(@_);
237 $dbh->rollback if $oldAutoCommit;
241 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
247 =item replace OLD_RECORD
249 Replaces the OLD_RECORD with this one in the database. If there is an error,
250 returns the error, otherwise returns false.
254 # the replace method can be inherited from FS::Record
258 Checks all fields to make sure this is a valid credit applicaiton. If there is
259 an error, returns the error, otherwise returns false. Called by the insert
264 # the check method should currently be supplied - FS::Record contains some
265 # data checking routines
271 $self->ut_numbern('creditbillpkgnum')
272 || $self->ut_foreign_key('creditbillnum', 'cust_credit_bill', 'creditbillnum')
273 || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum' )
274 || $self->ut_foreign_keyn('billpkgtaxlocationnum',
275 'cust_bill_pkg_tax_location',
276 'billpkgtaxlocationnum')
277 || $self->ut_foreign_keyn('billpkgtaxratelocationnum',
278 'cust_bill_pkg_tax_rate_location',
279 'billpkgtaxratelocationnum')
280 || $self->ut_money('amount')
281 || $self->ut_enum('setuprecur', [ 'setup', 'recur' ] )
282 || $self->ut_numbern('sdate')
283 || $self->ut_numbern('edate')
285 return $error if $error;
290 sub cust_bill_pkg_tax_Xlocation {
292 if ($self->billpkgtaxlocationnum) {
294 'cust_bill_pkg_tax_location',
295 { 'billpkgtaxlocationnum' => $self->billpkgtaxlocationnum },
298 } elsif ($self->billpkgtaxratelocationnum) {
300 'cust_bill_pkg_tax_rate_location',
301 { 'billpkgtaxratelocationnum' => $self->billpkgtaxratelocationnum },
312 B<setuprecur> field is a kludge to compensate for cust_bill_pkg having separate
313 setup and recur fields. It should be removed once that's fixed.
315 B<insert> method used to assume that the frequency of the package associated
316 with the associated line item remained unchanged during the lifetime of the
317 system. That is still used as a fallback. It may get the tax exemption
318 adjustments wrong if package definitions change frequency. The presense of
319 delete methods in FS::cust_main_county and FS::tax_rate makes crediting of
320 old "texas tax" unreliable in the presense of changing taxes. Explicit tax
321 credit requests? Carry 'taxable' onto line items?
325 L<FS::Record>, schema.html from the base documentation.