From: Ivan Kohler Date: Mon, 18 Sep 2017 21:57:12 +0000 (-0700) Subject: Merge branch 'master' of git.freeside.biz:/home/git/freeside X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=377a2f99f2377a0670f6a5326be636b259ae27ca;hp=a227cd4747d1e472a07845bad6d9e61afe268189 Merge branch 'master' of git.freeside.biz:/home/git/freeside --- diff --git a/FS/FS.pm b/FS/FS.pm index 134a34cb2..9575c3db3 100644 --- a/FS/FS.pm +++ b/FS/FS.pm @@ -67,6 +67,8 @@ L - Customer searching L - Batch customer importing +L - Batch contact importing + =head2 Database record classes L - Database record base class diff --git a/FS/FS/contact.pm b/FS/FS/contact.pm index 568d46f07..44c538806 100644 --- a/FS/FS/contact.pm +++ b/FS/FS/contact.pm @@ -10,6 +10,7 @@ use FS::Record qw( qsearch qsearchs dbh ); use FS::Cursor; use FS::contact_phone; use FS::contact_email; +use FS::contact::Import; use FS::queue; use FS::phone_type; #for cgi_contact_fields use FS::cust_contact; diff --git a/FS/FS/contact_import.pm b/FS/FS/contact_import.pm deleted file mode 100644 index d6cd690bf..000000000 --- a/FS/FS/contact_import.pm +++ /dev/null @@ -1,161 +0,0 @@ -package FS::contact_import; - -use strict; -use vars qw( $DEBUG ); #$conf ); -use Data::Dumper; -use FS::Misc::DateTime qw( parse_datetime ); -use FS::Record qw( qsearchs ); -use FS::contact; -use FS::cust_main; - -$DEBUG = 0; - -=head1 NAME - -FS::contact_import - Batch contact importing - -=head1 SYNOPSIS - - use FS::contact_import; - - #import - FS::contact_import::batch_import( { - file => $file, #filename - type => $type, #csv or xls - format => $format, #default - agentnum => $agentnum, - job => $job, #optional job queue job, for progressbar updates - pkgbatch => $pkgbatch, #optional batch unique identifier - } ); - die $error if $error; - - #ajax helper - use FS::UI::Web::JSRPC; - my $server = - new FS::UI::Web::JSRPC 'FS::contact_import::process_batch_import', $cgi; - print $server->process; - -=head1 DESCRIPTION - -Batch contact importing. - -=head1 SUBROUTINES - -=item process_batch_import - -Load a batch import as a queued JSRPC job - -=cut - -sub process_batch_import { - my $job = shift; - my $param = shift; - warn Dumper($param) if $DEBUG; - - my $files = $param->{'uploaded_files'} - or die "No files provided.\n"; - - my (%files) = map { /^(\w+):([\.\w]+)$/ ? ($1,$2):() } split /,/, $files; - - my $dir = '%%%FREESIDE_CACHE%%%/cache.'. $FS::UID::datasrc. '/'; - - my $file = $dir. $files{'file'}; - - my $type; - if ( $file =~ /\.(\w+)$/i ) { - $type = lc($1); - } else { - #or error out??? - warn "can't parse file type from filename $file; defaulting to CSV"; - $type = 'csv'; - } - - my $error = - FS::contact_import::batch_import( { - job => $job, - file => $file, - type => $type, - agentnum => $param->{'agentnum'}, - 'format' => $param->{'format'}, - } ); - - unlink $file; - - die "$error\n" if $error; - -} - -=item batch_import - -=cut - -my %formatfields = ( - 'default' => [ qw( custnum last first title comment selfservice_access emailaddress phonetypenum1 phonetypenum3 phonetypenum2 ) ], -); - -sub _formatfields { - \%formatfields; -} - -## not tested but maybe allow 2nd format to attach location in the future -my %import_options = ( - 'table' => 'contact', - - 'preinsert_callback' => sub { - my($record, $param) = @_; - my @location_params = grep /^location\./, keys %$param; - if (@location_params) { - my $cust_location = FS::cust_location->new({ - 'custnum' => $record->custnum, - }); - foreach my $p (@location_params) { - $p =~ /^location.(\w+)$/; - $cust_location->set($1, $param->{$p}); - } - - my $error = $cust_location->find_or_insert; # this avoids duplicates - return "error creating location: $error" if $error; - $record->set('locationnum', $cust_location->locationnum); - } - ''; - }, - -); - -sub _import_options { - \%import_options; -} - -sub batch_import { - my $opt = shift; - - my $iopt = _import_options; - $opt->{$_} = $iopt->{$_} foreach keys %$iopt; - - my $format = delete $opt->{'format'}; - - my $formatfields = _formatfields(); - die "unknown format $format" unless $formatfields->{$format}; - - my @fields; - foreach my $field ( @{ $formatfields->{$format} } ) { - push @fields, $field; - } - - $opt->{'fields'} = \@fields; - - FS::Record::batch_import( $opt ); - -} - -=head1 BUGS - -Not enough documentation. - -=head1 SEE ALSO - -L - -=cut - -1; \ No newline at end of file diff --git a/FS/FS/cust_main/Search.pm b/FS/FS/cust_main/Search.pm index 27559d73d..e8b1bde15 100644 --- a/FS/FS/cust_main/Search.pm +++ b/FS/FS/cust_main/Search.pm @@ -93,7 +93,7 @@ sub smart_search { my $phonenum = "$1$2$3"; #my $extension = $4; - #cust_main phone numbers + #cust_main phone numbers and contact phone number push @cust_main, qsearch( { 'table' => 'cust_main', 'hashref' => { %options }, @@ -102,20 +102,12 @@ sub smart_search { join(' OR ', map "$_ = '$phonen'", qw( daytime night mobile fax ) ). + " OR phonenum = '$phonenum' ". ' ) '. " AND $agentnums_sql", #agent virtualization + 'addl_from' => ' left join cust_contact using (custnum) left join contact_phone using (contactnum) ', } ); - #contact phone numbers - push @cust_main, - grep $agentnums_href->{$_->agentnum}, #agent virt - grep $_, #skip contacts that don't have cust_main records - map $_->contact->cust_main, - qsearch({ - 'table' => 'contact_phone', - 'hashref' => { 'phonenum' => $phonenum }, - }); - unless ( @cust_main || $phonen =~ /x\d+$/ ) { #no exact match #try looking for matches with extensions unless one was specified @@ -136,28 +128,20 @@ sub smart_search { } - if ( $search =~ /@/ ) { #email address - - # invoicing email address - push @cust_main, - grep $agentnums_href->{$_->agentnum}, #agent virt - map $_->cust_main, - qsearch( { - 'table' => 'cust_main_invoice', - 'hashref' => { 'dest' => $search }, - } - ); - - # contact email address - push @cust_main, - grep $agentnums_href->{$_->agentnum}, #agent virt - grep $_, #skip contacts that don't have cust_main records - map $_->contact->cust_main, - qsearch( { - 'table' => 'contact_email', - 'hashref' => { 'emailaddress' => $search }, - } - ); + if ( $search =~ /@/ ) { #email address from cust_main_invoice and contact_email + + push @cust_main, qsearch( { + 'table' => 'cust_main', + 'hashref' => { %options }, + 'extra_sql' => ( scalar(keys %options) ? ' AND ' : ' WHERE ' ). + ' ( '. + join(' OR ', map "$_ = '$search'", + qw( dest emailaddress ) + ). + ' ) '. + " AND $agentnums_sql", #agent virtualization + 'addl_from' => ' left join cust_main_invoice using (custnum) left join cust_contact using (custnum) left join contact_email using (contactnum) ', + } ); # custnum search (also try agent_custid), with some tweaking options if your # legacy cust "numbers" have letters @@ -281,8 +265,8 @@ sub smart_search { } elsif ( ! $NameParse->parse($value) ) { my %name = $NameParse->components; - $first = $name{'given_name_1'} || $name{'initials_1'}; #wtf NameParse, Ed? - $last = $name{'surname_1'}; + $first = lc($name{'given_name_1'}) || $name{'initials_1'}; #wtf NameParse, Ed? + $last = lc($name{'surname_1'}); } @@ -292,28 +276,18 @@ sub smart_search { #exact my $sql = scalar(keys %options) ? ' AND ' : ' WHERE '; - $sql .= "( LOWER(cust_main.last) = $q_last AND LOWER(cust_main.first) = $q_first )"; + $sql .= "( (LOWER(cust_main.last) = $q_last AND LOWER(cust_main.first) = $q_first) + OR (LOWER(contact.last) = $q_last AND LOWER(contact.first) = $q_first) )"; - #cust_main + #cust_main and contacts push @cust_main, qsearch( { 'table' => 'cust_main', - 'hashref' => \%options, + 'select' => 'cust_main.*, cust_contact.*, contact.contactnum, contact.last as contact_last, contact.first as contact_first, contact.title', + 'hashref' => { %options }, 'extra_sql' => "$sql AND $agentnums_sql", #agent virtualization + 'addl_from' => ' left join cust_contact on cust_main.custnum = cust_contact.custnum left join contact using (contactnum) ', } ); - #contacts - push @cust_main, - grep $agentnums_href->{$_->agentnum}, #agent virt - grep $_, #skip contacts that don't have cust_main records - map $_->cust_main, - qsearch( { - 'table' => 'contact', - 'hashref' => { 'first' => $first, - 'last' => $last, - }, - } - ); - # or it just be something that was typed in... (try that in a sec) } @@ -326,7 +300,9 @@ sub smart_search { OR LOWER(cust_main.last) = $q_value OR LOWER(cust_main.company) = $q_value OR LOWER(cust_main.ship_company) = $q_value - "; + OR LOWER(contact.first) = $q_value + OR LOWER(contact.last) = $q_value + )"; #address1 (yes, it's a kludge) $sql .= " OR EXISTS ( @@ -336,20 +312,12 @@ sub smart_search { )" if $conf->exists('address1-search'); - #contacts (look, another kludge) - $sql .= " OR EXISTS ( SELECT 1 FROM contact - WHERE ( LOWER(contact.first) = $q_value - OR LOWER(contact.last) = $q_value - ) - AND contact.custnum IS NOT NULL - AND contact.custnum = cust_main.custnum - ) - ) "; - push @cust_main, qsearch( { 'table' => 'cust_main', - 'hashref' => \%options, + 'select' => 'cust_main.*, cust_contact.*, contact.contactnum, contact.last as contact_last, contact.first as contact_first, contact.title', + 'hashref' => { %options }, 'extra_sql' => "$sql AND $agentnums_sql", #agent virtualization + 'addl_from' => 'left join cust_contact on cust_main.custnum = cust_contact.custnum left join contact using (contactnum) ', } ); #no exact match, trying substring/fuzzy diff --git a/FS/MANIFEST b/FS/MANIFEST index 10f0cc221..81087dea7 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -510,6 +510,7 @@ t/class_Common.t FS/category_Common.pm t/category_Common.t FS/contact.pm +FS/contact/Import.pm t/contact.t FS/contact_phone.pm t/contact_phone.t diff --git a/Makefile b/Makefile index c6eef91b0..a1e4566a2 100644 --- a/Makefile +++ b/Makefile @@ -236,7 +236,7 @@ perl-modules: " blib/lib/FS/part_export/*.pm;\ perl -p -i -e "\ s|%%%FREESIDE_CACHE%%%|${FREESIDE_CACHE}|g;\ - " blib/lib/FS/cust_main/*.pm blib/lib/FS/cust_pkg/*.pm;\ + " blib/lib/FS/cust_main/*.pm blib/lib/FS/cust_pkg/*.pm blib/lib/FS/contact/*.pm;\ perl -p -i -e "\ s|%%%FREESIDE_LOG%%%|${FREESIDE_LOG}|g;\ " blib/lib/FS/Daemon/*.pm;\ diff --git a/httemplate/elements/searchbar-cust_main.html b/httemplate/elements/searchbar-cust_main.html index 5bfef484a..7f8f9d850 100644 --- a/httemplate/elements/searchbar-cust_main.html +++ b/httemplate/elements/searchbar-cust_main.html @@ -26,7 +26,7 @@ my $curuser = $FS::CurrentUser::CurrentUser; my $menu_position = $curuser->option('menu_position') || 'top'; my $cust_width = 246; -my $cust_label = '(cust #, name, company'; +my $cust_label = '(cust #, name, company, email'; if ( $conf->exists('address1-search') ) { $cust_label .= ', address'; $cust_width += 56; diff --git a/httemplate/misc/process/contact-import.cgi b/httemplate/misc/process/contact-import.cgi index cbdcad455..108ee93e9 100644 --- a/httemplate/misc/process/contact-import.cgi +++ b/httemplate/misc/process/contact-import.cgi @@ -5,6 +5,6 @@ die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('Import'); my $server = - new FS::UI::Web::JSRPC 'FS::contact_import::process_batch_import', $cgi; + new FS::UI::Web::JSRPC 'FS::contact::Import::process_batch_import', $cgi; diff --git a/httemplate/search/contact.html b/httemplate/search/contact.html index 759f09521..1615dfab2 100644 --- a/httemplate/search/contact.html +++ b/httemplate/search/contact.html @@ -29,6 +29,27 @@ my $email_sub = sub { join(', ', map $_->emailaddress, @contact_email); }; +my $work_phone_sub = sub { + my $contact = shift; + #can't because contactnum is in the wrong field + my @contact_workphone = qsearch('contact_phone', { 'contactnum' => $contact->contact_contactnum, 'phonetypenum' => '1' } ); + join(', ', map $_->phonenum, @contact_workphone); +}; + +my $mobile_phone_sub = sub { + my $contact = shift; + #can't because contactnum is in the wrong field + my @contact_mobilephone = qsearch('contact_phone', { 'contactnum' => $contact->contact_contactnum, 'phonetypenum' => '3' } ); + join(', ', map $_->phonenum, @contact_mobilephone); +}; + +my $home_phone_sub = sub { + my $contact = shift; + #can't because contactnum is in the wrong field + my @contact_homephone = qsearch('contact_phone', { 'contactnum' => $contact->contact_contactnum, 'phonetypenum' => '2' } ); + join(', ', map $_->phonenum, @contact_homephone); +}; + my $link; #for closure in this sub, we'll define it later my $contact_classname_sub = sub { my $contact = shift; @@ -44,9 +65,9 @@ my $contact_classname_sub = sub { $X_contact->contact_classname; }; -my @header = ( 'First', 'Last', 'Title', 'Email', 'Type' ); -my @fields = ( 'first', 'last', 'title', $email_sub, $contact_classname_sub ); -my @links = ( '', '', '', '', '', ); +my @header = ( 'First', 'Last', 'Title', 'Email', 'Work Phone', 'Mobile Phone', 'Home Phone', 'Type' ); +my @fields = ( 'first', 'last', 'title', $email_sub, $work_phone_sub, $mobile_phone_sub, $home_phone_sub, $contact_classname_sub ); +my @links = ( '', '', '', '', '', '', '', '', ); my $company_link = '';