// +---------------------------------------------------------------------- declare (strict_types=1); namespace app\api\model\bargain; use app\api\model\Goods as GoodsModel; use app\api\model\bargain\Active as ActiveModel; use app\api\model\bargain\TaskHelp as TaskHelpModel; use app\api\service\User as UserService; use app\api\service\Goods as GoodsService; use app\api\service\bargain\Amount as AmountService; use app\common\model\bargain\Task as TaskModel; use app\common\library\helper; use cores\exception\BaseException; use think\db\exception\DataNotFoundException; use think\db\exception\DbException; use think\db\exception\ModelNotFoundException; /** * 砍价任务模型 * Class Task * @package app\api\model\bargain */ class Task extends TaskModel { /** * 隐藏的字段 * @var array */ protected $hidden = [ 'peoples', 'section', 'is_delete', 'store_id', 'create_time', 'update_time', ]; /** * 我的砍价列表 * @return mixed|\think\Paginator * @throws \cores\exception\BaseException * @throws \think\db\exception\DbException */ public function getMyList() { // 当前登录用户ID $userId = UserService::getCurrentLoginUserId(); // 砍价活动列表 $list = $this->where('user_id', '=', $userId) ->where('is_delete', '=', 0) ->order(['create_time' => 'desc']) ->paginate(15); // 设置商品数据 return !$list->isEmpty() ? GoodsService::setGoodsData($list, true, ['goods_images']) : $list; } /** * 获取砍价任务详情 * @param int $taskId * @return static|null * @throws BaseException */ public function getDetail(int $taskId): ?Task { // 砍价任务详情 $detail = static::detail($taskId, ['user.avatar']); empty($detail) && throwError('砍价任务不存在'); return $detail; } /** * 获取砍价任务的商品列表(用于订单结算) * @param int $taskId 砍价任务ID * @return array * @throws BaseException */ public static function getTaskGoods(int $taskId): array { // 获取砍价任务详情 $taskInfo = static::detail($taskId); // 验证砍价任务状态(是否允许购买) self::checkTaskStatus($taskInfo); // 获取砍价活动详情 $activeInfo = (new ActiveModel)->getDetail($taskInfo['active_id']); // 获取商品详情 $goods = (new GoodsModel)->getBasic($taskInfo['goods_id'], false); // 获取商品sku信息 $goods['skuInfo'] = GoodsService::getSkuInfo($taskInfo['goods_id'], $taskInfo['goods_sku_id']); // 商品封面 (优先sku封面) $goods['goods_image'] = $goods['skuInfo']['goods_image'] ?: $goods['goods_image']; // 商品列表 (隐藏用不到的属性) $goodsList = [$goods->hidden(GoodsModel::getHidden(['content', 'images', 'goods_images']))]; foreach ($goodsList as &$item) { // 商品单价 $item['goods_price'] = $taskInfo['actual_price']; // 商品购买数量 $item['total_num'] = 1; $item['goods_sku_id'] = $item['skuInfo']['goods_sku_id']; // 商品购买总金额 $item['total_price'] = $taskInfo['actual_price']; // 砍价商品限购信息 $goods['is_restrict'] = $activeInfo['is_restrict']; $goods['restrict_total'] = $activeInfo['restrict_total']; } return $goodsList; } /** * 验证砍价任务状态(是否允许购买) * @param self $taskInfo * @return bool * @throws BaseException */ public static function checkTaskStatus(self $taskInfo): bool { if (empty($taskInfo) || $taskInfo['is_delete'] || !$taskInfo['status']) { throwError('砍价任务不存在或已结束'); } if ($taskInfo['is_buy']) { throwError('该砍价商品已购买'); } return true; } /** * 订单创建后将砍价任务结束 * @param int $taskId 砍价任务ID * @return int|bool */ public static function setTaskEnd(int $taskId) { // ps: 这里的save方法实际返回值是int, 所以不能强制类型bool return (new static)->where('task_id', '=', $taskId)->save(['status' => 0]); } /** * 获取用户是否正在参与改砍价活动,如果已参与则返回task_id * @param int $activeId * @param int $userId * @return bool|int */ public static function getHandByUser(int $activeId, int $userId) { $taskId = (new static)->where('active_id', '=', $activeId) ->where('user_id', '=', $userId) ->where('end_time', '>', time()) ->where('status', '=', 1) ->where('is_delete', '=', 0) ->value('task_id'); return $taskId ?: false; } /** * 新增砍价任务 * @param int $activeId * @param string $goodsSkuId * @return false|mixed * @throws BaseException */ public function partake(int $activeId, string $goodsSkuId) { // 当前用户ID $userId = UserService::getCurrentLoginUserId(); // 获取活动详情 $active = $this->getActiveDetail($activeId); // 验证能否创建砍价任务 if (!$this->onVerify($active, $userId)) { return false; } // 获取商品详情 $goods = GoodsModel::detail($active['goods_id']); // 商品sku信息 $goods['skuInfo'] = GoodsService::getSkuInfo($goods['goods_id'], $goodsSkuId); // 事务处理 return $this->transaction(function () use ($userId, $active, $goodsSkuId, $goods) { // 创建砍价任务 $this->add($userId, $active, $goodsSkuId, $goods); // 发起人自砍一刀 $active['is_self_cut'] && $this->onCutEvent($userId, true); return true; }); } /** * 帮砍一刀 * @return false|mixed * @throws BaseException * @throws DataNotFoundException * @throws DbException * @throws ModelNotFoundException */ public function helpCut() { // 当前用户ID $userId = UserService::getCurrentLoginUserId(); // 验证每日帮砍的次数限制 TaskHelpModel::checkHelpCutLimit($userId); // 当前是否已砍 if (TaskHelpModel::isCut((int)$this['task_id'])) { $this->error = '您已参与砍价,请不要重复操作'; return false; } // 帮砍一刀事件 return $this->transaction(function () use ($userId) { return $this->onCutEvent($userId, $this->isCreater($this)); }); } /** * 砍一刀的金额 * @return string */ public function getCutMoney(): string { return (string)$this['section'][$this['cut_people']]; } /** * 帮砍一刀事件 * @param int $userId * @param bool $isCreater * @return bool|false */ private function onCutEvent(int $userId, bool $isCreater = false): bool { // 砍价金额 $cutMoney = $this->getCutMoney(); // 砍价助力记录 $model = new TaskHelpModel; $model->add($this, $userId, $cutMoney, $isCreater); // 实际购买金额 $actualPrice = helper::bcsub($this['actual_price'], $cutMoney); // 更新砍价任务信息 return $this->save([ 'cut_people' => ['inc', 1], 'cut_money' => ['inc', $cutMoney], 'actual_price' => $actualPrice, 'is_floor' => helper::bcequal($actualPrice, $this['floor_price']), ]); } /** * 创建砍价任务记录 * @param int $userId * @param ActiveModel $active 砍价活动详情 * @param string $goodsSkuId 指定的商品sku * @param GoodsModel $goods 商品详情 * @return void * @throws \Exception */ private function add(int $userId, ActiveModel $active, string $goodsSkuId, GoodsModel $goods): void { // 分配砍价金额区间 $section = $this->calcBargainSection( $goods['skuInfo']['goods_price'], $active['floor_price'], $active['peoples'] ); // 新增记录 $this->save([ 'active_id' => $active['active_id'], 'user_id' => $userId, 'goods_id' => $active['goods_id'], 'goods_sku_id' => $goodsSkuId, 'goods_price' => $goods['skuInfo']['goods_price'], 'floor_price' => $active['floor_price'], 'peoples' => $active['peoples'], 'cut_people' => 0, 'section' => $section, 'cut_money' => 0.00, 'actual_price' => $goods['skuInfo']['goods_price'], 'end_time' => time() + ($active['expiryt_time'] * 3600), 'is_buy' => 0, 'status' => 1, 'store_id' => static::$storeId, ]); } /** * 砍价任务标记为已购买 * @return bool|false */ public function setIsBuy(): bool { return $this->save(['is_buy' => 1]); } /** * 分配砍价金额区间 * @param string $goodsPrice * @param string $floorPrice * @param int $peoples * @return array|mixed * @throws \Exception */ private function calcBargainSection(string $goodsPrice, string $floorPrice, int $peoples) { $amount = (float)helper::bcsub($goodsPrice, $floorPrice); $AmountService = new AmountService($amount, $peoples); return $AmountService->handle()['items']; } /** * 当前是否为发起人 * @param self $taskInfo 砍价任务 * @return bool * @throws BaseException */ public function isCreater(self $taskInfo): bool { if (!$userId = UserService::getCurrentLoginUserId(false)) { return false; } return $userId == $taskInfo['user_id']; } /** * 当前是否为发起人 * @param int $taskId 砍价任务ID * @return bool * @throws BaseException */ public function isCut(int $taskId): bool { return TaskHelpModel::isCut($taskId); } /** * 获取活动详情 * @param int $activeId * @return Active|array|null * @throws BaseException */ private function getActiveDetail(int $activeId) { // 获取活动详情 $ActiveModel = new ActiveModel; return $ActiveModel->getDetail($activeId); } /** * 验证能否创建砍价任务 * @param $active * @param int $userId * @return bool */ private function onVerify($active, int $userId): bool { // 活动是否开始 if (!$active['is_start']) { $this->error = '很抱歉,当前砍价活动未开始'; return false; } // 活动是否结束 if ($active['is_end']) { $this->error = '很抱歉,当前砍价活动已结束'; return false; } // 判断当前用户是否已参加 $taskId = static::getHandByUser($active['active_id'], $userId); if ($taskId !== false && $taskId > 0) { $this->error = '很抱歉,当前砍价活动您已参加,无需重复参与'; return false; } return true; } }