FE-1051: extract-party-modification. (#135)

* FE-1051: extract-party-modification.
This commit is contained in:
Aleksandra Usacheva 2020-06-01 15:30:49 +03:00 committed by GitHub
parent 2ad1462cae
commit 011ba0fdd5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 579 additions and 22 deletions

@ -1 +1 @@
Subproject commit cf6578dac794859b0495c812761ce5b94c11bbaf
Subproject commit 4e6aae0f31885d3c56d09c72de7ef8d432149dbf

View File

@ -3,12 +3,26 @@
<cc-timeline *ngIf="timelineInfo$ | async as timelineInfo">
<cc-timeline-item *ngFor="let item of timelineInfo">
<cc-timeline-item-title>
<span class="mat-caption">
<div class="mat-caption">
{{ item.action | actionName }} by {{ item.user_info.username }} at
{{ item.created_at | date: 'dd.MM.yyyy HH:mm:ss' }} ({{
item.created_at | humanizedDuration: { largest: 1, hasAgoEnding: true }
}})
</span>
</div>
<div *ngIf="canUseActionsForQuestionary(item.modifications)">
<button mat-icon-button [matMenuTriggerFor]="menu">
<mat-icon>more_horiz</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button
mat-menu-item
*ngIf="questionary$ | async as questionary"
(click)="extractPartyModification(questionary)"
>
Extract party mods
</button>
</mat-menu>
</div>
</cc-timeline-item-title>
<cc-timeline-item-badge>
<mat-icon>{{ item.action | actionIcon }}</mat-icon>

View File

@ -8,10 +8,11 @@ import {
SimpleChanges,
} from '@angular/core';
import { MatBottomSheet } from '@angular/material/bottom-sheet';
import { MatDialog } from '@angular/material/dialog';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Router } from '@angular/router';
import { from } from 'rxjs';
import { map, scan, switchMap } from 'rxjs/operators';
import { filter, map, scan, switchMap } from 'rxjs/operators';
import { AppAuthGuardService } from '../../../app-auth-guard.service';
import { ClaimStatus } from '../../../papi/model';
@ -21,10 +22,12 @@ import {
} from '../../../party-modification-creator';
import { extractClaimStatus } from '../../../shared/extract-claim-status';
import { getUnionKey } from '../../../shared/utils';
import { Questionary } from '../../../thrift-services/ank/gen-model/questionary_manager';
import { Claim, Modification } from '../../../thrift-services/damsel/gen-model/claim_management';
import { PartyModification } from '../../../thrift-services/damsel/gen-model/payment_processing';
import { RecreateClaimService } from '../recreate-claim';
import { ConversationService } from './conversation.service';
import { ExtractPartyModificationComponent } from './extract-party-modifications/extract-party-modification.component';
import { QuestionaryService } from './questionary.service';
import { SavePartyModificationsService } from './save-party-modifications.service';
import { TimelineAction } from './to-timeline-info/model';
@ -60,7 +63,8 @@ export class ConversationComponent implements OnChanges, OnInit {
private recreateClaimService: RecreateClaimService,
private partyModEmitter: PartyModificationEmitter,
private snackBar: MatSnackBar,
private appAuthGuardService: AppAuthGuardService
private appAuthGuardService: AppAuthGuardService,
private dialog: MatDialog
) {}
ngOnChanges(changes: SimpleChanges) {
@ -126,4 +130,26 @@ export class ConversationComponent implements OnChanges, OnInit {
},
});
}
extractPartyModification(questionary: Questionary) {
const dialog = this.dialog.open(ExtractPartyModificationComponent, {
disableClose: true,
data: { questionary },
});
dialog
.afterClosed()
.pipe(filter((r) => r.length > 0))
.subscribe((result) => {
this.snackBar.open('Party modifications extracted successfully', 'OK', {
duration: 1500,
});
this.partyModificationsChanged(result);
});
}
canUseActionsForQuestionary(modifications: Modification[]) {
return (
modifications.filter((m) => !!m?.claim_modification?.document_modification).length > 0
);
}
}

View File

@ -11,6 +11,7 @@ import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatListModule } from '@angular/material/list';
import { MatMenuModule } from '@angular/material/menu';
import { MatSelectModule } from '@angular/material/select';
import { MonacoEditorModule } from '../../../monaco-editor';
@ -26,6 +27,7 @@ import { ActionIconPipe } from './action-icon.pipe';
import { ActionNamePipe } from './action-name.pipe';
import { CommentComponent } from './comment/comment.component';
import { ConversationComponent } from './conversation.component';
import { ExtractPartyModificationModule } from './extract-party-modifications/extract-party-modification.module';
import { FileContainerModule } from './file-container';
import { FileUploaderModule } from './file-uploader/file-uploader.module';
import { QuestionaryModule } from './questionary/questionary.module';
@ -60,6 +62,8 @@ import { SendCommentComponent } from './send-comment';
UnsavedPartyModificationsModule,
PartyModificationCreatorModule,
MatDividerModule,
ExtractPartyModificationModule,
MatMenuModule,
],
declarations: [
ConversationComponent,

View File

@ -0,0 +1,2 @@
export * from './to-individual-entity-party-modification';
export * from './to-legal-entity-party-modification';

View File

@ -0,0 +1,16 @@
import * as uuid from 'uuid/v4';
import {
ContractorModification,
PartyModification,
} from '../../../../../thrift-services/damsel/gen-model/claim_management';
export const toContractorModification = (
modification: ContractorModification,
id: string = uuid()
): PartyModification => ({
contractor_modification: {
id,
modification,
},
});

View File

@ -0,0 +1,52 @@
import { getOr } from '../../../../../shared/utils';
import { QuestionaryData } from '../../../../../thrift-services/ank/gen-model/questionary_manager';
import { PartyModification } from '../../../../../thrift-services/damsel/gen-model/claim_management';
import { createRussianBankAccount } from '../creators/create-russian-bank-account';
import { toContractorModification } from './to-contractor-modification';
const path = 'contractor.individual_entity.russian_individual_entity';
export const toIndividualEntityPartyModification = (
d: QuestionaryData,
contractorID: string
): PartyModification =>
toContractorModification(
{
creation: {
legal_entity: {
russian_legal_entity: {
registered_name: getOr(d, `${path}.name`, ''),
registered_number: getOr(
d,
`${path}.registration_info.individual_registration_info.ogrnip`,
''
),
inn: getOr(d, `${path}.inn`, '') || '',
actual_address: getOr(
d,
`${path}.registration_info.individual_registration_info.registration_place`,
''
),
post_address: getOr(
d,
`${path}.registration_info.individual_registration_info.registration_place`,
''
),
representative_position: '',
representative_full_name: getOr(
d,
`${path}.russian_private_entity.fio`,
''
),
representative_document: getOr(
d,
`${path}.identity_document.russian_domestic_password.series_number`,
''
),
russian_bank_account: createRussianBankAccount(d),
},
},
},
},
contractorID
);

View File

@ -0,0 +1,64 @@
import { getOr } from '../../../../../shared/utils';
import { QuestionaryData } from '../../../../../thrift-services/ank/gen-model/questionary_manager';
import { PartyModification } from '../../../../../thrift-services/damsel/gen-model/claim_management';
import { createRussianBankAccount } from '../creators/create-russian-bank-account';
import { toContractorModification } from './to-contractor-modification';
const path = 'contractor.legal_entity.russian_legal_entity';
export const toLegalEntityPartyModification = (
d: QuestionaryData,
contractorID: string
): PartyModification =>
toContractorModification(
{
creation: {
legal_entity: {
russian_legal_entity: {
registered_name: getOr(d, `${path}.name`, ''),
registered_number: getOr(
d,
`${path}.registration_info.legal_registration_info.ogrn`,
''
),
inn: getOr(d, `${path}.inn`, ''),
actual_address:
getOr(
d,
`${path}.registration_info.legal_registration_info.actual_address`,
''
) ||
getOr(
d,
`${path}.registration_info.legal_registration_info.registration_address`,
''
),
post_address:
getOr(d, `${path}.postal_address`, '') ||
getOr(
d,
`${path}.registration_info.legal_registration_info.registration_address`,
''
),
representative_position: getOr(
d,
`${path}.legal_owner_info.head_position`,
''
),
representative_full_name: getOr(
d,
`${path}.legal_owner_info.russian_private_entity.fio`,
''
),
representative_document: getOr(
d,
`${path}.legal_owner_info.identity_document.russian_domestic_password.series_number`,
''
),
russian_bank_account: createRussianBankAccount(d),
},
},
},
},
contractorID
);

View File

@ -0,0 +1,20 @@
import { QuestionaryData } from '../../../../../thrift-services/ank/gen-model/questionary_manager';
import { PartyModification } from '../../../../../thrift-services/damsel/gen-model/claim_management';
import { PaymentInstitutionRef } from '../../../../../thrift-services/damsel/gen-model/domain';
export const createContractCreation = (
d: QuestionaryData,
contractorID: string,
contractID: string,
payment_institution: PaymentInstitutionRef
): PartyModification => ({
contract_modification: {
id: contractID,
modification: {
creation: {
contractor_id: contractorID,
payment_institution,
},
},
},
});

View File

@ -0,0 +1,25 @@
import get from 'lodash-es/get';
import { QuestionaryData } from '../../../../../thrift-services/ank/gen-model/questionary_manager';
import { PartyModification } from '../../../../../thrift-services/damsel/gen-model/claim_management';
import { toIndividualEntityPartyModification, toLegalEntityPartyModification } from '../converters';
export const createContractor = (d: QuestionaryData, contractorID: string): PartyModification => {
const isLegalEntityExist = get(d, 'contractor.legal_entity', false);
const isIndividualEntityExist = get(d, 'contractor.individual_entity', false);
const legalEntityCreation = isLegalEntityExist
? toLegalEntityPartyModification(d, contractorID)
: null;
const individualEntityCreation = isIndividualEntityExist
? toIndividualEntityPartyModification(d, contractorID)
: null;
if (isLegalEntityExist) {
return legalEntityCreation;
} else if (isIndividualEntityExist) {
return individualEntityCreation;
} else {
return null;
}
};

View File

@ -0,0 +1,28 @@
import { QuestionaryData } from '../../../../../thrift-services/ank/gen-model/questionary_manager';
import { PartyModification } from '../../../../../thrift-services/damsel/gen-model/claim_management';
import { createRussianBankAccount } from './create-russian-bank-account';
export const createPayoutToolCreation = (
d: QuestionaryData,
contractID: string,
payoutToolID: string
): PartyModification => ({
contract_modification: {
id: contractID,
modification: {
payout_tool_modification: {
payout_tool_id: payoutToolID,
modification: {
creation: {
currency: {
symbolic_code: 'RUB',
},
tool_info: {
russian_bank_account: createRussianBankAccount(d),
},
},
},
},
},
},
});

View File

@ -0,0 +1,13 @@
import { getOr } from '../../../../../shared/utils';
import { QuestionaryData } from '../../../../../thrift-services/ank/gen-model/questionary_manager';
import { RussianBankAccount } from '../../../../../thrift-services/damsel/gen-model/domain';
const path = 'bank_account.russian_bank_account';
export const createRussianBankAccount = (d: QuestionaryData): RussianBankAccount => {
const account = getOr(d, `${path}.account`, '');
const bank_name = getOr(d, `${path}.bank_name`, '');
const bank_bik = getOr(d, `${path}.bank_bik`, '');
const bank_post_account = getOr(d, `${path}.bank_post_account`, '');
return { account, bank_name, bank_bik, bank_post_account };
};

View File

@ -0,0 +1,10 @@
import { PartyModification } from '../../../../../thrift-services/damsel/gen-model/claim_management';
export const createShopAccountCreation = (shopID: string): PartyModification => ({
shop_modification: {
id: shopID,
modification: {
shop_account_creation: { currency: { symbolic_code: 'RUB' } },
},
},
});

View File

@ -0,0 +1,35 @@
import { getOr } from '../../../../../shared/utils';
import { QuestionaryData } from '../../../../../thrift-services/ank/gen-model/questionary_manager';
import { PartyModification } from '../../../../../thrift-services/damsel/gen-model/claim_management';
export const createShopCreation = (
d: QuestionaryData,
contract_id: string,
payout_tool_id: string,
categoryID: number,
shopID: string
): PartyModification => {
const defaultLocation = { url: '' };
const location = getOr(d, 'shop_info.location', defaultLocation);
const defaultDetails = {
name: '',
description: '',
};
const details = getOr(d, 'shop_info.details', defaultDetails);
return {
shop_modification: {
id: shopID,
modification: {
creation: {
contract_id,
payout_tool_id,
location,
details,
category: { id: categoryID },
},
},
},
};
};

View File

@ -0,0 +1,3 @@
export * from './create-contract-creation';
export * from './create-payout-tool-creation';
export * from './create-shop-creation';

View File

@ -0,0 +1,19 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'ccExtractFormCheckboxName',
})
export class ExtractFormCheckboxNamePipe implements PipeTransform {
transform(value: string): string {
switch (value) {
case 'contractCreation':
return 'Contract creation';
case 'payoutToolCreation':
return 'Payout tool creation';
case 'shopCreation':
return 'Shop creation';
default:
return value;
}
}
}

View File

@ -0,0 +1,16 @@
import {
CategoryRef,
PaymentInstitutionRef,
} from '../../../../thrift-services/damsel/gen-model/domain';
interface ExtractParamsFormValue {
contractCreation: boolean;
payoutToolCreation: boolean;
shopCreation: boolean;
}
export interface ExtractFormValue {
params: ExtractParamsFormValue;
category: CategoryRef;
payment_institution: PaymentInstitutionRef;
}

View File

@ -0,0 +1,37 @@
<h4 class="mat-dialog-title">Party modifications extraction params</h4>
<div fxLayout="column" fxLayoutGap="15px">
<form [formGroup]="form" fxLayout="column" fxLayoutGap="15px">
<div class="mat-body-1">Party modifications types:</div>
<div fxLayout="column" fxLayoutGap="15px">
<div *ngFor="let control of getParamsControls() | keyvalue">
<mat-checkbox [formControl]="control.value"
>{{ control.key | ccExtractFormCheckboxName }}
</mat-checkbox>
</div>
</div>
<div *ngIf="form.get('params').value.shopCreation">
<cc-category-ref [required]="true" [form]="form.get('category')"></cc-category-ref>
<cc-payment-institution-ref
[required]="true"
[form]="form.get('payment_institution')"
></cc-payment-institution-ref>
</div>
</form>
<mat-dialog-actions fxLayout="row" fxLayoutAlign="space-between center">
<button mat-dialog-close mat-button color="default" [mat-dialog-close]="false">
CANCEL
</button>
<button
mat-button
color="primary"
(click)="extract()"
[disabled]="
form.get('params').value.shopCreation &&
!form.get('category').value.id &&
!form.get('payment_institution').value.id
"
>
EXTRACT
</button>
</mat-dialog-actions>
</div>

View File

@ -0,0 +1,34 @@
import { Component, Inject } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { Questionary } from '../../../../thrift-services/ank/gen-model/questionary_manager';
import { ExtractPartyModificationsService } from './extract-party-modifications.service';
export interface ExtractPartyModification {
questionary: Questionary;
}
@Component({
templateUrl: 'extract-party-modification.component.html',
providers: [ExtractPartyModificationsService],
})
export class ExtractPartyModificationComponent {
form = this.extractPartyModificationsService.form;
constructor(
private dialogRef: MatDialogRef<ExtractPartyModificationComponent>,
private extractPartyModificationsService: ExtractPartyModificationsService,
@Inject(MAT_DIALOG_DATA) private data: ExtractPartyModification
) {}
extract() {
this.dialogRef.close(
this.extractPartyModificationsService.mapToModifications(this.data.questionary.data)
);
}
getParamsControls() {
return (this.form.get('params') as FormGroup).controls;
}
}

View File

@ -0,0 +1,29 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FlexModule } from '@angular/flex-layout';
import { ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDialogModule } from '@angular/material/dialog';
import { MatInputModule } from '@angular/material/input';
import { PartyModificationCreationModule } from '../../../../party-modification-creator/party-modification-creation';
import { ExtractFormCheckboxNamePipe } from './extract-form-checkbox-name.pipe';
import { ExtractPartyModificationComponent } from './extract-party-modification.component';
@NgModule({
declarations: [ExtractPartyModificationComponent, ExtractFormCheckboxNamePipe],
exports: [ExtractPartyModificationComponent],
imports: [
FlexModule,
MatDialogModule,
MatButtonModule,
MatInputModule,
ReactiveFormsModule,
MatCheckboxModule,
CommonModule,
PartyModificationCreationModule,
],
entryComponents: [ExtractPartyModificationComponent],
})
export class ExtractPartyModificationModule {}

View File

@ -0,0 +1,73 @@
import { Injectable } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import * as uuid from 'uuid/v4';
import { QuestionaryData } from '../../../../thrift-services/ank/gen-model/questionary_manager';
import { PartyModification } from '../../../../thrift-services/damsel/gen-model/claim_management';
import { createContractCreation, createPayoutToolCreation, createShopCreation } from './creators';
import { createContractor } from './creators/create-contractor';
import { createShopAccountCreation } from './creators/create-shop-account-creation';
import { ExtractFormValue } from './extract-form-value';
@Injectable()
export class ExtractPartyModificationsService {
form = this.fb.group({
params: this.fb.group({
contractCreation: true,
payoutToolCreation: true,
shopCreation: true,
}),
category: this.fb.group({}),
payment_institution: this.fb.group({}),
});
constructor(private fb: FormBuilder) {}
mapToModifications(d: QuestionaryData): PartyModification[] {
const {
params: { contractCreation, payoutToolCreation, shopCreation },
category,
payment_institution,
}: ExtractFormValue = this.form.value;
const shopID = shopCreation ? uuid() : '';
const contractID = contractCreation ? uuid() : '';
const contractorID = contractCreation ? uuid() : '';
const payoutToolID = payoutToolCreation ? uuid() : '';
const result = [];
if (contractCreation) {
const contractorCreationModification = createContractor(d, contractorID);
const contractCreationModification = createContractCreation(
d,
contractorID,
contractID,
payment_institution
);
result.push(contractorCreationModification, contractCreationModification);
}
if (payoutToolCreation) {
const payoutToolCreationModification = createPayoutToolCreation(
d,
contractID,
payoutToolID
);
result.push(payoutToolCreationModification);
}
if (shopCreation) {
const shopCreationModification = createShopCreation(
d,
contractID,
payoutToolID,
category.id,
shopID
);
const shopAccountCreation = createShopAccountCreation(shopID);
result.push(shopCreationModification, shopAccountCreation);
}
return result;
}
}

View File

@ -1,13 +1,17 @@
<form [formGroup]="form">
<form [formGroup]="form" fxLayout="column">
<mat-form-field fxFlex>
<mat-select placeholder="Select payment institution" formControlName="id">
<mat-select
placeholder="Select payment institution"
formControlName="id"
[required]="required"
>
<mat-option
*ngFor="let paymentInstitution of paymentInstitutions$ | async"
[value]="paymentInstitution.ref.id"
required
>
{{ paymentInstitution.ref.id }} {{ paymentInstitution.data.name }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-progress-bar *ngIf="isLoading" mode="indeterminate"></mat-progress-bar>
</form>

View File

@ -1,8 +1,10 @@
import { Component, Input, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MatSnackBar } from '@angular/material/snack-bar';
import get from 'lodash-es/get';
import sortBy from 'lodash-es/sortBy';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/internal/operators';
import { map } from 'rxjs/operators';
import { DomainTypedManager } from '../../../../thrift-services';
@ -25,18 +27,35 @@ export class PaymentInstitutionRefComponent implements OnInit {
@Input()
initialValue: PaymentInstitutionRef;
isLoading = true;
paymentInstitutions$: Observable<PaymentInstitutionObject[]>;
constructor(private fb: FormBuilder, private dtm: DomainTypedManager) {}
constructor(
private fb: FormBuilder,
private dtm: DomainTypedManager,
private snackBar: MatSnackBar
) {}
ngOnInit() {
this.paymentInstitutions$ = this.dtm
.getPaymentInstitutions()
.pipe(
map((paymentInstitutions) =>
sortBy(paymentInstitutions, (paymentInstitution) => paymentInstitution.ref.id)
)
);
this.paymentInstitutions$ = this.dtm.getPaymentInstitutions().pipe(
map((paymentInstitutions) =>
sortBy(paymentInstitutions, (paymentInstitution) => paymentInstitution.ref.id)
),
tap(
() => {
this.form.controls.id.enable();
this.isLoading = false;
},
() => {
this.isLoading = false;
this.snackBar.open(
'An error occurred while payment institutions receiving',
'OK'
);
}
)
);
const paymentInstitutionId = get(this, 'initialValue.id', '');
this.form.registerControl(
'id',

View File

@ -50,6 +50,7 @@
*ngIf="action.name === shopModificationNames.categoryModification"
[form]="form.get('modification')"
[initialValue]="modification?.category_modification"
[required]="true"
>
</cc-category-ref>
<cc-shop-details

View File

@ -109,6 +109,10 @@ import { ShopPayoutScheduleModificationComponent } from './shop/shop-payout-sche
FormWrapperComponent,
BusinessScheduleSelectorComponent,
],
exports: [PartyModificationCreationComponent],
exports: [
PartyModificationCreationComponent,
CategoryRefComponent,
PaymentInstitutionRefComponent,
],
})
export class PartyModificationCreationModule {}

View File

@ -1,7 +1,7 @@
<form [formGroup]="form" fxLayout="column" fxLayoutGap="5px">
<form [formGroup]="form" fxLayout="column">
<mat-form-field fxFlex>
<mat-select placeholder="Select category" formControlName="id">
<mat-option *ngFor="let category of categories$ | async" [value]="category.id" required>
<mat-select placeholder="Select category" formControlName="id" [required]="required">
<mat-option *ngFor="let category of categories$ | async" [value]="category.id">
{{ category.id }} {{ category.name }}
</mat-option>
</mat-select>

View File

@ -23,7 +23,7 @@
<cc-category-ref
*ngIf="form.get('category')"
[form]="form.get('category')"
[required]="false"
[required]="true"
[initialValue]="initialValue?.category"
>
</cc-category-ref>

View File

@ -88,5 +88,6 @@ import { TerminalsComponent } from './terminals/terminals.component';
EditTerminalDecisionPriorityComponent,
EditTerminalDecisionWeightComponent,
],
exports: [CategoryComponent],
})
export class ShopDetailsModule {}

View File

@ -1,3 +1,3 @@
<div class="cc-timeline-item-title">
<div><ng-content></ng-content></div>
<div fxFlex fxLayout="row" fxLayoutAlign="space-between center"><ng-content></ng-content></div>
</div>

View File

@ -0,0 +1,7 @@
import get from 'lodash-es/get';
import isEmpty from 'lodash-es/isEmpty';
export const getOr = (object: any, path: string | string[], defaultValue: any): any => {
const val = get(object, path);
return isEmpty(val) ? defaultValue : val;
};

View File

@ -6,3 +6,4 @@ export * from './map-values-to-thrift-enum';
export * from './map-values-to-number';
export * from './is-numeric';
export * from './wrap-values-to-array';
export * from './get-or';