diff --git a/src/Entity/Timesheet.php b/src/Entity/Timesheet.php
index 5d5e29a3..f13304e4 100644
--- a/src/Entity/Timesheet.php
+++ b/src/Entity/Timesheet.php
@@ -12,6 +12,7 @@ namespace App\Entity;
use App\Entity\User;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
/**
* Timesheet entity.
@@ -57,6 +58,7 @@ class Timesheet
* @var integer
*
* @ORM\Column(name="duration", type="integer", nullable=true)
+ * @Assert\GreaterThanOrEqual(0)
*/
private $duration = 0;
@@ -252,4 +254,20 @@ class Timesheet
{
return $this->rate;
}
+
+ /**
+ * @param ExecutionContextInterface $context
+ * @param mixed $payload
+ *
+ * @Assert\Callback
+ */
+ public function validate(ExecutionContextInterface $context, $payload)
+ {
+ if ($this->getEnd() !== null && $this->getEnd()->getTimestamp() < $this->getBegin()->getTimestamp()) {
+ $context->buildViolation('End date must not be earlier then start date.')
+ ->atPath('end')
+ ->setTranslationDomain('validators')
+ ->addViolation();
+ }
+ }
}
diff --git a/tests/Entity/TimesheetTest.php b/tests/Entity/TimesheetTest.php
new file mode 100644
index 00000000..1ca30409
--- /dev/null
+++ b/tests/Entity/TimesheetTest.php
@@ -0,0 +1,128 @@
+setName('Test Customer');
+
+ $project = new Project();
+ $project->setName('Test Project');
+ $project->setCustomer($customer);
+
+ $activity = new Activity();
+ $activity->setName('Test');
+ $activity->setProject($project);
+
+ $entity = new Timesheet();
+ $entity->setUser(new User());
+ $entity->setActivity($activity);
+
+ return $entity;
+ }
+
+ public function testValidationEndNotEarlierThanBegin()
+ {
+ $entity = $this->getEntity();
+ $begin = new \DateTime();
+ $end = clone $begin;
+ $end = $end->modify('-1 second');
+ $entity->setBegin($begin);
+ $entity->setEnd($end);
+
+ $this->assertHasViolationForField($entity, 'end');
+
+ // allow same begin and end
+ $entity = $this->getEntity();
+ $begin = new \DateTime();
+ $end = clone $begin;
+ $entity->setBegin($begin);
+ $entity->setEnd($end);
+
+ $this->assertHasViolationForField($entity, []);
+ }
+
+ public function testDurationMustBeGreatorOrEqualThanZero()
+ {
+ $entity = $this->getEntity();
+ $begin = new \DateTime();
+ $end = clone $begin;
+ $entity->setBegin($begin);
+ $entity->setEnd($end);
+ $entity->setDuration(-1);
+
+ $this->assertHasViolationForField($entity, 'duration');
+
+ // allow zero duration
+ $entity = $this->getEntity();
+ $begin = new \DateTime();
+ $end = clone $begin;
+ $entity->setBegin($begin);
+ $entity->setEnd($end);
+ $entity->setDuration(0);
+
+ $this->assertHasViolationForField($entity, []);
+ }
+
+ /**
+ * @param $value
+ * @param array|string $fieldNames
+ */
+ protected function assertHasViolationForField($value, $fieldNames)
+ {
+ $validator = Validation::createValidatorBuilder()->enableAnnotationMapping()->getValidator();
+ $validations = $validator->validate($value);
+
+ if (!is_array($fieldNames)) {
+ $fieldNames = [$fieldNames];
+ }
+
+ $violatedFields = [];
+ /** @var ConstraintViolationInterface $validation */
+ foreach($validations as $validation) {
+ $violatedFields[] = $validation->getPropertyPath();
+ }
+
+
+ foreach($fieldNames as $id => $propertyPath) {
+ $foundField = false;
+ if (in_array($propertyPath, $violatedFields)) {
+ $foundField = true;
+ unset($violatedFields[$id]);
+ }
+
+ $this->assertTrue($foundField, 'Failed finding violation for field: ' . $propertyPath);
+ }
+
+ $this->assertEmpty($violatedFields, sprintf('Unexpected violations found: %s', implode(', ', $violatedFields)));
+
+ $expected = count($fieldNames);
+ $actual = $validations->count();
+
+ $this->assertEquals($expected, $actual, sprintf('Expected %s violations, found %s.', $expected, $actual));
+ }
+}
diff --git a/translations/validators.de.xliff b/translations/validators.de.xliff
index 34d566cf..e742ccee 100644
--- a/translations/validators.de.xliff
+++ b/translations/validators.de.xliff
@@ -6,6 +6,10 @@
This value is not a valid role.
Dieser Wert ist keine gültige Rolle.
+
+ End date must not be earlier then start date.
+ Das Enddatum darf nicht vor dem Startdatum liegen.
+