startOfDay(); $rangeFrom = $from ? Carbon::parse($from)->startOfDay() : now()->startOfMonth(); $rangeTo = $to ? Carbon::parse($to)->endOfDay() : now()->endOfDay(); $departmentIds = $this->shell->departmentIds($organization, 'blood_bank', $ownerRef, $branchId); $appointmentBase = Appointment::owned($ownerRef) ->where('organization_id', $organization->id) ->when($departmentIds !== [], fn ($q) => $q->whereIn('department_id', $departmentIds)) ->when($departmentIds === [], fn ($q) => $q->whereRaw('1 = 0')) ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)); $visitIds = (clone $appointmentBase)->whereNotNull('visit_id')->pluck('visit_id'); $requestsToday = SpecialtyClinicalRecord::owned($ownerRef) ->where('organization_id', $organization->id) ->where('module_key', 'blood_bank') ->where('record_type', 'blood_request') ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->where('recorded_at', '>=', $todayStart) ->count(); $completedToday = (clone $appointmentBase) ->where('status', Appointment::STATUS_COMPLETED) ->where('completed_at', '>=', $todayStart) ->count(); $requestRecords = SpecialtyClinicalRecord::owned($ownerRef) ->where('organization_id', $organization->id) ->where('module_key', 'blood_bank') ->where('record_type', 'blood_request') ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->whereBetween('recorded_at', [$rangeFrom, $rangeTo]) ->get(); $urgencyMix = $requestRecords ->groupBy(fn (SpecialtyClinicalRecord $r) => (string) ($r->payload['urgency'] ?? 'Unknown')) ->map(fn (Collection $rows, string $urgency) => [ 'urgency' => $urgency, 'total' => $rows->count(), ]) ->values() ->sortByDesc('total') ->values(); $openVisitIds = Visit::owned($ownerRef) ->where('organization_id', $organization->id) ->whereIn('id', $visitIds->isEmpty() ? [0] : $visitIds) ->whereIn('status', [Visit::STATUS_OPEN, Visit::STATUS_IN_PROGRESS]) ->pluck('id'); $highUrgencyOpen = SpecialtyClinicalRecord::owned($ownerRef) ->where('organization_id', $organization->id) ->where('module_key', 'blood_bank') ->where('record_type', 'blood_request') ->whereIn('visit_id', $openVisitIds->isEmpty() ? [0] : $openVisitIds) ->get() ->filter(function (SpecialtyClinicalRecord $r) { $urgency = strtolower((string) ($r->payload['urgency'] ?? '')); return str_contains($urgency, 'urgent') || str_contains($urgency, 'emergency') || str_contains($urgency, 'massive'); }) ->count(); $issueRecords = SpecialtyClinicalRecord::owned($ownerRef) ->where('organization_id', $organization->id) ->where('module_key', 'blood_bank') ->where('record_type', 'issue_note') ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->whereBetween('recorded_at', [$rangeFrom, $rangeTo]) ->get(); $productsIssued = $issueRecords ->groupBy(fn (SpecialtyClinicalRecord $r) => (string) ($r->payload['product'] ?? 'Unknown')) ->map(fn (Collection $rows, string $product) => [ 'product' => $product, 'total' => $rows->sum(fn (SpecialtyClinicalRecord $r) => (int) ($r->payload['units_issued'] ?? 0)), 'issues' => $rows->count(), ]) ->values() ->sortByDesc('total') ->values(); // Fall back to request issued_units when no dedicated issue notes exist. if ($productsIssued->isEmpty()) { $productsIssued = $requestRecords ->filter(fn (SpecialtyClinicalRecord $r) => (int) ($r->payload['issued_units'] ?? 0) > 0) ->groupBy(fn (SpecialtyClinicalRecord $r) => (string) ($r->payload['product'] ?? 'Unknown')) ->map(fn (Collection $rows, string $product) => [ 'product' => $product, 'total' => $rows->sum(fn (SpecialtyClinicalRecord $r) => (int) ($r->payload['issued_units'] ?? 0)), 'issues' => $rows->count(), ]) ->values() ->sortByDesc('total') ->values(); } $lowStockFlags = SpecialtyClinicalRecord::owned($ownerRef) ->where('organization_id', $organization->id) ->where('module_key', 'blood_bank') ->where('record_type', 'inventory_note') ->when($branchId, fn ($q) => $q->where('branch_id', $branchId)) ->whereBetween('recorded_at', [$rangeFrom, $rangeTo]) ->get() ->filter(function (SpecialtyClinicalRecord $r) { if (! empty($r->payload['low_stock_alert'])) { return true; } foreach (['whole_blood_units', 'packed_rbc_units', 'platelet_units', 'ffp_units'] as $key) { if (isset($r->payload[$key]) && (int) $r->payload[$key] <= 2) { return true; } } return false; }) ->count(); $serviceLabels = collect($this->shell->provisionedServices($organization, 'blood_bank')) ->pluck('label') ->filter() ->values() ->all(); $revenueByService = BillLineItem::query() ->where('owner_ref', $ownerRef) ->where('source_type', 'specialty_service') ->whereIn('description', $serviceLabels ?: ['__none__']) ->whereBetween('created_at', [$rangeFrom, $rangeTo]) ->whereHas('bill', function ($q) use ($organization, $visitIds) { $q->where('organization_id', $organization->id) ->whereIn('visit_id', $visitIds->isEmpty() ? [0] : $visitIds) ->whereNotIn('status', [Bill::STATUS_VOID]); }) ->selectRaw('description, count(*) as total, sum(total_minor) as amount_minor') ->groupBy('description') ->orderByDesc('amount_minor') ->get(); return [ 'requests_today' => $requestsToday, 'completed_today' => $completedToday, 'high_urgency_open' => $highUrgencyOpen, 'urgency_mix' => $urgencyMix, 'products_issued' => $productsIssued, 'low_stock_flags' => $lowStockFlags, 'revenue_by_service' => $revenueByService, 'from' => $rangeFrom->toDateString(), 'to' => $rangeTo->toDateString(), ]; } }