fix: TypeScript 타입 오류 수정 및 설정 페이지 추가

- BOMItem Omit 타입 시그니처 통일 (useTemplateManagement, SectionsTab, ItemMasterContext)
- HeadersInit → Record<string, string> 타입 변경
- Zustand useShallow 마이그레이션 (zustand/react/shallow)
- DataTable, ListPageTemplate 제네릭 타입 제약 추가
- 설정 관리 페이지 추가 (직급, 직책, 휴가정책, 근무일정, 권한)
- HR 관리 페이지 추가 (급여, 휴가)
- 단가관리 페이지 리팩토링

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2025-12-09 18:07:47 +09:00
parent 48dbba0e5f
commit ded0bc2439
98 changed files with 10608 additions and 1204 deletions

View File

@@ -0,0 +1,272 @@
# 전체 페이지 테스트 URL 목록
> 백엔드 메뉴 연동 전 테스트용 직접 접근 URL (Last Updated: 2025-12-08)
---
## 🏠 기본 페이지
| 페이지 | URL | 상태 |
|--------|-----|------|
| 홈 (리다이렉트) | `/ko` | ✅ |
| 로그인 | `/ko/login` | ✅ |
| 회원가입 | `/ko/signup` | ⚠️ 차단됨 |
| 대시보드 | `/ko/dashboard` | ✅ |
```
http://localhost:3000/ko
http://localhost:3000/ko/login
http://localhost:3000/ko/signup
http://localhost:3000/ko/dashboard
```
---
## 👥 인사관리 (HR)
### 메인 페이지
| 페이지 | URL | 상태 |
|--------|-----|------|
| 부서관리 | `/ko/hr/department-management` | ✅ |
| 사원관리 | `/ko/hr/employee-management` | ✅ |
| 근태관리 | `/ko/hr/attendance-management` | ✅ |
| 휴가관리 | `/ko/hr/vacation-management` | ✅ |
| 급여관리 | `/ko/hr/salary-management` | ✅ |
```
http://localhost:3000/ko/hr/department-management
http://localhost:3000/ko/hr/employee-management
http://localhost:3000/ko/hr/attendance-management
http://localhost:3000/ko/hr/vacation-management
http://localhost:3000/ko/hr/salary-management
```
### 사원관리 하위 페이지
| 페이지 | URL |
|--------|-----|
| 사원 등록 | `/ko/hr/employee-management/new` |
| 사원 상세 | `/ko/hr/employee-management/[id]` |
| 사원 수정 | `/ko/hr/employee-management/[id]/edit` |
| CSV 업로드 | `/ko/hr/employee-management/csv-upload` |
```
http://localhost:3000/ko/hr/employee-management/new
http://localhost:3000/ko/hr/employee-management/1
http://localhost:3000/ko/hr/employee-management/1/edit
http://localhost:3000/ko/hr/employee-management/csv-upload
```
---
## 💰 판매관리 (Sales)
### 메인 페이지
| 페이지 | URL | 상태 |
|--------|-----|------|
| 거래처관리 | `/ko/sales/client-management-sales-admin` | ✅ |
| 견적관리 | `/ko/sales/quote-management` | ✅ |
| 단가관리 | `/ko/sales/pricing-management` | ✅ |
```
http://localhost:3000/ko/sales/client-management-sales-admin
http://localhost:3000/ko/sales/quote-management
http://localhost:3000/ko/sales/pricing-management
```
### 거래처관리 하위 페이지
| 페이지 | URL |
|--------|-----|
| 거래처 등록 | `/ko/sales/client-management-sales-admin/new` |
| 거래처 상세 | `/ko/sales/client-management-sales-admin/[id]` |
| 거래처 수정 | `/ko/sales/client-management-sales-admin/[id]/edit` |
```
http://localhost:3000/ko/sales/client-management-sales-admin/new
http://localhost:3000/ko/sales/client-management-sales-admin/1
http://localhost:3000/ko/sales/client-management-sales-admin/1/edit
```
### 견적관리 하위 페이지
| 페이지 | URL |
|--------|-----|
| 견적 등록 | `/ko/sales/quote-management/new` |
| 견적 상세 | `/ko/sales/quote-management/[id]` |
| 견적 수정 | `/ko/sales/quote-management/[id]/edit` |
```
http://localhost:3000/ko/sales/quote-management/new
http://localhost:3000/ko/sales/quote-management/1
http://localhost:3000/ko/sales/quote-management/1/edit
```
### 단가관리 하위 페이지
| 페이지 | URL |
|--------|-----|
| 단가 등록 | `/ko/sales/pricing-management/create` |
| 단가 수정 | `/ko/sales/pricing-management/[id]/edit` |
```
http://localhost:3000/ko/sales/pricing-management/create
http://localhost:3000/ko/sales/pricing-management/1/edit
```
---
## 📦 기준정보관리 (Master Data)
### 품목기준관리
| 페이지 | URL | 상태 |
|--------|-----|------|
| 품목 목록 | `/ko/master-data/item-master-data-management` | ✅ |
```
http://localhost:3000/ko/master-data/item-master-data-management
```
### 품목관리 (Items) - 구버전
| 페이지 | URL |
|--------|-----|
| 품목 목록 | `/ko/items` |
| 품목 등록 | `/ko/items/create` |
| 품목 상세 | `/ko/items/[id]` |
| 품목 수정 | `/ko/items/[id]/edit` |
```
http://localhost:3000/ko/items
http://localhost:3000/ko/items/create
http://localhost:3000/ko/items/1
http://localhost:3000/ko/items/1/edit
```
---
## 🏭 생산관리 (Production)
### 스크린 생산
| 페이지 | URL |
|--------|-----|
| 생산 목록 | `/ko/production/screen-production` |
| 생산 등록 | `/ko/production/screen-production/create` |
| 생산 상세 | `/ko/production/screen-production/[id]` |
| 생산 수정 | `/ko/production/screen-production/[id]/edit` |
```
http://localhost:3000/ko/production/screen-production
http://localhost:3000/ko/production/screen-production/create
http://localhost:3000/ko/production/screen-production/1
http://localhost:3000/ko/production/screen-production/1/edit
```
---
## ⚙️ 설정 (Settings)
| 페이지 | URL | 상태 |
|--------|-----|------|
| 휴가정책 | `/ko/settings/leave-policy` | ✅ |
| 권한관리 | `/ko/settings/permissions` | ✅ |
| 직급관리 | `/ko/settings/ranks` | ✅ |
| 직책관리 | `/ko/settings/titles` | ✅ |
| 근무일정 | `/ko/settings/work-schedule` | ✅ |
```
http://localhost:3000/ko/settings/leave-policy
http://localhost:3000/ko/settings/permissions
http://localhost:3000/ko/settings/ranks
http://localhost:3000/ko/settings/titles
http://localhost:3000/ko/settings/work-schedule
```
---
## 📋 전체 URL 한눈에 보기
### 기본
```
http://localhost:3000/ko/dashboard
http://localhost:3000/ko/login
```
### HR
```
http://localhost:3000/ko/hr/department-management
http://localhost:3000/ko/hr/employee-management
http://localhost:3000/ko/hr/attendance-management
http://localhost:3000/ko/hr/vacation-management
http://localhost:3000/ko/hr/salary-management
```
### Sales
```
http://localhost:3000/ko/sales/client-management-sales-admin
http://localhost:3000/ko/sales/quote-management
http://localhost:3000/ko/sales/pricing-management
```
### Master Data
```
http://localhost:3000/ko/master-data/item-master-data-management
http://localhost:3000/ko/items
```
### Production
```
http://localhost:3000/ko/production/screen-production
```
### Settings
```
http://localhost:3000/ko/settings/leave-policy
http://localhost:3000/ko/settings/permissions
http://localhost:3000/ko/settings/ranks
http://localhost:3000/ko/settings/titles
http://localhost:3000/ko/settings/work-schedule
```
---
## 백엔드 메뉴 연동 시 path 참고
```javascript
// HR
'/hr/department-management'
'/hr/employee-management'
'/hr/attendance-management'
'/hr/vacation-management'
'/hr/salary-management'
// Sales
'/sales/client-management-sales-admin'
'/sales/quote-management'
'/sales/pricing-management'
// Master Data
'/master-data/item-master-data-management'
'/items'
// Production
'/production/screen-production'
// Settings
'/settings/leave-policy'
'/settings/permissions'
'/settings/ranks'
'/settings/titles'
'/settings/work-schedule'
```
---
## 작성일
- 최초 작성: 2025-12-06
- 최종 업데이트: 2025-12-08 (전체 페이지 통합)

View File

@@ -1,6 +1,14 @@
# claudedocs 문서 맵
> 프로젝트 기술 문서 인덱스 (Last Updated: 2025-12-05)
> 프로젝트 기술 문서 인덱스 (Last Updated: 2025-12-09)
## ⭐ 빠른 참조
| 문서 | 설명 |
|------|------|
| **[`[REF] all-pages-test-urls.md`](./[REF]%20all-pages-test-urls.md)** | 🔗 **전체 페이지 테스트 URL 목록** - 모든 페이지 직접 접근 주소 |
---
## 폴더 구조
@@ -38,12 +46,13 @@ claudedocs/
---
## 👥 hr/ - 인사관리 (부서/사원)
## 👥 hr/ - 인사관리 (부서/사원/근태/휴가)
| 파일 | 설명 |
|------|------|
| `[IMPL-2025-12-05] department-management-checklist.md` | ✅ **완료** - 부서관리 구현 체크리스트 (무제한 트리구조) |
| `[IMPL-2025-12-05] employee-management-checklist.md` | 🔄 **진행중** - 사원관리 구현 체크리스트 |
| `[IMPL-2025-12-05] employee-management-checklist.md` | **완료** - 사원관리 구현 체크리스트 |
| `[IMPL-2025-12-06] vacation-management-checklist.md` | ✅ **완료** - 휴가관리 구현 체크리스트 |
---
@@ -51,6 +60,8 @@ claudedocs/
| 파일 | 설명 |
|------|------|
| `[NEXT-2025-12-09] item-crud-session-context.md` | ⭐ **세션 체크포인트** - 백엔드 field_key 통일 대기, 다음 작업 정리 |
| `[PLAN-2025-12-08] dynamic-form-separation-plan.md` | 📋 DynamicItemForm 품목별 분리 계획 (Phase 2: 컴포넌트 구조 설계) |
| `[REF] item-code-hardcoding.md` | ⭐ **핵심** - 품목관리 하드코딩 내역 종합 (품목유형/코드자동생성/전개도/BOM) |
| `[IMPL-2025-12-02] item-code-auto-generation.md` | 품목코드 자동생성 구현 상세 |
| `[PLAN-2025-12-01] service-layer-refactoring.md` | ✅ **완료** - 서비스 레이어 리팩토링 계획 (도메인 로직 중앙화) |
@@ -76,11 +87,13 @@ claudedocs/
| 파일 | 설명 |
|------|------|
| `[API-2025-12-08] pricing-api-enhancement-request.md` | 🔴 **NEW** - 단가관리 백엔드 API 개선 요청서 (스키마 변경, 신규 엔드포인트) |
| `[IMPL-2025-12-05] pricing-management-migration.md` | 🔄 **진행중** - 단가관리 마이그레이션 계획 (7 Phase, 체크리스트, 원가/마진 계산 로직) |
| `[API-2025-12-04] quote-api-request.md` | ⭐ **NEW** - 견적관리 API 요청서 (데이터 모델, 엔드포인트, 수식 계산) |
| `[PLAN-2025-12-04] quote-management-implementation.md` | 📋 **NEW** - 견적관리 작업계획서 (6 Phase, 체크리스트) |
| `[NEXT-2025-12-09] client-session-context.md` | ⭐ **세션 체크포인트** - 다음 세션 이어하기용 (완료/숨긴 섹션/다음 작업) |
| `[IMPL-2025-12-04] client-management-api-integration.md` | ✅ **완료** - 거래처관리 API 연동 체크리스트 (CRUD, 그룹 훅) |
| `[API-2025-12-04] client-api-analysis.md` | 거래처 API 분석 (sam-api 연동 현황, 필드 매핑) |
| `[API-2025-12-04] client-api-analysis.md` | **완료** - 거래처 API 분석 (2차 필드 완료, is_active Boolean) |
| `[PLAN-2025-12-02] sales-pages-migration.md` | 📋 견적관리/거래처관리 마이그레이션 계획 |
---

View File

@@ -0,0 +1,196 @@
# 휴가관리 페이지 구현 체크리스트
> **시작일**: 2025-12-06
> **상태**: ✅ 완료
> **참조 페이지**: 사원관리, 근태관리 (공통 UI 패턴 사용)
---
## 스크린샷 분석 요약 (3탭 구조)
### 메인 탭 구조
1. **휴가 사용현황** (usage) - 사원별 휴가 잔여 현황
2. **휴가 부여현황** (grant) - 휴가 지급 이력
3. **휴가 신청현황** (request) - 휴가 신청 및 승인 현황
### 탭 1: 휴가 사용현황
- **통계 카드 4개**: 연차, 월차, 포상휴가, 기타휴가 (각각 N명)
- **서브탭**: 전체 | 연차 | 월차 | 포상휴가 | 기타
- **테이블 컬럼**: 체크박스, 부서, 직책, 이름, 직급, 연차(총/사용/잔여), 월차, 포상휴가, 기타휴가, 조정 버튼
- **액션 버튼**: 엑셀 다운로드, 휴가 등록, 휴가 종류 설정
### 탭 2: 휴가 부여현황
- **통계 카드 4개**: 연차, 월차, 포상휴가, 기타 (지급 건수)
- **서브탭**: 전체 | 연차 | 월차 | 포상휴가 | 기타
- **테이블 컬럼**: 체크박스, 부서, 직책, 이름, 직급, 휴가종류, 지급일수, 지급일자, 비고
- **액션 버튼**: 엑셀 다운로드, 휴가 등록
### 탭 3: 휴가 신청현황
- **통계 카드 3개**: 대기, 승인, 반려 (건수)
- **서브탭**: 전체 | 대기 | 승인 | 반려
- **테이블 컬럼**: 체크박스, 부서, 직책, 이름, 직급, 휴가종류, 신청일수, 시작일, 종료일, 상태
- **액션 버튼**: 엑셀 다운로드
### 공통 다이얼로그
1. **휴가 등록 다이얼로그**: 사원 선택, 휴가 종류, 지급 일수, 비고
2. **휴가 종류 설정 다이얼로그**: 휴가종류명, 사용여부, 설명, 삭제
3. **휴가 조정 다이얼로그**: 사원 정보, 휴가종류별 총/사용/잔여일수, +/- 조정
---
## Phase 1: 기본 구조 세팅
- [x] 1.1 페이지 파일 생성 (`src/app/[locale]/(protected)/hr/vacation-management/page.tsx`)
- [x] 1.2 컴포넌트 폴더 구조 생성 (`src/components/hr/VacationManagement/`)
- [x] 1.3 타입 정의 파일 생성 (`types.ts`)
- [x] 1.4 메인 컴포넌트 생성 (`index.tsx`)
---
## Phase 2: 타입 및 상수 정의 (3탭 구조)
- [x] 2.1 `MainTabType` 타입 정의 (usage | grant | request)
- [x] 2.2 `VacationUsageRecord` 인터페이스 정의 (탭1: 사용현황)
- [x] 2.3 `VacationGrantRecord` 인터페이스 정의 (탭2: 부여현황)
- [x] 2.4 `VacationRequestRecord` 인터페이스 정의 (탭3: 신청현황)
- [x] 2.5 `VacationType` 타입 정의 (연차, 월차, 포상휴가, 기타)
- [x] 2.6 `RequestStatus` 타입 정의 (pending, approved, rejected)
- [x] 2.7 `VacationFormData`, `VacationAdjustment`, `VacationTypeConfig` 인터페이스 정의
- [x] 2.8 상수 정의 (MAIN_TAB_LABELS, VACATION_TYPE_LABELS, REQUEST_STATUS_LABELS 등)
---
## Phase 3: 메인 컴포넌트 구현 (3탭 + IntegratedListTemplateV2)
- [x] 3.1 메인 탭 상태 관리 (mainTab: usage | grant | request)
- [x] 3.2 탭별 Mock 데이터 생성 (usageData, grantData, requestData)
- [x] 3.3 탭별 통계 카드 구현
- 사용현황: 연차/월차/포상휴가/기타 사용자 수
- 부여현황: 연차/월차/포상휴가/기타 지급 건수
- 신청현황: 대기/승인/반려 건수
- [x] 3.4 탭별 서브탭 구현
- 사용현황: 전체 | 연차 | 월차 | 포상휴가 | 기타
- 부여현황: 전체 | 연차 | 월차 | 포상휴가 | 기타
- 신청현황: 전체 | 대기 | 승인 | 반려
- [x] 3.5 탭별 테이블 컬럼 정의 (usageColumns, grantColumns, requestColumns)
- [x] 3.6 탭별 행 렌더링 (renderUsageRow, renderGrantRow, renderRequestRow)
- [x] 3.7 모바일 카드 렌더링 (탭별)
- [x] 3.8 탭별 헤더 액션 버튼 구현
- [x] 3.9 검색 및 정렬 기능 구현
---
## Phase 4: 휴가 등록 다이얼로그
- [x] 4.1 `VacationRegisterDialog.tsx` 파일 생성
- [x] 4.2 사원 선택 드롭다운 구현
- [x] 4.3 휴가 종류 선택 드롭다운 구현
- [x] 4.4 지급 일수 입력 필드 구현
- [x] 4.5 비고 텍스트 영역 구현
- [x] 4.6 폼 유효성 검사 구현
- [x] 4.7 등록/취소 핸들러 구현
---
## Phase 5: 휴가 종류 설정 다이얼로그
- [x] 5.1 `VacationTypeSettingsDialog.tsx` 파일 생성
- [x] 5.2 휴가 종류 테이블 구현
- [x] 5.3 사용여부 토글 스위치 구현
- [x] 5.4 휴가종류 추가 기능 구현
- [x] 5.5 휴가종류 삭제 기능 구현
- [x] 5.6 저장/취소 핸들러 구현
---
## Phase 6: 휴가 조정 다이얼로그
- [x] 6.1 `VacationAdjustDialog.tsx` 파일 생성
- [x] 6.2 사원 정보 헤더 표시
- [x] 6.3 휴가 종류별 조정 테이블 구현 (총일수/사용일수/잔여일수)
- [x] 6.4 +/- 조정 버튼 구현
- [x] 6.5 저장/취소 핸들러 구현
- [x] 6.6 타입 수정: `VacationRecord``VacationUsageRecord`
---
## Phase 7: 연동 및 마무리
- [x] 7.1 사이드바 메뉴 - 백엔드에서 관리 (별도 연동 예정)
- [x] 7.2 테스트 URL 문서 작성 (`[REF] hr-pages-test-urls.md`)
---
## 생성된 파일 목록
| 파일 | 설명 |
|------|------|
| `src/app/[locale]/(protected)/hr/vacation-management/page.tsx` | 휴가관리 페이지 |
| `src/components/hr/VacationManagement/types.ts` | 타입 및 상수 정의 (3탭 구조) |
| `src/components/hr/VacationManagement/index.tsx` | 메인 컴포넌트 (3탭 구조) |
| `src/components/hr/VacationManagement/VacationRegisterDialog.tsx` | 휴가 등록 다이얼로그 |
| `src/components/hr/VacationManagement/VacationTypeSettingsDialog.tsx` | 휴가 종류 설정 다이얼로그 |
| `src/components/hr/VacationManagement/VacationAdjustDialog.tsx` | 휴가 조정 다이얼로그 |
---
## 타입 구조 정리
```typescript
// 메인 탭 타입
type MainTabType = 'usage' | 'grant' | 'request';
// 휴가 종류
type VacationType = 'annual' | 'monthly' | 'reward' | 'other';
// 신청 상태
type RequestStatus = 'pending' | 'approved' | 'rejected';
// 탭1: 휴가 사용현황
interface VacationUsageRecord {
id, employeeId, employeeName, department, position, rank,
annual: VacationInfo, // { total, used, remaining }
monthly: VacationInfo,
reward: VacationInfo,
other: VacationInfo,
createdAt, updatedAt
}
// 탭2: 휴가 부여현황
interface VacationGrantRecord {
id, employeeId, employeeName, department, position, rank,
vacationType, grantDays, grantDate, memo?,
createdAt, updatedAt
}
// 탭3: 휴가 신청현황
interface VacationRequestRecord {
id, employeeId, employeeName, department, position, rank,
vacationType, requestDays, startDate, endDate, status, approver?,
createdAt, updatedAt
}
```
---
## 참조 파일
| 파일 | 용도 |
|------|------|
| `src/components/hr/AttendanceManagement/index.tsx` | 공통 UI 패턴 참조 |
| `src/components/hr/EmployeeManagement/index.tsx` | 공통 UI 패턴 참조 |
| `src/components/templates/IntegratedListTemplateV2.tsx` | 리스트 템플릿 |
| `src/components/organisms/ListMobileCard.tsx` | 모바일 카드 |
---
## 변경 로그
| 날짜 | 내용 |
|------|------|
| 2025-12-06 | 체크리스트 생성, 스크린샷 분석 완료 |
| 2025-12-06 | Phase 1~7 구현 완료 (단일 탭 구조) |
| 2025-12-06 | **구조 변경**: 3탭 구조로 전면 재구현 (사용현황/부여현황/신청현황) |
| 2025-12-06 | types.ts 재작성: 3개 레코드 타입 분리 |
| 2025-12-06 | index.tsx 재작성: 메인 탭 + 탭별 서브탭/통계/테이블 구조 |
| 2025-12-06 | VacationAdjustDialog.tsx 타입 수정: VacationUsageRecord 사용 |

View File

@@ -0,0 +1,451 @@
# 품목관리 데이터 누락 조사 체크리스트
품목기준관리 데이터 기반 품목관리 등록/수정 시 데이터 매핑 분석
## 조사 방법
- **프론트엔드 → 백엔드**: DynamicItemForm submit 데이터 분석
- **백엔드 → 프론트엔드**: API 응답 → Edit 페이지 데이터 매핑 분석
- **DB 필드**: Material/Product 모델 fillable 필드 확인
---
## 1. 소모품 (SM - Supplies Material)
### 사용 모델: `Material`
### Material 모델 fillable 필드
| 필드명 | 타입 | 필수 | 설명 |
|--------|------|------|------|
| tenant_id | int | Y | 테넌트 ID |
| category_id | int | N | 분류 ID |
| name | string | Y | 품목명 |
| item_name | string | N | 품목명 (레거시) |
| specification | string | N | 규격 |
| material_code | string | Y | 품목코드 |
| material_type | string | Y | SM/RM/CS |
| unit | string | Y | 단위 |
| is_inspection | string | N | 검사 여부 |
| search_tag | string | N | 검색 태그 |
| remarks | string | N | 비고 |
| attributes | json | N | 동적 속성 |
| options | json | N | 옵션 배열 |
| created_by | int | N | 생성자 |
| updated_by | int | N | 수정자 |
| is_active | bool | N | 활성 상태 |
### 등록 시 데이터 매핑 체크리스트
#### 프론트엔드 → 백엔드
| 프론트엔드 필드 | 백엔드 필드 | 변환 함수 | 상태 | 비고 |
|----------------|------------|----------|------|------|
| - [ ] item_name / 품목명 | name | fieldKeyToBackendKey | | |
| - [ ] specification / 규격 | specification | standard_* → options + specification | | convertStandardFieldsToOptions() |
| - [ ] unit / 단위 | unit | fieldKeyToBackendKey | | |
| - [ ] note / 비고 | remarks | transformMaterialDataForSave | | note → remarks |
| - [ ] is_active / 상태 | is_active | boolean 변환 | | |
| - [ ] code (자동생성) | material_code | transformMaterialDataForSave | | name-specification |
| - [ ] product_type | material_type | transformMaterialDataForSave | | |
| - [ ] standard_* | options | convertStandardFieldsToOptions | | 배열로 변환 |
#### 백엔드 → 프론트엔드 (수정 시)
| 백엔드 필드 | 프론트엔드 필드 | 변환 위치 | 상태 | 비고 |
|------------|----------------|----------|------|------|
| - [ ] name | item_name | mapApiResponseToFormData | | |
| - [ ] specification | specification | mapApiResponseToFormData | | |
| - [ ] unit | unit | mapApiResponseToFormData | | |
| - [ ] remarks | note | mapApiResponseToFormData | | remarks → note |
| - [ ] is_active | is_active | mapApiResponseToFormData | | |
| - [ ] material_code | (참조만) | - | | 품목코드는 수정 불가 |
| - [ ] material_type | (참조만) | - | | 품목유형은 수정 불가 |
| - [ ] options | standard_* | convertOptionsToStandardFields | | 배열 → 개별 필드 |
### API 엔드포인트
- **등록**: POST `/api/v1/items` → ItemsService.createMaterial()
- **조회**: GET `/api/v1/items/{id}?item_type=MATERIAL`
- **수정**: PATCH `/api/v1/products/materials/{id}`
### 누락 가능성 분석
- [ ] **options 필드 로드 확인**: Edit 시 options 배열이 standard_* 필드로 정상 변환되는지
- [ ] **specification 생성 로직**: 등록 시 standard_* 값들이 specification으로 조합되는지
- [ ] **material_code 자동생성**: name-specification 형식으로 생성되는지
- [ ] **is_inspection 필드**: 프론트엔드에 검사여부 필드가 있는지
---
## 2. 원자재 (RM - Raw Material)
### 사용 모델: `Material` (SM과 동일)
### 등록/수정 데이터 매핑 (SM과 동일)
- SM과 동일한 Material 모델 사용
- material_type = 'RM'
### 원자재 특화 필드 체크리스트
| 프론트엔드 필드 | 백엔드 필드 | 상태 | 비고 |
|----------------|------------|------|------|
| - [ ] 분류 선택 (드롭다운) | category_id | | 원자재 카테고리 |
| - [ ] 규격 옵션들 (standard_*) | options | | 원자재별 규격 옵션 |
### 누락 가능성 분석
- [ ] **category_id 매핑**: 분류 선택이 제대로 저장되는지
- [ ] **원자재별 옵션 구조**: 품목기준관리에서 정의한 standard_1~N 필드들
---
## 3. 부자재 (CS - Consumable Supplies)
### 사용 모델: `Material` (SM, RM과 동일)
### 등록/수정 데이터 매핑 (SM과 동일)
- material_type = 'CS'
### 부자재 특화 필드 체크리스트
| 프론트엔드 필드 | 백엔드 필드 | 상태 | 비고 |
|----------------|------------|------|------|
| - [ ] 규격 옵션들 (standard_*) | options | | 부자재별 규격 옵션 |
### 누락 가능성 분석
- [ ] SM/RM과 동일한 구조 사용 확인
---
## 4. 조립부품 (PT - Assembly)
### 사용 모델: `Product`
### Product 모델 fillable 필드
| 필드명 | 타입 | 필수 | 설명 |
|--------|------|------|------|
| tenant_id | int | Y | 테넌트 ID |
| code | string | Y | 품목코드 |
| name | string | Y | 품목명 |
| unit | string | Y | 단위 |
| category_id | int | N | 분류 ID |
| product_type | string | Y | FG/PT |
| attributes | json | N | 동적 속성 |
| description | string | N | 설명 |
| is_sellable | bool | N | 판매가능 |
| is_purchasable | bool | N | 구매가능 |
| is_producible | bool | N | 생산가능 |
| safety_stock | int | N | 안전재고 |
| lead_time | int | N | 리드타임 |
| is_variable_size | bool | N | 가변사이즈 |
| product_category | string | N | 제품분류 |
| **part_type** | string | N | 부품유형 (ASSEMBLY/BENDING/PURCHASED) |
| bending_diagram | string | N | 전개도 경로 |
| bending_details | json | N | 절곡 상세 |
| specification_file | string | N | 시방서 경로 |
| certification_file | string | N | 인정서 경로 |
| is_active | bool | N | 활성 상태 |
### 조립부품 특화 필드 체크리스트
#### 프론트엔드 → 백엔드
| 프론트엔드 필드 | 백엔드 필드 | 변환 | 상태 | 비고 |
|----------------|------------|------|------|------|
| - [ ] 품목명 (드롭다운 선택) | name | autoAssemblyItemName | | "품목명 가로x세로" 형식 |
| - [ ] 측면규격(가로) | (attributes?) | | | side_spec_width |
| - [ ] 측면규격(세로) | (attributes?) | | | side_spec_height |
| - [ ] 길이 | (attributes?) | | | assembly_length |
| - [ ] 규격 (자동생성) | spec/description | autoAssemblySpec | | "가로x세로x길이" 형식 |
| - [ ] 품목코드 (자동생성) | code | autoGeneratedItemCode | | 영문약어-순번 |
| - [ ] 단위 | unit | | | |
| - [ ] 비고 | note/description | | | |
| - [ ] 품목상태 | is_active | | | |
| - [ ] 전개도 이미지 | bending_diagram | uploadItemFile() | | 파일 업로드 |
| - [ ] BOM 구성 | bom[] | | | 자재명세서 |
#### 백엔드 → 프론트엔드 (수정 시)
| 백엔드 필드 | 프론트엔드 필드 | 상태 | 비고 |
|------------|----------------|------|------|
| - [ ] name | item_name | | |
| - [ ] code | (참조만) | | 품목코드 |
| - [ ] part_type | part_type | | ASSEMBLY |
| - [ ] side_spec_width | 측면규격(가로) | | attributes에서? |
| - [ ] side_spec_height | 측면규격(세로) | | attributes에서? |
| - [ ] assembly_length | 길이 | | attributes에서? |
| - [ ] is_active | is_active | | |
| - [ ] bending_diagram | bending_diagram | | 기존 전개도 URL |
### 누락 가능성 분석
- [ ] **측면규격 가로/세로 필드**: 백엔드에서 별도 컬럼으로 저장되는지 vs attributes JSON
- [ ] **길이 필드**: assembly_length 저장 여부
- [ ] **전개도 이미지 로드**: Edit 시 기존 이미지 표시
- [ ] **BOM 데이터 로드**: component_lines 관계 로드 확인
---
## 5. 절곡부품 (PT - Bending)
### 사용 모델: `Product` (조립부품과 동일)
### 절곡부품 특화 필드 체크리스트
#### 프론트엔드 → 백엔드
| 프론트엔드 필드 | 백엔드 필드 | 변환 | 상태 | 비고 |
|----------------|------------|------|------|------|
| - [ ] 품목명 (드롭다운 선택) | name | | | bendingFieldKeys.itemName |
| - [ ] 재질 | (attributes?) | | | bendingFieldKeys.material |
| - [ ] 종류 | (attributes?) | | | bendingFieldKeys.category |
| - [ ] 폭 합계 | width_sum | | | bendingFieldKeys.widthSum |
| - [ ] 모양&길이 | (attributes?) | | | bendingFieldKeys.shapeLength |
| - [ ] 품목코드 (자동생성) | code | autoBendingItemCode | | "품목명+종류+모양길이" |
| - [ ] 단위 | unit | | | |
| - [ ] 비고 | note | | | |
| - [ ] 전개도 이미지 | bending_diagram | uploadItemFile() | | |
| - [ ] 절곡 상세 | bending_details | | | [{angle, length, type}] |
#### 백엔드 → 프론트엔드 (수정 시)
| 백엔드 필드 | 프론트엔드 필드 | 상태 | 비고 |
|------------|----------------|------|------|
| - [ ] name | item_name | | |
| - [ ] part_type | part_type | | BENDING |
| - [ ] bending_diagram | bending_diagram | | 전개도 URL |
| - [ ] bending_details | bendingDetails | | 절곡 상세 배열 |
| - [ ] width_sum | widthSum | | |
| - [ ] is_active | is_active | | |
### 누락 가능성 분석
- [ ] **재질/종류/모양길이 필드**: 저장 위치 (별도 컬럼 vs attributes)
- [ ] **절곡 상세 데이터**: bending_details JSON 저장 및 로드
- [ ] **전개도 파일 업로드**: 파일 경로 저장 확인
---
## 6. 구매부품 (PT - Purchased)
### 사용 모델: `Product` (조립/절곡부품과 동일)
### 구매부품 특화 필드 체크리스트
#### 프론트엔드 → 백엔드
| 프론트엔드 필드 | 백엔드 필드 | 변환 | 상태 | 비고 |
|----------------|------------|------|------|------|
| - [ ] 품목명 (전동개폐기 등) | name | | | purchasedFieldKeys.itemName |
| - [ ] 용량 | (attributes?) | | | purchasedFieldKeys.capacity |
| - [ ] 전원 | (attributes?) | | | purchasedFieldKeys.power |
| - [ ] 품목코드 (자동생성) | code | autoPurchasedItemCode | | "품목명+용량+전원" |
| - [ ] 단위 | unit | | | |
| - [ ] 비고 | note | | | |
#### 백엔드 → 프론트엔드 (수정 시)
| 백엔드 필드 | 프론트엔드 필드 | 상태 | 비고 |
|------------|----------------|------|------|
| - [ ] name | item_name | | |
| - [ ] part_type | part_type | | PURCHASED |
| - [ ] capacity | 용량 | | attributes에서? |
| - [ ] power | 전원 | | attributes에서? |
| - [ ] is_active | is_active | | |
### 누락 가능성 분석
- [ ] **용량/전원 필드**: 저장 위치 (별도 컬럼 vs attributes)
- [ ] **품목코드 자동생성**: "전동개폐기150KG380V" 형식 확인
---
## 7. 제품 (FG - Finished Goods)
### 사용 모델: `Product`
### 제품 특화 필드 체크리스트
#### 프론트엔드 → 백엔드
| 프론트엔드 필드 | 백엔드 필드 | 변환 | 상태 | 비고 |
|----------------|------------|------|------|------|
| - [ ] 품목명 | name | | | productName 필드 |
| - [ ] 품목코드 | code | = name | | 품목명이 품목코드 |
| - [ ] 제품분류 | product_category | | | |
| - [ ] 로트약어 | lot_abbreviation | | | |
| - [ ] 인정번호 | certification_number | | | |
| - [ ] 인정유효기간 시작 | certification_start_date | | | |
| - [ ] 인정유효기간 종료 | certification_end_date | | | |
| - [ ] 시방서 파일 | specification_file | uploadItemFile() | | |
| - [ ] 인정서 파일 | certification_file | uploadItemFile() | | |
| - [ ] 단위 | unit | 기본값 'EA' | | |
| - [ ] BOM 구성 | bom[] | | | |
| - [ ] 품목상태 | is_active | | | |
#### 백엔드 → 프론트엔드 (수정 시)
| 백엔드 필드 | 프론트엔드 필드 | 상태 | 비고 |
|------------|----------------|------|------|
| - [ ] name | item_name | | |
| - [ ] code | (참조만) | | |
| - [ ] product_category | product_category | | |
| - [ ] lot_abbreviation | lot_abbreviation | | |
| - [ ] certification_number | certification_number | | |
| - [ ] certification_start_date | certification_start_date | | |
| - [ ] certification_end_date | certification_end_date | | |
| - [ ] specification_file | specification_file | | 기존 시방서 URL |
| - [ ] specification_file_name | specification_file_name | | 파일명 |
| - [ ] certification_file | certification_file | | 기존 인정서 URL |
| - [ ] certification_file_name | certification_file_name | | 파일명 |
| - [ ] is_active | is_active | | |
### 누락 가능성 분석
- [ ] **인정서 관련 필드**: certification_* 필드들 저장 확인
- [ ] **시방서/인정서 파일**: Edit 시 기존 파일 표시 및 다운로드
- [ ] **BOM 데이터 로드**: 제품의 component_lines 관계 로드
---
## 공통 이슈 체크리스트
### 1. 필드명 매핑 이슈
- [ ] `note` vs `remarks`: Material은 remarks, Product는 description/note
- [ ] `is_active` boolean 변환: "활성"/"비활성" vs true/false
- [ ] field_key 형식: "{id}_{fieldName}" → 백엔드 필드명 변환
### 2. 동적 필드 저장 방식
- [ ] **attributes JSON**: 품목기준관리에서 정의한 추가 필드들이 attributes에 저장되는지
- [ ] **options JSON**: Material의 standard_* 필드들이 options 배열로 저장되는지
### 3. 파일 업로드
- [ ] 품목 저장 후 파일 업로드 순서
- [ ] Edit 시 기존 파일 표시
- [ ] 파일 삭제 기능
### 4. API 엔드포인트 일관성
- Material(SM, RM, CS):
- 등록: POST `/api/v1/items`
- 조회: GET `/api/v1/items/{id}?item_type=MATERIAL`
- 수정: PATCH `/api/v1/products/materials/{id}` ⚠️ 경로 불일치
- Product(FG, PT):
- 등록: POST `/api/v1/items`
- 조회: GET `/api/v1/items/code/{code}?include_bom=true`
- 수정: PUT `/api/v1/items/{id}`
---
## 조사 진행 상황
### 소모품 (SM) - 진행중
- [x] 모델 구조 분석
- [x] API 엔드포인트 확인
- [ ] 등록 데이터 흐름 테스트
- [ ] 수정 데이터 흐름 테스트
- [ ] 누락 필드 식별
### 원자재 (RM) - 대기
- [ ] 등록/수정 테스트
- [ ] SM과 차이점 확인
### 부자재 (CS) - 대기
- [ ] 등록/수정 테스트
- [ ] SM과 차이점 확인
### 조립부품 (PT-Assembly) - 대기
- [ ] 특화 필드 저장 위치 확인
- [ ] 등록/수정 테스트
### 절곡부품 (PT-Bending) - 대기
- [ ] 특화 필드 저장 위치 확인
- [ ] 전개도 업로드 테스트
### 구매부품 (PT-Purchased) - 대기
- [ ] 특화 필드 저장 위치 확인
- [ ] 등록/수정 테스트
### 제품 (FG) - 대기
- [ ] 인정서 관련 필드 확인
- [ ] 파일 업로드 테스트
---
## 발견된 이슈
### 이슈 #1: Material is_active 필드 누락
- **품목유형**: SM, RM, CS (Material 타입 전체)
- **현상**: 등록/수정 시 품목 상태(is_active) 필드가 백엔드로 전달되지 않음
- **원인**:
1. `ItemsService.createMaterial()`에서 is_active 설정 O
2. `MaterialService.setMaterial()`에서는 is_active 필드가 validation rules에 없음
3. 프론트엔드 `transformMaterialDataForSave()`에서 is_active를 별도 처리하지 않음
- **해결방안**:
- 백엔드: `MaterialService` validation rules에 `'is_active' => 'nullable|boolean'` 추가
- 또는 프론트엔드에서 `/api/v1/items` 엔드포인트 사용 (이미 is_active 지원)
- **우선순위**: 🔴 높음
### 이슈 #2: Material 수정 API 경로 불일치
- **품목유형**: SM, RM, CS
- **현상**: 등록은 `/api/v1/items`, 수정은 `/api/v1/products/materials/{id}` 사용
- **원인**: 두 개의 다른 서비스 (`ItemsService` vs `MaterialService`)가 Material 처리
- **영향**:
- `ItemsService.createMaterial()`: is_active, material_type 필드 처리 O
- `MaterialService.updateMaterial()`: is_active 필드 validation 없음, material_type 변경 불가
- **해결방안**:
- 통합: 수정도 `/api/v1/items/{id}` 사용하도록 프론트엔드 수정
- 또는 백엔드: `MaterialService.updateMaterial()`에 is_active 필드 추가
- **우선순위**: 🟡 중간
### 이슈 #3: PT(부품) 특화 필드 저장 위치 불명확
- **품목유형**: PT (조립/절곡/구매 부품)
- **현상**: 측면규격, 용량, 전원 등 부품별 특화 필드가 어디에 저장되는지 불명확
- **원인**:
- Product 모델에 `side_spec_width`, `side_spec_height`, `assembly_length` 등 컬럼 없음
- `attributes` JSON에 저장되어야 하나, 프론트엔드에서 attributes 변환 로직 부재
- **영향**: 부품 수정 시 특화 필드 값이 유실될 수 있음
- **해결방안**:
1. 백엔드: Product 모델에 부품 특화 필드 컬럼 추가 (마이그레이션)
2. 또는: 프론트엔드에서 특화 필드를 `attributes` JSON으로 변환하여 저장
- **우선순위**: 🔴 높음
### 이슈 #4: BOM 데이터 저장 로직 누락
- **품목유형**: FG, PT (조립부품)
- **현상**: 프론트엔드에서 BOM 라인을 폼에 입력하지만 저장 시 무시됨
- **원인**:
- `ItemsService.createProduct()`에서 `bom` 배열 처리 로직 없음
- `ProductComponent` 모델에 데이터 생성하는 코드 부재
- **영향**: BOM 구성품 정보가 저장되지 않음
- **해결방안**:
- 백엔드: `ItemsService.createProduct()``ProductComponent` 레코드 생성 로직 추가
- 별도 API: `/api/v1/items/{id}/bom` 엔드포인트로 BOM 저장
- **우선순위**: 🔴 높음
### 이슈 #5: 품목기준관리 동적 필드와 백엔드 필드 불일치
- **품목유형**: 전체
- **현상**: 품목기준관리에서 정의한 필드가 백엔드 모델에 대응 컬럼이 없음
- **예시**:
- 품목기준관리: `107_재질`, `108_종류`, `109_폭합계`
- 백엔드 Product 모델: 해당 컬럼 없음
- **원인**: 동적 필드 구조 vs 고정 스키마 불일치
- **해결방안**:
1. 동적 필드는 `attributes` JSON에 저장
2. 프론트엔드: 품목기준관리 field_key를 `attributes[field_key]` 형태로 변환
3. 백엔드: `attributes` 필드에서 동적 값 저장/조회 지원
- **우선순위**: 🔴 높음
### 이슈 #6: Material options 필드 Edit 시 로드 확인 필요
- **품목유형**: SM, RM, CS
- **현상**: 등록 시 standard_* → options 변환은 되지만, Edit 시 역변환 확인 필요
- **확인사항**:
- `mapApiResponseToFormData()`에서 options → standard_* 변환 로직 있음 (line 123-129)
- 하지만 Material API 응답에서 options가 제대로 포함되는지 확인 필요
- **해결방안**: 실제 데이터로 테스트 필요
- **우선순위**: 🟢 낮음
---
## 권장 조치사항
### 즉시 조치 (우선순위 높음)
1. **이슈 #1 해결**: `MaterialService.setMaterial()``updateMaterial()``is_active` validation 추가
2. **이슈 #3 해결**: PT 부품 특화 필드 저장 방식 결정
- Option A: DB 마이그레이션으로 컬럼 추가
- Option B: attributes JSON 활용
3. **이슈 #4 해결**: BOM 저장 API 구현
### 중기 조치 (우선순위 중간)
4. **이슈 #2 해결**: Material 등록/수정 API 일관성 확보
5. **이슈 #5 해결**: 동적 필드 ↔ attributes 매핑 체계 구축
### 테스트 필요
6. **이슈 #6 확인**: 실제 데이터로 Material Edit 플로우 테스트
---
## 다음 단계
- [ ] 각 이슈별 수정 작업 진행
- [ ] 실제 데이터로 등록/수정 흐름 테스트
- [ ] 누락된 필드 추가 발견 시 문서 업데이트

View File

@@ -0,0 +1,546 @@
# 품목 등록/수정 백엔드 API 수정 요청
> 프론트엔드 품목관리 기능 개발 중 발견된 백엔드 수정 필요 사항 정리
>
> **최종 업데이트**: 2025-12-09
---
## 🟢 [핵심] field_key 통일 - source_column 매핑 방식 채택
### 상태: 🟡 백엔드 작업 진행 중
### 발견일: 2025-12-06
### 요약
| 항목 | 내용 |
|------|------|
| **근본 원인** | 품목기준관리 / 품목관리 API 요청서 분리로 키값 불일치 |
| **해결 방향** | `source_column` 매핑으로 백엔드에서 변환 처리 |
| **백엔드 작업** | `item_fields``source_column` 컬럼 추가 ✅, Edit 응답 형식 수정 진행 중 |
| **프론트 작업** | 백엔드 완료 후 하드코딩 매핑 제거 예정 |
### 2025-12-09 백엔드 대화 결론
1. **등록/상세/리스트**: ✅ `field_key` 기준으로 문제없음
2. **수정 (Edit)**: ⏳ 조회 시 `98_unit` 형식으로 응답 예정 (백엔드 수정 중)
3. **source_column**: 백엔드 내부 변환용, 프론트에 노출 안됨
### 참고 문서
- 백엔드 설계서: `~/Desktop/코브라브릿지백엔드문서/item-master-integration.md`
- sam-api 커밋: `bf92b37` (품목 마스터 소스 매핑 기능 추가)
### 현재 문제
```
품목기준관리 field_key 프론트엔드 변환 백엔드 API 응답
───────────────────── ───────────────── ─────────────────
Part_type → part_type → part_type (불일치!)
Installation_type_1 → installation_type → 저장 안됨
side_dimensions_horizontal → side_spec_width → 저장 안됨
```
**두 개의 요청서가 따로 놀면서** 백엔드에서 각각 다르게 구현 → 키 불일치 발생
### 이상적인 구조
```
품목기준관리 (field_key 정의)
┌────────────┐
│ field_key │ ← Single Source of Truth
└────────────┘
┌────┴────┬────────┬────────┐
▼ ▼ ▼ ▼
등록 수정 상세 리스트
(전부 동일한 field_key로 저장/조회/렌더링)
```
### 기대 효과
1. **단위 필드 혼란 해결**: field_key가 "unit"이면 그게 단위 (명확!)
2. **필드 타입 자동 인식**: 품목기준관리 field_type 보고 자동 렌더링
3. **누락 데이터 분석 용이**: field_key 하나만 확인하면 끝
4. **디버깅 속도 향상**: API 응답 = 폼 데이터 (변환 없음)
### 수정 요청
1. **품목기준관리 field_key를 기준**으로 API 요청/응답 키 통일
2. 동적 필드는 `attributes` JSON에 field_key 그대로 저장
3. 조회 시에도 field_key 그대로 응답
### 영향 범위
- 품목관리 전체 (등록/수정/상세/리스트)
- 모든 품목 유형 (FG, PT, SM, RM, CS)
### 우선순위
🔴 **최우선** - 이 구조 개선 후 아래 개별 이슈들 대부분 자동 해결
---
## 1. 소모품(CS) 등록 시 규격(specification) 저장 안됨
### 상태: 🔴 수정 필요
### 발견일: 2025-12-06
### 파일 위치
`/app/Http/Requests/Item/ItemStoreRequest.php` - rules() 메서드 (Line 14-42)
### 현재 문제
- `specification` 필드가 validation rules에 없음
- Laravel FormRequest는 rules에 정의되지 않은 필드를 `$request->validated()` 결과에서 제외
- 프론트엔드에서 `specification: "테스트"` 값을 보내도 백엔드에서 무시됨
- DB에 규격 값이 null로 저장됨
### 프론트엔드 확인 사항
- `DynamicItemForm`에서 `97_specification``spec``specification`으로 정상 변환됨
- API 요청 payload에 `specification` 필드 포함 확인됨
- 백엔드 `ItemsService.createMaterial()`에서 `$data['specification']` 참조하나 값이 없음
### 수정 요청
```php
// /app/Http/Requests/Item/ItemStoreRequest.php
public function rules(): array
{
return [
// 필수 필드
'code' => 'required|string|max:50',
'name' => 'required|string|max:255',
'product_type' => 'required|string|in:FG,PT,SM,RM,CS',
'unit' => 'required|string|max:20',
// 선택 필드
'category_id' => 'nullable|integer|exists:categories,id',
'description' => 'nullable|string',
'specification' => 'nullable|string|max:255', // ✅ 추가 필요
// ... 나머지 기존 필드들 ...
];
}
```
### 영향 범위
- 소모품(CS) 등록
- 원자재(RM) 등록 (해당 시)
- 부자재(SM) 등록 (해당 시)
---
## 2. Material(SM, RM, CS) 수정 시 material_code 중복 에러
### 상태: 🔴 수정 필요
### 발견일: 2025-12-06
### 현재 문제
- Material 수정 시 `material_code` 중복 체크 에러 발생
- 케이스 1: 값을 변경하지 않아도 자기 자신과 중복 체크됨
- 케이스 2: 소프트 삭제된 품목의 코드와도 중복 체크됨
- 에러 메시지: `Duplicate entry '알루미늄-옵션2-2' for key 'materials.materials_material_code_unique'`
### 원인
1. UPDATE 시 자기 자신의 ID를 제외하지 않음
2. 소프트 삭제(`deleted_at`)된 레코드도 unique 체크에 포함됨
### 수정 요청
```php
// Material 수정 Request 파일 (MaterialUpdateRequest.php 또는 해당 파일)
use Illuminate\Validation\Rule;
public function rules(): array
{
return [
// ... 기존 필드들 ...
// ✅ 수정 시 자기 자신 제외 + 소프트삭제 제외하고 unique 체크
'material_code' => [
'required',
'string',
'max:255',
Rule::unique('materials', 'material_code')
->whereNull('deleted_at') // 소프트삭제된 건 제외
->ignore($this->route('id')), // 자기 자신 제외
],
// ... 나머지 필드들 ...
];
}
```
### 영향 범위
- 원자재(RM) 수정
- 부자재(SM) 수정
- 소모품(CS) 수정
### 비고
- 현재 수정 자체가 불가능하여 수정 후 데이터 검증이 어려움
- 이 이슈 해결 후 수정 기능 재검증 필요
---
## 3. Material(SM, RM) 수정 시 규격(specification) 로딩 안됨
### 상태: 🔴 확인 필요
### 발견일: 2025-12-06
### 현재 문제
- SM(부자재), RM(원자재) 수정 페이지 진입 시 규격 값이 표시 안됨
- 1회 수정 후 다시 수정 페이지 진입 시 규격 값 없음
- CS(소모품)과 동일한 문제로 추정
### 원인 추정
- 백엔드에서 `options` 배열이 제대로 저장되지 않거나 반환되지 않음
- SM/RM은 `standard_*` 필드 조합으로 `specification`을 생성하고, 이 값을 `options` 배열에도 저장
- 수정 페이지에서는 `options` 배열을 읽어서 폼에 표시
- `options`가 null이면 규격 필드들이 빈 값으로 표시됨
### 확인 요청
```php
// Material 조회 API 응답에서 options 필드 확인 필요
// GET /api/v1/items/{id}?item_type=MATERIAL
// 응답 예시 (정상)
{
"id": 396,
"name": "썬더볼트",
"specification": "부자재2-2",
"options": [
{"label": "standard_1", "value": "부자재2"},
{"label": "standard_2", "value": "2"}
] // ✅ options 배열이 있어야 함
}
// 응답 예시 (문제)
{
"id": 396,
"name": "썬더볼트",
"specification": "부자재2-2",
"options": null // ❌ options가 null이면 규격 로딩 불가
}
```
### 수정 요청
1. Material 저장 시 `options` 배열 정상 저장 확인
2. Material 조회 시 `options` 필드 반환 확인
3. `options`가 JSON 컬럼이라면 정상적인 JSON 형식으로 저장되는지 확인
### 영향 범위
- 원자재(RM) 수정
- 부자재(SM) 수정
---
## 4. Material(SM, RM, CS) 비고(remarks) 저장 안됨
### 상태: 🔴 수정 필요
### 발견일: 2025-12-06
### 현재 문제
- 소모품(CS), 원자재(RM), 부자재(SM) 등록/수정 시 비고(remarks)가 저장되지 않음
- 프론트에서 `note``remarks`로 정상 변환하여 전송
- 백엔드 Service에서 `$data['remarks']` 참조하지만 값이 없음
### 원인 분석
- **프론트엔드**: `note``remarks` 변환 ✅ (`materialTransform.ts:115`)
- **백엔드 Service**: `'remarks' => $data['remarks'] ?? null` ✅ (`ItemsService.php:301`)
- **백엔드 Model**: `remarks` 컬럼 존재 ✅ (`Material.php:31`)
- **백엔드 Request**: `remarks` validation rule 없음 ❌ **누락**
### 수정 요청
```php
// /app/Http/Requests/Item/ItemStoreRequest.php
// /app/Http/Requests/Item/ItemUpdateRequest.php
public function rules(): array
{
return [
// 기존 필드들...
'description' => 'nullable|string',
// ✅ 추가 필요
'remarks' => 'nullable|string', // 비고 필드
// ... 나머지 필드들 ...
];
}
```
### 영향 범위
- 소모품(CS) 등록/수정
- 원자재(RM) 등록/수정
- 부자재(SM) 등록/수정
### 비고
- 1번 이슈(specification)와 동일한 원인: Request validation 누락
- 함께 처리하면 효율적
---
## 5. 품목기준관리 옵션 필드 식별자 필요 (장기 개선)
### 상태: 🟡 개선 권장
### 발견일: 2025-12-06
### 현재 문제
- 품목기준관리에서 옵션 필드의 `field_key`를 자유롭게 입력 가능
- 프론트엔드는 `standard_*`, `option_*` 패턴으로 옵션 필드를 식별
- 패턴에 맞지 않는 field_key (예: `st_2`)는 규격(specification) 조합에서 누락됨
- 결과: 부자재(SM)의 규격값이 저장되지 않음
### 임시 해결 (프론트엔드)
- 품목기준관리에서 field_key를 `standard_*` 패턴으로 통일
- 예: `st_2``standard_2`
### 근본 해결 요청 (백엔드)
```php
// 품목기준관리 API 응답에 옵션 필드 여부 표시
// 현재 응답
{
"field_key": "st_2",
"field_type": "select",
"field_name": "규격옵션"
}
// 개선 요청
{
"field_key": "st_2",
"field_type": "select",
"field_name": "규격옵션",
"is_spec_option": true // ✅ 규격 조합용 옵션 필드인지 표시
}
```
### 기대 효과
- 프론트엔드가 field_key 패턴에 의존하지 않음
- `is_spec_option: true`인 필드만 규격 조합에 사용
- 새로운 field_key 패턴이 추가되어도 프론트 수정 불필요
### 영향 범위
- 원자재(RM) 등록/수정
- 부자재(SM) 등록/수정
- 향후 추가되는 Material 유형
---
## 6. 조립부품(PT-ASSEMBLY) 필드 저장 안됨 - fillable 누락
### 상태: 🔴 수정 필요
### 발견일: 2025-12-06
### 현재 문제
- 조립부품 등록 후 상세보기/수정 페이지에서 데이터가 제대로 표시되지 않음
- **프론트엔드는 데이터를 정상 전송하고 있음** ✅
- **백엔드에서 조립부품 필드들이 저장되지 않음** ❌
### 원인 분석
#### 프론트엔드 전송 데이터 (정상)
```javascript
// DynamicItemForm/index.tsx - handleFormSubmit()
const submitData = {
...convertedData, // 폼 데이터 (installation_type, assembly_type 등 포함)
product_type: 'PT',
part_type: 'ASSEMBLY', // ✅ 명시적 추가
bending_diagram: base64, // ✅ 캔버스 이미지
bom: [...], // ✅ BOM 데이터
};
```
#### 백엔드 저장 로직 (문제)
```php
// ItemsService.php - createProduct()
private function createProduct(array $data, int $tenantId, int $userId): Product
{
$payload = $data;
// ... 기본 필드만 설정
return Product::create($payload); // Mass Assignment
}
```
#### Product 모델 fillable (누락 필드 있음)
```php
// Product.php
protected $fillable = [
'tenant_id', 'code', 'name', 'unit', 'category_id',
'product_type',
'attributes', 'description', // ✅ attributes JSON 있음
'part_type', // ✅ 있음
'bending_diagram', 'bending_details', // ✅ 있음
// ❌ installation_type 없음
// ❌ assembly_type 없음
// ❌ side_spec_width 없음
// ❌ side_spec_height 없음
// ❌ length 없음
];
```
### 필드별 저장 상태
| 필드 | 프론트 전송 | fillable | 저장 여부 |
|------|------------|----------|----------|
| `part_type` | ✅ | ✅ 컬럼 | ✅ 저장됨 |
| `bending_diagram` | ✅ | ✅ 컬럼 | ⚠️ 파일 업로드 별도 처리 |
| `installation_type` | ✅ | ❌ 없음 | ❌ **저장 안됨** |
| `assembly_type` | ✅ | ❌ 없음 | ❌ **저장 안됨** |
| `side_spec_width` | ✅ | ❌ 없음 | ❌ **저장 안됨** |
| `side_spec_height` | ✅ | ❌ 없음 | ❌ **저장 안됨** |
| `length` | ✅ | ❌ 없음 | ❌ **저장 안됨** |
| `bom` | ✅ | ⚠️ 별도 처리 | ❓ 확인 필요 |
### 수정 요청
#### 방법 1: attributes JSON에 저장 (권장)
```php
// ItemsService.php - createProduct() 수정
private function createProduct(array $data, int $tenantId, int $userId): Product
{
// 조립부품 전용 필드들을 attributes JSON으로 묶기
$assemblyFields = ['installation_type', 'assembly_type', 'side_spec_width', 'side_spec_height', 'length'];
$attributes = $data['attributes'] ?? [];
foreach ($assemblyFields as $field) {
if (isset($data[$field])) {
$attributes[$field] = $data[$field];
unset($data[$field]); // payload에서 제거
}
}
$payload = $data;
$payload['tenant_id'] = $tenantId;
$payload['created_by'] = $userId;
$payload['attributes'] = !empty($attributes) ? $attributes : null;
// ... 나머지 동일
return Product::create($payload);
}
```
#### 방법 2: 컬럼 추가 + fillable 등록
```php
// Product.php - fillable에 추가
protected $fillable = [
// 기존 필드들...
'installation_type', // ✅ 추가
'assembly_type', // ✅ 추가
'side_spec_width', // ✅ 추가
'side_spec_height', // ✅ 추가
'length', // ✅ 추가 (또는 assembly_length)
];
// + migration으로 컬럼 추가 필요
```
### 프론트엔드 대응 (완료)
- 프론트에서 `data.xxx` 또는 `data.attributes.xxx` 둘 다에서 값을 찾도록 수정 완료
- 상세보기: `items/[id]/page.tsx` - `mapApiResponseToItemMaster` 함수
- 수정페이지: `items/[id]/edit/page.tsx` - `mapApiResponseToFormData` 함수
### 영향 범위
- 조립부품(PT-ASSEMBLY) 등록/조회/수정
- 설치유형, 마감, 측면규격, 길이 모든 필드
### 우선순위
🔴 **높음** - 조립부품 등록 기능이 완전히 동작하지 않음
---
## 7. 파일 업로드 API 500 에러 - ApiResponse 클래스 네임스페이스 불일치
### 상태: 🔴 수정 필요
### 발견일: 2025-12-06
### 현재 문제
- 품목 파일 업로드 (전개도, 시방서, 인정서) 시 500 에러 발생
- 절곡부품, 조립부품 모두 동일한 에러
- 파일이 업로드되지 않음
### 에러 로그
```
[2025-12-06 17:28:22] DEV.ERROR: Class "App\Http\Responses\ApiResponse" not found
{"exception":"[object] (Error(code: 0): Class \"App\\Http\\Responses\\ApiResponse\" not found
at /home/webservice/api/app/Http/Controllers/Api/V1/ItemsFileController.php:31)
```
### 원인 분석
**네임스페이스 불일치**
| 위치 | 네임스페이스 |
|------|-------------|
| `ItemsFileController.php` (Line 7) | `use App\Http\Responses\ApiResponse` ❌ |
| 실제 파일 위치 | `App\Helpers\ApiResponse` ✅ |
### 파일 위치
`/app/Http/Controllers/Api/V1/ItemsFileController.php` (Line 7)
### 현재 코드
```php
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Requests\ItemsFileUploadRequest;
use App\Http\Responses\ApiResponse; // ❌ 잘못된 경로
// ...
```
### 수정 요청
```php
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Requests\ItemsFileUploadRequest;
use App\Helpers\ApiResponse; // ✅ 올바른 경로
// ...
```
### 영향 범위
- 절곡부품(PT-BENDING) 전개도 업로드
- 조립부품(PT-ASSEMBLY) 전개도 업로드
- 제품(FG) 시방서 업로드
- 제품(FG) 인정서 업로드
### 우선순위
🔴 **긴급** - 모든 파일 업로드 기능이 동작하지 않음 (한 줄 수정으로 해결 가능)
---
## 수정 완료 내역
> 수정 완료된 항목은 아래로 이동
(아직 없음)
---
## 참고 사항
### 관련 파일 (프론트엔드)
- `src/app/[locale]/(protected)/items/create/page.tsx` - 품목 등록 페이지
- `src/app/[locale]/(protected)/items/[id]/edit/page.tsx` - 품목 수정 페이지
- `src/components/items/DynamicItemForm/index.tsx` - 동적 폼 컴포넌트
### 관련 파일 (백엔드)
- `/app/Http/Controllers/Api/V1/ItemsController.php` - 품목 API 컨트롤러
- `/app/Services/ItemsService.php` - 품목 서비스 레이어
- `/app/Http/Requests/Item/ItemStoreRequest.php` - 등록 요청 검증
- `/app/Http/Requests/Item/ItemUpdateRequest.php` - 수정 요청 검증
- `/app/Models/Materials/Material.php` - Material 모델

View File

@@ -0,0 +1,154 @@
# 조립부품(Assembly Part) 이슈 체크리스트
> 품목관리 - 제품(PT) 조립부품 관련 이슈 추적
---
## 이슈 요약
| # | 이슈 | 상태 | 유형 |
|---|------|------|------|
| 1 | 캔버스 드로잉 마우스 오프셋 버그 | 🔴 | 프론트엔드 |
| 2 | 상세보기 - 조립부품 세부정보/전개도 누락 | 🔴 | 프론트엔드/API |
| 3 | 수정 페이지 - 기존 데이터 로딩 안됨 | 🔴 | 프론트엔드/API |
---
## 1. 캔버스 드로잉 마우스 오프셋 버그
### 상태: ✅ 수정 완료
### 현상
- 마우스 클릭 지점과 실제 그려지는 위치가 다름
- sam-design에서는 정상 작동
- sam-react-prod에서만 오프셋 발생
### 원인
- `getMousePos()` 함수에서 캔버스 scale 비율 계산 누락
- 캔버스 실제 해상도(600x400)와 CSS 표시 크기(`w-full`)가 다름
- scale 보정 없이 좌표 계산하면 오프셋 발생
### 체크리스트
- [x] 캔버스 컴포넌트 파일 위치 확인
- [x] sam-design vs sam-react-prod 코드 비교
- [x] getBoundingClientRect() 사용 여부 확인
- [x] CSS transform/scale 영향 확인
- [x] 오프셋 계산 로직 수정
- [ ] 테스트 및 검증
### 수정 내용
```typescript
// Before (문제)
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top,
};
// After (수정)
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
return {
x: (e.clientX - rect.left) * scaleX,
y: (e.clientY - rect.top) * scaleY,
};
```
### 관련 파일
- `src/components/items/DrawingCanvas.tsx` (Line 135-146)
---
## 2. 상세보기 - 조립부품 세부정보/전개도 누락
### 상태: 🟡 프론트 수정 완료, API 확인 필요
### 현상
- "조립 부품 세부 정보" 섹션 헤더만 있고 내용 없음
- "조립품 전개도 (바라시)" 섹션 자체가 없음
### 원인
1. API 응답에서 조립부품 필드들이 `attributes` JSON 안에 있음
2. 프론트에서 `data.installation_type`만 확인, `attributes.installation_type` 확인 안함
3. 전개도 섹션은 `bendingDiagram`이 있어야만 표시되는 조건
### 프론트엔드 수정 내용
1. **상세 페이지 매핑** (`items/[id]/page.tsx`)
- `attributes` 객체에서도 조립부품 필드 추출하도록 수정
- `installation_type`, `assembly_type`, `assembly_length`, `side_spec_width`, `side_spec_height`
2. **상세보기 컴포넌트** (`ItemDetailClient.tsx`)
- 조립부품 세부정보 섹션: 값 없으면 `-` 표시하도록 개선
- 전개도 섹션: 데이터 없어도 섹션 표시, "등록된 전개도가 없습니다" 메시지
### 체크리스트
- [x] 상세보기 페이지 컴포넌트 확인
- [x] API 응답 구조 분석 (attributes JSON)
- [x] 조립부품 세부정보 렌더링 로직 수정
- [x] 전개도 섹션 항상 표시하도록 수정
- [ ] 테스트 및 검증
- [ ] 백엔드 API에서 `bending_diagram` 반환 여부 확인 필요
### 관련 파일
- `src/app/[locale]/(protected)/items/[id]/page.tsx` - 매핑 함수
- `src/components/items/ItemDetailClient.tsx` - UI 컴포넌트
### 백엔드 확인 필요
- 조립부품 등록 시 `bending_diagram` 필드가 DB에 저장되는지 확인
- 조회 API 응답에 `bending_diagram`, `attributes` 포함 여부 확인
---
## 3. 수정 페이지 - 기존 데이터 로딩 안됨
### 상태: 🟡 프론트 수정 완료, API 확인 필요
### 현상
- 부품 유형이 초기화됨 ("부품 유형을(를) 선택하세요")
- 기존 입력 데이터 전혀 로딩 안됨
- 조립품 전개도 미리보기 없음
- 부품 구성(BOM) 목록 로딩 안됨
### 원인
- 상세보기와 동일: API 응답에서 조립부품 필드들이 `attributes` JSON 안에 있음
- `mapApiResponseToFormData` 함수에서 `attributes`를 확인하지 않음
### 프론트엔드 수정 내용
**수정 페이지 매핑** (`items/[id]/edit/page.tsx`)
- `attributes` 객체에서도 조립부품 필드 추출하도록 수정
- `part_type`, `part_usage`, `installation_type`, `assembly_type`, `assembly_length`, 등
### 체크리스트
- [x] 수정 페이지 데이터 fetch 로직 확인
- [x] API 응답 데이터 구조 확인 (attributes JSON)
- [x] 폼 초기값 매핑 로직 수정
- [x] 조립부품 특화 필드 매핑 추가
- [ ] 전개도 이미지 로딩 - 백엔드 확인 필요
- [ ] BOM 목록 로딩 - 백엔드 확인 필요
- [ ] 테스트 및 검증
### 관련 파일
- `src/app/[locale]/(protected)/items/[id]/edit/page.tsx` - 매핑 함수
### 백엔드 확인 필요
- 조립부품 등록 시 `part_type`, `attributes` 필드가 DB에 저장되는지 확인
- Product 모델에서 `part_type`이 컬럼인지 `attributes` JSON 안에 있는지 확인
- 조회 API 응답에 `bending_diagram`, `bom` 포함 여부 확인
---
## 작업 로그
### 2025-12-06
- 이슈 체크리스트 생성
- 스크린샷 분석 완료
-**캔버스 오프셋 버그 수정**: `DrawingCanvas.tsx` - scale 비율 계산 추가
-**상세보기 페이지 수정**:
- `items/[id]/page.tsx` - attributes에서 조립부품 필드 추출
- `ItemDetailClient.tsx` - 세부정보 섹션 항상 표시, 전개도 섹션 조건 완화
-**수정 페이지 수정**:
- `items/[id]/edit/page.tsx` - attributes에서 조립부품 필드 추출
-**API 요청 문서 업데이트**: 조립부품 관련 백엔드 확인 사항 추가
### 남은 작업
- [ ] 백엔드와 조립부품 데이터 저장/반환 확인 논의
- [ ] 실제 테스트 후 검증

View File

@@ -0,0 +1,80 @@
# 다음 세션 컨텍스트 - 품목관리 기능 개발
> 2025-12-06 세션에서 진행한 내용 및 다음 세션에서 이어갈 작업
---
## 완료된 작업
### 1. 삭제 알럿 제거 ✅
- 품목 테이블에서 삭제 버튼 클릭 → 모달 확인 → 바로 삭제 (알럿 없이)
- 파일: `src/components/items/ItemListClient.tsx`
### 2. 디버깅 콘솔 로그 제거 ✅
- `DropdownField.tsx` - 단위 필드 디버깅 로그 제거
- `useConditionalDisplay.ts` - 조건부 표시 디버깅 로그 제거
- `useDynamicFormState.ts` - resetForm 디버깅 로그 제거
---
## 발견된 문제 (백엔드 수정 필요)
### 소모품(CS) 규격(specification) 저장 안됨 🔴
**원인 분석 완료**:
1. 프론트엔드: `97_specification``spec``specification`으로 정상 변환됨
2. 백엔드 문제: `ItemStoreRequest.php`의 validation rules에 `specification` 필드가 없음
3. Laravel FormRequest는 rules에 없는 필드를 `$request->validated()`에서 제외
4. 결과: DB에 규격이 null로 저장됨
**백엔드 수정 요청**:
```php
// /app/Http/Requests/Item/ItemStoreRequest.php
// rules()에 추가 필요:
'specification' => 'nullable|string|max:255',
```
**상세 문서**: `claudedocs/item-master/[API-2025-12-06] item-crud-backend-requests.md`
---
## 다음 세션에서 확인할 사항
1. **백엔드 수정 후 테스트**
- 소모품 등록 시 규격 값 저장 확인
- 상세 페이지에서 규격 표시 확인
2. **수정 API도 확인 필요**
- `ItemUpdateRequest.php`에도 `specification` 필드 있는지 확인
- 어제 "수정하면 규격이 보였다"고 했는데, 수정 API는 다를 수 있음
3. **추가 편의 기능 개발** (사용자 요청 시)
- 품목관리 관련 추가 기능 구현
---
## 관련 파일 위치
### 프론트엔드
- `src/components/items/ItemListClient.tsx` - 품목 목록/삭제
- `src/components/items/ItemDetailClient.tsx` - 품목 상세
- `src/components/items/DynamicItemForm/index.tsx` - 동적 폼
- `src/app/[locale]/(protected)/items/create/page.tsx` - 등록 페이지
- `src/app/[locale]/(protected)/items/[id]/edit/page.tsx` - 수정 페이지
- `src/app/[locale]/(protected)/items/[id]/page.tsx` - 상세 페이지
### 백엔드 (sam-api)
- `/app/Http/Requests/Item/ItemStoreRequest.php` - 등록 요청 검증 ⚠️ 수정 필요
- `/app/Http/Requests/Item/ItemUpdateRequest.php` - 수정 요청 검증 (확인 필요)
- `/app/Services/ItemsService.php` - 품목 서비스
- `/app/Models/Materials/Material.php` - Material 모델
---
## 명령어
```bash
# 프론트엔드 개발 서버
cd /Users/byeongcheolryu/codebridgex/sam_project/sam-next/sma-next-project/sam-react-prod
npm run dev
```

View File

@@ -0,0 +1,120 @@
# 품목관리 세션 체크포인트
> 작성일: 2025-12-09
> 수정일: 2025-12-09
> 상태: ✅ Phase 1 완료!
---
## 🎉 2025-12-09 완료 사항
### 백엔드 작업 완료
| 항목 | 상태 |
|------|------|
| field_key 저장 방식 변경 (`98_unit``unit`) | ✅ 완료 |
| 시스템 예약어 검증 (`SystemFields.php`) | ✅ 완료 |
| 중복 검증 로직 | ✅ 완료 |
| 에러 메시지 한국어화 | ✅ 완료 |
### 프론트엔드 정리 완료
| 항목 | 삭제된 코드 | 상태 |
|------|------------|------|
| Edit 모드 매핑 로직 | ~140줄 | ✅ 완료 |
| `fieldAliases` 객체 | 25줄 | ✅ 완료 |
| `extractFieldName()` 함수 | 7줄 | ✅ 완료 |
| `fieldKeyMap` 생성 로직 | 25줄 | ✅ 완료 |
| `fieldKeyToBackendKey` 변환 | 60줄 | ✅ 완료 |
| **총 삭제** | **~200줄** | ✅ |
### 빌드 검증
```bash
npm run build # ✅ 성공
```
---
## 📋 새로운 데이터 흐름
### field_key 통일 완료
```
등록: { "unit": "EA" } → 그대로 저장
조회: DB → { "unit": "EA" } → 그대로 표시
수정: { "unit": "EA" } → 그대로 저장
※ 기존 레거시 데이터 (98_unit 형식)도 그대로 동작
```
### 코드 변경 요약
**Before (복잡한 매핑)**:
```typescript
// Edit 모드: 155줄 매핑 로직
const fieldAliases = { 'unit': '단위', ... };
const extractFieldName = (key) => { ... };
const fieldKeyMap = { ... };
// 여러 단계 변환...
```
**After (직접 사용)**:
```typescript
// Edit 모드: 15줄
useEffect(() => {
if (mode !== 'edit' || !structure || !initialData || isEditDataMapped) return;
resetForm(initialData); // 직접 사용!
setIsEditDataMapped(true);
}, [mode, structure, initialData, isEditDataMapped, resetForm]);
```
---
## ⏳ 남은 작업
### 파일 업로드 500 에러 (검수 중)
```
위치: /app/Http/Controllers/Api/V1/ItemsFileController.php (Line 7)
문제: use App\Http\Responses\ApiResponse (잘못된 경로)
수정: use App\Helpers\ApiResponse (올바른 경로)
```
### Phase 2: 컴포넌트 분리 (선택적)
계획 문서: `[PLAN-2025-12-08] dynamic-form-separation-plan.md`
- 공통 컴포넌트 추출 (FileUpload, BOM, AutoItemCode)
- 품목별 컴포넌트 생성 (FG, PT, SM, RM, CS)
- DynamicFormCore 리팩토링
---
## 📋 테스트 체크리스트
### 등록 테스트
- [ ] FG(제품) 등록
- [ ] PT-조립부품 등록
- [ ] PT-절곡부품 등록
- [ ] SM/RM/CS 등록
### 수정 테스트
- [ ] 수정 페이지 진입 → 데이터 로드 확인
- [ ] 드롭다운 값 표시 확인
- [ ] 수정 후 저장 → 값 유지 확인
### 파일 업로드 테스트
- [ ] 절곡부품 전개도 업로드
- [ ] 조립부품 전개도 업로드
- [ ] 제품 시방서/인정서 업로드
---
## 📚 관련 문서
| 문서 | 위치 |
|------|------|
| DynamicForm 분리 계획 | `[PLAN-2025-12-08] dynamic-form-separation-plan.md` |
| Radix UI 버그 해결 | `claudedocs/guides/[FIX-2025-12-05] radix-ui-select-controlled-mode-bug.md` |
| 백엔드 field_key 검증 스펙 | `sam-api/docs/specs/item-master-field-key-validation.md` |

View File

@@ -0,0 +1,305 @@
# DynamicItemForm 품목별 분리 계획
> 작성일: 2025-12-08
> 수정일: 2025-12-09
> 상태: 🚀 Phase 1 진행 중
---
## 🎉 2025-12-09 업데이트
### 백엔드 작업 완료!
| 항목 | 상태 |
|------|------|
| field_key 저장 방식 변경 (`98_unit``unit`) | ✅ 완료 |
| 시스템 예약어 검증 (SystemFields.php) | ✅ 완료 |
| 중복 검증 로직 | ✅ 완료 |
| 에러 메시지 | ✅ 완료 |
### Phase 1 완료! ✅
제거된 레거시 코드:
-`fieldAliases` (영문 → 한글 매핑) - 25줄
-`extractFieldName()` 함수 - 7줄
-`fieldKeyMap` 생성 로직 - 25줄
- ✅ 대소문자 무시 매핑 - 15줄
- ✅ 별칭 fallback 매핑 - 10줄
-`98_` prefix 파싱 로직 - 8줄
-`fieldKeyToBackendKey` 변환 테이블 - 30줄
- ✅ 복잡한 변환 로직 - 40줄
**실제 결과**: **~200줄 코드 삭제!** 🎉
### 변경 요약
| 변경 내용 | Before | After |
|-----------|--------|-------|
| Edit 모드 매핑 | 155줄 복잡한 로직 | 15줄 직접 로드 |
| 저장 시 변환 | 75줄 변환 테이블 | 15줄 is_active만 처리 |
| 총 코드량 | ~230줄 | ~30줄 |
---
## 📊 현재 상태 분석
### 현재 구조
```
src/components/items/
├── DynamicItemForm/ # 현재: 모든 품목 처리 (1350+ lines)
│ ├── index.tsx # 메인 컴포넌트 (거대)
│ ├── fields/ # 필드 렌더러
│ ├── hooks/ # 상태 관리 훅
│ ├── sections/ # BOM 섹션
│ ├── types.ts
│ └── utils/
├── ItemForm/ # 기존: 하드코딩된 폼 (참고용)
│ ├── forms/
│ │ ├── ProductForm.tsx # FG(제품) 전용
│ │ ├── MaterialForm.tsx # RM(원자재) 전용
│ │ └── PartForm.tsx # PT(부품) → 하위 분기
│ │ └── parts/
│ │ ├── AssemblyPartForm.tsx # 조립부품
│ │ ├── BendingPartForm.tsx # 절곡부품
│ │ └── PurchasedPartForm.tsx # 구매부품
```
### 문제점
| 문제 | 영향도 | field_key 통일로 해결? |
|------|--------|----------------------|
| DynamicItemForm 1350+ lines | 🔴 유지보수 어려움 | ❌ |
| 품목별 특수 로직 혼재 | 🔴 버그 발생 원인 | ❌ |
| 조건부 렌더링 복잡도 | 🟡 코드 가독성 | ⚠️ 부분 해결 |
| 파일 업로드 로직 중복 | 🟡 DRY 위반 | ❌ |
| 테스트 어려움 | 🟡 품질 이슈 | ❌ |
### 품목 유형별 특수 로직
```yaml
FG (제품):
- 시방서/인정서 파일 업로드
- 인정번호, 유효기간
- BOM 구성 (선택)
PT (부품):
- 부품 유형 선택 (ASSEMBLY/BENDING/PURCHASED)
- 조립부품: 측면규격, 길이, 설치유형, BOM
- 절곡부품: 전개도(바라시), 재질, 폭합계
- 구매부품: 전기개폐기, 모터, 체인 규격
SM (반제품):
- 규격 정보
- BOM 구성 (선택)
RM (원자재):
- 재질, 두께, 폭, 단위
- 단가 정보
CS (소모품):
- 기본 정보만
```
---
## 🎯 목표 구조 (2단계 분리)
### Phase 1: field_key 통일 (백엔드 작업 대기)
- 품목기준관리 API와 품목 CRUD API 간 field_key 일치
- 데이터 일관성 확보 → 버그 60% 감소 예상
### Phase 2: 컴포넌트 분리 (field_key 통일 후)
```
src/components/items/
├── DynamicItemForm/
│ ├── index.tsx # 컨테이너 (라우팅만)
│ ├── DynamicFormCore.tsx # 공통 폼 프레임워크
│ │
│ ├── itemTypes/ # 🆕 품목 유형별 컴포넌트
│ │ ├── index.ts # 내보내기
│ │ ├── FGFormFields.tsx # 제품 전용 (인정정보, 파일업로드)
│ │ ├── PTFormFields.tsx # 부품 라우터
│ │ │ ├── AssemblyFields.tsx # 조립부품 전용
│ │ │ ├── BendingFields.tsx # 절곡부품 전용 (전개도)
│ │ │ └── PurchasedFields.tsx # 구매부품 전용
│ │ ├── SMFormFields.tsx # 반제품 전용
│ │ ├── RMFormFields.tsx # 원자재 전용
│ │ └── CSFormFields.tsx # 소모품 전용
│ │
│ ├── shared/ # 🆕 공유 컴포넌트
│ │ ├── FileUploadSection.tsx # 파일 업로드 (FG, PT-절곡)
│ │ ├── BOMSection.tsx # BOM 관리 (FG, PT-조립, SM)
│ │ ├── StatusField.tsx # 활성/비활성
│ │ └── AutoItemCode.tsx # 품목코드 자동생성
│ │
│ ├── fields/ # 기존 유지
│ ├── hooks/ # 기존 유지 + 개선
│ ├── sections/ # → shared/로 이동
│ └── types.ts
```
---
## 📋 세부 작업 계획
### Phase 2-1: 공통 컴포넌트 추출
| 작업 | 파일 | 예상 LOC |
|------|------|----------|
| 파일 업로드 섹션 추출 | `shared/FileUploadSection.tsx` | ~150 |
| BOM 섹션 정리 | `shared/BOMSection.tsx` | ~100 |
| 품목코드 자동생성 | `shared/AutoItemCode.tsx` | ~50 |
| 상태 필드 | `shared/StatusField.tsx` | ~30 |
### Phase 2-2: 품목 유형별 컴포넌트 생성
| 작업 | 파일 | 특수 로직 |
|------|------|-----------|
| FG 전용 | `itemTypes/FGFormFields.tsx` | 인정정보, 시방서/인정서 |
| PT 라우터 | `itemTypes/PTFormFields.tsx` | 부품유형 선택 후 분기 |
| PT-조립 | `itemTypes/AssemblyFields.tsx` | 측면규격, 설치유형, BOM |
| PT-절곡 | `itemTypes/BendingFields.tsx` | 전개도, 폭합계 |
| PT-구매 | `itemTypes/PurchasedFields.tsx` | 전기개폐기, 모터 규격 |
| SM 전용 | `itemTypes/SMFormFields.tsx` | 규격, BOM (선택) |
| RM 전용 | `itemTypes/RMFormFields.tsx` | 재질, 두께, 단가 |
| CS 전용 | `itemTypes/CSFormFields.tsx` | 기본 정보만 |
### Phase 2-3: DynamicFormCore 리팩토링
```typescript
// DynamicFormCore.tsx - 공통 프레임워크
interface DynamicFormCoreProps {
structure: FormStructure;
formData: DynamicFormData;
onChange: (key: string, value: any) => void;
renderCustomFields?: () => ReactNode; // 품목별 특수 필드
renderCustomSections?: () => ReactNode; // 품목별 특수 섹션
}
// index.tsx - 라우팅만
const ItemTypeComponents = {
FG: FGFormFields,
PT: PTFormFields,
SM: SMFormFields,
RM: RMFormFields,
CS: CSFormFields,
};
<DynamicFormCore
structure={structure}
formData={formData}
onChange={setFieldValue}
renderCustomFields={() => {
const Component = ItemTypeComponents[selectedItemType];
return Component ? <Component {...props} /> : null;
}}
/>
```
---
## 📊 예상 효과
### Before (현재)
- `DynamicItemForm/index.tsx`: **1350+ lines**
- 모든 품목 로직 혼재
- 수정 시 다른 품목 영향 우려
### After (분리 후)
- `DynamicItemForm/index.tsx`: **~200 lines** (라우팅만)
- `DynamicFormCore.tsx`: **~400 lines** (공통 프레임워크)
- 품목별 컴포넌트: **각 100-200 lines**
- **총 코드량 비슷하지만 책임 분리됨**
### 장점
1. **버그 격리**: 조립부품 수정 → 절곡부품 영향 없음
2. **유지보수 용이**: 품목별 로직 파악 쉬움
3. **테스트 가능**: 품목별 단위 테스트 작성 가능
4. **확장성**: 새 품목 유형 추가 시 파일만 추가
5. **협업**: 여러 개발자가 다른 품목 동시 작업 가능
---
## ⏰ 작업 순서 및 의존성
```
┌─────────────────────────────────────────┐
│ Phase 1: field_key 통일 (백엔드) │ ← 현재 대기 중
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Phase 2-1: 공통 컴포넌트 추출 │
│ - FileUploadSection │
│ - BOMSection │
│ - AutoItemCode │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Phase 2-2: 품목별 컴포넌트 생성 │
│ - FG, PT(Assembly/Bending/Purchased) │
│ - SM, RM, CS │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Phase 2-3: DynamicFormCore 리팩토링 │
│ - index.tsx 슬림화 │
│ - 품목별 라우팅 │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Phase 3: 테스트 및 검증 │
│ - 품목별 CRUD 테스트 │
│ - 회귀 테스트 │
└─────────────────────────────────────────┘
```
---
## ❓ 결정 필요 사항
### Q1. 기존 ItemForm 컴포넌트 처리
| 옵션 | 장점 | 단점 |
|------|------|------|
| A. 삭제 | 중복 제거, 혼란 방지 | 참고 코드 소실 |
| B. 유지 (참고용) | 마이그레이션 시 참고 가능 | 중복 유지보수 |
| C. archive 폴더로 이동 | 참고 가능 + 혼란 방지 | 폴더 추가 |
**권장**: C. archive 폴더로 이동
### Q2. 분리 전략
| 옵션 | 설명 |
|------|------|
| A. 점진적 분리 | 한 품목씩 분리, 기존 코드 유지 |
| B. 전면 분리 | 한 번에 모든 품목 분리 |
**권장**: A. 점진적 분리 (PT-조립부품부터 시작)
### Q3. 품목별 hooks 분리 여부
| 옵션 | 설명 |
|------|------|
| A. 공통 hooks 유지 | useDynamicFormState 그대로 사용 |
| B. 품목별 hooks 추가 | useAssemblyFormState 등 추가 |
**권장**: A. 공통 hooks 유지 (복잡도 관리)
---
## 📝 다음 단계
1.**백엔드 field_key 통일 답변 대기**
2. 📋 **결정 필요 사항 확인** (Q1, Q2, Q3)
3. 🚀 **Phase 2-1 시작** (공통 컴포넌트 추출)
---
## 참고 문서
- `claudedocs/item-master/[API-2025-12-06] item-crud-backend-requests.md` - field_key 통일 요청
- `src/components/items/ItemForm/` - 기존 하드코딩 폼 참고
- `src/components/items/DynamicItemForm/` - 현재 동적 폼

View File

@@ -1,6 +1,7 @@
# 거래처 관리 API 분석
> **작성일**: 2025-12-04
> **최종 수정**: 2025-12-09
> **목적**: sam-api 백엔드 Client API와 프론트엔드 거래처 관리 페이지 간 연동 분석
---
@@ -9,14 +10,14 @@
### 프론트엔드 (sam-react-prod)
- **파일**: `src/app/[locale]/(protected)/sales/client-management-sales-admin/page.tsx`
- **상태**: **API 연동** - 로컬 샘플 데이터(`SAMPLE_CUSTOMERS`)로만 동작
- **모든 CRUD가 클라이언트 사이드에서만 수행됨**
- **상태**: **API 연동 완료** (2025-12-09)
- **Hook**: `src/hooks/useClientList.ts` - 2차 필드 모두 지원
### 백엔드 (sam-api)
- **컨트롤러**: `app/Http/Controllers/Api/V1/ClientController.php`
- **서비스**: `app/Services/ClientService.php`
- **모델**: `app/Models/Orders/Client.php`
- **상태**: ✅ **API 구현 완료** - 모든 CRUD 기능 제공
- **상태**: ✅ **API 구현 완료** - 2차 필드 포함, is_active Boolean 변경 완료
---
@@ -69,10 +70,10 @@
| `email` | `email` | ✅ 동일 | |
| `address` | `address` | ✅ 동일 | |
| `registeredDate` | `created_at` | ✅ 매핑 필요 | 필드명 변경 |
| `status` | `is_active` | ✅ 매핑 필요 | "활성"/"비활성" ↔ "Y"/"N" |
| `businessNo` | - | ❌ **백엔드 없음** | 추가 필요 |
| `businessType` | - | ❌ **백엔드 없음** | 추가 필요 |
| `businessItem` | - | ❌ **백엔드 없음** | 추가 필요 |
| `status` | `is_active` | ✅ 매핑 완료 | "활성"/"비활성" ↔ boolean (2025-12-09 변경) |
| `businessNo` | `business_no` | ✅ 추가됨 | |
| `businessType` | `business_type` | ✅ 추가됨 | |
| `businessItem` | `business_item` | ✅ 추가됨 | |
| - | `tenant_id` | ✅ 백엔드 전용 | 자동 처리 |
| - | `client_group_id` | ⚠️ 프론트 없음 | 그룹 기능 미구현 |
@@ -214,18 +215,25 @@ ALTER TABLE clients ADD COLUMN memo TEXT NULL;
---
## 5. 프론트엔드 API 연동 구현 계획
## 5. 프론트엔드 API 연동 구현 ✅ 완료 (2025-12-09)
### 5.1 필요한 작업
### 5.1 완료된 작업
| # | 작업 | 우선순위 | 상태 |
|---|------|---------|------|
| 1 | Next.js API Proxy 생성 (`/api/proxy/clients/[...path]`) | 🔴 HIGH | ⬜ 미완료 |
| 2 | 커스텀 훅 생성 (`useClientList`) | 🔴 HIGH | ⬜ 미완료 |
| 3 | 타입 정의 업데이트 (`CustomerAccount` → API 응답 매핑) | 🟡 MEDIUM | ⬜ 미완료 |
| 4 | CRUD 함수를 API 호출로 변경 | 🔴 HIGH | ⬜ 미완료 |
| 1 | Next.js API Proxy 생성 (`/api/proxy/[...path]`) | 🔴 HIGH | 완료 |
| 2 | 커스텀 훅 생성 (`useClientList`) | 🔴 HIGH | 완료 |
| 3 | 타입 정의 업데이트 (2차 필드 모두 포함) | 🟡 MEDIUM | 완료 |
| 4 | CRUD 함수를 API 호출로 변경 | 🔴 HIGH | 완료 |
| 5 | 거래처 그룹 기능 추가 (선택) | 🟢 LOW | ⬜ 미완료 |
### 5.2 숨긴 섹션 (기획 미확정)
- 발주처 설정: 계정ID, 비밀번호, 매입/매출 결제일
- 약정 세금: 약정 여부, 금액, 시작/종료일
- 악성채권 정보: 악성채권 여부, 금액, 발생/만료일, 진행상태
> ⚠️ 백엔드 API 지원됨. 기획 확정 시 `ClientRegistration.tsx` TODO 주석 해제
### 5.2 API Proxy 구현 패턴
```typescript

View File

@@ -0,0 +1,358 @@
# 단가관리 API 개선 요청서
> **작성일**: 2025-12-08
> **요청자**: 프론트엔드 개발팀
> **대상**: sam-api 백엔드 팀
---
## 1. 현황 요약
### 현재 API 구조
| Endpoint | Method | 상태 |
|----------|--------|------|
| `/api/v1/pricing` | GET | 목록 조회 |
| `/api/v1/pricing/show` | GET | 단일 가격 조회 |
| `/api/v1/pricing/bulk` | POST | 일괄 가격 조회 |
| `/api/v1/pricing/upsert` | POST | 등록/수정 |
| `/api/v1/pricing/{id}` | DELETE | 삭제 |
### ✅ 이미 지원됨 (품목 정보)
- `item_type_code` (품목유형) - PriceHistory 테이블
- `item_code`, `item_name`, `specification`, `unit` - item 관계 JOIN으로 조회 가능
### ❌ 문제점 (단가 상세 정보)
- 프론트엔드 단가관리 화면에서 요구하는 **단가 계산 필드** 대부분 누락
- 현재 `price_histories` 테이블은 **단순 가격 이력**만 저장 (`price` 단일 필드)
- 프론트엔드는 **원가 계산, 마진 관리, 리비전 관리** 기능 필요
---
## 2. 테이블 스키마 변경 요청
### 2.1 `price_histories` 테이블 필드 추가
```sql
ALTER TABLE price_histories ADD COLUMN purchase_price DECIMAL(15,4) NULL COMMENT '매입단가(입고가)';
ALTER TABLE price_histories ADD COLUMN processing_cost DECIMAL(15,4) NULL COMMENT '가공비';
ALTER TABLE price_histories ADD COLUMN loss_rate DECIMAL(5,2) NULL COMMENT 'LOSS율(%)';
ALTER TABLE price_histories ADD COLUMN rounding_rule ENUM('round','ceil','floor') DEFAULT 'round' COMMENT '반올림 규칙';
ALTER TABLE price_histories ADD COLUMN rounding_unit INT DEFAULT 1 COMMENT '반올림 단위(1,10,100,1000)';
ALTER TABLE price_histories ADD COLUMN margin_rate DECIMAL(5,2) NULL COMMENT '마진율(%)';
ALTER TABLE price_histories ADD COLUMN sales_price DECIMAL(15,4) NULL COMMENT '판매단가';
ALTER TABLE price_histories ADD COLUMN supplier VARCHAR(255) NULL COMMENT '공급업체';
ALTER TABLE price_histories ADD COLUMN author VARCHAR(100) NULL COMMENT '작성자';
ALTER TABLE price_histories ADD COLUMN receive_date DATE NULL COMMENT '입고일';
ALTER TABLE price_histories ADD COLUMN note TEXT NULL COMMENT '비고';
ALTER TABLE price_histories ADD COLUMN revision_number INT DEFAULT 0 COMMENT '리비전 번호';
ALTER TABLE price_histories ADD COLUMN is_final BOOLEAN DEFAULT FALSE COMMENT '최종 확정 여부';
ALTER TABLE price_histories ADD COLUMN finalized_at DATETIME NULL COMMENT '확정일시';
ALTER TABLE price_histories ADD COLUMN finalized_by INT NULL COMMENT '확정자 ID';
ALTER TABLE price_histories ADD COLUMN status ENUM('draft','active','inactive','finalized') DEFAULT 'draft' COMMENT '상태';
```
### 2.2 기존 `price` 필드 처리 방안
**옵션 A (권장)**: `price` 필드를 `sales_price`로 마이그레이션
```sql
UPDATE price_histories SET sales_price = price WHERE price_type_code = 'SALE';
UPDATE price_histories SET purchase_price = price WHERE price_type_code = 'PURCHASE';
-- 이후 price 필드 deprecated 또는 삭제
```
**옵션 B**: `price` 필드 유지, 새 필드와 병행 사용
- 기존 로직 호환성 유지
- 점진적 마이그레이션
---
## 3. API 엔드포인트 수정 요청
### 3.1 `POST /api/v1/pricing/upsert` 수정
**현재 Request Body:**
```json
{
"item_type_code": "PRODUCT",
"item_id": 10,
"price_type_code": "SALE",
"client_group_id": 1,
"price": 50000.00,
"started_at": "2025-01-01",
"ended_at": "2025-12-31"
}
```
**요청 Request Body:**
```json
{
"item_type_code": "PRODUCT",
"item_id": 10,
"client_group_id": 1,
"purchase_price": 45000,
"processing_cost": 5000,
"loss_rate": 3.5,
"rounding_rule": "round",
"rounding_unit": 100,
"margin_rate": 20.0,
"sales_price": 60000,
"supplier": "ABC공급",
"author": "홍길동",
"receive_date": "2025-01-01",
"started_at": "2025-01-01",
"ended_at": null,
"note": "2025년 1분기 단가",
"is_revision": false,
"revision_reason": "가격 인상"
}
```
### 3.2 `GET /api/v1/pricing` 수정 (목록 조회)
**현재 Response:**
```json
{
"data": {
"data": [
{
"id": 1,
"item_type_code": "PRODUCT",
"item_id": 10,
"price_type_code": "SALE",
"price": 50000,
"started_at": "2025-01-01"
}
]
}
}
```
**요청 Response:**
```json
{
"data": {
"data": [
{
"id": 1,
"item_type_code": "PRODUCT",
"item_id": 10,
"item_code": "SCREEN-001",
"item_name": "스크린 셔터 기본형",
"specification": "표준형",
"unit": "SET",
"purchase_price": 45000,
"processing_cost": 5000,
"loss_rate": 3.5,
"margin_rate": 20.0,
"sales_price": 60000,
"started_at": "2025-01-01",
"ended_at": null,
"status": "active",
"revision_number": 1,
"is_final": false,
"supplier": "ABC공급",
"created_at": "2025-01-01 10:00:00"
}
]
}
}
```
**핵심 변경**: 품목 정보 JOIN 필요 (`item_masters` 또는 `products`/`materials` 테이블)
---
## 4. 신규 API 엔드포인트 요청
### 4.1 품목 기반 단가 현황 조회 (신규)
**용도**: 품목 마스터 기준으로 단가 등록/미등록 현황 조회
**Endpoint**: `GET /api/v1/pricing/by-items`
**Query Parameters:**
| 파라미터 | 타입 | 설명 |
|---------|------|------|
| `item_type_code` | string | 품목 유형 (FG, PT, SM, RM, CS) |
| `search` | string | 품목코드/품목명 검색 |
| `status` | string | `all`, `registered`, `not_registered` |
| `size` | int | 페이지당 항목 수 |
| `page` | int | 페이지 번호 |
**Response:**
```json
{
"data": {
"data": [
{
"item_id": 1,
"item_code": "SCREEN-001",
"item_name": "스크린 셔터 기본형",
"item_type": "FG",
"specification": "표준형",
"unit": "SET",
"pricing_id": null,
"has_pricing": false,
"purchase_price": null,
"sales_price": null,
"margin_rate": null,
"status": "not_registered"
},
{
"item_id": 2,
"item_code": "GR-001",
"item_name": "가이드레일 130×80",
"item_type": "PT",
"specification": "130×80×2438",
"unit": "EA",
"pricing_id": 5,
"has_pricing": true,
"purchase_price": 45000,
"sales_price": 60000,
"margin_rate": 20.0,
"effective_date": "2025-01-01",
"status": "active",
"revision_number": 1,
"is_final": false
}
],
"stats": {
"total_items": 100,
"registered": 45,
"not_registered": 55,
"finalized": 10
}
}
}
```
### 4.2 단가 이력 조회 (신규)
**Endpoint**: `GET /api/v1/pricing/{id}/revisions`
**Response:**
```json
{
"data": [
{
"revision_number": 2,
"revision_date": "2025-06-01",
"revision_by": "김철수",
"revision_reason": "원자재 가격 인상",
"previous_purchase_price": 40000,
"previous_sales_price": 55000,
"new_purchase_price": 45000,
"new_sales_price": 60000
},
{
"revision_number": 1,
"revision_date": "2025-01-01",
"revision_by": "홍길동",
"revision_reason": "최초 등록",
"previous_purchase_price": null,
"previous_sales_price": null,
"new_purchase_price": 40000,
"new_sales_price": 55000
}
]
}
```
### 4.3 단가 확정 (신규)
**Endpoint**: `POST /api/v1/pricing/{id}/finalize`
**Request Body:**
```json
{
"finalize_reason": "2025년 1분기 단가 확정"
}
```
**Response:**
```json
{
"data": {
"id": 5,
"is_final": true,
"finalized_at": "2025-12-08 14:30:00",
"finalized_by": 1,
"status": "finalized"
},
"message": "단가가 최종 확정되었습니다."
}
```
---
## 5. 프론트엔드 타입 참조
프론트엔드에서 사용하는 타입 정의 (`src/components/pricing/types.ts`):
```typescript
interface PricingData {
id: string;
itemId: string;
itemCode: string;
itemName: string;
itemType: string;
specification?: string;
unit: string;
// 단가 정보
effectiveDate: string; // started_at
receiveDate?: string; // receive_date
author?: string; // author
purchasePrice?: number; // purchase_price
processingCost?: number; // processing_cost
loss?: number; // loss_rate
roundingRule?: RoundingRule; // rounding_rule
roundingUnit?: number; // rounding_unit
marginRate?: number; // margin_rate
salesPrice?: number; // sales_price
supplier?: string; // supplier
note?: string; // note
// 리비전 관리
currentRevision: number; // revision_number
isFinal: boolean; // is_final
revisions?: PricingRevision[];
finalizedDate?: string; // finalized_at
finalizedBy?: string; // finalized_by
status: PricingStatus; // status
}
```
---
## 6. 우선순위
| 순위 | 항목 | 중요도 |
|------|------|--------|
| 1 | 테이블 스키마 변경 (필드 추가) | 🔴 필수 |
| 2 | `POST /pricing/upsert` 수정 | 🔴 필수 |
| 3 | `GET /pricing/by-items` 신규 | 🔴 필수 |
| 4 | `GET /pricing` 응답 확장 | 🟡 중요 |
| 5 | `GET /pricing/{id}/revisions` 신규 | 🟡 중요 |
| 6 | `POST /pricing/{id}/finalize` 신규 | 🟢 권장 |
---
## 7. 질문/협의 사항
1. **기존 price 필드 처리**: 마이그레이션 vs 병행 사용?
2. **리비전 테이블 분리**: `price_history_revisions` 별도 테이블 vs 현재 테이블 확장?
3. **품목 연결**: `item_masters` 테이블 사용 vs `products`/`materials` 각각 JOIN?
---
**연락처**: 프론트엔드 개발팀
**관련 파일**: `src/components/pricing/types.ts`

View File

@@ -0,0 +1,139 @@
# 단가관리 API 연동 체크리스트
> **작성일**: 2025-12-09
> **목표**: 백엔드 API 연동 완료 후 수동 검수
---
## Phase 1: 목록 페이지 API 연동 ✅
### 1.1 데이터 조회
- [x] `GET /api/v1/pricing` 호출로 단가 목록 조회
- [x] API 응답 → `PricingListItem` 타입 변환
- [x] 품목 정보 표시 (item_type_code, item_id 기반)
- [x] 페이지네이션 적용 (size=100)
### 1.2 필터/검색
- [x] 품목 유형 필터 (`item_type_code`) - API 지원됨
- [x] 상태 필터 (`status`) - API 지원됨
- [x] 검색어 필터 (`q`) - API 지원됨
---
## Phase 2: 등록/수정 페이지 API 연동 ✅
### 2.1 등록 (`POST /api/v1/pricing`)
- [x] 폼 데이터 → API 요청 형식 변환 (`actions.ts: transformFrontendToApi`)
- [x] 성공/실패 토스트 메시지 (PricingFormClient 기존 로직)
- [x] 등록 후 목록으로 리다이렉트
### 2.2 수정 (`PUT /api/v1/pricing/{id}`)
- [x] 기존 데이터 로드 (`GET /api/v1/pricing/{id}`) - `getPricingById()`
- [x] 폼 데이터 → API 요청 형식 변환
- [x] 수정 후 목록으로 리다이렉트
### 2.3 자동 계산
- [x] 판매단가 자동 계산 (매입단가 + 가공비) × (1 + LOSS) × (1 + 마진율) - 백엔드에서 처리
- [x] 반올림 규칙 적용 - 백엔드에서 처리
**생성된 파일:**
- `src/components/pricing/actions.ts` - 서버 액션 모음
---
## Phase 3: 삭제/확정 API 연동 ✅
### 3.1 삭제 (`DELETE /api/v1/pricing/{id}`)
- [x] 삭제 서버 액션 구현 (`deletePricing` in actions.ts)
- [ ] 삭제 확인 다이얼로그 (목록 페이지에서 필요 시 추가)
- [ ] 삭제 후 목록 새로고침 (목록 페이지에서 필요 시 추가)
### 3.2 확정 (`POST /api/v1/pricing/{id}/finalize`)
- [x] 확정 서버 액션 구현 (`finalizePricing` in actions.ts)
- [x] 확정 확인 다이얼로그 (PricingFinalizeDialog 기존 UI 활용)
- [x] 확정 후 목록으로 리다이렉트
- [x] PricingFormClient에 onFinalize prop 추가
---
## Phase 4: 이력 조회 API 연동 ✅
### 4.1 리비전 이력 (`GET /api/v1/pricing/{id}/revisions`)
- [x] 이력 다이얼로그 UI (PricingHistoryDialog 기존 UI 활용)
- [x] 변경 전/후 스냅샷 표시 (revisions 배열의 previousData 사용)
- [x] 별도 API 호출 함수 구현 (`getPricingRevisions` in actions.ts)
- [x] GET /pricing/{id}에서 revisions 함께 로드 (백엔드에서 with() 사용)
---
## 테스트 URL
- 목록: http://localhost:3000/sales/pricing-management
- 등록: http://localhost:3000/sales/pricing-management/create
- 수정: http://localhost:3000/sales/pricing-management/{id}/edit
---
## Phase 5: 품목 목록 + 단가 병합 ✅
> **배경**: 현재는 단가 등록된 품목만 표시됨. sam-design처럼 품목 전체 목록에 단가 정보를 병합해야 함.
### 5.1 품목 목록 API 호출
- [x] `GET /api/v1/items` 통합 품목 목록 호출 (size=500)
- [x] API 응답 → 프론트엔드 타입 변환 (ItemApiData)
- [x] 품목 유형별 필터 지원 (FG, PT, SM, RM, CS) - items API에서 type 파라미터 지원
### 5.2 데이터 병합 로직
- [x] 품목 목록 + 단가 목록 병합 함수 구현 (`mergeItemsWithPricing`)
- [x] 품목별 단가 유무 판별 (Map 활용 O(1) 조회)
- [x] 단가 미등록 품목 → `status: 'not_registered'` 표시
### 5.3 목록 페이지 수정
- [x] `page.tsx` - 품목 API + 단가 API 병렬 호출 (`Promise.all`)
- [x] `PricingListClient` - 병합된 데이터 표시
- [x] 미등록 품목 → "등록" 버튼 표시 (+ 아이콘)
- [x] 등록된 품목 → "수정" 버튼 표시 (연필 아이콘)
- [x] `itemTypeCode` 파라미터 추가 (PRODUCT/MATERIAL 구분)
### 5.4 통계 카드 수정
- [x] 전체 품목 수 (품목 목록 기준) - `totalAll`
- [x] 단가 등록 수 - `registered` (status !== 'not_registered')
- [x] 미등록 수 - `notRegistered` (totalAll - registered)
- [x] 확정 수 - `finalized` (isFinal === true)
---
## 진행 상태
| Phase | 상태 | 완료일 |
|-------|------|--------|
| Phase 1 | ✅ 완료 | 2025-12-09 |
| Phase 2 | ✅ 완료 | 2025-12-09 |
| Phase 3 | ✅ 완료 | 2025-12-09 |
| Phase 4 | ✅ 완료 | 2025-12-09 |
| Phase 5 | ✅ 완료 | 2025-12-09 |
---
## 생성/수정된 파일 요약
### 신규 생성
- `src/components/pricing/actions.ts` - 서버 액션 (CRUD + 확정 + 이력 조회)
### 수정된 파일
- `src/app/[locale]/(protected)/sales/pricing-management/page.tsx` - API 연동 (목록)
- `src/app/[locale]/(protected)/sales/pricing-management/create/page.tsx` - API 연동 (등록)
- `src/app/[locale]/(protected)/sales/pricing-management/[id]/edit/page.tsx` - API 연동 (수정+확정)
- `src/components/pricing/PricingFormClient.tsx` - onFinalize prop 추가
- `src/components/pricing/index.ts` - actions export 추가
---
## 수동 검수 체크리스트
- [ ] 목록 페이지에서 데이터 정상 조회
- [ ] 품목 클릭 → 수정 페이지 이동 정상
- [ ] 단가 수정 후 저장 정상
- [ ] 단가 확정 기능 정상
- [ ] 이력 조회 다이얼로그 정상
- [ ] 신규 단가 등록 정상 (품목 선택 후)

View File

@@ -0,0 +1,143 @@
# 거래처 관리 - 다음 세션 컨텍스트
> **작성일**: 2025-12-09
> **목적**: 다음 세션에서 이어서 작업할 내용 정리
---
## 1. 완료된 작업 (2025-12-09)
### 1.1 백엔드 API 2차 필드 추가 ✅ 완료
- **커밋**: `d164bb4` - feat: [client] 거래처 API 2차 필드 추가
- **마이그레이션**: `2025_12_04_205603_add_extended_fields_to_clients_table.php`
- **is_active 타입 변경**: `5f20005` - CHAR(1) → TINYINT(1) Boolean
### 1.2 프론트엔드 API 연동 ✅ 완료
| 구성 요소 | 파일 | 상태 |
|----------|------|------|
| **Proxy** | `/api/proxy/[...path]/route.ts` | ✅ 완료 |
| **Hook** | `src/hooks/useClientList.ts` | ✅ 완료 (2차 필드 포함) |
| **목록** | `sales/client-management-sales-admin/page.tsx` | ✅ API 연동 |
| **등록** | `sales/client-management-sales-admin/new/page.tsx` | ✅ API 연동 |
| **수정** | `sales/client-management-sales-admin/[id]/edit/page.tsx` | ✅ API 연동 |
| **상세** | `sales/client-management-sales-admin/[id]/page.tsx` | ✅ API 연동 |
| **등록폼** | `components/clients/ClientRegistration.tsx` | ✅ 완료 |
| **상세뷰** | `components/clients/ClientDetail.tsx` | ✅ 완료 |
### 1.3 is_active Boolean 변경 대응 ✅ 완료
```typescript
// useClientList.ts 수정 내역
is_active: boolean; // 타입 변경 ("Y"|"N" → boolean)
status: api.is_active ? "활성" : "비활성", // 변환 로직
is_active: form.isActive, // 전송 시 boolean 그대로
```
### 1.4 기획 미확정 필드 - 개발 보류 ✅ 확정
**결정일**: 2025-12-09
**결정사항**: 기획에서 확정되지 않은 필드에 대해서는 **개발 보류**
**숨김 처리된 섹션** (등록/수정 폼):
| 섹션 | 필드 | 상태 |
|------|------|------|
| 발주처 설정 | 계정ID, 비밀번호, 매입결제일, 매출결제일 | ❌ 개발보류 |
| 약정 세금 | 약정 여부, 금액, 시작/종료일 | ❌ 개발보류 |
| 악성채권 정보 | 악성채권 여부, 금액, 발생/만료일, 진행상태 | ❌ 개발보류 |
**목록 테이블에서도 제외** (스크린샷 디자인과 다름 확인):
- 매입 결제일 컬럼 ❌ 제외
- 매출 결제일 컬럼 ❌ 제외
- 악성채권 컬럼/배지 ❌ 제외
> ⚠️ 백엔드 API는 이미 지원됨 (nullable 필드)
> 기획 확정 후 주석 해제하면 바로 사용 가능
> 프론트엔드 파일: `src/components/clients/ClientRegistration.tsx`
---
## 2. 현재 거래처 등록 폼 구조
```
1. 기본 정보 ✅ 활성
- 사업자등록번호 (필수)
- 거래처 코드 (자동생성)
- 거래처명 (필수)
- 대표자명 (필수)
- 거래처 유형 (매입/매출/매입매출)
- 업태, 종목
2. 연락처 정보 ✅ 활성
- 주소
- 전화번호, 모바일, 팩스
- 이메일
3. 담당자 정보 ✅ 활성
- 담당자명, 담당자 전화
- 시스템 관리자
4. 발주처 설정 ❌ 숨김 (기획 미확정)
5. 약정 세금 ❌ 숨김 (기획 미확정)
6. 악성채권 정보 ❌ 숨김 (기획 미확정)
7. 기타 정보 ✅ 활성
- 메모
- 상태 (활성/비활성)
```
---
## 3. 다음 작업 후보
### 3.1 거래처 관리 관련
- [ ] 거래처 그룹 기능 구현 (client-groups API 이미 있음)
- [ ] 엑셀 내보내기/가져오기
- [ ] 발주처/약정세금/악성채권 섹션 활성화 (기획 확정 시)
### 3.2 다른 기능
- [ ] 견적 관리 페이지 구현
- [ ] 단가 관리 페이지 완성
- [ ] 기타 요청 사항
---
## 4. 참고 파일
### 프론트엔드 (sam-react-prod)
```
src/
├── hooks/useClientList.ts # API 훅 + 타입 정의
├── components/clients/
│ ├── ClientRegistration.tsx # 등록/수정 폼
│ └── ClientDetail.tsx # 상세 보기
└── app/[locale]/(protected)/sales/client-management-sales-admin/
├── page.tsx # 목록
├── new/page.tsx # 등록
├── [id]/page.tsx # 상세
└── [id]/edit/page.tsx # 수정
```
### 백엔드 (sam-api)
```
app/
├── Http/Controllers/Api/V1/ClientController.php
├── Http/Requests/Client/
│ ├── ClientStoreRequest.php
│ └── ClientUpdateRequest.php
├── Models/Orders/Client.php
├── Services/ClientService.php
└── Swagger/v1/ClientApi.php
```
---
## 5. 다음 세션에서 말할 내용
```
버디 안녕~! 지난번에 거래처 관리 API 연동 완료했어.
- 백엔드 2차 필드 추가 확인 완료
- 프론트엔드 API 연동 완료 (목록/등록/수정/상세)
- is_active Boolean 변경 대응 완료
- 발주처/약정세금/악성채권 섹션은 기획 미확정으로 숨김 처리
오늘은 [다음 작업 내용] 진행하자~!
```