feat(rental): add inventory and condition reports (#7)
This commit is contained in:
1 parent
92d8a3e351
commit
e4be5cd05f
193 files changed
+9697
-483
No files matched your search
@@ -0,0 +1,188 @@
|
||||
import { Component, inject, signal } from '@angular/core';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
import { finalize as rxFinalize } from 'rxjs';
|
||||
import {
|
||||
ConditionReportService,
|
||||
FurnitureInventory,
|
||||
FurnitureInventoryLine,
|
||||
FurniturePresence,
|
||||
} from '../../services/condition-report.service';
|
||||
import { httpErrorMessage } from '../../services/http-error-message';
|
||||
|
||||
const emptyInventory: FurnitureInventory = {
|
||||
id: '',
|
||||
templateVersion: '',
|
||||
lines: [],
|
||||
missingCategories: [],
|
||||
absentCategories: [],
|
||||
legalWarnings: [],
|
||||
readyForLeaseDocument: false,
|
||||
frozen: false,
|
||||
privateAreas: [],
|
||||
version: 0,
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'app-inventory-page',
|
||||
imports: [RouterLink],
|
||||
templateUrl: './inventory-page.html',
|
||||
styleUrl: './inventory-page.css',
|
||||
})
|
||||
export class InventoryPage {
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
private readonly inventories = inject(ConditionReportService);
|
||||
readonly leaseId = this.route.snapshot.paramMap.get('leaseId') ?? '';
|
||||
readonly inventory = signal<FurnitureInventory>(emptyInventory);
|
||||
readonly loading = signal(true);
|
||||
readonly saving = signal(false);
|
||||
readonly error = signal('');
|
||||
readonly success = signal('');
|
||||
|
||||
constructor() {
|
||||
this.load();
|
||||
}
|
||||
|
||||
load(): void {
|
||||
this.loading.set(true);
|
||||
this.error.set('');
|
||||
this.success.set('');
|
||||
this.inventories
|
||||
.getInventory(this.leaseId)
|
||||
.pipe(rxFinalize(() => this.loading.set(false)))
|
||||
.subscribe({
|
||||
next: (inventory) => this.inventory.set(inventory),
|
||||
error: (error) =>
|
||||
this.error.set(
|
||||
httpErrorMessage(error, 'Impossible de charger l’inventaire du mobilier.'),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
updateText(
|
||||
id: string,
|
||||
field: 'privateAreaId' | 'designation' | 'description',
|
||||
event: Event,
|
||||
): void {
|
||||
const value =
|
||||
event.target instanceof HTMLInputElement ||
|
||||
event.target instanceof HTMLTextAreaElement ||
|
||||
event.target instanceof HTMLSelectElement
|
||||
? event.target.value
|
||||
: '';
|
||||
this.updateLine(id, { [field]: value });
|
||||
}
|
||||
|
||||
updateQuantity(id: string, event: Event): void {
|
||||
const input = event.target instanceof HTMLInputElement ? event.target : null;
|
||||
const quantity =
|
||||
!input || input.value === '' || !Number.isFinite(input.valueAsNumber)
|
||||
? -1
|
||||
: input.valueAsNumber;
|
||||
this.updateLine(id, { quantity });
|
||||
}
|
||||
|
||||
updatePresence(id: string, event: Event): void {
|
||||
const line = this.inventory().lines.find((candidate) => candidate.id === id);
|
||||
const value = event.target instanceof HTMLSelectElement ? event.target.value : '';
|
||||
const presence: FurniturePresence | null =
|
||||
value === 'PRESENT' || value === 'ABSENT' ? value : null;
|
||||
if (!line?.category) return;
|
||||
this.updateLine(id, {
|
||||
presence,
|
||||
quantity:
|
||||
presence === 'ABSENT'
|
||||
? 0
|
||||
: presence === 'PRESENT' && line.quantity === 0
|
||||
? 1
|
||||
: line.quantity,
|
||||
});
|
||||
}
|
||||
|
||||
addLine(): void {
|
||||
if (this.inventory().frozen || this.saving()) return;
|
||||
this.inventory.update((inventory) => ({
|
||||
...inventory,
|
||||
lines: [
|
||||
...inventory.lines,
|
||||
{
|
||||
id: crypto.randomUUID(),
|
||||
category: null,
|
||||
presence: null,
|
||||
privateAreaId: '',
|
||||
designation: '',
|
||||
quantity: 1,
|
||||
description: '',
|
||||
},
|
||||
],
|
||||
}));
|
||||
}
|
||||
|
||||
removeLine(id: string): void {
|
||||
if (this.inventory().frozen || this.saving()) return;
|
||||
this.inventory.update((inventory) => ({
|
||||
...inventory,
|
||||
lines: inventory.lines.filter((line) => line.id !== id || line.category !== null),
|
||||
}));
|
||||
}
|
||||
|
||||
warningLabel(category: string): string {
|
||||
return (
|
||||
this.inventory().lines.find((line) => line.category === category)?.designation ?? category
|
||||
);
|
||||
}
|
||||
|
||||
save(): void {
|
||||
const inventory = this.inventory();
|
||||
if (inventory.frozen) {
|
||||
this.error.set('Cet inventaire est figé et ne peut plus être modifié.');
|
||||
return;
|
||||
}
|
||||
const validationError = this.validationError(inventory.lines);
|
||||
if (validationError) {
|
||||
this.error.set(validationError);
|
||||
return;
|
||||
}
|
||||
this.saving.set(true);
|
||||
this.error.set('');
|
||||
this.success.set('');
|
||||
this.inventories
|
||||
.saveInventory(this.leaseId, { version: inventory.version, lines: inventory.lines })
|
||||
.pipe(rxFinalize(() => this.saving.set(false)))
|
||||
.subscribe({
|
||||
next: (saved) => {
|
||||
this.inventory.set(saved);
|
||||
this.success.set('Inventaire enregistré.');
|
||||
},
|
||||
error: (error) =>
|
||||
this.error.set(httpErrorMessage(error, 'Impossible d’enregistrer l’inventaire.')),
|
||||
});
|
||||
}
|
||||
|
||||
private updateLine(id: string, changes: Partial<FurnitureInventoryLine>): void {
|
||||
if (this.inventory().frozen || this.saving()) return;
|
||||
this.inventory.update((inventory) => ({
|
||||
...inventory,
|
||||
lines: inventory.lines.map((line) => (line.id === id ? { ...line, ...changes } : line)),
|
||||
}));
|
||||
}
|
||||
|
||||
private validationError(lines: FurnitureInventoryLine[]): string | null {
|
||||
for (const line of lines) {
|
||||
if (!line.privateAreaId.trim()) return 'Renseignez la zone de chaque ligne.';
|
||||
if (!line.designation.trim()) return 'Renseignez la désignation de chaque ligne.';
|
||||
if (!Number.isInteger(line.quantity) || line.quantity < 0) {
|
||||
return 'Saisissez une quantité entière positive ou nulle.';
|
||||
}
|
||||
if (line.category && line.presence === null) {
|
||||
return 'Déclarez la présence ou l’absence de chaque élément réglementaire.';
|
||||
}
|
||||
if (line.category && line.presence === 'PRESENT' && line.quantity === 0) {
|
||||
return 'Un élément réglementaire présent doit avoir une quantité positive.';
|
||||
}
|
||||
if (line.category && line.presence === 'ABSENT' && line.quantity !== 0) {
|
||||
return 'Un élément réglementaire absent doit avoir une quantité nulle.';
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user