prizm export improvement for package changes
[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 0.04 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 0.04 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   $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
399                                      [ $element ],
400                                      $new->cust_svc->cust_pkg->part_pkg->pkg,
401                                      0,
402                                      1,
403                                     );
404   return $err_or_som
405     unless ref($err_or_som);
406
407   '';
408
409 }
410
411 sub _export_suspend {
412   my( $self, $svc ) = ( shift, shift );
413   my $depend = [];
414   my $ems = $self->option('ems') ? 1 : 0;
415   my $err_or_queue = '';
416
417   my $oldAutoCommit = $FS::UID::AutoCommit;
418   local $FS::UID::AutoCommit = 0;
419   my $dbh = dbh;
420
421   $err_or_queue = 
422      $self->queue_statuschange('suspendNetworkElements', [], $svc, 1, $ems);
423   unless (ref($err_or_queue)) {
424     $dbh->rollback if $oldAutoCommit;
425     return $err_or_queue;
426   }
427   push @$depend, $err_or_queue->jobnum;
428
429   if ($ems && $self->option('always_bam')) {
430     $err_or_queue =
431       $self->queue_statuschange('suspendNetworkElements', $depend, $svc, 1, 0);
432     unless (ref($err_or_queue)) {
433       $dbh->rollback if $oldAutoCommit;
434       return $err_or_queue;
435     }
436   }
437
438   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
439
440   '';
441 }
442
443 sub _export_unsuspend {
444   my( $self, $svc ) = ( shift, shift );
445   my $depend = [];
446   my $ems = $self->option('ems') ? 1 : 0;
447   my $err_or_queue = '';
448
449   my $oldAutoCommit = $FS::UID::AutoCommit;
450   local $FS::UID::AutoCommit = 0;
451   my $dbh = dbh;
452
453   if ($ems && $self->option('always_bam')) {
454     $err_or_queue =
455       $self->queue_statuschange('activateNetworkElements', [], $svc, 1, 0);
456     unless (ref($err_or_queue)) {
457       $dbh->rollback if $oldAutoCommit;
458       return $err_or_queue;
459     }
460     push @$depend, $err_or_queue->jobnum;
461   }
462
463   $err_or_queue =
464     $self->queue_statuschange('activateNetworkElements', $depend, $svc, 1, $ems);
465   unless (ref($err_or_queue)) {
466     $dbh->rollback if $oldAutoCommit;
467     return $err_or_queue;
468   }
469
470   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
471
472   '';
473 }
474
475 sub export_links {
476   my( $self, $svc, $arrayref ) = ( shift, shift, shift );
477
478   push @$arrayref, '<A HREF="http://'. $svc->ip_addr. '">SM</A>';
479
480   '';
481 }
482
483 sub queue_statuschange {
484   my( $self, $method, $jobs, $svc, @args ) = @_;
485
486   # already in a transaction and can't die here
487
488   my $queue = new FS::queue {
489     'svcnum' => $svc->svcnum,
490     'job'    => 'FS::part_export::prizm::statuschange',
491   };
492   my $error = $queue->insert(
493     ( map { $self->option($_) }
494           qw( url user password ) ),
495     $method,
496     $svc->mac_addr,
497     @args,
498   );
499
500   unless ($error) {                   # successful insertion
501     foreach my $job ( @$jobs ) {
502       $error ||= $queue->depend_insert($job);
503     }
504   }
505
506   $error or $queue;
507 }
508
509 sub statuschange {  # subroutine
510   my( $url, $user, $password, $method, $mac_addr, @args) = @_;
511
512   eval "use Net::Prizm 0.04 qw(CustomerInfo PrizmElement);";
513   die $@ if $@;
514
515   my $prizm = new Net::Prizm (
516     namespace => 'NetworkIfService',
517     url => $url,
518     user => $user,
519     password => $password,
520   );
521   
522   my $err_or_som = $prizm->getPrizmElements( [ 'MAC Address' ],
523                                              [ $mac_addr ],
524                                              [ '=' ],
525                                            );
526   die $err_or_som
527     unless ref($err_or_som);
528
529   die "Can't find prizm element for " . $mac_addr
530     unless $err_or_som->result->[0];
531
532   my $arg1;
533   # yuck!
534   if ($method =~ /suspendNetworkElements/ || $method =~ /activateNetworkElements/) {
535     $arg1 = [ $err_or_som->result->[0]->elementId ];
536   }else{
537     $arg1 = $err_or_som->result->[0]->elementId;
538   }
539   $err_or_som = $prizm->$method( $arg1, @args );
540
541   die $err_or_som
542     unless ref($err_or_som);
543
544   '';
545
546 }
547
548
549 1;