koprogo_api/application/dto/unit_dto.rs
1use crate::domain::entities::UnitType;
2use rust_decimal::Decimal;
3use serde::{Deserialize, Serialize};
4use validator::Validate;
5
6#[derive(Debug, Deserialize, Validate, Clone, utoipa::ToSchema)]
7#[serde(deny_unknown_fields)]
8pub struct CreateUnitDto {
9 /// Story H15 — FK vers `acps.id` (anciennement `organization_id`).
10 /// Le lot dérive son ACP de son building parent (cf. #602) ; le scoping
11 /// org se fait via `acps.organization_id`.
12 ///
13 /// OPTIONNEL depuis 2026-08-27. Le champ était obligatoire, ce qui
14 /// contredisait la ligne au-dessus : si l'ACP se dérive du building, le
15 /// client n'a pas à la fournir. Deux conséquences mesurées :
16 ///
17 /// 1. Un `POST /units` sans `acp_id` était rejeté par serde AVANT
18 /// d'atteindre le handler, avec un corps en TEXTE BRUT
19 /// (« Json deserialize error: missing field `acp_id` »). Le garde-fou
20 /// `if dto.acp_id.is_empty()` du handler, qui rend un JSON propre,
21 /// était donc mort pour ce cas : il ne se déclenchait que sur une
22 /// chaîne vide explicite.
23 ///
24 /// 2. Tout appelant faisant `.json()` sur cette réponse recevait
25 /// « Unexpected token 'J' », un message qui ne dit rien du défaut.
26 /// C'est ce qui faisait échouer `02-ag-full-cycle` (gate de
27 /// caractérisation) et taire `seedConformantUnits` en `status=400`.
28 ///
29 /// Absent ou vide, l'ACP est désormais lue sur le building parent, qui
30 /// est la source de vérité. Fournie, elle est utilisée telle quelle :
31 /// le comportement des appelants existants est inchangé.
32 #[serde(default)]
33 pub acp_id: Option<String>,
34 pub building_id: String,
35
36 #[validate(length(min = 1))]
37 pub unit_number: String,
38
39 pub unit_type: UnitType,
40 pub floor: Option<i32>,
41
42 #[validate(range(min = 0.1))]
43 pub surface_area: f64,
44
45 /// Quote-part en millièmes (Decimal exact, range 0.1..=1000 enforced en domain).
46 pub quota: Decimal,
47}
48
49/// `deny_unknown_fields` : un `PUT /units/{id}` portant `owner_id` repondait
50/// 200 en jetant le champ, laissant croire que le lot venait d'etre rattache a
51/// un proprietaire. `units.owner_id` est DEPRECIE depuis la migration
52/// `20250127000000_refactor_owners_multitenancy` — la relation vit dans
53/// `unit_owners` (API `/unit-owners`), qui porte les quotites et les dates.
54/// Le refus explicite renvoie desormais vers la bonne route au lieu de perdre
55/// la donnee en silence.
56#[derive(Debug, Deserialize, Validate, Clone, utoipa::ToSchema)]
57#[serde(deny_unknown_fields)]
58pub struct UpdateUnitDto {
59 #[validate(length(min = 1))]
60 pub unit_number: String,
61
62 pub unit_type: UnitType,
63 pub floor: i32,
64
65 #[validate(range(min = 0.1))]
66 pub surface_area: f64,
67
68 /// Quote-part en millièmes (Decimal exact, range 0.1..=1000 enforced en domain).
69 pub quota: Decimal,
70}
71
72#[derive(Debug, Serialize, utoipa::ToSchema)]
73pub struct UnitResponseDto {
74 pub id: String,
75 pub building_id: String,
76 pub unit_number: String,
77 pub unit_type: UnitType,
78 pub floor: Option<i32>,
79 pub surface_area: f64,
80 pub quota: Decimal,
81 pub owner_id: Option<String>,
82}