Complete validation parity audit
This commit is contained in:
1 parent
c10a4b2fb2
commit
15c30986aa
39 files changed
+1328
-372
No files matched your search
+268
-230
@@ -1,285 +1,323 @@
|
||||
# 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.
|
||||
This document accounts for the application's user-editable Angular forms and mutable rental HTTP
|
||||
commands for Epic #42 and Issue #59. It records the implemented contracts; it is not a claim that
|
||||
unrecorded CI or local test gates passed.
|
||||
|
||||
---
|
||||
The backend remains authoritative. Angular mirrors only important save-time constraints that can
|
||||
give immediate, actionable guidance. The implementation uses the existing Jakarta validation,
|
||||
domain rules, Angular forms, and `SaveFailure` contract; it does not add a generated schema, shared
|
||||
validation framework, state-management mechanism, or dependency.
|
||||
|
||||
## 1. Landlord Profile & Settings
|
||||
## Shared failure and interaction contract
|
||||
|
||||
- **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
|
||||
- Request validation failures use HTTP 400 and
|
||||
`{ "code": "validation_failed", "errors": [{ "field": "...", "code": "..." }] }`.
|
||||
- Conflicts use HTTP 409, oversized uploads use HTTP 413, unavailable IRL data uses HTTP 503,
|
||||
and unexpected failures use HTTP 500. HTTP 422 is not part of this contract.
|
||||
- Angular maps known field/code pairs to French inline guidance. Unknown fields/codes, network
|
||||
failures, conflicts without recognized guidance, and unexpected failures retain a safe visible
|
||||
operation-level fallback.
|
||||
- Save forms retain entered values after failure, prevent duplicate submission while their own
|
||||
operation is pending, and clear a server field error when that field is edited.
|
||||
- Required markers, touched-or-submit timing, and first-invalid focus are implemented by each form
|
||||
because the controls and nested paths differ; there is no generated or cross-stack validation
|
||||
layer.
|
||||
|
||||
---
|
||||
## 1. Landlord profile and settings
|
||||
|
||||
## 2. Apartment Core, Legal Profile & Private Areas
|
||||
- **Angular**: `LandlordProfileForm`, `LandlordProfileCompletionDialog`, `SettingsPage`
|
||||
- **Mutable command**: `PUT /api/landlord-profile`
|
||||
- **Backend**: `LandlordProfileController`, `LandlordProfileApi.Request`
|
||||
- **Authoritative constraints**:
|
||||
- `firstName`, `lastName`, `email`, `phone`, and `address` are `@NotBlank`.
|
||||
- `email` is `@Email`.
|
||||
- `phoneValid` is an `@AssertTrue` validation of the shared `PhoneNumber` domain type.
|
||||
- **Angular mirror**: the five required controls, email syntax, and phone syntax are checked before
|
||||
save; known server errors are shown inline and focused. Settings and the completion dialog keep
|
||||
operation-local loading/saving state.
|
||||
|
||||
- **Angular components**: `ApartmentFormComponent`, `ApartmentLegalProfileFormComponent`, `PrivateAreaEditorComponent`, `ApartmentsPageComponent`
|
||||
- **HTTP Endpoints**:
|
||||
There is no `/api/landlord/profile` route and no 100-character DTO limit on the name fields.
|
||||
|
||||
## 2. Apartments, legal profile, and private areas
|
||||
|
||||
- **Angular**: `ApartmentForm`, `ApartmentLegalProfileForm`, `PrivateAreaEditor`, `ApartmentsPage`
|
||||
- **Mutable commands**:
|
||||
- `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
|
||||
- `PUT /api/apartments/{apartmentId}`
|
||||
- `DELETE /api/apartments/{apartmentId}`
|
||||
- **Backend**: `ApartmentController`, `ApartmentApi.Request`
|
||||
- **Authoritative constraints**:
|
||||
- `name` and `address` are `@NotBlank`.
|
||||
- `purchasePrice` is `@NotNull @Positive`.
|
||||
- `privateAreas` is non-null; every item has a non-blank `id` and `label`, non-null `family` and
|
||||
`subtype`, and a family/subtype compatibility rule.
|
||||
- Legal-profile surface and annual energy cost are positive or zero when present. Providing an
|
||||
energy cost requires a reference year between 1900 and 2100. Construction period, energy
|
||||
rating, and French department code use explicit validity rules.
|
||||
- **Angular mirror**: required core fields, strictly positive purchase price, legal-profile numeric
|
||||
bounds and department code, and private-area indexed constraints are checked before save.
|
||||
Structured paths such as `privateAreas[0].label` focus and clear the matching row.
|
||||
|
||||
---
|
||||
Legal profile and private areas are part of the apartment request. There are no separate
|
||||
`/legal-profile` or `/private-areas` write endpoints, and the contract has no `buildingYear`,
|
||||
`coproprietyLots`, or private-area `surfaceArea` field.
|
||||
|
||||
## 3. Apartment Documents
|
||||
## 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
|
||||
- **Angular**: `ApartmentDocumentManager`
|
||||
- **Mutable commands**:
|
||||
- `POST /api/apartments/{apartmentId}/documents` (multipart)
|
||||
- `DELETE /api/apartments/{apartmentId}/documents/{documentId}`
|
||||
- **Backend**: `ApartmentDocumentController`, `ApartmentDocumentApi.UploadRequest`
|
||||
- **Multipart contract**:
|
||||
- `file`: required, non-empty PDF whose content is recognized as PDF.
|
||||
- `type`: required valid `ApartmentDocumentType` code.
|
||||
- `establishedOn`: required ISO local date.
|
||||
- `expiresOn`: optional ISO local date, not before `establishedOn`.
|
||||
- The attachment limit is configurable through `lmnp.attachments.max-size` and defaults to
|
||||
250 MB; Spring's multipart request limit defaults to 251 MB.
|
||||
- **Angular mirror**: PDF content, required type/file/establishment date, and date ordering are
|
||||
checked before upload. Upload and delete expose operation-local pending and error states.
|
||||
|
||||
---
|
||||
Apartment documents are PDF-only; image uploads belong to condition reports.
|
||||
|
||||
## 4. Tenants, Attachments & Transfers
|
||||
## 4. Tenants and attachments
|
||||
|
||||
- **Angular components**: `TenantsPageComponent`, `TenantAttachmentTransferService`
|
||||
- **HTTP Endpoints**:
|
||||
- **Angular**: `TenantForm`, `TenantsPage`, `TenantAttachmentTransferService`
|
||||
- **Mutable commands**:
|
||||
- `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
|
||||
- `PUT /api/tenants/{tenantId}`
|
||||
- `DELETE /api/tenants/{tenantId}`
|
||||
- `POST /api/tenants/{tenantId}/attachments` (multipart)
|
||||
- `DELETE /api/tenants/{tenantId}/attachments/{attachmentId}`
|
||||
- **Backend**: `TenantController`, `TenantApi.Request`
|
||||
- **Authoritative constraints**:
|
||||
- `firstName`, `lastName`, `email`, and `address` are `@NotBlank`.
|
||||
- `email` is `@Email`.
|
||||
- `phone` is optional; when present it must satisfy the shared `PhoneNumber` rule.
|
||||
- Attachment `file` is required and non-empty. Its content type is not restricted to PDF, and its
|
||||
size is bounded by `lmnp.attachments.max-size` (250 MB by default).
|
||||
- **Angular mirror**: required identity/address fields, email, and optional-phone syntax are checked
|
||||
before save. Tenant values survive a failed request. Attachment upload reports per-tenant
|
||||
progress; upload, download, and delete use action-local pending/error state.
|
||||
|
||||
---
|
||||
`TenantAttachmentTransferService` coordinates upload, download, and delete state; there is no
|
||||
attachment-transfer endpoint or `targetTenantId` request.
|
||||
|
||||
## 5. Lease Draft, Amendments & Custom Clauses
|
||||
## 5. Lease drafts, amendments, clauses, and document revisions
|
||||
|
||||
- **Angular components**: `LeaseFormComponent`, `LeaseAmendmentFormComponent`, `LeaseClauseEditorComponent`, `LeaseLegalTermsComponent`
|
||||
- **HTTP Endpoints**:
|
||||
- **Angular**: `LeaseForm`, `GuarantorForm`, `LeaseClauseEditor`, `LeaseLegalTerms`,
|
||||
`LeaseAmendmentForm`, `LeasesPage`
|
||||
- **Mutable commands**:
|
||||
- `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
|
||||
- `PUT /api/leases/{leaseId}`
|
||||
- `DELETE /api/leases/{leaseId}`
|
||||
- `POST /api/leases/{leaseId}/amendments`
|
||||
- `POST /api/leases/{leaseId}/revisions`
|
||||
- **Backend**: `LeaseController`, `LeaseApi.DraftRequest`, `LeaseApi.AmendmentRequest`,
|
||||
`LeaseNestedRequestValidator`
|
||||
- **Authoritative draft constraints**:
|
||||
- `apartmentId` is non-blank and `tenantIds` is a non-empty list of distinct, non-blank IDs.
|
||||
- `startDate`, `monthlyRent`, `monthlyCharges`, and `securityDeposit` are non-null.
|
||||
- Rent is strictly positive; charges and deposit are positive or zero; `dueDay` is 1 through 28.
|
||||
- Guarantor first name, last name, and email are required; email syntax is validated.
|
||||
- At most 20 special clauses and 20 parameters per clause are accepted. Parameter values are at
|
||||
most 500 characters, custom clause text 4,000, supplement justification 2,000, IRL series ID
|
||||
100, recent-work description 4,000, and other terms 8,000.
|
||||
- Domain services verify referenced resources, lease-type/legal-term coherence, special-clause
|
||||
catalog rules, and occupancy conflicts.
|
||||
- **Authoritative amendment constraints**:
|
||||
- `effectiveDate`, IANA `timeZone`, distinct non-empty `tenantIds`, `endDate`, `monthlyRent`, and
|
||||
`monthlyCharges` are required.
|
||||
- `effectiveDate` cannot be in the past; `endDate` cannot precede it.
|
||||
- Amount and due-day constraints match the draft request.
|
||||
- **Angular mirror**: important required fields, amount/day bounds, dates, guarantor contact data,
|
||||
clause inputs, and conditional legal terms are checked before submit. Known nested server paths
|
||||
are routed to the core form, guarantor, legal-terms, or clause editor without clearing the draft.
|
||||
- **Document revision command**: generating a revision has no editable request payload; its
|
||||
operation-local pending/error state is separate from lease editing and downloads.
|
||||
|
||||
---
|
||||
## 6. Furniture inventory
|
||||
|
||||
## 6. Furniture Inventory
|
||||
- **Angular**: `InventoryPage`, `InventoryLineCard`, `inventoryLineErrors`
|
||||
- **Mutable command**: `PUT /api/leases/{leaseId}/inventory`
|
||||
- **Backend**: `FurnitureInventoryController`, `FurnitureInventoryRequestValidator`
|
||||
- **Authoritative constraints**:
|
||||
- `version` and `lines` are required; version is non-negative.
|
||||
- Every line requires a unique non-blank ID, valid apartment `privateAreaId`, non-blank
|
||||
designation, equipment kind, and non-negative integer quantity.
|
||||
- A regulatory category is unique. Its equipment classification cannot change; present,
|
||||
absent, and undeclared quantities must agree with presence.
|
||||
- **Angular mirror**: the same editable line constraints produce indexed errors such as
|
||||
`lines[0].quantity`; the first invalid control is focused and editing clears that line's server
|
||||
error. Save is disabled only for the active inventory request.
|
||||
|
||||
- **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 and guarantee deeds
|
||||
|
||||
---
|
||||
|
||||
## 7. Lease Finalization & Guarantee Deeds
|
||||
|
||||
- **Angular components**: `LeaseFinalizationPageComponent`, `LeaseFinalizationFormComponent`, `GuaranteeDeedListComponent`
|
||||
- **HTTP Endpoints**:
|
||||
- **Angular**: `LeaseFinalizationPage`, `LeaseFinalizationForm`, `GuaranteeDeedList`
|
||||
- **Mutable commands**:
|
||||
- `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
|
||||
- **Backend**: `LeaseFinalizationController`, `LeaseFinalizationApi`
|
||||
- **Guarantee-deed multipart contract**: optional request parameters are validated together as
|
||||
`requirementIndex`, `guarantorId`, `tenantId`, and true `attestation`; `file` is a required,
|
||||
valid, non-empty PDF.
|
||||
- **Finalization multipart contract**:
|
||||
- `signedLease` is a required valid PDF.
|
||||
- JSON part `metadata` contains positive `revisionNumber`, `lastSignatureDate`,
|
||||
`allPartiesSigned`, `revisionMatches`, `confirmedWarningCodes`,
|
||||
`entryInventoryDiscrepancies`, `inventoryCorrections`, and
|
||||
`allInventoryCorrectionsInitialed`.
|
||||
- Boolean attestations and inventory corrections must be internally consistent; the service
|
||||
checks signature date, required deeds, warnings, and revision identity.
|
||||
- PDF size uses `lmnp.attachments.max-size` (250 MB by default).
|
||||
- **Angular mirror**: file validity, attestations, missing deeds, warning confirmations, and
|
||||
conditional inventory corrections are checked before the confirmation dialog. Upload and final
|
||||
submission use separate pending/error state and preserve correction input after failure.
|
||||
|
||||
---
|
||||
## 8. Lease ending, evidence, and resolution
|
||||
|
||||
## 8. Lease Ending, Evidence & Resolutions
|
||||
|
||||
- **Angular components**: `LeaseEndPageComponent`, `LeaseEndNoticeCardComponent`, `LeaseEndResolutionFormsComponent`
|
||||
- **HTTP Endpoints**:
|
||||
- **Angular**: `LeaseEndPage`, `LeaseEndNoticeForm`, `LeaseEndNoticeCard`,
|
||||
`LeaseEndResolutionForms`
|
||||
- **Mutable commands**:
|
||||
- `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
|
||||
- **Authoritative constraints**:
|
||||
- A notice supplies IANA `timeZone`, `receivedOn`, `requestedDepartureOn`, delivery mode, and the
|
||||
applicable confirmations. Receipt cannot be in the future and departure cannot precede it.
|
||||
- Evidence is optional only with explicit missing-evidence confirmation; uploaded evidence is
|
||||
non-empty and bounded by the configured attachment limit.
|
||||
- Withdrawal, invalidation, and agreed-end reasons are non-blank and at most 500 characters.
|
||||
- Closure requires a valid time zone and true `confirmed`.
|
||||
- Domain rules enforce the lease phase, notice history, eligible dates, and exit-report
|
||||
prerequisites.
|
||||
- **Angular mirror**: conditional delivery/evidence fields, dates, reasons, and closure confirmation
|
||||
are validated where their controls live. Each notice/action key has independent confirmation,
|
||||
pending, structured field-error, and operation-error state.
|
||||
|
||||
---
|
||||
## 9. State of the premises
|
||||
|
||||
## 9. State-of-the-Premises (Condition Report)
|
||||
|
||||
- **Angular components**: `ConditionReportPageComponent`, `ConditionReportEditorComponent`, `ConditionReportDetailsSectionComponent`, `ConditionReportInventorySectionComponent`, `ConditionReportEvidenceSectionComponent`
|
||||
- **HTTP Endpoints**:
|
||||
- **Angular**: `ConditionReportPage`, `ConditionReportEditor`,
|
||||
`ConditionReportDetailsSection`, `ConditionReportInventorySection`,
|
||||
`ConditionReportEvidenceSection`
|
||||
- **Mutable commands**:
|
||||
- `PUT /api/leases/{leaseId}/condition-reports/{type}`
|
||||
- `POST /api/leases/{leaseId}/condition-reports/{type}/photos` (multipart)
|
||||
- `DELETE /api/leases/{leaseId}/condition-reports/{type}/photos/{reference}?version=...`
|
||||
- `POST /api/leases/{leaseId}/condition-reports/{type}/revisions?version=...`
|
||||
- `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
|
||||
- `POST /api/leases/{leaseId}/condition-reports/ENTRY/supplements` (multipart)
|
||||
- **Draft authority**:
|
||||
- Non-negative `version`, `occurredOn`, IANA `timeZone`, and every nested list are required.
|
||||
- Participants, zones, observations, meters, access means, inventory splits, photo absences, and
|
||||
entry-photo dispositions have explicit required/type/non-negative constraints.
|
||||
- The draft validator enforces conditional observations, represented-party details, forwarding
|
||||
addresses on exit, and exact inventory split totals. Finalization readiness separately requires
|
||||
one photo or audited absence per zone and, for exit reports, a disposition for every entry
|
||||
photo.
|
||||
- **Photo multipart contract**:
|
||||
- Required non-blank `zoneId` parameter and required `file` part.
|
||||
- Supported content: JPEG, PNG, WebP, HEIC, or HEIF, checked against both MIME type and file
|
||||
signature.
|
||||
- The configurable photo limit defaults to 40 MB and is returned by the report response for
|
||||
frontend pre-checks.
|
||||
- **Finalization multipart contract**: required valid PDF `file` plus JSON `metadata` containing a
|
||||
non-negative `version` and true `attestation` and `allPartiesSigned`; the PDF limit defaults to
|
||||
250 MB and is also returned by the report response.
|
||||
- **Supplement multipart contract**: entry reports only; required valid PDF `signedPdf`, optional
|
||||
photo parts, and JSON `metadata` containing matching `photoZoneIds`, `requestedOn`,
|
||||
non-blank `acceptedAdditions`, `supplementType`, `lateWarningConfirmed`, optional
|
||||
`heatingPeriodStartedOn`, and optional `reason`. The request date cannot precede the report or be
|
||||
in the future; a heating-period start cannot follow the request. Photo/PDF limits match the
|
||||
upload types above.
|
||||
- **Angular mirror**: the editor checks the important nested draft rules before save; evidence
|
||||
controls pre-check types and response-provided limits. Structured server paths are mapped to the
|
||||
owning section, preserve the draft, focus the first correction, and clear independently.
|
||||
Saving, photo work, revision generation, finalization, delivery attestation, supplement upload,
|
||||
comparison loading, and downloads use separate busy states.
|
||||
|
||||
---
|
||||
This is the Issue #40 overlap: the existing decomposed condition-report editor and section
|
||||
components own the UI; Issue #59 adds no duplicate state-of-the-premises implementation.
|
||||
|
||||
## 10. Rent Payments, Allocations, Reversals & Refunds
|
||||
## 10. Rent payments, allocations, reversal, and refunds
|
||||
|
||||
- **Angular components**: `RentPaymentValidationDialogComponent`, `RentPaymentAllocationDialogComponent`, `RentPaymentReversalDialogComponent`, `RentPaymentRefundDialogComponent`
|
||||
- **HTTP Endpoints**:
|
||||
- **Angular**: `RentPaymentValidationDialog`, `RentPaymentAllocationDialog`,
|
||||
`RentPaymentReversalDialog`, `RentPaymentRefundDialog`, `RentDueCorrection`
|
||||
- **Mutable commands**:
|
||||
- `POST /api/rent-dues/refresh`
|
||||
- `POST /api/rent-payments`
|
||||
- `POST /api/rent-payments/{paymentId}/allocations`
|
||||
- `POST /api/rent-payments/{paymentId}/reversals`
|
||||
- `POST /api/rent-payments/{paymentId}/reversal`
|
||||
- `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
|
||||
- **Backend**: `RentDueController`, `RentPaymentController`, `RentPaymentApi`
|
||||
- **Authoritative constraints**:
|
||||
- Record: non-blank operation/due IDs and time zone, non-negative due version, required
|
||||
`receivedAt`, and strictly positive amount.
|
||||
- Allocation: non-blank operation/due IDs, non-negative payment/due versions, and strictly
|
||||
positive amount.
|
||||
- Reversal: non-negative payment version, non-blank reason of at most 500 characters, and
|
||||
non-blank time zone.
|
||||
- Refund: non-blank operation ID/time zone, non-negative payment version, required
|
||||
`refundedAt`, and strictly positive amount.
|
||||
- Application rules reject future receipt/refund dates, cross-lease or duplicate allocations,
|
||||
and amounts exceeding payment, due, or unallocated balances.
|
||||
- **Angular mirror**: dialogs validate the editable date, amount, due, and reason fields, display
|
||||
known server guidance inline, focus the first invalid control, and disable only their active
|
||||
submit. Stale/concurrent responses prompt data reconciliation without silently accepting a
|
||||
payment.
|
||||
|
||||
---
|
||||
The reversal route is singular. Amount DTOs use `@Positive`, not `@DecimalMin("0.01")`.
|
||||
|
||||
## 11. Rent Revisions (Preview, Proposal & Application)
|
||||
## 11. Rent revisions
|
||||
|
||||
- **Angular components**: `RentRevisionPreviewComponent`, `RentRevisionApplicationComponent`, `RentRevisionProposalsComponent`
|
||||
- **HTTP Endpoints**:
|
||||
- **Angular**: `RentRevisionPreviewComponent`, `RentRevisionProposalsComponent`,
|
||||
`RentRevisionApplicationComponent`
|
||||
- **Mutable commands**:
|
||||
- `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
|
||||
- **Backend**: `RentRevisionController`, `RentRevisionApi`
|
||||
- **Authoritative constraints**:
|
||||
- Preview requires IANA `timeZone`; optional series override is at most 9 characters and manual
|
||||
indices are strictly positive.
|
||||
- Proposal requires time zone, `requestedOn`, strictly positive chosen rent, non-negative chosen
|
||||
charges when present, and non-blank warning codes.
|
||||
- Application multipart parameters are `timeZone`, `sentOn`, `deliveryMethod`, and
|
||||
`deliveryConfirmed`; optional `proof` must be a valid PDF within the attachment limit.
|
||||
- Policy/domain rules enforce request windows, supported IRL series, confirmation of overrides,
|
||||
ceilings, delivery date, and idempotent application.
|
||||
- **Angular mirror**: manual-index confirmation and positivity, proposal values/warnings, delivery
|
||||
mode/confirmation, and PDF proof are validated locally. Preview, proposal, application, and
|
||||
downloads each expose their own pending/error state.
|
||||
- **Unavailable index contract**: `IRL_INDEX_UNAVAILABLE` is HTTP 503 Service Unavailable and
|
||||
switches the preview to the guided manual-index path.
|
||||
|
||||
---
|
||||
## 12. Session command
|
||||
|
||||
## Verification & Compliance Summary
|
||||
- **Angular**: `SessionService`, settings logout action
|
||||
- **Mutable command**: `POST /logout`
|
||||
- **Payload validation**: none.
|
||||
- **Interaction**: duplicate logout requests are prevented while pending; failure restores the
|
||||
available action with a safe visible error.
|
||||
|
||||
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.
|
||||
## Evidence and verification gates
|
||||
|
||||
Source-level evidence is kept beside behavior in focused frontend and backend tests. The critical
|
||||
browser journey in `frontend/e2e/app.spec.ts` verifies private-area client blocking, structured
|
||||
indexed server guidance, first-invalid focus, independent clearing, preserved values, and a
|
||||
successful retry. The same suite exercises profile completion, tenant/attachment creation, lease
|
||||
creation, rent-payment recording, and the native condition-photo control.
|
||||
|
||||
Local verification recorded on 2026-07-26:
|
||||
|
||||
- Prettier passed for every frontend/documentation file changed since the epic baseline.
|
||||
- Frontend lint, application/e2e type checking, production build, and all 327 unit tests passed.
|
||||
- All 36 Playwright journeys passed across mobile, tablet, and desktop viewports.
|
||||
- `./mvnw spotless:check verify` passed: 415 unit/web tests, 16 Docker-backed integration tests,
|
||||
packaging, and coverage checks.
|
||||
Reference in new issue
Block a user