Skip to content

Commit

Permalink
set rates via batch update (kimai#1326)
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinpapst authored Dec 29, 2019
1 parent 8aa8f45 commit baac4eb
Show file tree
Hide file tree
Showing 12 changed files with 258 additions and 49 deletions.
9 changes: 9 additions & 0 deletions src/Controller/TimesheetAbstractController.php
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,15 @@ protected function multiUpdate(Request $request, string $renderTemplate)
$timesheet->setExported($dto->isExported());
$execute = true;
}
if (null !== $dto->getHourlyRate()) {
$timesheet->setFixedRate(null);
$timesheet->setHourlyRate($dto->getHourlyRate());
$execute = true;
} elseif (null !== $dto->getFixedRate()) {
$timesheet->setFixedRate($dto->getFixedRate());
$timesheet->setHourlyRate(null);
$execute = true;
}
}

if ($execute) {
Expand Down
15 changes: 15 additions & 0 deletions src/Form/MultiUpdate/TimesheetMultiUpdate.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

use App\Form\Type\ActivityType;
use App\Form\Type\CustomerType;
use App\Form\Type\FixedRateType;
use App\Form\Type\HourlyRateType;
use App\Form\Type\ProjectType;
use App\Form\Type\TagsInputType;
use App\Form\Type\UserType;
Expand Down Expand Up @@ -57,6 +59,7 @@ public function buildForm(FormBuilderInterface $builder, array $options)
$activity = null;
$project = null;
$customer = null;
$currency = null;
$customerCount = $this->customers->countCustomer(true);

if (isset($options['data'])) {
Expand All @@ -70,6 +73,10 @@ public function buildForm(FormBuilderInterface $builder, array $options)
if (null === $project && null !== $activity) {
$project = $activity->getProject();
}

if (null !== $customer) {
$currency = $customer->getCurrency();
}
}

$builder
Expand Down Expand Up @@ -189,6 +196,14 @@ function (FormEvent $event) use ($activity) {
]);
}

$builder
->add('fixedRate', FixedRateType::class, [
'currency' => $currency,
])
->add('hourlyRate', HourlyRateType::class, [
'currency' => $currency,
]);

$builder->add('entities', HiddenType::class, [
'required' => false,
]);
Expand Down
32 changes: 32 additions & 0 deletions src/Form/MultiUpdate/TimesheetMultiUpdateDTO.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ class TimesheetMultiUpdateDTO extends MultiUpdateTableDTO
* @var bool|null
*/
private $exported = null;
/**
* @var float
*/
private $fixedRate;
/**
* @var float
*/
private $hourlyRate;

public function getCustomer(): ?Customer
{
Expand Down Expand Up @@ -136,4 +144,28 @@ public function setReplaceTags(bool $replaceTags): TimesheetMultiUpdateDTO

return $this;
}

public function getFixedRate(): ?float
{
return $this->fixedRate;
}

public function setFixedRate(?float $fixedRate): TimesheetMultiUpdateDTO
{
$this->fixedRate = $fixedRate;

return $this;
}

public function getHourlyRate(): ?float
{
return $this->hourlyRate;
}

public function setHourlyRate(?float $hourlyRate): TimesheetMultiUpdateDTO
{
$this->hourlyRate = $hourlyRate;

return $this;
}
}
16 changes: 9 additions & 7 deletions src/Validator/Constraints/TimesheetMultiUpdate.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,22 @@
*/
class TimesheetMultiUpdate extends Constraint
{
public const MISSING_ACTIVITY_ERROR = 'yd5hffg-dsfef3-426a-83d7-1f2d33hs5d84';
public const MISSING_PROJECT_ERROR = 'yd5hffg-dsfef3-426a-83d7-1f2d33hs5d85';
public const ACTIVITY_PROJECT_MISMATCH_ERROR = 'xy5hffg-dsfef3-426a-83d7-1f2d33hs5d86';
public const DISABLED_ACTIVITY_ERROR = 'yd5hffg-dsfef3-426a-83d7-1f2d33hs5d87';
public const DISABLED_PROJECT_ERROR = 'yd5hffg-dsfef3-426a-83d7-1f2d33hs5d88';
public const DISABLED_CUSTOMER_ERROR = 'yd5hffg-dsfef3-426a-83d7-1f2d33hs5d89';
public const MISSING_ACTIVITY_ERROR = 'ts-multi-update-84';
public const MISSING_PROJECT_ERROR = 'ts-multi-update-85';
public const ACTIVITY_PROJECT_MISMATCH_ERROR = 'ts-multi-update-86';
public const DISABLED_ACTIVITY_ERROR = 'ts-multi-update-87';
public const DISABLED_PROJECT_ERROR = 'ts-multi-update-88';
public const DISABLED_CUSTOMER_ERROR = 'ts-multi-update-89';
public const HOURLY_RATE_FIXED_RATE = 'ts-multi-update-90';

protected static $errorNames = [
self::MISSING_ACTIVITY_ERROR => 'A timesheet must have an activity.',
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::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.',
self::DISABLED_CUSTOMER_ERROR => 'Cannot start a disabled customer.',
self::HOURLY_RATE_FIXED_RATE => 'Cannot set hourly rate and fixed rate at the same time.',
];

public $message = 'This form has invalid settings.';
Expand Down
65 changes: 32 additions & 33 deletions src/Validator/Constraints/TimesheetMultiUpdateValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,43 +9,22 @@

namespace App\Validator\Constraints;

use App\Configuration\TimesheetConfiguration;
use App\Form\MultiUpdate\TimesheetMultiUpdateDTO;
use App\Validator\Constraints\TimesheetMultiUpdate as TimesheetConstraint;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use App\Validator\Constraints\TimesheetMultiUpdate as TimesheetMultiUpdateConstraint;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;

class TimesheetMultiUpdateValidator extends ConstraintValidator
final class TimesheetMultiUpdateValidator extends ConstraintValidator
{
/**
* @var AuthorizationCheckerInterface
*/
protected $auth;
/**
* @var TimesheetConfiguration
*/
protected $configuration;

/**
* @param AuthorizationCheckerInterface $auth
* @param TimesheetConfiguration $configuration
*/
public function __construct(AuthorizationCheckerInterface $auth, TimesheetConfiguration $configuration)
{
$this->auth = $auth;
$this->configuration = $configuration;
}

/**
* @param TimesheetMultiUpdateDTO|mixed $value
* @param Constraint $constraint
*/
public function validate($value, Constraint $constraint)
{
if (!($constraint instanceof TimesheetConstraint)) {
if (!($constraint instanceof TimesheetMultiUpdateConstraint)) {
throw new UnexpectedTypeException($constraint, __NAMESPACE__ . '\TimesheetMultiUpdate');
}

Expand All @@ -54,6 +33,20 @@ public function validate($value, Constraint $constraint)
}

$this->validateActivityAndProject($value, $this->context);

if (null !== $value->getFixedRate() && null !== $value->getHourlyRate()) {
$this->context->buildViolation('Cannot set hourly rate and fixed rate at the same time.')
->atPath('fixedRate')
->setTranslationDomain('validators')
->setCode(TimesheetMultiUpdateConstraint::HOURLY_RATE_FIXED_RATE)
->addViolation();

$this->context->buildViolation('Cannot set hourly rate and fixed rate at the same time.')
->atPath('hourlyRate')
->setTranslationDomain('validators')
->setCode(TimesheetMultiUpdateConstraint::HOURLY_RATE_FIXED_RATE)
->addViolation();
}
}

/**
Expand All @@ -67,36 +60,42 @@ protected function validateActivityAndProject(TimesheetMultiUpdateDTO $dto, Exec

// non global activity without project
if (null !== $activity && null !== $activity->getProject() && null === $project) {
$context->buildViolation('Missing project')
$context->buildViolation('Missing project.')
->atPath('project')
->setTranslationDomain('validators')
->setCode(TimesheetConstraint::MISSING_PROJECT_ERROR)
->setCode(TimesheetMultiUpdateConstraint::MISSING_PROJECT_ERROR)
->addViolation();

return;
}

// only project was chosen
if (null === $activity && null !== $project) {
$context->buildViolation('Missing activity')
->atPath('project')
$context->buildViolation('You need to choose an activity, if the project should be changed.')
->atPath('activity')
->setTranslationDomain('validators')
->setCode(TimesheetConstraint::MISSING_ACTIVITY_ERROR)
->setCode(TimesheetMultiUpdateConstraint::MISSING_ACTIVITY_ERROR)
->addViolation();

return;
}

if (null !== $activity) {
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)
->setCode(TimesheetMultiUpdateConstraint::ACTIVITY_PROJECT_MISMATCH_ERROR)
->addViolation();

return;
}

if (!$activity->isVisible()) {
$context->buildViolation('Cannot assign a disabled activity.')
->atPath('activity')
->setTranslationDomain('validators')
->setCode(TimesheetConstraint::DISABLED_ACTIVITY_ERROR)
->setCode(TimesheetMultiUpdateConstraint::DISABLED_ACTIVITY_ERROR)
->addViolation();
}
}
Expand All @@ -106,15 +105,15 @@ protected function validateActivityAndProject(TimesheetMultiUpdateDTO $dto, Exec
$context->buildViolation('Cannot assign a disabled project.')
->atPath('project')
->setTranslationDomain('validators')
->setCode(TimesheetConstraint::DISABLED_PROJECT_ERROR)
->setCode(TimesheetMultiUpdateConstraint::DISABLED_PROJECT_ERROR)
->addViolation();
}

if (!$project->getCustomer()->isVisible()) {
$context->buildViolation('Cannot assign a disabled customer.')
->atPath('customer')
->setTranslationDomain('validators')
->setCode(TimesheetConstraint::DISABLED_CUSTOMER_ERROR)
->setCode(TimesheetMultiUpdateConstraint::DISABLED_CUSTOMER_ERROR)
->addViolation();
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/Controller/PermissionControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ public function testSavePermission()
self::assertEquals(1, $permission->getRole()->getId());

// flush the cache to prevent wrong results
$em->clear(RolePermission::class);
$em->clear();

// update the permission
$this->request($client, '/admin/permissions/roles/1/view_user/0');
Expand Down
2 changes: 1 addition & 1 deletion tests/Controller/TagControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public function testMultiDeleteAction()
$this->assertIsRedirect($client, $this->createUrl('/admin/tags/'));
$client->followRedirect();

$em->clear(Tag::class);
$em->clear();
self::assertEquals(0, $em->getRepository(Tag::class)->count([]));
}
}
8 changes: 5 additions & 3 deletions tests/Controller/TimesheetControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ public function testMultiDeleteAction()
$this->assertIsRedirect($client, $this->createUrl('/timesheet/'));
$client->followRedirect();

$em->clear(Timesheet::class);
$em->clear();
self::assertEquals(0, $em->getRepository(Timesheet::class)->count([]));
}

Expand Down Expand Up @@ -404,18 +404,20 @@ public function testMultiUpdate()
$client->submit($form, [
'timesheet_multi_update' => [
'exported' => true,
'tags' => 'test, foo-bar'
'tags' => 'test, foo-bar',
'fixedRate' => 13,
]
]);

$em->clear(Timesheet::class);
$em->clear();

/** @var Timesheet[] $timesheets */
$timesheets = $em->getRepository(Timesheet::class)->findAll();
self::assertCount(10, $timesheets);
foreach ($timesheets as $timesheet) {
self::assertCount(2, $timesheet->getTags());
self::assertTrue($timesheet->isExported());
self::assertEquals(13, $timesheet->getFixedRate());
}
}
}
9 changes: 6 additions & 3 deletions tests/Controller/TimesheetTeamControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use App\Entity\User;
use App\Form\Type\DateRangeType;
use App\Tests\DataFixtures\TimesheetFixtures;
use App\Timesheet\Util;

/**
* @group integration
Expand Down Expand Up @@ -276,7 +277,7 @@ public function testMultiDeleteAction()
$this->assertIsRedirect($client, $this->createUrl('/team/timesheet/'));
$client->followRedirect();

$em->clear(Timesheet::class);
$em->clear();
self::assertEquals(0, $em->getRepository(Timesheet::class)->count([]));
}

Expand Down Expand Up @@ -323,11 +324,12 @@ public function testMultiUpdate()
'user' => $newUser->getId(),
'exported' => true,
'replaceTags' => true,
'tags' => 'test, foo-bar, tralalala'
'tags' => 'test, foo-bar, tralalala',
'hourlyRate' => 13.78,
]
]);

$em->clear(Timesheet::class);
$em->clear();

/** @var Timesheet[] $timesheets */
$timesheets = $em->getRepository(Timesheet::class)->findAll();
Expand All @@ -336,6 +338,7 @@ public function testMultiUpdate()
self::assertCount(3, $timesheet->getTags());
self::assertEquals($newUser->getId(), $timesheet->getUser()->getId());
self::assertTrue($timesheet->isExported());
self::assertEquals(Util::calculateRate(13.78, $timesheet->getDuration()), $timesheet->getRate());
}
}
}
2 changes: 1 addition & 1 deletion tests/Controller/UserControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ public function testDeleteActionWithTimesheetEntries()
$this->assertHasFlashDeleteSuccess($client);

// SQLIte does not necessarly support onCascade delete, so these timesheet will stay after deletion
// $em->clear(Timesheet::class);
// $em->clear();
// $timesheets = $em->getRepository(Timesheet::class)->count([]);
// $this->assertEquals(0, $timesheets);

Expand Down
8 changes: 8 additions & 0 deletions tests/Form/MultiUpdate/TimesheetMultiUpdateDTOTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public function testDefaultValues()
self::assertEquals([], $sut->getTags());
self::assertNull($sut->getUser());
self::assertFalse($sut->isReplaceTags());
self::assertNull($sut->getFixedRate());
self::assertNull($sut->getHourlyRate());
}

public function testSetterAndGetter()
Expand Down Expand Up @@ -86,5 +88,11 @@ public function testSetterAndGetter()
$customer = (new Customer())->setName('sdfsdfsd');
self::assertInstanceOf(TimesheetMultiUpdateDTO::class, $sut->setCustomer($customer));
self::assertSame($customer, $sut->getCustomer());

self::assertInstanceOf(TimesheetMultiUpdateDTO::class, $sut->setFixedRate(12.78));
self::assertEquals(12.78, $sut->getFixedRate());

self::assertInstanceOf(TimesheetMultiUpdateDTO::class, $sut->setHourlyRate(123.45));
self::assertEquals(123.45, $sut->getHourlyRate());
}
}
Loading

0 comments on commit baac4eb

Please sign in to comment.