1 package FS::part_export::saisei;
4 use vars qw( @ISA %info );
5 use base qw( FS::part_export );
6 use Date::Format 'time2str';
18 FS::part_export::saisei
22 Saisei integration for Freeside
26 This export offers basic svc_broadband provisioning for Saisei.
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.
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.
35 To use this export, follow the below instructions:
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.
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.
46 Create a package for the above created service, and order this package for a customer.
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.
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>.
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.
56 This module also provides generic methods for working through the L</Saisei API>.
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 error_url => '/edit/part_export.cgi?',
66 success_message => 'Saisei export of provisioned services successful',
70 tie my %options, 'Tie::IxHash',
71 'port' => { label => 'Port',
73 'username' => { label => 'Saisei API User Name',
75 'password' => { label => 'Saisei API Password',
77 'debug' => { type => 'checkbox',
78 label => 'Enable debug warnings' },
82 'svc' => 'svc_broadband',
83 'desc' => 'Export broadband service/account to Saisei',
84 'options' => \%options,
85 'scripts' => \%scripts,
87 This is a customer integration with Saisei. This will set up a rate plan and tie
88 the rate plan to a host and the access point via the Saisei API when the broadband service is provisioned.
89 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.
91 This will create and modify the rate plans at Saisei as soon as the broadband service attached to this export is created or modified.
92 This will also create and modify an access point at Saisei as soon as the tower is created or modified.
94 To use this export, follow the below instructions:
98 Create a new service definition and set the table to svc_broadband. The service name will become the Saisei rate plan name.
99 Set the upload speed, download speed, and tower to be required for the service. This is required to be able to export the service to Saisei.
100 Attach this Saisei export to this service.
104 Create a tower and add a sector to that tower. The sector name will be the name of the access point,
105 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.
106 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.
107 Each sector will be attached to its tower access point using the Saisei uplink field.
111 Create a package for the above created service, and order this package for a customer.
115 Provision the service, making sure to enter the IP address associated with this service, the upload and download speed are correct, and select the tower and sector for it's access point.
116 This provisioned service will then be exported as a host to Saisei.
118 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>.
122 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>.
123 Clicking on this link will export all services attached to this export not currently exported to Saisei.
127 <A HREF="http://www.freeside.biz/mediawiki/index.php/Saisei_provisioning_export" target="_new">Documentation</a>
132 my ($self, $svc_broadband) = @_;
134 my $rateplan_name = $self->get_rateplan_name($svc_broadband);
136 # check for existing rate plan
137 my $existing_rateplan;
138 $existing_rateplan = $self->api_get_rateplan($rateplan_name) unless $self->{'__saisei_error'};
140 # if no existing rate plan create one and modify it.
141 $self->api_create_rateplan($svc_broadband, $rateplan_name) unless $existing_rateplan;
142 $self->api_modify_rateplan($svc_broadband, $rateplan_name) unless ($self->{'__saisei_error'} || $existing_rateplan);
143 return $self->api_error if $self->{'__saisei_error'};
145 # set rateplan to existing one or newly created one.
146 my $rateplan = $existing_rateplan ? $existing_rateplan : $self->api_get_rateplan($rateplan_name);
148 my $username = $svc_broadband->{Hash}->{svcnum};
149 my $description = $svc_broadband->{Hash}->{description};
152 $self->{'__saisei_error'} = 'no username - can not export';
153 return $self->api_error;
156 # check for existing user.
158 $existing_user = $self->api_get_user($username) unless $self->{'__saisei_error'};
160 # if no existing user create one.
161 $self->api_create_user($username, $description) unless $existing_user;
162 return $self->api_error if $self->{'__saisei_error'};
164 # set user to existing one or newly created one.
165 my $user = $existing_user ? $existing_user : $self->api_get_user($username);
168 my $tower_sector = FS::Record::qsearchs({
169 'table' => 'tower_sector',
170 'select' => 'tower.towername,
171 tower.up_rate_limit as tower_upratelimit,
172 tower.down_rate_limit as tower_downratelimit,
173 tower_sector.sectorname,
174 tower_sector.towernum,
175 tower_sector.up_rate_limit as sector_upratelimit,
176 tower_sector.down_rate_limit as sector_downratelimit ',
177 'addl_from' => 'LEFT JOIN tower USING ( towernum )',
179 'sectornum' => $svc_broadband->{Hash}->{sectornum},
183 my $tower_name = $tower_sector->{Hash}->{towername};
184 $tower_name =~ s/\s/_/g;
187 'tower_name' => $tower_name,
188 'tower_num' => $tower_sector->{Hash}->{towernum},
189 'tower_uprate_limit' => $tower_sector->{Hash}->{tower_upratelimit},
190 'tower_downrate_limit' => $tower_sector->{Hash}->{tower_downratelimit},
193 my $tower_ap = process_tower($self, $tower_opt);
194 return $self->api_error if $self->{'__saisei_error'};
196 my $sector_name = $tower_sector->{Hash}->{sectorname};
197 $sector_name =~ s/\s/_/g;
200 'tower_name' => $tower_name,
201 'tower_num' => $tower_sector->{Hash}->{towernum},
202 'sector_name' => $sector_name,
203 'sector_uprate_limit' => $tower_sector->{Hash}->{sector_upratelimit},
204 'sector_downrate_limit' => $tower_sector->{Hash}->{sector_downratelimit},
205 'rateplan' => $rateplan_name,
207 my $accesspoint = process_sector($self, $sector_opt);
208 return $self->api_error if $self->{'__saisei_error'};
210 ## get custnum and pkgpart from cust_pkg for virtual access point
211 my $cust_pkg = FS::Record::qsearchs({
212 'table' => 'cust_pkg',
213 'hashref' => { 'pkgnum' => $svc_broadband->{Hash}->{pkgnum}, },
215 my $virtual_ap_name = $cust_pkg->{Hash}->{custnum}.'_'.$cust_pkg->{Hash}->{pkgpart}.'_'.$svc_broadband->{Hash}->{speed_down}.'_'.$svc_broadband->{Hash}->{speed_up};
217 my $virtual_ap_opt = {
218 'virtual_name' => $virtual_ap_name,
219 'sector_name' => $sector_name,
220 'virtual_uprate_limit' => $svc_broadband->{Hash}->{speed_up},
221 'virtual_downrate_limit' => $svc_broadband->{Hash}->{speed_down},
223 my $virtual_ap = process_virtual_ap($self, $virtual_ap_opt);
224 return $self->api_error if $self->{'__saisei_error'};
226 ## tie host to user add sector name as access point.
227 $self->api_add_host_to_user(
228 $user->{collection}->[0]->{name},
229 $rateplan->{collection}->[0]->{name},
230 $svc_broadband->{Hash}->{ip_addr},
231 $virtual_ap->{collection}->[0]->{name},
232 ) unless $self->{'__saisei_error'};
235 return $self->api_error;
239 sub _export_replace {
240 my ($self, $svc_broadband) = @_;
241 my $error = $self->_export_insert($svc_broadband);
246 my ($self, $svc_broadband) = @_;
248 my $rateplan_name = $self->get_rateplan_name($svc_broadband);
250 my $username = $svc_broadband->{Hash}->{svcnum};
252 ## untie host to user
253 $self->api_delete_host_to_user($username, $rateplan_name, $svc_broadband->{Hash}->{ip_addr}) unless $self->{'__saisei_error'};
258 sub _export_suspend {
259 my ($self, $svc_broadband) = @_;
263 sub _export_unsuspend {
264 my ($self, $svc_broadband) = @_;
269 my ($self, $svc_part) = @_;
271 if ( $FS::svc_Common::noexport_hack ) {
272 carp 'export_partsvc() suppressed by noexport_hack'
273 if $self->option('debug');
278 if ($svc_part->{Hash}->{svc_broadband__speed_down} eq "down" || $svc_part->{Hash}->{svc_broadband__speed_up} eq "up") {
279 for my $type (qw( down up )) {
280 my $speed_type = "broadband_".$type."stream";
281 foreach my $pkg_svc (FS::Record::qsearch({
282 'table' => 'pkg_svc',
283 'select' => 'pkg_svc.*, part_pkg_fcc_option.fccoptionname, part_pkg_fcc_option.optionvalue',
284 'addl_from' => ' LEFT JOIN part_pkg_fcc_option USING (pkgpart) ',
285 'extra_sql' => " WHERE pkg_svc.svcpart = ".$svc_part->{Hash}->{svcpart}." AND pkg_svc.quantity > 0 AND part_pkg_fcc_option.fccoptionname = '".$speed_type."'",
286 })) { $fcc_477_speeds->{
287 $pkg_svc->{Hash}->{pkgpart}}->{$speed_type} = $pkg_svc->{Hash}->{optionvalue} * 1000 unless !$pkg_svc->{Hash}->{optionvalue}; }
291 $fcc_477_speeds->{1}->{broadband_downstream} = $svc_part->{Hash}->{"svc_broadband__speed_down"};
292 $fcc_477_speeds->{1}->{broadband_upstream} = $svc_part->{Hash}->{"svc_broadband__speed_up"};
295 foreach my $key (keys %$fcc_477_speeds) {
297 $svc_part->{Hash}->{speed_down} = $fcc_477_speeds->{$key}->{broadband_downstream};
298 $svc_part->{Hash}->{speed_up} = $fcc_477_speeds->{$key}->{broadband_upstream};
299 $svc_part->{Hash}->{svc_broadband__speed_down} = $fcc_477_speeds->{$key}->{broadband_downstream};
300 $svc_part->{Hash}->{svc_broadband__speed_up} = $fcc_477_speeds->{$key}->{broadband_upstream};
302 my $temp_svc = $svc_part->{Hash};
303 my $svc_broadband = {};
304 map { if ($_ =~ /^svc_broadband__(.*)$/) { $svc_broadband->{Hash}->{$1} = $temp_svc->{$_}; } } keys %$temp_svc;
306 my $rateplan_name = $self->get_rateplan_name($svc_broadband, $svc_part->{Hash}->{svc});
308 # check for existing rate plan
309 my $existing_rateplan;
310 $existing_rateplan = $self->api_get_rateplan($rateplan_name) unless $self->{'__saisei_error'};
312 # Modify the existing rate plan with new service data.
313 $self->api_modify_existing_rateplan($svc_broadband, $rateplan_name) unless ($self->{'__saisei_error'} || !$existing_rateplan);
315 # if no existing rate plan create one and modify it.
316 $self->api_create_rateplan($svc_broadband, $rateplan_name) unless $existing_rateplan;
317 $self->api_modify_rateplan($svc_part, $rateplan_name) unless ($self->{'__saisei_error'} || $existing_rateplan);
321 return $self->api_error;
325 sub export_tower_sector {
326 my ($self, $tower) = @_;
328 if ( $FS::svc_Common::noexport_hack ) {
329 carp 'export_tower_sector() suppressed by noexport_hack'
330 if $self->option('debug');
334 #modify tower or create it.
335 my $tower_name = $tower->{Hash}->{towername};
336 $tower_name =~ s/\s/_/g;
338 'tower_name' => $tower_name,
339 'tower_num' => $tower->{Hash}->{towernum},
340 'tower_uprate_limit' => $tower->{Hash}->{up_rate_limit},
341 'tower_downrate_limit' => $tower->{Hash}->{down_rate_limit},
342 'modify_existing' => '1', # modify an existing access point with this info
345 my $tower_access_point = process_tower($self, $tower_opt);
346 return $tower_access_point if $tower_access_point->{error};
348 #get list of all access points
350 'table' => 'tower_sector',
352 'hashref' => { 'towernum' => $tower->{Hash}->{towernum}, },
355 #for each one modify or create it.
356 foreach my $tower_sector ( FS::Record::qsearch($hash_opt) ) {
357 my $sector_name = $tower_sector->{Hash}->{sectorname};
358 $sector_name =~ s/\s/_/g;
360 'tower_name' => $tower_name,
361 'tower_num' => $tower_sector->{Hash}->{towernum},
362 'sector_name' => $sector_name,
363 'sector_uprate_limit' => $tower_sector->{Hash}->{up_rate_limit},
364 'sector_downrate_limit' => $tower_sector->{Hash}->{down_rate_limit},
365 'modify_existing' => '1', # modify an existing access point with this info
367 my $sector_access_point = process_sector($self, $sector_opt) unless ($sector_name eq "_default");
368 return $sector_access_point if $sector_access_point->{error};
371 return { error => $self->api_error, };
374 ## creates the rateplan name
375 sub get_rateplan_name {
376 my ($self, $svc_broadband, $svc_name) = @_;
378 my $service_part = FS::Record::qsearchs( 'part_svc', { 'svcpart' => $svc_broadband->{Hash}->{svcpart} } ) unless $svc_name;
379 my $service_name = $svc_name ? $svc_name : $service_part->{Hash}->{svc};
381 my $rateplan_name = $service_name . " " . $svc_broadband->{Hash}->{speed_down} . "-" . $svc_broadband->{Hash}->{speed_up};
382 $rateplan_name =~ s/\s/_/g; $rateplan_name =~ s/[^A-Za-z0-9\-_]//g;
384 return $rateplan_name;
389 These methods allow access to the Saisei API using the credentials
390 set in the export options.
396 Accepts I<$method>, I<$path>, I<$params> hashref and optional.
397 Places an api call to the specified path and method with the specified params.
398 Returns the decoded json object returned by the api call.
399 Returns empty on failure; retrieve error messages using L</api_error>.
404 my ($self,$method,$path,$params) = @_;
406 $self->{'__saisei_error'} = '';
407 my $auth_info = $self->option('username') . ':' . $self->option('password');
410 warn "Calling $method on http://"
411 .$self->{Hash}->{machine}.':'.$self->option('port')
412 ."/rest/top/configurations/running/$path\n" if $self->option('debug');
414 my $data = encode_json($params) if keys %{ $params };
416 my $client = REST::Client->new();
417 $client->addHeader("Authorization", "Basic ".encode_base64($auth_info));
418 $client->setHost('http://'.$self->{Hash}->{machine}.':'.$self->option('port'));
419 $client->$method('/rest/top/configurations/running'.$path, $data, { "Content-type" => 'application/json'});
421 warn "Saisei Response Code is ".$client->responseCode()."\n" if $self->option('debug');
425 if ($client->responseCode() eq '200' || $client->responseCode() eq '201') {
426 eval { $result = decode_json($client->responseContent()) };
428 $self->{'__saisei_error'} = "There was an error decoding the JSON data from Saisei. Bad JSON data logged in error log if debug option was set.";
429 warn "Saisei RC 201 Response Content is not json\n".$client->responseContent()."\n" if $self->option('debug');
433 elsif ($client->responseCode() eq '404') {
434 eval { $result = decode_json($client->responseContent()) };
436 $self->{'__saisei_error'} = "There was an error decoding the JSON data from Saisei. Bad JSON data logged in error log if debug option was set.";
437 warn "Saisei RC 404 Response Content is not json\n".$client->responseContent()."\n" if $self->option('debug');
440 ## check if message is for empty hash.
441 my($does_not_exist) = $result->{message} =~ /'(.*)' does not exist$/;
442 $self->{'__saisei_error'} = "Saisei Error: ".$result->{message} unless $does_not_exist;
443 warn "Saisei Response Content is\n".$client->responseContent."\n" if ($self->option('debug') && !$does_not_exist);
446 elsif ($client->responseCode() eq '500') {
447 $self->{'__saisei_error'} = "Could not connect to the Saisei export host machine (".$self->{Hash}->{machine}.':'.$self->option('port').") during $method , we received the responce code: " . $client->responseCode();
448 warn "Saisei Response Content is\n".$client->responseContent."\n" if $self->option('debug');
452 $self->{'__saisei_error'} = "Received Bad response from server during $method , we received responce code: " . $client->responseCode();
453 warn "Saisei Response Content is\n".$client->responseContent."\n" if $self->option('debug');
463 Returns the error string set by L</Saisei API> methods,
464 or a blank string if most recent call produced no errors.
470 return $self->{'__saisei_error'} || '';
473 =head2 api_get_policies
475 Gets a list of global policies.
479 sub api_get_policies {
482 my $get_policies = $self->api_call("GET", '/policies/?token=1&order=name&start=0&limit=20&select=name%2Cpercent_rate%2Cassured%2C');
483 return if $self->api_error;
484 $self->{'__saisei_error'} = "Did not receive any global policies from Saisei."
485 unless $get_policies;
487 return $get_policies->{collection};
490 =head2 api_get_rateplan
492 Gets rateplan info for specific rateplan.
496 sub api_get_rateplan {
498 my $rateplan = shift;
500 my $get_rateplan = $self->api_call("GET", "/rate_plans/$rateplan");
501 return if $self->api_error;
503 return $get_rateplan;
508 Gets user info for specific user.
516 my $get_user = $self->api_call("GET", "/users/$user");
517 return if $self->api_error;
522 =head2 api_get_accesspoint
524 Gets user info for specific access point.
528 sub api_get_accesspoint {
530 my $accesspoint = shift;
532 my $get_accesspoint = $self->api_call("GET", "/access_points/$accesspoint");
533 return if $self->api_error;
535 return $get_accesspoint;
540 Gets user info for specific host.
548 my $get_host = $self->api_call("GET", "/hosts/$ip");
550 return { message => $self->api_error, } if $self->api_error;
555 =head2 api_create_rateplan
561 sub api_create_rateplan {
562 my ($self, $svc, $rateplan) = @_;
564 $self->{'__saisei_error'} = "There is no download speed set for the service !--service,".$svc->{Hash}->{svcnum}.",".$rateplan."--! with host (".$svc->{Hash}->{ip_addr}."). All services that are to be exported to Saisei need to have a download speed set for them." if !$svc->{Hash}->{speed_down};
565 $self->{'__saisei_error'} = "There is no upload speed set for the service !--service,".$svc->{Hash}->{svcnum}.",".$rateplan."--! with host (".$svc->{Hash}->{ip_addr}."). All services that are to be exported to Saisei need to have a upload speed set for them." if !$svc->{Hash}->{speed_up};
567 my $new_rateplan = $self->api_call(
569 "/rate_plans/$rateplan",
571 'downstream_rate' => $svc->{Hash}->{speed_down},
572 'upstream_rate' => $svc->{Hash}->{speed_up},
574 ) unless $self->{'__saisei_error'};
576 $self->{'__saisei_error'} = "Saisei could not create the rate plan $rateplan."
577 unless ($new_rateplan || $self->{'__saisei_error'});
579 return $new_rateplan;
583 =head2 api_modify_rateplan
585 Modify a new rateplan.
589 sub api_modify_rateplan {
590 my ($self,$svc,$rateplan_name) = @_;
593 my $policies = $self->api_get_policies();
595 foreach my $policy (@$policies) {
596 my $policyname = $policy->{name};
597 my $rate_multiplier = '';
598 if ($policy->{background}) { $rate_multiplier = ".01"; }
599 my $modified_rateplan = $self->api_call(
601 "/rate_plans/$rateplan_name/partitions/$policyname",
603 'restricted' => $policy->{assured}, # policy_assured_flag
604 'rate_multiplier' => $rate_multiplier, # policy_background 0.1
605 'rate' => $policy->{percent_rate}, # policy_percent_rate
609 $self->{'__saisei_error'} = "Saisei could not modify the rate plan $rateplan_name after it was created."
610 unless ($modified_rateplan || $self->{'__saisei_error'}); # should never happen
618 =head2 api_modify_existing_rateplan
620 Modify a existing rateplan.
624 sub api_modify_existing_rateplan {
625 my ($self,$svc,$rateplan_name) = @_;
627 $self->{'__saisei_error'} = "There is no download speed set for the service !--service,".$svc->{Hash}->{svcnum}.",".$rateplan_name."--! with host (".$svc->{Hash}->{ip_addr}."). All services that are to be exported to Saisei need to have a download speed set for them." if !$svc->{Hash}->{speed_down};
628 $self->{'__saisei_error'} = "There is no upload speed set for the service !--service,".$svc->{Hash}->{svcnum}.",".$rateplan_name."--! with host (".$svc->{Hash}->{ip_addr}."). All services that are to be exported to Saisei need to have a upload speed set for them." if !$svc->{Hash}->{speed_up};
630 my $modified_rateplan = $self->api_call(
632 "/rate_plans/$rateplan_name",
634 'downstream_rate' => $svc->{Hash}->{speed_down},
635 'upstream_rate' => $svc->{Hash}->{speed_up},
639 $self->{'__saisei_error'} = "Saisei could not modify the rate plan $rateplan_name."
640 unless ($modified_rateplan || $self->{'__saisei_error'}); # should never happen
646 =head2 api_create_user
652 sub api_create_user {
653 my ($self,$user, $description) = @_;
655 my $new_user = $self->api_call(
659 'description' => $description,
663 $self->{'__saisei_error'} = "Saisei could not create the user $user"
664 unless ($new_user || $self->{'__saisei_error'}); # should never happen
670 =head2 api_create_accesspoint
672 Creates a access point.
676 sub api_create_accesspoint {
677 my ($self,$accesspoint, $upratelimit, $downratelimit) = @_;
679 my $new_accesspoint = $self->api_call(
681 "/access_points/$accesspoint",
683 'downstream_rate_limit' => $downratelimit,
684 'upstream_rate_limit' => $upratelimit,
688 $self->{'__saisei_error'} = "Saisei could not create the access point $accesspoint"
689 unless ($new_accesspoint || $self->{'__saisei_error'}); # should never happen
694 =head2 api_modify_accesspoint
696 Modify a new access point.
700 sub api_modify_accesspoint {
701 my ($self, $accesspoint, $uplink) = @_;
703 my $modified_accesspoint = $self->api_call(
705 "/access_points/$accesspoint",
707 'uplink' => $uplink, # name of attached access point
711 $self->{'__saisei_error'} = "Saisei could not modify the access point $accesspoint after it was created."
712 unless ($modified_accesspoint || $self->{'__saisei_error'}); # should never happen
718 =head2 api_modify_existing_accesspoint
720 Modify a existing accesspoint.
724 sub api_modify_existing_accesspoint {
725 my ($self, $accesspoint, $uplink, $upratelimit, $downratelimit) = @_;
727 my $modified_accesspoint = $self->api_call(
729 "/access_points/$accesspoint",
731 'downstream_rate_limit' => $downratelimit,
732 'upstream_rate_limit' => $upratelimit,
733 # 'uplink' => $uplink, # name of attached access point
737 $self->{'__saisei_error'} = "Saisei could not modify the access point $accesspoint."
738 unless ($modified_accesspoint || $self->{'__saisei_error'}); # should never happen
744 =head2 api_add_host_to_user
746 ties host to user, rateplan and default access point.
750 sub api_add_host_to_user {
751 my ($self,$user, $rateplan, $ip, $accesspoint) = @_;
753 my $new_host = $self->api_call(
758 'rate_plan' => $rateplan,
759 'access_point' => $accesspoint,
763 $self->{'__saisei_error'} = "Saisei could not create the host $ip"
764 unless ($new_host || $self->{'__saisei_error'}); # should never happen
770 =head2 api_delete_host_to_user
772 unties host from user and rateplan.
773 this will set the host entry at Saisei to the default rate plan with the user and access point set to <none>.
777 sub api_delete_host_to_user {
778 my ($self,$user, $rateplan, $ip) = @_;
780 my $default_rate_plan = $self->api_call("GET", '?token=1&select=default_rate_plan');
781 return if $self->api_error;
782 $self->{'__saisei_error'} = "Can not delete the host as Saisei did not return a default rate plan. Please make sure Saisei has a default rateplan setup."
783 unless $default_rate_plan;
785 my $default_rateplan_name = $default_rate_plan->{collection}->[0]->{default_rate_plan}->{link}->{name};
787 my $delete_host = $self->api_call(
792 'access_point' => '<none>',
793 'rate_plan' => $default_rateplan_name,
797 $self->{'__saisei_error'} = "Saisei could not delete the host $ip"
798 unless ($delete_host || $self->{'__saisei_error'}); # should never happen
805 my ($self, $opt) = @_;
807 if (!$opt->{tower_uprate_limit} || !$opt->{tower_downrate_limit}) {
808 $self->{'__saisei_error'} = "Could not export tower !--tower,".$opt->{tower_num}.",".$opt->{tower_name}."--! because there was no up or down rates attached to the tower. Saisei requires a up and down rate be attached to each tower.";
809 return { error => $self->api_error, };
812 my $existing_tower_ap;
813 my $tower_name = $opt->{tower_name};
815 #check if tower has been set up as an access point.
816 $existing_tower_ap = $self->api_get_accesspoint($tower_name) unless $self->{'__saisei_error'};
818 # modify the existing accesspoint if changing tower .
819 $self->api_modify_existing_accesspoint (
821 '', # tower does not have a uplink on sectors.
822 $opt->{tower_uprate_limit},
823 $opt->{tower_downrate_limit},
824 ) if $existing_tower_ap->{collection} && $opt->{modify_existing};
826 #if tower does not exist as an access point create it.
827 $self->api_create_accesspoint(
829 $opt->{tower_uprate_limit},
830 $opt->{tower_downrate_limit},
831 ) unless $existing_tower_ap->{collection};
833 my $accesspoint = $self->api_get_accesspoint($tower_name);
835 return { error => $self->api_error, } if $self->api_error;
840 my ($self, $opt) = @_;
842 if (!$opt->{sector_name} || $opt->{sector_name} eq '_default') {
843 $self->{'__saisei_error'} = "No sector attached to Tower (".$opt->{tower_name}.") for service ".$opt->{'rateplan'}.". Saisei requires a tower sector to be attached to each service that is exported to Saisei.";
844 return { error => $self->api_error, };
847 if (!$opt->{sector_uprate_limit} || !$opt->{sector_downrate_limit}) {
848 $self->{'__saisei_error'} = "Could not export sector !--tower,".$opt->{tower_num}.",".$opt->{sector_name}."--! because there was no up or down rates attached to the sector. Saisei requires a up and down rate be attached to each sector.";
849 return { error => $self->api_error, };
852 my $existing_sector_ap;
853 my $sector_name = $opt->{sector_name};
855 #check if sector has been set up as an access point.
856 $existing_sector_ap = $self->api_get_accesspoint($sector_name);
858 # modify the existing accesspoint if changing sector .
859 $self->api_modify_existing_accesspoint (
862 $opt->{sector_uprate_limit},
863 $opt->{sector_downrate_limit},
864 ) if $existing_sector_ap && $opt->{modify_existing};
866 #if sector does not exist as an access point create it.
867 $self->api_create_accesspoint(
869 $opt->{sector_uprate_limit},
870 $opt->{sector_downrate_limit},
871 ) unless $existing_sector_ap;
873 # Attach newly created sector to it's tower.
874 $self->api_modify_accesspoint($sector_name, $opt->{tower_name}) unless ($self->{'__saisei_error'} || $existing_sector_ap);
876 # set access point to existing one or newly created one.
877 my $accesspoint = $existing_sector_ap ? $existing_sector_ap : $self->api_get_accesspoint($sector_name);
879 return { error => $self->api_error, } if $self->api_error;
883 =head2 require_tower_and_sector
885 sets whether the service export requires a sector with it's tower.
889 sub require_tower_and_sector {
893 =head2 tower_sector_required_fields
895 required fields needed for tower and sector export.
899 sub tower_sector_required_fields {
902 'up_rate_limit' => '1',
903 'down_rate_limit' => '1',
906 'up_rate_limit' => '1',
907 'down_rate_limit' => '1',
914 sub required_fields {
915 my @fields = ('svc_broadband__ip_addr_required', 'svc_broadband__speed_up_required', 'svc_broadband__speed_down_required', 'svc_broadband__sectornum_required');
919 sub process_virtual_ap {
920 my ($self, $opt) = @_;
922 my $existing_virtual_ap;
923 my $virtual_name = $opt->{virtual_name};
925 #check if virtual_ap has been set up as an access point.
926 $existing_virtual_ap = $self->api_get_accesspoint($virtual_name);
928 # modify the existing virtual accesspoint if changing it. this should never happen
929 $self->api_modify_existing_accesspoint (
932 $opt->{virtual_uprate_limit},
933 $opt->{virtual_downrate_limit},
934 ) if $existing_virtual_ap && $opt->{modify_existing};
936 #if virtual ap does not exist as an access point create it.
937 $self->api_create_accesspoint(
939 $opt->{virtual_uprate_limit},
940 $opt->{virtual_downrate_limit},
941 ) unless $existing_virtual_ap;
944 if ($existing_virtual_ap && (ref $existing_virtual_ap->{collection}->[0]->{uplink} eq "HASH") && ($existing_virtual_ap->{collection}->[0]->{uplink}->{link}->{name} ne $opt->{sector_name})) {
948 # Attach newly created virtual ap to tower sector ap or if sector has changed.
949 $self->api_modify_accesspoint($virtual_name, $opt->{sector_name}) unless ($self->{'__saisei_error'} || ($existing_virtual_ap && !$update_sector));
951 # set access point to existing one or newly created one.
952 my $accesspoint = $existing_virtual_ap ? $existing_virtual_ap : $self->api_get_accesspoint($virtual_name);
957 sub export_provisioned_services {
961 my $part_export = FS::Record::qsearchs('part_export', { 'exportnum' => $param->{export_provisioned_services_exportnum}, } )
962 or die "You are trying to use an unknown exportnum $param->{export_provisioned_services_exportnum}. This export does not exist.\n";
965 my @svcparts = FS::Record::qsearch({
966 'table' => 'export_svc',
967 'addl_from' => 'LEFT JOIN part_svc USING ( svcpart ) ',
968 'hashref' => { 'exportnum' => $param->{export_provisioned_services_exportnum}, },
970 my $part_count = scalar @svcparts;
972 my $parts = join "', '", map { $_->{Hash}->{svcpart} } @svcparts;
974 my @svcs = FS::Record::qsearch({
975 'table' => 'cust_svc',
976 'addl_from' => 'LEFT JOIN svc_broadband USING ( svcnum ) ',
977 'extra_sql' => " WHERE svcpart in ('".$parts."')",
980 my $svc_count = scalar @svcs;
983 for (my $c=1; $c <=100; $c=$c+1) { $status{int($svc_count * ($c/100))} = $c; }
986 foreach my $svc (@svcs) {
987 if ($status{$process_count}) { my $s = $status{$process_count}; $job->update_statustext($s); }
988 ## check if service exists as host if not export it.
989 my $host = api_get_host($part_export, $svc->{Hash}->{ip_addr});
990 die ("Please double check your credentials as ".$host->{message}."\n") if $host->{message};
991 warn "Exporting service ".$svc->{Hash}->{ip_addr}."\n" if ($part_export->option('debug'));
992 my $export_error = _export_insert($part_export,$svc) unless $host->{collection};
994 warn "Error exporting service ".$svc->{Hash}->{ip_addr}."\n" if ($part_export->option('debug'));
995 die ("$export_error\n");
1004 sub test_export_report {
1005 my ($self, $opts) = @_;
1008 ## check all part services for export errors
1009 my @exports = FS::Record::qsearch('part_export', { 'exporttype' => "saisei", } );
1010 my $export_nums = join "', '", map { $_->{Hash}->{exportnum} } @exports;
1012 my $svc_part_export_error;
1013 my @svcparts = FS::Record::qsearch({
1014 'table' => 'part_svc',
1015 'addl_from' => 'LEFT JOIN export_svc USING ( svcpart ) ',
1016 'extra_sql' => " WHERE export_svc.exportnum in ('".$export_nums."')",
1018 my $part_count = scalar @svcparts;
1021 foreach (@svcparts) {
1022 my $part_error->{'description'} = $_->svc;
1023 $part_error->{'link'} = $opts->{'fsurl'}."/edit/part_svc.cgi?".$_->svcpart;
1025 foreach my $s ('speed_up', 'speed_down') {
1026 my $speed = $_->part_svc_column($s);
1027 if ($speed->columnflag eq "" || $speed->columnflag eq "D") {
1028 $part_error->{'errors'}->{$speed->columnname} = "Field ".$speed->columnname." is not set to be required and can be set while provisioning the service." unless $speed->required eq "Y";
1030 elsif ($speed->columnflag eq "F" || $speed->columnflag eq "S") {
1031 $part_error->{'errors'}->{$speed->columnname} = "Field ".$speed->columnname." is set to auto fill while provisioning the service but there is no value set." unless $speed->columnvalue;
1033 elsif ($speed->columnflag eq "P") {
1034 my $fcc_speed_name = "broadband_".$speed->columnvalue."stream";
1035 foreach my $part_pkg ( FS::Record::qsearchs({
1036 'table' => 'part_pkg',
1037 'addl_from' => 'LEFT JOIN pkg_svc USING ( pkgpart ) ',
1038 'extra_sql' => " WHERE pkg_svc.svcpart = ".$_->svcpart,
1040 my $pkglink = '<a href="'.$opts->{'fsurl'}.'/edit/part_pkg.cgi?'.$part_pkg->pkgpart.'"><FONT COLOR="red"><B>'.$part_pkg->pkg.'</B></FONT></a>';
1041 $part_error->{'errors'}->{$speed->columnname} = "Field ".$speed->columnname." is set to package FCC 477 information, but package ".$pkglink." does not have FCC ".$fcc_speed_name." set."
1042 unless $part_pkg->fcc_option($fcc_speed_name);
1046 $part_error->{'errors'}->{'ip_addr'} = "Field IP Address is not set to required" if $_->part_svc_column("ip_addr")->required ne "Y";
1047 $svc_part_error->{$_->svcpart} = $part_error if $part_error->{'errors'};
1050 $svc_part_export_error->{"services"}->{'description'} = "Service definitions";
1051 $svc_part_export_error->{"services"}->{'count'} = $part_count;
1052 $svc_part_export_error->{"services"}->{'errors'} = $svc_part_error if $svc_part_error;
1054 push @export_error, $svc_part_export_error;
1056 ## check all provisioned cust services for export errors
1057 my $parts = join "', '", map { $_->{Hash}->{svcpart} } @svcparts;
1058 my $cust_svc_export_error;
1059 my @svcs = FS::Record::qsearch({
1060 'table' => 'cust_svc',
1061 'addl_from' => 'LEFT JOIN svc_broadband USING ( svcnum ) ',
1062 'extra_sql' => " WHERE svcpart in ('".$parts."')",
1064 my $svc_count = scalar @svcs;
1068 my $svc_error->{'description'} = $_->description;
1069 $svc_error->{'link'} = $opts->{'fsurl'}."/edit/svc_broadband.cgi?".$_->svcnum;
1071 foreach my $s ('speed_up', 'speed_down', 'ip_addr') {
1072 $svc_error->{'errors'}->{$s} = "Field ".$s." is not set and is required for this service to be exported to Saisei." unless $_->$s;
1075 my $sector = FS::Record::qsearchs({
1076 'table' => 'tower_sector',
1077 'extra_sql' => " WHERE sectornum = ".$_->sectornum." AND sectorname != '_default'",
1078 }) if $_->sectornum;
1080 $svc_error->{'errors'}->{'sectornum'} = "No tower sector is set for this service. There needs to be a tower and sector set to be exported to Saisei.";
1083 foreach my $s ('up_rate_limit', 'down_rate_limit') {
1084 $svc_error->{'errors'}->{'sectornum'} = "The sector ".$sector->description." does not have a ".$s." set. The sector needs a ".$s." set to be exported to Saisei."
1088 $cust_svc_error->{$_->svcnum} = $svc_error if $svc_error->{'errors'};
1091 $cust_svc_export_error->{"provisioned_services"}->{'description'} = "Provisioned services";
1092 $cust_svc_export_error->{"provisioned_services"}->{'count'} = $svc_count;
1093 $cust_svc_export_error->{"provisioned_services"}->{'errors'} = $cust_svc_error if $cust_svc_error;
1095 push @export_error, $cust_svc_export_error;
1098 ## check all towers and sectors for export errors
1099 my $tower_sector_export_error;
1100 my @towers = FS::Record::qsearch({
1103 my $tower_count = scalar @towers;
1107 my $tower_error->{'description'} = $_->towername;
1108 $tower_error->{'link'} = $opts->{'fsurl'}."/edit/tower.html?".$_->towernum;
1110 foreach my $s ('up_rate_limit', 'down_rate_limit') {
1111 $tower_error->{'errors'}->{$s} = "Field ".$s." is not set for the tower, this is required for this tower to be exported to Saisei." unless $_->$s;
1114 my @sectors = FS::Record::qsearch({
1115 'table' => 'tower_sector',
1116 'extra_sql' => " WHERE towernum = ".$_->towernum." AND sectorname != '_default' AND (up_rate_limit IS NULL OR down_rate_limit IS NULL)",
1118 foreach my $sector (@sectors) {
1119 foreach my $s ('up_rate_limit', 'down_rate_limit') {
1120 $tower_error->{'errors'}->{'sector_'.$s} = "The sector ".$sector->description." does not have a ".$s." set. The sector needs a ".$s." set to be exported to Saisei."
1124 $towers_error->{$_->towernum} = $tower_error if $tower_error->{'errors'};
1127 $tower_sector_export_error->{"tower_sector"}->{'description'} = "Tower / Sector";
1128 $tower_sector_export_error->{"tower_sector"}->{'count'} = $tower_count;
1129 $tower_sector_export_error->{"tower_sector"}->{'errors'} = $towers_error if $towers_error;
1131 push @export_error, $tower_sector_export_error;
1133 return [@export_error];