feat(secretary): implement scope separation for secretaries in clinics and personal practices

- Added `owner_type` and `clinic_id` fields to `DoctorSecretary` entity to distinguish between clinic and personal practice relationships.
- Updated repository methods to be scope-aware, allowing for specific queries based on the context of the secretary's relationship (clinic or doctor).
- Modified `SecretaryController` to handle secretary creation with appropriate scope based on the current user's role.
- Enhanced `AuthController` to build contexts that reflect the scope of the secretary's access.
- Updated `DashboardController` and `PatientController` to respect the new scope logic when retrieving data.
- Created migration to update the database schema accordingly, dropping the old unique constraint and adding the new fields and constraints.
This commit is contained in:
hamed
2026-06-15 11:19:37 +03:30
parent 5cdcec23a9
commit 4bf381ac94
10 changed files with 727 additions and 95 deletions
@@ -38,37 +38,87 @@ class DoctorSecretaryRepository extends ServiceEntityRepository
return $this->findBy(['doctor' => $doctor], ['createdAt' => 'DESC']);
}
public function isDoctorInClinic(Doctor $doctor, Clinic $clinic): bool
/** منشیان مطب شخصی یک دکتر (owner_type='doctor') */
public function findByDoctorScope(Doctor $doctor): array
{
return $clinic->getDoctors()->contains($doctor);
return $this->findBy(
['doctor' => $doctor, 'ownerType' => DoctorSecretary::OWNER_DOCTOR],
['createdAt' => 'DESC']
);
}
/** @return DoctorSecretary[] — all secretaries across all doctors of a clinic */
public function findByClinic(Clinic $clinic): array
/** رابطه منشی در scope مطب شخصی */
public function findActiveBySecretaryForDoctor(User $user, Doctor $doctor): ?DoctorSecretary
{
$doctorIds = $clinic->getDoctors()->map(fn(Doctor $d) => $d->getId())->toArray();
if (empty($doctorIds)) {
return [];
}
return $this->findOneBy([
'secretary' => $user,
'doctor' => $doctor,
'ownerType' => DoctorSecretary::OWNER_DOCTOR,
'active' => true,
]);
}
/** اولین رابطه فعال منشی در یک کلینیک (برای چک دسترسی کلی) */
public function findActiveBySecretaryForClinic(User $user, Clinic $clinic): ?DoctorSecretary
{
return $this->createQueryBuilder('s')
->where('s.secretary = :user')
->andWhere('s.clinic = :clinic')
->andWhere('s.ownerType = :type')
->andWhere('s.active = true')
->setParameter('user', $user)
->setParameter('clinic', $clinic)
->setParameter('type', DoctorSecretary::OWNER_CLINIC)
->setMaxResults(1)
->getQuery()
->getOneOrNullResult();
}
/** همه دکترهای کلینیک که این منشی به آن‌ها متصل است */
public function findDoctorsBySecretaryInClinic(User $user, Clinic $clinic): array
{
return $this->createQueryBuilder('s')
->select('d')
->join('s.doctor', 'd')
->where('d.id IN (:ids)')
->setParameter('ids', $doctorIds)
->orderBy('s.createdAt', 'DESC')
->where('s.secretary = :user')
->andWhere('s.clinic = :clinic')
->andWhere('s.ownerType = :type')
->andWhere('s.active = true')
->setParameter('user', $user)
->setParameter('clinic', $clinic)
->setParameter('type', DoctorSecretary::OWNER_CLINIC)
->getQuery()
->getResult();
}
/** همه روابط فعال یک منشی — برای auth context builder */
public function findAllActiveBySecretary(User $user): array
{
return $this->findBy(['secretary' => $user, 'active' => true]);
}
/** اولین رابطه فعال — backward compat برای کدهایی که هنوز migrate نشده‌اند */
public function findActiveBySecretary(User $user): ?DoctorSecretary
{
return $this->findOneBy(['secretary' => $user, 'active' => true]);
}
/** @return DoctorSecretary[] */
public function findAllActiveBySecretary(User $user): array
public function isDoctorInClinic(Doctor $doctor, Clinic $clinic): bool
{
return $this->findBy(['secretary' => $user, 'active' => true]);
return $clinic->getDoctors()->contains($doctor);
}
/** همه منشیان کلینیک (owner_type='clinic') */
public function findByClinic(Clinic $clinic): array
{
return $this->createQueryBuilder('s')
->where('s.clinic = :clinic')
->andWhere('s.ownerType = :type')
->setParameter('clinic', $clinic)
->setParameter('type', DoctorSecretary::OWNER_CLINIC)
->orderBy('s.createdAt', 'DESC')
->getQuery()
->getResult();
}
public function save(DoctorSecretary $entity, bool $flush = true): void