[freeside-commits] freeside/bin cust_main_special.pm, NONE, 1.1 rebill, NONE, 1.1
Jeff Finucane,420,,
jeff at wavetail.420.am
Mon May 4 11:33:50 PDT 2009
Update of /home/cvs/cvsroot/freeside/bin
In directory wavetail.420.am:/tmp/cvs-serv9006
Added Files:
cust_main_special.pm rebill
Log Message:
this is a quick hack to rebill customers when a cdr didn't happen
--- NEW FILE: cust_main_special.pm ---
package cust_main_special;
require 5.006;
use strict;
use vars qw( @ISA $DEBUG $me $conf );
use Safe;
use Carp;
use Data::Dumper;
use Date::Format;
use FS::UID qw( dbh );
use FS::Record qw( qsearchs qsearch );
use FS::payby;
use FS::cust_pkg;
use FS::cust_bill;
use FS::cust_bill_pkg;
use FS::cust_bill_pkg_display;
use FS::cust_bill_pkg_tax_location;
use FS::cust_main_county;
use FS::cust_location;
use FS::tax_rate;
use FS::cust_tax_location;
use FS::part_pkg_taxrate;
use FS::queue;
use FS::part_pkg;
@ISA = qw ( FS::cust_main );
$DEBUG = 0;
$me = '[emergency billing program]';
$conf = new FS::Conf;
=head1 METHODS
=over 4
=item bill_and_collect
Cancels and suspends any packages due, generates bills, applies payments and
cred
Warns on errors (Does not currently: If there is an error, returns the error, otherwise returns false.)
Options are passed as name-value pairs. Currently available options are:
=over 4
=item time
Bills the customer as if it were that time. Specified as a UNIX timestamp; see L<perlfunc/"time">). Also see L<Time::Local> and L<Date::Parse> for conversion functions. For example:
use Date::Parse;
...
$cust_main->bill( 'time' => str2time('April 20th, 2001') );
=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.
=item check_freq
"1d" for the traditional, daily events (the default), or "1m" for the new monthly events (part_event.check_freq)
=item resetup
If set true, re-charges setup fees.
=item debug
Debugging level. Default is 0 (no debugging), or can be set to 1 (passed-in options), 2 (traces progress), 3 (more information), or 4 (include full search queries)
=back
=cut
sub bill_and_collect {
my( $self, %options ) = @_;
#$options{actual_time} not $options{time} because freeside-daily -d is for
#pre-printing invoices
$self->cancel_expired_pkgs( $options{actual_time} );
$self->suspend_adjourned_pkgs( $options{actual_time} );
my $error = $self->bill( %options );
warn "Error billing, custnum ". $self->custnum. ": $error" if $error;
$self->apply_payments_and_credits;
unless ( $conf->exists('cancelled_cust-noevents')
&& ! $self->num_ncancelled_pkgs
) {
$error = $self->collect( %options );
warn "Error collecting, custnum". $self->custnum. ": $error" if $error;
}
}
=item bill OPTIONS
Generates invoices (see L<FS::cust_bill>) for this customer. Usually used in
conjunction with the collect method by calling B<bill_and_collect>.
If there is an error, returns the error, otherwise returns false.
Options are passed as name-value pairs. Currently available options are:
=over 4
=item resetup
If set true, re-charges setup fees.
=item time
Bills the customer as if it were that time. Specified as a UNIX timestamp; see L<perlfunc/"time">). Also see L<Time::Local> and L<Date::Parse> for conversion functions. For example:
use Date::Parse;
...
$cust_main->bill( 'time' => str2time('April 20th, 2001') );
=item pkg_list
An array ref of specific packages (objects) to attempt billing, instead trying all of them.
$cust_main->bill( pkg_list => [$pkg1, $pkg2] );
=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.
=item backbill
Used to specify the period starting date and preventing normal billing. Instead all outstanding cdrs/usage are processed as if from the unix timestamp in backbill and without changing the dates in the customer packages. Useful in those situations when cdrs were not imported before a billing run
=back
=cut
sub bill {
my( $self, %options ) = @_;
bless $self, 'cust_main_special';
return '' if $self->payby eq 'COMP';
warn "$me backbill usage for customer ". $self->custnum. "\n"
if $DEBUG;
my $time = $options{'time'} || time;
my $invoice_time = $options{'invoice_time'} || $time;
#put below somehow?
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
local $SIG{QUIT} = 'IGNORE';
local $SIG{TERM} = 'IGNORE';
local $SIG{TSTP} = 'IGNORE';
local $SIG{PIPE} = 'IGNORE';
my $oldAutoCommit = $FS::UID::AutoCommit;
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
$self->select_for_update; #mutex
my @cust_bill_pkg = ();
###
# find the packages which are due for billing, find out how much they are
# & generate invoice database.
###
my( $total_setup, $total_recur, $postal_charge ) = ( 0, 0, 0 );
my %taxlisthash;
my @precommit_hooks = ();
my @cust_pkgs = qsearch('cust_pkg', { 'custnum' => $self->custnum } );
foreach my $cust_pkg (@cust_pkgs) {
#NO!! next if $cust_pkg->cancel;
next if $cust_pkg->getfield('cancel');
warn " bill package ". $cust_pkg->pkgnum. "\n" if $DEBUG > 1;
#? 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;
foreach my $part_pkg ( $cust_pkg->part_pkg->self_and_bill_linked ) {
$cust_pkg->set($_, $hash{$_}) foreach qw ( setup last_bill bill );
my $error =
$self->_make_lines( 'part_pkg' => $part_pkg,
'cust_pkg' => $cust_pkg,
'precommit_hooks' => \@precommit_hooks,
'line_items' => \@cust_bill_pkg,
'setup' => \$total_setup,
'recur' => \$total_recur,
'tax_matrix' => \%taxlisthash,
'time' => $time,
'options' => \%options,
);
if ($error) {
$dbh->rollback if $oldAutoCommit;
return $error;
}
} #foreach my $part_pkg
} #foreach my $cust_pkg
unless ( @cust_bill_pkg ) { #don't create an invoice w/o line items
unless ( $options{backbill} ) {
#but do commit any package date cycling that happened
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
} else {
$dbh->rollback or die $dbh->errstr if $oldAutoCommit;
}
return '';
}
my $postal_pkg = $self->charge_postal_fee();
if ( $postal_pkg && !ref( $postal_pkg ) ) {
$dbh->rollback if $oldAutoCommit;
return "can't charge postal invoice fee for customer ".
$self->custnum. ": $postal_pkg";
}
if ( !$options{backbill} && $postal_pkg &&
( scalar( grep { $_->recur && $_->recur > 0 } @cust_bill_pkg) ||
!$conf->exists('postal_invoice-recurring_only')
)
)
{
foreach my $part_pkg ( $postal_pkg->part_pkg->self_and_bill_linked ) {
my $error =
$self->_make_lines( 'part_pkg' => $part_pkg,
'cust_pkg' => $postal_pkg,
'precommit_hooks' => \@precommit_hooks,
'line_items' => \@cust_bill_pkg,
'setup' => \$total_setup,
'recur' => \$total_recur,
'tax_matrix' => \%taxlisthash,
'time' => $time,
'options' => \%options,
);
if ($error) {
$dbh->rollback if $oldAutoCommit;
return $error;
}
}
}
warn "having a look at the taxes we found...\n" if $DEBUG > 2;
# keys are tax names (as printed on invoices / itemdesc )
# values are listrefs of taxlisthash keys (internal identifiers)
my %taxname = ();
# keys are taxlisthash keys (internal identifiers)
# values are (cumulative) amounts
my %tax = ();
# keys are taxlisthash keys (internal identifiers)
# values are listrefs of cust_bill_pkg_tax_location hashrefs
my %tax_location = ();
foreach my $tax ( keys %taxlisthash ) {
my $tax_object = shift @{ $taxlisthash{$tax} };
warn "found ". $tax_object->taxname. " as $tax\n" if $DEBUG > 2;
warn " ". join('/', @{ $taxlisthash{$tax} } ). "\n" if $DEBUG > 2;
my $hashref_or_error =
$tax_object->taxline( $taxlisthash{$tax},
'custnum' => $self->custnum,
'invoice_time' => $invoice_time
);
unless ( ref($hashref_or_error) ) {
$dbh->rollback if $oldAutoCommit;
return $hashref_or_error;
}
unshift @{ $taxlisthash{$tax} }, $tax_object;
my $name = $hashref_or_error->{'name'};
my $amount = $hashref_or_error->{'amount'};
#warn "adding $amount as $name\n";
$taxname{ $name } ||= [];
push @{ $taxname{ $name } }, $tax;
$tax{ $tax } += $amount;
$tax_location{ $tax } ||= [];
if ( $tax_object->get('pkgnum') || $tax_object->get('locationnum') ) {
push @{ $tax_location{ $tax } },
{
'taxnum' => $tax_object->taxnum,
'taxtype' => ref($tax_object),
'pkgnum' => $tax_object->get('pkgnum'),
'locationnum' => $tax_object->get('locationnum'),
'amount' => sprintf('%.2f', $amount ),
};
}
}
#move the cust_tax_exempt_pkg records to the cust_bill_pkgs we will commit
my %packagemap = map { $_->pkgnum => $_ } @cust_bill_pkg;
foreach my $tax ( keys %taxlisthash ) {
foreach ( @{ $taxlisthash{$tax} }[1 ... scalar(@{ $taxlisthash{$tax} })] ) {
next unless ref($_) eq 'FS::cust_bill_pkg';
push @{ $packagemap{$_->pkgnum}->_cust_tax_exempt_pkg },
splice( @{ $_->_cust_tax_exempt_pkg } );
}
}
#consolidate and create tax line items
warn "consolidating and generating...\n" if $DEBUG > 2;
foreach my $taxname ( keys %taxname ) {
my $tax = 0;
my %seen = ();
my @cust_bill_pkg_tax_location = ();
warn "adding $taxname\n" if $DEBUG > 1;
foreach my $taxitem ( @{ $taxname{$taxname} } ) {
next if $seen{$taxitem}++;
warn "adding $tax{$taxitem}\n" if $DEBUG > 1;
$tax += $tax{$taxitem};
push @cust_bill_pkg_tax_location,
map { new FS::cust_bill_pkg_tax_location $_ }
@{ $tax_location{ $taxitem } };
}
next unless $tax;
$tax = sprintf('%.2f', $tax );
$total_setup = sprintf('%.2f', $total_setup+$tax );
push @cust_bill_pkg, new FS::cust_bill_pkg {
'pkgnum' => 0,
'setup' => $tax,
'recur' => 0,
'sdate' => '',
'edate' => '',
'itemdesc' => $taxname,
'cust_bill_pkg_tax_location' => \@cust_bill_pkg_tax_location,
};
}
my $charged = sprintf('%.2f', $total_setup + $total_recur );
#create the new invoice
my $cust_bill = new FS::cust_bill ( {
'custnum' => $self->custnum,
'_date' => ( $invoice_time ),
'charged' => $charged,
} );
my $error = $cust_bill->insert;
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return "can't create invoice for customer #". $self->custnum. ": $error";
}
foreach my $cust_bill_pkg ( @cust_bill_pkg ) {
$cust_bill_pkg->invnum($cust_bill->invnum);
my $error = $cust_bill_pkg->insert;
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return "can't create invoice line item: $error";
}
}
#foreach my $hook ( @precommit_hooks ) {
# eval {
# &{$hook}; #($self) ?
# };
# if ( $@ ) {
# $dbh->rollback if $oldAutoCommit;
# return "$@ running precommit hook $hook\n";
# }
#}
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
''; #no error
}
sub _make_lines {
my ($self, %params) = @_;
warn " making lines\n" if $DEBUG > 1;
my $part_pkg = $params{part_pkg} or die "no part_pkg specified";
my $cust_pkg = $params{cust_pkg} or die "no cust_pkg specified";
my $precommit_hooks = $params{precommit_hooks} or die "no package specified";
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 $dbh = dbh;
my $real_pkgpart = $cust_pkg->pkgpart;
my %hash = $cust_pkg->hash;
my $old_cust_pkg = new FS::cust_pkg \%hash;
my $backbill = $options{backbill} || 0;
my @details = ();
my $lineitems = 0;
$cust_pkg->pkgpart($part_pkg->pkgpart);
###
# bill setup
###
my $setup = 0;
my $unitsetup = 0;
if ( ! $cust_pkg->setup &&
(
( $conf->exists('disable_setup_suspended_pkgs') &&
! $cust_pkg->getfield('susp')
) || ! $conf->exists('disable_setup_suspended_pkgs')
)
|| $options{'resetup'}
) {
warn " bill setup\n" if $DEBUG > 1;
$lineitems++;
$setup = eval { $cust_pkg->calc_setup( $time, \@details ) };
return "$@ running calc_setup for $cust_pkg\n"
if $@;
$unitsetup = $cust_pkg->part_pkg->unit_setup || $setup; #XXX uuh
$cust_pkg->setfield('setup', $time)
unless $cust_pkg->setup;
#do need it, but it won't get written to the db
#|| $cust_pkg->pkgpart != $real_pkgpart;
}
###
# bill recurring fee
###
#XXX unit stuff here too
my $recur = 0;
my $unitrecur = 0;
my $sdate;
if ( ! $cust_pkg->getfield('susp') and
( $part_pkg->getfield('freq') ne '0' &&
( $cust_pkg->getfield('bill') || 0 ) <= $time
)
|| ( $part_pkg->plan eq 'voip_cdr'
&& $part_pkg->option('bill_every_call')
)
|| $backbill
) {
# XXX should this be a package event? probably. events are called
# at collection time at the moment, though...
$part_pkg->reset_usage($cust_pkg, 'debug'=>$DEBUG)
if $part_pkg->can('reset_usage');
#don't want to reset usage just cause we want a line item??
#&& $part_pkg->pkgpart == $real_pkgpart;
warn " bill recur\n" if $DEBUG > 1;
$lineitems++;
# XXX shared with $recur_prog
$sdate = $cust_pkg->bill || $cust_pkg->setup || $time;
$sdate = $cust_pkg->lastbill || $backbill if $backbill;
#over two params! lets at least switch to a hashref for the rest...
my $increment_next_bill = ( $part_pkg->freq ne '0'
&& ( $cust_pkg->getfield('bill') || 0 ) <= $time
);
my %param = ( 'precommit_hooks' => $precommit_hooks,
'increment_next_bill' => $increment_next_bill,
);
$recur = eval { $cust_pkg->calc_recur( \$sdate, \@details, \%param ) };
return "$@ running calc_recur for $cust_pkg\n"
if ( $@ );
warn "details is now: \n" if $DEBUG > 2;
warn Dumper(\@details) if $DEBUG > 2;
if ( $increment_next_bill ) {
my $next_bill = $part_pkg->add_freq($sdate);
return "unparsable frequency: ". $part_pkg->freq
if $next_bill == -1;
#pro-rating magic - if $recur_prog fiddled $sdate, want to use that
# only for figuring next bill date, nothing else, so, reset $sdate again
# here
$sdate = $cust_pkg->bill || $cust_pkg->setup || $time;
$sdate = $cust_pkg->lastbill || $backbill if $backbill;
#no need, its in $hash{last_bill}# my $last_bill = $cust_pkg->last_bill;
$cust_pkg->last_bill($sdate);
$cust_pkg->setfield('bill', $next_bill );
}
}
warn "\$setup is undefined" unless defined($setup);
warn "\$recur is undefined" unless defined($recur);
warn "\$cust_pkg->bill is undefined" unless defined($cust_pkg->bill);
###
# If there's line items, create em cust_bill_pkg records
# If $cust_pkg has been modified, update it (if we're a real pkgpart)
###
if ( $lineitems ) {
if ( !$backbill && $cust_pkg->modified && $cust_pkg->pkgpart == $real_pkgpart ) {
# hmm.. and if just the options are modified in some weird price plan?
warn " package ". $cust_pkg->pkgnum. " modified; updating\n"
if $DEBUG >1;
my $error = $cust_pkg->replace( $old_cust_pkg,
'options' => { $cust_pkg->options },
);
return "Error modifying pkgnum ". $cust_pkg->pkgnum. ": $error"
if $error; #just in case
}
my @cust_pkg_detail = map { $_->detail } $cust_pkg->cust_pkg_detail('I');
if ( $DEBUG > 1 ) {
warn " tentatively adding customer package invoice detail: $_\n"
foreach @cust_pkg_detail;
}
push @details, @cust_pkg_detail;
$setup = sprintf( "%.2f", $setup );
$recur = sprintf( "%.2f", $recur );
my $cust_bill_pkg = new FS::cust_bill_pkg {
'pkgnum' => $cust_pkg->pkgnum,
'setup' => $setup,
'unitsetup' => $unitsetup,
'recur' => $recur,
'unitrecur' => $unitrecur,
'quantity' => $cust_pkg->quantity,
'details' => \@details,
};
warn "created cust_bill_pkg which looks like:\n" if $DEBUG > 2;
warn Dumper($cust_bill_pkg) if $DEBUG > 2;
if ($backbill) {
my %usage_cust_bill_pkg = $cust_bill_pkg->disintegrate;
$recur = 0;
foreach my $key (keys %usage_cust_bill_pkg) {
next if ($key eq 'setup' || $key eq 'recur');
$recur += $usage_cust_bill_pkg{$key}->recur;
}
$setup = 0;
}
$setup = sprintf( "%.2f", $setup );
$recur = sprintf( "%.2f", $recur );
if ( $setup < 0 && ! $conf->exists('allow_negative_charges') ) {
return "negative setup $setup for pkgnum ". $cust_pkg->pkgnum;
}
if ( $recur < 0 && ! $conf->exists('allow_negative_charges') ) {
return "negative recur $recur for pkgnum ". $cust_pkg->pkgnum;
}
if ( $setup != 0 || $recur != 0 ) {
warn " charges (setup=$setup, recur=$recur); adding line items\n"
if $DEBUG > 1;
$cust_bill_pkg->setup($setup);
$cust_bill_pkg->recur($recur);
warn "cust_bill_pkg now looks like:\n" if $DEBUG > 2;
warn Dumper($cust_bill_pkg) if $DEBUG > 2;
if ( $part_pkg->option('recur_temporality', 1) eq 'preceding' ) {
$cust_bill_pkg->sdate( $hash{last_bill} );
$cust_bill_pkg->edate( $sdate - 86399 ); #60s*60m*24h-1
} else { #if ( $part_pkg->option('recur_temporality', 1) eq 'upcoming' ) {
$cust_bill_pkg->sdate( $sdate );
$cust_bill_pkg->edate( $cust_pkg->bill );
}
$cust_bill_pkg->pkgpart_override($part_pkg->pkgpart)
unless $part_pkg->pkgpart == $real_pkgpart;
$$total_setup += $setup;
$$total_recur += $recur;
###
# handle taxes
###
my $error =
$self->_handle_taxes($part_pkg, $taxlisthash, $cust_bill_pkg, $cust_pkg, $options{invoice_time});
return $error if $error;
push @$cust_bill_pkgs, $cust_bill_pkg;
} #if $setup != 0 || $recur != 0
} #if $line_items
'';
}
=item collect OPTIONS
(Attempt to) collect money for this customer's outstanding invoices (see
L<FS::cust_bill>). Usually used after the bill method.
Actions are now triggered by billing events; see L<FS::part_event> and the
billing events web interface. Old-style invoice events (see
L<FS::part_bill_event>) have been deprecated.
If there is an error, returns the error, otherwise returns false.
Options are passed as name-value pairs.
Currently available options are:
=over 4
=item invoice_time
Use this time when deciding when to print invoices and late notices on those invoices. The default is now. It is specified as a UNIX timestamp; see L<perlfunc/"time">). Also see L<Time::Local> and L<Date::Parse> for conversion functions.
=item retry
Retry card/echeck/LEC transactions even when not scheduled by invoice events.
=item quiet
set true to surpress email card/ACH decline notices.
=item check_freq
"1d" for the traditional, daily events (the default), or "1m" for the new monthly events (part_event.check_freq)
=item payby
allows for one time override of normal customer billing method
=item debug
Debugging level. Default is 0 (no debugging), or can be set to 1 (passed-in options), 2 (traces progress), 3 (more information), or 4 (include full search queries)
=back
=cut
sub collect {
my( $self, %options ) = @_;
my $invoice_time = $options{'invoice_time'} || time;
#put below somehow?
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
local $SIG{QUIT} = 'IGNORE';
local $SIG{TERM} = 'IGNORE';
local $SIG{TSTP} = 'IGNORE';
local $SIG{PIPE} = 'IGNORE';
my $oldAutoCommit = $FS::UID::AutoCommit;
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
$self->select_for_update; #mutex
if ( $DEBUG ) {
my $balance = $self->balance;
warn "$me collect customer ". $self->custnum. ": balance $balance\n"
}
if ( exists($options{'retry_card'}) ) {
carp 'retry_card option passed to collect is deprecated; use retry';
$options{'retry'} ||= $options{'retry_card'};
}
if ( exists($options{'retry'}) && $options{'retry'} ) {
my $error = $self->retry_realtime;
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
}
}
# false laziness w/pay_batch::import_results
my $due_cust_event = $self->due_cust_event(
'debug' => ( $options{'debug'} || 0 ),
'time' => $invoice_time,
'check_freq' => $options{'check_freq'},
);
unless( ref($due_cust_event) ) {
$dbh->rollback if $oldAutoCommit;
return $due_cust_event;
}
foreach my $cust_event ( @$due_cust_event ) {
#XXX lock event
#re-eval event conditions (a previous event could have changed things)
unless ( $cust_event->test_conditions( 'time' => $invoice_time ) ) {
#don't leave stray "new/locked" records around
my $error = $cust_event->delete;
if ( $error ) {
#gah, even with transactions
$dbh->commit if $oldAutoCommit; #well.
return $error;
}
next;
}
{
local $FS::cust_main::realtime_bop_decline_quiet = 1 if $options{'quiet'};
warn " running cust_event ". $cust_event->eventnum. "\n"
if $DEBUG > 1;
#if ( my $error = $cust_event->do_event(%options) ) { #XXX %options?
if ( my $error = $cust_event->do_event() ) {
#XXX wtf is this? figure out a proper dealio with return value
#from do_event
# gah, even with transactions.
$dbh->commit if $oldAutoCommit; #well.
return $error;
}
}
}
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
'';
}
sub queued_bill {
## actual sub, not a method, designed to be called from the queue.
## sets up the customer, and calls the bill_and_collect
my (%args) = @_; #, ($time, $invoice_time, $check_freq, $resetup) = @_;
my $cust_main = qsearchs( 'cust_main', { custnum => $args{'custnum'} } );
$cust_main->bill_and_collect(
%args,
);
}
=back
=cut
1;
--- NEW FILE: rebill ---
#!/usr/bin/perl -w
use strict;
use Getopt::Std;
use Date::Parse;
use FS::UID qw(adminsuidsetup);
use FS::Record qw( qsearch );
use cust_main_special;
&untaint_argv; #what it sounds like (eww)
use vars qw(%opt);
getopts("p:a:d:sy:n", \%opt);
my $user = shift or die &usage;
adminsuidsetup $user;
my (@custnums) = @ARGV;
my $time = $opt{d} ? str2time($opt{d}) : $^T;
$time += $opt{y} * 86400 if $opt{y};
my $invoice_time = $opt{n} ? $^T : $time;
my %args = (
'time' => $time,
'invoice_time' => $invoice_time,
'actual_time' => $^T, #when freeside-bill was started
#(not, when using -m, freeside-queued)
'resetup' => ( $opt{'s'} ? $opt{'s'} : 0 ),
'backbill' => $time,
);
my $extra_sql = ( $opt{a} || $opt{p} ) ? ' AND ' : ' WHERE ';
$extra_sql .= "( ". join( ' OR ', map{ "custnum = $_" } @custnums ). " )";
$extra_sql = '' unless scalar @custnums;
my @cust = qsearch( { table => 'cust_main',
hashref => { $opt{a} ? ( 'agentnum' => $opt{a} ) : (),
$opt{p} ? ( 'payby' => $opt{p} ) : (),
},
extra_sql => $extra_sql,
}
);
foreach my $cust ( @cust ) {
my $balance = $cust->balance;
cust_main_special::bill($cust, %args);
if ($balance != $cust->balance){
$cust->apply_payments_and_credits;
my $error = $cust->collect(%args);
warn "Error collecting, custnum ". $cust->custnum. ": $error" if $error;
}
}
###
# subroutines
###
sub untaint_argv {
foreach $_ ( $[ .. $#ARGV ) { #untaint @ARGV
#$ARGV[$_] =~ /^([\w\-\/]*)$/ || die "Illegal arguement \"$ARGV[$_]\"";
# Date::Parse
$ARGV[$_] =~ /^(.*)$/ || die "Illegal arguement \"$ARGV[$_]\"";
$ARGV[$_]=$1;
}
}
sub usage {
die "Usage:\n\n freeside-daily [ -d 'date' ] [ -y days ] [ -p 'payby' ] [ -a agentnum ] [ -s ] user [ custnum custnum ... ]\n";
}
###
# documentation
###
=head1 NAME
freeside-daily - Run daily billing and invoice collection events.
=head1 SYNOPSIS
freeside-daily [ -d 'date' ] [ -y days ] [ -p 'payby' ] [ -a agentnum ] [ -s ] user [ custnum custnum ... ]
=head1 DESCRIPTION
Bills customers and runs invoice collection events. Should be run from
crontab daily.
Bills customers. Searches for customers who are due for billing and calls
the bill and collect methods of a cust_main object. See L<FS::cust_main>.
-d: Pretend it's 'date'. Date is in any format Date::Parse is happy with,
but be careful.
-y: In addition to -d, which specifies an absolute date, the -y switch
specifies an offset, in days. For example, "-y 15" would increment the
"pretend date" 15 days from whatever was specified by the -d switch
(or now, if no -d switch was given).
-n: When used with "-d" and/or "-y", specifies that invoices should be dated
with today's date, irregardless of the pretend date used to pre-generate
the invoices.
-p: Only process customers with the specified payby (I<CARD>, I<DCRD>, I<CHEK>, I<DCHK>, I<BILL>, I<COMP>, I<LECB>)
-a: Only process customers with the specified agentnum
-s: re-charge setup fees
-v: enable debugging
-l: debugging level
-m: Experimental multi-process mode uses the job queue for multi-process and/or multi-machine billing.
-r: Multi-process mode dry run option
-k: skip notify_flat_delay and vacuum
user: From the mapsecrets file - see config.html from the base documentation
custnum: if one or more customer numbers are specified, only bills those
customers. Otherwise, bills all customers.
=head1 BUGS
=head1 SEE ALSO
L<FS::cust_main>, config.html from the base documentation
=cut
More information about the freeside-commits
mailing list