em->getConnection(); for ($seat = 0; $seat < $resource->getCapacity(); $seat++) { $occupancy = new ResourceOccupancy($resource, $startsAt, $endsAt, $status); $occupancy->setSegmentName($segmentName); $occupancy->setHoldId($holdId); $occupancy->setAppointmentId($appointmentId); $this->em->persist($occupancy); $this->em->flush(); try { foreach ($buckets as $bucketAt) { $connection->insert('resource_occupancy_buckets', [ 'resource_id' => $resource->getId(), 'occupancy_id' => $occupancy->getId(), 'bucket_at' => $bucketAt, 'seat' => $seat, ]); } return $occupancy; } catch (UniqueConstraintViolationException) { // این صندلی همین حالا گرفته شد. سطل‌های نیمه‌نوشته و خودِ ردیف اشغال // پاک می‌شوند تا صندلی بعدی از صفر شروع کند. $connection->delete('resource_occupancy_buckets', ['occupancy_id' => $occupancy->getId()]); $this->em->remove($occupancy); $this->em->flush(); } } throw new AppException( ErrorCodes::ERR_SLOT_TAKEN, sprintf('«%s» در این زمان آزاد نیست', $resource->getName()), 409, 'resource_uuid', ); } /** * آزادسازی: وضعیت `released` و پاک کردن سطل‌ها. * * خودِ ردیف اشغال می‌ماند چون تاریخچهٔ بهره‌وری است؛ ولی سطل‌ها باید بروند وگرنه * کلید یکتا آن زمان را برای همیشه قفل نگه می‌دارد. * * @param iterable $occupancies */ public function release(iterable $occupancies): void { $connection = $this->em->getConnection(); $released = false; foreach ($occupancies as $occupancy) { $connection->delete('resource_occupancy_buckets', ['occupancy_id' => $occupancy->getId()]); $occupancy->markReleased(); $released = true; } if ($released) { $this->em->flush(); } } /** * آزادکردنِ هرچه این نوبت گرفته بود — هنگام لغو، انقضا یا جابه‌جایی. * * @return int تعداد ردیف‌های آزادشده */ public function releaseForAppointment(int $appointmentId): int { $occupancies = $this->em->getRepository(ResourceOccupancy::class)->findBy([ 'appointmentId' => $appointmentId, 'status' => ResourceOccupancy::BLOCKING_STATUSES, ]); $this->release($occupancies); return count($occupancies); } }