fix(secretary): scope plan limit to the acting environment

Secretary limit always read the doctor's plan, so a clinic on the
professional plan hit the doctor's free-plan cap of one secretary.

Limit and count are now per environment: clinic env uses the clinic
subscription and counts the clinic's active secretaries, personal env
uses the doctor's own plan. Counting is per person, not per row, so a
secretary linked to several doctors of one clinic counts once, and
re-linking an already-active person never trips the cap.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamed
2026-08-20 14:38:40 +03:30
co-authored by Claude Opus 5
parent 5fdc232e65
commit 25a12e9a06
4 changed files with 89 additions and 18 deletions
+56 -5
View File
@@ -11,6 +11,7 @@ use App\Secretary\Entity\DoctorSecretary;
use App\Secretary\Repository\DoctorSecretaryRepository;
use App\Sms\Entity\SmsLog;
use App\Sms\Service\SmsService;
use App\Subscription\Entity\SubscriptionPlan;
use App\Subscription\Service\SubscriptionService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
@@ -60,11 +61,61 @@ class SecretaryService
return $user;
}
public function doctorAtSecretaryLimit(Doctor $doctor): bool
/**
* سقف منشیِ **محیط**؛ محیط کلینیک و محیط مطب شخصی هرکدام سقف و شمارش جدا دارند.
*
* منشی روی ردیفِ پزشک ثبت می‌شود، ولی صاحبِ سقف محیط است: در کلینیک پلنِ کلینیک،
* در مطب شخصی پلنِ خودِ پزشک. بدون این، کلینیکِ حرفه‌ای به سقف پلن free پزشک
* می‌خورد و پلنِ کلینیک بی‌اثر می‌ماند.
*/
public function secretaryLimitFor(?Clinic $clinic, ?Doctor $doctor = null): int
{
$limit = $this->subscriptionService->getSecretaryLimit('doctor', $doctor->getId());
if ($clinic !== null) {
return $this->subscriptionService->getSecretaryLimit('clinic', $clinic->getId());
}
return $this->secretaryRepo->countActiveByDoctor($doctor) >= $limit;
return $doctor !== null
? $this->subscriptionService->getSecretaryLimit('doctor', $doctor->getId())
: SubscriptionPlan::UNLIMITED;
}
/** تعداد منشیِ فعالِ محیط، به تفکیک شخص (نه ردیف). */
public function secretaryCountFor(?Clinic $clinic, ?Doctor $doctor = null): int
{
if ($clinic !== null) {
return $this->secretaryRepo->countActiveDistinctSecretariesByClinic($clinic);
}
return $doctor !== null
? $this->secretaryRepo->countActiveDistinctSecretariesByDoctorScope($doctor)
: 0;
}
/**
* آیا افزودنِ این منشی به این محیط از سقف پلن عبور می‌کند؟
*
* $secretary وقتی داده شود و همان شخص از قبل در همین محیط فعال باشد، نفر تازه‌ای
* اضافه نمی‌شود؛ وصل‌کردن او به پزشکِ دومِ همان کلینیک نباید سقف بخورد.
*/
public function atSecretaryLimit(Doctor $doctor, ?Clinic $clinic = null, ?User $secretary = null): bool
{
$limit = $this->secretaryLimitFor($clinic, $doctor);
if ($limit === SubscriptionPlan::UNLIMITED) {
return false;
}
if ($secretary !== null && $this->secretaryIsActiveIn($secretary, $doctor, $clinic)) {
return false;
}
return $this->secretaryCountFor($clinic, $doctor) >= $limit;
}
private function secretaryIsActiveIn(User $secretary, Doctor $doctor, ?Clinic $clinic): bool
{
return $clinic !== null
? $this->secretaryRepo->findActiveBySecretaryForClinic($secretary, $clinic) !== null
: $this->secretaryRepo->findActiveBySecretaryForDoctor($secretary, $doctor) !== null;
}
/**
@@ -102,7 +153,7 @@ class SecretaryService
continue;
}
if ($this->doctorAtSecretaryLimit($doctor)) {
if ($this->atSecretaryLimit($doctor, $clinic, $secretary)) {
$skippedLimit[] = $doctorUuid;
continue;
}
@@ -162,7 +213,7 @@ class SecretaryService
}
continue;
}
if ($this->doctorAtSecretaryLimit($doctor)) {
if ($this->atSecretaryLimit($doctor, $clinic, $secretary)) {
$skippedLimit[] = $doctorUuid;
continue;
}