docs: add application-wide validation parity checklist and update e2e navigation test matchers (#59)
This commit is contained in:
1 parent
88f9c6cfc2
commit
5b39d18d25
2 files changed
+295
-13
No files matched your search
@@ -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.
|
||||
Reference in new issue
Block a user