diff --git a/cspell.json b/cspell.json index 6253e8b1..3d80e0bd 100644 --- a/cspell.json +++ b/cspell.json @@ -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"] + "words": ["submain", "papaparse", "msgpack", "termsets"] } diff --git a/package-lock.json b/package-lock.json index f1ee6c5d..6c440345 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@vality/fistful-proto": "2.0.1-6600be9.0", "@vality/machinegun-proto": "1.0.0", "@vality/magista-proto": "2.0.2-28d11b9.0", - "@vality/ng-core": "17.2.1-pr-62-e7ae3c9.0", + "@vality/ng-core": "17.2.1-pr-62-bd071a2.0", "@vality/ng-thrift": "17.0.1-pr-5-2ce0f11.0", "@vality/payout-manager-proto": "2.0.1-eb4091a.0", "@vality/repairer-proto": "2.0.2-07b73e9.0", @@ -6462,9 +6462,9 @@ "integrity": "sha512-BsDy5ejotfTtUlwuoX3kz+PYJ5NSTW6m5ZRGv+p5HaKXSjR7tserPdv0q133Wp4T+sg0ED0Qr9Peqsrn+9XlDQ==" }, "node_modules/@vality/ng-core": { - "version": "17.2.1-pr-62-e7ae3c9.0", - "resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-17.2.1-pr-62-e7ae3c9.0.tgz", - "integrity": "sha512-7x3TctfL6SAwmucbWo78i8Qk/ytC9qn4R2PXLxux1/6aHOaI+JB02Cv9zdTfXrKLQiPbzF/rAW/lbC8PtXVduQ==", + "version": "17.2.1-pr-62-bd071a2.0", + "resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-17.2.1-pr-62-bd071a2.0.tgz", + "integrity": "sha512-/zLUC/ogUZbIQEuciYDibCBMA3USixuPtm2aWtp7E+ppNJtpd56Wu4py/BTCrDUMjlt0kDog8aqqin1snqGscA==", "dependencies": { "@angular/material-date-fns-adapter": "^17.2.0", "@ng-matero/extensions": "^17.1.0", diff --git a/package.json b/package.json index 4c4ff11c..9673e564 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@vality/fistful-proto": "2.0.1-6600be9.0", "@vality/machinegun-proto": "1.0.0", "@vality/magista-proto": "2.0.2-28d11b9.0", - "@vality/ng-core": "17.2.1-pr-62-e7ae3c9.0", + "@vality/ng-core": "17.2.1-pr-62-bd071a2.0", "@vality/ng-thrift": "17.0.1-pr-5-2ce0f11.0", "@vality/payout-manager-proto": "2.0.1-eb4091a.0", "@vality/repairer-proto": "2.0.2-07b73e9.0", diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 2f158b6e..c044910b 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,4 +1,4 @@ -import { Component, isDevMode } from '@angular/core'; +import { Component } from '@angular/core'; import { Link } from '@vality/ng-core'; import { KeycloakService } from 'keycloak-angular'; import sortBy from 'lodash-es/sortBy'; @@ -81,15 +81,11 @@ export class AppComponent { url: '/sources', services: SOURCES_ROUTING_CONFIG.services, }, - ...(isDevMode() - ? [ - { - label: 'Tariffs', - url: '/tariffs', - services: TARIFFS_ROUTING_CONFIG.services, - }, - ] - : []), + { + label: 'Tariffs', + url: '/tariffs', + services: TARIFFS_ROUTING_CONFIG.services, + }, ], [ { diff --git a/src/app/sections/tariffs/components/cash-flows-selector-table/cash-flows-selector-table.component.html b/src/app/sections/tariffs/components/cash-flows-selector-table/cash-flows-selector-table.component.html new file mode 100644 index 00000000..80b02b6c --- /dev/null +++ b/src/app/sections/tariffs/components/cash-flows-selector-table/cash-flows-selector-table.component.html @@ -0,0 +1 @@ + diff --git a/src/app/sections/tariffs/components/cash-flows-selector-table/cash-flows-selector-table.component.ts b/src/app/sections/tariffs/components/cash-flows-selector-table/cash-flows-selector-table.component.ts new file mode 100644 index 00000000..df1cd33f --- /dev/null +++ b/src/app/sections/tariffs/components/cash-flows-selector-table/cash-flows-selector-table.component.ts @@ -0,0 +1,38 @@ +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(); + + columns: Column[] = [ + { + 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(' + '), + }, + ]; +} diff --git a/src/app/sections/tariffs/components/shops-tariffs/shops-tariffs.component.html b/src/app/sections/tariffs/components/shops-tariffs/shops-tariffs.component.html index 70fc6dda..058b7a8d 100644 --- a/src/app/sections/tariffs/components/shops-tariffs/shops-tariffs.component.html +++ b/src/app/sections/tariffs/components/shops-tariffs/shops-tariffs.component.html @@ -17,6 +17,10 @@ + + + {{ rowData?.length }} +
+ {{ item }} +
+
diff --git a/src/app/sections/tariffs/components/shops-tariffs/shops-tariffs.component.ts b/src/app/sections/tariffs/components/shops-tariffs/shops-tariffs.component.ts index 18a17458..ed152b60 100644 --- a/src/app/sections/tariffs/components/shops-tariffs/shops-tariffs.component.ts +++ b/src/app/sections/tariffs/components/shops-tariffs/shops-tariffs.component.ts @@ -2,7 +2,11 @@ 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 } from '@vality/domain-proto/internal/domain'; +import { + TermSetHierarchyRef, + type CashFlowSelector, + type CashFlowPosting, +} from '@vality/domain-proto/internal/domain'; import { CommonSearchQueryParams, ShopSearchQuery, @@ -23,7 +27,12 @@ import { TableModule, UpdateOptions, } from '@vality/ng-core'; +import { getUnionKey } from '@vality/ng-thrift'; import { map, shareReplay } from 'rxjs/operators'; +import { + DomainObjectCardComponent, + getDomainObjectDetails, +} from 'src/app/shared/components/thrift-api-crud'; import { Overwrite } from 'utility-types'; import { @@ -32,9 +41,12 @@ 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'; +import { SidenavInfoService } from '@cc/app/shared/components/sidenav-info'; import { DEBOUNCE_TIME_MS } from '@cc/app/tokens'; import { ShopsTariffsService } from './shops-tariffs.service'; @@ -45,6 +57,66 @@ type Params = Pick & { term_sets_ids?: TermSetHierarchyRef['id'][] } >; +function getViewedCashFlowSelectors(d: ShopTermSet) { + return ( + d.current_term_set.data.term_sets + ?.map?.((t) => t?.terms?.payments?.fees) + ?.filter?.(Boolean) ?? [] + ); +} + +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, @@ -76,7 +148,7 @@ export class ShopsTariffsComponent implements OnInit { hasMore$ = this.shopsTariffsService.hasMore$; isLoading$ = this.shopsTariffsService.isLoading$; columns: Column[] = [ - createShopColumn('shop_id', (d) => d.owner_id), + createShopColumn('shop_id', (d) => d.owner_id, undefined, { pinned: 'left' }), createPartyColumn('owner_id'), createContractColumn( (d) => d.contract_id, @@ -86,9 +158,20 @@ export class ShopsTariffsComponent implements OnInit { { field: 'currency' }, { field: 'current_term_set', - formatter: (d) => d.current_term_set?.data?.name, - description: (d) => d.current_term_set?.data?.description, - tooltip: (d) => d.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: 'decision', + formatter: (d) => getInlineDecisions(getViewedCashFlowSelectors(d)).map((v) => v.if), + }, + { + field: 'value', + formatter: (d) => getInlineDecisions(getViewedCashFlowSelectors(d)).map((v) => v.value), }, { field: 'term_set_history', @@ -109,6 +192,7 @@ export class ShopsTariffsComponent implements OnInit { private qp: QueryParamsService, @Inject(DEBOUNCE_TIME_MS) private debounceTimeMs: number, private dr: DestroyRef, + private sidenavInfoService: SidenavInfoService, ) {} ngOnInit() { diff --git a/src/app/sections/tariffs/components/termsets-card/termsets-card.component.html b/src/app/sections/tariffs/components/termsets-card/termsets-card.component.html new file mode 100644 index 00000000..6837d9fc --- /dev/null +++ b/src/app/sections/tariffs/components/termsets-card/termsets-card.component.html @@ -0,0 +1,3 @@ + + + diff --git a/src/app/sections/tariffs/components/termsets-card/termsets-card.component.ts b/src/app/sections/tariffs/components/termsets-card/termsets-card.component.ts new file mode 100644 index 00000000..c7488e9a --- /dev/null +++ b/src/app/sections/tariffs/components/termsets-card/termsets-card.component.ts @@ -0,0 +1,23 @@ +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(); + feesData = computed( + () => + this.data() + ?.data?.term_sets?.map?.((t) => t?.terms?.payments?.fees) + ?.filter?.(Boolean), + ); +} diff --git a/src/app/shared/utils/table/format-cash-volume.ts b/src/app/shared/utils/table/format-cash-volume.ts new file mode 100644 index 00000000..bb10ff5e --- /dev/null +++ b/src/app/shared/utils/table/format-cash-volume.ts @@ -0,0 +1,22 @@ +import { CashVolume } from '@vality/domain-proto/internal/domain'; +import { formatCurrency } from '@vality/ng-core'; +import { getUnionKey, getUnionValue } from '@vality/ng-thrift'; + +import { formatRational } from './format-rational'; + +export function formatCashVolume(d: CashVolume) { + switch (getUnionKey(d)) { + case 'fixed': + return formatCurrency(d?.fixed?.cash?.amount, d?.fixed?.cash?.currency?.symbolic_code); + case 'share': + return ( + formatRational(d?.share?.parts) + + (d?.share?.of === 2 ? ' of surplus' : '') + + (d?.share?.rounding_method === 1 ? ' (round .5+)' : '') + ); + case 'product': + return `${getUnionKey(d.product).slice(0, -3)}(${Array.from(getUnionValue(d.product)) + .map((c) => formatCashVolume(c)) + .join(', ')})`; + } +} diff --git a/src/app/shared/utils/table/format-predicate.ts b/src/app/shared/utils/table/format-predicate.ts index 436b76d6..06db0044 100644 --- a/src/app/shared/utils/table/format-predicate.ts +++ b/src/app/shared/utils/table/format-predicate.ts @@ -1,5 +1,6 @@ import { Predicate } from '@vality/domain-proto/domain'; -import { getUnionKey, getUnionValue } from '@vality/ng-thrift'; +import { formatCurrency, inlineJson } from '@vality/ng-core'; +import { getUnionKey, getUnionValue, toJson } from '@vality/ng-thrift'; import startCase from 'lodash-es/startCase'; export function formatPredicate(predicate: Predicate, level = 0) { @@ -18,24 +19,57 @@ export function formatPredicate(predicate: Predicate, level = 0) { if (predicatesSet.size <= 1) { return formatPredicate(predicatesSet.keys().next().value, level + 1); } - return `${startCase(getUnionKey(predicate))} ${ - predicatesSet.size <= 3 - ? `(${Array.from(predicatesSet) - .map((p) => formatPredicate(p, level + 1)) - .join(', ')})` - : predicatesSet.size - }`; + const res = Array.from(predicatesSet) + .map((p) => formatPredicate(p, level + 1)) + .join(type === 'all_of' ? ' & ' : ' OR '); + return level === 0 ? `(${res})` : res; + } + case 'condition': { + const condition = predicate.condition; + switch (getUnionKey(condition)) { + case 'currency_is': + return `currency: ${condition.currency_is.symbolic_code}`; + case 'bin_data': + return `bin_data: ${inlineJson(toJson(condition.bin_data), Infinity)}`; + case 'category_is': + return `category: #${condition.category_is.id}`; + case 'cost_in': + return `cost: ${ + getUnionKey(condition.cost_in.upper) === 'inclusive' ? '[' : '(' + }${formatCurrency( + getUnionValue(condition.cost_in.upper)?.amount, + getUnionValue(condition.cost_in.upper)?.currency?.symbolic_code, + )}, ${formatCurrency( + getUnionValue(condition.cost_in.lower)?.amount, + getUnionValue(condition.cost_in.lower)?.currency?.symbolic_code, + )}${getUnionKey(condition.cost_in.lower) === 'inclusive' ? ']' : ')'}`; + case 'cost_is_multiple_of': + return `cost_is_multiple: ${formatCurrency( + condition.cost_is_multiple_of.amount, + condition.cost_is_multiple_of.currency.symbolic_code, + )}`; + case 'identification_level_is': + return `identification_level: ${condition.identification_level_is}`; // TODO: fix enum value + case 'p2p_tool': + return `p2p_tool: ${inlineJson(toJson(condition.p2p_tool), Infinity)}`; + case 'party': + return `party: ${inlineJson(toJson(condition.party), Infinity)}`; + case 'payment_tool': + return `payment_tool: ${inlineJson(toJson(condition.payment_tool), Infinity)}`; + case 'payout_method_is': + return `payout_method: #${condition.payout_method_is.id}`; // TODO: fix enum value + case 'shop_location_is': + return `shop_url: ${condition.shop_location_is.url}`; + } + return ''; } - case 'condition': - return startCase(getUnionKey(getUnionValue(predicate) as Predicate['condition'])); case 'criterion': return `${startCase(getUnionKey(predicate))} #${predicate.criterion.id}`; case 'is_not': { if (getUnionKey(getUnionValue(predicate) as Predicate) !== 'is_not') { - return `${getUnionKey(predicate)} ${formatPredicate(predicate.is_not, level + 1)}`; + return `NOT ${formatPredicate(predicate.is_not, level + 1)}`; } - break; + return ''; } } - return startCase(getUnionKey(predicate)); } diff --git a/src/app/shared/utils/table/format-rational.ts b/src/app/shared/utils/table/format-rational.ts new file mode 100644 index 00000000..aca373ba --- /dev/null +++ b/src/app/shared/utils/table/format-rational.ts @@ -0,0 +1,6 @@ +import { Rational } from '@vality/domain-proto/internal/base'; +import round from 'lodash-es/round'; + +export function formatRational(value: Rational) { + return `${round((value.p / value.q) * 100, 4)}%`; +} diff --git a/src/app/shared/utils/table/index.ts b/src/app/shared/utils/table/index.ts index fde0d0b1..a196e171 100644 --- a/src/app/shared/utils/table/index.ts +++ b/src/app/shared/utils/table/index.ts @@ -4,3 +4,6 @@ export * from './create-shop-column'; export * from './create-predicate-column'; export * from './create-failure-column'; export * from './create-contract-column'; +export * from './format-cash-volume'; +export * from './format-rational'; +export * from './format-predicate';