'like', 'category_id' => '=', 'supplier_id' => '=', 'status' => '=', ]; protected array $quickSearchField = ['name', 'spec']; /** A1 商品列表(含分类/供应商/各等级价格) */ #[GetRoute(authorize: 'query')] public function query(Request $request): JsonResponse { $params = $request->all(); $pageSize = $params['pageSize'] ?? 10; $data = $this->buildSearch( $params, ProductModel::query()->with(['category:id,name', 'supplier:id,name', 'prices.level:id,name']) ) ->orderBy('sort') ->orderBy('id', 'desc') ->paginate($pageSize) ->toArray(); return $this->success($data); } /** 创建商品(事务内建商品 + 同步等级价格) */ #[PostRoute(authorize: 'create')] public function create(ProductFormRequest $request): JsonResponse { $validated = $request->validated(); $prices = $validated['prices'] ?? []; unset($validated['prices']); $product = DB::transaction(function () use ($validated, $prices) { $product = ProductModel::create($validated); foreach ($prices as $row) { ProductPriceModel::create([ 'product_id' => $product->id, 'level_id' => (int) $row['level_id'], 'price' => $row['price'], ]); } return $product; }); return $this->success(['id' => $product->id]); } /** 编辑商品(prices 按 level_id upsert,删除已移除的等级行;未提交 prices 键时保持原价) */ #[PutRoute(route: '/{id}', authorize: 'update', where: ['id' => '[0-9]+'])] public function update(int $id, ProductFormRequest $request): JsonResponse { $product = ProductModel::find($id); if (empty($product)) { throw new RepositoryException('商品不存在'); } $validated = $request->validated(); $prices = $validated['prices'] ?? []; unset($validated['prices']); DB::transaction(function () use ($product, $validated, $prices, $request) { $product->update($validated); if ($request->has('prices')) { $levelIds = []; foreach ($prices as $row) { $levelId = (int) $row['level_id']; $levelIds[] = $levelId; ProductPriceModel::updateOrCreate( ['product_id' => $product->id, 'level_id' => $levelId], ['price' => $row['price']], ); } $product->prices()->whereNotIn('level_id', $levelIds)->delete(); } }); return $this->success(); } /** 删除商品(软删除,连带价格行一并删除) */ #[DeleteRoute(route: '/{id}', authorize: 'delete', where: ['id' => '[0-9]+'])] public function delete(int $id): JsonResponse { $product = ProductModel::find($id); if (empty($product)) { throw new RepositoryException('商品不存在'); } DB::transaction(function () use ($product) { $product->prices()->delete(); $product->delete(); }); return $this->success(); } /** * A2 价格矩阵:行=商品(支持 category_id / keyword 过滤),列=全部启用等级,值=price(缺失为 null) */ #[GetRoute('/priceMatrix', 'query')] public function priceMatrix(Request $request): JsonResponse { $query = ProductModel::query()->with('prices:id,product_id,level_id,price'); if (($categoryId = (int) $request->input('category_id', 0)) > 0) { $query->where('category_id', $categoryId); } $keyword = trim((string) $request->input('keyword', '')); if ($keyword !== '') { $query->where(function ($q) use ($keyword) { $q->where('name', 'like', '%' . $keyword . '%') ->orWhere('spec', 'like', '%' . $keyword . '%'); }); } $products = $query->orderBy('sort')->orderBy('id')->get(); $levels = CustomerLevelModel::query() ->where('status', CustomerLevelModel::STATUS_NORMAL) ->orderBy('sort') ->get(['id', 'name']); $rows = $products->map(function (ProductModel $product) use ($levels) { $priceMap = $product->prices->keyBy('level_id'); $row = [ 'id' => $product->id, 'name' => $product->name, 'spec' => $product->spec, 'unit' => $product->unit, ]; foreach ($levels as $level) { $row['price_' . $level->id] = isset($priceMap[$level->id]) ? (float) $priceMap[$level->id]->price : null; } return $row; }); return $this->success([ 'levels' => $levels->toArray(), 'rows' => $rows->toArray(), ]); } /** * A2 批量调价:事务写入,写完后给受影响门店生成 Notice(type=price) */ #[PutRoute('/batchPrice', 'batchPrice')] public function batchPrice(BatchPriceRequest $request): JsonResponse { $updates = $request->validated('updates'); DB::transaction(function () use ($updates) { $productIds = []; $levelIds = []; foreach ($updates as $row) { ProductPriceModel::updateOrCreate( ['product_id' => (int) $row['product_id'], 'level_id' => (int) $row['level_id']], ['price' => $row['price']], ); $productIds[(int) $row['product_id']] = true; $levelIds[(int) $row['level_id']] = true; } $productNames = ProductModel::whereIn('id', array_keys($productIds)) ->pluck('name') ->implode('、'); $content = mb_substr('以下商品价格已调整:' . $productNames . ',下次登录小程序后按新价格显示', 0, 500); // 受影响门店:客户等级在本次调价等级范围内的正常门店,通知其绑定的正常用户 $userIds = UserModel::query() ->where('type', UserModel::TYPE_STORE) ->where('status', UserModel::STATUS_NORMAL) ->whereIn('store_id', function ($q) use ($levelIds) { $q->select('id') ->from('store') ->where('status', StoreModel::STATUS_NORMAL) ->whereIn('level_id', array_keys($levelIds)); }) ->pluck('id'); foreach ($userIds as $userId) { NoticeModel::create([ 'user_id' => $userId, 'type' => NoticeModel::TYPE_PRICE, 'title' => '商品价格变更', 'content' => $content, 'data' => [ 'product_ids' => array_keys($productIds), 'level_ids' => array_keys($levelIds), ], 'is_read' => NoticeModel::UNREAD, ]); } }); return $this->success(); } /** 商品下拉选项(仅上架,下单等场景用) */ #[GetRoute('/options', 'query')] public function options(Request $request): JsonResponse { $query = ProductModel::query()->where('status', ProductModel::STATUS_ON); $keyword = trim((string) $request->input('keyword', '')); if ($keyword !== '') { $query->where('name', 'like', '%' . $keyword . '%'); } $data = $query->orderBy('sort') ->get(['id', 'name', 'spec', 'unit']) ->toArray(); return $this->success($data); } }