X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_main.pm;h=4973f690da2ffa50e018f45a5787a268dc0a4648;hb=cf49d3c860a2000cfc23a0e0db472a7d6fc58935;hp=c83acc65e3cf0e6e4ece33943b33365b177d4836;hpb=bdf4497fd8d3778e9cc0f8b90dd8a742f3a84158;p=freeside.git diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index c83acc65e..4973f690d 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -2,8 +2,12 @@ package FS::cust_main; require 5.006; use strict; -use vars qw( @ISA @EXPORT_OK $DEBUG $me $conf @encrypted_fields - $import $skip_fuzzyfiles $ignore_expired_card @paytypes); +use vars qw( @ISA @EXPORT_OK $DEBUG $me $conf + @encrypted_fields + $import $ignore_expired_card + $skip_fuzzyfiles @fuzzyfields + @paytypes + ); use vars qw( $realtime_bop_decline_quiet ); #ugh use Safe; use Carp; @@ -41,6 +45,7 @@ use FS::cust_refund; use FS::part_referral; use FS::cust_main_county; use FS::cust_location; +use FS::cust_class; use FS::cust_main_exemption; use FS::cust_tax_adjustment; use FS::tax_rate; @@ -77,9 +82,11 @@ $DEBUG = 0; $me = '[FS::cust_main]'; $import = 0; -$skip_fuzzyfiles = 0; $ignore_expired_card = 0; +$skip_fuzzyfiles = 0; +@fuzzyfields = ( 'first', 'last', 'company', 'address1' ); + @encrypted_fields = ('payinfo', 'paycvv'); sub nohistory_fields { ('paycvv'); } @@ -1492,9 +1499,7 @@ sub queue_fuzzyfiles_update { my $dbh = dbh; my $queue = new FS::queue { 'job' => 'FS::cust_main::append_fuzzyfiles' }; - my $error = $queue->insert( map $self->getfield($_), - qw(first last company) - ); + my $error = $queue->insert( map $self->getfield($_), @fuzzyfields ); if ( $error ) { $dbh->rollback if $oldAutoCommit; return "queueing job (transaction rolled back): $error"; @@ -1502,9 +1507,7 @@ sub queue_fuzzyfiles_update { if ( $self->ship_last ) { $queue = new FS::queue { 'job' => 'FS::cust_main::append_fuzzyfiles' }; - $error = $queue->insert( map $self->getfield("ship_$_"), - qw(first last company) - ); + $error = $queue->insert( map $self->getfield("ship_$_"), @fuzzyfields ); if ( $error ) { $dbh->rollback if $oldAutoCommit; return "queueing job (transaction rolled back): $error"; @@ -1535,6 +1538,7 @@ sub check { || $self->ut_number('agentnum') || $self->ut_textn('agent_custid') || $self->ut_number('refnum') + || $self->ut_foreign_keyn('classnum', 'cust_class', 'classnum') || $self->ut_textn('custbatch') || $self->ut_name('last') || $self->ut_name('first') @@ -2303,6 +2307,53 @@ sub agent { qsearchs( 'agent', { 'agentnum' => $self->agentnum } ); } +=item cust_class + +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 +category. + +=cut + +sub categoryname { + my $self = shift; + my $cust_class = $self->cust_class; + $cust_class + ? $cust_class->categoryname + : ''; +} + +=item classname + +Returns the customer class name, or the empty string if there is no customer +class. + +=cut + +sub classname { + my $self = shift; + my $cust_class = $self->cust_class; + $cust_class + ? $cust_class->classname + : ''; +} + + =item bill_and_collect Cancels and suspends any packages due, generates bills, applies payments and @@ -2477,7 +2528,7 @@ plans support this feature (they tend to charge 0). =item invoice_terms -Options terms to be printed on this invocice. Otherwise, customer-specific +Optional terms to be printed on this invoice. Otherwise, customer-specific terms or the default terms are used. =back @@ -2621,6 +2672,141 @@ sub bill { } + my $listref_or_error = + $self->calculate_taxes( \@cust_bill_pkg, \%taxlisthash, $invoice_time); + + unless ( ref( $listref_or_error ) ) { + $dbh->rollback if $oldAutoCommit; + return $listref_or_error; + } + + foreach my $taxline ( @$listref_or_error ) { + $total_setup = sprintf('%.2f', $total_setup+$taxline->setup ); + push @cust_bill_pkg, $taxline; + } + + #add tax adjustments + warn "adding tax adjustments...\n" if $DEBUG > 2; + foreach my $cust_tax_adjustment ( + qsearch('cust_tax_adjustment', { 'custnum' => $self->custnum, + 'billpkgnum' => '', + } + ) + ) { + + my $tax = sprintf('%.2f', $cust_tax_adjustment->amount ); + + my $itemdesc = $cust_tax_adjustment->taxname; + $itemdesc = '' if $itemdesc eq 'Tax'; + + push @cust_bill_pkg, new FS::cust_bill_pkg { + 'pkgnum' => 0, + 'setup' => $tax, + 'recur' => 0, + 'sdate' => '', + 'edate' => '', + 'itemdesc' => $itemdesc, + 'itemcomment' => $cust_tax_adjustment->comment, + 'cust_tax_adjustment' => $cust_tax_adjustment, + #'cust_bill_pkg_tax_location' => \@cust_bill_pkg_tax_location, + }; + + } + + my $charged = sprintf('%.2f', $total_setup + $total_recur ); + + my @cust_bill = $self->cust_bill; + my $balance = $self->balance; + my $previous_balance = scalar(@cust_bill) + ? ( $cust_bill[$#cust_bill]->billing_balance || 0 ) + : 0; + + $previous_balance += $cust_bill[$#cust_bill]->charged + if scalar(@cust_bill); + #my $balance_adjustments = + # sprintf('%.2f', $balance - $prior_prior_balance - $prior_charged); + + #create the new invoice + my $cust_bill = new FS::cust_bill ( { + 'custnum' => $self->custnum, + '_date' => ( $invoice_time ), + 'charged' => $charged, + 'billing_balance' => $balance, + 'previous_balance' => $previous_balance, + 'invoice_terms' => $options{'invoice_terms'}, + } ); + $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 +} + +=item calculate_taxes LINEITEMREF TAXHASHREF INVOICE_TIME + +This is a weird one. Perhaps it should not even be exposed. + +Generates tax line items (see L) for this customer. +Usually used internally by bill method B. + +If there is an error, returns the error, otherwise returns reference to a +list of line items suitable for insertion. + +=over 4 + +=item LINEITEMREF + +An array ref of the line items being billed. + +=item TAXHASHREF + +A strange beast. The keys to this hash are internal identifiers consisting +of the name of the tax object type, a space, and its unique identifier ( e.g. + 'cust_main_county 23' ). The values of the hash are listrefs. The first +item in the list is the tax object. The remaining items are either line +items or floating point values (currency amounts). + +The taxes are calculated on this entity. Calculated exemption records are +transferred to the LINEITEMREF items on the assumption that they are related. + +Read the source. + +=item INVOICE_TIME + +This specifies the date appearing on the associated invoice. Some +jurisdictions (i.e. Texas) have tax exemptions which are date sensitive. + +=back + +=cut +sub calculate_taxes { + my ($self, $cust_bill_pkg, $taxlisthash, $invoice_time) = @_; + + my @tax_line_items = (); + warn "having a look at the taxes we found...\n" if $DEBUG > 2; # keys are tax names (as printed on invoices / itemdesc ) @@ -2639,20 +2825,18 @@ sub bill { # values are listrefs of cust_bill_pkg_tax_rate_location hashrefs my %tax_rate_location = (); - foreach my $tax ( keys %taxlisthash ) { - my $tax_object = shift @{ $taxlisthash{$tax} }; + 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; + warn " ". join('/', @{ $taxlisthash->{$tax} } ). "\n" if $DEBUG > 2; my $hashref_or_error = - $tax_object->taxline( $taxlisthash{$tax}, + $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; + return $hashref_or_error unless ref($hashref_or_error); + + unshift @{ $taxlisthash->{$tax} }, $tax_object; my $name = $hashref_or_error->{'name'}; my $amount = $hashref_or_error->{'amount'}; @@ -2692,9 +2876,9 @@ sub bill { } #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} })] ) { + 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 }, @@ -2724,7 +2908,6 @@ sub bill { next unless $tax; $tax = sprintf('%.2f', $tax ); - $total_setup = sprintf('%.2f', $total_setup+$tax ); my $pkg_category = qsearchs( 'pkg_category', { 'categoryname' => $taxname, 'disabled' => '', @@ -2743,7 +2926,7 @@ sub bill { } - push @cust_bill_pkg, new FS::cust_bill_pkg { + push @tax_line_items, new FS::cust_bill_pkg { 'pkgnum' => 0, 'setup' => $tax, 'recur' => 0, @@ -2757,88 +2940,9 @@ sub bill { } - #add tax adjustments - warn "adding tax adjustments...\n" if $DEBUG > 2; - foreach my $cust_tax_adjustment ( - qsearch('cust_tax_adjustment', { 'custnum' => $self->custnum, - 'billpkgnum' => '', - } - ) - ) { - - my $tax = sprintf('%.2f', $cust_tax_adjustment->amount ); - $total_setup = sprintf('%.2f', $total_setup+$tax ); - - my $itemdesc = $cust_tax_adjustment->taxname; - $itemdesc = '' if $itemdesc eq 'Tax'; - - push @cust_bill_pkg, new FS::cust_bill_pkg { - 'pkgnum' => 0, - 'setup' => $tax, - 'recur' => 0, - 'sdate' => '', - 'edate' => '', - 'itemdesc' => $itemdesc, - 'itemcomment' => $cust_tax_adjustment->comment, - 'cust_tax_adjustment' => $cust_tax_adjustment, - #'cust_bill_pkg_tax_location' => \@cust_bill_pkg_tax_location, - }; - - } - - my $charged = sprintf('%.2f', $total_setup + $total_recur ); - - my @cust_bill = $self->cust_bill; - my $balance = $self->balance; - my $previous_balance = scalar(@cust_bill) - ? ( $cust_bill[$#cust_bill]->billing_balance || 0 ) - : 0; - - $previous_balance += $cust_bill[$#cust_bill]->charged - if scalar(@cust_bill); - #my $balance_adjustments = - # sprintf('%.2f', $balance - $prior_prior_balance - $prior_charged); - - #create the new invoice - my $cust_bill = new FS::cust_bill ( { - 'custnum' => $self->custnum, - '_date' => ( $invoice_time ), - 'charged' => $charged, - 'billing_balance' => $balance, - 'previous_balance' => $previous_balance, - 'invoice_terms' => $options{'invoice_terms'}, - } ); - $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 + \@tax_line_items; } - sub _make_lines { my ($self, %params) = @_; @@ -3105,7 +3209,7 @@ sub _handle_taxes { } else { - my @loc_keys = qw( state county country ); + my @loc_keys = qw( city county state country ); my %taxhash; if ( $conf->exists('tax-pkg_address') && $cust_pkg->locationnum ) { my $cust_location = $cust_pkg->cust_location; @@ -3120,15 +3224,24 @@ sub _handle_taxes { $taxhash{'taxclass'} = $part_pkg->taxclass; - my @taxes = qsearch( 'cust_main_county', \%taxhash ); - + my @taxes = (); my %taxhash_elim = %taxhash; + my @elim = qw( city county state ); + do { - my @elim = qw( taxclass county state ); - while ( !scalar(@taxes) && scalar(@elim) ) { - $taxhash_elim{ shift(@elim) } = ''; + #first try a match with taxclass @taxes = qsearch( 'cust_main_county', \%taxhash_elim ); - } + + if ( !scalar(@taxes) && $taxhash_elim{'taxclass'} ) { + #then try a match without taxclass + my %no_taxclass = %taxhash_elim; + $no_taxclass{ 'taxclass' } = ''; + @taxes = qsearch( 'cust_main_county', \%no_taxclass ); + } + + $taxhash_elim{ shift(@elim) } = ''; + + } while ( !scalar(@taxes) && scalar(@elim) ); @taxes = grep { ! $_->taxname or ! $self->tax_exemption($_->taxname) } @taxes @@ -3850,11 +3963,14 @@ I, I and I are also available. Any of these options, if set, will override the value from the customer record. I is a free-text field passed to the gateway. It defaults to -"Internet services". +the value defined by the business-onlinepayment-description configuration +option, or "Internet services" if that is unset. If an I is specified, this payment (if successful) is applied to the specified invoice. If you don't specify an I you might want to -call the B method. +call the B method or set the I option. + +I can be set to true to apply a resulting payment. I can be set true to surpress email decline notices. @@ -3873,13 +3989,32 @@ sub realtime_bop { return $self->_new_realtime_bop(@_) if $self->_new_bop_required(); - my( $method, $amount, %options ) = @_; + my($method, $amount); + my %options = (); + if (ref($_[0]) eq 'HASH') { + %options = %{$_[0]}; + $method = $options{method}; + $amount = $options{amount}; + } else { + ( $method, $amount ) = ( shift, shift ); + %options = @_; + } if ( $DEBUG ) { warn "$me realtime_bop: $method $amount\n"; warn " $_ => $options{$_}\n" foreach keys %options; } - $options{'description'} ||= 'Internet services'; + unless ( $options{'description'} ) { + if ( $conf->exists('business-onlinepayment-description') ) { + my $dtempl = $conf->config('business-onlinepayment-description'); + + my $agent = $self->agent->agent; + #$pkgs... not here + $options{'description'} = eval qq("$dtempl"); + } else { + $options{'description'} = 'Internet services'; + } + } return $self->fake_bop($method, $amount, %options) if $options{'fake'}; @@ -4347,6 +4482,16 @@ sub realtime_bop { } else { $dbh->commit or die $dbh->errstr if $oldAutoCommit; + + if ( $options{'apply'} ) { + my $apply_error = $self->apply_payments_and_credits; + if ( $apply_error ) { + warn "WARNING: error applying payment: $apply_error\n"; + #but we still should return no error cause the payment otherwise went + #through... + } + } + return ''; #no error } @@ -4436,9 +4581,9 @@ sub realtime_bop { sub _bop_recurring_billing { my( $self, %opt ) = @_; - my $method = $conf->config('credit_card-recurring_billing_flag'); + my $method = scalar($conf->config('credit_card-recurring_billing_flag')); - if ( $method eq 'transaction_is_recur' ) { + if ( defined($method) && $method eq 'transaction_is_recur' ) { return 1 if $opt{'trans_is_recur'}; @@ -4629,6 +4774,9 @@ sub realtime_refund_bop { ) { warn " attempting void\n" if $DEBUG > 1; my $void = new Business::OnlinePayment( $processor, @bop_options ); + $content{'card_number'} = $cust_pay->payinfo + if $cust_pay->payby eq 'CARD' + && $void->can('info') && $void->info('CC_void_requires_card'); $void->content( 'action' => 'void', %content ); $void->submit(); if ( $void->is_success ) { @@ -4802,7 +4950,6 @@ sub _new_bop_required { ''; } - =item realtime_collect [ OPTION => VALUE ... ] Runs a realtime credit card, ACH (electronic check) or phone bill transaction @@ -4826,11 +4973,14 @@ I, I and I are also available. Any of these options, if set, will override the value from the customer record. I is a free-text field passed to the gateway. It defaults to -"Internet services". +the value defined by the business-onlinepayment-description configuration +option, or "Internet services" if that is unset. If an I is specified, this payment (if successful) is applied to the specified invoice. If you don't specify an I you might want to -call the B method. +call the B method or set the I option. + +I can be set to true to apply a resulting payment. I can be set true to surpress email decline notices. @@ -4878,7 +5028,8 @@ I, I and I are also available. Any of these options, if set, will override the value from the customer record. I is a free-text field passed to the gateway. It defaults to -"Internet services". +the value defined by the business-onlinepayment-description configuration +option, or "Internet services" if that is unset. If an I is specified, this payment (if successful) is applied to the specified invoice. If you don't specify an I you might want to @@ -4929,7 +5080,18 @@ sub _bop_options { sub _bop_defaults { my ($self, $options) = @_; - $options->{description} ||= 'Internet services'; + unless ( $options->{'description'} ) { + if ( $conf->exists('business-onlinepayment-description') ) { + my $dtempl = $conf->config('business-onlinepayment-description'); + + my $agent = $self->agent->agent; + #$pkgs... not here + $options->{'description'} = eval qq("$dtempl"); + } else { + $options->{'description'} = 'Internet services'; + } + } + $options->{payinfo} = $self->payinfo unless exists( $options->{payinfo} ); $options->{invnum} ||= ''; $options->{payname} = $self->payname unless exists( $options->{payname} ); @@ -5513,6 +5675,16 @@ sub _realtime_bop_result { } else { $dbh->commit or die $dbh->errstr if $oldAutoCommit; + + if ( $options{'apply'} ) { + my $apply_error = $self->apply_payments_and_credits; + if ( $apply_error ) { + warn "WARNING: error applying payment: $apply_error\n"; + #but we still should return no error cause the payment otherwise went + #through... + } + } + return ''; #no error } @@ -5943,6 +6115,9 @@ sub _new_realtime_refund_bop { ) { warn " attempting void\n" if $DEBUG > 1; my $void = new Business::OnlinePayment( $processor, @bop_options ); + $content{'card_number'} = $cust_pay->payinfo + if $cust_pay->payby eq 'CARD' + && $void->can('info') && $void->info('CC_void_requires_card'); $void->content( 'action' => 'void', %content ); $void->submit(); if ( $void->is_success ) { @@ -6374,7 +6549,6 @@ A hash of optional arguments may be passed. Currently "manual" is supported. If true, a payment receipt is sent instead of a statement when 'payment_receipt_email' configuration option is set. - Dies if there is an error. =cut @@ -7243,7 +7417,7 @@ sub charge { 'plan' => 'flat', 'freq' => 0, 'disabled' => 'Y', - 'classnum' => $classnum ? $classnum : '', + 'classnum' => ( $classnum ? $classnum : '' ), 'setuptax' => $setuptax, 'taxclass' => $taxclass, 'taxproductnum' => $taxproduct, @@ -8090,7 +8264,7 @@ sub _money_table_where { } -=item search_sql HASHREF +=item search HASHREF (Class method) @@ -8115,6 +8289,10 @@ listref of start date, end date listref +=item paydate_year + +=item paydate_month + =item current_balance listref (list returned by FS::UI::Web::parse_lt_gt($cgi, 'current_balance')) @@ -8129,7 +8307,7 @@ bool =cut -sub search_sql { +sub search { my ($class, $params) = @_; my $dbh = dbh; @@ -8193,6 +8371,21 @@ sub search_sql { } ### + # classnum + ### + + my @classnum = grep /^(\d*)$/, @{ $params->{'classnum'} }; + if ( @classnum ) { + push @where, '( '. join(' OR ', map { + $_ ? "cust_main.classnum = $_" + : "cust_main.classnum IS NULL" + } + @classnum + ). + ' )'; + } + + ### # payby ### @@ -8201,15 +8394,57 @@ sub search_sql { push @where, '( '. join(' OR ', map "cust_main.payby = '$_'", @payby). ' )'; } + ### + # paydate_year / paydate_month + ### + + if ( $params->{'paydate_year'} =~ /^(\d{4})$/ ) { + my $year = $1; + $params->{'paydate_month'} =~ /^(\d\d?)$/ + or die "paydate_year without paydate_month?"; + my $month = $1; + + push @where, + 'paydate IS NOT NULL', + "paydate != ''", + "CAST(paydate AS timestamp) < CAST('$year-$month-01' AS timestamp )" +; + } + + ### + # invoice terms + ### + + if ( $params->{'invoice_terms'} =~ /^([\w ]+)$/ ) { + my $terms = $1; + if ( $1 eq 'NULL' ) { + push @where, + "( cust_main.invoice_terms IS NULL OR cust_main.invoice_terms = '' )"; + } else { + push @where, + "cust_main.invoice_terms IS NOT NULL", + "cust_main.invoice_terms = '$1'"; + } + } + ## # amounts ## - #my $balance_sql = $class->balance_sql(); - my $balance_sql = FS::cust_main->balance_sql(); + if ( $params->{'current_balance'} ) { - push @where, map { s/current_balance/$balance_sql/; $_ } - @{ $params->{'current_balance'} }; + #my $balance_sql = $class->balance_sql(); + my $balance_sql = FS::cust_main->balance_sql(); + + my @current_balance = + ref( $params->{'current_balance'} ) + ? @{ $params->{'current_balance'} } + : ( $params->{'current_balance'} ); + + push @where, map { s/current_balance/$balance_sql/; $_ } + @current_balance; + + } ## # custbatch @@ -8287,13 +8522,13 @@ sub search_sql { } -=item email_search_sql HASHREF +=item email_search_result HASHREF (Class method) Emails a notice to the specified customers. -Valid parameters are those of the L method, plus the following: +Valid parameters are those of the L method, plus the following: =over 4 @@ -8327,7 +8562,7 @@ retrying everything. =cut -sub email_search_sql { +sub email_search_result { my($class, $params) = @_; my $from = delete $params->{from}; @@ -8340,7 +8575,7 @@ sub email_search_sql { $params->{'payby'} = [ split(/\0/, $params->{'payby'}) ] unless ref($params->{'payby'}); - my $sql_query = $class->search_sql($params); + my $sql_query = $class->search($params); my $count_query = delete($sql_query->{'count_query'}); my $count_sth = dbh->prepare($count_query) @@ -8392,7 +8627,7 @@ sub email_search_sql { use Storable qw(thaw); use Data::Dumper; use MIME::Base64; -sub process_email_search_sql { +sub process_email_search_result { my $job = shift; #warn "$me process_re_X $method for job $job\n" if $DEBUG; @@ -8404,7 +8639,7 @@ sub process_email_search_sql { $param->{'payby'} = [ split(/\0/, $param->{'payby'}) ] unless ref($param->{'payby'}); - my $error = FS::cust_main->email_search_sql( $param ); + my $error = FS::cust_main->email_search_result( $param ); die $error if $error; } @@ -8412,8 +8647,8 @@ sub process_email_search_sql { =item fuzzy_search FUZZY_HASHREF [ HASHREF, SELECT, EXTRA_SQL, CACHE_OBJ ] Performs a fuzzy (approximate) search and returns the matching FS::cust_main -records. Currently, I, I and/or I may be specified (the -appropriate ship_ field is also searched). +records. Currently, I, I, I and/or I may be +specified (the appropriate ship_ field is also searched). Additional options are the same as FS::Record::qsearch @@ -8542,15 +8777,18 @@ sub smart_search { } if ( $search =~ /^\s*(\d+)\s*$/ - || ( $conf->config('cust_main-agent_custid-format') eq 'ww?d+' - && $search =~ /^\s*(\w\w?\d+)\s*$/ - ) - ) + || ( $conf->config('cust_main-agent_custid-format') eq 'ww?d+' + && $search =~ /^\s*(\w\w?\d+)\s*$/ + ) + || ( $conf->exists('address1-search' ) + && $search =~ /^\s*(\d+\-?\w*)\s*$/ #i.e. 1234A or 9432-D + ) + ) { my $num = $1; - if ( $num <= 2147483647 ) { #need a bigint custnum? wow. + if ( $num =~ /^(\d+)$/ && $num <= 2147483647 ) { #need a bigint custnum? wow push @cust_main, qsearch( { 'table' => 'cust_main', 'hashref' => { 'custnum' => $num, %options }, @@ -8564,23 +8802,42 @@ sub smart_search { 'extra_sql' => " AND $agentnums_sql", #agent virtualization } ); + if ( $conf->exists('address1-search') ) { + my $len = length($num); + $num = lc($num); + foreach my $prefix ( '', 'ship_' ) { + push @cust_main, qsearch( { + 'table' => 'cust_main', + 'hashref' => { %options, }, + 'extra_sql' => + ( keys(%options) ? ' AND ' : ' WHERE ' ). + " LOWER(SUBSTRING(${prefix}address1 FROM 1 FOR $len)) = '$num' ". + " AND $agentnums_sql", + } ); + } + } + } elsif ( $search =~ /^\s*(\S.*\S)\s+\((.+), ([^,]+)\)\s*$/ ) { my($company, $last, $first) = ( $1, $2, $3 ); # "Company (Last, First)" #this is probably something a browser remembered, - #so just do an exact search + #so just do an exact search (but case-insensitive, so USPS standardization + #doesn't throw a wrench in the works) foreach my $prefix ( '', 'ship_' ) { push @cust_main, qsearch( { 'table' => 'cust_main', - 'hashref' => { $prefix.'first' => $first, - $prefix.'last' => $last, - $prefix.'company' => $company, - %options, - }, - 'extra_sql' => " AND $agentnums_sql", + 'hashref' => { %options }, + 'extra_sql' => + ( keys(%options) ? ' AND ' : ' WHERE ' ). + join(' AND ', + " LOWER(${prefix}first) = ". dbh->quote(lc($first)), + " LOWER(${prefix}last) = ". dbh->quote(lc($last)), + " LOWER(${prefix}company) = ". dbh->quote(lc($company)), + $agentnums_sql, + ), } ); } @@ -8639,11 +8896,16 @@ sub smart_search { #exact my $sql = scalar(keys %options) ? ' AND ' : ' WHERE '; - $sql .= " ( LOWER(last) = $q_value - OR LOWER(company) = $q_value - OR LOWER(ship_last) = $q_value - OR LOWER(ship_company) = $q_value - )"; + $sql .= " ( LOWER(last) = $q_value + OR LOWER(company) = $q_value + OR LOWER(ship_last) = $q_value + OR LOWER(ship_company) = $q_value + "; + $sql .= " OR LOWER(address1) = $q_value + OR LOWER(ship_address1) = $q_value + " + if $conf->exists('address1-search'); + $sql .= " )"; push @cust_main, qsearch( { 'table' => 'cust_main', @@ -8656,7 +8918,7 @@ sub smart_search { #getting complaints searches are not returning enough unless ( @cust_main && $skip_fuzzy || $conf->exists('disable-fuzzy') ) { - #still some false laziness w/search_sql (was search/cust_main.cgi) + #still some false laziness w/search (was search/cust_main.cgi) #substring @@ -8684,6 +8946,13 @@ sub smart_search { ; } + if ( $conf->exists('address1-search') ) { + push @hashrefs, + { 'address1' => { op=>'ILIKE', value=>"%$value%" }, }, + { 'ship_address1' => { op=>'ILIKE', value=>"%$value%" }, }, + ; + } + foreach my $hashref ( @hashrefs ) { push @cust_main, qsearch( { @@ -8714,6 +8983,10 @@ sub smart_search { push @cust_main, FS::cust_main->fuzzy_search( { $field => $value }, @fuzopts ); } + if ( $conf->exists('address1-search') ) { + push @cust_main, + FS::cust_main->fuzzy_search( { 'address1' => $value }, @fuzopts ); + } } @@ -8797,9 +9070,6 @@ sub email_search { =cut -use vars qw(@fuzzyfields); -@fuzzyfields = ( 'last', 'first', 'company' ); - sub check_and_rebuild_fuzzyfiles { my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc; rebuild_fuzzyfiles() if grep { ! -e "$dir/cust_main.$_" } @fuzzyfields @@ -8859,7 +9129,7 @@ sub all_X { \@array; } -=item append_fuzzyfiles LASTNAME COMPANY +=item append_fuzzyfiles FIRSTNAME LASTNAME COMPANY ADDRESS1 =cut @@ -8872,7 +9142,7 @@ sub append_fuzzyfiles { my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc; - foreach my $field (qw( first last company )) { + foreach my $field (@fuzzyfields) { my $value = shift; if ( $value ) {