feat: implement domain guard for commission calculation and enhance representation dashboard

- Added domain guard in CommissionService to ensure commission is calculated only when the appointment is booked under the same representation as the doctor.
- Updated RepresentationController to filter statistics by representation, ensuring accurate data is shown for each representative.
- Introduced new endpoints for the representation dashboard to provide summary statistics, doctor performance, and financial reports.
- Created new pages for RepresentationFinance and RepresentationSettlement to display financial data and allow for settlement requests.
- Added migration to include booking_representation_id in appointments for tracking the representative under which the appointment was booked.
This commit is contained in:
hamed
2026-06-24 16:14:41 +03:30
parent 89e4a424f8
commit 9603b702c1
18 changed files with 928 additions and 54 deletions
@@ -32,6 +32,7 @@ class AppointmentController extends BaseController
private readonly SlotCalculatorService $slotCalculator,
private readonly PatientService $patientService,
private readonly WeeklyScheduleRepository $scheduleRepo,
private readonly \App\Representation\Repository\RepresentationRepository $representationRepo,
) {}
// ── Public: available slots ───────────────────────────────────────────────
@@ -259,6 +260,15 @@ class AppointmentController extends BaseController
$appointment->setPatientGender($gender);
if (isset($data['note'])) $appointment->setNote($data['note']);
// نماینده‌ی دامنه‌ی جاری (city_id از سایت)؛ برای گاردِ پورسانت.
$cityId = (int) ($data['city_id'] ?? 0);
if ($cityId > 0) {
$bookingRep = $this->representationRepo->findActiveByCityId($cityId);
if ($bookingRep !== null) {
$appointment->setBookingRepresentationId($bookingRep->getId());
}
}
// آدرس نوبت از روی session متناظر در برنامه‌ی هفتگی تعیین می‌شود (location_id).
$locationId = $this->resolveSlotLocationId($doctor, $slotStart);
if ($locationId !== null) {
+5
View File
@@ -86,6 +86,9 @@ class Appointment
#[ORM\Column(name: 'address_id', type: 'integer', nullable: true)]
private ?int $addressId = null;
#[ORM\Column(name: 'booking_representation_id', type: 'integer', nullable: true)]
private ?int $bookingRepresentationId = null;
#[ORM\Column(name: 'created_at', type: 'integer')]
private int $createdAt;
@@ -119,8 +122,10 @@ class Appointment
public function getPatientGender(): ?string { return $this->patientGender; }
public function getPatientReason(): ?string { return $this->patientReason; }
public function getAddressId(): ?int { return $this->addressId; }
public function getBookingRepresentationId(): ?int { return $this->bookingRepresentationId; }
public function setNote(?string $v): self { $this->note = $v; return $this; }
public function setBookingRepresentationId(?int $v): self { $this->bookingRepresentationId = $v; return $this; }
public function setAddressId(?int $v): self { $this->addressId = $v; return $this; }
public function setPatientName(?string $v): self { $this->patientName = $v; return $this; }
public function setPatientMobile(?string $v): self { $this->patientMobile = $v; return $this; }