f31eea5cd7cd3be7a8bb0c0af14ef1ffb64c2e36
[freeside.git] / bin / bulk_void
1 #!/usr/bin/perl
2
3 use strict;
4 use warnings;
5 use vars qw( %opt );
6 use Date::Format;
7 use File::Slurp;
8 use FS::Misc::Getopt;
9 use FS::Record qw(qsearch qsearchs dbh);
10
11 getopts('cpiXr:t:u:vk:');
12
13 my $dbh = dbh;
14 $FS::UID::AutoCommit = 0;
15
16 sub usage() {
17   "Usage: bulk_void  -s start -e end
18                   -r void_reason
19                   { -c | -p | -i }
20                   [ -t payby ]
21                   [ -u filename ]
22                   [ -k pkgpart ]
23                   [ -v ]
24                   [ -X ]
25                   <user>
26 -s, -e: date range (required)
27 -r: void reason text (required)
28 -c, -p, -i: void credits, payments, invoices
29 -u: specifies a filename of customer numbers - only void for those customers
30 -k: skip invoices with only this pkgpart
31 -t: only void payments with this payby
32 -v: verbose - show more detail
33 -X: commit changes
34 ";
35 }
36
37 if (!$opt{start} or !$opt{end} or !$opt{r}) {
38   die usage;
39 }
40
41 print "DRY RUN--changes will not be committed.\n" unless $opt{X};
42
43 my %search = ();
44 $search{payby} = $opt{t} if $opt{t} && $opt{p};
45
46 my $extra_sql = (keys %search ? ' AND ' : ' WHERE ').
47                 " _date >= $opt{start} AND _date <= $opt{end}";
48
49 if ( $opt{u} ) {
50   my @custnums = map { chomp; $_; } read_file($opt{u});
51   $extra_sql .= ' AND custnum IN ('. join(',', @custnums). ') ';
52 }
53
54 my %tables = (
55   c => 'cust_credit',
56   p => 'cust_pay',
57   i => 'cust_bill',
58 );
59
60 my $reason = $opt{r};
61
62
63 foreach my $k (keys %tables) {
64   next unless $opt{$k};
65   my $table = $tables{$k};
66   debug("$table:");
67   my $done_count = 0;
68   my $error_count = 0;
69   my $pkey = '';
70
71   my $cursor = FS::Cursor->new({
72     table     => $table,
73     hashref   => \%search,
74     extra_sql => $extra_sql,
75   });
76   my $error;
77   while (my $record = $cursor->fetch) {
78
79     if ( $opt{k} && $opt{i} ) {
80       my @other_pkgs = grep { $_->pkgpart != $opt{k} }
81                          grep $_, map $_->cust_pkg, $record->cust_bill_pkg;
82       next if ! @other_pkgs;
83     }
84
85     if ( $opt{v} ) {
86       $pkey ||= $record->primary_key;
87       my $num = $record->get($pkey);
88       my $date = time2str('%x', $record->_date);
89       my $name = $record->cust_main->name;
90       warn "Voiding $table #$num ($date) for $name\n";
91     }
92
93     $error = $record->void($reason);
94     if ( $error ) {
95       $error = "$table #" . $record->get($record->primary_key) . ": $error";
96       print "$error\n";
97       $error_count++;
98       if ( $opt{X} ) {
99         $dbh->rollback;
100         exit(1);
101       }
102     } else {
103       $done_count++;
104     }
105   }
106   print " $table voided: $done_count\n errors: $error_count\n";
107 }
108
109 if ( $opt{X} ) {
110   $dbh->commit;
111   print "Committed changes.\n";
112 } else {
113   $dbh->rollback;
114   print "Reverted changes.\n";
115 }
116