X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_main.pm;h=cbf95f5490aaafdfd59016a86abfbe4fc641d454;hb=a8121357fa4c5215c19b17e2ed79ced4a7d9fa0b;hp=0c50b8462f7653302761ee3f4a30ac4fac21af96;hpb=3dff65c08ed16024f5fd5f55d23551f485ced164;p=freeside.git diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 0c50b8462..cbf95f549 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -1,22 +1,27 @@ package FS::cust_main; - -require 5.006; -use strict; -use base qw( FS::cust_main::Packages FS::cust_main::Status +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 + FS::cust_main::Billing_Realtime FS::cust_main::Billing_Discount FS::cust_main::Billing_ThirdParty FS::cust_main::Location + FS::cust_main::Credit_Limit + FS::cust_main::API FS::otaker_Mixin FS::payinfo_Mixin FS::cust_main_Mixin FS::geocode_Mixin FS::Quotable_Mixin FS::Sales_Mixin FS::o2m_Common FS::Record ); + +require 5.006; +use strict; use vars qw( $DEBUG $me $conf @encrypted_fields $import $ignore_expired_card $ignore_banned_card $ignore_illegal_zip + $ignore_invalid_card $skip_fuzzyfiles @paytypes ); @@ -59,7 +64,6 @@ use FS::cust_class; use FS::cust_main_exemption; use FS::cust_tax_adjustment; use FS::cust_tax_location; -use FS::agent; use FS::agent_currency; use FS::cust_main_invoice; use FS::cust_tag; @@ -89,6 +93,7 @@ $me = '[FS::cust_main]'; $import = 0; $ignore_expired_card = 0; $ignore_banned_card = 0; +$ignore_invalid_card = 0; $skip_fuzzyfiles = 0; @@ -102,6 +107,7 @@ sub nohistory_fields { ('payinfo', 'paycvv'); } install_callback FS::UID sub { $conf = new FS::Conf; #yes, need it for stuff below (prolly should be cached) + $ignore_invalid_card = $conf->exists('allow_invalid_cards'); }; sub _cache { @@ -346,6 +352,9 @@ created and inserted. If I is set, moves contacts and locations from that prospect. +If I is set to an arrayref of FS::contact objects, inserts those +new contacts with this new customer. + =cut sub insert { @@ -408,7 +417,9 @@ sub insert { my $loc = delete $self->hashref->{$l}; # XXX if we're moving a prospect's locations, do that here if ( !$loc ) { - return "$l not set"; + #return "$l not set"; + #location-less customer records are now permitted + next; } if ( !$loc->locationnum ) { @@ -458,7 +469,7 @@ sub insert { foreach my $l (qw(bill_location ship_location)) { warn " setting $l.custnum\n" if $DEBUG > 1; - my $loc = $self->$l; + my $loc = $self->$l or next; unless ( $loc->custnum ) { $loc->set(custnum => $self->custnum); $error ||= $loc->replace; @@ -532,6 +543,21 @@ sub insert { } + my $contact = delete $options{'contact'}; + if ( $contact ) { + + foreach my $c ( @$contact ) { + $c->custnum($self->custnum); + my $error = $c->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + + } + + } + warn " setting cust_main_exemption\n" if $DEBUG > 1; @@ -1240,13 +1266,14 @@ sub merge { } tie my %financial_tables, 'Tie::IxHash', - 'cust_bill' => 'invoices', - 'cust_bill_void' => 'voided invoices', - 'cust_statement' => 'statements', - 'cust_credit' => 'credits', - 'cust_pay' => 'payments', - 'cust_pay_void' => 'voided payments', - 'cust_refund' => 'refunds', + 'cust_bill' => 'invoices', + 'cust_bill_void' => 'voided invoices', + 'cust_statement' => 'statements', + 'cust_credit' => 'credits', + 'cust_credit_void' => 'voided credits', + 'cust_pay' => 'payments', + 'cust_pay_void' => 'voided payments', + 'cust_refund' => 'refunds', ; foreach my $table ( keys %financial_tables ) { @@ -1658,13 +1685,26 @@ sub queue_fuzzyfiles_update { local $FS::UID::AutoCommit = 0; my $dbh = dbh; - my @locations = $self->bill_location; - push @locations, $self->ship_location if $self->has_ship_address; + foreach my $field ( 'first', 'last', 'company', 'ship_company' ) { + my $queue = new FS::queue { + 'job' => 'FS::cust_main::Search::append_fuzzyfiles_fuzzyfield' + }; + my @args = "cust_main.$field", $self->get($field); + my $error = $queue->insert( @args ); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "queueing job (transaction rolled back): $error"; + } + } + + my @locations = (); + push @locations, $self->bill_location if $self->bill_locationnum; + push @locations, $self->ship_location if @locations && $self->has_ship_address; foreach my $location (@locations) { my $queue = new FS::queue { - 'job' => 'FS::cust_main::Search::append_fuzzyfiles' + 'job' => 'FS::cust_main::Search::append_fuzzyfiles_fuzzyfield' }; - my @args = map $location->get($_), @FS::cust_main::Search::fuzzyfields; + my @args = 'cust_location.address1', $location->address1; my $error = $queue->insert( @args ); if ( $error ) { $dbh->rollback if $oldAutoCommit; @@ -1696,8 +1736,8 @@ sub check { || $self->ut_number('agentnum') || $self->ut_textn('agent_custid') || $self->ut_number('refnum') - || $self->ut_foreign_key('bill_locationnum', 'cust_location','locationnum') - || $self->ut_foreign_key('ship_locationnum', 'cust_location','locationnum') + || $self->ut_foreign_keyn('bill_locationnum', 'cust_location','locationnum') + || $self->ut_foreign_keyn('ship_locationnum', 'cust_location','locationnum') || $self->ut_foreign_keyn('classnum', 'cust_class', 'classnum') || $self->ut_foreign_keyn('salesnum', 'sales', 'salesnum') || $self->ut_textn('custbatch') @@ -1705,9 +1745,12 @@ sub check { || $self->ut_name('first') || $self->ut_snumbern('signupdate') || $self->ut_snumbern('birthdate') + || $self->ut_namen('spouse_last') + || $self->ut_namen('spouse_first') || $self->ut_snumbern('spouse_birthdate') || $self->ut_snumbern('anniversary_date') || $self->ut_textn('company') + || $self->ut_textn('ship_company') || $self->ut_anything('comments') || $self->ut_numbern('referral_custnum') || $self->ut_textn('stateid') @@ -1725,11 +1768,13 @@ sub check { || $self->ut_currencyn('currency') ; - my $company = $self->company; - $company =~ s/^\s+//; - $company =~ s/\s+$//; - $company =~ s/\s+/ /g; - $self->company($company); + foreach (qw(company ship_company)) { + my $company = $self->get($_); + $company =~ s/^\s+//; + $company =~ s/\s+$//; + $company =~ s/\s+/ /g; + $self->set($_, $company); + } #barf. need message catalogs. i18n. etc. $error .= "Please select an advertising source." @@ -1826,7 +1871,8 @@ sub check { # Need some kind of global flag to accept invalid cards, for testing # on scrubbed data. - if ( !$import && $check_payinfo && $self->payby =~ /^(CARD|DCRD)$/ ) { + if ( !$import && !$ignore_invalid_card && $check_payinfo && + $self->payby =~ /^(CARD|DCRD)$/ ) { my $payinfo = $self->payinfo; $payinfo =~ s/\D//g; @@ -1898,7 +1944,8 @@ sub check { $self->payissue(''); } - } elsif ( $check_payinfo && $self->payby =~ /^(CHEK|DCHK)$/ ) { + } elsif ( !$ignore_invalid_card && $check_payinfo && + $self->payby =~ /^(CHEK|DCHK)$/ ) { my $payinfo = $self->payinfo; $payinfo =~ s/[^\d\@\.]//g; @@ -2005,9 +2052,17 @@ sub check { ) { $self->payname( $self->first. " ". $self->getfield('last') ); } else { - $self->payname =~ /^([\w \,\.\-\'\&]*)$/ - or return gettext('illegal_name'). " payname: ". $self->payname; - $self->payname($1); + + if ( $self->payby =~ /^(CHEK|DCHK)$/ ) { + $self->payname =~ /^([\w \,\.\-\']*)$/ + or return gettext('illegal_name'). " payname: ". $self->payname; + $self->payname($1); + } else { + $self->payname =~ /^([\w \,\.\-\'\&]*)$/ + or return gettext('illegal_name'). " payname: ". $self->payname; + $self->payname($1); + } + } ### end of stuff moved to cust_payby @@ -2076,7 +2131,7 @@ Returns all locations (see L) for this customer. sub cust_location { my $self = shift; - qsearch('cust_location', { 'custnum' => $self->custnum, + qsearch('cust_location', { 'custnum' => $self->custnum, 'prospectnum' => '' } ); } @@ -2092,6 +2147,21 @@ sub cust_contact { qsearch('contact', { 'custnum' => $self->custnum } ); } +=item cust_payby + +Returns all payment methods (see L) for this customer. + +=cut + +sub cust_payby { + my $self = shift; + qsearch({ + 'table' => 'cust_payby', + 'hashref' => { 'custnum' => $self->custnum }, + 'order_by' => 'ORDER BY weight ASC', + }); +} + =item unsuspend Unsuspends all unflagged suspended packages (see L @@ -2291,13 +2361,6 @@ sub notes { Returns the agent (see L) for this customer. -=cut - -sub agent { - my $self = shift; - qsearchs( 'agent', { 'agentnum' => $self->agentnum } ); -} - =item agent_name Returns the agent name (see L) for this customer. @@ -2314,13 +2377,6 @@ sub agent_name { Returns any tags associated with this customer, as FS::cust_tag objects, or an empty list if there are no tags. -=cut - -sub cust_tag { - my $self = shift; - qsearch('cust_tag', { 'custnum' => $self->custnum } ); -} - =item part_tag Returns any tags associated with this customer, as FS::part_tag objects, @@ -2339,17 +2395,6 @@ sub part_tag { Returns the customer class, as an FS::cust_class object, or the empty string if there is no customer class. -=cut - -sub cust_class { - my $self = shift; - if ( $self->classnum ) { - qsearchs('cust_class', { 'classnum' => $self->classnum } ); - } else { - return ''; - } -} - =item categoryname Returns the customer category name, or the empty string if there is no customer @@ -2793,7 +2838,7 @@ UNIX timestamps; see L). Also see L and L for conversion functions. The empty string can be passed to disable that time constraint completely. -Available options are: +Accepts the same options as L: =over 4 @@ -2801,6 +2846,12 @@ Available options are: set to true to disregard unapplied credits, payments and refunds outside the specified time period - by default the time period restriction only applies to invoices (useful for reporting, probably a bad idea for event triggering) +=item cutoff + +An absolute cutoff time. Payments, credits, and refunds I after this +time will be ignored. Note that START_TIME and END_TIME only limit the date +range for invoices and I payments, credits, and refunds. + =back =cut @@ -3023,13 +3074,6 @@ sub tax_exemption { =item cust_main_exemption -=cut - -sub cust_main_exemption { - my $self = shift; - qsearch( 'cust_main_exemption', { 'custnum' => $self->custnum } ); -} - =item invoicing_list [ ARRAYREF ] If an arguement is given, sets these email addresses as invoice recipients @@ -3712,6 +3756,19 @@ sub cust_credit_pkgnum { ); } +=item cust_credit_void + +Returns all voided credits (see L) for this customer. + +=cut + +sub cust_credit_void { + my $self = shift; + map { $_ } + sort { $a->_date <=> $b->_date } + qsearch( 'cust_credit_void', { 'custnum' => $self->custnum } ) +} + =item cust_pay Returns all the payments (see L) for this customer. @@ -3720,9 +3777,17 @@ Returns all the payments (see L) for this customer. sub cust_pay { my $self = shift; - return $self->num_cust_pay unless wantarray; - sort { $a->_date <=> $b->_date } - qsearch( 'cust_pay', { 'custnum' => $self->custnum } ) + my $opt = ref($_[0]) ? shift : { @_ }; + + return $self->num_cust_pay unless wantarray || keys %$opt; + + $opt->{'table'} = 'cust_pay'; + $opt->{'hashref'}{'custnum'} = $self->custnum; + + map { $_ } #behavior of sort undefined in scalar context + sort { $a->_date <=> $b->_date } + qsearch($opt); + } =item num_cust_pay @@ -3740,6 +3805,22 @@ sub num_cust_pay { $sth->fetchrow_arrayref->[0]; } +=item unapplied_cust_pay + +Returns all the unapplied payments (see L) for this customer. + +=cut + +sub unapplied_cust_pay { + my $self = shift; + + $self->cust_pay( + 'extra_sql' => ' AND '. FS::cust_pay->unapplied_sql. ' > 0', + #@_ + ); + +} + =item cust_pay_pkgnum Returns all the payments (see L) for this customer's specific @@ -4056,6 +4137,16 @@ sub ship_contact_firstlast { # code2country($self->country); #} +sub bill_country_full { + my $self = shift; + code2country($self->bill_location->country); +} + +sub ship_country_full { + my $self = shift; + code2country($self->ship_location->country); +} + =item county_state_county [ PREFIX ] Returns a string consisting of just the county, state and country. @@ -4541,121 +4632,6 @@ sub search { =over 4 -=item batch_charge - -=cut - -sub batch_charge { - my $param = shift; - #warn join('-',keys %$param); - my $fh = $param->{filehandle}; - my $agentnum = $param->{agentnum}; - my $format = $param->{format}; - - my $extra_sql = ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql; - - my @fields; - if ( $format eq 'simple' ) { - @fields = qw( custnum agent_custid amount pkg ); - } else { - die "unknown format $format"; - } - - eval "use Text::CSV_XS;"; - die $@ if $@; - - my $csv = new Text::CSV_XS; - #warn $csv; - #warn $fh; - - my $imported = 0; - #my $columns; - - 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; - - #while ( $columns = $csv->getline($fh) ) { - my $line; - while ( defined($line=<$fh>) ) { - - $csv->parse($line) or do { - $dbh->rollback if $oldAutoCommit; - return "can't parse: ". $csv->error_input(); - }; - - my @columns = $csv->fields(); - #warn join('-',@columns); - - my %row = (); - foreach my $field ( @fields ) { - $row{$field} = shift @columns; - } - - if ( $row{custnum} && $row{agent_custid} ) { - dbh->rollback if $oldAutoCommit; - return "can't specify custnum with agent_custid $row{agent_custid}"; - } - - my %hash = (); - if ( $row{agent_custid} && $agentnum ) { - %hash = ( 'agent_custid' => $row{agent_custid}, - 'agentnum' => $agentnum, - ); - } - - if ( $row{custnum} ) { - %hash = ( 'custnum' => $row{custnum} ); - } - - unless ( scalar(keys %hash) ) { - $dbh->rollback if $oldAutoCommit; - return "can't find customer without custnum or agent_custid and agentnum"; - } - - my $cust_main = qsearchs('cust_main', { %hash } ); - unless ( $cust_main ) { - $dbh->rollback if $oldAutoCommit; - my $custnum = $row{custnum} || $row{agent_custid}; - return "unknown custnum $custnum"; - } - - if ( $row{'amount'} > 0 ) { - my $error = $cust_main->charge($row{'amount'}, $row{'pkg'}); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; - } - $imported++; - } elsif ( $row{'amount'} < 0 ) { - my $error = $cust_main->credit( sprintf( "%.2f", 0-$row{'amount'} ), - $row{'pkg'} ); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; - } - $imported++; - } else { - #hmm? - } - - } - - $dbh->commit or die $dbh->errstr if $oldAutoCommit; - - return "Empty file!" unless $imported; - - ''; #no error - -} - =item notify CUSTOMER_OBJECT TEMPLATE_NAME OPTIONS Deprecated. Use event notification and message templates @@ -4895,9 +4871,9 @@ sub queueable_print { my %opt = @_; my $self = qsearchs('cust_main', { 'custnum' => $opt{custnum} } ) - or die "invalid customer number: " . $opt{custvnum}; + or die "invalid customer number: " . $opt{custnum}; - my $error = $self->print( $opt{template} ); + my $error = $self->print( { 'template' => $opt{template} } ); die $error if $error; } @@ -4996,6 +4972,9 @@ sub queued_bill { my $cust_main = qsearchs( 'cust_main', { custnum => $args{'custnum'} } ); warn 'bill_and_collect custnum#'. $cust_main->custnum. "\n";#log custnum w/pid + #without this errors don't get rolled back + $args{'fatal'} = 1; # runs from job queue, will be caught + $cust_main->bill_and_collect( %args ); } @@ -5125,13 +5104,12 @@ sub _upgrade_data { #class method die $error if $error; $cust_main->setfield($_, '') foreach @payfields; - $DEBUG = 2; $error = $cust_main->replace; die $error if $error; }; - FS::upgrade_journal->set_done('cust_main__trimspaces'); + FS::upgrade_journal->set_done('cust_main__cust_payby'); } $class->_upgrade_otaker(%opts);