feat(transport) : schéma + entités Carrier + contrat lecture (ERP-155/157)
Schéma BDD du répertoire transporteurs (M4) + entités + contrat de lecture (liste + détail), socle du front. - Migration Version20260615150000 : tables carrier / carrier_address / carrier_contact / carrier_price (FK cross-module, CHECK enum, index partiel uq_carrier_name_active, COMMENT ON COLUMN). uploaded_document et qualimat_carrier réutilisées (non recréées). - Entités Carrier* (#[Auditable], Timestampable/Blamable) + ApiResource LECTURE seule (GetCollection + Get via CarrierProvider, anti-N+1, exclusion archivés + ?includeArchived). Écriture (POST/PATCH + Processor) reportée WT4+. - QualimatCarrier : mapping ORM lecture seule sur la table référentielle existante (sortie du schema_filter, mapping aligné DDL ERP-39, schema:update no-op) + endpoint de recherche read-only (§ 4.7). - Relations cross-module des prix (Client/Supplier/adresses) via contrats Shared (ClientInterface, SupplierInterface, ClientAddressInterface, SupplierAddressInterface) + resolve_target_entities — sans import inter-module (règle n°1). Ajout du groupe supplier_address:read aux champs de SupplierAddress pour l'embed. - Garde-fous : ColumnCommentsCatalog (carrier* + qualimat_carrier), makefile test-db-setup (index partiel carrier), i18n audit (transport_carrier*), EntitiesAreTimestampableBlamableTest (QualimatCarrier whitelisté). - CarrierSerializationContractTest : contrat JSON liste + détail vérifié (embeds objet, booléens, enveloppe Hydra) ; JSON réel capturé dans spec-back § 4.0.bis. make db-reset OK, make test vert (731), make nuxt-test vert (480), php-cs-fixer OK.
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Module\Transport\Domain\Entity;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter;
|
||||
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
|
||||
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||
use ApiPlatform\Metadata\ApiFilter;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Attribute\Groups;
|
||||
use Symfony\Component\Serializer\Attribute\SerializedName;
|
||||
|
||||
/**
|
||||
* Mapping ORM LECTURE SEULE sur la table existante `qualimat_carrier`
|
||||
* (referentiel des transporteurs agrees QUALIMAT, ERP-39). La table est
|
||||
* alimentee/soft-deletee EXCLUSIVEMENT par la commande console `app:qualimat:sync` ;
|
||||
* cette entite n'expose donc AUCUNE ecriture (ni Post/Patch/Delete).
|
||||
*
|
||||
* Role M4 (ERP-155/157) :
|
||||
* - cible de la FK editable `carrier.qualimat_carrier_id` (§ 2.5) ;
|
||||
* - embarquee (groupe `qualimat:read`) dans la liste et le detail Carrier pour
|
||||
* afficher statut + date de validite QUALIMAT (RG-4.04) ;
|
||||
* - endpoint de recherche `GET /api/qualimat_carriers?...` pour la saisie
|
||||
* assistee du nom (§ 4.7) — filtres built-in name/siret (partiel), isActive.
|
||||
*
|
||||
* La table reste hors `schema_filter` Doctrine (doctrine.yaml) : c'est la
|
||||
* migration modulaire Version20260612150000 qui possede son DDL et ses COMMENT
|
||||
* (pas l'ORM). Lecture seule + referentiel synchronise => exclue de
|
||||
* EntitiesAreTimestampableBlamableTest et non #[Auditable].
|
||||
*/
|
||||
#[ApiResource(
|
||||
operations: [
|
||||
new GetCollection(
|
||||
security: "is_granted('transport.carriers.view')",
|
||||
normalizationContext: ['groups' => ['qualimat:read', 'default:read']],
|
||||
),
|
||||
new Get(
|
||||
security: "is_granted('transport.carriers.view')",
|
||||
normalizationContext: ['groups' => ['qualimat:read', 'default:read']],
|
||||
),
|
||||
],
|
||||
)]
|
||||
#[ApiFilter(SearchFilter::class, properties: ['name' => 'ipartial', 'siret' => 'partial'])]
|
||||
#[ApiFilter(BooleanFilter::class, properties: ['isActive'])]
|
||||
#[ApiFilter(OrderFilter::class, properties: ['name'], arguments: ['orderParameterName' => 'order'])]
|
||||
#[ORM\Entity]
|
||||
// Mapping reproduisant a l'identique le DDL de la migration ERP-39
|
||||
// (Version20260612150000) pour que `schema:update --force` reste un no-op :
|
||||
// contrainte d'unicite siret + index is_active.
|
||||
#[ORM\Table(name: 'qualimat_carrier')]
|
||||
#[ORM\UniqueConstraint(name: 'uq_qualimat_carrier_siret', columns: ['siret'])]
|
||||
#[ORM\Index(name: 'idx_qualimat_carrier_active', columns: ['is_active'])]
|
||||
class QualimatCarrier
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue(strategy: 'IDENTITY')]
|
||||
#[ORM\Column(type: 'bigint')]
|
||||
#[Groups(['qualimat:read'])]
|
||||
private ?string $id = null;
|
||||
|
||||
#[ORM\Column(length: 20)]
|
||||
#[Groups(['qualimat:read'])]
|
||||
private ?string $siret = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
#[Groups(['qualimat:read'])]
|
||||
private ?string $name = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
#[Groups(['qualimat:read'])]
|
||||
private ?string $address = null;
|
||||
|
||||
#[ORM\Column(name: 'postal_code', length: 10, nullable: true)]
|
||||
#[Groups(['qualimat:read'])]
|
||||
private ?string $postalCode = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
#[Groups(['qualimat:read'])]
|
||||
private ?string $city = null;
|
||||
|
||||
#[ORM\Column(length: 32, nullable: true)]
|
||||
#[Groups(['qualimat:read'])]
|
||||
private ?string $phone = null;
|
||||
|
||||
#[ORM\Column(length: 64, nullable: true)]
|
||||
#[Groups(['qualimat:read'])]
|
||||
private ?string $department = null;
|
||||
|
||||
#[ORM\Column(length: 32)]
|
||||
#[Groups(['qualimat:read'])]
|
||||
private ?string $status = null;
|
||||
|
||||
#[ORM\Column(name: 'validity_date', type: 'date_immutable', nullable: true)]
|
||||
#[Groups(['qualimat:read'])]
|
||||
private ?DateTimeImmutable $validityDate = null;
|
||||
|
||||
#[ORM\Column(name: 'is_active', options: ['default' => true])]
|
||||
#[Groups(['qualimat:read'])]
|
||||
#[SerializedName('isActive')]
|
||||
private bool $isActive = true;
|
||||
|
||||
// Colonne technique de synchro (soft-delete) — mappee pour completude, non
|
||||
// serialisee. Alimentee par app:qualimat:sync. columnDefinition pin la
|
||||
// precision TIMESTAMP(6) du DDL ERP-39 pour eviter un ALTER de schema:update
|
||||
// (le datetime_immutable par defaut mapperait sur TIMESTAMP(0)).
|
||||
#[ORM\Column(name: 'last_synced_at', type: 'datetime_immutable', columnDefinition: 'TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL')]
|
||||
private ?DateTimeImmutable $lastSyncedAt = null;
|
||||
|
||||
public function getId(): ?string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getSiret(): ?string
|
||||
{
|
||||
return $this->siret;
|
||||
}
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getAddress(): ?string
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
public function getPostalCode(): ?string
|
||||
{
|
||||
return $this->postalCode;
|
||||
}
|
||||
|
||||
public function getCity(): ?string
|
||||
{
|
||||
return $this->city;
|
||||
}
|
||||
|
||||
public function getPhone(): ?string
|
||||
{
|
||||
return $this->phone;
|
||||
}
|
||||
|
||||
public function getDepartment(): ?string
|
||||
{
|
||||
return $this->department;
|
||||
}
|
||||
|
||||
public function getStatus(): ?string
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
public function getValidityDate(): ?DateTimeImmutable
|
||||
{
|
||||
return $this->validityDate;
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->isActive;
|
||||
}
|
||||
|
||||
public function getLastSyncedAt(): ?DateTimeImmutable
|
||||
{
|
||||
return $this->lastSyncedAt;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user