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