[merge-dev-into-marketplace2][@dark.sun] chore: merge origin/dev в marketplace2
Резолв конфликтов:
- ledger2/operations.hpp: оставлен CONVERT_TO_MEMBER (marketplace2) +
принята новая логика withdraw из dev (WITHDRAW_PENDING вместо BLOCK).
- ledger2/wallets.hpp: реестр расширен до 16 кошельков
(USER_SHARED=6 с MARKETPLACE_ORDER_LOCK + COOPERATIVE=10 с WITHDRAW_PENDING).
- WalletProgramWidget.vue / WalletWidget.vue: убраны неиспользуемые helper'ы из marketplace2,
принят рефактор dev.
- schema.gql + zeus/* (controller + sdk): полная регенерация после merge.
Сопутствующие правки:
- Восстановлен marketplace-branch-ownership.service.ts (отсутствовал в origin/marketplace2,
но был импортирован из marketplace-return-claim.resolver.ts — взят из ветки
chore/trustees-ownership 46f5269b89).
- Исправлена опечатка MARKETPLACE_KU_CHAIRMEN_SERVICE → MARKETPLACE_KU_CHAIRMAN_SERVICE
в marketplace-desktop-grants.provider.ts.
- orderSelector.ts: blocked_amount → locked_amount, удалены did_convert/did_assign
(привести selector к новой схеме MarketplaceOrderCreateTxSnapshot).
- Пересобраны cooptypes и inter (stale dist после merge).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
Trial run of Typecheck workflow on PR → dev.
|
||||
Created 2026-05-23 to measure end-to-end duration of vue-tsc (desktop) and tsc (controller).
|
||||
Do not merge; close after measurement.
|
||||
+104
-160
@@ -5,8 +5,12 @@ name: Release
|
||||
# Порядок (через jobs.needs):
|
||||
# 1) release — контракты + контейнеры + webhook деплоя (атомарно)
|
||||
# 2) publish-packages — npm publish через lerna (если не -alpha)
|
||||
# 3) publish-docs — mkdocs + standards-site → gh-pages (если не -alpha + ветка main)
|
||||
# 4) trigger-coopenomics — repository_dispatch в coopenomics/coopenomics (тот же гейт, что docs)
|
||||
# 3) trigger-coopenomics — workflow_dispatch сборки сайта C9S/coopenomics на
|
||||
# Gitea (если не -alpha + ветка main); он сам тянет
|
||||
# mono и пересобирает доки.
|
||||
#
|
||||
# publish-docs (gh-pages на github.com) удалён 2026-05-25 — после переезда на
|
||||
# Gitea Pages-публикация невалидна; доки деплоятся через C9S/coopenomics.
|
||||
#
|
||||
# Зачем последовательно: пакеты/доки/внешний триггер не должны уезжать,
|
||||
# если релиз контрактов или контейнеров провалился. Раньше четыре workflow'а
|
||||
@@ -34,16 +38,7 @@ permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
# Гейт TS-типов перед сборкой образов. Reusable workflow `typecheck.yaml`
|
||||
# запускает vue-tsc на desktop и tsc --noEmit на controller. Нужно потому,
|
||||
# что `lerna run build` в корневом Dockerfile НЕ ловит TS-ошибки:
|
||||
# quasar build идёт через esbuild с выключенным vueTsc, а у controller'а
|
||||
# build-скрипта вообще нет (ts-node в рантайме).
|
||||
typecheck:
|
||||
uses: ./.github/workflows/typecheck.yaml
|
||||
|
||||
release:
|
||||
needs: typecheck
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
tag_name: ${{ steps.resolve.outputs.tag_name }}
|
||||
@@ -127,14 +122,41 @@ jobs:
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
# === Этап 1: контракты ===
|
||||
# CDT ставим из .deb (C9S/cdt v4.2.0) прямо в окружение job'а и
|
||||
# компилируем напрямую — БЕЗ вложенного docker. Почему не build-all.sh:
|
||||
# тот монтирует $(pwd):/project в sibling-контейнер, а под Gitea
|
||||
# act_runner сам job исполняется в контейнере → хостовый демон не видит
|
||||
# этот путь, /project пуст, cmake падает "no CMakeLists.txt".
|
||||
# build-all.sh остаётся для локальной сборки (оборачивает тот же
|
||||
# build_contracts_cdt.sh в docker).
|
||||
# CMakeLists хардкодит toolchain /cdt/build/...; .deb кладёт CDT в
|
||||
# /usr/opt/cdt/4.2.0, а сам CDTWasmToolchain.cmake указывает на /usr
|
||||
# абсолютно — поэтому симлинк /cdt/build → /usr/opt/cdt/4.2.0 сводит пути
|
||||
# без правки CMakeLists.
|
||||
|
||||
- name: Pull CDT toolchain image
|
||||
run: docker pull dicoop/blockchain_v5.1.1:dev
|
||||
- name: Install CDT 4.2.0 toolchain (.deb)
|
||||
run: |
|
||||
SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo"
|
||||
$SUDO apt-get update
|
||||
# build-essential — для host-компилятора (project() в CMakeLists);
|
||||
# libz3-4/libtinfo6/libxml2/zlib1g — рантайм бинарей CDT (clang-9,
|
||||
# ld.lld и т.д.). Пакет cdt объявляет только libcurl4-gnutls-dev,
|
||||
# поэтому остальные .so ставим явно — в образе dicoop/blockchain они
|
||||
# были из сборки исходников, из .deb не тянутся.
|
||||
$SUDO apt-get install -y --no-install-recommends \
|
||||
curl ca-certificates cmake make build-essential \
|
||||
libz3-4 libtinfo6 libxml2 zlib1g
|
||||
curl -fsSL -o /tmp/cdt.deb \
|
||||
https://git.coopenomics.world/C9S/cdt/releases/download/v4.2.0/cdt_4.2.0-1_amd64.deb
|
||||
$SUDO apt-get install -y /tmp/cdt.deb
|
||||
$SUDO mkdir -p /cdt
|
||||
$SUDO ln -sfn /usr/opt/cdt/4.2.0 /cdt/build
|
||||
cdt-cpp --version || true
|
||||
|
||||
- name: Compile contracts
|
||||
working-directory: components/contracts
|
||||
run: |
|
||||
./build-all.sh "$BUILD_MODE"
|
||||
./build_contracts_cdt.sh "$BUILD_MODE"
|
||||
echo "--- build/contracts ---"
|
||||
ls -la build/contracts/
|
||||
|
||||
@@ -215,6 +237,17 @@ jobs:
|
||||
- name: Build and push contracts image
|
||||
working-directory: components/contracts
|
||||
run: |
|
||||
# Ретрай push'а: резолвинг registry-1.docker.io на runner'е изредка
|
||||
# моргает (DNS-таймаут к 127.0.0.53) — 3 попытки с паузой.
|
||||
dpush() {
|
||||
local ref="$1" n=1
|
||||
until docker push "$ref"; do
|
||||
[ "$n" -ge 3 ] && { echo "::error::docker push $ref не удался после $n попыток"; return 1; }
|
||||
echo "::warning::docker push $ref упал (попытка $n/3) — повтор через 10с"
|
||||
n=$((n+1)); sleep 10
|
||||
done
|
||||
}
|
||||
|
||||
IMAGE="dicoop/contracts"
|
||||
SHORT_SHA="${SHA::7}"
|
||||
docker build \
|
||||
@@ -223,14 +256,14 @@ jobs:
|
||||
--label "build.mode=$BUILD_MODE" \
|
||||
-t "$IMAGE:$CONTRACTS_TAG" \
|
||||
./docker/.context
|
||||
docker push "$IMAGE:$CONTRACTS_TAG"
|
||||
dpush "$IMAGE:$CONTRACTS_TAG"
|
||||
|
||||
docker tag "$IMAGE:$CONTRACTS_TAG" "$IMAGE:$CONTRACTS_TAG-$SHORT_SHA"
|
||||
docker push "$IMAGE:$CONTRACTS_TAG-$SHORT_SHA"
|
||||
dpush "$IMAGE:$CONTRACTS_TAG-$SHORT_SHA"
|
||||
|
||||
if [ -n "$CONTRACTS_EXTRA" ]; then
|
||||
docker tag "$IMAGE:$CONTRACTS_TAG" "$IMAGE:$CONTRACTS_EXTRA"
|
||||
docker push "$IMAGE:$CONTRACTS_EXTRA"
|
||||
dpush "$IMAGE:$CONTRACTS_EXTRA"
|
||||
fi
|
||||
|
||||
- name: Verify pushed contracts image
|
||||
@@ -243,15 +276,31 @@ jobs:
|
||||
|
||||
- name: Build and push base image
|
||||
run: |
|
||||
dpush() {
|
||||
local ref="$1" n=1
|
||||
until docker push "$ref"; do
|
||||
[ "$n" -ge 3 ] && { echo "::error::docker push $ref не удался после $n попыток"; return 1; }
|
||||
echo "::warning::docker push $ref упал (попытка $n/3) — повтор через 10с"
|
||||
n=$((n+1)); sleep 10
|
||||
done
|
||||
}
|
||||
docker build --target runtime -t "dicoop/mono-base:$TAG_NAME" .
|
||||
docker push "dicoop/mono-base:$TAG_NAME"
|
||||
dpush "dicoop/mono-base:$TAG_NAME"
|
||||
if [ "$IS_PROD" = "true" ]; then
|
||||
docker tag "dicoop/mono-base:$TAG_NAME" dicoop/mono-base:latest
|
||||
docker push dicoop/mono-base:latest
|
||||
dpush dicoop/mono-base:latest
|
||||
fi
|
||||
|
||||
- name: Build and push service images
|
||||
run: |
|
||||
dpush() {
|
||||
local ref="$1" n=1
|
||||
until docker push "$ref"; do
|
||||
[ "$n" -ge 3 ] && { echo "::error::docker push $ref не удался после $n попыток"; return 1; }
|
||||
echo "::warning::docker push $ref упал (попытка $n/3) — повтор через 10с"
|
||||
n=$((n+1)); sleep 10
|
||||
done
|
||||
}
|
||||
build_service() {
|
||||
local SVC="$1" PKG="$2" CMD="$3"
|
||||
local DOCKERFILE="Dockerfile.$SVC"
|
||||
@@ -260,10 +309,10 @@ jobs:
|
||||
echo "CMD [\"pnpm\", \"-F\", \"$PKG\", \"run\", \"$CMD\"]"
|
||||
} > "$DOCKERFILE"
|
||||
docker build -t "dicoop/$SVC:$TAG_NAME" -f "$DOCKERFILE" .
|
||||
docker push "dicoop/$SVC:$TAG_NAME"
|
||||
dpush "dicoop/$SVC:$TAG_NAME"
|
||||
if [ "$IS_PROD" = "true" ]; then
|
||||
docker tag "dicoop/$SVC:$TAG_NAME" "dicoop/$SVC:latest"
|
||||
docker push "dicoop/$SVC:latest"
|
||||
dpush "dicoop/$SVC:latest"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -314,149 +363,44 @@ jobs:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
# ============================================================================
|
||||
# publish-docs — mkdocs + standards-site → gh-pages (бывший publish-docs.yaml).
|
||||
# trigger-coopenomics-docs — пересборка сайта C9S/coopenomics.
|
||||
# Гейт: не-alpha + ветка main.
|
||||
# ВНИМАНИЕ: gh-pages step пушит на github.com/coopenomics/mono — это GitHub
|
||||
# Pages. При переезде на Gitea Actions нужно будет либо подложить PAT для
|
||||
# внешнего push'а в GitHub, либо переключить публикацию на Gitea Pages.
|
||||
# ============================================================================
|
||||
publish-docs:
|
||||
needs: release
|
||||
if: ${{ !contains(github.ref, '-alpha') && needs.release.outputs.branch == 'main' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Версия pnpm берётся из `packageManager` корневого package.json,
|
||||
# синхронно с publish-packages job'ом и Dockerfile'ами.
|
||||
- name: Set up pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Install Python requirements
|
||||
run: |
|
||||
python -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install mkdocs-material mkdocs-macros-plugin mkdocs-section-index pymdown-extensions
|
||||
working-directory: ./components/docs
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: pnpm install
|
||||
working-directory: ./components/docs
|
||||
|
||||
- name: Patch spectaql-config.yml for CI
|
||||
run: |
|
||||
sed -i.bak "0,/url:.*/s|url:.*|url: 'https://testnet.coopenomics.world/backend/v1/graphql'|" spectaql-config.yml
|
||||
working-directory: ./components/controller
|
||||
|
||||
- name: Show patched spectaql-config.yml
|
||||
run: cat spectaql-config.yml
|
||||
working-directory: ./components/controller
|
||||
|
||||
- name: Build cooptypes
|
||||
run: pnpm run build
|
||||
working-directory: ./components/cooptypes
|
||||
|
||||
- name: Generate controller docs
|
||||
run: pnpm run docs
|
||||
working-directory: ./components/controller
|
||||
|
||||
- name: Copy controller docs
|
||||
run: |
|
||||
mkdir -p ./components/docs/docs/graphql
|
||||
cp -r ./components/controller/docs/* ./components/docs/docs/graphql/
|
||||
|
||||
- name: Generate sdk docs
|
||||
run: pnpm run docs
|
||||
working-directory: ./components/sdk
|
||||
|
||||
- name: Copy sdk docs
|
||||
run: |
|
||||
mkdir -p ./components/docs/docs/sdk
|
||||
cp -r ./components/sdk/docs/* ./components/docs/docs/sdk/
|
||||
|
||||
- name: Generate cooptypes docs
|
||||
run: pnpm run docs
|
||||
working-directory: ./components/cooptypes
|
||||
|
||||
- name: Copy cooptypes docs
|
||||
run: |
|
||||
mkdir -p ./components/docs/docs/cooptypes
|
||||
cp -r ./components/cooptypes/docs/* ./components/docs/docs/cooptypes/
|
||||
|
||||
# ── standards-site → /standards/ на docs.цифровой-кооператив.рф ──
|
||||
# Отдельный Vue-сайт с BPMN-графом кооперативных стандартов,
|
||||
# публикуется как поддиректория на том же домене. Vite собирает
|
||||
# с base='/standards/' (см. vite.config.ts), относительные пути
|
||||
# внутри dist/ и hash-router работают корректно.
|
||||
# dist кладём в docs/standards/ ДО mkdocs build — так же, как
|
||||
# graphql/sdk/cooptypes; mkdocs сам включит его в итоговый site/.
|
||||
- name: Build standards-site
|
||||
run: pnpm run build
|
||||
working-directory: ./components/contracts/standards-site
|
||||
|
||||
- name: Copy standards-site into docs/standards
|
||||
run: |
|
||||
mkdir -p ./components/docs/docs/standards
|
||||
cp -r ./components/contracts/standards-site/dist/* ./components/docs/docs/standards/
|
||||
|
||||
- name: Build docs (mkdocs)
|
||||
run: |
|
||||
source venv/bin/activate
|
||||
mkdocs build
|
||||
working-directory: ./components/docs
|
||||
|
||||
- name: Remove specific large file before publishing
|
||||
run: |
|
||||
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
|
||||
env:
|
||||
GIT_AUTHOR_NAME: github-actions
|
||||
GIT_AUTHOR_EMAIL: github-actions@github.com
|
||||
GIT_COMMITTER_NAME: github-actions
|
||||
GIT_COMMITTER_EMAIL: github-actions@github.com
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Trigger docs deployment webhook
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
curl -X POST "${{ vars.DOCS_DEPLOY_WEBHOOK_URL }}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"ref":"${{ github.ref }}","sha":"${{ github.sha }}","branch":"${{ github.ref_name }}"}'
|
||||
|
||||
# ============================================================================
|
||||
# trigger-coopenomics-docs — repository_dispatch в coopenomics/coopenomics
|
||||
# (бывший build-contracts-docs.yaml). Гейт идентичен publish-docs.
|
||||
#
|
||||
# coopenomics переехал на Gitea (C9S/coopenomics); его publish-docs.yaml
|
||||
# клонирует mono и пересобирает сайт. Gitea НЕ имеет API для
|
||||
# repository_dispatch — только workflow_dispatch, поэтому дёргаем целевой
|
||||
# workflow через Gitea API с PAT (secret DOCS_DISPATCH_TOKEN — префикс GITEA_
|
||||
# у секретов зарезервирован Gitea, нельзя; права write на C9S/coopenomics).
|
||||
# Целевой workflow слушает workflow_dispatch с входами
|
||||
# mono_sha/mono_ref.
|
||||
# ============================================================================
|
||||
trigger-coopenomics-docs:
|
||||
needs: release
|
||||
if: ${{ !contains(github.ref, '-alpha') && needs.release.outputs.branch == 'main' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger Coopenomics deployment
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.COOPENOMICS_PAT }}
|
||||
repository: coopenomics/coopenomics
|
||||
event-type: deploy_from_mono
|
||||
client-payload: '{"repository": "${{ github.repository }}", "sha": "${{ github.sha }}", "ref": "${{ github.ref }}", "actor": "${{ github.actor }}"}'
|
||||
- name: Trigger coopenomics website rebuild (Gitea workflow_dispatch)
|
||||
run: |
|
||||
curl -fsSL -X POST \
|
||||
-H "Authorization: token ${{ secrets.DOCS_DISPATCH_TOKEN }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"ref":"master","inputs":{"mono_sha":"${{ github.sha }}","mono_ref":"${{ github.ref }}"}}' \
|
||||
"${{ github.server_url }}/api/v1/repos/C9S/coopenomics/actions/workflows/publish-docs.yaml/dispatches"
|
||||
|
||||
# ============================================================================
|
||||
# trigger-mono-docs — деплой ВТОРОЙ документации (сайт доков mono) через
|
||||
# webhook DOCS_DEPLOY_WEBHOOK_URL. Отдельная от coopenomics публикация —
|
||||
# обе доки уезжают синхронно по релизу. Извлечён из бывшего publish-docs:
|
||||
# gh-pages на github.com выпилен как мёртвый, остался реальный deploy-webhook
|
||||
# (приёмник деплоит доки на своей стороне). Гейт: не-alpha + ветка main.
|
||||
# ============================================================================
|
||||
trigger-mono-docs:
|
||||
needs: release
|
||||
if: ${{ !contains(github.ref, '-alpha') && needs.release.outputs.branch == 'main' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger docs deployment webhook
|
||||
run: |
|
||||
curl -fsSL -X POST "${{ vars.DOCS_DEPLOY_WEBHOOK_URL }}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"ref":"${{ github.ref }}","sha":"${{ github.sha }}","branch":"${{ github.ref_name }}"}'
|
||||
|
||||
@@ -13,11 +13,11 @@ name: Typecheck
|
||||
#
|
||||
# Триггеры:
|
||||
# - pull_request на dev — гейт перед мерджем в основную ветку;
|
||||
# - workflow_call — переиспользуется из release.yaml как `needs:` у release-job,
|
||||
# чтобы битый тэг не уехал в DockerHub.
|
||||
# - workflow_call — оставлен для переиспользования, но release.yaml его
|
||||
# больше НЕ вызывает (гейт из релиза убран как избыточный — типы
|
||||
# проверяются на PR в dev до того, как код доедет до тэга).
|
||||
#
|
||||
# Push в dev/testnet/main НЕ триггерит — намеренно (PR-гейт достаточен,
|
||||
# а тэги покрывает workflow_call из release.yaml).
|
||||
# Push в dev/testnet/main НЕ триггерит — намеренно (PR-гейт на dev достаточен).
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -42,13 +42,15 @@ jobs:
|
||||
- name: Install workspace
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
# cooptypes/factory/sdk собираются здесь, потому что desktop
|
||||
# импортирует типы из @coopenomics/sdk (zeus-генерация поверх
|
||||
# controller GraphQL-схемы), а sdk в свою очередь зависит от
|
||||
# cooptypes. Без `unbuild` dist/ пусто — vue-tsc упадёт на
|
||||
# «Cannot find module '@coopenomics/sdk'».
|
||||
- name: Build type dependencies (cooptypes → factory → sdk)
|
||||
run: pnpm lerna run build --scope cooptypes --scope @coopenomics/factory --scope @coopenomics/sdk
|
||||
# Полный lerna build по всему workspace — как в корневом Dockerfile.
|
||||
# Не --scope, потому что workspace-пакеты unbuild-типа (sdk, inter,
|
||||
# notifications, factory, cooptypes, …) экспортируют типы только из
|
||||
# сгенерированного dist/. Без dist/ tsc/vue-tsc валятся на TS2307
|
||||
# «Cannot find module». Локально это маскируется ts-node-ом, но в
|
||||
# CI пути резолвятся через package.json → types → dist/.
|
||||
# См. Graphiti / run #148 root cause 2026-05-23.
|
||||
- name: Build all workspace packages
|
||||
run: pnpm lerna run build
|
||||
|
||||
# quasar prepare генерирует .quasar/* (auto-imports d.ts, типы для
|
||||
# quasar плагинов). Без него vue-tsc ругается на отсутствующие
|
||||
@@ -83,10 +85,12 @@ jobs:
|
||||
- name: Install workspace
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
# controller импортирует cooptypes и factory. Без build dist/
|
||||
# типы не резолвятся — TS2307 на каждом import.
|
||||
- name: Build type dependencies (cooptypes → factory)
|
||||
run: pnpm lerna run build --scope cooptypes --scope @coopenomics/factory
|
||||
# Полный lerna build по всему workspace — controller импортирует
|
||||
# @coopenomics/sdk, @coopenomics/inter, @coopenomics/notifications
|
||||
# помимо cooptypes/factory; их dist/ генерируется unbuild и без
|
||||
# этого шага tsc валится на 37 TS2307. См. root cause выше.
|
||||
- name: Build all workspace packages
|
||||
run: pnpm lerna run build
|
||||
|
||||
- name: tsc --noEmit (controller)
|
||||
run: pnpm --filter @coopenomics/controller run typecheck
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
node_modules/
|
||||
node_modules
|
||||
lerna-debug.log
|
||||
components/controller/graph.png
|
||||
blockchain-data/
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
store-dir=./.pnpm-store
|
||||
supported-architectures.os[]=darwin
|
||||
supported-architectures.os[]=linux
|
||||
supported-architectures.cpu[]=x64
|
||||
supported-architectures.cpu[]=arm64
|
||||
supported-architectures.libc[]=glibc
|
||||
|
||||
|
||||
@@ -214,6 +214,31 @@ await sendPOST('/v1/graphql', { query: QUERY, variables });
|
||||
|
||||
Канон — `features/Branch/CreateBranch/{api,model}/index.ts`: тип в model `export type IXxxInput = Mutations.X.Y.IInput['data']`; функция в api `function (data: IXxxInput) { ... variables: { data } }`. Не делать `as` cast'ов.
|
||||
|
||||
## ⚠️ ДИЗАЙН-КАНОН desktop — ОБЯЗАТЕЛЕН К ИСПОЛЬЗОВАНИЮ
|
||||
|
||||
**ПРИ ЛЮБОЙ ВЁРСТКЕ В `components/desktop/` НЕ ВЫДУМЫВАТЬ СТИЛИ.** Всегда сначала свериться с каноном. Не строй гипотез о цветах/радиусах/типографике/паттернах из памяти — иди и читай.
|
||||
|
||||
**Два источника правды (именно в этом порядке):**
|
||||
|
||||
1. **HTML-канон дизайн-системы** — единственный SoT для визуала (цвета, токены, радиусы, тени, типографика, патерны компонентов):
|
||||
|
||||
`/home/admin/blago/production/shared/MONO Design System.html`
|
||||
|
||||
Это самодостаточный HTML с всеми CSS-токенами `--p-*`, образцами компонентов (`.base-button`, `.base-card`, `.toast`, `.chip`, `.banner` и т.д.), цветовой палитрой и demo-блоками. **При сомнении в каноне — открыть этот файл и читать.** Не путать с `auth-prototype/index.html`, не путать с `ux-design-specification.md` — они НЕ канон.
|
||||
|
||||
2. **Живая реализация канона** в самом репозитории — `/_dev/ui` страница:
|
||||
|
||||
`components/desktop/src/pages/_dev/ui/index.vue`
|
||||
|
||||
Это уже заверстанная по канону witness-страница со всеми base-/widget-компонентами в действии (BaseButton, BaseInput, BaseCard, BaseChip, EmptyState, Toasts, AppHeader/AppDrawer фрагменты, WalletCard, IdentityPanel, DocumentRow, SignatureCard, ActivityTimeline, KpiCard, VerticalStepper и т.д.). **Когда делаешь новый компонент — сначала посмотри как уже сверстаны соседние в этой странице, используй те же токены / классы / структуру.** Эту страницу можно открыть в браузере (`/_dev/ui` в dev-сборке) — она же служит и для визуальной верификации.
|
||||
|
||||
**Запреты:**
|
||||
- Никаких magic-цветов в hex/rgb прямо в `<style>` Vue-файла — только токены `var(--p-*)` (см. `components/desktop/src/css/mono-platform/*.css`).
|
||||
- Никаких локальных переопределений `.q-btn`/`.q-card`/`.q-notification` в feature-файлах — все Quasar-overrides централизованно в `components/desktop/src/css/mono-platform/quasar-canon.css`.
|
||||
- Никаких ад-хок размеров — отступы/радиусы/typescale из токенов (`--p-1`..`--p-12`, `--p-r-sm/md/lg`, `--p-fs-h1..body-sm`).
|
||||
|
||||
**Кейс 2026-05-28:** после компакта забыл, что shared/MONO Design System.html — основной SoT, и нашёл «канон» в auth-prototype, который НЕ канон; перевёрстал тосты под нейтральный фон вместо тёмного, пришлось переделывать.
|
||||
|
||||
## Frontend desktop — English имена
|
||||
|
||||
В `components/desktop/` и любом Vue/TS frontend коде **все имена идентификаторов — английские**:
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
# Запуск mono dev-стека на macOS
|
||||
|
||||
Полная пошаговая инструкция, как поднять backend (`coopback`) + parser (`cooparser`) + блокчейн-ноду + БД на macOS через Docker Desktop. Прошёл — отметь, ниже разобраны типичные грабли.
|
||||
|
||||
## TL;DR
|
||||
|
||||
```bash
|
||||
cd ~/dacom-code/foundation/monocoop
|
||||
docker compose up -d # 1. поднять базу
|
||||
./scripts/dev-setup-macos.sh # 2. одной командой (см. ниже скрипт)
|
||||
```
|
||||
|
||||
Если скрипта ещё нет — выполняй шаги ниже руками.
|
||||
|
||||
## Шаги
|
||||
|
||||
### 1. Поднять контейнеры
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Поднимутся: `node` (NodeOS), `mongo`, `monoredis`, `postgres`, `coopback`, `cooparser`. **MinIO** входит в дефолт. **OpenSearch** — нет (тяжёлый, см. опц. сервисы).
|
||||
|
||||
### 2. Проверить чтоб .env'ы указывали на service-имена, а не localhost
|
||||
|
||||
`components/controller/.env`:
|
||||
- `MONGODB_URL=mongodb://mongo:27017/cooperative-x`
|
||||
- `REDIS_HOST=monoredis`
|
||||
- `POSTGRES_HOST=postgres`, `POSTGRES_PASSWORD=postgres!23!23`
|
||||
- `BLOCKCHAIN_RPC=http://node:8888`
|
||||
- `SIMPLE_EXPLORER_API=http://cooparser:4000`
|
||||
- `MINIO_ENDPOINT=minio:9000`
|
||||
- `CHAIN_ID=<реальный chain_id, см. ниже>`
|
||||
|
||||
`components/parser/.env`:
|
||||
- `MONGO_EXPLORER_URI=mongodb://mongo:27017/cooperative-x`
|
||||
- `REDIS_HOST=monoredis`
|
||||
- `API=http://node:8888`
|
||||
- `SHIP=ws://node:8080`
|
||||
|
||||
**Почему service-имена:** все контейнеры в bridge-сети `monocoop_default`. Внутри контейнера `127.0.0.1` = сам контейнер, не host. На macOS `network_mode: host` в Docker Desktop работает плохо — используем bridge + service names.
|
||||
|
||||
### 3. Узнать живой chain_id
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:8888/v1/chain/get_info | jq -r .chain_id
|
||||
```
|
||||
|
||||
И вписать в `components/controller/.env` → `CHAIN_ID=...`. **Если chain_id в .env не совпадает с живым — on-chain транзакции упадут на verify, хотя приложение запустится.**
|
||||
|
||||
### 4. Если правил .env — пересоздать контейнер (не restart!)
|
||||
|
||||
```bash
|
||||
docker compose up -d --force-recreate --no-deps coopback cooparser
|
||||
```
|
||||
|
||||
`docker restart` **не перечитывает** `env_file`. `--no-deps` — чтобы не пересоздавать БД (потеряются данные).
|
||||
|
||||
### 5. Native binding libxmljs2 — пересборка под Linux
|
||||
|
||||
На свежем чекауте `pnpm install` запускается на macOS, и `libxmljs2` собирает Mach-O бинарник. В Linux-контейнере он падает:
|
||||
|
||||
```
|
||||
Error: .../xmljs.node: invalid ELF header ERR_DLOPEN_FAILED
|
||||
```
|
||||
|
||||
**Фикс** (один раз после `pnpm install`):
|
||||
|
||||
```bash
|
||||
docker exec monocoop-coopback-1 sh -c '
|
||||
cd /app/node_modules/.pnpm/libxmljs2@0.37.0/node_modules/libxmljs2 \
|
||||
&& rm -rf build \
|
||||
&& PATH="$PWD/node_modules/.bin:$PATH" npm run install
|
||||
'
|
||||
docker restart monocoop-coopback-1
|
||||
```
|
||||
|
||||
Проверка — magic bytes Linux ELF:
|
||||
```bash
|
||||
docker exec monocoop-coopback-1 sh -c \
|
||||
'head -c 4 /app/node_modules/.pnpm/libxmljs2@0.37.0/node_modules/libxmljs2/build/Release/xmljs.node | od -c -N 4'
|
||||
# Должно быть: 177 E L F
|
||||
# Если cf fa ed fe — это macOS Mach-O, фикс не применился.
|
||||
```
|
||||
|
||||
### 6. Дождаться cold-start coopback (~5–6 минут)
|
||||
|
||||
`ts-node` без cache + DI tree NestJS = долго. Первые логи появляются спустя ~5 минут после старта контейнера.
|
||||
|
||||
```bash
|
||||
docker logs -f monocoop-coopback-1 | grep -E 'NestJS app|Nest application|Error'
|
||||
```
|
||||
|
||||
Готово, когда видишь:
|
||||
```
|
||||
Nest application successfully started
|
||||
NestJS app with Express routes running on port 2998
|
||||
```
|
||||
|
||||
Проверка:
|
||||
```bash
|
||||
curl -s -X POST -H 'Content-Type: application/json' \
|
||||
-d '{"query":"{__typename}"}' http://localhost:2998/v1/graphql
|
||||
# {"data":{"__typename":"Query"}}
|
||||
```
|
||||
|
||||
## Опциональные сервисы
|
||||
|
||||
### OpenSearch (поиск)
|
||||
|
||||
```bash
|
||||
docker compose --profile search up -d opensearch
|
||||
```
|
||||
|
||||
Затем в `components/controller/.env`:
|
||||
```
|
||||
OPENSEARCH_ENABLED=true
|
||||
```
|
||||
И пересоздать coopback:
|
||||
```bash
|
||||
docker compose up -d --force-recreate --no-deps coopback
|
||||
```
|
||||
|
||||
## Грабли (если опять провозился пол-дня)
|
||||
|
||||
| Симптом | Причина | Фикс |
|
||||
|---|---|---|
|
||||
| `invalid ELF header` | host'овый `pnpm install` положил Mach-O | См. п. 5 — пересборка libxmljs2 в контейнере |
|
||||
| `MongooseServerSelectionError: ECONNREFUSED 127.0.0.1:27017` | .env на localhost вместо service-имени | См. п. 2 |
|
||||
| `password authentication failed for user "postgres"` | в .env `POSTGRES_PASSWORD=postgres` вместо `postgres!23!23` | См. п. 2 |
|
||||
| Coopback ушёл в restart-loop, но рядом есть другой `monocoop-coopback-1` Up | Случайно запустился старый `components/controller/docker-compose.yml` (network_mode: host, без bind-mount, без CMD) | `docker stop coopback && docker rm coopback` |
|
||||
| `docker restart` не подхватил новые переменные .env | `restart` не перечитывает `env_file` | `docker compose up -d --force-recreate --no-deps <svc>` |
|
||||
| Coopback зависает на 5+ минут, лога нет | Это нормально для cold-start ts-node | Подождать. CPU должен крутиться ≥50%. Если CPU = 0% — другая проблема |
|
||||
| `[VaultDomainService] Ошибка при получении WIF... bad decrypt` | SERVER_SECRET не совпадает с тем, чем шифровали WIF в БД | См. CLAUDE.md → раздел Vault & SERVER_SECRET. Дефолт — `SECRET` |
|
||||
| Транзакции падают на on-chain verify | CHAIN_ID в .env не совпадает с живым | См. п. 3 |
|
||||
| `Custom endpoint \`minio://9000\` was not a valid URI` на bootstrap | `MINIO_ENDPOINT` без схемы | `MINIO_ENDPOINT=http://minio:9000` (со схемой!) |
|
||||
| nodeos в restart-loop с `Database dirty flag set` после sleep/crash Docker Desktop | unclean shutdown chain state | `docker stop monocoop-node-1`; `mv blockchain-data/state-history{,.broken-$(date +%s)}`; `docker compose up -d node` — nodeos сам сделает replay |
|
||||
| Cold-start coopback 10+ минут (вместо обычных 3-5) | ts-node без cache + bind-mount через osxfs + параллельная нагрузка (nodeos replay) | Подождать; в будущем — `tsx` / `ts-node --swc` для dev-режима |
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@coopenomics/blago-cli",
|
||||
"version": "2026.5.23-4",
|
||||
"version": "2026.5.27-2",
|
||||
"description": "CLI синхронизации артефактов Благорост с бэкендом через @coopenomics/sdk",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
|
||||
@@ -9,12 +9,15 @@ export interface CommunicationCursorsFile {
|
||||
messageLastTsByRoom: Record<string, number>
|
||||
/** project_hash → ISO instant: транскрипции с endedAt ≤ этого момента уже выгружены */
|
||||
transcriptionLastEndedExclusiveByProject: Record<string, string>
|
||||
/** matrixRoomId → ISO instant: транскрипции непроектной комнаты с endedAt ≤ этого момента уже выгружены */
|
||||
transcriptionLastEndedExclusiveByRoom: Record<string, string>
|
||||
}
|
||||
|
||||
function empty(): CommunicationCursorsFile {
|
||||
return {
|
||||
messageLastTsByRoom: {},
|
||||
transcriptionLastEndedExclusiveByProject: {},
|
||||
transcriptionLastEndedExclusiveByRoom: {},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +35,11 @@ export async function loadCommunicationCursors(root: string): Promise<Communicat
|
||||
&& typeof parsed.transcriptionLastEndedExclusiveByProject === 'object'
|
||||
? { ...parsed.transcriptionLastEndedExclusiveByProject }
|
||||
: {},
|
||||
transcriptionLastEndedExclusiveByRoom:
|
||||
parsed.transcriptionLastEndedExclusiveByRoom !== undefined
|
||||
&& typeof parsed.transcriptionLastEndedExclusiveByRoom === 'object'
|
||||
? { ...parsed.transcriptionLastEndedExclusiveByRoom }
|
||||
: {},
|
||||
}
|
||||
}
|
||||
catch {
|
||||
|
||||
@@ -43,7 +43,6 @@ export function projectCommunicationDayToMarkdown(
|
||||
}
|
||||
|
||||
export interface TranscriptionHeaderModel {
|
||||
matrixRoomId: string
|
||||
roomId: string
|
||||
startedAt: Date | string
|
||||
endedAt: Date | string | null | undefined
|
||||
@@ -71,7 +70,6 @@ export function renderCallTranscriptionMarkdown(
|
||||
const header = [
|
||||
`# Транскрипция звонка`,
|
||||
``,
|
||||
`- Matrix room: \`${transcription.matrixRoomId}\``,
|
||||
`- LiveKit room: \`${transcription.roomId}\``,
|
||||
`- Начало: ${start.toISOString()}`,
|
||||
`- Окончание: ${end ? end.toISOString() : '—'}`,
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
transcriptionMeetingFileStemUtc,
|
||||
type CommunicationDayLine,
|
||||
} from './communication-markdown.js'
|
||||
import { workspaceBasePath, type ProjectPathModel } from './layout.js'
|
||||
import { generateSlug, workspaceBasePath, type ProjectPathModel } from './layout.js'
|
||||
import { syncEntityFile } from './sync-entity-file.js'
|
||||
|
||||
interface ProjectRowLite {
|
||||
@@ -183,6 +183,19 @@ async function listRooms(ctx: AuthenticatedContext, projectHash: string) {
|
||||
return q[Queries.ChatCoop.ListProjectCommunicationRooms.name] ?? []
|
||||
}
|
||||
|
||||
/** Стабильная папка непроектной комнаты в `rooms/`. Системные — фиксированные, комнаты секретаря — slug + хвост id (уникальность). */
|
||||
function nonProjectRoomFolder(kind: string, matrixRoomId: string, displayLabel: string): string {
|
||||
if (kind === 'MEMBERS') {
|
||||
return 'komnata-paishchikov'
|
||||
}
|
||||
if (kind === 'COUNCIL') {
|
||||
return 'komnata-soveta'
|
||||
}
|
||||
const slug = generateSlug(displayLabel) || 'komnata'
|
||||
const shortId = createHash('sha256').update(matrixRoomId, 'utf8').digest('hex').slice(0, 6)
|
||||
return `${slug}-${shortId}`
|
||||
}
|
||||
|
||||
export async function pullProjectCommunicationArtifacts(
|
||||
ctx: AuthenticatedContext,
|
||||
index: IndexFile,
|
||||
@@ -198,6 +211,7 @@ export async function pullProjectCommunicationArtifacts(
|
||||
cursors = {
|
||||
messageLastTsByRoom: {},
|
||||
transcriptionLastEndedExclusiveByProject: {},
|
||||
transcriptionLastEndedExclusiveByRoom: {},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,7 +370,6 @@ export async function pullProjectCommunicationArtifacts(
|
||||
= dateFromUnknown(tr.endedAt) ?? (tr.endedAt as Date | string | null | undefined)
|
||||
const md = renderCallTranscriptionMarkdown(
|
||||
{
|
||||
matrixRoomId: String(tr.matrixRoomId),
|
||||
roomId: String(tr.roomId),
|
||||
startedAt,
|
||||
endedAt: endedAtTr,
|
||||
@@ -422,3 +435,207 @@ export async function pullProjectCommunicationArtifacts(
|
||||
warn(`Не удалось сохранить курсоры переписки: ${formatThrownValue(e)}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull переписки и транскрипций из комнат ВНЕ проектов Capital (пайщики, совет, комнаты секретаря).
|
||||
* Раскладка — отдельная верхняя папка `rooms/<folder>/{messages,meetings}/`, чтобы не смешивать с
|
||||
* проектными `meetings/`. Логика идентична проектной, но bucket = одна комната, курсор транскрипций — по matrixRoomId.
|
||||
*/
|
||||
export async function pullNonProjectCommunicationArtifacts(
|
||||
ctx: AuthenticatedContext,
|
||||
index: IndexFile,
|
||||
): Promise<void> {
|
||||
let rooms: { matrixRoomId: string, displayLabel: string, kind: string }[]
|
||||
try {
|
||||
const q = await ctx.client.Query(Queries.ChatCoop.ListNonProjectCommunicationRooms.query, {})
|
||||
rooms = (q[Queries.ChatCoop.ListNonProjectCommunicationRooms.name] ?? []) as typeof rooms
|
||||
}
|
||||
catch (e) {
|
||||
warn(`Список непроектных комнат (chatcoopListNonProjectCommunicationRooms): ${formatThrownValue(e)}`)
|
||||
return
|
||||
}
|
||||
if (rooms.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
let cursors: CommunicationCursorsFile
|
||||
try {
|
||||
cursors = await loadCommunicationCursors(ctx.root)
|
||||
}
|
||||
catch (e) {
|
||||
warn(`Курсоры переписки (комнаты): не удалось прочитать, начинаем с пустых: ${formatThrownValue(e)}`)
|
||||
cursors = {
|
||||
messageLastTsByRoom: {},
|
||||
transcriptionLastEndedExclusiveByProject: {},
|
||||
transcriptionLastEndedExclusiveByRoom: {},
|
||||
}
|
||||
}
|
||||
|
||||
for (const room of rooms) {
|
||||
const folder = nonProjectRoomFolder(room.kind, room.matrixRoomId, room.displayLabel)
|
||||
const basePath = `rooms/${folder}`
|
||||
const roomTitle = room.displayLabel || room.matrixRoomId
|
||||
|
||||
// Сообщения комнаты — по календарным суткам UTC новее курсора.
|
||||
try {
|
||||
const last = cursors.messageLastTsByRoom[room.matrixRoomId]
|
||||
const afterTs = last ?? 0
|
||||
const datesQ = await ctx.client.Query(Queries.ChatCoop.ListUtcDatesWithNewRoomMessages.query, {
|
||||
variables: { data: { matrixRoomId: room.matrixRoomId, afterOriginServerTsExclusive: afterTs } },
|
||||
})
|
||||
const dates = (datesQ[Queries.ChatCoop.ListUtcDatesWithNewRoomMessages.name] ?? []).sort()
|
||||
for (const utcDate of dates) {
|
||||
const mq = await ctx.client.Query(Queries.ChatCoop.GetRoomMessagesForUtcDate.query, {
|
||||
variables: { data: { matrixRoomId: room.matrixRoomId, utcDate } },
|
||||
})
|
||||
const linesRaw = mq[Queries.ChatCoop.GetRoomMessagesForUtcDate.name] ?? []
|
||||
const lines: CommunicationDayLine[] = linesRaw.map(m => ({
|
||||
originServerTs: m.originServerTs,
|
||||
authorLabel: m.authorLabel,
|
||||
coopUsername: m.coopUsername,
|
||||
kind: String(m.kind),
|
||||
bodyText: m.bodyText,
|
||||
}))
|
||||
if (lines.length === 0) {
|
||||
continue
|
||||
}
|
||||
const content = projectCommunicationDayToMarkdown(roomTitle, room.matrixRoomId, utcDate, [
|
||||
{ displayLabel: room.displayLabel, matrixRoomId: room.matrixRoomId, lines },
|
||||
])
|
||||
const rel = `${basePath}/messages/${utcDate}.md`
|
||||
const entityHash = messageDayEntityHash(room.matrixRoomId, utcDate)
|
||||
await syncEntityFile({
|
||||
root: ctx.root,
|
||||
index,
|
||||
entityType: 'room_message_day',
|
||||
entityHash,
|
||||
relativePath: rel,
|
||||
content,
|
||||
remoteUpdatedAt: `${utcDate}T23:59:59.999Z`,
|
||||
label: `переписка ${utcDate} (${room.matrixRoomId})`,
|
||||
})
|
||||
}
|
||||
|
||||
const maxQ = await ctx.client.Query(Queries.ChatCoop.GetMaxOriginServerTsForRoom.query, {
|
||||
variables: { data: { matrixRoomId: room.matrixRoomId } },
|
||||
})
|
||||
const maxTs = maxQ[Queries.ChatCoop.GetMaxOriginServerTsForRoom.name] as number | null | undefined
|
||||
if (maxTs !== undefined && maxTs !== null && Number.isFinite(maxTs)) {
|
||||
cursors.messageLastTsByRoom[room.matrixRoomId] = maxTs
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
warn(`Переписка Matrix, комната ${room.matrixRoomId} (${roomTitle}): ${formatThrownValue(e)}`)
|
||||
}
|
||||
|
||||
// Транскрипции звонков комнаты + sibling memo.
|
||||
try {
|
||||
const tExIso = cursors.transcriptionLastEndedExclusiveByRoom[room.matrixRoomId]
|
||||
const lowerBoundExclusive = tExIso === undefined ? new Date(0) : new Date(tExIso)
|
||||
|
||||
interface TranscriptionCandidate {
|
||||
id: string
|
||||
endedAt: Date
|
||||
memo: string
|
||||
updatedAt: Date | undefined
|
||||
}
|
||||
const byId = new Map<string, TranscriptionCandidate>()
|
||||
const tq = await ctx.client.Query(Queries.ChatCoop.GetTranscriptions.query, {
|
||||
variables: { data: { matrixRoomId: room.matrixRoomId, limit: CHATCOOP_TRANSCRIPTIONS_QUERY_LIMIT, offset: 0 } },
|
||||
})
|
||||
const list = tq[Queries.ChatCoop.GetTranscriptions.name] ?? []
|
||||
for (const t of list) {
|
||||
const end = dateFromUnknown(t.endedAt)
|
||||
if (t.status !== Zeus.TranscriptionStatus.COMPLETED || !end) {
|
||||
continue
|
||||
}
|
||||
const prev = byId.get(t.id)
|
||||
if (!prev || end > prev.endedAt) {
|
||||
byId.set(t.id, {
|
||||
id: t.id,
|
||||
endedAt: end,
|
||||
memo: typeof t.memo === 'string' ? t.memo : '',
|
||||
updatedAt: dateFromUnknown(t.updatedAt),
|
||||
})
|
||||
}
|
||||
}
|
||||
const allCompleted = [...byId.values()].sort((a, b) => a.endedAt.getTime() - b.endedAt.getTime())
|
||||
|
||||
const newMeetings = allCompleted.filter(c => c.endedAt.getTime() > lowerBoundExclusive.getTime())
|
||||
let maxEnded: Date | null = null
|
||||
for (const c of newMeetings) {
|
||||
const packQ = await ctx.client.Query(Queries.ChatCoop.GetTranscription.query, {
|
||||
variables: { data: { id: c.id } },
|
||||
})
|
||||
const pack = packQ[Queries.ChatCoop.GetTranscription.name]
|
||||
if (!pack?.transcription || pack.transcription.status !== Zeus.TranscriptionStatus.COMPLETED) {
|
||||
continue
|
||||
}
|
||||
const tr = pack.transcription
|
||||
const startedAt: Date | string = dateFromUnknown(tr.startedAt) ?? (tr.startedAt as Date | string)
|
||||
const endedAtTr: Date | string | null | undefined
|
||||
= dateFromUnknown(tr.endedAt) ?? (tr.endedAt as Date | string | null | undefined)
|
||||
const md = renderCallTranscriptionMarkdown(
|
||||
{
|
||||
roomId: String(tr.roomId),
|
||||
startedAt,
|
||||
endedAt: endedAtTr,
|
||||
},
|
||||
pack.segments.map(s => ({
|
||||
speakerName: s.speakerName,
|
||||
text: s.text,
|
||||
startOffset: s.startOffset,
|
||||
endOffset: s.endOffset,
|
||||
})),
|
||||
)
|
||||
const stem = transcriptionMeetingFileStemUtc(c.endedAt)
|
||||
const rel = `${basePath}/meetings/${stem}.md`
|
||||
const entityHash = c.id.toLowerCase()
|
||||
await syncEntityFile({
|
||||
root: ctx.root,
|
||||
index,
|
||||
entityType: 'call_transcription',
|
||||
entityHash,
|
||||
relativePath: rel,
|
||||
content: md,
|
||||
remoteUpdatedAt: toUpdatedIso(c.endedAt),
|
||||
label: `транскрипция ${c.id}`,
|
||||
})
|
||||
if (!maxEnded || c.endedAt > maxEnded) {
|
||||
maxEnded = c.endedAt
|
||||
}
|
||||
}
|
||||
|
||||
for (const c of allCompleted) {
|
||||
const stem = transcriptionMeetingFileStemUtc(c.endedAt)
|
||||
const memoRel = `${basePath}/meetings/${stem}.memo.md`
|
||||
const memoRemoteUpdatedAt = toUpdatedIso(c.updatedAt ?? c.endedAt)
|
||||
await syncTranscriptionMemoFile({
|
||||
root: ctx.root,
|
||||
index,
|
||||
transcriptionId: c.id,
|
||||
relativePath: memoRel,
|
||||
serverMemo: c.memo,
|
||||
remoteUpdatedAtIso: memoRemoteUpdatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
if (maxEnded) {
|
||||
cursors.transcriptionLastEndedExclusiveByRoom[room.matrixRoomId] = maxEnded.toISOString()
|
||||
}
|
||||
else if (tExIso === undefined) {
|
||||
cursors.transcriptionLastEndedExclusiveByRoom[room.matrixRoomId] = new Date().toISOString()
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
warn(`Транскрипции звонков, комната ${room.matrixRoomId}: ${formatThrownValue(e)}`)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await saveCommunicationCursors(ctx.root, cursors)
|
||||
}
|
||||
catch (e) {
|
||||
warn(`Не удалось сохранить курсоры переписки (комнаты): ${formatThrownValue(e)}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
storyFileRelativePath,
|
||||
workspaceBasePath,
|
||||
} from './layout.js'
|
||||
import { pullProjectCommunicationArtifacts } from './pull-communication.js'
|
||||
import { pullNonProjectCommunicationArtifacts, pullProjectCommunicationArtifacts } from './pull-communication.js'
|
||||
import { scaffoldBmadWorkspacesAfterPull } from './scaffold-bmad-workspace.js'
|
||||
import { syncEntityFile } from './sync-entity-file.js'
|
||||
import { writeWorkspaceIndexMarkdown } from './workspace-index.js'
|
||||
@@ -329,6 +329,8 @@ export async function runPull(ctx: AuthenticatedContext, options: RunPullOptions
|
||||
|
||||
await pullProjectCommunicationArtifacts(ctx, index, allProjects, projectByHash)
|
||||
|
||||
await pullNonProjectCommunicationArtifacts(ctx, index)
|
||||
|
||||
await scaffoldBmadWorkspacesAfterPull(ctx, allProjects, projectByHash)
|
||||
|
||||
await saveIndex(ctx.root, index)
|
||||
|
||||
@@ -367,7 +367,6 @@ export async function runRestore(ctx: AuthenticatedContext, userPath: string): P
|
||||
if (entry.entity_type === 'call_transcription') {
|
||||
interface TranscriptionRestorePack {
|
||||
transcription: {
|
||||
matrixRoomId: string
|
||||
roomId: string
|
||||
startedAt: Date | string
|
||||
endedAt: Date | string | null | undefined
|
||||
@@ -394,7 +393,6 @@ export async function runRestore(ctx: AuthenticatedContext, userPath: string): P
|
||||
}
|
||||
const content = renderCallTranscriptionMarkdown(
|
||||
{
|
||||
matrixRoomId: tr.matrixRoomId,
|
||||
roomId: tr.roomId,
|
||||
startedAt: tr.startedAt,
|
||||
endedAt: tr.endedAt,
|
||||
|
||||
@@ -4,11 +4,34 @@ import type { BlagoEntityType, IndexFile } from './index-store.js'
|
||||
import * as fs from 'node:fs/promises'
|
||||
|
||||
import * as path from 'node:path'
|
||||
import { parseBlagoMarkdown, serializeBlagoMarkdown } from '../format/index.js'
|
||||
import { sha256Hex } from '../lib/hash.js'
|
||||
|
||||
import { warn } from '../ui/output.js'
|
||||
import { findByHash, normalizeRelativePath, upsertEntry } from './index-store.js'
|
||||
|
||||
/**
|
||||
* Канонический вид для сравнения «есть ли реальный конфликт», без волатильных
|
||||
* серверных меток времени. updated_at/created_at сервер бьёт при дочерних мутациях
|
||||
* (создание/удаление issue/story у родителя), и они попадают и во frontmatter, и в
|
||||
* content_etag_local. Из-за этого raw-sha файла расходится с etag, локальный файл
|
||||
* считается «грязным», а pull при изменившемся remote_updated_at пишет маркеры слияния —
|
||||
* хотя содержательно текст совпадает. Сравнение в каноне (без updated_at/created_at)
|
||||
* отличает настоящую правку от чистого bump'а времени.
|
||||
*/
|
||||
function canonicalForCompare(raw: string): string {
|
||||
try {
|
||||
const parsed = parseBlagoMarkdown(raw)
|
||||
const data = { ...parsed.data }
|
||||
delete data.updated_at
|
||||
delete data.created_at
|
||||
return serializeBlagoMarkdown(data, parsed.body)
|
||||
}
|
||||
catch {
|
||||
return raw
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureDirForFile(absFile: string): Promise<void> {
|
||||
await fs.mkdir(path.dirname(absFile), { recursive: true })
|
||||
}
|
||||
@@ -139,6 +162,21 @@ export async function syncEntityFile(params: {
|
||||
const dirty
|
||||
= current !== null && current !== undefined && sha256Hex(current) !== prev.content_etag_local
|
||||
if (dirty && remoteUpdatedAt !== prev.remote_updated_at) {
|
||||
// Реальный конфликт — только если содержимое расходится вне волатильных меток времени.
|
||||
// Если локальный и серверный тексты совпадают по канону (отличие лишь в updated_at/created_at),
|
||||
// это не конфликт: принимаем серверную версию и лечим etag, без маркеров слияния.
|
||||
if (current !== null && current !== undefined && canonicalForCompare(current) === canonicalForCompare(content)) {
|
||||
await ensureDirForFile(absNew)
|
||||
await fs.writeFile(absNew, content, 'utf8')
|
||||
upsertEntry(index, {
|
||||
entity_type: entityType,
|
||||
entity_hash: entityHash,
|
||||
relative_path: rel,
|
||||
remote_updated_at: remoteUpdatedAt,
|
||||
content_etag_local: sha256Hex(await fs.readFile(absNew, 'utf8')),
|
||||
})
|
||||
return
|
||||
}
|
||||
const merged = wrapMergeConflictMarkers(current ?? '', content)
|
||||
await ensureDirForFile(absNew)
|
||||
await fs.writeFile(absNew, merged, 'utf8')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@coopenomics/boot",
|
||||
"type": "module",
|
||||
"version": "2026.5.23-4",
|
||||
"version": "2026.5.27-2",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@9.0.6",
|
||||
"description": "CLI-утилита инициализации блокчейна и кооператива",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Загружаем per-instance конфиг из корня репо.
|
||||
# Загружаем per-instance конфиг из корня репо (для CHAIN_URL/MONGODB_URL/API_URL,
|
||||
# которые читают TS-код boot и шелл-скрипты networks.sh/preactivate.sh).
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
||||
if [ -f "$ROOT_DIR/.env" ]; then
|
||||
set -a
|
||||
@@ -9,26 +10,34 @@ if [ -f "$ROOT_DIR/.env" ]; then
|
||||
set +a
|
||||
fi
|
||||
|
||||
# Останавливаем и удаляем контейнеры вместе с volumes
|
||||
echo "Останавливаем и удаляем контейнеры с volumes..."
|
||||
docker compose down -v mongo postgres cooparser || true
|
||||
# Останавливаем и удаляем контейнеры (сеть НЕ трогаем — к ней может быть
|
||||
# подключён внешний контейнер вроде provider-backend; down её снёс бы с ошибкой).
|
||||
echo "Останавливаем и удаляем контейнеры..."
|
||||
docker compose rm -fsv mongo postgres monoredis cooparser coopback || true
|
||||
|
||||
# Останавливаем blockchain контейнер перед удалением данных
|
||||
echo "Останавливаем blockchain контейнер..."
|
||||
docker compose stop node || true
|
||||
|
||||
# Удаляем blockchain data
|
||||
# Удаляем тома баз данных для чистого старта (named volumes rm -v не чистит)
|
||||
echo "Удаляем тома баз данных..."
|
||||
PROJECT="${COMPOSE_PROJECT_NAME:-$(basename "$ROOT_DIR")}"
|
||||
docker volume rm "${PROJECT}_postgres_data" "${PROJECT}_mongo_data" 2>/dev/null || true
|
||||
|
||||
# Удаляем blockchain data через контейнер (alpine под root стирает данные
|
||||
# независимо от владельца — без sudo на любой ноде; на проде nodeos пишет под root).
|
||||
echo "Удаляем blockchain data..."
|
||||
# sudo chmod -R 755 ../blockchain-data/ 2>/dev/null || true
|
||||
sudo rm -rf ../blockchain-data/
|
||||
docker run --rm -v "$(cd .. && pwd)/blockchain-data:/d" alpine sh -c 'rm -rf /d/* /d/.[!.]* 2>/dev/null || true'
|
||||
|
||||
# Пересоздаем и запускаем базы данных
|
||||
# Пересоздаем и запускаем базы данных + Redis (monoredis).
|
||||
# monoredis ОБЯЗАТЕЛЕН: без него coopback падает на старте с
|
||||
# `getaddrinfo EAI_AGAIN monoredis` → MaxRetriesPerRequestError → nodemon crash.
|
||||
echo "Пересоздаем и запускаем базы данных..."
|
||||
docker compose up -d mongo postgres
|
||||
docker compose up -d mongo postgres monoredis
|
||||
|
||||
# Ждем готовности MongoDB
|
||||
# Ждем готовности MongoDB (standalone, ping вместо ожидания PRIMARY).
|
||||
echo "Ждем готовности MongoDB..."
|
||||
until docker compose exec -T mongo mongosh --eval "db.adminCommand('ping')" --quiet > /dev/null 2>&1; do
|
||||
until docker compose exec -T mongo mongosh --quiet --eval "db.adminCommand({ping:1}).ok" > /dev/null 2>&1; do
|
||||
echo "MongoDB еще не готов, ждем..."
|
||||
sleep 2
|
||||
done
|
||||
@@ -42,7 +51,7 @@ until docker compose exec -T postgres pg_isready -U postgres -d voskhod > /dev/n
|
||||
done
|
||||
echo "PostgreSQL готов!"
|
||||
|
||||
# Запускаем boot процесс
|
||||
# Запускаем boot процесс (clean: только программы Благорост/маркетплейс)
|
||||
echo "Запускаем boot процесс..."
|
||||
pnpm run boot:clean
|
||||
|
||||
@@ -51,6 +60,6 @@ echo "Запускаем parser..."
|
||||
docker compose up -d cooparser
|
||||
|
||||
echo "Запускаем контроллер..."
|
||||
docker compose restart coopback || true
|
||||
docker compose up -d --force-recreate coopback || true
|
||||
|
||||
echo "Перезапуск завершен!"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Загружаем per-instance конфиг из корня репо.
|
||||
# Загружаем per-instance конфиг из корня репо (для CHAIN_URL/MONGODB_URL/API_URL,
|
||||
# которые читают TS-код boot и шелл-скрипты networks.sh/preactivate.sh).
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
||||
if [ -f "$ROOT_DIR/.env" ]; then
|
||||
set -a
|
||||
@@ -9,30 +10,32 @@ if [ -f "$ROOT_DIR/.env" ]; then
|
||||
set +a
|
||||
fi
|
||||
|
||||
# Останавливаем контроллер перед очисткой данных
|
||||
echo "Останавливаем контроллер..."
|
||||
docker compose down coopback || true
|
||||
|
||||
# Останавливаем и удаляем контейнеры вместе с volumes
|
||||
echo "Останавливаем и удаляем контейнеры с volumes..."
|
||||
docker compose down -v mongo postgres cooparser || true
|
||||
docker compose down -v mongo postgres monoredis cooparser coopback || true
|
||||
|
||||
# Останавливаем blockchain контейнер перед удалением данных
|
||||
echo "Останавливаем blockchain контейнер..."
|
||||
docker compose stop node || true
|
||||
|
||||
# Удаляем blockchain data
|
||||
# Удаляем blockchain data.
|
||||
# Контейнерный wipe (alpine под root) стирает данные независимо от их владельца —
|
||||
# без sudo на любой ноде (на Pi нет passwordless sudo; на проде nodeos пишет
|
||||
# данные под root). Единый способ с reboot.sh / clean_reboot.sh.
|
||||
echo "Удаляем blockchain data..."
|
||||
# sudo chmod -R 755 ../blockchain-data/ 2>/dev/null || true
|
||||
sudo rm -rf ../blockchain-data/
|
||||
docker run --rm -v "$(cd .. && pwd)/blockchain-data:/d" alpine sh -c 'rm -rf /d/* /d/.[!.]* 2>/dev/null || true'
|
||||
|
||||
# Пересоздаем и запускаем базы данных
|
||||
# Пересоздаем и запускаем базы данных + Redis (monoredis).
|
||||
# monoredis ОБЯЗАТЕЛЕН: без него coopback падает на старте с
|
||||
# `getaddrinfo EAI_AGAIN monoredis` → MaxRetriesPerRequestError → nodemon crash,
|
||||
# и провайдер не может взять org-данные partner1 (PROVIDER_URL=coopback:2998).
|
||||
echo "Пересоздаем и запускаем базы данных..."
|
||||
docker compose up -d mongo postgres
|
||||
docker compose up -d mongo postgres monoredis
|
||||
|
||||
# Ждем готовности MongoDB
|
||||
# Ждем готовности MongoDB (standalone, ping вместо ожидания PRIMARY).
|
||||
echo "Ждем готовности MongoDB..."
|
||||
until docker compose exec -T mongo mongosh --eval "db.adminCommand('ping')" --quiet > /dev/null 2>&1; do
|
||||
until docker compose exec -T mongo mongosh --quiet --eval "db.adminCommand({ping:1}).ok" > /dev/null 2>&1; do
|
||||
echo "MongoDB еще не готов, ждем..."
|
||||
sleep 2
|
||||
done
|
||||
@@ -46,7 +49,7 @@ until docker compose exec -T postgres pg_isready -U postgres -d voskhod > /dev/n
|
||||
done
|
||||
echo "PostgreSQL готов!"
|
||||
|
||||
# Запускаем boot процесс
|
||||
# Запускаем boot процесс (расширенный: совет + пайщики; partner1 — при EXTRA_RENT=1)
|
||||
echo "Запускаем boot процесс..."
|
||||
pnpm run boot:extra
|
||||
|
||||
@@ -55,7 +58,7 @@ echo "Запускаем parser..."
|
||||
docker compose up -d cooparser
|
||||
|
||||
echo "Запускаем контроллер..."
|
||||
docker compose up -d coopback
|
||||
docker compose up -d --force-recreate coopback || true
|
||||
|
||||
# Контроллер создаёт marketplace-таблицы через TypeORM synchronize при старте.
|
||||
# Ждём появления marketplace_ku_details, затем засеваем 3 ПВЗ Подмосковья
|
||||
|
||||
@@ -315,31 +315,46 @@ export default class Blockchain {
|
||||
async activateFeature(feature: Feature) {
|
||||
await this.update_pass_instance()
|
||||
|
||||
await this.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: 'eosio',
|
||||
name: 'activate',
|
||||
authorization: [
|
||||
{
|
||||
actor: 'eosio',
|
||||
permission: 'active',
|
||||
try {
|
||||
await this.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: 'eosio',
|
||||
name: 'activate',
|
||||
authorization: [
|
||||
{
|
||||
actor: 'eosio',
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: {
|
||||
feature_digest: feature.hash,
|
||||
},
|
||||
],
|
||||
data: {
|
||||
feature_digest: feature.hash,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
console.log('Фича активирована: ', feature.name)
|
||||
console.log('Фича активирована: ', feature.name)
|
||||
}
|
||||
catch (e: any) {
|
||||
// На уже забутстрапленном (не обнулённом) чейне протокол-фича уже активна —
|
||||
// nodeos отдаёт protocol_feature_exception (code 3250000). Это не повод
|
||||
// ронять весь boot: фича на месте, нужный результат достигнут, продолжаем.
|
||||
const errName = e?.json?.error?.name
|
||||
const errCode = e?.json?.error?.code
|
||||
const msg = e instanceof Error ? e.message : String(e)
|
||||
if (errName === 'protocol_feature_exception' || errCode === 3250000 || /already activated/i.test(msg)) {
|
||||
console.warn(`Фича уже активирована, пропускаю: ${feature.name}`)
|
||||
return
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
async createToken(params: TokenContract.Interfaces.ICreate) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import config from '../configs'
|
||||
import { initExtensionsInPostgres, initSystemStatus } from '../postgres-init'
|
||||
import { installExtraData, installInitialData, startInfra } from './infra'
|
||||
import { CooperativeClass, startCoop } from './cooperative'
|
||||
import { startCoop } from './cooperative'
|
||||
|
||||
export async function boot() {
|
||||
const blockchain = await startInfra()
|
||||
@@ -14,18 +13,29 @@ export async function boot() {
|
||||
}
|
||||
|
||||
export async function bootClean() {
|
||||
const blockchain = await startInfra()
|
||||
|
||||
console.log('Создаём программы (Благорост и маркетплейс)')
|
||||
|
||||
const cooperative = new CooperativeClass(blockchain)
|
||||
await cooperative.createPrograms(config.provider)
|
||||
// Только инфраструктура: контракты, фичи, токен, системные параметры.
|
||||
// Ни совета, ни программ — программы (createPrograms) требуют существующий
|
||||
// совет и создаются в boot/bootExtra внутри installInitialData. В clean их
|
||||
// не делаем (иначе soviet::createprog падает с «Совет не найден»).
|
||||
await startInfra()
|
||||
}
|
||||
|
||||
export async function bootExtra() {
|
||||
const blockchain = await startInfra()
|
||||
await installInitialData(blockchain, true) // Создать расширенный совет (устанавливает статус 'active' в MongoDB)
|
||||
await installExtraData(blockchain) // Добавить дополнительных пайщиков
|
||||
|
||||
// installExtraData регистрирует partner1 как coop с auto-approve от провайдера —
|
||||
// это и есть программная on-chain активация, которая триггерит аренду VM в
|
||||
// провайдере (PENDING→RENT). boot:extra используется НЕ только для аренды
|
||||
// сервера (например, просто пересев совета/чейна для других задач), поэтому
|
||||
// провижининг partner1 включается ТОЛЬКО под флагом EXTRA_RENT=1.
|
||||
if (process.env.EXTRA_RENT === '1') {
|
||||
console.log('EXTRA_RENT=1 → installExtraData: провижининг partner1 (триггер аренды)')
|
||||
await installExtraData(blockchain) // Регистрируем partner1 как coop (active)
|
||||
}
|
||||
else {
|
||||
console.log('EXTRA_RENT не задан → пропускаем провижининг partner1 (аренда не запускается)')
|
||||
}
|
||||
|
||||
console.log('Инициализируем статус системы в PostgreSQL')
|
||||
await initSystemStatus() // Устанавливает статус 'active' в PostgreSQL
|
||||
|
||||
@@ -15,6 +15,9 @@ import { sleep } from '../utils'
|
||||
import { generateRandomSHA256 } from '../utils/randomHash'
|
||||
import { initUsersInPostgres, initVaultInPostgres } from '../postgres-init'
|
||||
import { CooperativeClass } from './cooperative'
|
||||
import { signProgramAgreement } from './sign-program-agreement'
|
||||
import { fakeDocument } from '../tests/shared/fakeDocument'
|
||||
import { walletDraftId, walletProgramId } from '../tests/capital/consts'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
@@ -674,10 +677,17 @@ export async function installExtraData(blockchain: Blockchain) {
|
||||
status: 'active',
|
||||
})
|
||||
|
||||
// Ресурсы для партнёра: powerup CPU/NET. transfer токенов пропускаем —
|
||||
// eosio.token::transfer падает на проверке membership в wallet program,
|
||||
// которая для partner1 не настроена. Для provider sync + Hostkey-flow
|
||||
// токены не нужны: tx от имени partner1 пойдут от soviet/admin.
|
||||
// Записываем partner1 в ЦПП Кошелька оператора (voskhod, program_id=1).
|
||||
// БЕЗ этого провайдерский performInitialTransfers (150 AXON на partner1 ДО
|
||||
// перехода в ACTIVE, см. provider CLAUDE.md §13) падает ассертом
|
||||
// eosio.token::is_can_transfer «Получатель не является участником целевой
|
||||
// потребительской программы кошелька» — и инстанс навсегда застревает в
|
||||
// INSTALL. wallet::signagree (auth voskhod@active) делает partner1 членом
|
||||
// ЦПП Кошелька. Та же механика, что для обычных пайщиков в participant.ts.
|
||||
console.log('Подписываем wallet-соглашение за partner1 (членство в ЦПП Кошелька)')
|
||||
await signProgramAgreement(blockchain, config.provider, account.username, walletProgramId, walletDraftId, fakeDocument)
|
||||
|
||||
// Ресурсы для партнёра: powerup CPU/NET.
|
||||
await blockchain.powerup({
|
||||
payer: 'eosio',
|
||||
receiver: account.username,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@coopenomics/cleos",
|
||||
"version": "2026.5.23-4",
|
||||
"version": "2026.5.27-2",
|
||||
"private": true,
|
||||
"description": "Обёртка над кошельком cleos для EOSIO блокчейна",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
# Локальная сборка всех контрактов: оборачивает build_contracts_cdt.sh в
|
||||
# CDT-образ. Сам cmake/make живёт в build_contracts_cdt.sh — он же
|
||||
# используется в CI (там CDT ставится из .deb, без docker), поэтому
|
||||
# флаги сборки в одном месте и не разъезжаются.
|
||||
|
||||
mode="${1:-prod}"
|
||||
|
||||
if [ "$mode" = "test" ]; then
|
||||
is_testnet="ON"
|
||||
else
|
||||
is_testnet="OFF"
|
||||
fi
|
||||
|
||||
docker run --rm --name cdt \
|
||||
--volume "$(pwd):/project" \
|
||||
-w /project \
|
||||
dicoop/blockchain:latest \
|
||||
/bin/bash -c "mkdir -p build && cd build && cmake -DBUILD_TARGET= -DTEST_TARGET= -DVERBOSE=ON -DBUILD_TESTS=OFF -DIS_TESTNET=$is_testnet .. && make"
|
||||
/bin/bash -c "./build_contracts_cdt.sh $mode"
|
||||
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# Компиляция всех контрактов кооперативной экономики через CDT.
|
||||
#
|
||||
# ВАЖНО: скрипт запускается УЖЕ внутри окружения с установленным CDT —
|
||||
# тулчейн ожидается по пути /cdt/build/lib/cmake/cdt/CDTWasmToolchain.cmake
|
||||
# (он захардкожен в CMakeLists.txt). Здесь НЕТ docker — только cmake/make.
|
||||
#
|
||||
# Два способа подготовить это окружение:
|
||||
# - локально: build-all.sh оборачивает скрипт в `docker run dicoop/blockchain:latest`
|
||||
# (в образе CDT уже лежит в /cdt/build);
|
||||
# - в CI: CDT ставится из .deb (C9S/cdt), а /cdt/build симлинкуется на
|
||||
# /usr/opt/cdt/<ver> (см. .github/workflows/release.yaml).
|
||||
#
|
||||
# Один источник cmake-флагов для обоих путей — чтобы локальная и CI-сборка
|
||||
# не разъезжались.
|
||||
set -euo pipefail
|
||||
|
||||
mode="${1:-prod}"
|
||||
if [ "$mode" = "test" ]; then
|
||||
is_testnet="ON"
|
||||
else
|
||||
is_testnet="OFF"
|
||||
fi
|
||||
|
||||
mkdir -p build
|
||||
cd build
|
||||
cmake -DBUILD_TARGET= -DTEST_TARGET= -DVERBOSE=ON -DBUILD_TESTS=OFF -DIS_TESTNET="$is_testnet" ..
|
||||
make
|
||||
+2
-2
@@ -72,8 +72,8 @@ void capital::importcontrib(eosio::name coopname, eosio::name username, checksum
|
||||
auto idx = user_wallets.get_index<"byuserwallet"_n>();
|
||||
auto preimp_it = idx.find(combine_ids(ledger2_wallets::PREIMP_FUND.value, username.value));
|
||||
if (preimp_it != idx.end() && preimp_it->available.amount > 0) {
|
||||
eosio::check(preimp_it->blocked.amount == 0,
|
||||
"preimp.blocked > 0 не поддерживается при импорте — обратиться в поддержку");
|
||||
// Поле blocked упразднено (2026-05-24): резерв/блокировка на кошельках
|
||||
// больше не используется, проверка preimp.blocked == 0 излишня.
|
||||
Ledger2::apply(
|
||||
_capital, coopname, operations::capital::DROP_PREIMP,
|
||||
preimp_it->available, username, contributor_hash,
|
||||
|
||||
@@ -9,23 +9,26 @@ namespace Capital::Core {
|
||||
* @brief Совокупный баланс программы Благорост на уровне кооператива.
|
||||
*
|
||||
* Источник — ledger2 L2 (`wallets[BLAGOROST_FUND]`), агрегированный по всем
|
||||
* пайщикам. Σ L3.{available, blocked}[w.cap.blago] == L2.{available, blocked}
|
||||
* (Эпик 3 invariant), поэтому L2 — авторитетная сумма для CRPS-знаменателя.
|
||||
* Если кошелёк ещё не создан (никто не инвестировал) — нулевой баланс.
|
||||
* пайщикам. Σ L3.available[w.cap.blago] == L2.available (Эпик 3 invariant),
|
||||
* поэтому L2 — авторитетная сумма для CRPS-знаменателя. Если кошелёк ещё не
|
||||
* создан (никто не инвестировал) — нулевой баланс.
|
||||
*
|
||||
* Поле blocked упразднено (2026-05-24): субсчёт «заблокировано» больше не
|
||||
* используется, баланс кошелька = available.
|
||||
*/
|
||||
eosio::asset get_capital_program_share_balance(eosio::name coopname) {
|
||||
wallets2_index wallets(_ledger2, coopname.value);
|
||||
auto it = wallets.find(ledger2_wallets::BLAGOROST_FUND.value);
|
||||
if (it == wallets.end()) return eosio::asset(0, _root_govern_symbol);
|
||||
return it->available + it->blocked;
|
||||
return it->available;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Доля пайщика в Благоросте (available + blocked) из ledger2 L3.
|
||||
* @brief Доля пайщика в Благоросте (available) из ledger2 L3.
|
||||
*
|
||||
* Источник — `userwallets[(w.cap.blago, username)]`. Запись существует
|
||||
* только пока available + blocked > 0 (Эпик 3 §6: auto-delete на нуле),
|
||||
* поэтому отсутствие = нулевой баланс.
|
||||
* только пока available > 0 (Эпик 3 §6: auto-delete на нуле), поэтому
|
||||
* отсутствие = нулевой баланс. Поле blocked упразднено (2026-05-24).
|
||||
*/
|
||||
eosio::asset get_capital_program_user_share_balance(eosio::name coopname, eosio::name username) {
|
||||
userwallets_index user_wallets(_ledger2, coopname.value);
|
||||
@@ -33,6 +36,6 @@ namespace Capital::Core {
|
||||
auto key = combine_ids(ledger2_wallets::BLAGOROST_FUND.value, username.value);
|
||||
auto it = idx.find(key);
|
||||
if (it == idx.end()) return eosio::asset(0, _root_govern_symbol);
|
||||
return it->available + it->blocked;
|
||||
return it->available;
|
||||
}
|
||||
}
|
||||
@@ -67,12 +67,12 @@ void ledger2::revert(eosio::name coopname,
|
||||
eosio::check(original_operation_id != 0, "revert: original_operation_id обязателен");
|
||||
|
||||
// -------- validate mirror_wallet_op --------
|
||||
eosio::check(mirror_wallet_op <= 5, "revert: неизвестный mirror_wallet_op");
|
||||
// Не позволяем откатывать через BLOCK/UNBLOCK — они асимметричны и нет
|
||||
// адекватного зеркала в одной операции (BLOCK + UNBLOCK — обратные сами по себе).
|
||||
eosio::check(mirror_wallet_op != static_cast<uint8_t>(WalletOp::BLOCK) &&
|
||||
mirror_wallet_op != static_cast<uint8_t>(WalletOp::UNBLOCK),
|
||||
"revert: BLOCK/UNBLOCK не подлежат откату через revert (они симметричны сами себе)");
|
||||
// Зеркало revert — только TRANSFER (обмен wallet_from/wallet_to) либо BURN
|
||||
// (зеркало ISSUE: изъятие с wallet_from). Бывшие BLOCK/UNBLOCK упразднены
|
||||
// (2026-05-24); ISSUE/NONE как зеркало смысла не имеют.
|
||||
eosio::check(mirror_wallet_op == static_cast<uint8_t>(WalletOp::TRANSFER) ||
|
||||
mirror_wallet_op == static_cast<uint8_t>(WalletOp::BURN),
|
||||
"revert: mirror_wallet_op должен быть TRANSFER или BURN");
|
||||
|
||||
// -------- validate mirror wallets/accounts --------
|
||||
if (mirror_wallet_from.value != 0) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @brief Атомарная операция по кошельку (issue/transfer/block/unblock/burn/burn_blocked).
|
||||
* @brief Атомарная операция по кошельку (issue/transfer/burn).
|
||||
*
|
||||
* Внутренний action ledger2 — вызывается только через inline из apply().
|
||||
* Auth: только сам ledger2 (require_auth(get_self())).
|
||||
@@ -53,13 +53,15 @@ void ledger2::walletop(eosio::name coopname,
|
||||
eosio::check(amount.symbol == _root_govern_symbol,
|
||||
"walletop: некорректный символ валюты");
|
||||
eosio::check(memo.size() < 256, "walletop: memo > 255");
|
||||
// op_code = 5 (NONE) намеренно не допускается: NONE-операции — это только
|
||||
// бухпроводка без кошелькового движения, apply.cpp не диспатчит для них walletop.
|
||||
// Прямой вызов с op_code=5 был бы no-op и сбил бы инвариант parity.
|
||||
// Допустимы 0..4 (ISSUE/TRANSFER/BLOCK/UNBLOCK/BURN) и 6 (BURN_BLOCKED).
|
||||
eosio::check(op_code <= static_cast<uint8_t>(WalletOp::BURN_BLOCKED) &&
|
||||
op_code != static_cast<uint8_t>(WalletOp::NONE),
|
||||
"walletop: неизвестный op_code");
|
||||
// Допустимы только ISSUE(0), TRANSFER(1), BURN(4). NONE(5) — это только
|
||||
// бухпроводка без кошелькового движения, apply.cpp для неё walletop не диспатчит;
|
||||
// прямой вызов был бы no-op и сбил бы parity. Бывшие BLOCK(2)/UNBLOCK(3)/
|
||||
// BURN_BLOCKED(6) упразднены (2026-05-24): резерв возврата теперь TRANSFER на
|
||||
// w.wal.wpend, их op_code больше не валиден.
|
||||
eosio::check(op_code == static_cast<uint8_t>(WalletOp::ISSUE) ||
|
||||
op_code == static_cast<uint8_t>(WalletOp::TRANSFER) ||
|
||||
op_code == static_cast<uint8_t>(WalletOp::BURN),
|
||||
"walletop: недопустимый op_code");
|
||||
|
||||
wallets2_index wallets(get_self(), coopname.value);
|
||||
userwallets_index user_wallets(get_self(), coopname.value);
|
||||
@@ -172,7 +174,7 @@ void ledger2::walletop(eosio::name coopname,
|
||||
// миграций 048/049.
|
||||
//
|
||||
// walletop по построению применяет одно и то же `amount` к L2 и L3 (см.
|
||||
// ниже case'ы ISSUE/TRANSFER/BLOCK/UNBLOCK/BURN/BURN_BLOCKED), а sender-guard на
|
||||
// ниже case'ы ISSUE/TRANSFER/BURN), а sender-guard на
|
||||
// строке 46-47 запрещает обход. Поэтому инвариант сохраняется по
|
||||
// конструкции; полную сверку выполняет бэкенд («стол бухгалтера»),
|
||||
// вне транзакционного hot path.
|
||||
@@ -227,60 +229,6 @@ void ledger2::walletop(eosio::name coopname,
|
||||
cleanup_l3_if_empty(wallet_from);
|
||||
break;
|
||||
}
|
||||
case WalletOp::BLOCK: {
|
||||
eosio::check(wallet_from.value != 0, "walletop BLOCK: требуется wallet_from");
|
||||
eosio::check(wallet_to.value == 0, "walletop BLOCK: wallet_to должен быть пустым");
|
||||
|
||||
auto it = wallets.find(wallet_from.value);
|
||||
eosio::check(it != wallets.end() && it->available >= amount,
|
||||
std::string{"walletop BLOCK: недостаточно available на кошельке "} +
|
||||
wallet_from.to_string());
|
||||
wallets.modify(it, payer, [&](auto& w) {
|
||||
w.available -= amount;
|
||||
w.blocked += amount;
|
||||
});
|
||||
|
||||
if (is_user_shared_l3(wallet_from)) {
|
||||
auto uw = find_l3(wallet_from);
|
||||
eosio::check(uw != user_wallets.get_index<"byuserwallet"_n>().end() &&
|
||||
uw->available >= amount,
|
||||
std::string{"walletop BLOCK: недостаточно L3-available у пайщика "} +
|
||||
username.to_string() + " на " + wallet_from.to_string());
|
||||
auto uw_pri = user_wallets.find(uw->id);
|
||||
user_wallets.modify(uw_pri, payer, [&](auto& r) {
|
||||
r.available -= amount;
|
||||
r.blocked += amount;
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WalletOp::UNBLOCK: {
|
||||
eosio::check(wallet_from.value != 0, "walletop UNBLOCK: требуется wallet_from");
|
||||
eosio::check(wallet_to.value == 0, "walletop UNBLOCK: wallet_to должен быть пустым");
|
||||
|
||||
auto it = wallets.find(wallet_from.value);
|
||||
eosio::check(it != wallets.end() && it->blocked >= amount,
|
||||
std::string{"walletop UNBLOCK: недостаточно blocked на кошельке "} +
|
||||
wallet_from.to_string());
|
||||
wallets.modify(it, payer, [&](auto& w) {
|
||||
w.blocked -= amount;
|
||||
w.available += amount;
|
||||
});
|
||||
|
||||
if (is_user_shared_l3(wallet_from)) {
|
||||
auto uw = find_l3(wallet_from);
|
||||
eosio::check(uw != user_wallets.get_index<"byuserwallet"_n>().end() &&
|
||||
uw->blocked >= amount,
|
||||
std::string{"walletop UNBLOCK: недостаточно L3-blocked у пайщика "} +
|
||||
username.to_string() + " на " + wallet_from.to_string());
|
||||
auto uw_pri = user_wallets.find(uw->id);
|
||||
user_wallets.modify(uw_pri, payer, [&](auto& r) {
|
||||
r.blocked -= amount;
|
||||
r.available += amount;
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WalletOp::BURN: {
|
||||
eosio::check(wallet_from.value != 0, "walletop BURN: требуется wallet_from");
|
||||
eosio::check(wallet_to.value == 0, "walletop BURN: wallet_to должен быть пустым");
|
||||
@@ -311,30 +259,6 @@ void ledger2::walletop(eosio::name coopname,
|
||||
eosio::check(false, "walletop NONE: запрещённый op_code");
|
||||
break;
|
||||
}
|
||||
case WalletOp::BURN_BLOCKED: {
|
||||
eosio::check(wallet_from.value != 0, "walletop BURN_BLOCKED: требуется wallet_from");
|
||||
eosio::check(wallet_to.value == 0, "walletop BURN_BLOCKED: wallet_to должен быть пустым");
|
||||
|
||||
auto it = wallets.find(wallet_from.value);
|
||||
eosio::check(it != wallets.end() && it->blocked >= amount,
|
||||
std::string{"walletop BURN_BLOCKED: недостаточно blocked на кошельке "} +
|
||||
wallet_from.to_string());
|
||||
wallets.modify(it, payer, [&](auto& w) { w.blocked -= amount; });
|
||||
|
||||
if (is_user_shared_l3(wallet_from)) {
|
||||
auto uw = find_l3(wallet_from);
|
||||
eosio::check(uw != user_wallets.get_index<"byuserwallet"_n>().end() &&
|
||||
uw->blocked >= amount,
|
||||
std::string{"walletop BURN_BLOCKED: недостаточно L3-blocked у пайщика "} +
|
||||
username.to_string() + " на " + wallet_from.to_string());
|
||||
auto uw_pri = user_wallets.find(uw->id);
|
||||
user_wallets.modify(uw_pri, payer, [&](auto& r) { r.blocked -= amount; });
|
||||
}
|
||||
|
||||
cleanup_l2_if_empty(wallet_from);
|
||||
cleanup_l3_if_empty(wallet_from);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Post-mutation Σ L3 == L2: см. блок выше (строки 163-175). Снято
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* @brief Универсальное миграционное действие контракта ledger2 — точка
|
||||
* расширения для разовых исправлений состояния, которые можно провести
|
||||
@@ -9,23 +7,33 @@
|
||||
* после её прогона на проде тело очищается до пустого `require_auth(get_self())`
|
||||
* (как в `capital::migrate`).
|
||||
*
|
||||
* Текущая задача (2026-05-21): чистка осиротевших L3-записей `w.cap.gen`
|
||||
* на voskhod после смены WalletKind GENERATOR_FUND с USER_SHARED на
|
||||
* COOPERATIVE (см. `lib/core/ledger2/wallets.hpp`).
|
||||
* Текущая задача (2026-05-24): свёртка `blocked → available` по ВСЕМ коопам.
|
||||
*
|
||||
* Контекст: на voskhod `convertsegm` падал «недостаточно L3-средств у
|
||||
* пайщика» в проектах, где CRPS перераспределял доли между сегментами.
|
||||
* w.cap.gen был USER_SHARED, а CRPS в approvecmmt не делал per-user
|
||||
* компенсирующих TRANSFER между сегментами: `Σ COMMIT_RID == Σ ACCEPT_RID`
|
||||
* соблюдался только на проекте, не на пайщике.
|
||||
* Контекст: механика «заблокированного» баланса упразднена (см.
|
||||
* `lib/core/ledger2/operations.hpp` — удалены WalletOp BLOCK/UNBLOCK/BURN_BLOCKED;
|
||||
* резерв возврата паевого теперь выражается переводом на кошелёк-резерв
|
||||
* `w.wal.wpend`). Поле `blocked` остаётся в таблицах `wallets2`/`userwallets`
|
||||
* как deprecated (физическое удаление поля = небезопасная смена layout таблицы
|
||||
* на живых коопах, выносится в отдельный cleanup-деплой). Перед тем как поле
|
||||
* перестанет поддерживаться кодом, накопленные `blocked`-остатки нужно вернуть
|
||||
* в `available`, чтобы средства не «зависли» на упразднённом субсчёте.
|
||||
*
|
||||
* Архитектурный фикс: w.cap.gen — COOPERATIVE-пул без L3. Чтобы UI/бэкенд
|
||||
* не врали остатками по «личным» w.cap.gen, удаляем осиротевшие L3-записи
|
||||
* прямым `userwallets.erase`. L2-баланс `wallets2[w.cap.gen]` уже верен
|
||||
* (синхронен с Σ старых userwallets), его не трогаем.
|
||||
* Действие: пройти всех кооперативов (cooperatives2 в scope registrator) и для
|
||||
* каждого свернуть `blocked → available` на уровнях L2 (`wallets2`) и L3
|
||||
* (`userwallets`): `available += blocked; blocked = 0`. Сумма средств на кошельке
|
||||
* не меняется — только субсчёт.
|
||||
*
|
||||
* Идемпотентно: повторный вызов на чистой БД — no-op (lower_bound пуст).
|
||||
* Только voskhod: на остальных кооперативах w.cap.gen не использовался.
|
||||
* Идемпотентно: после свёртки `blocked == 0`, повторный прогон — no-op.
|
||||
*
|
||||
* Сигнатура без аргументов — действие вызывается автоматически при деплое
|
||||
* контракта (как и прочие задачи migrate); проходит по всем кооперативам сам.
|
||||
*
|
||||
* ПРЕДУСЛОВИЕ (операционное): на момент прогона не должно быть заявок на возврат
|
||||
* «в полёте» (статусы pending/authorized в `wallet::withdraws`) — их `blocked`
|
||||
* относится к старой механике и при свёртке в `available` вернётся пайщику как
|
||||
* свободные средства, а последующий `completewthd` (BURN с `w.wal.wpend`) не
|
||||
* найдёт резерва. Незавершённые заявки нужно довести (complete/decline) ДО
|
||||
* деплоя с этой миграцией.
|
||||
*
|
||||
* @ingroup public_ledger2_actions
|
||||
*
|
||||
@@ -34,24 +42,31 @@
|
||||
void ledger2::migrate() {
|
||||
require_auth(get_self());
|
||||
|
||||
const eosio::name target_coop = "voskhod"_n;
|
||||
cooperatives2_index coops(_registrator, _registrator.value);
|
||||
|
||||
userwallets_index user_wallets(get_self(), target_coop.value);
|
||||
auto idx = user_wallets.get_index<"bywallet"_n>();
|
||||
for (auto c = coops.begin(); c != coops.end(); ++c) {
|
||||
const eosio::name coopname = c->username;
|
||||
|
||||
// Собираем primary id записей до erase (модификация контейнера при
|
||||
// итерации через secondary index — небезопасна).
|
||||
std::vector<uint64_t> ids_to_erase;
|
||||
for (auto it = idx.lower_bound(ledger2_wallets::GENERATOR_FUND.value);
|
||||
it != idx.end() && it->wallet_name == ledger2_wallets::GENERATOR_FUND;
|
||||
++it) {
|
||||
ids_to_erase.push_back(it->id);
|
||||
}
|
||||
// --- L3: userwallets[coopname] ---
|
||||
// Модифицируем только не-ключевые поля (available/blocked) — итерация по
|
||||
// первичному индексу с modify безопасна (порядок строк не меняется).
|
||||
userwallets_index user_wallets(get_self(), coopname.value);
|
||||
for (auto it = user_wallets.begin(); it != user_wallets.end(); ++it) {
|
||||
if (it->blocked.amount <= 0) continue;
|
||||
user_wallets.modify(it, get_self(), [&](auto& r) {
|
||||
r.available += r.blocked;
|
||||
r.blocked = eosio::asset(0, r.blocked.symbol);
|
||||
});
|
||||
}
|
||||
|
||||
for (uint64_t id : ids_to_erase) {
|
||||
auto pri = user_wallets.find(id);
|
||||
if (pri != user_wallets.end()) {
|
||||
user_wallets.erase(pri);
|
||||
// --- L2: wallets2[coopname] ---
|
||||
wallets2_index wallets(get_self(), coopname.value);
|
||||
for (auto it = wallets.begin(); it != wallets.end(); ++it) {
|
||||
if (it->blocked.amount <= 0) continue;
|
||||
wallets.modify(it, get_self(), [&](auto& w) {
|
||||
w.available += w.blocked;
|
||||
w.blocked = eosio::asset(0, w.blocked.symbol);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@ static constexpr uint64_t _capital_program_id = 4;
|
||||
#ifdef IS_TESTNET
|
||||
static constexpr uint64_t MIN_SOVIET_MEMBERS_COUNT = 1; /*!< минимальное количество членов совета (тест) */
|
||||
#else
|
||||
static constexpr uint64_t MIN_SOVIET_MEMBERS_COUNT = 5; /*!< минимальное количество членов совета (прод) */
|
||||
static constexpr uint64_t MIN_SOVIET_MEMBERS_COUNT = 3; /*!< минимальное количество членов совета (прод) */
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,9 +31,9 @@
|
||||
* - `WalletOp::WALLET_ONLY` удалён (ADR-003): «без бухпроводок» определяется
|
||||
* парой `(debit_account_id == 0, credit_account_id == 0)` на уровне записи.
|
||||
* Для `o.cap.invest` теперь TRANSFER без проводок (оба account_id == 0).
|
||||
* - Добавлен `WalletOp::BURN` (ADR-003): `available -= amount` на `wallet_from`,
|
||||
* без `wallet_to`. На текущем этапе в `OPERATION_REGISTRY` не используется —
|
||||
* зарезервирован под будущие операции штатного сжигания.
|
||||
* - `WalletOp::BURN` (ADR-003): `available -= amount` на `wallet_from`,
|
||||
* без `wallet_to`. Используется в `o.wal.wthcpl` (сжигание резерва возврата
|
||||
* с `w.wal.wpend`), `o.cap.drppre` и как зеркало ISSUE в `revert`.
|
||||
* - Единые программные кошельки: `BLAGOROST_FUND` (`w.cap.blago`) и
|
||||
* `GENERATOR_FUND` (`w.cap.gen`) — заменили ранее декомпозированные
|
||||
* `bginv/bgprop/bgrid/bgmem` и `gncom/gnmem` (ADR-009).
|
||||
@@ -61,9 +61,9 @@ namespace operations {
|
||||
// wallet
|
||||
namespace wallet {
|
||||
inline constexpr eosio::name COMPLETE_DEPOSIT = "o.wal.depcpl"_n; ///< Завершение внесения паевого взноса (Dr 51 / Cr 80, ISSUE SHARE_FUND_PAY).
|
||||
inline constexpr eosio::name COMPLETE_WITHDRAW = "o.wal.wthcpl"_n; ///< Завершение возврата паевого взноса (Dr 80 / Cr 51, BURN_BLOCKED SHARE_FUND_PAY — деньги уходят из системы, без wallet_to).
|
||||
inline constexpr eosio::name REQUEST_WITHDRAW = "o.wal.wthreq"_n; ///< Запрос на возврат паевого: BLOCK на SHARE_FUND_PAY (без Dr/Cr).
|
||||
inline constexpr eosio::name DECLINE_WITHDRAW = "o.wal.wthdec"_n; ///< Отклонение запроса на возврат: UNBLOCK на SHARE_FUND_PAY (без Dr/Cr).
|
||||
inline constexpr eosio::name COMPLETE_WITHDRAW = "o.wal.wthcpl"_n; ///< Завершение возврата паевого взноса (Dr 80 / Cr 51, BURN с WITHDRAW_PENDING — деньги уходят из системы, без wallet_to).
|
||||
inline constexpr eosio::name REQUEST_WITHDRAW = "o.wal.wthreq"_n; ///< Запрос на возврат паевого: TRANSFER SHARE_FUND_PAY → WITHDRAW_PENDING (резерв, без Dr/Cr).
|
||||
inline constexpr eosio::name DECLINE_WITHDRAW = "o.wal.wthdec"_n; ///< Отклонение запроса на возврат: TRANSFER WITHDRAW_PENDING → SHARE_FUND_PAY (снятие резерва, без Dr/Cr).
|
||||
inline constexpr eosio::name CONVERT_TO_MEMBER = "o.wal.conv"_n; ///< Конвертация цифрового рубля в универсальный членский кошелёк (Dr 80 / Cr 86, TRANSFER SHARE_FUND_PAY → CK_MEMBER). Conditional-шаг серии createorder в «Столе заказов».
|
||||
}
|
||||
|
||||
@@ -135,14 +135,23 @@ namespace operations {
|
||||
* `(debit_account_id == 0, credit_account_id == 0)` на уровне записи реестра.
|
||||
* Compile-time правило `(debit==0) ⇔ (credit==0)` ловит смешанные пары.
|
||||
*/
|
||||
//
|
||||
// Удаление BLOCK/UNBLOCK/BURN_BLOCKED (2026-05-24): механика «заблокированного»
|
||||
// баланса упразднена. Резерв средств под заявку на возврат паевого теперь
|
||||
// выражается переводом на отдельный кошелёк-резерв `w.wal.wpend` (TRANSFER),
|
||||
// возврат резерва — обратным TRANSFER, завершение — BURN с резерва. Поле
|
||||
// `blocked` в таблицах wallets2/userwallets оставлено deprecated (всегда 0
|
||||
// после ledger2::migrate-свёртки) — физическое удаление поля = небезопасная
|
||||
// смена layout таблицы на живых коопах, выносится в отдельный cleanup-деплой.
|
||||
//
|
||||
// Числовые значения ISSUE/TRANSFER/BURN/NONE СОХРАНЕНЫ (не перенумерованы),
|
||||
// чтобы исторические op_code в blockchain_actions читались бэкендом без сдвига
|
||||
// смысла (2 и 3 — бывшие BLOCK/UNBLOCK — больше не выдаются и невалидны на входе).
|
||||
enum class WalletOp : uint8_t {
|
||||
ISSUE = 0, ///< первичный вход средств на кошелёк wallet_to (wallet_from = empty)
|
||||
TRANSFER = 1, ///< перемещение wallet_from → wallet_to (с Dr/Cr ИЛИ без — по парам account_id)
|
||||
BLOCK = 2, ///< available-=amount, blocked+=amount на wallet_from
|
||||
UNBLOCK = 3, ///< blocked-=amount, available+=amount на wallet_from
|
||||
BURN = 4, ///< изъятие amount с wallet_from->available, без wallet_to. Покрывает оба кейса: (a) штатное сжигание как бизнес-операция в OPERATION_REGISTRY; (b) зеркало ISSUE при `ledger2::revert` (различие — через operation_code: `o.adj.rev` для adjustment-mirror).
|
||||
NONE = 5, ///< только бухпроводка без перемещения средств (wallet_from = empty, wallet_to = empty, debit ≠ 0, credit ≠ 0). Покрывает кейсы внутрибалансовых проводок типа Dr 04 / Cr 08 (приём РИД в НМА), когда кошелёк уже на нужном программном фонде.
|
||||
BURN_BLOCKED = 6, ///< изъятие amount из wallet_from->blocked, без wallet_to. Используется при завершении возврата паевого (`o.wal.wthcpl`): средства предварительно заблокированы через BLOCK (REQUEST_WITHDRAW), на завершении сжигаются — выходят из системы (получателя на цепи нет). На L3 синхронно: from_uw->blocked -= amount.
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -151,7 +160,7 @@ enum class WalletOp : uint8_t {
|
||||
* Семантика полей по `wallet_op`:
|
||||
* - ISSUE: wallet_from = eosio::name{}, wallet_to = required.
|
||||
* - TRANSFER: wallet_from = required, wallet_to = required (≠ from).
|
||||
* - BLOCK / UNBLOCK / BURN / BURN_BLOCKED: wallet_from = required, wallet_to = eosio::name{}.
|
||||
* - BURN: wallet_from = required, wallet_to = eosio::name{}.
|
||||
*
|
||||
* Семантика бух.проводки:
|
||||
* - Без проводок: debit_account_id == 0 И credit_account_id == 0.
|
||||
@@ -163,7 +172,7 @@ struct OperationRegistryEntry {
|
||||
eosio::name process_type; ///< тип процесса с префиксом `p.<contract>.<noun>`
|
||||
WalletOp wallet_op;
|
||||
eosio::name wallet_from; ///< пустое имя для ISSUE
|
||||
eosio::name wallet_to; ///< пустое имя для BLOCK/UNBLOCK/BURN/BURN_BLOCKED
|
||||
eosio::name wallet_to; ///< пустое имя для BURN
|
||||
uint64_t debit_account_id; ///< 0 если без бухпроводки (тогда credit_account_id тоже == 0)
|
||||
uint64_t credit_account_id; ///< 0 если без бухпроводки (тогда debit_account_id тоже == 0)
|
||||
const char* human_name;
|
||||
@@ -190,12 +199,12 @@ static constexpr OperationRegistryEntry OPERATION_REGISTRY[] = {
|
||||
ledger2_accounts::BANK_ACCOUNT, ledger2_accounts::SHARE_FUND,
|
||||
"Внесение пайщиком паевого взноса" },
|
||||
|
||||
// 4. Возврат паевого взноса: Dr 80 / Cr 51, BURN_BLOCKED SHARE_FUND_PAY.
|
||||
// Сжигание из заблокированной суммы пайщика (BLOCK был на REQUEST_WITHDRAW):
|
||||
// 4. Возврат паевого взноса: Dr 80 / Cr 51, BURN WITHDRAW_PENDING.
|
||||
// Сжигание из кошелька-резерва (TRANSFER в резерв был на REQUEST_WITHDRAW):
|
||||
// деньги уходят из системы (банковский перевод пайщику), получателя на цепи нет.
|
||||
// Бухгалтерия: паевой фонд уменьшается (Дт 80), расчётный счёт уменьшается (Кт 51).
|
||||
{ operations::wallet::COMPLETE_WITHDRAW, processes::wallet::WITHDRAW, WalletOp::BURN_BLOCKED,
|
||||
ledger2_wallets::SHARE_FUND_PAY, eosio::name{},
|
||||
{ operations::wallet::COMPLETE_WITHDRAW, processes::wallet::WITHDRAW, WalletOp::BURN,
|
||||
ledger2_wallets::WITHDRAW_PENDING, eosio::name{},
|
||||
ledger2_accounts::SHARE_FUND, ledger2_accounts::BANK_ACCOUNT,
|
||||
"Возврат паевого взноса пайщику" },
|
||||
|
||||
@@ -328,17 +337,19 @@ static constexpr OperationRegistryEntry OPERATION_REGISTRY[] = {
|
||||
ledger2_accounts::SHARE_FUND, ledger2_accounts::TARGET_RECEIPTS,
|
||||
"Трансляция паевого взноса из ЦПП «Цифровой Кошелёк» в членский взнос за пользование инфраструктурой" },
|
||||
|
||||
// 15. Запрос на возврат паевого: BLOCK SHARE_FUND_PAY (без Dr/Cr — внутри одного бухсчёта 80).
|
||||
{ operations::wallet::REQUEST_WITHDRAW, processes::wallet::WITHDRAW, WalletOp::BLOCK,
|
||||
ledger2_wallets::SHARE_FUND_PAY, eosio::name{},
|
||||
// 15. Запрос на возврат паевого: TRANSFER SHARE_FUND_PAY → WITHDRAW_PENDING
|
||||
// (без Dr/Cr — оба кошелька на счёте 80; резерв средств на время рассмотрения).
|
||||
{ operations::wallet::REQUEST_WITHDRAW, processes::wallet::WITHDRAW, WalletOp::TRANSFER,
|
||||
ledger2_wallets::SHARE_FUND_PAY, ledger2_wallets::WITHDRAW_PENDING,
|
||||
0, 0,
|
||||
"Блокировка паевого под запрос на возврат" },
|
||||
"Резервирование паевого под запрос на возврат" },
|
||||
|
||||
// 16. Отклонение запроса на возврат: UNBLOCK SHARE_FUND_PAY (без Dr/Cr — зеркало REQUEST_WITHDRAW).
|
||||
{ operations::wallet::DECLINE_WITHDRAW, processes::wallet::WITHDRAW, WalletOp::UNBLOCK,
|
||||
ledger2_wallets::SHARE_FUND_PAY, eosio::name{},
|
||||
// 16. Отклонение запроса на возврат: TRANSFER WITHDRAW_PENDING → SHARE_FUND_PAY
|
||||
// (без Dr/Cr — зеркало REQUEST_WITHDRAW; возврат резерва пайщику).
|
||||
{ operations::wallet::DECLINE_WITHDRAW, processes::wallet::WITHDRAW, WalletOp::TRANSFER,
|
||||
ledger2_wallets::WITHDRAW_PENDING, ledger2_wallets::SHARE_FUND_PAY,
|
||||
0, 0,
|
||||
"Разблокировка паевого после отклонения запроса на возврат" },
|
||||
"Снятие резерва паевого после отклонения запроса на возврат" },
|
||||
|
||||
// 17. Возврат из ЦПП «Благорост» в Цифровой Кошелёк: TRANSFER BLAGOROST_FUND → SHARE_FUND_PAY (без Dr/Cr — оба счёта 80, зеркало INVEST).
|
||||
{ operations::capital::WITHDRAW_FROM_CAPITAL, processes::capital::WTHCAP, WalletOp::TRANSFER,
|
||||
@@ -395,7 +406,7 @@ static constexpr size_t OPERATION_REGISTRY_SIZE = sizeof(OPERATION_REGISTRY) / s
|
||||
// оба существуют в `LEDGER2_ACCOUNT_MAP`.
|
||||
// 4. Для TRANSFER: `wallet_from` ≠ `wallet_to`, оба ≠ 0.
|
||||
// 5. Для ISSUE: `wallet_from` == 0 и `wallet_to` ≠ 0.
|
||||
// 6. Для BLOCK / UNBLOCK / BURN / BURN_BLOCKED: `wallet_from` ≠ 0, `wallet_to` == 0.
|
||||
// 6. Для BURN: `wallet_from` ≠ 0, `wallet_to` == 0.
|
||||
// 7. Все id кошельков из записей существуют в `LEDGER2_WALLET_REGISTRY`.
|
||||
namespace ledger2_registry_detail {
|
||||
constexpr bool operation_codes_unique() {
|
||||
@@ -439,11 +450,11 @@ namespace ledger2_registry_detail {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Правило 6: BURN / BURN_BLOCKED — wallet_from required, wallet_to == 0 (ADR-003).
|
||||
// Правило 6: BURN — wallet_from required, wallet_to == 0 (ADR-003).
|
||||
constexpr bool burn_pattern_correct() {
|
||||
for (size_t i = 0; i < OPERATION_REGISTRY_SIZE; ++i) {
|
||||
const auto& e = OPERATION_REGISTRY[i];
|
||||
if (e.wallet_op != WalletOp::BURN && e.wallet_op != WalletOp::BURN_BLOCKED) continue;
|
||||
if (e.wallet_op != WalletOp::BURN) continue;
|
||||
if (e.wallet_from.value == 0) return false;
|
||||
if (e.wallet_to.value != 0) return false;
|
||||
}
|
||||
@@ -495,7 +506,7 @@ static_assert(ledger2_registry_detail::dr_ne_cr_when_posting(),
|
||||
static_assert(ledger2_registry_detail::transfer_wallet_from_ne_to(),
|
||||
"OPERATION_REGISTRY: TRANSFER с wallet_from == wallet_to или одним из них == 0");
|
||||
static_assert(ledger2_registry_detail::burn_pattern_correct(),
|
||||
"OPERATION_REGISTRY: BURN / BURN_BLOCKED требует wallet_from ≠ 0 и wallet_to == 0");
|
||||
"OPERATION_REGISTRY: BURN требует wallet_from ≠ 0 и wallet_to == 0");
|
||||
static_assert(ledger2_registry_detail::none_pattern_correct(),
|
||||
"OPERATION_REGISTRY: NONE требует wallet_from == 0, wallet_to == 0 и обе проводки заполненными");
|
||||
static_assert(ledger2_registry_detail::accounts_exist_in_map(),
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
* w.mkt.* — Маркетплейс (выплаты поставщикам)
|
||||
*
|
||||
* Sentinel `eosio::name{}` (пустое имя, value=0) — «кошелёк вне системы»
|
||||
* для ISSUE (нет wallet_from) и для BURN/BLOCK/UNBLOCK (нет wallet_to).
|
||||
* для ISSUE (нет wallet_from) и для BURN (нет wallet_to).
|
||||
*
|
||||
* При первом ISSUE/TRANSFER кошелёк создаётся автоматически по записи
|
||||
* из WALLET_REGISTRY. При обнулении available+blocked запись удаляется.
|
||||
@@ -46,7 +46,8 @@ struct ledger2_wallets {
|
||||
// wallet — паевой фонд + возвраты + ЦК
|
||||
static constexpr eosio::name SHARE_FUND_PAY = "w.wal.share"_n; ///< Паевой взнос пайщика (USER_SHARED)
|
||||
static constexpr eosio::name CK_MEMBER = "w.wal.member"_n; ///< ЦК — членская часть пайщика (USER_SHARED)
|
||||
static constexpr eosio::name WITHDRAWALS_SINK = "w.wal.wthdrw"_n; ///< DEPRECATED 2026-05-21: после переключения o.wal.wthcpl с TRANSFER на BURN_BLOCKED кошелёк больше не получает новых средств. Оставлен в реестре для исторических L2-балансов (накопленные возвраты до перехода). Не использовать в новых операциях.
|
||||
static constexpr eosio::name WITHDRAWALS_SINK = "w.wal.wthdrw"_n; ///< DEPRECATED 2026-05-21: исторический sink возвратов. Оставлен в реестре для исторических L2-балансов (накопленные возвраты до перехода). Не использовать в новых операциях.
|
||||
static constexpr eosio::name WITHDRAW_PENDING = "w.wal.wpend"_n; ///< Резерв паевого под заявку на возврат (COOPERATIVE-пул). o.wal.wthreq переводит сюда с w.wal.share, o.wal.wthdec возвращает обратно, o.wal.wthcpl сжигает отсюда. Заменил механику blocked/BLOCK/UNBLOCK 2026-05-24.
|
||||
|
||||
// registrator — минимальный паевой + вступительные
|
||||
static constexpr eosio::name MIN_SHARE_FUND = "w.reg.minshr"_n; ///< Минимальный паевой взнос пайщика (USER_SHARED, без сверки соглашений)
|
||||
@@ -95,7 +96,7 @@ struct Ledger2WalletMeta {
|
||||
WalletKind kind;
|
||||
};
|
||||
|
||||
inline constexpr std::array<Ledger2WalletMeta, 15> LEDGER2_WALLET_REGISTRY = {{
|
||||
inline constexpr std::array<Ledger2WalletMeta, 16> LEDGER2_WALLET_REGISTRY = {{
|
||||
// USER_SHARED (6) — L3-разрез по пайщику
|
||||
{ ledger2_wallets::MIN_SHARE_FUND, "Минимальный паевой взнос", WalletKind::USER_SHARED },
|
||||
{ ledger2_wallets::SHARE_FUND_PAY, "Паевой взнос пайщика", WalletKind::USER_SHARED },
|
||||
@@ -104,7 +105,7 @@ inline constexpr std::array<Ledger2WalletMeta, 15> LEDGER2_WALLET_REGISTRY = {{
|
||||
{ ledger2_wallets::PREIMP_FUND, "Первичный учёт РИД-взносов до перехода на электронный учёт", WalletKind::USER_SHARED },
|
||||
{ ledger2_wallets::MARKETPLACE_ORDER_LOCK,"ЦПП «Стол Заказов» — резерв под заказ у пайщика", WalletKind::USER_SHARED },
|
||||
|
||||
// COOPERATIVE (9) — единый кооперативный баланс, без L3
|
||||
// COOPERATIVE (10) — единый кооперативный баланс, без L3
|
||||
// GENERATOR_FUND переведён сюда из USER_SHARED (см. wallets.hpp:64) —
|
||||
// CRPS-распределение между сегментами проекта не поддерживает per-user
|
||||
// компенсирующие TRANSFER на approvecmmt, поэтому L3-проверка walletop
|
||||
@@ -112,6 +113,7 @@ inline constexpr std::array<Ledger2WalletMeta, 15> LEDGER2_WALLET_REGISTRY = {{
|
||||
{ ledger2_wallets::GENERATOR_FUND, "ЦПП «Генератор» — единый кошелёк программы", WalletKind::COOPERATIVE },
|
||||
{ ledger2_wallets::ENTRANCE_FEES, "Вступительные взносы", WalletKind::COOPERATIVE },
|
||||
{ ledger2_wallets::WITHDRAWALS_SINK, "Возвраты паевых взносов пайщикам (deprecated, не используется в новых операциях)", WalletKind::COOPERATIVE },
|
||||
{ ledger2_wallets::WITHDRAW_PENDING, "Резерв паевого под заявку на возврат", WalletKind::COOPERATIVE },
|
||||
{ ledger2_wallets::INFRA_FEES, "Членские взносы за инфраструктуру кооп. платформы", WalletKind::COOPERATIVE },
|
||||
{ ledger2_wallets::DELEGATE_FEES, "Делегатские членские взносы", WalletKind::COOPERATIVE },
|
||||
{ ledger2_wallets::SOV_EXPENSES, "Хозяйственные расходы из числа целевого финансирования", WalletKind::COOPERATIVE },
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
# программу «Цифровой Кошелёк» (SHARE_FUND_PAY).
|
||||
#
|
||||
# Четырёхактовый процесс с одной авторизацией советом. Запись заявки живёт в
|
||||
# таблице `withdraws` от создания до выплаты или отказа. На закрывающем
|
||||
# действии completewthd срабатывает одна операция o.wal.wthcpl — сжигание
|
||||
# заблокированной суммы (BURN_BLOCKED w.wal.share): деньги уходят из системы
|
||||
# пайщику банковским переводом, получателя на цепи нет.
|
||||
# таблице `withdraws` от создания до выплаты или отказа. На подаче заявки сумма
|
||||
# переводится в кошелёк-резерв (WITHDRAW_PENDING, w.wal.wpend). На закрывающем
|
||||
# действии completewthd срабатывает операция o.wal.wthcpl — сжигание суммы из
|
||||
# резерва (BURN w.wal.wpend): деньги уходят из системы пайщику банковским
|
||||
# переводом, получателя на цепи нет.
|
||||
#
|
||||
# Источники правды в коде:
|
||||
# • cpp/wallet/wallet.hpp — actions
|
||||
@@ -43,8 +44,8 @@ actions:
|
||||
actor: contributor
|
||||
role: opener
|
||||
purpose: >
|
||||
Пайщик создаёт заявку на возврат паевого взноса. Контракт блокирует
|
||||
сумму на кошельке SHARE_FUND_PAY (статус available → blocked) и
|
||||
Пайщик создаёт заявку на возврат паевого взноса. Контракт переводит
|
||||
сумму с кошелька SHARE_FUND_PAY в кошелёк-резерв WITHDRAW_PENDING и
|
||||
создаёт повестку в совете о возврате с callback'ом авторизации.
|
||||
- name: wallet::authwthd
|
||||
human: Авторизовать выплату
|
||||
@@ -60,16 +61,17 @@ actions:
|
||||
role: closer
|
||||
purpose: >
|
||||
Gateway подтвердил списание средств в пользу пайщика. Это закрывающее
|
||||
действие: применяется операция o.wal.wthcpl (BURN_BLOCKED w.wal.share)
|
||||
— заблокированная сумма сжигается, в бухгалтерии проходит обратная
|
||||
действие: применяется операция o.wal.wthcpl (BURN w.wal.wpend)
|
||||
— сумма из резерва сжигается, в бухгалтерии проходит обратная
|
||||
проводка Дт 80 / Кт 51. Запись заявки удаляется.
|
||||
- name: wallet::declinewthd
|
||||
human: Отклонить
|
||||
actor: soviet
|
||||
role: reject
|
||||
purpose: >
|
||||
Отказ на любом из этапов до выплаты. Сумма разблокируется на кошельке
|
||||
пайщика (UNBLOCK через o.wal.wthdec), запись заявки удаляется.
|
||||
Отказ на любом из этапов до выплаты. Сумма возвращается из резерва на
|
||||
кошелёк пайщика (o.wal.wthdec: WITHDRAW_PENDING → SHARE_FUND_PAY),
|
||||
запись заявки удаляется.
|
||||
|
||||
# ── Секция 3. Граф состояний ────────────────────────────────────────────────
|
||||
entity: wallet::withdraw
|
||||
@@ -80,8 +82,8 @@ states:
|
||||
- name: pending
|
||||
human: Ожидает решения совета
|
||||
description: >
|
||||
Заявка создана, сумма заблокирована на кошельке пайщика (available → blocked),
|
||||
повестка отправлена в совет.
|
||||
Заявка создана, сумма переведена в кошелёк-резерв (SHARE_FUND_PAY →
|
||||
WITHDRAW_PENDING), повестка отправлена в совет.
|
||||
kind: normal
|
||||
- name: authorized
|
||||
human: Выплата отправлена
|
||||
@@ -92,15 +94,15 @@ states:
|
||||
- name: completed
|
||||
human: Возврат выплачен
|
||||
description: >
|
||||
Gateway подтвердил списание, заблокированная сумма сжигается на кошельке
|
||||
пайщика (BURN_BLOCKED w.wal.share). В бухгалтерии прошла обратная
|
||||
Gateway подтвердил списание, сумма из резерва сжигается
|
||||
(BURN w.wal.wpend). В бухгалтерии прошла обратная
|
||||
проводка Дт 80 / Кт 51. Запись заявки удалена.
|
||||
kind: final
|
||||
- name: removed
|
||||
human: Отклонено
|
||||
description: >
|
||||
Возврат не состоялся: сумма разблокирована (UNBLOCK через o.wal.wthdec),
|
||||
запись удалена.
|
||||
Возврат не состоялся: сумма возвращена из резерва на кошелёк пайщика
|
||||
(o.wal.wthdec: WITHDRAW_PENDING → SHARE_FUND_PAY), запись удалена.
|
||||
kind: virtual
|
||||
virtual: true
|
||||
|
||||
@@ -172,7 +174,7 @@ scenario:
|
||||
- Сумма возврата ≤ доступного остатка.
|
||||
post:
|
||||
- В таблице withdraws создана запись со статусом `pending`.
|
||||
- На кошельке пайщика сумма переведена available → blocked.
|
||||
- Сумма переведена с SHARE_FUND_PAY в кошелёк-резерв WITHDRAW_PENDING.
|
||||
- Совет получил повестку о возврате.
|
||||
|
||||
- step: 2
|
||||
@@ -197,13 +199,13 @@ scenario:
|
||||
action: wallet::completewthd
|
||||
description: >
|
||||
Gateway подтверждает списание средств в пользу пайщика. Контракт
|
||||
применяет ledger2-операцию o.wal.wthcpl (BURN_BLOCKED w.wal.share с
|
||||
применяет ledger2-операцию o.wal.wthcpl (BURN w.wal.wpend с
|
||||
обратной проводкой Дт 80 / Кт 51) и удаляет запись заявки.
|
||||
pre:
|
||||
- Заявка в статусе `authorized`.
|
||||
- Gateway подтвердил списание.
|
||||
post:
|
||||
- Заблокированная сумма сожжена с SHARE_FUND_PAY (w.wal.share).
|
||||
- Сумма из резерва сожжена (WITHDRAW_PENDING, w.wal.wpend).
|
||||
- В ledger2 применена операция o.wal.wthcpl.
|
||||
- Запись заявки удалена.
|
||||
|
||||
@@ -214,8 +216,8 @@ scenario:
|
||||
actor: soviet
|
||||
description: >
|
||||
Совет принял отрицательное решение либо Gateway отклонил платёж.
|
||||
Сумма разблокирована на кошельке пайщика (UNBLOCK через o.wal.wthdec),
|
||||
запись удалена.
|
||||
Сумма возвращена из резерва на кошелёк пайщика (o.wal.wthdec:
|
||||
WITHDRAW_PENDING → SHARE_FUND_PAY), запись удалена.
|
||||
|
||||
# ── Секция 5. Документы и подписи ───────────────────────────────────────────
|
||||
documents:
|
||||
@@ -234,45 +236,45 @@ documents:
|
||||
# ── Секция 6. Операции ──────────────────────────────────────────────────────
|
||||
operations:
|
||||
- ledger_code: o.wal.wthreq
|
||||
human_name: Блокировка паевого под запрос на возврат
|
||||
wallet_op: BLOCK
|
||||
human_name: Резервирование паевого под запрос на возврат
|
||||
wallet_op: TRANSFER
|
||||
wallet_from: w.wal.share
|
||||
wallet_to: ~
|
||||
wallet_to: w.wal.wpend
|
||||
debit: ~
|
||||
credit: ~
|
||||
amount_ref: withdraw.quantity
|
||||
triggered_by: wallet::createwthd
|
||||
description: >
|
||||
На создании заявки контракт блокирует сумму на кошельке пайщика
|
||||
(SHARE_FUND_PAY): available → blocked. Бухгалтерская проводка не
|
||||
создаётся — движение внутри одного бухсчёта 80.
|
||||
На создании заявки контракт переводит сумму с кошелька пайщика
|
||||
(SHARE_FUND_PAY) в кошелёк-резерв (WITHDRAW_PENDING). Бухгалтерская
|
||||
проводка не создаётся — движение внутри одного бухсчёта 80.
|
||||
|
||||
- ledger_code: o.wal.wthdec
|
||||
human_name: Разблокировка паевого после отклонения запроса на возврат
|
||||
wallet_op: UNBLOCK
|
||||
wallet_from: w.wal.share
|
||||
wallet_to: ~
|
||||
human_name: Снятие резерва паевого после отклонения запроса на возврат
|
||||
wallet_op: TRANSFER
|
||||
wallet_from: w.wal.wpend
|
||||
wallet_to: w.wal.share
|
||||
debit: ~
|
||||
credit: ~
|
||||
amount_ref: withdraw.quantity
|
||||
triggered_by: wallet::declinewthd
|
||||
description: >
|
||||
Зеркало REQUEST_WITHDRAW: blocked → available на кошельке пайщика.
|
||||
Бухгалтерская проводка не создаётся.
|
||||
Зеркало REQUEST_WITHDRAW: сумма возвращается из резерва (WITHDRAW_PENDING)
|
||||
на кошелёк пайщика (SHARE_FUND_PAY). Бухгалтерская проводка не создаётся.
|
||||
|
||||
- ledger_code: o.wal.wthcpl
|
||||
human_name: Возврат паевого взноса пайщику
|
||||
wallet_op: BURN_BLOCKED
|
||||
wallet_from: w.wal.share # ЦПП «Цифровой Кошелёк» — паевые взносы деньгами
|
||||
wallet_op: BURN
|
||||
wallet_from: w.wal.wpend # кошелёк-резерв возвратов
|
||||
wallet_to: ~ # деньги уходят из системы, получателя на цепи нет
|
||||
debit: 80 # Паевой фонд (складочный капитал)
|
||||
credit: 51 # Расчётный счёт
|
||||
amount_ref: withdraw.quantity
|
||||
triggered_by: wallet::completewthd
|
||||
description: >
|
||||
Обратная операция к o.wal.depcpl: заблокированная сумма сжигается с
|
||||
кошелька пайщика «ЦПП Цифровой Кошелёк» (w.wal.share) — деньги уходят
|
||||
из системы банковским переводом пайщику. На цепи получателя нет.
|
||||
Обратная операция к o.wal.depcpl: зарезервированная сумма сжигается с
|
||||
кошелька-резерва (w.wal.wpend) — деньги уходят из системы банковским
|
||||
переводом пайщику. На цепи получателя нет.
|
||||
В бухгалтерии прошла проводка Дт 80 / Кт 51 — паевой фонд уменьшился,
|
||||
деньги ушли с расчётного счёта пайщику.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@coopenomics/contracts",
|
||||
"version": "2026.5.23-4",
|
||||
"version": "2026.5.27-2",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "create-nodejs-express-app",
|
||||
"version": "2026.5.23-4",
|
||||
"version": "2026.5.27-2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "create-nodejs-express-app",
|
||||
"version": "2026.5.23-4",
|
||||
"version": "2026.5.27-2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@a2seven/yoo-checkout": "^1.1.4",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@coopenomics/controller",
|
||||
"version": "2026.5.23-4",
|
||||
"version": "2026.5.27-2",
|
||||
"description": "Бэкенд GraphQL API кооператива на NestJS",
|
||||
"private": true,
|
||||
"bin": "bin/createNodejsApp.js",
|
||||
@@ -193,12 +193,12 @@
|
||||
"@antfu/eslint-config": "^2.24.1",
|
||||
"@graphql-codegen/cli": "^5.0.3",
|
||||
"@graphql-codegen/typescript": "^4.1.1",
|
||||
"@swc-node/register": "^1.11.1",
|
||||
"@swc/core": "^1.15.33",
|
||||
"@graphql-codegen/typescript-apollo-client-helpers": "^3.0.0",
|
||||
"@graphql-codegen/typescript-operations": "^4.3.1",
|
||||
"@nestjs/testing": "^11.1.19",
|
||||
"@redocly/cli": "^1.18.0",
|
||||
"@swc-node/register": "^1.11.1",
|
||||
"@swc/core": "^1.15.33",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^20.11.26",
|
||||
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
||||
|
||||
@@ -1435,7 +1435,6 @@ type CallTranscription {
|
||||
createdAt: DateTime!
|
||||
endedAt: DateTime
|
||||
id: String!
|
||||
matrixRoomId: String!
|
||||
|
||||
"""Пользовательская заметка о содержании звонка"""
|
||||
memo: String!
|
||||
@@ -1444,6 +1443,8 @@ type CallTranscription {
|
||||
Отображаемые имена участников (Synapse displayname); в БД хранятся канонические Matrix user id
|
||||
"""
|
||||
participants: [String!]!
|
||||
|
||||
"""Внутреннее имя комнаты звонка (LiveKit room name), не Matrix room id"""
|
||||
roomId: String!
|
||||
roomName: String!
|
||||
startedAt: DateTime!
|
||||
@@ -3554,6 +3555,17 @@ type ChatCoopCalendarRoomOption {
|
||||
matrixRoomId: String!
|
||||
}
|
||||
|
||||
type ChatcoopNonProjectCommunicationRoom {
|
||||
"""Подпись для отображения комнаты"""
|
||||
displayLabel: String!
|
||||
|
||||
"""Тип комнаты (пайщики / совет / секретарь)"""
|
||||
kind: NonProjectRoomKind!
|
||||
|
||||
"""Идентификатор комнаты Matrix"""
|
||||
matrixRoomId: String!
|
||||
}
|
||||
|
||||
type ChatcoopProjectCommunicationRoom {
|
||||
"""Подпись для отображения (комната / проект Capital)"""
|
||||
displayLabel: String!
|
||||
@@ -3577,6 +3589,28 @@ type ChatcoopRoomMessageLine {
|
||||
originServerTs: Float!
|
||||
}
|
||||
|
||||
type ChatcoopSecretaryRoom {
|
||||
"""Название комнаты"""
|
||||
displayLabel: String!
|
||||
|
||||
"""Можно ли удалить комнату из интерфейса (только комнаты секретаря)"""
|
||||
editable: Boolean!
|
||||
|
||||
"""Комната зашифрована (E2EE) — секретарь не транскрибирует такие комнаты"""
|
||||
encrypted: Boolean!
|
||||
|
||||
"""
|
||||
Внутренний идентификатор комнаты в реестре (для операций; это НЕ Matrix room id)
|
||||
"""
|
||||
id: String!
|
||||
|
||||
"""Тип комнаты"""
|
||||
kind: ManagedRoomKind!
|
||||
|
||||
"""Секретарь присутствует в комнате"""
|
||||
secretaryInRoom: Boolean!
|
||||
}
|
||||
|
||||
input CheckMatrixUsernameInput {
|
||||
username: String!
|
||||
}
|
||||
@@ -4635,6 +4669,16 @@ input CreateRequestInput {
|
||||
width: Int
|
||||
}
|
||||
|
||||
input CreateSecretaryRoomInput {
|
||||
"""Название комнаты"""
|
||||
displayName: String!
|
||||
|
||||
"""
|
||||
Публичная комната (любой может войти) либо приватная (вход по приглашению создателя)
|
||||
"""
|
||||
isPublic: Boolean!
|
||||
}
|
||||
|
||||
input CreateSovietIndividualDataInput {
|
||||
"""Дата рождения"""
|
||||
birthdate: String!
|
||||
@@ -6717,9 +6761,6 @@ type Ledger2Wallet {
|
||||
"""Доступный баланс"""
|
||||
available: String!
|
||||
|
||||
"""Заблокированный баланс"""
|
||||
blocked: String!
|
||||
|
||||
"""eosio::name-идентификатор кошелька (w.<contract>.<waltype>)"""
|
||||
id: String!
|
||||
|
||||
@@ -6899,6 +6940,14 @@ input MakeClearanceInput {
|
||||
username: String!
|
||||
}
|
||||
|
||||
"""Тип комнаты: пайщики, совет, проект Capital, комната секретаря"""
|
||||
enum ManagedRoomKind {
|
||||
CAPITAL_PROJECT
|
||||
COUNCIL
|
||||
MEMBERS
|
||||
SECRETARY
|
||||
}
|
||||
|
||||
input MarkReportPeriodInput {
|
||||
mark: ReportSubmissionMark
|
||||
period: Int
|
||||
@@ -8082,6 +8131,19 @@ input MarketplaceListOrdersInput {
|
||||
supplier_account: String
|
||||
}
|
||||
|
||||
input MarketplaceListOutgoingPaymentsAsSupplierFilterInput {
|
||||
"""Фильтр по статусам выплат. Пусто — показывать все статусы."""
|
||||
statuses: [MarketplaceOutgoingPaymentRequestStatus!]
|
||||
}
|
||||
|
||||
input MarketplaceListOutgoingPaymentsFilterInput {
|
||||
"""Фильтр по статусам выплат. Пусто — показывать все статусы."""
|
||||
statuses: [MarketplaceOutgoingPaymentRequestStatus!]
|
||||
|
||||
"""Поставщик-получатель выплаты. Пусто — по всем поставщикам."""
|
||||
supplier_account: String
|
||||
}
|
||||
|
||||
input MarketplaceListPendingOffersInput {
|
||||
"""Количество элементов на странице"""
|
||||
limit: Int! = 10
|
||||
@@ -8129,7 +8191,7 @@ type MarketplaceMemberWallet {
|
||||
username: String!
|
||||
|
||||
"""
|
||||
Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.member
|
||||
Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.order
|
||||
"""
|
||||
wallets: [MarketplaceWalletEntry!]!
|
||||
}
|
||||
@@ -8338,20 +8400,14 @@ type MarketplaceOrder {
|
||||
}
|
||||
|
||||
"""
|
||||
Снимок транзакции блокировки средств: ссылки на блок, флаги конверсии и сумма.
|
||||
Снимок транзакции резервирования средств: ссылки на блок и сумма резерва.
|
||||
"""
|
||||
type MarketplaceOrderCreateTxSnapshot {
|
||||
"""Номер блока, в который попала транзакция."""
|
||||
block_num: Int!
|
||||
|
||||
"""Сумма заблокированных средств (строка денежного актива)."""
|
||||
blocked_amount: String!
|
||||
|
||||
"""Был ли выполнен перевод средств между кошельками пайщика."""
|
||||
did_assign: Boolean!
|
||||
|
||||
"""Была ли выполнена конверсия паевого взноса в членский."""
|
||||
did_convert: Boolean!
|
||||
"""Сумма зарезервированных средств (строка денежного актива)."""
|
||||
locked_amount: String!
|
||||
|
||||
"""Время подписания заказа (ISO 8601)."""
|
||||
signed_at: String!
|
||||
@@ -9246,7 +9302,9 @@ type MarketplaceWalletEntry {
|
||||
"""Доступный остаток (`userwallets.available`)"""
|
||||
available: String!
|
||||
|
||||
"""Заблокированный остаток (`userwallets.blocked`)"""
|
||||
"""
|
||||
Заблокированный остаток (`userwallets.blocked`). Для marketplace-кошельков всегда `0` — резерв выражается через `.available` кошелька w.mkt.order. Поле остаётся для wallet/withdraw flow и legacy данных.
|
||||
"""
|
||||
blocked: String!
|
||||
|
||||
"""Человекочитаемое название (из cooptypes LEDGER2_WALLET_REGISTRY)"""
|
||||
@@ -9260,7 +9318,7 @@ type MarketplaceWalletEntry {
|
||||
"""
|
||||
label: String!
|
||||
|
||||
"""eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.member)"""
|
||||
"""eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.order)"""
|
||||
name: String!
|
||||
|
||||
"""program_id: 1 — Цифровой Кошелёк, 2 — Стол Заказов"""
|
||||
@@ -10248,6 +10306,13 @@ type Mutation {
|
||||
"""
|
||||
chatcoopCreateCalendarIcsSubscription: ChatCoopCalendarIcsUrlResponse!
|
||||
|
||||
"""
|
||||
Создать комнату с секретарём (публичную или приватную); секретарь подключается сразу
|
||||
|
||||
Требуемые роли: chairman, member.
|
||||
"""
|
||||
chatcoopCreateSecretaryRoom(data: CreateSecretaryRoomInput!): ChatcoopSecretaryRoom!
|
||||
|
||||
"""
|
||||
Удалить событие календаря
|
||||
|
||||
@@ -10255,6 +10320,13 @@ type Mutation {
|
||||
"""
|
||||
chatcoopDeleteCalendarEvent(id: String!): Boolean!
|
||||
|
||||
"""
|
||||
Удалить комнату секретаря: вывести секретаря и снять комнату с синхронизации (возвращает идентификатор комнаты в реестре)
|
||||
|
||||
Требуемые роли: chairman, member.
|
||||
"""
|
||||
chatcoopRemoveSecretaryRoom(data: RemoveSecretaryRoomInput!): String!
|
||||
|
||||
"""
|
||||
Обновить событие календаря
|
||||
|
||||
@@ -10984,6 +11056,13 @@ type Mutation {
|
||||
walmoveWallets(input: WalmoveInput!): Ledger2AdjustmentResult!
|
||||
}
|
||||
|
||||
"""Тип комнаты вне проекта: пайщики, совет, комната секретаря"""
|
||||
enum NonProjectRoomKind {
|
||||
COUNCIL
|
||||
MEMBERS
|
||||
SECRETARY
|
||||
}
|
||||
|
||||
input NotificationWorkflowRecipientInput {
|
||||
"""Username получателя"""
|
||||
username: String!
|
||||
@@ -12222,9 +12301,6 @@ type ProgramWallet {
|
||||
"""Номер блока последнего обновления"""
|
||||
blockNum: Float
|
||||
|
||||
"""Заблокированный баланс (формат: "100.0000 RUB")"""
|
||||
blocked: String!
|
||||
|
||||
"""Имя кооператива"""
|
||||
coopname: String!
|
||||
|
||||
@@ -12734,6 +12810,13 @@ type Query {
|
||||
"""
|
||||
chatcoopListCalendarRooms: [ChatCoopCalendarRoomOption!]!
|
||||
|
||||
"""
|
||||
Комнаты Matrix вне проектов Capital (пайщики/совет/секретарь) — для синхронизации в blago
|
||||
|
||||
Требуемые роли: chairman, member, user.
|
||||
"""
|
||||
chatcoopListNonProjectCommunicationRooms: [ChatcoopNonProjectCommunicationRoom!]!
|
||||
|
||||
"""
|
||||
Комнаты Matrix, привязанные к проекту Capital (реестр ChatCoop)
|
||||
|
||||
@@ -12741,6 +12824,13 @@ type Query {
|
||||
"""
|
||||
chatcoopListProjectCommunicationRooms(data: GetProjectCommunicationRoomsInput!): [ChatcoopProjectCommunicationRoom!]!
|
||||
|
||||
"""
|
||||
Все комнаты реестра ChatCoop (системные/проектные — read-only, комнаты секретаря — удаляемые)
|
||||
|
||||
Требуемые роли: chairman, member.
|
||||
"""
|
||||
chatcoopListSecretaryRooms: [ChatcoopSecretaryRoom!]!
|
||||
|
||||
"""
|
||||
UTC-даты (YYYY-MM-DD), в которых есть сообщения новее afterOriginServerTsExclusive, для комнаты Matrix
|
||||
|
||||
@@ -13205,7 +13295,7 @@ type Query {
|
||||
marketplaceListConsolidatedRequests(input: MarketplaceListConsolidatedRequestsInput, options: PaginationInput): MarketplaceConsolidatedRequestPaginationResult!
|
||||
|
||||
"""
|
||||
Список наклеек инвентаря КУ: admin/совет видят весь склад кооператива, оператор — только свой участок.
|
||||
Список наклеек инвентаря КУ — для admin-стола склада и операторских разделов.
|
||||
"""
|
||||
marketplaceListInventory(data: MarketplaceListInventoryInput): [MarketplaceInventoryItem!]!
|
||||
|
||||
@@ -13241,12 +13331,12 @@ type Query {
|
||||
"""
|
||||
Лента выплат поставщикам по всему кооперативу — для совета. Опциональные фильтры: по поставщику-получателю и по статусам.
|
||||
"""
|
||||
marketplaceListOutgoingPayments(statuses: [MarketplaceOutgoingPaymentRequestStatus!], supplier_account: String): [MarketplaceOutgoingPaymentRequest!]!
|
||||
marketplaceListOutgoingPayments(filter: MarketplaceListOutgoingPaymentsFilterInput): [MarketplaceOutgoingPaymentRequest!]!
|
||||
|
||||
"""
|
||||
История выплат поставщику в столе поставщика — статусы по каждому заказу.
|
||||
"""
|
||||
marketplaceListOutgoingPaymentsAsSupplier(statuses: [MarketplaceOutgoingPaymentRequestStatus!]): [MarketplaceOutgoingPaymentRequest!]!
|
||||
marketplaceListOutgoingPaymentsAsSupplier(filter: MarketplaceListOutgoingPaymentsAsSupplierFilterInput): [MarketplaceOutgoingPaymentRequest!]!
|
||||
|
||||
"""Список Offer'ов на модерации (admin)"""
|
||||
marketplaceListPendingOffers(input: MarketplaceListPendingOffersInput): MarketplaceOfferPaginationResult!
|
||||
@@ -13278,7 +13368,7 @@ type Query {
|
||||
marketplaceListWriteoffProposals(data: MarketplaceListWriteoffProposalsInput!, options: PaginationInput): PaginatedMarketplaceWriteoffProposals!
|
||||
|
||||
"""
|
||||
Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.member
|
||||
Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.order
|
||||
"""
|
||||
marketplaceMemberWallet: MarketplaceMemberWallet!
|
||||
|
||||
@@ -13632,6 +13722,11 @@ input RemoveAvailableCategoryTypesInput {
|
||||
categoryTypes: [CategoryTypeInput!]!
|
||||
}
|
||||
|
||||
input RemoveSecretaryRoomInput {
|
||||
"""Идентификатор комнаты в реестре, которую нужно удалить"""
|
||||
id: String!
|
||||
}
|
||||
|
||||
input ReplaceAvailableItemsInput {
|
||||
"""ID категорий (целые категории)"""
|
||||
categoryIds: [Int!]! = []
|
||||
|
||||
@@ -17,7 +17,4 @@ export class Ledger2WalletDTO {
|
||||
|
||||
@Field(() => String, { description: 'Доступный баланс' })
|
||||
available!: string;
|
||||
|
||||
@Field(() => String, { description: 'Заблокированный баланс' })
|
||||
blocked!: string;
|
||||
}
|
||||
|
||||
@@ -28,9 +28,6 @@ export class ProgramWalletDTO {
|
||||
@Field(() => String, { description: 'Доступный баланс (формат: "100.0000 RUB")' })
|
||||
available!: string;
|
||||
|
||||
@Field(() => String, { description: 'Заблокированный баланс (формат: "100.0000 RUB")' })
|
||||
blocked!: string;
|
||||
|
||||
@Field(() => String, { description: 'Паевой взнос (формат: "100.0000 RUB")' })
|
||||
membership_contribution!: string;
|
||||
|
||||
@@ -49,7 +46,6 @@ export class ProgramWalletDTO {
|
||||
dto.agreement_id = entity.agreement_id;
|
||||
dto.username = entity.username;
|
||||
dto.available = entity.available;
|
||||
dto.blocked = entity.blocked;
|
||||
dto.membership_contribution = entity.membership_contribution;
|
||||
dto.blockNum = entity.blockNum;
|
||||
return dto;
|
||||
|
||||
@@ -26,22 +26,29 @@ const logger = winston.createLogger({
|
||||
})
|
||||
),
|
||||
transports: [
|
||||
new DailyRotateFile({
|
||||
filename: 'logs/application-%DATE%.log',
|
||||
datePattern: 'YYYY-MM-DD',
|
||||
zippedArchive: true,
|
||||
maxSize: '20m',
|
||||
maxFiles: '30d',
|
||||
level: 'info',
|
||||
}),
|
||||
new DailyRotateFile({
|
||||
filename: 'logs/error-%DATE%.log',
|
||||
datePattern: 'YYYY-MM-DD',
|
||||
zippedArchive: true,
|
||||
maxSize: '20m',
|
||||
maxFiles: '30d',
|
||||
level: 'error',
|
||||
}),
|
||||
// При генерации схемы (build-time codegen) файловые транспорты не подключаем:
|
||||
// процесс запускается из-под пользователя рядом с работающим контейнером, чей logs/
|
||||
// принадлежит root, — запись туда падает с EACCES. На рантайм не влияет (флаг ставится только в codegen).
|
||||
...(process.env.CONTROLLER_SCHEMA_GEN
|
||||
? []
|
||||
: [
|
||||
new DailyRotateFile({
|
||||
filename: 'logs/application-%DATE%.log',
|
||||
datePattern: 'YYYY-MM-DD',
|
||||
zippedArchive: true,
|
||||
maxSize: '20m',
|
||||
maxFiles: '30d',
|
||||
level: 'info',
|
||||
}),
|
||||
new DailyRotateFile({
|
||||
filename: 'logs/error-%DATE%.log',
|
||||
datePattern: 'YYYY-MM-DD',
|
||||
zippedArchive: true,
|
||||
maxSize: '20m',
|
||||
maxFiles: '30d',
|
||||
level: 'error',
|
||||
}),
|
||||
]),
|
||||
new winston.transports.Console({
|
||||
stderrLevels: ['error'],
|
||||
level: config.env === 'development' ? 'debug' : 'info',
|
||||
|
||||
+11
-3
@@ -256,12 +256,20 @@ export class DecisionExpiredNotificationService implements OnModuleInit, OnModul
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем дату последней проверки (только это поле, чтобы не перезаписать другие параметры конфига)
|
||||
// Обновляем дату последней проверки. ВАЖНО: read-modify-write по СВЕЖЕМУ
|
||||
// config из БД, а не по захваченному при initialize() снимку `plugin`.
|
||||
// Cron-замыкание держит in-memory снимок с момента boot; за время между
|
||||
// boot и тиком онбординг-флаги (`onboarding_*_done/_hash`) и прочие поля
|
||||
// могли быть записаны в БД другими сервисами. Перезапись устаревшего
|
||||
// снимка стёрла бы их (lost update). Поэтому берём актуальный config и
|
||||
// трогаем только lastCheckDate.
|
||||
const fresh = await this.extensionRepository.findByName('chairman');
|
||||
const baseConfig = fresh?.config ?? plugin.config;
|
||||
const updatedConfig = {
|
||||
...plugin.config,
|
||||
...baseConfig,
|
||||
lastCheckDate: new Date().toISOString(),
|
||||
};
|
||||
await this.extensionRepository.update({ ...plugin, config: updatedConfig });
|
||||
await this.extensionRepository.update({ name: 'chairman', config: updatedConfig });
|
||||
|
||||
this.logger.debug('Проверка истекших решений завершена');
|
||||
} catch (error) {
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Matrix: комната секретаря — создаётся вручную председателем/советом из desktop.
|
||||
* Всегда незашифрована (E2EE секретарь транскрибировать не может). Тип (public/private)
|
||||
* выбирается при создании, поэтому isPrivate здесь не фиксируем — задаёт сервис.
|
||||
*/
|
||||
|
||||
import type { MatrixChatRoomPreset } from './matrix-chat-room-preset.types';
|
||||
|
||||
/**
|
||||
* Те же права, что у комнаты проекта Capital: модераторы (50) почти по всем настройкам;
|
||||
* 100 только у Matrix-админа для критичных state.
|
||||
*/
|
||||
function buildPowerLevels(adminUserId: string): Record<string, unknown> {
|
||||
return {
|
||||
users_default: 0,
|
||||
invite: 50,
|
||||
kick: 50,
|
||||
ban: 50,
|
||||
redact: 50,
|
||||
state_default: 50,
|
||||
events_default: 0,
|
||||
users: {
|
||||
[adminUserId]: 100,
|
||||
},
|
||||
events: {
|
||||
'm.room.name': 50,
|
||||
'm.room.topic': 50,
|
||||
'm.room.avatar': 50,
|
||||
'm.room.pinned_events': 50,
|
||||
'm.room.canonical_alias': 50,
|
||||
'm.room.aliases': 50,
|
||||
'm.room.power_levels': 100,
|
||||
'm.room.history_visibility': 50,
|
||||
'm.room.encryption': 100,
|
||||
'm.room.tombstone': 100,
|
||||
'm.room.server_acl': 100,
|
||||
|
||||
'im.vector.modular.widgets': 50,
|
||||
'm.widget': 50,
|
||||
'org.matrix.msc2876.widgets': 50,
|
||||
'io.element.widgets.layout': 50,
|
||||
|
||||
'io.element.call': 50,
|
||||
'io.element.call.member': 0,
|
||||
'org.matrix.msc3401.call': 50,
|
||||
'org.matrix.msc3401.call.member': 0,
|
||||
'm.call': 50,
|
||||
'm.call.invite': 50,
|
||||
'm.call.answer': 0,
|
||||
'm.call.hangup': 0,
|
||||
'm.call.candidates': 0,
|
||||
'm.call.select_answer': 0,
|
||||
'm.call.reject': 0,
|
||||
'm.call.negotiate': 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Базовый пресет комнаты секретаря. `isPrivate` переопределяется сервисом по флагу публичности.
|
||||
* `encrypt` всегда false — иначе секретарь не сможет читать сообщения и транскрибировать.
|
||||
*/
|
||||
export const SECRETARY_ROOM_MATRIX: MatrixChatRoomPreset = {
|
||||
label: 'Комната секретаря',
|
||||
isPrivate: true,
|
||||
encrypt: false,
|
||||
roomType: undefined,
|
||||
initialState: [],
|
||||
buildPowerLevels,
|
||||
};
|
||||
+26
@@ -23,6 +23,32 @@ export class ChatcoopProjectCommunicationRoomDTO {
|
||||
displayLabel!: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Тип непроектной комнаты: пайщики, совет или комната секретаря.
|
||||
*/
|
||||
export enum NonProjectRoomKindGql {
|
||||
MEMBERS = 'MEMBERS',
|
||||
COUNCIL = 'COUNCIL',
|
||||
SECRETARY = 'SECRETARY',
|
||||
}
|
||||
|
||||
registerEnumType(NonProjectRoomKindGql, {
|
||||
name: 'NonProjectRoomKind',
|
||||
description: 'Тип комнаты вне проекта: пайщики, совет, комната секретаря',
|
||||
});
|
||||
|
||||
@ObjectType('ChatcoopNonProjectCommunicationRoom')
|
||||
export class ChatcoopNonProjectCommunicationRoomDTO {
|
||||
@Field({ description: 'Идентификатор комнаты Matrix' })
|
||||
matrixRoomId!: string;
|
||||
|
||||
@Field({ description: 'Подпись для отображения комнаты' })
|
||||
displayLabel!: string;
|
||||
|
||||
@Field(() => NonProjectRoomKindGql, { description: 'Тип комнаты (пайщики / совет / секретарь)' })
|
||||
kind!: NonProjectRoomKindGql;
|
||||
}
|
||||
|
||||
@ObjectType('ChatcoopRoomMessageLine')
|
||||
export class ChatcoopRoomMessageLineDTO {
|
||||
@Field(() => Float, { description: 'origin_server_ts из Matrix (мс)' })
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Field, InputType, ObjectType, registerEnumType } from '@nestjs/graphql';
|
||||
import { IsBoolean, IsNotEmpty, IsString, MaxLength } from 'class-validator';
|
||||
|
||||
/**
|
||||
* Тип комнаты в реестре ChatCoop. Редактировать (удалять) можно только `SECRETARY`;
|
||||
* системные (`MEMBERS`/`COUNCIL`) и проектные (`CAPITAL_PROJECT`) защищены.
|
||||
*/
|
||||
export enum ManagedRoomKindGql {
|
||||
MEMBERS = 'MEMBERS',
|
||||
COUNCIL = 'COUNCIL',
|
||||
CAPITAL_PROJECT = 'CAPITAL_PROJECT',
|
||||
SECRETARY = 'SECRETARY',
|
||||
}
|
||||
|
||||
registerEnumType(ManagedRoomKindGql, {
|
||||
name: 'ManagedRoomKind',
|
||||
description: 'Тип комнаты: пайщики, совет, проект Capital, комната секретаря',
|
||||
});
|
||||
|
||||
@ObjectType('ChatcoopSecretaryRoom')
|
||||
export class ChatcoopSecretaryRoomDTO {
|
||||
@Field({ description: 'Внутренний идентификатор комнаты в реестре (для операций; это НЕ Matrix room id)' })
|
||||
id!: string;
|
||||
|
||||
@Field({ description: 'Название комнаты' })
|
||||
displayLabel!: string;
|
||||
|
||||
@Field(() => ManagedRoomKindGql, { description: 'Тип комнаты' })
|
||||
kind!: ManagedRoomKindGql;
|
||||
|
||||
@Field({ description: 'Комната зашифрована (E2EE) — секретарь не транскрибирует такие комнаты' })
|
||||
encrypted!: boolean;
|
||||
|
||||
@Field({ description: 'Секретарь присутствует в комнате' })
|
||||
secretaryInRoom!: boolean;
|
||||
|
||||
@Field({ description: 'Можно ли удалить комнату из интерфейса (только комнаты секретаря)' })
|
||||
editable!: boolean;
|
||||
}
|
||||
|
||||
@InputType('CreateSecretaryRoomInput')
|
||||
export class CreateSecretaryRoomInputDTO {
|
||||
@Field({ description: 'Название комнаты' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MaxLength(240)
|
||||
displayName!: string;
|
||||
|
||||
@Field({ description: 'Публичная комната (любой может войти) либо приватная (вход по приглашению создателя)' })
|
||||
@IsBoolean()
|
||||
isPublic!: boolean;
|
||||
}
|
||||
|
||||
@InputType('RemoveSecretaryRoomInput')
|
||||
export class RemoveSecretaryRoomInputDTO {
|
||||
@Field({ description: 'Идентификатор комнаты в реестре, которую нужно удалить' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
id!: string;
|
||||
}
|
||||
@@ -39,12 +39,9 @@ export class CallTranscriptionResponseDTO {
|
||||
@Field()
|
||||
id!: string;
|
||||
|
||||
@Field()
|
||||
@Field({ description: 'Внутреннее имя комнаты звонка (LiveKit room name), не Matrix room id' })
|
||||
roomId!: string;
|
||||
|
||||
@Field()
|
||||
matrixRoomId!: string;
|
||||
|
||||
@Field()
|
||||
roomName!: string;
|
||||
|
||||
|
||||
+32
@@ -11,19 +11,33 @@ import { AuthRoles } from '~/application/auth/decorators/auth.decorator';
|
||||
import { CurrentUser } from '~/application/auth/decorators/current-user.decorator';
|
||||
import type { MonoAccountDomainInterface } from '~/domain/account/interfaces/mono-account-domain.interface';
|
||||
import {
|
||||
ChatcoopNonProjectCommunicationRoomDTO,
|
||||
ChatcoopProjectCommunicationRoomDTO,
|
||||
ChatcoopRoomMessageLineDTO,
|
||||
GetMaxOriginServerTsForRoomInputDTO,
|
||||
GetProjectCommunicationRoomsInputDTO,
|
||||
GetRoomMessagesForUtcDateInputDTO,
|
||||
ListUtcDatesWithNewRoomMessagesInputDTO,
|
||||
NonProjectRoomKindGql,
|
||||
RoomMessageKindGql,
|
||||
} from '../dto/project-communication.dto';
|
||||
import type { InterNonProjectRoomKind } from '@coopenomics/inter';
|
||||
|
||||
function mapKind(kind: 'text' | 'audio'): RoomMessageKindGql {
|
||||
return kind === 'text' ? RoomMessageKindGql.TEXT : RoomMessageKindGql.AUDIO;
|
||||
}
|
||||
|
||||
function mapNonProjectKind(kind: InterNonProjectRoomKind): NonProjectRoomKindGql {
|
||||
switch (kind) {
|
||||
case 'members':
|
||||
return NonProjectRoomKindGql.MEMBERS;
|
||||
case 'council':
|
||||
return NonProjectRoomKindGql.COUNCIL;
|
||||
case 'secretary':
|
||||
return NonProjectRoomKindGql.SECRETARY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Доступ к данным переписки Capital↔Matrix для синхронизации (blago-cli, секретарь).
|
||||
* Источник — порт `INTER_PROJECT_COMMUNICATION_ARTIFACTS` (пакет inter).
|
||||
@@ -61,6 +75,24 @@ export class ProjectCommunicationResolver {
|
||||
}));
|
||||
}
|
||||
|
||||
@Query(() => [ChatcoopNonProjectCommunicationRoomDTO], {
|
||||
name: 'chatcoopListNonProjectCommunicationRooms',
|
||||
description: 'Комнаты Matrix вне проектов Capital (пайщики/совет/секретарь) — для синхронизации в blago',
|
||||
})
|
||||
@AuthRoles(['chairman', 'member', 'user'])
|
||||
async listNonProjectCommunicationRooms(
|
||||
@CurrentUser() user: MonoAccountDomainInterface
|
||||
): Promise<ChatcoopNonProjectCommunicationRoomDTO[]> {
|
||||
this.ensureComm();
|
||||
this.logger.debug(`chatcoopListNonProjectCommunicationRooms user=${user.username}`);
|
||||
const rooms = await this.comm!.listNonProjectCommunicationRooms();
|
||||
return rooms.map((r) => ({
|
||||
matrixRoomId: r.matrixRoomId,
|
||||
displayLabel: r.displayLabel,
|
||||
kind: mapNonProjectKind(r.kind),
|
||||
}));
|
||||
}
|
||||
|
||||
@Query(() => [String], {
|
||||
name: 'chatcoopListUtcDatesWithNewRoomMessages',
|
||||
description:
|
||||
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
import { Logger, UseGuards } from '@nestjs/common';
|
||||
import { ActiveUserStatusGuard } from '~/application/auth/guards/active-user-status.guard';
|
||||
import { GqlJwtAuthGuard } from '~/application/auth/guards/graphql-jwt-auth.guard';
|
||||
import { RolesGuard } from '~/application/auth/guards/roles.guard';
|
||||
import { AuthRoles } from '~/application/auth/decorators/auth.decorator';
|
||||
import { CurrentUser } from '~/application/auth/decorators/current-user.decorator';
|
||||
import type { MonoAccountDomainInterface } from '~/domain/account/interfaces/mono-account-domain.interface';
|
||||
import { SecretaryRoomManagementService } from '../services/secretary-room-management.service';
|
||||
import {
|
||||
ChatcoopSecretaryRoomDTO,
|
||||
CreateSecretaryRoomInputDTO,
|
||||
ManagedRoomKindGql,
|
||||
RemoveSecretaryRoomInputDTO,
|
||||
} from '../dto/secretary-room.dto';
|
||||
import type { ChatcoopManagedMatrixRoomKind } from '../../domain/entities/managed-matrix-room.entity';
|
||||
import type { ManagedMatrixRoomDomainEntity } from '../../domain/entities/managed-matrix-room.entity';
|
||||
|
||||
function mapManagedKind(kind: ChatcoopManagedMatrixRoomKind): ManagedRoomKindGql {
|
||||
switch (kind) {
|
||||
case 'members':
|
||||
return ManagedRoomKindGql.MEMBERS;
|
||||
case 'council':
|
||||
return ManagedRoomKindGql.COUNCIL;
|
||||
case 'capital_project':
|
||||
return ManagedRoomKindGql.CAPITAL_PROJECT;
|
||||
case 'secretary':
|
||||
return ManagedRoomKindGql.SECRETARY;
|
||||
}
|
||||
}
|
||||
|
||||
function toDto(room: ManagedMatrixRoomDomainEntity): ChatcoopSecretaryRoomDTO {
|
||||
return {
|
||||
id: room.id,
|
||||
displayLabel: room.displayLabel || 'Без названия',
|
||||
kind: mapManagedKind(room.kind),
|
||||
encrypted: room.encrypted,
|
||||
secretaryInRoom: room.secretaryInRoom,
|
||||
editable: room.kind === 'secretary',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Управление комнатами секретаря (стол связи desktop).
|
||||
* Доступ — председатель и члены совета: они создают комнаты для звонков с секретарём и удаляют свои.
|
||||
*/
|
||||
@Resolver()
|
||||
@UseGuards(GqlJwtAuthGuard, RolesGuard, ActiveUserStatusGuard)
|
||||
export class SecretaryRoomsResolver {
|
||||
private readonly logger = new Logger(SecretaryRoomsResolver.name);
|
||||
|
||||
constructor(private readonly service: SecretaryRoomManagementService) {}
|
||||
|
||||
@Query(() => [ChatcoopSecretaryRoomDTO], {
|
||||
name: 'chatcoopListSecretaryRooms',
|
||||
description: 'Все комнаты реестра ChatCoop (системные/проектные — read-only, комнаты секретаря — удаляемые)',
|
||||
})
|
||||
@AuthRoles(['chairman', 'member'])
|
||||
async listSecretaryRooms(
|
||||
@CurrentUser() user: MonoAccountDomainInterface
|
||||
): Promise<ChatcoopSecretaryRoomDTO[]> {
|
||||
this.logger.debug(`chatcoopListSecretaryRooms user=${user.username}`);
|
||||
const rooms = await this.service.listRooms();
|
||||
return rooms.map(toDto);
|
||||
}
|
||||
|
||||
@Mutation(() => ChatcoopSecretaryRoomDTO, {
|
||||
name: 'chatcoopCreateSecretaryRoom',
|
||||
description: 'Создать комнату с секретарём (публичную или приватную); секретарь подключается сразу',
|
||||
})
|
||||
@AuthRoles(['chairman', 'member'])
|
||||
async createSecretaryRoom(
|
||||
@CurrentUser() user: MonoAccountDomainInterface,
|
||||
@Args('data', { type: () => CreateSecretaryRoomInputDTO }) data: CreateSecretaryRoomInputDTO
|
||||
): Promise<ChatcoopSecretaryRoomDTO> {
|
||||
this.logger.log(`chatcoopCreateSecretaryRoom user=${user.username} isPublic=${data.isPublic}`);
|
||||
const room = await this.service.createSecretaryRoom({
|
||||
creatorUsername: user.username,
|
||||
creatorRole: user.role,
|
||||
displayName: data.displayName,
|
||||
isPublic: data.isPublic,
|
||||
});
|
||||
return toDto(room);
|
||||
}
|
||||
|
||||
@Mutation(() => String, {
|
||||
name: 'chatcoopRemoveSecretaryRoom',
|
||||
description: 'Удалить комнату секретаря: вывести секретаря и снять комнату с синхронизации (возвращает идентификатор комнаты в реестре)',
|
||||
})
|
||||
@AuthRoles(['chairman', 'member'])
|
||||
async removeSecretaryRoom(
|
||||
@CurrentUser() user: MonoAccountDomainInterface,
|
||||
@Args('data', { type: () => RemoveSecretaryRoomInputDTO }) data: RemoveSecretaryRoomInputDTO
|
||||
): Promise<string> {
|
||||
this.logger.log(`chatcoopRemoveSecretaryRoom user=${user.username} room=${data.id}`);
|
||||
return this.service.removeSecretaryRoom(data.id);
|
||||
}
|
||||
}
|
||||
-1
@@ -54,7 +54,6 @@ export class TranscriptionResolver {
|
||||
return {
|
||||
id: domain.id,
|
||||
roomId: domain.roomId,
|
||||
matrixRoomId: domain.matrixRoomId,
|
||||
roomName: domain.roomName,
|
||||
startedAt: domain.startedAt,
|
||||
endedAt: domain.endedAt,
|
||||
|
||||
+42
@@ -818,6 +818,48 @@ export class MatrixApiService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Приглашает пользователя в комнату (от имени Matrix-админа, который состоит в комнате).
|
||||
* Пользователь увидит приглашение и войдёт сам — в отличие от {@link joinRoom} (force-join).
|
||||
*/
|
||||
async inviteUser(userId: string, roomId: string): Promise<void> {
|
||||
try {
|
||||
const adminToken = await this.loginAdmin();
|
||||
await this.httpClient.post(
|
||||
`/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}/invite`,
|
||||
{ user_id: userId },
|
||||
{ headers: { Authorization: `Bearer ${adminToken}` } }
|
||||
);
|
||||
this.logger.log(`Пользователь ${userId} приглашён в комнату ${roomId}`);
|
||||
} catch (error: any) {
|
||||
this.logger.error(
|
||||
`Не удалось пригласить пользователя ${userId} в комнату ${roomId}: ${JSON.stringify(error?.response?.data)}`
|
||||
);
|
||||
throw new Error('Не удалось пригласить пользователя в комнату');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Исключает пользователя из комнаты (от имени Matrix-админа с power 100).
|
||||
* Используется для вывода секретаря из комнаты по требованию председателя/совета.
|
||||
*/
|
||||
async kickUser(userId: string, roomId: string, reason?: string): Promise<void> {
|
||||
try {
|
||||
const adminToken = await this.loginAdmin();
|
||||
await this.httpClient.post(
|
||||
`/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}/kick`,
|
||||
reason ? { user_id: userId, reason } : { user_id: userId },
|
||||
{ headers: { Authorization: `Bearer ${adminToken}` } }
|
||||
);
|
||||
this.logger.log(`Пользователь ${userId} исключён из комнаты ${roomId}`);
|
||||
} catch (error: any) {
|
||||
this.logger.error(
|
||||
`Не удалось исключить пользователя ${userId} из комнаты ${roomId}: ${JSON.stringify(error?.response?.data)}`
|
||||
);
|
||||
throw new Error('Не удалось исключить пользователя из комнаты');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает текущие права пользователей в комнате
|
||||
*/
|
||||
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
import { BadRequestException, Inject, Injectable, Logger, NotFoundException } from '@nestjs/common';
|
||||
import { MatrixApiService } from './matrix-api.service';
|
||||
import { ChatCoopApplicationService } from './chatcoop-application.service';
|
||||
import { MatrixUserManagementService } from '../../domain/services/matrix-user-management.service';
|
||||
import { SECRETARY_ROOM_MATRIX } from '../config/matrix-secretary-room.config';
|
||||
import {
|
||||
CHATCOOP_MANAGED_MATRIX_ROOM_REPOSITORY,
|
||||
type ChatcoopManagedMatrixRoomRepository,
|
||||
} from '../../domain/repositories/managed-matrix-room.repository';
|
||||
import {
|
||||
CHATCOOP_STATE_REPOSITORY,
|
||||
type ChatcoopStateRepository,
|
||||
} from '../../domain/repositories/chatcoop-state.repository';
|
||||
import type { ManagedMatrixRoomDomainEntity } from '../../domain/entities/managed-matrix-room.entity';
|
||||
|
||||
export interface CreateSecretaryRoomInput {
|
||||
/** Логин пайщика-создателя (председатель или член совета) */
|
||||
creatorUsername: string;
|
||||
/** Роль создателя в кооперативе — для прав в комнате (chairman / member) */
|
||||
creatorRole: string;
|
||||
/** Название комнаты */
|
||||
displayName: string;
|
||||
/** true — публичная (любой может войти), false — приватная (вход по приглашению) */
|
||||
isPublic: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Управление комнатами секретаря: создание/удаление и список всех комнат реестра.
|
||||
*
|
||||
* Принцип безопасности: секретарь присутствует ТОЛЬКО в комнатах, созданных нашим бэкендом
|
||||
* (kind `secretary`, а также системные members/council и проектные capital_project). Произвольные
|
||||
* «чужие» Matrix-комнаты сюда не подключаются — Synapse общий на все кооперативы, и force-join в
|
||||
* чужую комнату был бы дырой.
|
||||
*/
|
||||
@Injectable()
|
||||
export class SecretaryRoomManagementService {
|
||||
private readonly logger = new Logger(SecretaryRoomManagementService.name);
|
||||
|
||||
constructor(
|
||||
private readonly matrixApi: MatrixApiService,
|
||||
private readonly chatCoopApplicationService: ChatCoopApplicationService,
|
||||
private readonly matrixUserManagement: MatrixUserManagementService,
|
||||
@Inject(CHATCOOP_MANAGED_MATRIX_ROOM_REPOSITORY)
|
||||
private readonly managedRooms: ChatcoopManagedMatrixRoomRepository,
|
||||
@Inject(CHATCOOP_STATE_REPOSITORY)
|
||||
private readonly chatcoopState: ChatcoopStateRepository
|
||||
) {}
|
||||
|
||||
async listRooms(): Promise<ManagedMatrixRoomDomainEntity[]> {
|
||||
return this.managedRooms.findAll();
|
||||
}
|
||||
|
||||
async createSecretaryRoom(input: CreateSecretaryRoomInput): Promise<ManagedMatrixRoomDomainEntity> {
|
||||
const st = await this.chatcoopState.getSingleton();
|
||||
if (!st.isInitialized || !st.spaceId || st.spaceId.trim().length === 0) {
|
||||
throw new BadRequestException('ChatCoop не инициализирован — нельзя создать комнату секретаря');
|
||||
}
|
||||
const secretaryId = st.secretaryMatrixUserId;
|
||||
if (typeof secretaryId !== 'string' || secretaryId.trim().length === 0) {
|
||||
throw new BadRequestException('Секретарь не инициализирован — нельзя создать комнату секретаря');
|
||||
}
|
||||
const displayName = input.displayName.trim();
|
||||
if (displayName.length === 0) {
|
||||
throw new BadRequestException('Название комнаты не может быть пустым');
|
||||
}
|
||||
|
||||
const adminUserId = this.matrixApi.getAdminUserId();
|
||||
const powerLevels = SECRETARY_ROOM_MATRIX.buildPowerLevels(adminUserId);
|
||||
const isPrivate = !input.isPublic;
|
||||
|
||||
const roomId = await this.matrixApi.createRoom(
|
||||
displayName.slice(0, 240),
|
||||
`Комната секретаря — создал ${input.creatorUsername}`,
|
||||
isPrivate,
|
||||
SECRETARY_ROOM_MATRIX.roomType,
|
||||
SECRETARY_ROOM_MATRIX.initialState.length > 0 ? SECRETARY_ROOM_MATRIX.initialState : undefined,
|
||||
// Комната секретаря всегда незашифрована — иначе секретарь не читает сообщения и не транскрибирует.
|
||||
false,
|
||||
powerLevels as Record<string, unknown>
|
||||
);
|
||||
|
||||
await this.matrixApi.addRoomToSpace(st.spaceId.trim(), roomId);
|
||||
|
||||
// Force-join только секретарь.
|
||||
await this.matrixApi.joinRoom(secretaryId.trim(), roomId);
|
||||
|
||||
// Создатель сразу входит в свою комнату с правами модератора.
|
||||
const creatorMatrix = await this.matrixUserManagement.getMatrixUserByCoopUsername(input.creatorUsername);
|
||||
if (creatorMatrix) {
|
||||
try {
|
||||
await this.matrixApi.joinRoom(creatorMatrix.matrixUserId, roomId);
|
||||
await this.chatCoopApplicationService.applyMembersRoomStylePowerForUser(
|
||||
creatorMatrix.matrixUserId,
|
||||
roomId,
|
||||
input.creatorRole
|
||||
);
|
||||
} catch (err) {
|
||||
this.logger.warn(`Не удалось ввести создателя ${input.creatorUsername} в комнату ${roomId}: ${String(err)}`);
|
||||
}
|
||||
} else {
|
||||
this.logger.warn(`Нет Matrix-аккаунта для создателя ${input.creatorUsername} — он войдёт позже сам`);
|
||||
}
|
||||
|
||||
const room = await this.managedRooms.upsertRoom({
|
||||
matrixRoomId: roomId,
|
||||
encrypted: false,
|
||||
kind: 'secretary',
|
||||
displayLabel: displayName,
|
||||
projectHash: null,
|
||||
secretaryInRoom: true,
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
`Создана комната секретаря ${roomId} (${isPrivate ? 'приватная' : 'публичная'}) создателем ${input.creatorUsername}`
|
||||
);
|
||||
return room;
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаляет комнату секретаря: выводит секретаря из Matrix-комнаты и убирает запись из реестра ChatCoop.
|
||||
* Сама Matrix-комната не уничтожается — её участники сохраняют доступ и историю, но транскрипция/синхронизация прекращаются.
|
||||
* Разрешено только для kind `secretary` — системные и проектные комнаты так удалять нельзя.
|
||||
*
|
||||
* На вход — внутренний идентификатор реестра (`id`), а НЕ Matrix room id: интерфейс не оперирует
|
||||
* Matrix room id, чтобы не раздавать handle для входа в приватные комнаты.
|
||||
*/
|
||||
async removeSecretaryRoom(id: string): Promise<string> {
|
||||
const room = await this.managedRooms.findById(id);
|
||||
if (!room) {
|
||||
throw new NotFoundException('Комната не найдена в реестре ChatCoop');
|
||||
}
|
||||
if (room.kind !== 'secretary') {
|
||||
throw new BadRequestException('Удалять можно только комнаты секретаря (системные и проектные защищены)');
|
||||
}
|
||||
|
||||
const matrixRoomId = room.matrixRoomId;
|
||||
const st = await this.chatcoopState.getSingleton();
|
||||
const secretaryId = st.secretaryMatrixUserId;
|
||||
if (typeof secretaryId === 'string' && secretaryId.trim().length > 0) {
|
||||
try {
|
||||
await this.matrixApi.kickUser(secretaryId.trim(), matrixRoomId, 'Комната секретаря удалена');
|
||||
} catch (err) {
|
||||
this.logger.warn(`Не удалось вывести секретаря из ${matrixRoomId}: ${String(err)}`);
|
||||
}
|
||||
}
|
||||
|
||||
await this.managedRooms.setSecretaryInRoom(matrixRoomId, false);
|
||||
await this.managedRooms.deleteByMatrixRoomId(matrixRoomId);
|
||||
this.logger.log(`Комната секретаря ${id} удалена из реестра`);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,8 @@ import { ChatCoopCalendarResolver } from './application/resolvers/chatcoop-calen
|
||||
import { ChatCoopCalendarFeedController } from './application/controllers/chatcoop-calendar-feed.controller';
|
||||
import { TranscriptionResolver } from './application/resolvers/transcription.resolver';
|
||||
import { ProjectCommunicationResolver } from './application/resolvers/project-communication.resolver';
|
||||
import { SecretaryRoomsResolver } from './application/resolvers/secretary-rooms.resolver';
|
||||
import { SecretaryRoomManagementService } from './application/services/secretary-room-management.service';
|
||||
import { WinstonLoggerService } from '~/application/logger/logger-app.service';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { z } from 'zod';
|
||||
@@ -527,6 +529,7 @@ export class ChatCoopPlugin extends BaseExtModule {
|
||||
// Application Services
|
||||
ChatCoopApplicationService,
|
||||
CapitalProjectMatrixSyncService,
|
||||
SecretaryRoomManagementService,
|
||||
MatrixApiService,
|
||||
ChatCoopSecretaryMatrixTokenService,
|
||||
SecretaryAgentService,
|
||||
@@ -604,6 +607,7 @@ export class ChatCoopPlugin extends BaseExtModule {
|
||||
TranscriptionResolver,
|
||||
ActiveUserStatusGuard,
|
||||
ProjectCommunicationResolver,
|
||||
SecretaryRoomsResolver,
|
||||
],
|
||||
exports: [
|
||||
ChatCoopPlugin,
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Тип комнаты Matrix, зарегистрированной в реестре ChatCoop (централизованное хранение).
|
||||
*/
|
||||
export type ChatcoopManagedMatrixRoomKind = 'members' | 'council' | 'capital_project';
|
||||
export type ChatcoopManagedMatrixRoomKind = 'members' | 'council' | 'capital_project' | 'secretary';
|
||||
|
||||
/**
|
||||
* Запись о Matrix-комнате кооператива: источник правды для LiveKit/секретаря; совет при миграции v3 из legacy-конфига.
|
||||
|
||||
+6
@@ -20,10 +20,16 @@ export interface UpsertManagedMatrixRoomInput {
|
||||
*/
|
||||
export interface ChatcoopManagedMatrixRoomRepository {
|
||||
upsertRoom(input: UpsertManagedMatrixRoomInput): Promise<ManagedMatrixRoomDomainEntity>;
|
||||
/** Поиск по внутреннему идентификатору реестра (не Matrix room id) — для операций из интерфейса. */
|
||||
findById(id: string): Promise<ManagedMatrixRoomDomainEntity | null>;
|
||||
findByMatrixRoomId(matrixRoomId: string): Promise<ManagedMatrixRoomDomainEntity | null>;
|
||||
findByKind(kind: ChatcoopManagedMatrixRoomKind): Promise<ManagedMatrixRoomDomainEntity[]>;
|
||||
/** Комнаты проекта Capital (kind capital_project, projectHash задан). */
|
||||
findByProjectHash(projectHash: string): Promise<ManagedMatrixRoomDomainEntity[]>;
|
||||
/** Все комнаты реестра (для интерфейса управления присутствием секретаря). */
|
||||
findAll(): Promise<ManagedMatrixRoomDomainEntity[]>;
|
||||
/** Комнаты, не привязанные к проекту Capital (members/council/secretary) — для синхронизации в blago. */
|
||||
findNonProjectCommunicationRooms(): Promise<ManagedMatrixRoomDomainEntity[]>;
|
||||
/** Комнаты, в которых секретарь может участвовать в звонке и писать plaintext в Matrix */
|
||||
findEligibleForSecretaryTranscription(): Promise<ManagedMatrixRoomDomainEntity[]>;
|
||||
/** Обновить флаг членства секретаря (после успешного join или проверки Matrix) */
|
||||
|
||||
+15
@@ -1,6 +1,8 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type {
|
||||
InterCompletedCallTranscriptionHead,
|
||||
InterNonProjectCommunicationRoomRef,
|
||||
InterNonProjectRoomKind,
|
||||
InterProjectCommunicationArtifactsPort,
|
||||
InterProjectCommunicationRoomRef,
|
||||
InterRoomMessageLine,
|
||||
@@ -48,6 +50,19 @@ export class ChatcoopInterProjectCommunicationArtifactsAdapter implements InterP
|
||||
}));
|
||||
}
|
||||
|
||||
async listNonProjectCommunicationRooms(): Promise<InterNonProjectCommunicationRoomRef[]> {
|
||||
const rooms = await this.managedRooms.findNonProjectCommunicationRooms();
|
||||
return rooms
|
||||
.filter((r): r is typeof r & { kind: InterNonProjectRoomKind } =>
|
||||
r.kind === 'members' || r.kind === 'council' || r.kind === 'secretary'
|
||||
)
|
||||
.map((r) => ({
|
||||
matrixRoomId: r.matrixRoomId,
|
||||
displayLabel: r.displayLabel || r.matrixRoomId,
|
||||
kind: r.kind,
|
||||
}));
|
||||
}
|
||||
|
||||
async listUtcDatesWithNewMessages(
|
||||
matrixRoomId: string,
|
||||
afterOriginServerTsExclusive: number
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import type { ChatcoopManagedMatrixRoomKind } from '../../domain/entities/manage
|
||||
import { ManagedMatrixRoomTypeormEntity } from '../entities/managed-matrix-room.typeorm-entity';
|
||||
|
||||
function parseKind(raw: string): ChatcoopManagedMatrixRoomKind {
|
||||
if (raw === 'members' || raw === 'council' || raw === 'capital_project') {
|
||||
if (raw === 'members' || raw === 'council' || raw === 'capital_project' || raw === 'secretary') {
|
||||
return raw;
|
||||
}
|
||||
return 'capital_project';
|
||||
|
||||
+16
-1
@@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Not, Repository } from 'typeorm';
|
||||
import type {
|
||||
ChatcoopManagedMatrixRoomRepository,
|
||||
UpsertManagedMatrixRoomInput,
|
||||
@@ -39,6 +39,11 @@ export class ManagedMatrixRoomTypeormRepository implements ChatcoopManagedMatrix
|
||||
return ManagedMatrixRoomMapper.toDomain(row);
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<ManagedMatrixRoomDomainEntity | null> {
|
||||
const row = await this.repository.findOne({ where: { id } });
|
||||
return row ? ManagedMatrixRoomMapper.toDomain(row) : null;
|
||||
}
|
||||
|
||||
async findByMatrixRoomId(matrixRoomId: string): Promise<ManagedMatrixRoomDomainEntity | null> {
|
||||
const row = await this.repository.findOne({ where: { matrixRoomId } });
|
||||
return row ? ManagedMatrixRoomMapper.toDomain(row) : null;
|
||||
@@ -56,6 +61,16 @@ export class ManagedMatrixRoomTypeormRepository implements ChatcoopManagedMatrix
|
||||
return rows.map(ManagedMatrixRoomMapper.toDomain);
|
||||
}
|
||||
|
||||
async findAll(): Promise<ManagedMatrixRoomDomainEntity[]> {
|
||||
const rows = await this.repository.find();
|
||||
return rows.map(ManagedMatrixRoomMapper.toDomain);
|
||||
}
|
||||
|
||||
async findNonProjectCommunicationRooms(): Promise<ManagedMatrixRoomDomainEntity[]> {
|
||||
const rows = await this.repository.find({ where: { roomKind: Not('capital_project') } });
|
||||
return rows.map(ManagedMatrixRoomMapper.toDomain);
|
||||
}
|
||||
|
||||
async findEligibleForSecretaryTranscription(): Promise<ManagedMatrixRoomDomainEntity[]> {
|
||||
const rows = await this.repository.find({ where: { encrypted: false } });
|
||||
return rows.map(ManagedMatrixRoomMapper.toDomain);
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ export class MarketplaceDesktopGrantsProvider
|
||||
private readonly grantsRegistry: ExtensionGrantsRegistry,
|
||||
@Inject(MARKETPLACE_WHITELIST_SERVICE)
|
||||
private readonly whitelistService: MarketplaceWhitelistService,
|
||||
@Inject(MARKETPLACE_KU_CHAIRMEN_SERVICE)
|
||||
@Inject(MARKETPLACE_KU_CHAIRMAN_SERVICE)
|
||||
private readonly kuChairmanService: MarketplaceKuChairmanService,
|
||||
) {}
|
||||
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import { ForbiddenException, Inject, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import {
|
||||
BRANCH_BLOCKCHAIN_PORT,
|
||||
type BranchBlockchainPort,
|
||||
} from '~/domain/branch/interfaces/branch-blockchain.port';
|
||||
|
||||
export const MARKETPLACE_BRANCH_OWNERSHIP_SERVICE = Symbol(
|
||||
'MarketplaceBranchOwnershipService'
|
||||
);
|
||||
|
||||
/**
|
||||
* Проверяет, что аккаунт правомочен оперировать от имени кооперативного участка.
|
||||
*
|
||||
* Правомочны: председатель КУ (`branch.trustee`) либо его доверенные лица
|
||||
* (`branch.trusted[]`, до 3 individual-аккаунтов; см. `branch::addtrusted`).
|
||||
*
|
||||
* Branches не реплицируются в Postgres — chain RPC через port. ADR-011
|
||||
* не нарушается: read-path вычисляет authorization, а не отдаёт прикладную
|
||||
* сущность пользователю.
|
||||
*/
|
||||
@Injectable()
|
||||
export class MarketplaceBranchOwnershipService {
|
||||
constructor(
|
||||
@Inject(BRANCH_BLOCKCHAIN_PORT)
|
||||
private readonly branchPort: BranchBlockchainPort
|
||||
) {}
|
||||
|
||||
async canActAsBraname(coopname: string, account: string, braname: string): Promise<boolean> {
|
||||
const branch = await this.branchPort.getBranch(coopname, braname);
|
||||
if (!branch) return false;
|
||||
if (branch.trustee === account) return true;
|
||||
return branch.trusted?.includes(account) ?? false;
|
||||
}
|
||||
|
||||
async assertCanActAsBraname(
|
||||
coopname: string,
|
||||
account: string,
|
||||
braname: string
|
||||
): Promise<void> {
|
||||
const branch = await this.branchPort.getBranch(coopname, braname);
|
||||
if (!branch) {
|
||||
throw new NotFoundException(`Кооперативный участок ${braname} не найден.`);
|
||||
}
|
||||
const allowed =
|
||||
branch.trustee === account || (branch.trusted?.includes(account) ?? false);
|
||||
if (!allowed) {
|
||||
throw new ForbiddenException(
|
||||
'Действие доступно только председателю кооперативного участка или его доверенному лицу.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,29 @@ export class MeetTrackerService {
|
||||
this.logger.info('MeetTrackerService инициализирован');
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохраняет состояние трекинга собраний read-modify-write по СВЕЖЕМУ config.
|
||||
*
|
||||
* `this.pluginConfig` — снимок, захваченный при initialize() (boot), а
|
||||
* cron-проверка крутится часами; update() заменяет весь config JSONB целиком.
|
||||
* Если писать захваченный снимок, затрутся поля, записанные в БД после boot
|
||||
* другими сервисами (онбординг-флаги и т.п.) — это и есть наблюдавшийся
|
||||
* сброс онбординга. Поэтому берём актуальный config из БД и накладываем
|
||||
* только поля, которыми владеет meet-tracker.
|
||||
*/
|
||||
private async persistTrackedState(): Promise<void> {
|
||||
const fresh = await this.extensionRepository.findByName(this.extensionName);
|
||||
const baseConfig = fresh?.config ?? this.pluginConfig.config;
|
||||
const nextConfig: IConfig = {
|
||||
...baseConfig,
|
||||
trackedMeets: this.pluginConfig.config.trackedMeets,
|
||||
closedMeetIds: this.pluginConfig.config.closedMeetIds,
|
||||
lastCheckTimestamp: this.pluginConfig.config.lastCheckTimestamp,
|
||||
};
|
||||
await this.extensionRepository.update({ name: this.extensionName, config: nextConfig });
|
||||
this.pluginConfig = { ...this.pluginConfig, config: nextConfig };
|
||||
}
|
||||
|
||||
// Приватная функция для обновления trackedMeet на основе свежих данных
|
||||
private getUpdatedTrackedMeet(trackedMeet: TrackedMeet, meetData: any, extendedStatus: string): TrackedMeet {
|
||||
return {
|
||||
@@ -145,7 +168,7 @@ export class MeetTrackerService {
|
||||
this.pluginConfig.config.trackedMeets[trackedMeetIndex].notifications.endNotification = true;
|
||||
if (!closedMeetIds.includes(meetID)) {
|
||||
closedMeetIds.push(meetID);
|
||||
await this.extensionRepository.update(this.pluginConfig);
|
||||
await this.persistTrackedState();
|
||||
}
|
||||
}
|
||||
this.logger.info(`Удаление закрытого собрания ${meetHash} (№${meetID}) из списка отслеживаемых`);
|
||||
@@ -310,7 +333,7 @@ export class MeetTrackerService {
|
||||
this.pluginConfig.config.lastCheckTimestamp = now.toISOString();
|
||||
|
||||
// Сохраняем изменения в конфигурации
|
||||
await this.extensionRepository.update(this.pluginConfig);
|
||||
await this.persistTrackedState();
|
||||
} catch (error: any) {
|
||||
this.logger.error(`Ошибка при проверке собраний: ${error.message}`, error.stack);
|
||||
}
|
||||
|
||||
@@ -215,8 +215,18 @@ export class PowerupPlugin extends BaseExtModule implements OnModuleDestroy {
|
||||
|
||||
await this.blockchainPort.powerUp(username, quantity);
|
||||
|
||||
this.plugin.config.lastDailyReplenishmentDate = new Date().toISOString();
|
||||
await this.extensionRepository.update(this.plugin);
|
||||
// read-modify-write по СВЕЖЕМУ config: daily-cron держит in-memory снимок
|
||||
// `this.plugin` с момента boot, а update() заменяет весь config JSONB
|
||||
// целиком. Перезапись устаревшего снимка стёрла бы поля, записанные за
|
||||
// сутки другими сервисами (онбординг и т.п.). Берём актуальный config и
|
||||
// трогаем только lastDailyReplenishmentDate.
|
||||
const fresh = await this.extensionRepository.findByName(this.name);
|
||||
const nextConfig = {
|
||||
...(fresh?.config ?? this.plugin.config),
|
||||
lastDailyReplenishmentDate: new Date().toISOString(),
|
||||
};
|
||||
await this.extensionRepository.update({ name: this.name, config: nextConfig });
|
||||
this.plugin = { ...this.plugin, config: nextConfig };
|
||||
|
||||
await this.log({
|
||||
type: 'daily',
|
||||
|
||||
@@ -317,6 +317,9 @@ export const AllTypesProps: Record<string,any> = {
|
||||
attributes:"RequestAttributeInput",
|
||||
images:"RequestImageInput",
|
||||
type:"RequestTypeInput"
|
||||
},
|
||||
CreateSecretaryRoomInput:{
|
||||
|
||||
},
|
||||
CreateSovietIndividualDataInput:{
|
||||
passport:"PassportInput"
|
||||
@@ -621,6 +624,7 @@ export const AllTypesProps: Record<string,any> = {
|
||||
MakeClearanceInput:{
|
||||
document:"SignedDigitalDocumentInput"
|
||||
},
|
||||
ManagedRoomKind: "enum" as const,
|
||||
MarkReportPeriodInput:{
|
||||
mark:"ReportSubmissionMark",
|
||||
reportType:"ReportType"
|
||||
@@ -754,6 +758,12 @@ export const AllTypesProps: Record<string,any> = {
|
||||
MarketplaceListOrdersInput:{
|
||||
statuses:"MarketplaceOrderStatus"
|
||||
},
|
||||
MarketplaceListOutgoingPaymentsAsSupplierFilterInput:{
|
||||
statuses:"MarketplaceOutgoingPaymentRequestStatus"
|
||||
},
|
||||
MarketplaceListOutgoingPaymentsFilterInput:{
|
||||
statuses:"MarketplaceOutgoingPaymentRequestStatus"
|
||||
},
|
||||
MarketplaceListPendingOffersInput:{
|
||||
|
||||
},
|
||||
@@ -1118,8 +1128,14 @@ export const AllTypesProps: Record<string,any> = {
|
||||
chatcoopCreateCalendarEvent:{
|
||||
data:"CreateChatCoopCalendarEventInput"
|
||||
},
|
||||
chatcoopCreateSecretaryRoom:{
|
||||
data:"CreateSecretaryRoomInput"
|
||||
},
|
||||
chatcoopDeleteCalendarEvent:{
|
||||
|
||||
},
|
||||
chatcoopRemoveSecretaryRoom:{
|
||||
data:"RemoveSecretaryRoomInput"
|
||||
},
|
||||
chatcoopUpdateCalendarEvent:{
|
||||
data:"UpdateChatCoopCalendarEventInput"
|
||||
@@ -1508,6 +1524,7 @@ export const AllTypesProps: Record<string,any> = {
|
||||
input:"WalmoveInput"
|
||||
}
|
||||
},
|
||||
NonProjectRoomKind: "enum" as const,
|
||||
NotificationWorkflowRecipientInput:{
|
||||
|
||||
},
|
||||
@@ -1980,10 +1997,10 @@ export const AllTypesProps: Record<string,any> = {
|
||||
options:"PaginationInput"
|
||||
},
|
||||
marketplaceListOutgoingPayments:{
|
||||
statuses:"MarketplaceOutgoingPaymentRequestStatus"
|
||||
filter:"MarketplaceListOutgoingPaymentsFilterInput"
|
||||
},
|
||||
marketplaceListOutgoingPaymentsAsSupplier:{
|
||||
statuses:"MarketplaceOutgoingPaymentRequestStatus"
|
||||
filter:"MarketplaceListOutgoingPaymentsAsSupplierFilterInput"
|
||||
},
|
||||
marketplaceListPendingOffers:{
|
||||
input:"MarketplaceListPendingOffersInput"
|
||||
@@ -2085,6 +2102,9 @@ export const AllTypesProps: Record<string,any> = {
|
||||
},
|
||||
RemoveAvailableCategoryTypesInput:{
|
||||
categoryTypes:"CategoryTypeInput"
|
||||
},
|
||||
RemoveSecretaryRoomInput:{
|
||||
|
||||
},
|
||||
ReplaceAvailableItemsInput:{
|
||||
categoryTypes:"CategoryTypeInput"
|
||||
@@ -2705,7 +2725,6 @@ export const ReturnTypes: Record<string,any> = {
|
||||
createdAt:"DateTime",
|
||||
endedAt:"DateTime",
|
||||
id:"String",
|
||||
matrixRoomId:"String",
|
||||
memo:"String",
|
||||
participants:"String",
|
||||
roomId:"String",
|
||||
@@ -3384,6 +3403,11 @@ export const ReturnTypes: Record<string,any> = {
|
||||
displayLabel:"String",
|
||||
matrixRoomId:"String"
|
||||
},
|
||||
ChatcoopNonProjectCommunicationRoom:{
|
||||
displayLabel:"String",
|
||||
kind:"NonProjectRoomKind",
|
||||
matrixRoomId:"String"
|
||||
},
|
||||
ChatcoopProjectCommunicationRoom:{
|
||||
displayLabel:"String",
|
||||
matrixRoomId:"String"
|
||||
@@ -3395,6 +3419,14 @@ export const ReturnTypes: Record<string,any> = {
|
||||
kind:"RoomMessageKind",
|
||||
originServerTs:"Float"
|
||||
},
|
||||
ChatcoopSecretaryRoom:{
|
||||
displayLabel:"String",
|
||||
editable:"Boolean",
|
||||
encrypted:"Boolean",
|
||||
id:"String",
|
||||
kind:"ManagedRoomKind",
|
||||
secretaryInRoom:"Boolean"
|
||||
},
|
||||
ContactsDTO:{
|
||||
chairman:"PublicChairman",
|
||||
details:"OrganizationDetails",
|
||||
@@ -3808,7 +3840,6 @@ export const ReturnTypes: Record<string,any> = {
|
||||
},
|
||||
Ledger2Wallet:{
|
||||
available:"String",
|
||||
blocked:"String",
|
||||
id:"String",
|
||||
name:"String"
|
||||
},
|
||||
@@ -4165,9 +4196,7 @@ export const ReturnTypes: Record<string,any> = {
|
||||
},
|
||||
MarketplaceOrderCreateTxSnapshot:{
|
||||
block_num:"Int",
|
||||
blocked_amount:"String",
|
||||
did_assign:"Boolean",
|
||||
did_convert:"Boolean",
|
||||
locked_amount:"String",
|
||||
signed_at:"String",
|
||||
tx_hash:"String"
|
||||
},
|
||||
@@ -4632,7 +4661,9 @@ export const ReturnTypes: Record<string,any> = {
|
||||
chatcoopCreateAccount:"Boolean",
|
||||
chatcoopCreateCalendarEvent:"ChatCoopCalendarEvent",
|
||||
chatcoopCreateCalendarIcsSubscription:"ChatCoopCalendarIcsUrlResponse",
|
||||
chatcoopCreateSecretaryRoom:"ChatcoopSecretaryRoom",
|
||||
chatcoopDeleteCalendarEvent:"Boolean",
|
||||
chatcoopRemoveSecretaryRoom:"String",
|
||||
chatcoopUpdateCalendarEvent:"ChatCoopCalendarEvent",
|
||||
chatcoopUpdateTranscriptionMemo:"CallTranscription",
|
||||
completeCapitalOnboardingStep:"CapitalOnboardingState",
|
||||
@@ -5169,7 +5200,6 @@ export const ReturnTypes: Record<string,any> = {
|
||||
agreement_id:"ID",
|
||||
available:"String",
|
||||
blockNum:"Float",
|
||||
blocked:"String",
|
||||
coopname:"String",
|
||||
id:"ID",
|
||||
membership_contribution:"String",
|
||||
@@ -5258,7 +5288,9 @@ export const ReturnTypes: Record<string,any> = {
|
||||
chatcoopGetTranscriptions:"CallTranscription",
|
||||
chatcoopListCalendarEvents:"ChatCoopCalendarEvent",
|
||||
chatcoopListCalendarRooms:"ChatCoopCalendarRoomOption",
|
||||
chatcoopListNonProjectCommunicationRooms:"ChatcoopNonProjectCommunicationRoom",
|
||||
chatcoopListProjectCommunicationRooms:"ChatcoopProjectCommunicationRoom",
|
||||
chatcoopListSecretaryRooms:"ChatcoopSecretaryRoom",
|
||||
chatcoopListUtcDatesWithNewRoomMessages:"String",
|
||||
checkReportReadiness:"ReportReadinessView",
|
||||
cooperativeAgreements:"CoopAgreement",
|
||||
|
||||
@@ -2163,11 +2163,11 @@ export type ValueTypes = {
|
||||
createdAt?:boolean | `@${string}`,
|
||||
endedAt?:boolean | `@${string}`,
|
||||
id?:boolean | `@${string}`,
|
||||
matrixRoomId?:boolean | `@${string}`,
|
||||
/** Пользовательская заметка о содержании звонка */
|
||||
memo?:boolean | `@${string}`,
|
||||
/** Отображаемые имена участников (Synapse displayname); в БД хранятся канонические Matrix user id */
|
||||
participants?:boolean | `@${string}`,
|
||||
/** Внутреннее имя комнаты звонка (LiveKit room name), не Matrix room id */
|
||||
roomId?:boolean | `@${string}`,
|
||||
roomName?:boolean | `@${string}`,
|
||||
startedAt?:boolean | `@${string}`,
|
||||
@@ -3689,6 +3689,16 @@ export type ValueTypes = {
|
||||
matrixRoomId?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`,
|
||||
['...on ChatCoopCalendarRoomOption']?: Omit<ValueTypes["ChatCoopCalendarRoomOption"], "...on ChatCoopCalendarRoomOption">
|
||||
}>;
|
||||
["ChatcoopNonProjectCommunicationRoom"]: AliasType<{
|
||||
/** Подпись для отображения комнаты */
|
||||
displayLabel?:boolean | `@${string}`,
|
||||
/** Тип комнаты (пайщики / совет / секретарь) */
|
||||
kind?:boolean | `@${string}`,
|
||||
/** Идентификатор комнаты Matrix */
|
||||
matrixRoomId?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`,
|
||||
['...on ChatcoopNonProjectCommunicationRoom']?: Omit<ValueTypes["ChatcoopNonProjectCommunicationRoom"], "...on ChatcoopNonProjectCommunicationRoom">
|
||||
}>;
|
||||
["ChatcoopProjectCommunicationRoom"]: AliasType<{
|
||||
/** Подпись для отображения (комната / проект Capital) */
|
||||
@@ -3710,6 +3720,22 @@ export type ValueTypes = {
|
||||
originServerTs?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`,
|
||||
['...on ChatcoopRoomMessageLine']?: Omit<ValueTypes["ChatcoopRoomMessageLine"], "...on ChatcoopRoomMessageLine">
|
||||
}>;
|
||||
["ChatcoopSecretaryRoom"]: AliasType<{
|
||||
/** Название комнаты */
|
||||
displayLabel?:boolean | `@${string}`,
|
||||
/** Можно ли удалить комнату из интерфейса (только комнаты секретаря) */
|
||||
editable?:boolean | `@${string}`,
|
||||
/** Комната зашифрована (E2EE) — секретарь не транскрибирует такие комнаты */
|
||||
encrypted?:boolean | `@${string}`,
|
||||
/** Внутренний идентификатор комнаты в реестре (для операций; это НЕ Matrix room id) */
|
||||
id?:boolean | `@${string}`,
|
||||
/** Тип комнаты */
|
||||
kind?:boolean | `@${string}`,
|
||||
/** Секретарь присутствует в комнате */
|
||||
secretaryInRoom?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`,
|
||||
['...on ChatcoopSecretaryRoom']?: Omit<ValueTypes["ChatcoopSecretaryRoom"], "...on ChatcoopSecretaryRoom">
|
||||
}>;
|
||||
["CheckMatrixUsernameInput"]: {
|
||||
username: string | Variable<any, string>
|
||||
@@ -4442,6 +4468,12 @@ export type ValueTypes = {
|
||||
weightUnit?: string | undefined | null | Variable<any, string>,
|
||||
/** Ширина упаковки */
|
||||
width?: number | undefined | null | Variable<any, string>
|
||||
};
|
||||
["CreateSecretaryRoomInput"]: {
|
||||
/** Название комнаты */
|
||||
displayName: string | Variable<any, string>,
|
||||
/** Публичная комната (любой может войти) либо приватная (вход по приглашению создателя) */
|
||||
isPublic: boolean | Variable<any, string>
|
||||
};
|
||||
["CreateSovietIndividualDataInput"]: {
|
||||
/** Дата рождения */
|
||||
@@ -5931,8 +5963,6 @@ export type ValueTypes = {
|
||||
["Ledger2Wallet"]: AliasType<{
|
||||
/** Доступный баланс */
|
||||
available?:boolean | `@${string}`,
|
||||
/** Заблокированный баланс */
|
||||
blocked?:boolean | `@${string}`,
|
||||
/** eosio::name-идентификатор кошелька (w.<contract>.<waltype>) */
|
||||
id?:boolean | `@${string}`,
|
||||
/** Название кошелька */
|
||||
@@ -6028,6 +6058,8 @@ export type ValueTypes = {
|
||||
/** Имя пользователя */
|
||||
username: string | Variable<any, string>
|
||||
};
|
||||
/** Тип комнаты: пайщики, совет, проект Capital, комната секретаря */
|
||||
["ManagedRoomKind"]:ManagedRoomKind;
|
||||
["MarkReportPeriodInput"]: {
|
||||
mark?: ValueTypes["ReportSubmissionMark"] | undefined | null | Variable<any, string>,
|
||||
period?: number | undefined | null | Variable<any, string>,
|
||||
@@ -6868,6 +6900,16 @@ export type ValueTypes = {
|
||||
statuses?: Array<ValueTypes["MarketplaceOrderStatus"]> | undefined | null | Variable<any, string>,
|
||||
/** Фильтр по аккаунту поставщика. */
|
||||
supplier_account?: string | undefined | null | Variable<any, string>
|
||||
};
|
||||
["MarketplaceListOutgoingPaymentsAsSupplierFilterInput"]: {
|
||||
/** Фильтр по статусам выплат. Пусто — показывать все статусы. */
|
||||
statuses?: Array<ValueTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null | Variable<any, string>
|
||||
};
|
||||
["MarketplaceListOutgoingPaymentsFilterInput"]: {
|
||||
/** Фильтр по статусам выплат. Пусто — показывать все статусы. */
|
||||
statuses?: Array<ValueTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null | Variable<any, string>,
|
||||
/** Поставщик-получатель выплаты. Пусто — по всем поставщикам. */
|
||||
supplier_account?: string | undefined | null | Variable<any, string>
|
||||
};
|
||||
["MarketplaceListPendingOffersInput"]: {
|
||||
/** Количество элементов на странице */
|
||||
@@ -6903,7 +6945,7 @@ export type ValueTypes = {
|
||||
["MarketplaceMemberWallet"]: AliasType<{
|
||||
coopname?:boolean | `@${string}`,
|
||||
username?:boolean | `@${string}`,
|
||||
/** Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.member */
|
||||
/** Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.order */
|
||||
wallets?:ValueTypes["MarketplaceWalletEntry"],
|
||||
__typename?: boolean | `@${string}`,
|
||||
['...on MarketplaceMemberWallet']?: Omit<ValueTypes["MarketplaceMemberWallet"], "...on MarketplaceMemberWallet">
|
||||
@@ -7056,16 +7098,12 @@ export type ValueTypes = {
|
||||
__typename?: boolean | `@${string}`,
|
||||
['...on MarketplaceOrder']?: Omit<ValueTypes["MarketplaceOrder"], "...on MarketplaceOrder">
|
||||
}>;
|
||||
/** Снимок транзакции блокировки средств: ссылки на блок, флаги конверсии и сумма. */
|
||||
/** Снимок транзакции резервирования средств: ссылки на блок и сумма резерва. */
|
||||
["MarketplaceOrderCreateTxSnapshot"]: AliasType<{
|
||||
/** Номер блока, в который попала транзакция. */
|
||||
block_num?:boolean | `@${string}`,
|
||||
/** Сумма заблокированных средств (строка денежного актива). */
|
||||
blocked_amount?:boolean | `@${string}`,
|
||||
/** Был ли выполнен перевод средств между кошельками пайщика. */
|
||||
did_assign?:boolean | `@${string}`,
|
||||
/** Была ли выполнена конверсия паевого взноса в членский. */
|
||||
did_convert?:boolean | `@${string}`,
|
||||
/** Сумма зарезервированных средств (строка денежного актива). */
|
||||
locked_amount?:boolean | `@${string}`,
|
||||
/** Время подписания заказа (ISO 8601). */
|
||||
signed_at?:boolean | `@${string}`,
|
||||
/** Идентификатор транзакции в блокчейне. */
|
||||
@@ -7683,7 +7721,7 @@ export type ValueTypes = {
|
||||
["MarketplaceWalletEntry"]: AliasType<{
|
||||
/** Доступный остаток (`userwallets.available`) */
|
||||
available?:boolean | `@${string}`,
|
||||
/** Заблокированный остаток (`userwallets.blocked`) */
|
||||
/** Заблокированный остаток (`userwallets.blocked`). Для marketplace-кошельков всегда `0` — резерв выражается через `.available` кошелька w.mkt.order. Поле остаётся для wallet/withdraw flow и legacy данных. */
|
||||
blocked?:boolean | `@${string}`,
|
||||
/** Человекочитаемое название (из cooptypes LEDGER2_WALLET_REGISTRY) */
|
||||
human_name?:boolean | `@${string}`,
|
||||
@@ -7691,7 +7729,7 @@ export type ValueTypes = {
|
||||
kind?:boolean | `@${string}`,
|
||||
/** UX-метка кошелька в формате `<тип взноса> | <программа>` (например «Паевой | Цифровой Кошелёк», «Членский | Стол Заказов») */
|
||||
label?:boolean | `@${string}`,
|
||||
/** eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.member) */
|
||||
/** eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.order) */
|
||||
name?:boolean | `@${string}`,
|
||||
/** program_id: 1 — Цифровой Кошелёк, 2 — Стол Заказов */
|
||||
program_id?:boolean | `@${string}`,
|
||||
@@ -8092,7 +8130,9 @@ chatcoopCreateCalendarEvent?: [{ data: ValueTypes["CreateChatCoopCalendarEventIn
|
||||
|
||||
Требуемые роли: chairman, member, user. */
|
||||
chatcoopCreateCalendarIcsSubscription?:ValueTypes["ChatCoopCalendarIcsUrlResponse"],
|
||||
chatcoopCreateSecretaryRoom?: [{ data: ValueTypes["CreateSecretaryRoomInput"] | Variable<any, string>},ValueTypes["ChatcoopSecretaryRoom"]],
|
||||
chatcoopDeleteCalendarEvent?: [{ id: string | Variable<any, string>},boolean | `@${string}`],
|
||||
chatcoopRemoveSecretaryRoom?: [{ data: ValueTypes["RemoveSecretaryRoomInput"] | Variable<any, string>},boolean | `@${string}`],
|
||||
chatcoopUpdateCalendarEvent?: [{ data: ValueTypes["UpdateChatCoopCalendarEventInput"] | Variable<any, string>},ValueTypes["ChatCoopCalendarEvent"]],
|
||||
chatcoopUpdateTranscriptionMemo?: [{ data: ValueTypes["UpdateCallTranscriptionMemoInput"] | Variable<any, string>},ValueTypes["CallTranscription"]],
|
||||
completeCapitalOnboardingStep?: [{ data: ValueTypes["CapitalOnboardingStepInput"] | Variable<any, string>},ValueTypes["CapitalOnboardingState"]],
|
||||
@@ -8223,6 +8263,8 @@ walmoveWallets?: [{ input: ValueTypes["WalmoveInput"] | Variable<any, string>},V
|
||||
__typename?: boolean | `@${string}`,
|
||||
['...on Mutation']?: Omit<ValueTypes["Mutation"], "...on Mutation">
|
||||
}>;
|
||||
/** Тип комнаты вне проекта: пайщики, совет, комната секретаря */
|
||||
["NonProjectRoomKind"]:NonProjectRoomKind;
|
||||
["NotificationWorkflowRecipientInput"]: {
|
||||
/** Username получателя */
|
||||
username: string | Variable<any, string>
|
||||
@@ -9198,8 +9240,6 @@ walmoveWallets?: [{ input: ValueTypes["WalmoveInput"] | Variable<any, string>},V
|
||||
available?:boolean | `@${string}`,
|
||||
/** Номер блока последнего обновления */
|
||||
blockNum?:boolean | `@${string}`,
|
||||
/** Заблокированный баланс (формат: "100.0000 RUB") */
|
||||
blocked?:boolean | `@${string}`,
|
||||
/** Имя кооператива */
|
||||
coopname?:boolean | `@${string}`,
|
||||
/** Уникальный идентификатор кошелька в блокчейне */
|
||||
@@ -9453,7 +9493,15 @@ chatcoopGetTranscriptions?: [{ data?: ValueTypes["GetTranscriptionsInput"] | und
|
||||
|
||||
Требуемые роли: chairman, member. */
|
||||
chatcoopListCalendarRooms?:ValueTypes["ChatCoopCalendarRoomOption"],
|
||||
/** Комнаты Matrix вне проектов Capital (пайщики/совет/секретарь) — для синхронизации в blago
|
||||
|
||||
Требуемые роли: chairman, member, user. */
|
||||
chatcoopListNonProjectCommunicationRooms?:ValueTypes["ChatcoopNonProjectCommunicationRoom"],
|
||||
chatcoopListProjectCommunicationRooms?: [{ data: ValueTypes["GetProjectCommunicationRoomsInput"] | Variable<any, string>},ValueTypes["ChatcoopProjectCommunicationRoom"]],
|
||||
/** Все комнаты реестра ChatCoop (системные/проектные — read-only, комнаты секретаря — удаляемые)
|
||||
|
||||
Требуемые роли: chairman, member. */
|
||||
chatcoopListSecretaryRooms?:ValueTypes["ChatcoopSecretaryRoom"],
|
||||
chatcoopListUtcDatesWithNewRoomMessages?: [{ data: ValueTypes["ListUtcDatesWithNewRoomMessagesInput"] | Variable<any, string>},boolean | `@${string}`],
|
||||
checkReportReadiness?: [{ reportType: ValueTypes["ReportType"] | Variable<any, string>},ValueTypes["ReportReadinessView"]],
|
||||
cooperativeAgreements?: [{ coopname: string | Variable<any, string>},ValueTypes["CoopAgreement"]],
|
||||
@@ -9596,8 +9644,8 @@ marketplaceListMyOrders?: [{ input?: ValueTypes["MarketplaceListOrdersInput"] |
|
||||
marketplaceListMyReadyToReceive?:ValueTypes["MarketplaceOrder"],
|
||||
/** Все заявления текущего пайщика на гарантийный возврат — активные и архивные. */
|
||||
marketplaceListMyReturnClaims?:ValueTypes["MarketplaceReturnClaim"],
|
||||
marketplaceListOutgoingPayments?: [{ statuses?: Array<ValueTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null | Variable<any, string>, supplier_account?: string | undefined | null | Variable<any, string>},ValueTypes["MarketplaceOutgoingPaymentRequest"]],
|
||||
marketplaceListOutgoingPaymentsAsSupplier?: [{ statuses?: Array<ValueTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null | Variable<any, string>},ValueTypes["MarketplaceOutgoingPaymentRequest"]],
|
||||
marketplaceListOutgoingPayments?: [{ filter?: ValueTypes["MarketplaceListOutgoingPaymentsFilterInput"] | undefined | null | Variable<any, string>},ValueTypes["MarketplaceOutgoingPaymentRequest"]],
|
||||
marketplaceListOutgoingPaymentsAsSupplier?: [{ filter?: ValueTypes["MarketplaceListOutgoingPaymentsAsSupplierFilterInput"] | undefined | null | Variable<any, string>},ValueTypes["MarketplaceOutgoingPaymentRequest"]],
|
||||
marketplaceListPendingOffers?: [{ input?: ValueTypes["MarketplaceListPendingOffersInput"] | undefined | null | Variable<any, string>},ValueTypes["MarketplaceOfferPaginationResult"]],
|
||||
marketplaceListReturnClaimsByBraname?: [{ data: ValueTypes["MarketplaceListReturnClaimsByBranameInput"] | Variable<any, string>},ValueTypes["MarketplaceReturnClaim"]],
|
||||
marketplaceListShipments?: [{ data?: ValueTypes["MarketplaceListShipmentsInput"] | undefined | null | Variable<any, string>},ValueTypes["MarketplaceShipment"]],
|
||||
@@ -9606,7 +9654,7 @@ marketplaceListSupplierOrders?: [{ input?: ValueTypes["MarketplaceListOrdersInpu
|
||||
/** Список пайщиков-поставщиков, допущенных к публикации оферт */
|
||||
marketplaceListWhitelist?:ValueTypes["MarketplaceWhitelistEntry"],
|
||||
marketplaceListWriteoffProposals?: [{ data: ValueTypes["MarketplaceListWriteoffProposalsInput"] | Variable<any, string>, options?: ValueTypes["PaginationInput"] | undefined | null | Variable<any, string>},ValueTypes["PaginatedMarketplaceWriteoffProposals"]],
|
||||
/** Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.member */
|
||||
/** Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.order */
|
||||
marketplaceMemberWallet?:ValueTypes["MarketplaceMemberWallet"],
|
||||
/** Состояние онбординга пайщика в Столе заказов: показывать ли gate или пропускать на стол */
|
||||
marketplaceOnboardingState?:ValueTypes["MarketplaceOnboardingState"],
|
||||
@@ -9819,6 +9867,10 @@ validateReportEdits?: [{ editsJson: string | Variable<any, string>, reportType:
|
||||
["RemoveAvailableCategoryTypesInput"]: {
|
||||
/** Типы товаров для удаления */
|
||||
categoryTypes: Array<ValueTypes["CategoryTypeInput"]> | Variable<any, string>
|
||||
};
|
||||
["RemoveSecretaryRoomInput"]: {
|
||||
/** Идентификатор комнаты в реестре, которую нужно удалить */
|
||||
id: string | Variable<any, string>
|
||||
};
|
||||
["ReplaceAvailableItemsInput"]: {
|
||||
/** ID категорий (целые категории) */
|
||||
@@ -12239,11 +12291,11 @@ export type ResolverInputTypes = {
|
||||
createdAt?:boolean | `@${string}`,
|
||||
endedAt?:boolean | `@${string}`,
|
||||
id?:boolean | `@${string}`,
|
||||
matrixRoomId?:boolean | `@${string}`,
|
||||
/** Пользовательская заметка о содержании звонка */
|
||||
memo?:boolean | `@${string}`,
|
||||
/** Отображаемые имена участников (Synapse displayname); в БД хранятся канонические Matrix user id */
|
||||
participants?:boolean | `@${string}`,
|
||||
/** Внутреннее имя комнаты звонка (LiveKit room name), не Matrix room id */
|
||||
roomId?:boolean | `@${string}`,
|
||||
roomName?:boolean | `@${string}`,
|
||||
startedAt?:boolean | `@${string}`,
|
||||
@@ -13723,6 +13775,15 @@ export type ResolverInputTypes = {
|
||||
displayLabel?:boolean | `@${string}`,
|
||||
matrixRoomId?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
["ChatcoopNonProjectCommunicationRoom"]: AliasType<{
|
||||
/** Подпись для отображения комнаты */
|
||||
displayLabel?:boolean | `@${string}`,
|
||||
/** Тип комнаты (пайщики / совет / секретарь) */
|
||||
kind?:boolean | `@${string}`,
|
||||
/** Идентификатор комнаты Matrix */
|
||||
matrixRoomId?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
["ChatcoopProjectCommunicationRoom"]: AliasType<{
|
||||
/** Подпись для отображения (комната / проект Capital) */
|
||||
@@ -13742,6 +13803,21 @@ export type ResolverInputTypes = {
|
||||
/** origin_server_ts из Matrix (мс) */
|
||||
originServerTs?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
["ChatcoopSecretaryRoom"]: AliasType<{
|
||||
/** Название комнаты */
|
||||
displayLabel?:boolean | `@${string}`,
|
||||
/** Можно ли удалить комнату из интерфейса (только комнаты секретаря) */
|
||||
editable?:boolean | `@${string}`,
|
||||
/** Комната зашифрована (E2EE) — секретарь не транскрибирует такие комнаты */
|
||||
encrypted?:boolean | `@${string}`,
|
||||
/** Внутренний идентификатор комнаты в реестре (для операций; это НЕ Matrix room id) */
|
||||
id?:boolean | `@${string}`,
|
||||
/** Тип комнаты */
|
||||
kind?:boolean | `@${string}`,
|
||||
/** Секретарь присутствует в комнате */
|
||||
secretaryInRoom?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
["CheckMatrixUsernameInput"]: {
|
||||
username: string
|
||||
@@ -14469,6 +14545,12 @@ export type ResolverInputTypes = {
|
||||
weightUnit?: string | undefined | null,
|
||||
/** Ширина упаковки */
|
||||
width?: number | undefined | null
|
||||
};
|
||||
["CreateSecretaryRoomInput"]: {
|
||||
/** Название комнаты */
|
||||
displayName: string,
|
||||
/** Публичная комната (любой может войти) либо приватная (вход по приглашению создателя) */
|
||||
isPublic: boolean
|
||||
};
|
||||
["CreateSovietIndividualDataInput"]: {
|
||||
/** Дата рождения */
|
||||
@@ -15918,8 +16000,6 @@ export type ResolverInputTypes = {
|
||||
["Ledger2Wallet"]: AliasType<{
|
||||
/** Доступный баланс */
|
||||
available?:boolean | `@${string}`,
|
||||
/** Заблокированный баланс */
|
||||
blocked?:boolean | `@${string}`,
|
||||
/** eosio::name-идентификатор кошелька (w.<contract>.<waltype>) */
|
||||
id?:boolean | `@${string}`,
|
||||
/** Название кошелька */
|
||||
@@ -16011,6 +16091,8 @@ export type ResolverInputTypes = {
|
||||
/** Имя пользователя */
|
||||
username: string
|
||||
};
|
||||
/** Тип комнаты: пайщики, совет, проект Capital, комната секретаря */
|
||||
["ManagedRoomKind"]:ManagedRoomKind;
|
||||
["MarkReportPeriodInput"]: {
|
||||
mark?: ResolverInputTypes["ReportSubmissionMark"] | undefined | null,
|
||||
period?: number | undefined | null,
|
||||
@@ -16822,6 +16904,16 @@ export type ResolverInputTypes = {
|
||||
statuses?: Array<ResolverInputTypes["MarketplaceOrderStatus"]> | undefined | null,
|
||||
/** Фильтр по аккаунту поставщика. */
|
||||
supplier_account?: string | undefined | null
|
||||
};
|
||||
["MarketplaceListOutgoingPaymentsAsSupplierFilterInput"]: {
|
||||
/** Фильтр по статусам выплат. Пусто — показывать все статусы. */
|
||||
statuses?: Array<ResolverInputTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null
|
||||
};
|
||||
["MarketplaceListOutgoingPaymentsFilterInput"]: {
|
||||
/** Фильтр по статусам выплат. Пусто — показывать все статусы. */
|
||||
statuses?: Array<ResolverInputTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null,
|
||||
/** Поставщик-получатель выплаты. Пусто — по всем поставщикам. */
|
||||
supplier_account?: string | undefined | null
|
||||
};
|
||||
["MarketplaceListPendingOffersInput"]: {
|
||||
/** Количество элементов на странице */
|
||||
@@ -16857,7 +16949,7 @@ export type ResolverInputTypes = {
|
||||
["MarketplaceMemberWallet"]: AliasType<{
|
||||
coopname?:boolean | `@${string}`,
|
||||
username?:boolean | `@${string}`,
|
||||
/** Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.member */
|
||||
/** Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.order */
|
||||
wallets?:ResolverInputTypes["MarketplaceWalletEntry"],
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
@@ -17004,16 +17096,12 @@ export type ResolverInputTypes = {
|
||||
warranty_until?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
/** Снимок транзакции блокировки средств: ссылки на блок, флаги конверсии и сумма. */
|
||||
/** Снимок транзакции резервирования средств: ссылки на блок и сумма резерва. */
|
||||
["MarketplaceOrderCreateTxSnapshot"]: AliasType<{
|
||||
/** Номер блока, в который попала транзакция. */
|
||||
block_num?:boolean | `@${string}`,
|
||||
/** Сумма заблокированных средств (строка денежного актива). */
|
||||
blocked_amount?:boolean | `@${string}`,
|
||||
/** Был ли выполнен перевод средств между кошельками пайщика. */
|
||||
did_assign?:boolean | `@${string}`,
|
||||
/** Была ли выполнена конверсия паевого взноса в членский. */
|
||||
did_convert?:boolean | `@${string}`,
|
||||
/** Сумма зарезервированных средств (строка денежного актива). */
|
||||
locked_amount?:boolean | `@${string}`,
|
||||
/** Время подписания заказа (ISO 8601). */
|
||||
signed_at?:boolean | `@${string}`,
|
||||
/** Идентификатор транзакции в блокчейне. */
|
||||
@@ -17611,7 +17699,7 @@ export type ResolverInputTypes = {
|
||||
["MarketplaceWalletEntry"]: AliasType<{
|
||||
/** Доступный остаток (`userwallets.available`) */
|
||||
available?:boolean | `@${string}`,
|
||||
/** Заблокированный остаток (`userwallets.blocked`) */
|
||||
/** Заблокированный остаток (`userwallets.blocked`). Для marketplace-кошельков всегда `0` — резерв выражается через `.available` кошелька w.mkt.order. Поле остаётся для wallet/withdraw flow и legacy данных. */
|
||||
blocked?:boolean | `@${string}`,
|
||||
/** Человекочитаемое название (из cooptypes LEDGER2_WALLET_REGISTRY) */
|
||||
human_name?:boolean | `@${string}`,
|
||||
@@ -17619,7 +17707,7 @@ export type ResolverInputTypes = {
|
||||
kind?:boolean | `@${string}`,
|
||||
/** UX-метка кошелька в формате `<тип взноса> | <программа>` (например «Паевой | Цифровой Кошелёк», «Членский | Стол Заказов») */
|
||||
label?:boolean | `@${string}`,
|
||||
/** eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.member) */
|
||||
/** eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.order) */
|
||||
name?:boolean | `@${string}`,
|
||||
/** program_id: 1 — Цифровой Кошелёк, 2 — Стол Заказов */
|
||||
program_id?:boolean | `@${string}`,
|
||||
@@ -18006,7 +18094,9 @@ chatcoopCreateCalendarEvent?: [{ data: ResolverInputTypes["CreateChatCoopCalenda
|
||||
|
||||
Требуемые роли: chairman, member, user. */
|
||||
chatcoopCreateCalendarIcsSubscription?:ResolverInputTypes["ChatCoopCalendarIcsUrlResponse"],
|
||||
chatcoopCreateSecretaryRoom?: [{ data: ResolverInputTypes["CreateSecretaryRoomInput"]},ResolverInputTypes["ChatcoopSecretaryRoom"]],
|
||||
chatcoopDeleteCalendarEvent?: [{ id: string},boolean | `@${string}`],
|
||||
chatcoopRemoveSecretaryRoom?: [{ data: ResolverInputTypes["RemoveSecretaryRoomInput"]},boolean | `@${string}`],
|
||||
chatcoopUpdateCalendarEvent?: [{ data: ResolverInputTypes["UpdateChatCoopCalendarEventInput"]},ResolverInputTypes["ChatCoopCalendarEvent"]],
|
||||
chatcoopUpdateTranscriptionMemo?: [{ data: ResolverInputTypes["UpdateCallTranscriptionMemoInput"]},ResolverInputTypes["CallTranscription"]],
|
||||
completeCapitalOnboardingStep?: [{ data: ResolverInputTypes["CapitalOnboardingStepInput"]},ResolverInputTypes["CapitalOnboardingState"]],
|
||||
@@ -18136,6 +18226,8 @@ voteOnAnnualGeneralMeet?: [{ data: ResolverInputTypes["VoteOnAnnualGeneralMeetIn
|
||||
walmoveWallets?: [{ input: ResolverInputTypes["WalmoveInput"]},ResolverInputTypes["Ledger2AdjustmentResult"]],
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
/** Тип комнаты вне проекта: пайщики, совет, комната секретаря */
|
||||
["NonProjectRoomKind"]:NonProjectRoomKind;
|
||||
["NotificationWorkflowRecipientInput"]: {
|
||||
/** Username получателя */
|
||||
username: string
|
||||
@@ -19059,8 +19151,6 @@ walmoveWallets?: [{ input: ResolverInputTypes["WalmoveInput"]},ResolverInputType
|
||||
available?:boolean | `@${string}`,
|
||||
/** Номер блока последнего обновления */
|
||||
blockNum?:boolean | `@${string}`,
|
||||
/** Заблокированный баланс (формат: "100.0000 RUB") */
|
||||
blocked?:boolean | `@${string}`,
|
||||
/** Имя кооператива */
|
||||
coopname?:boolean | `@${string}`,
|
||||
/** Уникальный идентификатор кошелька в блокчейне */
|
||||
@@ -19310,7 +19400,15 @@ chatcoopGetTranscriptions?: [{ data?: ResolverInputTypes["GetTranscriptionsInput
|
||||
|
||||
Требуемые роли: chairman, member. */
|
||||
chatcoopListCalendarRooms?:ResolverInputTypes["ChatCoopCalendarRoomOption"],
|
||||
/** Комнаты Matrix вне проектов Capital (пайщики/совет/секретарь) — для синхронизации в blago
|
||||
|
||||
Требуемые роли: chairman, member, user. */
|
||||
chatcoopListNonProjectCommunicationRooms?:ResolverInputTypes["ChatcoopNonProjectCommunicationRoom"],
|
||||
chatcoopListProjectCommunicationRooms?: [{ data: ResolverInputTypes["GetProjectCommunicationRoomsInput"]},ResolverInputTypes["ChatcoopProjectCommunicationRoom"]],
|
||||
/** Все комнаты реестра ChatCoop (системные/проектные — read-only, комнаты секретаря — удаляемые)
|
||||
|
||||
Требуемые роли: chairman, member. */
|
||||
chatcoopListSecretaryRooms?:ResolverInputTypes["ChatcoopSecretaryRoom"],
|
||||
chatcoopListUtcDatesWithNewRoomMessages?: [{ data: ResolverInputTypes["ListUtcDatesWithNewRoomMessagesInput"]},boolean | `@${string}`],
|
||||
checkReportReadiness?: [{ reportType: ResolverInputTypes["ReportType"]},ResolverInputTypes["ReportReadinessView"]],
|
||||
cooperativeAgreements?: [{ coopname: string},ResolverInputTypes["CoopAgreement"]],
|
||||
@@ -19453,8 +19551,8 @@ marketplaceListMyOrders?: [{ input?: ResolverInputTypes["MarketplaceListOrdersIn
|
||||
marketplaceListMyReadyToReceive?:ResolverInputTypes["MarketplaceOrder"],
|
||||
/** Все заявления текущего пайщика на гарантийный возврат — активные и архивные. */
|
||||
marketplaceListMyReturnClaims?:ResolverInputTypes["MarketplaceReturnClaim"],
|
||||
marketplaceListOutgoingPayments?: [{ statuses?: Array<ResolverInputTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null, supplier_account?: string | undefined | null},ResolverInputTypes["MarketplaceOutgoingPaymentRequest"]],
|
||||
marketplaceListOutgoingPaymentsAsSupplier?: [{ statuses?: Array<ResolverInputTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null},ResolverInputTypes["MarketplaceOutgoingPaymentRequest"]],
|
||||
marketplaceListOutgoingPayments?: [{ filter?: ResolverInputTypes["MarketplaceListOutgoingPaymentsFilterInput"] | undefined | null},ResolverInputTypes["MarketplaceOutgoingPaymentRequest"]],
|
||||
marketplaceListOutgoingPaymentsAsSupplier?: [{ filter?: ResolverInputTypes["MarketplaceListOutgoingPaymentsAsSupplierFilterInput"] | undefined | null},ResolverInputTypes["MarketplaceOutgoingPaymentRequest"]],
|
||||
marketplaceListPendingOffers?: [{ input?: ResolverInputTypes["MarketplaceListPendingOffersInput"] | undefined | null},ResolverInputTypes["MarketplaceOfferPaginationResult"]],
|
||||
marketplaceListReturnClaimsByBraname?: [{ data: ResolverInputTypes["MarketplaceListReturnClaimsByBranameInput"]},ResolverInputTypes["MarketplaceReturnClaim"]],
|
||||
marketplaceListShipments?: [{ data?: ResolverInputTypes["MarketplaceListShipmentsInput"] | undefined | null},ResolverInputTypes["MarketplaceShipment"]],
|
||||
@@ -19463,7 +19561,7 @@ marketplaceListSupplierOrders?: [{ input?: ResolverInputTypes["MarketplaceListOr
|
||||
/** Список пайщиков-поставщиков, допущенных к публикации оферт */
|
||||
marketplaceListWhitelist?:ResolverInputTypes["MarketplaceWhitelistEntry"],
|
||||
marketplaceListWriteoffProposals?: [{ data: ResolverInputTypes["MarketplaceListWriteoffProposalsInput"], options?: ResolverInputTypes["PaginationInput"] | undefined | null},ResolverInputTypes["PaginatedMarketplaceWriteoffProposals"]],
|
||||
/** Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.member */
|
||||
/** Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.order */
|
||||
marketplaceMemberWallet?:ResolverInputTypes["MarketplaceMemberWallet"],
|
||||
/** Состояние онбординга пайщика в Столе заказов: показывать ли gate или пропускать на стол */
|
||||
marketplaceOnboardingState?:ResolverInputTypes["MarketplaceOnboardingState"],
|
||||
@@ -19669,6 +19767,10 @@ validateReportEdits?: [{ editsJson: string, reportType: ResolverInputTypes["Repo
|
||||
["RemoveAvailableCategoryTypesInput"]: {
|
||||
/** Типы товаров для удаления */
|
||||
categoryTypes: Array<ResolverInputTypes["CategoryTypeInput"]>
|
||||
};
|
||||
["RemoveSecretaryRoomInput"]: {
|
||||
/** Идентификатор комнаты в реестре, которую нужно удалить */
|
||||
id: string
|
||||
};
|
||||
["ReplaceAvailableItemsInput"]: {
|
||||
/** ID категорий (целые категории) */
|
||||
@@ -22016,11 +22118,11 @@ export type ModelTypes = {
|
||||
createdAt: ModelTypes["DateTime"],
|
||||
endedAt?: ModelTypes["DateTime"] | undefined | null,
|
||||
id: string,
|
||||
matrixRoomId: string,
|
||||
/** Пользовательская заметка о содержании звонка */
|
||||
memo: string,
|
||||
/** Отображаемые имена участников (Synapse displayname); в БД хранятся канонические Matrix user id */
|
||||
participants: Array<string>,
|
||||
/** Внутреннее имя комнаты звонка (LiveKit room name), не Matrix room id */
|
||||
roomId: string,
|
||||
roomName: string,
|
||||
startedAt: ModelTypes["DateTime"],
|
||||
@@ -23457,6 +23559,14 @@ export type ModelTypes = {
|
||||
["ChatCoopCalendarRoomOption"]: {
|
||||
displayLabel: string,
|
||||
matrixRoomId: string
|
||||
};
|
||||
["ChatcoopNonProjectCommunicationRoom"]: {
|
||||
/** Подпись для отображения комнаты */
|
||||
displayLabel: string,
|
||||
/** Тип комнаты (пайщики / совет / секретарь) */
|
||||
kind: ModelTypes["NonProjectRoomKind"],
|
||||
/** Идентификатор комнаты Matrix */
|
||||
matrixRoomId: string
|
||||
};
|
||||
["ChatcoopProjectCommunicationRoom"]: {
|
||||
/** Подпись для отображения (комната / проект Capital) */
|
||||
@@ -23474,6 +23584,20 @@ export type ModelTypes = {
|
||||
kind: ModelTypes["RoomMessageKind"],
|
||||
/** origin_server_ts из Matrix (мс) */
|
||||
originServerTs: number
|
||||
};
|
||||
["ChatcoopSecretaryRoom"]: {
|
||||
/** Название комнаты */
|
||||
displayLabel: string,
|
||||
/** Можно ли удалить комнату из интерфейса (только комнаты секретаря) */
|
||||
editable: boolean,
|
||||
/** Комната зашифрована (E2EE) — секретарь не транскрибирует такие комнаты */
|
||||
encrypted: boolean,
|
||||
/** Внутренний идентификатор комнаты в реестре (для операций; это НЕ Matrix room id) */
|
||||
id: string,
|
||||
/** Тип комнаты */
|
||||
kind: ModelTypes["ManagedRoomKind"],
|
||||
/** Секретарь присутствует в комнате */
|
||||
secretaryInRoom: boolean
|
||||
};
|
||||
["CheckMatrixUsernameInput"]: {
|
||||
username: string
|
||||
@@ -24193,6 +24317,12 @@ export type ModelTypes = {
|
||||
weightUnit?: string | undefined | null,
|
||||
/** Ширина упаковки */
|
||||
width?: number | undefined | null
|
||||
};
|
||||
["CreateSecretaryRoomInput"]: {
|
||||
/** Название комнаты */
|
||||
displayName: string,
|
||||
/** Публичная комната (любой может войти) либо приватная (вход по приглашению создателя) */
|
||||
isPublic: boolean
|
||||
};
|
||||
["CreateSovietIndividualDataInput"]: {
|
||||
/** Дата рождения */
|
||||
@@ -25593,8 +25723,6 @@ export type ModelTypes = {
|
||||
["Ledger2Wallet"]: {
|
||||
/** Доступный баланс */
|
||||
available: string,
|
||||
/** Заблокированный баланс */
|
||||
blocked: string,
|
||||
/** eosio::name-идентификатор кошелька (w.<contract>.<waltype>) */
|
||||
id: string,
|
||||
/** Название кошелька */
|
||||
@@ -25680,6 +25808,7 @@ export type ModelTypes = {
|
||||
/** Имя пользователя */
|
||||
username: string
|
||||
};
|
||||
["ManagedRoomKind"]:ManagedRoomKind;
|
||||
["MarkReportPeriodInput"]: {
|
||||
mark?: ModelTypes["ReportSubmissionMark"] | undefined | null,
|
||||
period?: number | undefined | null,
|
||||
@@ -26455,6 +26584,16 @@ export type ModelTypes = {
|
||||
statuses?: Array<ModelTypes["MarketplaceOrderStatus"]> | undefined | null,
|
||||
/** Фильтр по аккаунту поставщика. */
|
||||
supplier_account?: string | undefined | null
|
||||
};
|
||||
["MarketplaceListOutgoingPaymentsAsSupplierFilterInput"]: {
|
||||
/** Фильтр по статусам выплат. Пусто — показывать все статусы. */
|
||||
statuses?: Array<ModelTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null
|
||||
};
|
||||
["MarketplaceListOutgoingPaymentsFilterInput"]: {
|
||||
/** Фильтр по статусам выплат. Пусто — показывать все статусы. */
|
||||
statuses?: Array<ModelTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null,
|
||||
/** Поставщик-получатель выплаты. Пусто — по всем поставщикам. */
|
||||
supplier_account?: string | undefined | null
|
||||
};
|
||||
["MarketplaceListPendingOffersInput"]: {
|
||||
/** Количество элементов на странице */
|
||||
@@ -26490,7 +26629,7 @@ export type ModelTypes = {
|
||||
["MarketplaceMemberWallet"]: {
|
||||
coopname: string,
|
||||
username: string,
|
||||
/** Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.member */
|
||||
/** Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.order */
|
||||
wallets: Array<ModelTypes["MarketplaceWalletEntry"]>
|
||||
};
|
||||
["MarketplaceModerationLogEntry"]: {
|
||||
@@ -26631,16 +26770,12 @@ export type ModelTypes = {
|
||||
/** Дата окончания гарантии. */
|
||||
warranty_until?: ModelTypes["DateTime"] | undefined | null
|
||||
};
|
||||
/** Снимок транзакции блокировки средств: ссылки на блок, флаги конверсии и сумма. */
|
||||
/** Снимок транзакции резервирования средств: ссылки на блок и сумма резерва. */
|
||||
["MarketplaceOrderCreateTxSnapshot"]: {
|
||||
/** Номер блока, в который попала транзакция. */
|
||||
block_num: number,
|
||||
/** Сумма заблокированных средств (строка денежного актива). */
|
||||
blocked_amount: string,
|
||||
/** Был ли выполнен перевод средств между кошельками пайщика. */
|
||||
did_assign: boolean,
|
||||
/** Была ли выполнена конверсия паевого взноса в членский. */
|
||||
did_convert: boolean,
|
||||
/** Сумма зарезервированных средств (строка денежного актива). */
|
||||
locked_amount: string,
|
||||
/** Время подписания заказа (ISO 8601). */
|
||||
signed_at: string,
|
||||
/** Идентификатор транзакции в блокчейне. */
|
||||
@@ -27209,7 +27344,7 @@ export type ModelTypes = {
|
||||
["MarketplaceWalletEntry"]: {
|
||||
/** Доступный остаток (`userwallets.available`) */
|
||||
available: string,
|
||||
/** Заблокированный остаток (`userwallets.blocked`) */
|
||||
/** Заблокированный остаток (`userwallets.blocked`). Для marketplace-кошельков всегда `0` — резерв выражается через `.available` кошелька w.mkt.order. Поле остаётся для wallet/withdraw flow и legacy данных. */
|
||||
blocked: string,
|
||||
/** Человекочитаемое название (из cooptypes LEDGER2_WALLET_REGISTRY) */
|
||||
human_name: string,
|
||||
@@ -27217,7 +27352,7 @@ export type ModelTypes = {
|
||||
kind: string,
|
||||
/** UX-метка кошелька в формате `<тип взноса> | <программа>` (например «Паевой | Цифровой Кошелёк», «Членский | Стол Заказов») */
|
||||
label: string,
|
||||
/** eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.member) */
|
||||
/** eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.order) */
|
||||
name: string,
|
||||
/** program_id: 1 — Цифровой Кошелёк, 2 — Стол Заказов */
|
||||
program_id: number
|
||||
@@ -27828,10 +27963,18 @@ export type ModelTypes = {
|
||||
|
||||
Требуемые роли: chairman, member, user. */
|
||||
chatcoopCreateCalendarIcsSubscription: ModelTypes["ChatCoopCalendarIcsUrlResponse"],
|
||||
/** Создать комнату с секретарём (публичную или приватную); секретарь подключается сразу
|
||||
|
||||
Требуемые роли: chairman, member. */
|
||||
chatcoopCreateSecretaryRoom: ModelTypes["ChatcoopSecretaryRoom"],
|
||||
/** Удалить событие календаря
|
||||
|
||||
Требуемые роли: chairman, member. */
|
||||
chatcoopDeleteCalendarEvent: boolean,
|
||||
/** Удалить комнату секретаря: вывести секретаря и снять комнату с синхронизации (возвращает идентификатор комнаты в реестре)
|
||||
|
||||
Требуемые роли: chairman, member. */
|
||||
chatcoopRemoveSecretaryRoom: string,
|
||||
/** Обновить событие календаря
|
||||
|
||||
Требуемые роли: chairman, member. */
|
||||
@@ -28231,6 +28374,7 @@ export type ModelTypes = {
|
||||
Требуемые роли: chairman. */
|
||||
walmoveWallets: ModelTypes["Ledger2AdjustmentResult"]
|
||||
};
|
||||
["NonProjectRoomKind"]:NonProjectRoomKind;
|
||||
["NotificationWorkflowRecipientInput"]: {
|
||||
/** Username получателя */
|
||||
username: string
|
||||
@@ -29085,8 +29229,6 @@ export type ModelTypes = {
|
||||
available: string,
|
||||
/** Номер блока последнего обновления */
|
||||
blockNum?: number | undefined | null,
|
||||
/** Заблокированный баланс (формат: "100.0000 RUB") */
|
||||
blocked: string,
|
||||
/** Имя кооператива */
|
||||
coopname: string,
|
||||
/** Уникальный идентификатор кошелька в блокчейне */
|
||||
@@ -29411,10 +29553,18 @@ export type ModelTypes = {
|
||||
|
||||
Требуемые роли: chairman, member. */
|
||||
chatcoopListCalendarRooms: Array<ModelTypes["ChatCoopCalendarRoomOption"]>,
|
||||
/** Комнаты Matrix вне проектов Capital (пайщики/совет/секретарь) — для синхронизации в blago
|
||||
|
||||
Требуемые роли: chairman, member, user. */
|
||||
chatcoopListNonProjectCommunicationRooms: Array<ModelTypes["ChatcoopNonProjectCommunicationRoom"]>,
|
||||
/** Комнаты Matrix, привязанные к проекту Capital (реестр ChatCoop)
|
||||
|
||||
Требуемые роли: chairman, member, user. */
|
||||
chatcoopListProjectCommunicationRooms: Array<ModelTypes["ChatcoopProjectCommunicationRoom"]>,
|
||||
/** Все комнаты реестра ChatCoop (системные/проектные — read-only, комнаты секретаря — удаляемые)
|
||||
|
||||
Требуемые роли: chairman, member. */
|
||||
chatcoopListSecretaryRooms: Array<ModelTypes["ChatcoopSecretaryRoom"]>,
|
||||
/** UTC-даты (YYYY-MM-DD), в которых есть сообщения новее afterOriginServerTsExclusive, для комнаты Matrix
|
||||
|
||||
Требуемые роли: chairman, member, user. */
|
||||
@@ -29681,7 +29831,7 @@ export type ModelTypes = {
|
||||
marketplaceListCategories: Array<ModelTypes["MarketplaceCategory"]>,
|
||||
/** Постраничный список сводных заявок поставщика — для стола поставщика и для прослеживания состояния заказов. */
|
||||
marketplaceListConsolidatedRequests: ModelTypes["MarketplaceConsolidatedRequestPaginationResult"],
|
||||
/** Список наклеек инвентаря КУ: admin/совет видят весь склад кооператива, оператор — только свой участок. */
|
||||
/** Список наклеек инвентаря КУ — для admin-стола склада и операторских разделов. */
|
||||
marketplaceListInventory: Array<ModelTypes["MarketplaceInventoryItem"]>,
|
||||
/** Список заказов на кооперативном участке, ожидающих открытия и финальной подписи выдачи (для оператора кооперативного участка). */
|
||||
marketplaceListIssuancesByBraname: Array<ModelTypes["MarketplaceOrder"]>,
|
||||
@@ -29717,7 +29867,7 @@ export type ModelTypes = {
|
||||
marketplaceListWhitelist: Array<ModelTypes["MarketplaceWhitelistEntry"]>,
|
||||
/** Лента всех проектов списания кооператива с фильтром по статусу. */
|
||||
marketplaceListWriteoffProposals: ModelTypes["PaginatedMarketplaceWriteoffProposals"],
|
||||
/** Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.member */
|
||||
/** Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.order */
|
||||
marketplaceMemberWallet: ModelTypes["MarketplaceMemberWallet"],
|
||||
/** Состояние онбординга пайщика в Столе заказов: показывать ли gate или пропускать на стол */
|
||||
marketplaceOnboardingState: ModelTypes["MarketplaceOnboardingState"],
|
||||
@@ -29939,6 +30089,10 @@ export type ModelTypes = {
|
||||
["RemoveAvailableCategoryTypesInput"]: {
|
||||
/** Типы товаров для удаления */
|
||||
categoryTypes: Array<ModelTypes["CategoryTypeInput"]>
|
||||
};
|
||||
["RemoveSecretaryRoomInput"]: {
|
||||
/** Идентификатор комнаты в реестре, которую нужно удалить */
|
||||
id: string
|
||||
};
|
||||
["ReplaceAvailableItemsInput"]: {
|
||||
/** ID категорий (целые категории) */
|
||||
@@ -32300,11 +32454,11 @@ export type GraphQLTypes = {
|
||||
createdAt: GraphQLTypes["DateTime"],
|
||||
endedAt?: GraphQLTypes["DateTime"] | undefined | null,
|
||||
id: string,
|
||||
matrixRoomId: string,
|
||||
/** Пользовательская заметка о содержании звонка */
|
||||
memo: string,
|
||||
/** Отображаемые имена участников (Synapse displayname); в БД хранятся канонические Matrix user id */
|
||||
participants: Array<string>,
|
||||
/** Внутреннее имя комнаты звонка (LiveKit room name), не Matrix room id */
|
||||
roomId: string,
|
||||
roomName: string,
|
||||
startedAt: GraphQLTypes["DateTime"],
|
||||
@@ -33825,6 +33979,16 @@ export type GraphQLTypes = {
|
||||
displayLabel: string,
|
||||
matrixRoomId: string,
|
||||
['...on ChatCoopCalendarRoomOption']: Omit<GraphQLTypes["ChatCoopCalendarRoomOption"], "...on ChatCoopCalendarRoomOption">
|
||||
};
|
||||
["ChatcoopNonProjectCommunicationRoom"]: {
|
||||
__typename: "ChatcoopNonProjectCommunicationRoom",
|
||||
/** Подпись для отображения комнаты */
|
||||
displayLabel: string,
|
||||
/** Тип комнаты (пайщики / совет / секретарь) */
|
||||
kind: GraphQLTypes["NonProjectRoomKind"],
|
||||
/** Идентификатор комнаты Matrix */
|
||||
matrixRoomId: string,
|
||||
['...on ChatcoopNonProjectCommunicationRoom']: Omit<GraphQLTypes["ChatcoopNonProjectCommunicationRoom"], "...on ChatcoopNonProjectCommunicationRoom">
|
||||
};
|
||||
["ChatcoopProjectCommunicationRoom"]: {
|
||||
__typename: "ChatcoopProjectCommunicationRoom",
|
||||
@@ -33846,6 +34010,22 @@ export type GraphQLTypes = {
|
||||
/** origin_server_ts из Matrix (мс) */
|
||||
originServerTs: number,
|
||||
['...on ChatcoopRoomMessageLine']: Omit<GraphQLTypes["ChatcoopRoomMessageLine"], "...on ChatcoopRoomMessageLine">
|
||||
};
|
||||
["ChatcoopSecretaryRoom"]: {
|
||||
__typename: "ChatcoopSecretaryRoom",
|
||||
/** Название комнаты */
|
||||
displayLabel: string,
|
||||
/** Можно ли удалить комнату из интерфейса (только комнаты секретаря) */
|
||||
editable: boolean,
|
||||
/** Комната зашифрована (E2EE) — секретарь не транскрибирует такие комнаты */
|
||||
encrypted: boolean,
|
||||
/** Внутренний идентификатор комнаты в реестре (для операций; это НЕ Matrix room id) */
|
||||
id: string,
|
||||
/** Тип комнаты */
|
||||
kind: GraphQLTypes["ManagedRoomKind"],
|
||||
/** Секретарь присутствует в комнате */
|
||||
secretaryInRoom: boolean,
|
||||
['...on ChatcoopSecretaryRoom']: Omit<GraphQLTypes["ChatcoopSecretaryRoom"], "...on ChatcoopSecretaryRoom">
|
||||
};
|
||||
["CheckMatrixUsernameInput"]: {
|
||||
username: string
|
||||
@@ -34578,6 +34758,12 @@ export type GraphQLTypes = {
|
||||
weightUnit?: string | undefined | null,
|
||||
/** Ширина упаковки */
|
||||
width?: number | undefined | null
|
||||
};
|
||||
["CreateSecretaryRoomInput"]: {
|
||||
/** Название комнаты */
|
||||
displayName: string,
|
||||
/** Публичная комната (любой может войти) либо приватная (вход по приглашению создателя) */
|
||||
isPublic: boolean
|
||||
};
|
||||
["CreateSovietIndividualDataInput"]: {
|
||||
/** Дата рождения */
|
||||
@@ -36068,8 +36254,6 @@ export type GraphQLTypes = {
|
||||
__typename: "Ledger2Wallet",
|
||||
/** Доступный баланс */
|
||||
available: string,
|
||||
/** Заблокированный баланс */
|
||||
blocked: string,
|
||||
/** eosio::name-идентификатор кошелька (w.<contract>.<waltype>) */
|
||||
id: string,
|
||||
/** Название кошелька */
|
||||
@@ -36164,6 +36348,8 @@ export type GraphQLTypes = {
|
||||
/** Имя пользователя */
|
||||
username: string
|
||||
};
|
||||
/** Тип комнаты: пайщики, совет, проект Capital, комната секретаря */
|
||||
["ManagedRoomKind"]: ManagedRoomKind;
|
||||
["MarkReportPeriodInput"]: {
|
||||
mark?: GraphQLTypes["ReportSubmissionMark"] | undefined | null,
|
||||
period?: number | undefined | null,
|
||||
@@ -37004,6 +37190,16 @@ export type GraphQLTypes = {
|
||||
statuses?: Array<GraphQLTypes["MarketplaceOrderStatus"]> | undefined | null,
|
||||
/** Фильтр по аккаунту поставщика. */
|
||||
supplier_account?: string | undefined | null
|
||||
};
|
||||
["MarketplaceListOutgoingPaymentsAsSupplierFilterInput"]: {
|
||||
/** Фильтр по статусам выплат. Пусто — показывать все статусы. */
|
||||
statuses?: Array<GraphQLTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null
|
||||
};
|
||||
["MarketplaceListOutgoingPaymentsFilterInput"]: {
|
||||
/** Фильтр по статусам выплат. Пусто — показывать все статусы. */
|
||||
statuses?: Array<GraphQLTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null,
|
||||
/** Поставщик-получатель выплаты. Пусто — по всем поставщикам. */
|
||||
supplier_account?: string | undefined | null
|
||||
};
|
||||
["MarketplaceListPendingOffersInput"]: {
|
||||
/** Количество элементов на странице */
|
||||
@@ -37040,7 +37236,7 @@ export type GraphQLTypes = {
|
||||
__typename: "MarketplaceMemberWallet",
|
||||
coopname: string,
|
||||
username: string,
|
||||
/** Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.member */
|
||||
/** Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.order */
|
||||
wallets: Array<GraphQLTypes["MarketplaceWalletEntry"]>,
|
||||
['...on MarketplaceMemberWallet']: Omit<GraphQLTypes["MarketplaceMemberWallet"], "...on MarketplaceMemberWallet">
|
||||
};
|
||||
@@ -37192,17 +37388,13 @@ export type GraphQLTypes = {
|
||||
warranty_until?: GraphQLTypes["DateTime"] | undefined | null,
|
||||
['...on MarketplaceOrder']: Omit<GraphQLTypes["MarketplaceOrder"], "...on MarketplaceOrder">
|
||||
};
|
||||
/** Снимок транзакции блокировки средств: ссылки на блок, флаги конверсии и сумма. */
|
||||
/** Снимок транзакции резервирования средств: ссылки на блок и сумма резерва. */
|
||||
["MarketplaceOrderCreateTxSnapshot"]: {
|
||||
__typename: "MarketplaceOrderCreateTxSnapshot",
|
||||
/** Номер блока, в который попала транзакция. */
|
||||
block_num: number,
|
||||
/** Сумма заблокированных средств (строка денежного актива). */
|
||||
blocked_amount: string,
|
||||
/** Был ли выполнен перевод средств между кошельками пайщика. */
|
||||
did_assign: boolean,
|
||||
/** Была ли выполнена конверсия паевого взноса в членский. */
|
||||
did_convert: boolean,
|
||||
/** Сумма зарезервированных средств (строка денежного актива). */
|
||||
locked_amount: string,
|
||||
/** Время подписания заказа (ISO 8601). */
|
||||
signed_at: string,
|
||||
/** Идентификатор транзакции в блокчейне. */
|
||||
@@ -37820,7 +38012,7 @@ export type GraphQLTypes = {
|
||||
__typename: "MarketplaceWalletEntry",
|
||||
/** Доступный остаток (`userwallets.available`) */
|
||||
available: string,
|
||||
/** Заблокированный остаток (`userwallets.blocked`) */
|
||||
/** Заблокированный остаток (`userwallets.blocked`). Для marketplace-кошельков всегда `0` — резерв выражается через `.available` кошелька w.mkt.order. Поле остаётся для wallet/withdraw flow и legacy данных. */
|
||||
blocked: string,
|
||||
/** Человекочитаемое название (из cooptypes LEDGER2_WALLET_REGISTRY) */
|
||||
human_name: string,
|
||||
@@ -37828,7 +38020,7 @@ export type GraphQLTypes = {
|
||||
kind: string,
|
||||
/** UX-метка кошелька в формате `<тип взноса> | <программа>` (например «Паевой | Цифровой Кошелёк», «Членский | Стол Заказов») */
|
||||
label: string,
|
||||
/** eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.member) */
|
||||
/** eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.order) */
|
||||
name: string,
|
||||
/** program_id: 1 — Цифровой Кошелёк, 2 — Стол Заказов */
|
||||
program_id: number,
|
||||
@@ -38469,10 +38661,18 @@ export type GraphQLTypes = {
|
||||
|
||||
Требуемые роли: chairman, member, user. */
|
||||
chatcoopCreateCalendarIcsSubscription: GraphQLTypes["ChatCoopCalendarIcsUrlResponse"],
|
||||
/** Создать комнату с секретарём (публичную или приватную); секретарь подключается сразу
|
||||
|
||||
Требуемые роли: chairman, member. */
|
||||
chatcoopCreateSecretaryRoom: GraphQLTypes["ChatcoopSecretaryRoom"],
|
||||
/** Удалить событие календаря
|
||||
|
||||
Требуемые роли: chairman, member. */
|
||||
chatcoopDeleteCalendarEvent: boolean,
|
||||
/** Удалить комнату секретаря: вывести секретаря и снять комнату с синхронизации (возвращает идентификатор комнаты в реестре)
|
||||
|
||||
Требуемые роли: chairman, member. */
|
||||
chatcoopRemoveSecretaryRoom: string,
|
||||
/** Обновить событие календаря
|
||||
|
||||
Требуемые роли: chairman, member. */
|
||||
@@ -38873,6 +39073,8 @@ export type GraphQLTypes = {
|
||||
walmoveWallets: GraphQLTypes["Ledger2AdjustmentResult"],
|
||||
['...on Mutation']: Omit<GraphQLTypes["Mutation"], "...on Mutation">
|
||||
};
|
||||
/** Тип комнаты вне проекта: пайщики, совет, комната секретаря */
|
||||
["NonProjectRoomKind"]: NonProjectRoomKind;
|
||||
["NotificationWorkflowRecipientInput"]: {
|
||||
/** Username получателя */
|
||||
username: string
|
||||
@@ -39851,8 +40053,6 @@ export type GraphQLTypes = {
|
||||
available: string,
|
||||
/** Номер блока последнего обновления */
|
||||
blockNum?: number | undefined | null,
|
||||
/** Заблокированный баланс (формат: "100.0000 RUB") */
|
||||
blocked: string,
|
||||
/** Имя кооператива */
|
||||
coopname: string,
|
||||
/** Уникальный идентификатор кошелька в блокчейне */
|
||||
@@ -40186,10 +40386,18 @@ export type GraphQLTypes = {
|
||||
|
||||
Требуемые роли: chairman, member. */
|
||||
chatcoopListCalendarRooms: Array<GraphQLTypes["ChatCoopCalendarRoomOption"]>,
|
||||
/** Комнаты Matrix вне проектов Capital (пайщики/совет/секретарь) — для синхронизации в blago
|
||||
|
||||
Требуемые роли: chairman, member, user. */
|
||||
chatcoopListNonProjectCommunicationRooms: Array<GraphQLTypes["ChatcoopNonProjectCommunicationRoom"]>,
|
||||
/** Комнаты Matrix, привязанные к проекту Capital (реестр ChatCoop)
|
||||
|
||||
Требуемые роли: chairman, member, user. */
|
||||
chatcoopListProjectCommunicationRooms: Array<GraphQLTypes["ChatcoopProjectCommunicationRoom"]>,
|
||||
/** Все комнаты реестра ChatCoop (системные/проектные — read-only, комнаты секретаря — удаляемые)
|
||||
|
||||
Требуемые роли: chairman, member. */
|
||||
chatcoopListSecretaryRooms: Array<GraphQLTypes["ChatcoopSecretaryRoom"]>,
|
||||
/** UTC-даты (YYYY-MM-DD), в которых есть сообщения новее afterOriginServerTsExclusive, для комнаты Matrix
|
||||
|
||||
Требуемые роли: chairman, member, user. */
|
||||
@@ -40456,7 +40664,7 @@ export type GraphQLTypes = {
|
||||
marketplaceListCategories: Array<GraphQLTypes["MarketplaceCategory"]>,
|
||||
/** Постраничный список сводных заявок поставщика — для стола поставщика и для прослеживания состояния заказов. */
|
||||
marketplaceListConsolidatedRequests: GraphQLTypes["MarketplaceConsolidatedRequestPaginationResult"],
|
||||
/** Список наклеек инвентаря КУ: admin/совет видят весь склад кооператива, оператор — только свой участок. */
|
||||
/** Список наклеек инвентаря КУ — для admin-стола склада и операторских разделов. */
|
||||
marketplaceListInventory: Array<GraphQLTypes["MarketplaceInventoryItem"]>,
|
||||
/** Список заказов на кооперативном участке, ожидающих открытия и финальной подписи выдачи (для оператора кооперативного участка). */
|
||||
marketplaceListIssuancesByBraname: Array<GraphQLTypes["MarketplaceOrder"]>,
|
||||
@@ -40492,7 +40700,7 @@ export type GraphQLTypes = {
|
||||
marketplaceListWhitelist: Array<GraphQLTypes["MarketplaceWhitelistEntry"]>,
|
||||
/** Лента всех проектов списания кооператива с фильтром по статусу. */
|
||||
marketplaceListWriteoffProposals: GraphQLTypes["PaginatedMarketplaceWriteoffProposals"],
|
||||
/** Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.member */
|
||||
/** Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.order */
|
||||
marketplaceMemberWallet: GraphQLTypes["MarketplaceMemberWallet"],
|
||||
/** Состояние онбординга пайщика в Столе заказов: показывать ли gate или пропускать на стол */
|
||||
marketplaceOnboardingState: GraphQLTypes["MarketplaceOnboardingState"],
|
||||
@@ -40727,6 +40935,10 @@ export type GraphQLTypes = {
|
||||
["RemoveAvailableCategoryTypesInput"]: {
|
||||
/** Типы товаров для удаления */
|
||||
categoryTypes: Array<GraphQLTypes["CategoryTypeInput"]>
|
||||
};
|
||||
["RemoveSecretaryRoomInput"]: {
|
||||
/** Идентификатор комнаты в реестре, которую нужно удалить */
|
||||
id: string
|
||||
};
|
||||
["ReplaceAvailableItemsInput"]: {
|
||||
/** ID категорий (целые категории) */
|
||||
@@ -42348,6 +42560,13 @@ export enum LogEventType {
|
||||
VOTING_COMPLETED = "VOTING_COMPLETED",
|
||||
VOTING_STARTED = "VOTING_STARTED"
|
||||
}
|
||||
/** Тип комнаты: пайщики, совет, проект Capital, комната секретаря */
|
||||
export enum ManagedRoomKind {
|
||||
CAPITAL_PROJECT = "CAPITAL_PROJECT",
|
||||
COUNCIL = "COUNCIL",
|
||||
MEMBERS = "MEMBERS",
|
||||
SECRETARY = "SECRETARY"
|
||||
}
|
||||
/** Статус АПП приёмки на КУ. */
|
||||
export enum MarketplaceAplReceptionStatus {
|
||||
ACCEPTED_TO_COOP = "ACCEPTED_TO_COOP",
|
||||
@@ -42477,6 +42696,12 @@ export enum MarketplaceWriteoffProposalTrigger {
|
||||
CRON = "CRON",
|
||||
MANUAL = "MANUAL"
|
||||
}
|
||||
/** Тип комнаты вне проекта: пайщики, совет, комната секретаря */
|
||||
export enum NonProjectRoomKind {
|
||||
COUNCIL = "COUNCIL",
|
||||
MEMBERS = "MEMBERS",
|
||||
SECRETARY = "SECRETARY"
|
||||
}
|
||||
/** Тип юридического лица */
|
||||
export enum OrganizationType {
|
||||
AO = "AO",
|
||||
@@ -42788,6 +43013,7 @@ type ZEUS_VARIABLES = {
|
||||
["CreateProjectInvestInput"]: ValueTypes["CreateProjectInvestInput"];
|
||||
["CreateProjectPropertyInput"]: ValueTypes["CreateProjectPropertyInput"];
|
||||
["CreateRequestInput"]: ValueTypes["CreateRequestInput"];
|
||||
["CreateSecretaryRoomInput"]: ValueTypes["CreateSecretaryRoomInput"];
|
||||
["CreateSovietIndividualDataInput"]: ValueTypes["CreateSovietIndividualDataInput"];
|
||||
["CreateStoryInput"]: ValueTypes["CreateStoryInput"];
|
||||
["CreateSubscriptionInput"]: ValueTypes["CreateSubscriptionInput"];
|
||||
@@ -42895,6 +43121,7 @@ type ZEUS_VARIABLES = {
|
||||
["LoginInput"]: ValueTypes["LoginInput"];
|
||||
["LogoutInput"]: ValueTypes["LogoutInput"];
|
||||
["MakeClearanceInput"]: ValueTypes["MakeClearanceInput"];
|
||||
["ManagedRoomKind"]: ValueTypes["ManagedRoomKind"];
|
||||
["MarkReportPeriodInput"]: ValueTypes["MarkReportPeriodInput"];
|
||||
["MarketplaceAcceptConsolidatedRequestInput"]: ValueTypes["MarketplaceAcceptConsolidatedRequestInput"];
|
||||
["MarketplaceAcceptCppInput"]: ValueTypes["MarketplaceAcceptCppInput"];
|
||||
@@ -42941,6 +43168,8 @@ type ZEUS_VARIABLES = {
|
||||
["MarketplaceListIssuancesByBranameInput"]: ValueTypes["MarketplaceListIssuancesByBranameInput"];
|
||||
["MarketplaceListMyOffersInput"]: ValueTypes["MarketplaceListMyOffersInput"];
|
||||
["MarketplaceListOrdersInput"]: ValueTypes["MarketplaceListOrdersInput"];
|
||||
["MarketplaceListOutgoingPaymentsAsSupplierFilterInput"]: ValueTypes["MarketplaceListOutgoingPaymentsAsSupplierFilterInput"];
|
||||
["MarketplaceListOutgoingPaymentsFilterInput"]: ValueTypes["MarketplaceListOutgoingPaymentsFilterInput"];
|
||||
["MarketplaceListPendingOffersInput"]: ValueTypes["MarketplaceListPendingOffersInput"];
|
||||
["MarketplaceListReturnClaimsByBranameInput"]: ValueTypes["MarketplaceListReturnClaimsByBranameInput"];
|
||||
["MarketplaceListShipmentsByBranameInput"]: ValueTypes["MarketplaceListShipmentsByBranameInput"];
|
||||
@@ -42979,6 +43208,7 @@ type ZEUS_VARIABLES = {
|
||||
["MarketplaceWriteoffProposalTrigger"]: ValueTypes["MarketplaceWriteoffProposalTrigger"];
|
||||
["MarketplaceWriteoffStatementSignablePayloadInput"]: ValueTypes["MarketplaceWriteoffStatementSignablePayloadInput"];
|
||||
["MoveCapitalIssueToComponentInput"]: ValueTypes["MoveCapitalIssueToComponentInput"];
|
||||
["NonProjectRoomKind"]: ValueTypes["NonProjectRoomKind"];
|
||||
["NotificationWorkflowRecipientInput"]: ValueTypes["NotificationWorkflowRecipientInput"];
|
||||
["NotifyOnAnnualGeneralMeetInput"]: ValueTypes["NotifyOnAnnualGeneralMeetInput"];
|
||||
["OpenProjectInput"]: ValueTypes["OpenProjectInput"];
|
||||
@@ -43025,6 +43255,7 @@ type ZEUS_VARIABLES = {
|
||||
["RegisterParticipantInput"]: ValueTypes["RegisterParticipantInput"];
|
||||
["RemoveAvailableCategoriesInput"]: ValueTypes["RemoveAvailableCategoriesInput"];
|
||||
["RemoveAvailableCategoryTypesInput"]: ValueTypes["RemoveAvailableCategoryTypesInput"];
|
||||
["RemoveSecretaryRoomInput"]: ValueTypes["RemoveSecretaryRoomInput"];
|
||||
["ReplaceAvailableItemsInput"]: ValueTypes["ReplaceAvailableItemsInput"];
|
||||
["ReportHistoryFilterInput"]: ValueTypes["ReportHistoryFilterInput"];
|
||||
["ReportPreviewInput"]: ValueTypes["ReportPreviewInput"];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "cooptypes",
|
||||
"type": "module",
|
||||
"version": "2026.5.23-4",
|
||||
"version": "2026.5.27-2",
|
||||
"description": "Shared TypeScript типы и интерфейсы экосистемы кооперативов",
|
||||
"author": "Alex Ant <dacom.dark.sun@gmail.com>",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
*
|
||||
* Идентификаторы кошельков (`wallet_from`/`wallet_to`) — eosio::name с
|
||||
* префиксом `w.<contract>.<waltype>` (см. `./wallets.ts`). Пустая строка
|
||||
* (`""`) — sentinel «кошелёк вне системы» для ISSUE, BURN и BURN_BLOCKED.
|
||||
* (`""`) — sentinel «кошелёк вне системы» для ISSUE и BURN.
|
||||
*/
|
||||
import type { IName } from '../interfaces/ledger2'
|
||||
|
||||
export type WalletOp = 'ISSUE' | 'TRANSFER' | 'BLOCK' | 'UNBLOCK' | 'BURN' | 'BURN_BLOCKED' | 'NONE'
|
||||
export type WalletOp = 'ISSUE' | 'TRANSFER' | 'BURN' | 'NONE'
|
||||
|
||||
export interface OperationMeta {
|
||||
/** Машинный идентификатор — eosio::name в контракте. */
|
||||
@@ -34,7 +34,7 @@ export interface OperationMeta {
|
||||
wallet_op: WalletOp | null
|
||||
/** Кошелёк-источник (null для ISSUE и для adjustment-операций). */
|
||||
wallet_from: IName | null
|
||||
/** Кошелёк-приёмник (null для BLOCK/UNBLOCK/BURN/BURN_BLOCKED и для adjustment-операций). */
|
||||
/** Кошелёк-приёмник (null для BURN и для adjustment-операций). */
|
||||
wallet_to: IName | null
|
||||
/** Код счёта Дт (null без бухпроводки, ADR-003: ⇔ credit == null). */
|
||||
debit: number | null
|
||||
@@ -74,17 +74,17 @@ export const LEDGER2_OPERATION_REGISTRY: readonly OperationMeta[] = [
|
||||
human_name: 'Внесение пайщиком паевого взноса' },
|
||||
|
||||
{ code: 'o.wal.wthreq', process_type: 'p.wal.wthdrw', contract: 'wallet',
|
||||
name: 'REQUEST_WITHDRAW', wallet_op: 'BLOCK', wallet_from: 'w.wal.share', wallet_to: null,
|
||||
name: 'REQUEST_WITHDRAW', wallet_op: 'TRANSFER', wallet_from: 'w.wal.share', wallet_to: 'w.wal.wpend',
|
||||
debit: null, credit: null,
|
||||
human_name: 'Блокировка паевого под запрос на возврат' },
|
||||
human_name: 'Резервирование паевого под запрос на возврат' },
|
||||
|
||||
{ code: 'o.wal.wthdec', process_type: 'p.wal.wthdrw', contract: 'wallet',
|
||||
name: 'DECLINE_WITHDRAW', wallet_op: 'UNBLOCK', wallet_from: 'w.wal.share', wallet_to: null,
|
||||
name: 'DECLINE_WITHDRAW', wallet_op: 'TRANSFER', wallet_from: 'w.wal.wpend', wallet_to: 'w.wal.share',
|
||||
debit: null, credit: null,
|
||||
human_name: 'Разблокировка паевого после отклонения запроса на возврат' },
|
||||
human_name: 'Снятие резерва паевого после отклонения запроса на возврат' },
|
||||
|
||||
{ code: 'o.wal.wthcpl', process_type: 'p.wal.wthdrw', contract: 'wallet',
|
||||
name: 'COMPLETE_WITHDRAW', wallet_op: 'BURN_BLOCKED', wallet_from: 'w.wal.share', wallet_to: null,
|
||||
name: 'COMPLETE_WITHDRAW', wallet_op: 'BURN', wallet_from: 'w.wal.wpend', wallet_to: null,
|
||||
debit: 80, credit: 51,
|
||||
human_name: 'Возврат паевого взноса пайщику' },
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ export const LEDGER2_WALLET_REGISTRY: readonly WalletMeta[] = [
|
||||
{ name: "w.cap.gen", human_name: "ЦПП «Генератор» — единый кошелёк программы", kind: "COOPERATIVE" },
|
||||
{ name: "w.reg.entry", human_name: "Вступительные взносы", kind: "COOPERATIVE" },
|
||||
{ name: "w.wal.wthdrw", human_name: "Возвраты паевых взносов пайщикам (deprecated, не используется в новых операциях)", kind: "COOPERATIVE" },
|
||||
{ name: "w.wal.wpend", human_name: "Резерв паевого под заявку на возврат", kind: "COOPERATIVE" },
|
||||
{ name: "w.sov.infra", human_name: "Членские взносы за инфраструктуру кооп. платформы", kind: "COOPERATIVE" },
|
||||
{ name: "w.sov.delgte", human_name: "Делегатские членские взносы", kind: "COOPERATIVE" },
|
||||
{ name: "w.sov.expns", human_name: "Хозяйственные расходы из числа целевого финансирования", kind: "COOPERATIVE" },
|
||||
|
||||
+72
-67
@@ -1,72 +1,77 @@
|
||||
<template lang="pug">
|
||||
q-btn(
|
||||
color='accent',
|
||||
@click.stop='showDialog = true',
|
||||
:loading='loading',
|
||||
label="Коммит",
|
||||
icon="add",
|
||||
:size='mini ? "sm" : "md"',
|
||||
:dense="isMobile"
|
||||
:disabled='disabled'
|
||||
)
|
||||
div
|
||||
q-btn(
|
||||
color='accent',
|
||||
@click.stop='showDialog = true',
|
||||
:loading='loading',
|
||||
label="Коммит",
|
||||
icon="add",
|
||||
:size='mini ? "sm" : "md"',
|
||||
:dense="isMobile"
|
||||
:disabled='disabled'
|
||||
)
|
||||
|
||||
q-dialog(v-model='showDialog', @hide='clear')
|
||||
ModalBase(:title='"Зафиксировать взнос"')
|
||||
Form.create-commit-form(
|
||||
:class='isMobile ? "q-pb-md" : "q-pb-lg"',
|
||||
:handler-submit='handleCreateCommit',
|
||||
:is-submitting='isSubmitting',
|
||||
:button-submit-txt='"Зафиксировать"',
|
||||
:button-cancel-txt='"Отмена"',
|
||||
@cancel='clear'
|
||||
)
|
||||
.create-commit-dialog-content.column.q-gutter-y-sm
|
||||
section.create-commit-block
|
||||
.column.q-gutter-y-sm
|
||||
.create-commit-kv
|
||||
.text-caption.text-grey-7 Компонент проекта
|
||||
.text-body1.text-weight-medium.text-primary {{ projectLabel }}
|
||||
.create-commit-kv
|
||||
.text-caption.text-grey-7 Время
|
||||
.text-body1.text-weight-medium
|
||||
| {{ formatHours(formData.creator_hours) }}
|
||||
.create-commit-kv(v-if='commitCostFormatted')
|
||||
.text-caption.text-grey-7 Себестоимость
|
||||
.text-body1.text-weight-medium.text-primary {{ commitCostFormatted }}
|
||||
.text-caption.text-grey-6.q-pl-sm {{ commitCostCaption }}
|
||||
.create-commit-kv(v-else-if='contributorStore.self')
|
||||
.text-caption.text-grey-7 Себестоимость
|
||||
.text-body2.text-grey-6 Ставка в час не задана — сумму посчитать нельзя.
|
||||
template(v-if='commitBreakdown?.tail && commitBreakdown.tail > 1e-6')
|
||||
q-separator(color='grey-5', style='opacity: 0.35')
|
||||
.text-caption.text-grey-7
|
||||
| После фиксации в накоплении останется {{ formatHours(commitBreakdown.tail) }}.
|
||||
BaseDialog(
|
||||
v-model='showDialog',
|
||||
title='Зафиксировать взнос',
|
||||
size='md',
|
||||
@update:model-value='(v) => !v && clear()'
|
||||
)
|
||||
Form.create-commit-form(
|
||||
:class='isMobile ? "q-pb-md" : "q-pb-lg"',
|
||||
:handler-submit='handleCreateCommit',
|
||||
:is-submitting='isSubmitting',
|
||||
:button-submit-txt='"Зафиксировать"',
|
||||
:button-cancel-txt='"Отмена"',
|
||||
@cancel='clear'
|
||||
)
|
||||
.create-commit-dialog-content.column.q-gutter-y-sm
|
||||
section.create-commit-block
|
||||
.column.q-gutter-y-sm
|
||||
.create-commit-kv
|
||||
.text-caption.text-grey-7 Компонент проекта
|
||||
.text-body1.text-weight-medium.text-primary {{ projectLabel }}
|
||||
.create-commit-kv
|
||||
.text-caption.text-grey-7 Время
|
||||
.text-body1.text-weight-medium
|
||||
| {{ formatHours(formData.creator_hours) }}
|
||||
.create-commit-kv(v-if='commitCostFormatted')
|
||||
.text-caption.text-grey-7 Себестоимость
|
||||
.text-body1.text-weight-medium.text-primary {{ commitCostFormatted }}
|
||||
.text-caption.text-grey-6.q-pl-sm {{ commitCostCaption }}
|
||||
.create-commit-kv(v-else-if='contributorStore.self')
|
||||
.text-caption.text-grey-7 Себестоимость
|
||||
.text-body2.text-grey-6 Ставка в час не задана — сумму посчитать нельзя.
|
||||
template(v-if='commitBreakdown?.tail && commitBreakdown.tail > 1e-6')
|
||||
q-separator(color='grey-5', style='opacity: 0.35')
|
||||
.text-caption.text-grey-7
|
||||
| После фиксации в накоплении останется {{ formatHours(commitBreakdown.tail) }}.
|
||||
|
||||
section.create-commit-block
|
||||
.column.q-gutter-y-md
|
||||
.row.items-center.q-gutter-x-sm
|
||||
span.text-caption.text-grey-7 Удовлетворение результатом
|
||||
span.text-body2.text-weight-medium.text-accent
|
||||
| {{ satisfactionLabel }}
|
||||
q-rating(
|
||||
v-model='formData.satisfaction_stars',
|
||||
:max='5',
|
||||
:size='isMobile ? "lg" : "md"',
|
||||
color-selected='accent'
|
||||
)
|
||||
q-input(
|
||||
v-model='formData.review_text',
|
||||
type='textarea',
|
||||
autogrow,
|
||||
color="accent"
|
||||
filled,
|
||||
stack-label,
|
||||
label='Отзыв (необязательно)',
|
||||
placeholder='Например, что сработало хорошо или что стоит учесть дальше…',
|
||||
:maxlength='8000',
|
||||
counter,
|
||||
:input-style='{ minHeight: "96px" }'
|
||||
)
|
||||
section.create-commit-block
|
||||
.column.q-gutter-y-md
|
||||
.row.items-center.q-gutter-x-sm
|
||||
span.text-caption.text-grey-7 Удовлетворение результатом
|
||||
span.text-body2.text-weight-medium.text-accent
|
||||
| {{ satisfactionLabel }}
|
||||
q-rating(
|
||||
v-model='formData.satisfaction_stars',
|
||||
:max='5',
|
||||
:size='isMobile ? "lg" : "md"',
|
||||
color-selected='accent'
|
||||
)
|
||||
q-input(
|
||||
v-model='formData.review_text',
|
||||
type='textarea',
|
||||
autogrow,
|
||||
color="accent"
|
||||
filled,
|
||||
stack-label,
|
||||
label='Отзыв (необязательно)',
|
||||
placeholder='Например, что сработало хорошо или что стоит учесть дальше…',
|
||||
:maxlength='8000',
|
||||
counter,
|
||||
:input-style='{ minHeight: "96px" }'
|
||||
)
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -75,7 +80,7 @@ import { useCreateCommit, type ICreateCommitInput } from '../model';
|
||||
import { useSystemStore } from 'src/entities/System/model';
|
||||
import { useSessionStore } from 'src/entities/Session';
|
||||
import { FailAlert, SuccessAlert } from 'src/shared/api/alerts';
|
||||
import { ModalBase } from 'src/shared/ui/ModalBase';
|
||||
import { BaseDialog } from 'src/shared/ui/base/BaseDialog';
|
||||
import { Form } from 'src/shared/ui/Form';
|
||||
import { useWindowSize } from 'src/shared/hooks';
|
||||
import { formatHours } from 'src/shared/lib/utils';
|
||||
|
||||
+32
-27
@@ -1,32 +1,37 @@
|
||||
<template lang="pug">
|
||||
q-btn(
|
||||
color='negative',
|
||||
@click='showDialog = true',
|
||||
:loading='loading',
|
||||
label="Отклонить",
|
||||
:size='mini ? "sm" : "md"',
|
||||
:dense="isMobile"
|
||||
)
|
||||
div
|
||||
q-btn(
|
||||
color='negative',
|
||||
@click='showDialog = true',
|
||||
:loading='loading',
|
||||
label="Отклонить",
|
||||
:size='mini ? "sm" : "md"',
|
||||
:dense="isMobile"
|
||||
)
|
||||
|
||||
q-dialog(v-model='showDialog', @hide='clear')
|
||||
ModalBase(:title='"Отклонить коммит"')
|
||||
Form.q-pa-md(
|
||||
:handler-submit='handleDeclineCommit',
|
||||
:is-submitting='isSubmitting',
|
||||
:button-submit-txt='"Отклонить"',
|
||||
:button-cancel-txt='"Отмена"',
|
||||
@cancel='clear'
|
||||
BaseDialog(
|
||||
v-model='showDialog',
|
||||
title='Отклонить коммит',
|
||||
size='md',
|
||||
@update:model-value='(v) => !v && clear()'
|
||||
)
|
||||
Form.q-pa-md(
|
||||
:handler-submit='handleDeclineCommit',
|
||||
:is-submitting='isSubmitting',
|
||||
:button-submit-txt='"Отклонить"',
|
||||
:button-cancel-txt='"Отмена"',
|
||||
@cancel='clear'
|
||||
)
|
||||
q-input(
|
||||
v-model='reason',
|
||||
outline
|
||||
label='Причина отклонения',
|
||||
:rules='[(val) => !!val || "Причина обязательна"]',
|
||||
autocomplete='off'
|
||||
placeholder='Укажите причину отклонения...'
|
||||
type='textarea'
|
||||
rows='3'
|
||||
)
|
||||
q-input(
|
||||
v-model='reason',
|
||||
outline
|
||||
label='Причина отклонения',
|
||||
:rules='[(val) => !!val || "Причина обязательна"]',
|
||||
autocomplete='off'
|
||||
placeholder='Укажите причину отклонения...'
|
||||
type='textarea'
|
||||
rows='3'
|
||||
)
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -34,7 +39,7 @@ import { ref } from 'vue';
|
||||
import { useDeclineCommit } from '../model';
|
||||
import { useSystemStore } from 'src/entities/System/model';
|
||||
import { FailAlert, SuccessAlert } from 'src/shared/api/alerts';
|
||||
import { ModalBase } from 'src/shared/ui/ModalBase';
|
||||
import { BaseDialog } from 'src/shared/ui/base/BaseDialog';
|
||||
import { Form } from 'src/shared/ui/Form';
|
||||
import { useWindowSize } from 'src/shared/hooks';
|
||||
|
||||
|
||||
+74
-60
@@ -1,82 +1,96 @@
|
||||
<template lang="pug">
|
||||
q-btn(
|
||||
color='primary',
|
||||
@click='showDialog = true',
|
||||
:loading='loading',
|
||||
icon='add',
|
||||
label='Добавить'
|
||||
)
|
||||
q-dialog(v-model='showDialog', @hide='clear')
|
||||
ModalBase(:title='"Импорт участника"')
|
||||
Form.q-pa-md(
|
||||
:handler-submit='handleImportContributor',
|
||||
:is-submitting='isSubmitting',
|
||||
:button-submit-txt='"Импортировать"',
|
||||
:button-cancel-txt='"Отмена"',
|
||||
@cancel='clear'
|
||||
style="width: 600px; max-width: 100% !important;"
|
||||
div
|
||||
//- Canon header-кнопка: на мобильном — иконка-only + tooltip.
|
||||
q-btn(
|
||||
@click='showDialog = true',
|
||||
:color='isMobile ? "accent" : "primary"',
|
||||
:flat='isMobile',
|
||||
:dense='isMobile',
|
||||
:size='isMobile ? "sm" : undefined',
|
||||
:loading='loading',
|
||||
icon='add',
|
||||
:label='isMobile ? undefined : "Добавить"',
|
||||
no-wrap
|
||||
)
|
||||
q-tooltip(v-if='isMobile') Добавить участника
|
||||
|
||||
BaseDialog(
|
||||
v-model='showDialog',
|
||||
title='Импорт участника',
|
||||
size='md',
|
||||
@update:model-value='(v) => !v && clear()'
|
||||
)
|
||||
Form.q-pa-md(
|
||||
:handler-submit='handleImportContributor',
|
||||
:is-submitting='isSubmitting',
|
||||
:button-submit-txt='"Импортировать"',
|
||||
:button-cancel-txt='"Отмена"',
|
||||
@cancel='clear'
|
||||
style="width: 600px; max-width: 100% !important;"
|
||||
)
|
||||
q-input(
|
||||
v-model='formData.username'
|
||||
label='Имя пользователя'
|
||||
:rules='[(val) => !!val || "Имя пользователя обязательно"]'
|
||||
outlined
|
||||
)
|
||||
q-input(
|
||||
v-model='formData.username'
|
||||
label='Имя пользователя'
|
||||
:rules='[(val) => !!val || "Имя пользователя обязательно"]'
|
||||
outlined
|
||||
)
|
||||
|
||||
q-input(
|
||||
v-model='formData.contribution_amount'
|
||||
label='Сумма взноса'
|
||||
:rules='[(val) => !!val || "Сумма взноса обязательна"]'
|
||||
outlined
|
||||
)
|
||||
template(#append)
|
||||
span.text-grey-7 {{ info.symbols.root_govern_symbol }}
|
||||
q-input(
|
||||
v-model='formData.contribution_amount'
|
||||
label='Сумма взноса'
|
||||
:rules='[(val) => !!val || "Сумма взноса обязательна"]'
|
||||
outlined
|
||||
)
|
||||
template(#append)
|
||||
span.text-grey-7 {{ info.symbols.root_govern_symbol }}
|
||||
|
||||
q-input(
|
||||
v-model='formData.contributor_contract_number'
|
||||
label='Номер договора'
|
||||
:rules='[(val) => !!val || "Номер договора обязателен"]'
|
||||
outlined
|
||||
)
|
||||
q-input(
|
||||
v-model='formData.contributor_contract_number'
|
||||
label='Номер договора'
|
||||
:rules='[(val) => !!val || "Номер договора обязателен"]'
|
||||
outlined
|
||||
)
|
||||
|
||||
q-input(
|
||||
v-model='formData.contributor_contract_created_at'
|
||||
label='Дата договора (ДД.ММ.ГГГГ)'
|
||||
:rules='[(val) => !!val || "Дата договора обязательна"]'
|
||||
outlined
|
||||
)
|
||||
q-input(
|
||||
v-model='formData.contributor_contract_created_at'
|
||||
label='Дата договора (ДД.ММ.ГГГГ)'
|
||||
:rules='[(val) => !!val || "Дата договора обязательна"]'
|
||||
outlined
|
||||
)
|
||||
|
||||
q-input(
|
||||
v-model='formData.blagorost_agreement_number'
|
||||
label='Номер соглашения Благорост'
|
||||
:rules='[(val) => !!val || "Номер соглашения Благорост обязателен"]'
|
||||
outlined
|
||||
)
|
||||
q-input(
|
||||
v-model='formData.blagorost_agreement_number'
|
||||
label='Номер соглашения Благорост'
|
||||
:rules='[(val) => !!val || "Номер соглашения Благорост обязателен"]'
|
||||
outlined
|
||||
)
|
||||
|
||||
q-input(
|
||||
v-model='formData.blagorost_agreement_created_at'
|
||||
label='Дата соглашения Благорост (ДД.ММ.ГГГГ)'
|
||||
:rules='[(val) => !!val || "Дата соглашения Благорост обязательна"]'
|
||||
outlined
|
||||
)
|
||||
q-input(
|
||||
v-model='formData.blagorost_agreement_created_at'
|
||||
label='Дата соглашения Благорост (ДД.ММ.ГГГГ)'
|
||||
:rules='[(val) => !!val || "Дата соглашения Благорост обязательна"]'
|
||||
outlined
|
||||
)
|
||||
|
||||
q-input(
|
||||
v-model='formData.memo'
|
||||
label='Примечание'
|
||||
outlined
|
||||
)
|
||||
q-input(
|
||||
v-model='formData.memo'
|
||||
label='Примечание'
|
||||
outlined
|
||||
)
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useImportContributor } from '../model';
|
||||
import { FailAlert, SuccessAlert } from 'src/shared/api/alerts';
|
||||
import { ModalBase } from 'src/shared/ui/ModalBase';
|
||||
import { BaseDialog } from 'src/shared/ui/base/BaseDialog';
|
||||
import { Form } from 'src/shared/ui/Form';
|
||||
import { useSystemStore } from 'src/entities/System/model';
|
||||
import { useWindowSize } from 'src/shared/hooks';
|
||||
|
||||
const { importContributor } = useImportContributor();
|
||||
const { info } = useSystemStore();
|
||||
const { isMobile } = useWindowSize();
|
||||
|
||||
const showDialog = ref(false);
|
||||
const isSubmitting = ref(false);
|
||||
|
||||
+31
-25
@@ -1,35 +1,41 @@
|
||||
<template lang="pug">
|
||||
q-btn(
|
||||
color='primary',
|
||||
:loading='isGenerating',
|
||||
@click='showDialog = true',
|
||||
label='Инвестировать в проект'
|
||||
)
|
||||
q-dialog(v-model='showDialog', @hide='clear')
|
||||
ModalBase(:title='"Инвестирование в проект"')
|
||||
Form.q-pa-sm(
|
||||
:handler-submit='handleInvest',
|
||||
:is-submitting='isGenerating',
|
||||
:button-cancel-txt='"Отменить"',
|
||||
:button-submit-txt='"Инвестировать"',
|
||||
@cancel='clear'
|
||||
div
|
||||
q-btn(
|
||||
color='primary',
|
||||
:loading='isGenerating',
|
||||
@click='showDialog = true',
|
||||
label='Инвестировать в проект'
|
||||
)
|
||||
|
||||
BaseDialog(
|
||||
v-model='showDialog',
|
||||
title='Инвестирование в проект',
|
||||
size='md',
|
||||
@update:model-value='(v) => !v && clear()'
|
||||
)
|
||||
Form.q-pa-sm(
|
||||
:handler-submit='handleInvest',
|
||||
:is-submitting='isGenerating',
|
||||
:button-cancel-txt='"Отменить"',
|
||||
:button-submit-txt='"Инвестировать"',
|
||||
@cancel='clear'
|
||||
)
|
||||
q-input(
|
||||
v-model='quantity',
|
||||
standout='bg-teal text-white',
|
||||
placeholder='Введите сумму инвестиций',
|
||||
type='number',
|
||||
:min='0',
|
||||
:rules='[(val) => val > 0 || "Сумма инвестиций должна быть положительной"]'
|
||||
)
|
||||
q-input(
|
||||
v-model='quantity',
|
||||
standout='bg-teal text-white',
|
||||
placeholder='Введите сумму инвестиций',
|
||||
type='number',
|
||||
:min='0',
|
||||
:rules='[(val) => val > 0 || "Сумма инвестиций должна быть положительной"]'
|
||||
)
|
||||
template(#append)
|
||||
span.text-overline {{ currency }}
|
||||
template(#append)
|
||||
span.text-overline {{ currency }}
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { Form } from 'src/shared/ui/Form';
|
||||
import { ModalBase } from 'src/shared/ui/ModalBase';
|
||||
import { BaseDialog } from 'src/shared/ui/base/BaseDialog';
|
||||
import { useCreateProjectInvest } from '../model';
|
||||
import { FailAlert, SuccessAlert } from 'src/shared/api/alerts';
|
||||
import { useSetPlan } from '../../../Project/SetPlan/model';
|
||||
|
||||
+35
-40
@@ -1,50 +1,45 @@
|
||||
<template lang="pug">
|
||||
// Рендерим как обычную кнопку, если передан флаг fab
|
||||
q-btn(
|
||||
v-if="fab"
|
||||
color="accent"
|
||||
:label="fabMainLabel"
|
||||
icon="attach_money"
|
||||
@click="openInvest"
|
||||
fab
|
||||
).bg-fab-accent-radial
|
||||
q-dialog(v-model="showDialog", @hide="showDialog = false")
|
||||
ModalBase(title="Инвестирование в проект" style="width: 400px; max-width: 100%;")
|
||||
q-card-section.row.items-center
|
||||
span Инвестиции в проект деньгами или результатами доступны только через их компоненты. Пожалуйста, выберите компонент из списка и инвестируйте во все или в некоторые из них по отдельности.
|
||||
div.q-mt-md
|
||||
q-btn(flat label="Понятно" @click="showDialog = false")
|
||||
q-btn(
|
||||
color="primary"
|
||||
label="Перейти к компонентам"
|
||||
@click="goToComponents"
|
||||
)
|
||||
div
|
||||
// Рендерим как обычную кнопку, если передан флаг fab
|
||||
q-btn.bg-fab-accent-radial(
|
||||
v-if="fab"
|
||||
color="accent"
|
||||
:label="fabMainLabel"
|
||||
icon="attach_money"
|
||||
@click="openInvest"
|
||||
fab
|
||||
)
|
||||
|
||||
// Иначе рендерим как экшен для FAB
|
||||
q-fab-action.bg-fab-accent-radial(
|
||||
v-else
|
||||
icon="attach_money"
|
||||
:label="fabActionLabel"
|
||||
@click="openInvest"
|
||||
text-color="white"
|
||||
)
|
||||
q-dialog(v-model="showDialog", @hide="showDialog = false")
|
||||
ModalBase(title="Инвестирование в проект" style="width: 400px; max-width: 100%;")
|
||||
q-card-section.row.items-center
|
||||
span Инвестиции в проект деньгами или результатами доступны только через их компоненты. Пожалуйста, выберите компонент из списка и инвестируйте во все или в некоторые из них по отдельности.
|
||||
div.q-mt-md
|
||||
q-btn(flat label="Понятно" @click="showDialog = false")
|
||||
q-btn(
|
||||
color="primary"
|
||||
label="Перейти к компонентам"
|
||||
@click="goToComponents"
|
||||
)
|
||||
// Иначе рендерим как экшен для FAB
|
||||
q-fab-action.bg-fab-accent-radial(
|
||||
v-else
|
||||
icon="attach_money"
|
||||
:label="fabActionLabel"
|
||||
@click="openInvest"
|
||||
text-color="white"
|
||||
)
|
||||
|
||||
BaseDialog(
|
||||
v-model="showDialog",
|
||||
title="Инвестирование в проект",
|
||||
size="sm",
|
||||
@update:model-value="(v) => !v && (showDialog = false)"
|
||||
)
|
||||
q-card-section.row.items-center
|
||||
span Инвестиции в проект деньгами или результатами доступны только через их компоненты. Пожалуйста, выберите компонент из списка и инвестируйте во все или в некоторые из них по отдельности.
|
||||
div.q-mt-md
|
||||
q-btn(flat label="Понятно" @click="showDialog = false")
|
||||
q-btn(
|
||||
color="primary"
|
||||
label="Перейти к компонентам"
|
||||
@click="goToComponents"
|
||||
)
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ModalBase } from 'src/shared/ui/ModalBase';
|
||||
import { BaseDialog } from 'src/shared/ui/base/BaseDialog';
|
||||
import type { IProject } from '../../../../../entities/Project/model';
|
||||
import { formatCapitalFabLabel } from 'app/extensions/capital/shared/lib';
|
||||
|
||||
|
||||
+28
-22
@@ -1,31 +1,37 @@
|
||||
<template lang="pug">
|
||||
q-btn(
|
||||
v-if='canDelete'
|
||||
flat
|
||||
size="sm"
|
||||
color='negative'
|
||||
class='full-width q-mt-md'
|
||||
:label='label'
|
||||
@click='showDialog = true'
|
||||
:loading='isSubmitting'
|
||||
)
|
||||
q-dialog(v-model='showDialog', @hide='close')
|
||||
ModalBase(title='Удаление задачи')
|
||||
Form.q-pa-sm(
|
||||
:handler-submit='confirmDelete'
|
||||
:is-submitting='isSubmitting'
|
||||
:button-cancel-txt='"Отменить"'
|
||||
:button-submit-txt='"Удалить"'
|
||||
@cancel='close'
|
||||
)
|
||||
div(style='max-width: 360px')
|
||||
p Вы уверены, что хотите удалить задачу?
|
||||
div
|
||||
q-btn(
|
||||
v-if='canDelete'
|
||||
flat
|
||||
size="sm"
|
||||
color='negative'
|
||||
class='full-width q-mt-md'
|
||||
:label='label'
|
||||
@click='showDialog = true'
|
||||
:loading='isSubmitting'
|
||||
)
|
||||
|
||||
BaseDialog(
|
||||
v-model='showDialog',
|
||||
title='Удаление задачи',
|
||||
size='sm',
|
||||
@update:model-value='(v) => !v && close()'
|
||||
)
|
||||
Form.q-pa-sm(
|
||||
:handler-submit='confirmDelete'
|
||||
:is-submitting='isSubmitting'
|
||||
:button-cancel-txt='"Отменить"'
|
||||
:button-submit-txt='"Удалить"'
|
||||
@cancel='close'
|
||||
)
|
||||
div(style='max-width: 360px')
|
||||
p Вы уверены, что хотите удалить задачу?
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { FailAlert, SuccessAlert } from 'src/shared/api';
|
||||
import { ModalBase } from 'src/shared/ui/ModalBase';
|
||||
import { BaseDialog } from 'src/shared/ui/base/BaseDialog';
|
||||
import { Form } from 'src/shared/ui/Form';
|
||||
import { useDeleteIssue } from '../model';
|
||||
|
||||
|
||||
+30
-25
@@ -12,37 +12,42 @@
|
||||
@click="openDialog"
|
||||
)
|
||||
.text-caption.q-mt-xs.text-grey-7(v-if="moveDisabledReason") {{ moveDisabledReason }}
|
||||
q-dialog(v-model="dialogOpen" @hide="resetDialog")
|
||||
ModalBase(title="Перенос задачи в другой компонент")
|
||||
Form.q-pa-sm(
|
||||
:handler-submit="confirmMove"
|
||||
:is-submitting="isSubmitting"
|
||||
button-cancel-txt="Отменить"
|
||||
button-submit-txt="Перенести"
|
||||
@cancel="close"
|
||||
)
|
||||
div(style="max-width: 420px")
|
||||
p.text-body2.q-mb-sm
|
||||
| Перенос допустим только в рамках одного проекта. Для перемещения доступны другие компоненты проекта в статусах «ожидание» и «активен». Время по задаче переносится вместе с ней.
|
||||
q-select(
|
||||
v-model="selectedHash"
|
||||
:options="targetOptions"
|
||||
option-value="project_hash"
|
||||
option-label="label"
|
||||
emit-value
|
||||
map-options
|
||||
outlined
|
||||
dense
|
||||
label="Компонент для переноса"
|
||||
:loading="optionsLoading"
|
||||
)
|
||||
|
||||
BaseDialog(
|
||||
v-model="dialogOpen",
|
||||
title="Перенос задачи в другой компонент",
|
||||
size="md",
|
||||
@update:model-value="(v) => !v && resetDialog()"
|
||||
)
|
||||
Form.q-pa-sm(
|
||||
:handler-submit="confirmMove"
|
||||
:is-submitting="isSubmitting"
|
||||
button-cancel-txt="Отменить"
|
||||
button-submit-txt="Перенести"
|
||||
@cancel="close"
|
||||
)
|
||||
div(style="max-width: 420px")
|
||||
p.text-body2.q-mb-sm
|
||||
| Перенос допустим только в рамках одного проекта. Для перемещения доступны другие компоненты проекта в статусах «ожидание» и «активен». Время по задаче переносится вместе с ней.
|
||||
q-select(
|
||||
v-model="selectedHash"
|
||||
:options="targetOptions"
|
||||
option-value="project_hash"
|
||||
option-label="label"
|
||||
emit-value
|
||||
map-options
|
||||
outlined
|
||||
dense
|
||||
label="Компонент для переноса"
|
||||
:loading="optionsLoading"
|
||||
)
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { FailAlert, SuccessAlert } from 'src/shared/api';
|
||||
import { EMPTY_HASH } from 'src/shared/lib/consts';
|
||||
import { ModalBase } from 'src/shared/ui/ModalBase';
|
||||
import { BaseDialog } from 'src/shared/ui/base/BaseDialog';
|
||||
import { Form } from 'src/shared/ui/Form';
|
||||
import { Zeus } from '@coopenomics/sdk';
|
||||
import { api as ProjectApi } from 'app/extensions/capital/entities/Project/api';
|
||||
|
||||
+35
-30
@@ -42,44 +42,49 @@ div
|
||||
@click="() => openDialog(step)"
|
||||
)
|
||||
|
||||
q-dialog(v-model="dialogOpen" persistent)
|
||||
ModalBase(title="Предложение повестки" @close="closeDialog" style="min-width: 640px")
|
||||
template(#default)
|
||||
Loader(
|
||||
v-if="generatingDocument"
|
||||
text="Генерация документа..."
|
||||
)
|
||||
q-card-section(v-else-if="currentStep && currentGeneratedDoc && !generatingDocument")
|
||||
div.row.items-center.q-gutter-xs.text-subtitle1.text-weight-medium
|
||||
q-icon(name="help_outline" size="18px" class="text-primary")
|
||||
span Вопрос на повестке
|
||||
div.q-mt-sm.q-pa-sm.text-body1.rounded-borders {{ currentStep.question }}
|
||||
BaseDialog(
|
||||
v-model="dialogOpen",
|
||||
title="Предложение повестки",
|
||||
size="lg",
|
||||
:close-on-backdrop="false",
|
||||
:close-on-escape="false",
|
||||
@update:model-value="(v) => !v && closeDialog()"
|
||||
)
|
||||
Loader(
|
||||
v-if="generatingDocument"
|
||||
text="Генерация документа..."
|
||||
)
|
||||
q-card-section(v-else-if="currentStep && currentGeneratedDoc && !generatingDocument")
|
||||
div.row.items-center.q-gutter-xs.text-subtitle1.text-weight-medium
|
||||
q-icon(name="help_outline" size="18px" class="text-primary")
|
||||
span Вопрос на повестке
|
||||
div.q-mt-sm.q-pa-sm.text-body1.rounded-borders {{ currentStep.question }}
|
||||
|
||||
q-separator.q-my-md
|
||||
q-separator.q-my-md
|
||||
|
||||
div.row.items-center.q-gutter-xs.text-subtitle1.text-weight-medium
|
||||
q-icon(name="gavel" size="18px" class="text-primary")
|
||||
span Проект решения
|
||||
div.q-mt-sm.q-pa-sm.rounded-borders
|
||||
div(v-if="currentStep.decisionPrefix") {{ currentStep.decisionPrefix }}
|
||||
DocumentHtmlReader(:html="currentGeneratedDoc.html" :sanitize="false")
|
||||
div.q-mt-sm.text-caption.text-grey-6
|
||||
strong {{ currentGeneratedDoc.full_title }}
|
||||
div.row.items-center.q-gutter-xs.text-subtitle1.text-weight-medium
|
||||
q-icon(name="gavel" size="18px" class="text-primary")
|
||||
span Проект решения
|
||||
div.q-mt-sm.q-pa-sm.rounded-borders
|
||||
div(v-if="currentStep.decisionPrefix") {{ currentStep.decisionPrefix }}
|
||||
DocumentHtmlReader(:html="currentGeneratedDoc.html" :sanitize="false")
|
||||
div.q-mt-sm.text-caption.text-grey-6
|
||||
strong {{ currentGeneratedDoc.full_title }}
|
||||
|
||||
div.q-pb-lg(v-if="currentGeneratedDoc && !generatingDocument")
|
||||
q-btn(flat label="Отмена" @click="closeDialog" :disable="submitting")
|
||||
q-btn(
|
||||
color="primary"
|
||||
label="Объявить"
|
||||
:loading="submitting"
|
||||
@click="submitStep"
|
||||
)
|
||||
div.q-pb-lg(v-if="currentGeneratedDoc && !generatingDocument")
|
||||
q-btn(flat label="Отмена" @click="closeDialog" :disable="submitting")
|
||||
q-btn(
|
||||
color="primary"
|
||||
label="Объявить"
|
||||
:loading="submitting"
|
||||
@click="submitStep"
|
||||
)
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { DocumentHtmlReader } from 'src/shared/ui/DocumentHtmlReader';
|
||||
import { ModalBase } from 'src/shared/ui/ModalBase';
|
||||
import { BaseDialog } from 'src/shared/ui/base/BaseDialog';
|
||||
import { WindowLoader, Loader } from 'src/shared/ui/Loader';
|
||||
import type { ICouncilOnboardingStep } from 'src/shared/ui/CouncilOnboarding';
|
||||
import { useCapitalOnboarding } from '../model';
|
||||
|
||||
+33
-27
@@ -1,37 +1,43 @@
|
||||
<template lang="pug">
|
||||
q-btn(
|
||||
color='primary'
|
||||
flat
|
||||
class='q-mt-sm full-width'
|
||||
:loading='isGenerating'
|
||||
@click='showDialog = true'
|
||||
label='Инвестировать'
|
||||
)
|
||||
q-dialog(v-model='showDialog', @hide='clear')
|
||||
ModalBase(:title='"Инвестирование в программу"')
|
||||
Form.q-pa-sm(
|
||||
:handler-submit='handleInvest',
|
||||
:is-submitting='isGenerating',
|
||||
:button-cancel-txt='"Отменить"',
|
||||
:button-submit-txt='"Инвестировать"',
|
||||
@cancel='clear'
|
||||
div
|
||||
q-btn(
|
||||
color='primary'
|
||||
flat
|
||||
class='q-mt-sm full-width'
|
||||
:loading='isGenerating'
|
||||
@click='showDialog = true'
|
||||
label='Инвестировать'
|
||||
)
|
||||
|
||||
BaseDialog(
|
||||
v-model='showDialog',
|
||||
title='Инвестирование в программу',
|
||||
size='md',
|
||||
@update:model-value='(v) => !v && clear()'
|
||||
)
|
||||
Form.q-pa-sm(
|
||||
:handler-submit='handleInvest',
|
||||
:is-submitting='isGenerating',
|
||||
:button-cancel-txt='"Отменить"',
|
||||
:button-submit-txt='"Инвестировать"',
|
||||
@cancel='clear'
|
||||
)
|
||||
q-input(
|
||||
v-model='quantity',
|
||||
standout='bg-teal text-white',
|
||||
placeholder='Введите сумму',
|
||||
type='number',
|
||||
:min='0',
|
||||
:rules='[(val) => val > 0 || "Сумма должна быть положительной"]'
|
||||
)
|
||||
q-input(
|
||||
v-model='quantity',
|
||||
standout='bg-teal text-white',
|
||||
placeholder='Введите сумму',
|
||||
type='number',
|
||||
:min='0',
|
||||
:rules='[(val) => val > 0 || "Сумма должна быть положительной"]'
|
||||
)
|
||||
template(#append)
|
||||
span.text-overline {{ currency }}
|
||||
template(#append)
|
||||
span.text-overline {{ currency }}
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { Form } from 'src/shared/ui/Form';
|
||||
import { ModalBase } from 'src/shared/ui/ModalBase';
|
||||
import { BaseDialog } from 'src/shared/ui/base/BaseDialog';
|
||||
import { useCreateProgramInvest } from '../model';
|
||||
import { FailAlert, SuccessAlert } from 'src/shared/api/alerts';
|
||||
import { useSystemStore } from 'src/entities/System/model';
|
||||
|
||||
+37
-35
@@ -1,41 +1,43 @@
|
||||
<template lang="pug">
|
||||
q-btn(
|
||||
size='sm',
|
||||
color='primary',
|
||||
@click='showDialog = true',
|
||||
:loading='isSubmitting',
|
||||
label='Добавить соавтора'
|
||||
)
|
||||
|
||||
q-dialog(v-model='showDialog', @hide='close')
|
||||
ModalBase(title='Добавить соавторов')
|
||||
Form.q-pa-sm(
|
||||
:handler-submit='handleAddAuthors',
|
||||
:is-submitting='isSubmitting',
|
||||
:button-cancel-txt='"Отменить"',
|
||||
:button-submit-txt='"Добавить"',
|
||||
@cancel='close'
|
||||
)
|
||||
div(style='max-width: 400px')
|
||||
.text-body2.q-mb-sm
|
||||
| ⚠️ После добавления соавторов их удаление будет невозможно. Для добавления соавторов они должны предварительно получить допуск на участие в проекте.
|
||||
|
||||
ContributorSelector(
|
||||
v-model='selectedAuthors'
|
||||
:multi-select='true'
|
||||
:dense='true'
|
||||
:disable='isSubmitting'
|
||||
:project-hash='props.project.project_hash'
|
||||
placeholder='Выберите соавторов...'
|
||||
label='Соавторы'
|
||||
class='authors-selector'
|
||||
)
|
||||
|
||||
.text-caption.text-grey-6.q-mt-sm(v-if='selectedAuthors.length > 0')
|
||||
| Выбрано соавторов: {{ selectedAuthors.length }}
|
||||
div
|
||||
q-btn(
|
||||
size='sm',
|
||||
color='primary',
|
||||
@click='showDialog = true',
|
||||
:loading='isSubmitting',
|
||||
label='Добавить соавтора'
|
||||
)
|
||||
|
||||
BaseDialog(
|
||||
v-model='showDialog',
|
||||
title='Добавить соавторов',
|
||||
size='md',
|
||||
@update:model-value='(v) => !v && close()'
|
||||
)
|
||||
Form.q-pa-sm(
|
||||
:handler-submit='handleAddAuthors',
|
||||
:is-submitting='isSubmitting',
|
||||
:button-cancel-txt='"Отменить"',
|
||||
:button-submit-txt='"Добавить"',
|
||||
@cancel='close'
|
||||
)
|
||||
div(style='max-width: 400px')
|
||||
.text-body2.q-mb-sm
|
||||
| ⚠️ После добавления соавторов их удаление будет невозможно. Для добавления соавторов они должны предварительно получить допуск на участие в проекте.
|
||||
|
||||
ContributorSelector(
|
||||
v-model='selectedAuthors'
|
||||
:multi-select='true'
|
||||
:dense='true'
|
||||
:disable='isSubmitting'
|
||||
:project-hash='props.project.project_hash'
|
||||
placeholder='Выберите соавторов...'
|
||||
label='Соавторы'
|
||||
class='authors-selector'
|
||||
)
|
||||
|
||||
.text-caption.text-grey-6.q-mt-sm(v-if='selectedAuthors.length > 0')
|
||||
| Выбрано соавторов: {{ selectedAuthors.length }}
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -43,7 +45,7 @@ import { ref, watch } from 'vue';
|
||||
import { useAddAuthor } from '../model';
|
||||
import { FailAlert, SuccessAlert } from 'src/shared/api/alerts';
|
||||
import { ContributorSelector } from '../../../../entities/Contributor';
|
||||
import { ModalBase } from 'src/shared/ui/ModalBase';
|
||||
import { BaseDialog } from 'src/shared/ui/base/BaseDialog';
|
||||
import { Form } from 'src/shared/ui/Form';
|
||||
import type { IProject } from '../../../../entities/Project/model';
|
||||
import type { IContributor } from '../../../../entities/Contributor/model';
|
||||
|
||||
+28
-22
@@ -1,31 +1,37 @@
|
||||
<template lang="pug">
|
||||
q-btn(
|
||||
v-if='canDelete'
|
||||
flat
|
||||
color='negative'
|
||||
class='full-width q-mt-md'
|
||||
label='Удалить'
|
||||
@click='showDialog = true'
|
||||
:loading='isSubmitting'
|
||||
size="sm"
|
||||
)
|
||||
q-dialog(v-model='showDialog', @hide='close')
|
||||
ModalBase(:title='dialogTitle')
|
||||
Form.q-pa-sm(
|
||||
:handler-submit='confirmDelete'
|
||||
:is-submitting='isSubmitting'
|
||||
:button-cancel-txt='"Отменить"'
|
||||
:button-submit-txt='"Удалить"'
|
||||
@cancel='close'
|
||||
)
|
||||
div(style='max-width: 360px')
|
||||
p {{ confirmMessage }}
|
||||
div
|
||||
q-btn(
|
||||
v-if='canDelete'
|
||||
flat
|
||||
color='negative'
|
||||
class='full-width q-mt-md'
|
||||
label='Удалить'
|
||||
@click='showDialog = true'
|
||||
:loading='isSubmitting'
|
||||
size="sm"
|
||||
)
|
||||
|
||||
BaseDialog(
|
||||
v-model='showDialog',
|
||||
:title='dialogTitle',
|
||||
size='sm',
|
||||
@update:model-value='(v) => !v && close()'
|
||||
)
|
||||
Form.q-pa-sm(
|
||||
:handler-submit='confirmDelete'
|
||||
:is-submitting='isSubmitting'
|
||||
:button-cancel-txt='"Отменить"'
|
||||
:button-submit-txt='"Удалить"'
|
||||
@cancel='close'
|
||||
)
|
||||
div(style='max-width: 360px')
|
||||
p {{ confirmMessage }}
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { FailAlert, SuccessAlert } from 'src/shared/api';
|
||||
import { ModalBase } from 'src/shared/ui/ModalBase';
|
||||
import { BaseDialog } from 'src/shared/ui/base/BaseDialog';
|
||||
import { Form } from 'src/shared/ui/Form';
|
||||
import { useDeleteProject } from '../model';
|
||||
|
||||
|
||||
+48
-42
@@ -1,55 +1,61 @@
|
||||
<template lang="pug">
|
||||
q-btn(
|
||||
v-if="props.project?.permissions?.can_set_plan"
|
||||
size='sm',
|
||||
color='primary',
|
||||
@click='showDialog = true',
|
||||
label='Установить план проекта'
|
||||
)
|
||||
q-dialog(v-model='showDialog', @hide='clear')
|
||||
ModalBase(:title='"Установить план проекта"')
|
||||
Form.q-pa-md(
|
||||
:handler-submit='handleSetPlan',
|
||||
:is-submitting='isSubmitting',
|
||||
:button-submit-txt='"Установить план"',
|
||||
:button-cancel-txt='"Отмена"',
|
||||
@cancel='clear'
|
||||
style="width: 600px; max-width: 100% !important;"
|
||||
div
|
||||
q-btn(
|
||||
v-if="props.project?.permissions?.can_set_plan"
|
||||
size='sm',
|
||||
color='primary',
|
||||
@click='showDialog = true',
|
||||
label='Установить план проекта'
|
||||
)
|
||||
|
||||
BaseDialog(
|
||||
v-model='showDialog',
|
||||
title='Установить план проекта',
|
||||
size='md',
|
||||
@update:model-value='(v) => !v && clear()'
|
||||
)
|
||||
Form.q-pa-md(
|
||||
:handler-submit='handleSetPlan',
|
||||
:is-submitting='isSubmitting',
|
||||
:button-submit-txt='"Установить план"',
|
||||
:button-cancel-txt='"Отмена"',
|
||||
@cancel='clear'
|
||||
style="width: 600px; max-width: 100% !important;"
|
||||
)
|
||||
q-input(
|
||||
v-model.number='formData.plan_creators_hours'
|
||||
label='Плановое количество часов исполнителей'
|
||||
type='number'
|
||||
:rules='[(val) => val > 0 || "Количество часов должно быть больше 0"]'
|
||||
outlined
|
||||
)
|
||||
q-input(
|
||||
v-model.number='formData.plan_creators_hours'
|
||||
label='Плановое количество часов исполнителей'
|
||||
type='number'
|
||||
:rules='[(val) => val > 0 || "Количество часов должно быть больше 0"]'
|
||||
outlined
|
||||
)
|
||||
template(#append)
|
||||
span.text-grey-7 ч
|
||||
template(#append)
|
||||
span.text-grey-7 ч
|
||||
|
||||
q-input(
|
||||
v-model='formData.plan_expenses'
|
||||
label='Плановые расходы'
|
||||
:rules='[(val) => !!val || "Плановые расходы обязательны"]'
|
||||
outlined
|
||||
)
|
||||
template(#append)
|
||||
span.text-grey-7 {{ governSymbol }}
|
||||
q-input(
|
||||
v-model='formData.plan_expenses'
|
||||
label='Плановые расходы'
|
||||
:rules='[(val) => !!val || "Плановые расходы обязательны"]'
|
||||
outlined
|
||||
)
|
||||
template(#append)
|
||||
span.text-grey-7 {{ governSymbol }}
|
||||
|
||||
q-input(
|
||||
v-model='formData.plan_hour_cost'
|
||||
label='Стоимость часа работы'
|
||||
:rules='[(val) => !!val || "Стоимость часа работы обязательна"]'
|
||||
outlined
|
||||
)
|
||||
template(#append)
|
||||
span.text-grey-7 {{ governSymbol }}
|
||||
q-input(
|
||||
v-model='formData.plan_hour_cost'
|
||||
label='Стоимость часа работы'
|
||||
:rules='[(val) => !!val || "Стоимость часа работы обязательна"]'
|
||||
outlined
|
||||
)
|
||||
template(#append)
|
||||
span.text-grey-7 {{ governSymbol }}
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { useSetPlan } from '../model';
|
||||
import { FailAlert, SuccessAlert } from 'src/shared/api/alerts';
|
||||
import { ModalBase } from 'src/shared/ui/ModalBase';
|
||||
import { BaseDialog } from 'src/shared/ui/base/BaseDialog';
|
||||
import { Form } from 'src/shared/ui/Form';
|
||||
import type { IProject } from '../../../../entities/Project/model';
|
||||
|
||||
|
||||
+30
-28
@@ -1,42 +1,44 @@
|
||||
<template lang="pug">
|
||||
q-btn(
|
||||
color='primary',
|
||||
@click='showDialog = true',
|
||||
:loading='loading',
|
||||
label='Внести результат'
|
||||
)
|
||||
div
|
||||
q-btn(
|
||||
color='primary',
|
||||
@click='showDialog = true',
|
||||
:loading='loading',
|
||||
label='Внести результат'
|
||||
)
|
||||
|
||||
q-dialog(v-model='showDialog', @hide='clear')
|
||||
ModalBase(:title='"Подтверждение внесения результата"')
|
||||
.q-pa-md
|
||||
// Общая сумма стоимости сегмента
|
||||
ColorCard(color='green')
|
||||
.card-label Ваш паевой взнос
|
||||
.card-value {{ contributionAmount }}
|
||||
BaseDialog(
|
||||
v-model='showDialog',
|
||||
title='Подтверждение внесения результата',
|
||||
size='md',
|
||||
@update:model-value='(v) => !v && clear()'
|
||||
)
|
||||
.q-pa-md
|
||||
ColorCard(color='green')
|
||||
.card-label Ваш паевой взнос
|
||||
.card-value {{ contributionAmount }}
|
||||
|
||||
// Сумма взятой ссуды (только если > 0)
|
||||
ColorCard(color='orange', v-if='parseFloat(debtAmount) > 0')
|
||||
.card-label Сумма погашаемой ссуды
|
||||
.card-value {{ debtAmount }}
|
||||
ColorCard(color='orange', v-if='parseFloat(debtAmount) > 0')
|
||||
.card-label Сумма погашаемой ссуды
|
||||
.card-value {{ debtAmount }}
|
||||
|
||||
Form.q-pa-md(
|
||||
:handler-submit='handlePushResult',
|
||||
:is-submitting='isSubmitting',
|
||||
:button-submit-txt='"Подтвердить внесение"',
|
||||
:button-cancel-txt='"Отмена"',
|
||||
@cancel='clear'
|
||||
)
|
||||
Form.q-pa-md(
|
||||
:handler-submit='handlePushResult',
|
||||
:is-submitting='isSubmitting',
|
||||
:button-submit-txt='"Подтвердить внесение"',
|
||||
:button-cancel-txt='"Отмена"',
|
||||
@cancel='clear'
|
||||
)
|
||||
|
||||
template(#footer='')
|
||||
.q-pa-md.text-center
|
||||
p.text-caption Подтверждая внесение результата, вы соглашаетесь с генерацией и подписью заявления на паевой взнос
|
||||
.q-pa-md.text-center
|
||||
p.text-caption Подтверждая внесение результата, вы соглашаетесь с генерацией и подписью заявления на паевой взнос
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { usePushResult } from '../model';
|
||||
import { FailAlert, SuccessAlert } from 'src/shared/api/alerts';
|
||||
import { ModalBase } from 'src/shared/ui/ModalBase';
|
||||
import { BaseDialog } from 'src/shared/ui/base/BaseDialog';
|
||||
import { Form } from 'src/shared/ui/Form';
|
||||
import { ColorCard } from 'src/shared/ui/ColorCard';
|
||||
import { formatAsset2Digits } from 'src/shared/lib/utils/formatAsset2Digits';
|
||||
|
||||
+28
-22
@@ -1,32 +1,38 @@
|
||||
<template lang="pug">
|
||||
q-btn(
|
||||
color='grey',
|
||||
flat,
|
||||
dense,
|
||||
clickable,
|
||||
size='sm',
|
||||
@click='showDialog = true',
|
||||
:loading='isSubmitting'
|
||||
)
|
||||
q-icon(name='delete')
|
||||
q-dialog(v-model='showDialog', @hide='close')
|
||||
ModalBase(title='Удаление')
|
||||
Form.q-pa-sm(
|
||||
:handler-submit='deleteStory',
|
||||
:is-submitting='isSubmitting',
|
||||
:button-cancel-txt='"Отменить"',
|
||||
:button-submit-txt='"Удалить"',
|
||||
@cancel='close'
|
||||
)
|
||||
div(style='max-width: 300px')
|
||||
p Вы уверены, что хотите удалить?
|
||||
div
|
||||
q-btn(
|
||||
color='grey',
|
||||
flat,
|
||||
dense,
|
||||
clickable,
|
||||
size='sm',
|
||||
@click='showDialog = true',
|
||||
:loading='isSubmitting'
|
||||
)
|
||||
q-icon(name='delete')
|
||||
|
||||
BaseDialog(
|
||||
v-model='showDialog',
|
||||
title='Удаление',
|
||||
size='sm',
|
||||
@update:model-value='(v) => !v && close()'
|
||||
)
|
||||
Form.q-pa-sm(
|
||||
:handler-submit='deleteStory',
|
||||
:is-submitting='isSubmitting',
|
||||
:button-cancel-txt='"Отменить"',
|
||||
:button-submit-txt='"Удалить"',
|
||||
@cancel='close'
|
||||
)
|
||||
div(style='max-width: 300px')
|
||||
p Вы уверены, что хотите удалить?
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { FailAlert, SuccessAlert } from 'src/shared/api';
|
||||
import { useDeleteStory } from '../model';
|
||||
import { ref } from 'vue';
|
||||
import { ModalBase } from 'src/shared/ui/ModalBase';
|
||||
import { BaseDialog } from 'src/shared/ui/base/BaseDialog';
|
||||
import { Form } from 'src/shared/ui/Form';
|
||||
|
||||
const { deleteStory: deleteStoryAction } = useDeleteStory();
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ import { useSystemStore } from 'src/entities/System/model';
|
||||
import { EditAboutInput, EditHoursPerDayInput, EditRatePerHourInput } from 'app/extensions/capital/features/Contributor/EditContributor';
|
||||
import { formatAsset2Digits } from 'src/shared/lib/utils/formatAsset2Digits';
|
||||
import {ColorCard} from 'src/shared/ui';
|
||||
import { AutoAvatar } from 'src/shared/ui/AutoAvatar';
|
||||
import { AutoAvatar } from 'src/shared/ui/domain/AutoAvatar';
|
||||
import { CapitalWalletsCardsWidget } from 'app/extensions/capital/widgets/CapitalWalletsCardsWidget';
|
||||
const contributorStore = useContributorStore();
|
||||
const system = useSystemStore();
|
||||
|
||||
@@ -294,6 +294,7 @@ const menuButtons = computed(() => {
|
||||
props: {
|
||||
routeName: 'component-description',
|
||||
label: 'Описание',
|
||||
icon: 'description',
|
||||
routeParams: { project_hash: projectHash.value },
|
||||
query,
|
||||
},
|
||||
@@ -305,6 +306,7 @@ const menuButtons = computed(() => {
|
||||
props: {
|
||||
routeName: 'component-tasks',
|
||||
label: 'Задачи',
|
||||
icon: 'task_alt',
|
||||
routeParams: { project_hash: projectHash.value },
|
||||
query,
|
||||
},
|
||||
@@ -316,6 +318,7 @@ const menuButtons = computed(() => {
|
||||
props: {
|
||||
routeName: 'component-requirements',
|
||||
label: 'Артефакты',
|
||||
icon: 'inventory_2',
|
||||
routeParams: { project_hash: projectHash.value },
|
||||
query,
|
||||
},
|
||||
@@ -327,6 +330,7 @@ const menuButtons = computed(() => {
|
||||
props: {
|
||||
routeName: 'component-planning',
|
||||
label: 'План',
|
||||
icon: 'timeline',
|
||||
routeParams: { project_hash: projectHash.value },
|
||||
query,
|
||||
},
|
||||
@@ -338,6 +342,7 @@ const menuButtons = computed(() => {
|
||||
props: {
|
||||
routeName: 'component-voting',
|
||||
label: 'Голосование',
|
||||
icon: 'how_to_vote',
|
||||
routeParams: { project_hash: projectHash.value },
|
||||
query,
|
||||
},
|
||||
@@ -349,6 +354,7 @@ const menuButtons = computed(() => {
|
||||
props: {
|
||||
routeName: 'component-results',
|
||||
label: 'Результаты',
|
||||
icon: 'assessment',
|
||||
routeParams: { project_hash: projectHash.value },
|
||||
query,
|
||||
},
|
||||
@@ -360,6 +366,7 @@ const menuButtons = computed(() => {
|
||||
props: {
|
||||
routeName: 'component-contributors',
|
||||
label: 'Участники',
|
||||
icon: 'groups',
|
||||
routeParams: { project_hash: projectHash.value },
|
||||
query,
|
||||
},
|
||||
@@ -371,6 +378,7 @@ const menuButtons = computed(() => {
|
||||
props: {
|
||||
routeName: 'component-history',
|
||||
label: 'История',
|
||||
icon: 'history',
|
||||
routeParams: { project_hash: projectHash.value },
|
||||
query,
|
||||
},
|
||||
|
||||
-1
@@ -52,7 +52,6 @@ div
|
||||
import { onMounted, ref, computed } from 'vue';
|
||||
import { useSystemStore } from 'src/entities/System/model';
|
||||
import { useExpandableState } from 'src/shared/lib/composables';
|
||||
import 'src/shared/ui/TitleStyles';
|
||||
import { WindowLoader } from 'src/shared/ui/Loader';
|
||||
import { ColorCard } from 'src/shared/ui/ColorCard/ui';
|
||||
import { ResultSubmissionSegmentsWidget, ResultSubmissionActionsWidget } from 'app/extensions/capital/widgets';
|
||||
|
||||
-1
@@ -58,7 +58,6 @@ import { onMounted, onBeforeUnmount, ref, computed } from 'vue';
|
||||
import { useSystemStore } from 'src/entities/System/model';
|
||||
import { useExpandableState, useDataPoller } from 'src/shared/lib/composables';
|
||||
import { POLL_INTERVALS } from 'src/shared/lib/consts';
|
||||
import 'src/shared/ui/TitleStyles';
|
||||
import { WindowLoader } from 'src/shared/ui/Loader';
|
||||
import { ColorCard } from 'src/shared/ui/ColorCard/ui';
|
||||
import { ProjectVotingSegmentsWidget, SegmentVotesWidget } from 'app/extensions/capital/widgets';
|
||||
|
||||
@@ -38,7 +38,6 @@ import { WindowLoader } from 'src/shared/ui/Loader';
|
||||
import { InvitationsListWidget } from 'app/extensions/capital/widgets';
|
||||
import { ReferralLinkCard } from 'src/shared/ui/ReferralLinkCard';
|
||||
import { ColorCard } from 'src/shared/ui/ColorCard/ui';
|
||||
import 'src/shared/ui/TitleStyles';
|
||||
|
||||
const { referralLink } = useReferralLink();
|
||||
|
||||
|
||||
@@ -267,6 +267,7 @@ const menuButtons = computed(() => {
|
||||
props: {
|
||||
routeName: 'project-description',
|
||||
label: 'Описание',
|
||||
icon: 'description',
|
||||
routeParams: { project_hash: projectHash.value },
|
||||
query,
|
||||
},
|
||||
@@ -278,6 +279,7 @@ const menuButtons = computed(() => {
|
||||
props: {
|
||||
routeName: 'project-requirements',
|
||||
label: 'Артефакты',
|
||||
icon: 'inventory_2',
|
||||
routeParams: { project_hash: projectHash.value },
|
||||
query,
|
||||
},
|
||||
@@ -289,6 +291,7 @@ const menuButtons = computed(() => {
|
||||
props: {
|
||||
routeName: 'project-components',
|
||||
label: 'Компоненты',
|
||||
icon: 'widgets',
|
||||
routeParams: { project_hash: projectHash.value },
|
||||
query,
|
||||
},
|
||||
@@ -300,6 +303,7 @@ const menuButtons = computed(() => {
|
||||
props: {
|
||||
routeName: 'project-planning',
|
||||
label: 'План',
|
||||
icon: 'timeline',
|
||||
routeParams: { project_hash: projectHash.value },
|
||||
query,
|
||||
},
|
||||
@@ -311,6 +315,7 @@ const menuButtons = computed(() => {
|
||||
props: {
|
||||
routeName: 'project-contributors',
|
||||
label: 'Участники',
|
||||
icon: 'groups',
|
||||
routeParams: { project_hash: projectHash.value },
|
||||
query,
|
||||
},
|
||||
@@ -322,6 +327,7 @@ const menuButtons = computed(() => {
|
||||
props: {
|
||||
routeName: 'project-history',
|
||||
label: 'История',
|
||||
icon: 'history',
|
||||
routeParams: { project_hash: projectHash.value },
|
||||
query,
|
||||
},
|
||||
|
||||
@@ -45,7 +45,6 @@ div
|
||||
import { onMounted, onBeforeMount, onBeforeUnmount, ref, computed, markRaw, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useExpandableState } from 'src/shared/lib/composables';
|
||||
import 'src/shared/ui/TitleStyles';
|
||||
import { Fab } from 'src/shared/ui';
|
||||
import { FilterDialogWithButton } from 'app/extensions/capital/shared/ui';
|
||||
import { useHeaderActions } from 'src/shared/hooks';
|
||||
|
||||
-1
@@ -34,7 +34,6 @@ div
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useSystemStore } from 'src/entities/System/model';
|
||||
import { useExpandableState } from 'src/shared/lib/composables';
|
||||
import 'src/shared/ui/TitleStyles';
|
||||
import { WindowLoader } from 'src/shared/ui/Loader';
|
||||
import { ListResultProjectsWidget, ResultSubmissionSegmentsWidget, ResultSubmissionActionsWidget } from 'app/extensions/capital/widgets';
|
||||
import { useSegmentStore } from 'app/extensions/capital/entities/Segment/model';
|
||||
|
||||
-1
@@ -42,7 +42,6 @@ import { onMounted, onBeforeUnmount, ref } from 'vue';
|
||||
import { useSystemStore } from 'src/entities/System/model';
|
||||
import { useExpandableState, useDataPoller } from 'src/shared/lib/composables';
|
||||
import { POLL_INTERVALS } from 'src/shared/lib/consts';
|
||||
import 'src/shared/ui/TitleStyles';
|
||||
import { WindowLoader } from 'src/shared/ui/Loader';
|
||||
import { ListVotingProjectWidget, ProjectVotingSegmentsWidget, SegmentVotesWidget } from 'app/extensions/capital/widgets';
|
||||
import { useSessionStore } from 'src/entities/Session';
|
||||
|
||||
@@ -38,7 +38,6 @@ import { onMounted, ref } from 'vue';
|
||||
import { useSystemStore } from 'src/entities/System/model';
|
||||
import { useSessionStore } from 'src/entities/Session/model/store';
|
||||
import { useExpandableState } from 'src/shared/lib/composables';
|
||||
import 'src/shared/ui/TitleStyles';
|
||||
import { WindowLoader } from 'src/shared/ui/Loader';
|
||||
import { TimeStatsWidget, TimeIssuesWidget, TimeEntriesWidget } from 'app/extensions/capital/widgets';
|
||||
|
||||
|
||||
+20
-14
@@ -1,27 +1,30 @@
|
||||
<template lang="pug">
|
||||
div
|
||||
// Кнопка для открытия диалога
|
||||
//- Canon header-кнопка: на мобильном — иконка-only + tooltip.
|
||||
q-btn(
|
||||
size="md"
|
||||
flat
|
||||
icon="filter_list"
|
||||
label="Фильтры"
|
||||
:stretch="stretch"
|
||||
:style="style"
|
||||
@click="handleClick"
|
||||
:size='isMobile ? "sm" : "md"',
|
||||
flat,
|
||||
:dense='isMobile',
|
||||
icon='filter_list',
|
||||
:label='isMobile ? undefined : "Фильтры"',
|
||||
:stretch='stretch',
|
||||
:style='style',
|
||||
no-wrap,
|
||||
@click='handleClick'
|
||||
)
|
||||
q-badge(
|
||||
v-if="hasActiveFilters"
|
||||
color="red"
|
||||
v-if='hasActiveFilters'
|
||||
color='red'
|
||||
floating
|
||||
rounded
|
||||
)
|
||||
q-tooltip(v-if='isMobile') Фильтры
|
||||
|
||||
// Диалог фильтров
|
||||
//- Диалог фильтров
|
||||
FilterDialog(
|
||||
ref="filterDialogRef"
|
||||
title="Фильтры проектов"
|
||||
@filters-applied="handleFiltersApplied"
|
||||
ref='filterDialogRef'
|
||||
title='Фильтры проектов'
|
||||
@filters-applied='handleFiltersApplied'
|
||||
)
|
||||
</template>
|
||||
|
||||
@@ -29,6 +32,9 @@ div
|
||||
import { ref, computed } from 'vue';
|
||||
import { FilterDialog } from 'app/extensions/capital/features/FilterDialog';
|
||||
import { useProjectStore } from 'app/extensions/capital/entities/Project/model';
|
||||
import { useWindowSize } from 'src/shared/hooks';
|
||||
|
||||
const { isMobile } = useWindowSize();
|
||||
|
||||
// Props для стилизации из header
|
||||
defineProps<{
|
||||
|
||||
+2
-5
@@ -33,7 +33,6 @@ import { computed } from 'vue';
|
||||
import { useWalletStore } from 'src/entities/Wallet/model';
|
||||
import { ColorCard } from 'src/shared/ui';
|
||||
import { formatAsset2Digits } from 'src/shared/lib/utils/formatAsset2Digits';
|
||||
import { addAssets } from 'src/shared/lib/utils/addAssets';
|
||||
import { Zeus } from '@coopenomics/sdk';
|
||||
import { CreateProgramInvestButton } from 'app/extensions/capital/features/ProgramInvest/CreateProgramInvest/ui';
|
||||
|
||||
@@ -47,8 +46,7 @@ const generatorWallet = computed(() => {
|
||||
|
||||
return {
|
||||
available: wallet?.available || '0.00',
|
||||
blocked: wallet?.blocked || '0.00',
|
||||
total: addAssets(wallet?.available || '0.00', wallet?.blocked || '0.00'),
|
||||
total: wallet?.available || '0.00',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -61,8 +59,7 @@ const blagorostWallet = computed(() => {
|
||||
|
||||
return {
|
||||
available: wallet?.available || '0.00',
|
||||
blocked: wallet?.blocked || '0.00',
|
||||
total: addAssets(wallet?.available || '0.00', wallet?.blocked || '0.00'),
|
||||
total: wallet?.available || '0.00',
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
-1
@@ -67,7 +67,6 @@ import { EditAboutInput, EditHoursPerDayInput, EditRatePerHourInput } from 'app/
|
||||
import { ContributorGamificationWidget } from 'app/extensions/capital/widgets/ContributorGamificationWidget';
|
||||
import { ColorCard } from 'src/shared/ui';
|
||||
import { formatAsset2Digits } from 'src/shared/lib/utils/formatAsset2Digits';
|
||||
import 'src/shared/ui/CardStyles';
|
||||
|
||||
const contributorStore = useContributorStore();
|
||||
const { info } = useSystemStore();
|
||||
|
||||
+9
-24
@@ -244,45 +244,30 @@ const calculateInvestorTotal = (contributor: IContributor) => {
|
||||
// Функции для расчета сумм кошельков
|
||||
const calculateMainWalletTotal = (contributor: IContributor) => {
|
||||
const availableStr = contributor?.main_wallet?.available || '0.0000';
|
||||
const blockedStr = contributor?.main_wallet?.blocked || '0.0000';
|
||||
const total = Number(availableStr.split(' ')[0] || '0');
|
||||
|
||||
const available = Number(availableStr.split(' ')[0] || '0');
|
||||
const blocked = Number(blockedStr.split(' ')[0] || '0');
|
||||
|
||||
const total = available + blocked;
|
||||
|
||||
// Определяем валюту из любого поля, которое ее содержит, или используем системный символ
|
||||
const currency = availableStr.split(' ')[1] || blockedStr.split(' ')[1] || governSymbol.value;
|
||||
// Определяем валюту из поля available или используем системный символ
|
||||
const currency = availableStr.split(' ')[1] || governSymbol.value;
|
||||
|
||||
return currency ? `${total} ${currency}` : total.toString();
|
||||
};
|
||||
|
||||
const calculateGenerationWalletTotal = (contributor: IContributor) => {
|
||||
const availableStr = contributor?.generation_wallet?.available || '0.0000';
|
||||
const blockedStr = contributor?.generation_wallet?.blocked || '0.0000';
|
||||
const total = Number(availableStr.split(' ')[0] || '0');
|
||||
|
||||
const available = Number(availableStr.split(' ')[0] || '0');
|
||||
const blocked = Number(blockedStr.split(' ')[0] || '0');
|
||||
|
||||
const total = available + blocked;
|
||||
|
||||
// Определяем валюту из любого поля, которое ее содержит, или используем системный символ
|
||||
const currency = availableStr.split(' ')[1] || blockedStr.split(' ')[1] || governSymbol.value;
|
||||
// Определяем валюту из поля available или используем системный символ
|
||||
const currency = availableStr.split(' ')[1] || governSymbol.value;
|
||||
|
||||
return currency ? `${total} ${currency}` : total.toString();
|
||||
};
|
||||
|
||||
const calculateBlagorostWalletTotal = (contributor: IContributor) => {
|
||||
const availableStr = contributor?.blagorost_wallet?.available || '0.0000';
|
||||
const blockedStr = contributor?.blagorost_wallet?.blocked || '0.0000';
|
||||
const total = Number(availableStr.split(' ')[0] || '0');
|
||||
|
||||
const available = Number(availableStr.split(' ')[0] || '0');
|
||||
const blocked = Number(blockedStr.split(' ')[0] || '0');
|
||||
|
||||
const total = available + blocked;
|
||||
|
||||
// Определяем валюту из любого поля, которое ее содержит, или используем системный символ
|
||||
const currency = availableStr.split(' ')[1] || blockedStr.split(' ')[1] || governSymbol.value;
|
||||
// Определяем валюту из поля available или используем системный символ
|
||||
const currency = availableStr.split(' ')[1] || governSymbol.value;
|
||||
|
||||
return currency ? `${total} ${currency}` : total.toString();
|
||||
};
|
||||
|
||||
+134
-114
@@ -1,128 +1,145 @@
|
||||
<template lang="pug">
|
||||
q-btn(color="primary" @click="showDialog = true" icon="file_upload") Импорт из CSV
|
||||
q-dialog(v-model='showDialog', @hide='clear' maximized)
|
||||
ModalBase(:title='"Импорт участников"')
|
||||
.q-pa-md
|
||||
q-card(v-if='!hasConfig' flat)
|
||||
div
|
||||
//- Canon header-кнопка: на мобильном — иконка-only + tooltip.
|
||||
q-btn(
|
||||
@click='showDialog = true',
|
||||
:color='isMobile ? "accent" : "primary"',
|
||||
:flat='isMobile',
|
||||
:dense='isMobile',
|
||||
:size='isMobile ? "sm" : undefined',
|
||||
icon='file_upload',
|
||||
:label='isMobile ? undefined : "Импорт из CSV"',
|
||||
no-wrap
|
||||
)
|
||||
q-tooltip(v-if='isMobile') Импорт участников из CSV
|
||||
|
||||
BaseDialog(
|
||||
v-model='showDialog',
|
||||
title='Импорт участников',
|
||||
:maximized='true',
|
||||
@update:model-value='(v) => !v && clear()'
|
||||
)
|
||||
.q-pa-md
|
||||
q-card(v-if='!hasConfig' flat)
|
||||
q-card-section
|
||||
.row.items-center.q-gutter-sm
|
||||
q-icon(name='upload_file', size='24px')
|
||||
div
|
||||
.text-h6 Импорт участников
|
||||
.text-subtitle2 Загрузка участников из CSV файла
|
||||
q-space
|
||||
q-btn(flat color='primary' icon='download' @click='downloadSample') Скачать шаблон CSV
|
||||
|
||||
q-separator
|
||||
|
||||
q-card-section
|
||||
template(v-if='parsedData.length === 0')
|
||||
CsvUploader(@parsed='onCsvParsed')
|
||||
|
||||
template(
|
||||
v-if='parsedData.length > 0 && !isImporting && !isImportCompleted'
|
||||
)
|
||||
q-separator
|
||||
|
||||
q-card-section
|
||||
.row.items-center.q-gutter-sm
|
||||
q-icon(name='upload_file', size='24px')
|
||||
div
|
||||
.text-h6 Импорт участников
|
||||
.text-subtitle2 Загрузка участников из CSV файла
|
||||
q-space
|
||||
q-btn(flat color='primary' icon='download' @click='downloadSample') Скачать шаблон CSV
|
||||
.row.items-center.q-gutter-sm.q-mb-md
|
||||
q-icon(name='table_chart', size='20px')
|
||||
.text-subtitle1 Предварительный просмотр данных ({{ parsedData.length }} записей)
|
||||
|
||||
q-table(
|
||||
:rows='parsedData',
|
||||
:columns='previewColumns',
|
||||
:loading='false',
|
||||
:pagination='{ rowsPerPage: 0 }',
|
||||
row-key='id',
|
||||
binary-state-sort
|
||||
)
|
||||
template(#body-cell-status='props')
|
||||
q-td
|
||||
q-chip(
|
||||
:color='getStatusColor(props.value)',
|
||||
text-color='white',
|
||||
dense
|
||||
) {{ getStatusText(props.value) }}
|
||||
|
||||
q-separator
|
||||
|
||||
q-card-section
|
||||
template(v-if='parsedData.length === 0')
|
||||
CsvUploader(@parsed='onCsvParsed')
|
||||
.row.items-center.q-gutter-sm.q-mb-md
|
||||
q-icon(name='playlist_add_check', size='20px')
|
||||
.text-subtitle1 Импорт данных
|
||||
|
||||
template(
|
||||
v-if='parsedData.length > 0 && !isImporting && !isImportCompleted'
|
||||
)
|
||||
q-separator
|
||||
|
||||
q-card-section
|
||||
.row.items-center.q-gutter-sm.q-mb-md
|
||||
q-icon(name='table_chart', size='20px')
|
||||
.text-subtitle1 Предварительный просмотр данных ({{ parsedData.length }} записей)
|
||||
|
||||
q-table(
|
||||
:rows='parsedData',
|
||||
:columns='previewColumns',
|
||||
:loading='false',
|
||||
:pagination='{ rowsPerPage: 0 }',
|
||||
row-key='id',
|
||||
binary-state-sort
|
||||
.row.justify-end.q-gutter-sm.q-mb-md
|
||||
q-btn(
|
||||
color='primary',
|
||||
:loading='isImporting',
|
||||
@click='startImport',
|
||||
label='Начать импорт',
|
||||
unelevated,
|
||||
icon='play_arrow'
|
||||
)
|
||||
template(#body-cell-status='props')
|
||||
q-td
|
||||
q-chip(
|
||||
:color='getStatusColor(props.value)',
|
||||
text-color='white',
|
||||
dense
|
||||
) {{ getStatusText(props.value) }}
|
||||
|
||||
q-separator
|
||||
template(v-if='importProgress > 0')
|
||||
.q-mt-md
|
||||
.text-body2.q-mb-sm Статистика
|
||||
.row.q-gutter-md
|
||||
.col-auto
|
||||
.text-weight-medium
|
||||
q-icon.q-mr-xs(name='check_circle', color='positive')
|
||||
| Успешно: {{ successCount }}
|
||||
.col-auto
|
||||
.text-weight-medium
|
||||
q-icon.q-mr-xs(name='error', color='negative')
|
||||
| Ошибок: {{ errorCount }}
|
||||
|
||||
q-card-section
|
||||
.row.items-center.q-gutter-sm.q-mb-md
|
||||
q-icon(name='playlist_add_check', size='20px')
|
||||
.text-subtitle1 Импорт данных
|
||||
template(v-if='isImporting')
|
||||
q-separator
|
||||
|
||||
.row.justify-end.q-gutter-sm.q-mb-md
|
||||
q-btn(
|
||||
color='primary',
|
||||
:loading='isImporting',
|
||||
@click='startImport',
|
||||
label='Начать импорт',
|
||||
unelevated,
|
||||
icon='play_arrow'
|
||||
)
|
||||
|
||||
template(v-if='importProgress > 0')
|
||||
.q-mt-md
|
||||
.text-body2.q-mb-sm Статистика
|
||||
.row.q-gutter-md
|
||||
.col-auto
|
||||
.text-weight-medium
|
||||
q-icon.q-mr-xs(name='check_circle', color='positive')
|
||||
| Успешно: {{ successCount }}
|
||||
.col-auto
|
||||
.text-weight-medium
|
||||
q-icon.q-mr-xs(name='error', color='negative')
|
||||
| Ошибок: {{ errorCount }}
|
||||
|
||||
template(v-if='isImporting')
|
||||
q-separator
|
||||
|
||||
q-card-section
|
||||
.row.items-center.q-gutter-sm.q-mb-md
|
||||
q-icon(name='sync', size='20px')
|
||||
.text-subtitle1 Выполняется импорт данных
|
||||
|
||||
.q-mb-md
|
||||
.text-body2.q-mb-sm Прогресс импорта: {{ importProgress }}/{{ parsedData.length }} ({{ progressPercent }}%)
|
||||
q-linear-progress(
|
||||
:value='progressPercent / 100',
|
||||
color='primary',
|
||||
size='8px'
|
||||
)
|
||||
|
||||
.row.justify-end.q-gutter-sm
|
||||
q-btn(
|
||||
color='negative',
|
||||
@click='stopImport',
|
||||
label='Остановить',
|
||||
flat,
|
||||
icon='stop'
|
||||
)
|
||||
|
||||
template(v-if='importResults.length > 0 || isImportCompleted')
|
||||
q-separator
|
||||
|
||||
q-card-section
|
||||
.row.items-center.q-gutter-sm.q-mb-md
|
||||
q-icon(name='table_chart', size='20px')
|
||||
.text-subtitle1 Результаты импорта
|
||||
|
||||
ImportResultsTable(
|
||||
:items='importResults',
|
||||
:is-importing='isImporting',
|
||||
:current-index='currentImportIndex',
|
||||
@retry='retryImport',
|
||||
@retry-all-failed='retryAllFailed',
|
||||
@clear='clearAll'
|
||||
)
|
||||
q-card(v-else)
|
||||
q-card-section
|
||||
.row.items-center.q-gutter-sm
|
||||
q-icon(name='info', size='24px', color='info')
|
||||
div
|
||||
.text-h6 Конфигурация уже установлена
|
||||
.text-subtitle2 Импорт участников недоступен после установки конфигурации контракта.
|
||||
.row.items-center.q-gutter-sm.q-mb-md
|
||||
q-icon(name='sync', size='20px')
|
||||
.text-subtitle1 Выполняется импорт данных
|
||||
|
||||
.q-mb-md
|
||||
.text-body2.q-mb-sm Прогресс импорта: {{ importProgress }}/{{ parsedData.length }} ({{ progressPercent }}%)
|
||||
q-linear-progress(
|
||||
:value='progressPercent / 100',
|
||||
color='primary',
|
||||
size='8px'
|
||||
)
|
||||
|
||||
.row.justify-end.q-gutter-sm
|
||||
q-btn(
|
||||
color='negative',
|
||||
@click='stopImport',
|
||||
label='Остановить',
|
||||
flat,
|
||||
icon='stop'
|
||||
)
|
||||
|
||||
template(v-if='importResults.length > 0 || isImportCompleted')
|
||||
q-separator
|
||||
|
||||
q-card-section
|
||||
.row.items-center.q-gutter-sm.q-mb-md
|
||||
q-icon(name='table_chart', size='20px')
|
||||
.text-subtitle1 Результаты импорта
|
||||
|
||||
ImportResultsTable(
|
||||
:items='importResults',
|
||||
:is-importing='isImporting',
|
||||
:current-index='currentImportIndex',
|
||||
@retry='retryImport',
|
||||
@retry-all-failed='retryAllFailed',
|
||||
@clear='clearAll'
|
||||
)
|
||||
q-card(v-else)
|
||||
q-card-section
|
||||
.row.items-center.q-gutter-sm
|
||||
q-icon(name='info', size='24px', color='info')
|
||||
div
|
||||
.text-h6 Конфигурация уже установлена
|
||||
.text-subtitle2 Импорт участников недоступен после установки конфигурации контракта.
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -138,7 +155,10 @@ import { ref, computed, onMounted } from 'vue';
|
||||
import { FailAlert, NotifyAlert, SuccessAlert } from 'src/shared/api/alerts';
|
||||
import { useConfigStore } from 'app/extensions/capital/entities/Config/model';
|
||||
import { useSystemStore } from 'src/entities/System/model';
|
||||
import { ModalBase } from 'src/shared/ui/ModalBase';
|
||||
import { BaseDialog } from 'src/shared/ui/base/BaseDialog';
|
||||
import { useWindowSize } from 'src/shared/hooks';
|
||||
|
||||
const { isMobile } = useWindowSize();
|
||||
|
||||
// Определение колонок для предварительного просмотра
|
||||
const previewColumns: QTableProps['columns'] = [
|
||||
|
||||
+27
-23
@@ -1,34 +1,38 @@
|
||||
<template lang="pug">
|
||||
q-btn(
|
||||
color='positive',
|
||||
icon='check',
|
||||
size='sm',
|
||||
flat,
|
||||
round,
|
||||
@click='showDialog = true',
|
||||
:loading='isSubmitting'
|
||||
)
|
||||
q-tooltip Одобрить
|
||||
|
||||
q-dialog(v-model='showDialog', @hide='close')
|
||||
ModalBase(title='Подтверждение одобрения')
|
||||
Form.q-pa-sm(
|
||||
:handler-submit='confirmApproval',
|
||||
:is-submitting='isSubmitting',
|
||||
:button-cancel-txt='"Отменить"',
|
||||
:button-submit-txt='"Одобрить"',
|
||||
@cancel='close'
|
||||
)
|
||||
div(style='max-width: 300px')
|
||||
p Вы уверены, что хотите одобрить документ?
|
||||
div
|
||||
q-btn(
|
||||
color='positive',
|
||||
icon='check',
|
||||
size='sm',
|
||||
flat,
|
||||
round,
|
||||
@click='showDialog = true',
|
||||
:loading='isSubmitting'
|
||||
)
|
||||
q-tooltip Одобрить
|
||||
|
||||
BaseDialog(
|
||||
v-model='showDialog',
|
||||
title='Подтверждение одобрения',
|
||||
size='sm',
|
||||
@update:model-value='(v) => !v && close()'
|
||||
)
|
||||
Form.q-pa-sm(
|
||||
:handler-submit='confirmApproval',
|
||||
:is-submitting='isSubmitting',
|
||||
:button-cancel-txt='"Отменить"',
|
||||
:button-submit-txt='"Одобрить"',
|
||||
@cancel='close'
|
||||
)
|
||||
div(style='max-width: 300px')
|
||||
p Вы уверены, что хотите одобрить документ?
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { FailAlert, SuccessAlert } from 'src/shared/api';
|
||||
import { useConfirmApproval } from '../model';
|
||||
import { ref } from 'vue';
|
||||
import { ModalBase } from 'src/shared/ui/ModalBase';
|
||||
import { BaseDialog } from 'src/shared/ui/base/BaseDialog';
|
||||
import { Form } from 'src/shared/ui/Form';
|
||||
import type { IDocumentAggregate } from 'src/entities/Document/model/types';
|
||||
|
||||
|
||||
+35
-30
@@ -1,41 +1,46 @@
|
||||
<template lang="pug">
|
||||
q-btn(
|
||||
color='negative',
|
||||
icon='close',
|
||||
size='sm',
|
||||
flat,
|
||||
round,
|
||||
@click='showDialog = true',
|
||||
:loading='isSubmitting'
|
||||
)
|
||||
q-tooltip Отклонить
|
||||
div
|
||||
q-btn(
|
||||
color='negative',
|
||||
icon='close',
|
||||
size='sm',
|
||||
flat,
|
||||
round,
|
||||
@click='showDialog = true',
|
||||
:loading='isSubmitting'
|
||||
)
|
||||
q-tooltip Отклонить
|
||||
|
||||
q-dialog(v-model='showDialog', @hide='close')
|
||||
ModalBase(title='Отклонение одобрения')
|
||||
Form.q-pa-sm(
|
||||
:handler-submit='declineApproval',
|
||||
:is-submitting='isSubmitting',
|
||||
:button-cancel-txt='"Отменить"',
|
||||
:button-submit-txt='"Отклонить"',
|
||||
@cancel='close'
|
||||
)
|
||||
div(style='max-width: 400px')
|
||||
p Вы уверены, что хотите отклонить одобрение?
|
||||
q-input.q-mt-md(
|
||||
v-model='reason',
|
||||
label='Причина отклонения',
|
||||
outlined,
|
||||
type='textarea',
|
||||
rows='3',
|
||||
:rules='[val => !!val || "Причина обязательна"]'
|
||||
)
|
||||
BaseDialog(
|
||||
v-model='showDialog',
|
||||
title='Отклонение одобрения',
|
||||
size='md',
|
||||
@update:model-value='(v) => !v && close()'
|
||||
)
|
||||
Form.q-pa-sm(
|
||||
:handler-submit='declineApproval',
|
||||
:is-submitting='isSubmitting',
|
||||
:button-cancel-txt='"Отменить"',
|
||||
:button-submit-txt='"Отклонить"',
|
||||
@cancel='close'
|
||||
)
|
||||
div(style='max-width: 400px')
|
||||
p Вы уверены, что хотите отклонить одобрение?
|
||||
q-input.q-mt-md(
|
||||
v-model='reason',
|
||||
label='Причина отклонения',
|
||||
outlined,
|
||||
type='textarea',
|
||||
rows='3',
|
||||
:rules='[val => !!val || "Причина обязательна"]'
|
||||
)
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { FailAlert, SuccessAlert } from 'src/shared/api';
|
||||
import { useDeclineApproval } from '../model';
|
||||
import { ref } from 'vue';
|
||||
import { ModalBase } from 'src/shared/ui/ModalBase';
|
||||
import { BaseDialog } from 'src/shared/ui/base/BaseDialog';
|
||||
import { Form } from 'src/shared/ui/Form';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -106,8 +106,8 @@ export const useOnboardingFlow = () => {
|
||||
const importStep: AgendaStep = {
|
||||
key: 'import_participants',
|
||||
type: 'import',
|
||||
title: 'Импорт пайщиков',
|
||||
description: 'Загрузите пайщиков через CSV-файл.',
|
||||
title: 'Импорт пайщиков (опционально)',
|
||||
description: 'Необязательный шаг. Если у вас уже есть пайщики кооператива — загрузите их списком через CSV-файл. Если нет — просто двигайтесь дальше.',
|
||||
done: false,
|
||||
pending: false,
|
||||
};
|
||||
|
||||
+216
-93
@@ -11,92 +11,122 @@ OnboardingCompletionCelebration(
|
||||
)
|
||||
|
||||
// Показываем шаги если онбординг не завершен и данные загружены
|
||||
q-card(v-else flat)
|
||||
q-card-section.row.items-center.justify-between
|
||||
div
|
||||
div.text-h5 Адаптируйте кооператив к работе на платформе
|
||||
q-chip(
|
||||
.onboarding(v-else)
|
||||
.onboarding__head
|
||||
h2.onboarding__title Адаптируйте кооператив к работе на платформе
|
||||
p.onboarding__lead
|
||||
| Чтобы кооператив начал работать в цифровом контуре платформы, совет
|
||||
| принимает несколько решений в электронной форме — по одному на каждый
|
||||
| шаг ниже. Сначала утверждаются положения о цифровых сервисах и формы
|
||||
| документов кооператива, затем оформляется вступление в Потребительский
|
||||
| кооператив «Восход». После этого вы импортируете пайщиков и объявляете
|
||||
| общее собрание. Для каждого шага платформа сформирует проект повестки и
|
||||
| зафиксирует принятое решение — пройдите все шаги до окончания срока
|
||||
| адаптации.
|
||||
.onboarding__deadline
|
||||
q-icon(name="fa-regular fa-clock" size="15px")
|
||||
span {{ countdownLabel ? `Срок адаптации: ${countdownLabel}` : 'Отсчёт появится после получения данных' }}
|
||||
|
||||
color="primary"
|
||||
text-color="white"
|
||||
icon="schedule"
|
||||
) {{ countdownLabel || 'Отсчёт появится после получения данных' }}
|
||||
q-separator
|
||||
q-card-section
|
||||
q-list(separator)
|
||||
q-item(v-for="(step, index) in steps" :key="step.key")
|
||||
q-item-section
|
||||
div.row.items-center.q-gutter-sm
|
||||
q-icon(
|
||||
:name="getIcon(step)"
|
||||
:color="getIconColor(step)"
|
||||
size="22px"
|
||||
q-separator.onboarding__sep
|
||||
|
||||
.stepper.stepper--v
|
||||
.step(
|
||||
v-for="(step, index) in steps"
|
||||
:key="step.key"
|
||||
:class="stepClass(index)"
|
||||
)
|
||||
.step__rail
|
||||
.step__num
|
||||
q-icon(v-if="step.done" name="fa-solid fa-check")
|
||||
template(v-else) {{ index + 1 }}
|
||||
.step__line
|
||||
.step__body
|
||||
.step__label {{ step.title }}
|
||||
.step__sublabel {{ step.description }}
|
||||
.step__meta(v-if="!step.done && step.pending")
|
||||
q-icon(name="fa-solid fa-hourglass-half" size="12px")
|
||||
span {{ getPendingText(step) }}
|
||||
|
||||
.step__action(v-if="showAction(index)")
|
||||
BaseButton(
|
||||
v-if="step.type === 'agenda'"
|
||||
variant="primary"
|
||||
size="sm"
|
||||
:loading="loadingAction"
|
||||
@click="openAgendaDialog(step)"
|
||||
)
|
||||
div.text-subtitle1 {{ index + 1 }}. {{ step.title }}
|
||||
div.text-caption.text-grey-7 {{ step.description }}
|
||||
q-chip(
|
||||
v-if="!step.done && step.pending"
|
||||
dense
|
||||
color="amber"
|
||||
text-color="black"
|
||||
icon="hourglass_top"
|
||||
class="q-mt-xs"
|
||||
) {{ getPendingText(step) }}
|
||||
q-item-section(side)
|
||||
q-btn(
|
||||
v-if="step.type === 'agenda' && showAction(index)"
|
||||
:disable="loadingAction"
|
||||
color="primary"
|
||||
label="Объявить собрание совета"
|
||||
@click="openAgendaDialog(step)"
|
||||
)
|
||||
template(#icon-left)
|
||||
q-icon(name="fa-solid fa-bullhorn" size="14px")
|
||||
| Объявить собрание совета
|
||||
|
||||
q-btn(
|
||||
v-else-if="step.type === 'import' && showAction(index)"
|
||||
:disable="loadingAction || isActionDisabled(index)"
|
||||
color="primary"
|
||||
icon="upload_file"
|
||||
push
|
||||
label="Импорт пайщиков"
|
||||
@click="importDialog = true"
|
||||
)
|
||||
q-tooltip(
|
||||
v-if="isActionDisabled(index)"
|
||||
anchor="top middle"
|
||||
self="bottom middle"
|
||||
) Для продолжения примите решения советом
|
||||
q-btn(
|
||||
v-else-if="step.type === 'meet' && showAction(index)"
|
||||
:disable="loadingAction || isActionDisabled(index)"
|
||||
color="primary"
|
||||
label="Объявить общее собрание"
|
||||
@click="openMeetDialog"
|
||||
)
|
||||
q-tooltip(
|
||||
v-if="isActionDisabled(index)"
|
||||
anchor="top middle"
|
||||
self="bottom middle"
|
||||
) Для продолжения примите решения советом
|
||||
BaseButton(
|
||||
v-else-if="step.type === 'import'"
|
||||
variant="primary"
|
||||
size="sm"
|
||||
:disabled="loadingAction || isActionDisabled(index)"
|
||||
@click="importDialog = true"
|
||||
)
|
||||
template(#icon-left)
|
||||
q-icon(name="fa-solid fa-file-arrow-up" size="14px")
|
||||
| Импорт пайщиков
|
||||
q-tooltip(
|
||||
v-if="isActionDisabled(index)"
|
||||
anchor="top middle"
|
||||
self="bottom middle"
|
||||
) Для продолжения примите решения советом
|
||||
|
||||
q-dialog(v-model="agendaDialog.open" persistent)
|
||||
ModalBase(title="Предложение повестки" @close="closeAgendaDialog" style="min-width: 640px")
|
||||
template(#default)
|
||||
q-card-section
|
||||
div.row.items-center.q-gutter-xs.text-subtitle1.text-weight-medium
|
||||
q-icon(name="help_outline" size="18px" class="text-primary")
|
||||
span Вопрос на повестке
|
||||
div.q-mt-sm.q-pa-sm.text-body1.rounded-borders {{ agendaDialog.question }}
|
||||
BaseButton(
|
||||
v-else-if="step.type === 'meet'"
|
||||
variant="primary"
|
||||
size="sm"
|
||||
:disabled="loadingAction || isActionDisabled(index)"
|
||||
@click="openMeetDialog"
|
||||
)
|
||||
template(#icon-left)
|
||||
q-icon(name="fa-solid fa-users" size="14px")
|
||||
| Объявить общее собрание
|
||||
q-tooltip(
|
||||
v-if="isActionDisabled(index)"
|
||||
anchor="top middle"
|
||||
self="bottom middle"
|
||||
) Для продолжения примите решения советом
|
||||
|
||||
q-separator.q-my-md
|
||||
BaseDialog(
|
||||
:model-value="agendaDialog.open"
|
||||
size="lg"
|
||||
:title="agendaDialog.title || 'Предложение повестки'"
|
||||
:close-on-backdrop="!loadingAction"
|
||||
:close-on-escape="!loadingAction"
|
||||
@update:model-value="onAgendaDialogToggle"
|
||||
)
|
||||
.agenda-dialog
|
||||
.agenda-dialog__section
|
||||
.agenda-dialog__label
|
||||
q-icon(name="fa-regular fa-circle-question" size="16px")
|
||||
span Вопрос на повестке
|
||||
.agenda-dialog__question {{ agendaDialog.question }}
|
||||
|
||||
div.row.items-center.q-gutter-xs.text-subtitle1.text-weight-medium
|
||||
q-icon(name="gavel" size="18px" class="text-primary")
|
||||
span Проект решения
|
||||
div.q-mt-sm.q-pa-sm.rounded-borders
|
||||
DocumentHtmlReader(:html="agendaDialog.decision" :sanitize="false")
|
||||
div.q-pb-lg
|
||||
q-btn(flat label="Отмена" @click="closeAgendaDialog" :disable="loadingAction")
|
||||
q-btn(color="primary" label="Объявить" :loading="loadingAction" @click="submitAgenda")
|
||||
.agenda-dialog__section
|
||||
.agenda-dialog__label
|
||||
q-icon(name="fa-solid fa-gavel" size="16px")
|
||||
span Проект решения
|
||||
.agenda-dialog__decision
|
||||
DocumentHtmlReader(:html="agendaDialog.decision" :sanitize="false")
|
||||
|
||||
template(#footer)
|
||||
BaseButton(
|
||||
variant="ghost"
|
||||
:disabled="loadingAction"
|
||||
@click="closeAgendaDialog"
|
||||
) Отмена
|
||||
BaseButton(
|
||||
variant="primary"
|
||||
:loading="loadingAction"
|
||||
@click="submitAgenda"
|
||||
)
|
||||
template(#icon-left)
|
||||
q-icon(name="fa-solid fa-bullhorn" size="15px")
|
||||
| Объявить
|
||||
|
||||
CreateMeetForm(
|
||||
v-model="meetDialog"
|
||||
@@ -114,8 +144,9 @@ import { onMounted, ref } from 'vue';
|
||||
import { CreateMeetForm } from 'src/features/Meet/CreateMeet/ui';
|
||||
import { ParticipantsImportDialog } from 'src/features/User/ImportParticipants/ui';
|
||||
import { DocumentHtmlReader } from 'src/shared/ui/DocumentHtmlReader';
|
||||
import { ModalBase } from 'src/shared/ui/ModalBase';
|
||||
import { WindowLoader } from 'src/shared/ui/Loader';
|
||||
import { BaseButton } from 'src/shared/ui/base/BaseButton';
|
||||
import { BaseDialog } from 'src/shared/ui/base/BaseDialog';
|
||||
import OnboardingCompletionCelebration from './OnboardingCompletionCelebration.vue';
|
||||
import { useOnboardingFlow } from '../model';
|
||||
|
||||
@@ -138,18 +169,11 @@ const {
|
||||
|
||||
const importDialog = ref(false);
|
||||
|
||||
const getIcon = (step: { done: boolean; pending: boolean; type: string }) => {
|
||||
if (step.done) return 'task_alt';
|
||||
if (step.pending) return 'hourglass_top';
|
||||
if (step.type === 'import') return 'arrow_forward';
|
||||
return 'radio_button_unchecked';
|
||||
};
|
||||
|
||||
const getIconColor = (step: { done: boolean; pending: boolean; type: string }) => {
|
||||
if (step.done) return 'green-6';
|
||||
if (step.pending) return 'orange-6';
|
||||
if (step.type === 'import') return '';
|
||||
return 'grey-6';
|
||||
// Закрытие диалога повестки (крестик / клик по фону / Escape) сбрасывает
|
||||
// его состояние через closeAgendaDialog. Открытие идёт только через
|
||||
// кнопку шага (openAgendaDialog), поэтому здесь обрабатываем лишь закрытие.
|
||||
const onAgendaDialogToggle = (open: boolean) => {
|
||||
if (!open) closeAgendaDialog();
|
||||
};
|
||||
|
||||
const getPendingText = (step: { type: string }) => {
|
||||
@@ -191,5 +215,104 @@ const isActionDisabled = (index: number) => {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Канон-степпер: пройденный шаг — is-done (галочка, заливка primary),
|
||||
// текущий доступный к действию — is-active (подсветка номера). Ожидающие
|
||||
// решения и ещё недоступные шаги — нейтральные.
|
||||
const stepClass = (index: number) => {
|
||||
const step = steps.value[index];
|
||||
if (!step) return '';
|
||||
if (step.done) return 'is-done';
|
||||
if (showAction(index)) return 'is-active';
|
||||
return '';
|
||||
};
|
||||
|
||||
onMounted(init);
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.onboarding {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.onboarding__head {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--p-3, 12px);
|
||||
}
|
||||
.onboarding__title {
|
||||
margin: 0;
|
||||
font-size: var(--p-fs-h2);
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--p-ink);
|
||||
}
|
||||
.onboarding__lead {
|
||||
margin: 0;
|
||||
font-size: var(--p-fs-body);
|
||||
line-height: 1.55;
|
||||
color: var(--p-ink-2);
|
||||
}
|
||||
// Разделитель между шапкой и степпером — канон-воздух сверху/снизу.
|
||||
.onboarding__sep {
|
||||
margin: var(--p-5, 20px) 0;
|
||||
}
|
||||
// Действие шага — без обрамляющей рамки, просто отступ сверху.
|
||||
.step__action {
|
||||
margin-top: var(--p-3, 12px);
|
||||
}
|
||||
// Срок адаптации — спокойная плашка-предупреждение, отсчёт виден сразу.
|
||||
.onboarding__deadline {
|
||||
align-self: flex-start;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 12px;
|
||||
border-radius: var(--p-r-pill, 999px);
|
||||
background: var(--p-warn-soft);
|
||||
color: var(--p-warn);
|
||||
font-size: var(--p-fs-body-sm);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
// Диалог повестки.
|
||||
.agenda-dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--p-5, 20px);
|
||||
}
|
||||
.agenda-dialog__section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--p-2, 8px);
|
||||
}
|
||||
// Подписи секций — надстрочные метки (eyebrow): отдельный типографический
|
||||
// ярус ниже заголовка окна и заметно отличный от него, чтобы не казаться
|
||||
// конкурирующим заголовком на фоне крупного заголовка самого документа.
|
||||
.agenda-dialog__label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
font-size: var(--p-fs-body-sm);
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--p-ink-3);
|
||||
|
||||
.q-icon {
|
||||
color: var(--p-primary);
|
||||
}
|
||||
}
|
||||
.agenda-dialog__question {
|
||||
font-size: var(--p-fs-body);
|
||||
line-height: 1.5;
|
||||
color: var(--p-ink);
|
||||
}
|
||||
.agenda-dialog__decision {
|
||||
padding: var(--p-4, 16px);
|
||||
background: var(--p-surface-2);
|
||||
border: 1px solid var(--p-line);
|
||||
border-radius: var(--p-r-sm, 8px);
|
||||
max-height: 50vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
+53
-39
@@ -1,43 +1,41 @@
|
||||
<template lang="pug">
|
||||
form.full-width(@submit.prevent='submit')
|
||||
.q-gutter-md
|
||||
// Провайдер платежей по умолчанию
|
||||
.row.q-gutter-md
|
||||
.col-md-6.col-sm-12
|
||||
q-select(
|
||||
v-model='formData.provider_name'
|
||||
:options='providerOptions'
|
||||
label='Провайдер входящих платежей'
|
||||
placeholder='Выберите провайдера'
|
||||
dense
|
||||
standout="bg-teal text-white"
|
||||
emit-value
|
||||
map-options
|
||||
option-value='value'
|
||||
option-label='label'
|
||||
:loading='loading'
|
||||
)
|
||||
form.pp-form(@submit.prevent='submit')
|
||||
q-select.pp-form__select(
|
||||
v-model='formData.provider_name'
|
||||
:options='providerOptions'
|
||||
label='Провайдер входящих платежей'
|
||||
placeholder='Выберите провайдера'
|
||||
dense
|
||||
outlined
|
||||
color='primary'
|
||||
emit-value
|
||||
map-options
|
||||
option-value='value'
|
||||
option-label='label'
|
||||
:loading='loading'
|
||||
)
|
||||
|
||||
.col-md-12(v-if='selectedProviderDescription')
|
||||
.q-mt-sm.text-caption.text-grey-7 {{ selectedProviderDescription }}
|
||||
.pp-form__hint(v-if='selectedProviderDescription') {{ selectedProviderDescription }}
|
||||
|
||||
.q-mt-md
|
||||
.row.justify-start.q-gutter-sm
|
||||
q-btn(
|
||||
flat
|
||||
label='Отменить'
|
||||
color='grey'
|
||||
@click='resetForm'
|
||||
:loading='loading'
|
||||
:disable='!hasChanges'
|
||||
)
|
||||
q-btn(
|
||||
type='submit'
|
||||
label='Сохранить'
|
||||
color='primary'
|
||||
:loading='loading'
|
||||
:disable='!hasChanges'
|
||||
)
|
||||
.pp-form__actions
|
||||
q-btn(
|
||||
flat
|
||||
no-caps
|
||||
label='Отменить'
|
||||
color='grey-7'
|
||||
@click='resetForm'
|
||||
:loading='loading'
|
||||
:disable='!hasChanges'
|
||||
)
|
||||
q-btn(
|
||||
type='submit'
|
||||
no-caps
|
||||
unelevated
|
||||
label='Сохранить'
|
||||
color='primary'
|
||||
:loading='loading'
|
||||
:disable='!hasChanges'
|
||||
)
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -133,8 +131,24 @@ const submit = async () => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.full-width {
|
||||
<style scoped lang="scss">
|
||||
.pp-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--p-4, 16px);
|
||||
width: 100%;
|
||||
}
|
||||
.pp-form__select {
|
||||
max-width: 480px;
|
||||
}
|
||||
.pp-form__hint {
|
||||
max-width: 640px;
|
||||
font-size: var(--p-fs-body-sm);
|
||||
line-height: 1.5;
|
||||
color: var(--p-ink-2);
|
||||
}
|
||||
.pp-form__actions {
|
||||
display: flex;
|
||||
gap: var(--p-2, 8px);
|
||||
}
|
||||
</style>
|
||||
|
||||
+8
-4
@@ -10,7 +10,8 @@ form.full-width(@submit.prevent='submit')
|
||||
label='Рабочий стол для авторизованных пользователей'
|
||||
placeholder='Выберите рабочий стол'
|
||||
dense
|
||||
standout="bg-teal text-white"
|
||||
outlined
|
||||
color="primary"
|
||||
emit-value
|
||||
map-options
|
||||
option-value='value'
|
||||
@@ -26,7 +27,8 @@ form.full-width(@submit.prevent='submit')
|
||||
label='Страница для авторизованных пользователей'
|
||||
placeholder='Выберите страницу'
|
||||
dense
|
||||
standout="bg-teal text-white"
|
||||
outlined
|
||||
color="primary"
|
||||
emit-value
|
||||
map-options
|
||||
option-value='value'
|
||||
@@ -45,7 +47,8 @@ form.full-width(@submit.prevent='submit')
|
||||
label='Рабочий стол для неавторизованных пользователей'
|
||||
placeholder='Выберите рабочий стол'
|
||||
dense
|
||||
standout="bg-teal text-white"
|
||||
outlined
|
||||
color="primary"
|
||||
emit-value
|
||||
map-options
|
||||
option-value='value'
|
||||
@@ -61,7 +64,8 @@ form.full-width(@submit.prevent='submit')
|
||||
label='Страница для неавторизованных пользователей'
|
||||
placeholder='Выберите страницу'
|
||||
dense
|
||||
standout="bg-teal text-white"
|
||||
outlined
|
||||
color="primary"
|
||||
emit-value
|
||||
map-options
|
||||
option-value='value'
|
||||
|
||||
@@ -77,7 +77,7 @@ export default async function (): Promise<IWorkspaceConfig[]> {
|
||||
name: 'extensions',
|
||||
component: markRaw(ExtensionsManagement),
|
||||
meta: {
|
||||
title: 'Магазин приложений',
|
||||
title: 'Каталог приложений',
|
||||
icon: 'fa-solid fa-puzzle-piece',
|
||||
roles: ['chairman'],
|
||||
agreements: agreementsBase,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user