weekly quick-entry form (#2793)
This commit is contained in:
@@ -25,7 +25,7 @@ class ProjectValidator extends ConstraintValidator
|
||||
public function validate($value, Constraint $constraint)
|
||||
{
|
||||
if (!($constraint instanceof ProjectConstraint)) {
|
||||
throw new UnexpectedTypeException($constraint, __NAMESPACE__ . '\Project');
|
||||
throw new UnexpectedTypeException($constraint, ProjectConstraint::class);
|
||||
}
|
||||
|
||||
if (!\is_object($value) || !($value instanceof Project)) {
|
||||
|
||||
25
src/Validator/Constraints/QuickEntryModel.php
Normal file
25
src/Validator/Constraints/QuickEntryModel.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?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\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({"CLASS"})
|
||||
*/
|
||||
class QuickEntryModel extends Constraint
|
||||
{
|
||||
public const ACTIVITY_REQUIRED = 'quick-entry-model-01';
|
||||
public const PROJECT_REQUIRED = 'quick-entry-model-02';
|
||||
|
||||
public $messageActivityRequired = 'An activity needs to be selected.';
|
||||
public $messageProjectRequired = 'A project needs to be selected.';
|
||||
}
|
||||
56
src/Validator/Constraints/QuickEntryModelValidator.php
Normal file
56
src/Validator/Constraints/QuickEntryModelValidator.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?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\Validator\Constraints;
|
||||
|
||||
use App\Model\QuickEntryModel;
|
||||
use App\Validator\Constraints\QuickEntryModel as QuickEntryModelConstraint;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
|
||||
class QuickEntryModelValidator extends ConstraintValidator
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($value, Constraint $constraint)
|
||||
{
|
||||
if (!$constraint instanceof QuickEntryModelConstraint) {
|
||||
throw new UnexpectedTypeException($constraint, QuickEntryModelConstraint::class);
|
||||
}
|
||||
|
||||
if (!\is_object($value) || !($value instanceof QuickEntryModel)) {
|
||||
throw new UnexpectedTypeException($value, QuickEntryModel::class);
|
||||
}
|
||||
|
||||
/** @var QuickEntryModel $model */
|
||||
$model = $value;
|
||||
|
||||
if ($model->isPrototype()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($model->hasExistingTimesheet() || $model->hasNewTimesheet()) {
|
||||
if ($model->getActivity() === null) {
|
||||
$this->context->buildViolation($constraint->messageActivityRequired)
|
||||
->atPath('activity')
|
||||
->setCode(QuickEntryModelConstraint::ACTIVITY_REQUIRED)
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
if ($model->getProject() === null) {
|
||||
$this->context->buildViolation($constraint->messageProjectRequired)
|
||||
->atPath('project')
|
||||
->setCode(QuickEntryModelConstraint::PROJECT_REQUIRED)
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/Validator/Constraints/QuickEntryTimesheet.php
Normal file
20
src/Validator/Constraints/QuickEntryTimesheet.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?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\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({"CLASS"})
|
||||
*/
|
||||
class QuickEntryTimesheet extends Constraint
|
||||
{
|
||||
}
|
||||
61
src/Validator/Constraints/QuickEntryTimesheetValidator.php
Normal file
61
src/Validator/Constraints/QuickEntryTimesheetValidator.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?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\Validator\Constraints;
|
||||
|
||||
use App\Entity\Timesheet as TimesheetEntity;
|
||||
use App\Validator\Constraints\QuickEntryTimesheet as QuickEntryTimesheetConstraint;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
|
||||
class QuickEntryTimesheetValidator extends ConstraintValidator
|
||||
{
|
||||
/**
|
||||
* @var Constraint[]
|
||||
*/
|
||||
private $constraints;
|
||||
|
||||
/**
|
||||
* @param Constraint[] $constraints
|
||||
*/
|
||||
public function __construct(iterable $constraints)
|
||||
{
|
||||
$this->constraints = $constraints;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($value, Constraint $constraint)
|
||||
{
|
||||
if (!$constraint instanceof QuickEntryTimesheetConstraint) {
|
||||
throw new UnexpectedTypeException($constraint, QuickEntryTimesheetConstraint::class);
|
||||
}
|
||||
|
||||
if (!\is_object($value) || !($value instanceof TimesheetEntity)) {
|
||||
throw new UnexpectedTypeException($value, TimesheetEntity::class);
|
||||
}
|
||||
|
||||
/** @var TimesheetEntity $timesheet */
|
||||
$timesheet = $value;
|
||||
|
||||
if ($timesheet->getId() === null && $timesheet->getDuration(false) === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->constraints as $constraint) {
|
||||
$this->context
|
||||
->getValidator()
|
||||
->inContext($this->context)
|
||||
->atPath('duration')
|
||||
->validate($timesheet, $constraint, [Constraint::DEFAULT_GROUP]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,22 +18,32 @@ use Symfony\Component\Validator\Constraint;
|
||||
*/
|
||||
class Timesheet extends Constraint
|
||||
{
|
||||
public const MISSING_BEGIN_ERROR = 'kimai-timesheet-81';
|
||||
public const END_BEFORE_BEGIN_ERROR = 'kimai-timesheet-82';
|
||||
public const MISSING_ACTIVITY_ERROR = 'kimai-timesheet-84';
|
||||
public const MISSING_PROJECT_ERROR = 'kimai-timesheet-85';
|
||||
public const ACTIVITY_PROJECT_MISMATCH_ERROR = 'kimai-timesheet-86';
|
||||
public const DISABLED_ACTIVITY_ERROR = 'kimai-timesheet-87';
|
||||
public const DISABLED_PROJECT_ERROR = 'kimai-timesheet-88';
|
||||
public const DISABLED_CUSTOMER_ERROR = 'kimai-timesheet-89';
|
||||
public const PROJECT_NOT_STARTED = 'kimai-timesheet-91';
|
||||
public const PROJECT_ALREADY_ENDED = 'kimai-timesheet-92';
|
||||
/** @deprecated since 1.15.3 - use TimesheetBasic::MISSING_BEGIN_ERROR instead */
|
||||
public const MISSING_BEGIN_ERROR = TimesheetBasic::MISSING_BEGIN_ERROR;
|
||||
/** @deprecated since 1.15.3 - use TimesheetBasic::END_BEFORE_BEGIN_ERROR instead */
|
||||
public const END_BEFORE_BEGIN_ERROR = TimesheetBasic::END_BEFORE_BEGIN_ERROR;
|
||||
/** @deprecated since 1.15.3 - use TimesheetBasic::MISSING_ACTIVITY_ERROR instead */
|
||||
public const MISSING_ACTIVITY_ERROR = TimesheetBasic::MISSING_ACTIVITY_ERROR;
|
||||
/** @deprecated since 1.15.3 - use TimesheetBasic::MISSING_PROJECT_ERROR instead */
|
||||
public const MISSING_PROJECT_ERROR = TimesheetBasic::MISSING_PROJECT_ERROR;
|
||||
/** @deprecated since 1.15.3 - use TimesheetBasic::ACTIVITY_PROJECT_MISMATCH_ERROR instead */
|
||||
public const ACTIVITY_PROJECT_MISMATCH_ERROR = TimesheetBasic::ACTIVITY_PROJECT_MISMATCH_ERROR;
|
||||
/** @deprecated since 1.15.3 - use TimesheetBasic::DISABLED_ACTIVITY_ERROR instead */
|
||||
public const DISABLED_ACTIVITY_ERROR = TimesheetBasic::DISABLED_ACTIVITY_ERROR;
|
||||
/** @deprecated since 1.15.3 - use TimesheetBasic::DISABLED_PROJECT_ERROR instead */
|
||||
public const DISABLED_PROJECT_ERROR = TimesheetBasic::DISABLED_PROJECT_ERROR;
|
||||
/** @deprecated since 1.15.3 - use TimesheetBasic::DISABLED_CUSTOMER_ERROR instead */
|
||||
public const DISABLED_CUSTOMER_ERROR = TimesheetBasic::DISABLED_CUSTOMER_ERROR;
|
||||
/** @deprecated since 1.15.3 - use TimesheetBasic::PROJECT_NOT_STARTED instead */
|
||||
public const PROJECT_NOT_STARTED = TimesheetBasic::PROJECT_NOT_STARTED;
|
||||
/** @deprecated since 1.15.3 - use TimesheetBasic::PROJECT_ALREADY_ENDED instead */
|
||||
public const PROJECT_ALREADY_ENDED = TimesheetBasic::PROJECT_ALREADY_ENDED;
|
||||
|
||||
protected static $errorNames = [
|
||||
self::MISSING_BEGIN_ERROR => 'You must submit a begin date.',
|
||||
self::END_BEFORE_BEGIN_ERROR => 'End date must not be earlier then start date.',
|
||||
self::MISSING_ACTIVITY_ERROR => 'A timesheet must have an activity.',
|
||||
self::MISSING_PROJECT_ERROR => 'A timesheet must have a project.',
|
||||
self::MISSING_ACTIVITY_ERROR => 'An activity needs to be selected.',
|
||||
self::MISSING_PROJECT_ERROR => 'A project needs to be selected.',
|
||||
self::ACTIVITY_PROJECT_MISMATCH_ERROR => 'Project mismatch, project specific activity and timesheet project are different.',
|
||||
self::DISABLED_ACTIVITY_ERROR => 'Cannot start a disabled activity.',
|
||||
self::DISABLED_PROJECT_ERROR => 'Cannot start a disabled project.',
|
||||
|
||||
50
src/Validator/Constraints/TimesheetBasic.php
Normal file
50
src/Validator/Constraints/TimesheetBasic.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?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\Validator\Constraints;
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({"CLASS", "PROPERTY", "METHOD", "ANNOTATION"})
|
||||
*/
|
||||
class TimesheetBasic extends TimesheetConstraint
|
||||
{
|
||||
public const MISSING_BEGIN_ERROR = 'kimai-timesheet-81';
|
||||
public const END_BEFORE_BEGIN_ERROR = 'kimai-timesheet-82';
|
||||
public const MISSING_ACTIVITY_ERROR = 'kimai-timesheet-84';
|
||||
public const MISSING_PROJECT_ERROR = 'kimai-timesheet-85';
|
||||
public const ACTIVITY_PROJECT_MISMATCH_ERROR = 'kimai-timesheet-86';
|
||||
public const DISABLED_ACTIVITY_ERROR = 'kimai-timesheet-87';
|
||||
public const DISABLED_PROJECT_ERROR = 'kimai-timesheet-88';
|
||||
public const DISABLED_CUSTOMER_ERROR = 'kimai-timesheet-89';
|
||||
public const PROJECT_NOT_STARTED = 'kimai-timesheet-91';
|
||||
public const PROJECT_ALREADY_ENDED = 'kimai-timesheet-92';
|
||||
|
||||
protected static $errorNames = [
|
||||
self::MISSING_BEGIN_ERROR => 'You must submit a begin date.',
|
||||
self::END_BEFORE_BEGIN_ERROR => 'End date must not be earlier then start date.',
|
||||
self::MISSING_ACTIVITY_ERROR => 'An activity needs to be selected.',
|
||||
self::MISSING_PROJECT_ERROR => 'A project needs to be selected.',
|
||||
self::ACTIVITY_PROJECT_MISMATCH_ERROR => 'Project mismatch, project specific activity and timesheet project are different.',
|
||||
self::DISABLED_ACTIVITY_ERROR => 'Cannot start a disabled activity.',
|
||||
self::DISABLED_PROJECT_ERROR => 'Cannot start a disabled project.',
|
||||
self::DISABLED_CUSTOMER_ERROR => 'Cannot start a disabled customer.',
|
||||
self::PROJECT_NOT_STARTED => 'The project has not started at that time.',
|
||||
self::PROJECT_ALREADY_ENDED => 'The project is finished at that time.',
|
||||
];
|
||||
|
||||
public $message = 'This timesheet has invalid settings.';
|
||||
|
||||
public function getTargets()
|
||||
{
|
||||
return self::CLASS_CONSTRAINT;
|
||||
}
|
||||
}
|
||||
172
src/Validator/Constraints/TimesheetBasicValidator.php
Normal file
172
src/Validator/Constraints/TimesheetBasicValidator.php
Normal file
@@ -0,0 +1,172 @@
|
||||
<?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\Validator\Constraints;
|
||||
|
||||
use App\Entity\Timesheet as TimesheetEntity;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
|
||||
final class TimesheetBasicValidator extends ConstraintValidator
|
||||
{
|
||||
/**
|
||||
* @param TimesheetEntity $timesheet
|
||||
* @param Constraint $constraint
|
||||
*/
|
||||
public function validate($timesheet, Constraint $constraint)
|
||||
{
|
||||
if (!($constraint instanceof TimesheetBasic)) {
|
||||
throw new UnexpectedTypeException($constraint, TimesheetBasic::class);
|
||||
}
|
||||
|
||||
if (!\is_object($timesheet) || !($timesheet instanceof TimesheetEntity)) {
|
||||
throw new UnexpectedTypeException($timesheet, TimesheetEntity::class);
|
||||
}
|
||||
|
||||
$this->validateBeginAndEnd($timesheet, $this->context);
|
||||
$this->validateActivityAndProject($timesheet, $this->context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TimesheetEntity $timesheet
|
||||
* @param ExecutionContextInterface $context
|
||||
*/
|
||||
protected function validateBeginAndEnd(TimesheetEntity $timesheet, ExecutionContextInterface $context)
|
||||
{
|
||||
$begin = $timesheet->getBegin();
|
||||
$end = $timesheet->getEnd();
|
||||
|
||||
if (null === $begin) {
|
||||
$context->buildViolation('You must submit a begin date.')
|
||||
->atPath('begin')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetBasic::MISSING_BEGIN_ERROR)
|
||||
->addViolation();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (null !== $end && $begin > $end) {
|
||||
$context->buildViolation('End date must not be earlier then start date.')
|
||||
->atPath('end')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetBasic::END_BEFORE_BEGIN_ERROR)
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TimesheetEntity $timesheet
|
||||
* @param ExecutionContextInterface $context
|
||||
*/
|
||||
protected function validateActivityAndProject(TimesheetEntity $timesheet, ExecutionContextInterface $context)
|
||||
{
|
||||
if (null === ($activity = $timesheet->getActivity())) {
|
||||
$context->buildViolation('An activity needs to be selected.')
|
||||
->atPath('activity')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetBasic::MISSING_ACTIVITY_ERROR)
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
if (null === ($project = $timesheet->getProject())) {
|
||||
$context->buildViolation('A project needs to be selected.')
|
||||
->atPath('project')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetBasic::MISSING_PROJECT_ERROR)
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
if (null === $activity || null === $project) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null !== $activity->getProject() && $activity->getProject() !== $project) {
|
||||
$context->buildViolation('Project mismatch, project specific activity and timesheet project are different.')
|
||||
->atPath('project')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetBasic::ACTIVITY_PROJECT_MISMATCH_ERROR)
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
$timesheetEnd = $timesheet->getEnd();
|
||||
$newOrStarted = null === $timesheetEnd || $timesheet->getId() === null;
|
||||
|
||||
if ($newOrStarted && !$activity->isVisible()) {
|
||||
$context->buildViolation('Cannot start a disabled activity.')
|
||||
->atPath('activity')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetBasic::DISABLED_ACTIVITY_ERROR)
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
if ($newOrStarted && !$project->isVisible()) {
|
||||
$context->buildViolation('Cannot start a disabled project.')
|
||||
->atPath('project')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetBasic::DISABLED_PROJECT_ERROR)
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
if ($newOrStarted && !$project->getCustomer()->isVisible()) {
|
||||
$context->buildViolation('Cannot start a disabled customer.')
|
||||
->atPath('customer')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetBasic::DISABLED_CUSTOMER_ERROR)
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
$pathStart = 'begin';
|
||||
$pathEnd = 'end';
|
||||
|
||||
$projectBegin = $project->getStart();
|
||||
$projectEnd = $project->getEnd();
|
||||
|
||||
if (null === $projectBegin && null === $projectEnd) {
|
||||
return;
|
||||
}
|
||||
|
||||
$timesheetStart = $timesheet->getBegin();
|
||||
$timesheetEnd = $timesheet->getEnd();
|
||||
|
||||
if (null !== $timesheetStart && $pathStart !== null) {
|
||||
if (null !== $projectBegin && $timesheetStart->getTimestamp() < $projectBegin->getTimestamp()) {
|
||||
$context->buildViolation('The project has not started at that time.')
|
||||
->atPath($pathStart)
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetBasic::PROJECT_NOT_STARTED)
|
||||
->addViolation();
|
||||
} elseif (null !== $projectEnd && $timesheetStart->getTimestamp() > $projectEnd->getTimestamp()) {
|
||||
$context->buildViolation('The project is finished at that time.')
|
||||
->atPath($pathStart)
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetBasic::PROJECT_ALREADY_ENDED)
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $timesheetEnd && $pathEnd !== null) {
|
||||
if (null !== $projectEnd && $timesheetEnd->getTimestamp() > $projectEnd->getTimestamp()) {
|
||||
$context->buildViolation('The project is finished at that time.')
|
||||
->atPath($pathEnd)
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetBasic::PROJECT_ALREADY_ENDED)
|
||||
->addViolation();
|
||||
} elseif (null !== $projectBegin && $timesheetEnd->getTimestamp() < $projectBegin->getTimestamp()) {
|
||||
$context->buildViolation('The project has not started at that time.')
|
||||
->atPath($pathEnd)
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetBasic::PROJECT_NOT_STARTED)
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,11 @@ final class TimesheetBudgetUsedValidator extends ConstraintValidator
|
||||
$duration = $timesheet->getEnd()->getTimestamp() - $timesheet->getBegin()->getTimestamp();
|
||||
}
|
||||
|
||||
// this validator needs a project to calculate the rates
|
||||
if ($timesheet->getProject() === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$timeRate = $this->rateService->calculate($timesheet);
|
||||
$rate = $timeRate->getRate();
|
||||
|
||||
|
||||
30
src/Validator/Constraints/TimesheetExported.php
Normal file
30
src/Validator/Constraints/TimesheetExported.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\Validator\Constraints;
|
||||
|
||||
final class TimesheetExported extends TimesheetConstraint
|
||||
{
|
||||
public const TIMESHEET_EXPORTED = 'kimai-timesheet-exported-01';
|
||||
|
||||
protected static $errorNames = [
|
||||
self::TIMESHEET_EXPORTED => 'This timesheet is already exported.',
|
||||
];
|
||||
|
||||
public $message = 'This timesheet is already exported.';
|
||||
/**
|
||||
* @var \DateTime|string|null
|
||||
*/
|
||||
public $now;
|
||||
|
||||
public function getTargets()
|
||||
{
|
||||
return self::CLASS_CONSTRAINT;
|
||||
}
|
||||
}
|
||||
55
src/Validator/Constraints/TimesheetExportedValidator.php
Normal file
55
src/Validator/Constraints/TimesheetExportedValidator.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?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\Validator\Constraints;
|
||||
|
||||
use App\Entity\Timesheet as TimesheetEntity;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
|
||||
final class TimesheetExportedValidator extends ConstraintValidator
|
||||
{
|
||||
private $security;
|
||||
|
||||
public function __construct(Security $security)
|
||||
{
|
||||
$this->security = $security;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TimesheetEntity $timesheet
|
||||
* @param Constraint $constraint
|
||||
*/
|
||||
public function validate($timesheet, Constraint $constraint)
|
||||
{
|
||||
if (!($constraint instanceof TimesheetExported)) {
|
||||
throw new UnexpectedTypeException($constraint, TimesheetExported::class);
|
||||
}
|
||||
|
||||
if (!\is_object($timesheet) || !($timesheet instanceof TimesheetEntity)) {
|
||||
throw new UnexpectedTypeException($timesheet, TimesheetEntity::class);
|
||||
}
|
||||
|
||||
if (!$timesheet->isExported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null !== $this->security->getUser() && $this->security->isGranted('edit_exported_timesheet')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->context->buildViolation(TimesheetExported::getErrorName(TimesheetExported::TIMESHEET_EXPORTED))
|
||||
->atPath('exported')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetExported::TIMESHEET_EXPORTED)
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
@@ -12,12 +12,15 @@ namespace App\Validator\Constraints;
|
||||
final class TimesheetLongRunning extends TimesheetConstraint
|
||||
{
|
||||
public const LONG_RUNNING = 'kimai-timesheet-long-running-01';
|
||||
public const MAXIMUM = 'kimai-timesheet-long-running-02';
|
||||
|
||||
protected static $errorNames = [
|
||||
self::LONG_RUNNING => 'TIMESHEET_LONG_RUNNING',
|
||||
self::MAXIMUM => 'MAXIMUM',
|
||||
];
|
||||
|
||||
public $message = 'Maximum duration of {{ value }} hours exceeded.';
|
||||
public $maximumMessage = 'Maximum duration exceeded.';
|
||||
|
||||
public function getTargets()
|
||||
{
|
||||
|
||||
@@ -42,6 +42,18 @@ final class TimesheetLongRunningValidator extends ConstraintValidator
|
||||
return;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
$this->context->buildViolation($constraint->maximumMessage)
|
||||
->setTranslationDomain('validators')
|
||||
->atPath('duration')
|
||||
->setCode(TimesheetLongRunning::MAXIMUM)
|
||||
->addViolation();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$maxMinutes = $this->systemConfiguration->getTimesheetLongRunningDuration();
|
||||
|
||||
if ($maxMinutes <= 0) {
|
||||
|
||||
@@ -28,7 +28,7 @@ class TimesheetMultiUpdate extends Constraint
|
||||
|
||||
protected static $errorNames = [
|
||||
self::MISSING_ACTIVITY_ERROR => 'You need to choose an activity, if the project should be changed.',
|
||||
self::MISSING_PROJECT_ERROR => 'A timesheet must have a project.',
|
||||
self::MISSING_PROJECT_ERROR => 'A project needs to be selected.',
|
||||
self::ACTIVITY_PROJECT_MISMATCH_ERROR => 'Project mismatch: chosen project does not match the activity project.',
|
||||
self::DISABLED_ACTIVITY_ERROR => 'Cannot start a disabled activity.',
|
||||
self::DISABLED_PROJECT_ERROR => 'Cannot start a disabled project.',
|
||||
|
||||
@@ -13,7 +13,6 @@ use App\Entity\Timesheet as TimesheetEntity;
|
||||
use App\Validator\Constraints\Timesheet as TimesheetConstraint;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
|
||||
final class TimesheetValidator extends ConstraintValidator
|
||||
@@ -45,10 +44,6 @@ final class TimesheetValidator extends ConstraintValidator
|
||||
throw new UnexpectedTypeException($timesheet, TimesheetEntity::class);
|
||||
}
|
||||
|
||||
$this->validateBeginAndEnd($timesheet, $this->context);
|
||||
$this->validateActivityAndProject($timesheet, $this->context);
|
||||
$this->validateActiveLimit($timesheet, $this->context);
|
||||
|
||||
foreach ($this->constraints as $constraint) {
|
||||
$this->context
|
||||
->getValidator()
|
||||
@@ -56,142 +51,4 @@ final class TimesheetValidator extends ConstraintValidator
|
||||
->validate($timesheet, $constraint, [Constraint::DEFAULT_GROUP]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TimesheetEntity $timesheet
|
||||
* @param ExecutionContextInterface $context
|
||||
*/
|
||||
protected function validateActiveLimit(TimesheetEntity $timesheet, ExecutionContextInterface $context)
|
||||
{
|
||||
// TODO check active entries against hard_limit
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TimesheetEntity $timesheet
|
||||
* @param ExecutionContextInterface $context
|
||||
*/
|
||||
protected function validateBeginAndEnd(TimesheetEntity $timesheet, ExecutionContextInterface $context)
|
||||
{
|
||||
$begin = $timesheet->getBegin();
|
||||
$end = $timesheet->getEnd();
|
||||
|
||||
if (null === $begin) {
|
||||
$context->buildViolation('You must submit a begin date.')
|
||||
->atPath('begin')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetConstraint::MISSING_BEGIN_ERROR)
|
||||
->addViolation();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (null !== $end && $begin > $end) {
|
||||
$context->buildViolation('End date must not be earlier then start date.')
|
||||
->atPath('end')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetConstraint::END_BEFORE_BEGIN_ERROR)
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TimesheetEntity $timesheet
|
||||
* @param ExecutionContextInterface $context
|
||||
*/
|
||||
protected function validateActivityAndProject(TimesheetEntity $timesheet, ExecutionContextInterface $context)
|
||||
{
|
||||
if (null === ($activity = $timesheet->getActivity())) {
|
||||
$context->buildViolation('A timesheet must have an activity.')
|
||||
->atPath('activity')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetConstraint::MISSING_ACTIVITY_ERROR)
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
if (null === ($project = $timesheet->getProject())) {
|
||||
$context->buildViolation('A timesheet must have a project.')
|
||||
->atPath('project')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetConstraint::MISSING_PROJECT_ERROR)
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
if (null === $activity || null === $project) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null !== $activity->getProject() && $activity->getProject() !== $project) {
|
||||
$context->buildViolation('Project mismatch, project specific activity and timesheet project are different.')
|
||||
->atPath('project')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetConstraint::ACTIVITY_PROJECT_MISMATCH_ERROR)
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
$timesheetEnd = $timesheet->getEnd();
|
||||
|
||||
if (null === $timesheetEnd && !$activity->isVisible()) {
|
||||
$context->buildViolation('Cannot start a disabled activity.')
|
||||
->atPath('activity')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetConstraint::DISABLED_ACTIVITY_ERROR)
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
if (null === $timesheetEnd && !$project->isVisible()) {
|
||||
$context->buildViolation('Cannot start a disabled project.')
|
||||
->atPath('project')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetConstraint::DISABLED_PROJECT_ERROR)
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
if (null === $timesheetEnd && !$project->getCustomer()->isVisible()) {
|
||||
$context->buildViolation('Cannot start a disabled customer.')
|
||||
->atPath('customer')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetConstraint::DISABLED_CUSTOMER_ERROR)
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
$projectBegin = $project->getStart();
|
||||
$projectEnd = $project->getEnd();
|
||||
|
||||
if (null !== $projectBegin || null !== $projectEnd) {
|
||||
$timesheetStart = $timesheet->getBegin();
|
||||
$timesheetEnd = $timesheet->getEnd();
|
||||
|
||||
if (null !== $timesheetStart) {
|
||||
if (null !== $projectBegin && $timesheetStart->getTimestamp() < $projectBegin->getTimestamp()) {
|
||||
$context->buildViolation('The project has not started at that time.')
|
||||
->atPath('begin')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetConstraint::PROJECT_NOT_STARTED)
|
||||
->addViolation();
|
||||
} elseif (null !== $projectEnd && $timesheetStart->getTimestamp() > $projectEnd->getTimestamp()) {
|
||||
$context->buildViolation('The project is finished at that time.')
|
||||
->atPath('begin')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetConstraint::PROJECT_ALREADY_ENDED)
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $timesheetEnd) {
|
||||
if (null !== $projectEnd && $timesheetEnd->getTimestamp() > $projectEnd->getTimestamp()) {
|
||||
$context->buildViolation('The project is finished at that time.')
|
||||
->atPath('end')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetConstraint::PROJECT_ALREADY_ENDED)
|
||||
->addViolation();
|
||||
} elseif (null !== $projectBegin && $timesheetEnd->getTimestamp() < $projectBegin->getTimestamp()) {
|
||||
$context->buildViolation('The project has not started at that time.')
|
||||
->atPath('end')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetConstraint::PROJECT_NOT_STARTED)
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user