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