Files
crm_ecosplay/src/Entity/Customer.php
Serreau Jovann 8b35e2b6d2 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

444 lines
9.5 KiB
PHP

<?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';
public const STATE_PENDING_DELETE = 'pending_delete';
public const STATES = [
self::STATE_ACTIVE,
self::STATE_SUSPENDED,
self::STATE_DISABLED,
self::STATE_PENDING_DELETE,
];
#[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;
#[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;
#[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;
#[ORM\Column(length: 10, nullable: true)]
private ?string $ape = null;
#[ORM\Column(length: 20, nullable: true)]
private ?string $rna = null;
#[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;
#[ORM\Column(length: 10, nullable: true)]
private ?string $revendeurCode = null;
#[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;
}
public function getRevendeurCode(): ?string
{
return $this->revendeurCode;
}
public function setRevendeurCode(?string $revendeurCode): static
{
$this->revendeurCode = $revendeurCode;
return $this;
}
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');
return 'EC-'.$idPart.'-'.$namePart;
}
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;
}
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;
}
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;
}
public function getApe(): ?string
{
return $this->ape;
}
public function setApe(?string $ape): static
{
$this->ape = $ape;
return $this;
}
public function getRna(): ?string
{
return $this->rna;
}
public function setRna(?string $rna): static
{
$this->rna = $rna;
return $this;
}
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;
}
public function isPendingDelete(): bool
{
return self::STATE_PENDING_DELETE === $this->state;
}
public function getCreatedAt(): \DateTimeImmutable
{
return $this->createdAt;
}
public function getUpdatedAt(): ?\DateTimeImmutable
{
return $this->updatedAt;
}
public function setUpdatedAt(?\DateTimeImmutable $updatedAt): static
{
$this->updatedAt = $updatedAt;
return $this;
}
}