Compare commits

..

144 Commits

Author SHA1 Message Date
Alex Ant 4ccf2c743c процесс голосования через подпись номера решения 2025-05-12 10:41:37 +05:00
Alex Ant 418d43af58 almost done 2025-05-09 17:31:41 +05:00
Alex Ant 869a75f080 SDK компилируется 2025-05-07 08:23:59 +05:00
Alex Ant c7bc2d1a20 Merge branch 'dev' into document2 2025-05-05 18:56:04 +05:00
Alex Ant 3ea4cd6d73 chore(release): publish 2025-05-05 18:55:34 +05:00
Alex Ant 6a955737e0 update version 2 2025-05-05 18:55:01 +05:00
Alex Ant 51bf49b063 update version 2025-05-05 18:53:25 +05:00
Alex Ant 12468b16c8 Merge branch 'testnet' 2025-05-05 18:50:23 +05:00
Alex Ant 5ebd28d181 откатываем новую таблицу кооперативов 2025-05-05 18:49:08 +05:00
Alex Ant defe482ccd контракты скомпилировались 2025-05-05 18:46:44 +05:00
Alex Ant e99d141f28 before contract migrate 2025-05-05 17:25:56 +05:00
Alex Ant aa0a6731de chore(release): publish 2025-05-05 12:53:42 +05:00
Alex Ant 46f7f10dce coop migration 2025-05-05 12:28:54 +05:00
Alex Ant 552333faf6 chore(release): publish 2025-05-05 11:17:29 +05:00
Alex Ant e23589d4d3 Функционал миграций по версиям с интеграцией блокчейна и двух баз данных 2025-05-04 14:17:27 +05:00
Alex Ant 104b08e1ba chore(release): publish 2025-05-04 12:42:52 +05:00
Alex Ant 1b81cfc0a9 fix workflow #2 2025-05-04 12:42:23 +05:00
Alex Ant 6e51433ae9 update publish workflow 2025-05-04 12:39:52 +05:00
Alex Ant 421f356987 update version 2025-05-04 12:37:53 +05:00
Alex Ant abd9f1931d chore(release): publish 2025-05-04 12:32:16 +05:00
Alex Ant 2b87034abb fix workflows 2025-05-04 12:26:07 +05:00
Alex Ant 8d7c919332 chore(release): publish 2025-05-04 12:08:16 +05:00
Alex Ant 4785ea2886 publish workflow 2025-05-04 12:07:22 +05:00
Alex Ant 4daa06dac1 Обновление структуры signature_info: добавлен идентификатор подписи. Исправлено условие фильтрации истекших решений в ChairmanPlugin. 2025-05-04 11:40:00 +05:00
Alex Ant abc253f47d create document2 format 2025-05-04 11:02:25 +05:00
Alex Ant 5fea2f8f34 chore(release): publish 2025-05-03 16:19:40 +05:00
Alex Ant c29e224f2f Дефолтные конфиги для расширений, фиксы визуалов, микросервис отмены вопроса с повестки 2025-05-03 15:56:08 +05:00
Alex Ant b930543c2b chore(release): publish 2025-05-02 10:40:41 +05:00
Alex Ant 2768d72e90 update 2025-05-01 22:51:07 +05:00
Alex Ant 8ccefe8fac update 2025-05-01 22:46:58 +05:00
Alex Ant c93e59908d update 2025-05-01 22:29:40 +05:00
Alex Ant 22f0fd8a10 update 2025-05-01 22:28:01 +05:00
Alex Ant 8e17d96529 update 2025-05-01 22:23:56 +05:00
Alex Ant 494a164ee8 update 2025-05-01 22:21:00 +05:00
Alex Ant ee28ae1719 update 2025-05-01 22:17:07 +05:00
Alex Ant 3266061359 update 2025-05-01 22:13:38 +05:00
Alex Ant f69d849c4f update lock-file 2025-05-01 21:57:50 +05:00
Alex Ant 2628120e35 update 2025-05-01 21:54:59 +05:00
Alex Ant edf7d9ab62 docs #2 2025-05-01 21:52:49 +05:00
Alex Ant 2095e448e5 docs 2025-05-01 21:50:39 +05:00
Alex Ant beff507f4a Удалены отладочные логи из функций входа и выхода пользователя, а также обновлен способ проверки переменных окружения в функции работы с IndexedDB. 2025-05-01 18:23:56 +05:00
Alex Ant bf4fbfabac update 2025-05-01 16:46:36 +05:00
Alex Ant 2f1d88a74c Добавлены отладочные логи в функции входа и выхода пользователя, а также обновлен способ загрузки переменных окружения в конфигурации Quasar для режима разработки. 2025-05-01 16:00:45 +05:00
Alex Ant b444004f4a Удалены отладочные логи из функции получения переменных окружения и обновлен middleware для инъекции переменных окружения в браузер с использованием ssrMiddleware. 2025-05-01 14:33:41 +05:00
Alex Ant 6862f2881a update 2025-05-01 13:42:00 +05:00
Alex Ant 73afcabee2 Изменен способ определения переменных окружения в конфигурации Quasar: теперь они устанавливаются только для клиентской стороны, если режим не SSR. 2025-05-01 13:27:05 +05:00
Alex Ant b4168a4053 update 2025-05-01 11:34:33 +05:00
Alex Ant 1fe14d5811 update 2025-05-01 11:22:00 +05:00
Alex Ant 89e5c97ef8 update 2025-05-01 11:12:30 +05:00
Alex Ant 258b8304ff update 2025-05-01 11:01:19 +05:00
Alex Ant 69fe9c1348 update 2025-05-01 10:31:01 +05:00
Alex Ant fa4af94fa0 update 2025-05-01 10:29:53 +05:00
Alex Ant 2ac84a6910 update 2025-05-01 10:27:41 +05:00
Alex Ant 3f79cf9a1b update 2025-05-01 10:24:33 +05:00
Alex Ant c155605ca8 update 2025-05-01 09:59:02 +05:00
Alex Ant 80a94f79da update 2025-05-01 09:51:43 +05:00
Alex Ant 89ba6cb356 update 2025-05-01 09:41:37 +05:00
Alex Ant c7bd0326ba Обновление Dockerfile: замена команды установки зависимостей на принудительную, улучшение структуры образа. Изменение конфигурации GitHub Actions для отключения кеширования и упрощения сборки образов. 2025-05-01 09:36:57 +05:00
Alex Ant 2c551a0d9e Оптимизация Dockerfile: добавление этапа сборки для зависимостей, упрощение копирования файлов и улучшение структуры образа. Обновление конфигурации GitHub Actions для сборки и публикации образов с использованием Docker Buildx. 2025-04-30 21:49:16 +05:00
Alex Ant ba103a969e Добавление шагов для отладки в GitHub Actions: вывод последних коммитов, текущей ветки и содержимого компонентов. Обновление комментариев и улучшение сборки образов без кеша. 2025-04-30 21:32:51 +05:00
Alex Ant 0d9177f98f update 2025-04-30 21:29:50 +05:00
Alex Ant 60c32be299 update 2025-04-30 20:54:13 +05:00
Alex Ant 83c107b8b2 Добавление middleware для инъекции переменных окружения в window.__ENV__ и рефакторинг функции получения переменных окружения с учетом различных сред (SSR, SPA). Теперь переменные окружения экспортируются через единую функцию getEnv(). 2025-04-30 20:53:02 +05:00
Alex Ant b6d582fb2c Обновление конфигурации проекта: замена использования переменных окружения на новый модуль env, упрощение Dockerfile, улучшение сборки контейнеров и исправление мелких ошибок в компонентах. 2025-04-30 20:30:42 +05:00
Alex Ant a5818bdc53 update 2025-04-30 18:37:23 +05:00
Alex Ant de0fe5465f update 2025-04-30 18:00:07 +05:00
Alex Ant d43b188a30 update 2025-04-30 17:47:08 +05:00
Alex Ant bfb2be2a72 update 2025-04-30 17:45:28 +05:00
Alex Ant 73c2cd9cb3 update 2025-04-30 17:35:42 +05:00
Alex Ant 61cfc442ef update 2025-04-30 17:33:58 +05:00
Alex Ant 0ed04c2c27 update 2025-04-30 17:29:37 +05:00
Alex Ant 6773509ede update 2025-04-30 17:17:55 +05:00
Alex Ant acd86df785 update 2025-04-30 17:05:31 +05:00
Alex Ant c554b167b3 update 2025-04-30 16:53:55 +05:00
Alex Ant 18cf7b61e3 print env 2025-04-30 16:47:47 +05:00
Alex Ant e42b396a68 VITE_ prefix 2025-04-30 16:39:33 +05:00
Alex Ant c47e61d42a правки SSR проблема рендиринга 2025-04-30 15:55:07 +05:00
Alex Ant 92559370fa modify quasar conf 2025-04-30 12:49:12 +05:00
Alex Ant c3a2ec46b3 update 2025-04-30 11:32:47 +05:00
Alex Ant fe55d23e65 хук на деплой 2025-04-30 10:29:59 +05:00
Alex Ant b0d4f5d542 Добавление сборки и публикации нового Docker-образа для приложения @coopenomics/desktop в workflow. Обновление конфигурации сборки контейнеров. 2025-04-29 23:23:13 +05:00
Alex Ant be48f7a158 Обновление Dockerfile для упрощения сборки с использованием lerna. Добавление новых прокси-файлов для модулей @dicebear/core, @dicebear/collection и email-regex. Обновление зависимостей в package.json и pnpm-lock.yaml. Изменение конфигурации Quasar для оптимизации работы с новыми модулями. 2025-04-29 23:21:10 +05:00
Alex Ant 83bd744100 update workflow 2025-04-29 21:49:48 +05:00
Alex Ant ff1615f7b1 workflow initialization 2025-04-29 21:40:51 +05:00
Alex Ant 696e1a0d2c Merge branch 'main' into dev 2025-04-29 16:32:03 +05:00
Alex Ant 397670473a chore(release): publish 2025-04-29 16:27:09 +05:00
Alex Ant cab8061bc3 Merge branch 'testnet' 2025-04-29 16:21:40 +05:00
Alex Ant 3c2b9bcb4c update script 2025-04-29 16:21:22 +05:00
Alex Ant 7a274b7ab1 update templates 2025-04-29 16:17:58 +05:00
Alex Ant e524244bab Добавление нового поля 'type' в сущность Payment и соответствующие изменения в DTO, схемах и селекторах. Обновление логики обновления статусов платежей с учетом нового поля. Исправление параметров в методах для получения заказов. 2025-04-29 13:36:04 +05:00
Alex Ant 14ecaf259a Добавление нового поля 'coop' в интерфейс Model и обновление схемы валидации для SelectBranchStatement. Обновление логики комбинирования данных в классе Factory для включения данных о кооперативе. Комментарии в стилях инпутов переведены в закомментированный вид. 2025-04-29 12:25:08 +05:00
Alex Ant 749c13dc91 Обновление скриптов публикации и логики миграции. Изменены команды для публикации на продакшн, добавлен новый скрипт publish-prod.sh. Обновлена логика удаления кандидатов в функции миграции и исправлены ссылки на индексы кандидатов в confirmreg.cpp. 2025-04-29 11:05:03 +05:00
Alex Ant ab2ee4cc92 lerna update 2025-04-29 10:16:02 +05:00
Alex Ant 2907352d61 disable meet for temp 2025-04-29 10:11:14 +05:00
Alex Ant 390b995eb6 Добавление новых скриптов для развертывания и обновление интерфейсов для обработки голосования на общем собрании. Обновлены типы данных в интерфейсах и DTO, добавлены новые методы для генерации документов и улучшения функционала. Удалены устаревшие интерфейсы и компоненты, улучшена структура кода для повышения эффективности. 2025-04-28 23:55:55 +05:00
Alex Ant 24b3dd3c60 Удаление устаревших интерфейсов и таблиц, связанных с кошельками. Обновление логики обработки платежей и интерфейсов для работы с новыми типами данных. Улучшение структуры компонентов и интерфейсов для более эффективного взаимодействия с API. Обновление стилей и компонентов пользовательского интерфейса для улучшения визуального восприятия. 2025-04-26 17:14:22 +05:00
Alex Ant 55444a4b72 Обновление интерфейсов и DTO для работы с документами и собраниями. Добавлены новые поля и типы данных, улучшена обработка документов в сервисах. Удалены устаревшие селекторы и обновлены существующие для соответствия новым требованиям. Исправлены типы данных в селекторах и добавлены новые экспорты. 2025-04-25 23:44:32 +05:00
Alex Ant 76854207a7 Сейчас будем переводить сервис документов на агрегатор. Обновление интерфейсов и DTO для обработки документов. Изменены типы полей в интерфейсах DocumentPackageDomainInterface, DocumentRepository и DocumentDomainService для поддержки значений null. Добавлены проверки на наличие данных в DocumentDomainEntity и улучшена обработка документов в DocumentDomainService. Обновлены методы для работы с документами в API и хранилище. 2025-04-23 19:03:18 +05:00
Alex Ant 4fcd700e39 Первичная мобильная верстка страниц стола совета 2025-04-23 12:39:23 +05:00
Alex Ant 05dfcced1b Обновление конфигурации и добавление тестового режима для собраний. Изменен порт для state-history-endpoint, добавлен флаг TEST_MODE в контракте meet, обновлены проверки дат открытия и закрытия собраний. Обновлены интерфейсы и DTO для обработки новых типов данных и улучшения функционала. 2025-04-22 23:23:09 +05:00
Alex Ant 931c366907 Добавление функционала для генерации бюллетеней голосования на общем собрании. Обновлены интерфейсы и DTO для обработки новых типов документов, добавлены методы для генерации бюллетеней и голосования. Исправлены типы данных для идентификаторов вопросов повестки. 2025-04-21 20:29:12 +05:00
Alex Ant 71d35b74d7 Добавление новых методов для подписания решений председателем и секретарем на общем собрании. Удален метод закрытия собрания, обновлены интерфейсы и DTO для обработки новых типов документов и действий. 2025-04-21 16:44:27 +05:00
Alex Ant 21fac5c7df Добавление новых типов и методов для работы с собраниями в модуле общих собраний. Реализованы методы получения данных о собраниях, обновлены интерфейсы и DTO для обработки информации о голосованиях и документах собраний. 2025-04-20 16:41:20 +05:00
Alex Ant 0ba9d55081 Добавление новых входных типов и методов для получения данных о собраниях в модуле общих собраний. Реализованы методы getMeet и getMeets, а также обновлены интерфейсы и DTO для обработки данных. 2025-04-20 12:56:42 +05:00
Alex Ant b5b388e95d Обновление документации и DTO для модуля общих собраний. Добавлены описания для полей в DTO, улучшена структура и добавлены новые типы данных для обработки информации о собраниях и голосованиях. 2025-04-20 11:59:29 +05:00
Alex Ant 9c643395b3 Добавление модуля общих собраний и расширение DTO для документов, включая новые интерфейсы и классы для обработки документов собраний и решений. 2025-04-20 11:39:27 +05:00
Alex Ant b989074170 meet module & extends all documents DTOs 2025-04-18 15:27:35 +05:00
Alex Ant 261f010a49 update release script 2025-04-16 22:03:36 +05:00
Alex Ant af46867939 update releases script 2025-04-16 22:02:08 +05:00
Alex Ant be2355e5c5 generate changelog releases 2025-04-16 21:42:37 +05:00
Alex Ant 8b4a20ed3f make Templates correct 2025-04-16 20:08:50 +05:00
Alex Ant 1ab1e1d3c5 move templates #1 2025-04-16 20:08:24 +05:00
Alex Ant c9aab02aab wtf?! 2025-04-16 20:06:49 +05:00
Alex Ant 0643604778 add meet contract, cooptypes and empty documents templates 2025-04-16 19:58:49 +05:00
Alex Ant fe79f3ff98 desktop bug fixs 2025-04-15 15:57:55 +05:00
Alex Ant f919a14927 remove Env.ts file from desktop and move params to .env 2025-04-15 12:27:29 +05:00
Alex Ant dda389ad75 fix system contract action name 2025-04-15 10:27:53 +05:00
Alex Ant aa42864029 fix contracts build 2025-04-15 10:14:07 +05:00
Alex Ant 41bb17224a coopStore release 2025-04-11 18:29:07 +05:00
Alex Ant fd6f628c43 refactoring desktop 2025-04-10 11:40:14 +05:00
Alex Ant 28ca615d96 claim -> result, result -> assignment 2025-04-06 12:56:55 +05:00
Alex Ant 76203a88d8 before make refactoring 2025-04-06 11:25:29 +05:00
Alex Ant aa891adddf shared actions 2025-04-05 11:36:06 +05:00
Alex Ant ecdac6fff9 loan contract 2025-04-03 18:52:53 +05:00
Alex Ant bcba24ea5e contracts refactoring 2025-04-03 09:36:48 +05:00
Alex Ant 44b1f423cc appStore & medusa component & capital contract (start rework) 2025-03-28 18:42:28 +05:00
Alex Ant 85cc7361d7 refactoring participant widget to page 2025-03-27 19:14:02 +05:00
Alex Ant e5e28ff652 chore(release): publish 2025-03-27 18:13:33 +05:00
Alex Ant 4957182480 update mutation simplification 2025-03-27 17:30:31 +05:00
Alex Ant 84927e943a make participants editable 2025-03-27 14:32:23 +05:00
Alex Ant 0d06114f00 change decision dto and logout on some auth problems 2025-03-26 22:18:12 +05:00
Alex Ant 2654760e16 Merge pull request #201 from coopenomics/kamilocean/sprint-4
Kamilocean/sprint 4
2025-03-26 20:09:37 +03:00
Alex Ant d02e7ba2be README.md for cleos wallet 2025-03-26 18:27:05 +05:00
Alex Ant e18fd94336 make cleos separated component 2025-03-26 18:19:52 +05:00
Alex Ant 03e2410556 make cleos wallet accessable 2025-03-26 13:08:04 +05:00
Alex Ant 0f1f34f621 make unnesassary result 2025-03-26 11:42:05 +05:00
Alex Ant c08473618f some updates on contracts and tests 2025-03-22 13:58:51 +05:00
Alex Ant 3b4fb195bb update 2025-03-21 17:58:33 +05:00
Alex Ant 827eb10cc2 Merge branch 'dev' into TSK-900 2025-03-19 10:58:53 +05:00
Alex Ant 6e7c60de6e Создание кошелька при регистрации участника 2025-03-19 10:58:20 +05:00
kamilocean 54a4449e0b Merge branch 'dev' into kamilocean/sprint-4 2025-03-17 16:07:50 +03:00
Ilia Smirnov a6011f5d46 Merged readme fixes 2025-03-17 12:52:02 +03:00
Ilia Smirnov a1300e2b3c Merge branch 'dev' of github.com:coopenomics/mono into TSK-878 2025-03-17 12:49:40 +03:00
Alex Ant 8f58672403 Merge pull request #217 from coopenomics/dacom-dark-sun/sprint-4
Спринт-4 | dacom-dark-sun
2025-03-17 09:57:34 +03:00
4942 changed files with 51026 additions and 368939 deletions
Vendored
BIN
View File
Binary file not shown.
-3
View File
@@ -1,3 +0,0 @@
{
"agentCanUpdateSnapshot": true
}
-8
View File
@@ -1,8 +0,0 @@
---
description:
globs:
alwaysApply: true
---
ВАЖНО!!! Мы работаем в МОНО-репозитории pnpm, любая установка пакетов производится ЧЕРЕЗ ФИЛЬТР компонента в корне: pnpm add glob --filter notificator2.
ВАЖНО!!! НИКОГДА НЕ ЗАПУСКАЙ ПРИЛОЖЕНИЕ ИЛИ ЕГО СБОРКУ!!! Просто отчитайся что всё сделал.
-1
View File
@@ -10,7 +10,6 @@ alwaysApply: false
2. Типы IName, IChecksum256, ITimePointSec - это просто строки.
3. Домен должен быть полностью изолирован от инфраструктурных деталей.
4. Направление зависимостей - внутрь (к ядру, домену).
5. Домен, инфраструктура и приложение связаны через App.module, НЕ НУЖНО импортировать их друг в друга, а достаточно просто импортировать домен на уровне приложения чтобы использовать все экспорты домена.
## Структура проекта
- `domain/` - доменный слой (бизнес-логика, независимая от инфраструктуры)
+3 -3
View File
@@ -1,12 +1,12 @@
---
description:
globs:
alwaysApply: true
globs: **/desktop/**
---
# DESKTOP
- Пишем код фронтенда в архитектуре Feature Sliced Design (FSD).
- Стиль шаблонов PUG на composite API.
- Всегда создаём index.ts файлы для vue компонент.
- Создавай индексные файлы для vue через export {default as NAME} from './where', а на уровне выше, если это необходимо, делай export * from './ui'
- Всегда удаляем неиспользуемые импорты.
- Не используем emit избыточно. Вместо них используем композабл функции моделей фич, если необходимо. Emit только в крайнем случае.
-62
View File
@@ -1,62 +0,0 @@
---
description:
globs:
alwaysApply: false
---
Файл [main.py](mdc:monocoop/monocoop/monocoop/components/docs/main.py) автоматически генерит ссылки на документацию SDK и GraphQL, которые формируются и публикуются автоматически. При создании документации к методам всегда применяй ссылки на SDK и GraphQL по форме:
{{ get_sdk_doc("Mutations", "Accounts", "RegisterAccount") }} | {{ get_graphql_doc("Mutation.registerAccount") }}
### 🎯 Основные паттерны использования
**1. Стандартная структура для методов API:**
```markdown
## Название действия
{{ get_sdk_doc("Namespace", "Module", "Method") }} | {{ get_graphql_doc("Type.methodName") }}
{{ get_typedoc_input("Namespace.Module.Method") }}
Результат:
{{ get_typedoc_definition("Namespace.Module.Method", "IOutput") }}
```
**2. Описание + пример (для сложных методов):**
```markdown
{{ get_typedoc_desc("Namespace.Module.Method") }}
{{ get_typedoc_input("Namespace.Module.Method") }}
```
**3. Ссылки на типы данных:**
```markdown
У каждого аккаунта есть объект {{ get_graphql_definition("Account") }} в GraphQL-API
```
### 🔧 Макросы и их применение
| Макрос | Назначение | Пример использования |
|--------|------------|---------------------|
| `get_sdk_doc` | Ссылка на SDK документацию | `{{ get_sdk_doc("Mutations", "Payments", "CreateDeposit") }}` |
| `get_graphql_doc` | Ссылка на GraphQL операцию | `{{ get_graphql_doc("Query.getAccount") }}` |
| `get_graphql_definition` | Ссылка на GraphQL тип | `{{ get_graphql_definition("Account") }}` |
| `get_class_doc` | Ссылка на класс/метод SDK | `{{ get_class_doc("Document", "sign") }}` |
| `get_typedoc_input` | **Полный пример** TypeScript вызова | `{{ get_typedoc_input("Mutations.Auth.Login") }}` |
| `get_typedoc_definition` | **Структура интерфейса** | `{{ get_typedoc_definition("Mutations.Auth.Login", "IOutput") }}` |
| `get_typedoc_desc` | Описание + примеры | `{{ get_typedoc_desc("Mutations.Auth.Login") }}` |
| `get_typedoc_value` | Значение константы | `{{ get_typedoc_value("Constants.API_VERSION") }}` |
### 📝 Правила оформления
**ОБЯЗАТЕЛЬНО:**
- Всегда используй `get_typedoc_input` для демонстрации **КАК вызывать** метод
- Всегда используй `get_typedoc_definition` для показа **ЧТО возвращается**
- Комбинируй SDK и GraphQL ссылки через ` | `: `{{ get_sdk_doc(...) }} | {{ get_graphql_doc(...) }}`
**ЖЕЛАТЕЛЬНО:**
- Для сложных методов добавляй `get_typedoc_desc` в начало
- Используй `get_graphql_definition` для ссылок на типы данных в тексте
- Группируй связанные операции в одном разделе
**ФОРМАТ ССЫЛОК:**
- SDK: `"Namespace", "Module", "Method"` (3 аргумента)
- GraphQL: `"Type.methodName"` (точка между типом и методом)
- Определения: просто имя типа `"TypeName"`
-96
View File
@@ -1,96 +0,0 @@
---
description:
globs:
alwaysApply: false
---
# Структура файлов шаблонов документов и алгоритм правки
## Основные директории и файлы
### 1. Определение типов и интерфейсов (cooptypes)
**Путь:** `/cooptypes/src/cooperative/registry/[ID.DocumentName]/index.ts`
В этих файлах определяются:
- Интерфейсы данных (`IAgendaMeet`, etc.)
- Основная модель данных документа (`Model`)
- HTML шаблон документа (`context`)
- Переводы строк (`translations`)
- Пример данных для тестирования (`exampleData`)
**Для изменения шаблона нужно править:**
- Интерфейсы модели данных при изменении структуры
- HTML в переменной `context` для изменения внешнего вида
- Строки переводов в `translations.ru`
### 2. Схемы валидации (factory)
**Путь:** `/factory/src/Schema/[SchemaName].ts`
Содержат:
- JSON схемы для валидации данных
- Определение обязательных полей
**При изменении структуры данных:**
- Обновите соответствующую схему валидации
- Обновите список обязательных полей в `required`
### 3. Фабрики документов (factory)
**Путь:** `/factory/src/Actions/[ID.DocumentName].ts`
Обрабатывают:
- Получение данных из разных источников
- Сборку модели для шаблонизатора
- Валидацию данных по схеме
- Генерацию PDF
**При изменении логики сборки документа:**
- Обновите метод `generateDocument()`
- Добавьте новые источники данных
### 4. Шаблоны (factory)
**Путь:** `/factory/src/Templates/[ID.DocumentName].ts`
Связывают:
- Шаблон из cooptypes
- Схему валидации
- Метаданные документа
**После изменения в cooptypes:**
- Убедитесь, что схема в Templates соответствует новой модели
### 5. Тесты (factory)
**Путь:** `/factory/test/[category].test.ts`
Содержат:
- Тестовые данные для генерации документов
- Вызовы функции `testDocumentGeneration`
**После внесения изменений:**
- Обновите тестовые данные в соответствии с новой структурой
- Запустите тесты командой `pnpm test`
## Алгоритм внесения изменений
1. **Модификация типов**
- Изменить файл в `/cooptypes/src/cooperative/registry/[ID.DocumentName]/index.ts`
- Обновить интерфейсы, шаблон HTML и переводы
2. **Обновление схемы валидации**
- Изменить соответствующую схему в `/factory/src/Schema/`
- Убедиться, что обязательные поля совпадают с интерфейсом
3. **Компиляция библиотеки типов**
- Выполнить `cd /cooptypes && pnpm build`
4. **Обновление тестовых данных**
- Привести тестовые данные в соответствие с новой структурой
5. **Запуск тестов**
- Выполнить `cd /factory && pnpm test [filename].test.ts`
## Особенности шаблонизации
- Используется Nunjucks для шаблонов
- Поддерживаются конструкции: `{% if %}`, `{% for %}`, `{% trans %}`
- Переменные вставляются через `{{ variable }}`
- Переводы через `{% trans 'KEY' %}`
-261
View File
@@ -1,261 +0,0 @@
---
description:
globs: src/**/*.ts
alwaysApply: false
---
# Фабрика Документов Кооперативов
## Общая Архитектура
Фабрика документов — это система генерации PDF документов для кооперативов, построенная на TypeScript с использованием MongoDB для хранения данных. Система состоит из трех основных частей:
1. **registry/** — JSON-шаблоны документов (статические данные)
2. **factory/** — основная фабрика с логикой генерации
3. **cooptypes/** — типы данных и интерфейсы
## Структура Registry
В корневой папке `registry/` находятся JSON файлы с номерными названиями, представляющие шаблоны документов:
### Основные документы:
- `1.walletProgramAgreement.json` — соглашение о кошельке
- `2.regulationElectronicSignature.json` — регламент электронной подписи
- `3.privacyPolicy.json` — политика конфиденциальности
- `4.userAgreement.json` — пользовательское соглашение
- `50.CoopenomicsAgreement.json` — соглашение с партнерами
- `100.participantApplication.json` — заявление участника
- `101.selectBranchStatement.json` — заявление о выборе филиала
### Документы общих собраний (300-304):
- `300.annualGeneralMeetingAgenda.json` — предложение повестки дня
- `301.annualGeneralMeetingSovietDecision.json` — протокол заседания совета
- `302.annualGeneralMeetingNotification.json` — уведомление о собрании
- `303.annualGeneralMeetingVotingBallot.json` — бюллетень для голосования
- `304.annualGeneralMeetingDecision.json` — протокол общего собрания
### Инвестиционные документы (1000+):
- `1000.investAgreement.json` — инвестиционное соглашение
- `1001.investByResultStatement.json` — заявление о зачете по результатам
- `1002.investByResultAct.json` — акт зачета по результатам
- `1005.investByMoneyStatement.json` — заявление о зачете денежных средств
### Структура JSON-шаблона:
```json
{
"context": "<div>...HTML шаблон с переменными...</div>",
"model": {...данные для примера...},
"translation": {...переводы ключей...},
"object_model": {...схема объектной модели...}
}
```
## Фабрика (factory/)
### Основные компоненты:
#### src/index.ts — Главный класс Generator
```typescript
export class Generator implements IGenerator {
// Хранилище фабрик для каждого типа документа
factories: { [K in Numbers]: DocFactory<IGenerate> }
// MongoDB коннектор
public storage: MongoDBConnector
// Основной метод генерации
async generate(data: IGenerate, options?: IGenerationOptions): Promise<IGeneratedDocument>
}
```
#### Архитектура Factory Pattern:
- Базовый класс `DocFactory<T>` в `src/Factory/index.ts`
- Каждый документ имеет свою фабрику в `src/Actions/`
- Фабрики наследуются от `DocFactory` и реализуют метод `generateDocument()`
### Сервисы:
#### Services/Generator/ — PDF генерация
- `PDFService` — конвертирует HTML в PDF через WeasyPrint
- Использует шрифт Arial (base64)
- Добавляет метаданные в PDF
- Вычисляет SHA-256 хеш документа
#### Services/Templator/ — Шаблонизация
- Основан на Nunjucks
- Поддерживает кастомное расширение `{% trans %}` для переводов
- Рендерит HTML из шаблона с подстановкой переменных
#### Services/Validator/ — Валидация
- Использует AJV для JSON Schema валидации
- Поддерживает кастомные форматы (телефон)
- Локализация ошибок на русском языке
#### Services/Databazor/ — База данных
- `MongoDBConnector` — работа с MongoDB
- `DataService` — абстракция над данными
- Коллекции: `deltas`, `actions`, `documents`, и другие
### Модели данных (src/Models/):
#### Основные типы пользователей:
- `Individual` — физические лица (ФИО, паспорт, адрес)
- `Organization` — организации (ИНН, ОГРН, представитель)
- `Entrepreneur` — ИП (ФИО + ИНН/ОГРН)
#### Кооперативные данные:
- `Cooperative` — данные кооператива
- `PaymentMethod` — платежные методы
- `Vars` — переменные кооператива
- `Project` — проекты
### Система Action-ов:
Каждый документ имеет Action класс в `src/Actions/` с методом `generateDocument()`:
1. **Получение шаблона** — из локального Registry или MongoDB
2. **Сбор данных** — пользователь, кооператив, переменные, специфичные данные
3. **Валидация** — проверка по JSON схеме
4. **Рендеринг** — HTML из шаблона + данные
5. **PDF генерация** — HTML → PDF с метаданными
6. **Сохранение** — в MongoDB (если не skip_save)
## Система типов (cooptypes/)
### cooperative/registry/ — Типы документов
Каждый документ имеет папку с интерфейсами:
- `Action` — входные данные для генерации
- `Model` — модель данных для шаблона
- `Template` — структура шаблона
### contracts/ — Блокчейн контракты
- `registrator/` — регистрация кооперативов
- `soviet/` — управление советом
- `meet/` — общие собрания
- `wallet/`, `capital/`, `fund/` — финансовые операции
## База данных MongoDB
### Основные коллекции:
#### deltas — Состояние блокчейна
```javascript
{
block_num: number,
present: boolean,
code: string, // название контракта
scope: string, // область действия
table: string, // имя таблицы
primary_key: string,
value: {...} // данные записи
}
```
#### actions — Действия блокчейна
```javascript
{
block_num: number,
account: string,
name: string, // имя действия
receiver: string,
data: {...} // данные действия
}
```
#### documents — Сгенерированные документы
```javascript
{
hash: string, // SHA-256 хеш
binary: Uint8Array, // PDF данные
html: string, // HTML исходник
meta: { // метаданные
title: string,
created_at: string,
lang: string
}
}
```
### Таблицы данных:
- `coops` — кооперативы
- `meets` — собрания
- `questions` — вопросы собраний
- `decisions` — решения совета
- `individuals` — физические лица
- `organizations` — организации
- `entrepreneurs` — ИП
- `paymentMethods` — платежные методы
- `vars` — переменные кооперативов
## Генерация документов
### Процесс генерации:
1. **Вызов** `generator.generate(action, options)`
2. **Поиск фабрики** по `registry_id`
3. **Загрузка шаблона** (локально или из БД)
4. **Сбор данных** из MongoDB по `coopname`, `username`, `block_num`
5. **Создание модели** — объединение всех данных
6. **Валидация** модели по JSON схеме
7. **Рендеринг HTML** через Nunjucks
8. **Генерация PDF** через WeasyPrint
9. **Добавление метаданных** в PDF
10. **Вычисление хеша** SHA-256
11. **Сохранение** в MongoDB
### Пример использования:
```typescript
const generator = new Generator()
await generator.connect(mongoUri)
const document = await generator.generate({
registry_id: '300',
coopname: 'voskhod',
username: 'ant',
block_num: 0,
meet: {...},
questions: [...]
})
```
## Особенности реализации
### Шаблонизация:
- HTML шаблоны с CSS стилями
- Переменные в формате `{{variable.field}}`
- Условная логика `{% if condition %}`
- Циклы `{% for item in array %}`
- Переводы `{% trans 'KEY', var1, var2 %}`
### Подписи:
- Цифровые подписи вместо физических
- Текст "Подписано электронной подписью"
- Убраны подчеркивания для подписей
### Типы собраний:
- `regular` — очередное
- `extraordinary` — внеочередное
- Условная логика в шаблонах
### Филиалы:
- `coop.is_branched` — проверка на наличие филиалов
- "пайщиков" vs "уполномоченных" в зависимости от типа
### Форматирование дат:
- Формат: "г. Москва, 15 декабря 2024 г."
- Без кавычек вокруг дат
- Запятая после города
## Тестирование
### test/utils/index.ts — Тестовые утилиты:
- `preLoading()` — инициализация тестовых данных
- Создание кооператива, пользователей, платежных методов
- Настройка данных собраний и решений
- Очистка временных файлов
### Тестовые данные:
- Кооператив "ВОСХОД"
- Пользователи: ant, individual, entrepreneur
- Организации: voskhod, branch, exampleorg
- Собрания с вопросами и решениями
Фабрика поддерживает полный цикл создания документов кооператива от заявлений до протоколов собраний с возможностью кастомизации под разные типы кооперативов и требования.
-20
View File
@@ -1,20 +0,0 @@
---
globs: notifications/src/workflows/**/*.ts
---
# Правила валидации воркфлоу
## 🚫 ID воркфлоу
- **Длина ID не должна превышать 32 символа**
- Используйте описательные, но короткие идентификаторы малыми латинскими буквами и тире.
## 🚫 Условия в шаблонах
- **Запрещено использовать JavaScript выражения в шаблонах Novu**
- Нельзя использовать:
- Тернарные операторы: `{{condition ? "text1" : "text2"}}`
- Логические операторы: `{{field && "text"}}`
- Любые другие JS конструкции
## ✅ Рекомендации
- Добавляйте текстовые поля в payload для условной логики
- Вычисляйте значения на стороне сервера перед отправкой уведомления
- Используйте только простые переменные: `{{payload.fieldName}}`
+16 -85
View File
@@ -2,6 +2,8 @@ name: Build Docker Images
on:
push:
branches:
- testnet
tags:
- '*'
@@ -12,7 +14,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v3
with:
ref: ${{ github.ref }}
ref: testnet
fetch-depth: 0
- name: Debug info
@@ -26,23 +28,12 @@ jobs:
echo "Проверяем файлы в middlewares:"
ls -la components/desktop/src-ssr/middlewares/ || echo "Директория middlewares не найдена!"
- name: Set docker tags
- name: Set docker tag
run: |
if [[ $GITHUB_REF == refs/tags/* ]]; then
TAG_NAME=${GITHUB_REF#refs/tags/}
echo "DOCKER_TAG=$TAG_NAME" >> $GITHUB_ENV
# Проверяем, является ли тег продакшн-тегом (не содержит alpha, beta, rc и т.д.)
if [[ ! $TAG_NAME =~ -(alpha|beta|rc|test) ]]; then
echo "IS_PRODUCTION_TAG=true" >> $GITHUB_ENV
echo "Это продакшн тег, будем добавлять latest"
else
echo "IS_PRODUCTION_TAG=false" >> $GITHUB_ENV
echo "Это не продакшн тег, latest не добавляем"
fi
echo "DOCKER_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
else
echo "DOCKER_TAG=latest" >> $GITHUB_ENV
echo "IS_PRODUCTION_TAG=false" >> $GITHUB_ENV
fi
- name: Login to DockerHub
@@ -56,12 +47,6 @@ jobs:
run: |
docker build --target runtime -t dicoop/mono-base:${{ env.DOCKER_TAG }} .
docker push dicoop/mono-base:${{ env.DOCKER_TAG }}
# Если это продакшн тег, добавляем latest
if [[ "${{ env.IS_PRODUCTION_TAG }}" == "true" ]]; then
docker tag dicoop/mono-base:${{ env.DOCKER_TAG }} dicoop/mono-base:latest
docker push dicoop/mono-base:latest
fi
# Создаем сервисные образы на основе базового
- name: Build desktop image
@@ -70,11 +55,6 @@ jobs:
echo "CMD [\"pnpm\", \"-F\", \"@coopenomics/desktop\", \"run\", \"start\"]" >> Dockerfile.desktop
docker build -t dicoop/desktop:${{ env.DOCKER_TAG }} -f Dockerfile.desktop .
docker push dicoop/desktop:${{ env.DOCKER_TAG }}
if [[ "${{ env.IS_PRODUCTION_TAG }}" == "true" ]]; then
docker tag dicoop/desktop:${{ env.DOCKER_TAG }} dicoop/desktop:latest
docker push dicoop/desktop:latest
fi
- name: Build controller image
run: |
@@ -82,11 +62,6 @@ jobs:
echo "CMD [\"pnpm\", \"-F\", \"@coopenomics/controller\", \"run\", \"start\"]" >> Dockerfile.coopback
docker build -t dicoop/coopback:${{ env.DOCKER_TAG }} -f Dockerfile.coopback .
docker push dicoop/coopback:${{ env.DOCKER_TAG }}
if [[ "${{ env.IS_PRODUCTION_TAG }}" == "true" ]]; then
docker tag dicoop/coopback:${{ env.DOCKER_TAG }} dicoop/coopback:latest
docker push dicoop/coopback:latest
fi
- name: Build parser image
run: |
@@ -94,11 +69,6 @@ jobs:
echo "CMD [\"pnpm\", \"-F\", \"@coopenomics/parser\", \"run\", \"start\"]" >> Dockerfile.cooparser
docker build -t dicoop/cooparser:${{ env.DOCKER_TAG }} -f Dockerfile.cooparser .
docker push dicoop/cooparser:${{ env.DOCKER_TAG }}
if [[ "${{ env.IS_PRODUCTION_TAG }}" == "true" ]]; then
docker tag dicoop/cooparser:${{ env.DOCKER_TAG }} dicoop/cooparser:latest
docker push dicoop/cooparser:latest
fi
- name: Build notificator image
run: |
@@ -106,58 +76,19 @@ jobs:
echo "CMD [\"pnpm\", \"-F\", \"coop-notificator\", \"run\", \"start\"]" >> Dockerfile.notificator
docker build -t dicoop/notificator:${{ env.DOCKER_TAG }} -f Dockerfile.notificator .
docker push dicoop/notificator:${{ env.DOCKER_TAG }}
if [[ "${{ env.IS_PRODUCTION_TAG }}" == "true" ]]; then
docker tag dicoop/notificator:${{ env.DOCKER_TAG }} dicoop/notificator:latest
docker push dicoop/notificator:latest
fi
- name: Build notifications image
run: |
echo "FROM dicoop/mono-base:${{ env.DOCKER_TAG }}" > Dockerfile.notifications
echo "CMD [\"pnpm\", \"-F\", \"@coopenomics/notifications\", \"run\", \"sync\"]" >> Dockerfile.notifications
docker build -t dicoop/notifications:${{ env.DOCKER_TAG }} -f Dockerfile.notifications .
docker push dicoop/notifications:${{ env.DOCKER_TAG }}
if [[ "${{ env.IS_PRODUCTION_TAG }}" == "true" ]]; then
docker tag dicoop/notifications:${{ env.DOCKER_TAG }} dicoop/notifications:latest
docker push dicoop/notifications:latest
fi
# Отправка хука для деплоя
- name: Trigger deployment webhook
if: ${{ success() }}
run: |
if [[ $GITHUB_REF == refs/tags/*alpha* ]]; then
# Хук для тестнета (alpha теги)
curl -X POST ${{ vars.TESTNET_WEBHOOK_URL }} \
-H 'Content-Type: application/json' \
-d '${{ env.DOCKER_TAG }}'
elif [[ $GITHUB_REF == refs/tags/* ]]; then
# Хук для продакшена (остальные теги)
curl -X POST ${{ vars.PRODUCTION_WEBHOOK_URL }} \
-H 'Content-Type: application/json' \
-d '${{ env.DOCKER_TAG }}'
fi
# Уведомление в Telegram об успехе
- name: Telegram notify success
if: ${{ success() }}
run: |
if [[ "${{ env.IS_PRODUCTION_TAG }}" == "true" ]]; then
ADDITIONAL_INFO=" (с тегом latest)"
if [[ $GITHUB_REF == refs/tags/* ]]; then
# Хук для продакшена
curl -X POST ${{ vars.PRODUCTION_WEBHOOK_URL }} \
-H 'Content-Type: application/json' \
-d '${{ env.DOCKER_TAG }}'
else
ADDITIONAL_INFO=""
fi
curl -s -X POST https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage \
-d chat_id=${{ secrets.TELEGRAM_CHAT_ID }} \
-d text="✅ [GITHUB MONO] Успешная сборка контейнеров: $GITHUB_REPOSITORY ($GITHUB_REF) [${{ env.DOCKER_TAG }}]$ADDITIONAL_INFO"
# Уведомление в Telegram об ошибке
- name: Telegram notify failure
if: ${{ failure() }}
run: |
curl -s -X POST https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage \
-d chat_id=${{ secrets.TELEGRAM_CHAT_ID }} \
-d text="❌ [GITHUB MONO] Ошибка при сборке контейнеров: $GITHUB_REPOSITORY ($GITHUB_REF) [${{ env.DOCKER_TAG }}]"
# Хук для тестнета
curl -X POST ${{ vars.TESTNET_WEBHOOK_URL }} \
-H 'Content-Type: application/json' \
-d '${{ env.DOCKER_TAG }}'
fi
@@ -1,18 +0,0 @@
# .github/workflows/trigger-coopenomics.yml
name: Trigger Contracts Docs Deploy
on:
push:
branches: [dev, testnet, main, capital] # или когда нужно триггерить
jobs:
trigger-coopenomics:
runs-on: ubuntu-latest
steps:
- name: Trigger Coopenomics deployment
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.COOPENOMICS_PAT }}
repository: coopenomics/coopenomics # укажи правильный owner/repo
event-type: deploy_from_mono
client-payload: '{"repository": "${{ github.repository }}", "sha": "${{ github.sha }}", "ref": "${{ github.ref }}", "actor": "${{ github.actor }}"}'
+2 -16
View File
@@ -4,9 +4,6 @@ on:
push:
branches:
- main
- testnet
- dev
jobs:
build-and-publish-docs:
runs-on: ubuntu-latest
@@ -84,18 +81,6 @@ jobs:
mkdocs build
working-directory: ./components/docs
- name: Remove specific large file before publishing
run: |
# Удаляем конкретный большой файл sdk/typedoc.json
rm -f ./components/docs/site/sdk/typedoc.json
# Проверяем, что файл удален
if [ -f "./components/docs/site/sdk/typedoc.json" ]; then
echo "ERROR: typedoc.json still exists!"
exit 1
else
echo "SUCCESS: typedoc.json removed successfully"
fi
- name: Publish to GitHub Pages
run: npx gh-pages --nojekyll -d site --repo https://x-access-token:${GITHUB_TOKEN}@github.com/coopenomics/mono.git
working-directory: ./components/docs
@@ -104,4 +89,5 @@ jobs:
GIT_AUTHOR_EMAIL: github-actions@github.com
GIT_COMMITTER_NAME: github-actions
GIT_COMMITTER_EMAIL: github-actions@github.com
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+18 -16
View File
@@ -3,29 +3,31 @@ name: Publish Packages
on:
push:
tags:
- 'v*'
permissions:
contents: read
- '*'
jobs:
build-and-publish:
if: |
startsWith(github.ref, 'refs/tags/v') &&
!contains(github.ref, '-alpha')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Checkout repository
uses: actions/checkout@v3
- uses: actions/setup-node@v4
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 24
registry-url: https://registry.npmjs.org
node-version: 20
registry-url: 'https://registry.npmjs.org'
- run: npm install -g pnpm
- run: pnpm install
- run: pnpm lerna run build
- run: pnpm lerna publish from-package --yes
- name: Install pnpm
run: npm install -g pnpm
- name: Install dependencies
run: pnpm install
- name: Build all packages
run: pnpm lerna run build
- name: Publish to npm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: pnpm lerna publish from-package --yes --no-verify-access
-4
View File
@@ -5,7 +5,3 @@ blockchain-data/
scripts/changelog-prompt.md
scripts/changelog-release.md
scripts/release-info.md
components/docs/docs/sdk
dist/
.env
.DS_Store
-8
View File
@@ -1,8 +0,0 @@
{
"semi": false,
"singleQuote": true,
"printWidth": 120,
"plugins": [
"prettier-plugin-sort-imports"
]
}
-7
View File
@@ -1,7 +0,0 @@
{
"recommendations": [
"vue.volar",
"vue.vscode-typescript-vue-plugin"
]
}
+5 -63
View File
@@ -1,74 +1,16 @@
{
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": true,
"editor.formatOnSave": true,
"editor.tabCompletion": "onlySnippets",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint":"explicit"
},
"eslint.run": "onType",
"eslint.validate": ["javascript", "typescript", "vue"],
"i18n-ally.localesPaths": ["src/i18n"],
"typescript.format.enable": true,
"typescript.format.indentSwitchCase": false,
"typescript.validate.enable": true,
"notebook.defaultFormatter": "Vue.volar",
// Оптимизация для монорепозитория
"typescript.preferences.useAliasesForRenames": false,
"typescript.preferences.includePackageJsonAutoImports": "on",
"typescript.suggest.autoImports": true,
"typescript.suggest.paths": true,
"typescript.updateImportsOnFileMove.enabled": "always",
"typescript.workspaceSymbols.scope": "currentProject",
// Настройки для снижения нагрузки
"files.watcherExclude": {
"**/node_modules/**": true,
"**/dist/**": true,
"**/.git/objects/**": true,
"**/.git/subtree-cache/**": true,
"**/node_modules/*/**": true,
"**/.cache/**": true,
"**/.quasar/**": true,
"**/*.tsbuildinfo": true
},
"search.exclude": {
"**/node_modules": true,
"**/dist": true,
"**/.cache": true,
"**/.quasar": true,
"**/*.tsbuildinfo": true
},
"files.exclude": {
"**/.cache": true,
"**/*.tsbuildinfo": true,
"**/node_modules/.cache": true
},
// TypeScript server настройки для монорепозитория
"typescript.tsserver.maxTsServerMemory": 8192,
"typescript.tsserver.watchOptions": {
"excludeDirectories": [
"**/node_modules",
"**/dist",
"**/.cache",
"**/.quasar",
"**/build"
]
},
// Использовать локальный TypeScript из монорепо
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
// КРИТИЧЕСКИ ВАЖНО: включить project references для монорепозитория
"typescript.tsserver.useSyntaxServer": "auto",
"typescript.tsserver.experimental.enableProjectDiagnostics": true,
// Оптимизация для больших монорепозиториев
"typescript.disableAutomaticTypeAcquisition": true,
"typescript.surveys.enabled": false,
"[typescript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
}
"typescript.validate.enable": true
}
-73
View File
@@ -1,73 +0,0 @@
# AGENTS.md
## Cursor Cloud specific instructions
### Обзор проекта
Монорепозиторий «Цифровой Кооператив» (monocoop) — платформа управления кооперативами на блокчейне EOSIO. pnpm + Lerna. Компоненты в `components/`.
### Сервисы
| Компонент | Порт | Описание |
|-----------|------|----------|
| controller (coopback) | 2998 | NestJS GraphQL API |
| desktop | 3005 | Vue 3 + Quasar (SPA/SSR) |
| parser (cooparser) | 4000 | Индексация блокчейна через SHiP |
| node (Docker) | 8888 | EOSIO blockchain node |
| mongo (Docker) | 27017 | MongoDB (replica set) |
| redis (Docker) | 6379 | Redis |
| postgres (Docker) | 5432 | PostgreSQL 16 |
| SHiP (Docker) | 8070 | State History Plugin WebSocket |
### Полный цикл запуска dev-окружения
1. **Сборка shared-библиотек** (порядок важен):
```
pnpm --filter cooptypes run build
pnpm --filter @coopenomics/factory run build
pnpm --filter @coopenomics/sdk run build
pnpm --filter @coopenomics/notifications run build
```
2. **Сборка смарт-контрактов** (test-режим позволяет boot с 1 членом совета):
```
cd components/contracts && bash build-all.sh test
```
3. **Запуск инфраструктуры**:
```
docker compose up -d mongo postgres redis node
```
4. **Bootstrap системы**:
```
pnpm run boot
```
5. **Запуск парсера** (после boot; при первом запуске нужен `START_BLOCK=2` в `.env` для полного replay, потом вернуть `START_BLOCK=1`):
```
pnpm --filter @coopenomics/parser run dev
```
6. **Запуск controller и desktop**:
```
pnpm --filter @coopenomics/controller run dev
pnpm --filter @coopenomics/desktop run dev
```
### Критические gotchas
- **Node.js v20** обязателен. pnpm **v9.x** (не v10).
- **`/etc/hosts`**: для Cloud-окружения нужны записи `127.0.0.1 mongo` и `127.0.0.1 monoredis` — boot запускается на хосте и обращается к MongoDB по имени контейнера (replica set инициализирован с `mongo:27017`).
- **`docker-compose.override.yaml`**: в Cloud нужен override с `postgres: image: postgres:16` — `postgres:latest` (v18+) не работает с текущей конфигурацией volumes.
- **`.env` файлы**: controller и parser запускаются в Docker — используют docker hostnames (`mongo`, `node`, `monoredis`, `postgres`). Boot запускается на хосте — использует `127.0.0.1` с хостовыми портами (postgres: 5532, mongo: 27017).
- **Полный перезапуск**: `pnpm run reboot` — всё в одну команду. После первого reboot нужно `docker compose up -d --force-recreate coopback cooparser` если .env менялись.
- **SHiP порт 8070** — конфиг ноды `state-history-endpoint = 0.0.0.0:8070`, НЕ 8080. В `.env` парсера: `SHIP=ws://127.0.0.1:8070`.
- **CHAIN_ID** должен совпадать во всех `.env` файлах. Локальный chain_id: `cae86058a6d8698833afb474ab8a5ad8599c6cf54f9ebcf275dbac7055c16fe1`. В `.env-example` desktop стоял неправильный — исправлен.
- **postgres:latest (v18+)** не работает с текущей конфигурацией volume — используется `postgres:16`.
- **WeasyPrint** — системная зависимость для генерации PDF документов. Нужен в PATH: `pip install WeasyPrint==67`.
- **SSR-режим desktop в dev** — расширения (extensions) не рендерятся в SSR dev-сервере из-за проблемы гидратации Pinia: Vue-компоненты из маршрутов расширений сериализуются в `__INITIAL_STATE__` как plain-объекты `{__name, __file}` без render/setup функций. При гидратации клиент использует эти сломанные компоненты. Production-сборка (`quasar build --mode ssr`) работает корректно. **Для dev используйте SPA-режим**: `quasar dev` (без `--mode ssr`). Корневое решение: вынести `routes` из Pinia-стейта desktop-store, чтобы они не сериализовались при SSR.
- **Парсер и START_BLOCK**: при `START_BLOCK=1` на чистой БД парсер стартует с HEAD и вызывает `initializeFromBlockchain()` (загружает только кооператив и шаблоны, НЕ аккаунты). Для полного replay данных boot установите `START_BLOCK=2`, запустите, потом верните `START_BLOCK=1`.
- **boot/wallet-data и boot/blockchain-data** — создаются Docker от root, может потребоваться `sudo chown`.
- **Reboot-скрипты** (`components/boot/scripts/reboot.sh` и др.) ссылаются на docker-compose сервисы `cooparser` и `coopback`.
- **Тестовые учётные данные** (после boot): email `ivanov@example.com`, ключ `5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3`, пользователь `ant` (председатель).
-227
View File
@@ -1,230 +1,3 @@
# v2025.12.28-8
В этой версии представлен прототип трекера результатов интеллектуальной деятельности, реализован мост в 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): Модуль запросов и мутаций для контракта капитализации
### 🐛 Исправления ошибок
- [#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): Снижение точности валютных значений до двух знаков после запятой в документах
#releases
---
# v2025.12.28
В этой версии представлен прототип трекера результатов интеллектуальной деятельности, реализован мост в 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): Модуль запросов и мутаций для контракта капитализации
### 🐛 Исправления ошибок
- [#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): Снижение точности валютных значений до двух знаков после запятой в документах
#releases
---
# v2025.12.28
В этом релизе реализован смарт-контракт генерации и капитализации результатов интеллектуальной деятельности, завершена интеграция с учётными системами, улучшены интерфейсы и документация. Подробнее о контракте: https://coopenomics.world/contracts/group__public__capital.html
✨ Новые функции
- [#324](https://github.com/coopenomics/mono/issues/324): Реализован смарт-контракт генерации и капитализации результатов интеллектуальной деятельности ("Благорост")
- [#301](https://github.com/coopenomics/mono/issues/301): Контракт "Капитализация РИД" v0.2
- [#330](https://github.com/coopenomics/mono/issues/330): Прототип моста в 1С:Бухгалтерию: выгрузка документов и проводки по счетам
- [#322](https://github.com/coopenomics/mono/issues/322): Обновления ПО с нулевым даунтаймом по blue-green стратегии
- [#319](https://github.com/coopenomics/mono/issues/319): Палитра команд и быстрый доступ к страницам рабочих столов (cmk+k)
- [#308](https://github.com/coopenomics/mono/issues/308): Магазин приложений с поддержкой подключения нескольких рабочих столов одним приложением
- [#326](https://github.com/coopenomics/mono/issues/326): Минимальный интерфейс трекера результатов интеллектуальной деятельности по программе "Благорост"
- [#332](https://github.com/coopenomics/mono/issues/332): Прототип конструктора требований дополнительных документов при регистрации
🐛 Исправления ошибок
- [#312](https://github.com/coopenomics/mono/issues/312): Исправлена ошибка со статусом коммитов — подписка теперь работает корректно
- [#309](https://github.com/coopenomics/mono/issues/309): Исправлен баг с отображением чужих билетов времени в трекере
🔧 Улучшения
- [#325](https://github.com/coopenomics/mono/issues/325): Документирован смарт-контракт программы "Благорост"
- [#327](https://github.com/coopenomics/mono/issues/327): Подготовлена пользовательская документация цифрового кооператива по интерфейсам
- [#329](https://github.com/coopenomics/mono/issues/329): Интеграция и тестирование образовательной платформы LMS TUTOR на Wordpress
- [#328](https://github.com/coopenomics/mono/issues/328): Размещены прототипы мульти-лендингов на цифровой-кооператив.рф и coopenomics.world
- [#321](https://github.com/coopenomics/mono/issues/321): Встроена система проводок по фондам и интеграция с контрактами
- [#318](https://github.com/coopenomics/mono/issues/318): Настроены Loki & Grafana для выгрузки логов из контейнеров
- [#316](https://github.com/coopenomics/mono/issues/316): Завершён переход рабочего стола на GraphQL SDK
- [#314](https://github.com/coopenomics/mono/issues/314): Развёрнут GlitchTip как альтернатива Sentry
- [#307](https://github.com/coopenomics/mono/issues/307): Интеграция настроек контракта с нативными настройками приложения, поддержка импорта после конфигурации
- [#306](https://github.com/coopenomics/mono/issues/306): Собран модуль запросов и мутаций контракта капитализации
- [#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): Рефакторинг архитектуры, реализована двухконтурная шина данных и обработка микрофорков
- [#212](https://github.com/coopenomics/mono/issues/212): Уменьшена точность валютных значений в документах с четырёх до двух знаков
#releases
---
# v2025.9.1
В системе Кооперативной Экономики развернут смарт-контракт CAPITAL v0.2 для генерации и капитализации результатов интеллектуальной деятельности. Контракт описывает и обеспечивает:
- бизнес-процесс производства результатов интеллектуальной деятельности в кооперативах создателями, авторами, инвесторами, координаторами, мастерами и собственниками имущества в кооперации на проектах;
- приём результатов интеллектуальной деятельности в качестве паевых взносов в кооператив;
- распределение потока членских взносов среди вкладчиков;
- капитализацию результатов интеллектуальной деятельности новыми результатами по модели золотого сечения;
- оценку вкладов авторов и создателей по методу Водянова;
В основе математической модели контракта лежит принцип распределения справедливой выгоды между вкладчиками согласно концепции "Общественно-полезного времени", разработанной в рамках теорий Кузнецова и академика Глушкова при работе над проектом общегосударственной автоматизированной системы учета и обработки информации (ОГАС).
Подробнее в документации: https://coopenomics.world/contracts/group__public__capital.html
✨ Новые функции
- [#301](https://github.com/coopenomics/mono/issues/301): Реализация контракта "Капитализация" v0.2: регистрация вкладчиков, создание и управление проектами, поддержка инвестиций, ссуд, членских взносов, проведение голосований, расчет капитализации, интеграция с внешними контрактами, поддержка импорта данных.
#releases
---
# v2025.7.1-1
В этом релизе реализованы механизмы возврата паевого взноса пайщика и инструменты управления этим процессом для членов совета. Также внесены визуальные доработки интерфейсов для улучшения восприятия информации.
✨ Новые функции
- [#276](https://github.com/coopenomics/mono/issues/276): Реализовать путь возврата паевого взноса из кошелька пайщика
- [#281](https://github.com/coopenomics/mono/issues/281): Генерация заявления на возврат паевого взноса
- [#282](https://github.com/coopenomics/mono/issues/282): Генерация решения совета на возврат паевого взноса
- [#278](https://github.com/coopenomics/mono/issues/278): Введение методов управления возвратами паевых взносов в контроллере
- [#279](https://github.com/coopenomics/mono/issues/279): Отобразить исходящие платежи с кнопками управления в реестре платежей для совета
🐛 Исправления ошибок
- [#286](https://github.com/coopenomics/mono/issues/286): Поправить ошибки склонений времени
- [#262](https://github.com/coopenomics/mono/issues/262): Баг: рабочий стол совета включается, однако, страница всегда открывается со стола пайщика
- [#261](https://github.com/coopenomics/mono/issues/261): Баг: первая загрузка переадресует на главную страницу всегда - прямой переход на собрание становится недоступен.
🔧 Улучшения
- [#285](https://github.com/coopenomics/mono/issues/285): Корректировка дизайна кошелька, профиля, повестки совета, реестра документов, реестра платежей
- [#284](https://github.com/coopenomics/mono/issues/284): Разместить кошелек на главную вместо профиля
- [#283](https://github.com/coopenomics/mono/issues/283): Ввести лоадер на переходе между рабочими столами
- [#280](https://github.com/coopenomics/mono/issues/280): Мигрировать имеющиеся данные о входящих платежах в новую модель
- [#277](https://github.com/coopenomics/mono/issues/277): Рефакторинг модуля платежей: переход на унифицированную модель входящего и исходящего платежа
#releases
---
# v2025.6.14
Разработан модуль для проведения очередных общих собраний пайщиков. Исправлены баги, внесены улучшения пользовательского интерфейса.
✨ Новые функции
- [#264](https://github.com/coopenomics/mono/issues/264): Разработан смарт-контракт общего собрания пайщиков (meet)
- [#263](https://github.com/coopenomics/mono/issues/263): Реализован модуль оповещений на электронные почты по жизненному циклу общего собрания пайщиков
- [#273](https://github.com/coopenomics/mono/issues/273): Встроены реальные шаблоны документов общего собрания
- [#268](https://github.com/coopenomics/mono/issues/268): Добавлена страница результатов общего собрания
- [#267](https://github.com/coopenomics/mono/issues/267): Добавлена страница просмотра и скачивания бюллетеней и уведомлений по собранию
- [#270](https://github.com/coopenomics/mono/issues/270): Ссылка в оповещении ведет на страницу собрания с документом-уведомлением для подписи
🐛 Исправления ошибок
- [#262](https://github.com/coopenomics/mono/issues/262): Исправлено некорректное открытие рабочего стола совета
- [#261](https://github.com/coopenomics/mono/issues/261): Исправлена ошибка с редиректом при первой загрузке и прямом переходе на собрание
- [#259](https://github.com/coopenomics/mono/issues/259): Исправлены отступы в мобильной карточке пайщика
- [#258](https://github.com/coopenomics/mono/issues/258): Убран hover-эффект на документе и пайщике в таблице
🔧 Улучшения
- [#274](https://github.com/coopenomics/mono/issues/274): Настроена рассылка оповещений на почту при получении решения о проведении собрания
- [#272](https://github.com/coopenomics/mono/issues/272): Подписанные уведомления сохраняются и извлекаются из реестра по Graph-QL
- [#271](https://github.com/coopenomics/mono/issues/271): Ведется подсчет количества пайщиков в каждом кооперативе при добавлении и удалении
- [#260](https://github.com/coopenomics/mono/issues/260): Введен счетчик количества пайщиков в кооперативе на контракте регистратора
- [#256](https://github.com/coopenomics/mono/issues/256): Отображается статус членства каждого пайщика
- [#255](https://github.com/coopenomics/mono/issues/255): В разделе Платежи отображается ФИО/Наименование плательщика
- [#269](https://github.com/coopenomics/mono/issues/269): Реализована рассылка уведомлений перед началом собрания
- [#265](https://github.com/coopenomics/mono/issues/265): Проведена отладка и тестирование процесса общего собрания пайщиков
- [#266](https://github.com/coopenomics/mono/issues/266): Протестированы все процессы общего собрания
#releases
---
# v2025.5.14
В этом релизе реализован новый стандарт передачи и хранения документов по блокчейну с поддержкой неограниченного количества подписей и их валидацией. Также внесены улучшения в интерфейс и исправлены ошибки.
✨ Новые функции
- [#252](https://github.com/coopenomics/mono/issues/252): Внедрение обновленного стандарта хранения и передачи документов по блокчейну
- [#251](https://github.com/coopenomics/mono/issues/251): Реализация версионированного мигратора данных для контроллера кооператива
- [#244](https://github.com/coopenomics/mono/issues/244): Обновление стандарта сборки документов и переход на хэш-идентификаторы
🐛 Исправления ошибок
- [#249](https://github.com/coopenomics/mono/issues/249): Исправлена спутанная маршрутизация между рабочими столами кооперативов
- [#253](https://github.com/coopenomics/mono/issues/253): Исправлена избыточная точность суммы оплаты в заявлении на вступление
🔧 Улучшения
- [#259](https://github.com/coopenomics/mono/issues/259): Исправлены отступы в мобильной карточке пайщика на странице пайщиков
- [#258](https://github.com/coopenomics/mono/issues/258): Удалён hover-эффект для документов и пайщиков в таблице
- [#257](https://github.com/coopenomics/mono/issues/257): Добавлено сохранение светлой/тёмной темы в localStorage и восстановление при загрузке страницы
- [#256](https://github.com/coopenomics/mono/issues/256): Отображение статуса членства каждого пайщика в разделе "Пайщики"
- [#255](https://github.com/coopenomics/mono/issues/255): Отображение ФИО/Наименования плательщика в разделе "Платежи"
#releases
---
# v2025.5.2
В этом релизе рабочие столы переведены на серверный рендеринг, что улучшает стабильность развертывания и упрощает автоматизацию поставки ПО. Данный релиз является подготовительным к переходу на новый стандарт цифровых документов на платформе.
+13 -19
View File
@@ -1,4 +1,4 @@
FROM node:20-slim AS builder
FROM node:20-alpine AS builder
WORKDIR /app
@@ -12,29 +12,23 @@ RUN npm install -g pnpm lerna
# Используем версию pnpm, совместимую с существующим lock-файлом
RUN pnpm install
# Установка системных зависимостей для WeasyPrint и диагностических утилит (Debian/Ubuntu версии)
RUN apt-get update && apt-get install -y \
# Установка системных зависимостей для WeasyPrint
RUN apk add --no-cache \
python3 \
python3-pip \
python3-venv \
py3-pip \
gcc \
g++ \
musl-dev \
python3-dev \
libpango-1.0-0 \
libpangoft2-1.0-0 \
libpangocairo-1.0-0 \
libcairo2 \
libcairo2-dev \
pango \
zlib-dev \
jpeg-dev \
openjpeg-dev \
g++ \
libffi-dev \
shared-mime-info \
zlib1g-dev \
libjpeg-dev \
libopenjp2-7-dev \
procps \
wget \
harfbuzz-subset \
&& python3 -m venv /venv \
&& /venv/bin/pip install WeasyPrint==67 \
&& rm -rf /var/lib/apt/lists/*
&& /venv/bin/pip install WeasyPrint==62.3 \
&& rm -rf /var/cache/*
# Сборка всех компонентов
RUN lerna run build
+1 -1
View File
@@ -1,4 +1,4 @@
# Цифровой Кооператив
# MONO
Система управления взаимоотношениями в кооперативе. Включает в себя полный комплект программного обеспечения для подключения к платформе "Кооперативная Экономика" и управления взаимоотношениями с пайщиками в кооперативе и самим кооперативом на основе смарт-контрактов и простой электронной подписи.
-129
View File
@@ -1,129 +0,0 @@
# 📋 Резюме создания компонентов Notifications
## ✅ Что создано
### 1. 📚 Библиотека `@monocoop/notifications`
**Расположение:** `components/notifications/`
**Функции:**
- ✅ Типизированные интерфейсы для workflow с Zod
- ✅ Builder паттерн для создания workflow
- ✅ Базовые шаблоны для email, in-app, push, SMS
- ✅ Автоматическая конвертация Zod схем в JSON Schema для Novu
- ✅ Экспорт всех workflow для использования в других пакетах
**Структура:**
```
src/
├── types/ # Базовые типы и интерфейсы
├── base/ # Утилиты и настройки по умолчанию
├── workflows/ # Папки с workflow
│ └── welcome/ # Пример приветственного workflow
└── index.ts # Главный экспорт
```
### 2. 🚀 NestJS приложение `@monocoop/notificator2`
**Расположение:** `components/notificator2/`
**Функции:**
- ✅ Автоматический upsert всех workflow в Novu при запуске
- ✅ RESTful API для триггера уведомлений
- ✅ Типизированная валидация payload
- ✅ Health check endpoints
- ✅ Использует библиотеку notifications для типов
**API Endpoints:**
- `GET /api/notifications/health` - Health check
- `GET /api/notifications/workflows` - Список workflow
- `POST /api/notifications/trigger` - Универсальный триггер
- `POST /api/notifications/trigger/welcome` - Триггер welcome workflow
- `POST /api/notifications/workflows/upsert-all` - Принудительный upsert
## 🔧 Как использовать
### 1. Настройка библиотеки notifications
```bash
cd components/notifications
pnpm install # или npm install
pnpm build # для компиляции TypeScript
```
### 2. Настройка notificator2
```bash
cd components/notificator2
pnpm install # или npm install
# Настройка окружения
cp .env.example .env
# Добавить NOVU_API_KEY в .env
# Запуск
pnpm start:dev
```
### 3. Добавление нового workflow
**В библиотеке notifications:**
```typescript
// components/notifications/src/workflows/order/order-workflow.ts
export const orderWorkflow = WorkflowBuilder
.create<OrderPayload>()
.name('Order Confirmation')
.workflowId('order-confirmation')
.payloadSchema(orderPayloadSchema)
.addSteps([...])
.build();
```
**Регистрация в index.ts:**
```typescript
// components/notifications/src/workflows/index.ts
import { orderWorkflow } from './order';
export const allWorkflows = [
welcomeWorkflow,
orderWorkflow, // ← добавить новый workflow
];
```
### 4. Использование API
**Триггер welcome workflow:**
```bash
curl -X POST http://localhost:3000/api/notifications/trigger/welcome \
-H "Content-Type: application/json" \
-d '{
"subscriberId": "user-123",
"email": "user@example.com",
"payload": {
"userName": "Иван Иванов",
"userEmail": "user@example.com",
"age": 25
}
}'
```
## 🎯 Преимущества архитектуры
1. **Типобезопасность** - Zod схемы обеспечивают валидацию на уровне TypeScript и runtime
2. **Разделение ответственности** - Библиотека содержит только типы, сервер - логику
3. **Расширяемость** - Легко добавлять новые workflow
4. **Автоматизация** - Workflow автоматически синхронизируются с Novu
5. **Переиспользование** - Типы можно использовать в других частях системы
## 🔄 Workflow при запуске
1. **Запуск notificator2**
2. **Чтение всех workflow из библиотеки**
3. **Проверка существования в Novu**
4. **Создание/обновление workflow**
5. **Готов к приёму запросов на триггеры**
## 📁 Структура как в testFramework2.ts
Вся логика из `testFramework2.ts` была перенесена в structured архитектуру:
-`buildWorkflowData``WorkflowBuilder`
-`baseSteps``createEmailStep`, `createInAppStep`, etc.
-`upsertWorkflow``NovuService.upsertWorkflow`
-`triggerWorkflow``NovuService.triggerWorkflow`
- ✅ Типизация payload → Zod схемы + TypeScript типы
BIN
View File
Binary file not shown.
-5
View File
@@ -8,8 +8,3 @@ SERVER_URL=http://127.0.0.1:2998
MONGO_URI=mongodb://127.0.0.1:27017/cooperative-x
SIMPLE_EXPLORER_API=http://localhost:4000
SKIP_BLOCK_FETCH=TRUE
POSTGRES_HOST=127.0.0.1
POSTGRES_PORT=5432
POSTGRES_USERNAME=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DATABASE=voskhod
-22
View File
@@ -1,22 +0,0 @@
{
"configurations": [
{
"includePath": [
"${workspaceFolder}/**",
"${workspaceFolder}/lib/**",
"${workspaceFolder}/lib/eosiolib/contracts/**",
"${workspaceFolder}/lib/eosiolib/core/**"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE",
"int128_t=__int128_t",
"uint128_t=__uint128_t"
],
"cStandard": "c17",
"cppStandard": "c++20"
}
],
"version": 4
}
-68
View File
@@ -1,68 +0,0 @@
{
// Enable the ESlint flat config support
"prettier.enable": false,
"editor.formatOnSave": false,
// Auto fix
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
// "source.fixAll": "explicit",
// "source.organizeImports": "never"
},
// Silent the stylistic rules in you IDE, but still auto fix them
"eslint.rules.customizations": [
{
"rule": "style/*",
"severity": "off"
},
{
"rule": "*-indent",
"severity": "off"
},
{
"rule": "*-spacing",
"severity": "off"
},
{
"rule": "*-spaces",
"severity": "off"
},
{
"rule": "*-order",
"severity": "off"
},
{
"rule": "*-dangle",
"severity": "off"
},
{
"rule": "*-newline",
"severity": "off"
},
{
"rule": "*quotes",
"severity": "off"
},
{
"rule": "*semi",
"severity": "off"
}
],
// Enable eslint for all supported languages
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"html",
"markdown",
"json",
"jsonc",
"yaml"
],
// "[cpp]": {
// "editor.defaultFormatter": "ms-vscode.cpptools",
// },
// "C_Cpp.vcFormat.indent.withinParentheses": "indent",
"C_Cpp.errorSquiggles": "disabled"
}
+1 -1
View File
@@ -9,7 +9,7 @@ export default antfu(
},
{
rules: {
'no-unused-vars': 'off',
'no-unused-vars': 'warn',
'unused-imports/no-unused-vars': 'off',
'no-console': 'off',
'unicorn/prefer-number-properties': 'off',
+4 -11
View File
@@ -1,7 +1,7 @@
{
"name": "@coopenomics/boot",
"type": "module",
"version": "2026.2.22-2",
"version": "2025.5.5-2",
"private": true,
"packageManager": "pnpm@9.0.6",
"description": "",
@@ -15,19 +15,13 @@
"cleos": "esno src/index.ts cleos",
"unlock": "esno src/index.ts unlock",
"boot": "esno src/index.ts boot",
"boot:clean": "esno src/index.ts boot:clean",
"boot:extra": "esno src/index.ts boot:extra",
"reboot:clean": "cd components/boot/scripts && bash clean_reboot.sh",
"reboot:extra": "cd components/boot/scripts && bash extra_reboot.sh",
"create-coop": "esno src/index.ts create-coop",
"clear": "./scripts/clear.sh",
"clear": "esno src/index.ts clear",
"start": "./scripts/restart.sh",
"stop": "./scripts/stop.sh"
},
"dependencies": {
"@coopenomics/factory": "workspace:*",
"@types/pg": "^8.16.0",
"axios": "^1.13.5",
"axios": "^1.6.8",
"chai": "^5.1.2",
"commander": "^12.1.0",
"cooptypes": "workspace:*",
@@ -38,8 +32,7 @@
"eosjs-ecc": "^4.0.7",
"execa": "^9.5.2",
"mocha": "^10.7.3",
"mongoose": "^9.2.2",
"pg": "^8.16.3"
"mongoose": "^8.10.0"
},
"devDependencies": {
"@antfu/eslint-config": "^2.18.0",
-47
View File
@@ -1,47 +0,0 @@
#!/bin/bash
# Останавливаем и удаляем контейнеры вместе с volumes
echo "Останавливаем и удаляем контейнеры с volumes..."
docker compose down -v mongo postgres cooparser || true
# Останавливаем blockchain контейнер перед удалением данных
echo "Останавливаем blockchain контейнер..."
docker compose stop node || true
# Удаляем blockchain data
echo "Удаляем blockchain data..."
# sudo chmod -R 755 ../blockchain-data/ 2>/dev/null || true
sudo rm -rf ../blockchain-data/
# Пересоздаем и запускаем базы данных
echo "Пересоздаем и запускаем базы данных..."
docker compose up -d mongo postgres
# Ждем готовности MongoDB
echo "Ждем готовности MongoDB..."
until docker exec mongo mongosh --eval "db.adminCommand('ping')" --quiet > /dev/null 2>&1; do
echo "MongoDB еще не готов, ждем..."
sleep 2
done
echo "MongoDB готов!"
# Ждем готовности PostgreSQL
echo "Ждем готовности PostgreSQL..."
until docker exec postgres pg_isready -U postgres -d voskhod > /dev/null 2>&1; do
echo "PostgreSQL еще не готов, ждем..."
sleep 2
done
echo "PostgreSQL готов!"
# Запускаем boot процесс
echo "Запускаем boot процесс..."
pnpm run boot:clean
# Запускаем parser
echo "Запускаем parser..."
docker compose up -d cooparser
echo "Запускаем контроллер..."
docker compose restart coopback || true
echo "Перезапуск завершен!"
-34
View File
@@ -1,34 +0,0 @@
#!/bin/bash
# Останавливаем и удаляем контейнеры вместе с volumes
echo "Останавливаем и удаляем контейнеры с volumes..."
docker compose down -v mongo postgres cooparser || true
# Останавливаем blockchain контейнер перед удалением данных
echo "Останавливаем blockchain контейнер..."
docker compose stop node || true
# Удаляем blockchain data
echo "Удаляем blockchain data..."
# sudo chmod -R 755 ../blockchain-data/ 2>/dev/null || true
sudo rm -rf ../blockchain-data/
# Пересоздаем и запускаем базы данных
echo "Пересоздаем и запускаем базы данных..."
docker compose up -d mongo postgres
# Ждем готовности MongoDB
echo "Ждем готовности MongoDB..."
until docker exec mongo mongosh --eval "db.adminCommand('ping')" --quiet > /dev/null 2>&1; do
echo "MongoDB еще не готов, ждем..."
sleep 2
done
echo "MongoDB готов!"
# Ждем готовности PostgreSQL
echo "Ждем готовности PostgreSQL..."
until docker exec postgres pg_isready -U postgres -d voskhod > /dev/null 2>&1; do
echo "PostgreSQL еще не готов, ждем..."
sleep 2
done
echo "PostgreSQL готов!"
-51
View File
@@ -1,51 +0,0 @@
#!/bin/bash
# Останавливаем контроллер перед очисткой данных
echo "Останавливаем контроллер..."
docker compose down coopback || true
# Останавливаем и удаляем контейнеры вместе с volumes
echo "Останавливаем и удаляем контейнеры с volumes..."
docker compose down -v mongo postgres cooparser || true
# Останавливаем blockchain контейнер перед удалением данных
echo "Останавливаем blockchain контейнер..."
docker compose stop node || true
# Удаляем blockchain data
echo "Удаляем blockchain data..."
# sudo chmod -R 755 ../blockchain-data/ 2>/dev/null || true
sudo rm -rf ../blockchain-data/
# Пересоздаем и запускаем базы данных
echo "Пересоздаем и запускаем базы данных..."
docker compose up -d mongo postgres
# Ждем готовности MongoDB
echo "Ждем готовности MongoDB..."
until docker exec mongo mongosh --eval "db.adminCommand('ping')" --quiet > /dev/null 2>&1; do
echo "MongoDB еще не готов, ждем..."
sleep 2
done
echo "MongoDB готов!"
# Ждем готовности PostgreSQL
echo "Ждем готовности PostgreSQL..."
until docker exec postgres pg_isready -U postgres -d voskhod > /dev/null 2>&1; do
echo "PostgreSQL еще не готов, ждем..."
sleep 2
done
echo "PostgreSQL готов!"
# Запускаем boot процесс
echo "Запускаем boot процесс..."
pnpm run boot:extra
# Запускаем parser
echo "Запускаем parser..."
docker compose up -d cooparser
echo "Запускаем контроллер..."
docker compose up -d coopback
echo "Перезапуск завершен!"
-47
View File
@@ -1,47 +0,0 @@
#!/bin/bash
# Останавливаем и удаляем контейнеры вместе с volumes
echo "Останавливаем и удаляем контейнеры с volumes..."
docker compose down -v mongo postgres cooparser || true
# Останавливаем blockchain контейнер перед удалением данных
echo "Останавливаем blockchain контейнер..."
docker compose stop node || true
# Удаляем blockchain data
echo "Удаляем blockchain data..."
# sudo chmod -R 755 ../blockchain-data/ 2>/dev/null || true
sudo rm -rf ../blockchain-data/
# Пересоздаем и запускаем базы данных
echo "Пересоздаем и запускаем базы данных..."
docker compose up -d mongo postgres
# Ждем готовности MongoDB
echo "Ждем готовности MongoDB..."
until docker exec mongo mongosh --eval "db.adminCommand('ping')" --quiet > /dev/null 2>&1; do
echo "MongoDB еще не готов, ждем..."
sleep 2
done
echo "MongoDB готов!"
# Ждем готовности PostgreSQL
echo "Ждем готовности PostgreSQL..."
until docker exec postgres pg_isready -U postgres -d voskhod > /dev/null 2>&1; do
echo "PostgreSQL еще не готов, ждем..."
sleep 2
done
echo "PostgreSQL готов!"
# Запускаем boot процесс
echo "Запускаем boot процесс..."
pnpm run boot
# Запускаем parser
echo "Запускаем parser..."
docker compose up -d cooparser
echo "Запускаем контроллер..."
docker compose restart coopback || true
echo "Перезапуск завершен!"
+44 -133
View File
@@ -15,7 +15,6 @@ import EosApi from 'eosjs-api'
// @ts-expect-error заменить на antelope
import ecc from 'eosjs-ecc'
import config from '../configs'
import { consoleIt } from '../tests/shared/consoleIt'
import type { Contract, Feature, Keys, Network } from '../types'
@@ -44,8 +43,8 @@ export default class Blockchain {
this.api = new Api({
rpc,
signatureProvider,
textDecoder: new TextDecoder() as any,
textEncoder: new TextEncoder() as any,
textDecoder: new TextDecoder(),
textEncoder: new TextEncoder(),
})
this.api.read = await EosApi({ httpEndpoint: res })
}
@@ -608,10 +607,10 @@ export default class Blockchain {
}
async registerUser(
params: RegistratorContract.Actions.RegisterUser.IRegisterUser,
params: RegistratorContract.Actions.RegisterUser.IRegistrerUser,
) {
await this.update_pass_instance()
console.dir(params, { depth: null })
await this.api.transact(
{
actions: [
@@ -624,7 +623,9 @@ export default class Blockchain {
permission: 'active',
},
],
data: params,
data: {
...params,
},
},
],
},
@@ -637,69 +638,9 @@ export default class Blockchain {
console.log('Новый пользователь: ', params)
}
async addUser(
params: RegistratorContract.Actions.AddUser.IAddUser,
) {
await this.update_pass_instance()
console.dir(params, { depth: null })
await this.api.transact(
{
actions: [
{
account: RegistratorContract.contractName.production,
name: RegistratorContract.Actions.AddUser.actionName,
authorization: [
{
actor: params.coopname,
permission: 'active',
},
],
data: params,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
console.log('Добавлен пользователь: ', params.username)
}
async changeKey(
params: RegistratorContract.Actions.ChangeKey.IChangeKey,
) {
await this.update_pass_instance()
console.dir(params, { depth: null })
await this.api.transact(
{
actions: [
{
account: RegistratorContract.contractName.production,
name: RegistratorContract.Actions.ChangeKey.actionName,
authorization: [
{
actor: params.coopname,
permission: 'active',
},
],
data: params,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
console.log('Изменён ключ для пользователя: ', params.username)
}
async transfer(params: TokenContract.Actions.Transfer.ITransfer) {
await this.update_pass_instance()
console.dir(params, { depth: null })
const result = await this.api.transact(
{
actions: [
@@ -807,7 +748,7 @@ export default class Blockchain {
name: SovietContract.Actions.Decisions.VoteFor.actionName,
authorization: [
{
actor: params.username,
actor: params.member,
permission: 'active',
},
],
@@ -892,7 +833,7 @@ export default class Blockchain {
}
async ConfirmPayment(
params: GatewayContract.Actions.CompleteIncome.ICompleteIncome,
params: GatewayContract.Actions.CompleteIncome.ICompeteIncome,
) {
await this.update_pass_instance()
@@ -923,6 +864,38 @@ export default class Blockchain {
console.log('Заявление на вступление отправлено в совет: ', params)
}
async createBoard(
params: SovietContract.Actions.Boards.CreateBoard.ICreateboard,
) {
await this.update_pass_instance()
await this.api.transact(
{
actions: [
{
account: SovietContract.contractName.production,
name: SovietContract.Actions.Boards.CreateBoard.actionName,
authorization: [
{
actor: params.username,
permission: 'active',
},
],
data: {
...params,
},
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
console.log('Совет создан: ', params)
}
async createDraft(params: DraftContract.Actions.CreateDraft.ICreateDraft) {
await this.update_pass_instance()
@@ -950,7 +923,7 @@ export default class Blockchain {
},
)
console.log('Шаблон создан: ', params.registry_id)
console.log('Шаблон создан: ', params)
}
async createTranslation(
@@ -982,7 +955,7 @@ export default class Blockchain {
},
)
console.log('Перевод создан: ', params.registry_id)
console.log('Перевод создан: ', params)
}
async createProgram(
@@ -1016,66 +989,4 @@ export default class Blockchain {
console.log('Программа установлена: ', params)
}
async createBoard(params: SovietContract.Actions.Boards.CreateBoard.ICreateboard) {
await this.update_pass_instance()
console.dir(params, { depth: null })
const result = await this.api.transact(
{
actions: [
{
account: SovietContract.contractName.production,
name: SovietContract.Actions.Boards.CreateBoard.actionName,
authorization: [
{
actor: params.coopname,
permission: 'active',
},
],
data: {
...params,
},
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
console.log('Создан совет: ', params)
return result
}
/**
* Выполняет транзакцию и автоматически выводит консоль логи через consoleIt
* @param actions - массив действий для транзакции
* @param options - опции транзакции (по умолчанию стандартные)
* @param options.blocksBehind - количество блоков позади для подтверждения
* @param options.expireSeconds - время жизни транзакции в секундах
* @returns результат транзакции
*/
async transactWithLogs(
actions: any[],
options: { blocksBehind?: number, expireSeconds?: number } = {},
): Promise<any> {
await this.update_pass_instance()
const defaultOptions = {
blocksBehind: 3,
expireSeconds: 30,
...options,
}
const result = await this.api.transact(
{ actions },
defaultOptions,
)
// Автоматически выводим консоль логи
consoleIt(result)
return result
}
}
+3 -3
View File
@@ -10,7 +10,7 @@ enable-stale-production = true
read-only-read-window-time-us = 120000
net-threads = 2
max-transaction-time=10000
max-transaction-time=2000
http-server-address = 0.0.0.0:8888
p2p-listen-endpoint = 0.0.0.0:9876
@@ -26,8 +26,8 @@ max-body-size = 10485760
abi-serializer-max-time-ms = 200000
contracts-console = true
max-block-cpu-usage-threshold-us = 200000
max-block-net-usage-threshold-bytes = 800000
max-block-cpu-usage-threshold-us = 5000
max-block-net-usage-threshold-bytes = 1024
verbose-http-errors = true
chain-state-history = true
-10
View File
@@ -65,16 +65,6 @@ export default [
path: path.resolve(process.cwd(), '../contracts/build/contracts/fund'),
target: 'fund',
},
{
name: 'ledger',
path: path.resolve(process.cwd(), '../contracts/build/contracts/ledger'),
target: 'ledger',
},
{
name: 'test',
path: path.resolve(process.cwd(), '../contracts/build/contracts/test'),
target: 'test',
},
{
name: 'contributor',
path: path.resolve(process.cwd(), '../contracts/build/contracts/contributor'),
+2 -2
View File
@@ -9,8 +9,8 @@
"net_usage_leeway": 500,
"context_free_discount_net_usage_num": 20,
"context_free_discount_net_usage_den": 100,
"max_block_cpu_usage": 1000000,
"target_block_cpu_usage_pct": 1000,
"max_block_cpu_usage": 300000,
"target_block_cpu_usage_pct": 500,
"max_transaction_cpu_usage": 290000,
"min_transaction_cpu_usage": 100,
"max_transaction_lifetime": 3600,
+4 -12
View File
@@ -53,7 +53,7 @@ export default {
allocations: [
{
to: 'eosio',
quantity: `100000.0000 ${SYMBOL}`,
quantity: `10000.0000 ${SYMBOL}`,
},
],
accounts: [
@@ -91,14 +91,6 @@ export default {
name: 'loan',
code_permissions_to: ['loan'],
},
{
name: 'ledger',
code_permissions_to: ['ledger'],
},
{
name: 'test',
code_permissions_to: ['test'],
},
{
name: 'meet',
code_permissions_to: ['meet'],
@@ -135,9 +127,9 @@ export default {
name: 'contributor',
code_permissions_to: ['contributor'],
},
// {
// name: provider_chairman,
// },
{
name: provider_chairman,
},
{
name: provider,
code_permissions_to: ['registrator'],
-3
View File
@@ -3,7 +3,6 @@ import * as path from 'node:path'
import mongoose from 'mongoose'
export async function clearDB(): Promise<void> {
// Очистка MongoDB
// eslint-disable-next-line node/prefer-global/process
await mongoose.connect(process.env.MONGO_URI as string)
@@ -38,8 +37,6 @@ export async function clearDB(): Promise<void> {
catch (e) {
console.error('Ошибка при удалении:', e)
}
// PostgreSQL будет пересоздан полностью в скрипте extra_reboot.sh
}
export async function clearDirectory(dirPath: string): Promise<void> {
+5 -109
View File
@@ -6,8 +6,7 @@ import { config } from 'dotenv'
import { execCommand } from './docker/exec'
import { stopContainerByName } from './docker/stop'
import { runContainer } from './docker/run'
import { boot, bootClean, bootExtra } from './init/booter'
import { startCoop } from './init/cooperative'
import { boot } from './init/booter'
import { sleep } from './utils'
import { checkHealth } from './docker/health'
import { clearDB, clearDirectory, deleteFile } from './docker/purge'
@@ -121,13 +120,15 @@ program
}
try {
await clearDirectory(basePath)
await clearDB()
await runContainer()
await sleep(5000)
await checkHealth()
await boot()
console.log(`
Boot is
.d8888b. .d88888b. 888b d888 8888888b. 888 8888888888 88888888888 8888888888
d88P Y88b d88P" "Y88b 8888b d8888 888 Y88b 888 888 888 888
@@ -137,7 +138,7 @@ d88P Y88b d88P" "Y88b 8888b d8888 888 Y88b 888 888 888
888 888 888 888 888 Y8P 888 888 888 888 888 888
Y88b d88P Y88b. .d88P 888 " 888 888 888 888 888 888
"Y8888P" "Y88888P" 888 888 888 88888888 8888888888 888 8888888888
`)
process.exit(0)
}
@@ -147,111 +148,6 @@ Y88b d88P Y88b. .d88P 888 " 888 888 888 888 888
}
})
// Команда для получения списка контейнеров
program
.command('boot:clean')
.description('Boot infrastructure without initial data and cooperatives')
.action(async () => {
try {
await stopContainerByName('node')
}
catch (e) {
console.log('Нет контейнера для остановки. Стартуем новый..')
}
try {
await runContainer()
await sleep(5000)
await checkHealth()
await bootClean()
console.log(`
Boot Clean is
.d8888b. .d88888b. 888b d888 8888888b. 888 8888888888 88888888888 8888888888
d88P Y88b d88P" "Y88b 8888b d8888 888 Y88b 888 888 888 888
888 888 888 888 88888b.d88888 888 888 888 888 888 888
888 888 888 888Y88888P888 888 d88P 888 8888888 888 8888888
888 888 888 888 Y888P 888 8888888P" 888 888 888 888
888 888 888 888 888 Y8P 888 888 888 888 888 888
Y88b d88P Y88b. .d88P 888 " 888 888 888 888 888 888
"Y8888P" "Y88888P" 888 888 888 88888888 8888888888 888 8888888888
`)
process.exit(0)
}
catch (error) {
console.error('Failed to boot clean:', error)
process.exit(1)
}
})
// Команда для получения списка контейнеров
program
.command('boot:extra')
.description('Boot infrastructure with extended initial data and additional shareholders')
.action(async () => {
try {
await stopContainerByName('node')
}
catch (e) {
console.log('Нет контейнера для остановки. Стартуем новый..')
}
try {
await runContainer()
await sleep(5000)
await checkHealth()
await bootExtra()
const councilMembers = [
{ username: 'ant', fullName: 'Иванов Иван Иванович', email: 'ivanov@example.com' },
{ username: 'petr', fullName: 'Сидоров Петр Сергеевич', email: 'sidorov@example.com' },
{ username: 'anna', fullName: 'Петрова Анна Ивановна', email: 'petrova@example.com' },
{ username: 'mikhail', fullName: 'Кузнецов Михаил Андреевич', email: 'kuznetsov@example.com' },
{ username: 'olga', fullName: 'Соколова Ольга Викторовна', email: 'sokolova@example.com' },
]
console.log('\nЧлены совета (логин / ФИО / email):')
for (const member of councilMembers)
console.log(` - ${member.username}: ${member.fullName} / ${member.email}`)
console.log(`
Boot Extra is
.d8888b. .d88888b. 888b d888 8888888b. 888 8888888888 88888888888 8888888888
d88P Y88b d88P" "Y88b 8888b d8888 888 Y88b 888 888 888 888
888 888 888 888 88888b.d88888 888 888 888 888 888 888
888 888 888 888Y88888P888 888 d88P 888 8888888 888 8888888
888 888 888 888 Y888P 888 8888888P" 888 888 888 888
888 888 888 888 888 Y8P 888 888 888 888 888 888
Y88b d88P Y88b. .d88P 888 " 888 888 888 888 888 888
"Y8888P" "Y88888P" 888 888 888 88888888 8888888888 888 8888888888
`)
process.exit(0)
}
catch (error) {
console.error('Failed to boot extra:', error)
process.exit(1)
}
})
// Команда для создания кооператива
program
.command('create-coop')
.description('Create cooperative after clean boot')
.action(async () => {
try {
await startCoop()
console.log('Кооператив успешно создан')
process.exit(0)
}
catch (error) {
console.error('Failed to create cooperative:', error)
process.exit(1)
}
})
// Команда для получения списка контейнеров
program
.command('clear')
+3 -31
View File
@@ -1,35 +1,7 @@
import config from '../configs'
import { initExtensionsInPostgres, initSystemStatus } from '../postgres-init'
import { installExtraData, installInitialData, startInfra } from './infra'
import { CooperativeClass, startCoop } from './cooperative'
import { startInfra } from "./infra"
import { startCoop } from "./cooperative"
export async function boot() {
const blockchain = await startInfra()
await installInitialData(blockchain, false) // Создать базовый совет
console.log('Инициализируем статус системы в PostgreSQL')
await initSystemStatus()
await startInfra()
await startCoop()
}
export async function bootClean() {
const blockchain = await startInfra()
console.log('Создаём программы (Благорост и маркетплейс)')
const cooperative = new CooperativeClass(blockchain)
await cooperative.createPrograms(config.provider)
}
export async function bootExtra() {
const blockchain = await startInfra()
await installInitialData(blockchain, true) // Создать расширенный совет (устанавливает статус 'active' в MongoDB)
await installExtraData(blockchain) // Добавить дополнительных пайщиков
console.log('Инициализируем статус системы в PostgreSQL')
await initSystemStatus() // Устанавливает статус 'active' в PostgreSQL
console.log('Инициализируем extensions в PostgreSQL')
await initExtensionsInPostgres() // Инициализирует таблицу extensions с данными capital
}
+47 -89
View File
@@ -1,9 +1,12 @@
import type { Cooperative } from 'cooptypes'
import type { Keys } from '../types'
import axios from 'axios'
import { describe, expect, it } from 'vitest'
import { Registry } from '@coopenomics/factory'
import { Cooperative as TCooperative } from 'cooptypes'
import type { Account, Contract, Keys } from '../types'
import config, { GOVERN_SYMBOL, SYMBOL } from '../configs'
import Blockchain from '../blockchain'
import { sendPostToCoopbackWithSecret, sleep } from '../utils'
import { generateRandomSHA256 } from '../utils/randomHash'
import { processLastDecision } from '../tests/soviet/processLastDecision'
const test_hash
= '157192b276da23cc84ab078fc8755c051c5f0430bf4802e55718221e6b76c777'
@@ -12,23 +15,11 @@ const test_sign
const test_pkey = 'EOS5JhMfxbsNebajHcTEK8yC9uNN9Dit9hEmzE8ri8yMhhzxrLg3J'
const test_meta = JSON.stringify({})
const document: Cooperative.Document.ISignedDocument2 = {
const document = {
hash: test_hash,
signatures: [
{
id: 1,
signed_hash: test_hash,
public_key: test_pkey,
signed_at: '2025-05-14T12:22:26',
meta: test_meta,
signer: 'cooperative1',
signature: test_sign,
},
],
signature: test_sign,
public_key: test_pkey,
meta: test_meta,
version: '1.0.0',
doc_hash: test_hash,
meta_hash: test_hash,
}
export class CooperativeClass {
@@ -42,7 +33,7 @@ export class CooperativeClass {
await this.blockchain.createProgram({
coopname,
username: coopname,
type: 'marketplace',
type: 'cooplace',
title: 'КООПЛЕЙС',
announce: '',
description: '',
@@ -54,38 +45,6 @@ export class CooperativeClass {
meta: '',
is_can_coop_spend_share_contributions: false,
})
await this.blockchain.createProgram({
coopname,
username: coopname,
type: 'generator',
title: 'Целевая потребительская программа "Генератор"',
announce: '',
description: '',
preview: '',
images: '',
calculation_type: 'free',
fixed_membership_contribution: `${Number(0).toFixed(4)} ${GOVERN_SYMBOL}`,
membership_percent_fee: 0, // 10%
meta: '',
is_can_coop_spend_share_contributions: true,
})
await this.blockchain.createProgram({
coopname,
username: coopname,
type: 'blagorost',
title: 'Целевая потребительская программа "Благорост"',
announce: '',
description: '',
preview: '',
images: '',
calculation_type: 'free',
fixed_membership_contribution: `${Number(0).toFixed(4)} ${GOVERN_SYMBOL}`,
membership_percent_fee: 0, // 10%
meta: '',
is_can_coop_spend_share_contributions: true,
})
}
async createCooperative(username: string, keys?: Keys) {
@@ -123,36 +82,30 @@ export class CooperativeClass {
income_hash: registration_hash,
})
await processLastDecision(this.blockchain, config.provider)
// console.log('Голосуем по решению в провайдере')
console.log('Голосуем по решению в провайдере')
// await this.blockchain.votefor({
// version: '1.0.0',
// signed_at: '2025-05-14T12:22:26',
// signed_hash: test_hash,
// signature: test_sign,
// public_key: test_pkey,
// coopname: config.provider,
// username: config.provider_chairman,
// decision_id: 1,
// })
await this.blockchain.votefor({
coopname: config.provider,
member: config.provider_chairman,
decision_id: 1,
})
// console.log('Утверждаем решение в провайдере')
console.log('Утверждаем решение в провайдере')
// await this.blockchain.authorize({
// coopname: config.provider,
// chairman: config.provider_chairman,
// decision_id: 1,
// document,
// })
await this.blockchain.authorize({
coopname: config.provider,
chairman: config.provider_chairman,
decision_id: 1,
document,
})
// console.log('Исполняем решение в провайдере')
console.log('Исполняем решение в провайдере')
// await this.blockchain.exec({
// executer: config.provider_chairman,
// coopname: config.provider,
// decision_id: 1,
// })
await this.blockchain.exec({
executer: config.provider_chairman,
coopname: config.provider,
decision_id: 1,
})
console.log('Отправляем подписанное положение о ЦПП Кошелька оператору')
@@ -161,7 +114,12 @@ export class CooperativeClass {
administrator: config.provider,
username: username!,
agreement_type: 'wallet',
document,
document: { // отправляем произвольный документ с валидной подписью
hash: '157192B276DA23CC84AB078FC8755C051C5F0430BF4802E55718221E6B76C777',
public_key: 'PUB_K1_5JhMfxbsNebajHcTEK8yC9uNN9Dit9hEmzE8ri8yMhhzzEtUA4',
signature: 'SIG_K1_KmKWPBC8dZGGDGhbKEoZEzPr3h5crRrR2uLdGRF5DJbeibY1MY1bZ9sPwHsgmPfiGFv9psfoCVsXFh9TekcLuvaeuxRKA8',
meta: '{}',
},
})
console.log('Переводим аккаунт в кооператив')
@@ -183,7 +141,7 @@ export class CooperativeClass {
})
console.log('Переводим инициализационные токены')
await this.blockchain.transfer({ from: 'eosio', to: account.username, quantity: `100.0000 ${SYMBOL}`, memo: '' })
await this.blockchain.transfer({ from: 'eosio', to: username, quantity: `100.0000 ${SYMBOL}`, memo: '' })
console.log(`Арендуем ресурсы кооперативу`)
@@ -202,18 +160,18 @@ export async function startCoop() {
const blockchain = new Blockchain(config.network, config.private_keys)
const cooperative = new CooperativeClass(blockchain)
// await cooperative.createCooperative('cooperative1', {
// // eslint-disable-next-line node/prefer-global/process
// privateKey: process.env.EOSIO_PRV_KEY!,
// // eslint-disable-next-line node/prefer-global/process
// publicKey: process.env.EOSIO_PUB_KEY!,
// })
await cooperative.createCooperative('cooperative1', {
// eslint-disable-next-line node/prefer-global/process
privateKey: process.env.EOSIO_PRV_KEY!,
// eslint-disable-next-line node/prefer-global/process
publicKey: process.env.EOSIO_PUB_KEY!,
})
// await blockchain.preInit({
// coopname: 'cooperative1',
// username: config.provider,
// status: 'active',
// })
await blockchain.preInit({
coopname: 'cooperative1',
username: config.provider,
status: 'active',
})
console.log('Кооператив предварительно подготовлен к установке совета.')
}
+31 -264
View File
@@ -3,12 +3,11 @@ import axios from 'axios'
import { Generator, Registry } from '@coopenomics/factory'
import type { Cooperative } from 'cooptypes'
import { DraftContract } from 'cooptypes'
import mongoose, { Types } from 'mongoose'
import mongoose from 'mongoose'
import type { Account, Contract } from '../types'
import config from '../configs'
import Blockchain from '../blockchain'
import { sleep } from '../utils'
import { initUsersInPostgres, initVaultInPostgres } from '../postgres-init'
import { CooperativeClass } from './cooperative'
export async function startInfra() {
@@ -146,32 +145,10 @@ export async function startInfra() {
})
}
console.log(`Арендуем ресурсы провайдеру`)
await blockchain.powerup({
payer: 'eosio',
receiver: config.provider,
days: config.powerup.days,
payment: `10000.0000 ${config.token.symbol}`,
transfer: true,
})
await blockchain.transfer({
from: 'eosio',
to: config.provider,
quantity: `1000.0000 ${config.token.symbol}`,
memo: '',
})
console.log('Базовая инфраструктура установлена')
return blockchain
}
export async function installInitialData(blockchain: Blockchain, isExtended = false) {
const organizationData: Cooperative.Users.IOrganizationData = {
username: 'voskhod',
type: 'coop',
short_name: 'ПК "Восход"',
short_name: '"ПК Восход"',
full_name: 'Потребительский Кооператив "ВОСХОД"',
represented_by: {
first_name: 'Иван',
@@ -243,9 +220,9 @@ export async function installInitialData(blockchain: Blockchain, isExtended = fa
// добавляем переменные кооператива
const vars: Cooperative.Model.IVars = {
coopname: 'voskhod',
full_abbr: 'Потребительский Кооператив',
full_abbr_genitive: 'Потребительского Кооператива',
full_abbr_dative: 'Потребительскому Кооперативу',
full_abbr: 'потребительский кооператив',
full_abbr_genitive: 'потребительского кооператива',
full_abbr_dative: 'потребительскому кооперативу',
short_abbr: 'ПК',
website: 'цифровой-кооператив.рф',
name: 'Восход',
@@ -273,55 +250,13 @@ export async function installInitialData(blockchain: Blockchain, isExtended = fa
protocol_number: '10-04-2024',
protocol_day_month_year: '10 апреля 2024 г.',
},
generator_program: {
protocol_number: '1',
protocol_day_month_year: '09.02.2026 10:24',
},
generation_contract_template: {
protocol_number: '2',
protocol_day_month_year: '09.02.2026 10:24',
},
blagorost_program: {
protocol_number: '3',
protocol_day_month_year: '09.02.2026 10:24',
},
generator_offer_template: {
protocol_number: '4',
protocol_day_month_year: '09.02.2026 10:27',
},
blagorost_offer_template: {
protocol_number: '5',
protocol_day_month_year: '09.02.2026 10:27',
},
deleted: false,
block_num: 1,
}
// Сохраняем vars с указанием конкретного _id и _created_at
await generator.save('vars', vars)
// eslint-disable-next-line node/prefer-global/process
await mongoose.connect(process.env.MONGO_URI as string)
try {
await mongoose.connection.collection('vars').insertOne({
_id: new Types.ObjectId('69898c7d996550b4db4b1a36'),
_created_at: new Date('2026-02-08T13:29:12.423Z'),
...vars,
})
console.log('Vars сохранены с указанным _id и _created_at')
}
catch (e) {
console.log('Vars уже существуют, обновляем...')
await mongoose.connection.collection('vars').updateOne(
{ coopname: 'voskhod' },
{
$set: {
...vars,
_created_at: new Date('2026-02-08T13:29:12.423Z'),
},
},
)
}
try {
await mongoose.connection.collection('sync').deleteMany({})
console.log('Все документы удалены из коллекции sync')
@@ -346,17 +281,17 @@ export async function installInitialData(blockchain: Blockchain, isExtended = fa
console.error('Ошибка при удалении:', e)
}
// Собираем пользователей для инициализации в PostgreSQL
const usersToInit = [
{
username: 'ant',
// добавляем пользователя для подключений
try {
await mongoose.connection.collection('users').insertOne({
email: 'ivanov@example.com',
type: 'individual' as const,
username: 'ant',
type: 'individual',
role: 'chairman',
status: 'active',
is_registered: true,
},
]
})
}
catch (e) { console.log('user is exist') }
// имитируем установку
try {
@@ -377,195 +312,27 @@ export async function installInitialData(blockchain: Blockchain, isExtended = fa
})
}
catch (e) { console.log('vault is exist') }
console.log('Добавляем пайщика ant')
await blockchain.addUser({
coopname: 'voskhod',
referer: '',
username: 'ant',
type: 'individual',
created_at: '2025-01-15T10:00:00',
initial: '100.0000 RUB',
minimum: '200.0000 RUB',
spread_initial: true,
meta: 'Основатель кооператива ВОСХОД',
})
console.log('Устанавливаем дефолтный публичный ключ для ant')
await blockchain.changeKey({
coopname: 'voskhod',
changer: 'voskhod',
username: 'ant',
public_key: config.default_public_key,
})
// Если расширенный режим, сначала добавляем дополнительных пайщиков
if (isExtended) {
console.log('Добавляем дополнительных пайщиков для расширенного совета')
const extraUsers = [
{
username: 'petr',
first_name: 'Петр',
last_name: 'Сидоров',
middle_name: 'Сергеевич',
email: 'sidorov@example.com',
},
{
username: 'anna',
first_name: 'Анна',
last_name: 'Петрова',
middle_name: 'Ивановна',
email: 'petrova@example.com',
},
{
username: 'mikhail',
first_name: 'Михаил',
last_name: 'Кузнецов',
middle_name: 'Андреевич',
email: 'kuznetsov@example.com',
},
{
username: 'olga',
first_name: 'Ольга',
last_name: 'Соколова',
middle_name: 'Викторовна',
email: 'sokolova@example.com',
},
]
for (const user of extraUsers) {
// Добавляем в MongoDB
const userData: Cooperative.Users.IIndividualData = {
username: user.username,
first_name: user.first_name,
last_name: user.last_name,
middle_name: user.middle_name,
birthdate: '1990/04/01',
phone: '+71234567890',
email: user.email,
full_address: 'г. Москва, ул. Примерная д. 1',
passport: {
series: 7122,
number: Math.floor(Math.random() * 900000) + 100000,
issued_by: 'отделом УФМС по г. Москва',
issued_at: '2010/05/10',
code: '111-232',
},
}
await generator.save('individual', userData)
// Добавляем пользователя в список для PostgreSQL
usersToInit.push({
username: user.username,
email: user.email,
type: 'individual' as const,
role: 'member',
status: 'active',
is_registered: true,
})
console.log(`Добавляем пайщика ${user.username}`)
// Добавляем в блокчейн
await blockchain.addUser({
coopname: 'voskhod',
referer: user.username === 'petr' ? '' : 'petr',
username: user.username,
type: 'individual',
created_at: '2025-01-15T10:00:00',
initial: '100.0000 RUB',
minimum: '300.0000 RUB',
spread_initial: true,
meta: `Член совета кооператива ВОСХОД - ${user.first_name} ${user.middle_name} ${user.last_name}`,
})
console.log(`Устанавливаем дефолтный публичный ключ для ${user.username}`)
await blockchain.changeKey({
coopname: 'voskhod',
changer: 'voskhod',
username: user.username,
public_key: config.default_public_key,
})
}
}
console.log('Инициализируем пользователей в PostgreSQL')
await initUsersInPostgres(usersToInit)
console.log('Инициализируем vault в PostgreSQL')
await initVaultInPostgres()
console.log('Создаём совет')
const boardMembers: Array<{
username: string
is_voting: boolean
position_title: string
position: 'chairman' | 'member'
}> = [
{
username: 'ant',
is_voting: true,
position_title: 'Председатель совета',
position: 'chairman',
},
]
// Если расширенный режим, добавляем дополнительных членов
if (isExtended) {
boardMembers.push(
{
username: 'petr',
is_voting: true,
position_title: 'Член совета',
position: 'member',
},
{
username: 'anna',
is_voting: true,
position_title: 'Член совета',
position: 'member',
},
{
username: 'mikhail',
is_voting: true,
position_title: 'Член совета',
position: 'member',
},
{
username: 'olga',
is_voting: true,
position_title: 'Член совета',
position: 'member',
},
)
}
await blockchain.createBoard({
coopname: 'voskhod',
username: 'ant',
type: 'soviet',
members: boardMembers,
name: 'Совет',
description: isExtended ? 'Совет кооператива ВОСХОД (расширенный состав)' : 'Совет кооператива ВОСХОД',
})
console.log('Создаём программы и соглашения')
const cooperative = new CooperativeClass(blockchain)
await cooperative.createPrograms(config.provider)
console.log('Начальные данные установлены')
}
console.log(`Арендуем ресурсы провайдеру`)
await blockchain.powerup({
payer: 'eosio',
receiver: config.provider,
days: config.powerup.days,
payment: `100.0000 ${config.token.symbol}`,
transfer: true,
})
export async function installExtraData(blockchain: Blockchain) {
// В расширенном режиме пайщики уже добавлены в installInitialData
// Здесь можно добавить дополнительную логику инициализации если потребуется
console.log('Дополнительная инициализация для расширенного режима выполнена')
await blockchain.transfer({
from: 'eosio',
to: config.provider,
quantity: `100.0000 ${config.token.symbol}`,
memo: '',
})
console.log('Базовая установка завершена')
}
+27 -102
View File
@@ -6,96 +6,29 @@ import type { Account, Contract, Keys } from '../types'
import config, { GOVERN_SYMBOL, SYMBOL } from '../configs'
import Blockchain from '../blockchain'
import { sendPostToCoopbackWithSecret, sleep } from '../utils'
import { fakeDocument } from '../tests/shared/fakeDocument'
export class ParticipantsClass {
// const test_hash
// = '157192b276da23cc84ab078fc8755c051c5f0430bf4802e55718221e6b76c777'
// const test_sign
// = 'SIG_K1_KmKWPBC8dZGGDGhbKEoZEzPr3h5crRrR2uLdGRF5DJbeibY1MY1bZ9sPwHsgmPfiGFv9psfoCVsXFh9TekcLuvaeuxRKA8'
// const test_pkey = 'EOS5JhMfxbsNebajHcTEK8yC9uNN9Dit9hEmzE8ri8yMhhzxrLg3J'
// const test_meta = JSON.stringify({})
// const document = {
// hash: test_hash,
// signature: test_sign,
// public_key: test_pkey,
// meta: test_meta,
// }
export class CooperativeClass {
public blockchain: Blockchain
constructor(blockchain: Blockchain) {
this.blockchain = blockchain
}
async addUser2(coopname: string, username: string, keys?: Keys, referer?: string) {
const account = await this.blockchain.generateKeypair(
username,
keys,
'Аккаунт участника',
)
console.log('Регистрируем аккаунт')
const data: RegistratorContract.Actions.AddUser.IAddUser = {
coopname,
referer: referer || '',
username: account.username,
type: 'individual',
created_at: '2025-02-05T09:34:27',
initial: '100.0000 RUB',
minimum: '100.0000 RUB',
spread_initial: false,
meta: '',
}
await this.blockchain.api.transact({
actions: [
{
account: RegistratorContract.contractName.production,
name: RegistratorContract.Actions.AddUser.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data,
},
],
}, {
blocksBehind: 3,
expireSeconds: 30,
})
console.log('Регистрируем аккаунт как пользователя')
const changeKey: RegistratorContract.Actions.ChangeKey.IChangeKey = {
coopname,
username,
public_key: account.publicKey,
changer: coopname,
}
await this.blockchain.api.transact({
actions: [
{
account: RegistratorContract.contractName.production,
name: RegistratorContract.Actions.ChangeKey.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data: changeKey,
},
],
}, {
blocksBehind: 3,
expireSeconds: 30,
})
// console.log('Отправляем подписанное положение о ЦПП Кошелька оператору')
// await this.blockchain.sendAgreement({
// coopname: config.provider,
// administrator: config.provider,
// username: username!,
// agreement_type: 'wallet',
// document: fakeDocument,
// })
// console.log('создаём кошелёк')
// console.log(`Арендуем ресурсы кооперативу`)
// await this.blockchain.powerup({
// payer: 'eosio',
// receiver: username,
// days: config.powerup.days,
// payment: `100.0000 ${config.token.symbol}`,
// transfer: true,
// })
}
async addUser(username: string, keys?: Keys, referer?: string) {
async addUser(username: string, keys?: Keys) {
const account = await this.blockchain.generateKeypair(
username,
keys,
@@ -105,7 +38,7 @@ export class ParticipantsClass {
const data: RegistratorContract.Actions.AddUser.IAddUser = {
coopname: config.provider,
referer: referer || '',
referer: '',
username: account.username,
type: 'individual',
created_at: '2025-02-05T09:34:27',
@@ -159,7 +92,12 @@ export class ParticipantsClass {
administrator: config.provider,
username: username!,
agreement_type: 'wallet',
document: fakeDocument,
document: { // отправляем произвольный документ с валидной подписью
hash: '157192B276DA23CC84AB078FC8755C051C5F0430BF4802E55718221E6B76C777',
public_key: 'PUB_K1_5JhMfxbsNebajHcTEK8yC9uNN9Dit9hEmzE8ri8yMhhzzEtUA4',
signature: 'SIG_K1_KmKWPBC8dZGGDGhbKEoZEzPr3h5crRrR2uLdGRF5DJbeibY1MY1bZ9sPwHsgmPfiGFv9psfoCVsXFh9TekcLuvaeuxRKA8',
meta: '{}',
},
})
console.log('создаём кошелёк')
@@ -176,28 +114,15 @@ export class ParticipantsClass {
}
}
export async function addUser(username: string, referer?: string) {
export async function addUser(username: string) {
// инициализируем инстанс с ключами
const blockchain = new Blockchain(config.network, config.private_keys)
const participants = new ParticipantsClass(blockchain)
const cooperative = new CooperativeClass(blockchain)
await participants.addUser(username, {
await cooperative.addUser(username, {
// eslint-disable-next-line node/prefer-global/process
privateKey: process.env.EOSIO_PRV_KEY!,
// eslint-disable-next-line node/prefer-global/process
publicKey: process.env.EOSIO_PUB_KEY!,
}, referer)
}
export async function addUser2(coopname: string, username: string, referer?: string) {
// инициализируем инстанс с ключами
const blockchain = new Blockchain(config.network, config.private_keys)
const participants = new ParticipantsClass(blockchain)
await participants.addUser2(coopname, username, {
// eslint-disable-next-line node/prefer-global/process
privateKey: process.env.EOSIO_PRV_KEY!,
// eslint-disable-next-line node/prefer-global/process
publicKey: process.env.EOSIO_PUB_KEY!,
}, referer)
})
}
-294
View File
@@ -1,294 +0,0 @@
/* eslint-disable node/prefer-global/process */
import { Client } from 'pg'
import type { Cooperative } from 'cooptypes'
export async function initSystemStatus() {
console.log('Инициализация статуса системы для coopname: voskhod')
const client = new Client({
host: process.env.POSTGRES_HOST,
port: parseInt(process.env.POSTGRES_PORT || '5432'),
user: process.env.POSTGRES_USERNAME,
password: process.env.POSTGRES_PASSWORD,
database: process.env.POSTGRES_DATABASE,
})
try {
await client.connect()
console.log('Подключение к PostgreSQL установлено для initSystemStatus')
// Создаем enum тип для статуса системы
try {
await client.query(`
CREATE TYPE public.system_status_status_enum AS ENUM ('install', 'initialized', 'active', 'maintenance')
`)
}
catch (error) {
// Тип уже существует, продолжаем
console.log('Enum тип system_status_status_enum уже существует, пропускаем создание')
}
// Создаем таблицу system_status правильно, как в TypeORM entity
await client.query(`
CREATE TABLE IF NOT EXISTS public.system_status (
coopname varchar(12) NOT NULL,
install_code varchar(255) NULL,
install_code_expires_at timestamp NULL,
init_by_server bool NOT NULL DEFAULT false,
created_at timestamp NOT NULL DEFAULT now(),
updated_at timestamp NOT NULL DEFAULT now(),
status public.system_status_status_enum NOT NULL DEFAULT 'install'::system_status_status_enum,
CONSTRAINT system_status_pkey PRIMARY KEY (coopname)
)
`)
try {
// Устанавливаем начальный статус для voskhod (active - система готова к работе)
await client.query(`
INSERT INTO system_status (coopname, status)
VALUES ('voskhod', 'active')
ON CONFLICT (coopname) DO UPDATE SET
status = EXCLUDED.status,
updated_at = CURRENT_TIMESTAMP
`)
// Проверяем, что статус действительно установлен
const result = await client.query(`
SELECT status FROM system_status WHERE coopname = 'voskhod'
`)
console.log('Статус системы в PostgreSQL после установки:', result.rows[0]?.status)
}
catch (queryError) {
console.error('Ошибка при установке статуса системы в PostgreSQL:', queryError)
throw queryError
}
console.log('Статус системы инициализирован в PostgreSQL')
}
catch (error) {
console.error('Ошибка инициализации статуса системы в PostgreSQL:', error)
throw error
}
finally {
await client.end()
}
}
export async function initUsersInPostgres(
users: Array<{
username: string
email: string
type: 'individual' | 'entrepreneur' | 'organization'
role: string
status: string
is_registered: boolean
}>,
) {
const client = new Client({
host: process.env.POSTGRES_HOST,
port: parseInt(process.env.POSTGRES_PORT || '5432'),
user: process.env.POSTGRES_USERNAME,
password: process.env.POSTGRES_PASSWORD,
database: process.env.POSTGRES_DATABASE,
})
try {
await client.connect()
console.log('Подключение к PostgreSQL установлено для инициализации пользователей')
// Создаем таблицу users, если она не существует
await client.query(`
CREATE TABLE IF NOT EXISTS "users" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
username VARCHAR(50) UNIQUE NOT NULL,
status VARCHAR(20) DEFAULT 'created',
message TEXT DEFAULT '',
is_registered BOOLEAN DEFAULT FALSE,
has_account BOOLEAN DEFAULT FALSE,
type VARCHAR(20) NOT NULL,
public_key TEXT DEFAULT '',
referer VARCHAR(100) DEFAULT '',
email VARCHAR(255),
role VARCHAR(20) DEFAULT 'user',
is_email_verified BOOLEAN DEFAULT FALSE,
subscriber_id VARCHAR(100) DEFAULT '',
subscriber_hash VARCHAR(255) DEFAULT '',
legacy_mongo_id VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`)
// Создаем пользователей
for (const user of users) {
// Проверяем, что username не null и не пустой
if (!user.username || user.username.trim() === '') {
console.warn(`Пропускаем пользователя с пустым username:`, user)
continue
}
await client.query(`
INSERT INTO "users" (
username, email, type, role, status, is_registered,
has_account, is_email_verified, created_at, updated_at
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
ON CONFLICT (username) DO NOTHING
`, [
user.username,
user.email,
user.type,
user.role,
user.status,
user.is_registered,
false, // has_account
true, // is_email_verified
])
}
console.log(`Инициализировано ${users.length} пользователей в PostgreSQL`)
}
catch (error) {
console.error('Ошибка инициализации пользователей в PostgreSQL:', error)
throw error
}
finally {
await client.end()
}
}
export async function initVaultInPostgres() {
const client = new Client({
host: process.env.POSTGRES_HOST,
port: parseInt(process.env.POSTGRES_PORT || '5432'),
user: process.env.POSTGRES_USERNAME,
password: process.env.POSTGRES_PASSWORD,
database: process.env.POSTGRES_DATABASE,
})
try {
await client.connect()
console.log('Подключение к PostgreSQL установлено для инициализации vault')
// Создаем таблицу vaults, если она не существует
await client.query(`
CREATE TABLE IF NOT EXISTS "vaults" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
username VARCHAR(50) NOT NULL,
permission VARCHAR(20) DEFAULT 'active',
wif TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(username, permission)
)
`)
// Создаем индексы
await client.query(`CREATE INDEX IF NOT EXISTS idx_vaults_username ON "vaults"(username)`)
await client.query(`CREATE INDEX IF NOT EXISTS idx_vaults_username_permission ON "vaults"(username, permission)`)
// Сохраняем зашифрованный ключ в vault
await client.query(`
INSERT INTO "vaults" (username, permission, wif, created_at, updated_at)
VALUES ($1, $2, $3, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
ON CONFLICT (username, permission) DO NOTHING
`, [
'voskhod',
'active',
'9d6479a9d77ead53fb0e5e54b3608a95:2046ee3c1577d48aecbee49e8f25c4c2df37ab02f15d73d0d1b6352f53a4b774cb9e71b6028fd7caf64568e195c7878dfbb5d2bf10a3766d90ba9e92ea724428',
])
console.log('Vault инициализирован в PostgreSQL')
}
catch (error) {
console.error('Ошибка инициализации vault в PostgreSQL:', error)
throw error
}
finally {
await client.end()
}
}
export async function initExtensionsInPostgres() {
const client = new Client({
host: process.env.POSTGRES_HOST,
port: parseInt(process.env.POSTGRES_PORT || '5432'),
user: process.env.POSTGRES_USERNAME,
password: process.env.POSTGRES_PASSWORD,
database: process.env.POSTGRES_DATABASE,
})
try {
await client.connect()
console.log('Подключение к PostgreSQL установлено для инициализации extensions')
// Создаем таблицу extensions по аналогии с ExtensionEntity
await client.query(`
CREATE TABLE IF NOT EXISTS "extensions" (
name VARCHAR(12) PRIMARY KEY,
enabled BOOLEAN DEFAULT true,
config JSONB DEFAULT '{}',
schema_version INTEGER DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`)
// Создаем индексы
await client.query(`CREATE INDEX IF NOT EXISTS idx_extensions_name ON "extensions"(name)`)
await client.query(`CREATE INDEX IF NOT EXISTS idx_extensions_enabled ON "extensions"(enabled)`)
// Вставляем запись для capital extension
const capitalConfig = {
level_depth_base: 100000000,
github_repository: "coopenomics/results-test",
onboarding_init_at: "2026-02-09T07:16:18.380Z",
expense_pool_percent: 100,
onboarding_expire_at: "2026-03-11T07:16:18.380Z",
voting_period_in_days: 1,
authors_voting_percent: 62.8,
creators_voting_percent: 62.8,
energy_gain_coefficient: 1,
level_growth_coefficient: 1.5,
coordinator_bonus_percent: 5,
energy_decay_rate_per_day: 0.02,
coordinator_invite_validity_days: 30,
onboarding_blagorost_provision_done: true,
onboarding_blagorost_provision_hash: "CDE57D987E3C945E79E108920CE02A4A80CFA7980CAA912949BB6C2111B7027A",
onboarding_blagorost_offer_template_done: true,
onboarding_blagorost_offer_template_hash: "5CA88BBD303E5CCDA01E565FFE47E51855515176EC6C957F0FBDBCA4C53DBFD2",
onboarding_generator_offer_template_done: true,
onboarding_generator_offer_template_hash: "8DA31574E8CC764C3A1FCAE1172726656A3DCDB1BB82AB0E567E2732070C3A44",
onboarding_generator_program_template_done: true,
onboarding_generator_program_template_hash: "E55564D8946C55C93490B5277968FC890FDCB10A049DB5B2E0FE9F67FDA80896",
onboarding_generation_contract_template_done: true,
onboarding_generation_contract_template_hash: "A4BD579D6130CCE2D8C34337DFA591807C1F028A148DD53689881B12AC2627E2"
}
await client.query(`
INSERT INTO "extensions" (name, enabled, config, schema_version, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (name) DO UPDATE SET
enabled = EXCLUDED.enabled,
config = EXCLUDED.config,
schema_version = EXCLUDED.schema_version,
updated_at = CURRENT_TIMESTAMP
`, [
'capital',
true,
JSON.stringify(capitalConfig),
1,
new Date('2026-02-09T02:13:06.620Z'),
new Date('2026-02-09T02:27:57.155Z')
])
console.log('Extensions инициализированы в PostgreSQL')
}
catch (error) {
console.error('Ошибка инициализации extensions в PostgreSQL:', error)
throw error
}
finally {
await client.end()
}
}
@@ -1,454 +0,0 @@
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { CapitalContract, SovietContract } from 'cooptypes'
import Blockchain from '../blockchain'
import config from '../configs'
import { getTotalRamUsage, globalRamStats } from '../utils/getTotalRamUsage'
import { addUser, addUser2 } from '../init/participant'
import { generateRandomUsername } from '../utils/randomUsername'
import { generateRandomSHA256 } from '../utils/randomHash'
import { sleep } from '../utils'
import { CooperativeClass } from '../init/cooperative'
import { getCirculationAccount, getCoopProgramWallet, getLedgerAccountById, getUserProgramWalletAmount } from './wallet/walletUtils'
import { registerContributor } from './capital/registerContributor'
import { signAppendix } from './capital/signAppendix'
import { capitalProgramId, circulationAccountId, sourceProgramId } from './capital/consts'
import { commitToResult } from './capital/commitToResult'
import { refreshSegment } from './capital/refreshSegment'
import { signWalletAgreement } from './wallet/signWalletAgreement'
import { fakeDocument } from './shared/fakeDocument'
import { depositToWallet } from './wallet/depositToWallet'
import { signCapitalAgreement } from './capital/signCapitalAgreement'
import { addAuthor } from './capital/addAuthor'
import { getSegment } from './capital/getSegment'
import { investInProject } from './capital/investInProject'
import { processDebt } from './capital/processDebt'
import { processCreateProjectProperty } from './capital/processCreateProjectProperty'
import { processCreateProgramProperty } from './capital/processCreateProgramProperty'
import { processStartVoting } from './capital/processStartVoting'
import { createVoteDistribution, submitVote } from './capital/submitVote'
import { processCompleteVoting } from './capital/processCompleteVoting'
import { processPushResult } from './capital/processPushResult'
import { processConvertSegment } from './capital/processConvertSegment'
import { processCalculateVotes } from './capital/processCalculateVotes'
import { processAddContributor } from './capital/processAddContributor'
import { processDeleteProject } from './capital/processDeleteProject'
import { getProject } from './capital/getProject'
import { processFundProgram } from './capital/processFundProgram'
import { processRefreshProg } from './capital/processRefreshProg'
import { processFundProject } from './capital/processFundProject'
import { processRefreshProj } from './capital/processRefreshProj'
// const CLI_PATH = 'src/index.ts'
const blockchain = new Blockchain(config.network, config.private_keys)
let metaProject: CapitalContract.Actions.CreateProject.ICreateProject
let componentProject: CapitalContract.Actions.CreateProject.ICreateProject
let newComponentProject: CapitalContract.Actions.CreateProject.ICreateProject
let testCoop: string
let chairman: string
let tester1: string
let tester2: string
let tester3: string
let tester4: string
let tester5: string
let investor1: string
let investor2: string
let investor3: string
// let result1: CapitalContract.Actions.CreateResult.ICreateResult
// let result2: CapitalContract.Actions.CreateResult.ICreateResult
// let result3: CapitalContract.Actions.CreateResult.ICreateResult
const commits: string[] = []
const tester1CommitHours = 10
const tester2CommitHours = 20
const tester3CommitHours = 100
const investAmount1 = 100000
const investAmount2 = 25000
const investAmount3 = 25000
const totalToCapitalConvertAmount = 0
const totalToProjectConvertAmount = 0
// Хранение хэшей долгов для каждого пользователя
let userDebtHashes: { [username: string]: string[] } = {}
beforeAll(async () => {
testCoop = generateRandomUsername()
chairman = generateRandomUsername()
await blockchain.update_pass_instance()
const coop = new CooperativeClass(blockchain)
await coop.createCooperative(testCoop, {
privateKey: process.env.EOSIO_PRV_KEY!,
publicKey: process.env.EOSIO_PUB_KEY!,
})
await blockchain.preInit({
coopname: testCoop,
username: config.provider,
status: 'active',
})
await addUser2(testCoop, chairman)
await blockchain.createBoard({
coopname: testCoop,
username: testCoop,
type: 'soviet',
members: [{
username: chairman,
is_voting: true,
position_title: 'Председатель совета',
position: 'chairman',
}],
name: 'Совет',
description: '',
})
console.log('testcoop: ', testCoop)
investor1 = generateRandomUsername()
console.log('investor1: ', investor1)
await addUser2(testCoop, investor1)
tester1 = generateRandomUsername()
console.log('tester1: ', tester1)
await addUser2(testCoop, tester1)
tester2 = generateRandomUsername()
console.log('tester2: ', tester2)
await addUser2(testCoop, tester2)
tester3 = generateRandomUsername()
console.log('tester3: ', tester3)
await addUser2(testCoop, tester3)
tester4 = generateRandomUsername()
console.log('tester4: ', tester4)
await addUser2(testCoop, tester4)
tester5 = generateRandomUsername()
console.log('tester5: ', tester5)
await addUser2(testCoop, tester5)
// Инициализируем массивы хэшей долгов для каждого пользователя
userDebtHashes = {
[tester1]: [],
[tester2]: [],
[tester3]: [],
[tester4]: [],
[tester5]: [],
[investor1]: [],
[investor2]: [],
[investor3]: [],
}
investor2 = generateRandomUsername()
console.log('investor2: ', investor2)
await addUser2(testCoop, investor2, tester4)
investor3 = generateRandomUsername()
console.log('investor3: ', investor3)
await addUser2(testCoop, investor3, tester5)
// const { stdout } = await execa('esno', [CLI_PATH, 'boot'], { stdio: 'inherit' })
// expect(stdout).toContain('Boot process completed')
}, 500_000)
afterAll(() => {
console.log('\n📊 **RAM USAGE SUMMARY** 📊')
let totalGlobalRam = 0
for (const [key, ramUsed] of Object.entries(globalRamStats)) {
const ramKb = (ramUsed / 1024).toFixed(2)
console.log(` ${key} = ${ramKb} kb`)
totalGlobalRam += ramUsed
}
console.log(`\n💾 **TOTAL RAM USED IN TESTS**: ${(totalGlobalRam / 1024).toFixed(2)} kb\n`)
console.log(`testCoop: ${testCoop}\ntester1: ${tester1}\ntester2: ${tester2}\ntester3: ${tester3}\ntester4: ${tester4}\ntester5: ${tester5}\ninvestor1: ${investor1}\ninvestor2: ${investor2}\ninvestor3: ${investor3}`)
})
describe('тест контракта CAPITAL', () => {
it('создаём программу генерации', async () => {
const program = await getCoopProgramWallet(blockchain, testCoop, sourceProgramId)
if (!program) {
const data: SovietContract.Actions.Programs.CreateProgram.ICreateProgram = {
coopname: testCoop,
is_can_coop_spend_share_contributions: true,
username: chairman,
title: 'Договор УХД',
announce: '',
description: '',
preview: '',
images: '',
calculation_type: 'free',
fixed_membership_contribution: '0.0000 RUB',
membership_percent_fee: '0',
meta: '',
type: 'generator',
}
const result = await blockchain.api.transact(
{
actions: [
{
account: SovietContract.contractName.production,
name: SovietContract.Actions.Programs.CreateProgram.actionName,
authorization: [
{
actor: chairman,
permission: 'active',
},
],
data,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
getTotalRamUsage(result)
expect(result.transaction_id).toBeDefined()
}
})
it('создаём программу капитализации', async () => {
const program = await getCoopProgramWallet(blockchain, testCoop, capitalProgramId)
if (!program) {
const data: SovietContract.Actions.Programs.CreateProgram.ICreateProgram = {
coopname: testCoop,
is_can_coop_spend_share_contributions: false,
username: chairman,
title: 'Капитализация',
announce: '',
description: '',
preview: '',
images: '',
calculation_type: 'free',
fixed_membership_contribution: '0.0000 RUB',
membership_percent_fee: '0',
meta: '',
type: 'blagorost',
}
const result = await blockchain.api.transact(
{
actions: [
{
account: SovietContract.contractName.production,
name: SovietContract.Actions.Programs.CreateProgram.actionName,
authorization: [
{
actor: chairman,
permission: 'active',
},
],
data,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
getTotalRamUsage(result)
expect(result.transaction_id).toBeDefined()
}
})
it('импорт вкладчика ДО установки конфига - должен работать', async () => {
// Создаем тестового пользователя для импорта
const importUsername = generateRandomUsername()
console.log('importUsername: ', importUsername)
await addUser2(testCoop, importUsername)
const contributorHash = generateRandomSHA256()
const contributionAmount = '10000.0000 RUB'
// Получаем состояние паевого счета ДО импорта
const shareFundBefore = await getCirculationAccount(blockchain, testCoop)
const shareFundBalanceBefore = parseFloat(shareFundBefore.available)
console.log('Share fund before import:', shareFundBalanceBefore)
// Импортируем вкладчика
const importData: CapitalContract.Actions.ImportContributor.IImportContributor = {
coopname: testCoop,
username: importUsername,
contributor_hash: contributorHash,
contribution_amount: contributionAmount,
memo: 'договор №123 от такого то числа',
}
const result = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.ImportContributor.actionName,
authorization: [
{
actor: testCoop,
permission: 'active',
},
],
data: importData,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
getTotalRamUsage(result)
expect(result.transaction_id).toBeDefined()
// Проверяем, что вкладчик был создан
const contributorTable = await blockchain.getTableRows(
CapitalContract.contractName.production,
testCoop,
'contributors',
)
console.log('contributorTable: ', contributorTable)
expect(contributorTable.length).toBe(1)
const contributor = contributorTable[0]
expect(contributor.username).toBe(importUsername)
expect(contributor.status).toBe('active')
// Проверяем, что кошелек программы был создан
const wallet = await getUserProgramWalletAmount(blockchain, testCoop, importUsername, capitalProgramId)
expect(parseFloat(wallet)).toBe(parseFloat(contributionAmount))
// Проверяем увеличение паевого счета
const shareFundAfter = await getCirculationAccount(blockchain, testCoop)
const shareFundBalanceAfter = parseFloat(shareFundAfter.available)
console.log('Share fund after import:', shareFundBalanceAfter)
expect(shareFundBalanceAfter).toBe(shareFundBalanceBefore + parseFloat(contributionAmount))
})
it('инициализируем контракт CAPITAL и сверяем конфиг', async () => {
const data: CapitalContract.Actions.SetConfig.ISetConfig = {
coopname: testCoop,
config: {
coordinator_bonus_percent: 4,
expense_pool_percent: 100,
coordinator_invite_validity_days: 30,
voting_period_in_days: 7,
authors_voting_percent: 38.2,
creators_voting_percent: 38.2,
energy_decay_rate_per_day: 0.11,
level_depth_base: 1000,
level_growth_coefficient: 1.5,
energy_gain_coefficient: 0.01,
},
}
const result = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.SetConfig.actionName,
authorization: [
{
actor: testCoop,
permission: 'active',
},
],
data,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
getTotalRamUsage(result)
expect(result.transaction_id).toBeDefined()
const state = (await blockchain.getTableRows(
CapitalContract.contractName.production,
CapitalContract.contractName.production,
'state',
1,
testCoop,
testCoop,
))[0]
console.log('state: ', state)
expect(state).toBeDefined()
expect(state.coopname).toBe(testCoop)
expect(parseFloat(state.config.coordinator_bonus_percent)).toBe(data.config.coordinator_bonus_percent)
expect(parseFloat(state.config.expense_pool_percent)).toBe(data.config.expense_pool_percent)
expect(parseFloat(state.config.coordinator_invite_validity_days)).toBe(data.config.coordinator_invite_validity_days)
expect(parseFloat(state.config.voting_period_in_days)).toBe(data.config.voting_period_in_days)
expect(parseFloat(state.config.authors_voting_percent)).toBe(data.config.authors_voting_percent)
expect(parseFloat(state.config.creators_voting_percent)).toBe(data.config.creators_voting_percent)
})
it('импорт вкладчика ПОСЛЕ установки конфига - должен провалиться', async () => {
// Создаем еще одного тестового пользователя для импорта
const importUsername2 = generateRandomUsername()
console.log('importUsername2: ', importUsername2)
await addUser(importUsername2)
const contributorHash2 = generateRandomSHA256()
const contributionAmount2 = '5000.0000 RUB'
// Пытаемся импортировать вкладчика после установки конфига
const importData2: CapitalContract.Actions.ImportContributor.IImportContributor = {
coopname: testCoop,
username: importUsername2,
contributor_hash: contributorHash2,
contribution_amount: contributionAmount2,
memo: 'договор №123 от такого то числа',
}
// Ожидаем, что транзакция провалится
await expect(
blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.ImportContributor.actionName,
authorization: [
{
actor: testCoop,
permission: 'active',
},
],
data: importData2,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
),
).rejects.toThrow() // Ожидаем ошибку
// Проверяем, что вкладчик НЕ был создан
const contributorTable = await blockchain.getTableRows(
CapitalContract.contractName.production,
testCoop,
'contributors',
)
expect(contributorTable.length).toBe(1)
// Проверяем, что кошелек программы НЕ был создан
const wallet = await getUserProgramWalletAmount(blockchain, testCoop, importUsername2, capitalProgramId)
expect(wallet).toBe('0.0000 RUB')
})
})
File diff suppressed because it is too large Load Diff
@@ -1,75 +0,0 @@
import { CapitalContract } from 'cooptypes'
import { expect } from 'vitest'
import type Blockchain from '../../blockchain'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { makeCombinedChecksum256NameIndexKey } from '../shared/combinedKeys'
export async function addAuthor(blockchain: Blockchain, coopname: string, project_hash: string, author: string) {
const data: CapitalContract.Actions.AddAuthor.IAddAuthor = {
coopname,
project_hash,
author,
}
const result = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.AddAuthor.actionName,
authorization: [
{
actor: coopname,
permission: 'active',
},
],
data,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
getTotalRamUsage(result)
expect(result.transaction_id).toBeDefined()
const key = makeCombinedChecksum256NameIndexKey(project_hash, author)
const rows = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
CapitalContract.Tables.Segments.tableName,
1000,
key,
key,
3,
'i128',
))
const segment = rows[0]
console.log('segment: ', segment)
expect(segment).toBeDefined()
expect(segment.username).toBe(author)
expect(segment.project_hash).toBe(project_hash)
expect(segment.is_author).toBe(1)
const project = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
CapitalContract.Tables.Projects.tableName,
1000,
project_hash,
project_hash,
3,
'sha256',
))[0]
expect(project).toBeDefined()
return {
project,
segment,
}
}
@@ -2,20 +2,34 @@ import { expect } from 'vitest'
import { CapitalContract, SovietContract } from 'cooptypes'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { generateRandomSHA256 } from '../../utils/randomHash'
import { fakeDocument } from '../shared/fakeDocument'
import { processMasterApprove } from './processMasterApprove'
import { fakeDocument } from '../soviet/fakeDocument'
import { processDecision } from '../soviet/processDecision'
import { ratePerHour } from './consts'
export async function commitToResult(
blockchain: any,
coopname: string,
resultHash: string,
projectHash: string,
creator: string,
spendHours: number,
) {
const commitHash = generateRandomSHA256()
console.log(`\n🔹 Начало коммита: ${commitHash}, проект: ${projectHash}, часы: ${spendHours}`)
const totalSpended = `${(spendHours * parseFloat(ratePerHour)).toFixed(4)} RUB`
console.log(`\n🔹 Начало коммита: ${commitHash}, часы: ${spendHours}, сумма: ${totalSpended}`)
// Получение текущих данных перед коммитом
const prevResult = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'results',
1,
resultHash,
resultHash,
2,
'sha256',
))[0] || { spended: '0.0000 RUB', commits_count: 0 }
// Получение текущих данных проекта перед коммитом
const prevProject = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
@@ -39,34 +53,21 @@ export async function commitToResult(
))[0] || { contributed_hours: 0, available: '0.0000 RUB' }
console.log('📊 Балансы до коммита:')
console.log('▶ Результат:', prevResult)
console.log('▶ Проект:', prevProject)
console.log('▶ Контрибьютор:', prevContributor)
// Обновим состояние проекта после возможной смены статуса
const projectBefore = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'projects',
1,
projectHash,
projectHash,
3,
'sha256',
))[0]
const prevTotalCommits = projectBefore?.counts?.total_commits ?? 0
const prevCreatorsHours = projectBefore?.fact?.creators_hours ?? 0
// Создание коммита
const commitData: CapitalContract.Actions.CreateCommit.ICommit = {
coopname,
username: creator,
project_hash: projectHash,
application: coopname,
result_hash: resultHash,
commit_hash: commitHash,
creator_hours: spendHours,
description: `Коммит ${commitHash}`,
meta: `{"hours": ${spendHours}, "creator": "${creator}"}`,
contributed_hours: spendHours,
username: creator,
}
console.log(`\n🚀 Отправка транзакции CreateCommit для результата ${resultHash}`)
const createCommitResult = await blockchain.api.transact(
{
actions: [
@@ -88,7 +89,7 @@ export async function commitToResult(
expect(createCommitResult.transaction_id).toBeDefined()
// Проверка созданного коммита
const blockchainCommit = (await blockchain.getTableRows(
let blockchainCommit = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'commits',
@@ -102,30 +103,39 @@ export async function commitToResult(
console.log('🔍 Коммит после создания:', blockchainCommit)
expect(blockchainCommit).toBeDefined()
expect(blockchainCommit.commit_hash).toBe(commitHash)
expect(blockchainCommit.project_hash).toBe(projectHash)
expect(blockchainCommit.spended).toBe(totalSpended)
expect(blockchainCommit.status).toBe('created')
// Получение данных проекта для определения мастера
const projectData = (await blockchain.getTableRows(
CapitalContract.contractName.production,
// Утверждение коммита
const approveCommitData: SovietContract.Actions.Approves.ConfirmApprove.IConfirmApprove = {
coopname,
'projects',
1,
projectHash,
projectHash,
3,
'sha256',
))[0]
approval_hash: commitHash,
approved_document: fakeDocument,
}
expect(projectData).toBeDefined()
expect(projectData.master).toBeDefined()
console.log(`\n✅ Подтверждение коммита ${commitHash}`)
const approveCommitResult = await blockchain.api.transact(
{
actions: [
{
account: SovietContract.contractName.production,
name: SovietContract.Actions.Approves.ConfirmApprove.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data: approveCommitData,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
// Утверждение коммита мастером проекта
console.log(`\n✅ Подтверждение коммита ${commitHash} мастером ${projectData.master}`)
const approveCommitResult = await processMasterApprove(blockchain, coopname, commitHash, projectData.master)
getTotalRamUsage(approveCommitResult)
expect(approveCommitResult.transaction_id).toBeDefined()
// Проверка утвержденного коммита
const blockchainEmptyCommit = (await blockchain.getTableRows(
blockchainCommit = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'commits',
@@ -137,9 +147,96 @@ export async function commitToResult(
))[0]
// Коммит удаляем после утверждения
expect(blockchainEmptyCommit).toBeUndefined()
expect(blockchainCommit).toBeUndefined()
// // Получение всех решений и выполнение последнего
// const decisions = await blockchain.getTableRows(
// SovietContract.contractName.production,
// coopname,
// 'decisions',
// 1000,
// )
// const lastDecision = decisions[decisions.length - 1]
// console.log(`\n📜 Выполнение последнего решения: ${lastDecision.id}`)
// await processDecision(blockchain, lastDecision.id)
// {
// // Утверждение коммита
// const act1Data: CapitalContract.Actions.SetAct1.ISetAct1 = {
// coopname,
// application: coopname,
// username: creator,
// commit_hash: commitHash,
// act: fakeDocument,
// }
// console.log(`\n✅ Установка акта1 по коммиту ${commitHash}`)
// const act1Result = await blockchain.api.transact(
// {
// actions: [
// {
// account: CapitalContract.contractName.production,
// name: CapitalContract.Actions.SetAct1.actionName,
// authorization: [{ actor: coopname, permission: 'active' }],
// data: act1Data,
// },
// ],
// },
// {
// blocksBehind: 3,
// expireSeconds: 30,
// },
// )
// getTotalRamUsage(act1Result)
// expect(act1Result.transaction_id).toBeDefined()
// }
// {
// // Утверждение коммита
// const act2Data: CapitalContract.Actions.SetAct2.ISetAct2 = {
// coopname,
// application: coopname,
// username: creator,
// commit_hash: commitHash,
// act: fakeDocument,
// }
// console.log(`\n✅ Установка акта2 по коммиту ${commitHash}`)
// const act2Result = await blockchain.api.transact(
// {
// actions: [
// {
// account: CapitalContract.contractName.production,
// name: CapitalContract.Actions.SetAct2.actionName,
// authorization: [{ actor: coopname, permission: 'active' }],
// data: act2Data,
// },
// ],
// },
// {
// blocksBehind: 3,
// expireSeconds: 30,
// },
// )
// getTotalRamUsage(act2Result)
// expect(act2Result.transaction_id).toBeDefined()
// }
// Проверка результата после утверждения коммита
const finalResult = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'results',
1,
resultHash,
resultHash,
2,
'sha256',
))[0]
// Проверка проекта после утверждения коммита
const finalProject = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
@@ -163,19 +260,23 @@ export async function commitToResult(
))[0]
console.log('\n📊 Балансы после утверждения коммита:')
console.log('▶ Результат:', finalResult)
console.log('▶ Проект:', finalProject)
console.log('▶ Контрибьютор:', finalContributor)
// Проверки по проекту
expect(finalProject.counts.total_commits).toBe(prevTotalCommits + 1)
expect(parseInt(finalProject.fact.creators_hours)).toBe(prevCreatorsHours + spendHours)
// expect(parseFloat(finalResult.spended)).toBe(parseFloat(prevResult.spended) + parseFloat(totalSpended))
// expect(parseFloat(finalProject.spended)).toBe(parseFloat(prevProject.spended))
// Проверка, что у контрибьютора увеличились contributed_hours и available
// expect(parseFloat(finalContributor.contributed_hours)).toBe(parseFloat(prevContributor.contributed_hours) + spendHours)
// expect(parseFloat(finalContributor.available)).toBe(parseFloat(prevContributor.available) + parseFloat(totalSpended))
console.log(`\n✅ Коммит ${commitHash} завершен успешно!`)
return {
commitHash,
commit: blockchainCommit,
transactionId: approveCommitResult.transaction_id,
transaction: approveCommitResult,
finalResult,
finalProject,
finalContributor,
}
+2 -3
View File
@@ -2,6 +2,5 @@ export const sourceProgramId = 3
export const capitalProgramId = 4
export const walletProgramId = 1
export const ratePerHour = '1000.0000 RUB'
export const sourceProgramName = 'generator'
export const capitalProgramName = 'blagorost'
export const circulationAccountId = 80
export const sourceProgramName = 'source'
export const capitalProgramName = 'capital'
@@ -1,27 +0,0 @@
import { CapitalContract } from 'cooptypes'
import type Blockchain from '../../blockchain'
/**
* Получает кошелек капитализации пользователя
* @param blockchain экземпляр блокчейна
* @param coopname название кооператива
* @param username имя пользователя
* @returns кошелек капитализации или null если не найден
*/
export async function getCapitalWallet(
blockchain: Blockchain,
coopname: string,
username: string,
) {
const capitalWallets = await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'capwallets',
100,
username,
username,
2, // by_username index
)
return capitalWallets[0] || null
}
@@ -1,18 +0,0 @@
import { CapitalContract } from 'cooptypes'
import type Blockchain from '../../blockchain'
import { makeCombinedChecksum256NameIndexKey } from '../shared/combinedKeys'
export async function getProject(blockchain: Blockchain, coopname: string, project_hash: string): Promise<CapitalContract.Tables.Projects.IProject> {
const rows = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
CapitalContract.Tables.Projects.tableName,
1000,
project_hash,
project_hash,
3,
'sha256',
))
return rows[0]
}
@@ -1,30 +0,0 @@
import { CapitalContract } from 'cooptypes'
import type Blockchain from '../../blockchain'
/**
* Получает кошелек проекта пользователя
* @param blockchain экземпляр блокчейна
* @param coopname название кооператива
* @param projectHash хеш проекта
* @param username имя пользователя
* @returns кошелек проекта или null если не найден
*/
export async function getProjectWallet(
blockchain: Blockchain,
coopname: string,
projectHash: string,
username: string,
) {
const projectWallets = await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'projwallets',
100,
username,
username,
3, // by_username index
)
// Ищем кошелек для конкретного проекта
return projectWallets.find((wallet: any) => wallet.project_hash === projectHash) || null
}
@@ -1,19 +0,0 @@
import { CapitalContract } from 'cooptypes'
import type Blockchain from '../../blockchain'
import { makeCombinedChecksum256NameIndexKey } from '../shared/combinedKeys'
export async function getSegment(blockchain: Blockchain, coopname: string, project_hash: string, author: string): Promise<CapitalContract.Tables.Segments.ISegment> {
const key = makeCombinedChecksum256NameIndexKey(project_hash, author)
const rows = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
CapitalContract.Tables.Segments.tableName,
1000,
key,
key,
3,
'i128',
))
return rows[0]
}
@@ -4,10 +4,7 @@ import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { generateRandomSHA256 } from '../../utils/randomHash'
import { getCoopProgramWallet, getUserProgramWallet } from '../wallet/walletUtils'
import { processDecision } from '../soviet/processDecision'
import { consoleIt } from '../shared'
import { processApprove } from './processApprove'
import { getSegment } from './getSegment'
import { capitalProgramId, walletProgramId } from './consts'
import { capitalProgramId, sourceProgramId } from './consts'
export async function investInProject(
blockchain: any,
@@ -20,6 +17,18 @@ export async function investInProject(
const investHash = generateRandomSHA256()
console.log(`\n🔹 Начало инвестиции: ${investHash}`)
// Получение текущих данных перед инвестицией
const prevContributor = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'contributors',
1,
investor,
investor,
2,
'i64',
))[0] || { invested: '0.0000 RUB' }
const prevProject = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
@@ -31,19 +40,19 @@ export async function investInProject(
'sha256',
))[0] || { invested: '0.0000 RUB', available: '0.0000 RUB' }
const prevWalletWallet = await getUserProgramWallet(blockchain, coopname, investor, walletProgramId) || { blocked: '0.0000 RUB' }
const prevUserWallet = await getUserProgramWallet(blockchain, coopname, investor, capitalProgramId) || { blocked: '0.0000 RUB' }
const prevProgramWallet = await getCoopProgramWallet(blockchain, coopname, capitalProgramId) || { blocked: '0.0000 RUB', share_contributions: '0.0000 RUB' }
console.log('📊 Балансы до инвестиции:')
console.log('▶ Контрибьютор:', prevContributor)
console.log('▶ Проект:', prevProject)
console.log('▶ Главный кошелек пользователя:', prevWalletWallet)
console.log('▶ Кошелек пользователя (благорост):', prevUserWallet)
console.log('▶ Кошелек программы (благорост):', prevProgramWallet)
console.log('▶ Сумма инвестиции: ', investAmount)
console.log('▶ Кошелек пользователя:', prevUserWallet)
console.log('▶ Кошелек программы:', prevProgramWallet)
// Создание инвестиции
const createInvestData: CapitalContract.Actions.CreateProjectInvest.ICreateInvest = {
const createInvestData: CapitalContract.Actions.CreateInvest.ICreateInvest = {
coopname,
application: coopname,
project_hash: projectHash,
username: investor,
invest_hash: investHash,
@@ -57,7 +66,7 @@ export async function investInProject(
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.CreateProjectInvest.actionName,
name: CapitalContract.Actions.CreateInvest.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data: createInvestData,
},
@@ -68,31 +77,11 @@ export async function investInProject(
expireSeconds: 30,
},
)
consoleIt(createInvestResult)
getTotalRamUsage(createInvestResult)
expect(createInvestResult.transaction_id).toBeDefined()
// const blockchainInvest = (await blockchain.getTableRows(
// CapitalContract.contractName.production,
// coopname,
// 'invests',
// 1,
// investHash,
// investHash,
// 2,
// 'sha256',
// ))[0]
// console.log('🔍 Инвестиция в блокчейне:', blockchainInvest)
// expect(blockchainInvest).toBeDefined()
// expect(blockchainInvest.status).toBe('created')
// Утверждение инвестиции
// console.log(`\n✅ Подтверждение инвестиции ${investHash}`)
// const approveInvestResult = await processApprove(blockchain, coopname, investHash)
// Проверка утвержденной инвестиции
const blockchainEmptyInvest = (await blockchain.getTableRows(
let blockchainInvest = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'invests',
@@ -103,10 +92,94 @@ export async function investInProject(
'sha256',
))[0]
// Инвестиция удаляется после утверждения
expect(blockchainEmptyInvest).toBeUndefined()
console.log('🔍 Инвестиция в блокчейне:', blockchainInvest)
expect(blockchainInvest).toBeDefined()
expect(blockchainInvest.status).toBe('created')
const finalProject = (await blockchain.getTableRows(
// Утверждение инвестиции
const approveInvestData: CapitalContract.Actions.ApproveInvest.IApproveInvest = {
coopname,
application: coopname,
approver: 'ant',
invest_hash: investHash,
approved_statement: fakeDocument,
}
console.log(`\n✅ Подтверждение инвестиции ${investHash}`)
const approveInvestResult = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.ApproveInvest.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data: approveInvestData,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
getTotalRamUsage(approveInvestResult)
expect(approveInvestResult.transaction_id).toBeDefined()
blockchainInvest = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'invests',
1,
investHash,
investHash,
2,
'sha256',
))[0]
console.log('🔍 Инвестиция после утверждения:', blockchainInvest)
expect(blockchainInvest).toBeDefined()
expect(blockchainInvest.status).toBe('approved')
// Получение всех решений и выполнение последнего
const decisions = await blockchain.getTableRows(
SovietContract.contractName.production,
coopname,
'decisions',
1000,
)
const lastDecision = decisions[decisions.length - 1]
console.log(`\n📜 Выполнение последнего решения: ${lastDecision.id}`)
await processDecision(blockchain, lastDecision.id)
blockchainInvest = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'invests',
1,
investHash,
investHash,
2,
'sha256',
))[0]
console.log('📌 Инвестиция после завершения:', blockchainInvest)
expect(blockchainInvest).toBeUndefined()
// Получение новых данных после инвестиции
const contributor = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'contributors',
1,
investor,
investor,
2,
'i64',
))[0]
const project = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'projects',
@@ -117,30 +190,24 @@ export async function investInProject(
'sha256',
))[0]
const finalUserWallet = await getUserProgramWallet(blockchain, coopname, investor, capitalProgramId)
const finalProgramWallet = await getCoopProgramWallet(blockchain, coopname, capitalProgramId)
// Получение сегмента инвестора для данного проекта
const segment = await getSegment(blockchain, coopname, projectHash, investor)
const userWallet = await getUserProgramWallet(blockchain, coopname, investor, capitalProgramId)
const programWallet = await getCoopProgramWallet(blockchain, coopname, capitalProgramId)
console.log('\n📊 Балансы после инвестиции:')
console.log('▶ Проект:', finalProject)
console.log('▶ Сегмент:', segment)
console.log('▶ Кошелек пользователя (благорост):', finalUserWallet)
console.log('▶ Кошелек программы (благорост):', finalProgramWallet)
console.log('▶ Контрибьютор:', contributor)
console.log('▶ Проект:', project)
console.log('▶ Кошелек пользователя:', userWallet)
console.log('▶ Кошелек программы:', programWallet)
// Проверка изменения балансов
expect(parseFloat(finalUserWallet.blocked)).toBeCloseTo(parseFloat(prevUserWallet.blocked) + parseFloat(investAmount), 1)
expect(parseFloat(finalProgramWallet.blocked)).toBeCloseTo(parseFloat(prevProgramWallet.blocked) + parseFloat(investAmount), 1)
expect(parseFloat(contributor.invested)).toBe(parseFloat(prevContributor.invested) + parseFloat(investAmount))
expect(parseFloat(project.invested)).toBe(parseFloat(prevProject.invested) + parseFloat(investAmount))
expect(parseFloat(project.available)).toBe(parseFloat(prevProject.available) + parseFloat(investAmount))
expect(parseFloat(userWallet.blocked)).toBe(parseFloat(prevUserWallet.blocked) + parseFloat(investAmount))
expect(parseFloat(programWallet.blocked)).toBe(parseFloat(prevProgramWallet.blocked) + parseFloat(investAmount))
expect(parseFloat(programWallet.share_contributions)).toBe(parseFloat(prevProgramWallet.share_contributions) + parseFloat(investAmount))
console.log(`\n✅ Инвестирование на ${investAmount} завершено успешно!`)
return {
investHash,
// invest: blockchainInvest,
transactionId: createInvestResult.transaction_id,
prevProject,
project: finalProject,
segment,
}
return { investHash, transactionId: approveInvestResult.transaction_id }
}
@@ -1,53 +0,0 @@
import { expect } from 'vitest'
import { CapitalContract } from 'cooptypes'
import type Blockchain from '../../blockchain'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { getSegment } from './getSegment'
export async function processAddContributor(
blockchain: Blockchain,
coopname: string,
project_hash: string,
username: string,
) {
// Состояние сегмента до добавления (может не существовать)
let segmentBefore: any = null
try {
segmentBefore = await getSegment(blockchain, coopname, project_hash, username)
}
catch (error) {
// Сегмент может не существовать - это нормально
}
const data: CapitalContract.Actions.RegisterShare.IRegisterShare = {
coopname,
project_hash,
username,
}
// Выполняем добавление участника в проект
const txAdd = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.RegisterShare.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data,
},
],
},
{ blocksBehind: 3, expireSeconds: 30 },
)
getTotalRamUsage(txAdd)
expect(txAdd.transaction_id).toBeDefined()
// Состояние сегмента после добавления
const segmentAfter = await getSegment(blockchain, coopname, project_hash, username)
return {
transactionId: txAdd.transaction_id,
segmentBefore,
segmentAfter,
}
}
@@ -1,37 +0,0 @@
import { expect } from 'vitest'
import { SovietContract } from 'cooptypes'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { fakeDocument } from '../shared/fakeDocument'
export async function processApprove(blockchain: any, coopname: string, approvalHash: string) {
fakeDocument.signatures[0].signer = 'ant'
const data: SovietContract.Actions.Approves.ConfirmApprove.IConfirmApprove = {
coopname,
username: 'ant',
approval_hash: approvalHash,
approved_document: fakeDocument,
}
const result = await blockchain.api.transact(
{
actions: [
{
account: SovietContract.contractName.production,
name: SovietContract.Actions.Approves.ConfirmApprove.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
getTotalRamUsage(result)
expect(result.transaction_id).toBeDefined()
return result
}
@@ -1,111 +0,0 @@
import { expect } from 'vitest'
import { CapitalContract } from 'cooptypes'
import type Blockchain from '../../blockchain'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { makeCombinedChecksum256NameIndexKey } from '../shared/combinedKeys'
/**
* Функция для расчета голосов участника по методу Водянова
* @param blockchain экземпляр блокчейна
* @param data данные для расчета голосов
*/
export async function processCalculateVotes(
blockchain: Blockchain,
data: CapitalContract.Actions.CalculateVotes.IFinalVoting,
) {
const { coopname, username, project_hash } = data
console.log(`\n🧮 Начало расчета голосов для участника ${username} в проекте ${project_hash}`)
// Состояние до: данные проекта
const projectBefore = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'projects',
1,
project_hash,
project_hash,
3,
'sha256',
))[0]
// Состояние до: данные сегмента участника
const segmentKey = makeCombinedChecksum256NameIndexKey(project_hash, username)
const segmentBefore = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'segments',
1,
segmentKey,
segmentKey,
3,
'i128',
))[0]
// console.log('📊 Состояние до расчета голосов:')
// console.log('▶ Статус проекта:', projectBefore.status)
// console.log('▶ Голосов получено:', projectBefore.voting.votes_received)
// console.log('▶ Всего участников голосования:', projectBefore.voting.total_voters)
// console.log(`▶ Сегмент участника ${username}:`)
// console.log(' - voting_bonus:', segmentBefore.voting_bonus)
// console.log(' - equal_author_bonus:', segmentBefore.equal_author_bonus)
// console.log(' - direct_creator_bonus:', segmentBefore.direct_creator_bonus)
// console.log(' - total_segment_cost:', segmentBefore.total_segment_cost)
// Отправляем транзакцию расчета голосов
const txResult = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.CalculateVotes.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data,
},
],
},
{ blocksBehind: 3, expireSeconds: 30 },
)
getTotalRamUsage(txResult)
expect(txResult.transaction_id).toBeDefined()
// Состояние после: данные проекта
const projectAfter = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'projects',
1,
project_hash,
project_hash,
3,
'sha256',
))[0]
// Состояние после: данные сегмента участника
const segmentAfter = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'segments',
1,
segmentKey,
segmentKey,
3,
'i128',
))[0]
console.log('📊 Состояние после расчета голосов:')
console.log('▶ Статус проекта:', projectAfter.status)
console.log(`▶ Сегмент участника ${username}:`)
console.log(' - voting_bonus:', segmentAfter.voting_bonus)
console.log(' - total_voting_pool:', projectAfter.voting.amounts.total_voting_pool)
console.log(' - active_voting_amount:', projectAfter.voting.amounts.active_voting_amount)
console.log(' - equal_voting_amount:', projectAfter.voting.amounts.equal_voting_amount)
return {
txId: txResult.transaction_id,
projectBefore,
projectAfter,
segmentBefore,
segmentAfter,
}
}
@@ -1,82 +0,0 @@
import { expect } from 'vitest'
import { CapitalContract } from 'cooptypes'
import type Blockchain from '../../blockchain'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
/**
* Функция для завершения голосования по проекту
* @param blockchain экземпляр блокчейна
* @param data данные для завершения голосования
*/
export async function processCompleteVoting(
blockchain: Blockchain,
data: CapitalContract.Actions.CompleteVoting.ICompleteVoting,
) {
const { coopname, project_hash } = data
console.log(`\n✅ Начало завершения голосования для проекта ${project_hash}`)
// Состояние до: данные проекта
const projectBefore = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'projects',
1,
project_hash,
project_hash,
3,
'sha256',
))[0]
console.log('📊 Состояние до завершения голосования:')
console.log('▶ Статус проекта:', projectBefore.status)
console.log('▶ Голосов получено:', projectBefore.voting.votes_received)
console.log('▶ Всего участников голосования:', projectBefore.voting.total_voters)
console.log('▶ Дедлайн голосования:', projectBefore.voting.voting_deadline)
// Отправляем транзакцию завершения голосования
const txResult = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.CompleteVoting.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data,
},
],
},
{ blocksBehind: 3, expireSeconds: 30 },
)
getTotalRamUsage(txResult)
expect(txResult.transaction_id).toBeDefined()
// Состояние после: данные проекта
const projectAfter = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'projects',
1,
project_hash,
project_hash,
3,
'sha256',
))[0]
console.log('📊 Состояние после завершения голосования:')
console.log('▶ Статус проекта:', projectAfter.status)
console.log('▶ Голосов получено:', projectAfter.voting.votes_received)
console.log('▶ Всего участников голосования:', projectAfter.voting.total_voters)
// Проверяем что статус изменился на result
expect(projectAfter.status).toBe('result')
console.log(`\n✅ Голосование для проекта ${project_hash} успешно завершено!`)
return {
projectHash: project_hash,
txId: txResult.transaction_id,
projectBefore,
projectAfter,
}
}
@@ -1,176 +0,0 @@
import { expect } from 'vitest'
import { CapitalContract } from 'cooptypes'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { generateRandomSHA256 } from '../../utils/randomHash'
import { fakeDocument } from '../shared/fakeDocument'
import type Blockchain from '../../blockchain'
import { getCoopProgramWallet, getUserProgramWallet } from '../wallet/walletUtils'
import { getSegment } from './getSegment'
import { getProjectWallet } from './getProjectWallet'
import { capitalProgramId, walletProgramId } from './consts'
import { getProject } from './getProject'
/**
* Функция для конвертации сегмента участника
* @param blockchain экземпляр блокчейна
* @param coopname название кооператива
* @param username имя пользователя
* @param projectHash хеш проекта
* @param walletAmount сумма для конвертации в главный кошелек
* @param capitalAmount сумма для конвертации в капитализацию
* @param projectAmount сумма для конвертации в кошелек проекта
*/
export async function processConvertSegment(
blockchain: Blockchain,
coopname: string,
username: string,
projectHash: string,
walletAmount: string,
capitalAmount: string,
projectAmount: string,
) {
const convertHash = generateRandomSHA256()
console.log(`\n🔹 Начало процесса конвертации сегмента: ${convertHash}`)
// Получаем сегмент до конвертации
const segmentBefore = await getSegment(blockchain, coopname, projectHash, username)
console.log('📊 Сегмент до конвертации:', segmentBefore)
// Получаем кошельки до конвертации
const mainWalletBefore = await getCoopProgramWallet(blockchain, coopname, walletProgramId)
const capitalWalletBefore = await getCoopProgramWallet(blockchain, coopname, capitalProgramId)
const userCapitalWalletBefore = await getUserProgramWallet(blockchain, coopname, username, capitalProgramId)
const projectWalletBefore = await getProjectWallet(blockchain, coopname, projectHash, username)
const projectBefore = await getProject(blockchain, coopname, projectHash)
console.log('💰 Кошельки до конвертации:')
console.log(' ▶ Главный кошелек программы:', mainWalletBefore)
console.log(' ▶ Глобальный кошелек программы капитализации:', capitalWalletBefore)
console.log(' ▶ Кошелек пользователя в программе капитализации:', userCapitalWalletBefore)
console.log(' ▶ Кошелек проекта:', projectWalletBefore)
console.log(' ▶ Проект до конвертации: ', projectBefore)
// 1. Конвертируем сегмент
const convertSegmentData: CapitalContract.Actions.ConvertSegment.IConvertSegment = {
coopname,
username,
project_hash: projectHash,
convert_hash: convertHash,
wallet_amount: walletAmount,
capital_amount: capitalAmount,
convert_statement: fakeDocument,
}
console.log(`\n🚀 Отправка транзакции ConvertSegment для ${username}`)
console.log(` В главный кошелек: ${walletAmount}`)
console.log(` В капитализацию: ${capitalAmount}`)
console.log(` В проект: ${projectAmount}`)
const convertResult = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.ConvertSegment.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data: convertSegmentData,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
getTotalRamUsage(convertResult)
expect(convertResult.transaction_id).toBeDefined()
console.log('✅ Сегмент конвертирован:', convertHash)
// Получаем сегмент после конвертации (должен быть удален)
const segmentAfter = await getSegment(blockchain, coopname, projectHash, username)
// Получаем кошельки после конвертации
const mainWalletAfter = await getCoopProgramWallet(blockchain, coopname, walletProgramId)
const capitalWalletAfter = await getCoopProgramWallet(blockchain, coopname, capitalProgramId)
const userCapitalWalletAfter = await getUserProgramWallet(blockchain, coopname, username, capitalProgramId)
const projectWalletAfter = await getProjectWallet(blockchain, coopname, projectHash, username)
console.log('💰 Кошельки после конвертации:')
console.log(' ▶ Главный кошелек программы:', mainWalletAfter)
console.log(' ▶ Глобальный кошелек программы капитализации:', capitalWalletAfter)
console.log(' ▶ Кошелек пользователя в программе капитализации:', userCapitalWalletAfter)
console.log(' ▶ Кошелек проекта:', projectWalletAfter)
// Проверяем что сегмент удален после конвертации
expect(segmentAfter).toBeUndefined()
console.log('🔍 Сегмент после конвертации: ', segmentAfter)
// Проверяем изменения в кошельках
console.log('\n🔍 Проверка изменений в кошельках:')
// Проверяем главный кошелек программы (wallet_amount)
if (walletAmount !== '0.0000 RUB') {
const walletAmountValue = parseFloat(walletAmount.split(' ')[0])
const beforeAvailable = mainWalletBefore ? parseFloat(mainWalletBefore.available.split(' ')[0]) : 0
const afterAvailable = mainWalletAfter ? parseFloat(mainWalletAfter.available.split(' ')[0]) : 0
const expectedIncrease = beforeAvailable + walletAmountValue
console.log(`✅ Главный кошелек программы: ${beforeAvailable}${afterAvailable} (+${walletAmountValue})`)
expect(afterAvailable).toBeCloseTo(expectedIncrease, 1)
}
// Проверяем глобальный кошелек программы капитализации (capital_amount)
// investor_base уже заблокирован в _capital_program при инвестировании (createinvest)
// В capitalAmount теперь передается только чистая дельта интеллектуального вклада
if (capitalAmount !== '0.0000 RUB') {
const capitalAmountValue = parseFloat(capitalAmount.split(' ')[0])
const actualCapitalIncrease = capitalAmountValue
const beforeBlocked = capitalWalletBefore ? parseFloat(capitalWalletBefore.blocked.split(' ')[0]) : 0
const afterBlocked = capitalWalletAfter ? parseFloat(capitalWalletAfter.blocked.split(' ')[0]) : 0
const expectedIncrease = beforeBlocked + actualCapitalIncrease
console.log(`✅ Глобальный кошелек программы капитализации: ${beforeBlocked}${afterBlocked} (+${actualCapitalIncrease})`)
expect(afterBlocked).toBeCloseTo(expectedIncrease, 1)
}
// Проверяем кошелек пользователя в программе капитализации (capital_amount)
if (capitalAmount !== '0.0000 RUB') {
const capitalAmountValue = parseFloat(capitalAmount.split(' ')[0])
const actualCapitalIncrease = capitalAmountValue
const beforeBlocked = userCapitalWalletBefore ? parseFloat(userCapitalWalletBefore.blocked.split(' ')[0]) : 0
const afterBlocked = userCapitalWalletAfter ? parseFloat(userCapitalWalletAfter.blocked.split(' ')[0]) : 0
const expectedIncrease = beforeBlocked + actualCapitalIncrease
console.log(`✅ Кошелек пользователя в программе капитализации: ${beforeBlocked}${afterBlocked} (+${actualCapitalIncrease})`)
expect(afterBlocked).toBeCloseTo(expectedIncrease, 1)
}
// Проверяем кошелек проекта (project_amount)
if (projectAmount !== '0.0000 RUB' && projectBefore.parent_hash.includes('0000000000')) {
const projectAmountValue = parseFloat(projectAmount.split(' ')[0])
const beforeShares = projectWalletBefore ? parseFloat(projectWalletBefore.shares.split(' ')[0]) : 0
const afterShares = projectWalletAfter ? parseFloat(projectWalletAfter.shares.split(' ')[0]) : 0
const expectedIncrease = beforeShares + projectAmountValue
console.log(`✅ Кошелек проекта: ${beforeShares}${afterShares} (+${projectAmountValue})`)
expect(afterShares).toBeCloseTo(expectedIncrease, 1)
}
console.log('\n📊 Результаты после конвертации сегмента:')
console.log('▶ Сегмент до:', segmentBefore)
console.log('▶ Сегмент после удалён: ', segmentAfter)
console.log(`\n✅ Процесс конвертации сегмента завершен успешно!`)
return {
convertHash,
segmentBefore,
segmentAfter,
mainWalletBefore,
mainWalletAfter,
capitalWalletBefore,
capitalWalletAfter,
userCapitalWalletBefore,
userCapitalWalletAfter,
projectWalletBefore,
projectWalletAfter,
transactionId: convertResult.transaction_id,
}
}
@@ -1,109 +0,0 @@
import { expect } from 'vitest'
import { CapitalContract } from 'cooptypes'
import type Blockchain from '../../blockchain'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { getCoopProgramWallet, getLedgerAccountById } from '../wallet/walletUtils'
import { processLastDecision } from '../soviet/processLastDecision'
import { processApprove } from './processApprove'
import { capitalProgramId, circulationAccountId } from './consts'
export async function processCreateProgramProperty(
blockchain: Blockchain,
data: CapitalContract.Actions.CreateProgramProperty.ICreateProgramProperty,
fakeDocument: any,
) {
const { coopname, username, property_hash } = data
// Состояния до: кошелек программы капитализации и паевой счет в бухгалтерии
const programWalletBefore = await getCoopProgramWallet(blockchain, coopname, capitalProgramId)
const ledgerShareBefore = await getLedgerAccountById(blockchain, coopname, circulationAccountId)
// 1) Создаём программное предложение имущественного взноса
const txCreate = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.CreateProgramProperty.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data,
},
],
},
{ blocksBehind: 3, expireSeconds: 30 },
)
getTotalRamUsage(txCreate)
expect(txCreate.transaction_id).toBeDefined()
// 2) Подтверждение одобрения председателем (через soviet)
await processApprove(blockchain as any, coopname, property_hash as unknown as string)
// 3) Обработка решения совета (AUTHORIZE_PROGRAM_PROPERTY)
await processLastDecision(blockchain, coopname)
// 4) Акт 1 — подписывает участник-вноситель (username)
fakeDocument.signatures[0].signer = username
const act1Data = {
coopname,
username,
property_hash,
act: fakeDocument,
} as CapitalContract.Actions.Act1ProgramProperty.IAct1ProgramProperty
const txAct1 = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.Act1ProgramProperty.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data: act1Data,
},
],
},
{ blocksBehind: 3, expireSeconds: 30 },
)
getTotalRamUsage(txAct1)
expect(txAct1.transaction_id).toBeDefined()
// 5) Акт 2 — подписывает председатель совета кооператива ('ant')
fakeDocument.signatures[0].signer = 'ant'
const act2Data = {
coopname,
property_hash,
username: 'ant',
act: fakeDocument,
} as CapitalContract.Actions.Act2ProgramProperty.IAct2ProgramProperty
const txAct2 = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.Act2ProgramProperty.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data: act2Data,
},
],
},
{ blocksBehind: 3, expireSeconds: 30 },
)
getTotalRamUsage(txAct2)
expect(txAct2.transaction_id).toBeDefined()
// Состояния после
const programWalletAfter = await getCoopProgramWallet(blockchain, coopname, capitalProgramId)
const ledgerShareAfter = await getLedgerAccountById(blockchain, coopname, circulationAccountId)
return {
propertyHash: property_hash,
txCreateId: txCreate.transaction_id,
txAct1Id: txAct1.transaction_id,
txAct2Id: txAct2.transaction_id,
programWalletBefore,
programWalletAfter,
ledgerShareBefore,
ledgerShareAfter,
}
}
@@ -1,69 +0,0 @@
import { expect } from 'vitest'
import { CapitalContract } from 'cooptypes'
import type Blockchain from '../../blockchain'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { processLastDecision } from '../soviet/processLastDecision'
import { processApprove } from './processApprove'
import { getSegment } from './getSegment'
export async function processCreateProjectProperty(
blockchain: Blockchain,
data: CapitalContract.Actions.CreateProjectProperty.ICreateProjectProperty,
) {
const { coopname, username, project_hash, property_hash } = data
// Состояния до: сегмент проекта и данные проекта
const segmentBefore = await getSegment(blockchain, coopname, project_hash, username)
const projectBefore = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'projects',
1,
project_hash,
project_hash,
3,
'sha256',
))[0]
// 1) Создаём проектное предложение имущественного взноса
const txCreate = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.CreateProjectProperty.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data,
},
],
},
{ blocksBehind: 3, expireSeconds: 30 },
)
getTotalRamUsage(txCreate)
expect(txCreate.transaction_id).toBeDefined()
// 2) Подтверждение одобрения председателем (через soviet)
await processApprove(blockchain as any, coopname, property_hash as unknown as string)
// Состояния после: сегмент проекта и данные проекта
const segmentAfter = await getSegment(blockchain, coopname, project_hash, username)
const projectAfter = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'projects',
1,
project_hash,
project_hash,
3,
'sha256',
))[0]
return {
propertyHash: property_hash,
txCreateId: txCreate.transaction_id,
segmentBefore,
segmentAfter,
projectBefore,
projectAfter,
}
}
@@ -1,179 +0,0 @@
import { expect } from 'vitest'
import { CapitalContract, GatewayContract } from 'cooptypes'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { generateRandomSHA256 } from '../../utils/randomHash'
import { processLastDecision } from '../soviet/processLastDecision'
import type Blockchain from '../../blockchain'
import { formatDateToISOString } from '../shared/formatDateToISOString'
import { processApprove } from './processApprove'
import { getSegment } from './getSegment'
export async function processDebt(
blockchain: Blockchain,
coopname: string,
username: string,
projectHash: string,
debtAmount: string,
fakeDocument: any,
repaidAtDate: Date,
) {
const debtHash = generateRandomSHA256()
console.log(`\n🔹 Начало процесса создания долга: ${debtHash}`)
// Дата возврата через месяц
const repaidAt = formatDateToISOString(repaidAtDate)
// Получаем сегмент до создания долга
const segmentBefore = await getSegment(blockchain, coopname, projectHash, username)
console.log('📊 Сегмент до долга:', segmentBefore)
// Получаем контрибьютора до создания долга
const contributorsBefore = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'contributors',
1,
username,
username,
2,
'i64',
))
const contributorBefore = contributorsBefore[0]
console.log('📊 Контрибьютор до долга:', contributorBefore)
fakeDocument.signatures[0].signer = username
// 1. Создаем долг
const createDebtData: CapitalContract.Actions.CreateDebt.ICreateDebt = {
coopname,
username,
debt_hash: debtHash,
project_hash: projectHash,
amount: debtAmount,
repaid_at: repaidAt,
statement: fakeDocument,
}
console.log(`\n🚀 Отправка транзакции CreateDebt для ${username} на сумму ${debtAmount}`)
const createDebtResult = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.CreateDebt.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data: createDebtData,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
getTotalRamUsage(createDebtResult)
expect(createDebtResult.transaction_id).toBeDefined()
console.log('✅ Долг создан:', debtHash)
// Проверяем что долг создался
const debts = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'debts',
1,
debtHash,
debtHash,
3,
'sha256',
))
const createdDebt = debts[0]
console.log('🔍 Долг в блокчейне:', createdDebt)
expect(createdDebt).toBeDefined()
expect(createdDebt.status).toBe('created')
// 2. Одобряем долг через processApprove (soviet контракт)
console.log(`\n✅ Подтверждение долга ${debtHash} через soviet`)
await processApprove(blockchain, coopname, debtHash)
console.log('✅ Долг одобрен председателем (создана agenda)')
// 3. Процессим решение совета (agenda для авторизации долга)
await processLastDecision(blockchain, coopname)
console.log('✅ Решение совета принято (долг авторизован, outcome создан)')
// 4. Подтверждаем завершение вывода (gateway сам вызовет callback на capital)
const confirmOutcomeData: GatewayContract.Actions.CompleteOutcome.ICompleteOutcome = {
coopname,
outcome_hash: debtHash,
}
console.log(`\n✅ Подтверждение завершения вывода`)
const confirmResult = await blockchain.api.transact(
{
actions: [
{
account: GatewayContract.contractName.production,
name: GatewayContract.Actions.CompleteOutcome.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data: confirmOutcomeData,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
getTotalRamUsage(confirmResult)
expect(confirmResult.transaction_id).toBeDefined()
console.log('✅ Оплата долга подтверждена')
// Получаем финальные состояния
const segmentAfter = await getSegment(blockchain, coopname, projectHash, username)
const contributorsAfter = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'contributors',
1,
username,
username,
2,
'i64',
))
const contributorAfter = contributorsAfter[0]
// Получаем долг после подтверждения оплаты
const debtsAfter = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'debts',
1,
debtHash,
debtHash,
3,
'sha256',
))
const debtAfter = debtsAfter[0]
console.log('\n📊 Результаты после обработки долга:')
console.log('▶ Сегмент до:', segmentBefore)
console.log('▶ Сегмент после:', segmentAfter)
console.log('▶ Контрибьютор до:', contributorBefore)
console.log('▶ Контрибьютор после:', contributorAfter)
console.log('▶ Долг после оплаты:', debtAfter)
console.log(`\n✅ Процесс долга на ${debtAmount} завершен успешно!`)
return {
debtHash,
debt: createdDebt,
debtAfter,
segmentBefore,
segmentAfter,
contributorBefore,
contributorAfter,
transactionId: confirmResult.transaction_id,
}
}
@@ -1,62 +0,0 @@
import { expect } from 'vitest'
import { CapitalContract } from 'cooptypes'
import type Blockchain from '../../blockchain'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
export async function processDeleteProject(
blockchain: Blockchain,
coopname: string,
project_hash: string,
) {
// Состояние проекта до удаления
const projectBefore = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'projects',
1,
project_hash,
project_hash,
3,
'sha256',
))[0]
const data: CapitalContract.Actions.DeleteProject.IDeleteProject = {
coopname,
project_hash,
}
// Выполняем удаление проекта
const txDelete = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.DeleteProject.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data,
},
],
},
{ blocksBehind: 3, expireSeconds: 30 },
)
getTotalRamUsage(txDelete)
expect(txDelete.transaction_id).toBeDefined()
// Проверяем что проект удален
const projectsAfter = await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'projects',
1,
project_hash,
project_hash,
3,
'sha256',
)
return {
transactionId: txDelete.transaction_id,
projectBefore,
projectAfter: projectsAfter[0] || null, // null если удален
}
}
@@ -1,61 +0,0 @@
import { expect } from 'vitest'
import { CapitalContract } from 'cooptypes'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { getCapitalWallet } from './getCapitalWallet'
export async function processFundProgram(
blockchain: any,
coopname: string,
amount: string,
memo: string = 'Тестовое финансирование программы',
) {
console.log(`\n🔹 Начало финансирования программы капитализации: сумма=${amount}, memo=${memo}`)
// Получение данных ДО финансирования
const programWalletBefore = await getCapitalWallet(blockchain, coopname, coopname)
console.log('📊 Состояние программы ДО финансирования:')
console.log('▶ Кошелек программы:', programWalletBefore)
// Финансирование программы капитализации
const fundData: CapitalContract.Actions.FundProgram.IFundProgram = {
coopname,
amount,
memo,
}
const fundResult = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.FundProgram.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data: fundData,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
getTotalRamUsage(fundResult)
expect(fundResult.transaction_id).toBeDefined()
// Получение данных ПОСЛЕ финансирования
const programWalletAfter = await getCapitalWallet(blockchain, coopname, coopname)
console.log('📊 Состояние программы ПОСЛЕ финансирования:')
console.log('▶ Кошелек программы:', programWalletAfter)
console.log(`\n✅ Финансирование программы капитализации завершено успешно!`)
return {
transactionId: fundResult.transaction_id,
transaction: fundResult,
programWalletBefore,
programWalletAfter,
}
}
@@ -1,63 +0,0 @@
import { expect } from 'vitest'
import { CapitalContract } from 'cooptypes'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { getProjectWallet } from './getProjectWallet'
export async function processFundProject(
blockchain: any,
coopname: string,
projectHash: string,
amount: string,
memo: string = 'Тестовое финансирование проекта',
) {
console.log(`\n🔹 Начало финансирования проекта: проект=${projectHash}, сумма=${amount}, memo=${memo}`)
// Получение данных ДО финансирования
const projectWalletBefore = await getProjectWallet(blockchain, coopname, projectHash, coopname)
console.log('📊 Состояние проекта ДО финансирования:')
console.log('▶ Кошелек проекта:', projectWalletBefore)
// Финансирование проекта
const fundData: CapitalContract.Actions.FundProject.IFundProject = {
coopname,
project_hash: projectHash,
amount,
memo,
}
const fundResult = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.FundProject.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data: fundData,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
getTotalRamUsage(fundResult)
expect(fundResult.transaction_id).toBeDefined()
// Получение данных ПОСЛЕ финансирования
const projectWalletAfter = await getProjectWallet(blockchain, coopname, projectHash, coopname)
console.log('📊 Состояние проекта ПОСЛЕ финансирования:')
console.log('▶ Кошелек проекта:', projectWalletAfter)
console.log(`\n✅ Финансирование проекта завершено успешно!`)
return {
transactionId: fundResult.transaction_id,
transaction: fundResult,
projectWalletBefore,
projectWalletAfter,
}
}
@@ -1,30 +0,0 @@
import { expect } from 'vitest'
import { CapitalContract } from 'cooptypes'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
export async function processMasterApprove(
blockchain: any,
coopname: string,
commitHash: string,
master: string,
) {
const data: CapitalContract.Actions.CommitApprove.ICommitApprove = {
coopname,
master,
commit_hash: commitHash,
}
const result = await blockchain.transactWithLogs([
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.CommitApprove.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data,
},
])
getTotalRamUsage(result)
expect(result.transaction_id).toBeDefined()
return result
}
@@ -1,180 +0,0 @@
import { expect } from 'vitest'
import { CapitalContract, SovietContract } from 'cooptypes'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { generateRandomSHA256 } from '../../utils/randomHash'
import { setDocumentSignatures } from '../../utils/setDocumentSignatures'
import { processLastDecision } from '../soviet/processLastDecision'
import { fakeDocument } from '../shared/fakeDocument'
import type Blockchain from '../../blockchain'
import { getSegment } from './getSegment'
import { processApprove } from './processApprove'
/**
* Функция для внесения результата в кооператив
* @param blockchain экземпляр блокчейна
* @param coopname название кооператива
* @param username имя пользователя
* @param projectHash хеш проекта
* @param contributionAmount сумма взноса
* @param debtAmount сумма долга к погашению
* @param debtHashes вектор хэшей долгов для погашения
*/
export async function processPushResult(
blockchain: Blockchain,
coopname: string,
username: string,
projectHash: string,
contributionAmount: string,
debtAmount: string,
debtHashes: string[] = [],
) {
const resultHash = generateRandomSHA256()
console.log(`\n🔹 Начало процесса внесения результата: ${resultHash}`)
// Получаем сегмент до внесения результата
const segmentBefore = await getSegment(blockchain, coopname, projectHash, username)
console.log('📊 Сегмент до внесения результата:', segmentBefore)
// Устанавливаем подписи документа
const documentWithSignatures = setDocumentSignatures([username])
// 1. Вносим результат
const pushResultData: CapitalContract.Actions.PushResult.IPushResult = {
coopname,
username,
project_hash: projectHash,
result_hash: resultHash,
contribution_amount: contributionAmount,
debt_amount: debtAmount,
statement: documentWithSignatures,
debt_hashes: debtHashes,
}
console.log(`\n🚀 Отправка транзакции PushResult для ${username} на сумму ${contributionAmount}`)
const pushResult = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.PushResult.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data: pushResultData,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
getTotalRamUsage(pushResult)
expect(pushResult.transaction_id).toBeDefined()
console.log('✅ Результат внесен:', resultHash)
// Проверяем что результат создался
const results = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'results',
1,
resultHash,
resultHash,
3,
'sha256',
))
const createdResult = results[0]
console.log('🔍 Результат в блокчейне:', createdResult)
expect(createdResult).toBeDefined()
expect(createdResult.status).toBe('created')
// 2. Одобряем результат через processApprove (soviet контракт)
console.log(`\n✅ Одобрение результата ${resultHash} через soviet`)
await processApprove(blockchain, coopname, resultHash)
console.log('✅ Результат одобрен советом')
// 3. Процессим решение совета
await processLastDecision(blockchain, coopname)
console.log('✅ Решение совета принято')
console.log('🔍 Подписываем акт №1 вкладчиком', documentWithSignatures)
// 4. Подписываем акт №1 вкладчиком
const signAct1Data: CapitalContract.Actions.SignAct1.ISignAct1 = {
coopname,
username,
result_hash: resultHash,
act: documentWithSignatures,
}
console.log(`\n✅ Подписание акта №1 вкладчиком ${username}`)
const signAct1Result = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.SignAct1.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data: signAct1Data,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
getTotalRamUsage(signAct1Result)
expect(signAct1Result.transaction_id).toBeDefined()
console.log('✅ Акт №1 подписан вкладчиком')
// Создаем документ с подписями вкладчика и председателя
const documentWithChairmanSignature = setDocumentSignatures([username, 'ant'])
// 5. Подписываем акт №2 председателем
const signAct2Data: CapitalContract.Actions.SignAct2.ISignAct2 = {
coopname,
result_hash: resultHash,
act: documentWithChairmanSignature,
chairman: 'ant',
}
console.log(`\n✅ Подписание акта №2 председателем`, documentWithChairmanSignature)
const signAct2Result = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.SignAct2.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data: signAct2Data,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
getTotalRamUsage(signAct2Result)
expect(signAct2Result.transaction_id).toBeDefined()
console.log('✅ Акт №2 подписан председателем')
// Получаем финальные состояния
const segmentAfter = await getSegment(blockchain, coopname, projectHash, username)
console.log('\n📊 Результаты после внесения результата:')
console.log('▶ Сегмент до:', segmentBefore)
console.log('▶ Сегмент после:', segmentAfter)
console.log(`\n✅ Процесс внесения результата на ${contributionAmount} завершен успешно!`)
return {
resultHash,
result: createdResult,
segmentBefore,
segmentAfter,
transactionId: signAct2Result.transaction_id,
}
}
@@ -1,59 +0,0 @@
import { expect } from 'vitest'
import { CapitalContract } from 'cooptypes'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { getCapitalWallet } from './getCapitalWallet'
export async function processRefreshProg(
blockchain: any,
coopname: string,
username: string,
) {
console.log(`\n🔹 Начало обновления CRPS пайщика в программе капитализации: пользователь=${username}`)
// Получение данных ДО обновления
const programWalletBefore = await getCapitalWallet(blockchain, coopname, username)
console.log('📊 Состояние пайщика ДО обновления CRPS:')
console.log('▶ Кошелек пайщика:', programWalletBefore)
// Обновление CRPS пайщика в программе капитализации
const refreshData: CapitalContract.Actions.RefreshProgram.IRefreshProgram = {
coopname,
username,
}
const refreshResult = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.RefreshProgram.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data: refreshData,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
getTotalRamUsage(refreshResult)
expect(refreshResult.transaction_id).toBeDefined()
// Получение данных ПОСЛЕ обновления
const programWalletAfter = await getCapitalWallet(blockchain, coopname, username)
console.log('📊 Состояние пайщика ПОСЛЕ обновления CRPS:')
console.log('▶ Кошелек пайщика:', programWalletAfter)
console.log(`\n✅ Обновление CRPS пайщика в программе капитализации завершено успешно!`)
return {
transactionId: refreshResult.transaction_id,
transaction: refreshResult,
programWalletBefore,
programWalletAfter,
}
}
@@ -1,61 +0,0 @@
import { expect } from 'vitest'
import { CapitalContract } from 'cooptypes'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { getProjectWallet } from './getProjectWallet'
export async function processRefreshProj(
blockchain: any,
coopname: string,
projectHash: string,
username: string,
) {
console.log(`\n🔹 Начало обновления CRPS пайщика в проекте: проект=${projectHash}, пользователь=${username}`)
// Получение данных ДО обновления
const projectWalletBefore = await getProjectWallet(blockchain, coopname, projectHash, username)
console.log('📊 Состояние кошелька проекта ДО обновления CRPS:')
console.log('▶ Кошелек проекта:', projectWalletBefore)
// Обновление CRPS пайщика в проекте
const refreshData: CapitalContract.Actions.RefreshProject.IRefreshProject = {
coopname,
project_hash: projectHash,
username,
}
const refreshResult = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.RefreshProject.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data: refreshData,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
getTotalRamUsage(refreshResult)
expect(refreshResult.transaction_id).toBeDefined()
// Получение данных ПОСЛЕ обновления
const projectWalletAfter = await getProjectWallet(blockchain, coopname, projectHash, username)
console.log('📊 Состояние кошелька проекта ПОСЛЕ обновления CRPS:')
console.log('▶ Кошелек проекта:', projectWalletAfter)
console.log(`\n✅ Обновление CRPS пайщика в проекте завершено успешно!`)
return {
transactionId: refreshResult.transaction_id,
transaction: refreshResult,
projectWalletBefore,
projectWalletAfter,
}
}
@@ -1,59 +0,0 @@
import { expect } from 'vitest'
import { CapitalContract } from 'cooptypes'
import type Blockchain from '../../blockchain'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
export async function processStartVoting(
blockchain: Blockchain,
data: CapitalContract.Actions.StartVoting.IStartVoting,
) {
const { coopname, project_hash } = data
// Состояние до: данные проекта
const projectBefore = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'projects',
1,
project_hash,
project_hash,
3,
'sha256',
))[0]
console.log('projectBefore: ', projectBefore)
// 1) Запускаем голосование по проекту
const txStart = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.StartVoting.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data,
},
],
},
{ blocksBehind: 3, expireSeconds: 30 },
)
getTotalRamUsage(txStart)
expect(txStart.transaction_id).toBeDefined()
// Состояние после: данные проекта
const projectAfter = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'projects',
1,
project_hash,
project_hash,
3,
'sha256',
))[0]
console.log('projectAfter: ', projectAfter)
return {
projectHash: project_hash,
txStartId: txStart.transaction_id,
projectBefore,
projectAfter,
}
}
@@ -1,61 +0,0 @@
import { expect } from 'vitest'
import { CapitalContract } from 'cooptypes'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import type Blockchain from '../../blockchain'
export async function refreshSegment(
blockchain: Blockchain,
coopname: string,
projectHash: string,
username: string,
) {
console.log(`\n🔄 Обновляем сегмент CRPS для ${username} в проекте ${projectHash}`)
// Получение данных сегмента перед обновлением
const prevSegment = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
CapitalContract.Tables.Segments.tableName,
1000,
)).filter((s: any) => s.project_hash === projectHash && s.username === username)[0]
// console.log('📊 Сегмент до обновления:', prevSegment)
// Обновление сегмента через rfrshsegment
const refreshSegmentData: CapitalContract.Actions.RefreshSegment.IRefreshSegment = {
coopname,
project_hash: projectHash,
username,
}
const refreshResult = await blockchain.transactWithLogs(
[
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.RefreshSegment.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data: refreshSegmentData,
},
],
)
getTotalRamUsage(refreshResult)
expect(refreshResult.transaction_id).toBeDefined()
// Получение обновленного сегмента
const updatedSegment = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
CapitalContract.Tables.Segments.tableName,
1000,
)).filter((s: any) => s.project_hash === projectHash && s.username === username)[0]
// console.log('📊 Сегмент после обновления:', updatedSegment)
console.log(`✅ Сегмент для ${username} обновлен успешно!`)
return {
transactionId: refreshResult.transaction_id,
prevSegment,
updatedSegment,
}
}
@@ -1,69 +1,3 @@
import { expect } from 'vitest'
import { CapitalContract } from 'cooptypes'
import { fakeDocument } from '../shared/fakeDocument'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { processApprove } from './processApprove'
export function registerContributor() {
export async function registerContributor(
blockchain: any,
coopname: string,
username: string,
contributorHash: string,
ratePerHour: string,
hoursPerDay: number = 8,
) {
const contract = fakeDocument
contract.signatures[0].signer = username
const data: CapitalContract.Actions.RegisterContributor.IRegisterContributor = {
coopname,
username,
contributor_hash: contributorHash,
rate_per_hour: ratePerHour,
hours_per_day: hoursPerDay,
is_external_contract: false,
contract,
storage_agreement: fakeDocument,
generator_agreement: fakeDocument,
blagorost_agreement: fakeDocument,
}
const result = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.RegisterContributor.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
getTotalRamUsage(result)
expect(result.transaction_id).toBeDefined()
// Аппрув председателя (через совет) по хэшу договора УХД
await processApprove(blockchain, coopname, contributorHash)
// Проверяем, что контрибьютор активирован и договор сохранён
const contributor = (
await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'contributors',
1,
username,
username,
2,
'i64',
)
)[0]
expect(contributor).toBeDefined()
expect(contributor.status).toBe('active')
expect(contributor.contract).toBeDefined()
}
@@ -1,62 +0,0 @@
import { expect } from 'vitest'
import { CapitalContract } from 'cooptypes'
import { fakeDocument } from '../shared/fakeDocument'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { processApprove } from './processApprove'
export async function signAppendix(
blockchain: any,
coopname: string,
username: string,
projectHash: string,
appendixHash: string,
) {
// Подписание приложения пайщиком (через кооператив, как и в контракте)
const data: CapitalContract.Actions.GetClearance.IGetClearance = {
coopname,
username,
project_hash: projectHash,
appendix_hash: appendixHash,
document: fakeDocument,
}
const result = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.GetClearance.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
getTotalRamUsage(result)
expect(result.transaction_id).toBeDefined()
// Аппрув председателя (через совет) по хэшу приложения
await processApprove(blockchain, coopname, appendixHash)
// Проверяем, что приложение применилось к контрибьютору, а запись удалена
const contributor = (
await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'contributors',
1,
username,
username,
2,
'i64',
)
)[0]
expect(contributor).toBeDefined()
expect(contributor.appendixes).toContain(projectHash)
}
@@ -3,7 +3,7 @@ import { signAgreement } from '../soviet/signAgreement'
import { getCoopProgramWallet, getUserProgramWallet } from '../wallet/walletUtils'
import { sourceProgramId, sourceProgramName } from './consts'
export async function signGenerationContract(
export async function signGenerationAgreement(
blockchain: any,
coopname: string,
username: string,
@@ -1,166 +0,0 @@
import { expect } from 'vitest'
import { CapitalContract } from 'cooptypes'
import type Blockchain from '../../blockchain'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
/**
* Интерфейс для распределения голосов
*/
export interface VoteInput {
recipient: string
amount: string
}
/**
* Создает равномерное распределение голосующей суммы между участниками (кроме самого голосующего)
* @param voters список всех участников голосования
* @param voter участник который голосует (исключается из распределения)
* @param votingAmount общая голосующая сумма для распределения
* @returns массив распределения голосов
*/
export function createVoteDistribution(
voters: string[],
voter: string,
votingAmount: string,
): VoteInput[] {
const otherVoters = voters.filter(v => v !== voter)
const votingAmountFloat = parseFloat(votingAmount.split(' ')[0])
const currency = votingAmount.split(' ')[1]
// Равномерно распределяем сумму между остальными участниками
const amountPerVoter = votingAmountFloat / otherVoters.length
return otherVoters.map(recipient => ({
recipient,
amount: `${amountPerVoter.toFixed(4)} ${currency}`,
}))
}
/**
* Функция для голосования по методу Водянова
* @param blockchain экземпляр блокчейна
* @param coopname имя кооператива
* @param voter участник который голосует
* @param projectHash хэш проекта
* @param voteDistribution массив голосов для распределения
*/
export async function submitVote(
blockchain: Blockchain,
coopname: string,
voter: string,
projectHash: string,
voteDistribution: VoteInput[],
) {
console.log(`\n🗳️ Начало голосования участника ${voter}`)
// Состояние до: данные проекта
const projectBefore = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'projects',
1,
projectHash,
projectHash,
3,
'sha256',
))[0]
// Получаем все голоса по проекту до голосования
const votesBefore = await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'votes',
100,
projectHash,
projectHash,
2,
'sha256',
)
// Получаем голоса конкретного участника до голосования
const voterVotesBefore = votesBefore.filter((vote: any) =>
vote.project_hash === projectHash && vote.voter === voter,
)
console.log('📊 Состояние до голосования:')
console.log('▶ Проект votes_received:', projectBefore.voting.votes_received)
console.log('▶ Всего голосов в таблице:', votesBefore.filter((v: any) => v.project_hash === projectHash).length)
console.log('▶ Голосов от участника:', voterVotesBefore.length)
// Формируем данные для транзакции
const votes = voteDistribution.map(v => ({
recipient: v.recipient,
amount: v.amount,
}))
const data: any = {
coopname,
voter,
project_hash: projectHash,
votes,
}
console.log('📝 Данные голосования:', JSON.stringify(data, null, 2))
// Отправляем транзакцию голосования
const txResult = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.SubmitVote.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data,
},
],
},
{ blocksBehind: 3, expireSeconds: 30 },
)
getTotalRamUsage(txResult)
expect(txResult.transaction_id).toBeDefined()
// Состояние после: данные проекта
const projectAfter = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'projects',
1,
projectHash,
projectHash,
3,
'sha256',
))[0]
// Получаем все голоса по проекту после голосования
const votesAfter = await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'votes',
100,
projectHash,
projectHash,
2,
'sha256',
)
// Получаем голоса конкретного участника после голосования
const voterVotesAfter = votesAfter.filter((vote: any) =>
vote.project_hash === projectHash && vote.voter === voter,
)
console.log('📊 Состояние после голосования:')
console.log('▶ Проект votes_received:', projectAfter.voting.votes_received)
console.log('▶ Всего голосов в таблице:', votesAfter.filter((v: any) => v.project_hash === projectHash).length)
console.log('▶ Голосов от участника:', voterVotesAfter.length)
return {
txId: txResult.transaction_id,
projectBefore,
projectAfter,
votesBefore: votesBefore.filter((v: any) => v.project_hash === projectHash),
votesAfter: votesAfter.filter((v: any) => v.project_hash === projectHash),
voterVotesBefore,
voterVotesAfter,
voteInput: voteDistribution,
}
}
@@ -1,57 +0,0 @@
// Комбинирование индекса (project_hash + username) как в C++ combine_checksum_ids
// Возвращает десятичную строку uint128 для использования с key_type 'i128'
function charToSymbol(c: string): number {
if (c === '.') return 0
if (c >= '1' && c <= '5') return c.charCodeAt(0) - '1'.charCodeAt(0) + 1
if (c >= 'a' && c <= 'z') return c.charCodeAt(0) - 'a'.charCodeAt(0) + 6
return 0
}
function nameToUint64(name: string): bigint {
let value = 0n
let i = 0
for (; i < 12 && i < name.length; i++) {
value <<= 5n
value |= BigInt(charToSymbol(name[i]))
}
value <<= 4n + 5n * BigInt(12 - i)
if (i < name.length) {
value |= BigInt(charToSymbol(name[i])) & 0x0fn
}
return value
}
function hexToBytes(hex: string): Uint8Array {
const clean = hex.startsWith('0x') ? hex.slice(2) : hex
if (clean.length !== 64) throw new Error('Invalid sha256 hex')
const out = new Uint8Array(32)
for (let i = 0; i < 32; i++) {
out[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16)
}
return out
}
function readBigUInt64LE(bytes: Uint8Array, offset = 0): bigint {
let result = 0n
for (let i = 0; i < 8; i++) {
result |= BigInt(bytes[offset + i]) << (8n * BigInt(i))
}
return result
}
export function makeCombinedChecksum256NameIndexKey(projectHashHex: string, username: string): string {
const bytes = hexToBytes(projectHashHex)
const truncatedHashLE = readBigUInt64LE(bytes, 0)
const nameValue = nameToUint64(username)
const key128 = (truncatedHashLE << 64n) | nameValue
return key128.toString()
}
export function makeCombinedChecksum256Uint64IndexKey(projectHashHex: string, id: number): string {
const bytes = hexToBytes(projectHashHex)
const truncatedHashLE = readBigUInt64LE(bytes, 0)
const idValue = BigInt(id)
const key128 = (truncatedHashLE << 64n) | idValue
return key128.toString()
}
@@ -1,84 +0,0 @@
interface ActionTrace {
act?: {
account: string
name: string
authorization?: { actor: string }[]
}
console?: string
inline_traces?: ActionTrace[]
}
interface TransactionResult {
processed?: {
action_traces?: ActionTrace[]
}
}
/**
* Извлекает и печатает все консоль логи из результата транзакции
* @param transaction - результат транзакции blockchain.api.transact
*/
export function consoleIt(transaction: TransactionResult): void {
if (!transaction?.processed?.action_traces) {
console.log('❌ Нет данных о транзакции или action traces')
return
}
console.log('\n📋 КОНСОЛЬ ЛОГИ ТРАНЗАКЦИИ:')
console.log('='.repeat(50))
let hasConsoleLogs = false
function processActionTrace(trace: ActionTrace, depth = 0): void {
const indent = ' '.repeat(depth)
// Получаем информацию о действии
const account = trace.act?.account || 'unknown'
const action = trace.act?.name || 'unknown'
const actor = trace.act?.authorization?.[0]?.actor || 'unknown'
// Печатаем заголовок действия
console.log(`${indent}🔧 ${account}:${action} (actor: ${actor})`)
// Если есть консоль логи, печатаем их
if (trace.console) {
hasConsoleLogs = true
const logs = trace.console.split('\n').filter(log => log.trim())
if (logs.length > 0) {
console.log(`${indent}📝 Console output:`)
logs.forEach((log) => {
console.log(`${indent} ${log}`)
})
}
}
else {
console.log(`${indent} (нет консоль логов)`)
}
// Обрабатываем inline traces рекурсивно
if (trace.inline_traces?.length) {
console.log(`${indent} ↳ Inline actions:`)
trace.inline_traces.forEach((inlineTrace) => {
processActionTrace(inlineTrace, depth + 1)
})
}
if (depth === 0) {
console.log('') // Пустая строка между действиями верхнего уровня
}
}
// Обрабатываем все action traces
transaction.processed.action_traces.forEach((trace, index) => {
console.log(`\n📍 Action ${index + 1}:`)
processActionTrace(trace, 0)
})
if (!hasConsoleLogs) {
console.log('ℹ️ В транзакции не найдено консоль логов')
}
console.log('='.repeat(50))
console.log('🏁 Конец логов транзакции\n')
}
@@ -1,16 +0,0 @@
export const fakeDocument = {
version: '1.0.0',
hash: '157192b276da23cc84ab078fc8755c051c5f0430bf4802e55718221e6b76c777',
doc_hash: '157192b276da23cc84ab078fc8755c051c5f0430bf4802e55718221e6b76c777',
meta_hash: '157192b276da23cc84ab078fc8755c051c5f0430bf4802e55718221e6b76c777',
meta: '{}',
signatures: [{
id: 1,
signed_hash: '157192b276da23cc84ab078fc8755c051c5f0430bf4802e55718221e6b76c777',
signer: 'cooperative1',
public_key: 'EOS5JhMfxbsNebajHcTEK8yC9uNN9Dit9hEmzE8ri8yMhhzxrLg3J',
signature: 'SIG_K1_KmKWPBC8dZGGDGhbKEoZEzPr3h5crRrR2uLdGRF5DJbeibY1MY1bZ9sPwHsgmPfiGFv9psfoCVsXFh9TekcLuvaeuxRKA8',
signed_at: '2025-05-14T12:22:26',
meta: '{}',
}],
}
@@ -1,10 +0,0 @@
export const fakeVote = {
version: '1.0.0',
coopname: 'voskhod',
username: 'ant',
decision_id: '1',
signed_at: '2025-05-14T12:22:26.000',
signed_hash: '157192B276DA23CC84AB078FC8755C051C5F0430BF4802E55718221E6B76C777',
signature: 'SIG_K1_KmKWPBC8dZGGDGhbKEoZEzPr3h5crRrR2uLdGRF5DJbeibY1MY1bZ9sPwHsgmPfiGFv9psfoCVsXFh9TekcLuvaeuxRKA8',
public_key: 'PUB_K1_5JhMfxbsNebajHcTEK8yC9uNN9Dit9hEmzE8ri8yMhhzzEtUA4',
}
@@ -1,8 +0,0 @@
/**
* Форматирует дату в строку формата 'YYYY-MM-DDTHH:mm:ss'
* @param date - Дата для форматирования
* @returns Строка в формате 'YYYY-MM-DDTHH:mm:ss'
*/
export function formatDateToISOString(date: Date): string {
return date.toISOString().slice(0, 19);
}
@@ -1,5 +0,0 @@
export * from './combinedKeys'
export * from './consoleIt'
export * from './fakeDocument'
export * from './fakeVote'
export * from './formatDateToISOString'
@@ -1,12 +1,14 @@
import { SovietContract } from 'cooptypes'
import { expect } from 'vitest'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { fakeDocument } from '../shared/fakeDocument'
import type Blockchain from '../../blockchain'
import { fakeVote } from '../shared/fakeVote'
import { fakeDocument } from './fakeDocument'
export async function processDecision(blockchain: Blockchain, decisionId: number) {
const voteData: SovietContract.Actions.Decisions.VoteFor.IVoteForDecision = fakeVote
voteData.decision_id = decisionId
export async function processDecision(blockchain: any, decisionId: number) {
const voteData: SovietContract.Actions.Decisions.VoteFor.IVoteForDecision = {
coopname: 'voskhod',
member: 'ant',
decision_id: decisionId,
}
const authData: SovietContract.Actions.Decisions.Authorize.IAuthorize = {
coopname: 'voskhod',
@@ -51,4 +53,5 @@ export async function processDecision(blockchain: Blockchain, decisionId: number
)
getTotalRamUsage(result)
expect(result.transaction_id).toBeDefined()
}
@@ -1,19 +0,0 @@
import { SovietContract } from 'cooptypes'
import type Blockchain from '../../blockchain'
import { processDecision } from './processDecision'
export async function processLastDecision(blockchain: Blockchain, coopname: string) {
// Получаем последнее решение для голосования
const decisions = await blockchain.getTableRows(
SovietContract.contractName.production,
coopname,
SovietContract.Tables.Decisions.tableName,
100, // берем больше, чтобы гарантированно получить все
)
console.log('Активные вопросы на повестке: ', decisions)
// Берем ПОСЛЕДНИЙ элемент массива (самый свежий)
const lastDecision = decisions[decisions.length - 1]
// 3. Голосуем за решение совета
await processDecision(blockchain, lastDecision.id)
}
@@ -3,7 +3,7 @@ import { GatewayContract, WalletContract } from 'cooptypes'
import { expect } from 'vitest'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { generateRandomSHA256 } from '../../utils/randomHash'
import { compareTokenAmounts, getCirculationAccount, getCoopProgramWallet, getDeposit, getUserProgramWallet } from './walletUtils'
import { compareTokenAmounts, getCoopProgramWallet, getCoopWallet, getDeposit, getUserProgramWallet } from './walletUtils'
export async function depositToWallet(blockchain: any, coopname: string, username: string, amount: number) {
const depositId = randomInt(100000)
@@ -48,7 +48,8 @@ export async function depositToWallet(blockchain: any, coopname: string, usernam
const prevUserWallet = await getUserProgramWallet(blockchain, coopname, username, 1)
const prevUserWalletAvailable = prevUserWallet?.available || '0.0000 RUB'
const prevCoopWalletAvailable = (await getCirculationAccount(blockchain, coopname)).available
const prevCoopWallet = await getCoopWallet(blockchain, coopname)
const prevCoopWalletAvailable = prevCoopWallet?.circulating_account?.available || '0.0000 RUB'
const data2: GatewayContract.Actions.CompleteIncome.ICompleteIncome = {
coopname,
@@ -84,7 +85,7 @@ export async function depositToWallet(blockchain: any, coopname: string, usernam
// Проверяем изменение балансов
compareTokenAmounts(prevUserWalletAvailable, userWallet.available, amount)
compareTokenAmounts(prevCoopWalletAvailable, (await getCirculationAccount(blockchain, coopname)).available, amount)
compareTokenAmounts(prevCoopWalletAvailable, (await getCoopWallet(blockchain, coopname)).circulating_account.available, amount)
return { depositId, program, userWallet }
}
@@ -1,7 +1,6 @@
import { FundContract, LedgerContract, SovietContract, WalletContract } from 'cooptypes'
import { FundContract, SovietContract, WalletContract } from 'cooptypes'
import { expect } from 'vitest'
import type Blockchain from '../../blockchain'
import { circulationAccountId } from '../capital/consts'
export async function getUserProgramWallet(blockchain: any, coopname: string, username: string, program_id: number) {
const wallets = await blockchain.getTableRows(
@@ -17,11 +16,6 @@ export async function getUserProgramWallet(blockchain: any, coopname: string, us
return wallets.find((el: any) => el.program_id === program_id)
}
export async function getUserProgramWalletAmount(blockchain: any, coopname: string, username: string, program_id: number) {
const wallet = await getUserProgramWallet(blockchain, coopname, username, program_id)
return wallet ? `${(parseFloat(wallet.available) + parseFloat(wallet.blocked)).toFixed(4)} RUB` : '0.0000 RUB'
}
export async function getCoopProgramWallet(blockchain: any, coopname: string, program_id: number) {
const program = await blockchain.getTableRows(
SovietContract.contractName.production,
@@ -35,25 +29,6 @@ export async function getCoopProgramWallet(blockchain: any, coopname: string, pr
return program[0]
}
export async function getLedgerAccounts(blockchain: Blockchain, coopname: string) {
return (await blockchain.getTableRows(
LedgerContract.contractName.production,
coopname,
LedgerContract.Tables.Laccount.tableName,
100,
))
}
export async function getLedgerAccountById(blockchain: Blockchain, coopname: string, accountId: number) {
const accounts = await getLedgerAccounts(blockchain, coopname)
return accounts.find((account: any) => account.id === accountId)
}
export async function getCirculationAccount(blockchain: Blockchain, coopname: string) {
const accounts = await getLedgerAccounts(blockchain, coopname)
return accounts.find((account: any) => account.id === circulationAccountId) || { available: '0.0000 RUB' }
}
export async function getCoopWallet(blockchain: any, coopname: string) {
return (await blockchain.getTableRows(
FundContract.contractName.production,
@@ -81,5 +56,5 @@ export function compareTokenAmounts(prevAmount: string, currentAmount: string, e
const current = parseFloat(currentAmount.split(' ')[0])
const expected = prev + expectedIncrease
expect(current).toBeCloseTo(expected, 4)
expect(current).toBe(expected)
}
-2
View File
@@ -16,5 +16,3 @@ export async function sendPostToCoopbackWithSecret(url: string, data: any) {
},
})
}
export * from './randomData'
-180
View File
@@ -1,180 +0,0 @@
import { randomBytes } from 'node:crypto'
/**
* Генерирует случайное текстовое содержимое для проектов
* @param minLength минимальная длина текста (по умолчанию 500)
* @param maxLength максимальная длина текста (по умолчанию 2000)
* @returns строка с случайным текстовым содержимым
*/
export function generateRandomProjectData(minLength = 500, maxLength = 2000): string {
const templates = [
'Описание проекта',
'Цели и задачи',
'Ожидаемые результаты',
'Этапы реализации',
'Требования к участникам',
'Технические спецификации',
'План работ',
'Бюджет проекта',
'Риски и ограничения',
'Методология',
]
const words = [
'разработка',
'система',
'модуль',
'компонент',
'интерфейс',
'база данных',
'архитектура',
'тестирование',
'оптимизация',
'интеграция',
'документация',
'анализ',
'проектирование',
'внедрение',
'поддержка',
'масштабирование',
'безопасность',
'производительность',
'надежность',
'мониторинг',
'отчетность',
'функциональность',
'пользователь',
'администратор',
'контент',
'данные',
'процесс',
'workflow',
'автоматизация',
'управление',
'контроль',
]
const targetLength = Math.floor(Math.random() * (maxLength - minLength)) + minLength
let content = ''
// Добавляем секции
while (content.length < targetLength) {
const template = templates[Math.floor(Math.random() * templates.length)]
content += `\n\n## ${template}\n\n`
// Добавляем параграфы
const paragraphCount = Math.floor(Math.random() * 3) + 1
for (let i = 0; i < paragraphCount; i++) {
const sentenceCount = Math.floor(Math.random() * 5) + 3
for (let j = 0; j < sentenceCount; j++) {
const wordCount = Math.floor(Math.random() * 10) + 5
const sentence = []
for (let k = 0; k < wordCount; k++) {
sentence.push(words[Math.floor(Math.random() * words.length)])
}
content += `${sentence.join(' ')}. `
}
content += '\n\n'
}
// Добавляем случайные данные для разнообразия
if (Math.random() > 0.7) {
content += `\n### Дополнительная информация\n\n`
content += `ID: ${randomBytes(8).toString('hex')}\n`
content += `Timestamp: ${new Date().toISOString()}\n`
content += `Hash: ${randomBytes(16).toString('hex')}\n\n`
}
}
return content.substring(0, targetLength)
}
/**
* Генерирует расширенное описание проекта
*/
export function generateRandomDescription(): string {
const descriptions = [
'Инновационный проект по разработке современного решения для автоматизации бизнес-процессов',
'Комплексная система управления ресурсами предприятия с использованием передовых технологий',
'Платформа для координации работы распределенных команд и управления проектами',
'Решение для цифровой трансформации традиционных процессов с акцентом на эффективность',
'Система интеграции данных из различных источников с возможностью аналитики в реальном времени',
]
const extras = [
'Включает модули отчетности, мониторинга и аналитики.',
'Поддерживает интеграцию с внешними системами через API.',
'Обеспечивает высокую производительность и масштабируемость.',
'Реализует современные подходы к безопасности данных.',
'Предоставляет интуитивный пользовательский интерфейс.',
]
let result = descriptions[Math.floor(Math.random() * descriptions.length)]
result += ` ${extras[Math.floor(Math.random() * extras.length)]}`
result += ` ${extras[Math.floor(Math.random() * extras.length)]}`
return result
}
/**
* Генерирует метаданные проекта в виде JSON
*/
export function generateRandomMeta(): string {
const meta = {
version: `${Math.floor(Math.random() * 5) + 1}.${Math.floor(Math.random() * 10)}.${Math.floor(Math.random() * 100)}`,
category: ['development', 'infrastructure', 'analytics', 'automation'][Math.floor(Math.random() * 4)],
priority: ['low', 'medium', 'high', 'critical'][Math.floor(Math.random() * 4)],
tags: Array.from({ length: Math.floor(Math.random() * 5) + 3 }, () =>
['backend', 'frontend', 'devops', 'testing', 'documentation', 'security', 'performance'][
Math.floor(Math.random() * 7)
]),
created_by: 'system',
created_at: new Date().toISOString(),
last_modified: new Date().toISOString(),
complexity: Math.floor(Math.random() * 10) + 1,
estimated_duration_days: Math.floor(Math.random() * 180) + 30,
}
return JSON.stringify(meta)
}
/**
* Генерирует описание имущества
*/
export function generateRandomPropertyDescription(): string {
const types = [
'Серверное оборудование',
'Лицензии на программное обеспечение',
'Офисное оборудование',
'Интеллектуальная собственность',
'Доменные имена и хостинг',
'Техническая документация',
'Базы данных',
'Исследовательские материалы',
]
const details = [
'включает все необходимые компоненты и документацию',
'с полным комплектом сопроводительных материалов',
'в отличном техническом состоянии',
'прошедшее полную проверку и тестирование',
'с гарантийным обслуживанием',
'соответствует всем требованиям и стандартам',
]
const type = types[Math.floor(Math.random() * types.length)]
const detail = details[Math.floor(Math.random() * details.length)]
const additional = `ID: ${randomBytes(4).toString('hex').toUpperCase()}`
return `${type} - ${detail}. ${additional}. ${generateRandomDescription().slice(0, 100)}`
}
/**
* Генерирует случайную сумму взноса для контрибьютора
* @param min минимальная сумма (по умолчанию 500)
* @param max максимальная сумма (по умолчанию 5000)
*/
export function generateRandomContributionAmount(min = 500, max = 2000): string {
const amount = Math.floor(Math.random() * (max - min)) + min
return `${amount}.0000 RUB`
}
@@ -1,29 +0,0 @@
import { fakeDocument } from '../tests/shared/fakeDocument'
/**
* Устанавливает подписи документа на основе массива имен аккаунтов
* @param usernames массив имен аккаунтов для подписи документа
* @returns копия fakeDocument с установленными подписями
*/
export function setDocumentSignatures(usernames: string[]) {
const document = JSON.parse(JSON.stringify(fakeDocument))
// Очищаем существующие подписи
document.signatures = []
// Создаем подписи для каждого пользователя
usernames.forEach((username, index) => {
const signature = {
id: index + 1,
signed_hash: '157192b276da23cc84ab078fc8755c051c5f0430bf4802e55718221e6b76c777',
signer: username,
public_key: 'EOS5JhMfxbsNebajHcTEK8yC9uNN9Dit9hEmzE8ri8yMhhzxrLg3J',
signature: 'SIG_K1_KmKWPBC8dZGGDGhbKEoZEzPr3h5crRrR2uLdGRF5DJbeibY1MY1bZ9sPwHsgmPfiGFv9psfoCVsXFh9TekcLuvaeuxRKA8',
signed_at: '2025-05-14T12:22:26',
meta: '{}',
}
document.signatures.push(signature)
})
return document
}
-115
View File
@@ -1,115 +0,0 @@
#!/bin/bash
# Конфигурация
TOTAL_RUNS=100
LOG_FILE="stress-test-$(date +%Y%m%d-%H%M%S).log"
FAILED=false
SUCCESS_COUNT=0
FAILED_COUNT=0
START_TIME=$(date +%s)
# Функция для отображения прогресса
show_progress() {
local current=$1
local total=$2
local success=$3
local failed=$4
local percent=0
if [ $total -gt 0 ]; then
percent=$((current * 100 / total))
fi
local progress_bar=""
local bar_width=50
local filled=0
if [ $total -gt 0 ]; then
filled=$((current * bar_width / total))
fi
for ((j=1; j<=bar_width; j++)); do
if [ $j -le $filled ]; then
progress_bar="${progress_bar}"
else
progress_bar="${progress_bar}"
fi
done
echo "Прогресс: [${progress_bar}] ${percent}% (${current}/${total}) | ✅ ${success} | ❌ ${failed}"
}
echo "════════════════════════════════════════════════════" | tee -a "$LOG_FILE"
echo " СТРЕСС-ТЕСТИРОВАНИЕ CAPITAL CONTRACT" | tee -a "$LOG_FILE"
echo "════════════════════════════════════════════════════" | tee -a "$LOG_FILE"
echo "Запуск: $(date)" | tee -a "$LOG_FILE"
echo "Количество прогонов: $TOTAL_RUNS" | tee -a "$LOG_FILE"
echo "Лог-файл: $LOG_FILE" | tee -a "$LOG_FILE"
echo "════════════════════════════════════════════════════" | tee -a "$LOG_FILE"
echo "" | tee -a "$LOG_FILE"
for ((i=1; i<=TOTAL_RUNS; i++)); do
RUN_START=$(date +%s)
echo "════════════════════════════════════════" | tee -a "$LOG_FILE"
echo "📊 Запуск #$i из $TOTAL_RUNS" | tee -a "$LOG_FILE"
echo "════════════════════════════════════════" | tee -a "$LOG_FILE"
# Запускаем тест и сохраняем вывод
npx vitest run capital.test --run --testTimeout=60000 2>&1 | tee -a "$LOG_FILE"
TEST_EXIT_CODE=${PIPESTATUS[0]}
if [ $TEST_EXIT_CODE -eq 0 ]; then
RUN_END=$(date +%s)
RUN_DURATION=$((RUN_END - RUN_START))
SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
echo "✅ Запуск #$i успешен (время: ${RUN_DURATION}с)" | tee -a "$LOG_FILE"
echo "" | tee -a "$LOG_FILE"
else
RUN_END=$(date +%s)
RUN_DURATION=$((RUN_END - RUN_START))
FAILED_COUNT=$((FAILED_COUNT + 1))
echo "❌ ТЕСТ УПАЛ НА ЗАПУСКЕ #$i (время: ${RUN_DURATION}с)" | tee -a "$LOG_FILE"
echo "⚠️ Проверка на access violation или другие ошибки..." | tee -a "$LOG_FILE"
FAILED=true
break
fi
# Обновляем прогресс после каждого успешного теста
show_progress $((SUCCESS_COUNT + FAILED_COUNT)) $TOTAL_RUNS $SUCCESS_COUNT $FAILED_COUNT
echo "" | tee -a "$LOG_FILE"
done
END_TIME=$(date +%s)
TOTAL_DURATION=$((END_TIME - START_TIME))
AVG_DURATION=$((SUCCESS_COUNT > 0 ? TOTAL_DURATION / SUCCESS_COUNT : 0))
# Финальный показ прогресса
echo "" | tee -a "$LOG_FILE"
show_progress $((SUCCESS_COUNT + FAILED_COUNT)) $TOTAL_RUNS $SUCCESS_COUNT $FAILED_COUNT
echo "" | tee -a "$LOG_FILE"
echo "════════════════════════════════════════════════════" | tee -a "$LOG_FILE"
echo " ИТОГОВАЯ СТАТИСТИКА" | tee -a "$LOG_FILE"
echo "════════════════════════════════════════════════════" | tee -a "$LOG_FILE"
echo "Завершено: $(date)" | tee -a "$LOG_FILE"
echo "Успешных прогонов: $SUCCESS_COUNT" | tee -a "$LOG_FILE"
echo "Проваленных прогонов: $FAILED_COUNT" | tee -a "$LOG_FILE"
echo "Всего прогонов: $((SUCCESS_COUNT + FAILED_COUNT)) из $TOTAL_RUNS" | tee -a "$LOG_FILE"
echo "Общее время: ${TOTAL_DURATION}с ($(($TOTAL_DURATION / 60))м)" | tee -a "$LOG_FILE"
if [ $SUCCESS_COUNT -gt 0 ]; then
echo "Среднее время на прогон: ${AVG_DURATION}с" | tee -a "$LOG_FILE"
fi
if [ "$FAILED" = true ]; then
echo "Статус: ❌ ПРОВАЛЕН" | tee -a "$LOG_FILE"
echo "════════════════════════════════════════════════════" | tee -a "$LOG_FILE"
echo "" | tee -a "$LOG_FILE"
echo "⚠️ Тестирование прервано из-за ошибки на запуске #$i" | tee -a "$LOG_FILE"
echo "📋 Полный лог сохранен в: $LOG_FILE" | tee -a "$LOG_FILE"
exit 1
else
echo "Статус: ✅ УСПЕШНО" | tee -a "$LOG_FILE"
echo "════════════════════════════════════════════════════" | tee -a "$LOG_FILE"
echo "" | tee -a "$LOG_FILE"
echo "🎉 Все $TOTAL_RUNS запусков успешны!" | tee -a "$LOG_FILE"
echo "📋 Полный лог сохранен в: $LOG_FILE" | tee -a "$LOG_FILE"
exit 0
fi
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@coopenomics/cleos",
"version": "2026.2.22-2",
"version": "2025.5.5-2",
"private": true,
"description": "",
"scripts": {
-2
View File
@@ -1,2 +0,0 @@
./cleos.sh system newaccount eosio test "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV" "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV" --stake-net "0.0001 AXON" --stake-cpu "0.0001 AXON" --buy-ram "0.0001 AXON"
-1
View File
@@ -1 +0,0 @@
./cleos.sh push action eosio powerup '[eosio, test, 1, "150.0000 AXON", true]' -p eosio
@@ -1,537 +0,0 @@
---
name: Конструктор программ регистрации
overview: Создание гибкого конструктора программ регистрации с выбором дополнительных соглашений на основе типа аккаунта и кооператива. Для Восхода физики и ИП выбирают между программами "Генерация" и "Капитализация".
todos:
- id: backend-interfaces
content: Создать новые интерфейсы для программ регистрации в agreement-config.interface.ts
status: completed
- id: backend-programs-config
content: Создать конфигурацию программ registration-programs.config.ts с программами для voskhod
status: completed
- id: backend-agreement-service
content: Обновить AgreementConfigurationService с методами работы с программами
status: completed
- id: backend-documents-service
content: Добавить поддержку program_key в RegistrationDocumentsService
status: completed
- id: backend-dto
content: Обновить DTO для добавления program_key и создать RegistrationConfigDTO
status: completed
- id: backend-resolver
content: Добавить query getRegistrationConfig в SystemResolver
status: completed
- id: frontend-store
content: Обновить Registrator store для хранения selectedProgramKey и добавить шаг SelectProgram
status: completed
- id: frontend-select-component
content: Создать компонент SelectProgram.vue для выбора программы
status: completed
- id: frontend-conditional-rendering
content: Добавить условный рендеринг шага SelectProgram в SignUp.vue
status: completed
- id: frontend-pass-program-key
content: Обновить вызов generateRegistrationDocuments для передачи program_key
status: completed
- id: sdk-update
content: Обновить SDK мутацию и добавить query getRegistrationConfig
status: completed
- id: add-generator-agreement
content: Добавить соглашение generator_offer в registration-agreements.config.ts
status: completed
---
# План реализации конструктора программ регистрации
## Архитектура решения
Система будет состоять из трёх уровней:
```mermaid
graph TD
A[Frontend: SelectProgram шаг] --> B[Backend: getRegistrationConfig query]
A --> C[Backend: generateRegistrationDocuments mutation]
B --> D[RegistrationProgramsConfig]
C --> D
D --> E[AgreementConfigurationService]
E --> F[Генерация документов]
```
## 1. Backend: Расширение конфигурации соглашений
### 1.1 Новые интерфейсы конфигурации
**Файл:** [`components/controller/src/domain/registration/config/agreement-config.interface.ts`](components/controller/src/domain/registration/config/agreement-config.interface.ts)
Добавить новые интерфейсы для программ регистрации:
```typescript
/**
* Описание программы регистрации с её соглашениями
*/
export interface IRegistrationProgram {
/** Уникальный ключ программы (например, "generation", "capitalization") */
key: string;
/** Название программы для отображения */
title: string;
/** Описание программы */
description: string;
/** URL изображения (опционально) */
image_url?: string;
/** Минимальные требования для участия */
requirements?: string;
/** Для каких типов аккаунтов доступна эта программа */
applicable_account_types: AccountType[];
/** Список ID соглашений, которые требуются для этой программы */
agreement_ids: string[];
/** Порядок отображения */
order: number;
}
/**
* Конфигурация программ регистрации для кооператива
*/
export interface ICooperativeRegistrationPrograms {
/** Название кооператива */
coopname: string;
/** Доступные программы */
programs: IRegistrationProgram[];
/** Нужен ли выбор программы (если false - используется первая подходящая) */
requires_selection: boolean;
}
```
### 1.2 Создание конфигурации программ
**Файл:** `components/controller/src/domain/registration/config/registration-programs.config.ts` (новый)
```typescript
export const REGISTRATION_PROGRAMS_CONFIG: ICooperativeRegistrationPrograms[] = [
{
coopname: 'voskhod',
requires_selection: true,
programs: [
{
key: 'generation',
title: 'Программа Генерация',
description: 'Участвовать в производстве Кооперативной Экономики через вклад временем, имуществом или деньгами в конкретные проекты. Минимальный взнос 10 часов в месяц.',
applicable_account_types: [AccountType.individual, AccountType.entrepreneur],
agreement_ids: ['generator_offer'], // новое соглашение
order: 1,
},
{
key: 'capitalization',
title: 'Программа Капитализация',
description: 'Участвовать в производстве Кооперативной Экономики через вклад имуществом или денег в систему. Минимальный взнос 100 000 руб в течение 14 дней.',
applicable_account_types: [AccountType.individual, AccountType.entrepreneur],
agreement_ids: ['blagorost_offer'],
order: 2,
},
],
},
];
```
### 1.3 Добавление нового соглашения generator_offer
**Файл:** [`components/controller/src/domain/registration/config/registration-agreements.config.ts`](components/controller/src/domain/registration/config/registration-agreements.config.ts)
Добавить новое соглашение:
```typescript
{
id: 'generator_offer',
registry_id: Cooperative.Registry.GeneratorOffer.registry_id, // 996
agreement_type: 'generator',
title: 'Оферта по целевой потребительской программе "Генератор"',
checkbox_text: 'Я прочитал и принимаю',
link_text: 'оферту по целевой потребительской программе "Генератор"',
is_blockchain_agreement: true,
link_to_statement: true,
applicable_account_types: [], // не используется напрямую, только через программы
order: 6,
}
```
### 1.4 Обновление AgreementConfigurationService
**Файл:** [`components/controller/src/domain/registration/services/agreement-configuration.service.ts`](components/controller/src/domain/registration/services/agreement-configuration.service.ts)
Добавить методы для работы с программами:
```typescript
/**
* Получить доступные программы регистрации для кооператива и типа аккаунта
*/
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);
}
/**
* Получить конфигурацию программ для кооператива
*/
getCooperativeProgramsConfig(coopname: string): ICooperativeRegistrationPrograms | null {
return REGISTRATION_PROGRAMS_CONFIG.find(c => c.coopname === coopname) || null;
}
/**
* Получить соглашения для программы
*/
getAgreementsForProgram(programKey: string, 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);
}
```
Обновить метод `getAgreementsForAccountType`:
```typescript
getAgreementsForAccountType(
accountType: AccountType,
coopname?: string,
programKey?: string
): IAgreementConfigItem[] {
// Базовые соглашения (всегда включаются)
const baseAgreements = REGISTRATION_AGREEMENTS_CONFIG.agreements
.filter(a => a.applicable_account_types.includes(accountType))
.filter(a => a.id !== 'blagorost_offer' && a.id !== 'generator_offer');
// Если указана программа - добавляем её соглашения
if (programKey && coopname) {
const programAgreements = this.getAgreementsForProgram(programKey, coopname);
return [...baseAgreements, ...programAgreements].sort((a, b) => a.order - b.order);
}
// Старая логика для обратной совместимости (voskhod без program_key)
if (coopname === 'voskhod' && accountType === AccountType.individual) {
const capitalization = this.getAgreementById('blagorost_offer');
if (capitalization) {
return [...baseAgreements, capitalization].sort((a, b) => a.order - b.order);
}
}
return baseAgreements.sort((a, b) => a.order - b.order);
}
```
### 1.5 Обновление RegistrationDocumentsService
**Файл:** [`components/controller/src/domain/registration/services/registration-documents.service.ts`](components/controller/src/domain/registration/services/registration-documents.service.ts)
Добавить `program_key` в интерфейс:
```typescript
// В интерфейсе IGenerateRegistrationDocumentsInput
program_key?: string;
```
Обновить метод `generateRegistrationDocuments`:
```typescript
const agreementsConfig = this.agreementConfigService.getAgreementsForAccountType(
account_type,
coopname,
input.program_key
);
```
### 1.6 Обновление DTO
**Файл:** [`components/controller/src/application/user/dto/generate-registration-documents-input.dto.ts`](components/controller/src/application/user/dto/generate-registration-documents-input.dto.ts)
```typescript
@Field({ nullable: true, description: 'Ключ выбранной программы регистрации (опционально)' })
@IsString()
@IsOptional()
program_key?: string;
```
### 1.7 Новый Query для получения конфигурации
**Файл:** [`components/controller/src/application/system/resolvers/system.resolver.ts`](components/controller/src/application/system/resolvers/system.resolver.ts)
Добавить новый query:
```typescript
@Query(() => RegistrationConfigDTO, {
name: 'getRegistrationConfig',
description: 'Получить конфигурацию программ регистрации для кооператива',
})
async getRegistrationConfig(
@Args('coopname') coopname: string,
@Args('account_type', { type: () => AccountType }) accountType: AccountType,
): Promise<RegistrationConfigDTO> {
return this.systemService.getRegistrationConfig(coopname, accountType);
}
```
**Файл:** `components/controller/src/application/system/dto/registration-config.dto.ts` (новый)
```typescript
@ObjectType('RegistrationProgram')
export class RegistrationProgramDTO {
@Field() key!: string;
@Field() title!: string;
@Field() description!: string;
@Field({ nullable: true }) image_url?: string;
@Field({ nullable: true }) requirements?: string;
@Field(() => [AccountType]) applicable_account_types!: AccountType[];
@Field(() => Int) order!: number;
}
@ObjectType('RegistrationConfig')
export class RegistrationConfigDTO {
@Field() requires_selection!: boolean;
@Field(() => [RegistrationProgramDTO]) programs!: RegistrationProgramDTO[];
}
```
**Файл:** [`components/controller/src/application/system/services/system.service.ts`](components/controller/src/application/system/services/system.service.ts)
```typescript
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,
});
}
```
## 2. Frontend: Новый шаг выбора программы
### 2.1 Обновление Registrator Store
**Файл:** [`components/desktop/src/entities/Registrator/model/store.ts`](components/desktop/src/entities/Registrator/model/store.ts)
Добавить в state:
```typescript
const state = reactive({
// ... существующие поля
selectedProgramKey: '', // выбранный ключ программы
});
```
Добавить шаг `SelectProgram` в массив stepNames (между `SetUserData` и `GenerateAccount`).
### 2.2 Создание компонента SelectProgram
**Файл:** `components/desktop/src/pages/Registrator/SignUp/SelectProgram.vue` (новый)
```vue
<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
Loader(text='Загружаем доступные программы...')
div(v-else-if='programs.length > 0')
p.text-body1 Выберите программу, в которой вы хотите участвовать:
q-list.q-mt-md
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 {{ program.description }}
q-item-label(v-if='program.requirements', caption).q-mt-xs.text-weight-bold
| {{ program.requirements }}
q-item-section(side)
q-radio(
:model-value='registratorStore.state.selectedProgramKey',
:val='program.key',
@update:model-value='selectProgram(program.key)'
)
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 } 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';
// TODO: добавить API метод для получения конфигурации
const registratorStore = useRegistratorStore();
const systemStore = useSystemStore();
const isLoading = ref(false);
const programs = ref<any[]>([]);
const loadPrograms = async () => {
try {
isLoading.value = true;
// TODO: вызов API getRegistrationConfig
const config = await getRegistrationConfig(
systemStore.info.coopname,
registratorStore.state.userData.type
);
programs.value = config.programs;
} catch (e: any) {
FailAlert(e);
} finally {
isLoading.value = false;
}
};
const selectProgram = (key: string) => {
registratorStore.state.selectedProgramKey = key;
};
onMounted(() => {
if (registratorStore.state.step === registratorStore.steps.SelectProgram) {
loadPrograms();
}
});
</script>
```
### 2.3 Условный рендеринг шага
**Файл:** [`components/desktop/src/pages/Registrator/SignUp/SignUp.vue`](components/desktop/src/pages/Registrator/SignUp/SignUp.vue)
Добавить условный рендеринг:
```pug
SelectProgram(v-if='shouldShowProgramSelection')
```
Добавить computed для определения необходимости показа:
```typescript
const shouldShowProgramSelection = computed(() => {
// Показываем только если есть конфигурация программ для данного кооператива
// и тип аккаунта выбран
return (
registratorStore.state.userData.type &&
// TODO: проверка наличия программ из конфигурации
);
});
```
### 2.4 Обновление вызова generateRegistrationDocuments
**Файл:** `components/desktop/src/features/User/CreateUser` (или где происходит вызов)
Передавать `program_key` при генерации документов:
```typescript
await generateAllRegistrationDocuments(
coopname,
username,
accountType,
registratorStore.state.selectedProgramKey // новый параметр
);
```
## 3. SDK: Обновление мутации
**Файл:** [`components/sdk/src/mutations/registration/generateRegistrationDocuments.ts`](components/sdk/src/mutations/registration/generateRegistrationDocuments.ts) (если существует, иначе найти где определена)
Добавить опциональное поле `program_key` в input мутации.
**Файл:** Где определен query `getRegistrationConfig` (создать новый)
```typescript
export const getRegistrationConfig = {
name: 'getRegistrationConfig',
query: (coopname: string, account_type: string) => ({
getRegistrationConfig: [
{ coopname, account_type },
{
requires_selection: true,
programs: {
key: true,
title: true,
description: true,
image_url: true,
requirements: true,
order: true,
},
},
],
}),
};
```
## 4. Обновление логики перехода между шагами
В [`components/desktop/src/pages/Registrator/SignUp/SetUserData.vue`](components/desktop/src/pages/Registrator/SignUp/SetUserData.vue) при клике "Продолжить" нужно:
1. Проверить, нужен ли шаг SelectProgram
2. Если да - перейти на него
3. Если нет - перейти на GenerateAccount (как сейчас)
## Итоговая последовательность шагов регистрации
1. **EmailInput** - ввод email
2. **SetUserData** - выбор типа аккаунта и заполнение данных
3. **SelectProgram** *(условный)* - выбор программы (только для voskhod, физики/ИП)
4. **GenerateAccount** - генерация аккаунта
5. **SelectBranch** *(условный)* - выбор филиала
6. **ReadStatement** - чтение и принятие соглашений (уже учитывают program_key)
7. **SignStatement** - подпись
8. **PayInitial** - оплата
9. **WaitingRegistration** - ожидание
## Миграция и обратная совместимость
- Для всех кооперативов кроме voskhod ничего не меняется
- Для voskhod: если не передан `program_key`, используется старая логика (blagorost_offer для физиков)
- После развертывания можно убрать хардкод для voskhod из `AgreementConfigurationService`
@@ -1,11 +0,0 @@
---
description: Общие правила
alwaysApply: true
---
Мы работаем в МОНО-репозитории pnpm, любая установка пакетов производится ЧЕРЕЗ ФИЛЬТР компонента в корне: pnpm add glob --filter <package>.
НИКОГДА НЕ ЗАПУСКАЙ ПРИЛОЖЕНИЕ ИЛИ ЕГО СБОРКУ!!! Просто отчитайся что всё сделал.
НИКОГДА НЕ ЗАПУСКАЙ ТЕСТЫ ИЛИ ПРОВЕРКУ ТИПОВ ИЛИ ЛИНТЕР!!!
Никаких ANY типов! Строгая типизация всегда.
@@ -1,247 +0,0 @@
---
description: Правила работы с пагинацией в контроллере
alwaysApply: false
---
# Правила работы с пагинацией в Controller
## Общие принципы
**Всегда используйте доменные интерфейсы пагинации** вместо примитивных типов (`skip`, `take`, `limit`, `offset`).
Расчет общего количества записей **всегда** производится через `count()` в репозитории.
## Архитектурные слои и интерфейсы
### 1. DTO слой (GraphQL)
```typescript
// Входные параметры от клиента
PaginationInputDTO {
page: number; // Номер страницы (начиная с 1)
limit: number; // Количество элементов на странице
sortBy?: string; // Поле сортировки
sortOrder: 'ASC' | 'DESC'; // Направление сортировки
}
// Результат для клиента
PaginationResult<T> {
items: T[]; // Элементы текущей страницы
totalCount: number; // Общее количество элементов
totalPages: number; // Общее количество страниц
currentPage: number; // Текущая страница
}
```
### 2. Домен слой
```typescript
// Входные параметры для домена
PaginationInputDomainInterface {
page: number;
limit: number;
sortBy?: string;
sortOrder: 'ASC' | 'DESC';
}
// Результат из домена
PaginationResultDomainInterface<T> {
items: T[];
totalCount: number;
totalPages: number;
currentPage: number;
}
```
## Поток данных
### В Resolver (GraphQL слой)
```typescript
// Создание GraphQL типа для пагинированного результата
const paginatedEntitiesResult = createPaginationResult(EntityDTO, 'PaginatedEntities');
@Query(() => paginatedEntitiesResult)
async getEntities(
@Args('filter') filter?: FilterDTO,
@Args('options') options?: PaginationInputDTO
): Promise<PaginationResult<EntityDTO>> {
// Преобразование в доменный интерфейс
const domainOptions: PaginationInputDomainInterface | undefined = options
? {
page: options.page,
limit: options.limit,
sortBy: options.sortBy,
sortOrder: options.sortOrder,
}
: undefined;
// Вызов сервиса
const result = await this.service.getEntities(filter, domainOptions);
// DTO готовится в сервисе, резолвер проксирует результат
return result;
}
```
### В Service (Прикладной слой)
```typescript
async getEntities(
filter?: FilterDTO,
options?: PaginationInputDomainInterface
): Promise<PaginationResult<EntityDTO>> {
// Получение результата из домена
const domainResult = await this.interactor.getEntities(filter, options);
// Преобразование доменных сущностей в DTO
const items = domainResult.items.map(item => this.mapToDTO(item));
// Возврат полного пагинированного результата с DTO
return {
items,
totalCount: domainResult.totalCount,
totalPages: domainResult.totalPages,
currentPage: domainResult.currentPage,
};
}
```
### В Interactor/UseCase (Домен слой)
```typescript
async getEntities(
filter?: DomainFilter,
options?: PaginationInputDomainInterface
): Promise<PaginationResultDomainInterface<DomainEntity>> {
// Вызов репозитория
return await this.repository.findAllPaginated(filter, options);
}
```
### В Repository (Инфраструктурный слой)
```typescript
async findAllPaginated(
filter?: Filter,
options?: PaginationInputDomainInterface
): Promise<PaginationResultDomainInterface<Entity>> {
// 1. Валидация параметров
const validatedOptions = options
? PaginationUtils.validatePaginationOptions(options)
: { page: 1, limit: 10, sortOrder: 'ASC' };
// 2. Получение SQL параметров
const { limit, offset } = PaginationUtils.getSqlPaginationParams(validatedOptions);
// 3. Подсчет общего количества (ОБЯЗАТЕЛЬНО!)
const totalCount = await this.repository.count({ where });
// 4. Получение данных с пагинацией
const entities = await this.repository.find({
where,
skip: offset,
take: limit,
order: orderBy,
});
// 5. Преобразование в доменные сущности
const items = entities.map(entity => this.mapper.toDomain(entity));
// 6. Создание результата через утилиту
return PaginationUtils.createPaginationResult(items, totalCount, validatedOptions);
}
```
## GraphQL типы пагинации
Для создания GraphQL типов пагинированных результатов используйте `createPaginationResult`:
```typescript
import { createPaginationResult } from '~/application/common/dto/pagination.dto';
// Создание GraphQL типа для пагинированного результата
const paginatedEntitiesResult = createPaginationResult(EntityDTO, 'PaginatedEntities');
// Использование в резолвере
@Query(() => paginatedEntitiesResult)
async getEntities(...) : Promise<PaginationResult<EntityDTO>>
```
Функция `createPaginationResult` создает GraphQL DTO тип для пагинированного ответа, но не готовит сами данные.
## Утилиты пагинации
Используйте `PaginationUtils` для стандартных операций:
```typescript
import { PaginationUtils } from '~/shared/utils/pagination.utils';
// Валидация параметров
const validatedOptions = PaginationUtils.validatePaginationOptions(options);
// Получение SQL параметров (limit, offset)
const { limit, offset } = PaginationUtils.getSqlPaginationParams(options);
// Создание результата пагинации
const result = PaginationUtils.createPaginationResult(items, totalCount, options);
```
## Валидация параметров
- `page >= 1` - номер страницы должен быть положительным
- `1 <= limit <= 1000` - ограничение на количество элементов
- `sortOrder` должен быть `'ASC'` или `'DESC'`
## Запрещенные паттерны
❌ **НЕ ПРАВИЛЬНО:**
```typescript
// Не используйте примитивные типы
async getApprovals(filter?: Filter, skip = 0, take = 50)
// Не рассчитывайте пагинацию вручную
const totalPages = Math.ceil(totalCount / limit); // Делайте это в утилите
// Не возвращайте сырые данные без пагинации
return await this.repository.findAll(); // Всегда используйте пагинацию
```
✅ **ПРАВИЛЬНО:**
```typescript
// Сервис возвращает полный пагинированный результат с DTO
async getApprovals(
filter?: ApprovalFilterInput,
options?: PaginationInputDomainInterface
): Promise<PaginationResult<ApprovalDTO>> {
// Получение доменного результата
const domainResult = await this.repository.findAllPaginated(filter, options);
// Преобразование в DTO
const items = domainResult.items.map(item => this.toDTO(item));
// Возврат полного результата
return {
items,
totalCount: domainResult.totalCount,
totalPages: domainResult.totalPages,
currentPage: domainResult.currentPage,
};
}
// Резолвер проксирует результат от сервиса
@Query(() => paginatedApprovalsResult)
async getApprovals(filter, options): Promise<PaginationResult<ApprovalDTO>> {
return await this.service.getApprovals(filter, domainOptions);
}
```
## Ключевые моменты
1. **DTO готовится в Service** - сервис получает доменные сущности и преобразует их в DTO перед возвратом
2. **GraphQL типы создаются через createPaginationResult** - это создает схему GraphQL, а не данные
3. **Резолвер проксирует результат** - резолвер просто вызывает сервис и возвращает результат
4. **Полный пагинированный результат** - всегда возвращается PaginationResult<T> с items, totalCount, totalPages, currentPage
## Примеры из кода
- ✅ `ApprovalService` - правильная реализация с полным PaginationResult
- ✅ `InvestsManagementService` - прямое преобразование Domain → DTO в сервисе
- ✅ `TimeTrackingService` - явное преобразование полей в DTO
- ✅ `InvestTypeormRepository` - полная реализация с валидацией через PaginationUtils
@@ -1,53 +0,0 @@
---
description: О правилах на бэкенде
globs: **/controller/**/*.ts
---
# NestJS Controller и Чистая архитектура
## Основные принципы
1. Чистая архитектура - главный архитектурный подход. Пиши код с комментариями.
2. Типы IName, IChecksum256, ITimePointSec - это просто строки.
3. Домен должен быть полностью изолирован от инфраструктурных деталей.
4. Направление зависимостей - внутрь (к ядру, домену).
5. Домен, инфраструктура и приложение связаны через App.module, НЕ НУЖНО импортировать их друг в друга, а достаточно просто импортировать домен на уровне приложения чтобы использовать все экспорты домена.
## Структура проекта
- `domain/` - доменный слой (бизнес-логика, независимая от инфраструктуры)
- `infrastructure/` - инфраструктурный слой (адаптеры к внешним системам)
- `modules/` - слой приложения (DTO, резолверы, сервисы)
## Слои и их взаимодействие
1. **Домен**:
- Содержит чистые доменные интерфейсы и реализованные на них сущности
- НЕ должен зависеть от `cooptypes` (инфраструктурный контракт)
- Использует собственные доменные типы (например, Date вместо строковых timestamp)
- Интерфейсы портов описывают взаимодействие с внешними системами
2. **Инфраструктура**:
- Содержит адаптеры к внешним системам (блокчейн, БД и т.д.)
- Осуществляет преобразование между доменными и инфраструктурными типами
- Общая логика преобразования выносится в утилитарные классы (например, `DomainToBlockchainUtils`)
3. **Модули (приложение)**:
- DTO имплементируют доменные интерфейсы напрямую
- Резолвер вызывает сервис, который принимает DTO
- Сервис передает объекты DTO в интерактор приложения (юз-кейс)
## Правила
Чистая фрактальная трехуровневая архитектура срезов домена (domain) и инфраструктуры (infrastructure), оркестрируемая приложением (application). Фрактал раскрывается в расширениях (extensions), каждое из которых также выстраивается по правилам чистой трехуровневой архитектуры срезов домена и инфраструктуры с окрестрацией на уровне своего приложения.
Общая архитектура связей:
- AppResolver -> AppService[DTO<->Domain] -> AppInteractor -> DomainService -> DomainPort <- InfraAdapter -> [при необходимости] AnyAppOrDomainService
Все сервисы внедряются через инъекцию (DI) с указанием символа реализации. Импорты разрешены только вглубь - к домену. Из домена переход разрешен только в инфраструктуру через порт и его адаптер.
Прямые импорты без DI запрещены. Импорты из соседних срезов того же архитектурного уровня запрещены. Направление зависимостей от приложения - к домену, от домена через порт в его инфраструктурный адаптер.
Взаимодействие расширений с основным приложением осуществляется только через порты домена основного приложения.
Глобальные модули запрещены. В редких случаях сложных циклических зависимостей на период рефакторинга разрешается обоснованно использовать forwardRef.
Уровень приложения оркестрирует вызовы доменов через порты в инфраструктуру. Домены не обращаются друг к друга. Срезы уровня приложений не обращаются друг к другу, а только вызывают порты домена.
```
@@ -1,174 +0,0 @@
---
description: SDK мутации и запросы для подключения на рабочем столе
globs:
alwaysApply: false
---
Бэкенд реализован здесь components/controller, sdk здесь components/sdk.
Пример создания Query к API через SDK:
``` ts
async function loadBranches(data: IGetBranchesInput): Promise<IBranch[]> {
const { [Queries.Branches.GetBranches.name]: output } = await client.Query(
Queries.Branches.GetBranches.query,
{
variables: {
data
}
}
);
return output;
}
```
Такие запросы обычно хранятся в папке Entities и вызываются из его store.
Пример мутации:
``` ts
async function selectBranch(data: ISelectBranchInput): Promise<boolean>{
const {[Mutations.Branches.SelectBranch.name]: result} = await client.Mutation(Mutations.Branches.SelectBranch.mutation, {variables: {
data
}})
return result
}
```
Мутации обычно храним в папке features.
Как можешь обратить внимание, все мутации и запросы строятся по одному шаблону. Вся необходимая информация уже есть в SDK.
## Генерация и подпись документов
### Генерация документов
Для генерации документов используются мутации SDK типа `Generate[DocumentName]`. Например:
```ts
import { client } from 'src/shared/api/client';
import { Mutations } from '@coopenomics/sdk';
async function generateDocument(data: IGenerateDocumentInput): Promise<IGeneratedDocumentOutput> {
const { [Mutations.Capital.GenerateGenerationContract.name]: result } =
await client.Mutation(Mutations.Capital.GenerateGenerationContract.mutation, {
variables: {
data: {
coopname: 'coopname',
username: 'username',
lang: 'ru'
},
options
}
});
return result;
}
```
**Composable для генерации документов:**
```ts
import { ref } from 'vue';
import { api } from '../api';
import { useSystemStore } from 'src/entities/System/model';
import { useSessionStore } from 'src/entities/Session';
export function useGenerateDocument() {
const system = useSystemStore();
const session = useSessionStore();
const isGenerating = ref(false);
const generatedDocument = ref<IGeneratedDocumentOutput | null>(null);
const generate = async () => {
isGenerating.value = true;
try {
const data = {
coopname: system.info.coopname,
username: session.username,
lang: 'ru',
};
generatedDocument.value = await api.generateDocument(data);
return generatedDocument.value;
} finally {
isGenerating.value = false;
}
};
return {
generate,
isGenerating,
generatedDocument,
};
}
```
### Подпись документов
Для подписи документов используется класс `DigitalDocument`:
```ts
import { DigitalDocument } from 'src/shared/lib/document';
import { useSessionStore } from 'src/entities/Session';
const session = useSessionStore();
const digitalDocument = new DigitalDocument(generatedDocument);
// Подпись документа
const signedDoc = await digitalDocument.sign(session.username);
```
### Стандартный путь: генерация + подпись
**Стандартный путь (без демонстрации):**
```ts
// 1. Генерируем документ
const document = await generateDocument({
coopname: system.info.coopname,
username: session.username,
lang: 'ru'
});
// 2. Подписываем документ
const digitalDocument = new DigitalDocument(document);
const signedDoc = await digitalDocument.sign(session.username);
// 3. Отправляем подписанный документ на бэкенд
await submitDocumentMutation(signedDoc);
```
### Демонстрация документов (опционально)
Демонстрация документов не всегда требуется. Если нужно показать документ пользователю перед подписью:
```vue
<template>
<DocumentHtmlReader v-if="generatedDocument" :html="generatedDocument.html" />
<q-btn @click="signAndSubmit" label="Подписать документ" />
</template>
<script setup>
const { generate, isGenerating, generatedDocument } = useGenerateDocument();
// Генерация при монтировании
onMounted(async () => {
await generate();
});
// Подпись и отправка
const signAndSubmit = async () => {
const digitalDocument = new DigitalDocument(generatedDocument.value);
const signedDoc = await digitalDocument.sign(session.username);
await submitDocumentMutation(signedDoc);
};
</script>
```
**Важно:**
- Демонстрация документов опциональна
- Стандартный путь: генерация → подпись → отправка
- Используй `DocumentHtmlReader` для отображения HTML документов
- Все документы подписываются через `DigitalDocument.sign()`
- Данные для генерации обычно включают `coopname`, `username`, `lang`
@@ -1,16 +0,0 @@
---
description: О правилах на фронтенде
globs: **/desktop/**
alwaysApply: false
---
# DESKTOP
- Пишем код фронтенда в архитектуре Feature Sliced Design (FSD).
- Стиль шаблонов PUG на composite API.
- Всегда создаём index.ts файлы для vue компонент.
- Создавай индексные файлы для vue через export {default as NAME} from './where', а на уровне выше, если это необходимо, делай export * from './ui'
- Всегда удаляем неиспользуемые импорты.
- Не используем emit избыточно. Вместо них используем композабл функции моделей фич, если необходимо. Emit только в крайнем случае.
- Архитектура приложения состоит из папки src, которая формирует core приложение по FSD, и папки extensions, в каждой папке которого есть подобная FSD структура. Т.е. в src есть features, widgets, entities, shared для core приложения, и в extensions/<name>/ - тоже есть features, widgets, entities, shared для конкретного расширения. По-возможности делаем их изолированными, т.е. импорты между src и extensions ЗАПРЕЩЕНЫ. Исключением являются FailAlert и SuccessAlert, которые допустимо импортировать.
- Не создаём не используемые переменные!
-61
View File
@@ -1,61 +0,0 @@
---
description: Правила работы с макросом main.py в компоненте docs
alwaysApply: false
---
Файл [main.py](mdc:monocoop/monocoop/monocoop/components/docs/main.py) автоматически генерит ссылки на документацию SDK и GraphQL, которые формируются и публикуются автоматически. При создании документации к методам всегда применяй ссылки на SDK и GraphQL по форме:
{{ get_sdk_doc("Mutations", "Accounts", "RegisterAccount") }} | {{ get_graphql_doc("Mutation.registerAccount") }}
### 🎯 Основные паттерны использования
**1. Стандартная структура для методов API:**
```markdown
## Название действия
{{ get_sdk_doc("Namespace", "Module", "Method") }} | {{ get_graphql_doc("Type.methodName") }}
{{ get_typedoc_input("Namespace.Module.Method") }}
Результат:
{{ get_typedoc_definition("Namespace.Module.Method", "IOutput") }}
```
**2. Описание + пример (для сложных методов):**
```markdown
{{ get_typedoc_desc("Namespace.Module.Method") }}
{{ get_typedoc_input("Namespace.Module.Method") }}
```
**3. Ссылки на типы данных:**
```markdown
У каждого аккаунта есть объект {{ get_graphql_definition("Account") }} в GraphQL-API
```
### 🔧 Макросы и их применение
| Макрос | Назначение | Пример использования |
|--------|------------|---------------------|
| `get_sdk_doc` | Ссылка на SDK документацию | `{{ get_sdk_doc("Mutations", "Payments", "CreateDeposit") }}` |
| `get_graphql_doc` | Ссылка на GraphQL операцию | `{{ get_graphql_doc("Query.getAccount") }}` |
| `get_graphql_definition` | Ссылка на GraphQL тип | `{{ get_graphql_definition("Account") }}` |
| `get_class_doc` | Ссылка на класс/метод SDK | `{{ get_class_doc("Document", "sign") }}` |
| `get_typedoc_input` | **Полный пример** TypeScript вызова | `{{ get_typedoc_input("Mutations.Auth.Login") }}` |
| `get_typedoc_definition` | **Структура интерфейса** | `{{ get_typedoc_definition("Mutations.Auth.Login", "IOutput") }}` |
| `get_typedoc_desc` | Описание + примеры | `{{ get_typedoc_desc("Mutations.Auth.Login") }}` |
| `get_typedoc_value` | Значение константы | `{{ get_typedoc_value("Constants.API_VERSION") }}` |
### 📝 Правила оформления
**ОБЯЗАТЕЛЬНО:**
- Всегда используй `get_typedoc_input` для демонстрации **КАК вызывать** метод
- Всегда используй `get_typedoc_definition` для показа **ЧТО возвращается**
- Комбинируй SDK и GraphQL ссылки через ` | `: `{{ get_sdk_doc(...) }} | {{ get_graphql_doc(...) }}`
**ЖЕЛАТЕЛЬНО:**
- Для сложных методов добавляй `get_typedoc_desc` в начало
- Используй `get_graphql_definition` для ссылок на типы данных в тексте
- Группируй связанные операции в одном разделе
**ФОРМАТ ССЫЛОК:**
- SDK: `"Namespace", "Module", "Method"` (3 аргумента)
- GraphQL: `"Type.methodName"` (точка между типом и методом)
- Определения: просто имя типа `"TypeName"`

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