TD-447: Add withdrawal revision adjustment, remove old deanonimus service, add memoize parties, add fails autocomplete for repairing, add routing rules ref id (#155)

This commit is contained in:
Rinat Arsaev 2022-10-31 19:18:33 +06:00 committed by GitHub
parent f9a91b7f10
commit 2f96a41ac1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 142 additions and 86 deletions

15
package-lock.json generated
View File

@ -107,7 +107,8 @@
"prettier-plugin-organize-attributes": "0.0.5",
"ts-mockito": "2.6.1",
"ts-node": "10.9.1",
"typescript": "4.7.4"
"typescript": "4.7.4",
"typescript-memoize": "1.1.1"
}
},
"node_modules/@ampproject/remapping": {
@ -21132,6 +21133,12 @@
"node": ">=4.2.0"
}
},
"node_modules/typescript-memoize": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/typescript-memoize/-/typescript-memoize-1.1.1.tgz",
"integrity": "sha512-GQ90TcKpIH4XxYTI2F98yEQYZgjNMOGPpOgdjIBhaLaWji5HPWlRnZ4AeA1hfBxtY7bCGDJsqDDHk/KaHOl5bA==",
"dev": true
},
"node_modules/ua-parser-js": {
"version": "0.7.31",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz",
@ -38539,6 +38546,12 @@
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
"dev": true
},
"typescript-memoize": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/typescript-memoize/-/typescript-memoize-1.1.1.tgz",
"integrity": "sha512-GQ90TcKpIH4XxYTI2F98yEQYZgjNMOGPpOgdjIBhaLaWji5HPWlRnZ4AeA1hfBxtY7bCGDJsqDDHk/KaHOl5bA==",
"dev": true
},
"ua-parser-js": {
"version": "0.7.31",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz",

View File

@ -121,6 +121,7 @@
"prettier-plugin-organize-attributes": "0.0.5",
"ts-mockito": "2.6.1",
"ts-node": "10.9.1",
"typescript": "4.7.4"
"typescript": "4.7.4",
"typescript-memoize": "1.1.1"
}
}

View File

@ -0,0 +1,23 @@
import { Injectable, Injector } from '@angular/core';
import {
codegenClientConfig,
CodegenClient,
} from '@vality/deanonimus-proto/lib/deanonimus-Deanonimus';
import context from '@vality/deanonimus-proto/lib/deanonimus/context';
import * as service from '@vality/deanonimus-proto/lib/deanonimus/gen-nodejs/Deanonimus';
import { createThriftApi } from '@cc/app/api/utils';
@Injectable({ providedIn: 'root' })
export class DeanonimusService extends createThriftApi<CodegenClient>() {
constructor(injector: Injector) {
super(injector, {
service,
wachterServiceName: 'Deanonimus',
metadata: () =>
import('@vality/deanonimus-proto/lib/metadata.json').then((m) => m.default),
context,
...codegenClientConfig,
});
}
}

View File

@ -1,2 +1 @@
export * from './deanonimus.service';
export * from './utils';

View File

@ -1,2 +1,3 @@
export * from './party-management.service';
export * from './invoicing.service';
export * from './stores/parties-store.service';

View File

@ -0,0 +1,20 @@
import { Injectable } from '@angular/core';
import { PartyID } from '@vality/domain-proto';
import { shareReplay } from 'rxjs/operators';
import { MemoizeExpiring } from 'typescript-memoize';
import { PartyManagementService } from '@cc/app/api/payment-processing';
@Injectable({
providedIn: 'root',
})
export class PartiesStoreService {
constructor(private partyManagementService: PartyManagementService) {}
@MemoizeExpiring(30_000)
get(partyId: PartyID) {
return this.partyManagementService
.Get(partyId)
.pipe(shareReplay({ refCount: true, bufferSize: 1 }));
}
}

View File

@ -3,7 +3,7 @@ import { DataSourceItem } from '../types/data-source-item';
export function filterPredicate({ stringified }: DataSourceItem, filter: string): boolean {
let regexp;
try {
regexp = new RegExp(filter, 'g');
regexp = new RegExp(filter, 'gi');
} catch {
return false;
}

View File

@ -1,11 +1,12 @@
import { Component } from '@angular/core';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import maxBy from 'lodash-es/maxBy';
import { Observable, of } from 'rxjs';
import { catchError, filter, map, pluck, shareReplay, startWith, switchMap } from 'rxjs/operators';
import { DeanonimusService } from '@cc/app/api/deanonimus';
import { AppAuthGuardService } from '@cc/app/shared/services';
import { DeanonimusService, getMaxSearchHitParty } from '../../thrift-services/deanonimus';
import { ROUTING_CONFIG as SHOPS_ROUTING_CONFIG } from '../party-shops/routing-config';
import { ROUTING_CONFIG as RULESET_ROUTING_CONFIG } from '../routing-rules/party-routing-ruleset/routing-config';
@ -34,7 +35,7 @@ export class PartyComponent {
this.partyID$ = this.route.params.pipe(pluck('partyID'), shareReplay(1));
this.merchantEmail$ = this.partyID$.pipe(
switchMap((partyID) => this.deanonimusService.searchParty(partyID)),
map(getMaxSearchHitParty),
map((searchHits) => maxBy(searchHits, (searchHit) => searchHit.score)?.party),
pluck('email'),
catchError((err) => {
console.error(err);

View File

@ -21,6 +21,7 @@
}}</mat-chip>
</mat-chip-list>
<cc-metadata-form
[extensions]="extensions$ | async"
[formControl]="sameForm"
[metadata]="metadata$ | async"
[namespace]="
@ -38,6 +39,7 @@
<cc-metadata-form
*ngIf="typeControl.value === typesEnum.Different"
[extensions]="extensions$ | async"
[formControl]="form"
[metadata]="metadata$ | async"
[type]="

View File

@ -7,6 +7,8 @@ import { RepairInvoicesRequest, RepairWithdrawalsRequest, Machine } from '@valit
import isNil from 'lodash-es/isNil';
import { BehaviorSubject, from } from 'rxjs';
import { DomainMetadataFormExtensionsService } from '@cc/app/shared/services';
import { progressTo, getFormValueChanges } from '../../../../../utils';
import { RepairManagementService } from '../../../../api/repairer';
import { ErrorService } from '../../../../shared/services/error';
@ -41,6 +43,7 @@ export class RepairByScenarioDialogComponent
Validators.required
);
metadata$ = from(import('@vality/repairer-proto/lib/metadata.json').then((m) => m.default));
extensions$ = this.domainMetadataFormExtensionsService.extensions$;
progress$ = new BehaviorSubject(0);
typesEnum = Types;
@ -54,7 +57,8 @@ export class RepairByScenarioDialogComponent
injector: Injector,
private repairManagementService: RepairManagementService,
private errorService: ErrorService,
private notificationService: NotificationService
private notificationService: NotificationService,
private domainMetadataFormExtensionsService: DomainMetadataFormExtensionsService
) {
super(injector);
}

View File

@ -25,6 +25,9 @@
<mat-panel-title>
{{ (terminalsMapID$ | async)[candidate.terminal.id]?.data?.name }}
</mat-panel-title>
<mat-panel-description>
#{{ (terminalsMapID$ | async)[candidate.terminal.id]?.ref?.id }}
</mat-panel-description>
</mat-expansion-panel-header>
<div fxLayout="column" fxLayoutGap="24px">
<div fxLayout fxLayoutGap="8px">

View File

@ -5,13 +5,13 @@
<mat-radio-button [value]="1">Generate ID</mat-radio-button>
</mat-radio-group>
<cc-metadata-form
[formControl]="statusControl"
[formControl]="control"
[metadata]="metadata$ | async"
namespace="withdrawal_status"
type="Status"
namespace="withdrawal_adjustment"
type="ChangeRequest"
></cc-metadata-form>
<cc-metadata-form
[extensions]="extensions"
[extensions]="externalIdExtensions"
[formControl]="externalIdControl"
[metadata]="metadata$ | async"
namespace="base"
@ -25,7 +25,7 @@
</div>
<cc-base-dialog-actions>
<button
[disabled]="statusControl.invalid"
[disabled]="control.invalid"
color="primary"
mat-button
(click)="createAdjustment()"

View File

@ -3,8 +3,8 @@ import { Validators } from '@angular/forms';
import { FormControl } from '@ngneat/reactive-forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { ExternalID } from '@vality/fistful-proto/lib/base';
import { ChangeRequest } from '@vality/fistful-proto/lib/deposit_adjustment';
import { StatWithdrawal } from '@vality/fistful-proto/lib/fistful_stat';
import { Status } from '@vality/fistful-proto/lib/withdrawal';
import { BaseDialogResponseStatus, BaseDialogSuperclass } from '@vality/ng-core';
import { combineLatest, from, of } from 'rxjs';
import { catchError, finalize } from 'rxjs/operators';
@ -24,19 +24,23 @@ export class CreateAdjustmentDialogComponent extends BaseDialogSuperclass<
CreateAdjustmentDialogComponent,
{ withdrawals: StatWithdrawal[] }
> {
statusControl = new FormControl<Status>(
{ failed: { failure: { code: 'account_limit_exceeded:unknown' } } },
control = new FormControl<ChangeRequest>(
{
change_status: {
new_status: { failed: { failure: { code: 'account_limit_exceeded:unknown' } } },
},
},
[Validators.required]
);
externalIdControl = new FormControl<ExternalID>();
typeControl = new FormControl<number>(0);
metadata$ = from(import('@vality/fistful-proto/lib/metadata.json').then((m) => m.default));
extensions: MetadataFormExtension[] = [
externalIdExtensions: MetadataFormExtension[] = [
{
determinant: () => of(true),
extension: () => of({ label: 'External ID' }),
},
];
typeControl = new FormControl<number>(0);
metadata$ = from(import('@vality/fistful-proto/lib/metadata.json').then((m) => m.default));
progress = -1;
constructor(
@ -55,7 +59,7 @@ export class CreateAdjustmentDialogComponent extends BaseDialogSuperclass<
this.managementService
.CreateAdjustment(w.id, {
id: this.typeControl.value === 0 ? w.id : short().uuid(),
change: { change_status: { new_status: this.statusControl.value } },
change: this.control.value,
external_id: this.externalIdControl.value,
})
.pipe(

View File

@ -6,7 +6,7 @@ import { coerceBoolean } from 'coerce-property';
import { BehaviorSubject, Observable, of, ReplaySubject, Subject, merge } from 'rxjs';
import { catchError, debounceTime, filter, map, switchMap, first, takeUntil } from 'rxjs/operators';
import { DeanonimusService } from '@cc/app/thrift-services/deanonimus';
import { DeanonimusService } from '@cc/app/api/deanonimus';
import { Option } from '@cc/components/select-search-field';
import { createControlProviders, ValidatedFormControlSuperclass } from '@cc/utils';
import { progressTo } from '@cc/utils/operators';

View File

@ -3,7 +3,7 @@
title="Create payment adjustment ({{ dialogData.payments.length }})"
>
<cc-metadata-form
[extensions]="extensions"
[extensions]="extensions$ | async"
[formControl]="control"
[metadata]="metadata$ | async"
namespace="payment_processing"

View File

@ -8,9 +8,10 @@ import chunk from 'lodash-es/chunk';
import { BehaviorSubject, from, concatMap, of, forkJoin } from 'rxjs';
import { catchError, finalize, delay } from 'rxjs/operators';
import { DomainMetadataFormExtensionsService } from '@cc/app/shared/services';
import { InvoicingService } from '../../../../api/payment-processing';
import { NotificationService } from '../../../services/notification';
import { MetadataFormExtension, isTypeWithAliases } from '../../metadata-form';
@UntilDestroy()
@Component({
@ -25,39 +26,14 @@ export class CreatePaymentAdjustmentComponent extends BaseDialogSuperclass<
control = new FormControl<InvoicePaymentAdjustmentParams>(null);
progress$ = new BehaviorSubject(0);
metadata$ = from(import('@vality/domain-proto/lib/metadata.json').then((m) => m.default));
extensions: MetadataFormExtension[] = [
{
determinant: (data) => of(isTypeWithAliases(data, 'FailureCode', 'domain')),
extension: () =>
of({
options: [
'authorization_failed:unknown',
'authorization_failed:insufficient_funds',
'authorization_failed:payment_tool_rejected:bank_card_rejected:card_expired',
'authorization_failed:rejected_by_issuer',
'authorization_failed:operation_blocked',
'authorization_failed:account_stolen',
'authorization_failed:temporarily_unavailable',
'authorization_failed:account_limit_exceeded:number',
'authorization_failed:account_limit_exceeded:amount',
'authorization_failed:security_policy_violated',
'preauthorization_failed',
'authorization_failed:payment_tool_rejected:bank_card_rejected:cvv_invalid',
'authorization_failed:account_not_found',
'authorization_failed:payment_tool_rejected:bank_card_rejected:card_number_invalid',
'authorization_failed:rejected_by_issuer',
]
.sort()
.map((value) => ({ value })),
}),
},
];
extensions$ = this.domainMetadataFormExtensionsService.extensions$;
withError: StatPayment[] = [];
constructor(
injector: Injector,
private invoicingService: InvoicingService,
private notificationService: NotificationService
private notificationService: NotificationService,
private domainMetadataFormExtensionsService: DomainMetadataFormExtensionsService
) {
super(injector);
}

View File

@ -2,7 +2,7 @@ import { ChangeDetectorRef, OnDestroy, Pipe, PipeTransform } from '@angular/core
import { BehaviorSubject, combineLatest, Subject } from 'rxjs';
import { distinctUntilChanged, map, pluck, switchMap, takeUntil } from 'rxjs/operators';
import { PartyManagementService } from '@cc/app/api/payment-processing';
import { PartiesStoreService } from '@cc/app/api/payment-processing';
@Pipe({
name: 'shopName',
@ -15,13 +15,13 @@ export class ShopNamePipe implements PipeTransform, OnDestroy {
private destroy$ = new Subject<void>();
constructor(
private partyManagementService: PartyManagementService,
private partyManagementService: PartiesStoreService,
private ref: ChangeDetectorRef
) {
combineLatest([
this.partyIDChange$.pipe(
distinctUntilChanged(),
switchMap((id) => this.partyManagementService.Get(id)),
switchMap((id) => this.partyManagementService.get(id)),
map(({ shops }) => Array.from(shops.values()))
),
this.shopIDChange$.pipe(distinctUntilChanged()),

View File

@ -81,6 +81,36 @@ export class DomainMetadataFormExtensionsService {
}))
),
},
{
determinant: (data) =>
of(
isTypeWithAliases(data, 'FailureCode', 'domain') ||
isTypeWithAliases(data, 'FailureCode', 'base')
),
extension: () =>
of({
options: [
'authorization_failed:unknown',
'authorization_failed:insufficient_funds',
'authorization_failed:payment_tool_rejected:bank_card_rejected:card_expired',
'authorization_failed:rejected_by_issuer',
'authorization_failed:operation_blocked',
'authorization_failed:account_stolen',
'authorization_failed:temporarily_unavailable',
'authorization_failed:account_limit_exceeded:number',
'authorization_failed:account_limit_exceeded:amount',
'authorization_failed:security_policy_violated',
'preauthorization_failed',
'authorization_failed:payment_tool_rejected:bank_card_rejected:cvv_invalid',
'authorization_failed:account_not_found',
'authorization_failed:payment_tool_rejected:bank_card_rejected:card_number_invalid',
'authorization_failed:rejected_by_issuer',
]
.sort()
.map((value) => ({ value })),
generate: () => of('authorization_failed:unknown'),
}),
},
]),
shareReplay(1)
);

View File

@ -1,32 +1,33 @@
import { Injectable } from '@angular/core';
import { Party } from '@vality/deanonimus-proto';
import { Observable, of, Subject } from 'rxjs';
import { Observable, of, Subject, defer, BehaviorSubject } from 'rxjs';
import { catchError, map, shareReplay, switchMap } from 'rxjs/operators';
import { progress } from '@cc/app/shared/custom-operators';
import { DeanonimusService } from '../../thrift-services/deanonimus';
import { DeanonimusService } from '@cc/app/api/deanonimus';
import { progressTo, inProgressFrom } from '@cc/utils';
@Injectable()
export class FetchPartiesService {
private searchParties$: Subject<string> = new Subject();
// eslint-disable-next-line @typescript-eslint/member-ordering
parties$: Observable<Party[]> = this.searchParties$.pipe(
parties$: Observable<Party[]> = defer(() => this.searchParties$).pipe(
switchMap((text) =>
this.deanonimusService.searchParty(text).pipe(
map((hits) => hits.map((hit) => hit.party)),
catchError((err) => {
console.error(err);
return of<Party[]>([]);
})
}),
progressTo(this.progress$)
)
),
shareReplay(1)
);
inProgress$ = inProgressFrom(
() => this.progress$,
() => this.parties$
);
// eslint-disable-next-line @typescript-eslint/member-ordering
inProgress$: Observable<boolean> = progress(this.searchParties$, this.parties$);
private progress$ = new BehaviorSubject(0);
private searchParties$: Subject<string> = new Subject();
constructor(private deanonimusService: DeanonimusService) {}

View File

@ -1,16 +0,0 @@
import { Injectable, Injector } from '@angular/core';
import { SearchHit } from '@vality/deanonimus-proto';
import * as Deanonimus from '@vality/deanonimus-proto/lib/deanonimus/gen-nodejs/Deanonimus';
import { Observable } from 'rxjs';
import { ThriftService } from '../services/thrift/thrift-service';
@Injectable({ providedIn: 'root' })
export class DeanonimusService extends ThriftService {
constructor(injector: Injector) {
super(injector, '/wachter', Deanonimus, 'Deanonimus');
}
searchParty = (text: string): Observable<SearchHit[]> =>
this.toObservableAction('searchParty')(text);
}

View File

@ -1,5 +0,0 @@
import { Party, SearchHit } from '@vality/deanonimus-proto';
import maxBy from 'lodash-es/maxBy';
export const getMaxSearchHitParty = (searchHits: SearchHit[]): Party =>
maxBy(searchHits, (searchHit) => searchHit.score)?.party;

View File

@ -1 +0,0 @@
export * from './get-max-search-hit-party';