From: Christopher Burger Date: Thu, 7 Sep 2017 12:49:34 +0000 (-0400) Subject: RT# 77167 - Added the ability to import a list of contacts X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=0c8c33892661b2774fd29423e3791d08a257d0fe RT# 77167 - Added the ability to import a list of contacts --- diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm index 956ea6210..7bdb6059e 100644 --- a/FS/FS/Mason.pm +++ b/FS/FS/Mason.pm @@ -262,6 +262,7 @@ if ( -e $addl_handler_use_file ) { use FS::cust_category; use FS::prospect_main; use FS::contact; + use FS::contact::Import; use FS::phone_type; use FS::svc_pbx; use FS::discount; diff --git a/FS/FS/contact/Import.pm b/FS/FS/contact/Import.pm new file mode 100644 index 000000000..26bdcfa6e --- /dev/null +++ b/FS/FS/contact/Import.pm @@ -0,0 +1,161 @@ +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 $dir = '/usr/local/etc/freeside/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/contact_import.pm b/FS/FS/contact_import.pm new file mode 100644 index 000000000..d6cd690bf --- /dev/null +++ b/FS/FS/contact_import.pm @@ -0,0 +1,161 @@ +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/httemplate/elements/menu.html b/httemplate/elements/menu.html index d96309449..854145628 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -516,6 +516,7 @@ tie my %tools_importing, 'Tie::IxHash', 'Package definitions' => [ $fsurl.'misc/part_pkg-import.html', '' ], 'Customer packages' => [ $fsurl.'misc/cust_pkg-import.html', '' ], 'Customer notes' => [ $fsurl.'misc/cust_main_note-import.html', '' ], + 'Customer Contacts' => [ $fsurl.'misc/contact-import.cgi', '' ], 'One-time charges' => [ $fsurl.'misc/cust_main-import_charges.cgi', '' ], 'Payments' => [ $fsurl.'misc/cust_pay-import.cgi', '' ], 'Credits' => [ $fsurl.'misc/cust_credit-import.html', '' ], diff --git a/httemplate/misc/contact-import.cgi b/httemplate/misc/contact-import.cgi new file mode 100644 index 000000000..ae2e3494f --- /dev/null +++ b/httemplate/misc/contact-import.cgi @@ -0,0 +1,102 @@ +<% include("/elements/header.html",'Batch Contacts Import') %> + +Import a file containing customer contact records. +

+ +<& /elements/form-file_upload.html, + 'name' => 'ContactImportForm', + 'action' => 'process/contact-import.cgi', + 'num_files' => 1, + 'fields' => [ 'custbatch', 'format' ], + 'message' => 'Customer contacts import successful', + 'onsubmit' => "document.ContactImportForm.submitButton.disabled=true;", +&> + +<% &ntable("#cccccc", 2) %> + + + + + Format + + + + + + <% include( '/elements/file-upload.html', + 'field' => 'file', + 'label' => 'Filename', + ) + %> + + + + + + + + + + + +
+ +Uploaded files can be CSV (comma-separated value) files or Excel spreadsheets. The file should have a .CSV or .XLS extension. +

+ +Default Format has the following field order: +
+custnum<%$req%>, last<%$req%>, first<%$req%>, title<%$req%>, comment, selfservice_access, emailaddress, workphone, mobilephone, homephone +

+ +Field information: +
+You must include a customer number and either a last name, first name or title. + +
    + +
  • custnum: This is the customer number of the customer the contact is attached to.
  • + +
  • last: Last name for contact.
  • + +
  • first: First name for contact.
  • + +
  • title: Optional title for contact.
  • + +
  • comment: Optional comment for contact.
  • + +
  • selfservice_access: Empty for no self service access or Y if granting self service access.
  • + +
  • emailaddress: Email address for contact.
  • + +
  • workphone: Work phone number for contact. Format xxxxxxxxxx
  • + +
  • mobilephone: Mobile phone number for contact. Format xxxxxxxxxx
  • + +
  • homephone: Home phone number for contact. Format xxxxxxxxxx
  • + +
+ +
+ +<% include('/elements/footer.html') %> + +<%once> + +my $req = qq!*!; + + +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Import'); + +my $custbatch = time2str('webimport-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time); + + \ No newline at end of file diff --git a/httemplate/misc/process/contact-import.cgi b/httemplate/misc/process/contact-import.cgi new file mode 100644 index 000000000..cbdcad455 --- /dev/null +++ b/httemplate/misc/process/contact-import.cgi @@ -0,0 +1,10 @@ +<% $server->process %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Import'); + +my $server = + new FS::UI::Web::JSRPC 'FS::contact_import::process_batch_import', $cgi; + +