add "All dates" format to package import with contract_end, RT#34397
[freeside.git] / FS / FS / cust_pkg / Import.pm
1 package FS::cust_pkg::Import;
2
3 use strict;
4 use vars qw( $DEBUG ); #$conf );
5 use Data::Dumper;
6 use FS::Misc::DateTime qw( parse_datetime );
7 use FS::Record qw( qsearchs );
8 use FS::cust_pkg;
9 use FS::cust_main;
10 use FS::svc_acct;
11 use FS::svc_external;
12 use FS::svc_phone;
13 use FS::svc_domain;
14
15 $DEBUG = 0;
16
17 #install_callback FS::UID sub {
18 #  $conf = new FS::Conf;
19 #};
20
21 =head1 NAME
22
23 FS::cust_pkg::Import - Batch customer importing
24
25 =head1 SYNOPSIS
26
27   use FS::cust_pkg::Import;
28
29   #import
30   FS::cust_pkg::Import::batch_import( {
31     file      => $file,      #filename
32     type      => $type,      #csv or xls
33     format    => $format,    #extended, extended-plus_company, svc_external,
34                              # or svc_external_svc_phone
35     agentnum  => $agentnum,
36     job       => $job,       #optional job queue job, for progressbar updates
37     pkgbatch  => $pkgbatch, #optional batch unique identifier
38   } );
39   die $error if $error;
40
41   #ajax helper
42   use FS::UI::Web::JSRPC;
43   my $server =
44     new FS::UI::Web::JSRPC 'FS::cust_pkg::Import::process_batch_import', $cgi;
45   print $server->process;
46
47 =head1 DESCRIPTION
48
49 Batch package importing.
50
51 =head1 SUBROUTINES
52
53 =item process_batch_import
54
55 Load a batch import as a queued JSRPC job
56
57 =cut
58
59 sub process_batch_import {
60   my $job = shift;
61   my $param = shift;
62   warn Dumper($param) if $DEBUG;
63   
64   my $files = $param->{'uploaded_files'}
65     or die "No files provided.\n";
66
67   my (%files) = map { /^(\w+):([\.\w]+)$/ ? ($1,$2):() } split /,/, $files;
68
69   my $dir = '%%%FREESIDE_CACHE%%%/cache.'. $FS::UID::datasrc. '/';
70   my $file = $dir. $files{'file'};
71
72   my $type;
73   if ( $file =~ /\.(\w+)$/i ) {
74     $type = lc($1);
75   } else {
76     #or error out???
77     warn "can't parse file type from filename $file; defaulting to CSV";
78     $type = 'csv';
79   }
80
81   my $error =
82     FS::cust_pkg::Import::batch_import( {
83       job      => $job,
84       file     => $file,
85       type     => $type,
86       'params' => { pkgbatch => $param->{pkgbatch} },
87       agentnum => $param->{'agentnum'},
88       'format' => $param->{'format'},
89     } );
90
91   unlink $file;
92
93   die "$error\n" if $error;
94
95 }
96
97 =item batch_import
98
99 =cut
100
101 my %formatfields = (
102   'default'      => [],
103   'svc_acct'     => [qw( username _password domsvc )],
104   'svc_phone'    => [qw( countrycode phonenum sip_password pin )],
105   'svc_external' => [qw( id title )],
106   'location'     => [qw( address1 address2 city state zip country )],
107 );
108
109 sub _formatfields {
110   \%formatfields;
111 }
112
113 my %import_options = (
114   'table'         => 'cust_pkg',
115
116   'preinsert_callback'  => sub {
117     my($record, $param) = @_;
118     my @location_params = grep /^location\./, keys %$param;
119     if (@location_params) {
120       my $cust_location = FS::cust_location->new({
121           'custnum' => $record->custnum,
122       });
123       foreach my $p (@location_params) {
124         $p =~ /^location.(\w+)$/;
125         $cust_location->set($1, $param->{$p});
126       }
127
128       my $error = $cust_location->find_or_insert; # this avoids duplicates
129       return "error creating location: $error" if $error;
130       $record->set('locationnum', $cust_location->locationnum);
131     }
132     '';
133   },
134
135   'postinsert_callback' => sub {
136     my( $record, $param ) = @_;
137
138     my $formatfields = _formatfields;
139     foreach my $svc_x ( grep /^svc/, keys %$formatfields ) {
140
141       my $ff = $formatfields->{$svc_x};
142
143       if ( grep $param->{"$svc_x.$_"}, @$ff ) {
144         my $svc = "FS::$svc_x"->new( {
145           'pkgnum'  => $record->pkgnum,
146           'svcpart' => $record->part_pkg->svcpart($svc_x),
147           map { $_ => $param->{"$svc_x.$_"} } @$ff
148         } );
149
150         #this whole thing should be turned into a callback or config to turn on
151         if ( $svc_x eq 'svc_acct' && $svc->username =~ /\@/ ) {
152           my($username, $domain) = split(/\@/, $svc->username);
153           my $svc_domain = qsearchs('svc_domain', { 'domain' => $domain } )
154                          || new FS::svc_domain { 'svcpart' => 1,
155                                                  'domain'  => $domain, };
156           unless ( $svc_domain->svcnum ) {
157             my $error = $svc_domain->insert;
158             return "error auto-inserting domain: $error" if $error;
159           }
160           $svc->username($username);
161           $svc->domsvc($svc_domain->svcnum);
162         }
163
164         my $error = $svc->insert;
165         return "error inserting service: $error" if $error;
166       }
167
168     }
169
170     return ''; #no error
171
172   },
173 );
174
175 sub _import_options {
176   \%import_options;
177 }
178
179 sub batch_import {
180   my $opt = shift;
181
182   my $iopt = _import_options;
183   $opt->{$_} = $iopt->{$_} foreach keys %$iopt;
184
185   my $agentnum  = delete $opt->{agentnum}; # i like closures (delete though?)
186
187   my $format = delete $opt->{'format'};
188   my @fields = ();
189
190   if ( $format =~ /^(.*)-agent_custid(-agent_pkgid)?$/ ) {
191     $format = $1;
192     my $agent_pkgid = $2;
193     @fields = (
194       sub {
195         my( $self, $value ) = @_; # $conf, $param
196         my $cust_main = qsearchs('cust_main', {
197           'agentnum'     => $agentnum,
198           'agent_custid' => $value,
199         });
200         $self->custnum($cust_main->custnum) if $cust_main;
201       },
202     );
203     push @fields, 'agent_pkgid' if $agent_pkgid;
204   } else {
205     @fields = ( 'custnum' );
206   }
207
208   push @fields, ( 'pkgpart', 'discountnum' );
209
210   my @date_fields = ();
211   if ( $format =~ /all_dates/ ) {
212     @date_fields = qw(
213       order_date
214       start_date setup bill last_bill susp adjourn
215       resume
216       cancel expire
217       contract_end dundate
218     );
219   } else {
220     @date_fields = qw(
221       start_date setup bill last_bill susp adjourn
222       cancel expire
223     );
224   }
225
226   foreach my $field (@date_fields) { 
227     push @fields, sub {
228       my( $self, $value ) = @_; # $conf, $param
229       #->$field has undesirable effects
230       $self->set($field, parse_datetime($value) ); #$field closure
231     };
232   }
233
234   my $formatfields = _formatfields();
235
236   die "unknown format $format" unless $formatfields->{$format};
237
238   foreach my $field ( @{ $formatfields->{$format} } ) {
239
240     push @fields, sub {
241       my( $self, $value, $conf, $param ) = @_;
242       $param->{"$format.$field"} = $value;
243     };
244
245   }
246
247   $opt->{'fields'} = \@fields;
248
249   FS::Record::batch_import( $opt );
250
251 }
252
253 =head1 BUGS
254
255 Not enough documentation.
256
257 =head1 SEE ALSO
258
259 L<FS::cust_main>, L<FS::cust_pkg>,
260 L<FS::svc_acct>, L<FS::svc_external>, L<FS::svc_phone>
261
262 =cut
263
264 1;