Files
sam-docs/dev/dev_plans/quote-order-sync-improvement-plan.md

172 lines
6.1 KiB
Markdown
Raw Normal View History

# 견적 수정 → 기존 수주 업데이트 연동 개선 계획
> **작성일**: 2026-02-07
> **목적**: 견적 수정 시 연결된 기존 수주를 자동 업데이트하고, "수주등록" 대신 "수주 보기" 버튼 표시
> **상태**: 🔄 진행중
---
## 📍 현재 진행 상태
| 항목 | 내용 |
|------|------|
| **마지막 완료 작업** | 분석 완료 |
| **다음 작업** | Phase 1.1 - API 수정 |
| **진행률** | 0/4 (0%) |
| **마지막 업데이트** | 2026-02-07 |
---
## 1. 개요
### 1.1 배경
사용자가 수주(33) 상세 → 견적(41) 수정 → 저장 후 "수주등록" 버튼이 표시되어 클릭 시 새 수주가 생성됨.
**기대 동작**: 견적 수정 → 기존 수주(33)에 자동 반영 + "수주 보기" 버튼으로 기존 수주 이동
### 1.2 근본 원인
- `Quote.order_id`가 null인 상태에서 `Order.quote_id`만 설정된 경우 발생
- `QuoteService::update()``quote->order_id` 기준으로만 동기화 트리거
- `QuoteService::show()`는 역방향 참조(`Order.quote_id`)를 고려하지 않음
- 결과: 프론트에서 `orderId = null` → "수주등록" 버튼 표시
### 1.3 기존 구현 현황 (이미 구현된 부분)
| 기능 | 상태 | 위치 |
|------|------|------|
| `OrderService::syncFromQuote()` | ✅ 완전 구현 | `api/app/Services/OrderService.php:561-746` |
| `QuoteService::update()` → syncFromQuote 호출 | ✅ 구현 (order_id 기준) | `api/app/Services/Quote/QuoteService.php:448` |
| QuoteFooterBar "수주 보기" / "수주등록" 분기 | ✅ 구현 (orderId 기준) | `react/src/components/quotes/QuoteFooterBar.tsx:209` |
| `transformApiToV2`: order_id → orderId 매핑 | ✅ 구현 | `react/src/components/quotes/types.ts:275,1008` |
### 1.4 변경 승인 정책
| 분류 | 예시 | 승인 |
|------|------|------|
| ✅ 즉시 가능 | 기존 메서드에 조건 추가 | 불필요 |
| ⚠️ 컨펌 필요 | API 로직 변경, 데이터 보정 | **필수** |
---
## 2. 대상 범위
### 2.1 Phase 1: API 수정 (백엔드)
| # | 작업 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| 1.1 | QuoteService::show() - 역방향 order 참조 보정 | ⏳ | order_id null이면 orders() 관계로 탐색 |
| 1.2 | QuoteService::update() - 역방향 sync 트리거 | ⏳ | order_id null이어도 orders() 있으면 동기화 |
### 2.2 Phase 2: 프론트엔드 확인
| # | 작업 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| 2.1 | 견적 수정 후 view 모드 전환 시 데이터 갱신 확인 | ⏳ | orderId가 정상 반영되는지 |
| 2.2 | "수주 보기" 버튼 동작 확인 | ⏳ | 기존 수주로 정상 이동하는지 |
---
## 3. 작업 절차
### 3.1 상세 변경 사항
#### 1.1 QuoteService::show() 수정
**파일**: `api/app/Services/Quote/QuoteService.php` (line 168-187)
**현재 동작**: Quote 모델을 그대로 반환 (order_id가 null이면 null 그대로)
**변경**: quote.order_id가 null인데 Order.quote_id로 연결된 수주가 있으면 order_id를 보정
```php
// show() 메서드 내, return $quote; 직전에 추가
if (!$quote->order_id) {
$linkedOrder = \App\Models\Orders\Order::where('quote_id', $quote->id)
->where('tenant_id', $tenantId)
->first();
if ($linkedOrder) {
// DB에도 반영 (데이터 정합성 복구)
$quote->update(['order_id' => $linkedOrder->id]);
$quote->refresh();
}
}
```
#### 1.2 QuoteService::update() 동기화 트리거 확장
**파일**: `api/app/Services/Quote/QuoteService.php` (line 447-460)
**현재 동작**: `if ($quote->order_id)` 일 때만 syncFromQuote 호출
**변경**: order_id가 null이어도 orders() 관계로 연결된 수주가 있으면 동기화 실행
```php
// 기존 코드 (line 448)
if ($quote->order_id) {
// 변경 후
$orderId = $quote->order_id;
if (!$orderId) {
// 역방향 참조로 연결된 수주 찾기
$linkedOrder = \App\Models\Orders\Order::where('quote_id', $quote->id)
->where('tenant_id', $tenantId)
->first();
if ($linkedOrder) {
$quote->update(['order_id' => $linkedOrder->id]);
$orderId = $linkedOrder->id;
}
}
if ($orderId) {
```
### 3.2 영향 범위
| 영향 받는 부분 | 변경 여부 | 설명 |
|---------------|----------|------|
| QuoteService::show() | ✅ 수정 | 역방향 참조 보정 |
| QuoteService::update() | ✅ 수정 | sync 트리거 확장 |
| OrderService::syncFromQuote() | ❌ 변경 없음 | 이미 완전 구현 |
| QuoteFooterBar.tsx | ❌ 변경 없음 | orderId 기준 분기 이미 구현 |
| QuoteRegistrationV2.tsx | ❌ 변경 없음 | orderId 전달 이미 구현 |
| types.ts (transformApiToV2) | ❌ 변경 없음 | order_id → orderId 매핑 이미 구현 |
---
## 4. 검증 방법
### 4.1 테스트 시나리오
| # | 시나리오 | 예상 결과 |
|---|---------|----------|
| 1 | 수주(33) 상세 → 견적(41) 수정 → 저장 | 기존 수주(33) 품목 자동 업데이트 |
| 2 | 견적(41) 상세 view 모드 진입 | "수주 보기" 버튼 표시 (수주등록 아님) |
| 3 | "수주 보기" 버튼 클릭 | 수주(33) 상세 페이지로 이동 |
| 4 | 견적 수정 후 금액 변경 | 수주 총금액도 동기화 |
### 4.2 성공 기준
- 견적 수정 시 연결된 수주가 자동 업데이트됨
- "수주 보기" 버튼이 정상 표시됨
- 기존 수주로 정상 네비게이션됨
- 기존 convertToOrder() 플로우에 영향 없음
---
## 5. 참고 파일
| 파일 | 역할 |
|------|------|
| `api/app/Services/Quote/QuoteService.php` | 견적 서비스 (show, update, convertToOrder) |
| `api/app/Services/OrderService.php` | 수주 서비스 (syncFromQuote) |
| `api/app/Models/Quote/Quote.php` | 견적 모델 (orders() 관계) |
| `api/app/Models/Orders/Order.php` | 수주 모델 (quote() 관계) |
| `react/src/components/quotes/QuoteFooterBar.tsx` | 견적 푸터 바 (버튼 분기) |
| `react/src/components/quotes/QuoteRegistrationV2.tsx` | 견적 등록/수정 V2 |
| `react/src/components/quotes/types.ts` | 타입 및 API→V2 변환 |
| `react/src/app/[locale]/(protected)/sales/quote-management/[id]/page.tsx` | 견적 상세 페이지 |
---
*이 문서는 /sc:plan 스킬로 생성되었습니다.*