INT-101: Add wallet to routing rules (#93)

This commit is contained in:
Rinat Arsaev 2022-06-03 18:11:43 +03:00 committed by GitHub
parent 66a0cad007
commit 4d5264002d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 218 additions and 95 deletions

View File

@ -29,9 +29,7 @@ import { PartyDelegateRulesetsComponent } from './party-delegate-rulesets.compon
{
path: '',
loadChildren: () =>
import('../shop-routing-ruleset').then(
(m) => m.ShopRoutingRulesetModule
),
import('../routing-ruleset').then((m) => m.RoutingRulesetModule),
},
],
},

View File

@ -1,6 +1,6 @@
<cc-base-dialog title="Party routing rule params">
<div [formGroup]="form" fxLayout="column" fxLayoutGap="24px">
<mat-form-field>
<mat-form-field *ngIf="dialogData.type === 'payment'">
<mat-label>Shop</mat-label>
<mat-select formControlName="shopID" required>
<mat-option *ngFor="let shop of dialogData.shops" [value]="shop.id">
@ -8,6 +8,14 @@
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field *ngIf="dialogData.type === 'withdrawal'">
<mat-label>Wallet</mat-label>
<mat-select formControlName="shopID" required>
<mat-option *ngFor="let wallet of dialogData.wallets" [value]="wallet.id">
{{ wallet.name }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-divider></mat-divider>
<div class="cc-title">Routing ruleset</div>
<div fxLayout="column" fxLayoutGap="16px">

View File

@ -1,12 +1,13 @@
import { Component, Injector } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { FormBuilder } from '@ngneat/reactive-forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { Shop } from '@vality/domain-proto/lib/domain';
import { Shop, Wallet } from '@vality/domain-proto/lib/domain';
import { BaseDialogResponseStatus, BaseDialogSuperclass } from '@cc/components/base-dialog';
import { ErrorService } from '../../../../shared/services/error';
import { RoutingRulesService } from '../../../../thrift-services';
import { RoutingRulesType } from '../../types/routing-rules-type';
@UntilDestroy()
@Component({
@ -14,10 +15,11 @@ import { RoutingRulesService } from '../../../../thrift-services';
})
export class AddPartyRoutingRuleDialogComponent extends BaseDialogSuperclass<
AddPartyRoutingRuleDialogComponent,
{ refID: number; partyID: string; shops: Shop[] }
{ refID: number; partyID: string; shops: Shop[]; wallets: Wallet[]; type: RoutingRulesType }
> {
form = this.fb.group({
form = this.fb.group<{ shopID: string; walletID: string; name: string; description: string }>({
shopID: '',
walletID: '',
name: 'Ruleset[candidates]',
description: '',
});
@ -32,19 +34,27 @@ export class AddPartyRoutingRuleDialogComponent extends BaseDialogSuperclass<
}
add() {
const { shopID, name, description } = this.form.value;
this.routingRulesService
.addShopRuleset({
name,
description,
partyRulesetRefID: this.dialogData.refID,
partyID: this.dialogData.partyID,
shopID,
})
const { shopID, walletID, name, description } = this.form.value;
(this.dialogData.type === RoutingRulesType.Payment
? this.routingRulesService.addShopRuleset({
name,
description,
partyRulesetRefID: this.dialogData.refID,
partyID: this.dialogData.partyID,
shopID,
})
: this.routingRulesService.addWalletRuleset({
name,
description,
partyRulesetRefID: this.dialogData.refID,
partyID: this.dialogData.partyID,
walletID,
})
)
.pipe(untilDestroyed(this))
.subscribe(
() => this.dialogRef.close({ status: BaseDialogResponseStatus.Success }),
this.errorService.error
);
.subscribe({
next: () => this.dialogRef.close({ status: BaseDialogResponseStatus.Success }),
error: this.errorService.error,
});
}
}

View File

@ -18,9 +18,17 @@
</cc-routing-ruleset-header>
<cc-routing-rules-list
[data]="data$ | async"
[displayedColumns]="displayedColumns"
(toDetails)="navigateToShopRuleset($event.parentRefId, $event.delegateIdx)"
*ngIf="(shopsData$ | async)?.length || !(walletsData$ | async)?.length"
[data]="shopsData$ | async"
[displayedColumns]="shopsDisplayedColumns"
(toDetails)="navigateToDelegate($event.parentRefId, $event.delegateIdx)"
></cc-routing-rules-list>
<cc-routing-rules-list
*ngIf="(walletsData$ | async)?.length"
[data]="walletsData$ | async"
[displayedColumns]="walletsDisplayedColumns"
(toDetails)="navigateToDelegate($event.parentRefId, $event.delegateIdx)"
></cc-routing-rules-list>
</div>

View File

@ -1,13 +1,14 @@
import { Component } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { combineLatest } from 'rxjs';
import { filter, map, pluck, shareReplay, switchMap, take } from 'rxjs/operators';
import { combineLatest, Observable } from 'rxjs';
import { filter, map, pluck, shareReplay, startWith, switchMap, take } from 'rxjs/operators';
import { BaseDialogService } from '@cc/components/base-dialog/services/base-dialog.service';
import { BaseDialogResponseStatus } from '../../../../components/base-dialog';
import { DomainStoreService } from '../../../thrift-services/damsel/domain-store.service';
import { RoutingRulesType } from '../types/routing-rules-type';
import { AddPartyRoutingRuleDialogComponent } from './add-party-routing-rule-dialog';
import { InitializeRoutingRulesDialogComponent } from './initialize-routing-rules-dialog';
import { PartyRoutingRulesetService } from './party-routing-ruleset.service';
@ -22,25 +23,30 @@ import { PartyRoutingRulesetService } from './party-routing-ruleset.service';
export class PartyRoutingRulesetComponent {
partyRuleset$ = this.partyRoutingRulesetService.partyRuleset$;
partyID$ = this.partyRoutingRulesetService.partyID$;
routingRulesType$ = this.route.params.pipe(pluck('type'));
routingRulesType$ = this.route.params.pipe(
startWith(this.route.snapshot.params),
pluck('type')
) as Observable<RoutingRulesType>;
isLoading$ = this.domainStoreService.isLoading$;
displayedColumns = [
shopsDisplayedColumns = [
{ key: 'shop', name: 'Shop' },
{ key: 'id', name: 'Delegate (Ruleset Ref ID)' },
];
data$ = combineLatest([this.partyRuleset$, this.partyRoutingRulesetService.shops$]).pipe(
walletsDisplayedColumns = [
{ key: 'wallet', name: 'Wallet' },
{ key: 'id', name: 'Delegate (Ruleset Ref ID)' },
];
shopsData$ = combineLatest([this.partyRuleset$, this.partyRoutingRulesetService.shops$]).pipe(
filter(([r]) => !!r),
map(([ruleset, shops]) =>
ruleset.data.decisions.delegates
.filter((d) => d?.allowed?.condition?.party?.definition?.shop_is)
.map((delegate) => {
const shopId = delegate.allowed.condition.party.definition.shop_is;
.map((delegate, delegateIdx) => {
const shopId = delegate?.allowed?.condition?.party?.definition?.shop_is;
return {
parentRefId: ruleset.ref.id,
delegateIdx: ruleset.data.decisions.delegates.findIndex(
(d) => d === delegate
),
delegateIdx,
id: {
text: delegate?.description,
caption: delegate?.ruleset?.id,
@ -52,6 +58,34 @@ export class PartyRoutingRulesetComponent {
};
})
),
untilDestroyed(this),
shareReplay(1)
);
walletsData$ = combineLatest([
this.partyRuleset$,
this.partyRoutingRulesetService.wallets$,
]).pipe(
filter(([r]) => !!r),
map(([ruleset, wallets]) =>
ruleset.data.decisions.delegates
.filter((d) => d?.allowed?.condition?.party?.definition?.wallet_is)
.map((delegate, delegateIdx) => {
const walletId = delegate?.allowed?.condition?.party?.definition?.wallet_is;
return {
parentRefId: ruleset.ref.id,
delegateIdx,
id: {
text: delegate?.description,
caption: delegate?.ruleset?.id,
},
wallet: {
text: wallets?.find((w) => w?.id === walletId)?.name,
caption: walletId,
},
};
})
),
untilDestroyed(this),
shareReplay(1)
);
@ -84,13 +118,21 @@ export class PartyRoutingRulesetComponent {
combineLatest([
this.partyRoutingRulesetService.refID$,
this.partyRoutingRulesetService.shops$,
this.partyRoutingRulesetService.wallets$,
this.routingRulesType$,
this.partyRoutingRulesetService.partyID$,
])
.pipe(
take(1),
switchMap(([refID, shops, partyID]) =>
switchMap(([refID, shops, wallets, type, partyID]) =>
this.baseDialogService
.open(AddPartyRoutingRuleDialogComponent, { refID, shops, partyID })
.open(AddPartyRoutingRuleDialogComponent, {
refID,
shops,
wallets,
type,
partyID,
})
.afterClosed()
),
untilDestroyed(this)
@ -104,7 +146,7 @@ export class PartyRoutingRulesetComponent {
});
}
navigateToShopRuleset(parentRefId: number, delegateIdx: number) {
navigateToDelegate(parentRefId: number, delegateIdx: number) {
this.partyRoutingRulesetService.partyRuleset$
.pipe(take(1), untilDestroyed(this))
.subscribe((ruleset) =>
@ -114,7 +156,7 @@ export class PartyRoutingRulesetComponent {
'routing-rules',
this.route.snapshot.params.type,
parentRefId,
'shop-ruleset',
'delegate',
ruleset?.data?.decisions?.delegates?.[delegateIdx]?.ruleset?.id,
])
);

View File

@ -21,6 +21,10 @@ export class PartyRoutingRulesetService {
pluck('shops'),
map((shops) => Array.from(shops.values()))
);
wallets$ = defer(() => this.party$).pipe(
pluck('wallets'),
map((wallets) => Array.from(wallets.values()))
);
partyRuleset$ = combineLatest([this.routingRulesService.rulesets$, this.refID$]).pipe(
map(([rules, refID]) => rules.find((r) => r?.ref?.id === refID)),

View File

@ -1,4 +1,4 @@
<cc-base-dialog title="Shop routing rule params">
<cc-base-dialog title="Routing rule params">
<div [formGroup]="form" fxLayout="column" fxLayoutGap="24px">
<div fxLayout="column" fxLayoutGap="16px">
<mat-form-field>

View File

@ -7,20 +7,16 @@ import { Predicate, RiskScore } from '@vality/domain-proto/lib/domain';
import { BaseDialogSuperclass } from '@cc/components/base-dialog';
import { DomainStoreService } from '../../../../thrift-services/damsel/domain-store.service';
import {
AddShopRoutingRuleDialogService,
TerminalType,
} from './add-shop-routing-rule-dialog.service';
import { AddRoutingRuleDialogService, TerminalType } from './add-routing-rule-dialog.service';
@UntilDestroy()
@Component({
selector: 'cc-add-shop-routing-rule-dialog',
templateUrl: 'add-shop-routing-rule-dialog.component.html',
styleUrls: ['add-shop-routing-rule-dialog.component.scss'],
providers: [AddShopRoutingRuleDialogService],
templateUrl: 'add-routing-rule-dialog.component.html',
styleUrls: ['add-routing-rule-dialog.component.scss'],
providers: [AddRoutingRuleDialogService],
})
export class AddShopRoutingRuleDialogComponent extends BaseDialogSuperclass<
AddShopRoutingRuleDialogComponent,
export class AddRoutingRuleDialogComponent extends BaseDialogSuperclass<
AddRoutingRuleDialogComponent,
{ refID: number }
> {
form = this.addShopRoutingRuleDialogService.form;
@ -33,7 +29,7 @@ export class AddShopRoutingRuleDialogComponent extends BaseDialogSuperclass<
constructor(
injector: Injector,
private addShopRoutingRuleDialogService: AddShopRoutingRuleDialogService,
private addShopRoutingRuleDialogService: AddRoutingRuleDialogService,
private domainStoreService: DomainStoreService,
private fb: FormBuilder
) {

View File

@ -15,7 +15,7 @@ import { MatSelectModule } from '@angular/material/select';
import { MetadataFormModule } from '@cc/app/shared';
import { BaseDialogModule } from '@cc/components/base-dialog';
import { AddShopRoutingRuleDialogComponent } from './add-shop-routing-rule-dialog.component';
import { AddRoutingRuleDialogComponent } from './add-routing-rule-dialog.component';
import { ExpanderComponent } from './expander';
import { PredicateComponent } from './predicate';
@ -36,7 +36,7 @@ import { PredicateComponent } from './predicate';
MetadataFormModule,
BaseDialogModule,
],
declarations: [AddShopRoutingRuleDialogComponent, PredicateComponent, ExpanderComponent],
exports: [AddShopRoutingRuleDialogComponent],
declarations: [AddRoutingRuleDialogComponent, PredicateComponent, ExpanderComponent],
exports: [AddRoutingRuleDialogComponent],
})
export class AddShopRoutingRuleDialogModule {}
export class AddRoutingRuleDialogModule {}

View File

@ -8,7 +8,7 @@ import { startWith, switchMap, take } from 'rxjs/operators';
import { BaseDialogResponseStatus } from '@cc/components/base-dialog';
import { RoutingRulesService, TerminalService } from '../../../../thrift-services';
import { AddShopRoutingRuleDialogComponent } from './add-shop-routing-rule-dialog.component';
import { AddRoutingRuleDialogComponent } from './add-routing-rule-dialog.component';
export enum TerminalType {
New = 'new',
@ -16,7 +16,7 @@ export enum TerminalType {
}
@Injectable()
export class AddShopRoutingRuleDialogService {
export class AddRoutingRuleDialogService {
form = this.fb.group({
description: '',
weight: '',
@ -37,7 +37,7 @@ export class AddShopRoutingRuleDialogService {
constructor(
private fb: FormBuilder,
private dialogRef: MatDialogRef<AddShopRoutingRuleDialogComponent>,
private dialogRef: MatDialogRef<AddRoutingRuleDialogComponent>,
private routingRulesService: RoutingRulesService,
private terminalService: TerminalService
) {

View File

@ -0,0 +1,2 @@
export * from './add-routing-rule-dialog.component';
export * from './add-routing-rule-dialog.module';

View File

@ -0,0 +1 @@
export * from './routing-ruleset.module';

View File

@ -3,14 +3,14 @@ import { RouterModule } from '@angular/router';
import { AppAuthGuardService, DomainConfigRole } from '@cc/app/shared/services';
import { ShopRoutingRulesetComponent } from './shop-routing-ruleset.component';
import { RoutingRulesetComponent } from './routing-ruleset.component';
@NgModule({
imports: [
RouterModule.forChild([
{
path: ':partyRefID/shop-ruleset/:refID',
component: ShopRoutingRulesetComponent,
path: ':partyRefID/delegate/:refID',
component: RoutingRulesetComponent,
canActivate: [AppAuthGuardService],
data: {
roles: [DomainConfigRole.Checkout],
@ -19,4 +19,4 @@ import { ShopRoutingRulesetComponent } from './shop-routing-ruleset.component';
]),
],
})
export class ShopRoutingRulesetRoutingModule {}
export class RoutingRulesetRoutingModule {}

View File

@ -16,7 +16,7 @@
"
(add)="addShopRule()"
>
Shop routing rules
Routing rules
</cc-routing-ruleset-header>
<mat-accordion *ngIf="(candidates$ | async)?.length; else emptyShopRules">
@ -64,7 +64,7 @@
</mat-accordion>
<ng-template #emptyShopRules>
<div class="cc-display-1" fxFlexAlign="center">Shop routing rules is empty</div>
<div class="cc-display-1" fxFlexAlign="center">Routing rules is empty</div>
</ng-template>
</div>
</ng-template>

View File

@ -14,22 +14,21 @@ import { BaseDialogService } from '@cc/components/base-dialog/services/base-dial
import { ErrorService } from '../../../shared/services/error';
import { damselInstanceToObject } from '../../../thrift-services';
import { DomainStoreService } from '../../../thrift-services/damsel/domain-store.service';
import { AddShopRoutingRuleDialogComponent } from './add-shop-routing-rule-dialog';
import { ShopRoutingRulesetService } from './shop-routing-ruleset.service';
import { AddRoutingRuleDialogComponent } from './add-routing-rule-dialog';
import { RoutingRulesetService } from './routing-ruleset.service';
@UntilDestroy()
@Component({
selector: 'cc-shop-routing-ruleset',
templateUrl: 'shop-routing-ruleset.component.html',
providers: [ShopRoutingRulesetService],
templateUrl: 'routing-ruleset.component.html',
providers: [RoutingRulesetService],
})
export class ShopRoutingRulesetComponent {
shopRuleset$ = this.shopRoutingRulesetService.shopRuleset$;
partyID$ = this.shopRoutingRulesetService.partyID$;
partyRulesetRefID$ = this.shopRoutingRulesetService.partyRulesetRefID$;
export class RoutingRulesetComponent {
shopRuleset$ = this.routingRulesetService.shopRuleset$;
partyID$ = this.routingRulesetService.partyID$;
partyRulesetRefID$ = this.routingRulesetService.partyRulesetRefID$;
routingRulesType$ = this.route.params.pipe(pluck('type')) as Observable<RoutingRulesType>;
shop$ = this.shopRoutingRulesetService.shop$;
candidates$ = this.shopRoutingRulesetService.shopRuleset$.pipe(
shop$ = this.routingRulesetService.shop$;
candidates$ = this.routingRulesetService.shopRuleset$.pipe(
map((r) => r.data.decisions.candidates),
shareReplay(1)
);
@ -47,7 +46,7 @@ export class ShopRoutingRulesetComponent {
constructor(
private baseDialogService: BaseDialogService,
private shopRoutingRulesetService: ShopRoutingRulesetService,
private routingRulesetService: RoutingRulesetService,
private domainStoreService: DomainStoreService,
private errorService: ErrorService,
private notificationService: NotificationService,
@ -55,12 +54,12 @@ export class ShopRoutingRulesetComponent {
) {}
addShopRule() {
this.shopRoutingRulesetService.refID$
this.routingRulesetService.refID$
.pipe(
first(),
switchMap((refID) =>
this.baseDialogService
.open(AddShopRoutingRuleDialogComponent, { refID })
.open(AddRoutingRuleDialogComponent, { refID })
.afterClosed()
)
)
@ -69,18 +68,18 @@ export class ShopRoutingRulesetComponent {
next: (res) => {
if (res.status === BaseDialogResponseStatus.Success) {
this.domainStoreService.forceReload();
this.notificationService.success('Shop routing ruleset successfully added');
this.notificationService.success('Routing rule successfully added');
}
},
error: (err) => {
this.errorService.error(err);
this.notificationService.success('Error while adding shop routing ruleset');
this.notificationService.success('Error while adding routing rule');
},
});
}
removeShopRule(idx: number) {
this.shopRoutingRulesetService.removeShopRule(idx);
this.routingRulesetService.removeShopRule(idx);
}
terminalToObject(terminal: TerminalObject) {

View File

@ -23,13 +23,13 @@ import { PrettyJsonModule } from '@cc/components/pretty-json';
import { DamselModule } from '../../../thrift-services';
import { RoutingRulesetHeaderModule } from '../routing-ruleset-header';
import { AddShopRoutingRuleDialogModule } from './add-shop-routing-rule-dialog';
import { ShopRoutingRulesetRoutingModule } from './shop-routing-ruleset-routing.module';
import { ShopRoutingRulesetComponent } from './shop-routing-ruleset.component';
import { AddRoutingRuleDialogModule } from './add-routing-rule-dialog';
import { RoutingRulesetRoutingModule } from './routing-ruleset-routing.module';
import { RoutingRulesetComponent } from './routing-ruleset.component';
@NgModule({
imports: [
ShopRoutingRulesetRoutingModule,
RoutingRulesetRoutingModule,
CommonModule,
MatButtonModule,
FlexLayoutModule,
@ -50,10 +50,10 @@ import { ShopRoutingRulesetComponent } from './shop-routing-ruleset.component';
MatExpansionModule,
RoutingRulesetHeaderModule,
MatAutocompleteModule,
AddShopRoutingRuleDialogModule,
AddRoutingRuleDialogModule,
PrettyJsonModule,
MatProgressBarModule,
],
declarations: [ShopRoutingRulesetComponent],
declarations: [RoutingRulesetComponent],
})
export class ShopRoutingRulesetModule {}
export class RoutingRulesetModule {}

View File

@ -10,7 +10,7 @@ import { ErrorService } from '../../../shared/services/error';
import { RoutingRulesService as RoutingRulesDamselService } from '../../../thrift-services';
@Injectable()
export class ShopRoutingRulesetService {
export class RoutingRulesetService {
partyID$: Observable<string> = this.route.params.pipe(pluck('partyID'), shareReplay(1));
partyRulesetRefID$: Observable<number> = this.route.params.pipe(
pluck('partyRefID'),

View File

@ -1,2 +0,0 @@
export * from './add-shop-routing-rule-dialog.component';
export * from './add-shop-routing-rule-dialog.module';

View File

@ -1 +0,0 @@
export * from './shop-routing-ruleset.module';

View File

@ -148,6 +148,64 @@ export class RoutingRulesService {
);
}
addWalletRuleset({
name,
walletID,
partyID,
partyRulesetRefID,
description,
}: {
name: string;
walletID: string;
partyID: string;
partyRulesetRefID: number;
description?: string;
}): Observable<Version> {
return combineLatest([this.getRuleset(partyRulesetRefID), this.nextRefID$]).pipe(
take(1),
switchMap(([partyRuleset, id]) => {
const walletRuleset: RoutingRulesObject = {
ref: { id },
data: {
name,
description,
decisions: {
candidates: [],
},
},
};
const newPartyRuleset = this.cloneRulesetAndPushDelegate(partyRuleset, {
ruleset: { id },
allowed: {
condition: {
party: {
id: partyID,
definition: {
wallet_is: walletID,
},
},
},
},
});
return this.domainStoreService.commit({
ops: [
{
insert: {
object: { routing_rules: walletRuleset },
},
},
{
update: {
old_object: { routing_rules: partyRuleset },
new_object: { routing_rules: newPartyRuleset },
},
},
],
});
})
);
}
addShopRule({
refID,
terminalID,
@ -165,8 +223,8 @@ export class RoutingRulesService {
}): Observable<Version> {
return this.getRuleset(refID).pipe(
take(1),
switchMap((shopRuleset) => {
const newShopRule = this.cloneRulesetAndPushCandidate(shopRuleset, {
switchMap((ruleset) => {
const newShopRuleset = this.cloneRulesetAndPushCandidate(ruleset, {
description,
allowed: predicate,
terminal: {
@ -179,8 +237,8 @@ export class RoutingRulesService {
ops: [
{
update: {
old_object: { routing_rules: shopRuleset },
new_object: { routing_rules: newShopRule },
old_object: { routing_rules: ruleset },
new_object: { routing_rules: newShopRuleset },
},
},
],