From 07f0db17a7d95d0d843513d29b37eb53ec7ecf43 Mon Sep 17 00:00:00 2001 From: hskwon Date: Thu, 11 Dec 2025 11:07:33 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=92=88=EB=AA=A9=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EC=A4=91=EB=B3=B5=20=EC=8B=9C=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=20=EB=B0=8F=20=EC=A4=91=EB=B3=B5=20ID=20?= =?UTF-8?q?=EC=A0=9C=EA=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DuplicateCodeException 커스텀 예외 추가 - 등록/수정 시 자동 코드 증가 기능 제거 - 중복 발견 시 duplicate_id, duplicate_code 함께 반환 - resolveUniqueCode(), resolveUniqueMaterialCode() 메서드 제거 --- app/Exceptions/DuplicateCodeException.php | 51 ++++++++ app/Services/ItemsService.php | 140 +++++----------------- lang/ko/error.php | 1 + 3 files changed, 81 insertions(+), 111 deletions(-) create mode 100644 app/Exceptions/DuplicateCodeException.php diff --git a/app/Exceptions/DuplicateCodeException.php b/app/Exceptions/DuplicateCodeException.php new file mode 100644 index 0000000..4c4e8f8 --- /dev/null +++ b/app/Exceptions/DuplicateCodeException.php @@ -0,0 +1,51 @@ +duplicateCode = $code; + $this->duplicateId = $duplicateId; + + parent::__construct($message ?? __('error.item.duplicate_code')); + } + + public function getDuplicateId(): int + { + return $this->duplicateId; + } + + public function getDuplicateCode(): string + { + return $this->duplicateCode; + } + + /** + * HTTP 응답으로 변환 + */ + public function render() + { + return response()->json([ + 'success' => false, + 'message' => $this->getMessage(), + 'error' => [ + 'code' => 400, + ], + 'duplicate_id' => $this->duplicateId, + 'duplicate_code' => $this->duplicateCode, + ], 400); + } +} \ No newline at end of file diff --git a/app/Services/ItemsService.php b/app/Services/ItemsService.php index ed6c8ca..d809b86 100644 --- a/app/Services/ItemsService.php +++ b/app/Services/ItemsService.php @@ -3,6 +3,7 @@ namespace App\Services; use App\Constants\SystemFields; +use App\Exceptions\DuplicateCodeException; use App\Helpers\ItemTypeHelper; use App\Models\ItemMaster\ItemField; use App\Models\Materials\Material; @@ -448,8 +449,16 @@ public function createItem(array $data): Product|Material */ private function createProduct(array $data, int $tenantId, int $userId): Product { - // 품목 코드 중복 시 자동 증가 - $data['code'] = $this->resolveUniqueCode($data['code'], $tenantId); + // 품목 코드 중복 체크 + $code = $data['code'] ?? ''; + $existingProduct = Product::withTrashed() + ->where('tenant_id', $tenantId) + ->where('code', $code) + ->first(); + + if ($existingProduct) { + throw new DuplicateCodeException($code, $existingProduct->id); + } // 동적 필드를 options에 병합 $this->processDynamicOptions($data, 'products'); @@ -470,9 +479,16 @@ private function createProduct(array $data, int $tenantId, int $userId): Product */ private function createMaterial(array $data, int $tenantId, int $userId, string $materialType): Material { - // 품목 코드 중복 시 자동 증가 (Materials용) + // 품목 코드 중복 체크 $code = $data['code'] ?? $data['material_code'] ?? ''; - $data['material_code'] = $this->resolveUniqueMaterialCode($code, $tenantId); + $existingMaterial = Material::withTrashed() + ->where('tenant_id', $tenantId) + ->where('material_code', $code) + ->first(); + + if ($existingMaterial) { + throw new DuplicateCodeException($code, $existingMaterial->id); + } // 동적 필드를 options에 병합 $this->processDynamicOptions($data, 'materials'); @@ -481,7 +497,7 @@ private function createMaterial(array $data, int $tenantId, int $userId, string 'tenant_id' => $tenantId, 'created_by' => $userId, 'material_type' => $materialType, - 'material_code' => $data['material_code'], + 'material_code' => $code, 'name' => $data['name'], 'unit' => $data['unit'], 'category_id' => $data['category_id'] ?? null, @@ -498,104 +514,6 @@ private function createMaterial(array $data, int $tenantId, int $userId, string return Material::create($payload); } - /** - * Material 코드 중복 체크 및 고유 코드 생성 - */ - private function resolveUniqueMaterialCode(string $code, int $tenantId): string - { - $exists = Material::withTrashed() - ->where('tenant_id', $tenantId) - ->where('material_code', $code) - ->exists(); - - if (! $exists) { - return $code; - } - - // 마지막이 숫자인지 확인 - if (preg_match('/^(.+?)(\d+)$/', $code, $matches)) { - $prefix = $matches[1]; - $number = (int) $matches[2]; - $digits = strlen($matches[2]); - - do { - $number++; - $newCode = $prefix.str_pad($number, $digits, '0', STR_PAD_LEFT); - $exists = Material::withTrashed() - ->where('tenant_id', $tenantId) - ->where('material_code', $newCode) - ->exists(); - } while ($exists); - - return $newCode; - } - - // 마지막이 문자면 -001 추가 - $suffix = 1; - do { - $newCode = $code.'-'.str_pad($suffix, 3, '0', STR_PAD_LEFT); - $exists = Material::withTrashed() - ->where('tenant_id', $tenantId) - ->where('material_code', $newCode) - ->exists(); - $suffix++; - } while ($exists); - - return $newCode; - } - - /** - * 중복되지 않는 고유 코드 생성 - * - * - 중복 없으면 원본 반환 - * - 마지막이 숫자면 숫자 증가 (P-001 → P-002) - * - 마지막이 문자면 -001 추가 (ABC → ABC-001) - */ - private function resolveUniqueCode(string $code, int $tenantId): string - { - // 삭제된 항목 포함해서 중복 체크 - $exists = Product::withTrashed() - ->where('tenant_id', $tenantId) - ->where('code', $code) - ->exists(); - - if (! $exists) { - return $code; - } - - // 마지막이 숫자인지 확인 (예: P-001, ITEM123) - if (preg_match('/^(.+?)(\d+)$/', $code, $matches)) { - $prefix = $matches[1]; - $number = (int) $matches[2]; - $digits = strlen($matches[2]); - - // 숫자 증가하며 고유 코드 찾기 - do { - $number++; - $newCode = $prefix.str_pad($number, $digits, '0', STR_PAD_LEFT); - $exists = Product::withTrashed() - ->where('tenant_id', $tenantId) - ->where('code', $newCode) - ->exists(); - } while ($exists); - - return $newCode; - } - - // 마지막이 문자면 -001 추가 - $suffix = 1; - do { - $newCode = $code.'-'.str_pad($suffix, 3, '0', STR_PAD_LEFT); - $exists = Product::withTrashed() - ->where('tenant_id', $tenantId) - ->where('code', $newCode) - ->exists(); - $suffix++; - } while ($exists); - - return $newCode; - } - /** * 품목 수정 (Product/Material 통합) * @@ -632,14 +550,14 @@ private function updateProduct(int $id, array $data): Product // 코드 변경 시 중복 체크 if (isset($data['code']) && $data['code'] !== $product->code) { - $exists = Product::query() + $existingProduct = Product::query() ->where('tenant_id', $tenantId) ->where('code', $data['code']) ->where('id', '!=', $product->id) - ->exists(); + ->first(); - if ($exists) { - throw new BadRequestHttpException(__('error.duplicate_code')); + if ($existingProduct) { + throw new DuplicateCodeException($data['code'], $existingProduct->id); } } @@ -673,14 +591,14 @@ private function updateMaterial(int $id, array $data): Material // 코드 변경 시 중복 체크 $newCode = $data['material_code'] ?? $data['code'] ?? null; if ($newCode && $newCode !== $material->material_code) { - $exists = Material::query() + $existingMaterial = Material::query() ->where('tenant_id', $tenantId) ->where('material_code', $newCode) ->where('id', '!=', $material->id) - ->exists(); + ->first(); - if ($exists) { - throw new BadRequestHttpException(__('error.duplicate_code')); + if ($existingMaterial) { + throw new DuplicateCodeException($newCode, $existingMaterial->id); } $data['material_code'] = $newCode; } diff --git a/lang/ko/error.php b/lang/ko/error.php index 04cd165..5f3a25d 100644 --- a/lang/ko/error.php +++ b/lang/ko/error.php @@ -118,6 +118,7 @@ 'already_deleted' => '이미 삭제된 품목입니다.', 'in_use_as_bom_component' => '다른 제품의 BOM 구성품으로 사용 중이어서 삭제할 수 없습니다. (사용처: :count건)', 'invalid_item_type' => '유효하지 않은 품목 유형입니다.', + 'duplicate_code' => '중복된 품목 코드입니다.', ], // 잠금 관련