From 942edeabe8a856a3c681a545e864b65438783174 Mon Sep 17 00:00:00 2001 From: vine_liutk <961510893@qq.com> Date: Fri, 8 Apr 2022 15:50:24 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=A0=8D=E4=BB=B7=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/BargainActivityController.php | 12 +- .../Controllers/BargainActivityController.php | 23 -- .../Http/Controllers/BargainController.php | 243 ++++++++++++++++++ .../Resources/BargainActivityResource.php | 26 ++ .../Resources/BargainOrderLogResource.php | 27 ++ .../Http/Resources/BargainOrderResource.php | 27 ++ .../Api/Http/Resources/ProduckSkuResource.php | 1 + app/Endpoint/Api/routes.php | 12 +- app/Models/BargainActivity.php | 5 + app/Models/BargainOrder.php | 29 +++ app/Models/BargainOrderLog.php | 16 ++ app/Models/BargainSku.php | 10 + app/Models/Order.php | 11 + app/Models/ProductSku.php | 17 ++ app/Services/BargainService.php | 106 ++++++++ app/Services/OrderService.php | 15 +- ...110305_create_bargain_activities_table.php | 2 + 17 files changed, 553 insertions(+), 29 deletions(-) delete mode 100644 app/Endpoint/Api/Http/Controllers/BargainActivityController.php create mode 100644 app/Endpoint/Api/Http/Controllers/BargainController.php create mode 100644 app/Endpoint/Api/Http/Resources/BargainActivityResource.php create mode 100644 app/Endpoint/Api/Http/Resources/BargainOrderLogResource.php create mode 100644 app/Endpoint/Api/Http/Resources/BargainOrderResource.php create mode 100644 app/Services/BargainService.php diff --git a/app/Admin/Controllers/BargainActivityController.php b/app/Admin/Controllers/BargainActivityController.php index 5d0b93eb..f59eddd1 100644 --- a/app/Admin/Controllers/BargainActivityController.php +++ b/app/Admin/Controllers/BargainActivityController.php @@ -101,6 +101,7 @@ class BargainActivityController extends AdminController return array_column($value, 'name'); })->label(); + $show->field('images')->image()->width(10, 1); $show->field('description')->unescape()->width(10, 1); $show->width(6)->field('created_at'); @@ -148,6 +149,13 @@ class BargainActivityController extends AdminController // dd($value, explode(',', $value)); return json_encode(explode(',', $value)); }); + $form->text('share_title', '分享文案')->placeholder('默认使用活动名称'); + $form->image('share_image', '分享图') + ->move('bargain/share/'.Carbon::now()->toDateString()) + ->saveFullUrl() + ->autoUpload() + ->retainable() + ->removable(false)->required(); $form->showFooter(); }); $form->block(6, function (Form\BlockForm $form) { @@ -161,8 +169,8 @@ class BargainActivityController extends AdminController $form->saving(function ($form) { if ($form->is_enable) { //查询是否有除了自己以外开启的活动 - if ($form->id) { - if (BargainActivityModel::where('id', '<>', $form->id)->isEnable()->exists()) { + if ($form->model()->id) { + if (BargainActivityModel::where('id', '<>', $form->model()->id)->isEnable()->exists()) { return $form->response()->error('当前已有开启的活动'); } } else { diff --git a/app/Endpoint/Api/Http/Controllers/BargainActivityController.php b/app/Endpoint/Api/Http/Controllers/BargainActivityController.php deleted file mode 100644 index 521ba98d..00000000 --- a/app/Endpoint/Api/Http/Controllers/BargainActivityController.php +++ /dev/null @@ -1,23 +0,0 @@ -response()->json([ - // 'name' - ]); - } -} diff --git a/app/Endpoint/Api/Http/Controllers/BargainController.php b/app/Endpoint/Api/Http/Controllers/BargainController.php new file mode 100644 index 00000000..bb84b9cb --- /dev/null +++ b/app/Endpoint/Api/Http/Controllers/BargainController.php @@ -0,0 +1,243 @@ +findOrFail($id); + if (!$activity->is_enable) { + throw new BizException('砍价活动暂未开始'); + } + if ($activity->start_at > now() || $activity->end_at < now()) { + throw new BizException('不在活动时间范围'); + } + + return BargainActivityResource::make($activity); + } + + /** + * 通过砍价商品查看当前砍价记录 + * + * @param [type] $id + * @param Request $request + * @return void + */ + public function bargainSku($id) + { + //判断商品是否参与砍价 + $bargainSku = BargainSku::with(['activity'=>function ($query) { + return $query->where('is_enable', true)->where('start_at', '<=', now())->where('end_at', '>=', now()); + }, 'sku'])->where('sku_id', $id)->first(); + + if (!($bargainSku && $bargainSku->activity)) { + throw new BizException('该商品未参与砍价活动,或者活动已结束'); + } + + return response()->json([ + 'sku' => [ + 'name' => (string) $bargainSku->sku->name, + 'subtitle' => (string) $bargainSku->sku->subtitle, + 'cover' => (string) $bargainSku->sku->cover, + 'sell_price' => (string) $bargainSku->sku->sell_price_format, + 'vip_price' => (string) $bargainSku->sku->vip_price_format, + ], + 'max_bargain_price'=> array_sum(json_decode($bargainSku->activity->rules)), + ]); + } + + /** + * 通过商品获取当前砍价进度(看自己的) + * + * @param Request $request + * @return void + */ + public function barginaOrderBySku($id, Request $request) + { + // $order = []; + $user = $request->user(); + $userId = $user?->id ?? 0; + $order = BargainOrder::with(['logs', 'logs.userInfo']) + ->where([ + 'sku_id' => $id, + 'user_id'=> $userId, + ])->where('status', '>', 0) + ->where('expire_at', '>', now()) + ->orderBy('created_at', 'desc')->first(); + if ($order) { + return BargainOrderResource::make($order); + } + return response()->noContent(); + } + + /** + * 通过订单ID获取当前砍价进度(看别人的) + * + * @param Request $request + * @return void + */ + public function bargainOrderById($id, Request $request) + { + $order = BargainOrder::with('logs') + ->where([ + 'id' => $id, + ])->where('status', '>', 0) + ->where('expire_at', '>', now()) + ->orderBy('created_at', 'desc')->first(); + if ($order) { + return BargainOrderResource::make($order); + } + return response()->noContent(); + } + + /** + * 发起砍价 + * + * @param [type] $id + * @param Request $request + * @return void + */ + public function createBargainOrder(ProductSku $sku, Request $request) + { + if (!$sku->isBargaing()) { + throw new BizException('活动已结束'); + } + //判断是否已经有正在进行的砍价单, 直接返回 + $order = BargainOrder::where([ + 'sku_id' => $sku->id, + 'user_id'=> $request->user()->id, + ])->where('status', '>', 0) + ->where('expire_at', '>', now()) + ->orderBy('created_at', 'desc')->first(); + + if (!$order) { + //创建新的砍价单 + $bargainService = new BargainService(); + try { + DB::beginTransaction(); + $order = $bargainService->createBargainOrder($request->user(), $sku); + DB::commit(); + } catch (Throwable $th) { + DB::rollBack(); + report($th); + throw new BizException($th->getMessage()); + } + } + + $order->load(['logs', 'logs.userInfo']); + return BargainOrderResource::make($order); + } + + /** + * 帮人砍价 + * + * @param BargainOrder $order + * @param Request $request + * @return void + */ + public function bargain(BargainOrder $order, Request $request) + { + $bargainService = new BargainService(); + try { + DB::beginTransaction(); + $bargainService->bargain($request->user(), $order); + DB::commit(); + } catch (Throwable $th) { + DB::rollBack(); + report($th); + throw new BizException($th->getMessage()); + } + + $order->load(['logs', 'logs.userInfo']); + return BargainOrderResource::make($order); + } + + /** + * 通过砍价订单创建待支付商城订单 + * + * @param BargainOrder $bargainOrder + * @param Request $request + * @return void + */ + public function createMallOrderByBargainOrder(BargainOrder $bargainOrder, Request $request) + { + $rules = [ + 'shipping_address_id' => ['bail', 'required', 'int'], + 'note' => ['bail', 'nullable', 'string', 'max:255'], + ]; + $input = $request->validate($rules, [], [ + 'shipping_address_id' => '收货地址', + 'note' => '订单备注', + ]); + $user = $request->user(); + if ($user->id != $bargainOrder->user_id) { + throw new BizException('您不能替别人下单'); + } + + $orderService = new OrderService(); + try { + DB::beginTransaction(); + $bargainOrder->lockForUpdate();//下单的时候不允许砍价了 + + //已下单 + if ($bargainOrder->order_id > 0) { + throw new BizException('您已下单,无需重复下单'); + } + + //砍价超时 + if ($bargainOrder->expire_at > now()) { + throw new BizException('当前砍价已结束,无法下单'); + } + + //如果砍价活动结束了,也不能砍了 + $activity = $bargainOrder->sku->isBargaing(); + if (!$activity) { + throw new BizException('当前砍价已结束,无法下单'); + } + + $order = $orderService->createQuickOrder( + $user, + $bargainOrder->sku_id, + 1, + $input['shipping_address_id'], + null, + $input['note'] ?? null, + $bargainOrder + ); + $bargainOrder->update([ + 'order_id' => $order->id, + ]); + DB::commit(); + } catch (Throwable $th) { + DB::rollBack(); + report($th); + throw new BizException($th->getMessage()); + } + + OrderPaid::dispatchIf($order->isPaid(), $order); + + return OrderResource::make($order); + } +} diff --git a/app/Endpoint/Api/Http/Resources/BargainActivityResource.php b/app/Endpoint/Api/Http/Resources/BargainActivityResource.php new file mode 100644 index 00000000..cba46222 --- /dev/null +++ b/app/Endpoint/Api/Http/Resources/BargainActivityResource.php @@ -0,0 +1,26 @@ + $this->name, + 'images' => $this->images, + 'description' => $this->description, + 'skus' => ProductSkuSimpleResource::collection($this->whenLoaded('skus')), + 'share_image'=> $this->share_image ?? '', + 'share_title'=> $this->share_title ?? '', + ]; + } +} diff --git a/app/Endpoint/Api/Http/Resources/BargainOrderLogResource.php b/app/Endpoint/Api/Http/Resources/BargainOrderLogResource.php new file mode 100644 index 00000000..eb7102aa --- /dev/null +++ b/app/Endpoint/Api/Http/Resources/BargainOrderLogResource.php @@ -0,0 +1,27 @@ + (string) $this->whenLoaded('userInfo', function () { + return $this->userInfo->nickname; + }, ''), + 'avatar'=> (string) $this->whenLoaded('userInfo', function () { + return $this->userInfo->avatar; + }, ''), + 'bargain_amount' => $this->bargain_amount_format, + ]; + } +} diff --git a/app/Endpoint/Api/Http/Resources/BargainOrderResource.php b/app/Endpoint/Api/Http/Resources/BargainOrderResource.php new file mode 100644 index 00000000..b13634a7 --- /dev/null +++ b/app/Endpoint/Api/Http/Resources/BargainOrderResource.php @@ -0,0 +1,27 @@ +user(); + $userId = $user?->id ?? 0; + return [ + 'id' => $this->id, + 'logs' => BargainOrderLogResource::collection($this->whenLoaded('logs')), + 'expire_at'=> $this->expire_at->format('Y-m-d H:i:s'), + 'status' => $this->status, + 'is_owner' => $this->user_id == $userId ? true : false, + ]; + } +} diff --git a/app/Endpoint/Api/Http/Resources/ProduckSkuResource.php b/app/Endpoint/Api/Http/Resources/ProduckSkuResource.php index f6a87f1a..5c3ad62a 100644 --- a/app/Endpoint/Api/Http/Resources/ProduckSkuResource.php +++ b/app/Endpoint/Api/Http/Resources/ProduckSkuResource.php @@ -35,6 +35,7 @@ class ProduckSkuResource extends JsonResource }), 'growth_value' => (int) $this->growth_value, 'sales_value' => (int) $this->sales_value, + 'is_bargaing' => $this->isBargaing() ? true : false, ]; } } diff --git a/app/Endpoint/Api/routes.php b/app/Endpoint/Api/routes.php index 4d70178d..e39c1f7b 100644 --- a/app/Endpoint/Api/routes.php +++ b/app/Endpoint/Api/routes.php @@ -89,6 +89,13 @@ Route::group([ Route::get('configs', [SettingController::class, 'index']); Route::get('configs-custom', [SettingController::class, 'custom']); + //砍价 + Route::get('bargains/{activity}', [\App\Endpoint\Api\Http\Controllers\BargainController::class, 'activityDetail']); + Route::get('bargain-sku/{sku}', [\App\Endpoint\Api\Http\Controllers\BargainController::class, 'bargainSku']); + Route::get('bargain-order/sku/{sku}', [\App\Endpoint\Api\Http\Controllers\BargainController::class, 'barginaOrderBySku']); + Route::get('bargain-order/order/{order}', [\App\Endpoint\Api\Http\Controllers\BargainController::class, 'bargainOrderById']); + + //三方登录聚合 Route::group([ 'prefix' =>'socialite', @@ -196,7 +203,10 @@ Route::group([ Route::get('users/{phone}', [\App\Endpoint\Api\Http\Controllers\UserController::class, 'show']); - Route::get('bargains/{bargain}', [\App\Endpoint\Api\Http\Controllers\BargainActivityController::class, 'detail']); + //砍价 + Route::post('bargains/create-order/{sku}', [\App\Endpoint\Api\Http\Controllers\BargainController::class, 'createBargainOrder']); + Route::post('bargains/bargain/{order}', [\App\Endpoint\Api\Http\Controllers\BargainController::class, 'bargain']); + Route::post('bargains/create-mall-order/{bargainOrder}', [\App\Endpoint\Api\Http\Controllers\BargainController::class, 'createMallOrderByBargainOrder']); }); Route::group([ diff --git a/app/Models/BargainActivity.php b/app/Models/BargainActivity.php index 9b14ccb9..4356972f 100644 --- a/app/Models/BargainActivity.php +++ b/app/Models/BargainActivity.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Casts\JsonArray; use Dcat\Admin\Traits\HasDateTimeFormatter; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -11,6 +12,10 @@ class BargainActivity extends Model use HasFactory; use HasDateTimeFormatter; + protected $casts = [ + 'images' => JsonArray::class, + ]; + public static $enabledText = [ 0=>'禁用', 1=>'启用', diff --git a/app/Models/BargainOrder.php b/app/Models/BargainOrder.php index debe69f5..b50a802e 100644 --- a/app/Models/BargainOrder.php +++ b/app/Models/BargainOrder.php @@ -2,10 +2,39 @@ namespace App\Models; +use Dcat\Admin\Traits\HasDateTimeFormatter; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class BargainOrder extends Model { use HasFactory; + use HasDateTimeFormatter; + + protected $casts = [ + 'expire_at'=> 'datetime', + ]; + + protected $fillable = [ + 'order_id', 'remark', + ]; + + public const ORDER_STATUS_WAIT = 0; + public const ORDER_STATUS_START = 1; + public const ORDER_STATUS_FINISHED = 2; + + public function logs() + { + return $this->hasMany(BargainOrderLog::class, 'order_id'); + } + + public function sku() + { + return $this->hasOne(ProductSku::class, 'id', 'sku_id'); + } + + public function isFinished() + { + return $this->status == static::ORDER_STATUS_FINISHED; + } } diff --git a/app/Models/BargainOrderLog.php b/app/Models/BargainOrderLog.php index 0682b5f5..f9ac40e3 100644 --- a/app/Models/BargainOrderLog.php +++ b/app/Models/BargainOrderLog.php @@ -2,10 +2,26 @@ namespace App\Models; +use Dcat\Admin\Traits\HasDateTimeFormatter; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class BargainOrderLog extends Model { use HasFactory; + use HasDateTimeFormatter; + + protected $fillable = [ + 'user_id', 'bargain_amount', + ]; + + public function userInfo() + { + return $this->hasOne(UserInfo::class, 'user_id', 'user_id'); + } + + public function getBargainAmountFormatAttribute() + { + return bcdiv($this->attributes['bargain_amount'], 100, 2); + } } diff --git a/app/Models/BargainSku.php b/app/Models/BargainSku.php index f1cf2a79..d6019936 100644 --- a/app/Models/BargainSku.php +++ b/app/Models/BargainSku.php @@ -10,4 +10,14 @@ class BargainSku extends Model use HasFactory; protected $timestamp = false; + + public function activity() + { + return $this->belongsTo(BargainActivity::class, 'activity_id'); + } + + public function sku() + { + return $this->belongsTo(ProductSku::class, 'sku_id'); + } } diff --git a/app/Models/Order.php b/app/Models/Order.php index fb703acc..f4715a29 100644 --- a/app/Models/Order.php +++ b/app/Models/Order.php @@ -93,6 +93,7 @@ class Order extends Model 'is_settle', 'sales_value', 'is_settlable', + 'bargain_amount', ]; /** @@ -427,4 +428,14 @@ class Order extends Model { return OrderStatus::$statusTexts[$this->order_status] ?? OrderStatus::$statusTexts[OrderStatus::UNKNOWN]; } + + /** + * 获取订单砍价优惠 + * + * @return string + */ + public function getBargainAmountFormatAttribute() + { + return trim_trailing_zeros(bcdiv($this->attributes['bargain_amount'], 100, 2)); + } } diff --git a/app/Models/ProductSku.php b/app/Models/ProductSku.php index 46f943de..db97bac1 100644 --- a/app/Models/ProductSku.php +++ b/app/Models/ProductSku.php @@ -195,4 +195,21 @@ class ProductSku extends Model return bcdiv($price, 100, 2); } + + /** + * 是否正在进行砍价 + * + * @return boolean + */ + public function isBargaing() + { + $bargainSku = BargainSku::with(['activity'=>function ($query) { + return $query->where('is_enable', true)->where('start_at', '<=', now())->where('end_at', '>=', now()); + }])->where('sku_id', $this->id)->first(); + + if ($bargainSku && $bargainSku->activity) { + return $bargainSku->activity; + } + return null; + } } diff --git a/app/Services/BargainService.php b/app/Services/BargainService.php new file mode 100644 index 00000000..19ba02fe --- /dev/null +++ b/app/Services/BargainService.php @@ -0,0 +1,106 @@ +function ($query) { + return $query->where('is_enable', true)->where('start_at', '<=', now())->where('end_at', '>=', now()); + }])->where('sku_id', $productSku->id)->first(); + if (!($bargainSku && $bargainSku->activity)) { + throw new BizException('砍价商品活动已结束'); + } + + $order = new BargainOrder(); + $order->activity_id = $bargainSku->activity_id; + $order->user_id = $user->id; + $order->sku_id = $productSku->id; + $order->sku_price = $productSku->getRealPrice($user); + $order->status = BargainOrder::ORDER_STATUS_START; + $order->expire_at = now()->addHours($bargainSku->activity->expire_hours); + $order->save(); + + return $order; + } + + /** + * 用户砍价 + * + * @param User $user + * @param BargainOrder $order + * @return void + */ + public function bargain(User $user, BargainOrder $order) + { + $order->lockForUpdate(); + $order->load('sku'); + + //不能给自己砍 + if ($user->id == $order->user_id) { + throw new BizException('不能帮自己砍价'); + } + + //已砍完 + if ($order->isFinished()) { + throw new BizException('当前砍价单已完成,无法再砍'); + } + + //已下单 + if ($order->order_id > 0) { + throw new BizException('当前砍价单下单,无法再砍'); + } + + //砍价超时 + if ($order->expire_at > now()) { + throw new BizException('当前砍价已结束,无法再砍'); + } + + //如果砍价活动结束了,也不能砍了 + $activity = $order->sku->isBargaing(); + if (!$activity) { + throw new BizException('当前砍价已结束,无法再砍'); + } + + + //执行砍价动作; + //计算第几次砍价,并获得当前砍价金额 + $nowBargainCount = $order->logs->count(); + + $bargainRules = json_decode($activity->rules, true); + + $log = new BargainOrderLog([ + 'user_id'=>$user->id, + 'bargain_amount'=> bcmul(Arr::get($bargainRules, $nowBargainCount, 0), 100), + ]); + + $order->logs()->save($log); + $order->bargain_price += $log->bargain_amount; + if ($activity->times > 0 && $activity->times <= ($nowBargainCount ++)) { + $order->status = BargainOrder::ORDER_STATUS_FINISHED; + } + $order->save(); + + return $order; + } + + public function createMallOrder(BargainOrder $order) + { + } +} diff --git a/app/Services/OrderService.php b/app/Services/OrderService.php index cda3edac..eef62540 100644 --- a/app/Services/OrderService.php +++ b/app/Services/OrderService.php @@ -11,6 +11,7 @@ use App\Enums\WxpayTradeType; use App\Exceptions\BizException; use App\Exceptions\ShippingNotSupportedException; use App\Models\ActivityProductPart; +use App\Models\BargainOrder; use App\Models\DistributionPreIncomeJob; use App\Models\Order; use App\Models\OrderActivity; @@ -38,6 +39,7 @@ class OrderService * @param int $shippingAddressId * @param int|null $couponId * @param string|null $note + * @param BargainOrder|null $bargainOrder * @return \App\Models\Order */ public function createQuickOrder( @@ -46,7 +48,8 @@ class OrderService int $quantity, int $shippingAddressId, ?int $couponId = null, - ?string $note = null + ?string $note = null, + ?BargainOrder $bargainOrder = null, ): Order { $sku = ProductSku::online()->findOrFail($skuId); @@ -55,7 +58,7 @@ class OrderService 'quantity' => $quantity, ]; - return $this->createOrder($user, [$product], $shippingAddressId, $couponId, $note); + return $this->createOrder($user, [$product], $shippingAddressId, $couponId, $note, $bargainOrder); } /** @@ -97,6 +100,7 @@ class OrderService * @param int $shippingAddressId * @param int|null $couponId * @param string|null $note + * @param BargainOrder|null $bargainOrder * @return \App\Models\Order */ protected function createOrder( @@ -105,6 +109,7 @@ class OrderService int $shippingAddressId, ?int $couponId = null, ?string $note = null, + ?BargainOrder $bargainOrder = null, ): Order { foreach ($products as $product) { $sku = $product['sku']; @@ -144,7 +149,8 @@ class OrderService $shippingFee, $salesValue, $note, - $coupon + $coupon, + $bargainOrder,//添加砍价订单逻辑 ); $this->storeOrderProducts($order, $mapProducts); @@ -185,6 +191,7 @@ class OrderService $salesValue, ?string $note = null, ?UserCoupon $coupon = null, + ?BargainOrder $bargainOrder = null, ): Order { // 订单支付金额=商品总额-券折扣金额-会员折扣金额+邮费 $totalAmount = $productsTotalAmount - $couponDiscountAmount - $vipDiscountAmount; @@ -213,6 +220,8 @@ class OrderService 'consignee_telephone' => $shippingAddress->telephone, 'consignee_zone' => $shippingAddress->zone, 'consignee_address' => $shippingAddress->address, + //砍价订单金额 + 'bargain_amount'=>$bargainOrder?->bargain_price, ]; return $user->orders()->create($attrs); diff --git a/database/migrations/2022_04_06_110305_create_bargain_activities_table.php b/database/migrations/2022_04_06_110305_create_bargain_activities_table.php index 5b865480..7f39628a 100644 --- a/database/migrations/2022_04_06_110305_create_bargain_activities_table.php +++ b/database/migrations/2022_04_06_110305_create_bargain_activities_table.php @@ -17,6 +17,8 @@ class CreateBargainActivitiesTable extends Migration $table->id(); $table->string('name')->comment('活动名称'); $table->text('images')->nullable()->comment('活动图'); + $table->text('share_image')->nullable()->comment('分享图'); + $table->text('share_title')->nullable()->comment('分享文案'); $table->text('description')->nullable()->comment('活动描述'); $table->boolean('is_enable')->nullable()->comment('是否开启'); $table->text('rules')->nullable()->comment('砍价规则');