FIN-80: New terminals fees view (#384)

This commit is contained in:
Rinat Arsaev 2024-09-04 16:36:04 +05:00 committed by GitHub
parent ba1960d746
commit 7fa32c653c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 2304 additions and 2684 deletions

4181
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -16,17 +16,17 @@
"fix": "npm run lint:fix && npm run format:fix && npm run spell:fix"
},
"dependencies": {
"@angular/animations": "18.0.5",
"@angular/cdk": "18.0.5",
"@angular/common": "18.0.5",
"@angular/compiler": "18.0.5",
"@angular/core": "18.0.5",
"@angular/forms": "18.0.5",
"@angular/material": "18.0.5",
"@angular/platform-browser": "18.0.5",
"@angular/platform-browser-dynamic": "18.0.5",
"@angular/platform-server": "18.0.5",
"@angular/router": "18.0.5",
"@angular/animations": "18.2.2",
"@angular/cdk": "18.2.2",
"@angular/common": "18.2.2",
"@angular/compiler": "18.2.2",
"@angular/core": "18.2.2",
"@angular/forms": "18.2.2",
"@angular/material": "18.2.2",
"@angular/platform-browser": "18.2.2",
"@angular/platform-browser-dynamic": "18.2.2",
"@angular/platform-server": "18.2.2",
"@angular/router": "18.2.2",
"@ngneat/input-mask": "6.0.0",
"@vality/deanonimus-proto": "2.0.1-2a02d87.0",
"@vality/domain-proto": "2.0.1-e5d3c83.0",
@ -34,7 +34,7 @@
"@vality/fistful-proto": "2.0.1-88e69a5.0",
"@vality/machinegun-proto": "1.0.0",
"@vality/magista-proto": "2.0.2-ec1bdb9.0",
"@vality/ng-core": "18.2.0",
"@vality/ng-core": "18.3.1-pr-67-675080b.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",
@ -54,14 +54,14 @@
"tslib": "2.3.1",
"utility-types": "3.10.0",
"yaml": "2.4.5",
"zone.js": "0.14.2"
"zone.js": "0.14.10"
},
"devDependencies": {
"@angular-devkit/build-angular": "18.0.6",
"@angular-devkit/build-angular": "18.2.2",
"@angular-eslint/builder": "18.0.1",
"@angular-eslint/schematics": "18.0.1",
"@angular/cli": "18.0.6",
"@angular/compiler-cli": "18.0.5",
"@angular/cli": "18.2.2",
"@angular/compiler-cli": "18.2.2",
"@types/inputmask": "5.0.3",
"@types/jasmine": "4.0.3",
"@types/jwt-decode": "2.2.1",

View File

@ -114,11 +114,6 @@ export class AppComponent {
url: '/payments',
services: PAYMENTS_ROUTING_CONFIG.services,
},
{
label: 'Old Payments',
url: '/old-payments',
services: PAYMENTS_ROUTING_CONFIG.services,
},
{
label: 'Chargebacks',
url: '/chargebacks',

View File

@ -1,7 +1,7 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { Router } from '@angular/router';
import { StatPayment } from '@vality/magista-proto/magista';
import { LoadOptions, Column2, createMenuColumn } from '@vality/ng-core';
import { LoadOptions, Column2, createMenuColumn, TABLE_WRAPPER_STYLE } from '@vality/ng-core';
import { getUnionKey } from '@vality/ng-thrift';
import startCase from 'lodash-es/startCase';
@ -18,7 +18,7 @@ import {
@Component({
selector: 'cc-payments-table',
templateUrl: './payments-table.component.html',
styles: `:host { height: 100%; }`,
host: { style: TABLE_WRAPPER_STYLE },
})
export class PaymentsTableComponent {
@Input() data!: StatPayment[];
@ -31,8 +31,15 @@ export class PaymentsTableComponent {
@Output() more = new EventEmitter<void>();
columns: Column2<StatPayment>[] = [
{ field: 'id', cell: (d) => ({ click: () => this.toDetails(d) }), sticky: 'start' },
{ field: 'invoice_id', sticky: 'start' },
{
field: 'id',
cell: (d) => ({
value: `${d.invoice_id}.${d.id}`,
click: () => this.toDetails(d),
}),
sticky: 'start',
},
{ field: 'external_id' },
createCurrencyColumn((d) => ({ amount: d.amount, code: d.currency_symbolic_code }), {
field: 'amount',
}),
@ -66,7 +73,6 @@ export class PaymentsTableComponent {
createDomainObjectColumn((d) => ({ ref: { provider: d.provider_id } }), {
header: 'Provider',
}),
{ field: 'external_id' },
createFailureColumn2((d) => ({
failure: d.status?.failed?.failure?.failure,
noFailureMessage:

View File

@ -6,8 +6,8 @@ import { TableModule, VSelectPipe, Column2 } from '@vality/ng-core';
import type { TermSetHistory, ShopTermSet } from '@vality/dominator-proto/internal/dominator';
import { SidenavInfoModule } from '../../../../shared/components/sidenav-info';
import { getDomainObjectDetails } from '../../../../shared/components/thrift-api-crud';
import { getInlineDecisions2 } from '../../utils/get-inline-decisions';
import { createDomainObjectColumn } from '../../../../shared/utils/table2';
import { getFlatDecisions } from '../../utils/get-flat-decisions';
import {
getShopCashFlowSelectors,
isShopTermSetDecision,
@ -26,7 +26,7 @@ export class ShopsTermSetHistoryCardComponent {
historyData = computed(() =>
(this.data()?.term_set_history?.reverse?.() || []).map((t) => ({
value: t,
children: getInlineDecisions2(getShopCashFlowSelectors(t.term_set)).filter((v) =>
children: getFlatDecisions(getShopCashFlowSelectors(t.term_set)).filter((v) =>
isShopTermSetDecision(v, {
partyId: this.data().owner_id,
shopId: this.data().shop_id,
@ -38,14 +38,9 @@ export class ShopsTermSetHistoryCardComponent {
columns: Column2<TermSetHistory>[] = [
{ field: 'applied_at', cell: { type: 'datetime' } },
{
field: 'term_set',
cell: (d) => ({
value: getDomainObjectDetails({ term_set_hierarchy: d?.term_set })?.label,
description: getDomainObjectDetails({ term_set_hierarchy: d?.term_set })
?.description,
}),
},
createDomainObjectColumn((d) => ({ ref: { term_set_hierarchy: d?.term_set?.ref } }), {
header: 'Term Set',
}),
...SHOP_FEES_COLUMNS,
];
}

View File

@ -40,7 +40,7 @@ import {
createContractColumn,
createDomainObjectColumn,
} from '../../../../shared/utils/table2';
import { getInlineDecisions2, InlineDecision2 } from '../../utils/get-inline-decisions';
import { getFlatDecisions, FlatDecision } from '../../utils/get-flat-decisions';
import { ShopsTermSetHistoryCardComponent } from '../shops-term-set-history-card';
import { ShopsTermsService } from './shops-terms.service';
@ -89,7 +89,7 @@ export class ShopsTermsComponent implements OnInit {
map((terms) =>
terms.map((t) => ({
value: t,
children: getInlineDecisions2(getShopCashFlowSelectors(t.current_term_set)).filter(
children: getFlatDecisions(getShopCashFlowSelectors(t.current_term_set)).filter(
(v) =>
isShopTermSetDecision(v, {
partyId: t.owner_id,
@ -102,7 +102,7 @@ export class ShopsTermsComponent implements OnInit {
);
hasMore$ = this.shopsTermsService.hasMore$;
isLoading$ = this.shopsTermsService.isLoading$;
columns: Column2<ShopTermSet, InlineDecision2>[] = [
columns: Column2<ShopTermSet, FlatDecision>[] = [
createShopColumn(
(d) => ({
shopId: d.shop_id,

View File

@ -7,9 +7,9 @@ import {
} 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 { formatCashVolumes } from '../../../../../shared';
import { createFeesColumns } from '../../../utils/create-fees-columns';
import { FlatDecision } from '../../../utils/get-flat-decisions';
import { isThatCurrency } from '../../../utils/is-that-currency';
export function getShopCashFlowSelectors(d: TermSetHierarchyObject) {
@ -32,7 +32,7 @@ export function isThatShopParty(predicate: Predicate, partyId: PartyID, shopId:
}
export function isShopTermSetDecision(
v: InlineDecision2,
v: FlatDecision,
params: { partyId: PartyID; shopId: ShopID; currency: string },
) {
return (
@ -42,39 +42,13 @@ export function isShopTermSetDecision(
);
}
const BASE_SHOP_FEES_COLUMNS = createFeesColumns({
feeFilter: isShopFee,
otherFilter: (v) => !isShopRreserve(v),
});
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,
}),
},
...BASE_SHOP_FEES_COLUMNS.slice(0, -1),
{
field: 'rreserve',
header: 'RReserve',
@ -82,19 +56,5 @@ export const SHOP_FEES_COLUMNS = [
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>[];
BASE_SHOP_FEES_COLUMNS.at(-1),
] satisfies Column2<object, FlatDecision>[];

View File

@ -1,25 +1,3 @@
<cc-card [title]="'Term Sets History'">
<v-table
[cellTemplate]="{
payments_condition: arrayColumnTemplate,
payments: arrayColumnTemplate,
wallets_condition: arrayColumnTemplate,
wallets: arrayColumnTemplate
}"
[columns]="columns"
[data]="data()"
></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"
>
{{ value?.length > 1 ? item || '-' : item }}
</div>
</ng-container>
</ng-template>
<v-table2 [columns]="columns" [treeData]="historyData()"></v-table2>
</cc-card>

View File

@ -1,12 +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, type Column, VSelectPipe } from '@vality/ng-core';
import { TableModule, VSelectPipe, Column2 } from '@vality/ng-core';
import type { ProvisionTermSetHistory } from '@vality/dominator-proto/internal/dominator';
import type {
TerminalTermSet,
ProvisionTermSetHistory,
} from '@vality/dominator-proto/internal/dominator';
import { SidenavInfoModule } from '../../../../shared/components/sidenav-info';
import { createTerminalFeesColumn } from '../terminals-terms/utils/create-terminal-fees-column';
import {
TERMINAL_FEES_COLUMNS,
getTerminalTreeDataItem,
} from '../terminals-terms/utils/terminal-fees-columns';
@Component({
selector: 'cc-shops-term-set-history-card',
@ -16,9 +22,15 @@ import { createTerminalFeesColumn } from '../terminals-terms/utils/create-termin
styles: ``,
})
export class TerminalsTermSetHistoryCardComponent {
data = input<ProvisionTermSetHistory[]>();
columns: Column<ProvisionTermSetHistory>[] = [
{ field: 'applied_at', type: 'datetime' },
...createTerminalFeesColumn<ProvisionTermSetHistory>((d) => d.term_set),
data = input<TerminalTermSet>();
historyData = computed(() =>
(this.data()?.term_set_history?.reverse?.() || []).map(
getTerminalTreeDataItem((d) => d.term_set),
),
);
columns: Column2<ProvisionTermSetHistory>[] = [
{ field: 'applied_at', cell: { type: 'datetime' } },
...TERMINAL_FEES_COLUMNS,
];
}

View File

@ -1,4 +1,4 @@
<cc-page-layout title="Terminals terms">
<cc-page-layout fullHeight title="Terminals terms">
<cc-page-layout-actions>
<v-more-filters-button [filters]="filters"></v-more-filters-button>
</cc-page-layout-actions>
@ -10,38 +10,12 @@
</ng-template>
</v-filters>
<v-table
[cellTemplate]="{
payments_condition: arrayColumnTemplate,
payments: arrayColumnTemplate,
wallets_condition: arrayColumnTemplate,
wallets: arrayColumnTemplate
}"
<v-table2
[columns]="columns"
[data]="terms$ | async"
[hasMore]="hasMore$ | async"
[progress]="isLoading$ | async"
[treeData]="terms$ | 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]"
[title]="item"
matTooltipPosition="right"
style="
white-space: nowrap;
cursor: default;
max-width: 50vw;
overflow: hidden;
text-overflow: ellipsis;
"
>
{{ value?.length > 1 ? item || '-' : item }}
</div>
</ng-container>
</ng-template>
></v-table2>
</cc-page-layout>

View File

@ -10,7 +10,7 @@ import {
} from '@vality/dominator-proto/internal/dominator';
import {
clean,
Column,
Column2,
countChanged,
createControls,
debounceTimeWithFirst,
@ -32,14 +32,14 @@ import type { ProviderRef, TerminalRef } from '@vality/dominator-proto/internal/
import { PageLayoutModule } from '@cc/app/shared';
import { CurrencyFieldComponent } from '@cc/app/shared/components/currency-field';
import { MerchantFieldModule } from '@cc/app/shared/components/merchant-field';
import { createDomainObjectColumn } from '@cc/app/shared/utils/table/create-domain-object-column';
import { createDomainObjectColumn } from '@cc/app/shared/utils/table2/create-domain-object-column';
import { DEBOUNCE_TIME_MS } from '@cc/app/tokens';
import { SidenavInfoService } from '../../../../shared/components/sidenav-info';
import { TerminalsTermSetHistoryCardComponent } from '../terminals-term-set-history-card';
import { TerminalsTermsService } from './terminals-terms.service';
import { createTerminalFeesColumn } from './utils/create-terminal-fees-column';
import { TERMINAL_FEES_COLUMNS, getTerminalTreeDataItem } from './utils/terminal-fees-columns';
type Params = Pick<CommonSearchQueryParams, 'currencies'> &
Overwrite<
@ -73,21 +73,28 @@ export class TerminalsTermsComponent implements OnInit {
terminal_ids: null,
}),
);
terms$ = this.terminalsTermsService.result$;
terms$ = this.terminalsTermsService.result$.pipe(
map((terms) => terms.map(getTerminalTreeDataItem((d) => d.current_term_set))),
);
hasMore$ = this.terminalsTermsService.hasMore$;
isLoading$ = this.terminalsTermsService.isLoading$;
columns: Column<TerminalTermSet>[] = [
createDomainObjectColumn<TerminalTermSet>('terminal', (d) => d.terminal_id),
createDomainObjectColumn<TerminalTermSet>('provider', (d) => d.provider_id),
{ field: 'currencies', formatter: (d) => d.currencies.join(', ') },
...createTerminalFeesColumn<TerminalTermSet>((d) => d.current_term_set),
columns: Column2<TerminalTermSet>[] = [
createDomainObjectColumn((d) => ({ ref: { terminal: d.terminal_id } }), {
header: 'Terminal',
sticky: 'start',
}),
createDomainObjectColumn((d) => ({ ref: { provider: d.provider_id } }), {
header: 'Provider',
}),
{ field: 'currencies', cell: (d) => ({ value: d.currencies.join(', ') }) },
...TERMINAL_FEES_COLUMNS,
{
field: 'term_set_history',
formatter: (d) => d.term_set_history?.length || '',
click: (d) =>
this.sidenavInfoService.open(TerminalsTermSetHistoryCardComponent, {
data: d?.term_set_history?.reverse(),
}),
cell: (d) => ({
value: d.term_set_history?.length || '',
click: () =>
this.sidenavInfoService.open(TerminalsTermSetHistoryCardComponent, { data: d }),
}),
},
];
active$ = getValueChanges(this.filtersForm).pipe(

View File

@ -1,53 +0,0 @@
import type { ProvisionTermSet } from '@vality/dominator-proto/internal/proto/domain';
import type { Column } from '@vality/ng-core';
import { getInlineDecisions, formatLevelPredicate } from '../../../utils/get-inline-decisions';
export function createTerminalFeesColumn<T extends object>(
fn: (d: T) => ProvisionTermSet = (d) => d as never,
): Column<T>[] {
return [
{
field: 'payments_condition',
formatter: (d) =>
getInlineDecisions(
[fn(d)?.payments?.cash_flow].filter(Boolean),
(d) =>
!(
d.source.provider === 0 &&
d.destination.merchant === 0 &&
d.volume?.share?.parts?.p === 1 &&
d.volume?.share?.parts?.q === 1
),
).map((v) => formatLevelPredicate(v)),
},
{
field: 'payments',
formatter: (d) =>
getInlineDecisions(
[fn(d)?.payments?.cash_flow].filter(Boolean),
(d) =>
!(
d.source.provider === 0 &&
d.destination.merchant === 0 &&
d.volume?.share?.parts?.p === 1 &&
d.volume?.share?.parts?.q === 1
),
).map((v) => v?.value),
},
{
field: 'wallets_condition',
formatter: (d) =>
getInlineDecisions([fn(d)?.wallet?.withdrawals?.cash_flow].filter(Boolean)).map(
(v) => formatLevelPredicate(v),
),
},
{
field: 'wallets',
formatter: (d) =>
getInlineDecisions([fn(d)?.wallet?.withdrawals?.cash_flow].filter(Boolean)).map(
(v) => v?.value,
),
},
];
}

View File

@ -0,0 +1,58 @@
import { ProvisionTermSet, CashFlowPosting } from '@vality/domain-proto/internal/domain';
import { Column2, TreeDataItem } from '@vality/ng-core';
import { createFeesColumns } from '../../../utils/create-fees-columns';
import { FlatDecision, getFlatDecisions } from '../../../utils/get-flat-decisions';
export interface TerminalChild {
payment: FlatDecision;
withdrawal: FlatDecision;
}
export function getTerminalPaymentsCashFlowSelectors(d: ProvisionTermSet) {
return [d?.payments?.cash_flow].filter(Boolean);
}
export function getTerminalWalletsCashFlowSelectors(d: ProvisionTermSet) {
return [d?.wallet?.withdrawals?.cash_flow].filter(Boolean);
}
export function isPaymentFee(v: CashFlowPosting) {
return v?.source?.system === 0 && v?.destination?.provider === 0;
}
export function isWithdrawalFee(v: CashFlowPosting) {
return v?.source?.system === 0 && v?.destination?.provider === 0;
}
export function getTerminalTreeDataItem<T extends object>(
selectTermSet: (d: T) => ProvisionTermSet,
) {
return (d: T): TreeDataItem<T, TerminalChild> => {
const termSet = selectTermSet(d);
const paymentsDecisions = getFlatDecisions(getTerminalPaymentsCashFlowSelectors(termSet));
const withdrawalsDecisions = getFlatDecisions(getTerminalWalletsCashFlowSelectors(termSet));
return {
value: d,
children: new Array(Math.max(paymentsDecisions.length, withdrawalsDecisions.length))
.fill(null)
.map((_, idx) => ({
payment: paymentsDecisions[idx],
withdrawal: withdrawalsDecisions[idx],
})),
};
};
}
export const TERMINAL_FEES_COLUMNS = [
...createFeesColumns<TerminalChild>({
conditionLabel: 'Payment Condition',
feeFilter: isPaymentFee,
selectFlatDecision: (d) => d.payment,
}),
...createFeesColumns<TerminalChild>({
conditionLabel: 'Withdrawal Condition',
feeFilter: isWithdrawalFee,
selectFlatDecision: (d) => d.withdrawal,
}),
] satisfies Column2<object, TerminalChild>[];

View File

@ -6,8 +6,8 @@ import { TableModule, VSelectPipe, Column2 } from '@vality/ng-core';
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 { getInlineDecisions2 } from '../../utils/get-inline-decisions';
import { createDomainObjectColumn } from '../../../../shared/utils/table2';
import { getFlatDecisions } from '../../utils/get-flat-decisions';
import {
WALLET_FEES_COLUMNS,
isWalletTermSetDecision,
@ -26,7 +26,7 @@ export class WalletsTermSetHistoryCardComponent {
historyData = computed(() =>
(this.data()?.term_set_history?.reverse?.() || []).map((t) => ({
value: t,
children: getInlineDecisions2(getWalletCashFlowSelectors(t.term_set)).filter((v) =>
children: getFlatDecisions(getWalletCashFlowSelectors(t.term_set)).filter((v) =>
isWalletTermSetDecision(v, {
partyId: this.data().owner_id,
walletId: this.data().wallet_id,
@ -38,14 +38,9 @@ export class WalletsTermSetHistoryCardComponent {
columns: Column2<TermSetHistory>[] = [
{ field: 'applied_at', cell: { type: 'datetime' } },
{
field: 'term_set',
cell: (d) => ({
value: getDomainObjectDetails({ term_set_hierarchy: d?.term_set })?.label,
description: getDomainObjectDetails({ term_set_hierarchy: d?.term_set })
?.description,
}),
},
createDomainObjectColumn((d) => ({ ref: { term_set_hierarchy: d?.term_set?.ref } }), {
header: 'Term Set',
}),
...WALLET_FEES_COLUMNS,
];
}

View File

@ -4,13 +4,11 @@ import {
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 { createFeesColumns } from '../../../utils/create-fees-columns';
import { FlatDecision } from '../../../utils/get-flat-decisions';
import { isThatCurrency } from '../../../utils/is-that-currency';
export function getWalletCashFlowSelectors(d: TermSetHierarchyObject) {
@ -33,7 +31,7 @@ export function isThatWalletParty(predicate: Predicate, partyId: PartyID, wallet
}
export function isWalletTermSetDecision(
v: InlineDecision2,
v: FlatDecision,
params: { partyId: PartyID; walletId: WalletID; currency: string },
) {
return (
@ -43,47 +41,6 @@ export function isWalletTermSetDecision(
);
}
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>[];
export const WALLET_FEES_COLUMNS = createFeesColumns({
feeFilter: isWalletFee,
});

View File

@ -42,7 +42,7 @@ import {
} from '@cc/app/shared/utils/table2';
import { DEBOUNCE_TIME_MS } from '@cc/app/tokens';
import { InlineDecision2, getInlineDecisions2 } from '../../utils/get-inline-decisions';
import { FlatDecision, getFlatDecisions } from '../../utils/get-flat-decisions';
import { WalletsTermSetHistoryCardComponent } from '../wallets-term-set-history-card';
import {
@ -92,21 +92,20 @@ export class WalletsTermsComponent implements OnInit {
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,
}),
children: getFlatDecisions(getWalletCashFlowSelectors(t.current_term_set)).filter(
(v) =>
isWalletTermSetDecision(v, {
partyId: t.owner_id,
walletId: t.wallet_id,
currency: t.currency,
}),
),
})),
),
);
hasMore$ = this.walletsTermsService.hasMore$;
isLoading$ = this.walletsTermsService.isLoading$;
columns: Column2<WalletTermSet, InlineDecision2>[] = [
columns: Column2<WalletTermSet, FlatDecision>[] = [
createWalletColumn((d) => ({ id: d.wallet_id, name: d.wallet_name, partyId: d.owner_id }), {
sticky: 'start',
}),

View File

@ -0,0 +1,85 @@
import { CashFlowPosting } from '@vality/domain-proto/internal/domain';
import { Column2 } from '@vality/ng-core';
import { getCashVolumeParts, formatCashVolumes } from '../../../shared';
import {
formatLevelPredicate,
formatCashFlowSourceDestination,
FlatDecision,
} from './get-flat-decisions';
import { isOneHundredPercentCashFlowPosting } from './is-one-hundred-percent-cash-flow-posting';
export function createFeesColumns<T extends object>({
selectFlatDecision = (d) => d as never,
conditionLabel = 'Condition',
feeFilter = () => true,
otherFilter = () => true,
}: {
selectFlatDecision?: (d: T) => FlatDecision;
conditionLabel?: string;
feeFilter?: (v: CashFlowPosting) => boolean;
otherFilter?: (v: CashFlowPosting) => boolean;
} = {}): Column2<object, T>[] {
function getFeeCashVolumeParts(d: T) {
const decision = selectFlatDecision(d);
return decision
? getCashVolumeParts(decision.value.filter(feeFilter).map((v) => v.volume))
: null;
}
return [
{
field: conditionLabel,
header: conditionLabel,
child: (d) => ({
value: selectFlatDecision(d) ? formatLevelPredicate(selectFlatDecision(d)) : null,
}),
},
{
field: `${conditionLabel}_feeShare`,
header: 'Fee, %',
child: (d) => ({
value: getFeeCashVolumeParts(d)?.share,
}),
},
{
field: `${conditionLabel}_feeFixed`,
header: 'Fee, fix',
child: (d) => ({
value: getFeeCashVolumeParts(d)?.fixed,
}),
},
{
field: `${conditionLabel}_feeMin`,
header: 'Fee, min',
child: (d) => ({
value: getFeeCashVolumeParts(d)?.max,
}),
},
{
field: `${conditionLabel}_feeMax`,
header: 'Fee, max',
child: (d) => ({
value: getFeeCashVolumeParts(d)?.min,
}),
},
{
field: `${conditionLabel}_other`,
header: 'Other',
child: (d) => {
const decision = selectFlatDecision(d);
if (!decision) {
return null;
}
const cashFlow = decision.value.filter(
(v) =>
otherFilter(v) && !feeFilter(v) && !isOneHundredPercentCashFlowPosting(v),
);
return {
value: formatCashVolumes(cashFlow.map((v) => v.volume)),
tooltip: formatCashFlowSourceDestination(cashFlow),
};
},
},
];
}

View File

@ -0,0 +1,101 @@
import { CashFlow } from '@vality/domain-proto/internal/domain';
import { getUnionKey } from '@vality/ng-thrift';
import type {
CashFlowSelector,
CashFlowAccount,
Predicate,
} from '@vality/dominator-proto/internal/proto/domain';
import { formatPredicate, compareCashVolumes } from '@cc/app/shared';
// 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 formatLevelPredicate(v: { level: number; if?: Predicate }) {
return `${'\xa0'.repeat(Math.max(v.level - 1, 0))}${v.level > 0 ? '↳' : ''} ${formatPredicate(
v.if,
)}`;
}
export function formatCashFlowSourceDestination(value: CashFlow): string {
return value
.sort((a, b) => compareCashVolumes(a.volume, b.volume))
.map(
(v) =>
`${formatCashFlowAccount(v.source)}${formatCashFlowAccount(v.destination)}` +
(v.details ? ` (${v.details})` : ''),
)
.join(', ');
}
export interface FlatDecision {
value: CashFlow;
level: number;
if?: Predicate;
}
export function getFlatDecisions(d: CashFlowSelector[], level = 0) {
return d.reduce<FlatDecision[]>((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 thenFlatDecisions = getFlatDecisions([d.then_], level + 1);
if (d.if_) {
const ifFlatDecision = {
if: d.if_,
level,
};
return thenFlatDecisions.length > 1
? [{ ...ifFlatDecision, value: [] }, ...thenFlatDecisions]
: [{ ...thenFlatDecisions[0], ...ifFlatDecision }];
}
return thenFlatDecisions;
}),
);
}
return acc;
}, []);
}

View File

@ -1,156 +0,0 @@
import { CashFlow } from '@vality/domain-proto/internal/domain';
import { getUnionKey } from '@vality/ng-thrift';
import type {
CashFlowPosting,
CashFlowSelector,
CashFlowAccount,
Predicate,
} from '@vality/dominator-proto/internal/proto/domain';
import {
formatPredicate,
formatCashVolumes,
compareCashVolumes,
getCashVolumeParts,
CashVolumeParts,
} from '@cc/app/shared';
export interface InlineCashFlowSelector {
if?: Predicate;
value?: string;
parts?: CashVolumeParts;
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 formatLevelPredicate(v: { level: number; if?: Predicate }) {
return `${'\xa0'.repeat(Math.max(v.level - 1, 0))}${v.level > 0 ? '↳' : ''} ${formatPredicate(
v.if,
)}`;
}
export function getInlineDecisions(
d: CashFlowSelector[],
filterValue: (v: CashFlowPosting) => boolean = Boolean,
level = 0,
): InlineCashFlowSelector[] {
return d.reduce((acc, c) => {
if (c.value) {
const value = c.value.filter(filterValue);
acc.push({
value: formatCashVolumes(value.map((v) => v.volume)),
parts: getCashVolumeParts(value.map((v) => v.volume)),
level,
description: value
.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: d.if_,
level,
};
return thenInlineDecisions.length > 1
? [ifInlineDecision, ...thenInlineDecisions]
: [{ ...thenInlineDecisions[0], ...ifInlineDecision }];
}
return thenInlineDecisions;
})
.flat(),
);
}
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;
}, []);
}