This commit was generated by cvs2svn to compensate for changes in r10640,
[freeside.git] / bin / b-move-customers
1 #!/usr/bin/perl -w
2
3 #script to move customers from one installation to another
4 # source is remote, destination is local
5
6 use strict;
7 use vars qw( $sdbh );
8 use DBI;
9 use FS::UID qw( adminsuidsetup dbh );
10 use FS::Schema qw( dbdef );
11 use FS::Record qw( qsearchs );
12 use FS::agent;
13 use FS::cust_main;
14 use FS::part_pkg;
15 use FS::part_svc;
16 use FS::cust_bill_ApplicationCommon;
17 use FS::svc_Common;
18 use FS::cust_event;
19 use FS::svc_domain;
20 use FS::cust_pkg;
21
22 my $DANGEROUS = 0;
23 my $DRY = 1;
24
25 # XXX: use SSL for real version
26 my $source_datasrc = '';
27 my $source_user = '';
28 my $source_pw = '';
29
30 #### MANUAL ####
31 # 1. Destination agent must be created with correct agentnum
32 # 2. Destination refnum must be created as per below
33 # 3. System domain must be created appropriately
34 #####
35
36 my $dest_agentnum = 3;
37 my $src_agentnum = 1;
38 my $dest_refnum = 17;
39
40 my %domsvc_map = (
41   2 => 10375,
42 );
43
44 # XXX
45 my %eventparts = (
46  # 'CARD' => [ 1, ],
47  # 'CHEK' => [ 2, ],
48  # 'BILL' => [ 5, ],
49  # 'DCHK' => [ 12, ],
50  # 'DCRD' => [ 15, ],
51  # 'COMP' => [],
52 );
53
54 #--
55
56 # target(local) setup
57
58 my $user = shift
59   or die "Usage:\n  (edit variables at top of script and then)\n".
60          "  b-move-customers user\n";
61 adminsuidsetup $user;
62
63 $FS::cust_main::ignore_expired_card = 1;
64 $FS::cust_main::ignore_expired_card = 1;
65 $FS::part_pkg::skip_pkg_svc_hack = 1;
66 $FS::part_pkg::skip_pkg_svc_hack = 1;
67 $FS::cust_bill_ApplicationCommon::skip_apply_to_lineitems_hack = 1;
68 $FS::cust_bill_ApplicationCommon::skip_apply_to_lineitems_hack = 1;
69 $FS::svc_Common::noexport_hack = 1;
70 $FS::svc_Common::noexport_hack = 1;
71 $FS::svc_domain::whois_hack = 1;
72 $FS::svc_domain::whois_hack = 1;
73 $FS::cust_pkg::disable_agentcheck = 1;
74 $FS::cust_pkg::disable_agentcheck = 1;
75
76 my $void_paynum = 2147483646; #top of int range
77
78 # -- 
79
80 # source(remote) setup
81
82 $sdbh = DBI->connect($source_datasrc, $source_user, $source_pw)
83   or die $DBI::errstr;
84
85 $sdbh->{ChopBlanks} = 1;
86
87 # --
88
89 my %map = ();
90 $map{'_DOMSVC'} = \%domsvc_map;
91
92 import_table('pkg_category', 'nomap' => 1);
93 import_table('pkg_class', 'nomap' => 1,
94     'preinsert_callback' => sub {
95         my($row, $object) = @_;
96         my $src_categorynum = $row->{'categorynum'};
97         my $dest_categorynum = $map{'pkg_category'}->{$src_categorynum};
98         if ( $dest_categorynum ) {
99           $object->categorynum($dest_categorynum);
100         }
101     }
102 );
103
104 import_table('reason_type', 'nomap' => 1);
105 foreach my $src_typenum ( keys %{ $map{'reason_type'} } ) {
106   import_table('reason', 'reason_type' => $src_typenum,
107                          'search'      => 'reason_type',
108                          'map'         => 'reason_type',
109               );
110 }
111
112 my $customer_sth = $sdbh->prepare(
113   "SELECT * FROM cust_main WHERE agentnum = $src_agentnum ORDER BY custnum"
114 ) or die $sdbh->errstr;
115
116 $customer_sth->execute or die $customer_sth->errstr;
117
118 my %referrals = ();
119
120 while ( my $customerrow = $customer_sth->fetchrow_hashref ) {
121
122     my $src_custnum = $customerrow->{'custnum'};
123
124     if ( $customerrow->{'referral_custnum'} ) {
125         warn "   $src_custnum has referral_custnum ". $customerrow->{'referral_custnum'};
126         $referrals{$src_custnum} = $customerrow->{'referral_custnum'};
127     };
128
129     my $cust_main = new FS::cust_main {
130       %{ $customerrow },
131       'custnum'      => '',
132       'referral_custnum' => '',
133       'refnum'       => $dest_refnum,
134       'agentnum'     => $dest_agentnum,
135       'agent_custid' => $src_custnum,
136     };
137
138     my $error = $cust_main->insert;
139     if ( $error ) {
140       warn "*** WARNING: error importing customer src custnum $src_custnum: $error";
141       next;
142     }
143
144     warn "inserting dest customer ". $cust_main->custnum. " for $src_custnum\n";
145
146     $map{'cust_main'}->{$src_custnum} = $cust_main->custnum;
147
148     #now import the relations, easy and hard:
149     
150     import_table( 'cust_location', 'custnum' => $src_custnum );
151
152     import_table( 'cust_main_note', 'custnum' => $src_custnum );
153
154     import_table( 'cust_pay', 'custnum' => $src_custnum );
155
156     import_table( 'cust_credit', 'custnum' => $src_custnum, 
157          'preinsert_callback' => sub {
158             my($row, $object) = @_;
159             my $src_reasonnum = $row->{'reasonnum'};
160             my $dest_reasonnum = $map{'reason'}->{$src_reasonnum};
161             if ( $dest_reasonnum ) {
162               $object->reasonnum($dest_reasonnum);
163             }
164         }
165     );
166
167     import_table( 'cust_refund', 'custnum' => $src_custnum,
168       'post_callback' => sub {
169         #my( $src_refundnum, $dst_refundnum ) = @_;
170         my $src_refundnum = shift;
171
172         # cust_pay_refund (map refundnum and paynum...)
173         import_table( 'cust_pay_refund',
174                       'refundnum' => $src_refundnum,
175                       'search'    => 'refundnum',
176                       'map'       => 'cust_refund',
177                       'map2'      => 'cust_pay',
178                       'map2key'   => 'paynum',
179                     );
180
181       },
182     );
183
184     # cust_pay_void
185     import_table( 'cust_pay_void', 'custnum' => $src_custnum,
186       'preinsert_callback' => sub {
187         my($row, $object) = @_;
188         $object->paynum( $void_paynum-- );
189       },
190     );
191
192 # no data in old db for:
193 #       cust_attachment, cust_statement, cdr, cdr_*, cust_bill_event,
194 #       cust_main_exemption, cust_pay_batch, cust_tax_*, cust_recon,
195 #       inventory_item, part_bill_event, part_device, part_export, 
196 #       part_pop_local, part_virtual_field, pay_batch, phone_*, 
197 #       payment_gateway_*, prepay_credit, port, radius_usergroup, 
198 #       rate_*, reg_code, reg_code_pkg, registrar, router, 
199 #       svc_acct, svc_acct_pop, svc_broadband, svc_external,
200 #       svc_forward, svc_phone, svc_www, tax_*, usage_class, virtual_field
201 # appears to be unused in old db: inventory_class
202 # ignore queue
203
204     #werid direct cust_main relations: 
205
206     warn "   inserting cust_pkg for src cust $src_custnum\n";
207     # cust_pkg (part_pkg, part_svc, etc.)
208     import_table( 'cust_pkg', 'custnum' => $src_custnum,
209       'preinsert_callback' => sub {
210         my($row, $object) = @_;
211         my $src_pkgpart = $row->{'pkgpart'} or die "wtf";
212         my $dest_pkgpart = $map{'part_pkg'}->{$src_pkgpart};
213         if ( $dest_pkgpart ) {
214           $object->pkgpart($dest_pkgpart);
215           return;
216         }
217
218         my $sth = $sdbh->prepare(
219           "SELECT * FROM part_pkg WHERE pkgpart = $src_pkgpart"
220         ) or die $sdbh->errstr;
221
222         $sth->execute or die $sth->errstr;
223
224         my $part_pkg_row = $sth->fetchrow_hashref
225           or die "cust_pkg.pkgpart missing in part_pkg?!";
226
227         my $hashref = {
228           %{ $part_pkg_row },
229           'pkgpart'  => '',
230         };
231         my $src_classnum = $part_pkg_row->{'classnum'};
232         $hashref->{'classnum'} = $map{'pkg_class'}->{ $src_classnum }
233           if $src_classnum;
234
235         my $part_pkg = new FS::part_pkg $hashref;
236
237         #$part_pkg->setuptax('') if $part_pkg->setuptax =~ /^\s+$/;
238         #$part_pkg->recurtax('') if $part_pkg->recurtax =~ /^\s+$/;
239
240         my $error = $part_pkg->insert( 'options' => {} );
241         die "*** FATAL: error importing part_pkg src pkgpart $src_pkgpart ".
242             ": $error"
243           if $error;
244
245         $map{ 'part_pkg' }->{ $part_pkg_row->{'pkgpart'} } = $part_pkg->pkgpart;
246         
247         # part_pkg_option
248         import_table( 'part_pkg_option',
249                       'pkgpart' => $src_pkgpart,
250                       'search' => 'pkgpart',
251                       'map'    => 'part_pkg',
252                     );
253         
254         my $osth = $sdbh->prepare(
255           "SELECT * FROM part_pkg_option WHERE pkgpart = $src_pkgpart"
256         ) or die $sdbh->errstr;
257
258         # pkg_svc, part_svc, part_svc_column
259         import_table( 'pkg_svc',
260           'pkgpart' => $src_pkgpart,
261           'search'  => 'pkgpart',
262           'map'     => 'part_pkg',
263           'preinsert_callback' => sub {
264
265             my($row, $object) = @_;
266             my $src_svcpart = $row->{'svcpart'} or die "wtf2";
267             my $dest_svcpart = $map{'part_svc'}->{$src_svcpart};
268             if ( $dest_svcpart ) {
269               $object->svcpart($dest_svcpart);
270               return;
271             }
272
273             my $sth = $sdbh->prepare(
274               "SELECT * FROM part_svc WHERE svcpart = $src_svcpart"
275             ) or die $sdbh->errstr;
276
277             $sth->execute or die $sth->errstr;
278
279             my $part_svc_row = $sth->fetchrow_hashref
280               or die "svcpart missing in part_svc?!";
281
282             my $hashref = {
283               %{ $part_svc_row },
284               'svcpart' => '',
285             };
286
287             my $part_svc = new FS::part_svc $hashref;
288             $part_svc->disabled('') if $part_svc->disabled =~ /^\s+$/;
289             my $error = $part_svc->insert;
290             die "*** FATAL: error importing part_svc src svcpart $src_svcpart ".
291                 ": $error"
292               if $error;
293
294             $map{ 'part_svc' }->{ $part_svc_row->{'svcpart'} } = $part_svc->svcpart;
295
296             # part_svc_column
297             import_table( 'part_svc_column',
298                           'svcpart' => $src_svcpart,
299                           'search'  => 'svcpart',
300                           'map'     => 'part_svc',
301                           'preinsert_callback' => sub {
302                             my($row, $object) = @_;
303                             if ( $object->columnname eq 'domsvc' ) {
304                                $object->columnvalue( $map{'_DOMSVC'}->{ $object->columnvalue } );
305                             }
306                           },
307                         );
308         
309             #what we came here for in the first place
310             $object->svcpart( $part_svc->svcpart );
311
312           }
313         );
314
315         #what we came here for in the first place
316         $object->pkgpart( $part_pkg->pkgpart );
317
318       },
319
320       'post_callback' => sub {
321         #my( $src_pkgnum, $dst_pkgnum ) = @_;
322         my $src_pkgnum = shift;
323
324         #XXX grr... action makes this very hard... 
325         ## cust_pkg_reason (shit, and bring in/remap reasons)
326         #import_table( 'cust_pkg_reason',
327         #                'pkgnum'  => $src_pkgnum,
328         #                'search'  => 'pkgnum',
329         #                'map'     => 'cust_pkg',
330         #                'map2'    => 'reason',
331         #                'map2key' => 'reasonnum',
332         #            );
333
334         #cust_svc
335         import_table( 'cust_svc',
336                         'pkgnum'  => $src_pkgnum,
337                         'search'  => 'pkgnum',
338                         'map'     => 'cust_pkg',
339                         'map2'    => 'part_svc',
340                         'map2key' => 'svcpart',
341                         'post_callback' => sub {
342                           #my( $src_svcnum, $dst_svcnum ) = @_;
343                           my $src_svcnum = shift;
344
345                           #svc_domain
346                           import_table( 'svc_domain',
347                                           'svcnum' => $src_svcnum,
348                                           'search' => 'svcnum',
349                                           'map'    => 'cust_svc',
350                                           'noblank_primary' => 1,
351                                       );
352
353                         },
354                     );
355         
356         import_table('cust_pkg_detail', 
357                         'pkgnum'  => $src_pkgnum,
358                         'search'  => 'pkgnum',
359                         'map'     => 'cust_pkg',
360                      );
361
362       },
363
364     );
365     # end of cust_pkg (part_pkg, part_svc, etc.)
366
367     warn "   inserting cust_bill for src cust $src_custnum\n";
368     # cust_bill (invnum move)
369     import_table( 'cust_bill', 'custnum' => $src_custnum,
370       'preinsert_callback' => sub {
371         my($row, $object) = @_;
372         $object->agent_invid( $row->{'invnum'} );
373       },
374       'post_callback' => sub {
375         my( $src_invnum, $dst_invnum ) = @_;
376         #my $src_invnum = shift;
377
378         # cust_bill_pkg ( map invnum and pkgnum... )
379         import_table( 'cust_bill_pkg',
380                       'invnum' => $src_invnum,
381                       'search'  => 'invnum',
382                       'map'     => 'cust_bill',
383                       'map2'    => 'cust_pkg',
384                       'map2key' => 'pkgnum',
385                       'post_callback' => sub {
386                         my $src_billpkgnum = shift;
387
388                         import_table( 'cust_bill_pkg_detail',
389                                       'cust_bill_pkg.billpkgnum' => $src_billpkgnum,
390                                       'search'    => 'cust_bill_pkg.billpkgnum',
391                                       'map'       => 'cust_bill_pkg',
392                                       'addl_from' => 'left join cust_bill_pkg using ( invnum, pkgnum )',
393                                     );
394
395                       },
396                     );
397
398         # cust_credit_bill (map invnum and crednum... )
399         import_table( 'cust_credit_bill',
400                       'invnum' => $src_invnum,
401                       'search'  => 'invnum',
402                       'map'     => 'cust_bill',
403                       'map2'    => 'cust_credit',
404                       'map2key' => 'crednum',
405                       'post_callback' => sub {
406                         my $src_creditbillnum = shift;
407                         #map creditbillnum and billpkgnum
408                         import_table( 'cust_credit_bill_pkg',
409                                       'creditbillnum' => $src_creditbillnum,
410                                       'search'    => 'creditbillnum',
411                                       'map'       => 'cust_credit_bill',
412                                       'map2'      => 'cust_bill_pkg',
413                                       'map2key'   => 'billpkgnum',
414                                     );
415
416                       },
417                     );
418
419         # cust_bill_pay (map invnum and paynum...)
420         import_table( 'cust_bill_pay',
421                       'invnum' => $src_invnum,
422                       'search'  => 'invnum',
423                       'map'     => 'cust_bill',
424                       'map2'    => 'cust_pay',
425                       'map2key' => 'paynum',
426                       'post_callback' => sub {
427                         my $src_billpaynum = shift;
428                         #map billpaynum and billpkgnum
429                         import_table( 'cust_bill_pay_pkg',
430                                       'billpaynum' => $src_billpaynum,
431                                       'search'    => 'billpaynum',
432                                       'map'       => 'cust_bill_pay',
433                                       'map2'      => 'cust_bill_pkg',
434                                       'map2key'   => 'billpkgnum',
435                                     );
436                       },
437                     );
438
439         #need to do something about events. mark initial stuff as done
440         foreach my $eventpart ( @{ $eventparts{$cust_main->payby} } ) {
441
442           my $cust_event = new FS::cust_event {
443             'eventpart' => $eventpart,
444             'tablenum'  => $dst_invnum,
445             '_date'     => time, # XXX something?  probably not
446             'status'    => 'done',
447           };
448
449           my $error = $cust_event->insert;
450           die "*** FATAL: error inserting cust_event for eventpart $eventpart,".
451               " tablenum (invnum) $dst_invnum: $error"
452             if $error;
453
454         }
455
456       },
457     );
458
459     # ---
460
461        # (not used in old db: cust_bill_pay_batch, cust_pkg_option)
462
463     # ---
464
465     # (not in old db: cust_bill_pkg_display, cust_bill_pkg_tax_location,
466     #  cust_bill_pkg_tax_rate_location, cust_tax_adjustment, cust_svc_option, )
467     # (not used in old db: cust_tax_exempt_pkg)
468
469     #do this last, so no notices go out
470     import_table( 'cust_main_invoice', 'custnum' => $src_custnum );
471
472     #dbh->commit or die dbh->errstr;
473     warn "customer ". $cust_main->custnum. " inserted\n";
474     #exit;
475
476 }
477
478 foreach my $agent_custid ( keys %referrals ) {
479     my $referred_cust = qsearchs('cust_main', 
480                                     { 'agentnum' => $dest_agentnum, 
481                                       'agent_custid' => $agent_custid,
482                                     }
483                                 );
484     $referred_cust->referral_custnum($map{'cust_main'}->{$referrals{$agent_custid}});
485     $referred_cust->replace;
486 }
487
488
489 warn "import successful!\n";
490 if ( $DRY ) {
491   warn "rolling back (dry run)\n";
492   dbh->rollback or die dbh->errstr;
493   warn "rolled back\n"
494 } else {
495   warn "commiting\n";
496   dbh->commit or die dbh->errstr;
497   warn "committed\n";
498 }
499
500 sub import_table {
501   my( $table, %opt ) = @_;
502
503   eval "use FS::$table;";
504   die $@ if $@;
505
506   my $map = $opt{'map'} || 'cust_main';
507   my $search = $opt{'search'} || 'custnum';
508
509   $opt{'insert_opts'} ||= [];
510
511   my $primary_key = dbdef->table($table)->primary_key;
512
513   my $addl_from = defined($opt{'addl_from'}) ? $opt{'addl_from'} : '';
514
515   my $sth = $sdbh->prepare(
516     "SELECT * FROM $table $addl_from ".
517     ( $opt{'nomap'} ? '' : " WHERE $search = ". $opt{$search} )
518   ) or die $sdbh->errstr;
519
520   $sth->execute or die "(searching $table): ". $sth->errstr;
521
522   while ( my $row = $sth->fetchrow_hashref ) {
523     #my $src_custnum = $customerrow->{'custnum'};
524
525     my $hashref = { %$row };
526     $hashref->{$primary_key} = ''
527       unless $opt{'noblank_primary'};
528     $hashref->{ $search } = $map{$map}->{ $row->{$search} }
529       unless $opt{'nomap'};
530
531     if ( $opt{'map2'} ) {
532       my $key2 = $opt{'map2key'};
533       $hashref->{$key2} = $map{ $opt{'map2'} }->{ $row->{$key2} }
534         unless $opt{map2key} eq 'pkgnum' && (    $row->{$key2} eq '0'
535                                               || $row->{$key2} eq '-1'
536                                             )
537             or ! defined($row->{$key2})
538             or $row->{$key2} eq '';
539       #warn "map $opt{map2}.$opt{map2key}: ". $row->{$key2}. " to ". $map{ $opt{'map2'} }->{ $row->{$key2} };
540     }
541
542     if ( $opt{'map3'} ) {
543       my $key3 = $opt{'map3key'};
544       $hashref->{$key3} = $map{ $opt{'map3'} }->{ $row->{$key3} };
545     }
546
547     my $object = eval "new FS::$table \$hashref;";
548     die $@ if $@;
549
550     &{ $opt{preinsert_callback} }( $row, $object )
551       if $opt{preinsert_callback};
552
553     my $error = $object->insert( @{ $opt{'insert_opts'} } );
554     if ( $error ) {
555       warn "*** WARNING: error importing $table src $primary_key ". $row->{$primary_key}. ": $error";
556       next;
557     }
558
559     $map{ $table }->{ $row->{$primary_key} } = $object->get($primary_key);
560
561     &{ $opt{post_callback} }( $row->{$primary_key}, $object->get($primary_key) )
562       if $opt{post_callback};
563
564   }
565
566 }
567
568 1;
569