9 use FS::Record qw(qsearch qsearchs dbh);
11 getopts('cpiXr:t:u:vk:f');
14 $FS::UID::AutoCommit = 0;
17 "Usage: bulk_void -s start -e end
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
38 if (!$opt{start} or !$opt{end} or !$opt{r}) {
42 print "DRY RUN--changes will not be committed.\n" unless $opt{X};
45 $search{payby} = $opt{t} if $opt{t} && $opt{p};
47 my $extra_sql = (keys %search ? ' AND ' : ' WHERE ').
48 " _date >= $opt{start} AND _date <= $opt{end}";
51 my @custnums = map { chomp; $_; } read_file($opt{u});
52 $extra_sql .= ' AND custnum IN ('. join(',', @custnums). ') ';
64 foreach my $k (keys %tables) {
66 my $table = $tables{$k};
72 my $cursor = FS::Cursor->new({
75 extra_sql => $extra_sql,
78 while (my $record = $cursor->fetch) {
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;
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";
94 $error = $record->void($reason);
96 $error = "$table #" . $record->get($record->primary_key) . ": $error";
99 if ( $opt{X} && ! $opt{f} ) {
107 print " $table voided: $done_count\n errors: $error_count\n";
112 print "Committed changes.\n";
115 print "Reverted changes.\n";