250 lines
8.3 KiB
PHP
250 lines
8.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Repository;
|
|
|
|
use App\Entity\Employee;
|
|
use App\Entity\WorkHour;
|
|
use App\Repository\Contract\WorkHourReadRepositoryInterface;
|
|
use DateTimeImmutable;
|
|
use DateTimeInterface;
|
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
|
use Doctrine\Persistence\ManagerRegistry;
|
|
|
|
/**
|
|
* @extends ServiceEntityRepository<WorkHour>
|
|
*/
|
|
final class WorkHourRepository extends ServiceEntityRepository implements WorkHourReadRepositoryInterface
|
|
{
|
|
public function __construct(ManagerRegistry $registry)
|
|
{
|
|
parent::__construct($registry, WorkHour::class);
|
|
}
|
|
|
|
/**
|
|
* @param list<Employee> $employees
|
|
*
|
|
* @return array<int, WorkHour>
|
|
*/
|
|
public function findByDateAndEmployeesIndexedByEmployeeId(DateTimeImmutable $workDate, array $employees): array
|
|
{
|
|
if ([] === $employees) {
|
|
return [];
|
|
}
|
|
|
|
$qb = $this->createQueryBuilder('w')
|
|
->leftJoin('w.employee', 'e')
|
|
->addSelect('e')
|
|
->andWhere('w.workDate = :workDate')
|
|
->andWhere('w.employee IN (:employees)')
|
|
->setParameter('workDate', $workDate)
|
|
->setParameter('employees', $employees)
|
|
;
|
|
|
|
/** @var list<WorkHour> $workHours */
|
|
$workHours = $qb->getQuery()->getResult();
|
|
|
|
$byEmployeeId = [];
|
|
foreach ($workHours as $workHour) {
|
|
$employeeId = $workHour->getEmployee()?->getId();
|
|
if ($employeeId) {
|
|
$byEmployeeId[$employeeId] = $workHour;
|
|
}
|
|
}
|
|
|
|
return $byEmployeeId;
|
|
}
|
|
|
|
/**
|
|
* @param list<Employee> $employees
|
|
*
|
|
* @return list<WorkHour>
|
|
*/
|
|
public function findByDateRangeAndEmployees(DateTimeImmutable $from, DateTimeImmutable $to, array $employees): array
|
|
{
|
|
if ([] === $employees) {
|
|
return [];
|
|
}
|
|
|
|
$qb = $this->createQueryBuilder('w')
|
|
->leftJoin('w.employee', 'e')
|
|
->addSelect('e')
|
|
->andWhere('w.workDate >= :from')
|
|
->andWhere('w.workDate <= :to')
|
|
->andWhere('w.employee IN (:employees)')
|
|
->setParameter('from', $from)
|
|
->setParameter('to', $to)
|
|
->setParameter('employees', $employees)
|
|
;
|
|
|
|
// @var list<WorkHour> $workHours
|
|
return $qb->getQuery()->getResult();
|
|
}
|
|
|
|
public function hasValidatedInRange(Employee $employee, DateTimeInterface $from, DateTimeInterface $to): bool
|
|
{
|
|
$fromDate = DateTimeImmutable::createFromInterface($from);
|
|
$toDate = DateTimeImmutable::createFromInterface($to);
|
|
|
|
$qb = $this->createQueryBuilder('w')
|
|
->select('COUNT(w.id)')
|
|
->andWhere('w.employee = :employee')
|
|
->andWhere('w.workDate >= :from')
|
|
->andWhere('w.workDate <= :to')
|
|
->andWhere('w.isValid = :isValid')
|
|
->setParameter('employee', $employee)
|
|
->setParameter('from', $fromDate)
|
|
->setParameter('to', $toDate)
|
|
->setParameter('isValid', true)
|
|
;
|
|
|
|
return ((int) $qb->getQuery()->getSingleScalarResult()) > 0;
|
|
}
|
|
|
|
public function hasSiteValidatedInRange(Employee $employee, DateTimeInterface $from, DateTimeInterface $to): bool
|
|
{
|
|
$fromDate = DateTimeImmutable::createFromInterface($from);
|
|
$toDate = DateTimeImmutable::createFromInterface($to);
|
|
|
|
$qb = $this->createQueryBuilder('w')
|
|
->select('COUNT(w.id)')
|
|
->andWhere('w.employee = :employee')
|
|
->andWhere('w.workDate >= :from')
|
|
->andWhere('w.workDate <= :to')
|
|
->andWhere('w.isSiteValid = :isSiteValid')
|
|
->setParameter('employee', $employee)
|
|
->setParameter('from', $fromDate)
|
|
->setParameter('to', $toDate)
|
|
->setParameter('isSiteValid', true)
|
|
;
|
|
|
|
return ((int) $qb->getQuery()->getSingleScalarResult()) > 0;
|
|
}
|
|
|
|
public function findOneByEmployeeAndDate(Employee $employee, DateTimeInterface $date): ?WorkHour
|
|
{
|
|
$workDate = DateTimeImmutable::createFromInterface($date);
|
|
|
|
$qb = $this->createQueryBuilder('w')
|
|
->andWhere('w.employee = :employee')
|
|
->andWhere('w.workDate = :workDate')
|
|
->setParameter('employee', $employee)
|
|
->setParameter('workDate', $workDate)
|
|
->setMaxResults(1)
|
|
;
|
|
|
|
// @var null|WorkHour $workHour
|
|
return $qb->getQuery()->getOneOrNullResult();
|
|
}
|
|
|
|
/**
|
|
* Count weekend worked days by month.
|
|
* >= 5h total = 1.0 day, < 5h = 0.5 day.
|
|
*
|
|
* @return array<string, float> YYYY-MM => weekend worked day count
|
|
*/
|
|
public function countWeekendWorkedDaysByMonth(Employee $employee, DateTimeImmutable $from, DateTimeImmutable $to): array
|
|
{
|
|
$sql = <<<'SQL'
|
|
SELECT TO_CHAR(work_date, 'YYYY-MM') AS month,
|
|
SUM(
|
|
CASE
|
|
WHEN total_minutes >= 300 THEN 1.0
|
|
WHEN total_minutes > 0 THEN 0.5
|
|
ELSE 0
|
|
END
|
|
) AS cnt
|
|
FROM (
|
|
SELECT work_date,
|
|
COALESCE(
|
|
EXTRACT(EPOCH FROM (morning_to::time - morning_from::time)) / 60, 0
|
|
)
|
|
+ COALESCE(
|
|
EXTRACT(EPOCH FROM (afternoon_to::time - afternoon_from::time)) / 60, 0
|
|
)
|
|
+ COALESCE(
|
|
EXTRACT(EPOCH FROM (evening_to::time - evening_from::time)) / 60, 0
|
|
) AS total_minutes
|
|
FROM work_hours
|
|
WHERE employee_id = :employee
|
|
AND work_date >= :from
|
|
AND work_date <= :to
|
|
AND EXTRACT(ISODOW FROM work_date) IN (6, 7)
|
|
AND (morning_from IS NOT NULL OR afternoon_from IS NOT NULL OR evening_from IS NOT NULL)
|
|
) sub
|
|
GROUP BY month
|
|
SQL;
|
|
|
|
$conn = $this->getEntityManager()->getConnection();
|
|
$rows = $conn->fetchAllAssociative($sql, [
|
|
'employee' => $employee->getId(),
|
|
'from' => $from->format('Y-m-d'),
|
|
'to' => $to->format('Y-m-d'),
|
|
]);
|
|
|
|
$result = [];
|
|
foreach ($rows as $row) {
|
|
$result[(string) $row['month']] = (float) $row['cnt'];
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Return the set of Y-m-d dates where the employee has worked hours on the given dates.
|
|
*
|
|
* @param list<string> $dates Y-m-d formatted dates
|
|
*
|
|
* @return array<string, true> Y-m-d => true
|
|
*/
|
|
public function findWorkedDatesAmong(Employee $employee, array $dates): array
|
|
{
|
|
if ([] === $dates) {
|
|
return [];
|
|
}
|
|
|
|
$placeholders = [];
|
|
$params = ['employee' => $employee->getId()];
|
|
foreach (array_values($dates) as $i => $date) {
|
|
$key = "d{$i}";
|
|
$placeholders[] = ":{$key}";
|
|
$params[$key] = $date;
|
|
}
|
|
|
|
$sql = sprintf(
|
|
'SELECT work_date FROM work_hours WHERE employee_id = :employee AND work_date IN (%s) AND (morning_from IS NOT NULL OR afternoon_from IS NOT NULL OR evening_from IS NOT NULL)',
|
|
implode(', ', $placeholders)
|
|
);
|
|
|
|
$conn = $this->getEntityManager()->getConnection();
|
|
$rows = $conn->fetchAllAssociative($sql, $params);
|
|
|
|
$result = [];
|
|
foreach ($rows as $row) {
|
|
$result[(string) $row['work_date']] = true;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
public function hasPendingSiteValidationForSiteAndDate(int $siteId, DateTimeInterface $date): bool
|
|
{
|
|
$workDate = DateTimeImmutable::createFromInterface($date);
|
|
|
|
$qb = $this->createQueryBuilder('w')
|
|
->select('COUNT(w.id)')
|
|
->leftJoin('w.employee', 'e')
|
|
->leftJoin('e.site', 's')
|
|
->andWhere('s.id = :siteId')
|
|
->andWhere('w.workDate = :workDate')
|
|
->andWhere('w.isSiteValid = :isSiteValid')
|
|
->setParameter('siteId', $siteId)
|
|
->setParameter('workDate', $workDate)
|
|
->setParameter('isSiteValid', false)
|
|
;
|
|
|
|
return ((int) $qb->getQuery()->getSingleScalarResult()) > 0;
|
|
}
|
|
}
|