feat(capital): collect CPP document parameters in onboarding
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
import { createHash } from 'node:crypto';
|
||||
import type { DataSource } from 'typeorm';
|
||||
import mongoose from 'mongoose';
|
||||
import config from '~/config/config';
|
||||
|
||||
type MigrationLogger = {
|
||||
info: (message: string) => void;
|
||||
error: (message: string) => void;
|
||||
warn: (message: string) => void;
|
||||
};
|
||||
|
||||
const CAPITAL_DOC_REGISTRY_ID = 994;
|
||||
const TARGET_COOPNAME = 'voskhod';
|
||||
const COLLECTION_NAME = 'doc_private_data';
|
||||
|
||||
const capitalProgramPrivateData = {
|
||||
approval_protocol_number: '1',
|
||||
approval_protocol_day: '15',
|
||||
approval_protocol_month: 'января',
|
||||
approval_protocol_year: '26',
|
||||
cooperative_name: 'ВОСХОД',
|
||||
cooperative_short_name: 'ПК',
|
||||
cooperative_quoted_name: '«ВОСХОД»',
|
||||
website: 'https://цифровой-кооператив.рф',
|
||||
chairman_full_name: 'Муравьев Алексей Николаевич',
|
||||
generator_program_purpose:
|
||||
'развитию информационной экосистемы взаимодействия физических и юридических лиц, на основе международных кооперативных принципов и законодательства Российской Федерации в отношении потребительских обществ (кооперативов) под названием “Кооперативная Экономика”',
|
||||
eoap_definition:
|
||||
'информационная экосистема, интегрируемая в социально-экономическую среду Российской Федерации, состоящая из комплекса программных продуктов на базе технологии распределенного реестра, обеспечивающих широкое экономическое и социальное взаимодействие физических и юридических лиц, включая нерезидентов различных юрисдикций и организационно-правовых форм, на основе международных кооперативных принципов и законодательства Российской Федерации в отношении потребительских кооперативов (обществ) под названием “Кооперативная Экономика”',
|
||||
generator_task_goal:
|
||||
'центра привлечения и интеграции передовых инновационных цифровых разработок, а также экономических и социальных методов и решений',
|
||||
idea_unit_cost: '50',
|
||||
idea_unit_cost_words: 'пятьдесят',
|
||||
blagorost_goal_expansion:
|
||||
'вследствие увеличения количества Участников информационной кооперативной экосистемы - ЕОАП - для расширения и повышения социальной эффективности их экономического взаимодействия в некоммерческом формате',
|
||||
blagorost_task_expansion:
|
||||
'расширение участников ЕОАП - информационной кооперативной экосистемы как центра экономического взаимодействия в некоммерческом формате',
|
||||
blagorost_task_development:
|
||||
'развитие ЕОАП как центра привлечения и интеграции инновационных цифровых разработок, а также экономических и социальных методов и решений',
|
||||
return_source_description:
|
||||
'аппаратно-программная сеть узлов распределенного реестра в формате «СМЭВ+SWIFT», построенная на принципах самоорганизации и самофинансирования деятельности технологической инфраструктуры ЕОАП, обеспечивающей консенсус ее распределенных узлов по формированию базового продукта ЕОАП - полного цикла документооборота по синхронному взаимодействию пайщиков и кооперативов - участников экосистемы ЕОАП - через использование цифровых контрактов, с одновременным выполнением функций нотариата и учета финансовых и юридических событий',
|
||||
return_additional_source:
|
||||
'взносы пользователей ЕОАП, его отдельных программных продуктов и приложений, переданных Обществу или создаваемых в рамках Общества, которые интегрируются в ЕОАП',
|
||||
offer_template_number: '______',
|
||||
};
|
||||
|
||||
function stableStringify(value: unknown): string {
|
||||
if (value === null || typeof value !== 'object') return JSON.stringify(value);
|
||||
if (Array.isArray(value)) return `[${value.map((item) => stableStringify(item)).join(',')}]`;
|
||||
|
||||
const entries = Object.keys(value as Record<string, unknown>)
|
||||
.sort()
|
||||
.map((key) => {
|
||||
const v = (value as Record<string, unknown>)[key];
|
||||
return `${JSON.stringify(key)}:${stableStringify(v)}`;
|
||||
});
|
||||
|
||||
return `{${entries.join(',')}}`;
|
||||
}
|
||||
|
||||
function calculateSha256(value: unknown): string {
|
||||
return createHash('sha256').update(Buffer.from(stableStringify(value), 'utf8')).digest('hex').toUpperCase();
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'Backfill capital_program_doc_data_hash for voskhod',
|
||||
validUntil: new Date('2027-05-01T00:00:00Z'),
|
||||
|
||||
async up({ dataSource, logger }: { dataSource: DataSource; logger: MigrationLogger }): Promise<boolean> {
|
||||
if (config.coopname !== TARGET_COOPNAME) {
|
||||
logger.info(`COOPNAME=${config.coopname}: миграция предназначена только для ${TARGET_COOPNAME}, пропускаю.`);
|
||||
return true;
|
||||
}
|
||||
|
||||
const hash = calculateSha256(capitalProgramPrivateData);
|
||||
let mongo: mongoose.Connection | null = null;
|
||||
|
||||
try {
|
||||
mongo = await mongoose.createConnection(config.mongoose.url).asPromise();
|
||||
const db = mongo.db;
|
||||
const collection = db.collection(COLLECTION_NAME);
|
||||
await collection.createIndex({ hash: 1 }, { unique: true });
|
||||
await collection.updateOne(
|
||||
{ hash },
|
||||
{
|
||||
$setOnInsert: {
|
||||
hash,
|
||||
registry_id: CAPITAL_DOC_REGISTRY_ID,
|
||||
payload: capitalProgramPrivateData,
|
||||
_created_at: new Date(),
|
||||
},
|
||||
},
|
||||
{ upsert: true }
|
||||
);
|
||||
|
||||
const result = await dataSource.query(
|
||||
`UPDATE extensions
|
||||
SET config = jsonb_set(COALESCE(config, '{}'::jsonb), '{capital_program_doc_data_hash}', to_jsonb($1::text), true),
|
||||
updated_at = NOW()
|
||||
WHERE name = 'capital'`,
|
||||
[hash]
|
||||
);
|
||||
|
||||
logger.info(`capital_program_doc_data_hash=${hash} сохранён для ${TARGET_COOPNAME}; extensions updated=${result?.[1] ?? 'unknown'}.`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
logger.error(`Ошибка миграции capital_program_doc_data_hash для ${TARGET_COOPNAME}: ${message}`);
|
||||
return false;
|
||||
} finally {
|
||||
await mongo?.close();
|
||||
}
|
||||
},
|
||||
|
||||
async down({ dataSource, logger }: { dataSource: DataSource; logger: MigrationLogger }): Promise<boolean> {
|
||||
if (config.coopname !== TARGET_COOPNAME) {
|
||||
return true;
|
||||
}
|
||||
|
||||
await dataSource.query(
|
||||
`UPDATE extensions
|
||||
SET config = COALESCE(config, '{}'::jsonb) - 'capital_program_doc_data_hash',
|
||||
updated_at = NOW()
|
||||
WHERE name = 'capital'`
|
||||
);
|
||||
logger.warn('capital_program_doc_data_hash удалён из config capital; запись doc_private_data оставлена как идемпотентный audit trail.');
|
||||
return true;
|
||||
},
|
||||
};
|
||||
@@ -22,6 +22,26 @@ export class DocumentDomainService {
|
||||
) {}
|
||||
|
||||
public async generateDocument(data: GenerateDocumentDomainInterfaceWithOptions): Promise<DocumentDomainEntity> {
|
||||
const documentData = data.data as Cooperative.Document.IGenerate & {
|
||||
doc_data?: Record<string, unknown>;
|
||||
doc_data_hash?: string;
|
||||
};
|
||||
|
||||
if (documentData.doc_data) {
|
||||
const { doc_data, ...publicDocumentData } = documentData;
|
||||
const { hash } = documentData.doc_data_hash
|
||||
? { hash: documentData.doc_data_hash }
|
||||
: await this.saveDocData(doc_data, documentData.registry_id);
|
||||
|
||||
return await this.generatorInfrastructureService.generateDocument({
|
||||
...data,
|
||||
data: {
|
||||
...publicDocumentData,
|
||||
doc_data_hash: hash,
|
||||
} as Cooperative.Document.IGenerate,
|
||||
});
|
||||
}
|
||||
|
||||
return await this.generatorInfrastructureService.generateDocument(data);
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,9 @@ export class CapitalOnboardingStateDTO {
|
||||
@Field(() => String, { nullable: true })
|
||||
onboarding_blagorost_offer_template_hash?: string | null;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
capital_program_doc_data_hash?: string | null;
|
||||
|
||||
@Field(() => String)
|
||||
onboarding_init_at!: string;
|
||||
|
||||
|
||||
+1
@@ -125,6 +125,7 @@ export class CapitalOnboardingService {
|
||||
onboarding_blagorost_provision_hash: pluginConfig.onboarding_blagorost_provision_hash || null,
|
||||
blagorost_offer_template_done: !!pluginConfig.onboarding_blagorost_offer_template_done,
|
||||
onboarding_blagorost_offer_template_hash: pluginConfig.onboarding_blagorost_offer_template_hash || null,
|
||||
capital_program_doc_data_hash: pluginConfig.capital_program_doc_data_hash || null,
|
||||
onboarding_init_at: pluginConfig.onboarding_init_at || '',
|
||||
onboarding_expire_at: pluginConfig.onboarding_expire_at || '',
|
||||
};
|
||||
|
||||
+25
@@ -51,6 +51,11 @@ import { ProgramKey } from '~/domain/registration/enum';
|
||||
import type { GenerateCapitalRegistrationDocumentsDomainInput } from '../../domain/actions/generate-capital-registration-documents-domain-input.interface';
|
||||
import type { GenerateCapitalRegistrationDocumentsDomainOutput } from '../../domain/actions/generate-capital-registration-documents-domain-output.interface';
|
||||
import type { CompleteCapitalRegistrationDomainInput } from '../../domain/actions/complete-capital-registration-domain-input.interface';
|
||||
import {
|
||||
EXTENSION_REPOSITORY,
|
||||
type ExtensionDomainRepository,
|
||||
} from '~/domain/extension/repositories/extension-domain.repository';
|
||||
import type { IConfig } from '../../capital-extension.module';
|
||||
|
||||
/**
|
||||
* Интерактор домена для управления участием в CAPITAL контракте
|
||||
@@ -76,8 +81,24 @@ export class ParticipationManagementInteractor {
|
||||
private readonly projectManagementInteractor: ProjectManagementInteractor,
|
||||
private readonly domainToBlockchainUtils: DomainToBlockchainUtils,
|
||||
private readonly documentInteractor: DocumentInteractor,
|
||||
@Inject(EXTENSION_REPOSITORY)
|
||||
private readonly extensionRepository: ExtensionDomainRepository<IConfig>,
|
||||
) { }
|
||||
|
||||
private async getCapitalProgramDocDataHash(): Promise<string> {
|
||||
const extension = await this.extensionRepository.findByName('capital');
|
||||
const hash = (extension?.config as IConfig | undefined)?.capital_program_doc_data_hash?.trim();
|
||||
|
||||
if (!hash) {
|
||||
throw new HttpApiError(
|
||||
httpStatus.BAD_REQUEST,
|
||||
'Параметры документов ЦПП не заполнены: отсутствует capital_program_doc_data_hash в конфигурации capital'
|
||||
);
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение отображаемого имени из аккаунта через порт расширения
|
||||
*/
|
||||
@@ -778,6 +799,7 @@ export class ParticipationManagementInteractor {
|
||||
|
||||
// Генерируем параметры для соглашения Благорост
|
||||
await this.udataDocumentParametersService.generateBlagorostAgreementParametersIfNotExist(data.coopname, data.username);
|
||||
const capitalProgramDocDataHash = await this.getCapitalProgramDocDataHash();
|
||||
|
||||
blagorostAgreement = await this.documentInteractor.generateDocument({
|
||||
data: {
|
||||
@@ -785,6 +807,7 @@ export class ParticipationManagementInteractor {
|
||||
username: data.username,
|
||||
lang,
|
||||
registry_id: Cooperative.Registry.BlagorostAgreement.registry_id,
|
||||
doc_data_hash: capitalProgramDocDataHash,
|
||||
},
|
||||
options: { skip_save: false },
|
||||
});
|
||||
@@ -809,6 +832,7 @@ export class ParticipationManagementInteractor {
|
||||
|
||||
// Генерируем параметры для оферты Генератор
|
||||
await this.udataDocumentParametersService.generateGeneratorOfferParametersIfNotExist(data.coopname, data.username);
|
||||
const capitalProgramDocDataHash = await this.getCapitalProgramDocDataHash();
|
||||
|
||||
generatorOffer = await this.documentInteractor.generateDocument({
|
||||
data: {
|
||||
@@ -816,6 +840,7 @@ export class ParticipationManagementInteractor {
|
||||
username: data.username,
|
||||
lang,
|
||||
registry_id: Cooperative.Registry.GeneratorOffer.registry_id,
|
||||
doc_data_hash: capitalProgramDocDataHash,
|
||||
},
|
||||
options: { skip_save: false },
|
||||
});
|
||||
|
||||
@@ -40,6 +40,7 @@ export const defaultConfig = {
|
||||
onboarding_generator_offer_template_done: false,
|
||||
onboarding_blagorost_provision_done: false,
|
||||
onboarding_blagorost_offer_template_done: false,
|
||||
capital_program_doc_data_hash: '',
|
||||
/** Ветка для выборки коммитов (FR3 / PRD); URL репозитория — на проекте/компоненте (PRD §6.2.1). */
|
||||
github_sync_branch: 'dev',
|
||||
/** Интервал polling GitHub API в минутах (FR2); 0 — периодический опрос отключён. */
|
||||
@@ -253,6 +254,10 @@ export const Schema = z.object({
|
||||
.boolean()
|
||||
.default(defaultConfig.onboarding_blagorost_offer_template_done)
|
||||
.describe(describeField({ label: 'Шаг предложения Благорост выполнен', visible: false })),
|
||||
capital_program_doc_data_hash: z
|
||||
.string()
|
||||
.default(defaultConfig.capital_program_doc_data_hash)
|
||||
.describe(describeField({ label: 'PrivateData документов ЦПП', visible: false })),
|
||||
|
||||
});
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +1,3 @@
|
||||
export * from './ui';
|
||||
export { useCapitalOnboarding } from './model';
|
||||
export type { CapitalOnboardingState } from './api';
|
||||
|
||||
@@ -31,6 +31,7 @@ export const useCapitalOnboarding = () => {
|
||||
'blagorost_program': 998,
|
||||
'blagorost_offer_template': 999,
|
||||
};
|
||||
const capitalProgramDocDataRegistryIds = new Set([994, 995, 998, 999]);
|
||||
|
||||
// Генерация документа для шага
|
||||
const generateDocument = async (step: ICouncilOnboardingStep): Promise<GeneratedDocument> => {
|
||||
@@ -42,12 +43,18 @@ export const useCapitalOnboarding = () => {
|
||||
throw new Error(`Неизвестный шаг онбординга: ${step.id}`);
|
||||
}
|
||||
|
||||
const docDataHash = (onboardingState.value as any)?.capital_program_doc_data_hash;
|
||||
if (capitalProgramDocDataRegistryIds.has(registry_id) && !docDataHash) {
|
||||
throw new Error('Параметры документов ЦПП не заполнены: переустановите или обновите настройки расширения capital');
|
||||
}
|
||||
|
||||
const generateDocInput: Mutations.Documents.GenerateDocument.IInput = {
|
||||
input: {
|
||||
data: {
|
||||
coopname: systemStore.info?.coopname || '',
|
||||
username: sessionStore.username,
|
||||
registry_id,
|
||||
...(docDataHash && capitalProgramDocDataRegistryIds.has(registry_id) ? { doc_data_hash: docDataHash } : {}),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
+334
@@ -0,0 +1,334 @@
|
||||
<template lang="pug">
|
||||
BaseCard(title='Параметры документов ЦПП')
|
||||
.text-body2.text-grey-7.q-mb-md
|
||||
| Заполните поля, которые будут подставлены в положения и оферты программ «ГЕНЕРАТОР» и «БЛАГОРОСТ».
|
||||
| Перед установкой расширения сформируйте предпросмотр документов: так параметры сохранятся в PrivateData, а в config попадёт только hash.
|
||||
|
||||
q-input(
|
||||
:model-value='config?.capital_program_doc_data_hash || ""'
|
||||
label='Hash PrivateData документов'
|
||||
hint='Появится после формирования предпросмотра'
|
||||
:rules='[validateSavedHash]'
|
||||
readonly
|
||||
outlined
|
||||
dense
|
||||
)
|
||||
|
||||
q-banner(v-if='config?.capital_program_doc_data_hash' dense rounded class='bg-green-1 text-green-9 q-mb-md')
|
||||
| Параметры сохранены. Hash: {{ config.capital_program_doc_data_hash }}
|
||||
|
||||
q-expansion-item(default-opened label='Протокол и кооператив' icon='description')
|
||||
.row.q-col-gutter-md.q-mt-sm
|
||||
.col-12.col-md-3(v-for='field in protocolFields' :key='field.key')
|
||||
q-input(
|
||||
v-model='form[field.key]'
|
||||
:label='field.label'
|
||||
:type='field.type || "text"'
|
||||
:rules='requiredRules'
|
||||
outlined
|
||||
dense
|
||||
)
|
||||
.col-12.col-md-6(v-for='field in cooperativeFields' :key='field.key')
|
||||
q-input(
|
||||
v-model='form[field.key]'
|
||||
:label='field.label'
|
||||
:type='field.type || "text"'
|
||||
:rules='requiredRules'
|
||||
outlined
|
||||
dense
|
||||
)
|
||||
|
||||
q-expansion-item(label='Программа ГЕНЕРАТОР' icon='bolt')
|
||||
.q-mt-sm
|
||||
q-input.q-mb-md(
|
||||
v-for='field in generatorFields'
|
||||
:key='field.key'
|
||||
v-model='form[field.key]'
|
||||
:label='field.label'
|
||||
:type='field.type || "textarea"'
|
||||
:rules='requiredRules'
|
||||
outlined
|
||||
autogrow
|
||||
)
|
||||
|
||||
q-expansion-item(label='Программа БЛАГОРОСТ' icon='eco')
|
||||
.q-mt-sm
|
||||
q-input.q-mb-md(
|
||||
v-for='field in blagorostFields'
|
||||
:key='field.key'
|
||||
v-model='form[field.key]'
|
||||
:label='field.label'
|
||||
:type='field.type || "textarea"'
|
||||
:rules='requiredRules'
|
||||
outlined
|
||||
autogrow
|
||||
)
|
||||
|
||||
q-expansion-item(label='Возврат, источники и оферты' icon='article')
|
||||
.q-mt-sm
|
||||
q-input.q-mb-md(
|
||||
v-for='field in returnFields'
|
||||
:key='field.key'
|
||||
v-model='form[field.key]'
|
||||
:label='field.label'
|
||||
:type='field.type || "textarea"'
|
||||
:rules='requiredRules'
|
||||
outlined
|
||||
autogrow
|
||||
)
|
||||
|
||||
.row.items-center.justify-between.q-mt-lg
|
||||
.text-caption.text-grey-7
|
||||
| Предпросмотр генерирует документы без публикации и сохраняет PrivateData по hash.
|
||||
q-btn(
|
||||
color='primary'
|
||||
label='Сформировать предпросмотр'
|
||||
:loading='isGenerating'
|
||||
@click='generatePreview'
|
||||
)
|
||||
|
||||
BaseDialog(v-model='previewOpen' title='Предпросмотр положений ЦПП' size='xl')
|
||||
q-card-section(v-if='previewDocuments.length')
|
||||
q-tabs(v-model='activePreviewTab' dense align='left' class='text-primary')
|
||||
q-tab(
|
||||
v-for='doc in previewDocuments'
|
||||
:key='doc.registry_id'
|
||||
:name='String(doc.registry_id)'
|
||||
:label='doc.title'
|
||||
)
|
||||
q-separator
|
||||
q-tab-panels(v-model='activePreviewTab' animated)
|
||||
q-tab-panel(
|
||||
v-for='doc in previewDocuments'
|
||||
:key='doc.registry_id'
|
||||
:name='String(doc.registry_id)'
|
||||
)
|
||||
.doc-preview
|
||||
DocumentHtmlReader(:html='doc.html' :sanitize='false')
|
||||
q-card-actions(align='right')
|
||||
q-btn(flat label='Закрыть' @click='previewOpen = false')
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import { Mutations } from '@coopenomics/sdk';
|
||||
import { useSystemStore } from 'src/entities/System/model';
|
||||
import { useSessionStore } from 'src/entities/Session';
|
||||
import { client } from 'src/shared/api/client';
|
||||
import { FailAlert, SuccessAlert } from 'src/shared/api';
|
||||
import { BaseCard } from 'src/shared/ui/base/BaseCard';
|
||||
import { BaseDialog } from 'src/shared/ui/base/BaseDialog';
|
||||
import { DocumentHtmlReader } from 'src/shared/ui/DocumentHtmlReader';
|
||||
|
||||
type CapitalProgramPrivateData = {
|
||||
approval_protocol_number: string;
|
||||
approval_protocol_day: string;
|
||||
approval_protocol_month: string;
|
||||
approval_protocol_year: string;
|
||||
cooperative_name: string;
|
||||
cooperative_short_name: string;
|
||||
cooperative_quoted_name: string;
|
||||
website: string;
|
||||
chairman_full_name: string;
|
||||
generator_program_purpose: string;
|
||||
eoap_definition: string;
|
||||
generator_task_goal: string;
|
||||
idea_unit_cost: string;
|
||||
idea_unit_cost_words: string;
|
||||
blagorost_goal_expansion: string;
|
||||
blagorost_task_expansion: string;
|
||||
blagorost_task_development: string;
|
||||
return_source_description: string;
|
||||
return_additional_source: string;
|
||||
offer_template_number: string;
|
||||
};
|
||||
|
||||
type FieldDefinition = {
|
||||
key: keyof CapitalProgramPrivateData;
|
||||
label: string;
|
||||
type?: 'text' | 'textarea';
|
||||
};
|
||||
|
||||
interface Props {
|
||||
config: Record<string, any>;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:config', value: Record<string, any>): void;
|
||||
}>();
|
||||
|
||||
const system = useSystemStore();
|
||||
const session = useSessionStore();
|
||||
const isGenerating = ref(false);
|
||||
const previewOpen = ref(false);
|
||||
const activePreviewTab = ref('994');
|
||||
const previewDocuments = ref<Array<{ registry_id: number; title: string; html: string; hash: string }>>([]);
|
||||
|
||||
const requiredRules = [(value: string) => !!String(value || '').trim() || 'Заполните поле'];
|
||||
const validateSavedHash = (value: string) => !!String(value || '').trim() || 'Сформируйте предпросмотр и сохраните параметры документов';
|
||||
|
||||
const form = reactive<CapitalProgramPrivateData>({
|
||||
approval_protocol_number: '',
|
||||
approval_protocol_day: '',
|
||||
approval_protocol_month: '',
|
||||
approval_protocol_year: '',
|
||||
cooperative_name: '',
|
||||
cooperative_short_name: '',
|
||||
cooperative_quoted_name: '',
|
||||
website: '',
|
||||
chairman_full_name: '',
|
||||
generator_program_purpose: '',
|
||||
eoap_definition: '',
|
||||
generator_task_goal: '',
|
||||
idea_unit_cost: '',
|
||||
idea_unit_cost_words: '',
|
||||
blagorost_goal_expansion: '',
|
||||
blagorost_task_expansion: '',
|
||||
blagorost_task_development: '',
|
||||
return_source_description: '',
|
||||
return_additional_source: '',
|
||||
offer_template_number: '______',
|
||||
});
|
||||
|
||||
const protocolFields: FieldDefinition[] = [
|
||||
{ key: 'approval_protocol_number', label: 'Номер протокола' },
|
||||
{ key: 'approval_protocol_day', label: 'День протокола' },
|
||||
{ key: 'approval_protocol_month', label: 'Месяц протокола' },
|
||||
{ key: 'approval_protocol_year', label: 'Год протокола' },
|
||||
];
|
||||
|
||||
const cooperativeFields: FieldDefinition[] = [
|
||||
{ key: 'cooperative_name', label: 'Наименование кооператива' },
|
||||
{ key: 'cooperative_short_name', label: 'Краткое наименование' },
|
||||
{ key: 'cooperative_quoted_name', label: 'Наименование в кавычках' },
|
||||
{ key: 'website', label: 'Сайт' },
|
||||
{ key: 'chairman_full_name', label: 'ФИО председателя' },
|
||||
];
|
||||
|
||||
const generatorFields: FieldDefinition[] = [
|
||||
{ key: 'generator_program_purpose', label: 'Назначение программы ГЕНЕРАТОР' },
|
||||
{ key: 'eoap_definition', label: 'Определение ЕОАП' },
|
||||
{ key: 'generator_task_goal', label: 'Цель задач ГЕНЕРАТОР' },
|
||||
{ key: 'idea_unit_cost', label: 'Стоимость единицы идеи', type: 'text' },
|
||||
{ key: 'idea_unit_cost_words', label: 'Стоимость единицы идеи прописью', type: 'text' },
|
||||
];
|
||||
|
||||
const blagorostFields: FieldDefinition[] = [
|
||||
{ key: 'blagorost_goal_expansion', label: 'Расширение цели БЛАГОРОСТ' },
|
||||
{ key: 'blagorost_task_expansion', label: 'Задача расширения БЛАГОРОСТ' },
|
||||
{ key: 'blagorost_task_development', label: 'Задача развития БЛАГОРОСТ' },
|
||||
];
|
||||
|
||||
const returnFields: FieldDefinition[] = [
|
||||
{ key: 'return_source_description', label: 'Источник возврата' },
|
||||
{ key: 'return_additional_source', label: 'Дополнительный источник возврата' },
|
||||
{ key: 'offer_template_number', label: 'Номер шаблона оферты', type: 'text' },
|
||||
];
|
||||
|
||||
const allFields = computed(() => [
|
||||
...protocolFields,
|
||||
...cooperativeFields,
|
||||
...generatorFields,
|
||||
...blagorostFields,
|
||||
...returnFields,
|
||||
]);
|
||||
|
||||
watch(
|
||||
() => system.info,
|
||||
(info: any) => {
|
||||
if (!info) return;
|
||||
form.cooperative_name ||= String(info.coopname || '').toUpperCase();
|
||||
form.cooperative_short_name ||= 'ПК';
|
||||
form.cooperative_quoted_name ||= form.cooperative_name ? `«${form.cooperative_name}»` : '';
|
||||
form.website ||= info.website || info.contacts?.website || '';
|
||||
form.chairman_full_name ||= info.chairman?.full_name || info.chairman_full_name || '';
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function getPayload(): CapitalProgramPrivateData {
|
||||
return Object.fromEntries(
|
||||
allFields.value.map(({ key }) => [key, String(form[key] || '').trim()])
|
||||
) as CapitalProgramPrivateData;
|
||||
}
|
||||
|
||||
function validatePayload(payload: CapitalProgramPrivateData): string[] {
|
||||
return allFields.value
|
||||
.filter(({ key }) => !payload[key])
|
||||
.map(({ label }) => label);
|
||||
}
|
||||
|
||||
async function generateDocument(registry_id: number, payload: CapitalProgramPrivateData) {
|
||||
const { [Mutations.Documents.GenerateDocument.name]: result } = await client.Mutation(
|
||||
Mutations.Documents.GenerateDocument.mutation,
|
||||
{
|
||||
variables: {
|
||||
input: {
|
||||
data: {
|
||||
coopname: system.info?.coopname || '',
|
||||
username: session.username,
|
||||
lang: 'ru',
|
||||
registry_id,
|
||||
doc_data: payload,
|
||||
},
|
||||
options: { skip_save: true },
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function generatePreview() {
|
||||
const payload = getPayload();
|
||||
const missing = validatePayload(payload);
|
||||
if (missing.length) {
|
||||
FailAlert(`Заполните параметры документов: ${missing.join(', ')}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
isGenerating.value = true;
|
||||
const docs = await Promise.all([
|
||||
generateDocument(994, payload),
|
||||
generateDocument(998, payload),
|
||||
]);
|
||||
|
||||
const docDataHash = docs
|
||||
.map((doc: any) => doc?.meta?.doc_data_hash)
|
||||
.find((hash: unknown): hash is string => typeof hash === 'string' && hash.length > 0);
|
||||
|
||||
if (!docDataHash) {
|
||||
throw new Error('Генератор не вернул doc_data_hash');
|
||||
}
|
||||
|
||||
previewDocuments.value = docs.map((doc: any, index) => ({
|
||||
registry_id: index === 0 ? 994 : 998,
|
||||
title: doc.full_title,
|
||||
html: doc.html,
|
||||
hash: doc.hash,
|
||||
}));
|
||||
activePreviewTab.value = '994';
|
||||
previewOpen.value = true;
|
||||
emit('update:config', {
|
||||
...props.config,
|
||||
capital_program_doc_data_hash: docDataHash,
|
||||
});
|
||||
SuccessAlert('Параметры документов сохранены в PrivateData');
|
||||
} catch (error) {
|
||||
FailAlert(error);
|
||||
} finally {
|
||||
isGenerating.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.doc-preview :deep([data-doc-param]) {
|
||||
background: rgba(255, 193, 7, 0.25);
|
||||
border-radius: 4px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
</style>
|
||||
@@ -1 +1,2 @@
|
||||
export { default as CapitalOnboardingCard } from './CapitalOnboardingCard.vue';
|
||||
export { default as CapitalProgramDocumentParametersWidget } from './CapitalProgramDocumentParametersWidget.vue';
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
)
|
||||
ExtensionInstall(
|
||||
v-if='isInstall',
|
||||
:extension-name='extension.name',
|
||||
:schema='extension.schema',
|
||||
v-model:config='data',
|
||||
:form-ref='myFormRef'
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<template lang="pug">
|
||||
.extension-install
|
||||
q-form(ref='formRef')
|
||||
q-form(:ref='setFormRef')
|
||||
CapitalProgramDocumentParametersWidget(
|
||||
v-if='extensionName === "capital"'
|
||||
:config='config'
|
||||
@update:config='$emit("update:config", $event)'
|
||||
)
|
||||
BaseCard(v-if='schema && !isEmpty', title='Настройки')
|
||||
ZodForm(
|
||||
:schema='schema',
|
||||
@@ -17,8 +22,10 @@ import { computed } from 'vue';
|
||||
import { ZodForm } from 'src/shared/ui/ZodForm';
|
||||
import { BaseCard } from 'src/shared/ui/base/BaseCard';
|
||||
import { isExtensionSchemaEmpty } from 'src/shared/lib/utils';
|
||||
import { CapitalProgramDocumentParametersWidget } from 'app/extensions/capital/features/Onboarding';
|
||||
|
||||
interface Props {
|
||||
extensionName?: string;
|
||||
schema?: any;
|
||||
config: any;
|
||||
formRef?: any;
|
||||
@@ -27,6 +34,12 @@ interface Props {
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const isEmpty = computed(() => isExtensionSchemaEmpty(props.schema));
|
||||
|
||||
const setFormRef = (element: any) => {
|
||||
if (props.formRef && 'value' in props.formRef) {
|
||||
props.formRef.value = element;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -12,6 +12,7 @@ const onboardingStateFields = {
|
||||
onboarding_blagorost_provision_hash: true,
|
||||
blagorost_offer_template_done: true,
|
||||
onboarding_blagorost_offer_template_hash: true,
|
||||
capital_program_doc_data_hash: true,
|
||||
onboarding_init_at: true,
|
||||
onboarding_expire_at: true,
|
||||
} as const
|
||||
|
||||
Reference in New Issue
Block a user