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 if ( $cust_tax_exempt_pkg->cust_main_county ) {
171 my $error = $cust_tax_exempt_pkg->insert;
173 $dbh->rollback if $oldAutoCommit;
174 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_bill_pkg_tax_Xlocation {
297 if ($self->billpkgtaxlocationnum) {
299 'cust_bill_pkg_tax_location',
300 { 'billpkgtaxlocationnum' => $self->billpkgtaxlocationnum },
303 } elsif ($self->billpkgtaxratelocationnum) {
305 'cust_bill_pkg_tax_rate_location',
306 { 'billpkgtaxratelocationnum' => $self->billpkgtaxratelocationnum },
317 B<setuprecur> field is a kludge to compensate for cust_bill_pkg having separate
318 setup and recur fields. It should be removed once that's fixed.
320 B<insert> method used to assume that the frequency of the package associated
321 with the associated line item remained unchanged during the lifetime of the
322 system. That is still used as a fallback. It may get the tax exemption
323 adjustments wrong if package definitions change frequency. The presense of
324 delete methods in FS::cust_main_county and FS::tax_rate makes crediting of
325 old "texas tax" unreliable in the presense of changing taxes. Explicit tax
326 credit requests? Carry 'taxable' onto line items?
330 L<FS::Record>, schema.html from the base documentation.