Skip to content

Commit

Permalink
portalicious: import reconciliation data
Browse files Browse the repository at this point in the history
AB#32154 AB#32158
  • Loading branch information
aberonni committed Dec 19, 2024
1 parent 87c9806 commit aa2493b
Show file tree
Hide file tree
Showing 17 changed files with 542 additions and 204 deletions.
1 change: 1 addition & 0 deletions interfaces/Portalicious/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ module.exports = tseslint.config(
'app-confirmation-dialog[headerClass]',
'app-confirmation-dialog[headerIcon]',
'app-file-upload-control[accept]',
'app-import-file-dialog[accept]',
'app-metric-tile[chipIcon]',
'app-metric-tile[chipVariant]',
'iframe[referrerpolicy]',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<p-button
label="Choose file"
i18n-label
(onClick)="chooseCallback()"
(click)="chooseCallback()"
icon="pi pi-download"
[rounded]="true"
[outlined]="true"
Expand All @@ -39,7 +39,7 @@
<div class="txt-system-s">{{ file.size | filesize }}</div>
<p-button
icon="pi pi-trash"
(onClick)="removeFileCallback(file, $index)"
(click)="removeFileCallback(file, $index)"
[plain]="true"
[text]="true"
styleClass="grow-0"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<p-dialog
[modal]="true"
[dismissableMask]="true"
[closeOnEscape]="false"
[(visible)]="dialogVisible"
(onShow)="resetForm()"
styleClass="max-w-[46rem]"
>
<ng-template pTemplate="header">
<h3>
<i class="pi pi-download me-2"></i>
<ng-container>{{ header() }}</ng-container>
</h3>
</ng-template>

<form
class="space-y-3"
[formGroup]="formGroup"
(ngSubmit)="onFormSubmit()"
>
<ng-content></ng-content>
<div>
<app-file-upload-control
formControlName="file"
[accept]="accept()"
(clearFiles)="resetForm()"
></app-file-upload-control>
<app-form-error [error]="formFieldErrors()('file')" />
</div>
@if (mutation().isError()) {
<div class="mt-4">
@let detailedErrors = detailedImportErrors();

@if (detailedErrors) {
<app-form-error
error="Something went wrong with this import. Please fix the errors reported below and try again."
i18n-error
/>

<p-scrollPanel
styleClass="h-32 w-full border border-grey-300 bg-grey-100 p-4"
>
@for (error of detailedErrors; track $index) {
<pre>{{ error | json }}</pre>
}
</p-scrollPanel>
} @else {
<app-form-error [error]="mutation().failureReason()?.message" />
}
</div>
}
<div class="flex justify-end gap-3 pt-2">
<p-button
label="Cancel"
i18n-label="@@generic-cancel"
rounded
outlined
severity="contrast"
(click)="dialogVisible.set(false)"
[disabled]="mutation().isPending()"
/>
<p-button
label="Import file"
i18n-label
type="submit"
rounded
[loading]="mutation().isPending()"
/>
</div>
</form>
</p-dialog>
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { JsonPipe } from '@angular/common';
import { HttpErrorResponse } from '@angular/common/http';
import {
ChangeDetectionStrategy,
Component,
computed,
input,
model,
} from '@angular/core';
import {
FormControl,
FormGroup,
ReactiveFormsModule,
Validators,
} from '@angular/forms';

import { CreateMutationResult } from '@tanstack/angular-query-experimental';
import { ButtonModule } from 'primeng/button';
import { DialogModule } from 'primeng/dialog';
import { ScrollPanelModule } from 'primeng/scrollpanel';

import { FileUploadControlComponent } from '~/components/file-upload-control/file-upload-control.component';
import { FormErrorComponent } from '~/components/form-error/form-error.component';
import {
generateFieldErrors,
genericFieldIsRequiredValidationMessage,
} from '~/utils/form-validation';

export type ImportFileDialogFormGroup =
(typeof ImportFileDialogComponent)['prototype']['formGroup'];

@Component({
selector: 'app-import-file-dialog',
standalone: true,
imports: [
DialogModule,
ReactiveFormsModule,
ButtonModule,
FormErrorComponent,
FileUploadControlComponent,
ScrollPanelModule,
JsonPipe,
],
templateUrl: './import-file-dialog.component.html',
styles: ``,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ImportFileDialogComponent {
mutation =
// eslint-disable-next-line @typescript-eslint/no-explicit-any
input.required<CreateMutationResult<any, Error, any, any>>();
accept = input.required<string>();
header = input.required<string>();
dialogVisible = model<boolean>(false);

formGroup = new FormGroup({
file: new FormControl<File | null>(null, {
// eslint-disable-next-line @typescript-eslint/unbound-method
validators: [Validators.required],
}),
});

formFieldErrors = generateFieldErrors<ImportFileDialogFormGroup>(
this.formGroup,
{
file: genericFieldIsRequiredValidationMessage,
},
);

detailedImportErrors = computed(() => {
const error = this.mutation().failureReason();

if (error instanceof HttpErrorResponse) {
if (Array.isArray(error.error)) {
return error.error as unknown[];
}

return [error.error as unknown];
}

return undefined;
});

resetForm(): void {
this.formGroup.reset();
this.mutation().reset();
}

onFormSubmit(): void {
this.formGroup.markAllAsTouched();

if (!this.formGroup.valid) {
return;
}

this.mutation().mutate(this.formGroup.getRawValue());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,12 @@ export class RegistrationsTableComponent {
field: 'status',
header: $localize`:@@registration-status:Status`,
type: QueryTableColumnType.MULTISELECT,
options: Object.entries(REGISTRATION_STATUS_LABELS).map(
([value, label]) => ({
label,
value,
}),
),
options: Object.values(RegistrationStatusEnum)
.filter((status) => status !== RegistrationStatusEnum.deleted)
.map((status) => ({
label: REGISTRATION_STATUS_LABELS[status],
value: status,
})),
getCellChipData: (registration) =>
getChipDataByRegistrationStatus(registration.status),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { HttpParamsOptions } from '@angular/common/http';
import { Injectable, Signal } from '@angular/core';

import { ExportType } from '@121-service/src/metrics/enum/export-type.enum';
import { RegistrationStatusEnum } from '@121-service/src/registration/enum/registration-status.enum';

import { DomainApiService } from '~/domains/domain-api.service';
import {
Expand Down Expand Up @@ -60,11 +59,7 @@ export class MetricApiService extends DomainApiService {
maxPayment: payment,
},
processResponse: (response) => {
// TODO: AB#32158 - Should we filter out deleted transactions here?
return response.data.filter(
(transaction) =>
transaction.registrationStatus !== RegistrationStatusEnum.deleted,
);
return response.data;
},
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { Injectable, Signal } from '@angular/core';

import { CreatePaymentDto } from '@121-service/src/payments/dto/create-payment.dto';
import { FspInstructions } from '@121-service/src/payments/dto/fsp-instructions.dto';
import { GetImportTemplateResponseDto } from '@121-service/src/payments/dto/get-import-template-response.dto';
import { RetryPaymentDto } from '@121-service/src/payments/dto/retry-payment.dto';
import { BulkActionResultPaymentDto } from '@121-service/src/registration/dto/bulk-action-result.dto';
import { ImportResult } from '@121-service/src/registration/dto/bulk-import.dto';

import { DomainApiService } from '~/domains/domain-api.service';
import {
Expand Down Expand Up @@ -111,6 +113,40 @@ export class PaymentApiService extends DomainApiService {
});
}

getReconciliationDataTemplates(projectId: Signal<number>) {
return this.generateQueryOptions<Dto<GetImportTemplateResponseDto>[]>({
path: [
...BASE_ENDPOINT(projectId),
'fsp-reconciliation',
'import-template',
],
});
}

importReconciliationData({
projectId,
paymentId,
file,
}: {
projectId: Signal<number>;
paymentId: Signal<number>;
file: File;
}) {
const formData = new FormData();
formData.append('file', file);

return this.httpWrapperService.perform121ServiceRequest<Dto<ImportResult>>({
method: 'POST',
endpoint: this.pathToQueryKey([
...BASE_ENDPOINT(projectId),
paymentId,
'fsp-reconciliation',
]).join('/'),
body: formData,
isUpload: true,
});
}

public invalidateCache(
projectId: Signal<number>,
paymentId?: Signal<number>,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
@if (canImportReconciliationData()) {
<p-button
label="Import reconciliation data"
i18n-label
(click)="importReconciliationData()"
[disabled]="paymentInProgress.isPending()"
icon="pi pi-download"
outlined
rounded
></p-button>

<app-import-file-dialog
[(dialogVisible)]="dialogVisible"
[mutation]="importReconciliationDataMutation"
accept=".csv,text/csv,text/comma-separated-values,application/csv"
header="Import reconciliation data"
i18n-header
>
<p i18n>
Import the file sent by the FSP to see the transfer status of the
registrations included in this payment. Download the template
<p-button
[link]="true"
(click)="downloadReconciliationTemplatesMutation.mutate()"
>here</p-button
>.
</p>
<p i18n>
Make sure to upload the correct file as this will override the current
file.
</p>
<p>
<span i18n
>The status column in the file should contain one of the following
values:</span
>
<!-- eslint-disable-next-line @angular-eslint/template/i18n -->
<span class="ml-1 font-mono">success, error.</span>
</p>
</app-import-file-dialog>
}
Loading

0 comments on commit aa2493b

Please sign in to comment.