[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