Files
sam-docs/dev/dev_plans/quote-management-8issues-plan.md
권혁성 db63fcff85 refactor: [docs] 팀별 폴더 구조 재편 (공유/개발/프론트/기획)
- 개발팀 전용 폴더 dev/ 생성 (standards, guides, quickstart, changes, deploys, data, history, dev_plans 이동)
- 프론트엔드 전용 폴더 frontend/ 생성 (api/ → frontend/api-specs/)
- 기획팀 폴더 requests/ 생성
- plans/ → dev/dev_plans/ 이름 변경
- README.md 신규 (사람용 안내), INDEX.md 재작성 (Claude Code용)
- resources.md 신규 (노션 링크용, assets/brochure 이관 예정)
- CURRENT_WORKS.md 삭제, TODO.md → dev/ 이동
- 전체 참조 경로 업데이트

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:46:03 +09:00

529 lines
17 KiB
Markdown

# 견적 관리 8개 이슈 수정 계획
> **작성일**: 2026-01-06
> **목적**: 견적 관리 화면 8개 이슈 수정 (리스트/상세/수정 화면)
> **기준 문서**: react/src/components/quotes/ 컴포넌트
> **상태**: 🔄 진행중
---
## 📍 현재 진행 상태
| 항목 | 내용 |
|------|------|
| **마지막 완료 작업** | 이슈 #1 분석 완료 (백엔드 API 정상 확인) |
| **다음 작업** | 이슈 #1 분석 결과 사용자 컨펌 대기 |
| **진행률** | 0/8 (0%) - 이슈 #1 분석 완료, 컨펌 대기 |
| **마지막 업데이트** | 2026-01-06 |
---
## 1. 개요
### 1.1 배경
견적 관리 시스템에서 담당자, 연락처, 비고, 단위 등의 필드가 제대로 표시되지 않거나 잘못된 값이 표시되는 8개 이슈 발견
### 1.2 핵심 원칙
```
┌─────────────────────────────────────────────────────────────────┐
│ 🎯 핵심 원칙 │
├─────────────────────────────────────────────────────────────────┤
│ - 한 이슈씩 순차적으로 수정 (사용자 컨펌 후 다음 진행) │
│ - 프론트엔드 필드 매핑 일관성 유지 │
│ - 백엔드 API 응답 데이터 확인 후 프론트엔드 수정 │
│ - 커밋은 모든 작업 완료 후 일괄 진행 │
└─────────────────────────────────────────────────────────────────┘
```
### 1.3 🔴 작업 진행 절차 (필수)
```
┌─────────────────────────────────────────────────────────────────┐
│ 📋 각 이슈별 작업 흐름 │
├─────────────────────────────────────────────────────────────────┤
│ 1. 분석 → 분석 결과 보고 │
│ 2. 사용자 컨펌 대기 (테스트 가능한 정보 제공) │
│ 3. 컨펌 후 → 수정 작업 진행 │
│ 4. 수정 완료 → 결과 보고 (테스트 방법 포함) │
│ 5. 사용자 테스트 & 컨펌 │
│ 6. 컨펌 완료 → 다음 이슈 진행 │
│ │
│ ⚠️ 사용자 승인 없이 다음 단계 진행 금지! │
└─────────────────────────────────────────────────────────────────┘
```
**각 이슈 완료 보고 형식:**
```markdown
## 이슈 #N 완료 보고
**작업 내용:**
- 수정한 파일과 라인
- 변경 전/후 코드
**테스트 방법:**
- 테스트 URL
- 확인 포인트
**기대 결과:**
- 변경 전: [문제 상황]
- 변경 후: [예상 결과]
**다음 작업 진행할까요?**
```
### 1.4 핵심 발견: 필드명 불일치 문제
**Quote 인터페이스 (types.ts:85-115):**
```typescript
export interface Quote {
managerName?: string; // ← 담당자
managerContact?: string; // ← 연락처
description?: string; // ← 비고
// ...
}
```
**문제점:**
- `QuoteDocument.tsx`, `QuoteCalculationReport.tsx`에서 `quote.manager`, `quote.contact` 사용
- Quote 인터페이스에는 `managerName`, `managerContact`로 정의됨
- **필드명 불일치로 인해 항상 undefined → '-' 표시**
---
## 2. 대상 범위
### 2.1 이슈 목록
| # | 이슈 | 화면 | 상태 | 근본 원인 |
|---|------|------|:----:|----------|
| 1 | 담당자, 비고 컬럼 미표시 | 리스트 | ⏳ | API 응답 확인 필요 |
| 2 | 담당자, 연락처 미표시 + 단위 "set" | 상세 (/8) | ⏳ | 필드명 불일치 |
| 3 | 담당자, 연락처 미표시 + 총 수량 계산 | 상세 견적서 (/9) | ⏳ | 필드명 불일치 + 하드코딩 |
| 4 | 단위가 모두 "개소"로 표시 | 품목내역 | ⏳ | item.unit 누락 |
| 5 | 담당자/연락처/단위/수량 이슈 | 산출 내역서 | ⏳ | 필드명 불일치 + 하드코딩 |
| 6 | 세부산출 vs 소요자재 동일 | 산출 내역서 | ⏳ | leaf 노드 분리 미적용 |
| 7 | 작성자/담당자/연락처/비고 미표시 | 수정 (/edit) | ⏳ | 데이터 로딩 확인 |
| 8 | 1개 → 14개 견적 표시 | 자동 견적 산출 | ⏳ | state 초기화 누락 |
---
## 3. 상세 분석 및 수정 코드
### 3.1 이슈 #1: 리스트 화면 담당자/비고 미표시
**파일**: `react/src/components/quotes/QuoteManagementClient.tsx`
**현재 코드 (Line 421, 424):**
```typescript
<TableCell>{quote.managerName || '-'}</TableCell>
// ...
<TableCell>
<div className="max-w-[200px] line-clamp-2 text-sm">
{quote.description || '-'}
</div>
</TableCell>
```
**분석:**
- 코드 자체는 정확함 (`managerName`, `description` 사용)
- API 응답에서 `manager``managerName` 변환 확인 필요
**변환 함수 (types.ts:254-256, 266):**
```typescript
// transformApiToFrontend 함수
managerName: apiData.manager || apiData.manager_name || undefined,
managerContact: apiData.contact || apiData.manager_contact || undefined,
// ...
description: apiData.remarks || apiData.description || undefined,
```
**확인 필요:**
- API 응답에 `manager`, `contact`, `remarks` 필드가 포함되는지 확인
- 백엔드 QuoteResource에서 해당 필드 반환 여부 확인
---
### 3.2 이슈 #2: 상세 화면 담당자/연락처 미표시 + 단위 "set"
**파일**: `react/src/components/quotes/QuoteDocument.tsx`
**현재 코드 (Line 271-278):**
```typescript
<th>담당자</th>
<td>{quote.manager || '-'}</td> // ❌ 잘못된 필드명
// ...
<th>연락처</th>
<td>{quote.contact || '-'}</td> // ❌ 잘못된 필드명
```
**수정 코드:**
```typescript
<th>담당자</th>
<td>{quote.managerName || '-'}</td> // ✅ 올바른 필드명
// ...
<th>연락처</th>
<td>{quote.managerContact || '-'}</td> // ✅ 올바른 필드명
```
**단위 문제 (Line 34-42):**
```typescript
// 현재 코드 - 이미 올바름
const quoteItems = quote.items?.map((item, index) => ({
// ...
unit: item.unit || '', // ✅ 각 품목의 단위 사용
})) || [];
```
---
### 3.3 이슈 #3: 상세 견적서 총 수량 계산
**파일**: `react/src/components/quotes/QuoteDocument.tsx`
**현재 코드 (Line 345):**
```typescript
<th> 수량</th>
<td>{quote.items.reduce((sum, item) => sum + (item.quantity || 0), 0)}개소</td>
// ❌ "개소" 하드코딩
```
**수정 코드:**
```typescript
<th> 수량</th>
<td>{quote.items.reduce((sum, item) => sum + (item.quantity || 0), 0)} {quote.items[0]?.unit || 'EA'}</td>
// ✅ 첫 번째 품목의 단위 사용 또는 기본값 EA
```
---
### 3.4 이슈 #4: 품목내역 단위 "개소" 고정
**파일**: `react/src/components/quotes/QuoteDocument.tsx`
**현재 코드 (Line 383):**
```typescript
<td style={{ textAlign: 'center' }}>{item.unit}</td>
```
**분석:**
- 코드는 `item.unit`을 사용하고 있어 올바름
- 문제는 `item.unit` 값이 API에서 오지 않거나 비어 있음
- **확인 필요**: API 응답의 `unit` 필드 확인
---
### 3.5 이슈 #5: 산출 내역서 담당자/연락처/단위/수량
**파일**: `react/src/components/quotes/QuoteCalculationReport.tsx`
**현재 코드 (Line 307-314):**
```typescript
<th>담당자</th>
<td>{quote.manager || '-'}</td> // ❌ 잘못된 필드명
// ...
<th>연락처</th>
<td>{quote.contact || '-'}</td> // ❌ 잘못된 필드명
```
**수정 코드:**
```typescript
<th>담당자</th>
<td>{quote.managerName || '-'}</td> // ✅ 올바른 필드명
// ...
<th>연락처</th>
<td>{quote.managerContact || '-'}</td> // ✅ 올바른 필드명
```
**단위 하드코딩 (Line 394):**
```typescript
// 현재 코드
<td style={{ textAlign: 'center' }}>SET</td> // ❌ 하드코딩
// 수정 코드
<td style={{ textAlign: 'center' }}>{item.unit || 'SET'}</td> // ✅ 동적 + 기본값
```
**수량 기준 문제:**
- 현재: 1개 기준 수량 표시
- 필요: 세트 수량(예: 10개) 기준 표시
- **확인 필요**: 비즈니스 로직 명확화
---
### 3.6 이슈 #6: 세부산출내역 vs 소요자재내역 분리
**파일**: `react/src/components/quotes/QuoteCalculationReport.tsx`
**현재 상태 (Line 45-55):**
```typescript
// 소요자재 내역 - 실제 BOM 자재 데이터 사용
const materialItems = quote.bomMaterials?.map((material, index) => ({
// ...
})) || [];
```
**문제:**
- 세부산출내역과 소요자재내역이 동일한 `bomMaterials` 사용
- 소요자재는 leaf 노드만 표시해야 함
**수정 방향:**
```typescript
// 세부산출내역: 전체 BOM 항목
const detailItems = quote.bomMaterials || [];
// 소요자재내역: leaf 노드만 (has_children = false 또는 별도 필드)
const materialItems = quote.bomMaterials?.filter(m => !m.hasChildren) || [];
// 또는 백엔드에서 별도 필드로 제공 (leafMaterials)
```
**참고**: 이전 세션에서 백엔드 `getBomLeafMaterials()` 함수 추가됨
---
### 3.7 이슈 #7: 수정 화면 필드 미표시
**파일**: `react/src/components/quotes/QuoteRegistration.tsx`
**폼 필드 코드 (Line 593-683):**
```typescript
// 작성자 (Line 593-600)
<FormField label="작성자" htmlFor="writer">
<Input id="writer" value={formData.writer} disabled className="bg-gray-50" />
</FormField>
// 담당자 (Line 644-651)
<FormField label="발주 담당자" htmlFor="manager">
<Input id="manager" value={formData.manager} onChange={...} />
</FormField>
// 연락처 (Line 653-659)
<FormField label="연락처" htmlFor="contact">
<Input id="contact" value={formData.contact} onChange={...} />
</FormField>
// 비고 (Line 675-683)
<FormField label="비고" htmlFor="remarks">
<Textarea id="remarks" value={formData.remarks} onChange={...} />
</FormField>
```
**데이터 로딩 (types.ts:536-541):**
```typescript
// transformApiDataToFormData 함수
manager: apiData.manager || apiData.manager_name || '',
contact: apiData.contact || apiData.manager_contact || '',
// ...
remarks: apiData.remarks || apiData.description || '',
```
**분석:**
- 폼 필드 코드는 정상
- 변환 함수도 정상
- **확인 필요**:
1. API 응답에 `manager`, `contact`, `remarks` 필드 포함 여부
2. `editingQuote` prop이 올바르게 전달되는지
---
### 3.8 이슈 #8: 자동 견적 산출 14개 표시
**파일**: `react/src/components/quotes/QuoteRegistration.tsx`
**현재 상태:**
```typescript
// calculationResults state (추정)
const [calculationResults, setCalculationResults] = useState<...>(null);
```
**문제:**
- 새 계산 시 이전 결과가 초기화되지 않음
- 또는 API 응답에 중복 데이터 포함
**수정 방향:**
```typescript
// handleAutoCalculate 함수 시작 부분에 추가
const handleAutoCalculate = async () => {
// 이전 결과 초기화
setCalculationResults(null);
// ... 기존 로직
};
```
---
## 4. 작업 순서 및 체크리스트
| 순서 | 이슈 | 작업 | 파일 | 라인 | 난이도 |
|:----:|:----:|------|------|:----:|:------:|
| 1 | #1 | API 응답 확인 + 필요시 수정 | 백엔드 확인 | - | 🟡 |
| 2 | #2 | `manager``managerName` | QuoteDocument.tsx | 272, 278 | 🟢 |
| 3 | #3 | 총 수량 단위 동적화 | QuoteDocument.tsx | 345 | 🟢 |
| 4 | #4 | item.unit 확인 | API 확인 | - | 🟡 |
| 5 | #5 | 필드명 + 단위 수정 | QuoteCalculationReport.tsx | 308, 314, 394 | 🟢 |
| 6 | #6 | leaf 노드 분리 | QuoteCalculationReport.tsx | 45-55 | 🟡 |
| 7 | #7 | 데이터 로딩 확인 | API 확인 | - | 🟡 |
| 8 | #8 | state 초기화 추가 | QuoteRegistration.tsx | handleAutoCalculate | 🟢 |
---
## 5. 진행 기록
### 5.1 분석 단계 (완료)
**2026-01-06:**
- ✅ QuoteManagementClient.tsx 분석 (Line 410-440)
- ✅ QuoteDocument.tsx 분석 (Line 21-42, 265-285, 335-405)
- ✅ QuoteCalculationReport.tsx 분석 (Line 25-55, 300-320, 385-470)
- ✅ QuoteRegistration.tsx 분석 (Line 593-683)
- ✅ types.ts 분석 (인터페이스, 변환 함수)
- ✅ 계획 문서 작성
### 5.2 이슈별 진행 상태
| 이슈 | 상태 | 시작 | 완료 | 컨펌 |
|:----:|:----:|:----:|:----:|:----:|
| #1 | 🔍 분석완료 | 2026-01-06 | - | ⏳ 대기 |
| #2 | ⏳ 대기 | - | - | - |
| #3 | ⏳ 대기 | - | - | - |
| #4 | ⏳ 대기 | - | - | - |
| #5 | ⏳ 대기 | - | - | - |
| #6 | ⏳ 대기 | - | - | - |
| #7 | ⏳ 대기 | - | - | - |
| #8 | ⏳ 대기 | - | - | - |
### 5.3 이슈 #1 분석 결과 (2026-01-06)
**분석 내용:**
- 백엔드 API 응답 확인
- 프론트엔드 코드 검토
**백엔드 확인 결과:** ✅ 정상
| 파일 | 라인 | 필드 | 상태 |
|------|------|------|:----:|
| `api/app/Swagger/v1/QuoteApi.php` | 21-22, 48 | manager, contact, remarks | ✅ |
| `api/app/Models/Quote/Quote.php` | 30-31, 63 | fillable 정의 | ✅ |
| `api/app/Services/Quote/QuoteService.php` | 212-213, 243 | 저장/반환 | ✅ |
**프론트엔드 확인 결과:**
| 파일 | 현재 코드 | 문제점 |
|------|----------|--------|
| `QuoteManagementClient.tsx:421,424` | `quote.managerName`, `quote.description` | ✅ 올바름 |
| `types.ts:254-266` | `transformApiToFrontend` | ✅ 올바름 |
**결론:**
- 이슈 #1 (리스트 화면)은 코드가 이미 올바름
- 담당자/비고 미표시 원인: **데이터 자체가 없거나 API 호출 확인 필요**
**테스트 방법:**
1. 견적 리스트 화면: `http://sam.kr/quotes`
2. 브라우저 개발자 도구 → Network 탭
3. `/api/v1/quotes` 응답에서 `manager`, `contact`, `remarks` 필드 확인
**다음 단계 옵션:**
- A: 데이터가 있는 견적으로 테스트 확인
- B: 이슈 #2로 진행 (프론트엔드 필드명 수정 필요 확인됨)
---
## 6. 참고 정보
### 6.1 타입 정의 요약
**Quote 인터페이스 (types.ts:85-115):**
```typescript
interface Quote {
id: string;
quoteNumber: string;
managerName?: string; // 담당자
managerContact?: string; // 연락처
description?: string; // 비고
items: QuoteItem[];
bomMaterials?: BomMaterial[];
// ...
}
```
**QuoteItem 인터페이스 (types.ts:68-82):**
```typescript
interface QuoteItem {
id: string;
productName: string;
unit?: string; // 단위
quantity: number;
unitPrice: number;
totalAmount: number;
// ...
}
```
**QuoteFormData (수정 화면용):**
```typescript
interface QuoteFormData {
manager: string; // 담당자 (폼용)
contact: string; // 연락처 (폼용)
remarks: string; // 비고 (폼용)
writer: string; // 작성자
// ...
}
```
### 6.2 변환 함수 매핑
| API 필드 | Quote (리스트/상세) | QuoteFormData (수정) |
|----------|---------------------|----------------------|
| `manager` | `managerName` | `manager` |
| `contact` | `managerContact` | `contact` |
| `remarks` | `description` | `remarks` |
| `manager_name` (레거시) | `managerName` (폴백) | `manager` (폴백) |
| `manager_contact` (레거시) | `managerContact` (폴백) | `contact` (폴백) |
### 6.3 이전 세션 백엔드 작업
-`QuoteStoreRequest.php`: `prepareForValidation()` 추가
-`QuoteUpdateRequest.php`: `prepareForValidation()` 추가
-`FormulaEvaluatorService.php`: `getBomLeafMaterials()` 추가
-`QuoteService.php`: `calculateBomMaterials()` 수정
---
## 7. 컨펌 대기 목록
| # | 항목 | 변경 내용 | 영향 범위 | 상태 |
|---|------|----------|----------|------|
| - | - | - | - | - |
---
## 8. 변경 이력
| 날짜 | 항목 | 변경 내용 | 파일 |
|------|------|----------|------|
| 2026-01-06 | 계획 | 문서 초안 작성 | - |
| 2026-01-06 | 분석 | 상세 코드 스니펫 추가 | 5개 파일 분석 |
---
## 9. 새 세션 시작 가이드
### 9.1 컨텍스트 복구 순서
```bash
1. 이 문서 읽기: docs/dev_plans/quote-management-8issues-plan.md
2. Serena 메모리 로드: read_memory("quote-8issues-state")
3. 진행 상태 확인: 섹션 5.2 "이슈별 진행 상태"
4. 마지막 작업 지점에서 재개
```
### 9.2 작업 재개 체크리스트
- [ ] 이 계획 문서 전체 읽기
- [ ] Serena 메모리에서 상태 로드
- [ ] 마지막 완료 이슈 확인
- [ ] 다음 이슈 수정 시작
- [ ] 수정 후 사용자 컨펌 받기
- [ ] 진행 상태 업데이트
---
*이 문서는 /plan 스킬로 생성되었습니다.*