Files
lmnp-organizer/frontend/src/app/components/rent-payment-validation-dialog/rent-payment-validation-dialog.ts
T
nessar b9e99348f7 feat: add rent payment history and reversal dialogs
- Implemented RentPaymentHistoryServiceTest to validate payment retrieval with refunds and reversals.
- Created ReverseRentPaymentServiceTest to ensure correct handling of payment reversals and allocation releases.
- Developed rent-payment-allocation-dialog component with form for surplus allocation.
- Added rent-payment-refund-dialog component for processing refunds with validation.
- Introduced rent-payment-reversal-dialog component for reversing payments with reason input.
- Implemented utility functions for formatting Euro values and handling date formatting.
- Enhanced rent-payment-dialogs.spec.ts to cover new dialog functionalities and error handling.
2026-08-17 14:18:15 +02:00

104 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Component, computed, inject, signal } from '@angular/core';
import {
AbstractControl,
FormControl,
FormGroup,
ReactiveFormsModule,
ValidationErrors,
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';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { toSignal } from '@angular/core/rxjs-interop';
import { finalize } from 'rxjs';
import { RentDue } from '../../services/lease.service';
import { httpErrorMessage } from '../../services/http-error-message';
import { formatEuro, utcIsoDate } from '../../services/rent-payment-format';
import { RentPaymentService } from '../../services/rent-payment.service';
import { isRentPaymentDataStale, RENT_PAYMENT_DATA_STALE } from '../rent-payment-dialog-result';
export interface RentPaymentValidationDialogData {
rentDue: RentDue;
apartmentName: string;
}
@Component({
selector: 'app-rent-payment-validation-dialog',
imports: [
ReactiveFormsModule,
MatDialogModule,
MatButtonModule,
MatFormFieldModule,
MatInputModule,
MatProgressSpinnerModule,
],
templateUrl: './rent-payment-validation-dialog.html',
styleUrl: './rent-payment-validation-dialog.css',
})
export class RentPaymentValidationDialog {
readonly data = inject<RentPaymentValidationDialogData>(MAT_DIALOG_DATA);
private readonly dialogRef = inject(MatDialogRef<RentPaymentValidationDialog>);
private readonly payments = inject(RentPaymentService);
private readonly operationId = crypto.randomUUID();
readonly today = utcIsoDate();
readonly saving = signal(false);
readonly error = signal('');
readonly formatEuro = formatEuro;
readonly form = new FormGroup({
receivedAt: new FormControl(this.today, {
nonNullable: true,
validators: [Validators.required, this.notFuture.bind(this)],
}),
amount: new FormControl(this.data.rentDue.remainingAmount, {
nonNullable: true,
validators: [Validators.required, Validators.min(0.01)],
}),
});
private readonly amount = toSignal(this.form.controls.amount.valueChanges, {
initialValue: this.form.controls.amount.value,
});
readonly allocatedAmount = computed(() =>
Math.min(Math.max(this.amount() ?? 0, 0), this.data.rentDue.remainingAmount),
);
readonly remainingAfter = computed(() =>
Math.max(this.data.rentDue.remainingAmount - this.allocatedAmount(), 0),
);
readonly surplus = computed(() =>
Math.max((this.amount() ?? 0) - this.data.rentDue.remainingAmount, 0),
);
submit(): void {
this.form.markAllAsTouched();
if (this.form.invalid || this.saving()) return;
this.saving.set(true);
this.error.set('');
this.payments
.record({
operationId: this.operationId,
rentDueId: this.data.rentDue.id,
rentDueVersion: this.data.rentDue.version,
...this.form.getRawValue(),
})
.pipe(finalize(() => this.saving.set(false)))
.subscribe({
next: (payment) => this.dialogRef.close(payment),
error: (error) => {
if (isRentPaymentDataStale(error)) {
this.dialogRef.close(RENT_PAYMENT_DATA_STALE);
return;
}
this.error.set(httpErrorMessage(error, 'Impossible d’enregistrer ce règlement.'));
},
});
}
private notFuture(control: AbstractControl<string>): ValidationErrors | null {
return control.value > this.today ? { futureDate: true } : null;
}
}