TD-373: Load invoices and refunds in payments (#83)

This commit is contained in:
Rinat Arsaev 2022-08-16 17:56:18 +03:00 committed by GitHub
parent f0a1714760
commit 418b418cad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 68 additions and 46 deletions

View File

@ -1,11 +1,9 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
import { Invoice, PaymentSearchResult } from '@vality/swag-anapi-v2';
import isEmpty from 'lodash-es/isEmpty';
import isNil from 'lodash-es/isNil';
import isObject from 'lodash-es/isObject';
import { Observable } from 'rxjs';
import { ComponentChange, ComponentChanges } from '@dsh/type-utils';
import { ComponentChanges } from '@dsh/type-utils';
import { PaymentIds } from '../../types';
import { InvoiceDetailsService } from './services/invoice-details/invoice-details.service';
@ -15,10 +13,11 @@ import { isPaymentFlowHold } from './types/is-payment-flow-hold';
selector: 'dsh-payment-details',
templateUrl: 'payment-details.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [InvoiceDetailsService],
})
export class PaymentDetailsComponent implements OnChanges {
@Input() payment: PaymentSearchResult;
@Output() refreshPayment: EventEmitter<PaymentIds> = new EventEmitter();
@Output() refreshPayment = new EventEmitter<PaymentIds>();
get isHoldShown(): boolean {
if (isPaymentFlowHold(this.payment.flow)) {
@ -31,9 +30,9 @@ export class PaymentDetailsComponent implements OnChanges {
constructor(private invoiceDetails: InvoiceDetailsService) {}
ngOnChanges(changes: ComponentChanges<PaymentDetailsComponent>): void {
if (isObject(changes.payment)) {
this.changeInvoiceID(changes.payment);
ngOnChanges({ payment }: ComponentChanges<PaymentDetailsComponent>): void {
if (payment && payment.currentValue) {
this.changeInvoiceID(payment.currentValue);
}
}
@ -41,10 +40,7 @@ export class PaymentDetailsComponent implements OnChanges {
this.refreshPayment.emit(ids);
}
private changeInvoiceID({ currentValue: payment }: ComponentChange<PaymentDetailsComponent, 'payment'>): void {
if (isNil(payment)) {
return;
}
private changeInvoiceID(payment: PaymentSearchResult): void {
this.invoiceDetails.setInvoiceID(payment.invoiceID);
}
}

View File

@ -1,21 +1,23 @@
<ng-container *transloco="let t; scope: 'payment-section'; read: 'paymentSection.paymentDetails.invoiceDetails'">
<div *ngIf="invoice; else loading" fxLayout="column" fxLayoutGap="24px">
<div fxLayout="column" fxLayoutGap="24px">
<div class="payment-invoice-info-title mat-title" fxLayout fxLayoutAlign="space-between center">
<div>
{{ t('title') }}
<span dshSecondaryTitle>#{{ invoice.id }}</span>
<span *ngIf="invoice">#{{ invoice.id }}</span>
</div>
</div>
<dsh-invoice-details [invoice]="invoice"></dsh-invoice-details>
<ng-container *ngIf="!invoice; else details">
<div *ngIf="!isLoading; else loading" class="dsh-body-1">
{{ t('notFound') }}
</div>
<ng-template #loading>
<div *transloco="let c; scope: 'components'; read: 'components.shared'">
<div class="dsh-body-1">{{ c('loading') }}</div>
</div>
</ng-template>
</ng-container>
<ng-template #details>
<dsh-invoice-details [invoice]="invoice"></dsh-invoice-details>
</ng-template>
</div>
<ng-template #loading>
<div class="payment-invoice-info-title mat-title" fxLayout fxLayoutAlign="space-between center">
<div>
{{ t('title') }}
</div>
</div>
<div *transloco="let c; scope: 'components'; read: 'components.shared'">
<div class="dsh-body-1">{{ c('loading') }}</div>
</div>
</ng-template>
</ng-container>

View File

@ -10,4 +10,8 @@ import { Invoice } from '@vality/swag-payments';
// TODO: implement dump component for this + shared one for operations
export class PaymentInvoiceInfoComponent {
@Input() invoice: Invoice;
get isLoading() {
return this.invoice === undefined;
}
}

View File

@ -11,7 +11,6 @@ import { PaymentDetailsComponent } from './payment-details.component';
import { PaymentInvoiceInfoModule } from './payment-invoice-info';
import { PaymentMainInfoModule } from './payment-main-info';
import { RefundsModule } from './refunds';
import { InvoiceDetailsService } from './services/invoice-details/invoice-details.service';
@NgModule({
imports: [
@ -27,6 +26,5 @@ import { InvoiceDetailsService } from './services/invoice-details/invoice-detail
],
declarations: [PaymentDetailsComponent],
exports: [PaymentDetailsComponent],
providers: [InvoiceDetailsService],
})
export class PaymentsDetailsModule {}

View File

@ -7,7 +7,7 @@
<div fxLayout fxLayoutGap="16px" fxLayoutAlign="space-between">
<div>
{{ t('header') }}
<span dshSecondaryTitle>#{{ item.id }}</span>
<span>#{{ item.id }}</span>
</div>
</div>
<dsh-refund-details [refund]="item"></dsh-refund-details>

View File

@ -1,13 +1,15 @@
import { Inject, Injectable } from '@angular/core';
import { SearchRefundsRequestParams, RefundSearchResult } from '@vality/swag-anapi-v2';
import moment from 'moment';
import { Observable } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
import { Observable, switchMap } from 'rxjs';
import { shareReplay, first } from 'rxjs/operators';
import { SearchService } from '@dsh/api/anapi';
import { SEARCH_LIMIT } from '@dsh/app/sections/tokens';
import { DEBOUNCE_FETCHER_ACTION_TIME, PartialFetcher } from '@dsh/app/shared';
import { PaymentInstitutionRealmService } from '../../../../../../../services';
@Injectable()
export class FetchRefundsService extends PartialFetcher<
RefundSearchResult,
@ -20,7 +22,8 @@ export class FetchRefundsService extends PartialFetcher<
@Inject(SEARCH_LIMIT)
private searchLimit: number,
@Inject(DEBOUNCE_FETCHER_ACTION_TIME)
debounceActionTime: number
debounceActionTime: number,
private paymentInstitutionRealmService: PaymentInstitutionRealmService
) {
super(debounceActionTime);
}
@ -29,13 +32,19 @@ export class FetchRefundsService extends PartialFetcher<
{ invoiceID, paymentID }: Pick<SearchRefundsRequestParams, 'invoiceID' | 'paymentID'>,
continuationToken: string
) {
return this.searchService.searchRefunds({
fromTime: moment().subtract(3, 'y').startOf('d').utc().format(),
toTime: moment().endOf('d').utc().format(),
invoiceID,
paymentID,
limit: this.searchLimit,
continuationToken,
});
return this.paymentInstitutionRealmService.realm$.pipe(
first(),
switchMap((paymentInstitutionRealm) =>
this.searchService.searchRefunds({
fromTime: moment().subtract(3, 'y').startOf('d').utc().format(),
toTime: moment().endOf('d').utc().format(),
invoiceID,
paymentID,
limit: this.searchLimit,
continuationToken,
paymentInstitutionRealm,
})
)
);
}
}

View File

@ -3,11 +3,13 @@ import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { Invoice } from '@vality/swag-anapi-v2';
import moment from 'moment';
import { Observable, ReplaySubject } from 'rxjs';
import { distinctUntilChanged, switchMap, tap, pluck } from 'rxjs/operators';
import { distinctUntilChanged, switchMap, tap, map, withLatestFrom } from 'rxjs/operators';
import { SearchService } from '@dsh/api/anapi';
import { ErrorService } from '@dsh/app/shared/services';
import { PaymentInstitutionRealmService } from '../../../../../../services';
@UntilDestroy()
@Injectable()
export class InvoiceDetailsService {
@ -18,7 +20,11 @@ export class InvoiceDetailsService {
private invoiceData$ = new ReplaySubject<Invoice | null>(1);
private innerErrors$ = new ReplaySubject<Error>(1);
constructor(private searchService: SearchService, private errorService: ErrorService) {
constructor(
private searchService: SearchService,
private errorService: ErrorService,
private paymentInstitutionRealmService: PaymentInstitutionRealmService
) {
this.invoice$ = this.invoiceData$.asObservable();
this.error$ = this.innerErrors$.asObservable();
@ -36,15 +42,17 @@ export class InvoiceDetailsService {
tap(() => {
this.resetInvoiceData();
}),
switchMap((invoiceID: string) => {
withLatestFrom(this.paymentInstitutionRealmService.realm$),
switchMap(([invoiceID, paymentInstitutionRealm]) => {
return this.searchService.searchInvoices({
invoiceID,
fromTime: moment().subtract(3, 'y').startOf('d').utc().format(),
toTime: moment().endOf('d').utc().format(),
limit: 1,
paymentInstitutionRealm,
});
}),
pluck('result', 0),
map(({ result }) => result?.[0] ?? null),
untilDestroyed(this)
)
.subscribe({

View File

@ -5,7 +5,8 @@ import { Observable } from 'rxjs';
import { QueryParamsService } from '@dsh/app/shared/services';
import { PaymentInstitutionRealmService, RealmMixService } from '../../services';
import { RealmMixService } from '../../services';
import { PaymentInstitutionRealmService } from '../../services/payment-institution-realm.service';
import { Filters } from './payments-filters';
import { PaymentsExpandedIdManager, FetchPaymentsService } from './services';
import { PaymentSearchFormValue } from './types';
@ -14,7 +15,7 @@ import { PaymentSearchFormValue } from './types';
@Component({
selector: 'dsh-payments',
templateUrl: 'payments.component.html',
providers: [FetchPaymentsService, PaymentsExpandedIdManager, RealmMixService],
providers: [FetchPaymentsService, PaymentsExpandedIdManager, RealmMixService, PaymentInstitutionRealmService],
})
export class PaymentsComponent implements OnInit {
realm$ = this.paymentInstitutionRealmService.realm$;

View File

@ -5,7 +5,7 @@
>
<div fxLayout fxLayoutGap="16px" fxLayoutAlign="space-between">
<div class="mat-title">
{{ t('title') }} <span dshSecondaryTitle>#{{ (invoice$ | async)?.id }}</span>
{{ t('title') }} <span>#{{ (invoice$ | async)?.id }}</span>
</div>
</div>
<ng-container *ngIf="isLoading$ | async; else afterLoading">

View File

@ -4,12 +4,15 @@ import { PaymentInstitution } from '@vality/swag-payments';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { shareReplayRefCount } from '@dsh/operators';
import RealmEnum = PaymentInstitution.RealmEnum;
@Injectable()
export class PaymentInstitutionRealmService {
realm$: Observable<RealmEnum | undefined> = this.route.params.pipe(
map(() => this.route.snapshot.params.realm as RealmEnum)
map(({ realm }) => realm as RealmEnum),
shareReplayRefCount()
);
constructor(private route: ActivatedRoute) {}

View File

@ -212,6 +212,7 @@
"title": "Платеж совершен с удержанием денежных средств"
},
"invoiceDetails": {
"notFound": "Инвойс не найден",
"title": "Инвойс"
},
"payerDetails": {