improved calendar (#784)

This commit is contained in:
Kevin Papst
2019-05-19 16:16:20 +02:00
committed by GitHub
parent 1903d6fb77
commit d2ad87d09c
66 changed files with 1030 additions and 1190 deletions

View File

@@ -96,14 +96,15 @@ class TimesheetController extends BaseApiController
* @Rest\QueryParam(name="project", requirements="\d+", strict=true, nullable=true, description="Project ID to filter timesheets")
* @Rest\QueryParam(name="activity", requirements="\d+", strict=true, nullable=true, description="Activity ID to filter timesheets")
* @Rest\QueryParam(name="page", requirements="\d+", strict=true, nullable=true, description="The page to display, renders a 404 if not found (default: 1)")
* @Rest\QueryParam(name="size", requirements="\d+", strict=true, nullable=true, description="The amount of entries for each page (default: 25)")
* @Rest\QueryParam(name="size", requirements="\d+", strict=true, nullable=true, description="The amount of entries for each page (default: 50)")
* @Rest\QueryParam(name="tags", requirements="[a-zA-Z0-9 -,]+", strict=true, nullable=true, description="The name of tags which are in the datasets")
* @Rest\QueryParam(name="orderBy", requirements="id|begin|end|rate", strict=true, nullable=true, description="The field by which results will be ordered. Allowed values: id, begin, end, rate (default: begin)")
* @Rest\QueryParam(name="order", requirements="ASC|DESC", strict=true, nullable=true, description="The result order. Allowed values: ASC, DESC (default: DESC)")
* @Rest\QueryParam(name="begin", requirements=@Constraints\DateTime, strict=true, nullable=true, description="Only records after this date will be included (format: ISO 8601)")
* @Rest\QueryParam(name="end", requirements=@Constraints\DateTime, strict=true, nullable=true, description="Only records before this date will be included (format: ISO 8601)")
* @Rest\QueryParam(name="begin", requirements=@Constraints\DateTime(format="Y-m-d\TH:i:s"), strict=true, nullable=true, description="Only records after this date will be included (format: HTML5)")
* @Rest\QueryParam(name="end", requirements=@Constraints\DateTime(format="Y-m-d\TH:i:s"), strict=true, nullable=true, description="Only records before this date will be included (format: HTML5)")
* @Rest\QueryParam(name="exported", requirements="0|1", strict=true, nullable=true, description="Use this flag if you want to filter for export state. Allowed values: 0=not exported, 1=exported (default: all)")
* @Rest\QueryParam(name="active", requirements="0|1", strict=true, nullable=true, description="Filter for running/active records. Allowed values: 0=stopped, 1=active. (default: all)")
* @Rest\QueryParam(name="active", requirements="0|1", strict=true, nullable=true, description="Filter for running/active records. Allowed values: 0=stopped, 1=active (default: all)")
* @Rest\QueryParam(name="full", requirements="true", strict=true, nullable=true, description="Allows to fetch fully serialized objects including subresources (TimesheetSubCollection). Allowed values: true (default: false)")
*
* @Security("is_granted('view_own_timesheet') or is_granted('view_other_timesheet')")
*
@@ -160,11 +161,11 @@ class TimesheetController extends BaseApiController
}
if (null !== ($begin = $paramFetcher->get('begin'))) {
$query->setBegin(new \DateTime($begin));
$query->setBegin($this->dateTime->createDateTime($begin));
}
if (null !== ($end = $paramFetcher->get('end'))) {
$query->setEnd(new \DateTime($end));
$query->setEnd($this->dateTime->createDateTime($end));
}
if (null !== ($active = $paramFetcher->get('active'))) {
@@ -190,7 +191,11 @@ class TimesheetController extends BaseApiController
$data = (array) $data->getCurrentPageResults();
$view = new View($data, 200);
$view->getContext()->setGroups(['Default', 'Collection', 'Timesheet']);
if ('true' === $paramFetcher->get('full')) {
$view->getContext()->setGroups(['Default', 'Subresource', 'Timesheet']);
} else {
$view->getContext()->setGroups(['Default', 'Collection', 'Timesheet']);
}
return $this->viewHandler->handle($view);
}
@@ -424,7 +429,7 @@ class TimesheetController extends BaseApiController
* )
*
* @Rest\QueryParam(name="user", requirements="\d+|all", strict=true, nullable=true, description="User ID to filter timesheets. Needs permission 'view_other_timesheet', pass 'all' to fetch data for all user (default: current user)")
* @Rest\QueryParam(name="begin", requirements=@Constraints\DateTime, strict=true, nullable=true, description="Only records after this date will be included. Default: today - 1 year (format: ISO 8601)")
* @Rest\QueryParam(name="begin", requirements=@Constraints\DateTime(format="Y-m-d\TH:i:s"), strict=true, nullable=true, description="Only records after this date will be included. Default: today - 1 year (format: HTML5)")
* @Rest\QueryParam(name="size", requirements="\d+", strict=true, nullable=true, description="The amount of entries (default: 10)")
*
* @Security("is_granted('view_own_timesheet') or is_granted('view_other_timesheet')")
@@ -449,7 +454,7 @@ class TimesheetController extends BaseApiController
}
if (null !== ($reqBegin = $paramFetcher->get('begin'))) {
$begin = new \DateTime($reqBegin);
$begin = $this->dateTime->createDateTime($reqBegin);
}
$data = $this->repository->getRecentActivities($user, $begin, $limit);
@@ -474,7 +479,6 @@ class TimesheetController extends BaseApiController
*
* @Security("is_granted('view_own_timesheet')")
* @return Response
* @throws \Doctrine\ORM\Query\QueryException
*/
public function activeAction()
{

View File

@@ -1,66 +0,0 @@
<?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\Calendar;
class Config
{
/**
* @var array
*/
protected $config = [];
/**
* @param array $config
*/
public function __construct(array $config)
{
$this->config = $config;
}
/**
* @return array
*/
public function getBusinessDays()
{
return $this->config['businessHours']['days'];
}
/**
* @return string
*/
public function getBusinessTimeBegin()
{
return $this->config['businessHours']['begin'];
}
/**
* @return string
*/
public function getBusinessTimeEnd()
{
return $this->config['businessHours']['end'];
}
/**
* @return int
*/
public function getDayLimit()
{
return $this->config['day_limit'];
}
/**
* @return bool
*/
public function isShowWeekNumbers()
{
return $this->config['week_numbers'];
}
}

View File

@@ -1,59 +0,0 @@
<?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\Calendar;
class Service
{
/**
* @var array
*/
protected $config;
/**
* Service constructor.
* @param array $config
*/
public function __construct(array $config)
{
$this->config = $config;
}
/**
* @return Config
*/
public function getConfig()
{
return new Config($this->config);
}
/**
* @return Google
*/
public function getGoogle()
{
$apiKey = $this->config['google']['api_key'] ?? null;
$sources = [];
if (isset($this->config['google']['sources'])) {
foreach ($this->config['google']['sources'] as $name => $config) {
$source = new Source();
$source
->setColor($config['color'])
->setUri($config['id'])
->setId($name)
;
$sources[] = $source;
}
}
return new Google($apiKey, $sources);
}
}

View File

@@ -1,307 +0,0 @@
<?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\Calendar;
use App\Entity\Tag;
use App\Entity\Timesheet;
class TimesheetEntity
{
/**
* @var int
*/
protected $id;
/**
* @var \DateTime
*/
protected $start;
/**
* @var \DateTime|null
*/
protected $end;
/**
* @var string
*/
protected $title;
/**
* @var string
*/
protected $description;
/**
* @var string
*/
protected $customer;
/**
* @var string
*/
protected $project;
/**
* @var string
*/
protected $activity;
/**
* @var string|null
*/
protected $tags;
/**
* @var string|null
*/
protected $borderColor;
/**
* @var string|null
*/
protected $backgroundColor;
/**
* @param Timesheet $entry
*/
public function __construct(Timesheet $entry)
{
$this->id = $entry->getId();
$this->start = $entry->getBegin();
$this->title = $entry->getActivity()->getName();
$this->description = $entry->getDescription();
$this->customer = $entry->getProject()->getCustomer()->getName();
$this->project = $entry->getProject()->getName();
$this->activity = $entry->getActivity()->getName();
if (sizeof($entry->getTags()) > 0) {
$arr = [];
/** @var Tag $tag */
foreach ($entry->getTags() as $tag) {
array_push($arr, $tag->getName());
}
$this->tags = implode(', ', $arr);
}
$color = $entry->getActivity()->getColor();
if (empty($color)) {
$color = $entry->getProject()->getColor();
if (empty($color)) {
$color = $entry->getProject()->getCustomer()->getColor();
}
}
$this->borderColor = $color;
$this->backgroundColor = $color;
if (null !== $entry->getEnd()) {
$this->end = $entry->getEnd();
}
}
/**
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* @param int $id
* @return TimesheetEntity
*/
public function setId(int $id)
{
$this->id = $id;
return $this;
}
/**
* @return \DateTime
*/
public function getStart(): \DateTime
{
return $this->start;
}
/**
* @param \DateTime $start
* @return TimesheetEntity
*/
public function setStart(\DateTime $start)
{
$this->start = $start;
return $this;
}
/**
* @return \DateTime|null
*/
public function getEnd(): ?\DateTime
{
return $this->end;
}
/**
* @param \DateTime|null $end
* @return TimesheetEntity
*/
public function setEnd(?\DateTime $end)
{
$this->end = $end;
return $this;
}
/**
* @return string
*/
public function getTitle(): string
{
return $this->title;
}
/**
* @param string $title
* @return TimesheetEntity
*/
public function setTitle(string $title)
{
$this->title = $title;
return $this;
}
/**
* @return string
*/
public function getDescription(): ?string
{
return $this->description;
}
/**
* @param string $description
* @return TimesheetEntity
*/
public function setDescription(string $description)
{
$this->description = $description;
return $this;
}
/**
* @return string
*/
public function getCustomer(): string
{
return $this->customer;
}
/**
* @param string $customer
* @return TimesheetEntity
*/
public function setCustomer(string $customer)
{
$this->customer = $customer;
return $this;
}
/**
* @return string
*/
public function getProject(): string
{
return $this->project;
}
/**
* @param string $project
* @return TimesheetEntity
*/
public function setProject(string $project)
{
$this->project = $project;
return $this;
}
/**
* @return string
*/
public function getActivity(): string
{
return $this->activity;
}
/**
* @param string $activity
* @return TimesheetEntity
*/
public function setActivity(string $activity)
{
$this->activity = $activity;
return $this;
}
/**
* @return string|null
*/
public function getTags(): ?string
{
return $this->tags;
}
/**
* @param string|null $tags
* @return TimesheetEntity
*/
public function setTags(?string $tags)
{
$this->tags = $tags;
return $this;
}
/**
* @return null|string
*/
public function getBorderColor(): ?string
{
return $this->borderColor;
}
/**
* @param null|string $borderColor
* @return TimesheetEntity
*/
public function setBorderColor(?string $borderColor)
{
$this->borderColor = $borderColor;
return $this;
}
/**
* @return null|string
*/
public function getBackgroundColor(): ?string
{
return $this->backgroundColor;
}
/**
* @param null|string $backgroundColor
* @return TimesheetEntity
*/
public function setBackgroundColor(?string $backgroundColor)
{
$this->backgroundColor = $backgroundColor;
return $this;
}
}

View File

@@ -34,10 +34,8 @@ class BashExecutor
$command = rtrim($this->rootDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . ltrim($command, DIRECTORY_SEPARATOR);
ob_start();
passthru($command, $exitCode);
$result = ob_get_clean();
return new BashResult($exitCode, $result);
return new BashResult($exitCode);
}
}

View File

@@ -15,19 +15,13 @@ class BashResult
* @var string
*/
protected $exitCode;
/**
* @var string
*/
protected $result;
/**
* @param string $exitCode
* @param string $result
*/
public function __construct($exitCode, $result)
public function __construct($exitCode)
{
$this->exitCode = $exitCode;
$this->result = $result;
}
/**
@@ -37,34 +31,4 @@ class BashResult
{
return $this->exitCode;
}
/**
* @param string $exitCode
* @return BashResult
*/
public function setExitCode(string $exitCode)
{
$this->exitCode = $exitCode;
return $this;
}
/**
* @return string
*/
public function getResult(): string
{
return $this->result;
}
/**
* @param string $result
* @return BashResult
*/
public function setResult(string $result)
{
$this->result = $result;
return $this;
}
}

View File

@@ -49,7 +49,6 @@ class RunCodestyleCommand extends Command
->setName('kimai:codestyle')
->setDescription('Check and fix the projects coding style')
->addOption('fix', null, InputOption::VALUE_NONE, 'Fix all found problems')
->addOption('checkstyle', null, InputOption::VALUE_OPTIONAL, '')
;
}
@@ -60,38 +59,17 @@ class RunCodestyleCommand extends Command
{
$io = new SymfonyStyle($input, $output);
$filename = null;
$args = [];
if (!$input->getOption('fix')) {
$filename = $input->getOption('checkstyle');
$args[] = '--dry-run';
$args[] = '--verbose';
$args[] = '--show-progress=none';
if (!empty($filename) && (file_exists($filename) && !is_writeable($filename))) {
$io->error('Target file is not writeable: ' . $filename);
return;
}
if (!empty($filename)) {
$filename = $this->rootDir . '/' . $filename;
$args[] = '> ' . $filename;
} else {
$args[] = '--format=txt';
}
}
$result = $this->executor->execute('/vendor/bin/php-cs-fixer fix ' . implode(' ', $args));
$io->write($result->getResult());
if ($result->getExitCode() > 0) {
$io->error(
'Found problems while checking your code styles' .
(!empty($filename) ? '. Saved checkstyle data to: ' . $filename : '')
);
$io->error('Found violations while checking code styles');
return;
}

View File

@@ -31,6 +31,6 @@ class RunIntegrationTestsCommand extends RunUnitTestsCommand
*/
protected function createPhpunitCmdLine()
{
return '/bin/phpunit --group integration ' . $this->rootDir . '/tests';
return '/vendor/bin/phpunit --group integration ' . $this->rootDir . '/tests';
}
}

View File

@@ -60,8 +60,6 @@ class RunUnitTestsCommand extends Command
$result = $this->executor->execute($this->createPhpunitCmdLine());
$io->write($result->getResult());
if ($result->getExitCode() > 0) {
$io->error('Found problems while running tests');
@@ -76,6 +74,6 @@ class RunUnitTestsCommand extends Command
*/
protected function createPhpunitCmdLine()
{
return '/bin/phpunit --exclude-group integration ' . $this->rootDir . '/tests';
return '/vendor/bin/phpunit --exclude-group integration ' . $this->rootDir . '/tests';
}
}

View File

@@ -0,0 +1,100 @@
<?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\Configuration;
class CalendarConfiguration implements SystemBundleConfiguration
{
use StringAccessibleConfigTrait;
public function getPrefix(): string
{
return 'calendar';
}
/**
* @return array
*/
public function getBusinessDays(): array
{
return (array) $this->find('businessHours.days');
}
/**
* @return string
*/
public function getBusinessTimeBegin(): string
{
return (string) $this->find('businessHours.begin');
}
/**
* @return string
*/
public function getBusinessTimeEnd(): string
{
return (string) $this->find('businessHours.end');
}
/**
* @return string
*/
public function getTimeframeBegin(): string
{
return (string) $this->find('visibleHours.begin');
}
/**
* @return string
*/
public function getTimeframeEnd(): string
{
return (string) $this->find('visibleHours.end');
}
/**
* @return int
*/
public function getDayLimit(): int
{
return (int) $this->find('day_limit');
}
/**
* @return bool
*/
public function isShowWeekNumbers(): bool
{
return (bool) $this->find('week_numbers');
}
/**
* @return bool
*/
public function isShowWeekends(): bool
{
return (bool) $this->find('weekends');
}
/**
* @return null|string
*/
public function getGoogleApiKey(): ?string
{
return $this->find('google.api_key');
}
/**
* @return null|array
*/
public function getGoogleSources(): ?array
{
return $this->find('google.sources');
}
}

View File

@@ -9,14 +9,11 @@
namespace App\Controller;
use App\Calendar\Service;
use App\Calendar\TimesheetEntity;
use App\Entity\Timesheet;
use App\Repository\Query\TimesheetQuery;
use App\Repository\TimesheetRepository;
use App\Calendar\Google;
use App\Calendar\Source;
use App\Configuration\CalendarConfiguration;
use App\Timesheet\UserDateTimeFactory;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
/**
@@ -30,53 +27,34 @@ class CalendarController extends AbstractController
/**
* @Route(path="/", name="calendar", methods={"GET"})
*/
public function userCalendar(Service $calendar, UserDateTimeFactory $dateTime)
public function userCalendar(CalendarConfiguration $configuration, UserDateTimeFactory $dateTime)
{
return $this->render('calendar/user.html.twig', [
'config' => $calendar->getConfig(),
'google' => $calendar->getGoogle(),
'config' => $configuration,
'google' => $this->getGoogleSources($configuration),
'now' => $dateTime->createDateTime(),
]);
}
/**
* @Route(path="/user", name="calendar_entries", methods={"GET"})
* @return Google
*/
public function calendarEntries(Request $request, UserDateTimeFactory $dateTime, TimesheetRepository $repository)
protected function getGoogleSources(CalendarConfiguration $configuration)
{
$start = $request->get('start');
$end = $request->get('end');
$apiKey = $configuration->getGoogleApiKey() ?? null;
$sources = [];
$start = $dateTime->createDateTimeFromFormat('Y-m-d', $start);
if ($start === false) {
$start = $dateTime->createDateTime('first day of this month');
}
$start->setTime(0, 0, 0);
foreach ($configuration->getGoogleSources() as $name => $config) {
$source = new Source();
$source
->setColor($config['color'])
->setUri($config['id'])
->setId($name)
;
$end = $dateTime->createDateTimeFromFormat('Y-m-d', $end);
if ($end === false) {
$end = clone $start;
$end = $end->modify('last day of this month');
}
$end->setTime(23, 59, 59);
$query = new TimesheetQuery();
$query
->setBegin($start)
->setUser($this->getUser())
->setState(TimesheetQuery::STATE_ALL)
->setResultType(TimesheetQuery::RESULT_TYPE_QUERYBUILDER)
->setEnd($end)
;
/* @var $entries Timesheet[] */
$entries = $repository->findByQuery($query)->getQuery()->execute();
$result = [];
foreach ($entries as $entry) {
$result[] = new TimesheetEntity($entry);
$sources[] = $source;
}
return $this->json($result);
return new Google($apiKey, $sources);
}
}

View File

@@ -23,10 +23,13 @@ use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\CountryType;
use Symfony\Component\Form\Extension\Core\Type\CurrencyType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TimezoneType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Validator\Constraints\DateTime;
use Symfony\Component\Validator\Constraints\GreaterThanOrEqual;
use Symfony\Component\Validator\Constraints\NotNull;
/**
* Controller used for executing system relevant tasks.
@@ -84,41 +87,13 @@ class SystemConfigurationController extends AbstractController
}
/**
* @Route(path="/theme", name="system_configuration_theme", methods={"POST"})
* @Route(path="/update/{section}", name="system_configuration_update", methods={"POST"})
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function theme(Request $request)
{
return $this->handleConfigUpdate($request, SystemConfigurationModel::SECTION_THEME);
}
/**
* @Route(path="/timesheet", name="system_configuration_timesheet", methods={"POST"})
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function timesheet(Request $request)
{
return $this->handleConfigUpdate($request, SystemConfigurationModel::SECTION_TIMESHEET);
}
/**
* @Route(path="/customer", name="system_configuration_form_customer", methods={"POST"})
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function formDefaults(Request $request)
{
return $this->handleConfigUpdate($request, SystemConfigurationModel::SECTION_FORM_CUSTOMER);
}
/**
* @param Request $request
* @param string $section
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
protected function handleConfigUpdate(Request $request, string $section)
public function configUpdate(Request $request, string $section)
{
$configModel = null;
$configSettings = $this->getInitializedConfigurations();
@@ -173,15 +148,15 @@ class SystemConfigurationController extends AbstractController
*/
private function createConfigurationsForm(SystemConfigurationModel $configuration)
{
return $this->createForm(
SystemConfigurationForm::class,
$configuration,
[
'attr' => ['id' => 'system_configuration_form_' . $configuration->getSection()],
'action' => $this->generateUrl('system_configuration_' . $configuration->getSection()),
'method' => 'POST'
]
);
$options = [
'action' => $this->generateUrl('system_configuration_update', ['section' => $configuration->getSection()]),
'method' => 'POST',
];
return $this->container
->get('form.factory')
->createNamedBuilder('system_configuration_form_' . $configuration->getSection(), SystemConfigurationForm::class, $configuration, $options)
->getForm();
}
/**
@@ -260,10 +235,41 @@ class SystemConfigurationController extends AbstractController
->setConfiguration([
(new Configuration())
->setName('theme.select_type')
->setLabel('theme.select_type')
->setTranslationDomain('system-configuration')
->setType(EnhancedSelectboxType::class),
]),
(new SystemConfigurationModel())
->setSection(SystemConfigurationModel::SECTION_CALENDAR)
->setConfiguration([
(new Configuration())
->setName('calendar.week_numbers')
->setTranslationDomain('system-configuration')
->setType(CheckboxType::class),
(new Configuration())
->setName('calendar.weekends')
->setTranslationDomain('system-configuration')
->setType(CheckboxType::class),
(new Configuration())
->setName('calendar.businessHours.begin')
->setTranslationDomain('system-configuration')
->setType(TextType::class)
->setConstraints([new DateTime(['format' => 'H:i']), new NotNull()]),
(new Configuration())
->setName('calendar.businessHours.end')
->setTranslationDomain('system-configuration')
->setType(TextType::class)
->setConstraints([new DateTime(['format' => 'H:i']), new NotNull()]),
(new Configuration())
->setName('calendar.visibleHours.begin')
->setTranslationDomain('system-configuration')
->setType(TextType::class)
->setConstraints([new DateTime(['format' => 'H:i']), new NotNull()]),
(new Configuration())
->setName('calendar.visibleHours.end')
->setTranslationDomain('system-configuration')
->setType(TextType::class)
->setConstraints([new DateTime(['format' => 'H:i']), new NotNull()]),
]),
];
}
}

View File

@@ -153,13 +153,7 @@ class TimesheetController extends AbstractController
*/
public function editAction(Timesheet $entry, Request $request)
{
$route = 'timesheet';
if ('calendar' === $request->get('origin')) {
$route = 'calendar';
}
return $this->edit($entry, $request, $route, 'timesheet/edit.html.twig');
return $this->edit($entry, $request, 'timesheet', 'timesheet/edit.html.twig');
}
/**
@@ -173,23 +167,17 @@ class TimesheetController extends AbstractController
*/
public function createAction(Request $request, ProjectRepository $projectRepository, ActivityRepository $activityRepository)
{
$route = 'timesheet';
if ('calendar' === $request->get('origin')) {
$route = 'calendar';
}
return $this->create($request, $route, 'timesheet/edit.html.twig', $projectRepository, $activityRepository);
return $this->create($request, 'timesheet', 'timesheet/edit.html.twig', $projectRepository, $activityRepository);
}
/**
* @param Timesheet $entry
* @param string $redirectRoute
* @return \Symfony\Component\Form\FormInterface
*/
protected function getCreateForm(Timesheet $entry, string $redirectRoute)
protected function getCreateForm(Timesheet $entry)
{
return $this->createForm(TimesheetEditForm::class, $entry, [
'action' => $this->generateUrl('timesheet_create', ['origin' => $redirectRoute]),
'action' => $this->generateUrl('timesheet_create', []),
'include_rate' => $this->isGranted('edit_rate', $entry),
'customer' => true,
]);
@@ -198,16 +186,14 @@ class TimesheetController extends AbstractController
/**
* @param Timesheet $entry
* @param int $page
* @param string $redirectRoute
* @return \Symfony\Component\Form\FormInterface
*/
protected function getEditForm(Timesheet $entry, $page, string $redirectRoute)
protected function getEditForm(Timesheet $entry, $page)
{
return $this->createForm(TimesheetEditForm::class, $entry, [
'action' => $this->generateUrl('timesheet_edit', [
'id' => $entry->getId(),
'page' => $page,
'origin' => $redirectRoute,
]),
'include_rate' => $this->isGranted('edit_rate', $entry),
'include_exported' => $this->isGranted('edit_export', $entry),

View File

@@ -70,7 +70,7 @@ trait TimesheetControllerTrait
*/
protected function edit(Timesheet $entry, Request $request, $redirectRoute, $renderTemplate)
{
$editForm = $this->getEditForm($entry, $request->get('page'), $request->get('origin', 'timesheet'));
$editForm = $this->getEditForm($entry, $request->get('page'));
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
@@ -107,17 +107,20 @@ trait TimesheetControllerTrait
if ($start !== null) {
$start = $this->dateTime->createDateTimeFromFormat('Y-m-d', $start);
if ($start !== false) {
$start->setTime(10, 0, 0); // TODO make me configurable
$entry->setBegin($start);
}
}
$end = $request->get('end');
if ($end !== null) {
$end = $this->dateTime->createDateTimeFromFormat('Y-m-d', $end);
if ($end !== false) {
$end->setTime(18, 0, 0); // TODO make me configurable
$entry->setEnd($end);
// only check for an end date if a begin date was given
$end = $request->get('end');
if ($end !== null) {
$end = $this->dateTime->createDateTimeFromFormat('Y-m-d', $end);
if ($end !== false) {
$start->setTime(10, 0, 0);
$end->setTime(18, 0, 0);
$entry->setEnd($end);
$entry->setDuration($end->getTimestamp() - $start->getTimestamp());
}
}
}
}
@@ -126,14 +129,16 @@ trait TimesheetControllerTrait
$from = $this->dateTime->createDateTime($from);
if ($from !== false) {
$entry->setBegin($from);
}
}
$to = $request->get('to');
if ($to !== null) {
$to = $this->dateTime->createDateTime($to);
if ($to !== false) {
$entry->setEnd($to);
// only check for an end datetime if a begin datetime was given
$to = $request->get('to');
if ($to !== null) {
$to = $this->dateTime->createDateTime($to);
if ($to !== false) {
$entry->setEnd($to);
$entry->setDuration($to->getTimestamp() - $from->getTimestamp());
}
}
}
}
@@ -147,7 +152,7 @@ trait TimesheetControllerTrait
$entry->setActivity($activity);
}
$createForm = $this->getCreateForm($entry, $redirectRoute);
$createForm = $this->getCreateForm($entry);
$createForm->handleRequest($request);
if ($createForm->isSubmitted() && $createForm->isValid()) {
@@ -179,18 +184,16 @@ trait TimesheetControllerTrait
/**
* @param Timesheet $entry
* @param string $redirectRoute
* @return \Symfony\Component\Form\FormInterface
*/
abstract protected function getCreateForm(Timesheet $entry, string $redirectRoute);
abstract protected function getCreateForm(Timesheet $entry);
/**
* @param Timesheet $entry
* @param int $page
* @param string $redirectRoute
* @return \Symfony\Component\Form\FormInterface
*/
abstract protected function getEditForm(Timesheet $entry, $page, string $redirectRoute);
abstract protected function getEditForm(Timesheet $entry, $page);
/**
* Adds a "successful" flash message to the stack.

View File

@@ -146,10 +146,9 @@ class TimesheetTeamController extends AbstractController
/**
* @param Timesheet $entry
* @param string $redirectRoute
* @return \Symfony\Component\Form\FormInterface
*/
protected function getCreateForm(Timesheet $entry, string $redirectRoute)
protected function getCreateForm(Timesheet $entry)
{
return $this->createForm(TimesheetEditForm::class, $entry, [
'action' => $this->generateUrl('admin_timesheet_create'),
@@ -162,10 +161,9 @@ class TimesheetTeamController extends AbstractController
/**
* @param Timesheet $entry
* @param int $page
* @param string $redirectRoute
* @return \Symfony\Component\Form\FormInterface
*/
protected function getEditForm(Timesheet $entry, $page, string $redirectRoute)
protected function getEditForm(Timesheet $entry, $page)
{
return $this->createForm(TimesheetEditForm::class, $entry, [
'action' => $this->generateUrl('admin_timesheet_edit', [

View File

@@ -202,7 +202,6 @@ class Configuration implements ConfigurationInterface
->children()
->arrayNode('documents')
->requiresAtLeastOneElement()
->isRequired()
->scalarPrototype()->end()
->defaultValue([
'var/invoices/',
@@ -244,6 +243,7 @@ class Configuration implements ConfigurationInterface
$node = $builder->getRootNode();
$node
->addDefaultsIfNotSet()
->children()
->booleanNode('week_numbers')->defaultTrue()->end()
->integerNode('day_limit')->defaultValue(4)->end()
@@ -259,6 +259,13 @@ class Configuration implements ConfigurationInterface
->scalarNode('end')->defaultValue('20:00')->end()
->end()
->end()
->arrayNode('visibleHours')
->addDefaultsIfNotSet()
->children()
->scalarNode('begin')->defaultValue('00:00')->end()
->scalarNode('end')->defaultValue('24:00')->end()
->end()
->end()
->arrayNode('google')
->addDefaultsIfNotSet()
->children()
@@ -275,6 +282,7 @@ class Configuration implements ConfigurationInterface
->end()
->end()
->end()
->booleanNode('weekends')->defaultTrue()->end()
->end()
;

View File

@@ -71,6 +71,9 @@ class MenuSubscriber implements EventSubscriberInterface
$menu->addItem(
new MenuItemModel('timesheet', 'menu.timesheet', 'timesheet', [], 'fas fa-clock')
);
$menu->addItem(
new MenuItemModel('calendar', 'calendar.title', 'calendar', [], 'far fa-calendar-alt')
);
}
if ($auth->isGranted('view_invoice')) {

View File

@@ -14,6 +14,7 @@ class SystemConfiguration
public const SECTION_TIMESHEET = 'timesheet';
public const SECTION_FORM_CUSTOMER = 'form_customer';
public const SECTION_THEME = 'theme';
public const SECTION_CALENDAR = 'calendar';
/**
* @var string

View File

@@ -44,7 +44,7 @@ class SystemConfigurationForm extends AbstractType
'data_class' => SystemConfiguration::class,
'csrf_protection' => true,
'csrf_field_name' => '_token',
'csrf_token_id' => 'edit_user_preferences',
'csrf_token_id' => 'edit_system_configurations',
]);
}
}

View File

@@ -80,6 +80,7 @@ class TimesheetEditForm extends AbstractType
$end = null;
$begin = null;
$customerCount = $this->customers->countCustomer(true);
$isNew = true;
if (isset($options['data'])) {
/** @var Timesheet $entry */
@@ -89,6 +90,10 @@ class TimesheetEditForm extends AbstractType
$project = $entry->getProject();
$customer = null === $project ? null : $project->getCustomer();
if (null !== $entry->getId()) {
$isNew = false;
}
if (null === $project && null !== $activity) {
$project = $activity->getProject();
}
@@ -117,7 +122,7 @@ class TimesheetEditForm extends AbstractType
$dateTimeOptions['format'] = $options['date_format'];
}
if (null === $end || !$this->configuration->isDurationOnly()) {
if ($isNew || null === $end || !$this->configuration->isDurationOnly()) {
$builder->add('begin', DateTimePickerType::class, array_merge($dateTimeOptions, [
'label' => 'label.begin'
]));