X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_main.pm;h=3e767d9f0277f9cb844d8294c2d79215127799f5;hb=3bd1b2b68adbb67f90addd668132d3d3e9adb698;hp=e7b34459c6b8fdcd4299a27fab0a428b20d3759f;hpb=5da73ac30a52234cc126ead03cddaf5a4e131019;p=freeside.git diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index e7b34459c..3e767d9f0 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -29,6 +29,7 @@ use FS::cust_pkg; 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; @@ -37,6 +38,7 @@ use FS::cust_credit; use FS::cust_refund; use FS::part_referral; use FS::cust_main_county; +use FS::cust_tax_location; use FS::agent; use FS::cust_main_invoice; use FS::cust_credit_bill; @@ -225,6 +227,8 @@ Card Verification Value, "CVV2" (also known as CVC2 or CID), the 3 or 4 digit nu =item spool_cdr - Enable individual CDR spooling, empty or `Y' +=item dundate - a suggestion to events (see L) to delay until this unix timestamp + =item squelch_cdr - Discourage individual CDR printing, empty or `Y' =back @@ -339,6 +343,9 @@ sub insert { $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; @@ -415,6 +422,35 @@ sub insert { } +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; @@ -1229,6 +1265,7 @@ sub check { || $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: /; @@ -1960,7 +1997,12 @@ sub bill_and_collect { $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; @@ -1986,7 +2028,14 @@ sub bill_and_collect { $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; @@ -2066,7 +2115,6 @@ sub bill { $self->select_for_update; #mutex my @cust_bill_pkg = (); - my @appended_cust_bill_pkg = (); ### # find the packages which are due for billing, find out how much they are @@ -2105,7 +2153,6 @@ sub bill { 'cust_pkg' => $cust_pkg, 'precommit_hooks' => \@precommit_hooks, 'line_items' => \@cust_bill_pkg, - 'appended_line_items' => \@appended_cust_bill_pkg, 'setup' => \$total_setup, 'recur' => \$total_recur, 'tax_matrix' => \%taxlisthash, @@ -2121,8 +2168,6 @@ sub bill { } #foreach my $cust_pkg - push @cust_bill_pkg, @appended_cust_bill_pkg; - unless ( @cust_bill_pkg ) { #don't create an invoice w/o line items #but do commit any package date cycling that happened $dbh->commit or die $dbh->errstr if $oldAutoCommit; @@ -2135,14 +2180,18 @@ sub bill { 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, 'cust_pkg' => $postal_pkg, 'precommit_hooks' => \@precommit_hooks, 'line_items' => \@cust_bill_pkg, - 'appended_line_items' => \@appended_cust_bill_pkg, 'setup' => \$total_setup, 'recur' => \$total_recur, 'tax_matrix' => \%taxlisthash, @@ -2294,8 +2343,6 @@ sub _make_lines { 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 $appended_cust_bill_pkg = $params{appended_line_items} - or die "no appended 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"; @@ -2352,9 +2399,13 @@ sub _make_lines { my $recur = 0; my $unitrecur = 0; my $sdate; - if ( $part_pkg->getfield('freq') ne '0' && - ! $cust_pkg->getfield('susp') && - ( $cust_pkg->getfield('bill') || 0 ) <= $time + 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') + ) ) { # XXX should this be a package event? probably. events are called @@ -2371,41 +2422,50 @@ sub _make_lines { $sdate = $cust_pkg->bill || $cust_pkg->setup || $time; #over two params! lets at least switch to a hashref for the rest... - my %param = ( 'precommit_hooks' => $precommit_hooks, ); + 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 ( $@ ); + if ( $increment_next_bill ) { - #change this bit to use Date::Manip? CAREFUL with timezones (see - # mailing list archive) - my ($sec,$min,$hour,$mday,$mon,$year) = - (localtime($sdate) )[0,1,2,3,4,5]; - - #pro-rating magic - if $recur_prog fiddles $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; - $cust_pkg->last_bill($sdate); - - if ( $part_pkg->freq =~ /^\d+$/ ) { - $mon += $part_pkg->freq; - until ( $mon < 12 ) { $mon -= 12; $year++; } - } elsif ( $part_pkg->freq =~ /^(\d+)w$/ ) { - my $weeks = $1; - $mday += $weeks * 7; - } elsif ( $part_pkg->freq =~ /^(\d+)d$/ ) { - my $days = $1; - $mday += $days; - } elsif ( $part_pkg->freq =~ /^(\d+)h$/ ) { - my $hours = $1; - $hour += $hours; - } else { - return "unparsable frequency: ". $part_pkg->freq; + #change this bit to use Date::Manip? CAREFUL with timezones (see + # mailing list archive) + my ($sec,$min,$hour,$mday,$mon,$year) = + (localtime($sdate) )[0,1,2,3,4,5]; + + #pro-rating magic - if $recur_prog fiddles $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; + #no need, its in $hash{last_bill}# my $last_bill = $cust_pkg->last_bill; + $cust_pkg->last_bill($sdate); + + if ( $part_pkg->freq =~ /^\d+$/ ) { + $mon += $part_pkg->freq; + until ( $mon < 12 ) { $mon -= 12; $year++; } + } elsif ( $part_pkg->freq =~ /^(\d+)w$/ ) { + my $weeks = $1; + $mday += $weeks * 7; + } elsif ( $part_pkg->freq =~ /^(\d+)d$/ ) { + my $days = $1; + $mday += $days; + } elsif ( $part_pkg->freq =~ /^(\d+)h$/ ) { + my $hours = $1; + $hour += $hours; + } else { + return "unparsable frequency: ". $part_pkg->freq; + } + $cust_pkg->setfield('bill', + timelocal_nocheck($sec,$min,$hour,$mday,$mon,$year)); + } - $cust_pkg->setfield('bill', - timelocal_nocheck($sec,$min,$hour,$mday,$mon,$year)); } @@ -2446,6 +2506,14 @@ sub _make_lines { warn " charges (setup=$setup, recur=$recur); adding line items\n" if $DEBUG > 1; + + my @cust_pkg_detail = map { $_->detail } $cust_pkg->cust_pkg_detail('I'); + if ( $DEBUG > 1 ) { + warn " adding customer package invoice detail: $_\n" + foreach @cust_pkg_detail; + } + push @details, @cust_pkg_detail; + my $cust_bill_pkg = new FS::cust_bill_pkg { 'pkgnum' => $cust_pkg->pkgnum, 'setup' => $setup, @@ -2453,13 +2521,19 @@ sub _make_lines { 'recur' => $recur, 'unitrecur' => $unitrecur, 'quantity' => $cust_pkg->quantity, - 'sdate' => $sdate, - 'edate' => $cust_pkg->bill, 'details' => \@details, }; + + 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; - push @$cust_bill_pkgs, $cust_bill_pkg; $$total_setup += $setup; $$total_recur += $recur; @@ -2468,48 +2542,17 @@ sub _make_lines { # handle taxes ### - unless ( $self->tax =~ /Y/i || $self->payby eq 'COMP' ) { - - $self->_handle_taxes($part_pkg, $taxlisthash, $cust_bill_pkg); + my $error = + $self->_handle_taxes($part_pkg, $taxlisthash, $cust_bill_pkg, $cust_pkg); + return $error if $error; - } #unless $self->tax =~ /Y/i || $self->payby eq 'COMP' + push @$cust_bill_pkgs, $cust_bill_pkg; } #if $setup != 0 || $recur != 0 } #if $line_items - if ( $part_pkg->can('append_cust_bill_pkgs') ) { - my %param = ( 'precommit_hooks' => $precommit_hooks, ); - my ($more_cust_bill_pkgs) = - eval { $part_pkg->append_cust_bill_pkgs( $cust_pkg, \$sdate, \%param ) }; - - return "$@ running append_cust_bill_pkgs for $cust_pkg\n" - if ( $@ ); - return "$more_cust_bill_pkgs" - unless ( ref($more_cust_bill_pkgs) ); - - foreach my $cust_bill_pkg ( @{$more_cust_bill_pkgs} ) { - - $cust_bill_pkg->pkgpart_override($part_pkg->pkgpart) - unless $part_pkg->pkgpart == $real_pkgpart; - push @$appended_cust_bill_pkg, $cust_bill_pkg; - - unless ($cust_bill_pkg->duplicate) { - $$total_setup += $cust_bill_pkg->setup; - $$total_recur += $cust_bill_pkg->recur; - - ### - # handle taxes - ### - - unless ( $self->tax =~ /Y/i || $self->payby eq 'COMP' ) { - - $self->_handle_taxes($part_pkg, $taxlisthash, $cust_bill_pkg); - - } #unless $self->tax =~ /Y/i || $self->payby eq 'COMP' - } - } - } + ''; } @@ -2518,49 +2561,48 @@ sub _handle_taxes { my $part_pkg = shift; my $taxlisthash = shift; my $cust_bill_pkg = shift; + my $cust_pkg = shift; - my @taxes = (); - my @taxoverrides = $part_pkg->part_pkg_taxoverride; + my %cust_bill_pkg = (); + my %taxes = (); my $prefix = ( $conf->exists('tax-ship_address') && length($self->ship_last) ) ? 'ship_' : ''; + 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; + if ( $conf->exists('enable_taxproducts') - && (scalar(@taxoverrides) || $part_pkg->taxproductnum ) + && (scalar($part_pkg->part_pkg_taxoverride) || $part_pkg->has_taxproduct) + && ( $self->tax !~ /Y/i && $self->payby ne 'COMP' ) ) { - my @taxclassnums = (); - my $geocode = $self->geocode('cch'); - - if ( scalar( @taxoverrides ) ) { - @taxclassnums = map { $_->taxclassnum } @taxoverrides; - }elsif ( $part_pkg->taxproductnum ) { - @taxclassnums = map { $_->taxclassnum } - $part_pkg->part_pkg_taxrate('cch', $geocode); + foreach my $class (@classes) { + my $err_or_ref = $self->_gather_taxes( $part_pkg, $class, $prefix ); + return $err_or_ref unless ref($err_or_ref); + $taxes{$class} = $err_or_ref; } - my $extra_sql = - "AND (". - join(' OR ', map { "taxclassnum = $_" } @taxclassnums ). ")"; - - @taxes = qsearch({ 'table' => 'tax_rate', - 'hashref' => { 'geocode' => $geocode, }, - 'extra_sql' => $extra_sql, - }) - if scalar(@taxclassnums); - + unless (exists $taxes{''}) { + my $err_or_ref = $self->_gather_taxes( $part_pkg, '', $prefix ); + return $err_or_ref unless ref($err_or_ref); + $taxes{''} = $err_or_ref; + } - }else{ + } elsif ( $self->tax !~ /Y/i && $self->payby ne 'COMP' ) { my %taxhash = map { $_ => $self->get("$prefix$_") } qw( state county country ); $taxhash{'taxclass'} = $part_pkg->taxclass; - @taxes = qsearch( 'cust_main_county', \%taxhash ); + my @taxes = qsearch( 'cust_main_county', \%taxhash ); unless ( @taxes ) { $taxhash{'taxclass'} = ''; @@ -2573,39 +2615,105 @@ sub _handle_taxes { @taxes = qsearch( 'cust_main_county', \%taxhash ); } - } #if $conf->exists('enable_taxproducts') + $taxes{''} = [ @taxes ]; + $taxes{'setup'} = [ @taxes ]; + $taxes{'recur'} = [ @taxes ]; + $taxes{$_} = [ @taxes ] foreach (@classes); - # maybe eliminate this entirely, along with all the 0% records - unless ( @taxes ) { - my $error; - if ( $conf->exists('enable_taxproducts') ) { - $error = - "fatal: can't find tax rate for zip/taxproduct/pkgpart ". - join('/', ( map $self->get("$prefix$_"), - qw(zip) - ), - $part_pkg->taxproduct_description, - $part_pkg->pkgpart ). "\n"; - } else { - $error = + # maybe eliminate this entirely, along with all the 0% records + unless ( @taxes ) { + return "fatal: can't find tax rate for state/county/country/taxclass ". join('/', ( map $self->get("$prefix$_"), qw(state county country) ), $part_pkg->taxclass ). "\n"; } - return $error; + + } #if $conf->exists('enable_taxproducts') ... + + 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); - 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 ]; + 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 { + my $self = shift; + my $part_pkg = shift; + my $class = shift; + my $prefix = shift; + + my @taxes = (); + my $geocode = $self->geocode('cch'); + + my @taxclassnums = map { $_->taxclassnum } + $part_pkg->part_pkg_taxoverride($class); + + unless (@taxclassnums) { + @taxclassnums = map { $_->taxclassnum } + $part_pkg->part_pkg_taxrate('cch', $geocode, $class); + } + warn "Found taxclassnum values of ". join(',', @taxclassnums) + if $DEBUG; + + my $extra_sql = + "AND (". + join(' OR ', map { "taxclassnum = $_" } @taxclassnums ). ")"; + + @taxes = qsearch({ 'table' => 'tax_rate', + 'hashref' => { 'geocode' => $geocode, }, + 'extra_sql' => $extra_sql, + }) + if scalar(@taxclassnums); + + # maybe eliminate this entirely, along with all the 0% records + unless ( @taxes ) { + return + "fatal: can't find tax rate for zip/taxproduct/pkgpart ". + join('/', ( map $self->get("$prefix$_"), + qw(zip) + ), + $part_pkg->taxproduct_description, + $part_pkg->pkgpart ). "\n"; + } + + warn "Found taxes ". + join(',', map{ ref($_). " ". $_->get($_->primary_key) } @taxes). "\n" + if $DEBUG; + + [ @taxes ]; + } =item collect OPTIONS @@ -2920,14 +3028,16 @@ sub due_cust_event { # 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; @@ -4789,6 +4899,7 @@ the error, otherwise returns false. sub charge { my $self = shift; my ( $amount, $quantity, $pkg, $comment, $taxclass, $additional, $classnum ); + my ( $taxproduct, $override ); if ( ref( $_[0] ) ) { $amount = $_[0]->{amount}; $quantity = exists($_[0]->{quantity}) ? $_[0]->{quantity} : 1; @@ -4798,6 +4909,8 @@ sub charge { $taxclass = exists($_[0]->{taxclass}) ? $_[0]->{taxclass} : ''; $classnum = exists($_[0]->{classnum}) ? $_[0]->{classnum} : ''; $additional = $_[0]->{additional}; + $taxproduct = $_[0]->{taxproductnum}; + $override = { '' => $_[0]->{tax_override} }; }else{ $amount = shift; $quantity = 1; @@ -4819,13 +4932,14 @@ sub charge { my $dbh = dbh; my $part_pkg = new FS::part_pkg ( { - 'pkg' => $pkg, - 'comment' => $comment, - 'plan' => 'flat', - 'freq' => 0, - 'disabled' => 'Y', - 'classnum' => $classnum ? $classnum : '', - 'taxclass' => $taxclass, + 'pkg' => $pkg, + 'comment' => $comment, + 'plan' => 'flat', + 'freq' => 0, + 'disabled' => 'Y', + 'classnum' => $classnum ? $classnum : '', + 'taxclass' => $taxclass, + 'taxproductnum' => $taxproduct, } ); my %options = ( ( map { ("additional_info$_" => $additional->[$_] ) } @@ -4835,7 +4949,9 @@ sub charge { 'setup_fee' => $amount, ); - my $error = $part_pkg->insert( options => \%options ); + my $error = $part_pkg->insert( options => \%options, + tax_overrides => $override, + ); if ( $error ) { $dbh->rollback if $oldAutoCommit; return $error; @@ -4980,6 +5096,22 @@ sub cust_refund { 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 @@ -5068,15 +5200,16 @@ sub geocode { 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; } @@ -5867,22 +6000,28 @@ sub smart_search { # 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 } ); @@ -6272,6 +6411,9 @@ sub process_batch_import { =cut +use FS::svc_acct; +use FS::svc_external; + #some false laziness w/cdr.pm now sub batch_import { my $param = shift; @@ -6319,6 +6461,18 @@ sub batch_import { 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"; } @@ -6407,7 +6561,7 @@ sub batch_import { ); 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)$/ ) { @@ -6423,7 +6577,11 @@ sub batch_import { } 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 { @@ -6450,8 +6608,8 @@ sub batch_import { $columns[0] = $part_referral->refnum; } - #$cust_main{$field} = shift @$columns; - $cust_main{$field} = shift @columns; + my $value = shift @columns; + $cust_main{$field} = $value if length($value); } } @@ -6471,18 +6629,25 @@ sub batch_import { 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 ); @@ -6890,7 +7055,7 @@ sub _agent_plandata { " AND action = 'cust_bill_send_agent' ". " AND ( disabled IS NULL OR disabled != 'Y' ) ". " AND peo_agentnum.optionname = 'agentnum' ". - " AND agentnum IS NULL OR agentnum = $agentnum ". + " AND ( agentnum IS NULL OR agentnum = $agentnum ) ". " ORDER BY CASE WHEN peo_cust_bill_age.optionname != 'cust_bill_age' THEN -1