mirror of
https://github.com/valitydev/control-center.git
synced 2024-11-06 02:25:17 +00:00
IMP-232: Wallets terms (#361)
This commit is contained in:
parent
45cf33deb8
commit
a40b2f0a05
@ -2,5 +2,5 @@
|
||||
"$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json",
|
||||
"version": "0.2",
|
||||
"import": "./node_modules/@vality/cspell-config/cspell.config.js",
|
||||
"words": ["submain", "papaparse", "msgpack", "termsets"]
|
||||
"words": ["submain", "papaparse", "msgpack", "termsets", "rreserve"]
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ export class AppComponent {
|
||||
services: SOURCES_ROUTING_CONFIG.services,
|
||||
},
|
||||
{
|
||||
label: 'Tariffs',
|
||||
label: 'Terms',
|
||||
url: '/tariffs',
|
||||
services: TARIFFS_ROUTING_CONFIG.services,
|
||||
},
|
||||
|
@ -1 +0,0 @@
|
||||
<v-table [columns]="columns" [data]="data()"></v-table>
|
@ -1,38 +0,0 @@
|
||||
import { Component, input } from '@angular/core';
|
||||
import { CashFlowSelector } from '@vality/domain-proto/internal/domain';
|
||||
import { Column, TableModule } from '@vality/ng-core';
|
||||
import { getUnionKey } from '@vality/ng-thrift';
|
||||
|
||||
import { formatCashFlowDecisions } from '@cc/app/sections/tariffs/components/cash-flows-selector-table/format-cash-flow.decisions';
|
||||
import { formatCashVolume } from '@cc/app/shared/utils/table/format-cash-volume';
|
||||
|
||||
@Component({
|
||||
selector: 'cc-cash-flows-selector-table',
|
||||
standalone: true,
|
||||
imports: [TableModule],
|
||||
templateUrl: './cash-flows-selector-table.component.html',
|
||||
styles: ``,
|
||||
})
|
||||
export class CashFlowsSelectorTableComponent {
|
||||
data = input<CashFlowSelector[]>();
|
||||
|
||||
columns: Column<CashFlowSelector>[] = [
|
||||
{
|
||||
field: 'decisions',
|
||||
formatter: (d) => formatCashFlowDecisions(d?.decisions),
|
||||
},
|
||||
{
|
||||
field: 'value',
|
||||
formatter: (d) =>
|
||||
d?.value
|
||||
?.filter(
|
||||
(c) =>
|
||||
getUnionKey(c?.source) === 'merchant' &&
|
||||
getUnionKey(c?.destination) === 'system',
|
||||
)
|
||||
?.sort()
|
||||
?.map((c) => formatCashVolume(c.volume))
|
||||
.join(' + '),
|
||||
},
|
||||
];
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
<cc-page-layout title="Shops tariffs">
|
||||
<cc-page-layout title="Shops terms">
|
||||
<cc-page-layout-actions>
|
||||
<v-more-filters-button [filters]="filters"></v-more-filters-button>
|
||||
</cc-page-layout-actions>
|
||||
@ -18,8 +18,10 @@
|
||||
|
||||
<v-table
|
||||
[cellTemplate]="{
|
||||
decision: arrayColumnTemplate,
|
||||
value: arrayColumnTemplate
|
||||
condition: arrayColumnTemplate,
|
||||
fee: arrayColumnTemplate,
|
||||
rreserve: arrayColumnTemplate,
|
||||
other: arrayColumnTemplate
|
||||
}"
|
||||
[columns]="columns"
|
||||
[data]="tariffs$ | async"
|
||||
@ -29,10 +31,16 @@
|
||||
(update)="update($event)"
|
||||
></v-table>
|
||||
|
||||
<ng-template #arrayColumnTemplate let-value="value">
|
||||
{{ rowData?.length }}
|
||||
<div *ngFor="let item of value" style="white-space: nowrap">
|
||||
{{ item }}
|
||||
</div>
|
||||
<ng-template #arrayColumnTemplate let-colDef="colDef" let-rowData="rowData" let-value="value">
|
||||
<ng-container *ngIf="(rowData | vSelect: colDef.tooltip : '' : [colDef]) || ' ' as tooltip">
|
||||
<div
|
||||
*ngFor="let item of value; let index = index"
|
||||
[matTooltip]="tooltip[index]"
|
||||
matTooltipPosition="right"
|
||||
style="white-space: nowrap; cursor: default"
|
||||
>
|
||||
{{ item }}
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</cc-page-layout>
|
||||
|
@ -2,11 +2,8 @@ import { CommonModule } from '@angular/common';
|
||||
import { Component, DestroyRef, Inject, OnInit } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { NonNullableFormBuilder, ReactiveFormsModule } from '@angular/forms';
|
||||
import {
|
||||
TermSetHierarchyRef,
|
||||
type CashFlowSelector,
|
||||
type CashFlowPosting,
|
||||
} from '@vality/domain-proto/internal/domain';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { TermSetHierarchyRef } from '@vality/domain-proto/internal/domain';
|
||||
import {
|
||||
CommonSearchQueryParams,
|
||||
ShopSearchQuery,
|
||||
@ -26,9 +23,10 @@ import {
|
||||
QueryParamsService,
|
||||
TableModule,
|
||||
UpdateOptions,
|
||||
VSelectPipe,
|
||||
} from '@vality/ng-core';
|
||||
import { getUnionKey } from '@vality/ng-thrift';
|
||||
import { map, shareReplay } from 'rxjs/operators';
|
||||
import { getInlineDecisions } from 'src/app/sections/tariffs/utils/get-inline-decisions';
|
||||
import {
|
||||
DomainObjectCardComponent,
|
||||
getDomainObjectDetails,
|
||||
@ -41,8 +39,6 @@ import {
|
||||
createShopColumn,
|
||||
PageLayoutModule,
|
||||
ShopFieldModule,
|
||||
formatCashVolume,
|
||||
formatPredicate,
|
||||
} from '@cc/app/shared';
|
||||
import { CurrencyFieldComponent } from '@cc/app/shared/components/currency-field';
|
||||
import { MerchantFieldModule } from '@cc/app/shared/components/merchant-field';
|
||||
@ -65,58 +61,6 @@ function getViewedCashFlowSelectors(d: ShopTermSet) {
|
||||
);
|
||||
}
|
||||
|
||||
interface InlineCashFlowSelector {
|
||||
if?: string;
|
||||
value?: string;
|
||||
parent?: InlineCashFlowSelector;
|
||||
level: number;
|
||||
}
|
||||
|
||||
function getInlineDecisions(
|
||||
d: CashFlowSelector[],
|
||||
filterValue: (v: CashFlowPosting) => boolean = (v) =>
|
||||
getUnionKey(v?.source) === 'merchant' && getUnionKey(v?.destination) === 'system',
|
||||
level = 0,
|
||||
): InlineCashFlowSelector[] {
|
||||
return d.reduce((acc, c) => {
|
||||
if (c.value) {
|
||||
acc.push({
|
||||
value: c.value
|
||||
.filter(filterValue)
|
||||
.map((v) => formatCashVolume(v.volume))
|
||||
.join(' + '),
|
||||
level,
|
||||
});
|
||||
}
|
||||
if (c.decisions?.length) {
|
||||
acc.push(
|
||||
...c.decisions
|
||||
.map((d) => {
|
||||
const thenInlineDecisions = getInlineDecisions(
|
||||
[d.then_],
|
||||
filterValue,
|
||||
level + 1,
|
||||
);
|
||||
if (d.if_) {
|
||||
const ifInlineDecision = {
|
||||
if: `${' '.repeat(level)}${
|
||||
formatPredicate(d.if_) || (level > 0 ? '↳' : '')
|
||||
}`,
|
||||
level,
|
||||
};
|
||||
return thenInlineDecisions.length > 1
|
||||
? [ifInlineDecision, ...thenInlineDecisions]
|
||||
: [{ ...ifInlineDecision, value: thenInlineDecisions[0].value }];
|
||||
}
|
||||
return thenInlineDecisions;
|
||||
})
|
||||
.flat(),
|
||||
);
|
||||
}
|
||||
return acc;
|
||||
}, [] as InlineCashFlowSelector[]);
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'cc-shops-tariffs',
|
||||
standalone: true,
|
||||
@ -131,6 +75,8 @@ function getInlineDecisions(
|
||||
ShopFieldModule,
|
||||
ListFieldModule,
|
||||
CurrencyFieldComponent,
|
||||
VSelectPipe,
|
||||
MatTooltip,
|
||||
],
|
||||
templateUrl: './shops-tariffs.component.html',
|
||||
})
|
||||
@ -148,7 +94,15 @@ export class ShopsTariffsComponent implements OnInit {
|
||||
hasMore$ = this.shopsTariffsService.hasMore$;
|
||||
isLoading$ = this.shopsTariffsService.isLoading$;
|
||||
columns: Column<ShopTermSet>[] = [
|
||||
createShopColumn<ShopTermSet>('shop_id', (d) => d.owner_id, undefined, { pinned: 'left' }),
|
||||
createShopColumn<ShopTermSet>(
|
||||
'shop_id',
|
||||
(d) => d.owner_id,
|
||||
undefined,
|
||||
(d) => d.shop_name,
|
||||
{
|
||||
pinned: 'left',
|
||||
},
|
||||
),
|
||||
createPartyColumn<ShopTermSet>('owner_id'),
|
||||
createContractColumn<ShopTermSet>(
|
||||
(d) => d.contract_id,
|
||||
@ -166,12 +120,46 @@ export class ShopsTariffsComponent implements OnInit {
|
||||
}),
|
||||
},
|
||||
{
|
||||
field: 'decision',
|
||||
field: 'condition',
|
||||
formatter: (d) => getInlineDecisions(getViewedCashFlowSelectors(d)).map((v) => v.if),
|
||||
},
|
||||
{
|
||||
field: 'value',
|
||||
formatter: (d) => getInlineDecisions(getViewedCashFlowSelectors(d)).map((v) => v.value),
|
||||
field: 'fee',
|
||||
formatter: (d) =>
|
||||
getInlineDecisions(
|
||||
getViewedCashFlowSelectors(d),
|
||||
(v) => v?.source?.merchant === 0 && v?.destination?.system === 0,
|
||||
).map((v) => v.value),
|
||||
},
|
||||
{
|
||||
field: 'rreserve',
|
||||
header: 'RReserve',
|
||||
formatter: (d) =>
|
||||
getInlineDecisions(
|
||||
getViewedCashFlowSelectors(d),
|
||||
(v) => v?.source?.merchant === 0 && v?.destination?.merchant === 1,
|
||||
).map((v) => v.value),
|
||||
},
|
||||
{
|
||||
field: 'other',
|
||||
formatter: (d) =>
|
||||
getInlineDecisions(
|
||||
getViewedCashFlowSelectors(d),
|
||||
(v) =>
|
||||
!(
|
||||
(v?.source?.merchant === 0 && v?.destination?.system === 0) ||
|
||||
(v?.source?.merchant === 0 && v?.destination?.merchant === 1)
|
||||
),
|
||||
).map((v) => v.value),
|
||||
tooltip: (d) =>
|
||||
getInlineDecisions(
|
||||
getViewedCashFlowSelectors(d),
|
||||
(v) =>
|
||||
!(
|
||||
(v?.source?.merchant === 0 && v?.destination?.system === 0) ||
|
||||
(v?.source?.merchant === 0 && v?.destination?.merchant === 1)
|
||||
),
|
||||
).map((v) => v.description),
|
||||
},
|
||||
{
|
||||
field: 'term_set_history',
|
||||
|
@ -1,23 +0,0 @@
|
||||
import { Component, computed, input } from '@angular/core';
|
||||
import { TermSetHierarchyObject } from '@vality/domain-proto/internal/domain';
|
||||
|
||||
import { CardComponent } from '@cc/app/shared/components/sidenav-info/components/card/card.component';
|
||||
|
||||
import { CashFlowsSelectorTableComponent } from '../cash-flows-selector-table/cash-flows-selector-table.component';
|
||||
|
||||
@Component({
|
||||
selector: 'cc-termsets-card',
|
||||
standalone: true,
|
||||
imports: [CardComponent, CashFlowsSelectorTableComponent],
|
||||
templateUrl: './termsets-card.component.html',
|
||||
styles: ``,
|
||||
})
|
||||
export class TermsetsCardComponent {
|
||||
data = input<TermSetHierarchyObject>();
|
||||
feesData = computed(
|
||||
() =>
|
||||
this.data()
|
||||
?.data?.term_sets?.map?.((t) => t?.terms?.payments?.fees)
|
||||
?.filter?.(Boolean),
|
||||
);
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
<cc-page-layout title="Wallets terms">
|
||||
<cc-page-layout-actions>
|
||||
<v-more-filters-button [filters]="filters"></v-more-filters-button>
|
||||
</cc-page-layout-actions>
|
||||
<v-filters #filters [active]="active$ | async" (clear)="filtersForm.reset()">
|
||||
<ng-template [formGroup]="filtersForm">
|
||||
<cc-merchant-field formControlName="party_id"></cc-merchant-field>
|
||||
<cc-wallet-field formControlName="wallet_ids" multiple></cc-wallet-field>
|
||||
<v-list-field formControlName="identity_ids" label="Identity IDs"></v-list-field>
|
||||
<v-list-field formControlName="currencies" label="Currencies"></v-list-field>
|
||||
<v-list-field formControlName="term_sets_names" label="Term sets names"></v-list-field>
|
||||
<v-list-field formControlName="term_sets_ids" label="Term sets IDs"></v-list-field>
|
||||
</ng-template>
|
||||
</v-filters>
|
||||
|
||||
<v-table
|
||||
[cellTemplate]="{
|
||||
condition: arrayColumnTemplate,
|
||||
fee: arrayColumnTemplate,
|
||||
other: arrayColumnTemplate
|
||||
}"
|
||||
[columns]="columns"
|
||||
[data]="tariffs$ | async"
|
||||
[hasMore]="hasMore$ | async"
|
||||
[progress]="isLoading$ | async"
|
||||
(more)="more()"
|
||||
(update)="update($event)"
|
||||
></v-table>
|
||||
|
||||
<ng-template #arrayColumnTemplate let-colDef="colDef" let-rowData="rowData" let-value="value">
|
||||
<ng-container *ngIf="(rowData | vSelect: colDef.tooltip : '' : [colDef]) || ' ' as tooltip">
|
||||
<div
|
||||
*ngFor="let item of value; let index = index"
|
||||
[matTooltip]="tooltip[index]"
|
||||
matTooltipPosition="right"
|
||||
style="white-space: nowrap; cursor: default"
|
||||
>
|
||||
{{ item }}
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</cc-page-layout>
|
@ -0,0 +1,215 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, DestroyRef, Inject, OnInit } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { NonNullableFormBuilder, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import {
|
||||
TermSetHierarchyRef,
|
||||
type IdentityProviderRef,
|
||||
} from '@vality/domain-proto/internal/domain';
|
||||
import {
|
||||
CommonSearchQueryParams,
|
||||
type WalletTermSet,
|
||||
type WalletSearchQuery,
|
||||
} from '@vality/dominator-proto/internal/dominator';
|
||||
import {
|
||||
clean,
|
||||
Column,
|
||||
countChanged,
|
||||
createControls,
|
||||
debounceTimeWithFirst,
|
||||
FiltersModule,
|
||||
getValueChanges,
|
||||
InputFieldModule,
|
||||
ListFieldModule,
|
||||
LoadOptions,
|
||||
QueryParamsService,
|
||||
TableModule,
|
||||
UpdateOptions,
|
||||
VSelectPipe,
|
||||
} from '@vality/ng-core';
|
||||
import { map, shareReplay } from 'rxjs/operators';
|
||||
import { WalletsTariffsService } from 'src/app/sections/tariffs/components/wallets-tariffs/wallets-tariffs.service';
|
||||
import { getInlineDecisions } from 'src/app/sections/tariffs/utils/get-inline-decisions';
|
||||
import {
|
||||
DomainObjectCardComponent,
|
||||
getDomainObjectDetails,
|
||||
} from 'src/app/shared/components/thrift-api-crud';
|
||||
import { Overwrite } from 'utility-types';
|
||||
|
||||
import {
|
||||
createContractColumn,
|
||||
createPartyColumn,
|
||||
PageLayoutModule,
|
||||
WalletFieldModule,
|
||||
createWalletColumn,
|
||||
formatCashVolume,
|
||||
} from '@cc/app/shared';
|
||||
import { CurrencyFieldComponent } from '@cc/app/shared/components/currency-field';
|
||||
import { MerchantFieldModule } from '@cc/app/shared/components/merchant-field';
|
||||
import { SidenavInfoService } from '@cc/app/shared/components/sidenav-info';
|
||||
import { DEBOUNCE_TIME_MS } from '@cc/app/tokens';
|
||||
|
||||
type Params = Pick<CommonSearchQueryParams, 'currencies'> &
|
||||
Overwrite<
|
||||
Omit<WalletSearchQuery, 'common_search_query_params'>,
|
||||
{ term_sets_ids?: TermSetHierarchyRef['id'][]; identity_ids?: IdentityProviderRef['id'][] }
|
||||
>;
|
||||
|
||||
function getViewedCashFlowSelectors(d: WalletTermSet) {
|
||||
return (
|
||||
d.current_term_set.data.term_sets
|
||||
?.map?.((t) => t?.terms?.wallets?.withdrawals?.cash_flow)
|
||||
?.filter?.(Boolean) ?? []
|
||||
);
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'cc-wallets-tariffs',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
PageLayoutModule,
|
||||
TableModule,
|
||||
InputFieldModule,
|
||||
FiltersModule,
|
||||
ReactiveFormsModule,
|
||||
MerchantFieldModule,
|
||||
ListFieldModule,
|
||||
CurrencyFieldComponent,
|
||||
WalletFieldModule,
|
||||
MatTooltip,
|
||||
VSelectPipe,
|
||||
],
|
||||
templateUrl: './wallets-tariffs.component.html',
|
||||
})
|
||||
export class WalletsTariffsComponent implements OnInit {
|
||||
filtersForm = this.fb.group(
|
||||
createControls<Params>({
|
||||
currencies: null,
|
||||
party_id: null,
|
||||
wallet_ids: null,
|
||||
term_sets_names: null,
|
||||
term_sets_ids: null,
|
||||
identity_ids: null,
|
||||
}),
|
||||
);
|
||||
tariffs$ = this.walletsTariffsService.result$;
|
||||
hasMore$ = this.walletsTariffsService.hasMore$;
|
||||
isLoading$ = this.walletsTariffsService.isLoading$;
|
||||
columns: Column<WalletTermSet>[] = [
|
||||
createWalletColumn<WalletTermSet>(
|
||||
'wallet_id',
|
||||
(d) => d.owner_id,
|
||||
undefined,
|
||||
(d) => d.wallet_name,
|
||||
{
|
||||
pinned: 'left',
|
||||
},
|
||||
),
|
||||
createPartyColumn<WalletTermSet>('owner_id'),
|
||||
createContractColumn<WalletTermSet>(
|
||||
(d) => d.contract_id,
|
||||
(d) => d.owner_id,
|
||||
(d) => d.wallet_id,
|
||||
),
|
||||
{ field: 'currency' },
|
||||
{
|
||||
field: 'current_term_set',
|
||||
formatter: (d) =>
|
||||
getDomainObjectDetails({ term_set_hierarchy: d.current_term_set })?.label,
|
||||
click: (d) =>
|
||||
this.sidenavInfoService.open(DomainObjectCardComponent, {
|
||||
ref: { term_set_hierarchy: d?.current_term_set?.ref },
|
||||
}),
|
||||
},
|
||||
{
|
||||
field: 'condition',
|
||||
formatter: (d) => getInlineDecisions(getViewedCashFlowSelectors(d)).map((v) => v.if),
|
||||
},
|
||||
{
|
||||
field: 'fee',
|
||||
formatter: (d) =>
|
||||
getInlineDecisions(
|
||||
getViewedCashFlowSelectors(d),
|
||||
(v) => v?.source?.wallet === 1 && v?.destination?.system === 0,
|
||||
).map((v) => v.value),
|
||||
},
|
||||
{
|
||||
field: 'other',
|
||||
formatter: (d) =>
|
||||
getInlineDecisions(
|
||||
getViewedCashFlowSelectors(d),
|
||||
(v) =>
|
||||
!(
|
||||
(v?.source?.wallet === 1 && v?.destination?.system === 0) ||
|
||||
(v?.source?.wallet === 1 &&
|
||||
v?.destination?.wallet === 3 &&
|
||||
formatCashVolume(v?.volume) === '100%')
|
||||
),
|
||||
).map((v) => v.value),
|
||||
tooltip: (d) =>
|
||||
getInlineDecisions(
|
||||
getViewedCashFlowSelectors(d),
|
||||
(v) =>
|
||||
!(
|
||||
(v?.source?.wallet === 1 && v?.destination?.system === 0) ||
|
||||
(v?.source?.wallet === 1 &&
|
||||
v?.destination?.wallet === 3 &&
|
||||
formatCashVolume(v?.volume) === '100%')
|
||||
),
|
||||
).map((v) => v.description),
|
||||
},
|
||||
{
|
||||
field: 'term_set_history',
|
||||
formatter: (d) => d.term_set_history?.length,
|
||||
tooltip: (d) => d.term_set_history,
|
||||
},
|
||||
];
|
||||
active$ = getValueChanges(this.filtersForm).pipe(
|
||||
map((filters) => countChanged(this.initFiltersValue, filters)),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
|
||||
private initFiltersValue = this.filtersForm.value;
|
||||
|
||||
constructor(
|
||||
private walletsTariffsService: WalletsTariffsService,
|
||||
private fb: NonNullableFormBuilder,
|
||||
private qp: QueryParamsService<Params>,
|
||||
@Inject(DEBOUNCE_TIME_MS) private debounceTimeMs: number,
|
||||
private dr: DestroyRef,
|
||||
private sidenavInfoService: SidenavInfoService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.filtersForm.patchValue(this.qp.params);
|
||||
getValueChanges(this.filtersForm)
|
||||
.pipe(debounceTimeWithFirst(this.debounceTimeMs), takeUntilDestroyed(this.dr))
|
||||
.subscribe((filters) => {
|
||||
void this.qp.set(filters);
|
||||
this.load(filters);
|
||||
});
|
||||
}
|
||||
|
||||
load(params: Params, options?: LoadOptions) {
|
||||
const { currencies, term_sets_ids, identity_ids, ...otherParams } = params;
|
||||
this.walletsTariffsService.load(
|
||||
clean({
|
||||
common_search_query_params: { currencies },
|
||||
term_sets_ids: term_sets_ids?.map((id) => ({ id })),
|
||||
identity_ids: identity_ids?.map((id) => ({ id })),
|
||||
...otherParams,
|
||||
}),
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
update(options?: UpdateOptions) {
|
||||
this.walletsTariffsService.reload(options);
|
||||
}
|
||||
|
||||
more() {
|
||||
this.walletsTariffsService.more();
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
type WalletSearchQuery,
|
||||
type WalletTermSet,
|
||||
} from '@vality/dominator-proto/internal/dominator';
|
||||
import {
|
||||
FetchOptions,
|
||||
FetchSuperclass,
|
||||
handleError,
|
||||
NotifyLogService,
|
||||
clean,
|
||||
} from '@vality/ng-core';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { DominatorService } from '@cc/app/api/dominator';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class WalletsTariffsService extends FetchSuperclass<WalletTermSet, WalletSearchQuery> {
|
||||
constructor(
|
||||
private dominatorService: DominatorService,
|
||||
private log: NotifyLogService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected fetch(params: WalletSearchQuery, options: FetchOptions<string>) {
|
||||
return this.dominatorService
|
||||
.SearchWalletTermSets({
|
||||
...params,
|
||||
common_search_query_params: clean({
|
||||
continuation_token: options.continuationToken,
|
||||
limit: options.size,
|
||||
currencies: params?.common_search_query_params?.currencies,
|
||||
}),
|
||||
})
|
||||
.pipe(
|
||||
map(({ terms, continuation_token }) => ({
|
||||
result: terms,
|
||||
continuationToken: continuation_token,
|
||||
})),
|
||||
handleError(this.log.error),
|
||||
);
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import { RouterModule } from '@angular/router';
|
||||
import { AppAuthGuardService } from '../../shared/services';
|
||||
|
||||
import { ShopsTariffsComponent } from './components/shops-tariffs/shops-tariffs.component';
|
||||
import { WalletsTariffsComponent } from './components/wallets-tariffs/wallets-tariffs.component';
|
||||
import { ROUTING_CONFIG } from './routing-config';
|
||||
import { TariffsComponent } from './tariffs.component';
|
||||
|
||||
@ -20,6 +21,10 @@ import { TariffsComponent } from './tariffs.component';
|
||||
path: 'shops',
|
||||
component: ShopsTariffsComponent,
|
||||
},
|
||||
{
|
||||
path: 'wallets',
|
||||
component: WalletsTariffsComponent,
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'shops',
|
||||
|
@ -18,6 +18,10 @@ export class TariffsComponent {
|
||||
label: 'Shops',
|
||||
url: 'shops',
|
||||
},
|
||||
{
|
||||
label: 'Wallets',
|
||||
url: 'wallets',
|
||||
},
|
||||
];
|
||||
|
||||
constructor(public sidenavInfoService: SidenavInfoService) {}
|
||||
|
107
src/app/sections/tariffs/utils/get-inline-decisions.ts
Normal file
107
src/app/sections/tariffs/utils/get-inline-decisions.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import { getUnionKey } from '@vality/ng-thrift';
|
||||
|
||||
import type {
|
||||
CashFlowPosting,
|
||||
CashFlowSelector,
|
||||
CashFlowAccount,
|
||||
} from '@vality/dominator-proto/internal/proto/domain';
|
||||
|
||||
import { formatPredicate, formatCashVolumes, compareCashVolumes } from '@cc/app/shared';
|
||||
|
||||
export interface InlineCashFlowSelector {
|
||||
if?: string;
|
||||
value?: string;
|
||||
parent?: InlineCashFlowSelector;
|
||||
description?: string;
|
||||
level: number;
|
||||
}
|
||||
|
||||
// TODO: use enums
|
||||
function formatCashFlowAccount(acc: CashFlowAccount) {
|
||||
return (
|
||||
getUnionKey(acc) +
|
||||
':' +
|
||||
(() => {
|
||||
switch (getUnionKey(acc)) {
|
||||
case 'system':
|
||||
return {
|
||||
0: 'settlement',
|
||||
1: 'subagent',
|
||||
}[acc.system];
|
||||
case 'merchant':
|
||||
return {
|
||||
0: 'settlement',
|
||||
1: 'guarantee',
|
||||
2: 'payout',
|
||||
}[acc.merchant];
|
||||
case 'wallet':
|
||||
return {
|
||||
0: 'sender_source',
|
||||
1: 'sender_settlement',
|
||||
2: 'receiver_settlement',
|
||||
3: 'receiver_destination',
|
||||
}[acc.wallet];
|
||||
case 'external':
|
||||
return {
|
||||
0: 'income',
|
||||
1: 'outcome',
|
||||
}[acc.external];
|
||||
case 'provider':
|
||||
return {
|
||||
0: 'settlement',
|
||||
}[acc.provider];
|
||||
}
|
||||
})()
|
||||
);
|
||||
}
|
||||
|
||||
export function getInlineDecisions(
|
||||
d: CashFlowSelector[],
|
||||
filterValue: (v: CashFlowPosting) => boolean = Boolean,
|
||||
level = 0,
|
||||
): InlineCashFlowSelector[] {
|
||||
return d.reduce((acc, c) => {
|
||||
if (c.value) {
|
||||
acc.push({
|
||||
value: formatCashVolumes(c.value.filter(filterValue).map((v) => v.volume)),
|
||||
level,
|
||||
description: c.value
|
||||
.filter(filterValue)
|
||||
.sort((a, b) => compareCashVolumes(a.volume, b.volume))
|
||||
.map(
|
||||
(v) =>
|
||||
`${formatCashFlowAccount(v.source)} → ${formatCashFlowAccount(
|
||||
v.destination,
|
||||
)}` + (v.details ? ` (${v.details})` : ''),
|
||||
)
|
||||
.join(', '),
|
||||
});
|
||||
}
|
||||
if (c.decisions?.length) {
|
||||
acc.push(
|
||||
...c.decisions
|
||||
.map((d) => {
|
||||
const thenInlineDecisions = getInlineDecisions(
|
||||
[d.then_],
|
||||
filterValue,
|
||||
level + 1,
|
||||
);
|
||||
if (d.if_) {
|
||||
const ifInlineDecision = {
|
||||
if: `${' '.repeat(level)}${
|
||||
formatPredicate(d.if_) || (level > 0 ? '↳' : '')
|
||||
}`,
|
||||
level,
|
||||
};
|
||||
return thenInlineDecisions.length > 1
|
||||
? [ifInlineDecision, ...thenInlineDecisions]
|
||||
: [{ ...thenInlineDecisions[0], ...ifInlineDecision }];
|
||||
}
|
||||
return thenInlineDecisions;
|
||||
})
|
||||
.flat(),
|
||||
);
|
||||
}
|
||||
return acc;
|
||||
}, [] as InlineCashFlowSelector[]);
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
import { Component, computed, input } from '@angular/core';
|
||||
import { TermSetHierarchyObject } from '@vality/domain-proto/internal/domain';
|
||||
|
||||
import { CardComponent } from '../sidenav-info/components/card/card.component';
|
||||
|
||||
@Component({
|
||||
selector: 'cc-termsets-card',
|
||||
standalone: true,
|
||||
imports: [CardComponent],
|
||||
templateUrl: './termsets-history-card.component.html',
|
||||
styles: ``,
|
||||
})
|
||||
export class TermsetsHistoryCardComponent {
|
||||
data = input<TermSetHierarchyObject[]>();
|
||||
feesData = computed(
|
||||
() =>
|
||||
this.data()?.map?.(
|
||||
(d) =>
|
||||
d?.data?.term_sets?.map?.((t) => t?.terms?.payments?.fees)?.filter?.(Boolean),
|
||||
),
|
||||
);
|
||||
}
|
@ -13,10 +13,10 @@ import {
|
||||
NotifyLogService,
|
||||
FormControlSuperclass,
|
||||
createControlProviders,
|
||||
getValueChanges,
|
||||
debounceTimeWithFirst,
|
||||
progressTo,
|
||||
} from '@vality/ng-core';
|
||||
import { BehaviorSubject, Observable, of, ReplaySubject, Subject, merge } from 'rxjs';
|
||||
import { BehaviorSubject, Observable, of, ReplaySubject, Subject, concat, forkJoin } from 'rxjs';
|
||||
import { catchError, map, switchMap, tap, distinctUntilChanged } from 'rxjs/operators';
|
||||
|
||||
import { DeanonimusService } from '@cc/app/api/deanonimus';
|
||||
@ -28,7 +28,10 @@ import { DEBOUNCE_TIME_MS } from '../../../tokens';
|
||||
templateUrl: 'wallet-field.component.html',
|
||||
providers: createControlProviders(() => WalletFieldComponent),
|
||||
})
|
||||
export class WalletFieldComponent extends FormControlSuperclass<WalletID> implements AfterViewInit {
|
||||
export class WalletFieldComponent
|
||||
extends FormControlSuperclass<WalletID | WalletID[]>
|
||||
implements AfterViewInit
|
||||
{
|
||||
@Input() label: string;
|
||||
@Input({ transform: booleanAttribute }) required: boolean;
|
||||
@Input() size?: string;
|
||||
@ -38,7 +41,7 @@ export class WalletFieldComponent extends FormControlSuperclass<WalletID> implem
|
||||
|
||||
options$ = new ReplaySubject<Option<WalletID>[]>(1);
|
||||
searchChange$ = new Subject<string>();
|
||||
progress$ = new BehaviorSubject(false);
|
||||
progress$ = new BehaviorSubject(0);
|
||||
|
||||
private debounceTimeMs = inject(DEBOUNCE_TIME_MS);
|
||||
|
||||
@ -51,20 +54,27 @@ export class WalletFieldComponent extends FormControlSuperclass<WalletID> implem
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
merge(getValueChanges(this.control), this.searchChange$)
|
||||
.pipe(
|
||||
concat(
|
||||
of(this.control.value).pipe(
|
||||
switchMap((term) =>
|
||||
forkJoin(
|
||||
(Array.isArray(term) ? term : [term ?? '']).map((t) => this.findOption(t)),
|
||||
),
|
||||
),
|
||||
map((o) => o.filter(Boolean)),
|
||||
),
|
||||
this.searchChange$.pipe(
|
||||
distinctUntilChanged(),
|
||||
tap(() => {
|
||||
this.options$.next([]);
|
||||
this.progress$.next(true);
|
||||
}),
|
||||
debounceTimeWithFirst(this.debounceTimeMs),
|
||||
switchMap((term) => this.searchOptions(term)),
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
)
|
||||
),
|
||||
)
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((options) => {
|
||||
this.options$.next(options);
|
||||
this.progress$.next(false);
|
||||
});
|
||||
}
|
||||
|
||||
@ -84,6 +94,13 @@ export class WalletFieldComponent extends FormControlSuperclass<WalletID> implem
|
||||
this.log.error(err, 'Search error');
|
||||
return of([]);
|
||||
}),
|
||||
progressTo(this.progress$),
|
||||
);
|
||||
}
|
||||
|
||||
private findOption(id: WalletID) {
|
||||
return this.searchOptions(id).pipe(
|
||||
map((options) => (options?.length ? options.find((o) => o.value === id) : null)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -12,18 +12,15 @@ export function createShopColumn<T extends object>(
|
||||
field: ColumnObject<T>['field'],
|
||||
selectPartyId: (d: T) => PossiblyAsync<string>,
|
||||
selectShopId?: (d: T) => PossiblyAsync<string>,
|
||||
selectShopName?: (d: T) => PossiblyAsync<string>,
|
||||
params: Partial<ColumnObject<T>> = {},
|
||||
): ColumnObject<T> {
|
||||
if (!selectShopId) {
|
||||
selectShopId = (d) => get(d, field);
|
||||
}
|
||||
const partiesStoreService = inject(PartiesStoreService);
|
||||
const sidenavInfoService = inject(SidenavInfoService);
|
||||
return {
|
||||
field,
|
||||
header: 'Shop',
|
||||
description: (d) => selectShopId(d),
|
||||
formatter: (d) =>
|
||||
if (!selectShopName) {
|
||||
const partiesStoreService = inject(PartiesStoreService);
|
||||
selectShopName = (d) =>
|
||||
getPossiblyAsyncObservable(selectPartyId(d)).pipe(
|
||||
switchMap((partyId) =>
|
||||
combineLatest([
|
||||
@ -32,7 +29,14 @@ export function createShopColumn<T extends object>(
|
||||
]),
|
||||
),
|
||||
map(([party, shopId]) => party.shops.get(shopId).details.name),
|
||||
),
|
||||
);
|
||||
}
|
||||
const sidenavInfoService = inject(SidenavInfoService);
|
||||
return {
|
||||
field,
|
||||
header: 'Shop',
|
||||
description: (d) => getPossiblyAsyncObservable(selectShopId(d)),
|
||||
formatter: (d) => getPossiblyAsyncObservable(selectShopName(d)),
|
||||
click: (d) => {
|
||||
combineLatest([
|
||||
getPossiblyAsyncObservable(selectPartyId(d)),
|
||||
|
39
src/app/shared/utils/table/create-wallet-column.ts
Normal file
39
src/app/shared/utils/table/create-wallet-column.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { inject } from '@angular/core';
|
||||
import { PossiblyAsync, ColumnObject, getPossiblyAsyncObservable } from '@vality/ng-core';
|
||||
import get from 'lodash-es/get';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
|
||||
import { PartiesStoreService } from '../../../api/payment-processing';
|
||||
|
||||
export function createWalletColumn<T extends object>(
|
||||
field: ColumnObject<T>['field'],
|
||||
selectPartyId: (d: T) => PossiblyAsync<string>,
|
||||
selectWalletId?: (d: T) => PossiblyAsync<string>,
|
||||
selectWalletName?: (d: T) => PossiblyAsync<string>,
|
||||
params: Partial<ColumnObject<T>> = {},
|
||||
): ColumnObject<T> {
|
||||
if (!selectWalletId) {
|
||||
selectWalletId = (d) => get(d, field);
|
||||
}
|
||||
if (!selectWalletName) {
|
||||
const partiesStoreService = inject(PartiesStoreService);
|
||||
selectWalletName = (d) =>
|
||||
getPossiblyAsyncObservable(selectPartyId(d)).pipe(
|
||||
switchMap((partyId) =>
|
||||
combineLatest([
|
||||
partiesStoreService.get(partyId),
|
||||
getPossiblyAsyncObservable(selectWalletId(d)),
|
||||
]),
|
||||
),
|
||||
map(([party, walletId]) => party.wallets.get(walletId)?.name),
|
||||
);
|
||||
}
|
||||
return {
|
||||
field,
|
||||
header: 'Wallet',
|
||||
description: (d) => getPossiblyAsyncObservable(selectWalletId(d)),
|
||||
formatter: (d) => getPossiblyAsyncObservable(selectWalletName(d)),
|
||||
...params,
|
||||
} as ColumnObject<T>;
|
||||
}
|
@ -4,6 +4,20 @@ import { getUnionKey, getUnionValue } from '@vality/ng-thrift';
|
||||
|
||||
import { formatRational } from './format-rational';
|
||||
|
||||
const CASH_VOLUME_PRIORITY: Record<keyof CashVolume, number> = {
|
||||
fixed: 0,
|
||||
share: 1,
|
||||
product: 2,
|
||||
};
|
||||
|
||||
export function compareCashVolumes(a: CashVolume, b: CashVolume) {
|
||||
return CASH_VOLUME_PRIORITY[getUnionKey(a)] - CASH_VOLUME_PRIORITY[getUnionKey(b)];
|
||||
}
|
||||
|
||||
export function formatCashVolumes(c: CashVolume[]) {
|
||||
return c.sort(compareCashVolumes).map(formatCashVolume).join(' + ');
|
||||
}
|
||||
|
||||
export function formatCashVolume(d: CashVolume) {
|
||||
switch (getUnionKey(d)) {
|
||||
case 'fixed':
|
||||
@ -16,6 +30,7 @@ export function formatCashVolume(d: CashVolume) {
|
||||
);
|
||||
case 'product':
|
||||
return `${getUnionKey(d.product).slice(0, -3)}(${Array.from(getUnionValue(d.product))
|
||||
.sort(compareCashVolumes)
|
||||
.map((c) => formatCashVolume(c))
|
||||
.join(', ')})`;
|
||||
}
|
||||
|
@ -7,3 +7,4 @@ export * from './create-contract-column';
|
||||
export * from './format-cash-volume';
|
||||
export * from './format-rational';
|
||||
export * from './format-predicate';
|
||||
export * from './create-wallet-column';
|
||||
|
Loading…
Reference in New Issue
Block a user