Treat duplicate in backend and setup confirm page of merge

This commit is contained in:
Julie Lenaerts 2025-03-05 19:13:17 +01:00
parent 95adc29f9d
commit 6ded185289
9 changed files with 207 additions and 69 deletions

View File

@ -11,30 +11,43 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Controller; namespace Chill\PersonBundle\Controller;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Form\FindAccompanyingPeriodWorkType; use Chill\PersonBundle\Form\FindAccompanyingPeriodWorkType;
use Chill\PersonBundle\Form\PersonConfimDuplicateType;
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository; use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
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\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
class AccompanyingPeriodWorkDuplicateController extends AbstractController class AccompanyingPeriodWorkDuplicateController extends AbstractController
{ {
public function __construct(private readonly AccompanyingPeriodWorkRepository $accompanyingPeriodWorkRepository, private readonly TranslatorInterface $translator) {} public function __construct(private readonly AccompanyingPeriodWorkRepository $accompanyingPeriodWorkRepository, private readonly TranslatorInterface $translator, private readonly TranslatableStringHelperInterface $stringHelper, private readonly AccompanyingPeriodWorkMergeService $accompanyingPeriodWorkMergeService) {}
#[Route(path: '{_locale}/person/accompanying-period/work/{id}/assign-duplicate', name: 'chill_person_accompanying_period_work_assign_duplicate', methods: ['GET'])] /**
public function assignDuplicate(int $id, Request $request) * @ParamConverter("accompanyingPeriodWork", options={"id": "acpw_id"})
*/
#[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)
{ {
$acpw1= $this->accompanyingPeriodWorkRepository->find($id); $accompanyingPeriod = $acpw->getAccompanyingPeriod();
$accompanyingPeriod = $acpw1->getAccompanyingPeriod();
if (null === $acpw1) { $acpwList = $this->accompanyingPeriodWorkRepository->findByAccompanyingPeriod($accompanyingPeriod);
throw $this->createNotFoundException("Accompanying period work with id {$id} not".' found on this server');
} $acpwArray = array_map(function ($acpw) {
return [
'id' => $acpw->getId(),
'socialAction' => $this->stringHelper->localize($acpw->getSocialAction()->getTitle()),
'startDate' => $acpw->getStartDate(),
'endDate' => $acpw->getEndDate(),
];
}, $acpwList);
$this->denyAccessUnlessGranted( $this->denyAccessUnlessGranted(
'CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', 'CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE',
$acpw1, $acpw,
'You are not allowed to merge this accompanying period work' 'You are not allowed to merge this accompanying period work'
); );
@ -43,36 +56,83 @@ class AccompanyingPeriodWorkDuplicateController extends AbstractController
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
$acpw2 = $form->get('person')->getData(); $acpw2 = $this->accompanyingPeriodWorkRepository->find($form->get('acpw')->getData());
dump($acpw2);
// Validation: Ensure person1 is not the same as person2
if ($acpw1->getId() === $acpw2->getId()) {
$this->addFlash('error', $this->translator->trans('The entities you are trying to merge cannot be the same'));
return $this->redirectToRoute('chill_person_accompanying_period_work_show', ['id' => $acpw1->getId()]);
}
$direction = $form->get('direction')->getData(); $direction = $form->get('direction')->getData();
if ('starting' === $direction) { if ('starting' === $direction) {
$params = [ $params = [
'acpw1_id' => $acpw1->getId(), 'acpw1_id' => $acpw->getId(),
'acpw2_id' => $acpw2->getId(), 'acpw2_id' => $acpw2->getId(),
]; ];
} else { } else {
$params = [ $params = [
'acpw1_id' => $acpw2->getId(), 'acpw1_id' => $acpw2->getId(),
'acpw2_id' => $acpw1->getId(), 'acpw2_id' => $acpw->getId(),
]; ];
} }
// return $this->redirectToRoute('chill_person_acpw_duplicate_confirm', $params); return $this->redirectToRoute('chill_person_acpw_duplicate_confirm', $params);
} }
return $this->render('@ChillPerson/AccompanyingPeriodWorkDuplicate/assign_acpw_duplicate.html.twig', [ return $this->render('@ChillPerson/AccompanyingPeriodWorkDuplicate/assign_acpw_duplicate.html.twig', [
'accompanyingCourse' => $acpw1->getAccompanyingPeriod(), 'accompanyingCourse' => $acpw->getAccompanyingPeriod(),
'acpw' => $acpw1, 'acpwList' => $acpwArray,
'acpw' => $acpw,
'form' => $form->createView(), 'form' => $form->createView(),
]); ]);
} }
/**
* @ParamConverter("acpw1", options={"id": "acpw1_id"})
* @ParamConverter("acpw2", options={"id": "acpw2_id"})
*/
#[Route(path: '/{_locale}/person/{acpw1_id}/duplicate/{acpw2_id}/confirm', name: 'chill_person_acpw_duplicate_confirm')]
public function confirmAction(AccompanyingPeriodWork $acpw1, AccompanyingPeriodWork $acpw2, Request $request)
{
$accompanyingPeriod = $acpw1->getAccompanyingPeriod();
try {
$this->validateMerge($acpw1, $acpw2);
$form = $this->createForm(PersonConfimDuplicateType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->accompanyingPeriodWorkMergeService->merge($acpw1, $acpw2);
return $this->redirectToRoute('chill_person_accompanying_period_work_show', ['id' => $acpw1->getId()]);
}
return $this->render('@ChillPerson/AccompanyingPeriodWorkDuplicate/confirm.html.twig', [
'acpw' => $acpw1,
'acpw2' => $acpw2,
'accompanyingCourse' => $accompanyingPeriod,
'form' => $form->createView(),
]);
} catch (\InvalidArgumentException $e) {
$this->addFlash('error', $this->translator->trans($e->getMessage()));
return $this->redirectToRoute('chill_person_accompanying_period_work_assign_duplicate', [
'id' => $acpw1->getId(),
]);
}
}
private function validateMerge(AccompanyingPeriodWork $acpw1, AccompanyingPeriodWork $acpw2): void
{
$constraints = [
[$acpw1 === $acpw2, 'acpw_duplicate.You cannot merge a accompanying period work with itself. Please choose a different one'],
];
foreach ($constraints as [$condition, $message]) {
if ($condition) {
throw new \InvalidArgumentException($message);
}
}
}
} }

View File

@ -31,10 +31,9 @@ class FindAccompanyingPeriodWorkType extends AbstractType
$suggestedAcpw = $this->repository->findByAccompanyingPeriod($accompanyingPeriod); $suggestedAcpw = $this->repository->findByAccompanyingPeriod($accompanyingPeriod);
$builder $builder
->add('acpw', PickLinkedAccompanyingPeriodWorkType::class, [ ->add('acpw', HiddenType::class, [
'data' => null,
'label' => 'Accompanying period work', 'label' => 'Accompanying period work',
'suggested' => $suggestedAcpw,
'multiple' => false,
]) ])
->add('direction', HiddenType::class, [ ->add('direction', HiddenType::class, [
'data' => 'starting', 'data' => 'starting',

View File

@ -4,10 +4,10 @@
<label class="acpw-item"> <label class="acpw-item">
<div> <div>
<input type="radio" :value="acpw.id" v-model="selectedAcpw" name="item"/> <input type="radio" :value="acpw" v-model="selectedAcpw" name="item" />
</div> </div>
<accompanying-period-work-item v-for="acpw in acpwList" :acpw="acpw" /> <accompanying-period-work-item :acpw="acpw" />
</label> </label>
</div> </div>

View File

@ -1,5 +1,15 @@
<template> <template>
<div> <div>
<div class="row justify-content-end">
<div class="col-md-9 col-sm-12" v-if="selectedAcpw">
<ul class="list-suggest remove-items">
<li>
<span @click="selectedAcpw = null" class="chill-denomination">{{ trans(SOCIAL_ACTION) }}: {{ selectedAcpw.socialAction }}</span>
</li>
</ul>
</div>
</div>
<ul class="record_actions"> <ul 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">
@ -27,24 +37,32 @@
</modal> </modal>
</teleport> </teleport>
<input type="hidden" name="form[acpw]" :value="selectedAcpw" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref, watch } 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 { trans, ACPW_DUPLICATE_SELECT_ACCOMPANYING_PERIOD_WORK, CANCEL_1, CONFIRM } from "translator"; import { trans, ACPW_DUPLICATE_SELECT_ACCOMPANYING_PERIOD_WORK, SOCIAL_ACTION, CONFIRM } from "translator";
const props = defineProps<{ acpwList: AccompanyingPeriodWork[] }>(); const props = defineProps<{ acpwList: AccompanyingPeriodWork[] }>();
const selectedAcpw = ref(null); const selectedAcpw = ref(null);
const showModal = ref(false); const showModal = ref(false);
watch(selectedAcpw, (newValue: AccompanyingPeriodWork) => {
const inputField = document.getElementById('find_accompanying_period_work_acpw')
console.log(newValue.id)
if (inputField) {
inputField.value = newValue.id
}
});
const openModal = () => (showModal.value = true); const openModal = () => (showModal.value = true);
const closeModal = () => (showModal.value = false); const closeModal = () => (showModal.value = false);
const confirmSelection = () => { const confirmSelection = () => {
selectedAcpw.value = selectedAcpw.value;
closeModal(); closeModal();
}; };
</script> </script>

View File

@ -0,0 +1,12 @@
{%- macro details(acpw, options) -%}
<ul>
<li><b>{{ 'person_admin.social_action'|trans }}</b>:
{{ acpw.socialAction.title|localize_translatable_string }}</li>
<li><b>{{ 'export.list.acpw.startDate'|trans }}</b>:
{% if acpw.startDate %}{{ acpw.startDate|format_date('short') }}{% endif %}</li>
{% if acpw.endDate %}
<li><b>{{ 'export.list.acpw.endDate'|trans }}</b>:
{{ acpw.endDate|format_date('short') }}</li>
{% endif %}
</ul>
{% endmacro %}

View File

@ -4,13 +4,15 @@
{% block title %}{{ 'Assign an accompanying period work duplicate' }}{% endblock %} {% block title %}{{ 'Assign an accompanying period work duplicate' }}{% endblock %}
{% block content %} {% block content %}
<div class="person-duplicate"> <div class="person-duplicate">
<h1>{{ 'Assign an accompanying period work duplicate'|trans }}</h1> <h1>{{ 'acpw_duplicate.Assign duplicate'|trans }}</h1>
{{ form_start(form) }} {{ form_start(form) }}
{%- if form.acpw is defined -%}
{{ form_row(form.acpw) }}
<div id="linked-acpw-selector" data-acpw-list='{{ acpwList|json_encode|raw }}'></div>
{% endif %}
{{ form_rest(form) }} {{ form_rest(form) }}
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">
@ -30,9 +32,9 @@
{% endblock %} {% endblock %}
{% block js %} {% block js %}
{{ encore_entry_script_tags('mod_pickentity_type') }} {{ encore_entry_script_tags('mod_duplicate_selector') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ encore_entry_link_tags('mod_pickentity_type') }} {{ encore_entry_link_tags('mod_duplicate_selector') }}
{% endblock %} {% endblock %}

View File

@ -0,0 +1,77 @@
{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
{% import '@ChillPerson/AccompanyingPeriodWorkDuplicate/_details.html.twig' as details %}
{% block title %}{{ 'acpw_duplicate.title'|trans }}{% endblock %}
{% block content %}
<div class="container-fluid content"><div class="duplicate-content">
<h1>{{ 'acpw_duplicate.title'|trans }}</h1>
<div class="col-md-11">
<p><b>{{ 'acpw_duplicate.Accompanying period work to delete'|trans }}</b>:
{{ 'acpw_duplicate.Accompanying period work to delete explanation'|trans }}
</p>
<div class="col">
<h1><span><a class="btn btn-show" target="_blank" title="{{ 'Open in another window'|trans }}" href="{{ path('chill_crud_3party_3party_view', { id : acpw2.id }) }}"></a></span>
</h1>
<h4>{{ 'Deleted datas'|trans ~ ':' }}</h4>
{{ details.details(acpw2, accompanyingCourse) }}
</div>
</div>
<div class="col-md-11 mt-3">
<p><b>{{ 'acpw_duplicate.Accompanying period work to keep'|trans }}</b>:
{{ 'acpw_duplicate.Accompanying period work to keep explanation'|trans }}
</p>
<div class="col border">
<h1><span><a class="btn btn-show" target="_blank" title="{{ 'Open in another window'|trans }}" href="{{ path('chill_person_accompanying_period_work_show', { id : acpw.id }) }}"></a></span>
</h1>
<h4>{{ 'acpw_duplicate.Data to keep'|trans ~ ':' }}</h4>
{{ details.details(acpw, accompanyingCourse) }}
</div>
</div>
{{ form_start(form) }}
<div class="col-md-12 centered">
<div class="container-fluid" style="padding-top: 1em;">
<div class="clear" style="padding-top: 10px;">
{{ form_widget(form.confirm) }}
</div>
<div class="col-11">
{{ form_label(form.confirm) }}
</div>
</div>
</div>
<ul class="col-12 record_actions">
<li class="cancel">
<a href="{{ path('chill_person_accompanying_period_work_assign_duplicate', {id : acpw.id}) }}" class="btn btn-chill-gray center margin-5">
{{ 'Return'|trans }}
</a>
</li>
<li class="cancel">
<a href="{{ path('chill_person_acpw_duplicate_confirm', { acpw1_id : acpw2.id, acpw2_id : acpw.id }) }}"
class="btn btn-action">
<i class="fa fa-exchange"></i>
{{ 'Invert'|trans }}
</a>
</li>
<li>
<button class="btn btn-submit" type="submit"><i class="fa fa-cog fa-fw"></i>{{ 'Merge'|trans }}</button>
</li>
</ul>
{{ form_end(form) }}
</div></div>
{% endblock %}

View File

@ -25,43 +25,13 @@ class AccompanyingPeriodWorkMergeService
*/ */
public function merge(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete): void public function merge(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete): void
{ {
// Transfer non-duplicate data
$this->transferData($toKeep, $toDelete);
// Update linked entities
$this->updateReferences($toKeep, $toDelete); $this->updateReferences($toKeep, $toDelete);
$this->em->remove($toDelete); $this->em->remove($toDelete);
$this->em->flush(); $this->em->flush();
} }
/** /* private function generateStartDateSQL(): string
* @throws Exception
*/
private function transferData(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete): void
{
$conn = $this->em->getConnection();
$sqlStatements = [
$this->generateStartDateSQL(),
$this->generateEndDateSQL(),
$this->generateCommentSQL(),
];
$conn->beginTransaction();
try {
foreach ($sqlStatements as $sql) {
$conn->executeQuery($sql, ['toDelete' => $toDelete->getId(), 'toKeep' => $toKeep->getId()]);
}
$conn->commit();
} catch (\Exception $e) {
$conn->rollBack();
throw $e;
}
}
private function generateStartDateSQL(): string
{ {
return ' return '
UPDATE chill_person_accompanying_period_work UPDATE chill_person_accompanying_period_work
@ -99,7 +69,7 @@ class AccompanyingPeriodWorkMergeService
NULLIF(TRIM((SELECT note FROM chill_person_accompanying_period_work WHERE id = :toDelete)), '') NULLIF(TRIM((SELECT note FROM chill_person_accompanying_period_work WHERE id = :toDelete)), '')
) )
WHERE id = :toKeep"; WHERE id = :toKeep";
} }*/
/** /**
* @throws Exception * @throws Exception

View File

@ -1497,4 +1497,4 @@ entity_display_title:
acpw_duplicate: acpw_duplicate:
Select accompanying period work: Selectionner un action d'accompagnement Select accompanying period work: Selectionner un action d'accompagnement
Assign duplicate: Assigner un action d'accompagnement doublon Assign duplicate: signer un action d'accompagnement doublon