3ba1b27627dc1caf4f6c0c260e04060a1837055b
[freeside.git] / FS / FS / part_export / prizm.pm
1 package FS::part_export::prizm;
2
3 use vars qw(@ISA %info %options $DEBUG);
4 use Tie::IxHash;
5 use FS::Record qw(fields dbh);
6 use FS::part_export;
7
8 @ISA = qw(FS::part_export);
9 $DEBUG = 1;
10
11 tie %options, 'Tie::IxHash',
12   'url'      => { label => 'Northbound url', default=>'https://localhost:8443/prizm/nbi' },
13   'user'     => { label => 'Northbound username', default=>'nbi' },
14   'password' => { label => 'Password', default => '' },
15   'ems'      => { label => 'Full EMS', type => 'checkbox' },
16   'always_bam' => { label => 'Always activate/suspend authentication', type => 'checkbox' },
17   'element_name_length' => { label => 'Size of siteName (best left blank)' },
18 ;
19
20 my $notes = <<'EOT';
21 Real-time export of <b>svc_broadband</b>, <b>cust_pkg</b>, and <b>cust_main</b>
22 record data to Motorola
23 <a href="http://motorola.canopywireless.com/products/prizm/">Canopy Prizm
24 software</a> via the Northbound interface.<br><br>
25
26 Freeside will attempt to create an element in an existing network with the
27 values provided in svc_broadband.  Of particular interest are
28 <ul>
29   <li> mac address - used to identify the element
30   <li> vlan profile - an exact match for a vlan profiles defined in prizm
31   <li> ip address - defines the management ip address of the prizm element
32   <li> latitude - GPS latitude
33   <li> longitude - GPS longitude
34   <li> altitude - GPS altitude
35 </ul>
36
37 In addition freeside attempts to set the service plan name in prizm to the
38 name of the package in which the service resides.
39
40 The service is associated with a customer in prizm as well, and freeside
41 will create the customer should none already exist with import id matching
42 the freeside customer number.  The following fields are set.
43
44 <ul>
45   <li> importId - the freeside customer number
46   <li> customerType - freeside
47   <li> customerName - the name associated with the freeside shipping address
48   <li> address1 - the shipping address
49   <li> address2
50   <li> city
51   <li> state
52   <li> zipCode
53   <li> country
54   <li> workPhone - the daytime phone number
55   <li> homePhone - the night phone number
56   <li> freesideId - the freeside customer number
57 </ul>
58
59   Additionally set on the element are
60 <ul>
61   <li> Site Name - The shipping name followed by the service broadband description field
62   <li> Site Location - the shipping address
63   <li> Site Contact - the daytime and night phone numbers
64 </ul>
65
66 Freeside provisions, suspends, and unsuspends elements BAM only unless the
67 'Full EMS' checkbox is checked.<br><br>
68
69 When freeside provisions an element the siteName is copied internally by
70 prizm in such a manner that it is possible for the value to exceed the size
71 of the column used in the prizm database.  Therefore freeside truncates
72 by default this value to 50 characters.  It is thought that this
73 column is the account_name column of the element_user_account table.  It
74 may be possible to lift this limit by modifying the prizm database and
75 setting a new appropriate value on this export.  This is untested and
76 possibly harmful.
77
78 EOT
79
80 %info = (
81   'svc'      => 'svc_broadband',
82   'desc'     => 'Real-time export to Northbound Interface',
83   'options'  => \%options,
84   'nodomain' => 'Y',
85   'notes'    => $notes,
86 );
87
88 sub prizm_command {
89   my ($self,$namespace,$method) = (shift,shift,shift);
90
91   eval "use Net::Prizm qw(CustomerInfo PrizmElement);";
92   die $@ if $@;
93
94   my $prizm = new Net::Prizm (
95     namespace => $namespace,
96     url => $self->option('url'),
97     user => $self->option('user'),
98     password => $self->option('password'),
99   );
100   
101   $prizm->$method(@_);
102 }
103
104 sub queued_prizm_command {  # subroutine
105   my( $url, $user, $password, $namespace, $method, @args ) = @_;
106
107   eval "use Net::Prizm qw(CustomerInfo PrizmElement);";
108   die $@ if $@;
109
110   my $prizm = new Net::Prizm (
111     namespace => $namespace,
112     url => $url,
113     user => $user,
114     password => $password,
115   );
116   
117   $err_or_som = $prizm->$method( @args);
118
119   die $err_or_som
120     unless ref($err_or_som);
121
122   '';
123
124 }
125
126 sub _export_insert {
127   my( $self, $svc ) = ( shift, shift );
128
129   my $cust_main = $svc->cust_svc->cust_pkg->cust_main;
130
131   my $err_or_som = $self->prizm_command('CustomerIfService', 'getCustomers',
132                                         ['import_id'],
133                                         [$cust_main->custnum],
134                                         ['='],
135                                        );
136   return $err_or_som
137     unless ref($err_or_som);
138
139   my $pre = '';
140   if ( defined $cust_main->dbdef_table->column('ship_last') ) {
141     $pre = $cust_main->ship_last ? 'ship_' : '';
142   }
143   my $name = $pre ? $cust_main->ship_name : $cust_main->name;
144   my $location = join(" ", map { my $method = "$pre$_"; $cust_main->$method }
145                            qw (address1 address2 city state zip)
146                      );
147   my $contact = join(" ", map { my $method = "$pre$_"; $cust_main->$method }
148                           qw (daytime night)
149                      );
150
151   my $pcustomer;
152   if ($err_or_som->result->[0]) {
153     $pcustomer = $err_or_som->result->[0]->customerId;
154   }else{
155     my $chashref = $cust_main->hashref;
156     my $customerinfo = {
157       importId         => $cust_main->custnum,
158       customerName     => $name,
159       customerType     => 'freeside',
160       address1         => $chashref->{"${pre}address1"},
161       address2         => $chashref->{"${pre}address2"},
162       city             => $chashref->{"${pre}city"},
163       state            => $chashref->{"${pre}state"},
164       zipCode          => $chashref->{"${pre}zip"},
165       workPhone        => $chashref->{"${pre}daytime"},
166       homePhone        => $chashref->{"${pre}night"},
167       email            => @{[$cust_main->invoicing_list_emailonly]}[0],
168       extraFieldNames  => [ 'country', 'freesideId',
169                           ],
170       extraFieldValues => [ $chashref->{"${pre}country"}, $cust_main->custnum,
171                           ],
172     };
173
174     $err_or_som = $self->prizm_command('CustomerIfService', 'addCustomer',
175                                        $customerinfo);
176     return $err_or_som
177       unless ref($err_or_som);
178
179     $pcustomer = $err_or_som->result;
180   }
181   warn "multiple prizm customers found for $cust_main->custnum"
182     if scalar(@$pcustomer) > 1;
183
184 #  #kinda big question/expensive
185 #  $err_or_som = $self->prizm_command('NetworkIfService', 'getPrizmElements',
186 #                                     ['Network Default Gateway Address'],
187 #                                     [$svc->addr_block->ip_gateway],
188 #                                     ['='],
189 #                   );
190 #  return $err_or_som
191 #    unless ref($err_or_som);
192 #
193 #  return "No elements in network" unless exists $err_or_som->result->[0];
194
195   my $networkid = 0;
196 #  for (my $i = 0; $i < $err_or_som->result->[0]->attributeNames; $i++) {
197 #    if ($err_or_som->result->[0]->attributeNames->[$i] eq "Network.ID"){
198 #      $networkid = $err_or_som->result->[0]->attributeValues->[$i];
199 #      last;
200 #    }
201 #  }
202
203   my $element_name_length = 50;
204   $element_name_length = $1
205     if $self->option('element_name_length') =~ /^\s*(\d+)\s*$/;
206   $err_or_som = $self->prizm_command('NetworkIfService', 'addProvisionedElement',
207                                       $networkid,
208                                       $svc->mac_addr,
209                                       substr($name . " " . $svc->description,
210                                              0, $element_name_length),
211                                       $location,
212                                       $contact,
213                                       sprintf("%032X", $svc->authkey),
214                                       $svc->cust_svc->cust_pkg->part_pkg->pkg,
215                                       $svc->vlan_profile,
216                                       ($self->option('ems') ? 1 : 0 ),
217                                      );
218   return $err_or_som
219     unless ref($err_or_som);
220
221   my (@names) = ('Management IP',
222                  'GPS Latitude',
223                  'GPS Longitude',
224                  'GPS Altitude',
225                  'Site Name',
226                  'Site Location',
227                  'Site Contact',
228                  );
229   my (@values) = ($svc->ip_addr,
230                   $svc->latitude,
231                   $svc->longitude,
232                   $svc->altitude,
233                   $name . " " . $svc->description,
234                   $location,
235                   $contact,
236                   );
237   $element = $err_or_som->result->elementId;
238   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfig',
239                                      [ $element ],
240                                      \@names,
241                                      \@values,
242                                      0,
243                                      1,
244                                     );
245   return $err_or_som
246     unless ref($err_or_som);
247
248   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
249                                      [ $element ],
250                                      $svc->vlan_profile,
251                                      0,
252                                      1,
253                                     );
254   return $err_or_som
255     unless ref($err_or_som);
256
257   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
258                                      [ $element ],
259                                      $svc->cust_svc->cust_pkg->part_pkg->pkg,
260                                      0,
261                                      1,
262                                     );
263   return $err_or_som
264     unless ref($err_or_som);
265
266   $err_or_som = $self->prizm_command('NetworkIfService',
267                                      'activateNetworkElements',
268                                      [ $element ],
269                                      1,
270                                      ( $self->option('ems') ? 1 : 0 ),
271                                     );
272
273   return $err_or_som
274     unless ref($err_or_som);
275
276   $err_or_som = $self->prizm_command('CustomerIfService',
277                                      'addElementToCustomer',
278                                      0,
279                                      $cust_main->custnum,
280                                      0,
281                                      $svc->mac_addr,
282                                     );
283
284   return $err_or_som
285     unless ref($err_or_som);
286
287   '';
288 }
289
290 sub _export_delete {
291   my( $self, $svc ) = ( shift, shift );
292
293   my $oldAutoCommit = $FS::UID::AutoCommit;
294   local $FS::UID::AutoCommit = 0;
295   my $dbh = dbh;
296
297   my $cust_pkg = $svc->cust_svc->cust_pkg;
298
299   my $depend = [];
300
301   if ($cust_pkg) {
302     my $queue = new FS::queue {
303       'svcnum' => $svc->svcnum,
304       'job'    => 'FS::part_export::prizm::queued_prizm_command',
305     };
306     my $error = $queue->insert(
307       ( map { $self->option($_) }
308             qw( url user password ) ),
309       'CustomerIfService',
310       'removeElementFromCustomer',
311       0,
312       $cust_pkg->custnum,
313       0,
314       $svc->mac_addr,
315     );
316
317     if ($error) {
318       $dbh->rollback if $oldAutoCommit;
319       return $error;
320     }
321
322     push @$depend, $queue->jobnum;
323   }
324
325   my $err_or_queue =
326     $self->queue_statuschange('deleteElement', $depend, $svc, 1);
327
328   unless (ref($err_or_queue)) {
329     $dbh->rollback if $oldAutoCommit;
330     return $err_or_queue;
331   }
332
333   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
334
335   '';
336 }
337
338 sub _export_replace {
339   my( $self, $new, $old ) = ( shift, shift, shift );
340
341   my $err_or_som = $self->prizm_command('NetworkIfService', 'getPrizmElements',
342                                         [ 'MAC Address' ],
343                                         [ $old->mac_addr ],
344                                         [ '=' ],
345                                        );
346   return $err_or_som
347     unless ref($err_or_som);
348
349   return "Can't find prizm element for " . $old->mac_addr
350     unless $err_or_som->result->[0];
351
352   my %freeside2prizm = (  mac_addr     => 'MAC Address',
353                           ip_addr      => 'Management IP',
354                           latitude     => 'GPS Latitude',
355                           longitude    => 'GPS Longitude',
356                           altitude     => 'GPS Altitude',
357                           authkey      => 'Authentication Key',
358                        );
359   
360   my (@values);
361   my (@names) = map { push @values, $new->$_; $freeside2prizm{$_} }
362     grep { $old->$_ ne $new->$_ }
363       grep { exists($freeside2prizm{$_}) }
364         fields( 'svc_broadband' );
365
366   if ($old->description ne $new->description) {
367     my $cust_main = $old->cust_svc->cust_pkg->cust_main;
368     my $name = defined($cust_main->dbdef_table->column('ship_last'))
369              ? $cust_main->ship_name
370              : $cust_main->name;
371     push @values, $name . " " . $new->description;
372     push @names, "Site Name";
373   }
374
375   my $element = $err_or_som->result->[0]->elementId;
376
377   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfig',
378                                         [ $element ],
379                                         \@names,
380                                         \@values,
381                                         0,
382                                         1,
383                                        );
384   return $err_or_som
385     unless ref($err_or_som);
386
387   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
388                                      [ $element ],
389                                      $new->vlan_profile,
390                                      0,
391                                      1,
392                                     )
393     if $old->vlan_profile ne $new->vlan_profile;
394
395   return $err_or_som
396     unless ref($err_or_som);
397
398   '';
399
400 }
401
402 sub _export_suspend {
403   my( $self, $svc ) = ( shift, shift );
404   my $depend = [];
405   my $ems = $self->option('ems') ? 1 : 0;
406   my $err_or_queue = '';
407
408   my $oldAutoCommit = $FS::UID::AutoCommit;
409   local $FS::UID::AutoCommit = 0;
410   my $dbh = dbh;
411
412   $err_or_queue = 
413      $self->queue_statuschange('suspendNetworkElements', [], $svc, 1, $ems);
414   unless (ref($err_or_queue)) {
415     $dbh->rollback if $oldAutoCommit;
416     return $err_or_queue;
417   }
418   push @$depend, $err_or_queue->jobnum;
419
420   if ($ems && $self->option('always_bam')) {
421     $err_or_queue =
422       $self->queue_statuschange('suspendNetworkElements', $depend, $svc, 1, 0);
423     unless (ref($err_or_queue)) {
424       $dbh->rollback if $oldAutoCommit;
425       return $err_or_queue;
426     }
427   }
428
429   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
430
431   '';
432 }
433
434 sub _export_unsuspend {
435   my( $self, $svc ) = ( shift, shift );
436   my $depend = [];
437   my $ems = $self->option('ems') ? 1 : 0;
438   my $err_or_queue = '';
439
440   my $oldAutoCommit = $FS::UID::AutoCommit;
441   local $FS::UID::AutoCommit = 0;
442   my $dbh = dbh;
443
444   if ($ems && $self->option('always_bam')) {
445     $err_or_queue =
446       $self->queue_statuschange('activateNetworkElements', [], $svc, 1, 0);
447     unless (ref($err_or_queue)) {
448       $dbh->rollback if $oldAutoCommit;
449       return $err_or_queue;
450     }
451     push @$depend, $err_or_queue->jobnum;
452   }
453
454   $err_or_queue =
455     $self->queue_statuschange('activateNetworkElements', $depend, $svc, 1, $ems);
456   unless (ref($err_or_queue)) {
457     $dbh->rollback if $oldAutoCommit;
458     return $err_or_queue;
459   }
460
461   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
462
463   '';
464 }
465
466 sub queue_statuschange {
467   my( $self, $method, $jobs, $svc, @args ) = @_;
468
469   # already in a transaction and can't die here
470
471   my $queue = new FS::queue {
472     'svcnum' => $svc->svcnum,
473     'job'    => 'FS::part_export::prizm::statuschange',
474   };
475   my $error = $queue->insert(
476     ( map { $self->option($_) }
477           qw( url user password ) ),
478     $method,
479     $svc->mac_addr,
480     @args,
481   );
482
483   unless ($error) {                   # successful insertion
484     foreach my $job ( @$jobs ) {
485       $error ||= $queue->depend_insert($job);
486     }
487   }
488
489   $error or $queue;
490 }
491
492 sub statuschange {  # subroutine
493   my( $url, $user, $password, $method, $mac_addr, @args) = @_;
494
495   eval "use Net::Prizm qw(CustomerInfo PrizmElement);";
496   die $@ if $@;
497
498   my $prizm = new Net::Prizm (
499     namespace => 'NetworkIfService',
500     url => $url,
501     user => $user,
502     password => $password,
503   );
504   
505   my $err_or_som = $prizm->getPrizmElements( [ 'MAC Address' ],
506                                              [ $mac_addr ],
507                                              [ '=' ],
508                                            );
509   die $err_or_som
510     unless ref($err_or_som);
511
512   die "Can't find prizm element for " . $mac_addr
513     unless $err_or_som->result->[0];
514
515   my $arg1;
516   # yuck!
517   if ($method =~ /suspendNetworkElements/ || $method =~ /activateNetworkElements/) {
518     $arg1 = [ $err_or_som->result->[0]->elementId ];
519   }else{
520     $arg1 = $err_or_som->result->[0]->elementId;
521   }
522   $err_or_som = $prizm->$method( $arg1, @args );
523
524   die $err_or_som
525     unless ref($err_or_som);
526
527   '';
528
529 }
530
531
532 1;