use FS::pkg_category;
use FS::cust_event_fee;
use FS::Log;
+use FS::TaxEngine;
# 1 is mostly method/subroutine entry and options
# 2 traces progress of some operations
sub bill_and_collect {
my( $self, %options ) = @_;
- my $log = FS::Log->new('bill_and_collect');
- $log->debug('start', object => $self, agentnum => $self->agentnum);
+ my $log = FS::Log->new('FS::cust_main::Billing::bill_and_collect');
+ my %logopt = (object => $self);
+ $log->debug('start', %logopt);
my $error;
);
$job->update_statustext('0,cleaning expired packages') if $job;
+ $log->debug('canceling expired packages', %logopt);
$error = $self->cancel_expired_pkgs( $actual_time );
if ( $error ) {
$error = "Error expiring custnum ". $self->custnum. ": $error";
else { warn $error; }
}
+ $log->debug('suspending adjourned packages', %logopt);
$error = $self->suspend_adjourned_pkgs( $actual_time );
if ( $error ) {
$error = "Error adjourning custnum ". $self->custnum. ": $error";
else { warn $error; }
}
+ $log->debug('unsuspending resumed packages', %logopt);
$error = $self->unsuspend_resumed_pkgs( $actual_time );
if ( $error ) {
$error = "Error resuming custnum ".$self->custnum. ": $error";
}
$job->update_statustext('20,billing packages') if $job;
+ $log->debug('billing packages', %logopt);
$error = $self->bill( %options );
if ( $error ) {
$error = "Error billing custnum ". $self->custnum. ": $error";
}
$job->update_statustext('50,applying payments and credits') if $job;
+ $log->debug('applying payments and credits', %logopt);
$error = $self->apply_payments_and_credits;
if ( $error ) {
$error = "Error applying custnum ". $self->custnum. ": $error";
else { warn $error; }
}
- $job->update_statustext('70,running collection events') if $job;
- unless ( $conf->exists('cancelled_cust-noevents')
- && ! $self->num_ncancelled_pkgs
- ) {
+ # In a batch tax environment, do not run collection if any pending
+ # invoices were created. Collection will run after the next tax batch.
+ my $tax = FS::TaxEngine->new;
+ if ( $tax->info->{batch} and
+ qsearch('cust_bill', { custnum => $self->custnum, pending => 'Y' })
+ )
+ {
+ warn "skipped collection for custnum ".$self->custnum.
+ " due to pending invoices\n" if $DEBUG;
+ } elsif ( $conf->exists('cancelled_cust-noevents')
+ && ! $self->num_ncancelled_pkgs )
+ {
+ warn "skipped collection for custnum ".$self->custnum.
+ " because they have no active packages\n" if $DEBUG;
+ } else {
+ # run collection normally
+ $job->update_statustext('70,running collection events') if $job;
+ $log->debug('running collection events', %logopt);
$error = $self->collect( %options );
if ( $error ) {
$error = "Error collecting custnum ". $self->custnum. ": $error";
else { warn $error; }
}
}
+
$job->update_statustext('100,finished') if $job;
- $log->debug('finish', object => $self, agentnum => $self->agentnum);
+ $log->debug('finish', %logopt);
'';
A hashref of pkgparts to exclude from this billing run (can also be specified as a comma-separated scalar).
+=item no_prepaid
+
+Do not bill prepaid packages. Used by freeside-daily.
+
=item invoice_time
Used in conjunction with the I<time> option, this option specifies the date of for the generated invoices. Other calculations, such as whether or not to generate the invoice in the first place, are not affected.
return '' if $self->payby eq 'COMP';
local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG;
+ my $log = FS::Log->new('FS::cust_main::Billing::bill');
+ my %logopt = (object => $self);
+ $log->debug('start', %logopt);
warn "$me bill customer ". $self->custnum. "\n"
if $DEBUG;
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
+ $log->debug('acquiring lock', %logopt);
warn "$me acquiring lock on customer ". $self->custnum. "\n"
if $DEBUG;
$self->select_for_update; #mutex
+ $log->debug('running pre-bill events', %logopt);
warn "$me running pre-bill events for customer ". $self->custnum. "\n"
if $DEBUG;
return $error;
}
+ $log->debug('done running pre-bill events', %logopt);
warn "$me done running pre-bill events for customer ". $self->custnum. "\n"
if $DEBUG;
my %total_setup = map { my $z = 0; $_ => \$z; } @passes;
my %total_recur = map { my $z = 0; $_ => \$z; } @passes;
- my %taxlisthash = map { $_ => {} } @passes;
-
my @precommit_hooks = ();
$options{'pkg_list'} ||= [ $self->ncancelled_pkgs ]; #param checks?
+
+ my %tax_engines;
+ my $tax_is_batch = '';
+ foreach (@passes) {
+ $tax_engines{$_} = FS::TaxEngine->new(cust_main => $self,
+ invoice_time => $invoice_time,
+ cancel => $options{cancel}
+ );
+ $tax_is_batch ||= $tax_engines{$_}->info->{batch};
+ }
foreach my $cust_pkg ( @{ $options{'pkg_list'} } ) {
next if $options{'not_pkgpart'}->{$cust_pkg->pkgpart};
+ my $part_pkg = $cust_pkg->part_pkg;
+
+ next if $options{'no_prepaid'} && $part_pkg->is_prepaid;
+
+ $log->debug('bill package '. $cust_pkg->pkgnum, %logopt);
warn " bill package ". $cust_pkg->pkgnum. "\n" if $DEBUG;
#? to avoid use of uninitialized value errors... ?
$cust_pkg->setfield('bill', '')
unless defined($cust_pkg->bill);
- #my $part_pkg = $cust_pkg->part_pkg;
-
my $real_pkgpart = $cust_pkg->pkgpart;
my %hash = $cust_pkg->hash;
# we could implement this bit as FS::part_pkg::has_hidden, but we already
# suffer from performance issues
$options{has_hidden} = 0;
- my @part_pkg = $cust_pkg->part_pkg->self_and_bill_linked;
+ my @part_pkg = $part_pkg->self_and_bill_linked;
$options{has_hidden} = 1 if ($part_pkg[1] && $part_pkg[1]->hidden);
# if this package was changed from another package,
'line_items' => $cust_bill_pkg{$pass},
'setup' => $total_setup{$pass},
'recur' => $total_recur{$pass},
- 'tax_matrix' => $taxlisthash{$pass},
+ 'tax_engine' => $tax_engines{$pass},
'time' => $time,
'real_pkgpart' => $real_pkgpart,
'options' => \%options,
my @cust_bill_pkg = _omit_zero_value_bundles(@{ $cust_bill_pkg{$pass} });
- next unless @cust_bill_pkg; #don't create an invoice w/o line items
-
warn "$me billing pass $pass\n"
#.Dumper(\@cust_bill_pkg)."\n"
if $DEBUG > 2;
hashref => { 'billpkgnum' => '' }
);
warn "$me found pending fee events:\n".Dumper(\@pending_event_fees)."\n"
- if @pending_event_fees;
+ if @pending_event_fees and $DEBUG > 1;
+
+ # determine whether to generate an invoice
+ my $generate_bill = scalar(@cust_bill_pkg) > 0;
+ foreach my $event_fee (@pending_event_fees) {
+ $generate_bill = 1 unless $event_fee->nextbill;
+ }
+
+ # don't create an invoice with no line items, or where the only line
+ # items are fees that are supposed to be held until the next invoice
+ next if !$generate_bill;
+
+ # calculate fees...
my @fee_items;
foreach my $event_fee (@pending_event_fees) {
my $object = $event_fee->cust_event->cust_X;
+ my $part_fee = $event_fee->part_fee;
my $cust_bill;
- if ( $object->isa('FS::cust_main') ) {
+ if ( $object->isa('FS::cust_main')
+ or $object->isa('FS::cust_pkg')
+ or $object->isa('FS::cust_pay_batch') )
+ {
# Not the real cust_bill object that will be inserted--in particular
# there are no taxes yet. If you want to charge a fee on the total
# invoice amount including taxes, you have to put the fee on the next
'charged' => ${ $total_setup{$pass} } +
${ $total_recur{$pass} },
});
+
+ # If this is a package event, only apply the fee to line items
+ # from that package.
+ if ($object->isa('FS::cust_pkg')) {
+ $cust_bill->set('cust_bill_pkg',
+ [ grep { $_->pkgnum == $object->pkgnum } @cust_bill_pkg ]
+ );
+ }
+
} elsif ( $object->isa('FS::cust_bill') ) {
# simple case: applying the fee to a previous invoice (late fee,
# etc.)
$cust_bill = $object;
}
- my $part_fee = $event_fee->part_fee;
# if the fee def belongs to a different agent, don't charge the fee.
# event conditions should prevent this, but just in case they don't,
# skip the fee.
# also skip if it's disabled
next if $part_fee->disabled eq 'Y';
# calculate the fee
- my $fee_item = $event_fee->part_fee->lineitem($cust_bill);
+ my $fee_item = $part_fee->lineitem($cust_bill) or next;
# link this so that we can clear the marker on inserting the line item
$fee_item->set('cust_event_fee', $event_fee);
push @fee_items, $fee_item;
+
}
+
+ # add fees to the invoice
foreach my $fee_item (@fee_items) {
push @cust_bill_pkg, $fee_item;
my $part_fee = $fee_item->part_fee;
my $fee_location = $self->ship_location; # I think?
+
+ my $error = $tax_engines{''}->add_sale($fee_item);
- my $error = $self->_handle_taxes(
- $taxlisthash{$pass},
- $fee_item,
- location => $fee_location
- );
return $error if $error;
}
'line_items' => \@cust_bill_pkg,
'setup' => $total_setup{$pass},
'recur' => $total_recur{$pass},
- 'tax_matrix' => $taxlisthash{$pass},
+ 'tax_engine' => $tax_engines{$pass},
'time' => $time,
'real_pkgpart' => $real_pkgpart,
'options' => \%postal_options,
}
- my $listref_or_error =
- $self->calculate_taxes( \@cust_bill_pkg, $taxlisthash{$pass}, $invoice_time);
-
- unless ( ref( $listref_or_error ) ) {
- $dbh->rollback if $oldAutoCommit && !$options{no_commit};
- return $listref_or_error;
- }
-
- foreach my $taxline ( @$listref_or_error ) {
- ${ $total_setup{$pass} } =
- sprintf('%.2f', ${ $total_setup{$pass} } + $taxline->setup );
- push @cust_bill_pkg, $taxline;
- }
-
#add tax adjustments
+ #XXX does this work with batch tax engines?
warn "adding tax adjustments...\n" if $DEBUG > 2;
foreach my $cust_tax_adjustment (
qsearch('cust_tax_adjustment', { 'custnum' => $self->custnum,
my $charged = sprintf('%.2f', ${ $total_setup{$pass} } + ${ $total_recur{$pass} } );
- my @cust_bill = $self->cust_bill;
my $balance = $self->balance;
- my $previous_bill = $cust_bill[-1] if @cust_bill;
- my $previous_balance = 0;
- if ( $previous_bill ) {
- $previous_balance = $previous_bill->billing_balance
- + $previous_bill->charged;
- }
+ my $previous_bill = qsearchs({ 'table' => 'cust_bill',
+ 'hashref' => { custnum=>$self->custnum },
+ 'extra_sql' => 'ORDER BY _date DESC LIMIT 1',
+ });
+ my $previous_balance =
+ $previous_bill
+ ? ( $previous_bill->billing_balance + $previous_bill->charged )
+ : 0;
+
+ $log->debug('creating the new invoice', %logopt);
warn "creating the new invoice\n" if $DEBUG;
#create the new invoice
my $cust_bill = new FS::cust_bill ( {
'previous_balance' => $previous_balance,
'invoice_terms' => $options{'invoice_terms'},
'cust_bill_pkg' => \@cust_bill_pkg,
+ 'pending' => 'Y', # clear this after doing taxes
} );
- $error = $cust_bill->insert unless $options{no_commit};
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit && !$options{no_commit};
- return "can't create invoice for customer #". $self->custnum. ": $error";
+
+ if (!$options{no_commit}) {
+ # probably we ought to insert it as pending, and then rollback
+ # without ever un-pending it
+ $error = $cust_bill->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit && !$options{no_commit};
+ return "can't create invoice for customer #". $self->custnum. ": $error";
+ }
+
}
+
+ # calculate and append taxes
+ if ( ! $tax_is_batch) {
+ my $arrayref_or_error = $tax_engines{$pass}->calculate_taxes($cust_bill);
+
+ unless ( ref( $arrayref_or_error ) ) {
+ $dbh->rollback if $oldAutoCommit && !$options{no_commit};
+ return $arrayref_or_error;
+ }
+
+ # or should this be in TaxEngine?
+ my $total_tax = 0;
+ foreach my $taxline ( @$arrayref_or_error ) {
+ $total_tax += $taxline->setup;
+ $taxline->set('invnum' => $cust_bill->invnum); # just to be sure
+ push @cust_bill_pkg, $taxline; # for return_bill
+
+ if (!$options{no_commit}) {
+ my $error = $taxline->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ }
+
+ # add tax to the invoice amount and finalize it
+ ${ $total_setup{$pass} } = sprintf('%.2f', ${ $total_setup{$pass} } + $total_tax);
+ $charged = sprintf('%.2f', $charged + $total_tax);
+ $cust_bill->set('charged', $charged);
+ $cust_bill->set('pending', '');
+
+ if (!$options{no_commit}) {
+ my $error = $cust_bill->replace;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ } # if !$tax_is_batch
+ # if it IS batch, then we'll do all this in process_tax_batch
+
push @{$options{return_bill}}, $cust_bill if $options{return_bill};
} #foreach my $pass ( keys %cust_bill_pkg )
}
-=item calculate_taxes LINEITEMREF TAXHASHREF INVOICE_TIME
-
-Generates tax line items (see L<FS::cust_bill_pkg>) for this customer.
-Usually used internally by bill method B<bill>.
-
-If there is an error, returns the error, otherwise returns reference to a
-list of line items suitable for insertion.
-
-=over 4
-
-=item LINEITEMREF
-
-An array ref of the line items being billed.
-
-=item TAXHASHREF
-
-A strange beast. The keys to this hash are internal identifiers consisting
-of the name of the tax object type, a space, and its unique identifier ( e.g.
- 'cust_main_county 23' ). The values of the hash are listrefs. The first
-item in the list is the tax object. The remaining items are either line
-items or floating point values (currency amounts).
-
-The taxes are calculated on this entity. Calculated exemption records are
-transferred to the LINEITEMREF items on the assumption that they are related.
-
-Read the source.
-
-=item INVOICE_TIME
-
-This specifies the date appearing on the associated invoice. Some
-jurisdictions (i.e. Texas) have tax exemptions which are date sensitive.
-
-=back
-
-=cut
-
-sub calculate_taxes {
- my ($self, $cust_bill_pkg, $taxlisthash, $invoice_time) = @_;
-
- # $taxlisthash is a hashref
- # keys are identifiers, values are arrayrefs
- # each arrayref starts with a tax object (cust_main_county or tax_rate)
- # then any cust_bill_pkg objects the tax applies to
-
- local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG;
-
- warn "$me calculate_taxes\n"
- #.Dumper($self, $cust_bill_pkg, $taxlisthash, $invoice_time). "\n"
- if $DEBUG > 2;
-
- my @tax_line_items = ();
-
- # keys are tax names (as printed on invoices / itemdesc )
- # values are arrayrefs of taxlisthash keys (internal identifiers)
- my %taxname = ();
-
- # keys are taxlisthash keys (internal identifiers)
- # values are (cumulative) amounts
- my %tax_amount = ();
-
- # keys are taxlisthash keys (internal identifiers)
- # values are arrayrefs of cust_bill_pkg_tax_location hashrefs
- my %tax_location = ();
-
- # keys are taxlisthash keys (internal identifiers)
- # values are arrayrefs of cust_bill_pkg_tax_rate_location hashrefs
- my %tax_rate_location = ();
-
- # keys are taxlisthash keys (internal identifiers!)
- # values are arrayrefs of cust_tax_exempt_pkg objects
- my %tax_exemption;
-
- foreach my $tax ( keys %$taxlisthash ) {
- # $tax is a tax identifier (intersection of a tax definition record
- # and a cust_bill_pkg record)
- my $tax_object = shift @{ $taxlisthash->{$tax} };
- # $tax_object is a cust_main_county or tax_rate
- # (with billpkgnum, pkgnum, locationnum set)
- # the rest of @{ $taxlisthash->{$tax} } is cust_bill_pkg component objects
- # (setup, recurring, usage classes)
- warn "found ". $tax_object->taxname. " as $tax\n" if $DEBUG > 2;
- warn " ". join('/', @{ $taxlisthash->{$tax} } ). "\n" if $DEBUG > 2;
- # taxline calculates the tax on all cust_bill_pkgs in the
- # first (arrayref) argument, and returns a hashref of 'name'
- # (the line item description) and 'amount'.
- # It also calculates exemptions and attaches them to the cust_bill_pkgs
- # in the argument.
- my $taxables = $taxlisthash->{$tax};
- my $exemptions = $tax_exemption{$tax} ||= [];
- my $taxline = $tax_object->taxline(
- $taxables,
- 'custnum' => $self->custnum,
- 'invoice_time' => $invoice_time,
- 'exemptions' => $exemptions,
- );
- return $taxline unless ref($taxline);
-
- unshift @{ $taxlisthash->{$tax} }, $tax_object;
-
- if ( $tax_object->isa('FS::cust_main_county') ) {
- # then $taxline is a real line item
- push @{ $taxname{ $taxline->itemdesc } }, $taxline;
-
- } else {
- # leave this as is for now
-
- my $name = $taxline->{'name'};
- my $amount = $taxline->{'amount'};
-
- #warn "adding $amount as $name\n";
- $taxname{ $name } ||= [];
- push @{ $taxname{ $name } }, $tax;
-
- $tax_amount{ $tax } += $amount;
-
- # link records between cust_main_county/tax_rate and cust_location
- $tax_rate_location{ $tax } ||= [];
- my $taxratelocationnum =
- $tax_object->tax_rate_location->taxratelocationnum;
- push @{ $tax_rate_location{ $tax } },
- {
- 'taxnum' => $tax_object->taxnum,
- 'taxtype' => ref($tax_object),
- 'amount' => sprintf('%.2f', $amount ),
- 'locationtaxid' => $tax_object->location,
- 'taxratelocationnum' => $taxratelocationnum,
- };
- } #if ref($tax_object)...
- } #foreach keys %$taxlisthash
-
- #consolidate and create tax line items
- warn "consolidating and generating...\n" if $DEBUG > 2;
- foreach my $taxname ( keys %taxname ) {
- my @cust_bill_pkg_tax_location;
- my @cust_bill_pkg_tax_rate_location;
- my $tax_cust_bill_pkg = FS::cust_bill_pkg->new({
- 'pkgnum' => 0,
- 'recur' => 0,
- 'sdate' => '',
- 'edate' => '',
- 'itemdesc' => $taxname,
- 'cust_bill_pkg_tax_location' => \@cust_bill_pkg_tax_location,
- 'cust_bill_pkg_tax_rate_location' => \@cust_bill_pkg_tax_rate_location,
- });
-
- my $tax_total = 0;
- my %seen = ();
- warn "adding $taxname\n" if $DEBUG > 1;
- foreach my $taxitem ( @{ $taxname{$taxname} } ) {
- if ( ref($taxitem) eq 'FS::cust_bill_pkg' ) {
- # then we need to transfer the amount and the links from the
- # line item to the new one we're creating.
- $tax_total += $taxitem->setup;
- foreach my $link ( @{ $taxitem->get('cust_bill_pkg_tax_location') } ) {
- $link->set('tax_cust_bill_pkg', $tax_cust_bill_pkg);
- push @cust_bill_pkg_tax_location, $link;
- }
- } else {
- # the tax_rate way
- next if $seen{$taxitem}++;
- warn "adding $tax_amount{$taxitem}\n" if $DEBUG > 1;
- $tax_total += $tax_amount{$taxitem};
- push @cust_bill_pkg_tax_rate_location,
- map { new FS::cust_bill_pkg_tax_rate_location $_ }
- @{ $tax_rate_location{ $taxitem } };
- }
- }
- next unless $tax_total;
-
- # we should really neverround this up...I guess it's okay if taxline
- # already returns amounts with 2 decimal places
- $tax_total = sprintf('%.2f', $tax_total );
- $tax_cust_bill_pkg->set('setup', $tax_total);
-
- my $pkg_category = qsearchs( 'pkg_category', { 'categoryname' => $taxname,
- 'disabled' => '',
- },
- );
-
- my @display = ();
- if ( $pkg_category and
- $conf->config('invoice_latexsummary') ||
- $conf->config('invoice_htmlsummary')
- )
- {
-
- my %hash = ( 'section' => $pkg_category->categoryname );
- push @display, new FS::cust_bill_pkg_display { type => 'S', %hash };
-
- }
- $tax_cust_bill_pkg->set('display', \@display);
-
- push @tax_line_items, $tax_cust_bill_pkg;
- }
-
- \@tax_line_items;
-}
-
sub _make_lines {
my ($self, %params) = @_;
my $cust_bill_pkgs = $params{line_items} or die "no line buffer specified";
my $total_setup = $params{setup} or die "no setup accumulator specified";
my $total_recur = $params{recur} or die "no recur accumulator specified";
- my $taxlisthash = $params{tax_matrix} or die "no tax accumulator specified";
my $time = $params{'time'} or die "no time specified";
my (%options) = %{$params{options}};
+ my $tax_engine = $params{tax_engine};
+
if ( $part_pkg->freq ne '1' and ($options{'freq_override'} || 0) > 0 ) {
# this should never happen
die 'freq_override billing attempted on non-monthly package '.
my $setup = 0;
my $unitsetup = 0;
my @setup_discounts = ();
- my %setup_param = ( 'discounts' => \@setup_discounts );
+ my %setup_param = ( 'discounts' => \@setup_discounts,
+ 'real_pkgpart' => $params{real_pkgpart}
+ );
my $setup_billed_currency = '';
my $setup_billed_amount = 0;
+ # Conditions for setting setup date and charging the setup fee:
+ # - this is not a recurring-only billing run
+ # - and the package is not currently being canceled
+ # - and, unless we're specifically told otherwise via 'resetup':
+ # - it doesn't already HAVE a setup date
+ # - or a start date in the future
+ # - and it's not suspended
+ #
+ # The last condition used to check the "disable_setup_suspended" option but
+ # that's obsolete. We now never set the setup date on a suspended package.
if ( ! $options{recurring_only}
and ! $options{cancel}
and ( $options{'resetup'}
&& ( ! $cust_pkg->start_date
|| $cust_pkg->start_date <= $cmp_time
)
- && ( ! $conf->exists('disable_setup_suspended_pkgs')
- || ( $conf->exists('disable_setup_suspended_pkgs') &&
- ! $cust_pkg->getfield('susp')
- )
- )
+ && ( ! $cust_pkg->getfield('susp') )
)
)
)
my $recur_billed_amount = 0;
my $sdate;
if ( ! $cust_pkg->start_date
- and ( ! $cust_pkg->susp || $cust_pkg->option('suspend_bill',1)
- || ( $part_pkg->option('suspend_bill', 1) )
- && ! $cust_pkg->option('no_suspend_bill',1)
- )
+ and
+ ( ! $cust_pkg->susp
+ || ( $cust_pkg->susp != $cust_pkg->order_date
+ && ( $cust_pkg->option('suspend_bill',1)
+ || ( $part_pkg->option('suspend_bill', 1)
+ && ! $cust_pkg->option('no_suspend_bill',1)
+ )
+ )
+ )
+ )
and
( $part_pkg->freq ne '0' && ( $cust_pkg->bill || 0 ) <= $cmp_time )
|| ( $part_pkg->plan eq 'voip_cdr'
###
# handle taxes
###
-
- my $error = $self->_handle_taxes( $taxlisthash, $cust_bill_pkg );
+
+ my $error = $tax_engine->add_sale($cust_bill_pkg);
return $error if $error;
$cust_bill_pkg->set_display(
return @transfers;
}
+#### vestigial code ####
+
=item handle_taxes TAXLISTHASH CUST_BILL_PKG [ OPTIONS ]
This is _handle_taxes. It's called once for each cust_bill_pkg generated
- part_item: a part_pkg or part_fee object to be used as the package/fee
definition.
- location: a cust_location to be used as the billing location.
+- cancel: true if this package is being billed on cancellation. This
+ allows tax to be calculated on usage charges only.
If not supplied, part_item will be inferred from the pkgnum or feepart of the
cust_bill_pkg, and location from the pkgnum (or, for fees, the invnum and
my %taxes = ();
my @classes;
- #push @classes, $cust_bill_pkg->usage_classes if $cust_bill_pkg->type eq 'U';
push @classes, $cust_bill_pkg->usage_classes if $cust_bill_pkg->usage;
- push @classes, 'setup' if $cust_bill_pkg->setup;
- push @classes, 'recur' if $cust_bill_pkg->recur;
+ push @classes, 'setup' if $cust_bill_pkg->setup and !$options{cancel};
+ push @classes, 'recur' if $cust_bill_pkg->recur and !$options{cancel};
my $exempt = $conf->exists('cust_class-tax_exempt')
? ( $self->cust_class ? $self->cust_class->tax : '' )
}
+#### end vestigial code ####
+
=item collect [ HASHREF | OPTION => VALUE ... ]
(Attempt to) collect money for this customer's outstanding invoices (see
#a little false laziness w/due_cust_event (not too bad, really)
- my $join = FS::part_event_condition->join_conditions_sql;
+ # I guess this is always as of now?
+ my $join = FS::part_event_condition->join_conditions_sql('', 'time' => time);
my $order = FS::part_event_condition->order_conditions_sql;
my $mine =
'( '
#some false laziness w/Cron::bill bill_where
- my $join = FS::part_event_condition->join_conditions_sql( $eventtable);
+ my $join = FS::part_event_condition->join_conditions_sql( $eventtable,
+ 'time' => $opt{'time'});
my $where = FS::part_event_condition->where_conditions_sql($eventtable,
'time'=>$opt{'time'},
);
my $pkey = $object->primary_key;
$cross_where = "$eventtable.$pkey = ". $object->$pkey();
- my $join = FS::part_event_condition->join_conditions_sql( $eventtable );
+ my $join = FS::part_event_condition->join_conditions_sql( $eventtable,
+ 'time' => $opt{'time'});
my $extra_sql =
FS::part_event_condition->where_conditions_sql( $eventtable,
'time'=>$opt{'time'}
#return 0 unless
- my @payments = sort { $b->_date <=> $a->_date }
- grep { $_->unapplied > 0 }
- $self->cust_pay;
+ my @payments = $self->unapplied_cust_pay;
- my @invoices = sort { $a->_date <=> $b->_date}
- grep { $_->owed > 0 }
- $self->cust_bill;
+ my @invoices = $self->open_cust_bill;
if ( $conf->exists('pkg-balances') ) {
# limit @payments to those w/ a pkgnum grepped from $self
bill
(do_cust_event pre-bill)
_make_lines
- _handle_taxes
- (vendor-only) _gather_taxes
_omit_zero_value_bundles
- _handle_taxes (for fees)
calculate_taxes
apply_payments_and_credits