// +---------------------------------------------------------------------- declare (strict_types=1); namespace app\api\model\groupon; use app\api\service\User as UserService; use app\api\model\Goods as GoodsModel; use app\api\model\groupon\Task as TaskModel; use app\api\model\groupon\GoodsSku as GrouponGoodsSkuModel; use app\common\model\groupon\Goods as GrouponGoodsModel; use app\common\enum\groupon\ActiveType as ActiveTypeEnum; use app\common\enum\groupon\GoodsStatus as GoodsStatusEnum; use app\common\library\helper; use cores\exception\BaseException; /** * 拼团商品模型 * Class Goods * @package app\api\model\groupon */ class Goods extends GrouponGoodsModel { /** * 隐藏字段 * @var array */ protected $hidden = [ 'is_mock_task', 'mock_min_people', 'initial_sales', 'actual_sales', 'sort', 'group_task_num', 'store_id', 'create_time', 'update_time', ]; /** * 获取拼团活动列表(根据商品ID集) * @param array $grouponGoodsIds * @param array $param * @return mixed|\think\Paginator * @throws \think\Exception * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ public function getListByIds(array $grouponGoodsIds, array $param = []) { return $this->getList(array_merge($param, ['grouponGoodsIds' => $grouponGoodsIds])); } /** * 获取拼团商品列表 * @param array $param * @return mixed|\think\Paginator * @throws \think\Exception * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ public function getList(array $param) { // 查询条件 $params = $this->setQueryDefaultValue($param, [ 'grouponGoodsIds' => [], // 拼团商品ID集 'status' => 10, // 商品状态 'sortType' => 'all', // 排序类型 'sortPrice' => false, // 价格排序 高低 'listRows' => 15, // 每页数量 ]); // 查询模型 $query = $this->getNewQuery(); // 根据活动ID集 if (is_array($params['grouponGoodsIds']) && !empty($params['grouponGoodsIds'])) { $query->orderRaw('field(groupon_goods_id, ' . implode(',', $params['grouponGoodsIds']) . ')'); $query->where('groupon_goods_id', 'in', $params['grouponGoodsIds']); } // 排序规则 if ($params['sortType'] === 'all') { $query->order(['sort' => 'asc']); } elseif ($params['sortType'] === 'sales') { $query->order(['active_sales' => 'desc']); } elseif ($params['sortType'] === 'price') { $query->order(['groupon_price_min' => $params['sortPrice'] ? 'desc' : 'asc']); } // 拼团活动列表 $list = $query->field(['*', '(actual_sales + initial_sales) as active_sales']) ->where('start_time', '<=', time()) ->where('end_time', '>=', time()) ->where('status', '=', $params['status']) ->where('is_delete', '=', 0) ->order(['sort' => 'asc']) ->paginate($params['listRows']); // 设置商品数据 if (!$list->isEmpty()) { $list = $this->setGoodsListData($list, true, GoodsModel::getHidden(['goods_images'])); } return $list; } /** * 获取拼团商品详情 (包含主商品信息, 用于页面详情) * @param int $grouponGoodsId 拼团商品ID * @return GoodsModel * @throws BaseException * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ public function getGoodsDetail(int $grouponGoodsId): GoodsModel { // 获取拼团商品详情 $grouponGoods = static::detail($grouponGoodsId, ['skuList']); if (empty($grouponGoods) || $grouponGoods['is_delete'] || $grouponGoods['status'] == GoodsStatusEnum::FINISH) { throwError('很抱歉,拼团商品不存在或已下架'); } // 获取主商品详情 $goods = (new GoodsModel)->getDetails($grouponGoods['goods_id'], false); // 整理拼团商品信息 $goods = $this->mergeMainGoods($grouponGoods, $goods); // 商品sku信息 $goods['skuList'] = $this->getGrouponSku($grouponGoods['skuList'], $goods['skuList']); // 当前商品的可加入的凑团列表 $goods['taskQuickJoinList'] = TaskModel::getQuickJoinList($grouponGoodsId, 2); return $goods; } /** * 获取拼团商品基本信息 * @param int $grouponGoodsId * @return GoodsModel * @throws BaseException */ public function getGoodsBasic(int $grouponGoodsId): GoodsModel { // 获取拼团商品详情 $grouponGoods = static::detail($grouponGoodsId); if (empty($grouponGoods) || $grouponGoods['is_delete'] || $grouponGoods['status'] == GoodsStatusEnum::FINISH) { throwError('很抱歉,拼团商品不存在或已下架'); } // 获取主商品详情 $goods = (new GoodsModel)->getBasic($grouponGoods['goods_id'], false); // 隐藏冗余的属性 $goods->hidden(GoodsModel::getHidden(['content', 'goods_images'])); // 整理拼团商品信息 return $this->mergeMainGoods($grouponGoods, $goods); } /** * 合并拼团商品信息到主商品 * @param GrouponGoodsModel $grouponGoods 拼团商品详情 * @param GoodsModel $goods 主商品详情 * @return GoodsModel */ private function mergeMainGoods(GrouponGoodsModel $grouponGoods, GoodsModel $goods): GoodsModel { $diffPrice = helper::bcsub($goods['goods_price_min'], $grouponGoods['groupon_price_min']); $goods['original_price'] = $goods['goods_price_min']; $goods['groupon_goods_id'] = $grouponGoods['groupon_goods_id']; $goods['active_type'] = $grouponGoods['active_type']; $goods['start_time'] = $grouponGoods['start_time']; $goods['end_time'] = $grouponGoods['end_time']; $goods['groupon_price'] = $grouponGoods['groupon_price_min']; $goods['is_alone_buy'] = $grouponGoods['is_alone_buy']; $goods['active_status'] = $grouponGoods['active_status']; $goods['active_sales'] = $grouponGoods['active_sales']; $goods['is_restrict'] = $grouponGoods['is_restrict']; $goods['restrict_single'] = $grouponGoods['restrict_single']; $goods['restrict_total'] = $grouponGoods['restrict_total']; $goods['steps_config'] = $grouponGoods['steps_config']; $goods['diff_price'] = helper::number2(max($diffPrice, 0)); return $goods; } /** * 获取拼团商品的sku信息 * @param $grouponSku * @param $goodsSku * @return mixed */ protected function getGrouponSku($grouponSku, $goodsSku) { $grouponSku = helper::arrayColumn2Key($grouponSku, 'goods_sku_id'); foreach ($goodsSku as &$item) { $grouponSkuItem = clone $grouponSku[$item['goods_sku_id']]; $item['original_price'] = $item['goods_price']; $item['groupon_price'] = $grouponSkuItem['groupon_price']; $item['steps_price_config'] = $grouponSkuItem['steps_price_config']; } return $goodsSku; } /** * 获取订单提交的商品列表 * @param int $grouponGoodsId 拼团商品ID * @param string $goodsSkuId 商品skuID * @param int $goodsNum 购买数量 * @param int $taskId 拼单ID (参团时传参) * @param int $stepPeople 拼团人数 (仅阶梯团时传入) * @return array * @throws BaseException */ public function getCheckoutGoodsList(int $grouponGoodsId, string $goodsSkuId, int $goodsNum, int $taskId, int $stepPeople): array { if ($taskId > 0) { // 获取当前拼单详情 $taskInfo = TaskModel::getBasic($taskId); // 验证当前拼单是否允许加入新成员 $this->checkToJoinTask($taskInfo); $stepPeople = $taskInfo['people']; } // 拼团商品详情 $grouponGoods = $this->getGoodsBasic($grouponGoodsId); // 商品sku信息 $grouponGoods['skuInfo'] = GrouponGoodsSkuModel::getSkuInfo($grouponGoods['goods_id'], $grouponGoodsId, $goodsSkuId); // 商品封面 (优先sku封面) $grouponGoods['goods_image'] = $grouponGoods['skuInfo']['goods_image'] ?: $grouponGoods['goods_image']; // 商品列表 (隐藏用不到的属性) $goodsList = [$grouponGoods->hidden(GoodsModel::getHidden(['content', 'images', 'goods_images']))]; foreach ($goodsList as &$item) { // 商品价格 $item['goods_price'] = $this->getSkuGrouponPrice($item, $stepPeople); $item['line_price'] = $item['original_price']; // 记录拼团商品ID $item['goods_sku_id'] = $item['skuInfo']['goods_sku_id']; $item['goods_source_id'] = $item['groupon_goods_id']; // 商品购买数量 $item['total_num'] = $goodsNum; // 商品购买总金额 $item['total_price'] = helper::bcmul($item['goods_price'], $goodsNum); } return $goodsList; } /** * 获取实际的拼团商品SKU价格 * @param $grouponGoods * @param int $stepPeople 拼团人数 (仅阶梯团时传入) * @return mixed * @throws BaseException */ private function getSkuGrouponPrice($grouponGoods, int $stepPeople) { if ($grouponGoods['active_type'] != ActiveTypeEnum::STEPS) { return $grouponGoods['skuInfo']['groupon_price']; } $stepsIndex = array_search($stepPeople, $grouponGoods['steps_config']); $stepsIndex === false && throwError('很抱歉,阶梯团人数不正确'); if (!array_key_exists($stepsIndex, $grouponGoods['skuInfo']['steps_price_config'])) { throwError('很抱歉,未找到阶梯团价格'); } return $grouponGoods['skuInfo']['steps_price_config'][$stepsIndex]; } /** * 验证当前拼单是否允许加入新成员 * @param $taskInfo * @return void * @throws BaseException */ private function checkToJoinTask($taskInfo): void { $model = new TaskModel; $userId = UserService::getCurrentLoginUserId(true); if (!empty($userId) && !$model->checkToJoin($taskInfo, $userId)) { throwError($model->getError()); } } /** * 累计拼团商品销量 (实际) * @return mixed */ public static function setIncSales(int $grouponGoodsId) { return (new static)->setInc($grouponGoodsId, 'actual_sales', 1); } }