Compare commits
475 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0f617cf1a1 | |||
| f8881cfcde | |||
| 0f5c7ad108 | |||
| b1ccb70b5b | |||
| dedfd68980 | |||
| e47ef47283 | |||
| a7e675f5ad | |||
| 67f1922ca2 | |||
| 702eb927bd | |||
| a6b15f0749 | |||
| bf80a839fa | |||
| 585afb16eb | |||
| 7ea382058b | |||
| a531bb1ab4 | |||
| cd08236dd6 | |||
| 1d72bd3960 | |||
| 3c08215671 | |||
| 4c45d1d701 | |||
| 8e10c57af2 | |||
| 0aeeb69581 | |||
| db86a8e001 | |||
| 61e33a2959 | |||
| a68068ded4 | |||
| 99dc260013 | |||
| e1f4fa066d | |||
| 0d60f4004d | |||
| 297055cb7c | |||
| be7c5fd864 | |||
| 818fa8b0a5 | |||
| 4ea43712d2 | |||
| d023beee30 | |||
| 2d1bef7087 | |||
| 3d3ba56282 | |||
| 729816ed26 | |||
| c1e6b1a433 | |||
| 1e8e5c4412 | |||
| 6c72942a30 | |||
| 7dff06d5b9 | |||
| 830b09ea2c | |||
| 8f2454d4fc | |||
| 9953f7e980 | |||
| fcb816b0e6 | |||
| b294c61b4e | |||
| 6435b50c72 | |||
| 7c804da6b2 | |||
| eaae515860 | |||
| 9bcfd3a654 | |||
| 109f4aaf98 | |||
| beb87b0c00 | |||
| 947077d011 | |||
| 3297616ad1 | |||
| 90c830fdb1 | |||
| 130c544191 | |||
| 63dcdde872 | |||
| e1a496759b | |||
| 99283dfdd7 | |||
| adee9ee032 | |||
| aaf3b5b98f | |||
| 3dbad0ff30 | |||
| 615eba3a84 | |||
| e7de054de2 | |||
| 6f911605d9 | |||
| 00a8461b5b | |||
| 7154c7f0e0 | |||
| 0f07666aeb | |||
| 489dc482e8 | |||
| 55c4759780 | |||
| 16b4fe0080 | |||
| 9cd995c5f2 | |||
| e60d6a5d67 | |||
| b274af05c3 | |||
| 40e725f2ed | |||
| 0f091eefc9 | |||
| 2b00c58e9b | |||
| 6d9681fb8a | |||
| acc4d27677 | |||
| ffe581a92b | |||
| 6e643515ce | |||
| 1cc7aca53f | |||
| e5111622a6 | |||
| 99b98cb5c9 | |||
| 20447cfb46 | |||
| 723a1813c9 | |||
| 04d52a0631 | |||
| 097d18cd56 | |||
| 5043995d14 | |||
| 804cfc8b72 | |||
| a0dc6f3333 | |||
| a53957271e | |||
| abae483dd3 | |||
| 4418e09211 | |||
| ca79457c19 | |||
| d2cd947b9c | |||
| ea28285639 | |||
| 6174ddea9a | |||
| ce56adc3c8 | |||
| 59334bb13a | |||
| 21cd1f5217 | |||
| 515df53c96 | |||
| cbf7f48c44 | |||
| ed37aeb615 | |||
| ea89558696 | |||
| c0bad75a37 | |||
| bdc227f110 | |||
| a9c3a1b6de | |||
| f0e7b0b63a | |||
| a709e2f1e3 | |||
| 3a34bd74db | |||
| b755cb715a | |||
| 42753fae0a | |||
| d8b8fa2d6e | |||
| a75277e0b8 | |||
| f95b83674b | |||
| bb13e780af | |||
| 6e80d5187c | |||
| db9b704d1d | |||
| 65a2b5361c | |||
| 7bca73cd7b | |||
| 59383014be | |||
| 60a5296f5f | |||
| eb4fe333a7 | |||
| 8d06d6d817 | |||
| b6afb536b6 | |||
| 790047fa8a | |||
| baf55b6b6d | |||
| 92d85244f1 | |||
| 0b26433b1a | |||
| 4f06bb40f8 | |||
| 46d571b65a | |||
| 7da1bfd315 | |||
| b30f47b5aa | |||
| ccc6c7206c | |||
| a549f4cae0 | |||
| 07c451ce22 | |||
| 62da624369 | |||
| a1a9b03180 | |||
| e4c1d37c35 | |||
| 42cacac256 | |||
| 2816970327 | |||
| 5744fcdcb2 | |||
| 3c412b394d | |||
| 4e64cea373 | |||
| db579db87f | |||
| f612a918d9 | |||
| 1a78e06a7a | |||
| 374a98ffc3 | |||
| d723e2e273 | |||
| 41030171b5 | |||
| aa31706bcc | |||
| 4e3810493d | |||
| eeb2db92ed | |||
| 5a2930029b | |||
| 2e9a73923b | |||
| 97e67f8a88 | |||
| 074849af74 | |||
| 6939783518 | |||
| 260e48fa48 | |||
| 52c4ef5df5 | |||
| 46b634e0fa | |||
| a556c29a17 | |||
| bbc4f508b5 | |||
| fe0243549a | |||
| d9196ba650 | |||
| a0ea1fa392 | |||
| 195bcb4495 | |||
| 929627a191 | |||
| 83a313b4b7 | |||
| f734bfb50f | |||
| 9efc22d195 | |||
| bd3bef5fb0 | |||
| ddbe49e847 | |||
| ed9bddec0b | |||
| 73a07eeb54 | |||
| 6e27aed804 | |||
| 5f955781a6 | |||
| c124f7310e | |||
| 73ffcae549 | |||
| 80e55ba0be | |||
| eecf955096 | |||
| 6426db9a5d | |||
| bf4753cd09 | |||
| a592eaf383 | |||
| 75d654cec3 | |||
| a18ed37600 | |||
| 6747a8dfbc | |||
| 583dfb8126 | |||
| bda31dd086 | |||
| 283af35f3b | |||
| 82564e2580 | |||
| fc6a9026e6 | |||
| e1538bb5ab | |||
| d47db969ea | |||
| f01de37a59 | |||
| cee429a0a0 | |||
| 321ee89676 | |||
| aa60484315 | |||
| b807df3ee8 | |||
| c11e922573 | |||
| 7135a3bbeb | |||
| 142b87dd67 | |||
| dff4579f28 | |||
| 2b3b662a9a | |||
| 98bc5195b1 | |||
| a02912ccbb | |||
| 2f1d29dfd2 | |||
| 11efacc4cb | |||
| 8133d0da3d | |||
| 093d63faae | |||
| 07f5e04488 | |||
| acfc97fc7c | |||
| 6e30554143 | |||
| fa1c788c41 | |||
| 3a688942c3 | |||
| 6797522618 | |||
| 4730a53313 | |||
| 5a538792f0 | |||
| 5f20289767 | |||
| 4c7ed97a76 | |||
| a9c2228e3c | |||
| ec44fcb7cb | |||
| 1f9c8d261b | |||
| 23ab3bd416 | |||
| 79482775d2 | |||
| 2dcb3678b0 | |||
| 8d7c206012 | |||
| c21c264426 | |||
| 77a39c18f1 | |||
| 65a00b0a8e | |||
| 0c2d5679f1 | |||
| 75d0e0de43 | |||
| 19ff864eaa | |||
| 6bbee7cb10 | |||
| e268923cf6 | |||
| 4fb3d7182c | |||
| 6e7a6ec032 | |||
| 0e92c92f87 | |||
| a6f1eb39ec | |||
| 21364f6239 | |||
| a0527094a9 | |||
| eda890c294 | |||
| 1ed8db87cc | |||
| 136892478f | |||
| aee90d31a0 | |||
| 6fe632fda3 | |||
| fa975a2f5b | |||
| bd5ab14d2f | |||
| 32890bfda9 | |||
| a3d4113b97 | |||
| 8acfa722f8 | |||
| 234beba06a | |||
| 90c27a0f73 | |||
| 2577a5d2c2 | |||
| 84bf10b65b | |||
| 5d70f5fd97 | |||
| f42cc59d53 | |||
| d12b222d52 | |||
| b663c7f9d4 | |||
| 30abfa7e23 | |||
| 0be32c34c5 | |||
| 8d4863b894 | |||
| ad1ae8fb54 | |||
| b31e41bfd9 | |||
| 999d5a937d | |||
| 45ea543a4b | |||
| 9d72a5e02d | |||
| 8973c45b4e | |||
| b3e5a5dc62 | |||
| d8bddadd42 | |||
| aa552d1c80 | |||
| 6daac5f442 | |||
| a8c453fbda | |||
| 3e15da5ad7 | |||
| 8e776e7d42 | |||
| de3d5654b4 | |||
| b0e19ffa30 | |||
| bacf451cf5 | |||
| 2a5600b6af | |||
| bfbc78d9b7 | |||
| d509281bdc | |||
| 866fdfb4f6 | |||
| 6c14d0f4c5 | |||
| b31a8e3bf8 | |||
| 7f3261e453 | |||
| 861c3617dd | |||
| 1f4b1f5421 | |||
| f851460a32 | |||
| 992ac6d9eb | |||
| 2e9ac57930 | |||
| ce8d090f27 | |||
| 4ec8b59fd6 | |||
| 6b086af8b4 | |||
| 52f95665ed | |||
| d1ce5ab105 | |||
| 0fd10649f4 | |||
| 3c65900768 | |||
| 0b962b4fee | |||
| a58633fc5f | |||
| 24c2ed3ae3 | |||
| 84a77d98cf | |||
| 20e50e02e9 | |||
| c8bb354972 | |||
| 3f9b73d211 | |||
| 40bbffed17 | |||
| b16ec4e809 | |||
| 9629c895d5 | |||
| f4420fcc03 | |||
| 17f8c3e134 | |||
| 02512a522c | |||
| 4974f99842 | |||
| a3d0c32be3 | |||
| 31523160fc | |||
| 183d5cdeec | |||
| 52fed810f2 | |||
| f58d63da8d | |||
| 6793e6fbdc | |||
| 4f88e9b97d | |||
| 5004cec9b3 | |||
| 76c81e4209 | |||
| 323043dc01 | |||
| 37902ce70a | |||
| c69e795d49 | |||
| c3b82af95a | |||
| 24510d4828 | |||
| 11a04a99f7 | |||
| 6e96eeaa05 | |||
| 2c16d4af5a | |||
| d1f9cf43de | |||
| 26fb11cb30 | |||
| a7a3eb8658 | |||
| fbedcd1f5f | |||
| 6033af0f23 | |||
| 4809786b16 | |||
| a37f40ff6e | |||
| 44f5cc9de0 | |||
| e059d0cbd9 | |||
| c51040b0de | |||
| eba2abc108 | |||
| ed7626009a | |||
| 442413ec6b | |||
| 90986df641 | |||
| 8b505dbce3 | |||
| 2b26e9643b | |||
| eb16f01839 | |||
| 872cf33d1c | |||
| afbce19028 | |||
| e27228e735 | |||
| 3125232114 | |||
| e552840cc4 | |||
| 569142845a | |||
| e62d837858 | |||
| 27badfb88a | |||
| e4d0781eb8 | |||
| 93c8373335 | |||
| 42bad6b4ea | |||
| 83d4bd88cf | |||
| 4141267104 | |||
| d5778f165b | |||
| 7128e26a27 | |||
| 78247e9969 | |||
| 66d36e034e | |||
| 4100ee842c | |||
| d4857c8e6e | |||
| 0f52781489 | |||
| 332f19cd22 | |||
| 53a1c103d6 | |||
| addf7a3e6b | |||
| c76078ea06 | |||
| 9299288e8a | |||
| dc34f24ee2 | |||
| fe83bf1483 | |||
| c184f9281d | |||
| 3127a69b67 | |||
| d7326c4f82 | |||
| f8478284b6 | |||
| 1c910de4b1 | |||
| 2c3cb955ef | |||
| 1d7746d839 | |||
| e16d2ba238 | |||
| 60f8b448ec | |||
| c0d61f29a8 | |||
| c47b99f9ee | |||
| 653429a6d6 | |||
| c2250e1817 | |||
| 92ca48d65f | |||
| 2f53d9a79d | |||
| d025bfe2da | |||
| fbc2460ac4 | |||
| 76c2800275 | |||
| 536bf984ee | |||
| 23f8af43ee | |||
| eba54bf2e7 | |||
| 50270e6541 | |||
| 5f7d9e80ee | |||
| a71ef35d5c | |||
| 2cf5291831 | |||
| 75e3e9dd7b | |||
| 7a75365d2f | |||
| 7a23d59aa1 | |||
| 2583423600 | |||
| 1d748a0424 | |||
| 86172790d1 | |||
| 92c9103059 | |||
| 16393dbc9d | |||
| e841b0ef03 | |||
| 27c7ae9d76 | |||
| dc3a40675d | |||
| 31e65ac2e3 | |||
| c2444d1b09 | |||
| 2faadae4cf | |||
| d24e51c355 | |||
| 8ed8af1b9c | |||
| 5f6b416f26 | |||
| 3671c05573 | |||
| db6eed9292 | |||
| 4c903ba0bb | |||
| 9eee216065 | |||
| 9e1dd02d70 | |||
| b986bf9bcc | |||
| a3e0e62efc | |||
| 8646c5acf6 | |||
| dab9465755 | |||
| 3a076cc4ea | |||
| 1b6d1ea0ef | |||
| c490284c65 | |||
| c3ca329138 | |||
| d0f73c32bb | |||
| 79e0da7cf2 | |||
| b261f505aa | |||
| 65c4c95cbe | |||
| 5b3c9c9055 | |||
| d32d4f845c | |||
| 637950d37f | |||
| 0e1ede58e5 | |||
| e914a11768 | |||
| 73d66dab35 | |||
| 634766a08e | |||
| 5fbc450e96 | |||
| 5acd7ae2c4 | |||
| 09578ddc5d | |||
| 8270801711 | |||
| de91c3cfc8 | |||
| 06888a2061 | |||
| cde2d33d03 | |||
| 9cf89f95d1 | |||
| 20486e15ea | |||
| 420c403da1 | |||
| cb8fdf3461 | |||
| 0937d08770 | |||
| 9b1fd8e1aa | |||
| d87861142c | |||
| ff5fc1d0f3 | |||
| d5a671cbb8 | |||
| d0508cbd96 | |||
| 12098cf8ac | |||
| 2557c35bea | |||
| 11f23bbd77 | |||
| 2b2ec729ac | |||
| 9292acec8c | |||
| 3095ed5375 | |||
| a333e303c1 | |||
| bd8512feab | |||
| c235fc4a7a | |||
| 9c283b9aee | |||
| 4e824d739b | |||
| fcbbc792b0 | |||
| ba7cf8e79e | |||
| 4ab49d8e43 | |||
| 7f864db80d | |||
| 772a163693 | |||
| c8e106f272 | |||
| ade4f0a1d4 | |||
| 4fcf933dd1 | |||
| 1fb60771ca | |||
| 2cd391e106 | |||
| 9b922b00c1 |
@@ -2,8 +2,6 @@ name: Build Docker Images
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- testnet
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
@@ -28,12 +26,23 @@ jobs:
|
||||
echo "Проверяем файлы в middlewares:"
|
||||
ls -la components/desktop/src-ssr/middlewares/ || echo "Директория middlewares не найдена!"
|
||||
|
||||
- name: Set docker tag
|
||||
- name: Set docker tags
|
||||
run: |
|
||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
echo "DOCKER_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
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
|
||||
else
|
||||
echo "DOCKER_TAG=latest" >> $GITHUB_ENV
|
||||
echo "IS_PRODUCTION_TAG=false" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Login to DockerHub
|
||||
@@ -47,6 +56,12 @@ 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
|
||||
@@ -55,6 +70,11 @@ 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: |
|
||||
@@ -62,6 +82,11 @@ 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: |
|
||||
@@ -69,6 +94,11 @@ 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: |
|
||||
@@ -76,6 +106,11 @@ 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: |
|
||||
@@ -83,35 +118,41 @@ jobs:
|
||||
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/latest ]]; then
|
||||
# Хук для тестнета
|
||||
curl -X POST ${{ vars.TESTNET_WEBHOOK_URL }} \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '${{ env.DOCKER_TAG }}'
|
||||
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 }}'
|
||||
else
|
||||
# Хук для тестнета (обычная ветка)
|
||||
curl -X POST ${{ vars.TESTNET_WEBHOOK_URL }} \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '${{ env.DOCKER_TAG }}'
|
||||
# Хук для продакшена (остальные теги)
|
||||
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)"
|
||||
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 }}]"
|
||||
-d text="✅ [GITHUB MONO] Успешная сборка контейнеров: $GITHUB_REPOSITORY ($GITHUB_REF) [${{ env.DOCKER_TAG }}]$ADDITIONAL_INFO"
|
||||
|
||||
# Уведомление в Telegram об ошибке
|
||||
- name: Telegram notify failure
|
||||
|
||||
@@ -4,6 +4,9 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- testnet
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
build-and-publish-docs:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -81,6 +84,18 @@ 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
|
||||
@@ -89,5 +104,4 @@ 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,31 +3,29 @@ 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:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
node-version: 24
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- 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
|
||||
- run: npm install -g pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm lerna run build
|
||||
- run: pnpm lerna publish from-package --yes
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: pnpm lerna publish from-package --yes --no-verify-access
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 120,
|
||||
"plugins": [
|
||||
"prettier-plugin-sort-imports"
|
||||
]
|
||||
}
|
||||
Vendored
+4
-1
@@ -67,5 +67,8 @@
|
||||
|
||||
// Оптимизация для больших монорепозиториев
|
||||
"typescript.disableAutomaticTypeAcquisition": true,
|
||||
"typescript.surveys.enabled": false
|
||||
"typescript.surveys.enabled": false,
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
# AGENTS.md
|
||||
|
||||
## Cursor Cloud specific instructions
|
||||
|
||||
### Обзор
|
||||
|
||||
Монорепозиторий «Цифровой Кооператив» (monocoop) — платформа управления кооперативами на блокчейне EOSIO. pnpm v9 + Lerna. Node.js v20.
|
||||
|
||||
### Сервисы
|
||||
|
||||
| Компонент | Контейнер | Порт | Описание |
|
||||
|-----------|-----------|------|----------|
|
||||
| controller | coopback | 2998 | NestJS GraphQL API |
|
||||
| desktop | desktop | 2999 | Vue 3 + Quasar SPA |
|
||||
| parser | cooparser | 4000 | Индексация блокчейна через SHiP |
|
||||
| blockchain | node | 8888, 8070 | EOSIO node + State History Plugin |
|
||||
| MongoDB | mongo | 27017 | Основная БД (replica set) |
|
||||
| Redis | monoredis | 6379 | Кэш и стримы |
|
||||
| PG | (см. compose) | 5532→5432 | Реляционная БД |
|
||||
|
||||
### Полный перезапуск (одна команда)
|
||||
|
||||
```
|
||||
pnpm run reboot
|
||||
```
|
||||
|
||||
Делает: останавливает контейнеры → чистит blockchain data и volumes → поднимает инфру → ждёт готовности → `pnpm run boot` → запускает parser и controller.
|
||||
|
||||
### Первоначальная настройка Cloud-окружения
|
||||
|
||||
1. **`/etc/hosts`** — обязательно для boot (запускается на хосте, обращается к MongoDB по docker hostname):
|
||||
```
|
||||
echo "127.0.0.1 mongo" | sudo tee -a /etc/hosts
|
||||
echo "127.0.0.1 monoredis" | sudo tee -a /etc/hosts
|
||||
```
|
||||
|
||||
2. **WeasyPrint** — системная зависимость для генерации PDF:
|
||||
```
|
||||
sudo apt-get install -y python3 python3-venv libpango-1.0-0 libcairo2 libffi-dev libjpeg-dev libopenjp2-7-dev libharfbuzz-dev
|
||||
sudo python3 -m venv /opt/weasyprint-venv && sudo /opt/weasyprint-venv/bin/pip install WeasyPrint==67
|
||||
sudo ln -sf /opt/weasyprint-venv/bin/weasyprint /usr/local/bin/weasyprint
|
||||
```
|
||||
|
||||
3. **Контракты** (test-режим — позволяет boot с 1 членом совета):
|
||||
```
|
||||
cd components/contracts && sudo rm -rf build && bash build-all.sh test
|
||||
```
|
||||
|
||||
4. **Shared-библиотеки** (порядок важен):
|
||||
```
|
||||
pnpm --filter cooptypes run build
|
||||
pnpm --filter @coopenomics/factory run build
|
||||
pnpm --filter @coopenomics/sdk run build
|
||||
pnpm --filter @coopenomics/notifications run build
|
||||
```
|
||||
|
||||
5. **`.env` файлы** — скопировать из `.env-example`, адаптировать hostnames:
|
||||
- Controller/Parser (в Docker): хосты по именам контейнеров из docker-compose (порт БД 5432)
|
||||
- Boot (на хосте): `127.0.0.1`, PG порт `5532`, mongo через `/etc/hosts`
|
||||
- Desktop: `127.0.0.1`
|
||||
- **CHAIN_ID**: берётся из `curl http://localhost:8888/v1/chain/get_info` после старта ноды
|
||||
- Controller требует `VAPID_PUBLIC_KEY` и `VAPID_PRIVATE_KEY`
|
||||
|
||||
6. **Запуск**: `pnpm run reboot`, затем `docker compose up -d --force-recreate coopback cooparser` (если .env менялись)
|
||||
|
||||
### Запуск тестов
|
||||
|
||||
- **Factory** (`components/factory`): нужен только MongoDB. Запуск:
|
||||
```
|
||||
NODE_ENV=test SOURCE=local MONGO_URI=$MONGO_URI SKIP_BLOCK_FETCH=TRUE pnpm --filter @coopenomics/factory test
|
||||
```
|
||||
`MONGO_URI` по умолчанию: `mongodb://<host>:27017/cooperative-x`.
|
||||
- **Boot** (`components/boot`): требует полный EOSIO blockchain + MongoDB + PG. Запуск после `pnpm run reboot`:
|
||||
```
|
||||
pnpm --filter @coopenomics/boot test
|
||||
```
|
||||
- **Duplicate transaction** — в boot-тестах EOSIO отклоняет транзакции с одинаковым хешем (TAPOS block + action data). При повторном вызове `refreshSegment` для того же участника — добавить `await sleep(500)` перед ним. Паттерн уже используется (см. комментарий на строке ~720 capital.test.ts).
|
||||
|
||||
### Маркетплейс (cooplace extension)
|
||||
|
||||
**Архитектура:**
|
||||
- `cooplace` = бэкенд маркетплейса (карточки, категории, настройки, match, cycles)
|
||||
- `marketplace` = фронтенд расширение (витрина, заказы — использует API cooplace)
|
||||
- `market-admin` = фронтенд админки (настройки, модерация)
|
||||
|
||||
**Ключевой принцип:** Каждая встречная заявка сразу в блокчейн → блокировка средств. Карточки в БД, заявки в blockchain.
|
||||
|
||||
**Циклы:** min_units/deadline — порог для supply, не для match. При истечении → cancel через blockchain.
|
||||
|
||||
**Настройки** (MarketplaceSettings): lead_request_policy (offers_only/orders_only/both), publish_access_policy (all_members/whitelist/council_only), moderation, cycles, delivery types.
|
||||
|
||||
### Критические gotchas
|
||||
|
||||
- **SHiP порт 8070** — `state-history-endpoint = 0.0.0.0:8070` в config.ini. Парсер: `SHIP=ws://node:8070`.
|
||||
- **Парсер START_BLOCK**: при `START_BLOCK=1` на чистой БД стартует с HEAD и делает частичную инициализацию. Для полного replay: временно `START_BLOCK=2`, после первого запуска вернуть `1`.
|
||||
- **SSR desktop в dev** — расширения не рендерятся из-за Pinia SSR-сериализации компонентов. Dev — SPA (`quasar dev`), production build SSR работает.
|
||||
- **Тестовые учётные данные**: email `ivanov@example.com`, ключ — дефолтный EOSIO dev key (см. `components/boot/.env-example`), пользователь `ant` (председатель).
|
||||
- **Docker hostnames**: без `network_mode: host` — контейнеры обращаются друг к другу по именам контейнеров из docker-compose.
|
||||
- **Установка пакетов**: только через фильтр — `pnpm add <pkg> --filter <component>`.
|
||||
+123
@@ -1,3 +1,126 @@
|
||||
# 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 для генерации и капитализации результатов интеллектуальной деятельности. Контракт описывает и обеспечивает:
|
||||
|
||||
+19
-13
@@ -1,4 +1,4 @@
|
||||
FROM node:20-alpine AS builder
|
||||
FROM node:20-slim AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -12,23 +12,29 @@ RUN npm install -g pnpm lerna
|
||||
# Используем версию pnpm, совместимую с существующим lock-файлом
|
||||
RUN pnpm install
|
||||
|
||||
# Установка системных зависимостей для WeasyPrint
|
||||
RUN apk add --no-cache \
|
||||
# Установка системных зависимостей для WeasyPrint и диагностических утилит (Debian/Ubuntu версии)
|
||||
RUN apt-get update && apt-get install -y \
|
||||
python3 \
|
||||
py3-pip \
|
||||
python3-pip \
|
||||
python3-venv \
|
||||
gcc \
|
||||
musl-dev \
|
||||
python3-dev \
|
||||
pango \
|
||||
zlib-dev \
|
||||
jpeg-dev \
|
||||
openjpeg-dev \
|
||||
g++ \
|
||||
python3-dev \
|
||||
libpango-1.0-0 \
|
||||
libpangoft2-1.0-0 \
|
||||
libpangocairo-1.0-0 \
|
||||
libcairo2 \
|
||||
libcairo2-dev \
|
||||
libffi-dev \
|
||||
harfbuzz-subset \
|
||||
shared-mime-info \
|
||||
zlib1g-dev \
|
||||
libjpeg-dev \
|
||||
libopenjp2-7-dev \
|
||||
procps \
|
||||
wget \
|
||||
&& python3 -m venv /venv \
|
||||
&& /venv/bin/pip install WeasyPrint==62.3 \
|
||||
&& rm -rf /var/cache/*
|
||||
&& /venv/bin/pip install WeasyPrint==67 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Сборка всех компонентов
|
||||
RUN lerna run build
|
||||
|
||||
+367
@@ -0,0 +1,367 @@
|
||||
# MARKET-LOGIC.md — Бизнес-процессы маркетплейса
|
||||
|
||||
## 2А. БИЗНЕС-ПРОЦЕССЫ СМАРТ-КОНТРАКТОВ
|
||||
|
||||
### Основные бизнес-сценарии
|
||||
|
||||
**Сценарий 1: Поставка-приобретение имущества**
|
||||
- **Описание:** Обмен имущества между пайщиками через кооператив с блокировкой средств и документооборотом
|
||||
- **Участники:** Заказчик, Поставщик, Совет кооператива, Председатель КУ
|
||||
- **Бизнес-ценность:** Основной процесс кооперативного маркетплейса
|
||||
|
||||
**Сценарий 1А.** Прямая поставка (OFFER→ORDER) — поставщик публикует, заказчик откликается
|
||||
**Сценарий 1Б.** Обратная поставка (ORDER→OFFER) — заказчик публикует, поставщики откликаются
|
||||
**Сценарий 1В.** Из запасов кооператива (COOPSTOCK) — имущество уже на балансе
|
||||
|
||||
**Сценарий 2: Гарантийный возврат**
|
||||
- **Описание:** Возврат бракованного имущества в течение гарантийного срока
|
||||
- **Участники:** Заказчик, Поставщик, Председатель КУ, Совет
|
||||
- **Бизнес-ценность:** Защита интересов пайщика и качества имущества
|
||||
|
||||
**Сценарий 3: Уничтожение/перепредложение**
|
||||
- **Описание:** Утилизация просроченного или перепродажа по новой цене
|
||||
- **Участники:** Председатель КУ, Совет
|
||||
- **Бизнес-ценность:** Управление складскими запасами и минимизация потерь
|
||||
|
||||
**Сценарий 4: Транспортировка**
|
||||
- **Описание:** Перевозка имущества между КУ группами
|
||||
- **Участники:** Председатель КУ отправителя, Водитель, Председатель КУ получателя
|
||||
- **Бизнес-ценность:** Логистика распределённой кооперативной сети
|
||||
|
||||
---
|
||||
|
||||
### Процесс 1А: Прямая поставка (OFFER→ORDER)
|
||||
|
||||
**Предусловия:**
|
||||
- Поставщик создал карточку товара в БД (статус: `published`)
|
||||
- Заказчик нашёл карточку на витрине и решил заказать
|
||||
- У заказчика достаточно средств на цифровом кошельке
|
||||
|
||||
**Последовательность шагов:**
|
||||
|
||||
#### Шаг 1: orderoffer — Заказчик создаёт заявку
|
||||
|
||||
- **Предусловие:** Карточка товара в БД (published). При создании заявки происходит **match** — заявка публикуется в блокчейн
|
||||
- **Исполнитель:** Заказчик (через контроллер, авторизация от кооператива)
|
||||
- **Подписываемые документы:** Заявление на конвертацию из кошелька (convert_in)
|
||||
- **Проводки по кошелькам:**
|
||||
- Списать у заказчика `total_cost` из ЦПП «Цифровой Кошелёк»
|
||||
- Начислить заказчику `total_cost` в ЦПП «Маркетплейс»
|
||||
- Заблокировать у заказчика `total_cost` в ЦПП «Маркетплейс»
|
||||
- **Статус заявки:** `active`
|
||||
- **Параметры:** `delivery_type` (internal/external), `contribution_type` (share/member)
|
||||
|
||||
#### Шаг 2: accept — Поставщик принимает заявку
|
||||
|
||||
- **Исполнитель:** Поставщик
|
||||
- **Подписываемые документы:**
|
||||
- Заявление на конвертацию в кошелёк (convert_out)
|
||||
- Заявление на имущественный паевой взнос (contribution_statement)
|
||||
- **Проводки:** Нет
|
||||
- **Эффект:** Создаётся **одно** заявление в совет — `authcontrib` (авторизация взноса)
|
||||
- **Статус заявки:** `accepted`
|
||||
|
||||
#### Шаг 3: authcontrib — Совет авторизует взнос
|
||||
|
||||
- **Исполнитель:** Совет кооператива (автоматически через soviet контракт)
|
||||
- **Подписываемые документы:** Решение совета об авторизации взноса
|
||||
- **Проводки:** Нет
|
||||
- **Статус заявки:** `authorized` — поставщик может начинать поставку
|
||||
|
||||
#### Шаг 4: supply — Поставщик поставляет имущество на КУ
|
||||
|
||||
- **Исполнитель:** Поставщик
|
||||
- **Подписываемые документы:** Акт поставки (supply_act)
|
||||
- **Проводки:** Нет
|
||||
- **Статус заявки:** `supplied1`
|
||||
|
||||
#### Шаг 5: supplcnf — Председатель КУ подтверждает поставку
|
||||
|
||||
- **Исполнитель:** Председатель КУ поставщика
|
||||
- **Подписываемые документы:** Акт подтверждения поставки (supply_act_conf)
|
||||
- **Проводки по кошелькам:**
|
||||
- Начислить поставщику `base_cost` в ЦПП «Маркетплейс»
|
||||
- Заблокировать у поставщика `base_cost` в ЦПП «Маркетплейс»
|
||||
- **Проводки по ledger:**
|
||||
- Увеличить паевой фонд (счёт 80) на `total_cost`
|
||||
- **Статус заявки:** `supplied2`, имущество на складе КУ
|
||||
|
||||
#### Шаг 6: [Транспортировка] — При необходимости (см. Сценарий 4)
|
||||
|
||||
#### Шаг 7: delivered — Готово к выдаче
|
||||
|
||||
- **Исполнитель:** Председатель КУ получателя
|
||||
- **Подписываемые документы:** Нет
|
||||
- **Проводки:** Нет
|
||||
- **Статус заявки:** `delivered`
|
||||
|
||||
#### Шаг 8: reqreturn — Заказчик запрашивает возврат
|
||||
|
||||
- **Исполнитель:** Заказчик
|
||||
- **Подписываемые документы:** Заявление на возврат паевого взноса имуществом (return_statement) — с актуальными данными о весе/составе
|
||||
- **Проводки:** Нет
|
||||
- **Эффект:** Создаётся заявление в совет — `authreturn`
|
||||
- **Статус заявки:** `reqreturn`
|
||||
|
||||
#### Шаг 9: authreturn — Совет авторизует возврат
|
||||
|
||||
- **Исполнитель:** Совет кооператива
|
||||
- **Подписываемые документы:** Решение совета об авторизации возврата
|
||||
- **Проводки:** Нет
|
||||
- **Статус заявки:** `retauthorized`
|
||||
|
||||
#### Шаг 10: receive — Председатель КУ передаёт имущество
|
||||
|
||||
- **Исполнитель:** Председатель КУ
|
||||
- **Подписываемые документы:** Акт приёма-передачи (receive_act)
|
||||
- **Проводки:** Нет
|
||||
- **Статус заявки:** `received1`
|
||||
|
||||
#### Шаг 11: receivecnf — Заказчик подтверждает получение
|
||||
|
||||
- **Исполнитель:** Заказчик
|
||||
- **Подписываемые документы:** Акт подтверждения получения (receive_act_conf)
|
||||
- **Проводки по кошелькам:**
|
||||
- Списать заблокированный баланс заказчика `total_cost` из ЦПП «Маркетплейс»
|
||||
- **Проводки по ledger:**
|
||||
- Уменьшить паевой фонд (счёт 80) на `base_cost`
|
||||
- **Статус заявки:** `received2`
|
||||
- **Эффект:** Устанавливается `warranty_delay_until` (текущее время + гарантийный срок)
|
||||
|
||||
#### Шаг 12: complete — Завершение после гарантии
|
||||
|
||||
- **Исполнитель:** Система (после истечения `warranty_delay_until`)
|
||||
- **Проводки по кошелькам:**
|
||||
- Списать заблокированный баланс поставщика `base_cost` из ЦПП «Маркетплейс»
|
||||
- Начислить поставщику `base_cost` в ЦПП «Цифровой Кошелёк»
|
||||
- **Статус:** Заявка удаляется из блокчейна
|
||||
|
||||
**Постусловия:**
|
||||
- Заказчик получил имущество
|
||||
- Поставщик получил средства на кошелёк
|
||||
- Членские взносы распределены по фондам кооператива
|
||||
|
||||
---
|
||||
|
||||
### Процесс 1Б: Обратная поставка (ORDER→OFFER)
|
||||
|
||||
**Предусловия:**
|
||||
- Заказчик создал карточку заказа в БД (статус: `published`, тип: `order`)
|
||||
- Поставщик нашёл заказ и готов поставить
|
||||
|
||||
**Последовательность шагов:**
|
||||
|
||||
#### Шаг 1: createorder — Заказчик публикует заказ
|
||||
|
||||
- **Предусловие:** Карточка заказа в БД. При публикации — **match** в блокчейн
|
||||
- **Проводки:** Списание + блокировка `total_cost` заказчика (аналогично orderoffer)
|
||||
- **Статус:** `active`
|
||||
|
||||
#### Шаг 2: respondoffer — Поставщик откликается
|
||||
|
||||
- **Исполнитель:** Поставщик
|
||||
- **Подписываемые документы:** Заявление на взнос + конвертация
|
||||
- **Эффект:** Создаётся заявление в совет `authcontrib`
|
||||
- **Статус предложения:** `accepted`
|
||||
|
||||
#### Шаги 3-12: Аналогичны Процессу 1А (authcontrib → complete)
|
||||
|
||||
---
|
||||
|
||||
### Процесс 1В: Из запасов кооператива (COOPSTOCK)
|
||||
|
||||
**Предусловия:**
|
||||
- Имущество уже на балансе кооператива (на складе КУ)
|
||||
- Председатель КУ создаёт предложение coopstock
|
||||
|
||||
**Последовательность шагов:**
|
||||
|
||||
#### Шаг 1: coopstock — Создание предложения
|
||||
|
||||
- **Исполнитель:** Председатель КУ
|
||||
- **Проводки:** Нет (имущество уже на балансе)
|
||||
- **Статус:** `delivered` (сразу готово к выдаче)
|
||||
|
||||
#### Шаг 2: acceptstock — Заказчик принимает
|
||||
|
||||
- **Исполнитель:** Заказчик
|
||||
- **Подписываемые документы:** Конвертация + заявление на возврат
|
||||
- **Проводки:** Блокировка `total_cost` заказчика
|
||||
- **Эффект:** Сразу создаётся заявление в совет `authreturn`
|
||||
- **Статус:** `reqreturn`
|
||||
|
||||
#### Шаги 3-6: authreturn → receive → receivecnf → complete (аналогично 1А шаги 9-12)
|
||||
|
||||
**Особенности:**
|
||||
- Пропущены шаги accept, authcontrib, supply, supplcnf — не нужны
|
||||
- Нет проводок по паевому фонду при поставке (имущество уже на балансе)
|
||||
|
||||
---
|
||||
|
||||
### Процесс 2: Гарантийный возврат
|
||||
|
||||
**Предусловия:**
|
||||
- Заявка в статусе `received2` (имущество получено)
|
||||
- Не истёк `warranty_delay_until`
|
||||
- Заказчик обнаружил дефект
|
||||
|
||||
**Последовательность шагов:**
|
||||
|
||||
#### Шаг 1: dispute — Заказчик подаёт претензию
|
||||
|
||||
- **Подписываемые документы:** Претензия (wdispute) + фото/видео
|
||||
- **Эффект:** Деньги поставщика дополнительно блокируются
|
||||
|
||||
#### Шаг 2: Рассмотрение на КУ (вне контракта)
|
||||
|
||||
- **Исполнитель:** Председатель КУ
|
||||
- **Действия:** Осмотр, подтверждение/отклонение претензии
|
||||
|
||||
#### Шаг 3: wauthorize — Совет авторизует возврат
|
||||
|
||||
- **Подписываемые документы:** Решение о возврате (wreturn_auth) + решение о выдаче поставщику (wsupply_auth)
|
||||
|
||||
#### Шаг 4: wreturn — Возврат имущества в кооператив
|
||||
|
||||
- **Проводки:** Разблокировка средств заказчика, начисление на кошелёк
|
||||
|
||||
#### Шаг 5: woffer — Предложение имущества поставщику
|
||||
|
||||
#### Шаг 6: waccept — Поставщик принимает/отказывается
|
||||
|
||||
**Альтернативные потоки:**
|
||||
- **Отклонение претензии:** Заказчик забирает имущество обратно
|
||||
- **Поставщик не забрал:** Имущество перепредлагается (→ reoffer) или уничтожается (→ destroy)
|
||||
|
||||
---
|
||||
|
||||
### Процесс 3: Уничтожение имущества
|
||||
|
||||
**Предусловия:**
|
||||
- Заявка в статусе `delivered` или `supplied2`
|
||||
- Истёк `deadline_for_receipt` (заказчик не пришёл)
|
||||
|
||||
**Шаг 1: destroy — Уничтожение**
|
||||
|
||||
- **Исполнитель:** Председатель КУ (chairman)
|
||||
- **Подписываемые документы:** Акт уничтожения (destruction_act)
|
||||
- **Проводки:**
|
||||
- Возврат заказчику: `total_cost - cancellation_fee` → разблокировать и вернуть в кошелёк
|
||||
- Штраф `cancellation_fee` → в фонд членских взносов (spreadamount)
|
||||
- Поставщику: `base_cost` → разблокировать и вернуть в кошелёк
|
||||
- **Эффект:** Заявка удаляется из блокчейна
|
||||
|
||||
---
|
||||
|
||||
### Процесс 3Б: Перепредложение (reoffer)
|
||||
|
||||
**Предусловия:**
|
||||
- Аналогичны destroy, но срок годности НЕ истёк
|
||||
|
||||
**Шаг 1: reoffer — Перепредложение по новой цене**
|
||||
|
||||
- **Исполнитель:** Председатель КУ
|
||||
- **Проводки:** Аналогичны destroy (возврат средств)
|
||||
- **Эффект:** Старая заявка удаляется, создаётся новая типа `coopstock` со статусом `delivered`
|
||||
|
||||
---
|
||||
|
||||
### Процесс 4: Транспортировка (Shipment)
|
||||
|
||||
**Предусловия:**
|
||||
- Имущество на складе КУ (статус `supplied2` или `shiprecvd`)
|
||||
- Нужно доставить на другой КУ
|
||||
|
||||
**Последовательность шагов:**
|
||||
|
||||
#### Шаг 1: createship — Создание перевозки
|
||||
- **Исполнитель:** Представитель КУ отправителя
|
||||
- **Документ:** Акт передачи (shsendact)
|
||||
- **Статус перевозки:** `loading`
|
||||
|
||||
#### Шаг 2: signbydriver — Подпись водителя
|
||||
- **Исполнитель:** Водитель-пайщик
|
||||
- **Документ:** Акт приёма (shloadact)
|
||||
- **Эффект:** Товары снимаются со склада
|
||||
- **Статус:** `transit`
|
||||
|
||||
#### Шаг 3: arrived — Прибытие
|
||||
- **Исполнитель:** Водитель
|
||||
- **Документ:** Акт доставки (sharriveact)
|
||||
- **Статус:** `arrived`
|
||||
|
||||
#### Шаг 4: receiveshipm — Приём на складе
|
||||
- **Исполнитель:** Представитель КУ получателя
|
||||
- **Документ:** Акт приёма на складе (shrecvact)
|
||||
- **Эффект:** Товары ставятся на склад КУ назначения, перевозка удаляется
|
||||
- **Статус заявок:** `shiprecvd`
|
||||
|
||||
**Альтернативный поток:** retransport — промежуточная перегрузка на другой маршрут
|
||||
|
||||
---
|
||||
|
||||
### Связи между процессами
|
||||
|
||||
- **Процесс 1 → Процесс 4:** После `supplcnf` (шаг 5) товар может пойти на транспортировку перед `delivered`
|
||||
- **Процесс 1 → Процесс 2:** После `receivecnf` (шаг 11) возможен гарантийный возврат до истечения `warranty_delay_until`
|
||||
- **Процесс 2 → Процесс 3:** Если поставщик не забирает возвращённое имущество → destroy/reoffer
|
||||
- **Процесс 1 → Процесс 3:** Если заказчик не приходит за товаром → destroy/reoffer
|
||||
- **Процесс 3Б → Процесс 1В:** reoffer создаёт coopstock → новый цикл 1В
|
||||
|
||||
---
|
||||
|
||||
## Ключевой принцип: Match = Блокчейн
|
||||
|
||||
**Каждая встречная заявка сразу публикуется в блокчейн.**
|
||||
|
||||
Карточки хранятся в БД (draft → moderation → published).
|
||||
Но как только появляется встречная заявка — она немедленно уходит в блокчейн.
|
||||
Блокировка средств — дело смарт-контракта, не контроллера.
|
||||
|
||||
### Циклы поставки (min_units + cycle_deadline)
|
||||
|
||||
Циклы НЕ влияют на match. Они определяют:
|
||||
- **Когда поставщик начинает поставку** (supply) — при наборе min_units
|
||||
- **Когда цикл истекает** — cancel всех заявок через блокчейн → возврат средств
|
||||
|
||||
Поток:
|
||||
1. Карточка published, `min_units: 100`, `cycle_deadline: 7 февраля`
|
||||
2. Заказчик 1 → order на 30 ед. → **сразу в блокчейн** → средства заблокированы
|
||||
3. Заказчик 2 → order на 50 ед. → **сразу в блокчейн** → средства заблокированы
|
||||
4. Заказчик 3 → order на 25 ед. → **сразу в блокчейн** → средства заблокированы
|
||||
5. Итого 105 ≥ 100 → поставщику разрешено начать supply
|
||||
6. Если бы до 7 февраля набрали только 70 → cancel всех заявок → возврат через блокчейн → новый цикл
|
||||
|
||||
---
|
||||
|
||||
### Бизнес-правила и ограничения
|
||||
|
||||
**Правило 1: Блокировка средств при заказе**
|
||||
- **Описание:** Средства заказчика блокируются в момент создания заявки в блокчейне (match)
|
||||
- **Применение:** orderoffer, createorder, acceptstock
|
||||
- **Последствия нарушения:** Заявка не может быть создана без достаточных средств
|
||||
|
||||
**Правило 2: Одно заявление в совет при принятии**
|
||||
- **Описание:** При accept создаётся только authcontrib (на взнос). authreturn — перед получением
|
||||
- **Применение:** accept, reqreturn
|
||||
- **Обоснование:** Заявление на возврат содержит точные данные (вес), доступные после доставки
|
||||
|
||||
**Правило 3: Тип доставки определяет маршрут**
|
||||
- **Описание:** `delivery_type: internal` — между КУ через Shipment, `external` — через внешний сервис (СДЭК)
|
||||
- **Применение:** Определяется в карточке при создании
|
||||
|
||||
**Правило 4: Тип взноса определяет проводки**
|
||||
- **Описание:** `contribution_type: share` — паевой взнос (возврат имуществом), `member` — членский взнос (кооператив покупает)
|
||||
- **Применение:** Влияет на проводки при complete
|
||||
|
||||
**Правило 5: Гарантийный период**
|
||||
- **Описание:** `warranty_delay_until = received_at + warranty_period_secs`. До истечения — complete невозможен, dispute возможен
|
||||
- **Применение:** complete, dispute
|
||||
|
||||
**Правило 6: Карточки в БД, заявки в блокчейне**
|
||||
- **Описание:** Карточки хранятся в PostgreSQL (draft → moderation → published). Каждая встречная заявка сразу публикуется в блокчейн — средства блокируются смарт-контрактом
|
||||
- **Применение:** MatchService при каждой встречной заявке
|
||||
|
||||
**Правило 7: Циклы — порог для supply, не для match**
|
||||
- **Описание:** min_units определяет когда поставщику разрешено начать supply. cycle_deadline — когда cancel при ненаборе. Каждый заказ сразу в блокчейн независимо от цикла
|
||||
- **Применение:** CycleService (cron каждые 5 мин)
|
||||
@@ -1,135 +1,120 @@
|
||||
# MONO
|
||||
# Цифровой Кооператив
|
||||
|
||||
Система управления взаимоотношениями в кооперативе. Включает в себя полный комплект программного обеспечения для подключения к платформе "Кооперативная Экономика" и управления взаимоотношениями с пайщиками в кооперативе и самим кооперативом на основе смарт-контрактов и простой электронной подписи.
|
||||
<!-- badges -->
|
||||

|
||||

|
||||

|
||||
|
||||
## Ветки
|
||||
Платформа «Цифровой Кооператив» — комплексное программное обеспечение для управления кооперативными организациями на основе блокчейна EOSIO. Система обеспечивает полный цикл управления кооперативом: от регистрации пайщиков и электронного документооборота до проведения собраний и финансового учёта. Построена на принципах прозрачности, децентрализации и простой электронной подписи.
|
||||
|
||||
- Для разработки используется ветка `dev`.
|
||||
- Для стейджа используется ветка `testnet`.
|
||||
- Для продакшена используется ветка `main`.
|
||||
Проект является частью экосистемы [Кооперативная Экономика](https://coopenomics.world).
|
||||
|
||||
## Установка
|
||||
## Архитектура
|
||||
|
||||
```sh
|
||||
| Компонент | Пакет | Описание |
|
||||
|-----------|-------|----------|
|
||||
| [boot](components/boot) | `@coopenomics/boot` | CLI для инициализации и управления блокчейн-инфраструктурой |
|
||||
| [cleos](components/cleos) | `@coopenomics/cleos` | Утилита командной строки для работы с блокчейн-кошельком |
|
||||
| [contracts](components/contracts) | `@coopenomics/contracts` | Смарт-контракты EOSIO на C++ |
|
||||
| [controller](components/controller) | `@coopenomics/controller` | GraphQL API сервер (NestJS) |
|
||||
| [cooptypes](components/cooptypes) | `cooptypes` | Общие типы и интерфейсы блокчейн-контрактов |
|
||||
| [desktop](components/desktop) | `@coopenomics/desktop` | Рабочий стол кооператива (Vue 3 + Quasar) |
|
||||
| [factory](components/factory) | `@coopenomics/factory` | Генератор юридических документов |
|
||||
| [migrator](components/migrator) | `migrator` | Утилита миграции данных |
|
||||
| [notifications](components/notifications) | `@coopenomics/notifications` | Библиотека уведомлений на основе Novu |
|
||||
| [parser](components/parser) | `@coopenomics/parser` | Индексатор блокчейна через State History Plugin |
|
||||
| [sdk](components/sdk) | `@coopenomics/sdk` | TypeScript SDK для GraphQL API |
|
||||
| [setup](components/setup) | `@coopenomics/setup` | Мастер первоначальной настройки |
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
### Предварительные требования
|
||||
|
||||
- Node.js >= 20
|
||||
- pnpm 9
|
||||
- Docker и Docker Compose
|
||||
- [WeasyPrint](https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#installation) (для генерации PDF)
|
||||
|
||||
### Установка
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
Также для работы потребуется установленная фабрика документов `weasyprint` для вашей операционной системы: <https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#installation>
|
||||
### Конфигурация
|
||||
|
||||
## Конфигурация
|
||||
|
||||
Для конфигурации потребуетcя установленный `docker` и `docker-compose`. Выполните команду и следуйте инструкциям:
|
||||
|
||||
```sh
|
||||
```bash
|
||||
pnpm run setup
|
||||
```
|
||||
|
||||
## Запуск
|
||||
Интерактивный мастер создаст необходимые `.env` файлы для всех компонентов.
|
||||
|
||||
### Инфраструктура
|
||||
### Запуск инфраструктуры
|
||||
|
||||
Для работы системы необходимы базы `mongo`, `postgres` и `redis`.
|
||||
|
||||
Команда запуска баз данных в контейнерах для работы в фоновом режиме:
|
||||
|
||||
```sh
|
||||
```bash
|
||||
docker compose up -d
|
||||
pnpm run reboot
|
||||
```
|
||||
|
||||
### Блокчейн и контракты
|
||||
## Разработка
|
||||
|
||||
```sh
|
||||
pnpm boot
|
||||
```
|
||||
### Бэкенд (controller + parser)
|
||||
|
||||
Команда запустит блокчейн и установит все смарт-контракты, подготовив систему к локальной разработке или применению.
|
||||
|
||||
### Бэкенд
|
||||
|
||||
```sh
|
||||
```bash
|
||||
pnpm run dev:backend
|
||||
```
|
||||
|
||||
Команда запустит набор сервисов бэкенда и точку доступа GraphQL-API на 2998 порту.
|
||||
### Фронтенд (desktop)
|
||||
|
||||
### Фронтенд
|
||||
|
||||
Команда запустит рабочий стол кооператива на 3005 порту:
|
||||
|
||||
```sh
|
||||
```bash
|
||||
pnpm run dev:desktop
|
||||
```
|
||||
|
||||
### Очистка базы
|
||||
### Библиотеки (factory + cooptypes)
|
||||
|
||||
Команда очистит базу mongodb и начнет производство блоков с начала:
|
||||
|
||||
```sh
|
||||
pnpm run clear
|
||||
```bash
|
||||
pnpm run dev:lib
|
||||
```
|
||||
|
||||
Правильная последовательность команд при переустановке проекта "с нуля":
|
||||
### Все сервисы одновременно
|
||||
|
||||
```sh
|
||||
git pull
|
||||
pnpm install
|
||||
pnpm run setup
|
||||
pnpm run clear
|
||||
pnpm run boot
|
||||
pnpm run dev:backend
|
||||
pnpm run dev:desktop
|
||||
```bash
|
||||
pnpm run dev:all
|
||||
```
|
||||
|
||||
### Кошелёк
|
||||
Для работы с кошельком блокчейна используется утилита cleos, которая позволяет извлекать таблицы смарт-контрактов и отправлять транзакции в блокчейн если это требуется.
|
||||
> **Примечание:** установка пакетов производится только через фильтр: `pnpm add <пакет> --filter <компонент>`
|
||||
|
||||
Для работы с командным кошельком `cleos` локально необходимо чтобы инфраструктура была поднята:
|
||||
## Тестирование
|
||||
|
||||
```
|
||||
docker compose up -d
|
||||
```bash
|
||||
# Все тесты
|
||||
pnpm run test
|
||||
|
||||
# Юнит-тесты (cooptypes, parser, notifications)
|
||||
pnpm run test:unit
|
||||
|
||||
# Компонентные тесты (factory)
|
||||
pnpm run test:component
|
||||
|
||||
# Интеграционные тесты (boot + blockchain)
|
||||
pnpm run test:integration
|
||||
```
|
||||
|
||||
Запускаем контейнер с кошельком и входим в него командой:
|
||||
## Сборка
|
||||
|
||||
```
|
||||
pnpm run cleos
|
||||
```
|
||||
```bash
|
||||
# Библиотеки (cooptypes, factory)
|
||||
pnpm run build:lib
|
||||
|
||||
В кошельке уже находится дефолтный ключ для работы с локальной средой. Этот ключ зашифрован паролем. Пароль хранится в файле `password` и используется автоматически при разблокировке. Для разблокировки кошелька, находясь в контейнере выполняем:
|
||||
# Смарт-контракты
|
||||
pnpm run build:contracts:all
|
||||
|
||||
# Desktop (SSR)
|
||||
pnpm --filter @coopenomics/desktop run build
|
||||
```
|
||||
./unlock.sh
|
||||
```
|
||||
|
||||
Для использования кошелька выполняем:
|
||||
```
|
||||
./cleos.sh <command>
|
||||
```
|
||||
|
||||
Для получения полного списка команд вызываем кошелек без команд:
|
||||
```
|
||||
./cleos.sh
|
||||
```
|
||||
|
||||
Например, для получения информации о текущем состоянии блокчейна:
|
||||
```
|
||||
./cleos.sh get info
|
||||
```
|
||||
|
||||
### Возможные проблемы
|
||||
В случае, если при выполнении ./unlock.sh возникает проблема:
|
||||
`Unable to connect to keosd, if keosd is running please kill the process and try again`, то выполните команду:
|
||||
|
||||
```
|
||||
./reset.sh
|
||||
```
|
||||
|
||||
Данная команда удалит созданный ранее сокет кошелька, после чего, выполните ./unlock.sh повторно.
|
||||
|
||||
## Лицензия
|
||||
|
||||
Продукт Потребительского Кооператива "ВОСХОД" распространяется по лицензии BY-NC-SA 4.0.
|
||||
Продукт Потребительского Кооператива «ВОСХОД» распространяется по лицензии [BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode.ru).
|
||||
|
||||
Разрешено делиться, копировать и распространять материал на любом носителе и форме, адаптировать, делать ремиксы, видоизменять и создавать новое, опираясь на этот материал. При использовании, Вы должны обеспечить указание авторства, предоставить ссылку, и обозначить изменения, если таковые были сделаны. Если вы перерабатываете, преобразовываете материал или берёте его за основу для производного произведения, вы должны распространять переделанные вами части материала на условиях той же лицензии , в соответствии с которой распространяется оригинал. Запрещено коммерческое использование материала. Использование в коммерческих целях – это использование, в первую очередь направленное на получение коммерческого преимущества или денежного вознаграждения.
|
||||
|
||||
Юридический текст лицензии: <https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode.ru>
|
||||
Разрешено делиться, копировать и распространять материал, адаптировать и создавать производные произведения при условии указания авторства и сохранения той же лицензии. Коммерческое использование запрещено.
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
# TASKS.md — Прогресс выполнения задач
|
||||
|
||||
## Завершённые задачи
|
||||
- ✅ 1. Dev-окружение
|
||||
- ✅ 2. Security updates
|
||||
- ✅ 3. Unified test pipeline (162/163)
|
||||
- ✅ 4. README + описания компонентов
|
||||
- ✅ 5. AGENTS.md для всех компонентов
|
||||
- ✅ 6. Setup — профессиональный установщик
|
||||
- ✅ 7. Поисковая система документов (OpenSearch)
|
||||
- ✅ 8. Процессы (Capital extension) — бэкенд + фронтенд
|
||||
- ✅ 9. Отчёты ФНС — 8 генераторов, фабрика, GraphQL API
|
||||
|
||||
---
|
||||
|
||||
## Активные задачи
|
||||
|
||||
### 10. Генерация отчётов ФНС (доработка) ✅
|
||||
- [x] Фабрика генераторов (ReportRegistryService)
|
||||
- [x] 8 генераторов (Бухбаланс, 6-НДФЛ, РСВ, ПСВ, ДУСН, 4-ФСС, Увед. взносы, УУСН)
|
||||
- [x] GraphQL API (getAvailableReports, generateReport)
|
||||
- [x] Генераторы переписаны по XSD — структура соответствует схемам ФНС
|
||||
- [x] 48 unit-тестов для всех генераторов
|
||||
- [x] Desktop UI (страница отчётов) — расширение reports
|
||||
- [x] Интеграция с реальными данными ledger через LedgerInteractor
|
||||
- [x] OrganizationDataInput DTO для передачи данных организации
|
||||
|
||||
### 11. CASL + гранулированные права доступа + шаринг страниц
|
||||
|
||||
#### 11.1 CASL — система прав доступа ✅
|
||||
- [x] @casl/ability установлен
|
||||
- [x] Action enum: manage, read, create, update, delete, execute, share
|
||||
- [x] Subject enum: 30+ subjects для всех ресурсов
|
||||
- [x] CaslAbilityFactory: role-based + granular permissions
|
||||
- [x] CaslGuard + @CheckAbility decorator (обратная совместимость с RolesGuard)
|
||||
- [x] role в user сохранено (chairman, member, user)
|
||||
- [x] Тесты: 22/22 unit-тестов (chairman, member, user, granular permissions)
|
||||
|
||||
#### 11.2 Шаринг страниц ✅
|
||||
- [x] ShareTokenEntity с JWT, guest/member targets
|
||||
- [x] ShareService: create/verify/revoke
|
||||
- [x] GraphQL: createShareLink, revokeShareLink, getMyShareLinks, getSharedWithMe
|
||||
- [x] Два уровня: гости (linkName) и пайщики (targetUsername)
|
||||
- [x] Granular allowedActions per share link
|
||||
- [x] Кнопка "Поделиться" в Desktop Header (ShareHeaderAction)
|
||||
- [x] Диалог управления правами (ShareDialog)
|
||||
- [x] Страница "Доступные мне" на рабочем столе Пайщика (SharedWithMePage)
|
||||
|
||||
#### 11.3 Интеграция в desktop ✅
|
||||
- [x] useShareButtonProcess: авто-регистрация на всех страницах
|
||||
- [x] ShareButton + ShareDialog компоненты
|
||||
- [x] Интеграция в init-app process
|
||||
- [x] Валидация share tokens в route guard (frontend) — bypass ролей при наличии share_token
|
||||
|
||||
### 12. API ключи кооператива ✅
|
||||
- [x] ApiKeyEntity: хеш ключа (sha256), префикс, операции, срок
|
||||
- [x] ApiKeyService: create, validate, list, revoke
|
||||
- [x] ApiKeyGuard: аутентификация через x-api-key header
|
||||
- [x] GraphQL: createApiKey, getApiKeys, revokeApiKey (chairman only)
|
||||
- [x] Безопасность: ключ показывается ТОЛЬКО при создании, хранится хеш
|
||||
- [x] Desktop: ApiKeysPage в chairman extension
|
||||
- [x] UI: таблица ключей, создание, отзыв, копирование
|
||||
|
||||
### 15. Стол заказов (Marketplace) — полная реализация
|
||||
|
||||
#### 15.1 Смарт-контракт marketplace — реорганизация
|
||||
- [x] Исправить процесс: одно заявление в совет при accept (authcontrib), не два
|
||||
- [x] Новый action requestreturn — заявление на возврат подаётся перед получением
|
||||
- [x] authcontrib сразу ставит authorized
|
||||
- [x] authreturn работает для статуса reqreturn → retauthorized
|
||||
- [x] receive требует retauthorized (после авторизации возврата)
|
||||
- [x] orderoffer убран product_return_statement (подаётся позже)
|
||||
- [x] Путь coopstock — имущество уже в кооперативе
|
||||
- [x] acceptstock — заказчик принимает coopstock (сразу в requestreturn)
|
||||
- [x] Document::remove_document утилита
|
||||
- [x] Уничтожение просроченного (destroy) — возврат средств, штраф, выплата поставщику
|
||||
- [x] Перепредложение (reoffer) — закрытие старой заявки, создание coopstock с новой ценой
|
||||
- [x] Система перевозок — уже реализована (shipment/)
|
||||
- [x] Гарантийный возврат — уже реализован (dispute_on_offer/)
|
||||
- [x] Сборка marketplace.wasm + marketplace.abi (test mode) — OK
|
||||
- [x] ORDER→OFFER: createorder + respondoffer (заказчик публикует → поставщики откликаются)
|
||||
- [x] delivery_type: internal (между КУ) / external (СДЭК и т.д.) в request struct
|
||||
- [x] contribution_type: share (паевой) / member (членский) в request struct
|
||||
- [x] Убраны ВСЕ any из controller marketplace (service, interactor)
|
||||
- [x] Импортировано 87 файлов marketplace extension из ветки marketplace
|
||||
- [x] Domain: 11 entities, 7 repositories, 5 services (БД-first архитектура)
|
||||
- [x] Application: 30+ DTOs, 4 resolvers (категории, атрибуты, заявки)
|
||||
- [x] Infrastructure: TypeORM entities, mappers, adapters
|
||||
- [x] Архитектурный принцип: заявки в БД (draft→published), блокчейн при match
|
||||
- [x] Накопительный характер заявок — CycleService (canStartSupply, checkExpiredCycles, cron)
|
||||
|
||||
#### 15.2 Генерация типов
|
||||
- [x] Интерфейсы cooptypes: RequestReturn, Coopstock, AcceptStock, Destroy, Reoffer
|
||||
- [x] ABI marketplace сохранён (31 action) для генерации типов
|
||||
|
||||
#### 15.3 Controller (бэкенд)
|
||||
- [x] CooplaceBlockchainPort: 5 новых портов
|
||||
- [x] CooplaceBlockchainAdapter: 5 реализаций (reqReturn, coopstock, acceptStock, destroy, reoffer)
|
||||
- [x] DTOs: ReqReturnInput, CoopstockInput, AcceptStockInput, DestroyRequestInput, ReofferRequestInput
|
||||
- [x] GraphQL resolvers: 5 новых мутаций с авторизацией
|
||||
- [x] Service + Interactor: проброс до blockchain
|
||||
- [x] ShipmentResolver: createShipment, signByDriver, arrived, receiveShipment
|
||||
- [x] Интеграция с документами — через SignedDigitalDocumentInputDTO
|
||||
- [x] MarketplaceEventService: 13 event handlers для синхронизации с блокчейном
|
||||
|
||||
#### 15.4 Desktop (фронтенд)
|
||||
- [x] Маршруты: витрина, предложение, создание, мои заказы, модерация
|
||||
- [x] install.ts расширения market обновлён с agreementsBase и roles
|
||||
- [x] ReqReturnStep — шаг подачи заявления на возврат (step 7)
|
||||
- [x] RetAuthorizedStep — шаг авторизации возврата советом (step 8)
|
||||
- [x] Base.vue: новые статусы (reqreturn, retauthorized), badge coopstock
|
||||
- [x] Панель председателя КУ: WarehousePage (склад, выдача, destroy/reoffer)
|
||||
- [x] ShipmentsPage: таблица перевозок, timeline 4 этапов, создание
|
||||
- [x] DisputePage: подача претензии, список, timeline спора, решение совета
|
||||
|
||||
#### 15.X Разделение cooplace на бэкенд и фронтенд расширения
|
||||
- [x] CooplaceExtensionModule — бэкенд (карточки, категории, blockchain actions)
|
||||
- [x] ProductCardResolver: CRUD карточек (draft→published→archived)
|
||||
- [x] CategoryResolver: CRUD дерева категорий (chairman)
|
||||
- [x] Domain entities: ProductCard (тип, статус, delivery_type, contribution_type), Category, SupplyOrder
|
||||
- [x] Repositories: interfaces для карточек, категорий, заявок
|
||||
- [x] GraphQL: 3 queries + 6 mutations для карточек/категорий
|
||||
- [x] marketplace (desktop) — чистый фронтенд, использует API cooplace
|
||||
- [x] market-admin (desktop) — уже существует (extensions/market-admin)
|
||||
- [x] Controller стартует, 22+9 = 31 GraphQL endpoints
|
||||
|
||||
#### 15.Y Бэкенд карточек с циклами
|
||||
- [x] TypeORM entities: ProductCard (циклы), Category, SupplyOrder
|
||||
- [x] Repository adapters: CRUD + фильтры для всех entities
|
||||
- [x] ProductCardService: модерация (draft→moderation→published), циклы (min_units, deadline)
|
||||
- [x] addOrderToCard: инкремент cycle_collected_units, проверка min_units
|
||||
- [x] checkCycleDeadline: истечение → возврат → новый цикл (cycle_number++)
|
||||
- [x] ProductCardResolver: 9 queries/mutations с реальной БД логикой
|
||||
- [x] CooplaceExtensionModule: полная DI с TypeORM
|
||||
- [x] Исправлен белый экран desktop (vite-plugin-checker + share-button crash)
|
||||
|
||||
#### 15.Z Админка маркетплейса (настройки)
|
||||
- [x] MarketplaceSettingsEntity: lead_request_policy, publish_access_policy, whitelist, moderation, cycles, delivery, prices
|
||||
- [x] MarketplaceSettingsTypeormEntity + TypeORM repository (upsert)
|
||||
- [x] MarketplaceSettingsResolver: get/update settings, add/remove whitelist
|
||||
- [x] Перенесены ВСЕ DTOs/entities/repositories/services из extensions/marketplace → extensions/cooplace
|
||||
- [x] Desktop SettingsPage: radio groups, toggles, whitelist chips, price inputs
|
||||
- [x] market-admin: маршрут настроек как дефолтный + модерация + все заказы
|
||||
|
||||
#### 15.W Правильная логика match + cycles
|
||||
- [x] MatchService: каждая встречная заявка → сразу в блокчейн (блокировка средств смарт-контрактом)
|
||||
- [x] CycleService: min_units/deadline → порог для supply, НЕ для match
|
||||
- [x] CycleService: cron каждые 5 мин — проверка истёкших циклов → cancel через блокчейн
|
||||
- [x] MARKET-LOGIC.md обновлён с правильным принципом
|
||||
- [x] AGENTS.md обновлён с описанием маркетплейса
|
||||
|
||||
#### 15.5 Тесты
|
||||
- [x] Unit-тесты controller: 8 тестов marketplace actions + statuses (90/90 total)
|
||||
- [x] cooptypes собран с новыми actions
|
||||
- [x] Boot интеграционный тест: marketplace.test.ts (orderoffer + coopstock flows)
|
||||
- [x] Boot тест marketplace.test.ts: orderoffer + coopstock flows
|
||||
|
||||
### 16. Документация (docs)
|
||||
- [x] Раздел Cooplace: обзор архитектуры, жизненный цикл карточки, match, циклы
|
||||
- [x] Раздел Cooplace/dev: GraphQL API reference (настройки, карточки, поставки, перевозки, диспуты)
|
||||
- [x] Раздел Marketplace: пользовательская документация (витрина, заказ, администрирование)
|
||||
- [x] TypeDoc JSON сгенерирован из SDK (137MB)
|
||||
- [x] mkdocs.yml nav обновлён с «Стол заказов»
|
||||
- [x] .gitignore для typedoc.json (генерируется при сборке)
|
||||
|
||||
### 13. GraphQL Subscriptions ✅
|
||||
- [x] PubSubModule: глобальный PubSub provider
|
||||
- [x] WebSocket support в GraphQL module (graphql-ws)
|
||||
- [x] 5 Capital subscriptions: issueUpdated/Created, commitCreated/Updated, dataChanged
|
||||
- [x] systemStatusChanged — подписка на статус системы (install/init/update)
|
||||
- [x] sovietDataChanged — подписка на события собраний/решений
|
||||
- [x] Events publishing: GenerationService, ProjectManagementService, VotingService, SystemService, MeetEventService
|
||||
- [x] SDK: Subscriptions namespace (Capital)
|
||||
- [x] Auth через connectionParams.token
|
||||
- [x] `useGraphqlSubscription` composable на фронтенде
|
||||
- [x] **7 страниц Capital** переведены с polling на подписки
|
||||
- [x] **System store** — WebSocket мониторинг вместо setTimeout
|
||||
- [x] **init-wallet** — удалён рекурсивный setTimeout(run, 10_000)
|
||||
- [x] **MeetDetails, ListOfAgenda, WaitingRegistration** — подписки вместо setInterval
|
||||
- [x] **Provider, ConnectionAgreement** — удалён setInterval polling
|
||||
- [x] 0 использований setInterval в прикладном коде (только UI-анимация энергии)
|
||||
@@ -0,0 +1,64 @@
|
||||
# Test Plan — pnpm run test
|
||||
|
||||
## Архитектура
|
||||
```
|
||||
pnpm run test
|
||||
├── cooptypes — vitest run (smoke tests exports) ✅ 4/4
|
||||
├── parser — vitest run (config smoke tests) ✅ 3/3
|
||||
├── factory — vitest run (document generation tests) 🔧 17/85 → need mocks
|
||||
├── sdk — vitest run (API integration tests) 🔧 TODO
|
||||
├── notifications — vitest run (workflow tests) 🔧 TODO
|
||||
├── controller — jest / vitest (NestJS unit tests) 🔧 TODO
|
||||
└── boot — vitest run (blockchain integration) 🔧 53/60
|
||||
```
|
||||
|
||||
## Статус по компонентам
|
||||
|
||||
### cooptypes ✅ DONE
|
||||
- 4 smoke-теста экспортов
|
||||
- Не требует инфраструктуры
|
||||
|
||||
### parser ✅ DONE
|
||||
- 3 smoke-теста конфигурации
|
||||
- Не требует инфраструктуры
|
||||
|
||||
### factory 🔧 IN PROGRESS
|
||||
- **Проблема**: тесты обращаются к parser API (`SIMPLE_EXPLORER_API`) для get-tables/get-actions
|
||||
- **Решение**: Уже есть мок-система в `src/Utils/testMocks.ts` + `matchMock.ts`
|
||||
- **Нужно**: Добавить моки для ВСЕХ документов:
|
||||
- [ ] cooperative data mock (registrator.coops table)
|
||||
- [ ] soviet boards mock — уже есть через test setup в MongoDB
|
||||
- [ ] draft templates mock (draft.drafts + draft.translations tables)
|
||||
- [ ] decision data mocks (soviet.decisions table)
|
||||
- [ ] Мок для ReturnByMoney документов
|
||||
- [ ] Все документы 1000+ серии
|
||||
- **Текущие рабочие моки**: meet tables, votefor actions, returnByMoneyDecision actions
|
||||
- **После мокирования**: все 85 тестов должны проходить
|
||||
|
||||
### boot 🔧 NEEDS REBOOT
|
||||
- capital.test — 53/60 тестов проходят (после чистого reboot)
|
||||
- wallet.test — нужен полный boot с agreements
|
||||
- registrator.test — нужен полный boot
|
||||
- capital-import.test — отдельный тест импорта
|
||||
- **Требует**: pnpm run reboot перед запуском
|
||||
|
||||
### sdk 🔧 TODO
|
||||
- 1 тест файл с login + fetch extensions
|
||||
- Требует запущенный controller
|
||||
- Нужно: обновить chain_id, api_url, credentials
|
||||
|
||||
### controller 🔧 TODO
|
||||
- Все старые тесты удалены (устаревшие)
|
||||
- NestJS-приложение — нужны тесты через @nestjs/testing
|
||||
- Минимум: unit-тесты domain-логики, smoke-тест GraphQL API
|
||||
|
||||
### notifications 🔧 TODO
|
||||
- Нет тестов
|
||||
- Нужно: smoke-тесты workflow builder
|
||||
|
||||
### desktop — SKIP (нет тестов, UI-тестирование)
|
||||
|
||||
## Root script требования
|
||||
- `pnpm run test` — запускает ВСЕ тесты
|
||||
- fail-fast: если один пакет падает — весь pipeline падает
|
||||
- Последовательный запуск (не параллельный)
|
||||
@@ -8,3 +8,8 @@ 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
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
// Enable the ESlint flat config support
|
||||
"prettier.enable": false,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnSave": false,
|
||||
// Auto fix
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
# @coopenomics/boot
|
||||
|
||||
## Назначение
|
||||
|
||||
CLI-утилита для инициализации блокчейна EOSIO и развёртывания кооператива. Выполняет полный цикл: запуск ноды → создание аккаунтов → активация фич → деплой контрактов → выпуск токенов → загрузка шаблонов → создание кооператива и совета.
|
||||
|
||||
## Структура
|
||||
|
||||
```
|
||||
src/
|
||||
├── index.ts — CLI точка входа (commander): boot, boot:clean, boot:extra,
|
||||
│ start, stop, clear, cleos, deploy, create-coop, unlock
|
||||
├── init/
|
||||
│ ├── booter.ts — Главные сценарии: boot(), bootClean(), bootExtra()
|
||||
│ ├── infra.ts — startInfra() — базовая инфраструктура блокчейна
|
||||
│ │ installInitialData() — начальные данные (пользователи, совет)
|
||||
│ │ installExtraData() — расширенные данные
|
||||
│ └── cooperative.ts — CooperativeClass — создание программ ЦПП, регистрация
|
||||
│ └── participant.ts — Логика регистрации пайщика
|
||||
├── blockchain/
|
||||
│ └── index.ts — Класс Blockchain — обёртка над eosjs для работы с EOSIO
|
||||
│ (создание аккаунтов, деплой контрактов, транзакции, токены)
|
||||
├── configs/
|
||||
│ ├── index.ts — Главная конфигурация (аккаунты, ключи, токен, эмиссия)
|
||||
│ ├── networks.ts — Настройки сети (протокол, хост, порт)
|
||||
│ ├── contracts.ts — Список контрактов и путей к их WASM/ABI файлам
|
||||
│ └── protocol_features/ — JSON-файлы фич для активации на блокчейне
|
||||
├── docker/ — Управление Docker-контейнером ноды блокчейна
|
||||
│ ├── container.ts, run.ts, stop.ts, find.ts, exec.ts, deploy.ts
|
||||
│ ├── health.ts — Проверка готовности ноды
|
||||
│ └── purge.ts — Очистка данных блокчейна и БД
|
||||
├── *-init.ts — Инициализация реляционной БД (пользователи, vault, статус, extensions)
|
||||
├── tests/ — Интеграционные тесты
|
||||
│ ├── capital.test.ts — Основной тест (полный цикл ЦПП)
|
||||
│ ├── wallet.test.ts — Тесты кошелька
|
||||
│ ├── registrator.test.ts — Тесты регистрации
|
||||
│ └── capital/ — Вспомогательные функции для тестов капитала
|
||||
└── utils/ — Утилиты (sleep, randomHash, randomUsername, и т.д.)
|
||||
```
|
||||
|
||||
## Порядок загрузки (boot flow)
|
||||
|
||||
1. **startInfra()** — основа:
|
||||
- Инициализация `Blockchain` с ключами
|
||||
- Создание системных аккаунтов (eosio.token, registrator, soviet, и т.д.)
|
||||
- Активация фич протокола (PREACTIVATE_FEATURE → остальные)
|
||||
- Деплой смарт-контрактов (eosio.boot → eosio.system → все остальные)
|
||||
- Создание и выпуск токенов
|
||||
- Загрузка шаблонов документов из `@coopenomics/factory` Registry
|
||||
- Настройка ресурсов (powerup)
|
||||
|
||||
2. **installInitialData()** — данные кооператива:
|
||||
- Сохранение организации, платёжных методов, физлиц в MongoDB
|
||||
- Сохранение переменных кооператива (vars)
|
||||
- Регистрация пользователей в блокчейне
|
||||
- Инициализация реляционной БД (пользователи, vault)
|
||||
- Создание совета (board)
|
||||
- Создание программ ЦПП (Благорост, маркетплейс)
|
||||
|
||||
3. **bootClean()** — минимальная загрузка (только инфра + программы, без данных)
|
||||
|
||||
4. **bootExtra()** — расширенная загрузка (5 членов совета, дополнительные пайщики)
|
||||
|
||||
## Режимы контрактов
|
||||
|
||||
- **test** — контракты собранные с флагом `test` (позволяют boot с 1 членом совета)
|
||||
- **production** — стандартные контракты (требуют кворум)
|
||||
|
||||
Пути к WASM/ABI файлам указаны в `configs/contracts.ts`, относительно `../contracts/build/contracts/`.
|
||||
|
||||
## Тесты
|
||||
|
||||
- Фреймворк: **Vitest**
|
||||
- Основной тест: `src/tests/capital.test.ts` — полный цикл ЦПП (создание программ, проектов, инвестирование, голосование, конвертация)
|
||||
- Требует: полностью загруженный блокчейн (`pnpm run reboot`), MongoDB, реляционную БД
|
||||
|
||||
### Запуск
|
||||
|
||||
```
|
||||
pnpm --filter @coopenomics/boot test
|
||||
```
|
||||
|
||||
### Важно: Duplicate transaction
|
||||
|
||||
EOSIO отклоняет транзакции с одинаковым хешем (TAPOS block + action data). При повторных вызовах одинаковых действий нужен `await sleep(500)` между ними.
|
||||
|
||||
## Скрипты package.json
|
||||
|
||||
| Скрипт | Описание |
|
||||
|--------|----------|
|
||||
| `boot` | Полная загрузка: инфра + данные + кооператив |
|
||||
| `boot:clean` | Чистая загрузка: только инфра + программы |
|
||||
| `boot:extra` | Расширенная загрузка: инфра + 5 членов совета |
|
||||
| `create-coop` | Создание кооператива после boot:clean |
|
||||
| `test` | Vitest (capital.test.ts, timeout 240s) |
|
||||
| `test:all` | Все тесты |
|
||||
| `clear` | Очистка блокчейна и перезапуск ноды |
|
||||
|
||||
## Зависимости от других компонентов
|
||||
|
||||
- `cooptypes` (workspace) — типы контрактов
|
||||
- `@coopenomics/factory` (workspace) — реестр шаблонов, сохранение данных в MongoDB
|
||||
+100
-1
@@ -1 +1,100 @@
|
||||
# Загрузчик.
|
||||
# 🚀 @coopenomics/boot
|
||||
|
||||
CLI-утилита для инициализации блокчейн-инфраструктуры кооператива. Создаёт системные аккаунты, устанавливает смарт-контракты, настраивает токены и разворачивает кооперативную среду. Включает интеграционные тесты для основных подсистем платформы.
|
||||
|
||||
## Основные возможности
|
||||
|
||||
- Полный цикл развёртывания блокчейна EOSIO — от запуска ноды до создания кооператива
|
||||
- Установка системных и прикладных смарт-контрактов
|
||||
- Инициализация токенов и создание тестовых данных
|
||||
- Управление Docker-контейнерами блокчейн-ноды
|
||||
- Интеграционные тесты: паевые взносы (capital), кошелёк (wallet), регистрация участников (registrator)
|
||||
- Поддержка нескольких режимов загрузки: стандартный, чистый и расширенный
|
||||
|
||||
## Установка
|
||||
|
||||
Компонент является частью монорепозитория. Установка зависимостей из корня проекта:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
Или только для этого компонента:
|
||||
|
||||
```bash
|
||||
pnpm install --filter @coopenomics/boot
|
||||
```
|
||||
|
||||
## Скрипты
|
||||
|
||||
| Скрипт | Команда | Описание |
|
||||
|--------|---------|----------|
|
||||
| `boot` | `pnpm run boot` | Запуск блокчейна и установка контрактов |
|
||||
| `boot:clean` | `pnpm run boot:clean` | Чистый запуск с полной переустановкой |
|
||||
| `boot:extra` | `pnpm run boot:extra` | Расширенная инициализация с дополнительными данными |
|
||||
| `deploy` | `pnpm run deploy` | Развёртывание контрактов на запущенной ноде |
|
||||
| `cli` | `pnpm run cli` | Интерактивный командный интерфейс (Commander.js) |
|
||||
| `clear` | `pnpm run clear` | Очистка данных блокчейна |
|
||||
| `start` | `pnpm run start` | Перезапуск ноды |
|
||||
| `stop` | `pnpm run stop` | Остановка ноды |
|
||||
| `test` | `pnpm run test` | Интеграционные тесты (capital) |
|
||||
| `test:all` | `pnpm run test:all` | Все интеграционные тесты |
|
||||
|
||||
## Конфигурация
|
||||
|
||||
Скопируйте `.env-example` в `.env` и настройте переменные окружения:
|
||||
|
||||
- Endpoint блокчейн-ноды (API)
|
||||
- Строка подключения к MongoDB
|
||||
- Строка подключения к реляционной БД
|
||||
- Приватные ключи для развёртывания контрактов
|
||||
|
||||
Подробное описание переменных — в файле `.env-example`.
|
||||
|
||||
## Тестирование
|
||||
|
||||
Перед запуском тестов необходима работающая инфраструктура (см. `docker-compose.yaml` в корне проекта):
|
||||
|
||||
```bash
|
||||
pnpm --filter @coopenomics/boot run test
|
||||
pnpm --filter @coopenomics/boot run test:all
|
||||
```
|
||||
|
||||
Тесты используют **Vitest** с таймаутом 240 секунд — это связано с ожиданием подтверждения блокчейн-транзакций.
|
||||
|
||||
## Архитектура
|
||||
|
||||
```
|
||||
src/
|
||||
├── index.ts # Точка входа CLI (Commander.js)
|
||||
├── init/ # Логика инициализации
|
||||
│ ├── booter.ts # Загрузчик блокчейна
|
||||
│ ├── cooperative.ts # Настройка кооператива
|
||||
│ ├── infra.ts # Инфраструктурная инициализация
|
||||
│ └── participant.ts # Создание участников
|
||||
├── blockchain/ # Взаимодействие с EOSIO (eosjs)
|
||||
├── docker/ # Управление Docker-контейнерами
|
||||
├── configs/ # Конфигурация ноды и протокол-фичи
|
||||
├── tests/ # Интеграционные тесты
|
||||
│ ├── capital.test.ts # Тесты паевых взносов
|
||||
│ ├── wallet.test.ts # Тесты кошелька
|
||||
│ └── registrator.test.ts # Тесты регистрации
|
||||
└── utils/ # Вспомогательные утилиты
|
||||
scripts/
|
||||
├── reboot.sh # Полный перезапуск
|
||||
├── clean_reboot.sh # Чистый перезапуск
|
||||
└── extra_reboot.sh # Расширенный перезапуск
|
||||
```
|
||||
|
||||
## Ключевые зависимости
|
||||
|
||||
- **eosjs** — взаимодействие с блокчейном EOSIO
|
||||
- **commander** — CLI-фреймворк
|
||||
- **dockerode** — управление Docker-контейнерами из Node.js
|
||||
- **mongoose** — работа с MongoDB
|
||||
- **pg** — работа с реляционной БД
|
||||
- **vitest** — тестирование
|
||||
|
||||
## Лицензия
|
||||
|
||||
[BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode.ru)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "@coopenomics/boot",
|
||||
"type": "module",
|
||||
"version": "2025.10.16",
|
||||
"version": "2026.2.22-2",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@9.0.6",
|
||||
"description": "",
|
||||
"description": "CLI-утилита инициализации блокчейна и кооператива",
|
||||
"author": "Alex Ant <dacom.dark.sun@gmail.com>",
|
||||
"license": "",
|
||||
"keywords": [],
|
||||
@@ -15,13 +15,21 @@
|
||||
"cleos": "esno src/index.ts cleos",
|
||||
"unlock": "esno src/index.ts unlock",
|
||||
"boot": "esno src/index.ts boot",
|
||||
"clear": "esno src/index.ts clear",
|
||||
"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",
|
||||
"start": "./scripts/restart.sh",
|
||||
"stop": "./scripts/stop.sh"
|
||||
"stop": "./scripts/stop.sh",
|
||||
"test": "vitest run src/tests/capital.test.ts --testTimeout=240000",
|
||||
"test:all": "vitest run --testTimeout=240000"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coopenomics/factory": "workspace:*",
|
||||
"axios": "^1.6.8",
|
||||
"@types/pg": "^8.16.0",
|
||||
"axios": "^1.13.5",
|
||||
"chai": "^5.1.2",
|
||||
"commander": "^12.1.0",
|
||||
"cooptypes": "workspace:*",
|
||||
@@ -32,7 +40,8 @@
|
||||
"eosjs-ecc": "^4.0.7",
|
||||
"execa": "^9.5.2",
|
||||
"mocha": "^10.7.3",
|
||||
"mongoose": "^8.10.0"
|
||||
"mongoose": "^9.2.2",
|
||||
"pg": "^8.16.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^2.18.0",
|
||||
@@ -51,4 +60,4 @@
|
||||
"vite": "^5.2.11",
|
||||
"vitest": "^1.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
#!/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 "Перезапуск завершен!"
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/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 готов!"
|
||||
Executable
+51
@@ -0,0 +1,51 @@
|
||||
#!/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 "Перезапуск завершен!"
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# Останавливаем и удаляем контейнеры вместе с volumes
|
||||
echo "Останавливаем и удаляем контейнеры с volumes..."
|
||||
docker compose down -v mongo postgres cooparser || true
|
||||
docker compose down -v mongo postgres opensearch cooparser || true
|
||||
|
||||
# Останавливаем blockchain контейнер перед удалением данных
|
||||
echo "Останавливаем blockchain контейнер..."
|
||||
@@ -15,7 +15,7 @@ sudo rm -rf ../blockchain-data/
|
||||
|
||||
# Пересоздаем и запускаем базы данных
|
||||
echo "Пересоздаем и запускаем базы данных..."
|
||||
docker compose up -d mongo postgres
|
||||
docker compose up -d mongo postgres opensearch
|
||||
|
||||
# Ждем готовности MongoDB
|
||||
echo "Ждем готовности MongoDB..."
|
||||
|
||||
@@ -44,8 +44,8 @@ export default class Blockchain {
|
||||
this.api = new Api({
|
||||
rpc,
|
||||
signatureProvider,
|
||||
textDecoder: new TextDecoder(),
|
||||
textEncoder: new TextEncoder(),
|
||||
textDecoder: new TextDecoder() as any,
|
||||
textEncoder: new TextEncoder() as any,
|
||||
})
|
||||
this.api.read = await EosApi({ httpEndpoint: res })
|
||||
}
|
||||
@@ -637,6 +637,66 @@ 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 })
|
||||
@@ -863,38 +923,6 @@ 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.coopname,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: {
|
||||
...params,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
console.log('Совет создан: ', params)
|
||||
}
|
||||
|
||||
async createDraft(params: DraftContract.Actions.CreateDraft.ICreateDraft) {
|
||||
await this.update_pass_instance()
|
||||
|
||||
@@ -989,6 +1017,37 @@ 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 - массив действий для транзакции
|
||||
|
||||
@@ -10,7 +10,7 @@ enable-stale-production = true
|
||||
read-only-read-window-time-us = 120000
|
||||
|
||||
net-threads = 2
|
||||
max-transaction-time=2000
|
||||
max-transaction-time=10000
|
||||
|
||||
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 = 5000
|
||||
max-block-net-usage-threshold-bytes = 1024
|
||||
max-block-cpu-usage-threshold-us = 200000
|
||||
max-block-net-usage-threshold-bytes = 800000
|
||||
|
||||
verbose-http-errors = true
|
||||
chain-state-history = true
|
||||
|
||||
@@ -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": 300000,
|
||||
"target_block_cpu_usage_pct": 500,
|
||||
"max_block_cpu_usage": 1000000,
|
||||
"target_block_cpu_usage_pct": 1000,
|
||||
"max_transaction_cpu_usage": 290000,
|
||||
"min_transaction_cpu_usage": 100,
|
||||
"max_transaction_lifetime": 3600,
|
||||
|
||||
@@ -53,7 +53,7 @@ export default {
|
||||
allocations: [
|
||||
{
|
||||
to: 'eosio',
|
||||
quantity: `10000.0000 ${SYMBOL}`,
|
||||
quantity: `100000.0000 ${SYMBOL}`,
|
||||
},
|
||||
],
|
||||
accounts: [
|
||||
@@ -135,9 +135,9 @@ export default {
|
||||
name: 'contributor',
|
||||
code_permissions_to: ['contributor'],
|
||||
},
|
||||
{
|
||||
name: provider_chairman,
|
||||
},
|
||||
// {
|
||||
// name: provider_chairman,
|
||||
// },
|
||||
{
|
||||
name: provider,
|
||||
code_permissions_to: ['registrator'],
|
||||
|
||||
@@ -3,6 +3,7 @@ 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)
|
||||
|
||||
@@ -37,6 +38,8 @@ export async function clearDB(): Promise<void> {
|
||||
catch (e) {
|
||||
console.error('Ошибка при удалении:', e)
|
||||
}
|
||||
|
||||
// PostgreSQL будет пересоздан полностью в скрипте extra_reboot.sh
|
||||
}
|
||||
|
||||
export async function clearDirectory(dirPath: string): Promise<void> {
|
||||
|
||||
@@ -6,7 +6,8 @@ import { config } from 'dotenv'
|
||||
import { execCommand } from './docker/exec'
|
||||
import { stopContainerByName } from './docker/stop'
|
||||
import { runContainer } from './docker/run'
|
||||
import { boot } from './init/booter'
|
||||
import { boot, bootClean, bootExtra } from './init/booter'
|
||||
import { startCoop } from './init/cooperative'
|
||||
import { sleep } from './utils'
|
||||
import { checkHealth } from './docker/health'
|
||||
import { clearDB, clearDirectory, deleteFile } from './docker/purge'
|
||||
@@ -126,7 +127,7 @@ program
|
||||
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
|
||||
@@ -136,7 +137,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)
|
||||
}
|
||||
@@ -146,6 +147,111 @@ 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,7 +1,35 @@
|
||||
import { startInfra } from './infra'
|
||||
import { startCoop } from './cooperative'
|
||||
import config from '../configs'
|
||||
import { initExtensionsInPostgres, initSystemStatus } from '../postgres-init'
|
||||
import { installExtraData, installInitialData, startInfra } from './infra'
|
||||
import { CooperativeClass, startCoop } from './cooperative'
|
||||
|
||||
export async function boot() {
|
||||
await startInfra()
|
||||
const blockchain = await startInfra()
|
||||
await installInitialData(blockchain, false) // Создать базовый совет
|
||||
|
||||
console.log('Инициализируем статус системы в PostgreSQL')
|
||||
await initSystemStatus()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -58,8 +58,8 @@ export class CooperativeClass {
|
||||
await this.blockchain.createProgram({
|
||||
coopname,
|
||||
username: coopname,
|
||||
type: 'source',
|
||||
title: 'Генерация',
|
||||
type: 'generator',
|
||||
title: 'Целевая потребительская программа "Генератор"',
|
||||
announce: '',
|
||||
description: '',
|
||||
preview: '',
|
||||
@@ -74,8 +74,8 @@ export class CooperativeClass {
|
||||
await this.blockchain.createProgram({
|
||||
coopname,
|
||||
username: coopname,
|
||||
type: 'capital',
|
||||
title: 'Благосостояние',
|
||||
type: 'blagorost',
|
||||
title: 'Целевая потребительская программа "Благорост"',
|
||||
announce: '',
|
||||
description: '',
|
||||
preview: '',
|
||||
@@ -202,18 +202,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,11 +3,12 @@ import axios from 'axios'
|
||||
import { Generator, Registry } from '@coopenomics/factory'
|
||||
import type { Cooperative } from 'cooptypes'
|
||||
import { DraftContract } from 'cooptypes'
|
||||
import mongoose from 'mongoose'
|
||||
import mongoose, { Types } 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() {
|
||||
@@ -145,10 +146,32 @@ 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: 'Иван',
|
||||
@@ -220,9 +243,9 @@ export async function startInfra() {
|
||||
// добавляем переменные кооператива
|
||||
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: 'Восход',
|
||||
@@ -250,13 +273,55 @@ export async function startInfra() {
|
||||
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,
|
||||
}
|
||||
|
||||
await generator.save('vars', vars)
|
||||
|
||||
// Сохраняем vars с указанием конкретного _id и _created_at
|
||||
// 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')
|
||||
@@ -281,17 +346,17 @@ export async function startInfra() {
|
||||
console.error('Ошибка при удалении:', e)
|
||||
}
|
||||
|
||||
// добавляем пользователя для подключений
|
||||
try {
|
||||
await mongoose.connection.collection('users').insertOne({
|
||||
email: 'ivanov@example.com',
|
||||
// Собираем пользователей для инициализации в PostgreSQL
|
||||
const usersToInit = [
|
||||
{
|
||||
username: 'ant',
|
||||
type: 'individual',
|
||||
email: 'ivanov@example.com',
|
||||
type: 'individual' as const,
|
||||
role: 'chairman',
|
||||
status: 'active',
|
||||
is_registered: true,
|
||||
})
|
||||
}
|
||||
catch (e) { console.log('user is exist') }
|
||||
},
|
||||
]
|
||||
|
||||
// имитируем установку
|
||||
try {
|
||||
@@ -312,27 +377,195 @@ export async function startInfra() {
|
||||
})
|
||||
}
|
||||
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(`Арендуем ресурсы провайдеру`)
|
||||
await blockchain.powerup({
|
||||
payer: 'eosio',
|
||||
receiver: config.provider,
|
||||
days: config.powerup.days,
|
||||
payment: `100.0000 ${config.token.symbol}`,
|
||||
transfer: true,
|
||||
})
|
||||
|
||||
await blockchain.transfer({
|
||||
from: 'eosio',
|
||||
to: config.provider,
|
||||
quantity: `1000.0000 ${config.token.symbol}`,
|
||||
memo: '',
|
||||
})
|
||||
|
||||
console.log('Базовая установка завершена')
|
||||
console.log('Начальные данные установлены')
|
||||
}
|
||||
|
||||
export async function installExtraData(blockchain: Blockchain) {
|
||||
// В расширенном режиме пайщики уже добавлены в installInitialData
|
||||
// Здесь можно добавить дополнительную логику инициализации если потребуется
|
||||
console.log('Дополнительная инициализация для расширенного режима выполнена')
|
||||
}
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
/* 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()
|
||||
}
|
||||
}
|
||||
@@ -188,7 +188,7 @@ describe('тест контракта CAPITAL', () => {
|
||||
fixed_membership_contribution: '0.0000 RUB',
|
||||
membership_percent_fee: '0',
|
||||
meta: '',
|
||||
type: 'source',
|
||||
type: 'generator',
|
||||
}
|
||||
|
||||
const result = await blockchain.api.transact(
|
||||
@@ -234,7 +234,7 @@ describe('тест контракта CAPITAL', () => {
|
||||
fixed_membership_contribution: '0.0000 RUB',
|
||||
membership_percent_fee: '0',
|
||||
meta: '',
|
||||
type: 'capital',
|
||||
type: 'blagorost',
|
||||
}
|
||||
|
||||
const result = await blockchain.api.transact(
|
||||
@@ -346,6 +346,10 @@ describe('тест контракта CAPITAL', () => {
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -63,6 +63,8 @@ export async function commitToResult(
|
||||
project_hash: projectHash,
|
||||
commit_hash: commitHash,
|
||||
creator_hours: spendHours,
|
||||
description: `Коммит ${commitHash}`,
|
||||
meta: `{"hours": ${spendHours}, "creator": "${creator}"}`,
|
||||
}
|
||||
|
||||
const createCommitResult = await blockchain.api.transact(
|
||||
|
||||
@@ -2,6 +2,6 @@ export const sourceProgramId = 3
|
||||
export const capitalProgramId = 4
|
||||
export const walletProgramId = 1
|
||||
export const ratePerHour = '1000.0000 RUB'
|
||||
export const sourceProgramName = 'source'
|
||||
export const capitalProgramName = 'capital'
|
||||
export const circulationAccountId = 80
|
||||
export const sourceProgramName = 'generator'
|
||||
export const capitalProgramName = 'blagorost'
|
||||
export const circulationAccountId = 80
|
||||
|
||||
@@ -4,9 +4,10 @@ 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 { sourceProgramId } from './consts'
|
||||
import { capitalProgramId, walletProgramId } from './consts'
|
||||
|
||||
export async function investInProject(
|
||||
blockchain: any,
|
||||
@@ -30,14 +31,16 @@ export async function investInProject(
|
||||
'sha256',
|
||||
))[0] || { invested: '0.0000 RUB', available: '0.0000 RUB' }
|
||||
|
||||
const prevUserWallet = await getUserProgramWallet(blockchain, coopname, investor, sourceProgramId) || { blocked: '0.0000 RUB' }
|
||||
const prevProgramWallet = await getCoopProgramWallet(blockchain, coopname, sourceProgramId) || { blocked: '0.0000 RUB', share_contributions: '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('▶ Проект:', prevProject)
|
||||
console.log('▶ Кошелек пользователя:', prevUserWallet)
|
||||
console.log('▶ Кошелек программы:', prevProgramWallet)
|
||||
|
||||
console.log('▶ Главный кошелек пользователя:', prevWalletWallet)
|
||||
console.log('▶ Кошелек пользователя (благорост):', prevUserWallet)
|
||||
console.log('▶ Кошелек программы (благорост):', prevProgramWallet)
|
||||
console.log('▶ Сумма инвестиции: ', investAmount)
|
||||
// Создание инвестиции
|
||||
const createInvestData: CapitalContract.Actions.CreateProjectInvest.ICreateInvest = {
|
||||
coopname,
|
||||
@@ -65,28 +68,28 @@ 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]
|
||||
// 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('🔍 Инвестиция в блокчейне:', blockchainInvest)
|
||||
// expect(blockchainInvest).toBeDefined()
|
||||
// expect(blockchainInvest.status).toBe('created')
|
||||
|
||||
// Утверждение инвестиции
|
||||
console.log(`\n✅ Подтверждение инвестиции ${investHash}`)
|
||||
const approveInvestResult = await processApprove(blockchain, coopname, investHash)
|
||||
// console.log(`\n✅ Подтверждение инвестиции ${investHash}`)
|
||||
// const approveInvestResult = await processApprove(blockchain, coopname, investHash)
|
||||
|
||||
// Проверка утвержденной инвестиции
|
||||
const blockchainEmptyInvest = (await blockchain.getTableRows(
|
||||
@@ -114,8 +117,8 @@ export async function investInProject(
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
const finalUserWallet = await getUserProgramWallet(blockchain, coopname, investor, sourceProgramId)
|
||||
const finalProgramWallet = await getCoopProgramWallet(blockchain, coopname, sourceProgramId)
|
||||
const finalUserWallet = await getUserProgramWallet(blockchain, coopname, investor, capitalProgramId)
|
||||
const finalProgramWallet = await getCoopProgramWallet(blockchain, coopname, capitalProgramId)
|
||||
|
||||
// Получение сегмента инвестора для данного проекта
|
||||
const segment = await getSegment(blockchain, coopname, projectHash, investor)
|
||||
@@ -123,8 +126,8 @@ export async function investInProject(
|
||||
console.log('\n📊 Балансы после инвестиции:')
|
||||
console.log('▶ Проект:', finalProject)
|
||||
console.log('▶ Сегмент:', segment)
|
||||
console.log('▶ Кошелек пользователя:', finalUserWallet)
|
||||
console.log('▶ Кошелек программы:', finalProgramWallet)
|
||||
console.log('▶ Кошелек пользователя (благорост):', finalUserWallet)
|
||||
console.log('▶ Кошелек программы (благорост):', finalProgramWallet)
|
||||
|
||||
// Проверка изменения балансов
|
||||
expect(parseFloat(finalUserWallet.blocked)).toBeCloseTo(parseFloat(prevUserWallet.blocked) + parseFloat(investAmount), 1)
|
||||
@@ -134,8 +137,8 @@ export async function investInProject(
|
||||
|
||||
return {
|
||||
investHash,
|
||||
invest: blockchainInvest,
|
||||
transactionId: approveInvestResult.transaction_id,
|
||||
// invest: blockchainInvest,
|
||||
transactionId: createInvestResult.transaction_id,
|
||||
prevProject,
|
||||
project: finalProject,
|
||||
segment,
|
||||
|
||||
@@ -8,6 +8,7 @@ export async function processApprove(blockchain: any, coopname: string, approval
|
||||
|
||||
const data: SovietContract.Actions.Approves.ConfirmApprove.IConfirmApprove = {
|
||||
coopname,
|
||||
username: 'ant',
|
||||
approval_hash: approvalHash,
|
||||
approved_document: fakeDocument,
|
||||
}
|
||||
|
||||
@@ -68,8 +68,8 @@ export async function processCompleteVoting(
|
||||
console.log('▶ Голосов получено:', projectAfter.voting.votes_received)
|
||||
console.log('▶ Всего участников голосования:', projectAfter.voting.total_voters)
|
||||
|
||||
// Проверяем что статус изменился на completed
|
||||
expect(projectAfter.status).toBe('completed')
|
||||
// Проверяем что статус изменился на result
|
||||
expect(projectAfter.status).toBe('result')
|
||||
|
||||
console.log(`\n✅ Голосование для проекта ${project_hash} успешно завершено!`)
|
||||
|
||||
|
||||
@@ -58,7 +58,6 @@ export async function processConvertSegment(
|
||||
convert_hash: convertHash,
|
||||
wallet_amount: walletAmount,
|
||||
capital_amount: capitalAmount,
|
||||
project_amount: projectAmount,
|
||||
convert_statement: fakeDocument,
|
||||
}
|
||||
|
||||
@@ -121,22 +120,26 @@ export async function processConvertSegment(
|
||||
}
|
||||
|
||||
// Проверяем глобальный кошелек программы капитализации (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 + capitalAmountValue
|
||||
console.log(`✅ Глобальный кошелек программы капитализации: ${beforeBlocked} → ${afterBlocked} (+${capitalAmountValue})`)
|
||||
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 + capitalAmountValue
|
||||
console.log(`✅ Кошелек пользователя в программе капитализации: ${beforeBlocked} → ${afterBlocked} (+${capitalAmountValue})`)
|
||||
const expectedIncrease = beforeBlocked + actualCapitalIncrease
|
||||
console.log(`✅ Кошелек пользователя в программе капитализации: ${beforeBlocked} → ${afterBlocked} (+${actualCapitalIncrease})`)
|
||||
expect(afterBlocked).toBeCloseTo(expectedIncrease, 1)
|
||||
}
|
||||
|
||||
|
||||
@@ -95,13 +95,13 @@ export async function processDebt(
|
||||
// 2. Одобряем долг через processApprove (soviet контракт)
|
||||
console.log(`\n✅ Подтверждение долга ${debtHash} через soviet`)
|
||||
await processApprove(blockchain, coopname, debtHash)
|
||||
console.log('✅ Долг одобрен советом')
|
||||
console.log('✅ Долг одобрен председателем (создана agenda)')
|
||||
|
||||
// 3. Процессим решение совета
|
||||
// 3. Процессим решение совета (agenda для авторизации долга)
|
||||
await processLastDecision(blockchain, coopname)
|
||||
console.log('✅ Решение совета принято')
|
||||
console.log('✅ Решение совета принято (долг авторизован, outcome создан)')
|
||||
|
||||
// 5. Подтверждаем завершение вывода (gateway сам вызовет callback на capital)
|
||||
// 4. Подтверждаем завершение вывода (gateway сам вызовет callback на capital)
|
||||
const confirmOutcomeData: GatewayContract.Actions.CompleteOutcome.ICompleteOutcome = {
|
||||
coopname,
|
||||
outcome_hash: debtHash,
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function processStartVoting(
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
console.log('projectBefore: ', projectBefore)
|
||||
// 1) Запускаем голосование по проекту
|
||||
const txStart = await blockchain.api.transact(
|
||||
{
|
||||
@@ -49,7 +49,7 @@ export async function processStartVoting(
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
console.log('projectAfter: ', projectAfter)
|
||||
return {
|
||||
projectHash: project_hash,
|
||||
txStartId: txStart.transaction_id,
|
||||
|
||||
@@ -10,6 +10,7 @@ export async function registerContributor(
|
||||
username: string,
|
||||
contributorHash: string,
|
||||
ratePerHour: string,
|
||||
hoursPerDay: number = 8,
|
||||
) {
|
||||
const contract = fakeDocument
|
||||
contract.signatures[0].signer = username
|
||||
@@ -18,8 +19,12 @@ export async function registerContributor(
|
||||
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(
|
||||
|
||||
@@ -3,7 +3,7 @@ import { signAgreement } from '../soviet/signAgreement'
|
||||
import { getCoopProgramWallet, getUserProgramWallet } from '../wallet/walletUtils'
|
||||
import { sourceProgramId, sourceProgramName } from './consts'
|
||||
|
||||
export async function signGenerationAgreement(
|
||||
export async function signGenerationContract(
|
||||
blockchain: any,
|
||||
coopname: string,
|
||||
username: string,
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
||||
import Blockchain from '../blockchain'
|
||||
import config from '../configs'
|
||||
import { getTotalRamUsage, globalRamStats } from '../utils/getTotalRamUsage'
|
||||
import { addUser } from '../init/participant'
|
||||
import { generateRandomUsername } from '../utils/randomUsername'
|
||||
|
||||
const blockchain = new Blockchain(config.network, config.private_keys)
|
||||
|
||||
let supplier: string
|
||||
let customer: string
|
||||
|
||||
const fakeDocument = {
|
||||
hash: '157192B276DA23CC84AB078FC8755C051C5F0430BF4802E55718221E6B76C777',
|
||||
public_key: 'PUB_K1_5JhMfxbsNebajHcTEK8yC9uNN9Dit9hEmzE8ri8yMhhzzEtUA4',
|
||||
signature: 'SIG_K1_KmKWPBC8dZGGDGhbKEoZEzPr3h5crRrR2uLdGRF5DJbeibY1MY1bZ9sPwHsgmPfiGFv9psfoCVsXFh9TekcLuvaeuxRKA8',
|
||||
meta: '{}',
|
||||
}
|
||||
|
||||
const testHash = '0000000000000000000000000000000000000000000000000000000000000001'
|
||||
|
||||
beforeAll(async () => {
|
||||
await blockchain.update_pass_instance()
|
||||
|
||||
supplier = generateRandomUsername()
|
||||
customer = generateRandomUsername()
|
||||
|
||||
console.log('supplier:', supplier)
|
||||
console.log('customer:', customer)
|
||||
|
||||
await addUser(supplier)
|
||||
await addUser(customer)
|
||||
}, 500_000)
|
||||
|
||||
afterAll(() => {
|
||||
console.log('\n📊 **MARKETPLACE RAM USAGE** 📊')
|
||||
let total = 0
|
||||
for (const [key, ram] of Object.entries(globalRamStats)) {
|
||||
console.log(` ${key} = ${(ram / 1024).toFixed(2)} kb`)
|
||||
total += ram
|
||||
}
|
||||
console.log(`\n💾 **TOTAL**: ${(total / 1024).toFixed(2)} kb\n`)
|
||||
})
|
||||
|
||||
describe('Marketplace — orderoffer flow', () => {
|
||||
it('контракт marketplace задеплоен', async () => {
|
||||
const info = await blockchain.api.v1.chain.get_info()
|
||||
expect(info).toBeDefined()
|
||||
expect(info.chain_id).toBeDefined()
|
||||
})
|
||||
|
||||
it('создание заявки orderoffer (заказчик)', async () => {
|
||||
const coopname = config.coopname
|
||||
|
||||
const result = await blockchain.transact({
|
||||
account: 'marketplace',
|
||||
name: 'orderoffer',
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: {
|
||||
coopname,
|
||||
receiver_braname: coopname,
|
||||
username: customer,
|
||||
hash: testHash,
|
||||
units: 10,
|
||||
unit_cost: '100.0000 RUB',
|
||||
product_lifecycle_secs: 2592000,
|
||||
warranty_period_secs: 604800,
|
||||
membership_fee_amount: '50.0000 RUB',
|
||||
cancellation_fee_amount: '10.0000 RUB',
|
||||
convert_in: fakeDocument,
|
||||
meta: JSON.stringify({ title: 'Тестовый товар', description: 'Описание' }),
|
||||
},
|
||||
})
|
||||
|
||||
expect(result).toBeDefined()
|
||||
console.log('orderoffer tx:', result?.response?.transaction_id?.substring(0, 16))
|
||||
|
||||
const ramUsed = await getTotalRamUsage(coopname)
|
||||
globalRamStats['orderoffer'] = ramUsed
|
||||
})
|
||||
|
||||
it('принятие заявки поставщиком (accept)', async () => {
|
||||
const coopname = config.coopname
|
||||
|
||||
const result = await blockchain.transact({
|
||||
account: 'marketplace',
|
||||
name: 'accept',
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: {
|
||||
coopname,
|
||||
supplier_braname: coopname,
|
||||
username: supplier,
|
||||
request_hash: testHash,
|
||||
convert_out: fakeDocument,
|
||||
product_contribution_statement: fakeDocument,
|
||||
},
|
||||
})
|
||||
|
||||
expect(result).toBeDefined()
|
||||
console.log('accept tx:', result?.response?.transaction_id?.substring(0, 16))
|
||||
|
||||
const ramUsed = await getTotalRamUsage(coopname)
|
||||
globalRamStats['accept'] = ramUsed
|
||||
})
|
||||
})
|
||||
|
||||
describe('Marketplace — coopstock flow', () => {
|
||||
const stockHash = '0000000000000000000000000000000000000000000000000000000000000002'
|
||||
|
||||
it('создание предложения из запасов кооператива', async () => {
|
||||
const coopname = config.coopname
|
||||
|
||||
const result = await blockchain.transact({
|
||||
account: 'marketplace',
|
||||
name: 'coopstock',
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: {
|
||||
coopname,
|
||||
braname: coopname,
|
||||
hash: stockHash,
|
||||
units: 5,
|
||||
unit_cost: '50.0000 RUB',
|
||||
product_lifecycle_secs: 1296000,
|
||||
warranty_period_secs: 302400,
|
||||
membership_fee_amount: '25.0000 RUB',
|
||||
meta: JSON.stringify({ title: 'Уценённый товар', description: 'Из запасов' }),
|
||||
},
|
||||
})
|
||||
|
||||
expect(result).toBeDefined()
|
||||
console.log('coopstock tx:', result?.response?.transaction_id?.substring(0, 16))
|
||||
})
|
||||
})
|
||||
@@ -8,9 +8,11 @@ export async function processLastDecision(blockchain: Blockchain, coopname: stri
|
||||
SovietContract.contractName.production,
|
||||
coopname,
|
||||
SovietContract.Tables.Decisions.tableName,
|
||||
1,
|
||||
100, // берем больше, чтобы гарантированно получить все
|
||||
)
|
||||
const lastDecision = decisions[0]
|
||||
console.log('Активные вопросы на повестке: ', decisions)
|
||||
// Берем ПОСЛЕДНИЙ элемент массива (самый свежий)
|
||||
const lastDecision = decisions[decisions.length - 1]
|
||||
|
||||
// 3. Голосуем за решение совета
|
||||
await processDecision(blockchain, lastDecision.id)
|
||||
|
||||
@@ -16,3 +16,5 @@ export async function sendPostToCoopbackWithSecret(url: string, data: any) {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export * from './randomData'
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
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`
|
||||
}
|
||||
Executable
+115
@@ -0,0 +1,115 @@
|
||||
#!/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
|
||||
@@ -0,0 +1,11 @@
|
||||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
testTimeout: 240000,
|
||||
hookTimeout: 240000,
|
||||
sequence: {
|
||||
concurrent: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,77 @@
|
||||
# 🔑 @coopenomics/cleos
|
||||
|
||||
Обёртка над утилитой `cleos` из набора инструментов EOSIO. Запускает Docker-контейнер с предустановленным кошельком для прямого взаимодействия с блокчейном — отправки транзакций, просмотра таблиц контрактов, управления ключами и аккаунтами.
|
||||
|
||||
## Основные возможности
|
||||
|
||||
- Готовая среда с кошельком и ключами для локальной разработки
|
||||
- Отправка транзакций в блокчейн
|
||||
- Просмотр таблиц смарт-контрактов
|
||||
- Управление аккаунтами и ключами блокчейна
|
||||
- Разблокировка и сброс кошелька через вспомогательные скрипты
|
||||
|
||||
## Установка
|
||||
|
||||
Компонент является частью монорепозитория. Установка зависимостей из корня проекта:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
## Скрипты
|
||||
|
||||
| Скрипт | Команда | Описание |
|
||||
|--------|---------|----------|
|
||||
| `run` | `pnpm run run` | Запуск Docker-контейнера с кошельком |
|
||||
|
||||
Из корня монорепозитория можно использовать:
|
||||
|
||||
```bash
|
||||
pnpm run enter
|
||||
```
|
||||
|
||||
## Использование
|
||||
|
||||
Для работы необходима запущенная инфраструктура (см. `docker-compose.yaml` в корне проекта).
|
||||
|
||||
### Запуск
|
||||
|
||||
```bash
|
||||
pnpm run enter
|
||||
```
|
||||
|
||||
### Внутри контейнера
|
||||
|
||||
```bash
|
||||
./unlock.sh # Разблокировка кошелька
|
||||
./cleos.sh get info # Информация о блокчейне
|
||||
./cleos.sh get table <contract> <scope> <table> # Просмотр таблиц
|
||||
```
|
||||
|
||||
### Вспомогательные скрипты
|
||||
|
||||
| Скрипт | Назначение |
|
||||
|--------|-----------|
|
||||
| `unlock.sh` | Разблокировка кошелька (пароль применяется автоматически) |
|
||||
| `cleos.sh` | Прокси-вызов `cleos` с настроенным подключением к ноде |
|
||||
| `reset.sh` | Сброс состояния кошелька |
|
||||
|
||||
## Конфигурация
|
||||
|
||||
Кошелёк использует дефолтный ключ для локальной среды разработки. Пароль хранится в файле `password` внутри директории `eosio-wallet` и применяется автоматически при разблокировке.
|
||||
|
||||
## Архитектура
|
||||
|
||||
```
|
||||
├── run.sh # Скрипт запуска Docker-контейнера
|
||||
├── eosio-wallet/ # Хранилище кошелька
|
||||
│ └── password # Пароль для разблокировки
|
||||
└── scripts/ # Вспомогательные скрипты
|
||||
├── unlock.sh # Разблокировка кошелька
|
||||
├── cleos.sh # Прокси-вызов cleos
|
||||
└── reset.sh # Сброс кошелька
|
||||
```
|
||||
|
||||
## Лицензия
|
||||
|
||||
[BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode.ru)
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@coopenomics/cleos",
|
||||
"version": "2025.10.16",
|
||||
"version": "2026.2.22-2",
|
||||
"private": true,
|
||||
"description": "",
|
||||
"description": "Обёртка над кошельком cleos для EOSIO блокчейна",
|
||||
"scripts": {
|
||||
"run": "./run.sh"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,537 @@
|
||||
---
|
||||
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`
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
description: Общие правила
|
||||
alwaysApply: true
|
||||
---
|
||||
Мы работаем в МОНО-репозитории pnpm, любая установка пакетов производится ЧЕРЕЗ ФИЛЬТР компонента в корне: pnpm add glob --filter <package>.
|
||||
|
||||
НИКОГДА НЕ ЗАПУСКАЙ ПРИЛОЖЕНИЕ ИЛИ ЕГО СБОРКУ!!! Просто отчитайся что всё сделал.
|
||||
|
||||
НИКОГДА НЕ ЗАПУСКАЙ ТЕСТЫ ИЛИ ПРОВЕРКУ ТИПОВ ИЛИ ЛИНТЕР!!!
|
||||
|
||||
Никаких ANY типов! Строгая типизация всегда.
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
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.
|
||||
|
||||
Уровень приложения оркестрирует вызовы доменов через порты в инфраструктуру. Домены не обращаются друг к друга. Срезы уровня приложений не обращаются друг к другу, а только вызывают порты домена.
|
||||
|
||||
```
|
||||
+2
-2
@@ -51,8 +51,8 @@ import { client } from 'src/shared/api/client';
|
||||
import { Mutations } from '@coopenomics/sdk';
|
||||
|
||||
async function generateDocument(data: IGenerateDocumentInput): Promise<IGeneratedDocumentOutput> {
|
||||
const { [Mutations.Capital.GenerateGenerationAgreement.name]: result } =
|
||||
await client.Mutation(Mutations.Capital.GenerateGenerationAgreement.mutation, {
|
||||
const { [Mutations.Capital.GenerateGenerationContract.name]: result } =
|
||||
await client.Mutation(Mutations.Capital.GenerateGenerationContract.mutation, {
|
||||
variables: {
|
||||
data: {
|
||||
coopname: 'coopname',
|
||||
+1
@@ -1,4 +1,5 @@
|
||||
---
|
||||
description: О правилах на фронтенде
|
||||
globs: **/desktop/**
|
||||
alwaysApply: false
|
||||
---
|
||||
+1
-2
@@ -1,6 +1,5 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
description: Правила работы с макросом main.py в компоненте docs
|
||||
alwaysApply: false
|
||||
---
|
||||
Файл [main.py](mdc:monocoop/monocoop/monocoop/components/docs/main.py) автоматически генерит ссылки на документацию SDK и GraphQL, которые формируются и публикуются автоматически. При создании документации к методам всегда применяй ссылки на SDK и GraphQL по форме:
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
---
|
||||
description: Правила формирования агрегатов документов в controller
|
||||
alwaysApply: false
|
||||
globs: **/controller/**/*.resolver.ts
|
||||
---
|
||||
## 📋 Правила формирования агрегатов документов
|
||||
|
||||
+2
-3
@@ -1,9 +1,7 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
description: Правила контроля изменений документов в фабрике (factory)
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Структура файлов шаблонов документов и алгоритм правки
|
||||
|
||||
## Основные директории и файлы
|
||||
@@ -94,3 +92,4 @@ alwaysApply: false
|
||||
- Поддерживаются конструкции: `{% if %}`, `{% for %}`, `{% trans %}`
|
||||
- Переменные вставляются через `{{ variable }}`
|
||||
- Переводы через `{% trans 'KEY' %}`
|
||||
- Дополнительно поддерживается передача переменной в перевод через `{% trans 'KEY', some_var1, some_var2 %}`, при этом в самом переводе необходимо использовать порядковые ключи {0}, {1} для доступа к переданным переменным.
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
description: Typeorm entity name. Выбрать имя для typeorm сущности.
|
||||
alwaysApply: false
|
||||
---
|
||||
Имя сущность typeorm должно быть в паттерне **/entities/*entity.ts
|
||||
|
||||
'src/infrastructure/**/entities/*entity.{ts,js}',
|
||||
'src/extensions/**/entities/*entity.{ts,js}',
|
||||
'src/shared/**/entities/*entity.{ts,js}',
|
||||
|
||||
+1
-2
@@ -1,6 +1,5 @@
|
||||
---
|
||||
description:
|
||||
globs: **/controller/src/extensions/**
|
||||
description: Архитектура и правила расширений extensions в controller
|
||||
alwaysApply: false
|
||||
---
|
||||
# Архитектура расширений в MonoCoop
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
description: Добавить документ в фабрику документов. Создать шаблон документа.
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
## Создание шаблона
|
||||
|
||||
В библиотеке реестра типов документов находятся шаблоны: cooptypes/src/cooperative/registry/1.WalletAgreement
|
||||
|
||||
В каждом шаблоне определяется часть Action, которая отвечает за передаваемые извне параметры для модели документа. И Model, которая отвечает за общую модель документа, которая может включать в себя не только данные, которые передаются извне в действии по генерации документа, но и извлекаются самой фабрикой в процессе генерации. Meta данные при этом являются объединением обоих интерфейсов и на основе этих данных можно произвести полную регенерацию документа.
|
||||
|
||||
Содержание каждого index файла включает в себя интерфейс Action для генерации. Model с теми данными которые могут быть предоставлены самой фабрикой документов. Title, description, context и translations и exampleData.
|
||||
|
||||
Context включает в себя строковый шаблон jinja2, который использует данные из Action и Meta, а также translations для переводов на русский язык. Кроме того, каждый шаблон содержит стандартную css разметку для заголовка, места подписи, и т.д.
|
||||
|
||||
exampleData мы прописываем как пример данных, которые программа-визуализатор может подставить и показать визуально-заполненный шаблон. Потому когда ты создаешь новый документ ты должен в точности соблюдать заполнение всех полей. Во-первых ты должен декомпозировать документ на элементы переводов, затем эти элементы переводов вставить в текст и разбавить использованием переменных. Местами ты можешь использовать вот такие конструкции шаблона для передачи переменных прямо в перевод: {% trans 'phone_and_email_notice', individual.phone, individual.email %}
|
||||
|
||||
В объекте перевода:
|
||||
...phone_and_email_notice: 'номер телефона с активированной функцией получения sms: {0}, адрес электронной почты: {1}.',
|
||||
|
||||
Но обычно достаточно просто использовать переменные из Action и Model напрямую.
|
||||
|
||||
## Создание схемы
|
||||
|
||||
После того, как шаблон создан и экспортирован, необходимо создать схему. Схема создается в factory/src/Templates
|
||||
|
||||
Там реэкспортируются Action, Model как типы. И определяется схема ajv. Там есть стандартные, определенные части схемы, которые находятся в factory/src/Schema. Все части моделей Action и Model должны быть определены в одной схеме данных и затем экспортированы в реестр схем factory/src/Templates/registry.ts.
|
||||
|
||||
## Создание станка
|
||||
|
||||
Далее необходимо создать станок для обработки генерации документа в factory/src/Actions. Он дожен включать в себя интерфейс действия и всю подготовительную обработку данных для передачи информации в шаблон согласно схемы. В качестве ФИО/наименование пайщика используем factory/src/Schema/CommonUserSchema.ts.
|
||||
|
||||
## Экспорт станка
|
||||
|
||||
Станок необходимо экспортировать в src/index.ts через все реестры. Чтобы в итоге вызов метода генерации был делегирован корректному станку.
|
||||
|
||||
|
||||
## Создание теста
|
||||
|
||||
В папке factory/src/tests создаем или используем существующий и подходящий по контексту файл теста для генерации. Используем метод для генерации
|
||||
|
||||
it('генерируем соглашение о капитализации', async () => {
|
||||
await testDocumentGeneration({
|
||||
registry_id: 1000,
|
||||
coopname: 'voskhod',
|
||||
username: 'ant',
|
||||
lang: 'ru',
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
description: Создание шаблона документа без шапки из его основной версии в фабрике документов
|
||||
alwaysApply: false
|
||||
---
|
||||
Смотри, у меня есть проект документа оферты, @cooptypes/src/cooperative/registry/999.BlagorostOfferTemplate/index.ts мы его принимаем затем, чтобы получить данные для генерации @cooptypes/src/cooperative/registry/1000.BlagorostOffer/index.ts уже самой пользовательской оферты. Они в целом почти абсолютно идентичны за той лишь разницей, что в проекте кругом прочерки остаются вместо переменных для заполнения, и нет шапки с надписью "УТВЕРЖДЕНО" протоколом решения № ___ и дата ___. Т.е. мы как бы из первого в итоге получаем второе. Приняв первое - мы можем начать генерировать второе.
|
||||
|
||||
Мне надо, чтобы ты сделал аналогично для указанного мной документа. Для этого нужно переменные по аналогии заполнить пробелами и убрать шапку. Т.е. в итоге мы должны получить шаблон документа, который можно распечатать и заполнить ручкой. Вот такой вот документ мне надо чтоб ты создал. Он должен обладать принадлежностью к конкретному кооперативу, т.е. vars и chairman мы используем, как и coop. Всё остальное по сути нужно заменить на прочерки. Создай этот шаблон документа, исследовав что нужно тебе для этого.
|
||||
+2
-107
@@ -1,6 +1,5 @@
|
||||
---
|
||||
description:
|
||||
globs: src/**/*.ts
|
||||
description: Правила работы с фабрикой документов кооперативов (factory)
|
||||
alwaysApply: false
|
||||
---
|
||||
# Фабрика Документов Кооперативов
|
||||
@@ -15,29 +14,8 @@ alwaysApply: false
|
||||
|
||||
## Структура Registry
|
||||
|
||||
В корневой папке `registry/` находятся JSON файлы с номерными названиями, представляющие шаблоны документов:
|
||||
В корневой папке `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
|
||||
@@ -133,89 +111,6 @@ export class Generator implements IGenerator {
|
||||
- `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: [...]
|
||||
})
|
||||
```
|
||||
|
||||
## Особенности реализации
|
||||
|
||||
### Шаблонизация:
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
---
|
||||
globs: **/controller/**/*.ts
|
||||
description: Логирование и логгер на бэкенде (controller)
|
||||
alwaysApply: false
|
||||
---
|
||||
# Logger Rule
|
||||
+2
-3
@@ -1,9 +1,8 @@
|
||||
---
|
||||
description:
|
||||
globs: components/sdk/**
|
||||
description: Как добавлять селекторы, мутации и запросы в SDK
|
||||
globs: **/sdk/**
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Руководство по работе с GraphQL Zeus в SDK
|
||||
|
||||
## Основной процесс
|
||||
@@ -0,0 +1,12 @@
|
||||
project_name: "Project Filters"
|
||||
project_type: "library"
|
||||
project_level: 1
|
||||
output_folder: "docs"
|
||||
language: "ru"
|
||||
bmm:
|
||||
workflow_status_file: "docs/bmm-workflow-status.yaml"
|
||||
sprint_status_file: "docs/sprint-status.yaml"
|
||||
paths:
|
||||
docs: "docs"
|
||||
stories: "docs/stories"
|
||||
tests: "tests"
|
||||
@@ -0,0 +1,37 @@
|
||||
version: "6.0"
|
||||
project_name: "Project Filters"
|
||||
project_type: "library"
|
||||
project_level: 1
|
||||
language: "ru"
|
||||
initialized_at: "2026-02-04T12:00:00.000Z"
|
||||
last_updated: "2026-02-04T12:30:00.000Z"
|
||||
|
||||
workflow_phases:
|
||||
phase1_product_brief:
|
||||
status: "completed"
|
||||
required: false
|
||||
description: "Краткое описание продукта"
|
||||
completed_at: "2026-02-04T12:00:00.000Z"
|
||||
output_file: "docs/product-brief-project-filters-2026-02-04.md"
|
||||
|
||||
phase2_requirements:
|
||||
status: "pending"
|
||||
required: true
|
||||
description: "Технические требования"
|
||||
completed_at: null
|
||||
|
||||
phase3_architecture:
|
||||
status: "pending"
|
||||
required: false
|
||||
description: "Архитектурное решение"
|
||||
completed_at: null
|
||||
|
||||
phase4_implementation:
|
||||
status: "completed"
|
||||
required: true
|
||||
description: "Реализация историй"
|
||||
completed_at: "2026-02-04T12:00:00.000Z"
|
||||
output_file: "Реализован диалог фильтров с кнопкой в интерфейсе"
|
||||
|
||||
current_phase: "phase1_product_brief"
|
||||
next_recommended_action: "Создать краткое описание продукта (product brief)"
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
# Product Brief: Project Filters
|
||||
**Дата:** 2026-02-04
|
||||
**Проект:** Project Filters
|
||||
**Тип:** Библиотека / Фреймворк
|
||||
**Уровень:** 1 (Маленький проект)
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Мы создаем систему диалоговой фильтрации проектов для кооперативной платформы, которая позволит пользователям эффективно фильтровать и находить нужные проекты и задачи. Система предназначена для участников кооперативов - разработчиков, менеджеров проектов и администраторов, которые ежедневно работают с большим количеством проектов и задач. Это важно, потому что без эффективной фильтрации пользователи тратят много времени на поиск нужной информации, что снижает продуктивность работы.
|
||||
|
||||
## Problem Statement
|
||||
|
||||
В текущей системе проекты отображаются в виде длинного списка без возможности гибкой фильтрации. Пользователи не могут быстро найти проекты по статусу задач, приоритетам, ответственным лицам или мастерам. Это приводит к тому, что:
|
||||
|
||||
- Участники тратят много времени на ручной поиск нужных проектов
|
||||
- Сложно отслеживать проекты с определенными статусами (например, только завершенные или в работе)
|
||||
- Нет возможности фильтровать по ответственным лицам (создателям и мастерам проектов)
|
||||
- Отсутствует возможность комбинировать фильтры для точного поиска
|
||||
|
||||
## Why Now
|
||||
|
||||
С ростом количества проектов и участников в кооперативе становится критически важно иметь эффективные инструменты поиска и фильтрации. Текущая система уже достигла предела usability - пользователи жалуются на сложности с навигацией. Реализация фильтров сейчас позволит заложить фундамент для масштабирования платформы и улучшения пользовательского опыта.
|
||||
|
||||
## Solution Overview
|
||||
|
||||
Мы создадим диалоговое окно фильтров, которое заменит текущие горизонтальные кнопки фильтрации. Диалог будет содержать четыре типа фильтров:
|
||||
|
||||
1. **Фильтр по статусам задач** - множественный выбор из: Все статусы, Бэклог, К выполнению, В работе, На проверке, Выполнено, Отменено
|
||||
2. **Фильтр по приоритетам** - множественный выбор из: Все приоритеты, Срочный, Высокий, Средний, Низкий
|
||||
3. **Фильтр по исполнителю** - выбор конкретного участника или опция "Я"
|
||||
4. **Фильтр по мастеру** - выбор конкретного мастера проекта или опция "Я"
|
||||
|
||||
Диалог будет открываться по нажатию кнопки "Фильтры" в шапке страницы проектов.
|
||||
|
||||
## Target Audience
|
||||
|
||||
### Primary Users
|
||||
- **Разработчики** - ежедневно работают с задачами, нуждаются в фильтрации по статусам и приоритетам
|
||||
- **Менеджеры проектов** - отслеживают прогресс проектов, фильтруют по ответственным лицам
|
||||
- **Администраторы кооператива** - мониторят все проекты, используют комплексную фильтрацию
|
||||
|
||||
### Secondary Users
|
||||
- **Новые участники** - изучают структуру проектов через фильтры
|
||||
- **Внешние консультанты** - временно работают с проектами кооператива
|
||||
|
||||
Основные потребности:
|
||||
1. Быстрый поиск проектов по статусу задач
|
||||
2. Фильтрация по приоритетам для prioritization
|
||||
3. Поиск проектов по ответственным лицам
|
||||
4. Комбинирование фильтров для точного поиска
|
||||
|
||||
## Business Objectives
|
||||
|
||||
**Основные цели:**
|
||||
- Сократить время поиска проектов на 60%
|
||||
- Увеличить удовлетворенность пользователей работой с платформой
|
||||
- Улучшить прозрачность работы кооператива через эффективную навигацию
|
||||
|
||||
**Метрики успеха:**
|
||||
- Время на поиск конкретного проекта: < 30 секунд
|
||||
- Количество жалоб на навигацию: уменьшить на 80%
|
||||
- Уровень использования фильтров: > 70% активных пользователей
|
||||
|
||||
**Бизнес-ценность:**
|
||||
- Повышение продуктивности участников кооператива
|
||||
- Снижение операционных издержек на поддержку пользователей
|
||||
- Улучшение retention участников платформы
|
||||
|
||||
## Scope
|
||||
|
||||
### In Scope
|
||||
- Диалоговое окно с четырьмя типами фильтров
|
||||
- Интеграция с существующей системой фильтрации
|
||||
- Кнопка "Фильтры" в шапке страницы проектов
|
||||
- Сохранение состояния фильтров
|
||||
- Валидация и обработка ошибок
|
||||
|
||||
### Out of Scope
|
||||
- Расширенные фильтры (по дате создания, бюджету и т.д.)
|
||||
- Сохранение персональных настроек фильтров
|
||||
- Экспорт отфильтрованных данных
|
||||
- Интеграция с мобильной версией
|
||||
|
||||
### Future Considerations
|
||||
- Добавление фильтров по датам
|
||||
- Персональные шаблоны фильтров
|
||||
- Интеграция с системой уведомлений
|
||||
|
||||
## Stakeholders
|
||||
|
||||
- **Продуктовый менеджер** - определение требований и приоритетов
|
||||
- **UX/UI дизайнер** - проектирование интерфейса диалога
|
||||
- **Frontend разработчики** - реализация компонентов
|
||||
- **Backend разработчики** - поддержка API фильтрации
|
||||
- **QA инженеры** - тестирование функциональности
|
||||
- **Пользователи кооператива** - валидация решения
|
||||
|
||||
## Constraints and Assumptions
|
||||
|
||||
### Constraints
|
||||
- Должны использовать существующую архитектуру FSD
|
||||
- Интеграция только с текущими компонентами фильтрации
|
||||
- Ограничения по времени: 2 недели на реализацию
|
||||
|
||||
### Assumptions
|
||||
- Пользователи знакомы с базовыми принципами фильтрации
|
||||
- Backend API поддерживает все необходимые параметры фильтрации
|
||||
- Текущая система компонентов (ContributorSelector, etc.) стабильна
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- Диалог открывается и закрывается корректно
|
||||
- Все четыре типа фильтров работают правильно
|
||||
- Фильтры применяются мгновенно без перезагрузки страницы
|
||||
- Кнопка "Фильтры" интегрирована в шапку без конфликтов
|
||||
- Код соответствует архитектуре FSD и проходит все тесты
|
||||
|
||||
## Timeline
|
||||
|
||||
**Целевая дата запуска:** Через 2 недели после начала разработки
|
||||
|
||||
**Ключевые этапы:**
|
||||
1. Проектирование интерфейса (3 дня)
|
||||
2. Реализация компонентов (5 дней)
|
||||
3. Интеграция и тестирование (4 дня)
|
||||
4. Финальное тестирование и развертывание (2 дня)
|
||||
|
||||
## Risks
|
||||
|
||||
- **Технический риск:** Конфликты с существующими компонентами фильтрации
|
||||
- **Вероятность:** Средняя
|
||||
- **МитIGATION:** Тщательное тестирование интеграции
|
||||
|
||||
- **UX риск:** Пользователи не поймут новый интерфейс
|
||||
- **Вероятность:** Низкая
|
||||
- **МитIGATION:** Соблюдение существующих паттернов интерфейса
|
||||
|
||||
- **Производительность:** Диалог замедлит загрузку страницы
|
||||
- **Вероятность:** Низкая
|
||||
- **МитIGATION:** Ленивая загрузка компонентов
|
||||
@@ -0,0 +1,12 @@
|
||||
project_name: "Виджет участников проекта"
|
||||
project_type: "web-app"
|
||||
project_level: 1
|
||||
output_folder: "docs"
|
||||
language: "ru"
|
||||
bmm:
|
||||
workflow_status_file: "docs/bmm-workflow-status.yaml"
|
||||
sprint_status_file: "docs/sprint-status.yaml"
|
||||
paths:
|
||||
docs: "docs"
|
||||
stories: "docs/stories"
|
||||
tests: "tests"
|
||||
@@ -0,0 +1,42 @@
|
||||
timestamp: "2026-02-04T14:30:00.000Z"
|
||||
project_name: "Виджет участников проекта"
|
||||
project_type: "web-app"
|
||||
project_level: 1
|
||||
language: "ru"
|
||||
|
||||
workflow_phases:
|
||||
phase_1_product_brief:
|
||||
status: "completed"
|
||||
required: "recommended"
|
||||
completed: true
|
||||
artifacts:
|
||||
- "docs/product-brief-виджет-участников-проекта-2026-02-04.md"
|
||||
|
||||
phase_2_requirements:
|
||||
status: "completed"
|
||||
required: "required"
|
||||
completed: true
|
||||
artifacts:
|
||||
- "docs/tech-spec-виджет-участников-проекта-2026-02-04.md"
|
||||
|
||||
phase_3_architecture:
|
||||
status: "pending"
|
||||
required: "optional"
|
||||
completed: false
|
||||
artifacts: []
|
||||
|
||||
phase_4_implementation:
|
||||
status: "in_progress"
|
||||
required: "required"
|
||||
completed: false
|
||||
artifacts:
|
||||
- "docs/sprint-plan-виджет-участников-проекта-2026-02-04.md"
|
||||
- "docs/sprint-status.yaml"
|
||||
|
||||
current_phase: "phase_4_implementation"
|
||||
next_recommended_action: "Начать реализацию Sprint 1: STORY-001, STORY-002, STORY-003"
|
||||
|
||||
metadata:
|
||||
created_at: "2026-02-04T12:00:00.000Z"
|
||||
updated_at: "2026-02-04T15:00:00.000Z"
|
||||
version: "6.1"
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
# Product Brief: Виджет участников проекта
|
||||
|
||||
**Date:** 2026-02-04
|
||||
**Author:** AI Assistant
|
||||
**Version:** 1.1
|
||||
**Project Type:** web-app
|
||||
**Project Level:** 1
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Виджет для отображения информации об участниках проекта в графической форме. Показывает количество вкладов участников и информацию о том, как стать участником проекта. Это важно для понимания степени участия пользователей в проектах и их вклада в интеллектуальную собственность.
|
||||
|
||||
---
|
||||
|
||||
## Problem Statement
|
||||
|
||||
### The Problem
|
||||
|
||||
Пользователи не имеют полной информации о своем участии в проекте. Существует две критические проблемы:
|
||||
|
||||
1. **Отсутствие информации о вкладах**: После регистрации в проекте участник получает долю авторского права, но эта информация не отображается в интерфейсе. Виджет показывает только текущую информацию об участии, но не исторические данные о вкладах.
|
||||
|
||||
2. **Неправильная логика отображения участников**: Участники отображаются в виджете только после того, как сделали конкретный вклад в проект. Однако участники должны отображаться сразу после подачи заявления на участие и получения доступа к проекту. Это приводит к тому, что новые участники не видят себя в списке участников до тех пор, пока не внесут вклад.
|
||||
|
||||
### Why Now?
|
||||
|
||||
С ростом числа проектов и участников становится критически важно показывать прозрачную информацию об участии. Без этого пользователи не понимают ценность своего вклада и не могут оценить свою роль в проекте.
|
||||
|
||||
### Impact if Unsolved
|
||||
|
||||
Участники не будут понимать ценность своего участия, что приведет к снижению мотивации и вовлеченности. Новые участники не видят себя в списке участников проекта, что создает путаницу и неуверенность. Проекты потеряют прозрачность, что может привести к конфликтам и недоверию среди участников. Увеличивается риск потери потенциальных контрибьюторов, которые не понимают свой статус в проекте.
|
||||
|
||||
---
|
||||
|
||||
## Target Audience
|
||||
|
||||
### Primary Users
|
||||
|
||||
Участники проектов (ранние участники), которые приходят в проекты для получения доли в результате интеллектуальной деятельности. Они активно участвуют в проектах и используют виджет для отслеживания своего вклада.
|
||||
|
||||
### Secondary Users
|
||||
|
||||
Администраторы проектов, которые могут использовать виджет для оценки вклада участников.
|
||||
|
||||
### User Needs
|
||||
|
||||
- Видеть себя в списке участников сразу после получения доступа к проекту
|
||||
- Видеть количество своих вкладов в проект
|
||||
- Понимать, как стать участником проекта
|
||||
- Иметь прозрачную информацию об участии в графической форме
|
||||
- Отслеживать свой прогресс и ценность вклада
|
||||
- Видеть свою роль в проекте после назначения
|
||||
|
||||
---
|
||||
|
||||
## Solution Overview
|
||||
|
||||
### Proposed Solution
|
||||
|
||||
Комплексное улучшение виджета участников проекта:
|
||||
|
||||
1. **Исправление логики отображения участников**: Участники должны отображаться сразу после получения доступа к проекту (после подачи заявления и подписания соглашения об участии), а не только после внесения вклада.
|
||||
|
||||
2. **Добавление информации о вкладах**: Графическое отображение количества вкладов участника и информации о процессе становления участником проекта.
|
||||
|
||||
### Key Features
|
||||
|
||||
- **Исправление логики отображения**: Участники отображаются сразу после получения доступа к проекту
|
||||
- Графическое отображение количества вкладов участника
|
||||
- Информация о том, как стать участником проекта
|
||||
- Отображение ролей участников после их назначения
|
||||
- Интеграция с существующей системой учета вкладов
|
||||
- Адаптивный дизайн для различных устройств
|
||||
|
||||
### Value Proposition
|
||||
|
||||
Виджет обеспечивает прозрачность и мотивацию участников, показывая их реальный вклад в проект. Это помогает участникам лучше понимать ценность своего участия и стимулирует дальнейшую активность.
|
||||
|
||||
---
|
||||
|
||||
## Business Objectives
|
||||
|
||||
### Goals
|
||||
|
||||
- Увеличить прозрачность информации об участии в проектах
|
||||
- Повысить мотивацию участников через демонстрацию их вклада
|
||||
- Улучшить пользовательский опыт работы с проектами
|
||||
- Снизить количество вопросов о статусе участия
|
||||
|
||||
### Success Metrics
|
||||
|
||||
- Процент участников, просматривающих информацию о вкладах
|
||||
- Уровень удовлетворенности пользователей виджетом
|
||||
- Количество активных участников проектов
|
||||
|
||||
### Business Value
|
||||
|
||||
Повышение вовлеченности участников приводит к более качественной разработке проектов и лучшему распределению интеллектуальной собственности.
|
||||
|
||||
---
|
||||
|
||||
## Scope
|
||||
|
||||
### In Scope
|
||||
|
||||
- Исправление логики отображения участников (отображение сразу после получения доступа)
|
||||
- Добавление поля с количеством вкладов в графической форме
|
||||
- Интеграция с API для получения данных о вкладах и статусе участников
|
||||
- Добавление секции "Как стать участником"
|
||||
- Отображение ролей участников
|
||||
- Тестирование новой функциональности
|
||||
|
||||
### Out of Scope
|
||||
|
||||
- Изменение существующей логики учета вкладов
|
||||
- Переработка дизайна всего виджета
|
||||
- Добавление новых типов участников
|
||||
- Интеграция с внешними системами учета
|
||||
|
||||
### Future Considerations
|
||||
|
||||
- Добавление детальной истории вкладов
|
||||
- Персонализация отображения информации
|
||||
- Интеграция с системой уведомлений
|
||||
|
||||
---
|
||||
|
||||
## Key Stakeholders
|
||||
|
||||
- **Разработчики проекта** - Высокий уровень влияния. Отвечают за реализацию функциональности
|
||||
- **Участники проектов** - Средний уровень влияния. Основные пользователи виджета
|
||||
- **Администраторы проектов** - Средний уровень влияния. Могут использовать виджет для оценки вклада
|
||||
|
||||
---
|
||||
|
||||
## Constraints and Assumptions
|
||||
|
||||
### Constraints
|
||||
|
||||
- Интеграция должна быть совместима с существующей архитектурой
|
||||
- Ограничения по времени на реализацию (короткий срок)
|
||||
- Необходимость поддерживать существующий дизайн-систем
|
||||
|
||||
### Assumptions
|
||||
|
||||
- API для получения данных о вкладах уже существует
|
||||
- Пользователи имеют базовый уровень технической подготовки
|
||||
- Существующая инфраструктура может обработать дополнительную нагрузку
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- Участники отображаются в виджете сразу после получения доступа к проекту (подписания соглашения об участии)
|
||||
- Виджет корректно отображает количество вкладов участников
|
||||
- Информация о вкладах представлена в понятной графической форме
|
||||
- Пользователи могут легко найти информацию о том, как стать участником
|
||||
- Роли участников корректно отображаются после назначения
|
||||
- Новая функциональность не нарушает работу существующего виджета
|
||||
- Положительная обратная связь от пользователей
|
||||
|
||||
---
|
||||
|
||||
## Timeline and Milestones
|
||||
|
||||
### Target Launch
|
||||
|
||||
Запуск новой версии виджета в течение 2 недель после начала разработки.
|
||||
|
||||
### Key Milestones
|
||||
|
||||
- Завершение анализа требований: 1 день
|
||||
- Разработка UI компонентов: 3 дня
|
||||
- Интеграция с API: 2 дня
|
||||
- Тестирование: 2 дня
|
||||
- Деплой и мониторинг: 1 день
|
||||
|
||||
---
|
||||
|
||||
## Risks and Mitigation
|
||||
|
||||
### Risks
|
||||
|
||||
- **Технические сложности интеграции с API**
|
||||
- **Likelihood:** Средний
|
||||
- **Mitigation:** Предварительное тестирование API и наличие плана отката
|
||||
|
||||
- **Несовместимость с существующим дизайном**
|
||||
- **Likelihood:** Низкий
|
||||
- **Mitigation:** Использование существующей дизайн-системы
|
||||
|
||||
- **Недостаточная производительность**
|
||||
- **Likelihood:** Низкий
|
||||
- **Mitigation:** Оптимизация запросов к API
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Create Tech Spec - `/tech-spec`
|
||||
2. Conduct user research (optional) - `/research`
|
||||
3. Create UX design (if UI-heavy) - `/create-ux-design`
|
||||
|
||||
---
|
||||
|
||||
**This document was created using BMAD Method v6 - Phase 1 (Analysis)**
|
||||
|
||||
*To continue: Run `/workflow-status` to see your progress and next recommended action.*
|
||||
+251
@@ -0,0 +1,251 @@
|
||||
# Sprint Plan: Виджет участников проекта
|
||||
|
||||
**Date:** 2026-02-04
|
||||
**Scrum Master:** AI Assistant
|
||||
**Project Level:** 1
|
||||
**Total Stories:** 5
|
||||
**Total Points:** 12
|
||||
**Planned Sprints:** 2
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
План спринта для улучшения виджета участников проекта включает исправление логики отображения участников и добавление информации о вкладах. Проект разделен на 2 спринта по 1 неделе каждый с общим объемом 12 story points.
|
||||
|
||||
**Key Metrics:**
|
||||
- Total Stories: 5
|
||||
- Total Points: 12
|
||||
- Sprints: 2 (по 1 неделе)
|
||||
- Team Capacity: 8 points per sprint
|
||||
- Target Completion: 2026-02-18
|
||||
|
||||
---
|
||||
|
||||
## Story Inventory
|
||||
|
||||
### STORY-001: Исправить логику создания участников
|
||||
|
||||
**Priority:** Must Have
|
||||
|
||||
**User Story:**
|
||||
As a: Пользователь кооператива
|
||||
I want to: Видеть себя в списке участников сразу после получения доступа к проекту
|
||||
So that: Я понимаю свой статус участия без необходимости делать вклад
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Участник отображается в виджете сразу после подписания соглашения об участии
|
||||
- [ ] Не требуется делать вклад для отображения в списке участников
|
||||
- [ ] Логика создания Contributor изменена с "при первом вкладе" на "при подтверждении доступа"
|
||||
|
||||
**Technical Notes:**
|
||||
Изменить условие создания Contributor entity в backend. Добавить проверку статуса appendix вместо проверки наличия вкладов.
|
||||
|
||||
**Dependencies:**
|
||||
Нет
|
||||
|
||||
**Points:** 2
|
||||
|
||||
### STORY-002: Добавить расчет количества вкладов
|
||||
|
||||
**Priority:** Must Have
|
||||
|
||||
**User Story:**
|
||||
As a: Пользователь кооператива
|
||||
I want to: Видеть количество своих вкладов в проект
|
||||
So that: Я понимаю свой вклад в развитие проекта
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Для каждого участника рассчитывается общее количество вкладов
|
||||
- [ ] Данные агрегируются из таблицы segments эффективно
|
||||
- [ ] Расчет не вызывает N+1 запросов к базе данных
|
||||
- [ ] Поле contributed_total добавлено в Contributor entity
|
||||
|
||||
**Technical Notes:**
|
||||
Создать функцию агрегации данных из segments. Оптимизировать запросы с использованием индексов БД. Добавить поле в Contributor entity.
|
||||
|
||||
**Dependencies:**
|
||||
STORY-001 (нужна правильная логика создания участников)
|
||||
|
||||
**Points:** 3
|
||||
|
||||
### STORY-003: Обновить GraphQL селектор
|
||||
|
||||
**Priority:** Must Have
|
||||
|
||||
**User Story:**
|
||||
As a: Frontend разработчик
|
||||
I want to: Получать данные о количестве вкладов через GraphQL API
|
||||
So that: Могу отображать информацию о вкладах в UI
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] GraphQL query capitalContributors возвращает поле totalContributions
|
||||
- [ ] Селектор ContributorSelector включает новое поле
|
||||
- [ ] API корректно агрегирует данные из backend
|
||||
- [ ] Типизация TypeScript обновлена
|
||||
|
||||
**Technical Notes:**
|
||||
Добавить поле totalContributions в ContributorSelector. Обновить GraphQL schema и resolvers. Протестировать типизацию.
|
||||
|
||||
**Dependencies:**
|
||||
STORY-002 (нужен расчет количества вкладов)
|
||||
|
||||
**Points:** 2
|
||||
|
||||
### STORY-004: Обновить UI компонент
|
||||
|
||||
**Priority:** Must Have
|
||||
|
||||
**User Story:**
|
||||
As a: Пользователь кооператива
|
||||
I want to: Видеть количество вкладов участников в графической форме
|
||||
So that: Визуально понимаю вклад каждого участника
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] ContributorsListWidget отображает количество вкладов
|
||||
- [ ] Информация представлена в понятной графической форме
|
||||
- [ ] Дизайн адаптивен для различных устройств
|
||||
- [ ] Роли участников корректно отображаются
|
||||
|
||||
**Technical Notes:**
|
||||
Обновить ContributorsListWidget.vue для отображения поля totalContributions. Добавить графические элементы (иконки, бейджи). Протестировать адаптивность.
|
||||
|
||||
**Dependencies:**
|
||||
STORY-003 (нужен GraphQL API)
|
||||
|
||||
**Points:** 2
|
||||
|
||||
### STORY-005: Тестирование новой функциональности
|
||||
|
||||
**Priority:** Must Have
|
||||
|
||||
**User Story:**
|
||||
As a: QA инженер
|
||||
I want to: Убедиться что новая функциональность работает корректно
|
||||
So that: Уверен в качестве релиза
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Интеграционное тестирование всех изменений проведено
|
||||
- [ ] Существующие функции не нарушены
|
||||
- [ ] Время загрузки списка участников не превышает 2 секунды
|
||||
- [ ] Пагинация работает плавно при большом количестве участников
|
||||
- [ ] Все acceptance criteria из tech-spec выполнены
|
||||
|
||||
**Technical Notes:**
|
||||
Создать интеграционные тесты. Провести ручное тестирование всех сценариев. Замерить производительность. Проверить кросс-браузерную совместимость.
|
||||
|
||||
**Dependencies:**
|
||||
STORY-001, STORY-002, STORY-003, STORY-004 (все изменения должны быть готовы)
|
||||
|
||||
**Points:** 3
|
||||
|
||||
---
|
||||
|
||||
## Sprint Allocation
|
||||
|
||||
### Sprint 1 (Неделя 1) - 7/8 points
|
||||
|
||||
**Goal:** Реализовать backend изменения для корректного отображения участников и их вкладов
|
||||
|
||||
**Stories:**
|
||||
- STORY-001: Исправить логику создания участников (2 points) - Must Have
|
||||
- STORY-002: Добавить расчет количества вкладов (3 points) - Must Have
|
||||
- STORY-003: Обновить GraphQL селектор (2 points) - Must Have
|
||||
|
||||
**Total:** 7 points / 8 capacity (88% utilization)
|
||||
|
||||
**Risks:**
|
||||
- Сложности с изменением логики создания участников (mitigation: предварительный анализ кода)
|
||||
|
||||
**Dependencies:**
|
||||
- Доступ к backend инфраструктуре
|
||||
|
||||
---
|
||||
|
||||
### Sprint 2 (Неделя 2) - 5/8 points
|
||||
|
||||
**Goal:** Завершить frontend обновления и протестировать новую функциональность виджета
|
||||
|
||||
**Stories:**
|
||||
- STORY-004: Обновить UI компонент (2 points) - Must Have
|
||||
- STORY-005: Тестирование новой функциональности (3 points) - Must Have
|
||||
|
||||
**Total:** 5 points / 8 capacity (63% utilization)
|
||||
|
||||
**Risks:**
|
||||
- Проблемы с производительностью при большом количестве участников (mitigation: оптимизация запросов)
|
||||
|
||||
**Dependencies:**
|
||||
- Завершенные backend изменения из Sprint 1
|
||||
|
||||
---
|
||||
|
||||
## Epic Traceability
|
||||
|
||||
| Epic ID | Epic Name | Stories | Total Points | Sprint |
|
||||
|---------|-----------|---------|--------------|--------|
|
||||
| EPIC-001 | Улучшение виджета участников проекта | STORY-001, 002, 003, 004, 005 | 12 points | Sprint 1-2 |
|
||||
|
||||
---
|
||||
|
||||
## Requirements Coverage
|
||||
|
||||
| FR ID | FR Name | Story | Sprint |
|
||||
|-------|---------|-------|--------|
|
||||
| FR-001 | Исправление логики отображения участников | STORY-001 | 1 |
|
||||
| FR-002 | Добавление поля с количеством вкладов | STORY-002, 003, 004 | 1-2 |
|
||||
| FR-003 | Интеграция с API для данных о вкладах | STORY-003 | 1 |
|
||||
| FR-004 | Отображение ролей участников | STORY-004 | 2 |
|
||||
| FR-005 | Тестирование новой функциональности | STORY-005 | 2 |
|
||||
|
||||
---
|
||||
|
||||
## Risks and Mitigation
|
||||
|
||||
**High:**
|
||||
- Технические сложности изменения логики создания участников
|
||||
- **Likelihood:** Средний
|
||||
- **Mitigation:** Предварительный анализ кода, создание плана отката
|
||||
|
||||
**Medium:**
|
||||
- Проблемы с производительностью при расчете вкладов
|
||||
- **Likelihood:** Средний
|
||||
- **Mitigation:** Оптимизация запросов, использование индексов БД
|
||||
|
||||
**Low:**
|
||||
- Несовместимость с существующей синхронизацией блокчейна
|
||||
- **Likelihood:** Низкий
|
||||
- **Mitigation:** Тщательное тестирование синхронизации данных
|
||||
|
||||
---
|
||||
|
||||
## Definition of Done
|
||||
|
||||
For a story to be considered complete:
|
||||
- [ ] Code implemented and committed
|
||||
- [ ] Unit tests written and passing (≥80% coverage)
|
||||
- [ ] Integration tests passing
|
||||
- [ ] Code reviewed and approved
|
||||
- [ ] Documentation updated
|
||||
- [ ] Acceptance criteria validated
|
||||
- [ ] Non-functional requirements met (performance, security)
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
**Immediate:** Begin Sprint 1
|
||||
|
||||
Run /create-story to create detailed story documents for Sprint 1 stories, or run /dev-story STORY-001 to implement a specific story.
|
||||
|
||||
**Sprint cadence:**
|
||||
- Sprint length: 1 week
|
||||
- Sprint planning: Monday Week 1
|
||||
- Sprint review: Friday Week 1
|
||||
- Sprint retrospective: Friday Week 1
|
||||
|
||||
**Recommended:** Start with /dev-story STORY-001 to begin backend changes
|
||||
|
||||
---
|
||||
|
||||
**This plan was created using BMAD Method v6 - Phase 4 (Implementation Planning)**
|
||||
@@ -0,0 +1,90 @@
|
||||
version: "6.0.0"
|
||||
project_name: "Виджет участников проекта"
|
||||
project_type: "web-app"
|
||||
project_level: 1
|
||||
sprint_plan_path: "docs/sprint-plan-виджет-участников-проекта-2026-02-04.md"
|
||||
current_sprint: 1
|
||||
sprint_length_weeks: 1
|
||||
|
||||
sprints:
|
||||
- sprint_number: 1
|
||||
start_date: "2026-02-04"
|
||||
end_date: "2026-02-11"
|
||||
capacity_points: 8
|
||||
committed_points: 7
|
||||
completed_points: 0
|
||||
status: "not_started"
|
||||
goal: "Реализовать backend изменения для корректного отображения участников и их вкладов"
|
||||
stories:
|
||||
- story_id: "STORY-001"
|
||||
title: "Исправить логику создания участников"
|
||||
points: 2
|
||||
status: "not_started"
|
||||
assigned_to: null
|
||||
acceptance_criteria:
|
||||
- "Участник отображается в виджете сразу после подписания соглашения об участии"
|
||||
- "Не требуется делать вклад для отображения в списке участников"
|
||||
- "Логика создания Contributor изменена с 'при первом вкладе' на 'при подтверждении доступа'"
|
||||
- story_id: "STORY-002"
|
||||
title: "Добавить расчет количества вкладов"
|
||||
points: 3
|
||||
status: "not_started"
|
||||
assigned_to: null
|
||||
acceptance_criteria:
|
||||
- "Для каждого участника рассчитывается общее количество вкладов"
|
||||
- "Данные агрегируются из таблицы segments эффективно"
|
||||
- "Расчет не вызывает N+1 запросов к базе данных"
|
||||
- "Поле contributed_total добавлено в Contributor entity"
|
||||
- story_id: "STORY-003"
|
||||
title: "Обновить GraphQL селектор"
|
||||
points: 2
|
||||
status: "not_started"
|
||||
assigned_to: null
|
||||
acceptance_criteria:
|
||||
- "GraphQL query capitalContributors возвращает поле totalContributions"
|
||||
- "Селектор ContributorSelector включает новое поле"
|
||||
- "API корректно агрегирует данные из backend"
|
||||
- "Типизация TypeScript обновлена"
|
||||
|
||||
- sprint_number: 2
|
||||
start_date: "2026-02-11"
|
||||
end_date: "2026-02-18"
|
||||
capacity_points: 8
|
||||
committed_points: 5
|
||||
completed_points: 0
|
||||
status: "pending"
|
||||
goal: "Завершить frontend обновления и протестировать новую функциональность виджета"
|
||||
stories:
|
||||
- story_id: "STORY-004"
|
||||
title: "Обновить UI компонент"
|
||||
points: 2
|
||||
status: "pending"
|
||||
assigned_to: null
|
||||
acceptance_criteria:
|
||||
- "ContributorsListWidget отображает количество вкладов"
|
||||
- "Информация представлена в понятной графической форме"
|
||||
- "Дизайн адаптивен для различных устройств"
|
||||
- "Роли участников корректно отображаются"
|
||||
- story_id: "STORY-005"
|
||||
title: "Тестирование новой функциональности"
|
||||
points: 3
|
||||
status: "pending"
|
||||
assigned_to: null
|
||||
acceptance_criteria:
|
||||
- "Интеграционное тестирование всех изменений проведено"
|
||||
- "Существующие функции не нарушены"
|
||||
- "Время загрузки списка участников не превышает 2 секунды"
|
||||
- "Пагинация работает плавно при большом количестве участников"
|
||||
- "Все acceptance criteria из tech-spec выполнены"
|
||||
|
||||
velocity:
|
||||
sprint_1: null # Будет заполнено после завершения спринта
|
||||
sprint_2: null
|
||||
rolling_average: null
|
||||
|
||||
team:
|
||||
size: 1
|
||||
sprint_length_weeks: 1
|
||||
capacity_per_sprint: 8
|
||||
developer_level: "mid"
|
||||
productive_hours_per_day: 5
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
# Technical Specification: Виджет участников проекта
|
||||
|
||||
**Date:** 2026-02-04
|
||||
**Author:** AI Assistant
|
||||
**Version:** 1.0
|
||||
**Project Type:** web-app
|
||||
**Project Level:** 1
|
||||
**Status:** Draft
|
||||
|
||||
---
|
||||
|
||||
## Document Overview
|
||||
|
||||
This Technical Specification provides focused technical planning for Виджет участников проекта. It is designed for smaller projects (Level 0-1) that need clear requirements without heavyweight PRD overhead.
|
||||
|
||||
**Related Documents:**
|
||||
- Product Brief: docs/product-brief-виджет-участников-проекта-2026-02-04.md
|
||||
|
||||
---
|
||||
|
||||
## Problem & Solution
|
||||
|
||||
### Problem Statement
|
||||
|
||||
Пользователи не имеют полной информации о своем участии в проекте. Существует две критические проблемы:
|
||||
|
||||
1. **Отсутствие информации о вкладах**: После регистрации в проекте участник получает долю авторского права, но эта информация не отображается в интерфейсе. Виджет показывает только текущую информацию об участии, но не исторические данные о вкладах.
|
||||
|
||||
2. **Неправильная логика отображения участников**: Участники отображаются в виджете только после того, как сделали конкретный вклад в проект. Однако участники должны отображаться сразу после подачи заявления на участие и получения доступа к проекту. Это приводит к тому, что новые участники не видят себя в списке участников до тех пор, пока не внесут вклад.
|
||||
|
||||
### Proposed Solution
|
||||
|
||||
Комплексное улучшение виджета участников проекта:
|
||||
|
||||
1. **Исправление логики отображения участников**: Участники должны отображаться сразу после получения доступа к проекту (после подачи заявления и подписания соглашения об участии), а не только после внесения вклада.
|
||||
|
||||
2. **Добавление информации о вкладах**: Графическое отображение количества вкладов участника и информации о процессе становления участником проекта.
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
### What Needs to Be Built
|
||||
|
||||
- Исправление логики отображения участников (отображение сразу после получения доступа)
|
||||
- Добавление поля с количеством вкладов в графической форме
|
||||
- Интеграция с API для получения данных о вкладах и статусе участников
|
||||
- Отображение ролей участников
|
||||
- Тестирование новой функциональности
|
||||
|
||||
### What This Does NOT Include
|
||||
|
||||
- Изменение существующей логики учета вкладов
|
||||
- Переработка дизайна всего виджета
|
||||
- Добавление новых типов участников
|
||||
- Интеграция с внешними системами учета
|
||||
|
||||
---
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### Technology Stack
|
||||
|
||||
- **Frontend Framework:** Vue 3 + TypeScript + Vite
|
||||
- **UI Library:** Quasar Framework
|
||||
- **State Management:** Pinia
|
||||
- **API:** GraphQL (Zeus SDK)
|
||||
- **Backend:** NestJS + TypeORM + PostgreSQL
|
||||
- **Blockchain Integration:** CoopTypes contracts
|
||||
|
||||
### Architecture Overview
|
||||
|
||||
Проект использует FSD архитектуру с разделением на:
|
||||
- **Widgets Layer:** ContributorsListWidget - компонент отображения таблицы участников
|
||||
- **Entities Layer:** Contributor entity с store для управления данными
|
||||
- **Shared Layer:** Утилиты форматирования и API клиенты
|
||||
|
||||
Данные участников хранятся в композитной сущности: часть в PostgreSQL БД, часть синхронизируется из блокчейна. Логика создания участников должна быть изменена с "при первом вкладе" на "при подтверждении доступа к проекту".
|
||||
|
||||
### Data Model (if applicable)
|
||||
|
||||
**Contributor Entity:**
|
||||
- id, username, coopname - базовые данные
|
||||
- contributor_hash - уникальный идентификатор в блокчейне
|
||||
- appendixes[] - массив хэшей проектов с доступом
|
||||
- contributed_as_* - поля с количеством вкладов по ролям
|
||||
- status - статус участника
|
||||
- created_at - дата создания
|
||||
|
||||
**Segments Table:**
|
||||
- Связь между участниками и их вкладами
|
||||
- Используется для расчета contributed_as_* полей
|
||||
|
||||
### API Design (if applicable)
|
||||
|
||||
**GraphQL Queries:**
|
||||
- `capitalContributors(filter, options)` - получение списка участников с пагинацией
|
||||
- Фильтры: coopname, project_hash (опционально)
|
||||
- Пагинация: page, limit, sortBy, descending
|
||||
|
||||
**Требуемые изменения:**
|
||||
- Модификация логики создания Contributor - создавать при подтверждении appendix вместо первого вклада
|
||||
- Добавление расчета количества вкладов для каждого участника
|
||||
- Обновление ContributorSelector для включения поля общего количества вкладов
|
||||
|
||||
---
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Stories
|
||||
|
||||
1. **Исправить логику создания участников** - Изменить процесс создания Contributor с "при первом вкладе" на "при подтверждении доступа к проекту"
|
||||
2. **Добавить расчет количества вкладов** - Создать логику агрегации данных из segments для каждого участника
|
||||
3. **Обновить GraphQL селектор** - Добавить поле общего количества вкладов в ContributorSelector
|
||||
4. **Обновить UI компонент** - Добавить отображение количества вкладов в ContributorsListWidget
|
||||
5. **Тестирование новой функциональности** - Провести интеграционное тестирование всех изменений
|
||||
|
||||
### Development Phases
|
||||
|
||||
**Phase 1: Backend Changes (3 дня)**
|
||||
- Изменение логики создания участников
|
||||
- Добавление расчета количества вкладов
|
||||
- Обновление GraphQL селектора
|
||||
|
||||
**Phase 2: Frontend Updates (2 дня)**
|
||||
- Обновление UI компонента
|
||||
- Тестирование отображения
|
||||
|
||||
**Phase 3: Integration & Testing (2 дня)**
|
||||
- Интеграционное тестирование
|
||||
- Проверка существующих функций
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
How we'll know it's done:
|
||||
|
||||
- Участники отображаются в виджете сразу после получения доступа к проекту (подписания соглашения об участии)
|
||||
- Виджет корректно отображает количество вкладов участников
|
||||
- Информация о вкладах представлена в понятной графической форме
|
||||
- Роли участников корректно отображаются после назначения
|
||||
- Новая функциональность не нарушает работу существующего виджета
|
||||
- Все acceptance criteria из Product Brief выполнены
|
||||
|
||||
---
|
||||
|
||||
## Non-Functional Requirements
|
||||
|
||||
### Performance
|
||||
|
||||
- Время загрузки списка участников не должно превышать 2 секунды
|
||||
- Расчет количества вкладов должен выполняться эффективно без дополнительных N+1 запросов
|
||||
- Пагинация должна работать плавно при большом количестве участников
|
||||
|
||||
### Security
|
||||
|
||||
- Доступ к данным участников только для авторизованных пользователей кооператива
|
||||
- Валидация всех входных данных на backend
|
||||
- Логирование операций с данными участников
|
||||
|
||||
### Other
|
||||
|
||||
- Адаптивный дизайн для различных устройств
|
||||
- Поддержка существующих браузеров (Chrome, Firefox, Safari)
|
||||
- Локализация на русский язык
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Доступ к существующей инфраструктуре PostgreSQL и блокчейна
|
||||
- Существующие API для работы с appendix и segments
|
||||
- Команда backend разработчиков для изменений в логике создания участников
|
||||
|
||||
---
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
- **Технические сложности изменения логики создания участников**
|
||||
- **Likelihood:** Средний
|
||||
- **Mitigation:** Предварительный анализ кода и создание плана отката
|
||||
|
||||
- **Проблемы с производительностью при расчете вкладов**
|
||||
- **Likelihood:** Средний
|
||||
- **Mitigation:** Оптимизация запросов, использование индексов БД
|
||||
|
||||
- **Несовместимость с существующей синхронизацией блокчейна**
|
||||
- **Likelihood:** Низкий
|
||||
- **Mitigation:** Тщательное тестирование синхронизации данных
|
||||
|
||||
---
|
||||
|
||||
## Timeline
|
||||
|
||||
**Target Completion:** 2026-02-18 (2 недели после начала разработки)
|
||||
|
||||
**Milestones:**
|
||||
- Завершение анализа требований: 2026-02-04 (1 день)
|
||||
- Разработка backend изменений: 2026-02-05 - 2026-02-07 (3 дня)
|
||||
- Frontend обновления: 2026-02-10 - 2026-02-11 (2 дня)
|
||||
- Интеграционное тестирование: 2026-02-12 - 2026-02-13 (2 дня)
|
||||
- Деплой и мониторинг: 2026-02-14 - 2026-02-18 (1 неделя)
|
||||
|
||||
---
|
||||
|
||||
## Approval
|
||||
|
||||
**Reviewed By:**
|
||||
- [ ] AI Assistant (Author)
|
||||
- [ ] Technical Lead
|
||||
- [ ] Product Owner
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Phase 4: Implementation
|
||||
|
||||
For Level 1 projects (1-10 stories):
|
||||
- Run `/sprint-planning` to plan your sprint
|
||||
- Then create and implement stories
|
||||
|
||||
---
|
||||
|
||||
**This document was created using BMAD Method v6 - Phase 2 (Planning)**
|
||||
|
||||
*To continue: Run `/workflow-status` to see your progress and next recommended workflow.*
|
||||
@@ -0,0 +1,346 @@
|
||||
# Руководство по добавлению документов в онбординг председателя кооператива (Благорост)
|
||||
|
||||
## Обзор системы
|
||||
|
||||
Онбординг председателя кооператива по программе "Благорост" представляет собой последовательность шагов, где каждый шаг - это утверждение определенного документа советом кооператива.
|
||||
|
||||
### Как работает система:
|
||||
|
||||
1. **Председатель** отправляет документ на рассмотрение совета через интерфейс онбординга
|
||||
2. **Система** создает проект свободного решения с вопросом в повестке заседания совета
|
||||
3. **Совет** голосует по вопросу и принимает решение
|
||||
4. **Система отслеживания решений** автоматически отмечает шаг онбординга как завершенный
|
||||
5. **Интерфейс** обновляется, показывая завершенный шаг
|
||||
|
||||
### Архитектура:
|
||||
|
||||
- **Frontend**: Vue.js компонент `CapitalOnboardingCard.vue` отображает список шагов
|
||||
- **Backend**: NestJS сервисы обрабатывают создание решений и отслеживание их принятия
|
||||
- **Database**: Конфигурация расширения `capital` хранит состояние онбординга
|
||||
- **Blockchain**: Решения совета записываются в блокчейн
|
||||
- **Factory**: Генерирует документы на основе шаблонов
|
||||
|
||||
## Шаги добавления нового документа в онбординг
|
||||
|
||||
### 1. Создание документа в Factory
|
||||
|
||||
#### 1.1 Создать Action (src/Actions/{registry_id}.{DocumentName}.ts)
|
||||
```typescript
|
||||
import { DraftContract } from 'cooptypes'
|
||||
import { DocumentName } from '../Templates'
|
||||
import { DocFactory } from '../Factory'
|
||||
import type { IGeneratedDocument, IGenerationOptions, IMetaDocument, ITemplate } from '../Interfaces'
|
||||
import type { MongoDBConnector } from '../Services/Databazor'
|
||||
|
||||
export { DocumentName as Template } from '../Templates'
|
||||
|
||||
export class Factory extends DocFactory<DocumentName.Action> {
|
||||
constructor(storage: MongoDBConnector) {
|
||||
super(storage)
|
||||
}
|
||||
|
||||
async generateDocument(data: DocumentName.Action, options?: IGenerationOptions): Promise<IGeneratedDocument> {
|
||||
let template: ITemplate<DocumentName.Model>
|
||||
|
||||
if (process.env.SOURCE === 'local') {
|
||||
template = DocumentName.Template
|
||||
} else {
|
||||
template = await this.getTemplate(DraftContract.contractName.production, DocumentName.registry_id, data.block_num)
|
||||
}
|
||||
|
||||
const meta: IMetaDocument = await this.getMeta({ title: template.title, ...data })
|
||||
const vars = await this.getVars(data.coopname)
|
||||
const coop = await this.getCooperative(data.coopname)
|
||||
|
||||
const combinedData: DocumentName.Model = {
|
||||
meta,
|
||||
coop,
|
||||
vars,
|
||||
}
|
||||
|
||||
await this.validate(combinedData, template.model)
|
||||
|
||||
const translation = template.translations[meta.lang]
|
||||
const document: IGeneratedDocument = await this.generatePDF('', template.context, combinedData, translation, meta, options?.skip_save)
|
||||
|
||||
return document
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.2 Создать Template (src/Templates/{registry_id}.{DocumentName}.ts)
|
||||
```typescript
|
||||
import type { JSONSchemaType } from 'ajv'
|
||||
import { Cooperative } from 'cooptypes'
|
||||
import type { ITemplate } from '../Interfaces'
|
||||
import { IMetaJSONSchema } from '../Schema/MetaSchema'
|
||||
import { CommonUserSchema, CooperativeSchema, VarsSchema } from '../Schema'
|
||||
|
||||
export const registry_id = Cooperative.Registry.DocumentName.registry_id
|
||||
|
||||
// Модель действия для генерации
|
||||
export type Action = Cooperative.Registry.DocumentName.Action
|
||||
|
||||
// Модель данных
|
||||
export type Model = Cooperative.Registry.DocumentName.Model
|
||||
|
||||
// Схема для сверки
|
||||
export const Schema: JSONSchemaType<Model> = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
meta: IMetaJSONSchema,
|
||||
coop: CooperativeSchema,
|
||||
vars: VarsSchema,
|
||||
},
|
||||
required: ['meta', 'coop', 'vars'],
|
||||
additionalProperties: true,
|
||||
}
|
||||
|
||||
export const Template: ITemplate<Model> = {
|
||||
title: Cooperative.Registry.DocumentName.title,
|
||||
description: Cooperative.Registry.DocumentName.description,
|
||||
model: Schema,
|
||||
context: Cooperative.Registry.DocumentName.context,
|
||||
translations: Cooperative.Registry.DocumentName.translations,
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.3 Добавить экспорт в src/Actions/index.ts
|
||||
```typescript
|
||||
export * as DocumentName from './{registry_id}.DocumentName'
|
||||
```
|
||||
|
||||
### 2. Создание документа в CoopTypes
|
||||
|
||||
#### 2.1 Создать определение в cooperative/registry/{registry_id}.{DocumentName}/index.ts
|
||||
```typescript
|
||||
import type { IGenerate, IMetaDocument } from '../../document'
|
||||
import type { ICommonUser, ICooperativeData, IVars } from '../../model'
|
||||
|
||||
export const registry_id = {registry_id}
|
||||
|
||||
// Модель действия для генерации
|
||||
export interface Action extends IGenerate {
|
||||
registry_id: number
|
||||
}
|
||||
|
||||
export type Meta = IMetaDocument & Action
|
||||
|
||||
// Модель данных документа
|
||||
export interface Model {
|
||||
meta: IMetaDocument
|
||||
coop: ICooperativeData
|
||||
vars: IVars
|
||||
}
|
||||
|
||||
export const title = 'Название документа'
|
||||
export const description = 'Описание документа'
|
||||
export const context = `<div class="digital-document"><!-- HTML контент --></div>`
|
||||
export const translations = {
|
||||
ru: {
|
||||
// переводы
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 Добавить в cooperative/registry/index.ts
|
||||
```typescript
|
||||
export * as DocumentName from './{registry_id}.DocumentName'
|
||||
```
|
||||
|
||||
### 3. Обновление Frontend
|
||||
|
||||
#### 3.1 Обновить composable.ts (desktop/extensions/capital/features/Onboarding/model/composable.ts)
|
||||
```typescript
|
||||
// Добавить в stepToRegistryId
|
||||
const stepToRegistryId: Record<string, number> = {
|
||||
'document_name': {registry_id},
|
||||
// ... остальные
|
||||
};
|
||||
|
||||
// Добавить шаг в stepsConfig (в нужной позиции)
|
||||
{
|
||||
id: 'document_name',
|
||||
title: 'Название документа для отображения',
|
||||
description: 'Описание что делает этот шаг',
|
||||
question: 'Вопрос для повестки совета',
|
||||
decision: '',
|
||||
decisionPrefix: 'Утвердить документ:',
|
||||
status: state?.document_name_done ? 'completed' :
|
||||
state?.onboarding_document_name_hash ? 'in_progress' : 'pending',
|
||||
hash: typeof state?.onboarding_document_name_hash === 'string' && state.onboarding_document_name_hash ? state.onboarding_document_name_hash : null,
|
||||
// depends_on: ['other_step'] // если есть зависимости
|
||||
},
|
||||
```
|
||||
|
||||
### 4. Обновление Backend DTO
|
||||
|
||||
#### 4.1 Обновить enum в onboarding.dto.ts
|
||||
```typescript
|
||||
export enum CapitalOnboardingStepEnum {
|
||||
document_name = 'document_name',
|
||||
// ... остальные
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 Добавить поля в CapitalOnboardingStateDTO
|
||||
```typescript
|
||||
@Field(() => Boolean)
|
||||
document_name_done!: boolean;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
onboarding_document_name_hash?: string | null;
|
||||
```
|
||||
|
||||
### 5. Обновление Backend Service
|
||||
|
||||
#### 5.1 Обновить типы в onboarding.service.ts
|
||||
```typescript
|
||||
type OnboardingFlagKey =
|
||||
| 'onboarding_document_name_done'
|
||||
| // ... остальные
|
||||
|
||||
type OnboardingHashKey =
|
||||
| 'onboarding_document_name_hash'
|
||||
| // ... остальные
|
||||
```
|
||||
|
||||
#### 5.2 Обновить методы маппинга
|
||||
```typescript
|
||||
private mapStepToFlag(step: CapitalOnboardingStepEnum): OnboardingFlagKey {
|
||||
switch (step) {
|
||||
case CapitalOnboardingStepEnum.document_name:
|
||||
return 'onboarding_document_name_done';
|
||||
// ... остальные
|
||||
}
|
||||
}
|
||||
|
||||
private mapStepToHash(step: CapitalOnboardingStepEnum): OnboardingHashKey {
|
||||
switch (step) {
|
||||
case CapitalOnboardingStepEnum.document_name:
|
||||
return 'onboarding_document_name_hash';
|
||||
// ... остальные
|
||||
}
|
||||
}
|
||||
|
||||
private mapStepToVarsField(step: CapitalOnboardingStepEnum): string {
|
||||
switch (step) {
|
||||
case CapitalOnboardingStepEnum.document_name:
|
||||
return 'document_name';
|
||||
// ... остальные
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.3 Обновить buildState метод
|
||||
```typescript
|
||||
private buildState(pluginConfig: IConfig & Record<string, any>): CapitalOnboardingStateDTO {
|
||||
return {
|
||||
document_name_done: !!pluginConfig.onboarding_document_name_done,
|
||||
onboarding_document_name_hash: pluginConfig.onboarding_document_name_hash || null,
|
||||
// ... остальные поля
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Обновление GraphQL Schema
|
||||
|
||||
#### 6.1 Добавить шаг в enum CapitalOnboardingStep
|
||||
```graphql
|
||||
enum CapitalOnboardingStep {
|
||||
document_name
|
||||
# ... остальные
|
||||
}
|
||||
```
|
||||
|
||||
#### 6.2 Добавить поля в type CapitalOnboardingState
|
||||
```graphql
|
||||
type CapitalOnboardingState {
|
||||
document_name_done: Boolean!
|
||||
onboarding_document_name_hash: String
|
||||
# ... остальные поля
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Обновление SDK
|
||||
|
||||
#### 7.1 Обновить selectors/capital/onboardingStateSelector.ts
|
||||
```typescript
|
||||
const onboardingStateFields = {
|
||||
document_name_done: true,
|
||||
onboarding_document_name_hash: true,
|
||||
// ... остальные
|
||||
} as const
|
||||
```
|
||||
|
||||
### 8. Обновление сервиса обработки событий
|
||||
|
||||
#### 8.1 Обновить CapitalOnboardingEventsService
|
||||
```typescript
|
||||
private mapStepToFlag(step: string): keyof IConfig | null {
|
||||
const mapping: Record<string, keyof IConfig> = {
|
||||
document_name: 'onboarding_document_name_done',
|
||||
// ... остальные
|
||||
};
|
||||
|
||||
return mapping[step] || null;
|
||||
}
|
||||
```
|
||||
|
||||
### 9. Обновление конфигурации расширения
|
||||
|
||||
#### 9.1 Добавить поля в defaultConfig (capital-extension.module.ts)
|
||||
```typescript
|
||||
export const defaultConfig = {
|
||||
// ... другие поля
|
||||
|
||||
// Онбординг флаги
|
||||
onboarding_document_name_done: false,
|
||||
// ... остальные onboarding флаги
|
||||
} as const;
|
||||
```
|
||||
|
||||
#### 9.2 Добавить поля в Zod-схему (capital-extension.module.ts)
|
||||
```typescript
|
||||
// Онбординг флаги
|
||||
onboarding_document_name_done: z
|
||||
.boolean()
|
||||
.default(defaultConfig.onboarding_document_name_done)
|
||||
.describe(describeField({ label: 'Описание шага онбординга', visible: false })),
|
||||
// ... остальные onboarding поля
|
||||
```
|
||||
|
||||
**Примечание:** Hash поля (`onboarding_document_name_hash`) не нужно добавлять в конфигурацию, так как они используются опционально и инициализируются как `null` по умолчанию.
|
||||
|
||||
## Проверка работы
|
||||
|
||||
1. **Проверить отсутствие ошибок TypeScript** в файлах:
|
||||
- `capital-extension.module.ts`
|
||||
- `onboarding-events.service.ts`
|
||||
- Других обновленных файлах
|
||||
|
||||
2. Перезапустить сервер
|
||||
3. Открыть интерфейс онбординга
|
||||
4. Проверить что новый документ появился в списке
|
||||
5. Отправить документ на рассмотрение совета
|
||||
6. Проверить логи на наличие предупреждений о неизвестном шаге
|
||||
7. Принять решение советом
|
||||
8. Проверить что шаг отмечен как завершенный
|
||||
|
||||
## Возможные проблемы
|
||||
|
||||
1. **"Неизвестный шаг онбординга"** - проверить маппинг в CapitalOnboardingEventsService
|
||||
2. **Документ не генерируется** - проверить Action и Template в Factory
|
||||
3. **Шаг не отображается** - проверить composable.ts на фронтенде
|
||||
4. **GraphQL ошибки** - проверить схему и DTO
|
||||
|
||||
## Полезные команды
|
||||
|
||||
```bash
|
||||
# Проверить логи контроллера
|
||||
tail -f logs/controller.log
|
||||
|
||||
# Перезапустить сервисы
|
||||
pnpm restart:controller
|
||||
pnpm restart:desktop
|
||||
```
|
||||
@@ -0,0 +1,106 @@
|
||||
# Система регистрации в CAPITAL: выбор пути
|
||||
|
||||
## Обзор
|
||||
|
||||
Система регистрации в CAPITAL поддерживает два пути регистрации участников кооператива: **Благорост** и **Генератор**. Каждый путь определяет набор документов, которые необходимо подписать участнику для завершения регистрации.
|
||||
|
||||
## Архитектура потоков
|
||||
|
||||
### 1. Первоначальный выбор программы (SignUp.vue)
|
||||
|
||||
**Местоположение:** `desktop/src/pages/Registrator/SignUp/SignUp.vue`
|
||||
|
||||
На этапе первичной регистрации пользователь выбирает одну из двух программ:
|
||||
- **Программа Благорост** - подписывается `BlagorostOffer` (офферта Благорост)
|
||||
- **Программа Генератор** - подписывается `GeneratorOffer` (офферта Генератор)
|
||||
|
||||
Выбор сохраняется в поле `program_key` сущности `Contributor`:
|
||||
- `GENERATION` - для программы Генератор
|
||||
- `CAPITALIZATION` - для программы Благорост
|
||||
|
||||
### 2. Завершение регистрации (CapitalRegistrationPage.vue)
|
||||
|
||||
**Местоположение:** `desktop/extensions/capital/pages/CapitalRegistrationPage/ui/CapitalRegistrationPage.vue`
|
||||
|
||||
После первоначального выбора на странице завершения регистрации генерируется разный набор документов:
|
||||
|
||||
#### Путь Генератора (`program_key = GENERATION`)
|
||||
Генерируются и подписываются **3 документа**:
|
||||
1. `GenerationContract` (Договор УХД) - всегда
|
||||
2. `StorageAgreement` (Соглашение о хранении имущества) - всегда
|
||||
3. `BlagorostAgreement` (Соглашение Благорост) - только для этого пути
|
||||
|
||||
#### Путь Благороста (`program_key = CAPITALIZATION`)
|
||||
Генерируются и подписываются **2 документа**:
|
||||
1. `GenerationContract` (Договор УХД) - всегда
|
||||
2. `StorageAgreement` (Соглашение о хранении имущества) - всегда
|
||||
3. `BlagorostAgreement` НЕ генерируется (уже подписано через оферту)
|
||||
|
||||
## Сохранение данных
|
||||
|
||||
### Contributor entity
|
||||
```typescript
|
||||
program_key?: string; // Ключ выбранной программы
|
||||
blagorost_offer_hash?: string; // Хеш подписанной оферты Благорост
|
||||
generator_offer_hash?: string; // Хеш подписанной оферты Генератор
|
||||
generation_contract_hash?: string; // Хеш подписанного договора УХД
|
||||
storage_agreement_hash?: string; // Хеш подписанного соглашения о хранении
|
||||
blagorost_agreement_hash?: string; // Хеш подписанного соглашения Благорост
|
||||
```
|
||||
|
||||
### Udata параметры документов
|
||||
Для каждого документа генерируются уникальные параметры:
|
||||
```typescript
|
||||
// Благорост
|
||||
blagorost_agreement_number: string; // Номер соглашения
|
||||
blagorost_agreement_created_at: string; // Дата создания
|
||||
|
||||
// Генератор
|
||||
generator_agreement_number: string; // Номер соглашения
|
||||
generator_agreement_created_at: string; // Дата создания
|
||||
|
||||
// Договор УХД
|
||||
generation_contract_number: string; // Номер договора
|
||||
generation_contract_created_at: string; // Дата создания
|
||||
|
||||
// Соглашение о хранении
|
||||
storage_agreement_number: string; // Номер соглашения
|
||||
storage_agreement_created_at: string; // Дата создания
|
||||
```
|
||||
|
||||
## Ключевые особенности
|
||||
|
||||
1. **Условная генерация**: Набор документов зависит от первоначального выбора программы
|
||||
2. **Единая точка сохранения**: Один ключ `blagorost_agreement_number` используется для параметров соглашения Благорост, независимо от того, подписано ли оно через оферту или отдельно
|
||||
3. **Проверка завершения**: Регистрация считается завершенной при наличии `generation_contract_hash` и `storage_agreement_hash`
|
||||
4. **Блокчейн интеграция**: Все подписанные документы отправляются в блокчейн через действие `regcontrib`
|
||||
|
||||
## API endpoints
|
||||
|
||||
### Генерация документов
|
||||
```graphql
|
||||
mutation GenerateCapitalRegistrationDocuments($data: GenerateCapitalRegistrationDocumentsInputDTO!) {
|
||||
generateCapitalRegistrationDocuments(data: $data) {
|
||||
generation_contract { html, hash }
|
||||
storage_agreement { html, hash }
|
||||
blagorost_agreement { html, hash } # опционально
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Отправка в блокчейн
|
||||
```graphql
|
||||
mutation CompleteCapitalRegistration($data: CompleteCapitalRegistrationInputDTO!) {
|
||||
completeCapitalRegistration(data: $data) {
|
||||
transaction_id
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Расширения и сервисы
|
||||
|
||||
- **ParticipationManagementInteractor**: бизнес-логика регистрации
|
||||
- **UdataDocumentParametersService**: генерация параметров документов
|
||||
- **RegistrationDocumentsService**: генерация пакета документов
|
||||
- **useGenerateCapitalRegistrationDocuments**: фронтенд-композабл для генерации
|
||||
- **useCompleteCapitalRegistration**: фронтенд-композабл для отправки в блокчейн
|
||||
@@ -0,0 +1,147 @@
|
||||
# 🚨 Быстрое исправление циклических зависимостей
|
||||
|
||||
## Увидел ошибку? Действуй!
|
||||
|
||||
```
|
||||
Error: A circular dependency has been detected inside DocumentDomainModule
|
||||
```
|
||||
|
||||
### Шаг 1: Запусти анализ (30 секунд)
|
||||
|
||||
```bash
|
||||
pnpm analyze:modules
|
||||
```
|
||||
|
||||
Откроются 2 файла:
|
||||
- `potential-circular-dependencies.md` - **НАЧНИ С ЭТОГО!**
|
||||
- `module-dependency-graph.md` - полный граф
|
||||
|
||||
### Шаг 2: Найди проблему в отчёте
|
||||
|
||||
Открой `potential-circular-dependencies.md` и ищи:
|
||||
|
||||
#### A) Секция "CRITICAL: Global modules importing non-global"
|
||||
```md
|
||||
### ❌ BlockchainModule (@Global)
|
||||
**Problematic imports:**
|
||||
- ❌ RegistrationDomainModule (not global) 👈 ВОТ ОНО!
|
||||
```
|
||||
|
||||
**Решение:**
|
||||
```typescript
|
||||
// Вариант 1: Убери импорт
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [], // Убрал RegistrationDomainModule
|
||||
})
|
||||
export class BlockchainModule {}
|
||||
|
||||
// Вариант 2: Сделай импортируемый модуль тоже глобальным
|
||||
@Global() // Добавил
|
||||
@Module({...})
|
||||
export class RegistrationDomainModule {}
|
||||
```
|
||||
|
||||
#### B) Секция "Highly Coupled Modules"
|
||||
```md
|
||||
### DocumentDomainModule 📦
|
||||
⚠️ WARNING: Non-global module used by global module(s)! 👈 ПРОБЛЕМА!
|
||||
```
|
||||
|
||||
**Решение:**
|
||||
```typescript
|
||||
// В глобальном модуле используй forwardRef
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [forwardRef(() => DocumentDomainModule)],
|
||||
})
|
||||
```
|
||||
|
||||
### Шаг 3: Если анализ не помог - метод комментирования
|
||||
|
||||
**В `app.module.ts`:**
|
||||
|
||||
```typescript
|
||||
@Module({
|
||||
imports: [
|
||||
// Инфраструктура
|
||||
DatabaseModule,
|
||||
RedisModule,
|
||||
|
||||
// Domain - КОММЕНТИРУЙ ПО ОДНОМУ!
|
||||
// AuthDomainModule,
|
||||
// AccountDomainModule,
|
||||
RegistrationDomainModule, // 👈 Раскомментируй это
|
||||
// DocumentDomainModule, // 👈 Потом это
|
||||
// ParticipantDomainModule, // 👈 Потом это
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
**Алгоритм:**
|
||||
1. Закомментируй ВСЕ domain-модули
|
||||
2. Раскомментируй по одному
|
||||
3. После каждого раскомментирования запускай: `pnpm dev`
|
||||
4. Когда ошибка появится → ты нашёл виновника!
|
||||
|
||||
### Шаг 4: Применяй стандартные решения
|
||||
|
||||
#### Решение 1: forwardRef в imports
|
||||
```typescript
|
||||
@Module({
|
||||
imports: [
|
||||
forwardRef(() => AccountDomainModule),
|
||||
forwardRef(() => DocumentDomainModule),
|
||||
],
|
||||
})
|
||||
export class ParticipantDomainModule {}
|
||||
```
|
||||
|
||||
#### Решение 2: forwardRef в инжекции
|
||||
```typescript
|
||||
@Injectable()
|
||||
export class MyService {
|
||||
constructor(
|
||||
@Inject(forwardRef(() => SOME_SERVICE))
|
||||
private readonly someService: SomeService
|
||||
) {}
|
||||
}
|
||||
```
|
||||
|
||||
#### Решение 3: Убрать лишний модуль
|
||||
```typescript
|
||||
// ❌ БЫЛО два модуля
|
||||
// document/document.module.ts
|
||||
// document/document-validation.module.ts
|
||||
|
||||
// ✅ СТАЛО один модуль
|
||||
@Module({
|
||||
providers: [
|
||||
DocumentService,
|
||||
DocumentValidationService, // Просто добавили сюда
|
||||
],
|
||||
})
|
||||
export class DocumentDomainModule {}
|
||||
```
|
||||
|
||||
## 🎯 Чеклист: Что НЕ делать
|
||||
|
||||
- ❌ Не меняй порядок импортов (не поможет!)
|
||||
- ❌ Не делай модуль глобальным без необходимости
|
||||
- ❌ Не создавай отдельный модуль для одного сервиса
|
||||
- ❌ Не импортируй не-глобальные модули в глобальные (без forwardRef)
|
||||
|
||||
## 🎯 Чеклист: Что делать
|
||||
|
||||
- ✅ Используй `pnpm analyze:modules` ПЕРВЫМ делом
|
||||
- ✅ Используй forwardRef для domain-модулей
|
||||
- ✅ Проверь, не является ли проблемный модуль глобальным
|
||||
- ✅ Используй метод комментирования для точной локализации
|
||||
|
||||
## 📚 Подробности
|
||||
|
||||
См. полное руководство: `CIRCULAR_DEPENDENCIES_GUIDE.md`
|
||||
|
||||
---
|
||||
|
||||
*Сохрани этот файл в закладки - он спасёт тебя часы отладки!*
|
||||
@@ -0,0 +1,44 @@
|
||||
Правило 0. Secondary index используется ровно один раз — для получения primary key. Любые чтения и все изменения состояния выполняются только по primary key.
|
||||
|
||||
Запреты
|
||||
- ❌ Повторный get_*_by_hash() после emplace/modify
|
||||
- ❌ Передача hash в write-функции
|
||||
- ❌ Возврат row-объектов для дальнейшей мутации
|
||||
|
||||
Разрешено
|
||||
✅ find(id) сколько угодно раз
|
||||
✅ modify(id) после любых изменений
|
||||
✅ Передача id между всеми слоями
|
||||
|
||||
В прикладной форме
|
||||
- checksum256 / secondary_id — только вход
|
||||
- uint64_t (primary key) — вся внутренняя логика
|
||||
|
||||
В одном action:
|
||||
- byhash.find() → получить id
|
||||
- дальше запрещены любые повторные lookup по secondary index
|
||||
|
||||
Правило 1. Поиск по secondary_index'ам должен быть однократным. Запрещено в рамках одного действия повторно обращаться к поиску по secondary_index.
|
||||
|
||||
ПРАВИЛЬНО:
|
||||
auto project = get_project_or_fail(coopname, project_hash);
|
||||
uint64_t project_id = project.id;
|
||||
|
||||
// дальше ВЕЗДЕ
|
||||
set_master(coopname, project_id, master);
|
||||
increment_total_authors(coopname, project_id);
|
||||
upsert_author_segment(coopname, project_id, master);
|
||||
|
||||
|
||||
НЕ ПРАВИЛЬНО:
|
||||
increment_total_authors(coopname, project_hash); // внутри снова lookup
|
||||
|
||||
Правило 3. При работе с итераторами не используй перевод в объект
|
||||
segments_index segments(_capital, coopname.value);
|
||||
auto segment = segments.find(segment_id);
|
||||
|
||||
auto amount = segment->debt_amount // ПРАВИЛЬНО
|
||||
|
||||
auto segment_ = *segment // НЕ ПРАВИЛЬНО
|
||||
auto amount = segment.amount
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
Чистая фрактальная трехуровневая архитектура срезов домена (domain) и инфраструктуры (infrastructure), оркестрируемая приложением (application). Фрактал раскрывается в расширениях (extensions), каждое из которых также выстраивается по правилам чистой трехуровневой архитектуры срезов домена и инфраструктуры с окрестрацией на уровне своего приложения.
|
||||
|
||||
Общая архитектура связей:
|
||||
- AppResolver -> AppService[DTO<->Domain] -> AppInteractor -> DomainService -> DomainPort <- InfraAdapter -> [при необходимости] AnyAppOrDomainService
|
||||
|
||||
Все сервисы внедряются через инъекцию (DI) с указанием символа реализации. Импорты разрешены только вглубь - к домену. Из домена переход разрешен только в инфраструктуру через порт и его адаптер.
|
||||
|
||||
Прямые импорты без DI запрещены. Импорты из соседних срезов того же архитектурного уровня запрещены. Направление зависимостей от приложения - к домену, от домена через порт в его инфраструктурный адаптер.
|
||||
|
||||
Взаимодействие расширений с основным приложением осуществляется только через порты домена основного приложения.
|
||||
|
||||
Глобальные модули запрещены. В редких случаях сложных циклических зависимостей на период рефакторинга разрешается обоснованно использовать forwardRef.
|
||||
|
||||
Уровень приложения оркестрирует вызовы доменов через порты в инфраструктуру. Домены не обращаются друг к друга. Срезы уровня приложений не обращаются друг к другу, а только вызывают порты домена.
|
||||
@@ -0,0 +1,239 @@
|
||||
# Фабрика отслеживания решений (Decision Tracking Factory)
|
||||
|
||||
## Назначение
|
||||
|
||||
Фабрика отслеживания решений — это переиспользуемый сервис для автоматического обновления системных переменных (vars) при принятии решений советом или общим собранием пайщиков.
|
||||
|
||||
## Архитектура
|
||||
|
||||
Система построена согласно чистой архитектуре:
|
||||
|
||||
```
|
||||
domain/decision-tracking/ # Домен
|
||||
├── interfaces/ # Интерфейсы
|
||||
│ └── tracking-rule-domain.interface.ts
|
||||
├── ports/ # Порты
|
||||
│ └── decision-tracking.port.ts
|
||||
├── events/ # События
|
||||
│ └── decision-tracked.event.ts
|
||||
└── index.ts
|
||||
|
||||
infrastructure/decision-tracking/ # Инфраструктура
|
||||
├── adapters/ # Адаптеры
|
||||
│ └── decision-tracking.adapter.ts
|
||||
├── repositories/ # Репозитории
|
||||
│ └── tracking-rule.repository.ts
|
||||
└── decision-tracking-infrastructure.module.ts
|
||||
```
|
||||
|
||||
## Принцип работы
|
||||
|
||||
### 1. Регистрация правила отслеживания
|
||||
|
||||
Расширение регистрирует правило отслеживания при создании решения:
|
||||
|
||||
```typescript
|
||||
await decisionTrackingPort.registerTrackingRule({
|
||||
hash: documentHash,
|
||||
event_type: DecisionEventType.SOVIET_DECISION, // или MEET_DECISION
|
||||
vars_field: 'wallet_agreement',
|
||||
metadata: {
|
||||
onboarding_step: 'wallet_agreement',
|
||||
custom_data: '...'
|
||||
},
|
||||
expires_at: new Date('2025-02-15')
|
||||
});
|
||||
```
|
||||
|
||||
### 2. Автоматическое отслеживание
|
||||
|
||||
Фабрика автоматически подписывается на события блокчейна:
|
||||
- `action::soviet::newresolved` - решения совета
|
||||
- `action::meet::newresolved` - решения общих собраний
|
||||
- `action::meet::restartmeet` - перезапуск общих собраний
|
||||
|
||||
### 3. Обработка совпадений
|
||||
|
||||
При получении события с подходящим hash фабрика:
|
||||
1. Находит соответствующее правило отслеживания
|
||||
2. Обновляет поле в vars (если указаны decision_id и decision_date)
|
||||
3. Деактивирует правило
|
||||
4. Эмитит событие `decision.tracked`
|
||||
|
||||
### 4. Пост-обработка через события
|
||||
|
||||
Расширения могут подписаться на событие `decision.tracked` для дополнительной обработки:
|
||||
|
||||
```typescript
|
||||
@OnEvent(DecisionTrackedEvent.eventName)
|
||||
async handleDecisionTracked(event: DecisionTrackedEvent): Promise<void> {
|
||||
const { result } = event;
|
||||
|
||||
// Выполнить дополнительные действия
|
||||
if (result.metadata?.onboarding_step) {
|
||||
await this.updateOnboardingProgress(result.metadata.onboarding_step);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Использование
|
||||
|
||||
### В расширениях
|
||||
|
||||
1. Импортировать модуль инфраструктуры:
|
||||
|
||||
```typescript
|
||||
@Module({
|
||||
imports: [
|
||||
DecisionTrackingInfrastructureModule,
|
||||
// ...
|
||||
],
|
||||
})
|
||||
export class MyExtensionModule {}
|
||||
```
|
||||
|
||||
2. Инжектировать порт:
|
||||
|
||||
```typescript
|
||||
constructor(
|
||||
@Inject(DECISION_TRACKING_PORT)
|
||||
private readonly decisionTrackingPort: DecisionTrackingPort
|
||||
) {}
|
||||
```
|
||||
|
||||
3. Регистрировать правила при создании решений:
|
||||
|
||||
```typescript
|
||||
await this.decisionTrackingPort.registerTrackingRule({
|
||||
hash: documentHash,
|
||||
event_type: DecisionEventType.SOVIET_DECISION,
|
||||
vars_field: 'my_agreement',
|
||||
metadata: { /* custom data */ }
|
||||
});
|
||||
```
|
||||
|
||||
4. Подписаться на события отслеживания (опционально):
|
||||
|
||||
```typescript
|
||||
@OnEvent(DecisionTrackedEvent.eventName)
|
||||
async handleTracked(event: DecisionTrackedEvent) {
|
||||
// Пост-обработка
|
||||
}
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### DecisionTrackingPort
|
||||
|
||||
#### registerTrackingRule(input)
|
||||
Регистрирует новое правило отслеживания.
|
||||
|
||||
**Параметры:**
|
||||
- `hash` - Hash документа для отслеживания
|
||||
- `event_type` - Тип события (SOVIET_DECISION или MEET_DECISION)
|
||||
- `vars_field` - Ключ в vars для обновления
|
||||
- `metadata` - Метаданные для пост-обработки
|
||||
- `expires_at` - Дата истечения правила (опционально)
|
||||
|
||||
**Возвращает:** `TrackingRuleDomainInterface`
|
||||
|
||||
#### updateTrackingRuleHash(oldHash, newHash)
|
||||
Обновляет hash в существующем правиле (используется при перезапуске общих собраний).
|
||||
|
||||
#### getActiveRules()
|
||||
Получает все активные правила отслеживания.
|
||||
|
||||
#### getRuleByHash(hash)
|
||||
Получает правило по hash документа.
|
||||
|
||||
#### deactivateRule(id)
|
||||
Деактивирует правило отслеживания.
|
||||
|
||||
#### deleteRule(id)
|
||||
Удаляет правило отслеживания.
|
||||
|
||||
|
||||
## События
|
||||
|
||||
### DecisionTrackedEvent
|
||||
|
||||
Эмитится при успешном отслеживании и обработке решения.
|
||||
|
||||
**Данные события:**
|
||||
```typescript
|
||||
{
|
||||
matched: true,
|
||||
rule_id: 'uuid',
|
||||
hash: 'document_hash',
|
||||
event_type: DecisionEventType.SOVIET_DECISION,
|
||||
vars_field: 'wallet_agreement',
|
||||
decision_id: '123',
|
||||
decision_date: '2025-01-17T12:00:00Z',
|
||||
metadata: { /* custom data */ }
|
||||
}
|
||||
```
|
||||
|
||||
## Особенности
|
||||
|
||||
### Общие собрания
|
||||
Для общих собраний поддерживается автоматическое обновление hash при перезапуске через событие `restartmeet`.
|
||||
|
||||
### Управление правилами
|
||||
Расширения сами управляют своими правилами отслеживания. Если нужно отключить отслеживание, расширение должно вызвать `deactivateRule()` или `deleteRule()`. Фабрика не имеет встроенной логики истечения правил.
|
||||
|
||||
### TypeORM хранилище
|
||||
Используется TypeORM репозиторий с PostgreSQL для персистентного хранения правил отслеживания. Правила сохраняются в таблице `tracking_rules`.
|
||||
|
||||
## Пример: Онбординг председателя
|
||||
|
||||
```typescript
|
||||
// Регистрация отслеживания решения
|
||||
const document = await this.generateDecisionDocument(...);
|
||||
|
||||
await this.decisionTrackingPort.registerTrackingRule({
|
||||
hash: document.hash,
|
||||
event_type: DecisionEventType.SOVIET_DECISION,
|
||||
vars_field: 'wallet_agreement',
|
||||
metadata: {
|
||||
onboarding_step: 'wallet_agreement',
|
||||
},
|
||||
expires_at: onboardingExpireDate,
|
||||
});
|
||||
|
||||
// Подписка на событие для обновления статуса онбординга
|
||||
@OnEvent(DecisionTrackedEvent.eventName)
|
||||
async handleDecisionTracked(event: DecisionTrackedEvent) {
|
||||
if (event.result.metadata?.onboarding_step) {
|
||||
await this.markOnboardingStepComplete(
|
||||
event.result.metadata.onboarding_step
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Миграции базы данных
|
||||
|
||||
При первом запуске будет автоматически создана таблица `tracking_rules`:
|
||||
|
||||
```sql
|
||||
CREATE TABLE tracking_rules (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
hash VARCHAR(64) NOT NULL UNIQUE,
|
||||
event_type VARCHAR(20) NOT NULL,
|
||||
vars_field VARCHAR(50) NOT NULL,
|
||||
metadata JSONB,
|
||||
active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
expires_at TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_tracking_rules_hash ON tracking_rules(hash);
|
||||
```
|
||||
|
||||
## Преимущества
|
||||
|
||||
1. **Переиспользуемость** - Единая логика отслеживания для всех расширений
|
||||
2. **Разделение ответственности** - Фабрика отвечает только за отслеживание и обновление vars
|
||||
3. **Расширяемость** - Пост-обработка через события не нарушает DI
|
||||
4. **Автоматизация** - Отслеживание происходит автоматически без ручного вмешательства
|
||||
5. **Чистая архитектура** - Соблюдение принципов разделения на домен и инфраструктуру
|
||||
@@ -0,0 +1,118 @@
|
||||
# Техническое задание: Архитектура маркетплейса приложений для распределенных бэкендов
|
||||
|
||||
## Контекст проблемы
|
||||
|
||||
Мы имеем распределенную систему из множества бэкендов, каждый из которых обладает собственным локальным магазином приложений. Требуется централизовать управление приложениями через единый маркетплейс, обеспечив контроль доступа и монетизацию.
|
||||
|
||||
## Основные требования
|
||||
|
||||
1. **Централизованный каталог** приложений с возможностью монетизации
|
||||
2. **Установка приложений на уровне бэкенда** (не пользователя)
|
||||
3. **Контроль доступа** к платным приложениям
|
||||
4. **Минимальная нагрузка** на систему валидации
|
||||
5. **Безопасность** без единой точки отказа
|
||||
|
||||
## Предлагаемая архитектура
|
||||
|
||||
### 1. Компоненты системы
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
||||
│ Маркетплейс │────│ Распределенные │────│ Приложения │
|
||||
│ (Central API) │ │ Бэкенды │ │ (Developers) │
|
||||
└─────────────────┘ └──────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
### 2. Модель токенов
|
||||
|
||||
**Backend Admin Token**
|
||||
- Долгоживущий (365 дней)
|
||||
- Права: установка/удаление приложений, управление лицензиями
|
||||
- Используется только в server-to-server коммуникации
|
||||
|
||||
**Backend User Token**
|
||||
- Краткосрочный (1 час)
|
||||
- Права: чтение списка установленных приложений
|
||||
- Используется для загрузки каталога пользователями
|
||||
|
||||
**User App Access Token**
|
||||
- Краткосрочный (30 минут)
|
||||
- Содержит: backend_id, user_id, app_id, permissions
|
||||
- Передается в IFrame приложений для валидации доступа
|
||||
|
||||
### 3. Workflow установки приложений
|
||||
|
||||
**Для администраторов бэкенда:**
|
||||
1. Админ запрашивает полный каталог через Backend Admin Token
|
||||
2. Маркетплейс возвращает все приложения со статусами лицензий
|
||||
3. Админ выбирает приложение для установки
|
||||
4. Система проверяет наличие активной лицензии
|
||||
5. При успехе - создается запись об установке
|
||||
6. Бэкенд скачивает файлы приложения с серверов разработчика
|
||||
|
||||
**Для обычных пользователей:**
|
||||
1. Пользователь заходит на сайт бэкенда
|
||||
2. Система запрашивает список установленных приложений через Backend User Token
|
||||
3. Маркетплейс возвращает только доступные для данного бэкенда приложения
|
||||
4. Пользователь выбирает приложение
|
||||
5. Бэкенд генерирует User App Access Token
|
||||
6. Приложение загружается в IFrame с переданным токеном
|
||||
|
||||
### 4. Механизм контроля доступа
|
||||
|
||||
**Трехуровневая валидация:**
|
||||
|
||||
1. **Предварительная проверка** - маркетплейс фильтрует доступные приложения
|
||||
2. **Валидация при загрузке** - приложение проверяет JWT токен локально
|
||||
3. **Периодическая ревалидация** - приложение проверяет токен каждые 5 минут
|
||||
|
||||
**Техническая реализация валидации:**
|
||||
- Приложения кэшируют публичные ключи бэкендов (24 часа)
|
||||
- Валидация JWT происходит статически без запросов к API
|
||||
- Короткое время жизни токенов (30 мин) минимизирует риски
|
||||
|
||||
### 5. Монетизация и лицензии
|
||||
|
||||
**Модель лицензирования:**
|
||||
- Лицензия выдается на уровень бэкенда
|
||||
- Поддержка различных моделей оплаты (месячная, годовая, perpetual)
|
||||
- Trial-периоды для тестирования
|
||||
- Автоматическое отключение при неоплате
|
||||
|
||||
**Схема данных лицензий:**
|
||||
```sql
|
||||
backend_licenses (backend_id, app_id, status, expires_at, payment_plan)
|
||||
installations (backend_id, app_id, installed_at, config)
|
||||
```
|
||||
|
||||
### 6. Безопасность и производительность
|
||||
|
||||
**Меры безопасности:**
|
||||
- Разделение прав через разные типы токенов
|
||||
- Короткое время жизни пользовательских токенов
|
||||
- Статическая валидация без зависимостей от API маркетплейса
|
||||
- Кэширование криптографических ключей
|
||||
|
||||
**Оптимизация производительности:**
|
||||
- Локальная проверка JWT подписей (~1-5ms)
|
||||
- Кэширование публичных ключей (24 часа)
|
||||
- Минимальные запросы к центральному API
|
||||
- Предварительная фильтрация на стороне маркетплейса
|
||||
|
||||
## Критические вопросы для дальнейшего исследования
|
||||
|
||||
1. **Механизм отзыва доступа** - как оперативно блокировать приложения при неоплате?
|
||||
2. **Кэширование и инвалидация** - стратегии обновления публичных ключей
|
||||
3. **Мониторинг и аналитика** - отслеживание использования платных функций
|
||||
4. **Grace period** -如何处理 situations when payment is delayed?
|
||||
5. **Миграция с текущей системы** - план перехода с локальных магазинов
|
||||
|
||||
## Expected Outcomes
|
||||
|
||||
- Единая точка управления приложениями
|
||||
- Контроль доступа к платному контенту
|
||||
- Масштабируемая архитектура
|
||||
- Минимальное влияние на производительность
|
||||
- Гибкая система монетизации
|
||||
|
||||
Требуется проработка каждого компонента с учетом конкретных технических ограничений и бизнес-требований.
|
||||
@@ -0,0 +1,10 @@
|
||||
## Подключение (frontend)
|
||||
|
||||
Зачем: дать кооперативу пройти «подключение» на платформе, фиксируя шаги через документы/решения.
|
||||
|
||||
Как работает:
|
||||
- Страница `ConnectPage` (маршрут `/chairman/connect`) выводит 6 шагов: 5 предложений повестки (кошелёк, ПЭП, privacy, ПС, заявления) + 1 шаг общего собрания.
|
||||
- Шаги 1–5: кнопка открывает диалог с автотекстом → фронт шлёт `completeChairmanAgendaStep`, бэк генерирует и сразу публикует проект решения в блокчейн, сохраняет hash документа и ждёт подтверждения решения (событие `newresolved`). До события шаг помечается «в процессе».
|
||||
- Шаг 6: используется стандартный поток `createMeetWithAgenda` (генерация + подпись повестки), фронт берёт hash повестки и передаёт в `completeChairmanGeneralMeetStep`; бэк ждёт `newresolved` с тем же hash.
|
||||
- Отображение статуса: флаги из `getChairmanOnboardingState`. Для шагов 1–5, если флага нет, но в `vars` уже есть шапка документа — считаем выполненным (приоритет: vars).
|
||||
- Таймер: показывается время до `onboarding_expire_at` (30 дней от старта, выдаёт бэк). После истечения шаги не принимаются автоматически (ждём обновлений с сервера).
|
||||
@@ -0,0 +1,65 @@
|
||||
# ChatCoop Extension
|
||||
|
||||
Расширение для интеграции Matrix чата в кооперативную систему.
|
||||
|
||||
## Описание
|
||||
|
||||
ChatCoop предоставляет встроенный Matrix чат для пользователей кооператива. Расширение:
|
||||
|
||||
- Получает временный токен аутентификации через GraphQL API
|
||||
- Отображает Matrix клиент (Element Web) в iframe
|
||||
- Автоматически аутентифицирует пользователя с полученным токеном
|
||||
|
||||
## Структура
|
||||
|
||||
```
|
||||
chatcoop/
|
||||
├── install.ts # Конфигурация рабочего стола
|
||||
├── entities/
|
||||
│ └── ChatCoopChat/ # Entity для работы с Matrix токенами
|
||||
│ ├── api/ # GraphQL запросы через SDK
|
||||
│ └── model/ # Store и типы данных из SDK
|
||||
├── pages/
|
||||
│ └── ChatCoopPage/ # Главная страница с iframe
|
||||
├── shared/ # Общие компоненты
|
||||
└── widgets/ # Виджеты
|
||||
```
|
||||
|
||||
## SDK интеграция
|
||||
|
||||
Расширение использует типизированные запросы из SDK:
|
||||
|
||||
- **Query**: `Queries.ChatCoop.GetToken`
|
||||
- **Selector**: `chatcoopTokenSelector`
|
||||
- **Типы**: Автоматически генерируются из GraphQL схемы
|
||||
|
||||
## Работа с iframe URL
|
||||
|
||||
1. При загрузке страницы вызывается `chatcoopStore.loadToken()`
|
||||
2. Entity отправляет типизированный GraphQL запрос через SDK: `Queries.ChatCoop.GetToken`
|
||||
3. Бэкенд проверяет/создает Matrix пользователя и генерирует токен
|
||||
4. Бэкенд формирует полную iframe URL с токеном аутентификации
|
||||
5. Store сохраняет полученную ссылку в состоянии (типизировано через SDK)
|
||||
6. Страница загружает iframe с готовой ссылкой
|
||||
7. Пользователь автоматически входит в Matrix чат
|
||||
|
||||
## Конфигурация
|
||||
|
||||
- **MATRIX_CLIENT_URL**: URL Element Web клиента
|
||||
- **workspace**: 'chatcoop'
|
||||
- **defaultRoute**: 'chat'
|
||||
|
||||
## Архитектура
|
||||
|
||||
Расширение построено по принципам Feature-Sliced Design (FSD):
|
||||
|
||||
- **Entities**: `ChatCoopChat` - бизнес-сущность для работы с Matrix токенами
|
||||
- **Pages**: `ChatCoopPage` - UI страница без бизнес-логики
|
||||
- **Store**: Pinia store для управления состоянием токена
|
||||
- **API**: Функции для GraphQL запросов
|
||||
|
||||
## Зависимости
|
||||
|
||||
- GraphQL API с resolver `getToken`
|
||||
- Matrix Synapse сервер
|
||||
- Element Web клиент
|
||||
@@ -0,0 +1,121 @@
|
||||
# Справка по обновленным текстам системы Powerup
|
||||
|
||||
## Обзор изменений
|
||||
|
||||
Все тексты интерфейса системы powerup переработаны для понятного объяснения работы системы пользователям-кооперативам без технического образования.
|
||||
|
||||
## Основные концепции, раскрытые в текстах
|
||||
|
||||
### 1. Система квот и ресурсов
|
||||
- **1 квота = 5 AXON** на 24 часа
|
||||
- Распределение: 50% RAM (~24.4 КБ), 25% CPU, 25% NET
|
||||
- Курс конвертации: **1 AXON = 10 RUB**
|
||||
- Курс RAM: **1 байт = 0.0001 AXON**
|
||||
|
||||
### 2. Пакеты документов
|
||||
- Один пакет документов ≈ 10 КБ RAM
|
||||
- Одна квота позволяет хранить ~2 пакета документов одновременно
|
||||
- **Ключевой момент**: квоту можно использовать многократно в течение 24 часов после освобождения памяти
|
||||
- Квота ограничивает **одновременное** использование, а не общее количество операций за сутки
|
||||
|
||||
### 3. Регистрация аккаунтов
|
||||
- **1 AXON за аккаунт** (единоразовая оплата за постоянное хранение)
|
||||
- Дополнительно требуются квоты для обработки регистрационных документов
|
||||
|
||||
### 4. Автоматическое управление
|
||||
- При использовании >70% любого ресурса → автоматическая аренда квоты за 5 AXON
|
||||
- Ежедневное автоматическое пополнение в 00:00 на минимальную квоту (5 AXON)
|
||||
- Автоматическое продление аренды при возврате квот, если память используется
|
||||
|
||||
### 5. Делегаты
|
||||
- Операторы серверов, предоставляющие вычислительные мощности
|
||||
- Получают AXON за предоставление ресурсов
|
||||
- Могут обменять AXON на RUB через паевой взнос у кооператива-оператора
|
||||
|
||||
## Измененные файлы
|
||||
|
||||
### 1. ResourceInfoWidget.vue
|
||||
**Основной информационный блок**
|
||||
- Обновлено краткое описание системы аренды
|
||||
- Полностью переработан диалог "Как это работает" с разделами:
|
||||
- Что такое вычислительные ресурсы
|
||||
- Система квот: аренда ресурсов
|
||||
- Пакеты документов
|
||||
- Многократное использование
|
||||
- Возврат и продление аренды
|
||||
- Кто предоставляет ресурсы (делегаты)
|
||||
- Регистрация аккаунтов пайщиков
|
||||
- Автоматическое управление
|
||||
|
||||
**Добавлен FAQ с 7 вопросами:**
|
||||
1. Из чего складывается стоимость пакета документов?
|
||||
2. Сколько пакетов документов я могу обработать за день на одной квоте?
|
||||
3. Почему мне списали больше, чем минимальные 5 AXON в день?
|
||||
4. Что будет, если у меня закончатся AXON?
|
||||
5. Как узнать, сколько AXON мне нужно в месяц?
|
||||
6. Могу ли я вернуть неиспользованные AXON?
|
||||
|
||||
### 2. CpuResourceWidget.vue
|
||||
Обновлена подсказка:
|
||||
- Старая: "CPU ресурсы восстанавливаются со временем. Текущее использование показывает нагрузку в данный момент."
|
||||
- Новая: "CPU используется для вычислений при обработке документов. Квота восстанавливается автоматически через несколько минут после использования."
|
||||
|
||||
### 3. NetResourceWidget.vue
|
||||
Обновлена подсказка:
|
||||
- Старая: "Сетевые ресурсы восстанавливаются со временем. Показывают объем переданных данных в байтах."
|
||||
- Новая: "NET используется для передачи данных документов между узлами блокчейна. Квота восстанавливается автоматически через несколько минут."
|
||||
|
||||
### 4. RamResourceWidget.vue
|
||||
Обновлена подсказка:
|
||||
- Старая: "RAM ресурсы приобретаются и освобождаются. Показывают объем занятой памяти в байтах."
|
||||
- Новая: "RAM используется для хранения данных пайщиков и документов в смарт-контрактах. Освобождается после завершения обработки документов."
|
||||
|
||||
### 5. AxonWalletCard.vue
|
||||
Обновлена подсказка:
|
||||
- Старая: "AXON используется для оплаты пакетов документов. Минимально 5 AXON в день, по факту - от использования."
|
||||
- Новая: "AXON используется для аренды вычислительных ресурсов (минимум 5 AXON/день) и регистрации пайщиков (1 AXON/аккаунт). Курс: 1 AXON = 10 RUB."
|
||||
|
||||
## Примеры расчетов для пользователей
|
||||
|
||||
### Минимальный бюджет (1500 RUB / 150 AXON в месяц)
|
||||
- 30 дней × 5 AXON = 150 AXON
|
||||
- Покрывает только минимальные ежедневные квоты
|
||||
- Регистрация пайщиков требует дополнительных AXON
|
||||
|
||||
### Средний кооператив (50 пайщиков, умеренная активность)
|
||||
- Базовые квоты: ~150 AXON
|
||||
- Дополнительные квоты при активности: ~30 AXON
|
||||
- Буфер безопасности: ~20 AXON
|
||||
- **Итого: ~200 AXON в месяц (2000 RUB)**
|
||||
|
||||
### Регистрация пайщиков
|
||||
- **Последовательно**: 145 пайщиков за день на одной квоте (если быстро обрабатываются)
|
||||
- **Одновременно**: ~6 пайщиков (3 пакета по 2 пайщика) на одной квоте
|
||||
- **При массовой регистрации**: планировать дополнительные квоты
|
||||
|
||||
## Ключевые сообщения для пользователей
|
||||
|
||||
1. **Вы платите за реальное использование**, а не за абстрактные единицы
|
||||
2. **Квоты можно использовать многократно** в течение 24 часов
|
||||
3. **Стоимость прозрачна и предсказуема**: 1 AXON = 10 RUB
|
||||
4. **Система автоматическая**: не требует постоянного контроля
|
||||
5. **Делегаты получают справедливую оплату** за поддержку инфраструктуры
|
||||
6. **Рекомендуемый буфер**: 50-100 AXON для бесперебойной работы
|
||||
|
||||
## Стиль подачи информации
|
||||
|
||||
- ✅ Понятные аналогии (аренда вычислительных мощностей)
|
||||
- ✅ Конкретные примеры расчетов
|
||||
- ✅ Акцент на пакеты документов (знакомая концепция)
|
||||
- ✅ Объяснение динамической стоимости
|
||||
- ✅ Практические рекомендации
|
||||
- ❌ Без технического жаргона
|
||||
- ❌ Без лишнего пафоса
|
||||
- ❌ Без упрощения технических деталей (мс, КБ оставлены)
|
||||
|
||||
## Примечания для дальнейшего развития
|
||||
|
||||
1. FAQ будет дополняться на основе реальных вопросов пользователей
|
||||
2. Пороги автоматического пополнения сейчас в абсолютных величинах (конфиг), планируется переход на проценты
|
||||
3. Все расчеты основаны на текущем курсе: 1 AXON = 10 RUB, 1 байт = 0.0001 AXON
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
# Руководство по системе вычислительных ресурсов COOPOS
|
||||
|
||||
## Для кого это руководство
|
||||
|
||||
Это руководство предназначено для администраторов кооперативов, которые управляют бюджетом вычислительных ресурсов в системе COOPOS.
|
||||
|
||||
---
|
||||
|
||||
## Что нужно знать
|
||||
|
||||
### 💰 Базовая экономика
|
||||
|
||||
**1 AXON = 10 рублей**
|
||||
|
||||
Это служебный токен для оплаты вычислительных ресурсов в блокчейне COOPOS.
|
||||
|
||||
### 📦 Что такое квота
|
||||
|
||||
**1 квота = 5 AXON = 50 рублей**
|
||||
|
||||
Квота — это пакет вычислительных ресурсов на 24 часа:
|
||||
- 24.4 КБ оперативной памяти (RAM) для хранения данных документов
|
||||
- Процессорное время (CPU) для выполнения вычислений
|
||||
- Сетевой трафик (NET) для передачи данных
|
||||
|
||||
### 📄 Пакеты документов
|
||||
|
||||
Каждая операция в системе оформляется пакетом документов:
|
||||
- Регистрация пайщика → заявление + протокол совета + решение
|
||||
- Собрание → повестка + протокол + бюллетени
|
||||
- И так далее...
|
||||
|
||||
**Один пакет документов ≈ 10 КБ памяти**
|
||||
|
||||
Значит, одна квота позволяет одновременно обрабатывать 2 полных пакета документов.
|
||||
|
||||
---
|
||||
|
||||
## Как это работает
|
||||
|
||||
### 🔄 Многократное использование
|
||||
|
||||
**Важно!** Квоту можно использовать многократно в течение 24 часов.
|
||||
|
||||
После того как документ обработан и память освободилась, вы можете использовать ту же квоту для следующих документов.
|
||||
|
||||
**Пример:**
|
||||
- Утром зарегистрировали пайщика (10 КБ памяти)
|
||||
- Через час документы обработаны, память освободилась
|
||||
- Днём провели собрание (10 КБ памяти) на той же квоте
|
||||
- Вечером оформили паевой взнос — снова на той же квоте
|
||||
|
||||
### ⏰ Срок действия квоты
|
||||
|
||||
Квота арендуется на 24 часа и возвращается в тот же час, когда была получена.
|
||||
|
||||
**Что происходит при возврате:**
|
||||
- Если память не используется → ресурсы возвращаются в общий пул
|
||||
- Если в памяти есть активные данные → система автоматически продлевает аренду при наличии средств
|
||||
|
||||
### ⚙️ Автоматическое управление
|
||||
|
||||
Система сама следит за ресурсами и арендует квоты, когда нужно:
|
||||
|
||||
1. **Ежедневное пополнение** (00:00) → 5 AXON
|
||||
2. **При использовании >70%** любого ресурса → дополнительно 5 AXON
|
||||
3. **При возврате квот** с активными данными → продление аренды
|
||||
|
||||
---
|
||||
|
||||
## Что за что платится
|
||||
|
||||
### 🆕 Регистрация аккаунта пайщика
|
||||
|
||||
**1 AXON за аккаунт (единоразово)**
|
||||
|
||||
Это оплата за постоянное хранение базовой информации об аккаунте в блокчейне.
|
||||
|
||||
Плюс нужны квоты для обработки регистрационных документов.
|
||||
|
||||
### 📋 Обработка документов
|
||||
|
||||
**Стоимость зависит от:**
|
||||
- Объёма данных (паспорт, адрес, реквизиты)
|
||||
- Сложности проверок (подписи, валидация)
|
||||
- Времени хранения в памяти
|
||||
|
||||
**В среднем:** один пакет документов при хранении весь день ≈ 2.5 AXON (половина квоты)
|
||||
|
||||
Но если обработка быстрая (1-2 часа), та же квота используется повторно.
|
||||
|
||||
### 🖥️ Кому идут деньги
|
||||
|
||||
**90% → Делегатам сети**
|
||||
|
||||
Делегаты — это операторы серверов, которые:
|
||||
- Инвестируют в оборудование (серверы, память, процессоры)
|
||||
- Поддерживают работу блокчейна 24/7
|
||||
- Предоставляют вычислительные мощности
|
||||
|
||||
Полученные AXON делегаты могут обменять на рубли через кооператив-оператор.
|
||||
|
||||
**10% → Системный фонд**
|
||||
|
||||
Для покрытия операционных расходов и последующего распределения среди пайщиков.
|
||||
|
||||
---
|
||||
|
||||
## Сколько нужно AXON в месяц
|
||||
|
||||
### Минимальный кооператив (без активности)
|
||||
|
||||
**150 AXON = 1500 рублей**
|
||||
|
||||
- 30 дней × 5 AXON = 150 AXON
|
||||
- Только минимальные ежедневные квоты
|
||||
- Без регистрации новых пайщиков
|
||||
|
||||
### Средний кооператив (50 пайщиков, умеренная активность)
|
||||
|
||||
**200 AXON = 2000 рублей**
|
||||
|
||||
- Базовые квоты: 150 AXON
|
||||
- Дополнительные квоты при активности: 30 AXON
|
||||
- Буфер безопасности: 20 AXON
|
||||
|
||||
### Активный кооператив (100+ пайщиков, регулярные собрания)
|
||||
|
||||
**300-400 AXON = 3000-4000 рублей**
|
||||
|
||||
- Базовые квоты: 150 AXON
|
||||
- Частые операции: 100-150 AXON
|
||||
- Регистрация новых пайщиков: 50-100 AXON
|
||||
|
||||
---
|
||||
|
||||
## Примеры расчётов
|
||||
|
||||
### Регистрация 10 пайщиков за день
|
||||
|
||||
**Последовательно (по одному):**
|
||||
- 10 × 1 AXON = 10 AXON (оплата аккаунтов)
|
||||
- 1 квота = 5 AXON (если быстро обрабатывается)
|
||||
- **Итого: 15 AXON**
|
||||
|
||||
**Одновременно (пакетами по 3):**
|
||||
- 10 × 1 AXON = 10 AXON (оплата аккаунтов)
|
||||
- 2 квоты × 5 AXON = 10 AXON (для параллельной обработки)
|
||||
- **Итого: 20 AXON**
|
||||
|
||||
### Проведение общего собрания (50 участников)
|
||||
|
||||
- 1 квота (5 AXON) для подготовки повестки
|
||||
- 1-2 квоты (5-10 AXON) для обработки бюллетеней
|
||||
- 1 квота (5 AXON) для протокола
|
||||
- **Итого: 15-20 AXON**
|
||||
|
||||
### Месяц стандартной работы
|
||||
|
||||
- Минимальные ежедневные квоты: 150 AXON
|
||||
- 2 собрания: 40 AXON
|
||||
- 5 новых пайщиков: 10 AXON
|
||||
- Текущие операции: 20 AXON
|
||||
- **Итого: 220 AXON (2200 рублей)**
|
||||
|
||||
---
|
||||
|
||||
## Важные рекомендации
|
||||
|
||||
### ✅ Что делать
|
||||
|
||||
1. **Поддерживайте буфер 50-100 AXON** для бесперебойной работы
|
||||
2. **Регулярно проверяйте баланс** через панель мониторинга
|
||||
3. **Планируйте массовые операции** заранее (регистрация пайщиков, собрания)
|
||||
4. **Отслеживайте фактическое потребление** в логах расширения powerup
|
||||
|
||||
### ❌ Чего не делать
|
||||
|
||||
1. **Не допускайте нулевой баланс AXON** — это заблокирует все операции
|
||||
2. **Не паникуйте при дополнительных списаниях** — проверьте логи, это нормально при активной работе
|
||||
3. **Не покупайте AXON "впрок"** больше, чем нужно — планируйте на месяц
|
||||
|
||||
---
|
||||
|
||||
## Где пополнить AXON
|
||||
|
||||
Пополнение баланса AXON происходит через личный кабинет кооператива-оператора:
|
||||
|
||||
**https://лк.цифровой-кооператив.рф**
|
||||
|
||||
Курс обмена: **1 AXON = 10 RUB**
|
||||
|
||||
---
|
||||
|
||||
## Часто задаваемые вопросы
|
||||
|
||||
### Почему списалось больше, чем 5 AXON в день?
|
||||
|
||||
Дополнительные списания происходят при:
|
||||
- Автоматическом пополнении (когда использование ресурсов >70%)
|
||||
- Регистрации новых аккаунтов (1 AXON за аккаунт)
|
||||
|
||||
### Что будет, если закончатся AXON?
|
||||
|
||||
- ❌ Система не сможет автоматически пополнить квоты
|
||||
- ❌ Когда текущая квота исчерпается, все операции заблокируются
|
||||
- ❌ Пайщики не смогут подавать заявления
|
||||
- ❌ Невозможно зарегистрировать новых пайщиков
|
||||
|
||||
**Критично:** следите за балансом!
|
||||
|
||||
### Можно ли вернуть неиспользованные AXON?
|
||||
|
||||
Нет. AXON идут делегатам в качестве оплаты за предоставленные вычислительные мощности.
|
||||
|
||||
Однако неиспользованные ресурсы в рамках квоты можно использовать повторно в течение 24 часов.
|
||||
|
||||
### Сколько документов можно обработать на одной квоте?
|
||||
|
||||
**Одновременно:** 2 полных пакета документов (~10 КБ каждый)
|
||||
|
||||
**Последовательно за сутки:** от 24 до 48 пакетов (если обработка занимает 1-2 часа)
|
||||
|
||||
### Как оптимизировать расходы?
|
||||
|
||||
1. Планируйте регистрацию пайщиков последовательно, а не массово
|
||||
2. Проводите собрания в удобное время, когда ресурсов достаточно
|
||||
3. Отслеживайте пики потребления и корректируйте активность
|
||||
4. Используйте систему в рабочие часы для максимальной эффективности
|
||||
|
||||
---
|
||||
|
||||
## Техническая поддержка
|
||||
|
||||
Если у вас возникли вопросы по работе системы вычислительных ресурсов, обращайтесь в техническую поддержку кооператива-оператора.
|
||||
|
||||
---
|
||||
|
||||
**Последнее обновление:** 21 ноября 2024
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user