refactor(marketplace): drop blocked + simplify per review (rename block→lock, no w.mkt.member, no 91-transit)

Переделка PR #41 по двум замечаниям ревью @ant 2026-05-28:

(1) Согласованность имён operation_id с кошельком w.mkt.order:
  o.mkt.block / BLOCK_FOR_ORDER   → o.mkt.lock   / LOCK_ORDER
  o.mkt.unblk / UNBLOCK_ON_CANCEL → o.mkt.unlock / UNLOCK_ORDER
  CONSUME_BY_MEMBER оставлено (consume — это про BURN, не про lock).

(2) Упрощение архитектуры — убрать каскады и счёт 91:
  - Удалён кошелёк w.mkt.member (MARKETPLACE_MEMBER) — промежуточного
    программного членского нет; источник и приёмник средств — w.wal.member.
  - Удалены операции o.mkt.assign / o.mkt.recall (каскад createorder
    схлопнут с 3 шагов до 2: conv condi + lock).
  - Удалены парные *2-операции o.mkt.consum2 / o.mkt.return2 / o.mkt.wroff2 —
    счёт 91 (транзит) в marketplace больше не используется.
  - Все 3 «выбытие/возврат имущества» операции — одним вызовом через 86:
    o.mkt.consum: BURN w.mkt.order, Дт 86 / Кт 10 (было: Дт 91 / Кт 10 + 86 / 91)
    o.mkt.return: ISSUE → w.wal.member, Дт 10 / Кт 86 (было: Дт 91 / Кт 86 + 10 / 91)
    o.mkt.wroff:  NONE, Дт 86 / Кт 10 (было: Дт 91 / Кт 10 + 86 / 91)
  - Возврат при cancel/decline/expire — на w.wal.member (универсальный
    членский), а не на бывший w.mkt.member.

Итого marketplace ledger2-операций: было 13 → стало 8.
LEDGER2_WALLET_REGISTRY: 16 → 15. USER_SHARED program-mapping: 8 → 7.

Затронуто:
- C++ ledger2: wallets.hpp, operations.hpp
- C++ marketplace .cpp: createorder, cancelorder, declineorder, expireorder,
  signiss2 (включая special cases недо/переплаты), accretrn, execwroff
- TS controller: marketplace-member-wallet.{dto,resolver,test}.ts — 3 кошелька
  в выдаче вместо 4 (w.mkt.member выпал)
- TS invariants: I5/I6 переписаны под o.mkt.lock/unlock/consum, I4 для
  marketplace тривиально пройден (91 не задевается); 27/29 тестов passed,
  2 fail остаются pre-existing baseline (cooptypes/operations.ts Story 11.1)
- Cooptypes: wallets.generated.ts regen (wallets=15, mapping=7)
- Schema/zeus/sdk: regen, только description-only diff
- YAML p.mkt.supply.standard.yaml: обновлён header под reserve-flow + 6 ops

NOT в scope этого фикса (TODO follow-up):
- Полный rewrite секций операций в p.mkt.{supply,return,wroff}.standard.yaml
  (детальные блоки операций remain с упоминаниями o.mkt.block/unblk/w.mkt.member —
  бизнес-документация для бухгалтера, не влияет на компиляцию контракта)
- Memo-функции в marketplace.hpp для удалённых операций (assign_memo,
  consum_transit_close_memo и т.д.) — мёртвый код, в C++ unused functions OK

Проверки (tsc запрещён локально — см. memory mono-no-local-tsc):
- jest marketplace-member-wallet-resolver: 4/4 passed
- jest marketplace-ledger2-invariants: 27/29 passed (2 pre-existing baseline)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-28 13:43:18 +05:00
parent fd458a4e35
commit a550e06802
19 changed files with 276 additions and 521 deletions
@@ -83,23 +83,19 @@ namespace operations {
inline constexpr eosio::name CONVERT_TO_BLAGO = "o.cap.cnvbl"_n; ///< Конвертация сегмента: РИД → ЦПП «Благорост» (TRANSFER GENERATOR_FUND → BLAGOROST_FUND, без Dr/Cr — бухпроводка уже была сделана в ACCEPT_RID).
}
// marketplace — членская модель «Стола заказов» (Эпик 11; восстановлено из 0a8e226417a
// после кривого merge, который откатил namespace к legacy o.mkt.supply/recv).
// Composite-проводки через транзит счёта 91 разбиты на пары операций (*2-суффикс),
// вызываемые последовательно в одной транзакции Antelope — атомарность через транзакцию.
// marketplace — членская модель «Стола заказов».
// Упрощённая схема 2026-05-28: средства движутся через универсальный членский
// (w.wal.member) ↔ резерв под Order (w.mkt.order) без промежуточного программного
// кошелька; счёт 91 (транзит) больше не используется — выбытие/возврат имущества
// идут напрямую между 10 (Материалы) и 86 (Целевое финансирование).
namespace marketplace {
inline constexpr eosio::name ASSIGN_TO_PROGRAM = "o.mkt.assign"_n; ///< Целевое назначение членского взноса пайщика в программу Marketplace (TRANSFER CK_MEMBER → MARKETPLACE_MEMBER, без проводки — оба кошелька на счёте 86). Conditional-шаг серии createorder.
inline constexpr eosio::name BLOCK_FOR_ORDER = "o.mkt.block"_n; ///< Резервирование членского взноса заказчика под конкретный Order (TRANSFER MARKETPLACE_MEMBER → MARKETPLACE_ORDER_LOCK, без Dr/Cr — оба кошелька на 86). Имя o.mkt.block сохранено для совместимости op_code в blockchain_actions; семантика — резерв через отдельный кошелёк, не BLOCK.
inline constexpr eosio::name UNBLOCK_ON_CANCEL = "o.mkt.unblk"_n; ///< Снятие резерва при отмене Order'а (TRANSFER MARKETPLACE_ORDER_LOCK → MARKETPLACE_MEMBER, без Dr/Cr). Сумма возвращается на .available членского и может быть потрачена на следующий заказ в программе. Имя o.mkt.unblk сохранено для совместимости op_code.
inline constexpr eosio::name RECALL_TO_UNIVERSAL = "o.mkt.recall"_n; ///< Вывод программного членского взноса в универсальный членский кошелёк пайщика (TRANSFER MARKETPLACE_MEMBER → CK_MEMBER, без проводки — оба кошелька на счёте 86). Явное действие пайщика, не часть авто-flow отмены.
inline constexpr eosio::name PURCHASE_FROM_SUPPLIER = "o.mkt.purch"_n; ///< Приёмка имущества кооперативом по АПП приёмки от поставщика (Dr 10 / Cr 86, NONE — только бухпроводка, кошельки не двигаются; имущество — аналитика по 10). Атомарно с PAY_SUPPLIER на закрывающей подписи председателя.
inline constexpr eosio::name PAY_SUPPLIER = "o.mkt.payout"_n; ///< Оплата поставщику с расчётного счёта по факту приёмки (Dr 86 / Cr 51, ISSUE ∅ → SUPPLIER_PAYMENTS). Атомарно с PURCHASE_FROM_SUPPLIER.
inline constexpr eosio::name CONSUME_BY_MEMBER = "o.mkt.consum"_n; ///< Выдача имущества пайщику по АПП выдачи (часть 1 композитной проводки через транзит 91): Dr 91 / Cr 10, BURN с MARKETPLACE_ORDER_LOCK — сжигание резерва заказа (целевой расход членского) + выбытие имущества со склада на «прочие». Закрытие транзита — отдельной операцией CONSUME_TRANSIT_CLOSE, атомарно в той же транзакции signiss2.
inline constexpr eosio::name CONSUME_TRANSIT_CLOSE = "o.mkt.consum2"_n; ///< Выдача имущества пайщику (часть 2 композитной проводки): Dr 86 / Cr 91, NONE — закрытие транзита 91 на счёт ЦФ программы. Срабатывает после CONSUME_BY_MEMBER в той же транзакции.
inline constexpr eosio::name RETURN_BY_MEMBER = "o.mkt.return"_n; ///< Гарантийный возврат имущества пайщиком — compensating forward к CONSUME_BY_MEMBER (часть 1 композитной проводки): Dr 91 / Cr 86, ISSUE ∅ → MARKETPLACE_MEMBER — восстановление «прочих» за счёт ЦФ + восстановление .available заказчика. Реверты ledger2::revert в Столе заказов не используются.
inline constexpr eosio::name RETURN_TRANSIT_CLOSE = "o.mkt.return2"_n; ///< Гарантийный возврат (часть 2 композитной проводки): Dr 10 / Cr 91, NONE — имущество назад на склад через закрытие транзита. Срабатывает после RETURN_BY_MEMBER в той же транзакции decretvisit.
inline constexpr eosio::name WRITE_OFF_PERISHABLE = "o.mkt.wroff"_n; ///< Утилизация скоропорта со склада (часть 1 композитной проводки): Dr 91 / Cr 10, NONE — выбытие со склада на «прочие». По протоколу совета.
inline constexpr eosio::name WRITE_OFF_TRANSIT_CLOSE = "o.mkt.wroff2"_n; ///< Утилизация скоропорта (часть 2 композитной проводки): Dr 86 / Cr 91, NONE — закрытие транзита 91 на счёт ЦФ программы. Срабатывает после WRITE_OFF_PERISHABLE в той же транзакции execwroff.
inline constexpr eosio::name LOCK_ORDER = "o.mkt.lock"_n; ///< Резервирование членского взноса заказчика под конкретный Order (TRANSFER w.wal.member → w.mkt.order, без Dr/Cr — оба кошелька на 86). Единственный шаг ledger2 при createorder (плюс conditional o.wal.conv если на w.wal.member недостаточно).
inline constexpr eosio::name UNLOCK_ORDER = "o.mkt.unlock"_n; ///< Снятие резерва при отмене Order'а (TRANSFER w.mkt.order → w.wal.member, без Dr/Cr). Средства возвращаются на универсальный членский — могут быть потрачены на следующий заказ либо выведены пайщиком на паевой через wallet-операцию.
inline constexpr eosio::name PURCHASE_FROM_SUPPLIER = "o.mkt.purch"_n; ///< Приёмка имущества кооперативом по АПП приёмки от поставщика (Dr 10 / Cr 86, NONE — только бухпроводка, кошельки не двигаются; имущество — аналитика по 10). Атомарно с PAY_SUPPLIER на закрывающей подписи председателя.
inline constexpr eosio::name PAY_SUPPLIER = "o.mkt.payout"_n; ///< Оплата поставщику с расчётного счёта по факту приёмки (Dr 86 / Cr 51, ISSUE ∅ → SUPPLIER_PAYMENTS). Атомарно с PURCHASE_FROM_SUPPLIER.
inline constexpr eosio::name CONSUME_BY_MEMBER = "o.mkt.consum"_n; ///< Выдача имущества пайщику по АПП выдачи (BURN с w.mkt.order, Dr 86 / Cr 10 — сжигание резерва заказа + выбытие имущества со склада через целевое финансирование, одной операцией без транзита 91).
inline constexpr eosio::name RETURN_BY_MEMBER = "o.mkt.return"_n; ///< Гарантийный возврат имущества пайщиком — compensating forward к CONSUME_BY_MEMBER (ISSUE ∅ → w.wal.member, Dr 10 / Cr 86 — восстановление средств на универсальном членском заказчика + возврат имущества на склад через целевое финансирование, одной операцией без транзита 91). Реверты ledger2::revert в Столе заказов не используются.
inline constexpr eosio::name WRITE_OFF_PERISHABLE = "o.mkt.wroff"_n; ///< Утилизация скоропорта со склада (NONE Dr 86 / Cr 10, одной операцией без транзита 91). По протоколу совета.
}
// soviet
@@ -271,108 +267,72 @@ static constexpr OperationRegistryEntry OPERATION_REGISTRY[] = {
"Возврат беспроцентного займа пайщика по акту-2" },
// 12a. p.mkt.supply: Конвертация цифрового рубля в членский (Dr 80 / Cr 86,
// TRANSFER SHARE_FUND_PAY → CK_MEMBER). Conditional-шаг серии createorder.
// TRANSFER SHARE_FUND_PAY → CK_MEMBER). Conditional-шаг createorder/signiss2 —
// срабатывает только если на w.wal.member.available не хватает суммы.
{ operations::wallet::CONVERT_TO_MEMBER, processes::marketplace::SUPPLY, WalletOp::TRANSFER,
ledger2_wallets::SHARE_FUND_PAY, ledger2_wallets::CK_MEMBER,
ledger2_accounts::SHARE_FUND, ledger2_accounts::TARGET_RECEIPTS,
"Конвертация цифрового рубля в членский кошелёк пайщика" },
// 12b. p.mkt.supply: Целевое назначение членского в программу Marketplace
// (TRANSFER w.wal.member → w.mkt.member, без Dr/Cr — оба на 86, аналитика на L2).
// Conditional-шаг серии createorder.
{ operations::marketplace::ASSIGN_TO_PROGRAM, processes::marketplace::SUPPLY, WalletOp::TRANSFER,
ledger2_wallets::CK_MEMBER, ledger2_wallets::MARKETPLACE_MEMBER,
0, 0,
"Целевое назначение членского взноса в программу «Стол заказов»" },
// 12c. p.mkt.supply: Резервирование под Order (TRANSFER w.mkt.member → w.mkt.order,
// без Dr/Cr — оба кошелька на счёте 86). Заменяет механику BLOCK 2026-05-28:
// резерв через отдельный кошелёк, а не через .blocked одного кошелька.
{ operations::marketplace::BLOCK_FOR_ORDER, processes::marketplace::SUPPLY, WalletOp::TRANSFER,
ledger2_wallets::MARKETPLACE_MEMBER, ledger2_wallets::MARKETPLACE_ORDER_LOCK,
// 12b. p.mkt.supply: Резервирование под Order (TRANSFER w.wal.member → w.mkt.order,
// без Dr/Cr — оба кошелька на счёте 86). Единственный обязательный шаг при
// createorder; для signiss2 — на разницу при actual > ordered.
{ operations::marketplace::LOCK_ORDER, processes::marketplace::SUPPLY, WalletOp::TRANSFER,
ledger2_wallets::CK_MEMBER, ledger2_wallets::MARKETPLACE_ORDER_LOCK,
0, 0,
"Резервирование членского взноса под заказ" },
// 12d. p.mkt.supply: Снятие резерва при отмене Order'а (TRANSFER w.mkt.order → w.mkt.member,
// без Dr/Cr — зеркало 12c). Срабатывает на cancelorder / expirecycle / declinebatch.
// Сумма возвращается на w.mkt.member.available и может быть потрачена на следующий заказ.
{ operations::marketplace::UNBLOCK_ON_CANCEL, processes::marketplace::SUPPLY, WalletOp::TRANSFER,
ledger2_wallets::MARKETPLACE_ORDER_LOCK, ledger2_wallets::MARKETPLACE_MEMBER,
// 12c. p.mkt.supply: Снятие резерва (TRANSFER w.mkt.order → w.wal.member,
// без Dr/Cr — зеркало 12b). Срабатывает на cancelorder / declineorder /
// expireorder; для signiss2 — на разницу при actual < ordered. Средства
// возвращаются на универсальный членский — могут быть потрачены на
// следующий заказ либо выведены пайщиком на паевой через wallet-операцию.
{ operations::marketplace::UNLOCK_ORDER, processes::marketplace::SUPPLY, WalletOp::TRANSFER,
ledger2_wallets::MARKETPLACE_ORDER_LOCK, ledger2_wallets::CK_MEMBER,
0, 0,
"Снятие резерва членского взноса при отмене заказа" },
// 12e. p.mkt.supply: Вывод программного членского в универсальный членский
// (TRANSFER w.mkt.member → w.wal.member, без Dr/Cr — оба на 86).
// Явное действие пайщика — не часть авто-flow отмены.
{ operations::marketplace::RECALL_TO_UNIVERSAL, processes::marketplace::SUPPLY, WalletOp::TRANSFER,
ledger2_wallets::MARKETPLACE_MEMBER, ledger2_wallets::CK_MEMBER,
0, 0,
"Вывод членского взноса в универсальный членский кошелёк" },
// 12f. p.mkt.supply: Приёмка имущества кооперативом по АПП приёмки
// 12d. p.mkt.supply: Приёмка имущества кооперативом по АПП приёмки
// (Dr 10 / Cr 86, NONE — только бухпроводка, кошельки не двигаются).
// Имущество — аналитикой по счёту 10 (per-КУ субсчета), без отдельного кошелька.
// Атомарно с PAYOUT на закрывающей подписи председателя АПП приёмки.
// Атомарно с PAY_SUPPLIER на закрывающей подписи председателя АПП приёмки.
{ operations::marketplace::PURCHASE_FROM_SUPPLIER, processes::marketplace::SUPPLY, WalletOp::NONE,
eosio::name{}, eosio::name{},
ledger2_accounts::MATERIALS, ledger2_accounts::TARGET_RECEIPTS,
"Приёмка имущества кооперативом по АПП приёмки" },
// 12g. p.mkt.supply: Оплата поставщику с расчётного счёта
// (Dr 86 / Cr 51, ISSUE ∅ → w.mkt.payout). Атомарно с PURCH.
// 12e. p.mkt.supply: Оплата поставщику с расчётного счёта
// (Dr 86 / Cr 51, ISSUE ∅ → w.mkt.payout). Атомарно с PURCHASE_FROM_SUPPLIER.
{ operations::marketplace::PAY_SUPPLIER, processes::marketplace::SUPPLY, WalletOp::ISSUE,
eosio::name{}, ledger2_wallets::SUPPLIER_PAYMENTS,
ledger2_accounts::TARGET_RECEIPTS, ledger2_accounts::BANK_ACCOUNT,
"Оплата поставщику с расчётного счёта по факту приёмки" },
// 12h. p.mkt.supply: Выдача имущества пайщику по АПП выдачи — часть 1.
// BURN с w.mkt.order → 0 (сжигание резерва — целевой расход членского) +
// Dr 91 / Cr 10 (выбытие имущества со склада на «прочие»).
// Согласовано с Ангелиной 2026-05-11. Атомарность с CONSUME_TRANSIT_CLOSE
// обеспечивается транзакцией Antelope — контракт вызывает обе операции
// последовательно в signiss2. (BURN_BLOCKED → BURN с резерва 2026-05-28.)
// 12f. p.mkt.supply: Выдача имущества пайщику по АПП выдачи
// (BURN с w.mkt.order, Dr 86 / Cr 10 — сжигание резерва заказа + выбытие
// имущества со склада через целевое финансирование, одной операцией без
// транзита 91). Согласовано с Ангелиной 2026-05-11.
{ operations::marketplace::CONSUME_BY_MEMBER, processes::marketplace::SUPPLY, WalletOp::BURN,
ledger2_wallets::MARKETPLACE_ORDER_LOCK, eosio::name{},
ledger2_accounts::OTHER_INCOME_EXPENSES, ledger2_accounts::MATERIALS,
"Выдача имущества пайщику по АПП выдачи — выбытие со склада" },
ledger2_accounts::TARGET_RECEIPTS, ledger2_accounts::MATERIALS,
"Выдача имущества пайщику по АПП выдачи" },
// 12h2. p.mkt.supply: Выдача — часть 2. NONE Dr 86 / Cr 91 (закрытие
// транзита 91 на счёт ЦФ программы). Кошельки не двигаются.
{ operations::marketplace::CONSUME_TRANSIT_CLOSE, processes::marketplace::SUPPLY, WalletOp::NONE,
eosio::name{}, eosio::name{},
ledger2_accounts::TARGET_RECEIPTS, ledger2_accounts::OTHER_INCOME_EXPENSES,
"Выдача имущества пайщику — закрытие транзита на ЦФ программы" },
// 12i. p.mkt.return: Гарантийный возврат — часть 1. ISSUE ∅ → w.mkt.member
// (восстановление средств заказчика в программе) + Dr 91 / Cr 86
// (восстановление «прочих» за счёт ЦФ — зеркало 12h2). Compensating
// forward к CONSUM; ledger2::revert в Столе заказов не используется.
// Атомарность с RETURN_TRANSIT_CLOSE — транзакция Antelope в decretvisit.
// 12g. p.mkt.return: Гарантийный возврат имущества пайщиком
// (ISSUE ∅ → w.wal.member, Dr 10 / Cr 86 — восстановление средств на
// универсальном членском заказчика + возврат имущества на склад через
// целевое финансирование, одной операцией без транзита 91). Compensating
// forward к CONSUME_BY_MEMBER; ledger2::revert в Столе заказов не используется.
{ operations::marketplace::RETURN_BY_MEMBER, processes::marketplace::RETURN, WalletOp::ISSUE,
eosio::name{}, ledger2_wallets::MARKETPLACE_MEMBER,
ledger2_accounts::OTHER_INCOME_EXPENSES, ledger2_accounts::TARGET_RECEIPTS,
"Гарантийный возврат — восстановление средств в программе" },
eosio::name{}, ledger2_wallets::CK_MEMBER,
ledger2_accounts::MATERIALS, ledger2_accounts::TARGET_RECEIPTS,
"Гарантийный возврат — восстановление средств и имущества" },
// 12i2. p.mkt.return: Гарантийный возврат — часть 2. NONE Dr 10 / Cr 91
// (имущество назад на склад через транзит — зеркало 12h).
{ operations::marketplace::RETURN_TRANSIT_CLOSE, processes::marketplace::RETURN, WalletOp::NONE,
eosio::name{}, eosio::name{},
ledger2_accounts::MATERIALS, ledger2_accounts::OTHER_INCOME_EXPENSES,
"Гарантийный возврат — имущество назад на склад через транзит" },
// 12j. p.mkt.wroff: Утилизация скоропорта — часть 1. NONE Dr 91 / Cr 10
// (выбытие со склада на «прочие»). По протоколу совета.
// 12h. p.mkt.wroff: Утилизация скоропорта со склада (NONE Dr 86 / Cr 10,
// одной операцией без транзита 91). По протоколу совета.
{ operations::marketplace::WRITE_OFF_PERISHABLE, processes::marketplace::WRITEOFF, WalletOp::NONE,
eosio::name{}, eosio::name{},
ledger2_accounts::OTHER_INCOME_EXPENSES, ledger2_accounts::MATERIALS,
"Утилизация скоропорта — выбытие со склада" },
// 12j2. p.mkt.wroff: Утилизация скоропорта — часть 2. NONE Dr 86 / Cr 91
// (закрытие транзита). Атомарно с WROFF в execwroff.
{ operations::marketplace::WRITE_OFF_TRANSIT_CLOSE, processes::marketplace::WRITEOFF, WalletOp::NONE,
eosio::name{}, eosio::name{},
ledger2_accounts::TARGET_RECEIPTS, ledger2_accounts::OTHER_INCOME_EXPENSES,
"Утилизация скоропорта — закрытие транзита на ЦФ программы" },
ledger2_accounts::TARGET_RECEIPTS, ledger2_accounts::MATERIALS,
"Утилизация скоропорта" },
// 14. Конвертация в AXN: Dr 80 / Cr 86, TRANSFER SHARE_FUND_PAY → DELEGATE_FEES
{ operations::soviet::CONVERT_AXN, processes::soviet::AXN_CONVERT, WalletOp::TRANSFER,
@@ -64,9 +64,8 @@ struct ledger2_wallets {
static constexpr eosio::name GENERATOR_FUND = "w.cap.gen"_n; ///< Генератор — единый агрегированный кошелёк программы (COOPERATIVE — кооперативный пул, без L3-разреза по пайщику; L3-разрез из ADR-009 отменён из-за несовместимости с CRPS-перераспределением, см. wallets.hpp:107)
static constexpr eosio::name PREIMP_FUND = "w.cap.preimp"_n; ///< Первичный учёт РИД-взносов до перехода на электронный учёт (USER_SHARED; o.cap.preimp / o.cap.drppre)
// marketplace — программный членский + резерв под Order + выплаты
static constexpr eosio::name MARKETPLACE_MEMBER = "w.mkt.member"_n; ///< ЦПП «Стол Заказов» — программный членский кошелёк пайщика (USER_SHARED; .available = свободные средства программы)
static constexpr eosio::name MARKETPLACE_ORDER_LOCK = "w.mkt.order"_n; ///< ЦПП «Стол Заказов» — резерв средств пайщика под конкретный Order (USER_SHARED). TRANSFER w.mkt.member → w.mkt.order на createorder; обратный TRANSFER на cancel/decline/expire; BURN с w.mkt.order на signiss2 (выдача имущества). Заменяет механику BLOCK/UNBLOCK на w.mkt.member.blocked 2026-05-28.
// marketplace — резерв под Order + выплаты
static constexpr eosio::name MARKETPLACE_ORDER_LOCK = "w.mkt.order"_n; ///< ЦПП «Стол Заказов» — резерв средств пайщика под конкретный Order (USER_SHARED). TRANSFER w.wal.member → w.mkt.order на createorder; обратный TRANSFER на cancel/decline/expire; BURN с w.mkt.order на signiss2 (выдача имущества). Источник и приёмник возврата — универсальный членский w.wal.member; промежуточного программного кошелька w.mkt.member нет (упрощение 2026-05-28).
static constexpr eosio::name SUPPLIER_PAYMENTS = "w.mkt.payout"_n; ///< Выплаты поставщикам (sink PAYOUT, COOPERATIVE)
};
@@ -96,14 +95,13 @@ struct Ledger2WalletMeta {
WalletKind kind;
};
inline constexpr std::array<Ledger2WalletMeta, 16> LEDGER2_WALLET_REGISTRY = {{
// USER_SHARED (7) — L3-разрез по пайщику
inline constexpr std::array<Ledger2WalletMeta, 15> LEDGER2_WALLET_REGISTRY = {{
// USER_SHARED (6) — L3-разрез по пайщику
{ ledger2_wallets::MIN_SHARE_FUND, "Минимальный паевой взнос", WalletKind::USER_SHARED },
{ ledger2_wallets::SHARE_FUND_PAY, "Паевой взнос пайщика", WalletKind::USER_SHARED },
{ ledger2_wallets::CK_MEMBER, "ЦК — членская часть пайщика", WalletKind::USER_SHARED },
{ ledger2_wallets::BLAGOROST_FUND, "ЦПП «Благорост» — единый кошелёк программы у пайщика", WalletKind::USER_SHARED },
{ ledger2_wallets::PREIMP_FUND, "Первичный учёт РИД-взносов до перехода на электронный учёт", WalletKind::USER_SHARED },
{ ledger2_wallets::MARKETPLACE_MEMBER, "ЦПП «Стол Заказов» — программный членский у пайщика", WalletKind::USER_SHARED },
{ ledger2_wallets::MARKETPLACE_ORDER_LOCK,"ЦПП «Стол Заказов» — резерв под заказ у пайщика", WalletKind::USER_SHARED },
// COOPERATIVE (9) — единый кооперативный баланс, без L3
@@ -232,14 +230,13 @@ struct Ledger2WalletProgramMapping {
uint64_t required_program_id; // 0 = исключение (без проверки)
};
inline constexpr std::array<Ledger2WalletProgramMapping, 8> LEDGER2_USER_SHARED_PROGRAM_MAPPING = {{
inline constexpr std::array<Ledger2WalletProgramMapping, 7> LEDGER2_USER_SHARED_PROGRAM_MAPPING = {{
{ ledger2_wallets::MIN_SHARE_FUND, 0 /* w.reg.minshr — без проверки */ },
{ ledger2_wallets::SHARE_FUND_PAY, 1 /* ЦК */ },
{ ledger2_wallets::CK_MEMBER, 1 /* ЦК */ },
{ ledger2_wallets::BLAGOROST_FUND, 4 /* Благорост */ },
{ ledger2_wallets::GENERATOR_FUND, 3 /* Генератор */ },
{ ledger2_wallets::PREIMP_FUND, 0 /* w.cap.preimp — РИД-учёт до перехода на электронный учёт, без проверки */ },
{ ledger2_wallets::MARKETPLACE_MEMBER, 2 /* Marketplace */ },
{ ledger2_wallets::MARKETPLACE_ORDER_LOCK, 2 /* Marketplace */ },
}};
@@ -11,63 +11,48 @@
# актах приёма-передачи — приёмки кооперативом от поставщика и выдачи
# заказчику от кооператива.
#
# **Модель кошельков (refinement 2026-05-04, трёхуровневая серия):**
# Членский взнос пайщика проходит через два кошелька:
# **Модель кошельков (упрощённая схема 2026-05-28):**
# Членский взнос пайщика движется напрямую между двумя кошельками:
# • w.wal.member (контракт wallet, USER_SHARED) — универсальный членский
# кошелёк пайщика. Сюда стекаются возвраты из всех
# программ (Стол заказов / Благорост / и т.п.); пайщик
# тратит как «баллы» — может направить на следующий заказ
# в любой программе либо вывести обратно на паевой через
# операцию контракта wallet.
# • w.mkt.member (контракт marketplace, USER_SHARED) — членский в
# программе Marketplace. На нём лежит «свободный» остаток
# программы; средства под конкретный Order переводятся
# на отдельный кошелёк-резерв w.mkt.order.
# операцию контракта wallet. Источник средств при
# создании заказа и приёмник возврата при отмене.
# • w.mkt.order (контракт marketplace, USER_SHARED) — резерв средств
# пайщика под конкретный Order. Сюда уходят средства
# при создании заказа (createorder) и отсюда возвращаются
# обратно на w.mkt.member при отмене/истечении заказа,
# обратно на w.wal.member при отмене/истечении заказа,
# либо сжигаются при выдаче имущества (signiss2).
# Платформенные кошельки w.wal.share (Цифровой Кошелёк, паевые деньгами) и
# w.mkt.payout (Выплаты поставщикам) — без изменений. Имущество
# отслеживается бухгалтерской аналитикой по счёту 10 (per-КУ субсчета),
# без отдельного кошелька.
#
# Идентификация кошельков — eosio::name с префиксом w.<contract>.<waltype>
# (рефакторинг 2026-04-27 на ветке reports). null для незатронутых уровней;
# sentinel '' — legacy-допуск (рендерится как ∅).
# Идентификация кошельков — eosio::name с префиксом w.<contract>.<waltype>.
# null для незатронутых уровней; sentinel '' — legacy-допуск (рендерится как ∅).
#
# В процессе участвуют 9 ledger2-операций (часть — из контракта wallet):
# В процессе участвуют 6 ledger2-операций (одна — из контракта wallet):
# • o.wal.conv — конвертация цифрового рубля → общий членский пайщика
# (TRANSFER, w.wal.share → w.wal.member,
# Дт 80 / Кт 86) — conditional, только если на
# w.wal.member.available заказчика не хватает
# • o.mkt.assign — целевое назначение членского взноса в программу
# (TRANSFER, w.wal.member → w.mkt.member, без проводки —
# Дт=Кт по 86 смарт-контракт игнорирует) — conditional,
# только если на w.mkt.member.available не хватает
# • o.mkt.block — резервирование средств под Order
# (TRANSFER, w.mkt.member → w.mkt.order, без проводки —
# оба на 86)
# • o.mkt.unblk — снятие резерва при отмене Order'а
# (TRANSFER, w.mkt.order → w.mkt.member, без проводки;
# средства возвращаются на w.mkt.member.available
# и могут быть потрачены на следующий заказ)
# • o.mkt.recall — вывод из программы в общий членский кошелёк пайщика
# (TRANSFER, w.mkt.member → w.wal.member, без проводки) —
# ОТДЕЛЬНОЕ явное действие пайщика, не часть авто-flow
# отмены/возврата
# • o.mkt.purch — приёмка имущества кооперативом по АПП приёмки
# (ACCOUNT_ONLY, только проводка, Дт 10 / Кт 86)
# • o.mkt.payout — оплата поставщику с расчётного счёта
# (TRANSFER, ∅ → w.mkt.payout, Дт 86 / Кт 51)
# • o.mkt.consum — выдача имущества пайщику по АПП выдачи, часть 1
# (BURN с w.mkt.order — сжигание резерва, Дт 91 / Кт 10 —
# выбытие имущества со склада на «прочие»)
# • o.mkt.consum2 — выдача имущества пайщику, часть 2 — закрытие транзита
# (NONE, Дт 86 / Кт 91; кошельки не двигаются).
# Атомарно с o.mkt.consum в signiss2 — обе операции
# вызываются последовательно в одной транзакции Antelope.
# • o.mkt.lock — резервирование средств под Order
# (TRANSFER, w.wal.member → w.mkt.order, без проводки —
# оба на 86). Единственный обязательный шаг createorder.
# • o.mkt.unlock — снятие резерва при отмене Order'а
# (TRANSFER, w.mkt.order → w.wal.member, без проводки;
# средства возвращаются на универсальный членский
# и могут быть потрачены на следующий заказ либо выведены
# пайщиком на паевой через wallet-операцию).
# • o.mkt.purch — приёмка имущества кооперативом по АПП приёмки
# (NONE, только проводка Дт 10 / Кт 86)
# • o.mkt.payout — оплата поставщику с расчётного счёта
# (ISSUE ∅ → w.mkt.payout, Дт 86 / Кт 51)
# • o.mkt.consum — выдача имущества пайщику по АПП выдачи
# (BURN с w.mkt.order — сжигание резерва, Дт 86 / Кт 10 —
# одной операцией без транзита 91)
#
# Канон формата:
# coopenomics-docs/docs/standards/_spec/canon.md
@@ -1,17 +1,17 @@
/**
* @brief Председатель принимает гарантийный возврат на очном осмотре (Story 7.4, p.mkt.return).
*
* Композитная транзакция (атомарно в одной Antelope tx):
* - Ledger2::apply(o.mkt.return, fact_cost, orderer, hash=request.hash) — Дт 91 / Кт 86, ISSUE w.mkt.member.
* - Ledger2::apply(o.mkt.return2, fact_cost, orderer, hash=request.hash) — Дт 10 / Кт 91, NONE.
* - Ledger2::apply(o.mkt.return, fact_cost, orderer, hash=request.hash)
* — ISSUE w.wal.member, Дт 10 / Кт 86. Восстановление средств на универсальном
* членском заказчика + возврат имущества на склад через целевое финансирование,
* одной операцией без транзита 91.
*
* Compensating forward, не revert (Locked Decision L3 — AR14): новое событие в
* journal с прикладным полем `original_consume_op_id` (заполняется backend'ом
* в submretrn) для трассировки. Исходные o.mkt.consum / o.mkt.consum2 в
* журнале НЕ модифицируются.
* в submretrn) для трассировки. Исходная o.mkt.consum в журнале НЕ модифицируется.
*
* Status: approved_for_visit → return_accepted (final). Имущество возвращается
* на склад КУ; средства восстанавливаются на w.mkt.member.available заказчика.
* на склад КУ; средства восстанавливаются на w.wal.member.available заказчика.
*
* Guards:
* - Подписант (`signer`) авторизован для указанного КУ (`braname`).
@@ -38,15 +38,11 @@ void marketplace::accretrn(eosio::name coopname,
verify_document_or_fail(decision, { signer });
}
// Композитная пара return + return2
// o.mkt.return: ISSUE w.wal.member, Дт 10 / Кт 86 (одна операция без транзита 91)
Ledger2::apply(_marketplace, coopname,
operations::marketplace::RETURN_BY_MEMBER,
r.fact_cost, r.orderer, r.hash,
Marketplace::Memo::get_return_by_member_memo(r.id, r.original_order_id));
Ledger2::apply(_marketplace, coopname,
operations::marketplace::RETURN_TRANSIT_CLOSE,
r.fact_cost, r.orderer, r.hash,
Marketplace::Memo::get_return_transit_close_memo(r.id, r.original_order_id));
Marketplace::update_return_request(coopname, r.id, [&](auto& upd) {
upd.status = ReturnStatus::RETURN_ACCEPTED;
@@ -1,10 +1,10 @@
/**
* @brief Заказчик отменяет заказ до акцепта поставщиком (Story 4.4, p.mkt.supply).
*
* Триггерит `o.mkt.unblk` на full `order.total_cost` — TRANSFER w.mkt.order →
* w.mkt.member (снятие резерва). Сумма возвращается на `w.mkt.member.available`
* заказчика — может быть потрачена на следующий заказ программы либо выведена
* явным `o.mkt.recall` (отдельное действие).
* Триггерит `o.mkt.unlock` на full `order.total_cost` — TRANSFER w.mkt.order →
* w.wal.member (снятие резерва). Сумма возвращается на универсальный членский
* `w.wal.member.available` заказчика — может быть потрачена на следующий заказ
* программы либо выведена пайщиком на паевой через wallet-операцию.
*
* Guards:
* - Order существует.
@@ -25,7 +25,7 @@ void marketplace::cancelorder(eosio::name coopname,
"Нельзя отменить заказ: он уже акцептован поставщиком");
Ledger2::apply(_marketplace, coopname,
operations::marketplace::UNBLOCK_ON_CANCEL,
operations::marketplace::UNLOCK_ORDER,
o.total_cost, orderer, o.hash,
Marketplace::Memo::get_cancel_order_memo(o.id));
@@ -3,11 +3,9 @@
*
* Серия операций (атомарно в одной транзакции Antelope):
* 1. `o.wal.conv` (conditional) — TRANSFER w.wal.share → w.wal.member,
* Дт 80 / Кт 86. Только если на `w.wal.member.available` заказчика и
* на `w.mkt.member.available` суммарно не хватает суммы заказа.
* 2. `o.mkt.assign` (conditional) — TRANSFER w.wal.member → w.mkt.member,
* без проводки. Только если на `w.mkt.member.available` не хватает.
* 3. `o.mkt.block` (всегда) — TRANSFER w.mkt.member → w.mkt.order пайщика на
* Дт 80 / Кт 86. Только если на `w.wal.member.available` заказчика
* не хватает суммы заказа.
* 2. `o.mkt.lock` (всегда) — TRANSFER w.wal.member → w.mkt.order пайщика на
* total_cost (резерв средств под этот Order; без проводки — оба кошелька на 86).
*
* Guards (из p.mkt.supply.standard.yaml + Locked Decision L6):
@@ -16,16 +14,16 @@
* - Заказчик — активный пайщик кооператива (`get_participant_or_fail`).
* - `delivery_braname` существует в `branches` (КУ выдачи задаётся пайщиком
* из доступных и неизменен после создания Order'а).
* - Σ available трёх кошельков (w.wal.share + w.wal.member + w.mkt.member)
* >= total_cost; иначе createorder фейлится без создания Order'а.
* - Σ available двух кошельков (w.wal.share + w.wal.member) >= total_cost;
* иначе createorder фейлится без создания Order'а.
* - Подписка пайщика на оферту ЦПП «Стол заказов» (L2/L3 онбординг) —
* автоматически проверяется в `ledger2::walletop` через
* `assert_program_signed` (cross-contract в `wallet::users.programs[]`)
* при первом ASSIGN/TRANSFER на USER_SHARED-кошельке программы.
* при первом TRANSFER на USER_SHARED-кошельке программы (w.mkt.order).
*
* Сообщения проверок — для прямого показа пользователю (UI ловит check'ом).
*
* @note process_hash для всех трёх ledger2-операций — `order_hash`. Это
* @note process_hash для обеих ledger2-операций — `order_hash`. Это
* даёт backend'у одну точку группировки операций процесса
* p.mkt.supply через `getProcess(process_hash)` (Story 9.3).
*
@@ -73,31 +71,25 @@ void marketplace::createorder(eosio::name coopname,
eosio::check(total_cost.amount > 0,
"Итоговая сумма заказа должна быть больше нуля");
// ── Достаточность средств: Σ available трёх кошельков >= total_cost ─
// ── Достаточность средств: Σ available двух кошельков >= total_cost ─
auto bal_share = Marketplace::get_user_wallet_balance(
coopname, ledger2_wallets::SHARE_FUND_PAY, orderer);
auto bal_member = Marketplace::get_user_wallet_balance(
coopname, ledger2_wallets::CK_MEMBER, orderer);
auto bal_mkt = Marketplace::get_user_wallet_balance(
coopname, ledger2_wallets::MARKETPLACE_MEMBER, orderer);
eosio::asset total_available =
bal_share.available + bal_member.available + bal_mkt.available;
eosio::asset total_available = bal_share.available + bal_member.available;
eosio::check(total_available >= total_cost,
std::string{"Недостаточно средств для заказа: требуется "} +
total_cost.to_string() + ", доступно " + total_available.to_string());
// ── Расчёт conditional-долей серии ──────────────────────────────────
// Сначала тратим w.mkt.member.available; недостающее берём из w.wal.member
// через assign; недостающее в w.wal.member берём из w.wal.share через conv.
// ── Расчёт conditional-доли конвертации ─────────────────────────────
// Сначала тратим w.wal.member.available; недостающее берём из w.wal.share
// через o.wal.conv (паевой → членский).
const eosio::asset zero = eosio::asset(0, _root_govern_symbol);
eosio::asset need_to_assign = (bal_mkt.available >= total_cost)
? zero
: (total_cost - bal_mkt.available);
eosio::asset need_to_conv = (bal_member.available >= need_to_assign)
? zero
: (need_to_assign - bal_member.available);
eosio::asset need_to_conv = (bal_member.available >= total_cost)
? zero
: (total_cost - bal_member.available);
// ── Создание Order entity (id потребуется для memo) ─────────────────
orders_index orders(_marketplace, coopname.value);
@@ -127,7 +119,7 @@ void marketplace::createorder(eosio::name coopname,
o.batch_hash = batch_hash;
});
// ── Шаг 1: o.wal.conv (conditional) ──────────────────────────────────
// ── Шаг 1: o.wal.conv (conditional, w.wal.share → w.wal.member) ──────
if (need_to_conv.amount > 0) {
Ledger2::apply(_marketplace, coopname,
operations::wallet::CONVERT_TO_MEMBER,
@@ -135,18 +127,10 @@ void marketplace::createorder(eosio::name coopname,
Marketplace::Memo::get_create_order_convert_memo(new_id));
}
// ── Шаг 2: o.mkt.assign (conditional) ────────────────────────────────
if (need_to_assign.amount > 0) {
Ledger2::apply(_marketplace, coopname,
operations::marketplace::ASSIGN_TO_PROGRAM,
need_to_assign, orderer, order_hash,
Marketplace::Memo::get_create_order_assign_memo(new_id));
}
// ── Шаг 3: o.mkt.block (всегда) — TRANSFER w.mkt.member → w.mkt.order ──
// ── Шаг 2: o.mkt.lock (всегда, TRANSFER w.wal.member → w.mkt.order) ──
// Резервируем total_cost на отдельный кошелёк w.mkt.order под этот Order.
Ledger2::apply(_marketplace, coopname,
operations::marketplace::BLOCK_FOR_ORDER,
operations::marketplace::LOCK_ORDER,
total_cost, orderer, order_hash,
Marketplace::Memo::get_create_order_block_memo(new_id));
}
@@ -1,10 +1,10 @@
/**
* @brief Поставщик отказывается от одного Order'а до акцепта (Story 4.5, p.mkt.supply).
*
* Per-Order: o.mkt.unblk на total_cost (TRANSFER w.mkt.order → w.mkt.member —
* резерв возвращается на .available заказчика) + статус active → cancelled.
* Backend проходит циклом по orders соответствующего batch'а, вызывая
* `declineorder` per Order — векторов order_hash нет.
* Per-Order: o.mkt.unlock на total_cost (TRANSFER w.mkt.order → w.wal.member —
* резерв возвращается на универсальный членский заказчика) + статус active →
* cancelled. Backend проходит циклом по orders соответствующего batch'а,
* вызывая `declineorder` per Order — векторов order_hash нет.
*
* Guards:
* - Order существует и в статусе active.
@@ -23,7 +23,7 @@ void marketplace::declineorder(eosio::name coopname,
"Заказ уже не в активном статусе — отклонить нельзя");
Ledger2::apply(_marketplace, coopname,
operations::marketplace::UNBLOCK_ON_CANCEL,
operations::marketplace::UNLOCK_ORDER,
o.total_cost, o.orderer, o.hash,
Marketplace::Memo::get_decline_order_memo(o.id));
@@ -8,7 +8,7 @@
* threshold — это вычисление backend'а; on-chain — только закрытие конкретного
* Order'а с возвратом резерва.
*
* Per-Order: o.mkt.unblk на total_cost (TRANSFER w.mkt.order → w.mkt.member — снятие резерва на .available заказчика) + статус active → cancelled.
* Per-Order: o.mkt.unlock на total_cost (TRANSFER w.mkt.order → w.wal.member — снятие резерва на универсальный членский заказчика) + статус active → cancelled.
*
* Guards:
* - Order существует и в статусе active (после акцепта поставщика
@@ -28,7 +28,7 @@ void marketplace::expireorder(eosio::name coopname,
"Закрыть по таймауту можно только активный заказ");
Ledger2::apply(_marketplace, coopname,
operations::marketplace::UNBLOCK_ON_CANCEL,
operations::marketplace::UNLOCK_ORDER,
o.total_cost, o.orderer, o.hash,
Marketplace::Memo::get_expire_order_memo(o.id));
@@ -4,21 +4,21 @@
* Per-Order атомарная транзакция с поддержкой actual_quantity ≠ ordered (Story 6.2):
*
* 1) actual == ordered:
* Ledger2::apply(o.mkt.consum, fact_cost) — Дт 91 / Кт 10, BURN с w.mkt.order (резерв).
* Ledger2::apply(o.mkt.consum2, fact_cost) — Дт 86 / Кт 91, NONE.
* Ledger2::apply(o.mkt.consum, fact_cost) — BURN w.mkt.order, Дт 86 / Кт 10
* (сжигание резерва заказа + выбытие имущества со склада через целевое
* финансирование, одной операцией без транзита 91).
*
* 2) actual < ordered (выдано меньше — остаток резерва возвращается):
* Ledger2::apply(o.mkt.unblk, ordered_cost - fact_cost) — TRANSFER w.mkt.order →
* w.mkt.member на разницу (снятие части резерва).
* затем — те же consum + consum2 на fact_cost.
* Ledger2::apply(o.mkt.unlock, ordered_cost - fact_cost) — TRANSFER w.mkt.order →
* w.wal.member на разницу (снятие части резерва на универсальный членский).
* затем — consum на fact_cost.
*
* 3) actual > ordered (доплата с паевого):
* diff = fact_cost - ordered_cost
* проверка достаточности средств для diff (через w.wal.share + w.wal.member).
* Conditional o.wal.conv (если на w.wal.member недостача).
* Conditional o.mkt.assign (если на w.mkt.member недостача).
* o.mkt.block(diff) — TRANSFER w.mkt.member → w.mkt.order, резервируем доплату на этот же Order.
* затем — consum + consum2 на fact_cost.
* Conditional o.wal.conv (если на w.wal.member недостача — паевой → членский).
* o.mkt.lock(diff) — TRANSFER w.wal.member → w.mkt.order, добор резерва на этот же Order.
* затем — consum на fact_cost.
*
* Status: ready_to_receive → received. actual_quantity, fact_cost,
* issue_act_signiss2, warranty_until заполняются.
@@ -65,37 +65,32 @@ void marketplace::signiss2(eosio::name coopname,
// ── Корректирующие операции (если факт ≠ заказ) ─────────────────────
if (fact_cost < o.total_cost) {
// actual < ordered: снимаем разницу резерва → .available членского
// actual < ordered: снимаем разницу резерва → .available универсального членского
const eosio::asset diff = o.total_cost - fact_cost;
Ledger2::apply(_marketplace, coopname,
operations::marketplace::UNBLOCK_ON_CANCEL,
operations::marketplace::UNLOCK_ORDER,
diff, orderer, o.hash,
Marketplace::Memo::get_signiss2_correction_less_memo(o.id));
} else if (fact_cost > o.total_cost) {
// actual > ordered: добираем разницу с паевого + assign + block
// actual > ordered: добираем разницу с паевого + conv (conditional) + lock
const eosio::asset diff = fact_cost - o.total_cost;
// Проверка доступности diff в трёх кошельках
// Проверка доступности diff в двух кошельках (share + universal member)
auto bal_share = Marketplace::get_user_wallet_balance(
coopname, ledger2_wallets::SHARE_FUND_PAY, orderer);
auto bal_member = Marketplace::get_user_wallet_balance(
coopname, ledger2_wallets::CK_MEMBER, orderer);
auto bal_mkt = Marketplace::get_user_wallet_balance(
coopname, ledger2_wallets::MARKETPLACE_MEMBER, orderer);
eosio::asset total_avail = bal_share.available + bal_member.available + bal_mkt.available;
eosio::asset total_avail = bal_share.available + bal_member.available;
eosio::check(total_avail >= diff,
std::string{"Недостаточно средств для дооплаты по факту: требуется "} +
diff.to_string() + ", доступно " + total_avail.to_string());
const eosio::asset zero = eosio::asset(0, _root_govern_symbol);
eosio::asset need_to_assign = (bal_mkt.available >= diff)
? zero
: (diff - bal_mkt.available);
eosio::asset need_to_conv = (bal_member.available >= need_to_assign)
? zero
: (need_to_assign - bal_member.available);
eosio::asset need_to_conv = (bal_member.available >= diff)
? zero
: (diff - bal_member.available);
if (need_to_conv.amount > 0) {
Ledger2::apply(_marketplace, coopname,
@@ -103,28 +98,18 @@ void marketplace::signiss2(eosio::name coopname,
need_to_conv, orderer, o.hash,
Marketplace::Memo::get_signiss2_correction_more_convert_memo(o.id));
}
if (need_to_assign.amount > 0) {
Ledger2::apply(_marketplace, coopname,
operations::marketplace::ASSIGN_TO_PROGRAM,
need_to_assign, orderer, o.hash,
Marketplace::Memo::get_signiss2_correction_more_assign_memo(o.id));
}
Ledger2::apply(_marketplace, coopname,
operations::marketplace::BLOCK_FOR_ORDER,
operations::marketplace::LOCK_ORDER,
diff, orderer, o.hash,
Marketplace::Memo::get_signiss2_correction_more_block_memo(o.id));
}
// fact == ordered — без корректировок
// ── Композитная пара consum + consum2 на fact_cost ──────────────────
// ── o.mkt.consum на fact_cost (BURN w.mkt.order, Дт 86 / Кт 10) ──────
Ledger2::apply(_marketplace, coopname,
operations::marketplace::CONSUME_BY_MEMBER,
fact_cost, orderer, o.hash,
Marketplace::Memo::get_consume_by_member_memo(o.id));
Ledger2::apply(_marketplace, coopname,
operations::marketplace::CONSUME_TRANSIT_CLOSE,
fact_cost, orderer, o.hash,
Marketplace::Memo::get_consume_transit_close_memo(o.id));
// ── Закрытие Order'а ────────────────────────────────────────────────
const auto now = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
@@ -2,9 +2,9 @@
* @brief Backend исполняет одну позицию авторизованного советом проекта
* списания скоропорта (Story 8.4, p.mkt.wroff).
*
* Per-item композитная транзакция (атомарно):
* - Ledger2::apply(o.mkt.wroff, item.amount, …, hash=proposal.hash) — Дт 91 / Кт 10.
* - Ledger2::apply(o.mkt.wroff2, item.amount, …, hash=proposal.hash) — Дт 86 / Кт 91.
* Per-item операция:
* - Ledger2::apply(o.mkt.wroff, item.amount, …, hash=proposal.hash) — Дт 86 / Кт 10
* (списание со склада через целевое финансирование, одной операцией без транзита 91).
*
* Вызывается ТОЛЬКО после того, как совет авторизовал проект (status =
* AUTHORIZED через callback `onmktwoauth`). Backend проходит циклом по
@@ -39,15 +39,11 @@ void marketplace::execwroff(eosio::name coopname,
eosio::check(branch.is_user_authorized(signer),
"Подписант не уполномочен исполнять списание данного кооперативного участка");
// Композитная пара wroff + wroff2 для одной позиции
// o.mkt.wroff: Дт 86 / Кт 10 (одна операция без транзита 91)
Ledger2::apply(_marketplace, coopname,
operations::marketplace::WRITE_OFF_PERISHABLE,
item.amount, item.braname, p.hash,
Marketplace::Memo::get_writeoff_memo(p.id, item_index));
Ledger2::apply(_marketplace, coopname,
operations::marketplace::WRITE_OFF_TRANSIT_CLOSE,
item.amount, item.braname, p.hash,
Marketplace::Memo::get_writeoff_transit_close_memo(p.id, item_index));
// Помечаем позицию исполненной + финализируем proposal если все позиции готовы
Marketplace::update_writeoff_proposal(coopname, p.id, [&](auto& upd) {
+3 -5
View File
@@ -8137,7 +8137,7 @@ type MarketplaceMemberWallet {
username: String!
"""
Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.member → mkt.order
Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.order
"""
wallets: [MarketplaceWalletEntry!]!
}
@@ -9270,9 +9270,7 @@ type MarketplaceWalletEntry {
"""
label: String!
"""
eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.member / w.mkt.order)
"""
"""eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.order)"""
name: String!
"""program_id: 1 — Цифровой Кошелёк, 2 — Стол Заказов"""
@@ -13290,7 +13288,7 @@ type Query {
marketplaceListWriteoffProposals(data: MarketplaceListWriteoffProposalsInput!, options: PaginationInput): PaginatedMarketplaceWriteoffProposals!
"""
Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.member, w.mkt.order
Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.order
"""
marketplaceMemberWallet: MarketplaceMemberWallet!
@@ -5,17 +5,17 @@
* Никаких NestJS / TypeORM / реального chain'а — работа на синтетических
* массивах `Ledger2OperationDTO`.
*
* Сценарии:
* 1. Happy path для каждого I1..I6 (закрытый flow, инвариант ok).
* 2. Violation path (специально сломанная последовательность → инвариант
* падает с понятным `violation`).
* 3. Property-based — random последовательности block/unblk/consum/return
* обязаны сохранять I4 (transit = 0) и I6 (нет orphan-резервов).
* Архитектура 2026-05-28: reserve-кошелёк w.mkt.order; счёт 91 в marketplace
* не используется (все операции напрямую между 10 и 86); промежуточный
* программный кошелёк w.mkt.member упразднён — источник и приёмок резерва
* это универсальный членский w.wal.member.
*
* После миграции на reserve-кошелёк w.mkt.order (2026-05-28):
* - o.mkt.block: TRANSFER w.mkt.member → w.mkt.order
* - o.mkt.unblk: TRANSFER w.mkt.order → w.mkt.member
* - o.mkt.consum: BURN с w.mkt.order
* Операции:
* - o.mkt.lock: TRANSFER w.wal.member → w.mkt.order
* - o.mkt.unlock: TRANSFER w.mkt.order → w.wal.member
* - o.mkt.consum: BURN w.mkt.order, Дт 86 / Кт 10
* - o.mkt.return: ISSUE → w.wal.member, Дт 10 / Кт 86
* - o.mkt.wroff: NONE, Дт 86 / Кт 10
*/
import { createHash, randomBytes } from 'node:crypto'
@@ -45,10 +45,6 @@ const nextSeq = (): string => (seq++).toString()
const newProcessHash = (): string =>
createHash('sha256').update(randomBytes(32)).digest('hex')
/**
* Сборка трио apply + walletop + debit + credit под одним process_hash для
* данной marketplace-операции. Подражает ledger2::apply pipeline.
*/
function buildApplyTrio(params: {
processHash: string
operationCode: string
@@ -101,14 +97,12 @@ function buildApplyTrio(params: {
}
/**
* Полный happy-path order flow (createorder → signsupp → signiss2):
* 1. o.wal.conv (Dr 80 / Cr 86) — конвертация цифрового рубля
* 2. o.mkt.assign (TRANSFER без проводок) — членский в программу
* 3. o.mkt.block (TRANSFER w.mkt.member → w.mkt.order) — резерв под Order
* 4. o.mkt.purch (Dr 10 / Cr 86) — приёмка
* 5. o.mkt.payout (Dr 86 / Cr 51, ISSUE w.mkt.payout) — задолженность поставщику
* 6. o.mkt.consum (Dr 91 / Cr 10, BURN w.mkt.order) — выдача (сжигаем резерв)
* 7. o.mkt.consum2 (Dr 86 / Cr 91) — закрытие транзита
* Happy-path order flow (createorder → signsupp → signiss2):
* 1. o.wal.conv (Dr 80 / Cr 86) — конвертация цифрового рубля (conditional)
* 2. o.mkt.lock (TRANSFER w.wal.member → w.mkt.order, без проводок) — резерв
* 3. o.mkt.purch (Dr 10 / Cr 86) — приёмка
* 4. o.mkt.payout (Dr 86 / Cr 51, ISSUE w.mkt.payout) — задолженность поставщику
* 5. o.mkt.consum (Dr 86 / Cr 10, BURN w.mkt.order) — выдача (одна операция без 91)
*/
function happyPathOrderFlow(amount = 100): MarketplaceLedger2OperationRow[] {
const orderHash = newProcessHash()
@@ -124,18 +118,9 @@ function happyPathOrderFlow(amount = 100): MarketplaceLedger2OperationRow[] {
}),
...buildApplyTrio({
processHash: orderHash,
operationCode: 'o.mkt.assign',
operationCode: 'o.mkt.lock',
amount,
walletFrom: 'w.wal.member',
walletTo: 'w.mkt.member',
debitAccount: null,
creditAccount: null,
}),
...buildApplyTrio({
processHash: orderHash,
operationCode: 'o.mkt.block',
amount,
walletFrom: 'w.mkt.member',
walletTo: 'w.mkt.order',
debitAccount: null,
creditAccount: null,
@@ -164,17 +149,8 @@ function happyPathOrderFlow(amount = 100): MarketplaceLedger2OperationRow[] {
amount,
walletFrom: 'w.mkt.order',
walletTo: null,
debitAccount: 91,
creditAccount: 10,
}),
...buildApplyTrio({
processHash: orderHash,
operationCode: 'o.mkt.consum2',
amount,
walletFrom: null,
walletTo: null,
debitAccount: 86,
creditAccount: 91,
creditAccount: 10,
}),
]
}
@@ -184,19 +160,19 @@ function cancelOrderFlow(amount = 100): MarketplaceLedger2OperationRow[] {
return [
...buildApplyTrio({
processHash: orderHash,
operationCode: 'o.mkt.block',
operationCode: 'o.mkt.lock',
amount,
walletFrom: 'w.mkt.member',
walletFrom: 'w.wal.member',
walletTo: 'w.mkt.order',
debitAccount: null,
creditAccount: null,
}),
...buildApplyTrio({
processHash: orderHash,
operationCode: 'o.mkt.unblk',
operationCode: 'o.mkt.unlock',
amount,
walletFrom: 'w.mkt.order',
walletTo: 'w.mkt.member',
walletTo: 'w.wal.member',
debitAccount: null,
creditAccount: null,
}),
@@ -211,18 +187,9 @@ function returnFlow(amount = 100): MarketplaceLedger2OperationRow[] {
operationCode: 'o.mkt.return',
amount,
walletFrom: null,
walletTo: 'w.mkt.member',
debitAccount: 91,
creditAccount: 86,
}),
...buildApplyTrio({
processHash: requestHash,
operationCode: 'o.mkt.return2',
amount,
walletFrom: null,
walletTo: null,
walletTo: 'w.wal.member',
debitAccount: 10,
creditAccount: 91,
creditAccount: 86,
}),
]
}
@@ -236,17 +203,8 @@ function writeoffFlow(amount = 100): MarketplaceLedger2OperationRow[] {
amount,
walletFrom: null,
walletTo: null,
debitAccount: 91,
creditAccount: 10,
}),
...buildApplyTrio({
processHash: proposalHash,
operationCode: 'o.mkt.wroff2',
amount,
walletFrom: null,
walletTo: null,
debitAccount: 86,
creditAccount: 91,
creditAccount: 10,
}),
]
}
@@ -294,7 +252,6 @@ describe('I1 — баланс w.mkt.payout', () => {
it('happy path: payout + BURN → баланс = 0', () => {
const rows = happyPathOrderFlow(150)
// эмулируем gateway::payconfirm → BURN из w.mkt.payout
rows.push({
globalSequence: nextSeq(),
action: 'walletop',
@@ -348,8 +305,12 @@ describe('I3 — баланс счёта 10 (Материалы)', () => {
expect(res.ok).toBe(true)
})
it('happy path: purch + return2 → balance = 2× purch', () => {
const rows = [...happyPathOrderFlow(100).filter((r) => !['o.mkt.consum', 'o.mkt.consum2', 'o.mkt.payout'].includes(r.operationCode ?? ''))]
it('happy path: purch + return → balance = 2× purch (имущество вернулось на склад)', () => {
const rows = [
...happyPathOrderFlow(100).filter(
(r) => !['o.mkt.consum', 'o.mkt.payout'].includes(r.operationCode ?? ''),
),
]
rows.push(...returnFlow(100))
const accounts: MarketplaceAccountRow[] = [{ accountId: 10, balance: '200.0000 RUB' }]
const res = checkInvariantI3Account10Materials(rows, accounts)
@@ -373,52 +334,19 @@ describe('I3 — баланс счёта 10 (Материалы)', () => {
})
})
describe('I4 — транзитный счёт 91 = 0', () => {
describe('I4 — транзитный счёт 91 (в marketplace больше не используется)', () => {
beforeEach(() => {
seq = 100n
})
it('happy path: consum + consum2 атомарно → 91 = 0', () => {
const rows = happyPathOrderFlow(50)
const res = checkInvariantI4Account91Transit(rows)
expect(res.ok).toBe(true)
})
it('happy path: return + return2 → 91 = 0', () => {
const res = checkInvariantI4Account91Transit(returnFlow(80))
expect(res.ok).toBe(true)
})
it('happy path: wroff + wroff2 → 91 = 0', () => {
const res = checkInvariantI4Account91Transit(writeoffFlow(120))
expect(res.ok).toBe(true)
})
it('happy path: смешанный поток из 10 случайных закрытых процессов', () => {
it('happy path: новый flow не задевает 91 → I4 пройден тривиально', () => {
const rows: MarketplaceLedger2OperationRow[] = []
for (let i = 0; i < 4; i++) rows.push(...happyPathOrderFlow(50 + i))
for (let i = 0; i < 3; i++) rows.push(...returnFlow(20 + i))
for (let i = 0; i < 3; i++) rows.push(...writeoffFlow(15 + i))
for (let i = 0; i < 3; i++) rows.push(...happyPathOrderFlow(50 + i))
for (let i = 0; i < 2; i++) rows.push(...returnFlow(20 + i))
for (let i = 0; i < 2; i++) rows.push(...writeoffFlow(15 + i))
const res = checkInvariantI4Account91Transit(rows)
expect(res.ok).toBe(true)
})
it('violation: consum без consum2 (процесс не закрыт)', () => {
const orderHash = newProcessHash()
const rows = buildApplyTrio({
processHash: orderHash,
operationCode: 'o.mkt.consum',
amount: 100,
walletFrom: 'w.mkt.order',
walletTo: null,
debitAccount: 91,
creditAccount: 10,
})
const res = checkInvariantI4Account91Transit(rows)
expect(res.ok).toBe(false)
expect(res.violation).toMatch(/I4/)
expect(res.details?.[0]?.message).toMatch(/transit 91/)
})
})
describe('I5 — согласованность резерва на w.mkt.order', () => {
@@ -426,84 +354,76 @@ describe('I5 — согласованность резерва на w.mkt.order'
seq = 100n
})
it('happy path: block + consum → reserve = 0', () => {
it('happy path: lock + consum → reserve = 0', () => {
const rows = happyPathOrderFlow(100)
const wallets: MarketplaceWalletRow[] = [
{ wallet: 'w.mkt.order', balance: '0.0000 RUB' },
]
const wallets: MarketplaceWalletRow[] = [{ wallet: 'w.mkt.order', balance: '0.0000 RUB' }]
const res = checkInvariantI5ReserveConsistency(rows, wallets)
expect(res.ok).toBe(true)
})
it('happy path: block + unblk → reserve = 0', () => {
it('happy path: lock + unlock → reserve = 0', () => {
const rows = cancelOrderFlow(50)
const wallets: MarketplaceWalletRow[] = [
{ wallet: 'w.mkt.order', balance: '0.0000 RUB' },
]
const wallets: MarketplaceWalletRow[] = [{ wallet: 'w.mkt.order', balance: '0.0000 RUB' }]
const res = checkInvariantI5ReserveConsistency(rows, wallets)
expect(res.ok).toBe(true)
})
it('happy path: один активный block → reserve = amount', () => {
it('happy path: один активный lock → reserve = amount', () => {
const orderHash = newProcessHash()
const rows = buildApplyTrio({
processHash: orderHash,
operationCode: 'o.mkt.block',
operationCode: 'o.mkt.lock',
amount: 60,
walletFrom: 'w.mkt.member',
walletFrom: 'w.wal.member',
walletTo: 'w.mkt.order',
debitAccount: null,
creditAccount: null,
})
const wallets: MarketplaceWalletRow[] = [
{ wallet: 'w.mkt.order', balance: '60.0000 RUB' },
]
const wallets: MarketplaceWalletRow[] = [{ wallet: 'w.mkt.order', balance: '60.0000 RUB' }]
const res = checkInvariantI5ReserveConsistency(rows, wallets)
expect(res.ok).toBe(true)
})
it('violation: block есть, reserve = 0 (фантомный unblk)', () => {
it('violation: lock есть, reserve = 0 (фантомный unlock)', () => {
const orderHash = newProcessHash()
const rows = buildApplyTrio({
processHash: orderHash,
operationCode: 'o.mkt.block',
operationCode: 'o.mkt.lock',
amount: 60,
walletFrom: 'w.mkt.member',
walletFrom: 'w.wal.member',
walletTo: 'w.mkt.order',
debitAccount: null,
creditAccount: null,
})
const wallets: MarketplaceWalletRow[] = [
{ wallet: 'w.mkt.order', balance: '0.0000 RUB' },
]
const wallets: MarketplaceWalletRow[] = [{ wallet: 'w.mkt.order', balance: '0.0000 RUB' }]
const res = checkInvariantI5ReserveConsistency(rows, wallets)
expect(res.ok).toBe(false)
expect(res.violation).toMatch(/I5/)
})
})
describe('I6 — нет orphan o.mkt.block', () => {
describe('I6 — нет orphan o.mkt.lock', () => {
beforeEach(() => {
seq = 100n
})
it('happy path: block + consum', () => {
it('happy path: lock + consum', () => {
const res = checkInvariantI6NoOrphanedReserves(happyPathOrderFlow(100))
expect(res.ok).toBe(true)
})
it('happy path: block + unblk', () => {
it('happy path: lock + unlock', () => {
const res = checkInvariantI6NoOrphanedReserves(cancelOrderFlow(50))
expect(res.ok).toBe(true)
})
it('happy path: только block (активный Order, ещё не закрытый)', () => {
it('happy path: только lock (активный Order, ещё не закрытый)', () => {
const orderHash = newProcessHash()
const rows = buildApplyTrio({
processHash: orderHash,
operationCode: 'o.mkt.block',
operationCode: 'o.mkt.lock',
amount: 30,
walletFrom: 'w.mkt.member',
walletFrom: 'w.wal.member',
walletTo: 'w.mkt.order',
debitAccount: null,
creditAccount: null,
@@ -512,7 +432,7 @@ describe('I6 — нет orphan o.mkt.block', () => {
expect(res.ok).toBe(true)
})
it('violation: consum без block (двойная выдача / битый flow)', () => {
it('violation: consum без lock (двойная выдача / битый flow)', () => {
const orderHash = newProcessHash()
const rows = buildApplyTrio({
processHash: orderHash,
@@ -520,32 +440,32 @@ describe('I6 — нет orphan o.mkt.block', () => {
amount: 30,
walletFrom: 'w.mkt.order',
walletTo: null,
debitAccount: 91,
debitAccount: 86,
creditAccount: 10,
})
const res = checkInvariantI6NoOrphanedReserves(rows)
expect(res.ok).toBe(false)
expect(res.details?.[0]?.message).toMatch(/consum без предшествующего block/)
expect(res.details?.[0]?.message).toMatch(/consum без предшествующего lock/)
})
it('violation: block + unblk + consum (двойное закрытие)', () => {
it('violation: lock + unlock + consum (двойное закрытие)', () => {
const orderHash = newProcessHash()
const rows: MarketplaceLedger2OperationRow[] = [
...buildApplyTrio({
processHash: orderHash,
operationCode: 'o.mkt.block',
operationCode: 'o.mkt.lock',
amount: 30,
walletFrom: 'w.mkt.member',
walletFrom: 'w.wal.member',
walletTo: 'w.mkt.order',
debitAccount: null,
creditAccount: null,
}),
...buildApplyTrio({
processHash: orderHash,
operationCode: 'o.mkt.unblk',
operationCode: 'o.mkt.unlock',
amount: 30,
walletFrom: 'w.mkt.order',
walletTo: 'w.mkt.member',
walletTo: 'w.wal.member',
debitAccount: null,
creditAccount: null,
}),
@@ -555,7 +475,7 @@ describe('I6 — нет orphan o.mkt.block', () => {
amount: 30,
walletFrom: 'w.mkt.order',
walletTo: null,
debitAccount: 91,
debitAccount: 86,
creditAccount: 10,
}),
]
@@ -604,55 +524,6 @@ describe('checkAllMarketplaceLedger2Invariants — батч', () => {
})
})
describe('Property-based — случайные последовательности', () => {
/**
* Для seed-based-генератора используем counter с детерминированной
* подмешкой через `randomBytes` (тесты воспроизводимы в CI: jest гонит
* single-thread, seed одинаков от прогона к прогону).
*/
const RUN_COUNT = 50
beforeEach(() => {
seq = 100n
})
it('I4 сохраняется на 50 случайных последовательностях из mix happy/cancel/return/writeoff', () => {
for (let i = 0; i < RUN_COUNT; i++) {
const rows: MarketplaceLedger2OperationRow[] = []
const flows = [happyPathOrderFlow, cancelOrderFlow, returnFlow, writeoffFlow]
const n = (i % 4) + 2 // 2..5 потоков
for (let k = 0; k < n; k++) {
const flow = flows[(i + k) % flows.length]
rows.push(...flow(10 + ((i + k) % 90)))
}
const res = checkInvariantI4Account91Transit(rows)
expect(res.ok).toBe(true)
}
})
it('I6 сохраняется на тех же последовательностях', () => {
for (let i = 0; i < RUN_COUNT; i++) {
const rows: MarketplaceLedger2OperationRow[] = []
const flows = [happyPathOrderFlow, cancelOrderFlow]
const n = (i % 3) + 2
for (let k = 0; k < n; k++) {
const flow = flows[(i + k) % flows.length]
rows.push(...flow(10 + ((i + k) % 90)))
}
const res = checkInvariantI6NoOrphanedReserves(rows)
expect(res.ok).toBe(true)
}
})
it('инжекция случайного «потерянного consum» ломает I4 + I6', () => {
const rows = happyPathOrderFlow(100)
// Убираем consum2 — закрытие транзита 91 пропало.
const broken = rows.filter((r) => r.operationCode !== 'o.mkt.consum2')
const i4 = checkInvariantI4Account91Transit(broken)
expect(i4.ok).toBe(false)
})
})
describe('I2 — marketplace-вклад в счёт 86 (sanity)', () => {
beforeEach(() => {
seq = 100n
@@ -662,8 +533,8 @@ describe('I2 — marketplace-вклад в счёт 86 (sanity)', () => {
const rows = happyPathOrderFlow(100)
const res = checkInvariantI2Account86Delta(rows)
expect(res.ok).toBe(true)
// Cr 86: conv(+100) + purch(+100) + return(0) = +200
// Dr 86: payout(100) + consum2(100) = 200
// Cr 86: conv(+100) + purch(+100) = +200
// Dr 86: payout(100) + consum(100) = 200
// delta = 200 200 = 0
expect(res.expected).toBe('0.0000 RUB')
})
@@ -333,16 +333,15 @@ export function checkInvariantI4Account91Transit(
// ---------------------------------------------------------------------------
// I5 — Согласованность резерва под Order на кошельке w.mkt.order:
// sum(TRANSFER w.mkt.member → w.mkt.order) // o.mkt.block — резерв вошёл
// sum(TRANSFER w.mkt.order → w.mkt.member) // o.mkt.unblk — резерв снят
// sum(TRANSFER w.wal.member → w.mkt.order) // o.mkt.lock — резерв вошёл
// sum(TRANSFER w.mkt.order → w.wal.member) // o.mkt.unlock — резерв снят
// sum(BURN w.mkt.order) // o.mkt.consum — резерв сожжён (выдача)
// = sum(available у w.mkt.order-кошельков пайщиков).
//
// Переписан 2026-05-28 после миграции marketplace с BLOCK/UNBLOCK/BURN_BLOCKED
// на пары TRANSFER в отдельный кошелёк-резерв (по аналогии с dev wallet/withdraw
// → w.wal.wpend). Operation_id'ы o.mkt.block / o.mkt.unblk / o.mkt.consum
// сохранены; изменилась их семантика в OPERATION_REGISTRY: TRANSFER и BURN
// вместо BLOCK/UNBLOCK/BURN_BLOCKED.
// Архитектура 2026-05-28: вместо механики BLOCK/UNBLOCK на одном кошельке —
// пара TRANSFER через отдельный USER_SHARED-кошелёк w.mkt.order. Источник и
// приёмник средств — универсальный членский w.wal.member; промежуточного
// программного кошелька (бывший w.mkt.member) нет.
//
// На входе тестов wallets обычно содержит aggregated available по всем
// w.mkt.order-row. Можно подать одну строку с агрегированным `balance`.
@@ -354,12 +353,12 @@ export function checkInvariantI5ReserveConsistency(
let computed = 0n
for (const r of rows) {
if (r.action !== 'walletop') continue
// o.mkt.block: TRANSFER w.mkt.member → w.mkt.order (резерв входит)
if (r.walletFrom === 'w.mkt.member' && r.walletTo === 'w.mkt.order') {
// o.mkt.lock: TRANSFER w.wal.member → w.mkt.order (резерв входит)
if (r.walletFrom === 'w.wal.member' && r.walletTo === 'w.mkt.order') {
computed += parseAssetToBigInt(r.quantity)
}
// o.mkt.unblk: TRANSFER w.mkt.order → w.mkt.member (резерв снят)
else if (r.walletFrom === 'w.mkt.order' && r.walletTo === 'w.mkt.member') {
// o.mkt.unlock: TRANSFER w.mkt.order → w.wal.member (резерв снят)
else if (r.walletFrom === 'w.mkt.order' && r.walletTo === 'w.wal.member') {
computed -= parseAssetToBigInt(r.quantity)
}
// o.mkt.consum: BURN w.mkt.order (резерв сожжён при выдаче)
@@ -379,25 +378,25 @@ export function checkInvariantI5ReserveConsistency(
expected: formatBigIntAsset(computed),
actual: formatBigIntAsset(totalReserve),
violation:
'I5: резерв на w.mkt.order не совпадает с историей block unblk consum. ' +
'Возможный источник: пропущенный unblk при отмене Order или повторный block без unblk.',
'I5: резерв на w.mkt.order не совпадает с историей lock − unlock consum. ' +
'Возможный источник: пропущенный unlock при отмене Order или повторный lock без unlock.',
}
}
return { ok: true, invariant: 'I5', expected: formatBigIntAsset(computed) }
}
// ---------------------------------------------------------------------------
// I6 — Парность операций в жизненном цикле Order'а: каждому o.mkt.block по
// process_hash должно соответствовать либо o.mkt.unblk (отмена Order), либо
// I6 — Парность операций в жизненном цикле Order'а: каждому o.mkt.lock по
// process_hash должно соответствовать либо o.mkt.unlock (отмена Order), либо
// o.mkt.consum (выдача), либо открытый Order (process ещё активен — здесь
// не проверяем).
//
// Одна process_hash (= order_hash) может содержать block + unblk (отмена),
// block + consum (выдача) или только block (активный Order, не нарушение).
// Одна process_hash (= order_hash) может содержать lock + unlock (отмена),
// lock + consum (выдача) или только lock (активный Order, не нарушение).
//
// Для статической проверки в CI рассматриваем закрытые процессы: если в
// процессе есть o.mkt.unblk без o.mkt.block, или есть o.mkt.consum без
// o.mkt.block — нарушение. Если есть все три (block + unblk + consum) —
// процессе есть o.mkt.unlock без o.mkt.lock, или есть o.mkt.consum без
// o.mkt.lock — нарушение. Если есть все три (lock + unlock + consum) —
// тоже нарушение (двойное закрытие резерва).
// ---------------------------------------------------------------------------
export function checkInvariantI6NoOrphanedReserves(
@@ -407,27 +406,27 @@ export function checkInvariantI6NoOrphanedReserves(
const violations: Array<{ processHash: string; message: string }> = []
for (const [processHash, codes] of applyIndex) {
const hasBlock = codes.includes('o.mkt.block')
const hasUnblock = codes.includes('o.mkt.unblk')
const hasLock = codes.includes('o.mkt.lock')
const hasUnlock = codes.includes('o.mkt.unlock')
const hasConsum = codes.includes('o.mkt.consum')
if (hasConsum && !hasBlock) {
if (hasConsum && !hasLock) {
violations.push({
processHash,
message:
'consum без предшествующего block — невозможно списать резерв, которого не было.',
'consum без предшествующего lock — невозможно списать резерв, которого не было.',
})
}
if (hasUnblock && !hasBlock) {
if (hasUnlock && !hasLock) {
violations.push({
processHash,
message: 'unblk без block — невозможно снять резерв, который не вносился.',
message: 'unlock без lock — невозможно снять резерв, который не вносился.',
})
}
if (hasBlock && hasUnblock && hasConsum) {
if (hasLock && hasUnlock && hasConsum) {
violations.push({
processHash,
message: 'block + unblk + consum в одном процессе — двойное закрытие резерва.',
message: 'lock + unlock + consum в одном процессе — двойное закрытие резерва.',
})
}
}
@@ -435,7 +434,7 @@ export function checkInvariantI6NoOrphanedReserves(
return {
ok: false,
invariant: 'I6',
violation: 'I6: обнаружены процессы с некорректной парностью block/unblk/consum.',
violation: 'I6: обнаружены процессы с некорректной парностью lock/unlock/consum.',
details: violations,
}
}
@@ -4,17 +4,16 @@ import { Field, Int, ObjectType } from '@nestjs/graphql';
* Кошельки пайщика на Столе заказов.
*
* По стандарту контракта marketplace (см. `marketplace/p.mkt.supply.standard.yaml`,
* раздел «wallets») в процессах ЦПП «Стол Заказов» у пайщика участвуют четыре
* раздел «wallets») в процессах ЦПП «Стол Заказов» у пайщика участвуют три
* USER_SHARED-кошелька:
*
* 1. `w.wal.share` — ЦПП «Цифровой Кошелёк», паевые взносы деньгами (program_id=1).
* 2. `w.wal.member` — Универсальный членский кошелёк (program_id=1), играет роль
* транзитного: средства идут share → member → mkt.member.
* 3. `w.mkt.member` — Программный членский кошелёк ЦПП «Стол Заказов»
* (program_id=2), .available — свободные средства программы.
* 4. `w.mkt.order` — Резерв средств пайщика под конкретный Order (program_id=2).
* 2. `w.wal.member` — Универсальный членский кошелёк (program_id=1). Источник
* средств при createorder и приёмник при возврате (отмена/
* снятие резерва при signiss2 с актуал < ordered).
* 3. `w.mkt.order` — Резерв средств пайщика под конкретный Order (program_id=2).
* Сюда движутся средства на createorder (TRANSFER из
* w.mkt.member), обратно — на cancel/decline/expire.
* w.wal.member), обратно — на cancel/decline/expire.
* Сжигается BURN'ом на signiss2 (выдача имущества).
*
* Источник балансов — core `UserWalletRepository.findByUsername` (PG-кеш
@@ -32,7 +31,7 @@ import { Field, Int, ObjectType } from '@nestjs/graphql';
*/
@ObjectType('MarketplaceWalletEntry')
export class MarketplaceWalletEntryDTO {
@Field(() => String, { description: 'eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.member / w.mkt.order)' })
@Field(() => String, { description: 'eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.order)' })
public readonly name!: string;
@Field(() => String, { description: 'Человекочитаемое название (из cooptypes LEDGER2_WALLET_REGISTRY)' })
@@ -87,7 +86,7 @@ export class MarketplaceMemberWalletDTO {
public readonly coopname!: string;
@Field(() => [MarketplaceWalletEntryDTO], {
description: 'Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.member → mkt.order',
description: 'Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.order',
})
public readonly wallets!: MarketplaceWalletEntryDTO[];
@@ -20,9 +20,8 @@ import { MarketplaceMembershipGuard } from '../guards/marketplace-membership.gua
/**
* Релевантные стол-заказам USER_SHARED-кошельки по стандарту marketplace
* (`marketplace/p.mkt.supply.standard.yaml`, секция «wallets»). Порядок
* соответствует pipeline средств в orderoffer/createorder:
* `w.wal.share` (деньги) → `w.wal.member` (универсальный членский) →
* `w.mkt.member` (программный членский ЦПП) → `w.mkt.order` (резерв под Order).
* соответствует pipeline средств в createorder: `w.wal.share` (деньги) →
* `w.wal.member` (универсальный членский) → `w.mkt.order` (резерв под Order).
*
* `label` — UX-метка в формате `<тип взноса> | <программа>`. Не та же что
* `human_name` из cooptypes (там длинное юр.описание); этот label короткий,
@@ -35,7 +34,6 @@ const MARKETPLACE_RELEVANT_WALLETS: ReadonlyArray<{
}> = [
{ name: 'w.wal.share', program_id: 1, label: 'Паевой | Цифровой Кошелёк' },
{ name: 'w.wal.member', program_id: 1, label: 'Членский | Цифровой Кошелёк' },
{ name: 'w.mkt.member', program_id: 2, label: 'Членский | Стол Заказов' },
{ name: 'w.mkt.order', program_id: 2, label: 'Резерв | Стол Заказов' },
];
@@ -66,7 +64,7 @@ export class MarketplaceMemberWalletResolver {
@Query(() => MarketplaceMemberWalletDTO, {
name: 'marketplaceMemberWallet',
description:
'Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.member, w.mkt.order',
'Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.order',
})
@UseGuards(GqlJwtAuthGuard, MarketplaceMembershipGuard)
async marketplaceMemberWallet(
@@ -1,19 +1,18 @@
/**
* Unit-тесты MarketplaceMemberWalletResolver.
*
* Resolver возвращает массив 4-х USER_SHARED-кошельков стандарта marketplace:
* Resolver возвращает массив 3-х USER_SHARED-кошельков стандарта marketplace:
* - `w.wal.share` — паевые взносы деньгами (program_id=1).
* - `w.wal.member` — универсальный членский (program_id=1).
* - `w.mkt.member` — программный членский Стола Заказов (program_id=2).
* - `w.wal.member` — универсальный членский (program_id=1). Источник средств
* при createorder и приёмник при отмене/возврате.
* - `w.mkt.order` — резерв под Order (program_id=2). Введён 2026-05-28
* взамен механики .blocked на w.mkt.member.
* взамен механики .blocked на программном кошельке.
*
* Каждый кошелёк со своим `name`/`human_name`/`available`/`blocked`, без
* сворачивания. Источник — core `UserWalletRepository.findByUsername`
* (PG-кеш `ledger2::userwallets`). Пайщик без L3-записи по конкретному
* кошельку → `0/0` (рабочее состояние, а не ошибка) — например,
* `w.mkt.member` появляется только после первого `orderoffer`/`createorder`,
* а `w.mkt.order` — только после первого `createorder`.
* `w.mkt.order` появляется только после первого `createorder`.
*/
jest.mock('~/config/config', () => ({
@@ -36,10 +35,9 @@ const member = {
};
describe('MarketplaceMemberWalletResolver', () => {
it('пайщик с записями share + mkt.member + mkt.order → массив из 4 кошельков', async () => {
it('пайщик с записями share + mkt.order → массив из 3 кошельков, member нулевой', async () => {
const repo = makeRepo([
{ wallet_name: 'w.wal.share', available: '125.0000 RUB', blocked: '5.0000 RUB' },
{ wallet_name: 'w.mkt.member', available: '300.0000 RUB', blocked: '0.0000 RUB' },
{ wallet_name: 'w.mkt.order', available: '80.0000 RUB', blocked: '0.0000 RUB' },
// нерелевантные стол-заказам кошельки не должны попасть в выдачу
{ wallet_name: 'w.cap.blago', available: '999.0000 RUB', blocked: '0.0000 RUB' },
@@ -52,7 +50,7 @@ describe('MarketplaceMemberWalletResolver', () => {
expect(dto.username).toBe('alice');
expect(dto.coopname).toBe('voskhod');
expect(dto.wallets).toHaveLength(4);
expect(dto.wallets).toHaveLength(3);
expect(dto.wallets[0]).toMatchObject({
name: 'w.wal.share',
program_id: 1,
@@ -69,13 +67,6 @@ describe('MarketplaceMemberWalletResolver', () => {
blocked: '0',
});
expect(dto.wallets[2]).toMatchObject({
name: 'w.mkt.member',
program_id: 2,
label: 'Членский | Стол Заказов',
available: '300.0000 RUB',
blocked: '0.0000 RUB',
});
expect(dto.wallets[3]).toMatchObject({
name: 'w.mkt.order',
program_id: 2,
label: 'Резерв | Стол Заказов',
@@ -84,13 +75,13 @@ describe('MarketplaceMemberWalletResolver', () => {
});
});
it('пайщик без L3-записей вообще → 4 кошелька с 0/0 (а не NotFoundException)', async () => {
it('пайщик без L3-записей вообще → 3 кошелька с 0/0 (а не NotFoundException)', async () => {
const repo = makeRepo([]);
const resolver = new MarketplaceMemberWalletResolver(repo);
const dto = await resolver.marketplaceMemberWallet(member as any);
expect(dto.wallets).toHaveLength(4);
expect(dto.wallets).toHaveLength(3);
for (const w of dto.wallets) {
expect(w.available).toBe('0');
expect(w.blocked).toBe('0');
@@ -98,7 +89,6 @@ describe('MarketplaceMemberWalletResolver', () => {
expect(dto.wallets.map((w) => w.name)).toEqual([
'w.wal.share',
'w.wal.member',
'w.mkt.member',
'w.mkt.order',
]);
});
@@ -111,8 +101,7 @@ describe('MarketplaceMemberWalletResolver', () => {
expect(dto.wallets[0].human_name).toBe('Паевой взнос пайщика');
expect(dto.wallets[1].human_name).toBe('ЦК — членская часть пайщика');
expect(dto.wallets[2].human_name).toBe('ЦПП «Стол Заказов» — программный членский у пайщика');
expect(dto.wallets[3].human_name).toBe('ЦПП «Стол Заказов» — резерв под заказ у пайщика');
expect(dto.wallets[2].human_name).toBe('ЦПП «Стол Заказов» — резерв под заказ у пайщика');
});
it('w.mkt.payout (COOPERATIVE) не попадает в выдачу — это кооперативный кошелёк, не пайщика', async () => {
+12 -12
View File
@@ -6911,7 +6911,7 @@ export type ValueTypes = {
["MarketplaceMemberWallet"]: AliasType<{
coopname?:boolean | `@${string}`,
username?:boolean | `@${string}`,
/** Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.member → mkt.order */
/** Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.order */
wallets?:ValueTypes["MarketplaceWalletEntry"],
__typename?: boolean | `@${string}`,
['...on MarketplaceMemberWallet']?: Omit<ValueTypes["MarketplaceMemberWallet"], "...on MarketplaceMemberWallet">
@@ -7699,7 +7699,7 @@ export type ValueTypes = {
kind?:boolean | `@${string}`,
/** UX-метка кошелька в формате `<тип взноса> | <программа>` (например «Паевой | Цифровой Кошелёк», «Членский | Стол Заказов») */
label?:boolean | `@${string}`,
/** eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.member / w.mkt.order) */
/** eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.order) */
name?:boolean | `@${string}`,
/** program_id: 1 — Цифровой Кошелёк, 2 — Стол Заказов */
program_id?:boolean | `@${string}`,
@@ -9614,7 +9614,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.mkt.order */
/** Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.order */
marketplaceMemberWallet?:ValueTypes["MarketplaceMemberWallet"],
/** Состояние онбординга пайщика в Столе заказов: показывать ли gate или пропускать на стол */
marketplaceOnboardingState?:ValueTypes["MarketplaceOnboardingState"],
@@ -16873,7 +16873,7 @@ export type ResolverInputTypes = {
["MarketplaceMemberWallet"]: AliasType<{
coopname?:boolean | `@${string}`,
username?:boolean | `@${string}`,
/** Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.member → mkt.order */
/** Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.order */
wallets?:ResolverInputTypes["MarketplaceWalletEntry"],
__typename?: boolean | `@${string}`
}>;
@@ -17635,7 +17635,7 @@ export type ResolverInputTypes = {
kind?:boolean | `@${string}`,
/** UX-метка кошелька в формате `<тип взноса> | <программа>` (например «Паевой | Цифровой Кошелёк», «Членский | Стол Заказов») */
label?:boolean | `@${string}`,
/** eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.member / w.mkt.order) */
/** eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.order) */
name?:boolean | `@${string}`,
/** program_id: 1 — Цифровой Кошелёк, 2 — Стол Заказов */
program_id?:boolean | `@${string}`,
@@ -19479,7 +19479,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.mkt.order */
/** Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.order */
marketplaceMemberWallet?:ResolverInputTypes["MarketplaceMemberWallet"],
/** Состояние онбординга пайщика в Столе заказов: показывать ли gate или пропускать на стол */
marketplaceOnboardingState?:ResolverInputTypes["MarketplaceOnboardingState"],
@@ -26514,7 +26514,7 @@ export type ModelTypes = {
["MarketplaceMemberWallet"]: {
coopname: string,
username: string,
/** Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.member → mkt.order */
/** Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.order */
wallets: Array<ModelTypes["MarketplaceWalletEntry"]>
};
["MarketplaceModerationLogEntry"]: {
@@ -27241,7 +27241,7 @@ export type ModelTypes = {
kind: string,
/** UX-метка кошелька в формате `<тип взноса> | <программа>` (например «Паевой | Цифровой Кошелёк», «Членский | Стол Заказов») */
label: string,
/** eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.member / w.mkt.order) */
/** eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.order) */
name: string,
/** program_id: 1 — Цифровой Кошелёк, 2 — Стол Заказов */
program_id: number
@@ -29741,7 +29741,7 @@ export type ModelTypes = {
marketplaceListWhitelist: Array<ModelTypes["MarketplaceWhitelistEntry"]>,
/** Лента всех проектов списания кооператива с фильтром по статусу. */
marketplaceListWriteoffProposals: ModelTypes["PaginatedMarketplaceWriteoffProposals"],
/** Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.member, w.mkt.order */
/** Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.order */
marketplaceMemberWallet: ModelTypes["MarketplaceMemberWallet"],
/** Состояние онбординга пайщика в Столе заказов: показывать ли gate или пропускать на стол */
marketplaceOnboardingState: ModelTypes["MarketplaceOnboardingState"],
@@ -37072,7 +37072,7 @@ export type GraphQLTypes = {
__typename: "MarketplaceMemberWallet",
coopname: string,
username: string,
/** Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.member → mkt.order */
/** Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.order */
wallets: Array<GraphQLTypes["MarketplaceWalletEntry"]>,
['...on MarketplaceMemberWallet']: Omit<GraphQLTypes["MarketplaceMemberWallet"], "...on MarketplaceMemberWallet">
};
@@ -37860,7 +37860,7 @@ export type GraphQLTypes = {
kind: string,
/** UX-метка кошелька в формате `<тип взноса> | <программа>` (например «Паевой | Цифровой Кошелёк», «Членский | Стол Заказов») */
label: string,
/** eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.member / w.mkt.order) */
/** eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.order) */
name: string,
/** program_id: 1 — Цифровой Кошелёк, 2 — Стол Заказов */
program_id: number,
@@ -40524,7 +40524,7 @@ export type GraphQLTypes = {
marketplaceListWhitelist: Array<GraphQLTypes["MarketplaceWhitelistEntry"]>,
/** Лента всех проектов списания кооператива с фильтром по статусу. */
marketplaceListWriteoffProposals: GraphQLTypes["PaginatedMarketplaceWriteoffProposals"],
/** Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.member, w.mkt.order */
/** Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.order */
marketplaceMemberWallet: GraphQLTypes["MarketplaceMemberWallet"],
/** Состояние онбординга пайщика в Столе заказов: показывать ли gate или пропускать на стол */
marketplaceOnboardingState: GraphQLTypes["MarketplaceOnboardingState"],
@@ -24,7 +24,6 @@ export const LEDGER2_WALLET_REGISTRY: readonly WalletMeta[] = [
{ name: "w.wal.member", human_name: "ЦК — членская часть пайщика", kind: "USER_SHARED" },
{ name: "w.cap.blago", human_name: "ЦПП «Благорост» — единый кошелёк программы у пайщика", kind: "USER_SHARED" },
{ name: "w.cap.preimp", human_name: "Первичный учёт РИД-взносов до перехода на электронный учёт", kind: "USER_SHARED" },
{ name: "w.mkt.member", human_name: "ЦПП «Стол Заказов» — программный членский у пайщика", kind: "USER_SHARED" },
{ name: "w.mkt.order", human_name: "ЦПП «Стол Заказов» — резерв под заказ у пайщика", kind: "USER_SHARED" },
{ name: "w.cap.gen", human_name: "ЦПП «Генератор» — единый кошелёк программы", kind: "COOPERATIVE" },
{ name: "w.reg.entry", human_name: "Вступительные взносы", kind: "COOPERATIVE" },
@@ -57,6 +56,5 @@ export const LEDGER2_USER_SHARED_PROGRAM_MAPPING: readonly ProgramWalletMapping[
{ wallet_name: "w.cap.blago", required_program_id: 4, program_label: "Благорост" },
{ wallet_name: "w.cap.gen", required_program_id: 3, program_label: "Генератор" },
{ wallet_name: "w.cap.preimp", required_program_id: 0, program_label: null },
{ wallet_name: "w.mkt.member", required_program_id: 2, program_label: "Marketplace" },
{ wallet_name: "w.mkt.order", required_program_id: 2, program_label: "Marketplace" },
] as const
+12 -12
View File
@@ -6911,7 +6911,7 @@ export type ValueTypes = {
["MarketplaceMemberWallet"]: AliasType<{
coopname?:boolean | `@${string}`,
username?:boolean | `@${string}`,
/** Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.member → mkt.order */
/** Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.order */
wallets?:ValueTypes["MarketplaceWalletEntry"],
__typename?: boolean | `@${string}`,
['...on MarketplaceMemberWallet']?: Omit<ValueTypes["MarketplaceMemberWallet"], "...on MarketplaceMemberWallet">
@@ -7699,7 +7699,7 @@ export type ValueTypes = {
kind?:boolean | `@${string}`,
/** UX-метка кошелька в формате `<тип взноса> | <программа>` (например «Паевой | Цифровой Кошелёк», «Членский | Стол Заказов») */
label?:boolean | `@${string}`,
/** eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.member / w.mkt.order) */
/** eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.order) */
name?:boolean | `@${string}`,
/** program_id: 1 — Цифровой Кошелёк, 2 — Стол Заказов */
program_id?:boolean | `@${string}`,
@@ -9614,7 +9614,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.mkt.order */
/** Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.order */
marketplaceMemberWallet?:ValueTypes["MarketplaceMemberWallet"],
/** Состояние онбординга пайщика в Столе заказов: показывать ли gate или пропускать на стол */
marketplaceOnboardingState?:ValueTypes["MarketplaceOnboardingState"],
@@ -16873,7 +16873,7 @@ export type ResolverInputTypes = {
["MarketplaceMemberWallet"]: AliasType<{
coopname?:boolean | `@${string}`,
username?:boolean | `@${string}`,
/** Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.member → mkt.order */
/** Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.order */
wallets?:ResolverInputTypes["MarketplaceWalletEntry"],
__typename?: boolean | `@${string}`
}>;
@@ -17635,7 +17635,7 @@ export type ResolverInputTypes = {
kind?:boolean | `@${string}`,
/** UX-метка кошелька в формате `<тип взноса> | <программа>` (например «Паевой | Цифровой Кошелёк», «Членский | Стол Заказов») */
label?:boolean | `@${string}`,
/** eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.member / w.mkt.order) */
/** eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.order) */
name?:boolean | `@${string}`,
/** program_id: 1 — Цифровой Кошелёк, 2 — Стол Заказов */
program_id?:boolean | `@${string}`,
@@ -19479,7 +19479,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.mkt.order */
/** Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.order */
marketplaceMemberWallet?:ResolverInputTypes["MarketplaceMemberWallet"],
/** Состояние онбординга пайщика в Столе заказов: показывать ли gate или пропускать на стол */
marketplaceOnboardingState?:ResolverInputTypes["MarketplaceOnboardingState"],
@@ -26514,7 +26514,7 @@ export type ModelTypes = {
["MarketplaceMemberWallet"]: {
coopname: string,
username: string,
/** Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.member → mkt.order */
/** Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.order */
wallets: Array<ModelTypes["MarketplaceWalletEntry"]>
};
["MarketplaceModerationLogEntry"]: {
@@ -27241,7 +27241,7 @@ export type ModelTypes = {
kind: string,
/** UX-метка кошелька в формате `<тип взноса> | <программа>` (например «Паевой | Цифровой Кошелёк», «Членский | Стол Заказов») */
label: string,
/** eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.member / w.mkt.order) */
/** eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.order) */
name: string,
/** program_id: 1 — Цифровой Кошелёк, 2 — Стол Заказов */
program_id: number
@@ -29741,7 +29741,7 @@ export type ModelTypes = {
marketplaceListWhitelist: Array<ModelTypes["MarketplaceWhitelistEntry"]>,
/** Лента всех проектов списания кооператива с фильтром по статусу. */
marketplaceListWriteoffProposals: ModelTypes["PaginatedMarketplaceWriteoffProposals"],
/** Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.member, w.mkt.order */
/** Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.order */
marketplaceMemberWallet: ModelTypes["MarketplaceMemberWallet"],
/** Состояние онбординга пайщика в Столе заказов: показывать ли gate или пропускать на стол */
marketplaceOnboardingState: ModelTypes["MarketplaceOnboardingState"],
@@ -37072,7 +37072,7 @@ export type GraphQLTypes = {
__typename: "MarketplaceMemberWallet",
coopname: string,
username: string,
/** Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.member → mkt.order */
/** Релевантные стол-заказам USER_SHARED-кошельки пайщика; порядок: share → member → mkt.order */
wallets: Array<GraphQLTypes["MarketplaceWalletEntry"]>,
['...on MarketplaceMemberWallet']: Omit<GraphQLTypes["MarketplaceMemberWallet"], "...on MarketplaceMemberWallet">
};
@@ -37860,7 +37860,7 @@ export type GraphQLTypes = {
kind: string,
/** UX-метка кошелька в формате `<тип взноса> | <программа>` (например «Паевой | Цифровой Кошелёк», «Членский | Стол Заказов») */
label: string,
/** eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.member / w.mkt.order) */
/** eosio::name кошелька (w.wal.share / w.wal.member / w.mkt.order) */
name: string,
/** program_id: 1 — Цифровой Кошелёк, 2 — Стол Заказов */
program_id: number,
@@ -40524,7 +40524,7 @@ export type GraphQLTypes = {
marketplaceListWhitelist: Array<GraphQLTypes["MarketplaceWhitelistEntry"]>,
/** Лента всех проектов списания кооператива с фильтром по статусу. */
marketplaceListWriteoffProposals: GraphQLTypes["PaginatedMarketplaceWriteoffProposals"],
/** Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.member, w.mkt.order */
/** Кошельки пайщика, релевантные столу заказов: w.wal.share, w.wal.member, w.mkt.order */
marketplaceMemberWallet: GraphQLTypes["MarketplaceMemberWallet"],
/** Состояние онбординга пайщика в Столе заказов: показывать ли gate или пропускать на стол */
marketplaceOnboardingState: GraphQLTypes["MarketplaceOnboardingState"],