diff --git a/app/Services/ProductionOrderService.php b/app/Services/ProductionOrderService.php index 9d7cd9c..65d6fc7 100644 --- a/app/Services/ProductionOrderService.php +++ b/app/Services/ProductionOrderService.php @@ -29,7 +29,7 @@ public function index(array $params): LengthAwarePaginator ->where('tenant_id', $tenantId) ->whereIn('status_code', self::PRODUCTION_STATUSES) ->with(['client', 'workOrders.process', 'workOrders.assignees.user']) - ->withCount('workOrders'); + ->withCount(['workOrders', 'nodes']); // 검색어 필터 if (! empty($params['search'])) { @@ -71,7 +71,13 @@ public function index(array $params): LengthAwarePaginator // 가공 필드 추가 $result->getCollection()->transform(function (Order $order) { - $order->production_ordered_at = $order->workOrders->min('created_at'); + $minCreatedAt = $order->workOrders->min('created_at'); + $order->production_ordered_at = $minCreatedAt + ? $minCreatedAt->format('Y-m-d') + : null; + + // 개소수 (order_nodes 수) + $order->node_count = $order->nodes_count ?? 0; $workOrders = $order->workOrders; $order->work_order_progress = [ @@ -138,10 +144,14 @@ public function show(int $orderId): array 'workOrders.assignees.user', 'nodes', ]) + ->withCount('nodes') ->findOrFail($orderId); - // 생산지시일 - $order->production_ordered_at = $order->workOrders->min('created_at'); + // 생산지시일 (날짜만) + $minCreatedAt = $order->workOrders->min('created_at'); + $order->production_ordered_at = $minCreatedAt + ? $minCreatedAt->format('Y-m-d') + : null; $order->production_status = $this->mapProductionStatus($order->status_code); // WorkOrder 진행 현황 @@ -171,6 +181,7 @@ public function show(int $orderId): array 'order' => $order->makeHidden(['workOrders', 'nodes']), 'production_ordered_at' => $order->production_ordered_at, 'production_status' => $order->production_status, + 'node_count' => $order->nodes_count ?? 0, 'work_order_progress' => $workOrderProgress, 'work_orders' => $workOrders, 'bom_process_groups' => $bomProcessGroups, diff --git a/app/Services/WorkOrderService.php b/app/Services/WorkOrderService.php index e35f357..806c57a 100644 --- a/app/Services/WorkOrderService.php +++ b/app/Services/WorkOrderService.php @@ -850,6 +850,42 @@ private function syncOrderStatus(WorkOrder $workOrder, int $tenantId): void ); } + /** + * 자재 투입 시 작업지시가 대기 상태이면 자동으로 진행중으로 전환 + * + * pending/waiting 상태에서 첫 자재 투입이 발생하면 + * 작업지시 → in_progress, 수주 → IN_PRODUCTION 으로 자동 전환 + */ + private function autoStartWorkOrderOnMaterialInput(WorkOrder $workOrder, int $tenantId): void + { + // 아직 진행 전인 상태에서만 자동 전환 (자재투입 = 실질적 작업 시작) + if (! in_array($workOrder->status, [ + WorkOrder::STATUS_UNASSIGNED, + WorkOrder::STATUS_PENDING, + WorkOrder::STATUS_WAITING, + ])) { + return; + } + + $oldStatus = $workOrder->status; + $workOrder->status = WorkOrder::STATUS_IN_PROGRESS; + $workOrder->updated_by = $this->apiUserId(); + $workOrder->save(); + + // 감사 로그 + $this->auditLogger->log( + $tenantId, + self::AUDIT_TARGET, + $workOrder->id, + 'status_auto_changed_on_material_input', + ['status' => $oldStatus], + ['status' => WorkOrder::STATUS_IN_PROGRESS] + ); + + // 연결된 수주(Order) 상태 동기화 (IN_PROGRESS → IN_PRODUCTION) + $this->syncOrderStatus($workOrder, $tenantId); + } + /** * 작업지시 품목에 결과 데이터 저장 */ @@ -1458,6 +1494,9 @@ public function registerMaterialInput(int $workOrderId, array $inputs): array $totalCount = array_sum(array_column($delegatedResults, 'material_count')); $allResults = array_merge(...array_map(fn ($r) => $r['input_results'], $delegatedResults)); + // 자재 투입 시 작업지시가 대기 상태면 자동으로 진행중으로 전환 + $this->autoStartWorkOrderOnMaterialInput($workOrder, $tenantId); + return [ 'work_order_id' => $workOrderId, 'material_count' => $totalCount, @@ -1536,6 +1575,9 @@ public function registerMaterialInput(int $workOrderId, array $inputs): array $allResults = array_merge($allResults, $dr['input_results']); } + // 자재 투입 시 작업지시가 대기 상태면 자동으로 진행중으로 전환 + $this->autoStartWorkOrderOnMaterialInput($workOrder, $tenantId); + return [ 'work_order_id' => $workOrderId, 'material_count' => count($allResults), diff --git a/app/Swagger/v1/ProductionOrderApi.php b/app/Swagger/v1/ProductionOrderApi.php index cbd1ed6..bed878d 100644 --- a/app/Swagger/v1/ProductionOrderApi.php +++ b/app/Swagger/v1/ProductionOrderApi.php @@ -14,11 +14,12 @@ * @OA\Property(property="order_no", type="string", example="ORD-20260301-0001", description="수주번호 (= 생산지시번호)"), * @OA\Property(property="site_name", type="string", example="서울현장", nullable=true, description="현장명"), * @OA\Property(property="client_name", type="string", example="(주)고객사", nullable=true, description="거래처명"), - * @OA\Property(property="quantity", type="number", example=10, description="수량"), + * @OA\Property(property="quantity", type="number", example=232, description="부품수량 합계"), + * @OA\Property(property="node_count", type="integer", example=4, description="개소수 (order_nodes 수)"), * @OA\Property(property="delivery_date", type="string", format="date", example="2026-03-15", nullable=true, description="납기일"), - * @OA\Property(property="production_ordered_at", type="string", format="date-time", nullable=true, description="생산지시일 (첫 WorkOrder 생성일)"), + * @OA\Property(property="production_ordered_at", type="string", format="date", example="2026-02-21", nullable=true, description="생산지시일 (첫 WorkOrder 생성일, Y-m-d)"), * @OA\Property(property="production_status", type="string", enum={"waiting","in_production","completed"}, example="waiting", description="생산 상태"), - * @OA\Property(property="work_orders_count", type="integer", example=3, description="작업지시 수"), + * @OA\Property(property="work_orders_count", type="integer", example=2, description="작업지시 수 (공정별 1건)"), * @OA\Property(property="work_order_progress", type="object", * @OA\Property(property="total", type="integer", example=3), * @OA\Property(property="completed", type="integer", example=1), @@ -47,8 +48,9 @@ * description="생산지시 상세", * * @OA\Property(property="order", ref="#/components/schemas/ProductionOrderListItem"), - * @OA\Property(property="production_ordered_at", type="string", format="date-time", nullable=true), + * @OA\Property(property="production_ordered_at", type="string", format="date", example="2026-02-21", nullable=true), * @OA\Property(property="production_status", type="string", enum={"waiting","in_production","completed"}), + * @OA\Property(property="node_count", type="integer", example=4, description="개소수"), * @OA\Property(property="work_order_progress", type="object", * @OA\Property(property="total", type="integer"), * @OA\Property(property="completed", type="integer"),