respect project start and end dates in quick-entry form (#3642)

This commit is contained in:
Kevin Papst
2022-11-21 19:07:33 +01:00
committed by GitHub
parent 4f698f73dc
commit d6c15bb1cb
7 changed files with 110 additions and 79 deletions

View File

@@ -12,7 +12,6 @@ namespace App\Controller;
use App\Configuration\SystemConfiguration;
use App\Entity\Timesheet;
use App\Form\QuickEntryForm;
use App\Model\QuickEntryModel;
use App\Model\QuickEntryWeek;
use App\Repository\Query\TimesheetQuery;
use App\Repository\TimesheetRepository;
@@ -111,6 +110,11 @@ class QuickEntryController extends AbstractController
if (\array_key_exists($id, $rows)) {
continue;
}
// there is an edge case possible with a project that starts and ends between the start and end date
// user could still select it from the dropdown, but it is better to hide a row than displaying already ended projects
if (!$timesheet->getProject()->isVisibleAtDate($startWeek) && !$timesheet->getProject()->isVisibleAtDate($endWeek)) {
continue;
}
$rows[$id] = [
'days' => $week,
'project' => $timesheet->getProject(),
@@ -123,13 +127,13 @@ class QuickEntryController extends AbstractController
$defaultMinute = (int) $defaultBegin->format('i');
$defaultBegin->setTime($defaultHour, $defaultMinute, 0, 0);
// fill all rows and columns to make sure we do not have missing records
/** @var QuickEntryModel[] $models */
$models = [];
$formModel = new QuickEntryWeek($startWeek);
foreach ($rows as $id => $row) {
$model = new QuickEntryModel($user, $row['project'], $row['activity']);
$model = $formModel->addRow($user, $row['project'], $row['activity']);
foreach ($row['days'] as $dayId => $day) {
if (!\array_key_exists('entry', $day)) {
// fill all rows and columns to make sure we do not have missing records
$tmp = new Timesheet();
$tmp->setUser($user);
$tmp->setProject($row['project']);
@@ -141,11 +145,11 @@ class QuickEntryController extends AbstractController
$model->addTimesheet($day['entry']);
}
}
$models[] = $model;
}
// create prototype model
$empty = new QuickEntryModel($user);
$empty = $formModel->createRow($user);
$empty->markAsPrototype();
foreach ($week as $dayId => $day) {
$tmp = new Timesheet();
$tmp->setUser($user);
@@ -156,10 +160,10 @@ class QuickEntryController extends AbstractController
// add empty rows for simpler starting
$minRows = \intval($this->configuration->find('quick_entry.minimum_rows'));
if (\count($models) < $minRows) {
$newRows = $minRows - \count($models);
if ($formModel->countRows() < $minRows) {
$newRows = $minRows - $formModel->countRows();
for ($a = 0; $a < $newRows; $a++) {
$model = new QuickEntryModel();
$model = $formModel->addRow($user);
foreach ($week as $dayId => $day) {
$tmp = new Timesheet();
$tmp->setUser($user);
@@ -167,19 +171,14 @@ class QuickEntryController extends AbstractController
$tmp->getBegin()->setTime($defaultHour, $defaultMinute, 0, 0);
$model->addTimesheet($tmp);
}
$models[] = $model;
}
}
// sort rows by projects - make it configurable in the future
uasort($models, [$this, 'sortByProjectName']);
$formModel = new QuickEntryWeek($startWeek, $models);
$form = $this->createForm(QuickEntryForm::class, $formModel, [
'timezone' => $this->getDateTimeFactory()->getTimezone()->getName(),
'prototype_data' => $empty,
'start_date' => $startWeek,
'end_date' => $endWeek,
]);
$form->handleRequest($request);
@@ -194,7 +193,8 @@ class QuickEntryController extends AbstractController
foreach ($data->getRows() as $tmpModel) {
foreach ($tmpModel->getTimesheets() as $timesheet) {
if ($timesheet->getId() !== null) {
if ($timesheet->getDuration(false) === null || $timesheet->getEnd() === null) {
$duration = $timesheet->getDuration(false);
if ($duration === null || $timesheet->getEnd() === null) {
$deleteTimesheets[] = $timesheet;
} else {
$saveTimesheets[] = $timesheet;
@@ -235,16 +235,4 @@ class QuickEntryController extends AbstractController
'form' => $form->createView(),
]);
}
private function sortByProjectName(QuickEntryModel $a, QuickEntryModel $b): int
{
$aName = $a->getProject() !== null ? $a->getProject()->getName() : null;
$bName = $b->getProject() !== null ? $b->getProject()->getName() : null;
if ($aName === null || $bName === null) {
return -1;
}
return strcmp($aName, $bName);
}
}

View File

@@ -63,13 +63,6 @@ class QuickEntryForm extends AbstractType
}
));
$startDate = new \DateTime();
if ($builder->getData() !== null) {
/** @var QuickEntryWeek $data */
$data = $builder->getData();
$startDate = $data->getDate();
}
$builder->add('date', WeekPickerType::class, [
'model_timezone' => $options['timezone'],
'view_timezone' => $options['timezone'],
@@ -83,7 +76,8 @@ class QuickEntryForm extends AbstractType
'entry_options' => [
'label' => false,
'duration_minutes' => $this->configuration->getTimesheetIncrementDuration(),
'start_date' => $startDate,
'start_date' => $options['start_date'],
'end_date' => $options['end_date'],
'empty_data' => function (FormInterface $form) use ($options) {
return clone $options['prototype_data'];
},
@@ -103,13 +97,21 @@ class QuickEntryForm extends AbstractType
*/
public function configureOptions(OptionsResolver $resolver)
{
$start = new \DateTime();
$start->setTime(0, 0, 0);
$end = clone $start;
$end->add(new \DateInterval('P1W'));
$end->setTime(23, 59, 59);
$resolver->setDefaults([
'csrf_protection' => true,
'csrf_field_name' => '_token',
'csrf_token_id' => 'timesheet_quick_edit',
'data_class' => QuickEntryWeek::class,
'timezone' => date_default_timezone_get(),
'start_date' => new \DateTime(),
'start_date' => $start,
'end_date' => $end,
'prototype_data' => null,
]);
}

View File

@@ -24,10 +24,7 @@ use Symfony\Component\Validator\Constraints\Valid;
class QuickEntryWeekType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$projectOptions = [
'label' => false,
@@ -35,7 +32,9 @@ class QuickEntryWeekType extends AbstractType
'join_customer' => true,
'query_builder_for_user' => true,
'placeholder' => '',
'activity_enabled' => true
'activity_enabled' => true,
'project_date_start' => $options['start_date'],
'project_date_end' => $options['end_date'],
];
$builder->add('project', ProjectType::class, $projectOptions);
@@ -47,13 +46,6 @@ class QuickEntryWeekType extends AbstractType
return;
}
$begin = clone $data->getFirstEntry()->getBegin();
$begin->setTime(0, 0, 0);
$projectOptions['project_date_start'] = $begin;
$end = clone $data->getLatestEntry()->getBegin();
$end->setTime(23, 59, 59);
$projectOptions['project_date_end'] = $begin;
$projectOptions['projects'] = [$data->getProject()];
$event->getForm()->add('project', ProjectType::class, $projectOptions);
@@ -177,10 +169,7 @@ class QuickEntryWeekType extends AbstractType
);
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => QuickEntryModel::class,
@@ -188,6 +177,7 @@ class QuickEntryWeekType extends AbstractType
'duration_minutes' => null,
'duration_hours' => 10,
'start_date' => new DateTime(),
'end_date' => new DateTime(),
'prototype_data' => null,
]);
}

View File

@@ -22,6 +22,7 @@ class QuickEntryModel
private $user;
private $project;
private $activity;
private $prototype = false;
/**
* @var Timesheet[]
*/
@@ -34,17 +35,14 @@ class QuickEntryModel
$this->activity = $activity;
}
public function markAsPrototype(): void
{
$this->prototype = true;
}
public function isPrototype(): bool
{
if ($this->hasExistingTimesheet()) {
return false;
}
if ($this->hasNewTimesheet()) {
return false;
}
return $this->getUser() === null && $this->getProject() === null && $this->getActivity() === null;
return $this->prototype;
}
public function getUser(): ?User

View File

@@ -9,27 +9,45 @@
namespace App\Model;
use App\Entity\Activity;
use App\Entity\Project;
use App\Entity\User;
/**
* @internal
*/
class QuickEntryWeek
{
private $date;
private $rows;
private $startDate;
private $rows = [];
/**
* @param \DateTime $startDate
* @param QuickEntryModel[] $rows
*/
public function __construct(\DateTime $startDate, array $rows)
public function __construct(\DateTime $startDate)
{
$this->date = $startDate;
$this->rows = $rows;
$this->startDate = $startDate;
}
public function addRow(?User $user = null, ?Project $project = null, ?Activity $activity = null): QuickEntryModel
{
$model = $this->createRow($user, $project, $activity);
$this->rows[] = $model;
return $model;
}
public function createRow(?User $user = null, ?Project $project = null, ?Activity $activity = null): QuickEntryModel
{
return new QuickEntryModel($user, $project, $activity);
}
public function getDate(): \DateTime
{
return $this->date;
return $this->startDate;
}
public function countRows(): int
{
return \count($this->rows);
}
/**
@@ -37,7 +55,30 @@ class QuickEntryWeek
*/
public function getRows(): array
{
return $this->rows;
$rows = $this->rows;
// sort rows by projects - make it configurable in the future
uasort($rows, [$this, 'sortByProjectName']);
return $rows;
}
private function sortByProjectName(QuickEntryModel $a, QuickEntryModel $b): int
{
$aName = $a->getProject() !== null ? $a->getProject()->getName() : null;
$bName = $b->getProject() !== null ? $b->getProject()->getName() : null;
if ($aName === null && $bName === null) {
$result = 0;
} elseif ($aName === null && $bName !== null) {
$result = 1;
} elseif ($aName !== null && $bName === null) {
$result = -1;
} else {
$result = strcmp($aName, $bName);
}
return $result < 0 ? -1 : 1;
}
/**

View File

@@ -24,7 +24,7 @@ class QuickEntryModelTest extends TestCase
public function testEmptyModel()
{
$sut = new QuickEntryModel();
self::assertTrue($sut->isPrototype());
self::assertFalse($sut->isPrototype());
self::assertNull($sut->getProject());
self::assertNull($sut->getActivity());
self::assertNull($sut->getUser());
@@ -105,7 +105,6 @@ class QuickEntryModelTest extends TestCase
{
$sut = new QuickEntryModel();
self::assertTrue($sut->isPrototype());
self::assertFalse($sut->hasExistingTimesheet());
$mock = $this->createMock(Timesheet::class);
$mock->method('getId')->willReturn(1);
@@ -126,4 +125,18 @@ class QuickEntryModelTest extends TestCase
self::assertSame($activity, $sut->getActivity());
self::assertSame($user, $sut->getUser());
}
public function testPrototype()
{
$sut = new QuickEntryModel();
$sut->markAsPrototype();
self::assertTrue($sut->isPrototype());
$sut = new QuickEntryModel();
$sut->markAsPrototype();
$mock = $this->createMock(Timesheet::class);
$mock->method('getId')->willReturn(1);
$sut->addTimesheet($mock);
self::assertTrue($sut->isPrototype());
}
}

View File

@@ -21,9 +21,8 @@ class QuickEntryWeekTest extends TestCase
public function testModel()
{
$date = new \DateTime();
$rows = [];
$sut = new QuickEntryWeek($date, $rows);
$sut = new QuickEntryWeek($date);
self::assertSame($date, $sut->getDate());
self::assertEquals([], $sut->getRows());