|
|
@@ -3,6 +3,8 @@
|
|
|
* 用途:商城顾客售后申请(落 xhHdRefund 待审核)
|
|
|
* 谁用:app-mall RefundController 提交/列表/撤销、app-hd RefundController 审核通过/驳回
|
|
|
* 解决:申请只落待审核记录不动资金库存;审核通过时复用 HdRefundService 执行真实退款
|
|
|
+ * 退货并退款金额:普通商品按勾选原价分摊会员折/满减/红包(跑腿费不参与分摊);秒杀、团购按活动单价退
|
|
|
+ * 审核通过后退红包:现金已退清(含隔天 nextDayTkPrice)或商品/花材已全部退完,不依赖前端 refundHb
|
|
|
*/
|
|
|
|
|
|
namespace bizHd\refund\classes;
|
|
|
@@ -319,6 +321,7 @@ class HdRefundMallClass
|
|
|
|
|
|
/**
|
|
|
* 退货并退款应退金额:秒杀/团购按行上活动单价;普通商品再按整单优惠占比扣除会员折、满减、红包
|
|
|
+ * 跑腿费不参与分摊,与 mallApp refund.vue、下单 wholeDiscount 口径一致
|
|
|
* @param array $productList 已用订单行单价、seckillGoodsId 补齐的勾选商品
|
|
|
* @param object $order xhOrder
|
|
|
* @return float 不超过可退上限的应退金额
|
|
|
@@ -342,18 +345,7 @@ class HdRefundMallClass
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- $hbAmount = self::toMoney($order->hbAmount ?? 0);
|
|
|
- $prePrice = self::toMoney($order->prePrice ?? 0);
|
|
|
- if (bccomp($prePrice, '0', 2) > 0) {
|
|
|
- $allocBase = bcadd($prePrice, $hbAmount, 2);
|
|
|
- } else {
|
|
|
- $allocBase = self::toMoney($order->goodsPrice ?? 0);
|
|
|
- $allocBase = bcadd($allocBase, self::toMoney($order->sendCost ?? 0), 2);
|
|
|
- $allocBase = bcadd($allocBase, self::toMoney($order->packingFee ?? 0), 2);
|
|
|
- $allocBase = bcadd($allocBase, self::toMoney($order->labourCost ?? 0), 2);
|
|
|
- $allocBase = bcadd($allocBase, self::toMoney($order->serviceFee ?? 0), 2);
|
|
|
- }
|
|
|
-
|
|
|
+ $allocBase = self::getDiscountAllocBase($order);
|
|
|
$activityOriginal = self::sumOrderActivityOriginal($order, $isGroupBuy);
|
|
|
$normalBase = (float)$allocBase - (float)$activityOriginal;
|
|
|
if ($normalBase < 0) {
|
|
|
@@ -362,7 +354,7 @@ class HdRefundMallClass
|
|
|
|
|
|
$discountTotal = self::toMoney($order->discountAmount ?? 0);
|
|
|
$discountTotal = bcadd($discountTotal, self::toMoney($order->orderReachDiscountPrice ?? 0), 2);
|
|
|
- $discountTotal = bcadd($discountTotal, $hbAmount, 2);
|
|
|
+ $discountTotal = bcadd($discountTotal, self::toMoney($order->hbAmount ?? 0), 2);
|
|
|
|
|
|
$normalPrice = $selectedNormal;
|
|
|
if ($selectedNormal > 0 && $normalBase > 0 && (float)$discountTotal > 0) {
|
|
|
@@ -380,6 +372,49 @@ class HdRefundMallClass
|
|
|
return round((float)$price, 2);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 跑腿费(sendType=2 的 sendCost):下单时红包不抵运费、会员折/满减也不打在跑腿费上
|
|
|
+ * 退货分摊时要从基数里剔除,否则会把优惠摊到跑腿费上,商品应退被抬高
|
|
|
+ * @param object $order xhOrder
|
|
|
+ * @return string 两位小数字符串
|
|
|
+ */
|
|
|
+ protected static function getRunnerSendCost($order)
|
|
|
+ {
|
|
|
+ if ((int)($order->sendType ?? 0) !== 2) {
|
|
|
+ return '0.00';
|
|
|
+ }
|
|
|
+ return self::toMoney($order->sendCost ?? 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 整单优惠分摊基数:红包只抵商品;会员折/满减按「商品+非跑腿运费/包装-红包」
|
|
|
+ * prePrice 已扣红包且含跑腿费,这里加回红包再剔除跑腿费,与下单 wholeDiscount 口径一致
|
|
|
+ * @param object $order xhOrder
|
|
|
+ * @return string 两位小数字符串
|
|
|
+ */
|
|
|
+ protected static function getDiscountAllocBase($order)
|
|
|
+ {
|
|
|
+ $hbAmount = self::toMoney($order->hbAmount ?? 0);
|
|
|
+ $prePrice = self::toMoney($order->prePrice ?? 0);
|
|
|
+ $runnerCost = self::getRunnerSendCost($order);
|
|
|
+ if (bccomp($prePrice, '0', 2) > 0) {
|
|
|
+ $base = bcsub(bcadd($prePrice, $hbAmount, 2), $runnerCost, 2);
|
|
|
+ if (bccomp($base, '0', 2) < 0) {
|
|
|
+ return '0.00';
|
|
|
+ }
|
|
|
+ return $base;
|
|
|
+ }
|
|
|
+ $allocBase = self::toMoney($order->goodsPrice ?? 0);
|
|
|
+ // 非跑腿才把运费计入分摊基数;跑腿费已在 getRunnerSendCost 口径下排除
|
|
|
+ if ((int)($order->sendType ?? 0) !== 2) {
|
|
|
+ $allocBase = bcadd($allocBase, self::toMoney($order->sendCost ?? 0), 2);
|
|
|
+ }
|
|
|
+ $allocBase = bcadd($allocBase, self::toMoney($order->packingFee ?? 0), 2);
|
|
|
+ $allocBase = bcadd($allocBase, self::toMoney($order->labourCost ?? 0), 2);
|
|
|
+ $allocBase = bcadd($allocBase, self::toMoney($order->serviceFee ?? 0), 2);
|
|
|
+ return $allocBase;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 整单秒杀/团购商品原价合计,供从优惠分摊基数中剔除
|
|
|
* @param object $order
|
|
|
@@ -601,14 +636,78 @@ class HdRefundMallClass
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- $updatedOrder = OrderClass::getById($order->id, true, 'id, actPrice, hbId, hbAmount');
|
|
|
- if (!empty($updatedOrder) && (int)$updatedOrder->hbId > 0 && bccomp((string)$updatedOrder->actPrice, '0', 2) <= 0) {
|
|
|
+ $updatedOrder = OrderClass::getById($order->id, true);
|
|
|
+ if (self::shouldReturnHbAfterApprove($updatedOrder)) {
|
|
|
HbClass::hbBackIfNotExpired($updatedOrder);
|
|
|
}
|
|
|
|
|
|
return $refund;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 审核通过后是否退回红包。与商家端即时售后的 refundHb 勾选无关,由服务端自行判断:
|
|
|
+ * 1)现金已退清:remainRefundCash=0(当天 tkPrice + 隔天 nextDayTkPrice >= orderPrice)
|
|
|
+ * 不能只看 actPrice——隔天售后只加 nextDayTkPrice、不扣 actPrice,全额退完 actPrice 仍可能 >0
|
|
|
+ * 2)商品/花材已全部退完:红包只抵商品,退货并退款退光商品后即使还留跑腿费也应退红包
|
|
|
+ * @param object|null $order 审核写完金额后的订单
|
|
|
+ * @return bool
|
|
|
+ */
|
|
|
+ protected static function shouldReturnHbAfterApprove($order)
|
|
|
+ {
|
|
|
+ if (empty($order) || (int)($order->hbId ?? 0) <= 0) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (bccomp(self::remainRefundCash($order), '0', 2) === 0) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return self::isAllOrderProductsReturned($order);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 订单商品和花材是否都已退完(refundNum >= num)
|
|
|
+ * 只退款不写退货数量时返回 false,避免把部分退款误判成全额退货
|
|
|
+ * @param object $order
|
|
|
+ * @return bool
|
|
|
+ */
|
|
|
+ protected static function isAllOrderProductsReturned($order)
|
|
|
+ {
|
|
|
+ $orderSn = trim((string)($order->orderSn ?? ''));
|
|
|
+ if ($orderSn === '') {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ $goodsRows = OrderGoodsClass::getAllByCondition(['orderSn' => $orderSn]) ?: [];
|
|
|
+ $itemRows = OrderItemClass::getAllByCondition(['orderSn' => $orderSn]) ?: [];
|
|
|
+ if (empty($goodsRows) && empty($itemRows)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ foreach ($goodsRows as $goods) {
|
|
|
+ if (!self::isLineFullyReturned($goods)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ foreach ($itemRows as $item) {
|
|
|
+ if (!self::isLineFullyReturned($item)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 单行是否已退完数量
|
|
|
+ * @param array|object $line 订单商品或花材行
|
|
|
+ * @return bool
|
|
|
+ */
|
|
|
+ protected static function isLineFullyReturned($line)
|
|
|
+ {
|
|
|
+ $num = is_array($line) ? ($line['num'] ?? 0) : ($line->num ?? 0);
|
|
|
+ $refundNum = is_array($line) ? ($line['refundNum'] ?? 0) : ($line->refundNum ?? 0);
|
|
|
+ if (!is_numeric($num) || bccomp((string)$num, '0', 2) <= 0) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return bccomp((string)$refundNum, (string)$num, 2) >= 0;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 商家驳回申请
|
|
|
* @param object $refund
|