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