|
|
@@ -613,11 +613,20 @@ class OrderController extends BaseController
|
|
|
util::fail('请选择规格');
|
|
|
}
|
|
|
$stock = $goodsInfo->stock ?? 0;
|
|
|
- if ($stock <= 0) {
|
|
|
- util::fail('库存不足');
|
|
|
- }
|
|
|
$saleGoodsId = $specGoodsId > 0 ? $specGoodsId : $goodsId;
|
|
|
$activityType = strval($post['activityType'] ?? '');
|
|
|
+ if (bccomp($goodsNum, floatval($stock), 2) > 0) {
|
|
|
+ // 库存不足返回商品标识,供结算页红框高亮(与限购超限同形态)
|
|
|
+ $this->failStockExceed([
|
|
|
+ 'productId' => $saleGoodsId,
|
|
|
+ 'goodsId' => $goodsId,
|
|
|
+ 'specGoodsId' => $specGoodsId,
|
|
|
+ 'property' => 0,
|
|
|
+ 'activityType' => $activityType,
|
|
|
+ 'stock' => $stock,
|
|
|
+ 'num' => $goodsNum,
|
|
|
+ ]);
|
|
|
+ }
|
|
|
|
|
|
//事务处理
|
|
|
$connection = Yii::$app->db;
|
|
|
@@ -669,14 +678,27 @@ class OrderController extends BaseController
|
|
|
}
|
|
|
$seckillStock = floatval($seckillRow['stock'] ?? 0);
|
|
|
$soldCount = HomePageModuleClass::getSeckillSoldCount($mainId, $seckillActivityGoodsId);
|
|
|
- if (bcadd($soldCount, $goodsNum, 2) > $seckillStock) {
|
|
|
- util::fail('秒杀库存不足,请重新选择');
|
|
|
+ $seckillRemain = bcsub($seckillStock, $soldCount, 2);
|
|
|
+ if (bccomp($seckillRemain, '0', 2) < 0) {
|
|
|
+ $seckillRemain = '0';
|
|
|
+ }
|
|
|
+ if (bccomp($goodsNum, $seckillRemain, 2) > 0) {
|
|
|
+ $this->failStockExceed([
|
|
|
+ 'productId' => $saleGoodsId,
|
|
|
+ 'goodsId' => $goodsId,
|
|
|
+ 'specGoodsId' => $specGoodsId,
|
|
|
+ 'property' => 0,
|
|
|
+ 'activityType' => 'seckill',
|
|
|
+ 'stock' => $seckillRemain,
|
|
|
+ 'num' => $goodsNum,
|
|
|
+ ]);
|
|
|
}
|
|
|
$seckillLimit = floatval($seckillRow['limit'] ?? 0);
|
|
|
if ($seckillLimit > 0) {
|
|
|
$boughtCount = HomePageModuleClass::getSeckillCustomBoughtCount($mainId, $seckillActivityGoodsId, $customId);
|
|
|
if (bcadd($boughtCount, $goodsNum, 2) > $seckillLimit) {
|
|
|
- util::fail("秒杀商品每人限购{$seckillLimit}件,已超出可购买数量");
|
|
|
+ // 返回超限商品,供结算页红框高亮;code=-1 与花材限购错误对齐
|
|
|
+ HomePageModuleClass::failSeckillLimitExceed($saleGoodsId, $goodsId, $specGoodsId, $seckillLimit, $boughtCount, $goodsNum);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -955,6 +977,9 @@ class OrderController extends BaseController
|
|
|
if ($property === 1) {
|
|
|
$itemRows[$idx] = $row;
|
|
|
} elseif ($property === 0) {
|
|
|
+ if (strval($row['activityType'] ?? '') === 'groupBuy') {
|
|
|
+ util::fail('团购商品请从开团页提交订单');
|
|
|
+ }
|
|
|
$goodsRows[$idx] = $row;
|
|
|
} else {
|
|
|
util::fail('商品类型错误');
|
|
|
@@ -1062,6 +1087,19 @@ class OrderController extends BaseController
|
|
|
util::fail('有花材信息没有找到');
|
|
|
}
|
|
|
$num = $element['num'] ?? 0;
|
|
|
+ $itemStock = floatval($product['stock'] ?? 0);
|
|
|
+ if (bccomp(floatval($num), $itemStock, 2) > 0) {
|
|
|
+ // 花材库存不足也返回 productId,确认页/购物车按限购同款红框提示超出量
|
|
|
+ $this->failStockExceed([
|
|
|
+ 'productId' => $productId,
|
|
|
+ 'goodsId' => 0,
|
|
|
+ 'specGoodsId' => 0,
|
|
|
+ 'property' => 1,
|
|
|
+ 'activityType' => '',
|
|
|
+ 'stock' => $itemStock,
|
|
|
+ 'num' => $num,
|
|
|
+ ]);
|
|
|
+ }
|
|
|
$limitKey = \bizHd\product\classes\ProductClass::LIMIT_BUY_KEY . $productId;
|
|
|
$hasBuyNum = Yii::$app->redis->executeCommand('HGET', [$limitKey, $hdCustomId]);
|
|
|
$hasBuyNum = $num + ($hasBuyNum == null ? 0 : $hasBuyNum);
|
|
|
@@ -1138,8 +1176,16 @@ class OrderController extends BaseController
|
|
|
}
|
|
|
$stock = $goodsInfo->stock;
|
|
|
$stockSet = $goodsInfo->stockSet;
|
|
|
- if ($stockSet == 1 && $stock <= 0) {
|
|
|
- util::fail('花束库存不足');
|
|
|
+ if ($stockSet == 1 && bccomp($goodsNum, floatval($stock), 2) > 0) {
|
|
|
+ $this->failStockExceed([
|
|
|
+ 'productId' => $saleGoodsId,
|
|
|
+ 'goodsId' => $goodsId,
|
|
|
+ 'specGoodsId' => $specGoodsId,
|
|
|
+ 'property' => 0,
|
|
|
+ 'activityType' => strval($element['activityType'] ?? ''),
|
|
|
+ 'stock' => $stock,
|
|
|
+ 'num' => $goodsNum,
|
|
|
+ ]);
|
|
|
}
|
|
|
|
|
|
// 秒杀商品:不信任前端传的价格,独立回查秒杀配置核实活动状态/库存/限购后再核价
|
|
|
@@ -1156,14 +1202,27 @@ class OrderController extends BaseController
|
|
|
}
|
|
|
$seckillStock = floatval($seckillRow['stock']);
|
|
|
$soldCount = HomePageModuleClass::getSeckillSoldCount($mainId, $seckillActivityGoodsId);
|
|
|
- if (bcadd($soldCount, $goodsNum, 2) > $seckillStock) {
|
|
|
- util::fail('秒杀库存不足,请重新选择');
|
|
|
+ $seckillRemain = bcsub($seckillStock, $soldCount, 2);
|
|
|
+ if (bccomp($seckillRemain, '0', 2) < 0) {
|
|
|
+ $seckillRemain = '0';
|
|
|
+ }
|
|
|
+ if (bccomp($goodsNum, $seckillRemain, 2) > 0) {
|
|
|
+ $this->failStockExceed([
|
|
|
+ 'productId' => $saleGoodsId,
|
|
|
+ 'goodsId' => $goodsId,
|
|
|
+ 'specGoodsId' => $specGoodsId,
|
|
|
+ 'property' => 0,
|
|
|
+ 'activityType' => 'seckill',
|
|
|
+ 'stock' => $seckillRemain,
|
|
|
+ 'num' => $goodsNum,
|
|
|
+ ]);
|
|
|
}
|
|
|
$seckillLimit = floatval($seckillRow['limit'] ?? 0);
|
|
|
if ($seckillLimit > 0) {
|
|
|
$boughtCount = HomePageModuleClass::getSeckillCustomBoughtCount($mainId, $seckillActivityGoodsId, $hdCustomId); // TODO 同一个商品,如果缓存没有合理清除,就会一直限购
|
|
|
if (bcadd($boughtCount, $goodsNum, 2) > $seckillLimit) {
|
|
|
- util::fail("秒杀商品每人限购{$seckillLimit}件,已超出可购买数量");
|
|
|
+ // 返回超限商品,供混合结算页/购物车红框高亮;code=-1 与花材限购错误对齐
|
|
|
+ HomePageModuleClass::failSeckillLimitExceed($saleGoodsId, $goodsId, $specGoodsId, $seckillLimit, $boughtCount, $goodsNum);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -1301,118 +1360,26 @@ class OrderController extends BaseController
|
|
|
|
|
|
// 跑腿费(sendType=2 实际收取部分):不参与会员折/门店满减基数,随单传给折扣落地层剔除
|
|
|
$runnerSendCost = 0;
|
|
|
- // 跑腿 style=2:按包运费花束 / 不包运费花束 / 纯花材满减 + 跑腿或自定义(异常兜底)
|
|
|
+ // 跑腿 style=2:与团购开团/参团共用 PsMethodClass::calcMixRunnerFreight
|
|
|
if (isset($post['sendType']) && (int)$post['sendType'] === 2) {
|
|
|
- if ((int)$openIntraCity === 2) {
|
|
|
- util::fail('不能使用跑腿');
|
|
|
- }
|
|
|
- if (empty($post['long']) || empty($post['lat'])) {
|
|
|
- util::fail('位置错误');
|
|
|
- }
|
|
|
- $shopLat = $shop->lat ?? '';
|
|
|
- $shopLong = $shop->long ?? '';
|
|
|
- if (empty($shopLat) || empty($shopLong)) {
|
|
|
- util::fail('商家门店地址定位未设置,编号263');
|
|
|
- }
|
|
|
-
|
|
|
- $psConfig = PsMethodClass::getConfig($this->shopId, $this->mainId, 2);
|
|
|
- if (empty($psConfig) || (int)($psConfig['status'] ?? 0) !== 1) {
|
|
|
- util::fail('跑腿配送方式未启用');
|
|
|
- }
|
|
|
- $freightMode = PsMethodClass::resolveMixRunnerFreightMode($resolvedProduct);
|
|
|
- $calcType = (int)($psConfig['calcType'] ?? 0);
|
|
|
-
|
|
|
- $quoteCustom = (object)[
|
|
|
- 'name' => $post['receiveUserName'] ?? '',
|
|
|
- 'mobile' => $post['receiveMobile'] ?? '',
|
|
|
- 'fullAddress' => $post['address'] ?? ($post['fullAddress'] ?? ''),
|
|
|
- 'floor' => $post['floor'] ?? '',
|
|
|
- 'dist' => $post['dist'] ?? '',
|
|
|
- 'lat' => $post['lat'],
|
|
|
- 'long' => $post['long'],
|
|
|
- 'address' => $post['address'] ?? '',
|
|
|
- 'city' => $post['city'] ?? '',
|
|
|
- ];
|
|
|
- $distanceRespond = mapUtil::calcShopDistanceByEleBike($quoteCustom, $shop, 0);
|
|
|
- $distanceMeters = (float)($distanceRespond['distance'] ?? 0);
|
|
|
- $distanceKm = (float)($distanceRespond['distanceKm'] ?? 0);
|
|
|
- if ($distanceMeters <= 0 && $distanceKm <= 0) {
|
|
|
- util::fail('算运费失败,请完善收货地址');
|
|
|
- }
|
|
|
- if ($distanceMeters <= 0 && $distanceKm > 0) {
|
|
|
- $distanceMeters = $distanceKm * 1000;
|
|
|
- }
|
|
|
-
|
|
|
- $sendCost = 0;
|
|
|
- $freightSource = '';
|
|
|
-
|
|
|
- if ($freightMode === 'bouquetIncluded') {
|
|
|
- // A:含包运费花束 → hsFreeKm / hsPerKmPrice
|
|
|
- $sendCost = PsMethodClass::calcBouquetFreightByDistance($psConfig, $distanceMeters);
|
|
|
- $freightSource = 'bouquetIncluded';
|
|
|
- } elseif ($freightMode === 'flowerOnly'
|
|
|
- && PsMethodClass::matchReduceRuleFree($psConfig, $distanceMeters, $itemBigNum, $goodsOnlyPrice)) {
|
|
|
- // C:纯花材命中满减 → 免跑腿
|
|
|
- $sendCost = 0;
|
|
|
- $freightSource = 'reduceFree';
|
|
|
- } else {
|
|
|
- // B 或不免满减的 C:按 calcType 跑腿或自定义;跑腿异常最终自定义兜底
|
|
|
- if ($calcType === 1) {
|
|
|
- try {
|
|
|
- $sendCost = PsMethodClass::calcCustomFreightByDistance($psConfig, $distanceKm > 0 ? $distanceKm : ($distanceMeters / 1000));
|
|
|
- $freightSource = 'custom';
|
|
|
- } catch (\Exception $e) {
|
|
|
- util::fail($e->getMessage());
|
|
|
- }
|
|
|
- } else {
|
|
|
- $runnerOk = false;
|
|
|
- if ((int)$openIntraCity === 1 && !empty($post['deliveryPlatform'])) {
|
|
|
- $prefix = 'PT-MIX-' . $this->mainId . '-';
|
|
|
- $orderSn = $prefix . round(microtime(true) * 1000);
|
|
|
- $ghsInfo = ['mainId' => $this->mainId, 'shopId' => $this->shopId];
|
|
|
- try {
|
|
|
- $quoteResult = DeliveryQuoteUtil::getDeliveryQuote([
|
|
|
- 'productList' => $resolvedProduct,
|
|
|
- 'deliveryPlatform' => $post['deliveryPlatform'],
|
|
|
- 'deliveryBracketContent' => $post['deliveryBracketContent'] ?? '',
|
|
|
- 'ghsInfo' => $ghsInfo,
|
|
|
- 'custom' => $quoteCustom,
|
|
|
- 'order' => [
|
|
|
- 'orderSn' => $orderSn,
|
|
|
- 'goodsType' => $quoteGoodsType,
|
|
|
- 'itemTotalAmount' => $goodsOnlyPrice,
|
|
|
- 'remark' => $post['remark'] ?? '',
|
|
|
- ],
|
|
|
- 'mainId' => $this->mainId,
|
|
|
- 'productCount' => $totalNum,
|
|
|
- 'shConfig' => $psConfig,
|
|
|
- ]);
|
|
|
- $sendCost = $quoteResult['sendCost'];
|
|
|
- $distanceMeters = (float)($quoteResult['sendDistance'] ?? $distanceMeters);
|
|
|
- $freightSource = 'platform';
|
|
|
- $runnerOk = true;
|
|
|
- } catch (\Exception $e) {
|
|
|
- $runnerOk = false;
|
|
|
- noticeUtil::push('商城混合单跑腿报价异常,改走自定义:' . $e->getMessage());
|
|
|
- }
|
|
|
- }
|
|
|
- if (!$runnerOk) {
|
|
|
- try {
|
|
|
- $sendCost = PsMethodClass::calcCustomFreightByDistance(
|
|
|
- $psConfig,
|
|
|
- $distanceKm > 0 ? $distanceKm : ($distanceMeters / 1000)
|
|
|
- );
|
|
|
- $freightSource = 'customFallback';
|
|
|
- } catch (\Exception $e) {
|
|
|
- util::fail('跑腿计算异常且自定义计费不可用,请更换配送方式');
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
+ $runnerResult = PsMethodClass::calcMixRunnerFreight(
|
|
|
+ $shop,
|
|
|
+ $this->shopId,
|
|
|
+ $this->mainId,
|
|
|
+ $post,
|
|
|
+ $resolvedProduct,
|
|
|
+ $itemBigNum,
|
|
|
+ $goodsOnlyPrice,
|
|
|
+ $openIntraCity,
|
|
|
+ $quoteGoodsType,
|
|
|
+ $totalNum,
|
|
|
+ 'PT-MIX-',
|
|
|
+ '混合单'
|
|
|
+ );
|
|
|
+ $sendCost = $runnerResult['sendCost'];
|
|
|
$post['sendCost'] = $sendCost;
|
|
|
- $post['sendDistance'] = $distanceMeters;
|
|
|
- $post['freightSource'] = $freightSource;
|
|
|
+ $post['sendDistance'] = $runnerResult['sendDistance'];
|
|
|
+ $post['freightSource'] = $runnerResult['freightSource'];
|
|
|
$modifyPrice = bcadd($modifyPrice, $sendCost, 2);
|
|
|
$runnerSendCost = $sendCost;
|
|
|
}
|
|
|
@@ -2103,6 +2070,48 @@ class OrderController extends BaseController
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 库存不足:以 code=-1 返回商品标识、剩余库存与超出数量。
|
|
|
+ * 与限购超限同形态,供混合结算页/购物车红框高亮对应商品并提示超出多少。
|
|
|
+ *
|
|
|
+ * @param array $opts productId/goodsId/specGoodsId/property/activityType/stock/num
|
|
|
+ */
|
|
|
+ protected function failStockExceed($opts)
|
|
|
+ {
|
|
|
+ $productId = intval($opts['productId'] ?? 0);
|
|
|
+ $goodsId = intval($opts['goodsId'] ?? 0);
|
|
|
+ $specGoodsId = intval($opts['specGoodsId'] ?? 0);
|
|
|
+ $property = intval($opts['property'] ?? 0);
|
|
|
+ $activityType = strval($opts['activityType'] ?? '');
|
|
|
+ $stock = floatval($opts['stock'] ?? 0);
|
|
|
+ $num = floatval($opts['num'] ?? 0);
|
|
|
+ if (bccomp($stock, '0', 2) < 0) {
|
|
|
+ $stock = 0;
|
|
|
+ }
|
|
|
+ $exceed = bcsub($num, $stock, 2);
|
|
|
+ if (bccomp($exceed, '0', 2) < 0) {
|
|
|
+ $exceed = '0';
|
|
|
+ }
|
|
|
+ $stockShow = rtrim(rtrim(sprintf('%.2f', $stock), '0'), '.');
|
|
|
+ if ($stockShow === '') {
|
|
|
+ $stockShow = '0';
|
|
|
+ }
|
|
|
+ $msg = $activityType === 'seckill'
|
|
|
+ ? "秒杀商品库存不足,剩余{$stockShow}"
|
|
|
+ : "商品库存不足,剩余{$stockShow}";
|
|
|
+ util::error(-1, $msg, [
|
|
|
+ 'productId' => $productId,
|
|
|
+ 'goodsId' => $goodsId,
|
|
|
+ 'specGoodsId' => $specGoodsId,
|
|
|
+ 'property' => $property,
|
|
|
+ 'activityType' => $activityType,
|
|
|
+ 'reason' => 'stock',
|
|
|
+ 'stock' => floatval($stock),
|
|
|
+ 'num' => $num,
|
|
|
+ 'exceed' => floatval($exceed),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
//获取详情 ssh 20220512
|
|
|
public function actionInfo()
|
|
|
{
|