Release 2.42 (#5686)
This commit is contained in:
@@ -12,8 +12,6 @@ namespace App\API;
|
||||
use App\Activity\ActivityService;
|
||||
use App\Entity\Activity;
|
||||
use App\Entity\ActivityRate;
|
||||
use App\Entity\User;
|
||||
use App\Event\ActivityMetaDefinitionEvent;
|
||||
use App\Form\API\ActivityApiEditForm;
|
||||
use App\Form\API\ActivityRateApiForm;
|
||||
use App\Repository\ActivityRateRepository;
|
||||
@@ -26,7 +24,6 @@ use FOS\RestBundle\Request\ParamFetcherInterface;
|
||||
use FOS\RestBundle\View\View;
|
||||
use FOS\RestBundle\View\ViewHandlerInterface;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -46,7 +43,6 @@ final class ActivityController extends BaseApiController
|
||||
public function __construct(
|
||||
private readonly ViewHandlerInterface $viewHandler,
|
||||
private readonly ActivityRepository $repository,
|
||||
private readonly EventDispatcherInterface $dispatcher,
|
||||
private readonly ActivityRateRepository $activityRateRepository,
|
||||
private readonly ActivityService $activityService
|
||||
) {
|
||||
@@ -63,25 +59,12 @@ final class ActivityController extends BaseApiController
|
||||
#[Rest\QueryParam(name: 'globals', requirements: '0|1|true|false', strict: true, nullable: true, description: 'Use if you want to fetch only global activities. Allowed values: 0|1 (default: 0 for false)')]
|
||||
#[Rest\QueryParam(name: 'orderBy', requirements: 'id|name|project', strict: true, nullable: true, description: 'The field by which results will be ordered. Allowed values: id, name, project (default: name)')]
|
||||
#[Rest\QueryParam(name: 'order', requirements: 'ASC|DESC', strict: true, nullable: true, description: 'The result order. Allowed values: ASC, DESC (default: ASC)')]
|
||||
#[Rest\QueryParam(name: 'term', description: 'Free search term')]
|
||||
#[Rest\QueryParam(name: 'term', description: 'Free search term', nullable: true)]
|
||||
public function cgetAction(ParamFetcherInterface $paramFetcher, ProjectRepository $projectRepository): Response
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = $this->getUser();
|
||||
|
||||
$query = new ActivityQuery();
|
||||
$query->loadTeams();
|
||||
$query->setCurrentUser($user);
|
||||
|
||||
$order = $paramFetcher->get('order');
|
||||
if (\is_string($order) && $order !== '') {
|
||||
$query->setOrder($order);
|
||||
}
|
||||
|
||||
$orderBy = $paramFetcher->get('orderBy');
|
||||
if (\is_string($orderBy) && $orderBy !== '') {
|
||||
$query->setOrderBy($orderBy);
|
||||
}
|
||||
$this->prepareQuery($query, $paramFetcher);
|
||||
|
||||
$globals = $paramFetcher->get('globals');
|
||||
if (\is_string($globals) && ($globals === 'true' || $globals === '1')) {
|
||||
@@ -113,7 +96,6 @@ final class ActivityController extends BaseApiController
|
||||
$query->setSearchTerm(new SearchTerm($term));
|
||||
}
|
||||
|
||||
$query->setIsApiCall(true);
|
||||
$data = $this->repository->getActivitiesForQuery($query);
|
||||
$view = new View($data, 200);
|
||||
$view->getContext()->setGroups(self::GROUPS_COLLECTION);
|
||||
@@ -149,9 +131,7 @@ final class ActivityController extends BaseApiController
|
||||
}
|
||||
|
||||
$activity = new Activity();
|
||||
|
||||
$event = new ActivityMetaDefinitionEvent($activity);
|
||||
$this->dispatcher->dispatch($event);
|
||||
$this->activityService->loadMetaFields($activity);
|
||||
|
||||
$form = $this->createForm(ActivityApiEditForm::class, $activity, [
|
||||
'include_budget' => $this->isGranted('budget', $activity),
|
||||
@@ -185,8 +165,7 @@ final class ActivityController extends BaseApiController
|
||||
#[Route(methods: ['PATCH'], path: '/{id}', name: 'patch_activity', requirements: ['id' => '\d+'])]
|
||||
public function patchAction(Request $request, Activity $activity): Response
|
||||
{
|
||||
$event = new ActivityMetaDefinitionEvent($activity);
|
||||
$this->dispatcher->dispatch($event);
|
||||
$this->activityService->loadMetaFields($activity);
|
||||
|
||||
$form = $this->createForm(ActivityApiEditForm::class, $activity, [
|
||||
'include_budget' => $this->isGranted('budget', $activity),
|
||||
@@ -241,8 +220,7 @@ final class ActivityController extends BaseApiController
|
||||
#[Rest\RequestParam(name: 'value', strict: true, nullable: false, description: 'The meta-field value')]
|
||||
public function metaAction(Activity $activity, ParamFetcherInterface $paramFetcher): Response
|
||||
{
|
||||
$event = new ActivityMetaDefinitionEvent($activity);
|
||||
$this->dispatcher->dispatch($event);
|
||||
$this->activityService->loadMetaFields($activity);
|
||||
|
||||
$name = $paramFetcher->get('name');
|
||||
$value = $paramFetcher->get('value');
|
||||
|
||||
@@ -21,6 +21,7 @@ use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
|
||||
abstract class BaseApiController extends AbstractController
|
||||
{
|
||||
public const MAX_PAGE_SIZE = 500;
|
||||
public const DATE_ONLY_FORMAT = 'yyyy-MM-dd';
|
||||
public const DATE_FORMAT = DateTimeType::HTML5_FORMAT;
|
||||
public const DATE_FORMAT_PHP = 'Y-m-d\TH:i:s';
|
||||
@@ -84,8 +85,8 @@ abstract class BaseApiController extends AbstractController
|
||||
$size = $all['size'];
|
||||
if (is_numeric($size)) {
|
||||
$size = (int) $size;
|
||||
if ($size < 1 || $size > 500) {
|
||||
throw new BadRequestHttpException('Size must be between 1 and 500');
|
||||
if ($size < 1 || $size > self::MAX_PAGE_SIZE) {
|
||||
throw new BadRequestHttpException('Size must be between 1 and ' . self::MAX_PAGE_SIZE);
|
||||
}
|
||||
$query->setPageSize($size);
|
||||
}
|
||||
|
||||
@@ -23,16 +23,12 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
#[OA\Tag(name: 'Default')]
|
||||
final class ConfigurationController extends BaseApiController
|
||||
{
|
||||
public function __construct(private readonly ViewHandlerInterface $viewHandler)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch timesheet configuration
|
||||
*/
|
||||
#[OA\Response(response: 200, description: 'Returns the instance specific timesheet configuration', content: new OA\JsonContent(ref: new Model(type: TimesheetConfig::class)))]
|
||||
#[Route(path: '/config/timesheet', methods: ['GET'])]
|
||||
public function timesheetConfigAction(SystemConfiguration $configuration): Response
|
||||
public function timesheetConfigAction(SystemConfiguration $configuration, ViewHandlerInterface $viewHandler): Response
|
||||
{
|
||||
$model = new TimesheetConfig();
|
||||
$model->setTrackingMode($configuration->getTimesheetTrackingMode());
|
||||
@@ -44,7 +40,7 @@ final class ConfigurationController extends BaseApiController
|
||||
$view = new View($model, 200);
|
||||
$view->getContext()->setGroups(['Default', 'Config']);
|
||||
|
||||
return $this->viewHandler->handle($view);
|
||||
return $viewHandler->handle($view);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,11 +48,11 @@ final class ConfigurationController extends BaseApiController
|
||||
*/
|
||||
#[OA\Response(response: 200, description: 'Returns the configured color codes and names', content: new OA\JsonContent(type: 'object', example: ['Red' => '#ff0000'], additionalProperties: new OA\AdditionalProperties(type: 'string')))]
|
||||
#[Route(path: '/config/colors', methods: ['GET'])]
|
||||
public function colorConfigAction(SystemConfiguration $configuration): Response
|
||||
public function colorConfigAction(SystemConfiguration $configuration, ViewHandlerInterface $viewHandler): Response
|
||||
{
|
||||
$view = new View($configuration->getThemeColors(), 200);
|
||||
$view->getContext()->setGroups(['Default']);
|
||||
|
||||
return $this->viewHandler->handle($view);
|
||||
return $viewHandler->handle($view);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ use App\Customer\CustomerService;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\CustomerRate;
|
||||
use App\Entity\User;
|
||||
use App\Event\CustomerMetaDefinitionEvent;
|
||||
use App\Form\API\CustomerApiEditForm;
|
||||
use App\Form\API\CustomerRateApiForm;
|
||||
use App\Repository\CustomerRateRepository;
|
||||
@@ -25,7 +24,6 @@ use FOS\RestBundle\Request\ParamFetcherInterface;
|
||||
use FOS\RestBundle\View\View;
|
||||
use FOS\RestBundle\View\ViewHandlerInterface;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -45,7 +43,6 @@ final class CustomerController extends BaseApiController
|
||||
public function __construct(
|
||||
private readonly ViewHandlerInterface $viewHandler,
|
||||
private readonly CustomerRepository $repository,
|
||||
private readonly EventDispatcherInterface $dispatcher,
|
||||
private readonly CustomerRateRepository $customerRateRepository,
|
||||
private readonly CustomerService $customerService,
|
||||
) {
|
||||
@@ -59,7 +56,7 @@ final class CustomerController extends BaseApiController
|
||||
#[Rest\QueryParam(name: 'visible', requirements: '1|2|3', default: 1, strict: true, nullable: true, description: 'Visibility status to filter customers: 1=visible, 2=hidden, 3=both')]
|
||||
#[Rest\QueryParam(name: 'order', requirements: 'ASC|DESC', strict: true, nullable: true, description: 'The result order. Allowed values: ASC, DESC (default: ASC)')]
|
||||
#[Rest\QueryParam(name: 'orderBy', requirements: 'id|name', strict: true, nullable: true, description: 'The field by which results will be ordered. Allowed values: id, name (default: name)')]
|
||||
#[Rest\QueryParam(name: 'term', description: 'Free search term')]
|
||||
#[Rest\QueryParam(name: 'term', description: 'Free search term', nullable: true)]
|
||||
public function cgetAction(ParamFetcherInterface $paramFetcher): Response
|
||||
{
|
||||
/** @var User $user */
|
||||
@@ -125,9 +122,6 @@ final class CustomerController extends BaseApiController
|
||||
|
||||
$customer = $customerService->createNewCustomer('');
|
||||
|
||||
$event = new CustomerMetaDefinitionEvent($customer);
|
||||
$this->dispatcher->dispatch($event);
|
||||
|
||||
$form = $this->createForm(CustomerApiEditForm::class, $customer, [
|
||||
'include_budget' => $this->isGranted('budget', $customer),
|
||||
'include_time' => $this->isGranted('time', $customer),
|
||||
@@ -160,8 +154,7 @@ final class CustomerController extends BaseApiController
|
||||
#[Route(methods: ['PATCH'], path: '/{id}', name: 'patch_customer', requirements: ['id' => '\d+'])]
|
||||
public function patchAction(Request $request, Customer $customer): Response
|
||||
{
|
||||
$event = new CustomerMetaDefinitionEvent($customer);
|
||||
$this->dispatcher->dispatch($event);
|
||||
$this->customerService->loadMetaFields($customer);
|
||||
|
||||
$form = $this->createForm(CustomerApiEditForm::class, $customer, [
|
||||
'include_budget' => $this->isGranted('budget', $customer),
|
||||
@@ -216,8 +209,7 @@ final class CustomerController extends BaseApiController
|
||||
#[Rest\RequestParam(name: 'value', strict: true, nullable: false, description: 'The meta-field value')]
|
||||
public function metaAction(Customer $customer, ParamFetcherInterface $paramFetcher): Response
|
||||
{
|
||||
$event = new CustomerMetaDefinitionEvent($customer);
|
||||
$this->dispatcher->dispatch($event);
|
||||
$this->customerService->loadMetaFields($customer);
|
||||
|
||||
$name = $paramFetcher->get('name');
|
||||
$value = $paramFetcher->get('value');
|
||||
|
||||
@@ -23,12 +23,6 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
#[OA\Tag(name: 'Export')]
|
||||
final class ExportController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ViewHandlerInterface $viewHandler,
|
||||
private readonly ExportTemplateRepository $repository,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete export template
|
||||
*/
|
||||
@@ -36,12 +30,12 @@ final class ExportController extends BaseApiController
|
||||
#[OA\Delete(responses: [new OA\Response(response: 204, description: 'Delete export template')], x: ['internal' => true])]
|
||||
#[OA\Parameter(name: 'id', description: 'Export template ID to delete', in: 'path', required: true)]
|
||||
#[Route(path: '/{id}', name: 'delete_export_template', requirements: ['id' => '\d+'], methods: ['DELETE'])]
|
||||
public function deleteTemplate(ExportTemplate $exportTemplate): Response
|
||||
public function deleteTemplate(ExportTemplate $exportTemplate, ExportTemplateRepository $repository, ViewHandlerInterface $viewHandler): Response
|
||||
{
|
||||
$this->repository->removeExportTemplate($exportTemplate);
|
||||
$repository->removeExportTemplate($exportTemplate);
|
||||
|
||||
$view = new View(null, Response::HTTP_NO_CONTENT);
|
||||
|
||||
return $this->viewHandler->handle($view);
|
||||
return $viewHandler->handle($view);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ namespace App\API;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\ProjectRate;
|
||||
use App\Entity\User;
|
||||
use App\Event\ProjectMetaDefinitionEvent;
|
||||
use App\Form\API\ProjectApiEditForm;
|
||||
use App\Form\API\ProjectRateApiForm;
|
||||
use App\Project\ProjectService;
|
||||
@@ -26,7 +25,6 @@ use FOS\RestBundle\Request\ParamFetcherInterface;
|
||||
use FOS\RestBundle\View\View;
|
||||
use FOS\RestBundle\View\ViewHandlerInterface;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -47,7 +45,6 @@ final class ProjectController extends BaseApiController
|
||||
public function __construct(
|
||||
private readonly ViewHandlerInterface $viewHandler,
|
||||
private readonly ProjectRepository $repository,
|
||||
private readonly EventDispatcherInterface $dispatcher,
|
||||
private readonly ProjectRateRepository $projectRateRepository,
|
||||
private readonly ProjectService $projectService
|
||||
) {
|
||||
@@ -67,7 +64,7 @@ final class ProjectController extends BaseApiController
|
||||
#[Rest\QueryParam(name: 'globalActivities', requirements: '0|1', strict: true, nullable: true, description: "If given, filters projects by their 'global activity' support. Allowed values: 1 (supports global activities) and 0 (without global activities) (default: all)")]
|
||||
#[Rest\QueryParam(name: 'order', requirements: 'ASC|DESC', strict: true, nullable: true, description: 'The result order. Allowed values: ASC, DESC (default: ASC)')]
|
||||
#[Rest\QueryParam(name: 'orderBy', requirements: 'id|name|customer', strict: true, nullable: true, description: 'The field by which results will be ordered. Allowed values: id, name, customer (default: name)')]
|
||||
#[Rest\QueryParam(name: 'term', description: 'Free search term')]
|
||||
#[Rest\QueryParam(name: 'term', description: 'Free search term', nullable: true)]
|
||||
public function cgetAction(ParamFetcherInterface $paramFetcher, CustomerRepository $customerRepository): Response
|
||||
{
|
||||
/** @var User $user */
|
||||
@@ -212,8 +209,7 @@ final class ProjectController extends BaseApiController
|
||||
#[Route(methods: ['PATCH'], path: '/{id}', name: 'patch_project', requirements: ['id' => '\d+'])]
|
||||
public function patchAction(Request $request, Project $project): Response
|
||||
{
|
||||
$event = new ProjectMetaDefinitionEvent($project);
|
||||
$this->dispatcher->dispatch($event);
|
||||
$this->projectService->loadMetaFields($project);
|
||||
|
||||
$form = $this->createForm(ProjectApiEditForm::class, $project, [
|
||||
'timezone' => $this->getDateTimeFactory()->getTimezone()->getName(),
|
||||
@@ -270,8 +266,7 @@ final class ProjectController extends BaseApiController
|
||||
#[Rest\RequestParam(name: 'value', strict: true, nullable: false, description: 'The meta-field value')]
|
||||
public function metaAction(Project $project, ParamFetcherInterface $paramFetcher): Response
|
||||
{
|
||||
$event = new ProjectMetaDefinitionEvent($project);
|
||||
$this->dispatcher->dispatch($event);
|
||||
$this->projectService->loadMetaFields($project);
|
||||
|
||||
$name = $paramFetcher->get('name');
|
||||
$value = $paramFetcher->get('value');
|
||||
|
||||
@@ -92,7 +92,7 @@ final class TimesheetController extends BaseApiController
|
||||
#[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: 'billable', requirements: '0|1', strict: true, nullable: true, description: 'Filter for non-/billable records. Allowed values: 0=non-billable, 1=billable (default: all)')]
|
||||
#[Rest\QueryParam(name: 'full', requirements: '0|1|true|false', strict: true, nullable: true, description: 'Allows to fetch full objects including subresources. Allowed values: 0|1|false|true (default: false)')]
|
||||
#[Rest\QueryParam(name: 'term', description: 'Free search term')]
|
||||
#[Rest\QueryParam(name: 'term', description: 'Free search term', nullable: true)]
|
||||
#[Rest\QueryParam(name: 'modified_after', requirements: [new Constraints\DateTime(format: 'Y-m-d\TH:i:s')], strict: true, nullable: true, description: 'Only records changed after this date will be included (format: HTML5 datetime-local, e.g. YYYY-MM-DDThh:mm:ss)')]
|
||||
public function cgetAction(ParamFetcherInterface $paramFetcher, CustomerRepository $customerRepository, ProjectRepository $projectRepository, ActivityRepository $activityRepository, UserRepository $userRepository): Response
|
||||
{
|
||||
|
||||
@@ -58,7 +58,7 @@ final class UserController extends BaseApiController
|
||||
#[Rest\QueryParam(name: 'visible', requirements: '1|2|3', default: 1, strict: true, nullable: true, description: 'Visibility status to filter users: 1=visible, 2=hidden, 3=all')]
|
||||
#[Rest\QueryParam(name: 'orderBy', requirements: 'id|username|alias|email', strict: true, nullable: true, description: 'The field by which results will be ordered. Allowed values: id, username, alias, email (default: username)')]
|
||||
#[Rest\QueryParam(name: 'order', requirements: 'ASC|DESC', strict: true, nullable: true, description: 'The result order. Allowed values: ASC, DESC (default: ASC)')]
|
||||
#[Rest\QueryParam(name: 'term', description: 'Free search term')]
|
||||
#[Rest\QueryParam(name: 'term', description: 'Free search term', nullable: true)]
|
||||
#[Rest\QueryParam(name: 'full', requirements: '0|1|true|false', strict: true, nullable: true, description: 'Allows to fetch full objects including subresources. Allowed values: 0|1|false|true (default: false)')]
|
||||
public function cgetAction(ParamFetcherInterface $paramFetcher): Response
|
||||
{
|
||||
|
||||
@@ -40,6 +40,11 @@ class ActivityService
|
||||
{
|
||||
}
|
||||
|
||||
public function loadMetaFields(Activity $activity): void
|
||||
{
|
||||
$this->dispatcher->dispatch(new ActivityMetaDefinitionEvent($activity));
|
||||
}
|
||||
|
||||
public function createNewActivity(?Project $project = null): Activity
|
||||
{
|
||||
$activity = new Activity();
|
||||
@@ -49,7 +54,7 @@ class ActivityService
|
||||
$activity->setProject($project);
|
||||
}
|
||||
|
||||
$this->dispatcher->dispatch(new ActivityMetaDefinitionEvent($activity));
|
||||
$this->loadMetaFields($activity);
|
||||
$this->dispatcher->dispatch(new ActivityCreateEvent($activity));
|
||||
|
||||
return $activity;
|
||||
@@ -82,10 +87,10 @@ class ActivityService
|
||||
return $activity;
|
||||
}
|
||||
|
||||
public function deleteActivity(Activity $activity): void
|
||||
public function deleteActivity(Activity $activity, ?Activity $replace = null): void
|
||||
{
|
||||
$this->dispatcher->dispatch(new ActivityDeleteEvent($activity));
|
||||
$this->repository->deleteActivity($activity);
|
||||
$this->dispatcher->dispatch(new ActivityDeleteEvent($activity, $replace));
|
||||
$this->repository->deleteActivity($activity, $replace);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,11 +17,11 @@ final class Constants
|
||||
/**
|
||||
* The current release version
|
||||
*/
|
||||
public const VERSION = '2.41.0';
|
||||
public const VERSION = '2.42.0';
|
||||
/**
|
||||
* The current release: major * 10000 + minor * 100 + patch
|
||||
*/
|
||||
public const VERSION_ID = 24100;
|
||||
public const VERSION_ID = 24200;
|
||||
/**
|
||||
* The software name
|
||||
*/
|
||||
|
||||
@@ -14,11 +14,9 @@ use App\Activity\ActivityStatisticService;
|
||||
use App\Configuration\SystemConfiguration;
|
||||
use App\Entity\Activity;
|
||||
use App\Entity\ActivityRate;
|
||||
use App\Entity\MetaTableTypeInterface;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\Team;
|
||||
use App\Event\ActivityDetailControllerEvent;
|
||||
use App\Event\ActivityMetaDefinitionEvent;
|
||||
use App\Event\ActivityMetaDisplayEvent;
|
||||
use App\Export\Spreadsheet\EntityWithMetaFieldsExporter;
|
||||
use App\Export\Spreadsheet\Writer\BinaryFileResponseWriter;
|
||||
@@ -51,19 +49,14 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
#[Route(path: '/admin/activity')]
|
||||
final class ActivityController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ActivityRepository $repository,
|
||||
private readonly SystemConfiguration $configuration,
|
||||
private readonly EventDispatcherInterface $dispatcher,
|
||||
private readonly ActivityService $activityService
|
||||
)
|
||||
public function __construct(private readonly ActivityRepository $repository)
|
||||
{
|
||||
}
|
||||
|
||||
#[Route(path: '/', defaults: ['page' => 1], name: 'admin_activity', methods: ['GET'])]
|
||||
#[Route(path: '/page/{page}', requirements: ['page' => '[1-9]\d*'], name: 'admin_activity_paginated', methods: ['GET'])]
|
||||
#[IsGranted(new Expression("is_granted('listing', 'activity')"))]
|
||||
public function indexAction(int $page, Request $request): Response
|
||||
public function indexAction(int $page, Request $request, EventDispatcherInterface $dispatcher, SystemConfiguration $configuration): Response
|
||||
{
|
||||
$query = new ActivityQuery();
|
||||
$query->loadTeams();
|
||||
@@ -76,7 +69,10 @@ final class ActivityController extends AbstractController
|
||||
}
|
||||
|
||||
$entries = $this->repository->getPagerfantaForQuery($query);
|
||||
$metaColumns = $this->findMetaColumns($query);
|
||||
|
||||
$event = new ActivityMetaDisplayEvent($query, ActivityMetaDisplayEvent::ACTIVITY);
|
||||
$dispatcher->dispatch($event);
|
||||
$metaColumns = $event->getFields();
|
||||
|
||||
$table = new DataTable('activity_admin', $query);
|
||||
$table->setPagination($entries);
|
||||
@@ -114,29 +110,16 @@ final class ActivityController extends AbstractController
|
||||
'page_setup' => $page,
|
||||
'dataTable' => $table,
|
||||
'metaColumns' => $metaColumns,
|
||||
'defaultCurrency' => $this->configuration->getCustomerDefaultCurrency(),
|
||||
'defaultCurrency' => $configuration->getCustomerDefaultCurrency(),
|
||||
'now' => $this->getDateTimeFactory()->createDateTime(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ActivityQuery $query
|
||||
* @return MetaTableTypeInterface[]
|
||||
*/
|
||||
private function findMetaColumns(ActivityQuery $query): array
|
||||
{
|
||||
$event = new ActivityMetaDisplayEvent($query, ActivityMetaDisplayEvent::ACTIVITY);
|
||||
$this->dispatcher->dispatch($event);
|
||||
|
||||
return $event->getFields();
|
||||
}
|
||||
|
||||
#[Route(path: '/{id}/details', name: 'activity_details', methods: ['GET', 'POST'])]
|
||||
#[IsGranted('view', 'activity')]
|
||||
public function detailsAction(Activity $activity, TeamRepository $teamRepository, ActivityRateRepository $rateRepository, ActivityStatisticService $statisticService): Response
|
||||
public function detailsAction(Activity $activity, TeamRepository $teamRepository, ActivityRateRepository $rateRepository, ActivityStatisticService $statisticService, ActivityService $activityService, EventDispatcherInterface $dispatcher): Response
|
||||
{
|
||||
$event = new ActivityMetaDefinitionEvent($activity);
|
||||
$this->dispatcher->dispatch($event);
|
||||
$activityService->loadMetaFields($activity);
|
||||
|
||||
$stats = null;
|
||||
$rates = [];
|
||||
@@ -179,7 +162,7 @@ final class ActivityController extends AbstractController
|
||||
|
||||
// additional boxes by plugins
|
||||
$event = new ActivityDetailControllerEvent($activity);
|
||||
$this->dispatcher->dispatch($event);
|
||||
$dispatcher->dispatch($event);
|
||||
$boxes = $event->getController();
|
||||
|
||||
$page = $this->createPageSetup();
|
||||
@@ -247,31 +230,28 @@ final class ActivityController extends AbstractController
|
||||
|
||||
#[Route(path: '/create/{project}', name: 'admin_activity_create_with_project', methods: ['GET', 'POST'])]
|
||||
#[IsGranted('create_activity')]
|
||||
public function createWithProjectAction(Request $request, Project $project): Response
|
||||
public function createWithProjectAction(Project $project, Request $request, ActivityService $activityService, SystemConfiguration $configuration): Response
|
||||
{
|
||||
return $this->createActivity($request, $project);
|
||||
return $this->createActivity($request, $activityService, $configuration, $project);
|
||||
}
|
||||
|
||||
#[Route(path: '/create', name: 'admin_activity_create', methods: ['GET', 'POST'])]
|
||||
#[IsGranted('create_activity')]
|
||||
public function createAction(Request $request): Response
|
||||
public function createAction(Request $request, ActivityService $activityService, SystemConfiguration $configuration): Response
|
||||
{
|
||||
return $this->createActivity($request, null);
|
||||
return $this->createActivity($request, $activityService, $configuration, null);
|
||||
}
|
||||
|
||||
private function createActivity(Request $request, ?Project $project = null): Response
|
||||
private function createActivity(Request $request, ActivityService $activityService, SystemConfiguration $configuration, ?Project $project = null): Response
|
||||
{
|
||||
$activity = $this->activityService->createNewActivity($project);
|
||||
$activity = $activityService->createNewActivity($project);
|
||||
|
||||
$event = new ActivityMetaDefinitionEvent($activity);
|
||||
$this->dispatcher->dispatch($event);
|
||||
|
||||
$editForm = $this->createEditForm($activity);
|
||||
$editForm = $this->createEditForm($activity, $configuration);
|
||||
$editForm->handleRequest($request);
|
||||
|
||||
if ($editForm->isSubmitted() && $editForm->isValid()) {
|
||||
try {
|
||||
$this->activityService->saveActivity($activity);
|
||||
$activityService->saveActivity($activity);
|
||||
$this->flashSuccess('action.update.success');
|
||||
|
||||
return $this->redirectToRouteAfterCreate('activity_details', ['id' => $activity->getId()]);
|
||||
@@ -289,7 +269,7 @@ final class ActivityController extends AbstractController
|
||||
|
||||
#[Route(path: '/{id}/permissions', name: 'admin_activity_permissions', methods: ['GET', 'POST'])]
|
||||
#[IsGranted('permissions', 'activity')]
|
||||
public function teamPermissionsAction(Activity $activity, Request $request): Response
|
||||
public function teamPermissionsAction(Activity $activity, Request $request, ActivityService $activityService): Response
|
||||
{
|
||||
$form = $this->createForm(ActivityTeamPermissionForm::class, $activity, [
|
||||
'action' => $this->generateUrl('admin_activity_permissions', ['id' => $activity->getId()]),
|
||||
@@ -300,7 +280,7 @@ final class ActivityController extends AbstractController
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
try {
|
||||
$this->activityService->saveActivity($activity);
|
||||
$activityService->saveActivity($activity);
|
||||
$this->flashSuccess('action.update.success');
|
||||
|
||||
if ($this->isGranted('view', $activity)) {
|
||||
@@ -345,17 +325,16 @@ final class ActivityController extends AbstractController
|
||||
|
||||
#[Route(path: '/{id}/edit', name: 'admin_activity_edit', methods: ['GET', 'POST'])]
|
||||
#[IsGranted('edit', 'activity')]
|
||||
public function editAction(Activity $activity, Request $request): Response
|
||||
public function editAction(Activity $activity, Request $request, ActivityService $activityService, SystemConfiguration $configuration): Response
|
||||
{
|
||||
$event = new ActivityMetaDefinitionEvent($activity);
|
||||
$this->dispatcher->dispatch($event);
|
||||
$activityService->loadMetaFields($activity);
|
||||
|
||||
$editForm = $this->createEditForm($activity);
|
||||
$editForm = $this->createEditForm($activity, $configuration);
|
||||
$editForm->handleRequest($request);
|
||||
|
||||
if ($editForm->isSubmitted() && $editForm->isValid()) {
|
||||
try {
|
||||
$this->activityService->saveActivity($activity);
|
||||
$activityService->saveActivity($activity);
|
||||
$this->flashSuccess('action.update.success');
|
||||
|
||||
if ($this->isGranted('view', $activity)) {
|
||||
@@ -377,7 +356,7 @@ final class ActivityController extends AbstractController
|
||||
|
||||
#[Route(path: '/{id}/delete', name: 'admin_activity_delete', methods: ['GET', 'POST'])]
|
||||
#[IsGranted('delete', 'activity')]
|
||||
public function deleteAction(Activity $activity, Request $request, ActivityStatisticService $statisticService): Response
|
||||
public function deleteAction(Activity $activity, Request $request, ActivityStatisticService $statisticService, ActivityService $activityService): Response
|
||||
{
|
||||
$stats = $statisticService->getActivityStatistics($activity);
|
||||
|
||||
@@ -404,7 +383,9 @@ final class ActivityController extends AbstractController
|
||||
|
||||
if ($deleteForm->isSubmitted() && $deleteForm->isValid()) {
|
||||
try {
|
||||
$this->repository->deleteActivity($activity, $deleteForm->get('activity')->getData());
|
||||
/** @var Activity|null $replace */
|
||||
$replace = $deleteForm->get('activity')->getData();
|
||||
$activityService->deleteActivity($activity, $replace);
|
||||
$this->flashSuccess('action.delete.success');
|
||||
} catch (Exception $ex) {
|
||||
$this->flashDeleteException($ex);
|
||||
@@ -463,9 +444,9 @@ final class ActivityController extends AbstractController
|
||||
/**
|
||||
* @return FormInterface<mixed>
|
||||
*/
|
||||
private function createEditForm(Activity $activity): FormInterface
|
||||
private function createEditForm(Activity $activity, SystemConfiguration $configuration): FormInterface
|
||||
{
|
||||
$currency = $this->configuration->getCustomerDefaultCurrency();
|
||||
$currency = $configuration->getCustomerDefaultCurrency();
|
||||
$url = $this->generateUrl('admin_activity_create');
|
||||
if ($activity->getProject()?->getId() !== null) {
|
||||
$url = $this->generateUrl('admin_activity_create_with_project', ['project' => $activity->getProject()->getId()]);
|
||||
|
||||
@@ -14,10 +14,8 @@ use App\Customer\CustomerStatisticService;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\CustomerComment;
|
||||
use App\Entity\CustomerRate;
|
||||
use App\Entity\MetaTableTypeInterface;
|
||||
use App\Entity\Team;
|
||||
use App\Event\CustomerDetailControllerEvent;
|
||||
use App\Event\CustomerMetaDefinitionEvent;
|
||||
use App\Event\CustomerMetaDisplayEvent;
|
||||
use App\Export\Spreadsheet\EntityWithMetaFieldsExporter;
|
||||
use App\Export\Spreadsheet\Writer\BinaryFileResponseWriter;
|
||||
@@ -55,17 +53,14 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
#[Route(path: '/admin/customer')]
|
||||
final class CustomerController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CustomerRepository $repository,
|
||||
private readonly EventDispatcherInterface $dispatcher
|
||||
)
|
||||
public function __construct(private readonly CustomerRepository $repository)
|
||||
{
|
||||
}
|
||||
|
||||
#[Route(path: '/', defaults: ['page' => 1], name: 'admin_customer', methods: ['GET'])]
|
||||
#[Route(path: '/page/{page}', requirements: ['page' => '[1-9]\d*'], name: 'admin_customer_paginated', methods: ['GET'])]
|
||||
#[IsGranted(new Expression("is_granted('listing', 'customer')"))]
|
||||
public function indexAction(int $page, Request $request): Response
|
||||
public function indexAction(int $page, Request $request, EventDispatcherInterface $dispatcher): Response
|
||||
{
|
||||
$query = new CustomerQuery();
|
||||
$query->loadTeams();
|
||||
@@ -78,7 +73,9 @@ final class CustomerController extends AbstractController
|
||||
}
|
||||
|
||||
$entries = $this->repository->getPagerfantaForQuery($query);
|
||||
$metaColumns = $this->findMetaColumns($query);
|
||||
$event = new CustomerMetaDisplayEvent($query, CustomerMetaDisplayEvent::CUSTOMER);
|
||||
$dispatcher->dispatch($event);
|
||||
$metaColumns = $event->getFields();
|
||||
|
||||
$table = new DataTable('customer_admin', $query);
|
||||
$table->setPagination($entries);
|
||||
@@ -130,24 +127,13 @@ final class CustomerController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MetaTableTypeInterface[]
|
||||
*/
|
||||
private function findMetaColumns(CustomerQuery $query): array
|
||||
{
|
||||
$event = new CustomerMetaDisplayEvent($query, CustomerMetaDisplayEvent::CUSTOMER);
|
||||
$this->dispatcher->dispatch($event);
|
||||
|
||||
return $event->getFields();
|
||||
}
|
||||
|
||||
#[Route(path: '/create', name: 'admin_customer_create', methods: ['GET', 'POST'])]
|
||||
#[IsGranted('create_customer')]
|
||||
public function createAction(Request $request, CustomerService $customerService): Response
|
||||
{
|
||||
$customer = $customerService->createNewCustomer('');
|
||||
|
||||
return $this->renderCustomerForm($customer, $request, true, $customerService);
|
||||
return $this->renderCustomerForm($customer, $request, $customerService);
|
||||
}
|
||||
|
||||
#[Route(path: '/{id}/permissions', name: 'admin_customer_permissions', methods: ['GET', 'POST'])]
|
||||
@@ -298,10 +284,9 @@ final class CustomerController extends AbstractController
|
||||
|
||||
#[Route(path: '/{id}/details', name: 'customer_details', methods: ['GET', 'POST'])]
|
||||
#[IsGranted('view', 'customer')]
|
||||
public function detailsAction(Customer $customer, TeamRepository $teamRepository, CustomerRateRepository $rateRepository, CustomerStatisticService $statisticService): Response
|
||||
public function detailsAction(Customer $customer, TeamRepository $teamRepository, CustomerRateRepository $rateRepository, CustomerStatisticService $statisticService, CustomerService $customerService, EventDispatcherInterface $dispatcher): Response
|
||||
{
|
||||
$event = new CustomerMetaDefinitionEvent($customer);
|
||||
$this->dispatcher->dispatch($event);
|
||||
$customerService->loadMetaFields($customer);
|
||||
|
||||
$stats = null;
|
||||
$timezone = null;
|
||||
@@ -350,7 +335,7 @@ final class CustomerController extends AbstractController
|
||||
|
||||
// additional boxes by plugins
|
||||
$event = new CustomerDetailControllerEvent($customer);
|
||||
$this->dispatcher->dispatch($event);
|
||||
$dispatcher->dispatch($event);
|
||||
$boxes = $event->getController();
|
||||
|
||||
$page = $this->createPageSetup();
|
||||
@@ -424,12 +409,14 @@ final class CustomerController extends AbstractController
|
||||
#[IsGranted('edit', 'customer')]
|
||||
public function editAction(Customer $customer, Request $request, CustomerService $customerService): Response
|
||||
{
|
||||
return $this->renderCustomerForm($customer, $request, false, $customerService);
|
||||
$customerService->loadMetaFields($customer);
|
||||
|
||||
return $this->renderCustomerForm($customer, $request, $customerService);
|
||||
}
|
||||
|
||||
#[Route(path: '/{id}/delete', name: 'admin_customer_delete', methods: ['GET', 'POST'])]
|
||||
#[IsGranted('delete', 'customer')]
|
||||
public function deleteAction(Customer $customer, Request $request, CustomerStatisticService $statisticService): Response
|
||||
public function deleteAction(Customer $customer, Request $request, CustomerStatisticService $statisticService, CustomerService $customerService): Response
|
||||
{
|
||||
$stats = $statisticService->getCustomerStatistics($customer);
|
||||
|
||||
@@ -453,7 +440,9 @@ final class CustomerController extends AbstractController
|
||||
|
||||
if ($deleteForm->isSubmitted() && $deleteForm->isValid()) {
|
||||
try {
|
||||
$this->repository->deleteCustomer($customer, $deleteForm->get('customer')->getData());
|
||||
/** @var Customer|null $replace */
|
||||
$replace = $deleteForm->get('customer')->getData();
|
||||
$customerService->deleteCustomer($customer, $replace);
|
||||
$this->flashSuccess('action.delete.success');
|
||||
} catch (\Exception $ex) {
|
||||
$this->flashDeleteException($ex);
|
||||
@@ -497,9 +486,22 @@ final class CustomerController extends AbstractController
|
||||
return $writer->getFileResponse($spreadsheet);
|
||||
}
|
||||
|
||||
private function renderCustomerForm(Customer $customer, Request $request, bool $create, CustomerService $customerService): Response
|
||||
private function renderCustomerForm(Customer $customer, Request $request, CustomerService $customerService): Response
|
||||
{
|
||||
$editForm = $this->createEditForm($customer);
|
||||
$create = ($customer->getId() === null);
|
||||
|
||||
if ($create) {
|
||||
$url = $this->generateUrl('admin_customer_create');
|
||||
} else {
|
||||
$url = $this->generateUrl('admin_customer_edit', ['id' => $customer->getId()]);
|
||||
}
|
||||
|
||||
$editForm = $this->createForm(CustomerEditForm::class, $customer, [
|
||||
'action' => $url,
|
||||
'method' => 'POST',
|
||||
'include_budget' => $this->isGranted('budget', $customer),
|
||||
'include_time' => $this->isGranted('time', $customer),
|
||||
]);
|
||||
|
||||
$editForm->handleRequest($request);
|
||||
|
||||
@@ -557,28 +559,6 @@ final class CustomerController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FormInterface<Customer>
|
||||
*/
|
||||
private function createEditForm(Customer $customer): FormInterface
|
||||
{
|
||||
$event = new CustomerMetaDefinitionEvent($customer);
|
||||
$this->dispatcher->dispatch($event);
|
||||
|
||||
if ($customer->getId() === null) {
|
||||
$url = $this->generateUrl('admin_customer_create');
|
||||
} else {
|
||||
$url = $this->generateUrl('admin_customer_edit', ['id' => $customer->getId()]);
|
||||
}
|
||||
|
||||
return $this->createForm(CustomerEditForm::class, $customer, [
|
||||
'action' => $url,
|
||||
'method' => 'POST',
|
||||
'include_budget' => $this->isGranted('budget', $customer),
|
||||
'include_time' => $this->isGranted('time', $customer),
|
||||
]);
|
||||
}
|
||||
|
||||
private function createPageSetup(): PageSetup
|
||||
{
|
||||
$page = new PageSetup('customers');
|
||||
|
||||
@@ -11,13 +11,11 @@ namespace App\Controller;
|
||||
|
||||
use App\Configuration\SystemConfiguration;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\MetaTableTypeInterface;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\ProjectComment;
|
||||
use App\Entity\ProjectRate;
|
||||
use App\Entity\Team;
|
||||
use App\Event\ProjectDetailControllerEvent;
|
||||
use App\Event\ProjectMetaDefinitionEvent;
|
||||
use App\Event\ProjectMetaDisplayEvent;
|
||||
use App\Export\Spreadsheet\EntityWithMetaFieldsExporter;
|
||||
use App\Export\Spreadsheet\Writer\BinaryFileResponseWriter;
|
||||
@@ -59,19 +57,14 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
#[Route(path: '/admin/project')]
|
||||
final class ProjectController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProjectRepository $repository,
|
||||
private readonly SystemConfiguration $configuration,
|
||||
private readonly EventDispatcherInterface $dispatcher,
|
||||
private readonly ProjectService $projectService
|
||||
)
|
||||
public function __construct(private readonly ProjectRepository $repository)
|
||||
{
|
||||
}
|
||||
|
||||
#[Route(path: '/', defaults: ['page' => 1], name: 'admin_project', methods: ['GET'])]
|
||||
#[Route(path: '/page/{page}', requirements: ['page' => '[1-9]\d*'], name: 'admin_project_paginated', methods: ['GET'])]
|
||||
#[IsGranted(new Expression("is_granted('listing', 'project')"))]
|
||||
public function indexAction(int $page, Request $request): Response
|
||||
public function indexAction(int $page, Request $request, EventDispatcherInterface $dispatcher): Response
|
||||
{
|
||||
$query = new ProjectQuery();
|
||||
$query->loadTeams();
|
||||
@@ -84,7 +77,9 @@ final class ProjectController extends AbstractController
|
||||
}
|
||||
|
||||
$entries = $this->repository->getPagerfantaForQuery($query);
|
||||
$metaColumns = $this->findMetaColumns($query);
|
||||
$event = new ProjectMetaDisplayEvent($query, ProjectMetaDisplayEvent::PROJECT);
|
||||
$dispatcher->dispatch($event);
|
||||
$metaColumns = $event->getFields();
|
||||
|
||||
$table = new DataTable('project_admin', $query);
|
||||
$table->setPagination($entries);
|
||||
@@ -130,21 +125,9 @@ final class ProjectController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ProjectQuery $query
|
||||
* @return MetaTableTypeInterface[]
|
||||
*/
|
||||
private function findMetaColumns(ProjectQuery $query): array
|
||||
{
|
||||
$event = new ProjectMetaDisplayEvent($query, ProjectMetaDisplayEvent::PROJECT);
|
||||
$this->dispatcher->dispatch($event);
|
||||
|
||||
return $event->getFields();
|
||||
}
|
||||
|
||||
#[Route(path: '/{id}/permissions', name: 'admin_project_permissions', methods: ['GET', 'POST'])]
|
||||
#[IsGranted('permissions', 'project')]
|
||||
public function teamPermissions(Project $project, Request $request): Response
|
||||
public function teamPermissions(Project $project, Request $request, ProjectService $projectService): Response
|
||||
{
|
||||
$form = $this->createForm(ProjectTeamPermissionForm::class, $project, [
|
||||
'action' => $this->generateUrl('admin_project_permissions', ['id' => $project->getId()]),
|
||||
@@ -155,7 +138,7 @@ final class ProjectController extends AbstractController
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
try {
|
||||
$this->projectService->saveProject($project);
|
||||
$projectService->saveProject($project);
|
||||
$this->flashSuccess('action.update.success');
|
||||
|
||||
if ($this->isGranted('view', $project)) {
|
||||
@@ -177,28 +160,28 @@ final class ProjectController extends AbstractController
|
||||
|
||||
#[Route(path: '/create/{customer}', name: 'admin_project_create_with_customer', methods: ['GET', 'POST'])]
|
||||
#[IsGranted('create_project')]
|
||||
public function createWithCustomerAction(Request $request, Customer $customer): Response
|
||||
public function createWithCustomerAction(Request $request, Customer $customer, ProjectService $projectService, SystemConfiguration $configuration): Response
|
||||
{
|
||||
return $this->createProject($request, $customer);
|
||||
return $this->createProject($request, $projectService, $configuration, $customer);
|
||||
}
|
||||
|
||||
#[Route(path: '/create', name: 'admin_project_create', methods: ['GET', 'POST'])]
|
||||
#[IsGranted('create_project')]
|
||||
public function createAction(Request $request): Response
|
||||
public function createAction(Request $request, ProjectService $projectService, SystemConfiguration $configuration): Response
|
||||
{
|
||||
return $this->createProject($request, null);
|
||||
return $this->createProject($request, $projectService, $configuration, null);
|
||||
}
|
||||
|
||||
private function createProject(Request $request, ?Customer $customer = null): Response
|
||||
private function createProject(Request $request, ProjectService $projectService, SystemConfiguration $configuration, ?Customer $customer = null): Response
|
||||
{
|
||||
$project = $this->projectService->createNewProject($customer);
|
||||
$project = $projectService->createNewProject($customer);
|
||||
|
||||
$editForm = $this->createEditForm($project);
|
||||
$editForm = $this->createEditForm($project, $configuration->getCustomerDefaultCurrency());
|
||||
$editForm->handleRequest($request);
|
||||
|
||||
if ($editForm->isSubmitted() && $editForm->isValid()) {
|
||||
try {
|
||||
$this->projectService->saveProject($project, new Context($this->getUser()));
|
||||
$projectService->saveProject($project, new Context($this->getUser()));
|
||||
$this->flashSuccess('action.update.success');
|
||||
|
||||
return $this->redirectToRouteAfterCreate('project_details', ['id' => $project->getId()]);
|
||||
@@ -330,10 +313,9 @@ final class ProjectController extends AbstractController
|
||||
|
||||
#[Route(path: '/{id}/details', name: 'project_details', methods: ['GET', 'POST'])]
|
||||
#[IsGranted('view', 'project')]
|
||||
public function detailsAction(Project $project, TeamRepository $teamRepository, ProjectRateRepository $rateRepository, ProjectStatisticService $statisticService, CsrfTokenManagerInterface $csrfTokenManager): Response
|
||||
public function detailsAction(Project $project, TeamRepository $teamRepository, ProjectRateRepository $rateRepository, ProjectStatisticService $statisticService, ProjectService $projectService, CsrfTokenManagerInterface $csrfTokenManager, EventDispatcherInterface $dispatcher): Response
|
||||
{
|
||||
$event = new ProjectMetaDefinitionEvent($project);
|
||||
$this->dispatcher->dispatch($event);
|
||||
$projectService->loadMetaFields($project);
|
||||
|
||||
$stats = null;
|
||||
$defaultTeam = null;
|
||||
@@ -377,7 +359,7 @@ final class ProjectController extends AbstractController
|
||||
|
||||
// additional boxes by plugins
|
||||
$event = new ProjectDetailControllerEvent($project);
|
||||
$this->dispatcher->dispatch($event);
|
||||
$dispatcher->dispatch($event);
|
||||
$boxes = $event->getController();
|
||||
|
||||
$page = $this->createPageSetup();
|
||||
@@ -448,14 +430,16 @@ final class ProjectController extends AbstractController
|
||||
|
||||
#[Route(path: '/{id}/edit', name: 'admin_project_edit', methods: ['GET', 'POST'])]
|
||||
#[IsGranted('edit', 'project')]
|
||||
public function editAction(Project $project, Request $request): Response
|
||||
public function editAction(Project $project, Request $request, ProjectService $projectService, SystemConfiguration $configuration): Response
|
||||
{
|
||||
$editForm = $this->createEditForm($project);
|
||||
$projectService->loadMetaFields($project);
|
||||
|
||||
$editForm = $this->createEditForm($project, $configuration->getCustomerDefaultCurrency());
|
||||
$editForm->handleRequest($request);
|
||||
|
||||
if ($editForm->isSubmitted() && $editForm->isValid()) {
|
||||
try {
|
||||
$this->projectService->saveProject($project);
|
||||
$projectService->saveProject($project);
|
||||
$this->flashSuccess('action.update.success');
|
||||
|
||||
if ($this->isGranted('view', $project)) {
|
||||
@@ -502,7 +486,7 @@ final class ProjectController extends AbstractController
|
||||
|
||||
#[Route(path: '/{id}/delete', name: 'admin_project_delete', methods: ['GET', 'POST'])]
|
||||
#[IsGranted('delete', 'project')]
|
||||
public function deleteAction(Project $project, Request $request, ProjectStatisticService $statisticService): Response
|
||||
public function deleteAction(Project $project, Request $request, ProjectStatisticService $statisticService, ProjectService $projectService): Response
|
||||
{
|
||||
$stats = $statisticService->getProjectStatistics($project);
|
||||
|
||||
@@ -527,7 +511,9 @@ final class ProjectController extends AbstractController
|
||||
|
||||
if ($deleteForm->isSubmitted() && $deleteForm->isValid()) {
|
||||
try {
|
||||
$this->repository->deleteProject($project, $deleteForm->get('project')->getData());
|
||||
/** @var Project|null $replace */
|
||||
$replace = $deleteForm->get('project')->getData();
|
||||
$projectService->deleteProject($project, $replace);
|
||||
$this->flashSuccess('action.delete.success');
|
||||
} catch (\Exception $ex) {
|
||||
$this->flashDeleteException($ex);
|
||||
@@ -592,12 +578,8 @@ final class ProjectController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
private function createEditForm(Project $project): FormInterface
|
||||
private function createEditForm(Project $project, string $currency): FormInterface
|
||||
{
|
||||
$event = new ProjectMetaDefinitionEvent($project);
|
||||
$this->dispatcher->dispatch($event);
|
||||
|
||||
$currency = $this->configuration->getCustomerDefaultCurrency();
|
||||
$url = $this->generateUrl('admin_project_create');
|
||||
|
||||
if ($project->getId() !== null) {
|
||||
|
||||
@@ -45,6 +45,11 @@ final class CustomerService
|
||||
return $timezone;
|
||||
}
|
||||
|
||||
public function loadMetaFields(Customer $customer): void
|
||||
{
|
||||
$this->dispatcher->dispatch(new CustomerMetaDefinitionEvent($customer));
|
||||
}
|
||||
|
||||
public function createNewCustomer(string $name): Customer
|
||||
{
|
||||
$customer = new Customer($name);
|
||||
@@ -53,7 +58,7 @@ final class CustomerService
|
||||
$customer->setCurrency($this->configuration->getCustomerDefaultCurrency());
|
||||
$customer->setNumber($this->calculateNextCustomerNumber());
|
||||
|
||||
$this->dispatcher->dispatch(new CustomerMetaDefinitionEvent($customer));
|
||||
$this->loadMetaFields($customer);
|
||||
$this->dispatcher->dispatch(new CustomerCreateEvent($customer));
|
||||
|
||||
return $customer;
|
||||
@@ -86,10 +91,10 @@ final class CustomerService
|
||||
return $customer;
|
||||
}
|
||||
|
||||
public function deleteCustomer(Customer $customer): void
|
||||
public function deleteCustomer(Customer $customer, ?Customer $replace = null): void
|
||||
{
|
||||
$this->dispatcher->dispatch(new CustomerDeleteEvent($customer));
|
||||
$this->repository->deleteCustomer($customer);
|
||||
$this->dispatcher->dispatch(new CustomerDeleteEvent($customer, $replace));
|
||||
$this->repository->deleteCustomer($customer, $replace);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,7 +31,7 @@ final class InvoiceFixtures extends Fixture
|
||||
{
|
||||
$faker = Factory::create('en_US');
|
||||
/** @var non-empty-array<Customer> $customers */
|
||||
$customers = $manager->getRepository(Customer::class)->findAll();
|
||||
$customers = $manager->getRepository(Customer::class)->findBy(['visible' => true]);
|
||||
|
||||
foreach ($this->getInvoiceConfigs($faker) as $invoiceConfig) {
|
||||
// name, title, renderer, calculator, numberGenerator, company, vat, dueDays, address, paymentTerms
|
||||
|
||||
@@ -62,10 +62,12 @@ final class TimesheetFixtures extends Fixture implements FixtureGroupInterface
|
||||
$all = 0;
|
||||
|
||||
foreach ($allUser as $user) {
|
||||
// reload, because the manager might have been cleared
|
||||
$user = $manager->find(User::class, $user->getId());
|
||||
// random amount of timesheet entries for every user
|
||||
$timesheetForUser = rand(self::MIN_TIMESHEETS_PER_USER, self::MAX_TIMESHEETS_PER_USER);
|
||||
|
||||
// load on each round, because the manager might have been cleared
|
||||
$activities = $this->getAllActivities($manager);
|
||||
$projects = $this->getAllProjects($manager);
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ use Symfony\Contracts\EventDispatcher\Event;
|
||||
*/
|
||||
abstract class AbstractActivityEvent extends Event
|
||||
{
|
||||
public function __construct(private Activity $activity)
|
||||
public function __construct(private readonly Activity $activity)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ use Symfony\Contracts\EventDispatcher\Event;
|
||||
*/
|
||||
abstract class AbstractCustomerEvent extends Event
|
||||
{
|
||||
public function __construct(private Customer $customer)
|
||||
public function __construct(private readonly Customer $customer)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ use Symfony\Contracts\EventDispatcher\Event;
|
||||
*/
|
||||
abstract class AbstractProjectEvent extends Event
|
||||
{
|
||||
public function __construct(private Project $project)
|
||||
public function __construct(private readonly Project $project)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,19 @@
|
||||
|
||||
namespace App\Event;
|
||||
|
||||
use App\Entity\Activity;
|
||||
use App\Webhook\Attribute\AsWebhook;
|
||||
|
||||
#[AsWebhook(name: 'activity.deleted', description: 'Triggered right before an activity will be deleted', payload: 'object.getActivity()')]
|
||||
final class ActivityDeleteEvent extends AbstractActivityEvent
|
||||
{
|
||||
public function __construct(Activity $activity, private readonly ?Activity $replacementActivity = null)
|
||||
{
|
||||
parent::__construct($activity);
|
||||
}
|
||||
|
||||
public function getReplacementActivity(): ?Activity
|
||||
{
|
||||
return $this->replacementActivity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use Symfony\Contracts\EventDispatcher\Event;
|
||||
*/
|
||||
final class ActivityMetaDefinitionEvent extends Event
|
||||
{
|
||||
public function __construct(private Activity $entity)
|
||||
public function __construct(private readonly Activity $entity)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,19 @@
|
||||
|
||||
namespace App\Event;
|
||||
|
||||
use App\Entity\Customer;
|
||||
use App\Webhook\Attribute\AsWebhook;
|
||||
|
||||
#[AsWebhook(name: 'customer.deleted', description: 'Triggered right before a customer will be deleted', payload: 'object.getCustomer()')]
|
||||
final class CustomerDeleteEvent extends AbstractCustomerEvent
|
||||
{
|
||||
public function __construct(Customer $customer, private readonly ?Customer $replacementCustomer = null)
|
||||
{
|
||||
parent::__construct($customer);
|
||||
}
|
||||
|
||||
public function getReplacementCustomer(): ?Customer
|
||||
{
|
||||
return $this->replacementCustomer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use Symfony\Contracts\EventDispatcher\Event;
|
||||
*/
|
||||
final class CustomerMetaDefinitionEvent extends Event
|
||||
{
|
||||
public function __construct(private Customer $entity)
|
||||
public function __construct(private readonly Customer $entity)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,19 @@
|
||||
|
||||
namespace App\Event;
|
||||
|
||||
use App\Entity\Project;
|
||||
use App\Webhook\Attribute\AsWebhook;
|
||||
|
||||
#[AsWebhook(name: 'project.deleted', description: 'Triggered right before a project will be deleted', payload: 'object.getProject()')]
|
||||
final class ProjectDeleteEvent extends AbstractProjectEvent
|
||||
{
|
||||
public function __construct(Project $project, private readonly ?Project $replacementProject = null)
|
||||
{
|
||||
parent::__construct($project);
|
||||
}
|
||||
|
||||
public function getReplacementProject(): ?Project
|
||||
{
|
||||
return $this->replacementProject;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use Symfony\Contracts\EventDispatcher\Event;
|
||||
*/
|
||||
final class ProjectMetaDefinitionEvent extends Event
|
||||
{
|
||||
public function __construct(private Project $entity)
|
||||
public function __construct(private readonly Project $entity)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ final class KimaiMailer implements MailerInterface
|
||||
{
|
||||
}
|
||||
|
||||
public function send(RawMessage $message, Envelope $envelope = null): void
|
||||
public function send(RawMessage $message, ?Envelope $envelope = null): void
|
||||
{
|
||||
if (!$message instanceof Email) {
|
||||
$email = new Email();
|
||||
@@ -43,7 +43,7 @@ final class KimaiMailer implements MailerInterface
|
||||
$this->mailer->send($message);
|
||||
}
|
||||
|
||||
public function sendToUser(User $user, Email $message, Envelope $envelope = null): void
|
||||
public function sendToUser(User $user, Email $message, ?Envelope $envelope = null): void
|
||||
{
|
||||
if (!$user->isEnabled() || $user->getEmail() === null) {
|
||||
return;
|
||||
|
||||
@@ -41,6 +41,11 @@ final class ProjectService
|
||||
{
|
||||
}
|
||||
|
||||
public function loadMetaFields(Project $project): void
|
||||
{
|
||||
$this->dispatcher->dispatch(new ProjectMetaDefinitionEvent($project));
|
||||
}
|
||||
|
||||
public function createNewProject(?Customer $customer = null): Project
|
||||
{
|
||||
$project = new Project();
|
||||
@@ -50,7 +55,7 @@ final class ProjectService
|
||||
$project->setCustomer($customer);
|
||||
}
|
||||
|
||||
$this->dispatcher->dispatch(new ProjectMetaDefinitionEvent($project));
|
||||
$this->loadMetaFields($project);
|
||||
$this->dispatcher->dispatch(new ProjectCreateEvent($project));
|
||||
|
||||
return $project;
|
||||
@@ -90,10 +95,10 @@ final class ProjectService
|
||||
return $project;
|
||||
}
|
||||
|
||||
public function deleteProject(Project $project): void
|
||||
public function deleteProject(Project $project, ?Project $replace = null): void
|
||||
{
|
||||
$this->dispatcher->dispatch(new ProjectDeleteEvent($project));
|
||||
$this->repository->deleteProject($project);
|
||||
$this->dispatcher->dispatch(new ProjectDeleteEvent($project, $replace));
|
||||
$this->repository->deleteProject($project, $replace);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,7 +26,6 @@ use App\Repository\Search\SearchHelper;
|
||||
use App\Utils\Pagination;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\Exception\ORMException;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Query\Expr\Andx;
|
||||
@@ -414,7 +413,7 @@ class ActivityRepository extends EntityRepository
|
||||
$em->remove($delete);
|
||||
$em->flush();
|
||||
$em->commit();
|
||||
} catch (ORMException $ex) {
|
||||
} catch (\Exception $ex) {
|
||||
$em->rollback();
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ use App\Repository\Search\SearchHelper;
|
||||
use App\Utils\Pagination;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\Exception\ORMException;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Query\Expr\Andx;
|
||||
@@ -335,7 +334,7 @@ class CustomerRepository extends EntityRepository
|
||||
$em->remove($delete);
|
||||
$em->flush();
|
||||
$em->commit();
|
||||
} catch (ORMException $ex) {
|
||||
} catch (\Exception $ex) {
|
||||
$em->rollback();
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ use DateTime;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\Exception\ORMException;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Query\Expr\Andx;
|
||||
@@ -415,7 +414,7 @@ class ProjectRepository extends EntityRepository
|
||||
$em->remove($delete);
|
||||
$em->flush();
|
||||
$em->commit();
|
||||
} catch (ORMException $ex) {
|
||||
} catch (\Exception $ex) {
|
||||
$em->rollback();
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ namespace App\Repository;
|
||||
|
||||
use App\Entity\Role;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\Exception\ORMException;
|
||||
|
||||
/**
|
||||
* @extends EntityRepository<Role>
|
||||
@@ -35,7 +34,7 @@ class RoleRepository extends EntityRepository
|
||||
$em->remove($role);
|
||||
$em->flush();
|
||||
$em->commit();
|
||||
} catch (ORMException $ex) {
|
||||
} catch (\Exception $ex) {
|
||||
$em->rollback();
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
@@ -22,15 +22,15 @@ use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterfa
|
||||
final class LoginManager
|
||||
{
|
||||
public function __construct(
|
||||
private TokenStorageInterface $tokenStorage,
|
||||
private UserChecker $userChecker,
|
||||
private SessionAuthenticationStrategyInterface $sessionStrategy,
|
||||
private RequestStack $requestStack,
|
||||
private EventDispatcherInterface $eventDispatcher,
|
||||
private readonly TokenStorageInterface $tokenStorage,
|
||||
private readonly UserChecker $userChecker,
|
||||
private readonly SessionAuthenticationStrategyInterface $sessionStrategy,
|
||||
private readonly RequestStack $requestStack,
|
||||
private readonly EventDispatcherInterface $eventDispatcher,
|
||||
) {
|
||||
}
|
||||
|
||||
public function logInUser(User $user, Response $response = null)
|
||||
public function logInUser(User $user, ?Response $response = null): void
|
||||
{
|
||||
$this->userChecker->checkPreAuth($user);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user