diff --git a/backend/src/test/java/fr/nessar/lmnporganizer/rental/application/lease/AmendLeaseServiceTest.java b/backend/src/test/java/fr/nessar/lmnporganizer/rental/application/lease/AmendLeaseServiceTest.java index f8cfb51..79a442a 100644 --- a/backend/src/test/java/fr/nessar/lmnporganizer/rental/application/lease/AmendLeaseServiceTest.java +++ b/backend/src/test/java/fr/nessar/lmnporganizer/rental/application/lease/AmendLeaseServiceTest.java @@ -316,14 +316,7 @@ private Lease activeLease() { private Tenant tenant(TenantId id, String firstName, String address) { return new Tenant( - id, - LANDLORD_ID, - firstName, - "Martin", - firstName.toLowerCase() + "@example.com", - "", - address, - List.of()); + id, LANDLORD_ID, firstName, "Martin", id.value() + "@example.com", "", address, List.of()); } private RentDue due(YearMonth month) { diff --git a/frontend/src/app/components/landlord-profile-completion-dialog/landlord-profile-completion-dialog.spec.ts b/frontend/src/app/components/landlord-profile-completion-dialog/landlord-profile-completion-dialog.spec.ts index da3f77f..fd1483f 100644 --- a/frontend/src/app/components/landlord-profile-completion-dialog/landlord-profile-completion-dialog.spec.ts +++ b/frontend/src/app/components/landlord-profile-completion-dialog/landlord-profile-completion-dialog.spec.ts @@ -130,7 +130,7 @@ describe('LandlordProfileCompletionDialog', () => { expect(fixture.nativeElement.textContent).toContain( 'Saisissez un numéro de téléphone valide.', ); - component.clearSaveFieldError('phone'); + component.clearSaveFieldError('phoneValid'); fixture.detectChanges(); expect(fixture.nativeElement.textContent).not.toContain( diff --git a/frontend/src/app/components/landlord-profile-form/landlord-profile-form.css b/frontend/src/app/components/landlord-profile-form/landlord-profile-form.css index a150427..6b78561 100644 --- a/frontend/src/app/components/landlord-profile-form/landlord-profile-form.css +++ b/frontend/src/app/components/landlord-profile-form/landlord-profile-form.css @@ -31,6 +31,13 @@ width: 100%; } +.field-error { + display: block; + margin-top: -12px; + color: var(--danger); + font-size: 0.75rem; +} + .profile-actions { display: flex; flex-wrap: wrap; diff --git a/frontend/src/app/components/landlord-profile-form/landlord-profile-form.html b/frontend/src/app/components/landlord-profile-form/landlord-profile-form.html index 748c0ed..da66acb 100644 --- a/frontend/src/app/components/landlord-profile-form/landlord-profile-form.html +++ b/frontend/src/app/components/landlord-profile-form/landlord-profile-form.html @@ -21,7 +21,7 @@ autocomplete="given-name" /> @if (firstNameError()) { - {{ firstNameError() }} + {{ firstNameError() }} } @if (lastNameError()) { - {{ lastNameError() }} + {{ lastNameError() }} } @if (emailError()) { - {{ emailError() }} + {{ emailError() }} } @if (phoneError()) { - {{ phoneError() }} + {{ phoneError() }} } @if (addressError()) { - {{ addressError() }} + {{ addressError() }} } diff --git a/frontend/src/app/components/landlord-profile-form/landlord-profile-form.spec.ts b/frontend/src/app/components/landlord-profile-form/landlord-profile-form.spec.ts index 1ecfbf7..2060d37 100644 --- a/frontend/src/app/components/landlord-profile-form/landlord-profile-form.spec.ts +++ b/frontend/src/app/components/landlord-profile-form/landlord-profile-form.spec.ts @@ -22,7 +22,7 @@ describe('LandlordProfileForm', () => { expect(saveRequested).not.toHaveBeenCalled(); expect(fixture.nativeElement.textContent).toContain( - 'Veuillez renseigner tous les champs obligatoires.', + 'Le numéro de téléphone est obligatoire.', ); }); @@ -94,7 +94,6 @@ describe('LandlordProfileForm', () => { ]); fixture.detectChanges(); await fixture.whenStable(); - expect(fixture.nativeElement.textContent).toContain( 'Saisissez un numéro de téléphone valide.', ); @@ -108,6 +107,7 @@ describe('LandlordProfileForm', () => { phone.dispatchEvent(new Event('input')); expect(changed).toHaveBeenCalledWith('phone'); + expect(changed).toHaveBeenCalledWith('phoneValid'); fixture.componentRef.setInput('saveFieldErrors', [{ field: 'email', code: 'email' }]); fixture.detectChanges(); await fixture.whenStable(); diff --git a/frontend/src/app/components/landlord-profile-form/landlord-profile-form.ts b/frontend/src/app/components/landlord-profile-form/landlord-profile-form.ts index 2f47c26..de642f3 100644 --- a/frontend/src/app/components/landlord-profile-form/landlord-profile-form.ts +++ b/frontend/src/app/components/landlord-profile-form/landlord-profile-form.ts @@ -17,6 +17,8 @@ const EMPTY_PROFILE: LandlordProfile = { address: '', }; +type LandlordProfileField = keyof LandlordProfile | 'phoneValid'; + @Component({ selector: 'app-landlord-profile-form', imports: [ @@ -38,7 +40,7 @@ export class LandlordProfileForm { readonly saveError = input(''); readonly saveFieldErrors = input([]); readonly saveRequested = output(); - readonly fieldChanged = output(); + readonly fieldChanged = output(); private previousServerErrorSignature = ''; readonly profileModel = signal({ ...this.profile() }); @@ -93,6 +95,7 @@ export class LandlordProfileForm { fieldWasChanged(field: keyof LandlordProfile): void { this.validationError.set(''); this.fieldChanged.emit(field); + if (field === 'phone') this.fieldChanged.emit('phoneValid'); } showSaveError(): boolean { @@ -189,6 +192,7 @@ export class LandlordProfileForm { : this.addressError() ? '#profile-address' : ''; + if (!selector) return; const control = this.element.nativeElement.querySelector(selector); if (control instanceof HTMLElement) control.focus(); } diff --git a/frontend/src/app/components/lease-amendment-form/lease-amendment-form.spec.ts b/frontend/src/app/components/lease-amendment-form/lease-amendment-form.spec.ts index 0dbb937..ae76943 100644 --- a/frontend/src/app/components/lease-amendment-form/lease-amendment-form.spec.ts +++ b/frontend/src/app/components/lease-amendment-form/lease-amendment-form.spec.ts @@ -36,6 +36,9 @@ describe('LeaseAmendmentForm', () => { indexFlatRate: false, status: 'ACTIVE', }); + const submitted = vi.fn(); + fixture.componentInstance.submitted.subscribe(submitted); + fixture.detectChanges(); fixture.componentInstance.amendmentModel.update((model) => ({ ...model, effectiveDate: '2026-10-01', @@ -43,9 +46,6 @@ describe('LeaseAmendmentForm', () => { monthlyCharges: 0, dueDay: 28, })); - const submitted = vi.fn(); - fixture.componentInstance.submitted.subscribe(submitted); - fixture.detectChanges(); fixture.componentInstance.save(); @@ -84,14 +84,14 @@ describe('LeaseAmendmentForm', () => { indexFlatRate: false, status: 'ACTIVE', }); + const submitted = vi.fn(); + fixture.componentInstance.submitted.subscribe(submitted); + fixture.detectChanges(); fixture.componentInstance.amendmentModel.update((model) => ({ ...model, effectiveDate: '2026-10-01', endDate: '2026-09-30', })); - const submitted = vi.fn(); - fixture.componentInstance.submitted.subscribe(submitted); - fixture.detectChanges(); fixture.componentInstance.save(); diff --git a/frontend/src/app/components/lease-end-page/lease-end-page.spec.ts b/frontend/src/app/components/lease-end-page/lease-end-page.spec.ts index 86a410f..24d59f0 100644 --- a/frontend/src/app/components/lease-end-page/lease-end-page.spec.ts +++ b/frontend/src/app/components/lease-end-page/lease-end-page.spec.ts @@ -89,7 +89,7 @@ describe('LeaseEndPage', () => { }); }); - it('shows a skeleton, then a retryable load error', () => { + it('shows a skeleton, then a retryable safe load error', () => { const response = new Subject(); getState.mockReturnValueOnce(response); const fixture = TestBed.createComponent(LeaseEndPage); @@ -99,7 +99,10 @@ describe('LeaseEndPage', () => { response.error({ error: { message: 'Bail introuvable' } }); fixture.detectChanges(); - expect(fixture.nativeElement.textContent).toContain('Bail introuvable'); + expect(fixture.nativeElement.textContent).toContain( + 'Impossible de charger la fin de ce bail.', + ); + expect(fixture.nativeElement.textContent).not.toContain('Bail introuvable'); getState.mockReturnValueOnce(of(state)); fixture.nativeElement.querySelector('.load-error button').click(); @@ -204,7 +207,7 @@ describe('LeaseEndPage', () => { expect(fixture.nativeElement.textContent).toContain('Enregistrer l’accord'); }); - it('keeps notice loading and failures local without publishing false success', () => { + it('keeps notice loading and safe failures local without publishing false success', () => { getState.mockReturnValueOnce(of({ ...state, phase: 'ACTIVE', notices: [] })); const response = new Subject(); recordNotice.mockReturnValue(response); @@ -228,7 +231,8 @@ describe('LeaseEndPage', () => { response.error({ error: { message: 'Congé refusé' } }); fixture.detectChanges(); - expect(fixture.nativeElement.textContent).toContain('Congé refusé'); + expect(fixture.nativeElement.textContent).toContain('Impossible d’enregistrer ce congé.'); + expect(fixture.nativeElement.textContent).not.toContain('Congé refusé'); expect(fixture.componentInstance.state()?.phase).toBe('ACTIVE'); }); @@ -278,7 +282,7 @@ describe('LeaseEndPage', () => { expect(acceptWithdrawal).toHaveBeenCalledWith('lease-1', 'notice-1', request); }); - it('keeps a closure failure actionable', () => { + it('keeps a safe closure failure actionable', () => { close.mockReturnValue(throwError(() => ({ error: { message: 'Clôture refusée' } }))); getState.mockReturnValueOnce(of({ ...state, phase: 'AWAITING_CLOSURE' })); const fixture = TestBed.createComponent(LeaseEndPage); @@ -290,7 +294,8 @@ describe('LeaseEndPage', () => { }); fixture.detectChanges(); - expect(fixture.nativeElement.textContent).toContain('Clôture refusée'); + expect(fixture.nativeElement.textContent).toContain('Impossible de clôturer ce bail.'); + expect(fixture.nativeElement.textContent).not.toContain('Clôture refusée'); expect(fixture.componentInstance.state()?.phase).toBe('AWAITING_CLOSURE'); }); }); diff --git a/frontend/src/app/components/lease-form/lease-form.css b/frontend/src/app/components/lease-form/lease-form.css index 5cbb22a..2873321 100644 --- a/frontend/src/app/components/lease-form/lease-form.css +++ b/frontend/src/app/components/lease-form/lease-form.css @@ -72,6 +72,11 @@ .form-actions { margin-top: 28px; } +.core-field .field-error { + margin: -14px 0 0; + color: var(--danger); + font-size: 0.75rem; +} @media (min-width: 700px) { .three-columns { grid-template-columns: repeat(3, minmax(0, 1fr)); diff --git a/frontend/src/app/components/lease-form/lease-form.html b/frontend/src/app/components/lease-form/lease-form.html index 3fd8f58..ed38fe5 100644 --- a/frontend/src/app/components/lease-form/lease-form.html +++ b/frontend/src/app/components/lease-form/lease-form.html @@ -38,7 +38,8 @@
- + Appartement{{ apartment.name }} } + @if (fieldError('apartmentId')) { - {{ fieldError('apartmentId') }} + } - - +
+ Locataires } + @if (fieldError('tenantIds')) { - {{ fieldError('tenantIds') }} + } - +
@@ -83,7 +87,8 @@
- + Type de bailLocation meublée classique Location meublée étudiante + @if (fieldError('leaseType')) { - {{ fieldError('leaseType') }} + } - - +
+ Date d'entrée + @if (fieldError('startDate')) { - {{ fieldError('startDate') }} + } - +
Date de fin calculée @@ -120,7 +128,8 @@ @if (overlapWarning()) {

{{ overlapWarning() }}

} - + Loyer mensuel€ + @if (fieldError('monthlyRent')) { - {{ fieldError('monthlyRent') }} + } - - +
+ Charges mensuelles€ + @if (fieldError('monthlyCharges')) { - {{ fieldError('monthlyCharges') }} + } - - +
+ Dépôt de garantie€ + @if (fieldError('securityDeposit')) { - {{ fieldError('securityDeposit') }} + } - - +
+ Mode de récupération des chargesEntre le 1er et le 28 du mois + @if (fieldError('dueDay')) { - {{ fieldError('dueDay') }} + } - +
@if (leaseModel().chargeMethod === 'FLAT_RATE') { Indexer le forfait de charges avec le loyer diff --git a/frontend/src/app/components/lease-form/lease-form.spec.ts b/frontend/src/app/components/lease-form/lease-form.spec.ts index 676ca1c..2aeb3d2 100644 --- a/frontend/src/app/components/lease-form/lease-form.spec.ts +++ b/frontend/src/app/components/lease-form/lease-form.spec.ts @@ -174,6 +174,7 @@ describe('LeaseForm', () => { ]); fixture.detectChanges(); await new Promise((resolve) => queueMicrotask(resolve)); + fixture.detectChanges(); expect(fixture.componentInstance.leaseModel().monthlyCharges).toBe(50); expect(fixture.nativeElement.textContent).toContain('charges mensuelles doivent être'); @@ -182,6 +183,7 @@ describe('LeaseForm', () => { fixture.componentRef.setInput('saveFieldErrors', [{ field: 'dueDay', code: 'max' }]); fixture.detectChanges(); await new Promise((resolve) => queueMicrotask(resolve)); + fixture.detectChanges(); expect(document.activeElement?.id).toBe('dueDay'); }); diff --git a/frontend/src/app/components/rent-payment-allocation-dialog/rent-payment-allocation-dialog.ts b/frontend/src/app/components/rent-payment-allocation-dialog/rent-payment-allocation-dialog.ts index 52cfa8f..d81c3b0 100644 --- a/frontend/src/app/components/rent-payment-allocation-dialog/rent-payment-allocation-dialog.ts +++ b/frontend/src/app/components/rent-payment-allocation-dialog/rent-payment-allocation-dialog.ts @@ -19,7 +19,12 @@ import { RentDue } from '../../services/lease.service'; import { saveFailure } from '../../services/http-error-message'; import { formatEuro } from '../../services/rent-payment-format'; import { RentPayment, RentPaymentService } from '../../services/rent-payment.service'; -import { isRentPaymentDataStale, RENT_PAYMENT_DATA_STALE } from '../rent-payment-dialog-result'; +import { + isRentPaymentConflict, + isRentPaymentDataStale, + RENT_PAYMENT_CONFLICT_MESSAGE, + RENT_PAYMENT_DATA_STALE, +} from '../rent-payment-dialog-result'; export interface EligibleRentDue { rentDue: RentDue; @@ -136,7 +141,13 @@ export class RentPaymentAllocationDialog { const knownErrors = failure.fieldErrors.filter((fieldError) => this.isKnownFieldError(fieldError.field, fieldError.code), ); - this.error.set(knownErrors.length === failure.fieldErrors.length ? '' : failure.message); + this.error.set( + knownErrors.length > 0 && knownErrors.length === failure.fieldErrors.length + ? '' + : isRentPaymentConflict(error) + ? RENT_PAYMENT_CONFLICT_MESSAGE + : failure.message, + ); this.fieldErrorFields.set(new Set(knownErrors.map((fieldError) => fieldError.field))); for (const fieldError of knownErrors) { const control = this.form.get(fieldError.field); diff --git a/frontend/src/app/components/rent-payment-dialog-result.ts b/frontend/src/app/components/rent-payment-dialog-result.ts index e8eac5c..faa0389 100644 --- a/frontend/src/app/components/rent-payment-dialog-result.ts +++ b/frontend/src/app/components/rent-payment-dialog-result.ts @@ -1,6 +1,8 @@ import { RentPayment } from '../services/rent-payment.service'; export const RENT_PAYMENT_DATA_STALE = 'RENT_PAYMENT_DATA_STALE'; +export const RENT_PAYMENT_CONFLICT_MESSAGE = + 'Cette action n’est plus possible avec les données actuelles. Actualisez-les puis réessayez.'; const CONCURRENT_CHANGE = 'concurrent_change'; export type RentPaymentDialogResult = RentPayment | typeof RENT_PAYMENT_DATA_STALE; @@ -18,3 +20,7 @@ export function isRentPaymentDataStale(error: unknown): boolean { error.error.code === CONCURRENT_CHANGE ); } + +export function isRentPaymentConflict(error: unknown): boolean { + return typeof error === 'object' && error !== null && 'status' in error && error.status === 409; +} diff --git a/frontend/src/app/components/rent-payment-dialogs.spec.ts b/frontend/src/app/components/rent-payment-dialogs.spec.ts index a765a16..0e847e8 100644 --- a/frontend/src/app/components/rent-payment-dialogs.spec.ts +++ b/frontend/src/app/components/rent-payment-dialogs.spec.ts @@ -2,7 +2,7 @@ import { Type } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { of, throwError } from 'rxjs'; -import { RENT_PAYMENT_DATA_STALE } from './rent-payment-dialog-result'; +import { RENT_PAYMENT_CONFLICT_MESSAGE, RENT_PAYMENT_DATA_STALE } from './rent-payment-dialog-result'; import { RentPaymentAllocationDialog } from './rent-payment-allocation-dialog/rent-payment-allocation-dialog'; import { RentPaymentRefundDialog } from './rent-payment-refund-dialog/rent-payment-refund-dialog'; import { RentPaymentReversalDialog } from './rent-payment-reversal-dialog/rent-payment-reversal-dialog'; @@ -80,10 +80,31 @@ describe('rent payment dialogs', () => { component.submit(); expect(component.form.controls.amount.value).toBe(0.001); - expect(component.error()).toBe('Échéance déjà réglée'); + expect(component.error()).toBe(RENT_PAYMENT_CONFLICT_MESSAGE); expect(dialogRef.close).not.toHaveBeenCalled(); }); + it('keeps non-concurrent conflicts visible for every payment action', () => { + payments.refund.mockReturnValue(throwError(() => ({ status: 409, error: { message: 'detail' } }))); + const refund = create(RentPaymentRefundDialog, payment({ unallocatedBalance: 100 })); + refund.submit(); + expect(refund.error()).toBe(RENT_PAYMENT_CONFLICT_MESSAGE); + + payments.reverse.mockReturnValue(throwError(() => ({ status: 409, error: { message: 'detail' } }))); + const reversal = create(RentPaymentReversalDialog, payment()); + reversal.form.controls.reason.setValue('Erreur de saisie'); + reversal.submit(); + expect(reversal.error()).toBe(RENT_PAYMENT_CONFLICT_MESSAGE); + + payments.allocate.mockReturnValue(throwError(() => ({ status: 409, error: { message: 'detail' } }))); + const allocation = create(RentPaymentAllocationDialog, { + payment: payment({ unallocatedBalance: 100 }), + eligibleRentDues: [{ rentDue: due(), label: 'Juillet' }], + }); + allocation.submit(); + expect(allocation.error()).toBe(RENT_PAYMENT_CONFLICT_MESSAGE); + }); + it('maps a known server field error and clears it when that field changes', () => { payments.refund.mockReturnValue( throwError(() => ({ diff --git a/frontend/src/app/components/rent-payment-refund-dialog/rent-payment-refund-dialog.ts b/frontend/src/app/components/rent-payment-refund-dialog/rent-payment-refund-dialog.ts index c038a2e..4c3a9b6 100644 --- a/frontend/src/app/components/rent-payment-refund-dialog/rent-payment-refund-dialog.ts +++ b/frontend/src/app/components/rent-payment-refund-dialog/rent-payment-refund-dialog.ts @@ -16,7 +16,12 @@ import { finalize } from 'rxjs'; import { saveFailure } from '../../services/http-error-message'; import { browserTimeZone, formatEuro, localIsoDate } from '../../services/rent-payment-format'; import { RentPayment, RentPaymentService } from '../../services/rent-payment.service'; -import { isRentPaymentDataStale, RENT_PAYMENT_DATA_STALE } from '../rent-payment-dialog-result'; +import { + isRentPaymentConflict, + isRentPaymentDataStale, + RENT_PAYMENT_CONFLICT_MESSAGE, + RENT_PAYMENT_DATA_STALE, +} from '../rent-payment-dialog-result'; @Component({ selector: 'app-rent-payment-refund-dialog', @@ -92,7 +97,13 @@ export class RentPaymentRefundDialog { const knownErrors = failure.fieldErrors.filter((fieldError) => this.isKnownFieldError(fieldError.field, fieldError.code), ); - this.error.set(knownErrors.length === failure.fieldErrors.length ? '' : failure.message); + this.error.set( + knownErrors.length > 0 && knownErrors.length === failure.fieldErrors.length + ? '' + : isRentPaymentConflict(error) + ? RENT_PAYMENT_CONFLICT_MESSAGE + : failure.message, + ); this.fieldErrorFields.set(new Set(knownErrors.map((fieldError) => fieldError.field))); for (const fieldError of knownErrors) { const control = this.form.get(fieldError.field); diff --git a/frontend/src/app/components/rent-payment-reversal-dialog/rent-payment-reversal-dialog.ts b/frontend/src/app/components/rent-payment-reversal-dialog/rent-payment-reversal-dialog.ts index 8baf2d9..3890f23 100644 --- a/frontend/src/app/components/rent-payment-reversal-dialog/rent-payment-reversal-dialog.ts +++ b/frontend/src/app/components/rent-payment-reversal-dialog/rent-payment-reversal-dialog.ts @@ -15,7 +15,12 @@ import { finalize } from 'rxjs'; import { saveFailure } from '../../services/http-error-message'; import { browserTimeZone, formatEuro } from '../../services/rent-payment-format'; import { RentPayment, RentPaymentService } from '../../services/rent-payment.service'; -import { isRentPaymentDataStale, RENT_PAYMENT_DATA_STALE } from '../rent-payment-dialog-result'; +import { + isRentPaymentConflict, + isRentPaymentDataStale, + RENT_PAYMENT_CONFLICT_MESSAGE, + RENT_PAYMENT_DATA_STALE, +} from '../rent-payment-dialog-result'; @Component({ selector: 'app-rent-payment-reversal-dialog', @@ -87,7 +92,13 @@ export class RentPaymentReversalDialog { const knownErrors = failure.fieldErrors.filter((fieldError) => this.isKnownFieldError(fieldError.field, fieldError.code), ); - this.error.set(knownErrors.length === failure.fieldErrors.length ? '' : failure.message); + this.error.set( + knownErrors.length > 0 && knownErrors.length === failure.fieldErrors.length + ? '' + : isRentPaymentConflict(error) + ? RENT_PAYMENT_CONFLICT_MESSAGE + : failure.message, + ); this.fieldErrorFields.set(new Set(knownErrors.map((fieldError) => fieldError.field))); for (const fieldError of knownErrors) { const control = this.form.get(fieldError.field); diff --git a/frontend/src/app/components/rent-payment-validation-dialog/rent-payment-validation-dialog.ts b/frontend/src/app/components/rent-payment-validation-dialog/rent-payment-validation-dialog.ts index 0dec339..86b80a0 100644 --- a/frontend/src/app/components/rent-payment-validation-dialog/rent-payment-validation-dialog.ts +++ b/frontend/src/app/components/rent-payment-validation-dialog/rent-payment-validation-dialog.ts @@ -18,7 +18,12 @@ import { RentDue } from '../../services/lease.service'; import { saveFailure } from '../../services/http-error-message'; import { browserTimeZone, formatEuro, localIsoDate } from '../../services/rent-payment-format'; import { RentPaymentService } from '../../services/rent-payment.service'; -import { isRentPaymentDataStale, RENT_PAYMENT_DATA_STALE } from '../rent-payment-dialog-result'; +import { + isRentPaymentConflict, + isRentPaymentDataStale, + RENT_PAYMENT_CONFLICT_MESSAGE, + RENT_PAYMENT_DATA_STALE, +} from '../rent-payment-dialog-result'; export interface RentPaymentValidationDialogData { rentDue: RentDue; @@ -109,7 +114,13 @@ export class RentPaymentValidationDialog { const knownErrors = failure.fieldErrors.filter((fieldError) => this.isKnownFieldError(fieldError.field, fieldError.code), ); - this.error.set(knownErrors.length === failure.fieldErrors.length ? '' : failure.message); + this.error.set( + knownErrors.length > 0 && knownErrors.length === failure.fieldErrors.length + ? '' + : isRentPaymentConflict(error) + ? RENT_PAYMENT_CONFLICT_MESSAGE + : failure.message, + ); this.fieldErrorFields.set(new Set(knownErrors.map((fieldError) => fieldError.field))); for (const fieldError of knownErrors) { const control = this.form.get(fieldError.field);