Compare commits

..

4 Commits

18 changed files with 152 additions and 293 deletions

View File

@@ -0,0 +1,6 @@
kind: Feature
body: Create environment banner that can be activated and configured depending on the image deployed
time: 2025-10-07T10:19:49.784462956+02:00
custom:
Issue: "423"
SchemaChange: No schema change

View File

@@ -1,6 +1,13 @@
chill_main:
available_languages: [ '%env(resolve:LOCALE)%', 'en' ]
available_countries: ['BE', 'FR']
top_banner:
visible: true
text:
fr: 'Vous travaillez actuellement avec la version de PRÉ-PRODUCTION.'
nl: 'Je werkt momenteel in de PRE-PRODUCTIE versie'
color: '#353535'
background_color: '#d8bb48'
notifications:
from_email: '%env(resolve:NOTIFICATION_FROM_EMAIL)%'
from_name: '%env(resolve:NOTIFICATION_FROM_NAME)%'
@@ -120,6 +127,3 @@ chill_activity:
-
label: '5 hours'
seconds: 18000
chill_aside_activity:
show_concerned_persons_count: true

View File

@@ -25,7 +25,6 @@ final class ChillAsideActivityExtension extends Extension implements PrependExte
$config = $this->processConfiguration($configuration, $configs);
$container->setParameter('chill_aside_activity.form.time_duration', $config['form']['time_duration']);
$container->setParameter('chill_aside_activity.show_concerned_persons_count', $config['show_concerned_persons_count']);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config'));
$loader->load('services.yaml');

View File

@@ -141,11 +141,6 @@ class Configuration implements ConfigurationInterface
->end()
->end()
->end()
->end()
->booleanNode('show_concerned_persons_count')
->defaultTrue()
->info('Show the concerned persons count field in aside activity forms and views')
->end()
->end();
return $treeBuilder;

View File

@@ -62,10 +62,6 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface
#[ORM\ManyToOne(targetEntity: User::class)]
private User $updatedBy;
#[Assert\GreaterThanOrEqual(0)]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: true)]
private ?int $concernedPersonsCount = null;
public function getAgent(): ?User
{
return $this->agent;
@@ -190,16 +186,4 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface
return $this;
}
public function getConcernedPersonsCount(): ?int
{
return $this->concernedPersonsCount;
}
public function setConcernedPersonsCount(?int $concernedPersonsCount): self
{
$this->concernedPersonsCount = $concernedPersonsCount;
return $this;
}
}

View File

@@ -1,86 +0,0 @@
<?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\AsideActivityBundle\Export\Aggregator;
use Chill\AsideActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
class ByConcernedPersonsCountAggregator implements AggregatorInterface
{
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void
{
$qb->addSelect('aside.concernedPersonsCount AS by_concerned_persons_count_aggregator')
->addGroupBy('by_concerned_persons_count_aggregator');
}
public function applyOn(): string
{
return Declarations::ASIDE_ACTIVITY_TYPE;
}
public function buildForm(FormBuilderInterface $builder): void
{
// No form needed
}
public function getNormalizationVersion(): int
{
return 1;
}
public function normalizeFormData(array $formData): array
{
return [];
}
public function denormalizeFormData(array $formData, int $fromVersion): array
{
return [];
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data): callable
{
return function ($value): string {
if ('_header' === $value) {
return 'export.aggregator.Concerned persons count';
}
if (null === $value) {
return 'export.aggregator.No concerned persons count specified';
}
return (string) $value;
};
}
public function getQueryKeys($data): array
{
return ['by_concerned_persons_count_aggregator'];
}
public function getTitle(): string
{
return 'export.aggregator.Group by concerned persons count';
}
}

View File

@@ -1,116 +0,0 @@
<?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\AsideActivityBundle\Export\Export;
use Chill\AsideActivityBundle\Export\Declarations;
use Chill\AsideActivityBundle\Repository\AsideActivityRepository;
use Chill\AsideActivityBundle\Security\AsideActivityVoter;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Doctrine\ORM\Query;
use Symfony\Component\Form\FormBuilderInterface;
class SumConcernedPersonsCountAsideActivity implements ExportInterface, GroupedExportInterface
{
public function __construct(private readonly AsideActivityRepository $repository) {}
public function buildForm(FormBuilderInterface $builder) {}
public function getNormalizationVersion(): int
{
return 1;
}
public function normalizeFormData(array $formData): array
{
return [];
}
public function denormalizeFormData(array $formData, int $fromVersion): array
{
return [];
}
public function getFormDefaultData(): array
{
return [];
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'export.Sum concerned persons count for aside activities';
}
public function getGroup(): string
{
return 'export.Exports of aside activities';
}
public function getLabels($key, array $values, $data)
{
if ('export_sum_concerned_persons_count' !== $key) {
throw new \LogicException("the key {$key} is not used by this export");
}
$labels = array_combine($values, $values);
$labels['_header'] = $this->getTitle();
return static fn ($value) => $labels[$value];
}
public function getQueryKeys($data): array
{
return ['export_sum_concerned_persons_count'];
}
public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array
{
return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getTitle(): string
{
return 'export.Sum concerned persons count for aside activities';
}
public function getType(): string
{
return Declarations::ASIDE_ACTIVITY_TYPE;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder
{
$qb = $this->repository->createQueryBuilder('aside');
$qb->select('SUM(COALESCE(aside.concernedPersonsCount, 0)) as export_sum_concerned_persons_count');
return $qb;
}
public function requiredRole(): string
{
return AsideActivityVoter::STATS;
}
public function supportsModifiers(): array
{
return [
Declarations::ASIDE_ACTIVITY_TYPE,
];
}
}

View File

@@ -21,7 +21,6 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
@@ -30,13 +29,11 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
final class AsideActivityFormType extends AbstractType
{
private readonly array $timeChoices;
private readonly bool $showConcernedPersonsCount;
public function __construct(
ParameterBagInterface $parameterBag,
) {
$this->timeChoices = $parameterBag->get('chill_aside_activity.form.time_duration');
$this->showConcernedPersonsCount = $parameterBag->get('chill_aside_activity.show_concerned_persons_count');
}
public function buildForm(FormBuilderInterface $builder, array $options)
@@ -79,16 +76,6 @@ final class AsideActivityFormType extends AbstractType
->add('location', PickUserLocationType::class)
;
if ($this->showConcernedPersonsCount) {
$builder->add('concernedPersonsCount', IntegerType::class, [
'label' => 'Concerned persons count',
'required' => false,
'attr' => [
'min' => 0,
],
]);
}
foreach (['duration'] as $fieldName) {
$builder->get($fieldName)
->addModelTransformer($durationTimeTransformer);

View File

@@ -42,11 +42,6 @@
{%- if entity.location.name is defined -%}
<div><i class="fa fa-fw fa-map-marker"></i>{{ entity.location.name }}</div>
{%- endif -%}
{%- if entity.concernedPersonsCount > 0 -%}
<div><i class="fa fa-fw fa-user"></i>{{ entity.concernedPersonsCount }}</div>
{%- endif -%}
</div>
<div class="item-col" style="justify-content: flex-end;">
<div class="box">

View File

@@ -38,9 +38,6 @@
<dt class="inline">{{ 'Duration'|trans }}</dt>
<dd>{{ entity.duration|date('H:i') }}</dd>
<dt class="inline">{{ 'Concerned persons count'|trans }}</dt>
<dd>{{ entity.concernedPersonsCount }}</dd>
<dt class="inline">{{ 'Remark'|trans }}</dt>
{%- if entity.note is empty -%}
<dd>

View File

@@ -20,10 +20,6 @@ services:
tags:
- { name: chill.export, alias: 'avg_aside_activity_duration' }
Chill\AsideActivityBundle\Export\Export\SumConcernedPersonsCountAsideActivity:
tags:
- { name: chill.export, alias: 'sum_aside_activity_concerned_persons_count' }
## Filters
chill.aside_activity.export.date_filter:
class: Chill\AsideActivityBundle\Export\Filter\ByDateFilter
@@ -74,7 +70,3 @@ services:
Chill\AsideActivityBundle\Export\Aggregator\ByLocationAggregator:
tags:
- { name: chill.export_aggregator, alias: 'aside_activity_location_aggregator' }
Chill\AsideActivityBundle\Export\Aggregator\ByConcernedPersonsCountAggregator:
tags:
- { name: chill.export_aggregator, alias: 'aside_activity_concerned_persons_count_aggregator' }

View File

@@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\AsideActivity;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20251006113048 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add concernedPersonsCount property to AsideActivity entity';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_asideactivity.asideactivity ADD concernedPersonsCount INT DEFAULT NULL');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_asideactivity.AsideActivity DROP concernedPersonsCount');
}
}

View File

@@ -27,7 +27,6 @@ Emergency: Urgent
by: "Par "
location: Lieu
Asideactivity location: Localisation de l'activité
Concerned persons count: Nombre d'usager concernés
# Crud
crud:
@@ -191,7 +190,6 @@ export:
Count aside activities by various parameters.: Compte le nombre d'activités annexes selon divers critères
Average aside activities duration: Durée moyenne des activités annexes
Sum aside activities duration: Durée des activités annexes
Sum concerned persons count for aside activities: Nombre d'usager concernés par les activités annexes
filter:
Filter by aside activity date: Filtrer les activités annexes par date
Filter by aside activity type: Filtrer les activités annexes par type d'activité
@@ -212,8 +210,6 @@ export:
'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é
Group by concerned persons count: Grouper les activités annexes par nombre d'usagers conernés
Concerned persons count: Nombre d'usagers concernés
Aside activity type: Type d'activité annexe
by_user_job:
Aggregate by user job: Grouper les activités annexes par métier des utilisateurs

View File

@@ -205,6 +205,11 @@ class ChillMainExtension extends Extension implements
[]
);
$container->setParameter(
'chill_main.top_banner',
$config['top_banner'] ?? []
);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config'));
$loader->load('services.yaml');
$loader->load('services/doctrine.yaml');
@@ -250,6 +255,7 @@ class ChillMainExtension extends Extension implements
'name' => $config['installation_name'], ],
'available_languages' => $config['available_languages'],
'add_address' => $config['add_address'],
'chill_main_config' => $config,
],
'form_themes' => ['@ChillMain/Form/fields.html.twig'],
];

View File

@@ -168,6 +168,20 @@ class Configuration implements ConfigurationInterface
->end()
->end()
->end()
->arrayNode('top_banner')
->canBeUnset()
->children()
->booleanNode('visible')
->defaultFalse()
->end()
->arrayNode('text')
->useAttributeAsKey('lang')
->scalarPrototype()->end()
->end() // end of text
->scalarNode('color')->defaultNull()->end()
->scalarNode('background_color')->defaultNull()->end()
->end() // end of top_banner children
->end() // end of top_banner
->arrayNode('widgets')
->canBeEnabled()
->canBeUnset()

View File

@@ -0,0 +1,17 @@
{% if chill_main_config.top_banner is defined and chill_main_config.top_banner.text is defined %}
{% set banner_text = '' %}
{% set current_locale = app.request.locale %}
{% if chill_main_config.top_banner.text[current_locale] is defined %}
{% set banner_text = chill_main_config.top_banner.text[current_locale] %}
{% else %}
{% set banner_text = chill_main_config.top_banner.text|first %}
{% endif %}
{% if banner_text %}
<div class="top-banner w-100 text-center py-2"
style="{% if chill_main_config.top_banner.color is defined %}color: {{ chill_main_config.top_banner.color }};{% endif %}{% if chill_main_config.top_banner.background_color is defined %}background-color: {{ chill_main_config.top_banner.background_color }};{% endif %}">
{{ banner_text }}
</div>
{% endif %}
{% endif %}

View File

@@ -26,6 +26,10 @@
</head>
<body>
{% if chill_main_config.top_banner is defined and chill_main_config.top_banner.visible is true %}
{{ include('@ChillMain/Layout/_top_banner.html.twig') }}
{% endif %}
{% if responsive_debug is defined and responsive_debug == 1 %}
{{ include('@ChillMain/Layout/_debug.html.twig') }}
{% endif %}

View File

@@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Tests\DependencyInjection;
use Chill\MainBundle\DependencyInjection\Configuration;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* @internal
*
* @coversNothing
*/
class ConfigurationTest extends TestCase
{
public function testTopBannerConfiguration(): void
{
$containerBuilder = new ContainerBuilder();
$configuration = new Configuration([], $containerBuilder);
$processor = new Processor();
// Test with top_banner configuration
$config = [
'chill_main' => [
'top_banner' => [
'text' => [
'fr' => 'Vous travaillez actuellement avec la version de pré-production de Chill.',
'nl' => 'Je werkte momenteel in de pré-productie versie van Chill.',
],
'color' => 'white',
'background-color' => 'red',
],
],
];
$processedConfig = $processor->processConfiguration($configuration, $config);
self::assertArrayHasKey('top_banner', $processedConfig);
self::assertArrayHasKey('text', $processedConfig['top_banner']);
self::assertArrayHasKey('fr', $processedConfig['top_banner']['text']);
self::assertArrayHasKey('nl', $processedConfig['top_banner']['text']);
self::assertSame('white', $processedConfig['top_banner']['color']);
self::assertSame('red', $processedConfig['top_banner']['background_color']);
}
public function testTopBannerConfigurationOptional(): void
{
$containerBuilder = new ContainerBuilder();
$configuration = new Configuration([], $containerBuilder);
$processor = new Processor();
// Test without top_banner configuration
$config = [
'chill_main' => [],
];
$processedConfig = $processor->processConfiguration($configuration, $config);
// top_banner should not be present when not configured
self::assertArrayNotHasKey('top_banner', $processedConfig);
}
public function testTopBannerWithMinimalConfiguration(): void
{
$containerBuilder = new ContainerBuilder();
$configuration = new Configuration([], $containerBuilder);
$processor = new Processor();
// Test with minimal top_banner configuration (only text)
$config = [
'chill_main' => [
'top_banner' => [
'text' => [
'fr' => 'Test message',
],
],
],
];
$processedConfig = $processor->processConfiguration($configuration, $config);
self::assertArrayHasKey('top_banner', $processedConfig);
self::assertArrayHasKey('text', $processedConfig['top_banner']);
self::assertSame('Test message', $processedConfig['top_banner']['text']['fr']);
self::assertNull($processedConfig['top_banner']['color']);
self::assertNull($processedConfig['top_banner']['background_color']);
}
}