diff --git a/src/Repository/DispositionRepository.php b/src/Repository/DispositionRepository.php index 8e0c818..fcbd0c9 100644 --- a/src/Repository/DispositionRepository.php +++ b/src/Repository/DispositionRepository.php @@ -289,6 +289,178 @@ class DispositionRepository extends ServiceEntityRepository ; } + /** + * Returns feedback statistics per hotel for dispositions with status 'ended' or 'completed'. + * + * @return array + */ + public function getFeedbackStatisticsByHotel( + ?\DateTimeImmutable $dateFrom = null, + ?\DateTimeImmutable $dateTo = null, + ): array { + $qb = $this->createQueryBuilder('disposition'); + + $qb + ->select( + 'destination.hotelCode AS hotelCode', + 'destination.hotel AS hotel', + 'COUNT(disposition.id) AS totalDispositions', + 'SUM(CASE WHEN disposition.feedback IS NOT NULL THEN 1 ELSE 0 END) AS providedFeedbacks' + ) + ->innerJoin('disposition.assignment', 'assignment') + ->innerJoin('assignment.destination', 'destination') + ->leftJoin('disposition.feedback', 'feedback') + ->where($qb->expr()->in('disposition.status', ':statuses')) + ->setParameter('statuses', [Disposition::STATUS_ENDED, Disposition::STATUS_COMPLETED]) + ->groupBy('destination.hotelCode', 'destination.hotel') + ->orderBy('destination.hotel', 'ASC') + ; + + // Filter by effective date range (assignment date or fallback to destination date) + if (null !== $dateFrom) { + $qb + ->andWhere($qb->expr()->orX( + $qb->expr()->andX( + $qb->expr()->isNotNull('assignment.dateFrom'), + $qb->expr()->gte('assignment.dateFrom', ':dateFrom') + ), + $qb->expr()->andX( + $qb->expr()->isNull('assignment.dateFrom'), + $qb->expr()->gte('destination.dateFrom', ':dateFrom') + ) + )) + ->setParameter('dateFrom', $dateFrom) + ; + } + + if (null !== $dateTo) { + $qb + ->andWhere($qb->expr()->orX( + $qb->expr()->andX( + $qb->expr()->isNotNull('assignment.dateTo'), + $qb->expr()->lte('assignment.dateTo', ':dateTo') + ), + $qb->expr()->andX( + $qb->expr()->isNull('assignment.dateTo'), + $qb->expr()->lte('destination.dateTo', ':dateTo') + ) + )) + ->setParameter('dateTo', $dateTo) + ; + } + + $results = $qb->getQuery()->getResult(); + + // Calculate missing feedbacks and percentage + return array_map(function (array $row): array { + $total = (int) $row['totalDispositions']; + $provided = (int) $row['providedFeedbacks']; + $missing = $total - $provided; + $percentage = $total > 0 ? round(($provided / $total) * 100, 1) : 0.0; + + return [ + 'hotelCode' => $row['hotelCode'], + 'hotel' => $row['hotel'], + 'totalDispositions' => $total, + 'providedFeedbacks' => $provided, + 'missingFeedbacks' => $missing, + 'percentageProvided' => $percentage, + ]; + }, $results); + } + + /** + * Returns feedback statistics grouped by normalized hotel code (base 3-char code). + * + * @return array + */ + public function getFeedbackStatisticsByNormalizedHotelCode( + ?\DateTimeImmutable $dateFrom = null, + ?\DateTimeImmutable $dateTo = null, + ): array { + $qb = $this->createQueryBuilder('disposition'); + + // Normalize hotel code: strip SER prefix to get base 3-char code + $normalizedHotelCode = "CASE WHEN destination.hotelCode LIKE 'SER%' THEN SUBSTRING(destination.hotelCode, 4, 3) ELSE SUBSTRING(destination.hotelCode, 1, 3) END"; + + $qb + ->select( + $normalizedHotelCode.' AS hotelCode', + 'COUNT(disposition.id) AS totalDispositions', + 'SUM(CASE WHEN disposition.feedback IS NOT NULL THEN 1 ELSE 0 END) AS providedFeedbacks' + ) + ->innerJoin('disposition.assignment', 'assignment') + ->innerJoin('assignment.destination', 'destination') + ->leftJoin('disposition.feedback', 'feedback') + ->where($qb->expr()->in('disposition.status', ':statuses')) + ->setParameter('statuses', [Disposition::STATUS_ENDED, Disposition::STATUS_COMPLETED]) + ->groupBy('hotelCode') + ->orderBy('hotelCode', 'ASC') + ; + + if (null !== $dateFrom) { + $qb + ->andWhere($qb->expr()->orX( + $qb->expr()->andX( + $qb->expr()->isNotNull('assignment.dateFrom'), + $qb->expr()->gte('assignment.dateFrom', ':dateFrom') + ), + $qb->expr()->andX( + $qb->expr()->isNull('assignment.dateFrom'), + $qb->expr()->gte('destination.dateFrom', ':dateFrom') + ) + )) + ->setParameter('dateFrom', $dateFrom) + ; + } + + if (null !== $dateTo) { + $qb + ->andWhere($qb->expr()->orX( + $qb->expr()->andX( + $qb->expr()->isNotNull('assignment.dateTo'), + $qb->expr()->lte('assignment.dateTo', ':dateTo') + ), + $qb->expr()->andX( + $qb->expr()->isNull('assignment.dateTo'), + $qb->expr()->lte('destination.dateTo', ':dateTo') + ) + )) + ->setParameter('dateTo', $dateTo) + ; + } + + $results = $qb->getQuery()->getResult(); + + return array_map(function (array $row): array { + $total = (int) $row['totalDispositions']; + $provided = (int) $row['providedFeedbacks']; + $missing = $total - $provided; + $percentage = $total > 0 ? round(($provided / $total) * 100, 1) : 0.0; + + return [ + 'hotelCode' => $row['hotelCode'], + 'totalDispositions' => $total, + 'providedFeedbacks' => $provided, + 'missingFeedbacks' => $missing, + 'percentageProvided' => $percentage, + ]; + }, $results); + } + public function findOverdueContracts(): array { $dueDate = (new \DateTimeImmutable())->modify('-7 days');