feat: add rent payment history and reversal dialogs
- Implemented RentPaymentHistoryServiceTest to validate payment retrieval with refunds and reversals. - Created ReverseRentPaymentServiceTest to ensure correct handling of payment reversals and allocation releases. - Developed rent-payment-allocation-dialog component with form for surplus allocation. - Added rent-payment-refund-dialog component for processing refunds with validation. - Introduced rent-payment-reversal-dialog component for reversing payments with reason input. - Implemented utility functions for formatting Euro values and handling date formatting. - Enhanced rent-payment-dialogs.spec.ts to cover new dialog functionalities and error handling.
This commit is contained in:
1 parent
e02ad54bcb
commit
b9e99348f7
76 files changed
+3141
-1531
No files matched your search
+130
-1
@@ -275,6 +275,9 @@ test('creates a tenant with an attachment and a lease with a guarantor', async (
|
||||
}
|
||||
await route.fulfill({ contentType: 'application/json', json: leases });
|
||||
});
|
||||
await page.route('**/api/rent-dues', (route) =>
|
||||
route.fulfill({ contentType: 'application/json', json: [] }),
|
||||
);
|
||||
|
||||
await page.goto('/');
|
||||
await page.getByTestId('primary-navigation').getByRole('button', { name: 'Locataires' }).click();
|
||||
@@ -312,7 +315,7 @@ test('creates a tenant with an attachment and a lease with a guarantor', async (
|
||||
await page.getByLabel('Nom du garant', { exact: true }).fill('Durand');
|
||||
await page.getByLabel('E-mail du garant').fill('alex@example.com');
|
||||
await page.getByRole('button', { name: 'Ajouter le garant' }).click();
|
||||
await page.getByRole('button', { name: 'Enregistrer le bail' }).click();
|
||||
await page.getByRole('button', { name: 'Enregistrer le brouillon' }).click();
|
||||
|
||||
await expect(page.getByRole('heading', { name: 'Studio République' })).toBeVisible();
|
||||
await expect(page.getByText('Alex Durand')).toBeVisible();
|
||||
@@ -322,3 +325,129 @@ test('creates a tenant with an attachment and a lease with a guarantor', async (
|
||||
expect(download.suggestedFilename()).toBe('bail-lease-1.pdf');
|
||||
await expectNoHorizontalOverflow(page);
|
||||
});
|
||||
|
||||
test('records a received rent payment and keeps its surplus visible', async ({ page }) => {
|
||||
const apartment = {
|
||||
id: 'apartment-1',
|
||||
name: 'Studio République',
|
||||
address: '12 rue de la République, Lyon',
|
||||
purchasePrice: 150000,
|
||||
description: '',
|
||||
};
|
||||
const lease = {
|
||||
id: 'lease-1',
|
||||
apartmentId: apartment.id,
|
||||
tenantIds: ['tenant-1'],
|
||||
guarantors: [],
|
||||
startDate: '2026-07-01',
|
||||
endDate: '2027-06-30',
|
||||
monthlyRent: 850,
|
||||
monthlyCharges: 50,
|
||||
securityDeposit: 850,
|
||||
status: 'ACTIVE',
|
||||
};
|
||||
let rentDues = [
|
||||
{
|
||||
id: 'due-1',
|
||||
leaseId: lease.id,
|
||||
month: '2026-07',
|
||||
dueDate: '2026-07-05',
|
||||
rent: 850,
|
||||
charges: 50,
|
||||
total: 900,
|
||||
allocatedAmount: 0,
|
||||
remainingAmount: 900,
|
||||
settlement: 'UNPAID',
|
||||
timing: 'OVERDUE',
|
||||
version: 1,
|
||||
},
|
||||
];
|
||||
let payments: Record<string, unknown>[] = [];
|
||||
let submittedPayment: Record<string, unknown> | undefined;
|
||||
|
||||
await mockUser(page, true);
|
||||
await page.route('**/api/apartments', (route) =>
|
||||
route.fulfill({ contentType: 'application/json', json: [apartment] }),
|
||||
);
|
||||
await page.route('**/api/leases', (route) =>
|
||||
route.fulfill({ contentType: 'application/json', json: [lease] }),
|
||||
);
|
||||
await page.route('**/api/rent-dues', (route) =>
|
||||
route.fulfill({ contentType: 'application/json', json: rentDues }),
|
||||
);
|
||||
await page.route('**/api/rent-payments', async (route) => {
|
||||
if (route.request().method() === 'POST') {
|
||||
submittedPayment = route.request().postDataJSON() as Record<string, unknown>;
|
||||
const receivedAt = submittedPayment['receivedAt'] as string;
|
||||
payments = [
|
||||
{
|
||||
id: 'payment-1',
|
||||
leaseId: lease.id,
|
||||
receivedAt,
|
||||
amount: 950,
|
||||
status: 'CONFIRMED',
|
||||
allocations: [
|
||||
{
|
||||
id: 'allocation-1',
|
||||
rentDueId: 'due-1',
|
||||
amount: 900,
|
||||
allocatedAt: `${receivedAt}T12:00:00Z`,
|
||||
},
|
||||
],
|
||||
allocatedAmount: 900,
|
||||
refundedAmount: 0,
|
||||
unallocatedBalance: 50,
|
||||
confirmedAt: `${receivedAt}T12:00:00Z`,
|
||||
version: 1,
|
||||
refunds: [],
|
||||
reversal: null,
|
||||
},
|
||||
];
|
||||
rentDues = [
|
||||
{
|
||||
...rentDues[0],
|
||||
allocatedAmount: 900,
|
||||
remainingAmount: 0,
|
||||
settlement: 'PAID',
|
||||
version: 2,
|
||||
},
|
||||
];
|
||||
await route.fulfill({ contentType: 'application/json', json: payments[0] });
|
||||
return;
|
||||
}
|
||||
await route.fulfill({ contentType: 'application/json', json: payments });
|
||||
});
|
||||
|
||||
await page.goto('/');
|
||||
await page.getByTestId('primary-navigation').getByRole('button', { name: 'Loyers' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Studio République' })).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: 'Enregistrer un règlement' }).click();
|
||||
const dialog = page.getByRole('dialog');
|
||||
await expect(dialog.getByRole('heading', { name: 'Enregistrer un règlement' })).toBeVisible();
|
||||
await dialog.getByLabel('Montant reçu').fill('950');
|
||||
await expect(dialog.getByText(/Surplus non affecté/)).toContainText('50,00');
|
||||
await dialog.getByRole('button', { name: 'Confirmer le règlement' }).click();
|
||||
|
||||
await expect(page.getByRole('status')).toHaveText('Le règlement a été enregistré.');
|
||||
expect(submittedPayment).toMatchObject({
|
||||
rentDueId: 'due-1',
|
||||
rentDueVersion: 1,
|
||||
amount: 950,
|
||||
});
|
||||
expect(submittedPayment?.['operationId']).toEqual(expect.any(String));
|
||||
|
||||
const dueCard = page
|
||||
.getByRole('article')
|
||||
.filter({ has: page.getByRole('heading', { name: 'Studio République' }) });
|
||||
await expect(dueCard.getByText('Réglée', { exact: true })).toBeVisible();
|
||||
await expect(dueCard.getByRole('button', { name: 'Télécharger la quittance' })).toBeVisible();
|
||||
|
||||
const paymentCard = page
|
||||
.getByRole('article')
|
||||
.filter({ has: page.getByText('Confirmé', { exact: true }) });
|
||||
await expect(paymentCard.getByText(/^950,00\s€$/)).toBeVisible();
|
||||
await expect(paymentCard.getByText(/^50,00\s€$/)).toBeVisible();
|
||||
await expect(paymentCard.getByRole('button', { name: 'Rembourser' })).toBeVisible();
|
||||
await expectNoHorizontalOverflow(page);
|
||||
});
|
||||
Reference in new issue
Block a user