73085: Enable credit card/ach encryption on a live system [v3 merge]
[freeside.git] / FS / FS / Setup.pm
1 package FS::Setup;
2
3 use strict;
4 use vars qw( @ISA @EXPORT_OK );
5 use Exporter;
6 #use Tie::DxHash;
7 use Tie::IxHash;
8 use Crypt::OpenSSL::RSA;
9 use FS::UID qw( dbh driver_name );
10 use FS::Record;
11
12 use FS::svc_domain;
13 $FS::svc_domain::whois_hack = 1;
14 $FS::svc_domain::whois_hack = 1;
15
16 @ISA = qw( Exporter );
17 @EXPORT_OK = qw( create_initial_data enable_encryption );
18
19 =head1 NAME
20
21 FS::Setup - Database setup
22
23 =head1 SYNOPSIS
24
25   use FS::Setup;
26
27 =head1 DESCRIPTION
28
29 Currently this module simply provides a place to store common subroutines for
30 database setup.
31
32 =head1 SUBROUTINES
33
34 =over 4
35
36 =item
37
38 =cut
39
40 sub create_initial_data {
41   my %opt = @_;
42
43   my $oldAutoCommit = $FS::UID::AutoCommit;
44   local $FS::UID::AutoCommit = 0;
45   $FS::UID::AutoCommit = 0;
46
47   populate_locales();
48
49   populate_duplock();
50
51   #initial_data data
52   populate_initial_data(%opt);
53
54   populate_access();
55
56   populate_msgcat();
57
58   populate_numbering();
59
60   enable_encryption();
61   
62   if ( $oldAutoCommit ) {
63     dbh->commit or die dbh->errstr;
64   }
65
66 }
67
68 sub enable_encryption {
69
70   eval "use FS::Conf";
71   die $@ if $@;
72
73   my $conf = new FS::Conf;
74
75   die "encryption key(s) already in place"
76     if $conf->exists('encryptionpublickey')
77     || $conf->exists('encryptionprivatekey');
78
79   my $length = 2048;
80   my $rsa = Crypt::OpenSSL::RSA->generate_key($length);
81
82   $conf->set('encryption', 1);
83   $conf->set('encryptionmodule',     'Crypt::OpenSSL::RSA');
84   $conf->set('encryptionpublickey',  $rsa->get_public_key_string );
85   $conf->set('encryptionprivatekey', $rsa->get_private_key_string );
86
87   # reload Record globals, false laziness with FS::Record
88   $FS::Record::conf_encryption           = $conf->exists('encryption');
89   $FS::Record::conf_encryptionmodule     = $conf->config('encryptionmodule');
90   $FS::Record::conf_encryptionpublickey  = join("\n",$conf->config('encryptionpublickey'));
91   $FS::Record::conf_encryptionprivatekey = join("\n",$conf->config('encryptionprivatekey'));
92
93 }
94
95 sub populate_numbering {
96   eval "use FS::lata_Data;"; # this automatically populates the lata table, if unpopulated
97   eval "use FS::msa_Data;"; # this automatically populates the msa table, if unpopulated
98 }
99
100 sub populate_locales {
101
102   use Locale::Country;
103   use FS::cust_main_county;
104
105   #cust_main_county
106   foreach my $country ( sort map uc($_), all_country_codes ) {
107     _add_country($country);
108   }
109
110 }
111
112 sub populate_addl_locales {
113
114   my %addl = (
115     'US' => {
116       'FM' => 'Federated States of Micronesia',
117       'MH' => 'Marshall Islands',
118       'PW' => 'Palau',
119       'AA' => "Armed Forces Americas (except Canada)",
120       'AE' => "Armed Forces Europe / Canada / Middle East / Africa",
121       'AP' => "Armed Forces Pacific",
122     },
123   );
124
125   foreach my $country ( keys %addl ) {
126     foreach my $state ( keys %{ $addl{$country} } ) {
127       # $longname = $addl{$country}{$state};
128       _add_locale( 'country'=>$country, 'state'=>$state);
129     }
130   }
131
132 }
133
134 sub _add_country {
135
136   use Locale::SubCountry 1.42;
137
138   my( $country ) = shift;
139
140   my $subcountry = eval { new Locale::SubCountry($country) };
141   my @states = $subcountry ? $subcountry->all_codes : undef;
142   
143   if ( !scalar(@states) || ( scalar(@states)==1 && !defined($states[0]) ) ) {
144
145     _add_locale( 'country'=>$country );
146   
147   } else {
148   
149     if ( $states[0] =~ /^(\d+|\w)$/ ) {
150       @states = map $subcountry->full_name($_), @states
151     }
152   
153     foreach my $state ( @states ) {
154       _add_locale( 'country'=>$country, 'state'=>$state);
155     }
156     
157   }
158
159 }
160
161 sub _add_locale {
162   my $cust_main_county = new FS::cust_main_county( { 'tax'=>0, @_ });  
163   my $error = $cust_main_county->insert;
164   die $error if $error;
165 }
166
167 sub populate_duplock {
168
169   return unless driver_name =~ /^mysql/i;
170
171   my $sth = dbh->prepare(
172     "INSERT INTO duplicate_lock ( lockname ) VALUES ( 'svc_acct' )"
173   ) or die dbh->errstr;
174
175   $sth->execute or die $sth->errstr;
176
177 }
178
179 sub populate_initial_data {
180   my %opt = @_;
181
182   my $data = initial_data(%opt);
183
184   foreach my $table ( keys %$data ) {
185
186     #warn "popuilating $table\n";
187
188     my $class = "FS::$table";
189     eval "use $class;";
190     die $@ if $@;
191
192     $class->_populate_initial_data(%opt)
193       if $class->can('_populate_initial_data');
194
195     my @records = @{ $data->{$table} };
196
197     foreach my $record ( @records ) {
198
199       my $args = delete($record->{'_insert_args'}) || [];
200       my $object = $class->new( $record );
201       my $error = $object->insert( @$args );
202       die "error inserting record into $table: $error\n"
203         if $error;
204
205       #my $pkey = $object->primary_key;
206       #my $pkeyvalue = $object->$pkey();
207       #warn "  inserted $pkeyvalue\n";
208
209     }
210
211   }
212
213 }
214
215 sub initial_data {
216   my %opt = @_;
217
218   my $cust_location = FS::cust_location->new({
219       'address1'  => '1234 System Lane',
220       'city'      => 'Systemtown',
221       'state'     => 'CA',
222       'zip'       => '54321',
223       'country'   => 'US',
224   });
225
226   #tie my %hash, 'Tie::DxHash', 
227   tie my %hash, 'Tie::IxHash', 
228
229     #bootstrap user
230     'access_user' => [
231       { 'username'  => 'fs_bootstrap',
232         '_password' => 'changeme', #will trigger warning if you try to enable
233         'last'      => 'User',
234         'first'     => 'Bootstrap',
235         'disabled'  => 'Y',
236       },
237     ],
238
239     #superuser group
240     'access_group' => [
241       { 'groupname' => 'Superuser' },
242     ],
243
244     #reason types
245     'reason_type' => [],
246
247 #XXX need default new-style billing events
248 #    #billing events
249 #    'part_bill_event' => [
250 #      { 'payby'     => 'CARD',
251 #        'event'     => 'Batch card',
252 #        'seconds'   => 0,
253 #        'eventcode' => '$cust_bill->batch_card(%options);',
254 #        'weight'    => 40,
255 #        'plan'      => 'batch-card',
256 #      },
257 #      { 'payby'     => 'BILL',
258 #        'event'     => 'Send invoice',
259 #        'seconds'   => 0,
260 #        'eventcode' => '$cust_bill->send();',
261 #        'weight'    => 50,
262 #        'plan'      => 'send',
263 #      },
264 #      { 'payby'     => 'DCRD',
265 #        'event'     => 'Send invoice',
266 #        'seconds'   => 0,
267 #        'eventcode' => '$cust_bill->send();',
268 #        'weight'    => 50,
269 #        'plan'      => 'send',
270 #      },
271 #      { 'payby'     => 'DCHK',
272 #        'event'     => 'Send invoice',
273 #        'seconds'   => 0,
274 #        'eventcode' => '$cust_bill->send();',
275 #        'weight'    => 50,
276 #        'plan'      => 'send',
277 #      },
278 #      { 'payby'     => 'DCLN',
279 #        'event'     => 'Suspend',
280 #        'seconds'   => 0,
281 #        'eventcode' => '$cust_bill->suspend();',
282 #        'weight'    => 40,
283 #        'plan'      => 'suspend',
284 #      },
285 #      #{ 'payby'     => 'DCLN',
286 #      #  'event'     => 'Retriable',
287 #      #  'seconds'   => 0,
288 #      #  'eventcode' => '$cust_bill_event->retriable();',
289 #      #  'weight'    => 60,
290 #      #  'plan'      => 'retriable',
291 #      #},
292 #    ],
293     
294     #you must create a service definition. An example of a service definition
295     #would be a dial-up account or a domain. First, it is necessary to create a
296     #domain definition. Click on View/Edit service definitions and Add a new
297     #service definition with Table svc_domain (and no modifiers).
298     'part_svc' => [
299       { 'svc'   => 'Domain',
300         'svcdb' => 'svc_domain',
301       }
302     ],
303
304     #Now that you have created your first service, you must create a package
305     #including this service which you can sell to customers. Zero, one, or many
306     #services are bundled into a package. Click on View/Edit package
307     #definitions and Add a new package definition which includes quantity 1 of
308     #the svc_domain service you created above.
309     'part_pkg' => [
310       { 'pkg'     => 'System Domain',
311         'comment' => '(NOT FOR CUSTOMERS)',
312         'freq'    => '0',
313         'plan'    => 'flat',
314         '_insert_args' => [
315           'pkg_svc'     => { 1 => 1 }, # XXX
316           'primary_svc' => 1, #XXX
317           'options'     => {
318             'setup_fee' => '0',
319             'recur_fee' => '0',
320           },
321         ],
322       },
323     ],
324
325     #After you create your first package, then you must define who is able to
326     #sell that package by creating an agent type. An example of an agent type
327     #would be an internal sales representitive which sells regular and
328     #promotional packages, as opposed to an external sales representitive
329     #which would only sell regular packages of services. Click on View/Edit
330     #agent types and Add a new agent type.
331     'agent_type' => [
332       { 'atype' => 'Internal' },
333     ],
334
335     #Allow this agent type to sell the package you created above.
336     'type_pkgs' => [
337       { 'typenum' => 1, #XXX
338         'pkgpart' => 1, #XXX
339       },
340     ],
341
342     #After creating a new agent type, you must create an agent. Click on
343     #View/Edit agents and Add a new agent.
344     'agent' => [
345       { 'agent'   => 'Internal',
346         'typenum' => 1, # XXX
347       },
348     ],
349
350     #Set up at least one Advertising source. Advertising sources will help you
351     #keep track of how effective your advertising is, tracking where customers
352     #heard of your service offerings. You must create at least one advertising
353     #source. If you do not wish to use the referral functionality, simply
354     #create a single advertising source only. Click on View/Edit advertising
355     #sources and Add a new advertising source.
356     'part_referral' => [
357       { 'referral' => 'Internal', },
358     ],
359     
360     #Click on New Customer and create a new customer for your system accounts
361     #with billing type Complimentary. Leave the First package dropdown set to
362     #(none).
363     'cust_main' => [
364       { 'agentnum'  => 1, #XXX
365         'refnum'    => 1, #XXX
366         'first'     => 'System',
367         'last'      => 'Accounts',
368         'payby'     => 'COMP',
369         'payinfo'   => 'system', #or something
370         'paydate'   => '1/2037',
371         'bill_location' => $cust_location,
372         'ship_location' => $cust_location,
373       },
374     ],
375
376     #From the Customer View screen of the newly created customer, order the
377     #package you defined above.
378     'cust_pkg' => [
379       { 'custnum' => 1, #XXX
380         'pkgpart' => 1, #XXX
381       },
382     ],
383
384     #From the Package View screen of the newly created package, choose
385     #(Provision) to add the customer's service for this new package.
386     #Add your own domain.
387     'svc_domain' => [
388       { 'domain'  => $opt{'domain'},
389         'pkgnum'  => 1, #XXX
390         'svcpart' => 1, #XXX
391         'action'  => 'N', #pseudo-field
392       },
393     ],
394
395     #Go back to View/Edit service definitions on the main menu, and Add a new
396     #service definition with Table svc_acct. Select your domain in the domsvc
397     #Modifier. Set Fixed to define a service locked-in to this domain, or
398     #Default to define a service which may select from among this domain and
399     #the customer's domains.
400
401     #not yet....
402
403     #usage classes
404     'usage_class' => [],
405
406     #phone types
407     'phone_type' => [],
408
409     #message templates
410     'msg_template' => [],
411
412   ;
413
414   \%hash;
415
416 }
417
418 sub populate_access {
419
420   use FS::AccessRight;
421   use FS::access_right;
422   use FS::access_groupagent;
423
424   foreach my $rightname ( FS::AccessRight->default_superuser_rights ) {
425     my $access_right = new FS::access_right {
426       'righttype'   => 'FS::access_group',
427       'rightobjnum' => 1, #$supergroup->groupnum,
428       'rightname'   => $rightname,
429     };
430     my $ar_error = $access_right->insert;
431     die $ar_error if $ar_error;
432   }
433
434   #foreach my $agent ( qsearch('agent', {} ) ) {
435     my $access_groupagent = new FS::access_groupagent {
436       'groupnum' => 1, #$supergroup->groupnum,
437       'agentnum' => 1, #$agent->agentnum,
438     };
439     my $aga_error = $access_groupagent->insert;
440     die $aga_error if $aga_error;
441   #}
442
443 }
444
445 sub populate_msgcat {
446
447   use FS::Record qw(qsearch);
448   use FS::msgcat;
449
450   foreach my $del_msgcat ( qsearch('msgcat', {}) ) {
451     my $error = $del_msgcat->delete;
452     die $error if $error;
453   }
454
455   my %messages = FS::msgcat::_legacy_messages();
456
457   foreach my $msgcode ( keys %messages ) {
458     foreach my $locale ( keys %{$messages{$msgcode}} ) {
459       my $msgcat = new FS::msgcat( {
460         'msgcode' => $msgcode,
461         'locale'  => $locale,
462         'msg'     => $messages{$msgcode}{$locale},
463       });
464       my $error = $msgcat->insert;
465       die $error if $error;
466     }
467   }
468
469 }
470
471 =back
472
473 =head1 BUGS
474
475 Sure.
476
477 =head1 SEE ALSO
478
479 =cut
480
481 1;
482