billable timesheets, inactive projects report, bookmark export search (#2503)

This commit is contained in:
Kevin Papst
2021-04-18 21:51:27 +02:00
committed by GitHub
parent 8664d94ea6
commit 0330f45c6a
97 changed files with 1516 additions and 664 deletions

View File

@@ -10,49 +10,24 @@
namespace App\Reporting\ProjectView;
use App\Entity\Project;
use DateTime;
final class ProjectViewModel
{
/**
* @var Project
*/
private $project;
/**
* @var int
*/
private $timesheetCounter = 0;
private $durationDay = 0;
/**
* @var int
*/
private $durationWeek = 0;
/**
* @var int
*/
private $durationMonth = 0;
/**
* @var int
*/
private $durationTotal = 0;
/**
* @var float
*/
private $rateTotal = 0.00;
/**
* @var int
*/
private $notExportedDuration = 0;
/**
* @var float
*/
private $notExportedRate = 0.00;
/**
* @var int
*/
private $notBilledDuration = 0;
/**
* @var float
*/
private $notBilledRate = 0.00;
private $billableDuration = 0;
private $billableRate = 0.00;
private $lastRecord;
public function __construct(Project $project)
{
@@ -64,6 +39,16 @@ final class ProjectViewModel
return $this->project;
}
public function getTimesheetCounter(): int
{
return $this->timesheetCounter;
}
public function setTimesheetCounter(int $timesheetCounter): void
{
$this->timesheetCounter = $timesheetCounter;
}
public function getDurationDay(): int
{
return $this->durationDay;
@@ -144,6 +129,26 @@ final class ProjectViewModel
$this->notBilledRate = $notBilledRate;
}
public function getBillableDuration(): int
{
return $this->billableDuration;
}
public function setBillableDuration(int $billableDuration): void
{
$this->billableDuration = $billableDuration;
}
public function getBillableRate(): float
{
return $this->billableRate;
}
public function setBillableRate(float $billableRate): void
{
$this->billableRate = $billableRate;
}
public function getRateTotal(): float
{
return $this->rateTotal;
@@ -153,4 +158,14 @@ final class ProjectViewModel
{
$this->rateTotal = $rateTotal;
}
public function getLastRecord(): ?DateTime
{
return $this->lastRecord;
}
public function setLastRecord(DateTime $lastRecord): void
{
$this->lastRecord = $lastRecord;
}
}

View File

@@ -1,187 +0,0 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Reporting\ProjectView;
use App\Entity\Timesheet;
use App\Repository\ProjectRepository;
use App\Repository\TimesheetRepository;
use App\Timesheet\DateTimeFactory;
use Doctrine\DBAL\Types\Types;
use Exception;
final class ProjectViewService
{
private $repository;
private $timesheetRepository;
public function __construct(ProjectRepository $projectRepository, TimesheetRepository $timesheetRepository)
{
$this->repository = $projectRepository;
$this->timesheetRepository = $timesheetRepository;
}
/**
* @param ProjectViewQuery $query
* @return ProjectViewModel[]
* @throws Exception
*/
public function getProjectView(ProjectViewQuery $query): array
{
$factory = new DateTimeFactory($query->getToday()->getTimezone());
$user = $query->getUser();
$today = clone $query->getToday();
$begin = $factory->getStartOfWeek($today);
$end = $factory->getEndOfWeek($today);
$startMonth = (clone $begin)->modify('first day of this month');
$endMonth = (clone $begin)->modify('last day of this month');
$qb = $this->repository->createQueryBuilder('p');
$qb
->select('p AS project')
->addSelect('SUM(t.duration) AS totalDuration')
->addSelect('SUM(t.rate) AS totalRate')
->leftJoin('p.customer', 'c')
->leftJoin(Timesheet::class, 't', 'WITH', 'p.id = t.project')
->andWhere($qb->expr()->eq('p.visible', true))
->andWhere($qb->expr()->eq('c.visible', true))
->andWhere(
$qb->expr()->orX(
$qb->expr()->isNull('p.end'),
$qb->expr()->gte('p.end', ':project_end')
)
)
->addGroupBy('p')
->addGroupBy('t.project')
->setParameter('project_end', $today, Types::DATETIME_MUTABLE)
;
if ($query->getCustomer() !== null) {
$qb->andWhere($qb->expr()->eq('c', ':customer'));
$qb->setParameter('customer', $query->getCustomer()->getId());
}
if (!$query->isIncludeNoWork()) {
$qb->andHaving($qb->expr()->gt('totalDuration', 0));
}
if (!$query->isIncludeNoBudget()) {
$qb->andWhere(
$qb->expr()->orX(
$qb->expr()->gt('p.timeBudget', 0),
$qb->expr()->gt('p.budget', 0)
)
);
}
$this->repository->addPermissionCriteria($qb, $user);
$result = $qb->getQuery()->getResult();
$projectViews = [];
foreach ($result as $res) {
$entity = new ProjectViewModel($res['project']);
$entity->setDurationTotal($res['totalDuration'] ?? 0);
$entity->setRateTotal($res['totalRate'] ?? 0.00);
$projectViews[$entity->getProject()->getId()] = $entity;
}
$projectIds = array_keys($projectViews);
// values for today
$qb = $this->timesheetRepository->createQueryBuilder('t');
$qb
->select('IDENTITY(t.project) AS id, SUM(t.duration) AS duration')
->andWhere($qb->expr()->in('t.project', ':project'))
->andWhere('DATE(t.begin) = :starting_date')
->groupBy('t.project')
->setParameter('starting_date', $today->format('Y-m-d'))
->setParameter('project', array_values($projectIds))
;
$result = $qb->getQuery()->getScalarResult();
foreach ($result as $row) {
$projectViews[$row['id']]->setDurationDay($row['duration']);
}
// values for the current week
$qb = $this->timesheetRepository->createQueryBuilder('t');
$qb
->select('IDENTITY(t.project) AS id, SUM(t.duration) AS duration')
->andWhere($qb->expr()->in('t.project', ':project'))
->andWhere('DATE(t.begin) BETWEEN :start_date AND :end_date')
->groupBy('t.project')
->setParameter('start_date', $begin->format('Y-m-d'))
->setParameter('end_date', $end->format('Y-m-d'))
->setParameter('project', array_values($projectIds))
;
$result = $qb->getQuery()->getScalarResult();
foreach ($result as $row) {
$projectViews[$row['id']]->setDurationWeek($row['duration']);
}
// values for the current month
$qb = $this->timesheetRepository->createQueryBuilder('t');
$qb
->select('IDENTITY(t.project) AS id, SUM(t.duration) AS duration')
->andWhere($qb->expr()->in('t.project', ':project'))
->andWhere('DATE(t.begin) BETWEEN :start_month AND :end_month')
->groupBy('t.project')
->setParameter('start_month', $startMonth)
->setParameter('end_month', $endMonth)
->setParameter('project', array_values($projectIds))
;
$result = $qb->getQuery()->getScalarResult();
foreach ($result as $row) {
$projectViews[$row['id']]->setDurationMonth($row['duration']);
}
// values for the all time (not exported)
$qb = $this->timesheetRepository->createQueryBuilder('t');
$qb
->select('IDENTITY(t.project) AS id, SUM(t.duration) AS duration, SUM(t.rate) AS rate')
->andWhere($qb->expr()->in('t.project', ':project'))
->andWhere('t.exported = :exported')
->groupBy('t.project')
->setParameter('exported', false, Types::BOOLEAN)
->setParameter('project', array_values($projectIds))
;
$result = $qb->getQuery()->getScalarResult();
foreach ($result as $row) {
$projectViews[$row['id']]->setNotExportedDuration($row['duration']);
$projectViews[$row['id']]->setNotExportedRate($row['rate']);
}
// values for the all time (not exported and billable)
$qb = $this->timesheetRepository->createQueryBuilder('t');
$qb
->select('IDENTITY(t.project) AS id, SUM(t.duration) AS duration, SUM(t.rate) AS rate')
->andWhere($qb->expr()->in('t.project', ':project'))
->andWhere('t.exported = :exported')
->andWhere('t.billable = :billable')
->groupBy('t.project')
->setParameter('exported', false, Types::BOOLEAN)
->setParameter('billable', true, Types::BOOLEAN)
->setParameter('project', array_values($projectIds))
;
$result = $qb->getQuery()->getScalarResult();
foreach ($result as $row) {
$projectViews[$row['id']]->setNotBilledDuration($row['duration']);
$projectViews[$row['id']]->setNotBilledRate($row['rate']);
}
return array_values($projectViews);
}
}