diff --git a/README.md b/README.md index f0b95ab2a560ade00fafea50c4e61109884bbad7..db2b78d297a276aa93092046c49e07a17ae9b0ec 100644 --- a/README.md +++ b/README.md @@ -44,13 +44,11 @@ stopwaitsecs=3600 ``` supervisorctl reload -sudo supervisorctl reread +supervisorctl update -sudo supervisorctl update - -sudo supervisorctl status +supervisorctl restart dfac-worker:* -sudo supervisorctl restart dfac-worker:* +supervisorctl status ps -ef|grep dfac-worker ``` diff --git a/app/Console/Commands/GroupDue.php b/app/Console/Commands/GroupDue.php new file mode 100644 index 0000000000000000000000000000000000000000..8655692e6429c321a7e3392dc67953967b5043e3 --- /dev/null +++ b/app/Console/Commands/GroupDue.php @@ -0,0 +1,40 @@ +subDay()->toDateTimeString(); + // $groups = $group->where('status', Group::STATUS_PROCESSING)->where('valid_at', '<', $due_at)->get(); + $groups = $group->where('status', Group::STATUS_PROCESSING)->get(); + foreach ($groups as $group) { + $orders = Order::where('group_id', $group->id)->whereNotNull('paid_at')->where('refund_status', '<>', Order::REFUND_STATUS_SUCCESS)->get(); + foreach ($orders as $order) { + try { + $service->refund($order); + //推送模板消息 + // dispatch(new GroupCancelNotifyJob($order)); + } catch (InternalException $e) { + Log::debug($e->getMessage()); + } + } + $group->status = Group::STATUS_FAILED; + $group->save(); + } + $this->info("处理成功!"); + } +} diff --git a/app/Console/Commands/GroupingNotify.php b/app/Console/Commands/GroupingNotify.php new file mode 100644 index 0000000000000000000000000000000000000000..52e09176505bb088fff104ac4b234771e7a1e2ed --- /dev/null +++ b/app/Console/Commands/GroupingNotify.php @@ -0,0 +1,21 @@ +where('status', Group::STATUS_PROCESSING)->get(); + foreach ($groups as $group) { + dispatch(new GroupGroupingNotifyJob($group)); + } + $this->info("处理成功!"); + } +} diff --git a/app/Console/Commands/OrderCompleted.php b/app/Console/Commands/OrderCompleted.php new file mode 100644 index 0000000000000000000000000000000000000000..3004a019630fdc1274ada812e376b41267bdd508 --- /dev/null +++ b/app/Console/Commands/OrderCompleted.php @@ -0,0 +1,34 @@ +ask('父订单 ID'); + + $order = ParentOrder::find($order_id); + + if (!$order) { + return $this->error('订单不存在'); + } + app('App\Services\OrderService')->pay($order); + $order_no = '42000018822023050951343' . mt_rand(10000, 99999); + app('App\Services\OrderService')->payCompleted($order, $order_no); + + $this->info('完成'); + } +} diff --git a/app/Console/Commands/PointClear.php b/app/Console/Commands/PointClear.php index 71b083e62cb56296e2938956b9f08e5a8af5afc3..35813777ed4a73522acaa6daac6fc4ac70aae82a 100644 --- a/app/Console/Commands/PointClear.php +++ b/app/Console/Commands/PointClear.php @@ -14,10 +14,8 @@ class PointClear extends Command public function handle() { - if ($this->confirm('确定清除所有积分?')) { - DB::table('customer_extras')->update(['point_total' => 0, 'point_usable' => 0]); - DB::table('customer_point_records')->update(['status' => CustomerPointRecord::STATUS_EXPIRED]); - $this->info('清除成功'); - } + DB::table('customer_extras')->update(['point_total' => 0, 'point_usable' => 0]); + DB::table('customer_point_records')->where('status', CustomerPointRecord::STATUS_VALID)->update(['status' => CustomerPointRecord::STATUS_EXPIRED]); + $this->info('清除成功'); } } diff --git a/app/Console/Commands/SendWechatSmsTest.php b/app/Console/Commands/SendWechatSmsTest.php index eb339cea1bc449dfccc9ad513bf469710acde748..4d9c46c6d7219d1a7c3b275656d3ec3b880f0cd7 100644 --- a/app/Console/Commands/SendWechatSmsTest.php +++ b/app/Console/Commands/SendWechatSmsTest.php @@ -27,32 +27,8 @@ class SendWechatSmsTest extends Command return $this->error('顾客不存在'); } - $params = [ - 'touser' => $customer->wechat_openid, - 'template_id' => '4pZuY4vTKF1RqlJdx4bxqHoOPXie4z7maRXffqVx1_0', - 'miniprogram' => [ - 'appid' => 'wxb370f0991cdd81d3', - 'pagepath' => 'recommend/index/index' - ], - 'data' => [ - 'first' => [ - 'value' => '推荐官审核结果如下:' - ], - 'keyword1' => [ - 'value' => '成功' - ], - 'keyword2' => [ - 'value' => '' - ], - 'keyword3' => [ - 'value' => '东风推荐官活动' - ], - 'remark' => [ - 'value' => '' - ] - ] - ]; - $result = app(MessageTemplateService::class)->send($params); + // $result = app(MessageTemplateService::class)->orderCompleteNotify($customer->wechat_openid,'品牌水杯雨伞礼盒','20221111014547776307','2022-09-29 16:52:32'); + $result = app(MessageTemplateService::class)->orderCancelNotify($customer->wechat_openid,'品牌水杯雨伞礼盒','20221111014547776307','2022-09-29 16:52:32'); if($result){ $this->info("发送成功"); } else { diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 13133a1e2ff6f04c2c788512ade9ec9fba2f0173..93d080423ddd8b997f7f54b0efd3ddffd70d2904 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -40,6 +40,10 @@ class Kernel extends ConsoleKernel \App\Console\Commands\TopicSelectOver::class, \App\Console\Commands\SyncCarCoupons::class, \App\Console\Commands\DeleteGuoqingDraw::class, + \App\Console\Commands\OrderCompleted::class, + \App\Console\Commands\GroupDue::class, + \App\Console\Commands\GroupingNotify::class + ]; /** @@ -50,13 +54,16 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule) { - $schedule->command('dfac:sync-topic-view-count')->dailyAt('00:00')->onOneServer(); - $schedule->command('dfac:sync-short-url-pv')->dailyAt('00:00')->onOneServer(); - $schedule->command('dfac:sync-short-url-uv')->dailyAt('00:05')->onOneServer(); + $schedule->command('dfac:sync-topic-view-count')->dailyAt('00:10')->onOneServer(); + $schedule->command('dfac:sync-short-url-pv')->dailyAt('00:20')->onOneServer(); + $schedule->command('dfac:sync-short-url-uv')->dailyAt('00:30')->onOneServer(); // $schedule->command('dfac:auto-issue-car-coupons')->dailyAt('01:00')->onOneServer(); // $schedule->command('dfac:car-coupons-export')->dailyAt('02:00')->onOneServer(); - $schedule->command('dfac:point-clear')->yearly()->onOneServer(); + // $schedule->command('dfac:point-clear')->yearly()->onOneServer(); $schedule->command('dfac:topic-select-over')->everyMinute()->onOneServer(); + $schedule->command('dfac:group-due')->dailyAt('00:00')->onOneServer(); + $schedule->command('dfac:grouping-notify')->dailyAt('22:00')->onOneServer(); + // $schedule->command('dfac:delete-guoqing-draw')->dailyAt('00:00')->onOneServer(); // $schedule->command('queue:retry all')->weekly()->onOneServer(); diff --git a/app/Exports/OrdersExport.php b/app/Exports/OrdersExport.php index cf0942bc9353270bf3d524b833a445ff06693ff6..82812b2a16b9d1ba7ec60e9a22d8ed4f2ad0291b 100644 --- a/app/Exports/OrdersExport.php +++ b/app/Exports/OrdersExport.php @@ -42,9 +42,9 @@ class OrdersExport implements FromQuery, Responsable, WithMapping, WithHeadings, public function query() { if ($this->queryObj) - return $this->queryObj->with(['product','customer']); + return $this->queryObj->with(['product', 'customer']); else - return Order::query()->with(['product','customer']); + return Order::query()->with(['product', 'customer']); } public function map($order): array @@ -67,16 +67,14 @@ class OrdersExport implements FromQuery, Responsable, WithMapping, WithHeadings, $order->price, $order->point, $order->total_amount, + $order->payment_amount, $order->total_point, $order->store_id, $order->paid_at, - $order->payment_method, - $order->payment_no, $order->payment_point_record_id, $order->refund_status, $order->refund_no, $order->refund_point_record_id, - $order->closed, $order->closed_at, $order->ship_status, $order->send_at, @@ -108,16 +106,14 @@ class OrdersExport implements FromQuery, Responsable, WithMapping, WithHeadings, '单价', '积分单价', '订单总金额', + '订单实际支付金额', '订单总积分', '服务站ID', '支付时间', - '支付方式', - '支付平台订单号', '积分记录ID', '退款状态', '退款单号', '退款积分记录ID', - '订单是否已取消', '取消时间', '物流状态', '发货时间', diff --git a/app/Http/Controllers/Admin/ActivityController.php b/app/Http/Controllers/Admin/ActivityController.php index 9e15a02bebef80021e079b66669bc45ef1f9f4ed..026be8df515fb74d9db472aaccca8bab47aba0d9 100644 --- a/app/Http/Controllers/Admin/ActivityController.php +++ b/app/Http/Controllers/Admin/ActivityController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers\Admin; use App\Http\Queries\ActivityQuery; use App\Http\Resources\ActivityResource; use App\Models\Activity; +use App\Models\Product; use Illuminate\Http\Request; class ActivityController extends Controller @@ -49,6 +50,8 @@ class ActivityController extends Controller 'labels' => 'nullable|array', 'labels.*' => 'nullable|exists:activity_labels,label', 'class' => 'required|in:' . $classes, + 'limit_draw_count' => 'required|integer', + 'products' => 'required|array' ], [], [ 'title' => '标题', 'banner' => '海报图', @@ -66,10 +69,13 @@ class ActivityController extends Controller 'labels' => '活动位置', 'labels.*' => '活动标签', 'class' => '品牌种类', + 'limit_draw_count' => '限制获取抽奖机会次数', + 'products' => '商品组' ]); $activity->fill($data); $activity->save(); + Product::whereIn('id', $data['products'])->update(['activity_id' => $activity->id]); return $this->resource((new ActivityResource($activity))->showAllField()); } @@ -96,6 +102,8 @@ class ActivityController extends Controller 'labels' => 'nullable|array', 'labels.*' => 'nullable|exists:activity_labels,label', 'class' => 'required|in:' . $classes, + 'limit_draw_count' => 'required|integer', + 'products' => 'required|array' ], [], [ 'title' => '标题', 'banner' => '海报图', @@ -113,9 +121,14 @@ class ActivityController extends Controller 'valid' => '是否有效', 'show' => '是否展示', 'class' => '品牌种类', + 'limit_draw_count' => '限制获取抽奖机会次数', + 'products' => '商品组' + ]); $row->fill($data); $row->save(); + Product::whereIn('id', $data['products'])->update(['activity_id' => $row->id]); + Product::whereNotIn('id', $data['products'])->where('activity_id', $row->id)->update(['activity_id' => null]); return $this->resource((new ActivityResource($row))->showAllField()); } diff --git a/app/Http/Controllers/Admin/CourtesyCardController.php b/app/Http/Controllers/Admin/CourtesyCardController.php new file mode 100644 index 0000000000000000000000000000000000000000..3343c654969b74695d6400824e0a3828f500dd65 --- /dev/null +++ b/app/Http/Controllers/Admin/CourtesyCardController.php @@ -0,0 +1,49 @@ +validate($request, [ + 'customer_id' => 'required|integer', + 'courtesy_card_mould_id' => 'required|integer', + 'count' => 'required|integer|min:1', + ], [], [ + 'customer_id' => '用户ID', + 'courtesy_card_mould_id' => '优惠券模板ID', + 'count' => '数量', + ]); + $customer = Customer::findOrFail($request->customer_id); + $card_mould = CourtesyCardMould::findOrFail($request->courtesy_card_mould_id); + $card_mould->distributeCards($customer, $request->count); + return $this->message('发放成功'); + } + + public function index(CourtesyCardQuery $query) + { + $rows = $query->paginate(10); + return $this->resource(CourtesyCardResource::collection($rows)); + } + + public function show($id, CourtesyCardQuery $query) + { + $row = $query->findOrFail($id); + return $this->resource((new CourtesyCardResource($row))->showAllField()); + } + + public function destroy($id, CourtesyCard $courtesyCard) + { + $row = $courtesyCard->findOrFail($id); + $row->delete(); + return $this->message('删除成功'); + } +} diff --git a/app/Http/Controllers/Admin/CourtesyCardMouldController.php b/app/Http/Controllers/Admin/CourtesyCardMouldController.php new file mode 100644 index 0000000000000000000000000000000000000000..8035b87d95010a50b6934f87f5bd678a202f528d --- /dev/null +++ b/app/Http/Controllers/Admin/CourtesyCardMouldController.php @@ -0,0 +1,157 @@ +get(); + return $this->resource(CourtesyCardMouldResource::collection($rows)); + } + + public function index(CourtesyCardMouldQuery $query) + { + $rows = $query->paginate(10); + return $this->resource(CourtesyCardMouldResource::collection($rows)); + } + + public function store(Request $request, CourtesyCardMould $courtesyCardMould) + { + switch ($request->valid_at_type) { + case 'datetime': + $valid_at_value_rule = 'required|date_format:"Y-m-d H:i:s"'; + $valid_at = $request->valid_at_value; + break; + case 'days': + $valid_at_value_rule = 'required|integer'; + $valid_at = '+' . $request->valid_at_value . ' days'; + break; + default: + $valid_at_value_rule = 'required_with:valid_at_type|string'; + $valid_at = $request->valid_at_value; + } + + switch ($request->due_at_type) { + case 'datetime': + $due_at_value_rule = 'required|date_format:"Y-m-d H:i:s"'; + $due_at = $request->due_at_value; + break; + case 'days': + $due_at_value_rule = 'required|integer'; + $due_at = '+' . $request->due_at_value . ' days'; + break; + default: + $due_at_value_rule = 'required_with:due_at_type|string'; + $due_at = $request->due_at_value; + } + $data = $this->validate($request, [ + 'name' => 'required|string', + 'type' => 'required|in:fixed,percent', + 'value' => 'required|numeric', + 'min_amount' => 'required|numeric', + 'valid_at_type' => 'required|in:datetime,days', + 'valid_at_value' => $valid_at_value_rule, + 'due_at_type' => 'required|in:datetime,days', + 'due_at_value' => $due_at_value_rule, + 'products' => 'required|array', + 'products.*' => 'integer' + ], [], [ + 'name' => '优惠券的标题', + 'type' => '优惠券类型', + 'value' => '折扣值', + 'min_amount' => '使用该优惠券的最低订单金额', + 'valid_at_type' => '生效时间类型', + 'valid_at_value' => '生效时间值', + 'due_at_type' => '失效时间类型', + 'due_at_value' => '失效时间值', + 'products' => '商品组', + 'products.*' => '商品ID' + ]); + $courtesyCardMould->fill($data); + $courtesyCardMould->valid_at = $valid_at; + $courtesyCardMould->due_at = $due_at; + $courtesyCardMould->save(); + $courtesyCardMould->syncProducts($data['products']); + return $this->resource(new CourtesyCardMouldResource($courtesyCardMould)); + } + + public function update($id, Request $request, CourtesyCardMould $courtesyCardMould) + { + $row = $courtesyCardMould->findOrFail($id); + switch ($request->valid_at_type) { + case 'datetime': + $valid_at_value_rule = 'required|date_format:"Y-m-d H:i:s"'; + $valid_at = $request->valid_at_value; + break; + case 'days': + $valid_at_value_rule = 'required|integer'; + $valid_at = '+' . $request->valid_at_value . ' days'; + break; + default: + $valid_at_value_rule = 'required_with:valid_at_type|string'; + $valid_at = $request->valid_at_value; + } + + switch ($request->due_at_type) { + case 'datetime': + $due_at_value_rule = 'required|date_format:"Y-m-d H:i:s"'; + $due_at = $request->due_at_value; + break; + case 'days': + $due_at_value_rule = 'required|integer'; + $due_at = '+' . $request->due_at_value . ' days'; + break; + default: + $due_at_value_rule = 'required_with:due_at_type|string'; + $due_at = $request->due_at_value; + } + $data = $this->validate($request, [ + 'name' => 'required|string', + 'type' => 'required|in:fixed,percent', + 'value' => 'required|numeric', + 'min_amount' => 'required|numeric', + 'valid_at_type' => 'required|in:datetime,days', + 'valid_at_value' => $valid_at_value_rule, + 'due_at_type' => 'required|in:datetime,days', + 'due_at_value' => $due_at_value_rule, + 'products' => 'required|array', + 'products.*' => 'integer' + ], [], [ + 'name' => '优惠券的标题', + 'type' => '优惠券类型', + 'value' => '折扣值', + 'min_amount' => '使用该优惠券的最低订单金额', + 'valid_at_type' => '生效时间类型', + 'valid_at_value' => '生效时间值', + 'due_at_type' => '失效时间类型', + 'due_at_value' => '失效时间值', + 'products' => '商品组', + 'products.*' => '商品ID' + ]); + $row->fill($data); + $row->valid_at = $valid_at; + $row->due_at = $due_at; + $row->save(); + $row->syncProducts($data['products']); + return $this->resource(new CourtesyCardMouldResource($row)); + } + + public function destroy($id, CourtesyCardMould $courtesyCardMould) + { + $data = $courtesyCardMould->findOrFail($id); + $data->delete(); + return $this->message('删除成功'); + } + + public function show($id, CourtesyCardMouldQuery $query) + { + $row = $query->findOrFail($id); + return $this->resource(new CourtesyCardMouldResource($row)); + } +} diff --git a/app/Http/Controllers/Admin/OrderController.php b/app/Http/Controllers/Admin/OrderController.php index f133ce42ca2b828b3bf5fcd9eff41a6e128b71bf..d1b5dde101960d6c114f64eb1d87e62a357065f5 100644 --- a/app/Http/Controllers/Admin/OrderController.php +++ b/app/Http/Controllers/Admin/OrderController.php @@ -7,6 +7,9 @@ use App\Http\Resources\OrderResource; use App\Models\Order; use Illuminate\Http\Request; use Illuminate\Support\Carbon; +use Maatwebsite\Excel\Facades\Excel; +use App\Imports\OrderInvoiceImport; +use App\Imports\OrderbatchShipImport; class OrderController extends Controller { @@ -32,7 +35,7 @@ class OrderController extends Controller } // 判断当前订单发货状态是否为未发货 if ($order->ship_status !== Order::SHIP_STATUS_PENDING) { - return $this->failed('order-status-error', '该订单已发货'); + return $this->failed('order-status-error', '该订单物流状态错误 '); } // 判断当前订单发货状态是否已退款 if ($order->refund_status == Order::REFUND_STATUS_SUCCESS) { @@ -75,9 +78,9 @@ class OrderController extends Controller // 判断订单状态是否正确 if ($order->refund_status !== Order::REFUND_STATUS_APPLIED) { - return $this->failed('order-status-error','退款状态异常,无法退款'); + return $this->failed('order-status-error', '退款状态异常,无法退款'); } - + // 是否同意退款 if ($request->input('agree')) { // 调用退款逻辑 @@ -88,7 +91,6 @@ class OrderController extends Controller $order->update([ 'extra' => $extra, ]); - } else { // 将拒绝退款理由放到订单的 extra 字段中 $extra = $order->extra ?: []; @@ -107,4 +109,41 @@ class OrderController extends Controller { return app('App\Exports\OrdersExport')->withQuery($query)->download(); } + + public function uploadInvoice($id, Request $request) + { + $order = Order::findOrFail($id); + $data = $this->validate($request, [ + 'invoice_url' => 'required|url', + ], [], [ + 'invoice_url' => '发票地址', + ]); + $order->fill($data); + $order->save(); + return $this->resource(new OrderResource($order)); + } + + public function invoiceImport(Request $request) + { + $this->validate($request, [ + 'xlsx' => 'required|mimes:xlsx|file' + ], [], [ + 'xlsx' => '表格', + ]); + + Excel::import(new OrderInvoiceImport, $request->xlsx); + return $this->message('导入成功'); + } + + public function batchShip(Request $request) + { + $this->validate($request, [ + 'xlsx' => 'required|mimes:xlsx|file' + ], [], [ + 'xlsx' => '表格', + ]); + + Excel::import(new OrderbatchShipImport, $request->xlsx); + return $this->message('导入成功'); + } } diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index 45877d49d8a6f062257d32662ae7865f80694cc4..60b96cb3e6eb00e9b3983d76f684667e83849241 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -7,10 +7,17 @@ use App\Http\Resources\ProductResource; use App\Models\Product; use Illuminate\Http\Request; use App\Models\Category; +use App\Models\GroupProduct; use App\Models\SeckillProduct; class ProductController extends Controller { + public function all(ProductQuery $query) + { + $rows = $query->get(); + return $this->resource(ProductResource::collection($rows)); + } + public function index(ProductQuery $query) { $rows = $query->paginate(10); @@ -37,6 +44,7 @@ class ProductController extends Controller 'description' => 'required|string', 'image' => 'required|string', 'on_sale' => 'required|boolean', + 'stock_show' => 'required|boolean', 'limit_amount' => 'required|integer', 'vehicle_limit_amount' => 'required|integer', 'price' => 'required|numeric', @@ -49,8 +57,14 @@ class ProductController extends Controller 'images.*' => 'required|url', 'write_off_star' => 'required|array', 'write_off_star.*' => 'required|integer', + 'activity_id' => 'nullable|exists:activities,id', 'start_at' => 'required_if:model,' . Product::MODEL_SECKILL . '|date', 'end_at' => 'required_if:model,' . Product::MODEL_SECKILL . '|date', + 'group_size' => 'required_if:model,' . Product::MODEL_GROUP . '|integer|min:2', + 'group_limit' => 'required_if:model,' . Product::MODEL_GROUP . '|integer', + 'on_time' => 'required_if:model,' . Product::MODEL_GROUP . '|boolean', + 'group_start_at' => 'required_if:on_time,1|date', + 'group_end_at' => 'required_if:on_time,1|date', ], [], [ 'type' => '商品类型', 'model' => '商品模型', @@ -61,6 +75,7 @@ class ProductController extends Controller 'description' => '商品详情', 'image' => '主图', 'on_sale' => '是否在售', + 'stock_show' => '是否展示库存', 'need_verify' => '是否需要认证才能下单', 'limit_amount' => '限购数', 'vehicle_limit_amount' => '每辆车限购数', @@ -74,13 +89,19 @@ class ProductController extends Controller 'start_at' => '秒杀开始时间', 'end_at' => '秒杀结束时间', 'write_off_star' => '核销星级', + 'group_size' => '成团人数', + 'group_limit' => '限制成团数', + 'on_time' => '是否限制成团时间', + 'group_start_at' => '成团开始时间', + 'group_end_at' => '成团结束时间', + 'activity_id' => '活动ID' ]); $category = Category::find($request->category_id); if (is_null($category) || $category->is_directory == 1) { return $this->failed('the-category-can-not-add-product'); } - $write_off_star = implode(',',$data['write_off_star']); + $write_off_star = implode(',', $data['write_off_star']); $product->write_off_star = $write_off_star == 0 ? null : $write_off_star; unset($data['write_off_star']); @@ -92,8 +113,14 @@ class ProductController extends Controller ['product_id' => $product->id], ['start_at' => $data['start_at'], 'end_at' => $data['end_at']] ); + $product->load('seckill'); + } else if ($data['model'] == Product::MODEL_GROUP) { + GroupProduct::updateOrCreate( + ['product_id' => $product->id], + ['group_size' => $data['group_size'], 'group_limit' => $data['group_limit'], 'group_stock' => $data['group_limit'],'start_at' => $data['group_start_at'],'end_at' => $data['group_end_at'],'on_time' => $data['on_time']] + ); + $product->load('group'); } - $product->load('seckill'); return $this->resource(new ProductResource($product)); } @@ -114,6 +141,7 @@ class ProductController extends Controller 'description' => 'string', 'image' => 'string', 'on_sale' => 'boolean', + 'stock_show' => 'boolean', 'need_verify' => 'boolean', 'limit_amount' => 'integer', 'vehicle_limit_amount' => 'integer', @@ -123,10 +151,16 @@ class ProductController extends Controller 'order' => 'integer', 'selected' => 'boolean', 'images.*' => 'url', - 'write_off_star' => 'required|array', - 'write_off_star.*' => 'required|integer', + 'write_off_star' => 'array', + 'write_off_star.*' => 'integer', + 'activity_id' => 'nullable|exists:activities,id', 'start_at' => 'required_if:model,' . Product::MODEL_SECKILL . '|date', 'end_at' => 'required_if:model,' . Product::MODEL_SECKILL . '|date', + 'group_size' => 'required_if:model,' . Product::MODEL_GROUP . '|integer|min:2', + 'group_limit' => 'required_if:model,' . Product::MODEL_GROUP . '|integer', + 'on_time' => 'required_if:model,' . Product::MODEL_GROUP . '|boolean', + 'group_start_at' => 'required_if:on_time,1|date', + 'group_end_at' => 'required_if:on_time,1|date' ], [], [ 'type' => '商品类型', 'model' => '商品模型', @@ -137,6 +171,7 @@ class ProductController extends Controller 'description' => '商品详情', 'image' => '图片', 'on_sale' => '是否在售', + 'stock_show' => '是否展示库存', 'limit_amount' => '限购数', 'vehicle_limit_amount' => '每辆车限购数', 'price' => '价格', @@ -149,6 +184,13 @@ class ProductController extends Controller 'start_at' => '秒杀开始时间', 'end_at' => '秒杀结束时间', 'write_off_star' => '核销星级', + 'write_off_star.*' => '核销星级', + 'group_size' => '成团人数', + 'group_limit' => '限制成团数', + 'on_time' => '是否限制成团时间', + 'activity_id' => '活动ID', + 'group_start_at' => '成团开始时间', + 'group_end_at' => '成团结束时间' ]); if ($request->category) { $category = Category::find($request->category); @@ -156,9 +198,11 @@ class ProductController extends Controller return $this->failed('the-category-can-not-add-product'); } } - $write_off_star = implode(',',$data['write_off_star']); - $row->write_off_star = $write_off_star == 0 ? null : $write_off_star; - unset($data['write_off_star']); + if (isset($data['write_off_star'])) { + $write_off_star = implode(',', $data['write_off_star']); + $row->write_off_star = $write_off_star == 0 ? null : $write_off_star; + unset($data['write_off_star']); + } $row->fill($data); $row->save(); @@ -167,8 +211,16 @@ class ProductController extends Controller ['product_id' => $row->id], ['start_at' => $data['start_at'], 'end_at' => $data['end_at']] ); + $row->load('seckill'); + } else if ($request->model == Product::MODEL_GROUP) { + $group_stock = max($data['group_limit'] - $row->group_success_count, 0); + GroupProduct::updateOrCreate( + ['product_id' => $row->id], + ['group_size' => $data['group_size'], 'group_limit' => $data['group_limit'], 'group_stock' => $group_stock,'start_at' => $data['group_start_at'],'end_at' => $data['group_end_at'],'on_time' => $data['on_time']] + ); + $row->load('group'); } - $row->load('seckill'); + return $this->resource(new ProductResource($row)); } diff --git a/app/Http/Controllers/Admin/ProductZoneController.php b/app/Http/Controllers/Admin/ProductZoneController.php new file mode 100644 index 0000000000000000000000000000000000000000..d70cb341f969eeedbcafdcbba02403e053b268af --- /dev/null +++ b/app/Http/Controllers/Admin/ProductZoneController.php @@ -0,0 +1,93 @@ +get(); + return $this->resource(ProductZoneResource::collection($rows)); + } + + public function index(ProductZoneQuery $query) + { + $rows = $query->paginate(10); + return $this->resource(ProductZoneResource::collection($rows)); + } + + public function store(Request $request, ProductZone $productZone) + { + $data = $this->validate($request, [ + 'title' => 'required|string', + 'banner' => 'required|url', + 'order' => 'required|integer', + 'products' => 'required|array', + 'products.*' => 'integer', + 'describe' => 'required|string', + 'frame' => 'required|url', + 'show' => 'required|boolean', + 'end_at' => 'nullable|date_format:"Y-m-d H:i:s"', + ], [], [ + 'title' => '标题', + 'banner' => 'banner', + 'frame' => '专区框背景图', + 'order' => '序号', + 'products' => '商品组', + 'describe' => '描述', + 'show' => '是否显示', + 'end_at' => '结束时间', + ]); + $productZone->fill($data); + $productZone->save(); + $productZone->syncProducts($data['products']); + return $this->resource(new ProductZoneResource($productZone)); + } + + public function update($id, Request $request, ProductZone $productZone) + { + $row = $productZone->findOrFail($id); + $data = $this->validate($request, [ + 'title' => 'required|string', + 'banner' => 'required|url', + 'order' => 'required|integer', + 'products' => 'required|array', + 'products.*' => 'integer', + 'describe' => 'required|string', + 'show' => 'required|boolean', + 'end_at' => 'nullable|date_format:"Y-m-d H:i:s"', + 'frame' => 'required|url', + ], [], [ + 'title' => '标题', + 'banner' => 'banner', + 'order' => '序号', + 'products' => '商品组', + 'describe' => '描述', + 'show' => '是否显示', + 'frame' => '专区框背景图', + 'end_at' => '结束时间' + ]); + $row->fill($data); + $row->save(); + $row->syncProducts($data['products']); + return $this->resource(new ProductZoneResource($row)); + } + + public function destroy($id, ProductZone $productZone) + { + $data = $productZone->findOrFail($id); + $data->delete(); + return $this->message('删除成功'); + } + + public function show($id, ProductZoneQuery $query) + { + $row = $query->findOrFail($id); + return $this->resource(new ProductZoneResource($row)); + } +} diff --git a/app/Http/Controllers/Admin/ServiceOrderItemController.php b/app/Http/Controllers/Admin/ServiceOrderItemController.php index e313d6690d2dbf5a8cb21933cc06ce03170f8138..ee28eadb5a776f68d01c8b83dde7c0e7f31262f0 100644 --- a/app/Http/Controllers/Admin/ServiceOrderItemController.php +++ b/app/Http/Controllers/Admin/ServiceOrderItemController.php @@ -6,7 +6,7 @@ use App\Http\Queries\ServiceOrderItemQuery; use App\Http\Resources\ServiceOrderItemResource; use Illuminate\Http\Request; use Maatwebsite\Excel\Facades\Excel; -use App\Imports\InvoiceImport; +use App\Imports\ServiceOrderInvoiceImport; class ServiceOrderItemController extends Controller { @@ -30,7 +30,7 @@ class ServiceOrderItemController extends Controller 'xlsx' => '表格', ]); - Excel::import(new InvoiceImport, $request->xlsx); + Excel::import(new ServiceOrderInvoiceImport, $request->xlsx); return $this->message('导入成功'); } diff --git a/app/Http/Controllers/Web/ActivityController.php b/app/Http/Controllers/Web/ActivityController.php index 46c4166f14fe12dc8400ffeba481c614fea9b2ad..81e2cf3f215fc226ca40ecfb209cad0c852e09f2 100644 --- a/app/Http/Controllers/Web/ActivityController.php +++ b/app/Http/Controllers/Web/ActivityController.php @@ -8,6 +8,7 @@ use App\Models\Activity; use Illuminate\Http\Request; use App\Http\Resources\CustomerDrawResource; use App\Models\CustomerDraw; +use App\Http\Resources\ProductResource; class ActivityController extends Controller { @@ -66,4 +67,11 @@ class ActivityController extends Controller } return $this->success($data); } + + public function products($sign, ActivityQuery $query) + { + $activity = $query->where('sign', $sign)->firstOrFail(); + $rows = $activity->products()->paginate(10); + return $this->resource(ProductResource::collection($rows)); + } } diff --git a/app/Http/Controllers/Web/BannerController.php b/app/Http/Controllers/Web/BannerController.php index e863e8745d81a13c6f424961a6655ab46d2c7ce9..7a6996450b0df0abb8f6503938bff1f765f6b420 100644 --- a/app/Http/Controllers/Web/BannerController.php +++ b/app/Http/Controllers/Web/BannerController.php @@ -7,9 +7,9 @@ use App\Http\Resources\BannerResource; class BannerController extends Controller { - public function index(BannerQuery $query){ + public function index(BannerQuery $query) + { $rows = $query->where('valid', true)->paginate(10); return $this->resource(BannerResource::collection($rows)); } - } diff --git a/app/Http/Controllers/Web/CartItemController.php b/app/Http/Controllers/Web/CartItemController.php new file mode 100644 index 0000000000000000000000000000000000000000..0a5c074de08e9f638bc04be395fcc3d40d9cb7a9 --- /dev/null +++ b/app/Http/Controllers/Web/CartItemController.php @@ -0,0 +1,101 @@ +user()->cartItems()->with(['productSku.product'])->get(); + return $this->resource(CartItemResource::collection($cartItems)); + } + + public function num(Request $request) + { + $num = $request->user()->cartItems()->count(); + $data = [ + 'count' => $num + ]; + return $this->success($data); + } + + public function add(Request $request) + { + $customer = $request->user(); + $this->validate($request, [ + 'sku_id' => 'required|integer', + 'amount' => 'required|integer|min:1', + ], [], [ + 'sku_id' => '商品ID', + 'amount' => '数量', + ]); + + $sku = ProductSku::find($request->sku_id); + if (!$sku) { + return $this->failed(null, '该商品不存在'); + } + //只有普通商品能添加购物车,秒杀和拼图商品无法添加购物车 + if ($sku->product->model != Product::MODEL_NORMAL) { + return $this->failed(null, '秒杀和拼图商品无法添加购物车'); + } + if (!$sku->product->on_sale) { + return $this->failed(null, '该商品未上架'); + } + if ($sku->stock === 0) { + return $this->failed(null, '该商品已售完'); + } + if ($request->amount > 0 && $sku->stock < $request->amount) { + return $this->failed(null, '该商品库存不足'); + } + + $skuId = $request->sku_id; + $amount = $request->amount; + // 从数据库中查询该商品是否已经在购物车中 + if ($cart = $customer->cartItems()->where('product_sku_id', $skuId)->first()) { + // 如果存在则直接叠加商品数量 + $cart->update([ + 'amount' => $cart->amount + $amount, + ]); + } else { + + // 否则创建一个新的购物车记录 + $cart = new CartItem(['amount' => $amount]); + $cart->customer()->associate($customer); + $cart->productSku()->associate($skuId); + $cart->save(); + } + + return $this->message('添加成功'); + } + + public function remove(Request $request) + { + $data = $this->validate($request, [ + 'sku_ids' => 'required|array', + 'sku_ids.*' => 'required|integer', + ], [], [ + 'sku_ids' => '商品规格组', + 'sku_ids.*' => '商品规格ID', + ]); + $request->user()->cartItems()->whereIn('product_sku_id', $data['sku_ids'])->delete(); + return $this->message('移除成功'); + } + + public function sub(Request $request) + { + $this->validate($request, [ + 'sku_id' => 'required|integer', + ], [], [ + 'sku_id' => '商品规格ID', + ]); + $customer = $request->user(); + CartItem::where('product_sku_id', $request->sku_id)->where('customer_id', $customer->id)->where('amount', '>', 1)->decrement('amount', 1); + return $this->message('成功'); + } +} diff --git a/app/Http/Controllers/Web/CourtesyCardController.php b/app/Http/Controllers/Web/CourtesyCardController.php new file mode 100644 index 0000000000000000000000000000000000000000..db232d479f2fc2a85c79d4a9df00048a176e1ef4 --- /dev/null +++ b/app/Http/Controllers/Web/CourtesyCardController.php @@ -0,0 +1,95 @@ +user(); + $rows = $query->where('customer_id', $user->id)->paginate(10); + return $this->resource(CourtesyCardResource::collection($rows)); + } + + //下单时可用优惠券列表 + public function orderIndex(Request $request) + { + $data = $this->validate($request, [ + 'items' => 'required|array', + 'items.*' => 'required|array', + 'items.*.sku_id' => 'required|integer', + 'items.*.amount' => 'required|integer|min:1', + ], [], [ + 'items' => '商品组', + 'items.*' => '商品详情', + 'items.*.sku_id' => '商品规格ID', + 'items.*.amount' => '商品数量', + ]); + $items = $data['items']; + $customer = $request->user(); + $now = Carbon::now(); + + $sku_ids = array_column($items, 'sku_id'); + $amounts = array_combine($sku_ids, array_column($items, 'amount')); + $skus = ProductSku::whereIn('id', $sku_ids)->get(); + + $cards = CourtesyCard::where('customer_id', $customer->id)->where(function ($query) use ($now) { + $query->whereNull('due_at') + ->orWhere('due_at', '>=', $now); + })->whereNull('used_at')->where('enabled', true)->get(); + + $available = []; + $not_available = []; + foreach ($cards as $card) { + if ($card->valid_at && $card->valid_at->gt($now)) { + //未生效 + $not_available[] = [ + 'reason' => '未生效', + 'card' => new CourtesyCardResource($card) + ]; + continue; + } + + $cache_key = 'cardmould_' . $card->courtesy_card_mould_id . '_product_ids'; + $product_ids = Cache::remember($cache_key, 60, function () use ($card) { + return DB::table('product_courtesy_cards')->where('courtesy_card_mould_id', $card->courtesy_card_mould_id)->pluck('product_id')->toArray(); + }); + + $amount = 0; + foreach ($skus as $sku) { + if (in_array($sku->product_id, $product_ids)) { + $amount += $sku->price * $amounts[$sku->id]; + } + } + if ($amount < $card->min_amount) { + //订单金额不满足该优惠券最低金额 + $not_available[] = [ + 'reason' => '不满足最低使用金额', + 'card' => new CourtesyCardResource($card) + ]; + continue; + } else { + $available[] = [ + 'sub' => $amount - $card->getAdjustedPrice($amount), + 'card' => new CourtesyCardResource($card) + ]; + continue; + } + } + + $result = [ + 'available' => $available, + 'not_available' => $not_available + ]; + return $this->success($result); + } +} diff --git a/app/Http/Controllers/Web/CustomerAddressController.php b/app/Http/Controllers/Web/CustomerAddressController.php index e5a6c39d54d6eae36bc8ad126408005620bec016..457b8deec9df8f1847744a1fd5fbf5b9b39ac919 100644 --- a/app/Http/Controllers/Web/CustomerAddressController.php +++ b/app/Http/Controllers/Web/CustomerAddressController.php @@ -8,20 +8,22 @@ use App\Models\CustomerAddress; class CustomerAddressController extends Controller { - public function index(Request $request){ + public function index(Request $request) + { $customer = $request->user(); return $this->resource(CustomerAddressResource::collection($customer->addresses)); } - public function store(Request $request,CustomerAddress $customerAddress){ - $data = $this->validate($request,[ + public function store(Request $request, CustomerAddress $customerAddress) + { + $data = $this->validate($request, [ 'province' => 'required|string', 'city' => 'required|string', 'district' => 'required|string', 'address' => 'required|string', 'contact_name' => 'required|string|max:20', 'contact_phone' => 'required|phone:CN,mobile', - ],[],[ + ], [], [ 'province' => '省份', 'city' => '城市', 'district' => '区', @@ -36,17 +38,18 @@ class CustomerAddressController extends Controller return $this->resource(new CustomerAddressResource($customerAddress)); } - public function update($id,Request $request,CustomerAddress $customerAddress){ + public function update($id, Request $request, CustomerAddress $customerAddress) + { $address = $customerAddress->findOrFail($id); $this->authorize('update', $address); - $data = $this->validate($request,[ - 'province' => 'string', - 'city' => 'string', - 'district' => 'string', - 'address' => 'string', - 'contact_name' => 'string|max:20', - 'contact_phone' => 'phone:CN,mobile', - ],[],[ + $data = $this->validate($request, [ + 'province' => 'required|string', + 'city' => 'required|string', + 'district' => 'required|string', + 'address' => 'required|string', + 'contact_name' => 'required|string|max:20', + 'contact_phone' => 'required|phone:CN,mobile', + ], [], [ 'province' => '省份', 'city' => '城市', 'district' => '区', @@ -58,12 +61,12 @@ class CustomerAddressController extends Controller $address->save(); return $this->resource(new CustomerAddressResource($address)); } - - public function destroy($id,CustomerAddress $customerAddress){ + + public function destroy($id, CustomerAddress $customerAddress) + { $address = $customerAddress->findOrFail($id); $this->authorize('destroy', $address); $address->delete(); return $this->message('删除成功'); } - } diff --git a/app/Http/Controllers/Web/FollowerController.php b/app/Http/Controllers/Web/FollowerController.php index fe5ce6918f16eb89427715819c351f013c839904..53a7cae09e9a2a695a5d186335e024f9db2c86b4 100644 --- a/app/Http/Controllers/Web/FollowerController.php +++ b/app/Http/Controllers/Web/FollowerController.php @@ -39,7 +39,7 @@ class FollowerController extends Controller public function followings(Request $request) { $this->validate($request, [ - 'search' => 'string', + 'search' => 'nullable|string', ], [], [ 'search' => '关键字', ]); @@ -62,7 +62,7 @@ class FollowerController extends Controller public function followers(Request $request) { $this->validate($request, [ - 'search' => 'string', + 'search' => 'nullable|string', ], [], [ 'search' => '关键字', ]); diff --git a/app/Http/Controllers/Web/GroupController.php b/app/Http/Controllers/Web/GroupController.php new file mode 100644 index 0000000000000000000000000000000000000000..29420c44db3bd76ca486c7dc5012e858fd30f5b3 --- /dev/null +++ b/app/Http/Controllers/Web/GroupController.php @@ -0,0 +1,28 @@ +paginate(10); + foreach ($rows as &$row) { + $row->group_num = $row->orders()->whereNotNull('paid_at')->where('refund_status', '<>', Order::REFUND_STATUS_SUCCESS)->count(); + } + return $this->resource(GroupResource::collection($rows)); + } + + public function show($id, Group $group) + { + $group = $group->where('id', $id)->firstOrFail(); + $group->load('validOrders.customer'); + return $this->resource(new GroupResource($group)); + } +} diff --git a/app/Http/Controllers/Web/ImageController.php b/app/Http/Controllers/Web/ImageController.php index dc3746e8569ed9ede7266b887e5ec700f2d6c409..6620a889488b029f7c04e7cb4a877b974708b69f 100644 --- a/app/Http/Controllers/Web/ImageController.php +++ b/app/Http/Controllers/Web/ImageController.php @@ -16,7 +16,7 @@ class ImageController extends Controller 'type' => 'required|string', 'image' => 'required_without:image_base64|mimes:jpg,jpeg,bmp,png,gif|file|max:8192', 'image_base64' => 'required_without:image|string', - 'shape' => 'in:square' + 'shape' => 'nullable|in:square' ]); $user = $request->user(); $shape = $request->shape ?? false; diff --git a/app/Http/Controllers/Web/OrderController.php b/app/Http/Controllers/Web/OrderController.php index b613122d101afcd0ccacfe5ed03689106838d4b6..a9e9876d3841bffe8613643536ce44dfc3683e71 100644 --- a/app/Http/Controllers/Web/OrderController.php +++ b/app/Http/Controllers/Web/OrderController.php @@ -10,6 +10,10 @@ use App\Services\OrderService; use App\Models\ProductSku; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; +use App\Http\Resources\ParentOrderResource; +use App\Services\CheckService; +use App\Models\Product; +use App\Models\ParentOrder; class OrderController extends Controller { @@ -28,9 +32,9 @@ class OrderController extends Controller return $this->resource(new OrderResource($order)); } - public function store(Request $request, OrderService $orderService) + public function seckill(Request $request, OrderService $orderService, CheckService $checkService) { - // 秒杀商品随机拒绝 + // 随机拒绝 $percent = (int) config('app.random_drop_seckill_percent'); if (random_int(0, 100) < $percent) { throw_invalid_request_exception('please-try-again-later'); @@ -39,48 +43,217 @@ class OrderController extends Controller $this->validate($request, [ 'sku_id' => 'required|integer', 'amount' => 'required|integer|min:1', + 'vin' => 'nullable|regex:/^[A-HJ-NPR-Z\d]{8}$/', + 'store_id' => 'nullable|integer|exists:store_info,id', + 'courtesy_card_id' => 'nullable|integer', + 'address' => 'nullable|array', + 'address.full_address' => 'required_with:address|string|max:200', + 'address.contact_name' => 'required_with:address|string|max:20', + 'address.contact_phone' => 'required_with:address|phone:CN,mobile', ], [], [ - 'sku_id' => '商品规格 ID', + 'sku_id' => '商品规格ID', 'amount' => '数量', + 'vin' => '车架号', + 'store_id' => '服务站', + 'courtesy_card_id' => '优惠券ID', + 'address' => '收件人信息', + 'address.full_address' => '收件人地址', + 'address.contact_name' => '收件人姓名', + 'address.contact_phone' => '收件人手机号', ]); if (!$sku = ProductSku::find($request->sku_id)) { return $this->failed('the-product-sku-not-found'); } + //判断商品规格是否有效 + $checkService->checkSku($sku, $request->amount); + //判断商品是否有效 + $checkService->checkSeckillProduct($sku->product); + //判断身份密钥 + if (!$customer = $request->user()) { + return $this->failed('authentication-exception'); + } + $order = $orderService->store($customer, $sku, $request->amount, $request->address, $request->store_id, $request->vin, $request->courtesy_card_id); + $order->refresh(); + return $this->resource(new ParentOrderResource($order)); + } + + public function group(Request $request, OrderService $orderService, CheckService $checkService) + { + $this->validate($request, [ + 'sku_id' => 'required|integer', + 'amount' => 'required|integer|min:1', + 'vin' => 'nullable|regex:/^[A-HJ-NPR-Z\d]{8}$/', + 'store_id' => 'nullable|integer|exists:store_info,id', + 'group_id' => 'nullable|integer', + 'courtesy_card_id' => 'nullable|integer', + 'address' => 'nullable|array', + 'address.full_address' => 'required_with:address|string|max:200', + 'address.contact_name' => 'required_with:address|string|max:20', + 'address.contact_phone' => 'required_with:address|phone:CN,mobile', + ], [], [ + 'sku_id' => '商品规格ID', + 'amount' => '数量', + 'vin' => '车架号', + 'store_id' => '服务站', + 'group_id' => '拼团ID', + 'courtesy_card_id' => '优惠券ID', + 'address' => '收件人信息', + 'address.full_address' => '收件人地址', + 'address.contact_name' => '收件人姓名', + 'address.contact_phone' => '收件人手机号', + ]); - if (!$sku->on_sale) { - throw_invalid_request_exception('the-sku-not-on-sale'); + if (!$sku = ProductSku::find($request->sku_id)) { + return $this->failed('the-product-sku-not-found'); } - if ($sku->stock === 0) { - throw_invalid_request_exception('the-product-has-been-sold-out'); + //判断商品规格是否有效 + $checkService->checkSku($sku, $request->amount); + //判断商品是否有效 + $checkService->checkGroupProduct($sku->product); + //判断身份密钥 + if (!$customer = $request->user()) { + return $this->failed('authentication-exception'); + } + + $order = $orderService->store($customer, $sku, $request->amount, $request->address, $request->store_id, $request->vin, $request->courtesy_card_id, $request->group_id); + $order->refresh(); + $order->load('orders'); + return $this->resource(new ParentOrderResource($order)); + } + + public function store(Request $request, OrderService $orderService, CheckService $checkService) + { + $this->validate($request, [ + 'sku_id' => 'required|integer', + 'amount' => 'required|integer|min:1', + 'vin' => 'nullable|regex:/^[A-HJ-NPR-Z\d]{8}$/', + 'store_id' => 'nullable|integer|exists:store_info,id', + 'courtesy_card_id' => 'nullable|integer', + 'address' => 'nullable|array', + 'address.full_address' => 'required_with:address|string|max:200', + 'address.contact_name' => 'required_with:address|string|max:20', + 'address.contact_phone' => 'required_with:address|phone:CN,mobile', + ], [], [ + 'sku_id' => '商品规格ID', + 'amount' => '数量', + 'vin' => '车架号', + 'store_id' => '服务站', + 'courtesy_card_id' => '优惠券ID', + 'address' => '收件人信息', + 'address.full_address' => '收件人地址', + 'address.contact_name' => '收件人姓名', + 'address.contact_phone' => '收件人手机号', + ]); + + if (!$sku = ProductSku::find($request->sku_id)) { + return $this->failed('the-product-sku-not-found'); } - if ($request->amount > $sku->stock) { - throw_invalid_request_exception('the-product-out-of-stock'); + //判断商品规格是否有效 + $checkService->checkSku($sku, $request->amount); + //判断商品是否有效 + $checkService->checkNormalProduct($sku->product); + //判断身份密钥 + if (!$customer = $request->user()) { + return $this->failed('authentication-exception'); } - if (!$user = $request->user()) { - throw_invalid_request_exception('authentication-exception'); + $order = $orderService->store($customer, $sku, $request->amount, $request->address, $request->store_id, $request->vin, $request->courtesy_card_id); + $order->refresh(); + $order->load('orders'); + return $this->resource(new ParentOrderResource($order)); + } + + //判断下单需要哪些字段 + public function cartStoreFields(Request $request) + { + $data = $this->validate($request, [ + 'items' => 'required|array', + 'items.*' => 'required|array', + 'items.*.sku_id' => 'required|integer', + ], [], [ + 'items' => '商品组', + 'items.*' => '商品详情', + 'items.*.sku_id' => '商品规格ID', + ]); + $items = $data['items']; + $vin = false; + $store_id = false; + $address = false; + // 遍历用户提交的 SKU + foreach ($items as $item) { + $sku = ProductSku::findOrFail($item['sku_id']); + if ($vin == false && ($sku->product->vehicle_limit_amount > 0 || $sku->product->category->vehicle_limit_product_amount > 0)) { + $vin = true; + } + if ($store_id == false && $sku->product->type == Product::TYPE_COUPON) { + $store_id = true; + } + if ($address == false && $sku->product->type == Product::TYPE_NORMAL) { + $address = true; + } } + $data = [ + 'vin' => $vin, + 'store_id' => $store_id, + 'address' => $address + ]; - $order = $orderService->store($user, $sku, $request->amount, $request->address, $request->store_id, $request->vin); - $data = app('App\Services\OrderService')->pay($order); - $data['order'] = new OrderResource($order); return $this->success($data); } + public function cartStore(Request $request, OrderService $orderService) + { + $this->validate($request, [ + 'items' => 'required|array', + 'items.*' => 'required|array', + 'items.*.sku_id' => 'required|integer', + 'items.*.amount' => 'required|integer|min:1', + 'vin' => 'nullable|regex:/^[A-HJ-NPR-Z\d]{8}$/', + 'store_id' => 'nullable|integer|exists:store_info,id', + 'courtesy_card_id' => 'nullable|integer', + 'address' => 'nullable|array', + 'address.full_address' => 'required_with:address|string|max:200', + 'address.contact_name' => 'required_with:address|string|max:20', + 'address.contact_phone' => 'required_with:address|phone:CN,mobile', + ], [], [ + 'items' => '商品组', + 'items.*' => '商品详情', + 'items.*.sku_id' => '商品规格ID', + 'items.*.amount' => '商品数量', + 'vin' => '车架号', + 'store_id' => '服务站', + 'courtesy_card_id' => '优惠券ID', + 'address' => '收件人信息', + 'address.full_address' => '收件人地址', + 'address.contact_name' => '收件人姓名', + 'address.contact_phone' => '收件人手机号', + ]); + + if (!$customer = $request->user()) { + throw_invalid_request_exception('authentication-exception'); + } + + $order = $orderService->cartStore($customer, $request->items, $request->address, $request->store_id, $request->vin, $request->courtesy_card_id); + $order->refresh(); + $sku_ids = array_column($request->items, 'sku_id'); + $request->user()->cartItems()->whereIn('product_sku_id', $sku_ids)->delete(); + return $this->resource(new ParentOrderResource($order)); + } + public function cancel($id) { - $order = Order::findOrFail($id); + $order = ParentOrder::findOrFail($id); + $this->authorize('own', $order); app('App\Services\OrderService')->cancel($order); return $this->message('取消成功'); } public function pay($id, Request $request) { - $order = Order::where('id', $id)->firstOrFail(); + $order = ParentOrder::where('id', $id)->firstOrFail(); $this->authorize('own', $order); $customer = $request->user(); - $order->customer()->associate($customer); $data = app('App\Services\OrderService')->pay($order); return $this->success($data); @@ -102,6 +275,10 @@ class OrderController extends Controller if (!$order->paid_at) { return $this->failed('order-status-error', '该订单未支付,不可退款'); } + // 拼团订单不能退款 + if ($order->group_id) { + return $this->failed(null, '拼团订单无法退款'); + } // 判断订单退款状态是否正确 if ($order->refund_status !== Order::REFUND_STATUS_PENDING) { return $this->failed('order-status-error', '该订单已经申请过退款,请勿重复申请'); @@ -137,7 +314,31 @@ class OrderController extends Controller $order->save(); return $this->resource(new OrderResource($order)); } else { - return $this->failed('order-status-error', '此订单非未收货状态,无法确认收货'); + return $this->failed('order-status-error', '订单状态错误'); + } + } + + public function applyInvoice($id) + { + $order = Order::where('id', $id)->firstOrFail(); + + // 校验订单是否属于当前用户 + $this->authorize('own', $order); + // 判断订单是否已付款 + if (!$order->paid_at) { + return $this->failed('order-status-error', '该订单未支付,不可申请发票'); } + //未产生实际支付金额 + if ($order->payment_amount <= 0) { + return $this->failed('order-status-error', '该订单未产生实际支付金额'); + } + // 判断订单退款状态是否正确 + if ($order->apply_invoice_at) { + return $this->failed('order-status-error', '该订单已经申请过发票,请勿重复申请'); + } + $order->update([ + 'apply_invoice_at' => Carbon::now() + ]); + return $this->message('申请已提交'); } } diff --git a/app/Http/Controllers/Web/ParentOrderController.php b/app/Http/Controllers/Web/ParentOrderController.php new file mode 100644 index 0000000000000000000000000000000000000000..3af53f7c7448299e42325d5105fa22f883a0fc37 --- /dev/null +++ b/app/Http/Controllers/Web/ParentOrderController.php @@ -0,0 +1,25 @@ +user(); + $rows = $query->where('customer_id', $customer->id)->paginate(10); + return $this->resource(ParentOrderResource::collection($rows)); + } + + public function show($id, ParentOrderQuery $query) + { + $order = $query->where('id', $id)->firstOrFail(); + $this->authorize('own', $order); + return $this->resource(new ParentOrderResource($order)); + } +} diff --git a/app/Http/Controllers/Web/PaymentController.php b/app/Http/Controllers/Web/PaymentController.php index fc068e64e0c8dc1aef85041aab08780d02b44c5f..a2d24c0480ae6f16e74f60375008b5f88d7c8b82 100644 --- a/app/Http/Controllers/Web/PaymentController.php +++ b/app/Http/Controllers/Web/PaymentController.php @@ -2,8 +2,7 @@ namespace App\Http\Controllers\Web; -use App\Models\Order; -use Illuminate\Support\Facades\Log; +use App\Models\ParentOrder; use App\Models\ServiceOrder; class PaymentController extends Controller @@ -22,7 +21,7 @@ class PaymentController extends Controller return true; } else if ($message['return_code'] === 'SUCCESS') { // TODO: 你的发货逻辑 - $order = Order::where('no', $message['out_trade_no'])->first(); + $order = ParentOrder::where('no', $message['out_trade_no'])->first(); if ($order) { app('App\Services\OrderService')->payCompleted($order, $message['transaction_id']); } diff --git a/app/Http/Controllers/Web/ProductZoneController.php b/app/Http/Controllers/Web/ProductZoneController.php new file mode 100644 index 0000000000000000000000000000000000000000..8b3f1583695b87a5f4951bc18da78f4222f4a394 --- /dev/null +++ b/app/Http/Controllers/Web/ProductZoneController.php @@ -0,0 +1,32 @@ +where('show', true)->get(); + foreach ($rows as &$row) { + $row->load('topProducts'); + } + return $this->resource(ProductZoneResource::collection($rows)); + } + + public function show($id, ProductZoneQuery $query) + { + $product_zone = $query->findOrFail($id); + return $this->resource(new ProductZoneResource($product_zone)); + } + + public function products($id, ProductZoneQuery $query) + { + $product_zone = $query->findOrFail($id); + $rows = $product_zone->products()->paginate(10); + return $this->resource(ProductResource::collection($rows)); + } +} diff --git a/app/Http/Controllers/Web/TopicController.php b/app/Http/Controllers/Web/TopicController.php index bee99b718f8d3cf8b967cda425f33720d4eb0f3a..9ff54a0abd29dbb8a8a386348e759cb525648cb7 100644 --- a/app/Http/Controllers/Web/TopicController.php +++ b/app/Http/Controllers/Web/TopicController.php @@ -164,13 +164,13 @@ class TopicController extends Controller 'title' => 'required|string', 'banner' => 'required|url', 'body' => 'required|string', - 'images' => 'array', - 'posts' => 'array', - 'posts.*' => 'exists:topic_posts,id', - 'area' => 'string', - 'vehicle_serie' => 'string', - 'video' => 'url', - 'activity_id' => 'exists:activities,id', + 'images' => 'nullable|array', + 'posts' => 'nullable|array', + 'posts.*' => 'nullable|exists:topic_posts,id', + 'area' => 'nullable|string', + 'vehicle_serie' => 'nullable|string', + 'video' => 'nullable|url', + 'activity_id' => 'nullable|exists:activities,id', ], [], [ 'title' => '标题', 'banner' => '封面', diff --git a/app/Http/Queries/ActivityQuery.php b/app/Http/Queries/ActivityQuery.php index c94148bae9760508863f231fea6fa19c1650bd3a..68599bb8ebbd0c919029afb7b7602792e6f11601 100644 --- a/app/Http/Queries/ActivityQuery.php +++ b/app/Http/Queries/ActivityQuery.php @@ -11,7 +11,7 @@ class ActivityQuery extends QueryBuilder public function __construct() { parent::__construct(Activity::query()); - $this->allowedIncludes('prizes','tasks'); + $this->allowedIncludes('prizes', 'tasks', 'products'); $this->allowedFilters([ 'title', AllowedFilter::exact('sign'), diff --git a/app/Http/Queries/CourtesyCardMouldQuery.php b/app/Http/Queries/CourtesyCardMouldQuery.php new file mode 100644 index 0000000000000000000000000000000000000000..ecd085754f7d289f74f177f55b90d6cf243860db --- /dev/null +++ b/app/Http/Queries/CourtesyCardMouldQuery.php @@ -0,0 +1,20 @@ +allowedIncludes('products'); + $this->allowedFilters([ + 'name', + ]); + $this->defaultSort('-id'); + } +} diff --git a/app/Http/Queries/CourtesyCardQuery.php b/app/Http/Queries/CourtesyCardQuery.php new file mode 100644 index 0000000000000000000000000000000000000000..ed853be2f993b18043c0273169b32ab0f980d9ac --- /dev/null +++ b/app/Http/Queries/CourtesyCardQuery.php @@ -0,0 +1,25 @@ +allowedIncludes('courtesyCardMould', 'customer'); + $this->allowedFilters([ + 'name', + AllowedFilter::exact('customer_id'), + AllowedFilter::scope('status'), + AllowedFilter::scope('start_at'), + AllowedFilter::scope('end_at'), + AllowedFilter::exact('customer.phone'), + ]); + $this->defaultSort('-id'); + } +} diff --git a/app/Http/Queries/GroupQuery.php b/app/Http/Queries/GroupQuery.php new file mode 100644 index 0000000000000000000000000000000000000000..c026c72ea19a9637590531855f28fac90a7141d1 --- /dev/null +++ b/app/Http/Queries/GroupQuery.php @@ -0,0 +1,22 @@ +allowedIncludes('product', 'customer', 'orders'); + $this->allowedFilters([ + AllowedFilter::exact('customer_id'), + AllowedFilter::exact('product_id'), + AllowedFilter::exact('status'), + ]); + $this->defaultSort('id'); + } +} diff --git a/app/Http/Queries/OrderQuery.php b/app/Http/Queries/OrderQuery.php index e40aca94a45033d30747560ef09e02fc548a505a..6dd8641dc0749a85f772f226ff2626223321a98b 100644 --- a/app/Http/Queries/OrderQuery.php +++ b/app/Http/Queries/OrderQuery.php @@ -11,7 +11,7 @@ class OrderQuery extends QueryBuilder public function __construct() { parent::__construct(Order::query()); - $this->allowedIncludes('product','productSku','store'); + $this->allowedIncludes('customer', 'product', 'productSku', 'store', 'category', 'parentOrder','group'); $this->allowedFilters([ AllowedFilter::exact('no'), AllowedFilter::exact('customer_id'), @@ -19,9 +19,13 @@ class OrderQuery extends QueryBuilder AllowedFilter::exact('category_id'), AllowedFilter::exact('product_sku_id'), AllowedFilter::exact('store_id'), + AllowedFilter::exact('group_id'), + AllowedFilter::exact('parent_order_id'), AllowedFilter::exact('customer.phone'), AllowedFilter::scope('status'), AllowedFilter::scope('refund'), + AllowedFilter::scope('invoice'), + AllowedFilter::scope('start'), AllowedFilter::scope('end') ]); diff --git a/app/Http/Queries/ParentOrderQuery.php b/app/Http/Queries/ParentOrderQuery.php new file mode 100644 index 0000000000000000000000000000000000000000..f4afba2e347fc70daca8b6456ba39f2095b6b84e --- /dev/null +++ b/app/Http/Queries/ParentOrderQuery.php @@ -0,0 +1,24 @@ +allowedIncludes('courtesyCard', 'orders.product', 'orders.productSku', 'store'); + $this->allowedFilters([ + AllowedFilter::exact('no'), + AllowedFilter::exact('courtesy_card_id'), + AllowedFilter::exact('customer_id'), + AllowedFilter::exact('customer.phone'), + AllowedFilter::scope('status'), + ]); + $this->defaultSort('-id'); + } +} diff --git a/app/Http/Queries/ProductQuery.php b/app/Http/Queries/ProductQuery.php index 2d234c3df3b63b1e9cbbb9e1bb33dea17c95ab97..37d90e4f280bd3d8584da1af545236bd4760ec0d 100644 --- a/app/Http/Queries/ProductQuery.php +++ b/app/Http/Queries/ProductQuery.php @@ -6,12 +6,12 @@ use App\Models\Product; use Spatie\QueryBuilder\QueryBuilder; use Spatie\QueryBuilder\AllowedFilter; -class ProductQuery extends QueryBuilder +class ProductQuery extends QueryBuilder { public function __construct() { parent::__construct(Product::query()); - $this->allowedIncludes('category','skus','onSaleSkus','seckill'); + $this->allowedIncludes('category', 'skus', 'onSaleSkus', 'seckill', 'group'); $this->allowedFilters([ 'title', AllowedFilter::scope('category_id'), diff --git a/app/Http/Queries/ProductZoneQuery.php b/app/Http/Queries/ProductZoneQuery.php new file mode 100644 index 0000000000000000000000000000000000000000..dbbb5466e90626dc5736092cf94f790c9a2a0065 --- /dev/null +++ b/app/Http/Queries/ProductZoneQuery.php @@ -0,0 +1,21 @@ +allowedIncludes('products'); + $this->allowedFilters([ + 'title', + AllowedFilter::exact('id'), + ]); + $this->defaultSort('-id'); + } +} diff --git a/app/Http/Resources/CartItemResource.php b/app/Http/Resources/CartItemResource.php new file mode 100644 index 0000000000000000000000000000000000000000..ce41d40487fc9511b935fe2c08ecd46aae15aa70 --- /dev/null +++ b/app/Http/Resources/CartItemResource.php @@ -0,0 +1,24 @@ +showAllField) { + $this->resource->makeHidden(['updated_at']); + } + $data = parent::toArray($request); + $data['product_sku'] = new ProductSkuResource($this->whenLoaded('productSku')); + return $data; + } +} diff --git a/app/Http/Resources/CourtesyCardMouldResource.php b/app/Http/Resources/CourtesyCardMouldResource.php new file mode 100644 index 0000000000000000000000000000000000000000..90131e6060c4594285affd5608d9b6d68242e45b --- /dev/null +++ b/app/Http/Resources/CourtesyCardMouldResource.php @@ -0,0 +1,26 @@ +showAllField) { + $this->resource->makeHidden(['updated_at']); + } + $data = parent::toArray($request); + $data['products'] = ProductResource::collection($this->whenLoaded('products')); + $data['valid_at_type'] = $this->valid_at_type; + $data['valid_at_value'] = $this->valid_at_value; + $data['due_at_type'] = $this->due_at_type; + $data['due_at_value'] = $this->due_at_value; + return $data; + } +} diff --git a/app/Http/Resources/CourtesyCardResource.php b/app/Http/Resources/CourtesyCardResource.php new file mode 100644 index 0000000000000000000000000000000000000000..a8a37daf6c7e3a2699aa4c1f976e3cee203fb99f --- /dev/null +++ b/app/Http/Resources/CourtesyCardResource.php @@ -0,0 +1,25 @@ +showAllField) { + $this->resource->makeHidden(['updated_at']); + } + $data = parent::toArray($request); + $data['description'] = $this->description; + $data['courtesy_card_mould'] = new CourtesyCardMouldResource($this->whenLoaded('courtesyCardMould')); + + return $data; + } +} diff --git a/app/Http/Resources/GroupResource.php b/app/Http/Resources/GroupResource.php new file mode 100644 index 0000000000000000000000000000000000000000..efd0446adc800890360a4f39238bcac0e896113b --- /dev/null +++ b/app/Http/Resources/GroupResource.php @@ -0,0 +1,20 @@ +whenLoaded('customer')); + $data['valid_orders'] = OrderResource::collection($this->whenLoaded('validOrders')); + return $data; + } +} diff --git a/app/Http/Resources/ParentOrderResource.php b/app/Http/Resources/ParentOrderResource.php new file mode 100644 index 0000000000000000000000000000000000000000..a5f3f80fe156117c60d06f4684bd5eb85a59c064 --- /dev/null +++ b/app/Http/Resources/ParentOrderResource.php @@ -0,0 +1,24 @@ +status; + $data['status_ch'] = $this->status_ch; + $data['refund_status_ch'] = $this->refund_status_ch; + $data['customer'] = new CustomerResource($this->whenLoaded('customer')); + $data['orders'] = OrderResource::collection($this->whenLoaded('orders')); + $data['courtesy_card'] = new CourtesyCardResource($this->whenLoaded('courtesyCard')); + return $data; + } +} diff --git a/app/Http/Resources/ProductZoneResource.php b/app/Http/Resources/ProductZoneResource.php new file mode 100644 index 0000000000000000000000000000000000000000..129b48fe2111d669bb86b9224ccd279a4f0a8cab --- /dev/null +++ b/app/Http/Resources/ProductZoneResource.php @@ -0,0 +1,23 @@ +showAllField) { + $this->resource->makeHidden(['updated_at']); + } + $data = parent::toArray($request); + $data['products'] = ProductResource::collection($this->whenLoaded('products')); + $data['top_products'] = ProductResource::collection($this->whenLoaded('topProducts')); + return $data; + } +} diff --git a/app/Imports/OrderInvoiceImport.php b/app/Imports/OrderInvoiceImport.php new file mode 100644 index 0000000000000000000000000000000000000000..d1e760a928ef78028366c361d823e1b39817831b --- /dev/null +++ b/app/Imports/OrderInvoiceImport.php @@ -0,0 +1,22 @@ +toArray(); + foreach ($rows as $row) { + Order::where('id', $row['id'])->update(['invoice_url' => $row['invoice']]); + } + } +} diff --git a/app/Imports/OrderbatchShipImport.php b/app/Imports/OrderbatchShipImport.php new file mode 100644 index 0000000000000000000000000000000000000000..8713eb72e373a6a940733669155fd3e7c2083667 --- /dev/null +++ b/app/Imports/OrderbatchShipImport.php @@ -0,0 +1,56 @@ +toArray(); + foreach ($rows as $row) { + $order = Order::where('id', $row['id'])->first(); + + if (!$order) { + continue; + } + + // 判断当前订单是否已支付 + if (!$order->paid_at) { + continue; + } + // 判断当前订单发货状态是否为未发货 + if ($order->ship_status !== Order::SHIP_STATUS_PENDING) { + continue; + } + // 判断当前订单发货状态是否已退款 + if ($order->refund_status == Order::REFUND_STATUS_SUCCESS) { + continue; + } + // 判断当前顶是否为卡券订单 + if ($order->type == Order::TYPE_COUPON) { + continue; + } + + $data = [ + 'express_company' => $row['express_company'], + 'express_no' => $row['express_no'] + ]; + + // 将订单发货状态改为已发货,并存入物流信息 + $order->update([ + 'ship_status' => Order::SHIP_STATUS_DELIVERED, + 'send_at' => Carbon::now(), + 'ship_data' => $data, + ]); + } + } +} diff --git a/app/Imports/InvoiceImport.php b/app/Imports/ServiceOrderInvoiceImport.php similarity index 70% rename from app/Imports/InvoiceImport.php rename to app/Imports/ServiceOrderInvoiceImport.php index 800568e9c3bb21fd8f734abab264dc69083762a9..6eccf4ddbbfd9daa7fcca13f02903cdb739f9b15 100644 --- a/app/Imports/InvoiceImport.php +++ b/app/Imports/ServiceOrderInvoiceImport.php @@ -8,7 +8,7 @@ use App\Models\ServiceOrderItem; use Maatwebsite\Excel\Concerns\WithHeadingRow; use Maatwebsite\Excel\Concerns\Importable; -class InvoiceImport implements ToCollection, WithHeadingRow +class ServiceOrderInvoiceImport implements ToCollection, WithHeadingRow { use Importable; @@ -16,8 +16,7 @@ class InvoiceImport implements ToCollection, WithHeadingRow { $rows = $rows->toArray(); foreach ($rows as $row) { - ServiceOrderItem::where('no',$row['no'])->update(['invoice' => $row['invoice']]); + ServiceOrderItem::where('no', $row['no'])->update(['invoice' => $row['invoice']]); } } - } diff --git a/app/Jobs/CloseOrderJob.php b/app/Jobs/CloseOrderJob.php index 42d113935ef74cf50f03c4a1018915891b5968cf..14a53852fc6a7b910b2326dd5a9446b41bcaebc5 100644 --- a/app/Jobs/CloseOrderJob.php +++ b/app/Jobs/CloseOrderJob.php @@ -2,13 +2,13 @@ namespace App\Jobs; -use App\Models\Order; +use App\Models\ParentOrder; class CloseOrderJob extends Job { protected $order; - public function __construct(Order $order, $delay) + public function __construct(ParentOrder $order, $delay) { $this->order = $order; // 设置延迟的时间,delay() 方法的参数代表多少秒之后执行 @@ -22,10 +22,12 @@ class CloseOrderJob extends Job return; } // 如果已经取消订单,直接退出 - if ($this->order->closed) { + if ($this->order->closed_at) { return; } - app('App\Services\OrderService')->cancel($this->order); + if (!app()->environment('local')) { + app('App\Services\OrderService')->cancel($this->order); + } return true; } } diff --git a/app/Jobs/DuePrizeJob.php b/app/Jobs/DuePrizeJob.php index 55cd6852c6ded0ab287734d4ff6ae9ab1d4ee3f7..a8fb852cdb8d8a14cb52f67b1e6f32113fd3314a 100644 --- a/app/Jobs/DuePrizeJob.php +++ b/app/Jobs/DuePrizeJob.php @@ -5,7 +5,6 @@ namespace App\Jobs; use App\Models\CustomerCoupon; use App\Models\ActivityPrize; use Illuminate\Support\Carbon; -use Illuminate\Support\Facades\Log; class DuePrizeJob extends Job { @@ -13,11 +12,11 @@ class DuePrizeJob extends Job protected $activityPrize; - public function __construct(Array $activityPrize, CustomerCoupon $customerCoupon) + public function __construct(array $activityPrize, CustomerCoupon $customerCoupon) { $this->customerCoupon = $customerCoupon; $this->activityPrize = $activityPrize; - + // 设置延迟的时间,delay() 方法的参数代表多少秒之后执行 $this->delay(Carbon::parse($this->customerCoupon->due_at)); } @@ -30,8 +29,8 @@ class DuePrizeJob extends Job } //返还库存 - ActivityPrize::where('id',$this->activityPrize['id'])->increment('stock', 1); - + ActivityPrize::where('id', $this->activityPrize['id'])->increment('stock', 1); + return true; } } diff --git a/app/Jobs/GiveDrawTimesJob.php b/app/Jobs/GiveDrawTimesJob.php new file mode 100644 index 0000000000000000000000000000000000000000..6bd7be0769f9f54f19aac4707361edd38c0786e2 --- /dev/null +++ b/app/Jobs/GiveDrawTimesJob.php @@ -0,0 +1,23 @@ +order = $order; + } + + public function handle() + { + if ($this->order->product->activity_id) { + CustomerDraw::insertRecord($this->order->customer_id, $this->order->product->activity_id, '', $this->order->amount, 'order', $this->order->id); + } + } +} diff --git a/app/Jobs/GroupCancelNotifyJob.php b/app/Jobs/GroupCancelNotifyJob.php new file mode 100644 index 0000000000000000000000000000000000000000..23b7eb11909c2d040ff1bcea12f08dda9690556e --- /dev/null +++ b/app/Jobs/GroupCancelNotifyJob.php @@ -0,0 +1,25 @@ +order = $order; + } + + public function handle() + { + $now = Carbon::now()->toDateTimeString(); + + app(MessageTemplateService::class)->orderCancelNotify($this->order->customer->wechat_openid, $this->order->product->title, $this->order->no, $now); + return true; + } +} diff --git a/app/Jobs/GroupCompleteJob.php b/app/Jobs/GroupCompleteJob.php new file mode 100644 index 0000000000000000000000000000000000000000..bbb387502743589aa48f3f3fff661ff6614d5c80 --- /dev/null +++ b/app/Jobs/GroupCompleteJob.php @@ -0,0 +1,44 @@ +group = $group; + } + + public function handle() + { + $this->group->status = Group::STATUS_SUCCESS; + $this->group->save(); + $now = Carbon::now(); + //剩余成团数-1 不能小于0 + $this->group->product->group->group_stock = max($this->group->product->group->group_stock - 1, 0); + $this->group->product->group->save(); + $orders = $this->group->orders()->whereNotNull('paid_at')->where('refund_status', '<>', Order::REFUND_STATUS_SUCCESS)->get(); + foreach ($orders as $order) { + if ($order->ship_status == Order::SHIP_STATUS_GROUPING) { + //如果是券订单,需要去发券 + if ($order->type == Order::TYPE_COUPON) { + app('App\Services\OrderService')->orderCoupon($order); + //卡券订单直接已收货状态 + $order->ship_status = Order::SHIP_STATUS_RECEIVED; + $order->take_at = $now; + } else { + //成团的订单物流状态修改为待发货 + $order->ship_status = Order::SHIP_STATUS_PENDING; + } + $order->save(); + } + } + return true; + } +} diff --git a/app/Jobs/GroupGroupingNotifyJob.php b/app/Jobs/GroupGroupingNotifyJob.php new file mode 100644 index 0000000000000000000000000000000000000000..ab3e0c2312ad50ce97c3401f88fb13246a5b142b --- /dev/null +++ b/app/Jobs/GroupGroupingNotifyJob.php @@ -0,0 +1,33 @@ +group = $group; + } + + public function handle() + { + //不是拼团中 直接跳过 + if($this->group->status != Group::STATUS_PROCESSING){ + return true; + } + $orders = $this->group->orders()->whereNotNull('paid_at')->where('refund_status', '<>', Order::REFUND_STATUS_SUCCESS)->get(); + $now = Carbon::now()->toDateTimeString(); + + foreach ($orders as $order) { + app(MessageTemplateService::class)->orderGroupingNotify($order->customer->wechat_openid,$order->product->title,$order->no,$now); + } + return true; + } +} diff --git a/app/Jobs/GroupSuccessNotifyJob.php b/app/Jobs/GroupSuccessNotifyJob.php new file mode 100644 index 0000000000000000000000000000000000000000..128934263820f1511964713a6317d0f4fd52db1f --- /dev/null +++ b/app/Jobs/GroupSuccessNotifyJob.php @@ -0,0 +1,29 @@ +group = $group; + } + + public function handle() + { + $orders = $this->group->orders()->whereNotNull('paid_at')->where('refund_status', '<>', Order::REFUND_STATUS_SUCCESS)->get(); + $now = Carbon::now()->toDateTimeString(); + + foreach ($orders as $order) { + app(MessageTemplateService::class)->orderCompleteNotify($order->customer->wechat_openid,$order->product->title,$order->no,$now); + } + return true; + } +} diff --git a/app/Jobs/ProductSoldCountJob.php b/app/Jobs/ProductSoldCountJob.php index 123d89bee7cb5178495bf2cd8a7a5d22db34daf4..0192cb59977ee4f1d42d2cccefa2ebc5abb6b9cb 100644 --- a/app/Jobs/ProductSoldCountJob.php +++ b/app/Jobs/ProductSoldCountJob.php @@ -16,7 +16,7 @@ class ProductSoldCountJob extends Job public function handle() { - $count = Order::where('product_id',$this->product->id)->whereNotNull('paid_at')->where('refund_status','!=',Order::REFUND_STATUS_SUCCESS)->count(); + $count = Order::where('product_id', $this->product->id)->whereNotNull('paid_at')->where('refund_status', '!=', Order::REFUND_STATUS_SUCCESS)->sum('amount'); $this->product->sold_count = $count; $this->product->save(); return true; diff --git a/app/Models/Activity.php b/app/Models/Activity.php index 9263abd2e5e994e7e66adac638c65e89441e5c5f..e0f6aa21b0dd4be2d5232178f306ed949fd16036 100644 --- a/app/Models/Activity.php +++ b/app/Models/Activity.php @@ -64,13 +64,14 @@ class Activity extends Model * @var string[] */ protected $fillable = [ - 'title', 'sign', 'link', 'valid', 'order', 'banner', 'content', 'type', 'poster', 'start_at', 'end_at', 'sign', 'link_type', 'class', 'labels', 'qrcode', 'show' + 'title', 'sign', 'link', 'valid', 'order', 'banner', 'content', 'type', 'poster', 'start_at', 'end_at', 'sign', 'link_type', 'class', 'labels', 'qrcode', 'show', 'limit_draw_count' ]; protected $casts = [ 'valid' => 'boolean', 'show' => 'boolean', 'order' => 'integer', + 'limit_draw_count' => 'integer' ]; protected $dates = [ @@ -168,4 +169,9 @@ class Activity extends Model return $query; } } + + public function products() + { + return $this->hasMany(Product::class); + } } diff --git a/app/Models/CartItem.php b/app/Models/CartItem.php new file mode 100644 index 0000000000000000000000000000000000000000..a6f346181e3282c403a637cd77dc0dfb2a6dd99d --- /dev/null +++ b/app/Models/CartItem.php @@ -0,0 +1,29 @@ + 'integer', + 'product_sku_id' => 'integer', + 'amount' => 'integer' + ]; + + public function customer() + { + return $this->belongsTo(Customer::class); + } + + public function productSku() + { + return $this->belongsTo(ProductSku::class); + } +} diff --git a/app/Models/CourtesyCard.php b/app/Models/CourtesyCard.php new file mode 100644 index 0000000000000000000000000000000000000000..3a166eb08e134fe17cbbd36e1a483c4ca05b5ffb --- /dev/null +++ b/app/Models/CourtesyCard.php @@ -0,0 +1,149 @@ + '已使用', + self::STATUS_OVERDUE => '已过期', + self::STATUS_INVALID => '待使用' + ]; + + public static $typeMap = [ + self::TYPE_FIXED => '固定金额', + self::TYPE_PERCENT => '比例', + ]; + + protected $dates = ['valid_at', 'due_at']; + + public function getDescriptionAttribute() + { + $str = ''; + + if ($this->min_amount > 0) { + $str = '满' . str_replace('.00', '', $this->min_amount); + } + if ($this->type === self::TYPE_PERCENT) { + return $str . '优惠' . str_replace('.00', '', $this->value) . '%'; + } + + return $str . '减' . str_replace('.00', '', $this->value); + } + + protected $fillable = ['customer_id', 'name', 'type', 'value', 'min_amount', 'valid_at', 'due_at', 'total', 'used', 'courtesy_card_mould_id', 'enabled']; + + protected $casts = [ + 'min_amount' => 'float', + 'total' => 'integer', + 'used' => 'integer', + 'enabled' => 'boolean' + ]; + + public function getAdjustedPrice($orderAmount) + { + // 固定金额 + if ($this->type === self::TYPE_FIXED) { + // 为了保证系统健壮性,我们需要订单金额最少为 0.01 元 + return max(0.01, $orderAmount - $this->value); + } + + return number_format($orderAmount * (100 - $this->value) / 100, 2, '.', ''); + } + + public function checkAvailable(Customer $customer, $orderAmount = null) + { + + if (!$this->enabled) { + throw_invalid_request_exception('courtesy-card-invalid', [], '优惠券不可用'); + } + + if ($this->used_at) { + throw_invalid_request_exception('courtesy-card-invalid', [], '优惠券已被使用'); + } + + if ($this->valid_at && $this->valid_at->gt(Carbon::now())) { + throw_invalid_request_exception('courtesy-card-invalid', [], '该优惠券现在还不能使用'); + } + + if ($this->due_at && $this->due_at->lt(Carbon::now())) { + throw_invalid_request_exception('courtesy-card-invalid', [], '该优惠券已过期'); + } + + if (!is_null($orderAmount) && $orderAmount < $this->min_amount) { + throw_invalid_request_exception('courtesy-card-invalid', [], '订单金额不满足该优惠券最低金额'); + } + + if ($customer->id != $this->customer_id) { + throw_invalid_request_exception('courtesy-card-invalid', [], '该优惠券不属于您'); + } + } + + public function use() + { + $this->used_at = Carbon::now(); + $this->save(); + return true; + } + + public function scopeStatus($query, $status) + { + $now = Carbon::now()->toDateTimeString(); + switch ($status) { + case self::STATUS_USED: + return $query->whereNotNull('used_at'); + break; + case self::STATUS_OVERDUE: + return $query->where('due_at', '<', $now)->whereNotNull('used_at'); + break; + case self::STATUS_INVALID: + return $query->where(function ($query) use ($now) { + $query->whereNull('due_at') + ->orWhere('due_at', '>=', $now); + })->whereNull('used_at')->where('enabled', true); + break; + default: + return $query; + } + } + + public function courtesyCardMould() + { + return $this->belongsTo(CourtesyCardMould::class); + } + + public function customer() + { + return $this->belongsTo(Customer::class); + } + + public function scopeStartAt($query, $value) + { + if (empty($value)) { + return $query; + } + return $query->where('created_at', '>=', $value); + } + + public function scopeEndAt($query, $value) + { + if (empty($value)) { + return $query; + } + return $query->where('created_at', '<=', $value); + } +} diff --git a/app/Models/CourtesyCardMould.php b/app/Models/CourtesyCardMould.php new file mode 100644 index 0000000000000000000000000000000000000000..2eee24bce5e1d407d73f91c474b5df6b64e9620c --- /dev/null +++ b/app/Models/CourtesyCardMould.php @@ -0,0 +1,139 @@ + '固定金额', + self::TYPE_PERCENT => '比例', + ]; + + public function getDescriptionAttribute() + { + $str = ''; + + if ($this->min_amount > 0) { + $str = '满' . str_replace('.00', '', $this->min_amount); + } + if ($this->type === self::TYPE_PERCENT) { + return $str . '优惠' . str_replace('.00', '', $this->value) . '%'; + } + + return $str . '减' . str_replace('.00', '', $this->value); + } + + public static function findAvailableCode($length = 8) + { + do { + // 生成一个指定长度的随机字符串,并转成大写 + $code = strtoupper(Str::random($length)); + // 如果生成的码已存在就继续循环 + } while (self::query()->where('code', $code)->exists()); + + return $code; + } + + protected static function boot() + { + parent::boot(); + // 监听模型创建事件,在写入数据库之前触发 + static::creating(function ($model) { + if (!$model->code) { + $model->code = static::findAvailableCode(); + if (!$model->code) { + return false; + } + } + }); + } + + protected $fillable = ['code', 'name', 'type', 'value', 'min_amount', 'valid_at', 'due_at', 'used']; + + protected $casts = [ + 'min_amount' => 'float', + 'used' => 'integer' + ]; + + public function products() + { + return $this->belongsToMany(Product::class, 'product_courtesy_cards')->withTimestamps(); + } + + public function canUseBy($product_id) + { + return $this->products->contains($product_id); + } + + public function syncProducts($product_ids) + { + $this->products()->sync($product_ids); + } + + public function distributeCards(Customer $customer, $count) + { + $due_at = date('Y-m-d H:i:s', strtotime($this->due_at)); + $valid_at = date('Y-m-d H:i:s', strtotime($this->valid_at)); + for ($i = 0; $i < $count; $i++) { + CourtesyCard::create([ + 'customer_id' => $customer->id, + 'courtesy_card_mould_id' => $this->id, + 'name' => $this->name, + 'type' => $this->type, + 'value' => $this->value, + 'min_amount' => $this->min_amount, + 'valid_at' => $valid_at, + 'due_at' => $due_at, + 'enabled' => true + ]); + } + return true; + } + + public function getValidAtTypeAttribute() + { + if (is_datetime($this->valid_at)) { + return 'datetime'; + } else { + return 'days'; + } + } + + public function getDueAtTypeAttribute() + { + if (is_datetime($this->due_at)) { + return 'datetime'; + } else { + return 'days'; + } + } + + public function getValidAtValueAttribute() + { + if ($this->valid_at_type == 'datetime') { + return $this->valid_at; + } else { + return find_num_from_string($this->valid_at); + } + } + + public function getDueAtValueAttribute() + { + if ($this->due_at_type == 'datetime') { + return $this->due_at; + } else { + return find_num_from_string($this->due_at); + } + } +} diff --git a/app/Models/Customer.php b/app/Models/Customer.php index a5406a0c5899c7f768f0b420fc515d690ca4c207..2873cbd112b6bbe76933275be8a498d284e71fff 100644 --- a/app/Models/Customer.php +++ b/app/Models/Customer.php @@ -200,5 +200,13 @@ class Customer extends Model public function topics() { return $this->hasMany(Topic::class); + } + + public function cartItems() + { + return $this->hasMany(CartItem::class); + + + } } diff --git a/app/Models/CustomerDraw.php b/app/Models/CustomerDraw.php index 209f347b7c8c5d176055a2816c23799de605515f..3997161fe3f91da5030580d1b6762ba7c54363e6 100644 --- a/app/Models/CustomerDraw.php +++ b/app/Models/CustomerDraw.php @@ -67,6 +67,14 @@ class CustomerDraw extends Model public static function insertRecord($customer_id, $activity_id, $vin = null, $count = 1, $from_type = null, $from_id = null) { + $activity = Activity::findOrFail($activity_id); + if ($activity->limit_draw_count > 0) { + $num_1 = static::where('activity_id', $activity_id)->where('customer_id', $customer_id)->count(); + $num_2 = $activity->limit_draw_count - $num_1; + $count = min(max($num_2, 0), $count); + if ($count < 1) return true; + } + $now = Carbon::now()->toDateTimeString(); $row = [ 'activity_id' => $activity_id, diff --git a/app/Models/Group.php b/app/Models/Group.php new file mode 100644 index 0000000000000000000000000000000000000000..ad78014a3bb03fac1ff7e1ca7c68166de88c2c4e --- /dev/null +++ b/app/Models/Group.php @@ -0,0 +1,55 @@ + '待支付', + self::STATUS_PROCESSING => '拼团中', + self::STATUS_SUCCESS => '成功', + self::STATUS_FAILED => '失败' + ]; + + /** + * The attributes that are mass assignable. + * + * @var string[] + */ + protected $fillable = [ + 'group_size', 'valid_at', 'status', 'customer_id', 'product_id' + ]; + + protected $casts = [ + 'group_size' => 'integer', + ]; + + public function customer() + { + return $this->belongsTo(Customer::class); + } + + public function orders() + { + return $this->hasMany(Order::class); + } + + public function validOrders() + { + return $this->hasMany(Order::class)->whereNotNull('paid_at'); + } + + public function product() + { + return $this->belongsTo(Product::class); + } +} diff --git a/app/Models/GroupProduct.php b/app/Models/GroupProduct.php new file mode 100644 index 0000000000000000000000000000000000000000..1f9ec82ea27ae699d30888c3b2c981e526b76904 --- /dev/null +++ b/app/Models/GroupProduct.php @@ -0,0 +1,43 @@ + 'integer', + 'group_limit' => 'integer', + 'group_stock' => 'integer' + ]; + + protected $dates = ['start_at', 'end_at']; + + public function product() + { + return $this->belongsTo(Product::class); + } + + // 定义一个名为 is_before_start 的访问器,当前时间早于秒杀开始时间时返回 true + public function getIsBeforeStartAttribute() + { + return Carbon::now()->lt($this->start_at); + } + + // 定义一个名为 is_after_end 的访问器,当前时间晚于秒杀结束时间时返回 true + public function getIsAfterEndAttribute() + { + return Carbon::now()->gt($this->end_at); + } + + +} diff --git a/app/Models/Order.php b/app/Models/Order.php index 8b1b8c2bee5c76bb1f53c5d4d2c2bef5d838784f..3d37b0a7c328106f83f0d8472d11e8c5631ec773 100644 --- a/app/Models/Order.php +++ b/app/Models/Order.php @@ -15,12 +15,14 @@ class Order extends Model const REFUND_STATUS_SUCCESS = 'success'; const REFUND_STATUS_FAILED = 'failed'; + const SHIP_STATUS_GROUPING = 'grouping'; const SHIP_STATUS_PENDING = 'pending'; const SHIP_STATUS_DELIVERED = 'delivered'; const SHIP_STATUS_RECEIVED = 'received'; const STATUS_CANCELED = 'canceled'; const STATUS_TO_BE_PAID = 'to_be_paid'; + const STATUS_TO_BE_GROUPED = 'to_be_grouped'; const STATUS_TO_BE_SHIPPED = 'to_be_shipped'; const STATUS_TO_BE_RECEIVED = 'to_be_received'; const STATUS_COMPLETED = 'completed'; @@ -34,6 +36,7 @@ class Order extends Model ]; public static $shipStatusMap = [ + self::SHIP_STATUS_GROUPING => '未成团', self::SHIP_STATUS_PENDING => '未发货', self::SHIP_STATUS_DELIVERED => '已发货', self::SHIP_STATUS_RECEIVED => '已收货', @@ -42,6 +45,7 @@ class Order extends Model public static $statusMap = [ self::STATUS_CANCELED => '已取消', self::STATUS_TO_BE_PAID => '待付款', + self::STATUS_TO_BE_GROUPED => '待成团', self::STATUS_TO_BE_SHIPPED => '待发货', self::STATUS_TO_BE_RECEIVED => '待收货', self::STATUS_COMPLETED => '已收货', @@ -63,12 +67,8 @@ class Order extends Model 'total_point', 'remark', 'paid_at', - 'payment_method', - 'payment_no', 'refund_status', 'refund_no', - 'closed', - 'reviewed', 'ship_status', 'ship_data', 'extra', @@ -85,22 +85,26 @@ class Order extends Model 'category_id', 'closed_at', 'store_id', - 'vin' + 'vin', + 'payment_amount', + 'group_id', + 'apply_invoice_at', + 'invoice_url' ]; protected $casts = [ - 'closed' => 'boolean', - 'reviewed' => 'boolean', 'address' => 'json', 'ship_data' => 'json', 'extra' => 'json', 'total_amount' => 'float', + 'payment_amount' => 'float', 'total_point' => 'integer', 'amount' => 'integer', 'price' => 'float', 'point' => 'integer', 'rating' => 'integer', 'product_id' => 'integer', + 'store_id' => 'integer', 'product_sku_id' => 'integer', 'category_id' => 'integer', ]; @@ -111,6 +115,7 @@ class Order extends Model 'send_at', 'take_at', 'closed_at', + 'apply_invoice_at' ]; protected static function boot() @@ -135,11 +140,21 @@ class Order extends Model return $this->belongsTo(Product::class); } + public function parentOrder() + { + return $this->belongsTo(ParentOrder::class); + } + public function store() { return $this->belongsTo(Store::class); } + public function group() + { + return $this->belongsTo(Group::class); + } + public function productSku() { return $this->belongsTo(ProductSku::class); @@ -159,13 +174,16 @@ class Order extends Model { switch ($status) { case self::STATUS_CANCELED: //已取消 - return $query->where('closed', true); + return $query->whereNotNull('closed_at'); break; case self::STATUS_TO_BE_PAID: //待支付 - return $query->where('closed', false)->whereNull('paid_at'); + return $query->whereNull('closed_at')->whereNull('paid_at'); + break; + case self::STATUS_TO_BE_GROUPED: //待成团 + return $query->whereNotNull('paid_at')->where('ship_status', self::SHIP_STATUS_GROUPING)->where('refund_status', '<>', self::REFUND_STATUS_SUCCESS); break; case self::STATUS_TO_BE_SHIPPED: //待发货 - return $query->whereNotNull('paid_at')->where('ship_status', self::SHIP_STATUS_PENDING)->where('refund_status', self::REFUND_STATUS_PENDING); + return $query->whereNotNull('paid_at')->where('ship_status', self::SHIP_STATUS_PENDING)->where('refund_status', '<>', self::REFUND_STATUS_SUCCESS); break; case self::STATUS_TO_BE_RECEIVED: //待收货 return $query->whereNotNull('paid_at')->where('ship_status', self::SHIP_STATUS_DELIVERED)->where('refund_status', '<>', self::REFUND_STATUS_SUCCESS); @@ -227,12 +245,15 @@ class Order extends Model public function getStatusAttribute() { - if ($this->closed) { + if ($this->closed_at) { // 已取消 return self::STATUS_CANCELED; } else { if ($this->paid_at) { switch ($this->ship_status) { + case self::SHIP_STATUS_GROUPING: + return self::STATUS_TO_BE_GROUPED; + break; case self::SHIP_STATUS_PENDING: return self::STATUS_TO_BE_SHIPPED; break; @@ -252,7 +273,7 @@ class Order extends Model public function getStatusChAttribute() { - if ($this->closed) { + if ($this->closed_at) { // 已取消 return self::$statusMap[self::STATUS_CANCELED]; } else { @@ -280,6 +301,19 @@ class Order extends Model return CustomerCoupon::where('type_id', $this->id)->where('type', CustomerCoupon::TYPE_ORD)->where('valid', true)->get(); } + public function scopeInvoice($query, $value) + { + if ($value == 'pending') { + return $query->whereNull('apply_invoice_at')->whereNull('invoice_url'); + } else if ($value == 'applying') { + return $query->whereNotNull('apply_invoice_at')->whereNull('invoice_url'); + } else if ($value == 'invoiced') { + $query->whereNotNull('invoice_url'); + } else { + return $query; + } + } + public function scopeStart($query, $value) { if (empty($value)) { @@ -295,5 +329,4 @@ class Order extends Model } return $query->where('created_at', '<=', $value); } - } diff --git a/app/Models/ParentOrder.php b/app/Models/ParentOrder.php new file mode 100644 index 0000000000000000000000000000000000000000..1139f06980bb66d57dcd0eea92f3652a2e7880dd --- /dev/null +++ b/app/Models/ParentOrder.php @@ -0,0 +1,183 @@ + '未退款', + self::REFUND_STATUS_PARTIAL => '部分退款', + self::REFUND_STATUS_SUCCESS => '退款成功' + ]; + + const STATUS_CANCELED = 'canceled'; + const STATUS_TO_BE_PAID = 'to_be_paid'; + const STATUS_COMPLETED = 'completed'; + + public static $statusMap = [ + self::STATUS_CANCELED => '已取消', + self::STATUS_TO_BE_PAID => '待付款', + self::STATUS_COMPLETED => '已完成', + ]; + + protected $fillable = [ + 'no', + 'customer_id', + 'total_amount', + 'payment_amount', + 'total_point', + 'paid_at', + 'payment_method', + 'payment_no', + 'payment_point_record_id', + 'address', + 'closed_at', + 'remark', + 'extra', + 'refund_status', + 'courtesy_card_id', + 'vin', + 'store_id', + 'address', + 'created_at', + 'updated_at', + ]; + + protected $casts = [ + 'address' => 'json', + 'extra' => 'json', + 'total_amount' => 'float', + 'payment_amount' => 'float', + 'total_point' => 'integer', + 'customer_id' => 'integer', + 'payment_point_record_id' => 'integer', + 'courtesy_card_id' => 'integer', + 'store_id' => 'integer', + ]; + + protected $dates = [ + 'paid_at', + 'closed_at', + ]; + + protected static function boot() + { + parent::boot(); + // 监听模型创建事件,在写入数据库之前触发 + static::creating(function ($model) { + // 如果模型的 no 字段为空 + if (!$model->no) { + // 调用 findAvailableNo 生成订单流水号 + $model->no = static::findAvailableNo(); + // 如果生成失败,则终止创建订单 + if (!$model->no) { + return false; + } + } + }); + } + + public function getStatusAttribute() + { + if ($this->closed_at) { + // 已取消 + return self::STATUS_CANCELED; + } else { + if ($this->paid_at) { + return self::STATUS_COMPLETED; + } else { + //待支付 + return self::STATUS_TO_BE_PAID; + } + } + } + + public function getStatusChAttribute() + { + if ($this->closed_at) { + // 已取消 + return self::$statusMap[self::STATUS_CANCELED]; + } else { + if ($this->paid_at) { + if ($this->refund_status == self::REFUND_STATUS_PENDING) { + return self::$statusMap[self::STATUS_COMPLETED]; + } else if ($this->refund_status == self::REFUND_STATUS_PARTIAL) { + return self::$refundStatusMap[self::REFUND_STATUS_PARTIAL]; + } else { + return self::$refundStatusMap[self::REFUND_STATUS_SUCCESS]; + } + } else { + //待支付 + return self::$statusMap[self::STATUS_TO_BE_PAID]; + } + } + } + + public function getRefundStatusChAttribute() + { + return self::$refundStatusMap[$this->refund_status]; + } + + public function customer() + { + return $this->belongsTo(Customer::class); + } + + public function courtesyCard() + { + return $this->belongsTo(CourtesyCard::class); + } + + public function paymentPointRecord() + { + return $this->belongsTo(CustomerPointRecord::class, 'payment_point_record_id'); + } + + public static function findAvailableNo() + { + // 订单流水号前缀 + $prefix = date('YmdHis'); + do { + // 随机生成 6 位的数字 + $no = $prefix . str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT); + } while (self::query()->where('no', $no)->exists()); + + return $no; + } + + public function orders() + { + return $this->hasMany(Order::class); + } + + public function scopeStatus($query, $status) + { + switch ($status) { + case self::STATUS_CANCELED: //已取消 + return $query->whereNotNull('closed_at'); + break; + case self::STATUS_TO_BE_PAID: //待支付 + return $query->whereNull('closed_at')->whereNull('paid_at'); + break; + case self::STATUS_COMPLETED: //已完成 + return $query->whereNotNull('paid_at')->where('refund_status', self::REFUND_STATUS_PENDING); + break; + default: + return $query; + } + } + + public function store() + { + return $this->belongsTo(Store::class); + } +} diff --git a/app/Models/Product.php b/app/Models/Product.php index 01e9ce1d9c3979a9fb07ed20b4adc06464feb246..0119cecfd06df3f88de0baa0775ba38ba5a95420 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -4,6 +4,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Support\Str; +use Illuminate\Support\Facades\Storage; class Product extends Model { @@ -21,10 +22,12 @@ class Product extends Model const MODEL_NORMAL = 'normal'; const MODEL_SECKILL = 'seckill'; + const MODEL_GROUP = 'group'; public static $modelMap = [ self::MODEL_NORMAL => '普通商品', self::MODEL_SECKILL => '秒杀', + self::MODEL_GROUP => '拼团', ]; protected $fillable = [ @@ -51,7 +54,8 @@ class Product extends Model 'write_off_star', 'org_id', 'vehicle_limit_amount', - 'need_verify' + 'need_verify', + 'stock_show' ]; protected $casts = [ 'on_sale' => 'boolean', @@ -68,6 +72,7 @@ class Product extends Model 'vehicle_limit_amount' => 'integer', 'images' => 'json', 'org_id' => 'integer', + 'stock_show' => 'boolean' ]; public function seckill() @@ -75,6 +80,11 @@ class Product extends Model return $this->hasOne(SeckillProduct::class); } + public function group() + { + return $this->hasOne(GroupProduct::class); + } + // 与商品SKU关联 public function skus() { @@ -116,7 +126,7 @@ class Product extends Model if (Str::startsWith($this->attributes['image'], ['http://', 'https://'])) { return $this->attributes['image']; } - return \Storage::disk('public')->url($this->attributes['image']); + return Storage::disk('public')->url($this->attributes['image']); } public function getTypeChAttribute() @@ -153,4 +163,34 @@ class Product extends Model } return $query->where('point', '<=', $value); } + + public function courtesyCardMoulds() + { + return $this->belongsToMany(CourtesyCardMould::class, 'product_courtesy_cards')->withTimestamps(); + } + + public function isContainCard($courtesy_card_mould_id) + { + return $this->courtesyCardMoulds->contains($courtesy_card_mould_id); + } + + public function syncCourtesyCardMoulds($courtesy_card_mould_ids) + { + $this->courtesyCardMoulds()->sync($courtesy_card_mould_ids); + } + + public function zones() + { + return $this->belongsToMany(ProductZone::class, 'zone_products')->withTimestamps(); + } + + public function activity() + { + return $this->belongsTo(Activity::class); + } + + public function getGroupSuccessCountAttribute() + { + return Group::where('product_id', $this->id)->where('status', Group::STATUS_SUCCESS)->count(); + } } diff --git a/app/Models/ProductZone.php b/app/Models/ProductZone.php new file mode 100644 index 0000000000000000000000000000000000000000..94505302cc3a3057ea8d514db81fa2eb853a4bc8 --- /dev/null +++ b/app/Models/ProductZone.php @@ -0,0 +1,31 @@ + 'integer', + ]; + + public function products() + { + return $this->belongsToMany(Product::class, 'zone_products')->withTimestamps(); + } + + public function syncProducts($product_ids) + { + $this->products()->sync($product_ids); + } + + public function topProducts() + { + return $this->belongsToMany(Product::class, 'zone_products')->where('on_sale', true)->orderBy('order', 'desc')->limit(3); + } +} diff --git a/app/Models/SeckillProduct.php b/app/Models/SeckillProduct.php index ce83b92c347b2c128ac089225232380b586a7385..ef9dd676cf2a5903309a0805fee3de621bf0f57f 100644 --- a/app/Models/SeckillProduct.php +++ b/app/Models/SeckillProduct.php @@ -10,7 +10,7 @@ class SeckillProduct extends Model use HasFactory; // 调整管理后台时间展示格式 - protected $fillable = ['start_at', 'end_at','product_id']; + protected $fillable = ['start_at', 'end_at', 'product_id']; protected $dates = ['start_at', 'end_at']; public $timestamps = false; @@ -30,5 +30,4 @@ class SeckillProduct extends Model { return Carbon::now()->gt($this->end_at); } - -} \ No newline at end of file +} diff --git a/app/Models/ServiceOrder.php b/app/Models/ServiceOrder.php index 879276d3efdca24c82e8f4e7dc1943a3b88390ac..757fa66b0608c5b42a4dd43fb1140b0420c5350b 100644 --- a/app/Models/ServiceOrder.php +++ b/app/Models/ServiceOrder.php @@ -27,15 +27,15 @@ class ServiceOrder extends Model ]; protected $fillable = [ - 'no', - 'vin', + 'no', + 'vin', 'original_amount', - 'total_amount', - 'total_point', + 'total_amount', + 'total_point', 'paid_at', - 'payment_method', - 'payment_no', - 'closed', + 'payment_method', + 'payment_no', + 'closed', 'category' ]; @@ -125,5 +125,4 @@ class ServiceOrder extends Model { return self::$statusMap[$this->status]; } - } diff --git a/app/Observers/ActivityObserver.php b/app/Observers/ActivityObserver.php index 8a1aca84e0d4a50c68d49e008947b505a2d1436a..57142bf8d02db014039bc55848e31f593cdf7028 100644 --- a/app/Observers/ActivityObserver.php +++ b/app/Observers/ActivityObserver.php @@ -17,7 +17,7 @@ class ActivityObserver $activity->qrcode = app('App\Services\WechatService')->getUnlimitedQRCode($link_arr[0], $scene); } catch (Exception $e) { throw_invalid_request_exception(null, [], '生成小程序码失败'); - } } } } +} diff --git a/app/Observers/GroupObserver.php b/app/Observers/GroupObserver.php new file mode 100644 index 0000000000000000000000000000000000000000..ffb31f6fd9120cd9dec31d3be9513763bfaefca6 --- /dev/null +++ b/app/Observers/GroupObserver.php @@ -0,0 +1,24 @@ +isDirty('status')) { + //拼团成功后发送模板消息 + if ($group->status == Group::STATUS_SUCCESS) { + //推送模板消息 + dispatch(new GroupSuccessNotifyJob($group)); + } elseif ($group->status == Group::STATUS_PROCESSING) { //拼团中一小时后发送模板消息 + dispatch(new GroupGroupingNotifyJob($group, 3600)); + } + } + } +} diff --git a/app/Policies/ParentOrderPolicy.php b/app/Policies/ParentOrderPolicy.php new file mode 100644 index 0000000000000000000000000000000000000000..f6b097dc7d6463498917167ec207719ad1a2444f --- /dev/null +++ b/app/Policies/ParentOrderPolicy.php @@ -0,0 +1,14 @@ +isAuthorOf($order); + } +} diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index fdfac7efaaacb0abda0c522a916a16d16f986232..8073ebb236fad09eae3624332b3247a51e82eea4 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -29,5 +29,6 @@ class EventServiceProvider extends ServiceProvider \App\Models\AgentBinding::observe(\App\Observers\AgentBindingObserver::class); \App\Models\TopicComment::observe(\App\Observers\TopicCommentObserver::class); \App\Models\TopicSubsetComment::observe(\App\Observers\TopicSubsetCommentObserver::class); + \App\Models\Group::observe(\App\Observers\GroupObserver::class); } } diff --git a/app/Providers/CouponServiceProvider.php b/app/Providers/SelfServiceProvider.php similarity index 71% rename from app/Providers/CouponServiceProvider.php rename to app/Providers/SelfServiceProvider.php index 527b03d03c9bf3560186d08b77b73a3e0671081d..222e0e6c63a1e1f710e8da86919a4757f1d13839 100644 --- a/app/Providers/CouponServiceProvider.php +++ b/app/Providers/SelfServiceProvider.php @@ -5,8 +5,9 @@ namespace App\Providers; use App\Services\CouponService; use App\Services\CarCouponService; use Illuminate\Support\ServiceProvider; +use App\Services\CheckService; -class CouponServiceProvider extends ServiceProvider +class SelfServiceProvider extends ServiceProvider { /** * Register any application services. @@ -24,8 +25,10 @@ class CouponServiceProvider extends ServiceProvider return new CarCouponService(); }); $this->app->alias(CarCouponService::class, 'carCoupon'); - } + $this->app->singleton(CheckService::class, function ($app) { + return new CheckService(); + }); + $this->app->alias(CheckService::class, 'checkService'); + } } - - diff --git a/app/Services/ActionService.php b/app/Services/ActionService.php index 6a0d40d74527675cf658be01fe9404d5a3540303..98fbf75933ce6319aea7b7aebef9d9290a4372f8 100644 --- a/app/Services/ActionService.php +++ b/app/Services/ActionService.php @@ -7,7 +7,6 @@ use App\Models\CustomerPointRecord; use App\Models\CustomerHonorRecord; use App\Models\Customer; use App\Models\CustomerActionRecord; -use Illuminate\Support\Facades\Cache; class ActionService { diff --git a/app/Services/CheckService.php b/app/Services/CheckService.php new file mode 100644 index 0000000000000000000000000000000000000000..ee965e1cda6ed6de8b01f679d4e125d22a3b24c0 --- /dev/null +++ b/app/Services/CheckService.php @@ -0,0 +1,223 @@ +on_sale) { + throw_invalid_request_exception('the-sku-not-on-sale'); + } + //判断库存 + if ($sku->stock === 0) { + throw_invalid_request_exception('the-product-has-been-sold-out'); + } + //判断库存 + if ($amount > $sku->stock) { + throw_invalid_request_exception('the-product-out-of-stock'); + } + return true; + } + + public function checkSeckillProduct(Product $product) + { + if ($product->model != Product::MODEL_SECKILL) { + throw_invalid_request_exception('the-product-not-seckill'); + } + //判断商品是否在售 + if (!$product->on_sale) { + throw_invalid_request_exception('the-product-not-on-sale'); + } + //判断秒杀是否开始 + if ($product->seckill->is_before_start) { + throw_invalid_request_exception('seckill-is-before-start'); + } + //判断秒杀是否结束 + if ($product->seckill->is_after_end) { + throw_invalid_request_exception('seckill-is-after-end'); + } + return true; + } + + public function checkNormalProduct(Product $product) + { + if ($product->model != Product::MODEL_NORMAL) { + throw_invalid_request_exception('common-exception', [], '该商品非普通商品'); + } + //判断商品是否在售 + if (!$product->on_sale) { + throw_invalid_request_exception('the-product-not-on-sale'); + } + return true; + } + + public function checkGroupProduct(Product $product) + { + if ($product->model != Product::MODEL_GROUP) { + throw_invalid_request_exception('common-exception', [], '该商品非拼团商品'); + } + //判断商品是否在售 + if (!$product->on_sale) { + throw_invalid_request_exception('the-product-not-on-sale'); + } + return true; + } + + public function checkAddress($address) + { + $address = $address ?? []; + Validator::make($address, [ + 'full_address' => 'required|string', + 'contact_name' => 'required|string', + 'contact_phone' => 'required|phone:CN,mobile', + ], [], [ + 'full_address' => '收货地址', + 'contact_name' => '收件人', + 'contact_phone' => '收件人手机号', + ])->validate(); + return true; + } + + public function checkStoreId($store_id) + { + $data = ['store_id' => $store_id]; + Validator::make($data, [ + 'store_id' => 'required|integer|exists:store_info,id' + ], [], [ + 'store_id' => '服务站' + ])->validate(); + return true; + } + + public function checkVin($vin, Customer $customer) + { + //判断车辆归属 + if (!$customer->isMyCar($vin)) { + throw_invalid_request_exception('is-not-customer-vin'); + } + return true; + } + + //检测商品限购 + public function checkLimitAmount(Customer $customer, Product $product, $amount) + { + if ($product->limit_amount > 0) { + //查询未关闭且非退款成功的数量和 + $total = Order::where('customer_id', $customer->id)->whereNull('closed_at')->where('refund_status', '<>', Order::REFUND_STATUS_SUCCESS)->where('product_id', $product->id)->sum('amount'); + if (($total + $amount) > $product->limit_amount) { + throw_invalid_request_exception('the-product-limited', [], $product->title . '限购' . $product->limit_amount . '笔,无法下单'); + } + } + return true; + } + + public function checkVehicleLimitAmount($vin, Product $product, $amount) + { + if ($product->vehicle_limit_amount > 0) { + $vehicle_buy_total = Order::where('vin', $vin)->whereNull('closed_at')->where('refund_status', '<>', Order::REFUND_STATUS_SUCCESS)->where('product_id', $product->id)->sum('amount'); + if (($vehicle_buy_total + $amount) > $product->vehicle_limit_amount) { + throw_invalid_request_exception('the-product-limited', [], $product->title . '每辆车限购' . $product->vehicle_limit_amount . '笔,无法下单'); + } + } + return true; + } + + public function checkCategoryLimitProductAmount(Category $category, Customer $customer, array $ids) + { + if ($category->limit_product_amount > 0) { + $product_ids = Order::join('products', 'orders.product_id', '=', 'products.id') + ->where('products.category_id', $category->id) + ->where('orders.customer_id', $customer->id) + ->whereNull('orders.closed_at') + ->where('orders.refund_status', '<>', Order::REFUND_STATUS_SUCCESS) + ->pluck('orders.product_id')->toArray(); + + $count = count(array_unique(array_merge($product_ids, $ids))); + if ($count > $category->limit_product_amount) { + throw_invalid_request_exception('the-product-limited', [], $category->name . '内限购' . $category->limit_product_amount . '种商品,无法下单'); + } + } + return true; + } + + public function checkCategoryVehicleLimitProductAmount(Category $category, $vin, array $ids) + { + if ($category->vehicle_limit_product_amount > 0) { + $product_ids = Order::join('products', 'orders.product_id', '=', 'products.id') + ->where('products.category_id', $category->id) + ->where('orders.vin', $vin) + ->whereNull('orders.closed_at') + ->where('orders.refund_status', '<>', Order::REFUND_STATUS_SUCCESS) + ->pluck('orders.product_id')->toArray(); + + $count = count(array_unique(array_merge($product_ids, $ids))); + if ($count > $category->vehicle_limit_product_amount) { + throw_invalid_request_exception('the-product-limited', [], $category->name . '内每辆车限购' . $category->vehicle_limit_product_amount . '种商品,无法下单'); + } + } + return true; + } + + public function checkPointUsable(Customer $customer, $total_point) + { + if ($total_point < 1) { + return true; + } + $extra = $customer->extra()->firstOrCreate(); + + if ($extra->point_usable < $total_point) { + throw_invalid_request_exception('insufficient-points'); + } + return true; + } + + //判断商品是否超过限制成团数 + public function checkGroupLimit(Product $product) + { + $group = $product->group; + if ($group->group_limit) { + if ($product->group_success_count >= $group->group_limit) { + throw_invalid_request_exception(null, [], '该拼团商品已达成团上限'); + } + } + if ($group->on_time) { + //判断拼团是否开始 + if ($group->is_before_start) { + throw_invalid_request_exception(null, [], '该拼团商品未开始'); + } + //判断拼团是否结束 + if ($group->is_after_end) { + throw_invalid_request_exception(null, [], '该拼团商品已结束'); + } + } + return true; + } + + //判断成团是否有效 + public static function checkGroupValid($group, $customer) + { + if (!$group) { + throw_invalid_request_exception('common-exception', [], '成团不存在'); + } + //拼团状态不是为 拼团中 则为无效的成团 + if ($group->status != Group::STATUS_PROCESSING) { + throw_invalid_request_exception('common-exception', [], '无效的成团'); + } + + //不能参与自己的拼团 + if(Order::where('customer_id', $customer->id)->where('group_id', $group->id)->whereNotNull('paid_at')->where('refund_status', '<>', Order::REFUND_STATUS_SUCCESS)->exists()){ + throw_invalid_request_exception('common-exception', [], '无法与自己拼团,请重新选择'); + } + return true; + } +} diff --git a/app/Services/MessageTemplateService.php b/app/Services/MessageTemplateService.php index cea7ba778147f5b80f629840decf7264257a3548..fe025cc47370c2651857d60258b02990a6d46629 100644 --- a/app/Services/MessageTemplateService.php +++ b/app/Services/MessageTemplateService.php @@ -1,8 +1,10 @@ json(); - if(!is_array($result) || !isset($result['errcode'])){ + if (!is_array($result) || !isset($result['errcode'])) { throw_invalid_request_exception('send-wechat-sms-failed'); } - if ($result['errcode'] == 42001 && $times === 1) { //更新token 回调 只回调一次 $this->getToken(true); @@ -52,4 +53,82 @@ class MessageTemplateService $this->token = $token; return $token; } + + //拼团完成通知 + public function orderCompleteNotify($wechat_openid, $product, $order_no, $order_time) + { + $params = [ + 'touser' => $wechat_openid, + 'template_id' => 'Lh4mC5DNO2XG99zPp5ycPZqFTO3l41x8pptf5znNUBI', + 'miniprogram' => [ + 'appid' => 'wx402a75b45cd2733f', + 'pagepath' => 'pages/myOrder/myOrder' + ], + 'data' => [ + 'thing11' => [ + 'value' => $product + ], + 'character_string5' => [ + 'value' => $order_no + ], + 'time4' => [ + 'value' => $order_time + ] + ] + ]; + return $this->send($params); + } + + //拼团取消通知 + public function orderCancelNotify($wechat_openid, $product, $order_no, $order_time, $reason = '超出拼团时间') + { + $params = [ + 'touser' => $wechat_openid, + 'template_id' => 'KB7xeNTgwD43stuHNxfYlwFdwPzG2kVrHSjisRZ3pYI', + 'miniprogram' => [ + 'appid' => 'wx402a75b45cd2733f', + 'pagepath' => 'pages/myOrder/myOrder' + ], + 'data' => [ + 'thing5' => [ + 'value' => $product + ], + 'character_string1' => [ + 'value' => $order_no + ], + 'const2' => [ + 'value' => $reason + ], + 'time3' => [ + 'value' => $order_time + ] + ] + ]; + return $this->send($params); + } + + //待成团提醒 + public function orderGroupingNotify($wechat_openid, $product, $order_no, $order_time) + { + $params = [ + 'touser' => $wechat_openid, + 'template_id' => '_xKmzpTPa9A-1IAbGX3XH2g-bb_GJ22gToOcO_Url5E', + 'miniprogram' => [ + 'appid' => 'wx402a75b45cd2733f', + 'pagepath' => 'pages/myOrder/myOrder' + ], + 'data' => [ + 'thing12' => [ + 'value' => $product + ], + 'character_string2' => [ + 'value' => $order_no + ], + 'time15' => [ + 'value' => $order_time + ] + ] + ]; + return $this->send($params); + } } diff --git a/app/Services/OrderService.php b/app/Services/OrderService.php index 024dbe30231a751568819b9dee7abcc304d65b83..df1c805b5ce062d6ad2e44e866bcefbc0d68f4ec 100644 --- a/app/Services/OrderService.php +++ b/app/Services/OrderService.php @@ -11,160 +11,368 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Carbon; use App\Models\CustomerCoupon; use App\Models\Product; -use Illuminate\Support\Facades\Validator; +use App\Models\Group; +use App\Models\ParentOrder; +use App\Models\CourtesyCard; +use App\Jobs\GroupCompleteJob; +use App\Jobs\GiveDrawTimesJob; class OrderService { - public function store(Customer $customer, ProductSku $sku, $amount, $address, $store_id = null, $vin = null) + public function store(Customer $customer, ProductSku $sku, $amount, $address = null, $store_id = null, $vin = null, $courtesy_card_id = null, $group_id = null) { + $address = empty($address) ? null : $address; $store_id = empty($store_id) ? null : $store_id; - if (!$sku->product->on_sale) { - throw_invalid_request_exception('the-product-not-on-sale'); + $vin = empty($vin) ? null : $vin; + $courtesy_card_id = empty($courtesy_card_id) ? null : $courtesy_card_id; + $group_id = empty($group_id) ? null : $group_id; + + $checkService = app('checkService'); + + if (!empty($vin)) { + $checkService->checkVin($vin, $customer); } + //实物商品,要填写收货地址 + if ($sku->product->type == Product::TYPE_NORMAL && is_null($address)) { + throw_invalid_request_exception('common-exception', [], '请填写收货地址'); + } //非认证用户无法下单 if ($sku->product->need_verify == true && !$customer->isCarOwner()) { throw_invalid_request_exception('you-are-not-the-owner'); } - //不是卡券商品,要填写收货地址 - if ($sku->product->type == Product::TYPE_NORMAL) { - $address = $address ?? []; - Validator::make($address, [ - 'full_address' => 'required|string', - 'contact_name' => 'required|string', - 'contact_phone' => 'required|phone:CN,mobile', - ], [], [ - 'full_address' => '收货地址', - 'contact_name' => '收件人', - 'contact_phone' => '收件人手机号', - ])->validate(); - } else if ($sku->product->type == Product::TYPE_COUPON) { - if ($sku->product->write_off_star) { - $data = ['store_id' => $store_id]; - Validator::make($data, [ - 'store_id' => 'required|integer' - ], [], [ - 'store_id' => '服务站 ID' - ])->validate(); - } - } else if ($sku->product->type == Product::TYPE_SHOWCASE) { - throw_invalid_request_exception(null, [], '展示商品无法下单'); - } + //卡券商品,要填服务商 + // if ($sku->product->type == Product::TYPE_COUPON && is_null($store_id)) { + // throw_invalid_request_exception('common-exception', [], '请选择提货门店'); + // } - if ($sku->product->model == Product::MODEL_SECKILL) { - if ($sku->product->seckill->is_before_start) { - throw_invalid_request_exception('seckill-is-before-start'); - } - if ($sku->product->seckill->is_after_end) { - throw_invalid_request_exception('seckill-is-after-end'); - } + if ($sku->product->vehicle_limit_amount > 0) { + if (is_null($vin)) throw_invalid_request_exception('common-exception', [], '请选择使用车辆'); + //商品每辆车限购判断 + $checkService->checkVehicleLimitAmount($vin, $sku->product, $amount); } - if ($sku->product->limit_amount > 0) { - //查询未关闭且非退款成功的数量和 - $total = Order::where('customer_id', $customer->id)->where('closed', false)->where('refund_status', '<>', Order::REFUND_STATUS_SUCCESS)->where('product_id', $sku->product->id)->sum('amount'); - - if ($total + $amount > $sku->product->limit_amount) { - throw_invalid_request_exception('the-product-limited', [], '该商品限购' . $sku->product->limit_amount . '笔,无法下单'); - } - } - if ($sku->product->vehicle_limit_amount > 0) { - $vin = strtoupper(substr($vin, -8)); - $vin_arr = ['vin' => $vin]; - Validator::make($vin_arr, [ - 'vin' => 'required|regex:/^[A-HJ-NPR-Z\d]+$/', - ], [], [ - 'vin' => '车架号', - ])->validate(); - //判断车辆归属 - if (!$customer->isMyCar($vin)) { - throw_invalid_request_exception('is-not-customer-vin'); - } - $vehicle_buy_total = Order::where('vin', $vin)->where('closed', false)->where('refund_status', '<>', Order::REFUND_STATUS_SUCCESS)->where('product_id', $sku->product->id)->sum('amount'); - if ($vehicle_buy_total + $amount > $sku->product->vehicle_limit_amount) { - throw_invalid_request_exception('the-product-limited', [], '该商品每辆车限购' . $sku->product->vehicle_limit_amount . '笔,无法下单'); - } - - if ($sku->product->category->vehicle_limit_product_amount > 0) { - $p_ids = Order::join('products', 'orders.product_id', '=', 'products.id') - ->where('products.category_id', $sku->product->category_id) - ->where('orders.vin', $vin) - ->where('orders.closed', false) - ->where('orders.refund_status', '<>', Order::REFUND_STATUS_SUCCESS) - ->pluck('orders.product_id')->toArray(); - - $p_ids = array_unique($p_ids); - $count = count($p_ids); - if ($count >= $sku->product->category->vehicle_limit_product_amount) { - throw_invalid_request_exception('the-product-limited', [], '该商品类目下每辆车限购' . $sku->product->category->vehicle_limit_product_amount . '种商品,无法下单'); - } - } + if ($sku->product->category->vehicle_limit_product_amount > 0) { + if (is_null($vin)) throw_invalid_request_exception('common-exception', [], '请选择使用车辆'); + //商品类目每辆车限购判断 + $checkService->checkCategoryVehicleLimitProductAmount($sku->product->category, $vin, [$sku->product->id]); } - //同一类目下限制可购买的商品种类数 - if ($sku->product->category->limit_product_amount > 0) { - $product_ids = Order::join('products', 'orders.product_id', '=', 'products.id') - ->where('products.category_id', $sku->product->category_id) - ->where('orders.customer_id', $customer->id) - ->where('orders.closed', false) - ->where('orders.refund_status', '<>', Order::REFUND_STATUS_SUCCESS) - ->pluck('orders.product_id')->toArray(); + //商品限购判断 + $checkService->checkLimitAmount($customer, $sku->product, $amount); - $product_ids = array_unique($product_ids); - $count = count($product_ids); - if ($count >= $sku->product->category->limit_product_amount) { - throw_invalid_request_exception('the-product-limited', [], '该商品类目下限购' . $sku->product->category->limit_product_amount . '种商品,无法下单'); - } - } + //商品类目限购判断 + $checkService->checkCategoryLimitProductAmount($sku->product->category, $customer, [$sku->product->id]); $total_point = $sku->point * $amount; + //判断剩余积分是否不足 + $checkService->checkPointUsable($customer, $total_point); + + $parent_order = DB::transaction(function () use ($customer, $address, $sku, $amount, $store_id, $vin, $courtesy_card_id, $total_point, $group_id,$checkService) { + //拼团商品 + if ($sku->product->model == Product::MODEL_GROUP) { + //判断是否可拼团 + $checkService->checkGroupLimit($sku->product); + //如果没有成团id,则创建一个 + if (empty($group_id)) { + $group = Group::create([ + 'customer_id' => $customer->id, + 'product_id' => $sku->product->id, + 'group_size' => $sku->product->group->group_size + ]); + $group_id = $group->id; + } else { + $group = Group::find($group_id); + //判断成团是否有效 + $checkService->checkGroupValid($group,$customer); + } + $ship_status = Order::SHIP_STATUS_GROUPING; + } else { + $ship_status = Order::SHIP_STATUS_PENDING; + } - $extra = $customer->extra()->firstOrCreate(); - if ($extra->point_usable < $total_point) { - throw_invalid_request_exception('insufficient-points'); - } - - $order = DB::transaction(function () use ($customer, $address, $sku, $amount, $store_id, $vin) { //减库存 if ($sku->decreaseStock($amount) <= 0) { throw_invalid_request_exception('the-product-out-of-stock'); } + $total_amount = $sku->price * $amount; + if ($courtesy_card_id) { + if (!$courtesy_card = CourtesyCard::find($courtesy_card_id)) { + throw_invalid_request_exception('model-not-found-exception', [], '优惠券不存在'); + } + //判断优惠券能否被使用 + if (!$sku->product->isContainCard($courtesy_card->courtesy_card_mould_id)) { + throw_invalid_request_exception('model-not-found-exception', [], '商品不支持使用该优惠券'); + } + $payment_amount = $this->courtesyCardUse($courtesy_card, $total_amount, $customer); + } else { + $payment_amount = $total_amount; + } + + $parent_order = new ParentOrder([ + 'customer_id' => $customer->id, + 'total_amount' => $total_amount, + 'payment_amount' => $payment_amount, + 'total_point' => $total_point, + 'courtesy_card_id' => $courtesy_card_id, + 'address' => $address ?: [], + 'store_id' => $store_id, + 'vin' => $vin, + ]); + $parent_order->customer()->associate($customer); + $parent_order->save(); + // 创建一个订单 $order = new Order([ - 'address' => $address ?: [], - 'total_amount' => $sku->price * $amount, - 'total_point' => $sku->point * $amount, 'type' => $sku->product->type, 'product_id' => $sku->product_id, 'product_sku_id' => $sku->id, + 'category_id' => $sku->product->category->parent_id, 'amount' => $amount, 'price' => $sku->price, 'point' => $sku->point, - 'ship_status' => Order::SHIP_STATUS_PENDING, - 'refund_status' => Order::REFUND_STATUS_PENDING, - 'category_id' => $sku->product->category->parent_id, + 'total_amount' => $total_amount, + 'total_point' => $total_point, + 'payment_amount' => $payment_amount, + 'address' => $address ?: [], 'store_id' => $store_id, - 'vin' => empty($vin) ? null : $vin + 'vin' => $vin, + 'group_id' => $group_id, + 'ship_status' => $ship_status ]); // 订单关联到当前用户 $order->customer_id = $customer->id; + $order->parentOrder()->associate($parent_order); // 写入数据库 $order->save(); - return $order; + return $parent_order; }); - dispatch(new CloseOrderJob($order, config('app.order_ttl'))); + dispatch(new CloseOrderJob($parent_order, config('app.order_ttl'))); - return $order; + return $parent_order; } + //购物车下单 + public function cartStore(Customer $customer, array $items, $address = null, $store_id = null, $vin = null, $courtesy_card_id = null) + { + $address = empty($address) ? null : $address; + $store_id = empty($store_id) ? null : $store_id; + $vin = empty($vin) ? null : $vin; + $courtesy_card_id = empty($courtesy_card_id) ? null : $courtesy_card_id; + $courtesy_card = null; + if ($courtesy_card_id) { + if (!$courtesy_card = CourtesyCard::find($courtesy_card_id)) { + throw_invalid_request_exception('model-not-found-exception', [], '优惠券不存在'); + } + } + $checkService = app('checkService'); + $products = []; + $total_point = 0; + $a_amount = 0; + $c_amount = 0; + $i = 0; + // 遍历用户提交的 SKU + foreach ($items as &$item) { + $amount = $item['amount']; + + if (!$sku = ProductSku::find($item['sku_id'])) { + throw_invalid_request_exception('the-product-sku-not-found'); + } + $product = $sku->product; + //判断商品规格是否在售 + if (!$sku->on_sale) { + throw_invalid_request_exception('the-sku-not-on-sale', [], $sku->product->title . '已下架'); + } + //判断库存 + if ($amount > $sku->stock) { + throw_invalid_request_exception('the-product-out-of-stock', [], $sku->product->title . '已售罄'); + } + + //实物商品,要填写收货地址 + if ($sku->product->type == Product::TYPE_NORMAL && is_null($address)) { + throw_invalid_request_exception('common-exception', [], '请填写收货地址'); + } + + //卡券商品,要填服务商 + // if ($sku->product->type == Product::TYPE_COUPON && is_null($store_id)) { + // throw_invalid_request_exception('common-exception', [], '请选择提货门店'); + // } + + if ($sku->product->type == Product::TYPE_SHOWCASE) { + throw_invalid_request_exception(null, [], $sku->product->title . '为展示商品无法下单'); + } + + //非认证用户无法下单 + if ($sku->product->need_verify == true && !$customer->isCarOwner()) { + throw_invalid_request_exception(null, [], $sku->product->title . '需要认证才能下单'); + } + + $total_point += $sku->point * $amount; + if ($courtesy_card_id && $product->isContainCard($courtesy_card->courtesy_card_mould_id)) { + $c_amount += $sku->price * $amount; + $sku->can_use_card = true; + $i++; + } else { + $a_amount += $sku->price * $amount; + $sku->can_use_card = false; + } + + if (isset($products[$product->id])) { + $products[$product->id]['amount'] += $item['amount']; + } else { + $products[$product->id] = [ + 'product' => $product, + 'amount' => $item['amount'] + ]; + } + + $item['sku'] = $sku; + } + + $categories = []; + foreach ($products as $value) { + $product = $value['product']; + //判断商品是否在售 + if (!$product->on_sale) { + throw_invalid_request_exception('the-product-not-on-sale', [], $product->title . '已下架'); + } + + //判断商品类型 + if ($product->model != Product::MODEL_NORMAL) { + throw_invalid_request_exception('common-exception', [], $product->title . '类型错误'); + } + + + + //商品限购判断 + if ($product->limit_amount > 0) { + $checkService->checkLimitAmount($customer, $product, $value['amount']); + } + + //商品单个车型判断 + if ($product->vehicle_limit_amount > 0) { + if (is_null($vin)) throw_invalid_request_exception('common-exception', [], '请选择使用车辆'); + $checkService->checkVehicleLimitAmount($vin, $product, $value['amount']); + } + + $category = $product->category; + if (!isset($categories[$category->id])) { + $categories[$category->id] = [ + 'category' => $category, + 'product_ids' => [] + ]; + } + $categories[$category->id]['product_ids'][] = $product->id; + } + + foreach ($categories as $v) { + $category = $v['category']; + //同一类目下限制可购买的商品种类数 + if ($category->limit_product_amount > 0) { + $checkService->checkCategoryLimitProductAmount($category, $customer, $v['product_ids']); + } + if ($category->vehicle_limit_product_amount > 0) { + if (is_null($vin)) throw_invalid_request_exception('common-exception', [], '请选择使用车辆'); + //商品类目每辆车限购判断 + $checkService->checkCategoryVehicleLimitProductAmount($category, $vin, $v['product_ids']); + } + } + + $parent_order = DB::transaction(function () use ($customer, $address, $store_id, $vin, $courtesy_card_id, $total_point, $a_amount, $c_amount, $items, $products, $categories, $checkService, $courtesy_card, $i) { + $payment_c_amount = 0; + //判断剩余积分是否不足 + $checkService->checkPointUsable($customer, $total_point); + if ($courtesy_card_id) { + // 总金额已经计算出来了,检查是否符合优惠券规则 + $courtesy_card->checkAvailable($customer, $c_amount); + // 实际支付金额应该为折扣商品优惠后的价格加上不打折商品的价格 + $payment_c_amount = $courtesy_card->getAdjustedPrice($c_amount); + $payment_amount = $payment_c_amount + $a_amount; + $courtesy_card->use(); + } else { + $payment_amount = $a_amount; + } + + $total_amount = $a_amount + $c_amount; + $parent_order = new ParentOrder([ + 'customer_id' => $customer->id, + 'total_amount' => $total_amount, + 'payment_amount' => $payment_amount, + 'total_point' => $total_point, + 'courtesy_card_id' => $courtesy_card_id, + 'address' => $address ?: [], + 'store_id' => $store_id, + 'vin' => $vin, + ]); + $parent_order->customer()->associate($customer); + $parent_order->save(); + + $last_item_payment_amount = $payment_c_amount; + foreach ($items as $item) { + $amount = $item['amount']; + $sku = $item['sku']; + $item_total_amount = $sku->price * $amount; + $item_total_point = $sku->point * $amount; + + if ($courtesy_card_id && $sku->can_use_card) { + //最后一个元素 + if ($i == 1) { + $item_payment_amount = $last_item_payment_amount; + } else { + $item_payment_amount = round(($payment_c_amount / $c_amount) * $item_total_amount, 2); + $last_item_payment_amount -= $item_payment_amount; + } + $i--; + } else { + $item_payment_amount = $item_total_amount; + } + + //减库存 + if ($sku->decreaseStock($amount) <= 0) { + throw_invalid_request_exception('the-product-out-of-stock'); + } + + $product = $products[$sku->product_id]['product']; + $category = $categories[$product->category_id]['category']; + // 创建一个订单 + $order = new Order([ + 'type' => $product->type, + 'product_id' => $sku->product_id, + 'product_sku_id' => $sku->id, + 'category_id' => $category->parent_id, + 'amount' => $amount, + 'price' => $sku->price, + 'point' => $sku->point, + 'total_amount' => $item_total_amount, + 'payment_amount' => $item_payment_amount, + 'total_point' => $item_total_point, + 'address' => $address ?: [], + 'store_id' => $store_id, + 'vin' => $vin, + ]); + + // 订单关联到当前用户 + $order->customer_id = $customer->id; + $order->parentOrder()->associate($parent_order); + // 写入数据库 + $order->save(); + } + + return $parent_order; + }); + + dispatch(new CloseOrderJob($parent_order, config('app.order_ttl'))); + + return $parent_order; + } //订单支付 - public function pay(Order $order) + public function pay(ParentOrder $order) { - if ($order->paid_at || $order->closed) { + if ($order->paid_at || $order->closed_at) { throw_invalid_request_exception('order-status-error', [], '订单状态异常,无法支付'); } $customer = $order->customer; @@ -191,78 +399,120 @@ class OrderService Customer::computePoint($customer->id); if ($data['status'] == 'paid') return $data; - - $data['pay'] = app('App\Services\WechatService')->orderUnify($order); + if (!app()->runningInConsole()) { + $data['pay'] = app('App\Services\WechatService')->orderUnify($order); + } return $data; } //支付完成 - public function payCompleted(Order $order, $trade_no = null) + public function payCompleted(ParentOrder $parent_order, $trade_no = null) { - if ($order->paid_at || $order->closed) { + if ($parent_order->paid_at || $parent_order->closed_at) { throw_invalid_request_exception('order-status-error', [], '订单状态异常,无法支付完成'); } - $now = Carbon::now(); + $parent_order = DB::transaction(function () use ($parent_order, $trade_no) { + $now = Carbon::now(); + //支付完成,把冻结中的积分实际上去扣除 + if ($parent_order->paymentPointRecord) { + //冻结扣除 + app('App\Services\PointService')->freezeDeduct($parent_order->paymentPointRecord); + } - //支付完成,把冻结中的积分实际上去扣除 - if ($order->paymentPointRecord) { - //冻结扣除 - app('App\Services\PointService')->freezeDeduct($order->paymentPointRecord); - } + foreach ($parent_order->orders as $order) { + $order->paid_at = $now; + //拼团订单 + if ($order->group_id) { + if ($order->group->status == Group::STATUS_PENDING) { + $order->group->status = Group::STATUS_PROCESSING; + $order->group->valid_at = $now; + $order->group->save(); + goto over; + } elseif ($order->group->status == Group::STATUS_SUCCESS) { + goto loop; + } else { + $c = Order::where('group_id', $order->group_id)->whereNotNull('paid_at')->where('refund_status', '<>', Order::REFUND_STATUS_SUCCESS)->count(); + $c += 1; + //判断是否已达到成团人数 + if ($c >= $order->group->group_size) { + dispatch_now(new GroupCompleteJob($order->group)); + goto loop; + } else { + goto over; + } + } + } - //如果是券订单,需要去发券 - if ($order->type == Order::TYPE_COUPON) { + loop: + //如果是券订单,需要去发券 + if ($order->type == Order::TYPE_COUPON) { + $this->orderCoupon($order); + //卡券订单直接已收货状态 + $order->ship_status = Order::SHIP_STATUS_RECEIVED; + $order->take_at = $now; + } else { + //成团的订单物流状态修改为待发货 + $order->ship_status = Order::SHIP_STATUS_PENDING; + } - $due_at = date('Y-m-d H:i:s', strtotime($order->productSku->due_at)); - $valid_at = date('Y-m-d H:i:s', strtotime($order->productSku->valid_at)); + over: + $order->save(); + //计算商品销售量 + dispatch(new ProductSoldCountJob($order->product)); + //发放抽奖机会 + dispatch_now(new GiveDrawTimesJob($order)); + } - for ($i = 0; $i < $order->amount; $i++) { - app('coupon')->increase($order->customer_id, CustomerCoupon::TYPE_ORD, $order->product->category->name, $order->product->title, $order->id, $order->productSku->soc_code, $order->vin, $order->productSku->soc_price, $due_at, $valid_at); + //支付平台支付单号 + if ($trade_no) { + $parent_order->payment_method = 'wechat'; + $parent_order->payment_no = $trade_no; } - //卡券订单直接已收货状态 - $order->ship_status = Order::SHIP_STATUS_RECEIVED; - $order->take_at = $now; - } + $parent_order->paid_at = $now; + $parent_order->save(); + return $parent_order; + }); - //支付平台支付单号 - if ($trade_no) { - $order->payment_method = 'wechat'; - $order->payment_no = $trade_no; - } - $order->paid_at = $now; - $order->save(); - dispatch(new ProductSoldCountJob($order->product)); - return $order; + return $parent_order; } //取消订单 - public function cancel(Order $order) + public function cancel(ParentOrder $parentOrder) { - if ($order->paid_at || $order->closed) { + if ($parentOrder->paid_at || $parentOrder->closed_at) { throw_invalid_request_exception('order-status-error', [], '订单状态异常,无法取消'); } // 通过事务执行 sql - $order = DB::transaction(function () use ($order) { - // 将订单的 closed 字段标记为 true,即关闭订单 - $order->update(['closed' => true, 'closed_at' => Carbon::now()->toDateTimeString()]); - // 循环遍历订单中的商品 SKU,将订单中的数量加回到 SKU 的库存中去 - $order->productSku->addStock($order->amount); + $parentOrder = DB::transaction(function () use ($parentOrder) { + if ($parentOrder->courtesy_card_id) { + $parentOrder->courtesyCard->used_at = null; + $parentOrder->courtesyCard->save(); + } + $now = Carbon::now()->toDateTimeString(); + // 添加订单的 closed_at,即关闭订单 + $parentOrder->update(['closed_at' => $now]); + foreach ($parentOrder->orders as $order) { + // 循环遍历订单中的商品 SKU,将订单中的数量加回到 SKU 的库存中去 + $order->update(['closed_at' => $now]); + $order->productSku->addStock($order->amount); + } // 如果存在冻结中的积分记录 - if ($order->paymentPointRecord) { + if ($parentOrder->payment_point_record_id) { // 退还积分 - app('App\Services\PointService')->freezeReturn($order->paymentPointRecord); + app('App\Services\PointService')->freezeReturn($parentOrder->paymentPointRecord); // 重新计算用户积分 - Customer::computePoint($order->paymentPointRecord->customer_id); + Customer::computePoint($parentOrder->paymentPointRecord->customer_id); } - return $order; + + return $parentOrder; }); - return $order; + return $parentOrder; } //退款 public function refund(Order $order) { - if (empty($order->paid_at) || $order->closed) { + if (empty($order->paid_at) || $order->closed_at) { throw_invalid_request_exception('order-status-error', [], '订单状态异常,无法退款'); } @@ -281,34 +531,69 @@ class OrderService $order->productSku->addStock($order->amount); // 如果存在支付记录记录,则去退积分 - if ($order->paymentPointRecord && empty($order->refund_point_record_id)) { + if ($order->total_point && $order->parentOrder->paymentPointRecord && empty($order->refund_point_record_id)) { //积分退款 - $record = app('App\Services\PointService')->refund($order->paymentPointRecord); + $record = app('App\Services\PointService')->refund($order->parentOrder->paymentPointRecord, $order->total_point); $order->refund_point_record_id = $record->id; Customer::computePoint($order->customer_id); } // 判断该订单的支付方式 - if ($order->payment_method == 'wechat') { + if ($order->parentOrder->payment_method == 'wechat') { // 生成一个退款订单号 $refundNo = Order::getAvailableRefundNo(); + $total_amount = $order->parentOrder->payment_amount * 100; // 调用微信支付实例的 refund 方法 - $amount = $order->total_amount * 100; - $result = app('easywechat.payment')->refund->byOutTradeNumber($order->no, $refundNo, $amount, $amount); + $amount = $order->payment_amount * 100; + $result = app('easywechat.payment')->refund->byOutTradeNumber($order->parentOrder->no, $refundNo, $total_amount, $amount); - if ($result['return_code'] === 'SUCCESS') { + if ($result['result_code'] === 'SUCCESS') { // 将订单的退款状态标记为退款成功并保存退款订单号 $order->refund_no = $refundNo; } else { - throw_invalid_request_exception('order-refund-fail', [], $result['return_msg']); + throw_invalid_request_exception('order-refund-fail', [], $result['err_code_des']); } } - $order->refund_status = Order::REFUND_STATUS_SUCCESS; $order->save(); + if ($order->group_id) { + $c = Order::where('group_id', $order->group_id)->whereNotNull('paid_at')->where('refund_status', '<>', Order::REFUND_STATUS_SUCCESS)->count(); + if ($c == 0) { + Group::where('id', $order->group_id)->update(['status' => Group::STATUS_FAILED]); + } + } + + if ($order->parentOrder()->where('refund_status', '<>', Order::REFUND_STATUS_SUCCESS)->exists()) { + $order->parentOrder->refund_status = ParentOrder::REFUND_STATUS_PARTIAL; + } else { + $order->parentOrder->refund_status = ParentOrder::REFUND_STATUS_SUCCESS; + } + $order->parentOrder->save(); + return $order; }); return $order; } + + public function courtesyCardUse($courtesy_card, $total_amount, Customer $customer) + { + // 总金额已经计算出来了,检查是否符合优惠券规则 + $courtesy_card->checkAvailable($customer, $total_amount); + $courtesy_card->use(); + // 把订单金额修改为优惠后的金额 + $payment_amount = $courtesy_card->getAdjustedPrice($total_amount); + return $payment_amount; + } + + //订单完成后发券 + public function orderCoupon(Order $order) + { + $due_at = date('Y-m-d H:i:s', strtotime($order->productSku->due_at)); + $valid_at = date('Y-m-d H:i:s', strtotime($order->productSku->valid_at)); + for ($i = 0; $i < $order->amount; $i++) { + app('coupon')->increase($order->customer_id, CustomerCoupon::TYPE_ORD, $order->product->category->name, $order->product->title, $order->id, $order->productSku->soc_code, $order->vin, $order->productSku->soc_price, $due_at, $valid_at); + } + return true; + } } diff --git a/app/Services/PointService.php b/app/Services/PointService.php index c21e20e95acfff2215669b30cf5bd19734018e89..d1870c786f5cf0dce45798b56885358ff2a2256d 100644 --- a/app/Services/PointService.php +++ b/app/Services/PointService.php @@ -114,22 +114,31 @@ class PointService } //退款退还 - public function refund(CustomerPointRecord $paymentPointRecord, $action = 'order-refund') + public function refund(CustomerPointRecord $paymentPointRecord, int $total_point, $action = 'order-refund') { - foreach ($paymentPointRecord->source as $row) { + $point = $total_point; + $source = $paymentPointRecord->source; + foreach ($source as &$row) { + if ($point <= 0) return; + if ($row['value'] < 1) continue; + $value = min($point, $row['value']); CustomerPointRecord::where('id', $row['id'])->increment('usable_value', $row['value']); + $row['value'] = $row['value'] - $value; + $point -= $value; } + $paymentPointRecord->source = $source; + $paymentPointRecord->save(); //新增一条返还的积分记录 $record = CustomerPointRecord::create([ 'customer_id' => $paymentPointRecord->customer_id, 'action' => $action, - 'value' => $paymentPointRecord->value, + 'value' => $total_point, 'usable_value' => 0, 'type' => 'add', //增加 'source' => [ [ 'id' => $paymentPointRecord->id, - 'value' => $paymentPointRecord->value + 'value' => $total_point ] ], 'status' => CustomerPointRecord::STATUS_VALID, diff --git a/app/Services/ServiceOrderService.php b/app/Services/ServiceOrderService.php index 557504555aaec6ace4d75b3838e72c9e33ab5277..8ae500fe5535f8b276f9056fed3b82c53250fe1d 100644 --- a/app/Services/ServiceOrderService.php +++ b/app/Services/ServiceOrderService.php @@ -99,7 +99,7 @@ class ServiceOrderService return $order; } - public function extendedWarrantyStore(Customer $customer, $vin, $material_number, $point, $username, $phone, $idcard,$mileage,$term_sale_date,$close_date,$engine_code) + public function extendedWarrantyStore(Customer $customer, $vin, $material_number, $point, $username, $phone, $idcard, $mileage, $term_sale_date, $close_date, $engine_code) { $point = intval($point); //判断是否该车主 @@ -109,7 +109,7 @@ class ServiceOrderService //一辆车只能购买一次延保套餐 if (ServiceOrderItem::hasBuyExtendedWarranty($vin)) { - throw_invalid_request_exception('order-store-fail',[], "车辆[{$vin}]已购买过延保套餐,无法购买!"); + throw_invalid_request_exception('order-store-fail', [], "车辆[{$vin}]已购买过延保套餐,无法购买!"); } //获取该车架号可购买物料号数组 $products = app('App\Services\SocService')->getExtendedWarrantyProducts($vin); @@ -303,7 +303,7 @@ class ServiceOrderService // 如果存在支付记录记录,则去退积分 if ($orderItem->paymentPointRecord && empty($orderItem->refund_point_record_id)) { //积分退款 - $record = app('App\Services\PointService')->refund($orderItem->paymentPointRecord, 'service-order-refund'); + $record = app('App\Services\PointService')->refund($orderItem->paymentPointRecord, $orderItem->point, 'service-order-refund'); $orderItem->refund_point_record_id = $record->id; Customer::computePoint($order->customer_id); } @@ -315,14 +315,14 @@ class ServiceOrderService $amount = $orderItem->amount * 100; $total_amount = $order->total_amount * 100; $result = app('easywechat.payment')->refund->byOutTradeNumber($order->no, $refundNo, $total_amount, $amount); - if ($result['return_code'] === 'SUCCESS') { + if ($result['result_code'] === 'SUCCESS') { // 将订单的退款状态标记为退款成功并保存退款订单号 $orderItem->refund_no = $refundNo; } else { - throw_invalid_request_exception('order-refund-fail', [], $result['return_msg']); + throw_invalid_request_exception('order-refund-fail', [], $result['err_code_des']); } } - + $orderItem->refund_status = ServiceOrderItem::REFUND_STATUS_SUCCESS; $orderItem->extra = $extra; $orderItem->save(); diff --git a/app/Services/WechatService.php b/app/Services/WechatService.php index b3c041200f0093cbc0b706bd10aea0048929d357..ee21899d75425ab697b3054304ebe1a05b8e9ee5 100644 --- a/app/Services/WechatService.php +++ b/app/Services/WechatService.php @@ -5,7 +5,7 @@ namespace App\Services; // use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Storage; // use App\Exceptions\InvalidRequestException; -use App\Models\Order; +use App\Models\ParentOrder; use App\Models\ServiceOrder; use function EasyWeChat\Kernel\Support\generate_sign; @@ -26,6 +26,15 @@ class WechatService $optional['scene'] = $scene; if (!isset($optional['width'])) $optional['width'] = 500; + // $access_token = app('App\Services\JavaService')->getMiniToken(); + // $url = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" . $access_token; + // $response = Http::post($url, $optional); + // $result = $response->json(); + // if (isset($result['errcode']) && $result['errcode'] != 0) { + // throw new InvalidRequestException($result['errmsg'], 200, $result['errcode']); + // } + // $imgBuffer = $response->body(); + $response = app('easywechat.mini_program')->app_code->getUnlimit($scene, $optional); $imgBuffer = $response->getBody()->getContents(); @@ -36,15 +45,14 @@ class WechatService } //统一下单 - public function orderUnify(Order $order) + public function orderUnify(ParentOrder $order) { - // dd(config('wechat.payment.default')); $result = app('easywechat.payment')->order->unify([ - 'body' => $order->productSku->title, + 'body' => '东风卡友E家', 'out_trade_no' => $order->no, 'trade_type' => 'JSAPI', 'openid' => $order->customer->miniapp_openid, - 'total_fee' => $order->total_amount * 100, + 'total_fee' => $order->payment_amount * 100, ]); // 如果成功生成统一下单的订单,那么进行二次签名 diff --git a/bootstrap/app.php b/bootstrap/app.php index 6663c62553ee00292dbdd1d83753886378238b5a..55622aee966b060ce999e2d8039a10f3ac11acde 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -120,7 +120,7 @@ $app->register(Propaganistas\LaravelPhone\PhoneServiceProvider::class); $app->register(Overtrue\LaravelLang\TranslationServiceProvider::class); $app->register(Fruitcake\Cors\CorsServiceProvider::class); $app->register(Zing\LaravelFlysystem\Obs\ObsServiceProvider::class); -$app->register(App\Providers\CouponServiceProvider::class); +$app->register(App\Providers\SelfServiceProvider::class); $app->register(Maatwebsite\Excel\ExcelServiceProvider::class); $app->register(SimpleSoftwareIO\QrCode\QrCodeServiceProvider::class); $app->register(Overtrue\LaravelWeChat\ServiceProvider::class); diff --git a/config/app.php b/config/app.php index 4edd663fabb20251f2b32d29c3e68781a732bed9..eec542545aeecc3d574cb846732f834cd28f8b0a 100644 --- a/config/app.php +++ b/config/app.php @@ -125,7 +125,9 @@ return [ 'order_ttl' => 1800, - 'test_customer_ids' => explode(',',env('TEST_CUSTOMER_IDS','')), + 'seckill_order_ttl' => 600, + + 'test_customer_ids' => explode(',', env('TEST_CUSTOMER_IDS', '')), 'random_drop_seckill_percent' => env('RANDOM_DROP_SECKILL_PERCENT', 0), diff --git a/config/errcode.php b/config/errcode.php index cc58a29d10f5e142707cf5c174895f1bde2160a3..f52f2ec042f6aa0bc3bb3c437cb610bdf15ae896 100644 --- a/config/errcode.php +++ b/config/errcode.php @@ -376,5 +376,14 @@ return [ 'message' => '无法核销,核销数已达到上限', 'code' => 1510 ], - + 'courtesy-card-invalid' => [ + 'statusCode' => 200, + 'message' => '无效的优惠券', + 'code' => 1511 + ], + 'the-product-not-seckill' => [ + 'statusCode' => 200, + 'message' => '该商品非秒杀商品', + 'code' => 1512 + ] ]; diff --git a/config/logging.php b/config/logging.php index 0e9947f489554a5f1fc3c4c56c869e3424c29ea7..d76f79fb9b77a5c8ddfe70f6e95aa3e75f90bc52 100644 --- a/config/logging.php +++ b/config/logging.php @@ -51,6 +51,7 @@ return [ 'stack' => [ 'driver' => 'stack', 'channels' => ['daily'], + 'level' => 'debug' ], 'queue' => [ diff --git a/database/factories/OrderFactory.php b/database/factories/OrderFactory.php index 9334c2e25182f3c0261746959e0dd6472995c9a7..dc67db4dbbb6d7ab0b61e7b94b685024b09ea7be 100644 --- a/database/factories/OrderFactory.php +++ b/database/factories/OrderFactory.php @@ -13,7 +13,7 @@ class OrderFactory extends Factory public function definition() { - + // 随机生成发货状态 $ship = $this->faker->randomElement(array_keys(Order::$shipStatusMap)); // 10% 的概率把订单标记为退款 @@ -29,7 +29,6 @@ class OrderFactory extends Factory 'payment_no' => $this->faker->uuid, 'refund_status' => $refund ? Order::REFUND_STATUS_SUCCESS : Order::REFUND_STATUS_PENDING, 'refund_no' => $refund ? Order::getAvailableRefundNo() : null, - 'closed' => false, 'ship_status' => $ship, 'send_at' => $send_at, 'take_at' => $ship === Order::SHIP_STATUS_RECEIVED ? null : $this->faker->dateTimeBetween($send_at), diff --git a/database/migrations/2023_11_09_172727_create_cart_items_table.php b/database/migrations/2023_11_09_172727_create_cart_items_table.php new file mode 100644 index 0000000000000000000000000000000000000000..d0cb8d8d1ff24421ceab85098876228bc5f9a285 --- /dev/null +++ b/database/migrations/2023_11_09_172727_create_cart_items_table.php @@ -0,0 +1,36 @@ +id(); + $table->integer('customer_id')->comment('用户ID'); + $table->foreign('customer_id')->references('id')->on('sys_customer')->onDelete('cascade'); + $table->unsignedBigInteger('product_sku_id'); + $table->foreign('product_sku_id')->references('id')->on('product_skus')->onDelete('cascade'); + $table->unsignedInteger('amount'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('cart_items'); + } +} diff --git a/database/migrations/2023_11_10_164319_create_product_zones_table.php b/database/migrations/2023_11_10_164319_create_product_zones_table.php new file mode 100644 index 0000000000000000000000000000000000000000..b61eb00013f53251c8bb089ac0a1bee3f01d082e --- /dev/null +++ b/database/migrations/2023_11_10_164319_create_product_zones_table.php @@ -0,0 +1,44 @@ +id(); + $table->string('title')->comment('标题'); + $table->string('banner')->comment('专区banner'); + $table->integer('order')->default(0)->comment('排序'); + $table->timestamps(); + }); + + Schema::create('zone_products', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('product_zone_id')->comment('专区 id'); + $table->foreign('product_zone_id')->references('id')->on('product_zones')->onDelete('cascade'); + $table->unsignedBigInteger('product_id')->comment('所属商品 id'); + $table->foreign('product_id')->references('id')->on('products')->onDelete('cascade'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('zone_products'); + Schema::dropIfExists('product_zones'); + } +} diff --git a/database/migrations/2023_11_13_143254_create_courtesy_cards_table.php b/database/migrations/2023_11_13_143254_create_courtesy_cards_table.php new file mode 100644 index 0000000000000000000000000000000000000000..9b85494cd5d9fd06b36b9dd13a83ef893bbfd54a --- /dev/null +++ b/database/migrations/2023_11_13_143254_create_courtesy_cards_table.php @@ -0,0 +1,72 @@ +id(); + $table->string('code')->unique()->comment('唯一编码'); + $table->string('name')->comment('优惠券的标题'); + $table->string('type')->comment('优惠券类型,支持固定金额和百分比折扣'); + $table->integer('value')->comment('折扣值,根据不同类型含义不同'); + $table->decimal('min_amount', 10, 2)->comment('使用该优惠券的最低订单金额'); + $table->string('valid_at')->nullable()->comment('生效时间'); + $table->string('due_at')->nullable()->comment('失效时间'); + $table->unsignedInteger('total')->comment('全站可领取的数量'); + $table->unsignedInteger('used')->default(0)->comment('当前已领取的数量'); + $table->timestamps(); + }); + DB::statement("ALTER TABLE `" . env('DB_PREFIX', '') . "courtesy_card_moulds` comment='优惠券模板表'"); + + Schema::create('courtesy_cards', function (Blueprint $table) { + $table->id(); + $table->integer('customer_id')->comment('用户ID'); + $table->foreign('customer_id')->references('id')->on('sys_customer')->onDelete('cascade'); + $table->unsignedBigInteger('courtesy_card_mould_id')->comment('优惠券模板ID'); + $table->foreign('courtesy_card_mould_id')->references('id')->on('courtesy_card_moulds')->onDelete('cascade'); + $table->string('name')->comment('优惠券的标题'); + $table->string('type')->comment('优惠券类型,支持固定金额和百分比折扣'); + $table->integer('value')->comment('折扣值,根据不同类型含义不同'); + $table->decimal('min_amount', 10, 2)->comment('使用该优惠券的最低订单金额'); + $table->datetime('valid_at')->nullable()->comment('生效时间'); + $table->datetime('due_at')->nullable()->comment('失效时间'); + $table->dateTime('used_at')->nullable()->comment('使用时间'); + $table->boolean('enabled')->comment('优惠券是否生效'); + $table->timestamps(); + }); + DB::statement("ALTER TABLE `" . env('DB_PREFIX', '') . "courtesy_cards` comment='优惠券表'"); + + Schema::create('product_courtesy_cards', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('product_id')->comment('商品ID'); + $table->foreign('product_id')->references('id')->on('products')->onDelete('cascade'); + $table->unsignedBigInteger('courtesy_card_mould_id')->comment('优惠券模板ID'); + $table->foreign('courtesy_card_mould_id')->references('id')->on('courtesy_card_moulds')->onDelete('cascade'); + $table->timestamps(); + }); + DB::statement("ALTER TABLE `" . env('DB_PREFIX', '') . "product_courtesy_cards` comment='商品优惠券模板关联表'"); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('product_courtesy_cards'); + Schema::dropIfExists('courtesy_cards'); + Schema::dropIfExists('courtesy_card_moulds'); + } +} diff --git a/database/migrations/2023_11_14_095616_create_parent_orders_table.php b/database/migrations/2023_11_14_095616_create_parent_orders_table.php new file mode 100644 index 0000000000000000000000000000000000000000..7da13418847f09f224fd7952fcc299197d8d34ea --- /dev/null +++ b/database/migrations/2023_11_14_095616_create_parent_orders_table.php @@ -0,0 +1,51 @@ +id(); + $table->string('no')->unique()->comment('订单流水号'); + $table->integer('customer_id')->comment('下单的顾客ID'); + $table->foreign('customer_id')->references('id')->on('sys_customer')->onDelete('cascade'); + $table->decimal('total_amount', 10, 2)->default(0)->comment('订单总金额'); + $table->decimal('payment_amount', 10, 2)->default(0)->comment('实际支付金额'); + $table->unsignedInteger('total_point')->default(0)->comment('订单总积分'); + $table->dateTime('paid_at')->nullable()->comment('支付时间'); + $table->string('payment_method')->nullable()->comment('支付方式'); + $table->string('payment_no')->nullable()->comment('支付平台订单号'); + $table->unsignedBigInteger('payment_point_record_id')->nullable()->comment('积分记录ID'); + $table->datetime('closed_at')->nullable()->comment('取消时间'); + $table->unsignedBigInteger('courtesy_card_id')->nullable()->comment('优惠券ID'); + $table->foreign('courtesy_card_id')->references('id')->on('courtesy_cards')->onDelete('set null'); + $table->string('refund_status')->default(\App\Models\ParentOrder::REFUND_STATUS_PENDING)->comment('退款状态'); + $table->string('remark')->nullable()->comment('订单备注'); + $table->text('extra')->nullable()->comment('其他额外的数据'); + $table->timestamps(); + }); + + DB::statement("ALTER TABLE `" . env('DB_PREFIX', '') . "parent_orders` comment='父订单表'"); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('parent_orders'); + } +} diff --git a/database/migrations/2023_11_14_151348_create_groups_table.php b/database/migrations/2023_11_14_151348_create_groups_table.php new file mode 100644 index 0000000000000000000000000000000000000000..43fd17ecb5b05b9ddd6c14f5dbf213de991e8f25 --- /dev/null +++ b/database/migrations/2023_11_14_151348_create_groups_table.php @@ -0,0 +1,40 @@ +id(); + $table->integer('customer_id')->comment('团长用户ID'); + $table->foreign('customer_id')->references('id')->on('sys_customer')->onDelete('restrict'); + $table->unsignedBigInteger('product_id')->comment('所属商品 id'); + $table->foreign('product_id')->references('id')->on('products')->onDelete('restrict'); + $table->unsignedTinyInteger('group_size')->comment('成团人数'); + $table->dateTime('valid_at')->nullable()->comment('有效时间'); + $table->timestamps(); + }); + DB::statement("ALTER TABLE `" . env('DB_PREFIX', '') . "groups` comment='商城拼团表'"); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + + Schema::dropIfExists('groups'); + } +} diff --git a/database/migrations/2023_11_14_165547_add_fields_to_orders_table.php b/database/migrations/2023_11_14_165547_add_fields_to_orders_table.php new file mode 100644 index 0000000000000000000000000000000000000000..86282ad3daea47e47e6974362bfa64feaef0bd79 --- /dev/null +++ b/database/migrations/2023_11_14_165547_add_fields_to_orders_table.php @@ -0,0 +1,41 @@ +unsignedBigInteger('group_id')->nullable()->comment('拼团ID'); + $table->foreign('group_id')->references('id')->on('groups')->onDelete('cascade'); + $table->unsignedBigInteger('parent_order_id')->nullable()->comment('父订单ID'); + $table->foreign('parent_order_id')->references('id')->on('parent_orders')->onDelete('cascade'); + $table->decimal('payment_amount', 10, 2)->default(0)->comment('实际支付金额')->after('total_amount'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('orders', function (Blueprint $table) { + $table->dropForeign(['group_id']); + $table->dropIndex('orders_group_id_foreign'); + $table->dropColumn('group_id'); + $table->dropForeign(['parent_order_id']); + $table->dropIndex('orders_parent_order_id_foreign'); + $table->dropColumn('parent_order_id'); + }); + } +} diff --git a/database/migrations/2023_11_15_095304_create_group_products_table.php b/database/migrations/2023_11_15_095304_create_group_products_table.php new file mode 100644 index 0000000000000000000000000000000000000000..dae42b7b8fbeb8cf7072338f85b39506e787ba70 --- /dev/null +++ b/database/migrations/2023_11_15_095304_create_group_products_table.php @@ -0,0 +1,33 @@ +bigIncrements('id'); + $table->unsignedBigInteger('product_id'); + $table->foreign('product_id')->references('id')->on('products')->onDelete('cascade'); + $table->unsignedTinyInteger('group_size')->comment('成团人数'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('group_products'); + } +} diff --git a/database/migrations/2023_11_15_095432_insert_data_to_parent_orders_table.php b/database/migrations/2023_11_15_095432_insert_data_to_parent_orders_table.php new file mode 100644 index 0000000000000000000000000000000000000000..140db2578fb1447265479202296de3a505206da1 --- /dev/null +++ b/database/migrations/2023_11_15_095432_insert_data_to_parent_orders_table.php @@ -0,0 +1,69 @@ + $row->no, + 'customer_id' => $row->customer_id, + 'total_amount' => $row->total_amount, + 'payment_amount' => $row->total_amount, + 'total_point' => $row->total_point, + 'payment_method' => $row->payment_method, + 'paid_at' => $row->paid_at, + 'payment_no' => $row->payment_no, + 'payment_point_record_id' => $row->payment_point_record_id, + 'closed_at' => $row->closed_at, + 'refund_status' => $row->refund_status, + 'vin' => $row->vin, + 'store_id' => $row->store_id, + 'address' => $row->address, + 'created_at' => $row->created_at, + 'updated_at' => $row->updated_at, + ]; + $r = ParentOrder::create($insert_data); + $row->parent_order_id = $r->id; + $row->payment_amount = $row->total_amount; + $row->save(); + } + }); + Schema::table('orders', function (Blueprint $table) { + $table->dropColumn('payment_method'); + $table->dropColumn('payment_no'); + $table->dropColumn('closed'); + $table->dropForeign('orders_ibfk_3'); + $table->dropIndex('orders_payment_point_record_id_foreign'); + $table->dropColumn('payment_point_record_id'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('orders', function (Blueprint $table) { + $table->string('payment_method')->nullable()->comment('支付方式'); + $table->string('payment_no')->nullable()->comment('支付平台订单号'); + $table->unsignedBigInteger('payment_point_record_id')->nullable()->comment('积分记录ID'); + $table->foreign('payment_point_record_id')->references('id')->on('customer_point_records')->onDelete('restrict'); + $table->boolean('closed')->default(false)->comment('订单是否已关闭'); + }); + } +} diff --git a/database/migrations/2023_11_23_093743_add_status_to_groups_table.php b/database/migrations/2023_11_23_093743_add_status_to_groups_table.php new file mode 100644 index 0000000000000000000000000000000000000000..036ab151ff3278c41aab02bab0d45779d56f5be5 --- /dev/null +++ b/database/migrations/2023_11_23_093743_add_status_to_groups_table.php @@ -0,0 +1,32 @@ +string('status')->default(\App\Models\Group::STATUS_PENDING)->comment('拼团状态')->after('valid_at'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('groups', function (Blueprint $table) { + $table->dropColumn('status'); + }); + } +} diff --git a/database/migrations/2024_01_19_095854_add_fields_to_product_zones_table.php b/database/migrations/2024_01_19_095854_add_fields_to_product_zones_table.php new file mode 100644 index 0000000000000000000000000000000000000000..03a5c6e0a8ebab7cd705ab5e554860da80c4ef56 --- /dev/null +++ b/database/migrations/2024_01_19_095854_add_fields_to_product_zones_table.php @@ -0,0 +1,38 @@ +string('frame')->comment('小框图片')->after('banner'); + $table->datetime('end_at')->comment('结束时间')->nullable()->after('banner'); + $table->boolean('show')->default(TRUE)->comment('是否展示')->after('banner'); + $table->string('describe')->comment('描述')->after('banner'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('product_zones', function (Blueprint $table) { + $table->dropColumn('frame'); + $table->dropColumn('end_at'); + $table->dropColumn('show'); + $table->dropColumn('describe'); + }); + } +} diff --git a/database/migrations/2024_01_19_163242_add_apply_invoice_to_orders_table.php b/database/migrations/2024_01_19_163242_add_apply_invoice_to_orders_table.php new file mode 100644 index 0000000000000000000000000000000000000000..07a7cbedd4cb58fc7eca56d621fcfdc1b5997ff6 --- /dev/null +++ b/database/migrations/2024_01_19_163242_add_apply_invoice_to_orders_table.php @@ -0,0 +1,34 @@ +datetime('apply_invoice_at')->comment('申请发票时间')->nullable()->after('vin'); + $table->string('invoice_url')->comment('发票地址')->nullable()->after('vin'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('orders', function (Blueprint $table) { + $table->dropColumn('apply_invoice_at'); + $table->dropColumn('invoice_url'); + }); + } +} diff --git a/database/migrations/2024_01_19_164355_add_fields_to_parent_orders_table.php b/database/migrations/2024_01_19_164355_add_fields_to_parent_orders_table.php new file mode 100644 index 0000000000000000000000000000000000000000..3c4f5b8250d2ce3fcfbb4a1fb6bca8cad4109e98 --- /dev/null +++ b/database/migrations/2024_01_19_164355_add_fields_to_parent_orders_table.php @@ -0,0 +1,36 @@ +text('address')->nullable()->comment('JSON 格式的收货地址')->after('extra'); + $table->integer('store_id')->nullable()->comment('服务站ID')->after('extra'); + $table->char('vin', 8)->nullable()->comment('车辆VIN码')->after('extra'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('parent_orders', function (Blueprint $table) { + $table->dropColumn('address'); + $table->dropColumn('store_id'); + $table->dropColumn('vin'); + }); + } +} diff --git a/database/migrations/2024_01_22_150404_add_activity_id_to_products_table.php b/database/migrations/2024_01_22_150404_add_activity_id_to_products_table.php new file mode 100644 index 0000000000000000000000000000000000000000..8f4d471b1d5f463dbb93fb1bbf4e97381578fa50 --- /dev/null +++ b/database/migrations/2024_01_22_150404_add_activity_id_to_products_table.php @@ -0,0 +1,35 @@ +unsignedBigInteger('activity_id')->nullable()->comment('活动ID'); + $table->foreign('activity_id')->references('id')->on('activities')->onDelete('set null'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('products', function (Blueprint $table) { + $table->dropForeign(['activity_id']); + $table->dropIndex('products_activity_id_foreign'); + $table->dropColumn('activity_id'); + }); + } +} diff --git a/database/migrations/2024_01_25_163511_add_limit_draw_count_to_activities_table.php b/database/migrations/2024_01_25_163511_add_limit_draw_count_to_activities_table.php new file mode 100644 index 0000000000000000000000000000000000000000..e70d892aecbe1e19e70cb1afb0bb7bc6815cbfd8 --- /dev/null +++ b/database/migrations/2024_01_25_163511_add_limit_draw_count_to_activities_table.php @@ -0,0 +1,32 @@ +integer('limit_draw_count')->after('order')->unsigned()->default(0)->comment('限制获取抽奖机会次数,0为不限制'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('activities', function (Blueprint $table) { + $table->dropColumn('limit_draw_count'); + }); + } +} diff --git a/database/migrations/2024_02_05_102638_add_total_to_courtesy_card_moulds_table.php b/database/migrations/2024_02_05_102638_add_total_to_courtesy_card_moulds_table.php new file mode 100644 index 0000000000000000000000000000000000000000..053466277c7faaf5b7aa09eb2fcdc8217877f958 --- /dev/null +++ b/database/migrations/2024_02_05_102638_add_total_to_courtesy_card_moulds_table.php @@ -0,0 +1,32 @@ +dropColumn('total'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('courtesy_card_moulds', function (Blueprint $table) { + $table->unsignedInteger('total')->comment('全站可领取的数量'); + }); + } +} diff --git a/database/migrations/2024_11_21_174005_add_stock_show_to_products_table.php b/database/migrations/2024_11_21_174005_add_stock_show_to_products_table.php new file mode 100644 index 0000000000000000000000000000000000000000..66a41b8d31eb5884c515bc484c729e7ca2f5cbb0 --- /dev/null +++ b/database/migrations/2024_11_21_174005_add_stock_show_to_products_table.php @@ -0,0 +1,40 @@ +boolean('stock_show')->default(TRUE)->after('selected')->comment('是否展示库存'); + }); + Schema::table('group_products', function (Blueprint $table) { + $table->unsignedTinyInteger('group_limit')->nullable()->comment('限制成团数'); + $table->tinyInteger('group_stock')->nullable()->comment('剩余成团数'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('products', function (Blueprint $table) { + $table->dropColumn('stock_show'); + }); + Schema::table('group_products', function (Blueprint $table) { + $table->dropColumn('group_limit'); + $table->dropColumn('group_stock'); + }); + } +} diff --git a/database/migrations/2024_11_27_170319_add_start_at_to_group_products_table.php b/database/migrations/2024_11_27_170319_add_start_at_to_group_products_table.php new file mode 100644 index 0000000000000000000000000000000000000000..ce0b1e4c98c133e15d2d7ce0f5207bc1568e59df --- /dev/null +++ b/database/migrations/2024_11_27_170319_add_start_at_to_group_products_table.php @@ -0,0 +1,36 @@ +boolean('on_time')->default(FALSE)->comment('是否控制销售时间'); + $table->dateTime('start_at')->nullable()->comment('开始时间'); + $table->dateTime('end_at')->nullable()->comment('结束时间'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('group_products', function (Blueprint $table) { + $table->dropColumn('on_time'); + $table->dropColumn('start_at'); + $table->dropColumn('end_at'); + }); + } +} diff --git a/database/seeders/CustomerPointRecordSeeder.php b/database/seeders/CustomerPointRecordSeeder.php index 1f74f06b40cd4247f41a08d373649ee4357790de..3fc7c852ee8db8e2d82073a4d5b60d3658bc31a7 100644 --- a/database/seeders/CustomerPointRecordSeeder.php +++ b/database/seeders/CustomerPointRecordSeeder.php @@ -19,8 +19,7 @@ class CustomerPointRecordSeeder extends Seeder { // 获取 Faker 实例 $faker = app(\Faker\Generator::class); - //->where('phone','13260968023') - $customers = Customer::take(10)->orderBy('id', 'asc')->get(); + $customers = Customer::take(10)->orderBy('id', 'asc')->where('id', 533)->get(); $actions = CustomerAction::where('point', '>', 0)->get(); $now = Carbon::now(); $customers->each(function (Customer $customer) use ($now, $actions, $faker) { diff --git a/public/xlsx/order_invoice.xlsx b/public/xlsx/order_invoice.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..bc9d1bdb7535c83d3fa3abc9b7ffaa064ea67966 Binary files /dev/null and b/public/xlsx/order_invoice.xlsx differ diff --git a/public/xlsx/order_ship.xlsx b/public/xlsx/order_ship.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..5fd67a04e6ef7918b5fe80ede9c4720966be19ec Binary files /dev/null and b/public/xlsx/order_ship.xlsx differ diff --git a/routes/admin.php b/routes/admin.php index a83aae297879b8e6822dbecd5cc122d1c12b4400..b17f50f4ae011ac5d1f8c8209fd9f5230fb64c9d 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -55,6 +55,40 @@ $router->group(['namespace' => 'Admin', 'prefix' => 'admin'], function () use ($ $router->get('bindings', ['as' => 'bindings.index', 'uses' => 'AgentBindingController@index']); //邀请记录详情 $router->get('bindings/{id}', ['as' => 'bindings.show', 'uses' => 'AgentBindingController@show']); + //优惠券模板所有 + $router->get('courtesyCardMoulds/all', ['as' => 'courtesyCardMoulds.all', 'uses' => 'CourtesyCardMouldController@all']); + //优惠券模板列表 + $router->get('courtesyCardMoulds', ['as' => 'courtesyCardMoulds.index', 'uses' => 'CourtesyCardMouldController@index']); + //新增优惠券模板 + $router->post('courtesyCardMoulds', ['as' => 'courtesyCardMoulds.store', 'uses' => 'CourtesyCardMouldController@store']); + //修改优惠券模板 + $router->put('courtesyCardMoulds/{id}', ['as' => 'courtesyCardMoulds.update', 'uses' => 'CourtesyCardMouldController@update']); + //优惠券模板详情 + $router->get('courtesyCardMoulds/{id}', ['as' => 'courtesyCardMoulds.show', 'uses' => 'CourtesyCardMouldController@show']); + //删除优惠券模板 + $router->delete('courtesyCardMoulds/{id}', ['as' => 'courtesyCardMoulds.destroy', 'uses' => 'CourtesyCardMouldController@destroy']); + + //发放优惠券 + $router->post('courtesyCards', ['as' => 'courtesyCards.store', 'uses' => 'CourtesyCardController@store']); + //优惠券列表 + $router->get('courtesyCards', ['as' => 'courtesyCards.index', 'uses' => 'CourtesyCardController@index']); + //优惠券详情 + $router->get('courtesyCards/{id}', ['as' => 'courtesyCards.show', 'uses' => 'CourtesyCardController@show']); + //删除优惠券 + $router->delete('courtesyCards/{id}', ['as' => 'courtesyCards.destroy', 'uses' => 'CourtesyCardController@destroy']); + + //商品专区所有 + $router->get('product/zones/all', ['as' => 'product.zones.all', 'uses' => 'ProductZoneController@all']); + //商品专区列表 + $router->get('product/zones', ['as' => 'product.zones.index', 'uses' => 'ProductZoneController@index']); + //新增商品专区 + $router->post('product/zones', ['as' => 'product.zones.store', 'uses' => 'ProductZoneController@store']); + //修改商品专区 + $router->put('product/zones/{id}', ['as' => 'product.zones.update', 'uses' => 'ProductZoneController@update']); + //商品专区详情 + $router->get('product/zones/{id}', ['as' => 'product.zones.show', 'uses' => 'ProductZoneController@show']); + //删除商品专区 + $router->delete('product/zones/{id}', ['as' => 'product.zones.destroy', 'uses' => 'ProductZoneController@destroy']); //富文本列表 $router->get('articles', ['as' => 'articles.index', 'uses' => 'ArticleController@index']); @@ -317,9 +351,9 @@ $router->group(['namespace' => 'Admin', 'prefix' => 'admin'], function () use ($ //卡券列表 $router->get('carCoupons', ['as' => 'carCoupons.index', 'uses' => 'CarCouponController@index']); //油礼券导入 - $router->post('youli/import', [ 'uses' => 'CarCouponController@youliImport']); + $router->post('youli/import', ['uses' => 'CarCouponController@youliImport']); //油礼券导入记录导出 - $router->get('youli/export', [ 'uses' => 'CarCouponController@youliExport']); + $router->get('youli/export', ['uses' => 'CarCouponController@youliExport']); //卡券导入 $router->post('carCoupons/import', ['as' => 'carCoupons.import', 'uses' => 'CarCouponController@import']); //删除券 @@ -369,7 +403,8 @@ $router->group(['namespace' => 'Admin', 'prefix' => 'admin'], function () use ($ //帖子奖励积分 $router->post('topics/{id}/point', ['as' => 'topics.point', 'uses' => 'TopicController@point']); - + //全部商品 + $router->get('products/all', ['as' => 'products.all', 'uses' => 'ProductController@all']); //商品详情 $router->get('products/{id}', ['as' => 'products.show', 'uses' => 'ProductController@show']); //商品列表 @@ -407,6 +442,12 @@ $router->group(['namespace' => 'Admin', 'prefix' => 'admin'], function () use ($ //订单列表 $router->get('orders', ['as' => 'orders.index', 'uses' => 'OrderController@index']); + //批量上传发票 + $router->post('orders/invoiceImport', ['as' => 'orders.invoiceImport', 'uses' => 'OrderController@invoiceImport']); + //批量发货 + $router->post('orders/batchShip', ['as' => 'orders.batchShip', 'uses' => 'OrderController@batchShip']); + //订单上传发票 + $router->post('orders/{id}/uploadInvoice', ['as' => 'orders.uploadInvoice', 'uses' => 'OrderController@uploadInvoice']); //订单详情 $router->get('orders/{id}', ['as' => 'orders.show', 'uses' => 'OrderController@show']); //订单发货 diff --git a/routes/api.php b/routes/api.php index d83e1a0b83cbc344fdcdbcfc624667216b7e8603..6d363b38a31a4ada4658fe0e0deed766cb315399 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,6 +1,15 @@ group(['namespace' => 'Web', 'prefix' => 'web'], function () use ($router) { + + //成团详情 + $router->get('product/groups/{id}', ['as' => 'groups.show', 'uses' => 'GroupController@show']); + //商品专区列表 + $router->get('product/zones', ['as' => 'product.zones.index', 'uses' => 'ProductZoneController@index']); + //商品专区详情 + $router->get('product/zones/{id}', ['as' => 'product.zones.show', 'uses' => 'ProductZoneController@show']); + //商品专区商品列表 + $router->get('product/zones/{id}/products', ['as' => 'product.zones.products', 'uses' => 'ProductZoneController@products']); //通知列表 $router->get('notifications', ['as' => 'notifications.index', 'uses' => 'NotificationsController@index']); //通知统计 @@ -31,6 +40,8 @@ $router->group(['namespace' => 'Web', 'prefix' => 'web'], function () use ($rout $router->get('activities/all', ['as' => 'activities.all', 'uses' => 'ActivityController@all']); //活动详情 $router->get('activities/{sign}', ['as' => 'activities.show', 'uses' => 'ActivityController@show']); + //活动商品 + $router->get('activities/{sign}/products', ['as' => 'activities.products', 'uses' => 'ActivityController@products']); //活动列表 $router->get('activities', ['as' => 'activities.index', 'uses' => 'ActivityController@index']); //积分类型 @@ -45,8 +56,14 @@ $router->group(['namespace' => 'Web', 'prefix' => 'web'], function () use ($rout $router->get('topicCategories', ['as' => 'topicCategories.index', 'uses' => 'TopicCategoryController@index']); //商品栏目 $router->get('categories', ['as' => 'categories.index', 'uses' => 'CategoryController@index']); - //提交订单 - $router->post('orders', ['as' => 'orders.post', 'uses' => 'OrderController@store']); + //拼团 + $router->post('orders/group', ['as' => 'orders.group', 'uses' => 'OrderController@group']); + //秒杀 + $router->post('orders/seckill', ['as' => 'orders.seckill', 'uses' => 'OrderController@seckill']); + //立即下单 + $router->post('orders', ['as' => 'orders.store', 'uses' => 'OrderController@store']); + //购物车下单 + $router->post('orders/cart', ['as' => 'orders.cartStore', 'uses' => 'OrderController@cartStore']); //获取token $router->post('customer/getToken', ['as' => 'customers.getToken', 'uses' => 'CustomerController@getToken']); //经纪人排名 @@ -62,6 +79,7 @@ $router->group(['namespace' => 'Web', 'prefix' => 'web'], function () use ($rout //获取系统配置 $router->get('configs', ['as' => 'configs', 'uses' => 'ConfigController@index']); + $router->group(['middleware' => ['auth', 'throttle:' . config('app.rate_limits.access')]], function () use ($router) { //2024国庆活动 @@ -89,9 +107,29 @@ $router->group(['namespace' => 'Web', 'prefix' => 'web'], function () use ($rout $router->post('topic/posts', ['as' => 'topic.posts.store', 'uses' => 'TopicPostController@store']); //话题列表 $router->get('topic/posts', ['as' => 'topic.posts.index', 'uses' => 'TopicPostController@index']); + //返回购物车下单需要的字段 + $router->post('orders/cart/fields', ['uses' => 'OrderController@cartStoreFields']); + //获取下单时可用的优惠券 + $router->post('orders/cart/cards', ['uses' => 'CourtesyCardController@orderIndex']); + //查看购物车商品数量 + $router->get('cart/count', ['as' => 'cart.count', 'uses' => 'CartItemController@num']); + //添加购物车 + $router->post('cart', ['as' => 'cart.add', 'uses' => 'CartItemController@add']); + //移除商品 + $router->delete('cart', ['as' => 'cart.remove', 'uses' => 'CartItemController@remove']); + //查看购物车 + $router->get('cart', ['as' => 'cart.index', 'uses' => 'CartItemController@index']); + //购物车减一 + $router->put('cart/sub', ['as' => 'cart.sub', 'uses' => 'CartItemController@sub']); + //父订单列表 + $router->get('parent/orders', ['as' => 'parentOrders.index', 'uses' => 'ParentOrderController@index']); + //父订单详情 + $router->get('parent/orders/{id}', ['as' => 'parentOrders.show', 'uses' => 'ParentOrderController@show']); + //待成团列表 + $router->get('product/groups', ['as' => 'groups.index', 'uses' => 'GroupController@index']); //埋点 - $router->post('/eventRecords', ['as' => 'eventRecords.store', 'uses' => 'EventRecordController@store']); + $router->post('eventRecords', ['as' => 'eventRecords.store', 'uses' => 'EventRecordController@store']); //我的信息 $router->get('customer', ['as' => 'customer.me', 'uses' => 'CustomerController@me']); @@ -130,6 +168,8 @@ $router->group(['namespace' => 'Web', 'prefix' => 'web'], function () use ($rout $router->post('orders/{id}/cancel', ['as' => 'orders.cancel', 'uses' => 'OrderController@cancel']); //申请退款 $router->post('orders/{id}/applyRefund', ['as' => 'orders.applyRefund', 'uses' => 'OrderController@applyRefund']); + //申请发票 + $router->post('orders/{id}/applyInvoice', ['as' => 'orders.applyInvoice', 'uses' => 'OrderController@applyInvoice']); //确认收货 $router->put('orders/{id}/receive', ['as' => 'orders.receive', 'uses' => 'OrderController@receive']); @@ -273,5 +313,7 @@ $router->group(['namespace' => 'Web', 'prefix' => 'web'], function () use ($rout //用户信息 $router->get('customer/{id}', ['as' => 'customer.show', 'uses' => 'CustomerController@show']); + //我的优惠券 + $router->get('courtesyCards', ['as' => 'courtesyCards.index', 'uses' => 'CourtesyCardController@index']); }); });