Version 2.0.1 (#3853)
* configure email validation mode to fix deprecation message * allow to use non brand icon in saml provider * new method getCalculatedDuration() * getRawData() by id * only stop entries if new one is running * fix validator tampering with timesheet duration * fix allow setting null as customer * bump packages
This commit is contained in:
@@ -17,11 +17,11 @@ class Constants
|
||||
/**
|
||||
* The current release version
|
||||
*/
|
||||
public const VERSION = '2.0.0';
|
||||
public const VERSION = '2.0.1';
|
||||
/**
|
||||
* The current release: major * 10000 + minor * 100 + patch
|
||||
*/
|
||||
public const VERSION_ID = 20000;
|
||||
public const VERSION_ID = 20001;
|
||||
/**
|
||||
* The software name
|
||||
*/
|
||||
|
||||
@@ -21,12 +21,13 @@ final class UTCDateTimeType extends DateTimeType
|
||||
private static ?\DateTimeZone $utc = null;
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param T $value
|
||||
* @param AbstractPlatform $platform
|
||||
* @return mixed|string
|
||||
* @return (T is null ? null : string)
|
||||
* @template T<\DateTime>
|
||||
* @throws ConversionException
|
||||
*/
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform): mixed
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
|
||||
{
|
||||
if ($value instanceof \DateTime) {
|
||||
$value = clone $value;
|
||||
|
||||
@@ -184,7 +184,7 @@ class Project implements EntityWithMetaFields, EntityWithBudget
|
||||
return $this->customer;
|
||||
}
|
||||
|
||||
public function setCustomer(Customer $customer): Project
|
||||
public function setCustomer(?Customer $customer): Project
|
||||
{
|
||||
$this->customer = $customer;
|
||||
|
||||
|
||||
@@ -322,12 +322,21 @@ class Timesheet implements EntityWithMetaFields, ExportableItem
|
||||
{
|
||||
// only auto calculate if manually set duration is null - the result is important for eg. validations
|
||||
if ($calculate && $this->duration === null && $this->begin !== null && $this->end !== null) {
|
||||
return $this->end->getTimestamp() - $this->begin->getTimestamp();
|
||||
return $this->getCalculatedDuration();
|
||||
}
|
||||
|
||||
return $this->duration;
|
||||
}
|
||||
|
||||
public function getCalculatedDuration(): ?int
|
||||
{
|
||||
if ($this->begin !== null && $this->end !== null) {
|
||||
return $this->end->getTimestamp() - $this->begin->getTimestamp();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @return Timesheet
|
||||
|
||||
@@ -51,10 +51,10 @@ class TimesheetRepository extends EntityRepository
|
||||
/**
|
||||
* Fetches the raw data of a timesheet, to allow comparison e.g. of submitted and previously stored data.
|
||||
*
|
||||
* @param Timesheet $id
|
||||
* @param int $id
|
||||
* @return array
|
||||
*/
|
||||
public function getRawData(Timesheet $id): array
|
||||
public function getRawData(int $id): array
|
||||
{
|
||||
$qb = $this->createQueryBuilder('t');
|
||||
$qb
|
||||
|
||||
@@ -28,7 +28,7 @@ final class DurationCalculator implements CalculatorInterface
|
||||
return;
|
||||
}
|
||||
|
||||
$duration = $record->getEnd()->getTimestamp() - $record->getBegin()->getTimestamp();
|
||||
$duration = $record->getCalculatedDuration();
|
||||
$record->setDuration($duration);
|
||||
|
||||
$this->roundings->applyRoundings($record);
|
||||
|
||||
@@ -116,7 +116,7 @@ final class RoundingService
|
||||
$rounder->roundEnd($record, $rounding['end']);
|
||||
|
||||
if ($record->getBegin() !== null) {
|
||||
$duration = $record->getEnd()->getTimestamp() - $record->getBegin()->getTimestamp();
|
||||
$duration = $record->getCalculatedDuration();
|
||||
$record->setDuration($duration);
|
||||
|
||||
$rounder->roundDuration($record, $rounding['duration']);
|
||||
|
||||
@@ -127,14 +127,16 @@ final class TimesheetService
|
||||
$this->repository->save($timesheet);
|
||||
$this->dispatcher->dispatch(new TimesheetCreatePostEvent($timesheet));
|
||||
|
||||
// TODO really stop always or only if $timesheet->getEnd() === null
|
||||
try {
|
||||
$this->stopActiveEntries($timesheet);
|
||||
} catch (ValidationFailedException $vex) {
|
||||
// could happen for timesheets that were started in the future (end before begin)
|
||||
// or if you try to create a new timesheet while an old one is running for too long
|
||||
throw new ValidationFailedException($vex->getViolations(), 'Cannot stop running timesheet');
|
||||
if ($timesheet->isRunning()) {
|
||||
try {
|
||||
$this->stopActiveEntries($timesheet);
|
||||
} catch (ValidationFailedException $vex) {
|
||||
// could happen for timesheets that were started in the future (end before begin)
|
||||
// or if you try to create a new timesheet while an old one is running for too long
|
||||
throw new ValidationFailedException($vex->getViolations(), 'Cannot stop running timesheet');
|
||||
}
|
||||
}
|
||||
|
||||
$this->repository->commit();
|
||||
} catch (\Exception $ex) {
|
||||
$this->repository->rollback();
|
||||
|
||||
@@ -73,10 +73,24 @@ final class TimesheetBudgetUsedValidator extends ConstraintValidator
|
||||
return;
|
||||
}
|
||||
|
||||
$id = $timesheet->getId();
|
||||
|
||||
// when changing the date via the calendar and/or the API, the duration will not be reset by the
|
||||
// duration calculator (which runs after validation!) so we manually reset the duration before
|
||||
$timesheet->setDuration(null);
|
||||
$duration = $timesheet->getDuration();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Old solution (buggy) - duration MAY NOT BE RESET!
|
||||
// this will cause the timesheet to be deleted in the Weekly-QuickEntry-Flow
|
||||
// ------------------------------------------------------------------------
|
||||
// $timesheet->setDuration(null);
|
||||
// $duration = $timesheet->getDuration();
|
||||
|
||||
// another possible solution is cloning the timesheet
|
||||
// $timesheet = clone $timesheet;
|
||||
// $timesheet->setDuration(null);
|
||||
// $duration = $timesheet->getDuration();
|
||||
|
||||
$duration = $timesheet->getCalculatedDuration();
|
||||
|
||||
$timeRate = $this->rateService->calculate($timesheet);
|
||||
$rate = $timeRate->getRate();
|
||||
@@ -89,8 +103,8 @@ final class TimesheetBudgetUsedValidator extends ConstraintValidator
|
||||
$customerRate = $rate;
|
||||
$monthWasChanged = false;
|
||||
|
||||
if ($timesheet->getId() !== null) {
|
||||
$rawData = $this->timesheetRepository->getRawData($timesheet);
|
||||
if ($id !== null) {
|
||||
$rawData = $this->timesheetRepository->getRawData($id);
|
||||
|
||||
$activityId = (int) $rawData['activity'];
|
||||
$projectId = (int) $rawData['project'];
|
||||
|
||||
@@ -39,9 +39,12 @@ final class TimesheetLongRunningValidator extends ConstraintValidator
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var int $duration */
|
||||
$duration = $timesheet->getCalculatedDuration();
|
||||
|
||||
// one year is currently the maximum that can be logged (which is already not logically)
|
||||
// the database column could hold more data, but let's limit it here
|
||||
if ($timesheet->getDuration() > 31536000) {
|
||||
if ($duration > 31536000) {
|
||||
$this->context->buildViolation($constraint->maximumMessage)
|
||||
->setTranslationDomain('validators')
|
||||
->atPath('duration')
|
||||
@@ -57,7 +60,6 @@ final class TimesheetLongRunningValidator extends ConstraintValidator
|
||||
return;
|
||||
}
|
||||
|
||||
$duration = $timesheet->getEnd()->getTimestamp() - $timesheet->getBegin()->getTimestamp();
|
||||
// float on purpose, because one second more than the configured minutes is already too long
|
||||
$minutes = $duration / 60;
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ final class TimesheetZeroDurationValidator extends ConstraintValidator
|
||||
|
||||
$duration = 0;
|
||||
if ($timesheet->getEnd() !== null && $timesheet->getBegin() !== null) {
|
||||
$duration = $timesheet->getEnd()->getTimestamp() - $timesheet->getBegin()->getTimestamp();
|
||||
$duration = $timesheet->getCalculatedDuration();
|
||||
}
|
||||
|
||||
if ($duration <= 0) {
|
||||
|
||||
Reference in New Issue
Block a user