+
Dépôt de garantie€
+
@if (fieldError('securityDeposit')) {
-
{{ fieldError('securityDeposit') }}
+
{{ fieldError('securityDeposit') }}
}
-
-
+
+
Mode de récupération des chargesEntre le 1er et le 28 du mois
+
@if (fieldError('dueDay')) {
-
{{ 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);