feat: field_key 시스템 필드 예약어 검증 추가

- SystemFields 상수 클래스 생성 (app/Constants/)
  - source_table 기반 테이블별 예약어 관리
  - products/materials 테이블 고정 컬럼 정의
  - 공통 시스템 컬럼 포함
- ItemFieldService 수정
  - validateFieldKeyUnique에 시스템 필드 검증 추가
  - source_table 미지정시 그룹 전체 예약어 체크 (안전 모드)
- 에러 메시지 추가 (error.field_key_reserved)
- 작업 문서 추가 (docs/specs/item-master-field-key-validation.md)
This commit is contained in:
2025-12-09 14:06:35 +09:00
parent 004324d65b
commit d16f0410b8
4 changed files with 425 additions and 10 deletions

View File

@@ -2,11 +2,13 @@
namespace App\Services\ItemMaster;
use App\Constants\SystemFields;
use App\Models\ItemMaster\EntityRelationship;
use App\Models\ItemMaster\ItemField;
use App\Services\Service;
use App\Traits\LockCheckTrait;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class ItemFieldService extends Service
@@ -61,14 +63,64 @@ public function storeIndependent(array $data): ItemField
'created_by' => $userId,
]);
// 2. field_key 설정 ({ID}_{field_key} 형식)
// 2. field_key 설정 (시스템 필드 + 중복 검증 후 저장)
if (! empty($data['field_key'])) {
$field->update(['field_key' => $field->id.'_'.$data['field_key']]);
$sourceTable = $data['source_table'] ?? null;
$groupId = $data['group_id'] ?? 1;
$this->validateFieldKeyUnique($data['field_key'], $tenantId, $sourceTable, $groupId);
$field->update(['field_key' => $data['field_key']]);
}
return $field;
}
/**
* field_key 유효성 검증 (시스템 필드 + 중복 체크)
*
* @param string|null $sourceTable 소스 테이블 (products, materials), null이면 그룹 전체 예약어 체크
* @param int $groupId 그룹 ID (1=품목관리), sourceTable이 null일 때 사용
*
* @throws ValidationException
*/
private function validateFieldKeyUnique(
string $fieldKey,
int $tenantId,
?string $sourceTable = null,
int $groupId = 1,
?int $excludeId = null
): void {
// 1. 시스템 필드(예약어) 체크
if ($sourceTable) {
// source_table이 지정된 경우: 해당 테이블의 예약어만 체크
if (SystemFields::isReserved($fieldKey, $sourceTable)) {
throw ValidationException::withMessages([
'field_key' => [__('error.field_key_reserved', ['field_key' => $fieldKey])],
]);
}
} else {
// source_table이 없는 경우: 그룹 내 모든 테이블의 예약어 체크 (안전 모드)
if (SystemFields::isReservedInGroup($fieldKey, $groupId)) {
throw ValidationException::withMessages([
'field_key' => [__('error.field_key_reserved', ['field_key' => $fieldKey])],
]);
}
}
// 2. 기존 필드 중복 체크
$query = ItemField::where('tenant_id', $tenantId)
->where('field_key', $fieldKey);
if ($excludeId) {
$query->where('id', '!=', $excludeId);
}
if ($query->exists()) {
throw ValidationException::withMessages([
'field_key' => [__('validation.unique', ['attribute' => 'field_key'])],
]);
}
}
/**
* 필드 복제
*
@@ -109,11 +161,19 @@ public function clone(int $id): ItemField
'created_by' => $userId,
]);
// 2. field_key 복제 ({새ID}_{원본key} 형식)
// 2. field_key 복제 (원본key_copy 형식, 중복 시 _copy2, _copy3...)
if ($original->field_key) {
// 원본 field_key에서 키 부분 추출 (예: "81_itemNum" → "itemNum")
$originalKey = preg_replace('/^\d+_/', '', $original->field_key);
$cloned->update(['field_key' => $cloned->id.'_'.$originalKey.'_copy']);
$baseKey = $original->field_key.'_copy';
$copyKey = $baseKey;
$suffix = 1;
// 중복되지 않는 키 찾기
while (ItemField::where('tenant_id', $tenantId)->where('field_key', $copyKey)->exists()) {
$suffix++;
$copyKey = $baseKey.$suffix;
}
$cloned->update(['field_key' => $copyKey]);
}
return $cloned;
@@ -183,9 +243,12 @@ public function store(int $sectionId, array $data): ItemField
'created_by' => $userId,
]);
// 2. field_key 설정 ({ID}_{field_key} 형식)
// 2. field_key 설정 (시스템 필드 + 중복 검증 후 저장)
if (! empty($data['field_key'])) {
$field->update(['field_key' => $field->id.'_'.$data['field_key']]);
$sourceTable = $data['source_table'] ?? null;
$groupId = $data['group_id'] ?? 1;
$this->validateFieldKeyUnique($data['field_key'], $tenantId, $sourceTable, $groupId);
$field->update(['field_key' => $data['field_key']]);
}
// 3. 섹션-필드 관계 생성
@@ -237,9 +300,16 @@ public function update(int $id, array $data): ItemField
'updated_by' => $userId,
];
// field_key 변경 시 {ID}_{field_key} 형식 유지
// field_key 변경 시 시스템 필드 + 중복 검증 후 저장
if (array_key_exists('field_key', $data)) {
$updateData['field_key'] = $data['field_key'] ? $field->id.'_'.$data['field_key'] : null;
if ($data['field_key']) {
$sourceTable = $data['source_table'] ?? null;
$groupId = $field->group_id ?? 1;
$this->validateFieldKeyUnique($data['field_key'], $tenantId, $sourceTable, $groupId, $id);
$updateData['field_key'] = $data['field_key'];
} else {
$updateData['field_key'] = null;
}
}
// is_locked 변경 처리