diff --git a/.changes/unreleased/Feature-20230817-131059.yaml b/.changes/unreleased/Feature-20230817-131059.yaml new file mode 100644 index 000000000..29de8ec34 --- /dev/null +++ b/.changes/unreleased/Feature-20230817-131059.yaml @@ -0,0 +1,6 @@ +kind: Feature +body: Add locations in Aside Activity. By default, suggest user location, otherwise + a select with all locations. +time: 2023-08-17T13:10:59.152278661+02:00 +custom: + Issue: "133" diff --git a/.changes/unreleased/Feature-20230817-131152.yaml b/.changes/unreleased/Feature-20230817-131152.yaml new file mode 100644 index 000000000..bf9a165bf --- /dev/null +++ b/.changes/unreleased/Feature-20230817-131152.yaml @@ -0,0 +1,6 @@ +kind: Feature +body: 'Adapt Aside Activity exports: display location, filter by location, group by + location' +time: 2023-08-17T13:11:52.911356021+02:00 +custom: + Issue: "133" diff --git a/.changes/unreleased/Fixed-20230817-131239.yaml b/.changes/unreleased/Fixed-20230817-131239.yaml new file mode 100644 index 000000000..6fc62f433 --- /dev/null +++ b/.changes/unreleased/Fixed-20230817-131239.yaml @@ -0,0 +1,5 @@ +kind: Fixed +body: Missing translation in Work Actions exports +time: 2023-08-17T13:12:39.159627128+02:00 +custom: + Issue: "" diff --git a/.changes/unreleased/Fixed-20230822-142809.yaml b/.changes/unreleased/Fixed-20230822-142809.yaml new file mode 100644 index 000000000..b22477135 --- /dev/null +++ b/.changes/unreleased/Fixed-20230822-142809.yaml @@ -0,0 +1,6 @@ +kind: Fixed +body: "Corrects a typing error in 2 filters, which caused an \nerror when trying to + reedit a saved export\n\n" +time: 2023-08-22T14:28:09.485466139+02:00 +custom: + Issue: "135" diff --git a/.changes/unreleased/Fixed-20230829-181332.yaml b/.changes/unreleased/Fixed-20230829-181332.yaml new file mode 100644 index 000000000..bf77afcf9 --- /dev/null +++ b/.changes/unreleased/Fixed-20230829-181332.yaml @@ -0,0 +1,6 @@ +kind: Fixed +body: '[household] when moving a person to a sharing position to a not-sharing position + on the same household on the same date, remove the previous household membership on the same household. This fix duplicate member.' +time: 2023-08-29T18:13:32.799479781+02:00 +custom: + Issue: "136" diff --git a/.changes/unreleased/Fixed-20230829-181837.yaml b/.changes/unreleased/Fixed-20230829-181837.yaml new file mode 100644 index 000000000..447e8379d --- /dev/null +++ b/.changes/unreleased/Fixed-20230829-181837.yaml @@ -0,0 +1,6 @@ +kind: Fixed +body: | + Add missing translation for comment field placeholder in repositionning household editor. +time: 2023-08-29T18:18:37.691526331+02:00 +custom: + Issue: "" diff --git a/.changes/unreleased/Fixed-20230906-154856.yaml b/.changes/unreleased/Fixed-20230906-154856.yaml new file mode 100644 index 000000000..73fb2dc48 --- /dev/null +++ b/.changes/unreleased/Fixed-20230906-154856.yaml @@ -0,0 +1,5 @@ +kind: Fixed +body: Do not send an email to creator twice when adding a comment to a notification +time: 2023-09-06T15:48:56.991246312+02:00 +custom: + Issue: "" diff --git a/.changes/unreleased/UX-20230829-181733.yaml b/.changes/unreleased/UX-20230829-181733.yaml new file mode 100644 index 000000000..981f2c272 --- /dev/null +++ b/.changes/unreleased/UX-20230829-181733.yaml @@ -0,0 +1,6 @@ +kind: UX +body: | + Uniformize badge-person in household banner (background, size) +time: 2023-08-29T18:17:33.190396543+02:00 +custom: + Issue: "" diff --git a/.changes/v2.5.3.md b/.changes/v2.5.3.md new file mode 100644 index 000000000..164a0712a --- /dev/null +++ b/.changes/v2.5.3.md @@ -0,0 +1,3 @@ +## v2.5.3 - 2023-07-20 +### Fixed +* ([#132](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/132)) Rendez-vous documents created would appear in all documents lists of all persons with an accompanying period. Or statements are now added to the where clause to filter out documents that come from unrelated accompanying period/ or person rendez-vous. diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bd041441..cd9f4683a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), and is generated by [Changie](https://github.com/miniscruff/changie). +## v2.5.3 - 2023-07-20 +### Fixed +* ([#132](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/132)) Rendez-vous documents created would appear in all documents lists of all persons with an accompanying period. Or statements are now added to the where clause to filter out documents that come from unrelated accompanying period/ or person rendez-vous. + ## v2.5.2 - 2023-07-15 ### Fixed * [Collate Address] when updating address point, do not use the point's address reference if the similarity is below the requirement for associating the address reference and the address (it uses the postcode's center instead) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..20b03f783 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,80 @@ +# Contributing + +Chill is an open source, community-driven project. + +If you'd like to contribute, please read the following. + +## What can you do ? + +Chill is an open-source project driven by a community of developers, users and social workers. If you don't feel ready to contribute code or patches, reviewing issues and pull requests (PRs) can be a great start to get involved and give back. + +If you don't have your own instance or don't want to use it, you can try to reproduce bugs using the instance https://demo.chill.social + +## Core team + +The core team is the group of developers that determine the direction and evolution of the Chill project. Their votes rule if the features and patches proposed by the community are approved or rejected. + +All the Chill Core members are long-time contributors with solid technical expertise and they have demonstrated a strong commitment to drive the project forward. + +The core team: + +- elects his own members; +- merge pull requests; + +### members + +Project leader: [julienfastre](https://gitlab.com/julienfastre) + +Core members: + +- [tchama](https://gitlab.com/tchama) +- [LenaertsJ](https://gitlab.com/LenaertsJ) +- [nobohan](https://gitlab.com/nobohan) + +### Becoming a project member + +About once a year, the core team discusses the opportunity to invite new members. To become a core team member, you must: + +- take part on the development for at least 6 month: propose multiple merge requests and participate to the peer review process; +- through this participation, demonstrate your technical skills and your knowledge of the software and any of their dependencies; + +### Core Membership Revocation + +A Symfony Core membership can be revoked for any of the following reasons: + +- Refusal to follow the rules and policies stated in this document; +- Lack of activity for the past six months; +- Willful negligence or intent to harm the Chill project; + +The decision is taken by the majority of project members. + +## Code development rules + +### Merge requests + +Every merge request must contains: + +- one more entries suitable for generating a changelog. This is done using the [changie utility](https://changie.dev); +- a comprehensible description of the changes; +- if applicable, automated tests should be adapted or created; +- the code style must pass the project's rules, and non phpstan errors must be raised nor rector refactoring suggestion. + +The pipelines must pass. + +In case of emergency, some rules may be temporarily ignored. + +### Merge Request Voting Policy + +- -1 votes must always be justified by technical and objective reasons; +- +1 (technically: approbation on the merge request) votes do not require justification, unless there is at least one -1 vote; +- Core members can change their votes as many times as they desire during the course of a merge request discussion; +- Core members are not allowed to vote on their own merge requests. + +### Merge Request Merging Process + +All code must be committed to the repository through merge requests, except for minor changes which can be committed directly to the repository. + +### Release Policy + +The Core members are also the release manager for every Chill version. + diff --git a/docs/source/_static/code/exports/CountPerson.php b/docs/source/_static/code/exports/CountPerson.php index 8a6c3a0a6..a0f6931ac 100644 --- a/docs/source/_static/code/exports/CountPerson.php +++ b/docs/source/_static/code/exports/CountPerson.php @@ -85,7 +85,7 @@ class CountPerson implements ExportInterface public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) { // we gather all center the user choose. - $centers = array_map(static fn($el) => $el['center'], $acl); + $centers = array_map(static fn ($el) => $el['center'], $acl); $qb = $this->entityManager->createQueryBuilder(); diff --git a/docs/source/development/user-interface/widgets/ChillMainConfiguration.php b/docs/source/development/user-interface/widgets/ChillMainConfiguration.php index b427832df..c3a90dfee 100644 --- a/docs/source/development/user-interface/widgets/ChillMainConfiguration.php +++ b/docs/source/development/user-interface/widgets/ChillMainConfiguration.php @@ -49,7 +49,7 @@ class ChillMainConfiguration implements ConfigurationInterface ->end() // end of widgets ->end() // end of root/children ->end() // end of root -; + ; return $treeBuilder; } diff --git a/rector.php b/rector.php index 360b18552..0dd8e355c 100644 --- a/rector.php +++ b/rector.php @@ -2,6 +2,13 @@ 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. + */ + use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector; use Rector\Config\RectorConfig; use Rector\Set\ValueObject\LevelSetList; diff --git a/src/Bundle/ChillActivityBundle/Controller/ActivityController.php b/src/Bundle/ChillActivityBundle/Controller/ActivityController.php index e8686eebe..3cac75086 100644 --- a/src/Bundle/ChillActivityBundle/Controller/ActivityController.php +++ b/src/Bundle/ChillActivityBundle/Controller/ActivityController.php @@ -313,7 +313,6 @@ final class ActivityController extends AbstractController private function buildFilterOrder(AccompanyingPeriod|Person $associated): FilterOrderHelper { - $filterBuilder = $this->filterOrderHelperFactory->create(self::class); $types = $this->activityACLAwareRepository->findActivityTypeByAssociated($associated); $jobs = $this->activityACLAwareRepository->findUserJobByAssociated($associated); diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/EmergencyFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/EmergencyFilter.php index 53d93cf8c..48683cf73 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/EmergencyFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/EmergencyFilter.php @@ -22,11 +22,11 @@ use Symfony\Contracts\Translation\TranslatorInterface; class EmergencyFilter implements FilterInterface { private const CHOICES = [ - 'activity is emergency' => true, - 'activity is not emergency' => false, + 'activity is emergency' => 'true', + 'activity is not emergency' => 'false', ]; - private const DEFAULT_CHOICE = false; + private const DEFAULT_CHOICE = 'false'; public function __construct(private readonly TranslatorInterface $translator) { diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php index 1544dd764..7925f861b 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php @@ -322,7 +322,7 @@ final readonly class ActivityACLAwareRepository implements ActivityACLAwareRepos $counter++; } - foreach ($person->getAccompanyingPeriodParticipations() as $participation) { + foreach ($person->getAccompanyingPeriodParticipations() as $participation) { if (!$this->security->isGranted(ActivityVoter::SEE, $participation->getAccompanyingPeriod())) { continue; } diff --git a/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityDocumentACLAwareRepositoryTest.php b/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityDocumentACLAwareRepositoryTest.php index ce4f318e3..74f1d2bd0 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityDocumentACLAwareRepositoryTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityDocumentACLAwareRepositoryTest.php @@ -122,5 +122,4 @@ class ActivityDocumentACLAwareRepositoryTest extends KernelTestCase 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"]; } - } diff --git a/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityController.php b/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityController.php index 1aa53db4e..90ceaf205 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityController.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityController.php @@ -30,6 +30,7 @@ final class AsideActivityController extends CRUDController $asideActivity = new AsideActivity(); $asideActivity->setAgent($this->getUser()); + $asideActivity->setLocation($this->getUser()->getCurrentLocation()); $duration = $request->query->get('duration', '300'); $duration = DateTime::createFromFormat('U', $duration); diff --git a/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivity.php b/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivity.php index ec318f413..412bb873e 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivity.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivity.php @@ -14,6 +14,7 @@ namespace Chill\AsideActivityBundle\Entity; use Chill\MainBundle\Doctrine\Model\TrackCreationInterface; use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Entity\Location; use DateTimeInterface; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; @@ -34,7 +35,7 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface /** * @ORM\Column(type="datetime") */ - private ?\DateTimeInterface $createdAt = null; + private $createdAt; /** * @ORM\ManyToOne(targetEntity=User::class) @@ -45,7 +46,7 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface /** * @ORM\Column(type="datetime") */ - private ?\DateTimeInterface $date = null; + private $date; /** * @ORM\Column(type="time", nullable=true) @@ -60,25 +61,26 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface private ?int $id = null; /** - * @ORM\Column(type="string", length=100, nullable=true) + * @ORM\ManyToOne(targetEntity=Location::class) + * @ORM\JoinColumn(nullable=true) */ - private ?string $location = null; + private ?Location $location = null; /** * @ORM\Column(type="text", nullable=true) */ - private ?string $note = null; + private $note; /** * @ORM\ManyToOne(targetEntity=AsideActivityCategory::class, inversedBy="asideActivities") * @ORM\JoinColumn(nullable=false) */ - private ?\Chill\AsideActivityBundle\Entity\AsideActivityCategory $type = null; + private $type; /** * @ORM\Column(type="datetime", nullable=true) */ - private ?\DateTimeInterface $updatedAt = null; + private $updatedAt; /** * @ORM\ManyToOne(targetEntity=User::class) @@ -115,7 +117,7 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface return $this->id; } - public function getLocation(): ?string + public function getLocation(): ?Location { return $this->location; } @@ -175,7 +177,7 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface return $this; } - public function setLocation(?string $location): self + public function setLocation(?Location $location): self { $this->location = $location; diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByLocationAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByLocationAggregator.php new file mode 100644 index 000000000..c6a35e3d7 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByLocationAggregator.php @@ -0,0 +1,100 @@ +locationRepository->find($value)) { + return ''; + } + + return $l->getName(); + }; + } + + /** + * @inheritDoc + */ + public function getQueryKeys($data): array + { + return ['by_aside_activity_location_aggregator']; + } + + /** + * @inheritDoc + */ + public function getTitle(): string + { + return 'export.aggregator.Group by aside activity location'; + } + + /** + * @inheritDoc + */ + public function addRole(): ?string + { + return null; + } + + /** + * @inheritDoc + */ + public function alterQuery(QueryBuilder $qb, $data): void + { + $qb->addSelect('IDENTITY(aside.location) AS by_aside_activity_location_aggregator') + ->addGroupBy('by_aside_activity_location_aggregator'); + } + + /** + * @inheritDoc + */ + public function applyOn(): string + { + return Declarations::ASIDE_ACTIVITY_TYPE; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php index 89add7817..93b0c495d 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php @@ -23,6 +23,7 @@ use Chill\MainBundle\Export\Helper\DateTimeHelper; use Chill\MainBundle\Export\Helper\UserHelper; use Chill\MainBundle\Export\ListInterface; use Chill\MainBundle\Repository\CenterRepositoryInterface; +use Chill\MainBundle\Repository\LocationRepository; use Chill\MainBundle\Repository\ScopeRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use DateTimeInterface; @@ -32,23 +33,49 @@ use Doctrine\ORM\QueryBuilder; use LogicException; use Symfony\Component\Form\FormBuilderInterface; -final readonly class ListAsideActivity implements ListInterface, GroupedExportInterface +final class ListAsideActivity implements ListInterface, GroupedExportInterface { + private AsideActivityCategoryRepository $asideActivityCategoryRepository; + + private CategoryRender $categoryRender; + + private CenterRepositoryInterface $centerRepository; + + private DateTimeHelper $dateTimeHelper; + + private EntityManagerInterface $em; + + private ScopeRepositoryInterface $scopeRepository; + + private TranslatableStringHelperInterface $translatableStringHelper; + + private UserHelper $userHelper; + public function __construct( - private EntityManagerInterface $em, - private DateTimeHelper $dateTimeHelper, - private UserHelper $userHelper, - private ScopeRepositoryInterface $scopeRepository, - private CenterRepositoryInterface $centerRepository, - private AsideActivityCategoryRepository $asideActivityCategoryRepository, - private CategoryRender $categoryRender, - private TranslatableStringHelperInterface $translatableStringHelper + EntityManagerInterface $em, + DateTimeHelper $dateTimeHelper, + UserHelper $userHelper, + ScopeRepositoryInterface $scopeRepository, + CenterRepositoryInterface $centerRepository, + AsideActivityCategoryRepository $asideActivityCategoryRepository, + CategoryRender $categoryRender, + private LocationRepository $locationRepository, + TranslatableStringHelperInterface $translatableStringHelper ) { + $this->em = $em; + $this->dateTimeHelper = $dateTimeHelper; + $this->userHelper = $userHelper; + $this->scopeRepository = $scopeRepository; + $this->centerRepository = $centerRepository; + $this->asideActivityCategoryRepository = $asideActivityCategoryRepository; + $this->categoryRender = $categoryRender; + $this->translatableStringHelper = $translatableStringHelper; } public function buildForm(FormBuilderInterface $builder) { } + public function getFormDefaultData(): array { return []; @@ -71,67 +98,99 @@ final readonly class ListAsideActivity implements ListInterface, GroupedExportIn public function getLabels($key, array $values, $data) { - return match ($key) { - 'id', 'note' => static function ($value) use ($key) { - if ('_header' === $value) { - return 'export.aside_activity.' . $key; - } + switch ($key) { + case 'id': + case 'note': + return static function ($value) use ($key) { + if ('_header' === $value) { + return 'export.aside_activity.' . $key; + } - return $value ?? ''; - }, - 'duration' => static function ($value) use ($key) { - if ('_header' === $value) { - return 'export.aside_activity.' . $key; - } + return $value ?? ''; + }; - if (null === $value) { - return ''; - } + case 'duration': + return static function ($value) use ($key) { + if ('_header' === $value) { + return 'export.aside_activity.' . $key; + } - if ($value instanceof DateTimeInterface) { - return $value->format('H:i:s'); - } + if (null === $value) { + return ''; + } - return $value; - }, - 'createdAt', 'updatedAt', 'date' => $this->dateTimeHelper->getLabel('export.aside_activity.' . $key), - 'agent_id', 'creator_id' => $this->userHelper->getLabel($key, $values, 'export.aside_activity.' . $key), - 'aside_activity_type' => function ($value) { - if ('_header' === $value) { - return 'export.aside_activity.aside_activity_type'; - } + if ($value instanceof DateTimeInterface) { + return $value->format('H:i:s'); + } - if (null === $value || '' === $value || null === $c = $this->asideActivityCategoryRepository->find($value)) { - return ''; - } + return $value; + }; - return $this->categoryRender->renderString($c, []); - }, - 'main_scope' => function ($value) { - if ('_header' === $value) { - return 'export.aside_activity.main_scope'; - } + case 'createdAt': + case 'updatedAt': + case 'date': + return $this->dateTimeHelper->getLabel('export.aside_activity.' . $key); - if (null === $value || '' === $value || null === $c = $this->scopeRepository->find($value)) { - return ''; - } + case 'agent_id': + case 'creator_id': + return $this->userHelper->getLabel($key, $values, 'export.aside_activity.' . $key); - return $this->translatableStringHelper->localize($c->getName()); - }, - 'main_center' => function ($value) { - if ('_header' === $value) { - return 'export.aside_activity.main_center'; - } + case 'aside_activity_type': + return function ($value) { + if ('_header' === $value) { + return 'export.aside_activity.aside_activity_type'; + } - if (null === $value || '' === $value || null === $c = $this->centerRepository->find($value)) { - /** @var Center $c */ - return ''; - } + if (null === $value || '' === $value || null === $c = $this->asideActivityCategoryRepository->find($value)) { + return ''; + } - return $c->getName(); - }, - default => throw new LogicException('this key is not supported : ' . $key), - }; + return $this->categoryRender->renderString($c, []); + }; + + case 'location': + return function ($value) { + if ('_header' === $value) { + return 'export.aside_activity.location'; + } + + if (null === $value || '' === $value || null === $l = $this->locationRepository->find($value)) { + return ''; + } + + return $l->getName(); + }; + + case 'main_scope': + return function ($value) { + if ('_header' === $value) { + return 'export.aside_activity.main_scope'; + } + + if (null === $value || '' === $value || null === $c = $this->scopeRepository->find($value)) { + return ''; + } + + return $this->translatableStringHelper->localize($c->getName()); + }; + + case 'main_center': + return function ($value) { + if ('_header' === $value) { + return 'export.aside_activity.main_center'; + } + + if (null === $value || '' === $value || null === $c = $this->centerRepository->find($value)) { + /** @var Center $c */ + return ''; + } + + return $c->getName(); + }; + + default: + throw new LogicException('this key is not supported : ' . $key); + } } public function getQueryKeys($data) @@ -148,6 +207,7 @@ final readonly class ListAsideActivity implements ListInterface, GroupedExportIn 'date', 'duration', 'note', + 'location', ]; } @@ -183,6 +243,7 @@ final readonly class ListAsideActivity implements ListInterface, GroupedExportIn ->addSelect('IDENTITY(aside.type) AS aside_activity_type') ->addSelect('aside.date') ->addSelect('aside.duration') + ->addSelect('IDENTITY(aside.location) AS location') ->addSelect('aside.note'); return $qb; diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByLocationFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByLocationFilter.php new file mode 100644 index 000000000..f2808eca1 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByLocationFilter.php @@ -0,0 +1,105 @@ +add('locations', PickUserLocationType::class); + } + + /** + * @inheritDoc + */ + public function getFormDefaultData(): array + { + $user = $this->security->getUser(); + + if ($user instanceof User) { + return [ + 'locations' => $user->getCurrentLocation(), + ]; + } + + return [ + 'locations' => null, + ]; + } + + /** + * @inheritDoc + */ + public function describeAction($data, $format = 'string'): array + { + $locations = $data['locations']->map(fn (Location $l): string => $l->getName()); + + return ['export.filter.Filtered by aside activity location: only %location%', [ + '%location%' => implode(', ', $locations), + ]]; + } + + /** + * @inheritDoc + */ + public function addRole(): ?string + { + return null; + } + + /** + * @inheritDoc + */ + public function alterQuery(QueryBuilder $qb, $data): void + { + $clause = $qb->expr()->in('aside.location', ':locations'); + + $qb->andWhere($clause); + $qb->setParameter('locations', $data['locations']); + } + + /** + * @inheritDoc + */ + public function applyOn(): string + { + return Declarations::ASIDE_ACTIVITY_TYPE; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityFormType.php b/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityFormType.php index dee87061c..d1be3adcf 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityFormType.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityFormType.php @@ -13,13 +13,16 @@ namespace Chill\AsideActivityBundle\Form; use Chill\AsideActivityBundle\Entity\AsideActivity; use Chill\AsideActivityBundle\Form\Type\PickAsideActivityCategoryType; +use Chill\MainBundle\Entity\Location; use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\ChillTextareaType; use Chill\MainBundle\Form\Type\PickUserDynamicType; +use Chill\MainBundle\Form\Type\PickUserLocationType; use DateInterval; use DateTime; use DateTimeImmutable; use DateTimeZone; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; @@ -77,7 +80,9 @@ final class AsideActivityFormType extends AbstractType ->add('note', ChillTextareaType::class, [ 'label' => 'Note', 'required' => false, - ]); + ]) + ->add('location', PickUserLocationType::class) + ; foreach (['duration'] as $fieldName) { $builder->get($fieldName) diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/index.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/index.html.twig index 5ffc73684..1e2711bfe 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/index.html.twig +++ b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/index.html.twig @@ -39,6 +39,9 @@ {% endif %} + {%- if entity.location.name is defined -%} +
{{ entity.location.name }}
+ {%- endif -%}
diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/view.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/view.html.twig index 75da9a444..8fb487d31 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/view.html.twig +++ b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/view.html.twig @@ -22,6 +22,13 @@
{{ 'Created for'|trans }}
{{ entity.agent }}
+ +
{{ 'Asideactivity location'|trans }}
+ {%- if entity.location.name is defined -%} +
{{ entity.location.name }}
+ {%- else -%} +
{{ 'No data given'|trans }}
+ {%- endif -%}

{{ 'Activity data'|trans }}

diff --git a/src/Bundle/ChillAsideActivityBundle/src/config/services/export.yaml b/src/Bundle/ChillAsideActivityBundle/src/config/services/export.yaml index a29413e15..40cb120da 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/config/services/export.yaml +++ b/src/Bundle/ChillAsideActivityBundle/src/config/services/export.yaml @@ -46,19 +46,27 @@ services: tags: - { name: chill.export_filter, alias: 'aside_activity_user_filter' } + Chill\AsideActivityBundle\Export\Filter\ByLocationFilter: + tags: + - { name: chill.export_filter, alias: 'aside_activity_location_filter' } + ## Aggregators chill.aside_activity.export.type_aggregator: class: Chill\AsideActivityBundle\Export\Aggregator\ByActivityTypeAggregator tags: - - { name: chill.export_aggregator, alias: activity_type_aggregator } + - { name: chill.export_aggregator, alias: 'activity_type_aggregator' } chill.aside_activity.export.user_job_aggregator: class: Chill\AsideActivityBundle\Export\Aggregator\ByUserJobAggregator tags: - - { name: chill.export_aggregator, alias: aside_activity_user_job_aggregator } + - { name: chill.export_aggregator, alias: 'aside_activity_user_job_aggregator' } chill.aside_activity.export.user_scope_aggregator: class: Chill\AsideActivityBundle\Export\Aggregator\ByUserScopeAggregator tags: - - { name: chill.export_aggregator, alias: aside_activity_user_scope_aggregator } + - { name: chill.export_aggregator, alias: 'aside_activity_user_scope_aggregator' } + + Chill\AsideActivityBundle\Export\Aggregator\ByLocationAggregator: + tags: + - { name: chill.export_aggregator, alias: 'aside_activity_location_aggregator' } diff --git a/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20230816112809.php b/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20230816112809.php new file mode 100644 index 000000000..bde8af1e3 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20230816112809.php @@ -0,0 +1,39 @@ +addSql('DROP INDEX chill_asideactivity.IDX_A866DA0E64D218E'); + $this->addSql('ALTER TABLE chill_asideactivity.AsideActivity DROP CONSTRAINT FK_A866DA0E64D218E'); + $this->addSql('ALTER TABLE chill_asideactivity.AsideActivity DROP location_id'); + $this->addSql('ALTER TABLE chill_asideactivity.AsideActivity ADD location VARCHAR(100) DEFAULT NULL'); + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_asideactivity.asideactivity ADD location_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_asideactivity.asideactivity DROP location'); + $this->addSql('ALTER TABLE chill_asideactivity.asideactivity ADD CONSTRAINT FK_A866DA0E64D218E FOREIGN KEY (location_id) REFERENCES chill_main_location (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_A866DA0E64D218E ON chill_asideactivity.asideactivity (location_id)'); + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml b/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml index 25d07bd22..6028193db 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml +++ b/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml @@ -26,6 +26,7 @@ Users: Utilisateurs Emergency: Urgent by: "Par " location: Lieu +Asideactivity location: Localisation de l'activité # Crud crud: @@ -182,6 +183,7 @@ export: duration: Durée note: Note id: Identifiant + location: Localisation Exports of aside activities: Exports des activités annexes Count aside activities: Nombre d'activités annexes @@ -202,11 +204,16 @@ export: Filter by user jobs: Filtrer les activités annexes par métier des utilisateurs 'Filtered aside activities by user scope: only %scopes%': "Filtré par service des utilisateur: uniquement %scopes%" Filter by user scope: Filtrer les activités annexes par service d'utilisateur + Filter by aside activity location: Filtrer les activités annexes par localisation + 'Filtered by aside activity location: only %location%': "Filtré par localisation: uniquement %location%" aggregator: Group by aside activity type: Grouper les activités annexes par type d'activité Aside activity type: Type d'activité annexe Aggregate by user job: Grouper les activités annexes par métier des utilisateurs Aggregate by user scope: Grouper les activités annexes par service des utilisateurs + Aside activity location: Localisation des activités annexe + Group by aside activity location: Grouper les activités annexes par localisation + Aside activity localisation: Localisation # ROLES CHILL_ASIDE_ACTIVITY_STATS: Statistiques pour les activités annexes diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php index 6aa79729f..b9587f007 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php @@ -29,11 +29,11 @@ use Symfony\Contracts\Translation\TranslatorInterface; class CalendarRangeFilter implements FilterInterface { private const CHOICES = [ - 'Not made within a calendar range' => true, - 'Made within a calendar range' => false, + 'Not made within a calendar range' => 'true', + 'Made within a calendar range' => 'false', ]; - private const DEFAULT_CHOICE = false; + private const DEFAULT_CHOICE = 'false'; public function __construct(private readonly TranslatorInterface $translator) { diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReader.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReader.php index c70072a47..c3632d2db 100644 --- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReader.php +++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReader.php @@ -65,5 +65,4 @@ final readonly class MSUserAbsenceReader implements MSUserAbsenceReaderInterface default => throw new UserAbsenceSyncException("this status is not documented by Microsoft") }; } - } diff --git a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php index c33ccd853..1040f9f0a 100644 --- a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php +++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php @@ -147,6 +147,9 @@ final readonly class AccompanyingPeriodCalendarGenericDocProvider implements Gen return $query; } + + $query->addWhereClause(implode(" OR ", $or), $orParams, $orTypes); + return $this->addWhereClausesToQuery($query, $startDate, $endDate, $content); } @@ -187,6 +190,4 @@ final readonly class AccompanyingPeriodCalendarGenericDocProvider implements Gen return $query; } - - } diff --git a/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReaderTest.php b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReaderTest.php index 089477fda..2ceab8b1f 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReaderTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReaderTest.php @@ -172,5 +172,4 @@ class MSUserAbsenceReaderTest extends TestCase "User is absent: absence is always enabled" ]; } - } diff --git a/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php b/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php index 414f9da6f..f78f4d19e 100644 --- a/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php +++ b/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php @@ -92,5 +92,4 @@ final readonly class GenericDocForAccompanyingPeriodController ] )); } - } diff --git a/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForPerson.php b/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForPerson.php index 38c4c1b09..676ceb039 100644 --- a/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForPerson.php +++ b/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForPerson.php @@ -91,5 +91,4 @@ final readonly class GenericDocForPerson ] )); } - } diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQuery.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQuery.php index 30e07a841..b1631bb24 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQuery.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQuery.php @@ -82,7 +82,6 @@ class FetchQuery implements FetchQueryInterface } unset($this->wheres[$index], $this->whereParams[$index], $this->whereTypes[$index]); - } public function removeJoinClause(int $index): void @@ -92,7 +91,6 @@ class FetchQuery implements FetchQueryInterface } unset($this->joins[$index], $this->joinParams[$index], $this->joinTypes[$index]); - } public function getSelectKeyString(): string diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForAccompanyingPeriodProviderInterface.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForAccompanyingPeriodProviderInterface.php index 0d3cb1c32..bffa19b53 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForAccompanyingPeriodProviderInterface.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForAccompanyingPeriodProviderInterface.php @@ -27,5 +27,4 @@ interface GenericDocForAccompanyingPeriodProviderInterface * Return true if the user is allowed to see some documents for this provider. */ public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool; - } diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentGenericDocProvider.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentGenericDocProvider.php index fd36f7976..fe03a8b00 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentGenericDocProvider.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentGenericDocProvider.php @@ -143,5 +143,4 @@ final readonly class AccompanyingCourseDocumentGenericDocProvider implements Gen return $query; } - } diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocExtensionRuntime.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocExtensionRuntime.php index 2dee0ed0b..8bb97a9b9 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocExtensionRuntime.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocExtensionRuntime.php @@ -46,5 +46,4 @@ final readonly class GenericDocExtensionRuntime implements RuntimeExtensionInter throw new \LogicException("no renderer found"); } - } diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocRendererInterface.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocRendererInterface.php index 940001f4a..cdd28ac70 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocRendererInterface.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocRendererInterface.php @@ -20,5 +20,4 @@ interface GenericDocRendererInterface public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string; public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array; - } diff --git a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepository.php b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepository.php index 26a42b894..12506581c 100644 --- a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepository.php +++ b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepository.php @@ -98,7 +98,6 @@ final readonly class PersonDocumentACLAwareRepository implements PersonDocumentA continue; } $orPersonId[] = $participation->getPerson()->getId(); - } if ([] === $orPersonId) { diff --git a/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/FetchQueryToSqlBuilderTest.php b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/FetchQueryToSqlBuilderTest.php index 02be9460f..f55b889a9 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/FetchQueryToSqlBuilderTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/FetchQueryToSqlBuilderTest.php @@ -54,9 +54,6 @@ class FetchQueryToSqlBuilderTest extends KernelTestCase ); self::assertEquals(['foo', 'bar_baz', 'baz'], $params); self::assertEquals([Types::STRING, Types::STRING, Types::STRING], $types); - - - } public function testToSqlWithoutWhere(): void @@ -85,5 +82,4 @@ class FetchQueryToSqlBuilderTest extends KernelTestCase self::assertEquals([], $params); self::assertEquals([], $types); } - } diff --git a/src/Bundle/ChillDocStoreBundle/Tests/Repository/PersonDocumentACLAwareRepositoryTest.php b/src/Bundle/ChillDocStoreBundle/Tests/Repository/PersonDocumentACLAwareRepositoryTest.php index fd611042c..bb372a150 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/Repository/PersonDocumentACLAwareRepositoryTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/Repository/PersonDocumentACLAwareRepositoryTest.php @@ -142,7 +142,6 @@ class PersonDocumentACLAwareRepositoryTest extends KernelTestCase yield [$period, new DateTimeImmutable('2 years ago'), new DateTimeImmutable('1 year ago'), null]; yield [$period, null, null, 'test']; yield [$period, new DateTimeImmutable('2 years ago'), new DateTimeImmutable('1 year ago'), 'test']; - } public function provideDataBuildFetchQueryForPerson(): iterable @@ -154,5 +153,4 @@ class PersonDocumentACLAwareRepositoryTest extends KernelTestCase yield [null, null, 'test']; yield [new DateTimeImmutable('2 years ago'), new DateTimeImmutable('1 year ago'), 'test']; } - } diff --git a/src/Bundle/ChillMainBundle/Controller/CenterController.php b/src/Bundle/ChillMainBundle/Controller/CenterController.php index 1f0641000..c733dc653 100644 --- a/src/Bundle/ChillMainBundle/Controller/CenterController.php +++ b/src/Bundle/ChillMainBundle/Controller/CenterController.php @@ -78,6 +78,8 @@ class CenterController extends AbstractController $entities = $em->getRepository(\Chill\MainBundle\Entity\Center::class)->findAll(); + usort($entities, fn (Center $a, Center $b) => $a->getName() <=> $b->getName()); + return $this->render('@ChillMain/Center/index.html.twig', [ 'entities' => $entities, ]); diff --git a/src/Bundle/ChillMainBundle/Cron/CronManager.php b/src/Bundle/ChillMainBundle/Cron/CronManager.php index 3a4d548da..19289c29f 100644 --- a/src/Bundle/ChillMainBundle/Cron/CronManager.php +++ b/src/Bundle/ChillMainBundle/Cron/CronManager.php @@ -73,7 +73,6 @@ final readonly class CronManager implements CronManagerInterface foreach ($orderedJobs as $job) { if ($job->canRun($lasts[$job->getKey()] ?? null)) { if (array_key_exists($job->getKey(), $lasts)) { - $executionData = $lasts[$job->getKey()]->getLastExecutionData(); $this->entityManager diff --git a/src/Bundle/ChillMainBundle/Form/Type/Listing/FilterOrderType.php b/src/Bundle/ChillMainBundle/Form/Type/Listing/FilterOrderType.php index 51d1b3974..b0d80176b 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Listing/FilterOrderType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Listing/FilterOrderType.php @@ -122,7 +122,6 @@ final class FilterOrderType extends \Symfony\Component\Form\AbstractType foreach ($helper->getUserPickers() as $name => [ 'label' => $label, 'options' => $opts ]) { - $userPickersBuilder->add( $name, PickUserDynamicType::class, @@ -136,7 +135,6 @@ final class FilterOrderType extends \Symfony\Component\Form\AbstractType $builder->add($userPickersBuilder); } - } public static function buildCheckboxChoices(array $choices, array $trans = []): array diff --git a/src/Bundle/ChillMainBundle/Notification/Email/NotificationMailer.php b/src/Bundle/ChillMainBundle/Notification/Email/NotificationMailer.php index 0850e1e4a..5de93ce92 100644 --- a/src/Bundle/ChillMainBundle/Notification/Email/NotificationMailer.php +++ b/src/Bundle/ChillMainBundle/Notification/Email/NotificationMailer.php @@ -31,13 +31,18 @@ class NotificationMailer public function postPersistComment(NotificationComment $comment, PostPersistEventArgs $eventArgs): void { - foreach ( - array_merge( - $comment->getNotification()->getAddressees()->toArray(), - [$comment->getNotification()->getSender()] - ) as $dest - ) { - if (null === $dest->getEmail() || $comment->getCreatedBy() !== $dest) { + $dests = [$comment->getNotification()->getSender(), ...$comment->getNotification()->getAddressees()->toArray()]; + + $uniqueDests = []; + foreach ($dests as $dest) { + // avoid duplication + if (in_array(spl_object_hash($dest), $uniqueDests, true)) { + continue; + } + $uniqueDests[] = spl_object_hash($dest); + + // do not send if the sender does not have any email, nor to the creator of the comment + if (null === $dest->getEmail() || $comment->getCreatedBy() === $dest) { continue; } $email = new TemplatedEmail(); diff --git a/src/Bundle/ChillMainBundle/Tests/Cron/CronJobDatabaseInteractionTest.php b/src/Bundle/ChillMainBundle/Tests/Cron/CronJobDatabaseInteractionTest.php index 3dc6fd66c..335292cc7 100644 --- a/src/Bundle/ChillMainBundle/Tests/Cron/CronJobDatabaseInteractionTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Cron/CronJobDatabaseInteractionTest.php @@ -76,7 +76,6 @@ class CronJobDatabaseInteractionTest extends KernelTestCase // run a second time $manager->run(); } - } class JobWithReturn implements CronJobInterface diff --git a/src/Bundle/ChillMainBundle/Tests/Notification/Email/NotificationMailerTest.php b/src/Bundle/ChillMainBundle/Tests/Notification/Email/NotificationMailerTest.php new file mode 100644 index 000000000..c00804e0b --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Notification/Email/NotificationMailerTest.php @@ -0,0 +1,128 @@ +setEmail('user1@foo.com'); + $user2 = (new User())->setEmail('user2@foo.com'); + $user3 = (new User())->setEmail('user3@foo.com'); + + $notification = new Notification(); + $notification + ->setTitle('test notification') + ->setSender($user1) + ->addAddressee($user2) + ->addAddressee($user3) + ; + + $comment = (new NotificationComment()) + ->setContent("foo bar baz") + ->setCreatedBy($user2) + ; + $notification->addComment($comment); + + $mailer = $this->prophesize(MailerInterface::class); + + // a mail only to user1 and user3 should have been sent + $mailer->send(Argument::that(function (Email $email) { + foreach ($email->getTo() as $address) { + if ($address->getAddress() === 'user1@foo.com' || $address->getAddress() === 'user3@foo.com') { + return true; + } + } + + return false; + }))->shouldBeCalledTimes(2); + + $objectManager = $this->prophesize(EntityManagerInterface::class); + + $mailer = $this->buildNotificationMailer($mailer->reveal()); + $mailer->postPersistComment($comment, new PostPersistEventArgs($comment, $objectManager->reveal())); + } + + public function testPostPersistCommentDestWithNullEmail(): void + { + $user1 = (new User())->setEmail('user1@foo.com'); + $user2 = (new User())->setEmail('user2@foo.com'); + $user3 = (new User())->setEmail(null); + + $notification = new Notification(); + $notification + ->setTitle('test notification') + ->setSender($user1) + ->addAddressee($user2) + ->addAddressee($user3) + ; + + $comment = (new NotificationComment()) + ->setContent("foo bar baz") + ->setCreatedBy($user2) + ; + $notification->addComment($comment); + + $mailer = $this->prophesize(MailerInterface::class); + + // a mail only to user1 and user3 should have been sent + $mailer->send(Argument::that(function (Email $email) { + foreach ($email->getTo() as $address) { + if ($address->getAddress() === 'user1@foo.com') { + return true; + } + } + + return false; + }))->shouldBeCalledTimes(1); + + $objectManager = $this->prophesize(EntityManagerInterface::class); + + $mailer = $this->buildNotificationMailer($mailer->reveal()); + $mailer->postPersistComment($comment, new PostPersistEventArgs($comment, $objectManager->reveal())); + } + + private function buildNotificationMailer( + MailerInterface $mailer = null, + ): NotificationMailer { + return new NotificationMailer( + $mailer, + new NullLogger(), + new Translator('fr') + ); + } + + + +} diff --git a/src/Bundle/ChillMainBundle/Tests/Services/AddressGeographicalUnit/CollateAddressWithReferenceOrPostalCodeCronJobTest.php b/src/Bundle/ChillMainBundle/Tests/Services/AddressGeographicalUnit/CollateAddressWithReferenceOrPostalCodeCronJobTest.php index 69eeca2da..9e83f2e7e 100644 --- a/src/Bundle/ChillMainBundle/Tests/Services/AddressGeographicalUnit/CollateAddressWithReferenceOrPostalCodeCronJobTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Services/AddressGeographicalUnit/CollateAddressWithReferenceOrPostalCodeCronJobTest.php @@ -64,5 +64,4 @@ class CollateAddressWithReferenceOrPostalCodeCronJobTest extends TestCase yield [new \DateTimeImmutable('2023-07-10T12:00:00'), new \DateTimeImmutable('2023-07-01T12:00:00'), true]; yield [new \DateTimeImmutable('2023-07-10T12:00:00'), null, true]; } - } diff --git a/src/Bundle/ChillPersonBundle/Household/MembersEditor.php b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php index 50807bcff..7f4053917 100644 --- a/src/Bundle/ChillPersonBundle/Household/MembersEditor.php +++ b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php @@ -46,6 +46,14 @@ class MembersEditor { } + /** + * Add a person to the household + * + * The person is added to the household associated with this editor's instance. + * + * If the person is also a member of another household, or the same household at the same position, the person + * is not associated any more with the previous household. + */ public function addMovement(DateTimeImmutable $date, Person $person, ?Position $position, ?bool $holder = false, ?string $comment = null): self { if (null === $this->household) { @@ -60,68 +68,66 @@ class MembersEditor ->setComment($comment); $this->household->addMember($membership); - if (null !== $position) { - if ($position->getShareHousehold()) { - // launch event only if moving to a "share household" position, - // and if the destination household is different than the previous one - $event = new PersonAddressMoveEvent($person); - $event->setNextMembership($membership); + if ($membership->getShareHousehold()) { + // launch event only if moving to a "share household" position, + // and if the destination household is different than the previous one + $event = new PersonAddressMoveEvent($person); + $event->setNextMembership($membership); - $counter = 0; + $counter = 0; - foreach ($person->getHouseholdParticipationsShareHousehold() as $participation) { - if ($participation === $membership) { - continue; - } - - if ($participation->getStartDate() > $membership->getStartDate()) { - continue; - } - - ++$counter; - - if ($participation->getEndDate() === null || $participation->getEndDate() > $date) { - $participation->setEndDate($date); - $this->membershipsAffected[] = $participation; - $this->oldMembershipsHashes[] = spl_object_hash($participation); - - if ($participation->getHousehold() !== $this->household) { - $event->setPreviousMembership($participation); - $this->events[] = $event; - } - } + foreach ($person->getHouseholdParticipationsShareHousehold() as $participation) { + if ($participation === $membership) { + continue; } - // send also the event if there was no participation before - if (0 === $counter) { - $this->events[] = $event; + if ($participation->getStartDate() > $membership->getStartDate()) { + continue; } - foreach ($person->getHouseholdParticipationsNotShareHousehold() as $participation) { - if ($participation->getHousehold() === $this->household - && $participation->getEndDate() === null || $participation->getEndDate() > $membership->getStartDate() - && $participation->getStartDate() <= $membership->getStartDate() - ) { - $participation->setEndDate($membership->getStartDate()); + ++$counter; + + if ($participation->getEndDate() === null || $participation->getEndDate() > $date) { + $participation->setEndDate($date); + $this->membershipsAffected[] = $participation; + $this->oldMembershipsHashes[] = spl_object_hash($participation); + + if ($participation->getHousehold() !== $this->household) { + $event->setPreviousMembership($participation); + $this->events[] = $event; } } - } else { - // if a members is moved to the same household than the one he belongs to, - // we should make it leave the household - if ($person->getCurrentHousehold($date) === $this->household) { - $this->leaveMovement($date, $person); + } + + // send also the event if there was no participation before + if (0 === $counter) { + $this->events[] = $event; + } + + foreach ($person->getHouseholdParticipationsNotShareHousehold() as $participation) { + if ($participation->getHousehold() === $this->household + && $participation->getEndDate() === null || $participation->getEndDate() > $membership->getStartDate() + && $participation->getStartDate() <= $membership->getStartDate() + ) { + $participation->setEndDate($membership->getStartDate()); + } + } + } else { + // if there are multiple belongings not sharing household, close the others + foreach ($person->getHouseholdParticipations() as $participation) { + if ($participation === $membership) { + continue; } - // if there are multiple belongings not sharing household, close the others - foreach ($person->getHouseholdParticipationsNotShareHousehold() as $participation) { - if ($participation === $membership) { - continue; - } - - if ($participation->getHousehold() === $this->household - && ($participation->getEndDate() === null || $participation->getEndDate() > $membership->getStartDate()) - && $participation->getStartDate() <= $membership->getStartDate() - ) { + if ($participation->getHousehold() === $this->household + && ($participation->getEndDate() === null || $participation->getEndDate() > $membership->getStartDate()) + && $participation->getStartDate() <= $membership->getStartDate() + ) { + if ($participation->getShareHousehold()) { + // if a members is moved to the same household than the one he belongs to, + // we should make it leave the household + $this->leaveMovement($date, $person); + } else { $participation->setEndDate($membership->getStartDate()); } } @@ -149,6 +155,15 @@ class MembersEditor return null !== $this->household; } + /** + * Makes a person leave the household. + * + * Makes a person leave the household **associated with this editor**. + * + * @param DateTimeImmutable $date + * @param Person $person + * @return $this + */ public function leaveMovement( DateTimeImmutable $date, Person $person @@ -158,8 +173,9 @@ class MembersEditor $criteria->where( $expr->andX( - $expr->lt('startDate', $date), - $expr->isNull('endDate') + $expr->lte('startDate', $date), + $expr->isNull('endDate'), + $expr->eq('shareHousehold', true) ) ); diff --git a/src/Bundle/ChillPersonBundle/Resources/public/chill/chillperson.scss b/src/Bundle/ChillPersonBundle/Resources/public/chill/chillperson.scss index 59a9b05db..24f947100 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/chill/chillperson.scss +++ b/src/Bundle/ChillPersonBundle/Resources/public/chill/chillperson.scss @@ -207,10 +207,11 @@ div.banner { span.badge-member { flex-shrink: 0; flex-grow: 0; flex-basis: auto; color: $white; + background-color: transparentize($white, 0.85); border: 1px solid transparentize($white, 0.75); border-bottom: 3px solid transparentize( shade-color( $chill-green, 20%), 0.3); border-radius: 8px; - padding: 0.2em 0.7em; + padding: 0.0em 0.5em; margin-bottom: 0.2em; margin-right: 0.3em; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/PersonComment.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/PersonComment.vue index cb9a02d51..8f61a74d1 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/PersonComment.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/PersonComment.vue @@ -1,7 +1,7 @@