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