diff --git a/app/Http/Controllers/Api/V1/ProductController.php b/app/Http/Controllers/Api/V1/ProductController.php index 04cecf8..65f1765 100644 --- a/app/Http/Controllers/Api/V1/ProductController.php +++ b/app/Http/Controllers/Api/V1/ProductController.php @@ -70,11 +70,13 @@ public function search(Request $request) }, __('message.product.searched')); } + // Note: toggle 메서드는 is_active 필드 제거로 인해 비활성화됨 + // 필요시 attributes JSON이나 별도 필드로 구현 // POST /products/{id}/toggle - public function toggle(int $id) - { - return ApiResponse::handle(function () use ($id) { - return $this->service->toggle($id); - }, __('message.product.toggled')); - } + // public function toggle(int $id) + // { + // return ApiResponse::handle(function () use ($id) { + // return $this->service->toggle($id); + // }, __('message.product.toggled')); + // } } diff --git a/app/Http/Requests/Product/ProductStoreRequest.php b/app/Http/Requests/Product/ProductStoreRequest.php index c47a4ce..91ece77 100644 --- a/app/Http/Requests/Product/ProductStoreRequest.php +++ b/app/Http/Requests/Product/ProductStoreRequest.php @@ -14,16 +14,29 @@ public function authorize(): bool public function rules(): array { return [ + // 기본 필드 'code' => 'required|string|max:30', 'name' => 'required|string|max:100', + 'unit' => 'nullable|string|max:10', 'category_id' => 'required|integer', 'product_type' => 'required|string|max:30', - 'attributes' => 'nullable|array', 'description' => 'nullable|string|max:255', - 'is_sellable' => 'nullable|in:0,1', - 'is_purchasable' => 'nullable|in:0,1', - 'is_producible' => 'nullable|in:0,1', - 'is_active' => 'nullable|in:0,1', + + // 상태 플래그 + 'is_sellable' => 'nullable|boolean', + 'is_purchasable' => 'nullable|boolean', + 'is_producible' => 'nullable|boolean', + + // 하이브리드 구조: 고정 필드 + 'safety_stock' => 'nullable|integer|min:0', + 'lead_time' => 'nullable|integer|min:0', + 'is_variable_size' => 'nullable|boolean', + 'product_category' => 'nullable|string|max:20', + 'part_type' => 'nullable|string|max:20', + + // 하이브리드 구조: 동적 필드 + 'attributes' => 'nullable|array', + 'attributes_archive' => 'nullable|array', ]; } } diff --git a/app/Http/Requests/Product/ProductUpdateRequest.php b/app/Http/Requests/Product/ProductUpdateRequest.php index 7a581d6..6379e53 100644 --- a/app/Http/Requests/Product/ProductUpdateRequest.php +++ b/app/Http/Requests/Product/ProductUpdateRequest.php @@ -14,16 +14,29 @@ public function authorize(): bool public function rules(): array { return [ + // 기본 필드 'code' => 'sometimes|string|max:30', 'name' => 'sometimes|string|max:100', + 'unit' => 'nullable|string|max:10', 'category_id' => 'sometimes|integer', 'product_type' => 'sometimes|string|max:30', - 'attributes' => 'nullable|array', 'description' => 'nullable|string|max:255', - 'is_sellable' => 'nullable|in:0,1', - 'is_purchasable' => 'nullable|in:0,1', - 'is_producible' => 'nullable|in:0,1', - 'is_active' => 'nullable|in:0,1', + + // 상태 플래그 + 'is_sellable' => 'nullable|boolean', + 'is_purchasable' => 'nullable|boolean', + 'is_producible' => 'nullable|boolean', + + // 하이브리드 구조: 고정 필드 + 'safety_stock' => 'nullable|integer|min:0', + 'lead_time' => 'nullable|integer|min:0', + 'is_variable_size' => 'nullable|boolean', + 'product_category' => 'nullable|string|max:20', + 'part_type' => 'nullable|string|max:20', + + // 하이브리드 구조: 동적 필드 + 'attributes' => 'nullable|array', + 'attributes_archive' => 'nullable|array', ]; } } diff --git a/app/Services/ProductService.php b/app/Services/ProductService.php index dafef05..d1bb912 100644 --- a/app/Services/ProductService.php +++ b/app/Services/ProductService.php @@ -5,7 +5,6 @@ use App\Models\Commons\Category; use App\Models\Products\CommonCode; use App\Models\Products\Product; -use Illuminate\Support\Facades\Validator; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; class ProductService extends Service @@ -89,9 +88,11 @@ public function index(array $params) if ($productType) { $query->where('product_type', $productType); } - if ($active !== null && $active !== '') { - $query->where('is_active', (int) $active); - } + // Note: is_active 필드는 하이브리드 구조로 전환하면서 제거됨 + // 필요시 attributes JSON이나 별도 필드로 관리 + // if ($active !== null && $active !== '') { + // $query->where('is_active', (int) $active); + // } $paginator = $query->orderBy('id')->paginate($size); @@ -116,21 +117,10 @@ public function store(array $data) $tenantId = $this->tenantId(); $userId = $this->apiUserId(); - $v = Validator::make($data, [ - 'code' => 'required|string|max:30', - 'name' => 'required|string|max:100', - 'category_id' => 'required|integer', - 'product_type' => 'required|string|max:30', - 'attributes' => 'nullable|array', - 'description' => 'nullable|string|max:255', - 'is_sellable' => 'nullable|in:0,1', - 'is_purchasable' => 'nullable|in:0,1', - 'is_producible' => 'nullable|in:0,1', - 'is_active' => 'nullable|in:0,1', - ]); - $payload = $v->validate(); + // FormRequest에서 이미 검증됨 + $payload = $data; - // tenant별 code 유니크 수동 체크(운영 전 DB 유니크 구성도 권장) + // tenant별 code 유니크 수동 체크 $dup = Product::query() ->where('tenant_id', $tenantId) ->where('code', $payload['code']) @@ -139,14 +129,13 @@ public function store(array $data) throw new BadRequestHttpException(__('error.duplicate_key')); } + // 기본값 설정 $payload['tenant_id'] = $tenantId; $payload['created_by'] = $userId; - $payload['is_sellable'] = $payload['is_sellable'] ?? 1; - $payload['is_purchasable'] = $payload['is_purchasable'] ?? 0; - $payload['is_producible'] = $payload['is_producible'] ?? 1; - $payload['is_active'] = $payload['is_active'] ?? 1; + $payload['is_sellable'] = $payload['is_sellable'] ?? true; + $payload['is_purchasable'] = $payload['is_purchasable'] ?? false; + $payload['is_producible'] = $payload['is_producible'] ?? true; - // attributes array → json 저장 (Eloquent casts가 array면 그대로 가능) return Product::create($payload); } @@ -173,20 +162,10 @@ public function update(int $id, array $data) throw new BadRequestHttpException(__('error.not_found')); } - $v = Validator::make($data, [ - 'code' => 'sometimes|string|max:30', - 'name' => 'sometimes|string|max:100', - 'category_id' => 'sometimes|integer', - 'product_type' => 'sometimes|string|max:30', - 'attributes' => 'nullable|array', - 'description' => 'nullable|string|max:255', - 'is_sellable' => 'nullable|in:0,1', - 'is_purchasable' => 'nullable|in:0,1', - 'is_producible' => 'nullable|in:0,1', - 'is_active' => 'nullable|in:0,1', - ]); - $payload = $v->validate(); + // FormRequest에서 이미 검증됨 + $payload = $data; + // code 변경 시 중복 체크 if (isset($payload['code']) && $payload['code'] !== $p->code) { $dup = Product::query() ->where('tenant_id', $tenantId) @@ -229,24 +208,25 @@ public function search(array $params) }); } - return $qr->orderBy('name')->limit($lim)->get(['id', 'code', 'name', 'product_type', 'category_id', 'is_active']); + return $qr->orderBy('name')->limit($lim)->get(['id', 'code', 'name', 'product_type', 'category_id']); } - // 활성 토글 - public function toggle(int $id) - { - $tenantId = $this->tenantId(); - $userId = $this->apiUserId(); - - $p = Product::query()->where('tenant_id', $tenantId)->find($id); - if (! $p) { - throw new BadRequestHttpException(__('error.not_found')); - } - - $p->is_active = $p->is_active ? 0 : 1; - $p->updated_by = $userId; - $p->save(); - - return ['id' => $p->id, 'is_active' => (int) $p->is_active]; - } + // Note: toggle 메서드는 is_active 필드 제거로 인해 비활성화됨 + // 필요시 attributes JSON이나 별도 필드로 구현 + // public function toggle(int $id) + // { + // $tenantId = $this->tenantId(); + // $userId = $this->apiUserId(); + // + // $p = Product::query()->where('tenant_id', $tenantId)->find($id); + // if (! $p) { + // throw new BadRequestHttpException(__('error.not_found')); + // } + // + // $p->is_active = $p->is_active ? 0 : 1; + // $p->updated_by = $userId; + // $p->save(); + // + // return ['id' => $p->id, 'is_active' => (int) $p->is_active]; + // } }