Files
sam-docs/plans/archive/order-workorder-shipment-integration-plan.md
권혁성 06a4c798ec chore: 완료 계획 문서 22개 archive 이동 및 인덱스 업데이트
- 완료된 계획 문서 22개를 plans/archive/로 이동
  - tracked 16개 (git mv): bending-lot-pipeline, docs-update, fcm-notification 등
  - untracked 6개 (mv): bending-worklog, formula-engine, mng-item 등
- index_plans.md 전면 업데이트
  - 진행중 44개 / 완료 37개 현황 반영
  - 각 문서별 실제 진행률 기재 (0%~94%)
  - 카테고리별 재정리 (견적/생산/품목/문서/마이그레이션/시스템/UI)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 10:02:47 +09:00

22 KiB

수주-작업지시-출하 하이브리드 연동 구조 구현 계획

작성일: 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

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::table('shipments', function (Blueprint $table) {
            $table->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

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

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

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

// 기존 상태
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

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() 메서드에 호출 추가:

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

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() 메서드에 호출 추가:

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

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

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 세션 시작 시

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 스킬로 생성되었습니다.