FE-902: Сreate questionary document 📮 🗃 👉📄 (#74)

This commit is contained in:
Rinat Arsaev 2020-01-22 15:57:30 +03:00 committed by GitHub
parent 1232b5ecb3
commit 3f19e0e0b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
105 changed files with 1802 additions and 185 deletions

5
.huskyrc Normal file
View File

@ -0,0 +1,5 @@
{
"hooks": {
"pre-push": "npm run test-silent"
}
}

View File

@ -8,10 +8,13 @@
"typescript.tsdk": "node_modules/typescript/lib",
"cSpell.words": [
"CAPI",
"FATCA",
"SNILS",
"OGRN",
"OKVED",
"actionbar",
"anapi",
"anthroponym",
"codegen",
"dadata",
"datepicker",
@ -30,6 +33,7 @@
"snils",
"submodule",
"transloco",
"ЕГРЮЛ",
"СНИЛС",
"бенефициарного",
"бенефициарные",

View File

@ -7,6 +7,7 @@
- To get more help use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
- [Angular Material](https://material.angular.io/)
- [Prettier](https://prettier.io/)
- [PDFMake](https://pdfmake.github.io/docs/)
## Initialization

27
package-lock.json generated
View File

@ -6030,9 +6030,9 @@
}
},
"es5-ext": {
"version": "0.10.51",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.51.tgz",
"integrity": "sha512-oRpWzM2WcLHVKpnrcyB7OW8j/s67Ba04JCm0WnNv3RiABSvs7mrQlutB8DBv793gKcp0XENR8Il8WxGTlZ73gQ==",
"version": "0.10.50",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.50.tgz",
"integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==",
"requires": {
"es6-iterator": "~2.0.3",
"es6-symbol": "~3.1.1",
@ -6087,26 +6087,15 @@
"es6-iterator": "~2.0.1",
"es6-symbol": "3.1.1",
"event-emitter": "~0.3.5"
},
"dependencies": {
"es6-symbol": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz",
"integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=",
"requires": {
"d": "1",
"es5-ext": "~0.10.14"
}
}
}
},
"es6-symbol": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.2.tgz",
"integrity": "sha512-/ZypxQsArlv+KHpGvng52/Iz8by3EQPxhmbuz8yFG89N/caTFBSbcXONDw0aMjy827gQg26XAjP4uXFvnfINmQ==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz",
"integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=",
"requires": {
"d": "^1.0.1",
"es5-ext": "^0.10.51"
"d": "1",
"es5-ext": "~0.10.14"
}
},
"escape-html": {

View File

@ -2,7 +2,6 @@
"name": "dashboard",
"version": "0.0.0",
"scripts": {
"prepush": "npm run test-silent",
"start": "ng serve --port 8000",
"stub": "ng serve --port 8000 --configuration=stub-keycloak",
"aot": "ng serve --port 8000 --aot",
@ -12,7 +11,7 @@
"lint": "ng lint",
"lint-fix": "ng lint --fix",
"e2e": "ng e2e",
"prettier": "prettier \"**/*.{html,js,ts,css,scss,md,json,prettierrc,svg}\"",
"prettier": "prettier \"**/*.{html,js,ts,css,scss,md,json,prettierrc,svg,huskyrc}\"",
"prettify": "npm run prettier -- --write",
"check": "npm run prettier -- --list-different",
"fix": "npm run lint-fix; npm run prettify",

View File

@ -1,2 +1,3 @@
export * from './questionary.module';
export * from './questionary.service';
export * from './type-guards';

View File

@ -0,0 +1,2 @@
export * from './russian-individual-entity-questionary';
export * from './russian-legal-entity-questionary';

View File

@ -0,0 +1,35 @@
import {
Questionary,
IndividualEntityContractor,
RussianIndividualEntity,
QuestionaryData,
Contractor,
IndividualResidencyInfo,
IndividualRegistrationInfo
} from '../../../api-codegen/questionary';
import { Replace } from '../../../../type-utils';
type RussianIndividualEntityContractor = Replace<
IndividualEntityContractor,
{
individualEntity: Replace<
RussianIndividualEntity,
{
residencyInfo: IndividualResidencyInfo;
registrationInfo: IndividualRegistrationInfo;
}
>;
}
>;
type RussianIndividualEntityQuestionaryData = Replace<
QuestionaryData,
{ contractor: RussianIndividualEntityContractor }
>;
export type RussianIndividualEntityQuestionary = Replace<Questionary, { data: RussianIndividualEntityQuestionaryData }>;
export function isRussianIndividualEntityQuestionary(
questionary: Questionary
): questionary is RussianIndividualEntityQuestionary {
return questionary.data.contractor.contractorType === Contractor.ContractorTypeEnum.IndividualEntityContractor;
}

View File

@ -0,0 +1,41 @@
import {
Questionary,
LegalEntityContractor,
RussianLegalEntity,
QuestionaryData,
Contractor,
LegalResidencyInfo,
LegalRegistrationInfo
} from '../../../api-codegen/questionary';
import { Replace } from '../../../../type-utils';
type RussianLegalEntityContractor = Replace<
LegalEntityContractor,
{
legalEntity: Replace<
RussianLegalEntity,
{
residencyInfo: LegalResidencyInfo;
registrationInfo: LegalRegistrationInfo;
}
>;
}
>;
type RussianLegalEntityQuestionaryData = Replace<
QuestionaryData,
{
contractor: RussianLegalEntityContractor;
}
>;
export type RussianLegalEntityQuestionary = Replace<
Questionary,
{
data: RussianLegalEntityQuestionaryData;
}
>;
export function isRussianLegalEntityQuestionary(
questionary: Questionary
): questionary is RussianLegalEntityQuestionary {
return questionary.data.contractor.contractorType === Contractor.ContractorTypeEnum.LegalEntityContractor;
}

View File

@ -0,0 +1,18 @@
import { cmMarginsToIn } from './cm-margins-to-in';
describe('cmMarginsToIn', () => {
it('should convert 4 cm margins to in', () => {
const margins = cmMarginsToIn(5.08, 2.54, 5.08, 7.62);
expect(margins).toEqual([144, 72, 144, 216]);
});
it('should convert 2 cm margins to in', () => {
const margins = cmMarginsToIn(5.08, 7.62);
expect(margins).toEqual([144, 216]);
});
it('should convert 1 cm margins to in', () => {
const margins = cmMarginsToIn(7.62);
expect(margins).toBe(216);
});
});

View File

@ -0,0 +1,13 @@
import { Margins } from 'pdfmake/build/pdfmake';
import { cmToIn } from './cm-to-in';
/**
* left, top, right, bottom
*/
export function cmMarginsToIn(
...marginsCm: [number] | [number, number] | [number, number, number, number] | number[]
): Margins {
const marginsIn = marginsCm.slice(0, 4).map(cm => cmToIn(cm));
return (marginsIn.length === 1 ? marginsIn[0] : marginsIn) as Margins;
}

View File

@ -0,0 +1,13 @@
import { cmToIn } from './cm-to-in';
describe('cmToIn', () => {
it('should convert cm to in', () => {
const cm = cmToIn(5.08, 72);
expect(cm).toBe(144);
});
it('should convert cm to in with selected dpi', () => {
const cm = cmToIn(5.08, 100);
expect(cm).toBe(200);
});
});

View File

@ -0,0 +1,3 @@
export function cmToIn(cm: number, dpi = 72): number {
return (cm / 2.54) * dpi;
}

View File

@ -1,14 +0,0 @@
import { createFontFamily } from './font/font-family';
export enum Family {
serif = 'serif'
}
export const fonts = [
createFontFamily(Family.serif, {
normal: '/assets/fonts/Tinos regular.ttf',
bold: '/assets/fonts/Tinos 700.ttf',
italics: '/assets/fonts/Tinos italic.ttf',
bolditalics: '/assets/fonts/Tinos 700italic.ttf'
})
];

View File

@ -1,12 +1,9 @@
import { NgModule } from '@angular/core';
import { DocumentService } from './document.service';
import { DocumentFontsService } from './font/document-fonts.service';
import { FontsService } from './fonts';
@NgModule({
imports: [],
declarations: [],
entryComponents: [],
providers: [DocumentService, DocumentFontsService]
providers: [DocumentService, FontsService]
})
export class DocumentModule {}

View File

@ -1,18 +1,23 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { createPdf, TDocumentDefinitions, TCreatedPdf } from 'pdfmake/build/pdfmake';
import { createPdf, TDocumentDefinitions, TCreatedPdf, TableLayoutFunctions } from 'pdfmake/build/pdfmake';
import { DocumentFontsService } from './font/document-fonts.service';
import { fonts } from './document-fonts-config';
import { FontsService } from './fonts';
import { fontsConfig } from './fonts-config';
@Injectable()
export class DocumentService {
constructor(private documentFontService: DocumentFontsService) {
this.documentFontService.init(fonts);
constructor(private fontsService: FontsService) {
this.fontsService.loadFonts(fontsConfig);
}
createPdf(docDefinition: TDocumentDefinitions): Observable<TCreatedPdf> {
return this.documentFontService.init$.pipe(map(() => createPdf(docDefinition)));
createPdf(
docDefinition: TDocumentDefinitions,
tableLayouts?: { [name: string]: TableLayoutFunctions }
): Observable<TCreatedPdf> {
return this.fontsService.fontsData$.pipe(
map(({ fonts, vfs }) => createPdf(docDefinition, tableLayouts, fonts, vfs))
);
}
}

View File

@ -1,60 +0,0 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import pdfMake, { TFontFamilyTypes } from 'pdfmake/build/pdfmake';
import { forkJoin, Observable } from 'rxjs';
import { switchMap, map, tap, shareReplay } from 'rxjs/operators';
import { Font } from './font';
import { FontFamily } from './font-family';
import { blobToBase64 } from './blob-to-base64';
@Injectable()
export class DocumentFontsService<F extends FontFamily[] = FontFamily[]> {
init$: Observable<boolean> = this.init([] as F);
constructor(private http: HttpClient) {}
init(loadedFonts: F): Observable<boolean> {
this.init$ = this.loadFonts(loadedFonts).pipe(
shareReplay(1),
map(() => true)
);
return this.init$;
}
private loadFont(fontUrl: string): Observable<string> {
return this.http.request('GET', fontUrl, { responseType: 'blob' }).pipe(switchMap(blob => blobToBase64(blob)));
}
private loadFonts(loadedFonts: F): Observable<string[]> {
const fonts: Font[] = loadedFonts.reduce((r, family) => r.concat(Object.values(family)), []);
return forkJoin(fonts.map(font => this.loadFont(font.url))).pipe(
tap(fontsBase64 => {
const vfs = {};
for (let i = 0; i < fonts.length; ++i) {
fonts[i].base64 = fontsBase64[i];
vfs[fonts[i].hash] = fonts[i].base64;
}
pdfMake.vfs = vfs;
pdfMake.fonts = this.getPdfMakeFonts(loadedFonts);
})
);
}
private getFontFamilyHashMap<T extends string>(fonts: FontFamily<T>) {
return Object.entries(fonts).reduce((fontHashMap, [type, font]) => {
fontHashMap[type] = font.hash;
return fontHashMap;
}, {});
}
private getPdfMakeFonts(loadedFonts: F) {
return loadedFonts.reduce(
(currentFonts, family) => {
currentFonts[Object.values(family)[0].family] = this.getFontFamilyHashMap(family);
return currentFonts;
},
{} as { [name: string]: TFontFamilyTypes }
);
}
}

View File

@ -1,22 +0,0 @@
import { FontFamily, createFontFamily } from './font-family';
import { Font } from './font';
describe('createFontFamily', () => {
it('should return empty FontFamily', () => {
const family: FontFamily = createFontFamily('test', {});
expect(family).toEqual({});
});
it('should return FontFamily structure', () => {
const family: FontFamily = createFontFamily('test', { normal: 'a/b/c d.ttf' });
expect(family).toEqual({ normal: new Font('test', 'normal', 'a/b/c d.ttf') });
});
it('should return FontFamily with 2 style', () => {
const family: FontFamily = createFontFamily('test', { normal: 'a/b/c d.ttf', bold: 'bold.ttf' });
expect(family).toEqual({
normal: new Font('test', 'normal', 'a/b/c d.ttf'),
bold: new Font('test', 'bold', 'bold.ttf')
});
});
});

View File

@ -1,15 +0,0 @@
import { TFontFamilyTypes } from 'pdfmake/build/pdfmake';
import { Font } from './font';
export type FontFamily<T extends string = string> = Partial<Record<keyof TFontFamilyTypes, Font<T>>>;
export function createFontFamily<T extends string>(
family: T,
urlMap: Partial<Record<keyof TFontFamilyTypes, string>> = {}
): FontFamily<T> {
return Object.entries(urlMap).reduce((fontFamily, [type, url]: [keyof TFontFamilyTypes, string]) => {
fontFamily[type] = new Font(family, type, url);
return fontFamily;
}, {});
}

View File

@ -1,8 +0,0 @@
import { Font } from './font';
describe('Font', () => {
it('should return hash', () => {
const font = new Font('test', 'normal', 'a/b/c d.ttf');
expect(font.hash).toBe('test_normal_(a_b_c+d.ttf)');
});
});

View File

@ -1,11 +0,0 @@
import { TFontFamilyTypes } from 'pdfmake/build/pdfmake';
export class Font<T extends string = string> {
base64: string;
get hash() {
return `${this.family}_${this.type.toString()}_(${this.url.replace(/\//g, '_').replace(/ /g, '+')})`;
}
constructor(public family: T, public type: keyof TFontFamilyTypes, public url: string) {}
}

View File

@ -0,0 +1,21 @@
import { TFontFamilyTypes } from 'pdfmake/build/pdfmake';
const fontsDir = '/assets/fonts/';
const robotoDir = `${fontsDir}Roboto/`;
export enum FontFamily {
serif,
fa
}
export const fontsConfig: { [name in FontFamily]: TFontFamilyTypes } = {
[FontFamily.serif]: {
normal: `${robotoDir}Roboto-Regular.ttf`,
bold: `${robotoDir}Roboto-Bold.ttf`,
italics: `${robotoDir}Roboto-RegularItalic.ttf`,
bolditalics: `${robotoDir}Roboto-BoldItalic.ttf`
},
[FontFamily.fa]: {
normal: `${fontsDir}font-awesome5/fa-regular-400.ttf`
}
};

View File

@ -0,0 +1,6 @@
export interface Font {
family: string | number;
type: string;
url: string;
hash: string;
}

View File

@ -0,0 +1,6 @@
import { vfs, fonts } from 'pdfmake/build/pdfmake';
export interface FontsData {
vfs: typeof vfs;
fonts: typeof fonts;
}

View File

@ -0,0 +1,49 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { forkJoin, Observable } from 'rxjs';
import { switchMap, map, shareReplay } from 'rxjs/operators';
import { blobToBase64 } from './blob-to-base64';
import { toFonts } from './to-fonts';
import { Font } from './font';
import { FontsData } from './fonts-data';
@Injectable()
export class FontsService {
fontsData$: Observable<FontsData>;
constructor(private http: HttpClient) {}
loadFonts(fontsUrls: FontsData['fonts']): Observable<FontsData> {
const fonts = toFonts(fontsUrls);
return (this.fontsData$ = forkJoin(fonts.map(({ url }) => this.loadFont(url))).pipe(
map(fontsBase64 => ({ vfs: this.getVFS(fonts, fontsBase64), fonts: this.getFonts(fonts) })),
shareReplay(1)
));
}
private loadFont(url: string): Observable<string> {
return this.http.get(url, { responseType: 'blob' }).pipe(switchMap(blob => blobToBase64(blob)));
}
private getFonts(fonts: Font[]): FontsData['fonts'] {
return fonts.reduce(
(accFonts, { family, type, hash }) => {
if (accFonts[family]) accFonts[family][type] = hash;
else accFonts[family] = { [type]: hash };
return accFonts;
},
{} as FontsData['fonts']
);
}
private getVFS(fonts: Font[], fontsBase64: string[]): FontsData['vfs'] {
return fonts.reduce(
(accVFS, font, idx) => {
accVFS[font.hash] = fontsBase64[idx];
return accVFS;
},
{} as FontsData['vfs']
);
}
}

View File

@ -1,35 +1,38 @@
import { TestBed, getTestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { DocumentFontsService } from './document-fonts.service';
import { createFontFamily } from './font-family';
import { FontsService } from './fonts.service';
describe('DocumentFontsService', () => {
const fonts = [
createFontFamily('serif', {
describe('FontsService', () => {
const fonts = {
serif: {
normal: '/assets/regular.ttf'
})
];
}
};
function createDocumentFontsServiceService() {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [DocumentFontsService]
providers: [FontsService]
});
const injector = getTestBed();
const service: DocumentFontsService = injector.get(DocumentFontsService);
const service: FontsService = injector.get(FontsService);
const httpMock: HttpTestingController = injector.get(HttpTestingController);
return { injector, service, httpMock };
}
it('should load fonts', () => {
const { service, httpMock } = createDocumentFontsServiceService();
service.init(fonts);
service.init$.subscribe(result => {
expect(result).toEqual(true);
const blob = new Blob([new Uint8Array(2)]);
service.loadFonts(fonts);
service.fontsData$.subscribe(result => {
expect(result).toEqual({
vfs: { serif_normal: 'AAA=' },
fonts: { serif: { normal: 'serif_normal' } }
});
});
const req = httpMock.expectOne('/assets/regular.ttf');
req.flush(new Blob([new Uint8Array(2)], { type: 'blob' }));
req.flush(blob);
expect(req.request.method).toBe('GET');
});
});

View File

@ -0,0 +1,2 @@
export * from './font';
export * from './fonts.service';

View File

@ -0,0 +1,27 @@
import { TFontFamilyTypes } from 'pdfmake/build/pdfmake';
import { toFonts } from './to-fonts';
describe('toFonts', () => {
const fonts: { [name in string | number]: TFontFamilyTypes } = {
test: {
normal: 'normal',
bold: 'bold',
italics: '🥳',
bolditalics: 'url5'
},
1: {
normal: 'url'
}
};
it('should return fonts list', () => {
expect(toFonts(fonts)).toEqual([
{ family: '1', type: 'normal', hash: '1_normal', url: 'url' },
{ family: 'test', type: 'normal', hash: 'test_normal', url: 'normal' },
{ family: 'test', type: 'bold', hash: 'test_bold', url: 'bold' },
{ family: 'test', type: 'italics', hash: 'test_italics', url: '🥳' },
{ family: 'test', type: 'bolditalics', hash: 'test_bolditalics', url: 'url5' }
]);
});
});

View File

@ -0,0 +1,19 @@
import { TFontFamilyTypes } from 'pdfmake/build/pdfmake';
import { Font } from './font';
function familyToFonts(familyName: string | number, family: TFontFamilyTypes): Font[] {
return (Object.entries(family) as [string, string][]).map(([type, url]) => ({
family: familyName,
type,
hash: `${familyName}_${type}`,
url
}));
}
export function toFonts(pdfMakeFonts: { [name in string | number]: TFontFamilyTypes }): Font[] {
return Object.entries(pdfMakeFonts).reduce(
(accFonts, [familyName, familyFonts]) => accFonts.concat(familyToFonts(familyName, familyFonts)),
[] as Font[]
);
}

View File

@ -0,0 +1,7 @@
export * from './document.module';
export * from './document.service';
export * from './fonts';
export * from './fonts-config';
export * from './pdfmake-typings';
export * from './cm-to-in';
export * from './cm-margins-to-in';

View File

@ -0,0 +1,14 @@
import {
Content as PDFMakeContent,
Style as PDFMakeStyle,
Table as PDFMakeTable,
TableCell as PDFMakeTableCell
} from 'pdfmake/build/pdfmake';
import { Replace } from '../../type-utils';
type Style = PDFMakeStyle | string | string[];
export type Content = Replace<PDFMakeContent, { style?: Style }>;
export type TableCell = Replace<PDFMakeTableCell, { style?: Style }>;
export type Table = Replace<PDFMakeTable, { body: (Content | PDFMakeTableCell | string)[][] }>;

View File

@ -0,0 +1,12 @@
import { cmToIn, Content } from '../../document';
import { createCaptionedText } from '../create-content';
export function createCompanyHeader(companyName: string, companyInn: string): Content {
return {
columns: [
{ ...createCaptionedText(companyName, 'Наименование вашей компании'), width: 'auto' },
{ ...createCaptionedText(companyInn, 'ИНН'), width: 'auto' }
],
columnGap: cmToIn(2)
};
}

View File

@ -0,0 +1,143 @@
import { DocDef } from '../create-questionary';
import {
createVerticalParagraph,
createHeader,
createHeadline,
createEnding,
createInlineCheckboxWithTitle,
createInlineParagraph,
createHorizontalCheckbox
} from '../create-content';
import { getContactInfo, toYesNo, getIdentityDocument, getDate, getPercent, simpleYesNo } from '../select-data';
import { BeneficialOwner } from '../../api-codegen/questionary';
import { getIndividualResidencyInfo } from './get-individual-residency-info';
import { createCompanyHeader } from './create-company-header';
import { toOptional } from '../../../utils';
const EMPTY = '';
export function getDocDef(beneficialOwner: BeneficialOwner, companyName: string, companyInn: string): DocDef {
const {
russianPrivateEntity,
migrationCardInfo,
residenceApprove,
ownershipPercentage,
inn,
snils,
pdlRelationDegree,
pdlCategory,
residencyInfo,
identityDocument
} = toOptional(beneficialOwner);
const { birthDate, fio, birthPlace, citizenship, actualAddress, contactInfo } = toOptional(russianPrivateEntity);
const optionalMigrationCardInfo = toOptional(migrationCardInfo);
const optionalResidenceApprove = toOptional(residenceApprove);
const identityDocumentInfo = getIdentityDocument(identityDocument);
const { usaTaxResident, exceptUsaTaxResident } = getIndividualResidencyInfo(residencyInfo);
const contact = getContactInfo(contactInfo);
return {
content: [
createHeader('Приложение №'),
createCompanyHeader(companyName, companyInn),
createHeadline('АНКЕТА ФИЗИЧЕСКОГО ЛИЦА - БЕНЕФИЦИАРНОГО ВЛАДЕЛЬЦА'),
createVerticalParagraph('1. Бенефициарный владелец', [
[
createHorizontalCheckbox(
[
'лицо, не указанное в учредительных документах/выписке ЕГРЮЛ/списке участников/реестре акционеров, но которое косвенно владеет более 25% в капитале компании или контролирует действия компании',
'лицо, указанное в учредительных документах/выписке ЕГРЮЛ/списке участников/реестре акционеров (участник/акционер), владеющее в конечном счете более 25% в капитале компании'
],
-1 // TODO
)
]
]),
createInlineParagraph(
'2. Процент владения капиталом юридического лица',
getPercent(ownershipPercentage) || EMPTY
),
createInlineParagraph('3. Фамилия, Имя, Отчество (при наличии)', fio || EMPTY),
createInlineParagraph('4. Дата рождения', getDate(birthDate) || EMPTY),
createInlineParagraph('5. Место рождения', birthPlace || EMPTY),
createInlineParagraph('6. Гражданство', citizenship || 'РФ'), // TODO: move to questionary input
createInlineParagraph('7. ИНН (при наличии)', inn || EMPTY),
createInlineParagraph('8. Реквизиты документа, удостоверяющего личность', [
[`8.1. Вид документа: ${identityDocumentInfo.name || EMPTY}`],
[`8.2. Серия и номер: ${identityDocumentInfo.seriesNumber || EMPTY}`],
[
`8.3. Наименование органа, выдавшего документ, код подразделения (при наличии): ${identityDocumentInfo.issuer ||
EMPTY}`
],
[`8.4. Дата выдачи: ${getDate(identityDocumentInfo.issuedAt) || EMPTY}`]
]),
createInlineParagraph('9. Данные миграционной карты¹', [
[`9.1. Номер карты: ${optionalMigrationCardInfo.cardNumber || EMPTY}`],
[
`9.2. Дата начала срока пребывания в РФ: ${getDate(optionalMigrationCardInfo.beginningDate) ||
EMPTY}`
],
[
`9.3. Дата окончания срока пребывания в РФ: ${getDate(optionalMigrationCardInfo.expirationDate) ||
EMPTY}`
]
]),
createInlineParagraph(
'10. Данные документа, подтверждающего право иностранного гражданина или лица без гражданства на пребывание (проживание) в РФ1¹',
[
[`10.1. Вид документа: ${optionalResidenceApprove.name || EMPTY}`],
[`10.2. Серия (при наличии): ${optionalResidenceApprove.series || EMPTY}`],
[`10.3. Номер документа: ${optionalResidenceApprove.number || EMPTY}`],
[`10.4. Дата начала срока действия: ${getDate(optionalResidenceApprove.beginningDate) || EMPTY}`],
[
`10.5. Дата окончания срока действия: ${getDate(optionalResidenceApprove.expirationDate) ||
EMPTY}`
]
]
),
createInlineParagraph(
'11. Адрес места жительства (регистрации) или места пребывания',
actualAddress || EMPTY
),
createInlineParagraph('12. СНИЛС (при наличии)', snils || EMPTY),
createInlineParagraph('13. Контактная информация (телефон/email)', contact || EMPTY),
createVerticalParagraph('14. Принадлежность физического лица к некоторым категориям лиц', [
[
createInlineCheckboxWithTitle(
'14.1. Принадлежность к категории ПДЛ²',
simpleYesNo,
toYesNo(pdlCategory)
)
],
[
createInlineCheckboxWithTitle(
'14.2. Является родственником ПДЛ',
simpleYesNo,
toYesNo(!!pdlRelationDegree)
),
`14.3. Степень родства: ${pdlRelationDegree || EMPTY}`
],
[
createInlineCheckboxWithTitle(
'14.4. Является ли бенефициарный владелец налогоплательщиком/налоговым резидентом США?',
simpleYesNo,
toYesNo(usaTaxResident)
)
],
[
createInlineCheckboxWithTitle(
'14.5. Является ли бенефициарный владелец налогоплательщиком/налоговым резидентом иного иностранного государства (кроме США)?',
simpleYesNo,
toYesNo(exceptUsaTaxResident)
)
]
])
],
prefooter: createEnding(),
footer: [
'¹ Заполняется только для иностранных граждан и лиц без гражданства, находящихся на территории РФ в случае, если необходимость наличия у них данного документа предусмотрена законодательством РФ',
'² Публичные должностные лица, включая российские, иностранные и международные.'
].join('\n'),
footerHeight: 3.1
};
}

View File

@ -0,0 +1,20 @@
import get from 'lodash.get';
import { ResidencyInfo, IndividualResidencyInfo } from '../../api-codegen/questionary';
export function getIndividualResidencyInfo(
residencyInfo: ResidencyInfo
): {
usaTaxResident: boolean;
exceptUsaTaxResident: boolean;
} {
if (get(residencyInfo, 'residencyInfoType') === 'IndividualResidencyInfo') {
const { usaTaxResident, exceptUsaTaxResident } = residencyInfo as IndividualResidencyInfo;
return {
usaTaxResident,
exceptUsaTaxResident
};
}
console.error("ResidencyInfo isn't IndividualResidencyInfo");
return null;
}

View File

@ -0,0 +1 @@
export * from './get-doc-def';

View File

@ -0,0 +1,31 @@
import { Layout } from '../create-questionary';
import { Content } from '../../document';
export function createCaptionedText(text: string, caption: string): Content {
return {
layout: Layout.underline,
table: {
body: [
[
{
text,
style: {
bold: true,
alignment: 'center'
}
}
],
[
{
text: caption,
style: {
bold: true,
alignment: 'center',
italics: true
}
}
]
]
}
};
}

View File

@ -0,0 +1,74 @@
import { icons } from './icons';
import { Content } from '../../document';
import { Layout } from '../create-questionary';
import { createGrid } from './create-grid';
type Items<T extends any> = string[] | [T, string][];
function itemsIsKeyValue<T extends any>(srcItems: Items<T>): srcItems is [T, string][] {
return Array.isArray(srcItems[0]);
}
function itemsToMap<T extends any>(srcItems: Items<T>): Map<T, string> {
return itemsIsKeyValue(srcItems)
? new Map(srcItems)
: new Map<T, string>(srcItems.map((i, idx) => [idx, i] as any));
}
function itemsWithActive<T extends any>(
itemsSrc: Items<T>,
activeKey?: T
): { key: T; value: string; isActive: boolean }[] {
const items = itemsToMap(itemsSrc);
return Array.from(items).map(([key, value]) => ({
key,
value,
isActive: activeKey === key
}));
}
export function createCheckbox(text: string, active = false): Content {
return { text: [active ? icons.checkSquare : icons.square, ' ', text] };
}
export function createInlineCheckbox<T extends any>(itemsSrc: Items<T>, activeKey?: T): Content {
const text = itemsWithActive(itemsSrc, activeKey)
.reduce((acc, { value, isActive }) => [...acc, createCheckbox(value, isActive), ' '], [])
.slice(0, -1);
return { text };
}
export function createInlineCheckboxWithTitle<T extends any>(title: string, items: Items<T>, activeKey?: T): Content {
const inlineCheckbox = createInlineCheckbox(items, activeKey);
return { ...inlineCheckbox, text: [`${title}: `, ...inlineCheckbox.text] };
}
export function createVerticalCheckbox<T extends any>(itemsSrc: Items<T>, activeKey?: T): Content {
return {
layout: Layout.noBorders,
table: {
widths: ['*'],
body: itemsWithActive(itemsSrc, activeKey).map(({ value, isActive }) => [createCheckbox(value, isActive)])
}
};
}
export function createVerticalCheckboxWithTitle<T extends any>(title: string, items: Items<T>, activeKey?: T): Content {
const verticalCheckbox = createVerticalCheckbox(items, activeKey);
const { table } = verticalCheckbox;
return {
...verticalCheckbox,
table: {
...table,
widths: ['auto', ...table.widths],
body: table.body.map((row, idx) => [idx === 0 ? title : null, ...row])
}
};
}
export function createHorizontalCheckbox<T extends any>(itemsSrc: Items<T>, activeKey?: T): Content {
return createGrid(
itemsWithActive(itemsSrc, activeKey).map(({ value, isActive }) => createCheckbox(value, isActive)),
0.25
);
}

View File

@ -0,0 +1,22 @@
import moment from 'moment';
import { cmMarginsToIn, Content } from '../../document';
export function createEnding(): Content {
return {
layout: 'noBorders',
margin: cmMarginsToIn(0, 0.2, 0, 1.1),
table: {
widths: ['*', 'auto'],
body: [
[
'М.П.',
{
text: moment().format('LL') + '\n\n\n_____________________/______________/',
style: { alignment: 'right' }
}
]
]
}
};
}

View File

@ -0,0 +1,30 @@
import { Layout } from '../create-questionary';
import { Content, Table, cmMarginsToIn } from '../../document';
import { createTableBody } from './create-table-body';
import { getColumnsCount } from './get-columns-count';
type Item = Table['body'][number][number] | [Table['body'][number][number], number];
function getMargin(idx: number, count: number, gapCm: number) {
const marginFirst = cmMarginsToIn(0, 0, gapCm / 2, 0);
const marginInner = cmMarginsToIn(gapCm / 2, 0, gapCm / 2, 0);
const marginLast = cmMarginsToIn(gapCm / 2, 0, 0, 0);
return idx === 0 ? marginFirst : idx === count - 1 ? marginLast : marginInner;
}
function getTableCell(i: Item): Content {
const [item, colSpan] = Array.isArray(i) ? i : [i, 1];
const content = typeof item === 'object' ? item : { text: item };
return { ...content, colSpan };
}
export function createGrid(items: Item[], gapCm: number = 0): Content {
const row = items.map((i, idx) => ({ ...getTableCell(i), margin: getMargin(idx, items.length, gapCm) }));
return {
layout: Layout.wrapper,
table: {
widths: new Array(getColumnsCount(row)).fill('*'),
body: createTableBody([row])
}
};
}

View File

@ -0,0 +1,8 @@
import { Content } from '../../document';
export function createHeader(text: string): Content {
return {
text,
style: { alignment: 'right' }
};
}

View File

@ -0,0 +1,12 @@
import { Content, cmMarginsToIn } from '../../document';
export function createHeadline(text: string): Content {
return {
text,
style: {
alignment: 'center',
bold: true
},
margin: cmMarginsToIn(0, 0.1, 0, 0.1)
};
}

View File

@ -0,0 +1,22 @@
import { FontFamily, Content } from '../../document';
interface SourceIcons {
[name: string]: string;
}
type Icons<T extends SourceIcons> = Record<keyof T, Content>;
export function createIcons<T extends SourceIcons, R = Icons<T>>(iconsObj: T): R {
return Object.entries(iconsObj).reduce(
(acc, [name, text]) => {
acc[name] = {
text,
style: {
font: FontFamily.fa
}
};
return acc;
},
{} as R
);
}

View File

@ -0,0 +1,87 @@
import { Content, Table, cmMarginsToIn } from '../../document';
import { Layout } from '../create-questionary';
import { createTableBody } from './create-table-body';
import { PRIMARY_COLOR } from '../create-questionary';
import { getColumnsCount } from './get-columns-count';
import { createGrid } from './create-grid';
const MARGIN = cmMarginsToIn(0, 0.1);
function prepareBody(body: Table['body'] | string): Table['body'] {
body = typeof body === 'string' ? [[body]] : body;
return body.map(i => i.map(j => j || ''));
}
function setBodyColSpans(body: Table['body'], columnsCount: number): Table['body'] {
return body.map(row => {
const rowColumnsCount = row.reduce((sum, col) => sum + (typeof col === 'object' ? col.colSpan || 1 : 1), 0);
if (rowColumnsCount === columnsCount) {
return row;
}
const lastCol = row[row.length - 1];
const resultLastCol =
typeof lastCol === 'object' ? { ...lastCol, colSpan: lastCol.colSpan || 1 } : { text: lastCol, colSpan: 1 };
resultLastCol.colSpan += columnsCount - rowColumnsCount;
return [...row.slice(0, -1), resultLastCol];
});
}
function getColumnsCountByBody(body: Table['body']): number {
return body.reduce((maxCount, row) => Math.max(maxCount, getColumnsCount(row)), 1);
}
export function createVerticalParagraph(
header: string,
body: Table['body'] | string = [[]],
columnsCount?: number
): Content {
body = prepareBody(body);
if (!columnsCount) {
columnsCount = getColumnsCountByBody(body);
}
body = setBodyColSpans(body, columnsCount);
const headerRow: Table['body'][number] = [
{
colSpan: columnsCount,
style: { color: 'white' },
text: header
}
];
return {
layout: Layout.header,
margin: MARGIN,
table: {
widths: new Array(columnsCount).fill('*'),
body: createTableBody([headerRow, ...body])
}
};
}
export function createInlineParagraph(header: string, body: Table['body'] | string = [[]]): Content {
body = prepareBody(body);
const headerTable: Content = {
layout: Layout.noBorders,
table: {
rowSpan: body.length,
widths: ['*'],
body: [
[
{
text: header,
style: { color: 'white' },
fillColor: PRIMARY_COLOR
}
]
]
}
};
const bodyTable = {
layout: Layout.noBorders,
table: {
rowSpan: body.length,
widths: ['*'],
body
}
};
return { ...createGrid([headerTable, bodyTable]), margin: MARGIN };
}

View File

@ -0,0 +1,17 @@
import { Table } from '../../document';
export function createTableBody(body: Table['body']): Table['body'] {
/**
* Магия таблиц PDFMake (TODO: исправить если что-то изменится)
* В таблице первая колонка с `colSpan` свойством должна иметь после себя `colSpan - 1` пустых колонок
* похоже она использует их для расширения первой колонки
*/
return body.reduce((accBody, row, idx) => {
const [firstColumn, ...otherColumns] = row;
if (typeof firstColumn === 'object' && firstColumn.colSpan && firstColumn.colSpan > 1) {
const firstColumnFiller = new Array(firstColumn.colSpan - 1).fill(null);
accBody[idx] = [firstColumn, ...firstColumnFiller, ...otherColumns];
}
return accBody;
}, body.slice());
}

View File

@ -0,0 +1,5 @@
import { Table } from '../../document';
export function getColumnsCount(row: Table['body'][number]): number {
return row.reduce((accCount, col) => accCount + (typeof col === 'object' && col.colSpan ? col.colSpan : 1), 0);
}

View File

@ -0,0 +1,6 @@
import { createIcons } from './create-icons';
export const icons = createIcons({
checkSquare: '',
square: ''
});

View File

@ -0,0 +1,12 @@
export * from './create-checkbox';
export * from './create-paragraph';
export * from './create-icons';
export * from './create-header';
export * from './create-headline';
export * from './create-ending';
export * from './create-grid';
export * from './create-captioned-text';
export * from './icons';
export * from './text';

View File

@ -0,0 +1,11 @@
export function text(literals: TemplateStringsArray, ...placeholders: (string | number | null | undefined)[]): string {
const resultPlaceholders = placeholders.map(p => {
switch (p) {
case null:
case undefined:
return '';
}
return String(p);
});
return resultPlaceholders.map((p, i) => literals[i] + p).join('') + literals[literals.length - 1];
}

View File

@ -0,0 +1 @@
export const PRIMARY_COLOR = '#203764';

View File

@ -0,0 +1,33 @@
import { Margins, TDocumentHeaderFooterFunction } from 'pdfmake/build/pdfmake';
import { Content } from '../../document';
export function createFooter({
margin,
text,
content
}: {
margin: Margins;
text: string;
content: Content;
}): TDocumentHeaderFooterFunction {
const lineOffsetIn = 5;
const lineSizeIn = 100;
return () => ({
margin: [margin[0], margin[1] + lineOffsetIn, margin[2], margin[3]],
columns: [
[
content,
{
canvas: [
{ type: 'line', x1: 0, y1: -lineOffsetIn, x2: lineSizeIn, y2: -lineOffsetIn, lineWidth: 0.5 }
]
},
{
style: { fontSize: 6 },
text
}
]
]
});
}

View File

@ -0,0 +1,29 @@
import { TableLayoutFunctions, TDocumentDefinitions, PageSize } from 'pdfmake/build/pdfmake';
import { createStyles, createDefaultStyle } from './create-styles';
import { createTableLayouts } from './create-table-layouts';
import { DocDef } from './doc-def';
import { cmMarginsToIn } from '../../document/cm-margins-to-in';
import { createFooter } from './create-footer';
export function createQuestionary(data: DocDef): [TDocumentDefinitions, { [name: string]: TableLayoutFunctions }] {
const leftMarginCm = 3;
const topMarginCm = 2;
const rightMarginCm = 1.5;
const footerMarginCm = 2;
const pageMarginsIn = cmMarginsToIn(leftMarginCm, topMarginCm, rightMarginCm, footerMarginCm + data.footerHeight);
const footerMarginsIn = cmMarginsToIn(leftMarginCm, 0, rightMarginCm, 0);
return [
{
pageSize: 'A4' as PageSize,
pageMargins: pageMarginsIn,
content: data.content,
footer: createFooter({ margin: footerMarginsIn, text: data.footer, content: data.prefooter }),
styles: createStyles(),
defaultStyle: createDefaultStyle()
},
createTableLayouts()
];
}

View File

@ -0,0 +1,17 @@
import { Style as PDFMakeStyle } from 'pdfmake/build/pdfmake';
import { FontFamily } from '../../document';
export enum Style {}
export function createStyles(): { [name in Style]: PDFMakeStyle } {
return {};
}
export function createDefaultStyle(): PDFMakeStyle {
return {
font: FontFamily.serif,
fontSize: 8,
lineHeight: 1
};
}

View File

@ -0,0 +1,47 @@
import { TableLayoutFunctions } from 'pdfmake/build/pdfmake';
import { PRIMARY_COLOR } from './colors';
export enum Layout {
noBorders = 'noBorders',
noPaddings = 'noPaddings',
header = 'header',
wrapper = 'wrapper',
underline = 'underline'
}
const noPaddings: TableLayoutFunctions = {
paddingLeft: () => 0,
paddingRight: () => 0,
paddingTop: () => 0,
paddingBottom: () => 0
};
const noBorders: TableLayoutFunctions = {
hLineWidth: () => 0,
vLineWidth: () => 0
};
export function createTableLayouts(): { [name in Layout]: TableLayoutFunctions } {
return {
[Layout.noBorders]: noBorders,
[Layout.noPaddings]: noPaddings,
[Layout.wrapper]: {
...noBorders,
...noPaddings
},
[Layout.header]: {
fillColor(rowIdx) {
return rowIdx === 0 ? PRIMARY_COLOR : null;
},
...noBorders
},
[Layout.underline]: {
hLineWidth: idx => (idx === 1 ? 0.5 : 0),
vLineWidth: () => 0,
paddingLeft: () => 0,
paddingRight: () => 0,
paddingTop: () => 0
}
};
}

View File

@ -0,0 +1,8 @@
import { Content } from '../../document';
export interface DocDef {
content: (Content | string)[];
prefooter?: Content;
footer?: string;
footerHeight?: number;
}

View File

@ -0,0 +1,4 @@
export * from './create-questionary';
export * from './create-table-layouts';
export * from './doc-def';
export * from './colors';

View File

@ -0,0 +1,11 @@
import { BeneficialOwner } from '../api-codegen/questionary';
import { DocDef } from './create-questionary';
import { getDocDef } from './beneficial-owner';
export function getBeneficialOwnerDocDef(
beneficialOwner: BeneficialOwner,
companyName: string,
companyInn: string
): DocDef {
return getDocDef(beneficialOwner, companyName, companyInn);
}

View File

@ -0,0 +1,15 @@
import { Questionary, BeneficialOwner } from '../api-codegen/questionary';
import { isRussianIndividualEntityQuestionary, isRussianLegalEntityQuestionary } from '../api';
export function getBeneficialOwners(questionary: Questionary): BeneficialOwner[] | null {
let beneficialOwners: BeneficialOwner[] = [];
if (isRussianIndividualEntityQuestionary(questionary)) {
beneficialOwners = questionary.data.contractor.individualEntity.beneficialOwners;
} else if (isRussianLegalEntityQuestionary(questionary)) {
beneficialOwners = questionary.data.contractor.legalEntity.beneficialOwner;
} else {
console.error('Unknown questionary');
return null;
}
return beneficialOwners;
}

View File

@ -0,0 +1,15 @@
import { Questionary } from '../api-codegen/questionary';
import { getDocDef as getRussianIndividualEntityDocDef } from './russian-individual-entity';
import { getDocDef as getRussianLegalEntityDocDef } from './russian-legal-entity';
import { DocDef } from './create-questionary';
import { isRussianLegalEntityQuestionary, isRussianIndividualEntityQuestionary } from '../api';
export function getEntityQuestionaryDocDef(questionary: Questionary): DocDef {
if (isRussianIndividualEntityQuestionary(questionary)) {
return getRussianIndividualEntityDocDef(questionary);
} else if (isRussianLegalEntityQuestionary(questionary)) {
return getRussianLegalEntityDocDef(questionary);
}
console.error('Unknown questionary');
return null;
}

View File

@ -0,0 +1,2 @@
export * from './questionary-document.module';
export * from './questionary-document.service';

View File

@ -0,0 +1,12 @@
import { NgModule } from '@angular/core';
import { CardModule } from '../layout/card';
import { ButtonModule } from '../button';
import { DocumentModule } from '../document/document.module';
import { QuestionaryDocumentService } from './questionary-document.service';
@NgModule({
imports: [CardModule, ButtonModule, DocumentModule],
providers: [QuestionaryDocumentService]
})
export class QuestionaryDocumentModule {}

View File

@ -0,0 +1,42 @@
import { Injectable } from '@angular/core';
import { combineLatest, Observable, of } from 'rxjs';
import { TCreatedPdf } from 'pdfmake/build/pdfmake';
import isEmpty from 'lodash.isempty';
import { DocumentService } from '../document';
import { createQuestionary } from './create-questionary';
import { Questionary, BeneficialOwner } from '../api-codegen/questionary';
import { getEntityQuestionaryDocDef } from './get-entity-questionary-doc-def';
import { getBeneficialOwners } from './get-beneficial-owners';
import { getBeneficialOwnerDocDef } from './get-beneficial-owner-doc-def';
import { getCompanyInfo } from './select-data';
@Injectable()
export class QuestionaryDocumentService {
constructor(private documentService: DocumentService) {}
createDoc(questionary: Questionary): Observable<TCreatedPdf> {
return this.documentService.createPdf(...createQuestionary(getEntityQuestionaryDocDef(questionary)));
}
createBeneficialOwnerDoc(
beneficialOwner: BeneficialOwner,
companyName: string,
companyInn: string
): Observable<TCreatedPdf> {
const docDef = getBeneficialOwnerDocDef(beneficialOwner, companyName, companyInn);
return this.documentService.createPdf(...createQuestionary(docDef));
}
createBeneficialOwnerDocs(questionary: Questionary): Observable<TCreatedPdf[]> {
const beneficialOwners = getBeneficialOwners(questionary);
const { companyName, companyInn } = getCompanyInfo(questionary);
if (!isEmpty(beneficialOwners)) {
const beneficialOwnersDocs = beneficialOwners.map(beneficialOwner =>
this.createBeneficialOwnerDoc(beneficialOwner, companyName, companyInn)
);
return combineLatest(beneficialOwnersDocs);
}
return of([]);
}
}

View File

@ -0,0 +1,222 @@
import isEmpty from 'lodash.isempty';
import { DocDef } from '../create-questionary';
import {
createVerticalCheckboxWithTitle,
createInlineCheckboxWithTitle,
createInlineCheckbox,
createVerticalParagraph,
createHeader,
createHeadline,
createEnding
} from '../create-content';
import { YesNo, getShopLocationURL, getBusinessInfo, toYesNo, simpleYesNo } from '../select-data';
import {
AccountantInfo,
MonthOperationCount,
MonthOperationSum,
PropertyInfoDocumentType
} from '../../api-codegen/questionary';
import { getIndividualEntityName } from './get-individual-entity-name';
import { RussianIndividualEntityQuestionary } from '../../api';
import { toOptional } from '../../../utils';
const DocumentType = PropertyInfoDocumentType.DocumentTypeEnum;
const AccountantInfoType = AccountantInfo.AccountantInfoTypeEnum;
const EMPTY = '';
export function getDocDef(questionary: RussianIndividualEntityQuestionary): DocDef {
const { data } = toOptional(questionary);
const { contractor, shopInfo, contactInfo } = toOptional(data);
const { individualEntity } = toOptional(contractor);
const { location, details } = toOptional(shopInfo);
const { name: brandName } = toOptional(details);
const { phoneNumber, email } = toOptional(contactInfo);
const {
additionalInfo,
russianPrivateEntity,
registrationInfo,
inn,
snils,
propertyInfoDocumentType,
individualPersonCategories,
beneficialOwners,
residencyInfo
} = toOptional(individualEntity);
const {
nkoRelationTarget,
relationshipWithNko,
monthOperationSum,
monthOperationCount,
benefitThirdParties,
relationIndividualEntity
} = toOptional(additionalInfo);
const { fio } = toOptional(russianPrivateEntity);
const { registrationPlace } = toOptional(registrationInfo);
const { documentType } = toOptional(propertyInfoDocumentType);
const { foreignPublicPerson, foreignRelativePerson } = toOptional(individualPersonCategories);
const { usaTaxResident } = toOptional(residencyInfo);
const name = getIndividualEntityName(fio);
const url = getShopLocationURL(location);
const { hasChiefAccountant, staffCount, accountingOrgInn, accounting } = getBusinessInfo(additionalInfo);
const pdlRelationDegree = EMPTY; // TODO
const hasBeneficialOwner = !isEmpty(beneficialOwners);
return {
content: [
createHeader('Приложение №'),
createHeadline(
'ОПРОСНЫЙ ЛИСТ ИНДИВИДУАЛЬНОГО ПРЕДПРИНИМАТЕЛЯ ИЛИ ФИЗИЧЕСКОГО ЛИЦА, ЗАНИМАЮЩЕГОСЯ В УСТАНОВЛЕННОМ ЗАКОНОДАТЕЛЬСТВОМ РФ ПОРЯДКЕ ЧАСТНОЙ ПРАКТИКОЙ'
),
createVerticalParagraph('1. Основные сведения о Клиенте', [
[`1.1. Наименование: ${name || EMPTY}`, `1.2. ИНН: ${inn || EMPTY}`],
[`1.3. Фирменное наименование: ${brandName || EMPTY}`, `1.4. СНИЛС №: ${snils || EMPTY}`]
]),
createVerticalParagraph('2. Контактная информация', [
[
`2.1. Телефон: ${phoneNumber || EMPTY}`,
`2.2. Сайт (Url): ${url || EMPTY}`,
`2.3. Email: ${email || EMPTY}`
]
]),
createVerticalParagraph(
'3. Сведения о целях установления и предполагаемом характере деловых отношений с НКО',
[
[
`3.1. Цели установления отношений: ${nkoRelationTarget || 'подключение интернет-эквайринга'}`,
`3.2. Характер отношений: ${relationshipWithNko || 'долгосрочный'}`
]
]
),
createVerticalParagraph('4. Планируемые операции по счету, в месяц', [
[
createVerticalCheckboxWithTitle(
'4.1. Количество операций:',
[
[MonthOperationCount.LtTen, 'до 10'],
[MonthOperationCount.BtwTenToFifty, '10 - 50'],
[MonthOperationCount.GtFifty, 'свыше 50']
],
monthOperationCount
),
createVerticalCheckboxWithTitle(
'4.2. Сумма операций:',
[
[MonthOperationSum.LtFiveHundredThousand, 'до 500 000'],
[MonthOperationSum.BtwFiveHundredThousandToOneMillion, '500 000 - 1 000 000'],
[MonthOperationSum.GtOneMillion, 'свыше 1 000 000']
],
monthOperationSum
)
]
]),
createVerticalParagraph(
'5. Адрес фактического осуществления (ведения) деятельности',
registrationPlace || EMPTY
),
createVerticalParagraph('6. Тип документа, подтверждающий право нахождения по фактическому адресу', [
[
createInlineCheckbox(
[
[DocumentType.LeaseContract, 'Договор аренды'],
[DocumentType.SubleaseContract, 'Договор субаренды'],
[DocumentType.CertificateOfOwnership, 'Свидетельство о праве собственности']
],
documentType
)
]
]),
createVerticalParagraph('7. Сведения о хозяйственной деятельности', [
[
createInlineCheckboxWithTitle(
'7.1. Наличие в штате главного бухгалтера',
simpleYesNo,
hasChiefAccountant
),
`7.2. Штатная численность в организации: ${staffCount || EMPTY}`
],
[
{
...createVerticalCheckboxWithTitle(
'7.3. Бухгалтерский учет осуществляет:',
[
[AccountantInfoType.WithoutChiefHeadAccounting, 'ИП лично'],
[
AccountantInfoType.WithoutChiefAccountingOrganization,
`Организация ведущая бух. учет: ИНН: ${accountingOrgInn || EMPTY}`
],
[
AccountantInfoType.WithoutChiefIndividualAccountant,
'Бухгалтер индивидуальный специалист'
]
],
accounting
),
colSpan: 2
}
]
]),
createVerticalParagraph('8. Принадлежность физического лица к некоторым категория граждан', [
[
{
...createInlineCheckboxWithTitle(
'8.1. Принадлежность к категории ПДЛ¹',
simpleYesNo,
toYesNo(foreignPublicPerson)
),
colSpan: 2
}
],
[
createInlineCheckboxWithTitle(
'8.2. Является родственником ПДЛ',
simpleYesNo,
toYesNo(foreignRelativePerson)
),
`8.3. Степень родства: ${pdlRelationDegree || EMPTY}`
]
]),
createVerticalParagraph('9. Наличие выгодоприобретателя²', [
[
createInlineCheckbox(
[
[YesNo.no, 'Нет'],
[YesNo.yes, 'Да (обязательное заполнение анкеты Выгодоприобретателя по форме НКО)']
],
toYesNo(benefitThirdParties)
)
]
]),
createVerticalParagraph('10. Наличие бенефициарного владельца³', [
[
createInlineCheckbox(
[
[YesNo.no, 'Нет'],
[
YesNo.yes,
'Да (обязательное заполнение приложение для Бенефициарного владельца по форме НКО)'
]
],
toYesNo(hasBeneficialOwner)
)
]
]),
createVerticalParagraph(
'11. Имеются ли решения о ликвидации или о любой процедуре, применяемой в деле о банкротстве',
[[createInlineCheckbox(simpleYesNo, toYesNo(!!relationIndividualEntity))]]
),
createVerticalParagraph('12. Являетесь ли Вы налоговым резидентом США или иного иностранного государства', [
[createInlineCheckbox(simpleYesNo, toYesNo(usaTaxResident))]
])
],
prefooter: createEnding(),
footer: [
'¹ Публичные должностные лица, включая российские, иностранные и международные.',
'² Выгодоприобретатель - лицо, к выгоде которого действует клиент, в том числе на основании агентского договора, договоров поручения, комиссии и доверительного управления, при проведении операций с денежными средствами и иным имуществом.',
'³ Бенефициарный владелец - физическое лицо, которое в конечном счете прямо или косвенно (через третьих лиц) владеет (имеет преобладающее участие более 25 процентов в капитале) клиентом - юридическим лицом либо имеет возможность контролировать действия клиента. Бенефициарным владельцем клиента - физического лица считается это лицо, за исключением случаев, если имеются основания полагать, что бенефициарным владельцем является иное физическое лицо.'
].join('\n'),
footerHeight: 3.6
};
}

View File

@ -0,0 +1,3 @@
export function getIndividualEntityName(fio: string) {
return `ИП ${fio}`;
}

View File

@ -0,0 +1 @@
export * from './get-doc-def';

View File

@ -0,0 +1,21 @@
import { AuthorityConfirmingDocument } from '../../api-codegen/questionary';
import { AuthorityConfirmingDocumentType } from '../../api/questionary/model';
import { toOptional } from '../../../utils';
import { getDate } from '../select-data';
const mapAuthorityConfirmingDocumentType: { [name in AuthorityConfirmingDocumentType]: string } = {
solePartyDecision: 'Решение единственного участника',
meetingOfShareholders: 'Протокол общего собрания участников',
meetingOfParticipants: 'Протокол общего собрания акционеров'
};
export function getAuthorityConfirmingDocument(authorityConfirmingDocument: AuthorityConfirmingDocument): string {
const { type, number, date } = toOptional(authorityConfirmingDocument);
if (type || number || date) {
const printedType = mapAuthorityConfirmingDocumentType[type] || type;
const printedNumber = number ? `${number}` : null;
const printedDate = date ? `от ${getDate(date)}` : null;
return [printedType, printedNumber, printedDate].filter(i => i).join(' ');
}
return null;
}

View File

@ -0,0 +1,252 @@
import isEmpty from 'lodash.isempty';
import {
createInlineCheckboxWithTitle,
createVerticalParagraph,
createHeader,
createHeadline,
createEnding
} from '../create-content';
import { createInlineCheckbox, createVerticalCheckboxWithTitle } from '../create-content';
import { YesNo, getShopLocationURL, getContactInfo, getBusinessInfo, toYesNo, simpleYesNo } from '../select-data';
import { DocDef } from '../create-questionary';
import {
AccountantInfo,
MonthOperationCount,
MonthOperationSum,
PropertyInfoDocumentType
} from '../../api-codegen/questionary';
import { RussianLegalEntityQuestionary } from '../../api';
import { getAuthorityConfirmingDocument } from './get-authority-confirming-document';
import { toOptional } from '../../../utils';
const DocumentType = PropertyInfoDocumentType.DocumentTypeEnum;
const AccountantInfoType = AccountantInfo.AccountantInfoTypeEnum;
const EMPTY = '';
export function getDocDef(questionary: RussianLegalEntityQuestionary): DocDef {
const { data } = toOptional(questionary);
const { contractor, shopInfo, contactInfo } = toOptional(data);
const { location, details } = toOptional(shopInfo);
const { name } = toOptional(details);
const { phoneNumber, email } = toOptional(contactInfo);
const { legalEntity } = toOptional(contractor);
const {
additionalInfo,
inn,
name: brandName,
legalOwnerInfo,
beneficialOwner,
propertyInfoDocumentType,
registrationInfo,
residencyInfo
} = toOptional(legalEntity);
const { registrationPlace } = toOptional(registrationInfo);
const { taxResident, fatca, ownerResident } = toOptional(residencyInfo);
const {
relationIndividualEntity,
benefitThirdParties,
nkoRelationTarget,
relationshipWithNko,
monthOperationSum,
monthOperationCount
} = toOptional(additionalInfo);
const { pdlRelationDegree, pdlCategory, snils, authorityConfirmingDocument, russianPrivateEntity } = toOptional(
legalOwnerInfo
);
const { fio, contactInfo: privateEntityContactInfo } = toOptional(russianPrivateEntity);
const { documentType } = toOptional(propertyInfoDocumentType);
const url = getShopLocationURL(location);
const contact = getContactInfo(privateEntityContactInfo);
const authorityConfirmingDocumentInfo = getAuthorityConfirmingDocument(authorityConfirmingDocument);
const { hasChiefAccountant, staffCount, accounting, accountingOrgInn } = getBusinessInfo(additionalInfo);
const hasBeneficialOwner = !isEmpty(beneficialOwner);
return {
content: [
createHeader('Приложение №'),
createHeadline('ОПРОСНЫЙ ЛИСТ ЮРИДИЧЕСКОГО ЛИЦА (НЕ ЯВЛЯЮЩЕГОСЯ КРЕДИТНОЙ ОРГАНИЗАЦИЕЙ)'),
createVerticalParagraph('1. Основные сведения о Клиенте', [
[`1.1. Наименование: ${name || EMPTY}`, `1.2. ИНН: ${inn || EMPTY}`],
[{ text: `1.3. Фирменное наименование: ${brandName || EMPTY}`, colSpan: 2 }]
]),
createVerticalParagraph('2. Контактная информация', [
[
`2.1. Телефон: ${phoneNumber || EMPTY}`,
`2.2. Сайт (Url): ${url || EMPTY}`,
`2.3. Email: ${email || EMPTY}`
]
]),
createVerticalParagraph(
'3. Сведения о целях установления и предполагаемом характере деловых отношений с НКО',
[
[
`3.1. Цели установления отношений: ${nkoRelationTarget || 'подключение интернет-эквайринга'}`,
`3.2. Характер отношений: ${relationshipWithNko || 'долгосрочный'}`
]
]
),
createVerticalParagraph('4. Планируемые операции, в месяц', [
[
createVerticalCheckboxWithTitle(
'4.1. Количество операций:',
[
[MonthOperationCount.LtTen, 'до 10'],
[MonthOperationCount.BtwTenToFifty, '10 - 50'],
[MonthOperationCount.GtFifty, 'свыше 50']
],
monthOperationCount
),
createVerticalCheckboxWithTitle(
'4.2. Сумма операций:',
[
[MonthOperationSum.LtFiveHundredThousand, 'до 500 000'],
[MonthOperationSum.BtwFiveHundredThousandToOneMillion, '500 000 - 1 000 000'],
[MonthOperationSum.GtOneMillion, 'свыше 1 000 000']
],
monthOperationSum
)
]
]),
createVerticalParagraph('5. Сведения о единоличном исполнительном органе юридического лица', [
[{ text: `5.1. ФИО Единоличного исполнительного органа: ${fio || EMPTY}`, colSpan: 2 }],
[
{
text: `5.2. Действует на основании: ${authorityConfirmingDocumentInfo || EMPTY}`,
colSpan: 2
}
],
[`5.3. СНИЛС №: ${snils || EMPTY}`, `5.4. Контактная информация (телефон, email): ${contact || EMPTY}`]
]),
createVerticalParagraph(
'6. Данные о фактическом местонахождении органа управления (Руководителя)',
registrationPlace || EMPTY
),
createVerticalParagraph(
'7. Тип документа, подтверждающий право нахождения по фактическому адресу органа управления (Руководителя)',
[
[
createInlineCheckbox(
[
[DocumentType.LeaseContract, 'Договор аренды'],
[DocumentType.SubleaseContract, 'Договор субаренды'],
[DocumentType.CertificateOfOwnership, 'Свидетельство о праве собственности']
],
documentType
)
]
]
),
createVerticalParagraph('8. Сведения о хозяйственной деятельности организации', [
[
createInlineCheckboxWithTitle(
'8.1. Наличие в штате главного бухгалтера',
simpleYesNo,
hasChiefAccountant
),
`8.2. Штатная численность в организации: ${staffCount || EMPTY}`
],
[
createVerticalCheckboxWithTitle(
'8.3. Бухгалтерский учет осуществляет:',
[
[AccountantInfoType.WithoutChiefHeadAccounting, 'Руководитель организации'],
[
AccountantInfoType.WithoutChiefAccountingOrganization,
`Организация ведущая бух. учет: ИНН: ${accountingOrgInn || EMPTY}`
],
[
AccountantInfoType.WithoutChiefIndividualAccountant,
'Бухгалтер индивидуальный специалист'
]
],
accounting
)
]
]),
createVerticalParagraph(
'9. Принадлежность Единоличного исполнительного органа к некоторым категориям граждан',
[
[
createInlineCheckboxWithTitle(
'9.1. Принадлежность к категории ПДЛ¹',
simpleYesNo,
toYesNo(pdlCategory)
)
],
[
createInlineCheckboxWithTitle(
'9.2. Является родственником ПДЛ',
simpleYesNo,
toYesNo(!!pdlRelationDegree)
),
`9.3. Степень родства: ${pdlRelationDegree || EMPTY}`
]
]
),
createVerticalParagraph('10. Наличие выгодоприобретателя²', [
[
[
createInlineCheckbox(
[
[YesNo.no, 'Нет'],
[YesNo.yes, 'Да (обязательное заполнение анкеты Выгодоприобретателя по форме НКО)']
],
toYesNo(benefitThirdParties)
)
]
]
]),
createVerticalParagraph('11. Наличие бенефициарного владельца³', [
[
createInlineCheckbox(
[
[YesNo.no, 'Нет'],
[YesNo.yes, 'Да (обязательное заполнение приложение для Бенефициарного владельца)']
],
toYesNo(hasBeneficialOwner)
)
]
]),
createVerticalParagraph(
'12. Имеются ли решения о ликвидации или о любой процедуре, применяемой в деле о банкротстве, в отношении Вашей компании',
[[createInlineCheckbox(simpleYesNo, toYesNo(!!relationIndividualEntity))]]
),
createVerticalParagraph('13. Информация об иностранном налоговом резидентстве', [
[
{
text:
'13.1. Является ли Ваша организация налоговым резидентом США или иного иностранного государства?',
colSpan: 5
},
createInlineCheckbox(simpleYesNo, toYesNo(taxResident))
],
[
{
text:
'13.2. Является ли Бенефициарный владелец Вашей организации с долей владения 10% и более налоговым резидентом иностранного государства?',
colSpan: 5
},
createInlineCheckbox(simpleYesNo, toYesNo(ownerResident))
],
[
{
text:
'13.3. Является ли Ваша организация Финансовым Институтом в соответствии с FATCA и 173-ФЗ от 28.06.2014?',
colSpan: 5
},
createInlineCheckbox(simpleYesNo, toYesNo(fatca))
]
])
],
prefooter: createEnding(),
footer: [
'¹ Публичные должностные лица, включая российские, иностранные и международные.',
'² Выгодоприобретатель - лицо, к выгоде которого действует клиент, в том числе на основании агентского договора, договоров поручения, комиссии и доверительного управления, при проведении операций с денежными средствами и иным имуществом.',
'³ Бенефициарный владелец - физическое лицо, которое в конечном счете прямо или косвенно (через третьих лиц) владеет (имеет преобладающее участие более 25 процентов в капитале) клиентом - юридическим лицом либо имеет возможность контролировать действия клиента. Бенефициарным владельцем клиента - физического лица считается это лицо, за исключением случаев, если имеются основания полагать, что бенефициарным владельцем является иное физическое лицо.'
].join('\n'),
footerHeight: 3.2
};
}

View File

@ -0,0 +1 @@
export * from './get-doc-def';

View File

@ -0,0 +1,30 @@
import { hasChiefAccountant } from './has-chief-accountant';
import { AdditionalInfo, AccountantInfo, WithoutChiefAccountingOrganization } from '../../api-codegen/questionary';
import { YesNo } from './yes-no';
import { toOptional } from '../../../utils';
export interface BusinessInfo {
hasChiefAccountant: YesNo;
staffCount: number;
accounting: AccountantInfo.AccountantInfoTypeEnum;
accountingOrgInn?: string;
}
function isWithoutChiefAccountingOrganization(
accountantInfo: AccountantInfo
): accountantInfo is WithoutChiefAccountingOrganization {
return (
accountantInfo &&
accountantInfo.accountantInfoType === AccountantInfo.AccountantInfoTypeEnum.WithoutChiefAccountingOrganization
);
}
export function getBusinessInfo(additionalInfo: AdditionalInfo): BusinessInfo {
const { accountantInfo, staffCount } = toOptional(additionalInfo);
return {
hasChiefAccountant: hasChiefAccountant(accountantInfo),
staffCount,
accounting: toOptional(accountantInfo).accountantInfoType,
accountingOrgInn: isWithoutChiefAccountingOrganization(accountantInfo) ? toOptional(accountantInfo).inn : null
};
}

View File

@ -0,0 +1,20 @@
import get from 'lodash.get';
import { Questionary } from '../../api-codegen/questionary';
import { isRussianLegalEntityQuestionary, isRussianIndividualEntityQuestionary } from '../../api';
export function getCompanyInfo(questionary: Questionary): { companyName: string; companyInn: string } {
if (isRussianIndividualEntityQuestionary(questionary)) {
return {
companyName: get(questionary.data, ['shopInfo', 'details', 'name'], ''),
companyInn: questionary.data.contractor.individualEntity.inn
};
} else if (isRussianLegalEntityQuestionary(questionary)) {
return {
companyName: get(questionary.data, ['shopInfo', 'details', 'name'], ''),
companyInn: questionary.data.contractor.legalEntity.inn
};
}
console.error('Unknown questionary');
return null;
}

View File

@ -0,0 +1,7 @@
import { ContactInfo } from '../../api-codegen/questionary';
import { toOptional } from '../../../utils';
export function getContactInfo(contactInfo: ContactInfo): string {
const { phoneNumber, email } = toOptional(contactInfo);
return [phoneNumber, email].filter(i => i).join(', ');
}

View File

@ -0,0 +1,9 @@
import moment from 'moment';
export function getDate(date: string): string {
return date
? moment(date)
.utc()
.format('L')
: null;
}

View File

@ -0,0 +1,21 @@
import { RussianDomesticPassport, IdentityDocument } from '../../api-codegen/questionary';
function isRussianDomesticPassport(identityDocument: IdentityDocument): identityDocument is RussianDomesticPassport {
return identityDocument && identityDocument.identityDocumentType === 'RussianDomesticPassport';
}
export function getIdentityDocument(
identityDocument: IdentityDocument
): { name?: string; seriesNumber?: string; issuer?: string; issuedAt?: string } | null {
if (isRussianDomesticPassport(identityDocument)) {
const { seriesNumber, issuer, issuedAt, issuerCode } = identityDocument;
return {
name: 'Паспорт РФ',
seriesNumber,
issuer: [issuer, issuerCode].filter(i => !!i).join(', '),
issuedAt
};
}
console.error('Unknown identity document');
return {};
}

View File

@ -0,0 +1,5 @@
import isEmpty from 'lodash.isempty';
export function getPercent(value: string | number): string {
return isEmpty(value) ? null : `${value}%`;
}

View File

@ -0,0 +1,12 @@
import { ShopLocation, ShopLocationUrl } from '../../api-codegen/questionary';
function isShopLocationUrl(shopLocation: ShopLocation): shopLocation is ShopLocationUrl {
return shopLocation && shopLocation.locationType === ShopLocation.LocationTypeEnum.ShopLocationUrl;
}
export function getShopLocationURL(shopLocation: ShopLocation): string {
if (isShopLocationUrl(shopLocation)) {
return shopLocation.url;
}
return null;
}

View File

@ -0,0 +1,9 @@
import { AccountantInfo } from '../../api-codegen/questionary';
import { YesNo, toYesNo } from './yes-no';
export function hasChiefAccountant(accountantInfo: AccountantInfo): YesNo {
return toYesNo(
accountantInfo &&
accountantInfo.accountantInfoType === AccountantInfo.AccountantInfoTypeEnum.WithChiefAccountant
);
}

View File

@ -0,0 +1,10 @@
export * from './yes-no';
export * from './has-chief-accountant';
export * from './get-shop-location-url';
export * from './get-contact-info';
export * from './get-business-info';
export * from './get-identity-document';
export * from './get-company-info';
export * from './get-date';
export * from './get-percent';
export * from './simple-yes-no';

View File

@ -0,0 +1,3 @@
import { YesNo } from './yes-no';
export const simpleYesNo: [YesNo, string][] = [[YesNo.yes, 'Да'], [YesNo.no, 'Нет']];

View File

@ -0,0 +1,19 @@
import { toYesNo, YesNo } from './yes-no';
describe('toYesNo', () => {
it('null should return -1', () => {
expect(toYesNo(null)).toBe(-1);
});
it('undefined should return -1', () => {
expect(toYesNo(undefined)).toBe(-1);
});
it('true should return yes', () => {
expect(toYesNo(true)).toBe(YesNo.yes);
});
it('false should return no', () => {
expect(toYesNo(false)).toBe(YesNo.no);
});
});

View File

@ -0,0 +1,11 @@
export enum YesNo {
yes,
no
}
export function toYesNo(value: boolean): YesNo {
if (value === null || value === undefined) {
return -1;
}
return value ? YesNo.yes : YesNo.no;
}

View File

@ -15,5 +15,20 @@
*ngIf="isNotEmptyBankAccount$ | async"
[bankAccount]="bankAccount$ | async"
></dsh-bank-account-info>
<div
fxLayout="row wrap"
fxLayoutGap="10px"
style="float: left"
*transloco="let c; scope: 'claim-modification-containers'; read: 'claimModificationContainers.download'"
>
<button dsh-button (click)="downloadDocument()">{{ c.questionaryDocument }}</button>
<button
dsh-button
*ngFor="let beneficialOwnerDocument of beneficialOwnersDocuments$ | async; index as i"
(click)="downloadBeneficialOwnerDocument(beneficialOwnerDocument)"
>
{{ c.beneficialOwnerDocument | translocoParams: { id: i + 1 } }}
</button>
</div>
</div>
</div>

View File

@ -2,6 +2,7 @@ import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { pluck, map } from 'rxjs/operators';
import isEmpty from 'lodash.isempty';
import negate from 'lodash.negate';
import { TCreatedPdf } from 'pdfmake/build/pdfmake';
import { DocumentModificationUnit } from '../../../../api-codegen/claim-management';
import { DocumentModificationInfoService } from './document-modification-info.service';
@ -14,7 +15,7 @@ import { DocumentModificationInfoService } from './document-modification-info.se
export class DocumentModificationInfoComponent implements OnChanges {
@Input() unit: DocumentModificationUnit;
questionary$ = this.documentModificationInfoService.questionary$;
questionary$ = this.documentModificationInfoService.questionaryData$;
isLoading$ = this.documentModificationInfoService.isLoading$;
error$ = this.documentModificationInfoService.error$;
contractor$ = this.questionary$.pipe(pluck('contractor'));
@ -26,6 +27,8 @@ export class DocumentModificationInfoComponent implements OnChanges {
bankAccount$ = this.questionary$.pipe(pluck('bankAccount'));
isNotEmptyBankAccount$ = this.bankAccount$.pipe(map(negate(isEmpty)));
beneficialOwnersDocuments$ = this.documentModificationInfoService.beneficialOwnersDocuments$;
constructor(private documentModificationInfoService: DocumentModificationInfoService) {}
ngOnChanges({ unit }: SimpleChanges): void {
@ -33,4 +36,12 @@ export class DocumentModificationInfoComponent implements OnChanges {
this.documentModificationInfoService.receiveQuestionary(unit.currentValue.documentId);
}
}
downloadDocument() {
this.documentModificationInfoService.document$.subscribe(doc => doc.download('russian-entity-questionary'));
}
downloadBeneficialOwnerDocument(beneficialOwnerDocument: TCreatedPdf) {
beneficialOwnerDocument.download('beneficial-owner-questionary');
}
}

View File

@ -20,9 +20,20 @@ import {
AuthorityConfirmingDocumentComponent
} from './contractor-info';
import { QuestionaryModule } from '../../../../api';
import { QuestionaryDocumentModule } from '../../../../questionary-document';
import { ButtonModule } from '../../../../button';
@NgModule({
imports: [CommonModule, FlexLayoutModule, TranslocoModule, QuestionaryModule, DetailsItemModule, MatDividerModule],
imports: [
CommonModule,
FlexLayoutModule,
TranslocoModule,
QuestionaryModule,
DetailsItemModule,
MatDividerModule,
QuestionaryDocumentModule,
ButtonModule
],
declarations: [
DocumentModificationInfoComponent,
ShopInfoComponent,

View File

@ -3,20 +3,33 @@ import { Observable, Subject } from 'rxjs';
import { switchMap, pluck, shareReplay } from 'rxjs/operators';
import { QuestionaryService } from '../../../../api';
import { QuestionaryData } from '../../../../api-codegen/questionary';
import { QuestionaryData, Questionary } from '../../../../api-codegen/questionary';
import { booleanDelay, takeError } from '../../../../custom-operators';
import { QuestionaryDocumentService } from '../../../../questionary-document';
@Injectable()
export class DocumentModificationInfoService {
private receiveQuestionary$: Subject<string> = new Subject();
questionary$: Observable<QuestionaryData> = this.receiveQuestionary$.pipe(
questionary$: Observable<Questionary> = this.receiveQuestionary$.pipe(
switchMap(documentId => this.questionaryService.getQuestionary(documentId)),
pluck('questionary', 'data'),
pluck('questionary'),
shareReplay(1)
);
isLoading$ = this.questionary$.pipe(
questionaryData$: Observable<QuestionaryData> = this.questionary$.pipe(pluck('data'));
document$ = this.questionary$.pipe(
switchMap(questionary => this.questionaryDocumentService.createDoc(questionary)),
shareReplay(1)
);
beneficialOwnersDocuments$ = this.questionary$.pipe(
switchMap(questionary => this.questionaryDocumentService.createBeneficialOwnerDocs(questionary)),
shareReplay(1)
);
isLoading$ = this.questionaryData$.pipe(
booleanDelay(),
shareReplay(1)
);
@ -26,8 +39,11 @@ export class DocumentModificationInfoService {
shareReplay(1)
);
constructor(private questionaryService: QuestionaryService) {
this.questionary$.subscribe();
constructor(
private questionaryService: QuestionaryService,
private questionaryDocumentService: QuestionaryDocumentService
) {
this.questionaryData$.subscribe();
}
receiveQuestionary(documentId: string) {

View File

@ -10,7 +10,7 @@ import { ButtonModule } from '../../button';
import { DshTabsModule } from '../../layout/tabs';
import { ClaimRoutingModule } from './claim-routing.module';
import { ClaimComponent } from './claim.component';
import { ClaimsModule } from '../../api/claims';
import { ClaimsModule, QuestionaryModule } from '../../api';
import { ConversationModule } from './conversation';
import { StatusModule } from '../../status';
import { SpinnerModule } from '../../spinner';
@ -32,6 +32,7 @@ import { ConfirmActionDialogModule } from '../../confirm-action-dialog';
DocumentsModule,
TranslocoModule,
SpinnerModule,
QuestionaryModule,
ReactiveFormsModule,
MatDialogModule,
MatInputModule,

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More