refactor(frontend): separate furniture inventory client (#7)
This commit is contained in:
1 parent
b030ac579a
commit
ef04cbe45b
14 files changed
+263
-207
No files matched your search
+9
-23
@@ -2,16 +2,11 @@
|
||||
<h2>Date et fuseau</h2>
|
||||
<label>
|
||||
Date civile
|
||||
<input
|
||||
type="date"
|
||||
required
|
||||
[value]="report().occurredOn"
|
||||
(input)="setOccurredOn($any($event.target).value)"
|
||||
/>
|
||||
<input type="date" required [value]="report().occurredOn" (input)="setOccurredOn($event)" />
|
||||
</label>
|
||||
<label>
|
||||
Fuseau IANA du navigateur
|
||||
<input required [value]="report().timeZone" (input)="setTimeZone($any($event.target).value)" />
|
||||
<input required [value]="report().timeZone" (input)="setTimeZone($event)" />
|
||||
</label>
|
||||
</section>
|
||||
|
||||
@@ -27,7 +22,7 @@
|
||||
Présence
|
||||
<select
|
||||
[value]="participant.attendance"
|
||||
(change)="updateParticipant(index, 'attendance', $any($event.target).value)"
|
||||
(change)="updateParticipant(index, 'attendance', $event)"
|
||||
>
|
||||
<option value="PRESENT">Présent</option>
|
||||
<option value="REPRESENTED">Représenté</option>
|
||||
@@ -39,7 +34,7 @@
|
||||
<input
|
||||
required
|
||||
[value]="participant.representativeName"
|
||||
(input)="updateParticipant(index, 'representativeName', $any($event.target).value)"
|
||||
(input)="updateParticipant(index, 'representativeName', $event)"
|
||||
/>
|
||||
</label>
|
||||
}
|
||||
@@ -57,7 +52,7 @@
|
||||
<select
|
||||
required
|
||||
[value]="zone.privateAreaId"
|
||||
(change)="updateZone(zone.id, 'privateAreaId', $any($event.target).value)"
|
||||
(change)="updateZone(zone.id, 'privateAreaId', $event)"
|
||||
>
|
||||
@for (area of privateAreas(); track area.id) {
|
||||
<option [value]="area.id">{{ area.label }}</option>
|
||||
@@ -66,18 +61,11 @@
|
||||
</label>
|
||||
<label>
|
||||
Libellé
|
||||
<input
|
||||
required
|
||||
[value]="zone.label"
|
||||
(input)="updateZone(zone.id, 'label', $any($event.target).value)"
|
||||
/>
|
||||
<input required [value]="zone.label" (input)="updateZone(zone.id, 'label', $event)" />
|
||||
</label>
|
||||
<label>
|
||||
Équipement
|
||||
<select
|
||||
[value]="zone.equipment"
|
||||
(change)="updateZone(zone.id, 'equipment', $any($event.target).value)"
|
||||
>
|
||||
<select [value]="zone.equipment" (change)="updateZone(zone.id, 'equipment', $event)">
|
||||
<option value="NONE">Aucun</option>
|
||||
<option value="CLIMATISATION">Climatisation</option>
|
||||
<option value="OTHER">Autre</option>
|
||||
@@ -96,7 +84,7 @@
|
||||
État
|
||||
<select
|
||||
[value]="observation.state"
|
||||
(change)="updateObservation(observation.subjectId, 'state', $any($event.target).value)"
|
||||
(change)="updateObservation(observation.subjectId, 'state', $event)"
|
||||
>
|
||||
@for (state of states; track state) {
|
||||
<option [value]="state">{{ state }}</option>
|
||||
@@ -111,9 +99,7 @@
|
||||
<textarea
|
||||
[required]="needsObservation(observation.state)"
|
||||
[value]="observation.observation"
|
||||
(input)="
|
||||
updateObservation(observation.subjectId, 'observation', $any($event.target).value)
|
||||
"
|
||||
(input)="updateObservation(observation.subjectId, 'observation', $event)"
|
||||
></textarea>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
+21
-14
@@ -20,23 +20,19 @@ export class ConditionReportDetailsSection {
|
||||
readonly reportChanged = output<PropertyConditionReport>();
|
||||
readonly states = states;
|
||||
|
||||
setOccurredOn(occurredOn: string): void {
|
||||
this.update((report) => ({ ...report, occurredOn }));
|
||||
setOccurredOn(event: Event): void {
|
||||
this.update((report) => ({ ...report, occurredOn: this.value(event) }));
|
||||
}
|
||||
|
||||
setTimeZone(timeZone: string): void {
|
||||
this.update((report) => ({ ...report, timeZone }));
|
||||
setTimeZone(event: Event): void {
|
||||
this.update((report) => ({ ...report, timeZone: this.value(event) }));
|
||||
}
|
||||
|
||||
updateParticipant(
|
||||
index: number,
|
||||
field: 'attendance' | 'representativeName',
|
||||
value: string,
|
||||
): void {
|
||||
updateParticipant(index: number, field: 'attendance' | 'representativeName', event: Event): void {
|
||||
this.update((report) => ({
|
||||
...report,
|
||||
participants: report.participants.map((participant, item) =>
|
||||
item === index ? { ...participant, [field]: value } : participant,
|
||||
item === index ? { ...participant, [field]: this.value(event) } : participant,
|
||||
),
|
||||
}));
|
||||
}
|
||||
@@ -53,10 +49,12 @@ export class ConditionReportDetailsSection {
|
||||
}));
|
||||
}
|
||||
|
||||
updateZone(id: string, field: 'privateAreaId' | 'label' | 'equipment', value: string): void {
|
||||
updateZone(id: string, field: 'privateAreaId' | 'label' | 'equipment', event: Event): void {
|
||||
this.update((report) => ({
|
||||
...report,
|
||||
zones: report.zones.map((zone) => (zone.id === id ? { ...zone, [field]: value } : zone)),
|
||||
zones: report.zones.map((zone) =>
|
||||
zone.id === id ? { ...zone, [field]: this.value(event) } : zone,
|
||||
),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -76,11 +74,11 @@ export class ConditionReportDetailsSection {
|
||||
);
|
||||
}
|
||||
|
||||
updateObservation(id: string, field: 'state' | 'observation', value: string): void {
|
||||
updateObservation(id: string, field: 'state' | 'observation', event: Event): void {
|
||||
this.update((report) => ({
|
||||
...report,
|
||||
observations: report.observations.map((observation) =>
|
||||
observation.subjectId === id ? { ...observation, [field]: value } : observation,
|
||||
observation.subjectId === id ? { ...observation, [field]: this.value(event) } : observation,
|
||||
),
|
||||
}));
|
||||
}
|
||||
@@ -109,4 +107,13 @@ export class ConditionReportDetailsSection {
|
||||
private update(change: (report: PropertyConditionReport) => PropertyConditionReport): void {
|
||||
this.reportChanged.emit(change(this.report()));
|
||||
}
|
||||
|
||||
private value(event: Event): string {
|
||||
const target = event.target;
|
||||
return target instanceof HTMLInputElement ||
|
||||
target instanceof HTMLSelectElement ||
|
||||
target instanceof HTMLTextAreaElement
|
||||
? target.value
|
||||
: '';
|
||||
}
|
||||
}
|
||||
+3
-18
@@ -6,10 +6,7 @@
|
||||
<h3>{{ zone.label }}</h3>
|
||||
<label>
|
||||
Motif d’absence
|
||||
<input
|
||||
[value]="absenceReason(zone.id)"
|
||||
(input)="setZoneAbsence(zone.id, $any($event.target).value)"
|
||||
/>
|
||||
<input [value]="absenceReason(zone.id)" (input)="setZoneAbsence(zone.id, $event)" />
|
||||
</label>
|
||||
<label>
|
||||
Prendre ou choisir une photo
|
||||
@@ -72,13 +69,7 @@
|
||||
Photo de sortie correspondante
|
||||
<select
|
||||
[value]="disposition(entryPhoto.reference).exitPhotoId ?? ''"
|
||||
(change)="
|
||||
setEntryPhotoDisposition(
|
||||
entryPhoto.reference,
|
||||
'exitPhotoId',
|
||||
$any($event.target).value
|
||||
)
|
||||
"
|
||||
(change)="setEntryPhotoDisposition(entryPhoto.reference, 'exitPhotoId', $event)"
|
||||
>
|
||||
<option value="">Aucune</option>
|
||||
@for (exitPhoto of report().photos; track exitPhoto.reference) {
|
||||
@@ -90,13 +81,7 @@
|
||||
Motif d’absence
|
||||
<input
|
||||
[value]="disposition(entryPhoto.reference).absenceReason ?? ''"
|
||||
(input)="
|
||||
setEntryPhotoDisposition(
|
||||
entryPhoto.reference,
|
||||
'absenceReason',
|
||||
$any($event.target).value
|
||||
)
|
||||
"
|
||||
(input)="setEntryPhotoDisposition(entryPhoto.reference, 'absenceReason', $event)"
|
||||
/>
|
||||
</label>
|
||||
</article>
|
||||
|
||||
+11
-2
@@ -39,7 +39,8 @@ export class ConditionReportEvidenceSection implements OnDestroy {
|
||||
);
|
||||
}
|
||||
|
||||
setZoneAbsence(zoneId: string, reason: string): void {
|
||||
setZoneAbsence(zoneId: string, event: Event): void {
|
||||
const reason = this.value(event);
|
||||
this.update((report) => ({
|
||||
...report,
|
||||
zonePhotoAbsences: [
|
||||
@@ -52,8 +53,9 @@ export class ConditionReportEvidenceSection implements OnDestroy {
|
||||
setEntryPhotoDisposition(
|
||||
entryPhotoId: string,
|
||||
field: keyof EntryPhotoDisposition,
|
||||
value: string,
|
||||
event: Event,
|
||||
): void {
|
||||
const value = this.value(event);
|
||||
this.update((report) => {
|
||||
const current = report.entryPhotoDispositions.find(
|
||||
(item) => item.entryPhotoId === entryPhotoId,
|
||||
@@ -148,4 +150,11 @@ export class ConditionReportEvidenceSection implements OnDestroy {
|
||||
private update(change: (report: PropertyConditionReport) => PropertyConditionReport): void {
|
||||
this.reportChanged.emit(change(this.report()));
|
||||
}
|
||||
|
||||
private value(event: Event): string {
|
||||
const target = event.target;
|
||||
return target instanceof HTMLInputElement || target instanceof HTMLSelectElement
|
||||
? target.value
|
||||
: '';
|
||||
}
|
||||
}
|
||||
+15
-47
@@ -5,10 +5,7 @@
|
||||
<legend>{{ meter.label || meter.type }}</legend>
|
||||
<label>
|
||||
Type
|
||||
<select
|
||||
[value]="meter.type"
|
||||
(change)="updateMeter(meter.id, 'type', $any($event.target).value)"
|
||||
>
|
||||
<select [value]="meter.type" (change)="updateMeter(meter.id, 'type', $event)">
|
||||
<option value="ELECTRICITE">Électricité</option>
|
||||
<option value="GAZ">Gaz</option>
|
||||
<option value="EAU_FROIDE">Eau froide</option>
|
||||
@@ -21,26 +18,19 @@
|
||||
<input
|
||||
[required]="meter.type === 'OTHER'"
|
||||
[value]="meter.label"
|
||||
(input)="updateMeter(meter.id, 'label', $any($event.target).value)"
|
||||
(input)="updateMeter(meter.id, 'label', $event)"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Relevé
|
||||
<select
|
||||
[value]="meter.state"
|
||||
(change)="updateMeter(meter.id, 'state', $any($event.target).value)"
|
||||
>
|
||||
<select [value]="meter.state" (change)="updateMeter(meter.id, 'state', $event)">
|
||||
<option value="RECORDED">Index relevé</option>
|
||||
<option value="INACCESSIBLE">Compteur inaccessible</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Unité
|
||||
<input
|
||||
required
|
||||
[value]="meter.unit"
|
||||
(input)="updateMeter(meter.id, 'unit', $any($event.target).value)"
|
||||
/>
|
||||
<input required [value]="meter.unit" (input)="updateMeter(meter.id, 'unit', $event)" />
|
||||
</label>
|
||||
@if (meter.state === 'RECORDED') {
|
||||
<label>
|
||||
@@ -51,16 +41,13 @@
|
||||
step="any"
|
||||
required
|
||||
[value]="meter.index"
|
||||
(input)="updateMeter(meter.id, 'index', $any($event.target).value)"
|
||||
(input)="updateMeter(meter.id, 'index', $event)"
|
||||
/>
|
||||
</label>
|
||||
}
|
||||
<label>
|
||||
Référence photo
|
||||
<input
|
||||
[value]="meter.photoId"
|
||||
(input)="updateMeter(meter.id, 'photoId', $any($event.target).value)"
|
||||
/>
|
||||
<input [value]="meter.photoId" (input)="updateMeter(meter.id, 'photoId', $event)" />
|
||||
</label>
|
||||
<label>
|
||||
Observation
|
||||
@@ -70,7 +57,7 @@
|
||||
<textarea
|
||||
[required]="meter.state === 'INACCESSIBLE'"
|
||||
[value]="meter.observation"
|
||||
(input)="updateMeter(meter.id, 'observation', $any($event.target).value)"
|
||||
(input)="updateMeter(meter.id, 'observation', $event)"
|
||||
></textarea>
|
||||
</label>
|
||||
<button type="button" (click)="removeMeter(meter.id)">Supprimer ce compteur</button>
|
||||
@@ -86,10 +73,7 @@
|
||||
<legend>{{ access.destination || 'Accès' }}</legend>
|
||||
<label>
|
||||
Type
|
||||
<select
|
||||
[value]="access.type"
|
||||
(change)="updateAccessMean(access.id, 'type', $any($event.target).value)"
|
||||
>
|
||||
<select [value]="access.type" (change)="updateAccessMean(access.id, 'type', $event)">
|
||||
<option value="CLE">Clé</option>
|
||||
<option value="BADGE">Badge</option>
|
||||
<option value="TELECOMMANDE">Télécommande</option>
|
||||
@@ -101,7 +85,7 @@
|
||||
<input
|
||||
required
|
||||
[value]="access.destination"
|
||||
(input)="updateAccessMean(access.id, 'destination', $any($event.target).value)"
|
||||
(input)="updateAccessMean(access.id, 'destination', $event)"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
@@ -112,14 +96,14 @@
|
||||
step="1"
|
||||
required
|
||||
[value]="access.quantity"
|
||||
(input)="updateAccessMean(access.id, 'quantity', $any($event.target).value)"
|
||||
(input)="updateAccessMean(access.id, 'quantity', $event)"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Observation
|
||||
<textarea
|
||||
[value]="access.observation"
|
||||
(input)="updateAccessMean(access.id, 'observation', $any($event.target).value)"
|
||||
(input)="updateAccessMean(access.id, 'observation', $event)"
|
||||
></textarea>
|
||||
</label>
|
||||
<button type="button" (click)="removeAccessMean(access.id)">
|
||||
@@ -148,9 +132,7 @@
|
||||
État
|
||||
<select
|
||||
[value]="split.state"
|
||||
(change)="
|
||||
updateInventorySplit(conditionIndex, splitIndex, 'state', $any($event.target).value)
|
||||
"
|
||||
(change)="updateInventorySplit(conditionIndex, splitIndex, 'state', $event)"
|
||||
>
|
||||
@for (state of states; track state) {
|
||||
<option [value]="state">{{ state }}</option>
|
||||
@@ -165,14 +147,7 @@
|
||||
step="1"
|
||||
required
|
||||
[value]="split.quantity"
|
||||
(input)="
|
||||
updateInventorySplit(
|
||||
conditionIndex,
|
||||
splitIndex,
|
||||
'quantity',
|
||||
$any($event.target).value
|
||||
)
|
||||
"
|
||||
(input)="updateInventorySplit(conditionIndex, splitIndex, 'quantity', $event)"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
@@ -183,14 +158,7 @@
|
||||
<textarea
|
||||
[required]="needsObservation(split.state)"
|
||||
[value]="split.observation"
|
||||
(input)="
|
||||
updateInventorySplit(
|
||||
conditionIndex,
|
||||
splitIndex,
|
||||
'observation',
|
||||
$any($event.target).value
|
||||
)
|
||||
"
|
||||
(input)="updateInventorySplit(conditionIndex, splitIndex, 'observation', $event)"
|
||||
></textarea>
|
||||
</label>
|
||||
<button type="button" (click)="removeInventorySplit(conditionIndex, splitIndex)">
|
||||
@@ -215,7 +183,7 @@
|
||||
<textarea
|
||||
required
|
||||
[value]="forwardingAddress(participant.partyId)"
|
||||
(input)="updateForwardingAddress(participant.partyId, $any($event.target).value)"
|
||||
(input)="updateForwardingAddress(participant.partyId, $event)"
|
||||
></textarea>
|
||||
</label>
|
||||
}
|
||||
|
||||
+17
-4
@@ -48,7 +48,8 @@ export class ConditionReportInventorySection {
|
||||
}));
|
||||
}
|
||||
|
||||
updateMeter(id: string, field: keyof MeterReading, value: string): void {
|
||||
updateMeter(id: string, field: keyof MeterReading, event: Event): void {
|
||||
const value = this.value(event);
|
||||
this.update((report) => ({
|
||||
...report,
|
||||
meterReadings: report.meterReadings.map((meter) =>
|
||||
@@ -82,7 +83,8 @@ export class ConditionReportInventorySection {
|
||||
}));
|
||||
}
|
||||
|
||||
updateAccessMean(id: string, field: keyof AccessMeans, value: string): void {
|
||||
updateAccessMean(id: string, field: keyof AccessMeans, event: Event): void {
|
||||
const value = this.value(event);
|
||||
this.update((report) => ({
|
||||
...report,
|
||||
accessMeans: report.accessMeans.map((access) =>
|
||||
@@ -119,8 +121,9 @@ export class ConditionReportInventorySection {
|
||||
conditionIndex: number,
|
||||
splitIndex: number,
|
||||
field: keyof InventoryQuantitySplit,
|
||||
value: string,
|
||||
event: Event,
|
||||
): void {
|
||||
const value = this.value(event);
|
||||
this.updateInventoryCondition(conditionIndex, (condition) => ({
|
||||
...condition,
|
||||
splits: condition.splits.map((split, item) =>
|
||||
@@ -144,7 +147,8 @@ export class ConditionReportInventorySection {
|
||||
}));
|
||||
}
|
||||
|
||||
updateForwardingAddress(tenantPartyId: string, address: string): void {
|
||||
updateForwardingAddress(tenantPartyId: string, event: Event): void {
|
||||
const address = this.value(event);
|
||||
this.update((report) => ({
|
||||
...report,
|
||||
forwardingAddresses: [
|
||||
@@ -171,6 +175,15 @@ export class ConditionReportInventorySection {
|
||||
this.reportChanged.emit(change(this.report()));
|
||||
}
|
||||
|
||||
private value(event: Event): string {
|
||||
const target = event.target;
|
||||
return target instanceof HTMLInputElement ||
|
||||
target instanceof HTMLSelectElement ||
|
||||
target instanceof HTMLTextAreaElement
|
||||
? target.value
|
||||
: '';
|
||||
}
|
||||
|
||||
private updateInventoryCondition(
|
||||
index: number,
|
||||
change: (condition: InventoryCondition) => InventoryCondition,
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
type="checkbox"
|
||||
[checked]="finalizationAttested()"
|
||||
[disabled]="finalizing()"
|
||||
(change)="finalizationAttested.set($any($event.target).checked)"
|
||||
(change)="setFinalizationAttested($event)"
|
||||
/>
|
||||
J’atteste que le scan signé correspond à la dernière révision générée.
|
||||
</label>
|
||||
@@ -71,7 +71,7 @@
|
||||
type="checkbox"
|
||||
[checked]="allPartiesSigned()"
|
||||
[disabled]="finalizing()"
|
||||
(change)="allPartiesSigned.set($any($event.target).checked)"
|
||||
(change)="setAllPartiesSigned($event)"
|
||||
/>
|
||||
J’atteste que chaque partie présente ou son mandataire identifié a signé le document.
|
||||
</label>
|
||||
@@ -112,10 +112,7 @@
|
||||
@if (type === 'ENTRY') {
|
||||
<label>
|
||||
Nature du complément
|
||||
<select
|
||||
[value]="supplementType()"
|
||||
(change)="supplementType.set($any($event.target).value)"
|
||||
>
|
||||
<select [value]="supplementType()" (change)="setSupplementType($event)">
|
||||
<option value="GENERAL">Constat général (délai indicatif de 10 jours)</option>
|
||||
<option value="HEATING">Équipement de chauffage (premier mois de chauffe)</option>
|
||||
</select>
|
||||
@@ -126,7 +123,7 @@
|
||||
type="date"
|
||||
required
|
||||
[value]="supplementRequestedOn()"
|
||||
(input)="supplementRequestedOn.set($any($event.target).value)"
|
||||
(input)="setSupplementRequestedOn($event)"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
@@ -134,7 +131,7 @@
|
||||
<textarea
|
||||
required
|
||||
[value]="supplementAcceptedAdditions()"
|
||||
(input)="supplementAcceptedAdditions.set($any($event.target).value)"
|
||||
(input)="setSupplementAcceptedAdditions($event)"
|
||||
></textarea>
|
||||
</label>
|
||||
@if (supplementType() === 'HEATING') {
|
||||
@@ -144,7 +141,7 @@
|
||||
type="date"
|
||||
required
|
||||
[value]="heatingPeriodStartedOn()"
|
||||
(input)="heatingPeriodStartedOn.set($any($event.target).value)"
|
||||
(input)="setHeatingPeriodStartedOn($event)"
|
||||
/>
|
||||
</label>
|
||||
}
|
||||
@@ -158,24 +155,21 @@
|
||||
<textarea
|
||||
required
|
||||
[value]="supplementReason()"
|
||||
(input)="supplementReason.set($any($event.target).value)"
|
||||
(input)="setSupplementReason($event)"
|
||||
></textarea>
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
[checked]="lateWarningConfirmed()"
|
||||
(change)="lateWarningConfirmed.set($any($event.target).checked)"
|
||||
(change)="setLateWarningConfirmed($event)"
|
||||
/>
|
||||
Je confirme l’ajout malgré le dépassement du délai indicatif.
|
||||
</label>
|
||||
}
|
||||
<label>
|
||||
Zone des prochaines photos
|
||||
<select
|
||||
[value]="supplementPhotoZoneId()"
|
||||
(change)="supplementPhotoZoneId.set($any($event.target).value)"
|
||||
>
|
||||
<select [value]="supplementPhotoZoneId()" (change)="setSupplementPhotoZoneId($event)">
|
||||
<option value="">Choisir une zone</option>
|
||||
@for (zone of value.zones; track zone.id) {
|
||||
<option [value]="zone.id">{{ zone.label }}</option>
|
||||
|
||||
@@ -72,6 +72,12 @@ function eventFor(target: EventTarget): Event {
|
||||
return event;
|
||||
}
|
||||
|
||||
function valueEvent(value: string): Event {
|
||||
const input = document.createElement('input');
|
||||
input.value = value;
|
||||
return eventFor(input);
|
||||
}
|
||||
|
||||
describe('ConditionReportEditor', () => {
|
||||
it('uses a rear-camera input and explains an invalid degraded observation before saving', () => {
|
||||
const service = {
|
||||
@@ -368,13 +374,13 @@ describe('Condition-report sections', () => {
|
||||
fixture.componentRef.setInput('report', next);
|
||||
});
|
||||
|
||||
section.setOccurredOn('2026-07-23');
|
||||
section.setTimeZone('Europe/Paris');
|
||||
section.updateParticipant(0, 'attendance', 'REPRESENTED');
|
||||
section.updateParticipant(0, 'representativeName', 'Alex Martin');
|
||||
section.updateZone('kitchen', 'label', 'Cuisine équipée');
|
||||
section.updateObservation('kitchen', 'state', 'DEGRADE');
|
||||
section.updateObservation('kitchen', 'observation', 'Rayure');
|
||||
section.setOccurredOn(valueEvent('2026-07-23'));
|
||||
section.setTimeZone(valueEvent('Europe/Paris'));
|
||||
section.updateParticipant(0, 'attendance', valueEvent('REPRESENTED'));
|
||||
section.updateParticipant(0, 'representativeName', valueEvent('Alex Martin'));
|
||||
section.updateZone('kitchen', 'label', valueEvent('Cuisine équipée'));
|
||||
section.updateObservation('kitchen', 'state', valueEvent('DEGRADE'));
|
||||
section.updateObservation('kitchen', 'observation', valueEvent('Rayure'));
|
||||
section.addZone();
|
||||
|
||||
const added = current.zones.find((zone) => zone.id !== 'kitchen');
|
||||
@@ -446,20 +452,20 @@ describe('Condition-report sections', () => {
|
||||
fixture.componentRef.setInput('report', next);
|
||||
});
|
||||
|
||||
section.updateMeter('meter-1', 'state', 'INACCESSIBLE');
|
||||
section.updateMeter('meter-1', 'state', valueEvent('INACCESSIBLE'));
|
||||
expect(current.meterReadings[0]?.index).toBeNull();
|
||||
section.updateMeter('meter-1', 'state', 'RECORDED');
|
||||
section.updateMeter('meter-1', 'index', '12.5');
|
||||
section.updateMeter('meter-1', 'state', valueEvent('RECORDED'));
|
||||
section.updateMeter('meter-1', 'index', valueEvent('12.5'));
|
||||
section.addMeter();
|
||||
section.removeMeter('meter-1');
|
||||
section.updateAccessMean('key-1', 'quantity', '2');
|
||||
section.updateAccessMean('key-1', 'quantity', valueEvent('2'));
|
||||
section.addAccessMean();
|
||||
section.removeAccessMean('key-1');
|
||||
section.addInventorySplit(0);
|
||||
section.updateInventorySplit(0, 1, 'quantity', '0');
|
||||
section.updateInventorySplit(0, 1, 'state', 'ABSENT');
|
||||
section.updateInventorySplit(0, 1, 'quantity', valueEvent('0'));
|
||||
section.updateInventorySplit(0, 1, 'state', valueEvent('ABSENT'));
|
||||
section.removeInventorySplit(0, 1);
|
||||
section.updateForwardingAddress('tenant-1', '12 rue A');
|
||||
section.updateForwardingAddress('tenant-1', valueEvent('12 rue A'));
|
||||
|
||||
expect(current.meterReadings).toHaveLength(1);
|
||||
expect(current.meterReadings[0]?.index).toBe(0);
|
||||
@@ -503,10 +509,10 @@ describe('Condition-report sections', () => {
|
||||
const file = new File(['photo'], 'cuisine.webp', { type: 'image/webp' });
|
||||
Object.defineProperty(input, 'files', { value: { item: () => file } });
|
||||
|
||||
section.setZoneAbsence('kitchen', 'Accès impossible');
|
||||
section.setZoneAbsence('kitchen', '');
|
||||
section.setEntryPhotoDisposition('entry-photo', 'exitPhotoId', 'exit-photo');
|
||||
section.setEntryPhotoDisposition('entry-photo', 'absenceReason', '');
|
||||
section.setZoneAbsence('kitchen', valueEvent('Accès impossible'));
|
||||
section.setZoneAbsence('kitchen', valueEvent(''));
|
||||
section.setEntryPhotoDisposition('entry-photo', 'exitPhotoId', valueEvent('exit-photo'));
|
||||
section.setEntryPhotoDisposition('entry-photo', 'absenceReason', valueEvent(''));
|
||||
section.selectPhoto('kitchen', { target: input } as unknown as Event);
|
||||
section.uploadPhoto('kitchen');
|
||||
section.removePhoto({
|
||||
|
||||
@@ -171,6 +171,42 @@ export class ConditionReportPage {
|
||||
this.signedScan.set(file);
|
||||
}
|
||||
|
||||
setFinalizationAttested(event: Event): void {
|
||||
this.finalizationAttested.set(this.checked(event));
|
||||
}
|
||||
|
||||
setAllPartiesSigned(event: Event): void {
|
||||
this.allPartiesSigned.set(this.checked(event));
|
||||
}
|
||||
|
||||
setSupplementType(event: Event): void {
|
||||
this.supplementType.set(this.value(event) === 'HEATING' ? 'HEATING' : 'GENERAL');
|
||||
}
|
||||
|
||||
setSupplementRequestedOn(event: Event): void {
|
||||
this.supplementRequestedOn.set(this.value(event));
|
||||
}
|
||||
|
||||
setSupplementAcceptedAdditions(event: Event): void {
|
||||
this.supplementAcceptedAdditions.set(this.value(event));
|
||||
}
|
||||
|
||||
setHeatingPeriodStartedOn(event: Event): void {
|
||||
this.heatingPeriodStartedOn.set(this.value(event));
|
||||
}
|
||||
|
||||
setSupplementReason(event: Event): void {
|
||||
this.supplementReason.set(this.value(event));
|
||||
}
|
||||
|
||||
setLateWarningConfirmed(event: Event): void {
|
||||
this.lateWarningConfirmed.set(this.checked(event));
|
||||
}
|
||||
|
||||
setSupplementPhotoZoneId(event: Event): void {
|
||||
this.supplementPhotoZoneId.set(this.value(event));
|
||||
}
|
||||
|
||||
finalize(): void {
|
||||
const report = this.report();
|
||||
const scan = this.signedScan();
|
||||
@@ -337,6 +373,19 @@ export class ConditionReportPage {
|
||||
photoUrl(type: ConditionReportType, reference: string): string {
|
||||
return this.reports.photoUrl(this.leaseId, type, reference);
|
||||
}
|
||||
|
||||
private value(event: Event): string {
|
||||
const target = event.target;
|
||||
return target instanceof HTMLInputElement ||
|
||||
target instanceof HTMLSelectElement ||
|
||||
target instanceof HTMLTextAreaElement
|
||||
? target.value
|
||||
: '';
|
||||
}
|
||||
|
||||
private checked(event: Event): boolean {
|
||||
return event.target instanceof HTMLInputElement && event.target.checked;
|
||||
}
|
||||
}
|
||||
|
||||
interface ComparisonDisplayLine {
|
||||
|
||||
@@ -3,9 +3,9 @@ import { ActivatedRoute, provideRouter } from '@angular/router';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { vi } from 'vitest';
|
||||
import {
|
||||
ConditionReportService,
|
||||
FurnitureInventory,
|
||||
} from '../../services/condition-report.service';
|
||||
FurnitureInventoryService,
|
||||
} from '../../services/furniture-inventory.service';
|
||||
import { InventoryPage } from './inventory-page';
|
||||
|
||||
const inventory: FurnitureInventory = {
|
||||
@@ -51,7 +51,7 @@ describe('InventoryPage', () => {
|
||||
provide: ActivatedRoute,
|
||||
useValue: { snapshot: { paramMap: { get: () => 'lease-1' } } },
|
||||
},
|
||||
{ provide: ConditionReportService, useValue: service },
|
||||
{ provide: FurnitureInventoryService, useValue: service },
|
||||
],
|
||||
});
|
||||
const fixture = TestBed.createComponent(InventoryPage);
|
||||
@@ -141,7 +141,7 @@ describe('InventoryPage', () => {
|
||||
provide: ActivatedRoute,
|
||||
useValue: { snapshot: { paramMap: { get: () => 'lease-1' } } },
|
||||
},
|
||||
{ provide: ConditionReportService, useValue: service },
|
||||
{ provide: FurnitureInventoryService, useValue: service },
|
||||
],
|
||||
});
|
||||
const fixture = TestBed.createComponent(InventoryPage);
|
||||
|
||||
@@ -2,11 +2,11 @@ import { Component, inject, signal } from '@angular/core';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
import { finalize as rxFinalize } from 'rxjs';
|
||||
import {
|
||||
ConditionReportService,
|
||||
FurnitureInventory,
|
||||
FurnitureInventoryLine,
|
||||
FurnitureInventoryService,
|
||||
FurniturePresence,
|
||||
} from '../../services/condition-report.service';
|
||||
} from '../../services/furniture-inventory.service';
|
||||
import { httpErrorMessage } from '../../services/http-error-message';
|
||||
|
||||
const emptyInventory: FurnitureInventory = {
|
||||
@@ -30,7 +30,7 @@ const emptyInventory: FurnitureInventory = {
|
||||
})
|
||||
export class InventoryPage {
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
private readonly inventories = inject(ConditionReportService);
|
||||
private readonly inventories = inject(FurnitureInventoryService);
|
||||
readonly leaseId = this.route.snapshot.paramMap.get('leaseId') ?? '';
|
||||
readonly inventory = signal<FurnitureInventory>(emptyInventory);
|
||||
readonly loading = signal(true);
|
||||
|
||||
@@ -12,19 +12,6 @@ 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;
|
||||
@@ -206,41 +193,6 @@ export interface SaveConditionReportRequest {
|
||||
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;
|
||||
@@ -280,17 +232,6 @@ export interface ConditionReportComparison {
|
||||
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,
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { FurnitureInventoryService } from './furniture-inventory.service';
|
||||
|
||||
describe('FurnitureInventoryService', () => {
|
||||
let service: FurnitureInventoryService;
|
||||
let http: HttpTestingController;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [provideHttpClient(), provideHttpClientTesting()],
|
||||
});
|
||||
service = TestBed.inject(FurnitureInventoryService);
|
||||
http = TestBed.inject(HttpTestingController);
|
||||
});
|
||||
|
||||
afterEach(() => http.verify());
|
||||
|
||||
it('uses the versioned furniture-inventory contract', () => {
|
||||
service.getInventory('lease-1').subscribe();
|
||||
http.expectOne('/api/leases/lease-1/inventory').flush({});
|
||||
|
||||
service.saveInventory('lease-1', { version: 4, lines: [] }).subscribe();
|
||||
const request = http.expectOne('/api/leases/lease-1/inventory');
|
||||
expect(request.request.method).toBe('PUT');
|
||||
expect(request.request.body).toEqual({ version: 4, lines: [] });
|
||||
request.flush({});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
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 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[];
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class FurnitureInventoryService {
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user