use strict;
#FS::cust_main:_Marketgear when they're ready to move to 2.1
use base qw( FS::cust_main::Packages FS::cust_main::Status
+ FS::cust_main::NationalID
FS::cust_main::Billing FS::cust_main::Billing_Realtime
FS::cust_main::Billing_Discount
FS::otaker_Mixin FS::payinfo_Mixin FS::cust_main_Mixin
use FS::cust_attachment;
use FS::contact;
use FS::Locales;
+use FS::upgrade_journal;
# 1 is mostly method/subroutine entry and options
# 2 traces progress of some operations
provisioning jobs (exports) are scheduled. (You can schedule them later with
the B<reexport> method.)
-The I<tax_exemption> option can be set to an arrayref of tax names.
-FS::cust_main_exemption records will be created and inserted.
+The I<tax_exemption> option can be set to an arrayref of tax names or a hashref
+of tax names and exemption numbers. FS::cust_main_exemption records will be
+created and inserted.
If I<prospectnum> is set, moves contacts and locations from that prospect.
$self->signupdate(time) unless $self->signupdate;
- $self->censusyear($conf->config('census_year')) if $self->censustract;
+ $self->censusyear($conf->config('census_year')||'2012') if $self->censustract;
$self->auto_agent_custid()
if $conf->config('cust_main-auto_agent_custid') && ! $self->agent_custid;
my $tax_exemption = delete $options{'tax_exemption'};
if ( $tax_exemption ) {
- foreach my $taxname ( @$tax_exemption ) {
+
+ $tax_exemption = { map { $_ => '' } @$tax_exemption }
+ if ref($tax_exemption) eq 'ARRAY';
+
+ foreach my $taxname ( keys %$tax_exemption ) {
my $cust_main_exemption = new FS::cust_main_exemption {
- 'custnum' => $self->custnum,
- 'taxname' => $taxname,
+ 'custnum' => $self->custnum,
+ 'taxname' => $taxname,
+ 'exempt_number' => $tax_exemption->{$taxname},
};
my $error = $cust_main_exemption->insert;
if ( $error ) {
return "Can't merge a customer into self" if $self->custnum == $new_custnum;
- unless ( qsearchs( 'cust_main', { 'custnum' => $new_custnum } ) ) {
- return "Invalid new customer number: $new_custnum";
- }
+ my $new_cust_main = qsearchs( 'cust_main', { 'custnum' => $new_custnum } )
+ or return "Invalid new customer number: $new_custnum";
+
+ return 'Access denied: "Merge customer across agents" access right required to merge into a customer of a different agent'
+ if $self->agentnum != $new_cust_main->agentnum
+ && ! $FS::CurrentUser::CurrentUser->access_right('Merge customer across agents');
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
Currently available options are: I<tax_exemption>.
-The I<tax_exemption> option can be set to an arrayref of tax names.
-FS::cust_main_exemption records will be deleted and inserted as appropriate.
+The I<tax_exemption> option can be set to an arrayref of tax names or a hashref
+of tax names and exemption numbers. FS::cust_main_exemption records will be
+deleted and inserted as appropriate.
=cut
if ( $self->censustract ne '' and $self->censustract ne $old->censustract ) {
# update censusyear whenever tract code changes
- $self->censusyear($conf->config('census_year'));
+ $self->censusyear($conf->config('census_year')||'2012');
}
+ return "Invoicing locale is required"
+ if $old->locale
+ && ! $self->locale
+ && $conf->exists('cust_main-require_locale');
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
my $tax_exemption = delete $options{'tax_exemption'};
if ( $tax_exemption ) {
+ $tax_exemption = { map { $_ => '' } @$tax_exemption }
+ if ref($tax_exemption) eq 'ARRAY';
+
my %cust_main_exemption =
map { $_->taxname => $_ }
qsearch('cust_main_exemption', { 'custnum' => $old->custnum } );
- foreach my $taxname ( @$tax_exemption ) {
+ foreach my $taxname ( keys %$tax_exemption ) {
- next if delete $cust_main_exemption{$taxname};
+ if ( $cust_main_exemption{$taxname} &&
+ $cust_main_exemption{$taxname}->exempt_number eq $tax_exemption->{$taxname}
+ )
+ {
+ delete $cust_main_exemption{$taxname};
+ next;
+ }
my $cust_main_exemption = new FS::cust_main_exemption {
- 'custnum' => $self->custnum,
- 'taxname' => $taxname,
+ 'custnum' => $self->custnum,
+ 'taxname' => $taxname,
+ 'exempt_number' => $tax_exemption->{$taxname},
};
my $error = $cust_main_exemption->insert;
if ( $error ) {
|| $self->ut_textn('custbatch')
|| $self->ut_name('last')
|| $self->ut_name('first')
- || $self->ut_snumbern('birthdate')
|| $self->ut_snumbern('signupdate')
+ || $self->ut_snumbern('birthdate')
+ || $self->ut_snumbern('spouse_birthdate')
+ || $self->ut_snumbern('anniversary_date')
|| $self->ut_textn('company')
|| $self->ut_text('address1')
|| $self->ut_textn('address2')
|| $self->ut_floatn('cdr_termination_percentage')
|| $self->ut_floatn('credit_limit')
|| $self->ut_numbern('billday')
+ || $self->ut_numbern('prorate_day')
|| $self->ut_enum('edit_subject', [ '', 'Y' ] )
|| $self->ut_enum('calling_list_exempt', [ '', 'Y' ] )
+ || $self->ut_enum('invoice_noemail', [ '', 'Y' ] )
|| $self->ut_enum('locale', [ '', FS::Locales->locales ])
;
$self->set_coord
unless $import || ($self->latitude && $self->longitude);
+ my $company = $self->company;
+ $company =~ s/^\s+//;
+ $company =~ s/\s+$//;
+ $company =~ s/\s+/ /g;
+ $self->company($company);
+
#barf. need message catalogs. i18n. etc.
$error .= "Please select an advertising source."
if $error =~ /^Illegal or empty \(numeric\) refnum: /;
return $error if $error;
}
- if ( $conf->exists('cust_main-require_phone')
+ if ( $conf->exists('cust_main-require_phone', $self->agentnum)
&& ! length($self->daytime) && ! length($self->night) && ! length($self->mobile)
) {
$self->payname($1);
}
+ return "Please select an invoicing locale"
+ if ! $self->locale
+ && ! $self->custnum
+ && $conf->exists('cust_main-require_locale');
+
foreach my $flag (qw( tax spool_cdr squelch_cdr archived email_csv_cdr )) {
$self->$flag() =~ /^(Y?)$/ or return "Illegal $flag: ". $self->$flag();
$self->$flag($1);
}
return "Email address required"
- if $conf->exists('cust_main-require_invoicing_list_email')
+ if $conf->exists('cust_main-require_invoicing_list_email', $self->agentnum)
&& ! grep { $_ !~ /^([A-Z]+)$/ } @$arrayref;
'';
sub display_custnum {
my $self = shift;
+
+ my $prefix = $conf->config('cust_main-custnum-display_prefix', $self->agentnum) || '';
+ if ( my $special = $conf->config('cust_main-custnum-display_special') ) {
+ if ( $special eq 'CoStAg' ) {
+ $prefix = uc( join('',
+ $self->country,
+ ($self->state =~ /^(..)/),
+ $prefix || ($self->agent->agent =~ /^(..)/)
+ ) );
+ }
+ elsif ( $special eq 'CoStCl' ) {
+ $prefix = uc( join('',
+ $self->country,
+ ($self->state =~ /^(..)/),
+ ($self->classnum ? $self->cust_class->classname =~ /^(..)/ : '__')
+ ) );
+ }
+ # add any others here if needed
+ }
+
+ my $length = $conf->config('cust_main-custnum-display_length');
if ( $conf->exists('cust_main-default_agent_custid') && $self->agent_custid ){
return $self->agent_custid;
- } elsif ( $conf->config('cust_main-custnum-display_prefix') ) {
- return $conf->config('cust_main-custnum-display_prefix').
- sprintf('%08d', $self->custnum)
+ } elsif ( $prefix ) {
+ $length = 8 if !defined($length);
+ return $prefix .
+ sprintf('%0'.$length.'d', $self->custnum)
+ } elsif ( $length ) {
+ return sprintf('%0'.$length.'d', $self->custnum);
} else {
return $self->custnum;
}
code2country($self->country);
}
+=item county_state_county [ PREFIX ]
+
+Returns a string consisting of just the county, state and country.
+
+=cut
+
+sub county_state_country {
+ my $self = shift;
+ my $prefix = (@_ && $_[0]) ? shift : '';
+ my $label = $self->get($prefix.'country');
+ $label = $self->get($prefix.'state'). ", $label"
+ if $self->get($prefix.'state');
+ $label = $self->get($prefix.'county')." County, $label"
+ if $self->get($prefix.'county');
+ $label;
+}
+
=item geocode DATA_VENDOR
Returns a value for the customer location as encoded by DATA_VENDOR.
sub print {
my ($self, $template) = (shift, shift);
- do_print [ $self->print_ps($template) ];
+ do_print(
+ [ $self->print_ps($template) ],
+ 'agentnum' => $self->agentnum,
+ );
}
#these three subs should just go away once agent stuff is all config overrides
# then it's a tract code
$cust_main->set('censustract', $new_tract);
$cust_main->set('censusyear', $new_year);
+
+ local($ignore_expired_card) = 1;
+ local($ignore_illegal_zip) = 1;
+ local($ignore_banned_card) = 1;
+ local($skip_fuzzyfiles) = 1;
+ local($import) = 1; #prevent automatic geocoding (need its own variable?)
my $error = $cust_main->replace;
die $error if $error;
}
my @statements = (
'UPDATE h_cust_main SET paycvv = NULL WHERE paycvv IS NOT NULL',
- 'UPDATE cust_main SET signupdate = (SELECT signupdate FROM h_cust_main WHERE signupdate IS NOT NULL AND h_cust_main.custnum = cust_main.custnum ORDER BY historynum DESC LIMIT 1) WHERE signupdate IS NULL',
);
- # fix yyyy-m-dd formatted paydates
- if ( driver_name =~ /^mysql$/i ) {
+
+ #this seems to be the only expensive one.. why does it take so long?
+ unless ( FS::upgrade_journal->is_done('cust_main__signupdate') ) {
push @statements,
- "UPDATE cust_main SET paydate = CONCAT( SUBSTRING(paydate FROM 1 FOR 5), '0', SUBSTRING(paydate FROM 6) ) WHERE SUBSTRING(paydate FROM 7 FOR 1) = '-'";
+ 'UPDATE cust_main SET signupdate = (SELECT signupdate FROM h_cust_main WHERE signupdate IS NOT NULL AND h_cust_main.custnum = cust_main.custnum ORDER BY historynum DESC LIMIT 1) WHERE signupdate IS NULL';
+ FS::upgrade_journal->set_done('cust_main__signupdate');
+ }
+
+ unless ( FS::upgrade_journal->is_done('cust_main__paydate') ) {
+
+ # fix yyyy-m-dd formatted paydates
+ if ( driver_name =~ /^mysql/i ) {
+ push @statements,
+ "UPDATE cust_main SET paydate = CONCAT( SUBSTRING(paydate FROM 1 FOR 5), '0', SUBSTRING(paydate FROM 6) ) WHERE SUBSTRING(paydate FROM 7 FOR 1) = '-'";
+ } else { # the SQL standard
+ push @statements,
+ "UPDATE cust_main SET paydate = SUBSTRING(paydate FROM 1 FOR 5) || '0' || SUBSTRING(paydate FROM 6) WHERE SUBSTRING(paydate FROM 7 FOR 1) = '-'";
+ }
+ FS::upgrade_journal->set_done('cust_main__paydate');
}
- else { # the SQL standard
- push @statements,
- "UPDATE cust_main SET paydate = SUBSTRING(paydate FROM 1 FOR 5) || '0' || SUBSTRING(paydate FROM 6) WHERE SUBSTRING(paydate FROM 7 FOR 1) = '-'";
+
+ unless ( FS::upgrade_journal->is_done('cust_main__payinfo') ) {
+
+ push @statements, #fix the weird BILL with a cc# in payinfo problem
+ #DCRD to be safe
+ "UPDATE cust_main SET payby = 'DCRD' WHERE payby = 'BILL' and length(payinfo) = 16 and payinfo ". regexp_sql. q( '^[0-9]*$' );
+
+ FS::upgrade_journal->set_done('cust_main__payinfo');
+
}
+ my $t = time;
foreach my $sql ( @statements ) {
my $sth = dbh->prepare($sql) or die dbh->errstr;
$sth->execute or die $sth->errstr;
+ #warn ( (time - $t). " seconds\n" );
+ #$t = time;
}
local($ignore_expired_card) = 1;
local($ignore_banned_card) = 1;
local($skip_fuzzyfiles) = 1;
local($import) = 1; #prevent automatic geocoding (need its own variable?)
+
+ unless ( FS::upgrade_journal->is_done('cust_main__trimspaces') ) {
+
+ foreach my $cust_main ( qsearch({
+ 'table' => 'cust_main',
+ 'hashref' => {},
+ 'extra_sql' => 'WHERE '.
+ join(' OR ',
+ map "$_ LIKE ' %' OR $_ LIKE '% ' OR $_ LIKE '% %'",
+ qw( first last company )
+ ),
+ }) ) {
+ my $error = $cust_main->replace;
+ die $error if $error;
+ }
+
+ FS::upgrade_journal->set_done('cust_main__trimspaces');
+
+ }
+
$class->_upgrade_otaker(%opts);
}