Files
sam-docs/plans/qa-bugfix-plan.md
권혁성 4693acfd01 docs: [plans] QA 점검 이슈 수정 계획 문서 추가
- 경동dev 모듈별 기능/UI 점검 37건 이슈 분석
- Phase 0~4 단계별 수정 계획 수립
- 코드 레벨 심층 분석 (파일경로, 라인번호, 근본원인)
- 컨펌 6건 결정 반영 (5건 확정, 1건 보류)
2026-03-16 10:20:07 +09:00

33 KiB

경동dev QA 점검 이슈 수정 계획

작성일: 2026-03-13 목적: 경동dev 모듈별 기능 및 UI 점검 결과 37건 이슈 체계적 수정 기준 문서: 경동dev_모듈별_기능 및 UI점검 - 시트1.csv 상태: 🔄 진행중


📍 현재 진행 상태

항목 내용
마지막 완료 작업 컨펌 대기 6건 결정 완료
다음 작업 Phase 1: Critical 수정
진행률 0/37 (0%) — 컨펌 완료, 수정 대기
마지막 업데이트 2026-03-14

1. 개요

1.1 배경

경동dev 환경에서 모듈별 기능 및 UI를 점검한 결과 37건의 이슈가 발견됨. Critical 2건은 데이터 정합성에 직접 영향을 주므로 즉시 수정 필요.

1.2 이슈 통계

중요도 건수 비고
Critical 2건 데이터 정합성 파괴
Major 12건 핵심 기능 오류
Minor 14건 UI/UX 개선
확인 필요 9건 정책 확인 후 결정
모듈 건수
견적관리 20건
수주관리 5건
거래처관리 2건
단가관리 2건
품목관리 2건
생산/작업지시 2건
공통 4건

1.3 변경 승인 정책

분류 예시 승인
즉시 가능 UI 스타일, 라벨 변경, 필터 수정 불필요
⚠️ 컨펌 필요 상태 전이 로직, 데이터 차단 로직, API 변경 필수
🔴 금지 테이블 구조 변경, 기존 데이터 마이그레이션 별도 협의

1.4 준수 규칙

  • docs/dev/standards/api-rules.md — API 개발 규칙
  • docs/dev/standards/quality-checklist.md — 품질 체크리스트
  • docs/dev/standards/git-conventions.md — Git 커밋 컨벤션
  • docs/features/quotes/README.md — 견적 시스템 문서
  • docs/rules/pricing-policy.md — 단가 정책

2. Phase 구조

2.0 Phase 0: 사전 조사 (확인 필요 9건) — 예상 1일

코드 분석 사전 조사 완료. 정책 결정만 남음.

# 이슈 코드 분석 결과 결과 배정
9 견적 목록 정렬 정책 QuoteManagementClient.tsx:297 customSortFn에서 registrationDate DESC 하드코딩. API도 동일(QuoteService:46). 정책 맞음 정상 -
10 목록 작업 컬럼 빈 값 어떤 데이터가 들어가야 하는지 기획 확인 필요 기획 확인 (보류) -
11 견적 접수일 날짜 하루 밀림 api/config/app.php:60 timezone=Asia/Seoul 정상. QuoteService.php:344 registration_date는 프론트 전달값 사용. React에서 날짜 전송 시 UTC 변환 여부 확인 필요 프론트 확인 Phase 2
12 연락처 필수값 안내 시점 UX 정책 결정 필요: 저장 시 vs 실시간 검증 UX 결정 Phase 4
13 수동 품목 단가 0원 기획 확인 필요: 의도된 동작인지, 단가 입력 필드 필요 여부 기획 확인 Phase 2
15 부가세 값 저장/노출 안 됨 Quote 모델 fillable에 tax_amount 없음. Order 모델에만 존재. 견적에 부가세 저장 로직 자체가 미구현 🔴 버그 확정 Phase 2
17 PDF 생성 안 됨 QuoteDocumentService.php 존재. DomPDF 설정/폰트 확인 필요 (개발서버 환경 이슈 가능) 환경 확인 Phase 3
23 견적 수정→저장=확정 프로세스 결정: 저장/확정 분리 — [저장]=draft 유지, [견적확정]=finalized 전환. 확정 후 수정 시 리비전 생성 결정 완료 Phase 2
29 수주 수정 시 유효성 에러 UpdateOrderRequest.php:40-42에서 items.*.item_name required. 프론트에서 item_name 누락 시 에러 발생 🔴 버그 확정 Phase 3

2.1 Phase 1: Critical 수정 (2건) — 예상 1일

데이터 정합성에 직접 영향. 최우선 수정.

#27 수주등록 - 견적 불러오기 실패 Critical

항목 내용
현상 견적 선택 시 전환 가능한 견적 0건, 데이터 호출 실패
경로 /sales/order-management-sales?mode=new
비고 견적번호 검색으로는 데이터 불러와짐

근본 원인 (코드 분석 완료):

  1. 프론트 호출: react/src/components/orders/QuotationSelectDialog.tsx:46-52

    const handleFetchData = useCallback(async (query: string) => {
      const result = await getQuotesForSelect({ q: query || undefined, size: 50 });
      // ...
    }, []);
    
    • SearchableSelectionModalloadOnOpen 활성화
    • 초기 로드 시 빈 검색어(query: undefined) 전달
  2. API 호출: react/src/components/orders/actions.ts:1246-1266

    url: buildApiUrl('/api/v1/quotes', {
      status: 'finalized',     // ← finalized 상태만
      with_items: 'true',
      for_order: 'true',       // ← 수주 미생성 필터
      q: params?.q,
    }),
    
  3. 백엔드 필터: api/app/Services/Quote/QuoteService.php:48-59,76-81

    // for_order 필터 (라인 48-59)
    if ($forOrder) {
        $query->whereNull('order_id');
        $query->whereDoesntHave('orders');
    }
    // status 필터 (라인 76-81)
    if ($status === Quote::STATUS_CONVERTED) {
        $query->whereNotNull('order_id');
    } elseif ($status) {
        $query->where('status', $status)->whereNull('order_id');
    }
    
    • status=finalized + for_order=truewhereNull('order_id') 2회 적용 (중복이지만 논리적 문제 없음)
    • 실제 원인 추정: status 컬럼에 실제로 'finalized'가 아닌 다른 값으로 저장되어 있거나, whereDoesntHave('orders') 관계가 잘못 설정되어 있을 가능성
    • 검색어(q) 입력 시에는 search() 메서드가 다른 필터를 우회할 수 있음

수정 대상:

파일 라인 변경 내용
api/app/Services/Quote/QuoteService.php 48-81 for_order + status 필터 조건 상호작용 디버깅. whereDoesntHave('orders') 관계 확인
react/src/components/orders/actions.ts 1246-1266 getQuotesForSelect() 파라미터 검증
react/src/components/orders/QuotationSelectDialog.tsx 46-52 초기 로드 시 API 응답 로그 확인

검증:

  • finalized 상태 견적이 DB에 존재하는지 직접 쿼리
  • API 직접 호출하여 응답 확인: GET /api/v1/quotes?status=finalized&for_order=true
  • 검색어 있을 때와 없을 때 쿼리 차이 확인 (DB::enableQueryLog())
  • 견적 선택 → 수주 등록 정상 동작 확인

#31 생산지시 후 견적 수정 가능 → 금액 불일치 Critical

항목 내용
현상 생산지시 완료된 수주의 견적을 수정 가능 → 견적-수주-생산지시 금액 불일치
경로 /sales/order-management-sales/[id] → 견적 수정

컨펌 완료 (2026-03-14): 수정 허용 + 변경 전파 + 리비전 관리. 생산지시 있으면 차단.

결정된 흐름:

견적 수정 클릭
  ├─ 생산지시 존재? → ❌ 차단 "생산지시가 진행된 건은 수정할 수 없습니다"
  └─ 생산지시 없음?
       ├─ 수주 연결? → ⚠️ "연결된 수주건이 함께 변경됩니다" [확인/취소]
       │    ├─ [확인] → 리비전 생성(rev.N) + 견적 수정 + 수주 자동 동기화
       │    └─ [취소] → 변경 안 함
       └─ 수주 미연결 → 리비전 생성(rev.N) + 견적 수정

기존 리비전 시스템 (이미 구현됨):

  • quote_revisions 테이블 존재 (스냅샷 JSON 저장)
  • QuoteRevision 모델 + createRevision() 메서드 구현 완료
  • current_revision 컬럼으로 수정 차수 추적
  • syncFromQuote() 수주 동기화 호출 코드 존재

근본 원인 (코드 분석 완료):

  1. Quote 모델: api/app/Models/Quote/Quote.php:337-340

    public function isEditable(): bool
    {
        return true;  // ← 항상 true! 상태 체크 없음
    }
    
  2. QuoteService update(): api/app/Services/Quote/QuoteService.php:377-379

    • isEditable()이 항상 true이므로 이 검증이 무의미
  3. 프론트 수정 버튼: react/src/components/quotes/QuoteFooterBar.tsx:142-153

    • orderId가 있으면 수정 버튼을 숨기지만, 백엔드에서 차단하지 않음

수정 대상:

파일 라인 변경 내용
api/app/Models/Quote/Quote.php 337-340 isEditable() → 생산지시 존재 시 false 반환
api/app/Services/Quote/QuoteService.php update() 수주 연결 시 syncFromQuote() 동작 검증 + 강화
react/src/components/quotes/QuoteFooterBar.tsx 142-153 생산지시 존재 시 수정 버튼 비활성화. 수주 연결 시 확인 모달 추가
react/src/components/quotes/QuoteRegistration.tsx - 저장 전 "연결된 수주건이 함께 변경됩니다" 확인 모달

수정 코드 (제안):

// Quote.php - isEditable() 수정
public function isEditable(): bool
{
    // 생산지시가 존재하는 수주에 연결된 견적은 수정 불가
    if ($this->orders()->whereHas('workOrders')->exists()) {
        return false;
    }
    return true;  // draft, finalized, converted(수주연결) 모두 수정 가능
}

검증:

  • 생산지시 있는 견적 수정 시 차단 메시지 노출
  • 수주 연결 견적 수정 시 확인 모달 → 수주 자동 동기화
  • 수주 미연결 견적 수정 시 정상 동작
  • 리비전 생성 확인 (revision_number 증가, previous_data 스냅샷)
  • 기존 불일치 데이터 현황 파악 스크립트 실행

2.2 Phase 2: Major - 견적관리 (8건) — 예상 2.5일

2.2.1 BOM 탭 순서 통일 (#18, #19, #20, #22) — 4건 묶음

항목 내용
현상 등록/상세, 엑셀/수동, 산출 전/후에서 BOM 탭 순서 불일치 + inspection 라벨
기대 모든 케이스에서 동일한 탭 순서

컨펌 완료 (2026-03-14): 주자재 → 모터 → 제어기 → 절곡품 → 부자재 → 검사비 → 기타 (inspection → 검사비 라벨 변경 포함)

근본 원인 (코드 분석 완료):

  1. 프론트 탭 생성: react/src/components/quotes/LocationDetailPanel.tsx:157-179

    const detailTabs = useMemo((): TabDefinition[] => {
      const subtotals = location.bomResult.subtotals;
      const tabs: TabDefinition[] = [];
      Object.entries(subtotals).forEach(([key, value]) => {
        tabs.push({ value: key, label: obj.name || key });
      });
      tabs.push({ value: "etc", label: "기타" });
      return tabs;
    }, [location?.bomResult?.subtotals]);
    
    • Object.entries() 순서가 JavaScript 객체 키 삽입 순서에 의존 → 비결정적
  2. 백엔드 그룹화: api/app/Services/Quote/FormulaEvaluatorService.php:1852-1865

    // Step 8: 카테고리별 그룹화
    $groupedItems = [];
    foreach ($calculatedItems as $item) {
        $category = $item['category_group'];
        if (!isset($groupedItems[$category])) {
            $groupedItems[$category] = [...];
        }
    }
    
    • foreach 루프 순서가 $calculatedItems 배열 순서에 의존 → 비결정적
  3. 카테고리명 하드코딩: FormulaEvaluatorService.php:1923-1933

    private function getTenantCategoryName(string $category): string {
        return match ($category) {
            'material' => '주자재', 'motor' => '모터', 'controller' => '제어기',
            'steel' => '절곡품', 'parts' => '부자재',
            default => $category,  // ← 'inspection' 매핑 없음!
        };
    }
    
    • 'inspection' → '검사비' 매핑이 없음 → 탭 라벨 불일치

수정 대상:

파일 라인 변경 내용
react/src/components/quotes/types.ts 신규 BOM_CATEGORY_ORDER 정렬 순서 상수 정의
react/src/components/quotes/LocationDetailPanel.tsx 157-179 탭 생성 시 BOM_CATEGORY_ORDER 기준 정렬 적용
api/app/Services/Quote/FormulaEvaluatorService.php 1852-1865 $groupedItems를 카테고리 순서대로 정렬 후 반환
api/app/Services/Quote/FormulaEvaluatorService.php 1923-1933 'inspection' => '검사비' 매핑 추가

2.2.2 스크린+스틸 혼합 등록 차단 (#16)

항목 내용
현상 혼합 제품 등록 시 무조건 스크린으로 표기, 혼합 필터 미적용
기대 "혼합"으로 표시스크린+스틸 동시 등록 자체를 차단

컨펌 완료 (2026-03-14): 스크린과 스틸이 동시에 들어오는 수주는 없음 (인정 검사 때문). MIXED 타입 추가 불필요. 프론트에서 혼합 등록 밸리데이션 차단.

수정 방향 변경:

  • MIXED 타입 추가 → 불필요
  • deriveProductCategory() 수정 → 불필요
  • 스크린+스틸 동시 품목 추가 시 프론트 경고 + 차단 추가

수정 대상:

파일 라인 변경 내용
react/src/components/quotes/QuoteRegistration.tsx 품목 추가 핸들러 기존 품목과 다른 카테고리(스크린↔스틸) 추가 시 경고 모달 + 차단
react/src/components/quotes/QuoteManagementClient.tsx 312-323 혼합 필터 옵션 제거 (데드 코드 정리)

2.2.3 견적 저장/확정 분리 (#23)

항목 내용
현상 견적 수정 → 저장하면 바로 확정(finalized)되어 사용자 혼란
기대 저장과 확정을 분리하여 명시적 프로세스 제공

컨펌 완료 (2026-03-14): 저장/확정 분리 — [저장]=draft 유지, [견적확정]=finalized 전환. 확정 후 수정 시 리비전 생성.

결정된 흐름:

견적 등록 → [저장] → draft (자유롭게 수정, 리비전 안 쌓임)
                ↓
            [견적확정] 버튼 클릭 → finalized (리비전 시작, 수주 전환 가능)
                ↓
            수정하면 → rev.N 생성 + 수주 동기화 (#31 규칙 적용)

수정 대상:

파일 라인 변경 내용
react/src/components/quotes/QuoteFooterBar.tsx 142-153 [임시저장] → [저장], [저장] → [견적확정] 버튼 라벨/동작 분리
react/src/components/quotes/QuoteRegistration.tsx submit 핸들러 저장=draft 유지, 견적확정=finalized 전환 API 분리
api/app/Services/Quote/QuoteService.php update() draft 저장 시 리비전 미생성, finalize 시에만 상태 전환
api/app/Http/Controllers/Quote/QuoteController.php - finalize() 액션 추가 (또는 기존 활용)

2.2.4 기타 품목 수동추가 이슈 (#14, #21)

근본 원인: LocationDetailPanel.tsx:175-176

  • 기타 탭은 tabs.push({ value: "etc", label: "기타" })로 항상 마지막 추가
  • TabsList에 overflow/scroll 처리 없음 → 탭이 많으면 화면 밖으로 밀림
  • 수동 추가 시 새로운 카테고리가 생성되어 탭이 분리됨

수정 대상:

파일 라인 변경 내용
react/src/components/quotes/LocationDetailPanel.tsx 157-179 수동 추가 품목을 "기타" 탭에 병합하는 로직
react/src/components/quotes/LocationDetailPanel.tsx 200+ TabsList에 overflow-x-auto + flex-nowrap 스크롤 처리

2.2.5 필터 셀렉트박스 라벨 (#8)

근본 원인: QuoteManagementClient.tsx:312-336

  • 두 셀렉트박스 모두 placeholder 설정은 있음 ("제품분류", "상태")
  • 하지만 초기값이 'all'로 설정되어 "전체" 텍스트만 보임 → 어떤 필터인지 구분 불가

수정 대상:

파일 라인 변경 내용
react/src/components/quotes/QuoteManagementClient.tsx 312-336 SelectTrigger 앞에 라벨 텍스트 추가 또는 "전체" → "제품분류: 전체" / "상태: 전체"로 변경

2.2.6 거래처 선택 시 담당자 자동 채움 (#25)

근본 원인: QuoteRegistration.tsx:776-790

const handleClientChange = (selectedClient: Client | null) => {
  setFormData((prev) => ({
    ...prev,
    clientId: String(selectedClient.id),
    clientName: selectedClient.name,
    manager: selectedClient?.managerName || selectedClient?.representativeName || prev.manager,
    contact: selectedClient?.managerPhone || selectedClient?.mobile || selectedClient?.phone || prev.contact,
  }));
};
  • 자동 채움 로직은 구현되어 있음 (라인 783-784)
  • 원인 추정: 거래처 선택 모달에서 반환하는 Client 객체에 managerName/managerPhone 필드가 포함되지 않음 (목록 조회 시 select 컬럼에서 누락)

수정 대상:

파일 라인 변경 내용
react/src/components/quotes/actions.ts 또는 QuoteRegistration.tsx - 거래처 선택 시 상세 API 추가 호출하여 담당자 정보 로드
또는 api/app/Services/ClientService.php index() 목록 조회 시 contact_person, phone, manager_name 컬럼 포함

2.3 Phase 3: Major - 기타 모듈 (4건+) — 예상 2.25일

#6 거래처 등록 미노출

항목 내용
현상 신규 거래처 등록 시 회계관리 거래처 목록에 미노출

수정 대상:

파일 변경 내용
api/app/Services/ClientService.php 등록 프로세스 확인 (mng DB 동기화 여부)
mng/ 회계관리 거래처 목록 데이터 소스 확인 (samdb vs codebridge DB)

#32, #33 단가 등록/수정 오류 — 2건 묶음

근본 원인 (코드 분석 완료):

  1. PricingService 미구현: api/app/Services/Pricing/PricingService.php

    public function getItemPrice(...): array {
        // TODO: 실제 가격 조회 로직 구현
        // 현재는 임시로 0원 반환
        return ['price' => 0, 'warning' => null];
    }
    
    • store(), update() 메서드가 미구현 (TODO 상태)
  2. 프론트 폼 필드 불일치: react/src/components/pricing/PricingFormClient.tsx:70-76

    const displayItemCode = initialData?.itemCode || itemInfo?.itemCode || '';
    
    • API 응답 필드명(item_code)과 프론트 필드명(itemCode) 간 매핑 불일치 가능
  3. FormRequest 검증 부족: PriceUpdateRequest.php:15-17

    'sales_price' => 'nullable|numeric|min:0',  // 0원 허용, max 제한 없음
    

컨펌 완료 (2026-03-14): 단가 0원 허용. min:0 유지. PricingService 구현만 수정.

수정 대상:

파일 라인 변경 내용
api/app/Services/Pricing/PricingService.php 전체 store(), update() 메서드 구현 (현재 TODO)
react/src/components/pricing/PricingFormClient.tsx 70-76, 152-159 품목코드 매핑 + submit payload 필드명 정렬
api/app/Http/Requests/Pricing/PriceStoreRequest.php - item_id, sales_price 유효성 강화
api/app/Http/Requests/Pricing/PriceUpdateRequest.php 15-17 `nullable

#34 품목목록 규격 컬럼 비어있음

분석: api/app/Models/Items/Item.php:21-29

  • 규격은 attributes JSON 컬럼에 저장 (cast: array)
  • API 목록 조회 시 attributes 필드가 응답에 포함되는지, 프론트에서 올바르게 바인딩하는지 확인 필요
파일 변경 내용
api/app/Services/ItemService.php 목록 조회 시 attributes에서 규격 값 추출하여 응답에 포함
프론트 품목 목록 컴포넌트 규격 컬럼 데이터 바인딩 확인

#37 작업지시 품목 수량 수정 안 됨

수정 대상:

파일 변경 내용
api/app/Services/WorkOrderService.php updateItem 메서드 확인, 수량 업데이트 로직
react/src/components/production/WorkOrders/WorkOrderDetail.tsx 수량 수정 API 호출 로직 및 파라미터

#29 수주 수정 시 유효성 에러 (Phase 0에서 버그 확정)

근본 원인: api/app/Http/Requests/Order/UpdateOrderRequest.php:40-42

'items.*.item_name' => 'required|string|max:200',
'items.*.quantity' => 'required|numeric|min:0',
'items.*.unit_price' => 'required|numeric|min:0',
  • items 배열 전송 시 모든 item에 item_name 필수 → 프론트에서 누락 가능

수정 대상:

파일 라인 변경 내용
react/src/components/orders/ - 수주 수정 submit 시 items 배열에 item_name 포함 확인
api/app/Http/Requests/Order/UpdateOrderRequest.php 40-42 부분 수정 시 items 유효성 규칙 조정 (sometimes 적용 검토)

2.4 Phase 4: Minor 수정 (14건) — 예상 1.75일

4.1 공통 UI (4건)

# 이슈 파일:라인 원인 및 수정
1 리스트 열 너비 정책 react/src/components/templates/UniversalListPage/index.tsx:227-240 컬럼별 w-[Npx]/min-w-[Npx] 설정. useColumnSettings() 훅(라인 845) 활용하여 열 너비 고정 정책 적용
2 스티키 취소 레이어 react/src/components/organisms/FormActions.tsx:22-73 FormActions 자체는 스티키 미적용. 부모에서 sticky bottom-0 추가 필요. 취소/저장 영역 구분 강화 (배경색, 구분선)
3 모달 닫힘 정책 react/src/components/organisms/SearchableSelectionModal/SearchableSelectionModal.tsx:233 shadcn/ui Dialog의 onOpenChange 콜백 사용 중. 외부 클릭 시 닫힘은 기본 동작이나, 특정 모달에서 비활성화되었을 수 있음
4 밸리데이션 워딩 공통 에러 메시지 파일 에러 텍스트 전체 검증 및 일률 조정 필요

4.2 견적 UI (4건)

# 이슈 파일:라인 원인 및 수정
7 필터 버튼 활성화 스타일 QuoteManagementClient.tsx 선택된 날짜 필터 버튼에 active 스타일 클래스 추가
22 inspection → 검사비 LocationDetailPanel.tsx:112 Phase 2 #18-20에서 함께 처리 완료getTenantCategoryName()에 inspection→검사비 매핑 추가
24 견적상태 3곳 불일치 QuoteFooterBar.tsx:23 status 타입, QuoteManagementClient.tsx:115 getRevisionBadge(), QuoteSummaryPanel.tsx:43 (상태 미표시) 3곳의 상태 참조 체계가 다름. 통일된 상태 표시 컴포넌트 또는 유틸 함수 필요
26 수식 모달 하단 여백 FormulaViewModal.tsx padding-bottom 추가

4.3 수주 UI (2건)

# 이슈 파일:라인 원인 및 수정
28 수신처 필드 OrderRegistration.tsx react/src/components/ui/phone-input.tsx 컴포넌트 존재. 수신처 필드에 phone-input 교체
30 "만원원" 이중 표시 react/src/lib/utils/amount.ts:53-61 formatAmount()는 정상 ("만원" 반환). 수주 상세에서 별도로 "원"을 붙이는 코드가 있을 가능성 → 호출처 확인 필요

4.4 기타 (3건)

# 이슈 파일:라인 원인 및 수정
5 거래처 카운트 불일치 api/app/Services/ClientService.php:28-54 index()에서 is_active/client_type 필터 적용 후 카운트 vs 전체 카운트 쿼리 불일치. stats() 메서드와 index() 필터 조건 동기화 필요
35-1 품목 수정이력 api/app/Services/ItemService.php update() AuditLogger 호출 여부 확인. 누락 시 추가
35-2 생산현황판 데이터 불일치 ProductionDashboard/actions.ts vs WorkOrders/actions.ts 두 곳에서 getWorkOrders 호출 시 per_page, 필터 파라미터 차이. 대시보드는 per_page: 100, 목록은 별도 필터

3. 작업 일정

Phase 0: 사전 조사 (대부분 완료) [0.5일] ██
Phase 1: Critical               [1일]   ████
Phase 2: 견적 Major             [2.5일] ██████████
Phase 3: 기타 Major + #15,#29   [2.5일] ██████████
Phase 4: Minor                  [1.75일] ███████
                                ─────────────────
총계                             약 8.25일

병렬 처리 가능:

  • Phase 2 (견적) + Phase 3 (기타 모듈): 독립적 → 2.5일로 단축
  • Phase 4: Phase 1~3 중 동일 파일 수정 시 함께 처리

최적 일정 (병렬 적용):

Day 1     : Phase 0 마무리 + Phase 1 (Critical 2건)
Day 2-3.5 : Phase 2 + Phase 3 (병렬)
Day 4-5   : Phase 4 (Minor) + Phase 0 결과 반영
              ─────────────────
최적 총계   약 5일

4. 의존성 맵

Phase 0 (사전 조사)
├─→ #9 (정렬 정책) ────→ ✅ 정상 확인 완료
├─→ #11 (날짜 밀림) ──→ Phase 2로 배정 (프론트 날짜 전송 확인)
├─→ #15 (부가세) ────→ 🔴 Phase 3 배정 (Quote 모델에 tax_amount 미구현)
├─→ #17 (PDF) ──────→ Phase 3 (개발서버 환경 확인 필요)
├─→ #23 (확정 프로세스) → ✅ 결정 완료 → Phase 2 배정 (저장/확정 분리)
└─→ #29 (수주 수정 에러) → 🔴 Phase 3 배정 (UpdateOrderRequest 규칙 수정)

Phase 1 (Critical)
├─→ #27 (견적 불러오기) ── 독립
└─→ #31 (견적 수정 + 전파) ── 생산지시 차단 + 수주 동기화 + 리비전

Phase 2 (견적 Major)
├─→ #18,19,20 (BOM 탭 순서) ── 상호 의존 (함께 수정), #22와 연관
├─→ #23 (저장/확정 분리) ── #31과 연관 (리비전 생성 시점)
├─→ #16 (혼합 등록 차단) ── 독립 (프론트 밸리데이션)
├─→ #14,21 (레이아웃) ── #18-20과 부분 연관 (탭 구조)
├─→ #8 (필터 라벨) ── 독립
└─→ #25 (담당자 자동채움) ── 독립

Phase 3 (기타 Major) ── 모두 독립
Phase 4 (Minor) ── 모두 독립 (#22는 Phase 2에서 함께 처리)

5. 리스크

리스크 영향 대응
BOM 탭 순서 통일 시 기존 데이터 영향 저장된 calculation_inputs의 순서 변경 가능 기존 데이터는 순서 변경 없이 조회 시만 정렬 적용
#16 혼합 등록 차단 프론트 밸리데이션만 추가 → 우회 가능 백엔드에서도 검증 추가 검토 (FormRequest)
#31 견적 수정 + 수주 동기화 syncFromQuote() 동작 검증 필요 개발서버에서 시나리오 테스트 후 반영
#32,33 PricingService 미구현 store/update 전체 구현 필요 → 작업량 증가 기존 패턴(QuoteService) 참고하여 구현
#29 UpdateOrderRequest 수정 items 유효성 규칙 변경 → 다른 수주 기능에 사이드이펙트 sometimes 규칙 적용 시 기존 store도 영향 없는지 확인

6. 컨펌 결과

# 항목 결정 상태
1 #31 견적 수정 범위 수정 허용 + 변경 전파(수주 동기화) + 리비전 관리. 생산지시 있으면 차단 확정
2 #18-20 BOM 탭 순서 주자재→모터→제어기→절곡품→부자재→검사비→기타 (inspection→검사비 라벨 변경) 확정
3 #16 혼합 제품 혼합 수주 없음(인정검사). 스크린+스틸 동시 등록 프론트 차단. MIXED 타입 불필요 확정
4 #23 견적 확정 프로세스 저장/확정 분리 — [저장]=draft, [견적확정]=finalized. 확정 후 수정 시 리비전 확정
5 #33 단가 0원 0원 허용. min:0 유지. PricingService 미구현 버그만 수정 확정
6 #10 작업 컬럼 보류 — 기획 의도 확인 후 결정 보류

7. 변경 이력

날짜 항목 변경 내용 파일 승인
2026-03-13 - 문서 초안 작성 - -
2026-03-13 - 코드 레벨 심층 분석 반영 (4개 에이전트 병렬 분석) - -
2026-03-14 #31,#18-20,#16,#23,#33,#10 컨펌 대기 6건 결정 반영 (5건 확정, 1건 보류) -

8. 참고 문서

문서 경로
문서 인덱스 docs/INDEX.md
견적 시스템 docs/features/quotes/README.md
단가 정책 docs/rules/pricing-policy.md
API 규칙 docs/dev/standards/api-rules.md
품질 체크리스트 docs/dev/standards/quality-checklist.md
Git 컨벤션 docs/dev/standards/git-conventions.md
프론트엔드 아키텍처 docs/frontend/v1/01-architecture.md
통합 개선 마스터 docs/dev/dev_plans/integrated-master-plan.md

9. 파일 경로 인덱스

견적관리

파일 역할 관련 이슈
react/src/components/quotes/types.ts 타입 정의, 변환 함수 #16(L39,54-59), #18-20(탭순서)
react/src/components/quotes/actions.ts 서버 액션 #25(거래처 API)
react/src/components/quotes/QuoteManagementClient.tsx 목록 페이지 #8(L312-336), #16(L256-262), #24(L115)
react/src/components/quotes/QuoteRegistration.tsx 등록/수정 폼 #25(L776-790)
react/src/components/quotes/LocationListPanel.tsx 개소 목록 (V2) #14,21(엑셀업로드 L228-286)
react/src/components/quotes/LocationDetailPanel.tsx 개소 상세 (V2) #18-20(L157-179), #22(L112)
react/src/components/quotes/QuoteFooterBar.tsx 하단 액션 바 #31(L142-153), #24(L176-187)
api/app/Services/Quote/QuoteService.php CRUD + 상태관리 #27(L48-81), #31(L377-379)
api/app/Services/Quote/FormulaEvaluatorService.php 수식 평가 엔진 #18-20(L1852-1865,1923-1933)
api/app/Models/Quote/Quote.php 견적 모델 #31(L337-340 isEditable)

수주관리

파일 역할 관련 이슈
react/src/components/orders/QuotationSelectDialog.tsx 견적 선택 모달 #27(L46-52)
react/src/components/orders/actions.ts 서버 액션 #27(L1246-1266)
api/app/Http/Requests/Order/UpdateOrderRequest.php 수주 수정 검증 #29(L40-42)

단가관리

파일 역할 관련 이슈
react/src/components/pricing/PricingFormClient.tsx 단가 폼 #32(L70-76), #33(L139-148)
api/app/Services/Pricing/PricingService.php CRUD (미구현!) #32,33(TODO 상태)
api/app/Http/Requests/Pricing/PriceUpdateRequest.php 수정 검증 #33(L15-17)

공통

파일 역할 관련 이슈
react/src/components/templates/UniversalListPage/index.tsx 공통 목록 #1(L227-240,845)
react/src/components/organisms/FormActions.tsx 폼 액션 바 #2(L22-73)
react/src/components/organisms/SearchableSelectionModal/ 공통 모달 #3(L233)
react/src/lib/utils/amount.ts 금액 포맷 #30(L53-61)
api/app/Services/ClientService.php 거래처 CRUD #5(L28-54)

10. 검증 결과

각 Phase 완료 후 이 섹션에 검증 결과 추가

10.1 Phase 1 검증

테스트 예상 결과 실제 결과 상태
GET /api/v1/quotes?status=finalized&for_order=true 직접 호출 전환 가능 견적 N건
DB 직접 조회: SELECT * FROM quotes WHERE status='finalized' AND order_id IS NULL N건 존재
검색어 유무에 따른 쿼리 로그 비교 동일 결과
converted 견적 수정 시도 (API) 403 에러 + "수정 불가" 메시지
converted 견적 상세 (프론트) 수정 버튼 미노출
draft 견적 수정 정상 수정

10.2 Phase 2 검증

테스트 예상 결과 실제 결과 상태
BOM 탭 순서 (수동 등록) 통일된 순서
BOM 탭 순서 (상세 조회) 동일 순서
BOM 탭 순서 (엑셀 업로드) 동일 순서
스크린+스틸 동시 품목 추가 경고 모달 + 차단
혼합 필터 옵션 제거 확인 (데드 코드 정리)
거래처 선택 → 담당자 자동 채움
기타 품목 수동 추가 레이아웃 정상, 기타 탭에 병합

10.3 Phase 3 검증

테스트 예상 결과 실제 결과 상태
단가 등록 → 상세 확인 원본 품목코드/품목명 유지
단가 수정 → 저장 입력 금액 정상 저장
수주 수정 → 저장 유효성 에러 없이 저장

이 문서는 /plan 스킬로 생성되었습니다. (2026-03-13) 코드 레벨 분석: Sequential Thinking MCP + Explore Agent x8 (2회차) 컨펌 반영: 2026-03-14 (6건 중 5건 확정, 1건 보류)