TD-369: Change stat service (ANAPI V2) (#121)

This commit is contained in:
Rinat Arsaev 2022-08-09 21:00:11 +03:00 committed by GitHub
parent 26716d7c8a
commit 058b7147fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
87 changed files with 480 additions and 1132 deletions

View File

@ -11,7 +11,7 @@
"build-libs": "ng build ng-core",
"build": "npm run build-libs && npm run build-app",
"test": "ng test",
"lint": "eslint \"src/**/*.{ts,js,html}\" --max-warnings 1115",
"lint": "eslint \"src/**/*.{ts,js,html}\" --max-warnings 1033",
"lint-fix": "npm run lint -- --fix",
"lint-errors": "npm run lint -- --quiet",
"lint-libs": "eslint \"projects/**/*.{ts,js,html}\" --max-warnings 0",

View File

@ -3,6 +3,7 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { DEFAULT_DIALOG_CONFIG } from '../tokens';
import { BaseDialogResponse } from '../types/base-dialog-response';
import { BaseDialogResponseStatus } from '../types/base-dialog-response-status';
@Directive()
export class BaseDialogSuperclass<
@ -18,4 +19,25 @@ export class BaseDialogSuperclass<
dialogRef = this.injector.get(MatDialogRef) as MatDialogRef<DialogComponent, DialogResponse>;
constructor(private injector: Injector) {}
closeWithCancellation(data?: DialogResponseData) {
this.dialogRef.close({
status: BaseDialogResponseStatus.Cancelled,
data,
} as never);
}
closeWithSuccess(data?: DialogResponseData) {
this.dialogRef.close({
status: BaseDialogResponseStatus.Success,
data,
} as never);
}
closeWithError(data?: DialogResponseData) {
this.dialogRef.close({
status: BaseDialogResponseStatus.Error,
data,
} as never);
}
}

View File

@ -1 +1,2 @@
export * from './components';
export * from './utils/objects';

View File

@ -0,0 +1,26 @@
import isEmpty from 'lodash-es/isEmpty';
import isNil from 'lodash-es/isNil';
import isObject from 'lodash-es/isObject';
import { ValuesType } from 'utility-types';
function isEmptyValue(value: unknown): boolean {
return isNil(value) || value === '' || (typeof value === 'object' && isEmpty(value));
}
export function cleanObject<T extends object>(
obj: T,
requiredKeys: (keyof T)[] = [],
isNotDeep = false
): T {
if (!isObject(obj)) return obj;
if (Array.isArray(obj))
return obj
.slice()
.map((v: unknown) => (isObject(v) && !isNotDeep ? cleanObject(v) : v))
.filter((v) => !isEmptyValue(v)) as T;
return Object.fromEntries(
(Object.entries(obj) as [keyof T, ValuesType<T>][])
.map(([k, v]) => [k, isObject(v) && !isNotDeep ? cleanObject(v as object) : v] as const)
.filter(([k, v]) => requiredKeys.includes(k) || !isEmptyValue(v))
) as T;
}

View File

@ -0,0 +1 @@
export * from './clean-object';

View File

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

View File

@ -1,22 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { AppAuthGuardService, OperationRole } from '@cc/app/shared/services';
import { PartyPaymentsComponent } from './party-payments.component';
@NgModule({
imports: [
RouterModule.forChild([
{
path: '',
component: PartyPaymentsComponent,
canActivate: [AppAuthGuardService],
data: {
roles: [OperationRole.SearchPayments],
},
},
]),
],
})
export class PartyPaymentsRoutingModule {}

View File

@ -1,10 +0,0 @@
<div fxLayout="column" fxLayoutGap="24px">
<div class="cc-headline">Merchant's payments</div>
<cc-payments-searcher
*ngIf="searchType"
[initSearchParams]="initSearchParams$ | async"
[type]="searchType"
(paymentEventFired$)="paymentEventFired($event)"
(searchParamsChanged$)="searchParamsUpdated($event)"
></cc-payments-searcher>
</div>

View File

@ -1,50 +0,0 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { pluck, shareReplay } from 'rxjs/operators';
import {
PaymentActions,
PaymentMenuItemEvent,
SearcherType,
SearchFiltersParams,
SearchType,
} from '@cc/app/shared/components';
import { PartyPaymentsService } from './party-payments.service';
@Component({
templateUrl: 'party-payments.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [PartyPaymentsService, PartyPaymentsService],
})
export class PartyPaymentsComponent {
searchType: SearcherType;
initSearchParams$ = this.partyPaymentsService.data$;
constructor(
private route: ActivatedRoute,
private partyPaymentsService: PartyPaymentsService,
private router: Router
) {
this.route.params.pipe(pluck('partyID'), shareReplay(1)).subscribe((partyID) => {
this.searchType = {
type: SearchType.PartySearcher,
partyID,
};
});
}
searchParamsUpdated($event: SearchFiltersParams) {
this.partyPaymentsService.preserve($event);
}
paymentEventFired($event: PaymentMenuItemEvent) {
const { partyID, invoiceID, paymentID } = $event;
switch ($event.action) {
case PaymentActions.NavigateToPayment:
void this.router.navigate([
`/party/${partyID}/invoice/${invoiceID}/payment/${paymentID}`,
]);
}
}
}

View File

@ -1,51 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FlexModule } from '@angular/flex-layout';
import { ReactiveFormsModule } from '@angular/forms';
import { MatBadgeModule } from '@angular/material/badge';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatTableModule } from '@angular/material/table';
import {
PaymentsMainSearchFiltersModule,
PaymentsOtherSearchFiltersModule,
PaymentsSearcherModule,
PaymentsTableModule,
StatusModule,
} from '@cc/app/shared/components';
import { EmptySearchResultModule } from '@cc/components/empty-search-result';
import { PartyPaymentsRoutingModule } from './party-payments-routing.module';
import { PartyPaymentsComponent } from './party-payments.component';
@NgModule({
imports: [
FlexModule,
MatCardModule,
MatProgressBarModule,
CommonModule,
MatButtonModule,
ReactiveFormsModule,
MatFormFieldModule,
MatInputModule,
MatTableModule,
MatMenuModule,
MatIconModule,
PaymentsMainSearchFiltersModule,
PartyPaymentsRoutingModule,
StatusModule,
PaymentsTableModule,
MatBadgeModule,
PaymentsOtherSearchFiltersModule,
EmptySearchResultModule,
PaymentsSearcherModule,
],
declarations: [PartyPaymentsComponent],
})
export class PartyPaymentsModule {}

View File

@ -1,27 +0,0 @@
import { Injectable } from '@angular/core';
import { ActivatedRoute, Params, Router } from '@angular/router';
import pickBy from 'lodash-es/pickBy';
import { SearchFiltersParams } from '@cc/app/shared/components';
import { QueryParamsStore } from '@cc/app/shared/services';
import { wrapValuesToArray } from '@cc/utils/wrap-values-to-array';
const SHOP_IDS_AND_PRIMITIVES = (v, k) => typeof v === 'string' && k === 'shopIDs';
@Injectable()
export class PartyPaymentsService extends QueryParamsStore<SearchFiltersParams> {
constructor(protected route: ActivatedRoute, protected router: Router) {
super(router, route);
}
mapToData(queryParams: Params): SearchFiltersParams {
return {
...queryParams,
...wrapValuesToArray(pickBy(queryParams, SHOP_IDS_AND_PRIMITIVES)),
} as SearchFiltersParams;
}
mapToParams(data: SearchFiltersParams): Params {
return data;
}
}

View File

@ -1,27 +0,0 @@
import { Injectable } from '@angular/core';
import { ActivatedRoute, Params, Router } from '@angular/router';
import pickBy from 'lodash-es/pickBy';
import { SearchFiltersParams } from '@cc/app/shared/components';
import { QueryParamsStore } from '@cc/app/shared/services';
import { wrapValuesToArray } from '@cc/utils/wrap-values-to-array';
const shopIdsAndPrimitives = (v, k) => typeof v === 'string' && k === 'shopIDs';
@Injectable()
export class PaymentsSearchFiltersStore extends QueryParamsStore<SearchFiltersParams> {
constructor(protected route: ActivatedRoute, protected router: Router) {
super(router, route);
}
mapToData(queryParams: Params): SearchFiltersParams {
return {
...queryParams,
...wrapValuesToArray(pickBy(queryParams, shopIdsAndPrimitives)),
} as SearchFiltersParams;
}
mapToParams(data: SearchFiltersParams): Params {
return data;
}
}

View File

@ -16,11 +16,6 @@ import { PartyComponent } from './party.component';
roles: [PartyRole.Get],
},
children: [
{
path: 'payments',
loadChildren: () =>
import('../party-payments').then((m) => m.PartyPaymentsModule),
},
{
path: 'shops',
loadChildren: () =>
@ -31,17 +26,12 @@ import { PartyComponent } from './party.component';
loadChildren: () =>
import('../shop-details').then((m) => m.ShopDetailsModule),
},
{
path: 'invoice/:invoiceID/payment/:paymentID',
loadChildren: () =>
import('../payment-details').then((m) => m.PaymentDetailsModule),
},
{
path: 'routing-rules',
loadChildren: () =>
import('../routing-rules').then((m) => m.RoutingRulesModule),
},
{ path: '', redirectTo: 'payments', pathMatch: 'full' },
{ path: '', redirectTo: 'shops', pathMatch: 'full' },
],
},
]),

View File

@ -3,12 +3,7 @@ import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { Observable, of } from 'rxjs';
import { catchError, filter, map, pluck, shareReplay, startWith, switchMap } from 'rxjs/operators';
import {
AppAuthGuardService,
DomainConfigRole,
OperationRole,
PartyRole,
} from '@cc/app/shared/services';
import { AppAuthGuardService, DomainConfigRole, PartyRole } from '@cc/app/shared/services';
import { DeanonimusService, getMaxSearchHitParty } from '../../thrift-services/deanonimus';
@ -49,12 +44,6 @@ export class PartyComponent {
private getLinks() {
const links = [
{
name: 'Payments',
url: 'payments',
otherActiveUrlFragments: ['payment', 'invoice'],
activateRoles: [OperationRole.SearchPayments],
},
{
name: 'Shops',
url: 'shops',

View File

@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatSnackBar } from '@angular/material/snack-bar';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { StatPayment } from '@vality/domain-proto/lib/merch_stat';
import { StatPayment } from '@vality/magista-proto';
import { CreateAndCaptureComponent } from './create-and-capture/create-and-capture.component';
import { PaymentAdjustmentService } from './payment-adjustment.service';

View File

@ -1,11 +1,12 @@
import { Injectable } from '@angular/core';
import { StatPayment, StatResponse } from '@vality/domain-proto/lib/merch_stat';
import { StatPayment } from '@vality/magista-proto';
import { cleanObject } from '@vality/ng-core';
import { Observable, of, Subject } from 'rxjs';
import { mergeMap, shareReplay } from 'rxjs/operators';
import { MerchantStatisticsService } from '@cc/app/api/magista';
import { DomainService } from '../../domain';
import { QueryDsl } from '../../query-dsl';
import { MerchantStatisticsService } from '../../thrift-services/damsel/merchant-statistics.service';
import { SearchFormParams } from './search-form/search-form-params';
@Injectable()
@ -30,7 +31,7 @@ export class PaymentAdjustmentService {
): Observable<StatPayment[]> {
return this.getPayments(params, continuationToken).pipe(
mergeMap((res) => {
const mergedPayments = [...payments, ...res.data.payments];
const mergedPayments = [...payments, ...res.payments];
this.searchPaymentChanges$.next(mergedPayments);
return res.continuation_token
? this.getAllPayments(params, res.continuation_token, mergedPayments)
@ -39,10 +40,7 @@ export class PaymentAdjustmentService {
);
}
private getPayments(
params: SearchFormParams,
continuationToken?: string
): Observable<StatResponse> {
private getPayments(params: SearchFormParams, continuationToken?: string) {
const {
fromRevision,
toRevision,
@ -55,24 +53,24 @@ export class PaymentAdjustmentService {
providerID,
terminalID,
} = params;
return this.merchantStatisticsService.getPayments({
dsl: JSON.stringify({
query: {
payments: {
...(partyId ? { merchant_id: partyId } : {}),
...(shopId ? { shop_id: shopId } : {}),
from_time: fromTime,
to_time: toTime,
from_payment_domain_revision: fromRevision,
to_payment_domain_revision: toRevision,
...(providerID ? { payment_provider_id: providerID } : {}),
...(terminalID ? { payment_terminal_id: terminalID } : {}),
...(status ? { payment_status: status } : {}),
...(invoiceIds ? { invoice_ids: invoiceIds } : {}),
},
return this.merchantStatisticsService.SearchPayments(
cleanObject({
common_search_query_params: {
from_time: fromTime,
to_time: toTime,
party_id: partyId,
shop_ids: [shopId],
continuation_token: continuationToken,
},
} as QueryDsl),
...(continuationToken ? { continuation_token: continuationToken } : {}),
});
payment_params: {
from_payment_domain_revision: fromRevision,
to_payment_domain_revision: toRevision,
payment_provider_id: providerID,
payment_terminal_id: terminalID,
payment_status: status,
},
invoice_ids: invoiceIds,
})
);
}
}

View File

@ -1,12 +1,14 @@
import { InvoicePaymentStatus } from '@vality/magista-proto';
export interface SearchFormParams {
fromTime: string;
toTime: string;
partyId: string;
fromRevision: string;
toRevision: string;
fromRevision: number;
toRevision: number;
providerID: string;
terminalID: string;
status: string;
status: InvoicePaymentStatus;
shopId: string;
invoiceIds: string[];
}

View File

@ -25,7 +25,7 @@
<mat-form-field fxFlex>
<mat-select formControlName="status" placeholder="Payment status">
<mat-option [value]="null">any</mat-option>
<mat-option *ngFor="let status of statuses" [value]="status">{{
<mat-option *ngFor="let status of statuses" [value]="statusEnum[status]">{{
status
}}</mat-option>
</mat-select>
@ -38,6 +38,7 @@
matInput
placeholder="From domain revision"
required
type="number"
/>
</mat-form-field>
<mat-form-field fxFlex>
@ -46,6 +47,7 @@
matInput
placeholder="To domain revision"
required
type="number"
/>
</mat-form-field>
<mat-form-field fxFlex>

View File

@ -1,9 +1,11 @@
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { InvoicePaymentStatus } from '@vality/magista-proto';
import * as moment from 'moment';
import { map } from 'rxjs/operators';
import { getEnumKeys } from '../../../../utils';
import { PaymentAdjustmentService } from '../payment-adjustment.service';
import { SearchFormParams } from './search-form-params';
import { toSearchParams } from './to-search-params';
@ -14,15 +16,13 @@ import { toSearchParams } from './to-search-params';
templateUrl: './search-form.component.html',
})
export class SearchFormComponent implements OnInit {
@Output()
valueChanges: EventEmitter<SearchFormParams> = new EventEmitter();
@Output()
statusChanges: EventEmitter<string> = new EventEmitter();
@Output() valueChanges = new EventEmitter<SearchFormParams>();
@Output() statusChanges = new EventEmitter<string>();
form: UntypedFormGroup;
statuses: string[] = ['pending', 'processed', 'captured', 'cancelled', 'refunded', 'failed'];
statuses = getEnumKeys(InvoicePaymentStatus);
statusEnum = InvoicePaymentStatus;
constructor(
private paymentAdjustmentService: PaymentAdjustmentService,
@ -34,7 +34,7 @@ export class SearchFormComponent implements OnInit {
fromTime: [moment(), Validators.required],
toTime: [moment(), Validators.required],
invoiceIds: '',
partyId: '',
partyId: ['', Validators.required],
shopId: '',
fromRevision: [0, Validators.required],
toRevision: ['', Validators.required],

View File

@ -30,7 +30,7 @@
</ng-container>
<ng-container matColumnDef="revision">
<th *matHeaderCellDef mat-header-cell>Revision</th>
<td *matCellDef="let payment" mat-cell>{{ payment.domain_revision | ccThriftInt64 }}</td>
<td *matCellDef="let payment" mat-cell>{{ payment.domain_revision }}</td>
</ng-container>
<ng-container matColumnDef="invoiceId">
<th *matHeaderCellDef mat-header-cell>Invoice ID</th>

View File

@ -12,9 +12,6 @@ import {
import { MatPaginator } from '@angular/material/paginator';
import { MatTableDataSource } from '@angular/material/table';
import { StatPayment } from '@vality/domain-proto/lib/merch_stat';
import { Int64 } from '@vality/thrift-ts';
import { i64ToNumber } from '@cc/utils/i64-to-number';
@Component({
selector: 'cc-payment-adjustment-table',
@ -48,13 +45,8 @@ export class TableComponent implements OnInit, OnChanges {
ngOnInit() {
this.selection.changed.subscribe((e) => this.changeSelected.emit(e.source.selected));
this.dataSource.filterPredicate = ({ domain_revision }, filter) => {
const num = i64ToNumber(
(domain_revision as unknown as Int64).buffer,
(domain_revision as unknown as Int64).offset
);
return filter === num.toString();
};
this.dataSource.filterPredicate = ({ domain_revision }, filter) =>
filter === domain_revision.toString();
this.dataSource.paginator = this.paginator;
}

View File

@ -1,4 +1,4 @@
<div fxLayout="column" fxLayoutGap="24px">
<div fxLayout="column" fxLayoutGap="24px" style="margin: 24px">
<cc-headline>Payment details</cc-headline>
<ng-container *ngIf="payment$ | async as payment">
<mat-card>
@ -14,6 +14,7 @@
<h2 class="cc-headline">Refunds</h2>
<cc-payment-refunds
[invoiceID]="payment.invoice_id"
[partyID]="payment.owner_id"
[paymentID]="payment.id"
></cc-payment-refunds>
</mat-card-content>

View File

@ -1,7 +1,6 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subject } from 'rxjs';
import { pluck, shareReplay } from 'rxjs/operators';
import { pluck } from 'rxjs/operators';
import { PaymentDetailsService } from './payment-details.service';
@ -11,11 +10,10 @@ import { PaymentDetailsService } from './payment-details.service';
providers: [PaymentDetailsService],
})
export class PaymentDetailsComponent {
partyID$ = this.route.params.pipe(pluck('partyID'), shareReplay(1));
partyID$ = this.route.params.pipe(pluck('partyID'));
payment$ = this.paymentDetailsService.payment$;
isLoading$ = this.paymentDetailsService.isLoading$;
shop$ = this.paymentDetailsService.shop$;
updateSearchParams$ = new Subject();
constructor(
private paymentDetailsService: PaymentDetailsService,

View File

@ -1,15 +1,14 @@
import { Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { ActivatedRoute } from '@angular/router';
import { cleanObject } from '@vality/ng-core';
import { combineLatest, of } from 'rxjs';
import { map, pluck, 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';
import { QueryDsl } from '../../query-dsl';
import { MerchantStatisticsService } from '../../thrift-services/damsel/merchant-statistics.service';
@Injectable()
export class PaymentDetailsService {
private partyID$ = this.route.params.pipe(pluck('partyID'), shareReplay(1));
@ -20,19 +19,21 @@ export class PaymentDetailsService {
payment$ = this.routeParams$.pipe(
switchMap(({ partyID, invoiceID, paymentID }) =>
this.merchantStatisticsService
.getPayments({
dsl: JSON.stringify({
query: {
payments: {
...(paymentID ? { payment_id: paymentID } : {}),
...(partyID ? { merchant_id: partyID } : {}),
...(invoiceID ? { invoice_id: invoiceID } : {}),
},
.SearchPayments(
cleanObject({
common_search_query_params: {
from_time: new Date('2020-01-01').toISOString(), // TODO
to_time: new Date().toISOString(),
party_id: partyID,
},
} as QueryDsl),
})
payment_params: {
payment_id: paymentID,
},
invoice_ids: [invoiceID],
})
)
.pipe(
map(({ data }) => data.payments[0]),
map(({ payments }) => payments[0]),
tap((payment) => {
if (!payment) {
this.snackBar.open('An error occurred when receiving payment', 'OK');

View File

@ -4,7 +4,7 @@ import {
FailureReason,
InvoicePaymentStatus,
SubFailure,
} from '@vality/domain-proto/lib/domain';
} from '@vality/magista-proto/lib/domain';
import { getUnionKey } from '@cc/utils/get-union-key';

View File

@ -1,7 +1,7 @@
<div fxLayout="column" fxLayoutGap="16px">
<div fxLayout fxLayoutGap="16px">
<cc-details-item fxFlex title="Amount"
>{{ payment.amount | ccThriftInt64 | ccFormatAmount }}
>{{ payment.amount | ccFormatAmount }}
{{ payment.currency_symbolic_code | ccCurrency }}</cc-details-item
>
<cc-details-item fxFlex title="Created At">{{

View File

@ -1,13 +1,9 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { Shop } from '@vality/domain-proto/lib/domain';
import {
InvoicePaymentStatus,
Payer,
PaymentTool,
StatPayment,
} from '@vality/domain-proto/lib/merch_stat';
import { Payer, StatPayment } from '@vality/magista-proto';
import { InvoicePaymentStatus, PaymentTool } from '@vality/magista-proto/lib/domain';
import { getUnionKey } from '@cc/utils/get-union-key';
import { getUnionKey } from '../../../../utils';
@Component({
selector: 'cc-payment-main-info',
@ -20,13 +16,13 @@ export class PaymentMainInfoComponent {
getPayerEmail(payer: Payer): string {
if (payer.customer) {
return payer.customer.email;
return payer.customer.contact_info.email;
}
if (payer.payment_resource) {
return payer.payment_resource.email;
return payer.payment_resource.contact_info.email;
}
if (payer.recurrent) {
return payer.recurrent.email;
return payer.recurrent.contact_info.email;
}
return undefined;
}
@ -34,7 +30,7 @@ export class PaymentMainInfoComponent {
getPaymentTool(payer: Payer): PaymentTool {
return (
payer?.customer?.payment_tool ||
payer?.payment_resource?.payment_tool ||
payer?.payment_resource?.resource?.payment_tool ||
payer?.recurrent?.payment_tool
);
}

View File

@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { BankCard } from '@vality/domain-proto/lib/merch_stat';
import { BankCard } from '@vality/magista-proto/lib/domain';
@Component({
selector: 'cc-bank-card',

View File

@ -1,5 +1,5 @@
import { Pipe, PipeTransform } from '@angular/core';
import { BankCard } from '@vality/domain-proto/lib/merch_stat';
import { BankCard } from '@vality/magista-proto/lib/domain';
@Pipe({
name: 'toCardNumber',
@ -11,4 +11,4 @@ export class ToCardNumberPipe implements PipeTransform {
}
export const toCardNumber = (card: BankCard): string =>
`${card.bin}******${card.masked_pan}`.replace(/(.{4})/g, '$& ');
`${card.bin}******${card.last_digits}`.replace(/(.{4})/g, '$& ');

View File

@ -1,6 +1,6 @@
<cc-bank-card *ngIf="paymentTool.bank_card" [bankCard]="paymentTool.bank_card"></cc-bank-card>
<div *ngIf="paymentTool.crypto_currency">
{{ paymentTool.crypto_currency?.crypto_currency?.id }}
{{ paymentTool.crypto_currency?.id }}
</div>
<div *ngIf="paymentTool.digital_wallet">
{{ paymentTool.digital_wallet?.id }}

View File

@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { PaymentTool } from '@vality/domain-proto/lib/merch_stat';
import { PaymentTool } from '@vality/magista-proto/lib/domain';
@Component({
selector: 'cc-payment-tool',

View File

@ -1,19 +1,21 @@
import { Injectable } from '@angular/core';
import { StatRefund } from '@vality/domain-proto/lib/merch_stat';
import { StatRefund, RefundSearchQuery } from '@vality/magista-proto';
import { cleanObject } from '@vality/ng-core';
import { Observable } from 'rxjs';
import { map, shareReplay } from 'rxjs/operators';
import { DeepPartial } from 'utility-types';
import { MerchantStatisticsService } from '@cc/app/api/magista';
import { FetchResult, PartialFetcher } from '@cc/app/shared/services';
import { booleanDelay } from '@cc/utils/boolean-delay';
import { QueryDsl } from '../../../query-dsl';
import { MerchantStatisticsService } from '../../../thrift-services/damsel/merchant-statistics.service';
import { RefundsSearchParams } from './refunds-search-params';
const SEARCH_LIMIT = 5;
@Injectable()
export class FetchRefundsService extends PartialFetcher<StatRefund, RefundsSearchParams> {
export class FetchRefundsService extends PartialFetcher<
StatRefund,
DeepPartial<RefundSearchQuery>
> {
isLoading$ = this.doAction$.pipe(booleanDelay(), shareReplay(1));
constructor(private merchantStatisticsService: MerchantStatisticsService) {
@ -21,49 +23,27 @@ export class FetchRefundsService extends PartialFetcher<StatRefund, RefundsSearc
}
protected fetch(
params: RefundsSearchParams,
params: DeepPartial<RefundSearchQuery>,
continuationToken: string
): Observable<FetchResult<StatRefund>> {
const {
invoiceID,
id,
paymentID,
ownerID,
shopID,
status,
createdAt,
amount,
fee,
externalID,
currencySymbolicCode,
} = params;
return this.merchantStatisticsService
.getStatistics({
dsl: JSON.stringify({
query: {
refunds: {
size: SEARCH_LIMIT.toString(),
...(invoiceID ? { invoice_id: invoiceID } : {}),
...(id ? { id } : {}),
...(paymentID ? { payment_id: paymentID } : {}),
...(ownerID ? { owner_id: ownerID } : {}),
...(shopID ? { shop_id: shopID } : {}),
...(status ? { status } : {}),
...(amount ? { amount } : {}),
...(createdAt ? { created_at: createdAt } : {}),
...(fee ? { fee } : {}),
...(externalID ? { external_id: externalID } : {}),
...(currencySymbolicCode
? { currency_symbolic_code: currencySymbolicCode }
: {}),
.SearchRefunds(
cleanObject({
...params,
common_search_query_params: Object.assign(
{
continuation_token: continuationToken,
limit: SEARCH_LIMIT,
from_time: new Date('01.01.2020').toISOString(), // TODO
to_time: new Date().toISOString(),
},
},
} as QueryDsl),
...(continuationToken ? { continuation_token: continuationToken } : {}),
})
params.common_search_query_params
),
})
)
.pipe(
map(({ data, continuation_token }) => ({
result: data.refunds,
map(({ refunds, continuation_token }) => ({
result: refunds,
continuationToken: continuation_token,
}))
);

View File

@ -1,6 +1,6 @@
import { Component, Input, OnInit } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { InvoiceID, InvoicePaymentID } from '@vality/domain-proto';
import { InvoiceID, InvoicePaymentID, PartyID } from '@vality/domain-proto';
import { FetchRefundsService } from './fetch-refunds.service';
@ -11,6 +11,7 @@ import { FetchRefundsService } from './fetch-refunds.service';
export class PaymentRefundsComponent implements OnInit {
@Input() paymentID: InvoicePaymentID;
@Input() invoiceID: InvoiceID;
@Input() partyID: PartyID;
doAction$ = this.fetchRefundsService.doAction$;
isLoading$ = this.fetchRefundsService.isLoading$;
@ -21,8 +22,9 @@ export class PaymentRefundsComponent implements OnInit {
ngOnInit() {
this.fetchRefundsService.search({
paymentID: this.paymentID,
invoiceID: this.invoiceID,
common_search_query_params: { party_id: this.partyID },
payment_id: this.paymentID,
invoice_ids: [this.invoiceID],
});
this.fetchRefundsService.errors$.subscribe((e) =>
this.snackBar.open(`An error occurred while search refunds (${String(e)})`, 'OK')

View File

@ -1,21 +0,0 @@
import {
InvoiceID,
InvoicePaymentID,
InvoicePaymentRefundID,
InvoicePaymentRefundStatus,
ShopID,
} from '@vality/domain-proto/lib/domain';
export interface RefundsSearchParams {
invoiceID?: InvoiceID;
id?: InvoicePaymentRefundID;
paymentID?: InvoicePaymentID;
ownerID?: string;
shopID?: ShopID;
status?: InvoicePaymentRefundStatus;
createdAt?: string;
amount?;
fee?;
externalID?;
currencySymbolicCode?;
}

View File

@ -14,9 +14,10 @@ import { MatTableModule } from '@angular/material/table';
import { MatTooltipModule } from '@angular/material/tooltip';
import { ActionsModule, BaseDialogModule } from '@vality/ng-core';
import { MetadataFormModule } from '@cc/app/shared/components/metadata-form';
import { EmptySearchResultModule } from '../../../components/empty-search-result';
import { TableModule } from '../../../components/table';
import { MetadataFormModule } from '../../shared';
import { DateRangeModule } from '../../shared/components/date-range/date-range.module';
import { RepairByScenarioDialogComponent } from './components/repair-by-scenario-dialog/repair-by-scenario-dialog.component';
import { RepairingRoutingModule } from './repairing-routing.module';

View File

@ -13,7 +13,7 @@ import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select';
import { BaseDialogModule } from '@vality/ng-core';
import { MetadataFormModule } from '@cc/app/shared';
import { MetadataFormModule } from '@cc/app/shared/components/metadata-form';
import { AddRoutingRuleDialogComponent } from './add-routing-rule-dialog.component';
import { ExpanderComponent } from './expander';

View File

@ -1,7 +1,6 @@
<div fxLayout="column" fxLayoutGap="24px">
<cc-payments-searcher
[initSearchParams]="initsearchParams$ | async"
[type]="searchType"
(paymentEventFired$)="paymentEventFired($event)"
(searchParamsChanged$)="searchParamsUpdated($event)"
></cc-payments-searcher>

View File

@ -4,9 +4,7 @@ import { Router } from '@angular/router';
import {
PaymentActions,
PaymentMenuItemEvent,
SearcherType,
SearchFiltersParams,
SearchType,
} from '@cc/app/shared/components';
import { SearchPaymentsService } from './search-payments.service';
@ -17,10 +15,6 @@ import { SearchPaymentsService } from './search-payments.service';
providers: [SearchPaymentsService],
})
export class SearchPaymentsComponent {
searchType: SearcherType = {
type: SearchType.GlobalSearcher,
};
initsearchParams$ = this.searchPaymentsService.data$;
constructor(private searchPaymentsService: SearchPaymentsService, private router: Router) {}

View File

@ -13,7 +13,6 @@ import {
} from '@cc/app/shared/components';
import { EmptySearchResultModule } from '@cc/components/empty-search-result';
import { PartyPaymentsModule } from '../party-payments';
import { SearchPaymentsRoutingModule } from './search-payments-routing.module';
import { SearchPaymentsComponent } from './search-payments.component';
@ -29,7 +28,6 @@ import { SearchPaymentsComponent } from './search-payments.component';
EmptySearchResultModule,
PaymentsTableModule,
MatButtonModule,
PartyPaymentsModule,
PaymentsSearcherModule,
],
declarations: [SearchPaymentsComponent],

View File

@ -6,6 +6,10 @@ const ROUTES: Routes = [
path: 'party',
loadChildren: () => import('./party/party.module').then((m) => m.PartyModule),
},
{
path: 'party/:partyID/invoice/:invoiceID/payment/:paymentID',
loadChildren: () => import('./payment-details').then((m) => m.PaymentDetailsModule),
},
{
path: 'withdrawals',
loadChildren: () =>

View File

@ -16,9 +16,10 @@ import { BaseDialogModule } from '@vality/ng-core';
import { EmptySearchResultModule } from '../../../components/empty-search-result';
import { TableModule } from '../../../components/table';
import { MetadataFormModule, ThriftPipesModule } from '../../shared';
import { DateRangeModule } from '../../shared/components/date-range/date-range.module';
import { MerchantFieldModule } from '../../shared/components/merchant-field';
import { MetadataFormModule } from '../../shared/components/metadata-form';
import { ThriftPipesModule } from '../../shared/pipes/thrift';
import { CreateAdjustmentDialogComponent } from './components/create-adjustment-dialog/create-adjustment-dialog.component';
import { WithdrawalsRoutingModule } from './withdrawals-routing.module';
import { WithdrawalsComponent } from './withdrawals.component';

View File

@ -1,5 +1,5 @@
export * from './metadata-form.module';
export * from './types/metadata-form-data';
export { MetadataFormExtension } from '@cc/app/shared/components/metadata-form/types/metadata-form-extension';
export { MetadataFormExtensionResult } from '@cc/app/shared/components/metadata-form/types/metadata-form-extension';
export { MetadataFormExtensionOption } from '@cc/app/shared/components/metadata-form/types/metadata-form-extension';
export { MetadataFormExtension } from './types/metadata-form-extension';
export { MetadataFormExtensionResult } from './types/metadata-form-extension';
export { MetadataFormExtensionOption } from './types/metadata-form-extension';

View File

@ -3,10 +3,10 @@ import { Validator } from '@angular/forms';
import { Field, ValueType } from '@vality/thrift-ts';
import { ThriftAstMetadata } from '@cc/app/api/utils';
import { MetadataFormExtension } from '@cc/app/shared/components/metadata-form/types/metadata-form-extension';
import { createControlProviders, ValidatedFormControlSuperclass } from '@cc/utils';
import { MetadataFormData } from './types/metadata-form-data';
import { MetadataFormExtension } from './types/metadata-form-extension';
@Component({
selector: 'cc-metadata-form',

View File

@ -15,8 +15,9 @@ import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select';
import { MatTooltipModule } from '@angular/material/tooltip';
import { ThriftPipesModule, ValueTypeTitleModule } from '@cc/app/shared';
import { JsonViewerModule } from '@cc/app/shared/components/json-viewer';
import { ThriftPipesModule } from '@cc/app/shared/pipes/thrift';
import { ValueTypeTitleModule } from '@cc/app/shared/pipes/value-type-title';
import { ComplexFormComponent } from './components/complex-form/complex-form.component';
import { EnumFieldComponent } from './components/enum-field/enum-field.component';

View File

@ -1,7 +1,7 @@
import { ThemePalette } from '@angular/material/core';
import { Observable } from 'rxjs';
import { MetadataFormData } from '@cc/app/shared';
import { MetadataFormData } from './metadata-form-data';
export interface MetadataFormExtensionOption {
value: unknown;

View File

@ -2,7 +2,8 @@ import { ValueType } from '@vality/thrift-ts';
import { TypeDefs } from '@vality/thrift-ts/src/thrift-parser';
import { ThriftAstMetadata } from '@cc/app/api/utils';
import { MetadataFormData, TypeGroup } from '@cc/app/shared';
import { MetadataFormData, TypeGroup } from '../types/metadata-form-data';
export function getDefaultValue(metadata: ThriftAstMetadata[], namespace: string, type: ValueType) {
let data: MetadataFormData;

View File

@ -1,3 +1,2 @@
export * from './payments-main-search-filters.module';
export * from './payments-main-search-filters.component';
export * from './main-filter-search-type';

View File

@ -1,11 +0,0 @@
import { PartyID } from '@vality/domain-proto';
export enum MainSearchType {
PartySearchFilter = 'PartySearchFilter',
GlobalSearchFilter = 'GlobalSearchFilter',
}
export interface MainFilterSearchType {
type: MainSearchType;
partyID?: PartyID;
}

View File

@ -1,66 +1,63 @@
<form [formGroup]="form" fxLayout="column" fxLayoutGap="16px">
<div fxFlexFill fxLayout fxLayoutGap="16px">
<mat-form-field fxFlex>
<mat-label>Date Range</mat-label>
<mat-date-range-input [rangePicker]="picker">
<input formControlName="fromTime" matInput matStartDate placeholder="Start Date" />
<input formControlName="toTime" matEndDate matInput placeholder="End Date" />
</mat-date-range-input>
<mat-datepicker-toggle [for]="picker" matSuffix>
<mat-icon matDatepickerToggleIcon>keyboard_arrow_down</mat-icon>
</mat-datepicker-toggle>
<mat-date-range-picker #picker></mat-date-range-picker>
</mat-form-field>
<mat-form-field fxFlex>
<input
autocomplete="false"
formControlName="invoiceID"
matInput
placeholder="Invoice ID"
type="string"
/>
</mat-form-field>
<cc-merchant-searcher
*ngIf="type.type === mainSearchType.GlobalSearchFilter"
formControlName="partyID"
fxFlex
></cc-merchant-searcher>
<mat-form-field *ngIf="type.type === mainSearchType.PartySearchFilter" fxFlex>
<mat-label>Shops</mat-label>
<mat-select class="select" formControlName="shopIDs" multiple>
<mat-option *ngFor="let shop of shops$ | async" [value]="shop.id">
{{ shop.details.name }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div fxFlexFill fxLayout fxLayoutGap="16px">
<mat-form-field fxFlex>
<input
autocomplete="false"
formControlName="bin"
matInput
placeholder="Card Bin"
type="string"
/>
</mat-form-field>
<mat-form-field fxFlex>
<input
autocomplete="false"
formControlName="pan"
matInput
placeholder="Card Pan"
type="string"
/>
</mat-form-field>
<mat-form-field fxFlex>
<input
autocomplete="false"
formControlName="rrn"
matInput
placeholder="Payment RRN"
type="string"
/>
</mat-form-field>
</div>
</form>
<div [formGroup]="form" gdColumns="1fr 1fr 1fr 1fr" gdGap="16px">
<mat-form-field>
<mat-label>Date Range</mat-label>
<mat-date-range-input [rangePicker]="picker">
<input formControlName="fromTime" matInput matStartDate placeholder="Start Date" />
<input formControlName="toTime" matEndDate matInput placeholder="End Date" />
</mat-date-range-input>
<mat-datepicker-toggle [for]="picker" matSuffix>
<mat-icon matDatepickerToggleIcon>keyboard_arrow_down</mat-icon>
</mat-datepicker-toggle>
<mat-date-range-picker #picker></mat-date-range-picker>
</mat-form-field>
<mat-form-field>
<input
autocomplete="false"
formControlName="invoiceID"
matInput
placeholder="Invoice ID"
type="string"
/>
</mat-form-field>
<cc-merchant-field formControlName="partyID"></cc-merchant-field>
<mat-form-field>
<mat-label>Shops</mat-label>
<mat-select
[disabled]="!form.value['partyID']"
class="select"
formControlName="shopIDs"
multiple
>
<mat-option *ngFor="let shop of shops$ | async" [value]="shop.id">
{{ shop.details.name }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<input
autocomplete="false"
formControlName="bin"
matInput
placeholder="Card Bin"
type="string"
/>
</mat-form-field>
<mat-form-field>
<input
autocomplete="false"
formControlName="pan"
matInput
placeholder="Card Pan"
type="string"
/>
</mat-form-field>
<mat-form-field>
<input
autocomplete="false"
formControlName="rrn"
matInput
placeholder="Payment RRN"
type="string"
/>
</mat-form-field>
</div>

View File

@ -6,48 +6,24 @@ import {
OnInit,
Output,
} from '@angular/core';
import { MAT_DATE_FORMATS } from '@angular/material/core';
import { untilDestroyed, UntilDestroy } from '@ngneat/until-destroy';
import { filter } from 'rxjs/operators';
import { SearchFiltersParams } from '../search-filters-params';
import { MainFilterSearchType, MainSearchType } from './main-filter-search-type';
import { PaymentsMainSearchFiltersService } from './payments-main-search-filters.service';
export const MY_FORMATS = {
parse: {
dateInput: ['l', 'LL'],
},
display: {
dateInput: 'DD.MM.YYYY',
monthYearLabel: 'DD.MM.YYYY',
dateA11yLabel: 'DD.MM.YYYY',
monthYearA11yLabel: 'DD.MM.YYYY',
},
};
@UntilDestroy()
@Component({
selector: 'cc-payments-main-search-filters',
templateUrl: 'payments-main-search-filters.component.html',
styleUrls: ['payments-main-search-filters.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
PaymentsMainSearchFiltersService,
{ provide: MAT_DATE_FORMATS, useValue: MY_FORMATS },
],
providers: [PaymentsMainSearchFiltersService],
})
export class PaymentsMainSearchFiltersComponent implements OnInit {
@Input()
initParams: SearchFiltersParams;
@Input()
type: MainFilterSearchType;
@Output()
valueChanges = new EventEmitter<SearchFiltersParams>();
mainSearchType = MainSearchType;
@Input() initParams: SearchFiltersParams;
@Output() valueChanges = new EventEmitter<SearchFiltersParams>();
shops$ = this.paymentsMainSearchFiltersService.shops$;
form = this.paymentsMainSearchFiltersService.form;
constructor(private paymentsMainSearchFiltersService: PaymentsMainSearchFiltersService) {
@ -58,8 +34,10 @@ export class PaymentsMainSearchFiltersComponent implements OnInit {
ngOnInit() {
this.paymentsMainSearchFiltersService.init(this.initParams);
if (this.type.type === MainSearchType.PartySearchFilter) {
this.paymentsMainSearchFiltersService.getShops(this.type.partyID);
}
this.form.controls.partyID.valueChanges
.pipe(filter(Boolean), untilDestroyed(this))
.subscribe((partyID: string) => {
this.paymentsMainSearchFiltersService.getShops(partyID);
});
}
}

View File

@ -12,6 +12,7 @@ import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MerchantFieldModule } from '../../merchant-field';
import { MerchantSearcherModule } from '../../merchant-searcher';
import { PaymentsMainSearchFiltersComponent } from './payments-main-search-filters.component';
@ -30,6 +31,7 @@ import { PaymentsMainSearchFiltersComponent } from './payments-main-search-filte
FlexLayoutModule,
MatSelectModule,
MerchantSearcherModule,
MerchantFieldModule,
],
declarations: [PaymentsMainSearchFiltersComponent],
exports: [PaymentsMainSearchFiltersComponent],

View File

@ -4,10 +4,6 @@ import { SearchFiltersParams } from '../search-filters-params';
export const formParamsToSearchParams = (params: SearchFiltersParams): SearchFiltersParams => ({
...params,
...(params.paymentAmountFrom
? { paymentAmountFrom: amountToMinor(params.paymentAmountFrom) }
: {}),
...(params.paymentAmountTo ? { paymentAmountTo: amountToMinor(params.paymentAmountTo) } : {}),
...(params.paymentAmountFrom ? { paymentAmountFrom: toMinor(params.paymentAmountFrom) } : {}),
...(params.paymentAmountTo ? { paymentAmountTo: toMinor(params.paymentAmountTo) } : {}),
});
const amountToMinor = (amount: string): string => toMinor(Number(amount)).toString();

View File

@ -1,109 +0,0 @@
import { RadioButtonObject } from '@cc/components/utils';
export const PAYMENT_STATUSES: RadioButtonObject[] = [
{
value: 'captured',
title: 'Captured',
},
{
value: 'failed',
title: 'Failed',
},
{
value: 'pending',
title: 'Pending',
},
{
value: 'processed',
title: 'Processed',
},
{
value: 'cancelled',
title: 'Cancelled',
},
{
value: 'refunded',
title: 'Refunded',
},
];
export const PAYMENT_METHODS: RadioButtonObject[] = [
{
value: 'bank_card',
title: 'Bank Card',
},
{
value: 'terminal',
title: 'Terminal',
},
{
value: 'mobile',
title: 'Mobile Commerce',
},
];
export const TOKEN_PROVIDERS: RadioButtonObject[] = [
{
value: 'applepay',
title: 'Apple Pay',
},
{
value: 'googlepay',
title: 'Google Pay',
},
{
value: 'samsungpay',
title: 'Mobile Commerce',
},
];
export const PAYMENT_SYSTEMS: RadioButtonObject[] = [
{
value: 'visa',
title: 'Visa',
},
{
value: 'mastercard',
title: 'Mastercard',
},
{
value: 'visaelectron',
title: 'Visa Electron',
},
{
value: 'maestro',
title: 'Maestro',
},
{
value: 'forbrugsforeningen',
title: 'Forbrugsforeningen',
},
{
value: 'dankort',
title: 'Dankort',
},
{
value: 'amex',
title: 'Amex',
},
{
value: 'dinersclub',
title: 'Dinersclub',
},
{
value: 'discover',
title: 'Discover',
},
{
value: 'unionpay',
title: 'UnionPay',
},
{
value: 'jcb',
title: 'JCB',
},
{
value: 'nspkmir',
title: 'NSPK Mir',
},
];

View File

@ -1,28 +1,53 @@
<div fxLayout="column" fxLayoutGap="32px">
<h1 class="mat-headline">Other filters</h1>
<mat-dialog-content [formGroup]="form" fxLayout="column" fxLayoutGap="24px">
<div fxLayout="column" fxLayoutGap="16px">
<div fxLayout fxLayoutGap="16px">
<mat-form-field fxFlex>
<input formControlName="payerEmail" matInput placeholder="Payer email" />
</mat-form-field>
<mat-form-field fxFlex>
<input formControlName="terminalID" matInput placeholder="Terminal ID" />
</mat-form-field>
</div>
<div fxLayout fxLayoutGap="16px">
<mat-form-field fxFlex>
<input formControlName="providerID" matInput placeholder="Provider ID" />
</mat-form-field>
<div fxFlex></div>
</div>
<cc-base-dialog title="Other filters">
<div [formGroup]="form" fxLayout="column" fxLayoutGap="24px">
<div fxLayout fxLayoutGap="16px">
<mat-form-field fxFlex>
<input formControlName="payerEmail" matInput placeholder="Payer email" />
</mat-form-field>
<mat-form-field fxFlex>
<input formControlName="terminalID" matInput placeholder="Terminal ID" />
</mat-form-field>
</div>
<mat-divider></mat-divider>
<h2 class="mat-title">Payment Status</h2>
<cc-expandable-radio-group
[values]="paymentStatuses"
formControlName="paymentStatus"
></cc-expandable-radio-group>
<mat-form-field fxFlex>
<input formControlName="providerID" matInput placeholder="Provider ID" />
</mat-form-field>
<mat-form-field>
<mat-select formControlName="paymentStatus" placeholder="Payment Status">
<mat-option [value]="null">any</mat-option>
<mat-option *ngFor="let s of paymentStatuses" [value]="s.value">
{{ s.key }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-select formControlName="paymentMethod" placeholder="Payment Method">
<mat-option [value]="null">any</mat-option>
<mat-option *ngFor="let pm of paymentMethods" [value]="pm.value">
{{ pm.key }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-select formControlName="tokenProvider" placeholder="Token Provider">
<mat-option [value]="null">any</mat-option>
<mat-option *ngFor="let p of tokenProviders$ | async" [value]="p.ref.id">
{{ p.data.name }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-select formControlName="paymentSystem" placeholder="Payment System">
<mat-option [value]="null">any</mat-option>
<mat-option *ngFor="let ps of paymentSystems$ | async" [value]="ps.ref.id">
{{ ps.data.name }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-divider></mat-divider>
<h2 class="mat-title">Domain Revision</h2>
<div fxLayout fxLayoutGap="16px">
@ -46,27 +71,8 @@
<input formControlName="paymentAmountTo" matInput placeholder="To" />
</mat-form-field>
</div>
<mat-divider></mat-divider>
<h2 class="mat-title">Payment Method</h2>
<cc-expandable-radio-group
[values]="paymentMethods"
formControlName="paymentMethod"
></cc-expandable-radio-group>
<mat-divider></mat-divider>
<h2 class="mat-title">Token Provider</h2>
<cc-expandable-radio-group
[values]="tokenProviders"
formControlName="tokenProvider"
></cc-expandable-radio-group>
<mat-divider></mat-divider>
<h2 class="mat-title">Payment System</h2>
<cc-expandable-radio-group
[values]="paymentSystems"
formControlName="paymentSystem"
></cc-expandable-radio-group>
</mat-dialog-content>
<mat-dialog-actions fxLayout fxLayoutAlign="space-between">
<button mat-button (click)="cancel()">CANCEL</button>
</div>
<cc-base-dialog-actions>
<button [disabled]="form.invalid" color="primary" mat-button (click)="save()">SAVE</button>
</mat-dialog-actions>
</div>
</cc-base-dialog-actions>
</cc-base-dialog>

View File

@ -1,38 +1,46 @@
import { ChangeDetectionStrategy, Component, Inject, OnInit } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { ChangeDetectionStrategy, Component, OnInit, Injector } from '@angular/core';
import { PaymentToolType, InvoicePaymentStatus } from '@vality/magista-proto';
import { BaseDialogSuperclass } from '@vality/ng-core';
import { getEnumKeyValues } from '../../../../../../utils';
import { DomainStoreService } from '../../../../../thrift-services/damsel/domain-store.service';
import { SearchFiltersParams } from '../../search-filters-params';
import { PAYMENT_METHODS, PAYMENT_STATUSES, PAYMENT_SYSTEMS, TOKEN_PROVIDERS } from './constants';
import { OtherFiltersDialogService } from './other-filters-dialog.service';
@Component({
templateUrl: 'other-filters-dialog.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [OtherFiltersDialogService],
})
export class OtherFiltersDialogComponent implements OnInit {
paymentStatuses = PAYMENT_STATUSES;
paymentMethods = PAYMENT_METHODS;
tokenProviders = TOKEN_PROVIDERS;
paymentSystems = PAYMENT_SYSTEMS;
export class OtherFiltersDialogComponent
extends BaseDialogSuperclass<
OtherFiltersDialogComponent,
SearchFiltersParams,
SearchFiltersParams
>
implements OnInit
{
paymentStatuses = getEnumKeyValues(InvoicePaymentStatus);
paymentMethods = getEnumKeyValues(PaymentToolType);
tokenProviders$ = this.domainStoreService.getObjects('payment_token');
paymentSystems$ = this.domainStoreService.getObjects('payment_system');
currentDomainVersion$ = this.paymentsOtherSearchFiltersService.currentDomainVersion$;
form = this.paymentsOtherSearchFiltersService.form;
constructor(
private dialogRef: MatDialogRef<OtherFiltersDialogComponent>,
injector: Injector,
private paymentsOtherSearchFiltersService: OtherFiltersDialogService,
@Inject(MAT_DIALOG_DATA) public initParams: SearchFiltersParams
) {}
ngOnInit() {
this.form.patchValue(this.initParams);
private domainStoreService: DomainStoreService
) {
super(injector);
}
cancel() {
this.dialogRef.close();
ngOnInit() {
this.form.patchValue(this.dialogData);
}
save() {
this.dialogRef.close(this.form.value);
this.closeWithSuccess(this.form.value);
}
}

View File

@ -10,11 +10,10 @@ import { MatDividerModule } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { ExpandableRadioGroupModule } from '@cc/components/expandable-radio-group';
import { MatSelectModule } from '@angular/material/select';
import { BaseDialogModule } from '@vality/ng-core';
import { OtherFiltersDialogComponent } from './other-filters-dialog.component';
import { OtherFiltersDialogService } from './other-filters-dialog.service';
@NgModule({
imports: [
@ -29,10 +28,10 @@ import { OtherFiltersDialogService } from './other-filters-dialog.service';
MatDialogModule,
MatDividerModule,
FlexLayoutModule,
ExpandableRadioGroupModule,
MatSelectModule,
BaseDialogModule,
],
declarations: [OtherFiltersDialogComponent],
exports: [OtherFiltersDialogComponent],
providers: [OtherFiltersDialogService],
})
export class OtherFiltersDialogModule {}

View File

@ -1,11 +1,11 @@
import { Injectable } from '@angular/core';
import { UntypedFormBuilder, Validators } from '@angular/forms';
import { DomainService } from '../../../../../domain';
import { DomainStoreService } from '../../../../../thrift-services/damsel/domain-store.service';
@Injectable()
export class OtherFiltersDialogService {
currentDomainVersion$ = this.domainService.version$;
currentDomainVersion$ = this.domainStoreService.version$;
form = this.fb.group({
payerEmail: ['', [Validators.email]],
@ -18,8 +18,8 @@ export class OtherFiltersDialogService {
paymentAmountTo: '',
paymentMethod: null,
tokenProvider: null,
paymentSystemIs: null,
paymentSystem: null,
});
constructor(private fb: UntypedFormBuilder, private domainService: DomainService) {}
constructor(private fb: UntypedFormBuilder, private domainStoreService: DomainStoreService) {}
}

View File

@ -11,8 +11,6 @@ import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { ExpandableRadioGroupModule } from '@cc/components/expandable-radio-group';
import { OtherFiltersDialogModule } from './other-filters-dialog';
import { PaymentsOtherSearchFiltersComponent } from './payments-other-search-filters.component';
@ -29,7 +27,6 @@ import { PaymentsOtherSearchFiltersComponent } from './payments-other-search-fil
MatDialogModule,
MatDividerModule,
FlexLayoutModule,
ExpandableRadioGroupModule,
OtherFiltersDialogModule,
],
declarations: [PaymentsOtherSearchFiltersComponent],

View File

@ -1,7 +1,8 @@
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Observable, ReplaySubject, Subject } from 'rxjs';
import { filter, map, shareReplay, switchMap, take } from 'rxjs/operators';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { BaseDialogService, BaseDialogResponseStatus } from '@vality/ng-core';
import { ReplaySubject } from 'rxjs';
import { filter, map, shareReplay, switchMap, first } from 'rxjs/operators';
import { removeEmptyProperties } from '@cc/utils/remove-empty-properties';
@ -11,11 +12,10 @@ import { OtherFiltersDialogComponent } from './other-filters-dialog';
import { searchParamsToFormParams } from './search-params-to-form-params';
import { toFiltersCount } from './to-filters-count';
@UntilDestroy()
@Injectable()
export class PaymentsOtherSearchFiltersService {
private openFiltersDialog$ = new Subject<Observable<SearchFiltersParams> | void>();
private formParams = new ReplaySubject<SearchFiltersParams>();
private formParams = new ReplaySubject<SearchFiltersParams>(1);
private countableKeys = [
'payerEmail',
@ -41,29 +41,22 @@ export class PaymentsOtherSearchFiltersService {
shareReplay(1)
);
constructor(private dialog: MatDialog) {
this.openFiltersDialog$
.pipe(
switchMap(() => this.formParams.pipe(shareReplay(1), take(1))),
switchMap((formParams) =>
this.dialog
.open(OtherFiltersDialogComponent, {
disableClose: true,
width: '552px',
data: formParams,
})
.afterClosed()
),
filter((v) => !!v)
)
.subscribe((params) => this.formParams.next(params));
}
constructor(private baseDialogService: BaseDialogService) {}
init(params: SearchFiltersParams) {
this.formParams.next(searchParamsToFormParams(params));
}
openOtherFiltersDialog() {
this.openFiltersDialog$.next();
this.formParams
.pipe(
first(),
switchMap((data) =>
this.baseDialogService.open(OtherFiltersDialogComponent, data).afterClosed()
),
filter(({ status }) => status === BaseDialogResponseStatus.Success),
untilDestroyed(this)
)
.subscribe(({ data }) => this.formParams.next(data));
}
}

View File

@ -4,10 +4,6 @@ import { SearchFiltersParams } from '../search-filters-params';
export const searchParamsToFormParams = (params: SearchFiltersParams): SearchFiltersParams => ({
...params,
...(params.paymentAmountFrom
? { paymentAmountFrom: amountToMajor(params.paymentAmountFrom) }
: {}),
...(params.paymentAmountTo ? { paymentAmountTo: amountToMajor(params.paymentAmountTo) } : {}),
...(params.paymentAmountFrom ? { paymentAmountFrom: toMajor(params.paymentAmountFrom) } : {}),
...(params.paymentAmountTo ? { paymentAmountTo: toMajor(params.paymentAmountTo) } : {}),
});
const amountToMajor = (amount: string): string => toMajor(Number(amount)).toString();

View File

@ -1,22 +1,23 @@
import { InvoicePaymentStatus, PaymentToolType } from '@vality/magista-proto';
export interface SearchFiltersParams {
partyID?: string;
fromTime?: string;
toTime?: string;
invoiceID?: string;
shopID?: string;
shopIDs?: string[];
payerEmail?: string;
terminalID?: string;
providerID?: string;
rrn?: string;
paymentMethod?: string;
paymentMethod?: PaymentToolType;
paymentSystem?: string;
tokenProvider?: string;
bin?: string;
pan?: string;
domainRevisionFrom?: string;
domainRevisionTo?: string;
paymentAmountFrom?: string;
paymentAmountTo?: string;
paymentStatus?: string;
domainRevisionFrom?: number;
domainRevisionTo?: number;
paymentAmountFrom?: number;
paymentAmountTo?: number;
paymentStatus?: InvoicePaymentStatus;
}

View File

@ -1,21 +1,20 @@
import { Injectable } from '@angular/core';
import { StatPayment } from '@vality/domain-proto/lib/merch_stat';
import { StatPayment } from '@vality/magista-proto';
import { cleanObject } from '@vality/ng-core';
import * as moment from 'moment';
import { Observable } from 'rxjs';
import { map, shareReplay } from 'rxjs/operators';
import { map } from 'rxjs/operators';
import { MerchantStatisticsService } from '@cc/app/api/magista';
import { FetchResult, PartialFetcher } from '@cc/app/shared/services';
import { booleanDelay } from '@cc/utils/boolean-delay';
import { QueryDsl } from '../../../query-dsl';
import { MerchantStatisticsService } from '../../../thrift-services/damsel/merchant-statistics.service';
import { SearchFiltersParams } from '../payments-search-filters/search-filters-params';
import { SearchFiltersParams } from '../payments-search-filters';
const SEARCH_LIMIT = 10;
@Injectable()
export class FetchPaymentsService extends PartialFetcher<StatPayment, SearchFiltersParams> {
isLoading$ = this.doAction$.pipe(booleanDelay(), shareReplay(1));
isLoading$ = this.doAction$;
constructor(private merchantStatisticsService: MerchantStatisticsService) {
super();
@ -30,7 +29,6 @@ export class FetchPaymentsService extends PartialFetcher<StatPayment, SearchFilt
fromTime,
toTime,
invoiceID,
shopID,
shopIDs,
payerEmail,
terminalID,
@ -48,45 +46,41 @@ export class FetchPaymentsService extends PartialFetcher<StatPayment, SearchFilt
paymentStatus,
} = params;
return this.merchantStatisticsService
.getPayments({
dsl: JSON.stringify({
query: {
payments: {
.SearchPayments(
cleanObject(
{
common_search_query_params: {
from_time: moment(fromTime).utc().format(),
to_time: moment(toTime).utc().format(),
size: SEARCH_LIMIT.toString(),
...(partyID ? { merchant_id: partyID } : {}),
...(shopID ? { shop_id: shopID } : {}),
...(shopIDs?.length ? { shop_ids: shopIDs } : {}),
...(domainRevisionFrom
? { from_payment_domain_revision: domainRevisionFrom }
: {}),
...(domainRevisionTo
? { to_payment_domain_revision: domainRevisionTo }
: {}),
...(paymentAmountFrom
? { payment_amount_from: paymentAmountFrom }
: {}),
...(paymentAmountTo ? { payment_amount_to: paymentAmountTo } : {}),
...(providerID ? { payment_provider_id: providerID } : {}),
...(terminalID ? { payment_terminal_id: terminalID } : {}),
...(paymentStatus ? { payment_status: paymentStatus } : {}),
...(invoiceID ? { invoice_id: invoiceID } : {}),
...(payerEmail ? { payment_email: payerEmail } : {}),
...(rrn ? { payment_rrn: rrn } : {}),
...(paymentSystem ? { payment_system: paymentSystem } : {}),
...(paymentMethod ? { payment_method: paymentMethod } : {}),
...(tokenProvider ? { payment_token_provider: tokenProvider } : {}),
...(bin ? { payment_first6: bin } : {}),
...(pan ? { payment_last4: pan } : {}),
limit: SEARCH_LIMIT,
continuation_token: continuationToken,
party_id: partyID,
shop_ids: shopIDs,
},
invoice_ids: [invoiceID],
payment_params: {
payment_status: paymentStatus,
payment_tool: paymentMethod,
payment_email: payerEmail,
payment_first6: bin,
payment_system: { id: paymentSystem },
payment_last4: pan,
payment_provider_id: providerID,
payment_terminal_id: terminalID,
from_payment_domain_revision: domainRevisionFrom,
to_payment_domain_revision: domainRevisionTo,
payment_rrn: rrn,
payment_amount_from: paymentAmountFrom,
payment_amount_to: paymentAmountTo,
payment_token_provider: { id: tokenProvider },
},
},
} as QueryDsl),
...(continuationToken ? { continuation_token: continuationToken } : {}),
})
['common_search_query_params', 'payment_params']
)
)
.pipe(
map(({ data, continuation_token }) => ({
result: data.payments,
map(({ payments, continuation_token }) => ({
result: payments,
continuationToken: continuation_token,
}))
);

View File

@ -1,2 +1 @@
export * from './payments-searcher.module';
export * from './searcher-type';

View File

@ -1,43 +1,41 @@
<div fxLayout="column" fxLayoutGap="24px">
<mat-card>
<mat-card-content fxLayout>
<cc-payments-main-search-filters
[initParams]="initSearchParams"
[type]="mainFilterSearchType"
fxFlex="75"
(valueChanges)="searchParamsChanges($event)"
></cc-payments-main-search-filters>
<cc-payments-other-search-filters
[initParams]="initSearchParams"
class="other-filters-button"
fxFlex
fxLayoutAlign="center start"
(valueChanges)="searchParamsChanges($event)"
></cc-payments-other-search-filters>
</mat-card-content>
<cc-payments-main-search-filters
[initParams]="initSearchParams"
(valueChanges)="searchParamsChanges($event)"
></cc-payments-main-search-filters>
<mat-card-footer *ngIf="doAction$ | async">
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</mat-card-footer>
</mat-card>
<ng-container *ngIf="payments$ | async as payments">
<cc-empty-search-result
*ngIf="!(isLoading$ | async) && payments.length === 0"
></cc-empty-search-result>
<mat-card *ngIf="payments.length > 0" fxLayout="column" fxLayoutGap="16px">
<cc-payments-table
[payments]="payments"
[type]="tableType"
(menuItemSelected$)="paymentMenuItemSelected($event)"
></cc-payments-table>
<button
*ngIf="hasMore$ | async"
[disabled]="doAction$ | async"
fxFlex="100"
mat-button
(click)="fetchMore()"
>
{{ (doAction$ | async) ? 'LOADING...' : 'SHOW MORE' }}
</button>
</mat-card>
</ng-container>
<cc-actions>
<cc-payments-other-search-filters
[initParams]="initSearchParams"
(valueChanges)="searchParamsChanges($event)"
></cc-payments-other-search-filters>
</cc-actions>
<!-- TODO: Remove params (and params?.partyID validation) when backend starts working without merchant -->
<cc-empty-search-result
*ngIf="(!(isLoading$ | async) && (payments$ | async)?.length === 0) || !params?.partyID"
[label]="params?.partyID ? 'Payments not found' : 'Merchant input field is required'"
></cc-empty-search-result>
<mat-card
*ngIf="(payments$ | async)?.length > 0 && params?.partyID"
fxLayout="column"
fxLayoutGap="16px"
>
<cc-payments-table
[payments]="payments$ | async"
(menuItemSelected$)="paymentMenuItemSelected($event)"
></cc-payments-table>
<button
*ngIf="hasMore$ | async"
[disabled]="doAction$ | async"
fxFlex="100"
mat-button
(click)="fetchMore()"
>
{{ (doAction$ | async) ? 'LOADING...' : 'SHOW MORE' }}
</button>
</mat-card>
</div>

View File

@ -1,3 +0,0 @@
.other-filters-button {
padding-top: 16px;
}

View File

@ -7,94 +7,46 @@ import {
Output,
} from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { untilDestroyed, UntilDestroy } from '@ngneat/until-destroy';
import {
MainFilterSearchType,
MainSearchType,
SearchFiltersParams,
} from '../payments-search-filters';
import {
PaymentActions,
PaymentMenuItemEvent,
PaymentsTableType,
TableType,
} from '../payments-table';
import { SearchFiltersParams } from '../payments-search-filters';
import { PaymentActions, PaymentMenuItemEvent } from '../payments-table';
import { FetchPaymentsService } from './fetch-payments.service';
import { PaymentsSearcherService } from './payments-searcher.service';
import { SearcherType, SearchType } from './searcher-type';
@UntilDestroy()
@Component({
selector: 'cc-payments-searcher',
templateUrl: 'payments-searcher.component.html',
styleUrls: ['payments-searcher.component.scss'],
providers: [FetchPaymentsService, PaymentsSearcherService],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PaymentsSearcherComponent implements OnInit {
private searcherType: SearcherType;
@Input() initSearchParams: SearchFiltersParams;
@Output() searchParamsChanged$: EventEmitter<SearchFiltersParams> = new EventEmitter();
@Output() paymentEventFired$: EventEmitter<PaymentMenuItemEvent> = new EventEmitter();
@Input()
set type(type: SearcherType) {
this.searcherType = type;
switch (type.type) {
case SearchType.GlobalSearcher:
this.tableType = { type: TableType.GlobalTable };
this.mainFilterSearchType = { type: MainSearchType.GlobalSearchFilter };
break;
case SearchType.PartySearcher:
this.tableType = { type: TableType.PartyTable, partyID: type.partyID };
this.mainFilterSearchType = {
type: MainSearchType.PartySearchFilter,
partyID: type.partyID,
};
break;
default:
console.error('Wrong search type for payments searcher');
break;
}
}
// eslint-disable-next-line @typescript-eslint/member-ordering
@Input()
initSearchParams: SearchFiltersParams;
// eslint-disable-next-line @typescript-eslint/member-ordering
@Output()
searchParamsChanged$: EventEmitter<SearchFiltersParams> = new EventEmitter();
// eslint-disable-next-line @typescript-eslint/member-ordering
@Output()
paymentEventFired$: EventEmitter<PaymentMenuItemEvent> = new EventEmitter();
// eslint-disable-next-line @typescript-eslint/member-ordering
isLoading$ = this.fetchPaymentsService.isLoading$;
// eslint-disable-next-line @typescript-eslint/member-ordering
doAction$ = this.fetchPaymentsService.doAction$;
// eslint-disable-next-line @typescript-eslint/member-ordering
payments$ = this.fetchPaymentsService.searchResult$;
// eslint-disable-next-line @typescript-eslint/member-ordering
hasMore$ = this.fetchPaymentsService.hasMore$;
// eslint-disable-next-line @typescript-eslint/member-ordering
tableType: PaymentsTableType;
// eslint-disable-next-line @typescript-eslint/member-ordering
mainFilterSearchType: MainFilterSearchType;
params: SearchFiltersParams;
constructor(
private fetchPaymentsService: FetchPaymentsService,
private paymentsSearcherService: PaymentsSearcherService,
private snackBar: MatSnackBar
) {
this.paymentsSearcherService.searchParamsChanges$.subscribe((params) => {
const searchParams = {
...params,
partyID:
this.mainFilterSearchType.type === MainSearchType.PartySearchFilter
? this.searcherType.partyID
: params.partyID,
};
this.fetchPaymentsService.search(searchParams);
this.searchParamsChanged$.emit(searchParams);
});
this.paymentsSearcherService.searchParamsChanges$
.pipe(untilDestroyed(this))
.subscribe((params) => {
this.params = params;
// TODO: the partyID is optional, but the backend returns 500
if (params.partyID) {
this.fetchPaymentsService.search(params);
}
this.searchParamsChanged$.emit(params);
});
}
ngOnInit() {

View File

@ -11,6 +11,7 @@ import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatTableModule } from '@angular/material/table';
import { ActionsModule } from '@vality/ng-core';
import { EmptySearchResultModule } from '@cc/components/empty-search-result';
@ -18,7 +19,7 @@ import {
PaymentsMainSearchFiltersModule,
PaymentsOtherSearchFiltersModule,
} from '../payments-search-filters';
import { PaymentsTableModule } from '../payments-table/payments-table.module';
import { PaymentsTableModule } from '../payments-table';
import { StatusModule } from '../status';
import { PaymentsSearcherComponent } from './payments-searcher.component';
@ -41,6 +42,7 @@ import { PaymentsSearcherComponent } from './payments-searcher.component';
MatBadgeModule,
PaymentsOtherSearchFiltersModule,
EmptySearchResultModule,
ActionsModule,
],
declarations: [PaymentsSearcherComponent],
exports: [PaymentsSearcherComponent],

View File

@ -1,9 +0,0 @@
export enum SearchType {
PartySearcher = 'PartySearcher',
GlobalSearcher = 'GlobalSearcher',
}
export interface SearcherType {
type: SearchType;
partyID?: string;
}

View File

@ -1,4 +1,3 @@
export * from './payments-table.module';
export * from './payments-table';
export * from './payment-actions';
export * from './payment-menu-item-event';

View File

@ -26,7 +26,7 @@
<ng-container matColumnDef="shop">
<th *matHeaderCellDef mat-header-cell>Shop</th>
<td *matCellDef="let payment" mat-cell>
{{ payment.shop_id | shopName: partyID }}
{{ payment.shop_id | shopName: payment.owner_id }}
</td>
</ng-container>

View File

@ -1,10 +1,9 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { InvoiceID, InvoicePaymentID, PartyID } from '@vality/domain-proto/lib/domain';
import { StatPayment } from '@vality/domain-proto/lib/merch_stat';
import { StatPayment } from '@vality/magista-proto';
import { PaymentActions } from './payment-actions';
import { PaymentMenuItemEvent } from './payment-menu-item-event';
import { PaymentsTableType, TableType } from './payments-table';
@Component({
selector: 'cc-payments-table',
@ -13,29 +12,11 @@ import { PaymentsTableType, TableType } from './payments-table';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PaymentsTableComponent {
@Input()
payments: StatPayment[];
partyID: string;
@Input()
set type(type: PaymentsTableType) {
this.displayedColumns = [
'amount',
'status',
'createdAt',
...(type.type === TableType.PartyTable ? ['shop'] : []),
'actions',
];
this.partyID = type.partyID;
}
@Output()
menuItemSelected$: EventEmitter<PaymentMenuItemEvent> = new EventEmitter();
@Input() payments: StatPayment[];
@Output() menuItemSelected$: EventEmitter<PaymentMenuItemEvent> = new EventEmitter();
paymentActions = Object.keys(PaymentActions);
displayedColumns: string[];
displayedColumns = ['amount', 'status', 'createdAt', 'shop', 'actions'];
menuItemSelected(
action: string,

View File

@ -1,9 +0,0 @@
export enum TableType {
PartyTable = 'PartyTable',
GlobalTable = 'GlobalTable',
}
export interface PaymentsTableType {
type: TableType;
partyID?: string;
}

View File

@ -1,12 +1,14 @@
import { Pipe, PipeTransform } from '@angular/core';
import { i64ToNumber } from '@cc/utils/i64-to-number';
import { Int64 } from '@vality/thrift-ts';
@Pipe({
name: 'ccThriftInt64',
})
/**
* @deprecated
*/
export class ThriftInt64Pipe implements PipeTransform {
transform(value: any): any {
return i64ToNumber(value.buffer, value.offset);
transform(value: Int64 | number): any {
return typeof value === 'number' ? value : value.toNumber();
}
}

View File

@ -65,7 +65,7 @@ export abstract class PartialFetcher<R, P> {
shareReplay(SHARE_REPLAY_CONF)
);
this.doAction$ = progress(actionWithParams$, fetchResult$, true).pipe(
this.doAction$ = progress(actionWithParams$, fetchResult$, false).pipe(
shareReplay(SHARE_REPLAY_CONF)
);
this.doSearchAction$ = progress(

View File

@ -3,7 +3,6 @@ import { NgModule } from '@angular/core';
import { ClaimManagementService } from './claim-management.service';
import { DomainStoreService } from './domain-store.service';
import { DomainService } from './domain.service';
import { MerchantStatisticsService } from './merchant-statistics.service';
import { PaymentProcessingService } from './payment-processing.service';
import { RoutingRulesModule } from './routing-rules';
@ -12,7 +11,6 @@ import { RoutingRulesModule } from './routing-rules';
providers: [
DomainService,
PaymentProcessingService,
MerchantStatisticsService,
DomainStoreService,
ClaimManagementService,
],

View File

@ -1,21 +0,0 @@
import { Injectable, NgZone } from '@angular/core';
import { StatRequest, StatResponse } from '@vality/domain-proto/lib/merch_stat';
import { StatRequest as ThriftStatRequest } from '@vality/domain-proto/lib/merch_stat/gen-nodejs/merch_stat_types';
import * as MerchantStatistics from '@vality/domain-proto/lib/merch_stat/gen-nodejs/MerchantStatistics';
import { Observable } from 'rxjs';
import { KeycloakTokenInfoService } from '../../keycloak-token-info.service';
import { ThriftService } from '../services/thrift/thrift-service';
@Injectable()
export class MerchantStatisticsService extends ThriftService {
constructor(zone: NgZone, keycloakTokenInfoService: KeycloakTokenInfoService) {
super(zone, keycloakTokenInfoService, '/stat', MerchantStatistics);
}
getPayments = (req: StatRequest): Observable<StatResponse> =>
this.toObservableAction('GetPayments')(new ThriftStatRequest(req));
getStatistics = (req: StatRequest): Observable<StatResponse> =>
this.toObservableAction('GetStatistics')(new ThriftStatRequest(req));
}

View File

@ -1,6 +1,6 @@
<ng-template #content>
<div fxLayout="row" fxLayoutAlign="center center">
<h2 class="mat-headline empty-result">Search result is empty</h2>
<h2 class="mat-headline empty-result">{{ label || 'Search result is empty' }}</h2>
</div>
</ng-template>

View File

@ -8,4 +8,5 @@ import { coerceBoolean } from 'coerce-property';
})
export class EmptySearchResultComponent {
@Input() @coerceBoolean unwrapped = false;
@Input() label: string;
}

View File

@ -1,32 +0,0 @@
<div fxLayout="column">
<mat-form-field class="mat-form-field-container" fxFlexFill>
<mat-radio-group [formControl]="formControl" fxLayout="column" fxLayoutGap="16px">
<input [formControl]="control" matInput />
<mat-radio-button color="primary">Any</mat-radio-button>
<ng-container *ngFor="let v of visibleValues; index as i">
<div *ngIf="!(i % 2)" fxLayout fxLayoutGap="16px">
<mat-radio-button [value]="v.value" color="primary" fxFlex>{{
v.title
}}</mat-radio-button>
<mat-radio-button
*ngIf="values[i + 1]"
[value]="values[i + 1].value"
color="primary"
fxFlex
>{{ values[i + 1].title }}</mat-radio-button
>
</div>
</ng-container>
</mat-radio-group>
</mat-form-field>
<div
class="show-others"
fxLayout
fxLayoutAlign="start center"
fxLayoutGap="8px"
(click)="changeVisibility(!isExpanded)"
>
<div class="mat-body-1">{{ isExpanded ? 'Show less' : 'Show others' }}</div>
<mat-icon>{{ isExpanded ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}</mat-icon>
</div>
</div>

View File

@ -1,15 +0,0 @@
.show-others:hover {
cursor: pointer;
}
input {
display: none;
}
::ng-deep .mat-form-field-container .mat-form-field-underline {
background-color: transparent !important;
}
::ng-deep .mat-form-field-container .mat-form-field-infix {
border-top-width: 0;
}

View File

@ -1,80 +0,0 @@
import { FocusMonitor } from '@angular/cdk/a11y';
import { Platform } from '@angular/cdk/platform';
import { AutofillMonitor } from '@angular/cdk/text-field';
import {
Component,
ElementRef,
Input,
OnChanges,
Optional,
Self,
SimpleChanges,
ViewEncapsulation,
} from '@angular/core';
import { UntypedFormControl, FormGroupDirective, NgControl, NgForm } from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material/core';
import { CustomFormControl, RadioButtonObject } from '../utils';
@Component({
selector: 'cc-expandable-radio-group',
templateUrl: 'expandable-radio-group.component.html',
styleUrls: ['expandable-radio-group.component.scss'],
encapsulation: ViewEncapsulation.Emulated,
})
export class ExpandableRadioGroupComponent extends CustomFormControl implements OnChanges {
form = new UntypedFormControl();
@Input()
values: RadioButtonObject[];
control = new UntypedFormControl();
visibleValues: RadioButtonObject[];
isExpandable = false;
isExpanded = false;
constructor(
focusMonitor: FocusMonitor,
elementRef: ElementRef<HTMLElement>,
platform: Platform,
@Optional() @Self() ngControl: NgControl,
autofillMonitor: AutofillMonitor,
defaultErrorStateMatcher: ErrorStateMatcher,
@Optional() parentForm: NgForm,
@Optional() parentFormGroup: FormGroupDirective
) {
super(
focusMonitor,
elementRef,
platform,
ngControl,
autofillMonitor,
defaultErrorStateMatcher,
parentForm,
parentFormGroup
);
}
ngOnChanges(changes?: SimpleChanges) {
this.isExpandable = changes?.values?.currentValue > 4;
this.isExpanded =
this.ngControl.value &&
!this.values
.slice(0, 2)
.map((v) => v.value)
.includes(this.ngControl.value);
this.changeVisibility(this.isExpanded);
}
changeVisibility(value: boolean) {
this.isExpanded = value;
if (value) {
this.visibleValues = this.values;
} else {
this.visibleValues = this.values.slice(0, 2);
}
}
}

View File

@ -1,27 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { ReactiveFormsModule } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatRadioModule } from '@angular/material/radio';
import { ExpandableRadioGroupComponent } from './expandable-radio-group.component';
const EXPORTED_DECLARATIONS = [ExpandableRadioGroupComponent];
@NgModule({
imports: [
FlexLayoutModule,
CommonModule,
MatInputModule,
MatFormFieldModule,
MatRadioModule,
ReactiveFormsModule,
MatIconModule,
],
declarations: EXPORTED_DECLARATIONS,
exports: EXPORTED_DECLARATIONS,
})
export class ExpandableRadioGroupModule {}

View File

@ -1 +0,0 @@
export * from './expandable-radio-group.module';

View File

@ -1,3 +1,11 @@
import { ValuesType } from 'utility-types';
export function getEnumKeyValues<E extends Record<PropertyKey, unknown>>(srcEnum: E) {
return Object.entries(srcEnum)
.filter(([, v]) => typeof v === 'string')
.map(([value, key]) => ({ key, value })) as { key: keyof E; value: ValuesType<E> }[];
}
export function getEnumKeys<E extends Record<PropertyKey, unknown>>(srcEnum: E): (keyof E)[] {
return Object.values(srcEnum).filter((v) => typeof v === 'string') as string[];
}

View File

@ -1,28 +0,0 @@
export const i64ToNumber = (buffer: any, offset: number, allowImprecise = false) => {
const b = buffer;
const o = offset;
// Running sum of octets, doing a 2's complement
// eslint-disable-next-line no-bitwise
const negate = b[o] & 0x80;
let x = 0;
let carry = 1;
for (let i = 7, m = 1; i >= 0; i--, m *= 256) {
let v = b[o + i];
// 2's complement for negative numbers
if (negate) {
// eslint-disable-next-line no-bitwise
v = (v ^ 0xff) + carry;
// eslint-disable-next-line no-bitwise
carry = v >> 8;
// eslint-disable-next-line no-bitwise
v = v & 0xff;
}
x += v * m;
}
// Return Infinity if we've lost integer precision
const maxInt = Math.pow(2, 53);
if (!allowImprecise && x >= maxInt) {
return negate ? -Infinity : Infinity;
}
return negate ? -x : x;
};

View File

@ -8,7 +8,6 @@ export * from './wrap-values-to-array';
export * from './get-or';
export * from './skip-null-values';
export * from './thrift-json-converter';
export * from './i64-to-number';
export * from './to-minor';
export * from './to-major';
export * from './java-thrift-formatter';