mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
FIX [duplicate] adding test and handlers to handle fusion of doublons with special cases - after codewithme session
This commit is contained in:
parent
0423540976
commit
e0cdf06e99
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\PersonBundle\Actions\Remove\Handler;
|
||||
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
|
||||
|
||||
class PersonMoveAccompanyingPeriodParticipationHandler
|
||||
{
|
||||
public function supports(string $className, string $field): bool
|
||||
{
|
||||
return $className === AccompanyingPeriodParticipation::class;
|
||||
}
|
||||
|
||||
public function getSqls(string $className, string $field, Person $from, Person $to): array
|
||||
{
|
||||
$insertSql = sprintf(<<<SQL
|
||||
INSERT INTO chill_person_accompanying_period_participation (person_id, accompanyingperiod_id, id, startdate, enddate)
|
||||
SELECT %d, accompanyingperiod_id, nextval('chill_person_person_center_history_id_seq'), startdate, enddate
|
||||
FROM chill_person_accompanying_period_participation cpapp
|
||||
WHERE person_id = %d
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM chill_person_accompanying_period_participation cpapp2
|
||||
WHERE
|
||||
person_id = %d
|
||||
AND daterange(cpapp.startdate, cpapp.enddate) && daterange(cpapp2.startdate, cpapp2.enddate)
|
||||
)
|
||||
SQL, $to->getId(), $from->getId(), $to->getId());
|
||||
|
||||
$deleteSql = sprintf(<<<SQL
|
||||
DELETE FROM chill_person_accompanying_period_participation WHERE person_id = %d
|
||||
SQL, $from->getId());
|
||||
|
||||
return [$sqlInsert, $deleteSql];
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\PersonBundle\Actions\Remove\Handler;
|
||||
|
||||
use Chill\PersonBundle\Actions\Remove\PersonMoveSqlHandlerInterface;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
|
||||
class PersonMoveCenterHistoryHandler implements PersonMoveSqlHandlerInterface
|
||||
{
|
||||
public function supports(string $className, string $field): bool
|
||||
{
|
||||
return $className === Person\PersonCenterHistory::class;
|
||||
}
|
||||
|
||||
public function getSqls(string $className, string $field, Person $from, Person $to): array
|
||||
{
|
||||
$insertSql = sprintf(<<<SQL
|
||||
INSERT INTO chill_person_person_center_history (id, person_id, center_id, startdate, enddate, createdat, updatedat, createdby_id, updatedby_id)
|
||||
SELECT nextval('chill_person_person_center_history_id_seq'), %d, center_id, startdate, enddate, createdat, updatedat, createdby_id, updatedby_id
|
||||
FROM chill_person_person_center_history cppch
|
||||
WHERE person_id = %d
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM chill_person_person_center_history cppch2
|
||||
WHERE
|
||||
person_id = %d
|
||||
AND daterange(cppch.startdate, cppch.enddate) && daterange(cppch2.startdate, cppch2.enddate)
|
||||
)
|
||||
SQL, $to->getId(), $from->getId(), $to->getId());
|
||||
|
||||
$updateSql = sprintf(<<<SQL
|
||||
UPDATE chill_person_person_center_history cppch
|
||||
SET startdate =
|
||||
WHERE person_id = %d
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM chill_person_person_center_history cppch2
|
||||
WHERE
|
||||
person_id = %d
|
||||
AND daterange(cppch.startdate, cppch.enddate) && daterange(cppch2.startdate, cppch2.enddate)
|
||||
)
|
||||
|
||||
|
||||
SQL, $to->getId());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\PersonBundle\Actions\Remove\Handler;
|
||||
|
||||
use Chill\PersonBundle\Actions\Remove\PersonMoveSqlHandlerInterface;
|
||||
use Chill\PersonBundle\Entity\Household\HouseholdMember;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
|
||||
class PersonMoveHouseholdHandler implements PersonMoveSqlHandlerInterface
|
||||
{
|
||||
public function supports(string $className, string $field): bool
|
||||
{
|
||||
return $className === HouseholdMember::class;
|
||||
}
|
||||
|
||||
public function getSqls(string $className, string $field, Person $from, Person $to): array
|
||||
{
|
||||
$sqlInsert = sprintf(<<<SQL
|
||||
INSERT INTO chill_person_household_members (id, person_id, household_id, startdate, enddate, comment, sharedhousehold, position_id, holder)
|
||||
SELECT nextval('chill_person_household_members_id_seq'), %d, household_id, startdate, enddate, comment, sharedhousehold, position_id, holder
|
||||
FROM chill_person_household_members cphm
|
||||
WHERE person_id = %d
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM chill_person_household_members cphm_inner
|
||||
WHERE
|
||||
person_id = %d
|
||||
AND daterange(cphm.startdate, cphm.enddate) && daterange(cphm_inner.startdate, cphm_inner.enddate)
|
||||
)
|
||||
SQL, $to->getId(), $from->getId(), $to->getId());
|
||||
|
||||
$deleteSql = sprintf(<<<SQL
|
||||
DELETE FROM chill_person_household_members WHERE person_id = %d
|
||||
SQL, $from->getId());
|
||||
|
||||
return [$sqlInsert, $deleteSql];
|
||||
}
|
||||
|
||||
}
|
@ -35,22 +35,11 @@ use function in_array;
|
||||
*/
|
||||
class PersonMove
|
||||
{
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
protected $em;
|
||||
|
||||
/**
|
||||
* @var EventDispatcherInterface
|
||||
*/
|
||||
protected $eventDispatcher;
|
||||
|
||||
public function __construct(
|
||||
EntityManagerInterface $em,
|
||||
EventDispatcherInterface $eventDispatcher
|
||||
private EntityManagerInterface $em,
|
||||
private PersonMoveManager $personMoveManager,
|
||||
private EventDispatcherInterface $eventDispatcher
|
||||
) {
|
||||
$this->em = $em;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -88,9 +77,16 @@ class PersonMove
|
||||
}
|
||||
|
||||
foreach ($metadata->getAssociationMappings() as $field => $mapping) {
|
||||
|
||||
if ($this->personMoveManager->hasHandler($metadata->getName(), $field)) {
|
||||
$sqls = array_merge($sqls, $this->personMoveManager->getSqls($metadata->getName(), $field, $from, $to));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($mapping['sourceEntity'], $this->getIgnoredEntities(), true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Person::class === $mapping['targetEntity'] and true === $mapping['isOwningSide']) {
|
||||
if (in_array($mapping['sourceEntity'], $toDelete, true)) {
|
||||
$sql = $this->createDeleteSQL($metadata, $from, $field);
|
||||
@ -101,18 +97,10 @@ class PersonMove
|
||||
['to' => $to->getId(), 'original_action' => 'move']
|
||||
);
|
||||
$this->eventDispatcher->dispatch(ActionEvent::DELETE, $event);
|
||||
$sqls = array_merge($sqls, $event->getPreSql(), [$event->getSqlStatement()], $event->getPostSql());
|
||||
} else {
|
||||
$sql = $this->createMoveSQL($metadata, $from, $to, $field);
|
||||
$event = new ActionEvent(
|
||||
$from->getId(),
|
||||
$metadata->getName(),
|
||||
$sql,
|
||||
['to' => $to->getId(), 'original_action' => 'move']
|
||||
);
|
||||
$this->eventDispatcher->dispatch(ActionEvent::MOVE, $event);
|
||||
$sqls = array_merge($sqls, $this->createMoveSQLs($metadata, $from, $to, $field));
|
||||
}
|
||||
|
||||
$sqls = array_merge($sqls, $event->getPreSql(), [$event->getSqlStatement()], $event->getPostSql());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -150,7 +138,7 @@ class PersonMove
|
||||
);
|
||||
}
|
||||
|
||||
private function createMoveSQL(ClassMetadata $metadata, Person $from, Person $to, $field): string
|
||||
private function createMoveSQLs($metadata, Person $from, Person $to, $field): array
|
||||
{
|
||||
$mapping = $metadata->getAssociationMapping($field);
|
||||
|
||||
@ -160,9 +148,34 @@ class PersonMove
|
||||
$tableName = '';
|
||||
|
||||
if (array_key_exists('joinTable', $mapping)) {
|
||||
// there is a join_table: we have to find conflict
|
||||
$tableName = (null !== ($mapping['joinTable']['schema'] ?? null) ? $mapping['joinTable']['schema'] . '.' : '')
|
||||
. $mapping['joinTable']['name'];
|
||||
|
||||
$sqlInsert = sprintf(
|
||||
"INSERT INTO %s (%s, %s) SELECT %d, %s FROM %s WHERE %s = %d ON CONFLICT DO NOTHING",
|
||||
$tableName,
|
||||
$mapping['joinTable']['inverseJoinColumns'][0]['name'], // person_id
|
||||
$mapping['joinTable']['joinColumns'][0]['name'], // something_else_id
|
||||
$to->getId(),
|
||||
$mapping['joinTable']['joinColumns'][0]['name'], // something_else_id
|
||||
$tableName,
|
||||
$mapping['joinTable']['inverseJoinColumns'][0]['name'], // person_id
|
||||
$from->getId()
|
||||
|
||||
);
|
||||
$deleteSql = sprintf(
|
||||
"DELETE FROM %s WHERE %s = %d",
|
||||
$tableName,
|
||||
$mapping['joinTable']['inverseJoinColumns'][0]['name'], // person_id
|
||||
$from->getId()
|
||||
|
||||
);
|
||||
|
||||
return [
|
||||
$sqlInsert, $deleteSql
|
||||
];
|
||||
|
||||
foreach ($mapping['joinTable']['inverseJoinColumns'] as $columns) {
|
||||
$sets[] = sprintf('%s = %d', $columns['name'], $to->getId());
|
||||
}
|
||||
@ -176,18 +189,17 @@ class PersonMove
|
||||
$sets[] = sprintf('%s = %d', $columns['name'], $to->getId());
|
||||
}
|
||||
|
||||
|
||||
foreach ($mapping['joinColumns'] as $columns) {
|
||||
$conditions[] = sprintf('%s = %d', $columns['name'], $from->getId());
|
||||
}
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
return [sprintf(
|
||||
'UPDATE %s SET %s WHERE %s',
|
||||
$tableName,
|
||||
implode(' ', $sets),
|
||||
implode(' AND ', $conditions)
|
||||
);
|
||||
)];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -198,7 +210,6 @@ class PersonMove
|
||||
{
|
||||
return [
|
||||
Person\PersonCenterHistory::class,
|
||||
HouseholdMember::class,
|
||||
AccompanyingPeriodParticipation::class,
|
||||
AccompanyingPeriod\AccompanyingPeriodWork::class,
|
||||
Relationship::class
|
||||
|
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\PersonBundle\Actions\Remove;
|
||||
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
|
||||
class PersonMoveManager
|
||||
{
|
||||
public function __construct(
|
||||
/**
|
||||
* @var iterable<PersonMoveSqlHandlerInterface>
|
||||
*/
|
||||
private iterable $handlers,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string $className
|
||||
* @param string $field
|
||||
* @return bool
|
||||
*/
|
||||
public function hasHandler(string $className, string $field): bool
|
||||
{
|
||||
foreach ($this->handlers as $handler) {
|
||||
if ($handler->supports($className, $field)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string $className
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getSqls(string $className, string $field, Person $from, Person $to): array
|
||||
{
|
||||
foreach ($this->handlers as $handler) {
|
||||
if ($handler->supports($className, $field)) {
|
||||
return $handler->getSqls($className, $field, $from, $to);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\PersonBundle\Actions\Remove;
|
||||
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
|
||||
interface PersonMoveSqlHandlerInterface
|
||||
{
|
||||
/**
|
||||
* @param class-string $className
|
||||
*/
|
||||
public function supports(string $className, string $field): bool;
|
||||
|
||||
/**
|
||||
* @param class-string $className
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getSqls(string $className, string $field, Person $from, Person $to): array;
|
||||
}
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\PersonBundle;
|
||||
|
||||
use Chill\PersonBundle\Actions\Remove\PersonMoveSqlHandlerInterface;
|
||||
use Chill\PersonBundle\DependencyInjection\CompilerPass\AccompanyingPeriodTimelineCompilerPass;
|
||||
use Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface;
|
||||
use Chill\PersonBundle\Widget\PersonListWidgetFactory;
|
||||
@ -29,5 +30,7 @@ class ChillPersonBundle extends Bundle
|
||||
$container->addCompilerPass(new AccompanyingPeriodTimelineCompilerPass());
|
||||
$container->registerForAutoconfiguration(AccompanyingPeriodInfoUnionQueryPartInterface::class)
|
||||
->addTag('chill_person.accompanying_period_info_part');
|
||||
$container->registerForAutoconfiguration(PersonMoveSqlHandlerInterface::class)
|
||||
->addTag('chill_person.person_move_handler');
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace Action\Remove;
|
||||
|
||||
use Chill\ActivityBundle\Entity\Activity;
|
||||
use Chill\PersonBundle\Actions\Remove\PersonMove;
|
||||
use Chill\PersonBundle\Actions\Remove\PersonMoveManager;
|
||||
use Chill\PersonBundle\Entity\Household\Household;
|
||||
use Chill\PersonBundle\Entity\Household\HouseholdMember;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
class PersonMoveTest extends KernelTestCase
|
||||
{
|
||||
private EntityManagerInterface $em;
|
||||
|
||||
private PersonMoveManager $personMoveManager;
|
||||
|
||||
private EventDispatcherInterface $eventDispatcher;
|
||||
|
||||
/**
|
||||
* @var list<array{0: class-string, 1: int}>
|
||||
*/
|
||||
private static $entitiesToDelete = [];
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
$this->em = self::$container->get(EntityManagerInterface::class);
|
||||
$this->personMoveManager = self::$container->get(PersonMoveManager::class);
|
||||
$this->eventDispatcher = self::$container->get(EventDispatcherInterface::class);
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
$em = self::$container->get(EntityManagerInterface::class);
|
||||
|
||||
foreach (self::$entitiesToDelete as list($class, $id)) {
|
||||
$entity = $em->find($class, $id);
|
||||
|
||||
if (null !== $entity) {
|
||||
$em->remove($entity);
|
||||
}
|
||||
}
|
||||
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataProviderMovePerson
|
||||
*/
|
||||
public function testMovePerson(Person $personA, Person $personB, string $message): void
|
||||
{
|
||||
$move = new PersonMove($this->em, $this->personMoveManager, $this->eventDispatcher);
|
||||
$sqls = $move->getSQL($personA, $personB);
|
||||
//$conn = $this->em->getConnection();
|
||||
$this->em->getConnection()->transactional(function (Connection $conn) use ($personA, $personB, $sqls) {
|
||||
foreach ($sqls as $sql) {
|
||||
$conn->executeStatement($sql);
|
||||
}
|
||||
});
|
||||
|
||||
$personA = $this->em->find(Person::class, $personA->getId());
|
||||
$personB = $this->em->find(Person::class, $personB->getId());
|
||||
|
||||
self::assertNull($personA?->getId(), $message);
|
||||
self::assertNotNull($personB?->getId(), $message);
|
||||
}
|
||||
|
||||
public function dataProviderMovePerson(): iterable
|
||||
{
|
||||
$this->setUp();
|
||||
|
||||
$personA = new Person();
|
||||
$personB = new Person();
|
||||
|
||||
$this->em->persist($personA);
|
||||
$this->em->persist($personB);
|
||||
|
||||
self::$entitiesToDelete[] = [Person::class, $personA];
|
||||
self::$entitiesToDelete[] = [Person::class, $personB];
|
||||
|
||||
yield [$personA, $personB, "move 2 people without any associated data"];
|
||||
|
||||
$personA = new Person();
|
||||
$personB = new Person();
|
||||
|
||||
$activity = new Activity();
|
||||
$activity->setDate(new \DateTime('today'));
|
||||
$activity->addPerson($personA);
|
||||
$activity->addPerson($personB);
|
||||
|
||||
$this->em->persist($personA);
|
||||
$this->em->persist($personB);
|
||||
$this->em->persist($activity);
|
||||
|
||||
self::$entitiesToDelete[] = [Person::class, $personA];
|
||||
self::$entitiesToDelete[] = [Person::class, $personB];
|
||||
self::$entitiesToDelete[] = [Activity::class, $activity];
|
||||
|
||||
yield [$personA, $personB, "move 2 people having an activity"];
|
||||
|
||||
$personA = new Person();
|
||||
$personB = new Person();
|
||||
$household = new Household();
|
||||
$household->addMember(
|
||||
$memberA = (new HouseholdMember())->setPerson($personA)->setShareHousehold(true)
|
||||
->setStartDate(new \DateTimeImmutable('2023-01-01'))
|
||||
);
|
||||
$household->addMember(
|
||||
$memberB = (new HouseholdMember())->setPerson($personB)->setShareHousehold(true)
|
||||
->setStartDate(new \DateTimeImmutable('2023-01-01'))
|
||||
);
|
||||
|
||||
|
||||
|
||||
$this->em->persist($personA);
|
||||
$this->em->persist($personB);
|
||||
$this->em->persist($household);
|
||||
$this->em->persist($memberA);
|
||||
$this->em->persist($memberB);
|
||||
|
||||
self::$entitiesToDelete[] = [Person::class, $personA];
|
||||
self::$entitiesToDelete[] = [Person::class, $personB];
|
||||
self::$entitiesToDelete[] = [HouseholdMember::class, $memberA];
|
||||
self::$entitiesToDelete[] = [HouseholdMember::class, $memberB];
|
||||
self::$entitiesToDelete[] = [Household::class, $household];
|
||||
|
||||
yield [$personA, $personB, "move 2 people having the same household at the same time"];
|
||||
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
}
|
||||
}
|
@ -1,5 +1,13 @@
|
||||
services:
|
||||
Chill\PersonBundle\Actions\Remove\PersonMove:
|
||||
_defaults:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
Chill\PersonBundle\Actions\Remove\PersonMove: ~
|
||||
|
||||
Chill\PersonBundle\Actions\Remove\PersonMoveManager:
|
||||
arguments:
|
||||
$em: '@Doctrine\ORM\EntityManagerInterface'
|
||||
$eventDispatcher: '@Symfony\Component\EventDispatcher\EventDispatcherInterface'
|
||||
$handlers: !tagged_iterator chill_person.person_move_handler
|
||||
|
||||
Chill\PersonBundle\Actions\Remove\Handler\:
|
||||
resource: '../../Actions/Remove/Handler'
|
||||
|
Loading…
x
Reference in New Issue
Block a user