TD-935: New table with virtual scroll: Payments & Terms (#375)

This commit is contained in:
Rinat Arsaev 2024-07-15 19:00:40 +05:00 committed by GitHub
parent 043feb63bf
commit dc3855d791
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 457 additions and 280 deletions

33
package-lock.json generated
View File

@ -26,8 +26,8 @@
"@vality/fistful-proto": "2.0.1-88e69a5.0",
"@vality/machinegun-proto": "1.0.0",
"@vality/magista-proto": "2.0.2-28d11b9.0",
"@vality/ng-core": "18.1.0",
"@vality/ng-thrift": "18.0.1-pr-12-d099f93.0",
"@vality/ng-core": "18.2.1-pr-66-23525f5.0",
"@vality/ng-thrift": "18.0.1-pr-13-bdb6d51.0",
"@vality/payout-manager-proto": "2.0.1-eb4091a.0",
"@vality/repairer-proto": "2.0.2-07b73e9.0",
"@vality/scrooge-proto": "0.1.1-9ce7fc6.0",
@ -6615,9 +6615,9 @@
"integrity": "sha512-BsDy5ejotfTtUlwuoX3kz+PYJ5NSTW6m5ZRGv+p5HaKXSjR7tserPdv0q133Wp4T+sg0ED0Qr9Peqsrn+9XlDQ=="
},
"node_modules/@vality/ng-core": {
"version": "18.1.0",
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-18.1.0.tgz",
"integrity": "sha512-N0bOmfezQxKnZO9peMhMxlmMrH0A3dGrNNpzYgLoxO8XVIxoVSc9QpaXsb7YwR3M3pwmCWDjqxYXVhGVjVBrFA==",
"version": "18.2.1-pr-66-23525f5.0",
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-18.2.1-pr-66-23525f5.0.tgz",
"integrity": "sha512-Y4kMHbWgjMuDvk+ev7ZP7MY3QZFd1vaHy3ehGBAzNidzCQVC1eYkc3x6ta6VL8quRfrHzUGwphC50Pxgodxsaw==",
"dependencies": {
"@angular/material-date-fns-adapter": "^18.0.5",
"@ng-matero/extensions": "^18.0.3",
@ -6627,6 +6627,7 @@
"@s-libs/rxjs-core": "^18.0.0",
"dinero.js": "^2.0.0-alpha.14",
"fuse.js": "^7.0.0",
"ng-table-virtual-scroll": "^1.6.1",
"papaparse": "^5.4.1",
"tslib": "^2.3.0"
},
@ -6643,9 +6644,9 @@
}
},
"node_modules/@vality/ng-thrift": {
"version": "18.0.1-pr-12-d099f93.0",
"resolved": "https://registry.npmjs.org/@vality/ng-thrift/-/ng-thrift-18.0.1-pr-12-d099f93.0.tgz",
"integrity": "sha512-TjcZZWiVytQIOmuTGDKeZozWlhyjn04Npi4YXiGXG64tA0qtQgyWawl1OPUXTbX19tJMg7ib7tgZ/CIXrZrAWw==",
"version": "18.0.1-pr-13-bdb6d51.0",
"resolved": "https://registry.npmjs.org/@vality/ng-thrift/-/ng-thrift-18.0.1-pr-13-bdb6d51.0.tgz",
"integrity": "sha512-maXeenYBoPcjbvKZfR2/VkPRGxzxedOit0HXAqvK+h1RUiozmfkjC/YtXzxsiIAarPk36QQpES2xTmCvVb/WoQ==",
"dependencies": {
"tslib": "^2.3.0",
"yaml": "^2.4.5"
@ -6656,7 +6657,7 @@
"@angular/core": "^18.0.0",
"@angular/material": "^18.0.0",
"@types/lodash-es": "^4.0.0",
"@vality/ng-core": "^18.0.0 || ^18.0.1-pr",
"@vality/ng-core": "*",
"@vality/thrift-ts": "^2.4.1-8ad5123.0",
"lodash-es": "^4.0.0",
"rxjs": "^7.0.0",
@ -14090,6 +14091,20 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true
},
"node_modules/ng-table-virtual-scroll": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/ng-table-virtual-scroll/-/ng-table-virtual-scroll-1.6.1.tgz",
"integrity": "sha512-HXcRoPPHBBHU47HPsdegGoLKbu0UYnrIVVHLwwdtje1AduEKNY2ZCUK/T4nuX3biImSrKoydUa7/vbFtmYx86Q==",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/cdk": ">=15.0.0",
"@angular/common": ">=15.0.0",
"@angular/core": ">=15.0.0",
"@angular/material": ">=15.0.0"
}
},
"node_modules/ngx-color": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/ngx-color/-/ngx-color-9.0.0.tgz",

View File

@ -34,8 +34,8 @@
"@vality/fistful-proto": "2.0.1-88e69a5.0",
"@vality/machinegun-proto": "1.0.0",
"@vality/magista-proto": "2.0.2-28d11b9.0",
"@vality/ng-core": "18.1.0",
"@vality/ng-thrift": "18.0.1-pr-12-d099f93.0",
"@vality/ng-core": "18.2.1-pr-66-23525f5.0",
"@vality/ng-thrift": "18.0.1-pr-13-bdb6d51.0",
"@vality/payout-manager-proto": "2.0.1-eb4091a.0",
"@vality/repairer-proto": "2.0.2-07b73e9.0",
"@vality/scrooge-proto": "0.1.1-9ce7fc6.0",

View File

@ -1,4 +1,4 @@
<v-table
<v-table2
[columns]="columns"
[data]="data"
[hasMore]="hasMore"
@ -12,4 +12,4 @@
<v-table-actions>
<ng-content></ng-content>
</v-table-actions>
</v-table>
</v-table2>

View File

@ -1,20 +1,24 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { Router } from '@angular/router';
import { InvoicePaymentStatus } from '@vality/domain-proto/domain';
import { StatPayment } from '@vality/magista-proto/magista';
import { Column, TagColumn, LoadOptions, createOperationColumn } from '@vality/ng-core';
import { LoadOptions, Column2, createMenuColumn } from '@vality/ng-core';
import { getUnionKey } from '@vality/ng-thrift';
import startCase from 'lodash-es/startCase';
import { AmountCurrencyService } from '@cc/app/shared/services';
import { createFailureColumn, createPartyColumn, createShopColumn } from '../../../../shared';
import { createProviderColumn } from '../../../../shared/utils/table/create-provider-column';
import { createTerminalColumn } from '../../../../shared/utils/table/create-terminal-column';
import { createFailureColumn2 } from '../../../../shared';
import {
createPartyColumn,
createShopColumn,
createDomainObjectColumn,
createCurrencyColumn,
} from '../../../../shared/utils/table2';
@Component({
selector: 'cc-payments-table',
templateUrl: './payments-table.component.html',
styles: `:host { height: 100%; }`,
})
export class PaymentsTableComponent {
@Input() data!: StatPayment[];
@ -26,64 +30,58 @@ export class PaymentsTableComponent {
@Output() update = new EventEmitter<LoadOptions>();
@Output() more = new EventEmitter<void>();
columns: Column<StatPayment>[] = [
{ field: 'id', click: (d) => this.toDetails(d), pinned: 'left' },
{ field: 'invoice_id', pinned: 'left' },
{
columns: Column2<StatPayment>[] = [
{ field: 'id', cell: (d) => ({ click: () => this.toDetails(d) }), sticky: 'start' },
{ field: 'invoice_id', sticky: 'start' },
createCurrencyColumn((d) => ({ amount: d.amount, code: d.currency_symbolic_code }), {
field: 'amount',
type: 'currency',
formatter: (data) =>
this.amountCurrencyService.toMajor(data.amount, data.currency_symbolic_code),
typeParameters: {
currencyCode: 'currency_symbolic_code',
},
},
{
}),
createCurrencyColumn((d) => ({ amount: d.fee, code: d.currency_symbolic_code }), {
field: 'fee',
type: 'currency',
formatter: (data) =>
this.amountCurrencyService.toMajor(data.fee, data.currency_symbolic_code),
typeParameters: {
currencyCode: 'currency_symbolic_code',
},
},
}),
{
field: 'status',
type: 'tag',
formatter: (data) => getUnionKey(data.status),
typeParameters: {
label: (data) => startCase(getUnionKey(data.status)),
tags: {
captured: { color: 'success' },
refunded: { color: 'success' },
charged_back: { color: 'success' },
pending: { color: 'pending' },
processed: { color: 'pending' },
failed: { color: 'warn' },
cancelled: { color: 'neutral' },
cell: (d) => ({
value: startCase(getUnionKey(d.status)),
color: (
{
captured: 'success',
refunded: 'success',
charged_back: 'success',
pending: 'pending',
processed: 'pending',
failed: 'warn',
cancelled: 'neutral',
} as const
)[getUnionKey(d.status)],
}),
},
},
} as TagColumn<StatPayment, keyof InvoicePaymentStatus>,
{ field: 'created_at', type: 'datetime' },
createPartyColumn('owner_id'),
createShopColumn('shop_id', (d) => d.owner_id),
'domain_revision',
createTerminalColumn((d) => d.terminal_id.id),
createProviderColumn((d) => d.provider_id.id),
'external_id',
createFailureColumn<StatPayment>(
(d) => d.status?.failed?.failure?.failure,
(d) =>
{ field: 'created_at', cell: { type: 'datetime' } },
createPartyColumn((d) => ({ id: d.owner_id })),
createShopColumn((d) => ({ partyId: d.owner_id, shopId: d.shop_id })),
{ field: 'domain_revision' },
createDomainObjectColumn((d) => ({ ref: { terminal: d.terminal_id } }), {
header: 'Terminal',
}),
createDomainObjectColumn((d) => ({ ref: { provider: d.provider_id } }), {
header: 'Provider',
}),
{ field: 'external_id' },
createFailureColumn2((d) => ({
failure: d.status?.failed?.failure?.failure,
noFailureMessage:
getUnionKey(d.status?.failed?.failure) === 'failure'
? ''
: startCase(getUnionKey(d.status?.failed?.failure)),
),
createOperationColumn<StatPayment>([
})),
createMenuColumn((d) => ({
items: [
{
label: 'Details',
click: (data) => this.toDetails(data),
click: () => this.toDetails(d),
},
]),
],
})),
];
constructor(

View File

@ -1,4 +1,4 @@
<cc-page-layout title="Payments">
<cc-page-layout fullHeight title="Payments">
<cc-page-layout-actions>
<v-more-filters-button [filters]="filters"></v-more-filters-button>
</cc-page-layout-actions>

View File

@ -18,11 +18,10 @@
<v-table2
[columns]="columns"
[data]="tariffs$ | async"
[hasMore]="hasMore$ | async"
[maxSize]="250"
[progress]="isLoading$ | async"
infinityScroll
[treeData]="tariffs$ | async"
(more)="more()"
(update)="update($event)"
></v-table2>

View File

@ -40,10 +40,15 @@ import {
createContractColumn,
createDomainObjectColumn,
} from '../../../../shared/utils/table2';
import { getInlineDecisions2, InlineDecision2 } from '../../utils/get-inline-decisions';
import { ShopsTermSetHistoryCardComponent } from '../shops-term-set-history-card';
import { ShopsTariffsService } from './shops-tariffs.service';
import { createShopFeesColumn } from './utils/create-shop-fees-column';
import {
isShopTermSetDecision,
SHOP_FEES_COLUMNS,
getShopCashFlowSelectors,
} from './utils/shop-fees-columns';
type Params = Pick<CommonSearchQueryParams, 'currencies'> &
Overwrite<
@ -80,10 +85,24 @@ export class ShopsTariffsComponent implements OnInit {
term_sets_ids: null,
}),
);
tariffs$ = this.shopsTariffsService.result$;
tariffs$ = this.shopsTariffsService.result$.pipe(
map((terms) =>
terms.map((t) => ({
value: t,
children: getInlineDecisions2(getShopCashFlowSelectors(t.current_term_set)).filter(
(v) =>
isShopTermSetDecision(v, {
partyId: t.owner_id,
shopId: t.shop_id,
currency: t.currency,
}),
),
})),
),
);
hasMore$ = this.shopsTariffsService.hasMore$;
isLoading$ = this.shopsTariffsService.isLoading$;
columns: Column2<ShopTermSet>[] = [
columns: Column2<ShopTermSet, InlineDecision2>[] = [
createShopColumn(
(d) => ({
shopId: d.shop_id,
@ -101,12 +120,7 @@ export class ShopsTariffsComponent implements OnInit {
createDomainObjectColumn((d) => ({ ref: { term_set_hierarchy: d.current_term_set.ref } }), {
header: 'Term Set',
}),
...createShopFeesColumn<ShopTermSet>(
(d) => d?.current_term_set,
(d) => d.owner_id,
(d) => d.shop_id,
(d) => d.currency,
),
...SHOP_FEES_COLUMNS,
{
field: 'term_set_history',
cell: (d) => ({

View File

@ -1,52 +0,0 @@
import { Column2 } from '@vality/ng-core';
import type {
TermSetHierarchyObject,
CashFlowPosting,
} from '@vality/dominator-proto/internal/proto/domain';
import { createFeesColumns } from '../../../utils/create-fees-columns';
import {
getInlineDecisions,
type InlineCashFlowSelector,
} from '../../../utils/get-inline-decisions';
export function getViewedCashFlowSelectors(d: TermSetHierarchyObject) {
return d?.data?.term_sets?.map?.((t) => t?.terms?.payments?.fees)?.filter?.(Boolean) ?? [];
}
export function createShopFeesColumn<T extends object>(
fn: (d: T) => TermSetHierarchyObject = (d) => d as never,
getPartyId: (d: T) => string,
getShopId: (d: T) => string,
getCurrency: (d: T) => string,
): Column2<T>[] {
const filterRreserve = (v: CashFlowPosting) =>
v?.source?.merchant === 0 && v?.destination?.merchant === 1;
const filterDecisions = (d: T) => (v: InlineCashFlowSelector) =>
(!v?.if?.condition?.party?.definition?.shop_is ||
(v?.if?.condition?.party?.id === getPartyId(d) &&
v?.if?.condition?.party?.definition?.shop_is === getShopId(d))) &&
(!getCurrency(d) ||
!v?.if?.condition?.currency_is?.symbolic_code ||
v?.if?.condition?.currency_is?.symbolic_code === getCurrency(d));
const cols = createFeesColumns<T>(
(d) => getViewedCashFlowSelectors(fn(d)),
(v) => v?.source?.merchant === 0 && v?.destination?.system === 0,
(v) => !filterRreserve(v),
filterDecisions,
);
return [
...cols.slice(0, -1),
{
field: 'rreserve',
header: 'RReserve',
cell: (d) => ({
value: getInlineDecisions(getViewedCashFlowSelectors(fn(d)), filterRreserve)
.filter(filterDecisions(d))
.map((v) => v.value),
}),
},
cols.at(-1),
];
}

View File

@ -0,0 +1,100 @@
import {
CashFlowPosting,
Predicate,
PartyID,
ShopID,
TermSetHierarchyObject,
} from '@vality/domain-proto/internal/domain';
import { Column2 } from '@vality/ng-core';
import { getCashVolumeParts, formatCashVolumes } from '../../../../../shared';
import { InlineDecision2, formatLevelPredicate } from '../../../utils/get-inline-decisions';
import { isOneHundredPercentCashFlowPosting } from '../../../utils/is-one-hundred-percent-cash-flow-posting';
import { isThatCurrency } from '../../../utils/is-that-currency';
export function getShopCashFlowSelectors(d: TermSetHierarchyObject) {
return d?.data?.term_sets?.map?.((t) => t?.terms?.payments?.fees)?.filter?.(Boolean) ?? [];
}
export function isShopFee(v: CashFlowPosting) {
return v?.source?.merchant === 0 && v?.destination?.system === 0;
}
export function isShopRreserve(v: CashFlowPosting) {
return v?.source?.merchant === 0 && v?.destination?.merchant === 1;
}
export function isThatShopParty(predicate: Predicate, partyId: PartyID, shopId: ShopID) {
return (
predicate?.condition?.party?.id === partyId &&
predicate?.condition?.party?.definition?.shop_is === shopId
);
}
export function isShopTermSetDecision(
v: InlineDecision2,
params: { partyId: PartyID; shopId: ShopID; currency: string },
) {
return (
(!v?.if?.condition?.party?.definition?.shop_is ||
isThatShopParty(v?.if, params.partyId, params.shopId)) &&
(!v?.if?.condition?.currency_is?.symbolic_code || isThatCurrency(v?.if, params.currency))
);
}
export const SHOP_FEES_COLUMNS = [
{
field: 'condition',
child: (d) => ({ value: formatLevelPredicate(d) }),
},
{
field: 'feeShare',
header: 'Fee, %',
child: (d) => ({
value: getCashVolumeParts(d.value.filter(isShopFee).map((v) => v.volume))?.share,
}),
},
{
field: 'feeFixed',
header: 'Fee, fix',
child: (d) => ({
value: getCashVolumeParts(d.value.filter(isShopFee).map((v) => v.volume))?.fixed,
}),
},
{
field: 'feeMin',
header: 'Fee, min',
child: (d) => ({
value: getCashVolumeParts(d.value.filter(isShopFee).map((v) => v.volume))?.max,
}),
},
{
field: 'feeMax',
header: 'Fee, max',
child: (d) => ({
value: getCashVolumeParts(d.value.filter(isShopFee).map((v) => v.volume))?.min,
}),
},
{
field: 'rreserve',
header: 'RReserve',
child: (d) => ({
value: formatCashVolumes(d.value.filter(isShopRreserve).map((v) => v.volume)),
}),
},
{
field: 'other',
child: (d) => ({
value: formatCashVolumes(
d.value
.filter(
(v) =>
!isShopFee(v) &&
!isShopRreserve(v) &&
!isOneHundredPercentCashFlowPosting(v),
)
.map((v) => v.volume),
),
}),
},
] satisfies Column2<object, InlineDecision2>[];

View File

@ -1,2 +1 @@
export * from './shops-term-set-history-card.component';
export * from '../../utils/create-fees-columns';

View File

@ -1,3 +1,3 @@
<cc-card [title]="'Term Sets History'">
<v-table2 [columns]="columns" [data]="historyData()" infinityScroll></v-table2>
<v-table2 [columns]="columns" [treeData]="historyData()"></v-table2>
</cc-card>

View File

@ -7,7 +7,12 @@ import type { TermSetHistory, ShopTermSet } from '@vality/dominator-proto/intern
import { SidenavInfoModule } from '../../../../shared/components/sidenav-info';
import { getDomainObjectDetails } from '../../../../shared/components/thrift-api-crud';
import { createShopFeesColumn } from '../shops-tariffs/utils/create-shop-fees-column';
import { getInlineDecisions2 } from '../../utils/get-inline-decisions';
import {
getShopCashFlowSelectors,
isShopTermSetDecision,
SHOP_FEES_COLUMNS,
} from '../shops-tariffs/utils/shop-fees-columns';
@Component({
selector: 'cc-shops-term-set-history-card',
@ -18,7 +23,18 @@ import { createShopFeesColumn } from '../shops-tariffs/utils/create-shop-fees-co
})
export class ShopsTermSetHistoryCardComponent {
data = input<ShopTermSet>();
historyData = computed(() => this.data()?.term_set_history?.reverse?.());
historyData = computed(() =>
(this.data()?.term_set_history?.reverse?.() || []).map((t) => ({
value: t,
children: getInlineDecisions2(getShopCashFlowSelectors(t.term_set)).filter((v) =>
isShopTermSetDecision(v, {
partyId: this.data().owner_id,
shopId: this.data().shop_id,
currency: this.data().currency,
}),
),
})),
);
columns: Column2<TermSetHistory>[] = [
{ field: 'applied_at', cell: { type: 'datetime' } },
@ -30,11 +46,6 @@ export class ShopsTermSetHistoryCardComponent {
?.description,
}),
},
...createShopFeesColumn<TermSetHistory>(
(d) => d.term_set,
() => this.data().owner_id,
() => this.data().shop_id,
() => this.data().currency,
),
...SHOP_FEES_COLUMNS,
];
}

View File

@ -1,2 +1 @@
export * from './terminals-term-set-history-card.component';
export * from '../../utils/create-fees-columns';

View File

@ -1,30 +0,0 @@
import type { InlineCashFlowSelector } from '../../../utils/get-inline-decisions';
import type { TermSetHierarchyObject } from '@vality/dominator-proto/internal/proto/domain';
import { createFeesColumns } from '../../../utils/create-fees-columns';
export function getViewedCashFlowSelectors(d: TermSetHierarchyObject) {
return (
d.data.term_sets
?.map?.((t) => t?.terms?.wallets?.withdrawals?.cash_flow)
?.filter?.(Boolean) ?? []
);
}
export function createWalletFeesColumn<T extends object = TermSetHierarchyObject>(
fn: (d: T) => TermSetHierarchyObject = (d) => d as never,
getWalletId: (d: T) => string,
getCurrency: (d: T) => string,
) {
return createFeesColumns<T>(
(d) => getViewedCashFlowSelectors(fn(d)),
(v) => v?.source?.wallet === 1 && v?.destination?.system === 0,
undefined,
(d: T) => (v: InlineCashFlowSelector) =>
(!v?.if?.condition?.party?.definition?.wallet_is ||
v?.if?.condition?.party?.definition?.wallet_is === getWalletId(d)) &&
(!getCurrency(d) ||
!v?.if?.condition?.currency_is?.symbolic_code ||
v?.if?.condition?.currency_is?.symbolic_code === getCurrency(d)),
);
}

View File

@ -0,0 +1,89 @@
import {
CashFlowPosting,
PartyID,
Predicate,
WalletID,
} from '@vality/domain-proto/internal/domain';
import { Column2 } from '@vality/ng-core';
import type { TermSetHierarchyObject } from '@vality/dominator-proto/internal/proto/domain';
import { getCashVolumeParts, formatCashVolumes } from '../../../../../shared';
import { InlineDecision2, formatLevelPredicate } from '../../../utils/get-inline-decisions';
import { isOneHundredPercentCashFlowPosting } from '../../../utils/is-one-hundred-percent-cash-flow-posting';
import { isThatCurrency } from '../../../utils/is-that-currency';
export function getWalletCashFlowSelectors(d: TermSetHierarchyObject) {
return (
d.data.term_sets
?.map?.((t) => t?.terms?.wallets?.withdrawals?.cash_flow)
?.filter?.(Boolean) ?? []
);
}
export function isWalletFee(v: CashFlowPosting) {
return v?.source?.wallet === 1 && v?.destination?.system === 0;
}
export function isThatWalletParty(predicate: Predicate, partyId: PartyID, walletId: WalletID) {
return (
predicate?.condition?.party?.id === partyId &&
predicate?.condition?.party?.definition?.wallet_is === walletId
);
}
export function isWalletTermSetDecision(
v: InlineDecision2,
params: { partyId: PartyID; walletId: WalletID; currency: string },
) {
return (
(!v?.if?.condition?.party?.definition?.wallet_is ||
isThatWalletParty(v?.if, params.partyId, params.walletId)) &&
(!v?.if?.condition?.currency_is?.symbolic_code || isThatCurrency(v?.if, params.currency))
);
}
export const WALLET_FEES_COLUMNS = [
{
field: 'condition',
child: (d) => ({ value: formatLevelPredicate(d) }),
},
{
field: 'feeShare',
header: 'Fee, %',
child: (d) => ({
value: getCashVolumeParts(d.value.filter(isWalletFee).map((v) => v.volume))?.share,
}),
},
{
field: 'feeFixed',
header: 'Fee, fix',
child: (d) => ({
value: getCashVolumeParts(d.value.filter(isWalletFee).map((v) => v.volume))?.fixed,
}),
},
{
field: 'feeMin',
header: 'Fee, min',
child: (d) => ({
value: getCashVolumeParts(d.value.filter(isWalletFee).map((v) => v.volume))?.max,
}),
},
{
field: 'feeMax',
header: 'Fee, max',
child: (d) => ({
value: getCashVolumeParts(d.value.filter(isWalletFee).map((v) => v.volume))?.min,
}),
},
{
field: 'other',
child: (d) => ({
value: formatCashVolumes(
d.value
.filter((v) => !isWalletFee(v) && !isOneHundredPercentCashFlowPosting(v))
.map((v) => v.volume),
),
}),
},
] satisfies Column2<object, InlineDecision2>[];

View File

@ -15,11 +15,10 @@
<v-table2
[columns]="columns"
[data]="tariffs$ | async"
[hasMore]="hasMore$ | async"
[maxSize]="250"
[progress]="isLoading$ | async"
infinityScroll
[treeData]="tariffs$ | async"
(more)="more()"
(update)="update($event)"
></v-table2>

View File

@ -42,9 +42,14 @@ import {
} from '@cc/app/shared/utils/table2';
import { DEBOUNCE_TIME_MS } from '@cc/app/tokens';
import { InlineDecision2, getInlineDecisions2 } from '../../utils/get-inline-decisions';
import { WalletsTermSetHistoryCardComponent } from '../wallets-term-set-history-card';
import { createWalletFeesColumn } from './utils/create-wallet-fees-column';
import {
WALLET_FEES_COLUMNS,
getWalletCashFlowSelectors,
isWalletTermSetDecision,
} from './utils/wallet-fees-columns';
import { WalletsTariffsService } from './wallets-tariffs.service';
type Params = Pick<CommonSearchQueryParams, 'currencies'> &
@ -83,10 +88,25 @@ export class WalletsTariffsComponent implements OnInit {
identity_ids: null,
}),
);
tariffs$ = this.walletsTariffsService.result$;
tariffs$ = this.walletsTariffsService.result$.pipe(
map((terms) =>
terms.map((t) => ({
value: t,
children: getInlineDecisions2(
getWalletCashFlowSelectors(t.current_term_set),
).filter((v) =>
isWalletTermSetDecision(v, {
partyId: t.owner_id,
walletId: t.wallet_id,
currency: t.currency,
}),
),
})),
),
);
hasMore$ = this.walletsTariffsService.hasMore$;
isLoading$ = this.walletsTariffsService.isLoading$;
columns: Column2<WalletTermSet>[] = [
columns: Column2<WalletTermSet, InlineDecision2>[] = [
createWalletColumn((d) => ({ id: d.wallet_id, name: d.wallet_name, partyId: d.owner_id }), {
sticky: 'start',
}),
@ -97,21 +117,13 @@ export class WalletsTariffsComponent implements OnInit {
createDomainObjectColumn((d) => ({ ref: { term_set_hierarchy: d.current_term_set.ref } }), {
header: 'Term Set',
}),
...createWalletFeesColumn<WalletTermSet>(
(d) => d.current_term_set,
(d) => d.wallet_id,
(d) => d.currency,
),
...WALLET_FEES_COLUMNS,
{
field: 'term_set_history',
cell: (d) => ({
value: d.term_set_history?.length || '',
click: () =>
this.sidenavInfoService.open(WalletsTermSetHistoryCardComponent, {
data: d?.term_set_history?.reverse(),
walletId: d?.wallet_id,
currency: d?.currency,
}),
this.sidenavInfoService.open(WalletsTermSetHistoryCardComponent, { data: d }),
}),
},
];

View File

@ -1,2 +1 @@
export * from './wallets-term-set-history-card.component';
export * from '../../utils/create-fees-columns';

View File

@ -1,3 +1,3 @@
<cc-card [title]="'Term Sets History'">
<v-table2 [columns]="columns" [data]="data()" infinityScroll></v-table2>
<v-table2 [columns]="columns" [treeData]="historyData()"></v-table2>
</cc-card>

View File

@ -1,13 +1,18 @@
import { CommonModule } from '@angular/common';
import { Component, input } from '@angular/core';
import { Component, input, computed } from '@angular/core';
import { MatTooltip } from '@angular/material/tooltip';
import { TableModule, VSelectPipe, Column2 } from '@vality/ng-core';
import type { TermSetHistory } from '@vality/dominator-proto/internal/dominator';
import type { TermSetHistory, WalletTermSet } from '@vality/dominator-proto/internal/dominator';
import { SidenavInfoModule } from '../../../../shared/components/sidenav-info';
import { getDomainObjectDetails } from '../../../../shared/components/thrift-api-crud';
import { createWalletFeesColumn } from '../wallets-tariffs/utils/create-wallet-fees-column';
import { getInlineDecisions2 } from '../../utils/get-inline-decisions';
import {
WALLET_FEES_COLUMNS,
isWalletTermSetDecision,
getWalletCashFlowSelectors,
} from '../wallets-tariffs/utils/wallet-fees-columns';
@Component({
selector: 'cc-wallets-term-set-history-card',
@ -17,9 +22,20 @@ import { createWalletFeesColumn } from '../wallets-tariffs/utils/create-wallet-f
styles: ``,
})
export class WalletsTermSetHistoryCardComponent {
data = input<TermSetHistory[]>();
walletId = input<string>();
currency = input<string>();
data = input<WalletTermSet>();
historyData = computed(() =>
(this.data()?.term_set_history?.reverse?.() || []).map((t) => ({
value: t,
children: getInlineDecisions2(getWalletCashFlowSelectors(t.term_set)).filter((v) =>
isWalletTermSetDecision(v, {
partyId: this.data().owner_id,
walletId: this.data().wallet_id,
currency: this.data().currency,
}),
),
})),
);
columns: Column2<TermSetHistory>[] = [
{ field: 'applied_at', cell: { type: 'datetime' } },
{
@ -30,10 +46,6 @@ export class WalletsTermSetHistoryCardComponent {
?.description,
}),
},
...createWalletFeesColumn<TermSetHistory>(
(d) => d.term_set,
() => this.walletId(),
() => this.currency(),
),
...WALLET_FEES_COLUMNS,
];
}

View File

@ -1,71 +0,0 @@
import type {
CashFlowSelector,
CashFlowPosting,
} from '@vality/dominator-proto/internal/proto/domain';
import type { Column2 } from '@vality/ng-core';
import {
getInlineDecisions,
formatLevelPredicate,
type InlineCashFlowSelector,
} from './get-inline-decisions';
export function createFeesColumns<T extends object>(
getFees: (d: T) => CashFlowSelector[],
filterFee: (v: CashFlowPosting) => boolean,
filterOther: (v: CashFlowPosting) => boolean = () => true,
filterDecisions: (d: T) => (v: InlineCashFlowSelector) => boolean = () => () => true,
): Column2<T>[] {
const filterOtherFn: (v: CashFlowPosting) => boolean = (v) =>
!filterFee(v) &&
filterOther(v) &&
!(v?.volume?.share?.parts?.p === 1 && v?.volume?.share?.parts?.q === 1);
return [
{
field: 'condition',
cell: (d) =>
getInlineDecisions(getFees(d), filterFee)
.filter(filterDecisions(d))
.map((v) => ({ value: formatLevelPredicate(v) })),
},
{
field: 'feeShare',
header: 'Fee, %',
cell: (d) =>
getInlineDecisions(getFees(d), filterFee)
.filter(filterDecisions(d))
.map((v) => ({ value: v.parts?.share })),
},
{
field: 'feeFixed',
header: 'Fee, fix',
cell: (d) =>
getInlineDecisions(getFees(d), filterFee)
.filter(filterDecisions(d))
.map((v) => ({ value: v.parts?.fixed })),
},
{
field: 'feeMin',
header: 'Fee, min',
cell: (d) =>
getInlineDecisions(getFees(d), filterFee)
.filter(filterDecisions(d))
.map((v) => ({ value: v.parts?.max })),
},
{
field: 'feeMax',
header: 'Fee, max',
cell: (d) =>
getInlineDecisions(getFees(d), filterFee)
.filter(filterDecisions(d))
.map((v) => ({ value: v.parts?.min })),
},
{
field: 'other',
cell: (d) =>
getInlineDecisions(getFees(d), filterOtherFn)
.filter(filterDecisions(d))
.map((v) => ({ value: v.value, tooltip: v.description })),
},
];
}

View File

@ -1,3 +1,4 @@
import { CashFlow } from '@vality/domain-proto/internal/domain';
import { getUnionKey } from '@vality/ng-thrift';
import type {
@ -63,7 +64,7 @@ function formatCashFlowAccount(acc: CashFlowAccount) {
);
}
export function formatLevelPredicate(v: InlineCashFlowSelector) {
export function formatLevelPredicate(v: { level: number; if?: Predicate }) {
return `${'\xa0'.repeat(Math.max(v.level - 1, 0))}${v.level > 0 ? '↳' : ''} ${formatPredicate(
v.if,
)}`;
@ -118,3 +119,38 @@ export function getInlineDecisions(
return acc;
}, [] as InlineCashFlowSelector[]);
}
export interface InlineDecision2 {
value: CashFlow;
level: number;
if?: Predicate;
}
export function getInlineDecisions2(d: CashFlowSelector[], level = 0) {
return d.reduce<InlineDecision2[]>((acc, c) => {
if (c.value) {
acc.push({
value: c.value.sort((a, b) => compareCashVolumes(a.volume, b.volume)),
level,
});
}
if (c.decisions?.length) {
acc.push(
...c.decisions.flatMap((d) => {
const thenInlineDecisions = getInlineDecisions2([d.then_], level + 1);
if (d.if_) {
const ifInlineDecision = {
if: d.if_,
level,
};
return thenInlineDecisions.length > 1
? [{ ...ifInlineDecision, value: [] }, ...thenInlineDecisions]
: [{ ...thenInlineDecisions[0], ...ifInlineDecision }];
}
return thenInlineDecisions;
}),
);
}
return acc;
}, []);
}

View File

@ -0,0 +1,5 @@
import { CashFlowPosting } from '@vality/domain-proto/domain';
export function isOneHundredPercentCashFlowPosting(v: CashFlowPosting) {
return v?.volume?.share?.parts?.p === 1 && v?.volume?.share?.parts?.q === 1;
}

View File

@ -0,0 +1,5 @@
import { Predicate } from '@vality/domain-proto/internal/domain';
export function isThatCurrency(predicate: Predicate, currency: string) {
return predicate?.condition?.currency_is?.symbolic_code === currency;
}

View File

@ -1,5 +1,10 @@
import { Failure } from '@vality/domain-proto/domain';
import { PossiblyAsync, ColumnObject, getPossiblyAsyncObservable } from '@vality/ng-core';
import {
PossiblyAsync,
ColumnObject,
getPossiblyAsyncObservable,
createColumn,
} from '@vality/ng-core';
import { of } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
@ -45,3 +50,12 @@ export function createFailureColumn<T extends object>(
...params,
} as ColumnObject<T>;
}
export const createFailureColumn2 = createColumn(
({ failure, noFailureMessage }: { failure: Failure; noFailureMessage: string }) => ({
value: noFailureMessage || getFailureMessageTree(failure, false, 2),
description: failure?.reason || '',
tooltip: failure?.sub?.sub ? JSON.stringify(failure.sub.sub, null, 2) : '',
}),
{ header: 'Failure' },
);

View File

@ -14,5 +14,5 @@ export const createContractColumn = createColumn(
},
};
},
'Contract',
{ header: 'Contract' },
);

View File

@ -0,0 +1,23 @@
import { inject } from '@angular/core';
import { createColumn } from '@vality/ng-core';
import isNil from 'lodash-es/isNil';
import { of } from 'rxjs';
import { map } from 'rxjs/operators';
import { AmountCurrencyService } from '../../services';
export const createCurrencyColumn = createColumn(
({ amount, code }: { amount: number; code: string }) => {
const amountCurrencyService = inject(AmountCurrencyService);
return (isNil(amount) ? of(undefined) : amountCurrencyService.toMajor(amount, code)).pipe(
map((value) => ({
value,
type: 'currency',
params: {
code,
major: true,
},
})),
);
},
);

View File

@ -22,4 +22,4 @@ export const createDomainObjectColumn = createColumn(({ ref }: { ref: Reference
},
})),
);
}, 'Object');
});

View File

@ -24,5 +24,5 @@ export const createPartyColumn = createColumn(
})),
);
},
'Party',
{ header: 'Party' },
);

View File

@ -26,5 +26,5 @@ export const createShopColumn = createColumn(
})),
);
},
'Shop',
{ header: 'Shop' },
);

View File

@ -20,5 +20,5 @@ export const createWalletColumn = createColumn(
})),
);
},
'Wallet',
{ header: 'Wallet' },
);

View File

@ -3,3 +3,4 @@ export * from './create-party-column';
export * from './create-wallet-column';
export * from './create-contract-column';
export * from './create-domain-object-column';
export * from './create-currency-column';