Parcourir la source

Merge branch 'feature/V8-6941-complter-les-entits-avec-les-pro' into feature/V8-7097-upgrade-symfony-7--api-platform-

Olivier Massot il y a 8 mois
Parent
commit
126eb4a53d
100 fichiers modifiés avec 6660 ajouts et 1028 suppressions
  1. 1 0
      .gitignore
  2. 1 0
      composer.json
  3. 0 25
      rector.php
  4. 80 0
      src/Commands/Doctrine/SchemaValidateCommand.php
  5. 255 0
      src/Entity/Access/Access.php
  6. 15 0
      src/Entity/Access/AccessCommunication.php
  7. 15 0
      src/Entity/Access/AccessNetworkSetting.php
  8. 15 0
      src/Entity/Access/AccessSocial.php
  9. 25 0
      src/Entity/AccessWish/AccessFamilyWish.php
  10. 81 0
      src/Entity/AccessWish/AccessWish.php
  11. 30 0
      src/Entity/AccessWish/DocumentWish.php
  12. 66 0
      src/Entity/AccessWish/EducationStudentWish.php
  13. 441 0
      src/Entity/Awin/Product.php
  14. 444 0
      src/Entity/Billing/AbstractBillAccounting.php
  15. 16 0
      src/Entity/Billing/AccessBilling.php
  16. 18 0
      src/Entity/Billing/AdvancePayment.php
  17. 74 0
      src/Entity/Billing/Afi.php
  18. 16 0
      src/Entity/Billing/BergerLevrault.php
  19. 2 7
      src/Entity/Billing/Bill.php
  20. 2 293
      src/Entity/Billing/BillAccounting.php
  21. 2 24
      src/Entity/Billing/BillCredit.php
  22. 3 3
      src/Entity/Billing/BillLine.php
  23. 35 3
      src/Entity/Billing/BillPayment.php
  24. 130 0
      src/Entity/Billing/BillSchedule.php
  25. 46 0
      src/Entity/Billing/BillScheduleDate.php
  26. 50 0
      src/Entity/Billing/BillingExportSetting.php
  27. 15 0
      src/Entity/Billing/BillingSetting.php
  28. 15 0
      src/Entity/Billing/BillingSettingRent.php
  29. 16 0
      src/Entity/Billing/Ciril.php
  30. 72 0
      src/Entity/Billing/CirilCivil.php
  31. 105 0
      src/Entity/Billing/FamilyQuotientBand.php
  32. 89 0
      src/Entity/Billing/FamilyQuotientBandDetail.php
  33. 103 0
      src/Entity/Billing/FamilyQuotientModel.php
  34. 16 0
      src/Entity/Billing/Jvs.php
  35. 74 0
      src/Entity/Billing/Odyssee.php
  36. 30 0
      src/Entity/Billing/PayboxPaymentReturn.php
  37. 16 0
      src/Entity/Billing/Pes.php
  38. 16 0
      src/Entity/Billing/PesSetting.php
  39. 46 0
      src/Entity/Billing/SddBank.php
  40. 62 0
      src/Entity/Billing/SddRegie.php
  41. 90 1
      src/Entity/Booking/AbstractBooking.php
  42. 32 0
      src/Entity/Booking/CalendarSynchro.php
  43. 44 18
      src/Entity/Booking/Course.php
  44. 40 22
      src/Entity/Booking/EducationalProject.php
  45. 38 21
      src/Entity/Booking/Event.php
  46. 18 15
      src/Entity/Booking/Examen.php
  47. 4 0
      src/Entity/Booking/OrganizationHoliday.php
  48. 22 1
      src/Entity/Booking/PersonHoliday.php
  49. 46 0
      src/Entity/Core/AbstractControl.php
  50. 34 0
      src/Entity/Core/AbstractRepair.php
  51. 33 5
      src/Entity/Core/AddressPostal.php
  52. 16 0
      src/Entity/Core/City.php
  53. 5 5
      src/Entity/Core/ContactPoint.php
  54. 16 0
      src/Entity/Core/Department.php
  55. 45 0
      src/Entity/Core/File.php
  56. 120 0
      src/Entity/Core/LoginLog.php
  57. 16 0
      src/Entity/Core/Region.php
  58. 184 11
      src/Entity/Core/Tagg.php
  59. 28 0
      src/Entity/Education/Cycle.php
  60. 29 0
      src/Entity/Education/Education.php
  61. 34 0
      src/Entity/Education/EducationComplement.php
  62. 217 0
      src/Entity/Education/EducationCurriculum.php
  63. 200 0
      src/Entity/Education/EducationCurriculumPack.php
  64. 17 0
      src/Entity/Education/EducationStudent.php
  65. 68 0
      src/Entity/Message/AbstractMessage.php
  66. 2 26
      src/Entity/Message/Email.php
  67. 1 32
      src/Entity/Message/Mail.php
  68. 79 0
      src/Entity/Message/ReportMessage.php
  69. 1 0
      src/Entity/Message/Sms.php
  70. 16 0
      src/Entity/Network/Network.php
  71. 15 0
      src/Entity/Organization/Activity.php
  72. 15 0
      src/Entity/Organization/CotisationByYear.php
  73. 15 0
      src/Entity/Organization/CotisationStaffInfos.php
  74. 17 0
      src/Entity/Organization/OnlineRegistrationDocument.php
  75. 45 0
      src/Entity/Organization/OnlineRegistrationSettings.php
  76. 287 27
      src/Entity/Organization/Organization.php
  77. 62 0
      src/Entity/Person/AllowedIp.php
  78. 15 0
      src/Entity/Person/Medical.php
  79. 42 0
      src/Entity/Person/Person.php
  80. 456 0
      src/Entity/Place/AbstractPlace.php
  81. 2 435
      src/Entity/Place/Place.php
  82. 5 4
      src/Entity/Place/PlaceControl.php
  83. 6 5
      src/Entity/Place/PlaceRepair.php
  84. 4 1
      src/Entity/Place/PlaceSystem.php
  85. 4 4
      src/Entity/Place/Room.php
  86. 32 0
      src/Entity/Place/RoomControl.php
  87. 36 0
      src/Entity/Product/AbstractProduct.php
  88. 115 29
      src/Entity/Product/Equipment.php
  89. 3 2
      src/Entity/Product/EquipmentControl.php
  90. 3 3
      src/Entity/Product/EquipmentRepair.php
  91. 50 4
      src/Entity/Product/Intangible.php
  92. 34 0
      src/Entity/Product/IntangibleDiscountDetail.php
  93. 32 0
      src/Entity/Product/IntangiblePriceAndDiscount.php
  94. 47 0
      src/Entity/Token/Token.php
  95. 71 0
      src/Service/Cron/Job/SchemaValidation.php
  96. 15 0
      src/Service/Doctrine/SchemaValidation/DiffTypeEnum.php
  97. 72 0
      src/Service/Doctrine/SchemaValidation/Difference.php
  98. 751 0
      src/Service/Doctrine/SchemaValidation/SchemaSnippetsMaker.php
  99. 308 0
      src/Service/Doctrine/SchemaValidation/SchemaValidationService.php
  100. 68 2
      src/Service/Utils/EntityUtils.php

+ 1 - 0
.gitignore

@@ -63,3 +63,4 @@ opentalent_test.sql
 ###< fake database for applications tests ###
 
 .php-cs-fixer.cache
+/schema_validation_snippets/

+ 1 - 0
composer.json

@@ -29,6 +29,7 @@
     "lorenzo/pinky": "^1.0",
     "myclabs/php-enum": "^1.8",
     "nelmio/cors-bundle": "^2.5",
+    "nette/php-generator": "^4.1",
     "odolbeau/phone-number-bundle": "^4.1",
     "opentalent/phpdocx": "dev-master",
     "phpdocumentor/reflection-docblock": "^5.6",

+ 0 - 25
rector.php

@@ -1,25 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector;
-use Rector\Config\RectorConfig;
-use \Rector\Symfony\Set\SymfonySetList;
-
-return static function (RectorConfig $rectorConfig): void {
-    $rectorConfig->paths([
-        __DIR__ . '/config',
-        __DIR__ . '/public',
-        __DIR__ . '/src',
-    ]);
-
-    // register a single rule
-    $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class);
-    $rectorConfig->symfonyContainerXml(__DIR__ . '/var/cache/docker/App_KernelDockerDebugContainer.xml');
-
-    $rectorConfig->sets([
-        \Rector\Symfony\Set\SymfonyLevelSetList::UP_TO_SYMFONY_60,
-        SymfonySetList::SYMFONY_CODE_QUALITY,
-        SymfonySetList::SYMFONY_CONSTRUCTOR_INJECTION,
-    ]);
-};

+ 80 - 0
src/Commands/Doctrine/SchemaValidateCommand.php

@@ -0,0 +1,80 @@
+<?php
+
+/** @noinspection PhpUnused */
+
+namespace App\Commands\Doctrine;
+
+use App\Service\Doctrine\SchemaValidation\DiffTypeEnum;
+use App\Service\Doctrine\SchemaValidation\SchemaSnippetsMaker;
+use App\Service\Doctrine\SchemaValidation\SchemaValidationService;
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Valide le schéma Doctrine en le comparant à la V1.
+ *
+ * @see https://ressources-opentalent.atlassian.net/wiki/spaces/DEV/pages/240551231/V+rifier+le+sch+ma+Doctrine+de+la+V2+et+g+n+rer+les+entit+s+et+propri+t+s+manquantes
+ */
+#[AsCommand(
+    name: 'ot:schema:validate',
+    description: 'Compare le schema doctrine de la V2 à celui de la V1'
+)]
+class SchemaValidateCommand extends Command
+{
+    public function __construct(
+        private readonly SchemaValidationService $schemaValidationService,
+        private readonly SchemaSnippetsMaker $schemaSnippetsMaker,
+    ) {
+        parent::__construct();
+    }
+
+    /**
+     * Configures the command.
+     */
+    protected function configure(): void
+    {
+        $this->addOption(
+            'filter',
+            null,
+            InputOption::VALUE_OPTIONAL,
+            "Filter the type of difference to display (ex: 'MISSING_PROPERTY')."
+        );
+        $this->addOption(
+            'snippets',
+            null,
+            InputOption::VALUE_NONE,
+            'Make snippets of the missing classes and fields'
+        );
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        $filter = $input->getOption('filter') ? DiffTypeEnum::from($input->getOption('filter')) : null;
+
+        $diff = $this->schemaValidationService->validateSchema($filter);
+
+        $csv = $this->schemaValidationService->formatToCsv($diff);
+
+        if (empty($csv)) {
+            $output->writeln('No difference found');
+
+            return 0;
+        }
+
+        foreach ($csv as $line) {
+            $output->writeln($line);
+        }
+
+        $output->writeln(count($csv).' differences found');
+
+        if ($input->getOption('snippets')) {
+            $this->schemaSnippetsMaker->makeSnippets($diff);
+            $output->writeln('Snippets generated');
+        }
+
+        return 0;
+    }
+}

+ 255 - 0
src/Entity/Access/Access.php

@@ -13,12 +13,15 @@ use App\Entity\Billing\AccessBilling;
 use App\Entity\Billing\AccessFictionalIntangible;
 use App\Entity\Billing\AccessIntangible;
 use App\Entity\Billing\AccessPayer;
+use App\Entity\Billing\AdvancePayment;
 use App\Entity\Billing\Bill;
 use App\Entity\Billing\BillCredit;
 use App\Entity\Billing\BillLine;
 use App\Entity\Billing\EducationalProjectPayer;
+use App\Entity\Booking\AbstractBooking;
 use App\Entity\Booking\Attendance;
 use App\Entity\Booking\AttendanceBooking;
+use App\Entity\Booking\CalendarSynchro;
 use App\Entity\Booking\Course;
 use App\Entity\Booking\EducationalProject;
 use App\Entity\Booking\Event;
@@ -26,6 +29,7 @@ use App\Entity\Booking\EventUser;
 use App\Entity\Booking\ExamenConvocation;
 use App\Entity\Booking\PersonHoliday;
 use App\Entity\Booking\WorkByUser;
+use App\Entity\Core\AbstractControl;
 use App\Entity\Core\Notification;
 use App\Entity\Core\NotificationUser;
 use App\Entity\Core\Tagg;
@@ -33,6 +37,7 @@ use App\Entity\Donor\Donor;
 use App\Entity\Education\EducationNotationConfig;
 use App\Entity\Education\EducationStudent;
 use App\Entity\Education\EducationTeacher;
+use App\Entity\Message\AbstractMessage;
 use App\Entity\Message\Email;
 use App\Entity\Message\Mail;
 use App\Entity\Message\Sms;
@@ -50,6 +55,7 @@ use App\Entity\Product\Equipment;
 use App\Entity\Product\EquipmentLoan;
 use App\Entity\Product\EquipmentRepair;
 use App\Entity\Reward\AccessReward;
+use App\Entity\Token\Token;
 use App\Entity\Traits\CreatedOnAndByTrait;
 use App\Filter\ApiPlatform\Person\FullNameFilter;
 use App\Filter\ApiPlatform\Utils\InFilter;
@@ -359,6 +365,39 @@ class Access implements UserInterface, PasswordAuthenticatedUserInterface
     #[ORM\InverseJoinColumn(name: 'tag_id', referencedColumnName: 'id')]
     private Collection $tags;
 
+    #[ORM\ManyToMany(mappedBy: 'organizer', targetEntity: AbstractBooking::class, cascade: ['persist'], orphanRemoval: false)]
+    private Collection $bookingOrganizers;
+
+    #[ORM\OneToMany(mappedBy: 'access', targetEntity: AdvancePayment::class, cascade: [], orphanRemoval: true)]
+    private Collection $advancePayments;
+
+    #[ORM\OneToMany(mappedBy: 'author', targetEntity: AbstractMessage::class, cascade: [], orphanRemoval: true)]
+    private Collection $messages;
+
+    #[ORM\OneToMany(mappedBy: 'managerControl', targetEntity: Equipment::class, cascade: [], orphanRemoval: false)]
+    private Collection $equipmentManagerControls;
+
+    #[ORM\OneToMany(mappedBy: 'accompanist', targetEntity: AbstractControl::class, cascade: [], orphanRemoval: true)]
+    private Collection $accompanistControl;
+
+    #[ORM\OneToMany(mappedBy: 'access', targetEntity: AccessReward::class, cascade: ['persist'], orphanRemoval: true)]
+    private Collection $rewards;
+
+    #[ORM\OneToOne(mappedBy: 'access', targetEntity: AccessSocial::class, cascade: ['persist'], orphanRemoval: true)]
+    private AccessSocial $accessSocial;
+
+    #[ORM\OneToOne(mappedBy: 'access', targetEntity: AccessNetworkSetting::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
+    private AccessNetworkSetting $accessNetworkSetting;
+
+    #[ORM\OneToOne(mappedBy: 'access', targetEntity: AccessCommunication::class, cascade: ['persist'], orphanRemoval: true)]
+    private AccessCommunication $accessCommunication;
+
+    #[ORM\OneToOne(mappedBy: 'access', targetEntity: CalendarSynchro::class, cascade: ['persist'], orphanRemoval: true)]
+    private CalendarSynchro $calendarSynchro;
+
+    #[ORM\OneToMany(mappedBy: 'access', targetEntity: Token::class, cascade: ['persist'], orphanRemoval: true)]
+    private Collection $tokens;
+
     #[ORM\OneToOne(mappedBy: 'access', cascade: ['persist'], fetch: 'EAGER', orphanRemoval: true)]
     private ?Preferences $preferences;
 
@@ -2208,6 +2247,222 @@ class Access implements UserInterface, PasswordAuthenticatedUserInterface
         return $this;
     }
 
+    public function getBookingOrganizers(): Collection
+    {
+        return $this->bookingOrganizers;
+    }
+
+    public function addBookingOrganizer(AbstractBooking $bookingOrganizer): self
+    {
+        if (!$this->bookingOrganizers->contains($bookingOrganizer)) {
+            $this->bookingOrganizers[] = $bookingOrganizer;
+            $bookingOrganizer->addOrganizer($this);
+        }
+
+        return $this;
+    }
+
+    public function removeBookingOrganizer(AbstractBooking $bookingOrganizer): self
+    {
+        if ($this->bookingOrganizers->removeElement($bookingOrganizer)) {
+            $bookingOrganizer->removeOrganizer($this);
+        }
+
+        return $this;
+    }
+
+    public function getAdvancePayments(): Collection
+    {
+        return $this->advancePayments;
+    }
+
+    public function addAdvancePayment(AdvancePayment $advancePayment): self
+    {
+        if (!$this->advancePayments->contains($advancePayment)) {
+            $this->advancePayments[] = $advancePayment;
+            $advancePayment->setAccess($this);
+        }
+
+        return $this;
+    }
+
+    public function removeAdvancePayment(AdvancePayment $advancePayment): self
+    {
+        if ($this->advancePayments->removeElement($advancePayment)) {
+            $advancePayment->setAccess(null);
+        }
+
+        return $this;
+    }
+
+    public function getMessages(): Collection
+    {
+        return $this->messages;
+    }
+
+    public function addMessage(mixed $message): self
+    {
+        if (!$this->messages->contains($message)) {
+            $this->messages[] = $message;
+            $message->setAuthor($this);
+        }
+
+        return $this;
+    }
+
+    public function removeMessage(mixed $message): self
+    {
+        if ($this->messages->removeElement($message)) {
+            $message->setAuthor(null);
+        }
+
+        return $this;
+    }
+
+    public function getEquipmentManagerControls(): Collection
+    {
+        return $this->equipmentManagerControls;
+    }
+
+    public function addEquipmentManagerControl(Equipment $equipmentManagerControl): self
+    {
+        if (!$this->equipmentManagerControls->contains($equipmentManagerControl)) {
+            $this->equipmentManagerControls[] = $equipmentManagerControl;
+            $equipmentManagerControl->setManagerControl($this);
+        }
+
+        return $this;
+    }
+
+    public function removeEquipmentManagerControl(Equipment $equipmentManagerControl): self
+    {
+        if ($this->equipmentManagerControls->removeElement($equipmentManagerControl)) {
+            $equipmentManagerControl->setManagerControl(null);
+        }
+
+        return $this;
+    }
+
+    public function getAccompanistControl(): Collection
+    {
+        return $this->accompanistControl;
+    }
+
+    public function addAccompanistControl(AbstractControl $accompanistControl): self
+    {
+        if (!$this->accompanistControl->contains($accompanistControl)) {
+            $this->accompanistControl[] = $accompanistControl;
+            $accompanistControl->setAccompanist($this);
+        }
+
+        return $this;
+    }
+
+    public function removeAccompanistControl(AbstractControl $accompanistControl): self
+    {
+        if ($this->accompanistControl->removeElement($accompanistControl)) {
+            //            $accompanistControl->setAccompanist(null);  // TODO: actuellement, pas nullable: conserver?
+        }
+
+        return $this;
+    }
+
+    public function getRewards(): Collection
+    {
+        return $this->rewards;
+    }
+
+    public function addReward(AccessReward $reward): self
+    {
+        if (!$this->rewards->contains($reward)) {
+            $this->rewards[] = $reward;
+            $reward->setAccess($this);
+        }
+
+        return $this;
+    }
+
+    public function removeReward(AccessReward $reward): self
+    {
+        if ($this->rewards->removeElement($reward)) {
+            $reward->setAccess(null);
+        }
+
+        return $this;
+    }
+
+    public function getAccessSocial(): AccessSocial
+    {
+        return $this->accessSocial;
+    }
+
+    public function setAccessSocial(AccessSocial $accessSocial): self
+    {
+        $this->accessSocial = $accessSocial;
+
+        return $this;
+    }
+
+    public function getAccessNetworkSetting(): AccessNetworkSetting
+    {
+        return $this->accessNetworkSetting;
+    }
+
+    public function setAccessNetworkSetting(AccessNetworkSetting $accessNetworkSetting): self
+    {
+        $this->accessNetworkSetting = $accessNetworkSetting;
+
+        return $this;
+    }
+
+    public function getAccessCommunication(): AccessCommunication
+    {
+        return $this->accessCommunication;
+    }
+
+    public function setAccessCommunication(AccessCommunication $accessCommunication): self
+    {
+        $this->accessCommunication = $accessCommunication;
+
+        return $this;
+    }
+
+    public function getCalendarSynchro(): CalendarSynchro
+    {
+        return $this->calendarSynchro;
+    }
+
+    public function setCalendarSynchro(CalendarSynchro $calendarSynchro): self
+    {
+        $this->calendarSynchro = $calendarSynchro;
+
+        return $this;
+    }
+
+    public function getTokens(): Collection
+    {
+        return $this->tokens;
+    }
+
+    public function addToken(Token $token): self
+    {
+        if (!$this->tokens->contains($token)) {
+            $this->tokens[] = $token;
+            $token->setAccess($this);
+        }
+
+        return $this;
+    }
+
+    public function removeToken(Token $token): self
+    {
+        if ($this->tokens->removeElement($token)) {
+            //            $token->setAccess(null);  // TODO: actuellement, pas nullable: conserver?
+        }
+
+        return $this;
+    }
+
     public function setPreferences(?Preferences $preferences = null): self
     {
         if (!is_null($preferences)) {

+ 15 - 0
src/Entity/Access/AccessCommunication.php

@@ -21,8 +21,23 @@ class AccessCommunication
     #[ORM\GeneratedValue]
     private ?int $id = null;
 
+    #[ORM\OneToOne(inversedBy: 'accessCommunication', targetEntity: Access::class, cascade: ['persist'])]
+    protected Access $access;
+
     public function getId(): ?int
     {
         return $this->id;
     }
+
+    public function getAccess(): Access
+    {
+        return $this->access;
+    }
+
+    public function setAccess(Access $access): self
+    {
+        $this->access = $access;
+
+        return $this;
+    }
 }

+ 15 - 0
src/Entity/Access/AccessNetworkSetting.php

@@ -21,8 +21,23 @@ class AccessNetworkSetting
     #[ORM\GeneratedValue]
     private ?int $id = null;
 
+    #[ORM\OneToOne(inversedBy: 'accessNetworkSetting', targetEntity: Access::class, cascade: ['persist'])]
+    protected Access $access;
+
     public function getId(): ?int
     {
         return $this->id;
     }
+
+    public function getAccess(): Access
+    {
+        return $this->access;
+    }
+
+    public function setAccess(Access $access): self
+    {
+        $this->access = $access;
+
+        return $this;
+    }
 }

+ 15 - 0
src/Entity/Access/AccessSocial.php

@@ -21,8 +21,23 @@ class AccessSocial
     #[ORM\GeneratedValue]
     private ?int $id = null;
 
+    #[ORM\OneToOne(inversedBy: 'accessSocial', targetEntity: Access::class, cascade: ['persist'])]
+    protected Access $access;
+
     public function getId(): ?int
     {
         return $this->id;
     }
+
+    public function getAccess(): Access
+    {
+        return $this->access;
+    }
+
+    public function setAccess(Access $access): self
+    {
+        $this->access = $access;
+
+        return $this;
+    }
 }

+ 25 - 0
src/Entity/AccessWish/AccessFamilyWish.php

@@ -40,9 +40,13 @@ class AccessFamilyWish
     #[ORM\Column]
     private bool $closeRegistration = false;
 
+    #[ORM\OneToMany(mappedBy: 'accessFamilyWish', targetEntity: AccessWish::class, cascade: ['remove'], orphanRemoval: false)]
+    protected Collection $accessWishesGuardians;
+
     public function __construct()
     {
         $this->accessWishes = new ArrayCollection();
+        $this->accessWishesGuardians = new ArrayCollection();
     }
 
     public function getId(): ?int
@@ -109,4 +113,25 @@ class AccessFamilyWish
     {
         $this->closeRegistration = $closeRegistration;
     }
+
+    public function getAccessWishesGuardians(): Collection
+    {
+        return $this->accessWishesGuardians;
+    }
+
+    public function addAccessWishesGuardian(AccessWish $accessWishesGuardian): self
+    {
+        if (!$this->accessWishesGuardians->contains($accessWishesGuardian)) {
+            $this->accessWishesGuardians[] = $accessWishesGuardian;
+        }
+
+        return $this;
+    }
+
+    public function removeAccessWishesGuardian(AccessWish $accessWishesGuardian): self
+    {
+        $this->accessWishesGuardians->removeElement($accessWishesGuardian);
+
+        return $this;
+    }
 }

+ 81 - 0
src/Entity/AccessWish/AccessWish.php

@@ -6,12 +6,15 @@ namespace App\Entity\AccessWish;
 
 use ApiPlatform\Metadata\ApiResource;
 use App\Entity\Access\Access;
+use App\Entity\Billing\BillSchedule;
 use App\Entity\Core\Country;
 use App\Entity\Core\File;
 use App\Entity\Core\Tagg;
+use App\Entity\Education\EducationCurriculumPack;
 use App\Entity\Organization\Organization;
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use App\Entity\Person\PersonActivity;
+use App\Entity\Place\Place;
 use App\Enum\OnlineRegistration\ValidationStateEnum;
 use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\Common\Collections\Collection;
@@ -70,6 +73,24 @@ class AccessWish
     #[ORM\Column(length: 50, enumType: ValidationStateEnum::class)]
     private ValidationStateEnum $validationState;
 
+    #[ORM\ManyToOne(targetEntity: BillSchedule::class, cascade: ['persist'], inversedBy: 'accessWishes')]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
+    protected BillSchedule $billSchedule;
+
+    #[ORM\OneToOne(mappedBy: 'accessWishRib', targetEntity: DocumentWish::class, cascade: ['persist'])]
+    protected DocumentWish $documentRib;
+
+    #[ORM\OneToOne(mappedBy: 'accessWishSepa', targetEntity: DocumentWish::class, cascade: ['persist'])]
+    protected DocumentWish $documentSepa;
+
+    #[ORM\ManyToOne(targetEntity: EducationCurriculumPack::class, cascade: [])]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
+    protected EducationCurriculumPack $wishPack;
+
+    #[ORM\ManyToOne(targetEntity: Place::class, cascade: [])]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
+    protected Place $favoritePlace;
+
     /**
      * Date de création dde l'entité.
      *
@@ -302,4 +323,64 @@ class AccessWish
     {
         $this->validationState = $validationState;
     }
+
+    public function getBillSchedule(): BillSchedule
+    {
+        return $this->billSchedule;
+    }
+
+    public function setBillSchedule(BillSchedule $billSchedule): self
+    {
+        $this->billSchedule = $billSchedule;
+
+        return $this;
+    }
+
+    public function getDocumentRib(): DocumentWish
+    {
+        return $this->documentRib;
+    }
+
+    public function setDocumentRib(DocumentWish $documentRib): self
+    {
+        $this->documentRib = $documentRib;
+
+        return $this;
+    }
+
+    public function getDocumentSepa(): DocumentWish
+    {
+        return $this->documentSepa;
+    }
+
+    public function setDocumentSepa(DocumentWish $documentSepa): self
+    {
+        $this->documentSepa = $documentSepa;
+
+        return $this;
+    }
+
+    public function getWishPack(): EducationCurriculumPack
+    {
+        return $this->wishPack;
+    }
+
+    public function setWishPack(EducationCurriculumPack $wishPack): self
+    {
+        $this->wishPack = $wishPack;
+
+        return $this;
+    }
+
+    public function getFavoritePlace(): Place
+    {
+        return $this->favoritePlace;
+    }
+
+    public function setFavoritePlace(Place $favoritePlace): self
+    {
+        $this->favoritePlace = $favoritePlace;
+
+        return $this;
+    }
 }

+ 30 - 0
src/Entity/AccessWish/DocumentWish.php

@@ -38,6 +38,12 @@ class DocumentWish
     #[ORM\OneToMany(targetEntity: File::class, mappedBy: 'documentWish', orphanRemoval: true)]
     private Collection $files;
 
+    #[ORM\OneToOne(inversedBy: 'documentRib', targetEntity: AccessWish::class, cascade: [])]
+    protected AccessWish $accessWishRib;
+
+    #[ORM\OneToOne(inversedBy: 'documentSepa', targetEntity: AccessWish::class, cascade: [])]
+    protected AccessWish $accessWishSepa;
+
     public function __construct()
     {
         $this->files = new ArrayCollection();
@@ -101,4 +107,28 @@ class DocumentWish
 
         return $this;
     }
+
+    public function getAccessWishRib(): AccessWish
+    {
+        return $this->accessWishRib;
+    }
+
+    public function setAccessWishRib(AccessWish $accessWishRib): self
+    {
+        $this->accessWishRib = $accessWishRib;
+
+        return $this;
+    }
+
+    public function getAccessWishSepa(): AccessWish
+    {
+        return $this->accessWishSepa;
+    }
+
+    public function setAccessWishSepa(AccessWish $accessWishSepa): self
+    {
+        $this->accessWishSepa = $accessWishSepa;
+
+        return $this;
+    }
 }

+ 66 - 0
src/Entity/AccessWish/EducationStudentWish.php

@@ -5,13 +5,18 @@ declare(strict_types=1);
 namespace App\Entity\AccessWish;
 
 use ApiPlatform\Metadata\ApiResource;
+use App\Entity\Access\Access;
+use App\Entity\Booking\Course;
 use App\Entity\Education\Education;
 use App\Entity\Education\EducationCurriculum;
 use App\Entity\Education\EducationStudent;
 use App\Entity\Education\EducationTiming;
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
+use App\Entity\Product\EquipmentList;
 use App\Enum\OnlineRegistration\RegistrationStatusEnum;
 use App\Enum\OnlineRegistration\WishRegistrationEnum;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
 
 /**
@@ -55,6 +60,22 @@ class EducationStudentWish
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
     private ?EducationTiming $educationTiming = null;
 
+    #[ORM\ManyToMany(targetEntity: Access::class, cascade: [], orphanRemoval: false)]
+    protected Collection $teachers;
+
+    #[ORM\ManyToOne(targetEntity: EquipmentList::class, cascade: [])]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
+    protected EquipmentList $speciality;
+
+    #[ORM\ManyToOne(targetEntity: Course::class, cascade: [], inversedBy: 'educationStudentWishes')]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
+    protected Course $course;
+
+    public function __construct()
+    {
+        $this->teachers = new ArrayCollection();
+    }
+
     public function getId(): ?int
     {
         return $this->id;
@@ -151,4 +172,49 @@ class EducationStudentWish
 
         return $this;
     }
+
+    public function getTeachers(): Collection
+    {
+        return $this->teachers;
+    }
+
+    public function addTeacher(Access $teacher): self
+    {
+        if (!$this->teachers->contains($teacher)) {
+            $this->teachers[] = $teacher;
+        }
+
+        return $this;
+    }
+
+    public function removeTeacher(Access $teacher): self
+    {
+        $this->teachers->removeElement($teacher);
+
+        return $this;
+    }
+
+    public function getSpeciality(): EquipmentList
+    {
+        return $this->speciality;
+    }
+
+    public function setSpeciality(EquipmentList $speciality): self
+    {
+        $this->speciality = $speciality;
+
+        return $this;
+    }
+
+    public function getCourse(): Course
+    {
+        return $this->course;
+    }
+
+    public function setCourse(Course $course): self
+    {
+        $this->course = $course;
+
+        return $this;
+    }
 }

+ 441 - 0
src/Entity/Awin/Product.php

@@ -0,0 +1,441 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Entity\Awin;
+
+use ApiPlatform\Metadata\ApiResource;
+use Doctrine\ORM\Mapping as ORM;
+
+#[ApiResource(operations: [])]
+#[ORM\Entity]
+#[ORM\Table(name: 'AwinProduct')]
+class Product
+{
+    #[ORM\Id]
+    #[ORM\Column]
+    #[ORM\GeneratedValue]
+    private int $id;
+
+    #[ORM\Column]
+    protected mixed $slug;
+
+    #[ORM\Column]
+    protected mixed $name;
+
+    #[ORM\Column(length: 255, options: ['nullable' => true])]
+    protected string $description;
+
+    #[ORM\Column]
+    protected mixed $categoryCode;
+
+    #[ORM\Column]
+    protected mixed $subCategory;
+
+    /**
+     * @var array<string>
+     */
+    #[ORM\Column(type: 'json', options: ['nullable' => true])]
+    protected array $categories;
+
+    #[ORM\Column]
+    protected mixed $datetimeStart;
+
+    #[ORM\Column]
+    protected mixed $datetimeEnd;
+
+    /**
+     * @var array<string>
+     */
+    #[ORM\Column(type: 'json', options: ['nullable' => true])]
+    protected array $meetingSchedule;
+
+    #[ORM\Column]
+    protected mixed $placeCode;
+
+    #[ORM\Column]
+    protected mixed $place;
+
+    #[ORM\Column]
+    protected mixed $streetAddress;
+
+    #[ORM\Column]
+    protected mixed $postalCode;
+
+    #[ORM\Column]
+    protected mixed $city;
+
+    #[ORM\Column]
+    protected mixed $country;
+
+    #[ORM\Column]
+    protected mixed $latitude;
+
+    #[ORM\Column]
+    protected mixed $longitude;
+
+    #[ORM\Column]
+    protected mixed $mediumimage;
+
+    #[ORM\Column]
+    protected mixed $largeimage;
+
+    #[ORM\Column(length: 255, options: ['nullable' => true])]
+    protected string $deepLink;
+
+    #[ORM\Column]
+    protected mixed $priceMini;
+
+    #[ORM\Column]
+    protected mixed $priceMaxi;
+
+    #[ORM\Column(length: 255, options: ['nullable' => true])]
+    protected string $artists;
+
+    #[ORM\Column(length: 255, options: ['nullable' => true])]
+    protected string $uuid;
+
+    #[ORM\Column(type: 'date', options: ['nullable' => true])]
+    protected ?\DateTimeInterface $createDate;
+
+    #[ORM\Column(type: 'date', options: ['nullable' => true])]
+    protected ?\DateTimeInterface $updateDate;
+
+    public function getId(): int
+    {
+        return $this->id;
+    }
+
+    public function setId(int $id): self
+    {
+        $this->id = $id;
+
+        return $this;
+    }
+
+    public function getSlug(): mixed
+    {
+        return $this->slug;
+    }
+
+    public function setSlug(mixed $slug): self
+    {
+        $this->slug = $slug;
+
+        return $this;
+    }
+
+    public function getName(): mixed
+    {
+        return $this->name;
+    }
+
+    public function setName(mixed $name): self
+    {
+        $this->name = $name;
+
+        return $this;
+    }
+
+    public function getDescription(): string
+    {
+        return $this->description;
+    }
+
+    public function setDescription(string $description): self
+    {
+        $this->description = $description;
+
+        return $this;
+    }
+
+    public function getCategoryCode(): mixed
+    {
+        return $this->categoryCode;
+    }
+
+    public function setCategoryCode(mixed $categoryCode): self
+    {
+        $this->categoryCode = $categoryCode;
+
+        return $this;
+    }
+
+    public function getSubCategory(): mixed
+    {
+        return $this->subCategory;
+    }
+
+    public function setSubCategory(mixed $subCategory): self
+    {
+        $this->subCategory = $subCategory;
+
+        return $this;
+    }
+
+    /**
+     * @return string[]
+     */
+    public function getCategories(): array
+    {
+        return $this->categories;
+    }
+
+    /**
+     * @param string[] $categories
+     */
+    public function setCategories(array $categories): self
+    {
+        $this->categories = $categories;
+
+        return $this;
+    }
+
+    public function getDatetimeStart(): mixed
+    {
+        return $this->datetimeStart;
+    }
+
+    public function setDatetimeStart(mixed $datetimeStart): self
+    {
+        $this->datetimeStart = $datetimeStart;
+
+        return $this;
+    }
+
+    public function getDatetimeEnd(): mixed
+    {
+        return $this->datetimeEnd;
+    }
+
+    public function setDatetimeEnd(mixed $datetimeEnd): self
+    {
+        $this->datetimeEnd = $datetimeEnd;
+
+        return $this;
+    }
+
+    /**
+     * @return string[]
+     */
+    public function getMeetingSchedule(): array
+    {
+        return $this->meetingSchedule;
+    }
+
+    /**
+     * @param array<string> $meetingSchedule
+     *
+     * @return $this
+     */
+    public function setMeetingSchedule(array $meetingSchedule): self
+    {
+        $this->meetingSchedule = $meetingSchedule;
+
+        return $this;
+    }
+
+    public function getPlaceCode(): mixed
+    {
+        return $this->placeCode;
+    }
+
+    public function setPlaceCode(mixed $placeCode): self
+    {
+        $this->placeCode = $placeCode;
+
+        return $this;
+    }
+
+    public function getPlace(): mixed
+    {
+        return $this->place;
+    }
+
+    public function setPlace(mixed $place): self
+    {
+        $this->place = $place;
+
+        return $this;
+    }
+
+    public function getStreetAddress(): mixed
+    {
+        return $this->streetAddress;
+    }
+
+    public function setStreetAddress(mixed $streetAddress): self
+    {
+        $this->streetAddress = $streetAddress;
+
+        return $this;
+    }
+
+    public function getPostalCode(): mixed
+    {
+        return $this->postalCode;
+    }
+
+    public function setPostalCode(mixed $postalCode): self
+    {
+        $this->postalCode = $postalCode;
+
+        return $this;
+    }
+
+    public function getCity(): mixed
+    {
+        return $this->city;
+    }
+
+    public function setCity(mixed $city): self
+    {
+        $this->city = $city;
+
+        return $this;
+    }
+
+    public function getCountry(): mixed
+    {
+        return $this->country;
+    }
+
+    public function setCountry(mixed $country): self
+    {
+        $this->country = $country;
+
+        return $this;
+    }
+
+    public function getLatitude(): mixed
+    {
+        return $this->latitude;
+    }
+
+    public function setLatitude(mixed $latitude): self
+    {
+        $this->latitude = $latitude;
+
+        return $this;
+    }
+
+    public function getLongitude(): mixed
+    {
+        return $this->longitude;
+    }
+
+    public function setLongitude(mixed $longitude): self
+    {
+        $this->longitude = $longitude;
+
+        return $this;
+    }
+
+    public function getMediumimage(): mixed
+    {
+        return $this->mediumimage;
+    }
+
+    public function setMediumimage(mixed $mediumimage): self
+    {
+        $this->mediumimage = $mediumimage;
+
+        return $this;
+    }
+
+    public function getLargeimage(): mixed
+    {
+        return $this->largeimage;
+    }
+
+    public function setLargeimage(mixed $largeimage): self
+    {
+        $this->largeimage = $largeimage;
+
+        return $this;
+    }
+
+    public function getDeepLink(): string
+    {
+        return $this->deepLink;
+    }
+
+    public function setDeepLink(string $deepLink): self
+    {
+        $this->deepLink = $deepLink;
+
+        return $this;
+    }
+
+    public function getPriceMini(): mixed
+    {
+        return $this->priceMini;
+    }
+
+    public function setPriceMini(mixed $priceMini): self
+    {
+        $this->priceMini = $priceMini;
+
+        return $this;
+    }
+
+    public function getPriceMaxi(): mixed
+    {
+        return $this->priceMaxi;
+    }
+
+    public function setPriceMaxi(mixed $priceMaxi): self
+    {
+        $this->priceMaxi = $priceMaxi;
+
+        return $this;
+    }
+
+    public function getArtists(): string
+    {
+        return $this->artists;
+    }
+
+    public function setArtists(string $artists): self
+    {
+        $this->artists = $artists;
+
+        return $this;
+    }
+
+    public function getUuid(): string
+    {
+        return $this->uuid;
+    }
+
+    public function setUuid(string $uuid): self
+    {
+        $this->uuid = $uuid;
+
+        return $this;
+    }
+
+    public function getCreateDate(): \DateTimeInterface
+    {
+        return $this->createDate;
+    }
+
+    public function setCreateDate(\DateTimeInterface $createDate): self
+    {
+        $this->createDate = $createDate;
+
+        return $this;
+    }
+
+    public function getUpdateDate(): \DateTimeInterface
+    {
+        return $this->updateDate;
+    }
+
+    public function setUpdateDate(\DateTimeInterface $updateDate): self
+    {
+        $this->updateDate = $updateDate;
+
+        return $this;
+    }
+}

+ 444 - 0
src/Entity/Billing/AbstractBillAccounting.php

@@ -0,0 +1,444 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Entity\Billing;
+
+use App\Entity\Access\Access;
+use App\Entity\Core\File;
+use App\Entity\Core\Tagg;
+use App\Entity\Organization\Organization;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * Facture ou avoir.
+ *
+ * @see Bill, BillCredit, AdvancePayment
+ *
+ * @todo : A la suite de la migration, il faut supprimer le nom de la table pour avoir une table BillAccounting, et supprimer l'attribut discr.
+ */
+#[ORM\MappedSuperclass]
+abstract class AbstractBillAccounting
+{
+    #[ORM\Id]
+    #[ORM\Column]
+    #[ORM\GeneratedValue]
+    protected ?int $id = null;
+
+    #[ORM\Column(length: 255, nullable: false)]
+    protected string $discr;
+
+    #[ORM\ManyToOne(targetEntity: Organization::class)]
+    #[ORM\JoinColumn(nullable: true)]
+    protected Organization $organization;
+
+    #[ORM\OneToMany(mappedBy: 'bill', targetEntity: BillLine::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $billLines;
+
+    #[ORM\OneToMany(mappedBy: 'bill', targetEntity: BillCredit::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $billCredits;
+
+    #[ORM\OneToMany(mappedBy: 'bill', targetEntity: BillPayment::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $billPayments;
+
+    #[ORM\ManyToOne(inversedBy: 'billCredits')]
+    #[ORM\JoinColumn(nullable: true)]
+    protected AbstractBillAccounting $bill;
+
+    #[ORM\OneToMany(mappedBy: 'bill', targetEntity: BillingIntangibleExcludeDate::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $billingIntangibleExcludeDates;
+
+    #[ORM\ManyToOne(cascade: ['persist'], inversedBy: 'bills')]
+    #[ORM\JoinColumn(nullable: true)]
+    protected Pes $pes;
+
+    #[ORM\ManyToOne(cascade: ['persist'], inversedBy: 'bills')]
+    #[ORM\JoinColumn(nullable: true)]
+    protected BergerLevrault $bergerLevrault;
+
+    #[ORM\ManyToOne(cascade: ['persist'], inversedBy: 'bills')]
+    #[ORM\JoinColumn(nullable: true)]
+    protected Ciril $ciril;
+
+    #[ORM\ManyToOne(cascade: ['persist'], inversedBy: 'bills')]
+    #[ORM\JoinColumn(nullable: true)]
+    protected Jvs $jvs;
+
+    #[ORM\ManyToMany(targetEntity: Tagg::class, inversedBy: 'billAccountings', cascade: ['persist'])]
+    #[ORM\JoinTable(name: 'tag_billAccounting')]
+    #[ORM\JoinColumn(name: 'billAccounting_id', referencedColumnName: 'id')]
+    #[ORM\InverseJoinColumn(name: 'tag_id', referencedColumnName: 'id')]
+    protected Collection $tags;
+
+    #[ORM\ManyToOne(targetEntity: Access::class, cascade: [], inversedBy: 'bills')]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: false, onDelete: 'SET NULL')]
+    protected Access $access;
+
+    #[ORM\OneToOne(targetEntity: BillAccessDetail::class, cascade: ['persist'])]
+    protected BillAccessDetail $accessDetail;
+
+    #[ORM\OneToOne(targetEntity: BillPeriod::class, cascade: ['persist'])]
+    protected BillPeriod $billPeriod;
+
+    #[ORM\OneToOne(targetEntity: BillTotalDetail::class, cascade: ['persist'])]
+    protected BillTotalDetail $totalDetail;
+
+    #[ORM\OneToOne(targetEntity: File::class, cascade: ['persist'])]
+    protected File $file;
+
+    #[ORM\ManyToOne(targetEntity: Odyssee::class, inversedBy: 'bills', cascade: ['persist'])]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: false, onDelete: 'SET NULL')]
+    protected Odyssee $odyssee; // TODO: sûr que c'est pas nullable?
+
+    #[ORM\ManyToOne(targetEntity: Afi::class, inversedBy: 'bills', cascade: ['persist'])]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: false, onDelete: 'SET NULL')]
+    protected Afi $afi;
+
+    #[ORM\ManyToOne(targetEntity: CirilCivil::class, inversedBy: 'bills', cascade: ['persist'])]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: false, onDelete: 'SET NULL')]
+    protected CirilCivil $cirilCivil;
+
+    public function __construct()
+    {
+        $this->billLines = new ArrayCollection();
+        $this->billCredits = new ArrayCollection();
+        $this->billPayments = new ArrayCollection();
+        $this->billingIntangibleExcludeDates = new ArrayCollection();
+        $this->tags = new ArrayCollection();
+    }
+
+    public function getId(): ?int
+    {
+        return $this->id;
+    }
+
+    public function getDiscr(): ?string
+    {
+        return $this->discr;
+    }
+
+    public function setDiscr(string $discr): self
+    {
+        $this->discr = $discr;
+
+        return $this;
+    }
+
+    public function getOrganization(): ?Organization
+    {
+        return $this->organization;
+    }
+
+    public function setOrganization(?Organization $organization): self
+    {
+        $this->organization = $organization;
+
+        return $this;
+    }
+
+    /**
+     * @return Collection<int, BillLine>
+     */
+    public function getBillLines(): Collection
+    {
+        return $this->billLines;
+    }
+
+    public function addBillLine(BillLine $billLine): self
+    {
+        if (!$this->billLines->contains($billLine)) {
+            $this->billLines[] = $billLine;
+            $billLine->setBill($this);
+        }
+
+        return $this;
+    }
+
+    public function removeBillLine(BillLine $billLine): self
+    {
+        if ($this->billLines->removeElement($billLine)) {
+            // set the owning side to null (unless already changed)
+            if ($billLine->getBill() === $this) {
+                $billLine->setBill(null);
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * @return Collection<int, BillCredit>
+     */
+    public function getBillCredits(): Collection
+    {
+        return $this->billCredits;
+    }
+
+    public function addBillCredit(BillCredit $billCredit): self
+    {
+        if (!$this->billCredits->contains($billCredit)) {
+            $this->billCredits[] = $billCredit;
+            $billCredit->setBill($this);
+        }
+
+        return $this;
+    }
+
+    public function removeBillCredit(BillCredit $billCredit): self
+    {
+        if ($this->billCredits->removeElement($billCredit)) {
+            // set the owning side to null (unless already changed)
+            if ($billCredit->getBill() === $this) {
+                $billCredit->setBill(null);
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * @return Collection<int, BillPayment>
+     */
+    public function getBillPayments(): Collection
+    {
+        return $this->billPayments;
+    }
+
+    public function addBillPayment(BillPayment $billPayment): self
+    {
+        if (!$this->billPayments->contains($billPayment)) {
+            $this->billPayments[] = $billPayment;
+            $billPayment->setBill($this);
+        }
+
+        return $this;
+    }
+
+    public function removeBillPayment(BillPayment $billPayment): self
+    {
+        if ($this->billPayments->removeElement($billPayment)) {
+            // set the owning side to null (unless already changed)
+            if ($billPayment->getBill() === $this) {
+                $billPayment->setBill(null);
+            }
+        }
+
+        return $this;
+    }
+
+    public function getBill(): ?self
+    {
+        return $this->bill;
+    }
+
+    public function setBill(?self $bill): self
+    {
+        $this->bill = $bill;
+
+        return $this;
+    }
+
+    /**
+     * @return Collection<int, BillingIntangibleExcludeDate>
+     */
+    public function getBillingIntangibleExcludeDates(): Collection
+    {
+        return $this->billingIntangibleExcludeDates;
+    }
+
+    public function addBillingIntangibleExcludeDate(BillingIntangibleExcludeDate $billingIntangibleExcludeDate): self
+    {
+        if (!$this->billingIntangibleExcludeDates->contains($billingIntangibleExcludeDate)) {
+            $this->billingIntangibleExcludeDates[] = $billingIntangibleExcludeDate;
+            /* @phpstan-ignore-next-line */
+            $billingIntangibleExcludeDate->setBill($this);
+        }
+
+        return $this;
+    }
+
+    public function removeBillingIntangibleExcludeDate(BillingIntangibleExcludeDate $billingIntangibleExcludeDate): self
+    {
+        if ($this->billingIntangibleExcludeDates->removeElement($billingIntangibleExcludeDate)) {
+            // set the owning side to null (unless already changed)
+            if ($billingIntangibleExcludeDate->getBill() === $this) {
+                $billingIntangibleExcludeDate->setBill(null);
+            }
+        }
+
+        return $this;
+    }
+
+    public function getPes(): ?Pes
+    {
+        return $this->pes;
+    }
+
+    public function setPes(?Pes $pes): self
+    {
+        $this->pes = $pes;
+
+        return $this;
+    }
+
+    public function getBergerLevrault(): ?BergerLevrault
+    {
+        return $this->bergerLevrault;
+    }
+
+    public function setBergerLevrault(?BergerLevrault $bergerLevrault): self
+    {
+        $this->bergerLevrault = $bergerLevrault;
+
+        return $this;
+    }
+
+    public function getCiril(): ?Ciril
+    {
+        return $this->ciril;
+    }
+
+    public function setCiril(?Ciril $ciril): self
+    {
+        $this->ciril = $ciril;
+
+        return $this;
+    }
+
+    public function getJvs(): ?Jvs
+    {
+        return $this->jvs;
+    }
+
+    public function setJvs(?Jvs $jvs): self
+    {
+        $this->jvs = $jvs;
+
+        return $this;
+    }
+
+    /**
+     * @return Collection<int, Tagg>
+     */
+    public function getTags(): Collection
+    {
+        return $this->tags;
+    }
+
+    public function addTag(Tagg $tag): self
+    {
+        if (!$this->tags->contains($tag)) {
+            $this->tags[] = $tag;
+            $tag->addBillAccounting($this);
+        }
+
+        return $this;
+    }
+
+    public function removeTag(Tagg $tag): self
+    {
+        if ($this->tags->removeElement($tag)) {
+            $tag->removeBillAccounting($this);
+        }
+
+        return $this;
+    }
+
+    public function getAccess(): Access
+    {
+        return $this->access;
+    }
+
+    public function setAccess(?Access $access): self
+    {
+        $this->access = $access;
+
+        return $this;
+    }
+
+    public function getAccessDetail(): BillAccessDetail
+    {
+        return $this->accessDetail;
+    }
+
+    public function setAccessDetail(BillAccessDetail $accessDetail): self
+    {
+        $this->accessDetail = $accessDetail;
+
+        return $this;
+    }
+
+    public function getBillPeriod(): BillPeriod
+    {
+        return $this->billPeriod;
+    }
+
+    public function setBillPeriod(BillPeriod $billPeriod): self
+    {
+        $this->billPeriod = $billPeriod;
+
+        return $this;
+    }
+
+    public function getTotalDetail(): BillTotalDetail
+    {
+        return $this->totalDetail;
+    }
+
+    public function setTotalDetail(BillTotalDetail $totalDetail): self
+    {
+        $this->totalDetail = $totalDetail;
+
+        return $this;
+    }
+
+    public function getFile(): File
+    {
+        return $this->file;
+    }
+
+    public function setFile(File $file): self
+    {
+        $this->file = $file;
+
+        return $this;
+    }
+
+    public function getOdyssee(): Odyssee
+    {
+        return $this->odyssee;
+    }
+
+    public function setOdyssee(Odyssee $odyssee): self
+    {
+        $this->odyssee = $odyssee;
+
+        return $this;
+    }
+
+    public function getAfi(): Afi
+    {
+        return $this->afi;
+    }
+
+    public function setAfi(Afi $afi): self
+    {
+        $this->afi = $afi;
+
+        return $this;
+    }
+
+    public function getCirilCivil(): CirilCivil
+    {
+        return $this->cirilCivil;
+    }
+
+    public function setCirilCivil(CirilCivil $cirilCivil): self
+    {
+        $this->cirilCivil = $cirilCivil;
+
+        return $this;
+    }
+}

+ 16 - 0
src/Entity/Billing/AccessBilling.php

@@ -49,6 +49,10 @@ class AccessBilling
     #[ORM\OneToMany(targetEntity: BillPayment::class, mappedBy: 'accessBillingAccountBalanceReimbursement', cascade: ['persist'], orphanRemoval: true)]
     private Collection $accountBalanceReimbursements;
 
+    #[ORM\ManyToOne(targetEntity: BillSchedule::class, cascade: ['persist'], inversedBy: 'accessBilling')]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
+    protected BillSchedule $billSchedule;
+
     public function __construct()
     {
         $this->billDetachedPayments = new ArrayCollection();
@@ -186,4 +190,16 @@ class AccessBilling
 
         return $this;
     }
+
+    public function getBillSchedule(): BillSchedule
+    {
+        return $this->billSchedule;
+    }
+
+    public function setBillSchedule(BillSchedule $billSchedule): self
+    {
+        $this->billSchedule = $billSchedule;
+
+        return $this;
+    }
 }

+ 18 - 0
src/Entity/Billing/AdvancePayment.php

@@ -0,0 +1,18 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Entity\Billing;
+
+use ApiPlatform\Metadata\ApiResource;
+use Doctrine\ORM\Mapping as ORM;
+
+#[ApiResource(operations: [])]
+// #[Auditable]
+#[ORM\Table(name: 'BillAccounting')]
+#[ORM\Entity]
+class AdvancePayment extends AbstractBillAccounting
+{
+    #[ORM\Column(length: 255, nullable: false)]
+    protected string $discr = 'advancepayment';
+}

+ 74 - 0
src/Entity/Billing/Afi.php

@@ -0,0 +1,74 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Entity\Billing;
+
+use ApiPlatform\Metadata\ApiResource;
+use App\Entity\Core\File;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ORM\Mapping as ORM;
+
+#[ApiResource(operations: [])]
+#[ORM\Entity]
+class Afi
+{
+    #[ORM\Id]
+    #[ORM\Column]
+    #[ORM\GeneratedValue]
+    private int $id;
+
+    #[ORM\OneToMany(mappedBy: 'afi', targetEntity: Bill::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $bills;
+
+    #[ORM\OneToOne(targetEntity: File::class, cascade: ['persist'])]
+    protected File $file;
+
+    public function getId(): int
+    {
+        return $this->id;
+    }
+
+    public function setId(int $id): self
+    {
+        $this->id = $id;
+
+        return $this;
+    }
+
+    public function getBills(): Collection
+    {
+        return $this->bills;
+    }
+
+    public function addBill(Bill $bill): self
+    {
+        if (!$this->bills->contains($bill)) {
+            $this->bills[] = $bill;
+            $bill->setAfi($this);
+        }
+
+        return $this;
+    }
+
+    public function removeBill(Bill $bill): self
+    {
+        if ($this->bills->removeElement($bill)) {
+            //            $bill->setAfi(null);   // TODO: actuellement, pas nullable: conserver?
+        }
+
+        return $this;
+    }
+
+    public function getFile(): File
+    {
+        return $this->file;
+    }
+
+    public function setFile(File $file): self
+    {
+        $this->file = $file;
+
+        return $this;
+    }
+}

+ 16 - 0
src/Entity/Billing/BergerLevrault.php

@@ -7,6 +7,7 @@ namespace App\Entity\Billing;
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use ApiPlatform\Metadata\ApiResource;
 use App\Entity\Person\PersonActivity;
+use App\Entity\Core\File;
 use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
@@ -28,6 +29,9 @@ class BergerLevrault
     #[ORM\OneToMany(targetEntity: Bill::class, mappedBy: 'bergerLevrault', cascade: ['persist'], orphanRemoval: true)]
     private Collection $bills;
 
+    #[ORM\OneToOne(targetEntity: File::class, cascade: ['persist'])]
+    protected File $file;
+
     public function __construct()
     {
         $this->bills = new ArrayCollection();
@@ -67,4 +71,16 @@ class BergerLevrault
 
         return $this;
     }
+
+    public function getFile(): File
+    {
+        return $this->file;
+    }
+
+    public function setFile(File $file): self
+    {
+        $this->file = $file;
+
+        return $this;
+    }
 }

+ 2 - 7
src/Entity/Billing/Bill.php

@@ -6,18 +6,13 @@ namespace App\Entity\Billing;
 
 use ApiPlatform\Metadata\ApiResource;
 use App\Entity\Access\Access;
-// use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use Doctrine\ORM\Mapping as ORM;
 
-/**
- * @todo : A la suite de la migration, il faut supprimer le nom de la table pour avoir une table Bill, et supprimer l'attribut discr.
- * Classe ... qui ...
- */
 #[ApiResource(operations: [])]
 // #[Auditable]
-#[ORM\Entity]
 #[ORM\Table(name: 'BillAccounting')]
-class Bill extends BillAccounting implements BillAccountingInterface
+#[ORM\Entity]
+class Bill extends AbstractBillAccounting
 {
     #[ORM\ManyToOne(inversedBy: 'bills')]
     #[ORM\JoinColumn(nullable: false)]

+ 2 - 293
src/Entity/Billing/BillAccounting.php

@@ -5,19 +5,10 @@ declare(strict_types=1);
 namespace App\Entity\Billing;
 
 use ApiPlatform\Metadata\ApiResource;
-use App\Entity\Core\Tagg;
-use App\Entity\Organization\Organization;
-// use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
-use App\Entity\Person\PersonActivity;
-use Doctrine\Common\Collections\ArrayCollection;
-use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
 
-/**
- * @todo : A la suite de la migration, il faut supprimer le nom de la table pour avoir une table BillAccounting, et supprimer l'attribut discr.
- * Classe ... qui ...
- */
 #[ApiResource(operations: [])]
+#[ORM\Table(name: 'BillAccounting')]
 // #[Auditable]
 #[ORM\Entity]
 #[ORM\InheritanceType('SINGLE_TABLE')]
@@ -29,288 +20,6 @@ use Doctrine\ORM\Mapping as ORM;
         'billcredit' => BillCredit::class,
     ]
 )]
-class BillAccounting
+class BillAccounting extends AbstractBillAccounting
 {
-    #[ORM\Id]
-    #[ORM\Column]
-    #[ORM\GeneratedValue]
-    protected ?int $id = null;
-
-    #[ORM\ManyToOne]
-    #[ORM\JoinColumn(nullable: true)]
-    protected ?Organization $organization = null;
-
-    /** @var Collection<int, BillLine> */
-    #[ORM\OneToMany(targetEntity: BillLine::class, mappedBy: 'bill', cascade: ['persist'], orphanRemoval: true)]
-    protected Collection $billLines;
-
-    /** @var Collection<int, BillCredit> */
-    #[ORM\OneToMany(targetEntity: BillCredit::class, mappedBy: 'bill', cascade: ['persist'], orphanRemoval: true)]
-    protected Collection $billCredits;
-
-    /** @var Collection<int, BillPayment> */
-    #[ORM\OneToMany(targetEntity: BillPayment::class, mappedBy: 'bill', cascade: ['persist'], orphanRemoval: true)]
-    protected Collection $billPayments;
-
-    #[ORM\ManyToOne(inversedBy: 'billCredits')]
-    #[ORM\JoinColumn(nullable: true)]
-    protected ?BillAccounting $bill = null;
-
-    /** @var Collection<int, BillingIntangibleExcludeDate> */
-    #[ORM\OneToMany(targetEntity: BillingIntangibleExcludeDate::class, mappedBy: 'bill', cascade: ['persist'], orphanRemoval: true)]
-    protected Collection $billingIntangibleExcludeDates;
-
-    #[ORM\ManyToOne(cascade: ['persist'], inversedBy: 'bills')]
-    #[ORM\JoinColumn(nullable: true)]
-    protected ?Pes $pes = null;
-
-    #[ORM\ManyToOne(cascade: ['persist'], inversedBy: 'bills')]
-    #[ORM\JoinColumn(nullable: true)]
-    protected ?BergerLevrault $bergerLevrault = null;
-
-    #[ORM\ManyToOne(cascade: ['persist'], inversedBy: 'bills')]
-    #[ORM\JoinColumn(nullable: true)]
-    protected ?Ciril $ciril = null;
-
-    #[ORM\ManyToOne(cascade: ['persist'], inversedBy: 'bills')]
-    #[ORM\JoinColumn(nullable: true)]
-    protected ?Jvs $jvs = null;
-
-    /** @var Collection<int, Tagg> */
-    #[ORM\ManyToMany(targetEntity: Tagg::class, inversedBy: 'billAccountings', cascade: ['persist'])]
-    #[ORM\JoinTable(name: 'tag_billAccounting')]
-    #[ORM\JoinColumn(name: 'billAccounting_id', referencedColumnName: 'id')]
-    #[ORM\InverseJoinColumn(name: 'tag_id', referencedColumnName: 'id')]
-    protected Collection $tags;
-
-    public function __construct()
-    {
-        $this->billLines = new ArrayCollection();
-        $this->billCredits = new ArrayCollection();
-        $this->billPayments = new ArrayCollection();
-        $this->billingIntangibleExcludeDates = new ArrayCollection();
-        $this->tags = new ArrayCollection();
-    }
-
-    public function getId(): ?int
-    {
-        return $this->id;
-    }
-
-    public function getOrganization(): ?Organization
-    {
-        return $this->organization;
-    }
-
-    public function setOrganization(?Organization $organization): self
-    {
-        $this->organization = $organization;
-
-        return $this;
-    }
-
-    /**
-     * @return Collection<int, BillLine>
-     */
-    public function getBillLines(): Collection
-    {
-        return $this->billLines;
-    }
-
-    public function addBillLine(BillLine $billLine): self
-    {
-        if (!$this->billLines->contains($billLine)) {
-            $this->billLines[] = $billLine;
-            $billLine->setBill($this);
-        }
-
-        return $this;
-    }
-
-    public function removeBillLine(BillLine $billLine): self
-    {
-        if ($this->billLines->removeElement($billLine)) {
-            // set the owning side to null (unless already changed)
-            if ($billLine->getBill() === $this) {
-                $billLine->setBill(null);
-            }
-        }
-
-        return $this;
-    }
-
-    /**
-     * @return Collection<int, BillCredit>
-     */
-    public function getBillCredits(): Collection
-    {
-        return $this->billCredits;
-    }
-
-    public function addBillCredit(BillCredit $billCredit): self
-    {
-        if (!$this->billCredits->contains($billCredit)) {
-            $this->billCredits[] = $billCredit;
-            $billCredit->setBill($this);
-        }
-
-        return $this;
-    }
-
-    public function removeBillCredit(BillCredit $billCredit): self
-    {
-        if ($this->billCredits->removeElement($billCredit)) {
-            // set the owning side to null (unless already changed)
-            if ($billCredit->getBill() === $this) {
-                $billCredit->setBill(null);
-            }
-        }
-
-        return $this;
-    }
-
-    /**
-     * @return Collection<int, BillPayment>
-     */
-    public function getBillPayments(): Collection
-    {
-        return $this->billPayments;
-    }
-
-    public function addBillPayment(BillPayment $billPayment): self
-    {
-        if (!$this->billPayments->contains($billPayment)) {
-            $this->billPayments[] = $billPayment;
-            $billPayment->setBill($this);
-        }
-
-        return $this;
-    }
-
-    public function removeBillPayment(BillPayment $billPayment): self
-    {
-        if ($this->billPayments->removeElement($billPayment)) {
-            // set the owning side to null (unless already changed)
-            if ($billPayment->getBill() === $this) {
-                $billPayment->setBill(null);
-            }
-        }
-
-        return $this;
-    }
-
-    public function getBill(): ?self
-    {
-        return $this->bill;
-    }
-
-    public function setBill(?self $bill): self
-    {
-        $this->bill = $bill;
-
-        return $this;
-    }
-
-    /**
-     * @return Collection<int, BillingIntangibleExcludeDate>
-     */
-    public function getBillingIntangibleExcludeDates(): Collection
-    {
-        return $this->billingIntangibleExcludeDates;
-    }
-
-    public function addBillingIntangibleExcludeDate(BillingIntangibleExcludeDate $billingIntangibleExcludeDate): self
-    {
-        if (!$this->billingIntangibleExcludeDates->contains($billingIntangibleExcludeDate)) {
-            $this->billingIntangibleExcludeDates[] = $billingIntangibleExcludeDate;
-            /* @phpstan-ignore-next-line */
-            $billingIntangibleExcludeDate->setBill($this);
-        }
-
-        return $this;
-    }
-
-    public function removeBillingIntangibleExcludeDate(BillingIntangibleExcludeDate $billingIntangibleExcludeDate): self
-    {
-        if ($this->billingIntangibleExcludeDates->removeElement($billingIntangibleExcludeDate)) {
-            // set the owning side to null (unless already changed)
-            if ($billingIntangibleExcludeDate->getBill() === $this) {
-                $billingIntangibleExcludeDate->setBill(null);
-            }
-        }
-
-        return $this;
-    }
-
-    public function getPes(): ?Pes
-    {
-        return $this->pes;
-    }
-
-    public function setPes(?Pes $pes): self
-    {
-        $this->pes = $pes;
-
-        return $this;
-    }
-
-    public function getBergerLevrault(): ?BergerLevrault
-    {
-        return $this->bergerLevrault;
-    }
-
-    public function setBergerLevrault(?BergerLevrault $bergerLevrault): self
-    {
-        $this->bergerLevrault = $bergerLevrault;
-
-        return $this;
-    }
-
-    public function getCiril(): ?Ciril
-    {
-        return $this->ciril;
-    }
-
-    public function setCiril(?Ciril $ciril): self
-    {
-        $this->ciril = $ciril;
-
-        return $this;
-    }
-
-    public function getJvs(): ?Jvs
-    {
-        return $this->jvs;
-    }
-
-    public function setJvs(?Jvs $jvs): self
-    {
-        $this->jvs = $jvs;
-
-        return $this;
-    }
-
-    /**
-     * @return Collection<int, Tagg>
-     */
-    public function getTags(): Collection
-    {
-        return $this->tags;
-    }
-
-    public function addTag(Tagg $tag): self
-    {
-        if (!$this->tags->contains($tag)) {
-            $this->tags[] = $tag;
-        }
-
-        return $this;
-    }
-
-    public function removeTag(Tagg $tag): self
-    {
-        $this->tags->removeElement($tag);
-
-        return $this;
-    }
 }

+ 2 - 24
src/Entity/Billing/BillCredit.php

@@ -5,34 +5,12 @@ declare(strict_types=1);
 namespace App\Entity\Billing;
 
 use ApiPlatform\Metadata\ApiResource;
-use App\Entity\Access\Access;
-// use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use Doctrine\ORM\Mapping as ORM;
 
-/**
- * @todo : A la suite de la migration, il faut supprimer le nom de la table pour avoir une table BillCredit, et supprimer l'attribut discr.
- *
- * Classe ... qui ...
- */
 #[ApiResource(operations: [])]
 // #[Auditable]
-#[ORM\Entity]
 #[ORM\Table(name: 'BillAccounting')]
-class BillCredit extends BillAccounting implements BillAccountingInterface
+#[ORM\Entity]
+class BillCredit extends AbstractBillAccounting
 {
-    #[ORM\ManyToOne(inversedBy: 'billCredits')]
-    #[ORM\JoinColumn(nullable: false)]
-    protected Access $access;
-
-    public function getAccess(): ?Access
-    {
-        return $this->access;
-    }
-
-    public function setAccess(?Access $access): self
-    {
-        $this->access = $access;
-
-        return $this;
-    }
 }

+ 3 - 3
src/Entity/Billing/BillLine.php

@@ -26,7 +26,7 @@ class BillLine
 
     #[ORM\ManyToOne(inversedBy: 'billLines')]
     #[ORM\JoinColumn(nullable: false)]
-    private BillAccounting $bill;
+    private AbstractBillAccounting $bill;
 
     #[ORM\ManyToOne(inversedBy: 'billLines')]
     private ?Access $access = null;
@@ -42,12 +42,12 @@ class BillLine
         return $this->id;
     }
 
-    public function getBill(): ?BillAccounting
+    public function getBill(): ?AbstractBillAccounting
     {
         return $this->bill;
     }
 
-    public function setBill(?BillAccounting $bill): self
+    public function setBill(?AbstractBillAccounting $bill): self
     {
         $this->bill = $bill;
 

+ 35 - 3
src/Entity/Billing/BillPayment.php

@@ -27,7 +27,7 @@ class BillPayment
 
     #[ORM\ManyToOne(cascade: ['persist'], inversedBy: 'billPayments')]
     #[ORM\JoinColumn(nullable: true)]
-    private ?BillAccounting $bill = null;
+    private ?AbstractBillAccounting $bill = null;
 
     #[ORM\ManyToOne(cascade: ['persist'], inversedBy: 'billDetachedPayments')]
     #[ORM\JoinColumn(nullable: true)]
@@ -49,6 +49,14 @@ class BillPayment
     #[ORM\InverseJoinColumn(name: 'tag_id', referencedColumnName: 'id')]
     private Collection $tags;
 
+    #[ORM\ManyToOne(targetEntity: SddBank::class, cascade: ['persist'], inversedBy: 'billPayments')]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
+    protected SddBank $sddBank;
+
+    #[ORM\ManyToOne(targetEntity: SddRegie::class, cascade: ['persist'], inversedBy: 'billPayments')]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
+    protected SddRegie $sddRegie;
+
     public function __construct()
     {
         $this->billDebitBalances = new ArrayCollection();
@@ -60,12 +68,12 @@ class BillPayment
         return $this->id;
     }
 
-    public function getBill(): ?BillAccounting
+    public function getBill(): ?AbstractBillAccounting
     {
         return $this->bill;
     }
 
-    public function setBill(?BillAccounting $bill): self
+    public function setBill(?AbstractBillAccounting $bill): self
     {
         $this->bill = $bill;
 
@@ -149,4 +157,28 @@ class BillPayment
 
         return $this;
     }
+
+    public function getSddBank(): SddBank
+    {
+        return $this->sddBank;
+    }
+
+    public function setSddBank(SddBank $sddBank): self
+    {
+        $this->sddBank = $sddBank;
+
+        return $this;
+    }
+
+    public function getSddRegie(): SddRegie
+    {
+        return $this->sddRegie;
+    }
+
+    public function setSddRegie(SddRegie $sddRegie): self
+    {
+        $this->sddRegie = $sddRegie;
+
+        return $this;
+    }
 }

+ 130 - 0
src/Entity/Billing/BillSchedule.php

@@ -0,0 +1,130 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Entity\Billing;
+
+use ApiPlatform\Metadata\ApiResource;
+use App\Entity\AccessWish\AccessWish;
+use App\Entity\Organization\Organization;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ORM\Mapping as ORM;
+
+#[ApiResource(operations: [])]
+#[ORM\Entity]
+class BillSchedule
+{
+    #[ORM\Id]
+    #[ORM\Column]
+    #[ORM\GeneratedValue]
+    private int $id;
+
+    #[ORM\ManyToOne(targetEntity: Organization::class, cascade: [], inversedBy: 'billSchedules')]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: false, onDelete: 'SET NULL')]
+    protected Organization $organization;
+
+    #[ORM\OneToMany(mappedBy: 'billSchedule', targetEntity: BillScheduleDate::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $scheduleDates;
+
+    #[ORM\OneToMany(mappedBy: 'billSchedule', targetEntity: AccessBilling::class, cascade: [], orphanRemoval: false)] // TODO: à revoir
+    protected Collection $accessBilling;
+
+    #[ORM\OneToMany(mappedBy: 'billSchedule', targetEntity: AccessWish::class, cascade: [], orphanRemoval: false)] // TODO: à revoir
+    protected Collection $accessWishes;
+
+    public function getId(): int
+    {
+        return $this->id;
+    }
+
+    public function setId(int $id): self
+    {
+        $this->id = $id;
+
+        return $this;
+    }
+
+    public function getOrganization(): Organization
+    {
+        return $this->organization;
+    }
+
+    public function setOrganization(Organization $organization): self
+    {
+        $this->organization = $organization;
+
+        return $this;
+    }
+
+    public function getScheduleDates(): Collection
+    {
+        return $this->scheduleDates;
+    }
+
+    public function addScheduleDate(BillScheduleDate $scheduleDate): self
+    {
+        if (!$this->scheduleDates->contains($scheduleDate)) {
+            $this->scheduleDates[] = $scheduleDate;
+            $scheduleDate->setBillSchedule($this);
+        }
+
+        return $this;
+    }
+
+    public function removeScheduleDate(BillScheduleDate $scheduleDate): self
+    {
+        if ($this->scheduleDates->removeElement($scheduleDate)) {
+            $scheduleDate->setBillSchedule(null);
+        }
+
+        return $this;
+    }
+
+    public function getAccessBilling(): Collection
+    {
+        return $this->accessBilling;
+    }
+
+    public function addAccessBilling(AccessBilling $accessBilling): self
+    {
+        if (!$this->accessBilling->contains($accessBilling)) {
+            $this->accessBilling[] = $accessBilling;
+            $accessBilling->setBillSchedule($this);
+        }
+
+        return $this;
+    }
+
+    public function removeAccessBilling(AccessBilling $accessBilling): self
+    {
+        if ($this->accessBilling->removeElement($accessBilling)) {
+            //            $accessBilling->setBillSchedule(null);  // TODO: actuellement, pas nullable: conserver?
+        }
+
+        return $this;
+    }
+
+    public function getAccessWishes(): Collection
+    {
+        return $this->accessWishes;
+    }
+
+    public function addAccessWish(AccessWish $accessWish): self
+    {
+        if (!$this->accessWishes->contains($accessWish)) {
+            $this->accessWishes[] = $accessWish;
+            $accessWish->setBillSchedule($this);
+        }
+
+        return $this;
+    }
+
+    public function removeAccessWish(AccessWish $accessWish): self
+    {
+        if ($this->accessWishes->removeElement($accessWish)) {
+            //            $accessWish->setBillSchedule(null);  // TODO: actuellement, pas nullable: conserver?
+        }
+
+        return $this;
+    }
+}

+ 46 - 0
src/Entity/Billing/BillScheduleDate.php

@@ -0,0 +1,46 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Entity\Billing;
+
+use ApiPlatform\Metadata\ApiResource;
+use Doctrine\ORM\Mapping as ORM;
+
+#[ApiResource(operations: [])]
+#[ORM\Entity]
+class BillScheduleDate
+{
+    #[ORM\Id]
+    #[ORM\Column]
+    #[ORM\GeneratedValue]
+    private int $id;
+
+    #[ORM\ManyToOne(targetEntity: BillSchedule::class, cascade: ['persist'], inversedBy: 'scheduleDates')]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
+    protected mixed $billSchedule;
+
+    public function getId(): int
+    {
+        return $this->id;
+    }
+
+    public function setId(int $id): self
+    {
+        $this->id = $id;
+
+        return $this;
+    }
+
+    public function getBillSchedule(): mixed
+    {
+        return $this->billSchedule;
+    }
+
+    public function setBillSchedule(mixed $billSchedule): self
+    {
+        $this->billSchedule = $billSchedule;
+
+        return $this;
+    }
+}

+ 50 - 0
src/Entity/Billing/BillingExportSetting.php

@@ -6,6 +6,10 @@ namespace App\Entity\Billing;
 
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use ApiPlatform\Metadata\ApiResource;
+use App\Entity\Access\Access;
+use App\Entity\Organization\Organization;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
 
 /**
@@ -25,6 +29,17 @@ class BillingExportSetting
     #[ORM\JoinColumn(nullable: true)]
     private ?SddTeneur $teneur = null;
 
+    #[ORM\OneToOne(inversedBy: 'billingExportSetting', targetEntity: Organization::class, cascade: [])]
+    protected Organization $organization;
+
+    #[ORM\ManyToMany(targetEntity: Access::class, cascade: [], orphanRemoval: false)]
+    protected Collection $stageManagers;
+
+    public function __construct()
+    {
+        $this->stageManagers = new ArrayCollection();
+    }
+
     public function getId(): ?int
     {
         return $this->id;
@@ -41,4 +56,39 @@ class BillingExportSetting
 
         return $this;
     }
+
+    public function getOrganization(): Organization
+    {
+        return $this->organization;
+    }
+
+    public function setOrganization(Organization $organization): self
+    {
+        $this->organization = $organization;
+
+        return $this;
+    }
+
+    public function getStageManagers(): Collection
+    {
+        return $this->stageManagers;
+    }
+
+    public function addStageManager(Access $stageManager): self
+    {
+        if (!$this->stageManagers->contains($stageManager)) {
+            $this->stageManagers[] = $stageManager;
+            // TODO: compléter (je n'ai pas le mappedBy)
+        }
+
+        return $this;
+    }
+
+    public function removeStageManager(Access $stageManager): self
+    {
+        $this->stageManagers->removeElement($stageManager);
+
+        // TODO: compléter (je n'ai pas le mappedBy)
+        return $this;
+    }
 }

+ 15 - 0
src/Entity/Billing/BillingSetting.php

@@ -38,6 +38,9 @@ class BillingSetting
     #[ORM\OneToMany(targetEntity: FamilyQuotient::class, mappedBy: 'billingSetting', cascade: ['persist'], orphanRemoval: true)]
     private Collection $familyQuotients;
 
+    #[ORM\OneToOne(mappedBy: 'billingSetting', targetEntity: BillingSettingRent::class, cascade: ['persist'])]
+    protected BillingSettingRent $billingSettingRent;
+
     #[Pure]
     public function __construct()
     {
@@ -130,4 +133,16 @@ class BillingSetting
 
         return $this;
     }
+
+    public function getBillingSettingRent(): BillingSettingRent
+    {
+        return $this->billingSettingRent;
+    }
+
+    public function setBillingSettingRent(BillingSettingRent $billingSettingRent): self
+    {
+        $this->billingSettingRent = $billingSettingRent;
+
+        return $this;
+    }
 }

+ 15 - 0
src/Entity/Billing/BillingSettingRent.php

@@ -21,6 +21,21 @@ class BillingSettingRent
     #[ORM\GeneratedValue]
     private ?int $id = null;
 
+    #[ORM\OneToOne(inversedBy: 'billingSettingRent', targetEntity: BillingSetting::class, cascade: [])]
+    protected BillingSetting $billingSetting;
+
+    public function getBillingSetting(): BillingSetting
+    {
+        return $this->billingSetting;
+    }
+
+    public function setBillingSetting(BillingSetting $billingSetting): self
+    {
+        $this->billingSetting = $billingSetting;
+
+        return $this;
+    }
+
     public function getId(): ?int
     {
         return $this->id;

+ 16 - 0
src/Entity/Billing/Ciril.php

@@ -6,6 +6,7 @@ namespace App\Entity\Billing;
 
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use ApiPlatform\Metadata\ApiResource;
+use App\Entity\Core\File;
 use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
@@ -27,6 +28,9 @@ class Ciril
     #[ORM\OneToMany(targetEntity: Bill::class, mappedBy: 'ciril', cascade: ['persist'], orphanRemoval: true)]
     private Collection $bills;
 
+    #[ORM\OneToOne(targetEntity: File::class, cascade: ['persist'])]
+    protected File $file;
+
     public function __construct()
     {
         $this->bills = new ArrayCollection();
@@ -66,4 +70,16 @@ class Ciril
 
         return $this;
     }
+
+    public function getFile(): File
+    {
+        return $this->file;
+    }
+
+    public function setFile(File $file): self
+    {
+        $this->file = $file;
+
+        return $this;
+    }
 }

+ 72 - 0
src/Entity/Billing/CirilCivil.php

@@ -0,0 +1,72 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Entity\Billing;
+
+use ApiPlatform\Metadata\ApiResource;
+use App\Entity\Core\File;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ORM\Mapping as ORM;
+
+#[ApiResource(operations: [])]
+#[ORM\Entity]
+class CirilCivil
+{
+    #[ORM\Id]
+    #[ORM\Column]
+    #[ORM\GeneratedValue]
+    private int $id;
+
+    #[ORM\OneToMany(mappedBy: 'cirilCivil', targetEntity: Bill::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $bills;
+
+    #[ORM\OneToOne(targetEntity: File::class, cascade: ['persist'])]
+    protected File $file;
+
+    public function getId(): int
+    {
+        return $this->id;
+    }
+
+    public function setId(int $id): self
+    {
+        $this->id = $id;
+
+        return $this;
+    }
+
+    public function getBills(): Collection
+    {
+        return $this->bills;
+    }
+
+    public function addBill(Bill $bill): self
+    {
+        if (!$this->bills->contains($bill)) {
+            $this->bills[] = $bill;
+            $bill->setCirilCivil($this);
+        }
+
+        return $this;
+    }
+
+    public function removeBill(Bill $bill): self
+    {
+        $this->bills->removeElement($bill);
+
+        return $this;
+    }
+
+    public function getFile(): File
+    {
+        return $this->file;
+    }
+
+    public function setFile(File $file): self
+    {
+        $this->file = $file;
+
+        return $this;
+    }
+}

+ 105 - 0
src/Entity/Billing/FamilyQuotientBand.php

@@ -0,0 +1,105 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Entity\Billing;
+
+use ApiPlatform\Metadata\ApiResource;
+use App\Entity\Product\IntangibleDiscountDetail;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ORM\Mapping as ORM;
+
+#[ApiResource(operations: [])]
+#[ORM\Entity]
+class FamilyQuotientBand
+{
+    #[ORM\Id]
+    #[ORM\Column]
+    #[ORM\GeneratedValue]
+    private int $id;
+
+    #[ORM\ManyToOne(targetEntity: FamilyQuotientModel::class, cascade: [], inversedBy: 'familyQuotientBands')]
+    #[ORM\JoinColumn(nullable: false)]
+    protected mixed $familyQuotientModel;
+
+    #[ORM\OneToMany(
+        mappedBy: 'familyQuotientBand',
+        targetEntity: FamilyQuotientBandDetail::class,
+        cascade: ['persist'],
+        orphanRemoval: true,
+    )]
+    protected Collection $familyQuotientBandDetails;
+
+    #[ORM\OneToMany(mappedBy: 'familyQuotientBand', targetEntity: IntangibleDiscountDetail::class, cascade: [], orphanRemoval: true)]
+    protected Collection $intangibleDiscountDetails;
+
+    public function getId(): int
+    {
+        return $this->id;
+    }
+
+    public function setId(int $id): self
+    {
+        $this->id = $id;
+
+        return $this;
+    }
+
+    public function getFamilyQuotientModel(): mixed
+    {
+        return $this->familyQuotientModel;
+    }
+
+    public function setFamilyQuotientModel(mixed $familyQuotientModel): self
+    {
+        $this->familyQuotientModel = $familyQuotientModel;
+
+        return $this;
+    }
+
+    public function getFamilyQuotientBandDetails(): Collection
+    {
+        return $this->familyQuotientBandDetails;
+    }
+
+    public function addFamilyQuotientBandDetail(FamilyQuotientBandDetail $familyQuotientBandDetail): self
+    {
+        if (!$this->familyQuotientBandDetails->contains($familyQuotientBandDetail)) {
+            $this->familyQuotientBandDetails[] = $familyQuotientBandDetail;
+            $familyQuotientBandDetail->setFamilyQuotientBand($this);
+        }
+
+        return $this;
+    }
+
+    public function removeFamilyQuotientBandDetail(FamilyQuotientBandDetail $familyQuotientBandDetail): self
+    {
+        if ($this->familyQuotientBandDetails->removeElement($familyQuotientBandDetail)) {
+            $familyQuotientBandDetail->setFamilyQuotientBand(null);
+        }
+
+        return $this;
+    }
+
+    public function getIntangibleDiscountDetails(): Collection
+    {
+        return $this->intangibleDiscountDetails;
+    }
+
+    public function addIntangibleDiscountDetail(IntangibleDiscountDetail $intangibleDiscountDetail): self
+    {
+        if (!$this->intangibleDiscountDetails->contains($intangibleDiscountDetail)) {
+            $this->intangibleDiscountDetails[] = $intangibleDiscountDetail;
+            $intangibleDiscountDetail->setFamilyQuotientBand($this);
+        }
+
+        return $this;
+    }
+
+    public function removeIntangibleDiscountDetail(IntangibleDiscountDetail $intangibleDiscountDetail): self
+    {
+        $this->intangibleDiscountDetails->removeElement($intangibleDiscountDetail);
+
+        return $this;
+    }
+}

+ 89 - 0
src/Entity/Billing/FamilyQuotientBandDetail.php

@@ -0,0 +1,89 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Entity\Billing;
+
+use ApiPlatform\Metadata\ApiResource;
+use App\Entity\Product\IntangibleDiscountDetail;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ORM\Mapping as ORM;
+
+#[ApiResource(operations: [])]
+#[ORM\Entity]
+class FamilyQuotientBandDetail
+{
+    #[ORM\Id]
+    #[ORM\Column]
+    #[ORM\GeneratedValue]
+    private int $id;
+
+    #[ORM\ManyToOne(targetEntity: FamilyQuotientBand::class, cascade: [], inversedBy: 'familyQuotientBandDetails')]
+    #[ORM\JoinColumn(nullable: false)]
+    protected mixed $familyQuotientBand;
+
+    #[ORM\ManyToOne(targetEntity: ResidenceArea::class, cascade: [], inversedBy: 'intangibleDiscountDetails')]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
+    protected ResidenceArea $residenceArea;
+
+    #[ORM\OneToMany(mappedBy: 'familyQuotientBandDetail', targetEntity: IntangibleDiscountDetail::class, cascade: [], orphanRemoval: true)]
+    protected Collection $intangibleDiscountDetails;
+
+    public function getId(): int
+    {
+        return $this->id;
+    }
+
+    public function setId(int $id): self
+    {
+        $this->id = $id;
+
+        return $this;
+    }
+
+    public function getFamilyQuotientBand(): mixed
+    {
+        return $this->familyQuotientBand;
+    }
+
+    public function setFamilyQuotientBand(mixed $familyQuotientBand): self
+    {
+        $this->familyQuotientBand = $familyQuotientBand;
+
+        return $this;
+    }
+
+    public function getResidenceArea(): ResidenceArea
+    {
+        return $this->residenceArea;
+    }
+
+    public function setResidenceArea(ResidenceArea $residenceArea): self
+    {
+        $this->residenceArea = $residenceArea;
+
+        return $this;
+    }
+
+    public function getIntangibleDiscountDetails(): Collection
+    {
+        return $this->intangibleDiscountDetails;
+    }
+
+    public function addIntangibleDiscountDetail(IntangibleDiscountDetail $intangibleDiscountDetail): self
+    {
+        if (!$this->intangibleDiscountDetails->contains($intangibleDiscountDetail)) {
+            $this->intangibleDiscountDetails[] = $intangibleDiscountDetail;
+            $intangibleDiscountDetail->setFamilyQuotientBandDetail($this);
+        }
+
+        return $this;
+    }
+
+    public function removeIntangibleDiscountDetail(IntangibleDiscountDetail $intangibleDiscountDetail): self
+    {
+        $this->intangibleDiscountDetails->removeElement($intangibleDiscountDetail);
+
+        return $this;
+    }
+}

+ 103 - 0
src/Entity/Billing/FamilyQuotientModel.php

@@ -0,0 +1,103 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Entity\Billing;
+
+use ApiPlatform\Metadata\ApiResource;
+use App\Entity\Organization\Organization;
+use App\Entity\Product\IntangiblePriceAndDiscount;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ORM\Mapping as ORM;
+
+#[ApiResource(operations: [])]
+#[ORM\Entity]
+class FamilyQuotientModel
+{
+    #[ORM\Id]
+    #[ORM\Column]
+    #[ORM\GeneratedValue]
+    private int $id;
+
+    #[ORM\ManyToOne(targetEntity: Organization::class, cascade: [], inversedBy: 'familyQuotientModels')]
+    #[ORM\JoinColumn(nullable: false)]
+    protected Organization $organization;
+
+    #[ORM\OneToMany(mappedBy: 'familyQuotientModel', targetEntity: FamilyQuotientBand::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $familyQuotientBands;
+
+    #[ORM\OneToMany(mappedBy: 'familyQuotientModel', targetEntity: IntangiblePriceAndDiscount::class, cascade: [], orphanRemoval: false)]
+    protected Collection $intangiblePriceAndDiscounts;
+
+    public function getId(): int
+    {
+        return $this->id;
+    }
+
+    public function setId(int $id): self
+    {
+        $this->id = $id;
+
+        return $this;
+    }
+
+    public function getOrganization(): Organization
+    {
+        return $this->organization;
+    }
+
+    public function setOrganization(Organization $organization): self
+    {
+        $this->organization = $organization;
+
+        return $this;
+    }
+
+    public function getFamilyQuotientBands(): Collection
+    {
+        return $this->familyQuotientBands;
+    }
+
+    public function addFamilyQuotientBand(FamilyQuotientBand $familyQuotientBand): self
+    {
+        if (!$this->familyQuotientBands->contains($familyQuotientBand)) {
+            $this->familyQuotientBands[] = $familyQuotientBand;
+            $familyQuotientBand->setFamilyQuotientModel($this);
+        }
+
+        return $this;
+    }
+
+    public function removeFamilyQuotientBand(FamilyQuotientBand $familyQuotientBand): self
+    {
+        if ($this->familyQuotientBands->removeElement($familyQuotientBand)) {
+            $familyQuotientBand->setFamilyQuotientModel(null);
+        }
+
+        return $this;
+    }
+
+    public function getIntangiblePriceAndDiscounts(): Collection
+    {
+        return $this->intangiblePriceAndDiscounts;
+    }
+
+    public function addIntangiblePriceAndDiscount(IntangiblePriceAndDiscount $intangiblePriceAndDiscount): self
+    {
+        if (!$this->intangiblePriceAndDiscounts->contains($intangiblePriceAndDiscount)) {
+            $this->intangiblePriceAndDiscounts[] = $intangiblePriceAndDiscount;
+            $intangiblePriceAndDiscount->setFamilyQuotientModel($this);
+        }
+
+        return $this;
+    }
+
+    public function removeIntangiblePriceAndDiscount(IntangiblePriceAndDiscount $intangiblePriceAndDiscount): self
+    {
+        if ($this->intangiblePriceAndDiscounts->removeElement($intangiblePriceAndDiscount)) {
+            $intangiblePriceAndDiscount->setFamilyQuotientModel(null);
+        }
+
+        return $this;
+    }
+}

+ 16 - 0
src/Entity/Billing/Jvs.php

@@ -6,6 +6,7 @@ namespace App\Entity\Billing;
 
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use ApiPlatform\Metadata\ApiResource;
+use App\Entity\Core\File;
 use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
@@ -27,6 +28,9 @@ class Jvs
     #[ORM\OneToMany(targetEntity: Bill::class, mappedBy: 'jvs', cascade: ['persist'], orphanRemoval: true)]
     private Collection $bills;
 
+    #[ORM\OneToOne(targetEntity: File::class, cascade: ['persist'])]
+    protected File $file;
+
     public function __construct()
     {
         $this->bills = new ArrayCollection();
@@ -66,4 +70,16 @@ class Jvs
 
         return $this;
     }
+
+    public function getFile(): File
+    {
+        return $this->file;
+    }
+
+    public function setFile(File $file): self
+    {
+        $this->file = $file;
+
+        return $this;
+    }
 }

+ 74 - 0
src/Entity/Billing/Odyssee.php

@@ -0,0 +1,74 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Entity\Billing;
+
+use ApiPlatform\Metadata\ApiResource;
+use App\Entity\Core\File;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ORM\Mapping as ORM;
+
+#[ApiResource(operations: [])]
+#[ORM\Entity]
+class Odyssee
+{
+    #[ORM\Id]
+    #[ORM\Column]
+    #[ORM\GeneratedValue]
+    private int $id;
+
+    #[ORM\OneToMany(mappedBy: 'odyssee', targetEntity: Bill::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $bills;
+
+    #[ORM\OneToOne(targetEntity: File::class, cascade: ['persist'])]
+    protected File $file;
+
+    public function getId(): int
+    {
+        return $this->id;
+    }
+
+    public function setId(int $id): self
+    {
+        $this->id = $id;
+
+        return $this;
+    }
+
+    public function getBills(): Collection
+    {
+        return $this->bills;
+    }
+
+    public function addBill(Bill $bill): self
+    {
+        if (!$this->bills->contains($bill)) {
+            $this->bills[] = $bill;
+            $bill->setOdyssee($this);
+        }
+
+        return $this;
+    }
+
+    public function removeBill(Bill $bill): self
+    {
+        if ($this->bills->removeElement($bill)) {
+            //            $bill->setOdyssee(null); // TODO: actuellement, pas nullable: conserver?
+        }
+
+        return $this;
+    }
+
+    public function getFile(): File
+    {
+        return $this->file;
+    }
+
+    public function setFile(File $file): self
+    {
+        $this->file = $file;
+
+        return $this;
+    }
+}

+ 30 - 0
src/Entity/Billing/PayboxPaymentReturn.php

@@ -0,0 +1,30 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Entity\Billing;
+
+use ApiPlatform\Metadata\ApiResource;
+use Doctrine\ORM\Mapping as ORM;
+
+#[ApiResource(operations: [])]
+#[ORM\Entity]
+class PayboxPaymentReturn
+{
+    #[ORM\Id]
+    #[ORM\Column]
+    #[ORM\GeneratedValue]
+    private int $id;
+
+    public function getId(): int
+    {
+        return $this->id;
+    }
+
+    public function setId(int $id): self
+    {
+        $this->id = $id;
+
+        return $this;
+    }
+}

+ 16 - 0
src/Entity/Billing/Pes.php

@@ -6,6 +6,7 @@ namespace App\Entity\Billing;
 
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use ApiPlatform\Metadata\ApiResource;
+use App\Entity\Core\File;
 use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
@@ -27,6 +28,9 @@ class Pes
     #[ORM\OneToMany(targetEntity: Bill::class, mappedBy: 'pes', cascade: ['persist'], orphanRemoval: true)]
     private Collection $bills;
 
+    #[ORM\OneToOne(targetEntity: File::class, cascade: ['persist'])]
+    protected File $file;
+
     public function __construct()
     {
         $this->bills = new ArrayCollection();
@@ -66,4 +70,16 @@ class Pes
 
         return $this;
     }
+
+    public function getFile(): File
+    {
+        return $this->file;
+    }
+
+    public function setFile(File $file): self
+    {
+        $this->file = $file;
+
+        return $this;
+    }
 }

+ 16 - 0
src/Entity/Billing/PesSetting.php

@@ -6,6 +6,7 @@ namespace App\Entity\Billing;
 
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use ApiPlatform\Metadata\ApiResource;
+use App\Entity\Organization\Organization;
 use Doctrine\ORM\Mapping as ORM;
 
 /**
@@ -21,8 +22,23 @@ class PesSetting
     #[ORM\GeneratedValue]
     private ?int $id = null;
 
+    #[ORM\OneToOne(targetEntity: Organization::class, inversedBy: 'pesSetting', cascade: [])]
+    protected Organization $organization;
+
     public function getId(): ?int
     {
         return $this->id;
     }
+
+    public function getOrganization(): Organization
+    {
+        return $this->organization;
+    }
+
+    public function setOrganization(Organization $organization): self
+    {
+        $this->organization = $organization;
+
+        return $this;
+    }
 }

+ 46 - 0
src/Entity/Billing/SddBank.php

@@ -6,6 +6,9 @@ namespace App\Entity\Billing;
 
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use ApiPlatform\Metadata\ApiResource;
+use App\Entity\Core\File;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
 
 /**
@@ -21,12 +24,55 @@ class SddBank
     #[ORM\GeneratedValue]
     private ?int $id = null;
 
+    #[ORM\OneToMany(mappedBy: 'sddBank', targetEntity: BillPayment::class, cascade: [], orphanRemoval: false)]
+    protected Collection $billPayments;
+
+    #[ORM\OneToOne(targetEntity: File::class, cascade: ['persist'])]
+    protected File $file;
+
     public function __construct()
     {
+        $this->billPayments = new ArrayCollection();
     }
 
     public function getId(): ?int
     {
         return $this->id;
     }
+
+    public function getBillPayments(): Collection
+    {
+        return $this->billPayments;
+    }
+
+    public function addBillPayment(BillPayment $billPayment): self
+    {
+        if (!$this->billPayments->contains($billPayment)) {
+            $this->billPayments[] = $billPayment;
+            $billPayment->setSddBank($this);
+        }
+
+        return $this;
+    }
+
+    public function removeBillPayment(BillPayment $billPayment): self
+    {
+        if ($this->billPayments->removeElement($billPayment)) {
+            //            $billPayment->setSddBank(null); // TODO: actuellement, pas nullable: conserver?
+        }
+
+        return $this;
+    }
+
+    public function getFile(): File
+    {
+        return $this->file;
+    }
+
+    public function setFile(File $file): self
+    {
+        $this->file = $file;
+
+        return $this;
+    }
 }

+ 62 - 0
src/Entity/Billing/SddRegie.php

@@ -6,10 +6,14 @@ namespace App\Entity\Billing;
 
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use ApiPlatform\Metadata\ApiResource;
+use App\Entity\Core\File;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
 
 /**
  * Classe ... qui ...
+ * // TODO: possible factoriser avec SddBank?
  */
 // #[Auditable]
 #[ApiResource(operations: [])]
@@ -21,12 +25,70 @@ class SddRegie
     #[ORM\GeneratedValue]
     private ?int $id = null;
 
+    #[ORM\OneToMany(mappedBy: 'sddRegie', targetEntity: BillPayment::class, cascade: [], orphanRemoval: false)]
+    protected Collection $billPayments;
+
+    #[ORM\OneToOne(targetEntity: File::class, cascade: ['persist'])]
+    protected File $file;
+
+    #[ORM\OneToOne(targetEntity: File::class, cascade: ['persist'])]
+    protected File $bordereau;
+
     public function __construct()
     {
+        $this->billPayments = new ArrayCollection();
     }
 
     public function getId(): ?int
     {
         return $this->id;
     }
+
+    public function getBillPayments(): Collection
+    {
+        return $this->billPayments;
+    }
+
+    public function addBillPayment(BillPayment $billPayment): self
+    {
+        if (!$this->billPayments->contains($billPayment)) {
+            $this->billPayments[] = $billPayment;
+            $billPayment->setSddRegie($this);
+        }
+
+        return $this;
+    }
+
+    public function removeBillPayment(BillPayment $billPayment): self
+    {
+        if ($this->billPayments->removeElement($billPayment)) {
+            //            $billPayment->setSddBank(null); // TODO: actuellement, pas nullable: conserver?
+        }
+
+        return $this;
+    }
+
+    public function getFile(): File
+    {
+        return $this->file;
+    }
+
+    public function setFile(File $file): self
+    {
+        $this->file = $file;
+
+        return $this;
+    }
+
+    public function getBordereau(): File
+    {
+        return $this->bordereau;
+    }
+
+    public function setBordereau(File $bordereau): self
+    {
+        $this->bordereau = $bordereau;
+
+        return $this;
+    }
 }

+ 90 - 1
src/Entity/Booking/AbstractBooking.php

@@ -6,12 +6,19 @@ namespace App\Entity\Booking;
 
 use App\Attribute\ActivityYearConstraintAware;
 use App\Attribute\OrganizationDefaultValue;
+use App\Entity\Access\Access;
+use App\Entity\Core\Tagg;
+use App\Entity\Product\Equipment;
 use App\Entity\Traits\ActivityYearTrait;
 use App\Enum\Booking\VisibilityEnum;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
 
 /**
- * Classe ... qui ...
+ * Données d'un évènement générique, classe de base des évènements.
+ *
+ * @see Event, Course, EducationalProject, Examen, OrganizationHoliday, PersonHoliday
  */
 #[ActivityYearConstraintAware(startYearFieldName: 'startYear', endYearFieldName: 'endYear')]
 #[OrganizationDefaultValue(fieldName: 'organization')]
@@ -36,6 +43,9 @@ abstract class AbstractBooking
     #[ORM\GeneratedValue]
     protected ?int $id = null;
 
+    #[ORM\Column(length: 255, nullable: false)]
+    protected string $discr;
+
     #[ORM\Column]
     protected string $name;
 
@@ -51,6 +61,22 @@ abstract class AbstractBooking
     #[ORM\Column(unique: true)]
     protected string $uuid;
 
+    #[ORM\ManyToMany(targetEntity: Access::class, inversedBy: 'practicalCourses', cascade: [], orphanRemoval: false)]
+    protected Collection $organizer;
+
+    #[ORM\ManyToMany(targetEntity: Equipment::class, cascade: [], orphanRemoval: false)]
+    protected Collection $equipments;
+
+    #[ORM\ManyToMany(targetEntity: Tagg::class, inversedBy: 'bookings', cascade: ['persist'], orphanRemoval: false)]
+    protected Collection $tags;
+
+    public function __construct()
+    {
+        $this->organizer = new ArrayCollection();
+        $this->equipments = new ArrayCollection();
+        $this->tags = new ArrayCollection();
+    }
+
     public function getId(): ?int
     {
         return $this->id;
@@ -115,4 +141,67 @@ abstract class AbstractBooking
 
         return $this;
     }
+
+    public function getOrganizer(): Collection
+    {
+        return $this->organizer;
+    }
+
+    public function addOrganizer(Access $organizer): self
+    {
+        if (!$this->organizer->contains($organizer)) {
+            $this->organizer[] = $organizer;
+        }
+
+        return $this;
+    }
+
+    public function removeOrganizer(Access $organizer): self
+    {
+        $this->organizer->removeElement($organizer);
+
+        return $this;
+    }
+
+    public function getEquipments(): Collection
+    {
+        return $this->equipments;
+    }
+
+    public function addEquipment(Equipment $equipment): self
+    {
+        if (!$this->equipments->contains($equipment)) {
+            $this->equipments[] = $equipment;
+        }
+
+        return $this;
+    }
+
+    public function removeEquipment(Equipment $equipment): self
+    {
+        $this->equipments->removeElement($equipment);
+
+        return $this;
+    }
+
+    public function getTags(): Collection
+    {
+        return $this->tags;
+    }
+
+    public function addTag(Tagg $tag): self
+    {
+        if (!$this->tags->contains($tag)) {
+            $this->tags[] = $tag;
+        }
+
+        return $this;
+    }
+
+    public function removeTag(Tagg $tag): self
+    {
+        $this->tags->removeElement($tag);
+
+        return $this;
+    }
 }

+ 32 - 0
src/Entity/Booking/CalendarSynchro.php

@@ -6,6 +6,8 @@ namespace App\Entity\Booking;
 
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use ApiPlatform\Metadata\ApiResource;
+use App\Entity\Access\Access;
+use App\Entity\Core\File;
 use Doctrine\ORM\Mapping as ORM;
 
 /**
@@ -21,8 +23,38 @@ class CalendarSynchro
     #[ORM\GeneratedValue]
     private ?int $id = null;
 
+    #[ORM\OneToOne(inversedBy: 'calendarSynchro', targetEntity: Access::class, cascade: ['persist'])]
+    protected Access $access;
+
+    #[ORM\OneToOne(inversedBy: 'calendarSynchro', targetEntity: File::class, cascade: ['persist'])]
+    protected File $file;
+
     public function getId(): ?int
     {
         return $this->id;
     }
+
+    public function getAccess(): Access
+    {
+        return $this->access;
+    }
+
+    public function setAccess(Access $access): self
+    {
+        $this->access = $access;
+
+        return $this;
+    }
+
+    public function getFile(): File
+    {
+        return $this->file;
+    }
+
+    public function setFile(File $file): self
+    {
+        $this->file = $file;
+
+        return $this;
+    }
 }

+ 44 - 18
src/Entity/Booking/Course.php

@@ -7,11 +7,12 @@ namespace App\Entity\Booking;
 use ApiPlatform\Metadata\ApiResource;
 use App\Entity\Access\Access;
 use App\Entity\Billing\ResidenceArea;
+use App\Entity\AccessWish\EducationStudentWish;
 use App\Entity\Core\Tagg;
 use App\Entity\Education\Education;
 use App\Entity\Education\EducationCurriculum;
 use App\Entity\Organization\Organization;
-use App\Entity\Place\Place;
+use App\Entity\Place\AbstractPlace;
 use App\Entity\Place\Room;
 use App\Entity\Product\Equipment;
 use App\Repository\Booking\CourseRepository;
@@ -38,18 +39,17 @@ class Course extends AbstractBooking
 {
     /** @var Collection<int, CourseRecur> */
     #[ORM\OneToMany(targetEntity: CourseRecur::class, mappedBy: 'event', cascade: ['persist'], orphanRemoval: true)]
-    private Collection $eventRecur;
+    protected Collection $eventRecur;
 
-    /** @var Collection<int, Course> */
-    #[ORM\OneToMany(targetEntity: Course::class, mappedBy: 'parent', orphanRemoval: true)]
-    private Collection $timeline;
+    #[ORM\OneToMany(mappedBy: 'parent', targetEntity: Course::class, orphanRemoval: true)]
+    protected Collection $timeline;
 
     #[ORM\ManyToOne(inversedBy: 'timeline')]
-    private ?Course $parent = null;
+    protected Course $parent;
 
     #[ORM\ManyToOne(inversedBy: 'courses')]
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
-    protected ?Place $place = null;
+    protected ?AbstractPlace $place = null;
 
     #[ORM\ManyToOne(inversedBy: 'courses')]
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
@@ -60,7 +60,7 @@ class Course extends AbstractBooking
     #[ORM\JoinTable(name: 'booking_organizer')]
     #[ORM\JoinColumn(name: 'booking_id', referencedColumnName: 'id')]
     #[ORM\InverseJoinColumn(name: 'organizer_id', referencedColumnName: 'id')]
-    private Collection $organizer;
+    protected Collection $organizer;
 
     #[ORM\ManyToOne(inversedBy: 'courses')]
     #[ORM\JoinColumn(nullable: false)]
@@ -68,41 +68,44 @@ class Course extends AbstractBooking
 
     #[ORM\ManyToOne(inversedBy: 'courses')]
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
-    private ?Education $education = null;
+    protected ?Education $education = null;
 
     /** @var Collection<int, EducationCurriculum> */
     #[ORM\ManyToMany(targetEntity: EducationCurriculum::class)]
-    private Collection $educationCurriculum;
+    protected Collection $educationCurriculum;
 
     /** @var Collection<int, Access> */
     #[ORM\ManyToMany(targetEntity: Access::class, inversedBy: 'courses')]
     #[ORM\JoinTable(name: 'course_student')]
     #[ORM\JoinColumn(name: 'course_id', referencedColumnName: 'id')]
     #[ORM\InverseJoinColumn(name: 'student_id', referencedColumnName: 'id')]
-    private Collection $students;
+    protected Collection $students;
 
     /** @var Collection<int, Work> */
-    #[ORM\OneToMany(targetEntity: Work::class, mappedBy: 'course', cascade: ['persist'], orphanRemoval: true)]
-    private Collection $work;
+    #[ORM\OneToMany(mappedBy: 'course', targetEntity: Work::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $work;
 
     /** @var Collection<int, Equipment> */
     #[ORM\ManyToMany(targetEntity: Equipment::class)]
     #[ORM\JoinTable(name: 'booking_equipment')]
     #[ORM\JoinColumn(name: 'booking_id', referencedColumnName: 'id')]
     #[ORM\InverseJoinColumn(name: 'equipment_id', referencedColumnName: 'id')]
-    private Collection $equipments;
+    protected Collection $equipments;
 
     /** @var Collection<int, AttendanceBooking> */
     #[ORM\OneToMany(targetEntity: AttendanceBooking::class, mappedBy: 'course', cascade: ['persist'], orphanRemoval: true)]
     #[ORM\JoinColumn(nullable: false)]
-    private Collection $attendanceBooking;
+    protected Collection $attendanceBooking;
 
     /** @var Collection<int, Tagg> */
     #[ORM\ManyToMany(targetEntity: Tagg::class, inversedBy: 'courses', cascade: ['persist'])]
     #[ORM\JoinTable(name: 'tag_booking')]
     #[ORM\JoinColumn(name: 'booking_id', referencedColumnName: 'id')]
     #[ORM\InverseJoinColumn(name: 'tag_id', referencedColumnName: 'id')]
-    private Collection $tags;
+    protected Collection $tags;
+
+    #[ORM\OneToMany(mappedBy: 'course', targetEntity: EducationStudentWish::class, cascade: [], orphanRemoval: false)]
+    protected Collection $educationStudentWishes;
 
     public function __construct()
     {
@@ -115,6 +118,8 @@ class Course extends AbstractBooking
         $this->equipments = new ArrayCollection();
         $this->attendanceBooking = new ArrayCollection();
         $this->tags = new ArrayCollection();
+        $this->educationStudentWishes = new ArrayCollection();
+        parent::__construct();
     }
 
     /**
@@ -189,12 +194,12 @@ class Course extends AbstractBooking
         return $this;
     }
 
-    public function getPlace(): ?Place
+    public function getPlace(): ?AbstractPlace
     {
         return $this->place;
     }
 
-    public function setPlace(?Place $place): self
+    public function setPlace(?AbstractPlace $place): self
     {
         $this->place = $place;
 
@@ -416,4 +421,25 @@ class Course extends AbstractBooking
 
         return $this;
     }
+
+    public function getEducationStudentWishes(): Collection
+    {
+        return $this->educationStudentWishes;
+    }
+
+    public function addEducationStudentWish(EducationStudentWish $educationStudentWish): self
+    {
+        if (!$this->educationStudentWishes->contains($educationStudentWish)) {
+            $this->educationStudentWishes[] = $educationStudentWish;
+        }
+
+        return $this;
+    }
+
+    public function removeEducationStudentWish(EducationStudentWish $educationStudentWish): self
+    {
+        $this->educationStudentWishes->removeElement($educationStudentWish);
+
+        return $this;
+    }
 }

+ 40 - 22
src/Entity/Booking/EducationalProject.php

@@ -13,7 +13,7 @@ use App\Entity\Billing\ResidenceArea;
 use App\Entity\Core\File;
 use App\Entity\Core\Tagg;
 use App\Entity\Organization\Organization;
-use App\Entity\Place\Place;
+use App\Entity\Place\AbstractPlace;
 use App\Entity\Place\Room;
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use Doctrine\Common\Collections\ArrayCollection;
@@ -33,14 +33,14 @@ class EducationalProject extends AbstractBooking
 {
     /** @var Collection<int, EducationalProjectRecur> */
     #[ORM\OneToMany(targetEntity: EducationalProjectRecur::class, mappedBy: 'event', cascade: ['persist'], orphanRemoval: true)]
-    private Collection $eventRecur;
+    protected Collection $eventRecur;
 
     /** @var Collection<int, EducationalProject> */
-    #[ORM\OneToMany(targetEntity: EducationalProject::class, mappedBy: 'parent', orphanRemoval: true)]
-    private Collection $timeline;
+    #[ORM\OneToMany(mappedBy: 'parent', targetEntity: EducationalProject::class, orphanRemoval: true)]
+    protected Collection $timeline;
 
     #[ORM\ManyToOne(inversedBy: 'timeline')]
-    private ?EducationalProject $parent = null;
+    protected ?EducationalProject $parent = null;
 
     #[ORM\ManyToOne(inversedBy: 'educationalProjects')]
     #[ORM\JoinColumn(nullable: false)]
@@ -48,45 +48,46 @@ class EducationalProject extends AbstractBooking
 
     #[ORM\ManyToOne(inversedBy: 'silentPartners')]
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
-    private ?Access $silentPartner = null;
+    protected ?Access $silentPartner = null;
 
     #[ORM\ManyToOne(inversedBy: 'educationalProjects')]
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
-    private ?EducationalProjectPublic $public = null;
+    protected ?EducationalProjectPublic $public = null;
 
     #[ORM\ManyToOne(inversedBy: 'operationalPartners')]
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
-    private ?Access $operationalPartner = null;
+    protected ?Access $operationalPartner = null;
 
     /** @var Collection<int, Access> */
     #[ORM\ManyToMany(targetEntity: Access::class)]
     #[ORM\JoinTable(name: 'educationalproject_financier')]
-    private Collection $financiers;
+    protected Collection $financiers;
 
     /** @var Collection<int, File> */
     #[ORM\ManyToMany(targetEntity: File::class)]
-    private Collection $files;
+    protected Collection $files;
 
     /** @var Collection<int, EducationalProjectIntangible> */
-    #[ORM\OneToMany(targetEntity: EducationalProjectIntangible::class, mappedBy: 'educationalProject', cascade: ['persist'], orphanRemoval: true)]
-    private Collection $educationalProjectIntangibles;
+    #[ORM\OneToMany(mappedBy: 'educationalProject', targetEntity: EducationalProjectIntangible::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $educationalProjectIntangibles;
 
     /** @var Collection<int, EducationalProjectPayer> */
-    #[ORM\OneToMany(targetEntity: EducationalProjectPayer::class, mappedBy: 'educationalProjectReceiver', cascade: ['persist'], orphanRemoval: true)]
-    private Collection $billingReceivers;
+    #[ORM\OneToMany(mappedBy: 'educationalProjectReceiver', targetEntity: EducationalProjectPayer::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $billingReceivers;
 
     /** @var Collection<int, BillLine> */
-    #[ORM\OneToMany(targetEntity: BillLine::class, mappedBy: 'educationalProject', orphanRemoval: true)]
-    private Collection $billLines;
+    #[ORM\OneToMany(mappedBy: 'educationalProject', targetEntity: BillLine::class, orphanRemoval: true)]
+    protected Collection $billLines;
 
     /** @var Collection<int, AttendanceBooking> */
     #[ORM\OneToMany(targetEntity: AttendanceBooking::class, mappedBy: 'educationalProject', cascade: ['persist'], orphanRemoval: true)]
     #[ORM\JoinColumn(nullable: false)]
-    private Collection $attendanceBooking;
+    protected Collection $attendanceBooking;
 
     #[ORM\ManyToOne(inversedBy: 'educationalProjects')]
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
-    protected ?Place $place = null;
+    protected ?AbstractPlace $place = null;
+    protected AbstractPlace $place;
 
     #[ORM\ManyToOne(inversedBy: 'educationalProjects')]
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
@@ -97,14 +98,17 @@ class EducationalProject extends AbstractBooking
     #[ORM\JoinTable(name: 'booking_organizer')]
     #[ORM\JoinColumn(name: 'booking_id', referencedColumnName: 'id')]
     #[ORM\InverseJoinColumn(name: 'organizer_id', referencedColumnName: 'id')]
-    private Collection $organizer;
+    protected Collection $organizer;
 
     /** @var Collection<int, Tagg> */
     #[ORM\ManyToMany(targetEntity: Tagg::class, inversedBy: 'educationalProjects', cascade: ['persist'])]
     #[ORM\JoinTable(name: 'tag_booking')]
     #[ORM\JoinColumn(name: 'booking_id', referencedColumnName: 'id')]
     #[ORM\InverseJoinColumn(name: 'tag_id', referencedColumnName: 'id')]
-    private Collection $tags;
+    protected Collection $tags;
+
+    #[ORM\OneToOne(targetEntity: EducationalProjectAge::class, cascade: ['persist'])]
+    protected EducationalProjectAge $ageDistribution;
 
     public function __construct()
     {
@@ -118,6 +122,8 @@ class EducationalProject extends AbstractBooking
         $this->attendanceBooking = new ArrayCollection();
         $this->organizer = new ArrayCollection();
         $this->tags = new ArrayCollection();
+        $this->equipments = new ArrayCollection();
+        parent::__construct();
     }
 
     /**
@@ -408,12 +414,12 @@ class EducationalProject extends AbstractBooking
         return $this;
     }
 
-    public function getPlace(): ?Place
+    public function getPlace(): ?AbstractPlace
     {
         return $this->place;
     }
 
-    public function setPlace(?Place $place): self
+    public function setPlace(?AbstractPlace $place): self
     {
         $this->place = $place;
 
@@ -479,4 +485,16 @@ class EducationalProject extends AbstractBooking
 
         return $this;
     }
+
+    public function getAgeDistribution(): EducationalProjectAge
+    {
+        return $this->ageDistribution;
+    }
+
+    public function setAgeDistribution(EducationalProjectAge $ageDistribution): self
+    {
+        $this->ageDistribution = $ageDistribution;
+
+        return $this;
+    }
 }

+ 38 - 21
src/Entity/Booking/Event.php

@@ -11,7 +11,7 @@ use App\Entity\Core\Categories;
 use App\Entity\Core\File;
 use App\Entity\Core\Tagg;
 use App\Entity\Organization\Organization;
-use App\Entity\Place\Place;
+use App\Entity\Place\AbstractPlace;
 use App\Entity\Place\PlaceSystem;
 use App\Entity\Place\Room;
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
@@ -30,79 +30,82 @@ use Symfony\Component\Validator\Constraints as Assert;
 #[ORM\Table(name: 'Booking')]
 class Event extends AbstractBooking
 {
+    #[ORM\Column(length: 255, nullable: false)]
+    protected string $discr = 'event';
+
     #[ORM\ManyToOne(inversedBy: 'events')]
     #[ORM\JoinColumn(nullable: false)]
     protected Organization $organization;
 
     #[ORM\ManyToOne(inversedBy: 'events')]
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
-    protected ?Place $place = null;
+    protected AbstractPlace $place;
 
     #[ORM\ManyToOne(inversedBy: 'events')]
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
     protected ?Room $room = null;
 
     /** @var Collection<int, EventRecur> */
-    #[ORM\OneToMany(targetEntity: EventRecur::class, mappedBy: 'event', cascade: ['persist'], orphanRemoval: true)]
-    private Collection $eventRecur;
+    #[ORM\OneToMany(mappedBy: 'event', targetEntity: EventRecur::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $eventRecur;
 
     /** @var Collection<int, Event> */
-    #[ORM\OneToMany(targetEntity: Event::class, mappedBy: 'parent', orphanRemoval: true)]
-    private Collection $timeline;
+    #[ORM\OneToMany(mappedBy: 'parent', targetEntity: Event::class, orphanRemoval: true)]
+    protected Collection $timeline;
 
     #[ORM\ManyToOne(inversedBy: 'timeline')]
-    private ?Event $parent = null;
+    protected Event $parent;
 
     #[Assert\Valid]
     #[ORM\ManyToOne(cascade: ['persist'], inversedBy: 'events')]
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
-    private ?File $image = null;
+    protected File $image;
 
     #[ORM\ManyToOne]
-    private ?EventGender $gender = null;
+    protected EventGender $gender;
 
     /** @var Collection<int, EventUser> */
     #[Assert\Valid]
-    #[ORM\OneToMany(targetEntity: EventUser::class, mappedBy: 'event', cascade: ['persist'], orphanRemoval: true)]
-    private Collection $eventUser;
+    #[ORM\OneToMany(mappedBy: 'event', targetEntity: EventUser::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $eventUser;
 
     /** @var Collection<int, Categories> */
     #[ORM\ManyToMany(targetEntity: Categories::class, cascade: ['persist'])]
-    private Collection $categories;
+    protected Collection $categories;
 
     /** @var Collection<int, EventReport> */
-    #[ORM\OneToMany(targetEntity: EventReport::class, mappedBy: 'event', orphanRemoval: true)]
-    private Collection $eventReports;
+    #[ORM\OneToMany(mappedBy: 'event', targetEntity: EventReport::class, orphanRemoval: true)]
+    protected Collection $eventReports;
 
     /** @var Collection<int, File> */
     #[ORM\ManyToMany(targetEntity: File::class, cascade: ['persist'], orphanRemoval: true)]
     #[ORM\JoinTable(name: 'event_files')]
     #[ORM\JoinColumn(name: 'event_id', referencedColumnName: 'id', onDelete: 'cascade')]
     #[ORM\InverseJoinColumn(name: 'file_id', referencedColumnName: 'id', onDelete: 'cascade')]
-    private Collection $files;
+    protected Collection $files;
 
     #[ORM\ManyToOne]
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
-    private ?PlaceSystem $placeSystem = null;
+    protected PlaceSystem $placeSystem;
 
     /** @var Collection<int, AttendanceBooking> */
     #[ORM\OneToMany(targetEntity: AttendanceBooking::class, mappedBy: 'event', cascade: ['persist'], orphanRemoval: true)]
     #[ORM\JoinColumn(nullable: false)]
-    private Collection $attendanceBooking;
+    protected Collection $attendanceBooking;
 
     /** @var Collection<int, Access> */
     #[ORM\ManyToMany(targetEntity: Access::class, inversedBy: 'eventOrganizers')]
     #[ORM\JoinTable(name: 'booking_organizer')]
     #[ORM\JoinColumn(name: 'booking_id', referencedColumnName: 'id')]
     #[ORM\InverseJoinColumn(name: 'organizer_id', referencedColumnName: 'id')]
-    private Collection $organizer;
+    protected Collection $organizer;
 
     /** @var Collection<int, Tagg> */
     #[ORM\ManyToMany(targetEntity: Tagg::class, inversedBy: 'events', cascade: ['persist'])]
     #[ORM\JoinTable(name: 'tag_booking')]
     #[ORM\JoinColumn(name: 'booking_id', referencedColumnName: 'id')]
     #[ORM\InverseJoinColumn(name: 'tag_id', referencedColumnName: 'id')]
-    private Collection $tags;
+    protected Collection $tags;
 
     public function __construct()
     {
@@ -115,6 +118,8 @@ class Event extends AbstractBooking
         $this->attendanceBooking = new ArrayCollection();
         $this->organizer = new ArrayCollection();
         $this->tags = new ArrayCollection();
+        $this->equipments = new ArrayCollection();
+        parent::__construct();
     }
 
     public function getOrganization(): ?Organization
@@ -129,12 +134,12 @@ class Event extends AbstractBooking
         return $this;
     }
 
-    public function getPlace(): ?Place
+    public function getPlace(): ?AbstractPlace
     {
         return $this->place;
     }
 
-    public function setPlace(?Place $place): self
+    public function setPlace(?AbstractPlace $place): self
     {
         $this->place = $place;
 
@@ -446,4 +451,16 @@ class Event extends AbstractBooking
 
         return $this;
     }
+
+    public function getEquipments(): Collection
+    {
+        return $this->equipments;
+    }
+
+    public function setEquipments(Collection $equipments): self
+    {
+        $this->equipments = $equipments;
+
+        return $this;
+    }
 }

+ 18 - 15
src/Entity/Booking/Examen.php

@@ -11,7 +11,7 @@ use App\Entity\Education\Education;
 use App\Entity\Education\EducationCurriculum;
 use App\Entity\Organization\Jury;
 use App\Entity\Organization\Organization;
-use App\Entity\Place\Place;
+use App\Entity\Place\AbstractPlace;
 use App\Entity\Place\Room;
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use Doctrine\Common\Collections\ArrayCollection;
@@ -34,40 +34,40 @@ class Examen extends AbstractBooking
     protected Organization $organization;
 
     /** @var Collection<int, ExamenRecur> */
-    #[ORM\OneToMany(targetEntity: ExamenRecur::class, mappedBy: 'event', cascade: ['persist'], orphanRemoval: true)]
-    private Collection $eventRecur;
+    #[ORM\OneToMany(mappedBy: 'event', targetEntity: ExamenRecur::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $eventRecur;
 
     /** @var Collection<int, Examen> */
-    #[ORM\OneToMany(targetEntity: Examen::class, mappedBy: 'parent', orphanRemoval: true)]
-    private Collection $timeline;
+    #[ORM\OneToMany(mappedBy: 'parent', targetEntity: Examen::class, orphanRemoval: true)]
+    protected Collection $timeline;
 
     #[ORM\ManyToOne(inversedBy: 'timeline')]
-    private ?Examen $parent = null;
+    protected Examen $parent;
 
     #[ORM\ManyToOne(cascade: ['persist'], inversedBy: 'examens')]
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
-    private ?Jury $jury = null;
+    protected Jury $jury;
 
     #[ORM\ManyToOne(inversedBy: 'examens')]
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
-    private ?Education $education = null;
+    protected Education $education;
 
     /** @var Collection<int, EducationCurriculum> */
     #[ORM\ManyToMany(targetEntity: EducationCurriculum::class)]
-    private Collection $educationCurriculum;
+    protected Collection $educationCurriculum;
 
     /** @var Collection<int, ExamenConvocation> */
-    #[ORM\OneToMany(targetEntity: ExamenConvocation::class, mappedBy: 'examen', cascade: ['persist'], orphanRemoval: true)]
-    private Collection $convocation;
+    #[ORM\OneToMany(mappedBy: 'examen', targetEntity: ExamenConvocation::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $convocation;
 
     /** @var Collection<int, AttendanceBooking> */
-    #[ORM\OneToMany(targetEntity: AttendanceBooking::class, mappedBy: 'examen', cascade: ['persist'], orphanRemoval: true)]
+    #[ORM\OneToMany(mappedBy: 'examen', targetEntity: AttendanceBooking::class, cascade: ['persist'], orphanRemoval: true)]
     #[ORM\JoinColumn(nullable: false)]
-    private Collection $attendanceBooking;
+    protected Collection $attendanceBooking;
 
     #[ORM\ManyToOne(inversedBy: 'examens')]
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
-    protected ?Place $place = null;
+    protected AbstractPlace $place;
 
     #[ORM\ManyToOne(inversedBy: 'examens')]
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
@@ -78,7 +78,7 @@ class Examen extends AbstractBooking
     #[ORM\JoinTable(name: 'tag_booking')]
     #[ORM\JoinColumn(name: 'booking_id', referencedColumnName: 'id')]
     #[ORM\InverseJoinColumn(name: 'tag_id', referencedColumnName: 'id')]
-    private Collection $tags;
+    protected Collection $tags;
 
     public function __construct()
     {
@@ -88,6 +88,9 @@ class Examen extends AbstractBooking
         $this->convocation = new ArrayCollection();
         $this->attendanceBooking = new ArrayCollection();
         $this->tags = new ArrayCollection();
+        $this->organizer = new ArrayCollection();
+        $this->equipments = new ArrayCollection();
+        parent::__construct();
     }
 
     public function getOrganization(): ?Organization

+ 4 - 0
src/Entity/Booking/OrganizationHoliday.php

@@ -34,6 +34,10 @@ class OrganizationHoliday extends AbstractBooking
     public function __construct()
     {
         $this->eventRecur = new ArrayCollection();
+        $this->organizer = new ArrayCollection();
+        $this->equipments = new ArrayCollection();
+        $this->tags = new ArrayCollection();
+        parent::__construct();
     }
 
     /**

+ 22 - 1
src/Entity/Booking/PersonHoliday.php

@@ -8,6 +8,7 @@ use ApiPlatform\Metadata\ApiResource;
 use App\Entity\Access\Access;
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use App\Entity\Billing\ResidenceArea;
+use App\Entity\Organization\Organization;
 use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
@@ -24,15 +25,23 @@ use Doctrine\ORM\Mapping as ORM;
 class PersonHoliday extends AbstractBooking
 {
     /** @var Collection<int, PersonHolidayRecur> */
-    #[ORM\OneToMany(targetEntity: PersonHolidayRecur::class, mappedBy: 'event', cascade: ['persist'], orphanRemoval: true)]
+    #[ORM\OneToMany(mappedBy: 'event', targetEntity: PersonHolidayRecur::class, cascade: ['persist'], orphanRemoval: true)]
     protected Collection $eventRecur;
 
     #[ORM\ManyToOne(inversedBy: 'holidays')]
     private ?Access $access = null;
 
+    #[ORM\ManyToOne(targetEntity: Organization::class, cascade: [])]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: false, onDelete: 'SET NULL')]
+    protected Organization $organization;
+
     public function __construct()
     {
         $this->eventRecur = new ArrayCollection();
+        $this->organizer = new ArrayCollection();
+        $this->equipments = new ArrayCollection();
+        $this->tags = new ArrayCollection();
+        parent::__construct();
     }
 
     /**
@@ -76,4 +85,16 @@ class PersonHoliday extends AbstractBooking
 
         return $this;
     }
+
+    public function getOrganization(): Organization
+    {
+        return $this->organization;
+    }
+
+    public function setOrganization(Organization $organization): self
+    {
+        $this->organization = $organization;
+
+        return $this;
+    }
 }

+ 46 - 0
src/Entity/Core/AbstractControl.php

@@ -8,6 +8,8 @@ use App\Entity\Access\Access;
 use App\Entity\Place\PlaceControl;
 use App\Entity\Place\RoomControl;
 use App\Entity\Product\EquipmentControl;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
 
 #[ORM\MappedSuperclass]
@@ -30,8 +32,52 @@ abstract class AbstractControl
     #[ORM\JoinColumn(nullable: true)]
     protected ?Access $accompanist = null;
 
+    #[ORM\ManyToMany(targetEntity: Tagg::class, inversedBy: 'controls', cascade: ['persist'], orphanRemoval: false)]
+    protected Collection $tags;
+
+    public function __construct()
+    {
+        $this->tags = new ArrayCollection();
+    }
+
     public function getId(): ?int
     {
         return $this->id;
     }
+
+    public function getAccompanist(): Access
+    {
+        return $this->accompanist;
+    }
+
+    public function setAccompanist(Access $accompanist): self
+    {
+        $this->accompanist = $accompanist;
+
+        return $this;
+    }
+
+    public function getTags(): Collection
+    {
+        return $this->tags;
+    }
+
+    public function addTag(Tagg $tag): self
+    {
+        if (!$this->tags->contains($tag)) {
+            $this->tags[] = $tag;
+            $tag->addControl($this);
+        }
+
+        return $this;
+    }
+
+    public function removeTag(Tagg $tag): self
+    {
+        if ($this->tags->removeElement($tag)) {
+            $tag->addControl($this);
+        }
+
+        return $this;
+    }
 }

+ 34 - 0
src/Entity/Core/AbstractRepair.php

@@ -7,6 +7,8 @@ namespace App\Entity\Core;
 use App\Entity\Place\PlaceRepair;
 use App\Entity\Place\RoomRepair;
 use App\Entity\Product\EquipmentRepair;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
 
 #[ORM\MappedSuperclass]
@@ -25,8 +27,40 @@ abstract class AbstractRepair
     #[ORM\GeneratedValue]
     protected ?int $id = null;
 
+    #[ORM\ManyToMany(targetEntity: Tagg::class, inversedBy: 'repairs', cascade: ['persist'], orphanRemoval: false)]
+    protected Collection $tags;
+
+    public function __construct()
+    {
+        $this->tags = new ArrayCollection();
+    }
+
     public function getId(): ?int
     {
         return $this->id;
     }
+
+    public function getTags(): Collection
+    {
+        return $this->tags;
+    }
+
+    public function addTag(Tagg $tag): self
+    {
+        if (!$this->tags->contains($tag)) {
+            $this->tags[] = $tag;
+            $tag->addRepair($this);
+        }
+
+        return $this;
+    }
+
+    public function removeTag(Tagg $tag): self
+    {
+        if ($this->tags->removeElement($tag)) {
+            $tag->removeRepair($this);
+        }
+
+        return $this;
+    }
 }

+ 33 - 5
src/Entity/Core/AddressPostal.php

@@ -8,7 +8,7 @@ use ApiPlatform\Metadata\ApiResource;
 use App\Entity\Billing\ResidenceArea;
 use App\Entity\Organization\OrganizationAddressPostal;
 use App\Entity\Person\PersonAddressPostal;
-use App\Entity\Place\Place;
+use App\Entity\Place\AbstractPlace;
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use App\Entity\Traits\CreatedOnAndByTrait;
 use App\Repository\Core\AddressPostalRepository;
@@ -80,12 +80,16 @@ class AddressPostal
     private ?PersonAddressPostal $personAddressPostal = null;
 
     /** @var Collection<int, Place> */
-    #[ORM\OneToMany(targetEntity: Place::class, mappedBy: 'addressPostal')]
+    #[ORM\OneToMany(mappedBy: 'addressPostal', targetEntity: AbstractPlace::class)]
     private Collection $places;
 
+    #[ORM\OneToMany(mappedBy: 'addressPostal', targetEntity: OrganizationAddressPostal::class, cascade: [], orphanRemoval: false)]
+    protected Collection $organizationAddressPostals;
+
     public function __construct()
     {
         $this->places = new ArrayCollection();
+        $this->organizationAddressPostals = new ArrayCollection();
     }
 
     public function getId(): ?int
@@ -226,14 +230,14 @@ class AddressPostal
     }
 
     /**
-     * @return Collection<int, Place>
+     * @return Collection<int, AbstractPlace>
      */
     public function getPlaces(): Collection
     {
         return $this->places;
     }
 
-    public function addPlace(Place $place): self
+    public function addPlace(AbstractPlace $place): self
     {
         if (!$this->places->contains($place)) {
             $this->places[] = $place;
@@ -243,7 +247,7 @@ class AddressPostal
         return $this;
     }
 
-    public function removePlace(Place $place): self
+    public function removePlace(AbstractPlace $place): self
     {
         if ($this->places->removeElement($place)) {
             // set the owning side to null (unless already changed)
@@ -254,4 +258,28 @@ class AddressPostal
 
         return $this;
     }
+
+    public function getOrganizationAddressPostals(): Collection
+    {
+        return $this->organizationAddressPostals;
+    }
+
+    public function addOrganizationAddressPostal(OrganizationAddressPostal $organizationAddressPostal): self
+    {
+        if (!$this->organizationAddressPostals->contains($organizationAddressPostal)) {
+            $this->organizationAddressPostals[] = $organizationAddressPostal;
+            $organizationAddressPostal->setAddressPostal($this);
+        }
+
+        return $this;
+    }
+
+    public function removeOrganizationAddressPostal(OrganizationAddressPostal $organizationAddressPostal): self
+    {
+        if ($this->organizationAddressPostals->removeElement($organizationAddressPostal)) {
+            //            $organizationAddressPostal->setAddressPostal(null); // TODO: actuellement, pas nullable: conserver?
+        }
+
+        return $this;
+    }
 }

+ 16 - 0
src/Entity/Core/City.php

@@ -21,8 +21,24 @@ class City
     #[ORM\GeneratedValue]
     private ?int $id = null;
 
+    #[ORM\ManyToOne(targetEntity: Department::class, cascade: [])]
+    #[ORM\JoinColumn(nullable: false)]
+    protected Department $department;
+
     public function getId(): ?int
     {
         return $this->id;
     }
+
+    public function getDepartment(): Department
+    {
+        return $this->department;
+    }
+
+    public function setDepartment(Department $department): self
+    {
+        $this->department = $department;
+
+        return $this;
+    }
 }

+ 5 - 5
src/Entity/Core/ContactPoint.php

@@ -8,7 +8,7 @@ use ApiPlatform\Metadata\ApiResource;
 use App\Entity\Billing\ResidenceArea;
 use App\Entity\Organization\Organization;
 use App\Entity\Person\Person;
-use App\Entity\Place\Place;
+use App\Entity\Place\AbstractPlace;
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use App\Entity\Traits\CreatedOnAndByTrait;
 use App\Enum\Core\ContactPointTypeEnum;
@@ -84,7 +84,7 @@ class ContactPoint
     private Collection $person;
 
     /** @var Collection<int, Place> */
-    #[ORM\ManyToMany(targetEntity: Place::class, inversedBy: 'contactpoint')]
+    #[ORM\ManyToMany(targetEntity: AbstractPlace::class, mappedBy: 'contactpoint')]
     #[ORM\JoinTable(name: 'place_contactpoint')]
     #[ORM\JoinColumn(name: 'contactPoint_id', referencedColumnName: 'id', unique: true)]
     #[ORM\InverseJoinColumn(name: 'place_id', referencedColumnName: 'id')]
@@ -272,14 +272,14 @@ class ContactPoint
     }
 
     /**
-     * @return Collection<int, Place>
+     * @return Collection<int, AbstractPlace>
      */
     public function getPlace(): Collection
     {
         return $this->place;
     }
 
-    public function addPlace(Place $place): self
+    public function addPlace(AbstractPlace $place): self
     {
         if (!$this->place->contains($place)) {
             $this->place[] = $place;
@@ -289,7 +289,7 @@ class ContactPoint
         return $this;
     }
 
-    public function removePlace(Place $place): self
+    public function removePlace(AbstractPlace $place): self
     {
         if ($this->place->removeElement($place)) {
             $place->removeContactpoint($this);

+ 16 - 0
src/Entity/Core/Department.php

@@ -21,8 +21,24 @@ class Department
     #[ORM\GeneratedValue]
     private ?int $id = null;
 
+    #[ORM\ManyToOne(targetEntity: Region::class, cascade: [])]
+    #[ORM\JoinColumn(nullable: false)]
+    protected Region $region;
+
     public function getId(): ?int
     {
         return $this->id;
     }
+
+    public function getRegion(): Region
+    {
+        return $this->region;
+    }
+
+    public function setRegion(Region $region): self
+    {
+        $this->region = $region;
+
+        return $this;
+    }
 }

+ 45 - 0
src/Entity/Core/File.php

@@ -10,11 +10,13 @@ use ApiPlatform\Metadata\Get;
 use ApiPlatform\Metadata\Post;
 use ApiPlatform\Metadata\Put;
 use App\Entity\AccessWish\DocumentWish;
+use App\Entity\Booking\CalendarSynchro;
 use App\Entity\Booking\Event;
 use App\Entity\Booking\EventReport;
 use App\Entity\Booking\Work;
 use App\Entity\Education\EducationStudent;
 use App\Entity\Message\TemplateSystem;
+use App\Entity\Network\Network;
 use App\Entity\Organization\Activity;
 use App\Entity\Organization\OnlineRegistrationSettings;
 use App\Entity\Organization\Organization;
@@ -240,6 +242,12 @@ class File
     #[ORM\JoinColumn(onDelete: 'CASCADE')]
     private ?TemplateSystem $templateSystem = null;
 
+    #[ORM\OneToMany(mappedBy: 'image', targetEntity: Network::class)]
+    private Collection $networks;
+
+    #[ORM\OneToOne(targetEntity: CalendarSynchro::class, mappedBy: 'file')]
+    private CalendarSynchro $calendarSynchro;
+
     #[Pure]
     public function __construct()
     {
@@ -250,6 +258,7 @@ class File
         $this->events = new ArrayCollection();
         $this->activityLogos = new ArrayCollection();
         $this->activityImages = new ArrayCollection();
+        $this->networks = new ArrayCollection();
     }
 
     public function getId(): ?int
@@ -747,4 +756,40 @@ class File
 
         return $this;
     }
+
+    public function getNetworks(): Collection
+    {
+        return $this->networks;
+    }
+
+    public function addNetwork(Network $network): self
+    {
+        if (!$this->networks->contains($network)) {
+            $this->networks[] = $network;
+            $network->setImage($this);
+        }
+
+        return $this;
+    }
+
+    public function removeNetwork(Network $network): self
+    {
+        if ($this->networks->removeElement($network)) {
+            //            $network->setImage(null);  // TODO: actuellement, pas nullable: conserver?
+        }
+
+        return $this;
+    }
+
+    public function getCalendarSynchro(): CalendarSynchro
+    {
+        return $this->calendarSynchro;
+    }
+
+    public function setCalendarSynchro(CalendarSynchro $calendarSynchro): self
+    {
+        $this->calendarSynchro = $calendarSynchro;
+
+        return $this;
+    }
 }

+ 120 - 0
src/Entity/Core/LoginLog.php

@@ -0,0 +1,120 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Entity\Core;
+
+use ApiPlatform\Metadata\ApiResource;
+use Doctrine\ORM\Mapping as ORM;
+
+#[ApiResource(operations: [])]
+#[ORM\Entity]
+class LoginLog
+{
+    #[ORM\Id]
+    #[ORM\Column]
+    #[ORM\GeneratedValue]
+    private int $id;
+
+    #[ORM\Column]
+    protected mixed $login;
+
+    #[ORM\Column(type: 'integer', options: ['nullable' => true])]
+    protected int $organizationId;
+
+    #[ORM\Column]
+    protected mixed $navigateur;
+
+    #[ORM\Column]
+    protected mixed $ip;
+
+    #[ORM\Column(type: 'date', options: ['nullable' => true])]
+    protected ?\DateTimeInterface $date;
+
+    #[ORM\Column]
+    protected mixed $type;
+
+    public function getId(): int
+    {
+        return $this->id;
+    }
+
+    public function setId(int $id): self
+    {
+        $this->id = $id;
+
+        return $this;
+    }
+
+    public function getLogin(): mixed
+    {
+        return $this->login;
+    }
+
+    public function setLogin(mixed $login): self
+    {
+        $this->login = $login;
+
+        return $this;
+    }
+
+    public function getOrganizationId(): int
+    {
+        return $this->organizationId;
+    }
+
+    public function setOrganizationId(int $organizationId): self
+    {
+        $this->organizationId = $organizationId;
+
+        return $this;
+    }
+
+    public function getNavigateur(): mixed
+    {
+        return $this->navigateur;
+    }
+
+    public function setNavigateur(mixed $navigateur): self
+    {
+        $this->navigateur = $navigateur;
+
+        return $this;
+    }
+
+    public function getIp(): mixed
+    {
+        return $this->ip;
+    }
+
+    public function setIp(mixed $ip): self
+    {
+        $this->ip = $ip;
+
+        return $this;
+    }
+
+    public function getDate(): \DateTimeInterface
+    {
+        return $this->date;
+    }
+
+    public function setDate(\DateTimeInterface $date): self
+    {
+        $this->date = $date;
+
+        return $this;
+    }
+
+    public function getType(): mixed
+    {
+        return $this->type;
+    }
+
+    public function setType(mixed $type): self
+    {
+        $this->type = $type;
+
+        return $this;
+    }
+}

+ 16 - 0
src/Entity/Core/Region.php

@@ -21,8 +21,24 @@ class Region
     #[ORM\GeneratedValue]
     private ?int $id = null;
 
+    #[ORM\ManyToOne(targetEntity: Country::class, cascade: [])]
+    #[ORM\JoinColumn(nullable: false)]
+    protected Country $country;
+
     public function getId(): ?int
     {
         return $this->id;
     }
+
+    public function getCountry(): Country
+    {
+        return $this->country;
+    }
+
+    public function setCountry(Country $country): self
+    {
+        $this->country = $country;
+
+        return $this;
+    }
 }

+ 184 - 11
src/Entity/Core/Tagg.php

@@ -7,17 +7,21 @@ namespace App\Entity\Core;
 use ApiPlatform\Metadata\ApiResource;
 use App\Entity\Access\Access;
 use App\Entity\AccessWish\AccessWish;
+use App\Entity\Billing\AbstractBillAccounting;
 use App\Entity\Billing\AbstractBillingIntangible;
 use App\Entity\Billing\BillAccounting;
 use App\Entity\Billing\BillPayment;
 use App\Entity\Billing\ResidenceArea;
+use App\Entity\Booking\AbstractBooking;
 use App\Entity\Booking\Course;
 use App\Entity\Booking\EducationalProject;
 use App\Entity\Booking\Event;
 use App\Entity\Booking\Examen;
 use App\Entity\Education\Education;
+use App\Entity\Education\EducationCurriculumPack;
 use App\Entity\Education\EducationNotation;
 use App\Entity\Education\EducationStudent;
+use App\Entity\Message\AbstractMessage;
 use App\Entity\Message\Email;
 use App\Entity\Message\Mail;
 use App\Entity\Message\Sms;
@@ -25,9 +29,10 @@ use App\Entity\Organization\Activity;
 use App\Entity\Organization\Jury;
 use App\Entity\Organization\Organization;
 use App\Entity\Person\Commission;
-use App\Entity\Place\Place;
+use App\Entity\Place\AbstractPlace;
 use App\Entity\Place\PlaceControl;
 use App\Entity\Place\PlaceRepair;
+use App\Entity\Product\AbstractProduct;
 use App\Entity\Product\Equipment;
 use App\Entity\Product\EquipmentControl;
 use App\Entity\Product\EquipmentLoan;
@@ -63,8 +68,8 @@ class Tagg
     #[ORM\ManyToMany(targetEntity: Organization::class, mappedBy: 'tags')]
     protected Collection $organizations;
 
-    /** @var Collection<int, BillAccounting> */
-    #[ORM\ManyToMany(targetEntity: BillAccounting::class, mappedBy: 'tags')]
+    /** @var Collection<int, AbstractBillAccounting> */
+    #[ORM\ManyToMany(targetEntity: AbstractBillAccounting::class, mappedBy: 'tags')]
     protected Collection $billAccountings;
 
     /** @var Collection<int, Equipment> */
@@ -147,8 +152,8 @@ class Tagg
     #[ORM\ManyToMany(targetEntity: EquipmentLoan::class, mappedBy: 'tags')]
     protected Collection $equipmentLoans;
 
-    /** @var Collection<int, Place> */
-    #[ORM\ManyToMany(targetEntity: Place::class, mappedBy: 'tags')]
+    /** @var Collection<int, AbstractPlace> */
+    #[ORM\ManyToMany(targetEntity: AbstractPlace::class, mappedBy: 'tags')]
     protected Collection $places;
 
     /** @var Collection<int, Email> */
@@ -163,6 +168,24 @@ class Tagg
     #[ORM\ManyToMany(targetEntity: Sms::class, mappedBy: 'tags')]
     protected Collection $sms;
 
+    #[ORM\ManyToMany(targetEntity: AbstractProduct::class, mappedBy: 'tags', cascade: [], orphanRemoval: false)]
+    protected Collection $products;
+
+    #[ORM\ManyToMany(targetEntity: AbstractBooking::class, mappedBy: 'tags', cascade: [], orphanRemoval: false)]
+    protected Collection $bookings;
+
+    #[ORM\ManyToMany(targetEntity: AbstractMessage::class, mappedBy: 'tags', cascade: [], orphanRemoval: false)]
+    protected Collection $messages;
+
+    #[ORM\ManyToMany(targetEntity: AbstractRepair::class, mappedBy: 'tags', cascade: [], orphanRemoval: false)]
+    protected Collection $repairs;
+
+    #[ORM\ManyToMany(targetEntity: AbstractControl::class, mappedBy: 'tags', cascade: [], orphanRemoval: false)]
+    protected Collection $controls;
+
+    #[ORM\ManyToMany(targetEntity: EducationCurriculumPack::class, mappedBy: 'tags', cascade: [], orphanRemoval: false)]
+    protected Collection $educationCurriculumPacks;
+
     public function __construct()
     {
         $this->accesses = new ArrayCollection();
@@ -192,6 +215,12 @@ class Tagg
         $this->emails = new ArrayCollection();
         $this->mails = new ArrayCollection();
         $this->sms = new ArrayCollection();
+        $this->products = new ArrayCollection();
+        $this->bookings = new ArrayCollection();
+        $this->messages = new ArrayCollection();
+        $this->repairs = new ArrayCollection();
+        $this->controls = new ArrayCollection();
+        $this->educationCurriculumPacks = new ArrayCollection();
     }
 
     public function getId(): ?int
@@ -266,14 +295,14 @@ class Tagg
     }
 
     /**
-     * @return Collection<int, BillAccounting>
+     * @return Collection<int, AbstractBillAccounting>
      */
     public function getBillAccountings(): Collection
     {
         return $this->billAccountings;
     }
 
-    public function addBillAccounting(BillAccounting $billAccounting): self
+    public function addBillAccounting(AbstractBillAccounting $billAccounting): self
     {
         if (!$this->billAccountings->contains($billAccounting)) {
             $this->billAccountings[] = $billAccounting;
@@ -283,7 +312,7 @@ class Tagg
         return $this;
     }
 
-    public function removeBillAccounting(BillAccounting $billAccounting): self
+    public function removeBillAccounting(AbstractBillAccounting $billAccounting): self
     {
         if ($this->billAccountings->removeElement($billAccounting)) {
             $billAccounting->removeTag($this);
@@ -833,14 +862,14 @@ class Tagg
     }
 
     /**
-     * @return Collection<int, Place>
+     * @return Collection<int, AbstractPlace>
      */
     public function getPlaces(): Collection
     {
         return $this->places;
     }
 
-    public function addPlace(Place $place): self
+    public function addPlace(AbstractPlace $place): self
     {
         if (!$this->places->contains($place)) {
             $this->places[] = $place;
@@ -850,7 +879,7 @@ class Tagg
         return $this;
     }
 
-    public function removePlace(Place $place): self
+    public function removePlace(AbstractPlace $place): self
     {
         if ($this->places->removeElement($place)) {
             $place->removeTag($this);
@@ -939,4 +968,148 @@ class Tagg
 
         return $this;
     }
+
+    public function getProducts(): Collection
+    {
+        return $this->products;
+    }
+
+    public function addProduct(AbstractProduct $product): self
+    {
+        if (!$this->products->contains($product)) {
+            $this->products[] = $product;
+            $product->addTag($this);
+        }
+
+        return $this;
+    }
+
+    public function removeProduct(AbstractProduct $product): self
+    {
+        if ($this->products->removeElement($product)) {
+            $product->removeTag($this);
+        }
+
+        return $this;
+    }
+
+    public function getBookings(): Collection
+    {
+        return $this->bookings;
+    }
+
+    public function addBooking(AbstractBooking $booking): self
+    {
+        if (!$this->bookings->contains($booking)) {
+            $this->bookings[] = $booking;
+            $booking->addTag($this);
+        }
+
+        return $this;
+    }
+
+    public function removeBooking(AbstractBooking $booking): self
+    {
+        if ($this->bookings->removeElement($booking)) {
+            $booking->removeTag($this);
+        }
+
+        return $this;
+    }
+
+    public function getMessages(): Collection
+    {
+        return $this->messages;
+    }
+
+    public function addMessage(mixed $message): self
+    {
+        if (!$this->messages->contains($message)) {
+            $this->messages[] = $message;
+            $message->addTag($this);
+        }
+
+        return $this;
+    }
+
+    public function removeMessage(mixed $message): self
+    {
+        if ($this->messages->removeElement($message)) {
+            $message->removeTag($this);
+        }
+
+        return $this;
+    }
+
+    public function getRepairs(): Collection
+    {
+        return $this->repairs;
+    }
+
+    public function addRepair(AbstractRepair $repair): self
+    {
+        if (!$this->repairs->contains($repair)) {
+            $this->repairs[] = $repair;
+            $repair->addTag($this);
+        }
+
+        return $this;
+    }
+
+    public function removeRepair(AbstractRepair $repair): self
+    {
+        if ($this->repairs->removeElement($repair)) {
+            $repair->removeTag($this);
+        }
+
+        return $this;
+    }
+
+    public function getControls(): Collection
+    {
+        return $this->controls;
+    }
+
+    public function addControl(AbstractControl $control): self
+    {
+        if (!$this->controls->contains($control)) {
+            $this->controls[] = $control;
+            $control->addTag($this);
+        }
+
+        return $this;
+    }
+
+    public function removeControl(AbstractControl $control): self
+    {
+        if ($this->controls->removeElement($control)) {
+            $control->removeTag($this);
+        }
+
+        return $this;
+    }
+
+    public function getEducationCurriculumPacks(): Collection
+    {
+        return $this->educationCurriculumPacks;
+    }
+
+    public function addEducationCurriculumPack(EducationCurriculumPack $educationCurriculumPack): self
+    {
+        if (!$this->educationCurriculumPacks->contains($educationCurriculumPack)) {
+            $this->educationCurriculumPacks[] = $educationCurriculumPack;
+            $educationCurriculumPack->addTag($this);
+        }
+
+        return $this;
+    }
+
+    public function removeEducationCurriculumPack(EducationCurriculumPack $educationCurriculumPack): self
+    {
+        if ($this->educationCurriculumPacks->removeElement($educationCurriculumPack)) {
+            $educationCurriculumPack->removeTag($this);
+        }
+
+        return $this;
+    }
 }

+ 28 - 0
src/Entity/Education/Cycle.php

@@ -67,9 +67,13 @@ class Cycle
     #[ORM\OneToMany(targetEntity: CycleByEducation::class, mappedBy: 'cycle', orphanRemoval: true)]
     private Collection $cycleByEducations;
 
+    #[ORM\OneToMany(mappedBy: 'cycle', targetEntity: EducationCurriculum::class, cascade: [], orphanRemoval: false)]
+    protected Collection $educationCurriculums;
+
     public function __construct()
     {
         $this->cycleByEducations = new ArrayCollection();
+        $this->educationCurriculums = new ArrayCollection();
     }
 
     public function getId(): ?int
@@ -166,4 +170,28 @@ class Cycle
 
         return $this;
     }
+
+    public function getEducationCurriculums(): Collection
+    {
+        return $this->educationCurriculums;
+    }
+
+    public function addEducationCurriculum(EducationCurriculum $educationCurriculum): self
+    {
+        if (!$this->educationCurriculums->contains($educationCurriculum)) {
+            $this->educationCurriculums[] = $educationCurriculum;
+            $educationCurriculum->setCycle($this);
+        }
+
+        return $this;
+    }
+
+    public function removeEducationCurriculum(EducationCurriculum $educationCurriculum): self
+    {
+        if ($this->educationCurriculums->removeElement($educationCurriculum)) {
+            $educationCurriculum->setCycle(null);
+        }
+
+        return $this;
+    }
 }

+ 29 - 0
src/Entity/Education/Education.php

@@ -6,6 +6,7 @@ namespace App\Entity\Education;
 
 use ApiPlatform\Metadata\ApiResource;
 use App\Entity\Billing\ResidenceArea;
+use App\Entity\AccessWish\EducationStudentWish;
 use App\Entity\Booking\Course;
 use App\Entity\Booking\Examen;
 use App\Entity\Core\Tagg;
@@ -66,6 +67,9 @@ class Education
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
     private ?EducationNotationConfig $educationNotationConfig = null;
 
+    #[ORM\OneToMany(mappedBy: 'educationWish', targetEntity: EducationStudentWish::class, cascade: [], orphanRemoval: true)]
+    protected Collection $educationWishes;
+
     public function __construct()
     {
         $this->educationCurriculums = new ArrayCollection();
@@ -74,6 +78,7 @@ class Education
         $this->examens = new ArrayCollection();
         $this->educationTeachers = new ArrayCollection();
         $this->tags = new ArrayCollection();
+        $this->educationWishes = new ArrayCollection();
     }
 
     public function getId(): ?int
@@ -290,4 +295,28 @@ class Education
 
         return $this;
     }
+
+    public function getEducationWishes(): Collection
+    {
+        return $this->educationWishes;
+    }
+
+    public function addEducationWish(EducationStudentWish $educationWish): self
+    {
+        if (!$this->educationWishes->contains($educationWish)) {
+            $this->educationWishes[] = $educationWish;
+            $educationWish->setEducationWish($this);
+        }
+
+        return $this;
+    }
+
+    public function removeEducationWish(EducationStudentWish $educationWish): self
+    {
+        if ($this->educationWishes->removeElement($educationWish)) {
+            $educationWish->setEducationWish(null);
+        }
+
+        return $this;
+    }
 }

+ 34 - 0
src/Entity/Education/EducationComplement.php

@@ -5,6 +5,8 @@ declare(strict_types=1);
 namespace App\Entity\Education;
 
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
 use ApiPlatform\Metadata\ApiResource;
 use Doctrine\ORM\Mapping as ORM;
 
@@ -21,8 +23,40 @@ class EducationComplement
     #[ORM\GeneratedValue]
     private ?int $id = null;
 
+    #[ORM\OneToMany(mappedBy: 'educationComplement', targetEntity: Education::class, cascade: [], orphanRemoval: true)]
+    protected Collection $educations;
+
+    public function __construct()
+    {
+        $this->educations = new ArrayCollection();
+    }
+
     public function getId(): ?int
     {
         return $this->id;
     }
+
+    public function getEducations(): Collection
+    {
+        return $this->educations;
+    }
+
+    public function addEducation(Education $education): self
+    {
+        if (!$this->educations->contains($education)) {
+            $this->educations[] = $education;
+            $education->setEducationComplement($this);
+        }
+
+        return $this;
+    }
+
+    public function removeEducation(Education $education): self
+    {
+        if ($this->educations->removeElement($education)) {
+            $education->setEducationComplement(null);
+        }
+
+        return $this;
+    }
 }

+ 217 - 0
src/Entity/Education/EducationCurriculum.php

@@ -6,6 +6,10 @@ namespace App\Entity\Education;
 
 use ApiPlatform\Metadata\ApiResource;
 use App\Entity\Billing\ResidenceArea;
+use App\Entity\AccessWish\EducationStudentWish;
+use App\Entity\Booking\Course;
+use App\Entity\Booking\Examen;
+use App\Entity\Product\Intangible;
 use App\Repository\Education\EducationCurriculumRepository;
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use Doctrine\Common\Collections\ArrayCollection;
@@ -45,10 +49,53 @@ class EducationCurriculum
     #[ORM\InverseJoinColumn(name: 'educationTiming_id', referencedColumnName: 'id')]
     private Collection $educationTimings;
 
+    #[ORM\OneToMany(mappedBy: 'educationCurriculum', targetEntity: EducationStudentWish::class, cascade: [], orphanRemoval: false)]
+    protected Collection $educationStudentWish;
+
+    #[ORM\ManyToMany(targetEntity: Course::class, mappedBy: 'educationCurriculum', cascade: [], orphanRemoval: false)]
+    protected Collection $courses;
+
+    #[ORM\ManyToMany(targetEntity: Examen::class, mappedBy: 'educationCurriculum', cascade: [], orphanRemoval: false)]
+    protected Collection $examens;
+
+    #[ORM\ManyToMany(targetEntity: Intangible::class, mappedBy: 'educationCurriculums', cascade: [], orphanRemoval: false)]
+    protected Collection $intangibles;
+
+    #[ORM\ManyToMany(
+        targetEntity: EducationCurriculumPack::class,
+        mappedBy: 'requiredEducationCurriculums',
+        cascade: [],
+        orphanRemoval: true,
+    )]
+    protected Collection $requiredEducationCurriculumPacks;
+
+    #[ORM\ManyToMany(
+        targetEntity: EducationCurriculumPack::class,
+        mappedBy: 'requiredChoicesEducationCurriculums',
+        cascade: [],
+        orphanRemoval: true,
+    )]
+    protected Collection $requiredChoicesEducationCurriculumPacks;
+
+    #[ORM\ManyToMany(
+        targetEntity: EducationCurriculumPack::class,
+        mappedBy: 'optionnalEducationCurriculums',
+        cascade: [],
+        orphanRemoval: true,
+    )]
+    protected Collection $optionnalEducationCurriculumPacks;
+
     public function __construct()
     {
         $this->educationStudent = new ArrayCollection();
         $this->educationTimings = new ArrayCollection();
+        $this->educationStudentWish = new ArrayCollection();
+        $this->courses = new ArrayCollection();
+        $this->examens = new ArrayCollection();
+        $this->intangibles = new ArrayCollection();
+        $this->requiredEducationCurriculumPacks = new ArrayCollection();
+        $this->requiredChoicesEducationCurriculumPacks = new ArrayCollection();
+        $this->optionnalEducationCurriculumPacks = new ArrayCollection();
     }
 
     public function getId(): ?int
@@ -145,4 +192,174 @@ class EducationCurriculum
 
         return $this;
     }
+
+    public function getEducationStudentWish(): Collection
+    {
+        return $this->educationStudentWish;
+    }
+
+    public function addEducationStudentWish(EducationStudentWish $educationStudentWish): self
+    {
+        if (!$this->educationStudentWish->contains($educationStudentWish)) {
+            $this->educationStudentWish[] = $educationStudentWish;
+            $educationStudentWish->setEducationCurriculum($this);
+        }
+
+        return $this;
+    }
+
+    public function removeEducationStudentWish(EducationStudentWish $educationStudentWish): self
+    {
+        if ($this->educationStudentWish->removeElement($educationStudentWish)) {
+            $educationStudentWish->setEducationCurriculum(null);
+        }
+
+        return $this;
+    }
+
+    public function getCourses(): Collection
+    {
+        return $this->courses;
+    }
+
+    public function addCourse(Course $course): self
+    {
+        if (!$this->courses->contains($course)) {
+            $this->courses[] = $course;
+            $course->addEducationCurriculum($this);
+        }
+
+        return $this;
+    }
+
+    public function removeCourse(Course $course): self
+    {
+        if ($this->courses->removeElement($course)) {
+            $course->removeEducationCurriculum($this);
+        }
+
+        return $this;
+    }
+
+    public function getExamens(): Collection
+    {
+        return $this->examens;
+    }
+
+    public function addExamen(Examen $examen): self
+    {
+        if (!$this->examens->contains($examen)) {
+            $this->examens[] = $examen;
+            $examen->addEducationCurriculum($this);
+        }
+
+        return $this;
+    }
+
+    public function removeExamen(Examen $examen): self
+    {
+        if ($this->examens->removeElement($examen)) {
+            $examen->removeEducationCurriculum($this);
+        }
+
+        return $this;
+    }
+
+    public function getIntangibles(): Collection
+    {
+        return $this->intangibles;
+    }
+
+    public function addIntangible(Intangible $intangible): self
+    {
+        if (!$this->intangibles->contains($intangible)) {
+            $this->intangibles[] = $intangible;
+            $intangible->addEducationCurriculum($this);
+        }
+
+        return $this;
+    }
+
+    public function removeIntangible(Intangible $intangible): self
+    {
+        if ($this->intangibles->removeElement($intangible)) {
+            $intangible->removeEducationCurriculum($this);
+        }
+
+        return $this;
+    }
+
+    public function getRequiredEducationCurriculumPacks(): Collection
+    {
+        return $this->requiredEducationCurriculumPacks;
+    }
+
+    public function addRequiredEducationCurriculumPack(EducationCurriculumPack $requiredEducationCurriculumPack): self
+    {
+        if (!$this->requiredEducationCurriculumPacks->contains($requiredEducationCurriculumPack)) {
+            $this->requiredEducationCurriculumPacks[] = $requiredEducationCurriculumPack;
+            $requiredEducationCurriculumPack->addRequiredEducationCurriculum($this);
+        }
+
+        return $this;
+    }
+
+    public function removeRequiredEducationCurriculumPack(EducationCurriculumPack $requiredEducationCurriculumPack): self
+    {
+        if ($this->requiredEducationCurriculumPacks->removeElement($requiredEducationCurriculumPack)) {
+            $requiredEducationCurriculumPack->removeRequiredEducationCurriculum($this);
+        }
+
+        return $this;
+    }
+
+    public function getRequiredChoicesEducationCurriculumPacks(): Collection
+    {
+        return $this->requiredChoicesEducationCurriculumPacks;
+    }
+
+    public function addRequiredChoicesEducationCurriculumPack(
+        EducationCurriculumPack $requiredChoicesEducationCurriculumPack,
+    ): self {
+        if (!$this->requiredChoicesEducationCurriculumPacks->contains($requiredChoicesEducationCurriculumPack)) {
+            $this->requiredChoicesEducationCurriculumPacks[] = $requiredChoicesEducationCurriculumPack;
+            $requiredChoicesEducationCurriculumPack->addRequiredChoicesEducationCurriculum($this);
+        }
+
+        return $this;
+    }
+
+    public function removeRequiredChoicesEducationCurriculumPack(
+        EducationCurriculumPack $requiredChoicesEducationCurriculumPack,
+    ): self {
+        if ($this->requiredChoicesEducationCurriculumPacks->removeElement($requiredChoicesEducationCurriculumPack)) {
+            $requiredChoicesEducationCurriculumPack->removeRequiredChoicesEducationCurriculum($this);
+        }
+
+        return $this;
+    }
+
+    public function getOptionnalEducationCurriculumPacks(): Collection
+    {
+        return $this->optionnalEducationCurriculumPacks;
+    }
+
+    public function addOptionnalEducationCurriculumPack(EducationCurriculumPack $optionnalEducationCurriculumPack): self
+    {
+        if (!$this->optionnalEducationCurriculumPacks->contains($optionnalEducationCurriculumPack)) {
+            $this->optionnalEducationCurriculumPacks[] = $optionnalEducationCurriculumPack;
+            $optionnalEducationCurriculumPack->addOptionnalEducationCurriculum($this);
+        }
+
+        return $this;
+    }
+
+    public function removeOptionnalEducationCurriculumPack(EducationCurriculumPack $optionnalEducationCurriculumPack): self
+    {
+        if ($this->optionnalEducationCurriculumPacks->removeElement($optionnalEducationCurriculumPack)) {
+            $optionnalEducationCurriculumPack->removeOptionnalEducationCurriculum($this);
+        }
+
+        return $this;
+    }
 }

+ 200 - 0
src/Entity/Education/EducationCurriculumPack.php

@@ -0,0 +1,200 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Entity\Education;
+
+use ApiPlatform\Metadata\ApiResource;
+use App\Entity\Core\Tagg;
+use App\Entity\Organization\Organization;
+use App\Entity\Product\Intangible;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ORM\Mapping as ORM;
+
+#[ApiResource(operations: [])]
+#[ORM\Entity]
+class EducationCurriculumPack
+{
+    #[ORM\Id]
+    #[ORM\Column]
+    #[ORM\GeneratedValue]
+    private int $id;
+
+    #[ORM\ManyToOne(targetEntity: Organization::class, cascade: [], inversedBy: 'educationCurriculumPacks')]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: false, onDelete: 'SET NULL')]
+    public Organization $organization;
+
+    #[ORM\ManyToMany(targetEntity: Tagg::class, inversedBy: 'educationCurriculumPacks', cascade: ['persist'], orphanRemoval: false)]
+    public Collection $tags;
+
+    #[ORM\ManyToMany(
+        targetEntity: EducationCurriculum::class,
+        inversedBy: 'requiredEducationCurriculumPacks',
+        cascade: [],
+        orphanRemoval: false,
+    )]
+    public Collection $requiredEducationCurriculums;
+
+    #[ORM\ManyToMany(
+        targetEntity: EducationCurriculum::class,
+        inversedBy: 'requiredChoicesEducationCurriculumPacks',
+        cascade: [],
+        orphanRemoval: false,
+    )]
+    public Collection $requiredChoicesEducationCurriculums;
+
+    #[ORM\ManyToMany(
+        targetEntity: EducationCurriculum::class,
+        inversedBy: 'optionnalEducationCurriculumPacks',
+        cascade: [],
+        orphanRemoval: false,
+    )]
+    public Collection $optionnalEducationCurriculums;
+
+    #[ORM\ManyToMany(targetEntity: Intangible::class, mappedBy: 'educationCurriculumPacks', cascade: [], orphanRemoval: false)]
+    public Collection $intangibles;
+
+    public function getId(): int
+    {
+        return $this->id;
+    }
+
+    public function setId(int $id): self
+    {
+        $this->id = $id;
+
+        return $this;
+    }
+
+    public function getOrganization(): Organization
+    {
+        return $this->organization;
+    }
+
+    public function setOrganization(Organization $organization): self
+    {
+        $this->organization = $organization;
+
+        return $this;
+    }
+
+    public function getTags(): Collection
+    {
+        return $this->tags;
+    }
+
+    public function addTag(Tagg $tag): self
+    {
+        if (!$this->tags->contains($tag)) {
+            $this->tags[] = $tag;
+            $tag->addEducationCurriculumPack($this);
+        }
+
+        return $this;
+    }
+
+    public function removeTag(Tagg $tag): self
+    {
+        if ($this->tags->removeElement($tag)) {
+            $tag->removeEducationCurriculumPack($this);
+        }
+
+        return $this;
+    }
+
+    public function getRequiredEducationCurriculums(): Collection
+    {
+        return $this->requiredEducationCurriculums;
+    }
+
+    public function addRequiredEducationCurriculum(EducationCurriculum $requiredEducationCurriculum): self
+    {
+        if (!$this->requiredEducationCurriculums->contains($requiredEducationCurriculum)) {
+            $this->requiredEducationCurriculums[] = $requiredEducationCurriculum;
+            $requiredEducationCurriculum->addRequiredEducationCurriculumPack($this);
+        }
+
+        return $this;
+    }
+
+    public function removeRequiredEducationCurriculum(EducationCurriculum $requiredEducationCurriculum): self
+    {
+        if ($this->requiredEducationCurriculums->removeElement($requiredEducationCurriculum)) {
+            $requiredEducationCurriculum->removeRequiredEducationCurriculumPack($this);
+        }
+
+        return $this;
+    }
+
+    public function getRequiredChoicesEducationCurriculums(): Collection
+    {
+        return $this->requiredChoicesEducationCurriculums;
+    }
+
+    public function addRequiredChoicesEducationCurriculum(EducationCurriculum $requiredChoicesEducationCurriculum): self
+    {
+        if (!$this->requiredChoicesEducationCurriculums->contains($requiredChoicesEducationCurriculum)) {
+            $this->requiredChoicesEducationCurriculums[] = $requiredChoicesEducationCurriculum;
+            $requiredChoicesEducationCurriculum->addRequiredChoicesEducationCurriculumPack($this);
+        }
+
+        return $this;
+    }
+
+    public function removeRequiredChoicesEducationCurriculum(EducationCurriculum $requiredChoicesEducationCurriculum): self
+    {
+        if ($this->requiredChoicesEducationCurriculums->removeElement($requiredChoicesEducationCurriculum)) {
+            $requiredChoicesEducationCurriculum->removeRequiredChoicesEducationCurriculumPack($this);
+        }
+
+        return $this;
+    }
+
+    public function getOptionnalEducationCurriculums(): Collection
+    {
+        return $this->optionnalEducationCurriculums;
+    }
+
+    public function addOptionnalEducationCurriculum(EducationCurriculum $optionnalEducationCurriculum): self
+    {
+        if (!$this->optionnalEducationCurriculums->contains($optionnalEducationCurriculum)) {
+            $this->optionnalEducationCurriculums[] = $optionnalEducationCurriculum;
+            $optionnalEducationCurriculum->addOptionnalEducationCurriculumPack($this);
+        }
+
+        return $this;
+    }
+
+    public function removeOptionnalEducationCurriculum(EducationCurriculum $optionnalEducationCurriculum): self
+    {
+        if ($this->optionnalEducationCurriculums->removeElement($optionnalEducationCurriculum)) {
+            $optionnalEducationCurriculum->removeOptionnalEducationCurriculumPack($this);
+        }
+
+        return $this;
+    }
+
+    public function getIntangibles(): Collection
+    {
+        return $this->intangibles;
+    }
+
+    public function addIntangible(Intangible $intangible): self
+    {
+        if (!$this->intangibles->contains($intangible)) {
+            $this->intangibles[] = $intangible;
+            $intangible->addEducationCurriculumPack($this);
+        }
+
+        return $this;
+    }
+
+    public function removeIntangible(Intangible $intangible): self
+    {
+        if ($this->intangibles->removeElement($intangible)) {
+            $intangible->removeEducationCurriculumPack($this);
+        }
+
+        return $this;
+    }
+}

+ 17 - 0
src/Entity/Education/EducationStudent.php

@@ -9,6 +9,7 @@ use App\Entity\Access\Access;
 use App\Entity\Billing\ResidenceArea;
 use App\Entity\Core\Tagg;
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
+use App\Entity\Product\EquipmentList;
 use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
@@ -62,6 +63,10 @@ class EducationStudent
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
     private ?EducationTiming $educationTiming = null;
 
+    #[ORM\ManyToOne(targetEntity: EquipmentList::class, cascade: [])]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
+    protected EquipmentList $speciality;
+
     public function __construct()
     {
         $this->teachers = new ArrayCollection();
@@ -230,4 +235,16 @@ class EducationStudent
 
         return $this;
     }
+
+    public function getSpeciality(): EquipmentList
+    {
+        return $this->speciality;
+    }
+
+    public function setSpeciality(EquipmentList $speciality): self
+    {
+        $this->speciality = $speciality;
+
+        return $this;
+    }
 }

+ 68 - 0
src/Entity/Message/AbstractMessage.php

@@ -5,8 +5,11 @@ declare(strict_types=1);
 namespace App\Entity\Message;
 
 use App\Attribute\OrganizationDefaultValue;
+use App\Entity\Core\File;
 use App\Entity\Organization\Organization;
 use App\Enum\Message\MessageStatusEnum;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
 use Ramsey\Uuid\Doctrine\UuidGenerator;
 use Ramsey\Uuid\UuidInterface;
@@ -58,6 +61,20 @@ abstract class AbstractMessage
     #[ORM\Column(type: 'text', nullable: true)]
     protected ?string $text = null;
 
+    #[ORM\ManyToMany(targetEntity: File::class, cascade: ['persist'], orphanRemoval: true)]
+    #[ORM\JoinTable(name: 'messages_files')]
+    #[ORM\JoinColumn(name: 'message_id', referencedColumnName: 'id', onDelete: 'cascade')]
+    #[ORM\InverseJoinColumn(name: 'file_id', referencedColumnName: 'id', onDelete: 'cascade')]
+    protected Collection $files;
+
+    #[ORM\OneToMany(mappedBy: 'message', targetEntity: ReportMessage::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $reportMessage;
+
+    public function __construct()
+    {
+        $this->reportMessage = new ArrayCollection();
+    }
+
     public function getId(): ?int
     {
         return $this->id;
@@ -127,4 +144,55 @@ abstract class AbstractMessage
     {
         return $this->dateSent;
     }
+
+    /**
+     * @return Collection<int, File>
+     */
+    public function getFiles(): Collection
+    {
+        return $this->files;
+    }
+
+    public function addFile(File $file): self
+    {
+        if (!$this->files->contains($file)) {
+            $this->files[] = $file;
+            //            $file->addXXXX($this);  // TODO: quel méthode?
+        }
+
+        return $this;
+    }
+
+    public function removeFile(File $file): self
+    {
+        if ($this->files->removeElement($file)) {
+            //            $file->removeXXX($this);  // TODO: quel méthode?
+        }
+
+        return $this;
+    }
+
+    public function getReportMessage(): Collection
+    {
+        return $this->reportMessage;
+    }
+
+    public function addReportMessage(ReportMessage $reportMessage): self
+    {
+        if (!$this->reportMessage->contains($reportMessage)) {
+            $this->reportMessage[] = $reportMessage;
+            $reportMessage->setMessage($this);
+        }
+
+        return $this;
+    }
+
+    public function removeReportMessage(ReportMessage $reportMessage): self
+    {
+        if ($this->reportMessage->removeElement($reportMessage)) {
+            $reportMessage->setMessage(null);
+        }
+
+        return $this;
+    }
 }

+ 2 - 26
src/Entity/Message/Email.php

@@ -6,7 +6,6 @@ namespace App\Entity\Message;
 
 use ApiPlatform\Metadata\ApiResource;
 use App\Entity\Access\Access;
-use App\Entity\Core\File;
 use App\Entity\Core\Tagg;
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use App\Entity\Education\EducationStudent;
@@ -48,7 +47,7 @@ class Email extends AbstractMessage
     #[ORM\JoinTable(name: 'messages_files')]
     #[ORM\JoinColumn(name: 'message_id', referencedColumnName: 'id', onDelete: 'cascade')]
     #[ORM\InverseJoinColumn(name: 'file_id', referencedColumnName: 'id', onDelete: 'cascade')]
-    private Collection $files;
+    protected Collection $files;
 
     /** @var Collection<int, Tagg> */
     #[ORM\ManyToMany(targetEntity: Tagg::class, inversedBy: 'emails', cascade: ['persist'])]
@@ -63,6 +62,7 @@ class Email extends AbstractMessage
         $this->reports = new ArrayCollection();
         $this->files = new ArrayCollection();
         $this->tags = new ArrayCollection();
+        parent::__construct();
     }
 
     public function setIsSystem(bool $isSystem): self
@@ -131,30 +131,6 @@ class Email extends AbstractMessage
         return $this;
     }
 
-    /**
-     * @return Collection<int, File>
-     */
-    public function getFiles(): Collection
-    {
-        return $this->files;
-    }
-
-    public function addFile(File $file): self
-    {
-        if (!$this->files->contains($file)) {
-            $this->files[] = $file;
-        }
-
-        return $this;
-    }
-
-    public function removeFile(File $file): self
-    {
-        $this->files->removeElement($file);
-
-        return $this;
-    }
-
     /**
      * @return Collection<int, Tagg>
      */

+ 1 - 32
src/Entity/Message/Mail.php

@@ -6,7 +6,6 @@ namespace App\Entity\Message;
 
 use ApiPlatform\Metadata\ApiResource;
 use App\Entity\Access\Access;
-use App\Entity\Core\File;
 use App\Entity\Core\Tagg;
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use App\Entity\Education\EducationStudent;
@@ -31,13 +30,6 @@ class Mail extends AbstractMessage
     #[ORM\JoinColumn(nullable: true)]
     private ?Access $author = null;
 
-    /** @var Collection<int, File> */
-    #[ORM\ManyToMany(targetEntity: File::class, cascade: ['persist'], orphanRemoval: true)]
-    #[ORM\JoinTable(name: 'messages_files')]
-    #[ORM\JoinColumn(name: 'message_id', referencedColumnName: 'id', onDelete: 'cascade')]
-    #[ORM\InverseJoinColumn(name: 'file_id', referencedColumnName: 'id', onDelete: 'cascade')]
-    private Collection $files;
-
     /** @var Collection<int, Tagg> */
     #[ORM\ManyToMany(targetEntity: Tagg::class, inversedBy: 'mails', cascade: ['persist'])]
     #[ORM\JoinTable(name: 'tag_message')]
@@ -50,6 +42,7 @@ class Mail extends AbstractMessage
         $this->uuid = Uuid::uuid4();
         $this->files = new ArrayCollection();
         $this->tags = new ArrayCollection();
+        parent::__construct();
     }
 
     public function getAuthor(): ?Access
@@ -64,30 +57,6 @@ class Mail extends AbstractMessage
         return $this;
     }
 
-    /**
-     * @return Collection<int, File>
-     */
-    public function getFiles(): Collection
-    {
-        return $this->files;
-    }
-
-    public function addFile(File $file): self
-    {
-        if (!$this->files->contains($file)) {
-            $this->files[] = $file;
-        }
-
-        return $this;
-    }
-
-    public function removeFile(File $file): self
-    {
-        $this->files->removeElement($file);
-
-        return $this;
-    }
-
     /**
      * @return Collection<int, Tagg>
      */

+ 79 - 0
src/Entity/Message/ReportMessage.php

@@ -0,0 +1,79 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Entity\Message;
+
+use ApiPlatform\Metadata\ApiResource;
+use App\Entity\Access\Access;
+use App\Entity\Organization\Organization;
+use Doctrine\ORM\Mapping as ORM;
+
+#[ApiResource(operations: [])]
+#[ORM\Entity]
+class ReportMessage
+{
+    #[ORM\Id]
+    #[ORM\Column]
+    #[ORM\GeneratedValue]
+    private int $id;
+
+    #[ORM\ManyToOne(targetEntity: AbstractMessage::class, cascade: [], inversedBy: 'reportMessage')]
+    public mixed $message;
+
+    #[ORM\ManyToOne(targetEntity: Access::class, cascade: [])]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
+    public Access $access;
+
+    #[ORM\ManyToOne(targetEntity: Organization::class, cascade: [])]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
+    public Organization $organization;
+
+    public function getId(): int
+    {
+        return $this->id;
+    }
+
+    public function setId(int $id): self
+    {
+        $this->id = $id;
+
+        return $this;
+    }
+
+    public function getMessage(): mixed
+    {
+        return $this->message;
+    }
+
+    public function setMessage(mixed $message): self
+    {
+        $this->message = $message;
+
+        return $this;
+    }
+
+    public function getAccess(): Access
+    {
+        return $this->access;
+    }
+
+    public function setAccess(Access $access): self
+    {
+        $this->access = $access;
+
+        return $this;
+    }
+
+    public function getOrganization(): Organization
+    {
+        return $this->organization;
+    }
+
+    public function setOrganization(Organization $organization): self
+    {
+        $this->organization = $organization;
+
+        return $this;
+    }
+}

+ 1 - 0
src/Entity/Message/Sms.php

@@ -46,6 +46,7 @@ class Sms extends AbstractMessage
         $this->uuid = Uuid::uuid4();
         $this->reports = new ArrayCollection();
         $this->tags = new ArrayCollection();
+        parent::__construct();
     }
 
     public function getAuthor(): ?Access

+ 16 - 0
src/Entity/Network/Network.php

@@ -6,6 +6,7 @@ namespace App\Entity\Network;
 
 use ApiPlatform\Metadata\ApiResource;
 use App\Entity\Message\ReportSms;
+use App\Entity\Core\File;
 use App\Repository\Network\NetworkRepository;
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use Doctrine\Common\Collections\ArrayCollection;
@@ -42,6 +43,9 @@ class Network
     #[ORM\OneToMany(targetEntity: NetworkOrganization::class, mappedBy: 'network', orphanRemoval: true)]
     private Collection $organizations;
 
+    #[ORM\ManyToOne(targetEntity: File::class, cascade: [], inversedBy: 'networks')]
+    protected File $image;
+
     #[Pure]
     public function __construct()
     {
@@ -125,4 +129,16 @@ class Network
 
         return $this;
     }
+
+    public function getImage(): File
+    {
+        return $this->image;
+    }
+
+    public function setImage(File $image): self
+    {
+        $this->image = $image;
+
+        return $this;
+    }
 }

+ 15 - 0
src/Entity/Organization/Activity.php

@@ -58,6 +58,9 @@ class Activity
     #[ORM\InverseJoinColumn(name: 'tag_id', referencedColumnName: 'id')]
     private Collection $tags;
 
+    #[ORM\ManyToOne(targetEntity: TypeOfPractice::class, cascade: [])]
+    protected TypeOfPractice $typeOfSection;
+
     public function __construct()
     {
         $this->categories = new ArrayCollection();
@@ -195,4 +198,16 @@ class Activity
 
         return $this;
     }
+
+    public function getTypeOfSection(): TypeOfPractice
+    {
+        return $this->typeOfSection;
+    }
+
+    public function setTypeOfSection(TypeOfPractice $typeOfSection): self
+    {
+        $this->typeOfSection = $typeOfSection;
+
+        return $this;
+    }
 }

+ 15 - 0
src/Entity/Organization/CotisationByYear.php

@@ -25,6 +25,9 @@ class CotisationByYear
     #[ORM\JoinColumn(name: 'organization_id', referencedColumnName: 'id', nullable: false)]
     private Organization $organization;
 
+    #[ORM\OneToOne(mappedBy: 'cotisationByYear', targetEntity: CotisationStaffInfos::class, cascade: ['persist'])]
+    protected CotisationStaffInfos $cotisationStaffInfos;
+
     public function getId(): ?int
     {
         return $this->id;
@@ -41,4 +44,16 @@ class CotisationByYear
 
         return $this;
     }
+
+    public function getCotisationStaffInfos(): CotisationStaffInfos
+    {
+        return $this->cotisationStaffInfos;
+    }
+
+    public function setCotisationStaffInfos(CotisationStaffInfos $cotisationStaffInfos): self
+    {
+        $this->cotisationStaffInfos = $cotisationStaffInfos;
+
+        return $this;
+    }
 }

+ 15 - 0
src/Entity/Organization/CotisationStaffInfos.php

@@ -21,8 +21,23 @@ class CotisationStaffInfos
     #[ORM\GeneratedValue]
     private ?int $id = null;
 
+    #[ORM\OneToOne(targetEntity: CotisationByYear::class, inversedBy: 'cotisationStaffInfos', cascade: [])]
+    protected CotisationByYear $cotisationByYear;
+
     public function getId(): ?int
     {
         return $this->id;
     }
+
+    public function getCotisationByYear(): CotisationByYear
+    {
+        return $this->cotisationByYear;
+    }
+
+    public function setCotisationByYear(CotisationByYear $cotisationByYear): self
+    {
+        $this->cotisationByYear = $cotisationByYear;
+
+        return $this;
+    }
 }

+ 17 - 0
src/Entity/Organization/OnlineRegistrationDocument.php

@@ -29,6 +29,10 @@ class OnlineRegistrationDocument
     #[ORM\JoinColumn(nullable: true)]
     private ?OnlineRegistrationSettings $guardiansOnlineRegistrationSettings = null;
 
+    #[ORM\ManyToOne(targetEntity: OnlineRegistrationSettings::class, cascade: [], inversedBy: 'adultStudentDocuments')]
+    #[ORM\JoinColumn(nullable: true)]
+    protected OnlineRegistrationSettings $adultStudentOnlineRegistrationSettings;
+
     public function getId(): ?int
     {
         return $this->id;
@@ -57,4 +61,17 @@ class OnlineRegistrationDocument
 
         return $this;
     }
+
+    public function getAdultStudentOnlineRegistrationSettings(): OnlineRegistrationSettings
+    {
+        return $this->adultStudentOnlineRegistrationSettings;
+    }
+
+    public function setAdultStudentOnlineRegistrationSettings(
+        OnlineRegistrationSettings $adultStudentOnlineRegistrationSettings,
+    ): self {
+        $this->adultStudentOnlineRegistrationSettings = $adultStudentOnlineRegistrationSettings;
+
+        return $this;
+    }
 }

+ 45 - 0
src/Entity/Organization/OnlineRegistrationSettings.php

@@ -49,6 +49,17 @@ class OnlineRegistrationSettings
     #[ORM\OneToMany(targetEntity: OnlineRegistrationOpeningPeriod::class, mappedBy: 'onlineRegistrationSettingsNewEnrolments', cascade: ['persist'], orphanRemoval: true)]
     private Collection $openingPeriodsNewEnrolments;
 
+    #[ORM\OneToOne(inversedBy: 'onlineRegistrationSettings', targetEntity: Organization::class, cascade: [])]
+    protected Organization $organization;
+
+    #[ORM\OneToMany(
+        mappedBy: 'adultStudentOnlineRegistrationSettings',
+        targetEntity: OnlineRegistrationDocument::class,
+        cascade: ['persist'],
+        orphanRemoval: true,
+    )]
+    protected Collection $adultStudentDocuments;
+
     public function __construct()
     {
         $this->openingPeriods = new ArrayCollection();
@@ -57,6 +68,7 @@ class OnlineRegistrationSettings
         $this->introductionFiles = new ArrayCollection();
         $this->introductionFilesNewEnrolments = new ArrayCollection();
         $this->openingPeriodsNewEnrolments = new ArrayCollection();
+        $this->adultStudentDocuments = new ArrayCollection();
     }
 
     public function getId(): ?int
@@ -251,4 +263,37 @@ class OnlineRegistrationSettings
 
         return $this;
     }
+
+    public function getOrganization(): Organization
+    {
+        return $this->organization;
+    }
+
+    public function setOrganization(Organization $organization): self
+    {
+        $this->organization = $organization;
+
+        return $this;
+    }
+
+    public function getAdultStudentDocuments(): Collection
+    {
+        return $this->adultStudentDocuments;
+    }
+
+    public function addAdultStudentDocument(OnlineRegistrationDocument $adultStudentDocument): self
+    {
+        if (!$this->adultStudentDocuments->contains($adultStudentDocument)) {
+            $this->adultStudentDocuments[] = $adultStudentDocument;
+        }
+
+        return $this;
+    }
+
+    public function removeAdultStudentDocument(OnlineRegistrationDocument $adultStudentDocument): self
+    {
+        $this->adultStudentDocuments->removeElement($adultStudentDocument);
+
+        return $this;
+    }
 }

+ 287 - 27
src/Entity/Organization/Organization.php

@@ -7,8 +7,12 @@ namespace App\Entity\Organization;
 use ApiPlatform\Metadata\ApiResource;
 use ApiPlatform\Metadata\Get;
 use App\Entity\Access\Access;
+use App\Entity\Billing\BillingExportSetting;
 use App\Entity\Billing\BillingSetting;
+use App\Entity\Billing\BillSchedule;
+use App\Entity\Billing\FamilyQuotientModel;
 use App\Entity\Booking\Attendance;
+use App\Entity\Booking\AttendanceBookingReason;
 use App\Entity\Booking\Course;
 use App\Entity\Booking\EducationalProject;
 use App\Entity\Booking\Event;
@@ -26,12 +30,13 @@ use App\Entity\Education\EducationCategory;
 use App\Entity\Education\EducationNotationConfig;
 use App\Entity\Education\EducationTiming;
 use App\Entity\Education\PeriodNotation;
+use App\Entity\Message\AbstractMessage;
 use App\Entity\Message\Email;
 use App\Entity\Message\Mail;
 use App\Entity\Message\Sms;
 use App\Entity\Network\NetworkOrganization;
 use App\Entity\Person\Commission;
-use App\Entity\Place\Place;
+use App\Entity\Place\AbstractPlace;
 use App\Entity\Product\Equipment;
 use App\Entity\Product\Intangible;
 use App\Entity\Reward\Reward;
@@ -332,7 +337,7 @@ class Organization
     private Collection $commissions;
 
     /** @var Collection<int, Place> */
-    #[ORM\OneToMany(targetEntity: Place::class, mappedBy: 'organization', cascade: ['persist', 'remove'], orphanRemoval: true)]
+    #[ORM\OneToMany(mappedBy: 'organization', targetEntity: AbstractPlace::class, orphanRemoval: true)]
     private Collection $places;
 
     /** @var Collection<int, Attendance> */
@@ -365,6 +370,39 @@ class Organization
     #[ORM\InverseJoinColumn(name: 'tag_id', referencedColumnName: 'id')]
     private Collection $tags;
 
+    #[ORM\OneToMany(mappedBy: 'organization', targetEntity: NetworkOrganization::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $network;
+
+    #[ORM\OneToMany(mappedBy: 'parent', targetEntity: NetworkOrganization::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $networkChild;
+
+    #[ORM\OneToOne(mappedBy: 'organization', targetEntity: BillingExportSetting::class, cascade: ['persist'])]
+    protected BillingExportSetting $billingExportSetting;
+
+    #[ORM\OneToMany(mappedBy: 'organization', targetEntity: Access::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $access;
+
+    #[ORM\OneToMany(mappedBy: 'organization', targetEntity: AbstractMessage::class, cascade: [], orphanRemoval: true)]
+    protected Collection $messages;
+
+    #[ORM\OneToOne(mappedBy: 'organization', targetEntity: OnlineRegistrationSettings::class, cascade: ['persist'])]
+    protected OnlineRegistrationSettings $onlineRegistrationSettings;
+
+    #[ORM\OneToMany(mappedBy: 'organization', targetEntity: CotisationByYear::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $cotisationByYears;
+
+    #[ORM\OneToMany(mappedBy: 'organization', targetEntity: AttendanceBookingReason::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $attendanceBookingReasons;
+
+    #[ORM\OneToMany(mappedBy: 'organization', targetEntity: Cycle::class, cascade: [], orphanRemoval: true)]
+    protected Collection $educationCurriculumPacks;
+
+    #[ORM\OneToMany(mappedBy: 'organization', targetEntity: FamilyQuotientModel::class, cascade: [], orphanRemoval: true)]
+    protected Collection $familyQuotientModels;
+
+    #[ORM\OneToMany(mappedBy: 'organization', targetEntity: BillSchedule::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $billSchedules;
+
     #[Pure]
     public function __construct()
     {
@@ -404,6 +442,15 @@ class Organization
         $this->donors = new ArrayCollection();
         $this->tags = new ArrayCollection();
         $this->rewards = new ArrayCollection();
+        $this->network = new ArrayCollection();
+        $this->networkChild = new ArrayCollection();
+        $this->access = new ArrayCollection();
+        $this->messages = new ArrayCollection();
+        $this->cotisationByYears = new ArrayCollection();
+        $this->attendanceBookingReasons = new ArrayCollection();
+        $this->educationCurriculumPacks = new ArrayCollection();
+        $this->familyQuotientModels = new ArrayCollection();
+        $this->billSchedules = new ArrayCollection();
     }
 
     public function getId(): ?int
@@ -481,6 +528,25 @@ class Organization
         return $this->accesses;
     }
 
+    public function addAccess(Access $access): self
+    {
+        if (!$this->access->contains($access)) {
+            $this->access[] = $access;
+            $access->setOrganization($this);
+        }
+
+        return $this;
+    }
+
+    public function removeAccess(Access $access): self
+    {
+        if ($this->access->removeElement($access)) {
+            $access->setOrganization(null);
+        }
+
+        return $this;
+    }
+
     public function getNetworkOrganizations(): Collection
     {
         return new ArrayCollection([]);
@@ -1250,28 +1316,6 @@ class Organization
         return $this;
     }
 
-    public function addAccess(Access $access): self
-    {
-        if (!$this->accesses->contains($access)) {
-            $this->accesses[] = $access;
-            $access->setOrganization($this);
-        }
-
-        return $this;
-    }
-
-    public function removeAccess(Access $access): self
-    {
-        if ($this->accesses->removeElement($access)) {
-            // set the owning side to null (unless already changed)
-            if ($access->getOrganization() === $this) {
-                $access->setOrganization(null);
-            }
-        }
-
-        return $this;
-    }
-
     public function getContactPerson(): ?Access
     {
         return $this->contactPerson;
@@ -1699,14 +1743,14 @@ class Organization
     }
 
     /**
-     * @return Collection<int, Place>
+     * @return Collection<int, AbstractPlace>
      */
     public function getPlaces(): Collection
     {
         return $this->places;
     }
 
-    public function addPlace(Place $place): self
+    public function addPlace(AbstractPlace $place): self
     {
         if (!$this->places->contains($place)) {
             $this->places[] = $place;
@@ -1716,7 +1760,7 @@ class Organization
         return $this;
     }
 
-    public function removePlace(Place $place): self
+    public function removePlace(AbstractPlace $place): self
     {
         if ($this->places->removeElement($place)) {
             // set the owning side to null (unless already changed)
@@ -1908,4 +1952,220 @@ class Organization
 
         return $this;
     }
+
+    public function getNetwork(): Collection
+    {
+        return $this->network;
+    }
+
+    public function addNetwork(NetworkOrganization $network): self
+    {
+        if (!$this->network->contains($network)) {
+            $this->network[] = $network;
+            $network->setOrganization($this);
+        }
+
+        return $this;
+    }
+
+    public function removeNetwork(NetworkOrganization $network): self
+    {
+        if ($this->network->removeElement($network)) {
+            $network->setOrganization(null);
+        }
+
+        return $this;
+    }
+
+    public function getNetworkChild(): Collection
+    {
+        return $this->networkChild;
+    }
+
+    public function addNetworkChild(NetworkOrganization $networkChild): self
+    {
+        if (!$this->networkChild->contains($networkChild)) {
+            $this->networkChild[] = $networkChild;
+            $networkChild->setParent($this);
+        }
+
+        return $this;
+    }
+
+    public function removeNetworkChild(NetworkOrganization $networkChild): self
+    {
+        if ($this->networkChild->removeElement($networkChild)) {
+            $networkChild->setParent(null);
+        }
+
+        return $this;
+    }
+
+    public function getBillingExportSetting(): BillingExportSetting
+    {
+        return $this->billingExportSetting;
+    }
+
+    public function setBillingExportSetting(BillingExportSetting $billingExportSetting): self
+    {
+        $this->billingExportSetting = $billingExportSetting;
+
+        return $this;
+    }
+
+    public function getMessages(): Collection
+    {
+        return $this->messages;
+    }
+
+    public function addMessage(mixed $message): self
+    {
+        if (!$this->messages->contains($message)) {
+            $this->messages[] = $message;
+            $message->setOrganization($this);
+        }
+
+        return $this;
+    }
+
+    public function removeMessage(mixed $message): self
+    {
+        if ($this->messages->removeElement($message)) {
+            $message->setOrganization(null);
+        }
+
+        return $this;
+    }
+
+    public function getOnlineRegistrationSettings(): OnlineRegistrationSettings
+    {
+        return $this->onlineRegistrationSettings;
+    }
+
+    public function setOnlineRegistrationSettings(OnlineRegistrationSettings $onlineRegistrationSettings): self
+    {
+        $this->onlineRegistrationSettings = $onlineRegistrationSettings;
+
+        return $this;
+    }
+
+    public function getCotisationByYears(): Collection
+    {
+        return $this->cotisationByYears;
+    }
+
+    public function addCotisationByYear(CotisationByYear $cotisationByYear): self
+    {
+        if (!$this->cotisationByYears->contains($cotisationByYear)) {
+            $this->cotisationByYears[] = $cotisationByYear;
+            $cotisationByYear->setOrganization($this);
+        }
+
+        return $this;
+    }
+
+    public function removeCotisationByYear(CotisationByYear $cotisationByYear): self
+    {
+        if ($this->cotisationByYears->removeElement($cotisationByYear)) {
+            $cotisationByYear->setOrganization(null);
+        }
+
+        return $this;
+    }
+
+    public function getAttendanceBookingReasons(): Collection
+    {
+        return $this->attendanceBookingReasons;
+    }
+
+    public function addAttendanceBookingReason(AttendanceBookingReason $attendanceBookingReason): self
+    {
+        if (!$this->attendanceBookingReasons->contains($attendanceBookingReason)) {
+            $this->attendanceBookingReasons[] = $attendanceBookingReason;
+            $attendanceBookingReason->setOrganization($this);
+        }
+
+        return $this;
+    }
+
+    public function removeAttendanceBookingReason(AttendanceBookingReason $attendanceBookingReason): self
+    {
+        if ($this->attendanceBookingReasons->removeElement($attendanceBookingReason)) {
+            $attendanceBookingReason->setOrganization(null);
+        }
+
+        return $this;
+    }
+
+    public function getEducationCurriculumPacks(): Collection
+    {
+        return $this->educationCurriculumPacks;
+    }
+
+    public function addEducationCurriculumPack(Cycle $educationCurriculumPack): self
+    {
+        if (!$this->educationCurriculumPacks->contains($educationCurriculumPack)) {
+            $this->educationCurriculumPacks[] = $educationCurriculumPack;
+            $educationCurriculumPack->setOrganization($this);
+        }
+
+        return $this;
+    }
+
+    public function removeEducationCurriculumPack(Cycle $educationCurriculumPack): self
+    {
+        if ($this->educationCurriculumPacks->removeElement($educationCurriculumPack)) {
+            $educationCurriculumPack->setOrganization(null);
+        }
+
+        return $this;
+    }
+
+    public function getFamilyQuotientModels(): Collection
+    {
+        return $this->familyQuotientModels;
+    }
+
+    public function addFamilyQuotientModel(FamilyQuotientModel $familyQuotientModel): self
+    {
+        if (!$this->familyQuotientModels->contains($familyQuotientModel)) {
+            $this->familyQuotientModels[] = $familyQuotientModel;
+            $familyQuotientModel->setOrganization($this);
+        }
+
+        return $this;
+    }
+
+    public function removeFamilyQuotientModel(FamilyQuotientModel $familyQuotientModel): self
+    {
+        if ($this->familyQuotientModels->removeElement($familyQuotientModel)) {
+            //            $familyQuotientModel->setOrganization(null); // TODO: actuellement, pas nullable: conserver?
+        }
+
+        return $this;
+    }
+
+    public function getBillSchedules(): Collection
+    {
+        return $this->billSchedules;
+    }
+
+    public function addBillSchedule(BillSchedule $billSchedule): self
+    {
+        if (!$this->billSchedules->contains($billSchedule)) {
+            $this->billSchedules[] = $billSchedule;
+            $billSchedule->setOrganization($this);
+        }
+
+        return $this;
+    }
+
+    public function removeBillSchedule(BillSchedule $billSchedule): self
+    {
+        if ($this->billSchedules->removeElement($billSchedule)) {
+            //            $billSchedule->setOrganization(null);  // TODO: actuellement, pas nullable: conserver?
+        }
+
+        return $this;
+    }
 }

+ 62 - 0
src/Entity/Person/AllowedIp.php

@@ -0,0 +1,62 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Entity\Person;
+
+use ApiPlatform\Metadata\ApiResource;
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Validator\Constraints as Assert;
+
+#[ApiResource(operations: [])]
+#[ORM\Entity]
+class AllowedIp
+{
+    #[ORM\Id]
+    #[ORM\Column]
+    #[ORM\GeneratedValue]
+    private int $id;
+
+    #[Assert\NotNull]
+    #[ORM\ManyToOne(targetEntity: Person::class, inversedBy: 'allowedIps')]
+    private Person $person;
+
+    #[ORM\Column(type: 'string', nullable: true)]
+    private string $ipOrRange;
+
+    public function getId(): int
+    {
+        return $this->id;
+    }
+
+    public function setId(int $id): self
+    {
+        $this->id = $id;
+
+        return $this;
+    }
+
+    public function getPerson(): Person
+    {
+        return $this->person;
+    }
+
+    public function setPerson(Person $person): self
+    {
+        $this->person = $person;
+
+        return $this;
+    }
+
+    public function getIpOrRange(): string
+    {
+        return $this->ipOrRange;
+    }
+
+    public function setIpOrRange(string $ipOrRange): self
+    {
+        $this->ipOrRange = $ipOrRange;
+
+        return $this;
+    }
+}

+ 15 - 0
src/Entity/Person/Medical.php

@@ -21,8 +21,23 @@ class Medical
     #[ORM\GeneratedValue]
     private ?int $id = null;
 
+    #[ORM\OneToOne(mappedBy: 'medical', targetEntity: Person::class, cascade: ['persist'], orphanRemoval: true)]
+    private Person $person;
+
     public function getId(): ?int
     {
         return $this->id;
     }
+
+    public function getPerson(): Person
+    {
+        return $this->person;
+    }
+
+    public function setPerson(Person $person): self
+    {
+        $this->person = $person;
+
+        return $this;
+    }
 }

+ 42 - 0
src/Entity/Person/Person.php

@@ -127,6 +127,12 @@ class Person implements UserInterface, PasswordAuthenticatedUserInterface
     #[ORM\Column(type: 'json', nullable: true)]
     private array $confidentiality = [];
 
+    #[ORM\OneToOne(targetEntity: Medical::class, inversedBy: 'person', cascade: ['persist'])]
+    private Medical $medical;
+
+    #[ORM\OneToMany(targetEntity: AllowedIp::class, mappedBy: 'person', cascade: ['persist'], orphanRemoval: true)]
+    private Collection $allowedIps;
+
     #[Pure]
     public function __construct()
     {
@@ -644,4 +650,40 @@ class Person implements UserInterface, PasswordAuthenticatedUserInterface
 
         return $this;
     }
+
+    public function getMedical(): Medical
+    {
+        return $this->medical;
+    }
+
+    public function setMedical(Medical $medical): self
+    {
+        $this->medical = $medical;
+
+        return $this;
+    }
+
+    public function getAllowedIps(): Collection
+    {
+        return $this->allowedIps;
+    }
+
+    public function addAllowedIp(AllowedIp $allowedIp): self
+    {
+        if (!$this->allowedIps->contains($allowedIp)) {
+            $this->allowedIps[] = $allowedIp;
+            $allowedIp->setPerson($this);
+        }
+
+        return $this;
+    }
+
+    public function removeAllowedIp(AllowedIp $allowedIp): self
+    {
+        if ($this->allowedIps->removeElement($allowedIp)) {
+            //            $allowedIp->setPerson(null);  // TODO: actuellement, pas nullable: conserver?
+        }
+
+        return $this;
+    }
 }

+ 456 - 0
src/Entity/Place/AbstractPlace.php

@@ -0,0 +1,456 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Entity\Place;
+
+use App\Entity\Booking\Course;
+use App\Entity\Booking\EducationalProject;
+use App\Entity\Booking\Event;
+use App\Entity\Booking\Examen;
+use App\Entity\Core\AddressPostal;
+use App\Entity\Core\ContactPoint;
+use App\Entity\Core\Tagg;
+use App\Entity\Organization\Organization;
+use App\Entity\Product\Equipment;
+// use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * @todo : A la suite de la migration, il faut supprimer le nom de la table pour avoir une table Place, et supprimer l'attribut discr.
+ *
+ * Classe ... qui ...
+ */
+#[ORM\MappedSuperclass]
+abstract class AbstractPlace
+{
+    #[ORM\Id]
+    #[ORM\Column]
+    #[ORM\GeneratedValue]
+    private ?int $id = null;
+
+    #[ORM\Column(length: 255, nullable: false)]
+    protected string $discr;
+
+    #[ORM\ManyToOne(cascade: ['persist'], inversedBy: 'places')]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
+    private AddressPostal $addressPostal;
+
+    #[ORM\OneToMany(mappedBy: 'place', targetEntity: Event::class)]
+    private Collection $events;
+
+    #[ORM\ManyToMany(targetEntity: Tagg::class, inversedBy: 'places', cascade: ['persist'])]
+    #[ORM\JoinTable(name: 'tag_place')]
+    #[ORM\JoinColumn(name: 'place_id', referencedColumnName: 'id')]
+    #[ORM\InverseJoinColumn(name: 'tag_id', referencedColumnName: 'id')]
+    private Collection $tags;
+
+    #[ORM\ManyToOne(inversedBy: 'places')]
+    private Organization $organization;
+
+    #[ORM\ManyToMany(targetEntity: ContactPoint::class, inversedBy: 'place', cascade: ['persist'])]
+    private Collection $contactpoint;
+
+    #[ORM\OneToMany(mappedBy: 'place', targetEntity: Room::class, cascade: ['persist'], orphanRemoval: true)]
+    #[ORM\JoinColumn(nullable: false)]
+    private Collection $rooms;
+
+    #[ORM\OneToMany(mappedBy: 'place', targetEntity: PlaceControl::class, cascade: ['persist'], orphanRemoval: true)]
+    #[ORM\JoinColumn(nullable: false)]
+    private Collection $controls;
+
+    #[ORM\OneToMany(mappedBy: 'place', targetEntity: PlaceRepair::class, cascade: ['persist'], orphanRemoval: true)]
+    #[ORM\JoinColumn(nullable: false)]
+    private Collection $repairs;
+
+    #[ORM\OneToMany(mappedBy: 'place', targetEntity: Course::class)]
+    private Collection $courses;
+
+    #[ORM\OneToMany(mappedBy: 'place', targetEntity: EducationalProject::class)]
+    private Collection $educationalProjects;
+
+    #[ORM\OneToMany(mappedBy: 'place', targetEntity: Examen::class)]
+    private Collection $examens;
+
+    #[ORM\OneToMany(mappedBy: 'placeStorage', targetEntity: Equipment::class)]
+    private Collection $equipmentStorages;
+
+    #[ORM\OneToMany(mappedBy: 'placeWhereIsUsed', targetEntity: Equipment::class)]
+    private Collection $equipmentUseds;
+
+    public function __construct()
+    {
+        $this->events = new ArrayCollection();
+        $this->tags = new ArrayCollection();
+        $this->contactpoint = new ArrayCollection();
+        $this->rooms = new ArrayCollection();
+        $this->controls = new ArrayCollection();
+        $this->repairs = new ArrayCollection();
+        $this->courses = new ArrayCollection();
+        $this->educationalProjects = new ArrayCollection();
+        $this->examens = new ArrayCollection();
+        $this->equipmentStorages = new ArrayCollection();
+        $this->equipmentUseds = new ArrayCollection();
+    }
+
+    public function getId(): ?int
+    {
+        return $this->id;
+    }
+
+    public function getDiscr(): string
+    {
+        return $this->discr;
+    }
+
+    public function setDiscr(string $discr): self
+    {
+        $this->discr = $discr;
+
+        return $this;
+    }
+
+    public function getAddressPostal(): ?AddressPostal
+    {
+        return $this->addressPostal;
+    }
+
+    public function setAddressPostal(?AddressPostal $addressPostal): self
+    {
+        $this->addressPostal = $addressPostal;
+
+        return $this;
+    }
+
+    /**
+     * @return Collection<int, Event>
+     */
+    public function getEvents(): Collection
+    {
+        return $this->events;
+    }
+
+    public function addEvent(Event $event): self
+    {
+        if (!$this->events->contains($event)) {
+            $this->events[] = $event;
+            $event->setPlace($this);
+        }
+
+        return $this;
+    }
+
+    public function removeEvent(Event $event): self
+    {
+        if ($this->events->removeElement($event)) {
+            // set the owning side to null (unless already changed)
+            if ($event->getPlace() === $this) {
+                $event->setPlace(null);
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * @return Collection<int, Tagg>
+     */
+    public function getTags(): Collection
+    {
+        return $this->tags;
+    }
+
+    public function addTag(Tagg $tag): self
+    {
+        if (!$this->tags->contains($tag)) {
+            $this->tags[] = $tag;
+        }
+
+        return $this;
+    }
+
+    public function removeTag(Tagg $tag): self
+    {
+        $this->tags->removeElement($tag);
+
+        return $this;
+    }
+
+    public function getOrganization(): ?Organization
+    {
+        return $this->organization;
+    }
+
+    public function setOrganization(?Organization $organization): self
+    {
+        $this->organization = $organization;
+
+        return $this;
+    }
+
+    /**
+     * @return Collection<int, ContactPoint>
+     */
+    public function getContactpoint(): Collection
+    {
+        return $this->contactpoint;
+    }
+
+    public function addContactpoint(ContactPoint $contactpoint): self
+    {
+        if (!$this->contactpoint->contains($contactpoint)) {
+            $this->contactpoint[] = $contactpoint;
+        }
+
+        return $this;
+    }
+
+    public function removeContactpoint(ContactPoint $contactpoint): self
+    {
+        $this->contactpoint->removeElement($contactpoint);
+
+        return $this;
+    }
+
+    /**
+     * @return Collection<int, Room>
+     */
+    public function getRooms(): Collection
+    {
+        return $this->rooms;
+    }
+
+    public function addRoom(Room $room): self
+    {
+        if (!$this->rooms->contains($room)) {
+            $this->rooms[] = $room;
+            $room->setPlace($this);
+        }
+
+        return $this;
+    }
+
+    public function removeRoom(Room $room): self
+    {
+        if ($this->rooms->removeElement($room)) {
+            // set the owning side to null (unless already changed)
+            if ($room->getPlace() === $this) {
+                $room->setPlace(null);
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * @return Collection<int, PlaceControl>
+     */
+    public function getControls(): Collection
+    {
+        return $this->controls;
+    }
+
+    public function addControl(PlaceControl $control): self
+    {
+        if (!$this->controls->contains($control)) {
+            $this->controls[] = $control;
+            $control->setPlace($this);
+        }
+
+        return $this;
+    }
+
+    public function removeControl(PlaceControl $control): self
+    {
+        if ($this->controls->removeElement($control)) {
+            // set the owning side to null (unless already changed)
+            if ($control->getPlace() === $this) {
+                $control->setPlace(null);
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * @return Collection<int, PlaceRepair>
+     */
+    public function getRepairs(): Collection
+    {
+        return $this->repairs;
+    }
+
+    public function addRepair(PlaceRepair $repair): self
+    {
+        if (!$this->repairs->contains($repair)) {
+            $this->repairs[] = $repair;
+            $repair->setPlace($this);
+        }
+
+        return $this;
+    }
+
+    public function removeRepair(PlaceRepair $repair): self
+    {
+        if ($this->repairs->removeElement($repair)) {
+            // set the owning side to null (unless already changed)
+            if ($repair->getPlace() === $this) {
+                $repair->setPlace(null);
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * @return Collection<int, Course>
+     */
+    public function getCourses(): Collection
+    {
+        return $this->courses;
+    }
+
+    public function addCourse(Course $course): self
+    {
+        if (!$this->courses->contains($course)) {
+            $this->courses[] = $course;
+            $course->setPlace($this);
+        }
+
+        return $this;
+    }
+
+    public function removeCourse(Course $course): self
+    {
+        if ($this->courses->removeElement($course)) {
+            // set the owning side to null (unless already changed)
+            if ($course->getPlace() === $this) {
+                $course->setPlace(null);
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * @return Collection<int, EducationalProject>
+     */
+    public function getEducationalProjects(): Collection
+    {
+        return $this->educationalProjects;
+    }
+
+    public function addEducationalProject(EducationalProject $educationalProject): self
+    {
+        if (!$this->educationalProjects->contains($educationalProject)) {
+            $this->educationalProjects[] = $educationalProject;
+            $educationalProject->setPlace($this);
+        }
+
+        return $this;
+    }
+
+    public function removeEducationalProject(EducationalProject $educationalProject): self
+    {
+        if ($this->educationalProjects->removeElement($educationalProject)) {
+            // set the owning side to null (unless already changed)
+            if ($educationalProject->getPlace() === $this) {
+                $educationalProject->setPlace(null);
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * @return Collection<int, Examen>
+     */
+    public function getExamens(): Collection
+    {
+        return $this->examens;
+    }
+
+    public function addExamen(Examen $examen): self
+    {
+        if (!$this->examens->contains($examen)) {
+            $this->examens[] = $examen;
+            $examen->setPlace($this);
+        }
+
+        return $this;
+    }
+
+    public function removeExamen(Examen $examen): self
+    {
+        if ($this->examens->removeElement($examen)) {
+            // set the owning side to null (unless already changed)
+            if ($examen->getPlace() === $this) {
+                $examen->setPlace(null);
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * @return Collection<int, Equipment>
+     */
+    public function getEquipmentStorages(): Collection
+    {
+        return $this->equipmentStorages;
+    }
+
+    public function addEquipmentStorage(Equipment $equipmentStorage): self
+    {
+        if (!$this->equipmentStorages->contains($equipmentStorage)) {
+            $this->equipmentStorages[] = $equipmentStorage;
+            $equipmentStorage->setPlaceStorage($this);
+        }
+
+        return $this;
+    }
+
+    public function removeEquipmentStorage(Equipment $equipmentStorage): self
+    {
+        if ($this->equipmentStorages->removeElement($equipmentStorage)) {
+            // set the owning side to null (unless already changed)
+            if ($equipmentStorage->getPlaceStorage() === $this) {
+                $equipmentStorage->setPlaceStorage(null);
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * @return Collection<int, Equipment>
+     */
+    public function getEquipmentUseds(): Collection
+    {
+        return $this->equipmentUseds;
+    }
+
+    public function addEquipmentUsed(Equipment $equipmentUsed): self
+    {
+        if (!$this->equipmentUseds->contains($equipmentUsed)) {
+            $this->equipmentUseds[] = $equipmentUsed;
+            $equipmentUsed->setPlaceWhereIsUsed($this);
+        }
+
+        return $this;
+    }
+
+    public function removeEquipmentUsed(Equipment $equipmentUsed): self
+    {
+        if ($this->equipmentUseds->removeElement($equipmentUsed)) {
+            // set the owning side to null (unless already changed)
+            if ($equipmentUsed->getPlaceWhereIsUsed() === $this) {
+                $equipmentUsed->setPlaceWhereIsUsed(null);
+            }
+        }
+
+        return $this;
+    }
+}

+ 2 - 435
src/Entity/Place/Place.php

@@ -16,448 +16,15 @@ use App\Entity\Organization\Organization;
 use App\Entity\Person\CommissionMember;
 use App\Entity\Product\Equipment;
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
-use Doctrine\Common\Collections\ArrayCollection;
-use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
 
 /**
- * @todo : A la suite de la migration, il faut supprimer le nom de la table pour avoir une table Place, et supprimer l'attribut discr.
- *
  * Classe ... qui ...
  */
-#[ApiResource(operations: [])]
 // #[Auditable]
+#[ApiResource(operations: [])]
 #[ORM\Entity]
 #[ORM\Table(name: 'Place')]
-#[ORM\InheritanceType('SINGLE_TABLE')]
-#[ORM\DiscriminatorColumn(name: 'discr', type: 'string')]
-#[ORM\DiscriminatorMap([
-    'place' => 'Place',
-    'place_system' => 'PlaceSystem',
-])]
-class Place
+class Place extends AbstractPlace
 {
-    #[ORM\Id]
-    #[ORM\Column]
-    #[ORM\GeneratedValue]
-    private ?int $id = null;
-
-    #[ORM\ManyToOne(cascade: ['persist'], inversedBy: 'places')]
-    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
-    private ?AddressPostal $addressPostal = null;
-
-    /** @var Collection<int, Event> */
-    #[ORM\OneToMany(targetEntity: Event::class, mappedBy: 'place')]
-    private Collection $events;
-
-    /** @var Collection<int, Tagg> */
-    #[ORM\ManyToMany(targetEntity: Tagg::class, inversedBy: 'places', cascade: ['persist'])]
-    #[ORM\JoinTable(name: 'tag_place')]
-    #[ORM\JoinColumn(name: 'place_id', referencedColumnName: 'id')]
-    #[ORM\InverseJoinColumn(name: 'tag_id', referencedColumnName: 'id')]
-    private Collection $tags;
-
-    #[ORM\ManyToOne(inversedBy: 'places')]
-    private ?Organization $organization = null;
-
-    /** @var Collection<int, ContactPoint> */
-    #[ORM\ManyToMany(targetEntity: ContactPoint::class, inversedBy: 'place', cascade: ['persist'])]
-    private Collection $contactpoint;
-
-    /** @var Collection<int, Room> */
-    #[ORM\OneToMany(targetEntity: Room::class, mappedBy: 'place', cascade: ['persist'], orphanRemoval: true)]
-    #[ORM\JoinColumn(nullable: false)]
-    private Collection $rooms;
-
-    /** @var Collection<int, PlaceControl> */
-    #[ORM\OneToMany(targetEntity: PlaceControl::class, mappedBy: 'place', cascade: ['persist'], orphanRemoval: true)]
-    #[ORM\JoinColumn(nullable: false)]
-    private Collection $controls;
-
-    /** @var Collection<int, PlaceRepair> */
-    #[ORM\OneToMany(targetEntity: PlaceRepair::class, mappedBy: 'place', cascade: ['persist'], orphanRemoval: true)]
-    #[ORM\JoinColumn(nullable: false)]
-    private Collection $repairs;
-
-    /** @var Collection<int, Course> */
-    #[ORM\OneToMany(targetEntity: Course::class, mappedBy: 'place')]
-    private Collection $courses;
-
-    /** @var Collection<int, EducationalProject> */
-    #[ORM\OneToMany(targetEntity: EducationalProject::class, mappedBy: 'place')]
-    private Collection $educationalProjects;
-
-    /** @var Collection<int, Examen> */
-    #[ORM\OneToMany(targetEntity: Examen::class, mappedBy: 'place')]
-    private Collection $examens;
-
-    /** @var Collection<int, Equipment> */
-    #[ORM\OneToMany(targetEntity: Equipment::class, mappedBy: 'placeStorage')]
-    private Collection $equipmentStorages;
-
-    /** @var Collection<int, Equipment> */
-    #[ORM\OneToMany(targetEntity: Equipment::class, mappedBy: 'placeWhereIsUsed')]
-    private Collection $equipmentUseds;
-
-    public function __construct()
-    {
-        $this->events = new ArrayCollection();
-        $this->tags = new ArrayCollection();
-        $this->contactpoint = new ArrayCollection();
-        $this->rooms = new ArrayCollection();
-        $this->controls = new ArrayCollection();
-        $this->repairs = new ArrayCollection();
-        $this->courses = new ArrayCollection();
-        $this->educationalProjects = new ArrayCollection();
-        $this->examens = new ArrayCollection();
-        $this->equipmentStorages = new ArrayCollection();
-        $this->equipmentUseds = new ArrayCollection();
-    }
-
-    public function getId(): ?int
-    {
-        return $this->id;
-    }
-
-    public function getAddressPostal(): ?AddressPostal
-    {
-        return $this->addressPostal;
-    }
-
-    public function setAddressPostal(?AddressPostal $addressPostal): self
-    {
-        $this->addressPostal = $addressPostal;
-
-        return $this;
-    }
-
-    /**
-     * @return Collection<int, Event>
-     */
-    public function getEvents(): Collection
-    {
-        return $this->events;
-    }
-
-    public function addEvent(Event $event): self
-    {
-        if (!$this->events->contains($event)) {
-            $this->events[] = $event;
-            $event->setPlace($this);
-        }
-
-        return $this;
-    }
-
-    public function removeEvent(Event $event): self
-    {
-        if ($this->events->removeElement($event)) {
-            // set the owning side to null (unless already changed)
-            if ($event->getPlace() === $this) {
-                $event->setPlace(null);
-            }
-        }
-
-        return $this;
-    }
-
-    /**
-     * @return Collection<int, Tagg>
-     */
-    public function getTags(): Collection
-    {
-        return $this->tags;
-    }
-
-    public function addTag(Tagg $tag): self
-    {
-        if (!$this->tags->contains($tag)) {
-            $this->tags[] = $tag;
-        }
-
-        return $this;
-    }
-
-    public function removeTag(Tagg $tag): self
-    {
-        $this->tags->removeElement($tag);
-
-        return $this;
-    }
-
-    public function getOrganization(): ?Organization
-    {
-        return $this->organization;
-    }
-
-    public function setOrganization(?Organization $organization): self
-    {
-        $this->organization = $organization;
-
-        return $this;
-    }
-
-    /**
-     * @return Collection<int, ContactPoint>
-     */
-    public function getContactpoint(): Collection
-    {
-        return $this->contactpoint;
-    }
-
-    public function addContactpoint(ContactPoint $contactpoint): self
-    {
-        if (!$this->contactpoint->contains($contactpoint)) {
-            $this->contactpoint[] = $contactpoint;
-        }
-
-        return $this;
-    }
-
-    public function removeContactpoint(ContactPoint $contactpoint): self
-    {
-        $this->contactpoint->removeElement($contactpoint);
-
-        return $this;
-    }
-
-    /**
-     * @return Collection<int, Room>
-     */
-    public function getRooms(): Collection
-    {
-        return $this->rooms;
-    }
-
-    public function addRoom(Room $room): self
-    {
-        if (!$this->rooms->contains($room)) {
-            $this->rooms[] = $room;
-            $room->setPlace($this);
-        }
-
-        return $this;
-    }
-
-    public function removeRoom(Room $room): self
-    {
-        if ($this->rooms->removeElement($room)) {
-            // set the owning side to null (unless already changed)
-            if ($room->getPlace() === $this) {
-                $room->setPlace(null);
-            }
-        }
-
-        return $this;
-    }
-
-    /**
-     * @return Collection<int, PlaceControl>
-     */
-    public function getControls(): Collection
-    {
-        return $this->controls;
-    }
-
-    public function addControl(PlaceControl $control): self
-    {
-        if (!$this->controls->contains($control)) {
-            $this->controls[] = $control;
-            $control->setPlace($this);
-        }
-
-        return $this;
-    }
-
-    public function removeControl(PlaceControl $control): self
-    {
-        if ($this->controls->removeElement($control)) {
-            // set the owning side to null (unless already changed)
-            if ($control->getPlace() === $this) {
-                $control->setPlace(null);
-            }
-        }
-
-        return $this;
-    }
-
-    /**
-     * @return Collection<int, PlaceRepair>
-     */
-    public function getRepairs(): Collection
-    {
-        return $this->repairs;
-    }
-
-    public function addRepair(PlaceRepair $repair): self
-    {
-        if (!$this->repairs->contains($repair)) {
-            $this->repairs[] = $repair;
-            $repair->setPlace($this);
-        }
-
-        return $this;
-    }
-
-    public function removeRepair(PlaceRepair $repair): self
-    {
-        if ($this->repairs->removeElement($repair)) {
-            // set the owning side to null (unless already changed)
-            if ($repair->getPlace() === $this) {
-                $repair->setPlace(null);
-            }
-        }
-
-        return $this;
-    }
-
-    /**
-     * @return Collection<int, Course>
-     */
-    public function getCourses(): Collection
-    {
-        return $this->courses;
-    }
-
-    public function addCourse(Course $course): self
-    {
-        if (!$this->courses->contains($course)) {
-            $this->courses[] = $course;
-            $course->setPlace($this);
-        }
-
-        return $this;
-    }
-
-    public function removeCourse(Course $course): self
-    {
-        if ($this->courses->removeElement($course)) {
-            // set the owning side to null (unless already changed)
-            if ($course->getPlace() === $this) {
-                $course->setPlace(null);
-            }
-        }
-
-        return $this;
-    }
-
-    /**
-     * @return Collection<int, EducationalProject>
-     */
-    public function getEducationalProjects(): Collection
-    {
-        return $this->educationalProjects;
-    }
-
-    public function addEducationalProject(EducationalProject $educationalProject): self
-    {
-        if (!$this->educationalProjects->contains($educationalProject)) {
-            $this->educationalProjects[] = $educationalProject;
-            $educationalProject->setPlace($this);
-        }
-
-        return $this;
-    }
-
-    public function removeEducationalProject(EducationalProject $educationalProject): self
-    {
-        if ($this->educationalProjects->removeElement($educationalProject)) {
-            // set the owning side to null (unless already changed)
-            if ($educationalProject->getPlace() === $this) {
-                $educationalProject->setPlace(null);
-            }
-        }
-
-        return $this;
-    }
-
-    /**
-     * @return Collection<int, Examen>
-     */
-    public function getExamens(): Collection
-    {
-        return $this->examens;
-    }
-
-    public function addExamen(Examen $examen): self
-    {
-        if (!$this->examens->contains($examen)) {
-            $this->examens[] = $examen;
-            $examen->setPlace($this);
-        }
-
-        return $this;
-    }
-
-    public function removeExamen(Examen $examen): self
-    {
-        if ($this->examens->removeElement($examen)) {
-            // set the owning side to null (unless already changed)
-            if ($examen->getPlace() === $this) {
-                $examen->setPlace(null);
-            }
-        }
-
-        return $this;
-    }
-
-    /**
-     * @return Collection<int, Equipment>
-     */
-    public function getEquipmentStorages(): Collection
-    {
-        return $this->equipmentStorages;
-    }
-
-    public function addEquipmentStorage(Equipment $equipmentStorage): self
-    {
-        if (!$this->equipmentStorages->contains($equipmentStorage)) {
-            $this->equipmentStorages[] = $equipmentStorage;
-            $equipmentStorage->setPlaceStorage($this);
-        }
-
-        return $this;
-    }
-
-    public function removeEquipmentStorage(Equipment $equipmentStorage): self
-    {
-        if ($this->equipmentStorages->removeElement($equipmentStorage)) {
-            // set the owning side to null (unless already changed)
-            if ($equipmentStorage->getPlaceStorage() === $this) {
-                $equipmentStorage->setPlaceStorage(null);
-            }
-        }
-
-        return $this;
-    }
-
-    /**
-     * @return Collection<int, Equipment>
-     */
-    public function getEquipmentUseds(): Collection
-    {
-        return $this->equipmentUseds;
-    }
-
-    public function addEquipmentUsed(Equipment $equipmentUsed): self
-    {
-        if (!$this->equipmentUseds->contains($equipmentUsed)) {
-            $this->equipmentUseds[] = $equipmentUsed;
-            $equipmentUsed->setPlaceWhereIsUsed($this);
-        }
-
-        return $this;
-    }
-
-    public function removeEquipmentUsed(Equipment $equipmentUsed): self
-    {
-        if ($this->equipmentUseds->removeElement($equipmentUsed)) {
-            // set the owning side to null (unless already changed)
-            if ($equipmentUsed->getPlaceWhereIsUsed() === $this) {
-                $equipmentUsed->setPlaceWhereIsUsed(null);
-            }
-        }
-
-        return $this;
-    }
 }

+ 5 - 4
src/Entity/Place/PlaceControl.php

@@ -25,26 +25,27 @@ use Doctrine\ORM\Mapping as ORM;
 class PlaceControl extends AbstractControl
 {
     #[ORM\ManyToOne(inversedBy: 'controls')]
-    private ?Place $place = null;
+    protected AbstractPlace $place;
 
     /** @var Collection<int, Tagg> */
     #[ORM\ManyToMany(targetEntity: Tagg::class, inversedBy: 'placeControls', cascade: ['persist'])]
     #[ORM\JoinTable(name: 'tag_control')]
     #[ORM\JoinColumn(name: 'control_id', referencedColumnName: 'id')]
     #[ORM\InverseJoinColumn(name: 'tag_id', referencedColumnName: 'id')]
-    private Collection $tags;
+    protected Collection $tags;
 
     public function __construct()
     {
         $this->tags = new ArrayCollection();
+        parent::__construct();
     }
 
-    public function getPlace(): ?Place
+    public function getPlace(): ?AbstractPlace
     {
         return $this->place;
     }
 
-    public function setPlace(?Place $place): self
+    public function setPlace(?AbstractPlace $place): self
     {
         $this->place = $place;
 

+ 6 - 5
src/Entity/Place/PlaceRepair.php

@@ -27,21 +27,22 @@ class PlaceRepair extends AbstractRepair
 {
     #[ORM\ManyToOne(inversedBy: 'placeRepairProviders')]
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
-    private ?Access $provider = null;
+    protected ?Access $provider = null;
 
     #[ORM\ManyToOne(inversedBy: 'repairs')]
-    private ?Place $place = null;
+    protected ?Place $place = null;
 
     /** @var Collection<int, Tagg> */
     #[ORM\ManyToMany(targetEntity: Tagg::class, inversedBy: 'placeRepairs', cascade: ['persist'])]
     #[ORM\JoinTable(name: 'tag_repair')]
     #[ORM\JoinColumn(name: 'repair_id', referencedColumnName: 'id')]
     #[ORM\InverseJoinColumn(name: 'tag_id', referencedColumnName: 'id')]
-    private Collection $tags;
+    protected Collection $tags;
 
     public function __construct()
     {
         $this->tags = new ArrayCollection();
+        parent::__construct();
     }
 
     public function getProvider(): ?Access
@@ -56,12 +57,12 @@ class PlaceRepair extends AbstractRepair
         return $this;
     }
 
-    public function getPlace(): ?Place
+    public function getPlace(): ?AbstractPlace
     {
         return $this->place;
     }
 
-    public function setPlace(?Place $place): self
+    public function setPlace(?AbstractPlace $place): self
     {
         $this->place = $place;
 

+ 4 - 1
src/Entity/Place/PlaceSystem.php

@@ -14,6 +14,9 @@ use Doctrine\ORM\Mapping as ORM;
 // #[Auditable]
 #[ApiResource(operations: [])]
 #[ORM\Entity]
-class PlaceSystem extends Place
+#[ORM\Table(name: 'Place')]
+class PlaceSystem extends AbstractPlace
 {
+    #[ORM\Column(length: 255, nullable: false)]
+    protected string $discr = 'place_system';
 }

+ 4 - 4
src/Entity/Place/Room.php

@@ -29,8 +29,8 @@ class Room
     #[ORM\GeneratedValue]
     private ?int $id = null;
 
-    #[ORM\ManyToOne(targetEntity: Place::class, inversedBy: 'rooms')]
-    private ?Place $place = null;
+    #[ORM\ManyToOne(targetEntity: AbstractPlace::class, inversedBy: 'rooms')]
+    private ?AbstractPlace $place = null;
 
     /** @var Collection<int, RoomControl> */
     #[ORM\OneToMany(targetEntity: RoomControl::class, mappedBy: 'room', cascade: ['persist'], orphanRemoval: true)]
@@ -83,12 +83,12 @@ class Room
         return $this->id;
     }
 
-    public function getPlace(): ?Place
+    public function getPlace(): ?AbstractPlace
     {
         return $this->place;
     }
 
-    public function setPlace(?Place $place): self
+    public function setPlace(?AbstractPlace $place): self
     {
         $this->place = $place;
 

+ 32 - 0
src/Entity/Place/RoomControl.php

@@ -7,6 +7,8 @@ namespace App\Entity\Place;
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use ApiPlatform\Metadata\ApiResource;
 use App\Entity\Core\AbstractControl;
+use App\Entity\Core\Tagg;
+use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
 
 /**
@@ -23,6 +25,12 @@ class RoomControl extends AbstractControl
     #[ORM\ManyToOne(inversedBy: 'controls')]
     private ?Room $room = null;
 
+    #[ORM\ManyToMany(targetEntity: Tagg::class, inversedBy: 'controls', cascade: ['persist'], orphanRemoval: false)]
+    #[ORM\JoinTable(name: 'tag_control')]
+    #[ORM\JoinColumn(name: 'control_id')]
+    #[ORM\InverseJoinColumn(name: 'tag_id')]
+    protected Collection $tags;
+
     public function getRoom(): ?Room
     {
         return $this->room;
@@ -34,4 +42,28 @@ class RoomControl extends AbstractControl
 
         return $this;
     }
+
+    public function getTags(): Collection
+    {
+        return $this->tags;
+    }
+
+    public function addTag(Tagg $tag): self
+    {
+        if (!$this->tags->contains($tag)) {
+            $this->tags[] = $tag;
+            $tag->addControl($this);
+        }
+
+        return $this;
+    }
+
+    public function removeTag(Tagg $tag): self
+    {
+        if ($this->tags->removeElement($tag)) {
+            $tag->removeControl($this);
+        }
+
+        return $this;
+    }
 }

+ 36 - 0
src/Entity/Product/AbstractProduct.php

@@ -4,6 +4,10 @@ declare(strict_types=1);
 
 namespace App\Entity\Product;
 
+use App\Entity\Core\Tagg;
+use App\Entity\Organization\Organization;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
 
 /**
@@ -24,8 +28,40 @@ abstract class AbstractProduct
     #[ORM\GeneratedValue]
     protected ?int $id = null;
 
+    #[ORM\ManyToMany(targetEntity: Tagg::class, inversedBy: 'products', cascade: ['persist'], orphanRemoval: false)]
+    protected Collection $tags;
+
+    public function __construct()
+    {
+        $this->tags = new ArrayCollection();
+    }
+
     public function getId(): ?int
     {
         return $this->id;
     }
+
+    public function getTags(): Collection
+    {
+        return $this->tags;
+    }
+
+    public function addTag(Tagg $tag): self
+    {
+        if (!$this->tags->contains($tag)) {
+            $this->tags[] = $tag;
+            $tag->addProduct($this);
+        }
+
+        return $this;
+    }
+
+    public function removeTag(Tagg $tag): self
+    {
+        if ($this->tags->removeElement($tag)) {
+            $tag->removeProduct($this);
+        }
+
+        return $this;
+    }
 }

+ 115 - 29
src/Entity/Product/Equipment.php

@@ -6,10 +6,11 @@ namespace App\Entity\Product;
 
 use ApiPlatform\Metadata\ApiResource;
 use App\Entity\Access\Access;
+use App\Entity\Booking\ExamenConvocation;
 use App\Entity\Core\Tagg;
 use App\Entity\Organization\Organization;
 use App\Entity\Person\CommissionMember;
-use App\Entity\Place\Place;
+use App\Entity\Place\AbstractPlace;
 use App\Entity\Place\Room;
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use Doctrine\Common\Collections\ArrayCollection;
@@ -31,72 +32,81 @@ class Equipment extends AbstractProduct
     protected ?Organization $organization = null;
 
     #[ORM\ManyToOne(cascade: ['persist'], inversedBy: 'equipments')]
-    private ?Access $access = null;
+    protected ?Access $access = null;
 
     #[ORM\ManyToOne(inversedBy: 'equipmentStorages')]
     #[ORM\JoinColumn(name: 'place_storage_id', nullable: true, onDelete: 'SET NULL')]
-    private ?Place $placeStorage = null;
+    protected ?AbstractPlace $placeStorage = null;
 
     #[ORM\ManyToOne(inversedBy: 'equipmentStorages')]
     #[ORM\JoinColumn(name: 'room_storage_id', nullable: true, onDelete: 'SET NULL')]
-    private ?Room $roomStorage = null;
+    protected ?Room $roomStorage = null;
 
     #[ORM\ManyToOne(inversedBy: 'equipmentUseds')]
     #[ORM\JoinColumn(name: 'place_used_id', nullable: true, onDelete: 'SET NULL')]
-    private ?Place $placeWhereIsUsed = null;
+    protected ?AbstractPlace $placeWhereIsUsed = null;
 
     #[ORM\ManyToOne(inversedBy: 'equipmentUseds')]
     #[ORM\JoinColumn(name: 'room_used_id', nullable: true, onDelete: 'SET NULL')]
-    private ?Room $roomWhereIsUsed = null;
+    protected ?Room $roomWhereIsUsed = null;
 
     #[ORM\ManyToOne]
     #[ORM\JoinColumn(nullable: true)]
-    private ?EquipmentList $equipmentList = null;
+    protected ?EquipmentList $equipmentList = null;
 
     /** @var Collection<int, EquipmentComposition> */
-    #[ORM\OneToMany(targetEntity: EquipmentComposition::class, mappedBy: 'parent', orphanRemoval: true)]
-    private Collection $equipmentComposition;
+    #[ORM\OneToMany(mappedBy: 'parent', targetEntity: EquipmentComposition::class, orphanRemoval: true)]
+    protected Collection $equipmentComposition;
 
     /** @var Collection<int, EquipmentRepair> */
-    #[ORM\OneToMany(targetEntity: EquipmentRepair::class, mappedBy: 'equipment', orphanRemoval: true)]
-    private Collection $equipmentRepair;
+    #[ORM\OneToMany(mappedBy: 'equipment', targetEntity: EquipmentRepair::class, orphanRemoval: true)]
+    protected Collection $equipmentRepair;
 
     /** @var Collection<int, EquipmentControl> */
-    #[ORM\OneToMany(targetEntity: EquipmentControl::class, mappedBy: 'equipment', cascade: ['persist'], orphanRemoval: true)]
-    private Collection $equipmentControl;
+    #[ORM\OneToMany(mappedBy: 'equipment', targetEntity: EquipmentControl::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $equipmentControl;
 
     /** @var Collection<int, EquipmentLoan> */
-    #[ORM\OneToMany(targetEntity: EquipmentLoan::class, mappedBy: 'equipment', cascade: ['persist'], orphanRemoval: true)]
-    private Collection $equipmentLoan;
+    #[ORM\OneToMany(mappedBy: 'equipment', targetEntity: EquipmentLoan::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $equipmentLoan;
 
     #[ORM\ManyToOne(inversedBy: 'equipmentSuppliers')]
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
-    private ?Access $supplier = null;
+    protected ?Access $supplier = null;
 
     #[ORM\ManyToOne(inversedBy: 'equipmentControlManagers')]
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
-    private ?Access $controlManager = null;
+    protected ?Access $controlManager = null;
 
     #[ORM\ManyToOne(cascade: ['persist'], inversedBy: 'equipmentEditors')]
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
-    private ?Access $editor = null;
+    protected ?Access $editor = null;
 
     /** @var Collection<int, EquipmentList> */
     #[ORM\ManyToMany(targetEntity: EquipmentList::class)]
     #[ORM\JoinColumn(name: 'equipment_id', referencedColumnName: 'id')]
     #[ORM\InverseJoinColumn(name: 'equipmentlist_id', referencedColumnName: 'id')]
-    private Collection $instrumentOrVoice;
+    protected Collection $instrumentOrVoice;
 
     #[ORM\ManyToOne]
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
-    private ?Access $managerControl = null;
+    protected ?Access $managerControl = null;
 
     /** @var Collection<int, Tagg> */
     #[ORM\ManyToMany(targetEntity: Tagg::class, inversedBy: 'equipments', cascade: ['persist'])]
     #[ORM\JoinTable(name: 'tag_product')]
     #[ORM\JoinColumn(name: 'product_id', referencedColumnName: 'id')]
     #[ORM\InverseJoinColumn(name: 'tag_id', referencedColumnName: 'id')]
-    private Collection $tags;
+    protected Collection $tags;
+
+    #[ORM\OneToMany(mappedBy: 'children', targetEntity: EquipmentComposition::class, cascade: [], orphanRemoval: true)]
+    protected Collection $equipmentCompositionChildren;
+
+    #[ORM\OneToMany(mappedBy: 'equipment', targetEntity: EquipmentLoan::class, cascade: [], orphanRemoval: true)]
+    protected Collection $equipmentLoanFiltered;
+
+    #[ORM\ManyToMany(targetEntity: ExamenConvocation::class, mappedBy: 'equipments', cascade: [], orphanRemoval: false)]
+    protected Collection $examenConvocations;
 
     public function __construct()
     {
@@ -106,6 +116,10 @@ class Equipment extends AbstractProduct
         $this->equipmentLoan = new ArrayCollection();
         $this->instrumentOrVoice = new ArrayCollection();
         $this->tags = new ArrayCollection();
+        $this->equipmentCompositionChildren = new ArrayCollection();
+        $this->equipmentLoanFiltered = new ArrayCollection();
+        $this->examenConvocations = new ArrayCollection();
+        parent::__construct();
     }
 
     public function getOrganization(): ?Organization
@@ -132,12 +146,12 @@ class Equipment extends AbstractProduct
         return $this;
     }
 
-    public function getPlaceStorage(): ?Place
+    public function getPlaceStorage(): ?AbstractPlace
     {
         return $this->placeStorage;
     }
 
-    public function setPlaceStorage(?Place $placeStorage): self
+    public function setPlaceStorage(?AbstractPlace $placeStorage): self
     {
         $this->placeStorage = $placeStorage;
 
@@ -156,12 +170,12 @@ class Equipment extends AbstractProduct
         return $this;
     }
 
-    public function getPlaceWhereIsUsed(): ?Place
+    public function getPlaceWhereIsUsed(): ?AbstractPlace
     {
         return $this->placeWhereIsUsed;
     }
 
-    public function setPlaceWhereIsUsed(?Place $placeWhereIsUsed): self
+    public function setPlaceWhereIsUsed(?AbstractPlace $placeWhereIsUsed): self
     {
         $this->placeWhereIsUsed = $placeWhereIsUsed;
 
@@ -384,9 +398,6 @@ class Equipment extends AbstractProduct
         return $this;
     }
 
-    /**
-     * @return Collection<int, Tagg>
-     */
     public function getTags(): Collection
     {
         return $this->tags;
@@ -396,6 +407,7 @@ class Equipment extends AbstractProduct
     {
         if (!$this->tags->contains($tag)) {
             $this->tags[] = $tag;
+            $tag->addProduct($this);
         }
 
         return $this;
@@ -403,7 +415,81 @@ class Equipment extends AbstractProduct
 
     public function removeTag(Tagg $tag): self
     {
-        $this->tags->removeElement($tag);
+        if ($this->tags->removeElement($tag)) {
+            $tag->removeProduct($this);
+        }
+
+        return $this;
+    }
+
+    public function getEquipmentCompositionChildren(): Collection
+    {
+        return $this->equipmentCompositionChildren;
+    }
+
+    public function addEquipmentCompositionChildren(EquipmentComposition $equipmentCompositionChildren): self
+    {
+        if (!$this->equipmentCompositionChildren->contains($equipmentCompositionChildren)) {
+            $this->equipmentCompositionChildren[] = $equipmentCompositionChildren;
+            $equipmentCompositionChildren->setChildren($this);
+        }
+
+        return $this;
+    }
+
+    public function removeEquipmentCompositionChildren(EquipmentComposition $equipmentCompositionChildren): self
+    {
+        if ($this->equipmentCompositionChildren->removeElement($equipmentCompositionChildren)) {
+            $equipmentCompositionChildren->setChildren(null);
+        }
+
+        return $this;
+    }
+
+    public function getEquipmentLoanFiltered(): Collection
+    {
+        return $this->equipmentLoanFiltered;
+    }
+
+    public function addEquipmentLoanFiltered(EquipmentLoan $equipmentLoanFiltered): self
+    {
+        if (!$this->equipmentLoanFiltered->contains($equipmentLoanFiltered)) {
+            $this->equipmentLoanFiltered[] = $equipmentLoanFiltered;
+            $equipmentLoanFiltered->setEquipment($this);
+        }
+
+        return $this;
+    }
+
+    public function removeEquipmentLoanFiltered(EquipmentLoan $equipmentLoanFiltered): self
+    {
+        if ($this->equipmentLoanFiltered->removeElement($equipmentLoanFiltered)) {
+            $equipmentLoanFiltered->setEquipment(null);
+        }
+
+        return $this;
+    }
+
+    public function getExamenConvocations(): Collection
+    {
+        return $this->examenConvocations;
+    }
+
+    public function addExamenConvocation(ExamenConvocation $examenConvocation): self
+    {
+        if (!$this->examenConvocations->contains($examenConvocation)) {
+            $this->examenConvocations[] = $examenConvocation;
+            $examenConvocation->addEquipment($this);
+        }
+
+        return $this;
+    }
+
+    public function removeExamenConvocation(ExamenConvocation $examenConvocation): self
+    {
+        if ($this->examenConvocations->removeElement($examenConvocation)) {
+            $examenConvocation->removeEquipment($this);
+        }
 
         return $this;
     }

+ 3 - 2
src/Entity/Product/EquipmentControl.php

@@ -25,18 +25,19 @@ use Doctrine\ORM\Mapping as ORM;
 class EquipmentControl extends AbstractControl
 {
     #[ORM\ManyToOne(inversedBy: 'equipmentControl')]
-    private ?Equipment $equipment = null;
+    protected ?Equipment $equipment = null;
 
     /** @var Collection<int, Tagg> */
     #[ORM\ManyToMany(targetEntity: Tagg::class, inversedBy: 'equipmentControls', cascade: ['persist'])]
     #[ORM\JoinTable(name: 'tag_control')]
     #[ORM\JoinColumn(name: 'control_id', referencedColumnName: 'id')]
     #[ORM\InverseJoinColumn(name: 'tag_id', referencedColumnName: 'id')]
-    private Collection $tags;
+    protected Collection $tags;
 
     public function __construct()
     {
         $this->tags = new ArrayCollection();
+        parent::__construct();
     }
 
     public function getEquipment(): ?Equipment

+ 3 - 3
src/Entity/Product/EquipmentRepair.php

@@ -28,17 +28,17 @@ class EquipmentRepair extends AbstractRepair
 {
     #[ORM\ManyToOne(inversedBy: 'equipmentRepairProviders')]
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
-    private ?Access $provider = null;
+    protected ?Access $provider = null;
 
     #[ORM\ManyToOne(inversedBy: 'equipmentRepair')]
-    private ?Equipment $equipment = null;
+    protected ?Equipment $equipment = null;
 
     /** @var Collection<int, Tagg> */
     #[ORM\ManyToMany(targetEntity: Tagg::class, inversedBy: 'equipmentRepairs', cascade: ['persist'])]
     #[ORM\JoinTable(name: 'tag_repair')]
     #[ORM\JoinColumn(name: 'repair_id', referencedColumnName: 'id')]
     #[ORM\InverseJoinColumn(name: 'tag_id', referencedColumnName: 'id')]
-    private Collection $tags;
+    protected Collection $tags;
 
     public function __construct()
     {

+ 50 - 4
src/Entity/Product/Intangible.php

@@ -9,6 +9,7 @@ use App\Entity\Billing\AccessIntangible;
 use App\Entity\Core\Tagg;
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use App\Entity\Education\EducationCurriculum;
+use App\Entity\Education\EducationCurriculumPack;
 use App\Entity\Organization\Organization;
 use App\Entity\Person\CommissionMember;
 use Doctrine\Common\Collections\ArrayCollection;
@@ -31,24 +32,33 @@ class Intangible extends AbstractProduct
 
     /** @var Collection<int, EducationCurriculum> */
     #[ORM\ManyToMany(targetEntity: EducationCurriculum::class)]
-    private Collection $educationCurriculums;
+    protected Collection $educationCurriculums;
 
     /** @var Collection<int, AccessIntangible> */
-    #[ORM\OneToMany(targetEntity: AccessIntangible::class, mappedBy: 'intangible', cascade: ['persist'], orphanRemoval: true)]
-    private Collection $accessIntangibles;
+    #[ORM\OneToMany(mappedBy: 'intangible', targetEntity: AccessIntangible::class, cascade: ['persist'], orphanRemoval: true)]
+    protected Collection $accessIntangibles;
 
     /** @var Collection<int, Tagg> */
     #[ORM\ManyToMany(targetEntity: Tagg::class, inversedBy: 'intangibles', cascade: ['persist'])]
     #[ORM\JoinTable(name: 'tag_product')]
     #[ORM\JoinColumn(name: 'product_id', referencedColumnName: 'id')]
     #[ORM\InverseJoinColumn(name: 'tag_id', referencedColumnName: 'id')]
-    private Collection $tags;
+    protected Collection $tags;
+
+    #[ORM\ManyToMany(targetEntity: EducationCurriculumPack::class, inversedBy: 'intangibles', cascade: [], orphanRemoval: false)]
+    protected Collection $educationCurriculumPacks;
+
+    #[ORM\OneToOne(inversedBy: 'intangible', targetEntity: IntangiblePriceAndDiscount::class, cascade: ['persist'])]
+    protected IntangiblePriceAndDiscount $intangiblePriceAndDiscount;
 
     public function __construct()
     {
         $this->educationCurriculums = new ArrayCollection();
         $this->accessIntangibles = new ArrayCollection();
         $this->tags = new ArrayCollection();
+        $this->educationCurriculumPacks = new ArrayCollection();
+        $this->intangiblePriceAndDiscount = new IntangiblePriceAndDiscount();
+        parent::__construct();
     }
 
     public function getOrganization(): ?Organization
@@ -140,4 +150,40 @@ class Intangible extends AbstractProduct
 
         return $this;
     }
+
+    public function getEducationCurriculumPacks(): Collection
+    {
+        return $this->educationCurriculumPacks;
+    }
+
+    public function setEducationCurriculumPacks(Collection $educationCurriculumPacks): self
+    {
+        $this->educationCurriculumPacks = $educationCurriculumPacks;
+
+        return $this;
+    }
+
+    public function getIntangiblePriceAndDiscount(): IntangiblePriceAndDiscount
+    {
+        return $this->intangiblePriceAndDiscount;
+    }
+
+    public function addEducationCurriculumPack(EducationCurriculumPack $educationCurriculumPack): self
+    {
+        if (!$this->educationCurriculumPacks->contains($educationCurriculumPack)) {
+            $this->educationCurriculumPacks[] = $educationCurriculumPack;
+            $educationCurriculumPack->addIntangible($this);
+        }
+
+        return $this;
+    }
+
+    public function removeEducationCurriculumPack(EducationCurriculumPack $educationCurriculumPack): self
+    {
+        if ($this->educationCurriculumPacks->removeElement($educationCurriculumPack)) {
+            $educationCurriculumPack->removeIntangible($this);
+        }
+
+        return $this;
+    }
 }

+ 34 - 0
src/Entity/Product/IntangibleDiscountDetail.php

@@ -6,6 +6,8 @@ namespace App\Entity\Product;
 
 use ApiPlatform\Metadata\ApiResource;
 use App\Entity\Billing\FamilyQuotient;
+use App\Entity\Billing\FamilyQuotientBand;
+use App\Entity\Billing\FamilyQuotientBandDetail;
 use App\Entity\Billing\ResidenceArea;
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use Doctrine\ORM\Mapping as ORM;
@@ -35,6 +37,14 @@ class IntangibleDiscountDetail
     #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
     private ?FamilyQuotient $familyQuotient = null;
 
+    #[ORM\ManyToOne(targetEntity: FamilyQuotientBandDetail::class, cascade: [], inversedBy: 'intangibleDiscountDetails')]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
+    protected FamilyQuotientBandDetail $familyQuotientBandDetail;
+
+    #[ORM\ManyToOne(targetEntity: FamilyQuotientBand::class, cascade: [], inversedBy: 'intangibleDiscountDetails')]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
+    protected FamilyQuotientBand $familyQuotientBand;
+
     public function getId(): ?int
     {
         return $this->id;
@@ -75,4 +85,28 @@ class IntangibleDiscountDetail
 
         return $this;
     }
+
+    public function getFamilyQuotientBandDetail(): FamilyQuotientBandDetail
+    {
+        return $this->familyQuotientBandDetail;
+    }
+
+    public function setFamilyQuotientBandDetail(FamilyQuotientBandDetail $familyQuotientBandDetail): self
+    {
+        $this->familyQuotientBandDetail = $familyQuotientBandDetail;
+
+        return $this;
+    }
+
+    public function getFamilyQuotientBand(): FamilyQuotientBand
+    {
+        return $this->familyQuotientBand;
+    }
+
+    public function setFamilyQuotientBand(FamilyQuotientBand $familyQuotientBand): self
+    {
+        $this->familyQuotientBand = $familyQuotientBand;
+
+        return $this;
+    }
 }

+ 32 - 0
src/Entity/Product/IntangiblePriceAndDiscount.php

@@ -7,6 +7,7 @@ namespace App\Entity\Product;
 // use DH\Auditor\Provider\Doctrine\Auditing\Annotation\Auditable;
 use ApiPlatform\Metadata\ApiResource;
 use App\Entity\Person\CommissionMember;
+use App\Entity\Billing\FamilyQuotientModel;
 use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\Common\Collections\Collection;
 use Doctrine\ORM\Mapping as ORM;
@@ -35,6 +36,13 @@ class IntangiblePriceAndDiscount
     )]
     private Collection $intangibleDiscountDetails;
 
+    #[ORM\OneToOne(mappedBy: 'intangiblePriceAndDiscount', targetEntity: Intangible::class, cascade: [])]
+    protected Intangible $intangible;
+
+    #[ORM\ManyToOne(targetEntity: FamilyQuotientModel::class, cascade: [], inversedBy: 'intangiblePriceAndDiscounts')]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
+    protected FamilyQuotientModel $familyQuotientModel;
+
     public function __construct()
     {
         $this->intangibleDiscountDetails = new ArrayCollection();
@@ -74,4 +82,28 @@ class IntangiblePriceAndDiscount
 
         return $this;
     }
+
+    public function getIntangible(): Intangible
+    {
+        return $this->intangible;
+    }
+
+    public function setIntangible(Intangible $intangible): self
+    {
+        $this->intangible = $intangible;
+
+        return $this;
+    }
+
+    public function getFamilyQuotientModel(): FamilyQuotientModel
+    {
+        return $this->familyQuotientModel;
+    }
+
+    public function setFamilyQuotientModel(?FamilyQuotientModel $familyQuotientModel): self
+    {
+        $this->familyQuotientModel = $familyQuotientModel;
+
+        return $this;
+    }
 }

+ 47 - 0
src/Entity/Token/Token.php

@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Entity\Token;
+
+use ApiPlatform\Metadata\ApiResource;
+use App\Entity\Access\Access;
+use Doctrine\ORM\Mapping as ORM;
+
+#[ApiResource(operations: [])]
+#[ORM\Entity]
+class Token
+{
+    #[ORM\Id]
+    #[ORM\Column]
+    #[ORM\GeneratedValue]
+    private int $id;
+
+    #[ORM\ManyToOne(targetEntity: Access::class, cascade: [], inversedBy: 'tokens')]
+    #[ORM\JoinColumn(referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
+    private Access $access;
+
+    public function getId(): int
+    {
+        return $this->id;
+    }
+
+    public function setId(int $id): self
+    {
+        $this->id = $id;
+
+        return $this;
+    }
+
+    public function getAccess(): Access
+    {
+        return $this->access;
+    }
+
+    public function setAccess(Access $access): self
+    {
+        $this->access = $access;
+
+        return $this;
+    }
+}

+ 71 - 0
src/Service/Cron/Job/SchemaValidation.php

@@ -0,0 +1,71 @@
+<?php
+
+namespace App\Service\Cron\Job;
+
+use App\Service\Cron\BaseCronJob;
+use App\Service\Doctrine\SchemaValidation\DiffTypeEnum;
+use App\Service\Doctrine\SchemaValidation\SchemaValidationService;
+use JetBrains\PhpStorm\Pure;
+
+/**
+ * Valide le schéma Doctrine en le comparant à la V1.
+ *
+ * >>> ot:cron run schema-validation --preview
+ * >>> ot:cron run schema-validation
+ */
+class SchemaValidation extends BaseCronJob
+{
+    public const VALIDATION_FILTER = DiffTypeEnum::MISSING_RELATION;
+
+    #[Pure]
+    public function __construct(
+        private readonly SchemaValidationService $schemaValidationService,
+    ) {
+        parent::__construct();
+    }
+
+    /**
+     * @return array<string>
+     */
+    protected function getDiffAsCsv(): array
+    {
+        $diff = $this->schemaValidationService->validateSchema(self::VALIDATION_FILTER);
+
+        return $this->schemaValidationService->formatToCsv($diff);
+    }
+
+    /**
+     * Validate the doctrine schema (without reporting).
+     */
+    public function preview(): void
+    {
+        $csv = $this->getDiffAsCsv();
+
+        if (empty($csv)) {
+            $this->ui->print('No difference found');
+        } else {
+            foreach ($csv as $line) {
+                $this->ui->print($line);
+            }
+            $this->ui->print('');
+            $this->ui->print('> '.count($csv).' differences found');
+        }
+    }
+
+    /**
+     * Validate the doctrine schema and report.
+     */
+    public function execute(): void
+    {
+        $csv = $this->getDiffAsCsv();
+
+        if (empty($csv)) {
+            $this->logger->info('No difference found');
+        } else {
+            foreach ($csv as $line) {
+                $this->logger->warning($line);
+            }
+            $this->logger->warning(count($csv).' differences found');
+        }
+    }
+}

+ 15 - 0
src/Service/Doctrine/SchemaValidation/DiffTypeEnum.php

@@ -0,0 +1,15 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Service\Doctrine\SchemaValidation;
+
+enum DiffTypeEnum: string
+{
+    case MISSING_ENTITY = 'MISSING_ENTITY';
+    case MISSING_PROPERTY = 'MISSING_PROPERTY';
+    case MISSING_RELATION = 'MISSING_RELATION';
+    case DIFFERENT_TYPE = 'DIFFERENT_TYPE';
+    case DIFFERENT_RELATION_TYPE = 'DIFFERENT_RELATION_TYPE';
+    case DIFFERENT_RELATION_CONFIGURATION = 'DIFFERENT_RELATION_CONFIGURATION';
+}

+ 72 - 0
src/Service/Doctrine/SchemaValidation/Difference.php

@@ -0,0 +1,72 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Service\Doctrine\SchemaValidation;
+
+class Difference
+{
+    protected DiffTypeEnum $type;
+    protected ?string $message;
+    protected string $entity;
+    protected string $property;
+
+    /**
+     * @var string|array<string, string|array<string>>
+     */
+    protected string|array $expectedType;
+
+    /**
+     * @param string|array<string, string|array<string>> $expectedType
+     */
+    public function __construct(DiffTypeEnum $type, ?string $message, string|array $expectedType = [])
+    {
+        $this->type = $type;
+        $this->message = $message;
+        $this->expectedType = $expectedType;
+    }
+
+    public function getType(): DiffTypeEnum
+    {
+        return $this->type;
+    }
+
+    public function setType(DiffTypeEnum $type): self
+    {
+        $this->type = $type;
+
+        return $this;
+    }
+
+    public function getMessage(): ?string
+    {
+        return $this->message;
+    }
+
+    public function setMessage(?string $message): self
+    {
+        $this->message = $message;
+
+        return $this;
+    }
+
+    /**
+     * @return string|array<string, string|array<string>>
+     */
+    public function getExpectedType(): string|array
+    {
+        return $this->expectedType;
+    }
+
+    /**
+     * @param string|array<string, string|array<string>> $expectedType
+     *
+     * @return $this
+     */
+    public function setExpectedType(string|array $expectedType): self
+    {
+        $this->expectedType = $expectedType;
+
+        return $this;
+    }
+}

+ 751 - 0
src/Service/Doctrine/SchemaValidation/SchemaSnippetsMaker.php

@@ -0,0 +1,751 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Service\Doctrine\SchemaValidation;
+
+use ApiPlatform\Metadata\ApiResource;
+use App\Service\Utils\EntityUtils;
+use App\Service\Utils\FileUtils;
+use App\Service\Utils\Path;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\ORM\Mapping\ClassMetadataInfo;
+use Doctrine\ORM\Mapping\Column;
+use Doctrine\ORM\Mapping\Entity;
+use Doctrine\ORM\Mapping\InverseJoinColumn;
+use Doctrine\ORM\Mapping\JoinColumn;
+use Doctrine\ORM\Mapping\JoinTable;
+use Doctrine\ORM\Mapping\ManyToMany;
+use Doctrine\ORM\Mapping\ManyToOne;
+use Doctrine\ORM\Mapping\OneToMany;
+use Doctrine\ORM\Mapping\OneToOne;
+use Nette\InvalidStateException;
+use Nette\PhpGenerator\Attribute;
+use Nette\PhpGenerator\ClassType;
+use Nette\PhpGenerator\Method;
+use Nette\PhpGenerator\Parameter;
+use Nette\PhpGenerator\PhpFile;
+use Nette\PhpGenerator\PhpNamespace;
+use Nette\PhpGenerator\Property;
+use Nette\PhpGenerator\PsrPrinter;
+
+/**
+ * Service produisant les snippets des entités, propriétés et méthodes
+ * manquantes dans les entités de la V2.
+ *
+ * Les résultats sont enregistrés dans le répertoire `~/schema_validation_snippets`.
+ *
+ * @see https://github.com/nette/php-generator
+ */
+class SchemaSnippetsMaker
+{
+    public function __construct(
+        private readonly EntityUtils $entityUtils,
+    ) {
+    }
+
+    /**
+     * Make entities snippets from a 'diff' array,
+     * as generated by SchemaValidationService::validateSchema().
+     *
+     * @param array<string, Difference | array<Difference>> $diff
+     */
+    public function makeSnippets(array $diff): void
+    {
+        $snippetsDir = $this->getSnippetsDir();
+        $this->prepareSnippetsDir($snippetsDir);
+
+        foreach ($diff as $entity => $differences) {
+            if (empty($differences)) {
+                continue;
+            }
+
+            $class = $this->makeSnippetEntityClass($entity);
+
+            $methods = [];
+            $collections = [];
+
+            $expectedFields = [];
+            if (!is_array($differences)) {
+                // New entity
+                foreach ($differences->getExpectedType() as $field => $type) {
+                    $expectedFields[$field] = $type;
+                }
+            } else {
+                // Existing entity
+                foreach ($differences as $field => $difference) {
+                    $expectedFields[$field] = $difference->getExpectedType();
+                }
+            }
+
+            foreach ($expectedFields as $field => $expectedType) {
+                $prop = is_array($expectedType) ?
+                    $this->makeSnippetEntityCollectionProp($field, $expectedType) :
+                    $this->makeSnippetEntitySimpleProp($field, $expectedType);
+
+                $class->addMember($prop);
+
+                if ($prop->getType() === Collection::class) {
+                    $collections[] = $prop;
+                }
+
+                $methods = [...$methods, ...$this->makeMethodsSnippetForProp($prop)];
+            }
+
+            if ($collections) {
+                $class->addMember(
+                    $this->makeSnippetConstructor($collections)
+                );
+            }
+
+            foreach ($methods as $method) {
+                $class->addMember($method);
+            }
+
+            $file = $this->makeFileSnippet();
+
+            $namespace = $this->makeNamespaceSnippet($entity);
+
+            if (is_array($differences)) {
+                foreach ($this->getEntityToImportFromRelations($differences) as $use) {
+                    $namespace->addUse($use);
+                }
+            }
+
+            try {
+                $namespace->add($class);
+            } catch (InvalidStateException $e) {
+                var_dump($e->getMessage());
+                continue;
+            }
+
+            $file->addNamespace($namespace);
+
+            $fileName = $this->getSnippetPath($snippetsDir, $entity);
+
+            $this->writeSnippet($file, $fileName);
+        }
+    }
+
+    /**
+     * Make the getters / setters for the given property.
+     *
+     * @return array<Method>
+     */
+    protected function makeMethodsSnippetForProp(Property $prop): array
+    {
+        $methods = [];
+
+        if ($prop->getType() !== Collection::class) {
+            $methods[] = $this->makeSnippetGetterForProp($prop);
+            $methods[] = $this->makeSnippetSetterForProp($prop);
+        } else {
+            $methods[] = $this->makeSnippetGetterForProp($prop);
+            $methods[] = $this->makeSnippetAdderForCollection($prop);
+            $methods[] = $this->makeSnippetRemoverForCollection($prop);
+        }
+
+        return $methods;
+    }
+
+    /**
+     * Génère un objet ClassType.
+     *
+     * @see https://api.nette.org/php-generator/master/Nette/PhpGenerator/ClassType.html
+     */
+    protected function makeSnippetEntityClass(string $entity): ClassType
+    {
+        $class = new ClassType($entity);
+
+        $class->setAttributes([
+            new Attribute(ApiResource::class, ['operations' => []]),
+            new Attribute(Entity::class, []),
+        ]);
+
+        return $class;
+    }
+
+    /**
+     * Retourne le chemin absolu vers le répertoire dans lesquels sont créés les snippets.
+     */
+    protected function getSnippetsDir(): string
+    {
+        return Path::join(Path::getProjectDir(), 'schema_validation_snippets');
+    }
+
+    /**
+     * Vide et ajuste les droits du répertoire des snippets.
+     */
+    protected function prepareSnippetsDir(string $snippetsDir): void
+    {
+        if (is_dir($snippetsDir)) {
+            FileUtils::rrmDir($snippetsDir);
+        }
+        mkdir($snippetsDir, 0777, true);
+    }
+
+    /**
+     * Retourne le chemin absolu du snippet donné.
+     */
+    protected function getSnippetPath(string $snippetsDir, string $entity): string
+    {
+        try {
+            $fullName = $this->entityUtils->getFullNameFromEntityName($entity);
+        } catch (\LogicException) {
+            $fullName = '_NameSpaceNotFound/'.$entity;
+        }
+
+        $relativePath = str_replace('\\', '/', $fullName).'.php';
+
+        return Path::join($snippetsDir, $relativePath);
+    }
+
+    /**
+     * Créé le fichier du snippet sur le disque.
+     */
+    protected function writeSnippet(PhpFile $phpFile, string $fileName): void
+    {
+        if (!is_dir(dirname($fileName))) {
+            mkdir(dirname($fileName), 0777, true);
+        }
+
+        $printer = new PsrPrinter();
+
+        $content = $printer->printFile($phpFile);
+        $content = $this->postProcessFileContent($content);
+
+        $f = fopen($fileName, 'w+');
+        try {
+            fwrite($f, $content);
+        } finally {
+            fclose($f);
+        }
+    }
+
+    /**
+     * Parcourt les propriétés de type relations OneToOne ou ManyToOne pour lister
+     * les classes d'entités à importer (pour le typage de ces propriétés).
+     *
+     * @param array<string, Difference> $differences
+     *
+     * @return array<string>
+     */
+    protected function getEntityToImportFromRelations(array $differences): array
+    {
+        $imports = [];
+
+        foreach ($differences as $field => $difference) {
+            if (
+                !is_array($difference->getExpectedType())
+                || !in_array(isset($difference->getExpectedType()['type']), [ClassMetadataInfo::ONE_TO_ONE, ClassMetadataInfo::MANY_TO_ONE])
+                || !isset($difference->getExpectedType()['targetEntity'])
+            ) {
+                continue;
+            }
+
+            $entityName = $this->entityUtils->getEntityNameFromFullName($difference->getExpectedType()['targetEntity']);
+            if (!$entityName) {
+                continue;
+            }
+
+            try {
+                $fullName = $this->entityUtils->getFullNameFromEntityName($entityName);
+            } catch (\LogicException) {
+                continue;
+            }
+
+            $imports[] = $fullName;
+        }
+
+        return $imports;
+    }
+
+    /**
+     * Obtient le namespace pour l'entité donnée.
+     */
+    protected function getNamespaceValue(string $entity): string
+    {
+        try {
+            $fullQualifiedName = str_contains($entity, '\\') ?
+                $entity :
+                $this->entityUtils->getFullNameFromEntityName($entity);
+
+            return $this->entityUtils->getNamespaceFromName($fullQualifiedName);
+        } catch (\LogicException) {
+            return 'App\\Entity';
+        }
+    }
+
+    /**
+     * @param array<string, string|array<string>> $type
+     */
+    protected function getRelationTargetEntityName(array $type): string
+    {
+        $targetEntityName = $this->entityUtils->getEntityNameFromFullName($type['targetEntity']);
+        try {
+            return $this->entityUtils->getFullNameFromEntityName($targetEntityName);
+        } catch (\LogicException) {
+            return 'mixed';
+        }
+    }
+
+    /**
+     * Construit l'objet PhpFile.
+     *
+     * @see https://api.nette.org/php-generator/master/Nette/PhpGenerator/PhpFile.html
+     */
+    protected function makeFileSnippet(): PhpFile
+    {
+        $file = new PhpFile();
+        $file->addComment('This file is auto-generated.');
+        $file->setStrictTypes();
+
+        return $file;
+    }
+
+    /**
+     * Construit l'objet PhpNamespace.
+     *
+     * @see https://api.nette.org/php-generator/master/Nette/PhpGenerator/PhpNamespace.html
+     */
+    protected function makeNamespaceSnippet(string $entity): PhpNamespace
+    {
+        $namespaceValue = $this->getNamespaceValue($entity);
+
+        $namespace = new PhpNamespace($namespaceValue);
+        $namespace->addUse(ApiResource::class);
+        $namespace->addUse('Doctrine\Common\Collections\ArrayCollection');
+        $namespace->addUse('Doctrine\Common\Collections\Collection');
+        $namespace->addUse('Doctrine\ORM\Mapping', 'ORM');
+
+        return $namespace;
+    }
+
+    /**
+     * Construit l'objet Property pour le champs 'id' d'une entité.
+     *
+     * @see https://api.nette.org/php-generator/master/Nette/PhpGenerator/Property.html
+     */
+    protected function makeIdPropertySnippet(): Property
+    {
+        $prop = new Property('id');
+        $prop->setPrivate();
+        $prop->setType('int');
+        $prop->addAttribute('ORM\Id');
+        $prop->addAttribute('ORM\Column');
+        $prop->addAttribute('ORM\GeneratedValue');
+
+        return $prop;
+    }
+
+    protected function getPhpTypeFromDoctrineType(string $doctrineType): string
+    {
+        return [
+            'text' => 'string',
+            'boolean' => 'bool',
+            'integer' => 'int',
+            'datetime' => '?\DateTimeInterface',
+            'json_array' => 'array',
+        ][$doctrineType] ?? 'mixed';
+    }
+
+    /**
+     * Make a Property object for a simple field (not a relation).
+     *
+     * @see https://api.nette.org/php-generator/master/Nette/PhpGenerator/Property.html
+     */
+    protected function makeSnippetEntitySimpleProp(string $name, string $type): Property
+    {
+        if ($name === 'id') {
+            return $this->makeIdPropertySnippet();
+        }
+
+        $php_type = $this->getPhpTypeFromDoctrineType($type);
+
+        $prop = new Property($name);
+        $prop->setProtected();
+        $prop->setType($php_type);
+        $prop->setComment('-- Warning : auto-generated property, checkup the attribute options --');
+
+        if ($type === 'text') {
+            $prop->addAttribute(Column::class, ['length' => 255, 'options' => ['nullable' => true]]);
+        } elseif ($type === 'integer') {
+            $prop->addAttribute(Column::class, ['type' => 'integer', 'options' => ['nullable' => true]]);
+        } elseif ($type === 'boolean') {
+            $prop->addAttribute(Column::class, ['options' => ['default' => false]]);
+        } elseif ($type === 'datetime') {
+            $prop->addAttribute(Column::class, ['type' => 'date', 'options' => ['nullable' => true]]);
+        } elseif ($type === 'json_array') {
+            $prop->addAttribute(Column::class, ['type' => 'json', 'options' => ['nullable' => true]]);
+        } else {
+            $prop->addAttribute(Column::class, []);
+        }
+
+        return $prop;
+    }
+
+    /**
+     * Make a Property object for a relation field.
+     *
+     * @see https://api.nette.org/php-generator/master/Nette/PhpGenerator/Property.html
+     *
+     * @param array<mixed> $type
+     */
+    protected function makeSnippetEntityCollectionProp(string $name, array $type): Property
+    {
+        $prop = new Property($name);
+        $prop->setProtected();
+        $prop->setType('Collection');
+
+        if (
+            isset($type['type'])
+            && $type['type'] === ClassMetadataInfo::ONE_TO_ONE || $type['type'] === ClassMetadataInfo::MANY_TO_ONE
+        ) {
+            $targetEntityName = $this->getRelationTargetEntityName($type);
+            $prop->setType($targetEntityName);
+        } else {
+            $prop->setType(Collection::class);
+        }
+
+        $attributes = [];
+        $attributes[] = $this->makeRelationAttribute($type);
+
+        $joinTable = $this->makeJoinTableAttributes($type);
+        if ($joinTable) {
+            $attributes[] = $joinTable;
+        }
+
+        $attributes = array_merge($attributes, $this->makeJoinColumnAttributes($type));
+
+        $attributes = array_merge($attributes, $this->makeJoinColumnAttributes($type, true));
+
+        $prop->setAttributes($attributes);
+
+        return $prop;
+    }
+
+    /**
+     * Make the attribute defining the relation (ex: #[ORM\ManyToMany(...)]).
+     *
+     * @param array<string | array<string | array<string>>> $type
+     */
+    protected function makeRelationAttribute(array $type): Attribute
+    {
+        $options = [];
+        if (isset($type['mappedBy'])) {
+            $options['mappedBy'] = $type['mappedBy'];
+        }
+
+        if (isset($type['targetEntity'])) {
+            $options['targetEntity'] = $this->entityUtils->getEntityNameFromFullName($type['targetEntity']).'::class';
+        }
+
+        if (isset($type['cascade'])) {
+            $options['cascade'] = $type['cascade'];
+        }
+
+        if (isset($type['inversedBy'])) {
+            $options['inversedBy'] = $type['inversedBy'];
+        }
+
+        if (isset($type['orphanRemoval']) && ($type['type'] === ClassMetadataInfo::ONE_TO_MANY || $type['type'] === ClassMetadataInfo::MANY_TO_MANY)) {
+            $options['orphanRemoval'] = $type['orphanRemoval'];
+        }
+
+        $relationClassNames = [
+            ClassMetadataInfo::ONE_TO_MANY => OneToMany::class,
+            ClassMetadataInfo::MANY_TO_MANY => ManyToMany::class,
+            ClassMetadataInfo::MANY_TO_ONE => ManyToOne::class,
+            ClassMetadataInfo::ONE_TO_ONE => OneToOne::class,
+        ];
+
+        return new Attribute($relationClassNames[$type['type']], $options);
+    }
+
+    /**
+     * Make the #[ORM\JoinTable] attribute (if a definition exists).
+     *
+     * @param array<string | array<string | array<string>>> $type
+     */
+    protected function makeJoinTableAttributes(array $type): ?Attribute
+    {
+        if (!isset($type['joinTable'])) {
+            return null;
+        }
+
+        $options = [];
+        if (isset($type['joinTable']['name']) && $type['joinTable']['name']) {
+            $options['name'] = $type['joinTable']['name'];
+        }
+
+        return new Attribute(JoinTable::class, $options);
+    }
+
+    /**
+     * Make the #[JoinColumn] attributes, if definitions exists.
+     *
+     * Is `$inverse` is true, make the #[InverseJoinColumn] instead.
+     *
+     * @param array<string | array<string | array<string>>> $type
+     *
+     * @return array<Attribute>
+     */
+    protected function makeJoinColumnAttributes(array $type, bool $inverse = false): array
+    {
+        $key = $inverse ? 'inverseJoinColumns' : 'joinColumns';
+
+        $definition = $type[$key] ?? $type['joinTable'][$key] ?? [];
+        if (empty($definition)) {
+            return [];
+        }
+
+        $attributes = [];
+
+        foreach ($definition as $joinColDefinition) {
+            $options = [];
+
+            if (isset($joinColDefinition['name']) && $joinColDefinition['name'] !== $type['fieldName'].'_id') {
+                $options['name'] = $joinColDefinition['name'];
+            }
+            if (($joinColDefinition['unique'] ?? false) === true) {
+                $options['unique'] = true;
+            }
+            if (($joinColDefinition['nullable'] ?? true) === false) {
+                $options['nullable'] = false;
+            }
+            if (($joinColDefinition['onDelete'] ?? null) !== null) {
+                $options['onDelete'] = $joinColDefinition['onDelete'];
+            }
+            if (($joinColDefinition['columnDefinition'] ?? null) !== null) {
+                $options['columnDefinition'] = $joinColDefinition['columnDefinition'];
+            }
+            if (($joinColDefinition['referencedColumnName'] ?? 'id') !== 'id') {
+                $options['referencedColumnName'] = $joinColDefinition['referencedColumnName'];
+            }
+
+            if (empty($options)) {
+                // Useless attribute
+                continue;
+            }
+
+            $attributes[] = new Attribute(
+                $inverse ? InverseJoinColumn::class : JoinColumn::class,
+                $options
+            );
+        }
+
+        return $attributes;
+    }
+
+    /**
+     * Make the '__construct' method with collections initialization.
+     *
+     * @param array<Property> $collections
+     */
+    protected function makeSnippetConstructor(array $collections): Method
+    {
+        $constructor = new Method('__construct');
+        $constructor->setPublic();
+
+        foreach ($collections as $collection) {
+            $constructor->addBody('$this->'.$collection->getName().' = new ArrayCollection();');
+        }
+
+        return $constructor;
+    }
+
+    /**
+     * Make a 'getter' method for the given property.
+     */
+    protected function makeSnippetGetterForProp(Property $prop): Method
+    {
+        $method = new Method('get'.ucfirst($prop->getName()));
+        $method->setReturnType($prop->getType());
+        $method->setBody('return $this->'.$prop->getName().';');
+
+        return $method;
+    }
+
+    /**
+     * Make a 'setter' method for the given property.
+     */
+    protected function makeSnippetSetterForProp(Property $prop): Method
+    {
+        $method = new Method('set'.ucfirst($prop->getName()));
+
+        $parameter = new Parameter($prop->getName());
+        $parameter->setType($prop->getType());
+        $method->setParameters([$parameter]);
+
+        $method->setReturnType('self');
+        $method->setBody(
+            implode(
+                "\n",
+                [
+                    '$this->'.$prop->getName().' = $'.$prop->getName().';',
+                    'return $this;',
+                ]
+            )
+        );
+
+        return $method;
+    }
+
+    protected function getTargetEntityNameFromCollectionProp(Property $prop): ?string
+    {
+        if ($prop->getType() !== Collection::class) {
+            throw new \LogicException('The property must be a collection');
+        }
+
+        foreach ($prop->getAttributes() as $attribute) {
+            if (
+                $attribute instanceof Attribute
+                && ($attribute->getName() === OneToMany::class || $attribute->getName() === ManyToMany::class)
+            ) {
+                $targetEntityName = $attribute->getArguments()['targetEntity'];
+                if (!$targetEntityName) {
+                    return null;
+                }
+
+                // Normalize result (it could be a FQN, or a '::class' notation)
+                $targetEntityName = str_replace('::class', '', $targetEntityName);
+
+                if (!str_contains($targetEntityName, '\\')) {
+                    try {
+                        $targetEntityName = $this->entityUtils->getFullNameFromEntityName($targetEntityName);
+                    } catch (\LogicException) {
+                        return null;
+                    }
+                }
+
+                return $targetEntityName;
+            }
+        }
+
+        return null;
+    }
+
+    protected function getInverseSetterCallFromCollectionProp(Property $prop, bool $isRemoving = false): ?string
+    {
+        if (
+            $prop->getType() !== Collection::class
+        ) {
+            throw new \LogicException('The property must be a collection');
+        }
+
+        $relationAttr = null;
+        foreach ($prop->getAttributes() as $attribute) {
+            if ($attribute->getName() === OneToMany::class || $attribute->getName() === ManyToMany::class) {
+                $relationAttr = $attribute;
+            }
+        }
+
+        if (!$relationAttr) {
+            throw new \LogicException('Missing relation attribute for collection property '.$prop->getName());
+        }
+
+        $inversedBy = $relationAttr->getArguments()['inversedBy'] ?? $relationAttr->getArguments()['mappedBy'] ?? null;
+        if (!$inversedBy) {
+            var_dump('Could not determine the inverse prop for collection property '.$prop->getName());
+            $inversedBy = 'XXXX';
+        }
+
+        $attr = $prop->getAttributes()[0];
+
+        if ($attr->getName() === OneToMany::class) {
+            $prefix = 'set';
+        } else {
+            $prefix = $isRemoving ? 'remove' : 'add';
+            $inversedBy = $this->singularize($inversedBy);
+        }
+
+        return
+            $prefix.
+            ucfirst($inversedBy).
+            (($prefix === 'set' && $isRemoving) ? '(null)' : '($this)');
+    }
+
+    /**
+     * Make an 'adder' method for the given property.
+     */
+    protected function makeSnippetAdderForCollection(Property $prop): Method
+    {
+        $singularPropName = $this->singularize($prop->getName());
+
+        $method = new Method('add'.ucfirst($singularPropName));
+
+        $targetEntityName = $this->getTargetEntityNameFromCollectionProp($prop);
+
+        $parameter = new Parameter($singularPropName);
+        $parameter->setType($targetEntityName ?? 'mixed');
+        $method->setParameters([$parameter]);
+
+        $inverseSetterCall = $this->getInverseSetterCallFromCollectionProp($prop);
+
+        $method->setReturnType('self');
+        $method->setBody(implode(
+            "\n",
+            [
+                'if (!$this->'.$prop->getName().'->contains($'.$singularPropName.')) {',
+                '    $this->'.$prop->getName().'[] = $'.$singularPropName.';',
+                '    $'.$singularPropName.'->'.$inverseSetterCall.';',
+                '}',
+                '',
+                'return $this;',
+            ]));
+
+        return $method;
+    }
+
+    /**
+     * Make a 'remover' method for the given property.
+     */
+    protected function makeSnippetRemoverForCollection(Property $prop): Method
+    {
+        $singularPropName = $this->singularize($prop->getName());
+
+        $method = new Method('remove'.ucfirst($singularPropName));
+
+        $targetEntityName = $this->getTargetEntityNameFromCollectionProp($prop);
+
+        $parameter = new Parameter($singularPropName);
+        $parameter->setType($targetEntityName ?? 'mixed');
+        $method->setParameters([$parameter]);
+
+        $inverseSetterCall = $this->getInverseSetterCallFromCollectionProp($prop, true);
+
+        $method->setReturnType('self');
+        $method->setBody(
+            implode(
+                "\n",
+                [
+                    'if ($this->'.$prop->getName().'->removeElement($'.$singularPropName.')) {',
+                    '    $'.$singularPropName.'->'.$inverseSetterCall.';',
+                    '}',
+                    '',
+                    'return $this;',
+                ]
+            )
+        );
+
+        return $method;
+    }
+
+    /**
+     * Perform some post-fixes on the file content.
+     */
+    protected function postProcessFileContent(string $content): string
+    {
+        return preg_replace("/targetEntity: '(\w+)::class'/", 'targetEntity: $1::class', $content);
+    }
+
+    protected function singularize(string $name): string
+    {
+        $exceptions = ['access'];
+        if (in_array(strtolower($name), $exceptions)) {
+            return $name;
+        }
+
+        return preg_replace('/s$/', '', $name);
+    }
+}

+ 308 - 0
src/Service/Doctrine/SchemaValidation/SchemaValidationService.php

@@ -0,0 +1,308 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Service\Doctrine\SchemaValidation;
+
+use App\Service\ApiLegacy\ApiLegacyRequestService;
+use App\Service\Utils\EntityUtils;
+use Doctrine\ORM\EntityManagerInterface;
+use Doctrine\ORM\Mapping\ClassMetadataInfo;
+use Doctrine\ORM\Mapping\MappingException;
+use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
+use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
+use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
+use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
+
+/**
+ * Validation du schéma Doctrine par comparaison aux entités en production sur la V1.
+ *
+ * — À supprimer lorsque la migration sera achevée —
+ */
+class SchemaValidationService
+{
+    public function __construct(
+        private readonly EntityManagerInterface $entityManager,
+        private readonly ApiLegacyRequestService $apiLegacyRequestService,
+        private readonly EntityUtils $entityUtils,
+    ) {
+    }
+
+    /**
+     * Compare the V2 doctrine schema to the one in V1, and return a list of differences,
+     * of the form:
+     *
+     *     [<entity> → Difference | [<field> → Difference]]
+     *
+     * @return array<string, Difference|array<Difference>>
+     *
+     * @throws ClientExceptionInterface
+     * @throws MappingException
+     * @throws RedirectionExceptionInterface
+     * @throws ServerExceptionInterface
+     * @throws TransportExceptionInterface
+     */
+    public function validateSchema(?DiffTypeEnum $filter = null): array
+    {
+        $schemaV1 = $this->getV1Schema();
+        $schemaV2 = $this->getV2Schema();
+
+        return $this->getDiff($schemaV1, $schemaV2, $filter);
+    }
+
+    /**
+     * Retrieve the V2 schema.
+     *
+     * @return array<string, array<string|array<string|int>>>
+     *
+     * @throws MappingException
+     */
+    protected function getV2Schema(): array
+    {
+        $metadata = $this->entityManager->getMetadataFactory()->getAllMetadata();
+        $schema = [];
+
+        foreach ($metadata as $entityMetadata) {
+            $entityClassName = $this->entityUtils->getEntityNameFromFullName($entityMetadata->getName());
+
+            $schema[$entityClassName] = [];
+
+            foreach ($entityMetadata->getFieldNames() as $field) {
+                $schema[$entityClassName][$field] = $entityMetadata->getTypeOfField($field);
+            }
+
+            foreach ($entityMetadata->getAssociationNames() as $association) {
+                $schema[$entityClassName][$association] = $entityMetadata->getAssociationMapping($association);
+            }
+        }
+
+        return $schema;
+    }
+
+    /**
+     * Retrieve the V1 schema.
+     *
+     * @return array<string, array<string|array<string|int>>>
+     *
+     * @throws ClientExceptionInterface
+     * @throws RedirectionExceptionInterface
+     * @throws ServerExceptionInterface
+     * @throws TransportExceptionInterface
+     */
+    protected function getV1Schema(): array
+    {
+        $response = $this->apiLegacyRequestService->get('/_internal/doctrine/schema');
+
+        return json_decode($response->getContent(), true);
+    }
+
+    /**
+     * Get a list of differences between V1 and V2 doctrine schemas.
+     *
+     * @param array<string, array<string|array<string|int>>> $schemaV1
+     * @param array<string, array<string|array<string|int>>> $schemaV2
+     *
+     * @return array<string, Difference|array<Difference>>
+     */
+    protected function getDiff(array $schemaV1, array $schemaV2, ?DiffTypeEnum $filter = null): array
+    {
+        $diff = [
+        ];
+
+        foreach ($schemaV1 as $entity => $fields) {
+            if (!$this->isEntityInSchema($schemaV2, $entity)) {
+                // L'entité n'existe pas en V2
+                if (!$filter || $filter === DiffTypeEnum::MISSING_ENTITY) {
+                    $diff[$entity] = new Difference(
+                        DiffTypeEnum::MISSING_ENTITY,
+                        "Entity `$entity` is missing in V2",
+                        $fields
+                    );
+                }
+                continue;
+            }
+
+            foreach ($fields as $field => $fieldTypeV1) {
+                if (
+                    !$this->isPropertyInSchema($schemaV2, $entity, $field)
+                    && $this->isRelationField($schemaV1, $entity, $field)
+                    && $this->isPropertyInSchema($schemaV2, $entity, $field.'s')
+                ) {
+                    // Le champ existe en V2, mais il a été mis au pluriel, par exemple : $contactPoint devenu $contactPoints
+                    // Pour éviter les faux positifs, on renomme le champ dans le schéma V1
+                    $schemaV1[$entity][$field.'s'] = $fieldTypeV1;
+                    unset($schemaV1[$entity][$field]);
+                    $fields = $schemaV1[$entity];
+                }
+            }
+
+            $diff[$entity] = [];
+
+            foreach ($fields as $field => $fieldTypeV1) {
+                if (!$this->isPropertyInSchema($schemaV2, $entity, $field)) {
+                    // Le champ n'existe pas en V2
+                    if ($this->isRelationField($schemaV1, $entity, $field)) {
+                        $diff[$entity][$field] = new Difference(
+                            DiffTypeEnum::MISSING_RELATION,
+                            'Relation '.$this->getRelationTypeLabel($fieldTypeV1)." `$field` is missing in V2",
+                            $fieldTypeV1,
+                        );
+                    } else {
+                        $diff[$entity][$field] = new Difference(
+                            DiffTypeEnum::MISSING_PROPERTY,
+                            "Property `$field` is missing in V2",
+                            $fieldTypeV1
+                        );
+                    }
+                    continue;
+                }
+
+                $fieldTypeV2 = $schemaV2[$entity][$field];
+
+                // Si ce champ n'est pas une relation
+                if (!$this->isRelationField($schemaV1, $entity, $field)) {
+                    // Le champ n'est pas une relation en V1
+                    if ($fieldTypeV2 !== $fieldTypeV1) {
+                        // Le champ a un type différent en V2
+                        $diff[$entity][$field] = new Difference(
+                            DiffTypeEnum::DIFFERENT_TYPE,
+                            "Property `$field` has a different type (V1: `$fieldTypeV1`, V2: `$fieldTypeV2`)",
+                            $fieldTypeV1
+                        );
+                    }
+                } elseif (!$this->isRelationField($schemaV2, $entity, $field)) {
+                    // Le champ est une relation en V1 mais pas en V2
+                    $diff[$entity][$field] = new Difference(
+                        DiffTypeEnum::DIFFERENT_TYPE,
+                        "Property $field is a relation in V1 but not in V2",
+                        $fieldTypeV1
+                    );
+                } else {
+                    // Le champ est une relation dans les deux schémas, on compare leurs configurations
+                    $difference = $this->getRelationDiff($fieldTypeV1, $fieldTypeV2);
+                    if ($difference) {
+                        $diff[$entity][$field] = $difference;
+                    }
+                }
+            }
+
+            // Si filter est non null, on ne conserve que les différences du type donné
+            if ($filter !== null) {
+                $diff[$entity] = array_filter(
+                    $diff[$entity],
+                    function (Difference $difference) use ($filter) {
+                        return $difference->getType() === $filter;
+                    }
+                );
+            }
+        }
+
+        return $diff;
+    }
+
+    /**
+     * Returns true if the given entity name exists in the doctrine schema.
+     *
+     * @param array<string, array<string|array<string|int>>> $schema
+     */
+    protected function isEntityInSchema(array $schema, string $entity): bool
+    {
+        return isset($schema[$entity]);
+    }
+
+    /**
+     * Returns true if the given property name exists in the doctrine schema under this entity.
+     *
+     * @param array<string, array<string|array<string|int>>> $schema
+     */
+    protected function isPropertyInSchema(array $schema, string $entity, string $property): bool
+    {
+        return $this->isEntityInSchema($schema, $entity) && isset($schema[$entity][$property]);
+    }
+
+    /**
+     * Is the given field a relation field.
+     *
+     * @param array<string, array<string|array<string|int>>> $schema
+     */
+    protected function isRelationField(array $schema, string $entity, string $relation): bool
+    {
+        return $this->isPropertyInSchema($schema, $entity, $relation) && is_array($schema[$entity][$relation]);
+    }
+
+    /**
+     * Look up for differences in $relationCompared compared to $relationReference, and return
+     * a Difference if any, or null else.
+     *
+     * @param array<string, string> $relationReference
+     * @param array<string, string> $relationCompared
+     */
+    protected function getRelationDiff(array $relationReference, array $relationCompared): ?Difference
+    {
+        if ($relationReference['type'] !== $relationCompared['type']) {
+            return new Difference(
+                DiffTypeEnum::DIFFERENT_RELATION_TYPE,
+                "Relation type is different : {$this->getRelationTypeLabel($relationReference)} !== {$this->getRelationTypeLabel($relationCompared)}",
+                $relationReference
+            );
+        }
+
+        if (
+            $this->entityUtils->getEntityNameFromFullName($relationReference['targetEntity']) !== $this->entityUtils->getEntityNameFromFullName($relationCompared['targetEntity'])
+        ) {
+            return new Difference(
+                DiffTypeEnum::DIFFERENT_RELATION_CONFIGURATION,
+                'Relation configuration is different (targetEntity)',
+                $relationReference
+            );
+        }
+
+        if (
+            $relationReference['mappedBy'] !== $relationCompared['mappedBy']
+        ) {
+            return new Difference(
+                DiffTypeEnum::DIFFERENT_RELATION_CONFIGURATION,
+                'Relation configuration is different (mappedBy)',
+                $relationReference
+            );
+        }
+
+        return null;
+    }
+
+    /**
+     * Get the name of a relation from a ClassMetadataInfo integer constant.
+     *
+     * @param array<string, string|int> $relation
+     */
+    protected function getRelationTypeLabel(array $relation): string
+    {
+        return [
+            ClassMetadataInfo::ONE_TO_ONE => 'OneToOne',
+            ClassMetadataInfo::MANY_TO_ONE => 'ManyToOne',
+            ClassMetadataInfo::ONE_TO_MANY => 'OneToMany',
+            ClassMetadataInfo::MANY_TO_MANY => 'ManyToMany',
+        ][$relation['type']] ?? 'Unknown';
+    }
+
+    /**
+     * @param array<Difference|array<Difference>> $diff
+     *
+     * @return array<string>
+     */
+    public function formatToCsv(array $diff): array
+    {
+        $csv = [];
+        foreach ($diff as $entity => $differences) {
+            if (!is_array($differences)) {
+                $csv[] = implode(';', [$entity, '', $differences->getType()->value]);
+            } else {
+                foreach ($differences as $field => $difference) {
+                    $csv[] = implode(';', [$entity, $field, $difference->getType()->value]);
+                }
+            }
+        }
+
+        return $csv;
+    }
+}

+ 68 - 2
src/Service/Utils/EntityUtils.php

@@ -7,12 +7,25 @@ namespace App\Service\Utils;
 use App\Attribute\BillingSettingDefaultValue;
 use App\Attribute\OrganizationDefaultValue;
 use App\Entity\Access\Access;
+use Doctrine\ORM\EntityManagerInterface;
 
 /**
  * Class EntityUtils : Gestion des valeurs par défauts devant être présentes dans les entités.
  */
 class EntityUtils
 {
+    /**
+     * Cache of the mapping between entity names and fully qualified names.
+     *
+     * @var array<string, string>
+     */
+    protected array $entityNamesMappingCache = [];
+
+    public function __construct(
+        private readonly EntityManagerInterface $entityManager,
+    ) {
+    }
+
     /**
      * @throws \ReflectionException
      */
@@ -32,8 +45,8 @@ class EntityUtils
     protected function organizationDefaultValue(mixed $entity, Access $access): void
     {
         $reflection = new \ReflectionClass($entity::class);
-        $organizationFaultValue = $reflection->getAttributes(OrganizationDefaultValue::class)[0] ?? null;
-        $fieldName = $organizationFaultValue?->getArguments()['fieldName'] ?? null;
+        $organizationDefaultValue = $reflection->getAttributes(OrganizationDefaultValue::class)[0] ?? null;
+        $fieldName = $organizationDefaultValue?->getArguments()['fieldName'] ?? null;
         if ($fieldName) {
             $entity->{sprintf('set%s', ucfirst($fieldName))}(...[$access->getOrganization()]);
         }
@@ -51,4 +64,57 @@ class EntityUtils
             $entity->{sprintf('set%s', ucfirst($fieldName))}(...[$access->getOrganization()?->getBillingSetting()]);
         }
     }
+
+    /**
+     * Extract an entity base name from a fully qualified name
+     * Ex: '\App\Entity\Core\File' => 'File'.
+     */
+    public function getEntityNameFromFullName(string $fullName): string
+    {
+        $parts = explode('\\', $fullName);
+
+        return array_pop($parts);
+    }
+
+    /**
+     * Find the fully qualified name matching the given entity base name.
+     *
+     * Ex: 'File' => '\App\Entity\Core\File'
+     */
+    public function getFullNameFromEntityName(string $entityName): string
+    {
+        if (empty($this->entityNamesMappingCache)) {
+            $this->populateEntityNamesMappingCache();
+        }
+        if (!isset($this->entityNamesMappingCache[$entityName])) {
+            throw new \LogicException('No entity found for name `'.$entityName.'`');
+        }
+
+        return $this->entityNamesMappingCache[$entityName];
+    }
+
+    /**
+     * Populates a cache mapping entity base names to their fully qualified class names.
+     */
+    protected function populateEntityNamesMappingCache(): void
+    {
+        $this->entityNamesMappingCache = [];
+        $metadata = $this->entityManager->getMetadataFactory()->getAllMetadata();
+        foreach ($metadata as $entityMetadata) {
+            $baseName = $this->getEntityNameFromFullName($entityMetadata->getName());
+            $this->entityNamesMappingCache[$baseName] = $entityMetadata->getName();
+        }
+    }
+
+    /**
+     * Get the namespace from a fully qualified name
+     * Ex: '\App\Entity\Core\File' => 'App\Entity\Core'.
+     */
+    public function getNamespaceFromName(string $entityName): ?string
+    {
+        $parts = explode('\\', $entityName);
+        array_pop($parts);
+
+        return ltrim(implode('\\', $parts), '\\');
+    }
 }

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff