============================================ Shared Kernel — Couche Infrastructure ============================================ La couche infrastructure fournit les implémentations concrètes des interfaces domaine : modèles Django, repositories, bus d'événements, et utilitaires partagés. Module : ``src.share_kernel.infrastructure`` Modèles abstraits Django ======================== .. module:: src.share_kernel.infrastructure.django_base_model :synopsis: Modèles abstraits pour l'infrastructure Django .. class:: IdentifierModel(models.Model) Modèle abstrait avec clé primaire UUID. .. attribute:: id :type: UUIDField Clé primaire UUID4, générée automatiquement. .. class:: TimeStampedModel(models.Model) Modèle abstrait avec horodatage de création et modification. .. attribute:: created_at :type: DateTimeField Date de création (auto_now_add). .. attribute:: updated_at :type: DateTimeField Date de dernière modification (auto_now). .. class:: AuditModel(models.Model) Modèle abstrait avec traçabilité de création et modification. .. attribute:: created_by :type: ForeignKey Utilisateur ayant créé l'enregistrement. .. attribute:: updated_by :type: ForeignKey Utilisateur ayant effectué la dernière modification. .. class:: SoftDeleteModel(models.Model) Modèle abstrait avec suppression logique (soft delete). .. attribute:: is_deleted :type: BooleanField .. attribute:: deleted_at :type: DateTimeField .. attribute:: deleted_by :type: ForeignKey .. class:: BaseModel(IdentifierModel, TimeStampedModel) Combine UUID + horodatage. Base recommandée pour les modèles simples. .. class:: ERPBaseModel(IdentifierModel, TimeStampedModel, AuditModel) Combine UUID + horodatage + audit. Base recommandée pour les modèles ERP. **Exemple d'utilisation :** .. code-block:: python from src.share_kernel.infrastructure import BaseModel class Product(BaseModel): name = models.CharField(max_length=255) price = models.DecimalField(max_digits=10, decimal_places=2) class Meta: db_table = "products" Managers avec Soft Delete ========================= .. module:: src.share_kernel.infrastructure.managers :synopsis: QuerySets et Managers pour le soft delete .. class:: SoftDeleteQuerySet(QuerySet) QuerySet qui supporte la suppression logique. .. method:: alive() -> QuerySet Retourne uniquement les enregistrements non supprimés. .. method:: deleted() -> QuerySet Retourne uniquement les enregistrements supprimés. .. method:: soft_delete() -> int Marque les enregistrements comme supprimés (``is_deleted=True``). .. method:: restore() -> int Restaure les enregistrements supprimés. .. class:: SoftDeleteManager(Manager) Manager qui filtre automatiquement les enregistrements supprimés. .. method:: get_queryset() -> SoftDeleteQuerySet Retourne uniquement les enregistrements vivants. .. method:: all_with_deleted() -> SoftDeleteQuerySet Retourne tous les enregistrements, y compris les supprimés. Repository Django ================= .. module:: src.share_kernel.infrastructure.base_django_repo :synopsis: Implémentation du Repository Pattern avec Django ORM .. class:: DjangoRepository[TAggregate, TModel] Implémentation générique du pattern Repository utilisant Django ORM. .. method:: get_by_id(entity_id: UUID) -> TAggregate | None Récupère un agrégat par son ID. Retourne ``None`` si non trouvé. .. method:: get_by_id_or_raise(entity_id: UUID) -> TAggregate Récupère un agrégat ou lève ``EntityNotFoundException``. .. method:: save(aggregate: TAggregate) -> TAggregate Persiste un agrégat (création ou mise à jour). .. method:: delete(aggregate: TAggregate) -> None Supprime un agrégat. **Méthodes abstraites à implémenter :** .. method:: _to_aggregate(model: TModel) -> TAggregate :abstractmethod: Convertit un modèle Django en agrégat domaine. .. method:: _to_model(aggregate: TAggregate) -> TModel :abstractmethod: Convertit un agrégat domaine en modèle Django. **Exemple :** .. code-block:: python class ProductRepository(DjangoRepository[ProductAggregate, ProductModel]): model_class = ProductModel def _to_aggregate(self, model): return ProductAggregate(id=model.id, name=model.name, price=model.price) def _to_model(self, aggregate): return ProductModel(id=aggregate.id, name=aggregate.name, price=aggregate.price) Event Dispatcher ================ .. module:: src.share_kernel.infrastructure.event_dispatcher :synopsis: Dispatch des événements domaine depuis les agrégats .. class:: EventDispatcher Extrait les événements d'un agrégat et les publie sur le bus d'événements. .. method:: dispatch(aggregate: AggregateRoot) -> None Appelle ``aggregate.pull_domain_events()`` et publie chaque événement sur le bus d'événements global. **Exemple :** .. code-block:: python from src.share_kernel.infrastructure import EventDispatcher dispatcher = EventDispatcher() tenant_repo.save(tenant_aggregate) dispatcher.dispatch(tenant_aggregate) # Publie TenantCreated Données de référence (Master Data) ==================================== .. module:: src.share_kernel.infrastructure.reference_data :synopsis: Modèles de données de référence partagés (traduisibles via django-parler) Tous les modèles de référence héritent de ``TranslatableSharedModel`` et utilisent ``TranslatedFields`` pour les champs textuels. Les traductions sont stockées dans des tables ``*Translation`` distinctes. Voir :doc:`translations` pour le guide complet. .. class:: Currency(TranslatableSharedModel, ERPBaseModel) Devises ISO 4217. :Champs: ``code``, ``symbol``, ``decimal_places``, ``is_active`` :Champs traduits: ``name`` :Table: ``ref_currencies`` / ``ref_currencies_translation`` .. class:: Country(TranslatableSharedModel, ERPBaseModel) Pays ISO 3166-1. :Champs: ``code`` (alpha-2), ``code_alpha3`` (alpha-3), ``currency`` (FK), ``is_active`` :Champs traduits: ``name`` :Table: ``ref_countries`` / ``ref_countries_translation`` .. class:: Department(TranslatableSharedModel, ERPBaseModel) Départements organisationnels hiérarchiques. :Champs: ``code``, ``parent`` (self FK), ``manager`` (User FK), ``is_active`` :Champs traduits: ``name`` :Table: ``ref_departments`` / ``ref_departments_translation`` .. method:: get_ancestors() -> list[Department] Retourne la liste des départements parents (du plus proche au plus éloigné). .. class:: UnitOfMeasureCategory(TranslatableSharedModel, ERPBaseModel) Catégories d'unités de mesure. :Champs traduits: ``name`` :Table: ``ref_uom_categories`` / ``ref_uom_categories_translation`` .. class:: UnitOfMeasure(TranslatableSharedModel, ERPBaseModel) Unités de mesure avec conversion. :Champs: ``category`` (FK), ``symbol`` (unique), ``ratio``, ``is_reference``, ``rounding``, ``is_active`` :Champs traduits: ``name`` :Table: ``ref_units_of_measure`` / ``ref_units_of_measure_translation`` Utilitaires partagés ==================== .. module:: src.share_kernel.infrastructure.utils :synopsis: Fonctions utilitaires .. function:: generate_uuid() -> uuid.UUID Génère un nouveau UUID4. .. function:: validate_schema_name(name: str, regex: str = r"^[a-z][a-z0-9_]{2,62}$") -> bool Valide un nom de schéma PostgreSQL contre une regex configurable. .. function:: is_reserved_schema_name(name: str, reserved: list[str] | None = None) -> bool Vérifie si un nom de schéma est réservé (``public``, ``pg_catalog``, etc.).