+=item params
+
+=item job
+
+FS::queue object, will be updated with progress
+
+=item file
+
+=item type
+
+csv, xls, fixedlength, xml
+
+=item empty_ok
+
+=back
+
+=cut
+
+sub batch_import {
+ my $param = shift;
+
+ warn "$me batch_import call with params: \n". Dumper($param)
+ if $DEBUG;
+
+ my $table = $param->{table};
+
+ my $job = $param->{job};
+ my $file = $param->{file};
+ my $params = $param->{params} || {};
+
+ my $custnum_prefix = $conf->config('cust_main-custnum-display_prefix');
+ my $custnum_length = $conf->config('cust_main-custnum-display_length') || 8;
+
+ my( $type, $header, $sep_char,
+ $fixedlength_format, $xml_format, $asn_format,
+ $parser_opt, $row_callback, @fields );
+
+ my $postinsert_callback = '';
+ $postinsert_callback = $param->{'postinsert_callback'}
+ if $param->{'postinsert_callback'};
+ my $preinsert_callback = '';
+ $preinsert_callback = $param->{'preinsert_callback'}
+ if $param->{'preinsert_callback'};
+ my $insert_args_callback = '';
+ $insert_args_callback = $param->{'insert_args_callback'}
+ if $param->{'insert_args_callback'};
+
+ if ( $param->{'format'} ) {
+
+ my $format = $param->{'format'};
+ my $formats = $param->{formats};
+ die "unknown format $format" unless exists $formats->{ $format };
+
+ $type = $param->{'format_types'}
+ ? $param->{'format_types'}{ $format }
+ : $param->{type} || 'csv';
+
+
+ $header = $param->{'format_headers'}
+ ? $param->{'format_headers'}{ $param->{'format'} }
+ : 0;
+
+ $sep_char = $param->{'format_sep_chars'}
+ ? $param->{'format_sep_chars'}{ $param->{'format'} }
+ : ',';
+
+ $fixedlength_format =
+ $param->{'format_fixedlength_formats'}
+ ? $param->{'format_fixedlength_formats'}{ $param->{'format'} }
+ : '';
+
+ $parser_opt =
+ $param->{'format_parser_opts'}
+ ? $param->{'format_parser_opts'}{ $param->{'format'} }
+ : {};
+
+ $xml_format =
+ $param->{'format_xml_formats'}
+ ? $param->{'format_xml_formats'}{ $param->{'format'} }
+ : '';
+
+ $asn_format =
+ $param->{'format_asn_formats'}
+ ? $param->{'format_asn_formats'}{ $param->{'format'} }
+ : '';
+
+ $row_callback =
+ $param->{'format_row_callbacks'}
+ ? $param->{'format_row_callbacks'}{ $param->{'format'} }
+ : '';
+
+ @fields = @{ $formats->{ $format } };
+
+ } elsif ( $param->{'fields'} ) {
+
+ $type = ''; #infer from filename
+ $header = 0;
+ $sep_char = ',';
+ $fixedlength_format = '';
+ $row_callback = '';
+ @fields = @{ $param->{'fields'} };
+
+ } else {
+ die "neither format nor fields specified";
+ }
+
+ #my $file = $param->{file};
+
+ unless ( $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';
+ }
+ $type = 'csv'
+ if $param->{'default_csv'} && $type ne 'xls';
+ }
+
+
+ my $row = 0;
+ my $count;
+ my $parser;
+ my @buffer = ();
+ my $asn_header_buffer;
+ if ( $type eq 'csv' || $type eq 'fixedlength' ) {
+
+ if ( $type eq 'csv' ) {
+
+ $parser_opt->{'binary'} = 1;
+ $parser_opt->{'sep_char'} = $sep_char if $sep_char;
+ $parser = Text::CSV_XS->new($parser_opt);
+
+ } elsif ( $type eq 'fixedlength' ) {
+
+ eval "use Parse::FixedLength;";
+ die $@ if $@;
+ $parser = Parse::FixedLength->new($fixedlength_format, $parser_opt);
+
+ } else {
+ die "Unknown file type $type\n";
+ }
+
+ @buffer = split(/\r?\n/, slurp($file) );
+ splice(@buffer, 0, ($header || 0) );
+ $count = scalar(@buffer);
+
+ } elsif ( $type eq 'xls' ) {
+
+ eval "use Spreadsheet::ParseExcel;";
+ die $@ if $@;
+
+ eval "use DateTime::Format::Excel;";
+ #for now, just let the error be thrown if it is used, since only CDR
+ # formats bill_west and troop use it, not other excel-parsing things
+ #die $@ if $@;
+
+ my $excel = Spreadsheet::ParseExcel::Workbook->new->Parse($file);
+
+ $parser = $excel->{Worksheet}[0]; #first sheet
+
+ $count = $parser->{MaxRow} || $parser->{MinRow};
+ $count++;
+
+ $row = $header || 0;
+
+ } elsif ( $type eq 'xml' ) {
+
+ # FS::pay_batch
+ eval "use XML::Simple;";
+ die $@ if $@;
+ my $xmlrow = $xml_format->{'xmlrow'};
+ $parser = $xml_format->{'xmlkeys'};
+ die 'no xmlkeys specified' unless ref $parser eq 'ARRAY';
+ my $data = XML::Simple::XMLin(
+ $file,
+ 'SuppressEmpty' => '', #sets empty values to ''
+ 'KeepRoot' => 1,
+ );
+ my $rows = $data;
+ $rows = $rows->{$_} foreach @$xmlrow;
+ $rows = [ $rows ] if ref($rows) ne 'ARRAY';
+ $count = @buffer = @$rows;
+
+ } elsif ( $type eq 'asn.1' ) {
+
+ eval "use Convert::ASN1";
+ die $@ if $@;
+
+ my $asn = Convert::ASN1->new;
+ $asn->prepare( $asn_format->{'spec'} ) or die $asn->error;
+
+ $parser = $asn->find( $asn_format->{'macro'} ) or die $asn->error;
+
+ my $data = slurp($file);
+ my $asn_output = $parser->decode( $data )
+ or return "No ". $asn_format->{'macro'}. " found\n";
+
+ $asn_header_buffer = &{ $asn_format->{'header_buffer'} }( $asn_output );
+
+ my $rows = &{ $asn_format->{'arrayref'} }( $asn_output );
+ $count = @buffer = @$rows;
+
+ } else {