feat: [생산지시] 재고생산 보조 공정 일반 워크플로우에서 분리

- Process P-004 options에 is_auxiliary 플래그 도입
- WO 생성 시 Process의 is_auxiliary를 WO options에 자동 복사
- ProductionOrderService: 보조 공정 WO를 공정 진행 현황에서 제외
- WorkOrderService: 보조 공정 WO의 상태 변경이 수주 상태에 영향 주지 않도록 처리
  - syncOrderStatus(): 보조 공정이면 스킵
  - autoStartWorkOrderOnMaterialInput(): WO는 진행중 전환하되 수주 상태는 유지

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 20:05:54 +09:00
parent 38c2402771
commit 0aa0a8592d
3 changed files with 62 additions and 8 deletions

View File

@@ -1325,9 +1325,13 @@ public function createProductionOrder(int $orderId, array $data)
// 작업지시번호 생성
$workOrderNo = $this->generateWorkOrderNo($tenantId);
// 절곡 공정이면 bending_info 자동 생성
// 공정 옵션 초기화 (보조 공정 플래그 포함)
$workOrderOptions = null;
if ($processId) {
$process = \App\Models\Process::find($processId);
if ($process && ! empty($process->options['is_auxiliary'])) {
$workOrderOptions = ['is_auxiliary' => true];
}
// 이 작업지시에 포함되는 노드 ID만 추출
$nodeIds = collect($items)
->pluck('order_node_id')
@@ -1338,7 +1342,7 @@ public function createProductionOrder(int $orderId, array $data)
$buildResult = app(BendingInfoBuilder::class)->build($order, $processId, $nodeIds ?: null);
if ($buildResult) {
$workOrderOptions = ['bending_info' => $buildResult['bending_info']];
$workOrderOptions = array_merge($workOrderOptions ?? [], ['bending_info' => $buildResult['bending_info']]);
}
}

View File

@@ -82,8 +82,8 @@ public function index(array $params): LengthAwarePaginator
// 개소수 (order_nodes 수)
$order->node_count = $order->nodes_count ?? 0;
// 생산 공정이 있는 WO만 (구매품/서비스 제외)
$productionWOs = $order->workOrders->filter(fn ($wo) => ! empty($wo->process_id));
// 주요 생산 공정 WO만 (구매품 + 보조 공정 제외)
$productionWOs = $this->filterMainProductionWOs($order->workOrders);
$order->work_order_progress = [
'total' => $productionWOs->count(),
'completed' => $productionWOs->where('status', 'completed')->count()
@@ -158,8 +158,8 @@ public function show(int $orderId): array
: null;
$order->production_status = $this->mapProductionStatus($order->status_code);
// 생산 공정이 있는 WorkOrder만 필터 (process_id가 null인 구매품/서비스 제외)
$productionWorkOrders = $order->workOrders->filter(fn ($wo) => ! empty($wo->process_id));
// 주요 생산 공정 WO만 필터 (구매품 + 보조 공정 제외)
$productionWorkOrders = $this->filterMainProductionWOs($order->workOrders);
// WorkOrder 진행 현황 (생산 공정 기준)
$workOrderProgress = [
@@ -261,4 +261,23 @@ private function extractBomProcessGroups($nodes): array
return array_values($groups);
}
/**
* 주요 생산 공정 WO만 필터 (구매품/서비스 + 보조 공정 제외)
*
* 제외 대상:
* - process_id가 null인 WO (구매품/서비스)
* - options.is_auxiliary가 true인 WO (재고생산 등 보조 공정)
*/
private function filterMainProductionWOs($workOrders): \Illuminate\Support\Collection
{
return $workOrders->filter(function ($wo) {
if (empty($wo->process_id)) {
return false;
}
$options = is_array($wo->options) ? $wo->options : (json_decode($wo->options, true) ?? []);
return empty($options['is_auxiliary']);
});
}
}

View File

@@ -259,6 +259,17 @@ public function store(array $data)
$salesOrderId = $data['sales_order_id'] ?? null;
unset($data['items'], $data['bending_detail']);
// 공정의 is_auxiliary 플래그를 WO options에 복사
if (! empty($data['process_id'])) {
$process = \App\Models\Process::find($data['process_id']);
if ($process && ! empty($process->options['is_auxiliary'])) {
$opts = $data['options'] ?? [];
$opts = is_array($opts) ? $opts : (json_decode($opts, true) ?? []);
$opts['is_auxiliary'] = true;
$data['options'] = $opts;
}
}
$workOrder = WorkOrder::create($data);
// process 관계 로드 (isBending 체크용)
@@ -815,6 +826,11 @@ private function syncOrderStatus(WorkOrder $workOrder, int $tenantId): void
return;
}
// 보조 공정(재고생산 등)은 수주 상태에 영향 주지 않음
if ($this->isAuxiliaryWorkOrder($workOrder)) {
return;
}
$order = Order::where('tenant_id', $tenantId)->find($workOrder->sales_order_id);
if (! $order) {
return;
@@ -858,6 +874,9 @@ private function syncOrderStatus(WorkOrder $workOrder, int $tenantId): void
*/
private function autoStartWorkOrderOnMaterialInput(WorkOrder $workOrder, int $tenantId): void
{
// 보조 공정(재고생산 등)은 WO 자체는 진행중으로 전환하되, 수주 상태는 변경하지 않음
$isAuxiliary = $this->isAuxiliaryWorkOrder($workOrder);
// 아직 진행 전인 상태에서만 자동 전환 (자재투입 = 실질적 작업 시작)
if (! in_array($workOrder->status, [
WorkOrder::STATUS_UNASSIGNED,
@@ -882,8 +901,10 @@ private function autoStartWorkOrderOnMaterialInput(WorkOrder $workOrder, int $te
['status' => WorkOrder::STATUS_IN_PROGRESS]
);
// 연결된 수주(Order) 상태 동기화 (IN_PROGRESS → IN_PRODUCTION)
$this->syncOrderStatus($workOrder, $tenantId);
// 보조 공정이 아닌 경우만 수주 상태 동기화
if (! $isAuxiliary) {
$this->syncOrderStatus($workOrder, $tenantId);
}
}
/**
@@ -926,6 +947,16 @@ private function saveItemResults(WorkOrder $workOrder, ?array $resultData, int $
}
}
/**
* 보조 공정(재고생산 등) 여부 판단
*/
private function isAuxiliaryWorkOrder(WorkOrder $workOrder): bool
{
$options = is_array($workOrder->options) ? $workOrder->options : (json_decode($workOrder->options, true) ?? []);
return ! empty($options['is_auxiliary']);
}
/**
* LOT 번호 생성
*/