tenantId(); $query = Order::query() ->where('tenant_id', $tenantId) ->whereIn('status_code', self::PRODUCTION_STATUSES) ->with(['client', 'workOrders.process', 'workOrders.assignees.user']) ->withCount('workOrders'); // 검색어 필터 if (! empty($params['search'])) { $search = $params['search']; $query->where(function ($q) use ($search) { $q->where('order_no', 'like', "%{$search}%") ->orWhere('client_name', 'like', "%{$search}%") ->orWhere('site_name', 'like', "%{$search}%"); }); } // 생산 상태 필터 if (! empty($params['production_status'])) { switch ($params['production_status']) { case 'waiting': $query->where('status_code', Order::STATUS_IN_PROGRESS); break; case 'in_production': $query->where('status_code', Order::STATUS_IN_PRODUCTION); break; case 'completed': $query->whereIn('status_code', [ Order::STATUS_PRODUCED, Order::STATUS_SHIPPING, Order::STATUS_SHIPPED, ]); break; } } // 정렬 $sortBy = $params['sort_by'] ?? 'created_at'; $sortDir = $params['sort_dir'] ?? 'desc'; $query->orderBy($sortBy, $sortDir); // 페이지네이션 $perPage = $params['per_page'] ?? 20; $result = $query->paginate($perPage); // 가공 필드 추가 $result->getCollection()->transform(function (Order $order) { $order->production_ordered_at = $order->workOrders->min('created_at'); $workOrders = $order->workOrders; $order->work_order_progress = [ 'total' => $workOrders->count(), 'completed' => $workOrders->where('status', 'completed')->count() + $workOrders->where('status', 'shipped')->count(), 'in_progress' => $workOrders->where('status', 'in_progress')->count(), ]; // 프론트 탭용 production_status 매핑 $order->production_status = $this->mapProductionStatus($order->status_code); return $order; }); return $result; } /** * 상태별 통계 */ public function stats(): array { $tenantId = $this->tenantId(); $waiting = Order::where('tenant_id', $tenantId) ->where('status_code', Order::STATUS_IN_PROGRESS) ->count(); $inProduction = Order::where('tenant_id', $tenantId) ->where('status_code', Order::STATUS_IN_PRODUCTION) ->count(); $completed = Order::where('tenant_id', $tenantId) ->whereIn('status_code', [ Order::STATUS_PRODUCED, Order::STATUS_SHIPPING, Order::STATUS_SHIPPED, ]) ->count(); return [ 'total' => $waiting + $inProduction + $completed, 'waiting' => $waiting, 'in_production' => $inProduction, 'completed' => $completed, ]; } /** * 생산지시 상세 조회 */ public function show(int $orderId): array { $tenantId = $this->tenantId(); $order = Order::query() ->where('tenant_id', $tenantId) ->whereIn('status_code', self::PRODUCTION_STATUSES) ->with([ 'client', 'workOrders.process', 'workOrders.items', 'workOrders.assignees.user', 'nodes', ]) ->findOrFail($orderId); // 생산지시일 $order->production_ordered_at = $order->workOrders->min('created_at'); $order->production_status = $this->mapProductionStatus($order->status_code); // WorkOrder 진행 현황 $workOrderProgress = [ 'total' => $order->workOrders->count(), 'completed' => $order->workOrders->where('status', 'completed')->count() + $order->workOrders->where('status', 'shipped')->count(), 'in_progress' => $order->workOrders->where('status', 'in_progress')->count(), ]; // WorkOrder 목록 가공 $workOrders = $order->workOrders->map(function ($wo) { return [ 'id' => $wo->id, 'work_order_no' => $wo->work_order_no, 'process_name' => $wo->process?->process_name ?? '', 'quantity' => $wo->items->count(), 'status' => $wo->status, 'assignees' => $wo->assignees->map(fn ($a) => $a->user?->name ?? '')->filter()->values()->toArray(), ]; }); // BOM 데이터 (order_nodes에서 추출) $bomProcessGroups = $this->extractBomProcessGroups($order->nodes); return [ 'order' => $order->makeHidden(['workOrders', 'nodes']), 'production_ordered_at' => $order->production_ordered_at, 'production_status' => $order->production_status, 'work_order_progress' => $workOrderProgress, 'work_orders' => $workOrders, 'bom_process_groups' => $bomProcessGroups, ]; } /** * Order status_code → 프론트 production_status 매핑 */ private function mapProductionStatus(string $statusCode): string { return match ($statusCode) { Order::STATUS_IN_PROGRESS => 'waiting', Order::STATUS_IN_PRODUCTION => 'in_production', default => 'completed', }; } /** * order_nodes에서 BOM 공정 분류 추출 */ private function extractBomProcessGroups($nodes): array { $groups = []; foreach ($nodes as $node) { $bomResult = $node->options['bom_result'] ?? null; if (! $bomResult) { continue; } // bom_result 구조에 따라 공정별 그룹화 foreach ($bomResult as $item) { $processName = $item['process_name'] ?? '기타'; if (! isset($groups[$processName])) { $groups[$processName] = [ 'process_name' => $processName, 'size_spec' => $item['size_spec'] ?? null, 'items' => [], ]; } $groups[$processName]['items'][] = [ 'id' => $item['id'] ?? null, 'item_code' => $item['item_code'] ?? '', 'item_name' => $item['item_name'] ?? '', 'spec' => $item['spec'] ?? '', 'lot_no' => $item['lot_no'] ?? '', 'required_qty' => $item['required_qty'] ?? 0, 'qty' => $item['qty'] ?? 0, ]; } } return array_values($groups); } }