feat: add visit price requirement feature

- Introduced a new boolean flag `require_visit_price` in the `EntityInsurancePricing` to enforce visit price for appointments.
- Updated the appointment creation endpoints to validate `visit_price_rials` based on the new flag.
- Added `visit_price_rials` field to the `Appointment` entity to store the visit price.
- Enhanced the `PatientService` to validate visit price during session creation.
- Updated API documentation to reflect changes in appointment and insurance pricing.
- Implemented a new service `VisitPriceRequirementResolver` to determine if a visit price is required for a doctor based on their pricing settings.
- Added migrations to update the database schema for the new fields.
This commit is contained in:
hamed
2026-07-16 19:44:30 +03:30
parent 880c4c06cb
commit a0ddb4c0d1
18 changed files with 497 additions and 22 deletions
@@ -12,6 +12,7 @@ use App\Auth\Repository\UserActiveContextRepository;
use App\Clinic\Repository\ClinicRepository;
use App\Doctor\Entity\Doctor;
use App\Doctor\Repository\DoctorRepository;
use App\Insurance\Service\VisitPriceRequirementResolver;
use App\Patient\Service\PatientResolver;
use App\Secretary\Entity\DoctorSecretary;
use App\Secretary\Repository\DoctorSecretaryRepository;
@@ -42,6 +43,7 @@ class MyAppointmentsController extends BaseController
private readonly PatientResolver $patientResolver,
private readonly \App\Auth\Repository\UserRepository $userRepo,
private readonly \App\UserProfile\Repository\UserProfileRepository $profileRepo,
private readonly VisitPriceRequirementResolver $visitPriceResolver,
) {}
#[Route('/api/v1/my/appointment', methods: ['POST'])]
@@ -127,6 +129,11 @@ class MyAppointmentsController extends BaseController
return $this->error(ErrorCodes::FORBIDDEN, 'برای این پزشک مجاز به ثبت نوبت نیستید', 403);
}
$visitPriceRials = isset($data['visit_price_rials']) ? (int) $data['visit_price_rials'] : null;
if ($this->visitPriceResolver->isRequiredForDoctor($doctor) && ($visitPriceRials ?? 0) <= 0) {
return $this->error(ErrorCodes::VALIDATION, 'هزینه ویزیت الزامی است', 422, 'visit_price_rials');
}
// Identity is keyed on the national code (unique) so the case-file stays
// single per person even when booked under a different mobile.
$patient = $this->patientResolver->resolveForBooking($nationalCode, $mobile, $patientName);
@@ -163,6 +170,9 @@ class MyAppointmentsController extends BaseController
if (isset($data['deposit_amount_rials'])) {
$appointment->setDepositAmountRials((int) $data['deposit_amount_rials']);
}
if ($visitPriceRials !== null) {
$appointment->setVisitPriceRials($visitPriceRials);
}
// The resolver returns the patient keyed on national code, so its profile
// name is the real identity. Snapshot that (not the free-typed modal name)
// so the appointment never diverges from an existing profile; fall back to
+6
View File
@@ -159,6 +159,9 @@ class Appointment
#[ORM\Column(name: 'deposit_amount_rials', type: 'integer', nullable: true)]
private ?int $depositAmountRials = null;
#[ORM\Column(name: 'visit_price_rials', type: 'integer', nullable: true)]
private ?int $visitPriceRials = null;
/**
* Reserve-list entry (نوبت رزرو): booked for a day, not a time slot.
* slotStart/slotEnd hold that day's midnight so date queries keep working.
@@ -245,6 +248,7 @@ class Appointment
public function getStaff(): ?\App\Staff\Entity\ClinicStaff { return $this->staff; }
public function isDepositRequired(): bool { return $this->depositRequired; }
public function getDepositAmountRials(): ?int { return $this->depositAmountRials; }
public function getVisitPriceRials(): ?int { return $this->visitPriceRials; }
public function isReserve(): bool { return $this->isReserve; }
public function setServiceSection(?\App\ClinicService\Entity\ServiceSection $v): self { $this->serviceSection = $v; return $this; }
@@ -252,6 +256,7 @@ class Appointment
public function setStaff(?\App\Staff\Entity\ClinicStaff $v): self { $this->staff = $v; return $this; }
public function setDepositRequired(bool $v): self { $this->depositRequired = $v; return $this; }
public function setDepositAmountRials(?int $v): self { $this->depositAmountRials = $v; return $this; }
public function setVisitPriceRials(?int $v): self { $this->visitPriceRials = $v; return $this; }
/**
* Move the appointment to a new slot (جا به جایی نوبت) and/or flip its
@@ -347,6 +352,7 @@ class Appointment
'staff' => $this->staff ? ['uuid' => $this->staff->getUuid(), 'full_name' => $this->staff->getFullName()] : null,
'deposit_required' => $this->depositRequired,
'deposit_amount_rials' => $this->depositAmountRials,
'visit_price_rials' => $this->visitPriceRials,
'is_reserve' => $this->isReserve,
'version' => $this->version,
'created_at' => $this->createdAt,