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