330 lines
19 KiB
Markdown
330 lines
19 KiB
Markdown
# Validation Parity Checklist
|
|
|
|
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.
|
|
|
|
## Shared failure and interaction contract
|
|
|
|
- Request validation failures use HTTP 400 and
|
|
`{ "code": "validation_failed", "errors": [{ "field": "...", "code": "..." }] }`.
|
|
- Other safely rejected requests use the stable HTTP 400 code `invalid_request`.
|
|
- Conflicts use HTTP 409. Controller-level file limits use structured HTTP 400 `too_large`
|
|
field errors; multipart requests rejected before controller validation use coded HTTP 413
|
|
`attachment_too_large`. 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
|
|
|
|
- **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.
|
|
|
|
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/{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
|
|
|
|
- **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 and attachments
|
|
|
|
- **Angular**: `TenantForm`, `TenantsPage`, `TenantAttachmentTransferService`
|
|
- **Mutable commands**:
|
|
- `POST /api/tenants`
|
|
- `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 drafts, amendments, clauses, and document revisions
|
|
|
|
- **Angular**: `LeaseForm`, `LeaseGuaranteeForm`, `LeaseClauseEditor`, `LeaseLegalTerms`,
|
|
`LeaseAmendmentForm`, `LeasesPage`
|
|
- **Mutable commands**:
|
|
- `POST /api/leases`
|
|
- `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.
|
|
- Each `guarantees[]` entry names one `tenantId` already present in `tenantIds`; first name,
|
|
last name, and email are required, and 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, the selected tenant and
|
|
guarantee contact data,
|
|
clause inputs, and conditional legal terms are checked before submit. Known nested server paths
|
|
are routed to the core form, guarantee, 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
|
|
|
|
- **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, and non-negative integer quantity.
|
|
- A regulatory category is unique; present, absent, and undeclared quantities must agree with
|
|
presence. Its inventory line carries the only furniture observation in the condition report,
|
|
without a derived equipment zone.
|
|
- **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.
|
|
|
|
## 7. Lease finalization and guarantee deeds
|
|
|
|
- **Angular**: `LeaseFinalizationPage`, `LeaseFinalizationForm`, `GuaranteeDeedList`
|
|
- **Mutable commands**:
|
|
- `POST /api/leases/{leaseId}/guarantee-deeds` (multipart)
|
|
- `POST /api/leases/{leaseId}/finalization` (multipart)
|
|
- **Backend**: `LeaseFinalizationController`, `LeaseFinalizationApi`
|
|
- **Guarantee-deed multipart contract**: optional request parameters are validated together as
|
|
`requirementIndex`, `guaranteeId`, `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
|
|
|
|
- **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`
|
|
- **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
|
|
|
|
- **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/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, identified from its content signature
|
|
rather than filename extension or client-supplied MIME type.
|
|
- 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, reversal, and refunds
|
|
|
|
- **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}/reversal`
|
|
- `POST /api/rent-payments/{paymentId}/refunds`
|
|
- **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
|
|
|
|
- **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**: `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
|
|
|
|
- **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.
|
|
|
|
## 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 333 unit tests passed.
|
|
- All 36 Playwright journeys passed across mobile, tablet, and desktop viewports.
|
|
- `./mvnw spotless:check verify` passed: 417 unit/web tests, 16 Docker-backed integration tests,
|
|
packaging, and coverage checks.
|