# 견적 관리 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 스킬로 생성되었습니다.*