diff --git a/src/API/TimesheetController.php b/src/API/TimesheetController.php index 91f6ff0a..4c243425 100644 --- a/src/API/TimesheetController.php +++ b/src/API/TimesheetController.php @@ -276,8 +276,6 @@ class TimesheetController extends BaseApiController * required=true, * ) * - * @Security("is_granted('view_own_timesheet') or is_granted('view_other_timesheet')") - * * @ApiSecurity(name="apiUser") * @ApiSecurity(name="apiToken") * @@ -497,7 +495,7 @@ class TimesheetController extends BaseApiController } if (null !== ($reqLimit = $paramFetcher->get('size'))) { - $limit = $reqLimit; + $limit = (int) $reqLimit; } if (null !== ($reqBegin = $paramFetcher->get('begin'))) { diff --git a/src/Command/ImportTimesheetCommand.php b/src/Command/ImportTimesheetCommand.php index 3883b020..f107dbe8 100644 --- a/src/Command/ImportTimesheetCommand.php +++ b/src/Command/ImportTimesheetCommand.php @@ -90,6 +90,10 @@ class ImportTimesheetCommand extends Command * @var User[] */ private $userCache = []; + /** + * @var Tag[] + */ + private $tagCache = []; /** * Comment that will be added to new customers, projects and activities. * @@ -371,9 +375,7 @@ class ImportTimesheetCommand extends Command continue; } - if (null === ($tag = $this->tagRepository->findTagByName($tagName))) { - $tag = (new Tag())->setName($tagName); - } + $tag = $this->getTag($tagName); $timesheet->addTag($tag); } @@ -469,6 +471,23 @@ class ImportTimesheetCommand extends Command return $this->userCache[$user]; } + private function getTag(string $tagName): Tag + { + if (\array_key_exists($tagName, $this->tagCache)) { + return $this->tagCache[$tagName]; + } + + $tag = $this->tagRepository->findTagByName($tagName); + + if ($tag === null) { + $tag = (new Tag())->setName($tagName); + } + + $this->tagCache[$tagName] = $tag; + + return $this->tagCache[$tagName]; + } + private function getActivity($activity, Project $project, $activityType): Activity { $tmpActivity = null; @@ -497,8 +516,9 @@ class ImportTimesheetCommand extends Command private function getProject($project, $customer, $fallbackCustomer): Project { - if (!\array_key_exists($project, $this->projectCache)) { - /** @var Customer $tmpCustomer */ + $cacheKey = $project . '_____' . $customer; + + if (!\array_key_exists($cacheKey, $this->projectCache)) { $tmpCustomer = $this->getCustomer($customer, $fallbackCustomer); /** @var Project $tmpProject */ $tmpProject = null; @@ -533,10 +553,10 @@ class ImportTimesheetCommand extends Command $this->createdProjects++; } - $this->projectCache[$project] = $tmpProject; + $this->projectCache[$cacheKey] = $tmpProject; } - return $this->projectCache[$project]; + return $this->projectCache[$cacheKey]; } private function getCustomer($customer, $fallback): Customer @@ -560,42 +580,44 @@ class ImportTimesheetCommand extends Command } } - if (null === $this->customerFallback) { - $tmpFallback = null; - - if (!empty($fallback)) { - if (\is_int($customer)) { - $tmpFallback = $this->customers->find($fallback); - } else { - /** @var Customer|null $tmpFallback */ - $tmpFallback = $this->customers->findOneBy(['name' => $fallback]); - } - } - - if (null === $tmpFallback) { - $newName = $customer; - if (empty($customer)) { - $newName = self::DEFAULT_CUSTOMER; - if (!empty($fallback) && \is_string($fallback)) { - $newName = $fallback; - } - } - $tmpFallback = new Customer(); - $tmpFallback->setName(sprintf($newName, $this->dateTime)); - $tmpFallback->setComment($this->comment); - $tmpFallback->setCountry($this->configuration->getCustomerDefaultCountry()); - $timezone = date_default_timezone_get(); - if (null !== $this->configuration->getCustomerDefaultTimezone()) { - $timezone = $this->configuration->getCustomerDefaultTimezone(); - } - $tmpFallback->setTimezone($timezone); - $this->customers->saveCustomer($tmpFallback); - $this->createdCustomers++; - } - - $this->customerFallback = $tmpFallback; + if (null !== $this->customerFallback && !empty($fallback)) { + return $this->customerFallback; } + $tmpFallback = null; + + if (!empty($fallback)) { + if (is_numeric($fallback)) { + $tmpFallback = $this->customers->find((int) $fallback); + } else { + /** @var Customer|null $tmpFallback */ + $tmpFallback = $this->customers->findOneBy(['name' => $fallback]); + } + } + + if (null === $tmpFallback) { + $newName = $customer; + if (empty($customer)) { + $newName = self::DEFAULT_CUSTOMER; + if (!empty($fallback) && \is_string($fallback)) { + $newName = $fallback; + } + } + $tmpFallback = new Customer(); + $tmpFallback->setName(sprintf($newName, $this->dateTime)); + $tmpFallback->setComment($this->comment); + $tmpFallback->setCountry($this->configuration->getCustomerDefaultCountry()); + $timezone = date_default_timezone_get(); + if (null !== $this->configuration->getCustomerDefaultTimezone()) { + $timezone = $this->configuration->getCustomerDefaultTimezone(); + } + $tmpFallback->setTimezone($timezone); + $this->customers->saveCustomer($tmpFallback); + $this->createdCustomers++; + } + + $this->customerFallback = $tmpFallback; + return $this->customerFallback; } diff --git a/src/Controller/Reporting/ProjectViewController.php b/src/Controller/Reporting/ProjectViewController.php index fd6b1ef1..9febb633 100644 --- a/src/Controller/Reporting/ProjectViewController.php +++ b/src/Controller/Reporting/ProjectViewController.php @@ -50,6 +50,7 @@ final class ProjectViewController extends AbstractController 'title' => 'report_project_view', 'tableName' => 'project_view_reporting', 'now' => $this->getDateTimeFactory()->createDateTime(), + 'showDurations' => true, ]); } } diff --git a/src/EventSubscriber/Actions/ProjectSubscriber.php b/src/EventSubscriber/Actions/ProjectSubscriber.php index 65fb79d5..417eae28 100644 --- a/src/EventSubscriber/Actions/ProjectSubscriber.php +++ b/src/EventSubscriber/Actions/ProjectSubscriber.php @@ -72,7 +72,7 @@ class ProjectSubscriber extends AbstractActionsSubscriber $event->addDelete($this->path('admin_project_delete', ['id' => $project->getId()])); } - if ($project->isVisible() && $this->isGranted('view_reporting') && $this->isGranted('details_project')) { + if ($project->isVisible() && $this->isGranted('view_reporting') && $this->isGranted('details', $project)) { $event->addAction('report_project_details', ['url' => $this->path('report_project_details', ['project' => $project->getId()]), 'icon' => 'reporting', 'translation_domain' => 'reporting']); } } diff --git a/src/Export/Base/AbstractSpreadsheetRenderer.php b/src/Export/Base/AbstractSpreadsheetRenderer.php index 92d74014..2a9f3b4c 100644 --- a/src/Export/Base/AbstractSpreadsheetRenderer.php +++ b/src/Export/Base/AbstractSpreadsheetRenderer.php @@ -43,10 +43,13 @@ abstract class AbstractSpreadsheetRenderer public const DATETIME_FORMAT = 'yyyy-mm-dd hh:mm'; public const TIME_FORMAT = 'hh:mm'; public const DURATION_FORMAT = '[hh]:mm'; + public const DURATION_DECIMAL = '#0.00'; public const RATE_FORMAT_DEFAULT = '#.##0,00 [$%1$s];-#.##0,00 [$%1$s]'; public const RATE_FORMAT_LEFT = '_("%1$s"* #,##0.00_);_("%1$s"* \(#,##0.00\);_("%1$s"* "-"??_);_(@_)'; public const RATE_FORMAT = self::RATE_FORMAT_LEFT; + protected $durationFormat = self::DURATION_FORMAT; + protected $durationBase = 86400; /** * @var LocaleFormatExtensions */ @@ -183,7 +186,7 @@ abstract class AbstractSpreadsheetRenderer { $sheet->setCellValueByColumnAndRow($column, $row, sprintf('=SUM(%s:%s)', $startCoordinate, $endCoordinate)); $style = $sheet->getStyleByColumnAndRow($column, $row); - $style->getNumberFormat()->setFormatCode(self::DURATION_FORMAT); + $style->getNumberFormat()->setFormatCode($this->durationFormat); } protected function setDuration(Worksheet $sheet, $column, $row, $duration) @@ -191,8 +194,8 @@ abstract class AbstractSpreadsheetRenderer if (null === $duration) { $duration = 0; } - $sheet->setCellValueByColumnAndRow($column, $row, sprintf('=%s/86400', $duration)); - $sheet->getStyleByColumnAndRow($column, $row)->getNumberFormat()->setFormatCode(self::DURATION_FORMAT); + $sheet->setCellValueByColumnAndRow($column, $row, sprintf('=%s/%s', $duration, $this->durationBase)); + $sheet->getStyleByColumnAndRow($column, $row)->getNumberFormat()->setFormatCode($this->durationFormat); } protected function setRateTotal(Worksheet $sheet, $column, $row, $startCoordinate, $endCoordinate) @@ -232,6 +235,11 @@ abstract class AbstractSpreadsheetRenderer */ protected function getColumns(array $exportItems, TimesheetQuery $query, array $columns): array { + if (null !== $query->getCurrentUser() && $query->getCurrentUser()->isExportDecimal()) { + $this->durationFormat = self::DURATION_DECIMAL; + $this->durationBase = 3600; + } + $showRates = $this->isRenderRate($query); if (isset($columns['date']) && !isset($columns['date']['render'])) { diff --git a/src/Migrations/Version20210605154245.php b/src/Migrations/Version20210605154245.php index 8d6f633c..1ca010d9 100644 --- a/src/Migrations/Version20210605154245.php +++ b/src/Migrations/Version20210605154245.php @@ -21,7 +21,7 @@ final class Version20210605154245 extends AbstractMigration { public function getDescription(): string { - return 'Cleans up User table'; + return 'Cleans up the user table'; } public function up(Schema $schema): void diff --git a/src/Migrations/Version20210704111542.php b/src/Migrations/Version20210704111542.php index 35707fd5..bbf78c24 100644 --- a/src/Migrations/Version20210704111542.php +++ b/src/Migrations/Version20210704111542.php @@ -15,7 +15,7 @@ use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; /** - * Auto-generated Migration: Please modify to your needs! + * @version 1.15 */ final class Version20210704111542 extends AbstractMigration { diff --git a/templates/activity/edit.html.twig b/templates/activity/edit.html.twig index 6d653629..70854cb1 100644 --- a/templates/activity/edit.html.twig +++ b/templates/activity/edit.html.twig @@ -7,7 +7,7 @@ {% block main %} {% set formEditTemplate = app.request.xmlHttpRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig' %} {% set formOptions = { - 'title': (activity.id is null ? 'create'|trans : 'edit'|trans({}, 'actions')), + 'title': (activity.id is null ? 'create-activity'|trans({}, 'actions') : 'edit'|trans({}, 'actions')), 'form': form, 'back': path('admin_activity') } %} diff --git a/templates/embeds/budgets.html.twig b/templates/embeds/budgets.html.twig index ea3de3d5..afdb4513 100644 --- a/templates/embeds/budgets.html.twig +++ b/templates/embeds/budgets.html.twig @@ -1,6 +1,12 @@ {% embed '@AdminLTE/Widgets/box-widget.html.twig' %} {% import "macros/progressbar.html.twig" as progress %} - {% block box_title %}{{ 'label.timeBudget'|trans }}{% endblock %} + {% import "macros/widgets.html.twig" as widgets %} + {% block box_title %} + {{ 'label.timeBudget'|trans }} + {% if entity.timeBudget > 0 %} + {{ widgets.label(entity.timeBudget|duration, 'gray') }} + {% endif %} + {% endblock %} {% block box_attributes %}id="time_budget_box"{% endblock %} {% block box_body %} {{ progress.progressbar(stats.recordDuration, stats.durationBillable, 'label.billable'|trans, stats.durationBillable|duration ~ ' / ' ~ stats.recordDuration|duration, true) }} @@ -11,7 +17,13 @@ {% endembed %} {% embed '@AdminLTE/Widgets/box-widget.html.twig' %} {% import "macros/progressbar.html.twig" as progress %} - {% block box_title %}{{ 'label.budget'|trans }}{% endblock %} + {% import "macros/widgets.html.twig" as widgets %} + {% block box_title %} + {{ 'label.budget'|trans }} + {% if entity.budget > 0 %} + {{ widgets.label(entity.budget|money(currency), 'gray') }} + {% endif %} + {% endblock %} {% block box_attributes %}id="budget_box"{% endblock %} {% block box_body %} {{ progress.progressbar(stats.recordRate, stats.rateBillable, 'label.billable'|trans, stats.rateBillable|money(currency) ~ ' / ' ~ stats.recordRate|money(currency), true) }} diff --git a/templates/project/edit.html.twig b/templates/project/edit.html.twig index aaed2a25..8916c2c4 100644 --- a/templates/project/edit.html.twig +++ b/templates/project/edit.html.twig @@ -7,7 +7,7 @@ {% block main %} {% set formEditTemplate = app.request.xmlHttpRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig' %} {% set formOptions = { - 'title': (project.id is null ? 'create'|trans : 'edit'|trans({}, 'actions')), + 'title': (project.id is null ? 'create-project'|trans({}, 'actions') : 'edit'|trans({}, 'actions')), 'form': form, 'back': path('admin_project') } %} diff --git a/templates/reporting/project_view.html.twig b/templates/reporting/project_view.html.twig index d2eb6adc..c128d72f 100644 --- a/templates/reporting/project_view.html.twig +++ b/templates/reporting/project_view.html.twig @@ -3,12 +3,19 @@ {% block report_title %}{{ (title|default('report_project_view'))|trans({}, 'reporting') }}{% endblock %} +{% set showDurations = showDurations is defined and showDurations is same as (true) %} {% set columns = { 'name': {'class': 'alwaysVisible'}, 'lastRecord': {'class': 'hidden text-center hw-min', 'title': 'label.last_record'|trans}, - 'today': {'class': 'hidden-md hidden-sm hidden-xs text-center hw-min', 'title': 'stats.durationToday'|trans}, - 'week': {'class': 'hidden-md hidden-sm hidden-xs text-center hw-min', 'title': 'stats.durationWeek'|trans}, - 'month': {'class': 'hidden-md hidden-sm hidden-xs text-center hw-min', 'title': 'stats.durationMonth'|trans}, +} %} +{% if showDurations %} + {% set columns = columns|merge({ + 'today': {'class': 'hidden hidden-md hidden-sm hidden-xs text-center hw-min', 'title': 'stats.durationToday'|trans}, + 'week': {'class': 'hidden hidden-md hidden-sm hidden-xs text-center hw-min', 'title': 'stats.durationWeek'|trans}, + 'month': {'class': 'hidden hidden-md hidden-sm hidden-xs text-center hw-min', 'title': 'stats.durationMonth'|trans}, + }) %} +{% endif %} +{% set columns = columns|merge({ 'durationTotal': {'class': 'text-center hw-min', 'title': 'stats.durationTotal'|trans}, 'timeBudget': {'class': 'hidden-xs', 'title': 'label.timeBudget'|trans}, 'budget': {'class': 'hidden-xs', 'title': 'label.budget'|trans}, @@ -18,7 +25,7 @@ 'projectEnd': {'class': 'hidden-md hidden-sm hidden-xs hidden text-center w-min', 'title': 'label.project_end'|trans}, 'comment': {'class': 'hidden-md hidden-sm hidden-xs hidden', 'title': 'label.comment'|trans}, 'actions': {'class': 'actions alwaysVisible'}, -} %} +}) %} {% set tableName = tableName|default('project_view_reporting') %} {% block main_before %} @@ -70,9 +77,11 @@ – {% endif %} + {% if showDurations %}