Julien Fastré 5be85a4fc6
Refactored code to use PHP8 attributes instead of annotations
In this change, Doctrine and validation annotations have been replaced with PHP8 Attributes. The Rector tool has been configured with a list of annotations to convert to attributes. As a consequence, the PHPStan's rules have been updated to reflect these changes. The PHP8's nullable operator (?) has been added where required, and comments in field declaration have been replaced with #[Attribute] syntax.
2024-04-08 12:11:29 +02:00

785 lines
21 KiB
PHP

<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ThirdPartyBundle\Entity;
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Civility;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Validation\Constraint\PhonenumberConstraint;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ReadableCollection;
use Doctrine\ORM\Mapping as ORM;
use libphonenumber\PhoneNumber;
use Symfony\Component\Serializer\Annotation\Context;
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
/**
* ThirdParty is a party recorded in the database.
*
* A party may be attached to multiple centers. Being attach to a center allow
* all users with the right 'CHILL_3PARTY_3PARTY_SEE', 'CHILL_3PARTY_3 to see, select and edit parties for this
* center.
*
* A ThirdParty may have:
*
* * 0, one or more categories;
* * 0, one or more type
* * 1 kind.
*
* ## Kind
*
* The kind indicate if a thirdparty is a:
*
* * company ("personne morale");
* * a contact ("personne morale")
* * a child inside a company ("contact" in French). Only companies may have childs
*
* **take care** the french translation may lead to misinterpretation, as the string "contact" is the translation of
* kind "child".
*
* ## Categories and types
*
* ThirdParty may have zero, one or more categories and/or type.
*
* Categories are managed in the database. The Chill administrator may create or desactivate categories.
*
* Types are also a way to categorize the thirdparties, but this is done **into the code**, through
*
* @see{Chill\ThirdPartyBundle\ThirdPartyType\ThirdPartyTypeProviderInterface}. The type is stored into the
* database by a Php array, mapped by a jsonb into the database. This has one advantage: it is easily searchable.
*
* As the list of type is hardcoded into database, it is more easily searchable. (for chill 2.0, the
*
* @see{Chill\ThirdPartyBundle\Form\Type\PickThirdPartyDynamicType} does not support it yet, but
* the legacy @see{Chill\ThirdPartyBundle\Form\Type\PickThirdPartyType} does.
*
* The difference between categories and types is transparent for user: they choose the same fields into the UI, without
* noticing a difference.
*/
#[ORM\Entity]
#[ORM\Table(name: 'chill_3party.third_party')]
#[DiscriminatorMap(typeProperty: 'type', mapping: ['thirdparty' => ThirdParty::class])]
class ThirdParty implements TrackCreationInterface, TrackUpdateInterface, \Stringable
{
final public const KIND_CHILD = 'child';
final public const KIND_COMPANY = 'company';
final public const KIND_CONTACT = 'contact';
/**
* [fr] Sigle.
*/
#[Assert\Length(min: 2)]
#[Groups(['read', 'write', 'docgen:read', 'docgen:read:3party:parent'])]
#[ORM\Column(name: 'acronym', type: \Doctrine\DBAL\Types\Types::STRING, length: 64, nullable: true)]
private ?string $acronym = '';
/**
* Soft-delete flag.
*/
#[ORM\Column(name: 'active', type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['defaut' => true])]
private bool $active = true;
#[Groups(['read', 'write', 'docgen:read', 'docgen:read:3party:parent'])]
#[ORM\ManyToOne(targetEntity: Address::class, cascade: ['persist', 'remove'])]
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
#[Context(normalizationContext: ['groups' => 'docgen:read'], groups: ['docgen:read:3party:parent'])]
private ?Address $address = null;
/**
* Canonicalized form composed of name, company name and acronym.
*
* This field is read-only, and is generated on database side.
*/
#[ORM\Column(name: 'canonicalized', type: \Doctrine\DBAL\Types\Types::TEXT, options: ['default' => ''])]
private ?string $canonicalized = '';
/**
* @var Collection<ThirdPartyCategory>
*/
#[Groups(['docgen:read', 'docgen:read:3party:parent'])]
#[ORM\ManyToMany(targetEntity: ThirdPartyCategory::class)]
#[ORM\JoinTable(name: 'chill_3party.thirdparty_category', joinColumns: [new ORM\JoinColumn(name: 'thirdparty_id', referencedColumnName: 'id')], inverseJoinColumns: [new ORM\JoinColumn(name: 'category_id', referencedColumnName: 'id')])]
#[Context(normalizationContext: ['groups' => 'docgen:read'], groups: ['docgen:read:3party:parent'])]
private Collection $categories;
/**
* @var Collection<Center>
*/
#[ORM\ManyToMany(targetEntity: Center::class)]
#[ORM\JoinTable(name: 'chill_3party.party_center')]
private Collection $centers;
/**
* Contact Persons: One Institutional ThirdParty has Many Contact Persons.
*
* @var Collection<ThirdParty>
*/
#[Assert\Valid(traverse: true)]
#[ORM\OneToMany(targetEntity: ThirdParty::class, mappedBy: 'parent', cascade: ['persist'], orphanRemoval: true)]
private Collection $children;
#[Context(normalizationContext: ['groups' => 'docgen:read'], groups: ['docgen:read:3party:parent'])]
#[Groups(['read', 'write', 'docgen:read', 'docgen:read:3party:parent'])]
#[ORM\ManyToOne(targetEntity: Civility::class)] // ORM\JoinColumn(name="civility", referencedColumnName="id", nullable=true)
private ?Civility $civility = null;
#[Groups(['read', 'write'])]
#[ORM\Column(name: 'comment', type: \Doctrine\DBAL\Types\Types::TEXT, nullable: true)]
private ?string $comment = null;
#[Groups(['read', 'docgen:read', 'docgen:read:3party:parent'])]
#[ORM\Column(name: 'contact_data_anonymous', type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['default' => false])]
private bool $contactDataAnonymous = false;
#[ORM\Column(name: 'created_at', type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE, nullable: false)]
private \DateTimeImmutable $createdAt;
#[ORM\ManyToOne(targetEntity: User::class)]
#[ORM\JoinColumn(name: 'created_by', referencedColumnName: 'id')]
private ?User $createdBy = null;
#[Assert\Email]
#[Groups(['read', 'write', 'docgen:read', 'docgen:read:3party:parent'])]
#[ORM\Column(name: 'email', type: \Doctrine\DBAL\Types\Types::STRING, length: 255, nullable: true)]
private ?string $email = null;
#[Groups(['read', 'write', 'docgen:read', 'docgen:read:3party:parent'])]
#[ORM\Column(name: 'firstname', type: \Doctrine\DBAL\Types\Types::TEXT, options: ['default' => ''])]
private ?string $firstname = '';
#[Groups(['read', 'docgen:read', 'docgen:read:3party:parent'])]
#[ORM\Column(name: 'id', type: \Doctrine\DBAL\Types\Types::INTEGER)]
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'AUTO')]
private ?int $id = null;
#[Groups(['write', 'docgen:read', 'docgen:read:3party:parent'])]
#[ORM\Column(name: 'kind', type: \Doctrine\DBAL\Types\Types::STRING, length: 20, options: ['default' => ''])]
private string $kind = '';
#[Assert\Length(min: 2)]
#[Assert\NotNull]
#[Assert\NotBlank]
#[Groups(['read', 'write', 'docgen:read', 'docgen:read:3party:parent'])]
#[ORM\Column(name: 'name', type: \Doctrine\DBAL\Types\Types::STRING, length: 255)]
private string $name = '';
/**
* [fr] Raison sociale.
*/
#[Assert\Length(min: 3)]
#[Groups(['read', 'write', 'docgen:read', 'docgen:read:3party:parent'])]
#[ORM\Column(name: 'name_company', type: \Doctrine\DBAL\Types\Types::STRING, length: 255, nullable: true)]
private ?string $nameCompany = '';
/**
* Institutional ThirdParty: Many Contact Persons have One Institutional ThirdParty.
*/
#[Groups(['read', 'write', 'docgen:read'])]
#[ORM\ManyToOne(targetEntity: ThirdParty::class, inversedBy: 'children')]
#[ORM\JoinColumn(name: 'parent_id', referencedColumnName: 'id')]
#[Context(normalizationContext: ['groups' => 'docgen:read:3party:parent'], groups: ['docgen:read'])]
private ?ThirdParty $parent = null;
/**
* [fr] Qualité.
*/
#[Groups(['read', 'write', 'docgen:read', 'docgen:read:3party:parent'])]
#[ORM\Column(name: 'profession', type: \Doctrine\DBAL\Types\Types::TEXT, nullable: false)]
#[Context(normalizationContext: ['groups' => 'docgen:read'], groups: ['docgen:read:3party:parent'])]
private string $profession = '';
#[Groups(['read', 'write', 'docgen:read', 'docgen:read:3party:parent'])]
#[ORM\Column(name: 'telephone', type: 'phone_number', nullable: true)]
#[PhonenumberConstraint(type: 'any')]
private ?PhoneNumber $telephone = null;
#[ORM\Column(name: 'types', type: \Doctrine\DBAL\Types\Types::JSON, nullable: true)]
private ?array $thirdPartyTypes = [];
#[ORM\Column(name: 'updated_at', type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE, nullable: true)]
private ?\DateTimeImmutable $updatedAt = null;
#[ORM\ManyToOne(targetEntity: User::class)]
#[ORM\JoinColumn(name: 'updated_by', referencedColumnName: 'id')]
private ?User $updatedBy = null;
/**
* ThirdParty constructor.
*/
public function __construct()
{
$this->centers = new ArrayCollection();
$this->categories = new ArrayCollection();
$this->children = new ArrayCollection();
}
public function __toString(): string
{
return $this->getName();
}
/**
* Add a child and set the child as active.
*
* Method used in conjonction with getActiveChildren in form.
*
* @internal use the method addChild
*
* @return $this
*/
public function addActiveChild(ThirdParty $child): self
{
$child->setActive(true);
return $this->addChild($child);
}
/**
* @return $this
*/
public function addCategory(ThirdPartyCategory $category): self
{
if (!$this->categories->contains($category)) {
$this->categories[] = $category;
}
foreach ($this->children as $child) {
$child->addCategory($category);
}
return $this;
}
public function addCenter(Center $center)
{
if (false === $this->centers->contains($center)) {
$this->centers->add($center);
}
}
/**
* @return $this
*/
public function addChild(ThirdParty $child): self
{
$this->children[] = $child;
$child->setParent($this)->setKind(ThirdParty::KIND_CHILD);
return $this;
}
public function addThirdPartyTypes(?string $type): self
{
if (null === $type) {
return $this;
}
if (!\in_array($type, $this->thirdPartyTypes ?? [], true)) {
$this->thirdPartyTypes[] = $type;
}
foreach ($this->children as $child) {
$child->addThirdPartyTypes($type);
}
return $this;
}
public function addTypesAndCategories($typeAndCategory): self
{
if ($typeAndCategory instanceof ThirdPartyCategory) {
$this->addCategory($typeAndCategory);
return $this;
}
if (\is_string($typeAndCategory)) {
$this->addThirdPartyTypes($typeAndCategory);
return $this;
}
throw new \UnexpectedValueException(sprintf('typeAndCategory should be a string or a %s', ThirdPartyCategory::class));
}
public function getAcronym(): ?string
{
return $this->acronym;
}
public function getActive(): bool
{
return $this->active;
}
/**
* Get the children where active = true.
*/
public function getActiveChildren(): ReadableCollection
{
return $this->children->filter(static fn (ThirdParty $tp) => $tp->getActive());
}
public function getAddress(): ?Address
{
if ($this->isChild()) {
return $this->getParent()->getAddress();
}
return $this->address;
}
public function getCategories(): Collection
{
return $this->categories;
}
public function getCenters(): Collection
{
return $this->centers;
}
public function getChildren(): Collection
{
return $this->children;
}
public function getCivility(): ?Civility
{
return $this->civility;
}
/**
* Get comment.
*/
public function getComment(): ?string
{
return $this->comment;
}
public function getCreatedAt(): \DateTimeImmutable
{
return $this->createdAt;
}
/**
* Get email.
*/
public function getEmail(): ?string
{
return $this->email;
}
public function getFirstname(): string
{
return $this->firstname;
}
public function getId(): ?int
{
return $this->id;
}
public function getKind(): ?string
{
return $this->kind;
}
public function getName(): string
{
return $this->name;
}
public function getNameCompany(): ?string
{
return $this->nameCompany;
}
public function getParent(): ?ThirdParty
{
return $this->parent;
}
public function getProfession(): string
{
return $this->profession;
}
/**
* Get telephone.
*/
public function getTelephone(): ?PhoneNumber
{
return $this->telephone;
}
/**
* Get type.
*/
public function getThirdPartyTypes(): ?array
{
return $this->thirdPartyTypes ?? [];
}
public function getTypesAndCategories(): array
{
return \array_merge(
$this->getCategories()->toArray(),
$this->getThirdPartyTypes() ?? []
);
}
/**
* @return \DateTime|null
*/
public function getUpdatedAt(): ?\DateTimeImmutable
{
return $this->updatedAt;
}
public function getUpdatedBy(): ?User
{
return $this->updatedBy;
}
#[Groups(['read', 'docgen:read', 'docgen:read:3party:parent'])]
public function isChild(): bool
{
return null !== $this->parent;
}
public function isContactDataAnonymous(): bool
{
return $this->contactDataAnonymous;
}
/**
* Mechanism to differentiate children and parents.
*/
public function isLeaf(): bool
{
return 0 !== $this->children->count();
}
public function isParent(): bool
{
return !$this->isChild();
}
/**
* mark the child as unactive, but keep the child existing in the
* database. To effectively remove the child, use removeChild instead.
*
* @return $this
*/
public function removeActiveChild(ThirdParty $child): self
{
$child->setActive(false);
return $this;
}
/**
* @return $this
*/
public function removeCategory(ThirdPartyCategory $category): self
{
$this->categories->removeElement($category);
foreach ($this->children as $child) {
$child->removeCategory($category);
}
return $this;
}
public function removeCenter(Center $center)
{
if ($this->centers->contains($center)) {
$this->centers->removeElement($center);
}
}
/**
* Remove the child from the database.
*
* If you want to keep the child into the database
* but desactivate it, use removeActiveChildren instead.
*
* @return $this
*/
public function removeChild(ThirdParty $child): self
{
$this->children->removeElement($child);
return $this;
}
public function removeThirdPartyTypes(?string $type): self
{
if (null === $type) {
return $this;
}
if (\in_array($type, $this->thirdPartyTypes ?? [], true)) {
$this->thirdPartyTypes = \array_filter($this->thirdPartyTypes, fn ($e) => !\in_array($e, $this->thirdPartyTypes, true));
}
foreach ($this->children as $child) {
$child->removeThirdPartyTypes($type);
}
return $this;
}
public function removeTypesAndCategories($typeAndCategory): self
{
if ($typeAndCategory instanceof ThirdPartyCategory) {
$this->removeCategory($typeAndCategory);
return $this;
}
if (\is_string($typeAndCategory)) {
$this->removeThirdPartyTypes($typeAndCategory);
return $this;
}
throw new \UnexpectedValueException(sprintf('typeAndCategory should be a string or a %s', ThirdPartyCategory::class));
}
public function setAcronym(?string $acronym = null): ThirdParty
{
$this->acronym = (string) $acronym;
return $this;
}
/**
* @return $this
*/
public function setActive(bool $active)
{
$this->active = $active;
foreach ($this->children as $child) {
$child->setActive($active);
}
return $this;
}
/**
* @return $this
*/
public function setAddress(?Address $address = null)
{
$this->address = $address;
return $this;
}
/**
* @return $this
*/
public function setCenters(Collection $centers)
{
$this->centers = $centers;
return $this;
}
public function setCivility(?Civility $civility): ThirdParty
{
$this->civility = $civility;
return $this;
}
/**
* Set comment.
*
* @return ThirdParty
*/
public function setComment(?string $comment = null)
{
$this->comment = $comment;
return $this;
}
public function setContactDataAnonymous(bool $contactDataAnonymous): ThirdParty
{
$this->contactDataAnonymous = $contactDataAnonymous;
return $this;
}
/**
* @param \DateTimeImmutable $createdAt
*
* @return $this
*/
public function setCreatedAt(\DateTimeInterface $createdAt): ThirdParty
{
$this->createdAt = $createdAt;
return $this;
}
public function setCreatedBy(User $user): TrackCreationInterface
{
$this->createdBy = $user;
return $this;
}
/**
* Set email.
*
* @return ThirdParty
*/
public function setEmail(?string $email = null)
{
$this->email = trim((string) $email);
return $this;
}
public function setFirstname(?string $firstname): self
{
$this->firstname = trim((string) $firstname);
return $this;
}
public function setKind(?string $kind): ThirdParty
{
$this->kind = (string) $kind;
return $this;
}
public function setName(?string $name): self
{
$this->name = (string) $name;
return $this;
}
public function setNameCompany(?string $nameCompany): ThirdParty
{
$this->nameCompany = (string) $nameCompany;
return $this;
}
public function setParent(?ThirdParty $parent): ThirdParty
{
$this->parent = $parent;
return $this;
}
public function setProfession(?string $profession): self
{
$this->profession = (string) $profession;
return $this;
}
/**
* Set telephone.
*/
public function setTelephone(?PhoneNumber $telephone = null): self
{
$this->telephone = $telephone;
return $this;
}
/**
* Set type.
*
* @return ThirdParty
*/
public function setThirdPartyTypes(?array $type = [])
{
// remove all keys from the input data
$this->thirdPartyTypes = \array_values($type);
foreach ($this->children as $child) {
$child->setThirdPartyTypes($type);
}
return $this;
}
public function setTypesAndCategories(array $typesAndCategories): self
{
$types = \array_filter($typesAndCategories, static fn ($item) => !$item instanceof ThirdPartyCategory);
$this->setThirdPartyTypes($types);
// handle categories
foreach ($typesAndCategories as $t) {
$this->addTypesAndCategories($t);
}
$categories = \array_filter($typesAndCategories, static fn ($item) => $item instanceof ThirdPartyCategory);
$categoriesHashes = \array_map(static fn (ThirdPartyCategory $c) => \spl_object_hash($c), $categories);
foreach ($categories as $c) {
$this->addCategory($c);
}
foreach ($this->getCategories() as $t) {
if (!\in_array(\spl_object_hash($t), $categoriesHashes, true)) {
$this->removeCategory($t);
}
}
return $this;
}
/**
* @param \DateTimeImmutable $updatedAt
*
* @return $this
*/
public function setUpdatedAt(\DateTimeInterface $updatedAt): ThirdParty
{
$this->updatedAt = $updatedAt;
return $this;
}
/**
* @return $this
*/
public function setUpdatedBy(User $updatedBy): ThirdParty
{
$this->updatedBy = $updatedBy;
return $this;
}
}