- 완료된 계획 문서 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>
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_idFK가 없음- 작업 완료 시 출하로 자동 연결되지 않음
- 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 스킬로 생성되었습니다.