af77dfa783710a68fd9c0cc9bb8883919c5add62
[freeside.git] / FS / FS / part_export / saisei.pm
1 package FS::part_export::saisei;
2
3 use strict;
4 use vars qw( @ISA %info );
5 use base qw( FS::part_export );
6 use Date::Format 'time2str';
7 use Cpanel::JSON::XS;
8 use Storable qw(thaw);
9 use MIME::Base64;
10 use REST::Client;
11 use Data::Dumper;
12 use FS::Conf;
13
14 =pod
15
16 =head1 NAME
17
18 FS::part_export::saisei
19
20 =head1 SYNOPSIS
21
22 Saisei integration for Freeside
23
24 =head1 DESCRIPTION
25
26 This export offers basic svc_broadband provisioning for Saisei.
27
28 This is a customer integration with Saisei.  This will set up a rate plan and tie
29 the rate plan to a host and the access point via the Saisei API when the broadband service is provisioned.
30 It will also untie the host from the rate plan, setting it to the default rate plan via the API upon unprovisioning of the broadband service.
31
32 This will create and modify the rate plans at Saisei as soon as the broadband service attached to this export is created or modified.
33 This will also create and modify an access point at Saisei as soon as the tower is created or modified.
34
35 To use this export, follow the below instructions:
36
37 Create a new service definition and set the table to svc_broadband.  The service name will become the Saisei rate plan name.
38 Set the upload and download speed for the service. This is required to be able to export the service to Saisei.
39 Attach this Saisei export to this service.
40
41 Create a tower and add a sector to that tower.  The sector name will be the name of the access point,
42 Make sure you have set the up and down rate limit for the tower and the sector.  This is required to be able to export the access point.
43 The tower and sector will be set up as access points at Saisei upon the creation of the tower or sector.  They will be modified at Saisei when modified in freeside.
44 Each sector will be attached to its tower access point using the Saisei uplink field.
45
46 Create a package for the above created service, and order this package for a customer.
47
48 Provision the service, making sure to enter the IP address associated with this service and select the tower and sector for it's access point.
49 This provisioned service will then be exported as a host to Saisei.
50
51 Unprovisioning this service will set the host entry at Saisei to the default rate plan with the user and access point set to <none>.
52
53 After this export is set up and attached to a service, you can export the already provisioned services by clicking the link Export provisioned services attached to this export.
54 Clicking on this link will export all services attached to this export not currently exported to Saisei.
55
56 This module also provides generic methods for working through the L</Saisei API>.
57
58 =cut
59
60 tie my %scripts, 'Tie::IxHash',
61   'export_provisioned_services'  => { component => '/elements/popup_link.html',
62                                       label     => 'Export provisioned services',
63                                       description => 'will export provisioned services of part service with Saisei export attached.',
64                                       html_label => '<b>Export provisioned services attached to this export.</b>',
65                                     },
66 ;
67
68 tie my %options, 'Tie::IxHash',
69   'port'             => { label => 'Port',
70                           default => 5000 },
71   'username'         => { label => 'Saisei API User Name',
72                           default => '' },
73   'password'         => { label => 'Saisei API Password',
74                           default => '' },
75   'debug'            => { type => 'checkbox',
76                           label => 'Enable debug warnings' },
77 ;
78
79 %info = (
80   'svc'             => 'svc_broadband',
81   'desc'            => 'Export broadband service/account to Saisei',
82   'options'         => \%options,
83   'scripts'         => \%scripts,
84   'notes'           => <<'END',
85 This is a customer integration with Saisei.  This will set up a rate plan and tie
86 the rate plan to a host and the access point via the Saisei API when the broadband service is provisioned.
87 It will also untie the host from the rate plan, setting it to the default rate plan via the API upon unprovisioning of the broadband service.
88 <P>
89 This will create and modify the rate plans at Saisei as soon as the broadband service attached to this export is created or modified.
90 This will also create and modify an access point at Saisei as soon as the tower is created or modified.
91 <P>
92 To use this export, follow the below instructions:
93 <P>
94 <OL>
95 <LI>
96 Create a new service definition and set the table to svc_broadband.  The service name will become the Saisei rate plan name.
97 Set the upload and download speed for the service. This is required to be able to export the service to Saisei.
98 Attach this Saisei export to this service.
99 </LI>
100 <P>
101 <LI>
102 Create a tower and add a sector to that tower.  The sector name will be the name of the access point,
103 Make sure you have set the up and down rate limit for the tower and the sector.  This is required to be able to export the access point.
104 The tower and sector will be set up as access points at Saisei upon the creation of the tower or sector.  They will be modified at Saisei when modified in freeside.
105 Each sector will be attached to its tower access point using the Saisei uplink field.
106 </LI>
107 <P>
108 <LI>
109 Create a package for the above created service, and order this package for a customer.
110 </LI>
111 <P>
112 <LI>
113 Provision the service, making sure to enter the IP address associated with this service and select the tower and sector for it's access point.
114 This provisioned service will then be exported as a host to Saisei.
115 <P>
116 Unprovisioning this service will set the host entry at Saisei to the default rate plan with the user and access point set to <i>none</i>.
117 </LI>
118 <P>
119 <LI>
120 After this export is set up and attached to a service, you can export the already provisioned services by clicking the link <b>Export provisioned services attached to this export</b>.
121 Clicking on this link will export all services attached to this export not currently exported to Saisei.
122 </LI>
123 </OL>
124 <P>
125
126 END
127 );
128
129 sub _export_insert {
130   my ($self, $svc_broadband) = @_;
131
132   my $rateplan_name = $self->get_rateplan_name($svc_broadband);
133
134   # check for existing rate plan
135   my $existing_rateplan;
136   $existing_rateplan = $self->api_get_rateplan($rateplan_name) unless $self->{'__saisei_error'};
137
138   # if no existing rate plan create one and modify it.
139   $self->api_create_rateplan($svc_broadband, $rateplan_name) unless $existing_rateplan;
140   $self->api_modify_rateplan($svc_broadband, $rateplan_name) unless ($self->{'__saisei_error'} || $existing_rateplan);
141   return $self->api_error if $self->{'__saisei_error'};
142
143   # set rateplan to existing one or newly created one.
144   my $rateplan = $existing_rateplan ? $existing_rateplan : $self->api_get_rateplan($rateplan_name);
145
146   my $username = $svc_broadband->{Hash}->{svcnum};
147   my $description = $svc_broadband->{Hash}->{description};
148
149   if (!$username) {
150     $self->{'__saisei_error'} = 'no username - can not export';
151     return $self->api_error;
152   }
153   else {
154     # check for existing user.
155     my $existing_user;
156     $existing_user = $self->api_get_user($username) unless $self->{'__saisei_error'};
157  
158     # if no existing user create one.
159     $self->api_create_user($username, $description) unless $existing_user;
160     return $self->api_error if $self->{'__saisei_error'};
161
162     # set user to existing one or newly created one.
163     my $user = $existing_user ? $existing_user : $self->api_get_user($username);
164
165     ## add access point
166     my $tower_sector = FS::Record::qsearchs({
167       'table'     => 'tower_sector',
168       'select'    => 'tower.towername,
169                       tower.up_rate_limit as tower_upratelimit,
170                       tower.down_rate_limit as tower_downratelimit,
171                       tower_sector.sectorname,
172                       tower_sector.up_rate_limit as sector_upratelimit,
173                       tower_sector.down_rate_limit as sector_downratelimit ',
174       'addl_from' => 'LEFT JOIN tower USING ( towernum )',
175       'hashref'   => {
176                         'sectornum' => $svc_broadband->{Hash}->{sectornum},
177                      },
178     });
179
180     my $tower_name = $tower_sector->{Hash}->{towername};
181     $tower_name =~ s/\s/_/g;
182
183     my $tower_opt = {
184       'tower_name'           => $tower_name,
185       'tower_uprate_limit'   => $tower_sector->{Hash}->{tower_upratelimit},
186       'tower_downrate_limit' => $tower_sector->{Hash}->{tower_downratelimit},
187     };
188
189     my $tower_ap = process_tower($self, $tower_opt);
190     return $self->api_error if $self->{'__saisei_error'};
191
192     my $sector_name = $tower_sector->{Hash}->{sectorname};
193     $sector_name =~ s/\s/_/g;
194
195     my $sector_opt = {
196       'tower_name'            => $tower_name,
197       'sector_name'           => $sector_name,
198       'sector_uprate_limit'   => $tower_sector->{Hash}->{sector_upratelimit},
199       'sector_downrate_limit' => $tower_sector->{Hash}->{sector_downratelimit},
200     };
201     my $accesspoint = process_sector($self, $sector_opt);
202     return $self->api_error if $self->{'__saisei_error'};
203
204     ## tie host to user add sector name as access point.
205     $self->api_add_host_to_user(
206       $user->{collection}->[0]->{name},
207       $rateplan->{collection}->[0]->{name},
208       $svc_broadband->{Hash}->{ip_addr},
209       $accesspoint->{collection}->[0]->{name},
210     ) unless $self->{'__saisei_error'};
211   }
212
213   return $self->api_error;
214
215 }
216
217 sub _export_replace {
218   my ($self, $svc_broadband) = @_;
219   $self->_export_insert($svc_broadband);
220   return '';
221 }
222
223 sub _export_delete {
224   my ($self, $svc_broadband) = @_;
225
226   my $rateplan_name = $self->get_rateplan_name($svc_broadband);
227
228   my $username = $svc_broadband->{Hash}->{svcnum};
229
230   ## untie host to user
231   $self->api_delete_host_to_user($username, $rateplan_name, $svc_broadband->{Hash}->{ip_addr}) unless $self->{'__saisei_error'};
232
233   return '';
234 }
235
236 sub _export_suspend {
237   my ($self, $svc_broadband) = @_;
238   return '';
239 }
240
241 sub _export_unsuspend {
242   my ($self, $svc_broadband) = @_;
243   return '';
244 }
245
246 sub export_partsvc {
247   my ($self, $svc_part) = @_;
248
249   my $fcc_477_speeds;
250   if ($svc_part->{Hash}->{svc_broadband__speed_down} eq "down" || $svc_part->{Hash}->{svc_broadband__speed_up} eq "up") {
251     for my $type (qw( down up )) {
252       my $speed_type = "broadband_".$type."stream";
253       foreach my $pkg_svc (FS::Record::qsearch({
254         'table'     => 'pkg_svc',
255         'select'    => 'pkg_svc.*, part_pkg_fcc_option.fccoptionname, part_pkg_fcc_option.optionvalue',
256         'addl_from' => ' LEFT JOIN part_pkg_fcc_option USING (pkgpart) ',
257         'extra_sql' => " WHERE pkg_svc.svcpart = ".$svc_part->{Hash}->{svcpart}." AND pkg_svc.quantity > 0 AND part_pkg_fcc_option.fccoptionname = '".$speed_type."'",
258       })) { $fcc_477_speeds->{
259         $pkg_svc->{Hash}->{pkgpart}}->{$speed_type} = $pkg_svc->{Hash}->{optionvalue} * 1000 unless !$pkg_svc->{Hash}->{optionvalue}; }
260     }
261   }
262   else {
263     $fcc_477_speeds->{1}->{broadband_downstream} = $svc_part->{Hash}->{"svc_broadband__speed_down"};
264     $fcc_477_speeds->{1}->{broadband_upstream} = $svc_part->{Hash}->{"svc_broadband__speed_up"};
265   }
266
267   foreach my $key (keys %$fcc_477_speeds) {
268
269     $svc_part->{Hash}->{speed_down} = $fcc_477_speeds->{$key}->{broadband_downstream};
270     $svc_part->{Hash}->{speed_up} = $fcc_477_speeds->{$key}->{broadband_upstream};
271     $svc_part->{Hash}->{svc_broadband__speed_down} = $fcc_477_speeds->{$key}->{broadband_downstream};
272     $svc_part->{Hash}->{svc_broadband__speed_up} = $fcc_477_speeds->{$key}->{broadband_upstream};
273
274     my $temp_svc = $svc_part->{Hash};
275     my $svc_broadband = {};
276     map { if ($_ =~ /^svc_broadband__(.*)$/) { $svc_broadband->{Hash}->{$1} = $temp_svc->{$_}; }  } keys %$temp_svc;
277
278     my $rateplan_name = $self->get_rateplan_name($svc_broadband, $svc_part->{Hash}->{svc});
279
280     # check for existing rate plan
281     my $existing_rateplan;
282     $existing_rateplan = $self->api_get_rateplan($rateplan_name) unless $self->{'__saisei_error'};
283
284     # Modify the existing rate plan with new service data.
285     $self->api_modify_existing_rateplan($svc_broadband, $rateplan_name) unless ($self->{'__saisei_error'} || !$existing_rateplan);
286
287     # if no existing rate plan create one and modify it.
288     $self->api_create_rateplan($svc_broadband, $rateplan_name) unless $existing_rateplan;
289     $self->api_modify_rateplan($svc_part, $rateplan_name) unless ($self->{'__saisei_error'} || $existing_rateplan);
290
291   }
292
293   return $self->api_error;
294
295 }
296
297 sub export_tower_sector {
298   my ($self, $tower) = @_;
299
300   #modify tower or create it.
301   my $tower_name = $tower->{Hash}->{towername};
302   $tower_name =~ s/\s/_/g;
303   my $tower_opt = {
304     'tower_name'           => $tower_name,
305     'tower_uprate_limit'   => $tower->{Hash}->{up_rate_limit},
306     'tower_downrate_limit' => $tower->{Hash}->{down_rate_limit},
307     'modify_existing'      => '1', # modify an existing access point with this info
308   };
309
310   my $tower_access_point = process_tower($self, $tower_opt);
311
312   #get list of all access points
313   my $hash_opt = {
314       'table'     => 'tower_sector',
315       'select'    => '*',
316       'hashref'   => { 'towernum' => $tower->{Hash}->{towernum}, },
317   };
318
319   #for each one modify or create it.
320   foreach my $tower_sector ( FS::Record::qsearch($hash_opt) ) {
321     my $sector_name = $tower_sector->{Hash}->{sectorname};
322     $sector_name =~ s/\s/_/g;
323     my $sector_opt = {
324       'tower_name'            => $tower_name,
325       'sector_name'           => $sector_name,
326       'sector_uprate_limit'   => $tower_sector->{Hash}->{up_rate_limit},
327       'sector_downrate_limit' => $tower_sector->{Hash}->{down_rate_limit},
328       'modify_existing'       => '1', # modify an existing access point with this info
329     };
330     my $sector_access_point = process_sector($self, $sector_opt);
331   }
332
333   return $self->api_error;
334 }
335
336 ## creates the rateplan name
337 sub get_rateplan_name {
338   my ($self, $svc_broadband, $svc_name) = @_;
339
340   my $service_part = FS::Record::qsearchs( 'part_svc', { 'svcpart' => $svc_broadband->{Hash}->{svcpart} } ) unless $svc_name;
341   my $service_name = $svc_name ? $svc_name : $service_part->{Hash}->{svc};
342
343   my $rateplan_name = $service_name . " " . $svc_broadband->{Hash}->{speed_down} . "-" . $svc_broadband->{Hash}->{speed_up};
344   $rateplan_name =~ s/\s/_/g;
345
346   return $rateplan_name;
347 }
348
349 =head1 Saisei API
350
351 These methods allow access to the Saisei API using the credentials
352 set in the export options.
353
354 =cut
355
356 =head2 api_call
357
358 Accepts I<$method>, I<$path>, I<$params> hashref and optional.
359 Places an api call to the specified path and method with the specified params.
360 Returns the decoded json object returned by the api call.
361 Returns empty on failure;  retrieve error messages using L</api_error>.
362
363 =cut
364
365 sub api_call {
366   my ($self,$method,$path,$params) = @_;
367
368   $self->{'__saisei_error'} = '';
369   my $auth_info = $self->option('username') . ':' . $self->option('password');
370   $params ||= {};
371
372   warn "Calling $method on http://"
373     .$self->{Hash}->{machine}.':'.$self->option('port')
374     ."/rest/stm/configurations/running/$path\n" if $self->option('debug');
375
376   my $data = encode_json($params) if keys %{ $params };
377
378   my $client = REST::Client->new();
379   $client->addHeader("Authorization", "Basic ".encode_base64($auth_info));
380   $client->setHost('http://'.$self->{Hash}->{machine}.':'.$self->option('port'));
381   $client->$method('/rest/stm/configurations/running'.$path, $data, { "Content-type" => 'application/json'});
382
383   warn "Response Code is ".$client->responseCode()."\n" if $self->option('debug');
384
385   my $result;
386
387   if ($client->responseCode() eq '200' || $client->responseCode() eq '201') {
388     eval { $result = decode_json($client->responseContent()) };
389     unless ($result) {
390       $self->{'__saisei_error'} = "Error decoding json: $@";
391       return;
392     }
393   }
394   else {
395     $self->{'__saisei_error'} = "Bad response from server during $method: " . $client->responseContent()
396     unless ($method eq "GET");
397     warn "Response Content is\n".$client->responseContent."\n" if $self->option('debug');
398     return; 
399   }
400
401   return $result;
402   
403 }
404
405 =head2 api_error
406
407 Returns the error string set by L</Saisei API> methods,
408 or a blank string if most recent call produced no errors.
409
410 =cut
411
412 sub api_error {
413   my $self = shift;
414   return $self->{'__saisei_error'} || '';
415 }
416
417 =head2 api_get_policies
418
419 Gets a list of global policies.
420
421 =cut
422
423 sub api_get_policies {
424   my $self = shift;
425
426   my $get_policies = $self->api_call("GET", '/policies/?token=1&order=name&start=0&limit=20&select=name%2Cpercent_rate%2Cassured%2C');
427   return if $self->api_error;
428   $self->{'__saisei_error'} = "Did not receive any global policies"
429     unless $get_policies;
430
431   return $get_policies->{collection};
432 }
433
434 =head2 api_get_rateplan
435
436 Gets rateplan info for specific rateplan.
437
438 =cut
439
440 sub api_get_rateplan {
441   my $self = shift;
442   my $rateplan = shift;
443
444   my $get_rateplan = $self->api_call("GET", "/rate_plans/$rateplan");
445   return if $self->api_error;
446
447   return $get_rateplan;
448 }
449
450 =head2 api_get_user
451
452 Gets user info for specific user.
453
454 =cut
455
456 sub api_get_user {
457   my $self = shift;
458   my $user = shift;
459
460   my $get_user = $self->api_call("GET", "/users/$user");
461   return if $self->api_error;
462
463   return $get_user;
464 }
465
466 =head2 api_get_accesspoint
467
468 Gets user info for specific access point.
469
470 =cut
471
472 sub api_get_accesspoint {
473   my $self = shift;
474   my $accesspoint = shift;
475
476   my $get_accesspoint = $self->api_call("GET", "/access_points/$accesspoint");
477   return if $self->api_error;
478
479   return $get_accesspoint;
480 }
481
482 =head2 api_get_host
483
484 Gets user info for specific host.
485
486 =cut
487
488 sub api_get_host {
489   my $self = shift;
490   my $ip = shift;
491
492   my $get_host = $self->api_call("GET", "/hosts/$ip");
493
494   return if $self->api_error;
495
496   return $get_host;
497 }
498
499 =head2 api_create_rateplan
500
501 Creates a rateplan.
502
503 =cut
504
505 sub api_create_rateplan {
506   my ($self, $svc, $rateplan) = @_;
507
508   $self->{'__saisei_error'} = "No downrate listed for service $rateplan" if !$svc->{Hash}->{speed_down};
509   $self->{'__saisei_error'} = "No uprate listed for service $rateplan" if !$svc->{Hash}->{speed_up};
510
511   my $new_rateplan = $self->api_call(
512       "PUT", 
513       "/rate_plans/$rateplan",
514       {
515         'downstream_rate' => $svc->{Hash}->{speed_down},
516         'upstream_rate' => $svc->{Hash}->{speed_up},
517       },
518   ) unless $self->{'__saisei_error'};
519
520   $self->{'__saisei_error'} = "Rate Plan not created"
521     unless ($new_rateplan || $self->{'__saisei_error'});
522
523   return $new_rateplan;
524
525 }
526
527 =head2 api_modify_rateplan
528
529 Modify a new rateplan.
530
531 =cut
532
533 sub api_modify_rateplan {
534   my ($self,$svc,$rateplan_name) = @_;
535
536   # get policy list
537   my $policies = $self->api_get_policies();
538
539   foreach my $policy (@$policies) {
540     my $policyname = $policy->{name};
541     my $rate_multiplier = '';
542     if ($policy->{background}) { $rate_multiplier = ".01"; }
543     my $modified_rateplan = $self->api_call(
544       "PUT", 
545       "/rate_plans/$rateplan_name/partitions/$policyname",
546       {
547         'restricted'      =>  $policy->{assured},         # policy_assured_flag
548         'rate_multiplier' => $rate_multiplier,           # policy_background 0.1
549         'rate'            =>  $policy->{percent_rate}, # policy_percent_rate
550       },
551     );
552
553     $self->{'__saisei_error'} = "Rate Plan not modified after create"
554       unless ($modified_rateplan || $self->{'__saisei_error'}); # should never happen
555     
556   }
557
558   return;
559  
560 }
561
562 =head2 api_modify_existing_rateplan
563
564 Modify a existing rateplan.
565
566 =cut
567
568 sub api_modify_existing_rateplan {
569   my ($self,$svc,$rateplan_name) = @_;
570
571   my $modified_rateplan = $self->api_call(
572     "PUT",
573     "/rate_plans/$rateplan_name",
574     {
575       'downstream_rate' => $svc->{Hash}->{speed_down},
576       'upstream_rate' => $svc->{Hash}->{speed_up},
577     },
578   );
579
580     $self->{'__saisei_error'} = "Rate Plan not modified"
581       unless ($modified_rateplan || $self->{'__saisei_error'}); # should never happen
582
583   return;
584
585 }
586
587 =head2 api_create_user
588
589 Creates a user.
590
591 =cut
592
593 sub api_create_user {
594   my ($self,$user, $description) = @_;
595
596   my $new_user = $self->api_call(
597       "PUT", 
598       "/users/$user",
599       {
600         'description' => $description,
601       },
602   );
603
604   $self->{'__saisei_error'} = "User not created"
605     unless ($new_user || $self->{'__saisei_error'}); # should never happen
606
607   return $new_user;
608
609 }
610
611 =head2 api_create_accesspoint
612
613 Creates a access point.
614
615 =cut
616
617 sub api_create_accesspoint {
618   my ($self,$accesspoint, $upratelimit, $downratelimit) = @_;
619
620   # this has not been tested, but should work, if needed.
621   my $new_accesspoint = $self->api_call(
622       "PUT",
623       "/access_points/$accesspoint",
624       {
625          'downstream_rate_limit' => $downratelimit,
626          'upstream_rate_limit' => $upratelimit,
627       },
628   );
629
630   $self->{'__saisei_error'} = "Access point not created"
631     unless ($new_accesspoint || $self->{'__saisei_error'}); # should never happen
632   return;
633
634 }
635
636 =head2 api_modify_accesspoint
637
638 Modify a new access point.
639
640 =cut
641
642 sub api_modify_accesspoint {
643   my ($self, $accesspoint, $uplink) = @_;
644
645   my $modified_accesspoint = $self->api_call(
646     "PUT",
647     "/access_points/$accesspoint",
648     {
649       'uplink' => $uplink, # name of attached access point
650     },
651   );
652
653   $self->{'__saisei_error'} = "Rate Plan not modified"
654     unless ($modified_accesspoint || $self->{'__saisei_error'}); # should never happen
655
656   return;
657
658 }
659
660 =head2 api_modify_existing_accesspoint
661
662 Modify a existing accesspoint.
663
664 =cut
665
666 sub api_modify_existing_accesspoint {
667   my ($self, $accesspoint, $uplink, $upratelimit, $downratelimit) = @_;
668
669   my $modified_accesspoint = $self->api_call(
670     "PUT",
671     "/access_points/$accesspoint",
672     {
673       'downstream_rate_limit' => $downratelimit,
674       'upstream_rate_limit' => $upratelimit,
675 #      'uplink' => $uplink, # name of attached access point
676     },
677   );
678
679     $self->{'__saisei_error'} = "Access point not modified"
680       unless ($modified_accesspoint || $self->{'__saisei_error'}); # should never happen
681
682   return;
683
684 }
685
686 =head2 api_add_host_to_user
687
688 ties host to user, rateplan and default access point.
689
690 =cut
691
692 sub api_add_host_to_user {
693   my ($self,$user, $rateplan, $ip, $accesspoint) = @_;
694
695   my $new_host = $self->api_call(
696       "PUT", 
697       "/hosts/$ip",
698       {
699         'user'      => $user,
700         'rate_plan' => $rateplan,
701         'access_point' => $accesspoint,
702       },
703   );
704
705   $self->{'__saisei_error'} = "Host not created"
706     unless ($new_host || $self->{'__saisei_error'}); # should never happen
707
708   return $new_host;
709
710 }
711
712 =head2 api_delete_host_to_user
713
714 unties host from user and rateplan.
715 this will set the host entry at Saisei to the default rate plan with the user and access point set to <none>.
716
717 =cut
718
719 sub api_delete_host_to_user {
720   my ($self,$user, $rateplan, $ip) = @_;
721
722   my $default_rate_plan = $self->api_call("GET", '?token=1&select=default_rate_plan');
723     return if $self->api_error;
724   $self->{'__saisei_error'} = "Did not receive a default rate plan"
725     unless $default_rate_plan;
726
727   my $default_rateplan_name = $default_rate_plan->{collection}->[0]->{default_rate_plan}->{link}->{name};
728
729   my $delete_host = $self->api_call(
730       "PUT",
731       "/hosts/$ip",
732       {
733         'user'          => '<none>',
734         'access_point'  => '<none>',
735         'rate_plan'     => $default_rateplan_name,
736       },
737   );
738
739   $self->{'__saisei_error'} = "Host not created"
740     unless ($delete_host || $self->{'__saisei_error'}); # should never happen
741
742   return $delete_host;
743
744 }
745
746 sub process_tower {
747   my ($self, $opt) = @_;
748
749   my $existing_tower_ap;
750   my $tower_name = $opt->{tower_name};
751
752   #check if tower has been set up as an access point.
753   $existing_tower_ap = $self->api_get_accesspoint($tower_name) unless $self->{'__saisei_error'};
754
755   # modify the existing accesspoint if changing tower .
756   $self->api_modify_existing_accesspoint (
757     $tower_name,
758     '', # tower does not have a uplink on sectors.
759     $opt->{tower_uprate_limit},
760     $opt->{tower_downrate_limit},
761   ) if $existing_tower_ap && $opt->{modify_existing};
762
763   #if tower does not exist as an access point create it.
764   $self->api_create_accesspoint(
765       $tower_name,
766       $opt->{tower_uprate_limit},
767       $opt->{tower_downrate_limit}
768   ) unless $existing_tower_ap;
769
770   my $accesspoint = $self->api_get_accesspoint($tower_name);
771
772   return $accesspoint;
773 }
774
775 sub process_sector {
776   my ($self, $opt) = @_;
777
778   my $existing_sector_ap;
779   my $sector_name = $opt->{sector_name};
780
781   #check if sector has been set up as an access point.
782   $existing_sector_ap = $self->api_get_accesspoint($sector_name);
783
784   # modify the existing accesspoint if changing sector .
785   $self->api_modify_existing_accesspoint (
786     $sector_name,
787     $opt->{tower_name},
788     $opt->{sector_uprate_limit},
789     $opt->{sector_downrate_limit},
790   ) if $existing_sector_ap && $opt->{modify_existing};
791
792   #if sector does not exist as an access point create it.
793   $self->api_create_accesspoint(
794     $sector_name,
795     $opt->{sector_uprate_limit},
796     $opt->{sector_downrate_limit},
797   ) unless $existing_sector_ap;
798
799   # Attach newly created sector to it's tower.
800   $self->api_modify_accesspoint($sector_name, $opt->{tower_name}) unless ($self->{'__saisei_error'} || $existing_sector_ap);
801
802   # set access point to existing one or newly created one.
803   my $accesspoint = $existing_sector_ap ? $existing_sector_ap : $self->api_get_accesspoint($sector_name);
804
805   return $accesspoint;
806 }
807
808 sub export_provisioned_services {
809   my $job = shift;
810   my $param = thaw(decode_base64(shift));
811
812   my $part_export = FS::Record::qsearchs('part_export', { 'exportnum' => $param->{export_provisioned_services_exportnum}, } )
813   or die "unknown exportnum $param->{export_provisioned_services_exportnum}";
814   bless $part_export;
815
816   my @svcparts = FS::Record::qsearch({
817     'table' => 'export_svc',
818     'addl_from' => 'LEFT JOIN part_svc USING ( svcpart  ) ',
819     'hashref'   => { 'exportnum' => $param->{export_provisioned_services_exportnum}, },
820   });
821   my $part_count = scalar @svcparts;
822
823   my $parts = join "', '", map { $_->{Hash}->{svcpart} } @svcparts;
824
825   my @svcs = FS::Record::qsearch({
826     'table' => 'cust_svc',
827     'addl_from' => 'LEFT JOIN svc_broadband USING ( svcnum  ) ',
828     'extra_sql' => " WHERE svcpart in ('".$parts."')",
829   }) unless !$parts;
830
831   my $svc_count = scalar @svcs;
832
833   my %status = {};
834   for (my $c=1; $c <=100; $c=$c+1) { $status{int($svc_count * ($c/100))} = $c; }
835
836   my $process_count=0;
837   foreach my $svc (@svcs) {
838     if ($status{$process_count}) { my $s = $status{$process_count}; $job->update_statustext($s); }
839     ## check if service exists as host if not export it.
840     _export_insert($part_export,$svc) unless api_get_host($part_export, $svc->{Hash}->{ip_addr});
841     $process_count++;
842   }
843
844   return;
845
846 }
847
848 =head1 SEE ALSO
849
850 L<FS::part_export>
851
852 =cut
853
854 1;