add a -m mode to improve performance so upgrade can complete for large databases...
[freeside.git] / bin / freeside-migrate-events
1 #!/usr/bin/perl -w
2
3 use strict;
4 use Getopt::Std;
5 use FS::UID qw( adminsuidsetup dbh );
6 use FS::Record qw( qsearch );
7 use FS::part_bill_event;
8 use FS::part_event;
9 use FS::cust_bill_event;
10 use FS::cust_event;
11
12 use vars qw( $opt_m );
13 getopts('m');
14
15 my $user = shift or die &usage;
16 adminsuidsetup($user);
17
18 my %plan2action = (
19   'fee'                    => 'fee',
20   'fee_percent'            => 'NOTYET', #XXX need fee_percent action
21   'suspend'                => 'suspend',
22   'suspend-if-balance'     => 'suspend', #"if balance" becomes the balance cond
23   'suspend-if-pkgpart'     => 'suspend_if_pkgpart',
24   'suspend-unless-pkgpart' => 'suspend_unless_pkgpart',
25   'cancel'                 => 'cancel',
26   'addpost'                => 'addpost',
27   'comp'                   => 'NOTYET', #XXX or N/A or something
28   'credit'                 => 'writeoff',
29   'realtime-card'          => 'cust_bill_realtime_card',
30   'realtime-check'         => 'cust_bill_realtime_check',
31   'realtime-lec'           => 'cust_bill_realtime_lec',
32   'batch-card'             => 'cust_bill_batch',
33   #?'retriable'             =>
34   'send'                   => 'cust_bill_send',
35   'send_email'             => 'cust_bill_email',
36   'send_alternate'         => 'cust_bill_send_alternate',
37   'send_if_newest'         => 'cust_bill_send_if_newest',
38   'send_agent'             => 'cust_bill_send_agent',
39   'send_csv_ftp'           => 'cust_bill_send_csv_ftp',
40   'spool_csv',             => 'cust_bill_spool_csv',
41   'bill'                   => 'bill',
42   'apply'                  => 'apply',
43   'collect'                => 'collect',
44 );
45
46
47 foreach my $part_bill_event (
48   qsearch({
49     'table'   => 'part_bill_event',
50   })
51 ) {
52
53   print $part_bill_event->event;
54
55   my $action = $plan2action{ $part_bill_event->plan };
56
57   if ( $action eq 'NOTYET' ) {
58     warn "not migrating part_bill_event.eventpart ".$part_bill_event->eventpart.
59          "; ". $part_bill_event->plan. " plan not (yet) handled";
60     next;
61   } elsif ( ! $action ) {
62     warn "not migrating part_bill_event.eventpart ".$part_bill_event->eventpart.
63          "; unknown plan ". $part_bill_event->plan;
64     next;
65   }
66
67   my %plandata = map { /^(\w+) (.*)$/; ($1, $2); }
68                      split(/\n/, $part_bill_event->plandata);
69
70   #XXX may need to fudge some other plandata2option names
71
72   my $balanceover = 0;
73   my $honor_dundate = 0;
74
75   if ( $part_bill_event->plan eq 'suspend-if-balance' ) {
76     $balanceover = delete $plandata{'balanceover'};
77     $honor_dundate = ( (delete $plandata{'balance_honor_dundate'}) =~ /1/ );
78   }
79
80   my $part_event = new FS::part_event {
81     'event'      => $part_bill_event->event,
82     'eventtable' => 'cust_bill',
83     'check_freq' => $part_bill_event->freq || '1d',
84     'weight'     => $part_bill_event->weight,
85     'action'     => $action,
86     'disabled'   => $part_bill_event->disabled,
87   };
88
89   my $error = $part_event->insert(\%plandata);
90   die "error inserting part_event: $error\n" if $error;
91
92   print ' '. $part_event->eventpart;
93
94   my $once = new FS::part_event_condition {
95     'eventpart'     => $part_event->eventpart,
96     'conditionname' => 'once'
97   };
98   $error = $once->insert;
99   die $error if $error;
100
101   my $balance = new FS::part_event_condition {
102     'eventpart'     => $part_event->eventpart,
103     'conditionname' => 'balance'
104   };
105   $error = $balance->insert( 'balance' => $balanceover );
106   die $error if $error;
107
108   my $cust_bill_owed = new FS::part_event_condition {
109     'eventpart'     => $part_event->eventpart,
110     'conditionname' => 'cust_bill_owed'
111   };
112   $error = $cust_bill_owed->insert( 'owed' => 0 );
113   die $error if $error;
114
115   my $payby = new FS::part_event_condition {
116     'eventpart'     => $part_event->eventpart,
117     'conditionname' => 'payby'
118   };
119   $error = $payby->insert( 'payby' => { $part_bill_event->payby => 1 } );
120   die $error if $error;
121
122   if ( $part_bill_event->seconds ) {
123
124     my $age = new FS::part_event_condition { 
125       'eventpart'     => $part_event->eventpart,
126       'conditionname' => 'cust_bill_age'
127     };
128     $error = $age->insert( 'age' => ($part_bill_event->seconds/86400 ).'d' );
129     die $error if $error;
130
131   }
132
133   if ( $honor_dundate ) { 
134     my $dundate = new FS::part_event_condition {
135       'eventpart'     => $part_event->eventpart,
136       'conditionname' => 'dundate'
137     };
138     $error = $dundate->insert();
139     die $error if $error;
140   }
141   
142   #my $derror = $part_bill_event->delete;
143   #die "error removing part_bill_event: $derror\n" if $derror;
144
145   if ( $opt_m ) {
146
147     #this should probably just become the default once we're sure it works 100%
148
149     my $sth = dbh->prepare('
150       INSERT INTO cust_event ( eventpart, tablenum, _date, status, statustext )
151                        SELECT     ?     ,  invnum , _date, status, statustext
152                          FROM cust_bill_event WHERE eventpart = ?
153     ') or die dbh->errstr;
154
155     $sth->execute( $part_event->eventpart, $part_bill_event->eventpart )
156       or die $sth->errstr;
157
158   } else {
159
160     foreach my $cust_bill_event (
161       qsearch({
162         'table'     => 'cust_bill_event',
163         'hashref'   => { 'eventpart' => $part_bill_event->eventpart, },
164       })
165     ) {
166
167       my $cust_event = new FS::cust_event {
168         'eventpart'  => $part_event->eventpart,
169         'tablenum'   => $cust_bill_event->invnum,
170         '_date'      => $cust_bill_event->_date,
171         'status'     => $cust_bill_event->status,
172         'statustext' => $cust_bill_event->statustext,
173       };
174
175       my $cerror = $cust_event->insert;
176       #die "error inserting cust_event: $cerror\n" if $cerror;
177       warn "error inserting cust_event: $cerror\n" if $cerror;
178     
179       #my $dcerror = $cust_bill_event->delete;
180       #die "error removing cust_bill_event: $dcerror\n" if $dcerror;
181
182       print ".";
183
184     }
185
186   }
187
188   print "\n";
189
190 }
191
192 sub usage {
193   die "Usage:\n  freeside-migrate-events user\n"; 
194 }
195
196 =head1 NAME
197
198 freeside-migrate-events - Migrates 1.7/1.8-style invoice events to
199                           1.9/2.0-style billing events
200
201 =head1 SYNOPSIS
202
203   freeside-migrate-events
204
205 =head1 DESCRIPTION
206
207 Migrates events from L<FS::part_bill_event> to L<FS::part_event> and friends,
208 and from L<FS::cust_bill_event> records to L<FS::cust_event>
209
210 =head1 BUGS
211
212 Doesn't migrate any action options yet.
213
214 Doesn't translate option names that changed.
215
216 Doesn't migrate reasons.
217
218 Doesn't delete the old events (which is not a big deal, since the new code
219 won't run them...)
220
221 Can take lots of memory for large databases.
222
223 =head1 SEE ALSO
224
225 =cut
226
227 1;
228
229 __END__
230
231 #part_bill_event      part_event
232 #
233 #eventpart            n/a
234 #event                event
235 #freq                 check_freq
236 #payby                part_event_condition.conditionname = payby
237 #eventcode            PARSE_WITH_REGEX (probably can just get from plandata)
238 #seconds              part_event_condition.conditionname = cust_bill_age
239 #plandata             PARSE_WITH_REGEX (along with eventcode, yuck)
240 #reason               part_event_option.optionname = reason
241 #disabled             disabled
242 #
243
244     #these might help parse existing eventcode
245
246     $c =~ /^\s*\$cust_main\->(suspend|cancel|invoicing_list_addpost|bill|collect)\(\);\s*("";)?\s*$/
247
248       or $c =~ /^\s*\$cust_bill\->(comp|realtime_(card|ach|lec)|batch_card|send)\((%options)*\);\s*$/
249
250       or $c =~ /^\s*\$cust_bill\->send(_if_newest)?\(\'[\w\-\s]+\'\s*(,\s*(\d+|\[\s*\d+(,\s*\d+)*\s*\])\s*,\s*'[\w\@\.\-\+]*'\s*)?\);\s*$/
251
252 #      or $c =~ /^\s*\$cust_main\->apply_payments; \$cust_main->apply_credits; "";\s*$/
253       or $c =~ /^\s*\$cust_main\->apply_payments_and_credits; "";\s*$/
254
255       or $c =~ /^\s*\$cust_main\->charge\( \s*\d*\.?\d*\s*,\s*\'[\w \!\@\#\$\%\&\(\)\-\+\;\:\"\,\.\?\/]*\'\s*\);\s*$/
256
257       or $c =~ /^\s*\$cust_main\->suspend_(if|unless)_pkgpart\([\d\,\s]*\);\s*$/
258
259       or $c =~ /^\s*\$cust_bill\->cust_suspend_if_balance_over\([\d\.\s]*\);\s*$/
260
261       or do {
262         #log
263         return "illegal eventcode: $c";
264       };
265
266   }
267
268