docs: [MES] 데이터 정합성 보고서 v2 → v2.1 업데이트

- 이슈 #2 품질검사: 출하 연동 불필요 (품질검사는 출하 후 시공 완료 후)
- 이슈 #3 can_ship:  해결 완료
- 이슈 #4 재고차감:  비활성화 완료
- 이슈 #6 ShipmentItem FK:  해결 완료
- P0/P1/P2 로드맵 상태 전체 갱신
This commit is contained in:
2026-03-13 22:46:20 +09:00
parent 987030f24c
commit ac7fc2946b

View File

@@ -0,0 +1,506 @@
# MES 데이터 정합성 심층 분석 보고서 v2
**분석일**: 2026-03-13 (v2.1 - 정밀 코드 검증 반영)
**범위**: 수주(Order) → 생산(Production) → 품질(Quality) → 출고(Shipment) 전체 파이프라인
**방법**: 프론트엔드(sam-react-prod) + 백엔드(sam-api) 코드 레벨 재분석 + v2.1 정밀 검증
---
## v1 대비 변경사항 요약
| 항목 | v1 (초기 분석) | v2 (코드 업데이트 반영) |
|------|---------------|----------------------|
| StockLot.work_order_id FK | 확인 안됨 | ✅ 2026-02-21 추가 확인 (생산→재고 연결 기반 마련) |
| QualityDocument 시스템 | 존재 인지 | ✅ 2026-03-05~10 활발히 개선 중 (inspection_data, options JSON 추가) |
| 출하 자동생성 | 언급 | ✅ 상세 분석 완료: createShipmentFromOrder() 중복방지 + ensureShipmentExists() |
| 3월 MES FK 추가 | 미확인 | ❌ 3월 마이그레이션에 MES FK 추가 없음 확인 |
| LOT 백엔드 자동 채번 | 미구현 | ✅ v2.1 확인: generateLotNo() + saveItemResults() 구현됨 |
| 나머지 3개 이슈 | 발견 | 🔴 여전히 미해결 (can_ship, 수주생산 재고입고, ShipmentItem FK) |
---
## Executive Summary
| # | 이슈 | 심각도 | v1 판정 | v2 판정 | 변경 |
|---|------|--------|---------|---------|------|
| 1 | 생산완료→수주 PRODUCED 자동전환 | 🟡→🟢 | 조건부 동작 | **정상 동작 + 자동출하 생성** | ⬆ 개선 |
| 2 | 품질검사 이중 시스템 | 🔴 | 구조적 문제 | 🔴 **구조적 문제 지속** (QualityDocument 활발 개발 중이나 출고 연동 미완) | 유지 |
| 3 | 출고 시 can_ship 검증 | 🔴 | 누락 | 🔴 **여전히 누락** (canProceedToShip 호출 0회) | 유지 |
| 4 | 출고 시 재고 차감 | ✅ | 구현됨 | ✅ **구현됨**, ⚠️ soft fail 리스크 유지 | 유지 |
| 5 | LOT 추적 체계 | 🔴 | 단절 | 🟢 **선생산 정상** / 🟡 **수주생산 재고입고 미경유** (v2.1: generateLotNo() 구현 확인) | ⬆⬆ 상향 |
| 6 | 출고품목↔수주품목 FK | 🔴 | 없음 | 🔴 **여전히 없음** (3월 마이그레이션에도 미추가) | 유지 |
---
## 이슈 1: 생산완료 → 수주 상태 자동전환 + 출하 자동생성
### v2 판정: 🟢 정상 동작 (v1 대비 상향)
v2 분석에서 자동 출하 생성 로직까지 상세 확인 완료. **정상 동작 확인**.
### 전체 흐름
```
WorkOrder 상태 변경 (updateStatus)
syncOrderStatus() 자동 호출 (L971-1059)
메인 WO 필터링: is_auxiliary=false AND process_id≠null
전체 완료 시 → Order.status = PRODUCED
createShipmentFromOrder() 자동 호출 (L719-809)
Shipment 생성: status='scheduled', can_ship=true(자동)
기존 Shipment 있으면 → 중복 생성 방지 (L721-728)
```
### 코드 근거
**syncOrderStatus**: `WorkOrderService.php:971-1059`
```php
// L989-995: 메인 WO 필터 (보조공정 + process_id=null 제외)
$mainWorkOrders = $allWorkOrders->filter(fn ($wo) =>
!$this->isAuxiliaryWorkOrder($wo) && $wo->process_id !== null
);
// L1001-1019: 상태 결정
if ($shippedCount === $totalCount) {
$newOrderStatus = Order::STATUS_SHIPPED;
} elseif (($completedCount + $shippedCount) === $totalCount) {
$newOrderStatus = Order::STATUS_PRODUCED;
}
```
**createShipmentFromOrder**: `WorkOrderService.php:719-809`
```php
// L721-728: 중복 방지
$existingShipment = Shipment::where('order_id', $order->id)->first();
if ($existingShipment) return $existingShipment;
// L732-744: 출하 자동 생성
$shipment = Shipment::create([
'order_id' => $order->id,
'work_order_id' => null, // 수주 레벨 (WO 레벨 아님)
'status' => 'scheduled',
'can_ship' => true, // ← 자동으로 true 설정
]);
// L746-790: WO 아이템 → ShipmentItem 복사
```
**ensureShipmentExists**: 이미 PRODUCED인데 출하가 없는 경우 보완 (L1027-1033)
### 잔존 리스크 (낮음)
| 조건 | 원인 | 발생 가능성 |
|------|------|------------|
| `process_id = NULL`인 WO | 공정 매핑 실패 | 낮음 (생성 시 검증됨) |
| `is_auxiliary` 오설정 | options JSON 수동 수정 | 매우 낮음 |
### 회의 논의 포인트
- ✅ 이 부분은 정상 동작 확인됨. 추가 조치 불필요
- (선택) process_id=null WO가 실데이터에 존재하는지 한번 쿼리 확인
---
## 이슈 2: 품질검사 이중 시스템
### v2.1 판정: 🟡 이중 시스템은 존재하나, 출하 연동은 불필요 (비즈니스 흐름상 품질검사는 출하 후)
### v1 대비 변화
| 변경 사항 | 시기 | 내용 |
|-----------|------|------|
| `quality_document_locations.inspection_data` JSON 추가 | 2026-03-06 | 개소별 검사 데이터 저장 |
| `quality_document_locations.options` JSON 추가 | 2026-03-10 | 검사 옵션 확장 |
| QualityDocumentService 개선 | 2026-03 | inspectLocation() 등 기능 확장 |
### v2.1 비즈니스 흐름 정정
```
실제 흐름 (확인됨):
생산 완료 → 출하 → 현장 시공 → 시공 완료 → 제품검사(FQC)
∴ 품질검사는 출하 이후에 발생 → can_ship ↔ 품질검사 연동 불필요
∴ v2에서 "출고 연동 미완"으로 판정한 것은 오판 → 연동 자체가 해당 없음
```
**두 시스템 현재 상태**:
| 항목 | 경로A: Inspection | 경로B: QualityDocument |
|------|-------------------|----------------------|
| **테이블** | `inspections` | `quality_documents` + `quality_document_locations` |
| **FK 연결** | `work_order_id` (작업지시) | `order_id` (수주) + `order_item_id` (개소) |
| **3월 업데이트** | 변경 없음 | ✅ 활발히 개선 중 |
| **용도** | IQC/PQC (공정 중 검사) | FQC (출하 후 제품 검사) |
### 회의 논의 포인트
- QualityDocument가 활발히 개발 중 → **경로B를 FQC 표준으로 확정하는 것이 합리적**
- 경로A(Inspection)는 IQC/PQC 전용으로 역할 한정, FQC는 경로B로 통일
- ~~품질 완료 시 can_ship 연동~~ → **해당 없음** (품질검사는 출하 후 시공 완료 후 수행)
---
## 이슈 3: 출고 시 can_ship 검증 누락
### v2.1 판정: ✅ 해결 완료 — 백엔드 can_ship 검증 + 프론트 버튼 비활성화 추가
### 코드 현황 (변경 없음)
**canProceedToShip()**: `Shipment.php:220-223` — 정의만 존재
```php
public function canProceedToShip(): bool {
return $this->can_ship && $this->deposit_confirmed;
}
// grep 결과: 모델 정의 외 호출 0회
```
**updateStatus()**: `ShipmentService.php:305-356` — can_ship 검증 없이 바로 업데이트
```php
public function updateStatus(int $id, string $status, ?array $additionalData = null): Shipment
{
$shipment = Shipment::findOrFail($id);
// 🔴 can_ship 검증 없음
$shipment->update(['status' => $status, ...]);
}
```
**프론트엔드**: `ShipmentDetail.tsx:304-314` — can_ship 참조 자체가 없음
```typescript
const STATUS_TRANSITIONS: Record<ShipmentStatus, ShipmentStatus | null> = {
scheduled: 'ready', ready: 'shipping', shipping: 'completed', completed: null,
};
// v2.1 확인: canShip 변수 자체가 컴포넌트에 없음 (grep 0건)
// can_ship=false여도 상태 변경 버튼이 무조건 표시됨
```
### v2 신규 발견: 자동 출하에서 can_ship=true 자동 설정
```php
// createShipmentFromOrder (L732-744)
'can_ship' => true, // 자동 생성 시 무조건 true
```
→ 자동 생성된 출하는 can_ship=true이므로 문제 경감
**그러나** 수동 생성 출하에서는 여전히 검증 없음
### 위험 시나리오
```
수동 출하 생성 (can_ship=false)
→ 사용자가 "출하대기" 클릭 → 검증 없이 ready
→ "배송중" → "배송완료" → 재고 차감 시도
→ 재고 부족 시 soft fail (로그만, 상태는 completed) ❌
```
### ~~수정안~~ → ✅ v2.1에서 적용 완료
**백엔드** (ShipmentService::updateStatus() L312-317):
```php
if (in_array($status, ['ready', 'shipping', 'completed']) && !$shipment->can_ship) {
throw new BadRequestHttpException(__('error.shipment.cannot_ship'));
}
```
**프론트엔드** (ShipmentDetail.tsx):
```typescript
{STATUS_TRANSITIONS[detail.status] && detail.canShip && (
<Button onClick={handleOpenStatusDialog}>상태 변경</Button>
)}
{STATUS_TRANSITIONS[detail.status] && !detail.canShip && (
<Button variant="outline" size="sm" disabled>출하 불가 (품질 검수 필요)</Button>
)}
```
---
## 이슈 4: 출고 시 재고 차감
### v2.1 판정: ✅ 비활성화 완료 — 현재 시점에서 불필요한 로직
**비즈니스 흐름 정정** (v2.1):
```
- 수주생산: 재고를 거치지 않음 (make-to-order) → 출하 시 재고 차감 불필요
- 선생산 완성품: 재고에 입고 → 다른 WO의 자재 투입 시 차감 → 출하 시 차감 불필요
∴ decreaseStockForShipment() 자체가 현재 시점에서 쓸 일이 없는 로직
```
**조치**: `updateStatus()`에서 `decreaseStockForShipment()` 호출 비활성화 (TODO 주석으로 선생산 재검토 예약)
### ~~회의 논의 포인트~~ → 해결됨
- ~~Hard Fail vs Soft Fail~~ → 호출 자체 비활성화로 해소
- 선생산 로직이 본격 가동되면 재검토 필요
---
## 이슈 5: LOT 추적 체계
### v2.1 판정: 🟢 선생산 정상 / 🟡 수주생산 재고입고 미경유 (v1 🔴 → v2 🟡 → v2.1 ⬆ 상향)
### v2.1 정밀 검증 결과: LOT 자동 채번 **구현 확인**
v2에서 "백엔드 LOT 저장 로직 없음"으로 판정했으나, **현재 코드에 구현되어 있음**.
| 단계 | 메서드 | 위치 | 상태 |
|------|--------|------|------|
| LOT 자동 채번 | `generateLotNo()` | L1155-1176 | ✅ `KD-SA-YYMMDD-NN` 형식 |
| LOT options 저장 | `saveItemResults()` | L1105-1140 | ✅ `options.result.lot_no`에 저장 |
| 선생산 재고 입고 | `stockInFromProduction()` | L603-604 | ✅ `sales_order_id=null`일 때 호출 |
| StockLot 생성 | `increaseFromProduction()` | L349-398 | ✅ `work_order_id` FK 활용 |
### 핵심 코드 근거
**1. LOT 자동 채번 (구현됨)**
```php
// WorkOrderService.php:1155-1176
private function generateLotNo(WorkOrder $workOrder): string
{
$date = now()->format('ymd');
$prefix = 'KD-SA';
// 오늘 날짜의 마지막 LOT 번호 조회 → 시퀀스 증가
return sprintf('%s-%s-%02d', $prefix, $date, $seq);
}
```
**2. saveItemResults()에서 LOT 저장 (구현됨)**
```php
// WorkOrderService.php:1105-1140
private function saveItemResults(WorkOrder $workOrder, ?array $resultData, int $userId): void
{
$lotNo = $this->generateLotNo($workOrder); // ✅ LOT 자동 채번
foreach ($items as $item) {
$itemResult = [
'lot_no' => $lotNo, // ✅ 모든 아이템에 LOT 저장
'good_qty' => $item->quantity,
// ... 기타 결과 데이터
];
$options['result'] = $itemResult;
$item->save();
}
}
```
**3. 선생산 vs 수주생산 분기 (핵심 발견)**
```php
// WorkOrderService.php:602-605
// 작업완료 시: 선생산(수주 없음) → 재고 입고
if ($status === WorkOrder::STATUS_COMPLETED && !$workOrder->sales_order_id) {
$this->stockInFromProduction($workOrder); // ✅ 선생산만 호출
}
```
### 두 경로의 LOT 추적 현황
**경로A: 선생산 (sales_order_id = null) — ✅ 정상 동작**
```
WO 완료 → saveItemResults() (LOT 채번 + 저장)
→ stockInFromProduction() 호출
→ increaseFromProduction() → StockLot 생성 (work_order_id FK ✅)
→ Stock 합계 갱신 + 거래 이력 기록
```
**경로B: 수주 생산 (sales_order_id 존재) — 🟡 재고 미경유 (의도된 설계 가능성)**
```
WO 완료 → saveItemResults() (LOT 채번 + 저장 ✅)
→ stockInFromProduction() 미호출 (수주 연결이므로 skip)
→ syncOrderStatus() → Order PRODUCED
→ createShipmentFromOrder() → ShipmentItem에 lot_no 복사 ✅
→ 출하 완료 시 decreaseStockForShipment() → ⚠️ 입고 없이 차감 시도
```
→ 수주 생산은 make-to-order 방식으로 재고를 거치지 않는 **의도된 설계일 수 있음**.
→ 그러나 StockLot이 생성되지 않으므로 **재고 기반 LOT 추적은 불가**.
### v1 대비 개선 사항
| 개선 | 시기 | 코드 근거 |
|------|------|-----------|
| `stock_lots.work_order_id` FK 추가 | 2026-02-21 | 마이그레이션 확인 |
| `inspections.work_order_id` FK 추가 | 2026-02-27 | 마이그레이션 확인 |
| `generateLotNo()` 백엔드 자동 채번 | v2.1 확인 | WorkOrderService:1155-1176 |
| `saveItemResults()` LOT 저장 | v2.1 확인 | WorkOrderService:1105-1140 |
### 잔존 이슈 (v2.1 업데이트)
1. **수주생산 재고입고 경로**: StockLot 미생성 → 의도된 설계 확인됨 (make-to-order). 선생산 로직 추후 검증
2. **프론트 LOT 생성 코드**: WorkerScreen/actions.ts에서 별도 LOT 생성 → 백엔드와 이중 생성 가능성 (P1 #5-1)
3. ~~**출하 재고 차감**~~: ✅ decreaseStockForShipment() 비활성화로 해소
### ~~논의 필요 사항~~ → v2.1 확인됨
- **수주생산의 재고 미경유**: ✅ 의도된 설계 확인 (make-to-order, 주인이 있는 제품은 재고 미경유)
- 선생산 완성품은 재고 입고 → 다른 WO의 자재 투입 시 차감되는 흐름 (부품화)
---
## 이슈 6: 출고품목 ↔ 수주품목 FK 부재
### v2.1 판정: ✅ 해결 완료 — order_item_id + work_order_item_id 컬럼/인덱스 추가
### ShipmentItem 컬럼 (v2.1 수정됨)
```
id, tenant_id, shipment_id(FK), seq,
item_code, item_name, floor_unit, specification,
quantity, unit, lot_no, stock_lot_id(index only),
order_item_id(index) 🆕, work_order_item_id(index) 🆕, remarks
```
-`order_item_id` → 추가됨 (nullable, 인덱스)
-`work_order_item_id` → 추가됨 (nullable, 인덱스)
- 마이그레이션: `2026_03_13_100000_add_order_item_id_to_shipment_items`
- 모델: ShipmentItem에 orderItem(), workOrderItem() BelongsTo 관계 추가
- 데이터 입력: createShipmentFromOrder()에서 자동 매핑
### 추적 가능 여부 (v2.1 이후)
| 질문 | 답변 가능 여부 |
|------|--------------|
| "출고 #1234에서 어떤 수주 품목이 출고됐나?" | ✅ order_item_id로 조회 |
| "수주 품목 #999는 어느 출고에서 출고됐나?" | ✅ 역추적 가능 |
| "수주 10개 품목 중 미출고 품목은?" | ✅ 집계 가능 |
| "부분 출고 진행률은?" | ✅ 계산 가능 |
> ⚠️ 기존 데이터는 order_item_id=null. 소급 매칭은 추후 필요시 진행
### ~~자동 출하 생성 시 연결 기회 놓침~~ → ✅ v2.1에서 해결
```php
// createShipmentFromOrder (L756-768) — v2.1 수정 완료
ShipmentItem::create([
'shipment_id' => $shipment->id,
'item_code' => $woItem->item_id ? "ITEM-{$woItem->item_id}" : null,
'quantity' => $result['good_qty'] ?? $woItem->quantity,
'order_item_id' => $woItem->source_order_item_id, // ✅ 추가됨
'work_order_item_id' => $woItem->id, // ✅ 추가됨
]);
```
### ~~수정안~~ → ✅ 적용 완료
- **마이그레이션**: `2026_03_13_100000_add_order_item_id_to_shipment_items` (인덱스만, FK 제약 없음)
- **모델**: ShipmentItem에 $fillable, $casts, orderItem()/workOrderItem() 관계 추가
- **데이터 입력**: createShipmentFromOrder()의 WO 경로 + Order fallback 경로 모두 적용
---
## 전체 FK 연결 현황도 (v2 업데이트)
```
orders ──────────────────── order_items ──────── order_nodes
│ (order_id FK) │ (order_node_id FK) │
│ │ │
├─── work_orders │ │
│ │ (sales_order_id FK) │ │
│ │ │ │
│ └─── work_order_items │ │
│ │ │ │
│ │ source_order_item_id ──→ ❌ FK 없음 (인덱스만)
│ │ │
│ inspections │
│ │ (work_order_id FK ✅) [2026-02-27 추가] │
│ │ (lot_no ← 연결 안됨 ❌) │
│ │
│ stock_lots │
│ │ (work_order_id FK ✅) [2026-02-21 추가] ← 🆕 v1에서 미확인
│ │
├─── quality_document_orders ──→ quality_documents │
│ │ (order_id FK ✅) │
│ │ │
│ └─── quality_document_locations │
│ │ (order_item_id FK ✅) │
│ │ (inspection_data JSON 🆕 2026-03-06) │
│ │ (options JSON 🆕 2026-03-10) │
│ │
└─── shipments │
│ (order_id FK ✅, work_order_id FK ✅) │
│ │
└─── shipment_items │
│ (shipment_id FK ✅) │
│ (stock_lot_id → 인덱스만, FK 없음) │
│ (order_item_id ✅ 인덱스) 🆕 v2.1 │
│ (work_order_item_id ✅ 인덱스) 🆕 v2.1 │
```
---
## 개선 우선순위 로드맵 (v2.1 업데이트)
### P0 (즉시 - 운영 리스크) — v2.1 반영
| # | 작업 | 수정 범위 | 난이도 | 상태 |
|---|------|---------|--------|------|
| 1 | **can_ship 검증 추가** | ShipmentService::updateStatus() + ShipmentDetail.tsx | 하 | ✅ 완료 |
| 2 | ~~**재고 차감 실패 알림**~~ | decreaseStockForShipment() 호출 비활성화 — 수주생산은 재고 미경유, 선생산 완성품은 자재 투입 시 차감 | - | ✅ 비활성화 |
### P1 (단기 - 데이터 정합성) — v2.1 반영
| # | 작업 | 수정 범위 | 난이도 | 상태 |
|---|------|---------|--------|------|
| 3 | ~~**생산 LOT 백엔드 자동 채번**~~ | generateLotNo() + saveItemResults() 이미 구현됨 | - | ✅ 확인 완료 |
| 4 | **수주생산 재고입고 경로 검토** | 수주생산은 재고 미경유 (make-to-order) — 의도된 설계. 선생산 로직은 추후 검증 | 중 | 🟡 설계 확인됨 |
| 5 | **shipment_items에 order_item_id/work_order_item_id 추가** | 마이그레이션 + ShipmentItem 모델 + createShipmentFromOrder() | 중 | ✅ 완료 |
| 5-1 | **프론트 LOT 이중 생성 정리** | WorkerScreen/actions.ts 프론트 LOT 생성 코드 제거 (백엔드 채번으로 통일) | 하 | 미착수 |
### P2 (중기 - 구조 개선) — v2.1 품질검사 흐름 정정 반영
| # | 작업 | 수정 범위 | 난이도 |
|---|------|---------|--------|
| 6 | **품질검사 정본 = QualityDocument** | Inspection은 IQC/PQC 전용, FQC는 QualityDocument로 통일 | 상 |
| 7 | ~~**품질완료 → can_ship 자동 연동**~~ | ❌ 해당 없음 — 품질검사는 출하 후 시공 완료 후 수행 | - |
| 8 | **work_order_items.source_order_item_id FK** | 마이그레이션 1줄 | 하 |
| 9 | **stock_lot_id FK constraint 추가** | shipment_items 마이그레이션 | 하 |
---
## 정상 동작 확인 항목 (v2)
- ✅ 수주 → 생산지시 생성 (공정별 자동 분류)
- ✅ 작업지시 상태 관리 (유효 상태 전환 + auxiliary 필터링)
-**syncOrderStatus()**: 메인 WO 완료 → Order PRODUCED 자동 전환
-**createShipmentFromOrder()**: PRODUCED 전환 시 출하 자동 생성 (중복 방지 포함)
-**ensureShipmentExists()**: 이미 PRODUCED인데 출하 없는 경우 보완
- ✅ 자재 투입 재고 차감 (WorkOrderMaterialInput + StockService)
- ✅ 출고 완료 시 재고 차감 (FIFO + lockForUpdate + stock_transactions)
- ✅ 출고 완료 → 수주 상태 SHIPPED 자동 전환
- ✅ 매출 자동 생성 (sales_recognition 조건부)
- ✅ 수주 상태별 수정/삭제 제한
- ✅ 생산지시 되돌리기 (WorkOrder/Item/Result 삭제)
- 🆕 ✅ StockLot.work_order_id FK (생산→재고 연결 기반)
- 🆕 ✅ Inspection.work_order_id FK (검사→생산 연결)
- 🆕 ✅ generateLotNo() 백엔드 LOT 자동 채번 (v2.1 확인)
- 🆕 ✅ saveItemResults() LOT options 저장 (v2.1 확인)
- 🆕 ✅ 선생산 stockInFromProduction() → StockLot 생성 + work_order_id 연결 (v2.1 확인)
---
## 회의 토론 안건 정리
### 즉시 결정 필요 (P0) — ✅ 해결됨
1. ~~**can_ship 검증**~~: ✅ 백엔드 + 프론트 수정 완료
2. ~~**재고 차감 실패 처리**~~: ✅ decreaseStockForShipment() 비활성화 (수주생산=재고 미경유, 선생산=자재 투입 시 차감)
### 설계 방향 결정 필요 (P1) — v2.1 업데이트
3. ~~**LOT 채번 규칙**~~: ✅ 이미 구현됨 (`KD-SA-YYMMDD-NN`, 백엔드 generateLotNo())
4. **수주생산 재고입고 경로**: ✅ 수주생산은 재고 미경유 (make-to-order) — 의도된 설계 확인됨. 선생산 로직은 추후 검증
5. ~~**ShipmentItem FK**~~: ✅ order_item_id + work_order_item_id 컬럼/인덱스 추가 완료 (FK 제약 없이 인덱스만)
5-1. **프론트 LOT 이중 생성**: WorkerScreen/actions.ts의 프론트 LOT 생성 코드 → 백엔드 통일 후 제거
### 방향성 논의 (P2)
6. **품질 시스템 정본**: QualityDocument를 FQC 표준으로 확정하는 것에 이견 있는지?
7. ~~**품질→출하 자동 연동**~~: ❌ 해당 없음 — 비즈니스 흐름상 품질검사는 출하 후 시공 완료 후 수행되므로 can_ship 연동 불필요