IMP-57: Wallet balance (#211)

This commit is contained in:
Rinat Arsaev 2023-04-10 14:54:33 +04:00 committed by GitHub
parent fafe19bdfd
commit 77708832d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 140 additions and 28 deletions

14
package-lock.json generated
View File

@ -30,7 +30,7 @@
"@s-libs/ng-core": "15.0.0",
"@s-libs/rxjs-core": "15.0.0",
"@vality/deanonimus-proto": "2.0.1-2a3d5ad.0",
"@vality/domain-proto": "2.0.1-698c7d2.0",
"@vality/domain-proto": "2.0.1-e12c03c.0",
"@vality/dominant-cache-proto": "2.0.1-99f38c9.0",
"@vality/fistful-proto": "2.0.1-4ff4ea3.0",
"@vality/magista-proto": "2.0.1-cf0eff8.0",
@ -5202,9 +5202,9 @@
"integrity": "sha512-p/kR6o1mTWatvzpltDbSzJjRiA75ph8N5CZm5FTa+5ZgGer9pSN4sZhRGJj/iIfZkXZuVNZp9kisS+4+QKx13A=="
},
"node_modules/@vality/domain-proto": {
"version": "2.0.1-698c7d2.0",
"resolved": "https://registry.npmjs.org/@vality/domain-proto/-/domain-proto-2.0.1-698c7d2.0.tgz",
"integrity": "sha512-tt9AU+haIySRsP8H3dz54XXp6eZSbuR0ME3lRZfFOZbsx6NadkHEJt4eVtEFYxc5V76gBw4uxtxJDfUz/UDOrA=="
"version": "2.0.1-e12c03c.0",
"resolved": "https://registry.npmjs.org/@vality/domain-proto/-/domain-proto-2.0.1-e12c03c.0.tgz",
"integrity": "sha512-xWUtBeHMRkHIfNZP85XJSvVmBu+T5YXAELvnk6TKhpoRorseYKvcmIUA9Rsz4preQrUclZHngSjd23FSGRZn8Q=="
},
"node_modules/@vality/dominant-cache-proto": {
"version": "2.0.1-99f38c9.0",
@ -22956,9 +22956,9 @@
"integrity": "sha512-p/kR6o1mTWatvzpltDbSzJjRiA75ph8N5CZm5FTa+5ZgGer9pSN4sZhRGJj/iIfZkXZuVNZp9kisS+4+QKx13A=="
},
"@vality/domain-proto": {
"version": "2.0.1-698c7d2.0",
"resolved": "https://registry.npmjs.org/@vality/domain-proto/-/domain-proto-2.0.1-698c7d2.0.tgz",
"integrity": "sha512-tt9AU+haIySRsP8H3dz54XXp6eZSbuR0ME3lRZfFOZbsx6NadkHEJt4eVtEFYxc5V76gBw4uxtxJDfUz/UDOrA=="
"version": "2.0.1-e12c03c.0",
"resolved": "https://registry.npmjs.org/@vality/domain-proto/-/domain-proto-2.0.1-e12c03c.0.tgz",
"integrity": "sha512-xWUtBeHMRkHIfNZP85XJSvVmBu+T5YXAELvnk6TKhpoRorseYKvcmIUA9Rsz4preQrUclZHngSjd23FSGRZn8Q=="
},
"@vality/dominant-cache-proto": {
"version": "2.0.1-99f38c9.0",

View File

@ -44,7 +44,7 @@
"@s-libs/ng-core": "15.0.0",
"@s-libs/rxjs-core": "15.0.0",
"@vality/deanonimus-proto": "2.0.1-2a3d5ad.0",
"@vality/domain-proto": "2.0.1-698c7d2.0",
"@vality/domain-proto": "2.0.1-e12c03c.0",
"@vality/dominant-cache-proto": "2.0.1-99f38c9.0",
"@vality/fistful-proto": "2.0.1-4ff4ea3.0",
"@vality/magista-proto": "2.0.1-cf0eff8.0",

View File

@ -0,0 +1,43 @@
import { Injectable } from '@angular/core';
import {
accounter_AccounterCodegenClient,
ThriftAstMetadata,
accounter_Accounter,
} from '@vality/domain-proto';
import { Account } from '@vality/domain-proto/internal/accounter';
import { AccountID } from '@vality/domain-proto/internal/domain';
import { combineLatest, from, map, Observable, switchMap } from 'rxjs';
import { KeycloakTokenInfoService, toWachterHeaders } from '@cc/app/shared/services';
import { environment } from '@cc/environments/environment';
@Injectable({ providedIn: 'root' })
export class AccounterService {
private client$: Observable<accounter_AccounterCodegenClient>;
constructor(private keycloakTokenInfoService: KeycloakTokenInfoService) {
const headers$ = this.keycloakTokenInfoService.decoded$.pipe(
map(toWachterHeaders('Accounter'))
);
const metadata$ = from(
import('@vality/domain-proto/metadata.json').then(
(m) => m.default as ThriftAstMetadata[]
)
);
this.client$ = combineLatest([metadata$, headers$]).pipe(
switchMap(([metadata, headers]) =>
accounter_Accounter({
metadata,
headers,
logging: environment.logging.requests,
path: '/wachter',
})
)
);
}
// eslint-disable-next-line @typescript-eslint/naming-convention
GetAccountByID(id: AccountID): Observable<Account> {
return this.client$.pipe(switchMap((c) => c.GetAccountByID(id)));
}
}

View File

@ -0,0 +1 @@
export * from './accounter.service';

View File

@ -5,4 +5,5 @@ export interface WalletParams extends PagedBaseParameters {
party_id?: string;
identity_id?: string;
currency_code?: string;
wallet_id?: string[];
}

View File

@ -73,9 +73,9 @@ export class RepairingComponent implements OnInit {
createDatetimeFormattedColumn('created_at'),
createDescriptionFormattedColumn<Machine>(
'provider',
(data) => data.provider_id,
(data) =>
providers.find((p) => String(p.ref.id) === data.provider_id)?.data?.name,
(data) => data.provider_id
providers.find((p) => String(p.ref.id) === data.provider_id)?.data?.name
),
createTooltipTemplateGridColumn(
{

View File

@ -2,7 +2,7 @@
<h1 class="cc-display-1">Wallets</h1>
<mat-card [formGroup]="filters">
<mat-card-content gdColumns="1fr 1fr 1fr" gdGap="16px">
<mat-card-content gdColumns="1fr 1fr 1fr 1fr" gdGap="16px">
<cc-merchant-field formControlName="party_id"></cc-merchant-field>
<mat-form-field>
<mat-label>Identity ID</mat-label>
@ -12,10 +12,16 @@
<mat-label>Currency Code</mat-label>
<input formControlName="currency_code" matInput />
</mat-form-field>
<mat-form-field>
<mat-label>Wallet IDs</mat-label>
<input formControlName="wallet_id" matInput />
<mat-hint>id0,id1</mat-hint>
</mat-form-field>
</mat-card-content>
</mat-card>
<cc-simple-table
[cellTemplate]="{ balance: balanceTpl }"
[columns]="columns"
[data]="wallets$ | async"
[hasMore]="hasMore$ | async"
@ -23,5 +29,26 @@
(fetchMore)="fetchMore()"
(size)="search($event)"
(update)="search($event.size)"
></cc-simple-table>
>
<ng-template #balanceTpl let-col="colDef" let-index="index" let-row>
<ng-container *ngIf="{ visible: false } as balance">
<button
*ngIf="!balance.visible; else balanceTpl"
class="cell-button"
mat-icon-button
(click)="balance.visible = true"
>
<mat-icon>sync</mat-icon>
</button>
<ng-template #balanceTpl>
<ng-container *ngIf="getBalance(row.id) | async as account; else spinner">
{{ account.own_amount | amountCurrency : account.currency_sym_code }}
</ng-container>
<ng-template #spinner>
<button [loading]="true" class="cell-button" mat-icon-button></button>
</ng-template>
</ng-template>
</ng-container>
</ng-template>
</cc-simple-table>
</div>

View File

@ -0,0 +1,8 @@
:host {
display: block;
padding: 24px 16px;
}
.cell-button {
margin: -4px 0;
}

View File

@ -1,11 +1,17 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { untilDestroyed, UntilDestroy } from '@ngneat/until-destroy';
import { clean } from '@vality/ng-core';
import { startWith, map } from 'rxjs/operators';
import { StatWallet } from '@vality/fistful-proto/internal/fistful_stat';
import { clean, splitIds } from '@vality/ng-core';
import { of } from 'rxjs';
import { startWith, map, shareReplay, switchMap, catchError } from 'rxjs/operators';
import { Memoize } from 'typescript-memoize';
import { AccounterService } from '@cc/app/api/accounter';
import { WalletParams } from '@cc/app/api/fistful-stat/query-dsl/types/wallet';
import { ManagementService } from '@cc/app/api/wallet';
import { QueryParamsService } from '@cc/app/shared/services';
import { NotificationErrorService } from '@cc/app/shared/services/notification-error';
import {
createDatetimeFormattedColumn,
createDescriptionFormattedColumn,
@ -18,57 +24,71 @@ import { FetchWalletsService } from './fetch-wallets.service';
@Component({
selector: 'cc-wallets',
templateUrl: './wallets.component.html',
styles: [
`
:host {
display: block;
padding: 24px 16px;
}
`,
],
providers: [FetchWalletsService],
styleUrls: ['wallets.component.scss'],
})
export class WalletsComponent implements OnInit {
wallets$ = this.fetchWalletsService.searchResult$;
inProgress$ = this.fetchWalletsService.doAction$;
hasMore$ = this.fetchWalletsService.hasMore$;
columns = createGridColumns([
columns = createGridColumns<StatWallet>([
createDescriptionFormattedColumn('name', 'id'),
'currency_symbolic_code',
'identity_id',
createDatetimeFormattedColumn('created_at'),
'balance',
]);
filters = this.fb.group<WalletParams>({
party_id: null,
identity_id: null,
currency_code: null,
wallet_id: null,
...this.qp.params,
});
test$ = this.getBalance('294');
constructor(
private fetchWalletsService: FetchWalletsService,
private qp: QueryParamsService<WalletParams>,
private fb: FormBuilder
private fb: FormBuilder,
private walletManagementService: ManagementService,
private accounterService: AccounterService,
private errorService: NotificationErrorService
) {}
ngOnInit() {
this.filters.valueChanges
.pipe(
startWith(this.filters.value),
map((v) => ({ ...v, wallet_id: splitIds(v.wallet_id) })),
map((v) => clean(v)),
untilDestroyed(this)
)
.subscribe((value) => {
void this.qp.set(value);
this.fetchWalletsService.search(value);
this.search();
});
}
search(size: number) {
this.fetchWalletsService.search(clean(this.filters.value), size);
search(size?: number) {
const { wallet_id, ...v } = this.filters.value;
this.fetchWalletsService.search(clean({ ...v, wallet_id: splitIds(wallet_id) }), size);
}
fetchMore() {
this.fetchWalletsService.fetchMore();
}
@Memoize()
getBalance(walletId: string) {
return this.walletManagementService.Get(walletId, {}).pipe(
switchMap((wallet) => this.accounterService.GetAccountByID(Number(wallet.account.id))),
catchError((err) => {
this.errorService.error(err);
return of({});
}),
shareReplay({ refCount: true, bufferSize: 1 })
);
}
}

View File

@ -2,11 +2,15 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FlexModule, GridModule } from '@angular/flex-layout';
import { ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatTableModule } from '@angular/material/table';
import { MtxButtonModule } from '@ng-matero/extensions/button';
import { AmountCurrencyPipe } from '@cc/app/shared';
import { MerchantFieldModule } from '@cc/app/shared/components/merchant-field';
import { MetadataFormModule } from '@cc/app/shared/components/metadata-form';
import { EmptySearchResultModule } from '@cc/components/empty-search-result';
@ -32,6 +36,10 @@ import { WalletsComponent } from './wallets.component';
MerchantFieldModule,
GridModule,
SimpleTableModule,
MatButtonModule,
MatIconModule,
MtxButtonModule,
AmountCurrencyPipe,
],
declarations: [WalletsComponent],
})

View File

@ -1,6 +1,7 @@
import { formatCurrency, getCurrencySymbol } from '@angular/common';
import { Pipe, Inject, LOCALE_ID, DEFAULT_CURRENCY_CODE, PipeTransform } from '@angular/core';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import isNil from 'lodash-es/isNil';
import { ReplaySubject, combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';
@ -34,6 +35,8 @@ export class AmountCurrencyPipe implements PipeTransform {
combineLatest([this.domainStoreService.getObjects('currency'), this.params$])
.pipe(
map(([currencies, { amount, currencyCode, format }]) => {
if (isNil(amount)) return '?';
if (!currencyCode) return String(amount);
const exponent = currencies.find((c) => c.data.symbolic_code === currencyCode)
.data.exponent;
return formatCurrency(

View File

@ -13,7 +13,8 @@ export type GridColumn<T> =
> & {
_data?: any;
})
| keyof T;
| keyof T
| string;
export function createGridColumn<T>(col: GridColumn<T>) {
if (!isObject(col))