Files
ladill-care/app/Services/Care/PlanService.php
T
isaacclad c6fd2547ef
Deploy Ladill Care / deploy (push) Successful in 40s
Add branch seat selector on Care plan checkout.
Let operators pick how many branches to bill next to the monthly/prepaid toggles so Pro and Enterprise totals and charges use branches × per-branch rate.
2026-07-15 14:21:30 +00:00

160 lines
5.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Services\Care;
use App\Models\Branch;
use App\Models\Organization;
use Carbon\Carbon;
class PlanService
{
public function planKey(Organization $organization): string
{
$settings = $organization->settings ?? [];
$plan = (string) ($settings['plan'] ?? 'free');
if (in_array($plan, ['pro', 'enterprise'], true) && ! empty($settings['plan_expires_at'])) {
if (Carbon::parse($settings['plan_expires_at'])->isPast()) {
return 'free';
}
}
return array_key_exists($plan, config('care.plans', [])) ? $plan : 'free';
}
public function plan(Organization $organization): string
{
return $this->planKey($organization);
}
public function isPro(Organization $organization): bool
{
return $this->planKey($organization) === 'pro';
}
public function isEnterprise(Organization $organization): bool
{
return $this->planKey($organization) === 'enterprise';
}
public function hasPaidPlan(Organization $organization): bool
{
return in_array($this->planKey($organization), ['pro', 'enterprise'], true);
}
/** Lab, pharmacy, billing, and Ladill Queue integration require Pro or Enterprise. */
public function canUseProModules(Organization $organization): bool
{
return $this->hasPaidPlan($organization);
}
public function canUseQueueIntegration(Organization $organization): bool
{
return $this->hasPaidPlan($organization);
}
public function activeBranchCount(Organization $organization): int
{
return Branch::query()
->where('organization_id', $organization->id)
->where('is_active', true)
->count();
}
public function maxBranches(Organization $organization): ?int
{
$plan = config('care.plans.'.$this->planKey($organization));
$max = $plan['max_branches'] ?? null;
return $max === null ? null : (int) $max;
}
public function canAddBranch(Organization $organization, int $currentCount): bool
{
$max = $this->maxBranches($organization);
return $max === null || $currentCount < $max;
}
public function branchLimitMessage(Organization $organization): string
{
$max = $this->maxBranches($organization);
$key = $this->planKey($organization);
if ($key === 'free') {
return 'Your Free plan allows 1 branch. Upgrade to Care Pro for unlimited branches (billed per branch).';
}
if ($key === 'pro' && $max !== null) {
return "Your Pro plan allows up to {$max} branches (billed per branch).";
}
return 'Branch limit reached for your current plan.';
}
public function proPricePerBranchMinor(): int
{
return (int) config(
'care.plans.pro.price_minor_per_branch',
config('care.plans.pro.price_minor', 199000),
);
}
/** @deprecated Prefer proPricePerBranchMinor() — Pro is billed per branch. */
public function proPriceMinor(): int
{
return $this->proPricePerBranchMinor();
}
public function enterprisePricePerBranchMinor(): int
{
return (int) config(
'care.plans.enterprise.price_minor_per_branch',
config('care.plans.enterprise.price_minor', 499000),
);
}
/** Total monthly amount for Pro = active branches × per-branch rate (min 1). */
public function proPriceTotalMinor(Organization $organization): int
{
return $this->priceForBranches('pro', $this->activeBranchCount($organization));
}
/** Total monthly amount for Enterprise = active branches × per-branch rate (min 1). */
public function enterprisePriceMinor(Organization $organization): int
{
return $this->priceForBranches('enterprise', $this->activeBranchCount($organization));
}
public function monthlyPriceMinor(Organization $organization, string $plan): int
{
return $this->priceForBranches($plan, $this->activeBranchCount($organization));
}
/** Fixed branch count × per-branch rate (for checkout when the user picks branch seats). */
public function priceForBranches(string $plan, int $branches): int
{
$branches = max(1, $branches);
$rate = $plan === 'enterprise'
? $this->enterprisePricePerBranchMinor()
: $this->proPricePerBranchMinor();
return $branches * $rate;
}
public function resolveCheckoutBranches(Organization $organization, int $requested): int
{
$branches = max(1, $requested);
// Don't under-bill vs sites already configured in the account.
$active = max(1, $this->activeBranchCount($organization));
return max($branches, $active);
}
public function canSubscribeEnterprise(Organization $organization): bool
{
// Enterprise is a feature tier (not branch-gated). min_branches defaults to 1.
return $this->activeBranchCount($organization) >= (int) config('care.enterprise.min_branches', 1);
}
}