mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Merge remote-tracking branch 'origin/355-fusion-thirdparty' into testing-202505
This commit is contained in:
commit
4d1032c115
@ -57,6 +57,7 @@ import {
|
|||||||
THIRDPARTY_A_COMPANY,
|
THIRDPARTY_A_COMPANY,
|
||||||
PERSON,
|
PERSON,
|
||||||
THIRDPARTY,
|
THIRDPARTY,
|
||||||
|
THIRDPARTY_CONTACT,
|
||||||
} from "translator";
|
} from "translator";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
{% endblock crud_content_header %}
|
{% endblock crud_content_header %}
|
||||||
|
|
||||||
{% block crud_content_view %}
|
{% block crud_content_view %}
|
||||||
|
|
||||||
{% block crud_content_view_details %}
|
{% block crud_content_view_details %}
|
||||||
<dl class="chill_view_data">
|
<dl class="chill_view_data">
|
||||||
<dt>id</dt>
|
<dt>id</dt>
|
||||||
@ -20,7 +20,7 @@
|
|||||||
{{ 'Cancel'|trans }}
|
{{ 'Cancel'|trans }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content_view_actions_before %}{% endblock %}
|
{% block content_view_actions_before %}{% endblock %}
|
||||||
{% block content_form_actions_delete %}
|
{% block content_form_actions_delete %}
|
||||||
{% if chill_crud_action_exists(crud_name, 'delete') %}
|
{% if chill_crud_action_exists(crud_name, 'delete') %}
|
||||||
@ -32,7 +32,7 @@
|
|||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock content_form_actions_delete %}
|
{% endblock content_form_actions_delete %}
|
||||||
{% block content_view_actions_duplicate_link %}
|
{% block content_view_actions_duplicate_link %}
|
||||||
{% if chill_crud_action_exists(crud_name, 'new') %}
|
{% if chill_crud_action_exists(crud_name, 'new') %}
|
||||||
{% if is_granted(chill_crud_config('role', crud_name, 'new'), entity) %}
|
{% if is_granted(chill_crud_config('role', crud_name, 'new'), entity) %}
|
||||||
@ -44,6 +44,17 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock content_view_actions_duplicate_link %}
|
{% endblock content_view_actions_duplicate_link %}
|
||||||
|
{% 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_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) %}
|
||||||
|
@ -0,0 +1,125 @@
|
|||||||
|
<?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\ThirdPartyBundle\Controller;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Form\PersonConfimDuplicateType;
|
||||||
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
use Chill\ThirdPartyBundle\Form\ThirdpartyFindDuplicateType;
|
||||||
|
use Chill\ThirdPartyBundle\Service\ThirdpartyMergeService;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Session;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
|
||||||
|
use Symfony\Component\Translation\TranslatableMessage;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
|
class ThirdpartyDuplicateController extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(private readonly ThirdpartyMergeService $thirdPartyMergeService, private readonly TranslatorInterface $translator) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ParamConverter("thirdparty", options={"id": "thirdparty_id"})
|
||||||
|
*/
|
||||||
|
#[Route(path: '/{_locale}/3party/{thirdparty_id}/find-manually', name: 'chill_thirdparty_find_duplicate')]
|
||||||
|
public function findManuallyDuplicateAction(ThirdParty $thirdparty, Request $request)
|
||||||
|
{
|
||||||
|
$suggested = [];
|
||||||
|
|
||||||
|
if ('child' === $thirdparty->getKind()) {
|
||||||
|
$suggested = $thirdparty->getParent()->getChildren();
|
||||||
|
}
|
||||||
|
|
||||||
|
$form = $this->createForm(ThirdpartyFindDuplicateType::class, null, ['suggested' => $suggested]);
|
||||||
|
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$thirdparty2 = $form->get('thirdparty')->getData();
|
||||||
|
|
||||||
|
$direction = $form->get('direction')->getData();
|
||||||
|
|
||||||
|
if ('starting' === $direction) {
|
||||||
|
$params = [
|
||||||
|
'thirdparty1_id' => $thirdparty->getId(),
|
||||||
|
'thirdparty2_id' => $thirdparty2->getId(),
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$params = [
|
||||||
|
'thirdparty1_id' => $thirdparty2->getId(),
|
||||||
|
'thirdparty2_id' => $thirdparty->getId(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirectToRoute('chill_thirdparty_duplicate_confirm', $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('@ChillThirdParty/ThirdPartyDuplicate/find_duplicate.html.twig', [
|
||||||
|
'thirdparty' => $thirdparty,
|
||||||
|
'form' => $form->createView(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ParamConverter("thirdparty1", options={"id": "thirdparty1_id"})
|
||||||
|
* @ParamConverter("thirdparty2", options={"id": "thirdparty2_id"})
|
||||||
|
*/
|
||||||
|
#[Route(path: '/{_locale}/3party/{thirdparty1_id}/duplicate/{thirdparty2_id}/confirm', name: 'chill_thirdparty_duplicate_confirm')]
|
||||||
|
public function confirmAction(ThirdParty $thirdparty1, ThirdParty $thirdparty2, Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validateThirdpartyMerge($thirdparty1, $thirdparty2);
|
||||||
|
$form = $this->createForm(PersonConfimDuplicateType::class);
|
||||||
|
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
|
||||||
|
$this->thirdPartyMergeService->merge($thirdparty1, $thirdparty2);
|
||||||
|
|
||||||
|
$session = $request->getSession();
|
||||||
|
if ($session instanceof Session) {
|
||||||
|
$session->getFlashBag()->add('success', new TranslatableMessage('thirdparty_duplicate.Merge successful'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirectToRoute('chill_crud_3party_3party_view', ['id' => $thirdparty1->getId()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('@ChillThirdParty/ThirdPartyDuplicate/confirm.html.twig', [
|
||||||
|
'thirdparty' => $thirdparty1,
|
||||||
|
'thirdparty2' => $thirdparty2,
|
||||||
|
'form' => $form->createView(),
|
||||||
|
]);
|
||||||
|
} catch (\InvalidArgumentException $e) {
|
||||||
|
$this->addFlash('error', $this->translator->trans($e->getMessage()));
|
||||||
|
|
||||||
|
return $this->redirectToRoute('chill_thirdparty_find_duplicate', [
|
||||||
|
'thirdparty_id' => $thirdparty1->getId(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validateThirdpartyMerge(ThirdParty $thirdparty1, ThirdParty $thirdparty2): void
|
||||||
|
{
|
||||||
|
$constraints = [
|
||||||
|
[$thirdparty1 === $thirdparty2, 'thirdparty_duplicate.You cannot merge a thirdparty with itself. Please choose a different thirdparty'],
|
||||||
|
[$thirdparty1->getKind() !== $thirdparty2->getKind(), 'thirdparty_duplicate.A thirdparty can only be merged with a thirdparty of the same kind'],
|
||||||
|
[$thirdparty1->getParent() !== $thirdparty2->getParent(), 'thirdparty_duplicate.Two child thirdparties must have the same parent'],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($constraints as [$condition, $message]) {
|
||||||
|
if ($condition) {
|
||||||
|
throw new \InvalidArgumentException($message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\ThirdPartyBundle\Form;
|
||||||
|
|
||||||
|
use Chill\ThirdPartyBundle\Form\Type\PickThirdpartyDynamicType;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
class ThirdpartyFindDuplicateType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('thirdparty', PickThirdpartyDynamicType::class, [
|
||||||
|
'label' => 'Find duplicate',
|
||||||
|
'mapped' => false,
|
||||||
|
'suggested' => $options['suggested'],
|
||||||
|
])
|
||||||
|
->add('direction', HiddenType::class, [
|
||||||
|
'data' => 'starting',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'suggested' => [],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -171,7 +171,13 @@
|
|||||||
<a class="btn btn-sm btn-show" target="_blank" title="{{ 'Show thirdparty'|trans }}"
|
<a class="btn btn-sm btn-show" target="_blank" title="{{ 'Show thirdparty'|trans }}"
|
||||||
href="{{ path('chill_crud_3party_3party_view', { id: thirdparty.isChild ? thirdparty.parent.id : thirdparty.id }) }}"></a>
|
href="{{ path('chill_crud_3party_3party_view', { id: thirdparty.isChild ? thirdparty.parent.id : thirdparty.id }) }}"></a>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% elseif is_granted('CHILL_3PARTY_3PARTY_UPDATE', thirdparty) %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ chill_path_add_return_path('chill_thirdparty_find_duplicate',
|
||||||
|
{ 'thirdparty_id': thirdparty.id }) }}"
|
||||||
|
title="{{ 'Merge'|trans }}"
|
||||||
|
class="btn btn-misc"><i class="bi bi-chevron-contract"></i></a>
|
||||||
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if options['customButtons']['after'] is defined %}
|
{% if options['customButtons']['after'] is defined %}
|
||||||
|
@ -128,7 +128,7 @@
|
|||||||
<div class="flex-table">
|
<div class="flex-table">
|
||||||
{% for tp in thirdParty.activeChildren %}
|
{% for tp in thirdParty.activeChildren %}
|
||||||
<div class="item-bloc">
|
<div class="item-bloc">
|
||||||
{{ tp|chill_entity_render_box({'render': 'bloc', 'addLink': false, 'isConfidential': tp.contactDataAnonymous ? true : false }) }}
|
{{ tp|chill_entity_render_box({'render': 'bloc', 'addLink': false, 'isConfidential': tp.contactDataAnonymous ? true : false, 'showFusion': true }) }}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
{%- macro details(thirdparty, options) -%}
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><b>{{ 'name'|trans }}</b>:
|
||||||
|
{{ thirdparty.name }}</li>
|
||||||
|
<li><b>{{ 'First name'|trans }}</b>:
|
||||||
|
{% if thirdparty.firstname %}{{ thirdparty.firstname }}{% endif %}</li>
|
||||||
|
<li><b>{{ 'thirdparty.Civility'|trans }}</b>:
|
||||||
|
{% if thirdparty.getCivility %}{{ thirdparty.getCivility.name|localize_translatable_string }}{% endif %}</li>
|
||||||
|
<li><b>{{ 'thirdparty.NameCompany'|trans }}</b>:
|
||||||
|
{% if thirdparty.nameCompany is not empty %}{{ thirdparty.nameCompany }}{% endif %}</li>
|
||||||
|
<li><b>{{ 'thirdparty.Acronym'|trans }}</b>:
|
||||||
|
{% if thirdparty.acronym %}{{ thirdparty.acronym }}{% endif %}</li>
|
||||||
|
<li><b>{{ 'thirdparty.Profession'|trans }}</b>:
|
||||||
|
{% if thirdparty.profession %}{{ thirdparty.profession }}{% endif %}</li>
|
||||||
|
<li><b>{{ 'telephone'|trans }}</b>:
|
||||||
|
{% if thirdparty.telephone %}{{ thirdparty.telephone }}{% endif %}</li>
|
||||||
|
<li><b>{{ 'email'|trans }}</b>:
|
||||||
|
{% if thirdparty.email is not null %}{{ thirdparty.email }}{% endif %}</li>
|
||||||
|
<li><b>{{ 'Address'|trans }}</b>:
|
||||||
|
{%- if thirdparty.getAddress is not empty -%}
|
||||||
|
{{ thirdparty.getAddress|chill_entity_render_box }}
|
||||||
|
{% endif %}</li>
|
||||||
|
<li><b>{{ 'thirdparty.Contact data are confidential'|trans }}</b>:
|
||||||
|
{{ thirdparty.contactDataAnonymous }}</li>
|
||||||
|
<li><b>{{ 'Contacts'|trans }}</b>:
|
||||||
|
<ul>
|
||||||
|
{% for c in thirdparty.getChildren %}
|
||||||
|
<li>{{ c.name }} {{ c.firstName }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{% endmacro %}
|
@ -0,0 +1,97 @@
|
|||||||
|
{% extends "@ChillMain/layout.html.twig" %}
|
||||||
|
|
||||||
|
{% import '@ChillThirdParty/ThirdPartyDuplicate/_details.html.twig' as details %}
|
||||||
|
|
||||||
|
{% block title %}{{ 'thirdparty_duplicate.Thirdparty duplicate title'|trans ~ ' ' ~ thirdparty.name }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<style>
|
||||||
|
div.duplicate-content {
|
||||||
|
margin: 0 2rem;
|
||||||
|
}
|
||||||
|
div.col {
|
||||||
|
padding: 1em;
|
||||||
|
border: 3px solid #cccccc;
|
||||||
|
}
|
||||||
|
div.border {
|
||||||
|
border: 4px solid #3c9f8d;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<div class="container-fluid content"><div class="duplicate-content">
|
||||||
|
|
||||||
|
<h1>{{ 'thirdparty_duplicate.title'|trans }}</h1>
|
||||||
|
|
||||||
|
<div class="col-md-11">
|
||||||
|
<p><b>{{ 'thirdparty_duplicate.Thirdparty to delete'|trans }}</b>:
|
||||||
|
{{ 'thirdparty_duplicate.Thirdparty 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 : thirdparty2.id }) }}"></a></span>
|
||||||
|
{{ thirdparty2 }}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<h4>{{ 'Deleted datas'|trans ~ ':' }}</h4>
|
||||||
|
{{ details.details(thirdparty2) }}
|
||||||
|
|
||||||
|
{# <h4>{{ 'Moved links'|trans ~ ':' }}</h4>#}
|
||||||
|
{# {{ details.links(thirdparty2) }}#}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-11 mt-3">
|
||||||
|
<p><b>{{ 'thirdparty_duplicate.Thirdparty to keep'|trans }}</b>:
|
||||||
|
{{ 'thirdparty_duplicate.Thirdparty 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_crud_3party_3party_view', { id : thirdparty.id }) }}"></a></span>
|
||||||
|
{{ thirdparty }}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<h4>{{ 'thirdparty_duplicate.Data to keep'|trans ~ ':' }}</h4>
|
||||||
|
{{ details.details(thirdparty) }}
|
||||||
|
|
||||||
|
{# <h4>{{ 'thirdparty_duplicate.links to keep'|trans ~ ':' }}</h4>#}
|
||||||
|
{# {{ sidepane.links(thirdparty) }}#}
|
||||||
|
</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_thirdparty_find_duplicate', {thirdparty_id : thirdparty.id}) }}" class="btn btn-chill-gray center margin-5">
|
||||||
|
{{ 'Return'|trans }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="cancel">
|
||||||
|
<a href="{{ path('chill_thirdparty_duplicate_confirm', { thirdparty1_id : thirdparty2.id, thirdparty2_id : thirdparty.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 %}
|
@ -0,0 +1,38 @@
|
|||||||
|
{% extends "@ChillMain/layout.html.twig" %}
|
||||||
|
|
||||||
|
{% set activeRouteKey = 'chill_thirdparty_duplicate' %}
|
||||||
|
|
||||||
|
{% block title %}{{ 'thirdparty_duplicate.find'|trans ~ ' ' ~ thirdparty.name|capitalize }}{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="person-duplicate">
|
||||||
|
|
||||||
|
<h1>{{ 'thirdparty_duplicate.find'|trans }}</h1>
|
||||||
|
|
||||||
|
{{ form_start(form) }}
|
||||||
|
{{ form_rest(form) }}
|
||||||
|
|
||||||
|
<ul class="record_actions sticky-form-buttons">
|
||||||
|
<li class="cancel">
|
||||||
|
<a href="{{ path('chill_crud_3party_3party_view', {'id' : thirdparty.id}) }}" class="btn btn-cancel">
|
||||||
|
{{ 'Return'|trans }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button class="btn btn-save" type="submit">{{ 'Next'|trans }}</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{{ form_end(form) }}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
{{ encore_entry_script_tags('mod_pickentity_type') }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
{{ encore_entry_link_tags('mod_pickentity_type') }}
|
||||||
|
{% endblock %}
|
@ -0,0 +1,107 @@
|
|||||||
|
<?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\ThirdPartyBundle\Service;
|
||||||
|
|
||||||
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||||
|
|
||||||
|
class ThirdpartyMergeService
|
||||||
|
{
|
||||||
|
public function __construct(private readonly EntityManagerInterface $em) {}
|
||||||
|
|
||||||
|
public function merge(ThirdParty $toKeep, ThirdParty $toDelete): void
|
||||||
|
{
|
||||||
|
$conn = $this->em->getConnection();
|
||||||
|
$conn->beginTransaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$queries = [
|
||||||
|
...$this->updateReferences($toKeep, $toDelete),
|
||||||
|
...$this->removeThirdparty($toKeep, $toDelete),
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($queries as $query) {
|
||||||
|
$conn->executeStatement($query['sql'], $query['params']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->commit();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$conn->rollBack();
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateReferences(ThirdParty $toKeep, ThirdParty $toDelete): array
|
||||||
|
{
|
||||||
|
$queries = [];
|
||||||
|
$allMeta = $this->em->getMetadataFactory()->getAllMetadata();
|
||||||
|
|
||||||
|
foreach ($allMeta as $meta) {
|
||||||
|
if ($meta->isMappedSuperclass) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tableName = $meta->getTableName();
|
||||||
|
foreach ($meta->getAssociationMappings() as $assoc) {
|
||||||
|
if (ThirdParty::class !== $assoc['targetEntity']) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// phpstan wants boolean for if condition
|
||||||
|
if (($assoc['type'] & ClassMetadata::TO_ONE) !== 0) {
|
||||||
|
$joinColumn = $meta->getSingleAssociationJoinColumnName($assoc['fieldName']);
|
||||||
|
|
||||||
|
$suffix = (ThirdParty::class === $assoc['sourceEntity']) ? 'chill_3party.' : '';
|
||||||
|
|
||||||
|
$queries[] = [
|
||||||
|
'sql' => "UPDATE {$suffix}{$tableName} SET {$joinColumn} = :toKeep WHERE {$joinColumn} = :toDelete",
|
||||||
|
'params' => ['toKeep' => $toKeep->getId(), 'toDelete' => $toDelete->getId()],
|
||||||
|
];
|
||||||
|
} elseif (ClassMetadata::MANY_TO_MANY === $assoc['type'] && isset($assoc['joinTable'])) {
|
||||||
|
$joinTable = $assoc['joinTable']['name'];
|
||||||
|
$prefix = null !== ($assoc['joinTable']['schema'] ?? null) ? $assoc['joinTable']['schema'].'.' : '';
|
||||||
|
$joinColumn = $assoc['joinTable']['inverseJoinColumns'][0]['name'];
|
||||||
|
$queries[] = [
|
||||||
|
'sql' => "UPDATE {$prefix}{$joinTable} SET {$joinColumn} = :toKeep WHERE {$joinColumn} = :toDelete AND NOT EXISTS (SELECT 1 FROM {$prefix}{$joinTable} WHERE {$joinColumn} = :toKeep)",
|
||||||
|
'params' => ['toDelete' => $toDelete->getId(), 'toKeep' => $toKeep->getId()],
|
||||||
|
];
|
||||||
|
|
||||||
|
$queries[] = [
|
||||||
|
'sql' => "DELETE FROM {$joinTable} WHERE {$joinColumn} = :toDelete",
|
||||||
|
'params' => ['toDelete' => $toDelete->getId()],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $queries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeThirdparty(ThirdParty $toKeep, ThirdParty $toDelete): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'sql' => 'UPDATE chill_3party.third_party SET parent_id = :toKeep WHERE parent_id = :toDelete',
|
||||||
|
'params' => ['toKeep' => $toKeep->getId(), 'toDelete' => $toDelete->getId()],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'sql' => 'UPDATE chill_3party.thirdparty_category SET thirdparty_id = :toKeep WHERE thirdparty_id = :toDelete AND NOT EXISTS (SELECT 1 FROM chill_3party.thirdparty_category WHERE thirdparty_id = :toKeep)',
|
||||||
|
'params' => ['toKeep' => $toKeep->getId(), 'toDelete' => $toDelete->getId()],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'sql' => 'DELETE FROM chill_3party.third_party WHERE id = :toDelete',
|
||||||
|
'params' => ['toDelete' => $toDelete->getId()],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -39,6 +39,7 @@ class ThirdPartyRender implements ChillEntityRenderInterface
|
|||||||
'showContacts' => $options['showContacts'] ?? false,
|
'showContacts' => $options['showContacts'] ?? false,
|
||||||
'showParent' => $options['showParent'] ?? true,
|
'showParent' => $options['showParent'] ?? true,
|
||||||
'isConfidential' => $options['isConfidential'] ?? false,
|
'isConfidential' => $options['isConfidential'] ?? false,
|
||||||
|
'showFusion' => $options['showFusion'] ?? false,
|
||||||
];
|
];
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -0,0 +1,86 @@
|
|||||||
|
<?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\ThirdPartyBundle\Tests\Service;
|
||||||
|
|
||||||
|
use Chill\ActivityBundle\Entity\Activity;
|
||||||
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
use Chill\ThirdPartyBundle\Entity\ThirdPartyCategory;
|
||||||
|
use Chill\ThirdPartyBundle\Service\ThirdpartyMergeService;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class ThirdpartyMergeServiceTest extends KernelTestCase
|
||||||
|
{
|
||||||
|
private EntityManagerInterface $em;
|
||||||
|
private ThirdpartyMergeService $service;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
self::bootKernel();
|
||||||
|
$this->em = self::getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
$this->service = new ThirdpartyMergeService($this->em);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMergeUpdatesReferencesAndDeletesThirdparty(): void
|
||||||
|
{
|
||||||
|
// Create ThirdParty entities
|
||||||
|
$toKeep = new ThirdParty();
|
||||||
|
$toKeep->setName('Thirdparty to keep');
|
||||||
|
$this->em->persist($toKeep);
|
||||||
|
|
||||||
|
$toDelete = new ThirdParty();
|
||||||
|
$toDelete->setName('Thirdparty to delete');
|
||||||
|
$this->em->persist($toDelete);
|
||||||
|
|
||||||
|
// Create a related entity with TO_ONE relation (thirdparty parent)
|
||||||
|
$relatedToOneEntity = new ThirdParty();
|
||||||
|
$relatedToOneEntity->setName('RelatedToOne thirdparty');
|
||||||
|
$relatedToOneEntity->setParent($toDelete);
|
||||||
|
$this->em->persist($relatedToOneEntity);
|
||||||
|
|
||||||
|
// Create a related entity with TO_MANY relation (thirdparty category)
|
||||||
|
$thirdpartyCategory = new ThirdPartyCategory();
|
||||||
|
$thirdpartyCategory->setName(['fr' => 'Thirdparty category']);
|
||||||
|
$this->em->persist($thirdpartyCategory);
|
||||||
|
$toDelete->addCategory($thirdpartyCategory);
|
||||||
|
$this->em->persist($toDelete);
|
||||||
|
|
||||||
|
$activity = new Activity();
|
||||||
|
$activity->setDate(new \DateTime());
|
||||||
|
$activity->addThirdParty($toDelete);
|
||||||
|
$this->em->persist($activity);
|
||||||
|
|
||||||
|
$this->em->flush();
|
||||||
|
|
||||||
|
// Run merge
|
||||||
|
$this->service->merge($toKeep, $toDelete);
|
||||||
|
$this->em->refresh($toKeep);
|
||||||
|
$this->em->refresh($relatedToOneEntity);
|
||||||
|
|
||||||
|
// Check that references were updated
|
||||||
|
$this->assertEquals($toKeep->getId(), $relatedToOneEntity->getParent()->getId(), 'The parent thirdparty was succesfully merged');
|
||||||
|
|
||||||
|
$updatedRelatedManyEntity = $this->em->find(ThirdPartyCategory::class, $thirdpartyCategory->getId());
|
||||||
|
$this->assertContains($updatedRelatedManyEntity, $toKeep->getCategories(), 'The thirdparty category was found in the toKeep entity');
|
||||||
|
|
||||||
|
// Check that toDelete was removed
|
||||||
|
$this->em->clear();
|
||||||
|
$deletedThirdParty = $this->em->find(ThirdParty::class, $toDelete->getId());
|
||||||
|
$this->assertNull($deletedThirdParty);
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,14 @@
|
|||||||
---
|
|
||||||
services:
|
services:
|
||||||
Chill\ThirdPartyBundle\Serializer\Normalizer\:
|
_defaults:
|
||||||
autowire: true
|
autowire: true
|
||||||
|
autoconfigure: true
|
||||||
|
|
||||||
|
Chill\ThirdPartyBundle\Serializer\Normalizer\:
|
||||||
resource: '../Serializer/Normalizer/'
|
resource: '../Serializer/Normalizer/'
|
||||||
tags:
|
tags:
|
||||||
- { name: 'serializer.normalizer', priority: 64 }
|
- { name: 'serializer.normalizer', priority: 64 }
|
||||||
|
|
||||||
Chill\ThirdPartyBundle\Export\:
|
Chill\ThirdPartyBundle\Export\:
|
||||||
autowire: true
|
|
||||||
autoconfigure: true
|
|
||||||
resource: '../Export/'
|
resource: '../Export/'
|
||||||
|
|
||||||
Chill\ThirdPartyBundle\Validator\:
|
Chill\ThirdPartyBundle\Validator\:
|
||||||
@ -16,3 +16,5 @@ services:
|
|||||||
autowire: true
|
autowire: true
|
||||||
resource: '../Validator/'
|
resource: '../Validator/'
|
||||||
|
|
||||||
|
Chill\ThirdPartyBundle\Service\ThirdpartyMergeService: ~
|
||||||
|
|
||||||
|
@ -155,3 +155,16 @@ Telephone2: Autre téléphone
|
|||||||
Contact email: Courrier électronique du contact
|
Contact email: Courrier électronique du contact
|
||||||
Contact address: Adresse du contact
|
Contact address: Adresse du contact
|
||||||
Contact profession: Profession du contact
|
Contact profession: Profession du contact
|
||||||
|
|
||||||
|
thirdparty_duplicate:
|
||||||
|
title: Fusionner les tiers doublons
|
||||||
|
find: Désigner un tiers doublon
|
||||||
|
Thirdparty to keep: Tiers à conserver
|
||||||
|
Thirdparty to delete: Tiers à supprimer
|
||||||
|
Thirdparty to delete explanation: Ce tiers sera supprimé. Seuls les contacts de ce tiers, énumérés ci-dessous, seront transférés.
|
||||||
|
Thirdparty to keep explanation: Ce tiers sera conservé
|
||||||
|
Data to keep: Données conservées
|
||||||
|
You cannot merge a thirdparty with itself. Please choose a different thirdparty: Vous ne pouvez pas fusionner un tiers avec lui-même. Veuillez choisir un autre tiers.
|
||||||
|
A thirdparty can only be merged with a thirdparty of the same kind: Un tiers ne peut être fusionné qu'avec un tiers de même type.
|
||||||
|
Two child thirdparties must have the same parent: Deux tiers de type « contact » doivent avoir le même tiers parent.
|
||||||
|
Merge successful: La fusion a été effectuée avec succès
|
||||||
|
Loading…
x
Reference in New Issue
Block a user