+=cut
+
+sub process_batch_import {
+ my $job = shift;
+
+ my $hashcb = sub {
+ my %hash = @_;
+ my $custnum = $hash{'custnum'};
+ my $agentnum = $hash{'agentnum'};
+ my $agent_custid = $hash{'agent_custid'};
+ #standardize date
+ $hash{'_date'} = parse_datetime($hash{'_date'})
+ if $hash{'_date'} && $hash{'_date'} =~ /\D/;
+ #remove custnum_prefix
+ my $custnum_prefix = $conf->config('cust_main-custnum-display_prefix');
+ my $custnum_length = $conf->config('cust_main-custnum-display_length') || 8;
+ if (
+ $custnum_prefix
+ && $custnum =~ /^$custnum_prefix(0*([1-9]\d*))$/
+ && length($1) == $custnum_length
+ ) {
+ $custnum = $2;
+ }
+ # check agentnum against custnum and
+ # translate agent_custid into regular custnum
+ if ($custnum && $agent_custid) {
+ die "can't specify both custnum and agent_custid\n";
+ } elsif ($agentnum || $agent_custid) {
+ # here is the agent virtualization
+ my $extra_sql = ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql;
+ my %search;
+ $search{'agentnum'} = $agentnum
+ if $agentnum;
+ $search{'agent_custid'} = $agent_custid
+ if $agent_custid;
+ $search{'custnum'} = $custnum
+ if $custnum;
+ my $cust_main = qsearchs({
+ 'table' => 'cust_main',
+ 'hashref' => \%search,
+ 'extra_sql' => $extra_sql,
+ });
+ die "can't find customer with" .
+ ($agentnum ? " agentnum $agentnum" : '') .
+ ($custnum ? " custnum $custnum" : '') .
+ ($agent_custid ? " agent_custid $agent_custid" : '') . "\n"
+ unless $cust_main;
+ die "mismatched customer number\n"
+ if $custnum && ($custnum ne $cust_main->custnum);
+ $custnum = $cust_main->custnum;
+ }
+ $hash{'custnum'} = $custnum;
+ delete($hash{'agent_custid'});
+ return %hash;
+ };
+
+ my $opt = {
+ 'table' => 'cust_pay',
+ 'params' => [ '_date', 'agentnum', 'payby', 'paybatch' ],
+ #agent_custid isn't a cust_pay field, see hash callback
+ 'formats' => { 'simple' =>
+ [ qw(custnum agent_custid paid payinfo invnum) ] },
+ 'format_types' => { 'simple' => '' }, #force infer from file extension
+ 'default_csv' => 1, #if not .xls, will read as csv, regardless of extension
+ 'format_hash_callbacks' => { 'simple' => $hashcb },
+ 'insert_args_callback' => sub { ( 'manual'=>1 ); },
+ 'postinsert_callback' => sub {
+ my $cust_pay = shift;
+ my $cust_main = $cust_pay->cust_main
+ or return "can't find customer to which payments apply";
+ my $error = $cust_main->apply_payments_and_credits( 'manual'=>1 );
+ return $error
+ ? "can't apply payments to customer ".$cust_pay->custnum."$error"
+ : '';
+ },
+ };
+
+ FS::Record::process_batch_import( $job, $opt, @_ );
+
+}
+
+=item batch_import HASHREF
+
+Inserts new payments.
+
+=cut
+
+sub batch_import {
+ my $param = shift;
+
+ my $fh = $param->{filehandle};
+ my $format = $param->{'format'};
+
+ my $agentnum = $param->{agentnum};
+ my $_date = $param->{_date};
+ $_date = parse_datetime($_date) if $_date && $_date =~ /\D/;
+ my $paybatch = $param->{'paybatch'};
+
+ my $custnum_prefix = $conf->config('cust_main-custnum-display_prefix');
+ my $custnum_length = $conf->config('cust_main-custnum-display_length') || 8;
+
+ # here is the agent virtualization
+ my $extra_sql = ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql;
+
+ my @fields;
+ my $payby;
+ if ( $format eq 'simple' ) {
+ @fields = qw( custnum agent_custid paid payinfo invnum );
+ $payby = 'BILL';
+ } elsif ( $format eq 'extended' ) {
+ die "unimplemented\n";
+ @fields = qw( );
+ $payby = 'BILL';
+ } else {
+ die "unknown format $format";
+ }
+
+ eval "use Text::CSV_XS;";
+ die $@ if $@;
+
+ my $csv = new Text::CSV_XS;
+
+ my $imported = 0;
+
+ 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;
+
+ 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();
+
+ my %cust_pay = (
+ payby => $payby,
+ paybatch => $paybatch,
+ );
+ $cust_pay{_date} = $_date if $_date;
+
+ my $cust_main;
+ foreach my $field ( @fields ) {
+
+ if ( $field eq 'agent_custid'
+ && $agentnum
+ && $columns[0] =~ /\S+/ )
+ {
+
+ my $agent_custid = $columns[0];
+ my %hash = ( 'agent_custid' => $agent_custid,
+ 'agentnum' => $agentnum,
+ );
+
+ if ( $cust_pay{'custnum'} !~ /^\s*$/ ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't specify custnum with agent_custid $agent_custid";
+ }
+
+ $cust_main = qsearchs({
+ 'table' => 'cust_main',
+ 'hashref' => \%hash,
+ 'extra_sql' => $extra_sql,
+ });
+
+ unless ( $cust_main ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't find customer with agent_custid $agent_custid";
+ }
+
+ $field = 'custnum';
+ $columns[0] = $cust_main->custnum;
+ }
+
+ $cust_pay{$field} = shift @columns;
+ }
+
+ if ( $custnum_prefix && $cust_pay{custnum} =~ /^$custnum_prefix(0*([1-9]\d*))$/
+ && length($1) == $custnum_length ) {
+ $cust_pay{custnum} = $2;
+ }
+
+ my $custnum = $cust_pay{custnum};
+
+ my $cust_pay = new FS::cust_pay( \%cust_pay );
+ my $error = $cust_pay->insert;
+
+ if ( ! $error && $cust_pay->custnum != $custnum ) {
+ #invnum was defined, and ->insert set custnum to the customer for that
+ #invoice, but it wasn't the one the import specified.
+ $dbh->rollback if $oldAutoCommit;
+ $error = "specified invoice #". $cust_pay{invnum}.
+ " is for custnum ". $cust_pay->custnum.
+ ", not specified custnum $custnum";
+ }
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't insert payment for $line: $error";
+ }
+
+ if ( $format eq 'simple' ) {
+ # include agentnum for less surprise?
+ $cust_main = qsearchs({
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $cust_pay->custnum },
+ 'extra_sql' => $extra_sql,
+ })
+ unless $cust_main;
+
+ unless ( $cust_main ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't find customer to which payments apply at line: $line";
+ }
+
+ $error = $cust_main->apply_payments_and_credits;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "can't apply payments to customer for $line: $error";
+ }
+
+ }
+
+ $imported++;
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ return "Empty file!" unless $imported;
+
+ ''; #no error
+
+}
+
+=back