shish 2 місяців тому
батько
коміт
8ff1b2f6d8

+ 154 - 0
biz-ghs/clear/classes/OrderCgClearClass.php

@@ -734,4 +734,158 @@ class OrderCgClearClass extends BaseClass
         return array_values(array_unique(array_filter(array_map('intval', $arr))));
     }
 
+    protected static function zeroAmountWhereSql($alias = '')
+    {
+        $prefix = $alias !== '' ? $alias . '.' : '';
+        return '(' . $prefix . 'amount IS NULL OR ' . $prefix . 'amount = 0 OR ' . $prefix . 'amount = \'0\' OR ' . $prefix . 'amount = \'0.00\')';
+    }
+
+    protected static function pairAmountKey($orderId, $cgId)
+    {
+        return intval($orderId) . '_' . intval($cgId);
+    }
+
+    /**
+     * @return array<string,string> orderId_cgId => amount
+     */
+    public static function buildPairAmountMap($clear)
+    {
+        $pairs = self::buildPairsFromClear($clear);
+        $map = [];
+        foreach ($pairs as $pair) {
+            $amount = self::normalizeAmount($pair['amount'] ?? 0);
+            if (bccomp($amount, '0', 2) <= 0) {
+                continue;
+            }
+            $map[self::pairAmountKey($pair['orderId'] ?? 0, $pair['cgId'] ?? 0)] = $amount;
+        }
+        return $map;
+    }
+
+    public static function resolveAmountForRelationRow(array $row, array $pairAmountMap)
+    {
+        $orderId = intval($row['orderId'] ?? 0);
+        $cgId = intval($row['cgId'] ?? 0);
+        $key = self::pairAmountKey($orderId, $cgId);
+        if (isset($pairAmountMap[$key])) {
+            return $pairAmountMap[$key];
+        }
+        if ($cgId === 0 && $orderId > 0) {
+            $legacyKey = self::pairAmountKey(0, $orderId);
+            if (isset($pairAmountMap[$legacyKey])) {
+                return $pairAmountMap[$legacyKey];
+            }
+            $cg = PurchaseOrderClass::getById($orderId);
+            if (!empty($cg)) {
+                $amount = self::resolveDebtAmount($cg);
+                if (bccomp($amount, '0', 2) > 0) {
+                    return $amount;
+                }
+            }
+        }
+        if ($orderId > 0) {
+            $order = OrderClass::getById($orderId);
+            if (!empty($order)) {
+                $amount = self::resolveDebtAmount($order);
+                if (bccomp($amount, '0', 2) > 0) {
+                    return $amount;
+                }
+            }
+        }
+        if ($cgId > 0) {
+            $purchase = PurchaseClass::getById($cgId);
+            if (!empty($purchase)) {
+                $amount = self::resolveDebtAmount($purchase);
+                if (bccomp($amount, '0', 2) > 0) {
+                    return $amount;
+                }
+            }
+            $ghsCg = PurchaseOrderClass::getById($cgId);
+            if (!empty($ghsCg)) {
+                $amount = self::resolveDebtAmount($ghsCg);
+                if (bccomp($amount, '0', 2) > 0) {
+                    return $amount;
+                }
+            }
+        }
+        return null;
+    }
+
+    public static function countZeroAmountRelationRows()
+    {
+        $table = self::$baseFile::tableName();
+        return intval(Yii::$app->db->createCommand(
+            'SELECT COUNT(*) FROM ' . $table . ' WHERE ' . self::zeroAmountWhereSql()
+        )->queryScalar());
+    }
+
+    /**
+     * @return array[] id, clearId, orderId, cgId
+     */
+    public static function fetchZeroAmountRelationBatch($cursorId, $limit)
+    {
+        $table = self::$baseFile::tableName();
+        $sql = 'SELECT id, clearId, orderId, cgId FROM ' . $table
+            . ' WHERE ' . self::zeroAmountWhereSql();
+        $params = [];
+        if ($cursorId !== null) {
+            $sql .= ' AND id < :cursorId';
+            $params[':cursorId'] = intval($cursorId);
+        }
+        $sql .= ' ORDER BY id DESC LIMIT ' . intval($limit);
+        return Yii::$app->db->createCommand($sql)->bindValues($params)->queryAll();
+    }
+
+    /**
+     * @param array[] $rows
+     * @return array{updated:int,skipped:int}
+     */
+    public static function patchZeroAmountRows(array $rows, $dryRun = false)
+    {
+        if (empty($rows)) {
+            return ['updated' => 0, 'skipped' => 0];
+        }
+        $clearIds = array_values(array_unique(array_filter(array_map('intval', array_column($rows, 'clearId')))));
+        $clearList = ClearClass::getAllByCondition(['id' => ['in', $clearIds]], null, '*', null);
+        $clearMap = [];
+        if (!empty($clearList)) {
+            foreach ($clearList as $clear) {
+                $id = is_array($clear) ? ($clear['id'] ?? 0) : ($clear->id ?? 0);
+                if ($id > 0) {
+                    $clearMap[intval($id)] = $clear;
+                }
+            }
+        }
+        $pairMaps = [];
+        foreach ($clearIds as $clearId) {
+            if (isset($clearMap[$clearId])) {
+                $pairMaps[$clearId] = self::buildPairAmountMap($clearMap[$clearId]);
+            } else {
+                $pairMaps[$clearId] = [];
+            }
+        }
+
+        $table = self::$baseFile::tableName();
+        $updated = 0;
+        $skipped = 0;
+        foreach ($rows as $row) {
+            $relId = intval($row['id'] ?? 0);
+            $clearId = intval($row['clearId'] ?? 0);
+            if ($relId <= 0 || $clearId <= 0) {
+                $skipped++;
+                continue;
+            }
+            $amount = self::resolveAmountForRelationRow($row, $pairMaps[$clearId] ?? []);
+            if ($amount === null || bccomp($amount, '0', 2) <= 0) {
+                $skipped++;
+                continue;
+            }
+            if (!$dryRun) {
+                Yii::$app->db->createCommand()->update($table, ['amount' => $amount], ['id' => $relId])->execute();
+            }
+            $updated++;
+        }
+        return ['updated' => $updated, 'skipped' => $skipped];
+    }
+
 }

+ 106 - 0
console/controllers/ClearController.php

@@ -107,6 +107,112 @@ class ClearController extends Controller
         return $this->runSampleCheckOrderCgClear();
     }
 
+    /**
+     * 补写 xhOrderCgClear 中 amount=0 的行(取关联订单/采购单 actPrice)
+     *
+     * 用法:
+     *   php yii clear/patch-order-cg-clear-amount --dryRun=1 --limit=1000
+     *   php yii clear/patch-order-cg-clear-amount --reset=1 --batchSize=100 --sleepMs=200
+     */
+    public function actionPatchOrderCgClearAmount()
+    {
+        ini_set('memory_limit', '512M');
+        set_time_limit(0);
+
+        if ($this->batchSize < 1) {
+            $this->stderr("batchSize 必须大于 0\n");
+            return ExitCode::UNSPECIFIED_ERROR;
+        }
+
+        $this->backfillProgressKey = 'amount';
+        $pending = OrderCgClearClass::countZeroAmountRelationRows();
+
+        if ($this->reset) {
+            $this->clearBackfillProgress();
+            $this->stdout("已重置补 amount 进度\n");
+        }
+
+        $cursorId = $this->loadBackfillProgress();
+        $total = 0;
+        $updated = 0;
+        $skipped = 0;
+        $batchNo = 0;
+
+        $this->stdout(sprintf(
+            "补写 amount | 待处理约 %d 行 | dryRun=%d batchSize=%d sleepMs=%d limit=%d | 续跑 id<%s | 进度=%s\n",
+            $pending,
+            (int)$this->dryRun,
+            $this->batchSize,
+            $this->sleepMs,
+            (int)$this->limit,
+            $cursorId === null ? 'max' : (string)$cursorId,
+            $this->getBackfillProgressFile()
+        ));
+
+        while (true) {
+            if ($this->limit > 0 && $total >= $this->limit) {
+                break;
+            }
+            $fetchLimit = $this->batchSize;
+            if ($this->limit > 0) {
+                $fetchLimit = min($fetchLimit, $this->limit - $total);
+                if ($fetchLimit < 1) {
+                    break;
+                }
+            }
+
+            $rows = OrderCgClearClass::fetchZeroAmountRelationBatch($cursorId, $fetchLimit);
+            if (empty($rows)) {
+                break;
+            }
+            $batchNo++;
+            $batchMinId = null;
+
+            $result = OrderCgClearClass::patchZeroAmountRows($rows, (bool)$this->dryRun);
+            $batchUpdated = intval($result['updated'] ?? 0);
+            $batchSkipped = intval($result['skipped'] ?? 0);
+            $updated += $batchUpdated;
+            $skipped += $batchSkipped;
+            $total += count($rows);
+
+            foreach ($rows as $row) {
+                $batchMinId = intval($row['id']);
+                $cursorId = $batchMinId;
+            }
+
+            if (!$this->dryRun && $batchMinId !== null) {
+                $this->saveBackfillProgress($batchMinId);
+            }
+
+            $this->stdout(sprintf(
+                "第 %d 批:扫描 %d 行,补写 %d,跳过 %d,checkpoint id=%d\n",
+                $batchNo,
+                count($rows),
+                $batchUpdated,
+                $batchSkipped,
+                (int)$batchMinId
+            ));
+
+            if (count($rows) < $fetchLimit) {
+                break;
+            }
+            $this->sleepBetweenBatch();
+        }
+
+        if (!$this->dryRun && $cursorId !== null) {
+            $this->stdout("进度已保存,下次从 id < {$cursorId} 继续\n");
+        }
+        $remaining = OrderCgClearClass::countZeroAmountRelationRows();
+        $this->stdout(sprintf(
+            "完成:处理 %d 行,补写 %d,跳过 %d,剩余 amount=0 约 %d 行\n",
+            $total,
+            $updated,
+            $skipped,
+            $remaining
+        ));
+        return ExitCode::OK;
+    }
+
     /**
      * 漏补 xhOrderCgClear:clearStyle 1(hd2Gys) + 2(gys2Hd) + 3(gys2KmGys) 统一扫描
      *