Configurable activity and project number (#4729)

* added configurable activity number
* added configurable project number
* fix deprecations
* added some tests for entity exporter
* better configuration of dropdown pattern for customer, project and activity
This commit is contained in:
Kevin Papst
2024-04-04 17:43:22 +02:00
committed by GitHub
parent c1f5d3def4
commit a636683dee
55 changed files with 920 additions and 136 deletions

View File

@@ -0,0 +1,29 @@
<?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;
#[\Attribute(\Attribute::TARGET_CLASS)]
final class Activity extends Constraint
{
public const ACTIVITY_NUMBER_EXISTING = 'kimai-activity-00';
protected const ERROR_NAMES = [
self::ACTIVITY_NUMBER_EXISTING => 'The number %number% is already used.',
];
public string $message = 'This activity has invalid settings.';
public function getTargets(): string
{
return self::CLASS_CONSTRAINT;
}
}

View 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\Configuration\SystemConfiguration;
use App\Entity\Activity as ActivityEntity;
use App\Repository\ActivityRepository;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
final class ActivityValidator extends ConstraintValidator
{
public function __construct(
private readonly SystemConfiguration $systemConfiguration,
private readonly ActivityRepository $activityRepository
)
{
}
/**
* @param ActivityEntity|mixed $value
*/
public function validate(mixed $value, Constraint $constraint): void
{
if (!($constraint instanceof Activity)) {
throw new UnexpectedTypeException($constraint, Activity::class);
}
if (!($value instanceof ActivityEntity)) {
throw new UnexpectedTypeException($value, ActivityEntity::class);
}
if ((bool) $this->systemConfiguration->find('activity.allow_duplicate_number') === false && (($number = $value->getNumber()) !== null)) {
foreach ($this->activityRepository->findBy(['number' => $number]) as $tmp) {
if ($tmp->getId() !== $value->getId()) {
$this->context->buildViolation(Activity::getErrorName(Activity::ACTIVITY_NUMBER_EXISTING))
->setParameter('%number%', $number)
->atPath('number')
->setTranslationDomain('validators')
->setCode(Activity::ACTIVITY_NUMBER_EXISTING)
->addViolation();
break;
}
}
}
}
}

View File

@@ -17,7 +17,7 @@ final class Customer extends Constraint
public const CUSTOMER_NUMBER_EXISTING = 'kimai-customer-00';
protected const ERROR_NAMES = [
self::CUSTOMER_NUMBER_EXISTING => 'The account number %number% is already used.',
self::CUSTOMER_NUMBER_EXISTING => 'The number %number% is already used.',
];
public string $message = 'This customer has invalid settings.';

View File

@@ -18,13 +18,15 @@ use Symfony\Component\Validator\Exception\UnexpectedTypeException;
final class CustomerValidator extends ConstraintValidator
{
public function __construct(private SystemConfiguration $systemConfiguration, private CustomerRepository $customerRepository)
public function __construct(
private readonly SystemConfiguration $systemConfiguration,
private readonly CustomerRepository $customerRepository
)
{
}
/**
* @param CustomerEntity|mixed $value
* @param Constraint $constraint
*/
public function validate(mixed $value, Constraint $constraint): void
{
@@ -37,14 +39,16 @@ final class CustomerValidator extends ConstraintValidator
}
if ((bool) $this->systemConfiguration->find('customer.rules.allow_duplicate_number') === false && (($number = $value->getNumber()) !== null)) {
$tmp = $this->customerRepository->findOneBy(['number' => $number]);
if ($tmp !== null && $tmp->getId() !== $value->getId()) {
$this->context->buildViolation(Customer::getErrorName(Customer::CUSTOMER_NUMBER_EXISTING))
->setParameter('%number%', $number)
->atPath('number')
->setTranslationDomain('validators')
->setCode(Customer::CUSTOMER_NUMBER_EXISTING)
->addViolation();
foreach ($this->customerRepository->findBy(['number' => $number]) as $tmp) {
if ($tmp->getId() !== $value->getId()) {
$this->context->buildViolation(Customer::getErrorName(Customer::CUSTOMER_NUMBER_EXISTING))
->setParameter('%number%', $number)
->atPath('number')
->setTranslationDomain('validators')
->setCode(Customer::CUSTOMER_NUMBER_EXISTING)
->addViolation();
break;
}
}
}
}

View File

@@ -15,9 +15,11 @@ use Symfony\Component\Validator\Constraint;
final class Project extends Constraint
{
public const END_BEFORE_BEGIN_ERROR = 'kimai-project-00';
public const PROJECT_NUMBER_EXISTING = 'kimai-project-01';
protected const ERROR_NAMES = [
self::END_BEFORE_BEGIN_ERROR => 'End date must not be earlier then start date.',
self::PROJECT_NUMBER_EXISTING => 'The number %number% is already used.',
];
public string $message = 'This project has invalid settings.';

View File

@@ -9,12 +9,13 @@
namespace App\Validator\Constraints;
use App\Entity\Project;
use App\Configuration\SystemConfiguration;
use App\Entity\Project as ProjectEntity;
use App\Repository\ProjectRepository;
use App\Validator\Constraints\Project as ProjectEntityConstraint;
use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
final class ProjectValidator extends ConstraintValidator
@@ -23,6 +24,8 @@ final class ProjectValidator extends ConstraintValidator
* @param ProjectConstraint[] $constraints
*/
public function __construct(
private readonly SystemConfiguration $systemConfiguration,
private readonly ProjectRepository $projectRepository,
#[TaggedIterator(ProjectConstraint::class)]
private iterable $constraints = []
)
@@ -31,7 +34,6 @@ final class ProjectValidator extends ConstraintValidator
/**
* @param Project|mixed $value
* @param Constraint $constraint
*/
public function validate(mixed $value, Constraint $constraint): void
{
@@ -39,11 +41,31 @@ final class ProjectValidator extends ConstraintValidator
throw new UnexpectedTypeException($constraint, ProjectEntityConstraint::class);
}
if (!\is_object($value) || !($value instanceof Project)) {
if (!\is_object($value) || !($value instanceof ProjectEntity)) {
return;
}
$this->validateProject($value, $this->context);
if (null !== $value->getStart() && null !== $value->getEnd() && $value->getStart()->getTimestamp() > $value->getEnd()->getTimestamp()) {
$this->context->buildViolation(ProjectEntityConstraint::getErrorName(ProjectEntityConstraint::END_BEFORE_BEGIN_ERROR))
->atPath('end')
->setTranslationDomain('validators')
->setCode(ProjectEntityConstraint::END_BEFORE_BEGIN_ERROR)
->addViolation();
}
if ((bool) $this->systemConfiguration->find('project.allow_duplicate_number') === false && (($number = $value->getNumber()) !== null)) {
foreach ($this->projectRepository->findBy(['number' => $number]) as $tmp) {
if ($tmp->getId() !== $value->getId()) {
$this->context->buildViolation(Project::getErrorName(Project::PROJECT_NUMBER_EXISTING))
->setParameter('%number%', $number)
->atPath('number')
->setTranslationDomain('validators')
->setCode(Project::PROJECT_NUMBER_EXISTING)
->addViolation();
break;
}
}
}
foreach ($this->constraints as $innerConstraint) {
$this->context
@@ -52,15 +74,4 @@ final class ProjectValidator extends ConstraintValidator
->validate($value, $innerConstraint, [Constraint::DEFAULT_GROUP]);
}
}
protected function validateProject(Project $project, ExecutionContextInterface $context): void
{
if (null !== $project->getStart() && null !== $project->getEnd() && $project->getStart()->getTimestamp() > $project->getEnd()->getTimestamp()) {
$context->buildViolation(ProjectEntityConstraint::getErrorName(ProjectEntityConstraint::END_BEFORE_BEGIN_ERROR))
->atPath('end')
->setTranslationDomain('validators')
->setCode(ProjectEntityConstraint::END_BEFORE_BEGIN_ERROR)
->addViolation();
}
}
}