IMP-85,IMP-109,IMP-126: Add machines fail. Add terminal percent. Enable shops page (#293)

This commit is contained in:
Rinat Arsaev 2023-11-28 13:46:42 +07:00 committed by GitHub
parent 495648d2ee
commit f4be6326e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 409 additions and 185 deletions

View File

@ -2,5 +2,5 @@
"$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json",
"version": "0.2",
"import": "node_modules/@vality/cspell-config/cspell.config.js",
"words": ["submain", "papaparse"]
"words": ["submain", "papaparse", "msgpack"]
}

22
package-lock.json generated
View File

@ -22,11 +22,12 @@
"@ngneat/input-mask": "6.0.0",
"@ngneat/until-destroy": "9.2.2",
"@s-libs/ng-core": "16.0.0",
"@vality/deanonimus-proto": "2.0.1-346936.0",
"@vality/deanonimus-proto": "2.0.1-2a02d87.0",
"@vality/domain-proto": "2.0.1-45b0719.0",
"@vality/fistful-proto": "2.0.1-3b9a0a7.0",
"@vality/machinegun-proto": "1.0.0",
"@vality/magista-proto": "2.0.2-4383410.0",
"@vality/ng-core": "16.2.1-pr-49-2bd668d.0",
"@vality/ng-core": "16.2.1-pr-49-3e3274a.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",
@ -6305,9 +6306,9 @@
}
},
"node_modules/@vality/deanonimus-proto": {
"version": "2.0.1-346936.0",
"resolved": "https://registry.npmjs.org/@vality/deanonimus-proto/-/deanonimus-proto-2.0.1-346936.0.tgz",
"integrity": "sha512-loN+GivvUYd7+a0GwoibwzjCYJ9O3NJk6ot1vJujeg/0spnFHxoaASgm4JcW/HFWSwgEB6RZwo8ewFONwO2AHA=="
"version": "2.0.1-2a02d87.0",
"resolved": "https://registry.npmjs.org/@vality/deanonimus-proto/-/deanonimus-proto-2.0.1-2a02d87.0.tgz",
"integrity": "sha512-mokuK6w+ExASdDE6+L9bM2SaS0yVPSyBvXavY8rRtNzZgVdmj7KSRiyGmXz41grhS6jcvD8aR85Nj4o7/iogmQ=="
},
"node_modules/@vality/domain-proto": {
"version": "2.0.1-45b0719.0",
@ -6526,15 +6527,20 @@
"resolved": "https://registry.npmjs.org/@vality/fistful-proto/-/fistful-proto-2.0.1-3b9a0a7.0.tgz",
"integrity": "sha512-4zF+5fwW4iEXcFstRZXBB79T3E4zkQjsMnoknEkRjKoWC/IUL2qOzpyigBqAT9V1eU2IOLMxxBnVZcODeK2GXg=="
},
"node_modules/@vality/machinegun-proto": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@vality/machinegun-proto/-/machinegun-proto-1.0.0.tgz",
"integrity": "sha512-HSK9WXE+cT+1skU5w3xI++GM/RO7YXaNDFL8mGMiE5Mga8hVUlb+yLBvvNM+zCslqiI+dENFQjviZeFROWH3Kw=="
},
"node_modules/@vality/magista-proto": {
"version": "2.0.2-4383410.0",
"resolved": "https://registry.npmjs.org/@vality/magista-proto/-/magista-proto-2.0.2-4383410.0.tgz",
"integrity": "sha512-kAiKSTvof+jFuNkQKyAsc2s+Br2NXPWAyKuD0f7mQIk9HrP8uHsKJya5KxdOdng97JYe0MSUlx7seQxWmCgYfA=="
},
"node_modules/@vality/ng-core": {
"version": "16.2.1-pr-49-2bd668d.0",
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-16.2.1-pr-49-2bd668d.0.tgz",
"integrity": "sha512-xqi0j9RxgJDeBcsLlDTMC55x0/wKEZOzj8s+N/ViwuQjcoABenpIhW5rCW8YBl3uRbaBgBrnF8e3CA2nYoTDaw==",
"version": "16.2.1-pr-49-3e3274a.0",
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-16.2.1-pr-49-3e3274a.0.tgz",
"integrity": "sha512-ypMD5RwBnqzOKXucaTi1i6DmyHSCScMowWXownKys/R5ImW4Wavq/DEMaznTH3BBLBPbkxKDN6JruCLcmIrLew==",
"dependencies": {
"@angular/material-date-fns-adapter": "^16.0.0",
"@ng-matero/extensions": "^16.0.0",

View File

@ -13,7 +13,7 @@
"format:fix": "prettier ** --write --log-level=warn",
"spell": "cspell --no-progress **",
"spell:fix": "cspell --no-progress --show-suggestions --show-context **",
"fix": "npm run lint:fix && npm run format:fix"
"fix": "npm run lint:fix && npm run format:fix && npm run spell:fix"
},
"dependencies": {
"@angular/animations": "16.2.12",
@ -30,11 +30,12 @@
"@ngneat/input-mask": "6.0.0",
"@ngneat/until-destroy": "9.2.2",
"@s-libs/ng-core": "16.0.0",
"@vality/deanonimus-proto": "2.0.1-346936.0",
"@vality/deanonimus-proto": "2.0.1-2a02d87.0",
"@vality/domain-proto": "2.0.1-45b0719.0",
"@vality/fistful-proto": "2.0.1-3b9a0a7.0",
"@vality/machinegun-proto": "1.0.0",
"@vality/magista-proto": "2.0.2-4383410.0",
"@vality/ng-core": "16.2.1-pr-49-2bd668d.0",
"@vality/ng-core": "16.2.1-pr-49-3e3274a.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",

View File

@ -46,4 +46,8 @@ export class DeanonimusService {
searchShopText(text: string) {
return this.client$.pipe(switchMap((c) => c.searchShopText(text)));
}
searchWalletText(text: string) {
return this.client$.pipe(switchMap((c) => c.searchWalletText(text)));
}
}

View File

@ -0,0 +1,47 @@
import { Injectable } from '@angular/core';
import {
state_processing_Automaton,
state_processing_AutomatonCodegenClient,
ThriftAstMetadata,
} from '@vality/machinegun-proto';
import { MachineDescriptor, Args } from '@vality/machinegun-proto/state_processing';
import { combineLatest, from, map, Observable, switchMap } from 'rxjs';
import { KeycloakTokenInfoService, toWachterHeaders } from '@cc/app/shared/services';
import { environment } from '@cc/environments/environment';
import { ConfigService } from '../../core/config.service';
@Injectable({ providedIn: 'root' })
export class AutomatonService {
private client$: Observable<state_processing_AutomatonCodegenClient>;
constructor(
private keycloakTokenInfoService: KeycloakTokenInfoService,
configService: ConfigService,
) {
const headers$ = this.keycloakTokenInfoService.decoded$.pipe(
map(toWachterHeaders('Automaton')),
);
const metadata$ = from(
import('@vality/machinegun-proto/metadata.json').then(
(m) => m.default as ThriftAstMetadata[],
),
);
this.client$ = combineLatest([metadata$, headers$]).pipe(
switchMap(([metadata, headers]) =>
state_processing_Automaton({
metadata,
headers,
logging: environment.logging.requests,
...configService.config.api.wachter,
}),
),
);
}
// eslint-disable-next-line @typescript-eslint/naming-convention
Call(desc: MachineDescriptor, a: Args) {
return this.client$.pipe(switchMap((c) => c.Call(desc, a)));
}
}

View File

@ -0,0 +1,3 @@
export * from './automaton.service';
export * from './utils';
export * from './types';

View File

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

View File

@ -0,0 +1,5 @@
// https://github.com/empayre/cloud-infra/blob/a9b5047df3e2b8eb3377686251ca83b561f308d7/helmfile/config/machinegun-ha/config.yaml.gotmpl#L139
export enum Namespace {
Invoice = 'invoice',
Withdrawal = 'ff/withdrawal_v2',
}

View File

@ -0,0 +1,6 @@
import { Value } from '@vality/machinegun-proto/internal/msgpack';
// https://github.com/valitydev/holmes/blob/master/scripts/fail-machine.sh
export const FAILS_MACHINE_VALUE: Value = {
bin: JSON.stringify({ content_type: 'base64', content: 'g2o=' }),
};

View File

@ -0,0 +1 @@
export * from './fails-machine-value';

View File

@ -11,6 +11,7 @@ import { ROUTING_CONFIG as PAYMENTS_ROUTING_CONFIG } from './sections/payments/r
import { ROUTING_CONFIG as PAYOUTS_ROUTING_CONFIG } from './sections/payouts/payouts/routing-config';
import { ROUTING_CONFIG as REPAIRING_ROUTING_CONFIG } from './sections/repairing/routing-config';
import { ROUTING_CONFIG as PARTIES_ROUTING_CONFIG } from './sections/search-parties/routing-config';
import { SHOPS_ROUTING_CONFIG } from './sections/shops';
import { ROUTING_CONFIG as SOURCES_ROUTING_CONFIG } from './sections/sources/routing-config';
import { ROUTING_CONFIG as TERMINALS_ROUTING_CONFIG } from './sections/terminals';
import { ROUTING_CONFIG as WALLETS_ROUTING_CONFIG } from './sections/wallets/routing-config';
@ -73,12 +74,11 @@ export class AppComponent implements OnInit {
route: '/parties',
services: PARTIES_ROUTING_CONFIG.services,
},
// TODO
// {
// name: 'Shops',
// route: '/shops',
// services: SHOPS_ROUTING_CONFIG.services,
// },
{
name: 'Shops',
route: '/shops',
services: SHOPS_ROUTING_CONFIG.services,
},
{
name: 'Claims',
route: '/claims',

View File

@ -1,4 +1,4 @@
import { Component, Injector, OnInit } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { InvoicePaymentChargeback } from '@vality/domain-proto/domain';
@ -138,12 +138,11 @@ export class CreateChargebacksByFileDialogComponent
successfullyChargebacks: InvoicePaymentChargeback[] = [];
constructor(
injector: Injector,
private invoicingService: InvoicingService,
private log: NotifyLogService,
private amountCurrencyService: AmountCurrencyService,
) {
super(injector);
super();
}
ngOnInit() {
@ -158,7 +157,6 @@ export class CreateChargebacksByFileDialogComponent
selected.map((c) =>
this.invoicingService.CreateChargeback(c.invoiceId, c.paymentId, c.params),
),
2,
this.progress$,
)
.pipe(untilDestroyed(this))

View File

@ -1,4 +1,4 @@
import { Component, Injector } from '@angular/core';
import { Component } from '@angular/core';
import { Validators, FormBuilder } from '@angular/forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { Claim, ModificationUnit } from '@vality/domain-proto/claim_management';
@ -37,13 +37,12 @@ export class AddModificationDialogComponent extends DialogSuperclass<
private progress$ = new BehaviorSubject(0);
constructor(
injector: Injector,
private fb: FormBuilder,
private claimManagementService: ClaimManagementService,
private notificationService: NotificationService,
private notificationErrorService: NotificationErrorService,
) {
super(injector);
super();
}
add() {

View File

@ -1,4 +1,4 @@
import { Component, Injector } from '@angular/core';
import { Component } from '@angular/core';
import { Validators, FormBuilder } from '@angular/forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { Claim, ClaimStatus } from '@vality/domain-proto/claim_management';
@ -34,14 +34,13 @@ export class ChangeStatusDialogComponent extends DialogSuperclass<
private progress$ = new BehaviorSubject(0);
constructor(
injector: Injector,
private fb: FormBuilder,
private claimManagementService: ClaimManagementService,
private notificationService: NotificationService,
private allowedClaimStatusesService: AllowedClaimStatusesService,
private notificationErrorService: NotificationErrorService,
) {
super(injector);
super();
}
confirm(): void {

View File

@ -1,4 +1,4 @@
import { Component, Injector } from '@angular/core';
import { Component } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { untilDestroyed, UntilDestroy } from '@ngneat/until-destroy';
@ -23,13 +23,12 @@ export class CreateClaimDialogComponent extends DialogSuperclass<
progress$ = new BehaviorSubject(0);
constructor(
injector: Injector,
private claimService: ClaimManagementService,
private notificationService: NotificationService,
private notificationErrorService: NotificationErrorService,
private router: Router,
) {
super(injector);
super();
}
create() {

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, Injector } from '@angular/core';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Validators, NonNullableFormBuilder } from '@angular/forms';
import { untilDestroyed, UntilDestroy } from '@ngneat/until-destroy';
import { Revert } from '@vality/fistful-proto/internal/deposit_revert';
@ -31,13 +31,12 @@ export class CreateRevertDialogComponent extends DialogSuperclass<
progress$ = new BehaviorSubject(0);
constructor(
injector: Injector,
private fb: NonNullableFormBuilder,
private depositManagementService: ManagementService,
private idGenerator: UserInfoBasedIdGeneratorService,
private log: NotifyLogService,
) {
super(injector);
super();
}
createRevert() {

View File

@ -1,7 +1,9 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { withLatestFrom } from 'rxjs';
import { withLatestFrom, Observable } from 'rxjs';
import { map, shareReplay } from 'rxjs/operators';
import { ShopParty } from '../../shared/components/shops-table';
import { PartyShopsService } from './party-shops.service';
@Component({
@ -10,9 +12,14 @@ import { PartyShopsService } from './party-shops.service';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PartyShopsComponent {
shopsParty$ = this.partyShopsService.shops$.pipe(
shopsParty$: Observable<ShopParty[]> = this.partyShopsService.shops$.pipe(
withLatestFrom(this.partyShopsService.party$),
map(([shops, party]) => shops.map((shop) => ({ shop, party }))),
map(([shops, party]) =>
shops.map((shop) => ({
shop,
party: { id: party.id, email: party.contact_info?.email },
})),
),
shareReplay({ refCount: true, bufferSize: 1 }),
);
progress$ = this.partyShopsService.progress$;

View File

@ -1,4 +1,4 @@
import { Component, Injector } from '@angular/core';
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
import { untilDestroyed, UntilDestroy } from '@ngneat/until-destroy';
import { InvoicePaymentChargeback } from '@vality/domain-proto/domain';
@ -27,13 +27,12 @@ export class CreateChargebackDialogComponent extends DialogSuperclass<
extensions$ = this.domainMetadataFormExtensionsService.extensions$;
constructor(
injector: Injector,
private invoicingService: InvoicingService,
private domainMetadataFormExtensionsService: DomainMetadataFormExtensionsService,
private notificationErrorService: NotificationErrorService,
private notificationService: NotificationService,
) {
super(injector);
super();
}
create() {

View File

@ -11,7 +11,7 @@
></cc-metadata-form>
<v-dialog-actions>
<button
*ngIf="this.withError.length"
*ngIf="this.errors.length"
[disabled]="control.invalid || !!(progress$ | async)"
mat-raised-button
(click)="closeAndSelectWithAnError()"
@ -24,7 +24,7 @@
mat-raised-button
(click)="create()"
>
{{ this.withError.length ? 'Repeat for ' + this.withError.length : 'Create' }}
{{ this.errors.length ? 'Repeat for ' + this.errors.length : 'Create' }}
</button>
</v-dialog-actions>
</v-dialog>

View File

@ -1,12 +1,16 @@
import { Component, Injector } from '@angular/core';
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { InvoicePaymentAdjustmentParams } from '@vality/domain-proto/payment_processing';
import { StatPayment } from '@vality/magista-proto/magista';
import { DialogSuperclass, NotifyLogService } from '@vality/ng-core';
import chunk from 'lodash-es/chunk';
import { BehaviorSubject, from, concatMap, of, forkJoin } from 'rxjs';
import { catchError, finalize } from 'rxjs/operators';
import {
DialogSuperclass,
NotifyLogService,
forkJoinToResult,
splitResultsErrors,
ForkJoinErrorResult,
} from '@vality/ng-core';
import { BehaviorSubject, from } from 'rxjs';
import { DomainMetadataFormExtensionsService } from '@cc/app/shared/services';
@ -20,80 +24,52 @@ import { InvoicingService } from '../../../../api/payment-processing';
export class CreatePaymentAdjustmentComponent extends DialogSuperclass<
CreatePaymentAdjustmentComponent,
{ payments: StatPayment[] },
{ withError?: { payment: StatPayment; error: unknown }[] }
{ errors?: ForkJoinErrorResult<StatPayment>[] }
> {
control = new FormControl<InvoicePaymentAdjustmentParams>(null);
progress$ = new BehaviorSubject(0);
metadata$ = from(import('@vality/domain-proto/metadata.json').then((m) => m.default));
extensions$ = this.domainMetadataFormExtensionsService.extensions$;
withError: { payment: StatPayment; error: unknown }[] = [];
errors: ForkJoinErrorResult<StatPayment>[] = [];
constructor(
injector: Injector,
private invoicingService: InvoicingService,
private log: NotifyLogService,
private domainMetadataFormExtensionsService: DomainMetadataFormExtensionsService,
) {
super(injector);
super();
}
create() {
const payments = this.withError.length
? this.withError.map((w) => w.payment)
const payments = this.errors.length
? this.errors.map(({ data }) => data)
: this.dialogData.payments;
this.withError = [];
const progressStep = 100 / (payments.length + 1);
this.progress$.next(progressStep);
of(...chunk(payments, 4))
.pipe(
concatMap((payments) =>
forkJoin(
payments.map((p) =>
this.invoicingService
.CreatePaymentAdjustment(p.invoice_id, p.id, this.control.value)
.pipe(
catchError((error) => {
this.withError.push({ payment: p, error });
return of(null);
}),
finalize(() =>
this.progress$.next(this.progress$.value + progressStep),
),
),
),
),
this.errors = [];
forkJoinToResult(
payments.map((p) =>
this.invoicingService.CreatePaymentAdjustment(
p.invoice_id,
p.id,
this.control.value,
),
untilDestroyed(this),
)
.subscribe({
complete: () => {
if (!this.withError.length) {
this.log.success(`${payments.length} created successfully`);
this.closeWithSuccess();
} else {
const errors = this.withError
.map((w) => {
const error: string =
w.error?.['name'] || w.error?.['message'] || '';
if (error) {
return `${w.payment.id}: ${error}`;
}
return null;
})
.filter(Boolean)
.join(', ');
this.log.error(
new Error(
`${this.withError.length} out of ${payments.length} failed. Errors: ${errors}`,
),
);
}
this.progress$.next(0);
},
),
this.progress$,
payments,
)
.pipe(untilDestroyed(this))
.subscribe((res) => {
const [result, errors] = splitResultsErrors(res);
if (errors.length) {
this.errors = errors;
this.log.error(this.errors.map((e) => e.error));
} else {
this.log.success(`${result.length} created successfully`);
this.closeWithSuccess();
}
});
}
closeAndSelectWithAnError() {
this.closeWithError({ withError: this.withError });
this.closeWithError({ errors: this.errors });
}
}

View File

@ -41,13 +41,21 @@
(selectedChange)="selected$.next($event)"
(update)="load($event ?? {})"
>
<button
[disabled]="!(selected$ | async)?.length"
color="primary"
mat-raised-button
(click)="failMachines()"
>
Fail machines
</button>
<button
[disabled]="!(selected$ | async)?.length"
color="primary"
mat-raised-button
(click)="createPaymentAdjustment()"
>
Create adjustment
Create adjustments
</button>
</cc-payments-table>
</cc-page-layout>

View File

@ -16,10 +16,13 @@ import {
isEqualDateRange,
} from '@vality/ng-core';
import { endOfDay } from 'date-fns';
import { uniq } from 'lodash-es';
import lodashMerge from 'lodash-es/merge';
import { BehaviorSubject, debounceTime, from, of, merge } from 'rxjs';
import { startWith } from 'rxjs/operators';
import { Namespace } from '../../api/machinegun';
import { FailMachinesDialogComponent } from '../../shared/components/fail-machines-dialog';
import { MetadataFormExtension, isTypeWithAliases } from '../../shared/components/metadata-form';
import { DATE_RANGE_DAYS } from '../../tokens';
@ -143,8 +146,27 @@ export class PaymentsComponent implements OnInit {
if (res.status === DialogResponseStatus.Success) {
this.load();
this.selected$.next([]);
} else if (res.data?.withError?.length) {
this.selected$.next(res.data.withError.map((w) => w.payment));
} else if (res.data?.errors?.length) {
this.selected$.next(res.data.errors.map(({ data }) => data));
}
});
}
failMachines() {
this.dialogService
.open(FailMachinesDialogComponent, {
ids: uniq(this.selected$.value.map((s) => s.invoice_id)),
ns: Namespace.Invoice,
})
.afterClosed()
.subscribe((res) => {
if (res.status === DialogResponseStatus.Success) {
this.load();
this.selected$.next([]);
} else if (res.data?.errors?.length) {
this.selected$.next(
res.data.errors.map(({ index }) => this.selected$.value[index]),
);
}
});
}

View File

@ -1,4 +1,4 @@
import { Component, Injector } from '@angular/core';
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { DialogResponseStatus, DialogSuperclass } from '@vality/ng-core';
@ -24,12 +24,11 @@ export class CancelPayoutDialogComponent extends DialogSuperclass<
progress$ = new BehaviorSubject(0);
constructor(
injector: Injector,
private payoutManagementService: PayoutManagementService,
private notificationService: NotificationService,
private notificationErrorService: NotificationErrorService,
) {
super(injector);
super();
}
accept() {

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, Injector } from '@angular/core';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { DialogResponseStatus, DialogSuperclass } from '@vality/ng-core';
@ -39,13 +39,12 @@ export class CreatePayoutDialogComponent extends DialogSuperclass<CreatePayoutDi
});
constructor(
injector: Injector,
private fb: FormBuilder,
private payoutManagementService: PayoutManagementService,
private notificationService: NotificationService,
private notificationErrorService: NotificationErrorService,
) {
super(injector);
super();
}
create() {

View File

@ -1,4 +1,4 @@
import { Component, Injector, OnInit } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { Validators, FormControl } from '@angular/forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { DialogResponseStatus, DialogSuperclass } from '@vality/ng-core';
@ -57,13 +57,12 @@ export class RepairByScenarioDialogComponent
}
constructor(
injector: Injector,
private repairManagementService: RepairManagementService,
private notificationErrorService: NotificationErrorService,
private notificationService: NotificationService,
private domainMetadataFormExtensionsService: DomainMetadataFormExtensionsService,
) {
super(injector);
super();
}
ngOnInit() {

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, Injector, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { UntypedFormBuilder } from '@angular/forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { DialogSuperclass } from '@vality/ng-core';
@ -27,11 +27,10 @@ export class ChangeDelegateRulesetDialogComponent
rulesets$ = this.routingRulesService.rulesets$;
constructor(
injector: Injector,
private fb: UntypedFormBuilder,
private routingRulesService: RoutingRulesService,
) {
super(injector);
super();
}
ngOnInit() {

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, Injector } from '@angular/core';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { DialogSuperclass } from '@vality/ng-core';
import { BehaviorSubject } from 'rxjs';
@ -23,11 +23,10 @@ export class ChangeTargetDialogComponent extends DialogSuperclass<
initValue: Partial<TargetRuleset> = {};
constructor(
injector: Injector,
private routingRulesService: RoutingRulesService,
private notificationErrorService: NotificationErrorService,
) {
super(injector);
super();
this.routingRulesService
.getRuleset(this.dialogData?.mainRulesetRefID)
.pipe(untilDestroyed(this))

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, Injector } from '@angular/core';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { UntypedFormBuilder } from '@angular/forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { DialogSuperclass } from '@vality/ng-core';
@ -30,12 +30,11 @@ export class AttachNewRulesetDialogComponent extends DialogSuperclass<
targetRulesetValid$ = new BehaviorSubject<boolean>(undefined);
constructor(
injector: Injector,
private fb: UntypedFormBuilder,
private routingRulesService: RoutingRulesService,
private notificationErrorService: NotificationErrorService,
) {
super(injector);
super();
}
attach() {

View File

@ -1,4 +1,4 @@
import { Component, Injector } from '@angular/core';
import { Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { Shop } from '@vality/domain-proto/domain';
@ -26,12 +26,11 @@ export class AddPartyRoutingRuleDialogComponent extends DialogSuperclass<
});
constructor(
injector: Injector,
private fb: FormBuilder,
private routingRulesService: RoutingRulesService,
private notificationErrorService: NotificationErrorService,
) {
super(injector);
super();
}
add() {

View File

@ -1,4 +1,4 @@
import { Component, Injector } from '@angular/core';
import { Component } from '@angular/core';
import { UntypedFormBuilder } from '@angular/forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { DialogSuperclass } from '@vality/ng-core';
@ -22,12 +22,11 @@ export class InitializeRoutingRulesDialogComponent extends DialogSuperclass<
});
constructor(
injector: Injector,
private fb: UntypedFormBuilder,
private routingRulesService: RoutingRulesService,
private notificationErrorService: NotificationErrorService,
) {
super(injector);
super();
}
init() {

View File

@ -1,30 +1,41 @@
import { Component, OnInit } from '@angular/core';
import { untilDestroyed, UntilDestroy } from '@ngneat/until-destroy';
import { SearchShopHit } from '@vality/deanonimus-proto/internal/deanonimus';
import { Column, progressTo, QueryParamsService } from '@vality/ng-core';
import { BehaviorSubject, defer, of, combineLatest } from 'rxjs';
import { startWith, switchMap, map, shareReplay, debounceTime } from 'rxjs/operators';
import { Component } from '@angular/core';
import { UntilDestroy } from '@ngneat/until-destroy';
import { SearchShopHit } from '@vality/deanonimus-proto/deanonimus';
import { Column, progressTo, NotifyLogService } from '@vality/ng-core';
import { BehaviorSubject, defer, of, combineLatest, Subject, Observable } from 'rxjs';
import { switchMap, shareReplay, catchError, map } from 'rxjs/operators';
import { DeanonimusService } from '../../api/deanonimus';
import { ShopParty } from '../../shared/components/shops-table';
@UntilDestroy()
@Component({
selector: 'cc-shops',
templateUrl: './shops.component.html',
})
export class ShopsComponent implements OnInit {
filterChange$ = new BehaviorSubject(this.qp.params.search);
shopsParty$ = combineLatest([this.filterChange$, defer(() => this.updateShops$)]).pipe(
startWith(null),
debounceTime(200),
export class ShopsComponent {
filterChange$ = new Subject<string>();
shopsParty$: Observable<ShopParty[]> = combineLatest([
this.filterChange$,
defer(() => this.updateShops$),
]).pipe(
switchMap(([search]) =>
search
? this.deanonimusService
.searchShopText(search.trim())
.pipe(progressTo(this.progress$))
? this.deanonimusService.searchShopText(search.trim()).pipe(
progressTo(this.progress$),
catchError((err) => {
this.log.error(err);
return of<SearchShopHit[]>([]);
}),
)
: of<SearchShopHit[]>([]),
),
map((hits) => hits.map((h) => ({ shop: h.shop, party: {} }))),
map((shops) =>
shops.map(({ shop, party }) => ({
shop: shop as ShopParty['shop'],
party,
})),
),
shareReplay({ refCount: true, bufferSize: 1 }),
);
columns: Column<SearchShopHit>[] = [{ field: 'shop.details.name', description: 'shop.id' }];
@ -34,15 +45,9 @@ export class ShopsComponent implements OnInit {
constructor(
private deanonimusService: DeanonimusService,
private qp: QueryParamsService<{ search: string }>,
private log: NotifyLogService,
) {}
ngOnInit() {
this.filterChange$.pipe(untilDestroyed(this)).subscribe((search) => {
this.qp.set({ search });
});
}
update() {
this.updateShops$.next();
}

View File

@ -1,4 +1,4 @@
import { Component, Injector } from '@angular/core';
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
import { DialogSuperclass } from '@vality/ng-core';
@ -14,12 +14,11 @@ export class CreateSourceComponent extends DialogSuperclass<void> {
control = new FormControl();
constructor(
injector: Injector,
private fistfulAdminService: FistfulAdminService,
private errorService: NotificationErrorService,
private notificationService: NotificationService,
) {
super(injector);
super();
}
create() {

View File

@ -1,4 +1,4 @@
import { Component, Injector } from '@angular/core';
import { Component } from '@angular/core';
import { Validators, FormControl } from '@angular/forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { ChangeRequest } from '@vality/fistful-proto/deposit_adjustment';
@ -39,11 +39,10 @@ export class CreateAdjustmentDialogComponent extends DialogSuperclass<
progress$ = new BehaviorSubject(0);
constructor(
injector: Injector,
private managementService: ManagementService,
private log: NotifyLogService,
) {
super(injector);
super();
}
createAdjustment() {
@ -55,7 +54,6 @@ export class CreateAdjustmentDialogComponent extends DialogSuperclass<
external_id: this.externalIdControl.value,
}),
),
4,
this.progress$,
)
.pipe(untilDestroyed(this))

View File

@ -42,13 +42,21 @@
(update)="update($event)"
>
<v-table-actions>
<button
[disabled]="!selected?.length"
color="primary"
mat-raised-button
(click)="failMachines()"
>
Fail machines
</button>
<button
[disabled]="!selected?.length"
color="primary"
mat-raised-button
(click)="adjustment()"
>
Create adjustment
Create adjustments
</button>
</v-table-actions>
</v-table>

View File

@ -12,6 +12,7 @@ import {
DialogService,
DateRange,
getNoTimeZoneIsoString,
DialogResponseStatus,
} from '@vality/ng-core';
import { endOfDay } from 'date-fns';
import startCase from 'lodash-es/startCase';
@ -19,6 +20,8 @@ import startCase from 'lodash-es/startCase';
import { WithdrawalParams } from '@cc/app/api/fistful-stat';
import { getUnionKey } from '../../../utils';
import { Namespace } from '../../api/machinegun';
import { FailMachinesDialogComponent } from '../../shared/components/fail-machines-dialog';
import { AmountCurrencyService } from '../../shared/services';
import { CreateAdjustmentDialogComponent } from './components/create-adjustment-dialog/create-adjustment-dialog.component';
@ -139,4 +142,21 @@ export class WithdrawalsComponent implements OnInit {
withdrawals: this.selected,
});
}
failMachines() {
this.dialogService
.open(FailMachinesDialogComponent, {
ids: this.selected.map((s) => s.id),
ns: Namespace.Withdrawal,
})
.afterClosed()
.subscribe((res) => {
if (res.status === DialogResponseStatus.Success) {
this.fetchWithdrawalsService.reload();
this.selected = [];
} else if (res.data?.errors?.length) {
this.selected = res.data.errors.map(({ index }) => this.selected[index]);
}
});
}
}

View File

@ -1,5 +1,5 @@
import { CommonModule } from '@angular/common';
import { Component, Injector, OnInit } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { ReactiveFormsModule, FormControl, Validators } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
@ -72,12 +72,11 @@ export class ChangeChargebacksStatusDialogComponent
progress$ = new BehaviorSubject(0);
constructor(
injector: Injector,
private invoicingService: InvoicingService,
private log: NotifyLogService,
private domainMetadataFormExtensionsService: DomainMetadataFormExtensionsService,
) {
super(injector);
super();
}
ngOnInit() {
@ -96,7 +95,6 @@ export class ChangeChargebacksStatusDialogComponent
this.control.value,
),
),
4,
this.progress$,
)
.pipe(untilDestroyed(this))

View File

@ -0,0 +1,24 @@
<v-dialog
[progress]="progress$ | async"
noContent
title="Fail {{ dialogData.ids.length }} machines"
>
<v-dialog-actions>
<button
*ngIf="this.errors.length"
[disabled]="!!(progress$ | async)"
mat-raised-button
(click)="closeAndSelectWithAnError()"
>
Close and select with an error
</button>
<button
[disabled]="!!(progress$ | async)"
color="primary"
mat-raised-button
(click)="fail()"
>
{{ this.errors.length ? 'Repeat for ' + this.errors.length : 'Fail machines' }}
</button>
</v-dialog-actions>
</v-dialog>

View File

@ -0,0 +1,82 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { ID } from '@vality/machinegun-proto/internal/base';
import {
DialogSuperclass,
NotifyLogService,
forkJoinToResult,
splitResultsErrors,
ForkJoinErrorResult,
DialogModule,
} from '@vality/ng-core';
import { BehaviorSubject, of } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { AutomatonService, FAILS_MACHINE_VALUE, Namespace } from '../../../api/machinegun';
@UntilDestroy()
@Component({
standalone: true,
templateUrl: './fail-machines-dialog.component.html',
imports: [CommonModule, DialogModule, MatButtonModule],
})
export class FailMachinesDialogComponent extends DialogSuperclass<
FailMachinesDialogComponent,
{ ids: ID[]; ns: Namespace },
{ errors?: ForkJoinErrorResult<ID>[] }
> {
progress$ = new BehaviorSubject(0);
errors: ForkJoinErrorResult<ID>[] = [];
constructor(
private automatonService: AutomatonService,
private log: NotifyLogService,
) {
super();
}
fail() {
const ids = this.errors.length ? this.errors.map(({ data }) => data) : this.dialogData.ids;
this.errors = [];
forkJoinToResult(
ids.map((id) =>
this.automatonService
.Call(
{
ns: this.dialogData.ns,
ref: { id },
range: { limit: 1, direction: 1 },
},
FAILS_MACHINE_VALUE,
)
.pipe(
catchError((err) => {
if (err?.name === 'MachineFailed') {
return of(err);
}
throw err;
}),
),
),
this.progress$,
ids,
)
.pipe(untilDestroyed(this))
.subscribe((res) => {
const [result, errors] = splitResultsErrors(res);
if (errors.length) {
this.errors = errors;
this.log.error(this.errors.map((e) => e.error));
} else {
this.log.success(`${result.length} failed successfully`);
this.closeWithSuccess();
}
});
}
closeAndSelectWithAnError() {
this.closeWithError({ errors: this.errors });
}
}

View File

@ -0,0 +1 @@
export * from './fail-machines-dialog.component';

View File

@ -5,6 +5,7 @@
[externalFilter]="filterChange.observed"
[progress]="progress"
[size]="100"
name="shops"
sortOnFront
standaloneFilter
(filterChange)="filterChange.emit($event)"

View File

@ -30,9 +30,12 @@ import { ShopContractCardComponent } from '../shop-contract-card/shop-contract-c
import { SidenavInfoService } from '../sidenav-info';
import { DomainThriftViewerComponent } from '../thrift-api-crud';
interface ShopParty {
export interface ShopParty {
shop: Shop;
party: Party;
party: {
id: Party['id'];
email: Party['contact_info']['email'];
};
}
@UntilDestroy()
@ -60,12 +63,11 @@ export class ShopsTableComponent implements OnChanges {
@Input({ transform: booleanAttribute }) noPartyColumn: boolean = false;
columns$ = combineLatest([
this.partyDelegateRulesetsService.getDelegatesWithPaymentInstitution(
RoutingRulesType.Payment,
),
this.partyDelegateRulesetsService
.getDelegatesWithPaymentInstitution(RoutingRulesType.Payment)
.pipe(startWith([])),
defer(() => this.updateColumns$).pipe(startWith(null)),
]).pipe(
startWith([[]]),
map(([delegatesWithPaymentInstitution]): Column<ShopParty>[] => [
{
field: 'shop.id',
@ -82,13 +84,16 @@ export class ShopsTableComponent implements OnChanges {
},
sortable: !this.noSort,
},
{
field: 'party.contact_info.email',
header: 'Party',
description: 'party.id',
link: (d) => `/party/${d.party.id}`,
hide: this.noPartyColumn,
},
...(this.noPartyColumn
? []
: [
{
field: 'party.email',
header: 'Party',
description: 'party.id',
link: (d) => `/party/${d.party.id}`,
},
]),
{
field: 'shop.contract_id',
header: 'Contract',

View File

@ -1,4 +1,4 @@
import { Component, Injector } from '@angular/core';
import { Component } from '@angular/core';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
@ -37,11 +37,8 @@ export class DomainThriftFormDialogComponent<T = unknown, R = unknown> extends D
);
}
constructor(
injector: Injector,
private fb: FormBuilder,
) {
super(injector);
constructor(private fb: FormBuilder) {
super();
}
upsert() {

View File

@ -3,8 +3,10 @@ import { Injectable } from '@angular/core';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { ThriftAstMetadata } from '@vality/domain-proto';
import { DomainObject } from '@vality/domain-proto/domain';
import { Rational, Timestamp } from '@vality/domain-proto/internal/base';
import { getImportValue } from '@vality/ng-core';
import isEqual from 'lodash-es/isEqual';
import round from 'lodash-es/round';
import { of, Observable } from 'rxjs';
import { map, shareReplay } from 'rxjs/operators';
@ -24,13 +26,25 @@ export class DomainMetadataViewExtensionsService {
extensions$: Observable<MetadataViewExtension[]> = getImportValue<ThriftAstMetadata[]>(
import('@vality/domain-proto/metadata.json'),
).pipe(
map((metadata) => [
map((metadata): MetadataViewExtension[] => [
...this.createDomainObjectExtensions(metadata),
{
determinant: (data) => of(isTypeWithAliases(data, 'Timestamp', 'base')),
extension: (_, value) =>
extension: (_, value: Timestamp) =>
of({ value: formatDate(value, 'dd.MM.yyyy HH:mm:ss', 'en') }),
},
{
determinant: (data) =>
of(
isTypeWithAliases(data, 'Rational', 'base') &&
isTypeWithAliases(data.parent, 'CashVolumeShare', 'domain'),
),
extension: (_, value: Rational) =>
of({
value: `${round((value.p / value.q) * 100, 4)}%`,
tooltip: `${value.p}/${value.q}`,
}),
},
]),
untilDestroyed(this),
shareReplay(1),

View File

@ -1,7 +1,7 @@
import { inject } from '@angular/core';
import { PossiblyAsync, ColumnObject, getPossiblyAsyncObservable } from '@vality/ng-core';
import get from 'lodash-es/get';
import { map, switchMap } from 'rxjs/operators';
import { switchMap, map } from 'rxjs/operators';
import { PartiesStoreService } from '../../../api/payment-processing';