1 package FS::ClientAPI::Bulk;
5 use vars qw( $DEBUG $cache );
7 use FS::Record qw( qsearchs );
9 use FS::ClientAPI_SessionCache;
21 $cache ||= new FS::ClientAPI_SessionCache ( {
22 'namespace' => 'FS::ClientAPI::Agent', #yes, share session_ids
26 sub _izoom_ftp_row_fixup {
29 my @addr_fields = qw( address1 address2 city state zip );
30 my @fields = ( qw( agent_custid username _password first last ),
32 map { "ship_$_" } @addr_fields );
34 $hash->{$_} =~ s/[&\/\*'"]/_/g foreach @fields;
36 #$hash->{action} = '' if $hash->{action} eq 'R'; #unsupported for ftp
38 $hash->{refnum} = 1; #ahem
39 $hash->{country} = 'US';
40 $hash->{ship_country} = 'US';
41 $hash->{payby} = 'LECB';
42 $hash->{payinfo} = $hash->{daytime};
43 $hash->{ship_fax} = '' if ( !$hash->{sms} || $hash->{sms} eq 'F' );
46 grep { $hash->{"ship_$_"} &&
47 (! $hash->{$_} || $hash->{"ship_$_"} ne $hash->{$_} )
49 ( @addr_fields, 'fax' );
52 foreach ( @addr_fields, qw( first last ) ) {
53 $hash->{"ship_$_"} = $hash->{$_} unless $hash->{"ship_$_"};
63 sub _izoom_ftp_result {
64 my ($hash, $error) = @_;
66 qsearchs( 'cust_main', { 'agent_custid' => $hash->{agent_custid},
67 'agentnum' => $hash->{agentnum}
71 my $custnum = $cust_main ? $cust_main->custnum : '';
72 my @response = ( $hash->{action}, $hash->{agent_custid}, $custnum );
75 push @response, ( 'ERROR', $error );
77 push @response, ( 'OK', 'OK' );
80 join( ',', @response );
84 sub _izoom_ftp_badaction {
85 "Invalid action: $_[0] record: @_ ";
88 sub _izoom_soap_row_fixup { _izoom_ftp_row_fixup(@_) };
90 sub _izoom_soap_result {
91 my ($hash, $error) = @_;
93 if ( $hash->{action} eq 'R' ) {
95 return "Please check errors:\n $error"; # odd extra space
97 return join(' ', "Everything ok.", $hash->{pkg}, $hash->{adjourn} );
101 my $pkg = $hash->{pkg} || $hash->{saved_pkg} || '';
103 return join(' ', $hash->{agent_custid}, $error );
105 return join(' ', $hash->{agent_custid}, $pkg, $hash->{adjourn} );
110 sub _izoom_soap_badaction {
111 "Unknown action '$_[13]' ";
116 'fields' => [ qw ( action agent_custid username _password
117 daytime ship_fax sms first last
118 address1 address2 city state zip
119 pkg adjourn ship_address1 ship_address2
120 ship_city ship_state ship_zip ) ],
121 'fixup' => sub { _izoom_ftp_row_fixup(@_) },
122 'result' => sub { _izoom_ftp_result(@_) },
123 'action' => sub { _izoom_ftp_badaction(@_) },
126 'fields' => [ qw ( agent_custid username _password
127 daytime first last address1 address2
128 city state zip pkg action adjourn
129 ship_fax sms ship_address1 ship_address2
130 ship_city ship_state ship_zip ) ],
131 'fixup' => sub { _izoom_soap_row_fixup(@_) },
132 'result' => sub { _izoom_soap_result(@_) },
133 'action' => sub { _izoom_soap_badaction(@_) },
140 my $session = _cache->get($p->{'session_id'})
141 or return { 'error' => "Can't resume session" }; #better error message
143 my $conf = new FS::Conf;
144 my $format = $conf->config('selfservice-bulk_format', $session->{agentnum})
146 my ( @row ) = @{ $p->{row} };
148 warn "processrow called with '". join("' '", @row). "'\n" if $DEBUG;
150 return { 'error' => "unknown format: $format" }
151 unless exists $format{$format};
153 return { 'error' => "Invalid record record length: ". scalar(@row).
156 unless scalar(@row) == scalar(@{$format{$format}{fields}});
158 my %hash = ( 'agentnum' => $session->{agentnum} );
161 foreach my $field ( @{ $format{ $format }{ fields } } ) {
162 $hash{$field} = shift @row;
165 $error ||= &{ $format{ $format }{ fixup } }( \%hash );
167 # put in the fixup routine?
168 if ( 'R' eq $hash{action} ) {
169 warn "processing reconciliation\n" if $DEBUG;
170 $error ||= process_recon($hash{agentnum}, $hash{agent_custid});
171 } elsif ( 'P' eq $hash{action} ) {
173 } elsif( 'D' eq $hash{action} ) {
174 $hash{promo_pkg} = 'disk-1-'. $session->{agent};
175 } elsif ( 'S' eq $hash{action} ) {
176 $hash{promo_pkg} = 'disk-2-'. $session->{agent};
177 $hash{saved_pkg} = $hash{pkg};
180 $error ||= &{ $format{ $format }{ action } }( @row );
183 warn "processing provision\n" if ($DEBUG && !$error && $hash{action} ne 'R');
184 $error ||= provision( %hash ) unless $hash{action} eq 'R';
186 my $result = &{ $format{ $format }{ result } }( \%hash, $error );
188 warn "processrow returning '". join("' '", $result, $error). "'\n"
191 return { 'error' => $error, 'message' => $result };
198 delete $args{action};
201 qsearchs( 'cust_main',
202 { map { $_ => $args{$_} } qw ( agent_custid agentnum ) },
205 unless ( $cust_main ) {
206 $cust_main = new FS::cust_main { %args };
207 my $error = $cust_main->insert;
208 return $error if $error;
211 my @pkgs = grep { $_->part_pkg->freq } $cust_main->ncancelled_pkgs;
212 if ( scalar(@pkgs) > 1 ) {
213 return "Invalid account, should not be more then one active package ". #sic
214 "but found: ". scalar(@pkgs). " packages.";
217 my $part_pkg = qsearchs( 'part_pkg', { 'pkg' => $args{pkg} } )
218 or return "Unknown pkgpart: $args{pkg}"
222 my $create_package = $args{pkg};
223 if ( scalar(@pkgs) && $create_package ) {
224 my $pkg = pop(@pkgs);
226 if ( $part_pkg->pkgpart != $pkg->pkgpart ) {
227 my @cust_bill_pkg = $pkg->cust_bill_pkg();
228 if ( 1 == scalar(@cust_bill_pkg) ) {
229 my $cbp= pop(@cust_bill_pkg);
230 my $cust_bill = $cbp->cust_bill;
231 $cust_bill->delete(); #really? wouldn't a credit be better?
235 $create_package = '';
236 $pkg->setfield('adjourn', str2time($args{adjourn}));
237 my $error = $pkg->replace();
238 return $error if $error;
242 if ( $create_package ) {
243 my $cust_pkg = new FS::cust_pkg ( {
244 'pkgpart' => $part_pkg->pkgpart,
245 'adjourn' => str2time( $args{adjourn} ),
248 my $svcpart = $part_pkg->svcpart('svc_acct');
250 my $svc_acct = new FS::svc_acct ( {
251 'svcpart' => $svcpart,
252 'username' => $args{username},
253 '_password' => $args{_password},
256 my $error = $cust_main->order_pkg( cust_pkg => $cust_pkg,
257 svcs => [ $svc_acct ],
259 return $error if $error;
262 if ( $args{promo_pkg} ) {
264 qsearchs( 'part_pkg', { 'promo_code' => $args{promo_pkg} } )
265 or return "unknown pkgpart: $args{promo_pkg}";
267 my $svcpart = $part_pkg->svcpart('svc_external')
268 or return "unknown svcpart: svc_external";
270 my $cust_pkg = new FS::cust_pkg ( {
271 'svcpart' => $svcpart,
272 'pkgpart' => $part_pkg->pkgpart,
275 my $svc_ext = new FS::svc_external ( { 'svcpart' => $svcpart } );
277 my $ticket_subject = 'Send setup disk to customer '. $cust_main->custnum;
278 my $error = $cust_main->order_pkg ( cust_pkg => $cust_pkg,
279 svcs => [ $svc_ext ],
281 ticket_subject => $ticket_subject,
282 ticket_queue => "disk-$args{agentnum}",
284 return $error if $error;
287 my $error = $cust_main->bill();
288 return $error if $error;
292 my ( $agentnum, $id ) = @_;
293 my @recs = split /;/, $id;
295 foreach my $rec ( @recs ) {
296 my @record = split /,/, $rec;
297 my $result = process_recon_record(@record, $agentnum);
298 $err .= "$result\n" if $result;
303 sub process_recon_record {
304 my ( $agent_custid, $username, $_password, $daytime, $first, $last, $address1, $address2, $city, $state, $zip, $pkg, $adjourn, $agentnum) = @_;
306 warn "process_recon_record called with '". join("','", @_). "'\n" if $DEBUG;
308 my ($cust_pkg, $package);
311 qsearchs( 'cust_main',
312 { 'agent_custid' => $agent_custid, 'agentnum' => $agentnum },
317 my @cust_pkg = grep { $_->part_pkg->freq } $cust_main->ncancelled_pkgs;
318 if ( scalar(@cust_pkg) == 1) {
319 $cust_pkg = pop(@cust_pkg);
320 $package = $cust_pkg->part_pkg->pkg;
321 $comments = "$agent_custid wrong package, expected: $pkg found: $package"
322 if ( $pkg ne $package );
324 $comments = "invalid account, should be one active package but found: ".
325 scalar(@cust_pkg). " packages.";
329 "Customer not found agent_custid=$agent_custid, agentnum=$agentnum";
332 my $cust_recon = new FS::cust_recon( {
334 'agentnum' => $agentnum,
337 'address1' => $address1,
338 'address2' => $address2,
342 'custnum' => $cust_main ? $cust_main->custnum : '', #really?
343 'status' => $cust_main ? $cust_main->status : '',
345 'adjourn' => $cust_pkg ? $cust_pkg->adjourn : '',
346 'agent_custid' => $agent_custid, # redundant?
348 'agent_adjourn' => str2time($adjourn),
349 'comments' => $comments,
352 warn Dumper($cust_recon) if $DEBUG;
353 my $error = $cust_recon->insert;
354 return $error if $error;
356 warn "process_recon_record returning $comments\n" if $DEBUG;
365 my $session = _cache->get($p->{'session_id'})
366 or return { 'error' => "Can't resume session" }; #better error message
368 my $svc_domain = qsearchs( 'svc_domain', { 'domain' => $p->{domain} } )
369 or return { 'error' => 'Unknown domain '. $p->{domain} };
371 my $svc_acct = qsearchs( 'svc_acct', { 'username' => $p->{user},
372 'domsvc' => $svc_domain->svcnum,
376 return { 'error' => $p->{user}. '@'. $p->{domain}. " alerady in use" } # sic
379 return { 'error' => '',
380 'message' => $p->{user}. '@'. $p->{domain}. " is free"