registration with programs and agreements

This commit is contained in:
Alex Ant
2026-01-22 13:41:02 +05:00
parent 7135a3bbeb
commit c11e922573
75 changed files with 1373 additions and 282 deletions
@@ -1,6 +1,6 @@
---
alwaysApply: false
---
Смотри, у меня есть проект документа оферты, @cooptypes/src/cooperative/registry/999.BlagorostOfferTemplate/index.ts мы его принимаем затем, чтобы получить данные для генерации @cooptypes/src/cooperative/registry/1000.CapitalizationAgreement/index.ts уже самой пользовательской оферты. Они в целом почти абсолютно идентичны за той лишь разницей, что в проекте кругом прочерки остаются вместо переменных для заполнения, и нет шапки с надписью "УТВЕРЖДЕНО" протоколом решения № ___ и дата ___. Т.е. мы как бы из первого в итоге получаем второе. Приняв первое - мы можем начать генерировать второе.
Смотри, у меня есть проект документа оферты, @cooptypes/src/cooperative/registry/999.BlagorostOfferTemplate/index.ts мы его принимаем затем, чтобы получить данные для генерации @cooptypes/src/cooperative/registry/1000.BlagorostOffer/index.ts уже самой пользовательской оферты. Они в целом почти абсолютно идентичны за той лишь разницей, что в проекте кругом прочерки остаются вместо переменных для заполнения, и нет шапки с надписью "УТВЕРЖДЕНО" протоколом решения № ___ и дата ___. Т.е. мы как бы из первого в итоге получаем второе. Приняв первое - мы можем начать генерировать второе.
Мне надо, чтобы ты сделал аналогично для указанного мной документа. Для этого нужно переменные по аналогии заполнить пробелами и убрать шапку. Т.е. в итоге мы должны получить шаблон документа, который можно распечатать и заполнить ручкой. Вот такой вот документ мне надо чтоб ты создал. Он должен обладать принадлежностью к конкретному кооперативу, т.е. vars и chairman мы используем, как и coop. Всё остальное по сути нужно заменить на прочерки. Создай этот шаблон документа, исследовав что нужно тебе для этого.
+205
View File
@@ -0,0 +1,205 @@
# 🔄 Динамический поток регистрации
Система регистрации полностью управляется через конфигурацию без хардкода в бизнес-логике.
## 📋 Источники конфигурации
### 1. Конфигурация соглашений
**Файл**: `src/domain/registration/config/registration-agreements.config.ts`
Определяет все доступные соглашения:
```typescript
{
id: 'generator_offer',
registry_id: 996,
agreement_type: 'generator', // ← Используется при отправке в блокчейн
title: 'Оферта по программе "Генератор"',
is_blockchain_agreement: true,
link_to_statement: true,
applicable_account_types: [],
order: 6,
}
```
### 2. Конфигурация программ
**Файл**: `src/domain/registration/config/registration-programs.config.ts`
Определяет доступные программы регистрации:
```typescript
{
key: 'generation',
title: 'Программа Генерация',
description: '...',
applicable_account_types: [AccountType.individual, AccountType.entrepreneur],
agreement_ids: ['generator_offer'], // ← Связь с соглашениями
order: 1,
}
```
## 🔄 Как это работает
### 1️⃣ Генерация документов
**Сервис**: `RegistrationDocumentsService.generateRegistrationDocuments()`
```typescript
// Получаем соглашения из конфигурации
const agreementsConfig = this.agreementConfigService.getAgreementsForAccountType(
account_type,
coopname,
program_key // ← Ключ выбранной программы
);
// Генерируем документы для каждого соглашения
for (const config of agreementsConfig) {
const document = await generateDocument({
registry_id: config.registry_id,
...
});
}
```
### 2️⃣ Валидация при регистрации
**Интерактор**: `ParticipantInteractor.registerParticipant()`
```typescript
// ПРОВЕРКА 1: Получаем требуемые соглашения из конфигурации
const requiredAgreements = this.agreementConfigService.getRequiredAgreementIds(
candidate.type,
config.coopname,
data.program_key // ← Передаем ключ программы
);
// ПРОВЕРКА 2: Валидируем все требуемые документы динамически
const documentsToValidate = [{ id: 'statement', document: data.statement }];
for (const agreementId of requiredAgreements) {
if (data[agreementId]) {
documentsToValidate.push({ id: agreementId, document: data[agreementId] });
} else {
missingAgreements.push(agreementId);
}
}
// ПРОВЕРКА 3: Проверяем линкованные документы из конфигурации
const linkedAgreements = this.agreementConfigService.getLinkedAgreements(
candidate.type,
config.coopname,
data.program_key // ← Передаем ключ программы
);
// СОХРАНЕНИЕ: Сохраняем все требуемые соглашения
for (const agreementId of requiredAgreements) {
if (data[agreementId]) {
await this.candidateRepository.saveDocument(username, agreementId, data[agreementId]);
}
}
```
### 3️⃣ Отправка в блокчейн
**Адаптер**: `AccountBlockchainAdapter.registerBlockchainAccount()`
```typescript
// Получаем соглашения для блокчейна из конфигурации
const blockchainAgreements = this.agreementConfigService.getBlockchainAgreements(
accountType,
config.coopname,
candidate.program_key // ← Используем сохраненный ключ программы
);
// Динамически добавляем транзакции sendAgreement
for (const agreementConfig of blockchainAgreements) {
const document = candidate.documents[agreementConfig.id];
if (document) {
const action = this.createSendAgreementAction(
candidate.username,
agreementConfig.agreement_type, // ← Берем из конфигурации!
document
);
actions.push(action);
}
}
```
## ✨ Преимущества
### ✅ Никакого хардкода
```typescript
// ❌ ПЛОХО - хардкод
if (data.program_key === 'generation' && data.generator_offer) {
documentsToValidate.push({ id: 'generator_offer', document: data.generator_offer });
}
// ✅ ХОРОШО - динамика из конфигурации
for (const agreementId of requiredAgreements) {
if (data[agreementId]) {
documentsToValidate.push({ id: agreementId, document: data[agreementId] });
}
}
```
### ✅ Легко добавить новую программу
1. Добавляем соглашение в `registration-agreements.config.ts`:
```typescript
{
id: 'new_program_offer',
agreement_type: 'new_type',
registry_id: 1001,
...
}
```
2. Добавляем программу в `registration-programs.config.ts`:
```typescript
{
key: 'new_program',
title: 'Новая программа',
agreement_ids: ['new_program_offer'],
...
}
```
3. **Всё!** Код работает без изменений 🎉
### ✅ Единый источник истины
Конфигурация определяет:
- Какие документы генерировать
- Какие документы валидировать
- Какие документы линковать в заявление
- Какие документы сохранять
- Какие транзакции отправлять в блокчейн
- Какой `agreement_type` использовать
## 🔍 Полный цикл для программы "Генерация"
```
1. Frontend → generateRegistrationDocuments({ program_key: 'generation' })
2. Backend определяет: generation → ['generator_offer']
3. Генерируется документ с registry_id=996
4. Frontend подписывает документы
5. Frontend → registerParticipant({ program_key: 'generation', generator_offer: {...} })
6. Backend валидирует все требуемые документы (динамически)
7. Backend сохраняет program_key и все документы
8. Payment → registerBlockchainAccount(candidate)
9. Backend получает соглашения для блокчейна на основе candidate.program_key
10. Backend отправляет sendAgreement с agreement_type='generator'
```
## 📝 Итог
**Вся система управляется через конфигурацию!**
Добавление новых программ и соглашений не требует изменения бизнес-логики - только обновление конфигурационных файлов.
+39 -2
View File
@@ -2303,7 +2303,7 @@ type CapitalOnboardingState {
enum CapitalOnboardingStep {
blagorost_offer_template
blagorost_provision
blagorost_program
generation_agreement_template
generator_program_template
}
@@ -5470,6 +5470,9 @@ input GenerateRegistrationDocumentsInput {
"""Имя кооператива"""
coopname: String!
"""Ключ выбранной программы регистрации (опционально)"""
program_key: String
"""Имя пользователя (аккаунт)"""
username: String!
}
@@ -8331,6 +8334,9 @@ type Query {
"""Получить подписки пользователя у провайдера"""
getProviderSubscriptions: [ProviderSubscription!]!
"""Получить конфигурацию программ регистрации для кооператива"""
getRegistrationConfig(account_type: AccountType!, coopname: String!): RegistrationConfig!
"""Получить сводную публичную информацию о системе"""
getSystemInfo: SystemInfo!
@@ -8529,7 +8535,7 @@ input RegisterParticipantInput {
"""
Подписанный документ соглашения по капитализации (опционально, только если требуется)
"""
capitalization_agreement: SignedDigitalDocumentInput
blagorost_offer: SignedDigitalDocumentInput
"""Подписанный документ политики конфиденциальности от пайщика"""
privacy_agreement: SignedDigitalDocumentInput!
@@ -8560,6 +8566,37 @@ type RegisteredAccount {
tokens: Tokens!
}
type RegistrationConfig {
"""Доступные программы"""
programs: [RegistrationProgram!]!
"""Нужен ли выбор программы"""
requires_selection: Boolean!
}
type RegistrationProgram {
"""Для каких типов аккаунтов доступна программа"""
applicable_account_types: [AccountType!]!
"""Описание программы"""
description: String!
"""URL изображения (опционально)"""
image_url: String
"""Уникальный ключ программы"""
key: String!
"""Порядок отображения"""
order: Int!
"""Минимальные требования для участия"""
requirements: String
"""Название программы для отображения"""
title: String!
}
type RepresentedBy {
"""На основании чего действует"""
based_on: String!
@@ -1,8 +1,15 @@
import { Field, InputType } from '@nestjs/graphql';
import { Field, InputType, registerEnumType } from '@nestjs/graphql';
import { IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator';
import { ParticipantApplicationSignedDocumentInputDTO } from '../../document/documents-dto/participant-application-document.dto';
import { SignedDigitalDocumentInputDTO } from '~/application/document/dto/signed-digital-document-input.dto';
import type { RegisterParticipantDomainInterface } from '~/domain/participant/interfaces/register-participant-domain.interface';
import { ProgramKey } from '~/domain/registration/enum';
// Регистрируем enum для GraphQL
registerEnumType(ProgramKey, {
name: 'ProgramKey',
description: 'Ключ выбранной программы регистрации',
});
@InputType('RegisterParticipantInput')
export class RegisterParticipantInputDTO implements RegisterParticipantDomainInterface {
@@ -57,5 +64,17 @@ export class RegisterParticipantInputDTO implements RegisterParticipantDomainInt
})
@ValidateNested()
@IsOptional()
capitalization_agreement?: SignedDigitalDocumentInputDTO;
blagorost_offer?: SignedDigitalDocumentInputDTO;
@Field(() => SignedDigitalDocumentInputDTO, {
description: 'Подписанный документ оферты по программе "Генератор" (опционально, только для программы generation)',
nullable: true,
})
@ValidateNested()
@IsOptional()
generator_offer?: SignedDigitalDocumentInputDTO;
@Field(() => ProgramKey, { description: 'Ключ выбранной программы регистрации', nullable: true })
@IsOptional()
program_key?: ProgramKey;
}
@@ -35,6 +35,7 @@ import {
AGREEMENT_CONFIGURATION_SERVICE,
} from '~/domain/registration/services/agreement-configuration.service';
import { AccountType } from '~/application/account/enum/account-type.enum';
import { DocumentType, AgreementId } from '~/domain/registration/enum';
@Injectable()
export class ParticipantInteractor {
@@ -122,41 +123,29 @@ export class ParticipantInteractor {
);
}
// Подготавливаем структуру для валидации документов с использованием нового сервиса
const documentsToValidate: IDocumentToValidate[] = [
{ id: 'statement', document: data.statement },
{ id: 'wallet_agreement', document: data.wallet_agreement },
{ id: 'user_agreement', document: data.user_agreement },
{ id: 'privacy_agreement', document: data.privacy_agreement },
{ id: 'signature_agreement', document: data.signature_agreement },
];
// Добавляем capitalization_agreement если есть
if (data.capitalization_agreement) {
documentsToValidate.push({ id: 'capitalization_agreement', document: data.capitalization_agreement });
}
// Валидируем документы с проверкой оригиналов в базе
const validationResults = await this.documentValidationService.validateRegistrationDocuments(documentsToValidate);
if (!this.documentValidationService.allDocumentsValid(validationResults)) {
const errors = this.documentValidationService.getErrorMessages(validationResults);
this.logger.error(`Ошибка валидации документов: ${errors.join('; ')}`);
throw new HttpApiError(http.BAD_REQUEST, `Ошибка валидации документов: ${errors.join('; ')}`);
}
// ПРОВЕРКА 2: Все ли требуемые соглашения предоставлены?
// Проверяем наличие всех обязательных соглашений для типа аккаунта кандидата
// ПРОВЕРКА 1: Получаем список всех требуемых соглашений из конфигурации
// Это единственный источник истины - конфигурация определяет что требуется
const requiredAgreements = this.agreementConfigurationService.getRequiredAgreementIds(
candidate.type as AccountType,
config.coopname
config.coopname,
data.program_key
);
// ПРОВЕРКА 2: Подготавливаем структуру для валидации документов динамически на основе конфигурации
const documentsToValidate: IDocumentToValidate[] = [
// Заявление всегда обязательно
{ id: 'statement', document: data.statement },
];
// Проверяем наличие и валидируем все требуемые соглашения из конфигурации
const missingAgreements: string[] = [];
for (const agreementId of requiredAgreements) {
const agreementData = data[agreementId];
if (!agreementData) {
missingAgreements.push(agreementId);
} else {
// Добавляем документ в список для валидации
documentsToValidate.push({ id: agreementId, document: agreementData });
}
}
@@ -167,17 +156,39 @@ export class ParticipantInteractor {
);
}
// ПРОВЕРКА 3: Проверка линкованных документов в заявлении
const statementLinks = (data.statement.meta as any)?.links || [];
const expectedLinks = [
data.wallet_agreement.doc_hash,
data.signature_agreement.doc_hash,
data.privacy_agreement.doc_hash,
data.user_agreement.doc_hash,
];
// ПРОВЕРКА 3: Валидируем все собранные документы с проверкой оригиналов в базе
const validationResults = await this.documentValidationService.validateRegistrationDocuments(documentsToValidate);
if (data.capitalization_agreement) {
expectedLinks.push(data.capitalization_agreement.doc_hash);
if (!this.documentValidationService.allDocumentsValid(validationResults)) {
const errors = this.documentValidationService.getErrorMessages(validationResults);
this.logger.error(`Ошибка валидации документов: ${errors.join('; ')}`);
throw new HttpApiError(http.BAD_REQUEST, `Ошибка валидации документов: ${errors.join('; ')}`);
}
// ПРОВЕРКА 4: Проверка линкованных документов в заявлении на основе конфигурации
const statementLinks = (data.statement.meta as any)?.links || [];
// Получаем список документов, которые должны быть линкованы, из конфигурации
const linkedAgreements = this.agreementConfigurationService.getLinkedAgreements(
candidate.type as AccountType,
config.coopname,
data.program_key
);
const expectedLinks: string[] = [];
const missingLinkedDocs: string[] = [];
for (const agreement of linkedAgreements) {
const doc = data[agreement.id];
if (doc) {
expectedLinks.push(doc.doc_hash);
} else {
missingLinkedDocs.push(agreement.id);
}
}
if (missingLinkedDocs.length > 0) {
this.logger.warn(`Отсутствуют документы для линковки: ${missingLinkedDocs.join(', ')}`);
}
// Проверяем, что все ожидаемые хеши присутствуют в заявлении
@@ -190,20 +201,28 @@ export class ParticipantInteractor {
);
}
// Сохраняем документы в репозиторий кандидатов
await this.candidateRepository.saveDocument(data.username, 'statement', data.statement);
await this.candidateRepository.saveDocument(data.username, 'wallet_agreement', data.wallet_agreement);
await this.candidateRepository.saveDocument(data.username, 'privacy_agreement', data.privacy_agreement);
await this.candidateRepository.saveDocument(data.username, 'signature_agreement', data.signature_agreement);
await this.candidateRepository.saveDocument(data.username, 'user_agreement', data.user_agreement);
// Сохраняем заявление
await this.candidateRepository.saveDocument(data.username, DocumentType.STATEMENT, data.statement);
// Сохраняем capitalization_agreement если есть
if (data.capitalization_agreement) {
await this.candidateRepository.saveDocument(data.username, 'capitalization_agreement', data.capitalization_agreement);
// Сохраняем все требуемые соглашения динамически на основе конфигурации
for (const agreementId of requiredAgreements) {
const doc = data[agreementId];
if (doc) {
// Преобразуем agreementId в соответствующий DocumentType
const documentType = this.mapAgreementIdToDocumentType(agreementId);
if (documentType) {
await this.candidateRepository.saveDocument(
data.username,
documentType,
doc
);
}
}
}
await this.candidateRepository.update(data.username, {
braname: data.braname,
program_key: data.program_key,
});
// Обновляем статус пользователя
@@ -216,6 +235,29 @@ export class ParticipantInteractor {
return await this.accountDomainService.getAccount(data.username);
}
/**
* Преобразует AgreementId в соответствующий DocumentType
*/
private mapAgreementIdToDocumentType(agreementId: string): DocumentType | null {
switch (agreementId) {
case AgreementId.SIGNATURE_AGREEMENT:
return DocumentType.SIGNATURE_AGREEMENT;
case AgreementId.WALLET_AGREEMENT:
return DocumentType.WALLET_AGREEMENT;
case AgreementId.USER_AGREEMENT:
return DocumentType.USER_AGREEMENT;
case AgreementId.PRIVACY_AGREEMENT:
return DocumentType.PRIVACY_AGREEMENT;
case AgreementId.BLAGOROST_OFFER:
return DocumentType.BLAGOROST_OFFER;
case AgreementId.GENERATOR_OFFER:
return DocumentType.GENERATOR_OFFER;
default:
this.logger.warn(`Неизвестный agreementId: ${agreementId}`);
return null;
}
}
async addParticipant(data: AddParticipantDomainInterface): Promise<AccountDomainEntity> {
const newAccount: RegisterAccountDomainInterface = {
...data,
@@ -0,0 +1,73 @@
import { ObjectType, Field, Int } from '@nestjs/graphql';
import { IsString, IsBoolean, IsNumber, IsArray, IsOptional } from 'class-validator';
import { AccountType } from '~/application/account/enum/account-type.enum';
import type { IRegistrationProgram } from '~/domain/registration/config/agreement-config.interface';
/**
* DTO для описания программы регистрации
*/
@ObjectType('RegistrationProgram')
export class RegistrationProgramDTO {
@Field({ description: 'Уникальный ключ программы' })
@IsString()
key!: string;
@Field({ description: 'Название программы для отображения' })
@IsString()
title!: string;
@Field({ description: 'Описание программы' })
@IsString()
description!: string;
@Field({ nullable: true, description: 'URL изображения (опционально)' })
@IsString()
@IsOptional()
image_url?: string;
@Field({ nullable: true, description: 'Минимальные требования для участия' })
@IsString()
@IsOptional()
requirements?: string;
@Field(() => [AccountType], { description: 'Для каких типов аккаунтов доступна программа' })
@IsArray()
applicable_account_types!: AccountType[];
@Field(() => Int, { description: 'Порядок отображения' })
@IsNumber()
order!: number;
constructor(data?: IRegistrationProgram) {
if (data) {
this.key = data.key;
this.title = data.title;
this.description = data.description;
this.image_url = data.image_url;
this.requirements = data.requirements;
this.applicable_account_types = data.applicable_account_types;
this.order = data.order;
}
}
}
/**
* DTO для конфигурации программ регистрации
*/
@ObjectType('RegistrationConfig')
export class RegistrationConfigDTO {
@Field({ description: 'Нужен ли выбор программы' })
@IsBoolean()
requires_selection!: boolean;
@Field(() => [RegistrationProgramDTO], { description: 'Доступные программы' })
@IsArray()
programs!: RegistrationProgramDTO[];
constructor(data?: { requires_selection: boolean; programs: IRegistrationProgram[] }) {
if (data) {
this.requires_selection = data.requires_selection;
this.programs = data.programs.map((p) => new RegistrationProgramDTO(p));
}
}
}
@@ -9,7 +9,9 @@ import { SettingsDTO, UpdateSettingsInputDTO } from '../dto/settings.dto';
import { StartInstallInputDTO } from '../dto/start-install-input.dto';
import { StartInstallResultDTO } from '../dto/start-install-result.dto';
import { GetInstallationStatusInputDTO, InstallationStatusDTO } from '../dto/installation-status.dto';
import { RegistrationConfigDTO } from '../dto/registration-config.dto';
import { AuthRoles } from '~/application/auth/decorators/auth.decorator';
import { AccountType } from '~/application/account/enum/account-type.enum';
@Resolver(() => SystemInfoDTO)
export class SystemResolver {
@@ -102,4 +104,15 @@ export class SystemResolver {
): Promise<SettingsDTO> {
return this.systemService.updateSettings(data);
}
@Query(() => RegistrationConfigDTO, {
name: 'getRegistrationConfig',
description: 'Получить конфигурацию программ регистрации для кооператива',
})
async getRegistrationConfig(
@Args('coopname', { type: () => String }) coopname: string,
@Args('account_type', { type: () => AccountType }) accountType: AccountType
): Promise<RegistrationConfigDTO> {
return this.systemService.getRegistrationConfig(coopname, accountType);
}
}
@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { Injectable, Inject } from '@nestjs/common';
import { SystemInfoDTO } from '../dto/system.dto';
import { SystemInteractor } from '../interactors/system.interactor';
import { ProviderService } from '~/application/provider/services/provider.service';
@@ -14,10 +14,21 @@ import { OrganizationWithBankAccountDTO } from '~/application/common/dto/organiz
import { OrganizationDomainEntity } from '~/domain/branch/entities/organization-domain.entity';
import config from '~/config/config';
import { SettingsDTO, UpdateSettingsInputDTO } from '../dto/settings.dto';
import { RegistrationConfigDTO } from '../dto/registration-config.dto';
import {
AgreementConfigurationService,
AGREEMENT_CONFIGURATION_SERVICE,
} from '~/domain/registration/services/agreement-configuration.service';
import type { AccountType } from '~/application/account/enum/account-type.enum';
@Injectable()
export class SystemService {
constructor(private readonly systemInteractor: SystemInteractor, private readonly providerService: ProviderService) {}
constructor(
private readonly systemInteractor: SystemInteractor,
private readonly providerService: ProviderService,
@Inject(AGREEMENT_CONFIGURATION_SERVICE)
private readonly agreementConfigService: AgreementConfigurationService
) {}
public async getInfo(): Promise<SystemInfoDTO> {
const info = await this.systemInteractor.getInfo();
@@ -91,4 +102,28 @@ export class SystemService {
const settings = await this.systemInteractor.updateSettings(data);
return new SettingsDTO(settings);
}
/**
* Получить конфигурацию программ регистрации для кооператива
* @param coopname - название кооператива
* @param accountType - тип аккаунта
* @returns конфигурация программ регистрации
*/
public async getRegistrationConfig(coopname: string, accountType: AccountType): Promise<RegistrationConfigDTO> {
const config = this.agreementConfigService.getCooperativeProgramsConfig(coopname);
if (!config) {
return new RegistrationConfigDTO({
requires_selection: false,
programs: [],
});
}
const programs = this.agreementConfigService.getAvailablePrograms(coopname, accountType);
return new RegistrationConfigDTO({
requires_selection: config.requires_selection && programs.length > 1,
programs,
});
}
}
@@ -1,6 +1,7 @@
import { InputType, Field } from '@nestjs/graphql';
import { IsString, IsEnum } from 'class-validator';
import { IsString, IsEnum, IsOptional } from 'class-validator';
import { AccountType } from '~/application/account/enum/account-type.enum';
import { ProgramKey } from '~/domain/registration/enum';
@InputType('GenerateRegistrationDocumentsInput')
export class GenerateRegistrationDocumentsInputDTO {
@@ -15,4 +16,9 @@ export class GenerateRegistrationDocumentsInputDTO {
@Field(() => AccountType, { description: 'Тип аккаунта пайщика' })
@IsEnum(AccountType)
account_type!: AccountType;
@Field({ nullable: true, description: 'Ключ выбранной программы регистрации (опционально)' })
@IsEnum(ProgramKey)
@IsOptional()
program_key?: ProgramKey;
}
@@ -5,7 +5,6 @@ import {
} from '~/domain/registration/services/registration-documents.service';
import type { GenerateRegistrationDocumentsInputDTO } from '../dto/generate-registration-documents-input.dto';
import { GenerateRegistrationDocumentsOutputDTO } from '../dto/generate-registration-documents-output.dto';
import type { AccountType } from '~/application/account/enum/account-type.enum';
@Injectable()
export class RegistrationService {
@@ -20,11 +19,7 @@ export class RegistrationService {
async generateRegistrationDocuments(
data: GenerateRegistrationDocumentsInputDTO
): Promise<GenerateRegistrationDocumentsOutputDTO> {
const result = await this.registrationDocumentsService.generateRegistrationDocuments({
coopname: data.coopname,
username: data.username,
account_type: data.account_type as AccountType,
});
const result = await this.registrationDocumentsService.generateRegistrationDocuments(data);
return new GenerateRegistrationDocumentsOutputDTO(result);
}
@@ -1,4 +1,5 @@
import { ISignedDocumentDomainInterface } from '~/domain/document/interfaces/signed-document-domain.interface';
import { ProgramKey } from '~/domain/registration/enum';
/**
* Домен-интерфейс кандидата в пайщики
@@ -16,10 +17,12 @@ export interface CandidateDomainInterface {
signature_agreement?: ISignedDocumentDomainInterface;
privacy_agreement?: ISignedDocumentDomainInterface;
user_agreement?: ISignedDocumentDomainInterface;
capitalization_agreement?: ISignedDocumentDomainInterface;
blagorost_offer?: ISignedDocumentDomainInterface;
generator_offer?: ISignedDocumentDomainInterface;
};
registration_hash: string;
referer?: string;
public_key: string;
meta?: string;
program_key?: ProgramKey; // Ключ выбранной программы регистрации
}
@@ -2,6 +2,7 @@
import type { CandidateDomainInterface } from '../interfaces/candidate-domain.interface';
import type { ISignedDocumentDomainInterface } from '~/domain/document/interfaces/signed-document-domain.interface';
import { DocumentType } from '~/domain/registration/enum';
export interface CandidateRepository {
findByUsername(username: string): Promise<CandidateDomainInterface | null>;
@@ -10,13 +11,7 @@ export interface CandidateRepository {
update(username: string, data: Partial<CandidateDomainInterface>): Promise<CandidateDomainInterface | null>;
saveDocument(
username: string,
documentType:
| 'statement'
| 'wallet_agreement'
| 'signature_agreement'
| 'privacy_agreement'
| 'user_agreement'
| 'capitalization_agreement',
documentType: DocumentType,
document: ISignedDocumentDomainInterface
): Promise<void>;
}
@@ -1,4 +1,5 @@
import type { ISignedDocumentDomainInterface } from '~/domain/document/interfaces/signed-document-domain.interface';
import { ProgramKey } from '~/domain/registration/enum';
export interface RegisterParticipantDomainInterface {
username: string;
@@ -9,5 +10,9 @@ export interface RegisterParticipantDomainInterface {
user_agreement: ISignedDocumentDomainInterface;
wallet_agreement: ISignedDocumentDomainInterface;
/** Опциональное соглашение по капитализации (только для individual) */
capitalization_agreement?: ISignedDocumentDomainInterface;
blagorost_offer?: ISignedDocumentDomainInterface;
/** Опциональное соглашение по генератору (для программы generation) */
generator_offer?: ISignedDocumentDomainInterface;
/** Ключ выбранной программы регистрации */
program_key?: ProgramKey;
}
@@ -1,13 +1,14 @@
import type { AccountType } from '~/application/account/enum/account-type.enum';
import { AgreementType, AgreementId, ProgramKey } from '../enum';
/**
* Конфигурация одного соглашения при регистрации
*/
export interface IAgreementConfigItem {
/**
* Уникальный идентификатор типа соглашения (wallet, signature, privacy, user, capitalization)
* Уникальный идентификатор типа соглашения
*/
id: string;
id: AgreementId;
/**
* registry_id для генерации документа на фабрике документов
@@ -15,9 +16,9 @@ export interface IAgreementConfigItem {
registry_id: number;
/**
* Тип соглашения для блокчейна (wallet, signature, privacy, user, capitalization)
* Тип соглашения для блокчейна
*/
agreement_type: string;
agreement_type: AgreementType;
/**
* Человекочитаемое название документа
@@ -64,3 +65,46 @@ export interface IRegistrationAgreementsConfig {
*/
agreements: IAgreementConfigItem[];
}
/**
* Описание программы регистрации с её соглашениями
*/
export interface IRegistrationProgram {
/** Уникальный ключ программы */
key: ProgramKey;
/** Название программы для отображения */
title: string;
/** Описание программы */
description: string;
/** URL изображения (опционально) */
image_url?: string;
/** Минимальные требования для участия */
requirements?: string;
/** Для каких типов аккаунтов доступна эта программа */
applicable_account_types: AccountType[];
/** Список ID соглашений, которые требуются для этой программы */
agreement_ids: AgreementId[];
/** Порядок отображения */
order: number;
}
/**
* Конфигурация программ регистрации для кооператива
*/
export interface ICooperativeRegistrationPrograms {
/** Название кооператива */
coopname: string;
/** Доступные программы */
programs: IRegistrationProgram[];
/** Нужен ли выбор программы (если false - используется первая подходящая) */
requires_selection: boolean;
}
@@ -1,2 +1,3 @@
export * from './agreement-config.interface';
export * from './registration-agreements.config';
export * from './registration-programs.config';
@@ -1,6 +1,7 @@
import { Cooperative } from 'cooptypes';
import type { IRegistrationAgreementsConfig } from './agreement-config.interface';
import { AccountType } from '~/application/account/enum/account-type.enum';
import { AgreementType, AgreementId } from '../enum';
/**
* Конфигурация соглашений, требуемых при регистрации пайщика.
@@ -12,9 +13,9 @@ import { AccountType } from '~/application/account/enum/account-type.enum';
export const REGISTRATION_AGREEMENTS_CONFIG: IRegistrationAgreementsConfig = {
agreements: [
{
id: 'signature_agreement',
id: AgreementId.SIGNATURE_AGREEMENT,
registry_id: Cooperative.Registry.RegulationElectronicSignature.registry_id,
agreement_type: 'signature',
agreement_type: AgreementType.SIGNATURE,
title: 'Соглашение о порядке и правилах использования простой электронной подписи',
checkbox_text: 'Я прочитал и принимаю',
link_text: 'положение о порядке и правилах использования простой электронной подписи',
@@ -24,9 +25,9 @@ export const REGISTRATION_AGREEMENTS_CONFIG: IRegistrationAgreementsConfig = {
order: 1,
},
{
id: 'wallet_agreement',
id: AgreementId.WALLET_AGREEMENT,
registry_id: Cooperative.Registry.WalletAgreement.registry_id,
agreement_type: 'wallet',
agreement_type: AgreementType.WALLET,
title: 'Соглашение о целевой потребительской программе "Цифровой Кошелёк"',
checkbox_text: 'Я прочитал и принимаю',
link_text: 'положение о целевой потребительской программе "Цифровой Кошелёк"',
@@ -36,9 +37,9 @@ export const REGISTRATION_AGREEMENTS_CONFIG: IRegistrationAgreementsConfig = {
order: 2,
},
{
id: 'user_agreement',
id: AgreementId.USER_AGREEMENT,
registry_id: Cooperative.Registry.UserAgreement.registry_id,
agreement_type: 'user',
agreement_type: AgreementType.USER,
title: 'Пользовательское соглашение',
checkbox_text: 'Я прочитал и принимаю',
link_text: 'пользовательское соглашение',
@@ -48,9 +49,9 @@ export const REGISTRATION_AGREEMENTS_CONFIG: IRegistrationAgreementsConfig = {
order: 3,
},
{
id: 'privacy_agreement',
id: AgreementId.PRIVACY_AGREEMENT,
registry_id: Cooperative.Registry.PrivacyPolicy.registry_id,
agreement_type: 'privacy',
agreement_type: AgreementType.PRIVACY,
title: 'Политика конфиденциальности',
checkbox_text: 'Я прочитал и принимаю',
link_text: 'политику конфиденциальности',
@@ -60,9 +61,9 @@ export const REGISTRATION_AGREEMENTS_CONFIG: IRegistrationAgreementsConfig = {
order: 4,
},
{
id: 'capitalization_agreement',
registry_id: Cooperative.Registry.CapitalizationAgreement.registry_id,
agreement_type: 'capital',
id: AgreementId.BLAGOROST_OFFER,
registry_id: Cooperative.Registry.BlagorostOffer.registry_id,
agreement_type: AgreementType.CAPITAL,
title: 'Оферта по целевой потребительской программе "Благорост"',
checkbox_text: 'Я прочитал и принимаю',
link_text: 'оферту по целевой потребительской программе "Благорост"',
@@ -71,5 +72,17 @@ export const REGISTRATION_AGREEMENTS_CONFIG: IRegistrationAgreementsConfig = {
applicable_account_types: [AccountType.individual],
order: 5,
},
{
id: AgreementId.GENERATOR_OFFER,
registry_id: Cooperative.Registry.GeneratorOffer.registry_id,
agreement_type: AgreementType.GENERATOR,
title: 'Оферта по целевой потребительской программе "Генератор"',
checkbox_text: 'Я прочитал и принимаю',
link_text: 'оферту по целевой потребительской программе "Генератор"',
is_blockchain_agreement: true,
link_to_statement: true,
applicable_account_types: [],
order: 6,
},
],
};
@@ -0,0 +1,34 @@
import type { ICooperativeRegistrationPrograms } from './agreement-config.interface';
import { AccountType } from '~/application/account/enum/account-type.enum';
import { ProgramKey, AgreementId } from '../enum';
/**
* Конфигурация программ регистрации для разных кооперативов.
* Здесь определяются доступные программы, их описания и связанные соглашения.
*/
export const REGISTRATION_PROGRAMS_CONFIG: ICooperativeRegistrationPrograms[] = [
{
coopname: 'voskhod',
requires_selection: true,
programs: [
{
key: ProgramKey.GENERATION,
title: 'Программа Генерация',
description:
'Участвовать в производстве Кооперативной Экономики через вклад временем, имуществом или деньгами в конкретные проекты. Минимальный взнос 10 часов в месяц.',
applicable_account_types: [AccountType.individual, AccountType.entrepreneur],
agreement_ids: [AgreementId.GENERATOR_OFFER],
order: 1,
},
{
key: ProgramKey.CAPITALIZATION,
title: 'Программа Капитализация',
description:
'Участвовать в производстве Кооперативной Экономики через вклад имуществом или денег в систему. Минимальный взнос 100 000 руб в течение 14 дней.',
applicable_account_types: [AccountType.individual, AccountType.entrepreneur],
agreement_ids: [AgreementId.BLAGOROST_OFFER],
order: 2,
},
],
},
];
@@ -0,0 +1,22 @@
/**
* Идентификаторы соглашений для регистрации
*/
export enum AgreementId {
/** Соглашение о порядке и правилах использования простой электронной подписи */
SIGNATURE_AGREEMENT = 'signature_agreement',
/** Соглашение о целевой потребительской программе "Цифровой Кошелёк" */
WALLET_AGREEMENT = 'wallet_agreement',
/** Пользовательское соглашение */
USER_AGREEMENT = 'user_agreement',
/** Политика конфиденциальности */
PRIVACY_AGREEMENT = 'privacy_agreement',
/** Соглашение о капитализации */
BLAGOROST_OFFER = 'blagorost_offer',
/** Оферта по целевой потребительской программе "Генератор" */
GENERATOR_OFFER = 'generator_offer',
}
@@ -0,0 +1,22 @@
/**
* Типы соглашений для отправки в блокчейн
*/
export enum AgreementType {
/** Соглашение о порядке и правилах использования простой электронной подписи */
SIGNATURE = 'signature',
/** Соглашение о целевой потребительской программе "Цифровой Кошелёк" */
WALLET = 'wallet',
/** Пользовательское соглашение */
USER = 'user',
/** Политика конфиденциальности */
PRIVACY = 'privacy',
/** Соглашение о капитализации */
CAPITAL = 'capital',
/** Оферта по целевой потребительской программе "Генератор" */
GENERATOR = 'generator',
}
@@ -0,0 +1,25 @@
/**
* Типы документов для регистрации кандидата
*/
export enum DocumentType {
/** Заявление на вступление в кооператив */
STATEMENT = 'statement',
/** Соглашение о цифровом кошельке */
WALLET_AGREEMENT = 'wallet_agreement',
/** Соглашение о порядке использования электронной подписи */
SIGNATURE_AGREEMENT = 'signature_agreement',
/** Политика конфиденциальности */
PRIVACY_AGREEMENT = 'privacy_agreement',
/** Пользовательское соглашение */
USER_AGREEMENT = 'user_agreement',
/** Соглашение о капитализации */
BLAGOROST_OFFER = 'blagorost_offer',
/** Оферта по программе генератора */
GENERATOR_OFFER = 'generator_offer',
}
@@ -0,0 +1,4 @@
export { AgreementType } from './agreement-type.enum';
export { AgreementId } from './agreement-id.enum';
export { ProgramKey } from './program-key.enum';
export { DocumentType } from './document-type.enum';
@@ -0,0 +1,10 @@
/**
* Ключи программ регистрации
*/
export enum ProgramKey {
/** Программа Генерация - вклад временем, имуществом или деньгами в конкретные проекты */
GENERATION = 'generation',
/** Программа Капитализация - вклад имуществом или денег в систему */
CAPITALIZATION = 'capitalization',
}
@@ -1,5 +1,6 @@
import type { AccountType } from '~/application/account/enum/account-type.enum';
import type { GeneratedDocumentDomainInterface } from '~/domain/document/interfaces/generated-document-domain.interface';
import { ProgramKey, type AgreementId, type AgreementType } from '../enum';
/**
* Входные данные для генерации пакета документов при регистрации
@@ -11,6 +12,8 @@ export interface IGenerateRegistrationDocumentsInput {
username: string;
/** Тип аккаунта пайщика */
account_type: AccountType;
/** Ключ выбранной программы регистрации (опционально) */
program_key?: ProgramKey;
}
/**
@@ -18,9 +21,9 @@ export interface IGenerateRegistrationDocumentsInput {
*/
export interface IGeneratedRegistrationDocument {
/** Идентификатор соглашения (wallet_agreement, signature_agreement, etc.) */
id: string;
id: AgreementId;
/** Тип соглашения для блокчейна */
agreement_type: string;
agreement_type: AgreementType;
/** Название документа */
title: string;
/** Текст для галочки на фронте */
@@ -1,6 +1,7 @@
import { Global, Module, forwardRef } from '@nestjs/common';
import { AgreementConfigurationService, AGREEMENT_CONFIGURATION_SERVICE } from './services/agreement-configuration.service';
import { RegistrationDocumentsService, REGISTRATION_DOCUMENTS_SERVICE } from './services/registration-documents.service';
import { CooperativeConfigService } from './services/cooperative-config.service';
import { DocumentModule } from '~/application/document/document.module';
/**
@@ -12,6 +13,7 @@ import { DocumentModule } from '~/application/document/document.module';
@Module({
imports: [forwardRef(() => DocumentModule)],
providers: [
CooperativeConfigService,
AgreementConfigurationService,
{
provide: AGREEMENT_CONFIGURATION_SERVICE,
@@ -1,7 +1,14 @@
import { Injectable } from '@nestjs/common';
import { REGISTRATION_AGREEMENTS_CONFIG } from '../config/registration-agreements.config';
import type { IAgreementConfigItem } from '../config/agreement-config.interface';
import type { AccountType } from '~/application/account/enum/account-type.enum';
import { REGISTRATION_PROGRAMS_CONFIG } from '../config/registration-programs.config';
import type {
IAgreementConfigItem,
ICooperativeRegistrationPrograms,
IRegistrationProgram,
} from '../config/agreement-config.interface';
import { AccountType } from '~/application/account/enum/account-type.enum';
import { AgreementId, ProgramKey } from '../enum';
import { CooperativeConfigService } from './cooperative-config.service';
export const AGREEMENT_CONFIGURATION_SERVICE = Symbol('AgreementConfigurationService');
@@ -10,43 +17,75 @@ export const AGREEMENT_CONFIGURATION_SERVICE = Symbol('AgreementConfigurationSer
*/
@Injectable()
export class AgreementConfigurationService {
constructor(private readonly cooperativeConfigService: CooperativeConfigService) {}
/**
* Получить все соглашения для указанного типа аккаунта
* @param accountType - тип аккаунта (individual, organization, entrepreneur)
* @param coopname - название кооператива (для временного исключения capitalization_agreement)
* @param coopname - название кооператива (для временного исключения blagorost_offer)
* @param programKey - ключ выбранной программы регистрации (опционально)
* @returns отсортированный по order массив конфигураций соглашений
*/
getAgreementsForAccountType(accountType: AccountType, coopname?: string): IAgreementConfigItem[] {
return REGISTRATION_AGREEMENTS_CONFIG.agreements
.filter((agreement) => agreement.applicable_account_types.includes(accountType))
.filter((agreement) => {
// ВРЕМЕННО: исключаем capitalization_agreement для всех кооперативов кроме voskhod
if (agreement.id === 'capitalization_agreement' && coopname && coopname !== 'voskhod') {
return false;
}
return true;
})
.sort((a, b) => a.order - b.order);
/**
* Получить соглашения по умолчанию для кооператива (без учета программ)
*/
private getDefaultAgreementsForCooperative(
accountType: AccountType,
coopname?: string
): IAgreementConfigItem[] {
const coopnameValue = coopname || '';
const excludedAgreements = this.cooperativeConfigService.getExcludedFromBaseAgreements(coopnameValue);
// Получить базовые соглашения, исключая те, которые управляются отдельно
const baseAgreements = REGISTRATION_AGREEMENTS_CONFIG.agreements
.filter((a) => a.applicable_account_types.includes(accountType))
.filter((a) => !excludedAgreements.includes(a.id));
// Добавить дополнительные соглашения по умолчанию для кооператива
const additionalAgreements = this.cooperativeConfigService
.getDefaultAdditionalAgreements(coopnameValue, accountType)
.map(id => this.getAgreementById(id))
.filter((a): a is IAgreementConfigItem => a !== null);
return [...baseAgreements, ...additionalAgreements].sort((a, b) => a.order - b.order);
}
getAgreementsForAccountType(
accountType: AccountType,
coopname?: string,
programKey?: ProgramKey
): IAgreementConfigItem[] {
// Получить соглашения по умолчанию для кооператива
const defaultAgreements = this.getDefaultAgreementsForCooperative(accountType, coopname);
// Если указана программа - добавляем её соглашения
if (programKey && coopname) {
const programAgreements = this.getAgreementsForProgram(programKey, coopname);
return [...defaultAgreements, ...programAgreements].sort((a, b) => a.order - b.order);
}
return defaultAgreements;
}
/**
* Получить соглашения, которые нужно отправить в блокчейн через sendAgreement
* @param accountType - тип аккаунта
* @param coopname - название кооператива (для временного исключения capitalization_agreement)
* @param coopname - название кооператива (для временного исключения blagorost_offer)
* @param programKey - ключ выбранной программы регистрации
* @returns массив конфигураций blockchain agreements
*/
getBlockchainAgreements(accountType: AccountType, coopname?: string): IAgreementConfigItem[] {
return this.getAgreementsForAccountType(accountType, coopname).filter((agreement) => agreement.is_blockchain_agreement);
getBlockchainAgreements(accountType: AccountType, coopname?: string, programKey?: ProgramKey): IAgreementConfigItem[] {
return this.getAgreementsForAccountType(accountType, coopname, programKey).filter((agreement) => agreement.is_blockchain_agreement);
}
/**
* Получить соглашения, которые нужно линковать в заявление на вступление
* @param accountType - тип аккаунта
* @param coopname - название кооператива (для временного исключения capitalization_agreement)
* @param coopname - название кооператива (для временного исключения blagorost_offer)
* @param programKey - ключ выбранной программы регистрации
* @returns массив конфигураций соглашений для линковки
*/
getLinkedAgreements(accountType: AccountType, coopname?: string): IAgreementConfigItem[] {
return this.getAgreementsForAccountType(accountType, coopname).filter((agreement) => agreement.link_to_statement);
getLinkedAgreements(accountType: AccountType, coopname?: string, programKey?: ProgramKey): IAgreementConfigItem[] {
return this.getAgreementsForAccountType(accountType, coopname, programKey).filter((agreement) => agreement.link_to_statement);
}
/**
@@ -54,7 +93,7 @@ export class AgreementConfigurationService {
* @param id - идентификатор соглашения (wallet_agreement, signature_agreement, etc.)
* @returns конфигурация соглашения или null
*/
getAgreementById(id: string): IAgreementConfigItem | null {
getAgreementById(id: AgreementId): IAgreementConfigItem | null {
return REGISTRATION_AGREEMENTS_CONFIG.agreements.find((agreement) => agreement.id === id) || null;
}
@@ -70,28 +109,65 @@ export class AgreementConfigurationService {
* Проверить, требуется ли соглашение для данного типа аккаунта
* @param agreementId - идентификатор соглашения
* @param accountType - тип аккаунта
* @param coopname - название кооператива (для временного исключения capitalization_agreement)
* @param coopname - название кооператива (для временного исключения blagorost_offer)
* @returns true если соглашение требуется
*/
isAgreementRequired(agreementId: string, accountType: AccountType, coopname?: string): boolean {
const agreement = this.getAgreementById(agreementId);
if (!agreement) return false;
// ВРЕМЕННО: исключаем capitalization_agreement для всех кооперативов кроме voskhod
if (agreementId === 'capitalization_agreement' && coopname && coopname !== 'voskhod') {
return false;
}
return agreement.applicable_account_types.includes(accountType);
isAgreementRequired(agreementId: AgreementId, accountType: AccountType, coopname?: string, programKey?: ProgramKey): boolean {
// Проверить, входит ли соглашение в список требуемых для данного кооператива и типа аккаунта
const requiredAgreements = this.getRequiredAgreementIds(accountType, coopname, programKey);
return requiredAgreements.includes(agreementId);
}
/**
* Получить список идентификаторов всех требуемых соглашений для типа аккаунта
* @param accountType - тип аккаунта
* @param coopname - название кооператива (для временного исключения capitalization_agreement)
* @param coopname - название кооператива (для временного исключения blagorost_offer)
* @param programKey - ключ выбранной программы регистрации
* @returns массив идентификаторов
*/
getRequiredAgreementIds(accountType: AccountType, coopname?: string): string[] {
return this.getAgreementsForAccountType(accountType, coopname).map((a) => a.id);
getRequiredAgreementIds(accountType: AccountType, coopname?: string, programKey?: ProgramKey): string[] {
return this.getAgreementsForAccountType(accountType, coopname, programKey).map((a) => a.id);
}
/**
* Получить доступные программы регистрации для кооператива и типа аккаунта
* @param coopname - название кооператива
* @param accountType - тип аккаунта
* @returns массив доступных программ
*/
getAvailablePrograms(coopname: string, accountType: AccountType): IRegistrationProgram[] {
const config = REGISTRATION_PROGRAMS_CONFIG.find((c) => c.coopname === coopname);
if (!config) return [];
return config.programs
.filter((p) => p.applicable_account_types.includes(accountType))
.sort((a, b) => a.order - b.order);
}
/**
* Получить конфигурацию программ для кооператива
* @param coopname - название кооператива
* @returns конфигурация программ или null
*/
getCooperativeProgramsConfig(coopname: string): ICooperativeRegistrationPrograms | null {
return REGISTRATION_PROGRAMS_CONFIG.find((c) => c.coopname === coopname) || null;
}
/**
* Получить соглашения для программы
* @param programKey - ключ программы
* @param coopname - название кооператива
* @returns массив соглашений для программы
*/
getAgreementsForProgram(programKey: ProgramKey, coopname: string): IAgreementConfigItem[] {
const config = REGISTRATION_PROGRAMS_CONFIG.find((c) => c.coopname === coopname);
if (!config) return [];
const program = config.programs.find((p) => p.key === programKey);
if (!program) return [];
return program.agreement_ids
.map((id) => this.getAgreementById(id))
.filter((a): a is IAgreementConfigItem => a !== null);
}
}
@@ -0,0 +1,42 @@
import { Injectable } from '@nestjs/common';
import { AgreementId } from '../enum';
import { AccountType } from '~/application/account/enum/account-type.enum';
/**
* Сервис для определения конфигурации кооперативов
*/
@Injectable()
export class CooperativeConfigService {
/**
* Получить список кооперативов, которые поддерживают программы регистрации
*/
getCooperativesWithPrograms(): string[] {
return ['voskhod'];
}
/**
* Проверить, поддерживает ли кооператив программы регистрации
*/
hasPrograms(coopname: string): boolean {
return this.getCooperativesWithPrograms().includes(coopname);
}
/**
* Получить дополнительные соглашения по умолчанию для кооператива
*/
getDefaultAdditionalAgreements(coopname: string, accountType: AccountType): AgreementId[] {
if (coopname === 'voskhod' && accountType === AccountType.individual) {
return [AgreementId.BLAGOROST_OFFER];
}
return [];
}
/**
* Получить список соглашений, которые нужно исключить из базовых для данного кооператива
*/
getExcludedFromBaseAgreements(coopname: string): AgreementId[] {
// Для всех кооперативов исключаем соглашения, которые добавляются через программы или дополнительные
return [AgreementId.BLAGOROST_OFFER, AgreementId.GENERATOR_OFFER];
}
}
@@ -8,6 +8,7 @@ import type {
IGeneratedRegistrationDocument,
} from '../interfaces/registration-documents.interface';
import type { AccountType } from '~/application/account/enum/account-type.enum';
import { ProgramKey } from '../enum';
export const REGISTRATION_DOCUMENTS_SERVICE = Symbol('RegistrationDocumentsService');
@@ -33,16 +34,22 @@ export class RegistrationDocumentsService {
async generateRegistrationDocuments(
input: IGenerateRegistrationDocumentsInput
): Promise<IGenerateRegistrationDocumentsOutput> {
const { coopname, username, account_type } = input;
const { coopname, username, account_type, program_key } = input;
this.logger.log(`Начало генерации документов для регистрации: username=${username}, account_type=${account_type}`);
this.logger.log(
`Начало генерации документов для регистрации: username=${username}, account_type=${account_type}, program_key=${program_key || 'не указан'}`
);
// ВАЖНО: Проверяем, что запрашиваемый тип аккаунта совпадает с типом зарегистрированного кандидата
// Эта проверка делается на уровне сервиса для ранней валидации
// (дополнительная проверка также будет в registerParticipant)
// Получаем список соглашений для данного типа аккаунта
const agreementsConfig = this.agreementConfigService.getAgreementsForAccountType(account_type, coopname);
// Получаем список соглашений для данного типа аккаунта и выбранной программы
const agreementsConfig = this.agreementConfigService.getAgreementsForAccountType(
account_type,
coopname,
program_key
);
this.logger.log(`Найдено ${agreementsConfig.length} соглашений для типа ${account_type} (кооператив: ${coopname})`);
@@ -103,20 +110,22 @@ export class RegistrationDocumentsService {
/**
* Получить список идентификаторов документов, которые нужно линковать в заявление
* @param accountType - тип аккаунта
* @param coopname - название кооператива (для временного исключения capitalization_agreement)
* @param coopname - название кооператива (для временного исключения blagorost_offer)
* @param programKey - ключ выбранной программы регистрации
* @returns массив id документов для линковки
*/
getLinkedDocumentIds(accountType: AccountType, coopname?: string): string[] {
return this.agreementConfigService.getLinkedAgreements(accountType, coopname).map((a) => a.id);
getLinkedDocumentIds(accountType: AccountType, coopname?: string, programKey?: ProgramKey): string[] {
return this.agreementConfigService.getLinkedAgreements(accountType, coopname, programKey).map((a) => a.id);
}
/**
* Получить список идентификаторов документов, которые нужно отправить в блокчейн
* @param accountType - тип аккаунта
* @param coopname - название кооператива (для временного исключения capitalization_agreement)
* @param coopname - название кооператива (для временного исключения blagorost_offer)
* @param programKey - ключ выбранной программы регистрации
* @returns массив id документов для отправки в блокчейн
*/
getBlockchainDocumentIds(accountType: AccountType, coopname?: string): string[] {
return this.agreementConfigService.getBlockchainAgreements(accountType, coopname).map((a) => a.id);
getBlockchainDocumentIds(accountType: AccountType, coopname?: string, programKey?: ProgramKey): string[] {
return this.agreementConfigService.getBlockchainAgreements(accountType, coopname, programKey).map((a) => a.id);
}
}
@@ -4,7 +4,7 @@ import { IsEnum, IsNotEmpty, IsOptional, IsString, MaxLength } from 'class-valid
export enum CapitalOnboardingStepEnum {
generator_program_template = 'generator_program_template',
generation_agreement_template = 'generation_agreement_template',
blagorost_provision = 'blagorost_provision',
blagorost_program = 'blagorost_program',
blagorost_offer_template = 'blagorost_offer_template',
}
@@ -65,7 +65,7 @@ export class CapitalOnboardingEventsService {
const mapping: Record<string, keyof IConfig> = {
generator_program_template: 'onboarding_generator_program_template_done',
generation_agreement_template: 'onboarding_generation_agreement_template_done',
blagorost_provision: 'onboarding_blagorost_provision_done',
blagorost_program: 'onboarding_blagorost_provision_done',
blagorost_offer_template: 'onboarding_blagorost_offer_template_done',
};
@@ -40,7 +40,7 @@ export class CapitalOnboardingService {
return 'onboarding_generator_program_template_done';
case CapitalOnboardingStepEnum.generation_agreement_template:
return 'onboarding_generation_agreement_template_done';
case CapitalOnboardingStepEnum.blagorost_provision:
case CapitalOnboardingStepEnum.blagorost_program:
return 'onboarding_blagorost_provision_done';
case CapitalOnboardingStepEnum.blagorost_offer_template:
return 'onboarding_blagorost_offer_template_done';
@@ -55,7 +55,7 @@ export class CapitalOnboardingService {
return 'onboarding_generator_program_template_hash';
case CapitalOnboardingStepEnum.generation_agreement_template:
return 'onboarding_generation_agreement_template_hash';
case CapitalOnboardingStepEnum.blagorost_provision:
case CapitalOnboardingStepEnum.blagorost_program:
return 'onboarding_blagorost_provision_hash';
case CapitalOnboardingStepEnum.blagorost_offer_template:
return 'onboarding_blagorost_offer_template_hash';
@@ -67,11 +67,11 @@ export class CapitalOnboardingService {
private mapStepToVarsField(step: CapitalOnboardingStepEnum): string {
switch (step) {
case CapitalOnboardingStepEnum.generator_program_template:
return 'generator_program_template';
return 'generator_program';
case CapitalOnboardingStepEnum.generation_agreement_template:
return 'generation_agreement_template';
case CapitalOnboardingStepEnum.blagorost_provision:
return 'blagorost_provision';
case CapitalOnboardingStepEnum.blagorost_program:
return 'blagorost_program';
case CapitalOnboardingStepEnum.blagorost_offer_template:
return 'blagorost_offer_template';
default:
@@ -161,7 +161,7 @@ export class ParticipationManagementService {
const document = await this.documentInteractor.generateDocument({
data: {
...data,
registry_id: Cooperative.Registry.CapitalizationAgreement.registry_id,
registry_id: Cooperative.Registry.BlagorostOffer.registry_id,
},
options,
});
@@ -33,9 +33,9 @@ export class AccountBlockchainAdapter implements AccountBlockchainPort {
throw new HttpApiError(HttpStatus.BAD_REQUEST, 'Не найдено заявление на вступление');
}
// Получаем конфигурацию соглашений для типа аккаунта кандидата
// Получаем конфигурацию соглашений для типа аккаунта кандидата и выбранной программы
const accountType = candidate.type as AccountType;
const blockchainAgreements = this.agreementConfigService.getBlockchainAgreements(accountType, config.coopname);
const blockchainAgreements = this.agreementConfigService.getBlockchainAgreements(accountType, config.coopname, candidate.program_key);
// Проверяем наличие всех требуемых документов на основе конфигурации
for (const agreementConfig of blockchainAgreements) {
@@ -37,7 +37,13 @@ export class CandidateEntity {
user_agreement?: ISignedDocumentDomainInterface;
@Column('json', { nullable: true })
capitalization_agreement?: ISignedDocumentDomainInterface;
blagorost_offer?: ISignedDocumentDomainInterface;
@Column('json', { nullable: true })
generator_offer?: ISignedDocumentDomainInterface;
@Column({ nullable: true })
program_key?: string;
@Column({ nullable: false })
registration_hash!: string;
@@ -5,6 +5,7 @@ import { CandidateEntity } from '../entities/candidate.entity';
import type { CandidateRepository } from '~/domain/account/repository/candidate.repository';
import type { CandidateDomainInterface } from '~/domain/account/interfaces/candidate-domain.interface';
import type { ISignedDocumentDomainInterface } from '~/domain/document/interfaces/signed-document-domain.interface';
import { DocumentType, ProgramKey } from '~/domain/registration/enum';
@Injectable()
export class TypeOrmCandidateRepository implements CandidateRepository {
@@ -39,6 +40,7 @@ export class TypeOrmCandidateRepository implements CandidateRepository {
candidate.referer = data.referer;
candidate.public_key = data.public_key;
candidate.meta = data.meta;
candidate.program_key = data.program_key;
const createdEntity = await this.candidateRepository.save(candidate);
return this.mapToDomainEntity(createdEntity);
@@ -52,6 +54,7 @@ export class TypeOrmCandidateRepository implements CandidateRepository {
if (data.type) candidate.type = data.type;
if (data.braname) candidate.braname = data.braname;
if (data.registration_hash) candidate.registration_hash = data.registration_hash;
if (data.program_key !== undefined) candidate.program_key = data.program_key;
if (data.documents) {
if (data.documents.statement) candidate.statement = data.documents.statement;
@@ -59,8 +62,9 @@ export class TypeOrmCandidateRepository implements CandidateRepository {
if (data.documents.signature_agreement) candidate.signature_agreement = data.documents.signature_agreement;
if (data.documents.privacy_agreement) candidate.privacy_agreement = data.documents.privacy_agreement;
if (data.documents.user_agreement) candidate.user_agreement = data.documents.user_agreement;
if (data.documents.capitalization_agreement)
candidate.capitalization_agreement = data.documents.capitalization_agreement;
if (data.documents.blagorost_offer)
candidate.blagorost_offer = data.documents.blagorost_offer;
if (data.documents.generator_offer) candidate.generator_offer = data.documents.generator_offer;
}
const updatedEntity = await this.candidateRepository.save(candidate);
@@ -69,13 +73,7 @@ export class TypeOrmCandidateRepository implements CandidateRepository {
async saveDocument(
username: string,
documentType:
| 'statement'
| 'wallet_agreement'
| 'signature_agreement'
| 'privacy_agreement'
| 'user_agreement'
| 'capitalization_agreement',
documentType: DocumentType,
document: ISignedDocumentDomainInterface
): Promise<void> {
const candidate = await this.candidateRepository.findOneBy({ username });
@@ -84,23 +82,26 @@ export class TypeOrmCandidateRepository implements CandidateRepository {
}
switch (documentType) {
case 'statement':
case DocumentType.STATEMENT:
candidate.statement = document;
break;
case 'wallet_agreement':
case DocumentType.WALLET_AGREEMENT:
candidate.wallet_agreement = document;
break;
case 'signature_agreement':
case DocumentType.SIGNATURE_AGREEMENT:
candidate.signature_agreement = document;
break;
case 'privacy_agreement':
case DocumentType.PRIVACY_AGREEMENT:
candidate.privacy_agreement = document;
break;
case 'user_agreement':
case DocumentType.USER_AGREEMENT:
candidate.user_agreement = document;
break;
case 'capitalization_agreement':
candidate.capitalization_agreement = document;
case DocumentType.BLAGOROST_OFFER:
candidate.blagorost_offer = document;
break;
case DocumentType.GENERATOR_OFFER:
candidate.generator_offer = document;
break;
}
@@ -121,12 +122,14 @@ export class TypeOrmCandidateRepository implements CandidateRepository {
signature_agreement: entity.signature_agreement,
privacy_agreement: entity.privacy_agreement,
user_agreement: entity.user_agreement,
capitalization_agreement: entity.capitalization_agreement,
blagorost_offer: entity.blagorost_offer,
generator_offer: entity.generator_offer,
},
registration_hash: entity.registration_hash,
referer: entity.referer,
public_key: entity.public_key,
meta: entity.meta,
program_key: entity.program_key as ProgramKey | undefined,
};
}
}
@@ -64,34 +64,36 @@ export interface IVars {
protocol_number: string
protocol_day_month_year: string
}
participant_application?: {
participant_application?: { // Заявление на вступление
protocol_number: string
protocol_day_month_year: string
}
coopenomics_agreement?: {
coopenomics_agreement?: { // Оферта на подключение кооператива
protocol_number: string
protocol_day_month_year: string
}
generation_agreement_template?: {
// Благорост
blagorost_program?: { // Положение о программе "Благорост"
protocol_number: string
protocol_day_month_year: string
}
blagorost_provision?: {
blagorost_offer_template?: { // Шаблон оферты "Благорост"
protocol_number: string
protocol_day_month_year: string
}
blagorost_offer_template?: {
generator_program?: { // Положение о программе "Гененатор"
protocol_number: string
protocol_day_month_year: string
}
generation_program?: {
generator_offer_template?: { // Шаблон оферты "Генератора"
protocol_number: string
protocol_day_month_year: string
}
generation_offer_template?: {
generation_agreement_template?: { // Шаблон договора участия в хозяйственной деятельности
protocol_number: string
protocol_day_month_year: string
}
}
/**
File diff suppressed because one or more lines are too long
@@ -9,7 +9,7 @@
{{coop.chairman.last_name}} {{coop.chairman.first_name}} {{coop.chairman.middle_name}}, действующего на основании Устава с одной стороны, и Пайщик Общества __________________________, действующий на основании собственного волеизъявления, с другой стороны, а вместе Стороны, согласились с нижеследующим:
Настоящее Пользовательское соглашение, составленное в соответствии с Гражданским кодексом
РФ, Уставом Общества и на основании Положения Общества о целевой потребительской
программе “ГЕНЕРАТОР”, утвержденном Собранием Совета Общества (Протокол №{{vars.generation_program.protocol_number}} от {{vars.generation_program.protocol_day_month_year}}), формулируют соглашение между Обществом и Пайщиком а также является по отношению к Пайщику Офертой от Общества, где Общество является Оферентом, а Пайщик является Акцептантом.
программе “ГЕНЕРАТОР”, утвержденном Собранием Совета Общества (Протокол №{{vars.generator_program.protocol_number}} от {{vars.generator_program.protocol_day_month_year}}), формулируют соглашение между Обществом и Пайщиком а также является по отношению к Пайщику Офертой от Общества, где Общество является Оферентом, а Пайщик является Акцептантом.
Отметка о Согласии
с Настоящим Пользовательским соглашением производится электронно, а дата создания Личного кабинета пайщика на сайте {{vars.website}} считается датой акцепта настоящей Оферты как пайщика Общества по взаимодействию между Пайщиком и Обществом в соответствии с условиями целевой потребительской программы “ГЕНЕРАТОР” (далее “ЦПП”).
File diff suppressed because one or more lines are too long
@@ -15,7 +15,7 @@ export interface Model {
meta: IMetaDocument
coop: ICooperativeData
vars: IVars
blagorost_provision: {
blagorost_program: {
protocol_number: string
protocol_date: string
}
@@ -23,7 +23,7 @@ export interface Model {
export const title = 'Пользовательское соглашение (оферта) по присоединению к ЦПП «БЛАГОРОСТ»'
export const description = 'Шаблон публичной оферты по ЦПП «БЛАГОРОСТ» для утверждения Советом'
export const context = `<div class="digital-document"><div style="text-align: center"><h1 class="header">{% trans 'OFFER_TITLE' %}</h1><p class="subheader">{% trans 'offer_template_subtitle' %}</p></div><p>{% trans 'offer_template_intro_1' %} {{coop.chairman.last_name}} {{coop.chairman.first_name}} {{coop.chairman.middle_name}}{% trans 'offer_template_intro_2' %}</p><p>{% trans 'offer_template_legal_basis' %} {{ blagorost_provision.protocol_number }} {% trans 'from' %} {{ blagorost_provision.protocol_date }}{% trans 'offer_template_legal_basis_2' %}</p><p>{% trans 'offer_template_acceptance' %} {{vars.website}} {% trans 'offer_template_acceptance_2' %}</p><div style="text-align: center"><h3>{% trans 'terms_title' %}</h3></div><p><strong>{% trans 'platform_term' %}</strong> - {% trans 'platform_definition' %}</p><p><strong>{% trans 'property_term' %}</strong> - {% trans 'property_definition_template' %}</p><p><strong>{% trans 'money_term' %}</strong> - {% trans 'money_definition_template' %}</p><p><strong>{% trans 'participant_term' %}</strong> - {{vars.short_abbr}} "{{vars.name}}"{% trans 'participant_definition_template' %}</p><p><strong>{% trans 'site_term' %}</strong> - {{vars.website}}{% trans 'site_definition_template' %}</p><p><strong>{% trans 'share_contribution_term' %}</strong> - {% trans 'share_contribution_definition' %}</p><p><strong>{% trans 'share_capital_term' %}</strong> - {% trans 'share_capital_definition' %}</p><p><strong>{% trans 'personal_cabinet_term' %}</strong> - {% trans 'personal_cabinet_definition_template' %}</p><p><strong>{% trans 'personal_account_term' %}</strong> - {% trans 'personal_account_definition_template' %}</p><div style="text-align: center"><h3>{% trans 'goals_title' %}</h3></div><p>{% trans 'goals_intro_template' %}</p><p>{% trans 'goal_1' %}</p><p>{% trans 'goal_2' %}</p><p>{% trans 'tasks_intro' %}</p><p>{% trans 'task_1' %}</p><p>{% trans 'task_2' %}</p><div style="text-align: center"><h3>{% trans 'economic_mechanism_title' %}</h3></div><p>{% trans 'economic_mechanism_intro_template' %}</p><p>{% trans 'participation_proportion' %}</p><p>{% trans 'share_equivalence_template' %}</p><p>{% trans 'society_notification_template' %}</p><p>{% trans 'participant_rights_contributions' %}</p><p>{% trans 'minimum_contribution' %}</p><p>{% trans 'property_contributions' %}</p><p>{% trans 'money_contribution_procedure' %}</p><p>{% trans 'property_contribution_procedure_template' %}</p><p>{% trans 'council_consideration_delay' %}</p><p>{% trans 'council_consideration_time' %}</p><p>{% trans 'property_acceptance_act' %}</p><div style="text-align: center"><h3>{% trans 'return_mechanism_title' %}</h3></div><p>{% trans 'return_source' %}</p><p>{% trans 'economic_effect_template' %}</p><p>{% trans 'participant_return_right' %}</p><p>{% trans 'society_return_notification_template' %}</p><p>{% trans 'return_application' %}</p><p>{% trans 'return_procedure' %}</p><p>{% trans 'return_to_wallet' %}</p><p>{% trans 'exit_return_procedure_template' %}</p><p>{% trans 'exit_consideration_time_template' %}</p><div style="text-align: center"><h3>{% trans 'rights_obligations_title' %}</h3></div><p><strong>{% trans 'participant_rights_title' %}</strong></p><p>{% trans 'participant_right_1' %}</p><p>{% trans 'participant_right_2' %}</p><p>{% trans 'participant_right_3' %}</p><p>{% trans 'participant_right_4' %}</p><p><strong>{% trans 'participant_obligations_title' %}</strong></p><p>{% trans 'participant_obligation_1' %}</p><p>{% trans 'participant_obligation_2' %}</p><p><strong>{% trans 'society_obligations_title' %}</strong></p><p>{% trans 'society_obligation_1_template' %}</p><p>{% trans 'society_obligation_2_template' %}</p><p><strong>{% trans 'society_rights_title' %}</strong></p><p>{% trans 'society_right_1' %}</p><p>{% trans 'society_right_2_template' %}</p><div style="text-align: center"><h3>{% trans 'force_majeure_title' %}</h3></div><p>{% trans 'legislation_changes' %}</p><p>{% trans 'force_majeure_circumstances_template' %}</p><p>{% trans 'force_majeure_cases' %}</p><p>{% trans 'force_majeure_notification' %}</p><div style="text-align: center"><h3>{% trans 'other_conditions_title' %}</h3></div><p>{% trans 'site_definition_extended_template' %}</p><p>{% trans 'governing_relations_template' %}</p><p>{% trans 'amendments_procedure_template' %}</p><div class="signature"><p style="margin-top: 40px;"><strong>{% trans 'agreement_signature_template' %}</strong></p><p>{% trans 'signature_fields' %}</p></div></div><style>.digital-document {padding: 20px;white-space: pre-wrap;}.subheader {padding-bottom: 20px;}.signature {padding-top: 20px;}</style>`
export const context = `<div class="digital-document"><div style="text-align: center"><h1 class="header">{% trans 'OFFER_TITLE' %}</h1><p class="subheader">{% trans 'offer_template_subtitle' %}</p></div><p>{% trans 'offer_template_intro_1' %} {{coop.chairman.last_name}} {{coop.chairman.first_name}} {{coop.chairman.middle_name}}{% trans 'offer_template_intro_2' %}</p><p>{% trans 'offer_template_legal_basis' %} {{ blagorost_program.protocol_number }} {% trans 'from' %} {{ blagorost_program.protocol_date }}{% trans 'offer_template_legal_basis_2' %}</p><p>{% trans 'offer_template_acceptance' %} {{vars.website}} {% trans 'offer_template_acceptance_2' %}</p><div style="text-align: center"><h3>{% trans 'terms_title' %}</h3></div><p><strong>{% trans 'platform_term' %}</strong> - {% trans 'platform_definition' %}</p><p><strong>{% trans 'property_term' %}</strong> - {% trans 'property_definition_template' %}</p><p><strong>{% trans 'money_term' %}</strong> - {% trans 'money_definition_template' %}</p><p><strong>{% trans 'participant_term' %}</strong> - {{vars.short_abbr}} "{{vars.name}}"{% trans 'participant_definition_template' %}</p><p><strong>{% trans 'site_term' %}</strong> - {{vars.website}}{% trans 'site_definition_template' %}</p><p><strong>{% trans 'share_contribution_term' %}</strong> - {% trans 'share_contribution_definition' %}</p><p><strong>{% trans 'share_capital_term' %}</strong> - {% trans 'share_capital_definition' %}</p><p><strong>{% trans 'personal_cabinet_term' %}</strong> - {% trans 'personal_cabinet_definition_template' %}</p><p><strong>{% trans 'personal_account_term' %}</strong> - {% trans 'personal_account_definition_template' %}</p><div style="text-align: center"><h3>{% trans 'goals_title' %}</h3></div><p>{% trans 'goals_intro_template' %}</p><p>{% trans 'goal_1' %}</p><p>{% trans 'goal_2' %}</p><p>{% trans 'tasks_intro' %}</p><p>{% trans 'task_1' %}</p><p>{% trans 'task_2' %}</p><div style="text-align: center"><h3>{% trans 'economic_mechanism_title' %}</h3></div><p>{% trans 'economic_mechanism_intro_template' %}</p><p>{% trans 'participation_proportion' %}</p><p>{% trans 'share_equivalence_template' %}</p><p>{% trans 'society_notification_template' %}</p><p>{% trans 'participant_rights_contributions' %}</p><p>{% trans 'minimum_contribution' %}</p><p>{% trans 'property_contributions' %}</p><p>{% trans 'money_contribution_procedure' %}</p><p>{% trans 'property_contribution_procedure_template' %}</p><p>{% trans 'council_consideration_delay' %}</p><p>{% trans 'council_consideration_time' %}</p><p>{% trans 'property_acceptance_act' %}</p><div style="text-align: center"><h3>{% trans 'return_mechanism_title' %}</h3></div><p>{% trans 'return_source' %}</p><p>{% trans 'economic_effect_template' %}</p><p>{% trans 'participant_return_right' %}</p><p>{% trans 'society_return_notification_template' %}</p><p>{% trans 'return_application' %}</p><p>{% trans 'return_procedure' %}</p><p>{% trans 'return_to_wallet' %}</p><p>{% trans 'exit_return_procedure_template' %}</p><p>{% trans 'exit_consideration_time_template' %}</p><div style="text-align: center"><h3>{% trans 'rights_obligations_title' %}</h3></div><p><strong>{% trans 'participant_rights_title' %}</strong></p><p>{% trans 'participant_right_1' %}</p><p>{% trans 'participant_right_2' %}</p><p>{% trans 'participant_right_3' %}</p><p>{% trans 'participant_right_4' %}</p><p><strong>{% trans 'participant_obligations_title' %}</strong></p><p>{% trans 'participant_obligation_1' %}</p><p>{% trans 'participant_obligation_2' %}</p><p><strong>{% trans 'society_obligations_title' %}</strong></p><p>{% trans 'society_obligation_1_template' %}</p><p>{% trans 'society_obligation_2_template' %}</p><p><strong>{% trans 'society_rights_title' %}</strong></p><p>{% trans 'society_right_1' %}</p><p>{% trans 'society_right_2_template' %}</p><div style="text-align: center"><h3>{% trans 'force_majeure_title' %}</h3></div><p>{% trans 'legislation_changes' %}</p><p>{% trans 'force_majeure_circumstances_template' %}</p><p>{% trans 'force_majeure_cases' %}</p><p>{% trans 'force_majeure_notification' %}</p><div style="text-align: center"><h3>{% trans 'other_conditions_title' %}</h3></div><p>{% trans 'site_definition_extended_template' %}</p><p>{% trans 'governing_relations_template' %}</p><p>{% trans 'amendments_procedure_template' %}</p><div class="signature"><p style="margin-top: 40px;"><strong>{% trans 'agreement_signature_template' %}</strong></p><p>{% trans 'signature_fields' %}</p></div></div><style>.digital-document {padding: 20px;white-space: pre-wrap;}.subheader {padding-bottom: 20px;}.signature {padding-top: 20px;}</style>`
export const translations = {
ru: {
@@ -127,7 +127,7 @@ export const exampleData = {
short_abbr: 'ПК',
website: 'цифровой-кооператив.рф',
},
blagorost_provision: {
blagorost_program: {
protocol_number: '01-12-2024',
protocol_date: '01 декабря 2024 г.',
},
@@ -26,8 +26,8 @@ export * as ReturnByMoneyDecision from './901.ReturnByMoneyDecision'
export * as GeneratorProgramTemplate from './994.GeneratorProgramTemplate'
export * as GeneratorOfferTemplate from './995.GeneratorOfferTemplate'
export * as GeneratorOffer from './996.GeneratorOffer'
export * as CapitalizationAgreement from './1000.CapitalizationAgreement'
export * as BlagorostProvision from './998.BlagorostProvision'
export * as BlagorostOffer from './1000.BlagorostOffer'
export * as BlagorostProgramTemplate from './998.BlagorostProgramTemplate'
export * as BlagorostOfferTemplate from './999.BlagorostOfferTemplate'
export * as GenerationAgreementTemplate from './997.GenerationAgreementTemplate'
export * as GenerationAgreement from './1001.GenerationAgreement'
@@ -4,40 +4,40 @@ overview: Создание гибкого конструктора програ
todos:
- id: backend-interfaces
content: Создать новые интерфейсы для программ регистрации в agreement-config.interface.ts
status: in_progress
status: completed
- id: backend-programs-config
content: Создать конфигурацию программ registration-programs.config.ts с программами для voskhod
status: pending
status: completed
- id: backend-agreement-service
content: Обновить AgreementConfigurationService с методами работы с программами
status: pending
status: completed
- id: backend-documents-service
content: Добавить поддержку program_key в RegistrationDocumentsService
status: pending
status: completed
- id: backend-dto
content: Обновить DTO для добавления program_key и создать RegistrationConfigDTO
status: pending
status: completed
- id: backend-resolver
content: Добавить query getRegistrationConfig в SystemResolver
status: pending
status: completed
- id: frontend-store
content: Обновить Registrator store для хранения selectedProgramKey и добавить шаг SelectProgram
status: pending
status: completed
- id: frontend-select-component
content: Создать компонент SelectProgram.vue для выбора программы
status: pending
status: completed
- id: frontend-conditional-rendering
content: Добавить условный рендеринг шага SelectProgram в SignUp.vue
status: pending
status: completed
- id: frontend-pass-program-key
content: Обновить вызов generateRegistrationDocuments для передачи program_key
status: pending
status: completed
- id: sdk-update
content: Обновить SDK мутацию и добавить query getRegistrationConfig
status: pending
status: completed
- id: add-generator-agreement
content: Добавить соглашение generator_offer в registration-agreements.config.ts
status: pending
status: completed
---
# План реализации конструктора программ регистрации
@@ -132,7 +132,7 @@ export const REGISTRATION_PROGRAMS_CONFIG: ICooperativeRegistrationPrograms[] =
title: 'Программа Капитализация',
description: 'Участвовать в производстве Кооперативной Экономики через вклад имуществом или денег в систему. Минимальный взнос 100 000 руб в течение 14 дней.',
applicable_account_types: [AccountType.individual, AccountType.entrepreneur],
agreement_ids: ['capitalization_agreement'],
agreement_ids: ['blagorost_offer'],
order: 2,
},
],
@@ -214,7 +214,7 @@ getAgreementsForAccountType(
// Базовые соглашения (всегда включаются)
const baseAgreements = REGISTRATION_AGREEMENTS_CONFIG.agreements
.filter(a => a.applicable_account_types.includes(accountType))
.filter(a => a.id !== 'capitalization_agreement' && a.id !== 'generator_offer');
.filter(a => a.id !== 'blagorost_offer' && a.id !== 'generator_offer');
// Если указана программа - добавляем её соглашения
if (programKey && coopname) {
@@ -224,7 +224,7 @@ getAgreementsForAccountType(
// Старая логика для обратной совместимости (voskhod без program_key)
if (coopname === 'voskhod' && accountType === AccountType.individual) {
const capitalization = this.getAgreementById('capitalization_agreement');
const capitalization = this.getAgreementById('blagorost_offer');
if (capitalization) {
return [...baseAgreements, capitalization].sort((a, b) => a.order - b.order);
}
@@ -533,5 +533,5 @@ export const getRegistrationConfig = {
## Миграция и обратная совместимость
- Для всех кооперативов кроме voskhod ничего не меняется
- Для voskhod: если не передан `program_key`, используется старая логика (capitalization_agreement для физиков)
- Для voskhod: если не передан `program_key`, используется старая логика (blagorost_offer для физиков)
- После развертывания можно убрать хардкод для voskhod из `AgreementConfigurationService`
@@ -27,7 +27,7 @@ export const useCapitalOnboarding = () => {
const stepToRegistryId: Record<string, number> = {
'generator_program_template': 994,
'generation_agreement_template': 997,
'blagorost_provision': 998,
'blagorost_program': 998,
'blagorost_offer_template': 999,
};
@@ -100,7 +100,7 @@ export const useCapitalOnboarding = () => {
hash: typeof state?.onboarding_generation_agreement_template_hash === 'string' && state.onboarding_generation_agreement_template_hash ? state.onboarding_generation_agreement_template_hash : null,
},
{
id: 'blagorost_provision',
id: 'blagorost_program',
title: 'Положение о ЦПП «БЛАГОРОСТ»',
description: 'Утверждение Положения о целевой потребительской программе «БЛАГОРОСТ»',
question: 'О утверждении Положения о целевой потребительской программе «БЛАГОРОСТ»',
@@ -121,7 +121,7 @@ export const useCapitalOnboarding = () => {
status: state?.blagorost_offer_template_done ? 'completed' :
state?.onboarding_blagorost_offer_template_hash ? 'in_progress' : 'pending',
hash: typeof state?.onboarding_blagorost_offer_template_hash === 'string' && state.onboarding_blagorost_offer_template_hash ? state.onboarding_blagorost_offer_template_hash : null,
depends_on: ['blagorost_provision'], // Зависит от утверждения положения
depends_on: ['blagorost_program'], // Зависит от утверждения положения
},
];
});
@@ -8,7 +8,7 @@ export const useBlagorostPresets = (): IDocumentPreset[] => {
return [
{
id: 'blagorost_provision',
id: 'blagorost_program',
registry_id: 998,
title: 'Положение о ЦПП «БЛАГОРОСТ»',
description: 'Утверждение Положения о целевой потребительской программе «БЛАГОРОСТ»',
@@ -8,6 +8,7 @@ import type { IGenerateRegistrationDocumentsInput, IGenerateRegistrationDocument
async function generateRegistrationDocuments(
data: IGenerateRegistrationDocumentsInput
): Promise<IGenerateRegistrationDocumentsOutput> {
const { [Mutations.Registration.GenerateRegistrationDocuments.name]: output } =
await client.Mutation(Mutations.Registration.GenerateRegistrationDocuments.mutation, {
variables: {
@@ -37,12 +37,14 @@ export const useRegistrationStore = defineStore(namespace, () => {
const loadRegistrationDocuments = async (
coopname: string,
username: string,
accountType: Zeus.AccountType
accountType: Zeus.AccountType,
programKey?: string
): Promise<IRegistrationDocument[]> => {
const output = await api.generateRegistrationDocuments({
coopname,
username,
account_type: accountType,
program_key: programKey,
})
registrationDocuments.value = output.documents.map((doc) => ({
@@ -118,6 +118,8 @@ export const useRegistratorStore = defineStore(
role: 'user',
email: '',
selectedBranch: '',
selectedProgramKey: '',
requiresProgramSelection: false,
account: structuredClone(initialAccountState),
userData: structuredClone(initialUserDataState),
signature: '',
@@ -135,6 +137,7 @@ export const useRegistratorStore = defineStore(
const stepNames = [
'EmailInput',
'SetUserData',
'SelectProgram',
'GenerateAccount',
'SelectBranch',
'ReadStatement',
@@ -160,7 +163,11 @@ export const useRegistratorStore = defineStore(
);
const filteredSteps = computed(() =>
stepNames.filter((step) => step !== 'SelectBranch' || isBranched.value),
stepNames.filter((step) => {
if (step === 'SelectBranch' && !isBranched.value) return false;
if (step === 'SelectProgram' && !state.requiresProgramSelection) return false;
return true;
}),
);
const isStepDone = (stepName: StepName) => {
@@ -207,6 +214,8 @@ export const useRegistratorStore = defineStore(
const clearUserData = () => {
state.step = 1;
state.selectedBranch = '';
state.selectedProgramKey = '';
state.requiresProgramSelection = false;
state.email = '';
state.account = structuredClone(initialAccountState);
state.agreements = structuredClone(initialAgreementsState);
@@ -58,7 +58,8 @@ export function useCreateUser() {
* Генерирует все документы регистрации с бэкенда через Registration store
* Использует SDK для вызова generateRegistrationDocuments
*/
async function generateAllRegistrationDocuments() {
async function generateAllRegistrationDocuments(programKey?: string) {
const accountType = store.userData.type;
if (!accountType) {
throw new Error('Тип аккаунта не определён');
@@ -80,7 +81,8 @@ export function useCreateUser() {
await registrationStore.loadRegistrationDocuments(
info.coopname,
store.account.username,
zeusAccountType
zeusAccountType,
programKey || undefined
);
}
@@ -127,9 +129,10 @@ export function useCreateUser() {
const privacyDoc = registrationStore.getDocumentById('privacy_agreement');
const signatureDoc = registrationStore.getDocumentById('signature_agreement');
const userDoc = registrationStore.getDocumentById('user_agreement');
const capitalizationDoc = registrationStore.getDocumentById('capitalization_agreement');
const capitalizationDoc = registrationStore.getDocumentById('blagorost_offer');
const generatorDoc = registrationStore.getDocumentById('generator_offer');
const data: ISendStatement & { capitalization_agreement?: IDocument } = {
const data: ISendStatement & { blagorost_offer?: IDocument; generator_offer?: IDocument; program_key?: string } = {
username: store.account.username,
braname: store.selectedBranch,
statement: store.statement,
@@ -137,11 +140,17 @@ export function useCreateUser() {
privacy_agreement: privacyDoc?.signed_document || store.privacyAgreement,
signature_agreement: signatureDoc?.signed_document || store.signatureAgreement,
user_agreement: userDoc?.signed_document || store.userAgreement,
program_key: registratorStore.state.selectedProgramKey || undefined,
};
// Добавляем capitalization_agreement если есть
// Добавляем blagorost_offer если есть
if (capitalizationDoc?.signed_document) {
data.capitalization_agreement = capitalizationDoc.signed_document;
data.blagorost_offer = capitalizationDoc.signed_document;
}
// Добавляем generator_offer если есть
if (generatorDoc?.signed_document) {
data.generator_offer = generatorDoc.signed_document;
}
await api.sendStatement(data);
@@ -79,7 +79,7 @@ const loadDocuments = async (): Promise<void> => {
loadingText.value = 'Генерируем документы для подписи...'
// Генерируем все документы регистрации с бэкенда (они уже содержат HTML для отображения)
await generateAllRegistrationDocuments()
await generateAllRegistrationDocuments(registratorStore.state.selectedProgramKey)
loadingText.value = 'Заполняем заявление...'
@@ -97,10 +97,11 @@ const loadDocuments = async (): Promise<void> => {
}
const back = () => {
if (registratorStore.isBranched)
registratorStore.goTo('SelectBranch')
else
registratorStore.goTo('GenerateAccount')
if (registratorStore.isBranched) {
registratorStore.goTo('SelectBranch');
} else {
registratorStore.goTo('GenerateAccount');
}
}
onMounted(() => {
@@ -0,0 +1,121 @@
<template lang="pug">
div
q-step(
:name='registratorStore.steps.SelectProgram',
title='Выберите программу участия',
:done='registratorStore.isStepDone("SelectProgram")'
)
div(v-if='isLoading').full-width.text-center.q-mt-lg.q-mb-lg
Loader(text='Загружаем доступные программы...')
div(v-else-if='programs.length > 0')
p.text-body1.q-mb-md Выберите программу, в которой вы хотите участвовать:
q-list.q-mt-md(separator)
q-item(
v-for='program in programs',
:key='program.key',
clickable,
v-ripple,
:active='registratorStore.state.selectedProgramKey === program.key',
@click='selectProgram(program.key)'
)
q-item-section
q-item-label.text-h6 {{ program.title }}
q-item-label(caption).q-mt-sm.text-body2 {{ program.description }}
q-item-label(v-if='program.requirements', caption).q-mt-xs.text-weight-medium.text-primary
| {{ program.requirements }}
q-item-section(side, top)
q-radio(
:model-value='registratorStore.state.selectedProgramKey',
:val='program.key',
@update:model-value='selectProgram(program.key)',
color='primary'
)
div(v-else).text-center.q-mt-lg
p.text-body1 Доступных программ не найдено
div.q-mt-lg
q-btn.col-md-6.col-xs-12(flat, @click='registratorStore.prev()')
i.fa.fa-arrow-left
span.q-ml-md назад
q-btn.q-mt-lg.q-mb-lg(
color='primary',
label='Продолжить',
:disabled='!registratorStore.state.selectedProgramKey',
@click='registratorStore.next()'
)
</template>
<script lang="ts" setup>
import { ref, onMounted, watch } from 'vue';
import { useRegistratorStore } from 'src/entities/Registrator';
import { useSystemStore } from 'src/entities/System/model';
import { Loader } from 'src/shared/ui/Loader';
import { FailAlert } from 'src/shared/api';
import { client } from 'src/shared/api/client';
import { Queries } from '@coopenomics/sdk';
const registratorStore = useRegistratorStore();
const systemStore = useSystemStore();
const isLoading = ref(false);
const programs = ref<any[]>([]);
const loadPrograms = async () => {
try {
isLoading.value = true;
const accountType = registratorStore.state.userData.type;
if (!accountType) {
console.error('Account type not selected');
return;
}
const { [Queries.System.GetRegistrationConfig.name]: config } = await client.Query(
Queries.System.GetRegistrationConfig.query,
{
variables: {
coopname: systemStore.info.coopname,
account_type: accountType,
},
}
);
programs.value = config.programs || [];
// Если программа только одна - автоматически выбираем её и переходим дальше
if (programs.value.length === 1) {
registratorStore.state.selectedProgramKey = programs.value[0].key;
registratorStore.next();
} else if (programs.value.length === 0) {
// Если программ нет - просто переходим дальше
registratorStore.next();
}
} catch (e: any) {
console.error('Error loading programs:', e);
FailAlert(e);
} finally {
isLoading.value = false;
}
};
const selectProgram = (key: string) => {
registratorStore.state.selectedProgramKey = key;
};
onMounted(() => {
if (registratorStore.state.step === registratorStore.steps.SelectProgram) {
loadPrograms();
}
});
watch(() => registratorStore.state.step, (value: number) => {
if (value === registratorStore.steps.SelectProgram) {
loadPrograms();
}
});
</script>
@@ -39,9 +39,13 @@ div
import { ref } from 'vue';
import { UserDataForm } from 'src/shared/ui/UserDataForm/UserDataForm';
import { useRegistratorStore } from 'src/entities/Registrator';
import { useSystemStore } from 'src/entities/System/model';
import { StaticPrivacyDialog } from 'src/features/Agreementer/StaticPrivacyDialog';
import { Queries } from '@coopenomics/sdk';
import { client } from 'src/shared/api/client';
const store = useRegistratorStore();
const systemStore = useSystemStore();
// Отслеживание выбран ли тип аккаунта
const isAccountTypeSelected = ref<boolean>(
@@ -51,18 +55,43 @@ const isAccountTypeSelected = ref<boolean>(
);
// Обработчики событий от UserDataForm
const onTypeSelected = () =>
// type: 'individual' | 'entrepreneur' | 'organization',
{
isAccountTypeSelected.value = true;
// Можно использовать type для дополнительной логики в будущем
// const selectedType = type;
};
const onTypeSelected = async () => {
isAccountTypeSelected.value = true;
// Проверяем, нужен ли выбор программы для данного типа аккаунта
try {
const accountType = store.state.userData.type;
if (accountType) {
const { [Queries.System.GetRegistrationConfig.name]: config } = await client.Query(
Queries.System.GetRegistrationConfig.query,
{
variables: {
coopname: systemStore.info.coopname,
account_type: accountType,
},
}
);
store.state.requiresProgramSelection = config.requires_selection && config.programs.length > 0;
// Если программа только одна - автоматически выбираем её
if (config.programs.length === 1) {
store.state.selectedProgramKey = config.programs[0].key;
}
}
} catch (e) {
console.error('Error checking program selection requirement:', e);
store.state.requiresProgramSelection = false;
}
};
const onTypeCleared = () => {
isAccountTypeSelected.value = false;
// Сбрасываем чекбокс согласия при сбросе типа
store.state.agreements.condidential = false;
// Сбрасываем выбор программы
store.state.selectedProgramKey = '';
store.state.requiresProgramSelection = false;
};
const setData = (userDataForm: any) => {
@@ -13,6 +13,8 @@ div
SetUserData
SelectProgram
GenerateAccount
SelectBranch(v-if='isBranched')
@@ -33,6 +35,7 @@ import { watch, onMounted, onBeforeUnmount, computed } from 'vue';
import EmailInput from './EmailInput.vue';
import GenerateAccount from './GenerateAccount.vue';
import SetUserData from './SetUserData.vue';
import SelectProgram from './SelectProgram.vue';
import SignStatement from './SignStatement.vue';
import ReadStatement from './ReadStatement.vue';
import PayInitial from './PayInitial.vue';
@@ -30,7 +30,7 @@ export class Factory extends DocFactory<BlagorostOffer.Action> {
const common_user = this.getCommonUser(userData)
// Проверяем наличие данных протоколов, утвердивших предыдущие документы
if (!vars.blagorost_provision?.protocol_number || !vars.blagorost_provision?.protocol_day_month_year) {
if (!vars.blagorost_program?.protocol_number || !vars.blagorost_program?.protocol_day_month_year) {
throw new Error('Данные протокола об утверждении Положения о ЦПП «БЛАГОРОСТ» не найдены. Сначала утвердите Положение и сохраните данные протокола.')
}
@@ -38,9 +38,9 @@ export class Factory extends DocFactory<BlagorostOffer.Action> {
throw new Error('Данные протокола об утверждении шаблона публичной оферты ЦПП «БЛАГОРОСТ» не найдены. Сначала утвердите шаблон оферты и сохраните данные протокола.')
}
const blagorost_provision = {
protocol_number: vars.blagorost_provision.protocol_number,
protocol_date: vars.blagorost_provision.protocol_day_month_year,
const blagorost_program = {
protocol_number: vars.blagorost_program.protocol_number,
protocol_date: vars.blagorost_program.protocol_day_month_year,
}
const blagorost_offer_template = {
@@ -54,7 +54,7 @@ export class Factory extends DocFactory<BlagorostOffer.Action> {
coop,
vars,
common_user,
blagorost_provision,
blagorost_program,
blagorost_offer_template,
}
@@ -28,12 +28,12 @@ export class Factory extends DocFactory<GeneratorOfferTemplate.Action> {
const common_user = this.getCommonUser(user)
// Проверяем наличие данных протокола, утвердившего генерационную программу
if (!vars.generation_program?.protocol_number || !vars.generation_program?.protocol_day_month_year) {
if (!vars.generator_program?.protocol_number || !vars.generator_program?.protocol_day_month_year) {
throw new Error('Данные протокола об утверждении генерационной программы не найдены. Сначала утвердите генерационную программу и сохраните данные протокола.')
}
// Проверяем наличие данных протокола, утвердившего шаблон оферты генератора
if (!vars.generation_offer_template?.protocol_number || !vars.generation_offer_template?.protocol_day_month_year) {
if (!vars.generator_offer_template?.protocol_number || !vars.generator_offer_template?.protocol_day_month_year) {
throw new Error('Данные протокола об утверждении шаблона оферты генератора не найдены. Сначала утвердите шаблон оферты генератора и сохраните данные протокола.')
}
@@ -28,12 +28,12 @@ export class Factory extends DocFactory<GeneratorOffer.Action> {
const common_user = this.getCommonUser(user)
// Проверяем наличие данных протокола, утвердившего генерационную программу
if (!vars.generation_program?.protocol_number || !vars.generation_program?.protocol_day_month_year) {
if (!vars.generator_program?.protocol_number || !vars.generator_program?.protocol_day_month_year) {
throw new Error('Данные протокола об утверждении генерационной программы не найдены. Сначала утвердите генерационную программу и сохраните данные протокола.')
}
// Проверяем наличие данных протокола, утвердившего шаблон оферты генератора
if (!vars.generation_offer_template?.protocol_number || !vars.generation_offer_template?.protocol_day_month_year) {
if (!vars.generator_offer_template?.protocol_number || !vars.generator_offer_template?.protocol_day_month_year) {
throw new Error('Данные протокола об утверждении шаблона оферты генератора не найдены. Сначала утвердите шаблон оферты генератора и сохраните данные протокола.')
}
@@ -1,31 +1,31 @@
import { DraftContract } from 'cooptypes'
import { BlagorostProvision } from '../Templates'
import { BlagorostProgramTemplate } from '../Templates'
import { DocFactory } from '../Factory'
import type { IGeneratedDocument, IGenerationOptions, IMetaDocument, ITemplate } from '../Interfaces'
import type { MongoDBConnector } from '../Services/Databazor'
export { BlagorostProvision as Template } from '../Templates'
export { BlagorostProgramTemplate as Template } from '../Templates'
export class Factory extends DocFactory<BlagorostProvision.Action> {
export class Factory extends DocFactory<BlagorostProgramTemplate.Action> {
constructor(storage: MongoDBConnector) {
super(storage)
}
async generateDocument(data: BlagorostProvision.Action, options?: IGenerationOptions): Promise<IGeneratedDocument> {
let template: ITemplate<BlagorostProvision.Model>
async generateDocument(data: BlagorostProgramTemplate.Action, options?: IGenerationOptions): Promise<IGeneratedDocument> {
let template: ITemplate<BlagorostProgramTemplate.Model>
if (process.env.SOURCE === 'local') {
template = BlagorostProvision.Template
template = BlagorostProgramTemplate.Template
}
else {
template = await this.getTemplate(DraftContract.contractName.production, BlagorostProvision.registry_id, data.block_num)
template = await this.getTemplate(DraftContract.contractName.production, BlagorostProgramTemplate.registry_id, data.block_num)
}
const meta: IMetaDocument = await this.getMeta({ title: template.title, ...data })
const vars = await this.getVars(data.coopname)
const combinedData: BlagorostProvision.Model = { meta, vars }
const combinedData: BlagorostProgramTemplate.Model = { meta, vars }
await this.validate(combinedData, template.model)
@@ -27,20 +27,20 @@ export class Factory extends DocFactory<BlagorostOfferTemplate.Action> {
const coop = await this.getCooperative(data.coopname)
// Проверяем наличие данных протокола, утвердившего Положение (документ 998)
if (!vars.blagorost_provision?.protocol_number || !vars.blagorost_provision?.protocol_day_month_year) {
if (!vars.blagorost_program?.protocol_number || !vars.blagorost_program?.protocol_day_month_year) {
throw new Error('Данные протокола об утверждении Положения о ЦПП «БЛАГОРОСТ» не найдены. Сначала утвердите Положение и сохраните данные протокола.')
}
const blagorost_provision = {
protocol_number: vars.blagorost_provision.protocol_number,
protocol_date: vars.blagorost_provision.protocol_day_month_year,
const blagorost_program = {
protocol_number: vars.blagorost_program.protocol_number,
protocol_date: vars.blagorost_program.protocol_day_month_year,
}
const combinedData: BlagorostOfferTemplate.Model = {
meta,
coop,
vars,
blagorost_provision,
blagorost_program,
}
await this.validate(combinedData, template.model)
+2 -2
View File
@@ -22,7 +22,7 @@ export * as ReturnByAssetAct from './802.ReturnByAssetAct'
export * as ReturnByMoney from './900.ReturnByMoney'
export * as ReturnByMoneyDecision from './901.ReturnByMoneyDecision'
export * as BlagorostOffer from './1000.CapitalizationAgreement'
export * as BlagorostOffer from './1000.BlagorostOffer'
export * as GenerationAgreement from './1001.GenerationAgreement'
export * as ProjectGenerationAgreement from './1002.ProjectGenerationAgreement'
export * as ComponentGenerationAgreement from './1003.ComponentGenerationAgreement'
@@ -69,6 +69,6 @@ export * as AnnualGeneralMeetingDecision from './304.AnnualGeneralMeetingDecisio
export * as GeneratorProgramTemplate from './994.GeneratorProgramTemplate'
export * as GeneratorOfferTemplate from './995.GeneratorOfferTemplate'
export * as GeneratorOffer from './996.GeneratorOffer'
export * as BlagorostProvision from './998.BlagorostProvision'
export * as BlagorostProgramTemplate from './998.BlagorostProgramTemplate'
export * as GenerationAgreementTemplate from './997.GenerationAgreementTemplate'
export * as BlagorostOfferTemplate from './999.BlagorostOfferTemplate'
+6 -6
View File
@@ -87,7 +87,7 @@ export const VarsSchema: JSONSchemaType<IVars> = {
additionalProperties: true,
nullable: true,
},
blagorost_provision: {
blagorost_program: {
type: 'object',
nullable: true,
properties: {
@@ -107,7 +107,7 @@ export const VarsSchema: JSONSchemaType<IVars> = {
required: ['protocol_number', 'protocol_day_month_year'],
additionalProperties: true,
},
generation_program: {
generator_program: {
type: 'object',
nullable: true,
properties: {
@@ -117,7 +117,7 @@ export const VarsSchema: JSONSchemaType<IVars> = {
required: ['protocol_number', 'protocol_day_month_year'],
additionalProperties: true,
},
generation_offer_template: {
generator_offer_template: {
type: 'object',
nullable: true,
properties: {
@@ -148,10 +148,10 @@ export const VarsSchema: JSONSchemaType<IVars> = {
// 'participant_application', //not_required
// 'coopenomics_agreement', //not_required
// 'generation_agreement_template', //not_required
// 'blagorost_provision', //not_required
// 'blagorost_program', //not_required
// 'blagorost_offer_template', //not_required
// 'generation_program', //not_required
// 'generation_offer_template', //not_required
// 'generator_program', //not_required
// 'generator_offer_template', //not_required
],
additionalProperties: true,
}
@@ -4,10 +4,10 @@ import type { IMetaDocument, ITemplate } from '../Interfaces'
import { IMetaJSONSchema } from '../Schema/MetaSchema'
import { CommonUserSchema, CooperativeSchema, VarsSchema } from '../Schema'
export const registry_id = Cooperative.Registry.CapitalizationAgreement.registry_id
export const registry_id = Cooperative.Registry.BlagorostOffer.registry_id
// Модель действия для генерации
export type Action = Cooperative.Registry.CapitalizationAgreement.Action
export type Action = Cooperative.Registry.BlagorostOffer.Action
// Модель данных - используем полную модель с дополнительными полями
export interface Model {
@@ -15,7 +15,7 @@ export interface Model {
coop: Cooperative.Model.ICooperativeData
vars: Cooperative.Model.IVars
common_user: Cooperative.Model.ICommonUser
blagorost_provision: {
blagorost_program: {
protocol_number: string
protocol_date: string
}
@@ -33,7 +33,7 @@ export const Schema: JSONSchemaType<Model> = {
coop: CooperativeSchema,
vars: VarsSchema,
common_user: CommonUserSchema,
blagorost_provision: {
blagorost_program: {
type: 'object',
properties: {
protocol_number: { type: 'string' },
@@ -55,9 +55,9 @@ export const Schema: JSONSchemaType<Model> = {
} as any
export const Template: ITemplate<Model> = {
title: Cooperative.Registry.CapitalizationAgreement.title,
description: Cooperative.Registry.CapitalizationAgreement.description,
title: Cooperative.Registry.BlagorostOffer.title,
description: Cooperative.Registry.BlagorostOffer.description,
model: Schema,
context: Cooperative.Registry.CapitalizationAgreement.context,
translations: Cooperative.Registry.CapitalizationAgreement.translations,
context: Cooperative.Registry.BlagorostOffer.context,
translations: Cooperative.Registry.BlagorostOffer.translations,
}
@@ -5,7 +5,7 @@ import type { IVars } from '../Models'
import { IMetaJSONSchema } from '../Schema/MetaSchema'
import { VarsSchema } from '../Schema'
export const registry_id = Cooperative.Registry.BlagorostProvision.registry_id
export const registry_id = Cooperative.Registry.BlagorostProgramTemplate.registry_id
// Модель действия для генерации
export interface Action extends IGenerate {
@@ -30,9 +30,9 @@ export const Schema: JSONSchemaType<Model> = {
}
export const Template: ITemplate<Model> = {
title: Cooperative.Registry.BlagorostProvision.title,
description: Cooperative.Registry.BlagorostProvision.description,
title: Cooperative.Registry.BlagorostProgramTemplate.title,
description: Cooperative.Registry.BlagorostProgramTemplate.description,
model: Schema,
context: Cooperative.Registry.BlagorostProvision.context,
translations: Cooperative.Registry.BlagorostProvision.translations,
context: Cooperative.Registry.BlagorostProgramTemplate.context,
translations: Cooperative.Registry.BlagorostProgramTemplate.translations,
}
@@ -19,7 +19,7 @@ export const Schema: JSONSchemaType<Model> = {
meta: IMetaJSONSchema,
coop: CooperativeSchema,
vars: VarsSchema,
blagorost_provision: {
blagorost_program: {
type: 'object',
properties: {
protocol_number: { type: 'string' },
@@ -28,7 +28,7 @@ export const Schema: JSONSchemaType<Model> = {
required: ['protocol_number', 'protocol_date'],
},
},
required: ['meta', 'coop', 'vars', 'blagorost_provision'],
required: ['meta', 'coop', 'vars', 'blagorost_program'],
additionalProperties: true,
}
+2 -2
View File
@@ -26,10 +26,10 @@ export * as GeneratorOfferTemplate from './995.GeneratorOfferTemplate'
export * as GeneratorOffer from './996.GeneratorOffer'
// Документы ЦПП БЛАГОРОСТ
export * as BlagorostProvision from './998.BlagorostProvision'
export * as BlagorostProgramTemplate from './998.BlagorostProgramTemplate'
export * as GenerationAgreementTemplate from './997.GenerationAgreementTemplate'
export * as BlagorostOfferTemplate from './999.BlagorostOfferTemplate'
export * as BlagorostOffer from './1000.CapitalizationAgreement'
export * as BlagorostOffer from './1000.BlagorostOffer'
export * as GenerationAgreement from './1001.GenerationAgreement'
export * as ProjectGenerationAgreement from './1002.ProjectGenerationAgreement'
export * as ComponentGenerationAgreement from './1003.ComponentGenerationAgreement'
+3 -3
View File
@@ -61,13 +61,13 @@ import * as AnnualGeneralMeetingNotification from './302.AnnualGeneralMeetingNot
import * as AnnualGeneralMeetingVotingBallot from './303.AnnualGeneralMeetingVotingBallot'
import * as AnnualGeneralMeetingDecision from './304.AnnualGeneralMeetingDecision'
import * as BlagorostProvision from './998.BlagorostProvision'
import * as BlagorostProgramTemplate from './998.BlagorostProgramTemplate'
import * as BlagorostOfferTemplate from './999.BlagorostOfferTemplate'
import * as GenerationAgreementTemplate from './997.GenerationAgreementTemplate'
import * as GeneratorOfferTemplate from './995.GeneratorOfferTemplate'
import * as GeneratorOffer from './996.GeneratorOffer'
import * as GeneratorProgramTemplate from './994.GeneratorProgramTemplate'
import * as BlagorostOffer from './1000.CapitalizationAgreement'
import * as BlagorostOffer from './1000.BlagorostOffer'
export const Registry = {
1: WalletAgreement,
@@ -99,7 +99,7 @@ export const Registry = {
995: GeneratorOfferTemplate,
996: GeneratorOffer,
997: GenerationAgreementTemplate,
998: BlagorostProvision,
998: BlagorostProgramTemplate,
999: BlagorostOfferTemplate,
1000: BlagorostOffer,
1001: GenerationAgreement,
+1 -1
View File
@@ -107,7 +107,7 @@ export class Generator implements IGenerator {
// ЦПП БЛАГОРОСТ
[Actions.GenerationAgreementTemplate.Template.registry_id]: new Actions.GenerationAgreementTemplate.Factory(this.storage), // 997
[Actions.BlagorostProvision.Template.registry_id]: new Actions.BlagorostProvision.Factory(this.storage), // 998
[Actions.BlagorostProgramTemplate.Template.registry_id]: new Actions.BlagorostProgramTemplate.Factory(this.storage), // 998
[Actions.BlagorostOfferTemplate.Template.registry_id]: new Actions.BlagorostOfferTemplate.Factory(this.storage), // 999
[Actions.BlagorostOffer.Template.registry_id]: new Actions.BlagorostOffer.Factory(this.storage), // 1000
+1 -1
View File
@@ -22,7 +22,7 @@ describe('тест генератора документов ЦПП БЛАГОР
it('генерируем шаблон публичной оферты по ЦПП БЛАГОРОСТ', async () => {
// Добавляем данные протокола для документа 998
await generator.update('vars', { coopname: 'voskhod' }, {
blagorost_provision: {
blagorost_program: {
protocol_number: '01-12-2024',
protocol_day_month_year: '01 декабря 2024 г.',
},
+1 -1
View File
@@ -283,7 +283,7 @@ export async function preLoading() {
protocol_number: '10-05-2024',
protocol_day_month_year: '10 мая 2024 г.',
},
blagorost_provision: {
blagorost_program: {
protocol_number: '10-05-2024',
protocol_day_month_year: '10 мая 2024 г.',
},
@@ -0,0 +1,29 @@
import { registrationConfigSelector } from '../../selectors/system/registrationConfigSelector'
import { $, type GraphQLTypes, type InputType, Selector } from '../../zeus/index'
export const name = 'getRegistrationConfig'
/**
* Получить конфигурацию программ регистрации для кооператива
*/
export const query = Selector('Query')({
[name]: [
{
coopname: $('coopname', 'String!'),
account_type: $('account_type', 'AccountType!'),
},
registrationConfigSelector,
],
})
export interface IInput {
/**
* @private
*/
[key: string]: unknown
coopname: string
account_type: GraphQLTypes['AccountType']
}
export type IOutput = InputType<GraphQLTypes['Query'], typeof query>
@@ -7,5 +7,8 @@ export * as GetInstallationStatus from './getInstallationStatus'
/** Получить подписки пользователя у провайдера */
export * as GetProviderSubscriptions from './getProviderSubscriptions'
/** Получить конфигурацию программ регистрации для кооператива */
export * as GetRegistrationConfig from './getRegistrationConfig'
/** Получить сводную публичную информацию о системе */
export * as GetSystemInfo from './getSystemInfo'
@@ -3,4 +3,5 @@ export * from './cooperatorAccountSelector'
export * from './currentInstanceSelector'
export * from './installationStatusSelector'
export * from './providerSubscriptionSelector'
export * from './registrationConfigSelector'
export * from './systemInfoSelector'
@@ -0,0 +1,16 @@
import { Selector } from '../../zeus/index'
export const registrationProgramSelector = Selector('RegistrationProgram')({
key: true,
title: true,
description: true,
image_url: true,
requirements: true,
applicable_account_types: true,
order: true,
})
export const registrationConfigSelector = Selector('RegistrationConfig')({
requires_selection: true,
programs: registrationProgramSelector,
})
+18 -1
View File
@@ -1346,6 +1346,9 @@ export const AllTypesProps: Record<string,any> = {
},
getProviderSubscriptionById:{
},
getRegistrationConfig:{
account_type:"AccountType"
},
getUserWebPushSubscriptions:{
data:"GetUserSubscriptionsInput"
@@ -1382,7 +1385,7 @@ export const AllTypesProps: Record<string,any> = {
contract:"GenerationAgreementSignedDocumentInput"
},
RegisterParticipantInput:{
capitalization_agreement:"SignedDigitalDocumentInput",
blagorost_offer:"SignedDigitalDocumentInput",
privacy_agreement:"SignedDigitalDocumentInput",
signature_agreement:"SignedDigitalDocumentInput",
statement:"ParticipantApplicationSignedDocumentInput",
@@ -3386,6 +3389,7 @@ export const ReturnTypes: Record<string,any> = {
getPayments:"PaginatedGatewayPaymentsPaginationResult",
getProviderSubscriptionById:"ProviderSubscription",
getProviderSubscriptions:"ProviderSubscription",
getRegistrationConfig:"RegistrationConfig",
getSystemInfo:"SystemInfo",
getUserWebPushSubscriptions:"WebPushSubscriptionDto",
getWebPushSubscriptionStats:"SubscriptionStatsDto",
@@ -3417,6 +3421,19 @@ export const ReturnTypes: Record<string,any> = {
account:"Account",
tokens:"Tokens"
},
RegistrationConfig:{
programs:"RegistrationProgram",
requires_selection:"Boolean"
},
RegistrationProgram:{
applicable_account_types:"AccountType",
description:"String",
image_url:"String",
key:"String",
order:"Int",
requirements:"String",
title:"String"
},
RepresentedBy:{
based_on:"String",
first_name:"String",
+113 -5
View File
@@ -4726,6 +4726,8 @@ export type ValueTypes = {
account_type: ValueTypes["AccountType"] | Variable<any, string>,
/** Имя кооператива */
coopname: string | Variable<any, string>,
/** Ключ выбранной программы регистрации (опционально) */
program_key?: string | undefined | null | Variable<any, string>,
/** Имя пользователя (аккаунт) */
username: string | Variable<any, string>
};
@@ -6435,6 +6437,7 @@ getPayments?: [{ data?: ValueTypes["PaymentFiltersInput"] | undefined | null | V
getProviderSubscriptionById?: [{ id: number | Variable<any, string>},ValueTypes["ProviderSubscription"]],
/** Получить подписки пользователя у провайдера */
getProviderSubscriptions?:ValueTypes["ProviderSubscription"],
getRegistrationConfig?: [{ account_type: ValueTypes["AccountType"] | Variable<any, string>, coopname: string | Variable<any, string>},ValueTypes["RegistrationConfig"]],
/** Получить сводную публичную информацию о системе */
getSystemInfo?:ValueTypes["SystemInfo"],
getUserWebPushSubscriptions?: [{ data: ValueTypes["GetUserSubscriptionsInput"] | Variable<any, string>},ValueTypes["WebPushSubscriptionDto"]],
@@ -6561,7 +6564,7 @@ searchPrivateAccounts?: [{ data: ValueTypes["SearchPrivateAccountsInput"] | Vari
/** Имя кооперативного участка */
braname?: string | undefined | null | Variable<any, string>,
/** Подписанный документ соглашения по капитализации (опционально, только если требуется) */
capitalization_agreement?: ValueTypes["SignedDigitalDocumentInput"] | undefined | null | Variable<any, string>,
blagorost_offer?: ValueTypes["SignedDigitalDocumentInput"] | undefined | null | Variable<any, string>,
/** Подписанный документ политики конфиденциальности от пайщика */
privacy_agreement: ValueTypes["SignedDigitalDocumentInput"] | Variable<any, string>,
/** Подписанный документ положения о цифровой подписи от пайщика */
@@ -6581,6 +6584,30 @@ searchPrivateAccounts?: [{ data: ValueTypes["SearchPrivateAccountsInput"] | Vari
/** Токены доступа и обновления */
tokens?:ValueTypes["Tokens"],
__typename?: boolean | `@${string}`
}>;
["RegistrationConfig"]: AliasType<{
/** Доступные программы */
programs?:ValueTypes["RegistrationProgram"],
/** Нужен ли выбор программы */
requires_selection?:boolean | `@${string}`,
__typename?: boolean | `@${string}`
}>;
["RegistrationProgram"]: AliasType<{
/** Для каких типов аккаунтов доступна программа */
applicable_account_types?:boolean | `@${string}`,
/** Описание программы */
description?:boolean | `@${string}`,
/** URL изображения (опционально) */
image_url?:boolean | `@${string}`,
/** Уникальный ключ программы */
key?:boolean | `@${string}`,
/** Порядок отображения */
order?:boolean | `@${string}`,
/** Минимальные требования для участия */
requirements?:boolean | `@${string}`,
/** Название программы для отображения */
title?:boolean | `@${string}`,
__typename?: boolean | `@${string}`
}>;
["RepresentedBy"]: AliasType<{
/** На основании чего действует */
@@ -11547,6 +11574,8 @@ export type ResolverInputTypes = {
account_type: ResolverInputTypes["AccountType"],
/** Имя кооператива */
coopname: string,
/** Ключ выбранной программы регистрации (опционально) */
program_key?: string | undefined | null,
/** Имя пользователя (аккаунт) */
username: string
};
@@ -13258,6 +13287,7 @@ getPayments?: [{ data?: ResolverInputTypes["PaymentFiltersInput"] | undefined |
getProviderSubscriptionById?: [{ id: number},ResolverInputTypes["ProviderSubscription"]],
/** Получить подписки пользователя у провайдера */
getProviderSubscriptions?:ResolverInputTypes["ProviderSubscription"],
getRegistrationConfig?: [{ account_type: ResolverInputTypes["AccountType"], coopname: string},ResolverInputTypes["RegistrationConfig"]],
/** Получить сводную публичную информацию о системе */
getSystemInfo?:ResolverInputTypes["SystemInfo"],
getUserWebPushSubscriptions?: [{ data: ResolverInputTypes["GetUserSubscriptionsInput"]},ResolverInputTypes["WebPushSubscriptionDto"]],
@@ -13384,7 +13414,7 @@ searchPrivateAccounts?: [{ data: ResolverInputTypes["SearchPrivateAccountsInput"
/** Имя кооперативного участка */
braname?: string | undefined | null,
/** Подписанный документ соглашения по капитализации (опционально, только если требуется) */
capitalization_agreement?: ResolverInputTypes["SignedDigitalDocumentInput"] | undefined | null,
blagorost_offer?: ResolverInputTypes["SignedDigitalDocumentInput"] | undefined | null,
/** Подписанный документ политики конфиденциальности от пайщика */
privacy_agreement: ResolverInputTypes["SignedDigitalDocumentInput"],
/** Подписанный документ положения о цифровой подписи от пайщика */
@@ -13404,6 +13434,30 @@ searchPrivateAccounts?: [{ data: ResolverInputTypes["SearchPrivateAccountsInput"
/** Токены доступа и обновления */
tokens?:ResolverInputTypes["Tokens"],
__typename?: boolean | `@${string}`
}>;
["RegistrationConfig"]: AliasType<{
/** Доступные программы */
programs?:ResolverInputTypes["RegistrationProgram"],
/** Нужен ли выбор программы */
requires_selection?:boolean | `@${string}`,
__typename?: boolean | `@${string}`
}>;
["RegistrationProgram"]: AliasType<{
/** Для каких типов аккаунтов доступна программа */
applicable_account_types?:boolean | `@${string}`,
/** Описание программы */
description?:boolean | `@${string}`,
/** URL изображения (опционально) */
image_url?:boolean | `@${string}`,
/** Уникальный ключ программы */
key?:boolean | `@${string}`,
/** Порядок отображения */
order?:boolean | `@${string}`,
/** Минимальные требования для участия */
requirements?:boolean | `@${string}`,
/** Название программы для отображения */
title?:boolean | `@${string}`,
__typename?: boolean | `@${string}`
}>;
["RepresentedBy"]: AliasType<{
/** На основании чего действует */
@@ -18284,6 +18338,8 @@ export type ModelTypes = {
account_type: ModelTypes["AccountType"],
/** Имя кооператива */
coopname: string,
/** Ключ выбранной программы регистрации (опционально) */
program_key?: string | undefined | null,
/** Имя пользователя (аккаунт) */
username: string
};
@@ -20131,6 +20187,8 @@ export type ModelTypes = {
getProviderSubscriptionById: ModelTypes["ProviderSubscription"],
/** Получить подписки пользователя у провайдера */
getProviderSubscriptions: Array<ModelTypes["ProviderSubscription"]>,
/** Получить конфигурацию программ регистрации для кооператива */
getRegistrationConfig: ModelTypes["RegistrationConfig"],
/** Получить сводную публичную информацию о системе */
getSystemInfo: ModelTypes["SystemInfo"],
/** Получить веб-пуш подписки пользователя */
@@ -20257,7 +20315,7 @@ export type ModelTypes = {
/** Имя кооперативного участка */
braname?: string | undefined | null,
/** Подписанный документ соглашения по капитализации (опционально, только если требуется) */
capitalization_agreement?: ModelTypes["SignedDigitalDocumentInput"] | undefined | null,
blagorost_offer?: ModelTypes["SignedDigitalDocumentInput"] | undefined | null,
/** Подписанный документ политики конфиденциальности от пайщика */
privacy_agreement: ModelTypes["SignedDigitalDocumentInput"],
/** Подписанный документ положения о цифровой подписи от пайщика */
@@ -20276,6 +20334,28 @@ export type ModelTypes = {
account: ModelTypes["Account"],
/** Токены доступа и обновления */
tokens: ModelTypes["Tokens"]
};
["RegistrationConfig"]: {
/** Доступные программы */
programs: Array<ModelTypes["RegistrationProgram"]>,
/** Нужен ли выбор программы */
requires_selection: boolean
};
["RegistrationProgram"]: {
/** Для каких типов аккаунтов доступна программа */
applicable_account_types: Array<ModelTypes["AccountType"]>,
/** Описание программы */
description: string,
/** URL изображения (опционально) */
image_url?: string | undefined | null,
/** Уникальный ключ программы */
key: string,
/** Порядок отображения */
order: number,
/** Минимальные требования для участия */
requirements?: string | undefined | null,
/** Название программы для отображения */
title: string
};
["RepresentedBy"]: {
/** На основании чего действует */
@@ -25218,6 +25298,8 @@ export type GraphQLTypes = {
account_type: GraphQLTypes["AccountType"],
/** Имя кооператива */
coopname: string,
/** Ключ выбранной программы регистрации (опционально) */
program_key?: string | undefined | null,
/** Имя пользователя (аккаунт) */
username: string
};
@@ -27146,6 +27228,8 @@ export type GraphQLTypes = {
getProviderSubscriptionById: GraphQLTypes["ProviderSubscription"],
/** Получить подписки пользователя у провайдера */
getProviderSubscriptions: Array<GraphQLTypes["ProviderSubscription"]>,
/** Получить конфигурацию программ регистрации для кооператива */
getRegistrationConfig: GraphQLTypes["RegistrationConfig"],
/** Получить сводную публичную информацию о системе */
getSystemInfo: GraphQLTypes["SystemInfo"],
/** Получить веб-пуш подписки пользователя */
@@ -27274,7 +27358,7 @@ export type GraphQLTypes = {
/** Имя кооперативного участка */
braname?: string | undefined | null,
/** Подписанный документ соглашения по капитализации (опционально, только если требуется) */
capitalization_agreement?: GraphQLTypes["SignedDigitalDocumentInput"] | undefined | null,
blagorost_offer?: GraphQLTypes["SignedDigitalDocumentInput"] | undefined | null,
/** Подписанный документ политики конфиденциальности от пайщика */
privacy_agreement: GraphQLTypes["SignedDigitalDocumentInput"],
/** Подписанный документ положения о цифровой подписи от пайщика */
@@ -27294,6 +27378,30 @@ export type GraphQLTypes = {
account: GraphQLTypes["Account"],
/** Токены доступа и обновления */
tokens: GraphQLTypes["Tokens"]
};
["RegistrationConfig"]: {
__typename: "RegistrationConfig",
/** Доступные программы */
programs: Array<GraphQLTypes["RegistrationProgram"]>,
/** Нужен ли выбор программы */
requires_selection: boolean
};
["RegistrationProgram"]: {
__typename: "RegistrationProgram",
/** Для каких типов аккаунтов доступна программа */
applicable_account_types: Array<GraphQLTypes["AccountType"]>,
/** Описание программы */
description: string,
/** URL изображения (опционально) */
image_url?: string | undefined | null,
/** Уникальный ключ программы */
key: string,
/** Порядок отображения */
order: number,
/** Минимальные требования для участия */
requirements?: string | undefined | null,
/** Название программы для отображения */
title: string
};
["RepresentedBy"]: {
__typename: "RepresentedBy",
@@ -28476,7 +28584,7 @@ export enum ApprovalStatus {
}
export enum CapitalOnboardingStep {
blagorost_offer_template = "blagorost_offer_template",
blagorost_provision = "blagorost_provision",
blagorost_program = "blagorost_program",
generation_agreement_template = "generation_agreement_template",
generator_program_template = "generator_program_template"
}