perf(appointment): batch-fetch pending payments in expiry loop (fix N+1)

AppointmentExpiryService ran one findPendingByAppointment query per expiring
booking. Add PaymentRepository::findPendingByAppointments (one IN query keyed
by appointment id) and use it. Test covers expiry + payment cancellation for
several appointments at once.
This commit is contained in:
hamed
2026-06-28 17:10:32 +03:30
parent 36b87e9817
commit e1740462b5
3 changed files with 87 additions and 1 deletions
@@ -62,6 +62,36 @@ class PaymentRepository extends ServiceEntityRepository
]);
}
/**
* Batch variant of findPendingByAppointment: all pending payments for the
* given appointments in ONE query, keyed by appointment id. Avoids the N+1
* in AppointmentExpiryService when many bookings expire at once.
*
* @param Appointment[] $appointments
* @return array<int, Payment> appointment id => pending Payment
*/
public function findPendingByAppointments(array $appointments): array
{
if ($appointments === []) {
return [];
}
$payments = $this->createQueryBuilder('p')
->andWhere('p.appointment IN (:appointments)')
->andWhere('p.status = :status')
->setParameter('appointments', $appointments)
->setParameter('status', Payment::STATUS_PENDING)
->getQuery()
->getResult();
$byAppointment = [];
foreach ($payments as $payment) {
$byAppointment[$payment->getAppointment()->getId()] = $payment;
}
return $byAppointment;
}
public function save(Payment $entity, bool $flush = true): void
{
$this->getEntityManager()->persist($entity);