deb 9
[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:f');
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 -f: force - continue voiding invoices even if some have errors
33 -v: verbose - show more detail
34 -X: commit changes
35 ";
36 }
37
38 if (!$opt{start} or !$opt{end} or !$opt{r}) {
39   die usage;
40 }
41
42 print "DRY RUN--changes will not be committed.\n" unless $opt{X};
43
44 my %search = ();
45 $search{payby} = $opt{t} if $opt{t} && $opt{p};
46
47 my $extra_sql = (keys %search ? ' AND ' : ' WHERE ').
48                 " _date >= $opt{start} AND _date <= $opt{end}";
49
50 if ( $opt{u} ) {
51   my @custnums = map { chomp; $_; } read_file($opt{u});
52   $extra_sql .= ' AND custnum IN ('. join(',', @custnums). ') ';
53 }
54
55 my %tables = (
56   c => 'cust_credit',
57   p => 'cust_pay',
58   i => 'cust_bill',
59 );
60
61 my $reason = $opt{r};
62
63
64 foreach my $k (keys %tables) {
65   next unless $opt{$k};
66   my $table = $tables{$k};
67   debug("$table:");
68   my $done_count = 0;
69   my $error_count = 0;
70   my $pkey = '';
71
72   my $cursor = FS::Cursor->new({
73     table     => $table,
74     hashref   => \%search,
75     extra_sql => $extra_sql,
76   });
77   my $error;
78   while (my $record = $cursor->fetch) {
79
80     if ( $opt{k} && $opt{i} ) {
81       my @other_pkgs = grep { $_->pkgpart != $opt{k} }
82                          grep $_, map $_->cust_pkg, $record->cust_bill_pkg;
83       next if ! @other_pkgs;
84     }
85
86     if ( $opt{v} ) {
87       $pkey ||= $record->primary_key;
88       my $num = $record->get($pkey);
89       my $date = time2str('%x', $record->_date);
90       my $name = $record->cust_main->name;
91       warn "Voiding $table #$num ($date) for $name\n";
92     }
93
94     $error = $record->void($reason);
95     if ( $error ) {
96       $error = "$table #" . $record->get($record->primary_key) . ": $error";
97       print "$error\n";
98       $error_count++;
99       if ( $opt{X} && ! $opt{f} ) {
100         $dbh->rollback;
101         exit(1);
102       }
103     } else {
104       $done_count++;
105     }
106   }
107   print " $table voided: $done_count\n errors: $error_count\n";
108 }
109
110 if ( $opt{X} ) {
111   $dbh->commit;
112   print "Committed changes.\n";
113 } else {
114   $dbh->rollback;
115   print "Reverted changes.\n";
116 }
117