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:
@@ -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) {
|
||||
|
||||
@@ -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!
|
||||
|
||||
|
||||
+65
-194
@@ -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')
|
||||
})
|
||||
|
||||
+27
-28
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
+8
-9
@@ -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[];
|
||||
|
||||
|
||||
+3
-5
@@ -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(
|
||||
|
||||
+10
-21
@@ -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 () => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"],
|
||||
|
||||
Reference in New Issue
Block a user