mirror of
https://github.com/valitydev/dashboard.git
synced 2024-11-06 02:25:23 +00:00
[epic] Operations payments refactoring (#371)
- added accordion panel list in payments - added new quick filters(date, shops, invoices, binPan) - added new details view inlined in list - added new refunds view - added new hold detail view - implemented new error service api - added base dialog support
This commit is contained in:
parent
3d0f1734ae
commit
fb5adcc69d
@ -1,8 +1,6 @@
|
|||||||
// Karma configuration file, see link for more information
|
// Karma configuration file, see link for more information
|
||||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||||
|
|
||||||
process.env.CHROME_BIN = require('puppeteer').executablePath();
|
|
||||||
|
|
||||||
module.exports = function (config) {
|
module.exports = function (config) {
|
||||||
config.set({
|
config.set({
|
||||||
basePath: '',
|
basePath: '',
|
||||||
@ -11,6 +9,7 @@ module.exports = function (config) {
|
|||||||
require('karma-jasmine'),
|
require('karma-jasmine'),
|
||||||
require('karma-chrome-launcher'),
|
require('karma-chrome-launcher'),
|
||||||
require('karma-jasmine-html-reporter'),
|
require('karma-jasmine-html-reporter'),
|
||||||
|
require('karma-spec-reporter'),
|
||||||
require('karma-coverage-istanbul-reporter'),
|
require('karma-coverage-istanbul-reporter'),
|
||||||
require('@angular-devkit/build-angular/plugins/karma'),
|
require('@angular-devkit/build-angular/plugins/karma'),
|
||||||
],
|
],
|
||||||
@ -22,13 +21,31 @@ module.exports = function (config) {
|
|||||||
reports: ['html', 'lcovonly', 'text-summary'],
|
reports: ['html', 'lcovonly', 'text-summary'],
|
||||||
fixWebpackSourcePaths: true,
|
fixWebpackSourcePaths: true,
|
||||||
},
|
},
|
||||||
reporters: ['progress', 'kjhtml'],
|
reporters: ['progress', 'kjhtml', 'spec'],
|
||||||
port: 9876,
|
port: 9876,
|
||||||
colors: true,
|
colors: true,
|
||||||
logLevel: config.LOG_INFO,
|
logLevel: config.LOG_INFO,
|
||||||
autoWatch: false,
|
autoWatch: false,
|
||||||
browsers: ['ChromeHeadless'],
|
browsers: ['ChromeHeadless_no_sandbox'],
|
||||||
|
browserNoActivityTimeout: 300000,
|
||||||
|
browserDisconnectTimeout: 300000,
|
||||||
|
captureTimeout: 300000,
|
||||||
|
customLaunchers: {
|
||||||
|
ChromeHeadless_no_sandbox: {
|
||||||
|
base: 'ChromeHeadless',
|
||||||
|
flags: ['--no-sandbox', '--disable-setuid-sandbox', '--headless', '--disable-gpu'],
|
||||||
|
},
|
||||||
|
},
|
||||||
singleRun: true,
|
singleRun: true,
|
||||||
restartOnFileChange: false,
|
restartOnFileChange: false,
|
||||||
|
specReporter: {
|
||||||
|
maxLogLines: 5, // limit number of lines logged per test
|
||||||
|
suppressErrorSummary: false, // do not print error summary
|
||||||
|
suppressFailed: false, // do not print information about failed tests
|
||||||
|
suppressPassed: false, // do not print information about passed tests
|
||||||
|
suppressSkipped: true, // do not print information about skipped tests
|
||||||
|
showSpecTiming: true, // print the time elapsed for each spec
|
||||||
|
failFast: false, // test would finish with error when a first fail occurs.
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -27,8 +27,8 @@ module.exports = function (config) {
|
|||||||
logLevel: config.LOG_INFO,
|
logLevel: config.LOG_INFO,
|
||||||
autoWatch: true,
|
autoWatch: true,
|
||||||
browsers: ['ChromeHeadless_no_sandbox'],
|
browsers: ['ChromeHeadless_no_sandbox'],
|
||||||
browserNoActivityTimeout: 300000,
|
browserNoActivityTimeout: 30 * 60 * 1000,
|
||||||
browserDisconnectTimeout: 300000,
|
browserDisconnectTimeout: 30 * 60 * 1000,
|
||||||
captureTimeout: 300000,
|
captureTimeout: 300000,
|
||||||
customLaunchers: {
|
customLaunchers: {
|
||||||
ChromeHeadless_no_sandbox: {
|
ChromeHeadless_no_sandbox: {
|
||||||
@ -36,7 +36,7 @@ module.exports = function (config) {
|
|||||||
flags: ['--no-sandbox', '--disable-setuid-sandbox', '--headless', '--disable-gpu'],
|
flags: ['--no-sandbox', '--disable-setuid-sandbox', '--headless', '--disable-gpu'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
singleRun: true,
|
singleRun: false,
|
||||||
restartOnFileChange: true,
|
restartOnFileChange: true,
|
||||||
specReporter: {
|
specReporter: {
|
||||||
maxLogLines: 5, // limit number of lines logged per test
|
maxLogLines: 5, // limit number of lines logged per test
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"stub": "ng serve --port 8000 --configuration=stub-keycloak",
|
"stub": "ng serve --port 8000 --configuration=stub-keycloak",
|
||||||
"build": "ng build --prod --extraWebpackConfig webpack.extra.js --progress=false",
|
"build": "ng build --prod --extraWebpackConfig webpack.extra.js --progress=false",
|
||||||
"test": "ng test",
|
"test": "ng test",
|
||||||
"test-ci": "ng test --watch=false",
|
"test-ci": "ng run dashboard:test-ci",
|
||||||
"lint": "ng lint",
|
"lint": "ng lint",
|
||||||
"lint-fix": "ng lint --fix",
|
"lint-fix": "ng lint --fix",
|
||||||
"e2e": "ng e2e",
|
"e2e": "ng e2e",
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
import { UuidGeneratorModule } from '../../shared';
|
import { IdGeneratorModule } from '../../shared';
|
||||||
import { OrganizationsService } from './organizations.service';
|
import { OrganizationsService } from './organizations.service';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [UuidGeneratorModule],
|
imports: [IdGeneratorModule],
|
||||||
providers: [OrganizationsService],
|
providers: [OrganizationsService],
|
||||||
})
|
})
|
||||||
export class OrganizationsModule {}
|
export class OrganizationsModule {}
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
RoleId,
|
RoleId,
|
||||||
RolesService,
|
RolesService,
|
||||||
} from '@dsh/api-codegen/organizations';
|
} from '@dsh/api-codegen/organizations';
|
||||||
import { UuidGeneratorService } from '@dsh/app/shared';
|
import { IdGeneratorService } from '@dsh/app/shared';
|
||||||
|
|
||||||
import { WritableOrganization } from './types/writable-organization';
|
import { WritableOrganization } from './types/writable-organization';
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ export class OrganizationsService {
|
|||||||
private orgsService: OrgsService,
|
private orgsService: OrgsService,
|
||||||
private rolesService: RolesService,
|
private rolesService: RolesService,
|
||||||
private membersService: MembersService,
|
private membersService: MembersService,
|
||||||
private uuidGeneratorService: UuidGeneratorService
|
private uuidGeneratorService: IdGeneratorService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
listOrgMembership(limit?: number, continuationToken?: string) {
|
listOrgMembership(limit?: number, continuationToken?: string) {
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
import { Organization } from '../../../api-codegen/organizations';
|
import { Organization } from '@dsh/api-codegen/organizations';
|
||||||
|
|
||||||
export type WritableOrganization = Omit<Organization, 'id' | 'createdAt'>;
|
export type WritableOrganization = Omit<Organization, 'id' | 'createdAt'>;
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
|
import { IdGeneratorModule } from '@dsh/app/shared/services';
|
||||||
|
|
||||||
import { RefundService } from './refund.service';
|
import { RefundService } from './refund.service';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
imports: [IdGeneratorModule],
|
||||||
providers: [RefundService],
|
providers: [RefundService],
|
||||||
})
|
})
|
||||||
export class RefundModule {}
|
export class RefundModule {}
|
||||||
|
@ -2,14 +2,22 @@ import { Injectable } from '@angular/core';
|
|||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
import { PaymentsService, Refund, RefundParams } from '@dsh/api-codegen/capi/swagger-codegen';
|
import { PaymentsService, Refund, RefundParams } from '@dsh/api-codegen/capi/swagger-codegen';
|
||||||
|
import { IdGeneratorService } from '@dsh/app/shared/services/id-generator/id-generator.service';
|
||||||
import { genXRequestID } from '../utils';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RefundService {
|
export class RefundService {
|
||||||
constructor(private paymentsService: PaymentsService) {}
|
constructor(private paymentsService: PaymentsService, private idsService: IdGeneratorService) {}
|
||||||
|
|
||||||
createRefund(invoiceID: string, paymentID: string, refundParams: RefundParams): Observable<Refund> {
|
createRefund(invoiceID: string, paymentID: string, refundParams: RefundParams): Observable<Refund> {
|
||||||
return this.paymentsService.createRefund(genXRequestID(), invoiceID, paymentID, refundParams);
|
return this.paymentsService.createRefund(
|
||||||
|
this.idsService.generateRequestID(),
|
||||||
|
invoiceID,
|
||||||
|
paymentID,
|
||||||
|
refundParams
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRefunds(invoiceID: string, paymentID: string): Observable<Refund[]> {
|
||||||
|
return this.paymentsService.getRefunds(this.idsService.generateRequestID(), invoiceID, paymentID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
import { SearchService } from '@dsh/api-codegen/anapi';
|
import { InlineResponse20010, PaymentSearchResult, SearchService } from '@dsh/api-codegen/anapi';
|
||||||
|
|
||||||
import { genXRequestID, toDateLike } from '../utils';
|
import { genXRequestID, toDateLike } from '../utils';
|
||||||
import { Duration, PaymentsSearchParams } from './model';
|
import { Duration, PaymentsSearchParams } from './model';
|
||||||
@ -17,7 +18,7 @@ export class PaymentSearchService {
|
|||||||
params: PaymentsSearchParams,
|
params: PaymentsSearchParams,
|
||||||
limit: number,
|
limit: number,
|
||||||
continuationToken?: string
|
continuationToken?: string
|
||||||
) {
|
): Observable<InlineResponse20010> {
|
||||||
return this.searchService.searchPayments(
|
return this.searchService.searchPayments(
|
||||||
genXRequestID(),
|
genXRequestID(),
|
||||||
toDateLike(fromTime),
|
toDateLike(fromTime),
|
||||||
@ -58,13 +59,13 @@ export class PaymentSearchService {
|
|||||||
params: PaymentsSearchParams,
|
params: PaymentsSearchParams,
|
||||||
limit: number,
|
limit: number,
|
||||||
continuationToken?: string
|
continuationToken?: string
|
||||||
) {
|
): Observable<InlineResponse20010> {
|
||||||
const from = moment().subtract(amount, unit).startOf('d').utc().format();
|
const from = moment().subtract(amount, unit).startOf('d').utc().format();
|
||||||
const to = moment().endOf('d').utc().format();
|
const to = moment().endOf('d').utc().format();
|
||||||
return this.searchPayments(from, to, params, limit, continuationToken);
|
return this.searchPayments(from, to, params, limit, continuationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
getPaymentByDuration(duration: Duration, invoiceID: string, paymentID: string) {
|
getPaymentByDuration(duration: Duration, invoiceID: string, paymentID: string): Observable<PaymentSearchResult> {
|
||||||
return this.searchPaymentsByDuration(
|
return this.searchPaymentsByDuration(
|
||||||
duration,
|
duration,
|
||||||
{
|
{
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
import { SenderModule as ApiSenderModule } from '../../api-codegen/sender';
|
import { SenderModule as ApiSenderModule } from '../../api-codegen/sender';
|
||||||
import { UuidGeneratorModule } from '../../shared/services';
|
import { IdGeneratorModule } from '../../shared/services';
|
||||||
import { MessagesService } from './services/messages/messages.service';
|
import { MessagesService } from './services/messages/messages.service';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [ApiSenderModule, UuidGeneratorModule],
|
imports: [ApiSenderModule, IdGeneratorModule],
|
||||||
providers: [MessagesService],
|
providers: [MessagesService],
|
||||||
})
|
})
|
||||||
export class SenderModule {}
|
export class SenderModule {}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
import { IdGeneratorService } from '@dsh/app/shared/services/id-generator/id-generator.service';
|
||||||
|
|
||||||
import { MessagesService as ApiMessagesService } from '../../../../api-codegen/sender';
|
import { MessagesService as ApiMessagesService } from '../../../../api-codegen/sender';
|
||||||
import { UuidGeneratorService } from '../../../../shared/services/uuid-generator/uuid-generator.service';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MessagesService {
|
export class MessagesService {
|
||||||
constructor(private messagesService: ApiMessagesService, private idGeneratorService: UuidGeneratorService) {}
|
constructor(private messagesService: ApiMessagesService, private idGeneratorService: IdGeneratorService) {}
|
||||||
|
|
||||||
sendFeedbackEmailMsg(text: string) {
|
sendFeedbackEmailMsg(text: string) {
|
||||||
return this.messagesService.sendFeedbackEmailMsg(this.idGeneratorService.generateUUID(), { text });
|
return this.messagesService.sendFeedbackEmailMsg(this.idGeneratorService.generateUUID(), { text });
|
||||||
|
@ -3,4 +3,4 @@ import { Shop } from '@dsh/api-codegen/capi';
|
|||||||
import { findShopByID } from './find-shop-by-id';
|
import { findShopByID } from './find-shop-by-id';
|
||||||
import { getShopName } from './get-shop-name';
|
import { getShopName } from './get-shop-name';
|
||||||
|
|
||||||
export const toShopName = (s: Shop[], shopID: string): string | null => getShopName(findShopByID(s, shopID));
|
export const getShopNameById = (s: Shop[], shopID: string): string | null => getShopName(findShopByID(s, shopID));
|
||||||
|
@ -6,7 +6,7 @@ import { catchError, first, mapTo, shareReplay, switchMap, switchMapTo, takeLast
|
|||||||
|
|
||||||
import { ApiShopsService, CAPIClaimsService, CAPIPartiesService, createTestShopClaimChangeset } from '@dsh/api';
|
import { ApiShopsService, CAPIClaimsService, CAPIPartiesService, createTestShopClaimChangeset } from '@dsh/api';
|
||||||
import { Claim } from '@dsh/api-codegen/capi';
|
import { Claim } from '@dsh/api-codegen/capi';
|
||||||
import { ErrorService } from '@dsh/app/shared';
|
import { NotificationService } from '@dsh/app/shared';
|
||||||
|
|
||||||
@UntilDestroy()
|
@UntilDestroy()
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -23,7 +23,7 @@ export class BootstrapService {
|
|||||||
private shopService: ApiShopsService,
|
private shopService: ApiShopsService,
|
||||||
private capiClaimsService: CAPIClaimsService,
|
private capiClaimsService: CAPIClaimsService,
|
||||||
private capiPartiesService: CAPIPartiesService,
|
private capiPartiesService: CAPIPartiesService,
|
||||||
private errorService: ErrorService,
|
private notificationService: NotificationService,
|
||||||
// TODO: Wait access check
|
// TODO: Wait access check
|
||||||
// private organizationsService: OrganizationsService,
|
// private organizationsService: OrganizationsService,
|
||||||
// private userService: UserService,
|
// private userService: UserService,
|
||||||
@ -43,8 +43,8 @@ export class BootstrapService {
|
|||||||
).pipe(
|
).pipe(
|
||||||
takeLast(1),
|
takeLast(1),
|
||||||
mapTo(true),
|
mapTo(true),
|
||||||
catchError((err) => {
|
catchError(() => {
|
||||||
this.errorService.error(err, this.transloco.translate('errors.bootstrapAppFailed'));
|
this.notificationService.error(this.transloco.translate('errors.bootstrapAppFailed'));
|
||||||
return of(false);
|
return of(false);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -7,6 +7,7 @@ import { anyString, instance, mock, verify, when } from 'ts-mockito';
|
|||||||
|
|
||||||
import { MessagesService } from '@dsh/api/sender';
|
import { MessagesService } from '@dsh/api/sender';
|
||||||
import { ErrorModule, ErrorService, NotificationService } from '@dsh/app/shared/services';
|
import { ErrorModule, ErrorService, NotificationService } from '@dsh/app/shared/services';
|
||||||
|
import { getTranslocoModule } from '@dsh/app/shared/tests/get-transloco-module';
|
||||||
|
|
||||||
import { FeedbackDialogComponent } from './feedback-dialog.component';
|
import { FeedbackDialogComponent } from './feedback-dialog.component';
|
||||||
|
|
||||||
@ -27,7 +28,7 @@ describe('FeedbackDialogComponent', () => {
|
|||||||
mockNotificationService = mock(NotificationService);
|
mockNotificationService = mock(NotificationService);
|
||||||
|
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [MatDialogModule, ErrorModule, NoopAnimationsModule],
|
imports: [getTranslocoModule(), MatDialogModule, ErrorModule, NoopAnimationsModule],
|
||||||
declarations: [FeedbackDialogComponent],
|
declarations: [FeedbackDialogComponent],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
@ -71,10 +72,11 @@ describe('FeedbackDialogComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("shouldn't send message", () => {
|
it("shouldn't send message", () => {
|
||||||
when(mockMessagesService.sendFeedbackEmailMsg(anyString())).thenReturn(throwError('Test error'));
|
const error = new Error('Test error');
|
||||||
|
when(mockMessagesService.sendFeedbackEmailMsg(anyString())).thenReturn(throwError(error));
|
||||||
component.send();
|
component.send();
|
||||||
verify(mockMessagesService.sendFeedbackEmailMsg('')).once();
|
verify(mockMessagesService.sendFeedbackEmailMsg('')).once();
|
||||||
verify(mockErrorService.error('Test error')).once();
|
verify(mockErrorService.error(error)).once();
|
||||||
verify(mockMatDialogRef.close()).never();
|
verify(mockMatDialogRef.close()).never();
|
||||||
expect().nothing();
|
expect().nothing();
|
||||||
});
|
});
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
"input",
|
"input",
|
||||||
"keyboard_arrow_down",
|
"keyboard_arrow_down",
|
||||||
"keyboard_arrow_up",
|
"keyboard_arrow_up",
|
||||||
|
"launch",
|
||||||
"logo",
|
"logo",
|
||||||
"logo_white",
|
"logo_white",
|
||||||
"mastercard",
|
"mastercard",
|
||||||
|
@ -10,7 +10,6 @@ import { InvoiceDetailsService } from './invoice-details.service';
|
|||||||
@Component({
|
@Component({
|
||||||
templateUrl: 'invoice-details.component.html',
|
templateUrl: 'invoice-details.component.html',
|
||||||
styleUrls: ['invoice-details.component.scss'],
|
styleUrls: ['invoice-details.component.scss'],
|
||||||
providers: [InvoiceDetailsService],
|
|
||||||
})
|
})
|
||||||
export class InvoiceDetailsComponent implements OnInit {
|
export class InvoiceDetailsComponent implements OnInit {
|
||||||
invoice$ = this.invoiceDetailsService.invoice$;
|
invoice$ = this.invoiceDetailsService.invoice$;
|
||||||
|
@ -6,11 +6,11 @@ import { MatIconModule } from '@angular/material/icon';
|
|||||||
import { TranslocoModule } from '@ngneat/transloco';
|
import { TranslocoModule } from '@ngneat/transloco';
|
||||||
|
|
||||||
import { InvoiceSearchService, PaymentSearchService } from '@dsh/api/search';
|
import { InvoiceSearchService, PaymentSearchService } from '@dsh/api/search';
|
||||||
|
import { ToMajorModule } from '@dsh/app/shared/pipes';
|
||||||
import { ButtonModule } from '@dsh/components/buttons';
|
import { ButtonModule } from '@dsh/components/buttons';
|
||||||
import { IndicatorsModule } from '@dsh/components/indicators';
|
import { IndicatorsModule } from '@dsh/components/indicators';
|
||||||
import { LayoutModule } from '@dsh/components/layout';
|
import { LayoutModule } from '@dsh/components/layout';
|
||||||
|
|
||||||
import { ToMajorModule } from '../../to-major';
|
|
||||||
import { CreatePaymentLinkModule } from '../create-payment-link';
|
import { CreatePaymentLinkModule } from '../create-payment-link';
|
||||||
import { ShopDetailsModule } from '../shop-details/shop-details.module';
|
import { ShopDetailsModule } from '../shop-details/shop-details.module';
|
||||||
import { CartComponent } from './cart/cart.component';
|
import { CartComponent } from './cart/cart.component';
|
||||||
@ -19,6 +19,7 @@ import { DetailsComponent } from './details/details.component';
|
|||||||
import { StatusDetailsItemComponent } from './details/status-details-item';
|
import { StatusDetailsItemComponent } from './details/status-details-item';
|
||||||
import { InvoiceDetailsRoutingModule } from './invoice-details-routing.module';
|
import { InvoiceDetailsRoutingModule } from './invoice-details-routing.module';
|
||||||
import { InvoiceDetailsComponent } from './invoice-details.component';
|
import { InvoiceDetailsComponent } from './invoice-details.component';
|
||||||
|
import { InvoiceDetailsService } from './invoice-details.service';
|
||||||
import { CreatePaymentLinkDialogComponent, PaymentLinkComponent } from './payment-link';
|
import { CreatePaymentLinkDialogComponent, PaymentLinkComponent } from './payment-link';
|
||||||
import { PaymentComponent } from './payments/payment/payment.component';
|
import { PaymentComponent } from './payments/payment/payment.component';
|
||||||
import { PaymentsComponent } from './payments/payments.component';
|
import { PaymentsComponent } from './payments/payments.component';
|
||||||
@ -50,6 +51,6 @@ import { PaymentsComponent } from './payments/payments.component';
|
|||||||
CreatePaymentLinkDialogComponent,
|
CreatePaymentLinkDialogComponent,
|
||||||
],
|
],
|
||||||
exports: [StatusDetailsItemComponent],
|
exports: [StatusDetailsItemComponent],
|
||||||
providers: [InvoiceSearchService, PaymentSearchService],
|
providers: [InvoiceSearchService, PaymentSearchService, InvoiceDetailsService],
|
||||||
})
|
})
|
||||||
export class InvoiceDetailsModule {}
|
export class InvoiceDetailsModule {}
|
||||||
|
4
src/app/sections/partial-fetcher/consts.ts
Normal file
4
src/app/sections/partial-fetcher/consts.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { InjectionToken } from '@angular/core';
|
||||||
|
|
||||||
|
export const DEBOUNCE_FETCHER_ACTION_TIME = new InjectionToken<number>('debounceFetcherActionTime');
|
||||||
|
export const DEFAULT_FETCHER_DEBOUNCE_ACTION_TIME = 300;
|
@ -1,3 +1,5 @@
|
|||||||
export * from './partial-fetcher';
|
export * from './partial-fetcher';
|
||||||
export * from './fetch-result';
|
export * from './fetch-result';
|
||||||
export * from './fetch-action';
|
export * from './fetch-action';
|
||||||
|
export * from './indicators-partial-fetcher.service';
|
||||||
|
export * from './consts';
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
import { Inject, Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { shareReplay } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { SEARCH_LIMIT } from '@dsh/app/sections/tokens';
|
||||||
|
import { booleanDebounceTime, mapToTimestamp } from '@dsh/operators';
|
||||||
|
|
||||||
|
import { DEBOUNCE_FETCHER_ACTION_TIME } from './consts';
|
||||||
|
import { PartialFetcher } from './partial-fetcher';
|
||||||
|
|
||||||
|
// TODO: remove this disable after making partial fetcher with injectable debounce time
|
||||||
|
/* tslint:disable:no-unused-variable */
|
||||||
|
@Injectable()
|
||||||
|
export abstract class IndicatorsPartialFetcher<R, P> extends PartialFetcher<R, P> {
|
||||||
|
isLoading$: Observable<boolean> = this.doAction$.pipe(booleanDebounceTime(), shareReplay(1));
|
||||||
|
lastUpdated$: Observable<string> = this.searchResult$.pipe(mapToTimestamp, shareReplay(1));
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(SEARCH_LIMIT)
|
||||||
|
protected searchLimit: number,
|
||||||
|
@Inject(DEBOUNCE_FETCHER_ACTION_TIME)
|
||||||
|
protected debounceActionTime: number
|
||||||
|
) {
|
||||||
|
super(debounceActionTime);
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||||
import { EMPTY, merge, Observable, of, Subject } from 'rxjs';
|
import { EMPTY, merge, Observable, of, Subject } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
debounceTime,
|
debounceTime,
|
||||||
@ -12,15 +13,16 @@ import {
|
|||||||
tap,
|
tap,
|
||||||
} from 'rxjs/operators';
|
} from 'rxjs/operators';
|
||||||
|
|
||||||
import { progress, SHARE_REPLAY_CONF } from '../../custom-operators';
|
import { progress, SHARE_REPLAY_CONF } from '@dsh/operators';
|
||||||
|
|
||||||
import { FetchAction } from './fetch-action';
|
import { FetchAction } from './fetch-action';
|
||||||
import { FetchFn } from './fetch-fn';
|
import { FetchFn } from './fetch-fn';
|
||||||
import { FetchResult } from './fetch-result';
|
import { FetchResult } from './fetch-result';
|
||||||
import { scanAction, scanFetchResult } from './operators';
|
import { scanAction, scanFetchResult } from './operators';
|
||||||
|
|
||||||
|
// TODO: make fetcher injectable
|
||||||
|
@UntilDestroy()
|
||||||
export abstract class PartialFetcher<R, P> {
|
export abstract class PartialFetcher<R, P> {
|
||||||
private action$ = new Subject<FetchAction<P>>();
|
|
||||||
|
|
||||||
readonly fetchResultChanges$: Observable<{ result: R[]; hasMore: boolean; continuationToken: string }>;
|
readonly fetchResultChanges$: Observable<{ result: R[]; hasMore: boolean; continuationToken: string }>;
|
||||||
|
|
||||||
readonly searchResult$: Observable<R[]>;
|
readonly searchResult$: Observable<R[]>;
|
||||||
@ -29,13 +31,16 @@ export abstract class PartialFetcher<R, P> {
|
|||||||
readonly doSearchAction$: Observable<boolean>;
|
readonly doSearchAction$: Observable<boolean>;
|
||||||
readonly errors$: Observable<any>;
|
readonly errors$: Observable<any>;
|
||||||
|
|
||||||
|
private action$ = new Subject<FetchAction<P>>();
|
||||||
|
|
||||||
|
// TODO: make a dependency for DI
|
||||||
constructor(debounceActionTime: number = 300) {
|
constructor(debounceActionTime: number = 300) {
|
||||||
const actionWithParams$ = this.getActionWithParams(debounceActionTime);
|
const actionWithParams$ = this.getActionWithParams(debounceActionTime);
|
||||||
const fetchResult$ = this.getFetchResult(actionWithParams$);
|
const fetchResult$ = this.getFetchResult(actionWithParams$);
|
||||||
|
|
||||||
this.fetchResultChanges$ = fetchResult$.pipe(
|
this.fetchResultChanges$ = fetchResult$.pipe(
|
||||||
map(({ result, continuationToken }) => ({
|
map(({ result, continuationToken }) => ({
|
||||||
result,
|
result: result ?? [],
|
||||||
continuationToken,
|
continuationToken,
|
||||||
hasMore: !!continuationToken,
|
hasMore: !!continuationToken,
|
||||||
})),
|
})),
|
||||||
@ -69,7 +74,9 @@ export abstract class PartialFetcher<R, P> {
|
|||||||
this.doSearchAction$,
|
this.doSearchAction$,
|
||||||
this.errors$,
|
this.errors$,
|
||||||
this.fetchResultChanges$
|
this.fetchResultChanges$
|
||||||
).subscribe();
|
)
|
||||||
|
.pipe(untilDestroyed(this))
|
||||||
|
.subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
search(value: P) {
|
search(value: P) {
|
||||||
|
@ -3,10 +3,10 @@ import { NgModule } from '@angular/core';
|
|||||||
import { FlexModule } from '@angular/flex-layout';
|
import { FlexModule } from '@angular/flex-layout';
|
||||||
import { TranslocoModule } from '@ngneat/transloco';
|
import { TranslocoModule } from '@ngneat/transloco';
|
||||||
|
|
||||||
|
import { ToMajorModule } from '@dsh/app/shared/pipes';
|
||||||
import { ButtonModule } from '@dsh/components/buttons';
|
import { ButtonModule } from '@dsh/components/buttons';
|
||||||
import { CardModule, DetailsItemModule } from '@dsh/components/layout';
|
import { CardModule, DetailsItemModule } from '@dsh/components/layout';
|
||||||
|
|
||||||
import { ToMajorModule } from '../../../to-major';
|
|
||||||
import { StatusDetailsItemModule } from '../status-details-item';
|
import { StatusDetailsItemModule } from '../status-details-item';
|
||||||
import { DetailsComponent } from './details.component';
|
import { DetailsComponent } from './details.component';
|
||||||
import { ErrorToMessagePipe } from './error-to-message.pipe';
|
import { ErrorToMessagePipe } from './error-to-message.pipe';
|
||||||
|
@ -5,10 +5,10 @@ import { MatIconModule } from '@angular/material/icon';
|
|||||||
import { TranslocoModule } from '@ngneat/transloco';
|
import { TranslocoModule } from '@ngneat/transloco';
|
||||||
|
|
||||||
import { InvoiceSearchService } from '@dsh/api/search';
|
import { InvoiceSearchService } from '@dsh/api/search';
|
||||||
|
import { ToMajorModule } from '@dsh/app/shared/pipes';
|
||||||
import { SpinnerModule } from '@dsh/components/indicators';
|
import { SpinnerModule } from '@dsh/components/indicators';
|
||||||
import { CardModule, DetailsItemModule, LayoutModule } from '@dsh/components/layout';
|
import { CardModule, DetailsItemModule, LayoutModule } from '@dsh/components/layout';
|
||||||
|
|
||||||
import { ToMajorModule } from '../../../to-major';
|
|
||||||
import { StatusDetailsItemModule } from '../status-details-item';
|
import { StatusDetailsItemModule } from '../status-details-item';
|
||||||
import { InvoiceDetailsComponent } from './invoice-details.component';
|
import { InvoiceDetailsComponent } from './invoice-details.component';
|
||||||
|
|
||||||
|
@ -7,12 +7,12 @@ import { TranslocoModule, TRANSLOCO_SCOPE } from '@ngneat/transloco';
|
|||||||
|
|
||||||
import { InvoiceModule } from '@dsh/api/invoice';
|
import { InvoiceModule } from '@dsh/api/invoice';
|
||||||
import { SearchModule } from '@dsh/api/search';
|
import { SearchModule } from '@dsh/api/search';
|
||||||
|
import { ToMajorModule } from '@dsh/app/shared/pipes';
|
||||||
import { ButtonModule } from '@dsh/components/buttons';
|
import { ButtonModule } from '@dsh/components/buttons';
|
||||||
import { IndicatorsModule } from '@dsh/components/indicators';
|
import { IndicatorsModule } from '@dsh/components/indicators';
|
||||||
import { LayoutModule } from '@dsh/components/layout';
|
import { LayoutModule } from '@dsh/components/layout';
|
||||||
|
|
||||||
import { HumanizeDurationModule } from '../../humanize-duration';
|
import { HumanizeDurationModule } from '../../humanize-duration';
|
||||||
import { ToMajorModule } from '../../to-major';
|
|
||||||
import { ShopDetailsModule } from '../shop-details/shop-details.module';
|
import { ShopDetailsModule } from '../shop-details/shop-details.module';
|
||||||
import { DetailsModule } from './details';
|
import { DetailsModule } from './details';
|
||||||
import { HoldDetailsModule } from './hold-details';
|
import { HoldDetailsModule } from './hold-details';
|
||||||
|
@ -1 +1,2 @@
|
|||||||
|
// TODO: remove module when operations/payments epic migration would be finished
|
||||||
export * from './create-refund.component';
|
export * from './create-refund.component';
|
||||||
|
@ -12,11 +12,11 @@ import { AccountService } from '@dsh/api/account';
|
|||||||
import { RefundService } from '@dsh/api/refund';
|
import { RefundService } from '@dsh/api/refund';
|
||||||
import { RefundSearchService } from '@dsh/api/search';
|
import { RefundSearchService } from '@dsh/api/search';
|
||||||
import { ApiShopsService } from '@dsh/api/shop';
|
import { ApiShopsService } from '@dsh/api/shop';
|
||||||
|
import { ToMajorModule } from '@dsh/app/shared/pipes';
|
||||||
import { ButtonModule } from '@dsh/components/buttons';
|
import { ButtonModule } from '@dsh/components/buttons';
|
||||||
import { FormControlsModule } from '@dsh/components/form-controls';
|
import { FormControlsModule } from '@dsh/components/form-controls';
|
||||||
import { DetailsItemModule, LayoutModule } from '@dsh/components/layout';
|
import { DetailsItemModule, LayoutModule } from '@dsh/components/layout';
|
||||||
|
|
||||||
import { ToMajorModule } from '../../../to-major';
|
|
||||||
import { StatusDetailsItemModule } from '../status-details-item';
|
import { StatusDetailsItemModule } from '../status-details-item';
|
||||||
import { CreateRefundComponent } from './create-refund';
|
import { CreateRefundComponent } from './create-refund';
|
||||||
import { RefundItemComponent } from './refund-item';
|
import { RefundItemComponent } from './refund-item';
|
||||||
|
@ -3,10 +3,10 @@ import { NgModule } from '@angular/core';
|
|||||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
import { TranslocoModule } from '@ngneat/transloco';
|
import { TranslocoModule } from '@ngneat/transloco';
|
||||||
|
|
||||||
|
import { ToMajorModule } from '@dsh/app/shared/pipes';
|
||||||
import { SpinnerModule } from '@dsh/components/indicators';
|
import { SpinnerModule } from '@dsh/components/indicators';
|
||||||
import { CardModule } from '@dsh/components/layout';
|
import { CardModule } from '@dsh/components/layout';
|
||||||
|
|
||||||
import { ToMajorModule } from '../../../../to-major';
|
|
||||||
import { PercentDifferenceModule } from '../percent-difference';
|
import { PercentDifferenceModule } from '../percent-difference';
|
||||||
import { StatItemComponent } from './stat-item.component';
|
import { StatItemComponent } from './stat-item.component';
|
||||||
|
|
||||||
|
@ -4,8 +4,8 @@ import { FlexLayoutModule } from '@angular/flex-layout';
|
|||||||
import { TranslocoModule } from '@ngneat/transloco';
|
import { TranslocoModule } from '@ngneat/transloco';
|
||||||
|
|
||||||
import { AnalyticsModule } from '@dsh/api/analytics';
|
import { AnalyticsModule } from '@dsh/api/analytics';
|
||||||
|
import { ToMajorModule } from '@dsh/app/shared/pipes';
|
||||||
|
|
||||||
import { ToMajorModule } from '../../../to-major';
|
|
||||||
import { BalancesComponent } from './balances.component';
|
import { BalancesComponent } from './balances.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -7,7 +7,7 @@ import { deepEqual, instance, mock, verify, when } from 'ts-mockito';
|
|||||||
import { Claim, Modification } from '@dsh/api-codegen/claim-management';
|
import { Claim, Modification } from '@dsh/api-codegen/claim-management';
|
||||||
import { ClaimsService } from '@dsh/api/claims';
|
import { ClaimsService } from '@dsh/api/claims';
|
||||||
import { createTestContractCreationModification } from '@dsh/api/claims/claim-party-modification';
|
import { createTestContractCreationModification } from '@dsh/api/claims/claim-party-modification';
|
||||||
import { UuidGeneratorService } from '@dsh/app/shared/services/uuid-generator/uuid-generator.service';
|
import { IdGeneratorService } from '@dsh/app/shared/services/id-generator/id-generator.service';
|
||||||
|
|
||||||
import { createTestContractPayoutToolModification } from '../../tests/create-test-contract-payout-tool-modification';
|
import { createTestContractPayoutToolModification } from '../../tests/create-test-contract-payout-tool-modification';
|
||||||
import { createTestInternationalLegalEntityModification } from '../../tests/create-test-international-legal-entity-modification';
|
import { createTestInternationalLegalEntityModification } from '../../tests/create-test-international-legal-entity-modification';
|
||||||
@ -20,11 +20,11 @@ const TEST_UUID = 'test-uuid';
|
|||||||
describe('CreateInternationalShopEntityService', () => {
|
describe('CreateInternationalShopEntityService', () => {
|
||||||
let service: CreateInternationalShopEntityService;
|
let service: CreateInternationalShopEntityService;
|
||||||
let mockClaimsService: ClaimsService;
|
let mockClaimsService: ClaimsService;
|
||||||
let mockUuidGeneratorService: UuidGeneratorService;
|
let mockIdGeneratorService: IdGeneratorService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockClaimsService = mock(ClaimsService);
|
mockClaimsService = mock(ClaimsService);
|
||||||
mockUuidGeneratorService = mock(UuidGeneratorService);
|
mockIdGeneratorService = mock(IdGeneratorService);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -36,8 +36,8 @@ describe('CreateInternationalShopEntityService', () => {
|
|||||||
useFactory: () => instance(mockClaimsService),
|
useFactory: () => instance(mockClaimsService),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: UuidGeneratorService,
|
provide: IdGeneratorService,
|
||||||
useFactory: () => instance(mockUuidGeneratorService),
|
useFactory: () => instance(mockIdGeneratorService),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -82,7 +82,7 @@ describe('CreateInternationalShopEntityService', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
when(mockUuidGeneratorService.generateUUID()).thenReturn(TEST_UUID);
|
when(mockIdGeneratorService.generateUUID()).thenReturn(TEST_UUID);
|
||||||
when(mockClaimsService.createClaim(deepEqual(modifications))).thenReturn(of(claim));
|
when(mockClaimsService.createClaim(deepEqual(modifications))).thenReturn(of(claim));
|
||||||
when(mockClaimsService.requestReviewClaimByID(claim.id, claim.revision)).thenReturn(of(null));
|
when(mockClaimsService.requestReviewClaimByID(claim.id, claim.revision)).thenReturn(of(null));
|
||||||
});
|
});
|
||||||
|
@ -12,13 +12,13 @@ import {
|
|||||||
makeShopLocation,
|
makeShopLocation,
|
||||||
} from '@dsh/api/claims/claim-party-modification';
|
} from '@dsh/api/claims/claim-party-modification';
|
||||||
import { createInternationalContractPayoutToolModification } from '@dsh/api/claims/claim-party-modification/claim-contract-modification/create-international-contract-payout-tool-modification';
|
import { createInternationalContractPayoutToolModification } from '@dsh/api/claims/claim-party-modification/claim-contract-modification/create-international-contract-payout-tool-modification';
|
||||||
import { UuidGeneratorService } from '@dsh/app/shared/services/uuid-generator/uuid-generator.service';
|
import { IdGeneratorService } from '@dsh/app/shared/services/id-generator/id-generator.service';
|
||||||
|
|
||||||
import { InternationalShopEntityFormValue } from '../../types/international-shop-entity-form-value';
|
import { InternationalShopEntityFormValue } from '../../types/international-shop-entity-form-value';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CreateInternationalShopEntityService {
|
export class CreateInternationalShopEntityService {
|
||||||
constructor(private claimsService: ClaimsService, private uuidGenerator: UuidGeneratorService) {}
|
constructor(private claimsService: ClaimsService, private idGenerator: IdGeneratorService) {}
|
||||||
|
|
||||||
createShop(creationData: InternationalShopEntityFormValue) {
|
createShop(creationData: InternationalShopEntityFormValue) {
|
||||||
return this.claimsService.createClaim(this.createClaimsModifications(creationData)).pipe(
|
return this.claimsService.createClaim(this.createClaimsModifications(creationData)).pipe(
|
||||||
@ -39,10 +39,10 @@ export class CreateInternationalShopEntityService {
|
|||||||
payoutTool,
|
payoutTool,
|
||||||
correspondentPayoutTool = null,
|
correspondentPayoutTool = null,
|
||||||
}: InternationalShopEntityFormValue): Modification[] {
|
}: InternationalShopEntityFormValue): Modification[] {
|
||||||
const contractorID = this.uuidGenerator.generateUUID();
|
const contractorID = this.idGenerator.generateUUID();
|
||||||
const contractID = this.uuidGenerator.generateUUID();
|
const contractID = this.idGenerator.generateUUID();
|
||||||
const payoutToolID = this.uuidGenerator.generateUUID();
|
const payoutToolID = this.idGenerator.generateUUID();
|
||||||
const shopID = this.uuidGenerator.generateUUID();
|
const shopID = this.idGenerator.generateUUID();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
createInternationalLegalEntityModification(contractorID, {
|
createInternationalLegalEntityModification(contractorID, {
|
||||||
|
@ -13,7 +13,7 @@ import { TranslocoModule } from '@ngneat/transloco';
|
|||||||
import { ClaimsModule } from '@dsh/api/claims';
|
import { ClaimsModule } from '@dsh/api/claims';
|
||||||
import { PayoutToolDetailsModule } from '@dsh/app/shared/components';
|
import { PayoutToolDetailsModule } from '@dsh/app/shared/components';
|
||||||
import { AutocompleteVirtualScrollModule } from '@dsh/app/shared/components/selects/autocomplete-virtual-scroll';
|
import { AutocompleteVirtualScrollModule } from '@dsh/app/shared/components/selects/autocomplete-virtual-scroll';
|
||||||
import { UuidGeneratorModule } from '@dsh/app/shared/services';
|
import { IdGeneratorModule } from '@dsh/app/shared/services';
|
||||||
import { ButtonModule } from '@dsh/components/buttons';
|
import { ButtonModule } from '@dsh/components/buttons';
|
||||||
import { FormatInputModule } from '@dsh/components/form-controls';
|
import { FormatInputModule } from '@dsh/components/form-controls';
|
||||||
import { DetailsItemModule } from '@dsh/components/layout';
|
import { DetailsItemModule } from '@dsh/components/layout';
|
||||||
@ -46,7 +46,7 @@ import { CreateRussianShopEntityService } from './services/create-russian-shop-e
|
|||||||
AutocompleteVirtualScrollModule,
|
AutocompleteVirtualScrollModule,
|
||||||
MatDialogModule,
|
MatDialogModule,
|
||||||
ClaimsModule,
|
ClaimsModule,
|
||||||
UuidGeneratorModule,
|
IdGeneratorModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
CreateRussianShopEntityComponent,
|
CreateRussianShopEntityComponent,
|
||||||
|
@ -8,7 +8,7 @@ import { Contract } from '@dsh/api-codegen/capi';
|
|||||||
import { Claim, Modification } from '@dsh/api-codegen/claim-management';
|
import { Claim, Modification } from '@dsh/api-codegen/claim-management';
|
||||||
import { ClaimsService } from '@dsh/api/claims';
|
import { ClaimsService } from '@dsh/api/claims';
|
||||||
import { createTestContractCreationModification } from '@dsh/api/claims/claim-party-modification';
|
import { createTestContractCreationModification } from '@dsh/api/claims/claim-party-modification';
|
||||||
import { UuidGeneratorService } from '@dsh/app/shared/services/uuid-generator/uuid-generator.service';
|
import { IdGeneratorService } from '@dsh/app/shared/services/id-generator/id-generator.service';
|
||||||
|
|
||||||
import { createTestLegalEntityModification } from '../../tests/create-test-legal-entity-modification';
|
import { createTestLegalEntityModification } from '../../tests/create-test-legal-entity-modification';
|
||||||
import { createTestRussianContractPayoutToolModification } from '../../tests/create-test-russian-contract-payout-tool-modification';
|
import { createTestRussianContractPayoutToolModification } from '../../tests/create-test-russian-contract-payout-tool-modification';
|
||||||
@ -21,11 +21,11 @@ const TEST_UUID = 'test-uuid';
|
|||||||
describe('CreateRussianShopEntityService', () => {
|
describe('CreateRussianShopEntityService', () => {
|
||||||
let service: CreateRussianShopEntityService;
|
let service: CreateRussianShopEntityService;
|
||||||
let mockClaimsService: ClaimsService;
|
let mockClaimsService: ClaimsService;
|
||||||
let mockUuidGeneratorService: UuidGeneratorService;
|
let mockIdGeneratorService: IdGeneratorService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockClaimsService = mock(ClaimsService);
|
mockClaimsService = mock(ClaimsService);
|
||||||
mockUuidGeneratorService = mock(UuidGeneratorService);
|
mockIdGeneratorService = mock(IdGeneratorService);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -37,8 +37,8 @@ describe('CreateRussianShopEntityService', () => {
|
|||||||
useFactory: () => instance(mockClaimsService),
|
useFactory: () => instance(mockClaimsService),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: UuidGeneratorService,
|
provide: IdGeneratorService,
|
||||||
useFactory: () => instance(mockUuidGeneratorService),
|
useFactory: () => instance(mockIdGeneratorService),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -90,7 +90,7 @@ describe('CreateRussianShopEntityService', () => {
|
|||||||
let modifications: Modification[];
|
let modifications: Modification[];
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
when(mockUuidGeneratorService.generateUUID()).thenReturn(TEST_UUID);
|
when(mockIdGeneratorService.generateUUID()).thenReturn(TEST_UUID);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@ -13,13 +13,13 @@ import {
|
|||||||
createShopCreationModification,
|
createShopCreationModification,
|
||||||
makeShopLocation,
|
makeShopLocation,
|
||||||
} from '@dsh/api/claims/claim-party-modification';
|
} from '@dsh/api/claims/claim-party-modification';
|
||||||
import { UuidGeneratorService } from '@dsh/app/shared/services/uuid-generator/uuid-generator.service';
|
import { IdGeneratorService } from '@dsh/app/shared/services/id-generator/id-generator.service';
|
||||||
|
|
||||||
import { RussianShopCreateData } from '../../types/russian-shop-create-data';
|
import { RussianShopCreateData } from '../../types/russian-shop-create-data';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CreateRussianShopEntityService {
|
export class CreateRussianShopEntityService {
|
||||||
constructor(private claimsService: ClaimsService, private uuidGenerator: UuidGeneratorService) {}
|
constructor(private claimsService: ClaimsService, private idGenerator: IdGeneratorService) {}
|
||||||
|
|
||||||
createShop(creationData: RussianShopCreateData): Observable<Claim> {
|
createShop(creationData: RussianShopCreateData): Observable<Claim> {
|
||||||
return this.claimsService.createClaim(this.createShopCreationModifications(creationData)).pipe(
|
return this.claimsService.createClaim(this.createShopCreationModifications(creationData)).pipe(
|
||||||
@ -37,11 +37,11 @@ export class CreateRussianShopEntityService {
|
|||||||
payoutToolID: shopPayoutToolID,
|
payoutToolID: shopPayoutToolID,
|
||||||
bankAccount: { account, bankName, bankPostAccount, bankBik },
|
bankAccount: { account, bankName, bankPostAccount, bankBik },
|
||||||
}: RussianShopCreateData): PartyModification[] {
|
}: RussianShopCreateData): PartyModification[] {
|
||||||
const contractorID = this.uuidGenerator.generateUUID();
|
const contractorID = this.idGenerator.generateUUID();
|
||||||
const contractID = this.uuidGenerator.generateUUID();
|
const contractID = this.idGenerator.generateUUID();
|
||||||
const shopID = this.uuidGenerator.generateUUID();
|
const shopID = this.idGenerator.generateUUID();
|
||||||
|
|
||||||
let payoutToolID = this.uuidGenerator.generateUUID();
|
let payoutToolID = this.idGenerator.generateUUID();
|
||||||
const payoutChangeset: PartyModification[] = [];
|
const payoutChangeset: PartyModification[] = [];
|
||||||
|
|
||||||
if (isNil(shopPayoutToolID)) {
|
if (isNil(shopPayoutToolID)) {
|
||||||
|
@ -2,7 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { TranslocoTestingModule } from '@ngneat/transloco';
|
import { TranslocoTestingModule } from '@ngneat/transloco';
|
||||||
|
|
||||||
import { QueryFilterModule } from '@dsh/components/filters/query-filter';
|
import { QueryFilterModule } from '@dsh/app/shared/components/filters/query-filter';
|
||||||
|
|
||||||
import { ShopQueryFilterComponent } from './shop-query-filter.component';
|
import { ShopQueryFilterComponent } from './shop-query-filter.component';
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { TranslocoModule } from '@ngneat/transloco';
|
import { TranslocoModule } from '@ngneat/transloco';
|
||||||
|
|
||||||
import { QueryFilterModule } from '@dsh/components/filters/query-filter';
|
import { QueryFilterModule } from '@dsh/app/shared/components/filters/query-filter';
|
||||||
|
|
||||||
import { ShopQueryFilterComponent } from './shop-query-filter.component';
|
import { ShopQueryFilterComponent } from './shop-query-filter.component';
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { ChangeDetectionStrategy } from '@angular/core';
|
import { ChangeDetectionStrategy } from '@angular/core';
|
||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { ToMajorModule } from '../../../../../../to-major';
|
import { ToMajorModule } from '@dsh/app/shared/pipes';
|
||||||
|
|
||||||
import { generateMockBalance } from '../../tests/generate-mock-balance';
|
import { generateMockBalance } from '../../tests/generate-mock-balance';
|
||||||
import { generateMockShop } from '../../tests/generate-mock-shop';
|
import { generateMockShop } from '../../tests/generate-mock-shop';
|
||||||
import { ShopBalanceComponent } from './shop-balance.component';
|
import { ShopBalanceComponent } from './shop-balance.component';
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
import { ToMajorModule } from '../../../../../../to-major';
|
import { ToMajorModule } from '@dsh/app/shared/pipes';
|
||||||
|
|
||||||
import { ShopBalanceComponent } from './shop-balance.component';
|
import { ShopBalanceComponent } from './shop-balance.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -3,13 +3,13 @@ import { NgModule } from '@angular/core';
|
|||||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
import { TranslocoModule } from '@ngneat/transloco';
|
import { TranslocoModule } from '@ngneat/transloco';
|
||||||
|
|
||||||
|
import { ToMajorModule } from '@dsh/app/shared/pipes';
|
||||||
import { EmptySearchResultModule } from '@dsh/components/empty-search-result';
|
import { EmptySearchResultModule } from '@dsh/components/empty-search-result';
|
||||||
import { SpinnerModule } from '@dsh/components/indicators';
|
import { SpinnerModule } from '@dsh/components/indicators';
|
||||||
import { LastUpdatedModule } from '@dsh/components/indicators/last-updated/last-updated.module';
|
import { LastUpdatedModule } from '@dsh/components/indicators/last-updated/last-updated.module';
|
||||||
import { AccordionModule, CardModule, ExpandPanelModule, RowModule } from '@dsh/components/layout';
|
import { AccordionModule, CardModule, ExpandPanelModule, RowModule } from '@dsh/components/layout';
|
||||||
import { ShowMorePanelModule } from '@dsh/components/show-more-panel';
|
import { ShowMorePanelModule } from '@dsh/components/show-more-panel';
|
||||||
|
|
||||||
import { ToMajorModule } from '../../../../../to-major';
|
|
||||||
import { ShopRowHeaderComponent } from './components/shop-row-header/shop-row-header.component';
|
import { ShopRowHeaderComponent } from './components/shop-row-header/shop-row-header.component';
|
||||||
import { ShopRowComponent } from './components/shop-row/shop-row.component';
|
import { ShopRowComponent } from './components/shop-row/shop-row.component';
|
||||||
import { ShopBalanceModule } from './shop-balance';
|
import { ShopBalanceModule } from './shop-balance';
|
||||||
@ -34,6 +34,6 @@ import { ShopsListComponent } from './shops-list.component';
|
|||||||
ExpandPanelModule,
|
ExpandPanelModule,
|
||||||
],
|
],
|
||||||
declarations: [ShopsListComponent, ShopRowHeaderComponent, ShopRowComponent],
|
declarations: [ShopsListComponent, ShopRowHeaderComponent, ShopRowComponent],
|
||||||
exports: [ShopsListComponent],
|
exports: [ShopsListComponent, ShopRowComponent],
|
||||||
})
|
})
|
||||||
export class ShopListModule {}
|
export class ShopListModule {}
|
||||||
|
@ -6,13 +6,13 @@ import { TranslocoTestingModule } from '@ngneat/transloco';
|
|||||||
import { of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
import { instance, mock, verify, when } from 'ts-mockito';
|
import { instance, mock, verify, when } from 'ts-mockito';
|
||||||
|
|
||||||
|
import { ToMajorModule } from '@dsh/app/shared/pipes';
|
||||||
import { EmptySearchResultModule } from '@dsh/components/empty-search-result';
|
import { EmptySearchResultModule } from '@dsh/components/empty-search-result';
|
||||||
import { SpinnerModule } from '@dsh/components/indicators';
|
import { SpinnerModule } from '@dsh/components/indicators';
|
||||||
import { LastUpdatedModule } from '@dsh/components/indicators/last-updated/last-updated.module';
|
import { LastUpdatedModule } from '@dsh/components/indicators/last-updated/last-updated.module';
|
||||||
import { AccordionModule, CardModule, ExpandPanelModule, RowModule } from '@dsh/components/layout';
|
import { AccordionModule, CardModule, ExpandPanelModule, RowModule } from '@dsh/components/layout';
|
||||||
import { ShowMorePanelModule } from '@dsh/components/show-more-panel';
|
import { ShowMorePanelModule } from '@dsh/components/show-more-panel';
|
||||||
|
|
||||||
import { ToMajorModule } from '../../../../../to-major';
|
|
||||||
import { generateMockShopsItemList } from '../tests/generate-mock-shops-item-list';
|
import { generateMockShopsItemList } from '../tests/generate-mock-shops-item-list';
|
||||||
import { ShopRowHeaderComponent } from './components/shop-row-header/shop-row-header.component';
|
import { ShopRowHeaderComponent } from './components/shop-row-header/shop-row-header.component';
|
||||||
import { ShopRowComponent } from './components/shop-row/shop-row.component';
|
import { ShopRowComponent } from './components/shop-row/shop-row.component';
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
{{ t('title') }}
|
{{ t('title') }}
|
||||||
</div>
|
</div>
|
||||||
<div *ngFor="let payment of payments$ | async as payments; index as i" fxLayout="column" fxLayoutGap="24px">
|
<div *ngFor="let payment of payments$ | async as payments; index as i" fxLayout="column" fxLayoutGap="24px">
|
||||||
<div class="dsh-subheading-2">{{ t('payment') }} #{{ payment.id }}</div>
|
<!-- TODO: refactor payments display -->
|
||||||
|
<div class="dsh-subheading-2">{{ t('payment') }} #{{ payment.invoiceID + '_' + payment.id }}</div>
|
||||||
<dsh-payment-details [payment]="payment"></dsh-payment-details>
|
<dsh-payment-details [payment]="payment"></dsh-payment-details>
|
||||||
<mat-divider *ngIf="i < payments.length - 1"></mat-divider>
|
<mat-divider *ngIf="i < payments.length - 1"></mat-divider>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,12 +11,11 @@ import {
|
|||||||
PaymentDetailsModule,
|
PaymentDetailsModule,
|
||||||
RefundDetailsModule as ApiRefundDetailsModule,
|
RefundDetailsModule as ApiRefundDetailsModule,
|
||||||
} from '@dsh/app/shared/components';
|
} from '@dsh/app/shared/components';
|
||||||
import { ApiModelRefsModule } from '@dsh/app/shared/pipes';
|
import { ApiModelRefsModule, ToMajorModule } from '@dsh/app/shared/pipes';
|
||||||
import { ButtonModule } from '@dsh/components/buttons';
|
import { ButtonModule } from '@dsh/components/buttons';
|
||||||
import { IndicatorsModule } from '@dsh/components/indicators';
|
import { IndicatorsModule } from '@dsh/components/indicators';
|
||||||
import { LayoutModule } from '@dsh/components/layout';
|
import { LayoutModule } from '@dsh/components/layout';
|
||||||
|
|
||||||
import { ToMajorModule } from '../../../../../../to-major';
|
|
||||||
import { CancelInvoiceModule } from './cancel-invoice';
|
import { CancelInvoiceModule } from './cancel-invoice';
|
||||||
import { InvoiceActionsComponent } from './components/invoice-actions/invoice-actions.component';
|
import { InvoiceActionsComponent } from './components/invoice-actions/invoice-actions.component';
|
||||||
import { InvoiceCartLineComponent } from './components/invoice-cart-info/cart-info/invoice-cart-line.component';
|
import { InvoiceCartLineComponent } from './components/invoice-cart-info/cart-info/invoice-cart-line.component';
|
||||||
|
@ -6,11 +6,10 @@ import { TranslocoModule } from '@ngneat/transloco';
|
|||||||
|
|
||||||
import { InvoiceModule } from '@dsh/api/invoice';
|
import { InvoiceModule } from '@dsh/api/invoice';
|
||||||
import { InvoiceDetailsModule as ApiInvoiceDetailsModule } from '@dsh/app/shared/components';
|
import { InvoiceDetailsModule as ApiInvoiceDetailsModule } from '@dsh/app/shared/components';
|
||||||
import { ApiModelRefsModule } from '@dsh/app/shared/pipes';
|
import { ApiModelRefsModule, ToMajorModule } from '@dsh/app/shared/pipes';
|
||||||
import { IndicatorsModule } from '@dsh/components/indicators';
|
import { IndicatorsModule } from '@dsh/components/indicators';
|
||||||
import { LayoutModule } from '@dsh/components/layout';
|
import { LayoutModule } from '@dsh/components/layout';
|
||||||
|
|
||||||
import { ToMajorModule } from '../../../../../to-major';
|
|
||||||
import { InvoiceRowHeaderComponent } from './components/invoice-row-header/invoice-row-header.component';
|
import { InvoiceRowHeaderComponent } from './components/invoice-row-header/invoice-row-header.component';
|
||||||
import { InvoiceRowComponent } from './components/invoice-row/invoice-row.component';
|
import { InvoiceRowComponent } from './components/invoice-row/invoice-row.component';
|
||||||
import { InvoiceDetailsModule } from './invoice-details';
|
import { InvoiceDetailsModule } from './invoice-details';
|
||||||
|
@ -15,6 +15,7 @@ import { TranslocoModule, TRANSLOCO_SCOPE } from '@ngneat/transloco';
|
|||||||
|
|
||||||
import { InvoiceModule } from '@dsh/api/invoice';
|
import { InvoiceModule } from '@dsh/api/invoice';
|
||||||
import { InvoiceDetailsModule } from '@dsh/app/shared/components';
|
import { InvoiceDetailsModule } from '@dsh/app/shared/components';
|
||||||
|
import { ToMajorModule } from '@dsh/app/shared/pipes';
|
||||||
import { ButtonModule } from '@dsh/components/buttons';
|
import { ButtonModule } from '@dsh/components/buttons';
|
||||||
import { EmptySearchResultModule } from '@dsh/components/empty-search-result';
|
import { EmptySearchResultModule } from '@dsh/components/empty-search-result';
|
||||||
import { FormControlsModule, RangeDatepickerModule } from '@dsh/components/form-controls';
|
import { FormControlsModule, RangeDatepickerModule } from '@dsh/components/form-controls';
|
||||||
@ -25,7 +26,6 @@ import { ShowMorePanelModule } from '@dsh/components/show-more-panel';
|
|||||||
import { TableModule } from '@dsh/components/table';
|
import { TableModule } from '@dsh/components/table';
|
||||||
|
|
||||||
import { LanguageModule } from '../../../../language';
|
import { LanguageModule } from '../../../../language';
|
||||||
import { ToMajorModule } from '../../../../to-major';
|
|
||||||
import { ShopSelectorModule } from '../../../shop-selector';
|
import { ShopSelectorModule } from '../../../shop-selector';
|
||||||
import { CreateInvoiceModule } from './create-invoice';
|
import { CreateInvoiceModule } from './create-invoice';
|
||||||
import { InvoicesListModule } from './invoices-list';
|
import { InvoicesListModule } from './invoices-list';
|
||||||
|
@ -2,7 +2,7 @@ import { Observable } from 'rxjs';
|
|||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
import { Invoice, Shop } from '@dsh/api-codegen/anapi';
|
import { Invoice, Shop } from '@dsh/api-codegen/anapi';
|
||||||
import { toShopName } from '@dsh/api/shop/utils';
|
import { getShopNameById } from '@dsh/api/shop/utils';
|
||||||
|
|
||||||
import { InvoicesTableData } from './table';
|
import { InvoicesTableData } from './table';
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ const toInvoiceTableData = (
|
|||||||
status,
|
status,
|
||||||
createdAt: createdAt as any,
|
createdAt: createdAt as any,
|
||||||
invoiceID: id,
|
invoiceID: id,
|
||||||
shopName: toShopName(s, shopID),
|
shopName: getShopNameById(s, shopID),
|
||||||
product,
|
product,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -24,3 +24,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<dsh-scroll-up [hideAfter]="200"></dsh-scroll-up>
|
||||||
|
@ -5,12 +5,21 @@ import { TranslocoModule } from '@ngneat/transloco';
|
|||||||
|
|
||||||
import { SearchModule } from '@dsh/api/search';
|
import { SearchModule } from '@dsh/api/search';
|
||||||
import { LayoutModule } from '@dsh/components/layout';
|
import { LayoutModule } from '@dsh/components/layout';
|
||||||
|
import { ScrollUpModule } from '@dsh/components/navigation';
|
||||||
|
|
||||||
import { OperationsRoutingModule } from './operations-routing.module';
|
import { OperationsRoutingModule } from './operations-routing.module';
|
||||||
import { OperationsComponent } from './operations.component';
|
import { OperationsComponent } from './operations.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, OperationsRoutingModule, LayoutModule, FlexLayoutModule, SearchModule, TranslocoModule],
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
OperationsRoutingModule,
|
||||||
|
LayoutModule,
|
||||||
|
FlexLayoutModule,
|
||||||
|
SearchModule,
|
||||||
|
TranslocoModule,
|
||||||
|
ScrollUpModule,
|
||||||
|
],
|
||||||
declarations: [OperationsComponent],
|
declarations: [OperationsComponent],
|
||||||
})
|
})
|
||||||
export class OperationsModule {}
|
export class OperationsModule {}
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
import { InjectionToken } from '@angular/core';
|
||||||
|
|
||||||
|
export const PAYMENTS_UPDATE_DELAY_TOKEN = new InjectionToken<number>('payments-update-delay-token');
|
||||||
|
export const DEFAULT_PAYMENTS_UPDATE_DELAY = 300;
|
@ -0,0 +1 @@
|
|||||||
|
export * from './payments.module';
|
@ -1,26 +0,0 @@
|
|||||||
import { Observable } from 'rxjs';
|
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { PaymentSearchResult, Shop } from '@dsh/api-codegen/capi';
|
|
||||||
import { toShopName } from '@dsh/api/shop/utils';
|
|
||||||
|
|
||||||
import { PaymentsTableData } from './table';
|
|
||||||
|
|
||||||
const toPaymentTableData = (
|
|
||||||
{ amount, status, statusChangedAt, invoiceID, shopID, id, currency }: PaymentSearchResult,
|
|
||||||
s: Shop[]
|
|
||||||
): PaymentsTableData | null => ({
|
|
||||||
amount,
|
|
||||||
status,
|
|
||||||
currency,
|
|
||||||
invoiceID,
|
|
||||||
statusChangedAt: statusChangedAt as any,
|
|
||||||
paymentID: id,
|
|
||||||
shopName: toShopName(s, shopID),
|
|
||||||
});
|
|
||||||
|
|
||||||
const paymentsToTableData = (searchResult: PaymentSearchResult[], s: Shop[]) =>
|
|
||||||
searchResult.map((r) => toPaymentTableData(r, s));
|
|
||||||
|
|
||||||
export const mapToPaymentsTableData = (s: Observable<[PaymentSearchResult[], Shop[]]>) =>
|
|
||||||
s.pipe(map(([searchResult, shops]) => paymentsToTableData(searchResult, shops)));
|
|
@ -0,0 +1,18 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
|
import { MatDividerModule } from '@angular/material/divider';
|
||||||
|
import { TranslocoModule } from '@ngneat/transloco';
|
||||||
|
|
||||||
|
import { BaseDialogModule } from '@dsh/app/shared/components/dialog/base-dialog';
|
||||||
|
import { ButtonModule } from '@dsh/components/buttons';
|
||||||
|
|
||||||
|
import { AdditionalFiltersService } from './additional-filters.service';
|
||||||
|
import { DialogFiltersComponent } from './components/dialog-filters.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [CommonModule, BaseDialogModule, MatDividerModule, FlexLayoutModule, ButtonModule, TranslocoModule],
|
||||||
|
declarations: [DialogFiltersComponent],
|
||||||
|
providers: [AdditionalFiltersService],
|
||||||
|
})
|
||||||
|
export class AdditionalFiltersModule {}
|
@ -0,0 +1,13 @@
|
|||||||
|
// TODO: implement unit tests
|
||||||
|
// describe('AdditionalFiltersService', () => {
|
||||||
|
// let service: AdditionalFiltersService;
|
||||||
|
//
|
||||||
|
// beforeEach(() => {
|
||||||
|
// TestBed.configureTestingModule({});
|
||||||
|
// service = TestBed.inject(AdditionalFiltersService);
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// it('should be created', () => {
|
||||||
|
// expect(service).toBeTruthy();
|
||||||
|
// });
|
||||||
|
// });
|
@ -0,0 +1,25 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { take } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { PaymentsAdditionalFilters } from '../types/payments-additional-filters';
|
||||||
|
import { DialogFiltersComponent } from './components/dialog-filters.component';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AdditionalFiltersService {
|
||||||
|
constructor(private dialog: MatDialog) {}
|
||||||
|
|
||||||
|
openFiltersDialog(data: PaymentsAdditionalFilters): Observable<PaymentsAdditionalFilters> {
|
||||||
|
return this.dialog
|
||||||
|
.open<DialogFiltersComponent, PaymentsAdditionalFilters>(DialogFiltersComponent, {
|
||||||
|
panelClass: 'fill-bleed-dialog',
|
||||||
|
width: '552px',
|
||||||
|
minHeight: '400px',
|
||||||
|
disableClose: true,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
.afterClosed()
|
||||||
|
.pipe(take(1));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
<ng-container *transloco="let t; read: 'filters'">
|
||||||
|
<dsh-base-dialog [title]="t('additionalFilters')">
|
||||||
|
<!-- TODO: add title close button -->
|
||||||
|
<div>filters should be here</div>
|
||||||
|
<div actions>
|
||||||
|
<div fxLayout="row" fxLayoutAlign="space-between center">
|
||||||
|
<button dsh-button (click)="clear()">
|
||||||
|
{{ t('clearParams') }}
|
||||||
|
</button>
|
||||||
|
<button dsh-button color="accent" (click)="confirm()">
|
||||||
|
{{ t('save') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dsh-base-dialog>
|
||||||
|
</ng-container>
|
@ -0,0 +1,5 @@
|
|||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
// TODO: implement unit tests
|
||||||
|
// describe('DialogFiltersComponent', () => {
|
||||||
|
// let component: DialogFiltersComponent;
|
||||||
|
// let fixture: ComponentFixture<DialogFiltersComponent>;
|
||||||
|
//
|
||||||
|
// beforeEach(async(() => {
|
||||||
|
// TestBed.configureTestingModule({
|
||||||
|
// declarations: [DialogFiltersComponent],
|
||||||
|
// }).compileComponents();
|
||||||
|
// }));
|
||||||
|
//
|
||||||
|
// beforeEach(() => {
|
||||||
|
// fixture = TestBed.createComponent(DialogFiltersComponent);
|
||||||
|
// component = fixture.componentInstance;
|
||||||
|
// fixture.detectChanges();
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// it('should create', () => {
|
||||||
|
// expect(component).toBeTruthy();
|
||||||
|
// });
|
||||||
|
// });
|
@ -0,0 +1,25 @@
|
|||||||
|
import { Component, Inject } from '@angular/core';
|
||||||
|
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||||
|
|
||||||
|
import { PaymentsAdditionalFilters } from '../../types/payments-additional-filters';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'dsh-dialog-filters',
|
||||||
|
templateUrl: 'dialog-filters.component.html',
|
||||||
|
styleUrls: ['dialog-filters.component.scss'],
|
||||||
|
})
|
||||||
|
export class DialogFiltersComponent {
|
||||||
|
constructor(
|
||||||
|
@Inject(MAT_DIALOG_DATA) public data: PaymentsAdditionalFilters,
|
||||||
|
private dialogRef: MatDialogRef<DialogFiltersComponent, PaymentsAdditionalFilters>
|
||||||
|
) {}
|
||||||
|
|
||||||
|
clear(): void {
|
||||||
|
// TODO: fix logic. Should not close dialog. Only reset params
|
||||||
|
this.dialogRef.close({});
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm(): void {
|
||||||
|
this.dialogRef.close({ anyField: 'some value' });
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './additional-filters.module';
|
||||||
|
export * from './additional-filters.service';
|
@ -0,0 +1,24 @@
|
|||||||
|
<ng-container *transloco="let t; scope: 'operations'; read: 'operations.payments.filter'">
|
||||||
|
<dsh-filter
|
||||||
|
[title]="titleValues ? t('binPan') + ' · ' + titleValues : t('binPan')"
|
||||||
|
[active]="isActive"
|
||||||
|
(opened)="popupOpened()"
|
||||||
|
(closed)="popupClosed()"
|
||||||
|
>
|
||||||
|
<div class="card-bin-pan-filter-content">
|
||||||
|
<dsh-filter-button-content>
|
||||||
|
<form [formGroup]="form" fxLayout="column">
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ t('first6') }}</mat-label>
|
||||||
|
<dsh-format-input format="bin" formControlName="bin"></dsh-format-input>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ t('last4') }}</mat-label>
|
||||||
|
<dsh-format-input format="lastDigits" formControlName="pan"></dsh-format-input>
|
||||||
|
</mat-form-field>
|
||||||
|
</form>
|
||||||
|
</dsh-filter-button-content>
|
||||||
|
<dsh-filter-button-actions (clear)="clear()" (save)="save()"></dsh-filter-button-actions>
|
||||||
|
</div>
|
||||||
|
</dsh-filter>
|
||||||
|
</ng-container>
|
@ -0,0 +1,7 @@
|
|||||||
|
$content-size: 360px;
|
||||||
|
|
||||||
|
.card-bin-pan-filter {
|
||||||
|
&-content {
|
||||||
|
width: $content-size;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,215 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { instance, mock, verify } from 'ts-mockito';
|
||||||
|
|
||||||
|
import { getTranslocoModule } from '@dsh/app/shared/tests/get-transloco-module';
|
||||||
|
import { FilterComponent, FilterModule } from '@dsh/components/filters/filter';
|
||||||
|
import { FormatInputModule } from '@dsh/components/form-controls';
|
||||||
|
import { ComponentChange } from '@dsh/type-utils';
|
||||||
|
|
||||||
|
import { CardBinPanFilterComponent } from './card-bin-pan-filter.component';
|
||||||
|
import { CardBinPan } from './types/card-bin-pan';
|
||||||
|
|
||||||
|
describe('CardBinPanFilterComponent', () => {
|
||||||
|
let component: CardBinPanFilterComponent;
|
||||||
|
let fixture: ComponentFixture<CardBinPanFilterComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
getTranslocoModule(),
|
||||||
|
NoopAnimationsModule,
|
||||||
|
FilterModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
FormatInputModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
FlexLayoutModule,
|
||||||
|
],
|
||||||
|
declarations: [CardBinPanFilterComponent],
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CardBinPanFilterComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ngOnChanges', () => {
|
||||||
|
function tickBinPanChanges(
|
||||||
|
binPan: CardBinPan,
|
||||||
|
change: Partial<ComponentChange<CardBinPanFilterComponent, 'binPan'>> = {}
|
||||||
|
): void {
|
||||||
|
component.binPan = binPan;
|
||||||
|
component.ngOnChanges({
|
||||||
|
binPan: {
|
||||||
|
previousValue: undefined,
|
||||||
|
currentValue: component.binPan,
|
||||||
|
firstChange: false,
|
||||||
|
isFirstChange(): boolean {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
...change,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tickBinPanChanges(
|
||||||
|
{ bin: null, pan: null },
|
||||||
|
{
|
||||||
|
firstChange: true,
|
||||||
|
isFirstChange(): boolean {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update on any change', () => {
|
||||||
|
expect(component.titleValues).toBe('');
|
||||||
|
expect(component.form.value).toEqual({ bin: null, pan: null });
|
||||||
|
expect(component.isActive).toBe(false);
|
||||||
|
|
||||||
|
tickBinPanChanges({ bin: '123456', pan: '1234' });
|
||||||
|
|
||||||
|
expect(component.titleValues).toBe('1234 56** **** 1234');
|
||||||
|
expect(component.form.value).toEqual({ bin: '123456', pan: '1234' });
|
||||||
|
expect(component.isActive).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('update title', () => {
|
||||||
|
it('should format title using values', () => {
|
||||||
|
tickBinPanChanges({ bin: '111122', pan: '4444' });
|
||||||
|
|
||||||
|
expect(component.titleValues).toBe('1111 22** **** 4444');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format only bin', () => {
|
||||||
|
tickBinPanChanges({ bin: '123456', pan: null });
|
||||||
|
|
||||||
|
expect(component.titleValues).toBe('1234 56** **** ****');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format only pan', () => {
|
||||||
|
tickBinPanChanges({ bin: null, pan: '1234' });
|
||||||
|
|
||||||
|
expect(component.titleValues).toBe('**** **** **** 1234');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('update isActive status', () => {
|
||||||
|
it('should set isActive to true if any of values in binPan exist', () => {
|
||||||
|
tickBinPanChanges({ bin: null, pan: '4444' });
|
||||||
|
|
||||||
|
expect(component.isActive).toBe(true);
|
||||||
|
|
||||||
|
tickBinPanChanges({ bin: '111122', pan: null });
|
||||||
|
|
||||||
|
expect(component.isActive).toBe(true);
|
||||||
|
|
||||||
|
tickBinPanChanges({ bin: '111122', pan: '4444' });
|
||||||
|
|
||||||
|
expect(component.isActive).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set isActive to true if none of values in binPan exist', () => {
|
||||||
|
tickBinPanChanges({ bin: null, pan: null });
|
||||||
|
|
||||||
|
expect(component.isActive).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('update form', () => {
|
||||||
|
it('should form using provided nullable binPan values', () => {
|
||||||
|
tickBinPanChanges({ bin: null, pan: null });
|
||||||
|
|
||||||
|
expect(component.form.value).toEqual({ bin: null, pan: null });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should form using provided only bin binPan values', () => {
|
||||||
|
tickBinPanChanges({ bin: '123456', pan: null });
|
||||||
|
|
||||||
|
expect(component.form.value).toEqual({ bin: '123456', pan: null });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should form using provided only pan binPan values', () => {
|
||||||
|
tickBinPanChanges({ bin: null, pan: '1234' });
|
||||||
|
|
||||||
|
expect(component.form.value).toEqual({ bin: null, pan: '1234' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should form using provided fully binPan values', () => {
|
||||||
|
tickBinPanChanges({ bin: '123456', pan: '1234' });
|
||||||
|
|
||||||
|
expect(component.form.value).toEqual({ bin: '123456', pan: '1234' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should form using provided undefined binPan value', () => {
|
||||||
|
tickBinPanChanges(undefined);
|
||||||
|
|
||||||
|
expect(component.form.value).toEqual({ bin: null, pan: null });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onOpened', () => {
|
||||||
|
it('should update form value', () => {
|
||||||
|
expect(component.form.value).toEqual({ bin: null, pan: null });
|
||||||
|
|
||||||
|
component.binPan = { bin: '123456', pan: '1234' };
|
||||||
|
component.popupOpened();
|
||||||
|
|
||||||
|
expect(component.form.value).toEqual({ bin: '123456', pan: '1234' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onClosed', () => {
|
||||||
|
it('should emit filter changed using form values', () => {
|
||||||
|
const spyOnFilterChanged = spyOn(component.filterChanged, 'emit').and.callThrough();
|
||||||
|
|
||||||
|
component.form.setValue({ bin: '123456', pan: null });
|
||||||
|
component.popupClosed();
|
||||||
|
|
||||||
|
expect(spyOnFilterChanged).toHaveBeenCalledTimes(1);
|
||||||
|
expect(spyOnFilterChanged).toHaveBeenCalledWith({ bin: '123456', pan: null });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update title and active status using form values', () => {
|
||||||
|
component.form.setValue({ bin: null, pan: '1234' });
|
||||||
|
component.popupClosed();
|
||||||
|
|
||||||
|
expect(component.titleValues).toBe('**** **** **** 1234');
|
||||||
|
expect(component.isActive).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onSave', () => {
|
||||||
|
it('should close filter component', () => {
|
||||||
|
const mockFilterComponent = mock(FilterComponent);
|
||||||
|
component.filter = instance(mockFilterComponent);
|
||||||
|
|
||||||
|
component.save();
|
||||||
|
|
||||||
|
verify(mockFilterComponent.close()).once();
|
||||||
|
expect().nothing();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onClear', () => {
|
||||||
|
it('should reset form data', () => {
|
||||||
|
component.form.setValue({ bin: '123456', pan: '1234' });
|
||||||
|
|
||||||
|
component.clear();
|
||||||
|
|
||||||
|
expect(component.form.value).toEqual({ bin: null, pan: null });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,99 @@
|
|||||||
|
import { Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@angular/core';
|
||||||
|
import { FormBuilder, FormGroup } from '@ngneat/reactive-forms';
|
||||||
|
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||||
|
import isObject from 'lodash.isobject';
|
||||||
|
|
||||||
|
import { makeMaskedCardEnd, makeMaskedCardStart, splitCardNumber } from '@dsh/app/shared/utils/card-formatter';
|
||||||
|
import { FilterComponent } from '@dsh/components/filters/filter';
|
||||||
|
import { binValidator, lastDigitsValidator } from '@dsh/components/form-controls';
|
||||||
|
import { ComponentChanges } from '@dsh/type-utils';
|
||||||
|
|
||||||
|
import { CardBinPan } from './types/card-bin-pan';
|
||||||
|
|
||||||
|
@UntilDestroy()
|
||||||
|
@Component({
|
||||||
|
selector: 'dsh-card-bin-pan-filter',
|
||||||
|
templateUrl: './card-bin-pan-filter.component.html',
|
||||||
|
styleUrls: ['./card-bin-pan-filter.component.scss'],
|
||||||
|
})
|
||||||
|
export class CardBinPanFilterComponent implements OnChanges {
|
||||||
|
@ViewChild(FilterComponent) filter: FilterComponent;
|
||||||
|
|
||||||
|
@Input() binPan: Partial<CardBinPan>;
|
||||||
|
|
||||||
|
@Output() filterChanged = new EventEmitter<Partial<CardBinPan>>();
|
||||||
|
|
||||||
|
form: FormGroup<CardBinPan> = this.formBuilder.group({
|
||||||
|
bin: [null, binValidator],
|
||||||
|
pan: [null, lastDigitsValidator],
|
||||||
|
});
|
||||||
|
|
||||||
|
titleValues: string;
|
||||||
|
isActive = false;
|
||||||
|
|
||||||
|
constructor(private formBuilder: FormBuilder) {}
|
||||||
|
|
||||||
|
ngOnChanges(changes: ComponentChanges<CardBinPanFilterComponent>): void {
|
||||||
|
if (isObject(changes.binPan)) {
|
||||||
|
this.updateFilterForm(changes.binPan.currentValue);
|
||||||
|
this.updateBadgePresentation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
popupOpened(): void {
|
||||||
|
this.updateFilterForm(this.binPan);
|
||||||
|
}
|
||||||
|
|
||||||
|
popupClosed(): void {
|
||||||
|
this.saveFilterData();
|
||||||
|
}
|
||||||
|
|
||||||
|
save(): void {
|
||||||
|
this.filter.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(): void {
|
||||||
|
this.clearForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
private saveFilterData(): void {
|
||||||
|
this.updateBadgePresentation();
|
||||||
|
this.filterChanged.emit(this.form.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateFilterForm(binPan: Partial<CardBinPan> | undefined): void {
|
||||||
|
const { bin = null, pan = null } = binPan ?? {};
|
||||||
|
this.form.setValue({
|
||||||
|
bin,
|
||||||
|
pan,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateBadgePresentation(): void {
|
||||||
|
this.updateTitleValues();
|
||||||
|
this.updateActiveStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateActiveStatus(): void {
|
||||||
|
const { bin, pan } = this.form.value;
|
||||||
|
this.isActive = Boolean(bin) || Boolean(pan);
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateTitleValues(): void {
|
||||||
|
const { bin, pan } = this.form.controls;
|
||||||
|
const binString = bin.valid && Boolean(bin.value) ? bin.value : '';
|
||||||
|
const panString = pan.valid && Boolean(pan.value) ? pan.value : '';
|
||||||
|
const maskedBinPart = makeMaskedCardStart(binString, 12);
|
||||||
|
const maskedPanPart = makeMaskedCardEnd(panString, 4);
|
||||||
|
const filterValues = splitCardNumber(`${maskedBinPart}${maskedPanPart}`);
|
||||||
|
|
||||||
|
this.titleValues = Boolean(binString) || Boolean(panString) ? `${filterValues}` : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearForm(): void {
|
||||||
|
this.updateFilterForm({
|
||||||
|
bin: null,
|
||||||
|
pan: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
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 { TranslocoModule } from '@ngneat/transloco';
|
||||||
|
|
||||||
|
import { FilterModule } from '@dsh/components/filters/filter';
|
||||||
|
import { FormatInputModule } from '@dsh/components/form-controls';
|
||||||
|
|
||||||
|
import { CardBinPanFilterComponent } from './card-bin-pan-filter.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FilterModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
FormatInputModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
TranslocoModule,
|
||||||
|
FlexLayoutModule,
|
||||||
|
],
|
||||||
|
declarations: [CardBinPanFilterComponent],
|
||||||
|
exports: [CardBinPanFilterComponent],
|
||||||
|
})
|
||||||
|
export class CardBinPanFilterModule {}
|
@ -0,0 +1,2 @@
|
|||||||
|
export const BIN_LENGTH = 6;
|
||||||
|
export const PAN_LENGTH = 4;
|
@ -0,0 +1,4 @@
|
|||||||
|
export * from './card-bin-pan-filter.module';
|
||||||
|
export * from './card-bin-pan-filter.component';
|
||||||
|
export * from './types/card-bin-pan';
|
||||||
|
export { BIN_LENGTH, PAN_LENGTH } from './consts';
|
@ -0,0 +1,4 @@
|
|||||||
|
export interface CardBinPan {
|
||||||
|
bin: string;
|
||||||
|
pan: string;
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './payments-filters.module';
|
||||||
|
export * from './payments-filters.component';
|
@ -0,0 +1,27 @@
|
|||||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
|
||||||
|
<ng-container *ngIf="filtersData$ | async as filters">
|
||||||
|
<dsh-daterange-filter
|
||||||
|
[selected]="filters.daterange"
|
||||||
|
(selectedChange)="dateRangeChange($event)"
|
||||||
|
></dsh-daterange-filter>
|
||||||
|
<dsh-invoices-filter
|
||||||
|
[selected]="filters.invoiceIDs"
|
||||||
|
(selectedChange)="invoiceSelectionChange($event)"
|
||||||
|
></dsh-invoices-filter>
|
||||||
|
<dsh-filter-shops
|
||||||
|
[shops]="shops$ | async"
|
||||||
|
[selected]="selectedShops$ | async"
|
||||||
|
(selectedChange)="shopSelectionChange($event)"
|
||||||
|
></dsh-filter-shops>
|
||||||
|
<dsh-card-bin-pan-filter
|
||||||
|
[binPan]="filters.binPan"
|
||||||
|
(filterChanged)="binPanChanged($event)"
|
||||||
|
></dsh-card-bin-pan-filter>
|
||||||
|
<!-- hidden till additional filters would be supported-->
|
||||||
|
<!-- <ng-container *transloco="let t; read: 'filters'">-->
|
||||||
|
<!-- <dsh-filter-button [active]="isAdditionalFilterApplied" (click)="openFiltersDialog()">-->
|
||||||
|
<!-- {{ t('additionalFilters') }}-->
|
||||||
|
<!-- </dsh-filter-button>-->
|
||||||
|
<!-- </ng-container>-->
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
@ -0,0 +1,383 @@
|
|||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { deepEqual, instance, mock, verify, when } from 'ts-mockito';
|
||||||
|
|
||||||
|
import { Shop } from '@dsh/api-codegen/capi';
|
||||||
|
import { PaymentInstitutionRealm } from '@dsh/api/model';
|
||||||
|
import { getTranslocoModule } from '@dsh/app/shared/tests/get-transloco-module';
|
||||||
|
|
||||||
|
import { AdditionalFiltersService } from './additional-filters';
|
||||||
|
import { CardBinPanFilterModule } from './card-bin-pan-filter';
|
||||||
|
import { PaymentsFiltersComponent } from './payments-filters.component';
|
||||||
|
import { PaymentsFiltersService } from './services/payments-filters/payments-filters.service';
|
||||||
|
import { ShopsSelectionManagerService } from './services/shops-selection-manager/shops-selection-manager.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'dsh-daterange-filter',
|
||||||
|
template: '',
|
||||||
|
})
|
||||||
|
class MockDaterangeFilterComponent {
|
||||||
|
@Input() selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'dsh-invoices-filter',
|
||||||
|
template: '',
|
||||||
|
})
|
||||||
|
class MockInvoicesFilterComponent {
|
||||||
|
@Input() selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'dsh-filter-shops',
|
||||||
|
template: '',
|
||||||
|
})
|
||||||
|
class MockFilterShopsComponent {
|
||||||
|
@Input() selected;
|
||||||
|
@Input() shops;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'dsh-filter-button',
|
||||||
|
template: '',
|
||||||
|
})
|
||||||
|
class MockFilterButtonComponent {
|
||||||
|
@Input() active;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('PaymentsFiltersComponent', () => {
|
||||||
|
let component: PaymentsFiltersComponent;
|
||||||
|
let fixture: ComponentFixture<PaymentsFiltersComponent>;
|
||||||
|
let mockShopsSelectionManagerService: ShopsSelectionManagerService;
|
||||||
|
let mockPaymentsFiltersService: PaymentsFiltersService;
|
||||||
|
let mockAdditionalFiltersService: AdditionalFiltersService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockShopsSelectionManagerService = mock(ShopsSelectionManagerService);
|
||||||
|
mockPaymentsFiltersService = mock(PaymentsFiltersService);
|
||||||
|
mockAdditionalFiltersService = mock(AdditionalFiltersService);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
when(mockPaymentsFiltersService.filtersData$).thenReturn(
|
||||||
|
of({
|
||||||
|
daterange: {
|
||||||
|
begin: moment(),
|
||||||
|
end: moment(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function createComponent() {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [NoopAnimationsModule, getTranslocoModule(), FlexLayoutModule, CardBinPanFilterModule],
|
||||||
|
declarations: [
|
||||||
|
MockDaterangeFilterComponent,
|
||||||
|
MockInvoicesFilterComponent,
|
||||||
|
MockFilterShopsComponent,
|
||||||
|
MockFilterButtonComponent,
|
||||||
|
PaymentsFiltersComponent,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: ShopsSelectionManagerService,
|
||||||
|
useFactory: () => instance(mockShopsSelectionManagerService),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: PaymentsFiltersService,
|
||||||
|
useFactory: () => instance(mockPaymentsFiltersService),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: AdditionalFiltersService,
|
||||||
|
useFactory: () => instance(mockAdditionalFiltersService),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
fixture = TestBed.createComponent(PaymentsFiltersComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('creation', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createComponent();
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ngOnInit', () => {
|
||||||
|
it('should emit filters changed event on filters data change', async () => {
|
||||||
|
const filtersData = {
|
||||||
|
daterange: {
|
||||||
|
begin: moment(),
|
||||||
|
end: moment(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
when(mockPaymentsFiltersService.filtersData$).thenReturn(of(filtersData));
|
||||||
|
|
||||||
|
await createComponent();
|
||||||
|
|
||||||
|
const spyOnFiltersChanged = spyOn(component.filtersChanged, 'emit').and.callThrough();
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(spyOnFiltersChanged).toHaveBeenCalledTimes(1);
|
||||||
|
expect(spyOnFiltersChanged).toHaveBeenCalledWith(filtersData);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update selected ids on filters data change', async () => {
|
||||||
|
const filtersData = {
|
||||||
|
daterange: {
|
||||||
|
begin: moment(),
|
||||||
|
end: moment(),
|
||||||
|
},
|
||||||
|
shopIDs: ['id_one', 'id_two'],
|
||||||
|
};
|
||||||
|
|
||||||
|
when(mockPaymentsFiltersService.filtersData$).thenReturn(of(filtersData));
|
||||||
|
|
||||||
|
await createComponent();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
verify(mockShopsSelectionManagerService.setSelectedIds(deepEqual(['id_one', 'id_two']))).once();
|
||||||
|
expect().nothing();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ngOnChanges', () => {
|
||||||
|
it('should update tick realm changes', async () => {
|
||||||
|
await createComponent();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
component.ngOnChanges({
|
||||||
|
realm: {
|
||||||
|
previousValue: null,
|
||||||
|
currentValue: PaymentInstitutionRealm.test,
|
||||||
|
isFirstChange(): boolean {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
firstChange: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
verify(mockShopsSelectionManagerService.setRealm(PaymentInstitutionRealm.test)).once();
|
||||||
|
|
||||||
|
component.ngOnChanges({
|
||||||
|
realm: {
|
||||||
|
previousValue: PaymentInstitutionRealm.test,
|
||||||
|
currentValue: PaymentInstitutionRealm.live,
|
||||||
|
isFirstChange(): boolean {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
firstChange: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
verify(mockShopsSelectionManagerService.setRealm(PaymentInstitutionRealm.live)).once();
|
||||||
|
expect().nothing();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('openFiltersDialog', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
when(mockPaymentsFiltersService.filtersData$).thenReturn(
|
||||||
|
of({
|
||||||
|
daterange: {
|
||||||
|
begin: moment(),
|
||||||
|
end: moment(),
|
||||||
|
},
|
||||||
|
additional: {
|
||||||
|
myProperty: null,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should open dialog', async () => {
|
||||||
|
await createComponent();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
when(
|
||||||
|
mockAdditionalFiltersService.openFiltersDialog(
|
||||||
|
deepEqual({
|
||||||
|
myProperty: null,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).thenReturn(of());
|
||||||
|
|
||||||
|
component.openFiltersDialog();
|
||||||
|
|
||||||
|
verify(
|
||||||
|
mockAdditionalFiltersService.openFiltersDialog(
|
||||||
|
deepEqual({
|
||||||
|
myProperty: null,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).once();
|
||||||
|
expect().nothing();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should change isAdditionalFilterApplied using response from dialog', async () => {
|
||||||
|
await createComponent();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
when(
|
||||||
|
mockAdditionalFiltersService.openFiltersDialog(
|
||||||
|
deepEqual({
|
||||||
|
myProperty: null,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).thenReturn(of({}));
|
||||||
|
|
||||||
|
component.openFiltersDialog();
|
||||||
|
|
||||||
|
expect(component.isAdditionalFilterApplied).toBe(false);
|
||||||
|
|
||||||
|
when(
|
||||||
|
mockAdditionalFiltersService.openFiltersDialog(
|
||||||
|
deepEqual({
|
||||||
|
myProperty: null,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).thenReturn(
|
||||||
|
of({
|
||||||
|
something: null,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
component.openFiltersDialog();
|
||||||
|
|
||||||
|
expect(component.isAdditionalFilterApplied).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('dateRangeChange', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createComponent();
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should tick filters changes', () => {
|
||||||
|
const daterange = {
|
||||||
|
begin: moment(),
|
||||||
|
end: moment(),
|
||||||
|
};
|
||||||
|
component.dateRangeChange(daterange);
|
||||||
|
|
||||||
|
verify(
|
||||||
|
mockPaymentsFiltersService.changeFilters(
|
||||||
|
deepEqual({
|
||||||
|
daterange,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).once();
|
||||||
|
expect().nothing();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('invoiceSelectionChange', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createComponent();
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should tick filters change', () => {
|
||||||
|
component.invoiceSelectionChange(['invoice_id', 'another_invoice_id']);
|
||||||
|
|
||||||
|
verify(
|
||||||
|
mockPaymentsFiltersService.changeFilters(
|
||||||
|
deepEqual({
|
||||||
|
invoiceIDs: ['invoice_id', 'another_invoice_id'],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).once();
|
||||||
|
expect().nothing();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('shopSelectionChange', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createComponent();
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should tick filters change', () => {
|
||||||
|
const testList = new Array(4)
|
||||||
|
.fill({
|
||||||
|
id: '',
|
||||||
|
createdAt: new Date(),
|
||||||
|
isBlocked: false,
|
||||||
|
isSuspended: false,
|
||||||
|
categoryID: 1,
|
||||||
|
location: {
|
||||||
|
locationType: 'type',
|
||||||
|
},
|
||||||
|
details: {
|
||||||
|
name: 'my name',
|
||||||
|
description: 'some description',
|
||||||
|
},
|
||||||
|
contractID: 'contractID',
|
||||||
|
payoutToolID: 'payoutToolID',
|
||||||
|
scheduleID: 1,
|
||||||
|
account: {
|
||||||
|
currency: 'USD',
|
||||||
|
guaranteeID: 2,
|
||||||
|
settlementID: 2,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.map((el: Shop, i: number) => {
|
||||||
|
return {
|
||||||
|
...el,
|
||||||
|
id: `test_id_${i}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
component.shopSelectionChange(testList);
|
||||||
|
|
||||||
|
verify(
|
||||||
|
mockPaymentsFiltersService.changeFilters(
|
||||||
|
deepEqual({
|
||||||
|
shopIDs: ['test_id_0', 'test_id_1', 'test_id_2', 'test_id_3'],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).once();
|
||||||
|
expect().nothing();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('binPanChanged', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createComponent();
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should tick filters change', () => {
|
||||||
|
component.binPanChanged({
|
||||||
|
bin: '123456',
|
||||||
|
pan: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
verify(
|
||||||
|
mockPaymentsFiltersService.changeFilters(
|
||||||
|
deepEqual({
|
||||||
|
binPan: {
|
||||||
|
paymentMethod: 'bankCard',
|
||||||
|
bin: '123456',
|
||||||
|
pan: null,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).once();
|
||||||
|
expect().nothing();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,110 @@
|
|||||||
|
import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
|
||||||
|
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||||
|
import isEmpty from 'lodash.isempty';
|
||||||
|
import isNil from 'lodash.isnil';
|
||||||
|
import isObject from 'lodash.isobject';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { map, switchMap, take } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { Shop } from '@dsh/api-codegen/capi';
|
||||||
|
import { PaymentInstitutionRealm } from '@dsh/api/model';
|
||||||
|
import { Daterange } from '@dsh/pipes/daterange';
|
||||||
|
import { ComponentChange, ComponentChanges } from '@dsh/type-utils';
|
||||||
|
|
||||||
|
import { AdditionalFiltersService } from './additional-filters';
|
||||||
|
import { CardBinPan } from './card-bin-pan-filter';
|
||||||
|
import { PaymentsFiltersService } from './services/payments-filters/payments-filters.service';
|
||||||
|
import { ShopsSelectionManagerService } from './services/shops-selection-manager/shops-selection-manager.service';
|
||||||
|
import { PaymentsAdditionalFilters } from './types/payments-additional-filters';
|
||||||
|
import { PaymentsFiltersData } from './types/payments-filters-data';
|
||||||
|
|
||||||
|
@UntilDestroy()
|
||||||
|
@Component({
|
||||||
|
selector: 'dsh-payments-filters',
|
||||||
|
templateUrl: 'payments-filters.component.html',
|
||||||
|
})
|
||||||
|
export class PaymentsFiltersComponent implements OnInit, OnChanges {
|
||||||
|
@Input() realm: PaymentInstitutionRealm;
|
||||||
|
|
||||||
|
@Output() filtersChanged = new EventEmitter<PaymentsFiltersData>();
|
||||||
|
|
||||||
|
filtersData$: Observable<PaymentsFiltersData> = this.filtersHandler.filtersData$;
|
||||||
|
|
||||||
|
isAdditionalFilterApplied: boolean;
|
||||||
|
|
||||||
|
shops$: Observable<Shop[]> = this.shopService.shops$;
|
||||||
|
selectedShops$: Observable<Shop[]> = this.shopService.selectedShops$;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private shopService: ShopsSelectionManagerService,
|
||||||
|
private filtersHandler: PaymentsFiltersService,
|
||||||
|
private additionalFilters: AdditionalFiltersService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.filtersData$.pipe(untilDestroyed(this)).subscribe((filtersData: PaymentsFiltersData) => {
|
||||||
|
this.filtersChanged.emit(filtersData);
|
||||||
|
const { shopIDs = [] } = filtersData;
|
||||||
|
this.shopService.setSelectedIds(shopIDs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: ComponentChanges<PaymentsFiltersComponent>): void {
|
||||||
|
if (isObject(changes.realm)) {
|
||||||
|
this.updateRealm(changes.realm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openFiltersDialog(): void {
|
||||||
|
this.filtersData$
|
||||||
|
.pipe(
|
||||||
|
take(1),
|
||||||
|
map((filtersData: PaymentsFiltersData) => filtersData.additional),
|
||||||
|
switchMap((filters: PaymentsAdditionalFilters) => {
|
||||||
|
return this.additionalFilters.openFiltersDialog(filters);
|
||||||
|
}),
|
||||||
|
untilDestroyed(this)
|
||||||
|
)
|
||||||
|
.subscribe((filters: PaymentsAdditionalFilters) => {
|
||||||
|
this.isAdditionalFilterApplied = !isEmpty(filters);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dateRangeChange(dateRange: Daterange): void {
|
||||||
|
this.updateFilters({ daterange: dateRange });
|
||||||
|
}
|
||||||
|
|
||||||
|
invoiceSelectionChange(invoiceIds: string[]): void {
|
||||||
|
this.updateFilters({ invoiceIDs: invoiceIds });
|
||||||
|
}
|
||||||
|
|
||||||
|
shopSelectionChange(selectedShops: Shop[]): void {
|
||||||
|
this.updateFilters({
|
||||||
|
shopIDs: selectedShops.map(({ id }: Shop) => id),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
binPanChanged(binPan: Partial<CardBinPan>): void {
|
||||||
|
this.updateFilters({
|
||||||
|
binPan: {
|
||||||
|
paymentMethod: 'bankCard',
|
||||||
|
...binPan,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateRealm(change: ComponentChange<PaymentsFiltersComponent, 'realm'>): void {
|
||||||
|
const realm = change.currentValue;
|
||||||
|
if (isNil(realm)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.shopService.setRealm(realm);
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateFilters(change: Partial<PaymentsFiltersData>): void {
|
||||||
|
this.filtersHandler.changeFilters({
|
||||||
|
...change,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
|
import { TranslocoModule } from '@ngneat/transloco';
|
||||||
|
|
||||||
|
import { FilterShopsModule, InvoicesFilterModule } from '@dsh/app/shared/components';
|
||||||
|
import { DaterangeManagerModule } from '@dsh/app/shared/services/date-range-manager';
|
||||||
|
import { DaterangeFilterModule } from '@dsh/components/filters/daterange-filter';
|
||||||
|
import { FilterModule } from '@dsh/components/filters/filter';
|
||||||
|
|
||||||
|
import { AdditionalFiltersModule } from './additional-filters';
|
||||||
|
import { CardBinPanFilterModule } from './card-bin-pan-filter';
|
||||||
|
import { PaymentsFiltersComponent } from './payments-filters.component';
|
||||||
|
import { PaymentsFiltersStoreService } from './services/payments-filters-store/payments-filters-store.service';
|
||||||
|
import { PaymentsFiltersService } from './services/payments-filters/payments-filters.service';
|
||||||
|
import { ShopsSelectionManagerService } from './services/shops-selection-manager/shops-selection-manager.service';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
AdditionalFiltersModule,
|
||||||
|
TranslocoModule,
|
||||||
|
FlexLayoutModule,
|
||||||
|
FilterModule,
|
||||||
|
DaterangeFilterModule,
|
||||||
|
FilterShopsModule,
|
||||||
|
InvoicesFilterModule,
|
||||||
|
DaterangeManagerModule,
|
||||||
|
CardBinPanFilterModule,
|
||||||
|
],
|
||||||
|
declarations: [PaymentsFiltersComponent],
|
||||||
|
exports: [PaymentsFiltersComponent],
|
||||||
|
providers: [PaymentsFiltersService, PaymentsFiltersStoreService, ShopsSelectionManagerService],
|
||||||
|
})
|
||||||
|
export class PaymentsFiltersModule {}
|
@ -0,0 +1,333 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { deepEqual, instance, mock, when } from 'ts-mockito';
|
||||||
|
|
||||||
|
import { DaterangeManagerService } from '@dsh/app/shared/services/date-range-manager';
|
||||||
|
|
||||||
|
import { PaymentsFiltersStoreService } from './payments-filters-store.service';
|
||||||
|
|
||||||
|
describe('PaymentsFiltersStoreService', () => {
|
||||||
|
let service: PaymentsFiltersStoreService;
|
||||||
|
let mockDaterangeManagerService: DaterangeManagerService;
|
||||||
|
let mockRouter: Router;
|
||||||
|
let mockActivatedRoute: ActivatedRoute;
|
||||||
|
|
||||||
|
const daterange = {
|
||||||
|
begin: moment(),
|
||||||
|
end: moment(),
|
||||||
|
};
|
||||||
|
const formattedDaterange = {
|
||||||
|
begin: daterange.begin.format(),
|
||||||
|
end: daterange.end.format(),
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockDaterangeManagerService = mock(DaterangeManagerService);
|
||||||
|
mockRouter = mock(Router);
|
||||||
|
mockActivatedRoute = mock(ActivatedRoute);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
when(mockActivatedRoute.queryParams).thenReturn(of({}));
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
when(mockDaterangeManagerService.serializeDateRange(deepEqual(daterange))).thenReturn(formattedDaterange);
|
||||||
|
when(mockDaterangeManagerService.deserializeDateRange(deepEqual(formattedDaterange))).thenReturn(daterange);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
PaymentsFiltersStoreService,
|
||||||
|
{
|
||||||
|
provide: DaterangeManagerService,
|
||||||
|
useFactory: () => instance(mockDaterangeManagerService),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: Router,
|
||||||
|
useFactory: () => instance(mockRouter),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ActivatedRoute,
|
||||||
|
useFactory: () => instance(mockActivatedRoute),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
service = TestBed.inject(PaymentsFiltersStoreService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('mapToData', () => {
|
||||||
|
it('should format daterange params', () => {
|
||||||
|
expect(
|
||||||
|
service.mapToData({
|
||||||
|
fromTime: daterange.begin.format(),
|
||||||
|
toTime: daterange.end.format(),
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
daterange,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format invoices and shops ids params', () => {
|
||||||
|
expect(
|
||||||
|
service.mapToData({
|
||||||
|
shopIDs: ['shop-id-1', 'shop-id-2'],
|
||||||
|
invoiceIDs: ['invoice-id-1', 'invoice-id-2'],
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
shopIDs: ['shop-id-1', 'shop-id-2'],
|
||||||
|
invoiceIDs: ['invoice-id-1', 'invoice-id-2'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format invoices and shops single id params', () => {
|
||||||
|
expect(
|
||||||
|
service.mapToData({
|
||||||
|
shopIDs: 'shop-id',
|
||||||
|
invoiceIDs: 'invoice-id',
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
shopIDs: ['shop-id'],
|
||||||
|
invoiceIDs: ['invoice-id'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse binPan fully', () => {
|
||||||
|
expect(
|
||||||
|
service.mapToData({
|
||||||
|
first6: '123456',
|
||||||
|
last4: '1234',
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
binPan: {
|
||||||
|
paymentMethod: 'bankCard',
|
||||||
|
bin: '123456',
|
||||||
|
pan: '1234',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not create binPan object if there is no first6 and last4 exists', () => {
|
||||||
|
expect(
|
||||||
|
service.mapToData({
|
||||||
|
invoiceIDs: 'invoice-id',
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
invoiceIDs: ['invoice-id'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse bin if first6 has 6 numeric symbols', () => {
|
||||||
|
expect(
|
||||||
|
service.mapToData({
|
||||||
|
first6: '123456',
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
binPan: {
|
||||||
|
paymentMethod: 'bankCard',
|
||||||
|
bin: '123456',
|
||||||
|
pan: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse pan if last4 has 4 numeric symbols', () => {
|
||||||
|
expect(
|
||||||
|
service.mapToData({
|
||||||
|
last4: '1234',
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
binPan: {
|
||||||
|
paymentMethod: 'bankCard',
|
||||||
|
bin: null,
|
||||||
|
pan: '1234',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not parse bin if it has non-numeric symbols', () => {
|
||||||
|
expect(
|
||||||
|
service.mapToData({
|
||||||
|
first6: '12345a',
|
||||||
|
last4: '1234',
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
binPan: {
|
||||||
|
paymentMethod: 'bankCard',
|
||||||
|
bin: null,
|
||||||
|
pan: '1234',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not parse pan if it has non-numeric symbols', () => {
|
||||||
|
expect(
|
||||||
|
service.mapToData({
|
||||||
|
first6: '123456',
|
||||||
|
last4: '123a',
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
binPan: {
|
||||||
|
paymentMethod: 'bankCard',
|
||||||
|
bin: '123456',
|
||||||
|
pan: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not parse bin if it not 6 numeric symbols', () => {
|
||||||
|
expect(
|
||||||
|
service.mapToData({
|
||||||
|
first6: '12345',
|
||||||
|
last4: '1234',
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
binPan: {
|
||||||
|
paymentMethod: 'bankCard',
|
||||||
|
bin: null,
|
||||||
|
pan: '1234',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
service.mapToData({
|
||||||
|
first6: '1234567',
|
||||||
|
last4: '1234',
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
binPan: {
|
||||||
|
paymentMethod: 'bankCard',
|
||||||
|
bin: null,
|
||||||
|
pan: '1234',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not parse pan if it not 4 numeric symbols', () => {
|
||||||
|
expect(
|
||||||
|
service.mapToData({
|
||||||
|
first6: '123456',
|
||||||
|
last4: '12345',
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
binPan: {
|
||||||
|
paymentMethod: 'bankCard',
|
||||||
|
bin: '123456',
|
||||||
|
pan: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
service.mapToData({
|
||||||
|
first6: '123456',
|
||||||
|
last4: '123',
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
binPan: {
|
||||||
|
paymentMethod: 'bankCard',
|
||||||
|
bin: '123456',
|
||||||
|
pan: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty values as params', () => {
|
||||||
|
expect(service.mapToData({})).toEqual({});
|
||||||
|
expect(service.mapToData({ shopIDs: '' })).toEqual({});
|
||||||
|
expect(service.mapToData({ invoiceIDs: '' })).toEqual({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('mapToParams', () => {
|
||||||
|
it('should not set empty params', () => {
|
||||||
|
expect(
|
||||||
|
service.mapToParams({
|
||||||
|
daterange,
|
||||||
|
shopIDs: [],
|
||||||
|
invoiceIDs: ['my-invoice-id'],
|
||||||
|
additional: {
|
||||||
|
mine: {},
|
||||||
|
another: null,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
fromTime: formattedDaterange.begin,
|
||||||
|
toTime: formattedDaterange.end,
|
||||||
|
invoiceIDs: ['my-invoice-id'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should transform bin in first6', () => {
|
||||||
|
expect(
|
||||||
|
service.mapToParams({
|
||||||
|
daterange,
|
||||||
|
binPan: {
|
||||||
|
paymentMethod: 'bankCard',
|
||||||
|
bin: '123456',
|
||||||
|
pan: null,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
fromTime: formattedDaterange.begin,
|
||||||
|
toTime: formattedDaterange.end,
|
||||||
|
first6: '123456',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should transform pan in last4', () => {
|
||||||
|
expect(
|
||||||
|
service.mapToParams({
|
||||||
|
daterange,
|
||||||
|
binPan: {
|
||||||
|
paymentMethod: 'bankCard',
|
||||||
|
bin: null,
|
||||||
|
pan: '1234',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
fromTime: formattedDaterange.begin,
|
||||||
|
toTime: formattedDaterange.end,
|
||||||
|
last4: '1234',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should transform binPan data in first6 and last4', () => {
|
||||||
|
expect(
|
||||||
|
service.mapToParams({
|
||||||
|
daterange,
|
||||||
|
binPan: {
|
||||||
|
paymentMethod: 'bankCard',
|
||||||
|
bin: '123456',
|
||||||
|
pan: '1234',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
fromTime: formattedDaterange.begin,
|
||||||
|
toTime: formattedDaterange.end,
|
||||||
|
first6: '123456',
|
||||||
|
last4: '1234',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove binPan from params if bin and pan empty', () => {
|
||||||
|
expect(
|
||||||
|
service.mapToParams({
|
||||||
|
daterange,
|
||||||
|
binPan: {
|
||||||
|
paymentMethod: 'bankCard',
|
||||||
|
bin: null,
|
||||||
|
pan: null,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
fromTime: formattedDaterange.begin,
|
||||||
|
toTime: formattedDaterange.end,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,95 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||||
|
import isEmpty from 'lodash.isempty';
|
||||||
|
import isNil from 'lodash.isnil';
|
||||||
|
import isString from 'lodash.isstring';
|
||||||
|
import pickBy from 'lodash.pickby';
|
||||||
|
|
||||||
|
import { QueryParamsStore } from '@dsh/app/shared/services';
|
||||||
|
import { DaterangeManagerService } from '@dsh/app/shared/services/date-range-manager';
|
||||||
|
import { Daterange } from '@dsh/pipes/daterange';
|
||||||
|
import { wrapValuesToArray } from '@dsh/utils';
|
||||||
|
|
||||||
|
import { BIN_LENGTH, PAN_LENGTH } from '../../card-bin-pan-filter';
|
||||||
|
import { PaymentsFiltersData } from '../../types/payments-filters-data';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PaymentsFiltersStoreService extends QueryParamsStore<PaymentsFiltersData> {
|
||||||
|
constructor(
|
||||||
|
private daterangeManager: DaterangeManagerService,
|
||||||
|
protected router: Router,
|
||||||
|
protected route: ActivatedRoute
|
||||||
|
) {
|
||||||
|
super(router, route);
|
||||||
|
}
|
||||||
|
|
||||||
|
mapToData(params: Params): Partial<PaymentsFiltersData> {
|
||||||
|
const { fromTime, toTime, ...restParams } = params;
|
||||||
|
|
||||||
|
return this.removeUnusedFields({
|
||||||
|
daterange: this.formatDaterange(fromTime, toTime),
|
||||||
|
binPan: this.getBinPanParams(params),
|
||||||
|
...this.getListParams(restParams),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
mapToParams({ daterange, binPan, additional, ...restData }: PaymentsFiltersData): Params {
|
||||||
|
const { begin: fromTime, end: toTime } = this.daterangeManager.serializeDateRange(daterange);
|
||||||
|
const { bin = null, pan = null } = binPan ?? {};
|
||||||
|
|
||||||
|
return this.removeUnusedFields({
|
||||||
|
fromTime,
|
||||||
|
toTime,
|
||||||
|
first6: bin,
|
||||||
|
last4: pan,
|
||||||
|
...additional,
|
||||||
|
...restData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeUnusedFields<T>(data: T): T | Partial<T> {
|
||||||
|
return Object.entries(data).reduce((newData: T | Partial<T>, [key, value]: [string, any]) => {
|
||||||
|
if (!isEmpty(value)) {
|
||||||
|
newData[key] = value;
|
||||||
|
}
|
||||||
|
return newData;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getBinPanParams({ first6, last4 }: Params): PaymentsFiltersData['binPan'] | null {
|
||||||
|
const bin = Number(first6);
|
||||||
|
const pan = Number(last4);
|
||||||
|
const isValidBin = !isNil(first6) && !isNaN(bin) && first6.length === BIN_LENGTH;
|
||||||
|
const isValidPan = !isNil(last4) && !isNaN(pan) && last4.length === PAN_LENGTH;
|
||||||
|
|
||||||
|
if (isValidBin || isValidPan) {
|
||||||
|
return {
|
||||||
|
paymentMethod: 'bankCard',
|
||||||
|
bin: isValidBin ? first6 : null,
|
||||||
|
pan: isValidPan ? last4 : null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getListParams(params: Params): Partial<PaymentsFiltersData> {
|
||||||
|
const nonEmptyListParams = pickBy(
|
||||||
|
params,
|
||||||
|
(value: unknown, key: keyof PaymentsFiltersData) =>
|
||||||
|
['shopIDs', 'invoiceIDs'].includes(key) && !isEmpty(value)
|
||||||
|
);
|
||||||
|
const stringListParams = pickBy(nonEmptyListParams, (value: unknown) => isString(value));
|
||||||
|
|
||||||
|
return {
|
||||||
|
...nonEmptyListParams,
|
||||||
|
...wrapValuesToArray(stringListParams),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatDaterange(fromTime: string | undefined, toTime: string | undefined): Daterange | null {
|
||||||
|
return isNil(fromTime) || isNil(toTime)
|
||||||
|
? null
|
||||||
|
: this.daterangeManager.deserializeDateRange({ begin: fromTime, end: toTime });
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,163 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { cold } from 'jasmine-marbles';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { deepEqual, instance, mock, verify, when } from 'ts-mockito';
|
||||||
|
|
||||||
|
import { DaterangeManagerService } from '@dsh/app/shared/services/date-range-manager';
|
||||||
|
|
||||||
|
import { PaymentsFiltersStoreService } from '../payments-filters-store/payments-filters-store.service';
|
||||||
|
import { PaymentsFiltersService } from './payments-filters.service';
|
||||||
|
|
||||||
|
describe('PaymentsFiltersService', () => {
|
||||||
|
let service: PaymentsFiltersService;
|
||||||
|
let mockDaterangeManagerService: DaterangeManagerService;
|
||||||
|
let mockPaymentsFiltersStoreService: PaymentsFiltersStoreService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockDaterangeManagerService = mock(DaterangeManagerService);
|
||||||
|
mockPaymentsFiltersStoreService = mock(PaymentsFiltersStoreService);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function configureTestingModule() {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
PaymentsFiltersService,
|
||||||
|
{
|
||||||
|
provide: DaterangeManagerService,
|
||||||
|
useFactory: () => instance(mockDaterangeManagerService),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: PaymentsFiltersStoreService,
|
||||||
|
useFactory: () => instance(mockPaymentsFiltersStoreService),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
service = TestBed.inject(PaymentsFiltersService);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('creation', () => {
|
||||||
|
it('should be created', async () => {
|
||||||
|
const defaultDateRange = {
|
||||||
|
begin: moment(),
|
||||||
|
end: moment(),
|
||||||
|
};
|
||||||
|
|
||||||
|
when(mockDaterangeManagerService.defaultDaterange).thenReturn(defaultDateRange);
|
||||||
|
when(mockPaymentsFiltersStoreService.data$).thenReturn(of({}));
|
||||||
|
|
||||||
|
await configureTestingModule();
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('constructor', () => {
|
||||||
|
const defaultDateRange = {
|
||||||
|
begin: moment(),
|
||||||
|
end: moment(),
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
when(mockDaterangeManagerService.defaultDaterange).thenReturn(defaultDateRange);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge default daterange and query params data', async () => {
|
||||||
|
when(mockPaymentsFiltersStoreService.data$).thenReturn(
|
||||||
|
of({
|
||||||
|
shopIDs: [],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const expected$ = cold('(a|)', {
|
||||||
|
a: {
|
||||||
|
daterange: defaultDateRange,
|
||||||
|
shopIDs: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await configureTestingModule();
|
||||||
|
|
||||||
|
expect(service.filtersData$).toBeObservable(expected$);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should rewrite default daterange with query params data', async () => {
|
||||||
|
const storeDaterange = {
|
||||||
|
begin: moment().startOf('m'),
|
||||||
|
end: moment().endOf('m'),
|
||||||
|
};
|
||||||
|
|
||||||
|
when(mockPaymentsFiltersStoreService.data$).thenReturn(
|
||||||
|
of({
|
||||||
|
daterange: storeDaterange,
|
||||||
|
shopIDs: [],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const expected$ = cold('(a|)', {
|
||||||
|
a: {
|
||||||
|
daterange: storeDaterange,
|
||||||
|
shopIDs: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await configureTestingModule();
|
||||||
|
|
||||||
|
expect(service.filtersData$).toBeObservable(expected$);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use default daterange if query params data has none', async () => {
|
||||||
|
when(mockPaymentsFiltersStoreService.data$).thenReturn(
|
||||||
|
of({
|
||||||
|
shopIDs: ['shop-id'],
|
||||||
|
invoiceIDs: ['invoice-id'],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const expected$ = cold('(a|)', {
|
||||||
|
a: {
|
||||||
|
daterange: defaultDateRange,
|
||||||
|
shopIDs: ['shop-id'],
|
||||||
|
invoiceIDs: ['invoice-id'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await configureTestingModule();
|
||||||
|
|
||||||
|
expect(service.filtersData$).toBeObservable(expected$);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('changeFilters', () => {
|
||||||
|
const defaultDateRange = {
|
||||||
|
begin: moment(),
|
||||||
|
end: moment(),
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
when(mockDaterangeManagerService.defaultDaterange).thenReturn(defaultDateRange);
|
||||||
|
when(mockPaymentsFiltersStoreService.data$).thenReturn(
|
||||||
|
of({
|
||||||
|
shopIDs: [],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update properties of existing filters data', async () => {
|
||||||
|
await configureTestingModule();
|
||||||
|
|
||||||
|
service.changeFilters({
|
||||||
|
shopIDs: ['mine'],
|
||||||
|
});
|
||||||
|
|
||||||
|
verify(
|
||||||
|
mockPaymentsFiltersStoreService.preserve(
|
||||||
|
deepEqual({
|
||||||
|
daterange: defaultDateRange,
|
||||||
|
shopIDs: ['mine'],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).once();
|
||||||
|
expect().nothing();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,57 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||||
|
import { Observable, ReplaySubject } from 'rxjs';
|
||||||
|
import { map, withLatestFrom } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { DaterangeManagerService } from '@dsh/app/shared/services/date-range-manager';
|
||||||
|
|
||||||
|
import { PaymentsFiltersData } from '../../types/payments-filters-data';
|
||||||
|
import { PaymentsFiltersStoreService } from '../payments-filters-store/payments-filters-store.service';
|
||||||
|
|
||||||
|
@UntilDestroy()
|
||||||
|
@Injectable()
|
||||||
|
export class PaymentsFiltersService {
|
||||||
|
filtersData$: Observable<PaymentsFiltersData>;
|
||||||
|
|
||||||
|
private filtersChange$ = new ReplaySubject<Partial<PaymentsFiltersData>>(1);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private daterangeManager: DaterangeManagerService,
|
||||||
|
private filtersParamsStore: PaymentsFiltersStoreService
|
||||||
|
) {
|
||||||
|
this.initFiltersData();
|
||||||
|
this.initUpdatesData();
|
||||||
|
}
|
||||||
|
|
||||||
|
changeFilters(dataChange: Partial<PaymentsFiltersData>): void {
|
||||||
|
this.filtersChange$.next(dataChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
private initFiltersData(): void {
|
||||||
|
this.filtersData$ = this.filtersParamsStore.data$.pipe(
|
||||||
|
map((storeData: Partial<PaymentsFiltersData>) => {
|
||||||
|
return {
|
||||||
|
daterange: this.daterangeManager.defaultDaterange,
|
||||||
|
...storeData,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private initUpdatesData(): void {
|
||||||
|
this.filtersChange$
|
||||||
|
.pipe(
|
||||||
|
withLatestFrom(this.filtersData$),
|
||||||
|
map(([dataChange, filtersData]: [Partial<PaymentsFiltersData>, PaymentsFiltersData]) => {
|
||||||
|
return {
|
||||||
|
...filtersData,
|
||||||
|
...dataChange,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
untilDestroyed(this)
|
||||||
|
)
|
||||||
|
.subscribe((updatedData: PaymentsFiltersData) => {
|
||||||
|
this.filtersParamsStore.preserve(updatedData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,135 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { hot } from 'jasmine-marbles';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { instance, mock, when } from 'ts-mockito';
|
||||||
|
|
||||||
|
import { Shop } from '@dsh/api-codegen/capi';
|
||||||
|
import { PaymentInstitutionRealm } from '@dsh/api/model';
|
||||||
|
import { ApiShopsService } from '@dsh/api/shop';
|
||||||
|
|
||||||
|
import { ShopsSelectionManagerService } from './shops-selection-manager.service';
|
||||||
|
|
||||||
|
describe('ShopsSelectionManagerService', () => {
|
||||||
|
let service: ShopsSelectionManagerService;
|
||||||
|
let mockApiShopsService: ApiShopsService;
|
||||||
|
|
||||||
|
function createService() {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
ShopsSelectionManagerService,
|
||||||
|
{
|
||||||
|
provide: ApiShopsService,
|
||||||
|
useFactory: () => instance(mockApiShopsService),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
service = TestBed.inject(ShopsSelectionManagerService);
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockApiShopsService = mock(ApiShopsService);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const testList = new Array(12)
|
||||||
|
.fill({
|
||||||
|
id: '',
|
||||||
|
createdAt: new Date(),
|
||||||
|
isBlocked: false,
|
||||||
|
isSuspended: false,
|
||||||
|
categoryID: 0,
|
||||||
|
location: {
|
||||||
|
locationType: 'type',
|
||||||
|
},
|
||||||
|
details: {
|
||||||
|
name: 'my name',
|
||||||
|
description: 'some description',
|
||||||
|
},
|
||||||
|
contractID: 'contractID',
|
||||||
|
payoutToolID: 'payoutToolID',
|
||||||
|
scheduleID: 1,
|
||||||
|
account: {
|
||||||
|
currency: 'USD',
|
||||||
|
guaranteeID: 2,
|
||||||
|
settlementID: 2,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.map((el: Shop, i: number) => {
|
||||||
|
const isOdd = i % 2 === 0;
|
||||||
|
return {
|
||||||
|
...el,
|
||||||
|
id: isOdd ? `test_id_${i}` : `live_id_${i}`,
|
||||||
|
categoryID: isOdd ? 1 : 2,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
when(mockApiShopsService.shops$).thenReturn(of(testList));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('creation', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createService();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setRealm', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createService();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should tick shops after realm was inited', () => {
|
||||||
|
const beforeInit$ = hot('^', {});
|
||||||
|
|
||||||
|
expect(service.shops$.pipe(map((shops: Shop[]) => shops.map(({ id }) => id)))).toBeObservable(beforeInit$);
|
||||||
|
|
||||||
|
const afterInit$ = hot('a', {
|
||||||
|
a: ['test_id_0', 'test_id_2', 'test_id_4', 'test_id_6', 'test_id_8', 'test_id_10'],
|
||||||
|
});
|
||||||
|
|
||||||
|
service.setRealm(PaymentInstitutionRealm.test);
|
||||||
|
|
||||||
|
expect(service.shops$.pipe(map((shops: Shop[]) => shops.map(({ id }) => id)))).toBeObservable(afterInit$);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter shops by realm even if they changed', () => {
|
||||||
|
const testRealm$ = hot('a', {
|
||||||
|
a: ['test_id_0', 'test_id_2', 'test_id_4', 'test_id_6', 'test_id_8', 'test_id_10'],
|
||||||
|
});
|
||||||
|
|
||||||
|
service.setRealm(PaymentInstitutionRealm.test);
|
||||||
|
|
||||||
|
expect(service.shops$.pipe(map((shops: Shop[]) => shops.map(({ id }) => id)))).toBeObservable(testRealm$);
|
||||||
|
|
||||||
|
const liveRealm$ = hot('a', {
|
||||||
|
a: ['live_id_1', 'live_id_3', 'live_id_5', 'live_id_7', 'live_id_9', 'live_id_11'],
|
||||||
|
});
|
||||||
|
|
||||||
|
service.setRealm(PaymentInstitutionRealm.live);
|
||||||
|
|
||||||
|
expect(service.shops$.pipe(map((shops: Shop[]) => shops.map(({ id }) => id)))).toBeObservable(liveRealm$);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setSelectedIds', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createService();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should change selected list using selected ids', () => {
|
||||||
|
const liveRealm$ = hot('a', {
|
||||||
|
a: ['live_id_3', 'live_id_5'],
|
||||||
|
});
|
||||||
|
|
||||||
|
service.setRealm(PaymentInstitutionRealm.live);
|
||||||
|
service.setSelectedIds(['live_id_3', 'live_id_5']);
|
||||||
|
|
||||||
|
expect(service.selectedShops$.pipe(map((shops: Shop[]) => shops.map(({ id }) => id)))).toBeObservable(
|
||||||
|
liveRealm$
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,54 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||||
|
import { Observable, ReplaySubject } from 'rxjs';
|
||||||
|
import { map, mergeMap } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { Shop } from '@dsh/api-codegen/capi';
|
||||||
|
import { PaymentInstitutionRealm } from '@dsh/api/model';
|
||||||
|
import { ApiShopsService } from '@dsh/api/shop';
|
||||||
|
|
||||||
|
import { filterShopsByRealm } from '../../../../operators';
|
||||||
|
|
||||||
|
@UntilDestroy()
|
||||||
|
@Injectable()
|
||||||
|
export class ShopsSelectionManagerService {
|
||||||
|
shops$: Observable<Shop[]>;
|
||||||
|
selectedShops$: Observable<Shop[]>;
|
||||||
|
|
||||||
|
private realmChanges$ = new ReplaySubject<PaymentInstitutionRealm>(1);
|
||||||
|
private selectedIds$ = new ReplaySubject<string[]>(1);
|
||||||
|
|
||||||
|
constructor(private shopService: ApiShopsService) {
|
||||||
|
this.initShops();
|
||||||
|
this.initShopsSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
setRealm(realm: PaymentInstitutionRealm): void {
|
||||||
|
this.realmChanges$.next(realm);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedIds(selectedIds: string[]): void {
|
||||||
|
this.selectedIds$.next(selectedIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private initShops(): void {
|
||||||
|
this.shops$ = this.realmChanges$.pipe(filterShopsByRealm(this.shopService.shops$));
|
||||||
|
}
|
||||||
|
|
||||||
|
private initShopsSelection(): void {
|
||||||
|
this.selectedShops$ = this.selectedIds$.pipe(
|
||||||
|
map((selectedIds: string[]) => {
|
||||||
|
return selectedIds.reduce((idsMap: Map<string, string>, id: string) => {
|
||||||
|
idsMap.set(id, id);
|
||||||
|
return idsMap;
|
||||||
|
}, new Map<string, string>());
|
||||||
|
}),
|
||||||
|
mergeMap((selectedIds: Map<string, string>) => {
|
||||||
|
return this.shops$.pipe(map((shops: Shop[]) => [selectedIds, shops]));
|
||||||
|
}),
|
||||||
|
map(([selectedIds, shops]: [Map<string, string>, Shop[]]) => {
|
||||||
|
return shops.map((shop: Shop) => (selectedIds.has(shop.id) ? shop : null)).filter(Boolean);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
import { CardBinPan } from '../card-bin-pan-filter';
|
||||||
|
|
||||||
|
export type PaymentBinPan = {
|
||||||
|
// can be expanded in the future
|
||||||
|
paymentMethod: 'bankCard';
|
||||||
|
} & Partial<CardBinPan>;
|
@ -0,0 +1,4 @@
|
|||||||
|
// TODO: add filters fields
|
||||||
|
export interface PaymentsAdditionalFilters {
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
import { Daterange } from '@dsh/pipes/daterange';
|
||||||
|
|
||||||
|
import { PaymentBinPan } from './payment-bin-pan';
|
||||||
|
import { PaymentsAdditionalFilters } from './payments-additional-filters';
|
||||||
|
|
||||||
|
export interface PaymentsFiltersData {
|
||||||
|
daterange: Daterange;
|
||||||
|
invoiceIDs?: string[];
|
||||||
|
shopIDs?: string[];
|
||||||
|
binPan?: PaymentBinPan;
|
||||||
|
additional?: PaymentsAdditionalFilters;
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
<dsh-row
|
||||||
|
*transloco="let t; scope: 'operations'; read: 'operations.payments.table'"
|
||||||
|
fxLayout="row"
|
||||||
|
fxLayoutAlign="space-between center"
|
||||||
|
fxLayoutGap="24px"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
<dsh-row-header-label fxFlex>{{ t('amount') }}</dsh-row-header-label>
|
||||||
|
<dsh-row-header-label fxFlex>{{ t('status') }}</dsh-row-header-label>
|
||||||
|
<dsh-row-header-label fxFlex>{{ t('statusChanged') }}</dsh-row-header-label>
|
||||||
|
<dsh-row-header-label fxFlex fxHide.lt-md> {{ t('shop') }} </dsh-row-header-label>
|
||||||
|
</dsh-row>
|
@ -0,0 +1,70 @@
|
|||||||
|
import { ChangeDetectionStrategy } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { TranslocoTestingModule } from '@ngneat/transloco';
|
||||||
|
|
||||||
|
import { RowModule } from '@dsh/components/layout';
|
||||||
|
|
||||||
|
import { PaymentsRowHeaderComponent } from './payments-row-header.component';
|
||||||
|
|
||||||
|
const translationConfig = {
|
||||||
|
ru: {
|
||||||
|
operations: {
|
||||||
|
payments: {
|
||||||
|
table: {
|
||||||
|
amount: 'Сумма списания',
|
||||||
|
status: 'Статус',
|
||||||
|
statusChanged: 'Статус изменен',
|
||||||
|
invoice: 'Инвойс',
|
||||||
|
shop: 'Магазин',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('PaymentsRowHeaderComponent', () => {
|
||||||
|
let fixture: ComponentFixture<PaymentsRowHeaderComponent>;
|
||||||
|
let component: PaymentsRowHeaderComponent;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
RowModule,
|
||||||
|
TranslocoTestingModule.withLangs(translationConfig, {
|
||||||
|
availableLangs: ['ru'],
|
||||||
|
defaultLang: 'ru',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
declarations: [PaymentsRowHeaderComponent],
|
||||||
|
})
|
||||||
|
.overrideComponent(PaymentsRowHeaderComponent, {
|
||||||
|
set: {
|
||||||
|
changeDetection: ChangeDetectionStrategy.Default,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(PaymentsRowHeaderComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('template', () => {
|
||||||
|
it('should render columns with names from translation config', () => {
|
||||||
|
const columns = fixture.debugElement.queryAll(By.css('dsh-row dsh-row-header-label'));
|
||||||
|
|
||||||
|
expect(columns.length).toBe(4);
|
||||||
|
expect(columns[0].nativeElement.textContent.trim()).toBe('Сумма списания');
|
||||||
|
expect(columns[1].nativeElement.textContent.trim()).toBe('Статус');
|
||||||
|
expect(columns[2].nativeElement.textContent.trim()).toBe('Статус изменен');
|
||||||
|
expect(columns[3].nativeElement.textContent.trim()).toBe('Магазин');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,8 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'dsh-payments-row-header',
|
||||||
|
templateUrl: 'payments-row-header.component.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class PaymentsRowHeaderComponent {}
|
@ -0,0 +1,12 @@
|
|||||||
|
<dsh-row fxLayout="row" fxLayoutAlign="space-between center" fxLayoutGap="24px">
|
||||||
|
<dsh-row-label fxFlex>
|
||||||
|
<div class="dsh-body-2">
|
||||||
|
<dsh-balance [amount]="payment.amount" [currency]="payment.currency"></dsh-balance>
|
||||||
|
</div>
|
||||||
|
</dsh-row-label>
|
||||||
|
<dsh-row-label fxFlex>
|
||||||
|
<dsh-payment-status [status]="payment.status"></dsh-payment-status>
|
||||||
|
</dsh-row-label>
|
||||||
|
<dsh-row-label fxFlex>{{ payment.statusChangedAt | date: 'dd MMMM yyyy, HH:mm' }}</dsh-row-label>
|
||||||
|
<dsh-row-label fxFlex fxHide.lt-md>{{ payment.shopID | shopDetails }}</dsh-row-label>
|
||||||
|
</dsh-row>
|
@ -0,0 +1,89 @@
|
|||||||
|
import { ChangeDetectionStrategy } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { TranslocoTestingModule } from '@ngneat/transloco';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import { PaymentSearchResult } from '@dsh/api-codegen/capi';
|
||||||
|
import { BalanceModule } from '@dsh/app/shared/components/balance/balance.module';
|
||||||
|
import { RowModule } from '@dsh/components/layout';
|
||||||
|
|
||||||
|
import { generateMockPayment } from '../../../tests/generate-mock-payment';
|
||||||
|
import { MockShopDetailsPipe } from '../../../tests/mock-shop-details-pipe';
|
||||||
|
import { PaymentStatusModule } from '../../payment-status';
|
||||||
|
import { PaymentsRowComponent } from './payments-row.component';
|
||||||
|
|
||||||
|
describe('PaymentsRowComponent', () => {
|
||||||
|
let fixture: ComponentFixture<PaymentsRowComponent>;
|
||||||
|
let component: PaymentsRowComponent;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
RowModule,
|
||||||
|
PaymentStatusModule,
|
||||||
|
BalanceModule,
|
||||||
|
TranslocoTestingModule.withLangs(
|
||||||
|
{
|
||||||
|
ru: {
|
||||||
|
paymentStatus: {
|
||||||
|
pending: 'Запущен',
|
||||||
|
processed: 'Обработан',
|
||||||
|
captured: 'Подтвержден',
|
||||||
|
cancelled: 'Отменен',
|
||||||
|
refunded: 'Возвращен',
|
||||||
|
failed: 'Неуспешен',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
availableLangs: ['ru'],
|
||||||
|
defaultLang: 'ru',
|
||||||
|
}
|
||||||
|
),
|
||||||
|
],
|
||||||
|
declarations: [PaymentsRowComponent, MockShopDetailsPipe],
|
||||||
|
})
|
||||||
|
.overrideComponent(PaymentsRowComponent, {
|
||||||
|
set: {
|
||||||
|
changeDetection: ChangeDetectionStrategy.Default,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(PaymentsRowComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
component.payment = generateMockPayment();
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('template', () => {
|
||||||
|
it('should show balances component if shop was provided', () => {
|
||||||
|
const date = moment();
|
||||||
|
component.payment = generateMockPayment({
|
||||||
|
amount: 20,
|
||||||
|
currency: 'USD',
|
||||||
|
status: PaymentSearchResult.StatusEnum.Pending,
|
||||||
|
statusChangedAt: date.toDate(),
|
||||||
|
invoiceID: 'id',
|
||||||
|
id: 'id',
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const labels = fixture.debugElement.queryAll(By.css('dsh-row dsh-row-label'));
|
||||||
|
|
||||||
|
expect(labels.length).toBe(4);
|
||||||
|
expect(labels[0].nativeElement.textContent.trim()).toBe(`$0.20`);
|
||||||
|
expect(labels[1].nativeElement.textContent.trim()).toBe(`Запущен`);
|
||||||
|
expect(labels[2].nativeElement.textContent.trim()).toBe(date.format('DD MMMM YYYY, HH:mm'));
|
||||||
|
expect(labels[3].nativeElement.textContent.trim()).toBe(`shopID_name`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,12 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||||
|
|
||||||
|
import { PaymentSearchResult } from '@dsh/api-codegen/anapi';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'dsh-payments-row',
|
||||||
|
templateUrl: 'payments-row.component.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class PaymentsRowComponent {
|
||||||
|
@Input() payment: PaymentSearchResult;
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './payments-panels.module';
|
||||||
|
export * from './payments-panels.component';
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './payment-detail-header.module';
|
||||||
|
export * from './payment-detail-header.component';
|
@ -0,0 +1,8 @@
|
|||||||
|
<div
|
||||||
|
*transloco="let t; scope: 'operations'; read: 'operations.payments.details'"
|
||||||
|
fxLayout="row"
|
||||||
|
fxLayoutAlign="space-between center"
|
||||||
|
>
|
||||||
|
<div>{{ t('name') + ' ' + '#' + id }}</div>
|
||||||
|
<div>{{ changedDate | date: 'dd MMMM yyyy, HH:mm' }}</div>
|
||||||
|
</div>
|
@ -0,0 +1,44 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslocoTestingModule } from '@ngneat/transloco';
|
||||||
|
|
||||||
|
import { PaymentDetailHeaderComponent } from './payment-detail-header.component';
|
||||||
|
|
||||||
|
describe('PaymentDetailHeaderComponent', () => {
|
||||||
|
let component: PaymentDetailHeaderComponent;
|
||||||
|
let fixture: ComponentFixture<PaymentDetailHeaderComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
TranslocoTestingModule.withLangs(
|
||||||
|
{
|
||||||
|
ru: {
|
||||||
|
operations: {
|
||||||
|
payments: {
|
||||||
|
details: {
|
||||||
|
name: 'Платеж',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
availableLangs: ['ru'],
|
||||||
|
defaultLang: 'ru',
|
||||||
|
}
|
||||||
|
),
|
||||||
|
],
|
||||||
|
declarations: [PaymentDetailHeaderComponent],
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(PaymentDetailHeaderComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,11 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'dsh-payment-detail-header',
|
||||||
|
templateUrl: 'payment-detail-header.component.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.Default,
|
||||||
|
})
|
||||||
|
export class PaymentDetailHeaderComponent {
|
||||||
|
@Input() id: string;
|
||||||
|
@Input() changedDate: Date;
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
|
import { TranslocoModule } from '@ngneat/transloco';
|
||||||
|
|
||||||
|
import { PaymentDetailHeaderComponent } from './payment-detail-header.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [CommonModule, TranslocoModule, FlexLayoutModule],
|
||||||
|
declarations: [PaymentDetailHeaderComponent],
|
||||||
|
exports: [PaymentDetailHeaderComponent],
|
||||||
|
})
|
||||||
|
export class PaymentDetailHeaderModule {}
|
@ -0,0 +1 @@
|
|||||||
|
export * from './payment-status.module';
|
@ -0,0 +1,3 @@
|
|||||||
|
<dsh-status *transloco="let paymentStatus; read: 'paymentStatus'" [color]="status | paymentStatusColor">
|
||||||
|
{{ paymentStatus(status) }}
|
||||||
|
</dsh-status>
|
@ -0,0 +1,67 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { TranslocoTestingModule } from '@ngneat/transloco';
|
||||||
|
|
||||||
|
import { PaymentSearchResult } from '@dsh/api-codegen/capi';
|
||||||
|
import { StatusModule } from '@dsh/components/indicators';
|
||||||
|
|
||||||
|
import { PaymentStatusComponent } from './payment-status.component';
|
||||||
|
import { PaymentStatusColorPipe } from './pipes/status-color/status-color.pipe';
|
||||||
|
|
||||||
|
describe('PaymentStatusComponent', () => {
|
||||||
|
let component: PaymentStatusComponent;
|
||||||
|
let fixture: ComponentFixture<PaymentStatusComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
StatusModule,
|
||||||
|
TranslocoTestingModule.withLangs(
|
||||||
|
{
|
||||||
|
ru: {
|
||||||
|
paymentStatus: {
|
||||||
|
pending: 'Запущен',
|
||||||
|
processed: 'Обработан',
|
||||||
|
captured: 'Подтвержден',
|
||||||
|
cancelled: 'Отменен',
|
||||||
|
refunded: 'Возвращен',
|
||||||
|
failed: 'Неуспешен',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
availableLangs: ['ru'],
|
||||||
|
defaultLang: 'ru',
|
||||||
|
}
|
||||||
|
),
|
||||||
|
],
|
||||||
|
declarations: [PaymentStatusComponent, PaymentStatusColorPipe],
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(PaymentStatusComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('template', () => {
|
||||||
|
it('should render valid status name', () => {
|
||||||
|
component.status = PaymentSearchResult.StatusEnum.Refunded;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(fixture.debugElement.query(By.css('dsh-status')).nativeElement.textContent.trim()).toBe('Возвращен');
|
||||||
|
|
||||||
|
component.status = PaymentSearchResult.StatusEnum.Processed;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(fixture.debugElement.query(By.css('dsh-status')).nativeElement.textContent.trim()).toBe('Обработан');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,11 @@
|
|||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
|
||||||
|
import { PaymentSearchResult } from '@dsh/api-codegen/capi';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'dsh-payment-status',
|
||||||
|
templateUrl: './payment-status.component.html',
|
||||||
|
})
|
||||||
|
export class PaymentStatusComponent {
|
||||||
|
@Input() status: PaymentSearchResult.StatusEnum;
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { TranslocoModule } from '@ngneat/transloco';
|
||||||
|
|
||||||
|
import { StatusModule } from '@dsh/components/indicators';
|
||||||
|
|
||||||
|
import { PaymentStatusComponent } from './payment-status.component';
|
||||||
|
import { PaymentStatusColorPipe } from './pipes/status-color/status-color.pipe';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [CommonModule, StatusModule, TranslocoModule],
|
||||||
|
declarations: [PaymentStatusComponent, PaymentStatusColorPipe],
|
||||||
|
exports: [PaymentStatusComponent, PaymentStatusColorPipe],
|
||||||
|
})
|
||||||
|
export class PaymentStatusModule {}
|
@ -0,0 +1,38 @@
|
|||||||
|
import { PaymentSearchResult } from '@dsh/api-codegen/capi';
|
||||||
|
|
||||||
|
import { StatusColor } from '../../../../../../../../theme-manager';
|
||||||
|
import { PaymentStatusColorPipe } from './status-color.pipe';
|
||||||
|
|
||||||
|
const statusEnum = PaymentSearchResult.StatusEnum;
|
||||||
|
|
||||||
|
describe('PaymentStatusColorPipe', () => {
|
||||||
|
let pipe: PaymentStatusColorPipe;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
pipe = new PaymentStatusColorPipe();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('create an instance', () => {
|
||||||
|
expect(pipe).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('transform', () => {
|
||||||
|
it('should return "success" color for Captured or Processed statuses', () => {
|
||||||
|
expect(pipe.transform(statusEnum.Captured)).toBe(StatusColor.success);
|
||||||
|
expect(pipe.transform(statusEnum.Processed)).toBe(StatusColor.success);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return "warn" color for Failed or Cancelled statuses', () => {
|
||||||
|
expect(pipe.transform(statusEnum.Failed)).toBe(StatusColor.warn);
|
||||||
|
expect(pipe.transform(statusEnum.Cancelled)).toBe(StatusColor.warn);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return "pending" color for Pending status', () => {
|
||||||
|
expect(pipe.transform(statusEnum.Pending)).toBe(StatusColor.pending);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return "neutral" color for Refunded status', () => {
|
||||||
|
expect(pipe.transform(statusEnum.Refunded)).toBe(StatusColor.neutral);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -2,7 +2,7 @@ import { Pipe, PipeTransform } from '@angular/core';
|
|||||||
|
|
||||||
import { PaymentSearchResult } from '@dsh/api-codegen/capi/swagger-codegen';
|
import { PaymentSearchResult } from '@dsh/api-codegen/capi/swagger-codegen';
|
||||||
|
|
||||||
import { StatusColor } from '../../../../theme-manager';
|
import { StatusColor } from '../../../../../../../../theme-manager';
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'paymentStatusColor',
|
name: 'paymentStatusColor',
|
@ -0,0 +1,18 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { FlexModule } from '@angular/flex-layout';
|
||||||
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { TranslocoModule } from '@ngneat/transloco';
|
||||||
|
|
||||||
|
import { BaseDialogModule } from '@dsh/app/shared/components/dialog/base-dialog';
|
||||||
|
import { MaxLengthInputModule } from '@dsh/app/shared/components/inputs/max-length-input/max-length-input.module';
|
||||||
|
|
||||||
|
import { CancelHoldService } from './cancel-hold.service';
|
||||||
|
import { CancelHoldDialogComponent } from './components/cancel-hold-dialog/cancel-hold-dialog.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [CommonModule, BaseDialogModule, MaxLengthInputModule, ReactiveFormsModule, FlexModule, TranslocoModule],
|
||||||
|
declarations: [CancelHoldDialogComponent],
|
||||||
|
providers: [CancelHoldService],
|
||||||
|
})
|
||||||
|
export class CancelHoldModule {}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user