- 개발팀 전용 폴더 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>
529 lines
17 KiB
Markdown
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 스킬로 생성되었습니다.* |