mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-10-31 01:08:26 +00:00 
			
		
		
		
	Resolve "Fusion des tiers"
This commit is contained in:
		| @@ -64,3 +64,5 @@ const props = defineProps({ | ||||
|     entity: Object, | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| thirdparty_duplicate: merge: Fussioner find: 'Désigner un tiers doublon' | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
|     {% endblock crud_content_header %} | ||||
|  | ||||
|     {% block crud_content_view %} | ||||
|          | ||||
|  | ||||
|         {% block crud_content_view_details %} | ||||
|             <dl class="chill_view_data"> | ||||
|                 <dt>id</dt> | ||||
| @@ -20,7 +20,7 @@ | ||||
|                         {{ 'Cancel'|trans }} | ||||
|                     </a> | ||||
|                 </li> | ||||
|             {% endblock %}  | ||||
|             {% endblock %} | ||||
|             {% block content_view_actions_before %}{% endblock %} | ||||
|             {% block content_form_actions_delete %} | ||||
|                 {% if chill_crud_action_exists(crud_name, 'delete') %} | ||||
| @@ -32,7 +32,7 @@ | ||||
|                 </li> | ||||
|                 {% endif %} | ||||
|                 {% endif %} | ||||
|             {% endblock content_form_actions_delete %}  | ||||
|             {% endblock content_form_actions_delete %} | ||||
|             {% block content_view_actions_duplicate_link %} | ||||
|                 {% if chill_crud_action_exists(crud_name, 'new') %} | ||||
|                 {% if is_granted(chill_crud_config('role', crud_name, 'new'), entity) %} | ||||
| @@ -44,6 +44,17 @@ | ||||
|                 {% endif %} | ||||
|                 {% endif %} | ||||
|             {% 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 %} | ||||
|                 {% if chill_crud_action_exists(crud_name, 'edit') %} | ||||
|                 {% 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 }}" | ||||
|                             href="{{ path('chill_crud_3party_3party_view', { id: thirdparty.isChild ? thirdparty.parent.id : thirdparty.id }) }}"></a> | ||||
|                     </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 %} | ||||
|  | ||||
|                 {% if options['customButtons']['after'] is defined %} | ||||
|   | ||||
| @@ -128,7 +128,7 @@ | ||||
|                                 <div class="flex-table"> | ||||
|                                     {% for tp in thirdParty.activeChildren %} | ||||
|                                     <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> | ||||
|                                     {% endfor %} | ||||
|                                 </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|chill_format_phonenumber }}{% 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="record_actions sticky-form-buttons"> | ||||
|                 <li class="cancel"> | ||||
|                     <a href="{{ path('chill_thirdparty_find_duplicate', {thirdparty_id : thirdparty.id}) }}" class="btn btn-cancel"> | ||||
|                         {{ 'Cancel'|trans }} | ||||
|                     </a> | ||||
|                 </li> | ||||
|                 <li> | ||||
|                     <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-submit" 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,115 @@ | ||||
| <?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\DBAL\Exception; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
| use Doctrine\ORM\Mapping\ClassMetadata; | ||||
| use Doctrine\ORM\Mapping\MappingException; | ||||
|  | ||||
| readonly class ThirdpartyMergeService | ||||
| { | ||||
|     public function __construct(private EntityManagerInterface $em) {} | ||||
|  | ||||
|     /** | ||||
|      * @throws Exception | ||||
|      */ | ||||
|     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; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @throws MappingException | ||||
|      */ | ||||
|     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, | ||||
|             'showParent' => $options['showParent'] ?? true, | ||||
|             'isConfidential' => $options['isConfidential'] ?? false, | ||||
|             'showFusion' => $options['showFusion'] ?? false, | ||||
|         ]; | ||||
|  | ||||
|         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: | ||||
|     Chill\ThirdPartyBundle\Serializer\Normalizer\: | ||||
|     _defaults: | ||||
|         autowire: true | ||||
|         autoconfigure: true | ||||
|  | ||||
|     Chill\ThirdPartyBundle\Serializer\Normalizer\: | ||||
|         resource: '../Serializer/Normalizer/' | ||||
|         tags: | ||||
|             - { name: 'serializer.normalizer', priority: 64 } | ||||
|  | ||||
|     Chill\ThirdPartyBundle\Export\: | ||||
|         autowire: true | ||||
|         autoconfigure: true | ||||
|         resource: '../Export/' | ||||
|  | ||||
|     Chill\ThirdPartyBundle\Validator\: | ||||
| @@ -16,3 +16,5 @@ services: | ||||
|         autowire: true | ||||
|         resource: '../Validator/' | ||||
|  | ||||
|     Chill\ThirdPartyBundle\Service\ThirdpartyMergeService: ~ | ||||
|  | ||||
|   | ||||
| @@ -135,7 +135,6 @@ is thirdparty: Le demandeur est un tiers | ||||
| Filter by person's who have a residential address located at a thirdparty of type: Filtrer les usagers qui ont une addresse de résidence chez un tiers | ||||
| "Filtered by person's who have a residential address located at a thirdparty of type %thirdparty_type% and valid on %date_calc%": "Uniquement les usagers qui ont une addresse de résidence chez un tiers de catégorie %thirdparty_type% et valide sur la date %date_calc%" | ||||
|  | ||||
|  | ||||
| # admin | ||||
| admin: | ||||
|     export_description: Liste des tiers (format CSV) | ||||
| @@ -155,3 +154,16 @@ Telephone2: Autre téléphone | ||||
| Contact email: Courrier électronique du contact | ||||
| Contact address: Adresse 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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user