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