FR-740: Existing international bank account (#566)

This commit is contained in:
Rinat Arsaev 2021-09-14 13:03:32 +03:00 committed by GitHub
parent ab6bb2548b
commit f4b59a76e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 409 additions and 376 deletions

View File

@ -22,6 +22,8 @@
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
<option name="IMPORT_SORT_MEMBERS" value="false" />
<option name="USE_PATH_MAPPING" value="DIFFERENT_PATHS" />
</TypeScriptCodeStyleSettings>
<VueCodeStyleSettings>
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />

View File

@ -1,6 +1,7 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="CommaExpressionJS" enabled="true" level="INFORMATION" enabled_by_default="true" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

View File

@ -9,7 +9,7 @@
"build": "ng build --extra-webpack-config webpack.extra.js",
"test": "ng test",
"coverage": "npx http-server -c-1 -o -p 9875 ./coverage",
"lint-cmd": "eslint \"src/**/*.{ts,js,html}\" --max-warnings 1635",
"lint-cmd": "eslint \"src/**/*.{ts,js,html}\" --max-warnings 1633",
"lint-cache-cmd": "npm run lint-cmd -- --cache",
"lint": "npm run lint-cache-cmd",
"lint-fix": "npm run lint-cache-cmd -- --fix",

View File

@ -1,4 +1,11 @@
import { InternationalBankAccount, PartyModification, PayoutToolInfo } from '@dsh/api-codegen/claim-management';
import { Overwrite } from 'utility-types';
import {
CorrespondentAccount,
InternationalBankAccount,
PartyModification,
PayoutToolInfo,
} from '@dsh/api-codegen/claim-management';
import { createContractPayoutToolCreationModification } from '@dsh/api/claims/claim-party-modification';
import PayoutToolTypeEnum = PayoutToolInfo.PayoutToolTypeEnum;
@ -7,7 +14,18 @@ export function createInternationalContractPayoutToolModification(
id: string,
payoutToolID: string,
symbolicCode: string,
params: Omit<InternationalBankAccount, 'payoutToolType'>
{
correspondentAccount,
...params
}: Overwrite<
Omit<InternationalBankAccount, 'payoutToolType'>,
{
correspondentAccount: Overwrite<
CorrespondentAccount,
{ accountHolder?: CorrespondentAccount['accountHolder'] }
>;
}
>
): PartyModification {
return createContractPayoutToolCreationModification(id, payoutToolID, {
currency: {
@ -16,6 +34,14 @@ export function createInternationalContractPayoutToolModification(
toolInfo: {
payoutToolType: PayoutToolTypeEnum.InternationalBankAccount,
...params,
...(correspondentAccount
? {
correspondentAccount: {
accountHolder: '', // add ui field or remove it
...correspondentAccount,
},
}
: {}),
},
});
}

View File

@ -0,0 +1,27 @@
<div
*transloco="let t; scope: 'create-shop'; read: 'createShop.internationalLegalEntity.internationalBankAccountForm'"
fxLayout="column"
fxLayoutGap="24px"
[formGroup]="formControl"
>
<div fxLayout="column" fxLayoutGap="16px">
<dsh-payout-tool-form formControlName="payoutTool"></dsh-payout-tool-form>
<mat-form-field>
<mat-label>{{ t('currency') }}</mat-label>
<mat-select aria-label="currency" formControlName="currency" required>
<mat-option *ngFor="let currency of currencies" [value]="currency">
{{ currency }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<mat-checkbox
[checked]="formControl.controls.correspondentPayoutTool.enabled"
(change)="toggleCorrespondentPayoutTool()"
>{{ t('correspondentAccount') }}</mat-checkbox
>
<dsh-payout-tool-form
*ngIf="formControl.controls.correspondentPayoutTool.enabled"
formControlName="correspondentPayoutTool"
></dsh-payout-tool-form>
</div>

View File

@ -0,0 +1,36 @@
import { ChangeDetectionStrategy, Component, Injector } from '@angular/core';
import { FormBuilder } from '@ngneat/reactive-forms';
import { PayoutToolForm } from '@dsh/app/shared/components/shop-creation/create-international-shop-entity/components/payout-tool-form/payout-tool-form.component';
import { createValidatedAbstractControlProviders, ValidatedWrappedAbstractControlSuperclass } from '@dsh/utils';
export interface InternationalBankAccountForm {
payoutTool: PayoutToolForm;
currency: string;
correspondentPayoutTool?: PayoutToolForm;
}
@Component({
selector: 'dsh-international-bank-account-form',
templateUrl: 'international-bank-account-form.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: createValidatedAbstractControlProviders(InternationalBankAccountFormComponent),
})
export class InternationalBankAccountFormComponent extends ValidatedWrappedAbstractControlSuperclass<InternationalBankAccountForm> {
formControl = this.fb.group<InternationalBankAccountForm>({
payoutTool: null,
currency: '',
correspondentPayoutTool: { value: null, disabled: true },
});
currencies = ['RUB', 'USD', 'EUR'];
constructor(injector: Injector, private fb: FormBuilder) {
super(injector);
}
toggleCorrespondentPayoutTool(): void {
const { correspondentPayoutTool } = this.formControl.controls;
if (correspondentPayoutTool.disabled) correspondentPayoutTool.enable();
else correspondentPayoutTool.disable();
}
}

View File

@ -2,7 +2,7 @@
*transloco="let t; scope: 'create-shop'; read: 'createShop.internationalLegalEntity.payoutTool'"
fxLayout="column"
fxLayoutGap="16px"
[formGroup]="form"
[formGroup]="formControl"
>
<mat-form-field>
<mat-label>{{ t('accountNumber') }}</mat-label>
@ -31,23 +31,14 @@
<input aria-label="bankName" matInput formControlName="name" />
</mat-form-field>
<mat-form-field>
<mat-label>{{ t('bankCountry') }}</mat-label>
<input aria-label="bankCountry" matInput formControlName="country" required />
<mat-hint>{{ t('countryHint') }}</mat-hint>
</mat-form-field>
<dsh-country-autocomplete-field
formControlName="country"
[label]="t('bankCountry')"
required
></dsh-country-autocomplete-field>
<mat-form-field>
<mat-label>{{ t('bankAddress') }}</mat-label>
<input aria-label="bankAddress" matInput formControlName="address" />
</mat-form-field>
<mat-form-field>
<mat-label>{{ t('currency') }}</mat-label>
<mat-select aria-label="currency" formControlName="currency" required>
<mat-option *ngFor="let currency of temporaryCurrencies" [value]="currency">
{{ currency }}
</mat-option>
</mat-select>
</mat-form-field>
</form>

View File

@ -1,41 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { getTranslocoModule } from '@dsh/app/shared/tests/get-transloco-module';
import { createMockPayoutToolForm } from '../../tests/create-mock-payout-tool-form';
import { PayoutToolFormComponent } from './payout-tool-form.component';
describe('PayoutToolFormComponent', () => {
let fixture: ComponentFixture<PayoutToolFormComponent>;
let component: PayoutToolFormComponent;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
getTranslocoModule(),
NoopAnimationsModule,
ReactiveFormsModule,
MatFormFieldModule,
MatInputModule,
MatSelectModule,
],
declarations: [PayoutToolFormComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PayoutToolFormComponent);
component = fixture.componentInstance;
});
it('should create', () => {
component.form = createMockPayoutToolForm();
fixture.detectChanges();
expect(component).toBeTruthy();
});
});

View File

@ -1,12 +1,42 @@
import { Component, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { ChangeDetectionStrategy, Component, Injector } from '@angular/core';
import { Validators } from '@angular/forms';
import { FormBuilder } from '@ngneat/reactive-forms';
import { createValidatedAbstractControlProviders, ValidatedWrappedAbstractControlSuperclass } from '@dsh/utils';
import { payoutToolFormValidator } from './utils/payout-tool-form-validator';
export interface PayoutToolForm {
number: string;
iban: string;
abaRtn: string;
address: string;
bic: string;
name: string;
country: string;
}
@Component({
selector: 'dsh-payout-tool-form',
templateUrl: 'payout-tool-form.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: createValidatedAbstractControlProviders(PayoutToolFormComponent),
})
export class PayoutToolFormComponent {
@Input() form: FormGroup;
export class PayoutToolFormComponent extends ValidatedWrappedAbstractControlSuperclass<PayoutToolForm> {
formControl = this.fb.group<PayoutToolForm>(
{
number: ['', [Validators.pattern(/^[0-9A-Z]{8,40}$/)]],
iban: ['', [Validators.pattern(/^[A-Z0-9]{14,35}$/)]],
bic: ['', [Validators.pattern(/^([A-Z0-9]{8}|[A-Z0-9]{11})$/)]],
abaRtn: ['', [Validators.pattern(/^[0-9]{9}$/)]],
name: ['', [Validators.maxLength(100)]],
country: '',
address: ['', [Validators.maxLength(1000)]],
},
{ validator: payoutToolFormValidator }
);
temporaryCurrencies = ['RUB', 'USD', 'EUR'];
constructor(injector: Injector, private fb: FormBuilder) {
super(injector);
}
}

View File

@ -1,12 +1,10 @@
import { FormGroup, ValidatorFn } from '@ngneat/reactive-forms';
import isEmpty from 'lodash-es/isEmpty';
import { InternationalBankAccountFormValue } from '../../types/international-bank-account-form-value';
import { PayoutToolForm } from '../payout-tool-form.component';
// bic | iban | abaRtn | country & address & name should be provided;
export const payoutToolFormValidator: ValidatorFn = (
form: FormGroup<InternationalBankAccountFormValue>
): { error: boolean } | null => {
export const payoutToolFormValidator: ValidatorFn = (form: FormGroup<PayoutToolForm>): { error: boolean } | null => {
const { bic, iban, abaRtn, country, address, name } = form.controls;
const isValidNumbers = [

View File

@ -30,16 +30,15 @@
<h2 class="dsh-title">{{ t('payoutToolTitle') }}</h2>
<dsh-payout-tool-form [form]="formControl.controls.payoutTool"></dsh-payout-tool-form>
<mat-checkbox
[checked]="hasCorrespondentAccount"
(change)="onCorrespondentAccountChange(!hasCorrespondentAccount)"
>{{ t('correspondentAccount') }}</mat-checkbox
>
<dsh-payout-tool-form
*ngIf="hasCorrespondentAccount"
[form]="formControl.controls.correspondentPayoutTool"
></dsh-payout-tool-form>
<dsh-created-existing-switch formGroupName="bankAccount" [form]="formControl.controls.bankAccount">
<dsh-international-bank-account-form
*dshCreatedCase
formControlName="created"
></dsh-international-bank-account-form>
<dsh-existing-bank-account
*dshExistingCase
formControlName="existing"
bankAccountType="PayoutToolDetailsInternationalBankAccount"
></dsh-existing-bank-account>
</dsh-created-existing-switch>
</form>

View File

@ -4,7 +4,6 @@ import { FormBuilder } from '@ngneat/reactive-forms';
import { createTypeUnionDefaultForm } from '@dsh/app/shared/components/shop-creation/created-existing-switch/created-existing-switch.component';
import { createValidatedAbstractControlProviders, ValidatedWrappedAbstractControlSuperclass } from '@dsh/utils';
import { InternationalPayoutToolFormService } from '../../services/international-payout-tool-form/international-payout-tool-form.service';
import { InternationalShopEntityFormValue } from '../../types/international-shop-entity-form-value';
@Component({
@ -18,24 +17,10 @@ export class ShopFormComponent extends ValidatedWrappedAbstractControlSuperclass
shopDetails: null,
orgDetails: createTypeUnionDefaultForm(),
paymentInstitution: null,
payoutTool: this.internationalPayoutToolFormService.getForm(),
bankAccount: createTypeUnionDefaultForm(),
});
hasCorrespondentAccount = false;
constructor(
injector: Injector,
private fb: FormBuilder,
private internationalPayoutToolFormService: InternationalPayoutToolFormService
) {
constructor(injector: Injector, private fb: FormBuilder) {
super(injector);
}
onCorrespondentAccountChange(value: boolean): void {
if (value) {
this.formControl.addControl('correspondentPayoutTool', this.internationalPayoutToolFormService.getForm());
} else {
this.formControl.removeControl('correspondentPayoutTool');
}
this.hasCorrespondentAccount = value;
}
}

View File

@ -15,8 +15,10 @@ import { CategoryAutocompleteFieldModule } from '@dsh/app/shared/components/inpu
import { CountryAutocompleteFieldModule } from '@dsh/app/shared/components/inputs/country-autocomplete-field';
import { PaymentInstitutionFieldModule } from '@dsh/app/shared/components/inputs/payment-institution-field';
import { ShopFieldModule } from '@dsh/app/shared/components/inputs/shop-field';
import { InternationalBankAccountFormComponent } from '@dsh/app/shared/components/shop-creation/create-international-shop-entity/components/international-bank-account-form/international-bank-account-form.component';
import { NewContractorFormComponent } from '@dsh/app/shared/components/shop-creation/create-international-shop-entity/components/new-contractor-form/new-contractor-form.component';
import { CreatedExistingSwitchModule } from '@dsh/app/shared/components/shop-creation/created-existing-switch/created-existing-switch.module';
import { ExistingBankAccountModule } from '@dsh/app/shared/components/shop-creation/existing-bank-account/existing-bank-account.module';
import { ExistingContractFormModule } from '@dsh/app/shared/components/shop-creation/existing-contract-form/existing-contract-form.module';
import { ShopDetailsFormModule } from '@dsh/app/shared/components/shop-creation/shop-details-form';
import { CountryCodesModule } from '@dsh/app/shared/services';
@ -26,7 +28,6 @@ import { PayoutToolFormComponent } from './components/payout-tool-form/payout-to
import { ShopFormComponent } from './components/shop-form/shop-form.component';
import { CreateInternationalShopEntityComponent } from './create-international-shop-entity.component';
import { CreateInternationalShopEntityService } from './services/create-international-shop-entity/create-international-shop-entity.service';
import { InternationalPayoutToolFormService } from './services/international-payout-tool-form/international-payout-tool-form.service';
@NgModule({
imports: [
@ -51,14 +52,16 @@ import { InternationalPayoutToolFormService } from './services/international-pay
ContractorDetailsModule,
ErrorMessageModule,
ExistingContractFormModule,
ExistingBankAccountModule,
],
declarations: [
CreateInternationalShopEntityComponent,
PayoutToolFormComponent,
ShopFormComponent,
NewContractorFormComponent,
InternationalBankAccountFormComponent,
],
exports: [CreateInternationalShopEntityComponent],
providers: [CreateInternationalShopEntityService, InternationalPayoutToolFormService],
providers: [CreateInternationalShopEntityService],
})
export class CreateInternationalShopEntityModule {}

View File

@ -1,6 +1,5 @@
import { Injectable } from '@angular/core';
import { IdGeneratorService } from '@rbkmoney/id-generator';
import isNil from 'lodash-es/isNil';
import { Observable } from 'rxjs';
import { mapTo, switchMap } from 'rxjs/operators';
@ -15,6 +14,10 @@ import {
import { createInternationalContractPayoutToolModification } from '@dsh/api/claims/claim-party-modification/claim-contract-modification/create-international-contract-payout-tool-modification';
import { InternationalShopEntityFormValue } from '../../types/international-shop-entity-form-value';
import {
payoutToolDetailsInternationalBankAccountToInternationalBankAccount,
payoutToolFormToInternationalBankAccount,
} from './utils';
@Injectable()
export class CreateInternationalShopEntityService {
@ -34,8 +37,7 @@ export class CreateInternationalShopEntityService {
shopDetails,
orgDetails: { created: newContractor, existing: contract },
paymentInstitution,
payoutTool,
correspondentPayoutTool = null,
bankAccount: { created: newBankAccount, existing: payoutTool },
}: InternationalShopEntityFormValue): Modification[] {
const contractorID = this.idGenerator.uuid();
const contractID = this.idGenerator.uuid();
@ -68,31 +70,26 @@ export class CreateInternationalShopEntityService {
contractorID,
paymentInstitution: { id: paymentInstitution?.id ?? 1 },
}),
createInternationalContractPayoutToolModification(contractID, payoutToolID, payoutTool.currency, {
iban: payoutTool.iban,
number: payoutTool.number,
bank: {
abaRtn: payoutTool.abaRtn,
address: payoutTool.address,
bic: payoutTool.bic,
name: payoutTool.name,
country: payoutTool.country,
},
correspondentAccount: isNil(correspondentPayoutTool)
? null
createInternationalContractPayoutToolModification(
contractID,
payoutToolID,
newBankAccount ? newBankAccount.currency : payoutTool.currency,
newBankAccount
? {
...payoutToolFormToInternationalBankAccount(newBankAccount.payoutTool),
correspondentAccount: newBankAccount.correspondentPayoutTool
? payoutToolFormToInternationalBankAccount(newBankAccount.correspondentPayoutTool)
: null,
}
: {
accountHolder: '', // add ui field or remove it
iban: correspondentPayoutTool.iban,
number: correspondentPayoutTool.number,
bank: {
abaRtn: correspondentPayoutTool.abaRtn,
address: correspondentPayoutTool.address,
bic: correspondentPayoutTool.bic,
name: correspondentPayoutTool.name,
country: correspondentPayoutTool.country,
},
},
}),
...payoutToolDetailsInternationalBankAccountToInternationalBankAccount(payoutTool.details),
correspondentAccount: payoutTool.details.correspondentBankAccount
? payoutToolDetailsInternationalBankAccountToInternationalBankAccount(
payoutTool.details.correspondentBankAccount
)
: null,
}
),
createShopCreationModification(shopID, {
category: {
categoryID: shopDetails.category?.categoryID ?? 1,

View File

@ -0,0 +1,2 @@
export * from './payout-tool-details-international-bank-account-to-international-bank-account';
export * from './payout-tool-form-to-international-bank-account';

View File

@ -0,0 +1,19 @@
import { PayoutToolDetailsInternationalBankAccount } from '@dsh/api-codegen/capi';
import { InternationalBankAccount } from '@dsh/api-codegen/claim-management';
export function payoutToolDetailsInternationalBankAccountToInternationalBankAccount(
form: Omit<PayoutToolDetailsInternationalBankAccount, 'detailsType'>
): Required<Pick<InternationalBankAccount, 'iban' | 'number' | 'bank'>> {
const { bankDetails } = form;
return {
iban: form.iban,
number: form.number,
bank: {
abaRtn: bankDetails.abartn,
address: bankDetails.address,
bic: bankDetails.bic,
name: bankDetails.name,
country: bankDetails.countryCode,
},
};
}

View File

@ -0,0 +1,19 @@
import { InternationalBankAccount } from '@dsh/api-codegen/claim-management';
import { PayoutToolForm } from '../../../components/payout-tool-form/payout-tool-form.component';
export function payoutToolFormToInternationalBankAccount(
form: PayoutToolForm
): Required<Pick<InternationalBankAccount, 'iban' | 'number' | 'bank'>> {
return {
iban: form.iban,
number: form.number,
bank: {
abaRtn: form.abaRtn,
address: form.address,
bic: form.bic,
name: form.name,
country: form.country,
},
};
}

View File

@ -1,49 +0,0 @@
import { TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { instance, mock } from 'ts-mockito';
import { CountryCodesService } from '@dsh/app/shared/services/country-codes/country-codes.service';
import { InternationalPayoutToolFormService } from './international-payout-tool-form.service';
describe('InternationalPayoutToolFormService', () => {
let service: InternationalPayoutToolFormService;
let mockCountryCodesService: CountryCodesService;
beforeEach(() => {
mockCountryCodesService = mock(CountryCodesService);
});
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ReactiveFormsModule],
providers: [
InternationalPayoutToolFormService,
{
provide: CountryCodesService,
useFactory: () => instance(mockCountryCodesService),
},
],
});
service = TestBed.inject(InternationalPayoutToolFormService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
describe('getForm', () => {
it('should create new form', () => {
expect(service.getForm().value).toEqual({
number: '',
iban: '',
abaRtn: '',
address: '',
bic: '',
name: '',
country: '',
currency: '',
});
});
});
});

View File

@ -1,29 +0,0 @@
import { Injectable } from '@angular/core';
import { Validators } from '@angular/forms';
import { FormBuilder, FormGroup } from '@ngneat/reactive-forms';
import { alpha3CountryValidator } from '@dsh/utils';
import { InternationalBankAccountFormValue } from '../../types/international-bank-account-form-value';
import { payoutToolFormValidator } from './payout-tool-form-validator';
@Injectable()
export class InternationalPayoutToolFormService {
constructor(private fb: FormBuilder) {}
getForm(): FormGroup<InternationalBankAccountFormValue> {
return this.fb.group(
{
number: ['', [Validators.pattern(/^[0-9A-Z]{8,40}$/)]],
iban: ['', [Validators.pattern(/^[A-Z0-9]{14,35}$/)]],
bic: ['', [Validators.pattern(/^([A-Z0-9]{8}|[A-Z0-9]{11})$/)]],
abaRtn: ['', [Validators.pattern(/^[0-9]{9}$/)]],
name: ['', [Validators.maxLength(100)]],
country: ['', [alpha3CountryValidator]],
address: ['', [Validators.maxLength(1000)]],
currency: ['', [Validators.pattern(/^[A-Z]{3}$/)]],
},
{ validator: payoutToolFormValidator }
);
}
}

View File

@ -1,16 +0,0 @@
import { FormControl, FormGroup } from '@ngneat/reactive-forms';
import { InternationalBankAccountFormValue } from '../types/international-bank-account-form-value';
export function createMockPayoutToolForm(): FormGroup<InternationalBankAccountFormValue> {
return new FormGroup<InternationalBankAccountFormValue>({
number: new FormControl<string>(''),
iban: new FormControl<string>(''),
bic: new FormControl<string>(''),
abaRtn: new FormControl<string>(''),
name: new FormControl<string>(''),
country: new FormControl<string>(''),
address: new FormControl<string>(''),
currency: new FormControl<string>(''),
});
}

View File

@ -1,10 +0,0 @@
export interface InternationalBankAccountFormValue {
number: string;
iban: string;
abaRtn: string;
address: string;
bic: string;
name: string;
country: string;
currency: string;
}

View File

@ -1,15 +1,18 @@
import { PaymentInstitution } from '@dsh/api-codegen/capi';
import { TypeUnion } from '@dsh/app/shared/components/shop-creation/created-existing-switch/created-existing-switch.component';
import { ExistingBankAccountForm } from '@dsh/app/shared/components/shop-creation/existing-bank-account/existing-bank-account.component';
import { ShopDetailsForm } from '@dsh/app/shared/components/shop-creation/shop-details-form/shop-details-form.component';
import { ExistingContractForm } from '../../existing-contract-form/existing-contract-form.component';
import { InternationalBankAccountForm } from '../components/international-bank-account-form/international-bank-account-form.component';
import { NewContractorForm } from '../components/new-contractor-form/new-contractor-form.component';
import { InternationalBankAccountFormValue } from './international-bank-account-form-value';
export interface InternationalShopEntityFormValue {
shopDetails: ShopDetailsForm;
orgDetails: TypeUnion<NewContractorForm, ExistingContractForm<'InternationalLegalEntity'>>;
paymentInstitution: PaymentInstitution;
payoutTool: InternationalBankAccountFormValue;
correspondentPayoutTool?: InternationalBankAccountFormValue;
bankAccount: TypeUnion<
InternationalBankAccountForm,
ExistingBankAccountForm<'PayoutToolDetailsInternationalBankAccount'>
>;
}

View File

@ -1,49 +0,0 @@
import { ChangeDetectionStrategy, Component, Injector } from '@angular/core';
import { FormControl } from '@ngneat/reactive-forms';
import { UntilDestroy } from '@ngneat/until-destroy';
import { Observable, BehaviorSubject, of } from 'rxjs';
import { switchMap, tap, share } from 'rxjs/operators';
import { PayoutsService } from '@dsh/api';
import { PayoutTool, Shop } from '@dsh/api-codegen/capi';
import {
ValidatedWrappedAbstractControlSuperclass,
createValidatedAbstractControlProviders,
progressTo,
errorTo,
} from '@dsh/utils';
@UntilDestroy()
@Component({
selector: 'dsh-existing-bank-account',
templateUrl: 'existing-bank-account.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: createValidatedAbstractControlProviders(ExistingBankAccountComponent),
})
export class ExistingBankAccountComponent extends ValidatedWrappedAbstractControlSuperclass<PayoutTool, Shop> {
formControl = new FormControl<Shop>(null);
payoutTool: PayoutTool = null;
progress$ = new BehaviorSubject<number>(0);
error$ = new BehaviorSubject<unknown>(null);
constructor(injector: Injector, private payoutsService: PayoutsService) {
super(injector);
}
protected setUpInnerToOuter$(value$: Observable<Shop>): Observable<PayoutTool> {
return value$.pipe(
switchMap((shop) => {
if (!shop?.contractID || !shop?.payoutToolID) return of<PayoutTool>(null);
return this.payoutsService
.getPayoutToolByID(shop.contractID, shop.payoutToolID)
.pipe(progressTo(this.progress$), errorTo(this.error$));
}),
tap((payoutTool) => (this.payoutTool = payoutTool)),
share()
);
}
protected outerToInner(): Shop {
return null;
}
}

View File

@ -1,9 +1,9 @@
import { ChangeDetectionStrategy, Component, Injector } from '@angular/core';
import { FormBuilder } from '@ngneat/reactive-forms';
import { BankContent } from '@dsh/api-codegen/aggr-proxy';
import { createValidatedAbstractControlProviders, ValidatedWrappedAbstractControlSuperclass } from '@dsh/utils';
import { BankContent } from '../../../../../../api-codegen/aggr-proxy';
import { RussianBankAccountForm } from './types/bank-account-form-data';
@Component({

View File

@ -17,6 +17,10 @@
</div>
<dsh-created-existing-switch formGroupName="bankAccount" [form]="formControl.controls.bankAccount">
<dsh-russian-bank-account-form *dshCreatedCase formControlName="created"></dsh-russian-bank-account-form>
<dsh-existing-bank-account *dshExistingCase formControlName="existing"></dsh-existing-bank-account>
<dsh-existing-bank-account
*dshExistingCase
formControlName="existing"
bankAccountType="PayoutToolDetailsBankAccount"
></dsh-existing-bank-account>
</dsh-created-existing-switch>
</div>

View File

@ -17,6 +17,7 @@ import { BaseDialogModule } from '@dsh/app/shared/components/dialog/base-dialog'
import { PaymentInstitutionFieldModule } from '@dsh/app/shared/components/inputs/payment-institution-field';
import { ShopFieldModule } from '@dsh/app/shared/components/inputs/shop-field';
import { CreatedExistingSwitchModule } from '@dsh/app/shared/components/shop-creation/created-existing-switch/created-existing-switch.module';
import { ExistingBankAccountModule } from '@dsh/app/shared/components/shop-creation/existing-bank-account/existing-bank-account.module';
import { ExistingContractFormModule } from '@dsh/app/shared/components/shop-creation/existing-contract-form/existing-contract-form.module';
import { ShopDetailsFormModule } from '@dsh/app/shared/components/shop-creation/shop-details-form/shop-details-form.module';
import { ButtonModule } from '@dsh/components/buttons';
@ -25,7 +26,6 @@ import { DetailsItemModule } from '@dsh/components/layout';
import { DaDataModule } from '../../../../dadata';
import { ShopPayoutToolDetailsService } from '../../../../sections/payment-section/integrations/shops/services/shop-payout-tool-details/shop-payout-tool-details.service';
import { ExistingBankAccountComponent } from './components/existing-bank-account/existing-bank-account.component';
import { NewContractorFormComponent } from './components/new-contractor-form/new-contractor-form.component';
import { OrgDetailsFormComponent } from './components/org-details-form/org-details-form.component';
import { RussianBankAccountFormComponent } from './components/russian-bank-account-form/russian-bank-account-form.component';
@ -58,12 +58,12 @@ import { CreateRussianShopEntityService } from './services/create-russian-shop-e
PaymentInstitutionFieldModule,
CreatedExistingSwitchModule,
ExistingContractFormModule,
ExistingBankAccountModule,
],
declarations: [
CreateRussianShopEntityComponent,
OrgDetailsFormComponent,
ShopFormComponent,
ExistingBankAccountComponent,
RussianBankAccountFormComponent,
NewContractorFormComponent,
],

View File

@ -3,7 +3,7 @@ import { IdGeneratorService } from '@rbkmoney/id-generator';
import { forkJoin, Observable, of } from 'rxjs';
import { pluck, switchMap } from 'rxjs/operators';
import { Claim, PartyModification, RussianBankAccount } from '@dsh/api-codegen/claim-management';
import { Claim, PartyModification } from '@dsh/api-codegen/claim-management';
import { ClaimsService } from '@dsh/api/claims';
import {
createContractCreationModification,
@ -52,8 +52,7 @@ export class CreateRussianShopEntityService {
representativePosition,
} = contract?.contractor || { ...newContractor, postAddress: '' };
const payoutToolBankAccount: Omit<RussianBankAccount, 'payoutToolType'> =
(payoutTool?.details as Omit<RussianBankAccount, 'payoutToolType'>) || bankAccount;
const payoutToolBankAccount = payoutTool?.details || bankAccount;
const result: PartyModification[] = [
createRussianLegalEntityModification(contractorID, {

View File

@ -1,5 +1,6 @@
import { PaymentInstitution, PayoutTool } from '@dsh/api-codegen/capi';
import { PaymentInstitution } from '@dsh/api-codegen/capi';
import { TypeUnion } from '@dsh/app/shared/components/shop-creation/created-existing-switch/created-existing-switch.component';
import { ExistingBankAccountForm } from '@dsh/app/shared/components/shop-creation/existing-bank-account/existing-bank-account.component';
import { ShopDetailsForm } from '../../shop-details-form/shop-details-form.component';
import { OrgDetailsForm } from '../components/org-details-form/org-details-form.component';
@ -9,5 +10,5 @@ export interface RussianShopForm {
shopDetails: ShopDetailsForm;
orgDetails: OrgDetailsForm;
paymentInstitution: PaymentInstitution;
bankAccount: TypeUnion<RussianBankAccountForm, PayoutTool>;
bankAccount: TypeUnion<RussianBankAccountForm, ExistingBankAccountForm<'PayoutToolDetailsBankAccount'>>;
}

View File

@ -1,15 +1,15 @@
<div
*transloco="let p; scope: 'create-shop'; read: 'createShop.russianLegalEntity.payoutTool'"
*transloco="let p; scope: 'create-shop'; read: 'createShop.existingBankAccountForm'"
fxLayout="column"
fxLayoutGap="16px"
>
<dsh-shop-field required [formControl]="formControl" [label]="p('existing.label')"></dsh-shop-field>
<dsh-shop-field required [formControl]="formControl" [label]="p('shopLabel')"></dsh-shop-field>
<ng-container *transloco="let t">
<div *ngIf="progress$ | async; else (error$ | async) ? error : loaded" class="dsh-body-1">
{{ t('loading') }}
</div>
<ng-template #error>
<div class="dsh-body-1">{{ t('errors.common') }}</div>
<div class="dsh-body-1">{{ error$ | async | errorMessage }}</div>
</ng-template>
<ng-template #loaded>
<dsh-payout-tool-details

View File

@ -0,0 +1,99 @@
import { ChangeDetectionStrategy, Component, Injector, Input } from '@angular/core';
import { FormControl } from '@ngneat/reactive-forms';
import { TranslocoService } from '@ngneat/transloco';
import { UntilDestroy } from '@ngneat/until-destroy';
import { Observable, BehaviorSubject, of, throwError, EMPTY } from 'rxjs';
import { switchMap, tap, share, catchError } from 'rxjs/operators';
import { Overwrite } from 'utility-types';
import { PayoutsService } from '@dsh/api';
import {
PayoutTool,
PayoutToolDetailsBankAccount,
PayoutToolDetailsInternationalBankAccount,
Shop,
} from '@dsh/api-codegen/capi';
import { CommonError, ErrorService } from '@dsh/app/shared';
import {
ValidatedWrappedAbstractControlSuperclass,
createValidatedAbstractControlProviders,
progressTo,
errorTo,
} from '@dsh/utils';
type BankAccountType = 'PayoutToolDetailsInternationalBankAccount' | 'PayoutToolDetailsBankAccount';
export type ExistingBankAccountForm<T extends BankAccountType = BankAccountType> = Overwrite<
PayoutTool,
{
details: T extends 'PayoutToolDetailsInternationalBankAccount'
? PayoutToolDetailsInternationalBankAccount
: PayoutToolDetailsBankAccount;
}
>;
@UntilDestroy()
@Component({
selector: 'dsh-existing-bank-account',
templateUrl: 'existing-bank-account.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: createValidatedAbstractControlProviders(ExistingBankAccountComponent),
})
export class ExistingBankAccountComponent extends ValidatedWrappedAbstractControlSuperclass<
ExistingBankAccountForm,
Shop
> {
@Input() bankAccountType: BankAccountType;
formControl = new FormControl<Shop>(null);
payoutTool: PayoutTool = null;
progress$ = new BehaviorSubject<number>(0);
error$ = new BehaviorSubject<unknown>(null);
constructor(
injector: Injector,
private payoutsService: PayoutsService,
private transloco: TranslocoService,
private errorService: ErrorService
) {
super(injector);
}
protected setUpInnerToOuter$(value$: Observable<Shop>): Observable<PayoutTool> {
return value$.pipe(
switchMap((shop) =>
(shop?.contractID && shop?.payoutToolID ? this.getPayoutToolByShop(shop) : of<PayoutTool>(null)).pipe(
progressTo(this.progress$),
errorTo(this.error$),
catchError((err) => (this.errorService.error(err, false), EMPTY))
)
),
tap((payoutTool) => (this.payoutTool = payoutTool)),
share()
);
}
protected outerToInner(): Shop {
return null;
}
private getPayoutToolByShop(shop: Shop): Observable<PayoutTool> {
return this.payoutsService.getPayoutToolByID(shop.contractID, shop.payoutToolID).pipe(
switchMap((payoutTool) => {
if (payoutTool.details.detailsType !== this.bankAccountType)
return this.transloco
.selectTranslate(
`existingBankAccountForm.errors.${
this.bankAccountType === 'PayoutToolDetailsInternationalBankAccount'
? 'onlyInternationalShopCanBeSelected'
: 'onlyRussianShopCanBeSelected'
}`,
null,
'create-shop'
)
.pipe(switchMap((t) => throwError(new CommonError(t))));
return of(payoutTool);
})
);
}
}

View File

@ -0,0 +1,25 @@
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 { ErrorMessageModule, PayoutToolDetailsModule } from '@dsh/app/shared';
import { ShopFieldModule } from '@dsh/app/shared/components/inputs/shop-field';
import { ExistingBankAccountComponent } from './existing-bank-account.component';
@NgModule({
imports: [
TranslocoModule,
FlexModule,
ShopFieldModule,
ReactiveFormsModule,
CommonModule,
PayoutToolDetailsModule,
ErrorMessageModule,
],
declarations: [ExistingBankAccountComponent],
exports: [ExistingBankAccountComponent],
})
export class ExistingBankAccountModule {}

View File

@ -5,7 +5,7 @@
>
<dsh-shop-field required [label]="p('useShop')" [formControl]="formControl"></dsh-shop-field>
<ng-container class="dsh-body-1" *transloco="let t">
<div *ngIf="contractProgress$ | async; else (error$ | async) ? error : contract && loaded" class="dsh-body-1">
<div *ngIf="progress$ | async; else (error$ | async) ? error : contract && loaded" class="dsh-body-1">
{{ t('loading') }}
</div>
<ng-template #error>

View File

@ -8,7 +8,7 @@ import { Overwrite } from 'utility-types';
import { Shop, Contract, InternationalLegalEntity, LegalEntity, RussianLegalEntity } from '@dsh/api-codegen/capi';
import { ContractsService } from '@dsh/api/contracts';
import { CommonError } from '@dsh/app/shared';
import { CommonError, ErrorService } from '@dsh/app/shared';
import {
ValidatedWrappedAbstractControlSuperclass,
createValidatedAbstractControlProviders,
@ -36,7 +36,7 @@ export class ExistingContractFormComponent extends ValidatedWrappedAbstractContr
@Input() entityType: EntityTypeEnum;
formControl = this.fb.control<Shop>(null);
contractProgress$ = new BehaviorSubject(0);
progress$ = new BehaviorSubject(0);
error$ = new BehaviorSubject<unknown>(null);
contract: Contract = null;
@ -44,7 +44,8 @@ export class ExistingContractFormComponent extends ValidatedWrappedAbstractContr
injector: Injector,
private contractsService: ContractsService,
private fb: FormBuilder,
private transloco: TranslocoService
private transloco: TranslocoService,
private errorService: ErrorService
) {
super(injector);
}
@ -56,33 +57,34 @@ export class ExistingContractFormComponent extends ValidatedWrappedAbstractContr
protected setUpInnerToOuter$(value$: Observable<Shop>): Observable<ExistingContractForm> {
return value$.pipe(
switchMap((shop) =>
(shop
? (this.contractsService.getContractByID(shop.contractID) as Observable<ExistingContractForm>).pipe(
switchMap((contract) => {
if (contract.contractor.entityType !== this.entityType)
return this.transloco
.selectTranslate(
`existingContractForm.errors.${
this.entityType === EntityTypeEnum.InternationalLegalEntity
? 'onlyInternationalShopCanBeSelected'
: 'onlyRussianShopCanBeSelected'
}`,
null,
'create-shop'
)
.pipe(switchMap((t) => throwError(new CommonError(t))));
return of(contract);
})
)
: of<ExistingContractForm>(null)
).pipe(
progressTo(this.contractProgress$),
(shop ? this.getContract(shop.contractID) : of<ExistingContractForm>(null)).pipe(
progressTo(this.progress$),
errorTo(this.error$),
catchError(() => EMPTY)
catchError((err) => (this.errorService.error(err, false), EMPTY))
)
),
tap((contract) => (this.contract = contract)),
share()
);
}
private getContract(contractID: Contract['id']): Observable<ExistingContractForm> {
return this.contractsService.getContractByID(contractID).pipe(
switchMap((contract: ExistingContractForm) => {
if (contract.contractor.entityType !== this.entityType)
return this.transloco
.selectTranslate(
`existingContractForm.errors.${
this.entityType === EntityTypeEnum.InternationalLegalEntity
? 'onlyInternationalShopCanBeSelected'
: 'onlyRussianShopCanBeSelected'
}`,
null,
'create-shop'
)
.pipe(switchMap((t) => throwError(new CommonError(t))));
return of(contract);
})
);
}
}

View File

@ -1,12 +1,12 @@
import { Injectable } from '@angular/core';
import isNil from 'lodash-es/isNil';
import { CountryCodes } from '@dsh/utils';
import { CountryCodes } from './types';
@Injectable()
export class CountryCodesService {
getCountryCode(country: string): number {
const code = CountryCodes[country];
const code = CountryCodes[country] as CountryCodes;
if (isNil(code)) {
throw new Error(`Can't get code for country [${country}]`);
}

View File

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

View File

@ -10,6 +10,13 @@
"onlyRussianShopCanBeSelected": "Можно выбрать только российский магазин"
}
},
"existingBankAccountForm": {
"shopLabel": "Использовать реквизиты на основе магазина",
"errors": {
"onlyInternationalShopCanBeSelected": "Можно выбрать только международный магазин",
"onlyRussianShopCanBeSelected": "Можно выбрать только российский магазин"
}
},
"russianLegalEntity": {
"title": "Заявка на создание магазина",
"orgDetails": {
@ -43,11 +50,6 @@
"bik": "БИК",
"postAccount": "Корреспондентский счет",
"account": "Расчетный счет"
},
"existing": {
"label": "Использовать реквизиты на основе магазина",
"name": "Наименование банка",
"bankAccount": "БИК / Кор. счет / Расчетный счет"
}
},
"send": "Отправить на рассмотрение",
@ -62,10 +64,12 @@
"actualAddress": "Адрес места нахождения (если отличается от места регистрации)",
"country": "Страна резиденции",
"paymentInstitution": "Платежная организация",
"countryHint": "Alpha-3 код по стандарту ISO 3166-1. Примеры: USA, GBR, DEU...",
"payoutToolTitle": "Вывод средств",
"correspondentAccount": "Корреспондентский счет",
"send": "Отправить на рассмотрение",
"internationalBankAccountForm": {
"currency": "Валюта средства вывода",
"correspondentAccount": "Корреспондентский счет"
},
"payoutTool": {
"accountNumber": "Номер счета",
"description": "Необходимо заполнить либо IBAN, либо BIC, либо ABA RTN, либо наименование, страну и адрес",
@ -74,9 +78,7 @@
"abaRtn": "ABA RTN",
"bankName": "Наименование юридического лица банковской организации",
"bankCountry": "Страна резиденции банковской организации",
"countryHint": "Alpha-3 код по стандарту ISO 3166-1. Примеры: USA, GBR, DEU...",
"bankAddress": "Адрес юридического лица банковской организации",
"currency": "Валюта средства вывода"
"bankAddress": "Адрес юридического лица банковской организации"
},
"orgDetails": {
"useShop": "Использовать контракт на основе магазина"

View File

@ -10,7 +10,6 @@ export * from './query-list-started-array-changes';
export * from './extract-error';
export * from './form';
export * from './query-params-to-str';
export * from './validators';
export * from './is-empty-value';
export * from './required-super';
export * from './operators';

View File

@ -1,29 +0,0 @@
import { FormControl } from '@ngneat/reactive-forms';
import { alpha3CountryValidator } from '@dsh/utils';
describe('alpha3CountryValidator', () => {
it('should be valid', () => {
expect(alpha3CountryValidator(new FormControl('USA'))).toBeNull();
});
describe('should be invalid', () => {
const unknownCountryCode = { unknownCountryCode: true };
it('non-existent country', () => {
expect(alpha3CountryValidator(new FormControl('XXX'))).toEqual(unknownCountryCode);
});
it('empty', () => {
expect(alpha3CountryValidator(new FormControl(''))).toEqual(null);
});
it('null', () => {
expect(alpha3CountryValidator(new FormControl(null))).toEqual(null);
});
it('undefined', () => {
expect(alpha3CountryValidator(new FormControl(undefined))).toEqual(null);
});
});
});

View File

@ -1,12 +0,0 @@
import { FormControl } from '@ngneat/reactive-forms';
import { ValidationErrors } from '@ngneat/reactive-forms/lib/types';
import isNil from 'lodash-es/isNil';
import { CountryCodes } from '../types/country-codes';
export function alpha3CountryValidator(
control: FormControl<string>
): ValidationErrors<{ unknownCountryCode: boolean }> {
if (!control?.value) return null;
return isNil(CountryCodes[control?.value]) ? { unknownCountryCode: true } : null;
}

View File

@ -1,2 +0,0 @@
export * from './alpha-3-country-validator/alpha-3-country-validator';
export * from './types/country-codes';