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