Release 2.9.0 (#4526)
* added fix to work around bc break in new phpword version * fix phpoffice deprecations * mark unused option as deprecated * support for DateTimeInterface and DateTimeImmutable where possible * use TRUSTED_PROXIES setting - fixes #4533 * re-enable Kimai test in docker test build script (#4541) * bump dependencies
This commit is contained in:
@@ -16,7 +16,8 @@ use App\Model\ActivityBudgetStatisticModel;
|
||||
use App\Model\ActivityStatistic;
|
||||
use App\Repository\TimesheetRepository;
|
||||
use App\Timesheet\DateTimeFactory;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
@@ -26,19 +27,15 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
*/
|
||||
class ActivityStatisticService
|
||||
{
|
||||
public function __construct(private TimesheetRepository $timesheetRepository, private EventDispatcherInterface $dispatcher)
|
||||
public function __construct(private readonly TimesheetRepository $timesheetRepository, private readonly EventDispatcherInterface $dispatcher)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING: this method does not respect the budget type. Your results will always be wither the "full lifetime data" or the "selected date-range".
|
||||
*
|
||||
* @param Activity $activity
|
||||
* @param DateTime|null $begin
|
||||
* @param DateTime|null $end
|
||||
* @return ActivityStatistic
|
||||
* WARNING: this method does not respect the budget type.
|
||||
* Your results will always be with the "full lifetime data" or the "selected date-range".
|
||||
*/
|
||||
public function getActivityStatistics(Activity $activity, ?DateTime $begin = null, ?DateTime $end = null): ActivityStatistic
|
||||
public function getActivityStatistics(Activity $activity, ?DateTimeInterface $begin = null, ?DateTimeInterface $end = null): ActivityStatistic
|
||||
{
|
||||
$statistics = $this->getBudgetStatistic([$activity], $begin, $end);
|
||||
$event = new ActivityStatisticEvent($activity, array_pop($statistics), $begin, $end);
|
||||
@@ -47,7 +44,7 @@ class ActivityStatisticService
|
||||
return $event->getStatistic();
|
||||
}
|
||||
|
||||
public function getBudgetStatisticModel(Activity $activity, DateTime $today): ActivityBudgetStatisticModel
|
||||
public function getBudgetStatisticModel(Activity $activity, DateTimeInterface $today): ActivityBudgetStatisticModel
|
||||
{
|
||||
$stats = new ActivityBudgetStatisticModel($activity);
|
||||
$stats->setStatisticTotal($this->getActivityStatistics($activity));
|
||||
@@ -68,10 +65,9 @@ class ActivityStatisticService
|
||||
|
||||
/**
|
||||
* @param Activity[] $activities
|
||||
* @param DateTime $today
|
||||
* @return ActivityBudgetStatisticModel[]
|
||||
*/
|
||||
public function getBudgetStatisticModelForActivities(array $activities, DateTime $today): array
|
||||
public function getBudgetStatisticModelForActivities(array $activities, DateTimeInterface $today): array
|
||||
{
|
||||
$models = [];
|
||||
$monthly = [];
|
||||
@@ -121,11 +117,9 @@ class ActivityStatisticService
|
||||
|
||||
/**
|
||||
* @param Activity[] $activities
|
||||
* @param DateTime|null $begin
|
||||
* @param DateTime|null $end
|
||||
* @return array<int, ActivityStatistic>
|
||||
*/
|
||||
private function getBudgetStatistic(array $activities, ?DateTime $begin = null, ?DateTime $end = null): array
|
||||
private function getBudgetStatistic(array $activities, ?DateTimeInterface $begin = null, ?DateTimeInterface $end = null): array
|
||||
{
|
||||
$statistics = [];
|
||||
foreach ($activities as $activity) {
|
||||
@@ -139,25 +133,25 @@ class ActivityStatisticService
|
||||
if (null !== $result) {
|
||||
foreach ($result as $resultRow) {
|
||||
$statistic = $statistics[$resultRow['id']];
|
||||
$statistic->setDuration($statistic->getDuration() + $resultRow['duration']);
|
||||
$statistic->setRate($statistic->getRate() + $resultRow['rate']);
|
||||
$statistic->setInternalRate($statistic->getInternalRate() + $resultRow['internalRate']);
|
||||
$statistic->setCounter($statistic->getCounter() + $resultRow['counter']);
|
||||
$statistic->addDuration((int) $resultRow['duration']);
|
||||
$statistic->addRate((float) $resultRow['rate']);
|
||||
$statistic->addInternalRate((float) $resultRow['internalRate']);
|
||||
$statistic->addCounter((int) $resultRow['counter']);
|
||||
if ($resultRow['billable']) {
|
||||
$statistic->setDurationBillable($statistic->getDurationBillable() + $resultRow['duration']);
|
||||
$statistic->setRateBillable($statistic->getRateBillable() + $resultRow['rate']);
|
||||
$statistic->setInternalRateBillable($statistic->getInternalRateBillable() + $resultRow['internalRate']);
|
||||
$statistic->setCounterBillable($statistic->getCounterBillable() + $resultRow['counter']);
|
||||
$statistic->addDurationBillable((int) $resultRow['duration']);
|
||||
$statistic->addRateBillable((float) $resultRow['rate']);
|
||||
$statistic->addInternalRateBillable((float) $resultRow['internalRate']);
|
||||
$statistic->addCounterBillable((int) $resultRow['counter']);
|
||||
if ($resultRow['exported']) {
|
||||
$statistic->setDurationBillableExported($statistic->getDurationBillableExported() + $resultRow['duration']);
|
||||
$statistic->setRateBillableExported($statistic->getRateBillableExported() + $resultRow['rate']);
|
||||
$statistic->addDurationBillableExported((int) $resultRow['duration']);
|
||||
$statistic->addRateBillableExported((float) $resultRow['rate']);
|
||||
}
|
||||
}
|
||||
if ($resultRow['exported']) {
|
||||
$statistic->setDurationExported($statistic->getDurationExported() + $resultRow['duration']);
|
||||
$statistic->setRateExported($statistic->getRateExported() + $resultRow['rate']);
|
||||
$statistic->setInternalRateExported($statistic->getInternalRateExported() + $resultRow['internalRate']);
|
||||
$statistic->setCounterExported($statistic->getCounterExported() + $resultRow['counter']);
|
||||
$statistic->addDurationExported((int) $resultRow['duration']);
|
||||
$statistic->addRateExported((float) $resultRow['rate']);
|
||||
$statistic->addInternalRateExported((float) $resultRow['internalRate']);
|
||||
$statistic->addCounterExported((int) $resultRow['counter']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,7 +159,7 @@ class ActivityStatisticService
|
||||
return $statistics;
|
||||
}
|
||||
|
||||
private function createStatisticQueryBuilder(array $activities, DateTime $begin = null, ?DateTime $end = null): QueryBuilder
|
||||
private function createStatisticQueryBuilder(array $activities, \DateTimeInterface $begin = null, ?\DateTimeInterface $end = null): QueryBuilder
|
||||
{
|
||||
$qb = $this->timesheetRepository->createQueryBuilder('t');
|
||||
$qb
|
||||
@@ -187,14 +181,14 @@ class ActivityStatisticService
|
||||
if ($begin !== null) {
|
||||
$qb
|
||||
->andWhere($qb->expr()->gte('t.begin', ':begin'))
|
||||
->setParameter('begin', $begin, Types::DATETIME_MUTABLE)
|
||||
->setParameter('begin', DateTimeImmutable::createFromInterface($begin), Types::DATETIME_IMMUTABLE)
|
||||
;
|
||||
}
|
||||
|
||||
if ($end !== null) {
|
||||
$qb
|
||||
->andWhere($qb->expr()->lte('t.begin', ':end'))
|
||||
->setParameter('end', $end, Types::DATETIME_MUTABLE)
|
||||
->setParameter('end', DateTimeImmutable::createFromInterface($end), Types::DATETIME_IMMUTABLE)
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
@@ -158,10 +158,12 @@ final class ExportCreateCommand extends Command
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
if (!$start instanceof \DateTime) {
|
||||
if (!$start instanceof \DateTimeInterface) {
|
||||
$start = $dateFactory->getStartOfMonth();
|
||||
}
|
||||
$start->setTime(0, 0, 0);
|
||||
|
||||
$start = \DateTimeImmutable::createFromInterface($start);
|
||||
$start = $start->setTime(0, 0, 0);
|
||||
|
||||
$end = $input->getOption('end');
|
||||
if (!empty($end)) {
|
||||
@@ -174,11 +176,12 @@ final class ExportCreateCommand extends Command
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($end)) {
|
||||
if (!$end instanceof \DateTimeInterface) {
|
||||
$end = $dateFactory->getEndOfMonth($start);
|
||||
}
|
||||
|
||||
$end->setTime(23, 59, 59);
|
||||
$end = \DateTimeImmutable::createFromInterface($end);
|
||||
$end = $end->setTime(23, 59, 59);
|
||||
|
||||
$directory = rtrim(sys_get_temp_dir(), '/') . '/';
|
||||
if ($input->getOption('directory') !== null) {
|
||||
|
||||
@@ -64,7 +64,7 @@ final class InvoiceCreateCommand extends Command
|
||||
->addOption('project', null, InputOption::VALUE_OPTIONAL, 'Comma separated list of project IDs', null)
|
||||
->addOption('by-customer', null, InputOption::VALUE_NONE, 'If set, one invoice for each active customer in the given timerange is created')
|
||||
->addOption('by-project', null, InputOption::VALUE_NONE, 'If set, one invoice for each active project in the given timerange is created')
|
||||
->addOption('set-exported', null, InputOption::VALUE_NONE, 'Whether the invoice items should be marked as exported')
|
||||
->addOption('set-exported', null, InputOption::VALUE_NONE, '[DEPRECATED] this flag has no meaning any more: invoiced items are always exported')
|
||||
->addOption('template', null, InputOption::VALUE_OPTIONAL, 'Invoice template', null)
|
||||
->addOption('search', null, InputOption::VALUE_OPTIONAL, 'Search term to filter invoice entries', null)
|
||||
->addOption('exported', null, InputOption::VALUE_OPTIONAL, 'Exported filter for invoice entries (possible values: exported, all), by default only "not exported" items are fetched', null)
|
||||
@@ -156,10 +156,12 @@ final class InvoiceCreateCommand extends Command
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
if (!$start instanceof \DateTime) {
|
||||
if (!$start instanceof \DateTimeInterface) {
|
||||
$start = $dateFactory->getStartOfMonth();
|
||||
}
|
||||
$start->setTime(0, 0, 0);
|
||||
|
||||
$start = \DateTimeImmutable::createFromInterface($start);
|
||||
$start = $start->setTime(0, 0, 0);
|
||||
|
||||
$end = $input->getOption('end');
|
||||
if (!empty($end)) {
|
||||
@@ -171,17 +173,18 @@ final class InvoiceCreateCommand extends Command
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
if (!$end instanceof \DateTime) {
|
||||
if (!$end instanceof \DateTimeInterface) {
|
||||
$end = $dateFactory->getEndOfMonth();
|
||||
}
|
||||
$end->setTime(23, 59, 59);
|
||||
|
||||
$end = \DateTimeImmutable::createFromInterface($end);
|
||||
$end = $end->setTime(23, 59, 59);
|
||||
|
||||
$searchTerm = null;
|
||||
if (null !== $input->getOption('search')) {
|
||||
$searchTerm = new SearchTerm($input->getOption('search'));
|
||||
}
|
||||
|
||||
$markAsExported = false;
|
||||
if ($input->getOption('preview') !== null) {
|
||||
$this->previewUniqueFile = (bool) $input->getOption('preview-unique');
|
||||
$this->previewDirectory = rtrim($input->getOption('preview'), '/') . '/';
|
||||
@@ -191,7 +194,7 @@ final class InvoiceCreateCommand extends Command
|
||||
return Command::FAILURE;
|
||||
}
|
||||
} elseif ($input->getOption('set-exported')) {
|
||||
$markAsExported = true;
|
||||
@trigger_error('The "set-exported" option of kimai:invoice:create command has no meaning anymore, it will be removed soon', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
// =============== VALIDATION END ===============
|
||||
|
||||
@@ -17,11 +17,11 @@ class Constants
|
||||
/**
|
||||
* The current release version
|
||||
*/
|
||||
public const VERSION = '2.8.0';
|
||||
public const VERSION = '2.9.0';
|
||||
/**
|
||||
* The current release: major * 10000 + minor * 100 + patch
|
||||
*/
|
||||
public const VERSION_ID = 20800;
|
||||
public const VERSION_ID = 20900;
|
||||
/**
|
||||
* The software name
|
||||
*/
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
namespace App\Controller\Reporting;
|
||||
|
||||
use App\Controller\AbstractController;
|
||||
use App\Entity\Customer;
|
||||
use App\Form\Model\DateRange;
|
||||
use App\Project\ProjectStatisticService;
|
||||
use App\Reporting\ProjectDateRange\ProjectDateRangeForm;
|
||||
@@ -37,15 +38,20 @@ final class ProjectDateRangeController extends AbstractController
|
||||
]);
|
||||
$form->submit($request->query->all(), false);
|
||||
|
||||
$begin = $query->getMonth() ?? $defaultStart;
|
||||
|
||||
$dateRange = new DateRange(true);
|
||||
$dateRange->setBegin($query->getMonth() ?? $defaultStart);
|
||||
$dateRange->setEnd($dateFactory->getEndOfMonth($dateRange->getBegin()));
|
||||
$dateRange->setBegin($begin);
|
||||
$end = $dateFactory->getEndOfMonth($dateRange->getBegin()); // this resets the time
|
||||
|
||||
$dateRange->setEnd($end);
|
||||
|
||||
$projects = $service->findProjectsForDateRange($query, $dateRange);
|
||||
$entries = $service->getBudgetStatisticModelForProjectsByDateRange($projects, $dateRange->getBegin(), $dateRange->getEnd(), $dateRange->getEnd());
|
||||
$entries = $service->getBudgetStatisticModelForProjectsByDateRange($projects, $begin, $end, $end);
|
||||
|
||||
$byCustomer = [];
|
||||
foreach ($entries as $entry) {
|
||||
/** @var Customer $customer */
|
||||
$customer = $entry->getProject()->getCustomer();
|
||||
if (!isset($byCustomer[$customer->getId()])) {
|
||||
$byCustomer[$customer->getId()] = ['customer' => $customer, 'projects' => []];
|
||||
|
||||
@@ -17,6 +17,8 @@ use App\Model\CustomerStatistic;
|
||||
use App\Repository\TimesheetRepository;
|
||||
use App\Timesheet\DateTimeFactory;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
@@ -27,17 +29,12 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
*/
|
||||
class CustomerStatisticService
|
||||
{
|
||||
public function __construct(private TimesheetRepository $timesheetRepository, private EventDispatcherInterface $dispatcher)
|
||||
public function __construct(private readonly TimesheetRepository $timesheetRepository, private readonly EventDispatcherInterface $dispatcher)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING: this method does not respect the budget type. Your results will always be wither the "full lifetime data" or the "selected date-range".
|
||||
*
|
||||
* @param Customer $customer
|
||||
* @param DateTime|null $begin
|
||||
* @param DateTime|null $end
|
||||
* @return CustomerStatistic
|
||||
* WARNING: this method does not respect the budget type. Your results will always be with the "full lifetime data" or the "selected date-range".
|
||||
*/
|
||||
public function getCustomerStatistics(Customer $customer, ?DateTime $begin = null, ?DateTime $end = null): CustomerStatistic
|
||||
{
|
||||
@@ -48,13 +45,13 @@ class CustomerStatisticService
|
||||
return $event->getStatistic();
|
||||
}
|
||||
|
||||
public function getBudgetStatisticModel(Customer $customer, DateTime $today): CustomerBudgetStatisticModel
|
||||
public function getBudgetStatisticModel(Customer $customer, DateTimeInterface $today): CustomerBudgetStatisticModel
|
||||
{
|
||||
$stats = new CustomerBudgetStatisticModel($customer);
|
||||
$stats->setStatisticTotal($this->getCustomerStatistics($customer));
|
||||
|
||||
$begin = null;
|
||||
$end = $today;
|
||||
$end = DateTime::createFromInterface($today);
|
||||
|
||||
if ($customer->isMonthlyBudget()) {
|
||||
$dateFactory = new DateTimeFactory($today->getTimezone());
|
||||
@@ -69,11 +66,9 @@ class CustomerStatisticService
|
||||
|
||||
/**
|
||||
* @param Customer[] $customers
|
||||
* @param DateTime|null $begin
|
||||
* @param DateTime|null $end
|
||||
* @return array<int, CustomerStatistic>
|
||||
*/
|
||||
private function getBudgetStatistic(array $customers, ?DateTime $begin = null, ?DateTime $end = null): array
|
||||
private function getBudgetStatistic(array $customers, ?DateTimeInterface $begin = null, ?DateTimeInterface $end = null): array
|
||||
{
|
||||
$statistics = [];
|
||||
foreach ($customers as $customer) {
|
||||
@@ -87,25 +82,25 @@ class CustomerStatisticService
|
||||
if (null !== $result) {
|
||||
foreach ($result as $resultRow) {
|
||||
$statistic = $statistics[$resultRow['id']];
|
||||
$statistic->setDuration($statistic->getDuration() + $resultRow['duration']);
|
||||
$statistic->setRate($statistic->getRate() + $resultRow['rate']);
|
||||
$statistic->setInternalRate($statistic->getInternalRate() + $resultRow['internalRate']);
|
||||
$statistic->setCounter($statistic->getCounter() + $resultRow['counter']);
|
||||
$statistic->addDuration((int) $resultRow['duration']);
|
||||
$statistic->addRate((float) $resultRow['rate']);
|
||||
$statistic->addInternalRate((float) $resultRow['internalRate']);
|
||||
$statistic->addCounter((int) $resultRow['counter']);
|
||||
if ($resultRow['billable']) {
|
||||
$statistic->setDurationBillable($statistic->getDurationBillable() + $resultRow['duration']);
|
||||
$statistic->setRateBillable($statistic->getRateBillable() + $resultRow['rate']);
|
||||
$statistic->setInternalRateBillable($statistic->getInternalRateBillable() + $resultRow['internalRate']);
|
||||
$statistic->setCounterBillable($statistic->getCounterBillable() + $resultRow['counter']);
|
||||
$statistic->addDurationBillable((int) $resultRow['duration']);
|
||||
$statistic->addRateBillable((float) $resultRow['rate']);
|
||||
$statistic->addInternalRateBillable((float) $resultRow['internalRate']);
|
||||
$statistic->addCounterBillable((int) $resultRow['counter']);
|
||||
if ($resultRow['exported']) {
|
||||
$statistic->setDurationBillableExported($statistic->getDurationBillableExported() + $resultRow['duration']);
|
||||
$statistic->setRateBillableExported($statistic->getRateBillableExported() + $resultRow['rate']);
|
||||
$statistic->addDurationBillableExported((int) $resultRow['duration']);
|
||||
$statistic->addRateBillableExported((float) $resultRow['rate']);
|
||||
}
|
||||
}
|
||||
if ($resultRow['exported']) {
|
||||
$statistic->setDurationExported($statistic->getDurationExported() + $resultRow['duration']);
|
||||
$statistic->setRateExported($statistic->getRateExported() + $resultRow['rate']);
|
||||
$statistic->setInternalRateExported($statistic->getInternalRateExported() + $resultRow['internalRate']);
|
||||
$statistic->setCounterExported($statistic->getCounterExported() + $resultRow['counter']);
|
||||
$statistic->addDurationExported((int) $resultRow['duration']);
|
||||
$statistic->addRateExported((float) $resultRow['rate']);
|
||||
$statistic->addInternalRateExported((float) $resultRow['internalRate']);
|
||||
$statistic->addCounterExported((int) $resultRow['counter']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,7 +108,7 @@ class CustomerStatisticService
|
||||
return $statistics;
|
||||
}
|
||||
|
||||
private function createStatisticQueryBuilder(array $customers, DateTime $begin = null, ?DateTime $end = null): QueryBuilder
|
||||
private function createStatisticQueryBuilder(array $customers, ?DateTimeInterface $begin = null, ?DateTimeInterface $end = null): QueryBuilder
|
||||
{
|
||||
$qb = $this->timesheetRepository->createQueryBuilder('t');
|
||||
$qb
|
||||
@@ -136,14 +131,14 @@ class CustomerStatisticService
|
||||
if ($begin !== null) {
|
||||
$qb
|
||||
->andWhere($qb->expr()->gte('t.begin', ':begin'))
|
||||
->setParameter('begin', $begin, Types::DATETIME_MUTABLE)
|
||||
->setParameter('begin', DateTimeImmutable::createFromInterface($begin), Types::DATETIME_IMMUTABLE)
|
||||
;
|
||||
}
|
||||
|
||||
if ($end !== null) {
|
||||
$qb
|
||||
->andWhere($qb->expr()->lte('t.begin', ':end'))
|
||||
->setParameter('end', $end, Types::DATETIME_MUTABLE)
|
||||
->setParameter('end', DateTimeImmutable::createFromInterface($end), Types::DATETIME_IMMUTABLE)
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
@@ -109,8 +109,7 @@ final class AppExtension extends Extension
|
||||
$locales = explode('|', $container->getParameter('app_locales'));
|
||||
|
||||
$directory = $container->getParameter('kernel.project_dir');
|
||||
$config = $directory . DIRECTORY_SEPARATOR . 'config/locales.php';
|
||||
$settings = include $config;
|
||||
$settings = include $directory . DIRECTORY_SEPARATOR . 'config/locales.php';
|
||||
|
||||
$appLocales = [];
|
||||
$defaults = [
|
||||
|
||||
@@ -13,13 +13,27 @@ use App\Model\ActivityBudgetStatisticModel;
|
||||
|
||||
final class ActivityBudgetStatisticEvent
|
||||
{
|
||||
private readonly ?\DateTime $begin;
|
||||
private readonly ?\DateTime $end;
|
||||
|
||||
/**
|
||||
* @param ActivityBudgetStatisticModel[] $models
|
||||
* @param \DateTime|null $begin
|
||||
* @param \DateTime|null $end
|
||||
*/
|
||||
public function __construct(private array $models, private ?\DateTime $begin = null, private ?\DateTime $end = null)
|
||||
public function __construct(
|
||||
private readonly array $models,
|
||||
?\DateTimeInterface $begin = null,
|
||||
?\DateTimeInterface $end = null
|
||||
)
|
||||
{
|
||||
if ($begin !== null) {
|
||||
$begin = \DateTime::createFromInterface($begin);
|
||||
}
|
||||
$this->begin = $begin;
|
||||
|
||||
if ($end !== null) {
|
||||
$end = \DateTime::createFromInterface($end);
|
||||
}
|
||||
$this->end = $end;
|
||||
}
|
||||
|
||||
public function getModel(int $activityId): ?ActivityBudgetStatisticModel
|
||||
|
||||
@@ -11,12 +11,32 @@ namespace App\Event;
|
||||
|
||||
use App\Entity\Activity;
|
||||
use App\Model\ActivityStatistic;
|
||||
use DateTime;
|
||||
use DateTimeInterface;
|
||||
|
||||
final class ActivityStatisticEvent extends AbstractActivityEvent
|
||||
{
|
||||
public function __construct(Activity $activity, private ActivityStatistic $statistic, private ?\DateTime $begin = null, private ?\DateTime $end = null)
|
||||
private readonly ?DateTime $begin;
|
||||
private readonly ?DateTime $end;
|
||||
|
||||
public function __construct(
|
||||
Activity $activity,
|
||||
private readonly ActivityStatistic $statistic,
|
||||
?DateTimeInterface $begin = null,
|
||||
?DateTimeInterface $end = null
|
||||
)
|
||||
{
|
||||
parent::__construct($activity);
|
||||
|
||||
if ($begin !== null) {
|
||||
$begin = DateTime::createFromInterface($begin);
|
||||
}
|
||||
$this->begin = $begin;
|
||||
|
||||
if ($end !== null) {
|
||||
$end = DateTime::createFromInterface($end);
|
||||
}
|
||||
$this->end = $end;
|
||||
}
|
||||
|
||||
public function getStatistic(): ActivityStatistic
|
||||
@@ -24,12 +44,12 @@ final class ActivityStatisticEvent extends AbstractActivityEvent
|
||||
return $this->statistic;
|
||||
}
|
||||
|
||||
public function getBegin(): ?\DateTime
|
||||
public function getBegin(): ?DateTime
|
||||
{
|
||||
return $this->begin;
|
||||
}
|
||||
|
||||
public function getEnd(): ?\DateTime
|
||||
public function getEnd(): ?DateTime
|
||||
{
|
||||
return $this->end;
|
||||
}
|
||||
|
||||
@@ -10,16 +10,32 @@
|
||||
namespace App\Event;
|
||||
|
||||
use App\Model\ProjectBudgetStatisticModel;
|
||||
use DateTime;
|
||||
use DateTimeInterface;
|
||||
|
||||
final class ProjectBudgetStatisticEvent
|
||||
{
|
||||
private readonly ?DateTime $begin;
|
||||
private readonly ?DateTime $end;
|
||||
|
||||
/**
|
||||
* @param ProjectBudgetStatisticModel[] $models
|
||||
* @param \DateTime|null $begin
|
||||
* @param \DateTime|null $end
|
||||
*/
|
||||
public function __construct(private array $models, private ?\DateTime $begin = null, private ?\DateTime $end = null)
|
||||
public function __construct(
|
||||
private readonly array $models,
|
||||
?DateTimeInterface $begin = null,
|
||||
?DateTimeInterface $end = null
|
||||
)
|
||||
{
|
||||
if ($begin !== null) {
|
||||
$begin = \DateTime::createFromInterface($begin);
|
||||
}
|
||||
$this->begin = $begin;
|
||||
|
||||
if ($end !== null) {
|
||||
$end = \DateTime::createFromInterface($end);
|
||||
}
|
||||
$this->end = $end;
|
||||
}
|
||||
|
||||
public function getModel(int $projectId): ?ProjectBudgetStatisticModel
|
||||
@@ -45,12 +61,12 @@ final class ProjectBudgetStatisticEvent
|
||||
return $this->models;
|
||||
}
|
||||
|
||||
public function getBegin(): ?\DateTime
|
||||
public function getBegin(): ?DateTime
|
||||
{
|
||||
return $this->begin;
|
||||
}
|
||||
|
||||
public function getEnd(): ?\DateTime
|
||||
public function getEnd(): ?DateTime
|
||||
{
|
||||
return $this->end;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,12 @@ use App\Model\ProjectStatistic;
|
||||
|
||||
final class ProjectStatisticEvent extends AbstractProjectEvent
|
||||
{
|
||||
public function __construct(Project $project, private ProjectStatistic $statistic, private ?\DateTime $begin = null, private ?\DateTime $end = null)
|
||||
public function __construct(
|
||||
Project $project,
|
||||
private readonly ProjectStatistic $statistic,
|
||||
private readonly ?\DateTimeInterface $begin = null,
|
||||
private readonly ?\DateTimeInterface $end = null
|
||||
)
|
||||
{
|
||||
parent::__construct($project);
|
||||
}
|
||||
@@ -24,12 +29,12 @@ final class ProjectStatisticEvent extends AbstractProjectEvent
|
||||
return $this->statistic;
|
||||
}
|
||||
|
||||
public function getBegin(): ?\DateTime
|
||||
public function getBegin(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->begin;
|
||||
}
|
||||
|
||||
public function getEnd(): ?\DateTime
|
||||
public function getEnd(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->end;
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ abstract class AbstractSpreadsheetRenderer
|
||||
|
||||
$sheet->setCellValue(CellAddress::fromColumnAndRow($column, $row), $excelDate);
|
||||
// TODO why is that format hardcoded and does not depend on the users locale?
|
||||
$sheet->getStyle(CellAddress::fromColumnAndRow($column, $row))->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD2);
|
||||
$sheet->getStyle(CellAddress::fromColumnAndRow($column, $row))->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD);
|
||||
}
|
||||
|
||||
protected function setDurationTotal(Worksheet $sheet, int $column, int $row, string $startCoordinate, string $endCoordinate): void
|
||||
|
||||
@@ -24,11 +24,11 @@ final class DateFormatter implements CellFormatterInterface
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$value instanceof \DateTime) {
|
||||
throw new \InvalidArgumentException('Unsupported value given, only DateTime is supported');
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
throw new \InvalidArgumentException('Unsupported value given, only DateTimeInterface is supported');
|
||||
}
|
||||
|
||||
$sheet->setCellValue(CellAddress::fromColumnAndRow($column, $row), Date::PHPToExcel($value));
|
||||
$sheet->getStyleByColumnAndRow($column, $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD2);
|
||||
$sheet->getStyle(CellAddress::fromColumnAndRow($column, $row))->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,11 +25,11 @@ final class DateTimeFormatter implements CellFormatterInterface
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$value instanceof \DateTime) {
|
||||
throw new \InvalidArgumentException('Unsupported value given, only DateTime is supported');
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
throw new \InvalidArgumentException('Unsupported value given, only DateTimeInterface is supported');
|
||||
}
|
||||
|
||||
$sheet->setCellValue(CellAddress::fromColumnAndRow($column, $row), Date::PHPToExcel($value));
|
||||
$sheet->getStyleByColumnAndRow($column, $row)->getNumberFormat()->setFormatCode(self::DATETIME_FORMAT);
|
||||
$sheet->getStyle(CellAddress::fromColumnAndRow($column, $row))->getNumberFormat()->setFormatCode(self::DATETIME_FORMAT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ final class TimeFormatter implements CellFormatterInterface
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$value instanceof \DateTime) {
|
||||
throw new \InvalidArgumentException('Unsupported value given, only DateTime is supported');
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
throw new \InvalidArgumentException('Unsupported value given, only DateTimeInterface is supported');
|
||||
}
|
||||
|
||||
$sheet->setCellValue(CellAddress::fromColumnAndRow($column, $row), Date::PHPToExcel($value));
|
||||
|
||||
@@ -26,9 +26,9 @@ final class DateRange implements EquatableInterface
|
||||
return $this->begin;
|
||||
}
|
||||
|
||||
public function setBegin(DateTime $begin): DateRange
|
||||
public function setBegin(\DateTimeInterface $begin): DateRange
|
||||
{
|
||||
$this->begin = $begin;
|
||||
$this->begin = DateTime::createFromInterface($begin);
|
||||
if ($this->resetTimes) {
|
||||
$this->begin->setTime(0, 0, 0);
|
||||
}
|
||||
@@ -41,9 +41,9 @@ final class DateRange implements EquatableInterface
|
||||
return $this->end;
|
||||
}
|
||||
|
||||
public function setEnd(DateTime $end): DateRange
|
||||
public function setEnd(\DateTimeInterface $end): DateRange
|
||||
{
|
||||
$this->end = $end;
|
||||
$this->end = DateTime::createFromInterface($end);
|
||||
if ($this->resetTimes) {
|
||||
$this->end->setTime(23, 59, 59);
|
||||
}
|
||||
|
||||
@@ -39,10 +39,12 @@ class DatePickerType extends AbstractType
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($reverseTransform instanceof \DateTime && $options['force_time']) {
|
||||
if ($reverseTransform instanceof \DateTimeInterface && $options['force_time']) {
|
||||
if ($options['force_time'] === 'start') {
|
||||
$reverseTransform = \DateTime::createFromInterface($reverseTransform);
|
||||
$reverseTransform->setTime(0, 0, 0);
|
||||
} elseif ($options['force_time'] === 'end') {
|
||||
$reverseTransform = \DateTime::createFromInterface($reverseTransform);
|
||||
$reverseTransform->setTime(23, 59, 59);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,9 @@ final class InvoiceModelCustomerHydrator implements InvoiceModelHydrator
|
||||
'customer.invoice_text' => $customer->getInvoiceText() ?? '',
|
||||
];
|
||||
|
||||
$statistic = $this->customerStatisticService->getBudgetStatisticModel($customer, $model->getQuery()->getEnd());
|
||||
/** @var \DateTime $end */
|
||||
$end = $model->getQuery()->getEnd();
|
||||
$statistic = $this->customerStatisticService->getBudgetStatisticModel($customer, $end);
|
||||
|
||||
$values = array_merge($values, $this->getBudgetValues('customer.', $statistic, $model));
|
||||
|
||||
|
||||
@@ -57,7 +57,12 @@ final class DocxRenderer extends AbstractRenderer implements RendererInterface
|
||||
$i++;
|
||||
}
|
||||
|
||||
$cacheFile = $template->save();
|
||||
$cacheFile = @tempnam(sys_get_temp_dir(), 'kimai-invoice-docx');
|
||||
if (false === $cacheFile) {
|
||||
throw new \Exception('Could not open temporary file');
|
||||
}
|
||||
|
||||
$template->saveAs($cacheFile);
|
||||
|
||||
clearstatcache(true, $cacheFile);
|
||||
|
||||
|
||||
@@ -47,6 +47,11 @@ class TimesheetCountedStatistic implements \JsonSerializable
|
||||
$this->counter = $counter;
|
||||
}
|
||||
|
||||
public function addCounter(int $counter): void
|
||||
{
|
||||
$this->counter += $counter;
|
||||
}
|
||||
|
||||
public function getCounterBillable(): int
|
||||
{
|
||||
return $this->counterBillable;
|
||||
@@ -57,6 +62,11 @@ class TimesheetCountedStatistic implements \JsonSerializable
|
||||
$this->counterBillable = $counter;
|
||||
}
|
||||
|
||||
public function addCounterBillable(int $counter): void
|
||||
{
|
||||
$this->counterBillable += $counter;
|
||||
}
|
||||
|
||||
public function getCounterExported(): int
|
||||
{
|
||||
return $this->counterExported;
|
||||
@@ -67,6 +77,11 @@ class TimesheetCountedStatistic implements \JsonSerializable
|
||||
$this->counterExported = $counter;
|
||||
}
|
||||
|
||||
public function addCounterExported(int $counter): void
|
||||
{
|
||||
$this->counterExported += $counter;
|
||||
}
|
||||
|
||||
/**
|
||||
* For unified access, used in frontend.
|
||||
*
|
||||
@@ -82,6 +97,11 @@ class TimesheetCountedStatistic implements \JsonSerializable
|
||||
$this->recordDuration = $duration;
|
||||
}
|
||||
|
||||
public function addDuration(int $duration): void
|
||||
{
|
||||
$this->recordDuration += $duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* For unified access, used in frontend.
|
||||
*
|
||||
@@ -107,6 +127,11 @@ class TimesheetCountedStatistic implements \JsonSerializable
|
||||
$this->recordRate = $rate;
|
||||
}
|
||||
|
||||
public function addRate(float $rate): void
|
||||
{
|
||||
$this->recordRate += $rate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total internal rate of all included timesheet records.
|
||||
*
|
||||
@@ -127,6 +152,11 @@ class TimesheetCountedStatistic implements \JsonSerializable
|
||||
$this->internalRateBillable = $internalRateBillable;
|
||||
}
|
||||
|
||||
public function addInternalRateBillable(float $internalRateBillable): void
|
||||
{
|
||||
$this->internalRateBillable += $internalRateBillable;
|
||||
}
|
||||
|
||||
public function getInternalRateExported(): float
|
||||
{
|
||||
return $this->internalRateExported;
|
||||
@@ -137,11 +167,21 @@ class TimesheetCountedStatistic implements \JsonSerializable
|
||||
$this->internalRateExported = $internalRateExported;
|
||||
}
|
||||
|
||||
public function addInternalRateExported(float $internalRateExported): void
|
||||
{
|
||||
$this->internalRateExported += $internalRateExported;
|
||||
}
|
||||
|
||||
public function setInternalRate(float $internalRate): void
|
||||
{
|
||||
$this->internalRate = $internalRate;
|
||||
}
|
||||
|
||||
public function addInternalRate(float $internalRate): void
|
||||
{
|
||||
$this->internalRate += $internalRate;
|
||||
}
|
||||
|
||||
public function getDurationBillable(): int
|
||||
{
|
||||
return $this->recordDurationBillable;
|
||||
@@ -152,6 +192,11 @@ class TimesheetCountedStatistic implements \JsonSerializable
|
||||
$this->recordDurationBillable = $recordDuration;
|
||||
}
|
||||
|
||||
public function addDurationBillable(int $recordDuration): void
|
||||
{
|
||||
$this->recordDurationBillable += $recordDuration;
|
||||
}
|
||||
|
||||
public function getDurationBillableExported(): int
|
||||
{
|
||||
return $this->recordDurationBillableExported;
|
||||
@@ -162,6 +207,11 @@ class TimesheetCountedStatistic implements \JsonSerializable
|
||||
$this->recordDurationBillableExported = $recordDuration;
|
||||
}
|
||||
|
||||
public function addDurationBillableExported(int $recordDuration): void
|
||||
{
|
||||
$this->recordDurationBillableExported += $recordDuration;
|
||||
}
|
||||
|
||||
public function getRateBillable(): float
|
||||
{
|
||||
return $this->recordRateBillable;
|
||||
@@ -172,6 +222,11 @@ class TimesheetCountedStatistic implements \JsonSerializable
|
||||
$this->recordRateBillable = $recordRate;
|
||||
}
|
||||
|
||||
public function addRateBillable(float $recordRate): void
|
||||
{
|
||||
$this->recordRateBillable += $recordRate;
|
||||
}
|
||||
|
||||
public function getRateBillableExported(): float
|
||||
{
|
||||
return $this->recordRateBillableExported;
|
||||
@@ -182,6 +237,11 @@ class TimesheetCountedStatistic implements \JsonSerializable
|
||||
$this->recordRateBillableExported = $recordRate;
|
||||
}
|
||||
|
||||
public function addRateBillableExported(float $recordRate): void
|
||||
{
|
||||
$this->recordRateBillableExported += $recordRate;
|
||||
}
|
||||
|
||||
public function getDurationExported(): int
|
||||
{
|
||||
return $this->recordDurationExported;
|
||||
@@ -192,6 +252,11 @@ class TimesheetCountedStatistic implements \JsonSerializable
|
||||
$this->recordDurationExported = $recordDuration;
|
||||
}
|
||||
|
||||
public function addDurationExported(int $recordDuration): void
|
||||
{
|
||||
$this->recordDurationExported += $recordDuration;
|
||||
}
|
||||
|
||||
public function getRateExported(): float
|
||||
{
|
||||
return $this->recordRateExported;
|
||||
@@ -202,6 +267,11 @@ class TimesheetCountedStatistic implements \JsonSerializable
|
||||
$this->recordRateExported = $recordRate;
|
||||
}
|
||||
|
||||
public function addRateExported(float $recordRate): void
|
||||
{
|
||||
$this->recordRateExported += $recordRate;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): mixed
|
||||
{
|
||||
return [
|
||||
|
||||
@@ -34,7 +34,8 @@ use App\Repository\ProjectRepository;
|
||||
use App\Repository\TimesheetRepository;
|
||||
use App\Repository\UserRepository;
|
||||
use App\Timesheet\DateTimeFactory;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
@@ -54,14 +55,9 @@ class ProjectStatisticService
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING: this method does not respect the budget type. Your results will always be wither the "full lifetime data" or the "selected date-range".
|
||||
*
|
||||
* @param Project $project
|
||||
* @param DateTime|null $begin
|
||||
* @param DateTime|null $end
|
||||
* @return ProjectStatistic
|
||||
* WARNING: this method does not respect the budget type. Your results will always be with the "full lifetime data" or the "selected date-range".
|
||||
*/
|
||||
public function getProjectStatistics(Project $project, ?DateTime $begin = null, ?DateTime $end = null): ProjectStatistic
|
||||
public function getProjectStatistics(Project $project, ?DateTimeInterface $begin = null, ?DateTimeInterface $end = null): ProjectStatistic
|
||||
{
|
||||
$statistics = $this->getBudgetStatistic([$project], $begin, $end);
|
||||
$event = new ProjectStatisticEvent($project, array_pop($statistics), $begin, $end);
|
||||
@@ -71,14 +67,13 @@ class ProjectStatisticService
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ProjectInactiveQuery $query
|
||||
* @return Project[]
|
||||
*/
|
||||
public function findInactiveProjects(ProjectInactiveQuery $query): array
|
||||
{
|
||||
$user = $query->getUser();
|
||||
$lastChange = clone $query->getLastChange();
|
||||
$now = new DateTime('now', $lastChange->getTimezone());
|
||||
$lastChange = DateTimeImmutable::createFromInterface($query->getLastChange());
|
||||
$now = new DateTimeImmutable('now', $lastChange->getTimezone());
|
||||
|
||||
$qb2 = $this->projectRepository->createQueryBuilder('t1');
|
||||
$qb2
|
||||
@@ -101,15 +96,15 @@ class ProjectStatisticService
|
||||
$qb->expr()->lte('p.start', ':project_start')
|
||||
)
|
||||
)
|
||||
->setParameter('project_start', $now, Types::DATETIME_MUTABLE)
|
||||
->setParameter('project_start', $now, Types::DATETIME_IMMUTABLE)
|
||||
->andWhere(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->isNull('p.end'),
|
||||
$qb->expr()->gte('p.end', ':project_end')
|
||||
)
|
||||
)
|
||||
->setParameter('project_end', $now, Types::DATETIME_MUTABLE)
|
||||
->setParameter('begin', $lastChange, Types::DATETIME_MUTABLE)
|
||||
->setParameter('project_end', $now, Types::DATETIME_IMMUTABLE)
|
||||
->setParameter('begin', $lastChange, Types::DATETIME_IMMUTABLE)
|
||||
;
|
||||
|
||||
$this->projectRepository->addPermissionCriteria($qb, $user);
|
||||
@@ -125,7 +120,6 @@ class ProjectStatisticService
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ProjectDateRangeQuery $query
|
||||
* @return Project[]
|
||||
*/
|
||||
public function findProjectsForDateRange(ProjectDateRangeQuery $query, DateRange $dateRange): array
|
||||
@@ -208,7 +202,7 @@ class ProjectStatisticService
|
||||
return $projects;
|
||||
}
|
||||
|
||||
public function getBudgetStatisticModel(Project $project, DateTime $today): ProjectBudgetStatisticModel
|
||||
public function getBudgetStatisticModel(Project $project, DateTimeInterface $today): ProjectBudgetStatisticModel
|
||||
{
|
||||
$stats = new ProjectBudgetStatisticModel($project);
|
||||
$stats->setStatisticTotal($this->getProjectStatistics($project));
|
||||
@@ -229,10 +223,9 @@ class ProjectStatisticService
|
||||
|
||||
/**
|
||||
* @param Project[] $projects
|
||||
* @param DateTime $today
|
||||
* @return ProjectBudgetStatisticModel[]
|
||||
*/
|
||||
public function getBudgetStatisticModelForProjects(array $projects, DateTime $today): array
|
||||
public function getBudgetStatisticModelForProjects(array $projects, DateTimeInterface $today): array
|
||||
{
|
||||
$models = [];
|
||||
$monthly = [];
|
||||
@@ -282,12 +275,9 @@ class ProjectStatisticService
|
||||
|
||||
/**
|
||||
* @param Project[] $projects
|
||||
* @param DateTime $begin
|
||||
* @param DateTime $end
|
||||
* @param DateTime|null $totalsEnd
|
||||
* @return ProjectBudgetStatisticModel[]
|
||||
*/
|
||||
public function getBudgetStatisticModelForProjectsByDateRange(array $projects, DateTime $begin, DateTime $end, ?DateTime $totalsEnd = null): array
|
||||
public function getBudgetStatisticModelForProjectsByDateRange(array $projects, DateTimeInterface $begin, DateTimeInterface $end, ?DateTimeInterface $totalsEnd = null): array
|
||||
{
|
||||
$models = [];
|
||||
|
||||
@@ -313,11 +303,9 @@ class ProjectStatisticService
|
||||
|
||||
/**
|
||||
* @param Project[] $projects
|
||||
* @param DateTime|null $begin
|
||||
* @param DateTime|null $end
|
||||
* @return array<int, ProjectStatistic>
|
||||
*/
|
||||
public function getBudgetStatistic(array $projects, ?DateTime $begin = null, ?DateTime $end = null): array
|
||||
public function getBudgetStatistic(array $projects, ?DateTimeInterface $begin = null, ?DateTimeInterface $end = null): array
|
||||
{
|
||||
$statistics = [];
|
||||
foreach ($projects as $project) {
|
||||
@@ -711,15 +699,13 @@ class ProjectStatisticService
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @param Project[] $projects
|
||||
* @param DateTime $today
|
||||
* @return ProjectViewModel[]
|
||||
*/
|
||||
public function getProjectView(User $user, array $projects, DateTime $today): array
|
||||
public function getProjectView(User $user, array $projects, DateTimeInterface $today): array
|
||||
{
|
||||
$factory = DateTimeFactory::createByUser($user);
|
||||
$today = clone $today;
|
||||
$today = DateTimeImmutable::createFromInterface($today);
|
||||
|
||||
$startOfWeek = $factory->getStartOfWeek($today);
|
||||
$endOfWeek = $factory->getEndOfWeek($today);
|
||||
|
||||
30
src/Repository/Query/DateRangeInterface.php
Normal file
30
src/Repository/Query/DateRangeInterface.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?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\Repository\Query;
|
||||
|
||||
use App\Form\Model\DateRange;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface DateRangeInterface
|
||||
{
|
||||
public function getBegin(): ?\DateTime;
|
||||
|
||||
public function setBegin(\DateTimeInterface $begin): void;
|
||||
|
||||
public function getEnd(): ?\DateTime;
|
||||
|
||||
public function setEnd(\DateTimeInterface $end): void;
|
||||
|
||||
public function getDateRange(): ?DateRange;
|
||||
|
||||
public function setDateRange(DateRange $dateRange): void;
|
||||
}
|
||||
@@ -20,9 +20,9 @@ trait DateRangeTrait
|
||||
return $this->dateRange?->getBegin();
|
||||
}
|
||||
|
||||
public function setBegin(\DateTime $begin): void
|
||||
public function setBegin(\DateTimeInterface $begin): void
|
||||
{
|
||||
$this->dateRange->setBegin($begin);
|
||||
$this->dateRange->setBegin(\DateTime::createFromInterface($begin));
|
||||
}
|
||||
|
||||
public function getEnd(): ?\DateTime
|
||||
@@ -30,9 +30,9 @@ trait DateRangeTrait
|
||||
return $this->dateRange?->getEnd();
|
||||
}
|
||||
|
||||
public function setEnd(\DateTime $end): void
|
||||
public function setEnd(\DateTimeInterface $end): void
|
||||
{
|
||||
$this->dateRange->setEnd($end);
|
||||
$this->dateRange->setEnd(\DateTime::createFromInterface($end));
|
||||
}
|
||||
|
||||
public function getDateRange(): ?DateRange
|
||||
|
||||
@@ -16,7 +16,7 @@ use App\Form\Model\DateRange;
|
||||
/**
|
||||
* Query for created invoices.
|
||||
*/
|
||||
class InvoiceArchiveQuery extends BaseQuery
|
||||
class InvoiceArchiveQuery extends BaseQuery implements DateRangeInterface
|
||||
{
|
||||
use DateRangeTrait;
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ use App\Entity\Tag;
|
||||
use App\Entity\User;
|
||||
use App\Form\Model\DateRange;
|
||||
|
||||
class TimesheetQuery extends ActivityQuery implements BillableInterface
|
||||
class TimesheetQuery extends ActivityQuery implements BillableInterface, DateRangeInterface
|
||||
{
|
||||
use BillableTrait;
|
||||
use DateRangeTrait;
|
||||
|
||||
@@ -62,10 +62,10 @@ final class LocaleFormatExtensions extends AbstractExtension implements LocaleAw
|
||||
return [
|
||||
new TwigTest('weekend', [$this, 'isWeekend']),
|
||||
new TwigTest('today', function ($dateTime): bool {
|
||||
if (!$dateTime instanceof \DateTime) {
|
||||
if (!$dateTime instanceof \DateTimeInterface) {
|
||||
return false;
|
||||
}
|
||||
$compare = new \DateTime('now', $dateTime->getTimezone());
|
||||
$compare = new \DateTimeImmutable('now', $dateTime->getTimezone());
|
||||
|
||||
return $compare->format('Y-m-d') === $dateTime->format('Y-m-d');
|
||||
}),
|
||||
|
||||
@@ -58,8 +58,10 @@ final class TimesheetBudgetUsedValidator extends ConstraintValidator
|
||||
return;
|
||||
}
|
||||
|
||||
$begin = $timesheet->getBegin();
|
||||
|
||||
// we can only work with stopped entries
|
||||
if (null === $timesheet->getEnd() || null === $timesheet->getUser()) {
|
||||
if ($begin === null || $timesheet->getEnd() === null || $timesheet->getUser() === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -115,7 +117,7 @@ final class TimesheetBudgetUsedValidator extends ConstraintValidator
|
||||
if ($duration === $rawData['duration'] &&
|
||||
$rate === $rawData['rate'] &&
|
||||
$timesheet->isBillable() === $rawData['billable'] &&
|
||||
$timesheet->getBegin()->format('Y.m.d') === $rawData['begin']->format('Y.m.d') &&
|
||||
$begin->format('Y.m.d') === $rawData['begin']->format('Y.m.d') &&
|
||||
$timesheet->getProject()->getId() === $projectId &&
|
||||
($timesheet->getActivity() === null || $timesheet->getActivity()->getId() === $activityId)
|
||||
) {
|
||||
@@ -145,11 +147,11 @@ final class TimesheetBudgetUsedValidator extends ConstraintValidator
|
||||
}
|
||||
}
|
||||
|
||||
$monthWasChanged = $timesheet->getBegin()->format('Y.m') !== $rawData['begin']->format('Y.m');
|
||||
$monthWasChanged = $begin->format('Y.m') !== $rawData['begin']->format('Y.m');
|
||||
}
|
||||
|
||||
$now = new DateTime('now', $timesheet->getBegin()->getTimezone());
|
||||
$recordDate = $timesheet->getBegin();
|
||||
$now = new DateTime('now', $begin->getTimezone());
|
||||
$recordDate = $begin;
|
||||
|
||||
if (null !== ($activity = $timesheet->getActivity()) && $activity->hasBudgets()) {
|
||||
$dateTime = $activity->isMonthlyBudget() ? $recordDate : $now;
|
||||
|
||||
@@ -52,7 +52,7 @@ final class TimesheetLockdownValidator extends ConstraintValidator
|
||||
$now = new \DateTime('now', $timesheetStart->getTimezone());
|
||||
|
||||
if (!empty($constraint->now)) {
|
||||
if ($constraint->now instanceof \DateTime) {
|
||||
if ($constraint->now instanceof \DateTimeInterface) {
|
||||
$now = $constraint->now;
|
||||
} elseif (\is_string($constraint->now)) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user