mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-28 01:25:00 +00:00
Merge branch 'master' into migrate_to_sf72
# Conflicts: # src/Bundle/ChillEventBundle/Controller/EventController.php # src/Bundle/ChillEventBundle/Controller/ParticipationController.php # src/Bundle/ChillEventBundle/DependencyInjection/ChillEventExtension.php # src/Bundle/ChillEventBundle/Entity/Event.php # src/Bundle/ChillEventBundle/Form/EventType.php # src/Bundle/ChillEventBundle/Menu/AdminMenuBuilder.php # src/Bundle/ChillEventBundle/config/services.yaml # src/Bundle/ChillEventBundle/config/services/controller.yaml # src/Bundle/ChillMainBundle/Resources/views/Menu/user.html.twig # src/Bundle/ChillPersonBundle/Controller/AccompanyingPeriodWorkDuplicateController.php # src/Bundle/ChillPersonBundle/Controller/PersonController.php # src/Bundle/ChillPersonBundle/Form/PersonType.php
This commit is contained in:
@@ -70,6 +70,8 @@
|
||||
<option value="00:10:00">10 minutes</option>
|
||||
<option value="00:15:00">15 minutes</option>
|
||||
<option value="00:30:00">30 minutes</option>
|
||||
<option value="00:45:00">45 minutes</option>
|
||||
<option value="00:60:00">60 minutes</option>
|
||||
</select>
|
||||
<label class="input-group-text" for="slotMinTime">De</label>
|
||||
<select
|
||||
|
@@ -32,6 +32,8 @@
|
||||
<option value="00:10:00">10 minutes</option>
|
||||
<option value="00:15:00">15 minutes</option>
|
||||
<option value="00:30:00">30 minutes</option>
|
||||
<option value="00:45:00">45 minutes</option>
|
||||
<option value="00:60:00">60 minutes</option>
|
||||
</select>
|
||||
<label class="input-group-text" for="slotMinTime">De</label>
|
||||
<select
|
||||
@@ -102,7 +104,8 @@
|
||||
event.title
|
||||
}}</b>
|
||||
<b v-else-if="event.extendedProps.is === 'range'"
|
||||
>{{ formatDate(event.startStr) }} -
|
||||
>{{ formatDate(event.startStr, "time") }} -
|
||||
{{ formatDate(event.endStr, "time") }}:
|
||||
{{ event.extendedProps.locationName }}</b
|
||||
>
|
||||
<b v-else-if="event.extendedProps.is === 'local'">{{
|
||||
@@ -294,9 +297,26 @@ const nextWeeks = computed((): Weeks[] =>
|
||||
}),
|
||||
);
|
||||
|
||||
const formatDate = (datetime: string) => {
|
||||
console.log(typeof datetime);
|
||||
return ISOToDate(datetime);
|
||||
const formatDate = (datetime: string, format: null | "time" = null) => {
|
||||
const date = ISOToDate(datetime);
|
||||
if (!date) return "";
|
||||
|
||||
if (format === "time") {
|
||||
return date.toLocaleTimeString("fr-FR", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
}
|
||||
|
||||
// French date formatting
|
||||
return date.toLocaleDateString("fr-FR", {
|
||||
weekday: "short",
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
};
|
||||
|
||||
const baseOptions = ref<CalendarOptions>({
|
||||
|
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\CustomFieldsBundle\EntityRepository;
|
||||
|
||||
use Chill\CustomFieldsBundle\Entity\CustomFieldsDefaultGroup;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
class CustomFieldsDefaultGroupRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, CustomFieldsDefaultGroup::class);
|
||||
}
|
||||
|
||||
public function findOneByEntity(string $className): ?CustomFieldsDefaultGroup
|
||||
{
|
||||
return $this->findOneBy(['entity' => $className]);
|
||||
}
|
||||
}
|
@@ -127,3 +127,7 @@ services:
|
||||
factory: ["@doctrine", getRepository]
|
||||
arguments:
|
||||
- "Chill\\CustomFieldsBundle\\Entity\\CustomFieldLongChoice\\Option"
|
||||
|
||||
Chill\CustomFieldsBundle\EntityRepository\CustomFieldsDefaultGroupRepository:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
@@ -18,6 +18,7 @@ use Chill\DocStoreBundle\Exception\StoredObjectManagerException;
|
||||
use Chill\DocStoreBundle\Service\Cryptography\KeyGenerator;
|
||||
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
|
||||
@@ -147,16 +148,11 @@ class StoredObjectManager implements StoredObjectManagerInterface
|
||||
public function writeContent(string $filename, string $encryptedContent): void
|
||||
{
|
||||
$fullPath = $this->buildPath($filename);
|
||||
$dir = Path::getDirectory($fullPath);
|
||||
|
||||
if (!$this->filesystem->exists($dir)) {
|
||||
$this->filesystem->mkdir($dir);
|
||||
}
|
||||
|
||||
$result = file_put_contents($fullPath, $encryptedContent);
|
||||
|
||||
if (false === $result) {
|
||||
throw StoredObjectManagerException::unableToStoreDocumentOnDisk();
|
||||
try {
|
||||
$this->filesystem->dumpFile($fullPath, $encryptedContent);
|
||||
} catch (IOExceptionInterface $exception) {
|
||||
throw StoredObjectManagerException::unableToStoreDocumentOnDisk($exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\EventBundle\Controller\Admin;
|
||||
|
||||
use Chill\MainBundle\CRUD\Controller\CRUDController;
|
||||
use Chill\MainBundle\Pagination\PaginatorInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class EventBudgetKindController extends CRUDController
|
||||
{
|
||||
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
|
||||
{
|
||||
/* @var QueryBuilder $query */
|
||||
$query->addOrderBy('e.type', 'ASC');
|
||||
|
||||
return parent::orderQuery($action, $query, $request, $paginator);
|
||||
}
|
||||
}
|
@@ -23,11 +23,11 @@ use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Form\Type\PickPersonDynamicType;
|
||||
use Chill\PersonBundle\Privacy\PrivacyEvent;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Csv;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Ods;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
@@ -40,6 +40,8 @@ use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\Serializer\Exception\ExceptionInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
@@ -58,7 +60,8 @@ final class EventController extends AbstractController
|
||||
private readonly TranslatorInterface $translator,
|
||||
private readonly PaginatorFactory $paginator,
|
||||
private readonly Security $security,
|
||||
private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry,
|
||||
private readonly ManagerRegistry $managerRegistry,
|
||||
private readonly NormalizerInterface $normalizer,
|
||||
) {}
|
||||
|
||||
#[\Symfony\Component\Routing\Attribute\Route(path: '/{_locale}/event/event/{event_id}/delete', name: 'chill_event__event_delete', requirements: ['event_id' => '\d+'], methods: ['GET', 'POST', 'DELETE'])]
|
||||
@@ -75,6 +78,7 @@ final class EventController extends AbstractController
|
||||
|
||||
/** @var array $participations */
|
||||
$participations = $event->getParticipations();
|
||||
$budgetElements = $event->getBudgetElements();
|
||||
|
||||
$form = $this->createDeleteForm($event_id);
|
||||
|
||||
@@ -86,6 +90,10 @@ final class EventController extends AbstractController
|
||||
$em->remove($participation);
|
||||
}
|
||||
|
||||
foreach ($budgetElements as $e) {
|
||||
$em->remove($e);
|
||||
}
|
||||
|
||||
$em->remove($event);
|
||||
$em->flush();
|
||||
|
||||
@@ -103,8 +111,8 @@ final class EventController extends AbstractController
|
||||
}
|
||||
|
||||
return $this->render('@ChillEvent/Event/confirm_delete.html.twig', [
|
||||
'event_id' => $event->getId(),
|
||||
'delete_form' => $form,
|
||||
'id' => $event->getId(),
|
||||
'delete_form' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -169,6 +177,8 @@ final class EventController extends AbstractController
|
||||
|
||||
/**
|
||||
* Displays a form to create a new Event entity.
|
||||
*
|
||||
* @throws ExceptionInterface
|
||||
*/
|
||||
#[\Symfony\Component\Routing\Attribute\Route(path: '/{_locale}/event/event/new', name: 'chill_event__event_new', methods: ['GET', 'POST'])]
|
||||
public function newAction(?Center $center, Request $request): Response
|
||||
@@ -199,12 +209,15 @@ final class EventController extends AbstractController
|
||||
$this->addFlash('success', $this->translator
|
||||
->trans('The event was created'));
|
||||
|
||||
return $this->redirectToRoute('chill_event__event_show', ['event_id' => $entity->getId()]);
|
||||
return $this->redirectToRoute('chill_event__event_show', ['id' => $entity->getId()]);
|
||||
}
|
||||
|
||||
$entity_array = $this->normalizer->normalize($entity, 'json', ['groups' => 'read']);
|
||||
|
||||
return $this->render('@ChillEvent/Event/new.html.twig', [
|
||||
'entity' => $entity,
|
||||
'form' => $form,
|
||||
'form' => $form->createView(),
|
||||
'entity_json' => $entity_array,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -216,9 +229,6 @@ final class EventController extends AbstractController
|
||||
{
|
||||
$role = 'CHILL_EVENT_CREATE';
|
||||
|
||||
/**
|
||||
* @var Center $centers
|
||||
*/
|
||||
$centers = $this->authorizationHelper->getReachableCenters($this->getUser(), $role);
|
||||
|
||||
if (1 === \count($centers)) {
|
||||
@@ -238,7 +248,7 @@ final class EventController extends AbstractController
|
||||
->add('center_id', EntityType::class, [
|
||||
'class' => Center::class,
|
||||
'choices' => $centers,
|
||||
'placeholder' => '',
|
||||
'placeholder' => $this->translator->trans('Pick a center'),
|
||||
'label' => 'To which centre should the event be associated ?',
|
||||
])
|
||||
->add('submit', SubmitType::class, [
|
||||
@@ -317,7 +327,7 @@ final class EventController extends AbstractController
|
||||
|
||||
$this->addFlash('success', $this->translator->trans('The event was updated'));
|
||||
|
||||
return $this->redirectToRoute('chill_event__event_show', ['event_id' => $event_id]);
|
||||
return $this->redirectToRoute('chill_event__event_show', ['id' => $event_id]);
|
||||
}
|
||||
|
||||
return $this->render('@ChillEvent/Event/edit.html.twig', [
|
||||
|
@@ -15,11 +15,15 @@ use Chill\EventBundle\Entity\Event;
|
||||
use Chill\EventBundle\Entity\EventType;
|
||||
use Chill\EventBundle\Repository\EventACLAwareRepositoryInterface;
|
||||
use Chill\EventBundle\Repository\EventTypeRepository;
|
||||
use Chill\EventBundle\Security\EventVoter;
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Pagination\PaginatorFactoryInterface;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
|
||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||
use Chill\PersonBundle\Form\Type\PickPersonDynamicType;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
@@ -29,17 +33,18 @@ use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Twig\Environment;
|
||||
|
||||
final readonly class EventListController
|
||||
final class EventListController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private Environment $environment,
|
||||
private EventACLAwareRepositoryInterface $eventACLAwareRepository,
|
||||
private EventTypeRepository $eventTypeRepository,
|
||||
private FilterOrderHelperFactory $filterOrderHelperFactory,
|
||||
private FormFactoryInterface $formFactory,
|
||||
private PaginatorFactoryInterface $paginatorFactory,
|
||||
private TranslatableStringHelperInterface $translatableStringHelper,
|
||||
private UrlGeneratorInterface $urlGenerator,
|
||||
private readonly Environment $environment,
|
||||
private readonly EventACLAwareRepositoryInterface $eventACLAwareRepository,
|
||||
private readonly EventTypeRepository $eventTypeRepository,
|
||||
private readonly FilterOrderHelperFactory $filterOrderHelperFactory,
|
||||
private readonly FormFactoryInterface $formFactory,
|
||||
private readonly PaginatorFactoryInterface $paginatorFactory,
|
||||
private readonly TranslatableStringHelperInterface $translatableStringHelper,
|
||||
private readonly UrlGeneratorInterface $urlGenerator,
|
||||
private readonly AuthorizationHelper $authorizationHelper,
|
||||
) {}
|
||||
|
||||
#[\Symfony\Component\Routing\Attribute\Route(path: '{_locale}/event/event/list', name: 'chill_event_event_list')]
|
||||
@@ -50,6 +55,8 @@ final readonly class EventListController
|
||||
'q' => (string) $filter->getQueryString(),
|
||||
'dates' => $filter->getDateRangeData('dates'),
|
||||
'event_types' => $filter->getEntityChoiceData('event_types'),
|
||||
'responsables' => $filter->getUserPickerData('responsables'),
|
||||
'centers' => $filter->getEntityChoiceData('centers'),
|
||||
];
|
||||
$total = $this->eventACLAwareRepository->countAllViewable($filterData);
|
||||
$pagination = $this->paginatorFactory->create($total);
|
||||
@@ -73,6 +80,7 @@ final readonly class EventListController
|
||||
private function buildFilterOrder(): FilterOrderHelper
|
||||
{
|
||||
$types = $this->eventTypeRepository->findAllActive();
|
||||
$centers = $this->authorizationHelper->getReachableCenters($this->getUser(), EventVoter::SEE);
|
||||
|
||||
$builder = $this->filterOrderHelperFactory->create(__METHOD__);
|
||||
$builder
|
||||
@@ -80,6 +88,16 @@ final readonly class EventListController
|
||||
->addSearchBox(['name'])
|
||||
->addEntityChoice('event_types', 'event.filter.event_types', EventType::class, $types, [
|
||||
'choice_label' => fn (EventType $e) => $this->translatableStringHelper->localize($e->getName()),
|
||||
'expanded' => false,
|
||||
'required' => false,
|
||||
'attr' => ['class' => 'select2'],
|
||||
])
|
||||
->addUserPicker('responsables', 'event.filter.pick_responsable', ['multiple' => true, 'required' => false])
|
||||
->addEntityChoice('centers', 'event.filter.center', Center::class, $centers, [
|
||||
'choice_label' => fn (Center $c) => $c->getName(),
|
||||
'expanded' => false,
|
||||
'required' => false,
|
||||
'attr' => ['class' => 'select2'],
|
||||
]);
|
||||
|
||||
return $builder->build();
|
||||
|
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\EventBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\CRUD\Controller\CRUDController;
|
||||
use Chill\MainBundle\Pagination\PaginatorInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class EventThemeController extends CRUDController
|
||||
{
|
||||
protected function createFormFor(string $action, $entity, ?string $formClass = null, array $formOptions = []): FormInterface
|
||||
{
|
||||
if ('new' === $action) {
|
||||
return parent::createFormFor($action, $entity, $formClass, ['step' => 'create']);
|
||||
}
|
||||
|
||||
if ('edit' === $action) {
|
||||
return parent::createFormFor($action, $entity, $formClass, ['step' => 'edit']);
|
||||
}
|
||||
|
||||
throw new \LogicException('action is not supported: '.$action);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QueryBuilder|mixed $query
|
||||
*/
|
||||
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator): QueryBuilder
|
||||
{
|
||||
/* @var QueryBuilder $query */
|
||||
return $query->orderBy('e.ordering', 'ASC')
|
||||
->addOrderBy('e.id', 'ASC');
|
||||
}
|
||||
}
|
@@ -228,7 +228,7 @@ final class ParticipationController extends AbstractController
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('chill_event__event_show', [
|
||||
'event_id' => $participation->getEvent()->getId(),
|
||||
'id' => $participation->getEvent()->getId(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@ final class ParticipationController extends AbstractController
|
||||
);
|
||||
|
||||
return $this->redirectToRoute('chill_event__event_show', [
|
||||
'event_id' => $event->getId(),
|
||||
'id' => $event->getId(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -442,7 +442,7 @@ final class ParticipationController extends AbstractController
|
||||
));
|
||||
|
||||
return $this->redirectToRoute('chill_event__event_show', [
|
||||
'event_id' => $participation->getEvent()->getId(),
|
||||
'id' => $participation->getEvent()->getId(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@@ -11,6 +11,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\EventBundle\DependencyInjection;
|
||||
|
||||
use Chill\EventBundle\Controller\Admin\EventBudgetKindController;
|
||||
use Chill\EventBundle\Controller\EventThemeController;
|
||||
use Chill\EventBundle\Entity\EventBudgetKind;
|
||||
use Chill\EventBundle\Entity\EventTheme;
|
||||
use Chill\EventBundle\Form\EventBudgetKindType;
|
||||
use Chill\EventBundle\Form\EventThemeType;
|
||||
use Chill\EventBundle\Security\EventVoter;
|
||||
use Chill\EventBundle\Security\ParticipationVoter;
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
@@ -48,13 +54,14 @@ class ChillEventExtension extends Extension implements PrependExtensionInterface
|
||||
public function prepend(ContainerBuilder $container): void
|
||||
{
|
||||
$this->prependAuthorization($container);
|
||||
$this->prependCruds($container);
|
||||
$this->prependRoute($container);
|
||||
}
|
||||
|
||||
/**
|
||||
* add authorization hierarchy.
|
||||
*/
|
||||
protected function prependAuthorization(ContainerBuilder $container)
|
||||
protected function prependAuthorization(ContainerBuilder $container): void
|
||||
{
|
||||
$container->prependExtensionConfig('security', [
|
||||
'role_hierarchy' => [
|
||||
@@ -70,7 +77,7 @@ class ChillEventExtension extends Extension implements PrependExtensionInterface
|
||||
/**
|
||||
* add route to route loader for chill.
|
||||
*/
|
||||
protected function prependRoute(ContainerBuilder $container)
|
||||
protected function prependRoute(ContainerBuilder $container): void
|
||||
{
|
||||
// add routes for custom bundle
|
||||
$container->prependExtensionConfig('chill_main', [
|
||||
@@ -81,4 +88,54 @@ class ChillEventExtension extends Extension implements PrependExtensionInterface
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
protected function prependCruds(ContainerBuilder $container): void
|
||||
{
|
||||
$container->prependExtensionConfig('chill_main', [
|
||||
'cruds' => [
|
||||
[
|
||||
'class' => EventTheme::class,
|
||||
'name' => 'event_theme',
|
||||
'base_path' => '/admin/event/theme',
|
||||
'form_class' => EventThemeType::class,
|
||||
'controller' => EventThemeController::class,
|
||||
'actions' => [
|
||||
'index' => [
|
||||
'template' => '@ChillEvent/Admin/EventTheme/index.html.twig',
|
||||
'role' => 'ROLE_ADMIN',
|
||||
],
|
||||
'new' => [
|
||||
'role' => 'ROLE_ADMIN',
|
||||
'template' => '@ChillEvent/Admin/EventTheme/new.html.twig',
|
||||
],
|
||||
'edit' => [
|
||||
'role' => 'ROLE_ADMIN',
|
||||
'template' => '@ChillEvent/Admin/EventTheme/edit.html.twig',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'class' => EventBudgetKind::class,
|
||||
'name' => 'event_budget_kind',
|
||||
'base_path' => '/admin/event/budget',
|
||||
'form_class' => EventBudgetKindType::class,
|
||||
'controller' => EventBudgetKindController::class,
|
||||
'actions' => [
|
||||
'index' => [
|
||||
'template' => '@ChillEvent/Admin/BudgetKind/index.html.twig',
|
||||
'role' => 'ROLE_ADMIN',
|
||||
],
|
||||
'new' => [
|
||||
'role' => 'ROLE_ADMIN',
|
||||
'template' => '@ChillEvent/Admin/BudgetKind/new.html.twig',
|
||||
],
|
||||
'edit' => [
|
||||
'role' => 'ROLE_ADMIN',
|
||||
'template' => '@ChillEvent/Admin/BudgetKind/edit.html.twig',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
18
src/Bundle/ChillEventBundle/Entity/BudgetTypeEnum.php
Normal file
18
src/Bundle/ChillEventBundle/Entity/BudgetTypeEnum.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\EventBundle\Entity;
|
||||
|
||||
enum BudgetTypeEnum: string
|
||||
{
|
||||
case CHARGE = 'Charge';
|
||||
case RESOURCE = 'Resource';
|
||||
}
|
@@ -23,10 +23,13 @@ use Chill\MainBundle\Entity\HasScopeInterface;
|
||||
use Chill\MainBundle\Entity\Location;
|
||||
use Chill\MainBundle\Entity\Scope;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
|
||||
/**
|
||||
* Class Event.
|
||||
@@ -46,35 +49,63 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
|
||||
#[ORM\ManyToOne(targetEntity: Scope::class)]
|
||||
private ?Scope $circle = null;
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_MUTABLE)]
|
||||
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
|
||||
private ?\DateTime $date = null;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(name: 'id', type: \Doctrine\DBAL\Types\Types::INTEGER)]
|
||||
#[ORM\Column(name: 'id', type: Types::INTEGER)]
|
||||
#[ORM\GeneratedValue(strategy: 'AUTO')]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: User::class)]
|
||||
private ?User $moderator = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, User>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: User::class)]
|
||||
#[Serializer\Groups(['read'])]
|
||||
#[ORM\JoinTable('chill_event_animatorsintern')]
|
||||
private Collection $animatorsIntern;
|
||||
|
||||
/**
|
||||
* @var Collection<int, ThirdParty>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: ThirdParty::class)]
|
||||
#[Serializer\Groups(['read'])]
|
||||
#[ORM\JoinTable('chill_event_animatorsextern')]
|
||||
private Collection $animatorsExtern;
|
||||
|
||||
#[Assert\NotBlank]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 150)]
|
||||
#[Serializer\Groups(['read'])]
|
||||
#[ORM\Column(type: Types::STRING, length: 150)]
|
||||
private ?string $name = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, Participation>
|
||||
*/
|
||||
#[ORM\OneToMany(mappedBy: 'event', targetEntity: Participation::class)]
|
||||
#[Serializer\Groups(['read'])]
|
||||
private Collection $participations;
|
||||
|
||||
#[Assert\NotNull]
|
||||
#[Serializer\Groups(['read'])]
|
||||
#[ORM\ManyToOne(targetEntity: EventType::class)]
|
||||
private ?EventType $type = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, EventTheme>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: EventTheme::class)]
|
||||
#[Serializer\Groups(['read'])]
|
||||
#[ORM\JoinTable('chill_event_eventtheme')]
|
||||
private Collection $themes;
|
||||
|
||||
#[ORM\Embedded(class: CommentEmbeddable::class, columnPrefix: 'comment_')]
|
||||
private CommentEmbeddable $comment;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: Location::class)]
|
||||
#[Serializer\Groups(['read'])]
|
||||
#[ORM\JoinColumn(nullable: true)]
|
||||
private ?Location $location = null;
|
||||
|
||||
@@ -85,7 +116,17 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
|
||||
#[ORM\JoinTable('chill_event_event_documents')]
|
||||
private Collection $documents;
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DECIMAL, precision: 10, scale: 4, nullable: true, options: ['default' => '0.0'])]
|
||||
/**
|
||||
* @var Collection<int, EventBudgetElement>
|
||||
*/
|
||||
#[ORM\OneToMany(mappedBy: 'event', targetEntity: EventBudgetElement::class, cascade: ['persist'])]
|
||||
#[Serializer\Groups(['read'])]
|
||||
private Collection $budgetElements;
|
||||
|
||||
/**
|
||||
* @deprecated use budgetElements instead
|
||||
*/
|
||||
#[ORM\Column(type: Types::DECIMAL, precision: 10, scale: 4, nullable: true, options: ['default' => '0.0'])]
|
||||
private string $organizationCost = '0.0';
|
||||
|
||||
/**
|
||||
@@ -96,6 +137,20 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
|
||||
$this->participations = new ArrayCollection();
|
||||
$this->documents = new ArrayCollection();
|
||||
$this->comment = new CommentEmbeddable();
|
||||
$this->themes = new ArrayCollection();
|
||||
$this->budgetElements = new ArrayCollection();
|
||||
$this->animatorsIntern = new ArrayCollection();
|
||||
$this->animatorsExtern = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function addBudgetElement(EventBudgetElement $budgetElement)
|
||||
{
|
||||
if (!$this->budgetElements->contains($budgetElement)) {
|
||||
$this->budgetElements[] = $budgetElement;
|
||||
$budgetElement->setEvent($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,26 +181,69 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Center
|
||||
*/
|
||||
public function getCenter(): ?\Chill\MainBundle\Entity\Center
|
||||
public function getThemes(): Collection
|
||||
{
|
||||
return $this->themes;
|
||||
}
|
||||
|
||||
public function addTheme(EventTheme $theme): self
|
||||
{
|
||||
$this->themes->add($theme);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeTheme(EventTheme $theme): void
|
||||
{
|
||||
$this->themes->removeElement($theme);
|
||||
}
|
||||
|
||||
public function getAnimatorsIntern(): Collection
|
||||
{
|
||||
return $this->animatorsIntern;
|
||||
}
|
||||
|
||||
public function getAnimatorsExtern(): Collection
|
||||
{
|
||||
return $this->animatorsExtern;
|
||||
}
|
||||
|
||||
public function addAnimatorsIntern(User $ai): self
|
||||
{
|
||||
$this->animatorsIntern->add($ai);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeAnimatorsIntern(User $ai): void
|
||||
{
|
||||
$this->animatorsIntern->removeElement($ai);
|
||||
}
|
||||
|
||||
public function addAnimatorsExtern(ThirdParty $ae): self
|
||||
{
|
||||
$this->animatorsExtern->add($ae);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeAnimatorsExtern(ThirdParty $ae): void
|
||||
{
|
||||
$this->animatorsExtern->removeElement($ae);
|
||||
}
|
||||
|
||||
public function getCenter(): Center
|
||||
{
|
||||
return $this->center;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Scope
|
||||
*/
|
||||
public function getCircle(): ?\Chill\MainBundle\Entity\Scope
|
||||
public function getCircle(): ?Scope
|
||||
{
|
||||
return $this->circle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get date.
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getDate(): ?\DateTime
|
||||
{
|
||||
@@ -154,8 +252,6 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
|
||||
|
||||
/**
|
||||
* Get id.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getId(): ?int
|
||||
{
|
||||
@@ -169,14 +265,20 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
|
||||
|
||||
/**
|
||||
* Get label.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, EventBudgetElement>
|
||||
*/
|
||||
public function getBudgetElements(): Collection
|
||||
{
|
||||
return $this->budgetElements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Participation>
|
||||
*/
|
||||
@@ -199,22 +301,22 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* @return Scope
|
||||
*/
|
||||
public function getScope(): ?\Chill\MainBundle\Entity\Scope
|
||||
public function getScope(): Scope
|
||||
{
|
||||
return $this->getCircle();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EventType
|
||||
*/
|
||||
public function getType(): ?\Chill\EventBundle\Entity\EventType
|
||||
public function getType(): ?EventType
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function removeBudgetElement(EventBudgetElement $budgetElement): void
|
||||
{
|
||||
$this->budgetElements->removeElement($budgetElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove participation.
|
||||
*/
|
||||
@@ -314,11 +416,17 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
|
||||
$this->documents = $documents;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function getOrganizationCost(): string
|
||||
{
|
||||
return $this->organizationCost;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function setOrganizationCost(string $organizationCost): void
|
||||
{
|
||||
$this->organizationCost = $organizationCost;
|
||||
|
103
src/Bundle/ChillEventBundle/Entity/EventBudgetElement.php
Normal file
103
src/Bundle/ChillEventBundle/Entity/EventBudgetElement.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\EventBundle\Entity;
|
||||
|
||||
use Chill\EventBundle\Repository\EventThemeRepository;
|
||||
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Entity(repositoryClass: EventThemeRepository::class)]
|
||||
#[ORM\Table(name: 'chill_event_budget_element')]
|
||||
class EventBudgetElement
|
||||
{
|
||||
#[ORM\Column(name: 'id', type: Types::INTEGER)]
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue(strategy: 'AUTO')]
|
||||
private ?int $id = null;
|
||||
|
||||
#[Assert\GreaterThan(value: 0)]
|
||||
#[Assert\NotNull(message: 'The amount cannot be empty')]
|
||||
#[ORM\Column(name: 'amount', type: Types::DECIMAL, precision: 10, scale: 2)]
|
||||
private string $amount;
|
||||
|
||||
#[ORM\Embedded(class: CommentEmbeddable::class, columnPrefix: 'comment_budget_element_')]
|
||||
private ?CommentEmbeddable $comment = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: Event::class)]
|
||||
private Event $event;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: EventBudgetKind::class, inversedBy: 'EventBudgetElement')]
|
||||
#[ORM\JoinColumn]
|
||||
private EventBudgetKind $kind;
|
||||
|
||||
/* Getters and Setters */
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setId(?int $id): void
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function getAmount(): float
|
||||
{
|
||||
return (float) $this->amount;
|
||||
}
|
||||
|
||||
public function getComment(): ?CommentEmbeddable
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
public function getEvent(): Event
|
||||
{
|
||||
return $this->event;
|
||||
}
|
||||
|
||||
public function getKind(): EventBudgetKind
|
||||
{
|
||||
return $this->kind;
|
||||
}
|
||||
|
||||
public function setAmount(string $amount): self
|
||||
{
|
||||
$this->amount = $amount;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setComment(?CommentEmbeddable $comment = null): self
|
||||
{
|
||||
$this->comment = $comment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setEvent(Event $event): self
|
||||
{
|
||||
$this->event = $event;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setKind(EventBudgetKind $kind): self
|
||||
{
|
||||
$this->kind = $kind;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
78
src/Bundle/ChillEventBundle/Entity/EventBudgetKind.php
Normal file
78
src/Bundle/ChillEventBundle/Entity/EventBudgetKind.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\EventBundle\Entity;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* Type of event budget element.
|
||||
*/
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'chill_event_budget_kind')]
|
||||
class EventBudgetKind
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: Types::INTEGER)]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(type: Types::BOOLEAN, options: ['default' => true])]
|
||||
private bool $isActive = true;
|
||||
|
||||
#[ORM\Column(enumType: BudgetTypeEnum::class)]
|
||||
private BudgetTypeEnum $type;
|
||||
|
||||
#[ORM\Column(type: Types::JSON, length: 255, options: ['default' => '{}', 'jsonb' => true])]
|
||||
private array $name = [];
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getIsActive(): bool
|
||||
{
|
||||
return $this->isActive;
|
||||
}
|
||||
|
||||
public function getType(): BudgetTypeEnum
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function getName(): ?array
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setIsActive(bool $isActive): self
|
||||
{
|
||||
$this->isActive = $isActive;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setType(BudgetTypeEnum $type): self
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setName(array $name): self
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
158
src/Bundle/ChillEventBundle/Entity/EventTheme.php
Normal file
158
src/Bundle/ChillEventBundle/Entity/EventTheme.php
Normal file
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\EventBundle\Entity;
|
||||
|
||||
use Chill\EventBundle\Repository\EventThemeRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* Class EventTheme.
|
||||
*/
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
#[ORM\Entity(repositoryClass: EventThemeRepository::class)]
|
||||
#[ORM\Table(name: 'chill_event_event_theme')]
|
||||
class EventTheme
|
||||
{
|
||||
#[ORM\Column(type: Types::BOOLEAN, nullable: false)]
|
||||
private bool $isActive = true;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(name: 'id', type: Types::INTEGER)]
|
||||
#[ORM\GeneratedValue(strategy: 'AUTO')]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(type: Types::JSON)]
|
||||
private array $name;
|
||||
|
||||
/**
|
||||
* @var Collection<int, EventTheme>
|
||||
*/
|
||||
#[ORM\OneToMany(mappedBy: 'parent', targetEntity: EventTheme::class)]
|
||||
private Collection $children;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: EventTheme::class, inversedBy: 'children')]
|
||||
private ?EventTheme $parent = null;
|
||||
|
||||
#[ORM\Column(name: 'ordering', type: Types::FLOAT, options: ['default' => '0.0'])]
|
||||
private float $ordering = 0.0;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->children = new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active.
|
||||
*/
|
||||
public function getIsActive(): bool
|
||||
{
|
||||
return $this->isActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get id.
|
||||
*/
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get label.
|
||||
*/
|
||||
public function getName(): array
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setIsActive(bool $active): self
|
||||
{
|
||||
$this->isActive = $active;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setName(array $label): self
|
||||
{
|
||||
$this->name = $label;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addChild(self $child): self
|
||||
{
|
||||
if (!$this->children->contains($child)) {
|
||||
$this->children[] = $child;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeChild(self $child): self
|
||||
{
|
||||
if ($this->children->removeElement($child)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($child->getParent() === $this) {
|
||||
$child->setParent(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getChildren(): Collection
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
public function hasChildren(): bool
|
||||
{
|
||||
return 0 < $this->getChildren()->count();
|
||||
}
|
||||
|
||||
public function hasParent(): bool
|
||||
{
|
||||
return null !== $this->parent;
|
||||
}
|
||||
|
||||
public function getOrdering(): float
|
||||
{
|
||||
return $this->ordering;
|
||||
}
|
||||
|
||||
public function setOrdering(float $ordering): EventTheme
|
||||
{
|
||||
$this->ordering = $ordering;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getParent(): ?self
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
public function setParent(?self $parent): self
|
||||
{
|
||||
$this->parent = $parent;
|
||||
|
||||
$parent?->addChild($this);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
377
src/Bundle/ChillEventBundle/Export/Export/ListEvents.php
Normal file
377
src/Bundle/ChillEventBundle/Export/Export/ListEvents.php
Normal file
@@ -0,0 +1,377 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\EventBundle\Export\Export;
|
||||
|
||||
use Chill\EventBundle\Entity\BudgetTypeEnum;
|
||||
use Chill\EventBundle\Entity\Event;
|
||||
use Chill\EventBundle\Export\Declarations;
|
||||
use Chill\EventBundle\Repository\EventBudgetElementRepository;
|
||||
use Chill\EventBundle\Repository\EventThemeRepository;
|
||||
use Chill\EventBundle\Security\EventVoter;
|
||||
use Chill\EventBundle\Templating\Entity\EventThemeRender;
|
||||
use Chill\MainBundle\Export\ExportGenerationContext;
|
||||
use Chill\MainBundle\Export\FormatterInterface;
|
||||
use Chill\MainBundle\Export\GroupedExportInterface;
|
||||
use Chill\MainBundle\Export\ListInterface;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||
use Chill\ThirdPartyBundle\Export\Helper\LabelThirdPartyHelper;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\NativeQuery;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Validator\Constraints\Callback;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use Symfony\Contracts\Translation\TranslatableInterface;
|
||||
|
||||
/**
|
||||
* Render a list of events.
|
||||
*/
|
||||
class ListEvents implements ListInterface, GroupedExportInterface
|
||||
{
|
||||
protected array $fields = [
|
||||
'event_id',
|
||||
'event_center',
|
||||
'event_name',
|
||||
'event_date',
|
||||
'event_location',
|
||||
'event_type',
|
||||
'event_themes',
|
||||
'event_moderator',
|
||||
'event_animators',
|
||||
'event_participants_count',
|
||||
'event_budget_resources',
|
||||
'event_budget_charges',
|
||||
];
|
||||
|
||||
private readonly bool $filterStatsByCenters;
|
||||
|
||||
public function __construct(
|
||||
protected readonly EntityManagerInterface $entityManager,
|
||||
ParameterBagInterface $parameterBag,
|
||||
protected readonly TranslatableStringHelperInterface $translatableStringHelper,
|
||||
protected readonly EventThemeRender $eventThemeRender,
|
||||
protected readonly EventThemeRepository $eventThemeRepository,
|
||||
protected readonly LabelThirdPartyHelper $labelThirdPartyHelper,
|
||||
protected readonly EventBudgetElementRepository $eventBudgetElementRepository,
|
||||
) {
|
||||
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder): void
|
||||
{
|
||||
$builder
|
||||
->add('fields', ChoiceType::class, [
|
||||
'multiple' => true,
|
||||
'expanded' => true,
|
||||
'choices' => array_combine($this->fields, $this->fields),
|
||||
'label' => 'Fields to include in export',
|
||||
'constraints' => [new Callback([
|
||||
'callback' => static function ($selected, ExecutionContextInterface $context) {
|
||||
if (0 === \count($selected)) {
|
||||
$context->buildViolation('You must select at least one element')
|
||||
->atPath('fields')
|
||||
->addViolation();
|
||||
}
|
||||
},
|
||||
])],
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [
|
||||
'fields' => $this->fields,
|
||||
];
|
||||
}
|
||||
|
||||
public function getAllowedFormattersTypes(): array
|
||||
{
|
||||
return [FormatterInterface::TYPE_LIST];
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'export.event.list.description';
|
||||
}
|
||||
|
||||
public function getGroup(): string
|
||||
{
|
||||
return 'Exports of events';
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data)
|
||||
{
|
||||
return match ($key) {
|
||||
'event_id' => fn ($value) => '_header' === $value ? $key : $value,
|
||||
'event_name' => fn ($value) => '_header' === $value ? $key : $value,
|
||||
'event_date' => function ($value) use ($key) {
|
||||
if ('_header' === $value) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
if ($value instanceof \DateTime) {
|
||||
return $value->format('Y-m-d');
|
||||
}
|
||||
|
||||
$date = \DateTime::createFromFormat('Y-m-d H:i:s', $value);
|
||||
|
||||
return $date ? $date->format('Y-m-d') : $value;
|
||||
},
|
||||
'event_type' => function ($value) use ($key) {
|
||||
if ('_header' === $value) {
|
||||
return 'export.event.list.'.$key;
|
||||
}
|
||||
|
||||
return $this->translatableStringHelper->localize(json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR));
|
||||
},
|
||||
'event_center' => fn ($value) => '_header' === $value ? $key : $value,
|
||||
'event_moderator' => fn ($value) => '_header' === $value ? $key : $value,
|
||||
'event_participants_count' => fn ($value) => '_header' === $value ? $key : $value,
|
||||
'event_location' => fn ($value) => '_header' === $value ? $key : $value,
|
||||
'event_animators' => $this->labelThirdPartyHelper->getLabelMulti($key, $values, $key),
|
||||
'event_themes' => function ($value) use ($key) {
|
||||
if ('_header' === $value) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
if (null === $value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return implode(
|
||||
'|',
|
||||
array_map(
|
||||
fn ($t) => $this->eventThemeRender->renderString($this->eventThemeRepository->find($t), []),
|
||||
json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR)
|
||||
)
|
||||
);
|
||||
},
|
||||
'event_budget_resources' => function ($value) use ($key) {
|
||||
if ('_header' === $value) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
if (!$value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$ids = explode(',', $value);
|
||||
$ids = json_decode($value, true, 512, JSON_THROW_ON_ERROR);
|
||||
$elements = $this->eventBudgetElementRepository->findBy(['id' => $ids]);
|
||||
|
||||
return implode('|', array_map(function ($element) {
|
||||
$name = $this->translatableStringHelper->localize($element->getKind()->getName());
|
||||
$amount = number_format($element->getAmount(), 2, '.', '');
|
||||
|
||||
return $name.': '.$amount;
|
||||
}, $elements));
|
||||
},
|
||||
|
||||
'event_budget_charges' => function ($value) use ($key) {
|
||||
if ('_header' === $value) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
if (!$value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$ids = explode(',', $value);
|
||||
$ids = json_decode($value, true, 512, JSON_THROW_ON_ERROR);
|
||||
|
||||
$elements = $this->eventBudgetElementRepository->findBy(['id' => $ids]);
|
||||
|
||||
return implode('|', array_map(function ($element) {
|
||||
$name = $this->translatableStringHelper->localize($element->getKind()->getName());
|
||||
$amount = number_format($element->getAmount(), 2, '.', '');
|
||||
|
||||
return $name.': '.$amount;
|
||||
}, $elements));
|
||||
},
|
||||
|
||||
|
||||
default => fn ($value) => '_header' === $value ? $key : $value,
|
||||
};
|
||||
}
|
||||
|
||||
public function getQueryKeys(array $data): array
|
||||
{
|
||||
return $data['fields'];
|
||||
}
|
||||
|
||||
public function getResult($query, $data, ExportGenerationContext $context): array
|
||||
{
|
||||
return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
|
||||
}
|
||||
|
||||
public function getTitle(): string|TranslatableInterface
|
||||
{
|
||||
return 'export.event.list.title';
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return Declarations::EVENT;
|
||||
}
|
||||
|
||||
public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): NativeQuery|QueryBuilder
|
||||
{
|
||||
$centers = array_map(static fn ($el) => $el['center'], $acl);
|
||||
|
||||
// Throw an error if no fields are present
|
||||
if (!\array_key_exists('fields', $data)) {
|
||||
throw new \InvalidArgumentException('No fields have been checked.');
|
||||
}
|
||||
|
||||
$qb = $this->entityManager->createQueryBuilder()
|
||||
->from(Event::class, 'event');
|
||||
|
||||
if ($this->filterStatsByCenters) {
|
||||
$qb
|
||||
->andWhere('event.center IN (:authorized_centers)')
|
||||
->setParameter('authorized_centers', $centers);
|
||||
}
|
||||
|
||||
// Add fields based on selection
|
||||
foreach ($this->fields as $field) {
|
||||
if (\in_array($field, $data['fields'], true)) {
|
||||
switch ($field) {
|
||||
case 'event_id':
|
||||
$qb->addSelect('event.id AS event_id');
|
||||
break;
|
||||
|
||||
case 'event_name':
|
||||
$qb->addSelect('event.name AS event_name');
|
||||
break;
|
||||
|
||||
case 'event_date':
|
||||
$qb->addSelect('event.date AS event_date');
|
||||
break;
|
||||
|
||||
case 'event_type':
|
||||
if (!$this->hasJoin($qb, 'event.type')) {
|
||||
$qb->leftJoin('event.type', 'type');
|
||||
}
|
||||
$qb->addSelect('type.name AS event_type');
|
||||
break;
|
||||
|
||||
case 'event_center':
|
||||
if (!$this->hasJoin($qb, 'event.center')) {
|
||||
$qb->leftJoin('event.center', 'center');
|
||||
}
|
||||
$qb->addSelect('center.name AS event_center');
|
||||
break;
|
||||
|
||||
case 'event_moderator':
|
||||
if (!$this->hasJoin($qb, 'event.moderator')) {
|
||||
$qb->leftJoin('event.moderator', 'user');
|
||||
}
|
||||
$qb->addSelect('user.username AS event_moderator');
|
||||
break;
|
||||
|
||||
case 'event_participants_count':
|
||||
$qb->addSelect('(SELECT COUNT(p.id) FROM Chill\EventBundle\Entity\Participation p WHERE p.event = event.id) AS event_participants_count');
|
||||
break;
|
||||
|
||||
case 'event_location':
|
||||
if (!$this->hasJoin($qb, 'event.location')) {
|
||||
$qb->leftJoin('event.location', 'location');
|
||||
}
|
||||
$qb->addSelect('location.name AS event_location');
|
||||
break;
|
||||
|
||||
case 'event_animators':
|
||||
$qb->addSelect(
|
||||
'(SELECT AGGREGATE(tp.id) FROM Chill\ThirdPartyBundle\Entity\ThirdParty tp WHERE tp MEMBER OF event.animators) AS event_animators'
|
||||
);
|
||||
break;
|
||||
|
||||
case 'event_themes':
|
||||
$qb->addSelect(
|
||||
'(SELECT AGGREGATE(t.id) FROM Chill\EventBundle\Entity\EventTheme t WHERE t MEMBER OF event.themes) AS event_themes'
|
||||
);
|
||||
break;
|
||||
|
||||
case 'event_budget_resources':
|
||||
$qb->addSelect(
|
||||
'(SELECT AGGREGATE(ebr.id)
|
||||
FROM Chill\EventBundle\Entity\EventBudgetElement ebr
|
||||
JOIN ebr.kind kr
|
||||
WHERE ebr.event = event.id AND kr.type = :resource_type) AS event_budget_resources'
|
||||
);
|
||||
$qb->setParameter('resource_type', BudgetTypeEnum::RESOURCE->value);
|
||||
break;
|
||||
|
||||
case 'event_budget_charges':
|
||||
$qb->addSelect(
|
||||
'(SELECT AGGREGATE(ebc.id)
|
||||
FROM Chill\EventBundle\Entity\EventBudgetElement ebc
|
||||
JOIN ebc.kind kc
|
||||
WHERE ebc.event = event.id AND kc.type = :charge_type) AS event_budget_charges'
|
||||
);
|
||||
$qb->setParameter('charge_type', BudgetTypeEnum::CHARGE->value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
public function requiredRole(): string
|
||||
{
|
||||
return EventVoter::STATS;
|
||||
}
|
||||
|
||||
public function supportsModifiers()
|
||||
{
|
||||
return [Declarations::EVENT];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to check if a join already exists in the QueryBuilder.
|
||||
*/
|
||||
private function hasJoin($queryBuilder, $joinPath): bool
|
||||
{
|
||||
$joins = $queryBuilder->getDQLPart('join');
|
||||
if (!isset($joins['event'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($joins['event'] as $join) {
|
||||
if ($join->getJoin() === $joinPath) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function normalizeFormData(array $formData): array
|
||||
{
|
||||
return ['fields' => $formData['fields']];
|
||||
}
|
||||
|
||||
public function denormalizeFormData(array $formData, int $fromVersion): array
|
||||
{
|
||||
return ['fields' => $formData['fields']];
|
||||
}
|
||||
|
||||
public function getNormalizationVersion(): int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\EventBundle\Form;
|
||||
|
||||
use Chill\EventBundle\Entity\BudgetTypeEnum;
|
||||
use Chill\EventBundle\Entity\EventBudgetElement;
|
||||
use Chill\EventBundle\Entity\EventBudgetKind;
|
||||
use Chill\EventBundle\Repository\EventBudgetKindRepository;
|
||||
use Chill\MainBundle\Form\Type\CommentType;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\NumberType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class AddEventBudgetElementType extends AbstractType
|
||||
{
|
||||
public function __construct(private readonly EventBudgetKindRepository $kindRepository, private readonly TranslatableStringHelperInterface $translatableStringHelper) {}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$charges = $this->kindRepository->findByType(BudgetTypeEnum::CHARGE->value);
|
||||
$resources = $this->kindRepository->findByType(BudgetTypeEnum::RESOURCE->value);
|
||||
|
||||
$builder->add('kind', ChoiceType::class, [
|
||||
'choices' => [
|
||||
'event.budget.charges' => $charges,
|
||||
'event.budget.resources' => $resources,
|
||||
],
|
||||
'choice_label' => fn (EventBudgetKind $kind) => $this->translatableStringHelper->localize($kind->getName()),
|
||||
'choice_value' => fn (?EventBudgetKind $kind) => $kind?->getId(),
|
||||
'placeholder' => 'event.budget.Select a budget element kind',
|
||||
])
|
||||
->add('amount', NumberType::class, [
|
||||
'required' => true,
|
||||
])
|
||||
->add('comment', CommentType::class, [
|
||||
'label' => 'Comment',
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => EventBudgetElement::class,
|
||||
]);
|
||||
}
|
||||
}
|
53
src/Bundle/ChillEventBundle/Form/EventBudgetKindType.php
Normal file
53
src/Bundle/ChillEventBundle/Form/EventBudgetKindType.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\EventBundle\Form;
|
||||
|
||||
use Chill\EventBundle\Entity\BudgetTypeEnum;
|
||||
use Chill\EventBundle\Entity\EventBudgetKind;
|
||||
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EnumType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class EventBudgetKindType extends AbstractType
|
||||
{
|
||||
public function __construct(private readonly TranslatorInterface $translator) {}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('name', TranslatableStringFormType::class, [
|
||||
'label' => 'Title',
|
||||
])
|
||||
->add('type', EnumType::class, [
|
||||
'class' => BudgetTypeEnum::class,
|
||||
'choice_label' => fn (BudgetTypeEnum $type): string => $this->translator->trans($type->value),
|
||||
'expanded' => true,
|
||||
'multiple' => false,
|
||||
'mapped' => true,
|
||||
'label' => 'event.admin.Select budget type',
|
||||
])
|
||||
->add('isActive', CheckboxType::class, [
|
||||
'label' => 'Actif ?',
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver
|
||||
->setDefault('class', EventBudgetKind::class);
|
||||
}
|
||||
}
|
67
src/Bundle/ChillEventBundle/Form/EventThemeType.php
Normal file
67
src/Bundle/ChillEventBundle/Form/EventThemeType.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\EventBundle\Form;
|
||||
|
||||
use Chill\EventBundle\Entity\EventTheme;
|
||||
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\NumberType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
|
||||
class EventThemeType extends AbstractType
|
||||
{
|
||||
public function __construct(protected TranslatableStringHelperInterface $translatableStringHelper) {}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('name', TranslatableStringFormType::class, [
|
||||
'label' => 'Nom',
|
||||
]);
|
||||
|
||||
if ('create' === $options['step']) {
|
||||
$builder
|
||||
->add('parent', EntityType::class, [
|
||||
'class' => EventTheme::class,
|
||||
'required' => false,
|
||||
'choice_label' => fn (EventTheme $theme): ?string => $this->translatableStringHelper->localize($theme->getName()),
|
||||
'mapped' => 'create' == $options['step'],
|
||||
]);
|
||||
}
|
||||
|
||||
$builder
|
||||
->add('ordering', NumberType::class, [
|
||||
'required' => true,
|
||||
'scale' => 6,
|
||||
])
|
||||
->add('isActive', ChoiceType::class, [
|
||||
'choices' => [
|
||||
'Yes' => true,
|
||||
'No' => false,
|
||||
],
|
||||
'expanded' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => EventTheme::class,
|
||||
]);
|
||||
$resolver->setRequired('step')
|
||||
->setAllowedValues('step', ['create', 'edit']);
|
||||
}
|
||||
}
|
@@ -13,25 +13,39 @@ namespace Chill\EventBundle\Form;
|
||||
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\DocStoreBundle\Form\StoredObjectType;
|
||||
use Chill\EventBundle\Entity\BudgetTypeEnum;
|
||||
use Chill\EventBundle\Entity\Event;
|
||||
use Chill\EventBundle\Form\Type\PickEventThemeType;
|
||||
use Chill\EventBundle\Form\Type\PickEventTypeType;
|
||||
use Chill\EventBundle\Repository\EventBudgetKindRepository;
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Form\DataTransformer\IdToLocationDataTransformer;
|
||||
use Chill\MainBundle\Form\Type\ChillCollectionType;
|
||||
use Chill\MainBundle\Form\Type\ChillDateTimeType;
|
||||
use Chill\MainBundle\Form\Type\CommentType;
|
||||
use Chill\MainBundle\Form\Type\PickUserDynamicType;
|
||||
use Chill\MainBundle\Form\Type\PickUserLocationType;
|
||||
use Chill\MainBundle\Form\Type\ScopePickerType;
|
||||
use Chill\ThirdPartyBundle\Form\Type\PickThirdpartyDynamicType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class EventType extends AbstractType
|
||||
{
|
||||
public function __construct(
|
||||
private readonly IdToLocationDataTransformer $idToLocationDataTransformer,
|
||||
private readonly EventBudgetKindRepository $eventBudgetKindRepository,
|
||||
private readonly TranslatorInterface $translator,
|
||||
) {}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$chargeKinds = $this->eventBudgetKindRepository->findByType(BudgetTypeEnum::CHARGE->value);
|
||||
$resourceKinds = $this->eventBudgetKindRepository->findByType(BudgetTypeEnum::RESOURCE->value);
|
||||
|
||||
$builder
|
||||
->add('name', TextType::class, [
|
||||
'required' => true,
|
||||
@@ -49,11 +63,28 @@ class EventType extends AbstractType
|
||||
'class' => '',
|
||||
],
|
||||
])
|
||||
->add('themes', PickEventThemeType::class, [
|
||||
'multiple' => true,
|
||||
])
|
||||
->add('moderator', PickUserDynamicType::class, [
|
||||
'label' => 'Pick a moderator',
|
||||
])
|
||||
->add('location', PickUserLocationType::class, [
|
||||
'label' => 'event.fields.location',
|
||||
->add('animatorsIntern', PickUserDynamicType::class, [
|
||||
'multiple' => true,
|
||||
'label' => $this->translator->trans('event.fields.internal animators'),
|
||||
'required' => false,
|
||||
])
|
||||
->add('animatorsExtern', PickThirdpartyDynamicType::class, [
|
||||
'multiple' => true,
|
||||
'label' => $this->translator->trans('event.fields.external animators'),
|
||||
'required' => false,
|
||||
])
|
||||
->add('budgetElements', ChillCollectionType::class, [
|
||||
'entry_type' => AddEventBudgetElementType::class,
|
||||
'entry_options' => ['label' => false],
|
||||
'allow_add' => true,
|
||||
'allow_delete' => true,
|
||||
'by_reference' => false,
|
||||
])
|
||||
->add('comment', CommentType::class, [
|
||||
'label' => 'Comment',
|
||||
@@ -69,11 +100,11 @@ class EventType extends AbstractType
|
||||
'delete_empty' => fn (StoredObject $storedObject): bool => '' === $storedObject->getFilename(),
|
||||
'button_remove_label' => 'event.form.remove_document',
|
||||
'button_add_label' => 'event.form.add_document',
|
||||
])
|
||||
->add('organizationCost', MoneyType::class, [
|
||||
'label' => 'event.fields.organizationCost',
|
||||
'help' => 'event.form.organisationCost_help',
|
||||
]);
|
||||
|
||||
$builder->add('location', HiddenType::class)
|
||||
->get('location')
|
||||
->addModelTransformer($this->idToLocationDataTransformer);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
@@ -87,11 +118,9 @@ class EventType extends AbstractType
|
||||
->setAllowedTypes('role', 'string');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'chill_eventbundle_event';
|
||||
// as the js shares some hardcoded items from the activity bundle, we have to rewrite block prefix
|
||||
return 'chill_activitybundle_activity';
|
||||
}
|
||||
}
|
||||
|
45
src/Bundle/ChillEventBundle/Form/Type/PickEventThemeType.php
Normal file
45
src/Bundle/ChillEventBundle/Form/Type/PickEventThemeType.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\EventBundle\Form\Type;
|
||||
|
||||
use Chill\EventBundle\Entity\EventTheme;
|
||||
use Chill\EventBundle\Repository\EventThemeRepository;
|
||||
use Chill\EventBundle\Templating\Entity\EventThemeRender;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class PickEventThemeType extends AbstractType
|
||||
{
|
||||
public function __construct(private readonly EventThemeRender $eventThemeRender, private readonly EventThemeRepository $eventThemeRepository) {}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver
|
||||
->setDefaults([
|
||||
'class' => EventTheme::class,
|
||||
'choices' => $this->eventThemeRepository->findByActiveOrdered(),
|
||||
'choice_label' => fn (EventTheme $et) => $this->eventThemeRender->renderString($et, []),
|
||||
'placeholder' => 'event.form.Select one or more themes',
|
||||
'required' => true,
|
||||
'attr' => ['class' => 'select2'],
|
||||
'label' => 'event.theme.label',
|
||||
'multiple' => false,
|
||||
])
|
||||
->setAllowedTypes('multiple', ['bool']);
|
||||
}
|
||||
|
||||
public function getParent(): string
|
||||
{
|
||||
return EntityType::class;
|
||||
}
|
||||
}
|
@@ -23,15 +23,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
*/
|
||||
class PickEventTypeType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* @var TranslatableStringHelper
|
||||
*/
|
||||
protected $translatableStringHelper;
|
||||
|
||||
public function __construct(TranslatableStringHelper $helper)
|
||||
{
|
||||
$this->translatableStringHelper = $helper;
|
||||
}
|
||||
public function __construct(protected TranslatableStringHelper $translatableStringHelper) {}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
|
@@ -17,15 +17,7 @@ use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
|
||||
class AdminMenuBuilder implements LocalMenuBuilderInterface
|
||||
{
|
||||
/**
|
||||
* @var AuthorizationCheckerInterface
|
||||
*/
|
||||
protected $authorizationChecker;
|
||||
|
||||
public function __construct(AuthorizationCheckerInterface $authorizationChecker)
|
||||
{
|
||||
$this->authorizationChecker = $authorizationChecker;
|
||||
}
|
||||
public function __construct(protected AuthorizationCheckerInterface $authorizationChecker) {}
|
||||
|
||||
public function buildMenu($menuId, MenuItem $menu, array $parameters): void
|
||||
{
|
||||
@@ -52,6 +44,14 @@ class AdminMenuBuilder implements LocalMenuBuilderInterface
|
||||
$menu->addChild('Role', [
|
||||
'route' => 'chill_event_admin_role',
|
||||
])->setExtras(['order' => 6530]);
|
||||
|
||||
$menu->addChild('event.theme.label', [
|
||||
'route' => 'chill_crud_event_theme_index',
|
||||
])->setExtras(['order' => 6540]);
|
||||
|
||||
$menu->addChild('event.budget.label', [
|
||||
'route' => 'chill_crud_event_budget_kind_index',
|
||||
])->setExtras(['order' => 6550]);
|
||||
}
|
||||
|
||||
public static function getMenuIds(): array
|
||||
|
@@ -88,6 +88,16 @@ final readonly class EventACLAwareRepository implements EventACLAwareRepositoryI
|
||||
$qb->andWhere('event.type IN (:event_types)');
|
||||
$qb->setParameter('event_types', $filters['event_types']);
|
||||
}
|
||||
|
||||
if (0 < count($filters['centers'] ?? [])) {
|
||||
$qb->andWhere('event.center IN (:centers)');
|
||||
$qb->setParameter('centers', $filters['centers']);
|
||||
}
|
||||
|
||||
if (0 < count($filters['responsables'] ?? [])) {
|
||||
$qb->andWhere('event.moderator IN (:responsables)');
|
||||
$qb->setParameter('responsables', $filters['responsables']);
|
||||
}
|
||||
}
|
||||
|
||||
public function buildQueryByAllViewable(array $filters): QueryBuilder
|
||||
|
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\EventBundle\Repository;
|
||||
|
||||
use Chill\EventBundle\Entity\EventBudgetElement;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<EventBudgetElement>
|
||||
*/
|
||||
class EventBudgetElementRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, EventBudgetElement::class);
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\EventBundle\Repository;
|
||||
|
||||
use Chill\EventBundle\Entity\EventBudgetKind;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<EventBudgetKind>
|
||||
*/
|
||||
class EventBudgetKindRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, EventBudgetKind::class);
|
||||
}
|
||||
|
||||
public function findByActive(): array
|
||||
{
|
||||
return $this->createQueryBuilder('e')
|
||||
->select('e')
|
||||
->where('e.active = True')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
public function findByType(string $type): array
|
||||
{
|
||||
return $this->createQueryBuilder('e')
|
||||
->select('e')
|
||||
->where('e.type = :type')
|
||||
->setParameter('type', $type)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\EventBundle\Repository;
|
||||
|
||||
use Chill\EventBundle\Entity\EventTheme;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<EventTheme>
|
||||
*/
|
||||
class EventThemeRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, EventTheme::class);
|
||||
}
|
||||
|
||||
public function findByActiveOrdered(): array
|
||||
{
|
||||
return $this->createQueryBuilder('t')
|
||||
->select('t')
|
||||
->where('t.isActive = True')
|
||||
->orderBy('t.ordering', 'ASC')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
}
|
@@ -55,3 +55,13 @@ form#export_tableur {
|
||||
-webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;
|
||||
transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
|
||||
}
|
||||
|
||||
.participation-list {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.participations-wrapper {
|
||||
background-color: whitesmoke;
|
||||
padding: 1rem;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
14
src/Bundle/ChillEventBundle/Resources/public/vuejs/App.vue
Normal file
14
src/Bundle/ChillEventBundle/Resources/public/vuejs/App.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<location />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Location from "ChillActivityAssets/vuejs/Activity/components/Location.vue";
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
Location,
|
||||
},
|
||||
};
|
||||
</script>
|
@@ -0,0 +1,6 @@
|
||||
import { createApp } from "vue";
|
||||
|
||||
import App from "./App.vue";
|
||||
import store from "./store";
|
||||
|
||||
createApp(App).use(store).mount("#event");
|
76
src/Bundle/ChillEventBundle/Resources/public/vuejs/store.js
Normal file
76
src/Bundle/ChillEventBundle/Resources/public/vuejs/store.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import "es6-promise/auto";
|
||||
import { createStore } from "vuex";
|
||||
|
||||
import prepareLocations from "ChillActivityAssets/vuejs/Activity/store.locations";
|
||||
import { whoami } from "ChillMainAssets/lib/api/user";
|
||||
import { postLocation } from "ChillActivityAssets/vuejs/Activity/api";
|
||||
|
||||
const debug = process.env.NODE_ENV !== "production";
|
||||
|
||||
const store = createStore({
|
||||
strict: debug,
|
||||
state: {
|
||||
activity: window.entity, // activity is the event entity in this case (re-using component from activity bundle)
|
||||
currentEvent: null,
|
||||
availableLocations: [],
|
||||
me: null,
|
||||
},
|
||||
getters: {},
|
||||
actions: {
|
||||
addAvailableLocationGroup({ commit }, payload) {
|
||||
commit("addAvailableLocationGroup", payload);
|
||||
},
|
||||
updateLocation({ commit }, value) {
|
||||
// console.log("### action: updateLocation", value);
|
||||
let hiddenLocation = document.getElementById(
|
||||
"chill_activitybundle_activity_location",
|
||||
);
|
||||
if (value.onthefly) {
|
||||
const body = {
|
||||
type: "location",
|
||||
name:
|
||||
value.name === "__AccompanyingCourseLocation__" ? null : value.name,
|
||||
locationType: {
|
||||
id: value.locationType.id,
|
||||
type: "location-type",
|
||||
},
|
||||
};
|
||||
if (value.address.id) {
|
||||
Object.assign(body, {
|
||||
address: {
|
||||
id: value.address.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
postLocation(body)
|
||||
.then((location) => (hiddenLocation.value = location.id))
|
||||
.catch((err) => {
|
||||
console.log(err.message);
|
||||
});
|
||||
} else {
|
||||
hiddenLocation.value = value.id;
|
||||
}
|
||||
commit("updateLocation", value);
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
setWhoAmiI(state, me) {
|
||||
state.me = me;
|
||||
},
|
||||
addAvailableLocationGroup(state, group) {
|
||||
state.availableLocations.push(group);
|
||||
},
|
||||
updateLocation(state, value) {
|
||||
// console.log("### mutation: updateLocation", value);
|
||||
state.activity.location = value;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
whoami().then((me) => {
|
||||
store.commit("setWhoAmiI", me);
|
||||
});
|
||||
|
||||
prepareLocations(store);
|
||||
|
||||
export default store;
|
@@ -0,0 +1,12 @@
|
||||
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
|
||||
|
||||
{% block title %}
|
||||
{% include('@ChillMain/CRUD/_edit_title.html.twig') %}
|
||||
{% endblock %}
|
||||
|
||||
{% block admin_content %}
|
||||
{% embed '@ChillMain/CRUD/_edit_content.html.twig' %}
|
||||
{% block content_form_actions_view %}{% endblock %}
|
||||
{% block content_form_actions_save_and_show %}{% endblock %}
|
||||
{% endembed %}
|
||||
{% endblock %}
|
@@ -0,0 +1,51 @@
|
||||
{% extends '@ChillMain/Admin/layoutWithVerticalMenu.html.twig' %}
|
||||
|
||||
{% block title %}{{ 'event.admin.title.Event budget element list'|trans }}{% endblock title %}
|
||||
|
||||
{% block admin_content %}
|
||||
|
||||
<h1>{{ 'event.admin.title.Event budget element list'|trans }}</h1>
|
||||
|
||||
<table class="records_list table table-bordered border-dark">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'Name'|trans }}</th>
|
||||
<th>{{ 'Type'|trans }}</th>
|
||||
<th>{{ 'Active'|trans }}</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for entity in entities %}
|
||||
<tr>
|
||||
<td>{{ entity.name|localize_translatable_string }}</td>
|
||||
<td>{{ entity.type.value|trans }}</td>
|
||||
<td style="text-align:center;">
|
||||
{%- if entity.isActive -%}
|
||||
<i class="fa fa-check-square-o"></i>
|
||||
{%- else -%}
|
||||
<i class="fa fa-square-o"></i>
|
||||
{%- endif -%}
|
||||
</td>
|
||||
<td>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ path('chill_crud_event_budget_kind_edit', { 'id': entity.id }) }}" class="btn btn-edit" title="{{ 'edit'|trans }}"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{ chill_pagination(paginator) }}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li>
|
||||
<a href="{{ path('chill_crud_event_budget_kind_new') }}" class="btn btn-create">
|
||||
{{ 'event.admin.new.Create a new budget kind'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endblock %}
|
@@ -0,0 +1,11 @@
|
||||
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
|
||||
|
||||
{% block title %}
|
||||
{% include('@ChillMain/CRUD/_new_title.html.twig') %}
|
||||
{% endblock %}
|
||||
|
||||
{% block admin_content %}
|
||||
{% embed '@ChillMain/CRUD/_new_content.html.twig' %}
|
||||
{% block content_form_actions_save_and_show %}{% endblock %}
|
||||
{% endembed %}
|
||||
{% endblock %}
|
@@ -0,0 +1,26 @@
|
||||
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
|
||||
|
||||
{% block title %}
|
||||
{% include('@ChillMain/CRUD/_edit_title.html.twig') %}
|
||||
{% endblock %}
|
||||
|
||||
{% block admin_content %}
|
||||
{% embed '@ChillMain/CRUD/_edit_content.html.twig' %}
|
||||
|
||||
{% block crud_content_form_rows %}
|
||||
{{ form_row(form.name) }}
|
||||
<div class="mb-3 row">
|
||||
<label class="col-form-label col-sm-4">
|
||||
{{ 'Parent'|trans }}
|
||||
</label>
|
||||
<div class="col-sm-8">
|
||||
{{ entity.parent|chill_entity_render_box }}
|
||||
</div>
|
||||
</div>
|
||||
{{ form_row(form.ordering) }}
|
||||
{{ form_row(form.isActive) }}
|
||||
{% endblock crud_content_form_rows %}
|
||||
|
||||
{% block content_form_actions_save_and_show %}{% endblock %}
|
||||
{% endembed %}
|
||||
{% endblock admin_content %}
|
@@ -0,0 +1,45 @@
|
||||
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
|
||||
|
||||
{% block admin_content %}
|
||||
{% embed '@ChillMain/CRUD/_index.html.twig' %}
|
||||
{% block table_entities_thead_tr %}
|
||||
<th>{{ 'Id'|trans }}</th>
|
||||
<th>{{ 'Title'|trans }}</th>
|
||||
<th>{{ 'Ordering'|trans }}</th>
|
||||
<th>{{ 'active'|trans }}</th>
|
||||
<th> </th>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_entities_tbody %}
|
||||
{% for entity in entities %}
|
||||
<tr>
|
||||
<td>{{ entity.id }}</td>
|
||||
<td>
|
||||
{{ entity|chill_entity_render_box }}
|
||||
</td>
|
||||
<td>{{ entity.ordering }}</td>
|
||||
<td style="text-align:center;">
|
||||
{%- if entity.isActive -%}
|
||||
<i class="fa fa-check-square-o"></i>
|
||||
{%- else -%}
|
||||
<i class="fa fa-square-o"></i>
|
||||
{%- endif -%}
|
||||
</td>
|
||||
<td>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_crud_event_theme_edit', { 'id': entity.id }) }}" class="btn btn-edit"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block actions_before %}
|
||||
<li class='cancel'>
|
||||
<a href="{{ path('chill_main_admin_central') }}" class="btn btn-cancel">{{'Back to the admin'|trans}}</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% endblock %}
|
@@ -0,0 +1,11 @@
|
||||
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
|
||||
|
||||
{% block title %}
|
||||
{% include('@ChillMain/CRUD/_new_title.html.twig') %}
|
||||
{% endblock %}
|
||||
|
||||
{% block admin_content %}
|
||||
{% embed '@ChillMain/CRUD/_new_content.html.twig' %}
|
||||
{% block content_form_actions_save_and_show %}{% endblock %}
|
||||
{% endembed %}
|
||||
{% endblock admin_content %}
|
@@ -0,0 +1,13 @@
|
||||
{% set reversed_parents = parents|reverse %}
|
||||
<span class="chill-entity entity-event-theme">
|
||||
<span class="badge bg-chill-l-gray text-dark">
|
||||
{%- for p in reversed_parents %}
|
||||
<span class="parent-{{ loop.revindex0 }}">
|
||||
{{ p.name|localize_translatable_string }}{{ options['default.separator'] }}
|
||||
</span>
|
||||
{%- endfor -%}
|
||||
<span class="child">
|
||||
{{ eventTheme.name|localize_translatable_string }}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
@@ -12,7 +12,7 @@
|
||||
'title' : 'Delete event'|trans,
|
||||
'confirm_question' : 'Are you sure you want to remove that event ?'|trans,
|
||||
'cancel_route' : activeRouteKey,
|
||||
'cancel_parameters' : { 'event_id' : event_id },
|
||||
'cancel_parameters' : { 'id' : id },
|
||||
'form' : delete_form
|
||||
}
|
||||
) }}
|
||||
|
@@ -17,10 +17,12 @@
|
||||
{{ form_row(edit_form.date) }}
|
||||
|
||||
{{ form_row(edit_form.type, { label: "Event type" }) }}
|
||||
{{ form_row(edit_form.themes) }}
|
||||
{{ form_row(edit_form.moderator) }}
|
||||
{{ form_row(edit_form.animatorsIntern) }}
|
||||
{{ form_row(edit_form.animatorsExtern) }}
|
||||
{{ form_row(edit_form.location) }}
|
||||
{{ form_row(edit_form.organizationCost) }}
|
||||
|
||||
{{ form_row(edit_form.budgetElements) }}
|
||||
{{ form_row(edit_form.comment) }}
|
||||
{{ form_row(edit_form.documents) }}
|
||||
|
||||
|
@@ -34,7 +34,7 @@
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
{# {% if is_granted('CHILL_EVENT_SEE_DETAILS', event) %} #}
|
||||
<a href="{{ path('chill_event__event_show', { 'event_id' : event.id } ) }}" class="btn btn-view">
|
||||
<a href="{{ path('chill_event__event_show', { 'id' : event.id } ) }}" class="btn btn-view">
|
||||
{{ 'See'|trans }}
|
||||
</a>
|
||||
{# {% endif %} #}
|
||||
|
@@ -53,7 +53,7 @@
|
||||
{% set returnLabel = 'Back to %person% events'|trans({ '%person%' : currentPerson } ) %}
|
||||
|
||||
{% if is_granted('CHILL_EVENT_SEE_DETAILS', participation.event) %}
|
||||
<a href="{{ path('chill_event__event_show', { 'event_id' : participation.event.id, 'return_path' : currentPath, 'return_label' : returnLabel } ) }}"
|
||||
<a href="{{ path('chill_event__event_show', { 'id' : participation.event.id, 'return_path' : currentPath, 'return_label' : returnLabel } ) }}"
|
||||
class="btn btn-primary btn-sm" title="{{ 'See details of the event'|trans }}">
|
||||
<i class="fa fa-fw fa-eye"></i>
|
||||
</a>
|
||||
|
@@ -1,25 +1,30 @@
|
||||
{% extends '@ChillEvent/layout.html.twig' %} {% block js %}
|
||||
{{ encore_entry_script_tags("mod_async_upload") }}
|
||||
{{ encore_entry_script_tags("mod_pickentity_type") }}
|
||||
{% extends '@ChillEvent/layout.html.twig' %}
|
||||
|
||||
{% endblock %} {% block css %}
|
||||
{% block css %}
|
||||
{{ encore_entry_link_tags("mod_async_upload") }}
|
||||
{{ encore_entry_link_tags("mod_pickentity_type") }}
|
||||
{{ encore_entry_link_tags('vue_event') }}
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %} {% block title 'Event creation'|trans %} {% block event_content
|
||||
-%}
|
||||
{% block title 'Event creation'|trans %}
|
||||
|
||||
{% block event_content -%}
|
||||
<div class="col-10">
|
||||
<h1>{{ "Event creation" | trans }}</h1>
|
||||
|
||||
{{ form_start(form) }}
|
||||
{{ form_errors(form) }}
|
||||
{{ form_row(form.circle) }}
|
||||
{{ form_row(form.name) }}
|
||||
{{ form_row(form.circle) }}
|
||||
{{ form_row(form.date) }}
|
||||
{{ form_row(form.type, { label: "Event type" }) }}
|
||||
{{ form_row(form.themes) }}
|
||||
{{ form_row(form.moderator) }}
|
||||
{{ form_row(form.animatorsIntern) }}
|
||||
{{ form_row(form.animatorsExtern) }}
|
||||
{{ form_row(form.location) }}
|
||||
{{ form_row(form.organizationCost) }}
|
||||
<div id="location"></div>
|
||||
{{ form_row(form.budgetElements) }}
|
||||
{{ form_row(form.comment) }}
|
||||
{{ form_row(form.documents) }}
|
||||
|
||||
@@ -40,5 +45,18 @@
|
||||
</ul>
|
||||
|
||||
{{ form_end(form) }}
|
||||
|
||||
<div id="event"></div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ encore_entry_script_tags("mod_async_upload") }}
|
||||
{{ encore_entry_script_tags("mod_pickentity_type") }}
|
||||
{{ encore_entry_script_tags('vue_event') }}
|
||||
<script type="text/javascript">
|
||||
window.entity = {{ entity_json|json_encode|raw }};
|
||||
{% if app.user.currentLocation is not null %}window.default_location_id = {{ app.user.currentLocation.id }};{% endif %}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@@ -11,7 +11,7 @@ block js %}
|
||||
|
||||
{{ filter | chill_render_filter_order_helper }}
|
||||
|
||||
{# {% if is_granted('CHILL_EVENT_CREATE') %} #}
|
||||
{% if is_granted('CHILL_EVENT_CREATE') %}
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a
|
||||
@@ -25,7 +25,9 @@ block js %}
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
{# {% endif %} #} {% if events|length > 0 %}
|
||||
{% endif %}
|
||||
|
||||
{% if events|length > 0 %}
|
||||
<div class="flex-table">
|
||||
{% for e in events %}
|
||||
<div class="item-bloc">
|
||||
@@ -41,6 +43,12 @@ block js %}
|
||||
{{ e.moderator | chill_entity_render_box }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
{% for t in e.themes %}
|
||||
<span>{{ t|chill_entity_render_box }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<div class="container" style="text-align: right">
|
||||
@@ -48,11 +56,12 @@ block js %}
|
||||
<p>
|
||||
{{ 'count participations to this event'|trans({'count': e.participations|length}) }}
|
||||
</p>
|
||||
<p>{{ "center"|trans }}: {{ e.center.name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if e.participations|length > 0 %}
|
||||
<div class="item-row separator">
|
||||
<div class="participation-list item-row separator">
|
||||
<strong>{{ "Participations" | trans }} : </strong>
|
||||
{% for part in e.participations|slice(0, 5) %} {% include
|
||||
'@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
||||
@@ -106,7 +115,7 @@ block js %}
|
||||
href="{{
|
||||
chill_path_add_return_path(
|
||||
'chill_event__event_show',
|
||||
{ event_id: e.id }
|
||||
{ id: e.id }
|
||||
)
|
||||
}}"
|
||||
class="btn btn-show"
|
||||
|
@@ -16,6 +16,16 @@
|
||||
{{ encore_entry_link_tags('mod_document_action_buttons_group') }}
|
||||
{% endblock %}
|
||||
|
||||
{% macro insert_onthefly(type, entity, parent = null) %}
|
||||
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
||||
action: 'show', displayBadge: true,
|
||||
targetEntity: { name: type, id: entity.id },
|
||||
buttonText: entity|chill_entity_render_string,
|
||||
isDead: entity.deathdate is defined and entity.deathdate is not null,
|
||||
parent: parent
|
||||
} %}
|
||||
{% endmacro %}
|
||||
|
||||
{% block event_content -%}
|
||||
<div class="col-10">
|
||||
<h1>{{ 'Details of an event'|trans }}</h1>
|
||||
@@ -26,6 +36,10 @@
|
||||
<th>{{ 'Circle'|trans }}</th>
|
||||
<td>{{ event.circle.name|localize_translatable_string }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ 'Center'|trans }}</th>
|
||||
<td>{{ event.center.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ 'Name'|trans }}</th>
|
||||
<td>{{ event.name }}</td>
|
||||
@@ -39,12 +53,32 @@
|
||||
<td>{{ event.type.name|localize_translatable_string }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ 'Moderator'|trans }}</th>
|
||||
<td>{{ event.moderator|trans|default('-') }}</td>
|
||||
<th>{{ 'event.theme.label'|trans }}
|
||||
<td>
|
||||
{% for t in event.themes %}
|
||||
{{ t|chill_entity_render_box }}
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ 'event.fields.organizationCost'|trans }}</th>
|
||||
<td>{{ event.organizationCost|format_currency('EUR') }}</td>
|
||||
<th>{{ 'Moderator'|trans }}</th>
|
||||
<td>{{ event.moderator|chill_entity_render_string }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ 'event.animators.intern'|trans }}</th>
|
||||
<td>
|
||||
{% for a in event.animatorsIntern %}
|
||||
{{ _self.insert_onthefly('user', a) }}
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ 'event.animators.extern'|trans }}</th>
|
||||
<td>
|
||||
{% for a in event.animatorsExtern %}
|
||||
{{ _self.insert_onthefly('thirdparty', a) }}
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ 'event.fields.location'|trans }}</th>
|
||||
@@ -60,6 +94,77 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="budget-wrapper" style="background-color: whitesmoke; padding: 1rem; margin-bottom: .5rem;">
|
||||
{% set resources = event.budgetElements|filter(e => e.kind.type.value == 'Resource') %}
|
||||
{% set charges = event.budgetElements|filter(e => e.kind.type.value == 'Charge') %}
|
||||
|
||||
<h2>Budget de l'événement</h2>
|
||||
|
||||
<h3>Ressources</h3>
|
||||
{% if resources is not empty %}
|
||||
<table class="table table-bordered border-dark align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'event.budget.resource type'|trans }}</th>
|
||||
<th>{{ 'event.budget.amount'|trans }}</th>
|
||||
<th>{{ 'event.budget.comment'|trans }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% set totalResources = 0 %}
|
||||
{% for res in resources %}
|
||||
<tr>
|
||||
<td>{{ res.kind.name|localize_translatable_string }}</td>
|
||||
<td>{{ res.amount|format_currency('EUR') }}</td>
|
||||
<td>{{ res.comment.comment|chill_print_or_message(null, 'blockquote') }}</td>
|
||||
</tr>
|
||||
{% set totalResources = totalResources + res.amount %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th>Total</th>
|
||||
<td>{{ totalResources|format_currency('EUR') }}</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="chill-no-data-statement">{{ 'event.budget.no elements'|trans }}</p>
|
||||
{% endif %}
|
||||
|
||||
<h3>Charges</h3>
|
||||
{% if charges is not empty %}
|
||||
<table class="table table-bordered border-dark align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'event.budget.charge type'|trans }}</th>
|
||||
<th>{{ 'event.budget.amount'|trans }}</th>
|
||||
<th>{{ 'event.budget.comment'|trans }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% set totalCharges = 0 %}
|
||||
{% for chg in charges %}
|
||||
<tr>
|
||||
<td>{{ chg.kind.name|localize_translatable_string }}</td>
|
||||
<td>{{ chg.amount|format_currency('EUR') }}</td>
|
||||
<td>{{ chg.comment.comment|chill_print_or_message(null, 'blockquote') }}</td>
|
||||
</tr>
|
||||
{% set totalCharges = totalCharges + chg.amount %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th>Total</th>
|
||||
<td>{{ totalCharges|format_currency('EUR') }}</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="chill-no-data-statement">{{ 'event.budget.no elements'|trans }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if event.documents|length > 0 %}
|
||||
<div>
|
||||
<p><strong>{{ 'event.fields.documents'|trans }}</strong></p>
|
||||
@@ -80,6 +185,97 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="participations-wrapper">
|
||||
<h2>{{ 'Participations'|trans }}</h2>
|
||||
{% set count = event.participations|length %}
|
||||
<p class="chill-no-data-statement">{{ 'count participations to this event'|trans({'count': count}) }}</p>
|
||||
|
||||
{% if count > 0 %}
|
||||
<table class="table table-bordered border-dark align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'Person'|trans }}</th>
|
||||
<th>{{ 'Role'|trans }}</th>
|
||||
<th>{{ 'Status'|trans }}</th>
|
||||
<th>{{ 'Last update'|trans }}</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for participation in event.participations %}
|
||||
<tr>
|
||||
<td>
|
||||
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
||||
targetEntity: { name: 'person', id: participation.person.id },
|
||||
action: 'show',
|
||||
displayBadge: true,
|
||||
buttonText: participation.person|chill_entity_render_string,
|
||||
isDead: participation.person.deathdate is not null
|
||||
} %}
|
||||
</td>
|
||||
<td>{{ participation.role.name|localize_translatable_string }}</td>
|
||||
<td>{{ participation.status.name|localize_translatable_string }}</td>
|
||||
<td>{{ participation.lastUpdate|ago }} {# sf4 check: filter 'time_diff' is abandoned,
|
||||
alternative: knplabs/knp-time-bundle provide filter 'ago' #}
|
||||
<i class="fa fa-info-circle" title="{{ participation.lastUpdate|format_date("long")|escape('html_attr') }}"></i>
|
||||
</td>
|
||||
<td>
|
||||
<ul class="record_actions">
|
||||
{% if is_granted('CHILL_EVENT_PARTICIPATION_UPDATE', participation) %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_event_participation_edit', { 'participation_id' : participation.id }, false, 'See'|trans ) }}"
|
||||
class="btn btn-edit" title="{{ 'Edit'|trans }}"></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ path('chill_event_participation_delete', {'participation_id' : participation.id } ) }}"
|
||||
class="btn btn-delete" title="{{ 'Delete'|trans }}"></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
{{ form_start(form_add_participation_by_person) }}
|
||||
<div class="input-group">
|
||||
{{ form_widget(form_add_participation_by_person.person_id, { 'attr' : {
|
||||
'class' : 'custom-select',
|
||||
'style': 'min-width: 15em; max-width: 18em; display: inline-block;'
|
||||
}} ) }}
|
||||
</div>
|
||||
<input type="hidden" name="returnPath" value="{{ app.request.requestUri }}" />
|
||||
{{ form_end(form_add_participation_by_person) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="record_actions">
|
||||
{% if count > 0 %}
|
||||
<li>
|
||||
{{ form_start(form_export, {'attr': {'id': 'export_tableur'}}) }}
|
||||
<div class="input-group">
|
||||
{{ form_widget(form_export.format, { 'attr' : { 'class': 'custom-select' } }) }}
|
||||
<div class="input-group-append">
|
||||
{{ form_widget(form_export.submit, { 'attr' : { 'class': 'btn btn-save' } }) }}
|
||||
</div>
|
||||
<a class="bt-"></a>
|
||||
</div>
|
||||
{{ form_rest(form_export) }}
|
||||
{{ form_end(form_export) }}
|
||||
</li>
|
||||
<li><a href="{{ path('chill_event_participation_edit_multiple', { 'event_id' : event.id } ) }}" class="btn btn-edit">{{ 'Edit all the participations'|trans }}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="post_show">
|
||||
{{ chill_delegated_block('block_footer_show', { 'event': event }) }}
|
||||
</div>
|
||||
|
||||
<ul class="record_actions">
|
||||
|
||||
@@ -100,97 +296,5 @@
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<h2>{{ 'Participations'|trans }}</h2>
|
||||
{% set count = event.participations|length %}
|
||||
<p>{{ 'count participations to this event'|trans({'count': count}) }}</p>
|
||||
|
||||
{% if count > 0 %}
|
||||
<table class="table table-bordered border-dark align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'Person'|trans }}</th>
|
||||
<th>{{ 'Role'|trans }}</th>
|
||||
<th>{{ 'Status'|trans }}</th>
|
||||
<th>{{ 'Last update'|trans }}</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for participation in event.participations %}
|
||||
<tr>
|
||||
<td>
|
||||
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
||||
targetEntity: { name: 'person', id: participation.person.id },
|
||||
action: 'show',
|
||||
displayBadge: true,
|
||||
buttonText: participation.person|chill_entity_render_string,
|
||||
isDead: participation.person.deathdate is not null
|
||||
} %}
|
||||
</td>
|
||||
<td>{{ participation.role.name|localize_translatable_string }}</td>
|
||||
<td>{{ participation.status.name|localize_translatable_string }}</td>
|
||||
<td>{{ participation.lastUpdate|ago }} {# sf4 check: filter 'time_diff' is abandoned,
|
||||
alternative: knplabs/knp-time-bundle provide filter 'ago' #}
|
||||
<i class="fa fa-info-circle" title="{{ participation.lastUpdate|format_date("long")|escape('html_attr') }}"></i>
|
||||
</td>
|
||||
<td>
|
||||
<ul class="record_actions">
|
||||
{% if is_granted('CHILL_EVENT_PARTICIPATION_UPDATE', participation) %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_event_participation_edit', { 'participation_id' : participation.id }, false, 'See'|trans ) }}"
|
||||
class="btn btn-edit" title="{{ 'Edit'|trans }}"></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ path('chill_event_participation_delete', {'participation_id' : participation.id } ) }}"
|
||||
class="btn btn-delete" title="{{ 'Delete'|trans }}"></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
{% endif %}
|
||||
|
||||
<ul class="record_actions">
|
||||
{% if count > 0 %}
|
||||
<li><a href="{{ path('chill_event_participation_edit_multiple', { 'event_id' : event.id } ) }}" class="btn btn-edit">{{ 'Edit all the participations'|trans }}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
<div class="row" style="margin-bottom: 10em;">
|
||||
<div class="col-8">
|
||||
{{ form_start(form_add_participation_by_person) }}
|
||||
<div class="input-group">
|
||||
{{ form_widget(form_add_participation_by_person.person_id, { 'attr' : {
|
||||
'class' : 'custom-select',
|
||||
'style': 'min-width: 15em; max-width: 18em; display: inline-block;'
|
||||
}} ) }}
|
||||
</div>
|
||||
<input type="hidden" name="returnPath" value="{{ app.request.requestUri }}" />
|
||||
{{ form_end(form_add_participation_by_person) }}
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
{{ form_start(form_export, {'attr': {'id': 'export_tableur'}}) }}
|
||||
<div class="input-group">
|
||||
{{ form_widget(form_export.format, { 'attr' : { 'class': 'custom-select' } }) }}
|
||||
<div class="input-group-append">
|
||||
{{ form_widget(form_export.submit, { 'attr' : { 'class': 'btn btn-save' } }) }}
|
||||
</div>
|
||||
<a class="bt-"></a>
|
||||
</div>
|
||||
{{ form_rest(form_export) }}
|
||||
{{ form_end(form_export) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="post_show">
|
||||
{{ chill_delegated_block('block_footer_show', { 'event': event }) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@@ -11,7 +11,7 @@
|
||||
'title' : 'Remove participation'|trans,
|
||||
'confirm_question' : 'Are you sure you want to remove that participation ?'|trans,
|
||||
'cancel_route' : activeRouteKey,
|
||||
'cancel_parameters' : { 'event_id' : event_id },
|
||||
'cancel_parameters' : { 'id' : event_id },
|
||||
'form' : delete_form
|
||||
}
|
||||
) }}
|
||||
|
@@ -33,7 +33,7 @@
|
||||
{% set returnPath = app.request.get('return_path') %}
|
||||
{% set returnLabel = app.request.get('return_label') %}
|
||||
|
||||
<a href="{{ returnPath |default( path('chill_event__event_show', { 'event_id' : participation.event.id } )) }}" class="btn btn-cancel">
|
||||
<a href="{{ returnPath |default( path('chill_event__event_show', { 'id' : participation.event.id } )) }}" class="btn btn-cancel">
|
||||
{{ returnLabel |default('Back to the event'|trans) }}
|
||||
</a>
|
||||
</li>
|
||||
|
@@ -34,7 +34,7 @@
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ path('chill_event__event_show', { 'event_id' : participation.event.id } ) }}" class="btn btn-cancel">
|
||||
<a href="{{ path('chill_event__event_show', { 'id' : participation.event.id } ) }}" class="btn btn-cancel">
|
||||
{{ 'Back to the event'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
|
@@ -54,9 +54,9 @@ class EventVoter extends AbstractChillVoter implements ProvideRoleHierarchyInter
|
||||
) {
|
||||
$this->voterHelper = $voterHelperFactory
|
||||
->generate(self::class)
|
||||
->addCheckFor(null, [self::SEE])
|
||||
->addCheckFor(null, [self::SEE, self::CREATE])
|
||||
->addCheckFor(Event::class, [...self::ROLES])
|
||||
->addCheckFor(Person::class, [self::SEE, self::CREATE])
|
||||
->addCheckFor(Person::class, [self::SEE])
|
||||
->addCheckFor(Center::class, [self::STATS])
|
||||
->build();
|
||||
}
|
||||
|
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\EventBundle\Templating\Entity;
|
||||
|
||||
use Chill\EventBundle\Entity\EventTheme;
|
||||
use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Error\RuntimeError;
|
||||
use Twig\Error\SyntaxError;
|
||||
|
||||
/**
|
||||
* @implements ChillEntityRenderInterface<EventTheme>
|
||||
*/
|
||||
class EventThemeRender implements ChillEntityRenderInterface
|
||||
{
|
||||
public const AND_CHILDREN_MENTION = 'show_and_children_mention';
|
||||
|
||||
public const DEFAULT_ARGS = [
|
||||
self::SEPARATOR_KEY => ' > ',
|
||||
self::SHOW_AND_CHILDREN => false,
|
||||
self::AND_CHILDREN_MENTION => 'event_theme.and children',
|
||||
];
|
||||
|
||||
public const SEPARATOR_KEY = 'default.separator';
|
||||
|
||||
/**
|
||||
* Show a mention "and children" on each EventTheme, if the event theme
|
||||
* has at least one child.
|
||||
*/
|
||||
public const SHOW_AND_CHILDREN = 'show_and_children';
|
||||
|
||||
public function __construct(private readonly TranslatableStringHelper $translatableStringHelper, private readonly \Twig\Environment $engine, private readonly TranslatorInterface $translator) {}
|
||||
|
||||
/**
|
||||
* @throws RuntimeError
|
||||
* @throws SyntaxError
|
||||
* @throws LoaderError
|
||||
*/
|
||||
public function renderBox($entity, array $options): string
|
||||
{
|
||||
$options = array_merge(self::DEFAULT_ARGS, $options);
|
||||
// give some help to twig: an array of parents
|
||||
$parents = $this->buildParents($entity);
|
||||
|
||||
return $this
|
||||
->engine
|
||||
->render(
|
||||
'@ChillEvent/Entity/event_theme.html.twig',
|
||||
[
|
||||
'eventTheme' => $entity,
|
||||
'parents' => $parents,
|
||||
'options' => $options,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function renderString($entity, array $options): string
|
||||
{
|
||||
/** @var EventTheme $entity */
|
||||
$options = array_merge(self::DEFAULT_ARGS, $options);
|
||||
|
||||
$titles = [$this->translatableStringHelper->localize($entity->getName())];
|
||||
|
||||
// loop to parent, until root
|
||||
while ($entity->hasParent()) {
|
||||
$entity = $entity->getParent();
|
||||
$titles[] = $this->translatableStringHelper->localize(
|
||||
$entity->getName()
|
||||
);
|
||||
}
|
||||
|
||||
$titles = \array_reverse($titles);
|
||||
|
||||
$title = \implode($options[self::SEPARATOR_KEY], $titles);
|
||||
|
||||
if ($options[self::SHOW_AND_CHILDREN] && $entity->hasChildren()) {
|
||||
$title .= ' ('.$this->translator->trans($options[self::AND_CHILDREN_MENTION]).')';
|
||||
}
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
public function supports($entity, array $options): bool
|
||||
{
|
||||
return $entity instanceof EventTheme;
|
||||
}
|
||||
|
||||
private function buildParents(EventTheme $entity): array
|
||||
{
|
||||
$parents = [];
|
||||
|
||||
while ($entity->hasParent()) {
|
||||
$entity = $parents[] = $entity->getParent();
|
||||
}
|
||||
|
||||
return $parents;
|
||||
}
|
||||
}
|
@@ -1,3 +1,10 @@
|
||||
module.exports = function (encore, entries) {
|
||||
entries.push(__dirname + "/Resources/public/chill/index.js");
|
||||
|
||||
encore.addEntry(
|
||||
"vue_event",
|
||||
__dirname + "/Resources/public/vuejs/index.js",
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
@@ -10,3 +10,11 @@ services:
|
||||
Chill\EventBundle\Menu\:
|
||||
resource: '../Menu/'
|
||||
tags: ['chill.menu_builder']
|
||||
|
||||
|
||||
Chill\EventBundle\Templating\Entity\:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
resource: '../Templating/Entity'
|
||||
tags:
|
||||
- 'chill.render_entity'
|
||||
|
@@ -8,6 +8,9 @@ services:
|
||||
Chill\EventBundle\Export\Export\CountEvents:
|
||||
tags:
|
||||
- { name: chill.export, alias: 'count_events' }
|
||||
Chill\EventBundle\Export\Export\ListEvents:
|
||||
tags:
|
||||
- { name: chill.export, alias: 'list_events' }
|
||||
Chill\EventBundle\Export\Export\CountEventParticipations:
|
||||
tags:
|
||||
- { name: chill.export, alias: 'count_event_participants' }
|
||||
|
@@ -31,3 +31,29 @@ services:
|
||||
Chill\EventBundle\Form\Type\PickEventType:
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
Chill\EventBundle\Form\EventThemeType:
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
Chill\EventBundle\Form\Type\PickEventThemeType:
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
Chill\EventBundle\Form\EventType:
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
Chill\EventBundle\Form\EventBudgetKindType:
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
Chill\EventBundle\Form\AddEventBudgetElementType:
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
Chill\EventBundle\Form\Type\PickAnimatorType:
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
|
||||
|
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\Migrations\Event;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Add event theme entity.
|
||||
*/
|
||||
final class Version20250428092611 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add an event theme entity';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE SEQUENCE chill_event_event_theme_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||
$this->addSql('CREATE TABLE chill_event_event_theme (id INT NOT NULL, parent_id INT DEFAULT NULL, isActive BOOLEAN NOT NULL, name JSON NOT NULL, ordering DOUBLE PRECISION DEFAULT \'0.0\' NOT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('CREATE INDEX IDX_80D7C6B0727ACA70 ON chill_event_event_theme (parent_id)');
|
||||
$this->addSql('ALTER TABLE chill_event_event_theme ADD CONSTRAINT FK_80D7C6B0727ACA70 FOREIGN KEY (parent_id) REFERENCES chill_event_event_theme (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_event_event_theme DROP CONSTRAINT FK_80D7C6B0727ACA70');
|
||||
$this->addSql('DROP TABLE chill_event_event_theme');
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\Migrations\Event;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250429062911 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add themes property to event';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE chill_event_eventtheme (event_id INT NOT NULL, eventtheme_id INT NOT NULL, PRIMARY KEY(event_id, eventtheme_id))');
|
||||
$this->addSql('CREATE INDEX IDX_8D75029771F7E88B ON chill_event_eventtheme (event_id)');
|
||||
$this->addSql('CREATE INDEX IDX_8D750297A81D3C55 ON chill_event_eventtheme (eventtheme_id)');
|
||||
$this->addSql('ALTER TABLE chill_event_eventtheme ADD CONSTRAINT FK_8D75029771F7E88B FOREIGN KEY (event_id) REFERENCES chill_event_event (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_event_eventtheme ADD CONSTRAINT FK_8D750297A81D3C55 FOREIGN KEY (eventtheme_id) REFERENCES chill_event_event_theme (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_event_eventtheme DROP CONSTRAINT FK_8D75029771F7E88B');
|
||||
$this->addSql('ALTER TABLE chill_event_eventtheme DROP CONSTRAINT FK_8D750297A81D3C55');
|
||||
$this->addSql('DROP TABLE chill_event_eventtheme');
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\Migrations\Event;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20250505120818 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add budget kind admin entity';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE SEQUENCE chill_event_budget_kind_id_seq INCREMENT BY 1 MINVALUE 1 START 1
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE chill_event_budget_kind (id INT NOT NULL, isActive BOOLEAN DEFAULT true NOT NULL, type VARCHAR(255) NOT NULL, name JSONB DEFAULT '{}' NOT NULL, PRIMARY KEY(id))
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP SEQUENCE chill_event_budget_kind_id_seq CASCADE
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE chill_event_budget_kind
|
||||
SQL);
|
||||
}
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\Migrations\Event;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20250506114531 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Create event budget element entity';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE SEQUENCE chill_event_budget_element_id_seq INCREMENT BY 1 MINVALUE 1 START 1
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE chill_event_budget_element (id INT NOT NULL, event_id INT DEFAULT NULL, kind_id INT DEFAULT NULL, amount NUMERIC(10, 2) NOT NULL, comment_budget_element_comment TEXT DEFAULT NULL, comment_budget_element_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, comment_budget_element_userId INT DEFAULT NULL, PRIMARY KEY(id))
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_BA25859071F7E88B ON chill_event_budget_element (event_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_BA25859030602CA9 ON chill_event_budget_element (kind_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_event_budget_element ADD CONSTRAINT FK_BA25859071F7E88B FOREIGN KEY (event_id) REFERENCES chill_event_event (id) NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_event_budget_element ADD CONSTRAINT FK_BA25859030602CA9 FOREIGN KEY (kind_id) REFERENCES chill_event_budget_kind (id) NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP SEQUENCE chill_event_budget_element_id_seq CASCADE
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_event_budget_element DROP CONSTRAINT FK_BA25859071F7E88B
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_event_budget_element DROP CONSTRAINT FK_BA25859030602CA9
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE chill_event_budget_element
|
||||
SQL);
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\Migrations\Event;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20250507073301 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add animators field to an event';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE chill_event_thirdparty (event_id INT NOT NULL, thirdparty_id INT NOT NULL, PRIMARY KEY(event_id, thirdparty_id))
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_9946573E71F7E88B ON chill_event_thirdparty (event_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_9946573EC7D3A8E6 ON chill_event_thirdparty (thirdparty_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_event_thirdparty ADD CONSTRAINT FK_9946573E71F7E88B FOREIGN KEY (event_id) REFERENCES chill_event_event (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_event_thirdparty ADD CONSTRAINT FK_9946573EC7D3A8E6 FOREIGN KEY (thirdparty_id) REFERENCES chill_3party.third_party (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_event_thirdparty DROP CONSTRAINT FK_9946573E71F7E88B
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_event_thirdparty DROP CONSTRAINT FK_9946573EC7D3A8E6
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE chill_event_thirdparty
|
||||
SQL);
|
||||
}
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\Migrations\Event;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20250702144312 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add internal and external animators to event entity';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE chill_event_animatorsintern (event_id INT NOT NULL, user_id INT NOT NULL, PRIMARY KEY(event_id, user_id))
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_E699558771F7E88B ON chill_event_animatorsintern (event_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_E6995587A76ED395 ON chill_event_animatorsintern (user_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE chill_event_animatorsextern (event_id INT NOT NULL, thirdparty_id INT NOT NULL, PRIMARY KEY(event_id, thirdparty_id))
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_7EFBF7DE71F7E88B ON chill_event_animatorsextern (event_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_7EFBF7DEC7D3A8E6 ON chill_event_animatorsextern (thirdparty_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_event_animatorsintern ADD CONSTRAINT FK_E699558771F7E88B FOREIGN KEY (event_id) REFERENCES chill_event_event (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_event_animatorsintern ADD CONSTRAINT FK_E6995587A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_event_animatorsextern ADD CONSTRAINT FK_7EFBF7DE71F7E88B FOREIGN KEY (event_id) REFERENCES chill_event_event (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_event_animatorsextern ADD CONSTRAINT FK_7EFBF7DEC7D3A8E6 FOREIGN KEY (thirdparty_id) REFERENCES chill_3party.third_party (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_event_animatorsintern DROP CONSTRAINT FK_E699558771F7E88B
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_event_animatorsintern DROP CONSTRAINT FK_E6995587A76ED395
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_event_animatorsextern DROP CONSTRAINT FK_7EFBF7DE71F7E88B
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_event_animatorsextern DROP CONSTRAINT FK_7EFBF7DEC7D3A8E6
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE chill_event_animatorsintern
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE chill_event_animatorsextern
|
||||
SQL);
|
||||
}
|
||||
}
|
@@ -7,7 +7,8 @@ Participation: Participation
|
||||
Participations: Participations
|
||||
Status: Statut
|
||||
Last update: Dernière mise à jour
|
||||
Moderator: Animateur
|
||||
Moderator: Responsable
|
||||
Animators: Animateurs
|
||||
|
||||
#CRUD event
|
||||
Details of an event: Détails d'un événement
|
||||
@@ -74,7 +75,7 @@ Show the event: Voir l'événement
|
||||
Subscribe an event: Ajouter un événement
|
||||
Pick an event: Choisir un événement
|
||||
Pick a type of event: Choisir un type d'événement
|
||||
Pick a moderator: Choisir un animateur
|
||||
Pick a moderator: Choisir un responsable
|
||||
|
||||
# exports
|
||||
Select a format: Choisir un format
|
||||
@@ -128,15 +129,70 @@ Create a new type: Créer un nouveau type
|
||||
Create a new status: Créer un nouveau statut
|
||||
|
||||
event:
|
||||
admin:
|
||||
title:
|
||||
Event budget element list: Liste des elements du budget pour un évenement
|
||||
Select budget type: Selectionner le type d'element du budget
|
||||
new:
|
||||
Create a new budget kind: Créér un nouveau element de budget
|
||||
theme:
|
||||
label: Thématiques
|
||||
fields:
|
||||
organizationCost: Coût d'organisation
|
||||
location: Localisation
|
||||
documents: Documents
|
||||
internal animators: Animateurs internes
|
||||
external animators: Animateurs externes
|
||||
form:
|
||||
organisationCost_help: Coût d'organisation pour la structure. Utile pour les statistiques.
|
||||
add_document: Ajouter un document
|
||||
remove_document: Supprimer le document
|
||||
Select one or more themes: Selectionnez une ou plusieurs thématiques
|
||||
filter:
|
||||
event_types: Par types d'événement
|
||||
event_dates: Par date d'événement
|
||||
center: Par centre
|
||||
by_responsable: Par responsable
|
||||
pick_responsable: Filtrer par responsables
|
||||
budget:
|
||||
resources: Ressources
|
||||
charges: Charges
|
||||
label: Elements de budget d'un évenement
|
||||
Select a budget element kind: Selectionner un element de budget
|
||||
no elements: Il y a aucun element
|
||||
resource type: Ressource
|
||||
charge type: Charge
|
||||
amount: Montant
|
||||
comment: Commentaire
|
||||
animators:
|
||||
intern: Animateurs internes
|
||||
extern: Animateurs externes
|
||||
|
||||
crud:
|
||||
event_theme:
|
||||
title_new: Créér une nouvelle thématique
|
||||
title_edit: Modifier la thématique
|
||||
index:
|
||||
title: Liste des thématiques
|
||||
add_new: Créér une nouvelle thématique
|
||||
event_budget_kind:
|
||||
title_new: Créér un nouveau element de budget
|
||||
|
||||
export:
|
||||
event:
|
||||
list:
|
||||
title: Liste des évenements
|
||||
description: Crée la liste des évenements en fonction de différents paramètres.
|
||||
|
||||
event_id: Identifiant
|
||||
event_name: Nom
|
||||
event_date: Date
|
||||
event_type: Type d'évenement
|
||||
event_center: Centre
|
||||
event_moderator: Responsable
|
||||
event_participants_count: Nombre de participants
|
||||
event_location: Localisation
|
||||
event_budget_resources: Ressources
|
||||
event_budget_charges: Charges
|
||||
event_animators: Animateurs
|
||||
event_themes: Thématiques
|
||||
|
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Command;
|
||||
|
||||
use Chill\MainBundle\Security\RoleDumper;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
#[AsCommand(name: 'chill:main:dump-list-permissions', description: 'Print a markdown reference of permissions (roles) grouped by title with dependencies).')]
|
||||
final class DumpListPermissionsCommand extends Command
|
||||
{
|
||||
public function __construct(private readonly RoleDumper $roleDumper)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$markdown = $this->roleDumper->dumpAsMarkdown();
|
||||
$output->writeln($markdown);
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
@@ -48,6 +48,7 @@ class AbsenceController extends AbstractController
|
||||
$user = $this->security->getUser();
|
||||
|
||||
$user->setAbsenceStart(null);
|
||||
$user->setAbsenceEnd(null);
|
||||
$em = $this->managerRegistry->getManager();
|
||||
$em->flush();
|
||||
|
||||
|
@@ -345,7 +345,7 @@ class ExportController extends AbstractController
|
||||
* @param array $dataExport Raw data from export step
|
||||
* @param array $dataFormatter Raw data from formatter step
|
||||
*/
|
||||
private function buildExportDataForNormalization(string $alias, ?array $dataCenters, array $dataExport, array $dataFormatter, ?SavedExport $savedExport): array
|
||||
private function buildExportDataForNormalization(string $alias, ?array $dataCenters, array $dataExport, ?array $dataFormatter, ?SavedExport $savedExport): array
|
||||
{
|
||||
if ($this->filterStatsByCenters) {
|
||||
$formCenters = $this->createCreateFormExport($alias, 'generate_centers', [], null);
|
||||
@@ -365,7 +365,7 @@ class ExportController extends AbstractController
|
||||
$formExport->submit($dataExport);
|
||||
$dataExport = $formExport->getData();
|
||||
|
||||
if (\count($dataFormatter) > 0) {
|
||||
if (is_array($dataFormatter) && \count($dataFormatter) > 0) {
|
||||
$formFormatter = $this->createCreateFormExport(
|
||||
$alias,
|
||||
'generate_formatter',
|
||||
@@ -381,7 +381,7 @@ class ExportController extends AbstractController
|
||||
'export' => $dataExport['export']['export'] ?? [],
|
||||
'filters' => $dataExport['export']['filters'] ?? [],
|
||||
'aggregators' => $dataExport['export']['aggregators'] ?? [],
|
||||
'pick_formatter' => $dataExport['export']['pick_formatter']['alias'],
|
||||
'pick_formatter' => ($dataExport['export']['pick_formatter'] ?? [])['alias'] ?? '',
|
||||
'formatter' => $dataFormatter['formatter'] ?? [],
|
||||
];
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@ use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use Chill\MainBundle\Validation\Constraint\PhonenumberConstraint;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* User.
|
||||
@@ -45,6 +46,8 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE, nullable: true)]
|
||||
private ?\DateTimeImmutable $absenceStart = null;
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE, nullable: true)]
|
||||
private ?\DateTimeImmutable $absenceEnd = null;
|
||||
/**
|
||||
* Array where SAML attributes's data are stored.
|
||||
*/
|
||||
@@ -157,6 +160,11 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
return $this->absenceStart;
|
||||
}
|
||||
|
||||
public function getAbsenceEnd(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->absenceEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attributes.
|
||||
*
|
||||
@@ -336,7 +344,13 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
|
||||
public function isAbsent(): bool
|
||||
{
|
||||
return null !== $this->getAbsenceStart() && $this->getAbsenceStart() <= new \DateTimeImmutable('now');
|
||||
$now = new \DateTimeImmutable('now');
|
||||
$absenceStart = $this->getAbsenceStart();
|
||||
$absenceEnd = $this->getAbsenceEnd();
|
||||
|
||||
return null !== $absenceStart
|
||||
&& $absenceStart <= $now
|
||||
&& (null === $absenceEnd || $now <= $absenceEnd);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -410,6 +424,11 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
$this->absenceStart = $absenceStart;
|
||||
}
|
||||
|
||||
public function setAbsenceEnd(?\DateTimeImmutable $absenceEnd): void
|
||||
{
|
||||
$this->absenceEnd = $absenceEnd;
|
||||
}
|
||||
|
||||
public function setAttributeByDomain(string $domain, string $key, $value): self
|
||||
{
|
||||
$this->attributes[$domain][$key] = $value;
|
||||
@@ -675,4 +694,16 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
||||
{
|
||||
return 'fr';
|
||||
}
|
||||
|
||||
#[Assert\Callback]
|
||||
public function validateAbsenceDates(ExecutionContextInterface $context): void
|
||||
{
|
||||
if (null !== $this->getAbsenceEnd() && null === $this->getAbsenceStart()) {
|
||||
$context->buildViolation(
|
||||
'user.absence_end_requires_start'
|
||||
)
|
||||
->atPath('absenceEnd')
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -20,7 +20,7 @@ use Chill\MainBundle\Repository\CenterRepositoryInterface;
|
||||
use Chill\MainBundle\Repository\RegroupmentRepositoryInterface;
|
||||
|
||||
/**
|
||||
* @phpstan-type NormalizedData array{centers: array{centers: list<int>, regroupments: list<int>}, export: array{form: array<string, mixed>, version: int}, filters: array<string, array{enabled: boolean, form: array<string, mixed>, version: int}>, aggregators: array<string, array{enabled: boolean, form: array<string, mixed>, version: int}>, pick_formatter: string, formatter: array{form: array<string, mixed>, version: int}}
|
||||
* @phpstan-type NormalizedData array{centers: array{centers: list<int>, regroupments: list<int>}, export: array{form: array<string, mixed>, version: int}, filters: array<string, array{enabled: boolean, form: array<string, mixed>, version: int}>, aggregators: array<string, array{enabled: boolean, form: array<string, mixed>, version: int}>, pick_formatter?: string, formatter: array{form: array<string, mixed>, version: int}}
|
||||
*/
|
||||
class ExportConfigNormalizer
|
||||
{
|
||||
@@ -72,10 +72,14 @@ class ExportConfigNormalizer
|
||||
}
|
||||
$serialized['aggregators'] = $aggregatorsSerialized;
|
||||
|
||||
$serialized['pick_formatter'] = $formData['pick_formatter'];
|
||||
$formatter = $this->exportManager->getFormatter($formData['pick_formatter']);
|
||||
$serialized['formatter']['form'] = $formatter->normalizeFormData($formData['formatter']);
|
||||
$serialized['formatter']['version'] = $formatter->getNormalizationVersion();
|
||||
if ($export instanceof ExportInterface) {
|
||||
$serialized['pick_formatter'] = $formData['pick_formatter'];
|
||||
$formatter = $this->exportManager->getFormatter($formData['pick_formatter']);
|
||||
$serialized['formatter']['form'] = $formatter->normalizeFormData($formData['formatter']);
|
||||
$serialized['formatter']['version'] = $formatter->getNormalizationVersion();
|
||||
} elseif ($export instanceof DirectExportInterface) {
|
||||
$serialized['formatter'] = ['form' => [], 'version' => 0];
|
||||
}
|
||||
|
||||
return $serialized;
|
||||
}
|
||||
@@ -87,7 +91,12 @@ class ExportConfigNormalizer
|
||||
public function denormalizeConfig(string $exportAlias, array $serializedData, bool $replaceDisabledByDefaultData = false): array
|
||||
{
|
||||
$export = $this->exportManager->getExport($exportAlias);
|
||||
$formater = $this->exportManager->getFormatter($serializedData['pick_formatter']);
|
||||
|
||||
if ($export instanceof ExportInterface) {
|
||||
$formatter = $this->exportManager->getFormatter($serializedData['pick_formatter']);
|
||||
} else {
|
||||
$formatter = null;
|
||||
}
|
||||
|
||||
$filtersConfig = [];
|
||||
foreach ($serializedData['filters'] as $alias => $filterData) {
|
||||
@@ -117,8 +126,8 @@ class ExportConfigNormalizer
|
||||
'export' => $export->denormalizeFormData($serializedData['export']['form'], $serializedData['export']['version']),
|
||||
'filters' => $filtersConfig,
|
||||
'aggregators' => $aggregatorsConfig,
|
||||
'pick_formatter' => $serializedData['pick_formatter'],
|
||||
'formatter' => $formater->denormalizeFormData($serializedData['formatter']['form'], $serializedData['formatter']['version']),
|
||||
'pick_formatter' => $serializedData['pick_formatter'] ?? '',
|
||||
'formatter' => $formatter?->denormalizeFormData($serializedData['formatter']['form'], $serializedData['formatter']['version']),
|
||||
'centers' => [
|
||||
'centers' => array_values(array_filter(array_map(fn (int $id) => $this->centerRepository->find($id), $serializedData['centers']['centers']), fn ($item) => null !== $item)),
|
||||
'regroupments' => array_values(array_filter(array_map(fn (int $id) => $this->regroupmentRepository->find($id), $serializedData['centers']['regroupments']), fn ($item) => null !== $item)),
|
||||
|
@@ -23,9 +23,14 @@ class AbsenceType extends AbstractType
|
||||
{
|
||||
$builder
|
||||
->add('absenceStart', ChillDateType::class, [
|
||||
'required' => true,
|
||||
'required' => false,
|
||||
'input' => 'datetime_immutable',
|
||||
'label' => 'absence.Absence start',
|
||||
])
|
||||
->add('absenceEnd', ChillDateType::class, [
|
||||
'required' => false,
|
||||
'input' => 'datetime_immutable',
|
||||
'label' => 'absence.Absence end',
|
||||
]);
|
||||
}
|
||||
|
||||
|
@@ -55,6 +55,10 @@ class DateIntervalType extends AbstractType
|
||||
{
|
||||
$builder
|
||||
->add('n', IntegerType::class, [
|
||||
'attr' => [
|
||||
'min' => 0,
|
||||
'step' => 1,
|
||||
],
|
||||
'constraints' => [
|
||||
new GreaterThan([
|
||||
'value' => 0,
|
||||
|
@@ -105,6 +105,11 @@ class UserType extends AbstractType
|
||||
'required' => false,
|
||||
'input' => 'datetime_immutable',
|
||||
'label' => 'absence.Absence start',
|
||||
])
|
||||
->add('absenceEnd', ChillDateType::class, [
|
||||
'required' => false,
|
||||
'input' => 'datetime_immutable',
|
||||
'label' => 'absence.Absence end',
|
||||
]);
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
|
@@ -170,13 +170,14 @@ div.banner {
|
||||
font-weight: lighter;
|
||||
font-size: 50%;
|
||||
margin-left: 0.5em;
|
||||
&:before { content: '(n°'; }
|
||||
&:after { content: ')'; }
|
||||
|
||||
&.same-size {
|
||||
font-size: unset;
|
||||
font-weight: unset;
|
||||
}
|
||||
}
|
||||
span.age {
|
||||
margin-left: 0.5em;
|
||||
&:before { content: '('; }
|
||||
&:after { content: ')'; }
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -37,8 +37,13 @@ export const ISOToDate = (str: string | null): Date | null => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [year, month, day] = str.split("-").map((p) => parseInt(p));
|
||||
// If the string already contains time info, use it directly
|
||||
if (str.includes("T") || str.includes(" ")) {
|
||||
return new Date(str);
|
||||
}
|
||||
|
||||
// Otherwise, parse date only
|
||||
const [year, month, day] = str.split("-").map((p) => parseInt(p));
|
||||
return new Date(year, month - 1, day, 0, 0, 0, 0);
|
||||
};
|
||||
|
||||
@@ -69,20 +74,19 @@ export const ISOToDatetime = (str: string | null): Date | null => {
|
||||
*
|
||||
*/
|
||||
export const datetimeToISO = (date: Date): string => {
|
||||
let cal, time, offset;
|
||||
cal = [
|
||||
const cal = [
|
||||
date.getFullYear(),
|
||||
(date.getMonth() + 1).toString().padStart(2, "0"),
|
||||
date.getDate().toString().padStart(2, "0"),
|
||||
].join("-");
|
||||
|
||||
time = [
|
||||
const time = [
|
||||
date.getHours().toString().padStart(2, "0"),
|
||||
date.getMinutes().toString().padStart(2, "0"),
|
||||
date.getSeconds().toString().padStart(2, "0"),
|
||||
].join(":");
|
||||
|
||||
offset = [
|
||||
const offset = [
|
||||
date.getTimezoneOffset() <= 0 ? "+" : "-",
|
||||
Math.abs(Math.floor(date.getTimezoneOffset() / 60))
|
||||
.toString()
|
||||
|
@@ -10,8 +10,9 @@ $chill-household-context: #929d69;
|
||||
// Badges colors
|
||||
$social-issue-color: #4bafe8;
|
||||
$social-action-color: $orange;
|
||||
$event-theme-color: #ecc546;
|
||||
$activity-color: yellowgreen;
|
||||
|
||||
// budget colors
|
||||
$budget-resource-color: #6d9e63;
|
||||
$budget-charge-color: #e03851;
|
||||
$budget-charge-color: #e03851;
|
||||
|
@@ -44,8 +44,6 @@ section.chill-entity {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
span.id-number {
|
||||
&:before { content: '(n°'; }
|
||||
&:after { content: ')'; }
|
||||
}
|
||||
}
|
||||
p.moreinfo {}
|
||||
|
@@ -21,10 +21,12 @@
|
||||
>
|
||||
<template #header>
|
||||
<h2 class="modal-title">
|
||||
{{ $t(getTextTitle) }}
|
||||
{{ trans(getTextTitle) }}
|
||||
<span v-if="flag.loading" class="loading">
|
||||
<i class="fa fa-circle-o-notch fa-spin fa-fw" />
|
||||
<span class="sr-only">{{ $t("loading") }}</span>
|
||||
<span class="sr-only">{{
|
||||
trans(ADDRESS_LOADING)
|
||||
}}</span>
|
||||
</span>
|
||||
</h2>
|
||||
</template>
|
||||
@@ -43,7 +45,7 @@
|
||||
|
||||
<template #footer>
|
||||
<button @click="openEditPane" class="btn btn-create">
|
||||
{{ $t("create_a_new_address") }}
|
||||
{{ trans(CREATE_A_NEW_ADDRESS) }}
|
||||
</button>
|
||||
</template>
|
||||
</modal>
|
||||
@@ -62,13 +64,13 @@
|
||||
>
|
||||
<template #before v-if="!bypassFirstStep">
|
||||
<a class="btn btn-cancel" @click="resetPane">
|
||||
{{ $t("action.cancel") }}
|
||||
{{ trans(CANCEL) }}
|
||||
</a>
|
||||
</template>
|
||||
<template #action>
|
||||
<li>
|
||||
<button @click="openEditPane" class="btn btn-create">
|
||||
{{ $t("create_a_new_address") }}
|
||||
{{ trans(CREATE_A_NEW_ADDRESS) }}
|
||||
</button>
|
||||
</li>
|
||||
</template>
|
||||
@@ -85,10 +87,12 @@
|
||||
>
|
||||
<template #header>
|
||||
<h2 class="modal-title">
|
||||
{{ $t(getTextTitle) }}
|
||||
{{ trans(getTextTitle) }}
|
||||
<span v-if="flag.loading" class="loading">
|
||||
<i class="fa fa-circle-o-notch fa-spin fa-fw" />
|
||||
<span class="sr-only">{{ $t("loading") }}</span>
|
||||
<span class="sr-only">{{
|
||||
trans(ADDRESS_LOADING)
|
||||
}}</span>
|
||||
</span>
|
||||
</h2>
|
||||
</template>
|
||||
@@ -108,17 +112,17 @@
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<!--<button class="btn btn-cancel change-icon" @click="resetPane">{{ $t('action.cancel') }}</button>-->
|
||||
<!--<button class="btn btn-cancel change-icon" @click="resetPane">{{ trans(CANCEL) }}</button>-->
|
||||
<button
|
||||
v-if="!this.context.edit && this.useDatePane"
|
||||
class="btn btn-update change-icon"
|
||||
@click="closeEditPane"
|
||||
>
|
||||
{{ $t("nav.next") }}
|
||||
{{ trans(NEXT) }}
|
||||
<i class="fa fa-fw fa-arrow-right" />
|
||||
</button>
|
||||
<button v-else class="btn btn-save" @click="closeEditPane">
|
||||
{{ $t("action.save") }}
|
||||
{{ trans(SAVE) }}
|
||||
</button>
|
||||
</template>
|
||||
</modal>
|
||||
@@ -139,7 +143,7 @@
|
||||
>
|
||||
<template #before>
|
||||
<a class="btn btn-cancel" @click="resetPane">
|
||||
{{ $t("action.cancel") }}
|
||||
{{ trans(CANCEL) }}
|
||||
</a>
|
||||
</template>
|
||||
<template #action>
|
||||
@@ -148,13 +152,13 @@
|
||||
class="btn btn-update change-icon"
|
||||
@click="closeEditPane"
|
||||
>
|
||||
{{ $t("nav.next") }}
|
||||
{{ trans(NEXT) }}
|
||||
<i class="fa fa-fw fa-arrow-right" />
|
||||
</button>
|
||||
</li>
|
||||
<li v-else>
|
||||
<button class="btn btn-save" @click="closeEditPane">
|
||||
{{ $t("action.save") }}
|
||||
{{ trans(SAVE) }}
|
||||
</button>
|
||||
</li>
|
||||
</template>
|
||||
@@ -171,10 +175,12 @@
|
||||
>
|
||||
<template #header>
|
||||
<h2 class="modal-title">
|
||||
{{ $t(getTextTitle) }}
|
||||
{{ trans(getTextTitle) }}
|
||||
<span v-if="flag.loading" class="loading">
|
||||
<i class="fa fa-circle-o-notch fa-spin fa-fw" />
|
||||
<span class="sr-only">{{ $t("loading") }}</span>
|
||||
<span class="sr-only">{{
|
||||
trans(ADDRESS_LOADING)
|
||||
}}</span>
|
||||
</span>
|
||||
</h2>
|
||||
</template>
|
||||
@@ -193,10 +199,10 @@
|
||||
<template #footer>
|
||||
<button class="btn btn-misc" @click="openEditPane">
|
||||
<i class="fa fa-fw fa-arrow-left" />
|
||||
{{ $t("nav.previous") }}
|
||||
{{ trans(PREVIOUS) }}
|
||||
</button>
|
||||
<button class="btn btn-save" @click="closeDatePane">
|
||||
{{ $t("action.save") }}
|
||||
{{ trans(SAVE) }}
|
||||
</button>
|
||||
<!-- -->
|
||||
</template>
|
||||
@@ -216,13 +222,13 @@
|
||||
<template #before>
|
||||
<button class="btn btn-misc" @click="openEditPane">
|
||||
<i class="fa fa-fw fa-arrow-left" />
|
||||
{{ $t("nav.previous") }}
|
||||
{{ trans(PREVIOUS) }}
|
||||
</button>
|
||||
</template>
|
||||
<template #action>
|
||||
<li>
|
||||
<button class="btn btn-save" @click="closeDatePane">
|
||||
{{ $t("action.save") }}
|
||||
{{ trans(SAVE) }}
|
||||
</button>
|
||||
</li>
|
||||
</template>
|
||||
@@ -244,9 +250,16 @@ import {
|
||||
postPostalCode,
|
||||
} from "../api";
|
||||
import {
|
||||
postAddressToPerson,
|
||||
postAddressToHousehold,
|
||||
} from "ChillPersonAssets/vuejs/_api/AddAddress.js";
|
||||
CREATE_A_NEW_ADDRESS,
|
||||
ADDRESS_LOADING,
|
||||
ACTIVITY_CREATE_ADDRESS,
|
||||
ACTIVITY_EDIT_ADDRESS,
|
||||
CANCEL,
|
||||
SAVE,
|
||||
PREVIOUS,
|
||||
NEXT,
|
||||
trans,
|
||||
} from "translator";
|
||||
import ShowPane from "./ShowPane.vue";
|
||||
import SuggestPane from "./SuggestPane.vue";
|
||||
import EditPane from "./EditPane.vue";
|
||||
@@ -254,6 +267,17 @@ import DatePane from "./DatePane.vue";
|
||||
|
||||
export default {
|
||||
name: "AddAddress",
|
||||
setup() {
|
||||
return {
|
||||
trans,
|
||||
CREATE_A_NEW_ADDRESS,
|
||||
ADDRESS_LOADING,
|
||||
CANCEL,
|
||||
SAVE,
|
||||
PREVIOUS,
|
||||
NEXT,
|
||||
};
|
||||
},
|
||||
props: ["context", "options", "addressChangedCallback"],
|
||||
components: {
|
||||
Modal,
|
||||
@@ -373,9 +397,11 @@ export default {
|
||||
(this.options.title.edit !== null ||
|
||||
this.options.title.create !== null)
|
||||
) {
|
||||
console.log("this.options.title", this.options.title);
|
||||
|
||||
return this.context.edit
|
||||
? this.options.title.edit
|
||||
: this.options.title.create;
|
||||
? ACTIVITY_EDIT_ADDRESS
|
||||
: ACTIVITY_CREATE_ADDRESS;
|
||||
}
|
||||
return this.context.edit
|
||||
? this.defaultz.title.edit
|
||||
@@ -505,7 +531,7 @@ export default {
|
||||
getAddress(id)
|
||||
.then(
|
||||
(address) =>
|
||||
new Promise((resolve, reject) => {
|
||||
new Promise((resolve) => {
|
||||
this.entity.address = address;
|
||||
this.flag.loading = false;
|
||||
resolve();
|
||||
@@ -522,7 +548,7 @@ export default {
|
||||
fetchCountries()
|
||||
.then(
|
||||
(countries) =>
|
||||
new Promise((resolve, reject) => {
|
||||
new Promise((resolve) => {
|
||||
this.entity.loaded.countries = countries.results;
|
||||
if (this.flag.showPane === true) {
|
||||
this.closeShowPane();
|
||||
@@ -550,7 +576,7 @@ export default {
|
||||
fetchCities(country)
|
||||
.then(
|
||||
(cities) =>
|
||||
new Promise((resolve, reject) => {
|
||||
new Promise((resolve) => {
|
||||
this.entity.loaded.cities = cities.results.filter(
|
||||
(c) => c.origin !== 3,
|
||||
); // filter out user-defined cities
|
||||
@@ -569,7 +595,7 @@ export default {
|
||||
fetchReferenceAddresses(city)
|
||||
.then(
|
||||
(addresses) =>
|
||||
new Promise((resolve, reject) => {
|
||||
new Promise((resolve) => {
|
||||
this.entity.loaded.addresses = addresses.results;
|
||||
this.flag.loading = false;
|
||||
resolve();
|
||||
@@ -800,7 +826,7 @@ export default {
|
||||
return postAddress(payload)
|
||||
.then(
|
||||
(address) =>
|
||||
new Promise((resolve, reject) => {
|
||||
new Promise((resolve) => {
|
||||
this.entity.address = address;
|
||||
this.flag.loading = false;
|
||||
this.flag.success = true;
|
||||
@@ -849,7 +875,7 @@ export default {
|
||||
return patchAddress(payload.addressId, payload.newAddress)
|
||||
.then(
|
||||
(address) =>
|
||||
new Promise((resolve, reject) => {
|
||||
new Promise((resolve) => {
|
||||
this.entity.address = address;
|
||||
this.flag.loading = false;
|
||||
this.flag.success = true;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<h4 class="h3">
|
||||
{{ $t("fill_an_address") }}
|
||||
{{ trans(ADDRESS_FILL_AN_ADDRESS) }}
|
||||
</h4>
|
||||
<div class="row my-3">
|
||||
<div class="col-lg-6" v-if="!isNoAddress">
|
||||
@@ -9,40 +9,40 @@
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="floor"
|
||||
:placeholder="$t('floor')"
|
||||
:placeholder="trans(ADDRESS_FLOOR)"
|
||||
v-model="floor"
|
||||
/>
|
||||
<label for="floor">{{ $t("floor") }}</label>
|
||||
<label for="floor">{{ trans(ADDRESS_FLOOR) }}</label>
|
||||
</div>
|
||||
<div class="form-floating my-1">
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="corridor"
|
||||
:placeholder="$t('corridor')"
|
||||
:placeholder="trans(ADDRESS_CORRIDOR)"
|
||||
v-model="corridor"
|
||||
/>
|
||||
<label for="corridor">{{ $t("corridor") }}</label>
|
||||
<label for="corridor">{{ trans(ADDRESS_CORRIDOR) }}</label>
|
||||
</div>
|
||||
<div class="form-floating my-1">
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="steps"
|
||||
:placeholder="$t('steps')"
|
||||
:placeholder="trans(ADDRESS_STEPS)"
|
||||
v-model="steps"
|
||||
/>
|
||||
<label for="steps">{{ $t("steps") }}</label>
|
||||
<label for="steps">{{ trans(ADDRESS_STEPS) }}</label>
|
||||
</div>
|
||||
<div class="form-floating my-1">
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="flat"
|
||||
:placeholder="$t('flat')"
|
||||
:placeholder="trans(ADDRESS_FLAT)"
|
||||
v-model="flat"
|
||||
/>
|
||||
<label for="flat">{{ $t("flat") }}</label>
|
||||
<label for="flat">{{ trans(ADDRESS_FLAT) }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="isNoAddress ? 'col-lg-12' : 'col-lg-6'">
|
||||
@@ -52,10 +52,12 @@
|
||||
type="text"
|
||||
name="buildingName"
|
||||
maxlength="255"
|
||||
:placeholder="$t('buildingName')"
|
||||
:placeholder="trans(ADDRESS_BUILDING_NAME)"
|
||||
v-model="buildingName"
|
||||
/>
|
||||
<label for="buildingName">{{ $t("buildingName") }}</label>
|
||||
<label for="buildingName">{{
|
||||
trans(ADDRESS_BUILDING_NAME)
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="form-floating my-1">
|
||||
<input
|
||||
@@ -63,10 +65,10 @@
|
||||
type="text"
|
||||
name="extra"
|
||||
maxlength="255"
|
||||
:placeholder="$t('extra')"
|
||||
:placeholder="trans(ADDRESS_EXTRA)"
|
||||
v-model="extra"
|
||||
/>
|
||||
<label for="extra">{{ $t("extra") }}</label>
|
||||
<label for="extra">{{ trans(ADDRESS_EXTRA) }}</label>
|
||||
</div>
|
||||
<div class="form-floating my-1" v-if="!isNoAddress">
|
||||
<input
|
||||
@@ -74,18 +76,48 @@
|
||||
type="text"
|
||||
name="distribution"
|
||||
maxlength="255"
|
||||
:placeholder="$t('distribution')"
|
||||
:placeholder="trans(ADDRESS_DISTRIBUTION)"
|
||||
v-model="distribution"
|
||||
/>
|
||||
<label for="distribution">{{ $t("distribution") }}</label>
|
||||
<label for="distribution">{{
|
||||
trans(ADDRESS_DISTRIBUTION)
|
||||
}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
ADDRESS_STREET,
|
||||
ADDRESS_STREET_NUMBER,
|
||||
ADDRESS_FLOOR,
|
||||
ADDRESS_CORRIDOR,
|
||||
ADDRESS_STEPS,
|
||||
ADDRESS_FLAT,
|
||||
ADDRESS_BUILDING_NAME,
|
||||
ADDRESS_DISTRIBUTION,
|
||||
ADDRESS_EXTRA,
|
||||
ADDRESS_FILL_AN_ADDRESS,
|
||||
trans,
|
||||
} from "translator";
|
||||
export default {
|
||||
name: "AddressMore",
|
||||
setup() {
|
||||
return {
|
||||
ADDRESS_STREET,
|
||||
ADDRESS_STREET_NUMBER,
|
||||
ADDRESS_FLOOR,
|
||||
ADDRESS_CORRIDOR,
|
||||
ADDRESS_STEPS,
|
||||
ADDRESS_FLAT,
|
||||
ADDRESS_BUILDING_NAME,
|
||||
ADDRESS_DISTRIBUTION,
|
||||
ADDRESS_EXTRA,
|
||||
ADDRESS_FILL_AN_ADDRESS,
|
||||
trans,
|
||||
};
|
||||
},
|
||||
props: ["entity", "isNoAddress"],
|
||||
computed: {
|
||||
floor: {
|
||||
|
@@ -1,16 +1,16 @@
|
||||
<template>
|
||||
<div class="my-1">
|
||||
<label class="col-form-label" for="addressSelector">{{
|
||||
$t("address")
|
||||
trans(ADDRESS_ADDRESS)
|
||||
}}</label>
|
||||
<VueMultiselect
|
||||
id="addressSelector"
|
||||
v-model="value"
|
||||
:placeholder="$t('select_address')"
|
||||
:tag-placeholder="$t('create_address')"
|
||||
:select-label="$t('multiselect.select_label')"
|
||||
:deselect-label="$t('create_address')"
|
||||
:selected-label="$t('multiselect.selected_label')"
|
||||
:placeholder="trans(ADDRESS_SELECT_ADDRESS)"
|
||||
:tag-placeholder="trans(ADDRESS_CREATE_ADDRESS)"
|
||||
:select-label="trans(MULTISELECT_SELECT_LABEL)"
|
||||
:deselect-label="trans(ADDRESS_CREATE_ADDRESS)"
|
||||
:selected-label="trans(MULTISELECT_SELECTED_LABEL)"
|
||||
@search-change="listenInputSearch"
|
||||
:internal-search="false"
|
||||
ref="addressSelector"
|
||||
@@ -42,10 +42,10 @@
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="street"
|
||||
:placeholder="$t('street')"
|
||||
:placeholder="trans(ADDRESS_STREET)"
|
||||
v-model="street"
|
||||
/>
|
||||
<label for="street">{{ $t("street") }}</label>
|
||||
<label for="street">{{ trans(ADDRESS_STREET) }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
@@ -54,10 +54,12 @@
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="streetNumber"
|
||||
:placeholder="$t('streetNumber')"
|
||||
:placeholder="trans(ADDRESS_STREET_NUMBER)"
|
||||
v-model="streetNumber"
|
||||
/>
|
||||
<label for="streetNumber">{{ $t("streetNumber") }}</label>
|
||||
<label for="streetNumber">{{
|
||||
trans(ADDRESS_STREET_NUMBER)
|
||||
}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -69,10 +71,32 @@ import {
|
||||
searchReferenceAddresses,
|
||||
fetchReferenceAddresses,
|
||||
} from "../../api.js";
|
||||
import {
|
||||
ADDRESS_STREET,
|
||||
ADDRESS_STREET_NUMBER,
|
||||
ADDRESS_ADDRESS,
|
||||
MULTISELECT_SELECTED_LABEL,
|
||||
MULTISELECT_SELECT_LABEL,
|
||||
ADDRESS_SELECT_ADDRESS,
|
||||
ADDRESS_CREATE_ADDRESS,
|
||||
trans,
|
||||
} from "translator";
|
||||
|
||||
export default {
|
||||
name: "AddressSelection",
|
||||
components: { VueMultiselect },
|
||||
setup() {
|
||||
return {
|
||||
ADDRESS_STREET,
|
||||
ADDRESS_STREET_NUMBER,
|
||||
ADDRESS_ADDRESS,
|
||||
MULTISELECT_SELECTED_LABEL,
|
||||
MULTISELECT_SELECT_LABEL,
|
||||
ADDRESS_SELECT_ADDRESS,
|
||||
ADDRESS_CREATE_ADDRESS,
|
||||
trans,
|
||||
};
|
||||
},
|
||||
props: ["entity", "context", "updateMapCenter", "flag", "checkErrors"],
|
||||
data() {
|
||||
return {
|
||||
@@ -150,7 +174,7 @@ export default {
|
||||
searchReferenceAddresses(query, this.entity.selected.city)
|
||||
.then(
|
||||
(addresses) =>
|
||||
new Promise((resolve, reject) => {
|
||||
new Promise((resolve) => {
|
||||
this.entity.loaded.addresses =
|
||||
addresses.results;
|
||||
this.isLoading = false;
|
||||
@@ -168,7 +192,7 @@ export default {
|
||||
fetchReferenceAddresses(this.entity.selected.city)
|
||||
.then(
|
||||
(addresses) =>
|
||||
new Promise((resolve, reject) => {
|
||||
new Promise((resolve) => {
|
||||
this.entity.loaded.addresses =
|
||||
addresses.results;
|
||||
this.isLoading = false;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="my-1">
|
||||
<label class="col-form-label">{{ $t("city") }}</label>
|
||||
<label class="col-form-label">{{ trans(ADDRESS_CITY) }}</label>
|
||||
<VueMultiselect
|
||||
id="citySelector"
|
||||
v-model="value"
|
||||
@@ -12,15 +12,15 @@
|
||||
track-by="id"
|
||||
label="value"
|
||||
:custom-label="transName"
|
||||
:placeholder="$t('select_city')"
|
||||
:select-label="$t('multiselect.select_label')"
|
||||
:deselect-label="$t('create_postal_code')"
|
||||
:selected-label="$t('multiselect.selected_label')"
|
||||
:placeholder="trans(ADDRESS_SELECT_CITY)"
|
||||
:select-label="trans(MULTISELECT_SELECT_LABEL)"
|
||||
:deselect-label="trans(ADDRESS_CREATE_POSTAL_CODE)"
|
||||
:selected-label="trans(MULTISELECT_SELECTED_LABEL)"
|
||||
:taggable="true"
|
||||
:multiple="false"
|
||||
:internal-search="false"
|
||||
@tag="addPostcode"
|
||||
:tag-placeholder="$t('create_postal_code')"
|
||||
:tag-placeholder="trans(ADDRESS_CREATE_POSTAL_CODE)"
|
||||
:loading="isLoading"
|
||||
:options="cities"
|
||||
/>
|
||||
@@ -36,10 +36,10 @@
|
||||
class="form-control"
|
||||
type="text"
|
||||
id="code"
|
||||
:placeholder="$t('postalCode_code')"
|
||||
:placeholder="trans(ADDRESS_POSTAL_CODE_CODE)"
|
||||
v-model="code"
|
||||
/>
|
||||
<label for="code">{{ $t("postalCode_code") }}</label>
|
||||
<label for="code">{{ trans(ADDRESS_POSTAL_CODE_CODE) }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
@@ -48,10 +48,10 @@
|
||||
class="form-control"
|
||||
type="text"
|
||||
id="name"
|
||||
:placeholder="$t('postalCode_name')"
|
||||
:placeholder="trans(ADDRESS_POSTAL_CODE_NAME)"
|
||||
v-model="name"
|
||||
/>
|
||||
<label for="name">{{ $t("postalCode_name") }}</label>
|
||||
<label for="name">{{ trans(ADDRESS_POSTAL_CODE_NAME) }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -60,10 +60,32 @@
|
||||
<script>
|
||||
import VueMultiselect from "vue-multiselect";
|
||||
import { searchCities, fetchCities } from "../../api.js";
|
||||
import {
|
||||
MULTISELECT_SELECTED_LABEL,
|
||||
MULTISELECT_SELECT_LABEL,
|
||||
ADDRESS_POSTAL_CODE_CODE,
|
||||
ADDRESS_POSTAL_CODE_NAME,
|
||||
ADDRESS_CREATE_POSTAL_CODE,
|
||||
ADDRESS_CITY,
|
||||
ADDRESS_SELECT_CITY,
|
||||
trans,
|
||||
} from "translator";
|
||||
|
||||
export default {
|
||||
name: "CitySelection",
|
||||
components: { VueMultiselect },
|
||||
setup() {
|
||||
return {
|
||||
MULTISELECT_SELECTED_LABEL,
|
||||
MULTISELECT_SELECT_LABEL,
|
||||
ADDRESS_CITY,
|
||||
ADDRESS_SELECT_CITY,
|
||||
ADDRESS_POSTAL_CODE_CODE,
|
||||
ADDRESS_POSTAL_CODE_NAME,
|
||||
ADDRESS_CREATE_POSTAL_CODE,
|
||||
trans,
|
||||
};
|
||||
},
|
||||
props: [
|
||||
"entity",
|
||||
"context",
|
||||
@@ -167,7 +189,7 @@ export default {
|
||||
searchCities(query, this.entity.selected.country)
|
||||
.then(
|
||||
(cities) =>
|
||||
new Promise((resolve, reject) => {
|
||||
new Promise((resolve) => {
|
||||
this.entity.loaded.cities =
|
||||
cities.results.filter(
|
||||
(c) => c.origin !== 3,
|
||||
@@ -187,7 +209,7 @@ export default {
|
||||
fetchCities(this.entity.selected.country)
|
||||
.then(
|
||||
(cities) =>
|
||||
new Promise((resolve, reject) => {
|
||||
new Promise((resolve) => {
|
||||
this.entity.loaded.cities =
|
||||
cities.results.filter(
|
||||
(c) => c.origin !== 3,
|
||||
|
@@ -1,19 +1,19 @@
|
||||
<template>
|
||||
<div class="my-1">
|
||||
<label class="col-form-label" for="countrySelect">{{
|
||||
$t("country")
|
||||
trans(ADDRESS_COUNTRY)
|
||||
}}</label>
|
||||
<VueMultiselect
|
||||
id="countrySelect"
|
||||
label="name"
|
||||
track-by="id"
|
||||
:custom-label="transName"
|
||||
:placeholder="$t('select_country')"
|
||||
:placeholder="trans(ADDRESS_SELECT_COUNTRY)"
|
||||
:options="sortedCountries"
|
||||
v-model="value"
|
||||
:select-label="$t('multiselect.select_label')"
|
||||
:deselect-label="$t('multiselect.deselect_label')"
|
||||
:selected-label="$t('multiselect.selected_label')"
|
||||
:select-label="trans(MULTISELECT_SELECT_LABEL)"
|
||||
:deselect-label="trans(MULTISELECT_DESELECT_LABEL)"
|
||||
:selected-label="trans(MULTISELECT_SELECTED_LABEL)"
|
||||
@select="selectCountry"
|
||||
@remove="remove"
|
||||
/>
|
||||
@@ -23,10 +23,28 @@
|
||||
<script>
|
||||
import VueMultiselect from "vue-multiselect";
|
||||
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
|
||||
import {
|
||||
MULTISELECT_SELECTED_LABEL,
|
||||
MULTISELECT_SELECT_LABEL,
|
||||
MULTISELECT_DESELECT_LABEL,
|
||||
ADDRESS_COUNTRY,
|
||||
ADDRESS_SELECT_COUNTRY,
|
||||
trans,
|
||||
} from "translator";
|
||||
|
||||
export default {
|
||||
name: "CountrySelection",
|
||||
components: { VueMultiselect },
|
||||
setup() {
|
||||
return {
|
||||
MULTISELECT_SELECTED_LABEL,
|
||||
MULTISELECT_SELECT_LABEL,
|
||||
MULTISELECT_DESELECT_LABEL,
|
||||
ADDRESS_COUNTRY,
|
||||
ADDRESS_SELECT_COUNTRY,
|
||||
trans,
|
||||
};
|
||||
},
|
||||
props: ["context", "entity", "flag", "checkErrors"],
|
||||
emits: ["getCities"],
|
||||
data() {
|
||||
|
@@ -18,7 +18,7 @@
|
||||
</div>
|
||||
|
||||
<h4 class="h3">
|
||||
{{ $t("select_an_address_title") }}
|
||||
{{ trans(ADDRESS_SELECT_AN_ADDRESS_TITLE) }}
|
||||
</h4>
|
||||
<div class="row my-3">
|
||||
<div class="col-lg-6">
|
||||
@@ -31,7 +31,7 @@
|
||||
:value="valueConfidential"
|
||||
/>
|
||||
<label class="form-check-label" for="isConfidential">
|
||||
{{ $t("isConfidential") }}
|
||||
{{ trans(ADDRESS_IS_CONFIDENTIAL) }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
@@ -43,7 +43,7 @@
|
||||
:value="value"
|
||||
/>
|
||||
<label class="form-check-label" for="isNoAddress">
|
||||
{{ $t("isNoAddress") }}
|
||||
{{ trans(ADDRESS_IS_NO_ADDRESS) }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -108,6 +108,12 @@ import AddressSelection from "./AddAddress/AddressSelection";
|
||||
import AddressMap from "./AddAddress/AddressMap";
|
||||
import AddressMore from "./AddAddress/AddressMore";
|
||||
import ActionButtons from "./ActionButtons.vue";
|
||||
import {
|
||||
ADDRESS_SELECT_AN_ADDRESS_TITLE,
|
||||
ADDRESS_IS_CONFIDENTIAL,
|
||||
ADDRESS_IS_NO_ADDRESS,
|
||||
trans,
|
||||
} from "translator";
|
||||
|
||||
export default {
|
||||
name: "EditPane",
|
||||
@@ -119,6 +125,14 @@ export default {
|
||||
AddressMore,
|
||||
ActionButtons,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
trans,
|
||||
ADDRESS_SELECT_AN_ADDRESS_TITLE,
|
||||
ADDRESS_IS_CONFIDENTIAL,
|
||||
ADDRESS_IS_NO_ADDRESS,
|
||||
};
|
||||
},
|
||||
props: [
|
||||
"context",
|
||||
"options",
|
||||
|
@@ -5,7 +5,7 @@
|
||||
v-if="flag.loading"
|
||||
class="fa fa-circle-o-notch fa-spin fa-2x fa-fw"
|
||||
/>
|
||||
<span class="sr-only">{{ $t("loading") }}</span>
|
||||
<span class="sr-only">{{ trans(ADDRESS_LOADING) }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="errorMsg && errorMsg.length > 0" class="alert alert-danger">
|
||||
@@ -13,8 +13,10 @@
|
||||
</div>
|
||||
|
||||
<div v-if="flag.success" class="alert alert-success">
|
||||
{{ $t(getSuccessText) }}
|
||||
<span v-if="forceRedirect">{{ $t("wait_redirection") }}</span>
|
||||
{{ trans(getSuccessText) }}
|
||||
<span v-if="forceRedirect">{{
|
||||
trans(ADDRESS_WAIT_REDIRECTION)
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -28,7 +30,7 @@
|
||||
<div class="no-address-yet">
|
||||
<i class="fa fa-map-marker" aria-hidden="true" />
|
||||
<p class="chill-no-data-statement">
|
||||
{{ $t("not_yet_address") }}
|
||||
{{ trans(ADDRESS_NOT_YET_ADDRESS) }}
|
||||
</p>
|
||||
|
||||
<action-buttons
|
||||
@@ -43,10 +45,10 @@
|
||||
:class="getClassButton"
|
||||
type="button"
|
||||
name="button"
|
||||
:title="$t(getTextButton)"
|
||||
:title="trans(getTextButton)"
|
||||
>
|
||||
<span v-if="displayTextButton">{{
|
||||
$t(getTextButton)
|
||||
trans(getTextButton)
|
||||
}}</span>
|
||||
</button>
|
||||
</template>
|
||||
@@ -71,10 +73,10 @@
|
||||
:class="getClassButton"
|
||||
type="button"
|
||||
name="button"
|
||||
:title="$t(getTextButton)"
|
||||
:title="trans(getTextButton)"
|
||||
>
|
||||
<span v-if="displayTextButton">{{
|
||||
$t(getTextButton)
|
||||
trans(getTextButton)
|
||||
}}</span>
|
||||
</button>
|
||||
</template>
|
||||
@@ -95,10 +97,10 @@
|
||||
:class="getClassButton"
|
||||
type="button"
|
||||
name="button"
|
||||
:title="$t(getTextButton)"
|
||||
:title="trans(getTextButton)"
|
||||
>
|
||||
<span v-if="displayTextButton">{{
|
||||
$t(getTextButton)
|
||||
trans(getTextButton)
|
||||
}}</span>
|
||||
</button>
|
||||
</template>
|
||||
@@ -109,13 +111,36 @@
|
||||
<script>
|
||||
import AddressRenderBox from "ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue";
|
||||
import ActionButtons from "./ActionButtons.vue";
|
||||
import {
|
||||
ACTIVITY_CREATE_ADDRESS,
|
||||
ACTIVITY_EDIT_ADDRESS,
|
||||
ADDRESS_NOT_YET_ADDRESS,
|
||||
ADDRESS_WAIT_REDIRECTION,
|
||||
ADDRESS_LOADING,
|
||||
ADDRESS_ADDRESS_EDIT_SUCCESS,
|
||||
ADDRESS_ADDRESS_NEW_SUCCESS,
|
||||
trans,
|
||||
} from "translator";
|
||||
|
||||
export default {
|
||||
name: "ShowPane",
|
||||
methods: {},
|
||||
components: {
|
||||
AddressRenderBox,
|
||||
ActionButtons,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
trans,
|
||||
ACTIVITY_CREATE_ADDRESS,
|
||||
ACTIVITY_EDIT_ADDRESS,
|
||||
ADDRESS_NOT_YET_ADDRESS,
|
||||
ADDRESS_WAIT_REDIRECTION,
|
||||
ADDRESS_LOADING,
|
||||
ADDRESS_ADDRESS_NEW_SUCCESS,
|
||||
ADDRESS_ADDRESS_EDIT_SUCCESS,
|
||||
};
|
||||
},
|
||||
props: [
|
||||
"context",
|
||||
"defaultz",
|
||||
@@ -156,18 +181,20 @@ export default {
|
||||
(this.options.button.text.edit !== null ||
|
||||
this.options.button.text.create !== null)
|
||||
) {
|
||||
// console.log('this.options.button.text', this.options.button.text)
|
||||
return this.context.edit
|
||||
? this.options.button.text.edit
|
||||
: this.options.button.text.create;
|
||||
? ACTIVITY_CREATE_ADDRESS
|
||||
: ACTIVITY_EDIT_ADDRESS;
|
||||
}
|
||||
console.log("defaultz", this.defaultz);
|
||||
return this.context.edit
|
||||
? this.defaultz.button.text.edit
|
||||
: this.defaultz.button.text.create;
|
||||
},
|
||||
getSuccessText() {
|
||||
return this.context.edit
|
||||
? "address_edit_success"
|
||||
: "address_new_success";
|
||||
? ADDRESS_ADDRESS_EDIT_SUCCESS
|
||||
: ADDRESS_ADDRESS_NEW_SUCCESS;
|
||||
},
|
||||
onlyButton() {
|
||||
return typeof this.options.onlyButton !== "undefined"
|
||||
|
@@ -64,3 +64,5 @@ const props = defineProps({
|
||||
entity: Object,
|
||||
});
|
||||
</script>
|
||||
|
||||
thirdparty_duplicate: merge: Fussioner find: 'Désigner un tiers doublon'
|
||||
|
@@ -4,7 +4,7 @@
|
||||
{% endblock crud_content_header %}
|
||||
|
||||
{% block crud_content_view %}
|
||||
|
||||
|
||||
{% block crud_content_view_details %}
|
||||
<dl class="chill_view_data">
|
||||
<dt>id</dt>
|
||||
@@ -20,7 +20,7 @@
|
||||
{{ 'Cancel'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
{% block content_view_actions_before %}{% endblock %}
|
||||
{% block content_form_actions_delete %}
|
||||
{% if chill_crud_action_exists(crud_name, 'delete') %}
|
||||
@@ -32,7 +32,7 @@
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock content_form_actions_delete %}
|
||||
{% endblock content_form_actions_delete %}
|
||||
{% block content_view_actions_duplicate_link %}
|
||||
{% if chill_crud_action_exists(crud_name, 'new') %}
|
||||
{% if is_granted(chill_crud_config('role', crud_name, 'new'), entity) %}
|
||||
@@ -44,6 +44,17 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock content_view_actions_duplicate_link %}
|
||||
{% block content_view_actions_merge %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_thirdparty_find_duplicate',
|
||||
{ 'thirdparty_id': entity.id }) }}"
|
||||
title="{{ 'Merge'|trans }}"
|
||||
class="btn btn-misc">
|
||||
<i class="bi bi-chevron-contract"></i>
|
||||
{{ 'Merge'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
{% block content_view_actions_edit_link %}
|
||||
{% if chill_crud_action_exists(crud_name, 'edit') %}
|
||||
{% if is_granted(chill_crud_config('role', crud_name, 'edit'), entity) %}
|
||||
|
@@ -63,8 +63,7 @@
|
||||
|
||||
<script>
|
||||
const uncheckAll = () => {
|
||||
const allCenters = document.getElementsByName('centers[center][]');
|
||||
|
||||
const allCenters = document.getElementsByName('centers[centers][]');
|
||||
allCenters.forEach(checkbox => checkbox.checked = false)
|
||||
}
|
||||
</script>
|
||||
|
@@ -68,10 +68,17 @@
|
||||
{{ form_label(form.entity_choices[checkbox_name])}}
|
||||
{% endif %}
|
||||
<div class="col-sm-8 pt-2">
|
||||
{% for c in form['entity_choices'][checkbox_name].children %}
|
||||
{{ form_widget(c) }}
|
||||
{{ form_label(c) }}
|
||||
{% endfor %}
|
||||
{% set field = form['entity_choices'][checkbox_name] %}
|
||||
{% if field.vars.expanded %}
|
||||
{# Render expanded checkboxes/radios #}
|
||||
{% for c in field.children %}
|
||||
{{ form_widget(c) }}
|
||||
{{ form_label(c) }}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{# Render select dropdown #}
|
||||
{{ form_widget(field) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
@@ -0,0 +1,13 @@
|
||||
<header>
|
||||
<nav class="navbar navbar-dark bg-primary navbar-expand-md">
|
||||
<div class="container-xxl">
|
||||
|
||||
<div class="col-12">
|
||||
<a class="navbar-brand" href="{{ path('chill_main_homepage') }}">
|
||||
{{ include('@ChillMain/Layout/_header-logo.html.twig') }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
@@ -8,36 +8,36 @@
|
||||
|
||||
<div class="col-md-10">
|
||||
<h2>{{ 'absence.My absence'|trans }}</h2>
|
||||
<div>
|
||||
{% if user.absenceStart is not null %}
|
||||
<div class="alert alert-success flash_message">{{ 'absence.You are listed as absent, as of {date, date, short}'|trans({
|
||||
date: user.absenceStart
|
||||
}) }}
|
||||
{% if user.absenceEnd is not null %}
|
||||
{{ 'until %date%'|trans({'%date%': user.absenceEnd|format_date('short') }) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-warning flash_message">{{ 'absence.No absence listed'|trans }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
{{ form_start(form) }}
|
||||
{{ form_row(form.absenceStart) }}
|
||||
{{ form_row(form.absenceEnd) }}
|
||||
|
||||
{% if user.absenceStart is not null %}
|
||||
<div>
|
||||
<p>{{ 'absence.You are listed as absent, as of'|trans }} {{ user.absenceStart|format_date('long') }}</p>
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li>
|
||||
<a href="{{ path('chill_main_user_absence_unset') }}"
|
||||
class="btn btn-delete">{{ 'absence.Unset absence'|trans }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
<div>
|
||||
<p class="chill-no-data-statement">{{ 'absence.No absence listed'|trans }}</p>
|
||||
</div>
|
||||
<div>
|
||||
{{ form_start(form) }}
|
||||
{{ form_row(form.absenceStart) }}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li>
|
||||
<button class="btn btn-save" type="submit">
|
||||
{{ 'Save'|trans }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{{ form_end(form) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li>
|
||||
<a class="btn btn-delete" title="Modifier" href="{{ path('chill_main_user_absence_unset') }}">{{ 'absence.Unset absence'|trans }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<button class="btn btn-save" type="submit">
|
||||
{{ 'Save'|trans }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
{{ form_end(form) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
@@ -26,11 +26,12 @@
|
||||
|
||||
{{ 'Welcome' | trans }}<br/>
|
||||
|
||||
<b>
|
||||
{{ app.user.getUserIdentifier() }}
|
||||
{{ render(controller('Chill\\MainBundle\\Controller\\UIController::showNotificationUserCounterAction')) }}
|
||||
</b>
|
||||
|
||||
{% if app.user %}
|
||||
<b>
|
||||
{{ app.user.getUserIdentifier() }}
|
||||
{{ render(controller('Chill\\MainBundle\\Controller\\UIController::showNotificationUserCounterAction')) }}
|
||||
</b>
|
||||
{% endif %}
|
||||
{% if is_granted('IS_IMPERSONATOR') %}
|
||||
<i class="fa fa-wrench fa-lg" title="Impersonate mode"></i>
|
||||
{% endif %}
|
||||
|
@@ -16,29 +16,16 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>
|
||||
{{ 'Login to %installation_name%' | trans({ '%installation_name%' : installation.name } ) }}
|
||||
</title>
|
||||
<link rel="shortcut icon" href="{{ asset('build/images/favicon.ico') }}" type="image/x-icon">
|
||||
{{ encore_entry_link_tags('chill') }}
|
||||
</head>
|
||||
<body>
|
||||
<header class="navigation container-fluid">
|
||||
<div class="col-4 d-md-none parent">
|
||||
<div class="col-10 col-md-12 offset-2 logo-container">
|
||||
<a href="{{ path('chill_main_homepage') }}">
|
||||
<img class="logo" src="{{ asset('build/images/logo-chill-sans-slogan_white.png') }}">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
{% extends "@ChillMain/layout.html.twig" %}
|
||||
|
||||
<div id="content">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% set header_logo_only = 1 %}
|
||||
|
||||
{% block title %}{{ 'Login to %installation_name%' | trans({ '%installation_name%' : installation.name } ) }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div id="content">
|
||||
{% block password_content %}{% endblock %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
{% block title %}{{ "New password set"|trans }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block password_content %}
|
||||
<div class="col-10 centered">
|
||||
|
||||
<h1>{{ "New password set"|trans }}</h1>
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block password_content %}
|
||||
<div class="col-10 centered">
|
||||
|
||||
<h1>{{ title }}</h1>
|
||||
|
@@ -22,7 +22,7 @@
|
||||
|
||||
{% block title %}{{"Recover password"|trans}}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block password_content %}
|
||||
<div class="col-10 centered">
|
||||
|
||||
<h1>{{ 'Recover password'|trans }}</h1>
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
{% block title "Check your email"|trans %}
|
||||
|
||||
{% block content %}
|
||||
{% block password_content %}
|
||||
|
||||
<div class="col-10 centered">
|
||||
|
||||
|
@@ -30,7 +30,11 @@
|
||||
{{ include('@ChillMain/Layout/_debug.html.twig') }}
|
||||
{% endif %}
|
||||
|
||||
{{ include('@ChillMain/Layout/_header.html.twig') }}
|
||||
{% if header_logo_only is defined and header_logo_only == 1 %}
|
||||
{{ include('@ChillMain/Layout/_header_logo_only.html.twig') }}
|
||||
{% else %}
|
||||
{{ include('@ChillMain/Layout/_header.html.twig') }}
|
||||
{% endif %}
|
||||
|
||||
{% block top_banner %}{#
|
||||
To use if you want to add a banner below the header (ie the menu)
|
||||
@@ -75,7 +79,7 @@
|
||||
<div class="d-flex flex-row mb-5 alert alert-warning" role="alert">
|
||||
<p class="m-2">{{'absence.You are marked as being absent'|trans }}</p>
|
||||
<span class="ms-auto">
|
||||
<a class="btn btn-remove" title="Modifier" href="{{ path('chill_main_user_absence_index') }}">{{ 'absence.Unset absence'|trans }}</a>
|
||||
<a class="btn btn-delete" title="Modifier" href="{{ path('chill_main_user_absence_unset') }}">{{ 'absence.Unset absence'|trans }}</a>
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
86
src/Bundle/ChillMainBundle/Security/RoleDumper.php
Normal file
86
src/Bundle/ChillMainBundle/Security/RoleDumper.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Security;
|
||||
|
||||
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
final readonly class RoleDumper
|
||||
{
|
||||
public function __construct(
|
||||
private RoleProvider $roleProvider,
|
||||
private RoleHierarchyInterface $roleHierarchy,
|
||||
private TranslatorInterface $translator,
|
||||
) {}
|
||||
|
||||
public function dumpAsMarkdown(): string
|
||||
{
|
||||
$roles = $this->roleProvider->getRoles();
|
||||
$rolesWithoutScopes = $this->roleProvider->getRolesWithoutScopes();
|
||||
|
||||
// Group roles by title
|
||||
$groups = [];
|
||||
foreach ($roles as $role) {
|
||||
$title = $this->roleProvider->getRoleTitle($role);
|
||||
$title ??= 'Other';
|
||||
$groups[$title][] = $role;
|
||||
}
|
||||
|
||||
// Sort groups by title
|
||||
ksort($groups, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
|
||||
$lines = [];
|
||||
foreach ($groups as $title => $roleList) {
|
||||
// Sort roles by translated label for deterministic output
|
||||
usort($roleList, function (string $a, string $b): int {
|
||||
$ta = $this->translator->trans($a);
|
||||
$tb = $this->translator->trans($b);
|
||||
|
||||
return strcasecmp($ta, $tb);
|
||||
});
|
||||
|
||||
$translatedTitle = $this->translator->trans($title);
|
||||
$lines[] = '## '.$translatedTitle;
|
||||
|
||||
foreach ($roleList as $role) {
|
||||
// Translate primary role
|
||||
$translatedRole = $this->translator->trans($role);
|
||||
|
||||
// Scope marker: (S) if needs scope, (~~S~~) if no scope required
|
||||
$needsScope = !in_array($role, $rolesWithoutScopes, true);
|
||||
$scopeMarker = $needsScope ? '(S)' : '(~~S~~)';
|
||||
|
||||
// Compute dependent roles from hierarchy (exclude itself)
|
||||
$reachable = $this->roleHierarchy->getReachableRoleNames([$role]);
|
||||
$dependents = array_values(array_filter($reachable, static fn (string $r): bool => $r !== $role));
|
||||
|
||||
// Translate dependents and sort deterministically
|
||||
$translatedDependents = array_map(fn (string $r) => $this->translator->trans($r), $dependents);
|
||||
sort($translatedDependents, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
|
||||
if (count($translatedDependents) > 0) {
|
||||
$lines[] = sprintf('- **%s** %s: %s', $translatedRole, $scopeMarker, implode(', ', $translatedDependents));
|
||||
} else {
|
||||
$lines[] = sprintf('- **%s** %s', $translatedRole, $scopeMarker);
|
||||
}
|
||||
}
|
||||
|
||||
// Add a blank line between groups
|
||||
$lines[] = '';
|
||||
}
|
||||
|
||||
// Trim possible trailing blank line
|
||||
$markdown = rtrim(implode("\n", $lines));
|
||||
|
||||
return $markdown."\n"; // End with newline for POSIX friendliness
|
||||
}
|
||||
}
|
@@ -52,12 +52,8 @@ class RoleProvider
|
||||
|
||||
/**
|
||||
* Get the title for each role.
|
||||
*
|
||||
* @param string $role
|
||||
*
|
||||
* @return string the title of the role
|
||||
*/
|
||||
public function getRoleTitle($role)
|
||||
public function getRoleTitle(string $role): ?string
|
||||
{
|
||||
$this->initializeRolesTitlesCache();
|
||||
|
||||
|
@@ -39,6 +39,8 @@ class UserNormalizer implements \Symfony\Component\Serializer\Normalizer\Normali
|
||||
'label' => '',
|
||||
'email' => '',
|
||||
'isAbsent' => false,
|
||||
'absenceStart' => null,
|
||||
'absenceEnd' => null,
|
||||
];
|
||||
|
||||
public function __construct(private readonly UserRender $userRender, private readonly ClockInterface $clock) {}
|
||||
@@ -77,6 +79,11 @@ class UserNormalizer implements \Symfony\Component\Serializer\Normalizer\Normali
|
||||
['docgen:expects' => PhoneNumber::class, 'groups' => 'docgen:read']
|
||||
);
|
||||
|
||||
$absenceDatesContext = array_merge(
|
||||
$context,
|
||||
['docgen:expects' => \DateTimeImmutable::class, 'groups' => 'docgen:read']
|
||||
);
|
||||
|
||||
if (null === $object && 'docgen' === $format) {
|
||||
return [...self::NULL_USER, 'phonenumber' => $this->normalizer->normalize(null, $format, $phonenumberContext), 'civility' => $this->normalizer->normalize(null, $format, $civilityContext), 'user_job' => $this->normalizer->normalize(null, $format, $userJobContext), 'main_center' => $this->normalizer->normalize(null, $format, $centerContext), 'main_scope' => $this->normalizer->normalize(null, $format, $scopeContext), 'current_location' => $this->normalizer->normalize(null, $format, $locationContext), 'main_location' => $this->normalizer->normalize(null, $format, $locationContext)];
|
||||
}
|
||||
@@ -99,6 +106,8 @@ class UserNormalizer implements \Symfony\Component\Serializer\Normalizer\Normali
|
||||
'main_center' => $this->normalizer->normalize($object->getMainCenter(), $format, $centerContext),
|
||||
'main_scope' => $this->normalizer->normalize($object->getMainScope($at), $format, $scopeContext),
|
||||
'isAbsent' => $object->isAbsent(),
|
||||
'absenceStart' => $this->normalizer->normalize($object->getAbsenceStart(), $format, $absenceDatesContext),
|
||||
'absenceEnd' => $this->normalizer->normalize($object->getAbsenceEnd(), $format, $absenceDatesContext),
|
||||
];
|
||||
|
||||
if ('docgen' === $format) {
|
||||
|
@@ -67,4 +67,36 @@ class UserTest extends TestCase
|
||||
->first()->getEndDate()
|
||||
);
|
||||
}
|
||||
|
||||
public function testIsAbsent()
|
||||
{
|
||||
$user = new User();
|
||||
|
||||
// Absent: today is within absence period
|
||||
$absenceStart = new \DateTimeImmutable('-1 day');
|
||||
$absenceEnd = new \DateTimeImmutable('+1 day');
|
||||
$user->setAbsenceStart($absenceStart);
|
||||
$user->setAbsenceEnd($absenceEnd);
|
||||
self::assertTrue($user->isAbsent(), 'Should be absent when now is between start and end');
|
||||
|
||||
// Absent: end is null
|
||||
$user->setAbsenceStart(new \DateTimeImmutable('-2 days'));
|
||||
$user->setAbsenceEnd(null);
|
||||
self::assertTrue($user->isAbsent(), 'Should be absent when started and no end');
|
||||
|
||||
// Not absent: absenceStart is in the future
|
||||
$user->setAbsenceStart(new \DateTimeImmutable('+2 days'));
|
||||
$user->setAbsenceEnd(null);
|
||||
self::assertFalse($user->isAbsent(), 'Should not be absent if start is in the future');
|
||||
|
||||
// Not absent: absenceEnd is in the past
|
||||
$user->setAbsenceStart(new \DateTimeImmutable('-5 days'));
|
||||
$user->setAbsenceEnd(new \DateTimeImmutable('-1 day'));
|
||||
self::assertFalse($user->isAbsent(), 'Should not be absent if end is in the past');
|
||||
|
||||
// Not absent: both are null
|
||||
$user->setAbsenceStart(null);
|
||||
$user->setAbsenceEnd(null);
|
||||
self::assertFalse($user->isAbsent(), 'Should not be absent if start is null');
|
||||
}
|
||||
}
|
||||
|
98
src/Bundle/ChillMainBundle/Tests/Security/RoleDumperTest.php
Normal file
98
src/Bundle/ChillMainBundle/Tests/Security/RoleDumperTest.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Tests\Security;
|
||||
|
||||
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
|
||||
use Chill\MainBundle\Security\RoleDumper;
|
||||
use Chill\MainBundle\Security\RoleProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class RoleDumperTest extends TestCase
|
||||
{
|
||||
public function testDumpAsMarkdownGroupsByTitleTranslatesAndListsDependencies(): void
|
||||
{
|
||||
// Fake provider with two groups
|
||||
$provider = new class () implements ProvideRoleHierarchyInterface {
|
||||
public const R_PERSON_SEE = 'CHILL_PERSON_SEE';
|
||||
public const R_PERSON_UPDATE = 'CHILL_PERSON_UPDATE';
|
||||
public const R_REPORT_SEE = 'CHILL_REPORT_SEE';
|
||||
|
||||
public function getRoles(): array
|
||||
{
|
||||
return [self::R_PERSON_SEE, self::R_PERSON_UPDATE, self::R_REPORT_SEE];
|
||||
}
|
||||
|
||||
public function getRolesWithoutScope(): array
|
||||
{
|
||||
// In this test, assume REPORT_SEE does not need scope, others do
|
||||
return [self::R_REPORT_SEE];
|
||||
}
|
||||
|
||||
public function getRolesWithHierarchy(): array
|
||||
{
|
||||
return [
|
||||
'Person' => [self::R_PERSON_SEE, self::R_PERSON_UPDATE],
|
||||
'Report' => [self::R_REPORT_SEE],
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
$roleProvider = new RoleProvider([$provider]);
|
||||
|
||||
// Fake role hierarchy: UPDATE implies SEE; others none
|
||||
$roleHierarchy = new class () implements RoleHierarchyInterface {
|
||||
public function getReachableRoleNames(array $roles): array
|
||||
{
|
||||
$output = [];
|
||||
foreach ($roles as $r) {
|
||||
$output[] = $r;
|
||||
if ('CHILL_PERSON_UPDATE' === $r) {
|
||||
$output[] = 'CHILL_PERSON_SEE';
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(array_unique($output));
|
||||
}
|
||||
};
|
||||
|
||||
// Fake translator that clearly shows translation applied
|
||||
$translator = new class () implements TranslatorInterface {
|
||||
public function trans(string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string
|
||||
{
|
||||
return 'T('.$id.')';
|
||||
}
|
||||
|
||||
public function getLocale(): string
|
||||
{
|
||||
return 'en';
|
||||
}
|
||||
};
|
||||
|
||||
$dumper = new RoleDumper($roleProvider, $roleHierarchy, $translator);
|
||||
$md = $dumper->dumpAsMarkdown();
|
||||
|
||||
$expected = "## T(Person)\n"
|
||||
."- **T(CHILL_PERSON_SEE)** (S)\n"
|
||||
."- **T(CHILL_PERSON_UPDATE)** (S): T(CHILL_PERSON_SEE)\n\n"
|
||||
."## T(Report)\n"
|
||||
."- **T(CHILL_REPORT_SEE)** (~~S~~)\n";
|
||||
|
||||
self::assertSame($expected, $md);
|
||||
}
|
||||
}
|
@@ -101,6 +101,8 @@ final class UserNormalizerTest extends TestCase
|
||||
'text_without_absent' => 'SomeUser',
|
||||
'isAbsent' => false,
|
||||
'main_center' => ['context' => Center::class],
|
||||
'absenceStart' => ['context' => \DateTimeImmutable::class],
|
||||
'absenceEnd' => ['context' => \DateTimeImmutable::class],
|
||||
]];
|
||||
|
||||
yield [$userNoPhone, 'docgen', ['docgen:expects' => User::class],
|
||||
@@ -120,6 +122,8 @@ final class UserNormalizerTest extends TestCase
|
||||
'text_without_absent' => 'AnotherUser',
|
||||
'isAbsent' => false,
|
||||
'main_center' => ['context' => Center::class],
|
||||
'absenceStart' => ['context' => \DateTimeImmutable::class],
|
||||
'absenceEnd' => ['context' => \DateTimeImmutable::class],
|
||||
]];
|
||||
|
||||
yield [null, 'docgen', ['docgen:expects' => User::class], [
|
||||
@@ -138,6 +142,8 @@ final class UserNormalizerTest extends TestCase
|
||||
'text_without_absent' => '',
|
||||
'isAbsent' => false,
|
||||
'main_center' => ['context' => Center::class],
|
||||
'absenceStart' => null,
|
||||
'absenceEnd' => null,
|
||||
]];
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user