payTime ?? ''; return !empty($payTime) && $payTime !== '0000-00-00 00:00:00'; } /** * 售后入口校验:无支付时间直接拦截 */ public static function assertCanRefund($order) { if (empty($order)) { util::fail('没有原订单'); } if (!self::hasValidPayTime($order)) { util::fail('订单无支付时间,不能售后'); } } /** * 原单是否可走隔天售后(即原「冲销」路径): * - 非当天支付(仅看 payTime);或 * - 挂账已结清;或 * - 已结账单(当天已结账也按冲销处理,不走普通售后) * @return array{ok:bool,reason:string,sameDay:int} */ public static function checkEligible($order) { if (empty($order)) { return ['ok' => false, 'reason' => '没有原订单', 'sameDay' => self::SAME_DAY_YES, 'hasPayTime' => 0]; } // 只用支付时间;无支付时间本单不能售后(由 assertCanRefund 拦截) if (!self::hasValidPayTime($order)) { return [ 'ok' => false, 'reason' => '订单无支付时间,不能售后', 'sameDay' => self::SAME_DAY_YES, 'hasPayTime' => 0, ]; } $payTime = $order->payTime; $isToday = date('Y-m-d', strtotime($payTime)) === date('Y-m-d'); $debtPrice = bcadd((string)($order->debtPrice ?? '0'), '0', 2); $remainDebt = bcadd((string)($order->remainDebtPrice ?? '0'), '0', 2); $debtCleared = bccomp($debtPrice, '0', 2) > 0 && bccomp($remainDebt, '0', 2) === 0; // 已结账:当天也强制走冲销(sameDay=0),与隔天同一套资金/金额逻辑 $orderCleared = !empty($order->clearId); if ($isToday && !$debtCleared && !$orderCleared) { return [ 'ok' => false, 'reason' => '当天未结账订单请走普通售后;已结账/挂账结清/隔天可售后', 'sameDay' => self::SAME_DAY_YES, 'hasPayTime' => 1, ]; } return [ 'ok' => true, 'reason' => '', 'sameDay' => self::SAME_DAY_NO, 'orderCleared' => $orderCleared ? 1 : 0, 'hasPayTime' => 1, ]; } /** * 创建/审核时是否必须按隔天售后(sameDay=0): * 与 checkEligible 对齐——非当天 / 已结账单 / 挂账已结清,均不可再走当天售后。 * 无支付时间返回 false(须先走 assertCanRefund)。 */ public static function mustUseNextDay($order) { if (!self::hasValidPayTime($order)) { return false; } $check = self::checkEligible($order); return !empty($check['ok']); } /** * 是否微信/支付宝线上支付(看订单或售后快照) */ public static function isOnlinePayOrder($orderOrRefund) { if (empty($orderOrRefund)) { return false; } $payWay = intval($orderOrRefund->payWay ?? 0); $onlinePay = intval($orderOrRefund->onlinePay ?? dict::getDict('onlinePay', 'not')); $wxPay = dict::getDict('payWay', 'wxPay'); $aliPay = dict::getDict('payWay', 'alipay'); return $onlinePay == dict::getDict('onlinePay', 'yes') && in_array($payWay, [$wxPay, $aliPay], true); } /** * 余额付或挂账:原路就是动余额/欠款,禁止再「返充」 */ public static function isBalanceOrDebtPayWay($payWay) { $payWay = intval($payWay); return $payWay === intval(dict::getDict('payWay', 'balancePay')) || $payWay === intval(dict::getDict('payWay', 'debtPay')); } /** * 是否需人工确认原路(现金/银行卡/线下微信支付宝等) * 线上通道成功、余额/挂账账务完成可自动 hasReturn;线下钱是否退还要人确认。 */ public static function needsManualReturnConfirm($orderOrRefund) { if (empty($orderOrRefund)) { return false; } if (self::isOnlinePayOrder($orderOrRefund)) { return false; } $payWay = intval($orderOrRefund->payWay ?? 0); if (self::isBalanceOrDebtPayWay($payWay)) { return false; } return true; } /** * 是否待选手选资金落地(成功页二选一) */ public static function needFundAction($refund) { if (empty($refund)) { return 0; } if (intval($refund->hasReturn ?? 0) === self::FLAG_YES) { return 0; } if (intval($refund->returnBalance ?? 0) === self::FLAG_YES) { return 0; } if (intval($refund->couldReturn ?? 0) !== self::FLAG_YES) { return 0; } return 1; } /** * 生成售后资金快照(落 xhRefund/xhCgRefund) * - 有原单:onlinePay/payWay 与订单一致;余额/挂账 couldReturn=0,其它=1 * - 无原单:线下渠道由选手选 payWay;couldReturn=1(成功页可改走余额) * * @param object|null $order 原销售单;无原单传 null * @param bool $hasRelateOrder 是否有关联原单 * @param array $options payWay(无原单线下渠道) * @return array{onlinePay:int,payWay:int,hasReturn:int,couldReturn:int,returnBalance:int} */ public static function buildRefundPaySnapshot($order = null, $hasRelateOrder = true, $options = []) { $onlineNot = intval(dict::getDict('onlinePay', 'not')); // 兼容旧调用名:内部仍可被 buildRefundFundSnapshot 转发 if (!$hasRelateOrder || empty($order)) { $payWay = self::normalizeOfflinePayWay($options['payWay'] ?? null, null); return [ 'onlinePay' => $onlineNot, 'payWay' => $payWay, 'hasReturn' => self::FLAG_NO, 'couldReturn' => self::FLAG_YES, 'returnBalance' => self::FLAG_NO, ]; } $payWay = intval($order->payWay ?? 0); $onlinePay = intval($order->onlinePay ?? $onlineNot); $couldReturn = self::isBalanceOrDebtPayWay($payWay) ? self::FLAG_NO : self::FLAG_YES; return [ 'onlinePay' => $onlinePay, 'payWay' => $payWay, 'hasReturn' => self::FLAG_NO, 'couldReturn' => $couldReturn, 'returnBalance' => self::FLAG_NO, ]; } /** * @deprecated 已废弃 fundType;保留转发以免旧调用报错 */ public static function buildRefundFundSnapshot($fundTypeInput, $order = null, $hasRelateOrder = true, $options = []) { return self::buildRefundPaySnapshot($order, $hasRelateOrder, $options); } /** * 线下转账渠道:允许 0微信/1支付宝/4现金/5银行卡;非法则回落原单或现金 */ protected static function normalizeOfflinePayWay($payWay, $order = null) { $allowed = [ intval(dict::getDict('payWay', 'wxPay')), intval(dict::getDict('payWay', 'alipay')), intval(dict::getDict('payWay', 'cash')), intval(dict::getDict('payWay', 'bankCard')), ]; if ($payWay !== null && $payWay !== '' && in_array(intval($payWay), $allowed, true)) { return intval($payWay); } if (!empty($order)) { $orderPayWay = intval($order->payWay ?? -1); if (in_array($orderPayWay, $allowed, true)) { return $orderPayWay; } } return intval(dict::getDict('payWay', 'cash')); } /** * 售后单与采购镜像同步资金三态 */ public static function syncRefundFundFlags($refund, $fields = []) { if (empty($refund) || empty($fields)) { return; } foreach ($fields as $k => $v) { $refund->$k = $v; } $refund->save(false, array_keys($fields)); $cgRefundId = intval($refund->cgRefundId ?? 0); if ($cgRefundId <= 0) { return; } $cgRefund = CgRefundClass::getById($cgRefundId, true); if (empty($cgRefund)) { return; } foreach ($fields as $k => $v) { $cgRefund->$k = $v; } $cgRefund->save(false, array_keys($fields)); } /** * 标记已原路退回(现金/银行卡等线下人工确认) */ public static function markHasReturn($refund) { if (empty($refund)) { util::fail('退款单不存在'); } if (intval($refund->status) !== RefundOrderClass::STATUS_COMPLETE) { util::fail('售后未通过,不能确认原路退回'); } if (intval($refund->returnBalance ?? 0) === self::FLAG_YES) { util::fail('已返充余额,不能再标记原路退回'); } if (intval($refund->hasReturn ?? 0) === self::FLAG_YES) { return $refund; } self::syncRefundFundFlags($refund, ['hasReturn' => self::FLAG_YES]); return RefundOrderClass::getById($refund->id, true); } /** * 不想线下原路时:返充到客户余额(须 couldReturn=1 且尚未原路) * @return array{refund:object,customBalance:string} */ public static function applyReturnBalance($refund, $order = null) { if (empty($refund)) { util::fail('退款单不存在'); } if (intval($refund->status) !== RefundOrderClass::STATUS_COMPLETE) { util::fail('售后未通过,不能返充余额'); } if (intval($refund->hasReturn ?? 0) === self::FLAG_YES) { util::fail('已原路退回,不能再返充余额'); } if (intval($refund->returnBalance ?? 0) === self::FLAG_YES) { util::fail('已返充到余额'); } if (intval($refund->couldReturn ?? 0) !== self::FLAG_YES) { util::fail('该单不允许返充到余额'); } $customId = intval($refund->customId ?? 0); $custom = CustomClass::getLockById($customId); if (empty($custom)) { util::fail('没有找到客户'); } $pair = GhsRechargeSettleService::lockAccountPair($custom); CustomClass::nextDayRefundReturnBalance($pair['custom'], $pair['ghs'], $refund, $order); self::syncRefundFundFlags($refund, ['returnBalance' => self::FLAG_YES]); $custom = CustomClass::getById($customId, true); return [ 'refund' => RefundOrderClass::getById($refund->id, true), 'customBalance' => bcadd((string)($custom->balance ?? '0'), '0', 2), ]; } /** * GHS 侧隔天通过:累计 nextDayTkPrice(不改 actPrice);扣 buyAmount; * 余额/挂账原路加回客户余额;线上付扣门店可提现余额(与当天售后一致,必须在 ghs 端做)。 * @return string 当前客户余额 */ public static function applyGhsAmountAndFund($refund, $order) { $refundPrice = bcadd((string)($refund->refundPrice ?? '0'), '0', 2); $order->nextDayTkPrice = bcadd((string)($order->nextDayTkPrice ?? '0'), $refundPrice, 2); $order->refund = OrderClass::REFUND_YES; $order->save(false, ['nextDayTkPrice', 'refund']); $customId = intval($order->customId ?? 0); $custom = CustomClass::getLockById($customId); if (empty($custom)) { util::fail('没有找到客户'); } // 累计消费与是否当天无关:退了就要从 buyAmount 扣掉 $buyAmount = bcsub((string)($custom->buyAmount ?? '0'), $refundPrice, 2); if (bccomp($buyAmount, '0', 2) < 0) { $buyAmount = '0.00'; } $custom->buyAmount = $buyAmount; $custom->save(false, ['buyAmount']); $payWay = intval($refund->payWay ?? ($order->payWay ?? 0)); $balance = bcadd((string)($custom->balance ?? '0'), '0', 2); // 余额/挂账:原路=加回余额,完成后 hasReturn=1(couldReturn 已为 0) if (self::isBalanceOrDebtPayWay($payWay)) { $pair = GhsRechargeSettleService::lockAccountPair($custom); CustomClass::nextDayRefundReturnBalance($pair['custom'], $pair['ghs'], $refund, $order); self::syncRefundFundFlags($refund, ['hasReturn' => self::FLAG_YES]); $custom = CustomClass::getById($customId, true); $balance = bcadd((string)($custom->balance ?? '0'), '0', 2); } elseif (self::isOnlinePayOrder($refund) || self::isOnlinePayOrder($order)) { // 线上付收款时加过门店可提现余额,隔天售后也要在 ghs 端扣回(拉卡拉原路仍在采购侧发起) $shopId = intval($order->shopId ?? ($refund->shopId ?? 0)); $mainId = intval($order->mainId ?? ($refund->mainId ?? 0)); $shop = ShopClass::getLockById($shopId); if (empty($shop)) { util::fail('没有找到门店'); } $main = MainClass::getLockById($mainId); if (empty($main)) { util::fail('没有main信息'); } ShopClass::saleRefundReduceBalance($main, $shop, $refund, $refundPrice); } // 现金/银行卡等线下原路等人工 markHasReturn return $balance; } /** * HD 采购侧隔天通过:累计 nextDayTkPrice;线上原路走拉卡拉成功后 hasReturn=1。 */ public static function applyCgAmountAndFund($cgRefund, $cg) { $refundPrice = bcadd((string)($cgRefund->refundPrice ?? '0'), '0', 2); $cg->nextDayTkPrice = bcadd((string)($cg->nextDayTkPrice ?? '0'), $refundPrice, 2); $cg->refund = PurchaseClass::REFUND_YES; $cg->save(false, ['nextDayTkPrice', 'refund']); if (self::isOnlinePayOrder($cgRefund) || self::isOnlinePayOrder($cg)) { CgRefundClass::nextDayOriginalOnlineRefund($cgRefund, $cg); $cgRefund->hasReturn = self::FLAG_YES; $cgRefund->save(false, ['hasReturn']); $saleRefundId = intval($cgRefund->saleRefundId ?? 0); if ($saleRefundId > 0) { $saleRefund = RefundOrderClass::getById($saleRefundId, true); if (!empty($saleRefund)) { self::syncRefundFundFlags($saleRefund, ['hasReturn' => self::FLAG_YES]); } } } $ghsId = intval($cg->ghsId ?? 0); if ($ghsId > 0) { $ghs = \bizHd\ghs\classes\GhsClass::getLockById($ghsId); if (!empty($ghs) && isset($ghs->expendAmount)) { $expend = bcsub((string)($ghs->expendAmount ?? '0'), $refundPrice, 2); if (bccomp($expend, '0', 2) < 0) { $expend = '0.00'; } $ghs->expendAmount = $expend; $ghs->save(false, ['expendAmount']); } } } /** * 无原单退款通过:加库存(退货退款)、累计门店支出、扣客户消费;资金留给成功页确认原路/返充。 * 不写订单 nextDayTkPrice(无关联销售单)。 */ public static function passFreeRefund($refund) { if (empty($refund)) { util::fail('退款单不存在'); } if (intval($refund->status) === RefundOrderClass::STATUS_COMPLETE) { util::fail('已通过'); } if (intval($refund->status) === RefundOrderClass::STATUS_REJECT) { util::fail('已驳回'); } if (intval($refund->status) === RefundOrderClass::STATUS_CANCEL) { util::fail('已取消'); } $refund->status = RefundOrderClass::STATUS_COMPLETE; $refund->passTime = date('Y-m-d H:i:s'); $refund->save(); $refundSn = $refund->orderSn ?? ''; $refundPrice = bcadd((string)($refund->refundPrice ?? '0'), '0', 2); $refundType = intval($refund->refundType ?? RefundOrderClass::REFUND_TYPE_MONEY_GOOD); $shopId = intval($refund->shopId ?? 0); $mainId = intval($refund->mainId ?? 0); $sjId = intval($refund->sjId ?? 0); $customName = $refund->customName ?? ''; // 有明细则置通过;仅「退货退款」回库(库存不退只记账不改库存) $refundItemList = RefundOrderItemClass::getAllByCondition(['orderSn' => $refundSn], null, '*', null, true); if ($refundType === RefundOrderClass::REFUND_TYPE_MONEY_GOOD && empty($refundItemList)) { util::fail('没有找到退款花材'); } if (!empty($refundItemList)) { foreach ($refundItemList as $val) { $val->status = 1; $val->save(); if ($refundType !== RefundOrderClass::REFUND_TYPE_MONEY_GOOD) { continue; } $productId = intval($val->productId ?? 0); $ptItemId = intval($val->itemId ?? 0); $num = bcadd((string)($val->xhNum ?? '0'), '0', 2); if (bccomp($num, '0', 2) <= 0 || $productId <= 0) { continue; } if (intval($val->xhUnitType ?? 0) === 1) { $smallNum = $num; $bigNum = 0; } else { $smallNum = 0; $bigNum = $num; } $stockInfo = ProductClass::addStock($productId, $bigNum, $smallNum); ProductClass::tryRecoverLimitBuyWhenStockRollbackFromZero($productId, $stockInfo['oldStock'] ?? 0); $oldStock = $stockInfo['oldStock'] ?? 0; $newStock = $stockInfo['newStock'] ?? 0; $recordData = [ 'relateName' => $customName, 'itemNum' => $val->itemNum ?? 0, 'sjId' => $sjId, 'shopId' => $shopId, 'mainId' => $mainId, 'orderSn' => $refundSn, 'itemId' => $ptItemId, 'oldStock' => $oldStock, 'productId' => $productId, 'newStock' => $newStock, 'io' => 1, ]; StockRecordClass::ghsRefundAddRecord($recordData); $val->itemStock = $oldStock; $val->newStock = $newStock; $val->save(); } } $main = MainClass::getLockById($mainId); if (empty($main)) { util::fail('没有main信息'); } $main->totalExpend = bcadd((string)($main->totalExpend ?? '0'), $refundPrice, 2); $main->totalRefund = bcadd((string)($main->totalRefund ?? '0'), $refundPrice, 2); $main->save(); return self::applyFreeFund($refund); } /** * 无原单退款资金:仅扣减客户累计消费;原路/返充在成功页由 markHasReturn / applyReturnBalance 落地。 * @return string 当前客户余额 */ public static function applyFreeFund($refund) { $refundPrice = bcadd((string)($refund->refundPrice ?? '0'), '0', 2); $customId = intval($refund->customId ?? 0); $custom = CustomClass::getLockById($customId); if (empty($custom)) { util::fail('没有找到客户'); } $buyAmount = bcsub((string)($custom->buyAmount ?? '0'), $refundPrice, 2); if (bccomp($buyAmount, '0', 2) < 0) { $buyAmount = '0.00'; } $custom->buyAmount = $buyAmount; $custom->save(false, ['buyAmount']); return bcadd((string)($custom->balance ?? '0'), '0', 2); } /** * 按时段汇总成功隔天售后金额(收入/销量统计扣减用) */ public static function sumAmountByMainAndTime($mainId, $startTime, $endTime) { $sql = "SELECT COALESCE(SUM(refundPrice),0) AS total FROM xhRefund WHERE mainId=:mainId AND status=:status AND sameDay=:sameDay AND passTime BETWEEN :start AND :end"; $row = Yii::$app->db->createCommand($sql, [ ':mainId' => $mainId, ':status' => RefundOrderClass::STATUS_COMPLETE, ':sameDay' => self::SAME_DAY_NO, ':start' => $startTime, ':end' => $endTime, ])->queryOne(); return bcadd($row['total'] ?? '0', '0', 2); } /** * 渠道收入对冲:拉取隔天成功售后及原单支付字段 */ public static function listForChannelIncomeDeduct($mainId, $startTime, $endTime) { $sql = "SELECT r.refundPrice AS amount, r.hasReturn, r.returnBalance, r.couldReturn, r.onlinePay AS refundOnlinePay, r.payWay AS refundPayWay, o.id AS orderId, o.debtPrice, o.remainDebtPrice, o.onlinePay, o.payWay AS orderPayWay, o.payWay FROM xhRefund r LEFT JOIN xhGhsOrder o ON o.orderSn = r.relateOrderSn WHERE r.mainId=:mainId AND r.status=:status AND r.sameDay=:sameDay 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, ':start' => $startTime, ':end' => $endTime, ])->queryAll(); return $rows ?: []; } /** * 按客户汇总隔天退款金额 */ public static function sumAmountGroupByCustom($mainId, $startTime, $endTime) { $sql = "SELECT r.customId, COALESCE(SUM(r.refundPrice),0) AS total FROM xhRefund r WHERE r.mainId=:mainId AND r.status=:status AND r.sameDay=:sameDay AND IFNULL(NULLIF(r.passTime,'0000-00-00 00:00:00'), r.addTime) BETWEEN :start AND :end GROUP BY r.customId"; $rows = Yii::$app->db->createCommand($sql, [ ':mainId' => $mainId, ':status' => RefundOrderClass::STATUS_COMPLETE, ':sameDay' => self::SAME_DAY_NO, ':start' => $startTime, ':end' => $endTime, ])->queryAll(); $map = []; foreach ($rows as $row) { $map[$row['customId']] = bcadd($row['total'] ?? '0', '0', 2); } return $map; } /** * 按客户汇总隔天退货数量(仅退货退款;通过日无开单时客户业绩扎数可显示为负) * @return array customId => num */ public static function sumItemQtyByCustom($mainId, $startTime, $endTime) { $sql = "SELECT r.customId, COALESCE(SUM(i.xhNum),0) AS num 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 IFNULL(NULLIF(r.passTime,'0000-00-00 00:00:00'), r.addTime) BETWEEN :start AND :end GROUP BY r.customId"; $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(); $map = []; foreach ($rows as $row) { $map[$row['customId']] = bcadd($row['num'] ?? '0', '0', 2); } return $map; } /** * 按花材汇总隔天退货数量与金额(退货退款明细) */ public static function sumItemByProduct($mainId, $startTime, $endTime) { $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 IFNULL(NULLIF(r.passTime,'0000-00-00 00:00:00'), r.addTime) BETWEEN :start AND :end GROUP BY i.productId"; $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(); $map = []; foreach ($rows as $row) { $map[$row['productId']] = [ 'num' => bcadd($row['num'] ?? '0', '0', 2), 'amount' => bcadd($row['amount'] ?? '0', '0', 2), ]; } 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; } }