feat: finalize lease documents and IRL revisions

This commit is contained in:
nessar committed 2026-08-17 14:18:16 +02:00
1 parent f127c69a28
commit 1bf6a3449c
196 files changed
+20564 -666

No files matched your search

@@ -1,11 +1,24 @@
import { TestBed } from '@angular/core/testing';
import { vi } from 'vitest';
import { LeaseCard } from './lease-card';
import { emptyApartmentLegalProfile } from '../../services/apartment.service';
import { emptyLeaseLegalTerms } from '../../services/lease.service';
import { provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { WorkspaceService } from '../../services/workspace.service';
describe('LeaseCard', () => {
it('resolves apartment, tenants, and every status label', () => {
const fixture = TestBed.createComponent(LeaseCard);
fixture.componentRef.setInput('apartments', [
{ id: 'apartment-1', name: 'T2', address: '', purchasePrice: 0, description: '' },
{
id: 'apartment-1',
name: 'T2',
address: '',
purchasePrice: 0,
description: '',
legalProfile: emptyApartmentLegalProfile(),
},
]);
fixture.componentRef.setInput('tenants', [
{
@@ -22,11 +35,17 @@ describe('LeaseCard', () => {
apartmentId: 'apartment-1',
tenantIds: ['tenant-1', 'missing', 'tenant-2'],
guarantors: [],
specialClauses: [],
legalTerms: emptyLeaseLegalTerms(),
leaseType: 'STANDARD',
startDate: '',
endDate: '',
monthlyRent: 0,
monthlyCharges: 0,
securityDeposit: 0,
dueDay: 5,
chargeMethod: 'FLAT_RATE',
indexFlatRate: false,
status: 'DRAFT',
});
const component = fixture.componentInstance;
@@ -54,14 +73,58 @@ describe('LeaseCard', () => {
apartmentId: 'apartment-1',
tenantIds: [],
guarantors: [],
startDate: '2026-01-01',
endDate: '2027-01-01',
specialClauses: [],
legalTerms: emptyLeaseLegalTerms(),
leaseType: 'STANDARD',
startDate: '2026-07-19',
endDate: '2027-07-19',
monthlyRent: 800,
monthlyCharges: 50,
securityDeposit: 800,
dueDay: 5,
chargeMethod: 'FLAT_RATE',
indexFlatRate: false,
status: 'ACTIVE',
});
fixture.componentRef.setInput('downloading', true);
fixture.componentRef.setInput('revision', {
number: 3,
templateVersion: '2026.10',
sourceFingerprint: 'source',
sha256: 'sha256',
generatedAt: '2026-07-19T10:00:00Z',
authorId: 'owner-1',
annexes: [
{
category: 'NOTICE',
documentType: null,
sourceId: '',
version: 'FR-NOTICE-2023-03',
fileName: 'notice-information.pdf',
sha256: 'notice-sha256',
establishedOn: null,
expiresOn: null,
sourceUploadedAt: null,
},
{
category: 'APARTMENT_DOCUMENT',
documentType: 'DPE',
sourceId: 'document-1',
version: '',
fileName: 'dpe.pdf',
sha256: 'dpe-sha256',
establishedOn: '2026-07-19',
expiresOn: '2036-07-19',
sourceUploadedAt: '2026-07-19T10:00:00Z',
},
],
warningCodes: ['DPE_EXPIRED', 'CO_OWNERSHIP_RULES_EXTRACT_MISSING'],
warnings: [
{ code: 'DPE_EXPIRED', ruleVersion: '2026-07-01' },
{ code: 'CO_OWNERSHIP_RULES_EXTRACT_MISSING', ruleVersion: '2026-07-01' },
],
});
fixture.componentRef.setInput('dossierDownloading', true);
fixture.detectChanges();
const download = fixture.nativeElement.querySelector('.card-actions button');
@@ -72,9 +135,39 @@ describe('LeaseCard', () => {
fixture.componentRef.setInput('downloadError', 'Impossible de télécharger ce bail.');
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('Impossible de télécharger ce bail.');
expect(fixture.nativeElement.textContent).toContain('Révision 3 · modèle 2026.10');
expect(fixture.nativeElement.textContent).toContain('Notice d’information');
expect(fixture.nativeElement.textContent).toContain(
'Diagnostic de performance énergétique (DPE)',
);
expect(fixture.nativeElement.textContent).toContain('dpe-sha256');
expect(fixture.nativeElement.textContent).toContain('19 juillet 2026');
expect(fixture.nativeElement.textContent).not.toContain('18 juillet 2026');
expect(fixture.nativeElement.textContent).toContain(
'Le DPE présent dans le dossier est expiré.',
);
expect(fixture.nativeElement.textContent).toContain(
'L’extrait du règlement de copropriété manque au dossier.',
);
expect(fixture.nativeElement.textContent).toContain('Version de règle : 2026-07-01');
expect(fixture.nativeElement.textContent).not.toContain('DPE_EXPIRED');
const dossier: HTMLButtonElement = fixture.nativeElement.querySelector('.dossier-download');
expect(dossier.disabled).toBe(true);
expect(dossier.textContent).toContain('Préparation du dossier…');
expect(download.textContent).toContain('Télécharger le bail');
fixture.componentRef.setInput('downloadError', '');
fixture.componentRef.setInput(
'dossierDownloadError',
'Impossible de télécharger le dossier complet du bail.',
);
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain(
'Impossible de télécharger le dossier complet du bail.',
);
});
it('offers draft lifecycle actions with activation progress and an actionable failure', () => {
it('offers draft editing and dedicated finalization actions', () => {
const fixture = TestBed.createComponent(LeaseCard);
fixture.componentRef.setInput('apartments', []);
fixture.componentRef.setInput('tenants', []);
@@ -83,36 +176,101 @@ describe('LeaseCard', () => {
apartmentId: 'apartment-1',
tenantIds: [],
guarantors: [],
specialClauses: [],
legalTerms: emptyLeaseLegalTerms(),
leaseType: 'STANDARD',
startDate: '2026-01-01',
endDate: '2027-01-01',
monthlyRent: 800,
monthlyCharges: 50,
securityDeposit: 800,
dueDay: 5,
chargeMethod: 'FLAT_RATE',
indexFlatRate: false,
status: 'DRAFT',
});
fixture.componentRef.setInput('activating', true);
fixture.componentRef.setInput('activationError', 'Un bail actif chevauche cette période.');
fixture.detectChanges();
const requested = vi.fn();
fixture.componentInstance.finalizationRequested.subscribe(requested);
const text = fixture.nativeElement.textContent.replace(/\s+/g, ' ');
expect(text).toContain('Générer / télécharger le bail');
expect(text).toContain('Modifier');
expect(text).toContain('Supprimer');
expect(text).toContain('Activation…');
expect(text).toContain('Un bail actif chevauche cette période.');
const activation = [...fixture.nativeElement.querySelectorAll('button')].find((button) =>
button.textContent.includes('Activation…'),
expect(text).toContain('Finaliser le bail');
expect(text).not.toContain('Activer');
const finalization = [...fixture.nativeElement.querySelectorAll('button')].find((button) =>
button.textContent.includes('Finaliser le bail'),
);
expect(activation.disabled).toBe(true);
finalization.click();
expect(requested).toHaveBeenCalledWith('lease-1');
fixture.componentRef.setInput('lease', {
...fixture.componentInstance.lease(),
status: 'ACTIVE',
});
fixture.componentRef.setInput('activating', false);
fixture.detectChanges();
expect(fixture.nativeElement.textContent).not.toContain('Modifier');
expect(fixture.nativeElement.textContent).not.toContain('Supprimer');
expect(fixture.nativeElement.textContent).not.toContain('Activer');
expect(fixture.nativeElement.textContent).not.toContain('Finaliser le bail');
});
it('offers an IRL preview only for an active lease with the revision clause enabled', () => {
TestBed.configureTestingModule({
providers: [
provideHttpClient(),
provideHttpClientTesting(),
{ provide: WorkspaceService, useValue: { acceptRentRevisionApplication: vi.fn() } },
],
});
const http = TestBed.inject(HttpTestingController);
const initialLegalTerms = emptyLeaseLegalTerms();
const legalTerms = {
...initialLegalTerms,
rentRevision: { ...initialLegalTerms.rentRevision, enabled: true },
};
const fixture = TestBed.createComponent(LeaseCard);
fixture.componentRef.setInput('apartments', []);
fixture.componentRef.setInput('tenants', []);
fixture.componentRef.setInput('lease', {
id: 'lease-1',
apartmentId: 'apartment-1',
tenantIds: [],
guarantors: [],
specialClauses: [],
legalTerms,
leaseType: 'STANDARD',
startDate: '2026-07-19',
endDate: '2027-07-19',
monthlyRent: 800,
monthlyCharges: 50,
securityDeposit: 800,
dueDay: 5,
chargeMethod: 'FLAT_RATE',
indexFlatRate: false,
status: 'ACTIVE',
});
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('app-rent-revision-preview')).toBeNull();
http.expectNone('/api/leases/lease-1/rent-revisions');
const openRevision: HTMLButtonElement =
fixture.nativeElement.querySelector('.open-rent-revision');
expect(openRevision.getAttribute('aria-expanded')).toBe('false');
openRevision.click();
fixture.detectChanges();
expect(openRevision.getAttribute('aria-expanded')).toBe('true');
expect(fixture.nativeElement.querySelector('app-rent-revision-preview')).not.toBeNull();
http.expectOne('/api/leases/lease-1/rent-revisions').flush([]);
fixture.nativeElement.querySelector('.preview-submit').click();
http.expectOne('/api/leases/lease-1/rent-revisions/preview').flush({});
fixture.componentRef.setInput('lease', {
...fixture.componentInstance.lease(),
status: 'DRAFT',
});
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('app-rent-revision-preview')).toBeNull();
http.verify();
});
});