feat: 품목 코드 중복 시 에러 반환 및 중복 ID 제공
- DuplicateCodeException 커스텀 예외 추가 - 등록/수정 시 자동 코드 증가 기능 제거 - 중복 발견 시 duplicate_id, duplicate_code 함께 반환 - resolveUniqueCode(), resolveUniqueMaterialCode() 메서드 제거
This commit is contained in:
51
app/Exceptions/DuplicateCodeException.php
Normal file
51
app/Exceptions/DuplicateCodeException.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* 품목 코드 중복 예외
|
||||
*
|
||||
* 중복된 품목 ID를 함께 반환하기 위한 커스텀 예외
|
||||
*/
|
||||
class DuplicateCodeException extends Exception
|
||||
{
|
||||
protected int $duplicateId;
|
||||
|
||||
protected string $duplicateCode;
|
||||
|
||||
public function __construct(string $code, int $duplicateId, ?string $message = null)
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
'already_deleted' => '이미 삭제된 품목입니다.',
|
||||
'in_use_as_bom_component' => '다른 제품의 BOM 구성품으로 사용 중이어서 삭제할 수 없습니다. (사용처: :count건)',
|
||||
'invalid_item_type' => '유효하지 않은 품목 유형입니다.',
|
||||
'duplicate_code' => '중복된 품목 코드입니다.',
|
||||
],
|
||||
|
||||
// 잠금 관련
|
||||
|
||||
Reference in New Issue
Block a user