#!/usr/bin/perl use strict; use warnings; use vars qw( %opt ); use Date::Format; use File::Slurp; use FS::Misc::Getopt; use FS::Record qw(qsearch qsearchs dbh); getopts('cpiXr:t:u:vk:f'); my $dbh = dbh; $FS::UID::AutoCommit = 0; sub usage() { "Usage: bulk_void -s start -e end -r void_reason { -c | -p | -i } [ -t payby ] [ -u filename ] [ -k pkgpart ] [ -v ] [ -X ] -s, -e: date range (required) -r: void reason text (required) -c, -p, -i: void credits, payments, invoices -u: specifies a filename of customer numbers - only void for those customers -k: skip invoices with only this pkgpart -t: only void payments with this payby -f: force - continue voiding invoices even if some have errors -v: verbose - show more detail -X: commit changes "; } if (!$opt{start} or !$opt{end} or !$opt{r}) { die usage; } print "DRY RUN--changes will not be committed.\n" unless $opt{X}; my %search = (); $search{payby} = $opt{t} if $opt{t} && $opt{p}; my $extra_sql = (keys %search ? ' AND ' : ' WHERE '). " _date >= $opt{start} AND _date <= $opt{end}"; if ( $opt{u} ) { my @custnums = map { chomp; $_; } read_file($opt{u}); $extra_sql .= ' AND custnum IN ('. join(',', @custnums). ') '; } my %tables = ( c => 'cust_credit', p => 'cust_pay', i => 'cust_bill', ); my $reason = $opt{r}; foreach my $k (keys %tables) { next unless $opt{$k}; my $table = $tables{$k}; debug("$table:"); my $done_count = 0; my $error_count = 0; my $pkey = ''; my $cursor = FS::Cursor->new({ table => $table, hashref => \%search, extra_sql => $extra_sql, }); my $error; while (my $record = $cursor->fetch) { if ( $opt{k} && $opt{i} ) { my @other_pkgs = grep { $_->pkgpart != $opt{k} } grep $_, map $_->cust_pkg, $record->cust_bill_pkg; next if ! @other_pkgs; } if ( $opt{v} ) { $pkey ||= $record->primary_key; my $num = $record->get($pkey); my $date = time2str('%x', $record->_date); my $name = $record->cust_main->name; warn "Voiding $table #$num ($date) for $name\n"; } $error = $record->void($reason); if ( $error ) { $error = "$table #" . $record->get($record->primary_key) . ": $error"; print "$error\n"; $error_count++; if ( $opt{X} && ! $opt{f} ) { $dbh->rollback; exit(1); } } else { $done_count++; } } print " $table voided: $done_count\n errors: $error_count\n"; } if ( $opt{X} ) { $dbh->commit; print "Committed changes.\n"; } else { $dbh->rollback; print "Reverted changes.\n"; }