2026-04-01 15:42:52 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Entity;
|
|
|
|
|
|
|
|
|
|
use App\Repository\CustomerRepository;
|
|
|
|
|
use Doctrine\ORM\Mapping as ORM;
|
|
|
|
|
|
|
|
|
|
#[ORM\Entity(repositoryClass: CustomerRepository::class)]
|
|
|
|
|
class Customer
|
|
|
|
|
{
|
|
|
|
|
public const TYPE_ASSO = 'association';
|
|
|
|
|
public const TYPE_IE = 'auto-entrepreneur';
|
|
|
|
|
public const TYPE_SAS = 'sas';
|
|
|
|
|
public const TYPE_SARL = 'sarl';
|
|
|
|
|
public const TYPE_EURL = 'eurl';
|
|
|
|
|
public const TYPE_SA = 'sa';
|
|
|
|
|
public const TYPE_SCI = 'sci';
|
|
|
|
|
public const TYPE_PARTICULIER = 'particulier';
|
|
|
|
|
|
|
|
|
|
public const TYPES = [
|
|
|
|
|
self::TYPE_ASSO,
|
|
|
|
|
self::TYPE_IE,
|
|
|
|
|
self::TYPE_SAS,
|
|
|
|
|
self::TYPE_SARL,
|
|
|
|
|
self::TYPE_EURL,
|
|
|
|
|
self::TYPE_SA,
|
|
|
|
|
self::TYPE_SCI,
|
|
|
|
|
self::TYPE_PARTICULIER,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
public const STATE_ACTIVE = 'active';
|
|
|
|
|
public const STATE_SUSPENDED = 'suspended';
|
|
|
|
|
public const STATE_DISABLED = 'disabled';
|
2026-04-04 11:31:55 +02:00
|
|
|
public const STATE_PENDING_DELETE = 'pending_delete';
|
2026-04-01 15:42:52 +02:00
|
|
|
|
|
|
|
|
public const STATES = [
|
|
|
|
|
self::STATE_ACTIVE,
|
|
|
|
|
self::STATE_SUSPENDED,
|
|
|
|
|
self::STATE_DISABLED,
|
2026-04-04 11:31:55 +02:00
|
|
|
self::STATE_PENDING_DELETE,
|
2026-04-01 15:42:52 +02:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
#[ORM\Id]
|
|
|
|
|
#[ORM\GeneratedValue]
|
|
|
|
|
#[ORM\Column]
|
|
|
|
|
private ?int $id = null;
|
|
|
|
|
|
|
|
|
|
#[ORM\OneToOne(targetEntity: User::class)]
|
|
|
|
|
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
|
|
|
|
|
private User $user;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(length: 255, nullable: true)]
|
|
|
|
|
private ?string $raisonSociale = null;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(length: 255, nullable: true)]
|
|
|
|
|
private ?string $firstName = null;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(length: 255, nullable: true)]
|
|
|
|
|
private ?string $lastName = null;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(length: 255, nullable: true)]
|
|
|
|
|
private ?string $email = null;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(length: 20, nullable: true)]
|
|
|
|
|
private ?string $phone = null;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(length: 500, nullable: true)]
|
|
|
|
|
private ?string $address = null;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(length: 500, nullable: true)]
|
|
|
|
|
private ?string $address2 = null;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(length: 10, nullable: true)]
|
|
|
|
|
private ?string $zipCode = null;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(length: 255, nullable: true)]
|
|
|
|
|
private ?string $city = null;
|
|
|
|
|
|
2026-04-04 11:24:52 +02:00
|
|
|
#[ORM\Column(type: 'decimal', precision: 10, scale: 7, nullable: true)]
|
|
|
|
|
private ?string $geoLat = null;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(type: 'decimal', precision: 10, scale: 7, nullable: true)]
|
|
|
|
|
private ?string $geoLong = null;
|
|
|
|
|
|
2026-04-01 15:42:52 +02:00
|
|
|
#[ORM\Column(length: 14, nullable: true)]
|
|
|
|
|
private ?string $siret = null;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(length: 50, nullable: true)]
|
|
|
|
|
private ?string $rcs = null;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(length: 20, nullable: true)]
|
|
|
|
|
private ?string $numTva = null;
|
|
|
|
|
|
feat: auto-remplissage RCS, APE, TVA depuis recherche entreprise
Customer entity :
- Ajout champ ape (VARCHAR 10, nullable) avec getter/setter
- Migration : ALTER TABLE customer ADD ape
Recherche entreprise (entreprise-search.js) :
- RCS construit depuis SIREN + ville du siège (ex: RCS Saint-Quentin 418664058)
- TVA intracommunautaire calculée depuis SIREN (clé modulo 97)
- Code APE/NAF récupéré depuis activite_principale de l'API
- APE affiché dans les résultats de recherche à côté du SIREN/SIRET
- Auto-remplissage des champs : raisonSociale, siret, rcs, numTva, ape,
address, zipCode, city, firstName, lastName
Template create.html.twig :
- Ajout champ "Code APE / NAF" dans la section Entreprise
ClientsController :
- populateCustomerData : ajout setApe depuis le formulaire
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 10:59:24 +02:00
|
|
|
#[ORM\Column(length: 10, nullable: true)]
|
|
|
|
|
private ?string $ape = null;
|
|
|
|
|
|
2026-04-04 11:04:43 +02:00
|
|
|
#[ORM\Column(length: 20, nullable: true)]
|
|
|
|
|
private ?string $rna = null;
|
|
|
|
|
|
2026-04-01 15:42:52 +02:00
|
|
|
#[ORM\Column(length: 255, nullable: true)]
|
|
|
|
|
private ?string $stripeCustomerId = null;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(length: 30, nullable: true)]
|
|
|
|
|
private ?string $typeCompany = null;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(length: 20)]
|
|
|
|
|
private string $state = self::STATE_ACTIVE;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column]
|
|
|
|
|
private \DateTimeImmutable $createdAt;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(length: 50, unique: true, nullable: true)]
|
|
|
|
|
private ?string $codeComptable = null;
|
|
|
|
|
|
2026-04-04 21:39:26 +02:00
|
|
|
#[ORM\Column(length: 10, nullable: true)]
|
|
|
|
|
private ?string $revendeurCode = null;
|
|
|
|
|
|
2026-04-01 15:42:52 +02:00
|
|
|
#[ORM\Column(nullable: true)]
|
|
|
|
|
private ?\DateTimeImmutable $updatedAt = null;
|
|
|
|
|
|
|
|
|
|
public function __construct(User $user)
|
|
|
|
|
{
|
|
|
|
|
$this->user = $user;
|
|
|
|
|
$this->createdAt = new \DateTimeImmutable();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getCodeComptable(): ?string
|
|
|
|
|
{
|
|
|
|
|
return $this->codeComptable;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function setCodeComptable(?string $codeComptable): static
|
|
|
|
|
{
|
|
|
|
|
$this->codeComptable = $codeComptable;
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 21:39:26 +02:00
|
|
|
public function getRevendeurCode(): ?string
|
|
|
|
|
{
|
|
|
|
|
return $this->revendeurCode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function setRevendeurCode(?string $revendeurCode): static
|
|
|
|
|
{
|
|
|
|
|
$this->revendeurCode = $revendeurCode;
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 15:42:52 +02:00
|
|
|
public function generateCodeComptable(): string
|
|
|
|
|
{
|
|
|
|
|
$idPart = str_pad((string) ($this->id ?? 0), 4, '0', STR_PAD_LEFT);
|
|
|
|
|
$namePart = '';
|
|
|
|
|
|
|
|
|
|
if (null !== $this->raisonSociale && '' !== $this->raisonSociale) {
|
|
|
|
|
$clean = preg_replace('/[^A-Za-z]/', '', $this->raisonSociale);
|
|
|
|
|
$namePart = strtoupper(substr($clean, 0, 5));
|
|
|
|
|
} elseif (null !== $this->lastName && '' !== $this->lastName) {
|
|
|
|
|
$clean = preg_replace('/[^A-Za-z]/', '', $this->lastName);
|
|
|
|
|
$namePart = strtoupper(substr($clean, 0, 5));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$namePart = str_pad($namePart, 5, 'X');
|
|
|
|
|
|
feat: comptabilite + prestataires + rapport financier + stats dynamiques
Comptabilite (Super Admin) :
- ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE
(journal ventes, grand livre, FEC, balance agee, reglements,
commissions Stripe 1.5%+0.25E, couts services)
- Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli,
tableau pagine, champ signature DocuSeal
- Signature electronique DocuSeal + callback + envoi email signe
avec template dedie (compta_export_signed.html.twig)
- Rapport financier public (RapportFinancierPdf) : recettes par
service, depenses (Stripe, infra, prestataires), bilan excedent/deficit
- Codes comptables clients EC-XXXX (plus de 411xxx)
Prestataires (Super Admin) :
- Entite Prestataire (raisonSociale, siret, email, phone, adresse)
- Entite FacturePrestataire (numFacture, montantHt, montantTtc,
year, month, isPaid, PDF via Vich)
- CRUD complet avec recherche SIRET via proxy API data.gouv.fr
- Commande cron app:reminder:factures-prestataire (5 du mois)
- Factures prestataires integrees dans export couts services
- Sidebar Super Admin : entree Prestataires + Comptabilite
Stats (/admin/stats) :
- Cout prestataire dynamique depuis FacturePrestataire
- Fusion Infra + Prestataire en "Cout de fonctionnement"
- Commission Stripe corrigee (1.5% + 0.25E par transaction)
Divers :
- DocuSealService::sendComptaForSignature() + getApi()
- Customer::generateCodeComptable() format EC-XXXX-XXXXX
- Protection double prefixe EC- a la creation client
- Bouton regenerer PDF cache quand advert state=accepted
- Modals sans script inline (data-modal-open/close dans app.js)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
|
|
|
return 'EC-'.$idPart.'-'.$namePart;
|
2026-04-01 15:42:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getId(): ?int
|
|
|
|
|
{
|
|
|
|
|
return $this->id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getUser(): User
|
|
|
|
|
{
|
|
|
|
|
return $this->user;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getRaisonSociale(): ?string
|
|
|
|
|
{
|
|
|
|
|
return $this->raisonSociale;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function setRaisonSociale(?string $raisonSociale): static
|
|
|
|
|
{
|
|
|
|
|
$this->raisonSociale = $raisonSociale;
|
|
|
|
|
|
|
|
|
|
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 getFullName(): string
|
|
|
|
|
{
|
|
|
|
|
if (null !== $this->raisonSociale && '' !== $this->raisonSociale) {
|
|
|
|
|
return $this->raisonSociale;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return trim(($this->firstName ?? '').' '.($this->lastName ?? ''));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getEmail(): ?string
|
|
|
|
|
{
|
|
|
|
|
return $this->email;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function setEmail(?string $email): static
|
|
|
|
|
{
|
|
|
|
|
$this->email = $email;
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getPhone(): ?string
|
|
|
|
|
{
|
|
|
|
|
return $this->phone;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function setPhone(?string $phone): static
|
|
|
|
|
{
|
|
|
|
|
$this->phone = $phone;
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getAddress(): ?string
|
|
|
|
|
{
|
|
|
|
|
return $this->address;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function setAddress(?string $address): static
|
|
|
|
|
{
|
|
|
|
|
$this->address = $address;
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getAddress2(): ?string
|
|
|
|
|
{
|
|
|
|
|
return $this->address2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function setAddress2(?string $address2): static
|
|
|
|
|
{
|
|
|
|
|
$this->address2 = $address2;
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getZipCode(): ?string
|
|
|
|
|
{
|
|
|
|
|
return $this->zipCode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function setZipCode(?string $zipCode): static
|
|
|
|
|
{
|
|
|
|
|
$this->zipCode = $zipCode;
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getCity(): ?string
|
|
|
|
|
{
|
|
|
|
|
return $this->city;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function setCity(?string $city): static
|
|
|
|
|
{
|
|
|
|
|
$this->city = $city;
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
2026-04-04 11:24:52 +02:00
|
|
|
|
|
|
|
|
public function getGeoLat(): ?string
|
|
|
|
|
{
|
|
|
|
|
return $this->geoLat;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function setGeoLat(?string $geoLat): static
|
|
|
|
|
{
|
|
|
|
|
$this->geoLat = $geoLat;
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getGeoLong(): ?string
|
|
|
|
|
{
|
|
|
|
|
return $this->geoLong;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function setGeoLong(?string $geoLong): static
|
|
|
|
|
{
|
|
|
|
|
$this->geoLong = $geoLong;
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
2026-04-01 15:42:52 +02:00
|
|
|
|
|
|
|
|
public function getSiret(): ?string
|
|
|
|
|
{
|
|
|
|
|
return $this->siret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function setSiret(?string $siret): static
|
|
|
|
|
{
|
|
|
|
|
$this->siret = $siret;
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getRcs(): ?string
|
|
|
|
|
{
|
|
|
|
|
return $this->rcs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function setRcs(?string $rcs): static
|
|
|
|
|
{
|
|
|
|
|
$this->rcs = $rcs;
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getNumTva(): ?string
|
|
|
|
|
{
|
|
|
|
|
return $this->numTva;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function setNumTva(?string $numTva): static
|
|
|
|
|
{
|
|
|
|
|
$this->numTva = $numTva;
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
feat: auto-remplissage RCS, APE, TVA depuis recherche entreprise
Customer entity :
- Ajout champ ape (VARCHAR 10, nullable) avec getter/setter
- Migration : ALTER TABLE customer ADD ape
Recherche entreprise (entreprise-search.js) :
- RCS construit depuis SIREN + ville du siège (ex: RCS Saint-Quentin 418664058)
- TVA intracommunautaire calculée depuis SIREN (clé modulo 97)
- Code APE/NAF récupéré depuis activite_principale de l'API
- APE affiché dans les résultats de recherche à côté du SIREN/SIRET
- Auto-remplissage des champs : raisonSociale, siret, rcs, numTva, ape,
address, zipCode, city, firstName, lastName
Template create.html.twig :
- Ajout champ "Code APE / NAF" dans la section Entreprise
ClientsController :
- populateCustomerData : ajout setApe depuis le formulaire
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 10:59:24 +02:00
|
|
|
public function getApe(): ?string
|
|
|
|
|
{
|
|
|
|
|
return $this->ape;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function setApe(?string $ape): static
|
|
|
|
|
{
|
|
|
|
|
$this->ape = $ape;
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 11:04:43 +02:00
|
|
|
public function getRna(): ?string
|
|
|
|
|
{
|
|
|
|
|
return $this->rna;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function setRna(?string $rna): static
|
|
|
|
|
{
|
|
|
|
|
$this->rna = $rna;
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 15:42:52 +02:00
|
|
|
public function getStripeCustomerId(): ?string
|
|
|
|
|
{
|
|
|
|
|
return $this->stripeCustomerId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function setStripeCustomerId(?string $stripeCustomerId): static
|
|
|
|
|
{
|
|
|
|
|
$this->stripeCustomerId = $stripeCustomerId;
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getTypeCompany(): ?string
|
|
|
|
|
{
|
|
|
|
|
return $this->typeCompany;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function setTypeCompany(?string $typeCompany): static
|
|
|
|
|
{
|
|
|
|
|
$this->typeCompany = $typeCompany;
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getState(): string
|
|
|
|
|
{
|
|
|
|
|
return $this->state;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function setState(string $state): static
|
|
|
|
|
{
|
|
|
|
|
$this->state = $state;
|
|
|
|
|
$this->updatedAt = new \DateTimeImmutable();
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function isActive(): bool
|
|
|
|
|
{
|
|
|
|
|
return self::STATE_ACTIVE === $this->state;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 11:31:55 +02:00
|
|
|
public function isPendingDelete(): bool
|
|
|
|
|
{
|
|
|
|
|
return self::STATE_PENDING_DELETE === $this->state;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 15:42:52 +02:00
|
|
|
public function getCreatedAt(): \DateTimeImmutable
|
|
|
|
|
{
|
|
|
|
|
return $this->createdAt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getUpdatedAt(): ?\DateTimeImmutable
|
|
|
|
|
{
|
|
|
|
|
return $this->updatedAt;
|
|
|
|
|
}
|
feat: page client /admin/clients/{id} avec onglets et gestion contacts
Route /admin/clients/{id} (ClientsController::show) :
- 10 onglets : Information globale, Contacts, Factures, Avis de Paiement,
Devis, Impayes, Echeancier, EsyFlex, Sites Internet, Services
- Onglet actif via query param ?tab=
Onglet Information globale :
- Formulaire edition complet : identite (prenom, nom, email, phone, type),
entreprise (raison sociale, SIRET, RCS, TVA, APE, RNA),
adresse (adresse, complement, CP, ville, geoLat/geoLong hidden)
- Section systeme : code comptable, Stripe ID, dates creation/modification
- POST sauvegarde + updatedAt mis a jour
Onglet Contacts :
- Formulaire ajout contact : prenom, nom, email, phone, role, isBillingEmail
- Table des contacts existants avec suppression (data-confirm modal)
- Gestion via handleContactForm() : create/delete avec verification owner
Onglets placeholder :
- Factures, Avis, Devis, Impayes, Echeancier, EsyFlex, Sites, Services
affichent "Cette section sera disponible prochainement"
Customer entity :
- Ajout setUpdatedAt()
Template index :
- Nom du client cliquable (lien vers show)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 17:12:47 +02:00
|
|
|
|
|
|
|
|
public function setUpdatedAt(?\DateTimeImmutable $updatedAt): static
|
|
|
|
|
{
|
|
|
|
|
$this->updatedAt = $updatedAt;
|
|
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
2026-04-01 15:42:52 +02:00
|
|
|
}
|