Multi-owner Support Guide

Suivi fonctionnel et technique du module de copropriété multi-détenteurs introduit en janvier 2025.


Vue d’ensemble

  • Objectif : permettre à un lot (unit) d’avoir plusieurs copropriétaires simultanés, chacun avec une quote-part (%), un contact principal et un historique complet.

  • Backend : logique métier dans backend/src/application/use_cases/unit_owner_use_cases.rs et entité backend/src/domain/entities/unit_owner.rs.

  • Frontend : composants Svelte centrés sur frontend/src/components/UnitOwners.svelte, OwnerList.svelte, OwnerCreateModal.svelte et OwnerEditModal.svelte.

  • Tests : couverture via backend/tests/integration_unit_owner.rs (PostgreSQL réel) et scénarios BDD multi-tenant.


Modèle de données

La table unit_owners crée une relation many-to-many entre units et owners.

CREATE TABLE unit_owners (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    unit_id UUID NOT NULL REFERENCES units(id) ON DELETE CASCADE,
    owner_id UUID NOT NULL REFERENCES owners(id) ON DELETE CASCADE,
    ownership_percentage DOUBLE PRECISION NOT NULL CHECK (ownership_percentage > 0 AND ownership_percentage <= 1),
    start_date TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    end_date TIMESTAMPTZ,
    is_primary_contact BOOLEAN NOT NULL DEFAULT false,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    UNIQUE(unit_id, owner_id, end_date),
    CHECK (end_date IS NULL OR end_date > start_date)
);

Invariants clés

  • Quote-part : stockée sous forme décimale (0.25 = 25%). La création/mise à jour refuse toute valeur <= 0 ou > 1.

  • Somme des quotes-parts : UnitOwnerUseCases::add_owner_to_unit et update_ownership_percentage vérifient que la somme active ne dépasse jamais 100 %.

  • Temporalité : start_date est fixé à la création, end_date est rempli lors d’un retrait ou d’un transfert.

  • Contact principal : un seul copropriétaire actif peut être marqué is_primary_contact = true. La méthode interne unset_all_primary_contacts garantit l’unicité.


Règles métier & validations

Règle

Détails

Création d’un lien

Le lot et le copropriétaire doivent exister (UnitRepository, OwnerRepository).

Unicité active

Impossible d’ajouter deux fois le même copropriétaire tant que la relation actuelle n’est pas clôturée (end_date IS NULL).

Quote-part totale

Addition de toutes les quotes-part actives ≤ 1.0. Message d’erreur explicite si dépassement.

Mise à jour

Impossible de modifier une relation dont end_date est renseignée.

Transfert

Clôture automatique de l’ancien propriétaire et création d’une nouvelle relation avec la même quote-part.

Historique

Méthodes get_unit_ownership_history et get_owner_ownership_history exposent les relations passées (end_date non nul).


Endpoints API

Tous les endpoints sont préfixés par /api/v1.

Méthode

Endpoint

Description

GET

/units/{unit_id}/owners

Liste des copropriétaires actifs d’un lot.

GET

/units/{unit_id}/owners/history

Historique complet (actifs + inactifs).

GET

/units/{unit_id}/owners/total-percentage

Somme actuelle des quotes-parts.

POST

/units/{unit_id}/owners

Ajoute un copropriétaire (AddOwnerToUnitDto).

DELETE

/units/{unit_id}/owners/{owner_id}

Termine la relation (renseigne end_date).

PUT

/unit-owners/{relationship_id}

Met à jour la quote-part ou définit le contact principal.

POST

/units/{unit_id}/owners/transfer

Transfère une quote-part d’un propriétaire vers un autre.

GET

/owners/{owner_id}/units

Tous les lots actifs d’un copropriétaire.

GET

/owners/{owner_id}/units/history

Historique complet des lots possédés.

ℹ️ Les chemins d’update sont centralisés sur /unit-owners/{id} (contrainte technique : l’identifiant de relation est nécessaire pour conserver l’historique).

DTO principaux

// POST /units/{unit_id}/owners
{
  "owner_id": "5a4b1b4b-09f7-4b3f-a591-5c0f0ffbfa98",
  "ownership_percentage": 0.4,
  "is_primary_contact": true
}
// PUT /unit-owners/{relationship_id}
{
  "ownership_percentage": 0.25
}
// POST /units/{unit_id}/owners/transfer
{
  "from_owner_id": "0edab1e9-6764-4a22-9d71-6c8a7d72f7bc",
  "to_owner_id": "5c613076-2abf-4bf9-b6aa-05260cdc7246"
}

Réponses typiques (UnitOwnerResponseDto) :

{
  "id": "8b296dd4-87c5-4d49-8a0a-c44faa7a0f05",
  "unit_id": "f216217a-987c-4684-8796-79f6792a4a2a",
  "owner_id": "5a4b1b4b-09f7-4b3f-a591-5c0f0ffbfa98",
  "ownership_percentage": 0.4,
  "start_date": "2025-01-27T10:12:45.123456Z",
  "end_date": null,
  "is_primary_contact": true,
  "is_active": true,
  "created_at": "2025-01-27T10:12:45.123456Z",
  "updated_at": "2025-01-27T10:12:45.123456Z"
}

Frontend

Composant

Rôle

Endpoints consommés

UnitOwners.svelte

Vue embarquée sur la fiche lot (liste actifs, historique optionnel, somme des pourcentages, badges contact principal).

/units/{id}/owners, /units/{id}/owners/history, /owners/{owner_id}

OwnerList.svelte

Vue tableau paginée des copropriétaires avec accès à leurs lots (OwnerUnits.svelte).

/owners, /owners/{id}/units

OwnerCreateModal.svelte

Formulaire de création de copropriétaire (super-admin ⇒ choix organisation).

POST /owners

OwnerEditModal.svelte

Modification des coordonnées d’un copropriétaire.

PUT /owners/{id}

OwnerUnits.svelte

Vue détaillée des lots détenus par un copropriétaire (actifs + historique).

/owners/{owner_id}/units, /owners/{owner_id}/units/history

Le total des quotes-parts est affiché en temps réel et surligné en rouge si ≠ 100 %. Les badges « Contact principal » s’appuient sur is_primary_contact.


Scénarios d’usage

  1. Ajouter un nouveau copropriétaire principal

    1. Créer le copropriétaire via OwnerCreateModal.

    2. Sur la fiche lot, utiliser le bouton « Ajouter » (POST /units/{unit_id}/owners).

    3. Si is_primary_contact = true, les autres contacts sont automatiquement rétrogradés.

  2. Transférer une quote-part

    1. Appeler POST /units/{unit_id}/owners/transfer avec les identifiants source/cible.

    2. L’ancien lien est clôturé (end_date) et un nouveau lien est créé avec la même quote-part.

  3. Mettre à jour une quote-part suite à un acte notarié

    1. Récupérer l’id de relation (GET /units/{unit_id}/owners).

    2. Appeler PUT /unit-owners/{id} avec la nouvelle ownership_percentage.

    3. Vérifier que la somme (GET /units/{unit_id}/owners/total-percentage) reste = 1.0.

  4. Auditer l’historique d’un lot

    1. Consulter /units/{unit_id}/owners/history.

    2. Afficher les périodes start_dateend_date pour chaque copropriétaire.


Tests & Vérification

  • Unit tests : backend/src/domain/entities/unit_owner.rs (validation pourcentage, dates, contact principal).

  • Use cases : backend/src/application/use_cases/unit_owner_use_cases.rs (validation somme 100 %, transferts).

  • Intégration PostgreSQL : backend/tests/integration_unit_owner.rs.

  • API : couvertes par les tests BDD génériques (multi-tenancy) et les tests API owner/unit existants.

  • Frontend : interactions testées via Playwright (liste des copropriétaires dans frontend/tests/e2e/dashboards.spec.ts).

Pour valider manuellement :

# Rejouer la batterie de tests liée
make test-unit                # inclut les tests domaine
make test-integration         # exécute integration_unit_owner.rs
make test-bdd                 # scénarios Cucumber

Ressources liées

  • backend/migrations/20250127000000_refactor_owners_multitenancy.sql

  • backend/src/infrastructure/web/handlers/unit_owner_handlers.rs

  • docs/GIT_HOOKS.rst – prérequis qualité (pre-commit/pre-push)

  • README.md – aperçu des fonctionnalités multi-owner

  • CLAUDE.md – architecture et règlement de contribution


Checklist release

  • base de données migrée (sqlx migrate run)

  • cache SQLx préparé (cargo sqlx prepare)

  • tests verts (make test)

  • documentation Sphinx reconstruite (make docs-sphinx)

  • guides frontend/ops mis à jour