Compare commits
144 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ccf2c743c | |||
| 418d43af58 | |||
| 869a75f080 | |||
| c7bc2d1a20 | |||
| 3ea4cd6d73 | |||
| 6a955737e0 | |||
| 51bf49b063 | |||
| 12468b16c8 | |||
| 5ebd28d181 | |||
| defe482ccd | |||
| e99d141f28 | |||
| aa0a6731de | |||
| 46f7f10dce | |||
| 552333faf6 | |||
| e23589d4d3 | |||
| 104b08e1ba | |||
| 1b81cfc0a9 | |||
| 6e51433ae9 | |||
| 421f356987 | |||
| abd9f1931d | |||
| 2b87034abb | |||
| 8d7c919332 | |||
| 4785ea2886 | |||
| 4daa06dac1 | |||
| abc253f47d | |||
| 5fea2f8f34 | |||
| c29e224f2f | |||
| b930543c2b | |||
| 2768d72e90 | |||
| 8ccefe8fac | |||
| c93e59908d | |||
| 22f0fd8a10 | |||
| 8e17d96529 | |||
| 494a164ee8 | |||
| ee28ae1719 | |||
| 3266061359 | |||
| f69d849c4f | |||
| 2628120e35 | |||
| edf7d9ab62 | |||
| 2095e448e5 | |||
| beff507f4a | |||
| bf4fbfabac | |||
| 2f1d88a74c | |||
| b444004f4a | |||
| 6862f2881a | |||
| 73afcabee2 | |||
| b4168a4053 | |||
| 1fe14d5811 | |||
| 89e5c97ef8 | |||
| 258b8304ff | |||
| 69fe9c1348 | |||
| fa4af94fa0 | |||
| 2ac84a6910 | |||
| 3f79cf9a1b | |||
| c155605ca8 | |||
| 80a94f79da | |||
| 89ba6cb356 | |||
| c7bd0326ba | |||
| 2c551a0d9e | |||
| ba103a969e | |||
| 0d9177f98f | |||
| 60c32be299 | |||
| 83c107b8b2 | |||
| b6d582fb2c | |||
| a5818bdc53 | |||
| de0fe5465f | |||
| d43b188a30 | |||
| bfb2be2a72 | |||
| 73c2cd9cb3 | |||
| 61cfc442ef | |||
| 0ed04c2c27 | |||
| 6773509ede | |||
| acd86df785 | |||
| c554b167b3 | |||
| 18cf7b61e3 | |||
| e42b396a68 | |||
| c47e61d42a | |||
| 92559370fa | |||
| c3a2ec46b3 | |||
| fe55d23e65 | |||
| b0d4f5d542 | |||
| be48f7a158 | |||
| 83bd744100 | |||
| ff1615f7b1 | |||
| 696e1a0d2c | |||
| 397670473a | |||
| cab8061bc3 | |||
| 3c2b9bcb4c | |||
| 7a274b7ab1 | |||
| e524244bab | |||
| 14ecaf259a | |||
| 749c13dc91 | |||
| ab2ee4cc92 | |||
| 2907352d61 | |||
| 390b995eb6 | |||
| 24b3dd3c60 | |||
| 55444a4b72 | |||
| 76854207a7 | |||
| 4fcd700e39 | |||
| 05dfcced1b | |||
| 931c366907 | |||
| 71d35b74d7 | |||
| 21fac5c7df | |||
| 0ba9d55081 | |||
| b5b388e95d | |||
| 9c643395b3 | |||
| b989074170 | |||
| 261f010a49 | |||
| af46867939 | |||
| be2355e5c5 | |||
| 8b4a20ed3f | |||
| 1ab1e1d3c5 | |||
| c9aab02aab | |||
| 0643604778 | |||
| fe79f3ff98 | |||
| f919a14927 | |||
| dda389ad75 | |||
| aa42864029 | |||
| 41bb17224a | |||
| fd6f628c43 | |||
| 28ca615d96 | |||
| 76203a88d8 | |||
| aa891adddf | |||
| ecdac6fff9 | |||
| bcba24ea5e | |||
| 44b1f423cc | |||
| 85cc7361d7 | |||
| e5e28ff652 | |||
| 4957182480 | |||
| 84927e943a | |||
| 0d06114f00 | |||
| 2654760e16 | |||
| d02e7ba2be | |||
| e18fd94336 | |||
| 03e2410556 | |||
| 0f1f34f621 | |||
| c08473618f | |||
| 3b4fb195bb | |||
| 827eb10cc2 | |||
| 6e7c60de6e | |||
| 54a4449e0b | |||
| a6011f5d46 | |||
| a1300e2b3c | |||
| 8f58672403 |
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"agentCanUpdateSnapshot": true
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
ВАЖНО!!! Мы работаем в МОНО-репозитории pnpm, любая установка пакетов производится ЧЕРЕЗ ФИЛЬТР компонента в корне: pnpm add glob --filter notificator2.
|
||||
|
||||
ВАЖНО!!! НИКОГДА НЕ ЗАПУСКАЙ ПРИЛОЖЕНИЕ ИЛИ ЕГО СБОРКУ!!! Просто отчитайся что всё сделал.
|
||||
@@ -10,7 +10,6 @@ alwaysApply: false
|
||||
2. Типы IName, IChecksum256, ITimePointSec - это просто строки.
|
||||
3. Домен должен быть полностью изолирован от инфраструктурных деталей.
|
||||
4. Направление зависимостей - внутрь (к ядру, домену).
|
||||
5. Домен, инфраструктура и приложение связаны через App.module, НЕ НУЖНО импортировать их друг в друга, а достаточно просто импортировать домен на уровне приложения чтобы использовать все экспорты домена.
|
||||
|
||||
## Структура проекта
|
||||
- `domain/` - доменный слой (бизнес-логика, независимая от инфраструктуры)
|
||||
|
||||
@@ -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 только в крайнем случае.
|
||||
@@ -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"`
|
||||
@@ -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' %}`
|
||||
@@ -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
|
||||
- Собрания с вопросами и решениями
|
||||
|
||||
Фабрика поддерживает полный цикл создания документов кооператива от заявлений до протоколов собраний с возможностью кастомизации под разные типы кооперативов и требования.
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
globs: notifications/src/workflows/**/*.ts
|
||||
---
|
||||
# Правила валидации воркфлоу
|
||||
|
||||
## 🚫 ID воркфлоу
|
||||
- **Длина ID не должна превышать 32 символа**
|
||||
- Используйте описательные, но короткие идентификаторы малыми латинскими буквами и тире.
|
||||
|
||||
## 🚫 Условия в шаблонах
|
||||
- **Запрещено использовать JavaScript выражения в шаблонах Novu**
|
||||
- Нельзя использовать:
|
||||
- Тернарные операторы: `{{condition ? "text1" : "text2"}}`
|
||||
- Логические операторы: `{{field && "text"}}`
|
||||
- Любые другие JS конструкции
|
||||
|
||||
## ✅ Рекомендации
|
||||
- Добавляйте текстовые поля в payload для условной логики
|
||||
- Вычисляйте значения на стороне сервера перед отправкой уведомления
|
||||
- Используйте только простые переменные: `{{payload.fieldName}}`
|
||||
@@ -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 }}"}'
|
||||
@@ -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 }}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 120,
|
||||
"plugins": [
|
||||
"prettier-plugin-sort-imports"
|
||||
]
|
||||
}
|
||||
Vendored
-7
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"vue.volar",
|
||||
"vue.vscode-typescript-vue-plugin"
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+5
-63
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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,4 +1,4 @@
|
||||
# Цифровой Кооператив
|
||||
# MONO
|
||||
|
||||
Система управления взаимоотношениями в кооперативе. Включает в себя полный комплект программного обеспечения для подключения к платформе "Кооперативная Экономика" и управления взаимоотношениями с пайщиками в кооперативе и самим кооперативом на основе смарт-контрактов и простой электронной подписи.
|
||||
|
||||
|
||||
-129
@@ -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 типы
|
||||
Vendored
BIN
Binary file not shown.
@@ -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
@@ -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
@@ -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"
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 "Перезапуск завершен!"
|
||||
@@ -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 готов!"
|
||||
@@ -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 "Перезапуск завершен!"
|
||||
@@ -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 "Перезапуск завершен!"
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,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> {
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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('Кооператив предварительно подготовлен к установке совета.')
|
||||
}
|
||||
|
||||
@@ -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('Базовая установка завершена')
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,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)
|
||||
}
|
||||
|
||||
@@ -16,5 +16,3 @@ export async function sendPostToCoopbackWithSecret(url: string, data: any) {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export * from './randomData'
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "@coopenomics/cleos",
|
||||
"version": "2026.2.22-2",
|
||||
"version": "2025.5.5-2",
|
||||
"private": true,
|
||||
"description": "",
|
||||
"scripts": {
|
||||
|
||||
@@ -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 +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, которые допустимо импортировать.
|
||||
- Не создаём не используемые переменные!
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user