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