feat: enhance lease and rent payment management
- Added methods in LeaseRepository to find leases by apartment ID and exclude a specific lease ID. - Introduced a method in RentPaymentRepository to delete all payments by lease ID. - Updated LeaseService to handle overlapping leases and generate rent payments upon lease creation. - Implemented rent payment deletion in LeaseService when a lease is deleted. - Enhanced RentPaymentService to generate rent payments with pro-rata calculations for partial months. - Modified RentPaymentController to accept rent and charges when marking payments as paid. - Created RentPaymentPaidRequest DTO for validating rent payment requests. - Added a RentPaymentValidationDialog for confirming payment amounts before processing. - Updated frontend components to support new rent payment features, including validation and date filtering for leases. - Adjusted tests to cover new functionalities and ensure proper integration of rent payment management.
This commit is contained in:
1 parent
479012d661
commit
9cfb55d79c
25 files changed
+389
-175
No files matched your search
@@ -8,7 +8,7 @@
|
||||
<dl class="terms">
|
||||
<div>
|
||||
<dt>Période</dt>
|
||||
<dd>{{ lease().startDate }} → {{ lease().endDate }}</dd>
|
||||
<dd>{{ lease().startDate | date:'dd MMMM yyyy' }} → {{ lease().endDate | date:'dd MMMM yyyy' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Charges</dt>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component, input, output } from '@angular/core';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { Apartment } from '../../services/apartment.service';
|
||||
@@ -7,7 +8,7 @@ import { Tenant } from '../../services/tenant.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-lease-card',
|
||||
imports: [MatButtonModule, MatProgressSpinnerModule],
|
||||
imports: [MatButtonModule, MatProgressSpinnerModule, DatePipe],
|
||||
templateUrl: './lease-card.html',
|
||||
styleUrl: './lease-card.css',
|
||||
})
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
><input
|
||||
matInput
|
||||
[matDatepicker]="startDatePicker"
|
||||
[matDatepickerFilter]="dateFilter"
|
||||
name="startDate"
|
||||
[ngModel]="startDate()"
|
||||
(ngModelChange)="startDate.set($event)"
|
||||
@@ -99,6 +100,8 @@
|
||||
><input
|
||||
matInput
|
||||
[matDatepicker]="endDatePicker"
|
||||
[matDatepickerFilter]="dateFilter"
|
||||
[min]="startDate()"
|
||||
name="endDate"
|
||||
[ngModel]="endDate()"
|
||||
(ngModelChange)="endDate.set($event)"
|
||||
|
||||
@@ -29,6 +29,7 @@ import { GuarantorForm } from '../guarantor-form/guarantor-form';
|
||||
export class LeaseForm {
|
||||
readonly apartments = input.required<Apartment[]>();
|
||||
readonly tenants = input.required<Tenant[]>();
|
||||
readonly leases = input<Lease[]>([]);
|
||||
readonly saving = input(false);
|
||||
readonly saveError = input('');
|
||||
readonly cancelled = output<void>();
|
||||
@@ -43,6 +44,24 @@ export class LeaseForm {
|
||||
readonly guarantors = signal<Guarantor[]>([]);
|
||||
readonly error = signal('');
|
||||
|
||||
readonly dateFilter = (d: Date | null): boolean => {
|
||||
if (!d) return false;
|
||||
const aptId = this.apartmentId();
|
||||
if (!aptId) return true;
|
||||
|
||||
const time = new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
|
||||
|
||||
const overlapping = this.leases().some(lease => {
|
||||
if (lease.apartmentId !== aptId) return false;
|
||||
const [startYear, startMonth, startDay] = lease.startDate.split('-').map(Number);
|
||||
const [endYear, endMonth, endDay] = lease.endDate.split('-').map(Number);
|
||||
const start = new Date(startYear, startMonth - 1, startDay).getTime();
|
||||
const end = new Date(endYear, endMonth - 1, endDay).getTime();
|
||||
return time >= start && time <= end;
|
||||
});
|
||||
return !overlapping;
|
||||
};
|
||||
|
||||
addGuarantor(guarantor: Guarantor): void {
|
||||
this.guarantors.update((guarantors) => [...guarantors, guarantor]);
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
<app-lease-form
|
||||
[apartments]="workspace.apartments()"
|
||||
[tenants]="workspace.tenants()"
|
||||
[leases]="workspace.leases()"
|
||||
[saving]="workspace.leaseSaving()"
|
||||
[saveError]="workspace.leaseSaveError()"
|
||||
(cancelled)="closeForm()"
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
.form-dialog-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.form-fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
<h2 mat-dialog-title>Valider le paiement</h2>
|
||||
<form [formGroup]="form" (ngSubmit)="submit()">
|
||||
<mat-dialog-content class="form-dialog-content">
|
||||
<p>Vérifiez ou modifiez les montants payés avant de valider l'échéance.</p>
|
||||
<div class="form-fields">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Loyer principal (€)</mat-label>
|
||||
<input matInput type="number" step="0.01" min="0" formControlName="rent" required />
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Charges (€)</mat-label>
|
||||
<input matInput type="number" step="0.01" min="0" formControlName="charges" required />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-button mat-dialog-close type="button">Annuler</button>
|
||||
<button mat-flat-button type="submit" [disabled]="form.invalid">Valider le paiement</button>
|
||||
</mat-dialog-actions>
|
||||
</form>
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
|
||||
export interface RentPaymentValidationDialogData {
|
||||
rent: number;
|
||||
charges: number;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-rent-payment-validation-dialog',
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
MatDialogModule,
|
||||
MatButtonModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
],
|
||||
templateUrl: './rent-payment-validation-dialog.html',
|
||||
styleUrl: './rent-payment-validation-dialog.css',
|
||||
})
|
||||
export class RentPaymentValidationDialog {
|
||||
readonly data = inject<RentPaymentValidationDialogData>(MAT_DIALOG_DATA);
|
||||
readonly dialogRef = inject(MatDialogRef<RentPaymentValidationDialog>);
|
||||
|
||||
readonly form = new FormGroup({
|
||||
rent: new FormControl(this.data.rent, {
|
||||
nonNullable: true,
|
||||
validators: [Validators.required, Validators.min(0)],
|
||||
}),
|
||||
charges: new FormControl(this.data.charges, {
|
||||
nonNullable: true,
|
||||
validators: [Validators.required, Validators.min(0)],
|
||||
}),
|
||||
});
|
||||
|
||||
submit(): void {
|
||||
if (this.form.valid) {
|
||||
this.dialogRef.close(this.form.getRawValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,100 +1,101 @@
|
||||
<section class="page-section">
|
||||
<header class="page-heading">
|
||||
<div>
|
||||
<p class="eyebrow">Location</p>
|
||||
<h1>Mes loyers</h1>
|
||||
<p>Suivez les échéances et téléchargez les quittances après règlement.</p>
|
||||
</div>
|
||||
</header>
|
||||
<header class="page-heading">
|
||||
<div>
|
||||
<p class="eyebrow">Location</p>
|
||||
<h1>Mes loyers</h1>
|
||||
<p>Suivez les échéances et téléchargez les quittances après règlement.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="content-region" [attr.aria-busy]="contentLoading()">
|
||||
@if (!contentReady() && loadError()) {
|
||||
<article class="load-error" role="alert">
|
||||
<h2>Chargement impossible</h2>
|
||||
<p>{{ loadError() }}</p>
|
||||
<button mat-stroked-button type="button" (click)="load()">Réessayer</button>
|
||||
</article>
|
||||
} @else if (!contentReady()) {
|
||||
<div class="rent-payment-grid" aria-label="Chargement des loyers">
|
||||
@for (placeholder of [1, 2, 3]; track placeholder) {
|
||||
<article class="skeleton-card" aria-hidden="true">
|
||||
<span class="skeleton-line skeleton-line-short"></span>
|
||||
<span class="skeleton-line skeleton-line-title"></span>
|
||||
<span class="skeleton-line skeleton-line-medium"></span>
|
||||
</article>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
@if (contentLoading()) {
|
||||
<mat-progress-bar mode="indeterminate" aria-label="Actualisation des loyers" />
|
||||
}
|
||||
@if (loadError()) {
|
||||
<div class="refresh-error" role="alert">
|
||||
<p>{{ loadError() }}</p>
|
||||
<button mat-button type="button" (click)="load()">Réessayer</button>
|
||||
</div>
|
||||
}
|
||||
@if (workspace.rentPayments().length === 0) {
|
||||
<article class="empty-state">
|
||||
<p class="eyebrow">Échéancier</p>
|
||||
<h2>Aucune échéance de loyer</h2>
|
||||
<p>Les échéances sont créées automatiquement pour les baux actifs.</p>
|
||||
</article>
|
||||
} @else {
|
||||
<div class="rent-payment-grid">
|
||||
@for (payment of workspace.rentPayments(); track payment.id) {
|
||||
<article class="rent-payment" [attr.aria-busy]="workspace.rentPaymentSaving(payment.id)">
|
||||
<div class="payment-topline">
|
||||
<span class="payment-status" [class]="payment.status.toLowerCase()">
|
||||
{{ statusLabel(payment.status) }}
|
||||
</span>
|
||||
<strong>{{ payment.total.toLocaleString('fr-FR') }} €</strong>
|
||||
</div>
|
||||
<h2>{{ apartmentName(payment) }}</h2>
|
||||
<p class="payment-month">{{ monthLabel(payment.month) }}</p>
|
||||
<dl>
|
||||
<div><dt>Loyer</dt><dd>{{ payment.rent.toLocaleString('fr-FR') }} €</dd></div>
|
||||
<div><dt>Charges</dt><dd>{{ payment.charges.toLocaleString('fr-FR') }} €</dd></div>
|
||||
<div><dt>Échéance</dt><dd>{{ payment.dueDate }}</dd></div>
|
||||
</dl>
|
||||
@if (workspace.rentPaymentSaveError(payment.id) || workspace.rentReceiptDownloadError(payment.id)) {
|
||||
<p class="card-error" role="alert">
|
||||
{{ workspace.rentPaymentSaveError(payment.id) || workspace.rentReceiptDownloadError(payment.id) }}
|
||||
</p>
|
||||
}
|
||||
<div class="card-actions" aria-live="polite">
|
||||
@if (payment.status === 'PAID') {
|
||||
<button
|
||||
mat-button
|
||||
type="button"
|
||||
[disabled]="workspace.rentReceiptDownloading(payment.id)"
|
||||
(click)="workspace.downloadRentReceipt(payment)"
|
||||
>
|
||||
@if (workspace.rentReceiptDownloading(payment.id)) {
|
||||
<mat-spinner diameter="16" aria-hidden="true" /> Téléchargement…
|
||||
} @else {
|
||||
Télécharger la quittance
|
||||
}
|
||||
</button>
|
||||
} @else {
|
||||
<button
|
||||
mat-flat-button
|
||||
type="button"
|
||||
[disabled]="workspace.rentPaymentSaving(payment.id)"
|
||||
(click)="workspace.markRentPaymentPaid(payment.id)"
|
||||
>
|
||||
@if (workspace.rentPaymentSaving(payment.id)) {
|
||||
<mat-spinner diameter="16" aria-hidden="true" /> Enregistrement…
|
||||
} @else {
|
||||
Marquer comme payé
|
||||
}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</article>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
<div class="content-region" [attr.aria-busy]="contentLoading()">
|
||||
@if (!contentReady() && loadError()) {
|
||||
<article class="load-error" role="alert">
|
||||
<h2>Chargement impossible</h2>
|
||||
<p>{{ loadError() }}</p>
|
||||
<button mat-stroked-button type="button" (click)="load()">Réessayer</button>
|
||||
</article>
|
||||
} @else if (!contentReady()) {
|
||||
<div class="rent-payment-grid" aria-label="Chargement des loyers">
|
||||
@for (placeholder of [1, 2, 3]; track placeholder) {
|
||||
<article class="skeleton-card" aria-hidden="true">
|
||||
<span class="skeleton-line skeleton-line-short"></span>
|
||||
<span class="skeleton-line skeleton-line-title"></span>
|
||||
<span class="skeleton-line skeleton-line-medium"></span>
|
||||
</article>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
@if (contentLoading()) {
|
||||
<mat-progress-bar mode="indeterminate" aria-label="Actualisation des loyers" />
|
||||
}
|
||||
@if (loadError()) {
|
||||
<div class="refresh-error" role="alert">
|
||||
<p>{{ loadError() }}</p>
|
||||
<button mat-button type="button" (click)="load()">Réessayer</button>
|
||||
</div>
|
||||
}
|
||||
@if (workspace.rentPayments().length === 0) {
|
||||
<article class="empty-state">
|
||||
<p class="eyebrow">Échéancier</p>
|
||||
<h2>Aucune échéance de loyer</h2>
|
||||
<p>Les échéances sont créées automatiquement pour les baux actifs.</p>
|
||||
</article>
|
||||
} @else {
|
||||
<div class="rent-payment-grid">
|
||||
@for (payment of workspace.rentPayments(); track payment.id) {
|
||||
<article class="rent-payment" [attr.aria-busy]="workspace.rentPaymentSaving(payment.id)">
|
||||
<div class="payment-topline">
|
||||
<span class="payment-status" [class]="payment.status.toLowerCase()">
|
||||
{{ statusLabel(payment.status) }}
|
||||
</span>
|
||||
<strong>{{ payment.total.toLocaleString('fr-FR') }} €</strong>
|
||||
</div>
|
||||
<h2>{{ apartmentName(payment) }}</h2>
|
||||
<p class="payment-month">{{ monthLabel(payment.month) }}</p>
|
||||
<dl>
|
||||
<div>
|
||||
<dt>Loyer</dt>
|
||||
<dd>{{ payment.rent.toLocaleString('fr-FR') }} €</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Charges</dt>
|
||||
<dd>{{ payment.charges.toLocaleString('fr-FR') }} €</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Échéance</dt>
|
||||
<dd>{{ payment.dueDate | date:'dd MMMM yyyy' }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
@if (workspace.rentPaymentSaveError(payment.id) || workspace.rentReceiptDownloadError(payment.id)) {
|
||||
<p class="card-error" role="alert">
|
||||
{{ workspace.rentPaymentSaveError(payment.id) || workspace.rentReceiptDownloadError(payment.id) }}
|
||||
</p>
|
||||
}
|
||||
<div class="card-actions" aria-live="polite">
|
||||
@if (payment.status === 'PAID') {
|
||||
<button mat-button type="button" [disabled]="workspace.rentReceiptDownloading(payment.id)"
|
||||
(click)="workspace.downloadRentReceipt(payment)">
|
||||
@if (workspace.rentReceiptDownloading(payment.id)) {
|
||||
<mat-spinner diameter="16" aria-hidden="true" /> Téléchargement…
|
||||
} @else {
|
||||
Télécharger la quittance
|
||||
}
|
||||
</button>
|
||||
} @else {
|
||||
<button mat-flat-button type="button" [disabled]="workspace.rentPaymentSaving(payment.id)"
|
||||
(click)="markPaid(payment)">
|
||||
@if (workspace.rentPaymentSaving(payment.id)) {
|
||||
<mat-spinner diameter="16" aria-hidden="true" /> Enregistrement…
|
||||
} @else {
|
||||
Marquer comme payé
|
||||
}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</article>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,5 +1,7 @@
|
||||
import { signal } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { of } from 'rxjs';
|
||||
import { WorkspaceService } from '../../services/workspace.service';
|
||||
import { RentPaymentsPage } from './rent-payments-page';
|
||||
|
||||
@@ -34,8 +36,14 @@ describe('RentPaymentsPage', () => {
|
||||
rentReceiptDownloading: vi.fn(() => false),
|
||||
rentReceiptDownloadError: vi.fn(() => ''),
|
||||
};
|
||||
const dialog = {
|
||||
open: vi.fn(() => ({ afterClosed: () => of({ rent: 800, charges: 50 }) })),
|
||||
};
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{ provide: WorkspaceService, useValue: workspace }],
|
||||
providers: [
|
||||
{ provide: WorkspaceService, useValue: workspace },
|
||||
{ provide: MatDialog, useValue: dialog },
|
||||
],
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(RentPaymentsPage);
|
||||
@@ -44,6 +52,7 @@ describe('RentPaymentsPage', () => {
|
||||
expect(workspace.loadRentPayments).toHaveBeenCalled();
|
||||
expect(fixture.nativeElement.textContent).toContain('T2');
|
||||
fixture.nativeElement.querySelector('button').click();
|
||||
expect(workspace.markRentPaymentPaid).toHaveBeenCalledWith('payment-1');
|
||||
expect(dialog.open).toHaveBeenCalled();
|
||||
expect(workspace.markRentPaymentPaid).toHaveBeenCalledWith('payment-1', 800, 50);
|
||||
});
|
||||
});
|
||||
@@ -4,15 +4,19 @@ import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { RentPayment, RentPaymentStatus } from '../../services/rent-payment.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],
|
||||
imports: [MatButtonModule, MatProgressBarModule, MatProgressSpinnerModule, DatePipe],
|
||||
templateUrl: './rent-payments-page.html',
|
||||
styleUrl: './rent-payments-page.css',
|
||||
})
|
||||
export class RentPaymentsPage {
|
||||
readonly workspace = inject(WorkspaceService);
|
||||
private readonly dialog = inject(MatDialog);
|
||||
readonly contentReady = computed(
|
||||
() =>
|
||||
this.workspace.rentPaymentLoadState().loaded &&
|
||||
@@ -56,4 +60,18 @@ export class RentPaymentsPage {
|
||||
this.workspace.loadLeases();
|
||||
this.workspace.loadApartments();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -15,11 +15,11 @@ describe('RentPaymentService', () => {
|
||||
const service = TestBed.inject(RentPaymentService);
|
||||
|
||||
service.getRentPayments().subscribe();
|
||||
service.markPaid('payment-1').subscribe();
|
||||
service.markPaid('payment-1', 850, 50).subscribe();
|
||||
service.downloadReceipt('payment-1').subscribe();
|
||||
|
||||
expect(http.get).toHaveBeenCalledWith('/api/rent-payments');
|
||||
expect(http.put).toHaveBeenCalledWith('/api/rent-payments/payment-1/paid', {});
|
||||
expect(http.put).toHaveBeenCalledWith('/api/rent-payments/payment-1/paid', { rent: 850, charges: 50 });
|
||||
expect(http.get).toHaveBeenCalledWith('/api/rent-payments/payment-1/receipt', {
|
||||
responseType: 'blob',
|
||||
});
|
||||
|
||||
@@ -24,8 +24,8 @@ export class RentPaymentService {
|
||||
return this.http.get<RentPayment[]>('/api/rent-payments');
|
||||
}
|
||||
|
||||
markPaid(id: string): Observable<RentPayment> {
|
||||
return this.http.put<RentPayment>(`/api/rent-payments/${id}/paid`, {});
|
||||
markPaid(id: string, rent: number, charges: number): Observable<RentPayment> {
|
||||
return this.http.put<RentPayment>(`/api/rent-payments/${id}/paid`, { rent, charges });
|
||||
}
|
||||
|
||||
downloadReceipt(id: string): Observable<Blob> {
|
||||
|
||||
@@ -348,7 +348,7 @@ describe('WorkspaceService', () => {
|
||||
service.saveLease(lease);
|
||||
service.deleteLease(lease.id);
|
||||
service.loadRentPayments();
|
||||
service.markRentPaymentPaid(rentPayment.id);
|
||||
service.markRentPaymentPaid(rentPayment.id, rentPayment.rent, rentPayment.charges);
|
||||
|
||||
expect(service.apartments()).toEqual([apartment]);
|
||||
expect(service.tenants()).toEqual([tenant]);
|
||||
@@ -357,7 +357,7 @@ describe('WorkspaceService', () => {
|
||||
expect(apartments.updateApartment).toHaveBeenCalledWith('apartment-1', apartment);
|
||||
expect(tenants.updateTenant).toHaveBeenCalledWith('tenant-1', tenant);
|
||||
expect(leases.deleteLease).toHaveBeenCalledWith('lease-1');
|
||||
expect(rentPayments.markPaid).toHaveBeenCalledWith('payment-1');
|
||||
expect(rentPayments.markPaid).toHaveBeenCalledWith('payment-1', rentPayment.rent, rentPayment.charges);
|
||||
});
|
||||
|
||||
it('opens a confirmation pop-in before deleting a resource', () => {
|
||||
|
||||
@@ -242,8 +242,8 @@ export class WorkspaceService {
|
||||
this.rentPaymentResource.load();
|
||||
}
|
||||
|
||||
markRentPaymentPaid(id: string): void {
|
||||
this.rentPaymentResource.save(id, this.rentPaymentService.markPaid(id));
|
||||
markRentPaymentPaid(id: string, rent: number, charges: number): void {
|
||||
this.rentPaymentResource.save(id, this.rentPaymentService.markPaid(id, rent, charges));
|
||||
}
|
||||
|
||||
rentPaymentSaving(id: string): boolean {
|
||||
|
||||
Reference in new issue
Block a user