get(['id', 'variation_id', 'product_id']); $erpLinked = $items->filter(fn ($i) => $i->variation_id || $i->product_id)->count(); $itemTotal = max(1, $items->count()); $lines = LineTemplate::forBusiness($businessId)->productionLines()->get(['id', 'maintenance_equipment_id', 'cmms_synced_at']); $cmmsLinked = CmmsAvailability::isReady() ? $lines->filter(fn ($l) => $l->maintenance_equipment_id)->count() : 0; $lineTotal = max(1, $lines->count()); $mboms = Bom::forBusiness($businessId)->where('bom_type', 'mbom')->with('lines.itemMaster')->get(); $gateScores = []; $totalShortageValue = 0; foreach ($mboms as $mbom) { $gate = $this->connectionGate->assessBom($mbom, 1); $gateScores[] = $gate['stats']['erp_linked'] / max(1, $gate['stats']['total_leaves']); $totalShortageValue += $gate['analysis']['totals']['shortage_value'] ?? 0; } $bomHealth = $gateScores ? round(array_sum($gateScores) / count($gateScores) * 100) : 100; return [ 'erp_link_pct' => round($erpLinked / $itemTotal * 100), 'cmms_link_pct' => CmmsAvailability::isReady() ? round($cmmsLinked / $lineTotal * 100) : 0, 'cmms_available' => CmmsAvailability::isReady(), 'bom_integration_health' => $bomHealth, 'total_shortage_value' => $totalShortageValue, 'open_ecos' => EngineeringChangeOrder::forBusiness($businessId)->whereIn('status', ['draft', 'review'])->count(), 'draft_revisions' => LineRevision::forBusiness($businessId)->where('status', 'draft')->count(), 'pending_releases' => ReleasePackage::forBusiness($businessId)->whereIn('status', ['draft', 'pending_approval'])->count(), 'active_research' => ResearchProject::forBusiness($businessId)->where('status', 'active')->count(), 'mrp_runs_month' => MrpRun::forBusiness($businessId)->where('created_at', '>=', now()->startOfMonth())->count(), 'products' => ProductPlatform::forBusiness($businessId)->count(), 'production_lines' => $lineTotal, 'recent_ecos' => EngineeringChangeOrder::forBusiness($businessId) ->with(['lineRevision', 'requestedBy']) ->latest('id') ->limit(5) ->get(), 'lines_needing_attention' => $this->linesNeedingAttention($businessId), ]; } protected function linesNeedingAttention(int $businessId): array { $result = []; $lines = LineTemplate::forBusiness($businessId) ->productionLines() ->with(['revisions.boms', 'productPlatform']) ->get(); foreach ($lines as $line) { $assessment = $this->assessLine($line); if ($assessment['status'] !== 'ok') { $result[] = $assessment; } } return array_slice($result, 0, 8); } public function assessLine(LineTemplate $line): array { $revision = $line->revisions->sortByDesc('id')->first(); $mbom = $revision?->boms->where('bom_type', 'mbom')->first(); if (! $mbom) { return [ 'line' => $line, 'status' => 'no_mbom', 'issues' => [__('industrialengineering::lang.issue_no_mbom')], 'can_run_mrp' => false, 'erp_unlinked' => 0, ]; } $gate = $this->connectionGate->assessBom($mbom, 1); $issues = array_merge($gate['blockers'], $gate['warnings']); $cmmsMissing = CmmsAvailability::isReady() && ! $line->maintenance_equipment_id; if ($cmmsMissing) { $issues[] = __('industrialengineering::lang.issue_no_cmms'); } $status = 'ok'; if (! empty($gate['blockers'])) { $status = 'empty_bom'; } elseif ($gate['stats']['unlinked'] > 0) { $status = 'erp_pending'; } elseif ($cmmsMissing) { $status = 'cmms_pending'; } return [ 'line' => $line->fresh(), 'status' => $status, 'issues' => array_slice($issues, 0, 5), 'can_run_mrp' => (bool) ($gate['can_run_mrp'] ?? false), 'erp_unlinked' => (int) ($gate['stats']['unlinked'] ?? 0), 'cmms_linked' => (bool) $line->maintenance_equipment_id, ]; } }