Files
ladill-care/app/Services/Care/CarePricingService.php
T
isaaccladandCursor 5a1d47b9cc
Deploy Ladill Care / deploy (push) Successful in 51s
Add specialty module and GP service pricing settings.
Admin dashboard module cards open per-module settings with editable service prices; GP consultation and clinic fees live under Settings → GP pricing (inventory drugs unchanged).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-18 17:19:18 +00:00

184 lines
6.8 KiB
PHP

<?php
namespace App\Services\Care;
use App\Models\Organization;
/**
* Org-level price lists for GP (general practice) and specialty module services.
* Drug prices stay on Inventory (care_drugs.unit_price_minor); lab prices on investigation types.
*/
class CarePricingService
{
/**
* @return list<array{code: string, label: string, amount_minor: int, type: string}>
*/
public function defaultGpServices(): array
{
return [
[
'code' => 'gp.consultation',
'label' => 'GP consultation',
'amount_minor' => (int) config('care.billing.consultation_fee_minor', 5000),
'type' => 'consultation',
],
[
'code' => 'gp.follow_up',
'label' => 'Follow-up consultation',
'amount_minor' => (int) config('care.billing.follow_up_fee_minor', 3000),
'type' => 'consultation',
],
[
'code' => 'gp.patient_card',
'label' => 'Patient card / registration',
'amount_minor' => (int) config('care.billing.patient_card_fee_minor', 2000),
'type' => 'misc',
],
[
'code' => 'gp.vitals',
'label' => 'Vitals / nursing charge',
'amount_minor' => (int) config('care.billing.vitals_fee_minor', 1000),
'type' => 'nursing',
],
[
'code' => 'gp.procedure',
'label' => 'Minor procedure',
'amount_minor' => (int) config('care.billing.minor_procedure_fee_minor', 5000),
'type' => 'procedure',
],
[
'code' => 'gp.home_visit',
'label' => 'Home visit',
'amount_minor' => (int) config('care.billing.home_visit_fee_minor', 15000),
'type' => 'consultation',
],
];
}
/**
* @return list<array{code: string, label: string, amount_minor: int, type: string}>
*/
public function gpServices(Organization $organization): array
{
$catalog = collect($this->defaultGpServices())->keyBy(fn ($s) => (string) $s['code']);
$stored = data_get($organization->settings, 'gp_services');
if (is_array($stored)) {
foreach ($stored as $service) {
$code = (string) ($service['code'] ?? '');
if ($code === '' || ! $catalog->has($code)) {
continue;
}
$prior = $catalog->get($code, []);
$catalog->put($code, array_merge(is_array($prior) ? $prior : [], [
'code' => $code,
'label' => (string) ($service['label'] ?? $prior['label'] ?? $code),
'amount_minor' => (int) ($service['amount_minor'] ?? $prior['amount_minor'] ?? 0),
'type' => (string) ($service['type'] ?? $prior['type'] ?? 'misc'),
]));
}
}
return $catalog->values()->all();
}
public function gpAmount(Organization $organization, string $code): int
{
$match = collect($this->gpServices($organization))
->first(fn ($s) => ($s['code'] ?? '') === $code);
return (int) ($match['amount_minor'] ?? 0);
}
public function consultationFee(Organization $organization): int
{
return $this->gpAmount($organization, 'gp.consultation');
}
public function patientCardFee(Organization $organization): int
{
return $this->gpAmount($organization, 'gp.patient_card');
}
/**
* @param list<array{code: string, amount_minor: int|string, label?: string}> $rows
* @return list<array{code: string, label: string, amount_minor: int, type: string}>
*/
public function updateGpServices(Organization $organization, array $rows): array
{
$defaults = collect($this->defaultGpServices())->keyBy(fn ($s) => (string) $s['code']);
$merged = [];
foreach ($rows as $row) {
$code = (string) ($row['code'] ?? '');
if ($code === '' || ! $defaults->has($code)) {
continue;
}
$base = $defaults->get($code);
$merged[] = [
'code' => $code,
'label' => (string) ($base['label'] ?? $code),
'amount_minor' => max(0, (int) round(((float) ($row['amount'] ?? 0)) * 100)),
'type' => (string) ($base['type'] ?? 'misc'),
];
}
// Keep any defaults not submitted.
$byCode = collect($merged)->keyBy('code');
foreach ($defaults as $code => $base) {
if (! $byCode->has($code)) {
$byCode->put($code, $base);
}
}
$list = $byCode->values()->all();
$settings = $organization->settings ?? [];
$settings['gp_services'] = $list;
$organization->update(['settings' => $settings]);
return $list;
}
/**
* Persist specialty module service amounts (major currency input → minor).
*
* @param list<array{code: string, amount?: float|int|string, label?: string}> $rows
* @return list<array{code: string, label: string, amount_minor: int, type: string}>
*/
public function updateSpecialtyServices(
Organization $organization,
string $moduleKey,
array $rows,
SpecialtyShellService $shell,
): array {
$current = collect($shell->provisionedServices($organization, $moduleKey))
->keyBy(fn ($s) => (string) ($s['code'] ?? ''));
foreach ($rows as $row) {
$code = (string) ($row['code'] ?? '');
if ($code === '' || ! $current->has($code)) {
continue;
}
$prior = $current->get($code);
$current->put($code, array_merge(is_array($prior) ? $prior : [], [
'code' => $code,
'label' => (string) ($row['label'] ?? $prior['label'] ?? $code),
'amount_minor' => max(0, (int) round(((float) ($row['amount'] ?? 0)) * 100)),
'type' => (string) ($prior['type'] ?? 'misc'),
]));
}
$list = $current->filter(fn ($s, $code) => $code !== '')->values()->all();
$settings = $organization->settings ?? [];
$provisioning = is_array($settings['specialty_module_provisioning'] ?? null)
? $settings['specialty_module_provisioning']
: [];
$record = is_array($provisioning[$moduleKey] ?? null) ? $provisioning[$moduleKey] : [];
$record['services'] = $list;
$provisioning[$moduleKey] = $record;
$settings['specialty_module_provisioning'] = $provisioning;
$organization->update(['settings' => $settings]);
return $list;
}
}