diff --git a/CHANGELOG.md b/CHANGELOG.md index 8406fe96ce0..aa7fb57c866 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,75 @@ +# v2026.4.2-2 + +В этом релизе — стабилизация Благороста для вывода в продуктивную работу с результатами интеллектуальной деятельности. Отдельно заложена основа под отчётность в ФНС и ФСС и прототип поиска по документам. + +**Благорост и проекты** + +- Быстрые действия на странице программы: создать проект, компонент, задачу или требование. +- Требования к компонентам в репозитории: поддержка **Mermaid**, **Draw.io** и **BPMN**. +- Синхронизация проектов, компонентов, требований и задач с Git-репозиторием результатов. +- Встраивание полноразмерного видео (iframe) на страницах проектов. +- Скачивание пакета подписанных документов одной кнопкой. +- Комнаты проектов в кооперативном мессенджере. + +**Мессенджер и звонки** + +- Автосекретарь: запись синхронных звонков, текстовых и голосовых сообщений в проектных комнатах. + +**Отчётность и инфраструктура** + +- Прототип фабрики отчётов ФНС/ФСС: выгрузка в XML для дальнейшей отправки. +- Прототип поисковой системы по документам. +- Установщик для развёртывания на своих серверах (разработка или эксплуатация). +- Рефакторинг в сторону чистой архитектуры на бэкенде. +- Ускорение сборки фронтенда за счёт перехода на **Vite 8**. + +**Исправления** + +- Повторное общее собрание больше не мешало завершить онбординг кооператива. +- Центр уведомлений не блокировал загрузку рабочего стола при отключённом провайдере оповещений. +- Уведомления о собрании совета по свободным вопросам снова доходят до членов совета. +- В интерфейсе восстановлено отображение контактов кооператива. + +#releases + +--- + +# v2026.4.2-2 + +В этом релизе — стабилизация Благороста для вывода в продуктивную работу с результатами интеллектуальной деятельности. Отдельно заложена основа под отчётность в ФНС и ФСС и прототип поиска по документам. + +**Благорост и проекты** + +- Быстрые действия на странице программы: создать проект, компонент, задачу или требование. +- Требования к компонентам в репозитории: поддержка **Mermaid**, **Draw.io** и **BPMN**. +- Синхронизация проектов, компонентов, требований и задач с Git-репозиторием результатов. +- Встраивание полноразмерного видео (iframe) на страницах проектов. +- Скачивание пакета подписанных документов одной кнопкой. +- Комнаты проектов в кооперативном мессенджере. + +**Мессенджер и звонки** + +- Автосекретарь: запись синхронных звонков, текстовых и голосовых сообщений в проектных комнатах. + +**Отчётность и инфраструктура** + +- Прототип фабрики отчётов ФНС/ФСС: выгрузка в XML для дальнейшей отправки. +- Прототип поисковой системы по документам. +- Установщик для развёртывания на своих серверах (разработка или эксплуатация). +- Рефакторинг в сторону чистой архитектуры на бэкенде. +- Ускорение сборки фронтенда за счёт перехода на **Vite 8**. + +**Исправления** + +- Повторное общее собрание больше не мешало завершить онбординг кооператива. +- Центр уведомлений не блокировал загрузку рабочего стола при отключённом провайдере оповещений. +- Уведомления о собрании совета по свободным вопросам снова доходят до членов совета. +- В интерфейсе восстановлено отображение контактов кооператива. + +#releases + +--- + # v2025.12.28-8 В этой версии представлен прототип трекера результатов интеллектуальной деятельности, реализован мост в 1С, обновлен интерфейс и существенно повышена стабильность системы. diff --git a/components/boot/src/configs/genesis.json b/components/boot/src/configs/genesis.json index 16fcae132f4..a5ab8d320cc 100644 --- a/components/boot/src/configs/genesis.json +++ b/components/boot/src/configs/genesis.json @@ -16,7 +16,7 @@ "max_transaction_lifetime": 3600, "deferred_trx_expiration_window": 600, "max_transaction_delay": 3888000, - "max_inline_action_size": 4096, + "max_inline_action_size": 65536, "max_inline_action_depth": 10, "max_authority_depth": 6 }, diff --git a/components/controller/src/application/meet/dto/create-meet-agenda-input.dto.ts b/components/controller/src/application/meet/dto/create-meet-agenda-input.dto.ts index bbe2e29caf8..6a99ce247a9 100644 --- a/components/controller/src/application/meet/dto/create-meet-agenda-input.dto.ts +++ b/components/controller/src/application/meet/dto/create-meet-agenda-input.dto.ts @@ -1,10 +1,13 @@ import { Field, InputType } from '@nestjs/graphql'; -import { IsArray, IsNotEmpty, IsString, ValidateNested } from 'class-validator'; +import { IsArray, IsNotEmpty, IsOptional, IsString, MaxLength, ValidateNested } from 'class-validator'; import { Type } from 'class-transformer'; import type { CreateAnnualGeneralMeetInputDomainInterface } from '~/domain/meet/interfaces/create-annual-meet-input-domain.interface'; import { AgendaGeneralMeetPointInputDTO } from './agenda-meet-point-input.dto'; import { AnnualGeneralMeetingAgendaSignedDocumentInputDTO } from '~/application/document/documents-dto/annual-general-meeting-agenda-document.dto'; +/** Согласовано с лимитом на desktop (повестка / форма собрания) */ +const MEET_DETAILS_MAX_LEN = 10_000; + @InputType('CreateAnnualGeneralMeetInput') export class CreateAnnualGeneralMeetInputDTO implements CreateAnnualGeneralMeetInputDomainInterface { @Field(() => String, { description: 'Имя аккаунта кооператива' }) @@ -43,4 +46,13 @@ export class CreateAnnualGeneralMeetInputDTO implements CreateAnnualGeneralMeetI @Field(() => AnnualGeneralMeetingAgendaSignedDocumentInputDTO, { description: 'Предложение повестки собрания' }) proposal!: AnnualGeneralMeetingAgendaSignedDocumentInputDTO; + + @Field(() => String, { + nullable: true, + description: 'Дополнительная информация о формате собрания (ссылка, как участвовать и т.д.)', + }) + @IsOptional() + @IsString() + @MaxLength(MEET_DETAILS_MAX_LEN) + details?: string | null; } diff --git a/components/controller/src/application/meet/dto/meet-pre.dto.ts b/components/controller/src/application/meet/dto/meet-pre.dto.ts index 353e27e0998..5b79299db1e 100644 --- a/components/controller/src/application/meet/dto/meet-pre.dto.ts +++ b/components/controller/src/application/meet/dto/meet-pre.dto.ts @@ -65,6 +65,13 @@ export class MeetPreProcessingDTO implements MeetPreProcessingDomainInterface { @IsOptional() proposal?: DocumentAggregateDTO; + @Field(() => String, { + nullable: true, + description: 'Дополнительная информация о формате собрания', + }) + @IsOptional() + details?: string | null; + constructor( data: MeetPreProcessingDomainInterface, presiderCertificate?: UserCertificateDomainInterface | null, @@ -80,6 +87,7 @@ export class MeetPreProcessingDTO implements MeetPreProcessingDomainInterface { this.open_at = data.open_at; this.close_at = data.close_at; this.proposal = data.proposal ? new DocumentAggregateDTO(data.proposal) : undefined; + this.details = data.details ?? null; // Обрабатываем сертификат инициатора if (initiatorCertificate) { diff --git a/components/controller/src/application/meet/dto/restart-annual-general-meet-input.dto.ts b/components/controller/src/application/meet/dto/restart-annual-general-meet-input.dto.ts index 58be2663389..f0fdc9869d2 100644 --- a/components/controller/src/application/meet/dto/restart-annual-general-meet-input.dto.ts +++ b/components/controller/src/application/meet/dto/restart-annual-general-meet-input.dto.ts @@ -1,9 +1,11 @@ import { Field, InputType } from '@nestjs/graphql'; -import { IsString, IsDate, ValidateNested } from 'class-validator'; +import { IsString, IsDate, IsOptional, MaxLength, ValidateNested } from 'class-validator'; import { Type } from 'class-transformer'; import { AnnualGeneralMeetingAgendaSignedDocumentInputDTO } from '~/application/document/documents-dto/annual-general-meeting-agenda-document.dto'; import { RestartAnnualGeneralMeetInputDomainInterface } from '~/domain/meet/interfaces/restart-annual-general-meet-input-domain.interface'; +const MEET_DETAILS_MAX_LEN = 10_000; + @InputType('RestartAnnualGeneralMeetInput', { description: 'DTO для перезапуска ежегодного общего собрания кооператива' }) export class RestartAnnualGeneralMeetInputDTO implements RestartAnnualGeneralMeetInputDomainInterface { @Field(() => String, { description: 'Имя аккаунта кооператива' }) @@ -29,4 +31,14 @@ export class RestartAnnualGeneralMeetInputDTO implements RestartAnnualGeneralMee @IsDate() @Type(() => Date) new_close_at!: Date; + + @Field(() => String, { + nullable: true, + description: + 'Доп. информация для нового слота; передайте явно, если нужна в повестке и уведомлениях. Поле не передано — у нового собрания details пустой', + }) + @IsOptional() + @IsString() + @MaxLength(MEET_DETAILS_MAX_LEN) + details?: string | null; } diff --git a/components/controller/src/application/meet/interactors/meet.interactor.ts b/components/controller/src/application/meet/interactors/meet.interactor.ts index ced13b358a5..16a929bc40d 100644 --- a/components/controller/src/application/meet/interactors/meet.interactor.ts +++ b/components/controller/src/application/meet/interactors/meet.interactor.ts @@ -19,6 +19,8 @@ import { ProcessMeetDecisionInputDomainInterface } from '~/domain/meet/interface import { MeetProcessedDomainEntity } from '~/domain/meet/entities/meet-processed-domain.entity'; import { NotifyOnAnnualGeneralMeetInputDomainInterface } from '~/domain/meet/interfaces/notify-on-annual-general-meet-input-domain.interface'; import { generateUniqueHash } from '~/utils/generate-hash.util'; +import { HttpApiError } from '~/utils/httpApiError'; +import httpStatus from 'http-status'; @Injectable() export class MeetInteractor { @@ -48,10 +50,13 @@ export class MeetInteractor { // Генерируем уникальный хэш для собрания const hash = generateUniqueHash(); + const detailsNormalized = MeetInteractor.normalizeMeetDetailsField(data.details); + const preProcessing = new MeetPreProcessingDomainEntity({ ...data, hash, proposal: proposalAggregate, + details: detailsNormalized, }); // Сохраняем данные в репозиторий @@ -60,6 +65,8 @@ export class MeetInteractor { // Вызов блокчейн порта для создания собрания await this.meetBlockchainPort.createMeet({ ...data, hash }); + // Задержка синхронизации на 1 секунду, чтобы избежать ошибки "row not found" + await new Promise(resolve => setTimeout(resolve, 1000)); // Получаем обновленные данные из блокчейна const processingData = await this.meetBlockchainPort.getMeet({ coopname: data.coopname, @@ -173,22 +180,59 @@ export class MeetInteractor { } async restartMeet(data: RestartAnnualGeneralMeetInputDomainInterface): Promise { - // Вызов блокчейн порта для перезапуска собрания + const oldPre = await this.meetPreRepository.findByHash(data.hash); + const new_hash = await this.meetBlockchainPort.restartMeet(data); - // Получаем обновленные данные из обоих источников const processingData = await this.meetBlockchainPort.getMeet({ coopname: data.coopname, hash: new_hash, }); + if (!processingData) { + throw new HttpApiError(httpStatus.NOT_FOUND, 'Собрание после перезапуска не найдено в блокчейне'); + } + + const proposalAggregate = await this.documentAggregator.buildDocumentAggregate(data.newproposal); + + const meetRow = processingData.meet; + const agendaFromChain = processingData.questions.map((q) => ({ + context: q.context, + title: q.title, + decision: q.decision, + })); + + const detailsForNewPre = MeetInteractor.normalizeMeetDetailsField(data.details); + + const preProcessing = new MeetPreProcessingDomainEntity({ + hash: new_hash, + coopname: meetRow.coopname, + initiator: oldPre?.initiator ?? meetRow.initiator, + presider: oldPre?.presider ?? meetRow.presider, + secretary: oldPre?.secretary ?? meetRow.secretary, + agenda: agendaFromChain, + open_at: meetRow.open_at, + close_at: meetRow.close_at, + proposal: proposalAggregate ?? undefined, + details: detailsForNewPre, + }); + + await this.meetPreRepository.create(preProcessing); + const preMeet = await this.meetPreRepository.findByHash(new_hash); const processedMeetEntity = await this.meetProcessedRepository.findByHash(new_hash); - // Создаем агрегат из обоих источников данных return new MeetAggregate(preMeet, processingData, processedMeetEntity); } + /** Пустая строка → отсутствие значения в PG */ + private static normalizeMeetDetailsField(value: string | null | undefined): string | null | undefined { + if (value === undefined) return undefined; + if (value === null) return null; + const t = value.trim(); + return t.length > 0 ? t : null; + } + async signBySecretaryOnAnnualGeneralMeet( data: SignBySecretaryOnAnnualGeneralMeetInputDomainInterface ): Promise { diff --git a/components/controller/src/domain/meet/entities/meet-pre-domain.entity.ts b/components/controller/src/domain/meet/entities/meet-pre-domain.entity.ts index 46415b17a5c..d4983afb199 100644 --- a/components/controller/src/domain/meet/entities/meet-pre-domain.entity.ts +++ b/components/controller/src/domain/meet/entities/meet-pre-domain.entity.ts @@ -11,6 +11,7 @@ export class MeetPreProcessingDomainEntity implements MeetPreProcessingDomainInt public readonly open_at!: Date; public readonly close_at!: Date; public readonly proposal?: DocumentAggregateDomainInterface; + public readonly details?: string | null; constructor(data: MeetPreProcessingDomainInterface) { Object.assign(this, data); diff --git a/components/controller/src/domain/meet/interfaces/create-annual-meet-input-domain.interface.ts b/components/controller/src/domain/meet/interfaces/create-annual-meet-input-domain.interface.ts index 363eb6566a0..5958f85c77e 100644 --- a/components/controller/src/domain/meet/interfaces/create-annual-meet-input-domain.interface.ts +++ b/components/controller/src/domain/meet/interfaces/create-annual-meet-input-domain.interface.ts @@ -9,4 +9,5 @@ export type CreateAnnualGeneralMeetInputDomainInterface = Omit< open_at: Date; close_at: Date; hash?: string; + details?: string | null; }; diff --git a/components/controller/src/domain/meet/interfaces/meet-pre-domain.interface.ts b/components/controller/src/domain/meet/interfaces/meet-pre-domain.interface.ts index 738b3df3380..1c95f73051c 100644 --- a/components/controller/src/domain/meet/interfaces/meet-pre-domain.interface.ts +++ b/components/controller/src/domain/meet/interfaces/meet-pre-domain.interface.ts @@ -21,6 +21,8 @@ export interface MeetPreProcessingDomainInterface { open_at: Date; close_at: Date; proposal?: DocumentAggregateDomainInterface; + /** Текст уровня собрания (ссылка на трансляцию, формат участия и т.п.), только в PG */ + details?: string | null; } assertType>(); diff --git a/components/controller/src/domain/meet/interfaces/restart-annual-general-meet-input-domain.interface.ts b/components/controller/src/domain/meet/interfaces/restart-annual-general-meet-input-domain.interface.ts index b337cea3315..714f1d33831 100644 --- a/components/controller/src/domain/meet/interfaces/restart-annual-general-meet-input-domain.interface.ts +++ b/components/controller/src/domain/meet/interfaces/restart-annual-general-meet-input-domain.interface.ts @@ -9,4 +9,6 @@ export interface RestartAnnualGeneralMeetInputDomainInterface { newproposal: ISignedDocumentDomainInterface; new_open_at: Date; new_close_at: Date; + /** Если передано — пишем в meet_pre нового hash; если в мутации не передано — details у нового pre пустой (старый pre не подмешиваем) */ + details?: string | null; } diff --git a/components/controller/src/extensions/capital/application/services/generation.service.ts b/components/controller/src/extensions/capital/application/services/generation.service.ts index 77f50ecf995..32b6b863d46 100644 --- a/components/controller/src/extensions/capital/application/services/generation.service.ts +++ b/components/controller/src/extensions/capital/application/services/generation.service.ts @@ -312,7 +312,21 @@ export class GenerationService { return []; } - const rooms = await this.projectCommArtifacts.listCommunicationRoomsForProject(anchorProjectHash); + const projectForStory = await this.projectRepository.findByHash(anchorProjectHash); + if (!projectForStory) { + return []; + } + + // Компоненты не имеют отдельных записей чатов в реестре: комната Matrix у корневого проекта (parent_hash). + let matrixRoomProjectHash = anchorProjectHash.trim().toLowerCase(); + if (projectForStory.isComponent()) { + const parentHash = projectForStory.parent_hash?.trim().toLowerCase() ?? ''; + if (parentHash) { + matrixRoomProjectHash = parentHash; + } + } + + const rooms = await this.projectCommArtifacts.listCommunicationRoomsForProject(matrixRoomProjectHash); if (rooms.length === 0) { return []; } diff --git a/components/controller/src/extensions/participant/meet-workflow-notification.service.ts b/components/controller/src/extensions/participant/meet-workflow-notification.service.ts index f559fd565dc..0d9e21830a0 100644 --- a/components/controller/src/extensions/participant/meet-workflow-notification.service.ts +++ b/components/controller/src/extensions/participant/meet-workflow-notification.service.ts @@ -1,4 +1,5 @@ import { Injectable, Inject, OnModuleInit } from '@nestjs/common'; +import { MEET_REPOSITORY, MeetPreProcessingRepository } from '~/domain/meet/repositories/meet-pre.repository'; import { WinstonLoggerService } from '~/application/logger/logger-app.service'; import { NovuWorkflowAdapter } from '~/infrastructure/novu/novu-workflow.adapter'; import { NOVU_WORKFLOW_PORT } from '~/domain/notification/interfaces/novu-workflow.port'; @@ -20,6 +21,8 @@ export class MeetWorkflowNotificationService implements OnModuleInit { private readonly novuWorkflowAdapter: NovuWorkflowAdapter, @Inject(ACCOUNT_DATA_PORT) private readonly accountPort: AccountDataPort, + @Inject(MEET_REPOSITORY) + private readonly meetPreRepository: MeetPreProcessingRepository, private readonly logger: WinstonLoggerService ) { this.logger.setContext(MeetWorkflowNotificationService.name); @@ -56,6 +59,16 @@ export class MeetWorkflowNotificationService implements OnModuleInit { return config.timezone === 'Europe/Moscow' ? 'МСК' : config.timezone; } + /** + * Текст из meet_pre только для workflow «до/у начала»: meet-initial, meet-reminder-start, meet-started. + * В reminder-end, restart, ended поле details в схемах Novu нет — не передаём, иначе ошибка валидации. + */ + private async meetDetailsPayloadPart(hash: string): Promise<{ details?: string }> { + const pre = await this.meetPreRepository.findByHash(hash); + const trimmed = pre?.details?.trim(); + return trimmed ? { details: trimmed } : {}; + } + // Получение всех email-адресов пользователей private async getAllUserEmails(): Promise> { try { @@ -113,6 +126,7 @@ export class MeetWorkflowNotificationService implements OnModuleInit { const timezone = this.getTimezoneDisplay(); const meetUrl = this.getNotificationUrl(meet); + const detailsPart = await this.meetDetailsPayloadPart(meet.hash); const payload: Workflows.MeetInitial.IPayload = { coopShortName, meetId: meet.id, @@ -122,6 +136,7 @@ export class MeetWorkflowNotificationService implements OnModuleInit { meetEndTime, timezone, meetUrl, + ...detailsPart, }; // Отправляем уведомления каждому пользователю в цикле @@ -166,6 +181,7 @@ export class MeetWorkflowNotificationService implements OnModuleInit { const meetUrl = this.getNotificationUrl(meet); + const detailsPart = await this.meetDetailsPayloadPart(meet.hash); const payload: Workflows.MeetReminderStart.IPayload = { coopShortName, meetId: meet.id, @@ -173,6 +189,7 @@ export class MeetWorkflowNotificationService implements OnModuleInit { meetTime, timeDescription, meetUrl, + ...detailsPart, }; // Отправляем уведомления каждому пользователю в цикле @@ -211,6 +228,7 @@ export class MeetWorkflowNotificationService implements OnModuleInit { const timezone = this.getTimezoneDisplay(); const meetUrl = this.getNotificationUrl(meet); + const detailsPart = await this.meetDetailsPayloadPart(meet.hash); const payload: Workflows.MeetStarted.IPayload = { coopShortName, meetId: meet.id, @@ -218,6 +236,7 @@ export class MeetWorkflowNotificationService implements OnModuleInit { meetEndTime, timezone, meetUrl, + ...detailsPart, }; // Отправляем уведомления каждому пользователю в цикле @@ -263,6 +282,7 @@ export class MeetWorkflowNotificationService implements OnModuleInit { const timezone = this.getTimezoneDisplay(); const meetUrl = this.getNotificationUrl(meet); + const detailsPart = await this.meetDetailsPayloadPart(meet.hash); const payload: Workflows.MeetReminderEnd.IPayload = { coopShortName, meetId: meet.id, @@ -271,6 +291,7 @@ export class MeetWorkflowNotificationService implements OnModuleInit { timeDescription, timezone, meetUrl, + ...detailsPart, }; // Отправляем уведомления каждому пользователю в цикле diff --git a/components/controller/src/infrastructure/blockchain/adapters/meet-blockchain.adapter.ts b/components/controller/src/infrastructure/blockchain/adapters/meet-blockchain.adapter.ts index 9146cbd23c8..a4564ade564 100644 --- a/components/controller/src/infrastructure/blockchain/adapters/meet-blockchain.adapter.ts +++ b/components/controller/src/infrastructure/blockchain/adapters/meet-blockchain.adapter.ts @@ -181,9 +181,11 @@ export class MeetBlockchainAdapter implements MeetBlockchainPort { this.blockchainService.initialize(data.coopname, wif); - // Преобразуем доменный объект в инфраструктурный тип + const { details: _details, ...meetPayload } = data; + + // Преобразуем доменный объект в инфраструктурный тип (поле details только в PG, не в цепи) const blockchainData: MeetContract.Actions.CreateMeet.IInput = { - ...data, + ...meetPayload, hash: data.hash || generateUniqueHash(), proposal: this.domainToBlockchainUtils.convertSignedDocumentToBlockchainFormat(data.proposal), open_at: this.domainToBlockchainUtils.convertDateToBlockchainFormat(data.open_at), diff --git a/components/controller/src/infrastructure/database/typeorm/entities/meet-pre.entity.ts b/components/controller/src/infrastructure/database/typeorm/entities/meet-pre.entity.ts index cf616d4eec4..4bfd20eb906 100644 --- a/components/controller/src/infrastructure/database/typeorm/entities/meet-pre.entity.ts +++ b/components/controller/src/infrastructure/database/typeorm/entities/meet-pre.entity.ts @@ -34,6 +34,9 @@ export class MeetPreEntity implements MeetPreProcessingDomainInterface { @Column('jsonb', { nullable: true }) proposal?: DocumentAggregateDomainInterface; + @Column('text', { nullable: true }) + details?: string | null; + @CreateDateColumn() created_at!: Date; @@ -48,6 +51,7 @@ export class MeetPreEntity implements MeetPreProcessingDomainInterface { this.open_at = data.open_at; this.close_at = data.close_at; this.proposal = data.proposal; + this.details = data.details ?? null; } } @@ -63,6 +67,7 @@ export class MeetPreEntity implements MeetPreProcessingDomainInterface { open_at: this.open_at, close_at: this.close_at, proposal: this.proposal, + details: this.details ?? null, }); } diff --git a/components/notifications/src/workflows/meet-initial/index.ts b/components/notifications/src/workflows/meet-initial/index.ts index 2ce42ce5067..f4084ab1e65 100644 --- a/components/notifications/src/workflows/meet-initial/index.ts +++ b/components/notifications/src/workflows/meet-initial/index.ts @@ -16,6 +16,7 @@ export const meetInitialPayloadSchema = z.object({ meetEndTime: z.string(), timezone: z.string(), meetUrl: z.string(), + details: z.string().optional(), }); export type IPayload = z.infer; @@ -36,12 +37,12 @@ export const workflow: WorkflowDefinition = WorkflowBuilder createEmailStep( 'meet-initial-email', 'Уведомление о общем собрании пайщиков №{{payload.meetId}} в {{payload.coopShortName}}', - 'Уважаемый пайщик!

В кооперативе объявлено новое общее собрание №{{payload.meetId}}.

Дата и время начала: {{payload.meetDate}} в {{payload.meetTime}} ({{payload.timezone}})
Дата и время завершения: {{payload.meetEndDate}} в {{payload.meetEndTime}} ({{payload.timezone}})

Для ознакомления с повесткой, пожалуйста, перейдите по ссылке:
{{payload.meetUrl}}

С уважением, Совет {{payload.coopShortName}}.' + 'Уважаемый пайщик!

В кооперативе объявлено новое общее собрание №{{payload.meetId}}.

Дата и время начала: {{payload.meetDate}} в {{payload.meetTime}} ({{payload.timezone}})
Дата и время завершения: {{payload.meetEndDate}} в {{payload.meetEndTime}} ({{payload.timezone}})

Для ознакомления с повесткой, пожалуйста, перейдите по ссылке:
{{payload.meetUrl}}{{#if payload.details}}

Дополнительная информация:
{{payload.details}}
{{/if}}

С уважением, Совет {{payload.coopShortName}}.' ), createInAppStep( 'meet-initial-notification', 'Новое общее собрание №{{payload.meetId}}', - 'Назначено собрание на {{payload.meetDate}} в {{payload.meetTime}}' + 'Назначено собрание на {{payload.meetDate}} в {{payload.meetTime}}{{#if payload.details}}. {{payload.details}}{{/if}}' ), createPushStep( 'meet-initial-push', diff --git a/components/notifications/src/workflows/meet-reminder-start/index.ts b/components/notifications/src/workflows/meet-reminder-start/index.ts index bc00644b86c..85972a8761a 100644 --- a/components/notifications/src/workflows/meet-reminder-start/index.ts +++ b/components/notifications/src/workflows/meet-reminder-start/index.ts @@ -14,6 +14,7 @@ export const meetReminderStartPayloadSchema = z.object({ meetTime: z.string(), timeDescription: z.string(), meetUrl: z.string(), + details: z.string().optional(), }); export type IPayload = z.infer; @@ -34,12 +35,12 @@ export const workflow: WorkflowDefinition = WorkflowBuilder createEmailStep( 'meet-reminder-start-email', 'Напоминание о предстоящем общем собрании №{{payload.meetId}} в {{payload.coopShortName}}', - 'Уважаемый пайщик!

Напоминаем, что {{payload.timeDescription}} состоится общее собрание пайщиков №{{payload.meetId}} ({{payload.meetDate}} в {{payload.meetTime}}).

Для ознакомления с повесткой и подписи уведомления, пожалуйста, перейдите по ссылке:
{{payload.meetUrl}}

С уважением, Совет {{payload.coopShortName}}.' + 'Уважаемый пайщик!

Напоминаем, что {{payload.timeDescription}} состоится общее собрание пайщиков №{{payload.meetId}} ({{payload.meetDate}} в {{payload.meetTime}}).

Для ознакомления с повесткой и подписи уведомления, пожалуйста, перейдите по ссылке:
{{payload.meetUrl}}{{#if payload.details}}

Дополнительная информация:
{{payload.details}}
{{/if}}

С уважением, Совет {{payload.coopShortName}}.' ), createInAppStep( 'meet-reminder-start-notification', 'Напоминание о собрании №{{payload.meetId}}', - 'Собрание начнется {{payload.timeDescription}}' + 'Собрание начнется {{payload.timeDescription}}{{#if payload.details}}. {{payload.details}}{{/if}}' ), createPushStep( 'meet-reminder-start-push', diff --git a/components/notifications/src/workflows/meet-started/index.ts b/components/notifications/src/workflows/meet-started/index.ts index 6e2bd7d5e28..1933d626a3b 100644 --- a/components/notifications/src/workflows/meet-started/index.ts +++ b/components/notifications/src/workflows/meet-started/index.ts @@ -14,6 +14,7 @@ export const meetStartedPayloadSchema = z.object({ meetEndTime: z.string(), timezone: z.string(), meetUrl: z.string(), + details: z.string().optional(), }); export type IPayload = z.infer; @@ -34,12 +35,12 @@ export const workflow: WorkflowDefinition = WorkflowBuilder createEmailStep( 'meet-started-email', 'Собрание пайщиков №{{payload.meetId}} в {{payload.coopShortName}} началось', - 'Уважаемый пайщик!

Сегодня началось общее собрание пайщиков №{{payload.meetId}}.
Собрание будет проходить до {{payload.meetEndDate}} {{payload.meetEndTime}} ({{payload.timezone}}).

Просим принять участие в голосовании по вопросам повестки дня.
Для голосования перейдите по ссылке:
{{payload.meetUrl}}

С уважением, Совет {{payload.coopShortName}}.' + 'Уважаемый пайщик!

Сегодня началось общее собрание пайщиков №{{payload.meetId}}.
Собрание будет проходить до {{payload.meetEndDate}} {{payload.meetEndTime}} ({{payload.timezone}}).

Просим принять участие в голосовании по вопросам повестки дня.
Для голосования перейдите по ссылке:
{{payload.meetUrl}}{{#if payload.details}}

Дополнительная информация:
{{payload.details}}
{{/if}}

С уважением, Совет {{payload.coopShortName}}.' ), createInAppStep( 'meet-started-notification', 'Собрание №{{payload.meetId}} началось', - 'Примите участие в голосовании' + 'Примите участие в голосовании{{#if payload.details}}. {{payload.details}}{{/if}}' ), createPushStep( 'meet-started-push', diff --git a/scripts/changelog-release.md b/scripts/changelog-release.md index f300904b8c6..e643ae24b93 100644 --- a/scripts/changelog-release.md +++ b/scripts/changelog-release.md @@ -1,39 +1,33 @@ -# v2025.12.28-8 +# v2026.4.2-2 -В этой версии представлен прототип трекера результатов интеллектуальной деятельности, реализован мост в 1С, обновлен интерфейс и существенно повышена стабильность системы. +В этом релизе — стабилизация Благороста для вывода в продуктивную работу с результатами интеллектуальной деятельности. Отдельно заложена основа под отчётность в ФНС и ФСС и прототип поиска по документам. ---- +**Благорост и проекты** -### ✨ Новые функции -- [#332](https://github.com/coopenomics/mono/issues/332): Прототип конструктора требований дополнительных документов при регистрации -- [#330](https://github.com/coopenomics/mono/issues/330): Прототип моста в 1С:Бухгалтерию для передачи документов и проводок -- [#329](https://github.com/coopenomics/mono/issues/329): Интеграция и тестирование LMS TUTOR для образовательных задач -- [#328](https://github.com/coopenomics/mono/issues/328): Размещение прототипов мульти-лендингов на цифровой-кооператив.рф и coopenomics.world -- [#326](https://github.com/coopenomics/mono/issues/326): Минимальный интерфейс трекера результатов интеллектуальной деятельности -- [#324](https://github.com/coopenomics/mono/issues/324): Смарт-контракт генерации и капитализации результатов интеллектуальной деятельности ("Благорост") -- [#322](https://github.com/coopenomics/mono/issues/322): Поставка обновлений ПО с нулевым даунтаймом по blue-green стратегии -- [#321](https://github.com/coopenomics/mono/issues/321): Внедрение системы проводок по фондам для контрактов -- [#319](https://github.com/coopenomics/mono/issues/319): Палитра команд и быстрый доступ к страницам рабочих столов (cmk+k) -- [#316](https://github.com/coopenomics/mono/issues/316): Переход рабочего стола на GraphQL SDK -- [#314](https://github.com/coopenomics/mono/issues/314): Развёртывание GlitchTip для мониторинга ошибок -- [#306](https://github.com/coopenomics/mono/issues/306): Модуль запросов и мутаций для контракта капитализации +- Быстрые действия на странице программы: создать проект, компонент, задачу или требование. +- Требования к компонентам в репозитории: поддержка **Mermaid**, **Draw.io** и **BPMN**. +- Синхронизация проектов, компонентов, требований и задач с Git-репозиторием результатов. +- Встраивание полноразмерного видео (iframe) на страницах проектов. +- Скачивание пакета подписанных документов одной кнопкой. +- Комнаты проектов в кооперативном мессенджере. -### 🐛 Исправления ошибок -- [#312](https://github.com/coopenomics/mono/issues/312): Исправление подписки на изменение статуса коммитов -- [#309](https://github.com/coopenomics/mono/issues/309): Исправление отображения чужих билетов времени в трекере +**Мессенджер и звонки** -### 🔧 Улучшения -- [#331](https://github.com/coopenomics/mono/issues/331): Настройка системы мониторинга сбоев и ошибок на базе GlitchTIP, Loki, Prometheus -- [#327](https://github.com/coopenomics/mono/issues/327): Пользовательская документация по интерфейсам цифрового кооператива -- [#325](https://github.com/coopenomics/mono/issues/325): Документирование смарт-контракта программы "Благорост" -- [#308](https://github.com/coopenomics/mono/issues/308): Улучшение отображения рабочих столов в магазине приложений -- [#307](https://github.com/coopenomics/mono/issues/307): Объединение настроек контракта с нативными настройками приложения -- [#305](https://github.com/coopenomics/mono/issues/305): Объединение полей title и description в проекте -- [#304](https://github.com/coopenomics/mono/issues/304): Доменная модель контракта капитализации на бэкенде -- [#303](https://github.com/coopenomics/mono/issues/303): Пересмотр архитектуры парсера и формирования локальной истории -- [#302](https://github.com/coopenomics/mono/issues/302): Рефакторинг архитектуры, внедрение двухконтурной шины данных и обработки микрофорков -- [#301](https://github.com/coopenomics/mono/issues/301): Доработка и отладка контракта "Капитализация РИД" v0.2 -- [#222](https://github.com/coopenomics/mono/issues/222): Внедрение метода Водянова для распределения пула премий по программе "Благорост" -- [#212](https://github.com/coopenomics/mono/issues/212): Снижение точности валютных значений до двух знаков после запятой в документах +- Автосекретарь: запись синхронных звонков, текстовых и голосовых сообщений в проектных комнатах. + +**Отчётность и инфраструктура** + +- Прототип фабрики отчётов ФНС/ФСС: выгрузка в XML для дальнейшей отправки. +- Прототип поисковой системы по документам. +- Установщик для развёртывания на своих серверах (разработка или эксплуатация). +- Рефакторинг в сторону чистой архитектуры на бэкенде. +- Ускорение сборки фронтенда за счёт перехода на **Vite 8**. + +**Исправления** + +- Повторное общее собрание больше не мешало завершить онбординг кооператива. +- Центр уведомлений не блокировал загрузку рабочего стола при отключённом провайдере оповещений. +- Уведомления о собрании совета по свободным вопросам снова доходят до членов совета. +- В интерфейсе восстановлено отображение контактов кооператива. #releases diff --git a/scripts/release-info.md b/scripts/release-info.md index 05450f3be75..8a641983596 100644 --- a/scripts/release-info.md +++ b/scripts/release-info.md @@ -1,4 +1,4 @@ -VERSION: v2025.12.28-8 -FROM DATE: 2025-07-2T10:00:00+03:00 +VERSION: v2026.4.2-2 +FROM DATE: 2025-12-28T10:00:00+03:00 COMMENT: Релиз содержит прототип трекера результатов интеллектуальной деятельности, прототип моста в 1С, обновленный интерфейс и значительно повышенную стабильность.