Просмотр исходного кода

Merge branch 'after-sale' into redesign‌-260706

shish 10 часов назад
Родитель
Сommit
664ce33e3b

+ 216 - 0
biz-ghs/clear/classes/ClearClass.php

@@ -7,6 +7,7 @@ use biz\shop\classes\ShopClass;
 use bizGhs\base\classes\BaseClass;
 use bizGhs\custom\classes\CustomClass;
 use bizGhs\clear\classes\OrderCgClearClass;
+use bizGhs\ghs\classes\GhsBalanceChangeClass;
 use bizGhs\order\classes\OrderClass;
 use bizGhs\order\classes\PurchaseOrderClass;
 use bizGhs\shop\classes\MainClass;
@@ -150,6 +151,221 @@ class ClearClass extends BaseClass
         return $order;
     }
 
+    /**
+     * 【用途】作废该供货商下待付的 gys2KmGys 采购结账单(新建自动销欠前调用)
+     */
+    public static function expireAwaitPayPurchaseClears($ghsId, $shopId = 0)
+    {
+        $ghsId = intval($ghsId);
+        if ($ghsId <= 0) {
+            return;
+        }
+        $where = [
+            'ghsId' => $ghsId,
+            'clearStyle' => dict::getDict('clearStyle', 'gys2KmGys'),
+            'status' => self::STATUS_AWAIT_PAY,
+        ];
+        if (intval($shopId) > 0) {
+            $where['shopId'] = intval($shopId);
+        }
+        $list = self::getAllByCondition($where, null, '*', null, true);
+        if (empty($list)) {
+            return;
+        }
+        foreach ($list as $item) {
+            $item->status = self::STATUS_EXPIRE;
+            $item->remark = ($item->remark ?? '') . ';售后返充自动销欠前作废待付结账单';
+            $item->save(false, ['status', 'remark']);
+            OrderCgClearClass::markExpireByClearId($item->id ?? 0);
+        }
+    }
+
+    /**
+     * 【用途】采购售后返充余额后 FIFO 销欠:建结账单并减 remain,不再加余额(钱已入账)。
+     * 【为什么】对齐销售侧 afterCreditFifoClear,余额流水可挂 clearId/clearSn/clearAmount。
+     *
+     * @param object $ghs 买方供货商(已锁)
+     * @param array $plan [['cgId'=>int,'clearAmount'=>string,'orderSn'=>string], ...]
+     * @param array $meta shopId/sjId/staffId/staffName/payWay/remark
+     * @return object|null 结账单;无计划时 null
+     */
+    public static function settlePurchaseFifoAfterCredit($ghs, array $plan, array $meta = [])
+    {
+        if (empty($ghs) || empty($plan)) {
+            return null;
+        }
+        $planTotal = '0.00';
+        $pairs = [];
+        foreach ($plan as $row) {
+            $cgId = intval($row['cgId'] ?? 0);
+            $cut = bcadd((string)($row['clearAmount'] ?? '0'), '0', 2);
+            if ($cgId <= 0 || bccomp($cut, '0', 2) <= 0) {
+                continue;
+            }
+            $planTotal = bcadd($planTotal, $cut, 2);
+            $pairs[] = [
+                'orderId' => 0,
+                'orderSn' => '',
+                'cgId' => $cgId,
+                'cgSn' => (string)($row['orderSn'] ?? ''),
+                'amount' => $cut,
+            ];
+        }
+        if (empty($pairs) || bccomp($planTotal, '0', 2) <= 0) {
+            return null;
+        }
+
+        $ghsId = intval($ghs->id ?? 0);
+        $shopId = intval($meta['shopId'] ?? ($ghs->shopId ?? 0));
+        $sjId = intval($meta['sjId'] ?? 0);
+        self::expireAwaitPayPurchaseClears($ghsId, $shopId);
+
+        $customId = intval($ghs->customId ?? 0);
+        $custom = $customId > 0 ? CustomClass::getById($customId, true) : null;
+        $payWay = isset($meta['payWay']) && is_numeric($meta['payWay'])
+            ? intval($meta['payWay'])
+            : intval(dict::getDict('payWay', 'balancePay'));
+
+        $orderSn = orderSn::getPurchaseClearSn();
+        $data = [
+            'ghsId' => $ghsId,
+            'ghsName' => $ghs->name ?? '',
+            'ghsAvatar' => $ghs->avatar ?? '',
+            'ghsMobile' => $ghs->mobile ?? '',
+            'customId' => $customId,
+            'customName' => $custom->name ?? '',
+            'customAvatar' => $custom->avatar ?? '',
+            'customMobile' => $custom->mobile ?? '',
+            'sjId' => $sjId,
+            'shopId' => $shopId,
+            'clearStyle' => dict::getDict('clearStyle', 'gys2KmGys'),
+            'customShopAdminId' => intval($meta['staffId'] ?? 0),
+            'customShopId' => $shopId,
+            'customShopAdminName' => (string)($meta['staffName'] ?? ''),
+            'payWay' => $payWay,
+            'prePrice' => $planTotal,
+            'actPrice' => $planTotal,
+            'realPrice' => $planTotal,
+            'orderSn' => $orderSn,
+            'salt' => \common\components\stringUtil::charsShuffleLowerCase(10),
+            'deadline' => date('Y-m-d H:i:s', time() + 120),
+            'status' => self::STATUS_AWAIT_PAY,
+            'remark' => (string)($meta['remark'] ?? '采购售后返充余额自动销欠'),
+        ];
+        $clear = self::add($data, true);
+        OrderCgClearClass::bindRelations($clear, $pairs, OrderCgClearClass::STATUS_HAS_PAY);
+
+        // 只减采购单待结 / 更新 debtNum,不再加余额
+        self::applyPurchasePaymentSettleOnly($clear, $pairs, $payWay);
+
+        return $clear;
+    }
+
+    /**
+     * 【用途】结账单已建好后:按明细减采购 remain,足额写真实 clearId;不加 xhGhs 余额。
+     */
+    protected static function applyPurchasePaymentSettleOnly($clear, array $pairs, $payWay)
+    {
+        if (empty($clear)) {
+            util::fail('没有结账单');
+        }
+        if (intval($clear->status ?? 0) === self::STATUS_HAS_PAY) {
+            // bind 时可能已标 HAS_PAY,仍允许走减 remain
+        } else {
+            $clear->status = self::STATUS_HAS_PAY;
+        }
+        $clear->payWay = $payWay;
+        $clear->payTime = date('Y-m-d H:i:s');
+        $clear->save(false);
+
+        $clearId = intval($clear->id ?? 0);
+        $clearedCount = 0;
+        foreach ($pairs as $pair) {
+            $cgId = intval($pair['cgId'] ?? 0);
+            $cut = bcadd((string)($pair['amount'] ?? '0'), '0', 2);
+            if ($cgId <= 0 || bccomp($cut, '0', 2) <= 0) {
+                continue;
+            }
+            $purchase = PurchaseOrderClass::getById($cgId, true);
+            if (empty($purchase) || intval($purchase->debt ?? 0) !== PurchaseOrderClass::DEBT_YES) {
+                continue;
+            }
+            $remain = bcadd((string)($purchase->remainDebtPrice ?? '0'), '0', 2);
+            if (bccomp($remain, '0', 2) <= 0) {
+                $remain = bcadd((string)($purchase->actPrice ?? '0'), '0', 2);
+            }
+            $newRemain = bcsub($remain, $cut, 2);
+            if (bccomp($newRemain, '0', 2) < 0) {
+                $newRemain = '0.00';
+            }
+            $purchase->remainDebtPrice = $newRemain;
+            $saveAttrs = ['remainDebtPrice', 'debt', 'clearId'];
+            if (bccomp($newRemain, '0', 2) <= 0) {
+                $purchase->debt = PurchaseOrderClass::DEBT_NO;
+                $purchase->clearId = $clearId;
+                $clearedCount++;
+            } elseif (intval($purchase->clearId ?? 0) === 0) {
+                $purchase->clearId = 1;
+            }
+            $purchase->save(false, $saveAttrs);
+        }
+
+        if ($clearedCount > 0) {
+            $ghsId = intval($clear->ghsId ?? 0);
+            $ghs = GhsClass::getLockById($ghsId);
+            if (!empty($ghs)) {
+                $ghs->debtNum = max(0, intval($ghs->debtNum ?? 0) - $clearedCount);
+                $ghs->debt = bccomp((string)($ghs->balance ?? '0'), '0', 2) < 0
+                    ? GhsClass::DEBT_YES
+                    : (intval($ghs->debtNum) > 0 ? GhsClass::DEBT_YES : GhsClass::DEBT_NO);
+                $ghs->save(false, ['debtNum', 'debt']);
+                $customId = intval($ghs->customId ?? 0);
+                if ($customId > 0) {
+                    $custom = CustomClass::getLockById($customId);
+                    if (!empty($custom)) {
+                        $custom->debtNum = max(0, intval($custom->debtNum ?? 0) - $clearedCount);
+                        $custom->isDebt = bccomp((string)($custom->balance ?? '0'), '0', 2) < 0
+                            ? CustomClass::IS_DEBT_YES
+                            : (intval($custom->debtNum) > 0 ? CustomClass::IS_DEBT_YES : CustomClass::IS_DEBT_NO);
+                        $custom->save(false, ['debtNum', 'isDebt']);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 【用途】售后返充流水回写结账审计字段(供 ghs 余额变动展示「本单销」)
+     * 【注意】getByCondition 签名为 (condition, returnObject, order, field),
+     * 勿把「id DESC」传到第 4 参,否则会当成 SELECT 字段导致查不到 clearId。
+     */
+    public static function patchGhsBalanceChangeClearAudit($ghsId, $relateId, $clear, $clearAmount = null)
+    {
+        $ghsId = intval($ghsId);
+        $relateId = intval($relateId);
+        $clearId = intval(is_object($clear) ? ($clear->id ?? 0) : 0);
+        if ($ghsId <= 0 || $relateId <= 0 || $clearId <= 0) {
+            return;
+        }
+        // 第 3 参 order、第 4 参 field;取最新一条入账流水挂结账单
+        $row = GhsBalanceChangeClass::getByCondition([
+            'ghsId' => $ghsId,
+            'relateId' => $relateId,
+            'io' => 1,
+        ], true, 'id DESC', '*');
+        if (empty($row) || !method_exists($row, 'hasAttribute') || !$row->hasAttribute('clearId')) {
+            return;
+        }
+        $row->clearId = $clearId;
+        $row->clearSn = (string)(is_object($clear) ? ($clear->orderSn ?? '') : '');
+        $row->clearAmount = bcadd(
+            (string)($clearAmount !== null ? $clearAmount : (is_object($clear) ? ($clear->actPrice ?? 0) : 0)),
+            '0',
+            2
+        );
+        $row->save(false, ['clearId', 'clearSn', 'clearAmount']);
+    }
+
     //支付成功后的流程 ssh 2021.4.28
     public static function complete($order, $payWay)
     {

+ 74 - 5
biz-ghs/ghs/classes/GhsBalanceChangeClass.php

@@ -3,19 +3,88 @@
 namespace bizGhs\ghs\classes;
 
 use bizGhs\base\classes\BaseClass;
-use bizGhs\order\classes\PurchaseOrderClass;
-use bizGhs\shop\classes\ShopClass;
-use common\components\imgUtil;
+use bizGhs\clear\classes\ClearClass;
+use common\components\dict;
 
 class GhsBalanceChangeClass extends BaseClass
 {
 
     public static $baseFile = '\bizGhs\ghs\models\GhsBalanceChange';
 
-    /** 批发店供货商余额变动列表(xhGhsBalanceChange,按 ghsId) */
+    /**
+     * 批发店供货商余额变动列表(xhGhsBalanceChange,按 ghsId)
+     * 列表侧补全 clearId/clearSn/clearAmount,供前端展示「结账单 · 本单销」
+     */
     public static function getChangeList($where)
     {
-        return self::getList('*', $where, 'addTime DESC,id DESC');
+        $data = self::getList('*', $where, 'addTime DESC,id DESC');
+        if (!empty($data['list'])) {
+            foreach ($data['list'] as $k => $v) {
+                $data['list'][$k] = self::fillClearAuditForListRow(is_array($v) ? $v : (array)$v);
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * 【用途】列表行补全结账审计字段(对齐 CustomBalanceChangeClass)
+     * 1) 已有 clearId:缺 clearSn/金额则从结账单回填
+     * 2) 旧「采购单结账」流水:relateId=结账单 id
+     */
+    public static function fillClearAuditForListRow(array $row)
+    {
+        $clearId = intval($row['clearId'] ?? 0);
+        if ($clearId > 0) {
+            return self::normalizeClearAuditRow($row);
+        }
+        if (intval($row['io'] ?? 0) !== 1) {
+            return $row;
+        }
+        $relateId = intval($row['relateId'] ?? 0);
+        if ($relateId <= 0) {
+            return $row;
+        }
+        $event = (string)($row['event'] ?? '');
+        $capitalType = intval($row['capitalType'] ?? 0);
+        $clearCapital = intval(dict::getDict('capitalType', 'ghsCgOrderClear', 'id'));
+        // 旧手工/结账入账:event 前缀或 capitalType=采购结账
+        if (($clearCapital > 0 && $capitalType === $clearCapital)
+            || mb_strpos($event, '采购单结账') === 0) {
+            return self::attachClearMetaFromClearId($row, $relateId);
+        }
+        return $row;
+    }
+
+    protected static function normalizeClearAuditRow(array $row)
+    {
+        $clearId = intval($row['clearId'] ?? 0);
+        if ($clearId <= 0) {
+            return $row;
+        }
+        if (empty($row['clearSn']) || bccomp((string)($row['clearAmount'] ?? 0), '0', 2) <= 0) {
+            $row = self::attachClearMetaFromClearId($row, $clearId);
+        }
+        return $row;
+    }
+
+    /**
+     * 从采购结账单(ClearClass / xhClear)回填 clearSn、clearAmount
+     */
+    protected static function attachClearMetaFromClearId(array $row, $clearId)
+    {
+        $clearId = intval($clearId);
+        if ($clearId <= 0) {
+            return $row;
+        }
+        $clear = ClearClass::getById($clearId, true);
+        if (empty($clear)) {
+            return $row;
+        }
+        $row['clearId'] = $clearId;
+        $row['clearSn'] = is_object($clear) ? (string)($clear->orderSn ?? '') : (string)($clear['orderSn'] ?? '');
+        $actPrice = is_object($clear) ? ($clear->actPrice ?? 0) : ($clear['actPrice'] ?? 0);
+        $row['clearAmount'] = bcadd((string)$actPrice, '0', 2);
+        return $row;
     }
 
 }

+ 90 - 4
biz-ghs/order/classes/PurchaseOrderClass.php

@@ -19,6 +19,7 @@ use bizGhs\book\classes\BookItemCustomClass;
 use bizGhs\book\classes\BookItemGhsClass;
 use bizGhs\cg\classes\CgOrderItemClass;
 use bizGhs\cg\classes\CgOrderItemSendClass;
+use bizGhs\clear\classes\ClearClass;
 use bizGhs\clear\classes\OrderCgClearClass;
 use bizGhs\custom\classes\AccountMoneyClass;
 use bizGhs\custom\classes\CustomClass;
@@ -141,6 +142,7 @@ class PurchaseOrderClass extends BaseClass
      * @param int $ghsId 买方供货商
      * @param string|float $amount 本次入账金额
      * @return string 实际消欠合计
+     * @deprecated 采购售后返充请用 fifoClearRemainAfterCreditWithBill(会建结账单)
      */
     public static function fifoClearRemainAfterCredit($ghsId, $amount)
     {
@@ -202,6 +204,84 @@ class PurchaseOrderClass extends BaseClass
         return $used;
     }
 
+    /**
+     * 【用途】采购售后返充后 FIFO 消欠并生成结账单,回写余额流水 clear 字段。
+     * 【为什么】对齐销售侧 afterCreditFifoClear,便于余额变动展示「本单销」并可跳转结账单。
+     *
+     * @param object $ghs 买方供货商(已锁、余额已加完)
+     * @param string|float $amount 本次返充金额
+     * @param object $refund 采购售后单
+     * @param int $sjId
+     * @param int $shopId
+     * @return array{clearId:int,clearSn:string,clearAmount:string}
+     */
+    public static function fifoClearRemainAfterCreditWithBill($ghs, $amount, $refund, $sjId, $shopId)
+    {
+        $empty = ['clearId' => 0, 'clearSn' => '', 'clearAmount' => '0.00'];
+        $pool = bcadd((string)$amount, '0', 2);
+        $ghsId = intval($ghs->id ?? 0);
+        if (bccomp($pool, '0', 2) <= 0 || $ghsId <= 0) {
+            return $empty;
+        }
+        $orderList = self::getAllByCondition(
+            ['ghsId' => $ghsId, 'debt' => self::DEBT_YES],
+            'id asc',
+            '*',
+            null,
+            true
+        );
+        if (empty($orderList)) {
+            return $empty;
+        }
+        $plan = [];
+        $used = '0.00';
+        foreach ($orderList as $order) {
+            $left = bcsub($pool, $used, 2);
+            if (bccomp($left, '0', 2) <= 0) {
+                break;
+            }
+            $remain = bcadd((string)($order->remainDebtPrice ?? '0'), '0', 2);
+            if (bccomp($remain, '0', 2) <= 0) {
+                $remain = bcadd((string)($order->actPrice ?? '0'), '0', 2);
+            }
+            if (bccomp($remain, '0', 2) <= 0) {
+                continue;
+            }
+            $cut = bccomp($remain, $left, 2) <= 0 ? $remain : $left;
+            $plan[] = [
+                'cgId' => intval($order->id ?? 0),
+                'clearAmount' => $cut,
+                'orderSn' => (string)($order->orderSn ?? ''),
+            ];
+            $used = bcadd($used, $cut, 2);
+        }
+        if (empty($plan)) {
+            return $empty;
+        }
+
+        $clear = ClearClass::settlePurchaseFifoAfterCredit($ghs, $plan, [
+            'shopId' => intval($shopId),
+            'sjId' => intval($sjId),
+            'staffId' => intval($refund->shopAdminId ?? 0),
+            'staffName' => (string)($refund->shopAdminName ?? ''),
+            'payWay' => dict::getDict('payWay', 'balancePay'),
+            'remark' => '采购售后返充余额自动销欠,售后单:' . ($refund->refundSn ?? ''),
+        ]);
+        if (empty($clear)) {
+            return $empty;
+        }
+        $clearId = intval($clear->id ?? 0);
+        $clearSn = (string)($clear->orderSn ?? '');
+        $clearAmount = bcadd((string)($clear->actPrice ?? $used), '0', 2);
+        ClearClass::patchGhsBalanceChangeClearAudit($ghsId, intval($refund->id ?? 0), $clear, $clearAmount);
+
+        return [
+            'clearId' => $clearId,
+            'clearSn' => $clearSn,
+            'clearAmount' => $clearAmount,
+        ];
+    }
+
     //寄付与到付
     const YF_PAY_WAY_JF = 1;
     const YF_PAY_WAY_DF = 2;
@@ -340,9 +420,9 @@ class PurchaseOrderClass extends BaseClass
             }
         }
 
-        // 隔天等:入账后 FIFO 消挂账;当天已减本单 remain 则不再 FIFO,避免重复消欠
+        // 隔天等:入账后 FIFO 消挂账并生成结账单;当天已减本单 remain 则不再 FIFO,避免重复消欠
         if ($doFifo) {
-            self::fifoClearRemainAfterCredit(intval($ghs->id ?? 0), $amount);
+            self::fifoClearRemainAfterCreditWithBill($ghs, $amount, $refund, $sjId, $shopId);
         }
 
         return $ghs;
@@ -361,9 +441,11 @@ class PurchaseOrderClass extends BaseClass
         $ghs->save(false, ['balance', 'debtNum', 'debt']);
 
         $capitalType = dict::getDict('capitalType', 'ghsCgOrderClear', 'id');
+        $clearId = intval($clear->id ?? 0);
+        $clearSn = (string)($clear->orderSn ?? '');
         GhsBalanceChangeClass::add([
             'ghsId' => $ghs->id ?? 0,
-            'relateId' => $clear->id ?? 0,
+            'relateId' => $clearId,
             'ptStyle' => 2,
             'capitalType' => $capitalType,
             'amount' => $amount,
@@ -371,9 +453,13 @@ class PurchaseOrderClass extends BaseClass
             'staffId' => $clear->customShopAdminId ?? 0,
             'staffName' => $clear->customShopAdminName ?? '',
             'io' => 1,
-            'event' => '采购单结账 ' . ($clear->orderSn ?? ''),
+            'event' => '采购单结账 ' . $clearSn,
             'sjId' => $clear->sjId ?? 0,
             'shopId' => $clear->shopId ?? 0,
+            // 直接挂结账审计,列表无需再猜 relateId
+            'clearId' => $clearId,
+            'clearSn' => $clearSn,
+            'clearAmount' => bcadd((string)$amount, '0', 2),
         ], true);
 
         return $ghs;