svc_hardware: better error messages for bad hw_addr when not validating as a MAC...
[freeside.git] / bin / cust_bill-credit_ship2
1 #!/usr/bin/perl -w
2
3 use strict;
4 use vars qw( $opt_d $opt_f $opt_s $opt_p $opt_m $opt_c $opt_e $opt_r $opt_k );
5 use Getopt::Std;
6 use Date::Parse qw(str2time);
7 use FS::UID qw( dbh adminsuidsetup );
8 use FS::Record qw( qsearch qsearchs );
9 use FS::cust_main_county;
10 use FS::cust_bill;
11 use FS::msg_template;
12 use FS::cust_credit;
13 use FS::cust_credit_bill;
14
15 getopts('d:f:s:pm:c:erk');
16
17 my $user = shift or &usage;
18 adminsuidsetup $user;
19
20 #i should be an option
21 my $taxname = 'HST, ON';
22 my $rate = '13';
23                                                                                 
24 my $oldAutoCommit = $FS::UID::AutoCommit;
25 local $FS::UID::AutoCommit = 0;
26 my $dbh = dbh;
27
28 ###
29 # find tax classes with double taxes
30 ##
31
32 #my $sql = "select distinct taxclass from cust_main_county where taxname = '$taxname';";
33 #my $sth = dbh->prepare($sql) or die dbh->errstr;
34 #$sth->execute or die $sth->errstr;
35 #my @taxclasses = map $_->[0], @{ $sth->fetchall_arrayref() };
36 #
37 #my %taxclass = map {
38 #                my $taxclass = $_;
39 #                my @cust_main_county = qsearch('cust_main_county', {
40 #                                         'taxclass' => $taxclass,
41 #                                         'taxname'  => $taxname,
42 #                                         'tax'      => { op=>'>', value=>'0' },
43 #                                       });
44 #
45 #                $taxclass => ( scalar(@cust_main_county) > 1 );
46 #
47 #              } @taxclasses;
48
49 my %taxclass = map { $_ => 1 } (
50   'InternetService',
51   'ComputerSetup-Remote',
52   'Config File- Static IPs',
53   'GST',
54   'GST + PST',
55   'GST ONLY',
56   'HST ONLY',
57   'HardwareRental',
58   'HardwareSale',
59   'HardwareSale_WEx',
60   'InternetService_WEx',
61   'LondDistanceFixedRate',
62   'LondDistanceVariableRate',
63   'PST',
64   'SARA-ONLY-GSTPayable',
65   'SetupInternet',
66   'SetupVoice',
67   'Shipping',
68   'SoftwareLicenseToUse',
69   'TDMPRI',
70   'TDMPRI_WEx',
71   'WarrantyCoverageHardware',
72   'WarrantyCoverageSoftware',
73   'WebHosting',
74   'federal and qst',
75   'telephoneNumber',
76   'LongDistanceFixedRate_WEx',
77   'LongDistanceVariableRate_WEx',
78 );
79
80 my %h;
81 $FS::Record::nowarn_classload = 1;
82 $FS::Record::nowarn_classload = 1;
83
84 ###
85 # find invoices
86 ###
87
88 #my $extra_sql = $opt_s ? " AND cust_main.state = '$opt_s' " : '';
89
90 my $start_date = $opt_d ? str2time($opt_d) : 0;
91 my $end_date = $opt_f ? str2time($opt_f) - 1 : 1375471643;
92
93 my @cust_bill = qsearch({
94   'select'    => 'cust_bill.*',
95   'table'     => 'cust_bill',
96   'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
97   'hashref'   => { '_date' => { op=>'>', value=>$start_date } },
98   'extra_sql' => " AND _date < $end_date ",
99 #  'extra_sql' => $extra_sql,
100 });
101
102 my( @billpkgnums, @setuprecurs, @amounts );
103
104 foreach my $cust_bill ( @cust_bill ) {
105
106   my $tax = $cust_bill->tax;
107
108   my $cust_main = $cust_bill->cust_main;
109
110   next if $cust_bill->charged == 0
111        or $tax == 0
112        or $cust_main->tax eq 'Y';
113
114   my $credit = 0;
115
116   @billpkgnums = ();
117   @setuprecurs = ();
118   @amounts = ();
119
120   foreach my $cust_bill_pkg ( $cust_bill->cust_bill_pkg ) {
121     my $cust_pkg = $cust_bill_pkg->cust_pkg or next;
122
123 #    #my $loc = $cust_pkg->cust_location_or_main;
124 #    #next if $opt_s && $loc->state ne $opt_s;
125 #    next if $cust_pkg->cust_location; #we did these already
126 #    next if $opt_s && $cust_main->ship_state ne $opt_s;
127 #    next if $opt_s && $cust_main->state eq $opt_s; #we did these already too
128 #
129     if ( $opt_s ) {
130       if ( my $loc = $cust_pkg->cust_location ) {
131         next if $loc->state ne $opt_s;
132       } else {
133         next if ($cust_main->ship_state || $cust_main->state) ne $opt_s;
134       }
135     }
136
137     my $part_pkg = $cust_pkg->part_pkg;
138
139     #these were changed already
140     # next unless $taxclass{ $part_pkg->taxclass };
141
142     my $h = $h{$part_pkg->pkgpart} ||= qsearchs({
143       'table'     => 'h_part_pkg', 
144       'hashref'   => { 'pkgpart'      => $part_pkg->pkgpart,
145                        'history_date' => { op=>'<', value=>$cust_bill->_date },
146                      },
147       'extra_sql' => "AND history_action IN ( 'insert', 'replace_new' )",
148       'order_by'  => 'ORDER BY HISTORYNUM DESC LIMIT 1',
149     });
150     if ( !$h ) {
151       warn "can't find history record for pkgpart ". $part_pkg->pkgpart.
152            " (invoice ". $cust_bill->invnum. ")\n";
153       #well, we have no idea what the mystery package was, so, we have no idea
154       # if we need to credit it or not.
155       next; #assuming, not...
156     }
157     next unless $taxclass{ $h->taxclass }
158              || ( $h->taxclass eq 'AllTaxesApply' && $cust_bill->_date < 1371750732 ) ;
159
160     #my $amount = $cust_bill_pkg->setup + $cust_bill_pkg->recur;
161     #$credit += $rate * $amount / 100;
162
163     if ( $cust_bill_pkg->setup ) {
164       my $scredit = sprintf('%.2f', ($rate * $cust_bill_pkg->setup / 100) + 0.00000001);
165       $credit += $scredit;
166       push @setuprecurs, 'setup';
167       push @billpkgnums, $cust_bill_pkg->billpkgnum;
168       push @amounts, $scredit;
169     } 
170
171     if ( $cust_bill_pkg->recur ) {
172       my $rcredit = sprintf('%.2f', ($rate * $cust_bill_pkg->recur / 100) + 0.00000001);
173       $credit += $rcredit;
174       push @setuprecurs, 'recur';
175       push @billpkgnums, $cust_bill_pkg->billpkgnum;
176       push @amounts, $rcredit;
177     }
178
179   }
180
181   $credit = sprintf('%.2f', $credit + 0.00000001);
182
183   next if $credit == 0;
184
185   #$credit = sprintf('%.2f', $credit + 0.00000001);
186
187   warn "invoice ". $cust_bill->invnum. ": credit of $credit is more than orignally taxed ($tax)\n" if $credit > $tax;
188
189   warn "invoice ". $cust_bill->invnum. ": credit of $credit is more than 50% of originally taxed ($tax)\n" if $credit-0.01 > $tax/2;
190
191   #my $cr_percent = sprintf('%.1f', 100 * $credit / $tax);
192
193   my $cur_cr = 0;
194   $cur_cr += $_->amount foreach $cust_bill->cust_credited;
195   $cur_cr = '' if $cur_cr == 0;
196
197   next if $cur_cr > 0 && $opt_k;
198
199   if ( $opt_p ) {
200     #print $cust_bill->invnum. ','. $cust_bill->custnum. ",$tax,$credit,$cr_percent%\n";
201 #    print $cust_bill->invnum. ','. $cust_bill->custnum. ',"'.
202 #          $cust_bill->cust_main->name. '",'. "$tax,$credit,$cur_cr\n";
203     print $cust_bill->invnum. ','. $cust_bill->custnum. ',"'.
204           $cust_bill->cust_main->name. '",'. "$tax,$credit\n";
205   }
206
207 #  if ( $opt_m && ! $opt_r ) {
208 #
209 #    my $msg_template = qsearchs('msg_template', { 'msgnum' => $opt_m } )
210 #        or die "Template $opt_m not found";
211 #    my $error = $msg_template->send(
212 #      'cust_main' => $cust_main,
213 #      'object'    => $cust_main,
214 #    );
215 #    if ( $error ) {
216 #      warn "error sending email for invnum ". $cust_bill->invnum. ','.
217 #           " custnum ". $cust_bill->custnum. ": $error\n";
218 #    }
219 #  }
220
221   if ( $opt_c ) {
222     my $cust_credit = new FS::cust_credit {
223       'custnum'   => $cust_main->custnum,
224       'amount'    => $credit,
225       'reasonnum' => $opt_c,
226     };
227     my $error = $cust_credit->insert;
228     if ( $error ) {
229       warn "error inserting credit: $error\n";
230     }
231     my $cust_credit_bill = new FS::cust_credit_bill {
232       'crednum' => $cust_credit->crednum,
233       'invnum'  => $cust_bill->invnum,
234       'amount'  => $credit,
235     };
236     my $aerror = $cust_credit_bill->insert;
237     if ( $aerror ) {
238       warn "error applying credit to invnum ". $cust_bill->invnum. ": $aerror\n";
239     }
240   }
241
242 #  if ( $opt_e && ! $opt_r ) {
243 #    eval { $cust_bill->email };
244 #    if ( $@ ) {
245 #      warn "error sending invnum ". $cust_bill->invnum. ','.
246 #         " custnum ". $cust_bill->custnum. ": $@\n";
247 #    }
248 #  }
249
250 }
251
252 if ( $opt_r ) {
253   $dbh->rollback or die $dbh->errstr; #if $oldAutoCommit;
254 } else {
255   $dbh->commit or die $dbh->errstr; #if $oldAutoCommit;
256 }
257
258 sub usage {                                                                     
259   die "usage:  cust_bill-credit [ -d date ] [ -s state ] [ -p ] [ -m templatenum ] [ -c reasonnum ] [ -e ] [ -r ] employee_username\n";
260 }                                                                               
261                                                                                 
262 =head1 NAME                                                                     
263                                                                                 
264 cust_bill-credit
265                                                                                 
266 =head1 SYNOPSIS                                                                 
267                                                                                 
268   cust_bill-credit [ -d date ] [ -s state ] [ -p ] [ -m templatenum ] [ -c reasonnum ] [ -e ] employee_username   
269                                                                                 
270 =head1 DESCRIPTION
271
272 Command-line tool to search for and credit invoices.
273
274 -d: Search for invoices starting from this date
275
276 -f: Search for invoice finishing on this date
277
278 -s: Search for invoices for packages within this state
279
280 -p: Print an informational line for each invoice with invnum, custnum, original tax amount, calculate credit, and credit percentage of original.
281
282 -m: Send an email to the customer with this message template.
283
284 -c: Credit the invoice for one-half of the taxation amount, using this reason.
285
286 -k: But, don't credit if the customer already has a credit applied against this invoice
287
288 -e: re-Email invoice
289
290 -r: dRy run
291
292 employee_username
293
294 =head1 BUGS
295
296 =head1 SEE ALSO
297
298 L<FS::part_pkg>
299
300 =cut
301
302 1;