'custnum', 'serial', '', '', '', '',
'agentnum', 'int', '', '', '', '',
'agent_custid', 'varchar', 'NULL', $char_d, '', '',
+ 'custbatch', 'varchar', 'NULL', $char_d, '', '',
# 'titlenum', 'int', 'NULL', '', '', '',
'last', 'varchar', '', $char_d, '', '',
# 'middle', 'varchar', 'NULL', $char_d, '', '',
[ 'ship_last' ], [ 'ship_company' ],
[ 'ship_daytime' ], [ 'ship_night' ], [ 'ship_fax' ],
[ 'payby' ], [ 'paydate' ],
+ [ 'agentnum' ], [ 'custbatch' ],
],
},
use strict;
use vars qw(
@ISA @EXPORT_OK $DEBUG $me $cgi $dbh $freeside_uid $user
- $conf_dir $secrets $datasrc $db_user $db_pass %callback @callback
+ $conf_dir $cache_dir $secrets $datasrc $db_user $db_pass %callback @callback
$driver_name $AutoCommit $callback_hack $use_confcompat
);
use subs qw(
$freeside_uid = scalar(getpwnam('freeside'));
-$conf_dir = "%%%FREESIDE_CONF%%%";
+$conf_dir = "%%%FREESIDE_CONF%%%";
+$cache_dir = "%%%FREESIDE_CACHE%%%";
$AutoCommit = 1; #ours, not DBI
$use_confcompat = 1;
|| $self->ut_number('agentnum')
|| $self->ut_textn('agent_custid')
|| $self->ut_number('refnum')
+ || $self->ut_textn('custbatch')
|| $self->ut_name('last')
|| $self->ut_name('first')
|| $self->ut_snumbern('birthdate')
push @where, map { s/current_balance/$balance_sql/; $_ }
@{ $params->{'current_balance'} };
+ ##
+ # custbatch
+ ##
+
+ if ( $params->{'custbatch'} =~ /^([\w\/\-\:\.]+)$/ and $1 ) {
+ push @where,
+ "cust_main.custbatch = '$1'";
+ }
+
##
# setup queries, subs, etc. for the search
##
1;
}
+=item process_batch_import
+
+Load a batch import as a queued JSRPC job
+
+=cut
+
+use Storable qw(thaw);
+use Data::Dumper;
+use MIME::Base64;
+sub process_batch_import {
+ my $job = shift;
+
+ my $param = thaw(decode_base64(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::cust_main::batch_import( {
+ job => $job,
+ file => $file,
+ type => $type,
+ custbatch => $param->{custbatch},
+ agentnum => $param->{'agentnum'},
+ refnum => $param->{'refnum'},
+ pkgpart => $param->{'pkgpart'},
+ #'fields' => [qw( cust_pkg.setup dayphone first last address1 address2
+ # city state zip comments )],
+ 'format' => $param->{'format'},
+ } );
+
+ unlink $file;
+
+ die "$error\n" if $error;
+
+}
+
=item batch_import
=cut
sub batch_import {
my $param = shift;
- my $fh = $param->{filehandle};
- my $type = $param->{type} || 'csv';
+ my $job = $param->{job};
+
+ my $filename = $param->{file};
+ my $type = $param->{type} || 'csv';
+
+ my $custbatch = $param->{custbatch};
- my $agentnum = $param->{agentnum};
- my $refnum = $param->{refnum};
- my $pkgpart = $param->{pkgpart};
+ my $agentnum = $param->{agentnum};
+ my $refnum = $param->{refnum};
+ my $pkgpart = $param->{pkgpart};
- my $format = $param->{'format'};
+ my $format = $param->{'format'};
my @fields;
my $payby;
die "unknown format $format";
}
+ my $count;
my $parser;
- my $spoolfile = '';
+ my @buffer = ();
if ( $type eq 'csv' ) {
+
eval "use Text::CSV_XS;";
die $@ if $@;
+
$parser = new Text::CSV_XS;
+
+ @buffer = split(/\r?\n/, slurp($filename) );
+ $count = scalar(@buffer);
+
} elsif ( $type eq 'xls' ) {
eval "use Spreadsheet::ParseExcel;";
die $@ if $@;
- ( my $spool_fh, $spoolfile ) =
- tempfile('cust_main-batch_import-XXXXXXXXXXXX',
- DIR => '%%%FREESIDE_CACHE%%%',
- SUFFIX => '.xls',
- );
- print $spool_fh slurp($fh);
- close $spool_fh or die $!;
-
- my $excel = new Spreadsheet::ParseExcel::Workbook->Parse($spoolfile);
+ my $excel = new Spreadsheet::ParseExcel::Workbook->Parse($filename);
$parser = $excel->{Worksheet}[0]; #first sheet
+ $count = $parser->{MaxRow} || $parser->{MinRow};
+ $count++;
+
} else {
die "Unknown file type $type\n";
}
my $line;
my $row = 0;
+ my( $last, $min_sec ) = ( time, 5 ); #progressbar foo
while (1) {
my @columns = ();
if ( $type eq 'csv' ) {
- last unless defined($line=<$fh>);
+ last unless scalar(@buffer);
+ $line = shift(@buffer);
$parser->parse($line) or do {
$dbh->rollback if $oldAutoCommit;
#warn join('-',@columns);
my %cust_main = (
- agentnum => $agentnum,
- refnum => $refnum,
- country => $conf->config('countrydefault') || 'US',
- payby => $payby, #default
- paydate => '12/2037', #default
+ custbatch => $custbatch,
+ agentnum => $agentnum,
+ refnum => $refnum,
+ country => $conf->config('countrydefault') || 'US',
+ payby => $payby, #default
+ paydate => '12/2037', #default
);
my $billtime = time;
my %cust_pkg = ( pkgpart => $pkgpart );
}
}
- $cust_main{'payby'} = 'CARD' if length($cust_main{'payinfo'});
+ $cust_main{'payby'} = 'CARD'
+ if defined $cust_main{'payinfo'}
+ && length $cust_main{'payinfo'};
my $invoicing_list = $cust_main{'invoicing_list'}
? [ delete $cust_main{'invoicing_list'} ]
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
- return "can't insert customer ". ( $line ? "for $line" : '' ). ": $error";
+ return "can't insert customer". ( $line ? " for $line" : '' ). ": $error";
}
if ( $format eq 'simple' ) {
}
$row++;
- }
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ if ( $job && time - $min_sec > $last ) { #progress bar
+ $job->update_statustext( int(100 * $row / $count) );
+ $last = time;
+ }
+
+ }
- unlink($spoolfile) if $spoolfile;
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;;
return "Empty file!" unless $row;
-
-<script type="text/javascript">
+<SCRIPT TYPE="text/javascript">
function doUpload(form, callback) {
var name = 'form' + Math.floor(Math.random() * 99999); // perlize?
var d = document.createElement('DIV');
- d.innerHTML = '<iframe style="display:none" src="about:blank" id="'+name+'" name="'+name+'" onload="uploadComplete(\''+name+'\')"></iframe>';
+ d.innerHTML = '<iframe style="display:none" src="about:blank" ' +
+ 'id="' + name + '" ' +
+ 'name="' + name + '" ' +
+ 'onload="uploadComplete(\'' + name + '\')">' +
+ '</iframe>';
document.body.appendChild(d);
var i = document.getElementById(name);
document.getElementById('r').innerHTML = d.body.innerHTML;
if (typeof(i.onComplete) == 'function') {
var p;
- if (p = d.body.innerHTML.indexOf("Freeside File Upload Successful ") >= 0) {
- var v = d.body.innerHTML.substr(p+33)
+ if (p = d.body.innerHTML.indexOf("File Upload Successful ") >= 0) {
+ var v = d.body.innerHTML.substr(p+24);
var u = document.getElementById('uploaded_files');
v = v.substr(0, v.indexOf(';'));
u.value = v;
}
}
-</script>
+</SCRIPT>
+
+<INPUT TYPE="hidden" NAME="uploaded_files" ID="uploaded_files" VALUE="" />
+
+<INPUT TYPE="hidden" NAME="upload_fields" VALUE="<% join(',', @field) %>" />
- <input type="hidden" name="uploaded_files" id="uploaded_files" value="" />
- <input type="hidden" name="upload_fields" value="<% join(',', @field) %>" />
% foreach (@field) {
- <tr>
- <th><% shift @label %></th>
- <td><input type="file" name="<% $_ %>" /></td>
- </tr>
+ <TR>
+ <TH><% shift @label %></TH>
+ <TD><INPUT TYPE="file" NAME="<% $_ %>" /></TD>
+ </TR>
% }
- <div style="display:<% $debug ? 'visible' : 'none' %>">Debugging: <pre id="r"></pre></div>
-
-<%init>
-my %param = @_;
-my $debug = $param{'debug'};
+<DIV STYLE="display:<% $param{debug} ? 'visible' : 'none' %>">
+ Debugging: <PRE ID="r"></PRE>
+</DIV>
-my $callback = $param{'callback'} || "''";
+<%init>
-my @label = ();
-if ( ref($param{'label'}) ) {
- push @label, @{$param{'label'}};
-}else{
- push @label, $param{'label'};
-}
+my %param = @_;
-my @field = ();
-if ( ref($param{'field'}) ) {
- push @field, @{$param{'field'}};
-}else{
- push @field, $param{'field'};
-}
+my @label = ref($param{'label'}) ? @{$param{'label'}} : ($param{'label'});
+my @field = ref($param{'field'}) ? @{$param{'field'}} : ($param{'field'});
</%init>
--- /dev/null
+<%doc>
+
+Example:
+
+ <% include( '/elements/form-file_upload.html',
+
+ 'name' => 'form_name',
+ 'action' => 'process/target.cgi', #progress-init target
+ 'fields' => [ 'other', 'form', 'fields' ],
+ 'num_files' => 1, #or more
+
+ 'url' => $url
+ #OR
+ 'message' => 'Message',
+
+ #optional
+ 'key' => 'unique_key', #for using more than once on a page
+ )
+
+% #...
+
+% # num_files=>1
+ include( '/elements/file-upload.html',
+ 'field' => 'element',
+ 'label' => 'Label',
+ )
+
+% # OR
+
+% # num_files=>2 # or more
+ include( '/elements/file-upload.html',
+ 'field' => [ 'element', 'element2', ], #etc.
+ 'label' => [ 'Label', 'Label2', ], #etc.
+ )
+
+
+%>
+
+</%doc>
+
+<% include( '/elements/progress-init.html',
+ $opt{name},
+ $opt{fields},
+ $opt{action},
+ $msg_or_url,
+ $opt{key},
+ )
+%>
+
+<SCRIPT>
+
+ function <% $opt{key} %>gotUploaded(success, message) {
+
+ var uploaded = document.getElementById('uploaded_files');
+ var a = uploaded.value.split(',');
+ if (uploaded.value.split(',').length == <% $opt{num_files} %>){
+ process();
+ }else{
+ var p = document.getElementById('uploadError');
+ p.innerHTML='<FONT SIZE="+1" COLOR="#ff0000">Error: '+message+'</FONT><BR><BR>';
+ p.style='display:visible';
+ return false;
+ }
+
+ }
+
+</SCRIPT>
+
+<div style="display:none:" id="uploadError"></div>
+
+<FORM NAME = "<% $opt{name} %>"
+ ACTION = "<% $fsurl %>misc/file-upload.html"
+ METHOD = "POST"
+ ENCTYPE = "multipart/form-data"
+ onSubmit = "return doUpload(this, <% $opt{key} %>gotUploaded)"
+>
+
+<%init>
+
+#my( $formname, $fields, $action, $url_or_message, $key ) = @_;
+my %opt = ref($_[0]) ? %{ $_[0] } : @_;
+
+my $key = exists $opt{key} ? $opt{key} : '';
+
+push @{ $opt{fields} }, 'uploaded_files';
+
+my $msg_or_url = $opt{message}
+ ? { 'message' => $opt{message},
+ 'url' => $opt{url},
+ }
+ : $opt{url};
+
+</%init>
my $url_or_message_link;
if ( ref($url_or_message) ) { #its a message or something
- $url_or_message_link =
- 'message='. uri_escape( $url_or_message->{'message'} )
+ $url_or_message_link = 'message='. uri_escape( $url_or_message->{'message'} );
+ $url_or_message_link .= ';url='. uri_escape( $url_or_message->{'url'} )
+ if $url_or_message->{'url'};
} else {
$url_or_message_link = "url=$url_or_message";
}
//jsrsExecute( '<%$p%>elements/jsrsServer.html', updateStatus, 'job_status', '<% $jobnum %>' );
job_status( '<% $jobnum %>', updateStatus );
} else if ( status.indexOf('complete') > -1 ) {
-% if ( $message ) {
+% if ( $message ) {
+%
+% my $onClick = $url
+% ? "window.top.location.href = \\'$url\\';"
+% : 'parent.nd(1);';
document.getElementById("progress_message").innerHTML = "<% $message %>";
document.getElementById("progress_bar").innerHTML = '';
- document.getElementById("progress_percent").innerHTML = '<INPUT TYPE="button" VALUE="OK" onClick="parent.nd(1);">';
+ document.getElementById("progress_percent").innerHTML =
+ '<INPUT TYPE="button" VALUE="OK" onClick="<% $onClick %>">';
document.getElementById("progress_jobnum").innerHTML = '';
- if ( parent.document.<%$formname%>.submit.disabled == true ) {
- parent.document.<%$formname%>.submit.disabled=false;
- }
+
+% unless ( $url ) {
+ if ( parent.document.<%$formname%>.submit.disabled == true ) {
+ parent.document.<%$formname%>.submit.disabled=false;
+ }
+% }
+
% } elsif ( $url ) {
window.top.location.href = '<% $url %>';
Import a file containing customer records.
<BR><BR>
-<FORM ACTION="process/cust_main-import.cgi" METHOD="post" ENCTYPE="multipart/form-data">
+<% include( '/elements/form-file_upload.html',
+ 'name' => 'CustomerImportForm',
+ 'action' => 'process/cust_main-import.cgi',
+ 'num_files' => 1,
+ 'fields' => [ 'agentnum', 'custbatch', 'format' ],
+ 'message' => 'Customer import successful',
+ 'url' => $p."search/cust_main.html?custbatch=$custbatch",
+ )
+%>
<% &ntable("#cccccc", 2) %>
-<% include('/elements/tr-select-agent.html',
- #'curr_value' => '', #$agentnum,
- 'label' => "<B>Agent</B>",
- 'empty_label' => 'Select agent',
- )
-%>
+ <% include( '/elements/tr-select-agent.html',
+ #'curr_value' => '', #$agentnum,
+ 'label' => "<B>Agent</B>",
+ 'empty_label' => 'Select agent',
+ )
+ %>
+
+ <INPUT TYPE="hidden" NAME="custbatch" VALUE="<% $custbatch %>"%>
+
+ <TR>
+ <TH ALIGN="right">Format</TH>
+ <TD>
+ <SELECT NAME="format">
+ <!-- <OPTION VALUE="simple">Simple -->
+ <OPTION VALUE="extended" SELECTED>Extended
+ <OPTION VALUE="extended-plus_company">Extended plus company
+ </SELECT>
+ </TD>
+ </TR>
+
+ <% include( '/elements/file-upload.html',
+ 'field' => 'file',
+ 'label' => 'Filename',
+ )
+ %>
-<TR>
- <TH ALIGN="right">Format</TH>
- <TD>
- <SELECT NAME="format">
-<!-- <OPTION VALUE="simple">Simple -->
- <OPTION VALUE="extended" SELECTED>Extended
- <OPTION VALUE="extended-plus_company">Extended plus company
- </SELECT>
- </TD>
-</TR>
-<TR>
- <TH ALIGN="right">Filename</TH>
- <TD><INPUT TYPE="file" NAME="file"></TD>
-</TR>
% #include('/elements/tr-select-part_referral.html')
%
</TR>
-->
-<TR><TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px"><INPUT TYPE="submit" VALUE="Import file"></TD></TR>
+ <TR>
+ <TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px">
+ <INPUT TYPE = "submit"
+ ID = "submit"
+ VALUE = "Import file"
+ onClick = "document.CustomerImportForm.submit.disabled=true;"
+ >
+ </TD>
+ </TR>
</TABLE>
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Import');
+my $custbatch = time2str('webimport-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time);
+
</%init>
% if ($error) {
Error: <% $error %>
% }else{
-Freeside File Upload Successful <% join(',', @filenames) %>;
+File Upload Successful <% join(',', @filenames) %>;
% }
<% include('/elements/footer.html') %>
<%init>
or $error = "invalid upload_fields";
my $fields = $1;
-my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
+my $dir = $FS::UID::cache_dir. "/cache.". $FS::UID::datasrc;
foreach my $field (split /,/, $fields) {
next if $error;
my $fh = $cgi->upload($field)
or $error = "No valid file was provided.";
+ my $suffix = '';
+ if ( $cgi->param($field) =~ /(\.\w+)$/i ) {
+ $suffix = lc($1);
+ }
+
my $sh = new File::Temp( TEMPLATE => 'upload.XXXXXXXX',
+ SUFFIX => $suffix,
DIR => $dir,
UNLINK => 0,
)
-% if ( $error ) {
-% errorpage($error);
-% } else {
- <% include('/elements/header.html','Import successful') %>
- <% include('/elements/footer.html') %>
-% }
+<% $server->process %>
<%init>
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Import');
-my $fh = $cgi->upload('file');
-my $error = '';
-if ( defined($fh) ) {
-
- my $type;
- if ( $cgi->param('file') =~ /\.(\w+)$/i ) {
- $type = lc($1);
- } else {
- #or error out???
- warn "can't parse file type from filename ". $cgi->param('file').
- '; defaulting to CSV';
- $type = 'csv';
- }
-
- $error =
- FS::cust_main::batch_import( {
- filehandle => $fh,
- type => $type,
- agentnum => scalar($cgi->param('agentnum')),
- refnum => scalar($cgi->param('refnum')),
- pkgpart => scalar($cgi->param('pkgpart')),
- #'fields' => [qw( cust_pkg.setup dayphone first last address1 address2
- # city state zip comments )],
- 'format' => scalar($cgi->param('format')),
- } );
-
-} else {
- $error = 'No file';
-}
+my $server = new FS::UI::Web::JSRPC 'FS::cust_main::process_batch_import', $cgi;
</%init>
#$search_hash{'query'} = $cgi->keywords;
#scalars
-for my $param (qw( agentnum status cancelled_pkgs cust_fields flattened_pkgs)) {
+for my $param (qw(
+ agentnum status cancelled_pkgs cust_fields flattened_pkgs custbatch
+)) {
$search_hash{$param} = scalar( $cgi->param($param) )
if $cgi->param($param);
}