b495e4030a
Auto Tag Develop / tag (push) Failing after 28s
## Contexte Ticket Lesstime **#54** (1.1 / Backend / M) — spec `docs/specs/M1-clients/spec-back.md` § 3.4 / § 3.5. > 🔗 **MR stackée sur ERP-53** — cible `feature/ERP-53-migrer-tables-client-m1`, **pas** `develop`. À repointer vers `develop` quand ERP-53 sera mergé (cf. `STACK-BRANCHES-PROCEDURE.md`). Le diff ne montre que les fichiers d'ERP-54. ## Contenu **9 entités** (`src/Module/Commercial/Domain/Entity/`) : - Métier : `Client`, `ClientContact`, `ClientAddress`, `ClientRib` — `#[Auditable]` + Timestampable/Blamable. - Référentiels statiques lecture seule : `TvaMode`, `PaymentDelay`, `PaymentType`, `Bank` — whitelistés dans `EntitiesAreTimestampableBlamableTest::EXCLUDED`. **8 repositories** interfaces (`Domain/Repository/`) + impl Doctrine (`Infrastructure/Doctrine/`). > La spec § 3.5 ne définit que 8 entités (4 métier + 4 référentiels) ; pas de 9ᵉ entité malgré la formulation « 9 paires » du ticket. ## Décisions - **Aucun `#[ApiResource]` dans ce ticket** : le bloc ApiResource du `Client` (§ 3.4) référence `ClientProvider`/`ClientProcessor` = périmètre **ERP-55**. L'inclure casserait `cache:clear`/`make test`/`schema:validate`. Les entités sont des entités Doctrine pures (ORM + Assert + Groups). Endpoints lecture seule des référentiels → ticket dédié. - **Q4** : `Client` sans `#[ORM\UniqueConstraint]` — unicité du nom de société portée par l'index partiel Postgres `uq_client_company_name_active` (inexprimable en attribut ORM). - **Audit RIB (29/05)** : aucun `#[AuditIgnore]` sur `ClientRib.iban`/`bic` (tous champs audités, audit admin-only). - **Cross-module (règle n°1)** : M2M `Category` via le contrat `Shared\Domain\Contract\CategoryInterface` + `resolve_target_entities` (pas d'import direct Catalog→Commercial) ; `ClientAddress.sites` via `SiteInterface` existant. ## Infra nécessaire (découvert pendant le dev) - `doctrine.yaml` : mapping ORM du module `Commercial` (mappings explicites par module) + résolution `CategoryInterface → Category`. - `CommercialReferentialFixtures` **créée** (n'existait pas — ERP-53 avait seedé les CategoryType côté Catalog) : re-seed idempotent des 4 référentiels, sinon vidés au `db-reset` (désormais tables mappées). - `ColumnCommentsCatalog` étendu (colonnes M1) pour le chemin `schema:update`/test — sinon `ColumnsHaveSqlCommentTest` (garde-fou n°12) échoue. - Migration retrofit `Version20260528120000` (ERP-67) rendue résiliente (`$schema->hasTable()`) : elle rejouait tout le catalogue mais s'exécute avant la création des tables M1 → `relation tva_mode does not exist`. Conforme à son docblock (« les futures migrations posent leurs propres COMMENT »). - `makefile test-db-setup` : recréation de l'index partiel `uq_client_company_name_active` (analogue de la ligne existante pour `category`). ## Vérifications - `make php-cs-fixer-allow-risky` ✓ - `make db-reset` ✓ (bout en bout ; 4 référentiels + 4 CategoryType présents, 2 index partiels créés) - `make test` ✓ **312/312** (Architecture vert, 0 régression M0) - `doctrine:schema:validate` : Mapping **OK** ; « not in sync » = bruit cosmétique pré-existant du projet (clear COMMENT hors-ORM, drop index partiels, renommages d'index). Seul diff introduit : renommage cosmétique de l'index M2M `idx_client_category_category` (même colonne) — aucun écart de type/colonne/FK vs migration ERP-53. --------- Co-authored-by: admin malio <malio@yuno.malio.fr> Co-authored-by: Matthieu <contact@malio.fr> Co-authored-by: Matthieu <mtholot19@gmail.com> Reviewed-on: #29 Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr> Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
639 lines
18 KiB
PHP
639 lines
18 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Module\Commercial\Domain\Entity;
|
|
|
|
use App\Module\Commercial\Infrastructure\Doctrine\DoctrineClientRepository;
|
|
use App\Shared\Domain\Attribute\Auditable;
|
|
use App\Shared\Domain\Contract\BlamableInterface;
|
|
use App\Shared\Domain\Contract\CategoryInterface;
|
|
use App\Shared\Domain\Contract\TimestampableInterface;
|
|
use App\Shared\Domain\Trait\TimestampableBlamableTrait;
|
|
use DateTimeImmutable;
|
|
use Doctrine\Common\Collections\ArrayCollection;
|
|
use Doctrine\Common\Collections\Collection;
|
|
use Doctrine\ORM\Mapping as ORM;
|
|
use Symfony\Component\Serializer\Attribute\Groups;
|
|
use Symfony\Component\Validator\Constraints as Assert;
|
|
|
|
/**
|
|
* Client (M1 Commercial) — entite racine du repertoire clients. Porte le
|
|
* formulaire principal, l'onglet Information, l'onglet Comptabilite, le
|
|
* mecanisme d'archivage (is_archived / archived_at) et le soft-delete technique
|
|
* prepare mais non expose au M1 (deleted_at, HP-M2-1).
|
|
*
|
|
* Decisions structurantes :
|
|
* - Audit complet (#[Auditable]) sur tous les champs (M2M categories audite
|
|
* automatiquement). Timestampable/Blamable via le trait Shared.
|
|
* - PAS de #[ORM\UniqueConstraint] : l'unicite du nom de societe (RG-1.16) est
|
|
* portee par l'index partiel fonctionnel uq_client_company_name_active
|
|
* (LOWER(company_name) WHERE is_archived = FALSE AND deleted_at IS NULL),
|
|
* inexprimable en attribut ORM, donc possede par la seule migration. Le SIREN
|
|
* et l'email NE SONT PAS uniques (RG-1.15/1.17 supprimees, decision Q4).
|
|
* - distributor / broker : 2 FK auto-referentes mutuellement exclusives
|
|
* (RG-1.03, CHECK chk_client_distrib_or_broker en base).
|
|
* - categories : M2M vers Category (module Catalog) via le contrat
|
|
* CategoryInterface + resolve_target_entities (regle n°1, pas d'import direct).
|
|
*
|
|
* Aucun ApiResource au M1.1 (ERP-54) : les operations API (Provider + Processor,
|
|
* normalisation, archivage, accounting conditionnel) sont branchees en ERP-55.
|
|
*/
|
|
#[ORM\Entity(repositoryClass: DoctrineClientRepository::class)]
|
|
#[ORM\Table(name: 'client')]
|
|
// Index nommes pour matcher la migration (Version20260601000000). L'index
|
|
// unique partiel uq_client_company_name_active reste possede par la migration :
|
|
// Doctrine ORM ne sait pas exprimer un index fonctionnel (LOWER) + partiel
|
|
// (WHERE) via attribut. Pas de #[ORM\UniqueConstraint] (decision Q4).
|
|
#[ORM\Index(name: 'idx_client_is_archived', columns: ['is_archived'])]
|
|
#[ORM\Index(name: 'idx_client_deleted_at', columns: ['deleted_at'])]
|
|
#[ORM\Index(name: 'idx_client_distributor_id', columns: ['distributor_id'])]
|
|
#[ORM\Index(name: 'idx_client_broker_id', columns: ['broker_id'])]
|
|
#[ORM\Index(name: 'idx_client_created_by', columns: ['created_by'])]
|
|
#[ORM\Index(name: 'idx_client_updated_by', columns: ['updated_by'])]
|
|
#[Auditable]
|
|
class Client implements TimestampableInterface, BlamableInterface
|
|
{
|
|
use TimestampableBlamableTrait;
|
|
|
|
#[ORM\Id]
|
|
#[ORM\GeneratedValue]
|
|
#[ORM\Column]
|
|
#[Groups(['client:read'])]
|
|
private ?int $id = null;
|
|
|
|
// === Formulaire principal ===
|
|
#[ORM\Column(length: 180)]
|
|
#[Assert\NotBlank(message: 'Le nom de l\'entreprise est obligatoire.', normalizer: 'trim')]
|
|
#[Assert\Length(min: 2, max: 180, normalizer: 'trim')]
|
|
#[Groups(['client:read', 'client:write:main'])]
|
|
private ?string $companyName = null;
|
|
|
|
// RG-1.01 : firstName OU lastName obligatoire (validation au futur Processor).
|
|
#[ORM\Column(length: 120, nullable: true)]
|
|
#[Assert\Length(max: 120, normalizer: 'trim')]
|
|
#[Groups(['client:read', 'client:write:main'])]
|
|
private ?string $firstName = null;
|
|
|
|
#[ORM\Column(length: 120, nullable: true)]
|
|
#[Assert\Length(max: 120, normalizer: 'trim')]
|
|
#[Groups(['client:read', 'client:write:main'])]
|
|
private ?string $lastName = null;
|
|
|
|
#[ORM\Column(length: 20)]
|
|
#[Assert\NotBlank]
|
|
#[Groups(['client:read', 'client:write:main'])]
|
|
private ?string $phonePrimary = null;
|
|
|
|
#[ORM\Column(length: 20, nullable: true)]
|
|
#[Groups(['client:read', 'client:write:main'])]
|
|
private ?string $phoneSecondary = null;
|
|
|
|
#[ORM\Column(length: 180)]
|
|
#[Assert\NotBlank]
|
|
#[Assert\Email]
|
|
#[Groups(['client:read', 'client:write:main'])]
|
|
private ?string $email = null;
|
|
|
|
// RG-1.03 : distributor / broker auto-references mutuellement exclusives
|
|
// (CHECK chk_client_distrib_or_broker en base).
|
|
#[ORM\ManyToOne(targetEntity: self::class)]
|
|
#[ORM\JoinColumn(name: 'distributor_id', referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
|
|
#[Groups(['client:read', 'client:write:main'])]
|
|
private ?Client $distributor = null;
|
|
|
|
#[ORM\ManyToOne(targetEntity: self::class)]
|
|
#[ORM\JoinColumn(name: 'broker_id', referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
|
|
#[Groups(['client:read', 'client:write:main'])]
|
|
private ?Client $broker = null;
|
|
|
|
#[ORM\Column(name: 'triage_service', options: ['default' => false])]
|
|
#[Groups(['client:read', 'client:write:main'])]
|
|
private bool $triageService = false;
|
|
|
|
// RG : au moins une categorie (Count min 1). M2M vers Category via le contrat
|
|
// CategoryInterface (resolve_target_entities -> Category).
|
|
/** @var Collection<int, CategoryInterface> */
|
|
#[ORM\ManyToMany(targetEntity: CategoryInterface::class)]
|
|
#[ORM\JoinTable(name: 'client_category')]
|
|
#[ORM\JoinColumn(name: 'client_id', referencedColumnName: 'id', onDelete: 'CASCADE')]
|
|
#[ORM\InverseJoinColumn(name: 'category_id', referencedColumnName: 'id', onDelete: 'RESTRICT')]
|
|
#[Assert\Count(min: 1, minMessage: 'Au moins une catégorie est obligatoire.')]
|
|
#[Groups(['client:read', 'client:write:main'])]
|
|
private Collection $categories;
|
|
|
|
// === Onglet Information ===
|
|
#[ORM\Column(type: 'text', nullable: true)]
|
|
#[Groups(['client:read', 'client:write:information'])]
|
|
private ?string $description = null;
|
|
|
|
#[ORM\Column(length: 255, nullable: true)]
|
|
#[Groups(['client:read', 'client:write:information'])]
|
|
private ?string $competitors = null;
|
|
|
|
#[ORM\Column(type: 'date_immutable', nullable: true)]
|
|
#[Groups(['client:read', 'client:write:information'])]
|
|
private ?DateTimeImmutable $foundedAt = null;
|
|
|
|
#[ORM\Column(nullable: true)]
|
|
#[Assert\PositiveOrZero]
|
|
#[Groups(['client:read', 'client:write:information'])]
|
|
private ?int $employeesCount = null;
|
|
|
|
#[ORM\Column(type: 'decimal', precision: 15, scale: 2, nullable: true)]
|
|
#[Groups(['client:read', 'client:write:information'])]
|
|
private ?string $revenueAmount = null;
|
|
|
|
#[ORM\Column(length: 120, nullable: true)]
|
|
#[Groups(['client:read', 'client:write:information'])]
|
|
private ?string $directorName = null;
|
|
|
|
#[ORM\Column(type: 'decimal', precision: 15, scale: 2, nullable: true)]
|
|
#[Groups(['client:read', 'client:write:information'])]
|
|
private ?string $profitAmount = null;
|
|
|
|
// === Onglet Comptabilite ===
|
|
// Lecture conditionnee via le groupe `client:read:accounting` (ajoute par le
|
|
// futur Provider si l'user a la permission accounting.view). Ecriture via
|
|
// `client:write:accounting` (le futur Processor exige accounting.manage).
|
|
#[ORM\Column(length: 20, nullable: true)]
|
|
#[Groups(['client:read:accounting', 'client:write:accounting'])]
|
|
private ?string $siren = null;
|
|
|
|
#[ORM\Column(length: 40, nullable: true)]
|
|
#[Groups(['client:read:accounting', 'client:write:accounting'])]
|
|
private ?string $accountNumber = null;
|
|
|
|
#[ORM\ManyToOne(targetEntity: TvaMode::class)]
|
|
#[ORM\JoinColumn(name: 'tva_mode_id', referencedColumnName: 'id', nullable: true, onDelete: 'RESTRICT')]
|
|
#[Groups(['client:read:accounting', 'client:write:accounting'])]
|
|
private ?TvaMode $tvaMode = null;
|
|
|
|
#[ORM\Column(length: 40, nullable: true)]
|
|
#[Groups(['client:read:accounting', 'client:write:accounting'])]
|
|
private ?string $nTva = null;
|
|
|
|
#[ORM\ManyToOne(targetEntity: PaymentDelay::class)]
|
|
#[ORM\JoinColumn(name: 'payment_delay_id', referencedColumnName: 'id', nullable: true, onDelete: 'RESTRICT')]
|
|
#[Groups(['client:read:accounting', 'client:write:accounting'])]
|
|
private ?PaymentDelay $paymentDelay = null;
|
|
|
|
#[ORM\ManyToOne(targetEntity: PaymentType::class)]
|
|
#[ORM\JoinColumn(name: 'payment_type_id', referencedColumnName: 'id', nullable: true, onDelete: 'RESTRICT')]
|
|
#[Groups(['client:read:accounting', 'client:write:accounting'])]
|
|
private ?PaymentType $paymentType = null;
|
|
|
|
#[ORM\ManyToOne(targetEntity: Bank::class)]
|
|
#[ORM\JoinColumn(name: 'bank_id', referencedColumnName: 'id', nullable: true, onDelete: 'RESTRICT')]
|
|
#[Groups(['client:read:accounting', 'client:write:accounting'])]
|
|
private ?Bank $bank = null;
|
|
|
|
// === Sous-collections (exposees via sous-ressources API dediees, ulterieur) ===
|
|
/** @var Collection<int, ClientContact> */
|
|
#[ORM\OneToMany(mappedBy: 'client', targetEntity: ClientContact::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
|
|
private Collection $contacts;
|
|
|
|
/** @var Collection<int, ClientAddress> */
|
|
#[ORM\OneToMany(mappedBy: 'client', targetEntity: ClientAddress::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
|
|
private Collection $addresses;
|
|
|
|
/** @var Collection<int, ClientRib> */
|
|
#[ORM\OneToMany(mappedBy: 'client', targetEntity: ClientRib::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
|
|
private Collection $ribs;
|
|
|
|
// === Archive / Soft delete ===
|
|
#[ORM\Column(name: 'is_archived', options: ['default' => false])]
|
|
#[Groups(['client:read', 'client:write:archive'])]
|
|
private bool $isArchived = false;
|
|
|
|
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
|
|
#[Groups(['client:read'])]
|
|
private ?DateTimeImmutable $archivedAt = null;
|
|
|
|
// Soft delete technique (HP-M2-1) : non expose en lecture/ecriture au M1.
|
|
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
|
|
private ?DateTimeImmutable $deletedAt = null;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->categories = new ArrayCollection();
|
|
$this->contacts = new ArrayCollection();
|
|
$this->addresses = new ArrayCollection();
|
|
$this->ribs = new ArrayCollection();
|
|
}
|
|
|
|
public function getId(): ?int
|
|
{
|
|
return $this->id;
|
|
}
|
|
|
|
public function getCompanyName(): ?string
|
|
{
|
|
return $this->companyName;
|
|
}
|
|
|
|
public function setCompanyName(string $companyName): static
|
|
{
|
|
$this->companyName = $companyName;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getFirstName(): ?string
|
|
{
|
|
return $this->firstName;
|
|
}
|
|
|
|
public function setFirstName(?string $firstName): static
|
|
{
|
|
$this->firstName = $firstName;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getLastName(): ?string
|
|
{
|
|
return $this->lastName;
|
|
}
|
|
|
|
public function setLastName(?string $lastName): static
|
|
{
|
|
$this->lastName = $lastName;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getPhonePrimary(): ?string
|
|
{
|
|
return $this->phonePrimary;
|
|
}
|
|
|
|
public function setPhonePrimary(string $phonePrimary): static
|
|
{
|
|
$this->phonePrimary = $phonePrimary;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getPhoneSecondary(): ?string
|
|
{
|
|
return $this->phoneSecondary;
|
|
}
|
|
|
|
public function setPhoneSecondary(?string $phoneSecondary): static
|
|
{
|
|
$this->phoneSecondary = $phoneSecondary;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getEmail(): ?string
|
|
{
|
|
return $this->email;
|
|
}
|
|
|
|
public function setEmail(string $email): static
|
|
{
|
|
$this->email = $email;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getDistributor(): ?Client
|
|
{
|
|
return $this->distributor;
|
|
}
|
|
|
|
public function setDistributor(?Client $distributor): static
|
|
{
|
|
$this->distributor = $distributor;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getBroker(): ?Client
|
|
{
|
|
return $this->broker;
|
|
}
|
|
|
|
public function setBroker(?Client $broker): static
|
|
{
|
|
$this->broker = $broker;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function isTriageService(): bool
|
|
{
|
|
return $this->triageService;
|
|
}
|
|
|
|
public function setTriageService(bool $triageService): static
|
|
{
|
|
$this->triageService = $triageService;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/** @return Collection<int, CategoryInterface> */
|
|
public function getCategories(): Collection
|
|
{
|
|
return $this->categories;
|
|
}
|
|
|
|
public function addCategory(CategoryInterface $category): static
|
|
{
|
|
if (!$this->categories->contains($category)) {
|
|
$this->categories->add($category);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function removeCategory(CategoryInterface $category): static
|
|
{
|
|
$this->categories->removeElement($category);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getDescription(): ?string
|
|
{
|
|
return $this->description;
|
|
}
|
|
|
|
public function setDescription(?string $description): static
|
|
{
|
|
$this->description = $description;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getCompetitors(): ?string
|
|
{
|
|
return $this->competitors;
|
|
}
|
|
|
|
public function setCompetitors(?string $competitors): static
|
|
{
|
|
$this->competitors = $competitors;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getFoundedAt(): ?DateTimeImmutable
|
|
{
|
|
return $this->foundedAt;
|
|
}
|
|
|
|
public function setFoundedAt(?DateTimeImmutable $foundedAt): static
|
|
{
|
|
$this->foundedAt = $foundedAt;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getEmployeesCount(): ?int
|
|
{
|
|
return $this->employeesCount;
|
|
}
|
|
|
|
public function setEmployeesCount(?int $employeesCount): static
|
|
{
|
|
$this->employeesCount = $employeesCount;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getRevenueAmount(): ?string
|
|
{
|
|
return $this->revenueAmount;
|
|
}
|
|
|
|
public function setRevenueAmount(?string $revenueAmount): static
|
|
{
|
|
$this->revenueAmount = $revenueAmount;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getDirectorName(): ?string
|
|
{
|
|
return $this->directorName;
|
|
}
|
|
|
|
public function setDirectorName(?string $directorName): static
|
|
{
|
|
$this->directorName = $directorName;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getProfitAmount(): ?string
|
|
{
|
|
return $this->profitAmount;
|
|
}
|
|
|
|
public function setProfitAmount(?string $profitAmount): static
|
|
{
|
|
$this->profitAmount = $profitAmount;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getSiren(): ?string
|
|
{
|
|
return $this->siren;
|
|
}
|
|
|
|
public function setSiren(?string $siren): static
|
|
{
|
|
$this->siren = $siren;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getAccountNumber(): ?string
|
|
{
|
|
return $this->accountNumber;
|
|
}
|
|
|
|
public function setAccountNumber(?string $accountNumber): static
|
|
{
|
|
$this->accountNumber = $accountNumber;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getTvaMode(): ?TvaMode
|
|
{
|
|
return $this->tvaMode;
|
|
}
|
|
|
|
public function setTvaMode(?TvaMode $tvaMode): static
|
|
{
|
|
$this->tvaMode = $tvaMode;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getNTva(): ?string
|
|
{
|
|
return $this->nTva;
|
|
}
|
|
|
|
public function setNTva(?string $nTva): static
|
|
{
|
|
$this->nTva = $nTva;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getPaymentDelay(): ?PaymentDelay
|
|
{
|
|
return $this->paymentDelay;
|
|
}
|
|
|
|
public function setPaymentDelay(?PaymentDelay $paymentDelay): static
|
|
{
|
|
$this->paymentDelay = $paymentDelay;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getPaymentType(): ?PaymentType
|
|
{
|
|
return $this->paymentType;
|
|
}
|
|
|
|
public function setPaymentType(?PaymentType $paymentType): static
|
|
{
|
|
$this->paymentType = $paymentType;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getBank(): ?Bank
|
|
{
|
|
return $this->bank;
|
|
}
|
|
|
|
public function setBank(?Bank $bank): static
|
|
{
|
|
$this->bank = $bank;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/** @return Collection<int, ClientContact> */
|
|
public function getContacts(): Collection
|
|
{
|
|
return $this->contacts;
|
|
}
|
|
|
|
public function addContact(ClientContact $contact): static
|
|
{
|
|
if (!$this->contacts->contains($contact)) {
|
|
$this->contacts->add($contact);
|
|
$contact->setClient($this);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function removeContact(ClientContact $contact): static
|
|
{
|
|
if ($this->contacts->removeElement($contact) && $contact->getClient() === $this) {
|
|
$contact->setClient(null);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/** @return Collection<int, ClientAddress> */
|
|
public function getAddresses(): Collection
|
|
{
|
|
return $this->addresses;
|
|
}
|
|
|
|
public function addAddress(ClientAddress $address): static
|
|
{
|
|
if (!$this->addresses->contains($address)) {
|
|
$this->addresses->add($address);
|
|
$address->setClient($this);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function removeAddress(ClientAddress $address): static
|
|
{
|
|
if ($this->addresses->removeElement($address) && $address->getClient() === $this) {
|
|
$address->setClient(null);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/** @return Collection<int, ClientRib> */
|
|
public function getRibs(): Collection
|
|
{
|
|
return $this->ribs;
|
|
}
|
|
|
|
public function addRib(ClientRib $rib): static
|
|
{
|
|
if (!$this->ribs->contains($rib)) {
|
|
$this->ribs->add($rib);
|
|
$rib->setClient($this);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function removeRib(ClientRib $rib): static
|
|
{
|
|
if ($this->ribs->removeElement($rib) && $rib->getClient() === $this) {
|
|
$rib->setClient(null);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function isArchived(): bool
|
|
{
|
|
return $this->isArchived;
|
|
}
|
|
|
|
public function setIsArchived(bool $isArchived): static
|
|
{
|
|
$this->isArchived = $isArchived;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getArchivedAt(): ?DateTimeImmutable
|
|
{
|
|
return $this->archivedAt;
|
|
}
|
|
|
|
public function setArchivedAt(?DateTimeImmutable $archivedAt): static
|
|
{
|
|
$this->archivedAt = $archivedAt;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getDeletedAt(): ?DateTimeImmutable
|
|
{
|
|
return $this->deletedAt;
|
|
}
|
|
|
|
public function setDeletedAt(?DateTimeImmutable $deletedAt): static
|
|
{
|
|
$this->deletedAt = $deletedAt;
|
|
|
|
return $this;
|
|
}
|
|
}
|