mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-08-20 22:53:49 +00:00
Merge branch 'master' into 616_rapid-action
This commit is contained in:
@@ -40,6 +40,10 @@ class ByActivityNumberAggregator implements AggregatorInterface
|
||||
{
|
||||
// No form needed
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data)
|
||||
{
|
||||
|
@@ -52,6 +52,10 @@ class ByCreatorAggregator implements AggregatorInterface
|
||||
{
|
||||
// no form
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data)
|
||||
{
|
||||
|
@@ -57,6 +57,10 @@ class BySocialActionAggregator implements AggregatorInterface
|
||||
{
|
||||
// no form
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data)
|
||||
{
|
||||
|
@@ -57,6 +57,10 @@ class BySocialIssueAggregator implements AggregatorInterface
|
||||
{
|
||||
// no form
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data)
|
||||
{
|
||||
|
@@ -57,6 +57,10 @@ class ByThirdpartyAggregator implements AggregatorInterface
|
||||
{
|
||||
// no form
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data)
|
||||
{
|
||||
|
@@ -57,6 +57,10 @@ class CreatorScopeAggregator implements AggregatorInterface
|
||||
{
|
||||
// no form
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data)
|
||||
{
|
||||
|
@@ -29,14 +29,6 @@ class DateAggregator implements AggregatorInterface
|
||||
|
||||
private const DEFAULT_CHOICE = 'year';
|
||||
|
||||
private TranslatorInterface $translator;
|
||||
|
||||
public function __construct(
|
||||
TranslatorInterface $translator
|
||||
) {
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
public function addRole(): ?string
|
||||
{
|
||||
return null;
|
||||
@@ -84,9 +76,12 @@ class DateAggregator implements AggregatorInterface
|
||||
'multiple' => false,
|
||||
'expanded' => true,
|
||||
'empty_data' => self::DEFAULT_CHOICE,
|
||||
'data' => self::DEFAULT_CHOICE,
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return ['frequency' => self::DEFAULT_CHOICE];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data)
|
||||
{
|
||||
|
@@ -57,6 +57,10 @@ class LocationTypeAggregator implements AggregatorInterface
|
||||
{
|
||||
// no form
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data)
|
||||
{
|
||||
|
@@ -60,6 +60,10 @@ class ActivityTypeAggregator implements AggregatorInterface
|
||||
{
|
||||
// no form required for this aggregator
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data): Closure
|
||||
{
|
||||
|
@@ -58,6 +58,10 @@ class ActivityUserAggregator implements AggregatorInterface
|
||||
{
|
||||
// nothing to add
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, $values, $data): Closure
|
||||
{
|
||||
|
@@ -56,6 +56,10 @@ class ActivityUsersAggregator implements AggregatorInterface
|
||||
{
|
||||
// nothing to add on the form
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data)
|
||||
{
|
||||
|
@@ -55,6 +55,10 @@ class ActivityUsersJobAggregator implements \Chill\MainBundle\Export\AggregatorI
|
||||
{
|
||||
// nothing to add in the form
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data)
|
||||
{
|
||||
|
@@ -55,6 +55,10 @@ class ActivityUsersScopeAggregator implements \Chill\MainBundle\Export\Aggregato
|
||||
{
|
||||
// nothing to add in the form
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data)
|
||||
{
|
||||
|
@@ -110,6 +110,10 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali
|
||||
]
|
||||
);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data)
|
||||
{
|
||||
|
@@ -47,6 +47,10 @@ class SentReceivedAggregator implements AggregatorInterface
|
||||
{
|
||||
// No form needed
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data): callable
|
||||
{
|
||||
|
@@ -39,6 +39,10 @@ class AvgActivityDuration implements ExportInterface, GroupedExportInterface
|
||||
public function buildForm(FormBuilderInterface $builder)
|
||||
{
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getAllowedFormattersTypes(): array
|
||||
{
|
||||
|
@@ -40,6 +40,10 @@ class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterfac
|
||||
{
|
||||
// TODO: Implement buildForm() method.
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getAllowedFormattersTypes(): array
|
||||
{
|
||||
|
@@ -39,6 +39,10 @@ class CountActivity implements ExportInterface, GroupedExportInterface
|
||||
public function buildForm(FormBuilderInterface $builder)
|
||||
{
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getAllowedFormattersTypes(): array
|
||||
{
|
||||
|
@@ -44,6 +44,10 @@ class ListActivity implements ListInterface, GroupedExportInterface
|
||||
{
|
||||
$this->helper->buildForm($builder);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getAllowedFormattersTypes()
|
||||
{
|
||||
|
@@ -40,6 +40,10 @@ class SumActivityDuration implements ExportInterface, GroupedExportInterface
|
||||
{
|
||||
// TODO: Implement buildForm() method.
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getAllowedFormattersTypes(): array
|
||||
{
|
||||
|
@@ -40,6 +40,10 @@ class SumActivityVisitDuration implements ExportInterface, GroupedExportInterfac
|
||||
{
|
||||
// TODO: Implement buildForm() method.
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getAllowedFormattersTypes(): array
|
||||
{
|
||||
|
@@ -35,6 +35,10 @@ class CountActivity implements ExportInterface, GroupedExportInterface
|
||||
public function buildForm(FormBuilderInterface $builder)
|
||||
{
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getAllowedFormattersTypes()
|
||||
{
|
||||
|
@@ -88,6 +88,10 @@ class ListActivity implements ListInterface, GroupedExportInterface
|
||||
])],
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getAllowedFormattersTypes()
|
||||
{
|
||||
|
@@ -53,6 +53,10 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface
|
||||
public function buildForm(FormBuilderInterface $builder)
|
||||
{
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getAllowedFormattersTypes()
|
||||
{
|
||||
|
@@ -68,6 +68,10 @@ class ActivityTypeFilter implements FilterInterface
|
||||
'expanded' => true,
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string'): array
|
||||
{
|
||||
|
@@ -52,6 +52,10 @@ class ByCreatorFilter implements FilterInterface
|
||||
'multiple' => true,
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string'): array
|
||||
{
|
||||
|
@@ -60,6 +60,10 @@ class BySocialActionFilter implements FilterInterface
|
||||
'multiple' => true,
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string'): array
|
||||
{
|
||||
|
@@ -60,6 +60,10 @@ class BySocialIssueFilter implements FilterInterface
|
||||
'multiple' => true,
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string'): array
|
||||
{
|
||||
|
@@ -68,9 +68,12 @@ class EmergencyFilter implements FilterInterface
|
||||
'multiple' => false,
|
||||
'expanded' => true,
|
||||
'empty_data' => self::DEFAULT_CHOICE,
|
||||
'data' => self::DEFAULT_CHOICE,
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return ['accepted_emergency' => self::DEFAULT_CHOICE];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string'): array
|
||||
{
|
||||
|
@@ -44,6 +44,10 @@ class HasNoActivityFilter implements FilterInterface
|
||||
{
|
||||
//no form needed
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string'): array
|
||||
{
|
||||
|
@@ -46,6 +46,10 @@ class LocationFilter implements FilterInterface
|
||||
'label' => 'pick location',
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string'): array
|
||||
{
|
||||
|
@@ -65,6 +65,10 @@ class LocationTypeFilter implements FilterInterface
|
||||
//'label' => false,
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string'): array
|
||||
{
|
||||
|
@@ -69,9 +69,12 @@ class SentReceivedFilter implements FilterInterface
|
||||
'multiple' => false,
|
||||
'expanded' => true,
|
||||
'empty_data' => self::DEFAULT_CHOICE,
|
||||
'data' => self::DEFAULT_CHOICE,
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return ['accepted_sentreceived' => self::DEFAULT_CHOICE];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string'): array
|
||||
{
|
||||
|
@@ -61,6 +61,10 @@ class UserFilter implements FilterInterface
|
||||
'label' => 'Creators',
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string'): array
|
||||
{
|
||||
|
@@ -71,6 +71,10 @@ class UserScopeFilter implements FilterInterface
|
||||
'expanded' => true,
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string'): array
|
||||
{
|
||||
|
@@ -80,11 +80,9 @@ class ActivityDateFilter implements FilterInterface
|
||||
$builder
|
||||
->add('date_from', PickRollingDateType::class, [
|
||||
'label' => 'Activities after this date',
|
||||
'data' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START),
|
||||
])
|
||||
->add('date_to', PickRollingDateType::class, [
|
||||
'label' => 'Activities before this date',
|
||||
'data' => new RollingDate(RollingDate::T_TODAY),
|
||||
]);
|
||||
|
||||
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
|
||||
@@ -127,6 +125,10 @@ class ActivityDateFilter implements FilterInterface
|
||||
}
|
||||
});
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string')
|
||||
{
|
||||
|
@@ -78,6 +78,10 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter
|
||||
],
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string')
|
||||
{
|
||||
|
@@ -56,6 +56,10 @@ class ActivityUsersFilter implements FilterInterface
|
||||
'label' => 'Users',
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string')
|
||||
{
|
||||
|
@@ -82,6 +82,10 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt
|
||||
'expanded' => false,
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string')
|
||||
{
|
||||
|
@@ -112,7 +112,6 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt
|
||||
{
|
||||
$builder->add('date_from', DateType::class, [
|
||||
'label' => 'Implied in an activity after this date',
|
||||
'data' => new DateTime(),
|
||||
'attr' => ['class' => 'datepicker'],
|
||||
'widget' => 'single_text',
|
||||
'format' => 'dd-MM-yyyy',
|
||||
@@ -120,7 +119,6 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt
|
||||
|
||||
$builder->add('date_to', DateType::class, [
|
||||
'label' => 'Implied in an activity before this date',
|
||||
'data' => new DateTime(),
|
||||
'attr' => ['class' => 'datepicker'],
|
||||
'widget' => 'single_text',
|
||||
'format' => 'dd-MM-yyyy',
|
||||
@@ -130,7 +128,6 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt
|
||||
'class' => ActivityReason::class,
|
||||
'choice_label' => fn (ActivityReason $reason): ?string => $this->translatableStringHelper->localize($reason->getName()),
|
||||
'group_by' => fn (ActivityReason $reason): ?string => $this->translatableStringHelper->localize($reason->getCategory()->getName()),
|
||||
'data' => $this->activityReasonRepository->findAll(),
|
||||
'multiple' => true,
|
||||
'expanded' => false,
|
||||
'label' => 'Activity reasons for those activities',
|
||||
@@ -176,6 +173,10 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt
|
||||
}
|
||||
});
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return ['date_from' => new DateTime(), 'date_to' => new DateTime(), 'reasons' => $this->activityReasonRepository->findAll()];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string')
|
||||
{
|
||||
|
@@ -60,6 +60,10 @@ class UsersJobFilter implements FilterInterface
|
||||
'expanded' => true,
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string')
|
||||
{
|
||||
|
@@ -67,6 +67,10 @@ class UsersScopeFilter implements FilterInterface
|
||||
'expanded' => true,
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string')
|
||||
{
|
||||
|
@@ -0,0 +1,198 @@
|
||||
<?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\ActivityBundle\Repository;
|
||||
|
||||
use Chill\ActivityBundle\Entity\Activity;
|
||||
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
|
||||
use Chill\ActivityBundle\Service\GenericDoc\Providers\AccompanyingPeriodActivityGenericDocProvider;
|
||||
use Chill\ActivityBundle\Service\GenericDoc\Providers\PersonActivityGenericDocProvider;
|
||||
use Chill\DocStoreBundle\Entity\PersonDocument;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||
use Chill\DocStoreBundle\GenericDoc\Providers\PersonDocumentGenericDocProvider;
|
||||
use Chill\DocStoreBundle\Repository\PersonDocumentACLAwareRepositoryInterface;
|
||||
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
|
||||
use Chill\MainBundle\Entity\Scope;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface;
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\HttpKernel\HttpCache\Store;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
final readonly class ActivityDocumentACLAwareRepository implements ActivityDocumentACLAwareRepositoryInterface
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManagerInterface $em,
|
||||
private CenterResolverManagerInterface $centerResolverManager,
|
||||
private AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser,
|
||||
private Security $security
|
||||
) {
|
||||
}
|
||||
|
||||
public function buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface
|
||||
{
|
||||
$query = $this->buildBaseFetchQueryActivityDocumentLinkedToPersonFromPersonContext($person, $startDate, $endDate, $content);
|
||||
|
||||
return $this->addFetchQueryByPersonACL($query, $person);
|
||||
}
|
||||
|
||||
public function buildBaseFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
|
||||
{
|
||||
$storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
|
||||
$activityMetadata = $this->em->getClassMetadata(Activity::class);
|
||||
|
||||
$query = new FetchQuery(
|
||||
PersonActivityGenericDocProvider::KEY,
|
||||
sprintf('jsonb_build_object(\'id\', stored_obj.%s, \'activity_id\', activity.%s)', $storedObjectMetadata->getSingleIdentifierColumnName(), $activityMetadata->getSingleIdentifierColumnName()),
|
||||
sprintf('stored_obj.%s', $storedObjectMetadata->getColumnName('createdAt')),
|
||||
sprintf('%s AS stored_obj', $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName())
|
||||
);
|
||||
|
||||
$query->addJoinClause(
|
||||
'JOIN public.activity_storedobject activity_doc ON activity_doc.storedobject_id = stored_obj.id'
|
||||
);
|
||||
|
||||
$query->addJoinClause(
|
||||
'JOIN public.activity activity ON activity.id = activity_doc.activity_id'
|
||||
);
|
||||
|
||||
$query->addWhereClause(
|
||||
sprintf('activity.%s = ?', $activityMetadata->getSingleAssociationJoinColumnName('person')),
|
||||
[$person->getId()],
|
||||
[Types::INTEGER]
|
||||
);
|
||||
|
||||
return $this->addWhereClauses($query, $startDate, $endDate, $content);
|
||||
}
|
||||
|
||||
public function buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
|
||||
{
|
||||
$storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
|
||||
$activityMetadata = $this->em->getClassMetadata(Activity::class);
|
||||
|
||||
$query = new FetchQuery(
|
||||
AccompanyingPeriodActivityGenericDocProvider::KEY,
|
||||
sprintf('jsonb_build_object(\'id\', stored_obj.%s, \'activity_id\', activity.%s)', $storedObjectMetadata->getSingleIdentifierColumnName(), $activityMetadata->getSingleIdentifierColumnName()),
|
||||
sprintf('stored_obj.%s', $storedObjectMetadata->getColumnName('createdAt')),
|
||||
sprintf('%s AS stored_obj', $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName())
|
||||
);
|
||||
|
||||
$query->addJoinClause(
|
||||
'JOIN public.activity_storedobject activity_doc ON activity_doc.storedobject_id = stored_obj.id'
|
||||
);
|
||||
|
||||
$query->addJoinClause(
|
||||
'JOIN public.activity activity ON activity.id = activity_doc.activity_id'
|
||||
);
|
||||
|
||||
// add documents of activities from parcours context
|
||||
$or = [];
|
||||
$orParams = [];
|
||||
$orTypes = [];
|
||||
foreach ($person->getAccompanyingPeriodParticipations() as $participation) {
|
||||
if (!$this->security->isGranted(ActivityVoter::SEE, $participation->getAccompanyingPeriod())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$or[] = sprintf(
|
||||
'(activity.%s = ? AND stored_obj.%s BETWEEN ?::date AND COALESCE(?::date, \'infinity\'::date))',
|
||||
$activityMetadata->getSingleAssociationJoinColumnName('accompanyingPeriod'),
|
||||
$storedObjectMetadata->getColumnName('createdAt')
|
||||
);
|
||||
$orParams = [...$orParams, $participation->getAccompanyingPeriod()->getId(),
|
||||
DateTimeImmutable::createFromInterface($participation->getStartDate()),
|
||||
null === $participation->getEndDate() ? null : DateTimeImmutable::createFromInterface($participation->getEndDate())];
|
||||
$orTypes = [...$orTypes, Types::INTEGER, Types::DATE_IMMUTABLE, Types::DATE_IMMUTABLE];
|
||||
}
|
||||
|
||||
if ([] === $or) {
|
||||
$query->addWhereClause('TRUE = FALSE');
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
$query->addWhereClause(sprintf('(%s)', implode(' OR ', $or)), $orParams, $orTypes);
|
||||
|
||||
return $this->addWhereClauses($query, $startDate, $endDate, $content);
|
||||
}
|
||||
|
||||
private function addWhereClauses(FetchQuery $query, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
|
||||
{
|
||||
$storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
|
||||
|
||||
if (null !== $startDate) {
|
||||
$query->addWhereClause(
|
||||
sprintf('stored_obj.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')),
|
||||
[$startDate],
|
||||
[Types::DATE_IMMUTABLE]
|
||||
);
|
||||
}
|
||||
|
||||
if (null !== $endDate) {
|
||||
$query->addWhereClause(
|
||||
sprintf('stored_obj.%s < ?', $storedObjectMetadata->getColumnName('createdAt')),
|
||||
[$endDate],
|
||||
[Types::DATE_IMMUTABLE]
|
||||
);
|
||||
}
|
||||
|
||||
if (null !== $content and '' !== $content) {
|
||||
$query->addWhereClause(
|
||||
'stored_obj.title ilike ?',
|
||||
['%' . $content . '%'],
|
||||
[Types::STRING]
|
||||
);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
private function addFetchQueryByPersonACL(FetchQuery $fetchQuery, Person $person): FetchQuery
|
||||
{
|
||||
$activityMetadata = $this->em->getClassMetadata(Activity::class);
|
||||
|
||||
$reachableScopes = [];
|
||||
|
||||
foreach ($this->centerResolverManager->resolveCenters($person) as $center) {
|
||||
$reachableScopes = [
|
||||
...$reachableScopes,
|
||||
...$this->authorizationHelperForCurrentUser->getReachableScopes(ActivityVoter::SEE, $center)
|
||||
];
|
||||
}
|
||||
|
||||
if ([] === $reachableScopes) {
|
||||
$fetchQuery->addWhereClause('FALSE = TRUE');
|
||||
|
||||
return $fetchQuery;
|
||||
}
|
||||
|
||||
$fetchQuery->addWhereClause(
|
||||
sprintf(
|
||||
'activity.%s IN (%s)',
|
||||
$activityMetadata->getSingleAssociationJoinColumnName('scope'),
|
||||
implode(', ', array_fill(0, count($reachableScopes), '?'))
|
||||
),
|
||||
array_map(static fn (Scope $s) => $s->getId(), $reachableScopes),
|
||||
array_fill(0, count($reachableScopes), Types::INTEGER)
|
||||
);
|
||||
|
||||
return $fetchQuery;
|
||||
}
|
||||
}
|
@@ -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\ActivityBundle\Repository;
|
||||
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use DateTimeImmutable;
|
||||
|
||||
/**
|
||||
* Gives queries usable for fetching documents, with ACL aware
|
||||
*/
|
||||
interface ActivityDocumentACLAwareRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* Return a fetch query for querying document's activities for a person
|
||||
*
|
||||
* This method must check the rights to see a document: the user must be allowed to see the given activities
|
||||
*/
|
||||
public function buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface;
|
||||
|
||||
/**
|
||||
* Return a fetch query for querying document's activities for an activity in accompanying periods, but for a given person
|
||||
*
|
||||
* This method must check the rights to see a document: the user must be allowed to see the given accompanying periods
|
||||
*/
|
||||
public function buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery;
|
||||
}
|
@@ -1,5 +1,7 @@
|
||||
// Access to Bootstrap variables and mixins
|
||||
@import '~ChillMainAssets/module/bootstrap/shared';
|
||||
@import '~ChillPersonAssets/chill/scss/mixins.scss';
|
||||
@import 'bootstrap/scss/_badge.scss';
|
||||
|
||||
//// ACTIVITY CREATION
|
||||
// first step: select type page
|
||||
@@ -96,3 +98,25 @@ li.document-list-item {
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
.badge-activity-type {
|
||||
display: inline-block;
|
||||
background-color: #f3f3f3;
|
||||
|
||||
.title_label {
|
||||
@include chill_badge(#9acd32);
|
||||
}
|
||||
|
||||
.title_action {
|
||||
padding: var(--bs-badge-padding-y) var(--bs-badge-padding-x);
|
||||
margin-right: 1rem;
|
||||
|
||||
font-size: var(--bs-badge-font-size);
|
||||
font-weight: var(--bs-badge-font-weight);
|
||||
line-height: 1;
|
||||
color: var(--bs-badge-color);
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,83 @@
|
||||
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
|
||||
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
|
||||
{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %}
|
||||
|
||||
{% set person_id = null %}
|
||||
{% if activity.person %}
|
||||
{% set person_id = activity.person.id %}
|
||||
{% endif %}
|
||||
|
||||
{% set accompanying_course_id = null %}
|
||||
{% if activity.accompanyingPeriod %}
|
||||
{% set accompanying_course_id = activity.accompanyingPeriod.id %}
|
||||
{% endif %}
|
||||
|
||||
<div class="item-bloc activity-item{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
|
||||
<div class="item-row">
|
||||
<div class="item-col" style="width: unset">
|
||||
{% if document.isPending %}
|
||||
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
|
||||
{% elseif document.isFailure %}
|
||||
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
{% if activity.accompanyingPeriod is not null and context == 'person' %}
|
||||
<span class="badge bg-primary">
|
||||
<i class="fa fa-random"></i> {{ activity.accompanyingPeriod.id }}
|
||||
</span>
|
||||
{% endif %}
|
||||
<div class="badge-activity-type">
|
||||
<span class="title_label"></span>
|
||||
<span class="title_action">
|
||||
{{ activity.type.name | localize_translatable_string }}
|
||||
{% if activity.emergency %}
|
||||
<span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="denomination h2">
|
||||
{{ document.title|chill_print_or_message("No title") }}
|
||||
</div>
|
||||
{% if document.hasTemplate %}
|
||||
<div>
|
||||
<p>{{ document.template.name|localize_translatable_string }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="item-col">
|
||||
<div class="container">
|
||||
<div class="dates row text-end">
|
||||
<span>{{ document.createdAt|format_date('short') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="item-row separator">
|
||||
<div class="item-col item-meta">
|
||||
{{ mmm.createdBy(document) }}
|
||||
</div>
|
||||
<ul class="item-col record_actions flex-shrink-1">
|
||||
{% if is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) %}
|
||||
<li>
|
||||
{{ document|chill_document_button_group(document.title, is_granted('CHILL_ACTIVITY_UPDATE', activity), {small: false}) }}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_granted('CHILL_ACTIVITY_SEE', activity)%}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_activity_activity_show', {'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id}) }}" class="btn btn-show"></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_granted('CHILL_ACTIVITY_UPDATE', activity) %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_activity_activity_edit', {'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}" class="btn btn-edit"></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
@@ -24,6 +24,9 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Repository\PersonRepository;
|
||||
use Chill\PersonBundle\Templating\Entity\PersonRenderInterface;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||
use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender;
|
||||
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
@@ -55,6 +58,10 @@ class ActivityContext implements
|
||||
|
||||
private TranslatorInterface $translator;
|
||||
|
||||
private ThirdPartyRender $thirdPartyRender;
|
||||
|
||||
private ThirdPartyRepository $thirdPartyRepository;
|
||||
|
||||
public function __construct(
|
||||
DocumentCategoryRepository $documentCategoryRepository,
|
||||
NormalizerInterface $normalizer,
|
||||
@@ -63,7 +70,9 @@ class ActivityContext implements
|
||||
PersonRenderInterface $personRender,
|
||||
PersonRepository $personRepository,
|
||||
TranslatorInterface $translator,
|
||||
BaseContextData $baseContextData
|
||||
BaseContextData $baseContextData,
|
||||
ThirdPartyRender $thirdPartyRender,
|
||||
ThirdPartyRepository $thirdPartyRepository
|
||||
) {
|
||||
$this->documentCategoryRepository = $documentCategoryRepository;
|
||||
$this->normalizer = $normalizer;
|
||||
@@ -73,6 +82,8 @@ class ActivityContext implements
|
||||
$this->personRepository = $personRepository;
|
||||
$this->translator = $translator;
|
||||
$this->baseContextData = $baseContextData;
|
||||
$this->thirdPartyRender = $thirdPartyRender;
|
||||
$this->thirdPartyRepository = $thirdPartyRepository;
|
||||
}
|
||||
|
||||
public function adminFormReverseTransform(array $data): array
|
||||
@@ -89,6 +100,8 @@ class ActivityContext implements
|
||||
'person1Label' => $data['person1Label'] ?? $this->translator->trans('docgen.person 1'),
|
||||
'person2' => $data['person2'] ?? false,
|
||||
'person2Label' => $data['person2Label'] ?? $this->translator->trans('docgen.person 2'),
|
||||
'thirdParty' => $data['thirdParty'] ?? false,
|
||||
'thirdPartyLabel' => $data['thirdPartyLabel'] ?? $this->translator->trans('thirdParty'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -118,6 +131,14 @@ class ActivityContext implements
|
||||
->add('person2Label', TextType::class, [
|
||||
'label' => 'person 2 label',
|
||||
'required' => true,
|
||||
])
|
||||
->add('thirdParty', CheckboxType::class, [
|
||||
'required' => false,
|
||||
'label' => 'docgen.Ask for thirdParty',
|
||||
])
|
||||
->add('thirdPartyLabel', TextType::class, [
|
||||
'label' => 'docgen.thirdParty label',
|
||||
'required' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -143,6 +164,20 @@ class ActivityContext implements
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$thirdParties = $entity->getThirdParties();
|
||||
if ($options['thirdParty'] ?? false) {
|
||||
$builder->add('thirdParty', EntityType::class, [
|
||||
'class' => ThirdParty::class,
|
||||
'choices' => $thirdParties,
|
||||
'choice_label' => fn (ThirdParty $p) => $this->thirdPartyRender->renderString($p, []),
|
||||
'multiple' => false,
|
||||
'required' => false,
|
||||
'expanded' => true,
|
||||
'label' => $options['thirdPartyLabel'],
|
||||
'placeholder' => $this->translator->trans('Any third party selected'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function contextGenerationDataDenormalize(DocGeneratorTemplate $template, $entity, array $data): array
|
||||
@@ -157,6 +192,12 @@ class ActivityContext implements
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== ($id = ($data['thirdParty'] ?? null))) {
|
||||
$denormalized['thirdParty'] = $this->thirdPartyRepository->find($id);
|
||||
} else {
|
||||
$denormalized['thirdParty'] = null;
|
||||
}
|
||||
|
||||
return $denormalized;
|
||||
}
|
||||
|
||||
@@ -165,9 +206,11 @@ class ActivityContext implements
|
||||
$normalized = [];
|
||||
|
||||
foreach (['mainPerson', 'person1', 'person2'] as $k) {
|
||||
$normalized[$k] = null === $data[$k] ? null : $data[$k]->getId();
|
||||
$normalized[$k] = ($data[$k] ?? null)?->getId();
|
||||
}
|
||||
|
||||
$normalized['thirdParty'] = ($data['thirdParty'] ?? null)?->getId();
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
@@ -196,6 +239,13 @@ class ActivityContext implements
|
||||
}
|
||||
}
|
||||
|
||||
if ($options['thirdParty']) {
|
||||
$data['thirdParty'] = $this->normalizer->normalize($contextGenerationData['thirdParty'], 'docgen', [
|
||||
'docgen:expects' => ThirdParty::class,
|
||||
'groups' => 'docgen:read'
|
||||
]);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
@@ -235,7 +285,7 @@ class ActivityContext implements
|
||||
{
|
||||
$options = $template->getOptions();
|
||||
|
||||
return $options['mainPerson'] || $options['person1'] || $options['person2'];
|
||||
return $options['mainPerson'] || $options['person1'] || $options['person2'] || $options ['thirdParty'];
|
||||
}
|
||||
|
||||
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
|
||||
|
@@ -140,26 +140,36 @@ class ListActivitiesByAccompanyingPeriodContext implements
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list
|
||||
*/
|
||||
private function filterActivitiesByUser(array $activities, User $user): array
|
||||
{
|
||||
return array_filter(
|
||||
$activities,
|
||||
function ($activity) use ($user) {
|
||||
$activityUsernames = array_map(static fn ($user) => $user['username'], $activity['users'] ?? []);
|
||||
return in_array($user->getUsername(), $activityUsernames, true);
|
||||
}
|
||||
return array_values(
|
||||
array_filter(
|
||||
$activities,
|
||||
function ($activity) use ($user) {
|
||||
$activityUsernames = array_map(static fn ($user) => $user['username'], $activity['users'] ?? []);
|
||||
return in_array($user->getUsername(), $activityUsernames, true);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list
|
||||
*/
|
||||
private function filterWorksByUser(array $works, User $user): array
|
||||
{
|
||||
return array_filter(
|
||||
$works,
|
||||
function ($work) use ($user) {
|
||||
$workUsernames = array_map(static fn ($user) => $user['username'], $work['referrers'] ?? []);
|
||||
return array_values(
|
||||
array_filter(
|
||||
$works,
|
||||
function ($work) use ($user) {
|
||||
$workUsernames = array_map(static fn ($user) => $user['username'], $work['referrers'] ?? []);
|
||||
|
||||
return in_array($user->getUsername(), $workUsernames, true);
|
||||
}
|
||||
return in_array($user->getUsername(), $workUsernames, true);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,114 @@
|
||||
<?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\ActivityBundle\Service\GenericDoc\Providers;
|
||||
|
||||
use Chill\ActivityBundle\Entity\Activity;
|
||||
use Chill\ActivityBundle\Repository\ActivityDocumentACLAwareRepositoryInterface;
|
||||
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
final class AccompanyingPeriodActivityGenericDocProvider implements GenericDocForAccompanyingPeriodProviderInterface, GenericDocForPersonProviderInterface
|
||||
{
|
||||
public const KEY = 'accompanying_period_activity_document';
|
||||
|
||||
public function __construct(
|
||||
private EntityManagerInterface $em,
|
||||
private Security $security,
|
||||
private ActivityDocumentACLAwareRepositoryInterface $activityDocumentACLAwareRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
||||
{
|
||||
$storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
|
||||
$activityMetadata = $this->em->getClassMetadata(Activity::class);
|
||||
|
||||
$query = new FetchQuery(
|
||||
self::KEY,
|
||||
sprintf("jsonb_build_object('id', doc_obj.%s, 'activity_id', activity.%s)", $storedObjectMetadata->getSingleIdentifierColumnName(), $activityMetadata->getSingleIdentifierColumnName()),
|
||||
'doc_obj.'.$storedObjectMetadata->getColumnName('createdAt'),
|
||||
$storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName().' AS doc_obj'
|
||||
);
|
||||
|
||||
$query->addJoinClause(
|
||||
'JOIN public.activity_storedobject activity_doc ON activity_doc.storedobject_id = doc_obj.id'
|
||||
);
|
||||
|
||||
$query->addJoinClause(
|
||||
'JOIN public.activity activity ON activity.id = activity_doc.activity_id'
|
||||
);
|
||||
|
||||
$query->addWhereClause(
|
||||
'activity.accompanyingperiod_id = ?',
|
||||
[$accompanyingPeriod->getId()],
|
||||
[Types::INTEGER]
|
||||
);
|
||||
|
||||
if (null !== $startDate) {
|
||||
$query->addWhereClause(
|
||||
sprintf('doc_obj.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')),
|
||||
[$startDate],
|
||||
[Types::DATE_IMMUTABLE]
|
||||
);
|
||||
}
|
||||
|
||||
if (null !== $endDate) {
|
||||
$query->addWhereClause(
|
||||
sprintf('doc_obj.%s < ?', $storedObjectMetadata->getColumnName('createdAt')),
|
||||
[$endDate],
|
||||
[Types::DATE_IMMUTABLE]
|
||||
);
|
||||
}
|
||||
|
||||
if (null !== $content) {
|
||||
$query->addWhereClause(
|
||||
'doc_obj.title ilike ?',
|
||||
['%' . $content . '%'],
|
||||
[Types::STRING]
|
||||
);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AccompanyingPeriod $accompanyingPeriod
|
||||
* @return bool
|
||||
*/
|
||||
public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool
|
||||
{
|
||||
return $this->security->isGranted(ActivityVoter::SEE, $accompanyingPeriod);
|
||||
}
|
||||
|
||||
public function isAllowedForPerson(Person $person): bool
|
||||
{
|
||||
return $this->security->isGranted(AccompanyingPeriodVoter::SEE, $person);
|
||||
}
|
||||
|
||||
public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
||||
{
|
||||
return $this->activityDocumentACLAwareRepository
|
||||
->buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext($person, $startDate, $endDate, $content);
|
||||
}
|
||||
}
|
@@ -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\ActivityBundle\Service\GenericDoc\Providers;
|
||||
|
||||
use Chill\ActivityBundle\Repository\ActivityDocumentACLAwareRepository;
|
||||
use Chill\ActivityBundle\Repository\ActivityDocumentACLAwareRepositoryInterface;
|
||||
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
||||
use Chill\DocStoreBundle\Repository\PersonDocumentACLAwareRepositoryInterface;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
final readonly class PersonActivityGenericDocProvider implements GenericDocForPersonProviderInterface
|
||||
{
|
||||
public const KEY = 'person_activity_document';
|
||||
|
||||
public function __construct(
|
||||
private Security $security,
|
||||
private ActivityDocumentACLAwareRepositoryInterface $personActivityDocumentACLAwareRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
||||
{
|
||||
return $this->personActivityDocumentACLAwareRepository->buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(
|
||||
$person,
|
||||
$startDate,
|
||||
$endDate,
|
||||
$content
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Person $person
|
||||
* @return bool
|
||||
*/
|
||||
public function isAllowedForPerson(Person $person): bool
|
||||
{
|
||||
return $this->security->isGranted(ActivityVoter::SEE, $person);
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
<?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\ActivityBundle\Service\GenericDoc\Renderers;
|
||||
|
||||
use Chill\ActivityBundle\Repository\ActivityRepository;
|
||||
use Chill\ActivityBundle\Service\GenericDoc\Providers\AccompanyingPeriodActivityGenericDocProvider;
|
||||
use Chill\ActivityBundle\Service\GenericDoc\Providers\PersonActivityGenericDocProvider;
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||
use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
|
||||
use Chill\DocStoreBundle\Repository\StoredObjectRepository;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
|
||||
final class AccompanyingPeriodActivityGenericDocRenderer implements GenericDocRendererInterface
|
||||
{
|
||||
private StoredObjectRepository $objectRepository;
|
||||
|
||||
private ActivityRepository $activityRepository;
|
||||
|
||||
public function __construct(StoredObjectRepository $storedObjectRepository, ActivityRepository $activityRepository)
|
||||
{
|
||||
$this->objectRepository = $storedObjectRepository;
|
||||
$this->activityRepository = $activityRepository;
|
||||
}
|
||||
|
||||
public function supports(GenericDocDTO $genericDocDTO, $options = []): bool
|
||||
{
|
||||
return $genericDocDTO->key === AccompanyingPeriodActivityGenericDocProvider::KEY || $genericDocDTO->key === PersonActivityGenericDocProvider::KEY;
|
||||
}
|
||||
|
||||
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string
|
||||
{
|
||||
return '@ChillActivity/GenericDoc/activity_document.html.twig';
|
||||
}
|
||||
|
||||
public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array
|
||||
{
|
||||
return [
|
||||
'activity' => $this->activityRepository->find($genericDocDTO->identifiers['activity_id']),
|
||||
'document' => $this->objectRepository->find($genericDocDTO->identifiers['id']),
|
||||
'context' => $genericDocDTO->getContext(),
|
||||
];
|
||||
}
|
||||
}
|
@@ -0,0 +1,126 @@
|
||||
<?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\ActivityBundle\Tests\Repository;
|
||||
|
||||
use Chill\ActivityBundle\Repository\ActivityDocumentACLAwareRepository;
|
||||
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryToSqlBuilder;
|
||||
use Chill\MainBundle\Entity\Scope;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface;
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use phpseclib3\Math\BinaryField;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class ActivityDocumentACLAwareRepositoryTest extends KernelTestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
private CenterResolverManagerInterface $centerResolverManager;
|
||||
|
||||
private AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser;
|
||||
|
||||
private Security $security;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
$this->entityManager = self::$container->get(EntityManagerInterface::class);
|
||||
$this->centerResolverManager = self::$container->get(CenterResolverManagerInterface::class);
|
||||
$this->authorizationHelperForCurrentUser = self::$container->get(AuthorizationHelperForCurrentUserInterface::class);
|
||||
$this->security = self::$container->get(Security::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideDataForPerson
|
||||
*/
|
||||
public function testBuildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, array $reachableScopes, bool $_unused, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content): void
|
||||
{
|
||||
$authorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class);
|
||||
$authorizationHelper->getReachableScopes(ActivityVoter::SEE, Argument::any())
|
||||
->willReturn($reachableScopes);
|
||||
|
||||
$repository = new ActivityDocumentACLAwareRepository(
|
||||
$this->entityManager,
|
||||
$this->centerResolverManager,
|
||||
$authorizationHelper->reveal(),
|
||||
$this->security
|
||||
);
|
||||
|
||||
$query = $repository->buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext($person, $startDate, $endDate, $content);
|
||||
['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query);
|
||||
|
||||
$nb = $this->entityManager->getConnection()->fetchOne("SELECT COUNT(*) FROM ({$sql}) sq", $params, $types);
|
||||
|
||||
self::assertIsInt($nb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideDataForPerson
|
||||
*/
|
||||
public function testBuildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext(Person $person, array $_unused, bool $canSeePeriod, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content): void
|
||||
{
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted(ActivityVoter::SEE, Argument::type(AccompanyingPeriod::class))
|
||||
->willReturn($canSeePeriod);
|
||||
|
||||
$repository = new ActivityDocumentACLAwareRepository(
|
||||
$this->entityManager,
|
||||
$this->centerResolverManager,
|
||||
$this->authorizationHelperForCurrentUser,
|
||||
$security->reveal()
|
||||
);
|
||||
|
||||
$query = $repository->buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext($person, $startDate, $endDate, $content);
|
||||
|
||||
['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query);
|
||||
|
||||
$nb = $this->entityManager->getConnection()->fetchOne("SELECT COUNT(*) FROM ({$sql}) sq", $params, $types);
|
||||
|
||||
self::assertIsInt($nb);
|
||||
}
|
||||
|
||||
public function provideDataForPerson(): iterable
|
||||
{
|
||||
$this->setUp();
|
||||
|
||||
if (null === $person = $this->entityManager->createQuery("SELECT p FROM " . Person::class . " p WHERE SIZE(p.accompanyingPeriodParticipations) > 0 ")
|
||||
->setMaxResults(1)
|
||||
->getSingleResult()) {
|
||||
throw new \RuntimeException("no person in dtabase");
|
||||
}
|
||||
|
||||
if ([] === $scopes = $this->entityManager->createQuery("SELECT s FROM " . Scope::class . " s ")->setMaxResults(5)->getResult()) {
|
||||
throw new \RuntimeException("no scopes in database");
|
||||
}
|
||||
|
||||
yield [$person, [], true, null, null, null];
|
||||
yield [$person, $scopes, true, null, null, null];
|
||||
yield [$person, $scopes, true, new \DateTimeImmutable("1 month ago"), null, null];
|
||||
yield [$person, $scopes, true, new \DateTimeImmutable("1 month ago"), new \DateTimeImmutable("1 week ago"), null];
|
||||
yield [$person, $scopes, true, new \DateTimeImmutable("1 month ago"), new \DateTimeImmutable("1 week ago"), "content"];
|
||||
yield [$person, $scopes, true, null, new \DateTimeImmutable("1 week ago"), "content"];
|
||||
yield [$person, [], true, new \DateTimeImmutable("1 month ago"), new \DateTimeImmutable("1 week ago"), "content"];
|
||||
}
|
||||
|
||||
}
|
@@ -161,6 +161,7 @@ class TimelineActivityProvider implements TimelineProviderInterface
|
||||
|
||||
// loop on reachable scopes
|
||||
foreach ($reachableScopes as $scope) {
|
||||
/** @phpstan-ignore-next-line */
|
||||
if (in_array($scope->getId(), $scopes_ids, true)) {
|
||||
continue;
|
||||
}
|
||||
|
@@ -38,3 +38,6 @@ services:
|
||||
|
||||
Chill\ActivityBundle\Service\EntityInfo\:
|
||||
resource: '../Service/EntityInfo/'
|
||||
|
||||
Chill\ActivityBundle\Service\GenericDoc\:
|
||||
resource: '../Service/GenericDoc/'
|
||||
|
@@ -373,3 +373,8 @@ export:
|
||||
is sent: envoyé
|
||||
is received: reçu
|
||||
Group activity by sentreceived: Grouper les échanges par envoyé / reçu
|
||||
|
||||
generic_doc:
|
||||
filter:
|
||||
keys:
|
||||
accompanying_period_activity_document: Document des échanges des parcours
|
||||
|
@@ -50,6 +50,10 @@ class ByActivityTypeAggregator implements AggregatorInterface
|
||||
{
|
||||
// No form needed
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data)
|
||||
{
|
||||
|
@@ -57,6 +57,10 @@ class ByUserJobAggregator implements AggregatorInterface
|
||||
{
|
||||
// nothing to add in the form
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data)
|
||||
{
|
||||
|
@@ -57,6 +57,10 @@ class ByUserScopeAggregator implements AggregatorInterface
|
||||
{
|
||||
// nothing to add in the form
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data)
|
||||
{
|
||||
|
@@ -34,6 +34,10 @@ class AvgAsideActivityDuration implements ExportInterface, GroupedExportInterfac
|
||||
public function buildForm(FormBuilderInterface $builder)
|
||||
{
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getAllowedFormattersTypes(): array
|
||||
{
|
||||
|
@@ -34,6 +34,10 @@ class CountAsideActivity implements ExportInterface, GroupedExportInterface
|
||||
public function buildForm(FormBuilderInterface $builder)
|
||||
{
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getAllowedFormattersTypes(): array
|
||||
{
|
||||
|
@@ -73,6 +73,10 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface
|
||||
public function buildForm(FormBuilderInterface $builder)
|
||||
{
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getAllowedFormattersTypes()
|
||||
{
|
||||
|
@@ -34,6 +34,10 @@ class SumAsideActivityDuration implements ExportInterface, GroupedExportInterfac
|
||||
public function buildForm(FormBuilderInterface $builder)
|
||||
{
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getAllowedFormattersTypes(): array
|
||||
{
|
||||
|
@@ -76,6 +76,10 @@ class ByActivityTypeFilter implements FilterInterface
|
||||
},
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string'): array
|
||||
{
|
||||
|
@@ -72,11 +72,9 @@ class ByDateFilter implements FilterInterface
|
||||
$builder
|
||||
->add('date_from', PickRollingDateType::class, [
|
||||
'label' => 'export.filter.Aside activities after this date',
|
||||
'data' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START),
|
||||
])
|
||||
->add('date_to', PickRollingDateType::class, [
|
||||
'label' => 'export.filter.Aside activities before this date',
|
||||
'data' => new RollingDate(RollingDate::T_TODAY),
|
||||
]);
|
||||
|
||||
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
|
||||
@@ -119,6 +117,10 @@ class ByDateFilter implements FilterInterface
|
||||
}
|
||||
});
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string'): array
|
||||
{
|
||||
|
@@ -53,6 +53,10 @@ class ByUserFilter implements FilterInterface
|
||||
'label' => 'Creators',
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string'): array
|
||||
{
|
||||
|
@@ -60,6 +60,10 @@ class ByUserJobFilter implements FilterInterface
|
||||
'expanded' => true,
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string')
|
||||
{
|
||||
|
@@ -67,6 +67,10 @@ class ByUserScopeFilter implements FilterInterface
|
||||
'expanded' => true,
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string')
|
||||
{
|
||||
|
@@ -176,11 +176,12 @@ export:
|
||||
agent_id: Utilisateur
|
||||
creator_id: Créateur
|
||||
main_scope: Service principal de l'utilisateur
|
||||
main_center: Centre principal de l'utilisteur
|
||||
main_center: Centre principal de l'utilisateur
|
||||
aside_activity_type: Catégorie d'activité annexe
|
||||
date: Date
|
||||
duration: Durée
|
||||
note: Note
|
||||
id: Identifiant
|
||||
|
||||
Exports of aside activities: Exports des activités annexes
|
||||
Count aside activities: Nombre d'activités annexes
|
||||
|
@@ -21,7 +21,7 @@ class CalculatorResult
|
||||
|
||||
public $label;
|
||||
|
||||
public $result;
|
||||
public float $result;
|
||||
|
||||
public $type;
|
||||
}
|
||||
|
@@ -18,9 +18,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\CalendarBundle\Command;
|
||||
|
||||
use Chill\CalendarBundle\Exception\UserAbsenceSyncException;
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\EventsOnUserSubscriptionCreator;
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MapCalendarToUser;
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSGraphUserRepository;
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSUserAbsenceSync;
|
||||
use Chill\MainBundle\Repository\UserRepositoryInterface;
|
||||
use DateInterval;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
@@ -30,32 +33,17 @@ use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class MapAndSubscribeUserCalendarCommand extends Command
|
||||
final class MapAndSubscribeUserCalendarCommand extends Command
|
||||
{
|
||||
private EntityManagerInterface $em;
|
||||
|
||||
private EventsOnUserSubscriptionCreator $eventsOnUserSubscriptionCreator;
|
||||
|
||||
private LoggerInterface $logger;
|
||||
|
||||
private MapCalendarToUser $mapCalendarToUser;
|
||||
|
||||
private MSGraphUserRepository $userRepository;
|
||||
|
||||
public function __construct(
|
||||
EntityManagerInterface $em,
|
||||
EventsOnUserSubscriptionCreator $eventsOnUserSubscriptionCreator,
|
||||
LoggerInterface $logger,
|
||||
MapCalendarToUser $mapCalendarToUser,
|
||||
MSGraphUserRepository $userRepository
|
||||
private readonly EntityManagerInterface $em,
|
||||
private readonly EventsOnUserSubscriptionCreator $eventsOnUserSubscriptionCreator,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly MapCalendarToUser $mapCalendarToUser,
|
||||
private readonly UserRepositoryInterface $userRepository,
|
||||
private readonly MSUserAbsenceSync $userAbsenceSync,
|
||||
) {
|
||||
parent::__construct('chill:calendar:msgraph-user-map-subscribe');
|
||||
|
||||
$this->em = $em;
|
||||
$this->eventsOnUserSubscriptionCreator = $eventsOnUserSubscriptionCreator;
|
||||
$this->logger = $logger;
|
||||
$this->mapCalendarToUser = $mapCalendarToUser;
|
||||
$this->userRepository = $userRepository;
|
||||
}
|
||||
|
||||
public function execute(InputInterface $input, OutputInterface $output): int
|
||||
@@ -67,83 +55,109 @@ class MapAndSubscribeUserCalendarCommand extends Command
|
||||
/** @var DateInterval $interval the interval before the end of the expiration */
|
||||
$interval = new DateInterval('P1D');
|
||||
$expiration = (new DateTimeImmutable('now'))->add(new DateInterval($input->getOption('subscription-duration')));
|
||||
$total = $this->userRepository->countByMostOldSubscriptionOrWithoutSubscriptionOrData($interval);
|
||||
$users = $this->userRepository->findAllAsArray('fr');
|
||||
$created = 0;
|
||||
$renewed = 0;
|
||||
|
||||
$this->logger->info(self::class . ' the number of user to get - renew', [
|
||||
'total' => $total,
|
||||
$this->logger->info(self::class . ' start user to get - renew', [
|
||||
'expiration' => $expiration->format(DateTimeImmutable::ATOM),
|
||||
]);
|
||||
|
||||
while ($offset < $total) {
|
||||
$users = $this->userRepository->findByMostOldSubscriptionOrWithoutSubscriptionOrData(
|
||||
$interval,
|
||||
$limit,
|
||||
$offset
|
||||
);
|
||||
foreach ($users as $u) {
|
||||
++$offset;
|
||||
|
||||
foreach ($users as $user) {
|
||||
if (!$this->mapCalendarToUser->hasUserId($user)) {
|
||||
$this->mapCalendarToUser->writeMetadata($user);
|
||||
}
|
||||
|
||||
if ($this->mapCalendarToUser->hasUserId($user)) {
|
||||
// we first try to renew an existing subscription, if any.
|
||||
// if not, or if it fails, we try to create a new one
|
||||
if ($this->mapCalendarToUser->hasActiveSubscription($user)) {
|
||||
$this->logger->debug(self::class . ' renew a subscription for', [
|
||||
'userId' => $user->getId(),
|
||||
'username' => $user->getUsernameCanonical(),
|
||||
]);
|
||||
|
||||
['secret' => $secret, 'id' => $id, 'expiration' => $expirationTs]
|
||||
= $this->eventsOnUserSubscriptionCreator->renewSubscriptionForUser($user, $expiration);
|
||||
$this->mapCalendarToUser->writeSubscriptionMetadata($user, $expirationTs, $id, $secret);
|
||||
|
||||
if (0 !== $expirationTs) {
|
||||
++$renewed;
|
||||
} else {
|
||||
$this->logger->warning(self::class . ' could not renew subscription for a user', [
|
||||
'userId' => $user->getId(),
|
||||
'username' => $user->getUsernameCanonical(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->mapCalendarToUser->hasActiveSubscription($user)) {
|
||||
$this->logger->debug(self::class . ' create a subscription for', [
|
||||
'userId' => $user->getId(),
|
||||
'username' => $user->getUsernameCanonical(),
|
||||
]);
|
||||
|
||||
['secret' => $secret, 'id' => $id, 'expiration' => $expirationTs]
|
||||
= $this->eventsOnUserSubscriptionCreator->createSubscriptionForUser($user, $expiration);
|
||||
$this->mapCalendarToUser->writeSubscriptionMetadata($user, $expirationTs, $id, $secret);
|
||||
|
||||
if (0 !== $expirationTs) {
|
||||
++$created;
|
||||
} else {
|
||||
$this->logger->warning(self::class . ' could not create subscription for a user', [
|
||||
'userId' => $user->getId(),
|
||||
'username' => $user->getUsernameCanonical(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
++$offset;
|
||||
if (false === $u['enabled']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
$user = $this->userRepository->find($u['id']);
|
||||
|
||||
if (null === $user) {
|
||||
$this->logger->error("could not find user by id", ['uid' => $u['id']]);
|
||||
$output->writeln("could not find user by id : " . $u['id']);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->mapCalendarToUser->hasUserId($user)) {
|
||||
$user = $this->mapCalendarToUser->writeMetadata($user);
|
||||
|
||||
// if user still does not have userid, continue
|
||||
if (!$this->mapCalendarToUser->hasUserId($user)) {
|
||||
$this->logger->warning("user does not have a counterpart on ms api", ['userId' => $user->getId(), 'email' => $user->getEmail()]);
|
||||
$output->writeln(sprintf("giving up for user with email %s and id %s", $user->getEmail(), $user->getId()));
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// sync user absence
|
||||
try {
|
||||
$this->userAbsenceSync->syncUserAbsence($user);
|
||||
} catch (UserAbsenceSyncException $e) {
|
||||
$this->logger->error("could not sync user absence", ['userId' => $user->getId(), 'email' => $user->getEmail(), 'exception' => $e->getTraceAsString(), "message" => $e->getMessage()]);
|
||||
$output->writeln(sprintf("Could not sync user absence: id: %s and email: %s", $user->getId(), $user->getEmail()));
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// we first try to renew an existing subscription, if any.
|
||||
// if not, or if it fails, we try to create a new one
|
||||
if ($this->mapCalendarToUser->hasActiveSubscription($user)) {
|
||||
$this->logger->debug(self::class . ' renew a subscription for', [
|
||||
'userId' => $user->getId(),
|
||||
'username' => $user->getUsernameCanonical(),
|
||||
]);
|
||||
|
||||
['secret' => $secret, 'id' => $id, 'expiration' => $expirationTs]
|
||||
= $this->eventsOnUserSubscriptionCreator->renewSubscriptionForUser($user, $expiration);
|
||||
$this->mapCalendarToUser->writeSubscriptionMetadata($user, $expirationTs, $id, $secret);
|
||||
|
||||
if (0 !== $expirationTs) {
|
||||
++$renewed;
|
||||
} else {
|
||||
$this->logger->warning(self::class . ' could not renew subscription for a user', [
|
||||
'userId' => $user->getId(),
|
||||
'username' => $user->getUsernameCanonical(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->mapCalendarToUser->hasActiveSubscription($user)) {
|
||||
$this->logger->debug(self::class . ' create a subscription for', [
|
||||
'userId' => $user->getId(),
|
||||
'username' => $user->getUsernameCanonical(),
|
||||
]);
|
||||
|
||||
['secret' => $secret, 'id' => $id, 'expiration' => $expirationTs]
|
||||
= $this->eventsOnUserSubscriptionCreator->createSubscriptionForUser($user, $expiration);
|
||||
$this->mapCalendarToUser->writeSubscriptionMetadata($user, $expirationTs, $id, $secret);
|
||||
|
||||
if (0 !== $expirationTs) {
|
||||
++$created;
|
||||
} else {
|
||||
$this->logger->warning(self::class . ' could not create subscription for a user', [
|
||||
'userId' => $user->getId(),
|
||||
'username' => $user->getUsernameCanonical(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (0 === $offset % $limit) {
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
}
|
||||
}
|
||||
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$this->logger->warning(self::class . ' process executed', [
|
||||
'created' => $created,
|
||||
'renewed' => $renewed,
|
||||
]);
|
||||
|
||||
$output->writeln("users synchronized");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -152,7 +166,7 @@ class MapAndSubscribeUserCalendarCommand extends Command
|
||||
parent::configure();
|
||||
|
||||
$this
|
||||
->setDescription('MSGraph: collect user metadata and create subscription on events for users')
|
||||
->setDescription('MSGraph: collect user metadata and create subscription on events for users, and sync the user absence-presence')
|
||||
->addOption(
|
||||
'renew-before-end-interval',
|
||||
'r',
|
||||
|
@@ -0,0 +1,20 @@
|
||||
<?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\CalendarBundle\Exception;
|
||||
|
||||
class UserAbsenceSyncException extends \LogicException
|
||||
{
|
||||
public function __construct(string $message = "", int $code = 20_230_706, ?\Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
@@ -58,6 +58,10 @@ final class AgentAggregator implements AggregatorInterface
|
||||
{
|
||||
// no form
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data): Closure
|
||||
{
|
||||
|
@@ -59,6 +59,10 @@ class CancelReasonAggregator implements AggregatorInterface
|
||||
{
|
||||
// no form
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data): Closure
|
||||
{
|
||||
|
@@ -58,6 +58,10 @@ final class JobAggregator implements AggregatorInterface
|
||||
{
|
||||
// no form
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data): Closure
|
||||
{
|
||||
|
@@ -52,6 +52,10 @@ final class LocationAggregator implements AggregatorInterface
|
||||
{
|
||||
// no form
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data): Closure
|
||||
{
|
||||
|
@@ -58,6 +58,10 @@ final class LocationTypeAggregator implements AggregatorInterface
|
||||
{
|
||||
// no form
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data): Closure
|
||||
{
|
||||
|
@@ -40,6 +40,10 @@ class MonthYearAggregator implements AggregatorInterface
|
||||
{
|
||||
// No form needed
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data): Closure
|
||||
{
|
||||
|
@@ -58,6 +58,10 @@ final class ScopeAggregator implements AggregatorInterface
|
||||
{
|
||||
// no form
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data): Closure
|
||||
{
|
||||
|
@@ -56,6 +56,10 @@ class UrgencyAggregator implements AggregatorInterface
|
||||
{
|
||||
// no form
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data): Closure
|
||||
{
|
||||
|
@@ -36,6 +36,10 @@ class CountCalendars implements ExportInterface, GroupedExportInterface
|
||||
{
|
||||
// No form necessary
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getAllowedFormattersTypes(): array
|
||||
{
|
||||
|
@@ -36,6 +36,10 @@ class StatCalendarAvgDuration implements ExportInterface, GroupedExportInterface
|
||||
{
|
||||
// no form needed
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getAllowedFormattersTypes(): array
|
||||
{
|
||||
|
@@ -36,6 +36,10 @@ class StatCalendarSumDuration implements ExportInterface, GroupedExportInterface
|
||||
{
|
||||
// no form needed
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getAllowedFormattersTypes(): array
|
||||
{
|
||||
|
@@ -63,6 +63,10 @@ class AgentFilter implements FilterInterface
|
||||
'expanded' => true,
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string'): array
|
||||
{
|
||||
|
@@ -60,12 +60,12 @@ class BetweenDatesFilter implements FilterInterface
|
||||
public function buildForm(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder
|
||||
->add('date_from', PickRollingDateType::class, [
|
||||
'data' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START),
|
||||
])
|
||||
->add('date_to', PickRollingDateType::class, [
|
||||
'data' => new RollingDate(RollingDate::T_TODAY),
|
||||
]);
|
||||
->add('date_from', PickRollingDateType::class, [])
|
||||
->add('date_to', PickRollingDateType::class, []);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string'): array
|
||||
|
@@ -69,9 +69,12 @@ class CalendarRangeFilter implements FilterInterface
|
||||
'multiple' => false,
|
||||
'expanded' => true,
|
||||
'empty_data' => self::DEFAULT_CHOICE,
|
||||
'data' => self::DEFAULT_CHOICE,
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return ['hasCalendarRange' => self::DEFAULT_CHOICE];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string'): array
|
||||
{
|
||||
|
@@ -76,6 +76,10 @@ class JobFilter implements FilterInterface
|
||||
'expanded' => true,
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string'): array
|
||||
{
|
||||
|
@@ -76,6 +76,10 @@ class ScopeFilter implements FilterInterface
|
||||
'expanded' => true,
|
||||
]);
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string')
|
||||
{
|
||||
|
@@ -1,84 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
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\CalendarBundle\RemoteCalendar\Connector\MSGraph;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use DateInterval;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
||||
use function strtr;
|
||||
|
||||
/**
|
||||
* Contains classes and methods for fetching users with some calendar metadatas.
|
||||
*/
|
||||
class MSGraphUserRepository
|
||||
{
|
||||
private const MOST_OLD_SUBSCRIPTION_OR_ANY_MS_GRAPH = <<<'SQL'
|
||||
select
|
||||
{select}
|
||||
from users u
|
||||
where
|
||||
NOT attributes ?? 'msgraph'
|
||||
OR NOT attributes->'msgraph' ?? 'subscription_events_expiration'
|
||||
OR (attributes->'msgraph' ?? 'subscription_events_expiration' AND (attributes->'msgraph'->>'subscription_events_expiration')::int < EXTRACT(EPOCH FROM (NOW() + :interval::interval)))
|
||||
LIMIT :limit OFFSET :offset
|
||||
;
|
||||
SQL;
|
||||
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
public function countByMostOldSubscriptionOrWithoutSubscriptionOrData(DateInterval $interval): int
|
||||
{
|
||||
$rsm = new ResultSetMapping();
|
||||
$rsm->addScalarResult('c', 'c');
|
||||
|
||||
$sql = strtr(self::MOST_OLD_SUBSCRIPTION_OR_ANY_MS_GRAPH, [
|
||||
'{select}' => 'COUNT(u) AS c',
|
||||
'LIMIT :limit OFFSET :offset' => '',
|
||||
]);
|
||||
|
||||
return $this->entityManager->createNativeQuery($sql, $rsm)->setParameters([
|
||||
'interval' => $interval,
|
||||
])->getSingleScalarResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|User[]
|
||||
*/
|
||||
public function findByMostOldSubscriptionOrWithoutSubscriptionOrData(DateInterval $interval, int $limit = 50, int $offset = 0): array
|
||||
{
|
||||
$rsm = new ResultSetMappingBuilder($this->entityManager);
|
||||
$rsm->addRootEntityFromClassMetadata(User::class, 'u');
|
||||
|
||||
return $this->entityManager->createNativeQuery(
|
||||
strtr(self::MOST_OLD_SUBSCRIPTION_OR_ANY_MS_GRAPH, ['{select}' => $rsm->generateSelectClause()]),
|
||||
$rsm
|
||||
)->setParameters([
|
||||
'interval' => $interval,
|
||||
'limit' => $limit,
|
||||
'offset' => $offset,
|
||||
])->getResult();
|
||||
}
|
||||
}
|
@@ -0,0 +1,69 @@
|
||||
<?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\CalendarBundle\RemoteCalendar\Connector\MSGraph;
|
||||
|
||||
use Chill\CalendarBundle\Exception\UserAbsenceSyncException;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Clock\ClockInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
final readonly class MSUserAbsenceReader implements MSUserAbsenceReaderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private HttpClientInterface $machineHttpClient,
|
||||
private MapCalendarToUser $mapCalendarToUser,
|
||||
private ClockInterface $clock,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throw UserAbsenceSyncException when the data cannot be reached or is not valid from microsoft
|
||||
*/
|
||||
public function isUserAbsent(User $user): bool|null
|
||||
{
|
||||
$id = $this->mapCalendarToUser->getUserId($user);
|
||||
|
||||
if (null === $id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$automaticRepliesSettings = $this->machineHttpClient
|
||||
->request('GET', 'users/' . $id . '/mailboxSettings/automaticRepliesSetting')
|
||||
->toArray(true);
|
||||
} catch (ClientExceptionInterface|DecodingExceptionInterface|RedirectionExceptionInterface|TransportExceptionInterface $e) {
|
||||
throw new UserAbsenceSyncException("Error receiving response for mailboxSettings", 0, $e);
|
||||
} catch (ServerExceptionInterface $e) {
|
||||
throw new UserAbsenceSyncException("Server error receiving response for mailboxSettings", 0, $e);
|
||||
}
|
||||
|
||||
if (!array_key_exists("status", $automaticRepliesSettings)) {
|
||||
throw new \LogicException("no key \"status\" on automatic replies settings: " . json_encode($automaticRepliesSettings, JSON_THROW_ON_ERROR));
|
||||
}
|
||||
|
||||
return match ($automaticRepliesSettings['status']) {
|
||||
'disabled' => false,
|
||||
'alwaysEnabled' => true,
|
||||
'scheduled' =>
|
||||
RemoteEventConverter::convertStringDateWithoutTimezone($automaticRepliesSettings['scheduledStartDateTime']['dateTime']) < $this->clock->now()
|
||||
&& RemoteEventConverter::convertStringDateWithoutTimezone($automaticRepliesSettings['scheduledEndDateTime']['dateTime']) > $this->clock->now(),
|
||||
default => throw new UserAbsenceSyncException("this status is not documented by Microsoft")
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
<?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\CalendarBundle\RemoteCalendar\Connector\MSGraph;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
|
||||
interface MSUserAbsenceReaderInterface
|
||||
{
|
||||
/**
|
||||
* @throw UserAbsenceSyncException when the data cannot be reached or is not valid from microsoft
|
||||
*/
|
||||
public function isUserAbsent(User $user): bool|null;
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
<?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\CalendarBundle\RemoteCalendar\Connector\MSGraph;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Clock\ClockInterface;
|
||||
|
||||
readonly class MSUserAbsenceSync
|
||||
{
|
||||
public function __construct(
|
||||
private MSUserAbsenceReaderInterface $absenceReader,
|
||||
private ClockInterface $clock,
|
||||
private LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
public function syncUserAbsence(User $user): void
|
||||
{
|
||||
$absence = $this->absenceReader->isUserAbsent($user);
|
||||
|
||||
if (null === $absence) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($absence === $user->isAbsent()) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
$this->logger->info("will change user absence", ['userId' => $user->getId()]);
|
||||
|
||||
if ($absence) {
|
||||
$this->logger->debug("make user absent", ['userId' => $user->getId()]);
|
||||
$user->setAbsenceStart($this->clock->now());
|
||||
} else {
|
||||
$this->logger->debug("make user present", ['userId' => $user->getId()]);
|
||||
$user->setAbsenceStart(null);
|
||||
}
|
||||
}
|
||||
}
|
@@ -23,6 +23,8 @@ use Chill\CalendarBundle\Command\MapAndSubscribeUserCalendarCommand;
|
||||
use Chill\CalendarBundle\Controller\RemoteCalendarConnectAzureController;
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MachineHttpClient;
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MachineTokenStorage;
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSUserAbsenceReaderInterface;
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSUserAbsenceSync;
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraphRemoteCalendarConnector;
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\NullRemoteCalendarConnector;
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\RemoteCalendarConnectorInterface;
|
||||
@@ -37,17 +39,13 @@ class RemoteCalendarCompilerPass implements CompilerPassInterface
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
$config = $container->getParameter('chill_calendar');
|
||||
$connector = null;
|
||||
|
||||
if (!$config['remote_calendars_sync']['enabled']) {
|
||||
$connector = NullRemoteCalendarConnector::class;
|
||||
}
|
||||
|
||||
if ($config['remote_calendars_sync']['microsoft_graph']['enabled']) {
|
||||
if (true === $config['remote_calendars_sync']['microsoft_graph']['enabled']) {
|
||||
$connector = MSGraphRemoteCalendarConnector::class;
|
||||
|
||||
$container->setAlias(HttpClientInterface::class . ' $machineHttpClient', MachineHttpClient::class);
|
||||
} else {
|
||||
$connector = NullRemoteCalendarConnector::class;
|
||||
// remove services which cannot be loaded
|
||||
$container->removeDefinition(MapAndSubscribeUserCalendarCommand::class);
|
||||
$container->removeDefinition(AzureGrantAdminConsentAndAcquireToken::class);
|
||||
@@ -55,16 +53,14 @@ class RemoteCalendarCompilerPass implements CompilerPassInterface
|
||||
$container->removeDefinition(MachineTokenStorage::class);
|
||||
$container->removeDefinition(MachineHttpClient::class);
|
||||
$container->removeDefinition(MSGraphRemoteCalendarConnector::class);
|
||||
$container->removeDefinition(MSUserAbsenceReaderInterface::class);
|
||||
$container->removeDefinition(MSUserAbsenceSync::class);
|
||||
}
|
||||
|
||||
if (!$container->hasAlias(Azure::class) && $container->hasDefinition('knpu.oauth2.client.azure')) {
|
||||
$container->setAlias(Azure::class, 'knpu.oauth2.provider.azure');
|
||||
}
|
||||
|
||||
if (null === $connector) {
|
||||
throw new RuntimeException('Could not configure remote calendar');
|
||||
}
|
||||
|
||||
foreach ([
|
||||
NullRemoteCalendarConnector::class,
|
||||
MSGraphRemoteCalendarConnector::class, ] as $serviceId) {
|
||||
|
@@ -0,0 +1 @@
|
||||
import './scss/badge.scss';
|
@@ -0,0 +1,25 @@
|
||||
@import '~ChillPersonAssets/chill/scss/mixins.scss';
|
||||
@import '~ChillMainAssets/module/bootstrap/shared';
|
||||
|
||||
.badge-calendar {
|
||||
display: inline-block;
|
||||
background-color: #f3f3f3;
|
||||
|
||||
.title_label {
|
||||
@include chill_badge($chill-l-gray);
|
||||
}
|
||||
|
||||
.title_action {
|
||||
padding: var(--bs-badge-padding-y) var(--bs-badge-padding-x);
|
||||
margin-right: 1rem;
|
||||
|
||||
font-size: var(--bs-badge-font-size);
|
||||
font-weight: var(--bs-badge-font-weight);
|
||||
line-height: 1;
|
||||
color: var(--bs-badge-color);
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
}
|
||||
|
@@ -17,4 +17,4 @@ span.calendarRangeItems {
|
||||
text-decoration: none;
|
||||
padding: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,75 @@
|
||||
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
|
||||
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
|
||||
{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %}
|
||||
|
||||
{% set c = document.calendar %}
|
||||
|
||||
<div class="item-bloc">
|
||||
<div class="item-row">
|
||||
<div class="item-col" style="width: unset">
|
||||
{% if document.storedObject.isPending %}
|
||||
<div class="badge text-bg-info" data-docgen-is-pending="{{ document.storedObject.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div>
|
||||
{% elseif document.storedObject.isFailure %}
|
||||
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
{% if c.accompanyingPeriod is not null and context == 'person' %}
|
||||
<span class="badge bg-primary">
|
||||
<i class="fa fa-random"></i> {{ c.accompanyingPeriod.id }}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
<span class="badge-calendar">
|
||||
<span class="title_label"></span>
|
||||
<span class="title_action">
|
||||
{{ 'Calendar'|trans }}
|
||||
{% if c.endDate.diff(c.startDate).days >= 1 %}
|
||||
{{ c.startDate|format_datetime('short', 'short') }}
|
||||
- {{ c.endDate|format_datetime('short', 'short') }}
|
||||
{% else %}
|
||||
{{ c.startDate|format_datetime('short', 'short') }}
|
||||
- {{ c.endDate|format_datetime('none', 'short') }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="denomination h2">
|
||||
{{ document.storedObject.title|chill_print_or_message("No title") }}
|
||||
</div>
|
||||
{% if document.storedObject.hasTemplate %}
|
||||
<div>
|
||||
<p>{{ document.storedObject.template.name|localize_translatable_string }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="item-col">
|
||||
<div class="container">
|
||||
<div class="dates row text-end">
|
||||
<span>{{ document.storedObject.createdAt|format_date('short') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-row separator">
|
||||
<div class="item-col item-meta">
|
||||
{{ mmm.createdBy(document) }}
|
||||
</div>
|
||||
<ul class="item-col record_actions flex-shrink-1">
|
||||
{% if is_granted('CHILL_CALENDAR_DOC_SEE', document) %}
|
||||
<li>
|
||||
{{ document.storedObject|chill_document_button_group(document.storedObject.title, is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c)) }}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c) %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_edit', {'id': c.id, 'docId': document.id}) }}" class="btn btn-edit"></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,192 @@
|
||||
<?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\CalendarBundle\Service\GenericDoc\Providers;
|
||||
|
||||
use Chill\CalendarBundle\Entity\Calendar;
|
||||
use Chill\CalendarBundle\Entity\CalendarDoc;
|
||||
use Chill\CalendarBundle\Security\Voter\CalendarVoter;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use Service\GenericDoc\Providers\AccompanyingPeriodCalendarGenericDocProviderTest;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
/**
|
||||
* @see AccompanyingPeriodCalendarGenericDocProviderTest
|
||||
*/
|
||||
final readonly class AccompanyingPeriodCalendarGenericDocProvider implements GenericDocForAccompanyingPeriodProviderInterface, GenericDocForPersonProviderInterface
|
||||
{
|
||||
public const KEY = 'accompanying_period_calendar_document';
|
||||
|
||||
public function __construct(
|
||||
private Security $security,
|
||||
private EntityManagerInterface $em
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws MappingException
|
||||
*/
|
||||
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
||||
{
|
||||
$classMetadata = $this->em->getClassMetadata(CalendarDoc::class);
|
||||
$storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
|
||||
$calendarMetadata = $this->em->getClassMetadata(Calendar::class);
|
||||
|
||||
$query = new FetchQuery(
|
||||
self::KEY,
|
||||
sprintf("jsonb_build_object('id', cd.%s)", $classMetadata->getColumnName('id')),
|
||||
'cd.'.$storedObjectMetadata->getColumnName('createdAt'),
|
||||
$classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS cd'
|
||||
);
|
||||
$query->addJoinClause(
|
||||
sprintf(
|
||||
'JOIN %s doc_store ON doc_store.%s = cd.%s',
|
||||
$storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(),
|
||||
$storedObjectMetadata->getColumnName('id'),
|
||||
$classMetadata->getSingleAssociationJoinColumnName('storedObject')
|
||||
)
|
||||
);
|
||||
|
||||
$query->addJoinClause(
|
||||
sprintf(
|
||||
'JOIN %s calendar ON calendar.%s = cd.%s',
|
||||
$calendarMetadata->getSchemaName().'.'.$calendarMetadata->getTableName(),
|
||||
$calendarMetadata->getColumnName('id'),
|
||||
$classMetadata->getSingleAssociationJoinColumnName('calendar')
|
||||
)
|
||||
);
|
||||
|
||||
$query->addWhereClause(
|
||||
sprintf(
|
||||
'calendar.%s = ?',
|
||||
$calendarMetadata->getAssociationMapping('accompanyingPeriod')['joinColumns'][0]['name']
|
||||
),
|
||||
[$accompanyingPeriod->getId()],
|
||||
[Types::INTEGER]
|
||||
);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool
|
||||
{
|
||||
return $this->security->isGranted(CalendarVoter::SEE, $accompanyingPeriod);
|
||||
}
|
||||
|
||||
public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
||||
{
|
||||
$classMetadata = $this->em->getClassMetadata(CalendarDoc::class);
|
||||
$storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
|
||||
$calendarMetadata = $this->em->getClassMetadata(Calendar::class);
|
||||
|
||||
$query = new FetchQuery(
|
||||
self::KEY,
|
||||
sprintf("jsonb_build_object('id', cd.%s)", $classMetadata->getColumnName('id')),
|
||||
'cd.'.$storedObjectMetadata->getColumnName('createdAt'),
|
||||
$classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS cd'
|
||||
);
|
||||
$query->addJoinClause(
|
||||
sprintf(
|
||||
'JOIN %s doc_store ON doc_store.%s = cd.%s',
|
||||
$storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(),
|
||||
$storedObjectMetadata->getColumnName('id'),
|
||||
$classMetadata->getSingleAssociationJoinColumnName('storedObject')
|
||||
)
|
||||
);
|
||||
|
||||
$query->addJoinClause(
|
||||
sprintf(
|
||||
'JOIN %s calendar ON calendar.%s = cd.%s',
|
||||
$calendarMetadata->getSchemaName().'.'.$calendarMetadata->getTableName(),
|
||||
$calendarMetadata->getColumnName('id'),
|
||||
$classMetadata->getSingleAssociationJoinColumnName('calendar')
|
||||
)
|
||||
);
|
||||
|
||||
// get the documents associated with accompanying periods in which person participates
|
||||
$or = [];
|
||||
$orParams = [];
|
||||
$orTypes = [];
|
||||
foreach ($person->getAccompanyingPeriodParticipations() as $participation) {
|
||||
if (!$this->security->isGranted(CalendarVoter::SEE, $participation->getAccompanyingPeriod())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$or[] = sprintf(
|
||||
'(calendar.%s = ? AND cd.%s BETWEEN ?::date AND COALESCE(?::date, \'infinity\'::date))',
|
||||
$calendarMetadata->getSingleAssociationJoinColumnName('accompanyingPeriod'),
|
||||
$storedObjectMetadata->getColumnName('createdAt')
|
||||
);
|
||||
$orParams = [...$orParams, $participation->getAccompanyingPeriod()->getId(),
|
||||
DateTimeImmutable::createFromInterface($participation->getStartDate()),
|
||||
null === $participation->getEndDate() ? null : DateTimeImmutable::createFromInterface($participation->getEndDate())];
|
||||
$orTypes = [...$orTypes, Types::INTEGER, Types::DATE_IMMUTABLE, Types::DATE_IMMUTABLE];
|
||||
}
|
||||
|
||||
if ([] === $or) {
|
||||
$query->addWhereClause('TRUE = FALSE');
|
||||
|
||||
return $query;
|
||||
}
|
||||
return $this->addWhereClausesToQuery($query, $startDate, $endDate, $content);
|
||||
}
|
||||
|
||||
public function isAllowedForPerson(Person $person): bool
|
||||
{
|
||||
// check that the person is allowed to see an accompanying period. If yes, the
|
||||
// ACL on each accompanying period will be checked when the query is build
|
||||
return $this->security->isGranted(AccompanyingPeriodVoter::SEE, $person);
|
||||
}
|
||||
|
||||
private function addWhereClausesToQuery(FetchQuery $query, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate, ?string $content): FetchQuery
|
||||
{
|
||||
$storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
|
||||
|
||||
if (null !== $startDate) {
|
||||
$query->addWhereClause(
|
||||
sprintf('doc_store.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')),
|
||||
[$startDate],
|
||||
[Types::DATE_IMMUTABLE]
|
||||
);
|
||||
}
|
||||
|
||||
if (null !== $endDate) {
|
||||
$query->addWhereClause(
|
||||
sprintf('doc_store.%s < ?', $storedObjectMetadata->getColumnName('createdAt')),
|
||||
[$endDate],
|
||||
[Types::DATE_IMMUTABLE]
|
||||
);
|
||||
}
|
||||
|
||||
if (null !== $content) {
|
||||
$query->addWhereClause(
|
||||
sprintf('doc_store.%s ilike ?', $storedObjectMetadata->getColumnName('title')),
|
||||
['%' . $content . '%'],
|
||||
[Types::STRING]
|
||||
);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,126 @@
|
||||
<?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\CalendarBundle\Service\GenericDoc\Providers;
|
||||
|
||||
use Chill\CalendarBundle\Entity\Calendar;
|
||||
use Chill\CalendarBundle\Entity\CalendarDoc;
|
||||
use Chill\CalendarBundle\Security\Voter\CalendarVoter;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use Service\GenericDoc\Providers\PersonCalendarGenericDocProviderTest;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
/**
|
||||
* Provide calendar documents for calendar associated to persons
|
||||
*
|
||||
* @see PersonCalendarGenericDocProviderTest
|
||||
*/
|
||||
final readonly class PersonCalendarGenericDocProvider implements GenericDocForPersonProviderInterface
|
||||
{
|
||||
public const KEY = 'person_calendar_document';
|
||||
|
||||
public function __construct(
|
||||
private Security $security,
|
||||
private EntityManagerInterface $em
|
||||
) {
|
||||
}
|
||||
|
||||
private function addWhereClausesToQuery(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
|
||||
{
|
||||
$storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
|
||||
|
||||
if (null !== $startDate) {
|
||||
$query->addWhereClause(
|
||||
sprintf('doc_store.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')),
|
||||
[$startDate],
|
||||
[Types::DATE_IMMUTABLE]
|
||||
);
|
||||
}
|
||||
|
||||
if (null !== $endDate) {
|
||||
$query->addWhereClause(
|
||||
sprintf('doc_store.%s < ?', $storedObjectMetadata->getColumnName('createdAt')),
|
||||
[$endDate],
|
||||
[Types::DATE_IMMUTABLE]
|
||||
);
|
||||
}
|
||||
|
||||
if (null !== $content) {
|
||||
$query->addWhereClause(
|
||||
sprintf('doc_store.%s ilike ?', $storedObjectMetadata->getColumnName('title')),
|
||||
['%' . $content . '%'],
|
||||
[Types::STRING]
|
||||
);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws MappingException
|
||||
*/
|
||||
public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
||||
{
|
||||
$classMetadata = $this->em->getClassMetadata(CalendarDoc::class);
|
||||
$storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
|
||||
$calendarMetadata = $this->em->getClassMetadata(Calendar::class);
|
||||
|
||||
$query = new FetchQuery(
|
||||
self::KEY,
|
||||
sprintf("jsonb_build_object('id', cd.%s)", $classMetadata->getColumnName('id')),
|
||||
'cd.'.$storedObjectMetadata->getColumnName('createdAt'),
|
||||
$classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS cd'
|
||||
);
|
||||
$query->addJoinClause(
|
||||
sprintf(
|
||||
'JOIN %s doc_store ON doc_store.%s = cd.%s',
|
||||
$storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(),
|
||||
$storedObjectMetadata->getColumnName('id'),
|
||||
$classMetadata->getSingleAssociationJoinColumnName('storedObject')
|
||||
)
|
||||
);
|
||||
|
||||
$query->addJoinClause(
|
||||
sprintf(
|
||||
'JOIN %s calendar ON calendar.%s = cd.%s',
|
||||
$calendarMetadata->getSchemaName().'.'.$calendarMetadata->getTableName(),
|
||||
$calendarMetadata->getColumnName('id'),
|
||||
$classMetadata->getSingleAssociationJoinColumnName('calendar')
|
||||
)
|
||||
);
|
||||
|
||||
$query->addWhereClause(
|
||||
sprintf('calendar.%s = ?', $calendarMetadata->getSingleAssociationJoinColumnName('person')),
|
||||
[$person->getId()],
|
||||
[Types::INTEGER]
|
||||
);
|
||||
|
||||
return $this->addWhereClausesToQuery($query, $startDate, $endDate, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Person $person
|
||||
* @return bool
|
||||
*/
|
||||
public function isAllowedForPerson(Person $person): bool
|
||||
{
|
||||
return $this->security->isGranted(CalendarVoter::SEE, $person);
|
||||
}
|
||||
}
|
@@ -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\CalendarBundle\Service\GenericDoc\Renderers;
|
||||
|
||||
use Chill\CalendarBundle\Repository\CalendarDocRepository;
|
||||
use Chill\CalendarBundle\Service\GenericDoc\Providers\AccompanyingPeriodCalendarGenericDocProvider;
|
||||
use Chill\CalendarBundle\Service\GenericDoc\Providers\PersonCalendarGenericDocProvider;
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||
use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
|
||||
|
||||
final class AccompanyingPeriodCalendarGenericDocRenderer implements GenericDocRendererInterface
|
||||
{
|
||||
private CalendarDocRepository $repository;
|
||||
|
||||
public function __construct(CalendarDocRepository $calendarDocRepository)
|
||||
{
|
||||
$this->repository = $calendarDocRepository;
|
||||
}
|
||||
|
||||
public function supports(GenericDocDTO $genericDocDTO, $options = []): bool
|
||||
{
|
||||
return $genericDocDTO->key === AccompanyingPeriodCalendarGenericDocProvider::KEY || $genericDocDTO->key === PersonCalendarGenericDocProvider::KEY;
|
||||
}
|
||||
|
||||
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string
|
||||
{
|
||||
return '@ChillCalendar/GenericDoc/calendar_document.html.twig';
|
||||
}
|
||||
|
||||
public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array
|
||||
{
|
||||
return [
|
||||
'document' => $this->repository->find($genericDocDTO->identifiers['id']),
|
||||
'context' => $genericDocDTO->getContext(),
|
||||
];
|
||||
}
|
||||
}
|
@@ -0,0 +1,176 @@
|
||||
<?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\CalendarBundle\Tests\RemoteCalendar\Connector\MSGraph;
|
||||
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MapCalendarToUser;
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSUserAbsenceReader;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Component\Clock\MockClock;
|
||||
use Symfony\Component\HttpClient\MockHttpClient;
|
||||
use Symfony\Component\HttpClient\Response\MockResponse;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class MSUserAbsenceReaderTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
/**
|
||||
* @dataProvider provideDataTestUserAbsence
|
||||
*/
|
||||
public function testUserAbsenceReader(string $mockResponse, bool $expected, string $message): void
|
||||
{
|
||||
$user = new User();
|
||||
$client = new MockHttpClient([new MockResponse($mockResponse)]);
|
||||
$mapUser = $this->prophesize(MapCalendarToUser::class);
|
||||
$mapUser->getUserId($user)->willReturn('1234');
|
||||
$clock = new MockClock(new \DateTimeImmutable('2023-07-07T12:00:00'));
|
||||
|
||||
$absenceReader = new MSUserAbsenceReader($client, $mapUser->reveal(), $clock);
|
||||
|
||||
self::assertEquals($expected, $absenceReader->isUserAbsent($user), $message);
|
||||
}
|
||||
|
||||
public function testIsUserAbsentWithoutRemoteId(): void
|
||||
{
|
||||
$user = new User();
|
||||
$client = new MockHttpClient();
|
||||
|
||||
$mapUser = $this->prophesize(MapCalendarToUser::class);
|
||||
$mapUser->getUserId($user)->willReturn(null);
|
||||
$clock = new MockClock(new \DateTimeImmutable('2023-07-07T12:00:00'));
|
||||
|
||||
$absenceReader = new MSUserAbsenceReader($client, $mapUser->reveal(), $clock);
|
||||
|
||||
self::assertNull($absenceReader->isUserAbsent($user), "when no user found, absence should be null");
|
||||
}
|
||||
|
||||
public function provideDataTestUserAbsence(): iterable
|
||||
{
|
||||
// contains data that was retrieved from microsoft graph api on 2023-07-06
|
||||
|
||||
yield [
|
||||
<<<'JSON'
|
||||
{
|
||||
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4')/mailboxSettings/automaticRepliesSetting",
|
||||
"status": "disabled",
|
||||
"externalAudience": "none",
|
||||
"internalReplyMessage": "Je suis en congé.",
|
||||
"externalReplyMessage": "",
|
||||
"scheduledStartDateTime": {
|
||||
"dateTime": "2023-07-06T12:00:00.0000000",
|
||||
"timeZone": "UTC"
|
||||
},
|
||||
"scheduledEndDateTime": {
|
||||
"dateTime": "2023-07-07T12:00:00.0000000",
|
||||
"timeZone": "UTC"
|
||||
}
|
||||
}
|
||||
JSON,
|
||||
false,
|
||||
"User is present"
|
||||
];
|
||||
|
||||
yield [
|
||||
<<<'JSON'
|
||||
{
|
||||
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4')/mailboxSettings/automaticRepliesSetting",
|
||||
"status": "scheduled",
|
||||
"externalAudience": "none",
|
||||
"internalReplyMessage": "Je suis en congé.",
|
||||
"externalReplyMessage": "",
|
||||
"scheduledStartDateTime": {
|
||||
"dateTime": "2023-07-06T11:00:00.0000000",
|
||||
"timeZone": "UTC"
|
||||
},
|
||||
"scheduledEndDateTime": {
|
||||
"dateTime": "2023-07-21T11:00:00.0000000",
|
||||
"timeZone": "UTC"
|
||||
}
|
||||
}
|
||||
JSON,
|
||||
true,
|
||||
'User is absent with absence scheduled, we are within this period'
|
||||
];
|
||||
|
||||
yield [
|
||||
<<<'JSON'
|
||||
{
|
||||
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4')/mailboxSettings/automaticRepliesSetting",
|
||||
"status": "scheduled",
|
||||
"externalAudience": "none",
|
||||
"internalReplyMessage": "Je suis en congé.",
|
||||
"externalReplyMessage": "",
|
||||
"scheduledStartDateTime": {
|
||||
"dateTime": "2023-07-08T11:00:00.0000000",
|
||||
"timeZone": "UTC"
|
||||
},
|
||||
"scheduledEndDateTime": {
|
||||
"dateTime": "2023-07-21T11:00:00.0000000",
|
||||
"timeZone": "UTC"
|
||||
}
|
||||
}
|
||||
JSON,
|
||||
false,
|
||||
'User is present: absence is scheduled for later'
|
||||
];
|
||||
|
||||
yield [
|
||||
<<<'JSON'
|
||||
{
|
||||
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4')/mailboxSettings/automaticRepliesSetting",
|
||||
"status": "scheduled",
|
||||
"externalAudience": "none",
|
||||
"internalReplyMessage": "Je suis en congé.",
|
||||
"externalReplyMessage": "",
|
||||
"scheduledStartDateTime": {
|
||||
"dateTime": "2023-07-05T11:00:00.0000000",
|
||||
"timeZone": "UTC"
|
||||
},
|
||||
"scheduledEndDateTime": {
|
||||
"dateTime": "2023-07-06T11:00:00.0000000",
|
||||
"timeZone": "UTC"
|
||||
}
|
||||
}
|
||||
JSON,
|
||||
false,
|
||||
'User is present: absence is past'
|
||||
];
|
||||
|
||||
yield [
|
||||
<<<'JSON'
|
||||
{
|
||||
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4')/mailboxSettings/automaticRepliesSetting",
|
||||
"status": "alwaysEnabled",
|
||||
"externalAudience": "none",
|
||||
"internalReplyMessage": "Je suis en congé.",
|
||||
"externalReplyMessage": "",
|
||||
"scheduledStartDateTime": {
|
||||
"dateTime": "2023-07-06T12:00:00.0000000",
|
||||
"timeZone": "UTC"
|
||||
},
|
||||
"scheduledEndDateTime": {
|
||||
"dateTime": "2023-07-07T12:00:00.0000000",
|
||||
"timeZone": "UTC"
|
||||
}
|
||||
}
|
||||
JSON,
|
||||
true,
|
||||
"User is absent: absence is always enabled"
|
||||
];
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user