fix: use rent dues in rent schedule (#28)

This commit is contained in:
nessar committed 2026-08-17 14:18:15 +02:00
1 parent c2afe515c0
commit a039243d03
4 files changed
+274 -230

No files matched your search

@@ -1,44 +1,39 @@
import { Component, computed, inject, signal } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { RentPayment, RentPaymentStatus } from '../../services/rent-payment.service';
import { currentRentSchedule, sortRentPayments } from '../../services/rent-payment-schedule';
import { RentDue } from '../../services/lease.service';
import { WorkspaceService } from '../../services/workspace.service';
import { MatDialog } from '@angular/material/dialog';
import { RentPaymentValidationDialog } from '../rent-payment-validation-dialog/rent-payment-validation-dialog';
import { DatePipe } from '@angular/common';
@Component({
selector: 'app-rent-payments-page',
imports: [MatButtonModule, MatProgressBarModule, MatProgressSpinnerModule, DatePipe],
imports: [MatButtonModule, MatProgressBarModule, DatePipe],
templateUrl: './rent-payments-page.html',
styleUrl: './rent-payments-page.css',
})
export class RentPaymentsPage {
readonly workspace = inject(WorkspaceService);
private readonly dialog = inject(MatDialog);
readonly showFullSchedule = signal(false);
readonly displayedPayments = computed(() =>
readonly displayedRentDues = computed(() =>
this.showFullSchedule()
? sortRentPayments(this.workspace.rentPayments())
: currentRentSchedule(this.workspace.rentPayments()),
? sortRentDues(this.workspace.rentDues())
: currentRentDues(this.workspace.rentDues()),
);
readonly contentReady = computed(
() =>
this.workspace.rentPaymentLoadState().loaded &&
this.workspace.rentDueLoadState().loaded &&
this.workspace.leaseLoadState().loaded &&
this.workspace.apartmentLoadState().loaded,
);
readonly contentLoading = computed(
() =>
this.workspace.rentPaymentLoadState().loading ||
this.workspace.rentDueLoadState().loading ||
this.workspace.leaseLoadState().loading ||
this.workspace.apartmentLoadState().loading,
);
readonly loadError = computed(
() =>
this.workspace.rentPaymentLoadState().error ||
this.workspace.rentDueLoadState().error ||
this.workspace.leaseLoadState().error ||
this.workspace.apartmentLoadState().error,
);
@@ -47,9 +42,12 @@ export class RentPaymentsPage {
this.load();
}
apartmentName(payment: RentPayment): string {
const lease = this.workspace.leases().find((item) => item.id === payment.leaseId);
return this.workspace.apartments().find((item) => item.id === lease?.apartmentId)?.name ?? 'Bien inconnu';
apartmentName(rentDue: RentDue): string {
const lease = this.workspace.leases().find((item) => item.id === rentDue.leaseId);
return (
this.workspace.apartments().find((item) => item.id === lease?.apartmentId)?.name ??
'Bien inconnu'
);
}
monthLabel(month: string): string {
@@ -58,12 +56,18 @@ export class RentPaymentsPage {
);
}
statusLabel(status: RentPaymentStatus): string {
return { PAID: 'Payé', LATE: 'En retard', UPCOMING: 'À venir' }[status];
settlementLabel(settlement: RentDue['settlement']): string {
return { UNPAID: 'Non réglée', PARTIALLY_PAID: 'Partiellement réglée', PAID: 'Réglée' }[
settlement
];
}
timingLabel(timing: RentDue['timing']): string {
return { UPCOMING: 'À venir', OVERDUE: 'En retard' }[timing];
}
load(): void {
this.workspace.loadRentPayments();
this.workspace.loadRentDues();
this.workspace.loadLeases();
this.workspace.loadApartments();
}
@@ -71,19 +75,33 @@ export class RentPaymentsPage {
toggleSchedule(): void {
this.showFullSchedule.update((showFullSchedule) => !showFullSchedule);
}
}
markPaid(payment: RentPayment): void {
this.dialog
.open(RentPaymentValidationDialog, {
data: { rent: payment.rent, charges: payment.charges },
width: '400px',
})
.afterClosed()
.subscribe((result) => {
if (result) {
this.workspace.markRentPaymentPaid(payment.id, result.rent, result.charges);
}
});
function sortRentDues(rentDues: readonly RentDue[]): RentDue[] {
return [...rentDues].sort((left, right) => left.dueDate.localeCompare(right.dueDate));
}
function currentRentDues(rentDues: readonly RentDue[]): RentDue[] {
const rentDuesByLease = new Map<string, RentDue[]>();
for (const rentDue of sortRentDues(rentDues)) {
const schedule = rentDuesByLease.get(rentDue.leaseId);
if (schedule) schedule.push(rentDue);
else rentDuesByLease.set(rentDue.leaseId, [rentDue]);
}
return sortRentDues([...rentDuesByLease.values()].flatMap(currentSchedule));
}
function currentSchedule(schedule: RentDue[]): RentDue[] {
const nextDueIndex = schedule.findIndex((rentDue) => rentDue.timing === 'UPCOMING');
const throughNextDue = nextDueIndex === -1 ? schedule : schedule.slice(0, nextDueIndex + 1);
const lastPaidIndex = throughNextDue.reduce(
(lastPaid, rentDue, index) => (rentDue.settlement === 'PAID' ? index : lastPaid),
-1,
);
const firstUnpaidIndex = throughNextDue.findIndex((rentDue) => rentDue.settlement !== 'PAID');
const firstVisibleIndex = Math.min(
...[lastPaidIndex, firstUnpaidIndex].filter((index) => index !== -1),
);
return firstVisibleIndex === Infinity ? [] : throughNextDue.slice(firstVisibleIndex);
}