- 추가 이슈: #38 제어기 계산(Critical), #39 개소분리, #40 미터단위, #41 재고조정API, #42 검사모달, #43 절곡바라시(보류) - Phase 1 완료(3건), Phase 2 진행중(5/9), Phase 4 일부 완료 - 총 37건 → 43건, 진행률 16/43 (37%)
39 KiB
경동dev QA 점검 이슈 수정 계획
작성일: 2026-03-13 목적: 경동dev 모듈별 기능 및 UI 점검 결과 43건 이슈 체계적 수정 기준 문서:
경동dev_모듈별_기능 및 UI점검 - 시트1.csv상태: 🔄 진행중
📍 현재 진행 상태
| 항목 | 내용 |
|---|---|
| 마지막 완료 작업 | Phase 1 Critical 3건 + Phase 2 Major 5건 완료 (2026-03-16) |
| 다음 작업 | Phase 2: #25 거래처 담당자 자동채움, #39 개소별 분리 |
| 진행률 | 16/43 (37%) — Phase 1 완료, Phase 2 진행중, Phase 4 일부 완료 |
| 마지막 업데이트 | 2026-03-16 |
1. 개요
1.1 배경
경동dev 환경에서 모듈별 기능 및 UI를 점검한 결과 37건의 이슈가 발견됨. 2026-03-16 추가 점검으로 6건 추가 등록 (총 43건). Critical 2건은 데이터 정합성에 직접 영향을 주므로 즉시 수정 필요.
1.2 이슈 통계
| 중요도 | 건수 | 비고 |
|---|---|---|
| Critical | 3건 | 데이터 정합성 파괴 (#6 제어기 계산 오류 추가) |
| Major | 15건 | 핵심 기능 오류 (#1,#2,#5 추가) |
| Minor | 15건 | UI/UX 개선 (#3 추가) |
| 확인 필요 | 9건 | 정책 확인 후 결정 |
| 보류 (mng 선행) | 1건 | #4 절곡 바라시 |
| 모듈 | 건수 |
|---|---|
| 견적관리 | 22건 |
| 수주관리 | 5건 |
| 거래처관리 | 2건 |
| 단가관리 | 2건 |
| 품목관리 | 2건 |
| 생산/작업지시 | 3건 |
| 재고관리 | 1건 |
| 품질검사 | 1건 |
| 절곡(mng 선행) | 1건 |
| 공통 | 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 수정 (3건) — ✅ 완료
데이터 정합성에 직접 영향. 최우선 수정. 3건 모두 완료 (2026-03-16)
#27 수주등록 - 견적 불러오기 실패 Critical ✅
| 항목 | 내용 |
|---|---|
| 현상 | 견적 선택 시 전환 가능한 견적 0건, 데이터 호출 실패 |
| 경로 | /sales/order-management-sales?mode=new |
| 비고 | 견적번호 검색으로는 데이터 불러와짐 |
근본 원인 (코드 분석 완료):
-
프론트 호출:
react/src/components/orders/QuotationSelectDialog.tsx:46-52const handleFetchData = useCallback(async (query: string) => { const result = await getQuotesForSelect({ q: query || undefined, size: 50 }); // ... }, []);SearchableSelectionModal의loadOnOpen활성화- 초기 로드 시 빈 검색어(
query: undefined) 전달
-
API 호출:
react/src/components/orders/actions.ts:1246-1266url: buildApiUrl('/api/v1/quotes', { status: 'finalized', // ← finalized 상태만 with_items: 'true', for_order: 'true', // ← 수주 미생성 필터 q: params?.q, }), -
백엔드 필터:
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=true→whereNull('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()수주 동기화 호출 코드 존재
근본 원인 (코드 분석 완료):
-
Quote 모델:
api/app/Models/Quote/Quote.php:337-340public function isEditable(): bool { return true; // ← 항상 true! 상태 체크 없음 } -
QuoteService update():
api/app/Services/Quote/QuoteService.php:377-379isEditable()이 항상 true이므로 이 검증이 무의미
-
프론트 수정 버튼:
react/src/components/quotes/QuoteFooterBar.tsx:142-153orderId가 있으면 수정 버튼을 숨기지만, 백엔드에서 차단하지 않음
수정 대상:
| 파일 | 라인 | 변경 내용 |
|---|---|---|
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 스냅샷)
- 기존 불일치 데이터 현황 파악 스크립트 실행
#38 견적 금액 제어기 값 오류 (1개소만 계산) Critical ✅
| 항목 | 내용 |
|---|---|
| 현상 | 제어기(controller) BOM 금액이 1개소분만 계산됨. 다른 항목(주자재, 모터 등)은 수량에 맞게 정상 계산 |
| 기대 | 제어기도 개소 수량에 맞게 금액 계산 |
| 모듈 | 견적관리 |
수정 대상:
| 파일 | 변경 내용 |
|---|---|
api/app/Services/Quote/FormulaEvaluatorService.php |
제어기(controller) 카테고리 수량 계산 로직 확인 — 1개소 고정 버그 |
react/src/components/quotes/ |
프론트 BOM 결과 표시 시 제어기 수량/금액 확인 |
검증:
- 2개소 이상 견적에서 제어기 금액이 개소 수량에 맞게 계산되는지 확인
- 1개소 견적에서 기존 동작 유지 확인
- 다른 BOM 카테고리(주자재, 모터 등) 영향 없음 확인
2.2 Phase 2: Major - 견적관리 (9건) — 🔄 진행중 (5/9 완료)
2.2.1 BOM 탭 순서 통일 (#18, #19, #20, #22) — 4건 묶음 ✅
| 항목 | 내용 |
|---|---|
| 현상 | 등록/상세, 엑셀/수동, 산출 전/후에서 BOM 탭 순서 불일치 + inspection 라벨 |
| 기대 | 모든 케이스에서 동일한 탭 순서 |
✅ 컨펌 완료 (2026-03-14): 주자재 → 모터 → 제어기 → 절곡품 → 부자재 → 검사비 → 기타 (inspection → 검사비 라벨 변경 포함)
근본 원인 (코드 분석 완료):
-
프론트 탭 생성:
react/src/components/quotes/LocationDetailPanel.tsx:157-179const 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 객체 키 삽입 순서에 의존 → 비결정적
-
백엔드 그룹화:
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 배열 순서에 의존 → 비결정적
-
카테고리명 하드코딩:
FormulaEvaluatorService.php:1923-1933private 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.2.7 견적→수주 변환 시 개소별 분리 (#39)
| 항목 | 내용 |
|---|---|
| 현상 | 견적에서는 수량 2개 이상(여러 개소)을 하나로 등록 가능하나, 수주로 변환 시 그대로 넘어감 |
| 기대 | 수주 등록 시 개소별로 쪼개서 각 개소별 견적 단위로 분리되어 들어가야 함 |
| 예시 | 견적 1건(3개소, 수량3) → 수주 등록 시 개소별 3건으로 분리 |
| 모듈 | 견적관리 → 수주관리 |
수정 대상:
| 파일 | 변경 내용 |
|---|---|
api/app/Services/Order/OrderService.php |
견적→수주 변환(store) 시 개소별 분리 로직 추가 |
react/src/components/orders/ |
견적 불러오기 후 개소별 분리 표시 처리 |
검증:
- 2개소 이상 견적 → 수주 변환 시 개소별 분리 확인
- 1개소 견적은 기존 동작 유지
- 분리된 수주 각각의 금액/수량 정합성 확인
2.3 Phase 3: Major - 기타 모듈 (7건) — 예상 2.5일
#6 거래처 등록 미노출
| 항목 | 내용 |
|---|---|
| 현상 | 신규 거래처 등록 시 회계관리 거래처 목록에 미노출 |
수정 대상:
| 파일 | 변경 내용 |
|---|---|
api/app/Services/ClientService.php |
등록 프로세스 확인 (mng DB 동기화 여부) |
mng/ |
회계관리 거래처 목록 데이터 소스 확인 (samdb vs codebridge DB) |
#32, #33 단가 등록/수정 오류 — 2건 묶음
근본 원인 (코드 분석 완료):
-
PricingService 미구현:
api/app/Services/Pricing/PricingService.phppublic function getItemPrice(...): array { // TODO: 실제 가격 조회 로직 구현 // 현재는 임시로 0원 반환 return ['price' => 0, 'warning' => null]; }- store(), update() 메서드가 미구현 (TODO 상태)
-
프론트 폼 필드 불일치:
react/src/components/pricing/PricingFormClient.tsx:70-76const displayItemCode = initialData?.itemCode || itemInfo?.itemCode || '';- API 응답 필드명(
item_code)과 프론트 필드명(itemCode) 간 매핑 불일치 가능
- API 응답 필드명(
-
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 품목목록 규격 컬럼 비어있음 — 🔄 부분 완료
원인: attributes.spec(레거시)에 규격 저장. Item 모델에 accessor 미존재 → API 응답에 미포함.
데이터: 경동 893건 중 604건(68%) 규격 보유.
✅ 완료 (2026-03-16):
Item.php에specificationaccessor 추가 (attributes.spec→attributes.specification순서로 조회)$appends = ['specification']으로 API 응답에 자동 포함- 품목 목록 페이지 규격 표시 해결
⏳ 미완료 — 별도 세션 처리 (TODO):
| 영역 | 현황 | 필요 작업 |
|---|---|---|
| WorkOrder items | work_order_items.options에 width/height만 있고 spec 없음 |
WorkOrder API 응답에 마스터 Item.specification 조인 또는 options에 spec 추가 |
| 중간검사 성적서 | BendingWipInspectionContent에서 order.items[0].specification 참조 |
WorkOrder API에서 specification 내려줘야 표시됨 |
| 템플릿 검사 성적서 | TemplateInspectionContent에서 field.default_value 사용 (실제 규격 무시) |
WorkOrder.specification으로 대체 필요 |
| QMS 문서 | Mock data 사용 중 | API 연동 시 함께 처리 |
#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 적용 검토) |
#40 작업자 화면 — 자재투입 미터 단위 지원 Major
| 항목 | 내용 |
|---|---|
| 현상 | 자재투입 시 EA(개수) 단위는 기본 지원되지만, 미터(m) 단위 설정이 안 되어 있음 |
| 대상 품목 | 원단, 내화실, 슬랫코일 등 미터 단위로 관리되는 자재 |
| 운영 방식 | 미터 단위 차감을 하다가, 특정 개소에서 "소진" 체크로 해당 자재를 일괄 소진 처리 |
| 사유 | 로스가 많고 정확한 측정이 어려워 정밀 차감이 아닌 소진 방식으로 운영 |
| 모듈 | 생산/작업지시 (작업자 화면) |
수정 대상:
| 파일 | 변경 내용 |
|---|---|
react/src/components/production/Worker/ |
자재투입 UI에 미터(m) 단위 입력 지원 + 소진 체크 기능 추가 |
api/ (관련 Service) |
미터 단위 차감 로직 + 소진 처리 API |
items 테이블 options |
해당 품목의 단위(unit) 정보 활용 (EA/m 구분) |
검증:
- 미터 단위 품목 투입 시 m 단위로 입력/차감 확인
- 소진 체크 시 해당 자재 일괄 소진 처리 확인
- EA 단위 품목은 기존 동작 유지
#41 재고관리 — 재고 조정 API 개발 Major
| 항목 | 내용 |
|---|---|
| 현상 | 재고 조정 화면(모달)은 재고관리 또는 입고관리에 이미 개발되어 있으나, API가 미구현 |
| 기대 | 재고 조정 API 개발 + 조정 히스토리(이력) 적재 |
| 목적 | 재고 수정 사유/내역을 추적 가능하게 |
| 모듈 | 재고관리 |
수정 대상:
| 파일 | 변경 내용 |
|---|---|
api/app/Services/Inventory/ |
재고 조정 API (store/update) 구현 + 조정 사유 저장 |
api/app/Http/Controllers/ |
재고 조정 엔드포인트 추가 |
api/ (마이그레이션) |
재고 조정 히스토리 테이블 (또는 기존 audit_logs 활용) |
검증:
- 재고 조정 API 정상 동작 (수량 증가/감소)
- 조정 히스토리 정상 적재 (사유, 변경 전/후 수량, 작업자)
- 프론트 모달에서 API 연동 확인
2.4 Phase 4: Minor 수정 (15건) — 예상 1.75일
진행 상태: 8건 완료, 4건 스킵/이미적용, 2건 다른 Phase로 이관, 1건 별도 세션
4.1 공통 UI (4건)
| # | 이슈 | 상태 | 비고 |
|---|---|---|---|
| 1 | 리스트 열 너비 정책 | ⏭️ 패스 | 범위 넓어 별도 검토 필요 |
| 2 | 스티키 취소 레이어 | ✅ 이미 적용됨 | IntegratedDetailTemplate에 sticky 기본 내장 확인 |
| 3 | 모달 닫힘 정책 | ⏭️ 패스 | 현재 동작 확인 필요 (QA 기대 동작 불명확) |
| 4 | 밸리데이션 워딩 | ✅ 이미 정리됨 | Zod 기반 한글 메시지 통일 확인 |
4.2 견적 UI (4건)
| # | 이슈 | 상태 | 비고 |
|---|---|---|---|
| 7 | 필터 셀렉트박스 라벨 | ✅ 수정 완료 | "전체" → "제품분류: 전체" / "상태: 전체" |
| 22 | inspection → 검사비 | ⏭️ Phase 2 | #18-20 BOM 탭 순서와 함께 처리 |
| 24 | 견적상태 3곳 불일치 | ⏭️ Phase 2 | #23 저장/확정 분리와 함께 처리 |
| 26 | 수식 모달 하단 여백 | ✅ 수정 완료 | LocationDetail div에 pb-6 추가 |
4.3 수주 UI (2건)
| # | 이슈 | 상태 | 비고 |
|---|---|---|---|
| 28 | 수신처 필드 | ✅ 수정 완료 | Input → PhoneInput 교체 (자동 포맷팅) |
| 30 | "만원원" 이중 표시 | ✅ 수정 완료 | 수주 관련 4파일 19곳 "원" 중복 제거 |
4.4 품질검사 (1건)
| # | 이슈 | 상태 | 비고 |
|---|---|---|---|
| 42 | 제품검사 모달 페이지 이동/검색 | ⏭️ 별도 세션 | QMS 네비게이션 구조 변경 필요 (Major급) |
4.5 기타 (3건)
| # | 이슈 | 상태 | 비고 |
|---|---|---|---|
| 5 | 거래처 카운트 불일치 | ✅ 수정 완료 | 프론트 자체계산 → stats API 호출로 교체. 백엔드 active/inactive 추가 |
| 35-1 | 품목 수정이력 | ✅ 수정 완료 | ItemService.update()에 AuditLogger 감사 로그 추가 |
| 35-2 | 생산현황판 데이터 불일치 | ✅ 수정 완료 | Dashboard 통계를 work-orders/stats API 활용으로 교체 |
2.5 보류 — mng 선행 필요 (1건)
#43 절곡 바라시보기 기능 Major 보류
| 항목 | 내용 |
|---|---|
| 현상 | SAM에 절곡 바라시(전개도) 보기 기능이 없음 |
| 레퍼런스 | 5130: viewBendingWork_slat.php → 절곡 바라시 버튼 (bendingview.php) |
| 선행 조건 | mng에서 먼저 기능 개발 완료 필요 |
| 적용 방식 | mng 완료 후 해당 데이터를 호출하는 형태로 react에 적용 |
| 모듈 | 절곡/생산 |
mng 개발 완료 시점에 Phase 배정 예정
3. 작업 일정
Phase 0: 사전 조사 (대부분 완료) [0.5일] ██
Phase 1: Critical (3건) [1.5일] ██████
Phase 2: 견적 Major (9건) [3일] ████████████
Phase 3: 기타 Major (7건) [3일] ████████████
Phase 4: Minor (15건) [2일] ████████
보류: mng 선행 (1건) [TBD]
─────────────────
총계 약 10일 (+보류)
병렬 처리 가능:
- Phase 2 (견적) + Phase 3 (기타 모듈): 독립적 → 3일로 단축
- Phase 4: Phase 1~3 중 동일 파일 수정 시 함께 처리
최적 일정 (병렬 적용):
Day 1-1.5 : Phase 0 마무리 + Phase 1 (Critical 3건)
Day 2-4 : Phase 2 + Phase 3 (병렬)
Day 5-6 : Phase 4 (Minor) + Phase 0 결과 반영
─────────────────
최적 총계 약 6일 (+보류 #43)
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건 보류) | - | ✅ |
| 2026-03-16 | #38~#43 | 추가 이슈 6건 등록: #38 제어기 계산(Critical), #39 개소분리(Major), #40 미터단위(Major), #41 재고조정API(Major), #42 검사모달(Minor), #43 절곡바라시(보류) | - | ✅ |
| 2026-03-16 | Phase 4 | Minor 8건 수정 완료: #26 수식모달여백, #7 필터라벨, #30 만원원, #28 수신처, #5 거래처카운트, #35-1 품목이력, #35-2 생산현황판. 4건 스킵(#2 이미적용, #4 이미정리, #1/#3 패스), 2건 이관(#22→P2, #24→P2), 1건 별도(#42 Major급) | react aa42360, api 80cd341 |
✅ |
| 2026-03-16 | Phase 1 | Critical 3건 완료: #27 견적 불러오기(QuoteService 필터 수정+order_id 동기화), #31 생산지시 후 수정 차단(isEditable+프론트 연동), #38 제어기 수량 미반영(FormulaHandler 수량 곱셈) | api QuoteService/Quote.php/FormulaHandler, react QuoteFooterBar/QuoteRegistration/types | - |
| 2026-03-16 | Phase 2 | 5건 완료: #18-22 BOM 탭 순서(백+프론트 정렬+inspection 라벨), #16 혼합 등록 차단(모델 레벨 필터링), #23 저장/확정 분리(버튼 라벨+조건 분리), #14/#21 품목 수동추가(items.options에 bom_category 추가+API 필터+모달 연동), #8 필터 라벨(접두어 추가) | api FormulaEvaluatorService/ItemService/ItemsController/마이그레이션, react LocationDetailPanel/LocationListPanel/QuoteManagementClient/ItemSearchModal/types | - |
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건 보류)