Compare commits

...

8 Commits

27 changed files with 181 additions and 30 deletions

View File

@@ -0,0 +1,7 @@
kind: DX
body: |
Send notifications log to dedicated channel, if it exists
time: 2025-10-27T15:00:53.309372316+01:00
custom:
Issue: ""
SchemaChange: No schema change

View File

@@ -0,0 +1,6 @@
kind: DX
body: Add missing fixtures for proper loading of AccompanyingPeriods
time: 2025-10-30T12:37:32.824593456+01:00
custom:
Issue: "280"
SchemaChange: No schema change

3
.changes/v4.6.1.md Normal file
View File

@@ -0,0 +1,3 @@
## v4.6.1 - 2025-10-27
### Fixed
* Fix export case where no 'reason' is picked within the PersonHavingActivityBetweenDateFilter.php

View File

@@ -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). and is generated by [Changie](https://github.com/miniscruff/changie).
## v4.6.1 - 2025-10-27
### Fixed
* Fix export case where no 'reason' is picked within the PersonHavingActivityBetweenDateFilter.php
## v4.6.0 - 2025-10-15 ## v4.6.0 - 2025-10-15
### Feature ### Feature
* ([#423](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/423)) Create environment banner that can be activated and configured depending on the image deployed * ([#423](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/423)) Create environment banner that can be activated and configured depending on the image deployed

View File

@@ -90,7 +90,9 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt
public function getFormDefaultData(): array public function getFormDefaultData(): array
{ {
return []; return [
'reasons' => [],
];
} }
public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array

View File

@@ -42,6 +42,8 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void
{ {
error_log('alterQuery called with data: '.json_encode(array_keys($data)));
// create a subquery for activity // create a subquery for activity
$sqb = $qb->getEntityManager()->createQueryBuilder(); $sqb = $qb->getEntityManager()->createQueryBuilder();
$sqb->select('1') $sqb->select('1')
@@ -59,7 +61,6 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
if (\in_array('activity', $qb->getAllAliases(), true)) { if (\in_array('activity', $qb->getAllAliases(), true)) {
$sqb->andWhere('activity_person_having_activity.id = activity.id'); $sqb->andWhere('activity_person_having_activity.id = activity.id');
} }
if (isset($data['reasons']) && [] !== $data['reasons']) { if (isset($data['reasons']) && [] !== $data['reasons']) {
// add clause activity reason // add clause activity reason
$sqb->join('activity_person_having_activity.reasons', 'reasons_person_having_activity'); $sqb->join('activity_person_having_activity.reasons', 'reasons_person_having_activity');
@@ -124,12 +125,38 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
public function normalizeFormData(array $formData): array public function normalizeFormData(array $formData): array
{ {
return ['date_from_rolling' => $formData['date_from_rolling']->normalize(), 'date_to_rolling' => $formData['date_to_rolling']->normalize()]; $normalized = [
'date_from_rolling' => $formData['date_from_rolling']->normalize(),
'date_to_rolling' => $formData['date_to_rolling']->normalize(),
'reasons' => [],
];
if (isset($formData['reasons']) && [] !== $formData['reasons']) {
$normalized['reasons'] = array_map(
fn (ActivityReason $reason) => $reason->getId(),
$formData['reasons']
);
}
return $normalized;
} }
public function denormalizeFormData(array $formData, int $fromVersion): array public function denormalizeFormData(array $formData, int $fromVersion): array
{ {
return ['date_from_rolling' => RollingDate::fromNormalized($formData['date_from_rolling']), 'date_to_rolling' => RollingDate::fromNormalized($formData['date_to_rolling'])]; $denormalized = [
'date_from_rolling' => RollingDate::fromNormalized($formData['date_from_rolling']),
'date_to_rolling' => RollingDate::fromNormalized($formData['date_to_rolling']),
'reasons' => [],
];
if (isset($formData['reasons']) && [] !== $formData['reasons']) {
$denormalized['reasons'] = array_map(
fn ($id) => $this->activityReasonRepository->find($id),
$formData['reasons']
);
}
return $denormalized;
} }
public function getFormDefaultData(): array public function getFormDefaultData(): array
@@ -143,10 +170,12 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
public function describeAction($data, ExportGenerationContext $context): array public function describeAction($data, ExportGenerationContext $context): array
{ {
$reasons = $data['reasons'] ?? [];
return [ return [
[] === $data['reasons'] ? [] === $reasons ?
'export.filter.person_between_dates.describe_action_with_no_subject' 'export.filter.activity.describe_action_with_no_subject'
: 'export.filter.person_between_dates.describe_action_with_subject', : 'export.filter.activity.describe_action_with_subject',
[ [
'date_from' => $this->rollingDateConverter->convert($data['date_from_rolling']), 'date_from' => $this->rollingDateConverter->convert($data['date_from_rolling']),
'date_to' => $this->rollingDateConverter->convert($data['date_to_rolling']), 'date_to' => $this->rollingDateConverter->convert($data['date_to_rolling']),
@@ -154,7 +183,7 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
', ', ', ',
array_map( array_map(
fn (ActivityReason $r): string => '"'.$this->translatableStringHelper->localize($r->getName()).'"', fn (ActivityReason $r): string => '"'.$this->translatableStringHelper->localize($r->getName()).'"',
$data['reasons'] $reasons
) )
), ),
], ],
@@ -168,6 +197,7 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
public function validateForm($data, ExecutionContextInterface $context): void public function validateForm($data, ExecutionContextInterface $context): void
{ {
error_log('validateForm called with data: '.json_encode(array_keys($data)));
if ($this->rollingDateConverter->convert($data['date_from_rolling']) if ($this->rollingDateConverter->convert($data['date_from_rolling'])
>= $this->rollingDateConverter->convert($data['date_to_rolling'])) { >= $this->rollingDateConverter->convert($data['date_to_rolling'])) {
$context->buildViolation('export.filter.activity.person_between_dates.date mismatch') $context->buildViolation('export.filter.activity.person_between_dates.date mismatch')

View File

@@ -334,7 +334,7 @@ class ChillImportUsersCommand extends Command
protected function loadUsers() protected function loadUsers()
{ {
$reader = Reader::createFromPath($this->tempInput->getArgument('csvfile')); $reader = Reader::from($this->tempInput->getArgument('csvfile'));
$reader->setHeaderOffset(0); $reader->setHeaderOffset(0);
foreach ($reader->getRecords() as $line => $r) { foreach ($reader->getRecords() as $line => $r) {
@@ -362,7 +362,7 @@ class ChillImportUsersCommand extends Command
protected function prepareGroupingCenters() protected function prepareGroupingCenters()
{ {
$reader = Reader::createFromPath($this->tempInput->getOption('grouping-centers')); $reader = Reader::from($this->tempInput->getOption('grouping-centers'));
$reader->setHeaderOffset(0); $reader->setHeaderOffset(0);
foreach ($reader->getRecords() as $r) { foreach ($reader->getRecords() as $r) {
@@ -378,7 +378,7 @@ class ChillImportUsersCommand extends Command
protected function prepareWriter() protected function prepareWriter()
{ {
$this->output = $output = Writer::createFromPath($this->tempInput $this->output = $output = Writer::from($this->tempInput
->getOption('csv-dump'), 'a+'); ->getOption('csv-dump'), 'a+');
$output->insertOne([ $output->insertOne([

View File

@@ -119,7 +119,7 @@ class ChillUserSendRenewPasswordCodeCommand extends Command
protected function getReader() protected function getReader()
{ {
try { try {
$reader = Reader::createFromPath($this->input->getArgument('csvfile')); $reader = Reader::from($this->input->getArgument('csvfile'));
} catch (\Exception $e) { } catch (\Exception $e) {
$this->logger->error('The csv file could not be read', [ $this->logger->error('The csv file could not be read', [
'path' => $this->input->getArgument('csvfile'), 'path' => $this->input->getArgument('csvfile'),

View File

@@ -43,7 +43,7 @@ final readonly class UserExportController
$users = $this->userRepository->findAllAsArray($request->getLocale()); $users = $this->userRepository->findAllAsArray($request->getLocale());
$csv = Writer::createFromPath('php://temp', 'r+'); $csv = Writer::from('php://temp', 'r+');
$csv->insertOne( $csv->insertOne(
array_map( array_map(
fn (string $e) => $this->translator->trans('admin.users.export.'.$e), fn (string $e) => $this->translator->trans('admin.users.export.'.$e),
@@ -104,7 +104,7 @@ final readonly class UserExportController
$userPermissions = $this->userRepository->findAllUserACLAsArray(); $userPermissions = $this->userRepository->findAllUserACLAsArray();
$csv = Writer::createFromPath('php://temp', 'r+'); $csv = Writer::from('php://temp', 'r+');
$csv->insertOne( $csv->insertOne(
array_map( array_map(
fn (string $e) => $this->translator->trans('admin.users.export.'.$e), fn (string $e) => $this->translator->trans('admin.users.export.'.$e),

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\DataFixtures\ORM;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\LocationType;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Persistence\ObjectManager;
class LoadAdministrativeLocation extends AbstractFixture implements OrderedFixtureInterface
{
final public const ADMINISTRATIVE_LOCATION = 'administrative_location';
public function getOrder(): int
{
return 9000;
}
public function load(ObjectManager $manager): void
{
$o = new Location();
/** @var LocationType $locationType */
$locationType = $this->getReference(LoadLocationType::LOCATION_TYPE.'_0');
$o->setLocationType($locationType);
$o->setName('Commune de Bruxelles');
$o->setAvailableForUsers(true);
$manager->persist($o);
$this->addReference(self::ADMINISTRATIVE_LOCATION, $o);
echo "Adding one Administrative Location\n";
$manager->flush();
}
}

View File

@@ -25,6 +25,8 @@ class LoadLocationType extends AbstractFixture implements ContainerAwareInterfac
{ {
private ?ContainerInterface $container = null; private ?ContainerInterface $container = null;
final public const LOCATION_TYPE = 'location_type';
public function getOrder(): int public function getOrder(): int
{ {
return 52; return 52;
@@ -53,13 +55,15 @@ class LoadLocationType extends AbstractFixture implements ContainerAwareInterfac
], ],
]; ];
foreach ($arr as $a) { foreach ($arr as $index => $a) {
$locationType = (new LocationType()) $locationType = (new LocationType())
->setTitle($a['name']) ->setTitle($a['name'])
->setAvailableForUsers(true) ->setAvailableForUsers(true)
->setActive(true) ->setActive(true)
->setAddressRequired($a['address_required']); ->setAddressRequired($a['address_required']);
$manager->persist($locationType); $manager->persist($locationType);
$this->addReference(self::LOCATION_TYPE.'_'.$index, $locationType);
} }
$manager->flush(); $manager->flush();

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\DataFixtures\ORM;
use Chill\MainBundle\Entity\UserJob;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Persistence\ObjectManager;
class LoadUserJob extends AbstractFixture implements OrderedFixtureInterface
{
final public const USER_JOB = 'user_job';
private array $socialWorker = ['en' => 'social worker', 'fr' => 'travailleur social'];
public function getOrder(): int
{
return 9000;
}
public function load(ObjectManager $manager): void
{
$o = new UserJob();
$o->setLabel($this->socialWorker);
$manager->persist($o);
$this->addReference(self::USER_JOB, $o);
echo "Adding one AccompanyingPeriod User Job\n";
$manager->flush();
}
}

View File

@@ -64,7 +64,7 @@ class AddressReferenceBEFromBestAddress
$uncompressedStream = gzopen($tmpname, 'r'); $uncompressedStream = gzopen($tmpname, 'r');
$csv = Reader::createFromStream($uncompressedStream); $csv = Reader::from($uncompressedStream);
$csv->setDelimiter(','); $csv->setDelimiter(',');
$csv->setHeaderOffset(0); $csv->setHeaderOffset(0);

View File

@@ -287,7 +287,7 @@ final class AddressReferenceBaseImporter
$filename = sprintf('%s-%s.csv', (new \DateTimeImmutable())->format('Ymd-His'), uniqid()); $filename = sprintf('%s-%s.csv', (new \DateTimeImmutable())->format('Ymd-His'), uniqid());
$path = Path::normalize(sprintf('%s%s%s', sys_get_temp_dir(), DIRECTORY_SEPARATOR, $filename)); $path = Path::normalize(sprintf('%s%s%s', sys_get_temp_dir(), DIRECTORY_SEPARATOR, $filename));
$writer = Writer::createFromPath($path, 'w+'); $writer = Writer::from($path, 'w+');
// insert headers // insert headers
$writer->insertOne([ $writer->insertOne([
'postalcode', 'postalcode',

View File

@@ -53,7 +53,7 @@ class AddressReferenceFromBAN
// re-open it to read it // re-open it to read it
$csvDecompressed = gzopen($path, 'r'); $csvDecompressed = gzopen($path, 'r');
$csv = Reader::createFromStream($csvDecompressed); $csv = Reader::from($csvDecompressed);
$csv->setDelimiter(';')->setHeaderOffset(0); $csv->setDelimiter(';')->setHeaderOffset(0);
$stmt = new Statement(); $stmt = new Statement();
$stmt = $stmt->process($csv, [ $stmt = $stmt->process($csv, [

View File

@@ -41,7 +41,7 @@ class AddressReferenceFromBano
fseek($file, 0); fseek($file, 0);
$csv = Reader::createFromStream($file); $csv = Reader::from($file);
$csv->setDelimiter(','); $csv->setDelimiter(',');
$stmt = new Statement(); $stmt = new Statement();
$stmt = $stmt->process($csv, [ $stmt = $stmt->process($csv, [

View File

@@ -39,7 +39,7 @@ class AddressReferenceLU
fseek($file, 0); fseek($file, 0);
$csv = Reader::createFromStream($file); $csv = Reader::from($file);
$csv->setDelimiter(';'); $csv->setDelimiter(';');
$csv->setHeaderOffset(0); $csv->setHeaderOffset(0);

View File

@@ -43,7 +43,7 @@ class PostalCodeBEFromBestAddress
$uncompressedStream = gzopen($tmpname, 'r'); $uncompressedStream = gzopen($tmpname, 'r');
$csv = Reader::createFromStream($uncompressedStream); $csv = Reader::from($uncompressedStream);
$csv->setDelimiter(','); $csv->setDelimiter(',');
$csv->setHeaderOffset(0); $csv->setHeaderOffset(0);

View File

@@ -47,7 +47,7 @@ class PostalCodeFRFromOpenData
fseek($tmpfile, 0); fseek($tmpfile, 0);
$csv = Reader::createFromStream($tmpfile); $csv = Reader::from($tmpfile);
$csv->setDelimiter(','); $csv->setDelimiter(',');
$csv->setHeaderOffset(0); $csv->setHeaderOffset(0);

View File

@@ -18,7 +18,7 @@ use Symfony\Component\Notifier\Event\SentMessageEvent;
final readonly class SentMessageEventSubscriber implements EventSubscriberInterface final readonly class SentMessageEventSubscriber implements EventSubscriberInterface
{ {
public function __construct( public function __construct(
private LoggerInterface $logger, private LoggerInterface $notifierLogger, // will be send to "notifierLogger" if it exists
) {} ) {}
public static function getSubscribedEvents() public static function getSubscribedEvents()
@@ -33,9 +33,9 @@ final readonly class SentMessageEventSubscriber implements EventSubscriberInterf
$message = $event->getMessage(); $message = $event->getMessage();
if (null === $message->getMessageId()) { if (null === $message->getMessageId()) {
$this->logger->info('[sms] a sms message did not had any id after sending.', ['validReceiversI' => $message->getOriginalMessage()->getRecipientId()]); $this->notifierLogger->info('[sms] a sms message did not had any id after sending.', ['validReceiversI' => $message->getOriginalMessage()->getRecipientId()]);
} else { } else {
$this->logger->warning('[sms] a sms was sent', ['validReceiversI' => $message->getOriginalMessage()->getRecipientId(), 'idsI' => $message->getMessageId()]); $this->notifierLogger->warning('[sms] a sms was sent', ['validReceiversI' => $message->getOriginalMessage()->getRecipientId(), 'idsI' => $message->getMessageId()]);
} }
} }
} }

View File

@@ -49,7 +49,7 @@ final class ImportSocialWorkMetadata extends Command
$filepath = $input->getOption('filepath'); $filepath = $input->getOption('filepath');
try { try {
$csv = Reader::createFromPath($filepath); $csv = Reader::from($filepath);
} catch (\Throwable $e) { } catch (\Throwable $e) {
throw new \Exception('Error while loading CSV.', 0, $e); throw new \Exception('Error while loading CSV.', 0, $e);
} }

View File

@@ -11,15 +11,19 @@ declare(strict_types=1);
namespace Chill\PersonBundle\DataFixtures\ORM; namespace Chill\PersonBundle\DataFixtures\ORM;
use Chill\MainBundle\DataFixtures\ORM\LoadAdministrativeLocation;
use Chill\MainBundle\DataFixtures\ORM\LoadPostalCodes; use Chill\MainBundle\DataFixtures\ORM\LoadPostalCodes;
use Chill\MainBundle\DataFixtures\ORM\LoadUserJob;
use Chill\MainBundle\Entity\Address; use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Country; use Chill\MainBundle\Entity\Country;
use Chill\MainBundle\Entity\Gender; use Chill\MainBundle\Entity\Gender;
use Chill\MainBundle\Entity\GenderEnum; use Chill\MainBundle\Entity\GenderEnum;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\PostalCode; use Chill\MainBundle\Entity\PostalCode;
use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Repository\CenterRepository; use Chill\MainBundle\Repository\CenterRepository;
use Chill\MainBundle\Repository\CountryRepository; use Chill\MainBundle\Repository\CountryRepository;
use Chill\MainBundle\Repository\GenderRepository; use Chill\MainBundle\Repository\GenderRepository;
@@ -362,6 +366,10 @@ class LoadPeople extends AbstractFixture implements ContainerAwareInterface, Ord
$origin = $this->getReference(LoadAccompanyingPeriodOrigin::ACCOMPANYING_PERIOD_ORIGIN, AccompanyingPeriod\Origin::class); $origin = $this->getReference(LoadAccompanyingPeriodOrigin::ACCOMPANYING_PERIOD_ORIGIN, AccompanyingPeriod\Origin::class);
$accompanyingPeriod->setOrigin($origin); $accompanyingPeriod->setOrigin($origin);
$accompanyingPeriod->setIntensity('regular'); $accompanyingPeriod->setIntensity('regular');
$userJob = $this->getReference(LoadUserJob::USER_JOB, UserJob::class);
$accompanyingPeriod->setJob($userJob);
$administrativeLocation = $this->getReference(LoadAdministrativeLocation::ADMINISTRATIVE_LOCATION, Location::class);
$accompanyingPeriod->setAdministrativeLocation($administrativeLocation);
$accompanyingPeriod->setAddressLocation($this->createAddress()); $accompanyingPeriod->setAddressLocation($this->createAddress());
$manager->persist($accompanyingPeriod->getAddressLocation()); $manager->persist($accompanyingPeriod->getAddressLocation());
$workflow = $this->workflowRegistry->get($accompanyingPeriod); $workflow = $this->workflowRegistry->get($accompanyingPeriod);

View File

@@ -29,7 +29,7 @@ class LoadSocialWorkMetadata extends Fixture implements OrderedFixtureInterface
public function load(ObjectManager $manager): void public function load(ObjectManager $manager): void
{ {
try { try {
$csv = Reader::createFromPath(__DIR__.'/data/social_work_metadata.csv'); $csv = Reader::from(__DIR__.'/data/social_work_metadata.csv');
} catch (\Throwable $e) { } catch (\Throwable $e) {
throw new \Exception('Error while loading CSV.', 0, $e); throw new \Exception('Error while loading CSV.', 0, $e);
} }

View File

@@ -60,6 +60,7 @@ import {
EVALUATION_DOCUMENT_MOVE_SUCCESS, EVALUATION_DOCUMENT_MOVE_SUCCESS,
} from "translator"; } from "translator";
import { useToast } from "vue-toast-notification"; import { useToast } from "vue-toast-notification";
import { buildLinkCreate as buildLinkCreateNotification } from "ChillMainAssets/lib/entity-notification/api";
const props = defineProps(["evaluation", "docAnchorId"]); const props = defineProps(["evaluation", "docAnchorId"]);
const store = useStore(); const store = useStore();

View File

@@ -38,7 +38,7 @@ final readonly class SocialActionCSVExportService
array_keys($this->formatRow(new SocialAction())) array_keys($this->formatRow(new SocialAction()))
); );
$csv = Writer::createFromPath('php://temp', 'w+'); $csv = Writer::from('php://temp', 'w+');
$csv->insertOne($headers); $csv->insertOne($headers);
foreach ($actions as $action) { foreach ($actions as $action) {

View File

@@ -36,7 +36,7 @@ readonly class SocialIssueCSVExportService
public function generateCsv(array $issues): Writer public function generateCsv(array $issues): Writer
{ {
// CSV headers // CSV headers
$csv = Writer::createFromPath('php://temp', 'r+'); $csv = Writer::from('php://temp', 'r+');
$csv->insertOne( $csv->insertOne(
array_map( array_map(
fn (string $e) => $this->translator->trans($e), fn (string $e) => $this->translator->trans($e),

View File

@@ -52,7 +52,7 @@ class ThirdpartyCSVExportController extends AbstractController
fwrite($output, "\xEF\xBB\xBF"); fwrite($output, "\xEF\xBB\xBF");
// Create CSV writer // Create CSV writer
$csv = Writer::createFromStream($output); $csv = Writer::from($output);
// Write header row // Write header row
$header = array_map( $header = array_map(