[]]], tags: ['SkuMappings'], parameters: [ new OA\Parameter(name: 'page', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 1)), new OA\Parameter(name: 'per_page', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 15, maximum: 100)), new OA\Parameter(name: 'company_id', in: 'query', required: false, description: '公司 ID 精确筛选', schema: new OA\Schema(type: 'integer')), new OA\Parameter(name: 'platform_id', in: 'query', required: false, description: '平台 ID 精确筛选', schema: new OA\Schema(type: 'integer')), new OA\Parameter(name: 'store_id', in: 'query', required: false, description: '店铺 ID 精确筛选', schema: new OA\Schema(type: 'integer')), new OA\Parameter(name: 'origin_sku', in: 'query', required: false, description: '原始 SKU 模糊搜索', schema: new OA\Schema(type: 'string')), new OA\Parameter(name: 'platform_outer_sku', in: 'query', required: false, description: '平台侧 SKU 模糊搜索', schema: new OA\Schema(type: 'string')), new OA\Parameter(name: 'origin_sku_id', in: 'query', required: false, description: '内部 SKU ID 精确筛选', schema: new OA\Schema(type: 'integer')), new OA\Parameter(name: 'enabled', in: 'query', required: false, description: '启用状态', schema: new OA\Schema(type: 'boolean')), ], responses: [ new OA\Response( response: 200, description: '获取成功', content: new OA\JsonContent(properties: [ new OA\Property(property: 'code', type: 'integer', example: 0), new OA\Property(property: 'message', type: 'string', example: '获取成功'), new OA\Property(property: 'data', properties: [ new OA\Property(property: 'items', type: 'array', items: new OA\Items(ref: '#/components/schemas/SkuMapping')), new OA\Property(property: 'total', type: 'integer', example: 100), new OA\Property(property: 'page', type: 'integer', example: 1), new OA\Property(property: 'per_page', type: 'integer', example: 15), ], type: 'object'), ]) ), new OA\Response(response: 401, description: '未认证', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')), new OA\Response(response: 403, description: '无权限', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')), ] )] #[RequestMapping(path: "", methods: "GET")] public function index(): array { $query = SkuMapping::query()->select([ 'id', 'company_id', 'platform_id', 'store_id', 'origin_sku', 'origin_sku_id', 'platform_outer_sku', 'platform_product_id', 'enabled', 'note', 'created_at', 'updated_at', ]); $this->applyDataScope($query); // 筛选条件 $filters = [ 'company_id' => 'exact', 'platform_id' => 'exact', 'store_id' => 'exact', 'origin_sku_id' => 'exact', ]; foreach ($filters as $field => $type) { $value = $this->request->input($field); if ($value !== null && $value !== '') { $query->where($field, (int) $value); } } $origin_sku = $this->request->input('origin_sku'); if ($origin_sku !== null && $origin_sku !== '') { $query->where('origin_sku', 'ilike', "%{$origin_sku}%"); } $platform_outer_sku = $this->request->input('platform_outer_sku'); if ($platform_outer_sku !== null && $platform_outer_sku !== '') { $query->where('platform_outer_sku', 'ilike', "%{$platform_outer_sku}%"); } $enabled = $this->request->input('enabled'); if ($enabled !== null && $enabled !== '') { $query->where('enabled', filter_var($enabled, FILTER_VALIDATE_BOOLEAN)); } $query->orderBy('created_at', 'desc'); $per_page = min(max((int) $this->request->input('per_page', 15), 1), 100); $page = max((int) $this->request->input('page', 1), 1); $total = $query->count(); $items = $query->offset(($page - 1) * $per_page)->limit($per_page)->get(); return [ 'code' => 0, 'message' => '获取成功', 'data' => [ 'items' => $items, 'total' => $total, 'page' => $page, 'per_page' => $per_page, ], ]; } /** * 检查重复映射 */ #[OA\Get( path: '/sku-mappings/check-duplicate', summary: '检查同平台重复映射', description: '检查指定 origin_sku 在某平台下是否已存在映射记录,帮助用户决定是否复用已有编码', security: [['bearerAuth' => []]], tags: ['SkuMappings'], parameters: [ new OA\Parameter(name: 'origin_sku_id', in: 'query', required: true, description: '内部 SKU ID', schema: new OA\Schema(type: 'integer')), new OA\Parameter(name: 'platform_id', in: 'query', required: true, description: '平台 ID', schema: new OA\Schema(type: 'integer')), ], responses: [ new OA\Response( response: 200, description: '检查完成', content: new OA\JsonContent(properties: [ new OA\Property(property: 'code', type: 'integer', example: 0), new OA\Property(property: 'data', properties: [ new OA\Property(property: 'has_duplicate', type: 'boolean', example: true), new OA\Property(property: 'existing_mappings', type: 'array', items: new OA\Items(type: 'object')), new OA\Property(property: 'message', type: 'string'), ], type: 'object'), ]) ), new OA\Response(response: 422, description: '参数校验失败', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')), ] )] #[RequestMapping(path: "check-duplicate", methods: "GET")] public function checkDuplicate(): ResponseInterface|array { $origin_sku_id = $this->request->input('origin_sku_id'); $platform_id = $this->request->input('platform_id'); if (!$origin_sku_id || !$platform_id) { return $this->response->json([ 'code' => 422, 'message' => '缺少必填参数: origin_sku_id, platform_id', ])->withStatus(422); } $result = $this->skuService->checkDuplicate((int) $origin_sku_id, (int) $platform_id); return [ 'code' => 0, 'message' => 'success', 'data' => $result, ]; } /** * 自动生成 platform_outer_sku */ #[OA\Post( path: '/sku-mappings/generate-sku', summary: '自动生成平台侧 SKU', description: '根据指定策略自动生成 platform_outer_sku,同时返回重复检测结果', security: [['bearerAuth' => []]], tags: ['SkuMappings'], requestBody: new OA\RequestBody( required: true, content: new OA\JsonContent( required: ['origin_sku_id', 'platform_id', 'strategy'], properties: [ new OA\Property(property: 'origin_sku_id', type: 'integer', example: 1), new OA\Property(property: 'platform_id', type: 'integer', example: 1), new OA\Property(property: 'strategy', type: 'string', enum: ['prefix', 'prefix_random', 'manual'], example: 'prefix'), new OA\Property(property: 'prefix', type: 'string', example: 'C03'), new OA\Property(property: 'random_length', type: 'integer', example: 4), new OA\Property(property: 'manual_value', type: 'string', nullable: true), ] ) ), responses: [ new OA\Response( response: 200, description: '生成成功', content: new OA\JsonContent(properties: [ new OA\Property(property: 'code', type: 'integer', example: 0), new OA\Property(property: 'data', properties: [ new OA\Property(property: 'generated_sku', type: 'string', example: 'C03_0032'), new OA\Property(property: 'strategy_record', type: 'string', example: 'prefix:C03'), new OA\Property(property: 'has_duplicate', type: 'boolean'), new OA\Property(property: 'existing_mappings', type: 'array', items: new OA\Items(type: 'object')), new OA\Property(property: 'message', type: 'string'), ], type: 'object'), ]) ), new OA\Response(response: 422, description: '参数校验失败', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')), ] )] #[RequestMapping(path: "generate-sku", methods: "POST")] public function generateSku(): ResponseInterface|array { $data = $this->request->all(); $required = ['origin_sku_id', 'platform_id', 'strategy']; foreach ($required as $field) { if (!isset($data[$field]) || $data[$field] === '') { return $this->response->json([ 'code' => 422, 'message' => "缺少必填参数: {$field}", ])->withStatus(422); } } $origin_sku_id = (int) $data['origin_sku_id']; $platform_id = (int) $data['platform_id']; $strategy = $data['strategy']; $prefix = $data['prefix'] ?? ''; $random_length = (int) ($data['random_length'] ?? 4); $manual_value = $data['manual_value'] ?? null; // 查找 origin_sku 记录 $sku_origin = SkuOrigin::query()->find($origin_sku_id); if (!$sku_origin) { return $this->response->json([ 'code' => 422, 'message' => '指定的内部 SKU 不存在', ])->withStatus(422); } // 生成 platform_outer_sku $generated_sku = $this->skuService->generatePlatformOuterSku( strategy: $strategy, origin_sku: $sku_origin->sku, prefix: $prefix, random_length: $random_length, manual_value: $manual_value, ); $strategy_record = $this->skuService->buildStrategyRecord($strategy, $prefix, $random_length); // 检查重复 $duplicate_check = $this->skuService->checkDuplicate($origin_sku_id, $platform_id); return [ 'code' => 0, 'message' => 'success', 'data' => [ 'generated_sku' => $generated_sku, 'strategy_record' => $strategy_record, 'has_duplicate' => $duplicate_check['has_duplicate'], 'existing_mappings' => $duplicate_check['existing_mappings'], 'message' => $duplicate_check['message'], ], ]; } /** * 映射详情 */ #[OA\Get( path: '/sku-mappings/{id}', summary: 'SKU 映射详情', security: [['bearerAuth' => []]], tags: ['SkuMappings'], parameters: [ new OA\Parameter(name: 'id', in: 'path', required: true, schema: new OA\Schema(type: 'integer')), ], responses: [ new OA\Response( response: 200, description: '获取成功', content: new OA\JsonContent(properties: [ new OA\Property(property: 'code', type: 'integer', example: 0), new OA\Property(property: 'message', type: 'string', example: '获取成功'), new OA\Property(property: 'data', ref: '#/components/schemas/SkuMapping'), ]) ), new OA\Response(response: 404, description: '数据不存在', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')), ] )] #[RequestMapping(path: "{id}", methods: "GET")] public function show(int $id): ResponseInterface|array { $query = SkuMapping::query(); $this->applyDataScope($query); $record = $query->where('id', $id)->first(); if (!$record) { return $this->response->json([ 'code' => 404, 'message' => '数据不存在', ])->withStatus(404); } return [ 'code' => 0, 'message' => '获取成功', 'data' => $record, ]; } /** * 创建映射 */ #[OA\Post( path: '/sku-mappings', summary: '创建 SKU 映射', security: [['bearerAuth' => []]], tags: ['SkuMappings'], requestBody: new OA\RequestBody( required: true, content: new OA\JsonContent( required: ['company_id', 'platform_id', 'origin_sku'], properties: [ new OA\Property(property: 'company_id', type: 'integer', example: 3), new OA\Property(property: 'platform_id', type: 'integer', example: 1), new OA\Property(property: 'store_id', type: 'integer', nullable: true, example: 101), new OA\Property(property: 'origin_sku', type: 'string', example: '0032'), new OA\Property(property: 'origin_sku_id', type: 'integer', nullable: true, example: 1), new OA\Property(property: 'platform_product_id', type: 'string', example: 'ITEM-001'), new OA\Property(property: 'platform_outer_sku', type: 'string', nullable: true, example: 'C03_0032'), new OA\Property(property: 'generation_strategy', type: 'string', nullable: true, example: 'prefix:C03'), new OA\Property(property: 'warehouse_id', type: 'integer', nullable: true), new OA\Property(property: 'enabled', type: 'boolean', example: true), new OA\Property(property: 'note', type: 'string', nullable: true), ] ) ), responses: [ new OA\Response( response: 201, description: '创建成功', content: new OA\JsonContent(properties: [ new OA\Property(property: 'code', type: 'integer', example: 0), new OA\Property(property: 'message', type: 'string', example: '创建成功'), new OA\Property(property: 'data', ref: '#/components/schemas/SkuMapping'), ]) ), new OA\Response(response: 422, description: '参数校验失败', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')), ] )] #[RequestMapping(path: "", methods: "POST")] public function store(): ResponseInterface|array { $data = $this->request->all(); $required_fields = ['company_id', 'platform_id', 'origin_sku']; foreach ($required_fields as $field) { if (!isset($data[$field]) || $data[$field] === '') { return $this->response->json([ 'code' => 422, 'message' => "缺少必填字段: {$field}", ])->withStatus(422); } } $record = SkuMapping::query()->create([ 'company_id' => (int) $data['company_id'], 'platform_id' => (int) $data['platform_id'], 'store_id' => isset($data['store_id']) ? (int) $data['store_id'] : null, 'origin_sku' => $data['origin_sku'], 'origin_sku_id' => isset($data['origin_sku_id']) ? (int) $data['origin_sku_id'] : null, 'platform_product_id' => $data['platform_product_id'] ?? null, 'platform_outer_sku' => $data['platform_outer_sku'] ?? null, 'generation_strategy' => $data['generation_strategy'] ?? null, 'warehouse_id' => isset($data['warehouse_id']) ? (int) $data['warehouse_id'] : null, 'enabled' => $data['enabled'] ?? true, 'note' => $data['note'] ?? null, ]); $user_id = OperationLogService::getCurrentUserId(); if ($user_id) { OperationLogService::log( $user_id, 'create', 'sku_mapping', $record->id, '创建 SKU 映射: ' . $data['origin_sku'] . ' → ' . ($data['platform_outer_sku'] ?? 'N/A'), ip: $this->request->getHeaderLine('x-real-ip') ?: null, ); } return $this->response->json([ 'code' => 0, 'message' => '创建成功', 'data' => $record, ])->withStatus(201); } /** * 更新映射 */ #[OA\Put( path: '/sku-mappings/{id}', summary: '更新 SKU 映射', security: [['bearerAuth' => []]], tags: ['SkuMappings'], parameters: [ new OA\Parameter(name: 'id', in: 'path', required: true, schema: new OA\Schema(type: 'integer')), ], requestBody: new OA\RequestBody( required: true, content: new OA\JsonContent(properties: [ new OA\Property(property: 'origin_sku', type: 'string'), new OA\Property(property: 'origin_sku_id', type: 'integer', nullable: true), new OA\Property(property: 'platform_product_id', type: 'string'), new OA\Property(property: 'platform_outer_sku', type: 'string', nullable: true), new OA\Property(property: 'generation_strategy', type: 'string', nullable: true), new OA\Property(property: 'store_id', type: 'integer', nullable: true), new OA\Property(property: 'warehouse_id', type: 'integer', nullable: true), new OA\Property(property: 'enabled', type: 'boolean'), new OA\Property(property: 'note', type: 'string', nullable: true), ]) ), responses: [ new OA\Response( response: 200, description: '更新成功', content: new OA\JsonContent(properties: [ new OA\Property(property: 'code', type: 'integer', example: 0), new OA\Property(property: 'message', type: 'string', example: '更新成功'), new OA\Property(property: 'data', ref: '#/components/schemas/SkuMapping'), ]) ), new OA\Response(response: 404, description: '数据不存在', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')), ] )] #[RequestMapping(path: "{id}", methods: "PUT")] public function update(int $id): ResponseInterface|array { $query = SkuMapping::query(); $this->applyDataScope($query); $record = $query->where('id', $id)->first(); if (!$record) { return $this->response->json([ 'code' => 404, 'message' => '数据不存在', ])->withStatus(404); } $data = $this->request->all(); $allowed_fields = [ 'origin_sku', 'origin_sku_id', 'platform_product_id', 'platform_outer_sku', 'generation_strategy', 'store_id', 'warehouse_id', 'enabled', 'note', ]; $update_data = array_intersect_key($data, array_flip($allowed_fields)); $record->update($update_data); $user_id = OperationLogService::getCurrentUserId(); if ($user_id) { OperationLogService::log( $user_id, 'update', 'sku_mapping', $record->id, "更新 SKU 映射 #{$record->id}", detail: ['updated_fields' => array_keys($update_data)], ip: $this->request->getHeaderLine('x-real-ip') ?: null, ); } return [ 'code' => 0, 'message' => '更新成功', 'data' => $record->fresh(), ]; } /** * 删除映射 */ #[OA\Delete( path: '/sku-mappings/{id}', summary: '删除 SKU 映射', security: [['bearerAuth' => []]], tags: ['SkuMappings'], parameters: [ new OA\Parameter(name: 'id', in: 'path', required: true, schema: new OA\Schema(type: 'integer')), ], responses: [ new OA\Response( response: 200, description: '删除成功', content: new OA\JsonContent(properties: [ new OA\Property(property: 'code', type: 'integer', example: 0), new OA\Property(property: 'message', type: 'string', example: '删除成功'), ]) ), new OA\Response(response: 404, description: '数据不存在', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')), ] )] #[RequestMapping(path: "{id}", methods: "DELETE")] public function destroy(int $id): ResponseInterface|array { $query = SkuMapping::query(); $this->applyDataScope($query); $record = $query->where('id', $id)->first(); if (!$record) { return $this->response->json([ 'code' => 404, 'message' => '数据不存在', ])->withStatus(404); } $record->delete(); $user_id = OperationLogService::getCurrentUserId(); if ($user_id) { OperationLogService::log( $user_id, 'delete', 'sku_mapping', $id, "删除 SKU 映射 #{$id}", ip: $this->request->getHeaderLine('x-real-ip') ?: null, ); } return [ 'code' => 0, 'message' => '删除成功', ]; } /** * DataScope 过滤 */ private function applyDataScope(\Hyperf\Database\Model\Builder $query): void { $scope_type = $this->request->getAttribute('scope_type'); $scope_ids = $this->request->getAttribute('scope_ids', []); if ($scope_type === 'store') { $query->whereIn('store_id', $scope_ids); } elseif ($scope_type === 'platform') { $query->whereIn('platform_id', $scope_ids); } } }