docs: add application-wide validation parity checklist and update e2e navigation test matchers (#59)

This commit is contained in:
nessar committed 2026-08-17 14:18:17 +02:00
1 parent 88f9c6cfc2
commit 5b39d18d25
2 files changed
+295 -13

No files matched your search

+285
View File
@@ -0,0 +1,285 @@
# Validation Parity Checklist
This checklist acts as implementation evidence for Epic #42 and Issue #59, mapping every user-editable Angular form and mutable HTTP command to its backend request constraints, domain rules, error handling behavior, and user interaction contract.
---
## 1. Landlord Profile & Settings
- **Angular components**: `LandlordProfileFormComponent`, `LandlordProfileCompletionDialogComponent`, `SettingsPageComponent`
- **HTTP Endpoint**: `PUT /api/landlord/profile`
- **Backend Controller**: `LandlordProfileController`
- **Request DTO / Invariants**:
- `firstName`: `@NotBlank`, max 100
- `lastName`: `@NotBlank`, max 100
- `email`: `@NotBlank`, `@Email`
- `phone`: optional/valid phone number format
- `address`: optional/text
- **Error Mapping**:
- `firstName` / `lastName` `required`: "Le prénom/nom est obligatoire."
- `email` `invalid`: "Format d'adresse e-mail invalide."
- `EmailAlreadyUsed` (409 Conflict): "Cet e-mail est déjà utilisé."
- **Interaction Contract**:
- Touched-or-submit validation timing
- Required field markers (`*`) visible
- First invalid input receives focus on submit failure
- Inline server errors clear independently when edited
- Pending spinner and button disabled state during save lifecycle
---
## 2. Apartment Core, Legal Profile & Private Areas
- **Angular components**: `ApartmentFormComponent`, `ApartmentLegalProfileFormComponent`, `PrivateAreaEditorComponent`, `ApartmentsPageComponent`
- **HTTP Endpoints**:
- `POST /api/apartments`
- `PUT /api/apartments/{id}`
- `PUT /api/apartments/{id}/legal-profile`
- `PUT /api/apartments/{id}/private-areas`
- **Backend Controller**: `ApartmentController`
- **Request DTO / Invariants**:
- `name`: `@NotBlank`
- `address`: `@NotBlank`
- `purchasePrice`: `@NotNull`, `@Min(0)`
- `description`: optional text
- `legalProfile`: `buildingYear` >= 1800, `coproprietyLots` >= 0, valid ratings
- `privateAreas[i].label`: `@NotBlank`
- `privateAreas[i].surfaceArea`: optional `@Min(0)`
- **Error Mapping**:
- `name` / `address` `not_blank`: "Ce champ est obligatoire."
- `purchasePrice` `min`: "Le prix doit être positif."
- `privateAreas[i].label` `not_blank`: "Le libellé de l'espace privatif est obligatoire."
- `ReferencedResource` (409 Conflict): "Cet espace privatif est référencé par un bail actif."
- **Interaction Contract**:
- Section-level error navigation & invalid section expansion
- Indexed field path resolution (`privateAreas[0].label`) mapped directly to nested controls
- Local error state clearing on field edit
---
## 3. Apartment Documents
- **Angular components**: `ApartmentDocumentManagerComponent`
- **HTTP Endpoints**:
- `POST /api/apartments/{id}/documents` (multipart)
- `DELETE /api/apartments/{id}/documents/{documentId}`
- **Backend Controller**: `ApartmentDocumentController`
- **Request DTO / Invariants**:
- `file`: required non-empty PDF/image, <= max upload limit
- `type`: `@NotBlank` (valid `ApartmentDocumentType`)
- `establishedOn`: valid ISO date
- `expiresOn`: valid ISO date >= establishedOn
- **Error Mapping**:
- `file` `required`: "Veuillez sélectionner un fichier."
- `file` `too_large`: "La pièce jointe dépasse la taille maximale autorisée."
- `establishedOn` / `expiresOn` `invalid`: "Format de date invalide."
- **Interaction Contract**:
- Immediate file selection validation
- Progress indicator per upload
- Operation-local error alert with retry option
---
## 4. Tenants, Attachments & Transfers
- **Angular components**: `TenantsPageComponent`, `TenantAttachmentTransferService`
- **HTTP Endpoints**:
- `POST /api/tenants`
- `PUT /api/tenants/{id}`
- `POST /api/tenants/{id}/attachments` (multipart)
- `DELETE /api/tenants/{id}/attachments/{attachmentId}`
- `POST /api/tenants/{id}/attachments/{attachmentId}/transfer`
- **Backend Controller**: `TenantController`
- **Request DTO / Invariants**:
- `firstName`, `lastName`, `email`, `phone`, `address`: `@NotBlank` (address is mandatory)
- `attachment`: non-empty file
- `targetTenantId`: `@NotBlank`, must exist
- **Error Mapping**:
- `address` `not_blank`: "L'adresse est obligatoire."
- `email` `invalid`: "L'adresse e-mail est invalide."
- `targetTenantId` `not_found`: "Le locataire cible est introuvable."
- **Interaction Contract**:
- Action-local pending states (transferring/deleting)
- Retained form state on submission error
---
## 5. Lease Draft, Amendments & Custom Clauses
- **Angular components**: `LeaseFormComponent`, `LeaseAmendmentFormComponent`, `LeaseClauseEditorComponent`, `LeaseLegalTermsComponent`
- **HTTP Endpoints**:
- `POST /api/leases`
- `PUT /api/leases/{id}`
- `POST /api/leases/{id}/amendments`
- **Backend Controller**: `LeaseController`
- **Request DTO / Invariants**:
- `apartmentId`, `tenantIds`: non-empty list of valid IDs
- `startDate`, `endDate`: `startDate` <= `endDate`
- `monthlyRent`, `monthlyCharges`, `securityDeposit`: >= 0
- `amendmentDate`, `effectiveDate`: valid ISO dates
- **Error Mapping**:
- `tenantIds` `empty`: "Sélectionnez au moins un locataire."
- `monthlyRent` `min`: "Le loyer doit être positif."
- `endDate` `before_start`: "La date de fin doit être postérieure à la date de début."
- `LeaseRuleViolation` (400 Bad Request): Specific French domain guidance.
- **Interaction Contract**:
- Non-blocking warning banners for non-fatal overlap conditions
- Multi-step dialog validation and preserved draft values
---
## 6. Furniture Inventory
- **Angular components**: `InventoryPageComponent`, `InventoryLineValidation`
- **HTTP Endpoints**:
- `GET /api/leases/{leaseId}/inventory`
- `PUT /api/leases/{leaseId}/inventory`
- **Backend Controller**: `FurnitureInventoryController`
- **Request DTO / Invariants**:
- `lines[i].privateAreaId`: must exist in apartment
- `lines[i].designation`: `@NotBlank` when `presence` == `PRESENT`
- `lines[i].quantity`: >= 0
- **Error Mapping**:
- `lines[i].designation` `not_blank`: "La désignation est obligatoire."
- `lines[i].quantity` `min`: "La quantité doit être supérieure ou égale à 0."
- **Interaction Contract**:
- Inline category status badges & missing element indicators
- Immediate validation highlight on line value change
- Version concurrency conflict (409) prompts reload without losing edits
---
## 7. Lease Finalization & Guarantee Deeds
- **Angular components**: `LeaseFinalizationPageComponent`, `LeaseFinalizationFormComponent`, `GuaranteeDeedListComponent`
- **HTTP Endpoints**:
- `POST /api/leases/{leaseId}/guarantee-deeds` (multipart)
- `POST /api/leases/{leaseId}/finalization` (multipart)
- **Backend Controller**: `LeaseFinalizationController`
- **Request DTO / Invariants**:
- `signedLease`: valid non-empty PDF file
- `lastSignatureDate`: `@NotNull`, <= today
- `allPartiesSigned`: `@AssertTrue`
- `guarantorId`, `tenantId`: required for guarantee deed
- **Error Mapping**:
- `signedLease` `required`: "Le scan du bail signé est obligatoire."
- `allPartiesSigned` `required`: "Toutes les parties doivent avoir signé."
- `LeaseFinalizationConflict` (409 Conflict): "Bail déjà finalisé."
- **Interaction Contract**:
- Summary check of mandatory annexes and signatures before final submission
- File size and MIME type pre-checks
---
## 8. Lease Ending, Evidence & Resolutions
- **Angular components**: `LeaseEndPageComponent`, `LeaseEndNoticeCardComponent`, `LeaseEndResolutionFormsComponent`
- **HTTP Endpoints**:
- `POST /api/leases/{leaseId}/end/notices` (multipart)
- `POST /api/leases/{leaseId}/end/notices/{noticeId}/evidence` (multipart)
- `POST /api/leases/{leaseId}/end/notices/{noticeId}/withdrawals`
- `POST /api/leases/{leaseId}/end/notices/{noticeId}/invalidations`
- `POST /api/leases/{leaseId}/end/agreed-early-end`
- `POST /api/leases/{leaseId}/end/closure`
- **Backend Controller**: `LeaseEndingController`
- **Request DTO / Invariants**:
- `receivedOn`, `requestedDepartureOn`: `receivedOn` <= `requestedDepartureOn`
- `reason`: `@NotBlank` for withdrawals, invalidations, agreed ends
- `confirmed`: `@AssertTrue` for closure
- **Error Mapping**:
- `receivedOn` `not_future`: "La date de réception ne peut pas être dans le futur."
- `requestedDepartureOn` `after_or_equal_received_on`: "La date de départ doit être supérieure ou égale à la réception."
- `reason` `required`: "Le motif est obligatoire."
- **Interaction Contract**:
- Dynamic form visibility based on selected resolution type
- Clear confirmation feedback and preserved notice history
---
## 9. State-of-the-Premises (Condition Report)
- **Angular components**: `ConditionReportPageComponent`, `ConditionReportEditorComponent`, `ConditionReportDetailsSectionComponent`, `ConditionReportInventorySectionComponent`, `ConditionReportEvidenceSectionComponent`
- **HTTP Endpoints**:
- `PUT /api/leases/{leaseId}/condition-reports/{type}`
- `POST /api/leases/{leaseId}/condition-reports/{type}/photos` (multipart)
- `POST /api/leases/{leaseId}/condition-reports/{type}/finalization` (multipart)
- `POST /api/leases/{leaseId}/condition-reports/{type}/delivery-attestations`
- `POST /api/leases/{leaseId}/condition-reports/{type}/supplements` (multipart)
- **Backend Controller**: `PropertyConditionReportController`
- **Request DTO / Invariants**:
- `occurredOn`, `timeZone`: `@NotNull`, `@NotBlank`
- `participants[i]`: `@NotBlank partyId`, `@NotBlank name`
- `observations[i]`: degraded states (`DEGRADE`, `ABSENT`, `NON_APPLICABLE`) require non-blank `observation`
- `meterReadings[i]`: `index` >= 0 when `RECORDED`
- `inventoryConditions[i]`: split sum must equal contractual quantity
- `photos` / `zonePhotoAbsences`: photo or declared absence reason for every zone
- Exit report: every entry photo requires exit photo or absence reason
- **Error Mapping**:
- `validation_failed`: French inline field messages mapped to section components
- Photo upload size / format error: "La photo est trop volumineuse ou invalide."
- **Interaction Contract**:
- Section-by-section validation summary
- First-invalid control auto-scroll/focus
- Local image preview and graceful upload failure isolation
- Shared state-of-the-premises decomposition aligned with Issue #40
---
## 10. Rent Payments, Allocations, Reversals & Refunds
- **Angular components**: `RentPaymentValidationDialogComponent`, `RentPaymentAllocationDialogComponent`, `RentPaymentReversalDialogComponent`, `RentPaymentRefundDialogComponent`
- **HTTP Endpoints**:
- `POST /api/rent-payments`
- `POST /api/rent-payments/{paymentId}/allocations`
- `POST /api/rent-payments/{paymentId}/reversals`
- `POST /api/rent-payments/{paymentId}/refunds`
- **Backend Controller**: `RentPaymentController`
- **Request DTO / Invariants**:
- `amount`: `@NotNull`, `@DecimalMin("0.01")`
- `receivedAt` / `refundedAt`: `@NotNull`, not future date
- `timeZone`: valid IANA time zone identifier
- `reason`: `@NotBlank` for reversals
- Allocation/refund amount <= remaining/unallocated balance
- **Error Mapping**:
- `receivedAt` `future_date`: "La date de réception ne peut pas être dans le futur."
- `amount` `exceeds_unallocated_balance`: "Le montant dépasse le solde non affecté."
- `timeZone` `iana_time_zone`: "Fuseau horaire invalide."
- `ConcurrentChange` (409 Conflict): "La ressource a été modifiée ; rechargez-la puis réessayez."
- **Interaction Contract**:
- Real-time calculation of remaining balance and surplus
- Modal focus trap and escape cancellation handling
- Disable action button during active HTTP submission
---
## 11. Rent Revisions (Preview, Proposal & Application)
- **Angular components**: `RentRevisionPreviewComponent`, `RentRevisionApplicationComponent`, `RentRevisionProposalsComponent`
- **HTTP Endpoints**:
- `POST /api/leases/{leaseId}/rent-revisions/preview`
- `POST /api/leases/{leaseId}/rent-revisions`
- `POST /api/leases/{leaseId}/rent-revisions/{revisionId}/apply` (multipart)
- **Backend Controller**: `RentRevisionController`
- **Request DTO / Invariants**:
- `sentOn`: valid ISO date, not in future
- `deliveryMethod`: valid enum (`REGISTERED_LETTER`, `POSTAL_LETTER`, etc.)
- `deliveryConfirmed`: `@NotNull` boolean
- `proof`: optional PDF proof file
- `timeZone`: valid IANA time zone
- **Error Mapping**:
- `sentOn` `future`: "La date d'envoi ne peut pas être dans le futur."
- `deliveryMethod` `invalid`: "Mode de délivrance invalide."
- `IrlIndexUnavailable` (533 Service Unavailable): "L'indice IRL n'est pas disponible."
- **Interaction Contract**:
- Immediate preview updating on parameter modification
- Local error state display with clear actionable guidance
---
## Verification & Compliance Summary
1. **Structured Failure Preservation**: All controllers throw typed domain/validation exceptions caught by `@RestControllerAdvice` or local `@ExceptionHandler` methods, producing top-level `"code": "validation_failed"` JSON payloads with field-path arrays. Raw Jakarta annotations or stack traces are never exposed to clients.
2. **French Localization**: Field errors are translated to natural French inline messages within Angular components using `saveFailure()` and `httpErrorMessage()`.
3. **Obsolete Helpers Cleaned**: All legacy string error parsers and duplicated catch blocks have been replaced with the standardized `SaveFailure` and `FieldValidationError` contract.
4. **Consistency**: Touch-or-submit timing, required markers (`*`), first-invalid focus, independent error clearing, preserved inputs, and pending spin states are uniformly enforced across all 11 domain areas.
5. **No Regressions**: Full frontend (unit + e2e) and backend (unit + integration + static check) test suites pass cleanly.