+ $class->_upgrade_otaker(%opt);
+
+ # if we do this anywhere else, it should become an FS::Upgrade method
+ my $num_to_upgrade = $class->count('paybatch is not null');
+ my $num_jobs = FS::queue->count('job = \'FS::cust_pay::process_upgrade_paybatch\' and status != \'failed\'');
+ if ( $num_to_upgrade > 0 ) {
+ warn "Need to migrate paybatch field in $num_to_upgrade payments.\n";
+ if ( $opt{queue} ) {
+ if ( $num_jobs > 0 ) {
+ warn "Upgrade already queued.\n";
+ } else {
+ warn "Scheduling upgrade.\n";
+ my $job = FS::queue->new({ job => 'FS::cust_pay::process_upgrade_paybatch' });
+ $job->insert;
+ }
+ } else {
+ process_upgrade_paybatch();
+ }
+ }
+
+ ###
+ # set paycardtype
+ ###
+ $class->upgrade_set_cardtype;
+
+ # for batch payments, make sure paymask is set
+ do {
+ local $FS::payinfo_Mixin::allow_closed_replace = 1;
+ local $FS::payinfo_Mixin::ignore_masked_payinfo = 1;
+
+ my $cursor = FS::Cursor->new({
+ table => 'cust_pay',
+ extra_sql => ' WHERE paymask IS NULL AND payinfo IS NOT NULL
+ AND payby IN(\'CARD\', \'CHEK\')
+ AND batchnum IS NOT NULL',
+ });
+
+ # records from cursors for some reason don't decrypt payinfo, so
+ # call replace_old to fetch the record "normally"
+ while (my $cust_pay = $cursor->fetch) {
+ $cust_pay = $cust_pay->replace_old;
+ $cust_pay->set('paymask', $cust_pay->mask_payinfo);
+ my $error = $cust_pay->replace;
+ if ($error) {
+ die "$error (setting masked payinfo on payment#". $cust_pay->paynum.
+ ")\n"
+ }
+ }
+ };
+}
+
+sub process_upgrade_paybatch {
+ my $dbh = dbh;
+ local $FS::payinfo_Mixin::ignore_masked_payinfo = 1;
+ local $FS::UID::AutoCommit = 1;
+
+ ###
+ # migrate batchnums from the misused 'paybatch' field to 'batchnum'
+ ###
+ my $text = (driver_name =~ /^mysql/i) ? 'char' : 'text';
+ my $search = FS::Cursor->new( {
+ 'table' => 'cust_pay',
+ 'addl_from' => " JOIN pay_batch ON cust_pay.paybatch = CAST(pay_batch.batchnum AS $text) ",
+ } );
+ while (my $cust_pay = $search->fetch) {
+ $cust_pay->set('batchnum' => $cust_pay->paybatch);
+ $cust_pay->set('paybatch' => '');
+ my $error = $cust_pay->replace;
+ warn "error setting batchnum on cust_pay #".$cust_pay->paynum.":\n $error"
+ if $error;
+ }
+
+ ###
+ # migrate gateway info from the misused 'paybatch' field
+ ###
+
+ # not only cust_pay, but also voided and refunded payments
+ if (!FS::upgrade_journal->is_done('cust_pay__parse_paybatch_1')) {
+ local $FS::Record::nowarn_classload=1;
+ # really inefficient, but again, only has to run once
+ foreach my $table (qw(cust_pay cust_pay_void cust_refund)) {
+ my $and_batchnum_is_null =
+ ( $table =~ /^cust_pay/ ? ' AND batchnum IS NULL' : '' );
+ my $pkey = ($table =~ /^cust_pay/ ? 'paynum' : 'refundnum');
+ my $search = FS::Cursor->new({
+ table => $table,
+ extra_sql => "WHERE payby IN('CARD','CHEK') ".
+ "AND (paybatch IS NOT NULL ".
+ "OR (paybatch IS NULL AND auth IS NULL
+ $and_batchnum_is_null ) )
+ ORDER BY $pkey DESC"
+ });
+ while ( my $object = $search->fetch ) {
+ if ( $object->paybatch eq '' ) {
+ # repair for a previous upgrade that didn't save 'auth'
+ my $pkey = $object->primary_key;
+ # find the last history record that had a paybatch value
+ my $h = qsearchs({
+ table => "h_$table",
+ hashref => {
+ $pkey => $object->$pkey,
+ paybatch => { op=>'!=', value=>''},
+ history_action => 'replace_old',
+ },
+ order_by => 'ORDER BY history_date DESC LIMIT 1',
+ });
+ if (!$h) {
+ warn "couldn't find paybatch history record for $table ".$object->$pkey."\n";
+ next;
+ }
+ # if the paybatch didn't have an auth string, then it's fine
+ $h->paybatch =~ /:(\w+):/ or next;
+ # set paybatch to what it was in that record
+ $object->set('paybatch', $h->paybatch)
+ # and then upgrade it like the old records
+ }