- 품목 상세/수정 페이지 파일 다운로드 기능 개선 - DynamicItemForm 파일 업로드 UI/UX 개선 (시방서, 인정서) - BendingDiagramSection 조립/절곡 부품 전개도 통합 - API proxy route 품목 타입별 라우팅 개선 - ItemListClient 파일 다운로드 유틸리티 적용 - 품목코드 중복 체크 및 다이얼로그 추가 문서화: - DynamicItemForm 훅 분리 계획서 추가 (2161줄 → 900줄 목표) - 백엔드 API 마이그레이션 문서 추가 - 대용량 파일 처리 전략 가이드 추가 - 테넌트 데이터 격리 감사 문서 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
6.3 KiB
6.3 KiB
[FIX-2025-12-16] options vs item_details 중복 저장 버그
문제 현상
- 품목 수정 시
bending_details값이 111 → 1110으로 저장했는데, 다시 조회하면 111로 표시됨 - 입력한 최신값이 아닌 이전에 저장된 값이 표시되는 문제
근본 원인
백엔드 저장 구조
┌─────────────────────────────────────────────────────────────┐
│ 백엔드 저장 구조 │
├─────────────────────────────────────────────────────────────┤
│ │
│ [동적 필드] ─────────────────────▶ options (JSON) │
│ (품목기준관리에서 생성) [{label, value}, ...] │
│ │
│ [고정 필드] ─────────────────────▶ item_details (컬럼) │
│ (하드코딩: 시방서, 인정서, 전개도 등) bending_details 등 │
│ │
└─────────────────────────────────────────────────────────────┘
문제 발생 지점
백엔드 ItemService.php의 getKnownFields() (85-109줄)
private function getKnownFields(): array
{
// 1. 기본 고정 필드 (items 테이블 컬럼)
$baseFields = [
'id', 'tenant_id', 'item_type', 'code', 'name', 'unit', ...
];
// 2. ItemField에서 source_table='items' AND storage_type='column' 인 것만 조회
$columnFields = ItemField::where('source_table', 'items') // ⚠️ items만!
->where('storage_type', 'column')
...
// ❌ item_details 테이블 컬럼 누락!
// ❌ SystemFields 상수 미사용!
}
결과: bending_details 등 item_details 컬럼이 "알려진 필드"로 인식되지 않음
데이터 흐름
저장 시:
요청: { bending_details: 111 }
→ item_details.bending_details = 111 (extractDetailData로 저장)
→ options: [{label: "bending_details", value: "111"}] (동적 필드로 잘못 인식)
수정 시 (값을 1110으로 변경):
요청: { bending_details: 1110 }
→ item_details.bending_details = 1110 (최신값)
→ options: [{label: "bending_details", value: "1110"}] (기존 값 덮어쓰기)
⚠️ 문제: options 병합 시 이전 값이 남아있는 경우가 있음
조회 시 (프론트엔드):
API 응답: {
bending_details: 1110, // ← item_details에서 가져온 최신값
options: [{label: "bending_details", value: "111"}] // ← 첫 저장 시 값!
}
프론트엔드 매핑:
1. formData.bending_details = 1110 (API 응답에서)
2. options 순회: formData.bending_details = "111" ← 덮어쓰기! ❌
해결 방법
A. 프론트엔드 수정 (즉시 적용) ✅
파일: src/app/[locale]/(protected)/items/[id]/edit/page.tsx
변경 내용:
excludeKeys에'details'추가details객체 펼치기 로직 추가 (최신값 매핑)options순회 시item_details필드 제외
// 1. details는 아래에서 펼쳐서 추가
const excludeKeys = [..., 'details'];
// 2. details 객체가 있으면 펼쳐서 추가 (item_details 테이블 필드)
const details = (data as Record<string, unknown>).details;
if (details && typeof details === 'object') {
const detailExcludeKeys = ['id', 'item_id', 'created_at', 'updated_at'];
Object.entries(details).forEach(([key, value]) => {
if (!detailExcludeKeys.includes(key) && value !== null && value !== undefined) {
formData[key] = value;
}
});
}
// 3. options 순회 시 item_details 필드 제외
const detailsFieldsInOptions = [
'files', 'bending_details', 'bending_diagram',
'specification_file', 'certification_file',
];
if (data.options && Array.isArray(data.options)) {
data.options.forEach((opt) => {
if (opt.label && opt.value && !detailsFieldsInOptions.includes(opt.label)) {
formData[opt.label] = opt.value;
}
});
}
B. 백엔드 수정 (근본 해결, 권장)
파일: /app/Services/ItemService.php - getKnownFields()
수정 요청:
// 방안 A: extractDetailData() 필드들을 knownFields에 추가
private function getKnownFields(): array
{
$baseFields = [...];
// item_details 테이블 필드 추가
$detailFields = [
'bending_diagram', 'bending_details',
'specification_file', 'certification_file', ...
];
return array_unique(array_merge($baseFields, $columnFields, $detailFields, $apiFields));
}
// 방안 B: SystemFields 상수 활용
use App\Constants\SystemFields;
private function getKnownFields(): array
{
return array_unique(array_merge(
$baseFields,
$columnFields,
SystemFields::getAllReservedKeysForGroup(SystemFields::GROUP_ITEM_MASTER),
$apiFields
));
}
관련 파일
| 파일 | 역할 |
|---|---|
sam-api/app/Services/ItemService.php |
getKnownFields(), extractDetailData() |
sam-api/app/Constants/SystemFields.php |
시스템 필드 정의 (bending_details 포함) |
sam-api/app/Models/Items/ItemDetail.php |
item_details 테이블 모델 |
sam-next/.../edit/page.tsx |
mapApiResponseToFormData() |
영향 범위
bending_details(전개도 상세)bending_diagram(전개도 이미지)specification_file(시방서)certification_file(인정서)files(첨부파일)- 기타
item_details테이블의 고정 컬럼들
향후 계획
- 전개도 상세 입력 수치를 품목기준관리에서 동적 필드로 등록하는 형태로 변경 예정
- 그때는
options에 저장되는 것이 정상 - 현재는 하드코딩된 고정 필드이므로
item_details에만 저장되어야 함
참고
- 유사 버그:
files필드도 같은 문제가 있었음 (2025-12-15 수정) [GUIDE] radix-ui-select-controlled-mode-bug.md- Radix UI Select 관련 버그