RT# 77167 - Added the ability to import a list of contacts
[freeside.git] / FS / FS / contact_import.pm
1 package FS::contact_import;
2
3 use strict;
4 use vars qw( $DEBUG ); #$conf );
5 use Storable qw(thaw);
6 use Data::Dumper;
7 use MIME::Base64;
8 use FS::Misc::DateTime qw( parse_datetime );
9 use FS::Record qw( qsearchs );
10 use FS::contact;
11 use FS::cust_main;
12
13 $DEBUG = 0;
14
15 =head1 NAME
16
17 FS::contact_import - Batch contact importing
18
19 =head1 SYNOPSIS
20
21   use FS::contact_import;
22
23   #import
24   FS::contact_import::batch_import( {
25     file      => $file,      #filename
26     type      => $type,      #csv or xls
27     format    => $format,    #default
28     agentnum  => $agentnum,
29     job       => $job,       #optional job queue job, for progressbar updates
30     pkgbatch  => $pkgbatch,  #optional batch unique identifier
31   } );
32   die $error if $error;
33
34   #ajax helper
35   use FS::UI::Web::JSRPC;
36   my $server =
37     new FS::UI::Web::JSRPC 'FS::contact_import::process_batch_import', $cgi;
38   print $server->process;
39
40 =head1 DESCRIPTION
41
42 Batch contact importing.
43
44 =head1 SUBROUTINES
45
46 =item process_batch_import
47
48 Load a batch import as a queued JSRPC job
49
50 =cut
51
52 sub process_batch_import {
53   my $job = shift;
54   #my $param = shift;
55   my $param = thaw(decode_base64(shift));
56   warn Dumper($param) if $DEBUG;
57   
58   my $files = $param->{'uploaded_files'}
59     or die "No files provided.\n";
60
61   my (%files) = map { /^(\w+):([\.\w]+)$/ ? ($1,$2):() } split /,/, $files;
62
63   my $dir = '%%%FREESIDE_CACHE%%%/cache.'. $FS::UID::datasrc. '/';
64
65   my $file = $dir. $files{'file'};
66
67   my $type;
68   if ( $file =~ /\.(\w+)$/i ) {
69     $type = lc($1);
70   } else {
71     #or error out???
72     warn "can't parse file type from filename $file; defaulting to CSV";
73     $type = 'csv';
74   }
75
76   my $error =
77     FS::contact_import::batch_import( {
78       job      => $job,
79       file     => $file,
80       type     => $type,
81       agentnum => $param->{'agentnum'},
82       'format' => $param->{'format'},
83     } );
84
85   unlink $file;
86
87   die "$error\n" if $error;
88
89 }
90
91 =item batch_import
92
93 =cut
94
95 my %formatfields = (
96   'default'      => [ qw( custnum last first title comment selfservice_access emailaddress phonetypenum1 phonetypenum3 phonetypenum2 ) ],
97 );
98
99 sub _formatfields {
100   \%formatfields;
101 }
102
103 ## not tested but maybe allow 2nd format to attach location in the future
104 my %import_options = (
105   'table'         => 'contact',
106
107   'preinsert_callback'  => sub {
108     my($record, $param) = @_;
109     my @location_params = grep /^location\./, keys %$param;
110     if (@location_params) {
111       my $cust_location = FS::cust_location->new({
112           'custnum' => $record->custnum,
113       });
114       foreach my $p (@location_params) {
115         $p =~ /^location.(\w+)$/;
116         $cust_location->set($1, $param->{$p});
117       }
118
119       my $error = $cust_location->find_or_insert; # this avoids duplicates
120       return "error creating location: $error" if $error;
121       $record->set('locationnum', $cust_location->locationnum);
122     }
123     '';
124   },
125
126 );
127
128 sub _import_options {
129   \%import_options;
130 }
131
132 sub batch_import {
133   my $opt = shift;
134
135   my $iopt = _import_options;
136   $opt->{$_} = $iopt->{$_} foreach keys %$iopt;
137
138   my $format = delete $opt->{'format'};
139
140   my $formatfields = _formatfields();
141     die "unknown format $format" unless $formatfields->{$format};
142
143   my @fields;
144   foreach my $field ( @{ $formatfields->{$format} } ) {
145     push @fields, $field;
146   }
147
148   $opt->{'fields'} = \@fields;
149
150   FS::Record::batch_import( $opt );
151
152 }
153
154 =head1 BUGS
155
156 Not enough documentation.
157
158 =head1 SEE ALSO
159
160 L<FS::contact>
161
162 =cut
163
164 1;