# 수주-작업지시-출하 하이브리드 연동 구조 구현 계획 > **작성일**: 2025-01-19 > **목적**: Order → WorkOrder → Shipment 간 FK 연결 강화 및 상태 동기화 로직 구현 > **기준 문서**: `api/app/Models/Orders/Order.php`, `api/app/Models/Production/WorkOrder.php`, `api/app/Models/Tenants/Shipment.php` > **상태**: 📋 계획 수립 완료 (Serena ID: order-integration-state) --- ## 📍 현재 진행 상태 | 항목 | 내용 | |------|------| | **마지막 완료 작업** | Phase 4: 작업완료 시 자동 출하 생성 기능 구현 | | **다음 작업** | ✅ 모든 Phase 완료 | | **진행률** | 4/4 Phase (100%) | | **마지막 업데이트** | 2025-01-19 | --- ## 1. 개요 ### 1.1 배경 현재 SAM 시스템은 수주(Order), 작업지시(WorkOrder), 출하(Shipment)가 독립적으로 운영되고 있습니다. **현재 문제점:** - `shipments` 테이블에 `work_order_id` FK가 없음 - 작업 완료 시 출하로 자동 연결되지 않음 - Order의 전체 진행 상태를 추적할 수 없음 - 데이터 정합성 보장이 어려움 **목표:** - 하이브리드 마스터-디테일 구조로 전환 - `orders.status_code`로 전체 진행 상태 추적 - 각 단계별 상태 변경 시 연관 테이블 자동 동기화 ### 1.2 목표 구조 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 목표 구조 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ orders (마스터) │ │ ├─ status_code: 전체 진행상태 추적 │ │ │ DRAFT → CONFIRMED → IN_PRODUCTION → PRODUCED │ │ │ → SHIPPING → SHIPPED → COMPLETED │ │ │ │ │ ├──(1:N)──▶ work_orders (생산 상세) │ │ │ ├─ sales_order_id FK ✅ (기존) │ │ │ └─ status: 생산 프로세스 상태 │ │ │ │ │ └──(1:N)──▶ shipments (출하 상세) │ │ ├─ order_id FK ✅ (기존) │ │ ├─ work_order_id FK 🆕 (신규 추가) │ │ └─ status: 출하 프로세스 상태 │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ### 1.3 기준 원칙 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 🎯 핵심 원칙 │ ├─────────────────────────────────────────────────────────────────┤ │ 1. orders.status_code = 전체 프로세스의 Single Source of Truth │ │ 2. 하위 테이블(work_orders, shipments)은 상세 정보만 관리 │ │ 3. 상태 변경 시 상위 테이블 자동 동기화 │ │ 4. 기존 데이터 호환성 유지 (work_order_id는 nullable) │ └─────────────────────────────────────────────────────────────────┘ ``` ### 1.4 변경 승인 정책 | 분류 | 예시 | 승인 | |------|------|------| | ✅ 즉시 가능 | 모델 관계 추가, 상수 추가, 문서 수정 | 불필요 | | ⚠️ 컨펌 필요 | 마이그레이션, 서비스 로직 변경, 상태 동기화 | **필수** | | 🔴 금지 | 기존 테이블 구조 파괴적 변경, 기존 API 삭제 | 별도 협의 | ### 1.5 준수 규칙 - `docs/quickstart/quick-start.md` - 빠른 시작 가이드 - `docs/standards/quality-checklist.md` - 품질 체크리스트 - `CLAUDE.md` - SAM API Development Rules --- ## 2. 대상 범위 ### 2.1 Phase 1: DB 스키마 수정 | # | 작업 항목 | 상태 | 비고 | |---|----------|:----:|------| | 1.1 | `shipments` 테이블에 `work_order_id` FK 추가 마이그레이션 | ⏳ | nullable, index 포함 | ### 2.2 Phase 2: 모델 관계 추가 | # | 작업 항목 | 상태 | 비고 | |---|----------|:----:|------| | 2.1 | Order 모델에 `shipments()` HasMany 관계 추가 | ⏳ | | | 2.2 | WorkOrder 모델에 `shipments()` HasMany 관계 추가 | ⏳ | | | 2.3 | Shipment 모델에 `workOrder()` BelongsTo 관계 추가 | ⏳ | | | 2.4 | Shipment 모델에 `work_order_id` fillable 추가 | ⏳ | | ### 2.3 Phase 3: Order 상태 확장 및 동기화 로직 | # | 작업 항목 | 상태 | 비고 | |---|----------|:----:|------| | 3.1 | Order 모델에 생산/출하 관련 상태 상수 추가 | ⏳ | IN_PRODUCTION, PRODUCED, SHIPPING, SHIPPED | | 3.2 | WorkOrderService에 Order 상태 동기화 로직 추가 | ⏳ | 상태 변경 시 Order.status_code 업데이트 | | 3.3 | ShipmentService에 Order 상태 동기화 로직 추가 | ⏳ | 상태 변경 시 Order.status_code 업데이트 | ### 2.4 Phase 4: 연동 기능 (선택) | # | 작업 항목 | 상태 | 비고 | |---|----------|:----:|------| | 4.1 | ShipmentService.store()에 work_order_id 연결 로직 추가 | ⏳ | 출하 생성 시 WorkOrder 선택 가능 | | 4.2 | WorkOrder 완료 시 Shipment 자동 생성 옵션 | ⏳ | 선택적 기능 | --- ## 3. 작업 절차 ### 3.1 단계별 절차 ``` Phase 1: DB 스키마 수정 └── 1.1 마이그레이션 생성 및 실행 ├── add_work_order_id_to_shipments_table.php ├── work_order_id FK (nullable) └── index 추가 Phase 2: 모델 관계 추가 ├── 2.1 Order.php - shipments() HasMany ├── 2.2 WorkOrder.php - shipments() HasMany ├── 2.3 Shipment.php - workOrder() BelongsTo └── 2.4 Shipment.php - fillable에 work_order_id 추가 Phase 3: 상태 동기화 ├── 3.1 Order.php - 상태 상수 확장 │ ├── STATUS_IN_PRODUCTION = 'IN_PRODUCTION' │ ├── STATUS_PRODUCED = 'PRODUCED' │ ├── STATUS_SHIPPING = 'SHIPPING' │ └── STATUS_SHIPPED = 'SHIPPED' ├── 3.2 WorkOrderService.php - syncOrderStatus() 메서드 추가 │ ├── in_progress → Order: IN_PRODUCTION │ ├── completed → Order: PRODUCED │ └── shipped → Order: (Shipment 생성 시) └── 3.3 ShipmentService.php - syncOrderStatus() 메서드 추가 ├── scheduled/ready → Order: SHIPPING (첫 출하 생성 시) └── completed → Order: SHIPPED (모든 출하 완료 시) Phase 4: 연동 기능 (선택) ├── 4.1 ShipmentService.store() - work_order_id 파라미터 추가 └── 4.2 WorkOrderService.updateStatus() - 자동 Shipment 생성 옵션 ``` ### 3.2 상태 흐름도 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 전체 상태 흐름 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ [Order] │ │ DRAFT ──▶ CONFIRMED ──▶ IN_PRODUCTION ──▶ PRODUCED │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ WorkOrder WorkOrder WorkOrder │ │ 생성 in_progress completed │ │ │ │ │ ▼ │ │ ──────────────────────▶ SHIPPING ──▶ SHIPPED ──▶ COMPLETED │ │ │ │ │ │ ▼ ▼ │ │ Shipment Shipment │ │ 생성 completed │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` --- ## 4. 상세 작업 내용 ### 4.1 Phase 1: DB 스키마 수정 #### 1.1 마이그레이션: shipments 테이블에 work_order_id 추가 **파일**: `api/database/migrations/2025_01_19_XXXXXX_add_work_order_id_to_shipments_table.php` ```php foreignId('work_order_id') ->nullable() ->after('order_id') ->comment('작업지시 ID'); $table->index(['tenant_id', 'work_order_id']); }); } public function down(): void { Schema::table('shipments', function (Blueprint $table) { $table->dropIndex(['tenant_id', 'work_order_id']); $table->dropColumn('work_order_id'); }); } }; ``` --- ### 4.2 Phase 2: 모델 관계 추가 #### 2.1 Order 모델 - shipments() 관계 **파일**: `api/app/Models/Orders/Order.php` ```php use App\Models\Tenants\Shipment; /** * 출하 목록 */ public function shipments(): HasMany { return $this->hasMany(Shipment::class, 'order_id'); } ``` #### 2.2 WorkOrder 모델 - shipments() 관계 **파일**: `api/app/Models/Production/WorkOrder.php` ```php use App\Models\Tenants\Shipment; /** * 출하 목록 */ public function shipments(): HasMany { return $this->hasMany(Shipment::class); } ``` #### 2.3-2.4 Shipment 모델 수정 **파일**: `api/app/Models/Tenants/Shipment.php` ```php use App\Models\Production\WorkOrder; // fillable에 추가 protected $fillable = [ // ... 기존 필드들 'work_order_id', // 추가 ]; // casts에 추가 protected $casts = [ // ... 기존 캐스트들 'work_order_id' => 'integer', // 추가 ]; /** * 작업지시 관계 */ public function workOrder(): BelongsTo { return $this->belongsTo(WorkOrder::class); } ``` --- ### 4.3 Phase 3: Order 상태 확장 및 동기화 로직 #### 3.1 Order 모델 - 상태 상수 확장 **파일**: `api/app/Models/Orders/Order.php` ```php // 기존 상태 public const STATUS_DRAFT = 'DRAFT'; public const STATUS_CONFIRMED = 'CONFIRMED'; public const STATUS_IN_PROGRESS = 'IN_PROGRESS'; public const STATUS_COMPLETED = 'COMPLETED'; public const STATUS_CANCELLED = 'CANCELLED'; // 신규 상태 추가 public const STATUS_IN_PRODUCTION = 'IN_PRODUCTION'; // 생산중 public const STATUS_PRODUCED = 'PRODUCED'; // 생산완료 public const STATUS_SHIPPING = 'SHIPPING'; // 출하중 public const STATUS_SHIPPED = 'SHIPPED'; // 출하완료 /** * 전체 상태 목록 */ public const STATUSES = [ self::STATUS_DRAFT, self::STATUS_CONFIRMED, self::STATUS_IN_PRODUCTION, self::STATUS_PRODUCED, self::STATUS_SHIPPING, self::STATUS_SHIPPED, self::STATUS_COMPLETED, self::STATUS_CANCELLED, ]; /** * 상태 라벨 */ public const STATUS_LABELS = [ self::STATUS_DRAFT => '임시저장', self::STATUS_CONFIRMED => '확정', self::STATUS_IN_PRODUCTION => '생산중', self::STATUS_PRODUCED => '생산완료', self::STATUS_SHIPPING => '출하중', self::STATUS_SHIPPED => '출하완료', self::STATUS_COMPLETED => '완료', self::STATUS_CANCELLED => '취소', ]; ``` #### 3.2 WorkOrderService - Order 상태 동기화 **파일**: `api/app/Services/WorkOrderService.php` ```php use App\Models\Orders\Order; /** * Order 상태 동기화 * WorkOrder 상태 변경 시 Order.status_code 업데이트 */ private function syncOrderStatus(WorkOrder $workOrder): void { if (!$workOrder->sales_order_id) { return; } $order = Order::find($workOrder->sales_order_id); if (!$order) { return; } $newStatus = null; switch ($workOrder->status) { case WorkOrder::STATUS_IN_PROGRESS: case WorkOrder::STATUS_WAITING: case WorkOrder::STATUS_PENDING: // 하나라도 진행중이면 생산중 $newStatus = Order::STATUS_IN_PRODUCTION; break; case WorkOrder::STATUS_COMPLETED: // 모든 작업지시가 완료되었는지 확인 $allCompleted = WorkOrder::where('sales_order_id', $order->id) ->whereNotIn('status', [WorkOrder::STATUS_COMPLETED, WorkOrder::STATUS_SHIPPED]) ->doesntExist(); if ($allCompleted) { $newStatus = Order::STATUS_PRODUCED; } break; } if ($newStatus && $order->status_code !== $newStatus) { $order->update(['status_code' => $newStatus]); $this->auditLogger->log( $order->tenant_id, 'order', $order->id, 'status_synced_from_work_order', ['status_code' => $order->getOriginal('status_code')], ['status_code' => $newStatus, 'work_order_id' => $workOrder->id] ); } } ``` **updateStatus() 메서드에 호출 추가:** ```php public function updateStatus(int $id, string $status, ?array $resultData = null) { // ... 기존 로직 ... return DB::transaction(function () use ($workOrder, $status, $resultData, $tenantId, $userId) { // ... 기존 상태 변경 로직 ... $workOrder->save(); // Order 상태 동기화 추가 $this->syncOrderStatus($workOrder); // ... 나머지 로직 ... }); } ``` #### 3.3 ShipmentService - Order 상태 동기화 **파일**: `api/app/Services/ShipmentService.php` ```php use App\Models\Orders\Order; /** * Order 상태 동기화 * Shipment 상태 변경 시 Order.status_code 업데이트 */ private function syncOrderStatus(Shipment $shipment): void { if (!$shipment->order_id) { return; } $order = Order::find($shipment->order_id); if (!$order) { return; } $newStatus = null; switch ($shipment->status) { case 'scheduled': case 'ready': case 'shipping': // 출하 프로세스 시작 if (!in_array($order->status_code, [Order::STATUS_SHIPPING, Order::STATUS_SHIPPED, Order::STATUS_COMPLETED])) { $newStatus = Order::STATUS_SHIPPING; } break; case 'completed': // 모든 출하가 완료되었는지 확인 $allCompleted = Shipment::where('order_id', $order->id) ->where('status', '!=', 'completed') ->doesntExist(); if ($allCompleted) { $newStatus = Order::STATUS_SHIPPED; } break; } if ($newStatus && $order->status_code !== $newStatus) { $order->update(['status_code' => $newStatus]); } } ``` **store() 및 updateStatus() 메서드에 호출 추가:** ```php public function store(array $data): Shipment { // ... 기존 로직 ... return DB::transaction(function () use ($data, $tenantId, $userId) { // ... 기존 생성 로직 ... // Order 상태 동기화 추가 $this->syncOrderStatus($shipment); return $shipment->load('items'); }); } public function updateStatus(int $id, string $status, ?array $additionalData = null): Shipment { // ... 기존 로직 ... $shipment->update($updateData); // Order 상태 동기화 추가 $this->syncOrderStatus($shipment); return $shipment->load('items'); } ``` --- ### 4.4 Phase 4: 연동 기능 (선택) #### 4.1 ShipmentService.store() - work_order_id 연결 **파일**: `api/app/Services/ShipmentService.php` ```php public function store(array $data): Shipment { return DB::transaction(function () use ($data, $tenantId, $userId) { $shipment = Shipment::create([ // ... 기존 필드들 ... 'work_order_id' => $data['work_order_id'] ?? null, // 추가 ]); // WorkOrder가 있으면 상태를 shipped로 변경 if ($shipment->work_order_id) { $workOrder = WorkOrder::find($shipment->work_order_id); if ($workOrder && $workOrder->status === WorkOrder::STATUS_COMPLETED) { $workOrder->update([ 'status' => WorkOrder::STATUS_SHIPPED, 'shipped_at' => now(), ]); } } // ... 나머지 로직 ... }); } ``` #### 4.2 ShipmentStoreRequest - work_order_id 검증 **파일**: `api/app/Http/Requests/Shipment/ShipmentStoreRequest.php` ```php public function rules(): array { return [ // ... 기존 규칙들 ... 'work_order_id' => ['nullable', 'integer', 'exists:work_orders,id'], ]; } ``` --- ## 5. 컨펌 대기 목록 | # | 항목 | 변경 내용 | 영향 범위 | 상태 | |---|------|----------|----------|------| | 1 | 마이그레이션 | shipments에 work_order_id FK 추가 | DB | ⏳ 컨펌 필요 | | 2 | Order 상태 확장 | 4개 상태 추가 (IN_PRODUCTION, PRODUCED, SHIPPING, SHIPPED) | Order 모델, API | ⏳ 컨펌 필요 | | 3 | 상태 동기화 로직 | WorkOrder/Shipment 상태 변경 시 Order 자동 업데이트 | 서비스 로직 | ⏳ 컨펌 필요 | --- ## 6. 변경 이력 | 날짜 | 항목 | 변경 내용 | 파일 | 승인 | |------|------|----------|------|------| | 2025-01-19 | - | 계획 문서 초안 작성 | - | - | --- ## 7. 참고 문서 - **빠른 시작**: `docs/quickstart/quick-start.md` - **품질 체크리스트**: `docs/standards/quality-checklist.md` - **SAM API 규칙**: `CLAUDE.md` - **DB 스키마**: `docs/specs/database-schema.md` ### 분석된 기존 파일 | 파일 | 역할 | |------|------| | `api/app/Models/Orders/Order.php` | 수주 마스터 모델 | | `api/app/Models/Production/WorkOrder.php` | 작업지시 모델 | | `api/app/Models/Tenants/Shipment.php` | 출하 모델 | | `api/app/Services/WorkOrderService.php` | 작업지시 비즈니스 로직 | | `api/app/Services/ShipmentService.php` | 출하 비즈니스 로직 | | `api/database/migrations/2025_12_26_100000_create_work_orders_table.php` | 작업지시 테이블 | | `api/database/migrations/2025_12_26_150604_create_shipments_table.php` | 출하 테이블 | --- ## 8. 세션 및 메모리 관리 정책 ### 8.1 세션 시작 시 ```javascript read_memory("order-integration-state") // 상태 파악 read_memory("order-integration-snapshot") // 사고 흐름 복구 ``` ### 8.2 Serena 메모리 구조 - `order-integration-state`: { phase, progress, next_step, last_decision } - `order-integration-snapshot`: 현재까지의 논의 및 코드 변경점 요약 - `order-integration-rules`: 해당 작업에서 결정된 규칙들 --- ## 9. 검증 결과 > 작업 완료 후 이 섹션에 검증 결과 추가 ### 9.1 테스트 케이스 | 시나리오 | 예상 결과 | 실제 결과 | 상태 | |----------|----------|----------|------| | WorkOrder 생성 (in_progress) | Order.status = IN_PRODUCTION | - | ⏳ | | WorkOrder 완료 (completed) | Order.status = PRODUCED | - | ⏳ | | Shipment 생성 | Order.status = SHIPPING | - | ⏳ | | Shipment 완료 | Order.status = SHIPPED | - | ⏳ | | 모든 프로세스 완료 | Order.status = COMPLETED | - | ⏳ | ### 9.2 성공 기준 | 기준 | 달성 | 비고 | |------|------|------| | shipments.work_order_id FK 추가 완료 | ⏳ | | | 모델 관계 정상 동작 | ⏳ | | | Order 상태 자동 동기화 | ⏳ | | | 기존 데이터 호환성 유지 | ⏳ | | --- ## 10. 자기완결성 점검 결과 ### 10.1 체크리스트 검증 | # | 검증 항목 | 상태 | 비고 | |---|----------|:----:|------| | 1 | 작업 목적이 명확한가? | ✅ | 섹션 1.1 배경 | | 2 | 성공 기준이 정의되어 있는가? | ✅ | 섹션 9.2 | | 3 | 작업 범위가 구체적인가? | ✅ | 섹션 2 대상 범위 | | 4 | 의존성이 명시되어 있는가? | ✅ | Phase별 순서 정의 | | 5 | 참고 파일 경로가 정확한가? | ✅ | 섹션 7 참고 문서 | | 6 | 단계별 절차가 실행 가능한가? | ✅ | 섹션 3, 4 상세 코드 포함 | | 7 | 검증 방법이 명시되어 있는가? | ✅ | 섹션 9.1 테스트 케이스 | | 8 | 모호한 표현이 없는가? | ✅ | 구체적 코드 예시 포함 | ### 10.2 새 세션 시뮬레이션 테스트 | 질문 | 답변 가능 | 참조 섹션 | |------|:--------:|----------| | Q1. 이 작업의 목적은 무엇인가? | ✅ | 1.1 배경 | | Q2. 어디서부터 시작해야 하는가? | ✅ | 현재 진행 상태, 3.1 절차 | | Q3. 어떤 파일을 수정해야 하는가? | ✅ | 섹션 4 상세 작업 내용 | | Q4. 작업 완료 확인 방법은? | ✅ | 9. 검증 결과 | | Q5. 막혔을 때 참고 문서는? | ✅ | 7. 참고 문서 | **결과**: 5/5 통과 → ✅ 자기완결성 확보 --- *이 문서는 /sc:plan 스킬로 생성되었습니다.*