mirror of
https://github.com/valitydev/control-center.git
synced 2024-11-06 02:25:17 +00:00
IMP-196: New payment page (#345)
This commit is contained in:
parent
002f47d3f8
commit
ed8cb4601d
8
package-lock.json
generated
8
package-lock.json
generated
@ -25,7 +25,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-57-6aa62d9.0",
|
||||
"@vality/ng-core": "17.2.1-pr-57-1a93ecb.0",
|
||||
"@vality/payout-manager-proto": "2.0.1-eb4091a.0",
|
||||
"@vality/repairer-proto": "2.0.2-07b73e9.0",
|
||||
"@vality/thrift-ts": "2.4.1-8ad5123.0",
|
||||
@ -6454,9 +6454,9 @@
|
||||
"integrity": "sha512-BsDy5ejotfTtUlwuoX3kz+PYJ5NSTW6m5ZRGv+p5HaKXSjR7tserPdv0q133Wp4T+sg0ED0Qr9Peqsrn+9XlDQ=="
|
||||
},
|
||||
"node_modules/@vality/ng-core": {
|
||||
"version": "17.2.1-pr-57-6aa62d9.0",
|
||||
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-17.2.1-pr-57-6aa62d9.0.tgz",
|
||||
"integrity": "sha512-WlPp5EdDRL/tvDOuAsMtbd5xXL8hXWdRdcSLEjHn6SS4+Hnwodki6PzThjk/4T8drj/EUbaZSoTyL2J7lbaciw==",
|
||||
"version": "17.2.1-pr-57-1a93ecb.0",
|
||||
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-17.2.1-pr-57-1a93ecb.0.tgz",
|
||||
"integrity": "sha512-X3PXwqZu6Wej0ETv7mqI6VIZrLJ72GnTszkpbmJkEo+MZSdgzFfgyqMglNz8QKZ0ORclszjlG41J4jtBKspvVQ==",
|
||||
"dependencies": {
|
||||
"@angular/material-date-fns-adapter": "^17.2.0",
|
||||
"@ng-matero/extensions": "^17.1.0",
|
||||
|
@ -33,7 +33,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-57-6aa62d9.0",
|
||||
"@vality/ng-core": "17.2.1-pr-57-1a93ecb.0",
|
||||
"@vality/payout-manager-proto": "2.0.1-eb4091a.0",
|
||||
"@vality/repairer-proto": "2.0.2-07b73e9.0",
|
||||
"@vality/thrift-ts": "2.4.1-8ad5123.0",
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { PartyID } from '@vality/domain-proto/domain';
|
||||
import { ShopID, WalletID } from '@vality/domain-proto/internal/domain';
|
||||
import { progressTo } from '@vality/ng-core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { shareReplay } from 'rxjs/operators';
|
||||
import { BehaviorSubject, combineLatest } from 'rxjs';
|
||||
import { shareReplay, map } from 'rxjs/operators';
|
||||
import { MemoizeExpiring } from 'typescript-memoize';
|
||||
|
||||
import { PartyManagementService } from '@cc/app/api/payment-processing';
|
||||
@ -21,4 +22,39 @@ export class PartiesStoreService {
|
||||
.Get(partyId)
|
||||
.pipe(progressTo(this.progress$), shareReplay({ refCount: true, bufferSize: 1 }));
|
||||
}
|
||||
|
||||
@MemoizeExpiring(5 * 60_000)
|
||||
getShop(shopId: ShopID, partyId: PartyID) {
|
||||
return this.get(partyId).pipe(
|
||||
map((p) => p.shops.get(shopId)),
|
||||
progressTo(this.progress$),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
}
|
||||
|
||||
@MemoizeExpiring(5 * 60_000)
|
||||
getWallet(walletId: WalletID, partyId: PartyID) {
|
||||
return this.get(partyId).pipe(
|
||||
map((p) => p.wallets.get(walletId)),
|
||||
progressTo(this.progress$),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
}
|
||||
|
||||
@MemoizeExpiring(5 * 60_000)
|
||||
getContractor(shopId: ShopID, partyId: PartyID) {
|
||||
return combineLatest([this.get(partyId), this.getShop(shopId, partyId)]).pipe(
|
||||
map(([party, shop]) => {
|
||||
const contractorId = party.contracts.get(shop.contract_id)?.contractor_id;
|
||||
return party.contractors.get(contractorId)?.contractor;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@MemoizeExpiring(5 * 60_000)
|
||||
getContract(shopId: ShopID, partyId: PartyID) {
|
||||
return combineLatest([this.get(partyId), this.getShop(shopId, partyId)]).pipe(
|
||||
map(([party, shop]) => party.contracts.get(shop.contract_id)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,19 @@
|
||||
<cc-page-layout
|
||||
[progress]="isLoading$ | async"
|
||||
description="{{ (payment$ | async)?.id }}"
|
||||
[tags]="tags$ | async"
|
||||
id="{{
|
||||
(payment$ | async) ? (payment$ | async)?.invoice_id + '.' + (payment$ | async)?.id : ''
|
||||
}}"
|
||||
title="Payment"
|
||||
>
|
||||
<ng-container *ngIf="payment$ | async as payment">
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<cc-payment-main-info
|
||||
[payment]="payment"
|
||||
[shop]="shop$ | async"
|
||||
></cc-payment-main-info>
|
||||
<!-- <cc-json-viewer-->
|
||||
<!-- [metadata]="metadata$ | async"-->
|
||||
<!-- [value]="payment"-->
|
||||
<!-- namespace="magista"-->
|
||||
<!-- type="StatPayment"-->
|
||||
<!-- ></cc-json-viewer>-->
|
||||
<cc-magista-thrift-viewer
|
||||
[extensions]="extensions$ | async"
|
||||
[value]="payment"
|
||||
type="StatPayment"
|
||||
></cc-magista-thrift-viewer>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
|
@ -1,13 +1,27 @@
|
||||
import { ChangeDetectionStrategy, Component, DestroyRef } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, DestroyRef, Inject, LOCALE_ID } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ThriftAstMetadata } from '@vality/domain-proto';
|
||||
import { DialogService, DialogResponseStatus } from '@vality/ng-core';
|
||||
import { Subject, merge, defer, from } from 'rxjs';
|
||||
import { InvoicePaymentStatus, InvoicePaymentFlow } from '@vality/domain-proto/internal/domain';
|
||||
import {
|
||||
DialogService,
|
||||
DialogResponseStatus,
|
||||
getImportValue,
|
||||
formatCurrency,
|
||||
Color,
|
||||
} from '@vality/ng-core';
|
||||
import startCase from 'lodash-es/startCase';
|
||||
import { Subject, merge, defer, Observable, of } from 'rxjs';
|
||||
import { shareReplay, switchMap, map } from 'rxjs/operators';
|
||||
|
||||
import { InvoicingService } from '@cc/app/api/payment-processing';
|
||||
|
||||
import { getUnionKey, getUnionValue } from '../../../utils';
|
||||
import { MetadataViewExtension } from '../../shared/components/json-viewer';
|
||||
import { isTypeWithAliases } from '../../shared/components/metadata-form';
|
||||
import { DomainMetadataViewExtensionsService } from '../../shared/components/thrift-api-crud/domain/domain-thrift-viewer/services/domain-metadata-view-extensions';
|
||||
import { AmountCurrencyService } from '../../shared/services';
|
||||
|
||||
import { CreateChargebackDialogComponent } from './create-chargeback-dialog/create-chargeback-dialog.component';
|
||||
import { PaymentDetailsService } from './payment-details.service';
|
||||
|
||||
@ -19,7 +33,6 @@ import { PaymentDetailsService } from './payment-details.service';
|
||||
export class PaymentDetailsComponent {
|
||||
payment$ = this.paymentDetailsService.payment$;
|
||||
isLoading$ = this.paymentDetailsService.isLoading$;
|
||||
shop$ = this.paymentDetailsService.shop$;
|
||||
|
||||
chargebacks$ = merge(
|
||||
this.route.params,
|
||||
@ -32,8 +45,80 @@ export class PaymentDetailsComponent {
|
||||
map(({ chargebacks }) => chargebacks),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
metadata$ = from(
|
||||
import('@vality/magista-proto/metadata.json').then((m) => m.default as ThriftAstMetadata[]),
|
||||
metadata$ = getImportValue<ThriftAstMetadata[]>(import('@vality/magista-proto/metadata.json'));
|
||||
extensions$: Observable<MetadataViewExtension[]> = this.payment$.pipe(
|
||||
map((payment): MetadataViewExtension[] => [
|
||||
this.domainMetadataViewExtensionsService.createShopExtension(payment.owner_id),
|
||||
{
|
||||
determinant: (d) => of(isTypeWithAliases(d, 'Amount', 'domain')),
|
||||
extension: (_, amount: number) =>
|
||||
this.amountCurrencyService.getCurrency(payment.currency_symbolic_code).pipe(
|
||||
map((c) => ({
|
||||
value: formatCurrency(
|
||||
amount,
|
||||
c.data.symbolic_code,
|
||||
'long',
|
||||
this._locale,
|
||||
c.data.exponent,
|
||||
),
|
||||
})),
|
||||
),
|
||||
},
|
||||
{
|
||||
determinant: (d) =>
|
||||
of(
|
||||
isTypeWithAliases(d, 'InvoicePaymentStatus', 'domain') ||
|
||||
isTypeWithAliases(d, 'InvoicePaymentFlow', 'magista'),
|
||||
),
|
||||
extension: (_, v) => of({ hidden: !Object.keys(getUnionValue(v)).length }),
|
||||
},
|
||||
{
|
||||
determinant: (d) =>
|
||||
of(
|
||||
isTypeWithAliases(d?.trueParent, 'StatPayment', 'magista') &&
|
||||
(d.field.name === 'make_recurrent' ||
|
||||
d.field.name === 'currency_symbolic_code' ||
|
||||
isTypeWithAliases(d, 'InvoiceID', 'domain') ||
|
||||
isTypeWithAliases(d, 'InvoicePaymentID', 'domain')),
|
||||
),
|
||||
extension: () => of({ hidden: true }),
|
||||
},
|
||||
]),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
tags$ = this.payment$.pipe(
|
||||
map((payment) => [
|
||||
{
|
||||
value: startCase(getUnionKey(payment.status)),
|
||||
color: (
|
||||
{
|
||||
captured: 'success',
|
||||
refunded: 'success',
|
||||
charged_back: 'success',
|
||||
pending: 'pending',
|
||||
processed: 'pending',
|
||||
cancelled: 'warn',
|
||||
failed: 'warn',
|
||||
} as Record<keyof InvoicePaymentStatus, Color>
|
||||
)[getUnionKey(payment.status)],
|
||||
},
|
||||
{
|
||||
value: startCase(getUnionKey(payment.flow)),
|
||||
color: (
|
||||
{
|
||||
instant: 'success',
|
||||
hold: 'pending',
|
||||
} as Record<keyof InvoicePaymentFlow, Color>
|
||||
)[getUnionKey(payment.flow)],
|
||||
},
|
||||
{
|
||||
value: payment.make_recurrent ? 'Recurrent' : 'Not Recurrent',
|
||||
color: payment.make_recurrent ? 'success' : 'neutral',
|
||||
},
|
||||
{
|
||||
value: payment.currency_symbolic_code,
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
private updateChargebacks$ = new Subject<void>();
|
||||
@ -44,6 +129,9 @@ export class PaymentDetailsComponent {
|
||||
private invoicingService: InvoicingService,
|
||||
private dialogService: DialogService,
|
||||
private destroyRef: DestroyRef,
|
||||
private domainMetadataViewExtensionsService: DomainMetadataViewExtensionsService,
|
||||
private amountCurrencyService: AmountCurrencyService,
|
||||
@Inject(LOCALE_ID) private _locale: string,
|
||||
) {}
|
||||
|
||||
createChargeback() {
|
||||
|
@ -14,13 +14,12 @@ import { HeadlineModule } from '@cc/components/headline';
|
||||
import { ChargebacksComponent } from '../../shared/components/chargebacks/chargebacks.component';
|
||||
import { JsonViewerModule } from '../../shared/components/json-viewer';
|
||||
import { MetadataFormModule } from '../../shared/components/metadata-form';
|
||||
import { MagistaThriftViewerComponent } from '../../shared/components/thrift-api-crud';
|
||||
import { ThriftViewerModule } from '../../shared/components/thrift-viewer';
|
||||
|
||||
import { CreateChargebackDialogComponent } from './create-chargeback-dialog/create-chargeback-dialog.component';
|
||||
import { PaymentDetailsRoutingModule } from './payment-details-routing.module';
|
||||
import { PaymentDetailsComponent } from './payment-details.component';
|
||||
import { PaymentMainInfoModule } from './payment-main-info';
|
||||
import { PaymentToolModule } from './payment-main-info/payment-tool';
|
||||
import { RefundsTableModule } from './refunds-table';
|
||||
|
||||
@NgModule({
|
||||
@ -31,9 +30,7 @@ import { RefundsTableModule } from './refunds-table';
|
||||
MatCardModule,
|
||||
DetailsItemModule,
|
||||
StatusModule,
|
||||
PaymentToolModule,
|
||||
MatProgressSpinnerModule,
|
||||
PaymentMainInfoModule,
|
||||
MatButtonModule,
|
||||
MatDialogModule,
|
||||
ChargebacksComponent,
|
||||
@ -45,6 +42,7 @@ import { RefundsTableModule } from './refunds-table';
|
||||
ThriftViewerModule,
|
||||
JsonViewerModule,
|
||||
RefundsTableModule,
|
||||
MagistaThriftViewerComponent,
|
||||
],
|
||||
declarations: [PaymentDetailsComponent, CreateChargebackDialogComponent],
|
||||
})
|
||||
|
@ -1,22 +1,14 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { cleanPrimitiveProps } from '@vality/ng-core';
|
||||
import { combineLatest, of } from 'rxjs';
|
||||
import { map, pluck, shareReplay, switchMap, tap } from 'rxjs/operators';
|
||||
import { cleanPrimitiveProps, NotifyLogService, progressTo, inProgressFrom } from '@vality/ng-core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { map, shareReplay, switchMap, tap } from 'rxjs/operators';
|
||||
|
||||
import { MerchantStatisticsService } from '@cc/app/api/magista';
|
||||
import { PartyManagementService } from '@cc/app/api/payment-processing';
|
||||
import { progress } from '@cc/app/shared/custom-operators';
|
||||
|
||||
@Injectable()
|
||||
export class PaymentDetailsService {
|
||||
private partyID$ = this.route.params.pipe(pluck('partyID'), shareReplay(1));
|
||||
|
||||
private routeParams$ = this.route.params.pipe(shareReplay(1));
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/member-ordering
|
||||
payment$ = this.routeParams$.pipe(
|
||||
payment$ = this.route.params.pipe(
|
||||
switchMap(({ partyID, invoiceID, paymentID }) =>
|
||||
this.merchantStatisticsService
|
||||
.SearchPayments(
|
||||
@ -36,28 +28,21 @@ export class PaymentDetailsService {
|
||||
map(({ payments }) => payments[0]),
|
||||
tap((payment) => {
|
||||
if (!payment) {
|
||||
this.snackBar.open('An error occurred when receiving payment', 'OK');
|
||||
this.log.error('Payment not found');
|
||||
}
|
||||
}),
|
||||
progressTo(this.progress$),
|
||||
),
|
||||
),
|
||||
shareReplay(1),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
isLoading$ = inProgressFrom(() => this.progress$, this.payment$);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/member-ordering
|
||||
isLoading$ = progress(this.routeParams$, this.payment$).pipe(shareReplay(1));
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/member-ordering
|
||||
shop$ = this.payment$.pipe(
|
||||
switchMap((payment) => combineLatest([this.partyID$, of(payment.shop_id)])),
|
||||
switchMap(([partyID, shopID]) => this.partyManagementService.GetShop(partyID, shopID)),
|
||||
shareReplay(1),
|
||||
);
|
||||
private progress$ = new BehaviorSubject(0);
|
||||
|
||||
constructor(
|
||||
private partyManagementService: PartyManagementService,
|
||||
private merchantStatisticsService: MerchantStatisticsService,
|
||||
private route: ActivatedRoute,
|
||||
private snackBar: MatSnackBar,
|
||||
private log: NotifyLogService,
|
||||
) {}
|
||||
}
|
||||
|
@ -1,2 +0,0 @@
|
||||
export * from './payment-main-info.module';
|
||||
export * from './payment-main-info.component';
|
@ -1,47 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ContractID, PartyID, Party } from '@vality/domain-proto/domain';
|
||||
import { forkJoin, merge, of, Subject } from 'rxjs';
|
||||
import { catchError, filter, map, shareReplay, startWith, switchMap } from 'rxjs/operators';
|
||||
|
||||
import { PartyManagementService } from '@cc/app/api/payment-processing';
|
||||
import { progress } from '@cc/app/shared/custom-operators';
|
||||
|
||||
@Injectable()
|
||||
export class FetchContractorService {
|
||||
private getContractor$ = new Subject<{ partyID: PartyID; contractID: ContractID }>();
|
||||
private hasError$ = new Subject<void>();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/member-ordering
|
||||
contractor$ = this.getContractor$.pipe(
|
||||
switchMap(({ partyID, contractID }) =>
|
||||
forkJoin([
|
||||
of(contractID),
|
||||
this.partyManagementService.Get(partyID).pipe(
|
||||
catchError(() => {
|
||||
this.hasError$.next();
|
||||
return of('error');
|
||||
}),
|
||||
filter((result) => result !== 'error'),
|
||||
),
|
||||
]),
|
||||
),
|
||||
map(([contractID, party]: [ContractID, Party]) => {
|
||||
const contractorID = party.contracts.get(contractID)?.contractor_id;
|
||||
return party.contractors.get(contractorID)?.contractor;
|
||||
}),
|
||||
shareReplay(1),
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/member-ordering
|
||||
inProgress$ = progress(this.getContractor$, merge(this.contractor$, this.hasError$)).pipe(
|
||||
startWith(true),
|
||||
);
|
||||
|
||||
constructor(private partyManagementService: PartyManagementService) {
|
||||
this.contractor$.subscribe();
|
||||
}
|
||||
|
||||
getContractor(params: { partyID: PartyID; contractID: ContractID }) {
|
||||
this.getContractor$.next(params);
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from './payment-contractor.module';
|
@ -1,15 +0,0 @@
|
||||
<div *ngIf="contractor$ | async as contractor; else empty" style="display: flex; gap: 16px">
|
||||
<cc-details-item style="flex: 1" title="INN">{{ getINN(contractor) }}</cc-details-item>
|
||||
<cc-details-item style="flex: 1" title="Registered Name">{{
|
||||
getRegName(contractor)
|
||||
}}</cc-details-item>
|
||||
<div style="flex: 1"></div>
|
||||
</div>
|
||||
<ng-template #empty>
|
||||
<div *ngIf="inProgress$ | async; else emptyResult" style="display: flex">
|
||||
<cc-details-item style="flex: 1">Loading...</cc-details-item>
|
||||
</div>
|
||||
<ng-template #emptyResult>
|
||||
<div>Error contractor loading.</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
@ -1,41 +0,0 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { ContractID, Contractor, PartyID } from '@vality/domain-proto/domain';
|
||||
|
||||
import { FetchContractorService } from './fetch-contractor.service';
|
||||
|
||||
@Component({
|
||||
selector: 'cc-payment-contractor',
|
||||
templateUrl: 'payment-contractor.component.html',
|
||||
providers: [FetchContractorService],
|
||||
})
|
||||
export class PaymentContractorComponent implements OnInit {
|
||||
@Input()
|
||||
contractID: ContractID;
|
||||
|
||||
@Input()
|
||||
partyID: PartyID;
|
||||
|
||||
contractor$ = this.fetchContractorService.contractor$;
|
||||
inProgress$ = this.fetchContractorService.inProgress$;
|
||||
|
||||
constructor(private fetchContractorService: FetchContractorService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.fetchContractorService.getContractor({
|
||||
partyID: this.partyID,
|
||||
contractID: this.contractID,
|
||||
});
|
||||
}
|
||||
|
||||
getINN(contractor: Contractor) {
|
||||
return contractor.legal_entity?.russian_legal_entity?.inn || '-';
|
||||
}
|
||||
|
||||
getRegName(contractor: Contractor) {
|
||||
return (
|
||||
contractor.legal_entity?.russian_legal_entity?.registered_name ||
|
||||
contractor.legal_entity?.international_legal_entity?.trading_name ||
|
||||
'-'
|
||||
);
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { DetailsItemModule } from '@cc/components/details-item';
|
||||
|
||||
import { PaymentContractorComponent } from './payment-contractor.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [PaymentContractorComponent],
|
||||
imports: [DetailsItemModule, CommonModule],
|
||||
exports: [PaymentContractorComponent],
|
||||
})
|
||||
export class PaymentContractorModule {}
|
@ -1 +0,0 @@
|
||||
export * from './payment-error.module';
|
@ -1,15 +0,0 @@
|
||||
<div
|
||||
*ngIf="error.code !== 'operation_timeout'; else timeout"
|
||||
style="display: flex; flex-direction: column; gap: 16px"
|
||||
>
|
||||
<div style="display: flex; gap: 16px">
|
||||
<cc-details-item style="flex: 1" title="Code">{{ error.code }}</cc-details-item>
|
||||
<cc-details-item style="flex: 2" title="Reason">{{ error.reason }}</cc-details-item>
|
||||
</div>
|
||||
<cc-details-item *ngIf="error.path" style="flex: 1" title="Sub Failure">{{
|
||||
error.path
|
||||
}}</cc-details-item>
|
||||
</div>
|
||||
<ng-template #timeout>
|
||||
<cc-details-item style="flex: 1">Operation timeout</cc-details-item>
|
||||
</ng-template>
|
@ -1,59 +0,0 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import {
|
||||
FailureCode,
|
||||
FailureReason,
|
||||
SubFailure,
|
||||
InvoicePaymentStatus,
|
||||
} from '@vality/domain-proto/domain';
|
||||
|
||||
import { getUnionKey } from '@cc/utils/get-union-key';
|
||||
|
||||
export interface PaymentError {
|
||||
code: FailureCode;
|
||||
reason?: FailureReason;
|
||||
path?: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'cc-payment-error',
|
||||
templateUrl: 'payment-error.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class PaymentErrorComponent {
|
||||
@Input()
|
||||
set status(status: InvoicePaymentStatus) {
|
||||
const {
|
||||
failed: { failure },
|
||||
} = status;
|
||||
|
||||
switch (getUnionKey(failure)) {
|
||||
case 'failure': {
|
||||
const { code, reason, sub } = failure.failure;
|
||||
this.error = {
|
||||
code,
|
||||
reason,
|
||||
path: this.makePath(sub),
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'operation_timeout':
|
||||
this.error = {
|
||||
code: 'operation_timeout',
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
error: PaymentError;
|
||||
|
||||
private makePath(sub: SubFailure): string {
|
||||
const path = [];
|
||||
if (sub) {
|
||||
path.push(sub.code);
|
||||
if (sub.sub) {
|
||||
path.push(this.makePath(sub.sub));
|
||||
}
|
||||
}
|
||||
return path.join(' → ');
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
|
||||
import { DetailsItemModule } from '@cc/components/details-item';
|
||||
|
||||
import { PaymentErrorComponent } from './payment-error.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [PaymentErrorComponent],
|
||||
imports: [DetailsItemModule, CommonModule, MatProgressSpinnerModule],
|
||||
exports: [PaymentErrorComponent],
|
||||
})
|
||||
export class PaymentErrorModule {}
|
@ -1,70 +0,0 @@
|
||||
<div style="display: flex; flex-direction: column; gap: 16px">
|
||||
<div style="display: flex; gap: 16px">
|
||||
<cc-details-item style="flex: 1" title="Amount"
|
||||
>{{ payment.amount | ccFormatAmount }}
|
||||
{{ payment.currency_symbolic_code | ccCurrency }}</cc-details-item
|
||||
>
|
||||
<cc-details-item style="flex: 1" title="Created At">{{
|
||||
payment.created_at | date: 'dd.MM.yyyy HH:mm:ss'
|
||||
}}</cc-details-item>
|
||||
<cc-details-item style="flex: 1" title="Status">
|
||||
<cc-status [color]="payment.status | toStatus | toPaymentColor">{{
|
||||
payment.status | toStatus
|
||||
}}</cc-status>
|
||||
</cc-details-item>
|
||||
</div>
|
||||
<div style="display: flex; gap: 16px">
|
||||
<cc-details-item style="flex: 1" title="Payer">{{
|
||||
getPayerEmail(payment.payer)
|
||||
}}</cc-details-item>
|
||||
<cc-details-item style="flex: 1" title="Payment Tool">
|
||||
<cc-payment-tool [paymentTool]="getPaymentTool(payment.payer)"></cc-payment-tool>
|
||||
</cc-details-item>
|
||||
<cc-details-item style="flex: 1" title="Invoice">#{{ payment.invoice_id }}</cc-details-item>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; gap: 16px">
|
||||
<div style="display: flex; flex-direction: column; gap: 16px">
|
||||
<div>
|
||||
<mat-divider></mat-divider>
|
||||
</div>
|
||||
<div class="mat-h1">Shop</div>
|
||||
<cc-shop-details [shop]="shop"></cc-shop-details>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="hasError(payment.status)"
|
||||
style="display: flex; flex-direction: column; gap: 16px"
|
||||
>
|
||||
<div>
|
||||
<mat-divider></mat-divider>
|
||||
</div>
|
||||
<div class="mat-headline-4 mat-no-margin">Failure</div>
|
||||
<cc-payment-error [status]="payment.status"></cc-payment-error>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; gap: 16px">
|
||||
<div>
|
||||
<mat-divider></mat-divider>
|
||||
</div>
|
||||
<div class="mat-h1">Terminal</div>
|
||||
<cc-payment-terminal [terminalID]="payment.terminal_id.id"></cc-payment-terminal>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; gap: 16px">
|
||||
<div>
|
||||
<mat-divider></mat-divider>
|
||||
</div>
|
||||
<div class="mat-h1">Provider</div>
|
||||
<cc-payment-provider [providerID]="payment.provider_id.id"></cc-payment-provider>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; gap: 16px">
|
||||
<div>
|
||||
<mat-divider></mat-divider>
|
||||
</div>
|
||||
<div class="mat-h1">Contractor</div>
|
||||
<cc-payment-contractor
|
||||
*ngIf="payment && shop"
|
||||
[contractID]="shop.contract_id"
|
||||
[partyID]="payment?.owner_id"
|
||||
>
|
||||
</cc-payment-contractor>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,40 +0,0 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import { Shop, InvoicePaymentStatus, PaymentTool } from '@vality/domain-proto/domain';
|
||||
import { Payer, StatPayment } from '@vality/magista-proto/magista';
|
||||
|
||||
import { getUnionKey } from '../../../../utils';
|
||||
|
||||
@Component({
|
||||
selector: 'cc-payment-main-info',
|
||||
templateUrl: 'payment-main-info.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class PaymentMainInfoComponent {
|
||||
@Input() payment: StatPayment;
|
||||
@Input() shop: Shop;
|
||||
|
||||
getPayerEmail(payer: Payer): string {
|
||||
if (payer.customer) {
|
||||
return payer.customer.contact_info.email;
|
||||
}
|
||||
if (payer.payment_resource) {
|
||||
return payer.payment_resource.contact_info.email;
|
||||
}
|
||||
if (payer.recurrent) {
|
||||
return payer.recurrent.contact_info.email;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getPaymentTool(payer: Payer): PaymentTool {
|
||||
return (
|
||||
payer?.customer?.payment_tool ||
|
||||
payer?.payment_resource?.resource?.payment_tool ||
|
||||
payer?.recurrent?.payment_tool
|
||||
);
|
||||
}
|
||||
|
||||
hasError(status: InvoicePaymentStatus): boolean {
|
||||
return getUnionKey(status) === 'failed';
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
|
||||
import { ShopDetailsModule, StatusModule } from '@cc/app/shared/components';
|
||||
import { CommonPipesModule, ThriftPipesModule } from '@cc/app/shared/pipes';
|
||||
import { DetailsItemModule } from '@cc/components/details-item';
|
||||
|
||||
import { PaymentContractorModule } from './payment-contractor';
|
||||
import { PaymentErrorModule } from './payment-error';
|
||||
import { PaymentMainInfoComponent } from './payment-main-info.component';
|
||||
import { PaymentProviderModule } from './payment-provider';
|
||||
import { PaymentTerminalModule } from './payment-terminal';
|
||||
import { PaymentToolModule } from './payment-tool';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatIconModule,
|
||||
DetailsItemModule,
|
||||
StatusModule,
|
||||
PaymentToolModule,
|
||||
ThriftPipesModule,
|
||||
CommonPipesModule,
|
||||
MatDividerModule,
|
||||
PaymentContractorModule,
|
||||
PaymentTerminalModule,
|
||||
PaymentProviderModule,
|
||||
PaymentErrorModule,
|
||||
ShopDetailsModule,
|
||||
],
|
||||
declarations: [PaymentMainInfoComponent],
|
||||
exports: [PaymentMainInfoComponent],
|
||||
})
|
||||
export class PaymentMainInfoModule {}
|
@ -1,40 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { merge, of, Subject } from 'rxjs';
|
||||
import { catchError, filter, map, shareReplay, startWith, switchMap } from 'rxjs/operators';
|
||||
|
||||
import { DomainStoreService } from '@cc/app/api/domain-config';
|
||||
import { progress } from '@cc/app/shared/custom-operators';
|
||||
|
||||
@Injectable()
|
||||
export class FetchProviderService {
|
||||
private getProvider$ = new Subject<number>();
|
||||
private hasError$ = new Subject<void>();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/member-ordering
|
||||
provider$ = this.getProvider$.pipe(
|
||||
switchMap((providerID) =>
|
||||
this.domainStoreService.getObjects('provider').pipe(
|
||||
map((providerObject) => providerObject.find((obj) => obj.ref.id === providerID)),
|
||||
catchError(() => {
|
||||
this.hasError$.next();
|
||||
return of('error');
|
||||
}),
|
||||
filter((result) => result !== 'error'),
|
||||
),
|
||||
),
|
||||
shareReplay(1),
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/member-ordering
|
||||
inProgress$ = progress(this.getProvider$, merge(this.provider$, this.hasError$)).pipe(
|
||||
startWith(true),
|
||||
);
|
||||
|
||||
constructor(private domainStoreService: DomainStoreService) {
|
||||
this.provider$.subscribe();
|
||||
}
|
||||
|
||||
getProvider(providerID: number) {
|
||||
this.getProvider$.next(providerID);
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from './payment-provider.module';
|
@ -1,13 +0,0 @@
|
||||
<div *ngIf="provider$ | async as provider; else empty" style="display: flex; gap: 16px">
|
||||
<cc-details-item style="flex: 1" title="ID">{{ provider.ref.id }}</cc-details-item>
|
||||
<cc-details-item style="flex: 1" title="Name">{{ provider.data.name }}</cc-details-item>
|
||||
<div style="flex: 1"></div>
|
||||
</div>
|
||||
<ng-template #empty>
|
||||
<div *ngIf="inProgress$ | async; else emptyResult" style="display: flex">
|
||||
<cc-details-item style="flex: 1">Loading...</cc-details-item>
|
||||
</div>
|
||||
<ng-template #emptyResult>
|
||||
<div>Error provider loading.</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
@ -1,23 +0,0 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||
|
||||
import { FetchProviderService } from './fetch-provider.service';
|
||||
|
||||
@Component({
|
||||
selector: 'cc-payment-provider',
|
||||
templateUrl: 'payment-provider.component.html',
|
||||
providers: [FetchProviderService],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class PaymentProviderComponent implements OnInit {
|
||||
@Input()
|
||||
providerID: number;
|
||||
|
||||
provider$ = this.fetchProviderService.provider$;
|
||||
inProgress$ = this.fetchProviderService.inProgress$;
|
||||
|
||||
constructor(private fetchProviderService: FetchProviderService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.fetchProviderService.getProvider(this.providerID);
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { DetailsItemModule } from '@cc/components/details-item';
|
||||
|
||||
import { PaymentProviderComponent } from './payment-provider.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [PaymentProviderComponent],
|
||||
imports: [DetailsItemModule, CommonModule],
|
||||
exports: [PaymentProviderComponent],
|
||||
})
|
||||
export class PaymentProviderModule {}
|
@ -1,40 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { merge, of, Subject } from 'rxjs';
|
||||
import { catchError, filter, map, shareReplay, startWith, switchMap } from 'rxjs/operators';
|
||||
|
||||
import { DomainStoreService } from '@cc/app/api/domain-config';
|
||||
import { progress } from '@cc/app/shared/custom-operators';
|
||||
|
||||
@Injectable()
|
||||
export class FetchTerminalService {
|
||||
private getTerminal$ = new Subject<number>();
|
||||
private hasError$ = new Subject<void>();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/member-ordering
|
||||
terminal$ = this.getTerminal$.pipe(
|
||||
switchMap((terminalID) =>
|
||||
this.domainStoreService.getObjects('terminal').pipe(
|
||||
map((terminalObject) => terminalObject.find((obj) => obj.ref.id === terminalID)),
|
||||
catchError(() => {
|
||||
this.hasError$.next();
|
||||
return of('error');
|
||||
}),
|
||||
filter((result) => result !== 'error'),
|
||||
),
|
||||
),
|
||||
shareReplay(1),
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/member-ordering
|
||||
inProgress$ = progress(this.getTerminal$, merge(this.terminal$, this.hasError$)).pipe(
|
||||
startWith(true),
|
||||
);
|
||||
|
||||
constructor(private domainStoreService: DomainStoreService) {
|
||||
this.terminal$.subscribe();
|
||||
}
|
||||
|
||||
getTerminal(terminalID: number) {
|
||||
this.getTerminal$.next(terminalID);
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from './payment-terminal.module';
|
@ -1,13 +0,0 @@
|
||||
<div *ngIf="terminal$ | async as terminal; else empty" style="display: flex; gap: 16px">
|
||||
<cc-details-item style="flex: 1" title="ID">{{ terminal.ref.id }}</cc-details-item>
|
||||
<cc-details-item style="flex: 1" title="Name">{{ terminal.data.name }}</cc-details-item>
|
||||
<div style="flex: 1"></div>
|
||||
</div>
|
||||
<ng-template #empty>
|
||||
<div *ngIf="inProgress$ | async; else emptyResult" style="display: flex">
|
||||
<cc-details-item style="flex: 1">Loading...</cc-details-item>
|
||||
</div>
|
||||
<ng-template #emptyResult>
|
||||
<cc-details-item style="flex: 1">Error terminal loading.</cc-details-item>
|
||||
</ng-template>
|
||||
</ng-template>
|
@ -1,23 +0,0 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||
|
||||
import { FetchTerminalService } from './fetch-terminal.service';
|
||||
|
||||
@Component({
|
||||
selector: 'cc-payment-terminal',
|
||||
templateUrl: 'payment-terminal.component.html',
|
||||
providers: [FetchTerminalService],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class PaymentTerminalComponent implements OnInit {
|
||||
@Input()
|
||||
terminalID: number;
|
||||
|
||||
terminal$ = this.fetchTerminalService.terminal$;
|
||||
inProgress$ = this.fetchTerminalService.inProgress$;
|
||||
|
||||
constructor(private fetchTerminalService: FetchTerminalService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.fetchTerminalService.getTerminal(this.terminalID);
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { DetailsItemModule } from '@cc/components/details-item';
|
||||
|
||||
import { PaymentTerminalComponent } from './payment-terminal.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [PaymentTerminalComponent],
|
||||
imports: [DetailsItemModule, CommonModule],
|
||||
exports: [PaymentTerminalComponent],
|
||||
})
|
||||
export class PaymentTerminalModule {}
|
@ -1 +0,0 @@
|
||||
{{ bankCard | toCardNumber }} ({{ bankCard.payment_system.id }})
|
@ -1,11 +0,0 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import { BankCard } from '@vality/domain-proto/domain';
|
||||
|
||||
@Component({
|
||||
selector: 'cc-bank-card',
|
||||
templateUrl: 'bank-card.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class BankCardComponent {
|
||||
@Input() bankCard: BankCard;
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
|
||||
import { BankCardComponent } from './bank-card.component';
|
||||
import { ToCardNumberPipe } from './to-card-number.pipe';
|
||||
@NgModule({
|
||||
imports: [CommonModule, MatIconModule],
|
||||
declarations: [BankCardComponent, ToCardNumberPipe],
|
||||
exports: [BankCardComponent],
|
||||
})
|
||||
export class BankCardModule {}
|
@ -1,2 +0,0 @@
|
||||
export * from './bank-card.module';
|
||||
export * from './bank-card.component';
|
@ -1,14 +0,0 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { BankCard } from '@vality/domain-proto/domain';
|
||||
|
||||
@Pipe({
|
||||
name: 'toCardNumber',
|
||||
})
|
||||
export class ToCardNumberPipe implements PipeTransform {
|
||||
transform(card: BankCard): string {
|
||||
return toCardNumber(card);
|
||||
}
|
||||
}
|
||||
|
||||
export const toCardNumber = (card: BankCard): string =>
|
||||
`${card.bin}******${card.last_digits}`.replace(/(.{4})/g, '$& ');
|
@ -1,2 +0,0 @@
|
||||
export * from './payment-tool.module';
|
||||
export * from './payment-tool.component';
|
@ -1,13 +0,0 @@
|
||||
<cc-bank-card *ngIf="paymentTool.bank_card" [bankCard]="paymentTool.bank_card"></cc-bank-card>
|
||||
<div *ngIf="paymentTool.crypto_currency">
|
||||
{{ paymentTool.crypto_currency?.id }}
|
||||
</div>
|
||||
<div *ngIf="paymentTool.digital_wallet">
|
||||
{{ paymentTool.digital_wallet?.id }}
|
||||
</div>
|
||||
<div *ngIf="paymentTool.mobile_commerce">
|
||||
{{ paymentTool.mobile_commerce.phone }}
|
||||
</div>
|
||||
<div *ngIf="paymentTool.payment_terminal">
|
||||
{{ paymentTool.payment_terminal?.payment_service?.id }}
|
||||
</div>
|
@ -1,11 +0,0 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import { PaymentTool } from '@vality/domain-proto/domain';
|
||||
|
||||
@Component({
|
||||
selector: 'cc-payment-tool',
|
||||
templateUrl: 'payment-tool.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class PaymentToolComponent {
|
||||
@Input() paymentTool: PaymentTool;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { BankCardModule } from './bank-card';
|
||||
import { PaymentToolComponent } from './payment-tool.component';
|
||||
@NgModule({
|
||||
imports: [CommonModule, BankCardModule],
|
||||
declarations: [PaymentToolComponent],
|
||||
exports: [PaymentToolComponent],
|
||||
})
|
||||
export class PaymentToolModule {}
|
@ -27,8 +27,8 @@ export class PaymentsTableComponent {
|
||||
@Output() more = new EventEmitter<void>();
|
||||
|
||||
columns: Column<StatPayment>[] = [
|
||||
{ field: 'id', click: (d) => this.toDetails(d) },
|
||||
{ field: 'invoice_id' },
|
||||
{ field: 'id', click: (d) => this.toDetails(d), pinned: 'left' },
|
||||
{ field: 'invoice_id', pinned: 'left' },
|
||||
{
|
||||
field: 'amount',
|
||||
type: 'currency',
|
||||
@ -70,6 +70,7 @@ export class PaymentsTableComponent {
|
||||
'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) =>
|
||||
|
@ -1,3 +1,10 @@
|
||||
<mat-toolbar *ngIf="tags?.length" style="height: 48px; padding: 0 24px">
|
||||
<div style="display: flex; gap: 8px; align-items: center">
|
||||
<v-tag *ngFor="let tag of tags" [color]="tag.color" style="margin-top: 8px">{{
|
||||
tag.value
|
||||
}}</v-tag>
|
||||
</div>
|
||||
</mat-toolbar>
|
||||
<div [ngClass]="{ wrapper__offset: !noOffset }" class="wrapper">
|
||||
<div *ngIf="title" style="display: flex; flex-direction: column; gap: 8px">
|
||||
<div *ngIf="(path$ | async)?.length > 1" class="mat-caption mat-secondary-text">
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
EventEmitter,
|
||||
} from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { UrlService } from '@vality/ng-core';
|
||||
import { UrlService, Color } from '@vality/ng-core';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
@ -25,6 +25,7 @@ export class PageLayoutComponent {
|
||||
@Input() id?: string;
|
||||
@Input() progress?: boolean;
|
||||
@Input({ transform: booleanAttribute }) noOffset = false;
|
||||
@Input() tags?: { value: string; color: Color }[] | null;
|
||||
|
||||
@Output() idLinkClick = new EventEmitter<MouseEvent>();
|
||||
|
||||
|
@ -3,9 +3,12 @@ import { NgModule } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatToolbar } from '@angular/material/toolbar';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { ActionsModule } from '@vality/ng-core';
|
||||
import { ActionsModule, TagModule } from '@vality/ng-core';
|
||||
|
||||
import { ThriftPipesModule } from '../../pipes';
|
||||
|
||||
import { PageLayoutActionsComponent } from './components/page-layout-actions/page-layout-actions.component';
|
||||
import { PageLayoutComponent } from './page-layout.component';
|
||||
@ -19,6 +22,9 @@ import { PageLayoutComponent } from './page-layout.component';
|
||||
MatButtonModule,
|
||||
MatTooltipModule,
|
||||
ActionsModule,
|
||||
MatToolbar,
|
||||
TagModule,
|
||||
ThriftPipesModule,
|
||||
],
|
||||
declarations: [PageLayoutComponent, PageLayoutActionsComponent],
|
||||
exports: [PageLayoutComponent, PageLayoutActionsComponent],
|
||||
|
@ -1,7 +1,31 @@
|
||||
<cc-card [title]="(shop$ | async)?.details?.name">
|
||||
<mat-tab-group>
|
||||
<mat-tab label="Shop">
|
||||
<div style="padding-top: 24px">
|
||||
<cc-domain-thrift-viewer
|
||||
[progress]="!!(progress$ | async)"
|
||||
[value]="shop$ | async"
|
||||
type="Shop"
|
||||
></cc-domain-thrift-viewer>
|
||||
</div>
|
||||
</mat-tab>
|
||||
<mat-tab label="Contract">
|
||||
<div style="padding-top: 24px">
|
||||
<cc-domain-thrift-viewer
|
||||
[progress]="!!(progress$ | async)"
|
||||
[value]="contract$ | async"
|
||||
type="Contract"
|
||||
></cc-domain-thrift-viewer>
|
||||
</div>
|
||||
</mat-tab>
|
||||
<mat-tab label="Contractor">
|
||||
<div style="padding-top: 24px">
|
||||
<cc-domain-thrift-viewer
|
||||
[progress]="!!(progress$ | async)"
|
||||
[value]="contractor$ | async"
|
||||
type="Contractor"
|
||||
></cc-domain-thrift-viewer>
|
||||
</div>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</cc-card>
|
||||
|
@ -1,41 +1,52 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, Input, OnChanges } from '@angular/core';
|
||||
import { ComponentChanges } from '@vality/ng-core';
|
||||
import { ReplaySubject, defer, combineLatest } from 'rxjs';
|
||||
import { switchMap, map, shareReplay } from 'rxjs/operators';
|
||||
import { Component, input } from '@angular/core';
|
||||
import { toObservable } from '@angular/core/rxjs-interop';
|
||||
import { MatCard, MatCardContent } from '@angular/material/card';
|
||||
import { MatDivider } from '@angular/material/divider';
|
||||
import { MatTabGroup, MatTab } from '@angular/material/tabs';
|
||||
import { PartyID, ShopID } from '@vality/domain-proto/internal/domain';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { switchMap, shareReplay } from 'rxjs/operators';
|
||||
|
||||
import { PartiesStoreService } from '../../../api/payment-processing';
|
||||
import { CardComponent } from '../sidenav-info/components/card/card.component';
|
||||
import { DomainThriftViewerComponent } from '../thrift-api-crud';
|
||||
import { DomainThriftViewerComponent, MagistaThriftViewerComponent } from '../thrift-api-crud';
|
||||
import { ThriftViewerModule } from '../thrift-viewer';
|
||||
|
||||
@Component({
|
||||
selector: 'cc-shop-card',
|
||||
standalone: true,
|
||||
imports: [CommonModule, CardComponent, DomainThriftViewerComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
CardComponent,
|
||||
DomainThriftViewerComponent,
|
||||
MatCard,
|
||||
MatCardContent,
|
||||
ThriftViewerModule,
|
||||
MatDivider,
|
||||
MagistaThriftViewerComponent,
|
||||
MatTabGroup,
|
||||
MatTab,
|
||||
],
|
||||
templateUrl: './shop-card.component.html',
|
||||
})
|
||||
export class ShopCardComponent implements OnChanges {
|
||||
@Input() partyId: string;
|
||||
@Input() id: string;
|
||||
export class ShopCardComponent {
|
||||
partyId = input.required<PartyID>();
|
||||
id = input.required<ShopID>();
|
||||
|
||||
progress$ = this.partiesStoreService.progress$;
|
||||
shop$ = defer(() => this.partyId$).pipe(
|
||||
switchMap((partyID) => combineLatest([this.partiesStoreService.get(partyID), this.id$])),
|
||||
map(([party, id]) => party.shops.get(id)),
|
||||
shop$ = combineLatest([toObservable(this.partyId), toObservable(this.id)]).pipe(
|
||||
switchMap(([partyId, id]) => this.partiesStoreService.getShop(id, partyId)),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
contractor$ = combineLatest([toObservable(this.partyId), toObservable(this.id)]).pipe(
|
||||
switchMap(([partyId, id]) => this.partiesStoreService.getContractor(id, partyId)),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
contract$ = combineLatest([toObservable(this.partyId), toObservable(this.id)]).pipe(
|
||||
switchMap(([partyId, id]) => this.partiesStoreService.getContract(id, partyId)),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
|
||||
private id$ = new ReplaySubject<string>(1);
|
||||
private partyId$ = new ReplaySubject<string>(1);
|
||||
|
||||
constructor(private partiesStoreService: PartiesStoreService) {}
|
||||
|
||||
ngOnChanges(changes: ComponentChanges<ShopCardComponent>) {
|
||||
if (changes.id) {
|
||||
this.id$.next(this.id);
|
||||
}
|
||||
if (changes.partyId) {
|
||||
this.partyId$.next(this.partyId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Injectable, Type, Inject, Optional } from '@angular/core';
|
||||
import { Injectable, Type, Inject, Optional, InputSignal } from '@angular/core';
|
||||
import {
|
||||
QueryParamsService,
|
||||
QueryParamsNamespace,
|
||||
@ -12,6 +12,10 @@ import { filter, map } from 'rxjs/operators';
|
||||
|
||||
import { SIDENAV_INFO_COMPONENTS, SidenavInfoComponents } from './tokens';
|
||||
|
||||
type InputType<T> = {
|
||||
[N in keyof T]?: T[N] extends InputSignal<infer S> ? S : T[N];
|
||||
};
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
@ -50,7 +54,7 @@ export class SidenavInfoService {
|
||||
|
||||
toggle<C extends Type<unknown>>(
|
||||
component: PossiblyAsync<C>,
|
||||
inputs: { [N in keyof InstanceType<C>]?: InstanceType<C>[N] } = {},
|
||||
inputs: InputType<InstanceType<C>> = {},
|
||||
) {
|
||||
getPossiblyAsyncObservable(component).subscribe((comp) => {
|
||||
if (this.isEqual(comp, inputs)) {
|
||||
@ -61,10 +65,7 @@ export class SidenavInfoService {
|
||||
});
|
||||
}
|
||||
|
||||
open<C extends Type<unknown>>(
|
||||
component: C,
|
||||
inputs: { [N in keyof InstanceType<C>]?: InstanceType<C>[N] } = {},
|
||||
) {
|
||||
open<C extends Type<unknown>>(component: C, inputs: InputType<InstanceType<C>> = {}) {
|
||||
this.component$.next(component);
|
||||
this.inputs = inputs;
|
||||
void this.qp.set({
|
||||
@ -94,7 +95,7 @@ export class SidenavInfoService {
|
||||
|
||||
private isEqual<C extends Type<unknown>>(
|
||||
component: C,
|
||||
inputs: { [N in keyof InstanceType<C>]?: InstanceType<C>[N] } = {},
|
||||
inputs: InputType<InstanceType<C>> = {},
|
||||
) {
|
||||
return component === this.component$.value && isEqual(this.inputs, inputs);
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { ThriftAstMetadata } from '@vality/domain-proto';
|
||||
import { DomainObject } from '@vality/domain-proto/domain';
|
||||
import { Rational, Timestamp } from '@vality/domain-proto/internal/base';
|
||||
import { PartyID, ShopID } from '@vality/domain-proto/internal/domain';
|
||||
import { getImportValue } from '@vality/ng-core';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
import round from 'lodash-es/round';
|
||||
@ -15,6 +16,7 @@ import { MetadataViewExtension } from '@cc/app/shared/components/json-viewer';
|
||||
import { isTypeWithAliases, MetadataFormData } from '@cc/app/shared/components/metadata-form';
|
||||
|
||||
import { getUnionValue } from '../../../../../../../../utils';
|
||||
import { PartiesStoreService } from '../../../../../../../api/payment-processing';
|
||||
import { SidenavInfoService } from '../../../../../sidenav-info';
|
||||
import { getDomainObjectDetails } from '../../../utils';
|
||||
|
||||
@ -27,6 +29,17 @@ export class DomainMetadataViewExtensionsService {
|
||||
).pipe(
|
||||
map((metadata): MetadataViewExtension[] => [
|
||||
...this.createDomainObjectExtensions(metadata),
|
||||
{
|
||||
determinant: (data) => of(isTypeWithAliases(data, 'PartyID', 'domain')),
|
||||
extension: (_, partyId: PartyID) =>
|
||||
this.partiesStoreService.get(partyId).pipe(
|
||||
map((p) => ({
|
||||
value: p.contact_info.email,
|
||||
link: [[`/party/${p.id}`]],
|
||||
tooltip: p.id,
|
||||
})),
|
||||
),
|
||||
},
|
||||
{
|
||||
determinant: (data) => of(isTypeWithAliases(data, 'Timestamp', 'base')),
|
||||
extension: (_, value: Timestamp) =>
|
||||
@ -53,8 +66,33 @@ export class DomainMetadataViewExtensionsService {
|
||||
private domainStoreService: DomainStoreService,
|
||||
private sidenavInfoService: SidenavInfoService,
|
||||
private destroyRef: DestroyRef,
|
||||
private partiesStoreService: PartiesStoreService,
|
||||
) {}
|
||||
|
||||
createShopExtension(partyId: PartyID): MetadataViewExtension {
|
||||
return {
|
||||
determinant: (data) => of(isTypeWithAliases(data, 'ShopID', 'domain')),
|
||||
extension: (_, shopId: ShopID) =>
|
||||
this.partiesStoreService.getShop(shopId, partyId).pipe(
|
||||
map((p) => ({
|
||||
value: p.details.name,
|
||||
tooltip: shopId,
|
||||
click: () => {
|
||||
this.sidenavInfoService.toggle(
|
||||
import('../../../../../shop-card/shop-card.component').then(
|
||||
(r) => r.ShopCardComponent,
|
||||
),
|
||||
{
|
||||
partyId,
|
||||
id: shopId,
|
||||
},
|
||||
);
|
||||
},
|
||||
})),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
createDomainObjectExtensions(metadata: ThriftAstMetadata[]): MetadataViewExtension[] {
|
||||
const domainFields = new MetadataFormData<string, 'struct'>(
|
||||
metadata,
|
||||
|
@ -1 +1,2 @@
|
||||
export * from './magista-thrift-form';
|
||||
export * from './magista-thrift-viewer.component';
|
||||
|
@ -0,0 +1,23 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { ThriftAstMetadata } from '@vality/domain-proto';
|
||||
import { getImportValue } from '@vality/ng-core';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { MetadataViewExtension } from '../../json-viewer';
|
||||
import { DomainMetadataViewExtensionsService } from '../domain/domain-thrift-viewer/services/domain-metadata-view-extensions';
|
||||
import { ThriftViewerSuperclass, ThriftViewerBaseModule } from '../utils';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'cc-magista-thrift-viewer',
|
||||
template: `<cc-thrift-viewer-base [data]="data()" />`,
|
||||
imports: [ThriftViewerBaseModule],
|
||||
})
|
||||
export class MagistaThriftViewerComponent<T> extends ThriftViewerSuperclass<T> {
|
||||
domainMetadataViewExtensionsService = inject(DomainMetadataViewExtensionsService);
|
||||
|
||||
defaultNamespace = 'magista';
|
||||
metadata$ = getImportValue<ThriftAstMetadata[]>(import('@vality/magista-proto/metadata.json'));
|
||||
extensions$: Observable<MetadataViewExtension[]> =
|
||||
this.domainMetadataViewExtensionsService.extensions$;
|
||||
}
|
1
src/app/shared/components/thrift-api-crud/utils/index.ts
Normal file
1
src/app/shared/components/thrift-api-crud/utils/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './thrift-viewer-superclass';
|
@ -0,0 +1,2 @@
|
||||
export * from './thrift-viewer-superclass.directive';
|
||||
export * from './thrift-viewer-base.module';
|
@ -0,0 +1,11 @@
|
||||
<cc-thrift-viewer
|
||||
*ngIf="data() as data"
|
||||
[compared]="data.compared"
|
||||
[extensions]="data.extensions$ | async"
|
||||
[kind]="data.kind"
|
||||
[metadata]="data.metadata$ | async"
|
||||
[namespace]="data.namespace"
|
||||
[progress]="data.progress"
|
||||
[type]="data.type"
|
||||
[value]="data.value"
|
||||
></cc-thrift-viewer>
|
@ -0,0 +1,13 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { ThriftViewerModule } from '../../../thrift-viewer';
|
||||
|
||||
import { ThriftViewerBaseComponent } from './thrift-viewer-superclass.directive';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, ThriftViewerModule],
|
||||
declarations: [ThriftViewerBaseComponent],
|
||||
exports: [ThriftViewerBaseComponent],
|
||||
})
|
||||
export class ThriftViewerBaseModule {}
|
@ -0,0 +1,74 @@
|
||||
import {
|
||||
Component,
|
||||
Directive,
|
||||
Input,
|
||||
booleanAttribute,
|
||||
input,
|
||||
signal,
|
||||
OnChanges,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { ThriftAstMetadata } from '@vality/domain-proto';
|
||||
import { ValueType } from '@vality/thrift-ts';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { map, shareReplay } from 'rxjs/operators';
|
||||
|
||||
import { MetadataViewExtension } from '../../../json-viewer';
|
||||
import { ViewerKind } from '../../../thrift-viewer';
|
||||
|
||||
interface Data<T> {
|
||||
kind: ViewerKind;
|
||||
value: T;
|
||||
compared?: T;
|
||||
type: ValueType;
|
||||
progress: boolean;
|
||||
namespace: string;
|
||||
metadata$: Observable<ThriftAstMetadata[]>;
|
||||
extensions$: Observable<MetadataViewExtension[]>;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'cc-thrift-viewer-base',
|
||||
templateUrl: './thrift-viewer-base.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ThriftViewerBaseComponent<T> {
|
||||
data = input.required<Data<T>>();
|
||||
}
|
||||
|
||||
@Directive()
|
||||
export abstract class ThriftViewerSuperclass<T> implements OnChanges {
|
||||
@Input() kind = ViewerKind.Component;
|
||||
@Input() value: T;
|
||||
@Input() compared?: T;
|
||||
@Input() type: ValueType;
|
||||
@Input({ transform: booleanAttribute }) progress = false;
|
||||
@Input() extensions: MetadataViewExtension[] = [];
|
||||
@Input() namespace?: string;
|
||||
|
||||
abstract defaultNamespace: string;
|
||||
abstract metadata$: Observable<ThriftAstMetadata[]>;
|
||||
extensions$: Observable<MetadataViewExtension[]> = of([]);
|
||||
|
||||
data = signal<Data<T>>(this.createData());
|
||||
|
||||
ngOnChanges() {
|
||||
this.data.set(this.createData());
|
||||
}
|
||||
|
||||
private createData() {
|
||||
return {
|
||||
kind: this.kind,
|
||||
value: this.value,
|
||||
compared: this.compared,
|
||||
type: this.type,
|
||||
progress: this.progress,
|
||||
namespace: this.namespace || this.defaultNamespace,
|
||||
metadata$: this.metadata$,
|
||||
extensions$: this.extensions$.pipe(
|
||||
map((ext) => [...(ext || []), ...(this.extensions || [])]),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user