40361: Vocus CDR Format [minor doc fix]
[freeside.git] / FS / bin / freeside-cdr-vocus
1 #!/usr/bin/perl
2
3 use strict;
4 use Getopt::Std;
5 use Date::Format;
6 use Net::SFTP::Foreign::Compat;
7 use FS::UID qw(adminsuidsetup datasrc);
8 use FS::cdr;
9
10 ### So much false laziness with freeside-cdr-sftp_and_import
11 ###   but vocus needs special handling to choose which files to load
12
13 ###
14 # parse command line
15 ###
16
17 use vars qw( $opt_r $opt_d $opt_v $opt_s );
18 getopts('r:d:vs');
19
20 my $user = shift or die &usage;
21 adminsuidsetup $user;
22
23 # %%%FREESIDE_CACHE%%%
24 my $cachedir = '%%%FREESIDE_CACHE%%%/cache.'. datasrc. '/cdrs';
25 mkdir $cachedir unless -d $cachedir;
26
27 use vars qw( $servername );
28 $servername = shift or die &usage;
29
30 ###
31 # get the file list
32 ###
33
34 warn "Retrieving directory listing\n" if $opt_v;
35
36 my $ls;
37
38 my $ls_sftp = sftp();
39
40 $ls_sftp->setcwd($opt_r) or die "can't chdir to $opt_r\n"
41   if $opt_r;
42
43 $ls = $ls_sftp->ls('.', wanted => qr/^VAP.*\.csv\.gz$/i,
44                         names_only => 1 );
45
46
47 ###
48 # vocus-specific part -- only use highest-numbered file for day
49 ###
50 my %dates;
51 foreach my $filename ( @$ls ) {
52   my ($filepre,$filedate,$iter) = $filename =~ /^(VAP\d+\-)(\d{4}\-\d{2}\-\d{2})(\.\d+)?.csv.gz$/;
53   unless ($filepre && $filedate) {
54     die "unparsable filename $filename"; #no warn and skip, might process wrong file for date
55   }
56   $iter =~ s/\.// if length($iter);
57   #not clear if same day can have different initial digits after VAP,
58   #  stated rule is "use the highest-numbered file for a given day",
59   #  so using date as key, but die if iter can't resolve conflict
60   if (!$dates{$filedate}) {
61     $dates{$filedate} = {};
62   } elsif ($dates{$filedate}{'iter'} eq $iter) {
63     die "duplicate iterators found for $filedate\n"
64   }
65   $dates{$filedate}{'files'} ||= [];
66   push @{$dates{$filedate}{'files'}}, $filename;
67   # don't actually expect iter of 0, but just in case, 0 trumps undef
68   if (!defined($dates{$filedate}{'iter'}) or (($iter || 0) > $dates{$filedate}{'iter'})) {
69     $dates{$filedate}{'iter'} = $iter;
70     $dates{$filedate}{'pre'}  = $filepre;
71   }
72 }
73
74 ###
75 # import each file
76 ###
77
78 foreach my $filedate ( keys %dates ) {
79
80   my $filename = $dates{$filedate}{'pre'} . $filedate;
81   $filename .= '.'.$dates{$filedate}{'iter'}
82     if defined($dates{$filedate}{'iter'});
83   $filename .= '.csv.gz';
84
85   warn "Downloading $filename\n" if $opt_v;
86
87   #get the file
88   my $sftp = sftp();
89   $sftp->get($filename, "$cachedir/$filename")
90     or do {
91       unlink "$cachedir/$filename";
92       my $error = "Can't get $filename: ". $sftp->error . "\n";
93       if ( $opt_s ) {
94         warn $error;
95         next;
96       } else {
97         die $error;
98       }
99     };
100
101   warn "Processing $filename\n" if $opt_v;
102  
103   my $ungziped = $filename;
104   $ungziped =~ s/\.gz$//;
105   if(system('gunzip', "$cachedir/$filename") != 0) {
106     unlink "$cachedir/$filename";
107     my $error = "gunzip of '$cachedir/$filename' failed\n";
108     if ( $opt_s ) {
109       warn $error;
110       next;
111     } else {
112       die $error;
113     }
114   }
115
116   my $import_options = {
117     'file'            => "$cachedir/$ungziped",
118     'format'          => 'vocus',
119     'batch_namevalue' => 'vocus-'.$filedate, #should further ensure only one file per date
120 #    'empty_ok'        => 1,
121   };
122   
123   my $error = FS::cdr::batch_import($import_options);
124
125   if ( $error ) {
126
127     unlink "$cachedir/$filename";
128     unlink "$cachedir/$ungziped";
129     $error = "Error importing $ungziped: $error\n";
130     if ( $opt_s ) {
131       warn $error;
132       next;
133     } else {
134       die $error;
135     }
136
137   } else {
138
139     if ( $opt_d ) {
140       my $timestamp = time2str('%Y-%m-%d', time);
141       my $sftp = sftp();
142       foreach my $mfilename (@{$dates{$filedate}{'files'}}) {
143         warn "Moving $mfilename\n" if $opt_v;
144         $sftp->rename($mfilename, "$opt_d/$mfilename-$timestamp")
145           or do {
146             unlink "$cachedir/$filename";
147             unlink "$cachedir/$ungziped";
148             $error = "$mfilename imported, but can't move to $opt_d: ". $sftp->error . "\n";
149             if ( $opt_s ) {
150               warn $error;
151               next;
152             } else {
153               die $error;
154             }
155           };
156       }
157     }
158
159   }
160
161   unlink "$cachedir/$filename";
162   unlink "$cachedir/$ungziped";
163
164 }
165
166 ###
167 # subs
168 ###
169
170 sub usage {
171   "Usage:
172   freeside-cdr-vocus
173     [ -r remotefolder ] [ -d donefolder ] [ -v level ]
174     [ -s ] user [sftpuser@]servername
175   ";
176 }
177
178 use vars qw( $sftp );
179
180 sub sftp {
181
182   #reuse connections
183   return $sftp if $sftp && $sftp->cwd;
184
185   my %sftp = ( host => $servername );
186
187   $sftp = Net::SFTP::Foreign->new(%sftp);
188   $sftp->error and die "SFTP connection failed: ". $sftp->error;
189
190   $sftp;
191 }
192
193 =head1 NAME
194
195 freeside-cdr-vocus - Download Vocus CDR files
196
197 =head1 SYNOPSIS
198
199   freeside-cdr-vocus
200     [ -r remotefolder ] [ -d donefolder ] [ -v level ]
201     [ -s ] user [sftpuser@]servername
202
203 =head1 DESCRIPTION
204
205 Command line tool to download Vocus CDR files from a remote server via SFTP 
206 and then import them into the database.  CDR tarrif types need to be 
207 configured as CDR types in freeside.  If multiple files for a given day are 
208 found, the one with the highest iterator will be used (though upon successful
209 import, all existing files for the day will be moved by the -d option.)
210
211 -r: if specified, changes into this remote folder before starting
212
213 -d: if specified, moves files to the specified folder when done
214
215 -v: verbose
216
217 -s: Warn and skip files which could not be imported rather than abort
218
219 user: freeside username
220
221 [sftpuser@]servername: remote server
222
223 =head1 BUGS
224
225 =head1 SEE ALSO
226
227 L<FS::cdr>
228
229 =cut
230
231 1;
232