types.ts - Types TypeScript
Localisation : frontend/src/lib/types.ts
Définit toutes les interfaces TypeScript pour les entités du domaine et les types utilitaires.
Énumérations
UserRole
Rôles utilisateurs dans la plateforme SaaS multi-tenant.
export enum UserRole {
SUPERADMIN = "superadmin", // Administrateur plateforme
SYNDIC = "syndic", // Gestionnaire de copropriété
ACCOUNTANT = "accountant", // Comptable (consultation)
OWNER = "owner" // Copropriétaire (consultation)
}
Hiérarchie des Permissions :
SUPERADMIN (niveau 4) : Gestion organisations, utilisateurs, configuration
SYNDIC (niveau 3) : Gestion complète des immeubles, charges, copropriétaires
ACCOUNTANT (niveau 2) : Consultation comptable, génération rapports
OWNER (niveau 1) : Consultation uniquement (ses lots, charges)
Interfaces Entités Domaine
User
Utilisateur de la plateforme.
export interface User {
id: string;
email: string;
firstName: string;
lastName: string;
role: UserRole;
organizationId?: string; // Multi-tenant
buildingIds?: string[]; // Immeubles accessibles
}
Champs :
id: UUIDemail: Email unique (authentification)firstName/lastName: Nom completrole: Rôle utilisateur (UserRole)organizationId: ID organisation (multi-tenant ASBL)buildingIds: Liste immeubles accessibles (isolation données)
Building
Immeuble en copropriété.
export interface Building {
id: string;
name: string;
address: string;
city: string;
postal_code: string;
country: string;
total_units: number;
construction_year?: number;
created_at?: string;
updated_at?: string;
}
Champs :
id: UUIDname: Nom de l’immeuble (ex: “Résidence du Parc”)address/city/postal_code/country: Adresse complètetotal_units: Nombre total de lotsconstruction_year: Année de construction (optionnel)created_at/updated_at: Timestamps ISO 8601
Owner
Copropriétaire.
export interface Owner {
id: string;
first_name: string;
last_name: string;
email: string;
phone?: string;
created_at?: string;
}
Champs :
id: UUIDfirst_name/last_name: Nom completemail: Email de contactphone: Téléphone (optionnel)created_at: Timestamp création
⚠️ GDPR : Les données Owner sont sensibles (email, téléphone).
Unit
Lot dans un immeuble.
export interface Unit {
id: string;
building_id: string;
unit_number: string;
floor: number;
surface_area: number;
ownership_share: number;
unit_type: "Apartment" | "Parking" | "Storage";
owner_id?: string;
}
Champs :
id: UUIDbuilding_id: Référence Buildingunit_number: Numéro de lot (ex: “A-12”)floor: Étage (0 = RDC, -1 = Sous-sol)surface_area: Surface en m²ownership_share: Quote-part en millièmes (ex: 45 = 45/1000)unit_type: Type de lot (Apartment, Parking, Storage)owner_id: Référence Owner (optionnel si vacant)
Expense
Charge de copropriété.
export interface Expense {
id: string;
building_id: string;
description: string;
amount: number;
expense_date: string;
due_date: string;
category: "Maintenance" | "Repair" | "Insurance" |
"Utilities" | "Management" | "Other";
payment_status: "Pending" | "Paid" | "Overdue" | "Cancelled";
paid_date?: string;
}
Champs :
id: UUIDbuilding_id: Référence Buildingdescription: Description de la chargeamount: Montant en centimes (ex: 12050 = 120.50€)expense_date: Date de la dépense (ISO 8601)due_date: Date d’échéance (ISO 8601)category: Catégorie comptablepayment_status: Statut de paiementpaid_date: Date de paiement effectif (optionnel)
Catégories :
Maintenance : Entretien courant
Repair : Réparations
Insurance : Assurances
Utilities : Charges courantes (eau, électricité)
Management : Honoraires syndic
Other : Autres dépenses
Types Pagination
PageResponse<T>
Réponse paginée du backend.
export interface PageResponse<T> {
data: T[];
pagination: PaginationMeta;
}
Structure :
{
"data": [
{ "id": "...", "name": "..." },
{ "id": "...", "name": "..." }
],
"pagination": {
"current_page": 1,
"per_page": 20,
"total_items": 157,
"total_pages": 8,
"has_next": true,
"has_previous": false
}
}
PaginationMeta
Métadonnées de pagination.
export interface PaginationMeta {
current_page: number;
per_page: number;
total_items: number;
total_pages: number;
has_next: boolean;
has_previous: boolean;
}
PageRequest
Paramètres de requête paginée.
export interface PageRequest {
page?: number; // Défaut: 1
per_page?: number; // Défaut: 20
}
Exemple d’utilisation :
const response = await api.get<PageResponse<Building>>(
`/buildings?page=${page}&per_page=${perPage}`
);
const buildings = response.data;
const { current_page, total_pages, has_next } = response.pagination;
Helpers Permissions
hasPermission(user, requiredRole)
Vérifie si l’utilisateur a le niveau de permission requis.
export const hasPermission = (
user: User | null,
requiredRole: UserRole
): boolean => {
if (!user) return false;
const roleHierarchy = {
[UserRole.SUPERADMIN]: 4,
[UserRole.SYNDIC]: 3,
[UserRole.ACCOUNTANT]: 2,
[UserRole.OWNER]: 1
};
return roleHierarchy[user.role] >= roleHierarchy[requiredRole];
};
Exemple :
// SUPERADMIN peut tout faire
hasPermission(superadminUser, UserRole.OWNER); // true
// OWNER ne peut pas accéder aux fonctions SYNDIC
hasPermission(ownerUser, UserRole.SYNDIC); // false
// ACCOUNTANT peut accéder aux fonctions OWNER
hasPermission(accountantUser, UserRole.OWNER); // true
canAccessBuilding(user, buildingId)
Vérifie si l’utilisateur peut accéder à un immeuble spécifique.
export const canAccessBuilding = (
user: User | null,
buildingId: string
): boolean => {
if (!user) return false;
if (user.role === UserRole.SUPERADMIN) return true;
return user.buildingIds?.includes(buildingId) ?? false;
};
Logique :
SUPERADMIN : Accès à tous les immeubles
Autres rôles : Accès uniquement aux immeubles dans
buildingIds
Exemple :
<script lang="ts">
import { authStore } from '../stores/auth';
import { canAccessBuilding } from '../lib/types';
$: user = $authStore.user;
$: canEdit = canAccessBuilding(user, building.id);
</script>
{#if canEdit}
<button on:click={editBuilding}>Modifier</button>
{/if}
Utilisation dans Components
Import et Typage :
import type { Building, Owner, PageResponse } from '../lib/types';
import { UserRole, hasPermission } from '../lib/types';
Variables Typées :
let buildings: Building[] = [];
let owners: Owner[] = [];
let selectedBuilding: Building | null = null;
Paramètres de Fonction :
async function createBuilding(data: Partial<Building>) {
const building = await api.post<Building>('/buildings', data);
return building;
}
Reactive Statements :
$: isSyndic = hasPermission($authStore.user, UserRole.SYNDIC);
$: canManage = canAccessBuilding($authStore.user, buildingId);
Alignement Backend
Ces types doivent correspondre exactement aux DTOs du backend :
Building↔backend/src/application/dto/building_dto.rsOwner↔backend/src/application/dto/owner_dto.rsUnit↔backend/src/application/dto/unit_dto.rsExpense↔backend/src/application/dto/expense_dto.rs
⚠️ Important : Toute modification backend nécessite mise à jour frontend.
Génération Automatique
Pour éviter désynchronisation, envisager génération automatique depuis OpenAPI :
# Générer types depuis openapi.json
npx openapi-typescript ./openapi.json --output ./src/lib/types.ts
Alternatives :
openapi-generator : Génération complète client + types
tRPC : Types partagés TypeScript (nécessite backend Node.js)
GraphQL Codegen : Si migration vers GraphQL
Tests
// tests/unit/types.test.ts
import { describe, it, expect } from 'vitest';
import { hasPermission, UserRole } from '../src/lib/types';
describe('hasPermission', () => {
it('SUPERADMIN has all permissions', () => {
const user = { role: UserRole.SUPERADMIN } as User;
expect(hasPermission(user, UserRole.OWNER)).toBe(true);
expect(hasPermission(user, UserRole.SYNDIC)).toBe(true);
});
it('OWNER cannot access SYNDIC functions', () => {
const user = { role: UserRole.OWNER } as User;
expect(hasPermission(user, UserRole.SYNDIC)).toBe(false);
});
});
Références
Backend DTOs :
backend/src/application/dto/API Client :
frontend/src/lib/api.tsAuth Store :
frontend/src/stores/auth.tsComponents :
frontend/src/components/