429 lines
12 KiB
TypeScript
429 lines
12 KiB
TypeScript
import { HttpClient, HttpParams } from '@angular/common/http';
|
|
import { Injectable, inject } from '@angular/core';
|
|
import { Observable } from 'rxjs';
|
|
|
|
export type ConditionReportType = 'ENTRY' | 'EXIT';
|
|
export type ConditionReportStatus = 'DRAFT' | 'OBSOLETE' | 'FINALIZED';
|
|
export type ConditionState =
|
|
'NEUF' | 'BON_ETAT' | 'ETAT_USAGE' | 'DEGRADE' | 'ABSENT' | 'NON_APPLICABLE';
|
|
export type ReportPartyRole = 'LANDLORD' | 'TENANT';
|
|
export type ParticipantAttendance = 'PRESENT' | 'REPRESENTED';
|
|
export type ZoneEquipment = 'NONE' | 'CLIMATISATION' | 'OTHER';
|
|
export type MeterType = 'ELECTRICITE' | 'GAZ' | 'EAU_FROIDE' | 'EAU_CHAUDE' | 'OTHER';
|
|
export type MeterReadingState = 'RECORDED' | 'INACCESSIBLE';
|
|
export type AccessMeansType = 'CLE' | 'BADGE' | 'TELECOMMANDE' | 'OTHER';
|
|
export type FurniturePresence = 'PRESENT' | 'ABSENT';
|
|
export type FurnitureCategory =
|
|
| 'BEDDING'
|
|
| 'SHUTTERS'
|
|
| 'HOTPLATES'
|
|
| 'OVEN_OR_MICROWAVE'
|
|
| 'REFRIGERATOR_WITH_FREEZER'
|
|
| 'DISHES'
|
|
| 'UTENSILS'
|
|
| 'TABLE_AND_SEATING'
|
|
| 'STORAGE'
|
|
| 'LIGHTING'
|
|
| 'CLEANING';
|
|
|
|
export interface ReportParticipant {
|
|
partyId: string;
|
|
role: ReportPartyRole;
|
|
name: string;
|
|
attendance: ParticipantAttendance;
|
|
representativeName: string;
|
|
}
|
|
|
|
export interface ForwardingAddress {
|
|
tenantPartyId: string;
|
|
address: string;
|
|
}
|
|
|
|
export interface ConditionZone {
|
|
id: string;
|
|
privateAreaId: string;
|
|
label: string;
|
|
equipment: ZoneEquipment;
|
|
}
|
|
|
|
export interface ConditionObservation {
|
|
subjectId: string;
|
|
state: ConditionState;
|
|
observation: string;
|
|
}
|
|
|
|
export interface MeterReading {
|
|
id: string;
|
|
type: MeterType;
|
|
state: MeterReadingState;
|
|
index: number | null;
|
|
unit: string;
|
|
label: string;
|
|
photoId: string;
|
|
observation: string;
|
|
}
|
|
|
|
export interface AccessMeans {
|
|
id: string;
|
|
type: AccessMeansType;
|
|
destination: string;
|
|
quantity: number;
|
|
observation: string;
|
|
}
|
|
|
|
export interface InventoryQuantitySplit {
|
|
inventoryLineId: string;
|
|
state: ConditionState;
|
|
quantity: number;
|
|
observation: string;
|
|
}
|
|
|
|
export interface InventoryCondition {
|
|
inventoryLineId: string;
|
|
privateAreaId: string;
|
|
privateAreaLabel: string;
|
|
designation: string;
|
|
contractualQuantity: number;
|
|
splits: InventoryQuantitySplit[];
|
|
}
|
|
|
|
export interface ConditionPhoto {
|
|
reference: string;
|
|
zoneId: string;
|
|
sha256: string;
|
|
width: number;
|
|
height: number;
|
|
uploadedBy: string;
|
|
uploadedAt: string;
|
|
}
|
|
|
|
export interface ZonePhotoAbsence {
|
|
zoneId: string;
|
|
absence: { reason: string; author: string; recordedAt: string };
|
|
}
|
|
|
|
export interface EntryPhotoDisposition {
|
|
entryPhotoId: string;
|
|
exitPhotoId?: string;
|
|
absenceReason?: string;
|
|
}
|
|
|
|
export interface PropertyConditionReportSupplement {
|
|
id: string;
|
|
type: 'GENERAL' | 'HEATING';
|
|
signedPdf: { reference: string; sha256: string };
|
|
photos: ConditionPhoto[];
|
|
uploadedBy: string;
|
|
uploadedAt: string;
|
|
requestedOn: string;
|
|
heatingPeriodStartedOn: string | null;
|
|
acceptedAdditions: string;
|
|
audit: SupplementAudit;
|
|
}
|
|
|
|
export type SupplementAudit =
|
|
| { status: 'ON_TIME'; deadlineOn: string }
|
|
| {
|
|
status: 'LATE';
|
|
deadlineOn: string;
|
|
ruleVersion: string;
|
|
reason: string;
|
|
auditedBy: string;
|
|
auditedAt: string;
|
|
};
|
|
|
|
export interface PropertyConditionReportDeliveryAttestation {
|
|
partyId: string;
|
|
attestedBy: string;
|
|
attestedAt: string;
|
|
}
|
|
|
|
export interface PropertyConditionReportRevision {
|
|
number: number;
|
|
sha256: string;
|
|
sourceFingerprint: string;
|
|
obsolete: boolean;
|
|
}
|
|
|
|
export interface EntryReportSnapshot {
|
|
reportId: string;
|
|
participants: ReportParticipant[];
|
|
zones: ConditionZone[];
|
|
observations: ConditionObservation[];
|
|
meterReadings: MeterReading[];
|
|
accessMeans: AccessMeans[];
|
|
inventoryConditions: InventoryCondition[];
|
|
photos: ConditionPhoto[];
|
|
supplements: PropertyConditionReportSupplement[];
|
|
}
|
|
|
|
export interface PropertyConditionReportFinalization {
|
|
revisionNumber: number;
|
|
signedSha256: string;
|
|
authorId: string;
|
|
finalizedAt: string;
|
|
}
|
|
|
|
export interface PropertyConditionReport {
|
|
id: string;
|
|
type: ConditionReportType;
|
|
status: ConditionReportStatus;
|
|
occurredOn: string;
|
|
timeZone: string;
|
|
participants: ReportParticipant[];
|
|
forwardingAddresses: ForwardingAddress[];
|
|
zones: ConditionZone[];
|
|
observations: ConditionObservation[];
|
|
meterReadings: MeterReading[];
|
|
accessMeans: AccessMeans[];
|
|
inventoryConditions: InventoryCondition[];
|
|
photos: ConditionPhoto[];
|
|
zonePhotoAbsences: ZonePhotoAbsence[];
|
|
entryPhotoDispositions: EntryPhotoDisposition[];
|
|
supplements: PropertyConditionReportSupplement[];
|
|
entrySnapshot: EntryReportSnapshot | null;
|
|
revisions: PropertyConditionReportRevision[];
|
|
currentRevision: PropertyConditionReportRevision | null;
|
|
finalization: PropertyConditionReportFinalization | null;
|
|
deliveryAttestations: PropertyConditionReportDeliveryAttestation[];
|
|
leaseRevisionNumber: number | null;
|
|
leaseRevisionFingerprint: string | null;
|
|
version: number;
|
|
}
|
|
|
|
export interface SaveConditionReportRequest {
|
|
version: number;
|
|
occurredOn: string;
|
|
timeZone: string;
|
|
participants: ReportParticipant[];
|
|
forwardingAddresses: ForwardingAddress[];
|
|
zones: ConditionZone[];
|
|
observations: ConditionObservation[];
|
|
meterReadings: MeterReading[];
|
|
accessMeans: AccessMeans[];
|
|
inventoryConditions: InventoryCondition[];
|
|
zonePhotoAbsences: { zoneId: string; reason: string }[];
|
|
entryPhotoDispositions: EntryPhotoDisposition[];
|
|
}
|
|
|
|
export interface FurnitureInventoryLine {
|
|
id: string;
|
|
category: FurnitureCategory | null;
|
|
presence: FurniturePresence | null;
|
|
equipmentKind: 'NONE' | 'CLIMATISATION' | 'OTHER';
|
|
privateAreaId: string;
|
|
designation: string;
|
|
quantity: number;
|
|
description: string;
|
|
}
|
|
|
|
export interface FurnitureInventoryWarning {
|
|
code: string;
|
|
ruleVersion: string;
|
|
category: FurnitureCategory;
|
|
}
|
|
|
|
export interface FurnitureInventory {
|
|
id: string;
|
|
templateVersion: string;
|
|
lines: FurnitureInventoryLine[];
|
|
missingCategories: FurnitureCategory[];
|
|
absentCategories: FurnitureCategory[];
|
|
legalWarnings: FurnitureInventoryWarning[];
|
|
readyForLeaseDocument: boolean;
|
|
frozen: boolean;
|
|
privateAreas: { id: string; label: string }[];
|
|
version: number;
|
|
}
|
|
|
|
export interface SaveFurnitureInventoryRequest {
|
|
version: number;
|
|
lines: FurnitureInventoryLine[];
|
|
}
|
|
|
|
export interface ReportFinalizationRequest {
|
|
signedReport: File;
|
|
version: number;
|
|
allPartiesSigned: boolean;
|
|
}
|
|
|
|
export interface AppendConditionReportSupplementRequest {
|
|
signedPdf: File;
|
|
photos: { file: File; zoneId: string }[];
|
|
requestedOn: string;
|
|
acceptedAdditions: string;
|
|
supplementType: 'GENERAL' | 'HEATING';
|
|
heatingPeriodStartedOn?: string;
|
|
lateWarningConfirmed: boolean;
|
|
reason?: string;
|
|
}
|
|
|
|
export interface ConditionReportComparisonLine<T> {
|
|
reference: string;
|
|
entry: T | null;
|
|
exit: T | null;
|
|
changed: boolean;
|
|
absence?: { reason: string; auditedBy: string; auditedAt: string } | null;
|
|
}
|
|
|
|
export interface ConditionReportComparison {
|
|
observations: ConditionReportComparisonLine<ConditionObservation>[];
|
|
zones: ConditionReportComparisonLine<ConditionZone>[];
|
|
inventory: ConditionReportComparisonLine<InventoryCondition>[];
|
|
meters: ConditionReportComparisonLine<MeterReading>[];
|
|
accessMeans: ConditionReportComparisonLine<AccessMeans>[];
|
|
supplements: ConditionReportComparisonLine<PropertyConditionReportSupplement>[];
|
|
photos: ConditionReportComparisonLine<ConditionPhoto>[];
|
|
}
|
|
|
|
@Injectable({ providedIn: 'root' })
|
|
export class ConditionReportService {
|
|
private readonly http = inject(HttpClient);
|
|
|
|
getInventory(leaseId: string): Observable<FurnitureInventory> {
|
|
return this.http.get<FurnitureInventory>(`/api/leases/${leaseId}/inventory`);
|
|
}
|
|
|
|
saveInventory(
|
|
leaseId: string,
|
|
request: SaveFurnitureInventoryRequest,
|
|
): Observable<FurnitureInventory> {
|
|
return this.http.put<FurnitureInventory>(`/api/leases/${leaseId}/inventory`, request);
|
|
}
|
|
|
|
getReport(
|
|
leaseId: string,
|
|
type: ConditionReportType,
|
|
timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
): Observable<PropertyConditionReport> {
|
|
return this.http.get<PropertyConditionReport>(
|
|
`/api/leases/${leaseId}/condition-reports/${type}`,
|
|
{ params: new HttpParams().set('timeZone', timeZone) },
|
|
);
|
|
}
|
|
|
|
saveReport(
|
|
leaseId: string,
|
|
type: ConditionReportType,
|
|
report: SaveConditionReportRequest,
|
|
): Observable<PropertyConditionReport> {
|
|
return this.http.put<PropertyConditionReport>(
|
|
`/api/leases/${leaseId}/condition-reports/${type}`,
|
|
report,
|
|
);
|
|
}
|
|
|
|
uploadPhoto(
|
|
leaseId: string,
|
|
type: ConditionReportType,
|
|
file: File,
|
|
zoneId: string,
|
|
): Observable<PropertyConditionReport> {
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
formData.append('zoneId', zoneId);
|
|
return this.http.post<PropertyConditionReport>(
|
|
`/api/leases/${leaseId}/condition-reports/${type}/photos`,
|
|
formData,
|
|
);
|
|
}
|
|
|
|
deletePhoto(
|
|
leaseId: string,
|
|
type: ConditionReportType,
|
|
reference: string,
|
|
version: number,
|
|
): Observable<void> {
|
|
return this.http.delete<void>(
|
|
`/api/leases/${leaseId}/condition-reports/${type}/photos/${reference}`,
|
|
{ params: new HttpParams().set('version', version) },
|
|
);
|
|
}
|
|
|
|
photoUrl(leaseId: string, type: ConditionReportType, reference: string): string {
|
|
return `/api/leases/${encodeURIComponent(leaseId)}/condition-reports/${type}/photos/${encodeURIComponent(reference)}`;
|
|
}
|
|
|
|
createRevision(
|
|
leaseId: string,
|
|
type: ConditionReportType,
|
|
version: number,
|
|
): Observable<PropertyConditionReportRevision> {
|
|
return this.http.post<PropertyConditionReportRevision>(
|
|
`/api/leases/${leaseId}/condition-reports/${type}/revisions?version=${version}`,
|
|
{},
|
|
);
|
|
}
|
|
|
|
downloadRevision(leaseId: string, type: ConditionReportType, number: number): Observable<Blob> {
|
|
return this.http.get(`/api/leases/${leaseId}/condition-reports/${type}/revisions/${number}`, {
|
|
responseType: 'blob',
|
|
});
|
|
}
|
|
|
|
finalize(
|
|
leaseId: string,
|
|
type: ConditionReportType,
|
|
request: ReportFinalizationRequest,
|
|
): Observable<PropertyConditionReport> {
|
|
const formData = new FormData();
|
|
formData.append('file', request.signedReport);
|
|
formData.append('version', String(request.version));
|
|
formData.append('attestation', 'true');
|
|
formData.append('allPartiesSigned', String(request.allPartiesSigned));
|
|
return this.http.post<PropertyConditionReport>(
|
|
`/api/leases/${leaseId}/condition-reports/${type}/finalization`,
|
|
formData,
|
|
);
|
|
}
|
|
|
|
attestDelivery(
|
|
leaseId: string,
|
|
type: ConditionReportType,
|
|
version: number,
|
|
partyId: string,
|
|
): Observable<PropertyConditionReport> {
|
|
return this.http.post<PropertyConditionReport>(
|
|
`/api/leases/${leaseId}/condition-reports/${type}/delivery-attestations`,
|
|
null,
|
|
{ params: new HttpParams().set('version', version).set('partyId', partyId) },
|
|
);
|
|
}
|
|
|
|
appendSupplement(
|
|
leaseId: string,
|
|
request: AppendConditionReportSupplementRequest,
|
|
): Observable<PropertyConditionReport> {
|
|
const formData = new FormData();
|
|
formData.append('signedPdf', request.signedPdf);
|
|
request.photos.forEach((photo) => {
|
|
formData.append('photos', photo.file);
|
|
formData.append('photoZoneIds', photo.zoneId);
|
|
});
|
|
formData.append('requestedOn', request.requestedOn);
|
|
formData.append('acceptedAdditions', request.acceptedAdditions);
|
|
formData.append('supplementType', request.supplementType);
|
|
formData.append('lateWarningConfirmed', String(request.lateWarningConfirmed));
|
|
if (request.heatingPeriodStartedOn) {
|
|
formData.append('heatingPeriodStartedOn', request.heatingPeriodStartedOn);
|
|
}
|
|
if (request.reason?.trim()) formData.append('reason', request.reason.trim());
|
|
return this.http.post<PropertyConditionReport>(
|
|
`/api/leases/${leaseId}/condition-reports/ENTRY/supplements`,
|
|
formData,
|
|
);
|
|
}
|
|
|
|
getComparison(leaseId: string): Observable<ConditionReportComparison> {
|
|
return this.http.get<ConditionReportComparison>(
|
|
`/api/leases/${leaseId}/condition-report-comparison`,
|
|
);
|
|
}
|
|
|
|
downloadBundle(leaseId: string, type: ConditionReportType): Observable<Blob> {
|
|
return this.http.get(`/api/leases/${leaseId}/condition-reports/${type}/bundle`, {
|
|
responseType: 'blob',
|
|
});
|
|
}
|
|
}
|