shish 1 개월 전
부모
커밋
8b35cd3b70
2개의 변경된 파일162개의 추가작업 그리고 30개의 파일을 삭제
  1. 129 10
      biz-ghs/custom/classes/AccountMoneyClass.php
  2. 33 20
      console/controllers/GhsPurchaseBalanceMergeController.php

+ 129 - 10
biz-ghs/custom/classes/AccountMoneyClass.php

@@ -429,12 +429,129 @@ class AccountMoneyClass
         return bcadd((string)($sum ?: '0'), '0', 2);
     }
 
+    /**
+     * 【用途】统计某采购供货商仍待结的 ghs 采购单笔数。
+     */
+    public static function countPurchaseOrderDebtForGhs($ghsId)
+    {
+        $ghsId = intval($ghsId);
+        if ($ghsId <= 0) {
+            return 0;
+        }
+        return (int)PurchaseOrderClass::getCount([
+            'ghsId' => $ghsId,
+            'debt' => PurchaseOrderClass::DEBT_YES,
+        ]);
+    }
+
+    /**
+     * 【用途】统计应清理的幽灵待结单数量(actPrice<=0 或已取消仍标待结,去重)。
+     */
+    public static function countInvalidPurchaseDebtOrders($ghsId)
+    {
+        $ids = self::collectInvalidPurchaseDebtOrderIds($ghsId);
+        return count($ids);
+    }
+
+    /**
+     * 【用途】有效待结采购单统计(排除幽灵单后的笔数与金额,重算脚本与预览共用)。
+     * @return array{num:int,amount:string}
+     */
+    public static function getValidPurchaseDebtStats($ghsId)
+    {
+        $ghsId = intval($ghsId);
+        if ($ghsId <= 0) {
+            return ['num' => 0, 'amount' => '0.00'];
+        }
+        $invalidIds = self::collectInvalidPurchaseDebtOrderIds($ghsId);
+        $list = PurchaseOrderClass::getAllByCondition([
+            'ghsId' => $ghsId,
+            'debt' => PurchaseOrderClass::DEBT_YES,
+        ], null, 'id,actPrice', null, true);
+        $num = 0;
+        $amount = '0.00';
+        if (!empty($list)) {
+            foreach ($list as $order) {
+                $orderId = intval($order->id ?? 0);
+                if (isset($invalidIds[$orderId])) {
+                    continue;
+                }
+                $num++;
+                $amount = bcadd($amount, bcadd((string)($order->actPrice ?? '0'), '0', 2), 2);
+            }
+        }
+        return ['num' => $num, 'amount' => $amount];
+    }
+
+    /**
+     * 【用途】收集幽灵待结采购单 id(actPrice<=0、已取消仍标待结)。
+     */
+    protected static function collectInvalidPurchaseDebtOrderIds($ghsId)
+    {
+        $ghsId = intval($ghsId);
+        $ids = [];
+        if ($ghsId <= 0) {
+            return $ids;
+        }
+        $zeroList = PurchaseOrderClass::getAllByCondition([
+            'ghsId' => $ghsId,
+            'debt' => PurchaseOrderClass::DEBT_YES,
+            'actPrice<=' => 0,
+        ], null, 'id', null, true);
+        if (!empty($zeroList)) {
+            foreach ($zeroList as $order) {
+                $ids[intval($order->id ?? 0)] = 1;
+            }
+        }
+        $cancelList = PurchaseOrderClass::getAllByCondition([
+            'ghsId' => $ghsId,
+            'debt' => PurchaseOrderClass::DEBT_YES,
+            'status' => PurchaseOrderClass::PURCHASE_ORDER_STATUS_CANCEL,
+        ], null, 'id', null, true);
+        if (!empty($cancelList)) {
+            foreach ($cancelList as $order) {
+                $ids[intval($order->id ?? 0)] = 1;
+            }
+        }
+        return $ids;
+    }
+
+    /**
+     * 【用途】清理不应再计待结的采购单(已退尽、已取消仍标待结),避免「N 笔 0 元」。
+     * 【返回】被改为已结清的订单数。
+     */
+    public static function syncInvalidPurchaseDebtOrders($ghsId)
+    {
+        $ghsId = intval($ghsId);
+        if ($ghsId <= 0) {
+            return 0;
+        }
+        $invalidIds = self::collectInvalidPurchaseDebtOrderIds($ghsId);
+        if (empty($invalidIds)) {
+            return 0;
+        }
+        $fixed = 0;
+        foreach (array_keys($invalidIds) as $orderId) {
+            if ($orderId <= 0) {
+                continue;
+            }
+            $order = PurchaseOrderClass::getById($orderId, true);
+            if (empty($order) || intval($order->debt ?? 0) !== PurchaseOrderClass::DEBT_YES) {
+                continue;
+            }
+            $order->debt = PurchaseOrderClass::DEBT_NO;
+            $order->save(false, ['debt']);
+            $fixed++;
+        }
+        return $fixed;
+    }
+
     /**
      * 【用途】按采购待结订单重算 ownShop 侧 xhGhs 净 balance(ghsApp 采购供货商列表专用)。
-     * 【公式】净余额 = max(原 balance 正数部分, 0) - 采购待结合计;debtAmount 置 0;debtNum 与待结单数对齐。
-     * 【说明】不用「balance - debtAmount」旧字段合并,以待结采购单为待结真相源。
+     * 【公式】净余额 = max(当前 balance 正数部分, 0) - 采购待结合计;debtNum 与有效待结单数对齐。
+     * 【参数】$forceRecalc true 时已合并行也重算(控制台脚本重复执行);false 时仅首次合并
      */
-    public static function mergeOwnShopGhsBalanceFromPurchaseOrdersIfNeeded($ghs, $writeBalanceChange = true)
+    public static function mergeOwnShopGhsBalanceFromPurchaseOrdersIfNeeded($ghs, $writeBalanceChange = true, $forceRecalc = false)
     {
         if (empty($ghs)) {
             return $ghs;
@@ -442,7 +559,8 @@ class AccountMoneyClass
         if (!self::isGhsAppPurchaseSupplierRow($ghs)) {
             return self::mergeGhsDebtIntoBalanceIfNeeded($ghs, $writeBalanceChange);
         }
-        if (self::isMerged($ghs)) {
+        $wasMerged = self::isMerged($ghs);
+        if ($wasMerged && !$forceRecalc) {
             if (bccomp($ghs->debtAmount ?? '0', '0', 2) != 0) {
                 $ghs->debtAmount = '0.00';
                 $ghs->save(false, ['debtAmount']);
@@ -451,11 +569,11 @@ class AccountMoneyClass
         }
 
         $ghsId = intval($ghs->id ?? 0);
-        $orderDebt = self::sumPurchaseOrderDebtForGhs($ghsId);
-        $orderDebtNum = (int)PurchaseOrderClass::getCount([
-            'ghsId' => $ghsId,
-            'debt' => PurchaseOrderClass::DEBT_YES,
-        ]);
+        self::syncInvalidPurchaseDebtOrders($ghsId);
+
+        $debtStats = self::getValidPurchaseDebtStats($ghsId);
+        $orderDebt = $debtStats['amount'];
+        $orderDebtNum = $debtStats['num'];
 
         $rawBalance = bcadd((string)($ghs->balance ?? '0'), '0', 2);
         $positiveCredit = bccomp($rawBalance, '0', 2) > 0 ? $rawBalance : '0.00';
@@ -473,7 +591,8 @@ class AccountMoneyClass
         }
         $ghs->save(false, $saveAttrs);
 
-        if ($writeBalanceChange && bccomp($orderDebt, '0', 2) > 0) {
+        // 仅首次合并写说明流水,强制重跑不重复记
+        if ($writeBalanceChange && !$wasMerged && bccomp($orderDebt, '0', 2) > 0) {
             self::addPurchaseOrderMergeBalanceChange($ghs, $orderDebt, $newBalance);
         }
 

+ 33 - 20
console/controllers/GhsPurchaseBalanceMergeController.php

@@ -11,13 +11,15 @@ use yii\console\Controller;
  * ghsApp 采购供货商 xhGhs 余额合并(按采购待结订单重算)
  *
  * 【用途】仅处理 ghsApp 采购供货商行(ownPtStyle=ghs/kmGhs),不处理 hd 花店侧 xhGhs 行(ownPtStyle=hd)。
- * 【公式】净 balance = max(原正余额,0) - 待结 ghs 采购单 actPrice 合计,debtAmount 清 0。
+ * 【公式】净 balance = max(当前正余额,0) - 待结 ghs 采购单 actPrice 合计;debtNum 与有效待结单数对齐。
+ * 【可重复执行】默认 force=1,已 balanceMerged 的行也会重算,并清理 actPrice<=0/已取消仍标待结的幽灵单。
  *
  * hd 花店侧供货商请用:php yii balance-merge/run
  *
  * 用法:php yii ghs-purchase-balance-merge/run
  * 预览:php yii ghs-purchase-balance-merge/run --dryRun=1
  * 单户:php yii ghs-purchase-balance-merge/run --ghsId=123
+ * 仅未合并:php yii ghs-purchase-balance-merge/run --force=0
  */
 class GhsPurchaseBalanceMergeController extends Controller
 {
@@ -27,9 +29,12 @@ class GhsPurchaseBalanceMergeController extends Controller
     /** 指定 xhGhs.id,0=扫全表符合条件的 ghsApp 采购供货商行 */
     public $ghsId = 0;
 
+    /** 1=已合并行也重算(默认);0=仅处理未合并行 */
+    public $force = 1;
+
     public function options($actionID)
     {
-        return array_merge(parent::options($actionID), ['dryRun', 'ghsId']);
+        return array_merge(parent::options($actionID), ['dryRun', 'ghsId', 'force']);
     }
 
     /**
@@ -38,46 +43,47 @@ class GhsPurchaseBalanceMergeController extends Controller
     public function actionRun()
     {
         $dry = (int)$this->dryRun === 1;
+        $force = (int)$this->force === 1;
         $ghsId = intval($this->ghsId);
         echo $dry ? "【预览:ghsApp 采购供货商按订单重算余额】\n" : "【执行:ghsApp 采购供货商按订单重算余额】\n";
+        echo 'force=' . ($force ? '1(含已合并)' : '0(仅未合并)') . PHP_EOL;
 
         if ($ghsId > 0) {
-            $count = $this->mergeOneGhs($ghsId, $dry) ? 1 : 0;
+            $count = $this->mergeOneGhs($ghsId, $dry, $force) ? 1 : 0;
             echo "完成:处理 {$count} 条,dryRun=" . ($dry ? '1' : '0') . PHP_EOL;
             return;
         }
 
         $count = 0;
         $purchasePtStyles = $this->getGhsAppPurchasePtStyles();
-        // 本项目 conditionQuery 只支持哈希条件,不支持 Yii 的 ['and', ...] 写法
         $where = [
             'ownShopId>' => 0,
             'ownPtStyle' => ['in', $purchasePtStyles],
         ];
-        if ($this->columnExists('xhGhs', 'balanceMerged')) {
+        if (!$force && $this->columnExists('xhGhs', 'balanceMerged')) {
             $where['balanceMerged'] = 0;
-        } else {
+        } elseif (!$force) {
             $where['debtAmount>'] = 0;
         }
 
-        $list = BizGhsClass::getAllByCondition($where, null, 'id,ownShopId,ownPtStyle,balance,debtAmount,balanceMerged', null, true);
+        $list = BizGhsClass::getAllByCondition($where, null, 'id,ownShopId,ownPtStyle,balance,debtAmount,debtNum,balanceMerged', null, true);
         foreach ($list as $ghs) {
-            if ($this->mergeOneGhs(intval($ghs->id ?? 0), $dry)) {
+            if ($this->mergeOneGhs(intval($ghs->id ?? 0), $dry, $force)) {
                 $count++;
             }
         }
 
-        // ownPtStyle 未回填的历史行:再扫 ownPtStyle=0,由 isGhsAppPurchaseSupplierRow 按门店 ptStyle 判定
+        // ownPtStyle 未回填的历史行
         $legacyWhere = [
             'ownShopId>' => 0,
             'ownPtStyle' => 0,
         ];
-        if ($this->columnExists('xhGhs', 'balanceMerged')) {
+        if (!$force && $this->columnExists('xhGhs', 'balanceMerged')) {
             $legacyWhere['balanceMerged'] = 0;
         }
-        $legacyList = BizGhsClass::getAllByCondition($legacyWhere, null, 'id,ownShopId,ownPtStyle,balance,debtAmount,balanceMerged', null, true);
+        $legacyList = BizGhsClass::getAllByCondition($legacyWhere, null, 'id,ownShopId,ownPtStyle,balance,debtAmount,debtNum,balanceMerged', null, true);
         foreach ($legacyList as $ghs) {
-            if ($this->mergeOneGhs(intval($ghs->id ?? 0), $dry)) {
+            if ($this->mergeOneGhs(intval($ghs->id ?? 0), $dry, $force)) {
                 $count++;
             }
         }
@@ -99,7 +105,7 @@ class GhsPurchaseBalanceMergeController extends Controller
     /**
      * 合并单行;预览模式只打印将写入的金额。
      */
-    protected function mergeOneGhs($ghsId, $dry)
+    protected function mergeOneGhs($ghsId, $dry, $force)
     {
         if ($ghsId <= 0) {
             return false;
@@ -116,21 +122,28 @@ class GhsPurchaseBalanceMergeController extends Controller
         }
 
         $ownShopId = intval($ghs->ownShopId ?? 0);
-        $orderDebt = AccountMoneyClass::sumPurchaseOrderDebtForGhs($ghsId);
-        $rawBalance = bcadd((string)($ghs->balance ?? '0'), '0', 2);
-        $positiveCredit = bccomp($rawBalance, '0', 2) > 0 ? $rawBalance : '0.00';
+        $oldBalance = bcadd((string)($ghs->balance ?? '0'), '0', 2);
+        $oldDebtNum = intval($ghs->debtNum ?? 0);
+        $fixedOrders = AccountMoneyClass::countInvalidPurchaseDebtOrders($ghsId);
+        $debtStats = AccountMoneyClass::getValidPurchaseDebtStats($ghsId);
+        $orderDebt = $debtStats['amount'];
+        $orderDebtNum = $debtStats['num'];
+        $positiveCredit = bccomp($oldBalance, '0', 2) > 0 ? $oldBalance : '0.00';
         $newBalance = bcsub($positiveCredit, $orderDebt, 2);
 
         if ($dry) {
             echo "[ghsApp采购供货商] id={$ghsId} ownShopId={$ownShopId} ownPtStyle={$ghs->ownPtStyle}"
-                . " balance={$ghs->balance} debtAmount={$ghs->debtAmount}"
-                . " 采购待结合计={$orderDebt} => 新balance={$newBalance}" . PHP_EOL;
+                . " 原balance={$oldBalance} 原debtNum={$oldDebtNum}"
+                . " 清理幽灵单={$fixedOrders}"
+                . " 待结{$orderDebtNum}笔合计={$orderDebt} => 新balance={$newBalance} 新debtNum={$orderDebtNum}" . PHP_EOL;
             return true;
         }
 
         $locked = BizGhsClass::getLockById($ghsId);
-        AccountMoneyClass::mergeOwnShopGhsBalanceFromPurchaseOrdersIfNeeded($locked, true);
-        echo "[已合并] id={$ghsId} 采购待结={$orderDebt} 新balance={$newBalance}" . PHP_EOL;
+        $fixedOrders = AccountMoneyClass::syncInvalidPurchaseDebtOrders($ghsId);
+        AccountMoneyClass::mergeOwnShopGhsBalanceFromPurchaseOrdersIfNeeded($locked, false, $force);
+        echo "[已重算] id={$ghsId} 清理幽灵单={$fixedOrders} 待结{$orderDebtNum}笔合计={$orderDebt}"
+            . " balance {$oldBalance}=>{$newBalance} debtNum {$oldDebtNum}=>{$orderDebtNum}" . PHP_EOL;
         return true;
     }