1 package FS::part_export::saisei;
4 use vars qw( @ISA %info );
5 use base qw( FS::part_export );
6 use Date::Format 'time2str';
17 FS::part_export::saisei
21 Saisei integration for Freeside
25 This export offers basic svc_broadband provisioning for Saisei.
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.
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.
34 To use this export, follow the below instructions:
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.
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.
45 Create a package for the above created service, and order this package for a customer.
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.
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>.
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.
55 This module also provides generic methods for working through the L</Saisei API>.
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>',
67 tie my %options, 'Tie::IxHash',
68 'port' => { label => 'Port',
70 'username' => { label => 'Saisei API User Name',
72 'password' => { label => 'Saisei API Password',
74 'debug' => { type => 'checkbox',
75 label => 'Enable debug warnings' },
79 'svc' => 'svc_broadband',
80 'desc' => 'Export broadband service/account to Saisei',
81 'options' => \%options,
82 'scripts' => \%scripts,
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.
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.
91 To use this export, follow the below instructions:
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.
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.
108 Create a package for the above created service, and order this package for a customer.
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.
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>.
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.
129 my ($self, $svc_broadband) = @_;
131 my $rateplan_name = $self->get_rateplan_name($svc_broadband);
133 # check for existing rate plan
134 my $existing_rateplan;
135 $existing_rateplan = $self->api_get_rateplan($rateplan_name) unless $self->{'__saisei_error'};
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'};
142 # set rateplan to existing one or newly created one.
143 my $rateplan = $existing_rateplan ? $existing_rateplan : $self->api_get_rateplan($rateplan_name);
145 my $username = $svc_broadband->{Hash}->{svcnum};
146 my $description = $svc_broadband->{Hash}->{description};
149 $self->{'__saisei_error'} = 'no username - can not export';
150 return $self->api_error;
153 # check for existing user.
155 $existing_user = $self->api_get_user($username) unless $self->{'__saisei_error'};
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'};
161 # set user to existing one or newly created one.
162 my $user = $existing_user ? $existing_user : $self->api_get_user($username);
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 )',
175 'sectornum' => $svc_broadband->{Hash}->{sectornum},
179 my $tower_name = $tower_sector->{Hash}->{towername};
180 $tower_name =~ s/\s/_/g;
183 'tower_name' => $tower_name,
184 'tower_uprate_limit' => $tower_sector->{Hash}->{tower_upratelimit},
185 'tower_downrate_limit' => $tower_sector->{Hash}->{tower_downratelimit},
188 my $tower_ap = process_tower($self, $tower_opt);
189 return $self->api_error if $self->{'__saisei_error'};
191 my $sector_name = $tower_sector->{Hash}->{sectorname};
192 $sector_name =~ s/\s/_/g;
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},
200 my $accesspoint = process_sector($self, $sector_opt);
201 return $self->api_error if $self->{'__saisei_error'};
203 ## get custnum and pkgpart from cust_pkg for virtual access point
204 my $cust_pkg = FS::Record::qsearchs({
205 'table' => 'cust_pkg',
206 'hashref' => { 'pkgnum' => $svc_broadband->{Hash}->{pkgnum}, },
208 my $virtual_ap_name = $cust_pkg->{Hash}->{custnum}.'_'.$cust_pkg->{Hash}->{pkgpart}.'_'.$svc_broadband->{Hash}->{speed_down}.'_'.$svc_broadband->{Hash}->{speed_up};
210 my $virtual_ap_opt = {
211 'virtual_name' => $virtual_ap_name,
212 'sector_name' => $sector_name,
213 'virtual_uprate_limit' => $svc_broadband->{Hash}->{speed_up},
214 'virtual_downrate_limit' => $svc_broadband->{Hash}->{speed_down},
216 my $virtual_ap = process_virtual_ap($self, $virtual_ap_opt);
217 return $self->api_error if $self->{'__saisei_error'};
219 ## tie host to user add sector name as access point.
220 $self->api_add_host_to_user(
221 $user->{collection}->[0]->{name},
222 $rateplan->{collection}->[0]->{name},
223 $svc_broadband->{Hash}->{ip_addr},
224 $virtual_ap->{collection}->[0]->{name},
225 ) unless $self->{'__saisei_error'};
228 return $self->api_error;
232 sub _export_replace {
233 my ($self, $svc_broadband) = @_;
234 my $error = $self->_export_insert($svc_broadband);
239 my ($self, $svc_broadband) = @_;
241 my $rateplan_name = $self->get_rateplan_name($svc_broadband);
243 my $username = $svc_broadband->{Hash}->{svcnum};
245 ## untie host to user
246 $self->api_delete_host_to_user($username, $rateplan_name, $svc_broadband->{Hash}->{ip_addr}) unless $self->{'__saisei_error'};
251 sub _export_suspend {
252 my ($self, $svc_broadband) = @_;
256 sub _export_unsuspend {
257 my ($self, $svc_broadband) = @_;
262 my ($self, $svc_part) = @_;
265 if ($svc_part->{Hash}->{svc_broadband__speed_down} eq "down" || $svc_part->{Hash}->{svc_broadband__speed_up} eq "up") {
266 for my $type (qw( down up )) {
267 my $speed_type = "broadband_".$type."stream";
268 foreach my $pkg_svc (FS::Record::qsearch({
269 'table' => 'pkg_svc',
270 'select' => 'pkg_svc.*, part_pkg_fcc_option.fccoptionname, part_pkg_fcc_option.optionvalue',
271 'addl_from' => ' LEFT JOIN part_pkg_fcc_option USING (pkgpart) ',
272 'extra_sql' => " WHERE pkg_svc.svcpart = ".$svc_part->{Hash}->{svcpart}." AND pkg_svc.quantity > 0 AND part_pkg_fcc_option.fccoptionname = '".$speed_type."'",
273 })) { $fcc_477_speeds->{
274 $pkg_svc->{Hash}->{pkgpart}}->{$speed_type} = $pkg_svc->{Hash}->{optionvalue} * 1000 unless !$pkg_svc->{Hash}->{optionvalue}; }
278 $fcc_477_speeds->{1}->{broadband_downstream} = $svc_part->{Hash}->{"svc_broadband__speed_down"};
279 $fcc_477_speeds->{1}->{broadband_upstream} = $svc_part->{Hash}->{"svc_broadband__speed_up"};
282 foreach my $key (keys %$fcc_477_speeds) {
284 $svc_part->{Hash}->{speed_down} = $fcc_477_speeds->{$key}->{broadband_downstream};
285 $svc_part->{Hash}->{speed_up} = $fcc_477_speeds->{$key}->{broadband_upstream};
286 $svc_part->{Hash}->{svc_broadband__speed_down} = $fcc_477_speeds->{$key}->{broadband_downstream};
287 $svc_part->{Hash}->{svc_broadband__speed_up} = $fcc_477_speeds->{$key}->{broadband_upstream};
289 my $temp_svc = $svc_part->{Hash};
290 my $svc_broadband = {};
291 map { if ($_ =~ /^svc_broadband__(.*)$/) { $svc_broadband->{Hash}->{$1} = $temp_svc->{$_}; } } keys %$temp_svc;
293 my $rateplan_name = $self->get_rateplan_name($svc_broadband, $svc_part->{Hash}->{svc});
295 # check for existing rate plan
296 my $existing_rateplan;
297 $existing_rateplan = $self->api_get_rateplan($rateplan_name) unless $self->{'__saisei_error'};
299 # Modify the existing rate plan with new service data.
300 $self->api_modify_existing_rateplan($svc_broadband, $rateplan_name) unless ($self->{'__saisei_error'} || !$existing_rateplan);
302 # if no existing rate plan create one and modify it.
303 $self->api_create_rateplan($svc_broadband, $rateplan_name) unless $existing_rateplan;
304 $self->api_modify_rateplan($svc_part, $rateplan_name) unless ($self->{'__saisei_error'} || $existing_rateplan);
308 return $self->api_error;
312 sub export_tower_sector {
313 my ($self, $tower) = @_;
315 #modify tower or create it.
316 my $tower_name = $tower->{Hash}->{towername};
317 $tower_name =~ s/\s/_/g;
319 'tower_name' => $tower_name,
320 'tower_uprate_limit' => $tower->{Hash}->{up_rate_limit},
321 'tower_downrate_limit' => $tower->{Hash}->{down_rate_limit},
322 'modify_existing' => '1', # modify an existing access point with this info
325 my $tower_access_point = process_tower($self, $tower_opt);
327 #get list of all access points
329 'table' => 'tower_sector',
331 'hashref' => { 'towernum' => $tower->{Hash}->{towernum}, },
334 #for each one modify or create it.
335 foreach my $tower_sector ( FS::Record::qsearch($hash_opt) ) {
336 my $sector_name = $tower_sector->{Hash}->{sectorname};
337 $sector_name =~ s/\s/_/g;
339 'tower_name' => $tower_name,
340 'sector_name' => $sector_name,
341 'sector_uprate_limit' => $tower_sector->{Hash}->{up_rate_limit},
342 'sector_downrate_limit' => $tower_sector->{Hash}->{down_rate_limit},
343 'modify_existing' => '1', # modify an existing access point with this info
345 my $sector_access_point = process_sector($self, $sector_opt);
348 return $self->api_error;
351 ## creates the rateplan name
352 sub get_rateplan_name {
353 my ($self, $svc_broadband, $svc_name) = @_;
355 my $service_part = FS::Record::qsearchs( 'part_svc', { 'svcpart' => $svc_broadband->{Hash}->{svcpart} } ) unless $svc_name;
356 my $service_name = $svc_name ? $svc_name : $service_part->{Hash}->{svc};
358 my $rateplan_name = $service_name . " " . $svc_broadband->{Hash}->{speed_down} . "-" . $svc_broadband->{Hash}->{speed_up};
359 $rateplan_name =~ s/\s/_/g;
361 return $rateplan_name;
366 These methods allow access to the Saisei API using the credentials
367 set in the export options.
373 Accepts I<$method>, I<$path>, I<$params> hashref and optional.
374 Places an api call to the specified path and method with the specified params.
375 Returns the decoded json object returned by the api call.
376 Returns empty on failure; retrieve error messages using L</api_error>.
381 my ($self,$method,$path,$params) = @_;
383 $self->{'__saisei_error'} = '';
384 my $auth_info = $self->option('username') . ':' . $self->option('password');
387 warn "Calling $method on http://"
388 .$self->{Hash}->{machine}.':'.$self->option('port')
389 ."/rest/stm/configurations/running/$path\n" if $self->option('debug');
391 my $data = encode_json($params) if keys %{ $params };
393 my $client = REST::Client->new();
394 $client->addHeader("Authorization", "Basic ".encode_base64($auth_info));
395 $client->setHost('http://'.$self->{Hash}->{machine}.':'.$self->option('port'));
396 $client->$method('/rest/stm/configurations/running'.$path, $data, { "Content-type" => 'application/json'});
398 warn "Response Code is ".$client->responseCode()."\n" if $self->option('debug');
402 if ($client->responseCode() eq '200' || $client->responseCode() eq '201') {
403 eval { $result = decode_json($client->responseContent()) };
405 $self->{'__saisei_error'} = "Error decoding json: $@";
410 $self->{'__saisei_error'} = "Bad response from server during $method: " . $client->responseContent()
411 unless ($method eq "GET");
412 warn "Response Content is\n".$client->responseContent."\n" if $self->option('debug');
422 Returns the error string set by L</Saisei API> methods,
423 or a blank string if most recent call produced no errors.
429 return $self->{'__saisei_error'} || '';
432 =head2 api_get_policies
434 Gets a list of global policies.
438 sub api_get_policies {
441 my $get_policies = $self->api_call("GET", '/policies/?token=1&order=name&start=0&limit=20&select=name%2Cpercent_rate%2Cassured%2C');
442 return if $self->api_error;
443 $self->{'__saisei_error'} = "Did not receive any global policies"
444 unless $get_policies;
446 return $get_policies->{collection};
449 =head2 api_get_rateplan
451 Gets rateplan info for specific rateplan.
455 sub api_get_rateplan {
457 my $rateplan = shift;
459 my $get_rateplan = $self->api_call("GET", "/rate_plans/$rateplan");
460 return if $self->api_error;
462 return $get_rateplan;
467 Gets user info for specific user.
475 my $get_user = $self->api_call("GET", "/users/$user");
476 return if $self->api_error;
481 =head2 api_get_accesspoint
483 Gets user info for specific access point.
487 sub api_get_accesspoint {
489 my $accesspoint = shift;
491 my $get_accesspoint = $self->api_call("GET", "/access_points/$accesspoint");
492 return if $self->api_error;
494 return $get_accesspoint;
499 Gets user info for specific host.
507 my $get_host = $self->api_call("GET", "/hosts/$ip");
509 return if $self->api_error;
514 =head2 api_create_rateplan
520 sub api_create_rateplan {
521 my ($self, $svc, $rateplan) = @_;
523 $self->{'__saisei_error'} = "No downrate listed for service $rateplan" if !$svc->{Hash}->{speed_down};
524 $self->{'__saisei_error'} = "No uprate listed for service $rateplan" if !$svc->{Hash}->{speed_up};
526 my $new_rateplan = $self->api_call(
528 "/rate_plans/$rateplan",
530 'downstream_rate' => $svc->{Hash}->{speed_down},
531 'upstream_rate' => $svc->{Hash}->{speed_up},
533 ) unless $self->{'__saisei_error'};
535 $self->{'__saisei_error'} = "Rate Plan not created"
536 unless ($new_rateplan || $self->{'__saisei_error'});
538 return $new_rateplan;
542 =head2 api_modify_rateplan
544 Modify a new rateplan.
548 sub api_modify_rateplan {
549 my ($self,$svc,$rateplan_name) = @_;
552 my $policies = $self->api_get_policies();
554 foreach my $policy (@$policies) {
555 my $policyname = $policy->{name};
556 my $rate_multiplier = '';
557 if ($policy->{background}) { $rate_multiplier = ".01"; }
558 my $modified_rateplan = $self->api_call(
560 "/rate_plans/$rateplan_name/partitions/$policyname",
562 'restricted' => $policy->{assured}, # policy_assured_flag
563 'rate_multiplier' => $rate_multiplier, # policy_background 0.1
564 'rate' => $policy->{percent_rate}, # policy_percent_rate
568 $self->{'__saisei_error'} = "Rate Plan not modified after create"
569 unless ($modified_rateplan || $self->{'__saisei_error'}); # should never happen
577 =head2 api_modify_existing_rateplan
579 Modify a existing rateplan.
583 sub api_modify_existing_rateplan {
584 my ($self,$svc,$rateplan_name) = @_;
586 my $modified_rateplan = $self->api_call(
588 "/rate_plans/$rateplan_name",
590 'downstream_rate' => $svc->{Hash}->{speed_down},
591 'upstream_rate' => $svc->{Hash}->{speed_up},
595 $self->{'__saisei_error'} = "Rate Plan not modified"
596 unless ($modified_rateplan || $self->{'__saisei_error'}); # should never happen
602 =head2 api_create_user
608 sub api_create_user {
609 my ($self,$user, $description) = @_;
611 my $new_user = $self->api_call(
615 'description' => $description,
619 $self->{'__saisei_error'} = "User not created"
620 unless ($new_user || $self->{'__saisei_error'}); # should never happen
626 =head2 api_create_accesspoint
628 Creates a access point.
632 sub api_create_accesspoint {
633 my ($self,$accesspoint, $upratelimit, $downratelimit) = @_;
635 # this has not been tested, but should work, if needed.
636 my $new_accesspoint = $self->api_call(
638 "/access_points/$accesspoint",
640 'downstream_rate_limit' => $downratelimit,
641 'upstream_rate_limit' => $upratelimit,
645 $self->{'__saisei_error'} = "Access point not created"
646 unless ($new_accesspoint || $self->{'__saisei_error'}); # should never happen
651 =head2 api_modify_accesspoint
653 Modify a new access point.
657 sub api_modify_accesspoint {
658 my ($self, $accesspoint, $uplink) = @_;
660 my $modified_accesspoint = $self->api_call(
662 "/access_points/$accesspoint",
664 'uplink' => $uplink, # name of attached access point
668 $self->{'__saisei_error'} = "Rate Plan not modified"
669 unless ($modified_accesspoint || $self->{'__saisei_error'}); # should never happen
675 =head2 api_modify_existing_accesspoint
677 Modify a existing accesspoint.
681 sub api_modify_existing_accesspoint {
682 my ($self, $accesspoint, $uplink, $upratelimit, $downratelimit) = @_;
684 my $modified_accesspoint = $self->api_call(
686 "/access_points/$accesspoint",
688 'downstream_rate_limit' => $downratelimit,
689 'upstream_rate_limit' => $upratelimit,
690 # 'uplink' => $uplink, # name of attached access point
694 $self->{'__saisei_error'} = "Access point not modified"
695 unless ($modified_accesspoint || $self->{'__saisei_error'}); # should never happen
701 =head2 api_add_host_to_user
703 ties host to user, rateplan and default access point.
707 sub api_add_host_to_user {
708 my ($self,$user, $rateplan, $ip, $accesspoint) = @_;
710 my $new_host = $self->api_call(
715 'rate_plan' => $rateplan,
716 'access_point' => $accesspoint,
720 $self->{'__saisei_error'} = "Host not created"
721 unless ($new_host || $self->{'__saisei_error'}); # should never happen
727 =head2 api_delete_host_to_user
729 unties host from user and rateplan.
730 this will set the host entry at Saisei to the default rate plan with the user and access point set to <none>.
734 sub api_delete_host_to_user {
735 my ($self,$user, $rateplan, $ip) = @_;
737 my $default_rate_plan = $self->api_call("GET", '?token=1&select=default_rate_plan');
738 return if $self->api_error;
739 $self->{'__saisei_error'} = "Did not receive a default rate plan"
740 unless $default_rate_plan;
742 my $default_rateplan_name = $default_rate_plan->{collection}->[0]->{default_rate_plan}->{link}->{name};
744 my $delete_host = $self->api_call(
749 'access_point' => '<none>',
750 'rate_plan' => $default_rateplan_name,
754 $self->{'__saisei_error'} = "Host not created"
755 unless ($delete_host || $self->{'__saisei_error'}); # should never happen
762 my ($self, $opt) = @_;
764 my $existing_tower_ap;
765 my $tower_name = $opt->{tower_name};
767 #check if tower has been set up as an access point.
768 $existing_tower_ap = $self->api_get_accesspoint($tower_name) unless $self->{'__saisei_error'};
770 # modify the existing accesspoint if changing tower .
771 $self->api_modify_existing_accesspoint (
773 '', # tower does not have a uplink on sectors.
774 $opt->{tower_uprate_limit},
775 $opt->{tower_downrate_limit},
776 ) if $existing_tower_ap && $opt->{modify_existing};
778 #if tower does not exist as an access point create it.
779 $self->api_create_accesspoint(
781 $opt->{tower_uprate_limit},
782 $opt->{tower_downrate_limit}
783 ) unless $existing_tower_ap;
785 my $accesspoint = $self->api_get_accesspoint($tower_name);
791 my ($self, $opt) = @_;
793 my $existing_sector_ap;
794 my $sector_name = $opt->{sector_name};
796 #check if sector has been set up as an access point.
797 $existing_sector_ap = $self->api_get_accesspoint($sector_name);
799 # modify the existing accesspoint if changing sector .
800 $self->api_modify_existing_accesspoint (
803 $opt->{sector_uprate_limit},
804 $opt->{sector_downrate_limit},
805 ) if $existing_sector_ap && $opt->{modify_existing};
807 #if sector does not exist as an access point create it.
808 $self->api_create_accesspoint(
810 $opt->{sector_uprate_limit},
811 $opt->{sector_downrate_limit},
812 ) unless $existing_sector_ap;
814 # Attach newly created sector to it's tower.
815 $self->api_modify_accesspoint($sector_name, $opt->{tower_name}) unless ($self->{'__saisei_error'} || $existing_sector_ap);
817 # set access point to existing one or newly created one.
818 my $accesspoint = $existing_sector_ap ? $existing_sector_ap : $self->api_get_accesspoint($sector_name);
823 sub process_virtual_ap {
824 my ($self, $opt) = @_;
826 my $existing_virtual_ap;
827 my $virtual_name = $opt->{virtual_name};
829 #check if sector has been set up as an access point.
830 $existing_virtual_ap = $self->api_get_accesspoint($virtual_name);
832 # modify the existing virtual accesspoint if changing it. this should never happen
833 $self->api_modify_existing_accesspoint (
836 $opt->{virtual_uprate_limit},
837 $opt->{virtual_downrate_limit},
838 ) if $existing_virtual_ap && $opt->{modify_existing};
840 #if virtual ap does not exist as an access point create it.
841 $self->api_create_accesspoint(
843 $opt->{virtual_uprate_limit},
844 $opt->{virtual_downrate_limit},
845 ) unless $existing_virtual_ap;
848 if ($existing_virtual_ap && ($existing_virtual_ap->{collection}->[0]->{uplink}->{link}->{name} ne $opt->{sector_name})) {
852 # Attach newly created virtual ap to tower sector ap or if sector has changed.
853 $self->api_modify_accesspoint($virtual_name, $opt->{sector_name}) unless ($self->{'__saisei_error'} || ($existing_virtual_ap && !$update_sector));
855 # set access point to existing one or newly created one.
856 my $accesspoint = $existing_virtual_ap ? $existing_virtual_ap : $self->api_get_accesspoint($virtual_name);
861 sub export_provisioned_services {
865 my $part_export = FS::Record::qsearchs('part_export', { 'exportnum' => $param->{export_provisioned_services_exportnum}, } )
866 or die "unknown exportnum $param->{export_provisioned_services_exportnum}";
869 my @svcparts = FS::Record::qsearch({
870 'table' => 'export_svc',
871 'addl_from' => 'LEFT JOIN part_svc USING ( svcpart ) ',
872 'hashref' => { 'exportnum' => $param->{export_provisioned_services_exportnum}, },
874 my $part_count = scalar @svcparts;
876 my $parts = join "', '", map { $_->{Hash}->{svcpart} } @svcparts;
878 my @svcs = FS::Record::qsearch({
879 'table' => 'cust_svc',
880 'addl_from' => 'LEFT JOIN svc_broadband USING ( svcnum ) ',
881 'extra_sql' => " WHERE svcpart in ('".$parts."')",
884 my $svc_count = scalar @svcs;
887 for (my $c=1; $c <=100; $c=$c+1) { $status{int($svc_count * ($c/100))} = $c; }
890 foreach my $svc (@svcs) {
891 if ($status{$process_count}) { my $s = $status{$process_count}; $job->update_statustext($s); }
892 ## check if service exists as host if not export it.
893 _export_insert($part_export,$svc) unless api_get_host($part_export, $svc->{Hash}->{ip_addr});