mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-28 01:25:00 +00:00
Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
c39637180a
|
|||
15f9409bc8 | |||
5b90d23367 | |||
c48625d1cd | |||
1195b54a68 | |||
2a280b814f |
8
.changes/v4.4.0.md
Normal file
8
.changes/v4.4.0.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
## v4.4.0 - 2025-09-11
|
||||||
|
### Feature
|
||||||
|
* ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Allow the merge of two accompanying period works
|
||||||
|
* ([#369](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/369)) Duplication of a document to another accompanying period work evaluation
|
||||||
|
* ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Fusion of two accompanying period works
|
||||||
|
### Fixed
|
||||||
|
* Fix display of 'duplicate' and 'merge' buttons in CRUD templates
|
||||||
|
* Fix saving notification preferences in user's profile
|
@@ -6,6 +6,15 @@ 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.4.0 - 2025-09-11
|
||||||
|
### Feature
|
||||||
|
* ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Allow the merge of two accompanying period works
|
||||||
|
* ([#369](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/369)) Duplication of a document to another accompanying period work evaluation
|
||||||
|
* ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Fusion of two accompanying period works
|
||||||
|
### Fixed
|
||||||
|
* Fix display of 'duplicate' and 'merge' buttons in CRUD templates
|
||||||
|
* Fix saving notification preferences in user's profile
|
||||||
|
|
||||||
## v4.3.0 - 2025-09-08
|
## v4.3.0 - 2025-09-08
|
||||||
### Feature
|
### Feature
|
||||||
* ([#409](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/409)) Add 45 and 60 min calendar ranges
|
* ([#409](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/409)) Add 45 and 60 min calendar ranges
|
||||||
|
@@ -55,5 +55,6 @@
|
|||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% block content_view_actions_duplicate_link %}{% endblock %}
|
||||||
{% endembed %}
|
{% endembed %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@@ -4,6 +4,7 @@ import { StoredObject, StoredObjectVersion } from "../../types";
|
|||||||
import DropFileWidget from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFileWidget.vue";
|
import DropFileWidget from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFileWidget.vue";
|
||||||
import { computed, reactive } from "vue";
|
import { computed, reactive } from "vue";
|
||||||
import { useToast } from "vue-toast-notification";
|
import { useToast } from "vue-toast-notification";
|
||||||
|
import { DOCUMENT_REPLACE, DOCUMENT_ADD, trans } from "translator";
|
||||||
|
|
||||||
interface DropFileConfig {
|
interface DropFileConfig {
|
||||||
allowRemove: boolean;
|
allowRemove: boolean;
|
||||||
@@ -75,10 +76,10 @@ function closeModal(): void {
|
|||||||
@click="openModal"
|
@click="openModal"
|
||||||
class="btn btn-create"
|
class="btn btn-create"
|
||||||
>
|
>
|
||||||
Ajouter un document
|
{{ trans(DOCUMENT_ADD) }}
|
||||||
</button>
|
</button>
|
||||||
<button v-else @click="openModal" class="btn btn-edit">
|
<button v-else @click="openModal" class="dropdown-item">
|
||||||
Remplacer le document
|
{{ trans(DOCUMENT_REPLACE) }}
|
||||||
</button>
|
</button>
|
||||||
<modal
|
<modal
|
||||||
v-if="state.showModal"
|
v-if="state.showModal"
|
||||||
|
@@ -23,6 +23,8 @@ See the document: Voir le document
|
|||||||
|
|
||||||
document:
|
document:
|
||||||
Any title: Aucun titre
|
Any title: Aucun titre
|
||||||
|
replace: Remplacer
|
||||||
|
Add: Ajouter un document
|
||||||
|
|
||||||
generic_doc:
|
generic_doc:
|
||||||
filter:
|
filter:
|
||||||
|
@@ -118,7 +118,7 @@
|
|||||||
|
|
||||||
{{ entity.notes|chill_print_or_message("Aucune note", 'blockquote') }}
|
{{ entity.notes|chill_print_or_message("Aucune note", 'blockquote') }}
|
||||||
{% endblock crud_content_view_details %}
|
{% endblock crud_content_view_details %}
|
||||||
|
{% block content_view_actions_duplicate_link %}{% endblock %}
|
||||||
{% block content_view_actions_back %}
|
{% block content_view_actions_back %}
|
||||||
<li class="cancel">
|
<li class="cancel">
|
||||||
<a class="btn btn-cancel" href="{{ chill_return_path_or('chill_job_report_index', { 'person': entity.person.id }) }}">
|
<a class="btn btn-cancel" href="{{ chill_return_path_or('chill_job_report_index', { 'person': entity.person.id }) }}">
|
||||||
|
@@ -46,6 +46,7 @@
|
|||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
{% endblock crud_content_view_details %}
|
{% endblock crud_content_view_details %}
|
||||||
|
{% block content_view_actions_duplicate_link %}{% endblock %}
|
||||||
|
|
||||||
{% block content_view_actions_back %}
|
{% block content_view_actions_back %}
|
||||||
<li class="cancel">
|
<li class="cancel">
|
||||||
|
@@ -206,6 +206,8 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% block content_view_actions_duplicate_link %}{% endblock %}
|
||||||
|
|
||||||
{% block content_view_actions_after %}
|
{% block content_view_actions_after %}
|
||||||
<li>
|
<li>
|
||||||
<a class="btn btn-misc" href="{{ chill_return_path_or('chill_crud_immersion_bilan', { 'id': entity.id, 'person_id': entity.person.id }) }}">
|
<a class="btn btn-misc" href="{{ chill_return_path_or('chill_crud_immersion_bilan', { 'id': entity.id, 'person_id': entity.person.id }) }}">
|
||||||
|
@@ -94,6 +94,7 @@
|
|||||||
|
|
||||||
|
|
||||||
{% endblock crud_content_view_details %}
|
{% endblock crud_content_view_details %}
|
||||||
|
{% block content_view_actions_duplicate_link %}{% endblock %}
|
||||||
|
|
||||||
{% block content_view_actions_back %}
|
{% block content_view_actions_back %}
|
||||||
<li class="cancel">
|
<li class="cancel">
|
||||||
|
@@ -0,0 +1,64 @@
|
|||||||
|
<?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\Action\User\UpdateProfile;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\MainBundle\Notification\NotificationFlagManager;
|
||||||
|
use Chill\MainBundle\Validation\Constraint\PhonenumberConstraint;
|
||||||
|
use libphonenumber\PhoneNumber;
|
||||||
|
|
||||||
|
final class UpdateProfileCommand
|
||||||
|
{
|
||||||
|
public array $notificationFlags = [];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
#[PhonenumberConstraint]
|
||||||
|
public ?PhoneNumber $phonenumber,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public static function create(User $user, NotificationFlagManager $flagManager): self
|
||||||
|
{
|
||||||
|
$updateProfileCommand = new self($user->getPhonenumber());
|
||||||
|
|
||||||
|
foreach ($flagManager->getAllNotificationFlagProviders() as $provider) {
|
||||||
|
$updateProfileCommand->setNotificationFlag(
|
||||||
|
$provider->getFlag(),
|
||||||
|
User::NOTIF_FLAG_IMMEDIATE_EMAIL,
|
||||||
|
$user->isNotificationSendImmediately($provider->getFlag())
|
||||||
|
);
|
||||||
|
$updateProfileCommand->setNotificationFlag(
|
||||||
|
$provider->getFlag(),
|
||||||
|
User::NOTIF_FLAG_DAILY_DIGEST,
|
||||||
|
$user->isNotificationDailyDigest($provider->getFlag())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $updateProfileCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param User::NOTIF_FLAG_IMMEDIATE_EMAIL|User::NOTIF_FLAG_DAILY_DIGEST $kind
|
||||||
|
*/
|
||||||
|
private function setNotificationFlag(string $type, string $kind, bool $value): void
|
||||||
|
{
|
||||||
|
if (!array_key_exists($type, $this->notificationFlags)) {
|
||||||
|
$this->notificationFlags[$type] = ['immediate_email' => true, 'daily_digest' => false];
|
||||||
|
}
|
||||||
|
|
||||||
|
$k = match ($kind) {
|
||||||
|
User::NOTIF_FLAG_IMMEDIATE_EMAIL => 'immediate_email',
|
||||||
|
User::NOTIF_FLAG_DAILY_DIGEST => 'daily_digest',
|
||||||
|
};
|
||||||
|
|
||||||
|
$this->notificationFlags[$type][$k] = $value;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Action\User\UpdateProfile;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
|
||||||
|
final readonly class UpdateProfileCommandHandler
|
||||||
|
{
|
||||||
|
public function updateProfile(User $user, UpdateProfileCommand $command): void
|
||||||
|
{
|
||||||
|
$user->setPhonenumber($command->phonenumber);
|
||||||
|
|
||||||
|
foreach ($command->notificationFlags as $flag => $values) {
|
||||||
|
$user->setNotificationImmediately($flag, $values['immediate_email']);
|
||||||
|
$user->setNotificationDailyDigest($flag, $values['daily_digest']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,63 +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\MainBundle\Controller;
|
|
||||||
|
|
||||||
use Chill\MainBundle\Form\UserProfileType;
|
|
||||||
use Chill\MainBundle\Security\ChillSecurity;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
|
||||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
|
||||||
|
|
||||||
final class UserProfileController extends AbstractController
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private readonly TranslatorInterface $translator,
|
|
||||||
private readonly ChillSecurity $security,
|
|
||||||
private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User profile that allows editing of phonenumber and visualization of certain data.
|
|
||||||
*/
|
|
||||||
#[Route(path: '/{_locale}/main/user/my-profile', name: 'chill_main_user_profile')]
|
|
||||||
public function __invoke(Request $request)
|
|
||||||
{
|
|
||||||
if (!$this->security->isGranted('ROLE_USER')) {
|
|
||||||
throw new AccessDeniedHttpException();
|
|
||||||
}
|
|
||||||
|
|
||||||
$user = $this->security->getUser();
|
|
||||||
$editForm = $this->createForm(UserProfileType::class, $user);
|
|
||||||
|
|
||||||
$editForm->get('notificationFlags')->setData($user->getNotificationFlags());
|
|
||||||
|
|
||||||
$editForm->handleRequest($request);
|
|
||||||
|
|
||||||
if ($editForm->isSubmitted() && $editForm->isValid()) {
|
|
||||||
$notificationFlagsData = $editForm->get('notificationFlags')->getData();
|
|
||||||
$user->setNotificationFlags($notificationFlagsData);
|
|
||||||
|
|
||||||
$em = $this->managerRegistry->getManager();
|
|
||||||
$em->flush();
|
|
||||||
$this->addFlash('success', $this->translator->trans('user.profile.Profile successfully updated!'));
|
|
||||||
|
|
||||||
return $this->redirectToRoute('chill_main_user_profile');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->render('@ChillMain/User/profile.html.twig', [
|
|
||||||
'user' => $user,
|
|
||||||
'form' => $editForm->createView(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,75 @@
|
|||||||
|
<?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\Controller;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Action\User\UpdateProfile\UpdateProfileCommand;
|
||||||
|
use Chill\MainBundle\Action\User\UpdateProfile\UpdateProfileCommandHandler;
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\MainBundle\Form\UpdateProfileType;
|
||||||
|
use Chill\MainBundle\Notification\NotificationFlagManager;
|
||||||
|
use Chill\MainBundle\Security\ChillSecurity;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Component\Form\FormFactoryInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Session;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Twig\Environment;
|
||||||
|
|
||||||
|
final readonly class UserUpdateProfileController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private TranslatorInterface $translator,
|
||||||
|
private ChillSecurity $security,
|
||||||
|
private EntityManagerInterface $entityManager,
|
||||||
|
private NotificationFlagManager $notificationFlagManager,
|
||||||
|
private FormFactoryInterface $formFactory,
|
||||||
|
private UrlGeneratorInterface $urlGenerator,
|
||||||
|
private Environment $twig,
|
||||||
|
private UpdateProfileCommandHandler $updateProfileCommandHandler,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User profile that allows editing of phonenumber and visualization of certain data.
|
||||||
|
*/
|
||||||
|
#[Route(path: '/{_locale}/main/user/my-profile', name: 'chill_main_user_profile')]
|
||||||
|
public function __invoke(Request $request, Session $session)
|
||||||
|
{
|
||||||
|
if (!$this->security->isGranted('ROLE_USER')) {
|
||||||
|
throw new AccessDeniedHttpException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->security->getUser();
|
||||||
|
|
||||||
|
$command = UpdateProfileCommand::create($user, $this->notificationFlagManager);
|
||||||
|
$editForm = $this->formFactory->create(UpdateProfileType::class, $command);
|
||||||
|
|
||||||
|
$editForm->handleRequest($request);
|
||||||
|
|
||||||
|
if ($editForm->isSubmitted() && $editForm->isValid()) {
|
||||||
|
$this->updateProfileCommandHandler->updateProfile($user, $command);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
$session->getFlashBag()->add('success', $this->translator->trans('user.profile.Profile successfully updated!'));
|
||||||
|
|
||||||
|
return new RedirectResponse($this->urlGenerator->generate('chill_main_user_profile'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response($this->twig->render('@ChillMain/User/profile.html.twig', [
|
||||||
|
'user' => $user,
|
||||||
|
'form' => $editForm->createView(),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
}
|
@@ -652,42 +652,66 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getNotificationFlags(): array
|
private function getNotificationFlagData(string $flag): array
|
||||||
{
|
{
|
||||||
return $this->notificationFlags;
|
return $this->notificationFlags[$flag] ?? [self::NOTIF_FLAG_IMMEDIATE_EMAIL];
|
||||||
}
|
|
||||||
|
|
||||||
public function setNotificationFlags(array $notificationFlags)
|
|
||||||
{
|
|
||||||
$this->notificationFlags = $notificationFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getNotificationFlagData(string $flag): array
|
|
||||||
{
|
|
||||||
return $this->notificationFlags[$flag] ?? [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setNotificationFlagData(string $flag, array $data): void
|
|
||||||
{
|
|
||||||
$this->notificationFlags[$flag] = $data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isNotificationSendImmediately(string $type): bool
|
public function isNotificationSendImmediately(string $type): bool
|
||||||
{
|
{
|
||||||
if ([] === $this->getNotificationFlagData($type) || in_array(User::NOTIF_FLAG_IMMEDIATE_EMAIL, $this->getNotificationFlagData($type), true)) {
|
return $this->isNotificationForElement($type, self::NOTIF_FLAG_IMMEDIATE_EMAIL);
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
public function setNotificationImmediately(string $type, bool $active): void
|
||||||
|
{
|
||||||
|
$this->setNotificationFlagElement($type, $active, self::NOTIF_FLAG_IMMEDIATE_EMAIL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setNotificationDailyDigest(string $type, bool $active): void
|
||||||
|
{
|
||||||
|
$this->setNotificationFlagElement($type, $active, self::NOTIF_FLAG_DAILY_DIGEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param self::NOTIF_FLAG_IMMEDIATE_EMAIL|self::NOTIF_FLAG_DAILY_DIGEST $kind
|
||||||
|
*/
|
||||||
|
private function setNotificationFlagElement(string $type, bool $active, string $kind): void
|
||||||
|
{
|
||||||
|
$notificationFlags = [...$this->notificationFlags];
|
||||||
|
$changed = false;
|
||||||
|
|
||||||
|
if (!isset($notificationFlags[$type])) {
|
||||||
|
$notificationFlags[$type] = [self::NOTIF_FLAG_IMMEDIATE_EMAIL];
|
||||||
|
$changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
if ($active) {
|
||||||
|
if (!in_array($kind, $notificationFlags[$type], true)) {
|
||||||
|
$notificationFlags[$type] = [...$notificationFlags[$type], $kind];
|
||||||
|
$changed = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (in_array($kind, $notificationFlags[$type], true)) {
|
||||||
|
$notificationFlags[$type] = array_values(
|
||||||
|
array_filter($notificationFlags[$type], static fn ($k) => $k !== $kind)
|
||||||
|
);
|
||||||
|
$changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($changed) {
|
||||||
|
$this->notificationFlags = [...$notificationFlags];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isNotificationForElement(string $type, string $kind): bool
|
||||||
|
{
|
||||||
|
return in_array($kind, $this->getNotificationFlagData($type), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isNotificationDailyDigest(string $type): bool
|
public function isNotificationDailyDigest(string $type): bool
|
||||||
{
|
{
|
||||||
if (in_array(User::NOTIF_FLAG_DAILY_DIGEST, $this->getNotificationFlagData($type), true)) {
|
return $this->isNotificationForElement($type, self::NOTIF_FLAG_DAILY_DIGEST);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLocale(): string
|
public function getLocale(): string
|
||||||
|
@@ -1,75 +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\MainBundle\Form\DataMapper;
|
|
||||||
|
|
||||||
use Chill\MainBundle\Entity\User;
|
|
||||||
use Symfony\Component\Form\DataMapperInterface;
|
|
||||||
|
|
||||||
final readonly class NotificationFlagDataMapper implements DataMapperInterface
|
|
||||||
{
|
|
||||||
public function __construct(private array $notificationFlagProviders) {}
|
|
||||||
|
|
||||||
public function mapDataToForms($viewData, $forms): void
|
|
||||||
{
|
|
||||||
if (null === $viewData) {
|
|
||||||
$viewData = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$formsArray = iterator_to_array($forms);
|
|
||||||
|
|
||||||
foreach ($this->notificationFlagProviders as $flagProvider) {
|
|
||||||
$flag = $flagProvider->getFlag();
|
|
||||||
|
|
||||||
if (isset($formsArray[$flag])) {
|
|
||||||
$flagForm = $formsArray[$flag];
|
|
||||||
|
|
||||||
$immediateEmailChecked = in_array(User::NOTIF_FLAG_IMMEDIATE_EMAIL, $viewData[$flag] ?? [], true)
|
|
||||||
|| !array_key_exists($flag, $viewData);
|
|
||||||
$dailyEmailChecked = in_array(User::NOTIF_FLAG_DAILY_DIGEST, $viewData[$flag] ?? [], true);
|
|
||||||
|
|
||||||
if ($flagForm->has('immediate_email')) {
|
|
||||||
$flagForm->get('immediate_email')->setData($immediateEmailChecked);
|
|
||||||
}
|
|
||||||
if ($flagForm->has('daily_email')) {
|
|
||||||
$flagForm->get('daily_email')->setData($dailyEmailChecked);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function mapFormsToData($forms, &$viewData): void
|
|
||||||
{
|
|
||||||
$formsArray = iterator_to_array($forms);
|
|
||||||
$viewData = [];
|
|
||||||
|
|
||||||
foreach ($this->notificationFlagProviders as $flagProvider) {
|
|
||||||
$flag = $flagProvider->getFlag();
|
|
||||||
|
|
||||||
if (isset($formsArray[$flag])) {
|
|
||||||
$flagForm = $formsArray[$flag];
|
|
||||||
$viewData[$flag] = [];
|
|
||||||
|
|
||||||
if (true === $flagForm['immediate_email']->getData()) {
|
|
||||||
$viewData[$flag][] = User::NOTIF_FLAG_IMMEDIATE_EMAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (true === $flagForm['daily_email']->getData()) {
|
|
||||||
$viewData[$flag][] = User::NOTIF_FLAG_DAILY_DIGEST;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([] === $viewData[$flag]) {
|
|
||||||
$viewData[$flag][] = User::NOTIF_FLAG_IMMEDIATE_EMAIL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -11,11 +11,9 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\MainBundle\Form\Type;
|
namespace Chill\MainBundle\Form\Type;
|
||||||
|
|
||||||
use Chill\MainBundle\Form\DataMapper\NotificationFlagDataMapper;
|
|
||||||
use Chill\MainBundle\Notification\NotificationFlagManager;
|
use Chill\MainBundle\Notification\NotificationFlagManager;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
@@ -30,27 +28,24 @@ class NotificationFlagsType extends AbstractType
|
|||||||
|
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
{
|
{
|
||||||
$builder->setDataMapper(new NotificationFlagDataMapper($this->notificationFlagProviders));
|
|
||||||
|
|
||||||
foreach ($this->notificationFlagProviders as $flagProvider) {
|
foreach ($this->notificationFlagProviders as $flagProvider) {
|
||||||
$flag = $flagProvider->getFlag();
|
$flag = $flagProvider->getFlag();
|
||||||
$builder->add($flag, FormType::class, [
|
$flagBuilder = $builder->create($flag, options: [
|
||||||
'label' => $flagProvider->getLabel(),
|
'label' => $flagProvider->getLabel(),
|
||||||
'required' => false,
|
'compound' => true,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$builder->get($flag)
|
$flagBuilder
|
||||||
->add('immediate_email', CheckboxType::class, [
|
->add('immediate_email', CheckboxType::class, [
|
||||||
'label' => false,
|
'label' => false,
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'mapped' => false,
|
|
||||||
])
|
])
|
||||||
->add('daily_email', CheckboxType::class, [
|
->add('daily_digest', CheckboxType::class, [
|
||||||
'label' => false,
|
'label' => false,
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'mapped' => false,
|
|
||||||
])
|
])
|
||||||
;
|
;
|
||||||
|
$builder->add($flagBuilder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +53,7 @@ class NotificationFlagsType extends AbstractType
|
|||||||
{
|
{
|
||||||
$resolver->setDefaults([
|
$resolver->setDefaults([
|
||||||
'data_class' => null,
|
'data_class' => null,
|
||||||
|
'compound' => true,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,31 +11,29 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Chill\MainBundle\Form;
|
namespace Chill\MainBundle\Form;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Action\User\UpdateProfile\UpdateProfileCommand;
|
||||||
use Chill\MainBundle\Form\Type\ChillPhoneNumberType;
|
use Chill\MainBundle\Form\Type\ChillPhoneNumberType;
|
||||||
use Chill\MainBundle\Form\Type\NotificationFlagsType;
|
use Chill\MainBundle\Form\Type\NotificationFlagsType;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
class UserProfileType extends AbstractType
|
class UpdateProfileType extends AbstractType
|
||||||
{
|
{
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
{
|
{
|
||||||
$builder
|
$builder
|
||||||
->add('phonenumber', ChillPhoneNumberType::class, [
|
->add('phonenumber', ChillPhoneNumberType::class, [
|
||||||
'required' => false,
|
'required' => false,
|
||||||
])
|
])
|
||||||
->add('notificationFlags', NotificationFlagsType::class, [
|
->add('notificationFlags', NotificationFlagsType::class)
|
||||||
'label' => false,
|
|
||||||
'mapped' => false,
|
|
||||||
])
|
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configureOptions(OptionsResolver $resolver)
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
{
|
{
|
||||||
$resolver->setDefaults([
|
$resolver->setDefaults([
|
||||||
'data_class' => \Chill\MainBundle\Entity\User::class,
|
'data_class' => UpdateProfileCommand::class,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -44,17 +44,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock content_view_actions_duplicate_link %}
|
{% endblock content_view_actions_duplicate_link %}
|
||||||
{% block content_view_actions_merge %}
|
{% block content_view_actions_merge %}{% endblock %}
|
||||||
<li>
|
|
||||||
<a href="{{ chill_path_add_return_path('chill_thirdparty_find_duplicate',
|
|
||||||
{ 'thirdparty_id': entity.id }) }}"
|
|
||||||
title="{{ 'Merge'|trans }}"
|
|
||||||
class="btn btn-misc">
|
|
||||||
<i class="bi bi-chevron-contract"></i>
|
|
||||||
{{ 'Merge'|trans }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endblock %}
|
|
||||||
{% block content_view_actions_edit_link %}
|
{% block content_view_actions_edit_link %}
|
||||||
{% if chill_crud_action_exists(crud_name, 'edit') %}
|
{% if chill_crud_action_exists(crud_name, 'edit') %}
|
||||||
{% if is_granted(chill_crud_config('role', crud_name, 'edit'), entity) %}
|
{% if is_granted(chill_crud_config('role', crud_name, 'edit'), entity) %}
|
||||||
|
@@ -280,11 +280,17 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block pick_linked_entities_widget %}
|
|
||||||
<input type="hidden" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value|escape('html_attr') }}" {% endif %} data-input-uniqid="{{ form.vars['uniqid'] }}" />
|
|
||||||
<div data-input-uniqid="{{ form.vars['uniqid'] }}" data-module="pick-linked-entities" data-pick-entities-type="{{ form.vars['pick-entities-type'] }}"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
|
{% block pick_linked_entities_widget %}
|
||||||
|
<input type="hidden" {{ block('widget_attributes') }}
|
||||||
|
{% if value is not empty %}value="{{ value|escape('html_attr') }}" {% endif %}
|
||||||
|
data-input-uniqid="{{ form.vars['uniqid'] }}"/>
|
||||||
|
<div
|
||||||
|
data-input-uniqid="{{ form.vars['uniqid'] }}"
|
||||||
|
data-module="pick-linked-entities"
|
||||||
|
data-pick-entities-type="{{ form.vars['pick-entities-type'] }}"
|
||||||
|
data-suggested="{{ form.vars['suggested']|json_encode|escape('html_attr') }}"
|
||||||
|
></div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block pick_postal_code_widget %}
|
{% block pick_postal_code_widget %}
|
||||||
|
@@ -64,7 +64,7 @@
|
|||||||
{{ form_widget(flag.immediate_email, {'label_attr': { 'class': 'checkbox-inline checkbox-switch'}}) }}
|
{{ form_widget(flag.immediate_email, {'label_attr': { 'class': 'checkbox-inline checkbox-switch'}}) }}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
{{ form_widget(flag.daily_email, {'label_attr': { 'class': 'checkbox-inline checkbox-switch'}}) }}
|
{{ form_widget(flag.daily_digest, {'label_attr': { 'class': 'checkbox-inline checkbox-switch'}}) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@@ -0,0 +1,85 @@
|
|||||||
|
<?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 Action\User\UpdateProfile;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Action\User\UpdateProfile\UpdateProfileCommand;
|
||||||
|
use Chill\MainBundle\Action\User\UpdateProfile\UpdateProfileCommandHandler;
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use libphonenumber\PhoneNumber;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
final class UpdateProfileCommandHandlerTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testUpdateProfileWithNullPhoneAndFlags(): void
|
||||||
|
{
|
||||||
|
$user = new User();
|
||||||
|
|
||||||
|
// Pre-set some flags to opposite values to check they are updated
|
||||||
|
$flag = 'tickets';
|
||||||
|
$user->setNotificationImmediately($flag, true);
|
||||||
|
$user->setNotificationDailyDigest($flag, true);
|
||||||
|
|
||||||
|
$command = new UpdateProfileCommand(null);
|
||||||
|
$command->notificationFlags = [
|
||||||
|
$flag => [
|
||||||
|
'immediate_email' => false,
|
||||||
|
'daily_digest' => false,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
(new UpdateProfileCommandHandler())->updateProfile($user, $command);
|
||||||
|
|
||||||
|
self::assertNull($user->getPhonenumber(), 'Phone should be set to null');
|
||||||
|
self::assertFalse($user->isNotificationSendImmediately($flag));
|
||||||
|
self::assertFalse($user->isNotificationDailyDigest($flag));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUpdateProfileWithPhoneAndMultipleFlags(): void
|
||||||
|
{
|
||||||
|
$user = new User();
|
||||||
|
|
||||||
|
$phone = new PhoneNumber();
|
||||||
|
$phone->setCountryCode(33); // France
|
||||||
|
$phone->setNationalNumber(612345678);
|
||||||
|
|
||||||
|
$command = new UpdateProfileCommand($phone);
|
||||||
|
$command->notificationFlags = [
|
||||||
|
'reports' => [
|
||||||
|
'immediate_email' => true,
|
||||||
|
'daily_digest' => false,
|
||||||
|
],
|
||||||
|
'activities' => [
|
||||||
|
'immediate_email' => false,
|
||||||
|
'daily_digest' => true,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
(new UpdateProfileCommandHandler())->updateProfile($user, $command);
|
||||||
|
|
||||||
|
// Phone assigned
|
||||||
|
self::assertInstanceOf(PhoneNumber::class, $user->getPhonenumber());
|
||||||
|
self::assertSame(33, $user->getPhonenumber()->getCountryCode());
|
||||||
|
self::assertSame('612345678', (string) $user->getPhonenumber()->getNationalNumber());
|
||||||
|
|
||||||
|
// Flags applied
|
||||||
|
self::assertTrue($user->isNotificationSendImmediately('reports'));
|
||||||
|
self::assertFalse($user->isNotificationDailyDigest('reports'));
|
||||||
|
|
||||||
|
self::assertFalse($user->isNotificationSendImmediately('activities'));
|
||||||
|
self::assertTrue($user->isNotificationDailyDigest('activities'));
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Action\User\UpdateProfile;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Action\User\UpdateProfile\UpdateProfileCommand;
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\MainBundle\Notification\FlagProviders\NotificationFlagProviderInterface;
|
||||||
|
use Chill\MainBundle\Notification\NotificationFlagManager;
|
||||||
|
use libphonenumber\PhoneNumber;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\Translation\TranslatableMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
final class UpdateProfileCommandTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testCreateTransfersPhonenumberAndNotificationFlags(): void
|
||||||
|
{
|
||||||
|
$user = new User();
|
||||||
|
|
||||||
|
// set a phone number
|
||||||
|
$phone = new PhoneNumber();
|
||||||
|
$phone->setCountryCode(32); // Belgium
|
||||||
|
$phone->setNationalNumber(471234567);
|
||||||
|
$user->setPhonenumber($phone);
|
||||||
|
|
||||||
|
// configure notification flags on the user via helpers
|
||||||
|
$flagA = 'foo';
|
||||||
|
$flagB = 'bar';
|
||||||
|
|
||||||
|
// For tickets: immediate true, daily false
|
||||||
|
$user->setNotificationImmediately($flagA, true);
|
||||||
|
$user->setNotificationDailyDigest($flagA, false);
|
||||||
|
|
||||||
|
// For reports: immediate false, daily true
|
||||||
|
$user->setNotificationImmediately($flagB, false);
|
||||||
|
$user->setNotificationDailyDigest($flagB, true);
|
||||||
|
|
||||||
|
// a third flag not explicitly set to validate default behavior from User
|
||||||
|
$flagC = 'foobar'; // by default immediate-email is true, daily-digest is false per User::getNotificationFlagData
|
||||||
|
|
||||||
|
$manager = $this->createNotificationFlagManager([$flagA, $flagB, $flagC]);
|
||||||
|
|
||||||
|
$command = UpdateProfileCommand::create($user, $manager);
|
||||||
|
|
||||||
|
// phone number transferred
|
||||||
|
self::assertInstanceOf(PhoneNumber::class, $command->phonenumber);
|
||||||
|
self::assertSame($phone->getCountryCode(), $command->phonenumber->getCountryCode());
|
||||||
|
self::assertSame($phone->getNationalNumber(), $command->phonenumber->getNationalNumber());
|
||||||
|
|
||||||
|
// flags transferred consistently
|
||||||
|
self::assertArrayHasKey($flagA, $command->notificationFlags);
|
||||||
|
self::assertArrayHasKey($flagB, $command->notificationFlags);
|
||||||
|
self::assertArrayHasKey($flagC, $command->notificationFlags);
|
||||||
|
|
||||||
|
self::assertSame([
|
||||||
|
'immediate_email' => true,
|
||||||
|
'daily_digest' => false,
|
||||||
|
], $command->notificationFlags[$flagA]);
|
||||||
|
|
||||||
|
self::assertSame([
|
||||||
|
'immediate_email' => false,
|
||||||
|
'daily_digest' => true,
|
||||||
|
], $command->notificationFlags[$flagB]);
|
||||||
|
|
||||||
|
// default from User::getNotificationFlagData -> immediate true, daily false
|
||||||
|
self::assertSame([
|
||||||
|
'immediate_email' => true,
|
||||||
|
'daily_digest' => false,
|
||||||
|
], $command->notificationFlags[$flagC]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createNotificationFlagManager(array $flags): NotificationFlagManager
|
||||||
|
{
|
||||||
|
$providers = array_map(fn (string $flag) => new class ($flag) implements NotificationFlagProviderInterface {
|
||||||
|
public function __construct(private readonly string $flag) {}
|
||||||
|
|
||||||
|
public function getFlag(): string
|
||||||
|
{
|
||||||
|
return $this->flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLabel(): TranslatableMessage
|
||||||
|
{
|
||||||
|
return new TranslatableMessage($this->flag);
|
||||||
|
}
|
||||||
|
}, $flags);
|
||||||
|
|
||||||
|
return new NotificationFlagManager($providers);
|
||||||
|
}
|
||||||
|
}
|
@@ -96,11 +96,13 @@ final class NotificationTest extends KernelTestCase
|
|||||||
$this->assertTrue($user->isNotificationSendImmediately($notification->getType()), 'Should return true when no notification flags are set, by default immediate email');
|
$this->assertTrue($user->isNotificationSendImmediately($notification->getType()), 'Should return true when no notification flags are set, by default immediate email');
|
||||||
|
|
||||||
// immediate-email preference
|
// immediate-email preference
|
||||||
$user->setNotificationFlags(['test_notification_type' => [User::NOTIF_FLAG_IMMEDIATE_EMAIL, User::NOTIF_FLAG_DAILY_DIGEST]]);
|
$user->setNotificationImmediately('test_notification_type', true);
|
||||||
|
$user->setNotificationDailyDigest('test_notification_type', true);
|
||||||
$this->assertTrue($user->isNotificationSendImmediately($notification->getType()), 'Should return true when preferences contain immediate-email');
|
$this->assertTrue($user->isNotificationSendImmediately($notification->getType()), 'Should return true when preferences contain immediate-email');
|
||||||
|
|
||||||
// daily-email preference
|
// daily-email preference
|
||||||
$user->setNotificationFlags(['test_notification_type' => [User::NOTIF_FLAG_DAILY_DIGEST]]);
|
$user->setNotificationDailyDigest('test_notification_type', true);
|
||||||
|
$user->setNotificationImmediately('test_notification_type', false);
|
||||||
$this->assertFalse($user->isNotificationSendImmediately($notification->getType()), 'Should return false when preference is daily-email only');
|
$this->assertFalse($user->isNotificationSendImmediately($notification->getType()), 'Should return false when preference is daily-email only');
|
||||||
$this->assertTrue($user->isNotificationDailyDigest($notification->getType()), 'Should return true when preference is daily-email');
|
$this->assertTrue($user->isNotificationDailyDigest($notification->getType()), 'Should return true when preference is daily-email');
|
||||||
|
|
||||||
|
@@ -0,0 +1,82 @@
|
|||||||
|
<?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\Entity;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class UserNotificationFlagsPersistenceTest extends KernelTestCase
|
||||||
|
{
|
||||||
|
public function testFlushPersistsNotificationFlagsChanges(): void
|
||||||
|
{
|
||||||
|
self::bootKernel();
|
||||||
|
$em = self::getContainer()->get('doctrine')->getManager();
|
||||||
|
|
||||||
|
$user = new User();
|
||||||
|
$user->setUsername('user_'.bin2hex(random_bytes(4)));
|
||||||
|
$user->setLabel('Test User');
|
||||||
|
$user->setPassword('secret');
|
||||||
|
|
||||||
|
// Étape 1: créer et persister l’utilisateur
|
||||||
|
$em->persist($user);
|
||||||
|
$em->flush();
|
||||||
|
$id = $user->getId();
|
||||||
|
self::assertNotNull($id, 'User should have an ID after flush');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Sanity check: par défaut, pas de daily digest pour "alerts"
|
||||||
|
self::assertFalse($user->isNotificationDailyDigest('alerts'));
|
||||||
|
|
||||||
|
// Étape 2: activer le daily digest -> setNotificationFlagElement réassigne la propriété
|
||||||
|
$user->setNotificationDailyDigest('alerts', true);
|
||||||
|
$em->flush(); // persist le changement
|
||||||
|
$em->clear(); // simule un nouveau cycle de requête
|
||||||
|
|
||||||
|
// Étape 3: recharger depuis la base et vérifier la persistance
|
||||||
|
/** @var User $reloaded */
|
||||||
|
$reloaded = $em->find(User::class, $id);
|
||||||
|
self::assertNotNull($reloaded);
|
||||||
|
self::assertTrue(
|
||||||
|
$reloaded->isNotificationDailyDigest('alerts'),
|
||||||
|
'Daily digest flag should be persisted'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Étape 4: modifier via setNotificationFlagData (remplacement du tableau)
|
||||||
|
// Cette méthode doit réassigner la propriété (copie -> réassignation)
|
||||||
|
$reloaded->setNotificationImmediately('alerts', true);
|
||||||
|
$reloaded->setNotificationDailyDigest('alerts', false);
|
||||||
|
$em->flush();
|
||||||
|
$em->clear();
|
||||||
|
|
||||||
|
/** @var User $reloaded2 */
|
||||||
|
$reloaded2 = $em->find(User::class, $id);
|
||||||
|
self::assertNotNull($reloaded2);
|
||||||
|
|
||||||
|
// Le daily digest n’est plus actif, seul immediate-email est présent
|
||||||
|
self::assertFalse($reloaded2->isNotificationDailyDigest('alerts'));
|
||||||
|
self::assertTrue($reloaded2->isNotificationSendImmediately('alerts'));
|
||||||
|
} finally {
|
||||||
|
// Nettoyage
|
||||||
|
$managed = $em->find(User::class, $id);
|
||||||
|
if (null !== $managed) {
|
||||||
|
$em->remove($managed);
|
||||||
|
$em->flush();
|
||||||
|
}
|
||||||
|
$em->clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -99,4 +99,22 @@ class UserTest extends TestCase
|
|||||||
$user->setAbsenceEnd(null);
|
$user->setAbsenceEnd(null);
|
||||||
self::assertFalse($user->isAbsent(), 'Should not be absent if start is null');
|
self::assertFalse($user->isAbsent(), 'Should not be absent if start is null');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testSetNotification(): void
|
||||||
|
{
|
||||||
|
$user = new User();
|
||||||
|
|
||||||
|
self::assertTrue($user->isNotificationSendImmediately('dummy'));
|
||||||
|
self::assertFalse($user->isNotificationDailyDigest('dummy'));
|
||||||
|
|
||||||
|
$user->setNotificationImmediately('dummy', false);
|
||||||
|
self::assertFalse($user->isNotificationSendImmediately('dummy'));
|
||||||
|
|
||||||
|
$user->setNotificationDailyDigest('dummy', true);
|
||||||
|
self::assertTrue($user->isNotificationDailyDigest('dummy'));
|
||||||
|
|
||||||
|
$user->setNotificationImmediately('dummy', true);
|
||||||
|
self::assertTrue($user->isNotificationSendImmediately('dummy'));
|
||||||
|
self::assertTrue($user->isNotificationDailyDigest('dummy'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -144,7 +144,7 @@ class NotificationMailerTest extends TestCase
|
|||||||
$idProperty->setValue($user, 456);
|
$idProperty->setValue($user, 456);
|
||||||
|
|
||||||
// Set notification flags for the user
|
// Set notification flags for the user
|
||||||
$user->setNotificationFlags(['test_notification_type' => [User::NOTIF_FLAG_IMMEDIATE_EMAIL]]);
|
$user->setNotificationImmediately('test_notification_type', true);
|
||||||
|
|
||||||
$messageBus = $this->createMock(MessageBusInterface::class);
|
$messageBus = $this->createMock(MessageBusInterface::class);
|
||||||
$messageBus->expects($this->once())
|
$messageBus->expects($this->once())
|
||||||
|
@@ -113,3 +113,5 @@ services:
|
|||||||
Chill\MainBundle\Service\EntityInfo\ViewEntityInfoManager:
|
Chill\MainBundle\Service\EntityInfo\ViewEntityInfoManager:
|
||||||
arguments:
|
arguments:
|
||||||
$vienEntityInfoProviders: !tagged_iterator chill_main.entity_info_provider
|
$vienEntityInfoProviders: !tagged_iterator chill_main.entity_info_provider
|
||||||
|
|
||||||
|
Chill\MainBundle\Action\User\UpdateProfile\UpdateProfileCommandHandler: ~
|
||||||
|
@@ -18,6 +18,7 @@ use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepos
|
|||||||
use Chill\PersonBundle\Service\AccompanyingPeriodWork\AccompanyingPeriodWorkMergeService;
|
use Chill\PersonBundle\Service\AccompanyingPeriodWork\AccompanyingPeriodWorkMergeService;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpFoundation\Session\Session;
|
use Symfony\Component\HttpFoundation\Session\Session;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
use Symfony\Component\Translation\TranslatableMessage;
|
use Symfony\Component\Translation\TranslatableMessage;
|
||||||
@@ -32,7 +33,7 @@ class AccompanyingPeriodWorkDuplicateController extends AbstractController
|
|||||||
* @ParamConverter("accompanyingPeriodWork", options={"id": "acpw_id"})
|
* @ParamConverter("accompanyingPeriodWork", options={"id": "acpw_id"})
|
||||||
*/
|
*/
|
||||||
#[Route(path: '{_locale}/person/accompanying-period/work/{id}/assign-duplicate', name: 'chill_person_accompanying_period_work_assign_duplicate')]
|
#[Route(path: '{_locale}/person/accompanying-period/work/{id}/assign-duplicate', name: 'chill_person_accompanying_period_work_assign_duplicate')]
|
||||||
public function assignDuplicate(AccompanyingPeriodWork $acpw, Request $request)
|
public function assignDuplicate(AccompanyingPeriodWork $acpw, Request $request): Response
|
||||||
{
|
{
|
||||||
$accompanyingPeriod = $acpw->getAccompanyingPeriod();
|
$accompanyingPeriod = $acpw->getAccompanyingPeriod();
|
||||||
|
|
||||||
@@ -79,7 +80,7 @@ class AccompanyingPeriodWorkDuplicateController extends AbstractController
|
|||||||
* @ParamConverter("acpw2", options={"id": "acpw2_id"})
|
* @ParamConverter("acpw2", options={"id": "acpw2_id"})
|
||||||
*/
|
*/
|
||||||
#[Route(path: '/{_locale}/person/{acpw1_id}/acpw-duplicate/{acpw2_id}/confirm', name: 'chill_person_acpw_duplicate_confirm')]
|
#[Route(path: '/{_locale}/person/{acpw1_id}/acpw-duplicate/{acpw2_id}/confirm', name: 'chill_person_acpw_duplicate_confirm')]
|
||||||
public function confirmAction(AccompanyingPeriodWork $acpw1, AccompanyingPeriodWork $acpw2, Request $request)
|
public function confirmAction(AccompanyingPeriodWork $acpw1, AccompanyingPeriodWork $acpw2, Request $request): Response
|
||||||
{
|
{
|
||||||
$accompanyingPeriod = $acpw1->getAccompanyingPeriod();
|
$accompanyingPeriod = $acpw1->getAccompanyingPeriod();
|
||||||
|
|
||||||
@@ -98,6 +99,7 @@ class AccompanyingPeriodWorkDuplicateController extends AbstractController
|
|||||||
$session->getFlashBag()->add('success', new TranslatableMessage('acpw_duplicate.Successfully merged'));
|
$session->getFlashBag()->add('success', new TranslatableMessage('acpw_duplicate.Successfully merged'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return $this->redirectToRoute('chill_person_accompanying_period_work_show', ['id' => $acpw1->getId()]);
|
return $this->redirectToRoute('chill_person_accompanying_period_work_show', ['id' => $acpw1->getId()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
|||||||
namespace Chill\PersonBundle\Controller;
|
namespace Chill\PersonBundle\Controller;
|
||||||
|
|
||||||
use Chill\MainBundle\Routing\ChillUrlGeneratorInterface;
|
use Chill\MainBundle\Routing\ChillUrlGeneratorInterface;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
|
||||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter;
|
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter;
|
||||||
use Chill\PersonBundle\Service\AccompanyingPeriodWorkEvaluationDocument\AccompanyingPeriodWorkEvaluationDocumentDuplicator;
|
use Chill\PersonBundle\Service\AccompanyingPeriodWorkEvaluationDocument\AccompanyingPeriodWorkEvaluationDocumentDuplicator;
|
||||||
@@ -24,15 +25,16 @@ use Symfony\Component\Routing\Annotation\Route;
|
|||||||
use Symfony\Component\Security\Core\Security;
|
use Symfony\Component\Security\Core\Security;
|
||||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||||
use Symfony\Component\Serializer\SerializerInterface;
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
|
||||||
|
|
||||||
class AccompanyingPeriodWorkEvaluationDocumentDuplicateController
|
readonly class AccompanyingPeriodWorkEvaluationDocumentDuplicateController
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly AccompanyingPeriodWorkEvaluationDocumentDuplicator $duplicator,
|
private AccompanyingPeriodWorkEvaluationDocumentDuplicator $duplicator,
|
||||||
private readonly Security $security,
|
private Security $security,
|
||||||
private readonly SerializerInterface $serializer,
|
private SerializerInterface $serializer,
|
||||||
private readonly EntityManagerInterface $entityManager,
|
private EntityManagerInterface $entityManager,
|
||||||
private readonly ChillUrlGeneratorInterface $urlGenerator,
|
private ChillUrlGeneratorInterface $urlGenerator,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
#[Route('/api/1.0/person/accompanying-course-work-evaluation-document/{id}/duplicate', methods: ['POST'])]
|
#[Route('/api/1.0/person/accompanying-course-work-evaluation-document/{id}/duplicate', methods: ['POST'])]
|
||||||
@@ -56,6 +58,32 @@ class AccompanyingPeriodWorkEvaluationDocumentDuplicateController
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ParamConverter("document", options={"id": "document_id"})
|
||||||
|
* @ParamConverter("evaluation", options={"id": "evaluation_id"})
|
||||||
|
*/
|
||||||
|
#[Route('/api/1.0/person/accompanying-course-work-evaluation-document/{document_id}/evaluation/{evaluation_id}/duplicate', methods: ['POST'])]
|
||||||
|
public function duplicateToEvaluationApi(AccompanyingPeriodWorkEvaluationDocument $document, AccompanyingPeriodWorkEvaluation $evaluation): Response
|
||||||
|
{
|
||||||
|
$work = $evaluation->getAccompanyingPeriodWork();
|
||||||
|
|
||||||
|
if (!$this->security->isGranted(AccompanyingPeriodWorkVoter::UPDATE, $work)) {
|
||||||
|
throw new AccessDeniedHttpException('not allowed to edit this accompanying period work');
|
||||||
|
}
|
||||||
|
|
||||||
|
$duplicatedDocument = $this->duplicator->duplicateToEvaluation($document, $evaluation);
|
||||||
|
|
||||||
|
$this->entityManager->persist($duplicatedDocument);
|
||||||
|
$this->entityManager->persist($duplicatedDocument->getStoredObject());
|
||||||
|
$this->entityManager->persist($evaluation);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
return new JsonResponse(
|
||||||
|
$this->serializer->serialize($duplicatedDocument, 'json', [AbstractNormalizer::GROUPS => ['read']]),
|
||||||
|
json: true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[Route('/{_locale}/person/accompanying-course-work-evaluation-document/{id}/duplicate', name: 'chill_person_accompanying_period_work_evaluation_document_duplicate', methods: ['POST'])]
|
#[Route('/{_locale}/person/accompanying-course-work-evaluation-document/{id}/duplicate', name: 'chill_person_accompanying_period_work_evaluation_document_duplicate', methods: ['POST'])]
|
||||||
public function duplicate(AccompanyingPeriodWorkEvaluationDocument $document): Response
|
public function duplicate(AccompanyingPeriodWorkEvaluationDocument $document): Response
|
||||||
{
|
{
|
||||||
|
@@ -0,0 +1,58 @@
|
|||||||
|
<?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\PersonBundle\Controller;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
|
||||||
|
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||||
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
|
||||||
|
|
||||||
|
readonly class AccompanyingPeriodWorkEvaluationDocumentMoveController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private Security $security,
|
||||||
|
private SerializerInterface $serializer,
|
||||||
|
private EntityManagerInterface $entityManager,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ParamConverter("document", options={"id": "document_id"})
|
||||||
|
* @ParamConverter("evaluation", options={"id": "evaluation_id"})
|
||||||
|
*/
|
||||||
|
#[Route('/api/1.0/person/accompanying-course-work-evaluation-document/{document_id}/evaluation/{evaluation_id}/move', methods: ['POST'])]
|
||||||
|
public function moveToEvaluationApi(AccompanyingPeriodWorkEvaluationDocument $document, AccompanyingPeriodWorkEvaluation $evaluation): Response
|
||||||
|
{
|
||||||
|
$work = $evaluation->getAccompanyingPeriodWork();
|
||||||
|
|
||||||
|
if (!$this->security->isGranted(AccompanyingPeriodWorkVoter::UPDATE, $work)) {
|
||||||
|
throw new AccessDeniedHttpException('not allowed to edit this accompanying period work');
|
||||||
|
}
|
||||||
|
|
||||||
|
$document->setAccompanyingPeriodWorkEvaluation($evaluation);
|
||||||
|
|
||||||
|
$this->entityManager->persist($document);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
return new JsonResponse(
|
||||||
|
$this->serializer->serialize($document, 'json', [AbstractNormalizer::GROUPS => ['read']]),
|
||||||
|
json: true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -98,16 +98,6 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
|
|||||||
|
|
||||||
public function setAccompanyingPeriodWorkEvaluation(?AccompanyingPeriodWorkEvaluation $accompanyingPeriodWorkEvaluation): AccompanyingPeriodWorkEvaluationDocument
|
public function setAccompanyingPeriodWorkEvaluation(?AccompanyingPeriodWorkEvaluation $accompanyingPeriodWorkEvaluation): AccompanyingPeriodWorkEvaluationDocument
|
||||||
{
|
{
|
||||||
// if an evaluation is already associated, we cannot change the association (removing the association,
|
|
||||||
// by setting a null value, is allowed.
|
|
||||||
if (
|
|
||||||
$this->accompanyingPeriodWorkEvaluation instanceof AccompanyingPeriodWorkEvaluation
|
|
||||||
&& $accompanyingPeriodWorkEvaluation instanceof AccompanyingPeriodWorkEvaluation
|
|
||||||
) {
|
|
||||||
if ($this->accompanyingPeriodWorkEvaluation !== $accompanyingPeriodWorkEvaluation) {
|
|
||||||
throw new \RuntimeException('It is not allowed to change the evaluation for a document');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->accompanyingPeriodWorkEvaluation = $accompanyingPeriodWorkEvaluation;
|
$this->accompanyingPeriodWorkEvaluation = $accompanyingPeriodWorkEvaluation;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
@@ -24,7 +24,7 @@ class FindAccompanyingPeriodWorkType extends AbstractType
|
|||||||
{
|
{
|
||||||
$builder
|
$builder
|
||||||
->add('acpw', PickLinkedAccompanyingPeriodWorkType::class, [
|
->add('acpw', PickLinkedAccompanyingPeriodWorkType::class, [
|
||||||
'label' => 'Social action',
|
'label' => 'Accompanying period work',
|
||||||
'multiple' => false,
|
'multiple' => false,
|
||||||
'accompanyingPeriod' => $options['accompanyingPeriod'],
|
'accompanyingPeriod' => $options['accompanyingPeriod'],
|
||||||
])
|
])
|
||||||
|
@@ -16,18 +16,26 @@ use Symfony\Component\Form\AbstractType;
|
|||||||
use Symfony\Component\Form\FormInterface;
|
use Symfony\Component\Form\FormInterface;
|
||||||
use Symfony\Component\Form\FormView;
|
use Symfony\Component\Form\FormView;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
|
||||||
class PickLinkedAccompanyingPeriodWorkType extends AbstractType
|
class PickLinkedAccompanyingPeriodWorkType extends AbstractType
|
||||||
{
|
{
|
||||||
|
public function __construct(private readonly NormalizerInterface $normalizer) {}
|
||||||
|
|
||||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||||
{
|
{
|
||||||
$view->vars['multiple'] = $options['multiple'];
|
$view->vars['multiple'] = $options['multiple'];
|
||||||
$view->vars['types'] = ['acpw'];
|
$view->vars['types'] = ['acpw'];
|
||||||
$view->vars['uniqid'] = uniqid('pick_acpw_dyn');
|
$view->vars['uniqid'] = uniqid('pick_acpw_dyn');
|
||||||
|
$view->vars['suggested'] = [];
|
||||||
$view->vars['as_id'] = true === $options['as_id'] ? '1' : '0';
|
$view->vars['as_id'] = true === $options['as_id'] ? '1' : '0';
|
||||||
$view->vars['submit_on_adding_new_entity'] = false;
|
$view->vars['submit_on_adding_new_entity'] = false;
|
||||||
$view->vars['pick-entities-type'] = 'acpw';
|
$view->vars['pick-entities-type'] = 'acpw';
|
||||||
$view->vars['attr']['data-accompanying-period-id'] = $options['accompanyingPeriod']->getId();
|
$view->vars['attr']['data-accompanying-period-id'] = $options['accompanyingPeriod']->getId();
|
||||||
|
|
||||||
|
foreach ($options['suggested'] as $suggestion) {
|
||||||
|
$view->vars['suggested'][] = $this->normalizer->normalize($suggestion, 'json', ['groups' => 'read']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configureOptions(OptionsResolver $resolver)
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
@@ -38,6 +46,7 @@ class PickLinkedAccompanyingPeriodWorkType extends AbstractType
|
|||||||
->setDefault('multiple', false)
|
->setDefault('multiple', false)
|
||||||
->setAllowedTypes('multiple', ['bool'])
|
->setAllowedTypes('multiple', ['bool'])
|
||||||
->setDefault('compound', false)
|
->setDefault('compound', false)
|
||||||
|
->setDefault('suggested', [])
|
||||||
->setDefault('as_id', false)
|
->setDefault('as_id', false)
|
||||||
->setAllowedTypes('as_id', ['bool'])
|
->setAllowedTypes('as_id', ['bool'])
|
||||||
->setDefault('submit_on_adding_new_entity', false)
|
->setDefault('submit_on_adding_new_entity', false)
|
||||||
|
@@ -41,7 +41,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
methods: {
|
methods: {
|
||||||
pickWork: function (payload: { work: AccompanyingPeriodWork }) {
|
pickWork: function (payload: { work: AccompanyingPeriodWork }) {
|
||||||
console.log("payload", payload);
|
console.log("payload", payload);
|
||||||
input.value = payload.work.id.toString();
|
|
||||||
|
input.value = payload.work.id?.toString() ?? "";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@@ -84,7 +84,7 @@ export interface AccompanyingPeriodWorkEvaluationDocument {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AccompanyingPeriodWork {
|
export interface AccompanyingPeriodWork {
|
||||||
id: number;
|
id?: number;
|
||||||
accompanyingPeriod?: AccompanyingPeriod;
|
accompanyingPeriod?: AccompanyingPeriod;
|
||||||
accompanyingPeriodWorkEvaluations: AccompanyingPeriodWorkEvaluation[];
|
accompanyingPeriodWorkEvaluations: AccompanyingPeriodWorkEvaluation[];
|
||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
|
@@ -972,7 +972,7 @@ div#workEditor {
|
|||||||
font-size: 85%;
|
font-size: 85%;
|
||||||
}
|
}
|
||||||
|
|
||||||
i.fa {
|
& > i.fa {
|
||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
color: $white;
|
color: $white;
|
||||||
|
|
||||||
|
@@ -0,0 +1,31 @@
|
|||||||
|
<template>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label class="col-sm-4 col-form-label visually-hidden">{{
|
||||||
|
trans(EVALUATION_PUBLIC_COMMENT)
|
||||||
|
}}</label>
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<ckeditor
|
||||||
|
:editor="ClassicEditor"
|
||||||
|
:config="classicEditorConfig"
|
||||||
|
:placeholder="trans(EVALUATION_COMMENT_PLACEHOLDER)"
|
||||||
|
:value="comment"
|
||||||
|
@input="$emit('update:comment', $event)"
|
||||||
|
tag-name="textarea"
|
||||||
|
></ckeditor>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { Ckeditor } from "@ckeditor/ckeditor5-vue";
|
||||||
|
import { ClassicEditor } from "ckeditor5";
|
||||||
|
import classicEditorConfig from "ChillMainAssets/module/ckeditor5/editor_config";
|
||||||
|
import {
|
||||||
|
EVALUATION_PUBLIC_COMMENT,
|
||||||
|
EVALUATION_COMMENT_PLACEHOLDER,
|
||||||
|
trans,
|
||||||
|
} from "translator";
|
||||||
|
|
||||||
|
defineProps(["comment"]);
|
||||||
|
defineEmits(["update:comment"]);
|
||||||
|
</script>
|
@@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label class="col-4 col-sm-2 col-md-4 col-lg-2 col-form-label">
|
||||||
|
{{ trans(EVALUATION_STARTDATE) }}
|
||||||
|
</label>
|
||||||
|
<div class="col-8 col-sm-4 col-md-8 col-lg-4">
|
||||||
|
<input
|
||||||
|
class="form-control form-control-sm"
|
||||||
|
type="date"
|
||||||
|
:value="startDate"
|
||||||
|
@input="$emit('update:startDate', $event.target.value)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="col-4 col-sm-2 col-md-4 col-lg-2 col-form-label">
|
||||||
|
{{ trans(EVALUATION_ENDDATE) }}
|
||||||
|
</label>
|
||||||
|
<div class="col-8 col-sm-4 col-md-8 col-lg-4">
|
||||||
|
<input
|
||||||
|
class="form-control form-control-sm"
|
||||||
|
type="date"
|
||||||
|
:value="endDate"
|
||||||
|
@input="$emit('update:endDate', $event.target.value)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label class="col-4 col-sm-2 col-md-4 col-lg-2 col-form-label">
|
||||||
|
{{ trans(EVALUATION_MAXDATE) }}
|
||||||
|
</label>
|
||||||
|
<div class="col-8 col-sm-4 col-md-8 col-lg-4">
|
||||||
|
<input
|
||||||
|
class="form-control form-control-sm"
|
||||||
|
type="date"
|
||||||
|
:value="maxDate"
|
||||||
|
@input="$emit('update:maxDate', $event.target.value)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="col-4 col-sm-2 col-md-4 col-lg-2 col-form-label">
|
||||||
|
{{ trans(EVALUATION_WARNING_INTERVAL) }}
|
||||||
|
</label>
|
||||||
|
<div class="col-8 col-sm-4 col-md-8 col-lg-4">
|
||||||
|
<input
|
||||||
|
class="form-control form-control-sm"
|
||||||
|
type="number"
|
||||||
|
:value="warningInterval"
|
||||||
|
@input="$emit('update:warningInterval', $event.target.value)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {
|
||||||
|
EVALUATION_STARTDATE,
|
||||||
|
EVALUATION_ENDDATE,
|
||||||
|
EVALUATION_MAXDATE,
|
||||||
|
EVALUATION_WARNING_INTERVAL,
|
||||||
|
trans,
|
||||||
|
} from "translator";
|
||||||
|
|
||||||
|
defineProps(["startDate", "endDate", "maxDate", "warningInterval"]);
|
||||||
|
defineEmits([
|
||||||
|
"update:startDate",
|
||||||
|
"update:endDate",
|
||||||
|
"update:maxDate",
|
||||||
|
"update:warningInterval",
|
||||||
|
]);
|
||||||
|
</script>
|
@@ -0,0 +1,51 @@
|
|||||||
|
<template>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<h6>{{ trans(EVALUATION_DOCUMENT_ADD) }} :</h6>
|
||||||
|
<pick-template
|
||||||
|
entityClass="Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation"
|
||||||
|
:id="evaluation.id"
|
||||||
|
:templates="templates"
|
||||||
|
:preventDefaultMoveToGenerate="true"
|
||||||
|
@go-to-generate-document="$emit('submitBeforeGenerate', $event)"
|
||||||
|
>
|
||||||
|
<template v-slot:title>
|
||||||
|
<label class="col-form-label">{{
|
||||||
|
trans(EVALUATION_GENERATE_A_DOCUMENT)
|
||||||
|
}}</label>
|
||||||
|
</template>
|
||||||
|
</pick-template>
|
||||||
|
<div>
|
||||||
|
<label class="col-form-label">{{
|
||||||
|
trans(EVALUATION_DOCUMENT_UPLOAD)
|
||||||
|
}}</label>
|
||||||
|
<ul class="record_actions document-upload">
|
||||||
|
<li>
|
||||||
|
<drop-file-modal
|
||||||
|
:allow-remove="false"
|
||||||
|
@add-document="$emit('addDocument', $event)"
|
||||||
|
></drop-file-modal>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import PickTemplate from "ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue";
|
||||||
|
import DropFileModal from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFileModal.vue";
|
||||||
|
import {
|
||||||
|
EVALUATION_DOCUMENT_ADD,
|
||||||
|
EVALUATION_DOCUMENT_UPLOAD,
|
||||||
|
EVALUATION_GENERATE_A_DOCUMENT,
|
||||||
|
trans,
|
||||||
|
} from "translator";
|
||||||
|
|
||||||
|
defineProps(["evaluation", "templates"]);
|
||||||
|
defineEmits(["addDocument", "submitBeforeGenerate"]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
ul.document-upload {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
</style>
|
@@ -0,0 +1,361 @@
|
|||||||
|
<template>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<h5>{{ trans(EVALUATION_DOCUMENTS) }} :</h5>
|
||||||
|
<div class="flex-table">
|
||||||
|
<div
|
||||||
|
class="item-bloc"
|
||||||
|
v-for="(d, i) in documents"
|
||||||
|
:key="d.id"
|
||||||
|
:class="[
|
||||||
|
parseInt(docAnchorId) === d.id ? 'bg-blink' : 'nothing',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<div :id="'document_' + d.id" class="item-row">
|
||||||
|
<div class="input-group input-group-lg mb-3 row">
|
||||||
|
<label class="col-sm-3 col-form-label"
|
||||||
|
>Titre du document:</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input
|
||||||
|
class="form-control document-title"
|
||||||
|
type="text"
|
||||||
|
:value="d.title"
|
||||||
|
:id="d.id"
|
||||||
|
:data-key="i"
|
||||||
|
@input="$emit('inputDocumentTitle', $event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item-row">
|
||||||
|
<div class="item-col item-meta">
|
||||||
|
<p v-if="d.createdBy" class="createdBy">
|
||||||
|
Créé par {{ d.createdBy.text }}<br />
|
||||||
|
Le
|
||||||
|
{{
|
||||||
|
$d(ISOToDatetime(d.createdAt.datetime), "long")
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item-row">
|
||||||
|
<div class="item-col">
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li
|
||||||
|
v-if="
|
||||||
|
d.workflows_availables.length > 0 ||
|
||||||
|
d.workflows.length > 0
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<list-workflow-modal
|
||||||
|
:workflows="d.workflows"
|
||||||
|
:allowCreate="true"
|
||||||
|
relatedEntityClass="Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument"
|
||||||
|
:relatedEntityId="d.id"
|
||||||
|
:workflowsAvailables="
|
||||||
|
d.workflows_availables
|
||||||
|
"
|
||||||
|
:preventDefaultMoveToGenerate="true"
|
||||||
|
:goToGenerateWorkflowPayload="{ doc: d }"
|
||||||
|
@go-to-generate-workflow="
|
||||||
|
$emit('goToGenerateWorkflow', $event)
|
||||||
|
"
|
||||||
|
></list-workflow-modal>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
v-if="AmIRefferer"
|
||||||
|
class="btn btn-notify"
|
||||||
|
@click="
|
||||||
|
$emit(
|
||||||
|
'goToGenerateNotification',
|
||||||
|
d,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
></button>
|
||||||
|
<template v-else>
|
||||||
|
<button
|
||||||
|
id="btnGroupNotifyButtons"
|
||||||
|
type="button"
|
||||||
|
class="btn btn-notify dropdown-toggle"
|
||||||
|
:title="
|
||||||
|
trans(EVALUATION_NOTIFICATION_SEND)
|
||||||
|
"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
|
||||||
|
</button>
|
||||||
|
<ul
|
||||||
|
class="dropdown-menu"
|
||||||
|
aria-labelledby="btnGroupNotifyButtons"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
class="dropdown-item"
|
||||||
|
@click="
|
||||||
|
$emit(
|
||||||
|
'goToGenerateNotification',
|
||||||
|
d,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
trans(
|
||||||
|
EVALUATION_NOTIFICATION_NOTIFY_REFERRER,
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
class="dropdown-item"
|
||||||
|
@click="
|
||||||
|
$emit(
|
||||||
|
'goToGenerateNotification',
|
||||||
|
d,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
trans(
|
||||||
|
EVALUATION_NOTIFICATION_NOTIFY_ANY,
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<document-action-buttons-group
|
||||||
|
:stored-object="d.storedObject"
|
||||||
|
:filename="d.title"
|
||||||
|
:can-edit="true"
|
||||||
|
:execute-before-leave="
|
||||||
|
submitBeforeLeaveToEditor
|
||||||
|
"
|
||||||
|
:davLink="
|
||||||
|
d.storedObject._links?.dav_link.href
|
||||||
|
"
|
||||||
|
:davLinkExpiration="
|
||||||
|
d.storedObject._links?.dav_link
|
||||||
|
.expiration
|
||||||
|
"
|
||||||
|
@on-stored-object-status-change="
|
||||||
|
$emit('statusDocumentChanged', $event)
|
||||||
|
"
|
||||||
|
></document-action-buttons-group>
|
||||||
|
</li>
|
||||||
|
<li v-if="Number.isInteger(d.id)">
|
||||||
|
<div class="duplicate-dropdown">
|
||||||
|
<button
|
||||||
|
class="btn btn-edit dropdown-toggle"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
{{ trans(EVALUATION_DOCUMENT_EDIT) }}
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<!--delete-->
|
||||||
|
<li v-if="d.workflows.length === 0">
|
||||||
|
<a
|
||||||
|
class="dropdown-item"
|
||||||
|
@click="
|
||||||
|
$emit('removeDocument', d)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="fa fa-trash-o"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
{{
|
||||||
|
trans(
|
||||||
|
EVALUATION_DOCUMENT_DELETE,
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<!--replace document-->
|
||||||
|
<li
|
||||||
|
v-if="
|
||||||
|
d.storedObject._permissions
|
||||||
|
.canEdit
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<drop-file-modal
|
||||||
|
:existing-doc="d.storedObject"
|
||||||
|
:allow-remove="false"
|
||||||
|
@add-document="
|
||||||
|
(arg) =>
|
||||||
|
$emit(
|
||||||
|
'replaceDocument',
|
||||||
|
d,
|
||||||
|
arg.stored_object,
|
||||||
|
arg.stored_object_version,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
></drop-file-modal>
|
||||||
|
</li>
|
||||||
|
<!--duplicate document-->
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
class="dropdown-item"
|
||||||
|
@click="
|
||||||
|
$emit(
|
||||||
|
'duplicateDocument',
|
||||||
|
d,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="fa fa-copy"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
{{
|
||||||
|
trans(
|
||||||
|
EVALUATION_DOCUMENT_DUPLICATE_HERE,
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
class="dropdown-item"
|
||||||
|
@click="
|
||||||
|
prepareDocumentDuplicationToWork(
|
||||||
|
d,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="fa fa-copy"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
{{
|
||||||
|
trans(
|
||||||
|
EVALUATION_DOCUMENT_DUPLICATE_TO_OTHER_EVALUATION,
|
||||||
|
)
|
||||||
|
}}</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<!--move document-->
|
||||||
|
<li
|
||||||
|
v-if="
|
||||||
|
d.storedObject._permissions
|
||||||
|
.canEdit
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="dropdown-item"
|
||||||
|
@click="
|
||||||
|
prepareDocumentMoveToWork(d)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="fa fa-arrows"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
{{
|
||||||
|
trans(
|
||||||
|
EVALUATION_DOCUMENT_MOVE,
|
||||||
|
)
|
||||||
|
}}</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AccompanyingPeriodWorkSelectorModal
|
||||||
|
v-if="showAccompanyingPeriodSelector"
|
||||||
|
v-model:selectedAcpw="selectedAcpw"
|
||||||
|
:accompanying-period-id="accompanyingPeriodId"
|
||||||
|
:is-evaluation-selector="true"
|
||||||
|
:ignore-accompanying-period-work-ids="[]"
|
||||||
|
@close-modal="showAccompanyingPeriodSelector = false"
|
||||||
|
@update:selectedEvaluation="selectedEvaluation = $event"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ISOToDatetime } from "ChillMainAssets/chill/js/date";
|
||||||
|
import ListWorkflowModal from "ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflowModal.vue";
|
||||||
|
import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue";
|
||||||
|
import DropFileModal from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFileModal.vue";
|
||||||
|
import {
|
||||||
|
EVALUATION_NOTIFICATION_NOTIFY_REFERRER,
|
||||||
|
EVALUATION_NOTIFICATION_NOTIFY_ANY,
|
||||||
|
EVALUATION_NOTIFICATION_SEND,
|
||||||
|
EVALUATION_DOCUMENTS,
|
||||||
|
EVALUATION_DOCUMENT_MOVE,
|
||||||
|
EVALUATION_DOCUMENT_DELETE,
|
||||||
|
EVALUATION_DOCUMENT_EDIT,
|
||||||
|
EVALUATION_DOCUMENT_DUPLICATE_HERE,
|
||||||
|
EVALUATION_DOCUMENT_DUPLICATE_TO_OTHER_EVALUATION,
|
||||||
|
trans,
|
||||||
|
} from "translator";
|
||||||
|
import { ref, watch } from "vue";
|
||||||
|
import AccompanyingPeriodWorkSelectorModal from "ChillPersonAssets/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkSelectorModal.vue";
|
||||||
|
|
||||||
|
defineProps([
|
||||||
|
"documents",
|
||||||
|
"docAnchorId",
|
||||||
|
"accompanyingPeriodId",
|
||||||
|
"accompanyingPeriodWorkId",
|
||||||
|
]);
|
||||||
|
const emit = defineEmits([
|
||||||
|
"inputDocumentTitle",
|
||||||
|
"removeDocument",
|
||||||
|
"duplicateDocument",
|
||||||
|
"statusDocumentChanged",
|
||||||
|
"goToGenerateWorkflow",
|
||||||
|
"goToGenerateNotification",
|
||||||
|
"duplicateDocumentToWork",
|
||||||
|
]);
|
||||||
|
|
||||||
|
const showAccompanyingPeriodSelector = ref(false);
|
||||||
|
const selectedEvaluation = ref(null);
|
||||||
|
const selectedDocumentToDuplicate = ref(null);
|
||||||
|
const selectedDocumentToMove = ref(null);
|
||||||
|
|
||||||
|
const prepareDocumentDuplicationToWork = (d) => {
|
||||||
|
selectedDocumentToDuplicate.value = d;
|
||||||
|
/** ensure selectedDocumentToMove is null */
|
||||||
|
selectedDocumentToMove.value = null;
|
||||||
|
|
||||||
|
showAccompanyingPeriodSelector.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const prepareDocumentMoveToWork = (d) => {
|
||||||
|
selectedDocumentToMove.value = d;
|
||||||
|
/** ensure selectedDocumentToDuplicate is null */
|
||||||
|
selectedDocumentToDuplicate.value = null;
|
||||||
|
|
||||||
|
showAccompanyingPeriodSelector.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(selectedEvaluation, (val) => {
|
||||||
|
if (selectedDocumentToDuplicate.value) {
|
||||||
|
emit("duplicateDocumentToEvaluation", {
|
||||||
|
evaluation: val,
|
||||||
|
document: selectedDocumentToDuplicate.value,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
emit("moveDocumentToEvaluation", {
|
||||||
|
evaluationDest: val,
|
||||||
|
document: selectedDocumentToMove.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,32 @@
|
|||||||
|
<template>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label class="col-4 col-sm-2 col-md-4 col-lg-2 col-form-label">
|
||||||
|
{{ trans(EVALUATION_TIME_SPENT) }}
|
||||||
|
</label>
|
||||||
|
<div class="col-8 col-sm-4 col-md-8 col-lg-4">
|
||||||
|
<select
|
||||||
|
class="form-control form-control-sm"
|
||||||
|
:value="timeSpent"
|
||||||
|
@input="$emit('update:timeSpent', $event.target.value)"
|
||||||
|
>
|
||||||
|
<option disabled value="">
|
||||||
|
{{ trans(EVALUATION_TIME_SPENT) }}
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
v-for="time in timeSpentChoices"
|
||||||
|
:value="time.value"
|
||||||
|
:key="time.value"
|
||||||
|
>
|
||||||
|
{{ time.text }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { EVALUATION_TIME_SPENT, trans } from "translator";
|
||||||
|
|
||||||
|
defineProps(["timeSpent", "timeSpentChoices"]);
|
||||||
|
defineEmits(["update:timeSpent"]);
|
||||||
|
</script>
|
@@ -11,12 +11,13 @@ import { findSocialActionsBySocialIssue } from "ChillPersonAssets/vuejs/_api/Soc
|
|||||||
import { create } from "ChillPersonAssets/vuejs/_api/AccompanyingCourseWork.js";
|
import { create } from "ChillPersonAssets/vuejs/_api/AccompanyingCourseWork.js";
|
||||||
import { fetchResults, makeFetch } from "ChillMainAssets/lib/api/apiMethods.ts";
|
import { fetchResults, makeFetch } from "ChillMainAssets/lib/api/apiMethods.ts";
|
||||||
import { fetchTemplates } from "ChillDocGeneratorAssets/api/pickTemplate.js";
|
import { fetchTemplates } from "ChillDocGeneratorAssets/api/pickTemplate.js";
|
||||||
import { duplicate } from "../_api/accompanyingCourseWorkEvaluationDocument";
|
import {
|
||||||
|
duplicate,
|
||||||
|
duplicateDocumentToEvaluation,
|
||||||
|
moveDocumentToEvaluation,
|
||||||
|
} from "../_api/accompanyingCourseWorkEvaluationDocument";
|
||||||
|
|
||||||
const debug = process.env.NODE_ENV !== "production";
|
const debug = process.env.NODE_ENV !== "production";
|
||||||
const evalFQDN = encodeURIComponent(
|
|
||||||
"Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluation",
|
|
||||||
);
|
|
||||||
|
|
||||||
const store = createStore({
|
const store = createStore({
|
||||||
strict: debug,
|
strict: debug,
|
||||||
@@ -298,15 +299,47 @@ const store = createStore({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
addDuplicatedDocument(state, { document, evaluation_key }) {
|
addDuplicatedDocument(state, { document, evaluation_key }) {
|
||||||
console.log("add duplicated document", document);
|
|
||||||
console.log("add duplicated dcuemnt - evaluation key", evaluation_key);
|
|
||||||
|
|
||||||
let evaluation = state.evaluationsPicked.find(
|
let evaluation = state.evaluationsPicked.find(
|
||||||
(e) => e.key === evaluation_key,
|
(e) => e.key === evaluation_key,
|
||||||
);
|
);
|
||||||
document.key = evaluation.documents.length + 1;
|
document.key = evaluation.documents.length + 1;
|
||||||
evaluation.documents.splice(0, 0, document);
|
evaluation.documents.splice(0, 0, document);
|
||||||
},
|
},
|
||||||
|
addDuplicatedDocumentToEvaluation(state, { document, evaluation }) {
|
||||||
|
let evaluationDest = state.evaluationsPicked.find(
|
||||||
|
(e) => e.id === evaluation.id,
|
||||||
|
);
|
||||||
|
if (evaluationDest) {
|
||||||
|
console.log("add duplicated document to evaluation", evaluationDest);
|
||||||
|
document.key = evaluationDest.documents.length + 1;
|
||||||
|
evaluationDest.documents.splice(0, 0, document);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
moveDocumentToEvaluation(
|
||||||
|
state,
|
||||||
|
{ evaluationInitial, evaluationDest, document },
|
||||||
|
) {
|
||||||
|
let evaluationA = state.evaluationsPicked.find(
|
||||||
|
(e) => e.id === evaluationInitial.id,
|
||||||
|
);
|
||||||
|
let evaluationB = state.evaluationsPicked.find(
|
||||||
|
(e) => e.id === evaluationDest.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (evaluationB) {
|
||||||
|
// add document to chosen evaluation if evaluation is part of the same social work
|
||||||
|
document.key = evaluationB.documents.length + 1;
|
||||||
|
evaluationB.documents.splice(0, 0, document);
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove document from original evaluation
|
||||||
|
const indexToRemove = evaluationA.documents.findIndex(
|
||||||
|
(doc) => doc.id === document.id,
|
||||||
|
);
|
||||||
|
if (indexToRemove !== -1) {
|
||||||
|
evaluationA.documents.splice(indexToRemove, 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Replaces a document in the state with a new document.
|
* Replaces a document in the state with a new document.
|
||||||
*
|
*
|
||||||
@@ -603,6 +636,44 @@ const store = createStore({
|
|||||||
const newDoc = await duplicate(document.id);
|
const newDoc = await duplicate(document.id);
|
||||||
commit("addDuplicatedDocument", { document: newDoc, evaluation_key });
|
commit("addDuplicatedDocument", { document: newDoc, evaluation_key });
|
||||||
},
|
},
|
||||||
|
async duplicateDocumentToEvaluation({ commit }, { document, evaluation }) {
|
||||||
|
try {
|
||||||
|
const newDoc = await duplicateDocumentToEvaluation(
|
||||||
|
document.id,
|
||||||
|
evaluation.id,
|
||||||
|
);
|
||||||
|
commit("addDuplicatedDocumentToEvaluation", {
|
||||||
|
document: newDoc,
|
||||||
|
evaluation,
|
||||||
|
});
|
||||||
|
|
||||||
|
return newDoc;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to move document:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async moveDocumentToEvaluation(
|
||||||
|
{ commit },
|
||||||
|
{ evaluationInitial, evaluationDest, document },
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const response = await moveDocumentToEvaluation(
|
||||||
|
document.id,
|
||||||
|
evaluationDest.id,
|
||||||
|
);
|
||||||
|
commit("moveDocumentToEvaluation", {
|
||||||
|
evaluationInitial,
|
||||||
|
evaluationDest,
|
||||||
|
document,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to move document:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
removeDocument({ commit }, payload) {
|
removeDocument({ commit }, payload) {
|
||||||
commit("removeDocument", payload);
|
commit("removeDocument", payload);
|
||||||
},
|
},
|
||||||
|
@@ -9,3 +9,23 @@ export const duplicate = async (
|
|||||||
`/api/1.0/person/accompanying-course-work-evaluation-document/${id}/duplicate`,
|
`/api/1.0/person/accompanying-course-work-evaluation-document/${id}/duplicate`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const duplicateDocumentToEvaluation = async (
|
||||||
|
document_id: number,
|
||||||
|
evaluation_id: number,
|
||||||
|
): Promise<AccompanyingPeriodWorkEvaluationDocument> => {
|
||||||
|
return makeFetch<null, AccompanyingPeriodWorkEvaluationDocument>(
|
||||||
|
"POST",
|
||||||
|
`/api/1.0/person/accompanying-course-work-evaluation-document/${document_id}/evaluation/${evaluation_id}/duplicate`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const moveDocumentToEvaluation = async (
|
||||||
|
document_id: number,
|
||||||
|
evaluation_id: number,
|
||||||
|
): Promise<AccompanyingPeriodWorkEvaluationDocument> => {
|
||||||
|
return makeFetch<null, AccompanyingPeriodWorkEvaluationDocument>(
|
||||||
|
"POST",
|
||||||
|
`/api/1.0/person/accompanying-course-work-evaluation-document/${document_id}/evaluation/${evaluation_id}/move`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@@ -0,0 +1,70 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<div class="item-bloc">
|
||||||
|
<div class="item-row">
|
||||||
|
<h2 class="badge-title">
|
||||||
|
<span class="title_label"></span>
|
||||||
|
<span class="title_action">
|
||||||
|
<span>
|
||||||
|
{{ trans(EVALUATION) }}:
|
||||||
|
<span class="badge bg-light text-dark">
|
||||||
|
{{ eval?.evaluation?.title.fr }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<ul class="small_in_title columns mt-1">
|
||||||
|
<li>
|
||||||
|
<span class="item-key">
|
||||||
|
{{
|
||||||
|
trans(
|
||||||
|
ACCOMPANYING_COURSE_WORK_START_DATE,
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
:
|
||||||
|
</span>
|
||||||
|
<b>{{ formatDate(eval.startDate) }}</b>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li v-if="eval.endDate">
|
||||||
|
<span class="item-key">
|
||||||
|
{{
|
||||||
|
trans(ACCOMPANYING_COURSE_WORK_END_DATE)
|
||||||
|
}}
|
||||||
|
:
|
||||||
|
</span>
|
||||||
|
<b>{{ formatDate(eval.endDate) }}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
ACCOMPANYING_COURSE_WORK_END_DATE,
|
||||||
|
ACCOMPANYING_COURSE_WORK_START_DATE,
|
||||||
|
EVALUATION,
|
||||||
|
trans,
|
||||||
|
} from "translator";
|
||||||
|
import { ISOToDate } from "ChillMainAssets/chill/js/date";
|
||||||
|
import { DateTime } from "ChillMainAssets/types";
|
||||||
|
import { AccompanyingPeriodWorkEvaluation } from "../../../types";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const props = defineProps<{ eval: AccompanyingPeriodWorkEvaluation }>();
|
||||||
|
const formatDate = (dateObject: DateTime) => {
|
||||||
|
if (dateObject) {
|
||||||
|
const parsedDate = ISOToDate(dateObject.datetime);
|
||||||
|
if (parsedDate) {
|
||||||
|
return new Intl.DateTimeFormat("default", {
|
||||||
|
dateStyle: "short",
|
||||||
|
}).format(parsedDate);
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@@ -0,0 +1,47 @@
|
|||||||
|
<template>
|
||||||
|
<div class="results">
|
||||||
|
<div
|
||||||
|
v-for="evaluation in evaluations"
|
||||||
|
:key="evaluation.id"
|
||||||
|
class="list-item"
|
||||||
|
>
|
||||||
|
<label class="acpw-item">
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
:value="evaluation"
|
||||||
|
v-model="selectedEvaluation"
|
||||||
|
name="item"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<accompanying-period-work-evaluation-item :eval="evaluation" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { AccompanyingPeriodWorkEvaluation } from "../../../types";
|
||||||
|
import { defineProps, ref, watch } from "vue";
|
||||||
|
import AccompanyingPeriodWorkEvaluationItem from "ChillPersonAssets/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkEvaluationItem.vue";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const props = defineProps<{
|
||||||
|
evaluations: AccompanyingPeriodWorkEvaluation[];
|
||||||
|
}>();
|
||||||
|
const selectedEvaluation = ref<AccompanyingPeriodWorkEvaluation | null>(null);
|
||||||
|
|
||||||
|
// eslint-disable-next-line vue/valid-define-emits
|
||||||
|
const emit = defineEmits();
|
||||||
|
|
||||||
|
watch(selectedEvaluation, (newValue) => {
|
||||||
|
emit("update:selectedEvaluation", newValue);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.acpw-item {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
</style>
|
@@ -26,14 +26,24 @@ import AccompanyingPeriodWorkItem from "./AccompanyingPeriodWorkItem.vue";
|
|||||||
import { AccompanyingPeriodWork } from "../../../types";
|
import { AccompanyingPeriodWork } from "../../../types";
|
||||||
import { defineProps, ref, watch } from "vue";
|
import { defineProps, ref, watch } from "vue";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
accompanyingPeriodWorks: AccompanyingPeriodWork[];
|
accompanyingPeriodWorks: AccompanyingPeriodWork[];
|
||||||
|
selectedAcpw?: AccompanyingPeriodWork | null;
|
||||||
}>();
|
}>();
|
||||||
const selectedAcpw = ref<AccompanyingPeriodWork | null>(null);
|
const selectedAcpw = ref<AccompanyingPeriodWork | null>(
|
||||||
|
props.selectedAcpw ?? null,
|
||||||
|
);
|
||||||
|
|
||||||
// eslint-disable-next-line vue/valid-define-emits
|
const emit = defineEmits<{
|
||||||
const emit = defineEmits();
|
"update:selectedAcpw": [value: AccompanyingPeriodWork | null];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.selectedAcpw,
|
||||||
|
(val) => {
|
||||||
|
selectedAcpw.value = val ?? null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
watch(selectedAcpw, (newValue) => {
|
watch(selectedAcpw, (newValue) => {
|
||||||
emit("update:selectedAcpw", newValue);
|
emit("update:selectedAcpw", newValue);
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="row justify-content-end">
|
<div class="row justify-content-end" v-if="!isEvaluationSelector">
|
||||||
<div class="col-md-6 col-sm-10" v-if="selectedAcpw">
|
<div class="col-md-6 col-sm-10" v-if="selectedAcpw">
|
||||||
<ul class="list-suggest remove-items">
|
<ul class="list-suggest remove-items">
|
||||||
<li>
|
<li>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="record_actions">
|
<ul v-if="!showModal" class="record_actions">
|
||||||
<li>
|
<li>
|
||||||
<a class="btn btn-sm btn-create mt-3" @click="openModal">
|
<a class="btn btn-sm btn-create mt-3" @click="openModal">
|
||||||
{{ trans(ACPW_DUPLICATE_SELECT_ACCOMPANYING_PERIOD_WORK) }}
|
{{ trans(ACPW_DUPLICATE_SELECT_ACCOMPANYING_PERIOD_WORK) }}
|
||||||
@@ -40,9 +40,15 @@
|
|||||||
|
|
||||||
<template #body>
|
<template #body>
|
||||||
<accompanying-period-work-list
|
<accompanying-period-work-list
|
||||||
|
v-if="evaluations.length === 0"
|
||||||
:accompanying-period-works="accompanyingPeriodWorks"
|
:accompanying-period-works="accompanyingPeriodWorks"
|
||||||
v-model:selectedAcpw="selectedAcpw"
|
v-model:selectedAcpw="selectedAcpw"
|
||||||
/>
|
/>
|
||||||
|
<accompanying-period-work-evaluation-list
|
||||||
|
v-if="evaluations.length > 0"
|
||||||
|
:evaluations="evaluations"
|
||||||
|
v-model:selectedEvaluation="selectedEvaluation"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -60,58 +66,109 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from "vue";
|
import { ref, watch, onMounted } from "vue";
|
||||||
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
|
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
|
||||||
import AccompanyingPeriodWorkList from "./AccompanyingPeriodWorkList.vue";
|
import AccompanyingPeriodWorkList from "./AccompanyingPeriodWorkList.vue";
|
||||||
import { AccompanyingPeriodWork } from "../../../types";
|
import { AccompanyingPeriodWork } from "../../../types";
|
||||||
import {
|
import {
|
||||||
|
trans,
|
||||||
ACPW_DUPLICATE_SELECT_ACCOMPANYING_PERIOD_WORK,
|
ACPW_DUPLICATE_SELECT_ACCOMPANYING_PERIOD_WORK,
|
||||||
CONFIRM,
|
CONFIRM,
|
||||||
trans,
|
|
||||||
} from "translator";
|
} from "translator";
|
||||||
import { fetchResults } from "ChillMainAssets/lib/api/apiMethods";
|
import { fetchResults } from "ChillMainAssets/lib/api/apiMethods";
|
||||||
|
import AccompanyingPeriodWorkEvaluationList from "ChillPersonAssets/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkEvaluationList.vue";
|
||||||
interface AccompanyingPeriodWorkSelectorModalProps {
|
import { AccompanyingPeriodWorkEvaluation } from "../../../types";
|
||||||
accompanyingPeriodId: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedAcpw = ref<AccompanyingPeriodWork | null>(null);
|
const selectedAcpw = ref<AccompanyingPeriodWork | null>(null);
|
||||||
|
const selectedEvaluation = ref<AccompanyingPeriodWorkEvaluation | null>(null);
|
||||||
const showModal = ref(false);
|
const showModal = ref(false);
|
||||||
const accompanyingPeriodWorks = ref<AccompanyingPeriodWork[]>([]);
|
const accompanyingPeriodWorks = ref<AccompanyingPeriodWork[]>([]);
|
||||||
const props = defineProps<AccompanyingPeriodWorkSelectorModalProps>();
|
const evaluations = ref<AccompanyingPeriodWorkEvaluation[]>([]);
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
accompanyingPeriodId: string;
|
||||||
|
isEvaluationSelector: boolean;
|
||||||
|
ignoreAccompanyingPeriodWorkIds: number[];
|
||||||
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
pickWork: [payload: { work: AccompanyingPeriodWork | null }];
|
pickWork: [payload: { work: AccompanyingPeriodWork | null }];
|
||||||
|
closeModal: [];
|
||||||
|
"update:selectedEvaluation": [evaluation: AccompanyingPeriodWorkEvaluation];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.accompanyingPeriodId) {
|
if (props.accompanyingPeriodId) {
|
||||||
getAccompanyingPeriodWorks(props.accompanyingPeriodId);
|
getAccompanyingPeriodWorks(parseInt(props.accompanyingPeriodId));
|
||||||
} else {
|
} else {
|
||||||
console.error("No accompanyingperiod id was given");
|
console.error("No accompanyingperiod id was given");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showModal.value = true;
|
||||||
});
|
});
|
||||||
const getAccompanyingPeriodWorks = async (periodId: number) => {
|
const getAccompanyingPeriodWorks = async (periodId: number) => {
|
||||||
const url = `/api/1.0/person/accompanying-course/${periodId}/works.json`;
|
const url = `/api/1.0/person/accompanying-course/${periodId}/works.json`;
|
||||||
|
|
||||||
try {
|
const accompanyingPeriodWorksFetched =
|
||||||
accompanyingPeriodWorks.value = await fetchResults(url);
|
await fetchResults<AccompanyingPeriodWork>(url);
|
||||||
} catch (error) {
|
if (props.isEvaluationSelector) {
|
||||||
console.log(error);
|
accompanyingPeriodWorks.value = accompanyingPeriodWorksFetched.filter(
|
||||||
|
(acpw: AccompanyingPeriodWork) =>
|
||||||
|
acpw.accompanyingPeriodWorkEvaluations.length > 0 &&
|
||||||
|
typeof acpw.id !== "undefined" &&
|
||||||
|
!props.ignoreAccompanyingPeriodWorkIds.includes(acpw.id),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
accompanyingPeriodWorks.value = accompanyingPeriodWorksFetched;
|
||||||
}
|
}
|
||||||
/* makeFetch<number, AccompanyingPeriodWork[]>("GET", url)
|
|
||||||
.then((response) => {
|
|
||||||
accompanyingPeriodWorks.value = response;
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log(error);
|
|
||||||
});*/
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const openModal = () => (showModal.value = true);
|
watch(selectedAcpw, (newValue) => {
|
||||||
const closeModal = () => (showModal.value = false);
|
const inputField = document.getElementById(
|
||||||
|
"find_accompanying_period_work_acpw",
|
||||||
|
) as HTMLInputElement;
|
||||||
|
if (inputField) {
|
||||||
|
inputField.value = String(newValue?.id || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if (!props.isEvaluationSelector) {
|
||||||
|
console.log("Emitting from watch:", { work: newValue });
|
||||||
|
emit("pickWork", { work: newValue });
|
||||||
|
}*/
|
||||||
|
});
|
||||||
|
|
||||||
|
const openModal = () => {
|
||||||
|
showModal.value = true;
|
||||||
|
};
|
||||||
|
const closeModal = () => {
|
||||||
|
showModal.value = false;
|
||||||
|
selectedEvaluation.value = null;
|
||||||
|
// selectedAcpw.value = null;
|
||||||
|
emit("closeModal");
|
||||||
|
};
|
||||||
const confirmSelection = () => {
|
const confirmSelection = () => {
|
||||||
emit("pickWork", { work: selectedAcpw.value });
|
selectedAcpw.value = selectedAcpw.value;
|
||||||
closeModal();
|
console.log("selectedAcpw", selectedAcpw.value);
|
||||||
|
|
||||||
|
if (!props.isEvaluationSelector) {
|
||||||
|
if (selectedAcpw.value) {
|
||||||
|
// only emit if something is actually selected!
|
||||||
|
emit("pickWork", { work: selectedAcpw.value });
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
// optionally show some error or warning if not selected
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedAcpw.value && props.isEvaluationSelector) {
|
||||||
|
evaluations.value =
|
||||||
|
selectedAcpw.value.accompanyingPeriodWorkEvaluations;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedEvaluation.value && props.isEvaluationSelector) {
|
||||||
|
// console.log('evaluation log in modal', selectedEvaluation.value)
|
||||||
|
emit("update:selectedEvaluation", selectedEvaluation.value);
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
{%- macro details(w, accompanyingCourse, options) -%}
|
{%- macro details(w, accompanyingCourse, options) -%}
|
||||||
{% include '@ChillPerson/AccompanyingCourseWork/_item.html.twig' with {
|
{% include '@ChillPerson/AccompanyingCourseWork/_item.html.twig' with {
|
||||||
'displayAction': false,
|
'displayAction': true,
|
||||||
'displayContent': 'short',
|
'displayContent': 'short',
|
||||||
'displayFontSmall': true,
|
'displayFontSmall': true,
|
||||||
'itemBlocClass': '',
|
'itemBlocClass': '',
|
||||||
'displayNotification': false
|
'displayNotification': true
|
||||||
} %}
|
} %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
{% set activeRouteKey = 'chill_person_accompanying_period_work_assign_duplicate' %}
|
{% set activeRouteKey = 'chill_person_accompanying_period_work_assign_duplicate' %}
|
||||||
|
|
||||||
{% block title %}{{ 'Assign an accompanying period work duplicate' }}{% endblock %}
|
|
||||||
|
|
||||||
{% import '@ChillPerson/AccompanyingPeriodWorkDuplicate/_details.html.twig' as details %}
|
{% import '@ChillPerson/AccompanyingPeriodWorkDuplicate/_details.html.twig' as details %}
|
||||||
|
|
||||||
|
{% block title %}{{ 'Assign an accompanying period work duplicate' }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="person-duplicate">
|
<div class="person-duplicate">
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>{{ 'acpw_duplicate.Assign duplicate'|trans }}</h3>
|
<h1>{{ 'acpw_duplicate.Assign duplicate'|trans }}</h1>
|
||||||
{{ form_start(form) }}
|
{{ form_start(form) }}
|
||||||
{%- if form.acpw is defined -%}
|
{%- if form.acpw is defined -%}
|
||||||
{{ form_row(form.acpw) }}
|
{{ form_row(form.acpw) }}
|
||||||
|
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
|||||||
namespace Chill\PersonBundle\Service\AccompanyingPeriodWorkEvaluationDocument;
|
namespace Chill\PersonBundle\Service\AccompanyingPeriodWorkEvaluationDocument;
|
||||||
|
|
||||||
use Chill\DocStoreBundle\Service\StoredObjectDuplicate;
|
use Chill\DocStoreBundle\Service\StoredObjectDuplicate;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
|
||||||
use Symfony\Component\Clock\ClockInterface;
|
use Symfony\Component\Clock\ClockInterface;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
@@ -36,4 +37,17 @@ class AccompanyingPeriodWorkEvaluationDocumentDuplicator
|
|||||||
|
|
||||||
return $newDocument;
|
return $newDocument;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function duplicateToEvaluation(AccompanyingPeriodWorkEvaluationDocument $document, AccompanyingPeriodWorkEvaluation $evaluation): AccompanyingPeriodWorkEvaluationDocument
|
||||||
|
{
|
||||||
|
$newDocument = new AccompanyingPeriodWorkEvaluationDocument();
|
||||||
|
$newDocument
|
||||||
|
->setTitle($document->getTitle().' ('.$this->translator->trans('accompanying_course_evaluation_document.duplicated_at', ['at' => $this->clock->now()]).')')
|
||||||
|
->setStoredObject($this->storedObjectDuplicate->duplicate($document->getStoredObject()))
|
||||||
|
;
|
||||||
|
|
||||||
|
$evaluation->addDocument($newDocument);
|
||||||
|
|
||||||
|
return $newDocument;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1993,3 +1993,33 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
|
|
||||||
|
/1.0/person/accompanying-course-work-evaluation-document/{document_id}/evaluation/{evaluation_id}/duplicate:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- accompanying-course-work-evaluation-document
|
||||||
|
summary: Dupliate an an accompanying period work evaluation document to another evaluation
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: document_id
|
||||||
|
required: true
|
||||||
|
description: The document's id
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: integer
|
||||||
|
minimum: 1
|
||||||
|
- in: path
|
||||||
|
name: evaluation_id
|
||||||
|
required: true
|
||||||
|
description: The evaluation's id
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: integer
|
||||||
|
minimum: 1
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
@@ -753,6 +753,42 @@ evaluation:
|
|||||||
delay: Délai
|
delay: Délai
|
||||||
notificationDelay: Délai de notification
|
notificationDelay: Délai de notification
|
||||||
url: Lien internet
|
url: Lien internet
|
||||||
|
title: Ecrire une évaluation
|
||||||
|
status: Statut
|
||||||
|
choose_a_status: Choisir un statut
|
||||||
|
startdate: Date d'ouverture
|
||||||
|
enddate: Date de fin
|
||||||
|
maxdate: Date d'échéance
|
||||||
|
warning_interval: Rappel (jours)
|
||||||
|
public_comment: Note publique
|
||||||
|
comment_placeholder: Commencez à écrire ...
|
||||||
|
generate_a_document: Générer un document
|
||||||
|
choose_a_template: Choisir un modèle
|
||||||
|
add_a_document: Ajouter un document
|
||||||
|
add: Ajouter une évaluation
|
||||||
|
time_spent: Temps de rédaction
|
||||||
|
select_time_spent: Indiquez le temps de rédaction
|
||||||
|
Documents: Documents
|
||||||
|
document_add: Générer ou téléverser un document
|
||||||
|
document_upload: Téléverser un document
|
||||||
|
document_title: Titre du document
|
||||||
|
template_title: Nom du template
|
||||||
|
browse: Ajouter un document
|
||||||
|
replace: Remplacer
|
||||||
|
download: Télécharger le fichier existant
|
||||||
|
notification_notify_referrer: Notifier le référent
|
||||||
|
notification_notify_any: Notifier d'autres utilisateurs
|
||||||
|
notification_send: Envoyer une notification
|
||||||
|
document:
|
||||||
|
edit: Modifier
|
||||||
|
delete: Supprimer
|
||||||
|
move: Déplacer
|
||||||
|
duplicate: Dupliquer
|
||||||
|
duplicate_here: Dupliquer ici
|
||||||
|
duplicate_to_other_evaluation: Dupliquer vers une autre évaluation
|
||||||
|
duplicate_success: Le document d'évaluation a été dupliquer
|
||||||
|
move_success: Le document d'évaluation a été déplacer
|
||||||
|
|
||||||
|
|
||||||
goal:
|
goal:
|
||||||
desactivationDate: Date de désactivation
|
desactivationDate: Date de désactivation
|
||||||
@@ -777,7 +813,6 @@ relation:
|
|||||||
reverseTitle: Deuxième membre
|
reverseTitle: Deuxième membre
|
||||||
|
|
||||||
days: jours
|
days: jours
|
||||||
months: mois
|
|
||||||
years: années
|
years: années
|
||||||
|
|
||||||
# specific to closing motive
|
# specific to closing motive
|
||||||
@@ -1525,3 +1560,6 @@ my_parcours_filters:
|
|||||||
parcours_intervening: Intervenant
|
parcours_intervening: Intervenant
|
||||||
is_open: Parcours ouverts
|
is_open: Parcours ouverts
|
||||||
is_closed: Parcours clôturés
|
is_closed: Parcours clôturés
|
||||||
|
|
||||||
|
document_duplicate:
|
||||||
|
to_evaluation_success: "Le document a été dupliquer"
|
||||||
|
@@ -152,6 +152,17 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</dl>
|
</dl>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% block content_view_actions_merge %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ chill_path_add_return_path('chill_thirdparty_find_duplicate',
|
||||||
|
{ 'thirdparty_id': entity.id }) }}"
|
||||||
|
title="{{ 'Merge'|trans }}"
|
||||||
|
class="btn btn-misc">
|
||||||
|
<i class="bi bi-chevron-contract"></i>
|
||||||
|
{{ 'Merge'|trans }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endblock %}
|
||||||
{% block content_form_actions_delete %}{% endblock %}
|
{% block content_form_actions_delete %}{% endblock %}
|
||||||
{% block content_view_actions_duplicate_link %}{% endblock %}
|
{% block content_view_actions_duplicate_link %}{% endblock %}
|
||||||
{% endembed %}
|
{% endembed %}
|
||||||
|
Reference in New Issue
Block a user