}
35 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "@symfony/webpack-encore": "^0.31.0",
4 | "FOSJsRoutingBundle": "FriendsOfSymfony/FOSJsRoutingBundle#2.5.4",
5 | "babel-eslint": "^10.1.0",
6 | "bootstrap": "^4.4.1",
7 | "bootstrap-select": "^1.13.12",
8 | "browser-update": "^3.3.16",
9 | "core-js": "^3.0.0",
10 | "daterangepicker": "^3.0.5",
11 | "eslint": "^7.12.1",
12 | "eslint-config-prettier": "^6.10.1",
13 | "eslint-plugin-prettier": "^3.1.2",
14 | "font-awesome": "^4.7.0",
15 | "jquery": "^3.5.1",
16 | "moment": "2.29.1",
17 | "node-sass": "^5.0.0",
18 | "popper.js": "^1.16.1",
19 | "prettier": "^2.0.2",
20 | "regenerator-runtime": "^0.13.2",
21 | "sass-loader": "^10.0.5",
22 | "stylelint": "^13.2.1",
23 | "stylelint-config-standard": "^20.0.0",
24 | "stylelint-scss": "^3.16.0",
25 | "webpack-notifier": "^1.6.0"
26 | },
27 | "resolutions": {
28 | "moment": "2.24.0"
29 | },
30 | "license": "GPL-3.0",
31 | "private": false,
32 | "scripts": {
33 | "dev-server": "encore dev-server",
34 | "dev": "encore dev",
35 | "watch": "encore dev --watch",
36 | "build": "encore production --progress",
37 | "lint": "npx eslint 'assets/js/**/*.js' --cache --max-warnings=0",
38 | "lint:fix": "npx eslint 'assets/js/**/*.js' --cache --max-warnings=0 --fix",
39 | "lint:css": "npx stylelint 'assets/css/**/*.{css,scss}' --cache --max-warnings=0",
40 | "lint:css:fix": "npx stylelint 'assets/css/**/*.{css,scss}' --cache --max-warnings=0 --fix"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/phpcs.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | src/
24 | tests/
25 |
26 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | includes:
2 | - vendor/phpstan/phpstan-symfony/extension.neon
3 | - vendor/phpstan/phpstan-doctrine/extension.neon
4 |
5 | parameters:
6 | tmpDir: var/cache/phpstan
7 | excludes_analyse:
8 | - config/
9 | - src/Migrations/
10 | - public/
11 | - features/
12 | - var/
13 | - node_modules/
14 | - vendor/
15 | symfony:
16 | container_xml_path: %rootDir%/../../../var/cache/test/App_KernelTestDebugContainer.xml
17 | doctrine:
18 | repositoryClass: Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository
19 | objectManagerLoader: tests/ObjectManager.php
20 | inferPrivatePropertyTypeFromConstructor: true
21 | checkMissingIterableValueType: false
22 | checkGenericClassInNonGenericObjectType: false
23 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 | src
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | tests
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crf-devs/resop/cf3a8239a29c40da4d9f0cc603975626d5106077/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.php:
--------------------------------------------------------------------------------
1 | handle($request);
28 | $response->send();
29 | $kernel->terminate($request, $response);
30 |
--------------------------------------------------------------------------------
/public/ping.php:
--------------------------------------------------------------------------------
1 | getUser();
21 | $organization = $parameters['organization'] ?? null;
22 | $parameters = array_merge($parameters, ['organization' => $user->getId()]);
23 | if (null !== $organization && $user->getId() !== $organization) {
24 | $parameters['organizationId'] = $organization;
25 | }
26 | }
27 |
28 | return parent::generateUrl($route, $parameters, $referenceType);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Controller/Organization/AssetType/AssetTypeListController.php:
--------------------------------------------------------------------------------
1 | assetTypeRepository = $assetTypeRepository;
25 | }
26 |
27 | public function __invoke(): Response
28 | {
29 | /** @var Organization $organization */
30 | $organization = $this->getUser();
31 |
32 | return $this->render('organization/assetType/list.html.twig', [
33 | 'assetTypes' => $this->assetTypeRepository->findByOrganization($organization),
34 | ]);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Controller/Organization/Children/EditController.php:
--------------------------------------------------------------------------------
1 | }/edit", name="app_organization_edit", methods={"GET", "POST"})
18 | * @IsGranted(OrganizationVoter::CAN_MANAGE, subject="object")
19 | */
20 | class EditController extends AbstractOrganizationController
21 | {
22 | public function __invoke(Request $request, Organization $object): Response
23 | {
24 | $form = $this->createForm(OrganizationType::class, $object);
25 | $form->handleRequest($request);
26 |
27 | if ($form->isSubmitted() && $form->isValid()) {
28 | $flashMessage = 'La structure a été mise à jour avec succès.';
29 |
30 | $entityManager = $this->getDoctrine()->getManager();
31 | $entityManager->persist($object);
32 | $entityManager->flush();
33 |
34 | $this->addFlash('success', $flashMessage);
35 |
36 | return $this->redirectToRoute('app_organization_list');
37 | }
38 |
39 | return $this->render(
40 | 'organization/edit.html.twig',
41 | [
42 | 'organization' => $object,
43 | 'form' => $form->createView(),
44 | ]
45 | )->setStatusCode($form->isSubmitted() ? Response::HTTP_BAD_REQUEST : Response::HTTP_OK);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Controller/Organization/Children/ListController.php:
--------------------------------------------------------------------------------
1 | organizationRepository = $organizationRepository;
24 | }
25 |
26 | public function __invoke(): Response
27 | {
28 | $organization = $this->getUser();
29 | if (!$organization instanceof Organization || !$organization->isParent()) {
30 | throw new AccessDeniedException();
31 | }
32 |
33 | $organizations = $this->organizationRepository->findBy(['parent' => $organization], ['name' => 'ASC']);
34 |
35 | return $this->render('organization/list.html.twig', [
36 | 'organizations' => $organizations,
37 | ]);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Controller/Organization/CommissionableAsset/AssetShowModalController.php:
--------------------------------------------------------------------------------
1 | }/modal", name="app_organization_asset_show_modal", methods={"GET", "POST"})
14 | */
15 | class AssetShowModalController extends AbstractController
16 | {
17 | public function __invoke(CommissionableAsset $asset): Response
18 | {
19 | return $this->render('organization/commissionable_asset/show-modal-content.html.twig', ['asset' => $asset]);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Controller/Organization/CommissionableAsset/PreAddAssetController.php:
--------------------------------------------------------------------------------
1 | createForm(
24 | PreAddAssetType::class,
25 | ['organizationId' => $organization->getId()],
26 | ['organization' => $organization]
27 | )->createView();
28 |
29 | return $this->render('organization/commissionable_asset/preAdd.html.twig', [
30 | 'form' => $form,
31 | 'organization' => $organization,
32 | ]);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Controller/Organization/DashboardController.php:
--------------------------------------------------------------------------------
1 | }", name="app_organization_dashboard", methods={"GET"})
15 | */
16 | final class DashboardController extends AbstractController
17 | {
18 | public function __invoke(): Response
19 | {
20 | return $this->render('organization/home.html.twig');
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Controller/Organization/Forecast/PlanningForecastController.php:
--------------------------------------------------------------------------------
1 | planningDomain = $planningDomain;
28 | $this->missionTypeForecastDomain = $missionTypeForecastDomain;
29 | }
30 |
31 | public function __invoke(Request $request): Response
32 | {
33 | $form = $this->planningDomain->generateForm(PlanningForecastType::class);
34 | $filters = $this->planningDomain->generateFilters($form);
35 |
36 | return $this->render('organization/forecast/forecast.html.twig', [
37 | 'filters' => $filters,
38 | 'form' => $form->createView(),
39 | 'forecast' => $this->missionTypeForecastDomain->calculatePerMissionTypes($filters),
40 | ]);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Controller/Organization/IndexController.php:
--------------------------------------------------------------------------------
1 | redirectToRoute('app_organization_dashboard');
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Controller/Organization/Mission/AddUserAjaxController.php:
--------------------------------------------------------------------------------
1 | }/users/add/{userToAdd<\d+>}", name="app_organization_mission_add_user", methods={"POST"})
16 | */
17 | class AddUserAjaxController extends AbstractController
18 | {
19 | public function __invoke(Mission $mission, User $userToAdd): Response
20 | {
21 | if (!$mission->users->contains($userToAdd)) {
22 | $mission->users->add($userToAdd);
23 | }
24 |
25 | $this->getDoctrine()->getManager()->flush();
26 |
27 | return new JsonResponse();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Controller/Organization/Mission/MissionModalController.php:
--------------------------------------------------------------------------------
1 | }/modal", name="app_organization_mission_modal", methods={"GET"}, options={"expose"=true})
14 | */
15 | class MissionModalController extends AbstractController
16 | {
17 | public function __invoke(Mission $mission): Response
18 | {
19 | return $this->render('organization/mission/show-modal-content.html.twig', [
20 | 'mission' => $mission,
21 | ]);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Controller/Organization/Mission/MissionsFindByFiltersController.php:
--------------------------------------------------------------------------------
1 | planningDomain = $planningDomain;
25 | $this->missionRepository = $missionRepository;
26 | $this->serializer = $serializer;
27 | }
28 |
29 | public function __invoke(): JsonResponse
30 | {
31 | $form = $this->planningDomain->generateForm();
32 | $filters = $this->planningDomain->generateFilters($form);
33 |
34 | $data = $this->missionRepository->findByPlanningFilters($filters, $this->planningDomain->getAvailableResources($filters, true));
35 |
36 | // TODO Paginate
37 | return new JsonResponse($this->serializer->serialize($data, 'json', ['groups' => ['mission:ajax']]), 200, [], true);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Controller/Organization/MissionType/MissionTypeDeleteController.php:
--------------------------------------------------------------------------------
1 | translator = $translator;
27 | $this->entityManager = $entityManager;
28 | }
29 |
30 | public function __invoke(MissionType $missionType): RedirectResponse
31 | {
32 | $this->entityManager->remove($missionType);
33 | $this->entityManager->flush();
34 |
35 | $this->addFlash('success', 'organization.missionType.deleteSuccessMessage');
36 |
37 | return $this->redirectToRoute('app_organization_mission_type_index');
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Controller/Organization/Planning/PlanningCheckLastUpdateController.php:
--------------------------------------------------------------------------------
1 | planningDomain = $planningDomain;
22 | }
23 |
24 | public function __invoke(Request $request): JsonResponse
25 | {
26 | $form = $this->planningDomain->generateForm();
27 | $filters = $this->planningDomain->generateFilters($form);
28 |
29 | return new JsonResponse($this->planningDomain->generateLastUpdateAndCount($filters));
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Controller/Organization/SearchController.php:
--------------------------------------------------------------------------------
1 | }/search", name="app_organization_search", methods={"GET"}, requirements={"id"="\d+"})
19 | */
20 | final class SearchController extends AbstractOrganizationController
21 | {
22 | public function __invoke(Request $request, UserRepository $userRepository, CommissionableAssetRepository $commissionableAssetRepository, OrganizationRepository $organizationRepository): Response
23 | {
24 | /** @var Organization $organization */
25 | $organization = $this->getUser();
26 |
27 | /** @var string $query */
28 | $query = preg_replace('/\s+/', ' ', trim((string) $request->query->get('query')));
29 | if (empty($query)) {
30 | return $this->redirectToRoute('app_organization_dashboard');
31 | }
32 |
33 | return $this->render('organization/search.html.twig', [
34 | 'query' => $query,
35 | 'users' => $userRepository->search($organization, $query),
36 | 'assets' => $commissionableAssetRepository->search($organization, $query),
37 | ]);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Controller/Organization/Security/LogoutController.php:
--------------------------------------------------------------------------------
1 | }/missions/add/modal", name="app_organization_user_add_to_mission_modal", methods={"GET"})
17 | */
18 | class AddToMissionModalController extends AbstractController
19 | {
20 | private PlanningDomain $planningDomain;
21 | private MissionRepository $missionRepository;
22 |
23 | public function __construct(PlanningDomain $planningDomain, MissionRepository $missionRepository)
24 | {
25 | $this->planningDomain = $planningDomain;
26 | $this->missionRepository = $missionRepository;
27 | }
28 |
29 | public function __invoke(User $userToAdd): Response
30 | {
31 | $form = $this->planningDomain->generateForm(MissionsSearchType::class);
32 | $filters = $form->getData();
33 |
34 | return $this->render('organization/mission/add-to-mission-modal-content.html.twig', [
35 | 'userToAdd' => $userToAdd,
36 | 'filters' => $filters,
37 | 'form' => $form->createView(),
38 | 'missions' => $this->missionRepository->findByFilters($filters),
39 | ]);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Controller/Organization/User/UserDeleteController.php:
--------------------------------------------------------------------------------
1 | }/delete", name="app_organization_user_delete", methods={"GET"})
18 | * @IsGranted(UserVoter::CAN_EDIT, subject="userToDelete")
19 | */
20 | class UserDeleteController extends AbstractOrganizationController
21 | {
22 | private UserAvailabilityRepository $userAvailabilityRepository;
23 |
24 | public function __construct(UserAvailabilityRepository $userAvailabilityRepository)
25 | {
26 | $this->userAvailabilityRepository = $userAvailabilityRepository;
27 | }
28 |
29 | public function __invoke(EntityManagerInterface $entityManager, User $userToDelete): RedirectResponse
30 | {
31 | $entityManager->beginTransaction();
32 | $this->userAvailabilityRepository->deleteByOwner($userToDelete);
33 | $entityManager->remove($userToDelete);
34 | $entityManager->flush();
35 | $entityManager->commit();
36 |
37 | $this->addFlash('success', 'Le bénévole a été supprimé avec succès.');
38 |
39 | return $this->redirectToRoute('app_organization_user_list', ['organization' => $userToDelete->getNotNullOrganization()->id]);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Controller/Organization/User/UserEditController.php:
--------------------------------------------------------------------------------
1 | }/edit", name="app_organization_user_edit", methods={"GET", "POST"})
18 | * @IsGranted(UserVoter::CAN_EDIT, subject="userToEdit")
19 | */
20 | class UserEditController extends AbstractOrganizationController
21 | {
22 | public function __invoke(Request $request, User $userToEdit): Response
23 | {
24 | $form = $this
25 | ->createForm(UserType::class, $userToEdit, ['display_type' => UserType::DISPLAY_ORGANIZATION])
26 | ->handleRequest($request);
27 |
28 | if ($form->isSubmitted() && $form->isValid()) {
29 | $entityManager = $this->getDoctrine()->getManager();
30 | $entityManager->persist($userToEdit);
31 | $entityManager->flush();
32 |
33 | $this->addFlash('success', 'Les informations ont été mises à jour avec succès.');
34 |
35 | return $this->redirectToRoute('app_organization_user_list', ['organization' => $userToEdit->getNotNullOrganization()->id]);
36 | }
37 |
38 | return $this->render('organization/user/edit.html.twig', [
39 | 'user' => $userToEdit,
40 | 'form' => $form->createView(),
41 | ])->setStatusCode($form->isSubmitted() ? Response::HTTP_BAD_REQUEST : Response::HTTP_OK);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Controller/Organization/User/UserMissionsListController.php:
--------------------------------------------------------------------------------
1 | }/missions", name="app_organization_user_missions_list", methods={"GET"})
14 | */
15 | class UserMissionsListController extends AbstractController
16 | {
17 | public function __invoke(User $item): Response
18 | {
19 | return $this->render('organization/user/missions_list.html.twig', [
20 | 'user' => $item,
21 | 'missions' => $item->missions,
22 | ]);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Controller/Organization/User/UserShowModalController.php:
--------------------------------------------------------------------------------
1 | }/modal", name="app_organization_user_show_modal", methods={"GET"})
14 | */
15 | class UserShowModalController extends AbstractController
16 | {
17 | public function __invoke(User $userToShow): Response
18 | {
19 | return $this->render('organization/user/show-modal-content.html.twig', [
20 | 'user' => $userToShow,
21 | ]);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Controller/User/Availability/MissionModalController.php:
--------------------------------------------------------------------------------
1 | }/modal", name="app_user_availability_mission_modal", methods={"GET"}, options={"expose"=true})
15 | * @Security("mission.users.contains(user)")
16 | */
17 | class MissionModalController extends AbstractController
18 | {
19 | public function __invoke(Mission $mission): Response
20 | {
21 | return $this->render('organization/mission/show-modal-content.html.twig', [
22 | 'mission' => $mission,
23 | 'showLinks' => false,
24 | ]);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Controller/User/Availability/MissionsFindController.php:
--------------------------------------------------------------------------------
1 | ?}/missions", name="app_user_availability_missions_week", methods={"GET"})
18 | */
19 | class MissionsFindController extends AbstractController
20 | {
21 | use UserAvailabityControllerTrait;
22 |
23 | private MissionRepository $missionRepository;
24 | private SerializerInterface $serializer;
25 |
26 | public function __construct(MissionRepository $missionRepository, SerializerInterface $serializer)
27 | {
28 | $this->missionRepository = $missionRepository;
29 | $this->serializer = $serializer;
30 | }
31 |
32 | public function __invoke(Request $request): JsonResponse
33 | {
34 | $user = $this->getUser();
35 | if (!$user instanceof User) {
36 | throw $this->createAccessDeniedException();
37 | }
38 |
39 | [$start, $end] = $this->getDatesByWeek($request->attributes->get('week'));
40 |
41 | $data = $this->missionRepository->findByPlanningFilters(['from' => $start, 'to' => $end], [[(int) $user->getId()], []]);
42 |
43 | return new JsonResponse($this->serializer->serialize($data, 'json', ['groups' => ['mission:ajax']]), 200, [], true);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Controller/User/Availability/UserAvailabityControllerTrait.php:
--------------------------------------------------------------------------------
1 | add(new \DateInterval('P7D'));
20 |
21 | return [$start, $end];
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Controller/User/Security/LogoutController.php:
--------------------------------------------------------------------------------
1 | add(\DateInterval::createFromDateString($slotInterval));
22 |
23 | if ($resource instanceof User) {
24 | return new UserAvailability(null, $resource, $startDate, $endTime, $status);
25 | }
26 |
27 | if ($resource instanceof CommissionableAsset) {
28 | return new CommissionableAssetAvailability(null, $resource, $startDate, $endTime, $status);
29 | }
30 |
31 | throw new \LogicException(sprintf('Not handled resource of type "%s"', \get_class($resource)));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/DataFixtures/Faker/Provider/MissionTypeProvider.php:
--------------------------------------------------------------------------------
1 | skillSetDomain = $skillSetDomain;
18 | }
19 |
20 | public function randomSkillRequirement(): array
21 | {
22 | return array_map(
23 | static function (string $skill) {
24 | return [
25 | 'skill' => $skill,
26 | 'number' => Faker::numberBetween(1, 5),
27 | ];
28 | },
29 | Faker::randomElements($this->skillSetDomain->getSkillSetKeys(), random_int(1, 3))
30 | );
31 | }
32 |
33 | public function randomAssetTypeRequirement(AssetType ...$assetTypes): array
34 | {
35 | return array_map(
36 | static function (AssetType $assetType) {
37 | return [
38 | 'type' => $assetType->id,
39 | 'number' => Faker::numberBetween(1, 5),
40 | ];
41 | },
42 | Faker::randomElements($assetTypes, random_int(1, 2))
43 | );
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/DataFixtures/Faker/Provider/UserProvider.php:
--------------------------------------------------------------------------------
1 | skillSetDomain = $skillSetDomain;
20 | $this->phoneNumberUtil = $phoneNumberUtil;
21 | }
22 |
23 | public function randomSkillSet(): array
24 | {
25 | return Faker::randomElements($this->skillSetDomain->getSkillSetKeys(), random_int(1, 3));
26 | }
27 |
28 | public function phoneNumberObject(string $phoneNumber, ?string $defaultRegion = 'FR'): PhoneNumber
29 | {
30 | return $this->phoneNumberUtil->parse($phoneNumber, $defaultRegion);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Entity/AvailabilitableInterface.php:
--------------------------------------------------------------------------------
1 | initialize($id, $startTime, $endTime, $status);
35 | $this->asset = $asset;
36 | }
37 |
38 | public function getOwner(): AvailabilitableInterface
39 | {
40 | return $this->asset;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Entity/UserAvailability.php:
--------------------------------------------------------------------------------
1 | initialize($id, $startTime, $endTime, $status);
35 | $this->user = $user;
36 | }
37 |
38 | public function getOwner(): AvailabilitableInterface
39 | {
40 | return $this->user;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Entity/UserPasswordInterface.php:
--------------------------------------------------------------------------------
1 | skillSetDomain = $skillSetDomain;
17 | }
18 |
19 | public function prePersist(User $user): void
20 | {
21 | $user->skillSet = $this->skillSetDomain->getIncludedSkillsFromSkillSet($user->skillSet);
22 | }
23 |
24 | public function preUpdate(User $user): void
25 | {
26 | $this->prePersist($user);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/EntityListener/UserPasswordEntityListener.php:
--------------------------------------------------------------------------------
1 | encoder = $encoder;
17 | }
18 |
19 | public function prePersist(UserPasswordInterface $user): void
20 | {
21 | if (!$plainPassword = $user->getPlainPassword()) {
22 | return;
23 | }
24 |
25 | $user->setPassword($this->encoder->encodePassword($user, $plainPassword));
26 | $user->eraseCredentials();
27 | }
28 |
29 | public function preUpdate(UserPasswordInterface $user): void
30 | {
31 | $this->prePersist($user);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/EventListener/RouteLoggerListener.php:
--------------------------------------------------------------------------------
1 | 'onKernelResponse',
23 | ];
24 | }
25 |
26 | public function onKernelResponse(ResponseEvent $event): void
27 | {
28 | $event->getResponse()->headers->set(self::ROUTE_HEADER, $event->getRequest()->attributes->get('_route', 'null'));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Exception/ConstraintViolationListException.php:
--------------------------------------------------------------------------------
1 | violations = $violations;
16 | parent::__construct('Validation error');
17 | }
18 |
19 | public function __toString(): string
20 | {
21 | $message = '';
22 | foreach ($this->violations as $violation) {
23 | if ('' !== $message) {
24 | $message .= "\n";
25 | }
26 | if ($propertyPath = $violation->getPropertyPath()) {
27 | $message .= "$propertyPath: ";
28 | }
29 |
30 | $message .= $violation->getMessage();
31 | }
32 |
33 | return $message;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Form/Factory/OrganizationSelectorFormFactory.php:
--------------------------------------------------------------------------------
1 | formFactory = $formFactory;
19 | }
20 |
21 | public function createForm(Organization $organization, Organization $loggedOrganization): FormInterface
22 | {
23 | return $this->formFactory->create(
24 | OrganizationSelectorType::class,
25 | ['organization' => $organization],
26 | ['currentOrganization' => $loggedOrganization]
27 | );
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Form/Type/AssetTypeType.php:
--------------------------------------------------------------------------------
1 | add('name', null, ['label' => 'Nom'])
20 | ->add('properties', CollectionType::class, [
21 | 'entry_type' => AssetTypePropertyType::class,
22 | 'allow_add' => true,
23 | 'allow_delete' => true,
24 | 'label' => 'organization.asset_type.properties.main_title',
25 | ])
26 | ->add('submit', SubmitType::class)
27 | ;
28 | }
29 |
30 | public function configureOptions(OptionsResolver $resolver): void
31 | {
32 | $resolver->setDefaults([
33 | 'error_bubbling' => true,
34 | 'data_class' => AssetType::class,
35 | ]);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Form/Type/AvailabilitiesDomainType.php:
--------------------------------------------------------------------------------
1 | add('availabilityDomains', CollectionType::class, [
20 | 'entry_type' => AvailabilityDomainType::class,
21 | ])
22 | ->add('submit', SubmitType::class)
23 | ;
24 | }
25 |
26 | public function configureOptions(OptionsResolver $resolver): void
27 | {
28 | $resolver
29 | ->setDefaults([
30 | 'data_class' => AvailabilitiesDomain::class,
31 | ])
32 | ;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Form/Type/OrganizationEntityType.php:
--------------------------------------------------------------------------------
1 | setDefaults([
19 | 'class' => Organization::class,
20 | 'choice_label' => 'name',
21 | 'attr' => [
22 | 'class' => 'selectpicker show-tick',
23 | 'data-live-search' => 'true',
24 | 'title' => 'user.selectOrganization',
25 | ],
26 | 'group_by' => 'parentName',
27 | 'query_builder' => static function (OrganizationRepository $repository): QueryBuilder {
28 | return $repository->createActiveOrganizationQueryBuilder();
29 | },
30 | ]);
31 | }
32 |
33 | public function getParent(): ?string
34 | {
35 | return EntityType::class;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Form/Type/OrganizationSelectorType.php:
--------------------------------------------------------------------------------
1 | organizationRepository = $organizationRepository;
21 | }
22 |
23 | /**
24 | * @param array{currentOrganization: Organization, route_to_redirect: string} $options
25 | */
26 | public function buildForm(FormBuilderInterface $builder, array $options): void
27 | {
28 | if (!$options['currentOrganization']->isParent()) {
29 | return;
30 | }
31 |
32 | $builder
33 | ->add(
34 | 'organization',
35 | EntityType::class,
36 | [
37 | 'class' => Organization::class,
38 | 'label' => 'organization.childrenSelector.label',
39 | 'query_builder' => $this->organizationRepository->findByParentQueryBuilder($options['currentOrganization']),
40 | ]
41 | );
42 | }
43 |
44 | public function configureOptions(OptionsResolver $resolver): void
45 | {
46 | $resolver->setRequired(['currentOrganization']);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Form/Type/OrganizationType.php:
--------------------------------------------------------------------------------
1 | add('name', TextType::class, [
20 | 'label' => 'organization.name',
21 | ])
22 | ->add('submit', SubmitType::class, [
23 | 'label' => 'action.submit',
24 | ]);
25 | }
26 |
27 | public function configureOptions(OptionsResolver $resolver): void
28 | {
29 | $resolver->setDefaults([
30 | 'data_class' => Organization::class,
31 | ]);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Form/Type/PlanningDynamicFiltersType.php:
--------------------------------------------------------------------------------
1 | add(
26 | $property['key'],
27 | ChoiceType::class,
28 | [
29 | 'required' => false,
30 | 'label' => $property['columnLabel'] ?? $property['label'],
31 | 'placeholder' => 'organization.planning.booleanFilterPlaceholder',
32 | 'choices' => [
33 | 'common.no' => 0,
34 | 'common.yes' => 1,
35 | ],
36 | 'attr' => ['class' => 'dynamic_planning_filter ml-2'],
37 | 'row_attr' => ['class' => 'form-inline mb-2'],
38 | ]
39 | );
40 | }
41 | }
42 |
43 | public function configureOptions(OptionsResolver $resolver): void
44 | {
45 | $resolver
46 | ->setDefaults(['config' => []]);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Form/Type/PreAddAssetType.php:
--------------------------------------------------------------------------------
1 | getParentOrganization();
22 |
23 | $builder
24 | ->add('type', EntityType::class, [
25 | 'required' => true,
26 | 'class' => AssetType::class,
27 | 'choice_name' => 'name',
28 | 'query_builder' => fn (AssetTypeRepository $assetTypeRepository) => $assetTypeRepository->findByOrganizationQB($parentOrganization),
29 | ])
30 | ->add('submit', SubmitType::class, ['label' => 'Continuer'])
31 | ->add('organizationId', HiddenType::class)
32 | ;
33 | }
34 |
35 | public function getBlockPrefix(): string
36 | {
37 | return '';
38 | }
39 |
40 | public function configureOptions(OptionsResolver $resolver): void
41 | {
42 | $resolver
43 | ->setRequired(['organization'])
44 | ->addAllowedTypes('organization', Organization::class)
45 | ->setDefaults([
46 | 'csrf_protection' => false,
47 | ]);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Form/Type/UserLoginType.php:
--------------------------------------------------------------------------------
1 | add('identifier', TextType::class, [
19 | 'label' => 'user.login',
20 | ])
21 | ->add('birthday', BirthdayType::class, [
22 | 'format' => 'dd MMMM yyyy',
23 | 'input' => 'string',
24 | 'label' => 'user.dob',
25 | ]);
26 | }
27 |
28 | public function configureOptions(OptionsResolver $resolver): void
29 | {
30 | $resolver->setDefaults([
31 | 'csrf_token_id' => 'authenticate',
32 | ]);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Migrations/Factory/MigrationFactoryDecorator.php:
--------------------------------------------------------------------------------
1 | migrationFactory = $migrationFactory;
20 | $this->container = $container;
21 | }
22 |
23 | public function createVersion(string $migrationClassName): AbstractMigration
24 | {
25 | $instance = $this->migrationFactory->createVersion($migrationClassName);
26 |
27 | if ($instance instanceof ContainerAwareInterface) {
28 | $instance->setContainer($this->container);
29 | }
30 |
31 | return $instance;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Migrations/Version20200321175315.php:
--------------------------------------------------------------------------------
1 | abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
15 |
16 | $this->addSql('ALTER TABLE organization ADD parent_id INT DEFAULT NULL');
17 | $this->addSql('ALTER TABLE organization ADD CONSTRAINT FK_C1EE637C727ACA70 FOREIGN KEY (parent_id) REFERENCES organization (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
18 | $this->addSql('CREATE INDEX IDX_C1EE637C727ACA70 ON organization (parent_id)');
19 | }
20 |
21 | public function down(Schema $schema): void
22 | {
23 | $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
24 |
25 | $this->addSql('ALTER TABLE organization DROP CONSTRAINT FK_C1EE637C727ACA70');
26 | $this->addSql('DROP INDEX IDX_C1EE637C727ACA70');
27 | $this->addSql('ALTER TABLE organization DROP parent_id');
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Migrations/Version20200322115752.php:
--------------------------------------------------------------------------------
1 | abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
20 |
21 | $this->addSql('ALTER TABLE users ADD birthday VARCHAR(255) NOT NULL');
22 | }
23 |
24 | public function down(Schema $schema): void
25 | {
26 | $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
27 |
28 | $this->addSql('ALTER TABLE users DROP birthday');
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Migrations/Version20200322141349.php:
--------------------------------------------------------------------------------
1 | abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
15 |
16 | $this->addSql('ALTER TABLE organization ADD password VARCHAR(255) DEFAULT NULL');
17 | }
18 |
19 | public function down(Schema $schema): void
20 | {
21 | $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
22 |
23 | $this->addSql('ALTER TABLE organization DROP password');
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Migrations/Version20200322144012.php:
--------------------------------------------------------------------------------
1 | abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
15 |
16 | $this->addSql('CREATE UNIQUE INDEX organisation_name_unique ON organization (name)');
17 | }
18 |
19 | public function down(Schema $schema): void
20 | {
21 | $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
22 |
23 | $this->addSql('DROP INDEX organisation_name_unique');
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Migrations/Version20200322162455.php:
--------------------------------------------------------------------------------
1 | abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
15 |
16 | $this->addSql('ALTER TABLE users DROP skill_set');
17 | $this->addSql('ALTER TABLE users ADD skill_set text[] DEFAULT NULL');
18 | $this->addSql('CREATE INDEX user_skill_set_idx ON users (skill_set)');
19 | $this->addSql('CREATE INDEX user_vulnerable_idx ON users (vulnerable)');
20 | $this->addSql('CREATE INDEX user_fully_equipped_idx ON users (fully_equipped)');
21 | }
22 |
23 | public function down(Schema $schema): void
24 | {
25 | $this->throwIrreversibleMigrationException();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Migrations/Version20200324193917.php:
--------------------------------------------------------------------------------
1 | abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
20 |
21 | $this->addSql('ALTER TABLE commissionable_asset DROP last_commission_date');
22 | $this->addSql('CREATE INDEX commissionable_asset_type_idx ON commissionable_asset (type)');
23 | }
24 |
25 | public function down(Schema $schema): void
26 | {
27 | $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
28 |
29 | $this->addSql('DROP INDEX commissionable_asset_type_idx');
30 | $this->addSql('ALTER TABLE commissionable_asset ADD last_commission_date TIMESTAMP(0) WITH TIME ZONE DEFAULT NULL');
31 | $this->addSql('COMMENT ON COLUMN commissionable_asset.last_commission_date IS \'(DC2Type:datetimetz_immutable)\'');
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Migrations/Version20200324213836.php:
--------------------------------------------------------------------------------
1 | abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
20 |
21 | $this->addSql('CREATE INDEX commissionable_asset_name_idx ON commissionable_asset (name)');
22 | $this->addSql('CREATE INDEX user_firstname_idx ON users (first_name)');
23 | $this->addSql('CREATE INDEX user_lastname_idx ON users (last_name)');
24 | }
25 |
26 | public function down(Schema $schema): void
27 | {
28 | $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
29 |
30 | $this->addSql('DROP INDEX commissionable_asset_name_idx');
31 | $this->addSql('DROP INDEX user_firstname_idx');
32 | $this->addSql('DROP INDEX user_lastname_idx');
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Migrations/Version20200330155339.php:
--------------------------------------------------------------------------------
1 | abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
15 |
16 | $this->addSql('ALTER TABLE commissionable_asset ALTER has_mobile_radio SET NOT NULL');
17 | }
18 |
19 | public function down(Schema $schema): void
20 | {
21 | $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
22 |
23 | $this->addSql('ALTER TABLE commissionable_asset ALTER has_mobile_radio DROP NOT NULL');
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Migrations/Version20200403195104.php:
--------------------------------------------------------------------------------
1 | abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
20 |
21 | $this->addSql('DROP TABLE IF EXISTS sessions'); // The "bin/console doctrine:schema:drop --full-database" command does not drop this table
22 | $this->addSql('CREATE TABLE sessions ( sess_id VARCHAR(128) NOT NULL PRIMARY KEY, sess_data BYTEA NOT NULL, sess_time INTEGER NOT NULL, sess_lifetime INTEGER NOT NULL );');
23 | }
24 |
25 | public function down(Schema $schema): void
26 | {
27 | $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
28 |
29 | $this->addSql('DROP TABLE sessions');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Migrations/Version20200405080155.php:
--------------------------------------------------------------------------------
1 | abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
20 |
21 | $this->addSql('DROP INDEX organisation_name_unique');
22 | $this->addSql('CREATE INDEX organization_name_idx ON organization (name)');
23 | }
24 |
25 | public function down(Schema $schema): void
26 | {
27 | $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
28 |
29 | $this->addSql('DROP INDEX organization_name_idx');
30 | $this->addSql('CREATE UNIQUE INDEX organisation_name_unique ON organization (name)');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Migrations/Version20200411165144.php:
--------------------------------------------------------------------------------
1 | abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
20 |
21 | $this->addSql('CREATE SEQUENCE mission_type_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
22 | $this->addSql('CREATE TABLE mission_type (id INT NOT NULL, organization_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, user_skills_requirement JSON NOT NULL, asset_types_requirement JSON NOT NULL, PRIMARY KEY(id))');
23 | $this->addSql('CREATE INDEX IDX_A59CFB2632C8A3DE ON mission_type (organization_id)');
24 | $this->addSql('ALTER TABLE mission_type ADD CONSTRAINT FK_A59CFB2632C8A3DE FOREIGN KEY (organization_id) REFERENCES organization (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
25 | }
26 |
27 | public function down(Schema $schema): void
28 | {
29 | $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
30 |
31 | $this->addSql('DROP SEQUENCE mission_type_id_seq CASCADE');
32 | $this->addSql('DROP TABLE mission_type');
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Migrations/Version20200414132415.php:
--------------------------------------------------------------------------------
1 | abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
20 |
21 | $this->addSql('ALTER TABLE commissionable_asset ADD license_plate VARCHAR(255) DEFAULT NULL, ADD comments TEXT DEFAULT NULL');
22 | }
23 |
24 | public function down(Schema $schema): void
25 | {
26 | $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
27 |
28 | $this->addSql('ALTER TABLE commissionable_asset DROP license_plate, DROP comments');
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Migrations/Version20200415083630.php:
--------------------------------------------------------------------------------
1 | abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
20 |
21 | $this->addSql('ALTER TABLE users ADD driving_licence BOOLEAN DEFAULT FALSE NOT NULL');
22 | }
23 |
24 | public function down(Schema $schema): void
25 | {
26 | $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
27 |
28 | $this->addSql('ALTER TABLE users DROP driving_licence');
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Migrations/Version20200418152205.php:
--------------------------------------------------------------------------------
1 | abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
20 |
21 | $this->addSql('ALTER TABLE user_availability ADD comment TEXT DEFAULT \'\' NOT NULL');
22 | $this->addSql('ALTER TABLE commissionable_asset_availability ADD comment TEXT DEFAULT \'\' NOT NULL');
23 | }
24 |
25 | public function down(Schema $schema): void
26 | {
27 | $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
28 |
29 | $this->addSql('ALTER TABLE user_availability DROP comment');
30 | $this->addSql('ALTER TABLE commissionable_asset_availability DROP comment');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Migrations/Version20200420210639.php:
--------------------------------------------------------------------------------
1 | abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
20 |
21 | $this->addSql('ALTER TABLE mission_type ADD minimum_available_hours INT DEFAULT NULL');
22 | }
23 |
24 | public function down(Schema $schema): void
25 | {
26 | $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
27 |
28 | $this->addSql('ALTER TABLE mission_type DROP minimum_available_hours');
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Migrations/Version20200422103425.php:
--------------------------------------------------------------------------------
1 | abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
20 |
21 | $this->addSql('ALTER TABLE users ALTER phone_number TYPE VARCHAR(35)');
22 | $this->addSql('ALTER TABLE users ALTER phone_number DROP NOT NULL');
23 | $this->addSql('COMMENT ON COLUMN users.phone_number IS \'(DC2Type:phone_number)\'');
24 | }
25 |
26 | public function down(Schema $schema): void
27 | {
28 | $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
29 |
30 | $this->addSql('ALTER TABLE users ALTER phone_number TYPE VARCHAR(255)');
31 | $this->addSql('ALTER TABLE users ALTER phone_number SET NOT NULL');
32 | $this->addSql('COMMENT ON COLUMN users.phone_number IS NULL');
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Migrations/Version20200426171023.php:
--------------------------------------------------------------------------------
1 | addSql('UPDATE users SET phone_number = CONCAT(\'+33\', SUBSTRING(phone_number,2)) WHERE phone_number LIKE \'0%\'');
20 | }
21 |
22 | public function down(Schema $schema): void
23 | {
24 | $this->addSql('UPDATE users SET phone_number = CONCAT(\'0\', SUBSTRING(phone_number,4)) WHERE phone_number LIKE \'+33%\'');
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Migrations/Version20200503083535.php:
--------------------------------------------------------------------------------
1 | abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
20 |
21 | $this->addSql('ALTER TABLE mission ADD comment TEXT DEFAULT \'\' NOT NULL');
22 | }
23 |
24 | public function down(Schema $schema): void
25 | {
26 | $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
27 |
28 | $this->addSql('ALTER TABLE mission DROP comment');
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Monolog/Processor/ChangeLevelProcessor.php:
--------------------------------------------------------------------------------
1 | command = $command;
19 | $this->input = $input;
20 | }
21 |
22 | public function getCurrentCommand(): ?Command
23 | {
24 | return $this->command;
25 | }
26 |
27 | public function getInput(): ?InputInterface
28 | {
29 | return $this->input;
30 | }
31 |
32 | public function unsetCurrentCommand(): void
33 | {
34 | $this->command = null;
35 | }
36 |
37 | public function __invoke(array $record): array
38 | {
39 | if (null === $this->command || null === $this->input) {
40 | return $record;
41 | }
42 |
43 | $record['extra'] += [
44 | 'command_name' => $this->command->getName(),
45 | 'command_arguments' => \json_encode($this->input->getArguments()),
46 | ];
47 |
48 | return $record;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Monolog/Processor/ErrorProcessor.php:
--------------------------------------------------------------------------------
1 | = Logger::ERROR && !isset($record['context']['error'])) {
15 | $record['context']['error'] = $record['message'] ?? '';
16 | }
17 |
18 | return $record;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Monolog/Processor/ExceptionProcessor.php:
--------------------------------------------------------------------------------
1 | includeStacktraces();
17 |
18 | $formatedException = json_decode($jsonFormatter->format(['e' => $e]), true, 512, \JSON_THROW_ON_ERROR);
19 | $formatedException['e']['trace_string'] = json_encode($formatedException['e']['trace'] ?? [], \JSON_UNESCAPED_SLASHES + \JSON_THROW_ON_ERROR);
20 | unset($formatedException['e']['trace']); // The default trace format is not compliant with kibana
21 |
22 | $record['context']['exception'] = $formatedException['e'];
23 | }
24 |
25 | return $record;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/ParamConverter/OrganizationParamConverter.php:
--------------------------------------------------------------------------------
1 | organizationRepository = $organizationRepository;
21 | }
22 |
23 | /**
24 | * {@inheritdoc}
25 | */
26 | public function apply(Request $request, ParamConverter $configuration): bool
27 | {
28 | $name = $configuration->getName();
29 | $id = $request->attributes->getInt($name);
30 |
31 | if (!empty($organizationId = $request->query->getInt('organizationId'))
32 | ) {
33 | $id = $organizationId;
34 | }
35 |
36 | $organization = $this->organizationRepository->find($id);
37 |
38 | if (null === $organization) {
39 | throw new NotFoundHttpException(sprintf('Organization with id "%d" does not exist.', $id));
40 | }
41 |
42 | $request->attributes->set($name, $organization);
43 |
44 | return true;
45 | }
46 |
47 | /**
48 | * {@inheritdoc}
49 | */
50 | public function supports(ParamConverter $configuration): bool
51 | {
52 | return Organization::class === $configuration->getClass();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Repository/AvailabilitableRepositoryInterface.php:
--------------------------------------------------------------------------------
1 | andWhere('(ua.startTime >= :start and ua.endTime <= :end) or (ua.startTime <= :start and ua.endTime >= :start) or (ua.startTime <= :end and ua.endTime >= :end)')
16 | ->setParameter('start', $from)
17 | ->setParameter('end', $to)
18 | ->getQuery()
19 | ->setHint(Query::HINT_INCLUDE_META_COLUMNS, true)
20 | ->getArrayResult();
21 | }
22 |
23 | private function findLastUpdatesForEntities(QueryBuilder $qb): ?array
24 | {
25 | $rootAlias = $qb->getRootAliases()[0];
26 |
27 | return $qb
28 | ->select(sprintf('MAX(COALESCE(%s.updatedAt, %s.createdAt)) as last_update, COUNT(%s) as total_count', $rootAlias, $rootAlias, $rootAlias))
29 | ->setMaxResults(1)
30 | ->getQuery()
31 | ->getOneOrNullResult();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Repository/MissionTypeRepository.php:
--------------------------------------------------------------------------------
1 | findByOrganizationQb($organization)->getQuery()->getResult();
29 | }
30 |
31 | public function findByOrganizationQb(Organization $organization): QueryBuilder
32 | {
33 | $qb = $this->createQueryBuilder('mt');
34 |
35 | $qb
36 | ->join('mt.organization', 'o')
37 | ->where($qb->expr()->orX('o.id = :orga', 'o.parent = :orga'))
38 | ->setParameter('orga', $organization->parent ?: $organization)
39 | ->addOrderBy('mt.name', 'ASC');
40 |
41 | return $qb;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Repository/SearchableRepositoryInterface.php:
--------------------------------------------------------------------------------
1 | guardHandler = $guardHandler;
23 | $this->formAuthenticator = $formAuthenticator;
24 | }
25 |
26 | /**
27 | * @param User|UserInterface $user
28 | */
29 | public function handleAuthentication(Request $request, UserInterface $user): Response
30 | {
31 | if (!$user instanceof User) {
32 | throw new \InvalidArgumentException(sprintf('Method %s only accepts a %s instance as its second argument.', __METHOD__, User::class));
33 | }
34 |
35 | $response = $this->guardHandler->authenticateUserAndHandleSuccess(
36 | $user,
37 | $request,
38 | $this->formAuthenticator,
39 | 'main'
40 | );
41 |
42 | if (!$response instanceof Response) {
43 | throw new \RuntimeException('Guard handler must return a Response object.');
44 | }
45 |
46 | return $response;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Security/Voter/CommissionableAssetVoter.php:
--------------------------------------------------------------------------------
1 | getUser();
29 |
30 | if (!$loggedOrganization instanceof Organization) {
31 | return false;
32 | }
33 |
34 | return $subject->organization === $loggedOrganization || $subject->organization->parent === $loggedOrganization;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Security/Voter/OrganizationVoter.php:
--------------------------------------------------------------------------------
1 | getUser();
29 |
30 | if (!$loggedOrganization instanceof Organization) {
31 | return false;
32 | }
33 |
34 | if (self::CAN_CREATE === $attribute) {
35 | return $loggedOrganization->isParent();
36 | }
37 |
38 | return $this->canManageOrganization($loggedOrganization, $subject);
39 | }
40 |
41 | private function canManageOrganization(Organization $loggedOrganization, Organization $organization): bool
42 | {
43 | return $loggedOrganization === $organization || $loggedOrganization === $organization->parent;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Security/Voter/UserVoter.php:
--------------------------------------------------------------------------------
1 | getUser();
28 |
29 | if (!$loggedOrganization instanceof Organization || null === $subject->organization) {
30 | return false;
31 | }
32 |
33 | return $subject->organization === $loggedOrganization || $subject->getNotNullOrganization()->parent === $loggedOrganization;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Twig/Cache/PlanningFilesystemAdapter.php:
--------------------------------------------------------------------------------
1 | cacheDirectory = $directory.\DIRECTORY_SEPARATOR.$namespace.\DIRECTORY_SEPARATOR;
19 | }
20 |
21 | /**
22 | * {@inheritdoc}
23 | */
24 | protected function doFetch(array $ids)
25 | {
26 | /** @var array $values */
27 | $values = parent::doFetch($ids);
28 | foreach ($values as $id => $value) {
29 | $file = $this->getFile($id);
30 | if (!$handle = @fopen($file, 'rb')) {
31 | continue;
32 | }
33 | $values[$id] = [
34 | "\x9D".pack('VN', (int) (0.1 + (int) fgets($handle) - 1527506807), ceil(filectime($file) / 100))."\x5F" => $value,
35 | ];
36 | fclose($handle);
37 | }
38 |
39 | return $values;
40 | }
41 |
42 | private function getFile(string $id): string
43 | {
44 | // Use MD5 to favor speed over security, which is not an issue here
45 | $hash = str_replace('/', '-', base64_encode(hash('md5', static::class.$id, true)));
46 | $dir = $this->cacheDirectory.strtoupper($hash[0].\DIRECTORY_SEPARATOR.$hash[1].\DIRECTORY_SEPARATOR);
47 |
48 | return $dir.substr($hash, 2, 20);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Twig/Cache/RequestGenerator.php:
--------------------------------------------------------------------------------
1 | translator = $translator;
19 | }
20 |
21 | /**
22 | * {@inheritdoc}
23 | */
24 | public function getFunctions(): array
25 | {
26 | return [
27 | new TwigFunction('dynamicPropertyValue', [$this, 'dynamicPropertyValue']),
28 | ];
29 | }
30 |
31 | /**
32 | * @param bool|string|int $value
33 | */
34 | public function dynamicPropertyValue($value, array $propertyDefinition): string
35 | {
36 | if (\in_array($propertyDefinition['type'], [DynamicPropertiesType::TYPE_CHOICE, DynamicPropertiesType::TYPE_CHOICE_WITH_OTHER], true)) {
37 | return array_flip($propertyDefinition['choices'] ?? [])[$value] ?? $value;
38 | }
39 |
40 | if (DynamicPropertiesType::TYPE_BOOLEAN === $propertyDefinition['type']) {
41 | // false value will be sent by `default` twig filter as an empty string or an hyphen
42 | if (\in_array($value, ['', '-'], true)) {
43 | $value = false;
44 | }
45 |
46 | return $this->translator->trans(sprintf('common.%s', (bool) $value ? 'yes' : 'no'));
47 | }
48 |
49 | return (string) $value;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Twig/Extension/TwigTextExtension.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
{{ 'action.loading'|trans }}
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/templates/_footer.html.twig:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/templates/base.html.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{ 'project.name' | trans }} - {% block title %}{{ 'nav.welcome' | trans }}!{% endblock %}
8 | {% block stylesheets %}
9 | {{ encore_entry_link_tags('app') }}
10 | {% endblock %}
11 |
12 |
13 | {% block document_body %}
14 |
15 | {% include '_navbar.html.twig' %}
16 |
17 |
18 | {% block body %}{% endblock %}
19 |
20 |
21 | {% include '_footer.html.twig' %}
22 |
23 | {% include '_ajax_modal.html.twig' %}
24 |
25 | {% block javascripts %}
26 | {{ encore_entry_script_tags('app') }}
27 | {% endblock %}
28 |
29 | {% endblock %}
30 |
31 |
--------------------------------------------------------------------------------
/templates/misc/flash-messages.html.twig:
--------------------------------------------------------------------------------
1 | {% for message in app.flashes('success') %}
2 |
3 | {{ message | trans }}
4 |
5 | {% endfor %}
6 | {% for message in app.flashes('error') %}
7 |
8 | {{ message }}
9 |
10 | {% endfor %}
11 |
--------------------------------------------------------------------------------
/templates/misc/form_theme.html.twig:
--------------------------------------------------------------------------------
1 | {% block dynamic_properties_widget %}
2 |
9 | {% endblock dynamic_properties_widget %}
10 |
--------------------------------------------------------------------------------
/templates/organization/_delete_modal.html.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 | {{ 'message.confirmDelete' | trans }} : .
11 |
12 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/templates/organization/assetType/edit.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'organization/base.html.twig' %}
2 |
3 | {% block title %}{{ 'nav.section.organization' | trans }}{% endblock %}
4 |
5 | {% block javascripts %}
6 | {{ parent() }}
7 | {{ encore_entry_script_tags('asset-type-form') }}
8 | {% endblock %}
9 |
10 | {% block body %}
11 | {{ 'common.backToList' | trans }}
12 |
13 | {% if app.request.get('_route') == 'app_organization_assetType_new' %}
14 | {{ 'organization.asset_type.add_new' | trans }} - {{ app.user }}
15 | {% else %}
16 | {{ 'organization.asset_type.edit_form_title' | trans }} - {{ app.user }}
17 | {% endif %}
18 |
19 |
20 | {{ form_start(form, { attr: { id: 'edit-asset-type-form', 'data-persisted-keys': persistedKeys|join(',')}}) }}
21 |
22 | {{ form_errors(form) }}
23 |
24 | {{ form_row(form.name) }}
25 |
26 | {{ form_row(form.properties) }}
27 |
28 |
29 |
30 |
33 |
34 |
35 |
36 |
37 | {{ form_rest(form) }}
38 |
39 |
40 | {{ form_end(form) }}
41 | {% endblock %}
42 |
--------------------------------------------------------------------------------
/templates/organization/assetType/list.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'organization/base.html.twig' %}
2 |
3 | {% block title %}{{ 'nav.section.organization' | trans }}{% endblock %}
4 |
5 | {% block javascripts %}
6 | {{ parent() }}
7 | {% endblock %}
8 |
9 | {% block body %}
10 | {{ 'organization.asset_type.main_title' | trans }} - {{ app.user }}
11 |
12 |
17 |
18 |
19 |
20 |
21 |
22 | {{ 'common.name' | trans }} |
23 | {{ 'common.actions' | trans }} |
24 |
25 |
26 |
27 | {% for assetType in assetTypes %}
28 |
29 | {{ assetType.name }} |
30 |
31 | {{ 'action.edit' | trans }}
32 | |
33 |
34 | {% endfor %}
35 |
36 |
37 |
38 | {% endblock %}
39 |
--------------------------------------------------------------------------------
/templates/organization/commissionable_asset/_show.html.twig:
--------------------------------------------------------------------------------
1 | {% if app.user == asset.organization or app.user == asset.organization.parent %}
2 | {{ 'action.edit' | trans }}
3 | {% endif %}
4 |
5 | {{ asset }}
6 |
7 |
8 | - {{ 'organization.default'|trans }}
9 | - {{ asset.organization }}
10 |
11 | {% for prop in asset.assetType | assetTypeProperties() %}
12 | - {{ prop.label | default('') }}
13 | -
14 | {% if prop != null %}
15 |
16 | {{ dynamicPropertyValue(asset.properties[prop.key]|default('-'), prop)|truncate(75) }}
17 |
18 | {% else %}
19 | -
20 | {% endif %}
21 |
22 | {% endfor %}
23 |
24 |
--------------------------------------------------------------------------------
/templates/organization/commissionable_asset/availability.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'organization/base.html.twig' %}
2 |
3 | {% block title %}{{ 'organization.asset.availabilities' | trans ({ '%asset%' : asset }) }}{% endblock %}
4 |
5 | {% block javascripts %}
6 | {{ parent() }}
7 | {{ encore_entry_script_tags('availability-form') }}
8 | {{ encore_entry_script_tags('availability-table') }}
9 | {% endblock %}
10 |
11 | {% block body %}
12 | {{ 'organization.asset.availabilities' | trans ({ '%asset%' : asset }) }}
13 |
14 | {% include 'availability/_table.html.twig' with { availabilityType: 'assets', availabilityId: asset.id } %}
15 | {% endblock %}
16 |
--------------------------------------------------------------------------------
/templates/organization/commissionable_asset/form.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'organization/base.html.twig' %}
2 |
3 | {% set actionName = asset.id is not empty ? 'Modification' : 'Création' %}
4 |
5 | {% block title %}{{ 'organization.asset.createEdit' | trans ({ '%action%' : actionName }) }}{% endblock %}
6 |
7 | {% block body %}
8 |
9 | {% if asset.id %}
10 | {{ 'action.delete' | trans }}
17 | {% endif %}
18 |
19 | {{ 'organization.asset.createEdit' | trans ({ '%action%' : actionName }) }}
20 |
21 | {{ form_start(form) }}
22 | {% if form.organization is defined %}
23 | {{ form_row(form.organization) }}
24 | {% endif %}
25 | {{ form_rest(form) }}
26 | {{ form_end(form) }}
27 | {% endblock %}
28 |
--------------------------------------------------------------------------------
/templates/organization/commissionable_asset/list.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'organization/base.html.twig' %}
2 |
3 | {% block title %}{{ 'nav.section.organization' | trans }}{% endblock %}
4 |
5 | {% block javascripts %}
6 | {{ parent() }}
7 | {{ encore_entry_script_tags('availabilitable-list') }}
8 | {% endblock %}
9 |
10 | {% block body %}
11 | {{ 'organization.assets' | trans }} - {{ organization }}
12 |
13 | {{ form(organization_selector_form) }}
14 |
15 |
22 |
23 | {{ include('organization/commissionable_asset/_list.html.twig') }}
24 | {% endblock %}
25 |
--------------------------------------------------------------------------------
/templates/organization/commissionable_asset/preAdd.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'organization/base.html.twig' %}
2 |
3 | {% block body %}
4 | {{ form_start(form, { method: 'GET', action: path('app_organization_asset_add', {organization: organization.id | default(app.user.id)})}) }}
5 | {{ form_rest(form) }}
6 | {{ form_end(form) }}
7 | {% endblock %}
8 |
9 | {% block title %}{{ 'organization.assetType' }}{% endblock %}
10 |
11 |
--------------------------------------------------------------------------------
/templates/organization/commissionable_asset/show-modal-content.html.twig:
--------------------------------------------------------------------------------
1 |
2 | {% include 'organization/commissionable_asset/_show.html.twig' %}
3 |
4 |
--------------------------------------------------------------------------------
/templates/organization/edit.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'organization/base.html.twig' %}
2 |
3 | {% if organization.id is null %}
4 | {% set formAction = path('app_organization_new') %}
5 | {% set formTitle = 'organization.add' | trans %}
6 | {% else %}
7 | {% set formAction = path('app_organization_edit', { object: organization.id }) %}
8 | {% set formTitle = 'organization.edit' | trans %}
9 | {% endif %}
10 |
11 | {% block title %}{{formTitle}}{% endblock %}
12 |
13 | {% block body %}
14 |
15 | {{formTitle}}
16 | {{ form_start(form, { method: 'POST', action: formAction }) }}
17 | {{ form_row(form.name) }}
18 | {{ form_row(form.submit) }}
19 | {{ form_end(form) }}
20 | {% endblock %}
21 |
--------------------------------------------------------------------------------
/templates/organization/forecast/_search_type.html.twig:
--------------------------------------------------------------------------------
1 |
2 | {{ form_start(form) }}
3 |
4 |
5 |
6 |
7 | {{ form_row(form.availableFrom) }}
8 | {{ form_row(form.availableTo) }}
9 |
10 |
11 |
12 | {{ form_label(form.organizations, null, {'label_attr': {'class': 'col-12 col-md-3'}}) }}
13 |
14 | {{ form_widget(form.organizations) }}
15 |
16 |
17 |
18 |
19 | {{ form_label(form.missionTypes, null, {'label_attr': {'class': 'col-12 col-md-3'}}) }}
20 |
21 | {{ form_widget(form.missionTypes) }}
22 |
23 |
24 |
25 |
26 | {{ form_widget(form.userPropertyFilters) }}
27 |
28 |
29 |
30 |
31 | {{ form_row(form.displayAvailableWithBooked) }}
32 |
33 |
34 |
35 |
36 |
37 | {{ form_end(form) }}
38 |
39 |
--------------------------------------------------------------------------------
/templates/organization/mission/_form.html.twig:
--------------------------------------------------------------------------------
1 | {{ form_start(form) }}
2 |
3 | {% if form.organization is defined %}
4 | {{ form_row(form.organization) }}
5 | {% endif %}
6 |
7 |
8 | {{ form_row(form.type) }}
9 |
10 | {{ 'organization.mission.newType' | trans }}
11 |
12 |
13 | {{ form_row(form.name) }}
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | {{ form_row(form.startTime) }}
22 | {{ form_row(form.endTime) }}
23 |
24 |
25 | {{ form_rest(form) }}
26 |
27 |
28 | {{ form_end(form) }}
29 |
--------------------------------------------------------------------------------
/templates/organization/mission/_search_type.html.twig:
--------------------------------------------------------------------------------
1 |
2 | {{ form_start(form) }}
3 |
4 |
5 |
6 |
7 | {{ form_row(form.from) }}
8 | {{ form_row(form.to) }}
9 |
10 |
11 |
12 |
13 | {{ form_label(form.missionTypes, null, {'label_attr': {'class': 'col-12 col-md-3'}}) }}
14 |
15 | {{ form_widget(form.missionTypes) }}
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | {{ form_end(form) }}
28 |
29 |
--------------------------------------------------------------------------------
/templates/organization/mission/_show.html.twig:
--------------------------------------------------------------------------------
1 | {% if app.user == mission.organization %}
2 | {{ 'action.edit' | trans }}
3 | {% endif %}
4 |
5 |
6 | {{ mission.type.name | default('organization.mission.title') | trans }}
7 | {{ mission }}
8 |
9 |
10 | {% if mission.startTime is not null %}
11 | {{ 'common.start' | trans }}: {{ mission.startTime | format_datetime(date_format='full', time_format='short') }}
12 | {% endif %}
13 |
14 | {% if mission.endTime is not null %}
15 | {{ 'common.end' | trans }}: {{ mission.endTime | format_datetime(date_format='full', time_format='short') }}
16 | {% endif %}
17 |
18 | {% if mission.comment %}
19 | {{ 'organization.mission.comment' | trans }}
20 | {{ mission.comment | nl2br }}
21 | {% endif %}
22 |
23 | {{ 'organization.users' | trans }}
24 | {{ include('organization/user/_list.html.twig', {users: mission.users | sortBySkills, organization: mission.organization}) }}
25 |
26 | {{ 'organization.assets' | trans }}
27 | {{ include('organization/commissionable_asset/_list.html.twig', {assets: mission.assets, organization: mission.organization}) }}
28 |
29 |
--------------------------------------------------------------------------------
/templates/organization/mission/add-to-mission-modal-content.html.twig:
--------------------------------------------------------------------------------
1 |
2 |
{{ userToAdd }} {{ userToAdd|userBadges }}
3 |
{{ 'organization.mission.periodList' | trans({ '%from%' : filters.from | format_date('long'), '%to%' : filters.to | format_date('long') }) }}
4 |
5 |
8 |
9 | {% include 'organization/mission/_list.html.twig' with {modalLinks: true} %}
10 |
11 |
--------------------------------------------------------------------------------
/templates/organization/mission/edit.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'organization/base.html.twig' %}
2 |
3 | {% block javascripts %}
4 | {{ parent() }}
5 | {{ encore_entry_script_tags('missions') }}
6 | {% endblock %}
7 |
8 | {% block title %}{{ 'organization.mission.editFormTitle' | trans }}{% endblock %}
9 |
10 | {% block body %}
11 | {{ 'action.delete' | trans }}
12 |
13 | {{ 'common.backToList' | trans }}
14 |
15 | {{ 'organization.mission.editFormTitle' | trans }}
16 |
17 | {{ include('organization/mission/_form.html.twig', {'button_label': 'action.save' | trans}) }}
18 | {% endblock %}
19 |
--------------------------------------------------------------------------------
/templates/organization/mission/index.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'organization/base.html.twig' %}
2 |
3 | {% block javascripts %}
4 | {{ parent() }}
5 | {{ encore_entry_script_tags('missions') }}
6 | {% endblock %}
7 |
8 | {% block title %}{{ 'organization.mission.listTitle' | trans }}{% endblock %}
9 |
10 | {% block body %}
11 | {{ 'organization.mission.listTitle' | trans }}
12 |
13 | {% include 'organization/mission/_search_type.html.twig' %}
14 |
15 | {% set searchOptions = {
16 | 'from': filters.from | default(false) ? filters.from | date('Y-m-d\\T00:00:00') : null,
17 | 'to': filters.to | default(false) ? filters.to | date('Y-m-d\\T00:00:00') : null,
18 | 'missionTypes': filters.missionTypes | default({}) | map(type => type.id),
19 | } | filter(val => val) %}
20 |
21 |
31 |
32 | {% include('organization/mission/_list.html.twig') %}
33 | {% endblock %}
34 |
--------------------------------------------------------------------------------
/templates/organization/mission/new.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'organization/base.html.twig' %}
2 |
3 | {% block javascripts %}
4 | {{ parent() }}
5 | {{ encore_entry_script_tags('missions') }}
6 | {% endblock %}
7 |
8 | {% block title %}{{ 'organization.mission.addNew' | trans }}{% endblock %}
9 |
10 | {% block body %}
11 | {{ 'common.backToList' | trans }}
12 |
13 | {{ 'organization.mission.addNew' | trans }}
14 |
15 | {{ include('organization/mission/_form.html.twig') }}
16 | {% endblock %}
17 |
--------------------------------------------------------------------------------
/templates/organization/mission/show-modal-content.html.twig:
--------------------------------------------------------------------------------
1 |
2 | {% include 'organization/mission/_show.html.twig' %}
3 |
4 |
--------------------------------------------------------------------------------
/templates/organization/mission/show.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'organization/base.html.twig' %}
2 |
3 | {% block javascripts %}
4 | {{ parent() }}
5 | {{ encore_entry_script_tags('missions') }}
6 | {% endblock %}
7 |
8 | {% block title %}{{ mission }}{% endblock %}
9 |
10 | {% block body %}
11 | {{ 'common.backToList' | trans }}
12 |
13 | {% include 'organization/mission/_show.html.twig' %}
14 | {% endblock %}
15 |
--------------------------------------------------------------------------------
/templates/organization/mission_type/_form.html.twig:
--------------------------------------------------------------------------------
1 | {{ form_start(form) }}
2 |
3 | {{ form_row(form.name) }}
4 |
5 | {{ form_label(form.minimumAvailableHours) }}
6 |
12 | {{ form_help(form.minimumAvailableHours) }}
13 |
14 |
15 | {{ form_row(form.userSkillsRequirement) }}
16 |
17 |
18 |
19 |
20 |
21 |
22 | {{ form_row(form.assetTypesRequirement) }}
23 |
24 |
25 |
26 |
27 |
28 |
29 | {{ form_end(form) }}
30 |
--------------------------------------------------------------------------------
/templates/organization/mission_type/edit.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'organization/base.html.twig' %}
2 |
3 | {% block javascripts %}
4 | {{ parent() }}
5 | {{ encore_entry_script_tags('mission-type-form') }}
6 | {% endblock %}
7 |
8 | {% block title %}{{ 'organization.missionType.editFormTitle' | trans }}{% endblock %}
9 |
10 | {% block body %}
11 | {{ 'common.backToList' | trans }}
12 |
13 | {{ 'organization.missionType.editFormTitle' | trans }}
14 |
15 | {{ include('organization/mission_type/_form.html.twig', {'button_label': 'action.save' | trans}) }}
16 | {% endblock %}
17 |
--------------------------------------------------------------------------------
/templates/organization/mission_type/index.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'organization/base.html.twig' %}
2 |
3 | {% block title %}{{ 'organization.missionType.mainTitle' | trans }}{% endblock %}
4 |
5 | {% block body %}
6 | {{ 'organization.missionType.mainTitle' | trans }}
7 |
8 |
13 |
14 |
15 |
16 |
17 |
18 | {{ 'common.name' | trans }} |
19 | {{ 'common.actions' | trans }} |
20 |
21 |
22 |
23 | {% for mission_type in mission_types %}
24 |
25 | {{ mission_type.name }} |
26 |
27 | {{ 'action.edit' | trans }}
28 | {{ 'action.delete' | trans }}
29 | |
30 |
31 | {% else %}
32 |
33 | {{ 'message.noAvailableData' | trans }} |
34 |
35 | {% endfor %}
36 |
37 |
38 |
39 | {% endblock %}
40 |
--------------------------------------------------------------------------------
/templates/organization/mission_type/new.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'organization/base.html.twig' %}
2 |
3 | {% block javascripts %}
4 | {{ parent() }}
5 | {{ encore_entry_script_tags('mission-type-form') }}
6 | {% endblock %}
7 |
8 | {% block title %}{{ 'organization.missionType.addNew' | trans }}{% endblock %}
9 |
10 | {% block body %}
11 | {{ 'common.backToList' | trans }}
12 |
13 | {{ 'organization.missionType.addNew' | trans }}
14 |
15 | {{ include('organization/mission_type/_form.html.twig') }}
16 | {% endblock %}
17 |
--------------------------------------------------------------------------------
/templates/organization/planning/_availabilities_assets.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'organization/planning/_availabilities_base.html.twig' %}
2 | {% import 'organization/commissionable_asset/_show.html.twig' as assetsMacro %}
3 |
4 | {% block type %}{{ type | assetTypeName }}{% endblock type %}
5 |
6 | {# CAUTION: columns number of blocks itemDataHeader and itemDataDetails should be the same than in _availabilities_users.html.twig #}
7 | {% block itemDataHeader %}
8 | {% for prop in type | assetTypeProperties(itemDataRow) %}
9 | {{ prop.label | default('') }} |
10 | {% endfor %}
11 | {% endblock itemDataHeader %}
12 |
13 | {% block itemDataRowHeader %}
14 |
17 | {% endblock itemDataRowHeader %}
18 |
19 | {% block itemDataDetails %}
20 | {% for prop in type | assetTypeProperties(itemDataRow) %}
21 |
22 | {% if prop != null %}
23 |
24 | {{ dynamicPropertyValue(item.entity.properties[prop.key] | default('-'), prop)|truncate(75) }}
25 |
26 | {% endif %}
27 | |
28 | {% endfor %}
29 | |
30 | {% endblock itemDataDetails %}
31 |
--------------------------------------------------------------------------------
/templates/organization/planning/planning.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'organization/base.html.twig' %}
2 |
3 | {% block javascripts %}
4 | {{ parent() }}
5 | {{ encore_entry_script_tags('planning') }}
6 | {{ encore_entry_script_tags('availability-table') }}
7 | {% endblock %}
8 |
9 | {% block title %}{{ 'calendar.planning' | trans }}{% endblock %}
10 |
11 | {% block container %}
12 |
13 |
14 | {{ include('misc/flash-messages.html.twig') }}
15 |
16 | {{ 'calendar.planning' | trans }} -
17 | {{ 'calendar.period' | trans ({
18 | '%from%' : periodCalculator.from | format_date(pattern="eeee dd MMMM"),
19 | '%to%' : periodCalculator.to | date_modify('- 1 minute') | format_date(pattern="eeee dd MMMM")
20 | }) }}
21 |
22 | {% include 'organization/planning/_search_type.html.twig' %}
23 |
24 | {% include 'organization/planning/_results.html.twig' %}
25 |
26 | {% endblock %}
27 |
--------------------------------------------------------------------------------
/templates/organization/search.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'organization/base.html.twig' %}
2 |
3 | {% block title %}Espace structure{% endblock %}
4 |
5 | {% block javascripts %}
6 | {{ parent() }}
7 | {{ encore_entry_script_tags('availabilitable-list') }}
8 | {% endblock %}
9 |
10 | {% block body %}
11 | {{ include('misc/flash-messages.html.twig') }}
12 |
13 | {{ 'action.searchQuery' | trans({'%query%': query}) }}
14 |
15 |
16 | Bénévoles
17 | {% if users|length %}
18 | {% include 'organization/user/_list.html.twig' with {organization: app.user} %}
19 | {% else %}
20 | {{ 'organization.search.noUsers' | trans }}
21 | {% endif %}
22 | Afficher la liste de mes bénévoles inscrits
23 |
24 |
25 | Véhicules
26 | {% if assets|length %}
27 | {% include'organization/commissionable_asset/_list.html.twig' %}
28 | {% else %}
29 | {{ 'organization.search.noAssets' | trans }}
30 | {% endif %}
31 | Afficher la liste de mes véhicules
32 | {% endblock %}
33 |
--------------------------------------------------------------------------------
/templates/organization/user/_show.html.twig:
--------------------------------------------------------------------------------
1 | {% if app.user == user.organization or app.user == user.organization.parent %}
2 | {{ 'action.edit' | trans }}
3 | {% endif %}
4 |
5 | {{ user }}
6 |
7 |
8 | - {{ 'user.skills'|trans }}
9 | - {{ user|userBadges }}
10 | - {{ 'user.identificationNumber'|trans }}
11 | - {{ user.identificationNumber }}
12 | - {{ 'user.info'|trans }}
13 | -
14 | {{ user.emailAddress }}
15 |
16 | {{ user.phoneNumber|phone_number_format('NATIONAL') }}
17 |
18 |
19 | - {{ 'user.dob'|trans }}
20 | - {{ user.birthday|format_date('long') }}
21 | {% for user_property in user_properties %}
22 | - {{ user_property.columnLabel|default(user_property.label)|default }}
23 | -
24 | {{ dynamicPropertyValue(user.properties[user_property.key]|default('-'), user_property)|truncate(75) }}
25 |
26 | {% endfor %}
27 |
28 |
29 | {{ 'organization.mission.listTitle'|trans }}
30 | {% include 'organization/mission/_list.html.twig' with {missions: user.missions, modalLinks: true} %}
31 |
--------------------------------------------------------------------------------
/templates/organization/user/edit.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'organization/base.html.twig' %}
2 |
3 | {% block javascripts %}
4 | {{ parent() }}
5 | {{ encore_entry_script_tags('form-choice-with-other') }}
6 | {% endblock %}
7 |
8 | {% block body %}
9 | {{ 'organization.editUserProfile' | trans }}
10 |
11 | {{ 'action.delete' | trans }}
18 |
19 | {% include '/user/_user_form.html.twig' %}
20 | {% endblock %}
21 |
--------------------------------------------------------------------------------
/templates/organization/user/list.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'organization/base.html.twig' %}
2 |
3 | {% block javascripts %}
4 | {{ parent() }}
5 | {{ encore_entry_script_tags('availabilitable-list') }}
6 | {% endblock %}
7 |
8 | {% block title %}{{ 'organization.users' | trans }}{% endblock %}
9 |
10 | {% block body %}
11 | {{ 'organization.users' | trans }} - {{ organization }}
12 |
13 | {{ form(organization_selector_form) }}
14 |
15 | {{ include('organization/user/_list.html.twig') }}
16 | {% endblock %}
17 |
--------------------------------------------------------------------------------
/templates/organization/user/missions_list.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'organization/base.html.twig' %}
2 |
3 | {% block title %}{{ user }} - {{ 'organization.mission.listTitle' | trans }}{% endblock %}
4 |
5 | {% block body %}
6 | {{ user }} - {{ 'organization.mission.listTitle' | trans }}
7 |
8 | {% include 'organization/mission/_list.html.twig' %}
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/templates/organization/user/show-modal-content.html.twig:
--------------------------------------------------------------------------------
1 |
2 | {% include 'organization/user/_show.html.twig' %}
3 |
4 |
--------------------------------------------------------------------------------
/templates/user/_introduction.html.twig:
--------------------------------------------------------------------------------
1 |
2 | {{ 'register.introduction'|trans|raw }}
3 |
4 |
--------------------------------------------------------------------------------
/templates/user/_user_form.html.twig:
--------------------------------------------------------------------------------
1 | {{ form_start(form) }}
2 | {{ form_errors(form) }}
3 |
4 |
5 |
6 | {{ form_row(form.identificationNumber, { 'label': 'user.identificationNumber' | trans }) }}
7 |
8 |
9 | {% if form.birthday is defined %}
10 | {{ form_row(form.birthday, { 'label': 'user.dob' | trans }) }}
11 | {% endif %}
12 |
13 |
14 |
15 |
16 |
17 | {{ form_row(form.organization) }}
18 |
19 |
20 |
21 |
22 |
23 | {{ form_row(form.firstName, { 'label': 'user.firstName' | trans }) }}
24 |
25 |
26 | {{ form_row(form.lastName, { 'label': 'user.lastName' | trans }) }}
27 |
28 |
29 |
30 |
31 |
32 | {{ form_row(form.emailAddress, { 'label' : 'user.email' | trans }) }}
33 |
34 |
35 | {{ form_row(form.phoneNumber, { 'label' : 'user.mobile' | trans }) }}
36 |
37 |
38 |
39 |
40 |
41 | {{ form_row(form.skillSet) }}
42 |
43 |
44 |
45 | {{ form_row(form.properties) }}
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | {{ form_end(form) }}
54 |
--------------------------------------------------------------------------------
/templates/user/account-form.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'base.html.twig' %}
2 |
3 | {% set actionName = (user is defined and user.id is not null) ? 'Modification' : 'Création' %}
4 | {% block title %}{{ 'user.accountAction' | trans({ '%action%' : actionName }) }}{% endblock %}
5 |
6 | {% block javascripts %}
7 | {{ parent() }}
8 | {{ encore_entry_script_tags('form-choice-with-other') }}
9 | {% endblock %}
10 |
11 | {% block body %}
12 | {{ 'user.accountAction' | trans({ '%action%' : actionName }) }}
13 |
14 | {% if user is not defined or user.id is not defined or user.id is null %}
15 | {% include '/user/_introduction.html.twig' %}
16 | {% endif %}
17 |
18 | {% include '/user/_user_form.html.twig' with { 'form': form } %}
19 | {% endblock %}
20 |
--------------------------------------------------------------------------------
/templates/user/availability.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'base.html.twig' %}
2 |
3 | {% block title %}{{ 'user.availabilities' | trans }}{% endblock %}
4 |
5 | {% block javascripts %}
6 | {{ parent() }}
7 | {{ encore_entry_script_tags('availability-form') }}
8 | {{ encore_entry_script_tags('availability-table') }}
9 | {% endblock %}
10 |
11 | {% block body %}
12 | {% include 'availability/_table.html.twig' with { availabilityType: 'users', availabilityId: app.user.id } %}
13 | {% endblock %}
14 |
--------------------------------------------------------------------------------
/tests/Behat/FixturesContext.php:
--------------------------------------------------------------------------------
1 | aliceFixturesLoader = $aliceFixturesLoader;
23 | $this->kernel = $kernel;
24 | $this->entityManager = $entityManager;
25 | }
26 |
27 | /**
28 | * @AfterScenario @javascript
29 | */
30 | public function loadFixtures(): void
31 | {
32 | StaticDriver::beginTransaction();
33 | $this->aliceFixturesLoader->load(
34 | new Application($this->kernel),
35 | $this->entityManager,
36 | [],
37 | 'panther',
38 | false,
39 | false
40 | );
41 | StaticDriver::commit();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/Domain/AvailabilitiesDomainTest.php:
--------------------------------------------------------------------------------
1 | expectException(\InvalidArgumentException::class);
18 | $this->expectExceptionMessage($message);
19 |
20 | new AvailabilitiesDomain([], $slotInterval);
21 | }
22 |
23 | public function getInvalidSlotIntervals(): array
24 | {
25 | return [
26 | ['+2 hours 30 minutes', 'Invalid slot interval: unable to set a complete day with "+2 hours 30 minutes".'],
27 | ['+42 minutes', 'Invalid slot interval: unable to set a complete day with "+42 minutes".'],
28 | ['+5 hours', 'Invalid slot interval: unable to set a complete day with "+5 hours".'],
29 | ];
30 | }
31 |
32 | /**
33 | * @dataProvider getValidSlotIntervals
34 | */
35 | public function testNewAvailabilitiesDomain(string $slotInterval): void
36 | {
37 | self::assertInstanceOf(AvailabilitiesDomain::class, new AvailabilitiesDomain([], $slotInterval));
38 | }
39 |
40 | public function getValidSlotIntervals(): array
41 | {
42 | return [
43 | ['+1 hour 30 minutes'],
44 | ['+2 hours'],
45 | ['+3 hours'],
46 | ['+4 hours'],
47 | ['+6 hours'],
48 | ];
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/tests/Entity/OrganizationTest.php:
--------------------------------------------------------------------------------
1 | id = 1;
16 | $organization->name = 'DT75';
17 |
18 | self::assertSame(1, $organization->id);
19 | self::assertSame('DT75', $organization->name);
20 | self::assertSame('DT75', (string) $organization);
21 | self::assertNull($organization->parent);
22 | }
23 |
24 | public function testCreateOrganizationWithParent(): void
25 | {
26 | $parent = new Organization();
27 | $parent->id = 1;
28 | $parent->name = 'DT75';
29 | $child = new Organization();
30 | $child->id = 2;
31 | $child->name = 'UL09';
32 | $child->parent = $parent;
33 |
34 | self::assertSame(2, $child->id);
35 | self::assertSame('UL09', $child->name);
36 | self::assertSame('DT75 - UL09', (string) $child);
37 | self::assertSame($parent, $child->parent);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/ObjectManager.php:
--------------------------------------------------------------------------------
1 | boot();
13 |
14 | $container = $kernel->getContainer();
15 |
16 | return $container->get('doctrine')->getManager();
17 |
--------------------------------------------------------------------------------
/tests/Twig/Cache/RequestGeneratorTest.php:
--------------------------------------------------------------------------------
1 | generateKey($data));
18 | }
19 |
20 | public function getFilters(): array
21 | {
22 | return [
23 | ['3a3160c3d13a2b6e9a56089f00c0743bd55166fb', ['foo']],
24 | ['289df0705aad811a4f04744ea205da25cad15371', ['bar']],
25 | ];
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/translations/security.fr.yaml:
--------------------------------------------------------------------------------
1 | "Invalid credentials.": "Veuillez saisir un numéro NIVOL ou une adresse e-mail valide, ou la date de naissance ne corresponds pas à ce NIVOL/email."
2 |
--------------------------------------------------------------------------------
/translations/security_organization.fr.yml:
--------------------------------------------------------------------------------
1 | "Invalid credentials.": "Le mot de passe associé à cette structure est invalide."
2 |
--------------------------------------------------------------------------------
/translations/validators.fr.yaml:
--------------------------------------------------------------------------------
1 | organization:
2 | asset_type:
3 | property_unique_error: Au moins un identifiant unique est dupliqué
4 | mission_type:
5 | user_skill_unique_error: Au moins une compétence de bénévole est dupliquée
6 | asset_type_unique_error: Au moins un type de véhicule est dupliqué
7 |
--------------------------------------------------------------------------------