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