456 lines
17 KiB
PHP
456 lines
17 KiB
PHP
<?php
|
|
/**
|
|
* This service file is part of item.
|
|
*
|
|
* @author ctexthuang
|
|
* @contact ctexthuang@qq.com
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Service\ServiceTrait\Api;
|
|
|
|
use App\Amqp\Producer\OrderGoodStockProducer;
|
|
use App\Cache\Redis\Api\ApiRedisKey;
|
|
use App\Cache\Redis\Api\SiteCache;
|
|
use App\Cache\Redis\RedisCache;
|
|
use App\Constants\ApiCode;
|
|
use App\Constants\Common\CouponCode;
|
|
use App\Constants\Common\GoodCode;
|
|
use App\Constants\Common\OrderCode;
|
|
use App\Constants\Common\PayCode;
|
|
use App\Constants\Common\RefundCode;
|
|
use App\Constants\ConfigCode;
|
|
use App\Exception\ErrException;
|
|
use App\Model\Order;
|
|
use App\Model\PayOrder;
|
|
use Exception;
|
|
use Hyperf\Amqp\Producer;
|
|
use Hyperf\Context\ApplicationContext;
|
|
use Hyperf\Di\Annotation\Inject;
|
|
use Psr\Container\ContainerExceptionInterface;
|
|
use Psr\Container\NotFoundExceptionInterface;
|
|
|
|
trait OrderTrait
|
|
{
|
|
/**
|
|
* @var SiteCache
|
|
*/
|
|
#[Inject]
|
|
protected SiteCache $siteCache;
|
|
|
|
/**
|
|
* @var RedisCache $redis
|
|
*/
|
|
#[Inject]
|
|
protected RedisCache $redis;
|
|
|
|
/**
|
|
* 生成订单号
|
|
* @param int $type
|
|
* @param int $userId
|
|
* @return string
|
|
*/
|
|
protected function generateOrderNo(int $type,int $userId): string
|
|
{
|
|
return match ($type) {
|
|
OrderCode::ORDER_TYPE_GOOD => OrderCode::ORDER_TYPE_GOOD_PREFIX . '_' .date("YmdHis") . $userId . mt_rand(10000000, 99999999),
|
|
OrderCode::ORDER_TYPE_BALANCE => OrderCode::ORDER_TYPE_BALANCE_PREFIX. '_' .date("YmdHis"). $userId. mt_rand(10000000, 99999999),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param int $type
|
|
* @param int $userId
|
|
* @return string
|
|
*/
|
|
protected function generateRefundOrderNo(int $type,int $userId): string
|
|
{
|
|
return match ($type) {
|
|
OrderCode::ORDER_TYPE_GOOD => RefundCode::ORDER_TYPE_GOOD_PREFIX . '_' .date("YmdHis") . $userId . mt_rand(10000000, 99999999),
|
|
OrderCode::ORDER_TYPE_BALANCE => RefundCode::ORDER_TYPE_BALANCE_PREFIX. '_' .date("YmdHis"). $userId. mt_rand(10000000, 99999999),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 生成购物车数据
|
|
* @param array $data
|
|
* @return void
|
|
*/
|
|
protected function buildCartData(array $data): void
|
|
{
|
|
$copies = 0;
|
|
foreach ($data as $oneCopies) {
|
|
$one = [];
|
|
foreach ($oneCopies as $oneGood) {
|
|
$this->cartFirstData[$oneGood] = ($this->cartFirstData[$oneGood] ?? 0) + 1;
|
|
$one[$oneGood] = ($one[$oneGood] ?? 0) + 1;
|
|
}
|
|
|
|
$this->cartSecondData[] = $one;
|
|
|
|
$copies++;
|
|
}
|
|
|
|
$this->copies = $copies;
|
|
}
|
|
|
|
/**
|
|
* 检测商品
|
|
* @return void
|
|
* @throws ContainerExceptionInterface
|
|
* @throws NotFoundExceptionInterface
|
|
*/
|
|
protected function checkGood(): void
|
|
{
|
|
foreach ($this->cartFirstData as $key => $one) {
|
|
if (!in_array($key, $this->goodIds)) throw new ErrException('商品不存在',ApiCode::ORDER_GOOD_IN_EXISTENCE);
|
|
|
|
if ($this->orderType == 0) $this->orderType = $this->skuArr[$key]['type'];
|
|
if ($this->skuArr[$key]['type'] != $this->orderType) throw new ErrException('自选菜品跟套餐菜品请分开订单下单');
|
|
|
|
if ($this->orderType != OrderCode::ORDER_TYPE_MEAL) return;
|
|
|
|
if ($this->isMealCateringByCache($this->siteId, $key, $this->cycleId)) throw new ErrException('该点位的套餐'.$this->skuArr[$key]['title'].'已配餐截单,请选择其他套餐');
|
|
|
|
if ($this->checkSkuMeal($key, $this->cycleId)) throw new ErrException('该套餐'.$this->skuArr[$key]['title'].'已配餐截单,请选择其他套餐');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 检测库存
|
|
* @return void
|
|
* @throws ContainerExceptionInterface
|
|
* @throws NotFoundExceptionInterface
|
|
*/
|
|
protected function checkStock(): void
|
|
{
|
|
foreach ($this->cartFirstData as $key => $one) {
|
|
if (($this->redis->zScore($this->stockKey,$key) ?? 0) >= $one) continue;
|
|
|
|
throw new ErrException('商品库存不足',ApiCode::ORDER_GOOD_INSUFFICIENT_STOCK);
|
|
}
|
|
}
|
|
|
|
protected function buildCartStock()
|
|
{
|
|
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
* @throws ContainerExceptionInterface
|
|
* @throws NotFoundExceptionInterface
|
|
*/
|
|
protected function checkTodayCutOffTime(): void
|
|
{
|
|
$todayCutOffTime = $this->configCache->getConfigValue(ConfigCode::TODAY_CUT_OFF_TIME_KEY);
|
|
$newCycleTime = $this->configCache->getConfigValue(ConfigCode::START_OF_THE_NEW_CYCLE);
|
|
|
|
if (date('H:i:s') >= $todayCutOffTime && date('H:i:s') < $newCycleTime) throw new ErrException('平台已全部截止下单哦');
|
|
}
|
|
|
|
/**
|
|
* 检测地点
|
|
* @param int $siteId
|
|
* @return int
|
|
* @throws ContainerExceptionInterface
|
|
* @throws NotFoundExceptionInterface
|
|
*/
|
|
protected function checkSite(int $siteId): int
|
|
{
|
|
$siteInfo = $this->siteCache->getSiteInfo($siteId);
|
|
if (empty($siteInfo)) throw new ErrException('该地点暂时不支持点餐');
|
|
|
|
$kitchenId = (int)($siteInfo['kitchen_id'] ?? 0);
|
|
if ($kitchenId <= 0) throw new ErrException('该地点暂时不支持点餐');
|
|
|
|
$closeOrderFlag = match ($this->orderType) {
|
|
OrderCode::ORDER_TYPE_OPTIONAL => $this->checkIsStopOrder($siteId, $this->cycleId),
|
|
OrderCode::ORDER_TYPE_MEAL => $this->checkMealIsStopOrder($siteId, $this->cycleId),
|
|
};
|
|
|
|
if ($closeOrderFlag) throw new ErrException('该点已经截单');
|
|
|
|
$this->orderRes['site_id'] = $siteId;
|
|
$this->orderRes['site_info'] = $siteInfo;
|
|
|
|
return $kitchenId;
|
|
}
|
|
|
|
/**
|
|
* 获取商品信息
|
|
* @param int $kitchenId
|
|
* @return void
|
|
* @throws ContainerExceptionInterface
|
|
* @throws NotFoundExceptionInterface
|
|
*/
|
|
protected function getGoodInfo(int $kitchenId): void
|
|
{
|
|
$this->stockKey = ApiRedisKey::goodStockKey($this->cycleId,$kitchenId);
|
|
if (!$this->redis->exists($this->stockKey)) throw new ErrException('库存不足');
|
|
|
|
$skuArr = [];
|
|
// $skuImageArr = [];
|
|
switch ($this->orderType) {
|
|
case OrderCode::ORDER_TYPE_OPTIONAL:
|
|
$optionalGoodKey = ApiRedisKey::optionalGoodListKey($this->cycleId,$kitchenId);
|
|
$optionalGood = $this->redis->get($optionalGoodKey);
|
|
if (empty($optionalGood)) throw new ErrException('商品不存在');
|
|
|
|
$optionalGood = json_decode($optionalGood, true);
|
|
|
|
foreach ($optionalGood as $one){
|
|
$newSkuList = array_map(function($sku) use($one) {
|
|
$sku['spu_title'] = strtolower($one['title']);
|
|
$sku['type'] = GoodCode::SPU_TYPE_OPTIONAL;
|
|
return $sku;
|
|
}, $one['sku_list']);
|
|
//
|
|
// $skuImageArr = array_merge($skuImageArr,[
|
|
// $one['id'] => $one['url']
|
|
// ]);
|
|
$skuArr = array_merge($skuArr,$newSkuList);
|
|
}
|
|
|
|
break;
|
|
case OrderCode::ORDER_TYPE_MEAL:
|
|
$mealGoodKey = ApiRedisKey::mealGoodListKey($this->cycleId,$kitchenId);
|
|
$mealGood = $this->redis->get($mealGoodKey);
|
|
if (empty($mealGood)) throw new ErrException('商品不存在');
|
|
|
|
$mealGood = json_decode($mealGood, true);
|
|
|
|
foreach ($mealGood as $one){
|
|
$newSkuList = array_map(function($sku) use($one) {
|
|
$sku['spu_title'] = $one['title'];
|
|
$sku['type'] = GoodCode::SPU_TYPE_MEAL;
|
|
return $sku;
|
|
}, $one['sku_list']);
|
|
|
|
// $skuImageArr = array_merge($skuImageArr,[
|
|
// $one['id'] => $one['url']
|
|
// ]);
|
|
// $skuImageArr = array_merge($skuImageArr,$one['image_list']);
|
|
$skuArr = array_merge($skuArr,$newSkuList);
|
|
}
|
|
|
|
break;
|
|
default:
|
|
throw new ErrException('还不支持下单');
|
|
}
|
|
|
|
$this->skuArr = array_column($skuArr,null,'id');
|
|
// $this->skuImageArr = array_column($skuArr,null,'id');
|
|
$this->goodIds = array_column($skuArr,'id');
|
|
|
|
unset($skuArr);
|
|
}
|
|
|
|
/**
|
|
* @return int
|
|
*/
|
|
protected function getAutoCouponId(): int
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* 计算价格
|
|
* @return void
|
|
*/
|
|
protected function computePrice(): void
|
|
{
|
|
$this->orderRes['good_ids'] = $this->cartSecondData;
|
|
// var_dump($this->skuArr);
|
|
foreach ($this->cartSecondData as $oneCopies) {
|
|
$oneCopiesTotalPrice = '0.00';
|
|
|
|
// $copiesType = 1;
|
|
|
|
$oneCopiesGoodInfo = [];
|
|
|
|
foreach ($oneCopies as $key => $oneGood) {
|
|
if (empty($oneCopiesGoodInfo[$key])) {
|
|
$oneCopiesGoodInfo[$key] = [
|
|
'num' => $oneGood,
|
|
'good_name' => ($this->skuArr[$key]['spu_title'] ?? '').'-'.($this->skuArr[$key]['title'] ?? ''),
|
|
'good_url' => $this->skuArr[$key]['url'] ?? '',
|
|
'unit_price' => $this->skuArr[$key]['price'],
|
|
'type' => $this->skuArr[$key]['type'],
|
|
'id' => $key,
|
|
'spu_id' => $this->skuArr[$key]['spu_id'],
|
|
'is_add_staple_food' => $this->skuArr[$key]['is_add_staple_food'],
|
|
'meal_copies' => $this->skuArr[$key]['is_add_staple_food'] == GoodCode::IS_ADD_STAPLE_FOOD ? 0 : 1,
|
|
];
|
|
} else {
|
|
$oneCopiesGoodInfo[$key]['num'] += $oneGood;
|
|
}
|
|
|
|
$oneCopiesTotalPrice = bcadd($oneCopiesTotalPrice, bcmul($this->skuArr[$key]['price'],(string)$oneGood,2),2);
|
|
}
|
|
|
|
$this->orderRes['good'][] = [
|
|
'good_info' => array_values($oneCopiesGoodInfo),
|
|
'price' => $oneCopiesTotalPrice,
|
|
];
|
|
|
|
// 加一份米饭不算份数
|
|
switch ($this->orderType) {
|
|
case OrderCode::ORDER_TYPE_OPTIONAL:
|
|
$this->orderRes['copies']++;
|
|
break;
|
|
case OrderCode::ORDER_TYPE_MEAL:
|
|
$this->orderRes['copies'] = $this->orderRes['copies'] + array_sum(array_column($oneCopiesGoodInfo,'meal_copies'));
|
|
break;
|
|
}
|
|
// if ($this->skuArr[$key]['is_add_staple_food'] != GoodCode::IS_ADD_STAPLE_FOOD) $this->orderRes['copies']++;
|
|
|
|
$this->orderRes['total_good_price'] = bcadd($this->orderRes['total_good_price'],$oneCopiesTotalPrice,2);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 计算服务费
|
|
* @return void
|
|
* @throws ContainerExceptionInterface
|
|
* @throws NotFoundExceptionInterface
|
|
*/
|
|
protected function computeSundryPrice(): void
|
|
{
|
|
$this->orderRes['sundry_num'] = match ((int)$this->configCache->getConfigValueByKey(ConfigCode::SUNDRY_PRICE_COMPUTE_TYPE))
|
|
{
|
|
1 => $this->orderType == OrderCode::ORDER_TYPE_OPTIONAL ? $this->copies : 0,
|
|
2 => $this->orderType == OrderCode::ORDER_TYPE_MEAL ? $this->copies : 0,
|
|
3 => $this->copies,
|
|
};
|
|
|
|
$this->orderRes['sundry_price'] = $this->configCache->getConfigValueByKey(ConfigCode::SUNDRY_UNIT_PRICE);
|
|
$this->orderRes['total_sundry_price'] = bcmul($this->orderRes['sundry_price'],(string)$this->orderRes['sundry_num'],2);
|
|
}
|
|
|
|
// /**
|
|
// * 计算优惠
|
|
// * @return void
|
|
// */
|
|
// protected function computeFavorable(): void
|
|
// {
|
|
// if ($this->couponId <= 0 && $this->isAutoSelectCoupon == 1) {
|
|
// $this->couponId = $this->getAutoCouponId();
|
|
// }
|
|
//
|
|
// if ($this->couponId <= 0) {
|
|
// $this->orderRes['coupon_id'] = 0;
|
|
// $this->orderRes['coupon'] = [];
|
|
// $this->orderRes['favorable_good_price'] = '0';
|
|
// $this->orderRes['favorable_sundry_price'] = '0';
|
|
// return;
|
|
// }
|
|
//
|
|
// $couponInfo = []; // todo 优惠券信息
|
|
//
|
|
// $this->orderRes['coupon_id'] = $this->couponId;
|
|
// $this->orderRes['coupon'] = $couponInfo;
|
|
//
|
|
// //todo 优惠计算
|
|
// $this->orderRes['favorable_good_price'] = '1.00';
|
|
// $this->orderRes['favorable_sundry_price'] = '1.00';
|
|
// }
|
|
|
|
/**
|
|
* 计算最终价格
|
|
* @return void
|
|
*/
|
|
protected function computeFinallyPrice(): void
|
|
{
|
|
$this->orderRes['good_after_discount_price'] = bcsub($this->orderRes['total_good_price'],$this->orderRes['favorable_good_price'],2);
|
|
$this->orderRes['sundry_after_discount_price'] = bcsub($this->orderRes['total_sundry_price'],$this->orderRes['favorable_sundry_price'],2);
|
|
$this->orderRes['total_price'] = bcadd($this->orderRes['total_good_price'],$this->orderRes['total_sundry_price'],2);
|
|
$this->orderRes['actual_price'] = bcadd($this->orderRes['good_after_discount_price'],$this->orderRes['sundry_after_discount_price'],2);
|
|
}
|
|
|
|
/**
|
|
* @param int $orderId
|
|
* @param int $orderStatus
|
|
* @param array $refundGoodIds
|
|
* @return void
|
|
* @throws ContainerExceptionInterface
|
|
* @throws NotFoundExceptionInterface
|
|
*/
|
|
protected function sendStockMq(int $orderId,int $orderStatus,array $refundGoodIds = []): void
|
|
{
|
|
$message = new OrderGoodStockProducer([
|
|
'order_id' => $orderId,
|
|
'type' => $orderStatus,
|
|
'refund_goods' => $refundGoodIds
|
|
]);
|
|
$producer = ApplicationContext::getContainer()->get(Producer::class);
|
|
$producer->produce($message);
|
|
}
|
|
|
|
/**
|
|
* 重载计算
|
|
* @return void
|
|
*/
|
|
protected function reloadCompute(): void
|
|
{
|
|
if ($this->couponId <= 0 || empty($this->orderRes['coupon']) || $this->orderRes['coupon_id'] <= 0) return;
|
|
|
|
if ($this->orderRes['coupon']['amount'] <= 0 && $this->orderRes['coupon']['coupon_type'] != CouponCode::COUPON_TYPE_INSTANT_REDUCTION) {
|
|
// $orderMaxPrice = max(array_column($this->orderRes['good'],'price'));
|
|
$this->orderRes['coupon']['amount'] = match ($this->orderRes['coupon']['coupon_type']) {
|
|
CouponCode::COUPON_TYPE_INSTANT_REDUCTION => $this->orderRes['coupon']['amount'],
|
|
CouponCode::COUPON_TYPE_DISCOUNT => bcmul(
|
|
bcdiv(
|
|
bcsub(
|
|
"100",
|
|
(string)$this->orderRes['coupon']['ratio'],
|
|
2
|
|
),
|
|
"100",
|
|
2
|
|
),
|
|
bcadd((string)max(array_column($this->orderRes['good'],'price')),(string)$this->orderRes['sundry_price'],2),
|
|
2
|
|
),
|
|
default => 0
|
|
};
|
|
}
|
|
|
|
// $this->orderRes['favorable_good_price'] = $this->orderRes['coupon']['coupon_type'] == CouponCode::COUPON_TYPE_INSTANT_REDUCTION ?
|
|
// min($this->orderRes['coupon']['amount'], $this->orderRes['good_after_discount_price']) :
|
|
// $this->orderRes['coupon']['amount'];
|
|
// $this->orderRes['good_after_discount_price'] = bcsub((string)$this->orderRes['good_after_discount_price'],(string)$this->orderRes['favorable_good_price'],2);
|
|
$this->orderRes['discount_price'] = min($this->orderRes['coupon']['amount'],$this->orderRes['actual_price']);
|
|
$this->orderRes['actual_price'] = bcsub((string)$this->orderRes['actual_price'],(string)$this->orderRes['discount_price'],2);
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
protected function getCouponInfo(): void
|
|
{
|
|
if ($this->couponId <= 0) return;
|
|
|
|
$couponInfo = $this->userCouponModel->where('id', $this->couponId)->where('user_id',$this->userId)->first();
|
|
if (empty($couponInfo)) throw new ErrException('优惠券信息错误');
|
|
if (in_array($couponInfo->status,[CouponCode::COUPON_STATUS_USED,CouponCode::COUPON_STATUS_EXPIRE])) throw new ErrException('优惠券已经被使用');
|
|
|
|
$couponTemplateInfo = $this->couponTemplateModel->getInfoById($couponInfo->coupon_template_id);
|
|
if (empty($couponTemplateInfo)) throw new ErrException('优惠券信息错误');
|
|
|
|
$this->couponId = $couponInfo->id;
|
|
$this->orderRes['coupon'] = [
|
|
'id' => $couponInfo->id,
|
|
'coupon_type' => $couponTemplateInfo->coupon_type,
|
|
'amount' => $couponTemplateInfo->amount,
|
|
'ratio' => $couponTemplateInfo->ratio,
|
|
'coupon_template_id' => $couponTemplateInfo->id,
|
|
'coupon_name' => $couponInfo->coupon_name,
|
|
'status' => $couponInfo->status,
|
|
'validity_start_time' => $couponInfo->validity_start_time,
|
|
'validity_end_time' => $couponInfo->validity_end_time,
|
|
];
|
|
$this->orderRes['coupon_id'] = $couponInfo->id;
|
|
}
|
|
} |