*/ public function catalogProducts(): array { return [ 'whole_blood' => ['label' => 'Whole blood', 'reorder_level' => 2], 'packed_rbc' => ['label' => 'Packed RBC', 'reorder_level' => 2], 'platelets' => ['label' => 'Platelets', 'reorder_level' => 2], 'ffp' => ['label' => 'FFP', 'reorder_level' => 2], 'cryo' => ['label' => 'Cryoprecipitate', 'reorder_level' => 1], ]; } /** * Ensure catalog rows exist for the branch, then return them ordered. * * @return Collection */ public function ensureStockRows( Organization $organization, string $ownerRef, ?int $branchId, ): Collection { foreach ($this->catalogProducts() as $code => $meta) { BloodBankStock::query()->firstOrCreate( [ 'organization_id' => $organization->id, 'branch_id' => $branchId, 'product_code' => $code, ], [ 'owner_ref' => $ownerRef, 'product_label' => $meta['label'], 'units_on_hand' => 0, 'reorder_level' => $meta['reorder_level'], ], ); } return BloodBankStock::owned($ownerRef) ->where('organization_id', $organization->id) ->when( $branchId === null, fn ($q) => $q->whereNull('branch_id'), fn ($q) => $q->where('branch_id', $branchId), ) ->orderBy('product_label') ->get(); } /** * @param list $rows * @return Collection */ public function updateStock( Organization $organization, string $ownerRef, ?int $branchId, array $rows, ?string $actorRef = null, ): Collection { $allowed = array_keys($this->catalogProducts()); return DB::transaction(function () use ($organization, $ownerRef, $branchId, $rows, $actorRef, $allowed) { $this->ensureStockRows($organization, $ownerRef, $branchId); foreach ($rows as $row) { $code = (string) ($row['product_code'] ?? ''); if (! in_array($code, $allowed, true)) { continue; } $stock = BloodBankStock::owned($ownerRef) ->where('organization_id', $organization->id) ->where('product_code', $code) ->when( $branchId === null, fn ($q) => $q->whereNull('branch_id'), fn ($q) => $q->where('branch_id', $branchId), ) ->first(); if (! $stock) { continue; } $stock->update([ 'units_on_hand' => max(0, (int) ($row['units_on_hand'] ?? 0)), 'reorder_level' => max(0, (int) ($row['reorder_level'] ?? $stock->reorder_level)), 'notes' => isset($row['notes']) ? (string) $row['notes'] : $stock->notes, 'updated_by' => $actorRef, ]); } return $this->ensureStockRows($organization, $ownerRef, $branchId); }); } /** * @return array{ * stock: Collection, * low_stock: Collection, * low_stock_count: int, * total_units: int, * recent_notes: Collection * } */ public function adminOverview( Organization $organization, string $ownerRef, ?int $branchId, ): array { $stock = $this->ensureStockRows($organization, $ownerRef, $branchId); $lowStock = $stock->filter(fn (BloodBankStock $row) => $row->isLowStock())->values(); $recentNotes = 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)) ->with(['visit.patient']) ->orderByDesc('recorded_at') ->limit(8) ->get(); return [ 'stock' => $stock, 'low_stock' => $lowStock, 'low_stock_count' => $lowStock->count(), 'total_units' => (int) $stock->sum('units_on_hand'), 'recent_notes' => $recentNotes, ]; } }