use FS::cust_svc;
use FS::cust_bill;
use FS::cust_bill_pkg;
+use FS::cust_bill_pkg_display;
use FS::cust_pay;
use FS::cust_pay_pending;
use FS::cust_pay_void;
=item spool_cdr - Enable individual CDR spooling, empty or `Y'
+=item dundate - a suggestion to events (see L<FS::part_bill_event">) to delay until this unix timestamp
+
=item squelch_cdr - Discourage individual CDR printing, empty or `Y'
=back
$self->signupdate(time) unless $self->signupdate;
+ $self->auto_agent_custid()
+ if $conf->config('cust_main-auto_agent_custid') && ! $self->agent_custid;
+
my $error = $self->SUPER::insert;
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
}
+use File::CounterFile;
+sub auto_agent_custid {
+ my $self = shift;
+
+ my $format = $conf->config('cust_main-auto_agent_custid');
+ my $agent_custid;
+ if ( $format eq '1YMMXXXXXXXX' ) {
+
+ my $counter = new File::CounterFile 'cust_main.agent_custid';
+ $counter->lock;
+
+ my $ym = 100000000000 + time2str('%y%m00000000', time);
+ if ( $ym > $counter->value ) {
+ $counter->{'value'} = $agent_custid = $ym;
+ $counter->{'updated'} = 1;
+ } else {
+ $agent_custid = $counter->inc;
+ }
+
+ $counter->unlock;
+
+ } else {
+ die "Unknown cust_main-auto_agent_custid format: $format";
+ }
+
+ $self->agent_custid($agent_custid);
+
+}
+
sub start_copy_skel {
my $self = shift;
|| $self->ut_textn('stateid_state')
|| $self->ut_textn('invoice_terms')
;
+
#barf. need message catalogs. i18n. etc.
$error .= "Please select an advertising source."
if $error =~ /^Illegal or empty \(numeric\) refnum: /;
$self->ncancelled_pkgs;
foreach my $cust_pkg ( @cancel_pkgs ) {
- my $error = $cust_pkg->cancel;
+ my $cpr = $cust_pkg->last_cust_pkg_reason('expire');
+ my $error = $cust_pkg->cancel($cpr ? ( 'reason' => $cpr->reasonnum,
+ 'reason_otaker' => $cpr->otaker
+ )
+ : ()
+ );
warn "Error cancelling expired pkg ". $cust_pkg->pkgnum.
" for custnum ". $self->custnum. ": $error"
if $error;
$self->ncancelled_pkgs;
foreach my $cust_pkg ( @susp_pkgs ) {
- my $error = $cust_pkg->suspend;
+ my $cpr = $cust_pkg->last_cust_pkg_reason('adjourn')
+ if ($cust_pkg->adjourn && $cust_pkg->adjourn < $^T);
+ my $error = $cust_pkg->suspend($cpr ? ( 'reason' => $cpr->reasonnum,
+ 'reason_otaker' => $cpr->otaker
+ )
+ : ()
+ );
+
warn "Error suspending package ". $cust_pkg->pkgnum.
" for custnum ". $self->custnum. ": $error"
if $error;
return "can't charge postal invoice fee for customer ".
$self->custnum. ": $postal_pkg";
}
- if ( $postal_pkg ) {
+ if ( $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,
'details' => \@details,
};
- if ( $part_pkg->option('recur_temporality') eq 'preceding' ) {
+ 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') eq 'upcoming' ) {
+ } else { #if ( $part_pkg->option('recur_temporality', 1) eq 'upcoming' ) {
$cust_bill_pkg->sdate( $sdate );
$cust_bill_pkg->edate( $cust_pkg->bill );
}
} #if $conf->exists('enable_taxproducts') ...
- my $section = $cust_pkg->part_pkg->option('usage_section', 'Hush!')
- if $cust_pkg->part_pkg->option('separate_usage', 'Hush!' );
- my $want_duplicate =
- $cust_pkg->part_pkg->option('summarize_usage', 'Hush!') &&
- $cust_pkg->part_pkg->option('usage_section', 'Hush!');
+ my @display = ();
+ if ( $conf->exists('separate_usage') ) {
+ my $section = $cust_pkg->part_pkg->option('usage_section', 'Hush!');
+ my $summary = $cust_pkg->part_pkg->option('summarize_usage', 'Hush!');
+ push @display, new FS::cust_bill_pkg_display { type => 'S' };
+ push @display, new FS::cust_bill_pkg_display { type => 'R' };
+ push @display, new FS::cust_bill_pkg_display { type => 'U',
+ section => $section
+ };
+ if ($section && $summary) {
+ $display[2]->post_total('Y');
+ push @display, new FS::cust_bill_pkg_display { type => 'U',
+ summary => 'Y',
+ }
+ }
+ }
+ $cust_bill_pkg->set('display', \@display);
-#BUNK. DO NOT CREATE DUPLICATE cust_bill_pkg!!!!!!!!!!!!
-#
-# # XXX this mostly goes away with cust_bill_pkg refactor
-#
-# $cust_bill_pkg{setup} = $cust_bill_pkg if $cust_bill_pkg->setup;
-# $cust_bill_pkg{recur} = $cust_bill_pkg if $cust_bill_pkg->recur;
-#
-#
-# #split setup and recur
-# if ($cust_bill_pkg->setup && $cust_bill_pkg->recur) {
-# my $cust_bill_pkg_recur = new FS::cust_bill_pkg { $cust_bill_pkg->hash };
-# $cust_bill_pkg_recur->details($cust_bill_pkg->
-# $cust_bill_pkg_recur->setup(0);
-# $cust_bill_pkg_recur->unitsetup(0);
-# $cust_bill_pkg{recur} = $cust_bill_pkg_recur;
-#
-# $cust_bill_pkg->set('details', []);
-# $cust_bill_pkg->recur(0);
-# $cust_bill_pkg->unitrecur(0);
-# $cust_bill_pkg->type('');
-# }
-#
-# #split usage from recur
-# my $usage = sprintf( "%.2f", $cust_bill_pkg{recur}->usage );
-# warn "usage is $usage\n" if $DEBUG;
-# if ($usage) {
-# my $cust_bill_pkg_usage =
-# new FS::cust_bill_pkg { $cust_bill_pkg{recur}->hash };
-# $cust_bill_pkg_usage->recur( $usage );
-# $cust_bill_pkg_usage->type( 'U' );
-# $cust_bill_pkg_usage->duplicate( $want_duplicate ? 'Y' : '' );
-# $cust_bill_pkg_usage->section( $section );
-# $cust_bill_pkg_usage->post_total( $want_duplicate ? 'Y' : '' );
-# my $recur = sprintf( "%.2f", $cust_bill_pkg{recur}->recur - $usage );
-# $cust_bill_pkg{recur}->recur( $recur );
-# $cust_bill_pkg{recur}->type( '' );
-# $cust_bill_pkg{recur}->set('details', []);
-# $cust_bill_pkg{''} = $cust_bill_pkg_usage;
-# }
-#
-# #subdivide usage by usage_class
-# if (exists($cust_bill_pkg{''})) {
-# foreach my $class (grep {$_ && $_ ne 'setup' && $_ ne 'recur' } @classes) {
-# my $usage = sprintf( "%.2f", $cust_bill_pkg{''}->usage($class) );
-# my $cust_bill_pkg_usage =
-# new FS::cust_bill_pkg { $cust_bill_pkg{''}->hash };
-# $cust_bill_pkg_usage->recur( $usage );
-# $cust_bill_pkg_usage->set('details', []);
-# my $classless = sprintf( "%.2f", $cust_bill_pkg{''}->recur - $usage );
-# $cust_bill_pkg{''}->recur( $classless );
-# $cust_bill_pkg{$class} = $cust_bill_pkg_usage;
-# }
-# delete $cust_bill_pkg{''} unless $cust_bill_pkg{''}->recur;
-# }
-#
-# foreach my $key (keys %cust_bill_pkg) {
-# my @taxes = @{ $taxes{$key} };
-# my $cust_bill_pkg = $cust_bill_pkg{$key};
-#
-# foreach my $tax ( @taxes ) {
-# my $taxname = ref( $tax ). ' '. $tax->taxnum;
-# if ( exists( $taxlisthash->{ $taxname } ) ) {
-# push @{ $taxlisthash->{ $taxname } }, $cust_bill_pkg;
-# }else{
-# $taxlisthash->{ $taxname } = [ $tax, $cust_bill_pkg ];
-# }
-# }
-# }
-#
-# # sort setup,recur,'', and the rest numeric && return
-# my @result = map { $cust_bill_pkg{$_} }
-# sort { my $ad = ($a=~/^\d+$/); my $bd = ($b=~/^\d+$/);
-# ( $ad cmp $bd ) || ( $ad ? $a<=>$b : $b cmp $a )
-# }
-# keys %cust_bill_pkg;
-#
-# \@result;
+ my %tax_cust_bill_pkg = $cust_bill_pkg->disintegrate;
+ foreach my $key (keys %tax_cust_bill_pkg) {
+ my @taxes = @{ $taxes{$key} };
+ my $tax_cust_bill_pkg = $tax_cust_bill_pkg{$key};
+
+ foreach my $tax ( @taxes ) {
+ my $taxname = ref( $tax ). ' '. $tax->taxnum;
+ if ( exists( $taxlisthash->{ $taxname } ) ) {
+ push @{ $taxlisthash->{ $taxname } }, $tax_cust_bill_pkg;
+ }else{
+ $taxlisthash->{ $taxname } = [ $tax, $tax_cust_bill_pkg ];
+ }
+ }
+ }
+
+ '';
}
sub _gather_taxes {
# 3: insert
##
- foreach my $cust_event ( @cust_event ) {
+ unless( $opt{testonly} ) {
+ foreach my $cust_event ( @cust_event ) {
- my $error = $cust_event->insert();
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
+ my $error = $cust_event->insert();
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
}
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
qsearch( 'cust_refund', { 'custnum' => $self->custnum } )
}
+=item display_custnum
+
+Returns the displayed customer number for this customer: agent_custid if
+cust_main-default_agent_custid is set and it has a value, custnum otherwise.
+
+=cut
+
+sub display_custnum {
+ my $self = shift;
+ if ( $conf->exists('cust_main-default_agent_custid') && $self->agent_custid ){
+ return $self->agent_custid;
+ } else {
+ return $self->custnum;
+ }
+}
+
=item name
Returns a name string for this customer, either "Company (Last, First)" or
my $extra_sql = "AND plus4lo <= '$plus4' AND plus4hi >= '$plus4'";
my $geocode = '';
- my $cust_tax_location =
- qsearchs( {
- 'table' => 'cust_tax_location',
- 'hashref' => { 'zip' => $zip, 'data_vendor' => $data_vendor },
- 'extra_sql' => $extra_sql,
- }
- );
- $geocode = $cust_tax_location->geocode
- if $cust_tax_location;
+ my @cust_tax_location =
+ qsearch( {
+ 'table' => 'cust_tax_location',
+ 'hashref' => { 'zip' => $zip, 'data_vendor' => $data_vendor },
+ 'extra_sql' => $extra_sql,
+ 'order_by' => 'ORDER BY plus4hi',#overlapping with distinct ends
+ }
+ );
+ $geocode = $cust_tax_location[0]->geocode
+ if scalar(@cust_tax_location);
$geocode;
}
# custnum search (also try agent_custid), with some tweaking options if your
# legacy cust "numbers" have letters
- } elsif ( $search =~ /^\s*(\d+)\s*$/
+ }
+
+ if ( $search =~ /^\s*(\d+)\s*$/
|| ( $conf->config('cust_main-agent_custid-format') eq 'ww?d+'
&& $search =~ /^\s*(\w\w?\d+)\s*$/
)
)
{
- push @cust_main, qsearch( {
- 'table' => 'cust_main',
- 'hashref' => { 'custnum' => $1, %options },
- 'extra_sql' => " AND $agentnums_sql", #agent virtualization
- } );
+ my $num = $1;
+
+ if ( $num <= 2147483647 ) { #need a bigint custnum? wow.
+ push @cust_main, qsearch( {
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $num, %options },
+ 'extra_sql' => " AND $agentnums_sql", #agent virtualization
+ } );
+ }
push @cust_main, qsearch( {
'table' => 'cust_main',
- 'hashref' => { 'agent_custid' => $1, %options },
+ 'hashref' => { 'agent_custid' => $num, %options },
'extra_sql' => " AND $agentnums_sql", #agent virtualization
} );
=cut
+use FS::svc_acct;
+use FS::svc_external;
+
#some false laziness w/cdr.pm now
sub batch_import {
my $param = shift;
svc_acct.username svc_acct._password
);
$payby = 'BILL';
+ } elsif ( $format eq 'svc_external' ) {
+ @fields = qw( agent_custid refnum
+ last first company address1 address2 city state zip country
+ daytime night
+ ship_last ship_first ship_company ship_address1 ship_address2
+ ship_city ship_state ship_zip ship_country
+ payinfo paycvv paydate
+ invoicing_list
+ cust_pkg.pkgpart cust_pkg.bill
+ svc_external.id svc_external.title
+ );
+ $payby = 'BILL';
} else {
die "unknown format $format";
}
);
my $billtime = time;
my %cust_pkg = ( pkgpart => $pkgpart );
- my %svc_acct = ();
+ my %svc_x = ();
foreach my $field ( @fields ) {
if ( $field =~ /^cust_pkg\.(pkgpart|setup|bill|susp|adjourn|expire|cancel)$/ ) {
} elsif ( $field =~ /^svc_acct\.(username|_password)$/ ) {
- $svc_acct{$1} = shift @columns;
+ $svc_x{$1} = shift @columns;
+
+ } elsif ( $field =~ /^svc_external\.(id|title)$/ ) {
+
+ $svc_x{$1} = shift @columns;
} else {
if ( $cust_pkg{'pkgpart'} ) {
my $cust_pkg = new FS::cust_pkg ( \%cust_pkg );
- my @svc_acct = ();
- if ( $svc_acct{'username'} ) {
+ my @svc_x = ();
+ my $svcdb = '';
+ if ( $svc_x{'username'} ) {
+ $svcdb = 'svc_acct';
+ } elsif ( $svc_x{'id'} || $svc_x{'title'} ) {
+ $svcdb = 'svc_external';
+ }
+ if ( $svcdb ) {
my $part_pkg = $cust_pkg->part_pkg;
unless ( $part_pkg ) {
$dbh->rollback if $oldAutoCommit;
return "unknown pkgpart: ". $cust_pkg{'pkgpart'};
}
- $svc_acct{svcpart} = $part_pkg->svcpart( 'svc_acct' );
- push @svc_acct, new FS::svc_acct ( \%svc_acct )
+ $svc_x{svcpart} = $part_pkg->svcpart( $svcdb );
+ my $class = "FS::$svcdb";
+ push @svc_x, $class->new( \%svc_x );
}
- $hash{$cust_pkg} = \@svc_acct;
+ $hash{$cust_pkg} = \@svc_x;
}
my $error = $cust_main->insert( \%hash, $invoicing_list );