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

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

shish 2 дней назад
Родитель
Сommit
dec73c8618

+ 6 - 6
app-ghs/controllers/RefundController.php

@@ -6,7 +6,7 @@ use bizGhs\notify\classes\NotifyClass;
 use bizGhs\order\classes\CheckOrderClass;
 use bizGhs\order\classes\RefundOrderItemClass;
 use bizGhs\order\services\RefundOrderService;
-use bizGhs\order\services\NextDayRefundService;
+use bizGhs\order\classes\NextDayRefundClass;
 use bizGhs\shop\classes\ShopClass;
 use bizHd\cg\classes\CgRefundClass;
 use bizHd\cg\classes\CgRefundItemClass;
@@ -37,8 +37,8 @@ class RefundController extends BaseController
         if (empty($order) || intval($order->mainId) !== intval($this->mainId)) {
             util::fail('没有找到订单');
         }
-        $check = NextDayRefundService::checkEligible($order);
-        $check['hasPayTime'] = NextDayRefundService::hasValidPayTime($order) ? 1 : 0;
+        $check = NextDayRefundClass::checkEligible($order);
+        $check['hasPayTime'] = NextDayRefundClass::hasValidPayTime($order) ? 1 : 0;
         $check['hasNextDay'] = bccomp((string)($order->nextDayTkPrice ?? '0'), '0', 2) > 0 ? 1 : 0;
         $check['nextDayTkPrice'] = $order->nextDayTkPrice ?? '0.00';
         $check['tkPrice'] = $order->tkPrice ?? '0.00';
@@ -773,7 +773,7 @@ class RefundController extends BaseController
                         'customBalance' => bcadd((string)($custom->balance ?? '0'), '0', 2),
                         'orderId' => intval($id),
                         'nextDayTkPrice' => $order->nextDayTkPrice ?? '0.00',
-                        'needFundAction' => NextDayRefundService::needFundAction($refundInfo),
+                        'needFundAction' => NextDayRefundClass::needFundAction($refundInfo),
                     ];
                 } catch (\Exception $exception) {
                     if ($transaction->isActive) {
@@ -923,7 +923,7 @@ class RefundController extends BaseController
                 $connection = Yii::$app->db;
                 $transaction = $connection->beginTransaction();
                 try {
-                    $refund = NextDayRefundService::markHasReturn($refund);
+                    $refund = NextDayRefundClass::markHasReturn($refund);
                     $transaction->commit();
                     return [
                         'id' => intval($refund->id ?? 0),
@@ -975,7 +975,7 @@ class RefundController extends BaseController
                     if ($relateOrderSn !== '') {
                         $order = OrderClass::getOneByCondition(['orderSn' => $relateOrderSn], true);
                     }
-                    $result = NextDayRefundService::applyReturnBalance($refund, $order);
+                    $result = NextDayRefundClass::applyReturnBalance($refund, $order);
                     $refund = $result['refund'];
                     $transaction->commit();
                     return [

+ 35 - 1
app-ghs/controllers/StatItemController.php

@@ -5,6 +5,7 @@ namespace ghs\controllers;
 use biz\stat\classes\StatItemClass;
 use bizGhs\order\classes\OrderClass;
 use bizGhs\order\classes\OrderItemClass;
+use bizGhs\order\classes\NextDayRefundClass;
 use bizGhs\product\classes\ProductClass;
 use bizGhs\shop\classes\ShopAdminClass;
 use bizGhs\stat\classes\StatSaleClass;
@@ -104,7 +105,11 @@ class StatItemController extends BaseController
                         }
                     }
 
-                    $num = bcsub($item['xhNum'], $item['refundNum'], 2);
+                    $num = NextDayRefundClass::payDayRemainNum(
+                        $item['xhNum'] ?? 0,
+                        $item['refundNum'] ?? 0,
+                        $item['nextRefundNum'] ?? 0
+                    );
                     $unitPrice = $item['xhUnitPrice'] ?? 0;
                     $unitCost = $item['cost'] ?? 0;
                     $unitGross = bcsub($unitPrice, $unitCost, 2);
@@ -141,6 +146,35 @@ class StatItemController extends BaseController
                 }
             }
         }
+
+        // 通过日:退货扣数量+金额;仅退款只扣金额(按原单花材占比分摊)
+        // 支付日净销量已用 nextRefundNum(payDayRemainNum),无需再加回
+        $deductMaps = [
+            NextDayRefundClass::sumItemByProduct($this->mainId, $currentStartTime, $currentEndTime),
+            NextDayRefundClass::sumMoneyOnlyAmountByProduct($this->mainId, $currentStartTime, $currentEndTime),
+        ];
+        foreach ($deductMaps as $deductMap) {
+            foreach ($deductMap as $pid => $row) {
+                if (!empty($staffId)) {
+                    $currentCgStaffId = $productInfo[$pid]['cgStaffId'] ?? 0;
+                    if ($staffId != $currentCgStaffId) {
+                        continue;
+                    }
+                }
+                if (!isset($stat[$pid])) {
+                    continue;
+                }
+                $subNum = (string)($row['num'] ?? '0');
+                $subAmt = (string)($row['amount'] ?? '0');
+                $stat[$pid]['num'] = bcsub((string)$stat[$pid]['num'], $subNum, 2);
+                $stat[$pid]['amount'] = bcsub((string)$stat[$pid]['amount'], $subAmt, 2);
+                $stat[$pid]['gross'] = bcsub((string)$stat[$pid]['gross'], $subAmt, 2);
+                $totalNum = bcsub((string)$totalNum, $subNum, 2);
+                $totalAmount = bcsub((string)$totalAmount, $subAmt, 2);
+                $totalMl = bcsub((string)$totalMl, $subAmt, 2);
+            }
+        }
+
         //增加毛利率
         foreach ($stat as $key => $val) {
             $profit = $val['gross'] ?? 0;

+ 3 - 3
app-hd/controllers/CgRefundController.php

@@ -6,7 +6,7 @@ use biz\wx\classes\WxMessageClass;
 use bizGhs\notify\classes\NotifyClass;
 use bizGhs\order\classes\OrderClass;
 use bizGhs\order\classes\RefundOrderClass;
-use bizGhs\order\services\NextDayRefundService;
+use bizGhs\order\classes\NextDayRefundClass;
 use bizHd\cg\classes\CgRefundClass;
 use bizHd\cg\classes\CgRefundItemClass;
 use bizHd\cg\services\CgRefundService;
@@ -90,8 +90,8 @@ class CgRefundController extends BaseController
         if (empty($saleOrder)) {
             util::fail('没有订单信息');
         }
-        NextDayRefundService::assertCanRefund($saleOrder);
-        $forceNextDay = NextDayRefundService::mustUseNextDay($saleOrder);
+        NextDayRefundClass::assertCanRefund($saleOrder);
+        $forceNextDay = NextDayRefundClass::mustUseNextDay($saleOrder);
         // 售后时限只用支付时间
         $payTime = $saleOrder->payTime;
         $currentTime = strtotime($payTime);

+ 274 - 8
biz-ghs/order/services/NextDayRefundService.php → biz-ghs/order/classes/NextDayRefundClass.php

@@ -1,16 +1,13 @@
 <?php
 /**
- * 售后资金/隔天售后辅助服务
- * 用途:资格校验、支付快照与资金三态(hasReturn/couldReturn/returnBalance)、隔天金额与返余额
- * 字段:sameDay 区分当天/隔天;onlinePay/payWay 与原单一致;废弃 fundType。
+ * 隔天售后业务类(biz-ghs/order/classes)
+ * 用途:资格校验、支付快照与资金三态、隔天金额/返余额、统计净销量辅助
+ * 调用方:RefundOrderClass、CgRefundClass、统计类、售后 Controller/Service。
  */
-namespace bizGhs\order\services;
+namespace bizGhs\order\classes;
 
 use bizGhs\custom\classes\CustomClass;
 use bizGhs\custom\services\GhsRechargeSettleService;
-use bizGhs\order\classes\OrderClass;
-use bizGhs\order\classes\RefundOrderClass;
-use bizGhs\order\classes\RefundOrderItemClass;
 use bizGhs\product\classes\ProductClass;
 use bizGhs\shop\classes\MainClass;
 use bizGhs\stock\classes\StockRecordClass;
@@ -20,7 +17,7 @@ use common\components\dict;
 use common\components\util;
 use Yii;
 
-class NextDayRefundService
+class NextDayRefundClass
 {
     const SAME_DAY_YES = 1;
     const SAME_DAY_NO = 0;
@@ -611,4 +608,273 @@ class NextDayRefundService
         }
         return $map;
     }
+
+    /**
+     * 支付日净销量:当天已退 = refundNum - nextRefundNum,净销 = xhNum - 当天已退。
+     * 隔天退货记在 nextRefundNum,不在支付日从销量扣掉(通过日再扣)。
+     *
+     * @param mixed $xhNum 下单数量
+     * @param mixed $refundNum 总已退
+     * @param mixed $nextRefundNum 隔天已退
+     * @return string
+     */
+    public static function payDayRemainNum($xhNum, $refundNum, $nextRefundNum = 0)
+    {
+        $sameDayRefund = bcsub((string)$refundNum, (string)($nextRefundNum ?? 0), 2);
+        if (bccomp($sameDayRefund, '0', 2) < 0) {
+            $sameDayRefund = '0';
+        }
+        return bcsub((string)$xhNum, $sameDayRefund, 2);
+    }
+
+    /**
+     * @deprecated 已改用订单行 nextRefundNum + payDayRemainNum,保留兼容旧调用
+     * 关联原单上「已发生」的隔天退货(不限通过日):用于销量按 payTime 统计时加回 refundNum 中的隔天部分,
+     * 避免与按 passTime 扣减重复;口径对齐客户业绩(支付日先全额,通过日再扣)。
+     *
+     * @param int $mainId
+     * @param string[] $relateOrderSns 原销售单号列表
+     * @return array productId => [num, amount]
+     */
+    public static function sumItemByProductForRelateOrderSns($mainId, $relateOrderSns)
+    {
+        $relateOrderSns = array_values(array_unique(array_filter($relateOrderSns)));
+        if (empty($relateOrderSns)) {
+            return [];
+        }
+        $snParams = [];
+        foreach ($relateOrderSns as $i => $sn) {
+            $snParams[':sn' . $i] = $sn;
+        }
+        $in = implode(',', array_keys($snParams));
+        $sql = "SELECT i.productId, COALESCE(SUM(i.xhNum),0) AS num, COALESCE(SUM(i.xhPrice),0) AS amount
+            FROM xhRefundItem i
+            INNER JOIN xhRefund r ON r.orderSn = i.orderSn
+            WHERE r.mainId=:mainId AND r.status=:status AND r.sameDay=:sameDay
+              AND r.refundType=:rtype
+              AND r.relateOrderSn IN ($in)
+            GROUP BY i.productId";
+        $params = array_merge([
+            ':mainId' => $mainId,
+            ':status' => RefundOrderClass::STATUS_COMPLETE,
+            ':sameDay' => self::SAME_DAY_NO,
+            ':rtype' => RefundOrderClass::REFUND_TYPE_MONEY_GOOD,
+        ], $snParams);
+        $rows = Yii::$app->db->createCommand($sql, $params)->queryAll();
+        $map = [];
+        foreach ($rows as $row) {
+            $map[$row['productId']] = [
+                'num' => bcadd($row['num'] ?? '0', '0', 2),
+                'amount' => bcadd($row['amount'] ?? '0', '0', 2),
+            ];
+        }
+        return $map;
+    }
+
+    /**
+     * 隔天「仅退款」按通过日汇总金额(无花材明细,进收支分类「销售仅退款」等)
+     */
+    public static function sumMoneyOnlyAmountByMainAndTime($mainId, $startTime, $endTime)
+    {
+        $sql = "SELECT COALESCE(SUM(refundPrice),0) AS total FROM xhRefund
+            WHERE mainId=:mainId AND status=:status AND sameDay=:sameDay
+              AND refundType=:rtype
+              AND IFNULL(NULLIF(passTime,'0000-00-00 00:00:00'), addTime) BETWEEN :start AND :end";
+        $row = Yii::$app->db->createCommand($sql, [
+            ':mainId' => $mainId,
+            ':status' => RefundOrderClass::STATUS_COMPLETE,
+            ':sameDay' => self::SAME_DAY_NO,
+            ':rtype' => RefundOrderClass::REFUND_TYPE_MONEY,
+            ':start' => $startTime,
+            ':end' => $endTime,
+        ])->queryOne();
+        return bcadd($row['total'] ?? '0', '0', 2);
+    }
+
+    /**
+     * 隔天「仅退款」按通过日分摊到原单花材(无明细时按原单行金额占比分摊 refundPrice)
+     * 花材销量页用:通过日扣金额,数量不扣。
+     *
+     * @return array productId => [num=>0, amount=>...]
+     */
+    public static function sumMoneyOnlyAmountByProduct($mainId, $startTime, $endTime)
+    {
+        $sql = "SELECT r.id, r.refundPrice, r.relateOrderSn
+            FROM xhRefund r
+            WHERE r.mainId=:mainId AND r.status=:status AND r.sameDay=:sameDay
+              AND r.refundType=:rtype
+              AND IFNULL(NULLIF(r.relateOrderSn,''), '') <> ''
+              AND IFNULL(NULLIF(r.passTime,'0000-00-00 00:00:00'), r.addTime) BETWEEN :start AND :end";
+        $rows = Yii::$app->db->createCommand($sql, [
+            ':mainId' => $mainId,
+            ':status' => RefundOrderClass::STATUS_COMPLETE,
+            ':sameDay' => self::SAME_DAY_NO,
+            ':rtype' => RefundOrderClass::REFUND_TYPE_MONEY,
+            ':start' => $startTime,
+            ':end' => $endTime,
+        ])->queryAll();
+        if (empty($rows)) {
+            return [];
+        }
+        $map = [];
+        foreach ($rows as $row) {
+            $refundPrice = bcadd((string)($row['refundPrice'] ?? '0'), '0', 2);
+            if (bccomp($refundPrice, '0', 2) <= 0) {
+                continue;
+            }
+            $relateSn = $row['relateOrderSn'] ?? '';
+            $items = \bizGhs\order\classes\OrderItemClass::getAllByCondition(
+                ['orderSn' => $relateSn],
+                null,
+                'productId,xhNum,xhUnitPrice',
+                null,
+                true
+            );
+            if (empty($items)) {
+                continue;
+            }
+            $weights = [];
+            $weightSum = '0.00';
+            foreach ($items as $item) {
+                $pid = intval($item->productId ?? 0);
+                if ($pid <= 0) {
+                    continue;
+                }
+                $line = bcmul((string)($item->xhNum ?? '0'), (string)($item->xhUnitPrice ?? '0'), 2);
+                if (bccomp($line, '0', 2) <= 0) {
+                    continue;
+                }
+                $weights[$pid] = bcadd($weights[$pid] ?? '0', $line, 2);
+                $weightSum = bcadd($weightSum, $line, 2);
+            }
+            if (bccomp($weightSum, '0', 2) <= 0) {
+                continue;
+            }
+            $allocated = '0.00';
+            $pids = array_keys($weights);
+            $last = count($pids) - 1;
+            foreach ($pids as $idx => $pid) {
+                if ($idx === $last) {
+                    $part = bcsub($refundPrice, $allocated, 2);
+                } else {
+                    $part = bcdiv(bcmul($refundPrice, $weights[$pid], 4), $weightSum, 2);
+                    $allocated = bcadd($allocated, $part, 2);
+                }
+                if (!isset($map[$pid])) {
+                    $map[$pid] = ['num' => '0.00', 'amount' => '0.00'];
+                }
+                $map[$pid]['amount'] = bcadd($map[$pid]['amount'], $part, 2);
+            }
+        }
+        return $map;
+    }
+
+    /**
+     * 按配送员汇总隔天退货数量/金额(通过日;原单 sendStaffId)
+     * @return array sendStaffId => [num, amount]
+     */
+    public static function sumItemBySendStaff($mainId, $startTime, $endTime)
+    {
+        $sql = "SELECT IFNULL(o.sendStaffId,0) AS sendStaffId,
+                COALESCE(SUM(i.xhNum),0) AS num, COALESCE(SUM(i.xhPrice),0) AS amount
+            FROM xhRefundItem i
+            INNER JOIN xhRefund r ON r.orderSn = i.orderSn
+            INNER JOIN xhGhsOrder o ON o.orderSn = r.relateOrderSn
+            WHERE r.mainId=:mainId AND r.status=:status AND r.sameDay=:sameDay
+              AND r.refundType=:rtype
+              AND IFNULL(NULLIF(r.passTime,'0000-00-00 00:00:00'), r.addTime) BETWEEN :start AND :end
+            GROUP BY IFNULL(o.sendStaffId,0)";
+        $rows = Yii::$app->db->createCommand($sql, [
+            ':mainId' => $mainId,
+            ':status' => RefundOrderClass::STATUS_COMPLETE,
+            ':sameDay' => self::SAME_DAY_NO,
+            ':rtype' => RefundOrderClass::REFUND_TYPE_MONEY_GOOD,
+            ':start' => $startTime,
+            ':end' => $endTime,
+        ])->queryAll();
+        return self::mapStaffNumAmount($rows);
+    }
+
+    /**
+     * @deprecated 已改用订单行 nextRefundNum + payDayRemainNum,保留兼容旧调用
+     * 关联原单上已发生的隔天退货,按配送员汇总(支付日统计加回用)
+     */
+    public static function sumItemBySendStaffForRelateOrderSns($mainId, $relateOrderSns)
+    {
+        $relateOrderSns = array_values(array_unique(array_filter($relateOrderSns)));
+        if (empty($relateOrderSns)) {
+            return [];
+        }
+        $snParams = [];
+        foreach ($relateOrderSns as $i => $sn) {
+            $snParams[':sn' . $i] = $sn;
+        }
+        $in = implode(',', array_keys($snParams));
+        $sql = "SELECT IFNULL(o.sendStaffId,0) AS sendStaffId,
+                COALESCE(SUM(i.xhNum),0) AS num, COALESCE(SUM(i.xhPrice),0) AS amount
+            FROM xhRefundItem i
+            INNER JOIN xhRefund r ON r.orderSn = i.orderSn
+            INNER JOIN xhGhsOrder o ON o.orderSn = r.relateOrderSn
+            WHERE r.mainId=:mainId AND r.status=:status AND r.sameDay=:sameDay
+              AND r.refundType=:rtype
+              AND r.relateOrderSn IN ($in)
+            GROUP BY IFNULL(o.sendStaffId,0)";
+        $params = array_merge([
+            ':mainId' => $mainId,
+            ':status' => RefundOrderClass::STATUS_COMPLETE,
+            ':sameDay' => self::SAME_DAY_NO,
+            ':rtype' => RefundOrderClass::REFUND_TYPE_MONEY_GOOD,
+        ], $snParams);
+        $rows = Yii::$app->db->createCommand($sql, $params)->queryAll();
+        return self::mapStaffNumAmount($rows);
+    }
+
+    /**
+     * 隔天仅退款按配送员汇总金额(通过日)
+     */
+    public static function sumMoneyOnlyAmountBySendStaff($mainId, $startTime, $endTime)
+    {
+        $sql = "SELECT IFNULL(o.sendStaffId,0) AS sendStaffId, COALESCE(SUM(r.refundPrice),0) AS amount
+            FROM xhRefund r
+            INNER JOIN xhGhsOrder o ON o.orderSn = r.relateOrderSn
+            WHERE r.mainId=:mainId AND r.status=:status AND r.sameDay=:sameDay
+              AND r.refundType=:rtype
+              AND IFNULL(NULLIF(r.relateOrderSn,''), '') <> ''
+              AND IFNULL(NULLIF(r.passTime,'0000-00-00 00:00:00'), r.addTime) BETWEEN :start AND :end
+            GROUP BY IFNULL(o.sendStaffId,0)";
+        $rows = Yii::$app->db->createCommand($sql, [
+            ':mainId' => $mainId,
+            ':status' => RefundOrderClass::STATUS_COMPLETE,
+            ':sameDay' => self::SAME_DAY_NO,
+            ':rtype' => RefundOrderClass::REFUND_TYPE_MONEY,
+            ':start' => $startTime,
+            ':end' => $endTime,
+        ])->queryAll();
+        $map = [];
+        foreach ($rows as $row) {
+            $sid = intval($row['sendStaffId'] ?? 0);
+            $map[$sid] = [
+                'num' => '0.00',
+                'amount' => bcadd($row['amount'] ?? '0', '0', 2),
+            ];
+        }
+        return $map;
+    }
+
+    /**
+     * @param array $rows
+     * @return array
+     */
+    protected static function mapStaffNumAmount($rows)
+    {
+        $map = [];
+        foreach ($rows as $row) {
+            $sid = intval($row['sendStaffId'] ?? 0);
+            $map[$sid] = [
+                'num' => bcadd($row['num'] ?? '0', '0', 2),
+                'amount' => bcadd($row['amount'] ?? '0', '0', 2),
+            ];
+        }
+        return $map;
+    }
 }

+ 10 - 5
biz-ghs/order/classes/RefundOrderClass.php

@@ -136,6 +136,8 @@ class RefundOrderClass extends BaseClass
         $refundSn = $refund->orderSn ?? '';
         $refundPrice = $refund->refundPrice ?? 0;
         $refundType = $refund->refundType ?? 1;
+        // 提前读取:退货累加 nextRefundNum 时要用(隔天售后数量单独记账)
+        $sameDay = intval($refund->sameDay ?? self::SAME_DAY_YES);
         if ($refundType == RefundOrderClass::REFUND_TYPE_MONEY_GOOD) {
             $refundItemList = RefundOrderItemClass::getAllByCondition(['orderSn' => $refundSn], null, '*', null, true);
             if (empty($refundItemList)) {
@@ -172,7 +174,11 @@ class RefundOrderClass extends BaseClass
                     if ($num > $remainNum) {
                         util::fail("{$itemName} 超过可退数量");
                     }
+                    // refundNum=总已退;隔天部分另记 nextRefundNum,支付日统计用 refundNum-nextRefundNum
                     $oneItem->refundNum = bcadd($refundNum, $num);
+                    if ($sameDay === self::SAME_DAY_NO) {
+                        $oneItem->nextRefundNum = bcadd($oneItem->nextRefundNum ?? 0, $num);
+                    }
                     $oneItem->save();
 
                     $currentCost = bcmul($cost, $num, 2);
@@ -245,9 +251,8 @@ class RefundOrderClass extends BaseClass
         $main->save();
 
         // 隔天售后:累计 nextDayTkPrice,不改 actPrice;资金按支付快照原路处理
-        $sameDay = intval($refund->sameDay ?? self::SAME_DAY_YES);
         if ($sameDay === self::SAME_DAY_NO) {
-            \bizGhs\order\services\NextDayRefundService::applyGhsAmountAndFund($refund, $order);
+            \bizGhs\order\classes\NextDayRefundClass::applyGhsAmountAndFund($refund, $order);
             return;
         }
 
@@ -396,9 +401,9 @@ class RefundOrderClass extends BaseClass
         }
 
         // 当天售后:线上/余额/挂账原路已完成 → hasReturn=1;线下留待成功页人工确认
-        if (!\bizGhs\order\services\NextDayRefundService::needsManualReturnConfirm($refund)) {
-            \bizGhs\order\services\NextDayRefundService::syncRefundFundFlags($refund, [
-                'hasReturn' => \bizGhs\order\services\NextDayRefundService::FLAG_YES,
+        if (!\bizGhs\order\classes\NextDayRefundClass::needsManualReturnConfirm($refund)) {
+            \bizGhs\order\classes\NextDayRefundClass::syncRefundFundFlags($refund, [
+                'hasReturn' => \bizGhs\order\classes\NextDayRefundClass::FLAG_YES,
             ]);
         }
     }

+ 4 - 0
biz-ghs/order/models/OrderItem.php

@@ -4,6 +4,10 @@ namespace bizGhs\order\models;
 
 use bizGhs\base\models\Base;
 
+/**
+ * 供货商销售单明细 xhGhsOrderItem
+ * nextRefundNum:隔天售后已退数量;当天已退 = refundNum - nextRefundNum
+ */
 class OrderItem extends Base
 {
 

+ 2 - 2
biz-ghs/order/services/OrderService.php

@@ -937,10 +937,10 @@ class OrderService extends BaseService
         // 未完成订单:先确认送达/完成,再继续售后(前端不再预确认)
         $order = OrderClass::ensureCompletedForRefund($order, $post);
         // 无支付时间不能售后
-        \bizGhs\order\services\NextDayRefundService::assertCanRefund($order);
+        \bizGhs\order\classes\NextDayRefundClass::assertCanRefund($order);
         $sameDay = intval($post['sameDay'] ?? \bizGhs\order\classes\RefundOrderClass::SAME_DAY_YES);
         // 非当天 / 已结账 / 挂账结清 → 强制隔天售后(sameDay=0),不再拦截为「不能售后」
-        if (\bizGhs\order\services\NextDayRefundService::mustUseNextDay($order)) {
+        if (\bizGhs\order\classes\NextDayRefundClass::mustUseNextDay($order)) {
             $sameDay = \bizGhs\order\classes\RefundOrderClass::SAME_DAY_NO;
             $post['sameDay'] = $sameDay;
         }

+ 10 - 10
biz-ghs/order/services/RefundOrderService.php

@@ -16,7 +16,7 @@ use bizGhs\order\classes\OrderClass;
 use bizGhs\order\classes\OrderItemClass;
 use bizGhs\order\classes\RefundOrderClass;
 use bizGhs\order\classes\RefundOrderItemClass;
-use bizGhs\order\services\NextDayRefundService;
+use bizGhs\order\classes\NextDayRefundClass;
 use bizGhs\shop\classes\MainClass;
 use bizGhs\shop\classes\ShopMoneyClass;
 use bizGhs\stock\classes\StockRecordClass;
@@ -56,10 +56,10 @@ class RefundOrderService extends BaseService
             util::fail("已取消订单,不能申请售后");
         }
         // 无支付时间不能售后;仅用 payTime 判是否转隔天
-        NextDayRefundService::assertCanRefund($order);
+        NextDayRefundClass::assertCanRefund($order);
         // 审核时若已非当天或已结清:自动转为隔天售后(不再要求驳回)
         $sameDay = intval($refund->sameDay ?? RefundOrderClass::SAME_DAY_YES);
-        if ($sameDay === RefundOrderClass::SAME_DAY_YES && NextDayRefundService::mustUseNextDay($order)) {
+        if ($sameDay === RefundOrderClass::SAME_DAY_YES && NextDayRefundClass::mustUseNextDay($order)) {
             self::convertRefundToNextDay($refund, $order);
             $sameDay = RefundOrderClass::SAME_DAY_NO;
         }
@@ -138,7 +138,7 @@ class RefundOrderService extends BaseService
      */
     public static function convertRefundToNextDay($refund, $order)
     {
-        $snap = NextDayRefundService::buildRefundPaySnapshot(
+        $snap = NextDayRefundClass::buildRefundPaySnapshot(
             $order,
             true,
             [
@@ -277,19 +277,19 @@ class RefundOrderService extends BaseService
         $data['orderTime'] = $order->payTime??'';
 
         // 无支付时间不能售后
-        NextDayRefundService::assertCanRefund($order);
+        NextDayRefundClass::assertCanRefund($order);
         // sameDay:非当天/已结清强制隔天;支付快照与资金三态写入售后单
         $sameDay = intval($post['sameDay'] ?? RefundOrderClass::SAME_DAY_YES);
-        if (NextDayRefundService::mustUseNextDay($order)) {
+        if (NextDayRefundClass::mustUseNextDay($order)) {
             $sameDay = RefundOrderClass::SAME_DAY_NO;
         }
         if ($sameDay === RefundOrderClass::SAME_DAY_NO) {
-            $check = NextDayRefundService::checkEligible($order);
+            $check = NextDayRefundClass::checkEligible($order);
             if (empty($check['ok'])) {
                 util::fail($check['reason'] ?? '当前订单不可隔天售后');
             }
         }
-        $snap = NextDayRefundService::buildRefundPaySnapshot(
+        $snap = NextDayRefundClass::buildRefundPaySnapshot(
             $order,
             true,
             [
@@ -344,7 +344,7 @@ class RefundOrderService extends BaseService
         }
 
         // 无原单:线下原路渠道 = 确认页「退款方式」;couldReturn=1 供成功页返充
-        $snap = NextDayRefundService::buildRefundPaySnapshot(
+        $snap = NextDayRefundClass::buildRefundPaySnapshot(
             null,
             false,
             [
@@ -450,7 +450,7 @@ class RefundOrderService extends BaseService
             }
         }
 
-        NextDayRefundService::passFreeRefund($refund);
+        NextDayRefundClass::passFreeRefund($refund);
         $refund = RefundOrderClass::getById($refund->id, true);
         $custom = CustomClass::getById($customId, true);
 

+ 2 - 2
biz-ghs/stat/classes/StatKdClass.php

@@ -3,7 +3,7 @@
 namespace bizGhs\stat\classes;
 
 use biz\shop\classes\ShopAdminClass;
-use bizGhs\order\services\NextDayRefundService;
+use bizGhs\order\classes\NextDayRefundClass;
 use bizGhs\order\classes\OrderClass;
 use bizGhs\order\classes\OrderItemClass;
 use bizGhs\order\classes\StockOutOrderClass;
@@ -423,7 +423,7 @@ class StatKdClass extends BaseClass
      */
     protected static function deductNextDayFromChannelIncome(&$incomeList, $mainId, $startTime, $endTime)
     {
-        $rows = NextDayRefundService::listForChannelIncomeDeduct($mainId, $startTime, $endTime);
+        $rows = NextDayRefundClass::listForChannelIncomeDeduct($mainId, $startTime, $endTime);
         if (empty($rows)) {
             return;
         }

+ 190 - 8
biz-ghs/stat/classes/StatSaleClass.php

@@ -13,7 +13,7 @@ use bizGhs\order\classes\OrderItemClass;
 use bizGhs\order\classes\PurchaseOrderClass;
 use bizGhs\order\classes\PurchaseOrderItemClass;
 use bizGhs\order\classes\RefundOrderClass;
-use bizGhs\order\services\NextDayRefundService;
+use bizGhs\order\classes\NextDayRefundClass;
 use bizGhs\order\classes\StockWastageOrderClass;
 use bizGhs\product\classes\ProductClass;
 use bizGhs\staff\classes\SalaryClass;
@@ -202,7 +202,7 @@ class StatSaleClass extends BaseClass
                 }
             }
             // 隔天售后扣客户采购金额(笔数不减)
-            $fwdMap = NextDayRefundService::sumAmountGroupByCustom($mainId, $currentStartTime, $currentEndTime);
+            $fwdMap = NextDayRefundClass::sumAmountGroupByCustom($mainId, $currentStartTime, $currentEndTime);
             foreach ($fwdMap as $cid => $amt) {
                 if (!isset($arr[$cid])) {
                     continue;
@@ -332,7 +332,7 @@ class StatSaleClass extends BaseClass
         // 隔天售后扣减批发收入(笔数不变)
         $pfIncome = bcsub(
             (string)$pfIncome,
-            NextDayRefundService::sumAmountByMainAndTime($mainId, $currentStartTime, $currentEndTime),
+            NextDayRefundClass::sumAmountByMainAndTime($mainId, $currentStartTime, $currentEndTime),
             2
         );
 
@@ -585,7 +585,12 @@ class StatSaleClass extends BaseClass
                         $name = $orderItem->name ?? '';
                         $productId = $orderItem->productId ?? 0;
                         $num = $orderItem->xhNum ?? 0;
-                        $remainNum = bcsub($num, $refundNum);
+                        // 支付日净销量:当天已退=refundNum-nextRefundNum
+                        $remainNum = NextDayRefundClass::payDayRemainNum(
+                            $num,
+                            $refundNum,
+                            $orderItem->nextRefundNum ?? 0
+                        );
 
                         $xhUnitPrice = $orderItem->xhUnitPrice ?? 0;
                         $amount = bcmul($remainNum, $xhUnitPrice, 2);
@@ -628,6 +633,10 @@ class StatSaleClass extends BaseClass
                                 if ($refund->refundType == 1) {
                                     //退货退款前面已经考虑,这里不再处理 OK
                                 } else {
+                                    // 隔天仅退款记在通过日,这里跳过避免与下方 sumMoneyOnly 重复
+                                    if (intval($refund->sameDay ?? RefundOrderClass::SAME_DAY_YES) === RefundOrderClass::SAME_DAY_NO) {
+                                        continue;
+                                    }
                                     $onlyRefund = bcadd($onlyRefund, $refund->refundPrice, 2);
                                 }
                             }
@@ -638,6 +647,20 @@ class StatSaleClass extends BaseClass
             }
         }
 
+        // 隔天售后:支付日已用 nextRefundNum;通过日再扣数量/金额;仅退款并入销售仅退款
+        $onlyRefund = bcadd(
+            (string)$onlyRefund,
+            self::applyNextDayAdjustToProductIncomeRows(
+                $arr,
+                $tbSaleIncome,
+                $productList,
+                $mainId,
+                $currentStartTime,
+                $currentEndTime
+            ),
+            2
+        );
+
         //零售开单
         $lsIncome = 0;
         $hdWhere = ['mainId' => $mainId, 'status' => ['in', [HdOrderClass::ORDER_STATUS_UN_SEND, HdOrderClass::ORDER_STATUS_SENDING, HdOrderClass::ORDER_STATUS_COMPLETE]]];
@@ -911,7 +934,11 @@ class StatSaleClass extends BaseClass
                         $name = $orderItem->name ?? '';
                         $productId = $orderItem->productId ?? 0;
                         $num = $orderItem->xhNum ?? 0;
-                        $remainNum = bcsub($num, $refundNum);
+                        $remainNum = NextDayRefundClass::payDayRemainNum(
+                            $num,
+                            $refundNum,
+                            $orderItem->nextRefundNum ?? 0
+                        );
                         $classId = $orderItem->classId ?? 0;
                         $className = $classList[$classId]['name'] ?? '已删除';
                         $xhUnitPrice = $orderItem->xhUnitPrice ?? 0;
@@ -952,6 +979,10 @@ class StatSaleClass extends BaseClass
                                 if ($refund->refundType == 1) {
                                     //退货退款前面已经考虑,这里不再处理 OK
                                 } else {
+                                    // 隔天仅退款记在通过日,这里跳过避免与下方 sumMoneyOnly 重复
+                                    if (intval($refund->sameDay ?? RefundOrderClass::SAME_DAY_YES) === RefundOrderClass::SAME_DAY_NO) {
+                                        continue;
+                                    }
                                     $onlyRefund = bcadd($onlyRefund, $refund->refundPrice, 2);
                                 }
                             }
@@ -962,6 +993,40 @@ class StatSaleClass extends BaseClass
             }
         }
 
+        // 隔天售后:支付日已用 nextRefundNum;通过日再按售后单扣分类收入;仅退款进「销售仅退款」
+        $productClassMap = ProductClass::getAllByCondition(['mainId' => $mainId], null, 'id,classId', 'id');
+        $applyClassDelta = function ($pidMap, $sign) use (&$arr, &$classList, $productClassMap) {
+            foreach ($pidMap as $pid => $row) {
+                $classId = $productClassMap[$pid]['classId'] ?? 0;
+                $deltaNum = bcmul((string)($row['num'] ?? '0'), (string)$sign, 2);
+                $deltaAmt = bcmul((string)($row['amount'] ?? '0'), (string)$sign, 2);
+                if (!isset($arr[$classId])) {
+                    if (bccomp($deltaAmt, '0', 2) == 0 && bccomp($deltaNum, '0', 2) == 0) {
+                        continue;
+                    }
+                    $arr[$classId] = [
+                        'className' => $classList[$classId]['name'] ?? '已删除',
+                        'classId' => $classId,
+                        'expend' => 0,
+                        'income' => '0.00',
+                        'saleNum' => '0.00',
+                        'cgNum' => 0,
+                    ];
+                }
+                $arr[$classId]['income'] = bcadd((string)($arr[$classId]['income'] ?? 0), $deltaAmt, 2);
+                $arr[$classId]['saleNum'] = bcadd((string)($arr[$classId]['saleNum'] ?? 0), $deltaNum, 2);
+            }
+        };
+        $applyClassDelta(
+            NextDayRefundClass::sumItemByProduct($mainId, $currentStartTime, $currentEndTime),
+            -1
+        );
+        $onlyRefund = bcadd(
+            (string)$onlyRefund,
+            NextDayRefundClass::sumMoneyOnlyAmountByMainAndTime($mainId, $currentStartTime, $currentEndTime),
+            2
+        );
+
         //零售开单
         $lsIncome = 0;
         $hdWhere = ['mainId' => $mainId, 'status' => ['in', [HdOrderClass::ORDER_STATUS_UN_SEND, HdOrderClass::ORDER_STATUS_SENDING, HdOrderClass::ORDER_STATUS_COMPLETE]]];
@@ -1397,14 +1462,23 @@ class StatSaleClass extends BaseClass
                 $orderItemList = OrderItemClass::getAllByCondition(['orderSn' => $orderSn], null, '*', null, true);
                 if (!empty($orderItemList)) {
                     foreach ($orderItemList as $orderItem) {
-                        $refundNum = $orderItem->refundNum ?? 0;
-                        $num = $orderItem->xhNum ?? 0;
-                        $remainNum = bcsub($num, $refundNum);
+                        $remainNum = NextDayRefundClass::payDayRemainNum(
+                            $orderItem->xhNum ?? 0,
+                            $orderItem->refundNum ?? 0,
+                            $orderItem->nextRefundNum ?? 0
+                        );
                         $arr[$sendStaffId]['itemNum'] = bcadd($arr[$sendStaffId]['itemNum'], $remainNum);
                     }
                 }
             }
 
+            // 隔天售后:数量已用 nextRefundNum;通过日再扣数量/金额
+            self::applyNextDayAdjustToSendStaffRows(
+                $arr,
+                $mainId,
+                $currentStartTime,
+                $currentEndTime
+            );
         }
         if (!empty($arr)) {
             $arr = array_values($arr);
@@ -1496,10 +1570,118 @@ class StatSaleClass extends BaseClass
                     ];
                 }
             }
+            // 隔天售后按客户扣金额后归到片区(笔数不减;无当日开单的片区不新建行)
+            $fwdMap = NextDayRefundClass::sumAmountGroupByCustom($mainId, $currentStartTime, $currentEndTime);
+            foreach ($fwdMap as $cid => $amt) {
+                $distId = $customList[$cid]['distId'] ?? 0;
+                if (!isset($arr[$distId])) {
+                    continue;
+                }
+                $arr[$distId]['amount'] = bcsub((string)$arr[$distId]['amount'], (string)$amt, 2);
+                $arr[$distId]['profit'] = bcsub((string)$arr[$distId]['profit'], (string)$amt, 2);
+                $totalAmount = bcsub((string)$totalAmount, (string)$amt, 2);
+                $totalProfit = bcsub((string)$totalProfit, (string)$amt, 2);
+            }
             $arr = arrayUtil::arraySort($arr, 'profit');
         }
         $totalAmount = floatval($totalAmount);
         return ['list' => $arr, 'totalAmount' => $totalAmount, 'totalNum' => $totalNum, 'totalCost' => $totalCost, 'totalProfit' => $totalProfit];
     }
 
+    /**
+     * 花材收支明细:支付日已用 nextRefundNum;此处按通过日扣退货数量/金额,并返回隔天仅退款合计。
+     *
+     * @param array $arr 产品收支行(引用修改 income/inNum)
+     * @param array $tbSaleIncome 归属成本花材销售明细(引用同步加减)
+     * @param array $productList 花材字典
+     * @param int $mainId
+     * @param string $startTime
+     * @param string $endTime
+     * @return string 通过日隔天「仅退款」合计,需加到 onlyRefund
+     */
+    private static function applyNextDayAdjustToProductIncomeRows(
+        &$arr,
+        &$tbSaleIncome,
+        $productList,
+        $mainId,
+        $startTime,
+        $endTime
+    ) {
+        $applyDelta = function ($pidMap, $sign) use (&$arr, &$tbSaleIncome, $productList) {
+            foreach ($pidMap as $pid => $row) {
+                $deltaNum = bcmul((string)($row['num'] ?? '0'), (string)$sign, 2);
+                $deltaAmt = bcmul((string)($row['amount'] ?? '0'), (string)$sign, 2);
+                if (bccomp($deltaAmt, '0', 2) == 0 && bccomp($deltaNum, '0', 2) == 0) {
+                    continue;
+                }
+                // 通过日扣减时:当日无该花材开单则不新建行(与花材销量一致)
+                if ($sign < 0 && !isset($arr[$pid])) {
+                    continue;
+                }
+                if (!isset($arr[$pid])) {
+                    $arr[$pid] = [
+                        'name' => $productList[$pid]['name'] ?? '已删除',
+                        'id' => $pid,
+                        'expend' => 0,
+                        'outNum' => 0,
+                        'inNum' => '0.00',
+                        'income' => '0.00',
+                    ];
+                }
+                $arr[$pid]['income'] = bcadd((string)($arr[$pid]['income'] ?? 0), $deltaAmt, 2);
+                $arr[$pid]['inNum'] = bcadd((string)($arr[$pid]['inNum'] ?? 0), $deltaNum, 2);
+                if (isset($tbSaleIncome[$pid])) {
+                    $tbSaleIncome[$pid]['price'] = bcadd((string)($tbSaleIncome[$pid]['price'] ?? 0), $deltaAmt, 2);
+                    $tbSaleIncome[$pid]['num'] = bcadd((string)($tbSaleIncome[$pid]['num'] ?? 0), $deltaNum, 2);
+                }
+            }
+        };
+
+        // 通过日:扣退货数量+金额
+        $applyDelta(
+            NextDayRefundClass::sumItemByProduct($mainId, $startTime, $endTime),
+            -1
+        );
+
+        return NextDayRefundClass::sumMoneyOnlyAmountByMainAndTime($mainId, $startTime, $endTime);
+    }
+
+    /**
+     * 配送业绩:支付日数量已用 nextRefundNum;通过日再扣数量/金额(仅退款只减金额)。
+     * 仅调整当日已有配送员行,不因纯售后新建行。
+     *
+     * @param array $arr sendStaffId => 业绩行(引用修改 itemNum/amount)
+     * @param int $mainId
+     * @param string $startTime
+     * @param string $endTime
+     */
+    private static function applyNextDayAdjustToSendStaffRows(
+        &$arr,
+        $mainId,
+        $startTime,
+        $endTime
+    ) {
+        $applyDelta = function ($staffMap, $sign) use (&$arr) {
+            foreach ($staffMap as $sid => $row) {
+                if (!isset($arr[$sid])) {
+                    continue;
+                }
+                $deltaNum = bcmul((string)($row['num'] ?? '0'), (string)$sign, 2);
+                $deltaAmt = bcmul((string)($row['amount'] ?? '0'), (string)$sign, 2);
+                $arr[$sid]['itemNum'] = bcadd((string)($arr[$sid]['itemNum'] ?? 0), $deltaNum, 2);
+                $arr[$sid]['amount'] = bcadd((string)($arr[$sid]['amount'] ?? 0), $deltaAmt, 2);
+            }
+        };
+
+        $applyDelta(
+            NextDayRefundClass::sumItemBySendStaff($mainId, $startTime, $endTime),
+            -1
+        );
+        // 仅退款只减金额
+        $applyDelta(
+            NextDayRefundClass::sumMoneyOnlyAmountBySendStaff($mainId, $startTime, $endTime),
+            -1
+        );
+    }
+
 }

+ 7 - 2
biz-hd/cg/classes/CgRefundClass.php

@@ -157,6 +157,8 @@ class CgRefundClass extends BaseClass
         }
         $refundType = $cgRefund->refundType ?? 1;
         $refundSn = $cgRefund->orderSn ?? '';
+        // 提前读取:退货累加 nextRefundNum 时要用
+        $sameDay = intval($cgRefund->sameDay ?? self::SAME_DAY_YES);
         $cgRefund->status = 1;
         $cgRefund->save();
 
@@ -256,7 +258,11 @@ class CgRefundClass extends BaseClass
                     if ($num > $remainNum) {
                         util::fail("{$itemName} 超过可退数量");
                     }
+                    // refundNum=总已退;隔天部分另记 nextRefundNum
                     $oneItem->refundNum = bcadd($refundNum, $num);
+                    if ($sameDay === self::SAME_DAY_NO) {
+                        $oneItem->nextRefundNum = bcadd($oneItem->nextRefundNum ?? 0, $num);
+                    }
                     $oneItem->save();
 
                     if ($refundSingle->xhUnitType == 1) {
@@ -297,9 +303,8 @@ class CgRefundClass extends BaseClass
         }
 
         // 隔天售后:累计 nextDayTkPrice,不改 actPrice;原路退在此发起
-        $sameDay = intval($cgRefund->sameDay ?? self::SAME_DAY_YES);
         if ($sameDay === self::SAME_DAY_NO) {
-            \bizGhs\order\services\NextDayRefundService::applyCgAmountAndFund($cgRefund, $cg);
+            \bizGhs\order\classes\NextDayRefundClass::applyCgAmountAndFund($cgRefund, $cg);
             return;
         }
 

+ 4 - 4
biz-hd/cg/services/CgRefundService.php

@@ -9,7 +9,7 @@ use biz\shop\classes\ShopClass;
 use biz\wx\classes\WxMessageClass;
 use bizGhs\order\classes\OrderClass;
 use bizGhs\order\classes\RefundOrderClass;
-use bizGhs\order\services\NextDayRefundService;
+use bizGhs\order\classes\NextDayRefundClass;
 use bizGhs\order\services\RefundOrderService;
 use bizGhs\product\classes\ProductClass;
 use bizGhs\stock\classes\StockRecordClass;
@@ -54,14 +54,14 @@ class CgRefundService extends BaseService
             util::fail('没有订单信息');
         }
         // 无支付时间不能售后;仅用 payTime 判当天/隔天
-        NextDayRefundService::assertCanRefund($order);
+        NextDayRefundClass::assertCanRefund($order);
 
         // 非当天 / 已结账 / 挂账结清 → 隔天;支付快照与订单一致
         $sameDay = CgRefundClass::SAME_DAY_YES;
-        if (NextDayRefundService::mustUseNextDay($order)) {
+        if (NextDayRefundClass::mustUseNextDay($order)) {
             $sameDay = CgRefundClass::SAME_DAY_NO;
         }
-        $snap = NextDayRefundService::buildRefundPaySnapshot(
+        $snap = NextDayRefundClass::buildRefundPaySnapshot(
             $order,
             true,
             [

+ 4 - 0
biz-hd/purchase/models/PurchaseItem.php

@@ -4,6 +4,10 @@ namespace bizHd\purchase\models;
 
 use bizHd\base\models\Base;
 
+/**
+ * 花店采购单明细 xhCgItem
+ * nextRefundNum:隔天售后已退数量;当天已退 = refundNum - nextRefundNum
+ */
 class PurchaseItem extends Base
 {