|
@@ -7,6 +7,7 @@
|
|
|
|
|
|
|
|
namespace bizHd\order\classes;
|
|
namespace bizHd\order\classes;
|
|
|
|
|
|
|
|
|
|
+use bizGhs\express\classes\DeliveryAuthTokenClass;
|
|
|
use bizHd\base\classes\BaseClass;
|
|
use bizHd\base\classes\BaseClass;
|
|
|
use common\components\dict;
|
|
use common\components\dict;
|
|
|
use Yii;
|
|
use Yii;
|
|
@@ -187,6 +188,203 @@ class PsMethodClass extends BaseClass
|
|
|
return $result;
|
|
return $result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 自定义计费三项是否均已有效填写(起步公里/起步价/超出每公里价均大于 0)
|
|
|
|
|
+ * @param array $config
|
|
|
|
|
+ * @return bool
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function isCustomConfigValid(array $config)
|
|
|
|
|
+ {
|
|
|
|
|
+ $startKm = (float)($config['startKm'] ?? 0);
|
|
|
|
|
+ $startPrice = (float)($config['startPrice'] ?? 0);
|
|
|
|
|
+ $perKmPrice = (float)($config['perKmPrice'] ?? 0);
|
|
|
|
|
+ return $startKm > 0 && $startPrice > 0 && $perKmPrice > 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 跑腿 style=2 是否允许启用:已绑定跑腿 或 自定义计费配置完整
|
|
|
|
|
+ * @param int $mainId 商户 ID
|
|
|
|
|
+ * @param array $config 待保存的 xhPsMethod 配置
|
|
|
|
|
+ * @return bool
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function canEnableRunnerMethod($mainId, array $config)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (self::isCustomConfigValid($config)) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ $hasAuth = DeliveryAuthTokenClass::getByCondition(['mainId' => (int)$mainId], true);
|
|
|
|
|
+ return !empty($hasAuth);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 按自定义规则计距算运费(公里)
|
|
|
|
|
+ * 超出起步部分:不足 1 公里按 1 公里(向上取整)
|
|
|
|
|
+ * @param array $config xhPsMethod
|
|
|
|
|
+ * @param float $distanceKm 配送距离(公里)
|
|
|
|
|
+ * @return float
|
|
|
|
|
+ * @throws \Exception
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function calcCustomFreightByDistance(array $config, $distanceKm)
|
|
|
|
|
+ {
|
|
|
|
|
+ $startKm = (float)($config['startKm'] ?? 0);
|
|
|
|
|
+ $startPrice = (float)($config['startPrice'] ?? 0);
|
|
|
|
|
+ $perKmPrice = (float)($config['perKmPrice'] ?? 0);
|
|
|
|
|
+ $changeRate = (float)($config['changeRate'] ?? 0);
|
|
|
|
|
+ if ($startKm <= 0 || $startPrice <= 0 || $perKmPrice <= 0) {
|
|
|
|
|
+ throw new \Exception('自定义计费规则未配置完整');
|
|
|
|
|
+ }
|
|
|
|
|
+ // 与前端一致保留两位公里
|
|
|
|
|
+ $distanceKm = round((float)$distanceKm, 2);
|
|
|
|
|
+ if ($distanceKm <= 0) {
|
|
|
|
|
+ throw new \Exception('算运费失败,距离无效');
|
|
|
|
|
+ }
|
|
|
|
|
+ $sendCost = $startPrice;
|
|
|
|
|
+ if ($distanceKm > $startKm) {
|
|
|
|
|
+ $extraDistance = (float)bcsub((string)$distanceKm, (string)$startKm, 2);
|
|
|
|
|
+ // 不足 1 公里按 1 公里
|
|
|
|
|
+ $billableExtra = (int)ceil($extraDistance - 1e-9);
|
|
|
|
|
+ if ($billableExtra < 0) {
|
|
|
|
|
+ $billableExtra = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ $extraCost = bcmul((string)$billableExtra, (string)$perKmPrice, 2);
|
|
|
|
|
+ $sendCost = bcadd((string)$sendCost, $extraCost, 2);
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($changeRate != 0) {
|
|
|
|
|
+ $rate = bcadd('1', bcdiv((string)$changeRate, '100', 4), 4);
|
|
|
|
|
+ $sendCost = bcmul((string)$sendCost, $rate, 2);
|
|
|
|
|
+ }
|
|
|
|
|
+ return (float)$sendCost;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 花束包运费:hsFreeKm 内免费,超出按 hsPerKmPrice 每公里加收(不足 1 公里按 1 公里)
|
|
|
|
|
+ * @param array $config xhPsMethod
|
|
|
|
|
+ * @param float $distanceMeters 距离(米)
|
|
|
|
|
+ * @return float
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function calcBouquetFreightByDistance(array $config, $distanceMeters)
|
|
|
|
|
+ {
|
|
|
|
|
+ $hsFreeKm = (float)($config['hsFreeKm'] ?? 0);
|
|
|
|
|
+ $hsPerKmPrice = (float)($config['hsPerKmPrice'] ?? 0);
|
|
|
|
|
+ $freeMeters = $hsFreeKm * 1000;
|
|
|
|
|
+ $distanceMeters = (float)$distanceMeters;
|
|
|
|
|
+ if ($distanceMeters <= $freeMeters) {
|
|
|
|
|
+ return 0.0;
|
|
|
|
|
+ }
|
|
|
|
|
+ $diff = $distanceMeters - $freeMeters;
|
|
|
|
|
+ $km = (int)ceil($diff / 1000);
|
|
|
|
|
+ if ($km <= 0 || $hsPerKmPrice <= 0) {
|
|
|
|
|
+ return 0.0;
|
|
|
|
|
+ }
|
|
|
|
|
+ return (float)bcmul((string)$km, (string)$hsPerKmPrice, 2);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 纯花材是否命中 xhPsReduceRule 免跑腿
|
|
|
|
|
+ * @param array $config 含 reduceRules
|
|
|
|
|
+ * @param float $distanceMeters
|
|
|
|
|
+ * @param float $bigNum 花材扎数
|
|
|
|
|
+ * @param float $itemTotalAmount 商品金额
|
|
|
|
|
+ * @return bool
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function matchReduceRuleFree(array $config, $distanceMeters, $bigNum, $itemTotalAmount)
|
|
|
|
|
+ {
|
|
|
|
|
+ $rules = $config['reduceRules'] ?? [];
|
|
|
|
|
+ if (!is_array($rules) || empty($rules)) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ $distanceMeters = (float)$distanceMeters;
|
|
|
|
|
+ $bigNum = (float)$bigNum;
|
|
|
|
|
+ $itemTotalAmount = (float)$itemTotalAmount;
|
|
|
|
|
+ foreach ($rules as $rule) {
|
|
|
|
|
+ $meetNum = (float)($rule['meetNum'] ?? 0);
|
|
|
|
|
+ $meetAmount = (float)($rule['meetAmount'] ?? 0);
|
|
|
|
|
+ $freeKm = (float)($rule['freeKm'] ?? 0);
|
|
|
|
|
+ if ($freeKm <= 0) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ $freeMeters = $freeKm * 1000;
|
|
|
|
|
+ $numOk = $meetNum <= 0 || $bigNum >= $meetNum;
|
|
|
|
|
+ $amountOk = $meetAmount <= 0 || $itemTotalAmount >= $meetAmount;
|
|
|
|
|
+ if ($numOk && $amountOk && $distanceMeters <= $freeMeters) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 混合单跑腿运费模式:bouquetIncluded / bouquetNotIncluded / flowerOnly
|
|
|
|
|
+ * @param array $productList [['property'=>0|1,'freightType'=>0|1], ...]
|
|
|
|
|
+ * @return string
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function resolveMixRunnerFreightMode(array $productList)
|
|
|
|
|
+ {
|
|
|
|
|
+ $hasBouquet = false;
|
|
|
|
|
+ $hasIncluded = false;
|
|
|
|
|
+ foreach ($productList as $row) {
|
|
|
|
|
+ if ((int)($row['property'] ?? 1) !== 0) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ $hasBouquet = true;
|
|
|
|
|
+ if ((int)($row['freightType'] ?? 0) === 1) {
|
|
|
|
|
+ $hasIncluded = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($hasBouquet && $hasIncluded) {
|
|
|
|
|
+ return 'bouquetIncluded';
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($hasBouquet) {
|
|
|
|
|
+ return 'bouquetNotIncluded';
|
|
|
|
|
+ }
|
|
|
|
|
+ return 'flowerOnly';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 跑腿保存/启用前校验,不通过时抛异常
|
|
|
|
|
+ * @param int $mainId 商户 ID
|
|
|
|
|
+ * @param array $data 前端提交的配置
|
|
|
|
|
+ * @throws \Exception
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function validateRunnerSave($mainId, array $data)
|
|
|
|
|
+ {
|
|
|
|
|
+ $style = (int)($data['style'] ?? 0);
|
|
|
|
|
+ if ($style !== 2) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ $status = (int)($data['status'] ?? 1);
|
|
|
|
|
+ // 无论 calcType=0 用跑腿还是 =1 用自定义,都必须填写自定义计费(跑腿异常时兜底)
|
|
|
|
|
+ if (!self::isCustomConfigValid($data)) {
|
|
|
|
|
+ throw new \Exception('请完整填写自定义计费规则(起步公里、起步价、超出每公里价)');
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($status === 1 && !self::canEnableRunnerMethod($mainId, $data)) {
|
|
|
|
|
+ throw new \Exception('请先绑定跑腿商户或配置完整的自定义计费后再启用');
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 取消全部跑腿授权后:若自定义计费也不完整则禁用 style=2;
|
|
|
|
|
+ * 自定义完整则保持启用(calcType=1 主路径,或 calcType=0 跑腿异常时兜底)
|
|
|
|
|
+ * @param int $mainId 商户 ID
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function disableRunnerIfNoAuthLeft($mainId)
|
|
|
|
|
+ {
|
|
|
|
|
+ $remaining = DeliveryAuthTokenClass::getCount(['mainId' => (int)$mainId]);
|
|
|
|
|
+ if ($remaining > 0) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ $ps = self::getByCondition(['mainId' => (int)$mainId, 'style' => 2]);
|
|
|
|
|
+ if (empty($ps)) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ $psArr = is_object($ps) ? $ps->toArray() : $ps;
|
|
|
|
|
+ // 仍有完整自定义配置时不禁用,结算可走自定义或兜底
|
|
|
|
|
+ if (self::isCustomConfigValid($psArr)) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ self::updateByCondition(['id' => (int)$psArr['id']], ['status' => 0]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 保存指定配送方式配置及关联说明项、满减规则
|
|
* 保存指定配送方式配置及关联说明项、满减规则
|
|
|
* @param int $shopId 门店ID
|
|
* @param int $shopId 门店ID
|
|
@@ -200,6 +398,8 @@ class PsMethodClass extends BaseClass
|
|
|
$id = $data['id'] ?? 0;
|
|
$id = $data['id'] ?? 0;
|
|
|
$style = $data['style'] ?? 0;
|
|
$style = $data['style'] ?? 0;
|
|
|
|
|
|
|
|
|
|
+ self::validateRunnerSave($mainId, $data);
|
|
|
|
|
+
|
|
|
$config = null;
|
|
$config = null;
|
|
|
if ($id > 0) {
|
|
if ($id > 0) {
|
|
|
$config = self::getByCondition(['id' => $id, 'mainId' => $mainId], true);
|
|
$config = self::getByCondition(['id' => $id, 'mainId' => $mainId], true);
|
|
@@ -217,10 +417,13 @@ class PsMethodClass extends BaseClass
|
|
|
'minNum' => isset($data['minNum']) ? (int)$data['minNum'] : 0,
|
|
'minNum' => isset($data['minNum']) ? (int)$data['minNum'] : 0,
|
|
|
'unMeet' => isset($data['unMeet']) ? (int)$data['unMeet'] : 0,
|
|
'unMeet' => isset($data['unMeet']) ? (int)$data['unMeet'] : 0,
|
|
|
'unMeetFee' => isset($data['unMeetFee']) ? (float)$data['unMeetFee'] : 0.00,
|
|
'unMeetFee' => isset($data['unMeetFee']) ? (float)$data['unMeetFee'] : 0.00,
|
|
|
|
|
+ 'calcType' => isset($data['calcType']) ? (int)$data['calcType'] : 0,
|
|
|
'startKm' => isset($data['startKm']) ? (float)$data['startKm'] : 0.00,
|
|
'startKm' => isset($data['startKm']) ? (float)$data['startKm'] : 0.00,
|
|
|
'startPrice' => isset($data['startPrice']) ? (float)$data['startPrice'] : 0.00,
|
|
'startPrice' => isset($data['startPrice']) ? (float)$data['startPrice'] : 0.00,
|
|
|
'perKmPrice' => isset($data['perKmPrice']) ? (float)$data['perKmPrice'] : 0.00,
|
|
'perKmPrice' => isset($data['perKmPrice']) ? (float)$data['perKmPrice'] : 0.00,
|
|
|
'changeRate' => isset($data['changeRate']) ? (float)$data['changeRate'] : 0.00,
|
|
'changeRate' => isset($data['changeRate']) ? (float)$data['changeRate'] : 0.00,
|
|
|
|
|
+ 'hsFreeKm' => isset($data['hsFreeKm']) ? (float)$data['hsFreeKm'] : 0.00,
|
|
|
|
|
+ 'hsPerKmPrice' => isset($data['hsPerKmPrice']) ? (float)$data['hsPerKmPrice'] : 0.00,
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
$transaction = Yii::$app->db->beginTransaction();
|
|
$transaction = Yii::$app->db->beginTransaction();
|
|
@@ -335,6 +538,38 @@ class PsMethodClass extends BaseClass
|
|
|
return implode('或', $parts);
|
|
return implode('或', $parts);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 未达门槛时「还差多少可免」提示,如:订单还差20元和3扎可免包装费
|
|
|
|
|
+ * @param array $config
|
|
|
|
|
+ * @param float $itemTotalAmount
|
|
|
|
|
+ * @param float $bigNum
|
|
|
|
|
+ * @param string $feeLabel 包装费|运费
|
|
|
|
|
+ * @return string
|
|
|
|
|
+ */
|
|
|
|
|
+ protected static function buildUnMeetShortTip($config, $itemTotalAmount, $bigNum, $feeLabel)
|
|
|
|
|
+ {
|
|
|
|
|
+ $minAmount = isset($config['minAmount']) ? (float)$config['minAmount'] : 0.00;
|
|
|
|
|
+ $minNum = isset($config['minNum']) ? (int)$config['minNum'] : 0;
|
|
|
|
|
+ $parts = [];
|
|
|
|
|
+ if ($minAmount > 0) {
|
|
|
|
|
+ $shortAmount = round(max(0, $minAmount - (float)$itemTotalAmount), 2);
|
|
|
|
|
+ if ($shortAmount > 0) {
|
|
|
|
|
+ $parts[] = $shortAmount . '元';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($minNum > 0) {
|
|
|
|
|
+ $shortNum = max(0, $minNum - (float)$bigNum);
|
|
|
|
|
+ // 扎数展示取整友好值
|
|
|
|
|
+ if ($shortNum > 0) {
|
|
|
|
|
+ $parts[] = (fmod($shortNum, 1) == 0 ? (int)$shortNum : round($shortNum, 2)) . '扎';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (empty($parts)) {
|
|
|
|
|
+ return '';
|
|
|
|
|
+ }
|
|
|
|
|
+ return '订单还差' . implode('和', $parts) . '可免' . $feeLabel;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 判断当天是否已收过包装类附加费(xhPsMethod unMeet=2)
|
|
* 判断当天是否已收过包装类附加费(xhPsMethod unMeet=2)
|
|
|
*/
|
|
*/
|
|
@@ -608,9 +843,8 @@ class PsMethodClass extends BaseClass
|
|
|
|| ($unMeet === 2 && self::hasPackFeeDecisionToday($customId, $style));
|
|
|| ($unMeet === 2 && self::hasPackFeeDecisionToday($customId, $style));
|
|
|
if (!$locked) {
|
|
if (!$locked) {
|
|
|
$feeLabel = $unMeet === 2 ? '包装费' : '运费';
|
|
$feeLabel = $unMeet === 2 ? '包装费' : '运费';
|
|
|
- if ($conditionText !== '') {
|
|
|
|
|
- $tip = "订单不满{$conditionText},加{$feeLabel}{$unMeetFee}元";
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 展示还差多少可免附加费,便于用户凑单
|
|
|
|
|
+ $tip = self::buildUnMeetShortTip($config, $itemTotalAmount, $bigNum, $feeLabel);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|