IMP-231: Add shops tariffs page (only dev) (#359)

This commit is contained in:
Rinat Arsaev 2024-05-20 19:19:11 +05:00 committed by GitHub
parent 4e3e09e257
commit 1650516ab6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 408 additions and 52 deletions

8
package-lock.json generated
View File

@ -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-61-d842667.0",
"@vality/ng-core": "17.2.1-pr-62-e7ae3c9.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-61-d842667.0",
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-17.2.1-pr-61-d842667.0.tgz",
"integrity": "sha512-QquwzzrFUHfc9vixSsX1ZkjQ+lkYbtEgT0tlqp1BnJ2N/kSR9eWZSV3N488j0ZPnDoJmktnwmUjDituQN855QA==",
"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==",
"dependencies": {
"@angular/material-date-fns-adapter": "^17.2.0",
"@ng-matero/extensions": "^17.1.0",

View File

@ -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-61-d842667.0",
"@vality/ng-core": "17.2.1-pr-62-e7ae3c9.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",

View File

@ -24,7 +24,7 @@ export class DominatorService {
map(toWachterHeaders('Dominator')),
);
const metadata$ = from(
import('@vality/fistful-proto/metadata.json').then(
import('@vality/dominator-proto/metadata.json').then(
(m) => m.default as ThriftAstMetadata[],
),
);

View File

@ -1,4 +1,4 @@
import { Component } from '@angular/core';
import { Component, isDevMode } from '@angular/core';
import { Link } from '@vality/ng-core';
import { KeycloakService } from 'keycloak-angular';
import sortBy from 'lodash-es/sortBy';
@ -18,6 +18,7 @@ import { ROUTING_CONFIG as REPAIRING_ROUTING_CONFIG } from './sections/repairing
import { ROUTING_CONFIG as PARTIES_ROUTING_CONFIG } from './sections/search-parties/routing-config';
import { SHOPS_ROUTING_CONFIG } from './sections/shops';
import { ROUTING_CONFIG as SOURCES_ROUTING_CONFIG } from './sections/sources/routing-config';
import { ROUTING_CONFIG as TARIFFS_ROUTING_CONFIG } from './sections/tariffs/routing-config';
import { ROUTING_CONFIG as TERMINALS_ROUTING_CONFIG } from './sections/terminals';
import { ROUTING_CONFIG as WALLETS_ROUTING_CONFIG } from './sections/wallets/routing-config';
import { ROUTING_CONFIG as WITHDRAWALS_ROUTING_CONFIG } from './sections/withdrawals/routing-config';
@ -80,6 +81,15 @@ export class AppComponent {
url: '/sources',
services: SOURCES_ROUTING_CONFIG.services,
},
...(isDevMode()
? [
{
label: 'Tariffs',
url: '/tariffs',
services: TARIFFS_ROUTING_CONFIG.services,
},
]
: []),
],
[
{
@ -103,41 +113,40 @@ export class AppComponent {
services: CLAIMS_ROUTING_CONFIG.services,
},
],
sortBy(
[
{
label: 'Payments',
url: '/payments',
services: PAYMENTS_ROUTING_CONFIG.services,
},
{
label: 'Payouts',
url: '/payouts',
services: PAYOUTS_ROUTING_CONFIG.services,
},
{
label: 'Chargebacks',
url: '/chargebacks',
services: WALLETS_ROUTING_CONFIG.services,
},
{
label: 'Deposits',
url: '/deposits',
services: DEPOSITS_ROUTING_CONFIG.services,
},
{
label: 'Withdrawals',
url: '/withdrawals',
services: WITHDRAWALS_ROUTING_CONFIG.services,
},
],
'label',
),
[
{
label: 'Payments',
url: '/payments',
services: PAYMENTS_ROUTING_CONFIG.services,
},
{
label: 'Payouts',
url: '/payouts',
services: PAYOUTS_ROUTING_CONFIG.services,
},
{
label: 'Chargebacks',
url: '/chargebacks',
services: WALLETS_ROUTING_CONFIG.services,
},
{
label: 'Deposits',
url: '/deposits',
services: DEPOSITS_ROUTING_CONFIG.services,
},
{
label: 'Withdrawals',
url: '/withdrawals',
services: WITHDRAWALS_ROUTING_CONFIG.services,
},
],
];
return menuItems.map((group) =>
group.filter((item) =>
this.appAuthGuardService.userHasSomeServiceMethods(item.services),
),
);
return menuItems
.map((group) =>
group.filter((item) =>
this.appAuthGuardService.userHasSomeServiceMethods(item.services),
),
)
.map((group) => sortBy(group, 'label'));
}
}

View File

@ -64,6 +64,10 @@ const ROUTES: Routes = [
path: 'shops',
loadChildren: () => import('./shops').then((m) => m.ShopsModule),
},
{
path: 'tariffs',
loadChildren: () => import('./tariffs').then((m) => m.TariffsModule),
},
{
path: '404',
loadChildren: () => import('./not-found').then((m) => m.NotFoundModule),

View File

@ -0,0 +1,27 @@
<cc-page-layout title="Shops tariffs">
<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-shop-field
[partyId]="filtersForm.value.party_id"
formControlName="shop_ids"
multiple
></cc-shop-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
[columns]="columns"
[data]="tariffs$ | async"
[hasMore]="hasMore$ | async"
[progress]="isLoading$ | async"
(more)="more()"
(update)="update($event)"
></v-table>
</cc-page-layout>

View File

@ -0,0 +1,143 @@
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 {
CommonSearchQueryParams,
ShopSearchQuery,
ShopTermSet,
} from '@vality/dominator-proto/internal/dominator';
import {
clean,
Column,
countChanged,
createControls,
debounceTimeWithFirst,
FiltersModule,
getValueChanges,
InputFieldModule,
ListFieldModule,
LoadOptions,
QueryParamsService,
TableModule,
UpdateOptions,
} from '@vality/ng-core';
import { map, shareReplay } from 'rxjs/operators';
import { Overwrite } from 'utility-types';
import {
createContractColumn,
createPartyColumn,
createShopColumn,
PageLayoutModule,
ShopFieldModule,
} from '@cc/app/shared';
import { CurrencyFieldComponent } from '@cc/app/shared/components/currency-field';
import { MerchantFieldModule } from '@cc/app/shared/components/merchant-field';
import { DEBOUNCE_TIME_MS } from '@cc/app/tokens';
import { ShopsTariffsService } from './shops-tariffs.service';
type Params = Pick<CommonSearchQueryParams, 'currencies'> &
Overwrite<
Omit<ShopSearchQuery, 'common_search_query_params'>,
{ term_sets_ids?: TermSetHierarchyRef['id'][] }
>;
@Component({
selector: 'cc-shops-tariffs',
standalone: true,
imports: [
CommonModule,
PageLayoutModule,
TableModule,
InputFieldModule,
FiltersModule,
ReactiveFormsModule,
MerchantFieldModule,
ShopFieldModule,
ListFieldModule,
CurrencyFieldComponent,
],
templateUrl: './shops-tariffs.component.html',
})
export class ShopsTariffsComponent implements OnInit {
filtersForm = this.fb.group(
createControls<Params>({
currencies: null,
party_id: null,
shop_ids: null,
term_sets_names: null,
term_sets_ids: null,
}),
);
tariffs$ = this.shopsTariffsService.result$;
hasMore$ = this.shopsTariffsService.hasMore$;
isLoading$ = this.shopsTariffsService.isLoading$;
columns: Column<ShopTermSet>[] = [
createShopColumn<ShopTermSet>('shop_id', (d) => d.owner_id),
createPartyColumn<ShopTermSet>('owner_id'),
createContractColumn<ShopTermSet>(
(d) => d.contract_id,
(d) => d.owner_id,
(d) => d.shop_id,
),
{ 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,
},
{
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 shopsTariffsService: ShopsTariffsService,
private fb: NonNullableFormBuilder,
private qp: QueryParamsService<Params>,
@Inject(DEBOUNCE_TIME_MS) private debounceTimeMs: number,
private dr: DestroyRef,
) {}
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, ...otherParams } = params;
this.shopsTariffsService.load(
clean({
common_search_query_params: { currencies },
term_sets_ids: term_sets_ids?.map((id) => ({ id })),
...otherParams,
}),
options,
);
}
update(options?: UpdateOptions) {
this.shopsTariffsService.reload(options);
}
more() {
this.shopsTariffsService.more();
}
}

View File

@ -0,0 +1,43 @@
import { Injectable } from '@angular/core';
import { ShopSearchQuery, ShopTermSet } 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 ShopsTariffsService extends FetchSuperclass<ShopTermSet, ShopSearchQuery> {
constructor(
private dominatorService: DominatorService,
private log: NotifyLogService,
) {
super();
}
protected fetch(params: ShopSearchQuery, options: FetchOptions<string>) {
return this.dominatorService
.SearchShopTermSets({
...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),
);
}
}

View File

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

View File

@ -0,0 +1,5 @@
import { Services, RoutingConfig } from '@cc/app/shared/services';
export const ROUTING_CONFIG: RoutingConfig = {
services: [Services.Dominator],
};

View File

@ -0,0 +1,34 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { AppAuthGuardService } from '../../shared/services';
import { ShopsTariffsComponent } from './components/shops-tariffs/shops-tariffs.component';
import { ROUTING_CONFIG } from './routing-config';
import { TariffsComponent } from './tariffs.component';
@NgModule({
imports: [
RouterModule.forChild([
{
path: '',
component: TariffsComponent,
canActivate: [AppAuthGuardService],
data: ROUTING_CONFIG,
children: [
{
path: 'shops',
component: ShopsTariffsComponent,
},
{
path: '',
redirectTo: 'shops',
pathMatch: 'full',
},
],
},
]),
],
exports: [RouterModule],
})
export class TariffsRoutingModule {}

View File

@ -0,0 +1,15 @@
<mat-sidenav-container autosize>
<mat-sidenav-content style="overflow: unset"
><router-outlet></router-outlet
></mat-sidenav-content>
<mat-sidenav
[fixedTopGap]="64"
[opened]="!(sidenavInfoService.opened$ | async)"
fixedInViewport="true"
mode="side"
position="end"
style="background: transparent; border: none; padding: 24px 0 24px 0"
>
<v-nav [links]="links" type="secondary"></v-nav
></mat-sidenav>
</mat-sidenav-container>

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatSidenavModule } from '@angular/material/sidenav';
import { RouterOutlet } from '@angular/router';
import { Link, NavComponent } from '@vality/ng-core';
import { SidenavInfoService } from '@cc/app/shared/components/sidenav-info';
@Component({
selector: 'cc-tariffs',
standalone: true,
imports: [CommonModule, RouterOutlet, MatSidenavModule, NavComponent],
templateUrl: './tariffs.component.html',
})
export class TariffsComponent {
links: Link[] = [
{
label: 'Shops',
url: 'shops',
},
];
constructor(public sidenavInfoService: SidenavInfoService) {}
}

View File

@ -0,0 +1,10 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ShopsTariffsComponent } from './components/shops-tariffs/shops-tariffs.component';
import { TariffsRoutingModule } from './tariffs-routing.module';
@NgModule({
imports: [CommonModule, TariffsRoutingModule, ShopsTariffsComponent],
})
export class TariffsModule {}

View File

@ -8,4 +8,5 @@ export enum Services {
Invoicing = 'Invoicing',
RepairManagement = 'RepairManagement',
PayoutManagement = 'PayoutManagement',
Dominator = 'Dominator',
}

View File

@ -0,0 +1,35 @@
import { inject } from '@angular/core';
import { ContractID, PartyID, ShopID } from '@vality/domain-proto/domain';
import { Column, PossiblyAsync, getPossiblyAsyncObservable } from '@vality/ng-core';
import { combineLatest } from 'rxjs';
import { take } from 'rxjs/operators';
import { ShopContractCardComponent } from '../../components/shop-contract-card/shop-contract-card.component';
import { SidenavInfoService } from '../../components/sidenav-info';
export function createContractColumn<T extends object>(
selectContractId: (d: T) => PossiblyAsync<ContractID>,
selectPartyId: (d: T) => PossiblyAsync<PartyID>,
selectShopId: (d: T) => PossiblyAsync<ShopID>,
): Column<T> {
const sidenavInfoService = inject(SidenavInfoService);
return {
field: 'contract',
header: 'Contract',
formatter: selectContractId,
click: (d) => {
combineLatest([
getPossiblyAsyncObservable(selectPartyId(d)),
getPossiblyAsyncObservable(selectShopId(d)),
])
.pipe(take(1))
.subscribe(([partyId, id]) => {
sidenavInfoService.toggle(ShopContractCardComponent, {
partyId,
id,
});
});
},
};
}

View File

@ -2,9 +2,11 @@ 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 { map, switchMap, take } from 'rxjs/operators';
import { PartiesStoreService } from '../../../api/payment-processing';
import { ShopCardComponent } from '../../components/shop-card/shop-card.component';
import { SidenavInfoService } from '../../components/sidenav-info';
export function createShopColumn<T extends object>(
field: ColumnObject<T>['field'],
@ -16,7 +18,7 @@ export function createShopColumn<T extends object>(
selectShopId = (d) => get(d, field);
}
const partiesStoreService = inject(PartiesStoreService);
// const sidenavInfoService = inject(SidenavInfoService);
const sidenavInfoService = inject(SidenavInfoService);
return {
field,
header: 'Shop',
@ -31,14 +33,16 @@ export function createShopColumn<T extends object>(
),
map(([party, shopId]) => party.shops.get(shopId).details.name),
),
// TODO
// click: (d) => {
// combineLatest([selectPartyId(d), selectShopId(d)])
// .pipe(take(1))
// .subscribe(([partyId, id]) => {
// sidenavInfoService.toggle(ShopCardComponent, { id, partyId });
// });
// },
click: (d) => {
combineLatest([
getPossiblyAsyncObservable(selectPartyId(d)),
getPossiblyAsyncObservable(selectShopId(d)),
])
.pipe(take(1))
.subscribe(([partyId, id]) => {
sidenavInfoService.toggle(ShopCardComponent, { id, partyId });
});
},
...params,
} as ColumnObject<T>;
}

View File

@ -3,3 +3,4 @@ export * from './create-party-column';
export * from './create-shop-column';
export * from './create-predicate-column';
export * from './create-failure-column';
export * from './create-contract-column';