|
|
@@ -0,0 +1,821 @@
|
|
|
+<?php
|
|
|
+/**
|
|
|
+ * 用途:按订单计算分销提成并落库(无记录新建,待结算/失效自动重算)
|
|
|
+ * 谁用:OrderClass::payAfter、RefundController/MtController、定时 settle-due、console calc-order
|
|
|
+ */
|
|
|
+
|
|
|
+namespace bizHd\distribution\classes;
|
|
|
+
|
|
|
+use bizHd\custom\classes\CustomClass;
|
|
|
+use bizHd\goods\classes\GoodsCategoryClass;
|
|
|
+use bizHd\order\classes\OrderClass;
|
|
|
+use bizHd\order\classes\OrderGoodsClass;
|
|
|
+use bizHd\order\classes\OrderItemClass;
|
|
|
+use bizHd\shop\classes\ShopClass;
|
|
|
+use Yii;
|
|
|
+
|
|
|
+class DistributionCommissionClass
|
|
|
+{
|
|
|
+ /**
|
|
|
+ * 按订单 ID 计算并落库分销提成
|
|
|
+ * @param int $orderId xhOrder.id
|
|
|
+ * @return array
|
|
|
+ * @throws \Throwable
|
|
|
+ */
|
|
|
+ public static function calcAndPersist($orderId)
|
|
|
+ {
|
|
|
+ $context = self::buildOrderContext($orderId);
|
|
|
+ $snapshot = self::computeCommissionSnapshot($context);
|
|
|
+
|
|
|
+ $existing = DistributionOrderClass::getByCondition(['orderId' => $context['orderId'], 'distId' => $context['distId']]);
|
|
|
+ $isRecalc = false;
|
|
|
+ if (!empty($existing)) {
|
|
|
+ $allowRemoveSettled = !empty($snapshot['shouldRemoveDistOrder']);
|
|
|
+ if ((int)($existing['settleStatus'] ?? 0) === 1 && !$allowRemoveSettled) {
|
|
|
+ throw new \Exception('已结算订单不可重算,记录ID:' . (int)$existing['id']);
|
|
|
+ }
|
|
|
+ $isRecalc = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ $transaction = Yii::$app->db->beginTransaction();
|
|
|
+ try {
|
|
|
+ // 全部退款导致分佣为 0:删除分销订单记录并回滚统计
|
|
|
+ if (!empty($snapshot['shouldRemoveDistOrder'])) {
|
|
|
+ $distributionOrderId = 0;
|
|
|
+ if (!empty($existing)) {
|
|
|
+ self::rollbackAllDistOrderEffects($existing);
|
|
|
+ $distOrderId = (int)$existing['id'];
|
|
|
+ DistributionOrderClass::deleteByCondition(['id' => $distOrderId]);
|
|
|
+ // 兜底:按 orderId + distId 再删一次,防止条件未命中
|
|
|
+ DistributionOrderClass::deleteByCondition([
|
|
|
+ 'orderId' => (int)$context['orderId'],
|
|
|
+ 'distId' => (int)$context['distId'],
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ $transaction->commit();
|
|
|
+ return array_merge($snapshot['result'], [
|
|
|
+ 'ok' => 1,
|
|
|
+ 'recalc' => $isRecalc ? 1 : 0,
|
|
|
+ 'distributionOrderId' => 0,
|
|
|
+ 'deleted' => !empty($existing) ? 1 : 0,
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!empty($existing)) {
|
|
|
+ self::rollbackPendingEffects($existing, $context);
|
|
|
+ DistributionOrderClass::updateByCondition(['id' => (int)$existing['id']], $snapshot['distOrderData']);
|
|
|
+ $distributionOrderId = (int)$existing['id'];
|
|
|
+ } else {
|
|
|
+ $distOrder = DistributionOrderClass::add($snapshot['distOrderData'], true);
|
|
|
+ $distributionOrderId = (int)$distOrder->id;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($snapshot['shouldApplyStats']) {
|
|
|
+ // 待结算与已结算(finishTime+settleDays 到期)走不同累加字段
|
|
|
+ $newSettleStatus = (int)($snapshot['distOrderData']['settleStatus'] ?? 0);
|
|
|
+ if ($newSettleStatus === 1) {
|
|
|
+ self::applySettledEffects($context, $snapshot, $distributionOrderId);
|
|
|
+ } else {
|
|
|
+ self::applyPendingEffects($context, $snapshot, $distributionOrderId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $transaction->commit();
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $transaction->rollBack();
|
|
|
+ throw $e;
|
|
|
+ }
|
|
|
+
|
|
|
+ return array_merge($snapshot['result'], [
|
|
|
+ 'ok' => 1,
|
|
|
+ 'recalc' => $isRecalc ? 1 : 0,
|
|
|
+ 'distributionOrderId' => $distributionOrderId,
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 仅计算不落库(与落库同一套规则)
|
|
|
+ * @param int $orderId
|
|
|
+ * @return array
|
|
|
+ * @throws \Throwable
|
|
|
+ */
|
|
|
+ public static function calculatePreview($orderId)
|
|
|
+ {
|
|
|
+ $context = self::buildOrderContext($orderId);
|
|
|
+ $snapshot = self::computeCommissionSnapshot($context);
|
|
|
+ return array_merge($snapshot['result'], ['ok' => 1, 'preview' => 1]);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 支付/售后后尝试落库分销提成(失败不阻断主流程)
|
|
|
+ * @param int $orderId xhOrder.id
|
|
|
+ */
|
|
|
+ public static function tryCalcDistributionAfterPay($orderId)
|
|
|
+ {
|
|
|
+ $orderId = (int)$orderId;
|
|
|
+ if ($orderId <= 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ self::calcAndPersist($orderId);
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ Yii::warning('分销计佣失败 orderId=' . $orderId . ' ' . $e->getMessage(), __METHOD__);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 分销结算用完成时间:优先 xhOrder.successTime
|
|
|
+ * @param \bizHd\order\models\Order|array $order
|
|
|
+ * @return string|null
|
|
|
+ */
|
|
|
+ public static function getSuccessTimeForDistribution($order)
|
|
|
+ {
|
|
|
+ $successTime = is_object($order) ? ($order->successTime ?? '') : ($order['successTime'] ?? '');
|
|
|
+ if (!empty($successTime) && $successTime !== '0000-00-00 00:00:00') {
|
|
|
+ return $successTime;
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 零售订单完成后:以 successTime 同步 xhDistributionOrder.finishTime(不触发结佣)
|
|
|
+ * @param int $orderId xhOrder.id
|
|
|
+ * @param string|null $successTime 与 xhOrder.successTime 一致;空则读库或补写
|
|
|
+ */
|
|
|
+ public static function onRetailOrderCompleted($orderId, $successTime = null)
|
|
|
+ {
|
|
|
+ $orderId = (int)$orderId;
|
|
|
+ if ($orderId <= 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ $order = OrderClass::getById($orderId, true);
|
|
|
+ if (empty($order) || (int)($order->status ?? 0) !== OrderClass::ORDER_STATUS_COMPLETE) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ $time = $successTime;
|
|
|
+ if ($time === null || $time === '') {
|
|
|
+ $time = self::getSuccessTimeForDistribution($order);
|
|
|
+ }
|
|
|
+ if ($time === null) {
|
|
|
+ $time = date('Y-m-d H:i:s');
|
|
|
+ OrderClass::updateById($orderId, ['successTime' => $time]);
|
|
|
+ } elseif ($time !== self::getSuccessTimeForDistribution($order)) {
|
|
|
+ OrderClass::updateById($orderId, ['successTime' => $time]);
|
|
|
+ }
|
|
|
+ DistributionOrderClass::touchFinishTimeByOrderId($orderId, $time);
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ Yii::warning('分销完成时间回写失败 orderId=' . $orderId . ' ' . $e->getMessage(), __METHOD__);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验订单并组装计佣上下文
|
|
|
+ */
|
|
|
+ protected static function buildOrderContext($orderId)
|
|
|
+ {
|
|
|
+ $orderId = (int)$orderId;
|
|
|
+ if ($orderId <= 0) {
|
|
|
+ throw new \Exception('订单ID无效');
|
|
|
+ }
|
|
|
+
|
|
|
+ $order = OrderClass::getById($orderId, true);
|
|
|
+ if (empty($order)) {
|
|
|
+ throw new \Exception('订单不存在');
|
|
|
+ }
|
|
|
+
|
|
|
+ $shopId = (int)($order->shopId ?? 0);
|
|
|
+ if ($shopId <= 0) {
|
|
|
+ throw new \Exception('订单门店无效');
|
|
|
+ }
|
|
|
+ if ((int)($order->payStatus ?? 0) !== 1) {
|
|
|
+ throw new \Exception('订单未付款,无法计佣');
|
|
|
+ }
|
|
|
+ if ((int)($order->status ?? 0) === OrderClass::ORDER_STATUS_CANCEL) {
|
|
|
+ throw new \Exception('订单已取消,无法计佣');
|
|
|
+ }
|
|
|
+
|
|
|
+ $shop = ShopClass::getById($shopId, true);
|
|
|
+ $mainId = (int)($shop->mainId ?? 0);
|
|
|
+ $rule = DistributionRuleClass::getRule($shopId, $mainId);
|
|
|
+ if ((int)($rule['status'] ?? 0) !== 1) {
|
|
|
+ throw new \Exception('门店分销功能未开启');
|
|
|
+ }
|
|
|
+
|
|
|
+ $buyerId = (int)($order->customId ?? 0);
|
|
|
+ if ($buyerId <= 0) {
|
|
|
+ throw new \Exception('订单无客户信息');
|
|
|
+ }
|
|
|
+
|
|
|
+ $buyerDu = DistributionUserClass::getByCondition(['customId' => $buyerId]);
|
|
|
+ $distId = (int)($buyerDu['inviterId'] ?? 0);
|
|
|
+ if ($distId <= 0) {
|
|
|
+ throw new \Exception('买家无绑定分销员');
|
|
|
+ }
|
|
|
+
|
|
|
+ $bindTime = $buyerDu['inviteTime'] ?? '';
|
|
|
+ $orderTime = !empty($order->payTime) && $order->payTime !== '0000-00-00 00:00:00'
|
|
|
+ ? $order->payTime
|
|
|
+ : ($order->addTime ?? date('Y-m-d H:i:s'));
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'orderId' => $orderId,
|
|
|
+ 'order' => $order,
|
|
|
+ 'shopId' => $shopId,
|
|
|
+ 'mainId' => $mainId,
|
|
|
+ 'rule' => $rule,
|
|
|
+ 'buyerId' => $buyerId,
|
|
|
+ 'distId' => $distId,
|
|
|
+ 'bindTime' => $bindTime,
|
|
|
+ 'orderTime' => $orderTime,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 统一计佣:商品实付、范围、比例、落库快照
|
|
|
+ */
|
|
|
+ protected static function computeCommissionSnapshot(array $context)
|
|
|
+ {
|
|
|
+ $order = $context['order'];
|
|
|
+ $rule = $context['rule'];
|
|
|
+ $shopId = $context['shopId'];
|
|
|
+ $mainId = $context['mainId'];
|
|
|
+ $buyerId = $context['buyerId'];
|
|
|
+ $distId = $context['distId'];
|
|
|
+ $bindTime = $context['bindTime'];
|
|
|
+ $orderTime = $context['orderTime'];
|
|
|
+
|
|
|
+ $bindDays = self::calcBindDaysAt($bindTime, $orderTime);
|
|
|
+ $payAmount = round((float)($order->actPrice ?? 0), 2);
|
|
|
+ $sendCost = round((float)($order->sendCost ?? 0), 2);
|
|
|
+ $goodsPayAmount = self::resolveGoodsPayAmount($order);
|
|
|
+ $orderAmount = $goodsPayAmount;
|
|
|
+
|
|
|
+ $commissionBase = self::adjustCommissionBaseByScope(
|
|
|
+ $rule,
|
|
|
+ $order->orderSn ?? '',
|
|
|
+ $goodsPayAmount,
|
|
|
+ $shopId,
|
|
|
+ $mainId
|
|
|
+ );
|
|
|
+ // 订单有累计退款时,计佣基数扣减 tkPrice(与 xhOrder.tkPrice 一致)
|
|
|
+ $commissionBase = self::applyRefundToCommissionBase($commissionBase, $order);
|
|
|
+
|
|
|
+ $calc = self::resolveCommissionRate($rule, $bindDays);
|
|
|
+ $commissionRate = $calc['rate'];
|
|
|
+ $invalidReason = $calc['invalidReason'];
|
|
|
+ $settleStatus = 0;
|
|
|
+ $commissionAmount = 0;
|
|
|
+ $orderComplete = (int)($order->status ?? 0) === OrderClass::ORDER_STATUS_COMPLETE;
|
|
|
+ $settleDays = max(0, (int)($rule['settleDays'] ?? 0));
|
|
|
+
|
|
|
+ $existingRow = DistributionOrderClass::getByCondition([
|
|
|
+ 'orderId' => (int)$context['orderId'],
|
|
|
+ 'distId' => $distId,
|
|
|
+ ]);
|
|
|
+ $finishTime = self::resolveDistributionFinishTime($order, $existingRow);
|
|
|
+
|
|
|
+ if ($invalidReason !== '') {
|
|
|
+ $settleStatus = 2;
|
|
|
+ } elseif ($commissionBase > 0 && $commissionRate > 0) {
|
|
|
+ $commissionAmount = round($commissionBase * $commissionRate / 100, 2);
|
|
|
+ }
|
|
|
+ // 完成不等于已结算:须 finishTime + settleDays 到期后才 settleStatus=1
|
|
|
+ if ($settleStatus !== 2 && $commissionAmount > 0) {
|
|
|
+ $canSettle = false;
|
|
|
+ if ($orderComplete && !empty($finishTime)) {
|
|
|
+ $canSettle = time() >= (strtotime($finishTime) + $settleDays * 86400);
|
|
|
+ }
|
|
|
+ $settleStatus = $canSettle ? 1 : 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ $buyer = CustomClass::getById($buyerId, true);
|
|
|
+ $dist = CustomClass::getById($distId, true);
|
|
|
+ $buyerName = $buyer->name ?? ($order->customName ?? '');
|
|
|
+ $distName = $dist->name ?? '';
|
|
|
+
|
|
|
+ $distOrderData = [
|
|
|
+ 'shopId' => $shopId,
|
|
|
+ 'hdId' => (int)($order->hdId ?? 0),
|
|
|
+ 'orderId' => (int)$context['orderId'],
|
|
|
+ 'orderSn' => $order->orderSn ?? '',
|
|
|
+ 'buyerId' => $buyerId,
|
|
|
+ 'buyerName' => $buyerName,
|
|
|
+ 'distId' => $distId,
|
|
|
+ 'distName' => $distName,
|
|
|
+ 'orderAmount' => $orderAmount,
|
|
|
+ 'payAmount' => $payAmount,
|
|
|
+ 'sendCost' => $sendCost,
|
|
|
+ 'commissionBase' => $commissionBase,
|
|
|
+ 'commissionRate' => $commissionRate,
|
|
|
+ 'commissionAmount' => $commissionAmount,
|
|
|
+ 'bonusType' => (int)($rule['bonusType'] ?? 1),
|
|
|
+ 'bindTime' => $bindTime ?: null,
|
|
|
+ 'bindDays' => $bindDays,
|
|
|
+ 'settleStatus' => $settleStatus,
|
|
|
+ 'settleTime' => $settleStatus === 1 ? date('Y-m-d H:i:s') : null,
|
|
|
+ 'orderTime' => $order->addTime ?? null,
|
|
|
+ 'payTime' => $order->payTime ?? null,
|
|
|
+ 'finishTime' => $finishTime,
|
|
|
+ 'invalidReason' => $settleStatus === 2 ? $invalidReason : '',
|
|
|
+ 'invalidTime' => $settleStatus === 2 ? date('Y-m-d H:i:s') : null,
|
|
|
+ ];
|
|
|
+
|
|
|
+ $shouldRemoveDistOrder = self::shouldRemoveDistributionOrder($order, $commissionAmount, $commissionBase);
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'distOrderData' => $distOrderData,
|
|
|
+ // 有效计佣(非失效)均需同步 xhDistributionUser / 流水;待结算与已结算走不同累加字段
|
|
|
+ 'shouldApplyStats' => ($settleStatus !== 2 && $commissionAmount > 0),
|
|
|
+ 'shouldRemoveDistOrder' => $shouldRemoveDistOrder,
|
|
|
+ 'commissionAmount' => $commissionAmount,
|
|
|
+ 'commissionBase' => $commissionBase,
|
|
|
+ 'buyerName' => $buyerName,
|
|
|
+ 'result' => [
|
|
|
+ 'orderId' => (int)$context['orderId'],
|
|
|
+ 'orderSn' => $order->orderSn ?? '',
|
|
|
+ 'buyerId' => $buyerId,
|
|
|
+ 'distId' => $distId,
|
|
|
+ 'bindDays' => $bindDays,
|
|
|
+ 'commissionRate' => $commissionRate,
|
|
|
+ 'commissionBase' => $commissionBase,
|
|
|
+ 'goodsPayAmount' => $goodsPayAmount,
|
|
|
+ 'tkPrice' => round((float)($order->tkPrice ?? 0), 2),
|
|
|
+ 'commissionAmount' => $commissionAmount,
|
|
|
+ 'settleStatus' => $settleStatus,
|
|
|
+ 'invalidReason' => $invalidReason,
|
|
|
+ 'shouldRemoveDistOrder' => $shouldRemoveDistOrder ? 1 : 0,
|
|
|
+ 'deleted' => 0,
|
|
|
+ ],
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否应删除 xhDistributionOrder(有退款且分佣为 0)
|
|
|
+ */
|
|
|
+ protected static function shouldRemoveDistributionOrder($order, $commissionAmount, $commissionBase)
|
|
|
+ {
|
|
|
+ if (round((float)$commissionAmount, 2) > 0) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ $tkPrice = round((float)($order->tkPrice ?? 0), 2);
|
|
|
+ if ($tkPrice <= 0) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ $actPrice = round((float)($order->actPrice ?? 0), 2);
|
|
|
+ // 退款后实付已退尽(HdRefundService 会扣减 actPrice)
|
|
|
+ if (bccomp((string)$actPrice, '0', 2) <= 0) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ $mainPay = round((float)($order->mainPay ?? 0), 2);
|
|
|
+ if ($mainPay > 0 && bccomp((string)$tkPrice, (string)$mainPay, 2) >= 0) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ // 累计退款扣减后计佣基数已为 0
|
|
|
+ if (bccomp((string)round((float)$commissionBase, 2), '0', 2) <= 0) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 累计退款已覆盖原实付 mainPay(兼容 actPrice 未归零的数据)
|
|
|
+ */
|
|
|
+ protected static function isOrderFullyRefunded($order)
|
|
|
+ {
|
|
|
+ $tkPrice = round((float)($order->tkPrice ?? 0), 2);
|
|
|
+ if ($tkPrice <= 0) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ $actPrice = round((float)($order->actPrice ?? 0), 2);
|
|
|
+ if (bccomp((string)$actPrice, '0', 2) <= 0) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ $mainPay = round((float)($order->mainPay ?? 0), 2);
|
|
|
+ if ($mainPay > 0 && bccomp((string)$tkPrice, (string)$mainPay, 2) >= 0) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return bccomp((string)$tkPrice, (string)$actPrice, 2) >= 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 删除分销订单前回滚待结算/已结算统计与流水
|
|
|
+ */
|
|
|
+ protected static function rollbackAllDistOrderEffects(array $existing)
|
|
|
+ {
|
|
|
+ $settleStatus = (int)($existing['settleStatus'] ?? 0);
|
|
|
+ if ($settleStatus === 1) {
|
|
|
+ self::rollbackSettledEffects($existing);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ self::rollbackPendingEffects($existing, []);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 回滚已结算计佣对 xhDistributionUser / 流水的影响
|
|
|
+ */
|
|
|
+ protected static function rollbackSettledEffects(array $existing)
|
|
|
+ {
|
|
|
+ $oldAmount = round((float)($existing['commissionAmount'] ?? 0), 2);
|
|
|
+ $oldBase = round((float)($existing['commissionBase'] ?? 0), 2);
|
|
|
+ $distOrderId = (int)$existing['id'];
|
|
|
+
|
|
|
+ DistributionFlowClass::deleteByCondition([
|
|
|
+ 'refType' => 1,
|
|
|
+ 'refId' => $distOrderId,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ if ($oldAmount <= 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ $distId = (int)($existing['distId'] ?? 0);
|
|
|
+ $buyerId = (int)($existing['buyerId'] ?? 0);
|
|
|
+
|
|
|
+ DistributionUserClass::$baseFile::updateAllCounters(
|
|
|
+ [
|
|
|
+ 'settledCommission' => -$oldAmount,
|
|
|
+ 'availDeposit' => -$oldAmount,
|
|
|
+ 'totalCommission' => -$oldAmount,
|
|
|
+ 'orderCount' => -1,
|
|
|
+ 'orderAmount' => -$oldBase,
|
|
|
+ ],
|
|
|
+ ['customId' => $distId]
|
|
|
+ );
|
|
|
+
|
|
|
+ if ($buyerId > 0) {
|
|
|
+ DistributionUserClass::$baseFile::updateAllCounters(
|
|
|
+ [
|
|
|
+ 'upOrderCount' => -1,
|
|
|
+ 'upOrderAmount' => -$oldBase,
|
|
|
+ 'upCommissionAmount' => -$oldAmount,
|
|
|
+ ],
|
|
|
+ ['customId' => $buyerId]
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 重算前回滚原待结算计佣对统计/流水的影响
|
|
|
+ */
|
|
|
+ protected static function rollbackPendingEffects(array $existing, array $context)
|
|
|
+ {
|
|
|
+ $settleStatus = (int)($existing['settleStatus'] ?? 0);
|
|
|
+ $oldAmount = round((float)($existing['commissionAmount'] ?? 0), 2);
|
|
|
+ $oldBase = round((float)($existing['commissionBase'] ?? 0), 2);
|
|
|
+ if ($settleStatus !== 0 || $oldAmount <= 0) {
|
|
|
+ DistributionFlowClass::deleteByCondition([
|
|
|
+ 'refType' => 1,
|
|
|
+ 'refId' => (int)$existing['id'],
|
|
|
+ ]);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ $distId = (int)($existing['distId'] ?? 0);
|
|
|
+ $buyerId = (int)($existing['buyerId'] ?? 0);
|
|
|
+ $distOrderId = (int)$existing['id'];
|
|
|
+
|
|
|
+ DistributionUserClass::$baseFile::updateAllCounters(
|
|
|
+ [
|
|
|
+ 'pendingCommission' => -$oldAmount,
|
|
|
+ 'totalCommission' => -$oldAmount,
|
|
|
+ 'orderCount' => -1,
|
|
|
+ 'orderAmount' => -$oldBase,
|
|
|
+ ],
|
|
|
+ ['customId' => $distId]
|
|
|
+ );
|
|
|
+
|
|
|
+ if ($buyerId > 0) {
|
|
|
+ DistributionUserClass::$baseFile::updateAllCounters(
|
|
|
+ [
|
|
|
+ 'upOrderCount' => -1,
|
|
|
+ 'upOrderAmount' => -$oldBase,
|
|
|
+ 'upCommissionAmount' => -$oldAmount,
|
|
|
+ ],
|
|
|
+ ['customId' => $buyerId]
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ DistributionFlowClass::deleteByCondition([
|
|
|
+ 'refType' => 1,
|
|
|
+ 'refId' => $distOrderId,
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 写入待结算流水并累加分销员/买家统计
|
|
|
+ */
|
|
|
+ protected static function applyPendingEffects(array $context, array $snapshot, $distributionOrderId)
|
|
|
+ {
|
|
|
+ $order = $context['order'];
|
|
|
+ $shopId = $context['shopId'];
|
|
|
+ $distId = $context['distId'];
|
|
|
+ $buyerId = $context['buyerId'];
|
|
|
+ $orderTime = $context['orderTime'];
|
|
|
+ $commissionAmount = $snapshot['commissionAmount'];
|
|
|
+ $commissionBase = $snapshot['commissionBase'];
|
|
|
+ $buyerName = $snapshot['buyerName'];
|
|
|
+
|
|
|
+ self::ensureDistributionUser($distId, $shopId);
|
|
|
+ self::ensureDistributionUser($buyerId, $shopId);
|
|
|
+
|
|
|
+ $distRow = DistributionUserClass::getByCondition(['customId' => $distId]);
|
|
|
+ $availBefore = round((float)($distRow['availDeposit'] ?? 0), 2);
|
|
|
+
|
|
|
+ DistributionFlowClass::add([
|
|
|
+ 'shopId' => $shopId,
|
|
|
+ 'hdId' => (int)($order->hdId ?? 0),
|
|
|
+ 'xhCustomId' => $distId,
|
|
|
+ 'flowType' => 1,
|
|
|
+ 'amount' => $commissionAmount,
|
|
|
+ 'availAfter' => $availBefore,
|
|
|
+ 'refType' => 1,
|
|
|
+ 'refId' => (int)$distributionOrderId,
|
|
|
+ 'refSn' => $order->orderSn ?? '',
|
|
|
+ 'buyerName' => $buyerName,
|
|
|
+ 'flowStatus' => 1,
|
|
|
+ 'flowTime' => $orderTime,
|
|
|
+ 'remark' => '订单分红计佣',
|
|
|
+ ], true);
|
|
|
+
|
|
|
+ DistributionUserClass::$baseFile::updateAllCounters(
|
|
|
+ [
|
|
|
+ 'pendingCommission' => $commissionAmount,
|
|
|
+ 'totalCommission' => $commissionAmount,
|
|
|
+ 'orderCount' => 1,
|
|
|
+ 'orderAmount' => $commissionBase,
|
|
|
+ ],
|
|
|
+ ['customId' => $distId]
|
|
|
+ );
|
|
|
+
|
|
|
+ DistributionUserClass::$baseFile::updateAllCounters(
|
|
|
+ [
|
|
|
+ 'upOrderCount' => 1,
|
|
|
+ 'upOrderAmount' => $commissionBase,
|
|
|
+ 'upCommissionAmount' => $commissionAmount,
|
|
|
+ ],
|
|
|
+ ['customId' => $buyerId]
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 订单已完成:写入已结算流水,累加 settledCommission / availDeposit(可存入余额)
|
|
|
+ */
|
|
|
+ protected static function applySettledEffects(array $context, array $snapshot, $distributionOrderId)
|
|
|
+ {
|
|
|
+ $order = $context['order'];
|
|
|
+ $shopId = $context['shopId'];
|
|
|
+ $distId = $context['distId'];
|
|
|
+ $buyerId = $context['buyerId'];
|
|
|
+ $orderTime = $context['orderTime'];
|
|
|
+ $commissionAmount = $snapshot['commissionAmount'];
|
|
|
+ $commissionBase = $snapshot['commissionBase'];
|
|
|
+ $buyerName = $snapshot['buyerName'];
|
|
|
+
|
|
|
+ self::ensureDistributionUser($distId, $shopId);
|
|
|
+ self::ensureDistributionUser($buyerId, $shopId);
|
|
|
+
|
|
|
+ $distRow = DistributionUserClass::getByCondition(['customId' => $distId]);
|
|
|
+ $availBefore = round((float)($distRow['availDeposit'] ?? 0), 2);
|
|
|
+ $availAfter = round($availBefore + $commissionAmount, 2);
|
|
|
+
|
|
|
+ DistributionFlowClass::add([
|
|
|
+ 'shopId' => $shopId,
|
|
|
+ 'hdId' => (int)($order->hdId ?? 0),
|
|
|
+ 'xhCustomId' => $distId,
|
|
|
+ 'flowType' => 1,
|
|
|
+ 'amount' => $commissionAmount,
|
|
|
+ 'availAfter' => $availAfter,
|
|
|
+ 'refType' => 1,
|
|
|
+ 'refId' => (int)$distributionOrderId,
|
|
|
+ 'refSn' => $order->orderSn ?? '',
|
|
|
+ 'buyerName' => $buyerName,
|
|
|
+ 'flowStatus' => 2,
|
|
|
+ 'flowTime' => $orderTime,
|
|
|
+ 'remark' => '订单分红计佣',
|
|
|
+ ], true);
|
|
|
+
|
|
|
+ DistributionUserClass::$baseFile::updateAllCounters(
|
|
|
+ [
|
|
|
+ 'settledCommission' => $commissionAmount,
|
|
|
+ 'availDeposit' => $commissionAmount,
|
|
|
+ 'totalCommission' => $commissionAmount,
|
|
|
+ 'orderCount' => 1,
|
|
|
+ 'orderAmount' => $commissionBase,
|
|
|
+ ],
|
|
|
+ ['customId' => $distId]
|
|
|
+ );
|
|
|
+
|
|
|
+ DistributionUserClass::$baseFile::updateAllCounters(
|
|
|
+ [
|
|
|
+ 'upOrderCount' => 1,
|
|
|
+ 'upOrderAmount' => $commissionBase,
|
|
|
+ 'upCommissionAmount' => $commissionAmount,
|
|
|
+ ],
|
|
|
+ ['customId' => $buyerId]
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析订单「商品实付」金额(计佣基数来源,不含杂费)
|
|
|
+ */
|
|
|
+ protected static function resolveGoodsPayAmount($order)
|
|
|
+ {
|
|
|
+ $actPrice = (string)($order->actPrice ?? '0');
|
|
|
+ $goodsPrice = (string)($order->goodsPrice ?? '0');
|
|
|
+ $prePrice = (string)($order->prePrice ?? '0');
|
|
|
+
|
|
|
+ if (bccomp($goodsPrice, '0', 2) > 0) {
|
|
|
+ if (bccomp($prePrice, '0', 2) > 0 && bccomp($prePrice, $actPrice, 2) > 0 && bccomp($actPrice, '0', 2) > 0) {
|
|
|
+ return round((float)bcmul($goodsPrice, bcdiv($actPrice, $prePrice, 6), 2), 2);
|
|
|
+ }
|
|
|
+ if (bccomp($actPrice, '0', 2) > 0 && bccomp($goodsPrice, $actPrice, 2) > 0) {
|
|
|
+ return round((float)$actPrice, 2);
|
|
|
+ }
|
|
|
+ return round((float)$goodsPrice, 2);
|
|
|
+ }
|
|
|
+
|
|
|
+ $fees = '0';
|
|
|
+ foreach (['sendCost', 'labourCost', 'packingFee', 'packCost', 'serviceFee'] as $field) {
|
|
|
+ $fees = bcadd($fees, (string)($order->$field ?? '0'), 2);
|
|
|
+ }
|
|
|
+ $goodsPay = bcsub($actPrice, $fees, 2);
|
|
|
+
|
|
|
+ if (bccomp($goodsPay, '0', 2) <= 0) {
|
|
|
+ $lineSum = (string)self::sumOrderLinesAmount($order->orderSn ?? '');
|
|
|
+ if (bccomp($lineSum, '0', 2) > 0) {
|
|
|
+ $goodsPay = $lineSum;
|
|
|
+ if (bccomp($prePrice, '0', 2) > 0 && bccomp($prePrice, $actPrice, 2) > 0 && bccomp($actPrice, '0', 2) > 0) {
|
|
|
+ $goodsPay = bcmul($lineSum, bcdiv($actPrice, $prePrice, 6), 2);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return round(max(0, (float)$goodsPay), 2);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected static function sumOrderLinesAmount($orderSn)
|
|
|
+ {
|
|
|
+ if ($orderSn === '') {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ $sum = '0';
|
|
|
+ foreach (OrderGoodsClass::getListBySn($orderSn) as $row) {
|
|
|
+ $sum = bcadd($sum, (string)($row['price'] ?? '0'), 2);
|
|
|
+ }
|
|
|
+ foreach (OrderItemClass::getListBySn($orderSn) as $row) {
|
|
|
+ $sum = bcadd($sum, (string)($row['price'] ?? '0'), 2);
|
|
|
+ }
|
|
|
+ return round((float)$sum, 2);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 分销 finishTime:与 xhOrder.successTime 对齐,无则保留库中 finishTime
|
|
|
+ */
|
|
|
+ protected static function resolveDistributionFinishTime($order, array $existingRow)
|
|
|
+ {
|
|
|
+ $fromOrder = self::getSuccessTimeForDistribution($order);
|
|
|
+ if ($fromOrder !== null) {
|
|
|
+ return $fromOrder;
|
|
|
+ }
|
|
|
+ $storedFinish = $existingRow['finishTime'] ?? '';
|
|
|
+ if (!empty($storedFinish) && $storedFinish !== '0000-00-00 00:00:00') {
|
|
|
+ return $storedFinish;
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected static function calcBindDaysAt($bindTime, $orderTime)
|
|
|
+ {
|
|
|
+ if (empty($bindTime) || $bindTime === '0000-00-00 00:00:00') {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ $bindTs = strtotime($bindTime);
|
|
|
+ $orderTs = strtotime($orderTime);
|
|
|
+ if ($bindTs <= 0 || $orderTs <= 0) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ return max(0, (int)floor(($orderTs - $bindTs) / 86400));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计佣基数扣减订单累计退款金额
|
|
|
+ * @param float $commissionBase 范围调整后的基数
|
|
|
+ * @param object $order xhOrder
|
|
|
+ * @return float
|
|
|
+ */
|
|
|
+ protected static function applyRefundToCommissionBase($commissionBase, $order)
|
|
|
+ {
|
|
|
+ $commissionBase = round((float)$commissionBase, 2);
|
|
|
+ $tkPrice = round((float)($order->tkPrice ?? 0), 2);
|
|
|
+ if ($tkPrice <= 0 || $commissionBase <= 0) {
|
|
|
+ return $commissionBase;
|
|
|
+ }
|
|
|
+ $after = bcsub((string)$commissionBase, (string)$tkPrice, 2);
|
|
|
+ return round(max(0, (float)$after), 2);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected static function resolveCommissionRate($rule, $bindDays)
|
|
|
+ {
|
|
|
+ $bonusType = (int)($rule['bonusType'] ?? 1);
|
|
|
+ $durationType = (int)($rule['durationType'] ?? 1);
|
|
|
+ $durationDays = (int)($rule['durationDays'] ?? 0);
|
|
|
+
|
|
|
+ if ($bonusType === 1 && $durationType === 2 && $durationDays > 0 && $bindDays > $durationDays) {
|
|
|
+ return ['rate' => 0, 'invalidReason' => '超出分红限制天数'];
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($bonusType === 1) {
|
|
|
+ return ['rate' => round((float)($rule['bonusRate'] ?? 0), 2), 'invalidReason' => ''];
|
|
|
+ }
|
|
|
+
|
|
|
+ $tiers = isset($rule['tiers']) && is_array($rule['tiers']) ? $rule['tiers'] : [];
|
|
|
+ foreach ($tiers as $tier) {
|
|
|
+ $minDays = (int)($tier['minDays'] ?? 0);
|
|
|
+ $maxDays = (int)($tier['maxDays'] ?? 0);
|
|
|
+ $rate = round((float)($tier['bonusRate'] ?? 0), 2);
|
|
|
+ if ($bindDays >= $minDays && ($maxDays === 0 || $bindDays <= $maxDays)) {
|
|
|
+ return ['rate' => $rate, 'invalidReason' => ''];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return ['rate' => 0, 'invalidReason' => '未匹配到时间阶梯'];
|
|
|
+ }
|
|
|
+
|
|
|
+ protected static function adjustCommissionBaseByScope($rule, $orderSn, $goodsPayAmount, $shopId, $mainId)
|
|
|
+ {
|
|
|
+ $productScope = (int)($rule['productScope'] ?? 1);
|
|
|
+ $goodsPayAmount = round((float)$goodsPayAmount, 2);
|
|
|
+ if ($productScope === 1 || $goodsPayAmount <= 0 || $orderSn === '') {
|
|
|
+ return $goodsPayAmount;
|
|
|
+ }
|
|
|
+
|
|
|
+ $goodsList = OrderGoodsClass::getListBySn($orderSn);
|
|
|
+ $itemList = OrderItemClass::getListBySn($orderSn);
|
|
|
+ $totalLineAmount = 0;
|
|
|
+ $eligibleAmount = 0;
|
|
|
+
|
|
|
+ if ($productScope === 2) {
|
|
|
+ $goodsIds = array_map('intval', $rule['goodsIds'] ?? []);
|
|
|
+ $goodsIds = array_filter($goodsIds);
|
|
|
+ foreach ($goodsList as $row) {
|
|
|
+ $lineAmount = round((float)($row['price'] ?? 0), 2);
|
|
|
+ $totalLineAmount += $lineAmount;
|
|
|
+ if (in_array((int)($row['goodsId'] ?? 0), $goodsIds, true)) {
|
|
|
+ $eligibleAmount += $lineAmount;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ foreach ($itemList as $row) {
|
|
|
+ $totalLineAmount += round((float)($row['price'] ?? 0), 2);
|
|
|
+ }
|
|
|
+ } elseif ($productScope === 3) {
|
|
|
+ $categoryIds = array_map('intval', $rule['categoryIds'] ?? []);
|
|
|
+ $categoryIds = array_filter($categoryIds);
|
|
|
+ $goodsIdsInOrder = array_filter(array_map('intval', array_column($goodsList, 'goodsId')));
|
|
|
+ $eligibleGoodsIds = [];
|
|
|
+ if (!empty($goodsIdsInOrder) && !empty($categoryIds)) {
|
|
|
+ $catRows = GoodsCategoryClass::getAllByCondition([
|
|
|
+ 'gId' => ['in', $goodsIdsInOrder],
|
|
|
+ 'cId' => ['in', $categoryIds],
|
|
|
+ ]);
|
|
|
+ foreach ($catRows as $catRow) {
|
|
|
+ $eligibleGoodsIds[(int)$catRow['gId']] = 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ foreach ($goodsList as $row) {
|
|
|
+ $lineAmount = round((float)($row['price'] ?? 0), 2);
|
|
|
+ $totalLineAmount += $lineAmount;
|
|
|
+ if (!empty($eligibleGoodsIds[(int)($row['goodsId'] ?? 0)])) {
|
|
|
+ $eligibleAmount += $lineAmount;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ foreach ($itemList as $row) {
|
|
|
+ $lineAmount = round((float)($row['price'] ?? 0), 2);
|
|
|
+ $totalLineAmount += $lineAmount;
|
|
|
+ if (in_array((int)($row['classId'] ?? 0), $categoryIds, true)) {
|
|
|
+ $eligibleAmount += $lineAmount;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($totalLineAmount <= 0 || $eligibleAmount <= 0) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ return round($goodsPayAmount * ($eligibleAmount / $totalLineAmount), 2);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected static function ensureDistributionUser($customId, $shopId)
|
|
|
+ {
|
|
|
+ $customId = (int)$customId;
|
|
|
+ $row = DistributionUserClass::getByCondition(['customId' => $customId]);
|
|
|
+ if (!empty($row)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ DistributionUserClass::add([
|
|
|
+ 'id' => $customId,
|
|
|
+ 'customId' => $customId,
|
|
|
+ 'shopId' => (int)$shopId,
|
|
|
+ ], true);
|
|
|
+ }
|
|
|
+}
|