1 package FS::part_export::saisei;
4 use base qw( FS::part_export );
5 use vars qw( @ISA %info );
6 use Date::Format 'time2str';
8 use Net::HTTPS::Any qw(https_post);
15 #@ISA = qw( FS::part_export::http );
21 FS::part_export::saisei
25 Saisei integration for Freeside
29 This export offers basic svc_broadband provisioning for Saisei.
31 This module also provides generic methods for working through the L</Saisei API>.
35 tie my %options, 'Tie::IxHash',
36 'username' => { label => 'User Name',
38 'password' => { label => 'Password',
40 'host' => { label => 'Host',
41 default => 'STM IP ADDRESS' },
42 'port' => { label => 'Port',
44 'customer_name' => { label => 'Customer Name',
45 default => 'FREESIDE CUST $custnum' },
46 'account_id' => { label => 'Account ID',
47 default => 'SVC$svcnum' },
48 'product_id' => { label => 'Account Product ID' },
49 'debug' => { type => 'checkbox',
50 label => 'Enable debug warnings' },
54 'svc' => 'svc_broadband',
55 'desc' => 'Export broadband service/account to Saisei',
56 'options' => \%options,
58 This is customer integration with Saisei.
62 #"/STM_IP:5000/rest/top/configurations/running/" is for http 5029 for https
65 #Users are tracked by their name which gives access to the internal slice data which in turn allows the viewing of Applications and Geo-Locations.
66 #Creating a user name requires a command of the following format: -
67 #'put', 'users/USER_NAME', {'description':description}
68 #When creating a user name it is usual to add a description and since a user attribute set does not normally contain the users plan name it is best to encode it into the description field.
71 my ($self, $svc_broadband) = @_;
72 my $rateplan_name = $svc_broadband->{Hash}->{description};
73 $rateplan_name =~ s/\s/_/g;
76 # load needed info from our end
77 my $cust_main = $svc_broadband->cust_main;
78 return "Could not load service customer" unless $cust_main;
79 my $conf = new FS::Conf;
82 my $policies = $self->api_get_policies();
84 # check for existing rate plan
85 my $existing_rateplan;
86 $existing_rateplan = $self->api_get_rateplan($rateplan_name) unless $self->{'__saisei_error'};
88 # if no existing rate plan create one and modify it.
89 $self->api_create_rateplan($svc_broadband, $rateplan_name) unless $existing_rateplan;
90 $self->api_modify_rateplan($policies->{collection}, $svc_broadband, $rateplan_name) unless ($self->{'__saisei_error'} || $existing_rateplan);
92 # set rateplan to existing one or newly created one.
93 my $rateplan = $existing_rateplan ? $existing_rateplan : $self->api_get_rateplan($rateplan_name);
95 my @email = map { $_->emailaddress } FS::Record::qsearch({
96 'table' => 'cust_contact',
97 'select' => 'emailaddress',
98 'addl_from' => ' JOIN contact_email USING (contactnum)',
99 'hashref' => { 'custnum' => $cust_main->{Hash}->{custnum}, },
101 my $username = $email[0];
102 my $description = $cust_main->{Hash}->{first}." ".$cust_main->{Hash}->{last};
104 # check for existing user.
106 $existing_user = $self->api_get_user($username) unless ( $self->{'__saisei_error'} || !$username);
108 # if no existing user create one.
109 $self->api_create_user($username, $description) unless $existing_user;
111 # set user to existing one or newly created one.
112 my $user = $existing_user ? $existing_user : $self->api_get_user($username);
114 ## add access point ?
117 $self->api_add_host_to_user($user->{collection}->[0]->{name}, $rateplan->{collection}->[0]->{name}, $svc_broadband->{Hash}->{ip_addr}) unless $self->{'__saisei_error'};
119 #die('ending for testing');
124 sub _export_replace {
125 my ($self, $svc_phone) = @_;
130 my ($self, $svc_broadband) = @_;
132 my $cust_main = $svc_broadband->cust_main;
133 return "Could not load service customer" unless $cust_main;
134 my $conf = new FS::Conf;
136 my $rateplan_name = $svc_broadband->{Hash}->{description};
137 $rateplan_name =~ s/\s/_/g;
139 my @email = map { $_->emailaddress } FS::Record::qsearch({
140 'table' => 'cust_contact',
141 'select' => 'emailaddress',
142 'addl_from' => ' JOIN contact_email USING (contactnum)',
143 'hashref' => { 'custnum' => $cust_main->{Hash}->{custnum}, },
145 my $username = $email[0];
148 $self->api_delete_host_to_user($username, $rateplan_name, $svc_broadband->{Hash}->{ip_addr}) unless $self->{'__saisei_error'};
153 sub _export_suspend {
154 my ($self, $svc_phone) = @_;
158 sub _export_unsuspend {
159 my ($self, $svc_phone) = @_;
165 These methods allow access to the Saisei API using the credentials
166 set in the export options.
172 Accepts I<$service>, I<$method>, I<$params> hashref and optional
173 I<$returnfield>. Places an api call to the specified service
174 and method with the specified params. Returns the decoded json
175 object returned by the api call. If I<$returnfield> is specified,
176 returns only that field of the decoded object, and errors out if
177 that field does not exist. Returns empty on failure; retrieve
178 error messages using L</api_error>.
180 Must run L</api_login> first.
185 my ($self,$method,$path,$params) = @_;
186 $self->{'__saisei_error'} = '';
187 my $auth_info = $self->option('username') . ':' . $self->option('password');
190 print "Calling /$method\n" if $self->option('debug');
192 my $data = encode_json($params) if keys %{ $params };
194 my $client = REST::Client->new();
195 $client->addHeader("Authorization", "Basic ".encode_base64($auth_info));
196 $client->setHost('http://'.$self->option('host').':'.$self->option('port'));
197 $client->$method('/rest/stm/configurations/running/'.$path, $data, { "Content-type" => 'application/json'});
201 if ($client->responseCode() eq '200' || $client->responseCode() eq '201') {
202 eval { $result = decode_json($client->responseContent()) };
204 $self->{'__saisei_error'} = "Error decoding json: $@";
209 $self->{'__saisei_error'} = "Bad response from server during $method: " . $client->responseContent();
210 print "My response content fo /$method\n". Dumper($client->responseContent) if $self->option('debug');
220 Returns the error string set by L</PortaOne API> methods,
221 or a blank string if most recent call produced no errors.
227 return $self->{'__saisei_error'} || '';
230 =head2 api_get_policies
232 Gets a list of global policies.
236 sub api_get_policies {
239 my $get_policies = $self->api_call("GET", 'policies/?token=1&order=name&start=0&limit=20&select=name%2Cpercent_rate%2Cassured%2C');
240 return if $self->api_error;
241 $self->{'__saisei_error'} = "Did not receive any global policies"
242 unless $get_policies;
244 return $get_policies;
247 =head2 api_get_rateplan
249 Gets rateplan info for specific rateplan.
253 sub api_get_rateplan {
255 my $rateplan = shift;
257 my $get_rateplan = $self->api_call("GET", "rate_plans/$rateplan");
258 return if $self->api_error;
259 $self->{'__saisei_error'} = "Did not receive any rateplan info"
260 unless $get_rateplan;
262 return $get_rateplan;
267 Gets user info for specific user.
275 my $get_user = $self->api_call("GET", "users/$user");
276 return if $self->api_error;
277 $self->{'__saisei_error'} = "Did not receive any user info"
283 =head2 api_get_accesspoint
285 Gets user info for specific access point.
289 sub api_get_accesspoint {
293 my $get_accesspoint = $self->api_call("GET", "access_points/$accesspoint");
294 return if $self->api_error;
295 $self->{'__saisei_error'} = "Did not receive any user info"
296 unless $get_accesspoint;
301 =head2 api_create_rateplan
307 sub api_create_rateplan {
308 my ($self, $svc, $rateplan) = @_;
310 my $new_rateplan = $self->api_call(
312 "rate_plans/$rateplan",
314 'downstream_rate' => $svc->{Hash}->{speed_down},
315 'upstream_rate' => $svc->{Hash}->{speed_up},
319 $self->{'__saisei_error'} = "Rate Plan not created"
320 unless $new_rateplan; # should never happen
321 return $new_rateplan;
325 =head2 api_modify_rateplan
331 sub api_modify_rateplan {
332 my ($self,$policies,$svc,$rateplan_name) = @_;
334 foreach my $policy (@$policies) {
335 my $policyname = $policy->{name};
336 my $rate_multiplier = '';
337 if ($policy->{background}) { $rate_multiplier = ".01"; }
338 my $modified_rateplan = $self->api_call(
340 "rate_plans/$rateplan_name/partitions/$policyname",
342 'restricted' => $policy->{assured}, # policy_assured_flag
343 'rate_multiplier' => $rate_multiplier, # policy_background 0.1
344 'rate' => $policy->{percent_rate}, # policy_percent_rate
348 $self->{'__saisei_error'} = "Rate Plan not modified"
349 unless $modified_rateplan; # should never happen
357 =head2 api_create_user
363 sub api_create_user {
364 my ($self,$user, $description) = @_;
366 my $new_user = $self->api_call(
370 'description' => $description,
374 $self->{'__saisei_error'} = "User not created"
375 unless $new_user; # should never happen
381 =head2 api_create_accesspoint
383 Creates a access point.
387 sub api_create_accesspoint {
388 my ($self,$accesspoint) = @_;
390 my $new_accesspoint = $self->api_call(
392 "access_points/$accesspoint",
394 'description' => 'my description',
398 $self->{'__saisei_error'} = "Access point not created"
399 unless $new_accesspoint; # should never happen
404 =head2 api_add_host_to_user
406 ties host to user and rateplan.
410 sub api_add_host_to_user {
411 my ($self,$user, $rateplan, $ip) = @_;
413 my $new_host = $self->api_call(
418 'rate_plan' => $rateplan,
422 $self->{'__saisei_error'} = "Host not created"
423 unless $new_host; # should never happen
429 =head2 api_add_host_to_user
431 ties host to user and rateplan.
435 sub api_delete_host_to_user {
436 my ($self,$user, $rateplan, $ip) = @_;
438 my $delete_host = $self->api_call("DELETE", "hosts/$ip");
440 $self->{'__saisei_error'} = "Host not created"
441 unless $delete_host; # should never happen