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:
272
claudedocs/[REF] all-pages-test-urls.md
Normal file
272
claudedocs/[REF] all-pages-test-urls.md
Normal 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 (전체 페이지 통합)
|
||||
@@ -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` | 📋 견적관리/거래처관리 마이그레이션 계획 |
|
||||
|
||||
---
|
||||
|
||||
196
claudedocs/hr/[IMPL-2025-12-06] vacation-management-checklist.md
Normal file
196
claudedocs/hr/[IMPL-2025-12-06] vacation-management-checklist.md
Normal 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 사용 |
|
||||
@@ -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 플로우 테스트
|
||||
|
||||
---
|
||||
|
||||
## 다음 단계
|
||||
|
||||
- [ ] 각 이슈별 수정 작업 진행
|
||||
- [ ] 실제 데이터로 등록/수정 흐름 테스트
|
||||
- [ ] 누락된 필드 추가 발견 시 문서 업데이트
|
||||
@@ -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 모델
|
||||
@@ -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 요청 문서 업데이트**: 조립부품 관련 백엔드 확인 사항 추가
|
||||
|
||||
### 남은 작업
|
||||
- [ ] 백엔드와 조립부품 데이터 저장/반환 확인 논의
|
||||
- [ ] 실제 테스트 후 검증
|
||||
@@ -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
|
||||
```
|
||||
@@ -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` |
|
||||
@@ -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/` - 현재 동적 폼
|
||||
@@ -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
|
||||
|
||||
@@ -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`
|
||||
@@ -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 추가
|
||||
|
||||
---
|
||||
|
||||
## 수동 검수 체크리스트
|
||||
|
||||
- [ ] 목록 페이지에서 데이터 정상 조회
|
||||
- [ ] 품목 클릭 → 수정 페이지 이동 정상
|
||||
- [ ] 단가 수정 후 저장 정상
|
||||
- [ ] 단가 확정 기능 정상
|
||||
- [ ] 이력 조회 다이얼로그 정상
|
||||
- [ ] 신규 단가 등록 정상 (품목 선택 후)
|
||||
143
claudedocs/sales/[NEXT-2025-12-09] client-session-context.md
Normal file
143
claudedocs/sales/[NEXT-2025-12-09] client-session-context.md
Normal 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 변경 대응 완료
|
||||
- 발주처/약정세금/악성채권 섹션은 기획 미확정으로 숨김 처리
|
||||
|
||||
오늘은 [다음 작업 내용] 진행하자~!
|
||||
```
|
||||
Reference in New Issue
Block a user