marketplace2 → dev: Стол заказов (весь стек + dev-мердж + ku-selforg) #155

Open
claude wants to merge 670 commits from marketplace2 into dev
Owner

Зонтичный PR: подтягивает всю ветку marketplace2 (продукт «Стол заказов») в dev.

marketplace2 сейчас на 657 коммитов впереди dev и не отстаёт (0) — dev уже
целиком слит внутрь marketplace2 отдельным merge-коммитом, поэтому конфликтов
при мердже в dev быть не должно.

Состав: полный Стол заказов (каталог, заказы, приёмка/выдача, возвраты,
списание скоропорта, склад кооператива), экономика КУ (accrue/distribute/
spend, реестр плановых расходов), реестр поставщиков, самоорганизация
кооперативных участков (feat/ku-selforg — собрания/решения, приватность,
доверенные лица), плюс всё, что уже было в dev на момент слияния (двухфазный
регистрационный взнос через счёт 76, шасси расходов, self-service выход
пайщика и др.) — уже интегрировано и совместимо.

НЕ мерджить без явного решения — зонтичный PR на ревью.

🤖 Generated with Claude Code

Зонтичный PR: подтягивает всю ветку marketplace2 (продукт «Стол заказов») в dev. marketplace2 сейчас на 657 коммитов впереди dev и не отстаёт (0) — dev уже целиком слит внутрь marketplace2 отдельным merge-коммитом, поэтому конфликтов при мердже в dev быть не должно. Состав: полный Стол заказов (каталог, заказы, приёмка/выдача, возвраты, списание скоропорта, склад кооператива), экономика КУ (accrue/distribute/ spend, реестр плановых расходов), реестр поставщиков, самоорганизация кооперативных участков (feat/ku-selforg — собрания/решения, приватность, доверенные лица), плюс всё, что уже было в dev на момент слияния (двухфазный регистрационный взнос через счёт 76, шасси расходов, self-service выход пайщика и др.) — уже интегрировано и совместимо. НЕ мерджить без явного решения — зонтичный PR на ревью. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude added 657 commits 2026-07-20 13:38:35 +00:00
Перенесено с origin/feat/marketplace-orders (commit 585afb16eb):
- contracts/cpp/marketplace/* — новые actions deliver_on_order/{createorder,respondoffer},
  deliver_on_offer/{acceptstock,coopstock,destroy,reoffer,reqreturn}
- cooptypes/contracts/marketplace/* — типы новых actions
- sdk/mutations/cooplace/* — disputeOnRequest и др.
- controller/extensions/marketplace (87 файлов) + extensions/cooplace (split) — НОВЫЕ
- controller/application/cooplace + domain/cooplace — обновлены под новые actions
- desktop/pages/Marketplace/{DisputePage,ShipmentsPage,WarehousePage} — НОВЫЕ
- desktop/widgets/Marketplace/SupplyOrderRequestCard/ui/Steps/{ReqReturnStep,RetAuthorizedStep}
- desktop/features/Request — обновлены
- desktop/extensions/market — НОВОЕ
- boot/tests/marketplace.test.ts — НОВОЕ
- MARKET-LOGIC.md — описание бизнес-процессов

Поправлен include marketplace.hpp: ../lib/common.hpp → ../lib/index.hpp
(на reports common.hpp удалён, заменён на index.hpp).

shared_marketplace.hpp НЕ переносим — на reports его содержимое размазано
по lib/core/marketplace/marketplace.hpp + lib/domain/table_marketplace_*.hpp

Сборка пока не проверена — следующий шаг.
Поправки на reports-base, чтобы новые actions из marketplace-orders
скомпилировались:

1. lib/core/document.hpp — добавлен Document::remove_document(docs, name)
   (вызывается в reqreturn.cpp; в исходном marketplace-orders отсутствовал —
   значит контракт там и не собирался).

2. lib/domain/table_marketplace_requests.hpp — добавлены поля request:
   - delivery_type (internal/external)
   - contribution_type (share/member)
   Эти поля устанавливаются в orderoffer/createorder и используются в
   проводках complete (см. MARKET-LOGIC.md, Правила 3 и 4).

3. marketplace.hpp — переключён include с ../lib/common.hpp на ../lib/index.hpp
   (на reports common.hpp удалён рефактором 1744fab580).

Сборка: bash build.sh marketplace test → marketplace.wasm OK.
1. extensions/marketplace/* — заменить пути:
   - ~/modules/auth/* → ~/application/auth/*
   - ~/modules/logger/logger-app.service → ~/application/logger/logger-app.service
   На reports эти каталоги называются application/, не modules/.

2. extensions/marketplace/marketplace-extension.module.ts — убрать
   ExtensionPortsModule (на reports его нет, capital его не использует —
   это артефакт marketplace-orders без поддержки на текущей базе).

3. infrastructure/blockchain/adapters/cooplace-blockchain.adapter.ts —
   взять с feat/marketplace-orders: добавлены 5 методов
   (reqReturn, coopstock, acceptStock, destroy, reoffer), которые
   требует CooplaceBlockchainPort.

cooptypes пересобран (pnpm -C cooptypes build) — Ledger2/Ledger2Contract
теперь экспортируются.

tsc --noEmit: EXIT=0
controller:
- app.module.ts — импорт MarketplacePluginModule + CooplaceExtensionModule в imports
- extensions/extensions.registry.ts — заменить заглушку 'orders' на реальный
  entry 'market' с MarketplacePluginModule + MarketplacePlugin + MarketplaceSchema

desktop:
- src/processes/init-installed-extensions/extensions-registry.ts —
  импорт marketInstall из extensions/market/install и регистрация ключа 'market'

tsc --noEmit (controller): EXIT=0
1. Убран лишний import { withDefaults } from 'vue' из 9 файлов:
   - features/Request/CreateChildOrder/ui/CreateChildOrderButton.vue
   - widgets/Marketplace/SupplyOrderRequestCard/ui/Steps/{First..Eighth}Step.vue
   В Vue 3 withDefaults — compiler macro, доступен глобально без импорта,
   явный import конфликтует с локальным declaration (TS2440).

2. widgets/Marketplace/SupplyOrderRequestCard/ui/Base/Base.vue —
   request.membership_fee_amount → request.membership_fee
   (поле в IRequestData/SDK называется membership_fee).

vue-tsc --noEmit --skipLibCheck: 0 ошибок.
Цель — убрать дублирующее имя 'cooplace' в пользу единого 'marketplace'
во всех слоях (controller, sdk, desktop). Имя 'cooplace' было артефактом
переходного периода между плагин-стилем (старая marketplace ветка) и
extension-стилем (feat/marketplace-orders); оставалось 4 параллельных
каталога с одной семантикой.

Каталоги (git mv с историей):
- controller/src/application/cooplace → application/marketplace
- controller/src/domain/cooplace → domain/marketplace
- controller/src/extensions/cooplace → extensions/marketplace-cards
  (отдельная extension со своей сферой ProductCard/Category/SupplyOrder,
   именно поэтому marketplace-cards, а не marketplace — чтобы не
   коллизировать с extensions/marketplace, который про request/attribute)
- sdk/src/mutations/cooplace → mutations/marketplace
- sdk/src/selectors/cooplace → selectors/marketplace

Файлы:
- cooplace.module.ts → marketplace.module.ts (в application и domain)
- cooplace.service/interactor/resolver.ts → marketplace.*
- cooplace-extension.module.ts → marketplace-cards.module.ts
- cooplace-blockchain.port/adapter.ts → marketplace-blockchain.*

Классы:
- CooplaceModule → MarketplaceModule
- CooplaceDomainModule → MarketplaceDomainModule
- CooplaceExtensionModule → MarketplaceCardsModule
- CooplaceInteractor/Resolver/Service → Marketplace*
- CooplaceBlockchainPort/Adapter → MarketplaceBlockchainPort/Adapter
- COOPLACE_BLOCKCHAIN_PORT (Symbol) → MARKETPLACE_BLOCKCHAIN_PORT
- camelCase: cooplaceInteractor/Service/BlockchainPort → marketplace*

Чтобы избежать коллизии класс-имён в extensions/marketplace
(где уже были MarketplaceDomainModule и MarketplaceApplicationModule
для catalog-слоя), переименовал их:
- MarketplaceDomainModule → MarketplaceExtensionDomainModule
- MarketplaceApplicationModule → MarketplaceExtensionApplicationModule
Эти классы видны только внутри extensions/marketplace, наружу торчит
только MarketplacePluginModule + MarketplacePlugin.

SDK re-exports:
- mutations/index.ts: 'export * as Cooplace' → 'export * as Marketplace'
- selectors/index.ts: 'export * from ./cooplace' → './marketplace'

Сборки после изменений:
- controller tsc --noEmit: EXIT=0
- desktop vue-tsc --noEmit --skipLibCheck: EXIT=0
- контракт marketplace не затронут (cooplace там никогда не было)
Стандарт описывал клиринговую модель donor'а (паевый взнос имуществом
+ возврат паевого взноса имуществом). На ветке marketplace2 заменён
тремя новыми стандартами под членскую модель Стол заказов MVP:
  • p.mkt.supply.standard.yaml   — Прямая поставка-приобретение имущества
  • p.mkt.return.standard.yaml   — Гарантийный возврат имущества
  • p.mkt.wroff.standard.yaml    — Утилизация скоропорта

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Добавлены кооперативные стандарты для процессов контракта marketplace
в режиме членских взносов (упрощённая модель с одним программным
кошельком w.mkt.fund, без транзитов):

  • p.mkt.supply.standard.yaml   — Прямая поставка-приобретение имущества
                                    (7 операций ledger2, двухвариантная приёмка А/Б,
                                     двойные подписи АПП приёмки и АПП выдачи)
  • p.mkt.return.standard.yaml   — Гарантийный возврат имущества
                                    (compensating forward o.mkt.return,
                                     без ledger2::revert)
  • p.mkt.wroff.standard.yaml    — Утилизация скоропорта
                                    (ACCOUNT_ONLY с транзитом через счёт 91)

Все wallet_from/wallet_to используют eosio::name-строки
(w.mkt.fund, w.wal.share, w.mkt.payout) согласно рефакторингу 2026-04-27
на ветке reports. Документы привязаны к существующим шаблонам:
  • АПП приёмки = registry_id 702 (AssetContributionAct)
  • АПП выдачи  = registry_id 802 (ReturnByAssetAct)
  • Заявление возврата = registry_id 800 (ReturnByAssetStatement)
  • ТТН и протокол списания = 0 + TODO (нет шаблонов в реестре).

Все операции имеют статус proposed; имена ≤12 chars eosio::name.
Бухгалтерские проводки помечены TBD-Standardization для последующей
сверки с Ангелиной/Игорем.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Учётная модель ledger2 теперь выражается в стандарте на трёх уровнях:
L1 — бухгалтерские проводки (debit/credit), L2 — кошельки кооператива
(wallet_from → wallet_to, агрегаты), L3 — кошелёк пайщика (per-user split
available/blocked: user_wallet, user_ref, available_delta, blocked_delta).
В types/standard.ts добавлены поля под L3, расширен enum WalletOp до полного
набора _blago (BURN/REVOKE/ACCOUNT_ONLY); FocusBar показывает три отдельные
карточки «Проводки / Кошельки кооператива / Кошелёк пайщика» с условным
показом — пустой уровень не выводится; L3-карточка тянется на всю ширину
op-сетки и идёт горизонтальной строкой (wallet · user · Доступно · Заблокировано),
лейблы по-русски. На прозрачные --soft-варианты фона добавлен override на
непрозрачный --bg, чтобы overlay-панель не светила сквозь себя.

Рабочая область процесса доработана: FocusBar переехал из нижней горизонтальной
полосы в левую вертикальную панель (правый верх занят зум-контролами VueFlow,
куда добавлена кнопка Maximize/Minimize2 для fullscreen-режима через нативный
Fullscreen API + автo-фит при смене размера); стартовый зум усилен (boost
2.0 → 2.8 ≈ +3 нажатия «+»). В fullscreen появляются дублирующие prev/next
оверлеями у нижнего края (внешние ◀/▶ остаются за экраном). Навигация по ←/→
теперь идёт по happy-path: pickHappyTransition в graph/layout.ts штрафует
имена-отказы (cancel/decline/expire/reject/abort/abandon/fail/revoke/discard)
и legacy actions[].role === 'reject', штрафует самопетли (from === to),
бонус за target-статус с продолжением; tie-break — порядок YAML.

Vite dev: cooptypes (workspace ESM-бандл ~2 МБ) добавлен в optimizeDeps.include —
без этого vite отдавал его через @fs трансформ-конвейером (раздувался до 10 МБ),
по сети валился dynamic import ProcessPage с «Failed to fetch dynamically
imported module». Pre-bundled chunk теперь 987 КБ.

Пилот L3 на p.mkt.supply.standard.yaml (createorder + полный жизненный цикл):
переименовано w.mkt.fund → w.mkt.member по всему файлу (33 случая в supply +
return — это per-user членский кошелёк программы Marketplace, fund-наименование
вводило в заблуждение); o.mkt.fconv (TRANSFER) получил L3 +order.total_cost
на available заказчика, o.mkt.fblock (BLOCK) — only-L3 split −available/+blocked,
o.mkt.funblk (UNBLOCK) — зеркало, o.mkt.fefund (TRANSFER) — −available на возврат,
o.mkt.consum (REVOKE) — only-L3 −blocked + L1 Дт 86/Кт 10. Также введён
промежуточный статус ship_ready («Отгрузка собрана») между accepted и
supply_prepared: prepship больше не самопетля accepted→accepted, а accepted→
ship_ready, signsupp идёт от ship_ready → supply_prepared — линейный поток
без обратных стрелок dagre.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts:
#	components/contracts/standards-site/src/components/FocusBar.vue
#	components/contracts/standards-site/src/types/standard.ts
После мержа origin/ledger3 в WalletOp добавлен NONE (внутрибалансовые
проводки без движения кошелька, например ACCEPT_RID — Dr 04 / Cr 08
без перемещения w.cap.gen).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В §1 паспорта стандарта поле `summary` упразднено. Единственное прозаическое
описание процесса теперь в `purpose` — рендерится на главной странице
(line-clamp 3) и в карточке «Начало процесса». Чтобы не было соблазна писать
дублирующее «краткое описание процесса» вроде «от подписанного заявления до
карточки активного пайщика».

- 15 *.standard.yaml: блок summary удалён
- standards-site: type Standard.summary удалён, рендер в FocusBar/HomePage/StartNode/EndNode
  переключён на purpose; CSS-класс home-card__summary → home-card__purpose
- SKILL.md и a2-spetsifikatsiya: помечено что summary упразднён в v1

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts:
#	components/contracts/cpp/capital/p.cap.rid.standard.yaml
- p.reg.accept.standard.yaml: прозаические поля (purpose/description/guards/pre/post) переписаны
  для НЕ-технарей — без имён экшенов, операций, кошельков, проводок, таблиц
- введён термин «регистрационный взнос» (вступительный + минимальный паевой) с раскрытием
  при первом упоминании
- standards-site/FocusBar.vue: ROLE_HUMAN += candidate → «Кандидат»
- standards-site/vite.config.ts: плагин watch-cpp-standards подключает ../cpp/ к vite watcher
  и инвалидирует loader.ts при изменении *.standard.yaml — HMR срабатывает без перезапуска

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- operations.hpp: enum WalletOp + REVOKE (списание blocked + обяз. Dr/Cr); удалены CONFIRM_SUPPLY/CONFIRM_RECEIPT; добавлены 12 операций членской модели — CONVERT_TO_MEMBER в operations::wallet и 11 в operations::marketplace (ASSIGN_TO_PROGRAM, BLOCK_FOR_ORDER, UNBLOCK_ON_CANCEL, RECALL_TO_UNIVERSAL, PURCHASE_FROM_SUPPLIER, PAY_SUPPLIER, CONSUME_BY_MEMBER + CONSUME_TRANSIT_CLOSE, RETURN_BY_MEMBER + RETURN_TRANSIT_CLOSE, WRITE_OFF_PERISHABLE + WRITE_OFF_TRANSIT_CLOSE); composite-проводки через транзит счёта 91 «Прочие доходы и расходы» разбиты на пары операций (часть 1 + часть 2, *2-суффикс), вызываемые последовательно в одной транзакции Antelope — атомарность через транзакцию.
- accounts.hpp: добавлены счета 10 «Материалы» (А) и 91 «Прочие доходы и расходы» (А/П).
- wallets.hpp: добавлен USER_SHARED-кошелёк w.mkt.member («ЦПП Стол Заказов — программный членский») с program_id=2 (marketplace).
- processes.hpp: удалён p.mkt.reqst (клиринговый), добавлены p.mkt.supply / p.mkt.return / p.mkt.wroff.
- p.mkt.{supply,return,wroff}.standard.yaml: composite-проводки разнесены на пары operations[]; транзит через 91 явно описан в комментариях и description (нетто-эффект — Дт 86 / Кт 10 на выдаче/утилизации и Дт 10 / Кт 86 на возврате).

Согласовано с Ангелиной 2026-05-11.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- accounts.ts: + 10 «Материалы», 91 «Прочие доходы и расходы».
- processes.ts: убран legacy p.mkt.reqst (REQUEST), добавлены p.mkt.supply / p.mkt.return / p.mkt.wroff.
- operations.ts: WalletOp += 'REVOKE'; legacy o.mkt.supply/recv (CONFIRM_SUPPLY/RECEIPT) заменены на 12 операций членской модели (CONVERT_TO_MEMBER, ASSIGN_TO_PROGRAM, BLOCK_FOR_ORDER, UNBLOCK_ON_CANCEL, RECALL_TO_UNIVERSAL, PURCHASE_FROM_SUPPLIER, PAY_SUPPLIER, CONSUME_BY_MEMBER + CONSUME_TRANSIT_CLOSE, RETURN_BY_MEMBER + RETURN_TRANSIT_CLOSE, WRITE_OFF_PERISHABLE + WRITE_OFF_TRANSIT_CLOSE); composite-проводки через 91 разбиты на пары *...*2.
- wallets.generated.ts: auto-regen из wallets.hpp — +w.mkt.member (program_id=2).

Без этого в standards-site новые кошельки и операции marketplace показывались «нет в реестре».

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- process-hash-locator.ts: удалён мёртвый ключ p.mkt.reqst (привязка к legacy таблице marketplace::requests). Добавлены пустые locators для p.mkt.supply / p.mkt.return / p.mkt.wroff — реализация контракта marketplace под членскую модель ещё не написана, операции на бэкенд не прилетают. TODO-комментарий зафиксирован: при реализации использовать поле request_hash (универсальное; order_hash избегаем — пересекается с offer_hash из orderoffer-actions).
- process-registry.service.ts: убрано упоминание p.mkt.reqst из устаревшего комментария-примера мульти-операционных процессов.

Без этого фикса не пуш-критично (Phase A никогда не маппила бы operation на удалённый process_type), но мёртвый ключ создавал бы путаницу.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts:
#	components/contracts/cpp/lib/core/ledger2/wallets.hpp
#	components/cooptypes/src/ledger2/wallets.generated.ts
- Подключить MarketplacePluginModule в ExtensionsModule.register (расширение
  не загружалось в NestJS DI-контейнер).
- Унифицировать имя расширения: MarketplacePlugin.name = 'market' (совпадает
  с ключом AppRegistry; install через installExtension({name:"market"}) теперь
  не падает на findByName('marketplace')).
- Расширить MarketplacePlugin.initialize: bucket coop-<coopname> через
  optional file-storage порт (@Optional injection, fallback под warn-логом до
  merge PR #359); три AC-лога backend'а — 'Создан физический бакет ...',
  'File storage готов', 'marketplace-extension готов'.
- Bootstrap config-миграция v1 для расширения market.
- Atomic rollback в ExtensionInteractor.installApp: при провале runApp снести
  запись через uninstallApp, только если её не было до install; ошибка
  пробрасывается наверх (UI Каталога показывает 'Ошибка').
- Process locator: обновить TODO p.mkt.* — заглушки готовы, активация в
  Story 4.1 при создании marketplace::requests.

Unit-тесты:
- tests/unit/marketplace/marketplace-plugin-initialize.test.ts (4 проходят).
- tests/unit/appstore/extension-interactor-install-rollback.test.ts (3 проходят).

Refs: blago/production/1-prilozhenie-stol-zakazov/components/3-minimalnyy-produkt/_bmad-output/implementation-artifacts/spec-1-1-install-from-catalog.md

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(marketplace): Story 1.2 — регистрация оферты в AgreementRegistry

Перепиcано через существующий core-механизм `AgreementRegistrationPort`
(закрытый PR #369 шёл через параллельный coop_cpp_registry — техдолг).
Аналогично тому, как Capital регистрирует свои оферты —
register-capital-in-agreement-registry.ts.

- constants/marketplace-agreement-ids.ts — MARKETPLACE_EXTENSION_NAME='market',
  MARKETPLACE_OFFER_AGREEMENT_ID='order_table_offer',
  MARKETPLACE_AGREEMENT_TYPE='order_table',
  MARKETPLACE_OFFER_TEMPLATE_REGISTRY_ID=0 (placeholder; Story 1.7 заменит
  на реальный registry_id из платформенной document factory, по аналогии с
  Cooperative.Registry.GeneratorOffer/BlagorostOffer).
- application/registration/register-marketplace-in-agreement-registry.ts —
  чистая функция: при ID>0 зовёт port.registerAgreement с одной офертой
  (applicable_account_types: individual + entrepreneur, order: 7); при ID<=0
  возвращает false без вызовов. Programs marketplace не регистрирует —
  Стол заказов ЦПП без program-надстройки.
- marketplace-extension.module — @Inject(AGREEMENT_REGISTRATION_PORT) +
  вызов registerMarketplaceInAgreementRegistry в initialize после initBucket;
  info-лог при skip и при успешной регистрации, error при exception.

Tests:
- tests/unit/marketplace/marketplace-plugin-register.test.ts (3 кейса):
  чистая функция — port не вызван пока placeholder=0, константы согласованы,
  programs не регистрируются.
- tests/unit/marketplace/marketplace-plugin-initialize.test.ts — обновлён
  конструктор (добавлен makeAgreementPort стаб); 4 кейса Story 1.1
  продолжают проходить.

10/10 unit-тестов зелёных, tsc 0 ошибок.

Ref: blago/.../components/3-minimalnyy-produkt/_bmad-output/implementation-artifacts/spec-1-2-cpp-registry-link.md
(spec будет переписан вместе с blago-issue после merge).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(marketplace): MARKETPLACE_AGREEMENT_TYPE = 'marketplace' (валидный eosio::name)

Per review PR #370: 'order_table' содержит '_' — не валидный eosio::name
(допустимы только [.a-z1-5], max 12). В lib/consts.hpp уже определено
program-имя `_marketplace_program = "marketplace"_n` (program_id=2), его
и принимает `soviet::coagreements`/`sndagreement`.

- MARKETPLACE_AGREEMENT_TYPE: 'order_table' → 'marketplace'.
- MARKETPLACE_OFFER_AGREEMENT_ID: 'order_table_offer' → 'marketplace_offer'
  (консистентно с 'generator_offer'/'blagorost_offer' у Capital).
- AgreementRegistrationSpec.agreement_type — JSDoc уточнён: должен быть
  eosio::name (max 12, без `_`), пример обновлён.
- Тест проверяет eosio::name regex `/^[.a-z1-5]{1,12}$/` для type.

10/10 unit зелёные, tsc 0 ошибок.

---------

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(marketplace): Story 1.2 — регистрация оферты в AgreementRegistry

Перепиcано через существующий core-механизм `AgreementRegistrationPort`
(закрытый PR #369 шёл через параллельный coop_cpp_registry — техдолг).
Аналогично тому, как Capital регистрирует свои оферты —
register-capital-in-agreement-registry.ts.

- constants/marketplace-agreement-ids.ts — MARKETPLACE_EXTENSION_NAME='market',
  MARKETPLACE_OFFER_AGREEMENT_ID='order_table_offer',
  MARKETPLACE_AGREEMENT_TYPE='order_table',
  MARKETPLACE_OFFER_TEMPLATE_REGISTRY_ID=0 (placeholder; Story 1.7 заменит
  на реальный registry_id из платформенной document factory, по аналогии с
  Cooperative.Registry.GeneratorOffer/BlagorostOffer).
- application/registration/register-marketplace-in-agreement-registry.ts —
  чистая функция: при ID>0 зовёт port.registerAgreement с одной офертой
  (applicable_account_types: individual + entrepreneur, order: 7); при ID<=0
  возвращает false без вызовов. Programs marketplace не регистрирует —
  Стол заказов ЦПП без program-надстройки.
- marketplace-extension.module — @Inject(AGREEMENT_REGISTRATION_PORT) +
  вызов registerMarketplaceInAgreementRegistry в initialize после initBucket;
  info-лог при skip и при успешной регистрации, error при exception.

Tests:
- tests/unit/marketplace/marketplace-plugin-register.test.ts (3 кейса):
  чистая функция — port не вызван пока placeholder=0, константы согласованы,
  programs не регистрируются.
- tests/unit/marketplace/marketplace-plugin-initialize.test.ts — обновлён
  конструктор (добавлен makeAgreementPort стаб); 4 кейса Story 1.1
  продолжают проходить.

10/10 unit-тестов зелёных, tsc 0 ошибок.

Ref: blago/.../components/3-minimalnyy-produkt/_bmad-output/implementation-artifacts/spec-1-2-cpp-registry-link.md
(spec будет переписан вместе с blago-issue после merge).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(marketplace): MARKETPLACE_AGREEMENT_TYPE = 'marketplace' (валидный eosio::name)

Per review PR #370: 'order_table' содержит '_' — не валидный eosio::name
(допустимы только [.a-z1-5], max 12). В lib/consts.hpp уже определено
program-имя `_marketplace_program = "marketplace"_n` (program_id=2), его
и принимает `soviet::coagreements`/`sndagreement`.

- MARKETPLACE_AGREEMENT_TYPE: 'order_table' → 'marketplace'.
- MARKETPLACE_OFFER_AGREEMENT_ID: 'order_table_offer' → 'marketplace_offer'
  (консистентно с 'generator_offer'/'blagorost_offer' у Capital).
- AgreementRegistrationSpec.agreement_type — JSDoc уточнён: должен быть
  eosio::name (max 12, без `_`), пример обновлён.
- Тест проверяет eosio::name regex `/^[.a-z1-5]{1,12}$/` для type.

10/10 unit зелёные, tsc 0 ошибок.

* feat(marketplace): Story 1.3 — доступ к Столу заказов по членству

Реализует:
- MarketplaceMembershipGuard: 401 (нет JWT), 403 (status != active),
  server-secret bypass, формирует request.currentMember и
  ctx.currentMember для GraphQL.
- mapUserRoleToCoreRoles: user.role → core_roles[]
  - 'user'     → ['User']
  - 'member'   → ['User', 'Member']
  - 'chairman' → ['User', 'Member', 'Chairman']
  - 'admin'/unknown → []
- @CurrentMarketplaceMember decorator + MarketplaceCurrentMemberDTO
  ({username, core_roles[], marketplace_roles[]}).
- Query marketplaceWhoAmI(): возвращает контекст текущего пайщика для
  фронта (UI открывается на primary столе по роли).
- marketplace_roles[] пока пустой — наполняется в Story 1.6.

Тесты: 18/18 зелёных (5 mapper + 6 guard + 4 initialize + 3 register).
tsc 0 ошибок.

---------

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[598-4][@ant]
* feat(marketplace): Story 1.2 — регистрация оферты в AgreementRegistry

Перепиcано через существующий core-механизм `AgreementRegistrationPort`
(закрытый PR #369 шёл через параллельный coop_cpp_registry — техдолг).
Аналогично тому, как Capital регистрирует свои оферты —
register-capital-in-agreement-registry.ts.

- constants/marketplace-agreement-ids.ts — MARKETPLACE_EXTENSION_NAME='market',
  MARKETPLACE_OFFER_AGREEMENT_ID='order_table_offer',
  MARKETPLACE_AGREEMENT_TYPE='order_table',
  MARKETPLACE_OFFER_TEMPLATE_REGISTRY_ID=0 (placeholder; Story 1.7 заменит
  на реальный registry_id из платформенной document factory, по аналогии с
  Cooperative.Registry.GeneratorOffer/BlagorostOffer).
- application/registration/register-marketplace-in-agreement-registry.ts —
  чистая функция: при ID>0 зовёт port.registerAgreement с одной офертой
  (applicable_account_types: individual + entrepreneur, order: 7); при ID<=0
  возвращает false без вызовов. Programs marketplace не регистрирует —
  Стол заказов ЦПП без program-надстройки.
- marketplace-extension.module — @Inject(AGREEMENT_REGISTRATION_PORT) +
  вызов registerMarketplaceInAgreementRegistry в initialize после initBucket;
  info-лог при skip и при успешной регистрации, error при exception.

Tests:
- tests/unit/marketplace/marketplace-plugin-register.test.ts (3 кейса):
  чистая функция — port не вызван пока placeholder=0, константы согласованы,
  programs не регистрируются.
- tests/unit/marketplace/marketplace-plugin-initialize.test.ts — обновлён
  конструктор (добавлен makeAgreementPort стаб); 4 кейса Story 1.1
  продолжают проходить.

10/10 unit-тестов зелёных, tsc 0 ошибок.

Ref: blago/.../components/3-minimalnyy-produkt/_bmad-output/implementation-artifacts/spec-1-2-cpp-registry-link.md
(spec будет переписан вместе с blago-issue после merge).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(marketplace): MARKETPLACE_AGREEMENT_TYPE = 'marketplace' (валидный eosio::name)

Per review PR #370: 'order_table' содержит '_' — не валидный eosio::name
(допустимы только [.a-z1-5], max 12). В lib/consts.hpp уже определено
program-имя `_marketplace_program = "marketplace"_n` (program_id=2), его
и принимает `soviet::coagreements`/`sndagreement`.

- MARKETPLACE_AGREEMENT_TYPE: 'order_table' → 'marketplace'.
- MARKETPLACE_OFFER_AGREEMENT_ID: 'order_table_offer' → 'marketplace_offer'
  (консистентно с 'generator_offer'/'blagorost_offer' у Capital).
- AgreementRegistrationSpec.agreement_type — JSDoc уточнён: должен быть
  eosio::name (max 12, без `_`), пример обновлён.
- Тест проверяет eosio::name regex `/^[.a-z1-5]{1,12}$/` для type.

10/10 unit зелёные, tsc 0 ошибок.

* feat(marketplace): Story 1.3 — доступ к Столу заказов по членству

Реализует:
- MarketplaceMembershipGuard: 401 (нет JWT), 403 (status != active),
  server-secret bypass, формирует request.currentMember и
  ctx.currentMember для GraphQL.
- mapUserRoleToCoreRoles: user.role → core_roles[]
  - 'user'     → ['User']
  - 'member'   → ['User', 'Member']
  - 'chairman' → ['User', 'Member', 'Chairman']
  - 'admin'/unknown → []
- @CurrentMarketplaceMember decorator + MarketplaceCurrentMemberDTO
  ({username, core_roles[], marketplace_roles[]}).
- Query marketplaceWhoAmI(): возвращает контекст текущего пайщика для
  фронта (UI открывается на primary столе по роли).
- marketplace_roles[] пока пустой — наполняется в Story 1.6.

Тесты: 18/18 зелёных (5 mapper + 6 guard + 4 initialize + 3 register).
tsc 0 ошибок.

* feat(marketplace): Story 1.4 — L3 fallback gate онбординга

Domain service `MarketplaceOnboardingService.getOnboardingState(username)`:
- placeholder=0 (Story 1.7 не выполнена) → {requires_gate:false,
  source:'not_configured'} — фронт пропускает на стол.
- agreementRepository.findByUsername → найдена запись type='marketplace'
  c подходящим draft_id (или без draft_id как fallback) →
  {requires_gate:false, source:'agreement_signed', completed_at,
  agreement_id}.
- иначе {requires_gate:true, source:'gate_required'}.

Не заводим локальную таблицу marketplace_onboarding_state — core
AgreementRepository уже даёт state через PG-кеш agreements3
(AgreementSyncService); TTL 60s из PRD не нужен.

Mutation подписания не реализуется в этой Story — при placeholder=0 gate
не показывается, путь активируется после Story 1.7.

GraphQL Query `marketplaceOnboardingState: MarketplaceOnboardingState`
под GqlJwtAuthGuard + MarketplaceMembershipGuard. username берётся из
@CurrentMarketplaceMember, фронт не подменяет member_id.

Тесты: 22/22 зелёных (4 onboarding-service + 18 предыдущих).
tsc 0 ошибок.

* feat(marketplace): Story 1.5 — кошелёк ЦК пайщика на столе

Query marketplaceMemberWallet под GqlJwtAuthGuard + MarketplaceMembershipGuard,
делегирует чтение в core WalletService.getProgramWallet(coopname, username,
program_type=MAIN). PG-кеш ledger2::userwallets уже консистентен с
blockchain (UserWalletSyncService); локальная таблица
marketplace_member_wallet_link из PRD не заводится — техдолг PRD.

DTO MarketplaceMemberWallet возвращает:
- available/blocked — из w.wal.share части ledger2-кошелька;
- membership_contribution — w.wal.member.value;
- contract='wallet', coopname, username.

Если PG-кеш пуст → NotFoundException (CLAUDE.md запрещает RPC fallback).
Поля undefined нормализуются в '0'.

Тесты: 25/25 зелёных (3 wallet resolver + 22 предыдущих).
tsc 0 ошибок.

* feat(marketplace): Story 1.6 — массив marketplace-ролей + RoleGuard

mapCoreRolesToMarketplaceRoles(coreRoles[], context):
- User → [orderer] +opt[offerer] +opt[operator]
- Member → +board_readonly
- Chairman → +admin, +board
- без User → []

isOfferer/isKuChairman context-флаги зарезервированы (Эпики 2/3).

MarketplaceMembershipGuard теперь заполняет marketplace_roles[] через
mapper (раньше был placeholder []).

MarketplaceRoleGuard (новый): отдельный guard под
@RequireMarketplaceRole('admin', 'board') (OR-семантика). Если декоратор
не задан → guard разрешает. При запрете:
- ForbiddenException "Forbidden: marketplace role 'X' required, member has [...]"
- warn-лог forbidden-attempt: member=, action=, requested_role=[],
  actual_marketplace_roles=[], actual_core_roles=[].

server-secret bypass обоих guards (inter-service).

README рядом с guards описывает паттерн UseGuards + декоратор + Phase 2
миграцию в CASL (Guard остаётся, меняется источник policy).

Тесты: 40/40 зелёных (7 marketplace-mapper + 8 role-guard + 6 membership
+ 5 core-mapper + 4 onboarding + 3 wallet + 4 init + 3 register).
tsc 0 ошибок.

* feat(cooptypes,marketplace): Story 1.7 — template ЦПП «Стол заказов» в registry

cooptypes registry:
- 1100.MarketplaceOfferTemplate — шаблон оферты ЦПП marketplace
  (по аналогии с 999.BlagorostOfferTemplate). Содержит placeholder
  content (рыбу) с явным caveat-блоком; финальный юридический текст
  готовится отдельно и заменит content без изменения registry_id.
- 1101.MarketplaceOffer — instance, renderуется для пайщика при подписании
  (Story 1.4 L3 или Story 1.11 L2; аналог 1000.BlagorostOffer).
- Зарегистрированы в registry/index.ts.

marketplace controller:
- MARKETPLACE_OFFER_TEMPLATE_REGISTRY_ID импортируется из
  Cooperative.Registry.MarketplaceOfferTemplate.registry_id (=1100).
- Добавлена MARKETPLACE_OFFER_INSTANCE_REGISTRY_ID (=1101) для будущей
  mutation подписания.

Auto-activates:
- Story 1.2: registerMarketplaceInAgreementRegistry больше не пропускает
  регистрацию; port.registerAgreement вызывается при restart расширения.
- Story 1.4: marketplaceOnboardingState возвращает requires_gate=true для
  пайщика без подписи (а не not_configured).

Тесты обновлены под id=1100:
- marketplace-plugin-register: AC successful registration; rollback кейс
  через jest.doMock.
- marketplace-onboarding-service: placeholder=0 через jest.doMock; добавлен
  кейс «Story 1.7 размещена → requires_gate=true».

42/42 marketplace unit зелёных, tsc 0 ошибок.

Юридический финальный текст оферты — отдельный todo (todo-tspp-templates.md).

* feat(marketplace): Story 1.8 — централизованная access-matrix (CASL-compat)

marketplaceAccessMatrix: Record<MarketplaceRole, Record<Resource, Action[]>>
для всех 6 ролей. Покрывает Эпики 1-4 (Order, Offer, KU, Receiving,
Issuance, Warehouse, Vitrine, Whitelist, Agenda, Writeoff, Decision,
Extension). Эпики 5-10 расширят без правок resolver-ов.

canAccess(roles[], resource, action) — OR по ролям, exact-match action.
roleHasAnyAction(roles, resource, baseAction) — UI-фильтр меню.

Новый декоратор @RequireMarketplaceAccess(resource, action). Старый
@RequireMarketplaceRole сохранён для обратной совместимости и для
требований, не привязанных к resource.

MarketplaceRoleGuard расширен: читает оба декоратора через Reflector;
если оба заданы — AND-семантика. Лог-формат отказа по access:
`requested_access=Resource:action`.

README рядом с guards: секция access-matrix, нотация actions (:own/:all/
:to-self/:own-KU/:first), обновлённый Phase 2 plan (canAccess →
CASL ability.can без изменения вызывающего кода).

Тесты: 60/60 marketplace зелёных (10 suites). tsc 0 ошибок.

CI-checker orphan resolvers/matrix entries не реализован (требует
AST-парсер) — фиксируется как Phase 1.5 техдолг.

* feat(marketplace): Story 1.9 — L1 решение совета о принятии положения ЦПП

extension.config расширяется coopAcceptance: {accepted, document_registry_id,
accepted_at, accepted_by_board_decision_id}. Bootstrap-миграция v2 добавляет
поле к существующим конфигам идемпотентно.

MarketplaceCoopAcceptanceService:
- getStatus() → {status: 'active'|'not_accepted', ...} из extension.config.
- accept({document_registry_id, accepted_by_board_decision_id, accepted_at?})
  → пишет coopAcceptance в config, возвращает 'active' state.

GraphQL:
- Query marketplaceCppStatus — открыт всем активным пайщикам (фронт решает,
  показывать ли баннер «Не подключено»).
- Mutation marketplaceAcceptCpp — admin-only через
  @RequireMarketplaceAccess('Extension', 'configure').

MVP-stub: реальная интеграция с повесткой совета (FR40, Эпик 8) ещё не
готова — председатель передаёт board_decision_id как string. Phase 2 после
Эпика 8: mutation валидирует существование решения / переедет в side-effect
core-controlled повестки.

Тесты: 66/66 marketplace unit зелёных. tsc 0 ошибок.

* feat(marketplace): Story 1.10 — статус оферты в core AgreementRegistry

Query marketplaceRegistrationOfferStatus → читает через AGREEMENT_QUERY_PORT.
getAgreementById(MARKETPLACE_OFFER_AGREEMENT_ID), возвращает
{registered, agreement_id, registry_id, agreement_type, title,
applicable_account_types}. Используется admin UI для статуса «Оферта
зарегистрирована в registration-flow».

MarketplaceCoopAcceptanceService.accept (Story 1.9) теперь имеет
side-effect: re-register оферту в core AgreementRegistry через
registerMarketplaceInAgreementRegistry (идемпотентно). Покрывает кейс,
когда Story 1.7 поставила template позже restart-а расширения и
MarketplacePlugin.initialize пропустил регистрацию (placeholder=0 → ok).

AGREEMENT_REGISTRATION_PORT инжектируется @Optional — старые unit-тесты
Story 1.9 (constructor без порта) проходят.

AC PRD `coop_registration_offers_registry` = платформенный AgreementRegistry
с extension_name ключом. Per-coop scoping не нужен для одно-coop
controller'а; multi-coop приложит CooperativeConfigService без правок
marketplace-кода (тех-долг PRD, принят).

Тесты: 68/68 marketplace зелёных (новый 1.10 resolver + предыдущие).
tsc 0 ошибок.

* feat(marketplace): Story 1.11 — L2 онбординг через core registration-flow

Backend marketplace для L2 не вносит нового кода — flow держится на
уже реализованных компонентах + core registration-flow:

1. Story 1.2/1.7/1.10: marketplace_offer зарегистрирована в
   AgreementRegistry, видна core SignUp.
2. Core registration-flow рендерит instance 1101.MarketplaceOffer
   через documentFactory.
3. Подпись через wallet::signagree / soviet::sndagreement('marketplace')
   — работа core.
4. AgreementSyncService наполняет AgreementRepository.
5. Story 1.4 MarketplaceOnboardingService читает PG-кеш и НЕ показывает
   L3 gate если подпись есть.

Добавлено:
- onboarding/README.md — описание полного L1→L2→L3 цикла, какие компоненты
  предоставляет marketplace и что требует от core; раздел фоллоуапов.
- marketplace-l2-onboarding-flow.test.ts — 3 scenario-кейса (подпись через
  L2 → нет gate; нет подписи → gate; ownership by username).

Эпик 1 закрыт: 11/11 stories реализованы, 71 marketplace unit-тест
покрывает критические инварианты онбординга.

Открытые фоллоуапы (вынесены как отдельный backlog):
- L3 mutation marketplaceSignOnboardingOffer (write-pool + sndagreement).
- Source-маркер registration_flow vs extension_gate в DTO.
- Mutation marketplaceAcceptCpp валидация реальной повестки совета (Эпик 8).

* [58bfe503][@ant] fix: переписать marketplaceMemberWallet на массив кошельков по стандарту marketplace — устранить подгонку под старую нотацию membership_contribution, показывать share/member/mkt.member как есть из ledger2::userwallets

Review @dacom-dark-sun на PR #380 (line 13/53 в DTO+Resolver) указал: текущая
реализация Story 1.5 сворачивает w.wal.share + w.wal.member в свёрнутый
ProgramWallet и подставляет membership_contribution — это «полная хуйня,
старая нотация, кошельков система обновилась». По стандарту marketplace
(components/contracts/cpp/marketplace/p.mkt.supply.standard.yaml, секция wallets)
у пайщика 3 USER_SHARED-кошелька: w.wal.share (ЦК паевые), w.wal.member
(универсальный членский — транзитный share→mkt), w.mkt.member (программный
членский ЦПП Стол Заказов). Каждый со своим available+blocked.

Перепиcан DTO MarketplaceMemberWalletDTO: вместо плоских полей возвращается
массив MarketplaceWalletEntryDTO[{name, human_name, program_id, program_label,
kind, available, blocked}]. Resolver зовёт core UserWalletRepository.findByUsername
напрямую (PG-кеш ledger2::userwallets, ADR-011), фильтрует 3 релевантных wallet_name,
для каждого ставит 0/0 если L3-запись ещё не создана (w.mkt.member появляется
после первого orderoffer/createorder). WalletService.getProgramWallet больше не
зовётся — он сворачивает share+member и неприменим для UX, требующего видеть
каждый кошелёк отдельно. WalletModule убран из marketplace-application.module
(USER_WALLET_REPOSITORY доступен глобально из @Global TypeormModule).
Unit-тесты переписаны: 4 кейса (массив с заполнениями, нулевой пайщик, human_name
из cooptypes, w.mkt.payout COOPERATIVE не попадает в выдачу) — 4/4 зелёных.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [58bfe503][@ant] fix: переименовать program_label → label с UX-нотацией «<тип взноса> | <программа>» — review @dacom-dark-sun PR #380 line 34

Прежние значения «ЦК» / «Marketplace» — это технические идентификаторы программ
из LEDGER2_USER_SHARED_PROGRAM_MAPPING, а не UX-метки. Review указал нужный
формат: «Паевой | Цифровой Кошелёк», «Членский | Цифровой Кошелёк», «Членский | Стол Заказов».

Поле `program_label` переименовано в `label`. Семантика: короткая
человеческая метка кошелька для panel/list. Длинное юридическое название
по-прежнему доступно в `human_name` (из cooptypes LEDGER2_WALLET_REGISTRY).
program_id (1/2) остаётся машинно-читаемым полем для группировки.

Тест переписан: 4/4 зелёных (label проверяется для всех 3 кошельков).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [598-5][@ant] feat: backend marketplace_ku_details + Yandex Geocoder — Stories 2.1+2.2

Эпик 2 (Сеть кооперативных участков), бэкенд-часть:

— Story 2.1: marketplace_ku_details (1:1 расширение core coop_ku) — entity,
  domain, mapper, repository adapter; GraphQL мутации
  marketplaceDetailKU / marketplaceSetKUStatus / marketplaceRetryKUGeocode +
  query marketplaceListKUDetails. ACL — @AuthRoles(['chairman']) для
  mutations, ['chairman','member','user'] для list (Story 1.6/1.8 access-matrix
  не merged в marketplace2 — chairman ≈ marketplace-role admin).

— Story 2.2: GeocoderPort + YandexGeocoderAdapter с локальным rate-limit
  (sliding window) и таймаутом запроса; конфиг через
  YANDEX_GEOCODER_API_KEY/BASE_URL/RATE_LIMIT_RPS/TIMEOUT_MS env.
  Post-effect hook detailKU при смене addressFull сбрасывает координаты
  в PENDING и асинхронно перезаписывает на OK или FAILED + errorMessage,
  не прерывая основную мутацию.

Тесты: 18 unit-кейсов в tests/unit/marketplace (mapper round-trip,
service happy/edge paths, geocoder happy/empty/HTTP500/exception/no-key/
rate-limit). pnpm typecheck + targeted jest проходят.

* [598-5][@ant] feat: KUMapWithList + админ-страница ПВЗ — Story 2.3

Эпик 2, фронтенд:

— `entities/MarketplaceKUDetails` — FSD entity со store/api/types.
  API — raw GraphQL (fetch) поверх backend-резолверов Story 2.1/2.2.
  Типизация — ручная, до регенерации Zeus в `@coopenomics/sdk` (техдолг).
— `widgets/KUMapWithList` — компонент карта Yandex + список ПВЗ с
  синхронным выбором (клик в списке центрирует карту и открывает balloon
  пина; клик на пин подсвечивает карточку). Responsive: на мобильных
  layout вертикальный, карта сверху. Динамическая загрузка Yandex Maps
  SDK через `loadYandexMaps`; деградированный UI если ключа нет.
  Aria-label на регионе/карточках, q-skeleton на loading.
— `features/MarketplaceDetailKU` — диалог формы детализации ПВЗ
  (адрес, контакты, режим работы по дням). Используется на admin-странице
  для create/edit.
— `pages/Marketplace/PvzList` — admin/orderer/offerer страница списка ПВЗ.
  Для admin'а — кнопки edit/деактивировать/активировать/повторить
  геокодинг (Story 2.1 acceptance); slot `cardAction` позволяет
  заказчику/поставщику расширить под «Выбрать как место получения» /
  «Везти сюда» (передаётся при reuse).
— Регистрация роута `/:coopname/market/pvz` в `extensions/market/install.ts`.

Limitations (вынесены в PR-описание):
  • Зависит от регенерации Zeus типов в SDK — пока через raw GraphQL.
  • Не интегрирован в orderer/offerer create-order/pre-shipment flow —
    integration на стороне Эпиков 4-5.
  • WCAG 2.1 AA полный audit — Phase 2 post-MVP (Эпик 10 Story 10.2 notes).
  • Yandex Maps JS API требует `YANDEX_MAPS_API_KEY` в env (window.__APP_CONFIG__).

* [598-5][@ant] refactor: GeocoderPort провайдер-агностичен + чистка @description ревью PR #381

— env YANDEX_GEOCODER_* → GEOCODER_*; добавлен GEOCODER_PROVIDER (yandex|noop)
— infra adapters: NoopGeocoderAdapter + geocoderPortFactory; YandexGeocoderAdapter
  читает config.geocoder.*; module использует useFactory
— resolver / DTO: убраны ссылки на «Story X.Y / Эпик 2» из GraphQL @description
  (остаются в JSDoc-комментариях кода)
— jest: maxWorkers=1 в jest.config.js + явный --runInBand в package.json scripts
  (вешает dev-стек docker при параллельном пуле)
— schema.gql + controller/zeus регенерированы

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [598-5][@ant] feat: SDK marketplace KU details — селектор, 3 мутации, 1 query ревью PR #381

— Selectors.marketplaceKUDetailsSelector + workingHoursSelector/workingHoursDaySelector
— Mutations.Marketplace.DetailKU/SetKUStatus/RetryKUGeocode
— Queries.Marketplace.ListKUDetails (новый модуль queries/marketplace)
— zeus regenerated (controller schema → SDK)
— убирает техдолг raw-graphql во фронте

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [598-5][@ant] refactor: ПВЗ в отдельный workspace + переход с raw-graphql на Zeus ревью PR #381

— Стол ПВЗ — отдельный workspace 'market-pvz', defaultRoute marketplace-pvz,
  доступ roles:['chairman'] (заказчик/поставщик его не видят и не перегружены)
— entities/MarketplaceKUDetails/api: client.Query/Mutation из @coopenomics/sdk
  (Mutations.Marketplace.DetailKU/SetKUStatus/RetryKUGeocode +
  Queries.Marketplace.ListKUDetails); raw-graphql.ts удалён

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [598-5][@ant] refactor(sdk): kuDetailsSelector — _validate для всех селекторов как в других модулях

— rawWorkingHoursBreakSelector / Day / Sselector + rawMarketplaceKUDetailsSelector
— const _validate: MakeAllFieldsRequired<ValueTypes['Type']> = rawXxxSelector
  по аналогии с capital/voteSelector.ts: typecheck падёт, если в schema
  появится новое поле, не покрытое селектором.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix(ledger2): handle WalletOp::REVOKE in walletop dispatcher (Story 11.4)

REVOKE (op_code=6) was added to enum and OPERATION_REGISTRY for o.mkt.consum
(2026-05-11), but walletop.cpp rejected op_code > 4. Added explicit case:
списывает amount с wallet_from.blocked в пустоту, без зачисления; L3-зеркало
для USER_SHARED. NONE добавлен как unreachable case для исчерпывающего switch.

Прерывает блокер для Story 11.1 (canonical marketplace actions).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(marketplace): canonical scaffolding — entity tables + 18 action skeletons (Story 11.1)

Удалены donor-actions старой клиринговой модели (AR30):
- 50 .cpp файлов в components/contracts/cpp/marketplace/src/{deliver_on_offer,
  deliver_on_order,shipment,dispute_on_offer,корень};
- legacy marketplace.{cpp,hpp} с ~30 actions (orderoffer/accept/supply/dispute/
  shipment/etc) на legacy Wallet:: API мимо ledger2;
- три donor entity-таблицы: lib/domain/table_marketplace_{requests,segments,
  shipments}.hpp;
- lib/core/marketplace/marketplace.hpp donor-helpers (get_request_by_hash,
  get_shipment_by_hash, namespace DocumentNames клирингового состава).

Создана canonical инфраструктура трёх процессов p.mkt.{supply,return,wroff}:
- lib/domain/table_marketplace_orders.hpp — Order entity с полным
  жизненным циклом p.mkt.supply (active → cancelled / accepted →
  ship_ready → supply_prepared → accepted_to_coop → ready_to_receive →
  received), 8 secondary индексов, поля для трассировки signsupp/signchair/
  signiss1/signiss2 АПП-документов и warranty_until для submretrn гарда.
- lib/domain/table_marketplace_return_requests.hpp — ReturnRequest entity
  для p.mkt.return (pending_review → approved_for_visit → return_accepted /
  rejected_at_ku / rejected_remote), original_consume_op_id для
  compensating-forward трассировки (d6 A4), photos vector<checksum256>.
- lib/domain/table_marketplace_writeoff_proposals.hpp — WriteoffProposal
  entity для p.mkt.wroff с vector<wroff_item> per-КУ позиций.
- lib/core/marketplace/marketplace.hpp — canonical helpers
  get_*_by_hash[_or_fail] / update_* + cross-contract get_user_wallet_balance
  для guard'а Locked Decision L6 в createorder.

Контракт marketplace.{cpp,hpp} переписан целиком под 18 canonical actions
(p.mkt.supply 10 + p.mkt.return 5 + p.mkt.wroff 3) согласно YAML-стандартам
рядом с .hpp. Каждый action — отдельный .cpp файл в marketplace/src/
по образцу Capital'а; на этом коммите все 18 — skeleton с
eosio::check(false, "TODO Story 11.1 Шаг N: <action> ещё не реализован")
и подробным @brief docstring'ом со ссылкой на Story / guards / ledger2-
операции из YAML. Реальная реализация actions — последующими коммитами
(Шаги 4-6 спецификации Story 11.1).

Batch (consolidated request) намеренно НЕ выведен в on-chain entity —
Locked Decision L10: backend-only сущность, on-chain действия per-Order;
batch_hash на Order'е — opaque ссылка для трассировки.

Foundation Story 11.4 на месте (предыдущий коммит): WalletOp::REVOKE
обработан walletop dispatcher'ом; OPERATION_REGISTRY содержит все 13
marketplace-операций; счета 10/91 в LEDGER2_ACCOUNT_MAP.

Spec артефакт: blago/production/1-prilozhenie-stol-zakazov/components/
3-minimalnyy-produkt/_bmad-output/implementation-artifacts/
spec-11-1-ledger2-marketplace-ops.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(marketplace): implement createorder action (Story 4.1, p.mkt.supply)

Первая реализация canonical action — proof-of-pattern для остальных 17.
Серия трёх ledger2-операций (атомарно в одной транзакции Antelope):
- o.wal.conv (conditional, недостача из w.wal.share);
- o.mkt.assign (conditional, недостача из w.wal.member);
- o.mkt.block (всегда, total_cost на w.mkt.member).

L6 guard через cross-contract read трёх кошельков (Marketplace::
get_user_wallet_balance) — фейлится с понятной ошибкой если суммарных
средств не хватает. process_hash для всех трёх ops = order_hash.

Создан Order entity со status=ACTIVE; cycle_type валидируется по
enum (timebased/volumebased/opensubscr/individual). actual_quantity
и fact_cost ставятся равными quantity/total_cost — обновятся в
signiss2 при выдаче.

Plus: rename WroffStatus::DRAFT → PROPOSED (DRAFT конфликтует с macro
из lib/consts.hpp; on-chain строка осталась "draft"_n как в YAML).

Build: pnpm build:contract:test marketplace — green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(marketplace): implement 17 canonical actions (Story 11.1, p.mkt.{supply,return,wroff})

Реализованы все оставшиеся 17 actions поверх createorder (proof-of-pattern):

p.mkt.supply (9 actions, дополнительно к уже реализованному createorder):
- cancelorder — отмена до акцепта; o.mkt.unblk; ACTIVE → CANCELLED.
- expirecycle — закрытие цикла; threshold_reached==false → o.mkt.unblk per
  order + ACTIVE → CANCELLED; threshold_reached==true — no-op (backend
  формирует batch).
- acceptbatch — поставщик акцепт; ACTIVE → ACCEPTED, batch_hash зафиксирован;
  без ledger2 (L10).
- declinebatch — отказ до акцепта; o.mkt.unblk per order + ACTIVE → CANCELLED.
- prepship — сборка партии; ACCEPTED → SHIP_READY; shipping_method
  валидируется (varianta | variantb).
- signsupp — первая подпись АПП приёмки поставщиком; verify_document_or_fail
  ({offerer}); SHIP_READY → SUPPLY_PREPARED + acceptance_act_signsupp.
- signchair — закрывающая подпись АПП приёмки председателем; per-Order
  Ledger2::apply(o.mkt.purch) + Ledger2::apply(o.mkt.payout) атомарно;
  SUPPLY_PREPARED → ACCEPTED_TO_COOP + acceptance_act_signchair.
- signiss1 — открытие выдачи председателем; verify_document_or_fail({chairman});
  ACCEPTED_TO_COOP → READY_TO_RECEIVE + issue_act_signiss1.
- signiss2 — финальная подпись заказчика с поддержкой actual_quantity ≠
  ordered: при actual<ordered — o.mkt.unblk на разницу; при actual>ordered —
  L6 guard на доступность diff в трёх кошельках, conditional o.wal.conv +
  o.mkt.assign + o.mkt.block на разницу; всегда — o.mkt.consum + o.mkt.consum2
  на fact_cost; READY_TO_RECEIVE → RECEIVED + warranty_until.

p.mkt.return (5 actions):
- submretrn — заявление на возврат; гард warranty_until > now; создание
  return_request в PENDING_REVIEW; order.return_request_id для двусторонней
  связи. photos обязательны.
- aprretrem — удалённое одобрение визита; PENDING_REVIEW → APPROVED_FOR_VISIT.
- rejretrem — удалённый отказ; PENDING_REVIEW → REJECTED_REMOTE + reason_remote.
- accretrn — приём возврата на очном осмотре; Ledger2::apply(o.mkt.return) +
  Ledger2::apply(o.mkt.return2) (compensating forward, не revert);
  APPROVED_FOR_VISIT → RETURN_ACCEPTED.
- rejretrn — отказ на очном осмотре; APPROVED_FOR_VISIT → REJECTED_AT_KU +
  reason_visit.

p.mkt.wroff (3 actions):
- propwroff — формирование проекта списания; total_amount = Σ items.amount;
  PROPOSED.
- execwroff — исполнение per-item: Ledger2::apply(o.mkt.wroff) +
  Ledger2::apply(o.mkt.wroff2) каждой позиции; PROPOSED → EXECUTED;
  process_hash единый = proposal.hash, item.ku_chairman как username для
  per-КУ аналитики.
- declwroff — отклонение советом; PROPOSED → REJECTED + reject_reason.

Все compose'ятся через единый Ledger2::apply orchestrator, не нарушая
foundation Story 11.4. Composite-операции (consum+consum2, return+return2,
wroff+wroff2) в одной транзакции Antelope обеспечивают атомарность L1
проводок через транзит счёта 91. Cross-contract balance read через
Marketplace::get_user_wallet_balance(_ledger2 scope) — для L6-гарда
createorder и signiss2 (correction>).

Build: pnpm build:contract:test marketplace — green.
Foundation: walletop REVOKE + 13 marketplace operations + WalletOp NONE/REVOKE
+ счета 10/91 + кошельки w.mkt.{member,payout} — на месте (предыдущие коммиты).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(marketplace): структура по процессам + чистка избыточных дат on-chain

Структура:
- src/p.mkt.supply/  — 10 actions процесса прямой поставки-приобретения
                       (createorder, cancelorder, expirecycle, acceptbatch,
                        declinebatch, prepship, signsupp, signchair,
                        signiss1, signiss2);
- src/p.mkt.return/  — 5 actions процесса гарантийного возврата
                       (submretrn, aprretrem, rejretrem, accretrn, rejretrn);
- src/p.mkt.wroff/   — 3 actions процесса утилизации скоропорта (списания)
                       (propwroff, execwroff, declwroff).
Имена подпапок 1:1 совпадают с process_type из YAML-стандартов рядом —
прозрачная навигация от .cpp к стандарту.

Удалены избыточные date-поля из 3 entity-таблиц (orders / return_requests /
writeoff_proposals): created_at, accepted_at, shipped_at, received_to_coop_at,
ready_at, received_at, cancelled_at, reviewed_at, resolved_at, proposed_at,
decided_at. Контракт ничего не проверяет по этим датам — все timestamp'ы
переходов состояний восстанавливаются на бэкенде из blockchain_actions[at]
по соответствующим actions через ParserClient. Сокращает RAM-стоимость
state на каждый Order / ReturnRequest / WriteoffProposal.

Сохранено on-chain:
- order.warranty_until — нужно для guard'а submretrn (`now() < warranty_until`)
  без cross-action lookup; вычисляется в signiss2 как
  signiss2.timestamp + warranty_period_secs.
- batch_hash, status, документы (acceptance_act_signsupp/signchair,
  issue_act_signiss1/signiss2, statement, decision_remote/visit,
  protocol, photos), reason fields, fact_cost / actual_quantity, items
  для wroff_proposal, links (return_request_id, original_order_id,
  original_consume_op_id) — это смысловая on-chain информация, не
  выводимая из истории.

Удалены secondary-индексы bycreated и byproposed (бэкенд индексирует
по сохранённым в Postgres timestamp'ам блоков, on-chain pagination
по чрон. порядку = по primary key id).

Build: pnpm build:contract:test marketplace — green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(marketplace): ревью PR #375 — braname/branch, per-Order, memos, без prepship

- ku_chairman заменён на braname (приёмка/выдача) в Order; в return_request
  привязка к КУ удалена — каждое решение принимает braname параметром, авторизация
  через Branch::is_user_authorized (председатель/trustee/доверенные)
- Все per-batch actions теперь per-Order (acceptorder/declineorder/expireorder,
  signsupp/signchair); векторов order_hashes нет — backend проходит циклом
- prepship удалён как избыточный шаг (после acceptorder сразу signsupp);
  OrderStatus::SHIP_READY убран
- execwroff per-item: один item за вызов с item_index; финализация PROPOSED → EXECUTED
  автоматически после исполнения последней позиции; declwroff фейлится если
  хотя бы одна позиция уже исполнена
- propwroff валидирует существование braname для каждого item через get_branch_or_fail
- Memos вынесены в lib/core/marketplace/memo.hpp по аналогии с Capital::Memo;
  все Ledger2::apply используют человекочитаемые memo с подстановкой id
- Все eosio::check сообщения переписаны на пользовательский русский текст
  без префиксов action/Locked Decision (UI показывает их напрямую)
- walletop REVOKE дополнен подробным docstring: разница с BURN (blocked vs
  available, обязательная пара Dr/Cr, отсутствие альтернативы через UNBLOCK+BURN)

Build: marketplace.wasm + ledger2.wasm — green под coopos 5.2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(marketplace): order.current_warehouse_braname (текущая точка хранения)

Поле введено сейчас, чтобы избежать binary_extension при включении полноценного
процесса перемещения имущества между КУ после релиза.

Заполнение:
- signchair (приёмка кооперативом): current_warehouse_braname = accept_braname
- signiss1 (готово к выдаче): current_warehouse_braname = delivery_braname

Промежуточные перемещения между приёмным и выдающим КУ контрактом не
подписываются (бездокументарно) — точка хранения переходит «скачком» в момент
готовности к выдаче. Подписание ТТН по внутренним передачам — отдельная
история после релиза, текущее поле даёт ей точку расширения без миграции.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [598-6][@ant] feat: backend whitelist + дефолтная витрина — Story 3.1

Story 3.1 «Управление whitelist поставщиков + дефолтная витрина»:

— Domain entities `MarketplaceVitrineDomainEntity`,
  `MarketplaceWhitelistEntryDomainEntity` (role: 'auto-coop' | 'manual')
  и domain-репозитории `MarketplaceVitrineDomainRepository`,
  `MarketplaceWhitelistDomainRepository` (DIP — реализация в infra).
— TypeORM-entities `marketplace_vitrine` (PK=id, MVP всегда 'default'),
  `marketplace_whitelist` (UUID, UNIQUE(coop, member)). Подключение
  `marketplace` уже работает с `synchronize:true` — DDL автоматический.
— Mappers + adapters; идемпотентные `ensureDefault` / `add(role)`.
— Bootstrap-миграция v3 `marketplaceBootstrapV3Migration`: в afterMigrate
  создаёт `{id:'default', display_name:'Стол заказов'}` + auto-coop
  whitelist entry (FR5 — перепоставка остатков самим коопом).
— `MarketplaceWhitelistService.isOfferer` — источник `context.isOfferer`
  для `mapCoreRolesToMarketplaceRoles`: «открытая витрина» (только
  auto-coop) → true для всех User; «по whitelist» → true только для
  manual-записей. TTL-кеш 60s, инвалидируется при add/remove.
— `MarketplaceVitrineService.getDefault/list` — read-only API
  конфигурации витрины (конструктор кастомных витрин Out-of-MVP).
— GraphQL endpoints:
  `marketplaceListWhitelist` / `marketplaceAddToWhitelist` /
  `marketplaceRemoveFromWhitelist` под `@RequireMarketplaceAccess(
  'Whitelist','manage')` (admin); `marketplaceDefaultVitrine` под
  `'Vitrine','read'` (всем marketplace-ролям). auto-coop неудаляем.
— Access-matrix: добавлен `Vitrine:['read']` для offerer/operator,
  расширены `admin: Offer:['moderate','read']`, `Vitrine:['manage','read']`.
— `MarketplaceMembershipGuard` теперь async, дёргает
  `whitelistService.isOfferer(coopname, username)` для генерации
  marketplace-роли `offerer` на каждый GraphQL-запрос (через cache).
— Регистрация миграции v3 в `extension-domain.module.ts`, новых
  entities/adapters/mappers/repositories в `marketplace-infrastructure
  .module.ts`, сервисов/резолверов в `marketplace-application.module.ts`.

Тесты: 21 unit-кейс (`tests/unit/marketplace/`):
— marketplace-whitelist-service.test.ts (11): list, add, remove
  manual/auto-coop/missing, isOfferer семантика 3-х режимов + TTL-кеш
  + инвалидация на add/remove;
— marketplace-vitrine-service.test.ts (3): getDefault / null / list;
— marketplace-membership-guard.test.ts (7, обновлён под async + DI):
  open vitrine → +offerer, whitelist → no offerer, member/chairman
  роли, status/auth/server-secret bypass.

pnpm tsc проходит чисто на изменённых файлах (предсуществующие ошибки
в `file-storage/` не относятся к Story 3.1). targeted jest зелёный.

Зависимости: Эпик 1 (whitelist использует marketplace-roles.mapper,
access-matrix, MembershipGuard). Не блокирует и не блокируется

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-6][@ant] feat: backend Offer CRUD + 10 baseline-категорий — Story 3.2

Story 3.2 «Поставщик публикует и управляет жизненным циклом Offer'а»:

— Domain: `MarketplaceOfferDomainEntity` (полный набор полей под
  Stories 3.2/3.3/3.4 — quantity_available/blocked/consumed,
  cycle_type/days/target_volume/max_wait_days/min_threshold,
  status PENDING_MODERATION→ACTIVE→REJECTED/WITHDRAWN,
  approved_by/at, rejected_by/at, reject_reason);
  `MarketplaceCategoryDomainEntity` (справочник 10 baseline);
  типы `MarketplaceOfferStatus`/`CycleType`/`UnitOfMeasure`.
— TypeORM-entities `marketplace_offer` (UUID, 3 индекса под Stories 3.2/3.5)
  и `marketplace_category` (PK=integer, seed-таблица).
— Repositories: `MarketplaceOfferDomainRepository`
  (findById/list/countByCategory/countRecentCreatedBy/create/applyUpdate)
  + `MarketplaceCategoryDomainRepository` (listBaseline/findById/upsertBaseline).
— Adapters TypeORM-based с QueryBuilder (фильтры по supplier/status/
  category/available_only, сортировки по created_at/price);
  countByCategory считает только ACTIVE + (unlimited OR available>0).
— `MarketplaceOfferService` инкапсулирует AC Story 3.2:
  • create: PENDING_MODERATION + валидация (product_name 1..200,
    description ≤2000, baseline category 1..10, numeric price 0-4 знака,
    cycle/unit enums, quantity invariants);
  • rate-limit: 10 Offer'ов/час на supplier (`countRecentCreatedBy`);
  • update: ownership + reset в PENDING_MODERATION; REJECTED/WITHDRAWN → 403;
  • withdraw: ownership + status WITHDRAWN; stub `hasActiveOrders`=false
    (точка интеграции с Story 4.x order-репозиторием после merge #375);
  • unlimited_flag=true атомарно обнуляет quantity_available.
— `MarketplaceCategoryService.listBaseline` — read для UI (форма
  создания Offer'а + фильтр-чипы Story 3.5).
— GraphQL resolver: `marketplaceCreateOffer` / `marketplaceUpdateOffer` /
  `marketplaceWithdrawOffer` / `marketplaceListMyOffers` —
  `@RequireMarketplaceAccess('Offer','create:own'|'update:own'|'delete:own')`;
  `marketplaceListCategories` — `'Offer','read'`.
— DTO: MarketplaceOfferDTO + MarketplaceOfferPageDTO + MarketplaceCategoryDTO,
  Create/Update/Withdraw/ListMy Input DTOs (class-validator: Min/Max/Matches/
  IsIn для enum-полей).
— Bootstrap-миграция v4 (`marketplace-bootstrap-v4`): идемпотентный upsert
  10 категорий через `categoryRepo.upsertBaseline()`. DDL — synchronize:true.
— Регистрация: новые entities/adapters/mappers/repositories в
  infrastructure.module, services/resolver в application.module,
  миграция v4 в extension-domain.module.

Тесты: 24 unit-кейса в `marketplace-offer-service.test.ts`:
— create (11): happy path + rate-limit + категория вне baseline +
  отсутствующая в БД + product_name пустой/>200 + quantity invariants +
  unlimited_flag → 0 + неверные cycle/unit/price;
— update (6): reset в PENDING_MODERATION + ownership + WITHDRAWN/REJECTED 403
  + 404 + unlimited→0 + invalid category;
— withdraw (5): happy + ownership + уже WITHDRAWN + 404 + sentinel под
  active-orders;
— listMine + getById (2).

`pnpm tsc --noEmit` чисто; jest зелёный.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-6][@ant] feat: backend модерация Offer'ов админом — Story 3.3

Story 3.3 «Модерация Offer'ов админом (approve/reject + комментарий)»:

— Domain: `MarketplaceModerationLogDomainEntity` (offer_id, action,
  by_account, reason, created_at; append-only) + repository интерфейс
  `MarketplaceModerationLogDomainRepository.append/listByOffer`.
— TypeORM entity `marketplace_moderation_log` (UUID PK, индексы по
  offer_id+created_at и by_account); adapter + mapper.
— `MarketplaceModerationService`:
  • listPending — `repo.list({status:PENDING_MODERATION}, paging)`;
  • approve(offer_id, admin): PENDING → ACTIVE, approved_by/approved_at,
    очистка rejected_*; append log с action='approve';
    EventEmitter2.emit `marketplace.offer.approved` (порядок save→emit
    соблюдён, INV-12 controller/CLAUDE.md);
  • reject(offer_id, admin, reason): обязательное и trim'нутое reason
    1..1000 char; PENDING → REJECTED + rejected_by/at + reject_reason;
    log с action='reject'; emit `marketplace.offer.rejected`;
  • listLog — append-only история по offer'у;
  • Не-PENDING статус → 409 Conflict; missing → 404; reason invalid → 400.
— GraphQL resolver `marketplace-moderation.resolver.ts`:
  `marketplaceListPendingOffers` / `marketplaceApproveOffer` /
  `marketplaceRejectOffer` / `marketplaceListModerationLog` — все под
  `@RequireMarketplaceAccess('Offer','moderate')` (только admin).
— DTO: Approve/Reject/ListPending Input + ModerationLogEntry Output.
— Подключение в infrastructure + application модули; EventEmitter2 уже
  в core-ApplicationModule (через `@nestjs/event-emitter`).

Тесты: 10 unit-кейсов в `marketplace-moderation-service.test.ts`:
— approve (3): happy + 409 на не-PENDING + 404;
— reject (5): happy + empty reason 400 + >1000 chars 400 + 409 на
  уже-rejected + trim применяется к reason;
— listPending+listLog (2): корректная делегация с filter/paging.

`pnpm tsc --noEmit` чисто; jest зелёный.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-6][@ant] feat: backend counters available/blocked/consumed — Story 3.4

Story 3.4 «Backend ведёт available/blocked count Offer'ов»:

— Domain контракт `MarketplaceOfferDomainRepository` расширен 3-мя
  атомарными дельтами: `applyBlockDelta` / `applyUnblockDelta` /
  `applyConsumeDelta`. Возвращают `OfferCountersDeltaResult` —
  discriminated `{ok, reason?, offer?}` с reason'ами `insufficient_*`,
  `offer_not_active`, `offer_not_found`.
— Adapter реализован одним SQL UPDATE с WHERE-CAS-условием и RETURNING:
  • block: `quantity_blocked+=K`, `available -= K` (если не unlimited);
    WHERE status=ACTIVE AND (unlimited OR available>=K);
  • unblock: `quantity_blocked-=K`, `available += K` (если не unlimited);
    WHERE blocked>=K;
  • consume: `quantity_blocked-=K`, `quantity_consumed+=K`;
    WHERE blocked>=K.
  0 affected rows → fallback findOne + классификация причины. Атомарность
  обеспечивается одним SQL-statement'ом, гонки между параллельными
  Order-блокировками не разрушают инвариант.
— `MarketplaceOfferCountersService` — точка интеграции с Эпиком 4
  (`o.mkt.block/unblock/consume` canonical из PR #375): order-side
  syncer вызывает `onOrderBlocked/Unblocked/Consumed/Adjusted` внутри
  `dispatch` после `save` Order'а до `emit pubsub` (INV-12, см.
  controller/CLAUDE.md). Валидация qty > 0 integer; перевод reason →
  NestJS exception (404/400). EventEmitter2 emit
  `marketplace.offer.counters.changed` после успешной операции — Story
  3.5 каталог и offerer-«Активность» подписываются (Phase 2 GraphQL Sub).
— `onOrderAdjusted(qty_diff)` (FR23 «факт меньше заказа») семантически
  делегирует в `onOrderUnblocked`.

Инвариант (для не-unlimited):
  `available + blocked + consumed = lifetime_published`
поддерживается дельтами (изменение суммы 0 на каждом методе).

Подключение в `marketplace-application.module.ts`. Существующие моки в
тестах 3.2 / 3.3 расширены под новый интерфейс репозитория.

Тесты: 11 unit-кейсов в `marketplace-offer-counters-service.test.ts`:
— happy paths (4: block / unblock / consume / adjusted-delegate);
— qty validation (1: 0 / negative / non-integer);
— классификация reason → exception (4: insufficient_available/blocked,
  not_active, not_found);
— emit skip on error (1);
— math invariant (1): block 10 → unblock 3 → consume 7, net 0.

Атомарность SQL CAS-update — интеграционный уровень
(testcontainers PG, after merge Эпика 4). Точка интеграции зафиксирована
интерфейсом.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-6][@ant] feat: каталог Offer'ов на orderer-столе — Story 3.5

Story 3.5 «Каталог Offer'ов с фильтром-чипами по 10 категориям»:

Backend:
— `marketplaceListCatalog(input?: ListCatalogInput)`: только ACTIVE +
  (unlimited OR available>0), фильтр `category_id`, paging limit/offset
  (default 24/0), sort `created_at_desc` (default) | `price_asc` |
  `price_desc`. Доступ `@RequireMarketplaceAccess('Offer','read')` —
  всем marketplace-ролям.
— `marketplaceCategoryOfferCounts`: счётчики активных Offer'ов per
  category для фильтр-чипов. Гарантированно возвращает все 10
  baseline-категорий (даже count=0) — UX-DR10 рисует полный набор чипов.
— DTO: `MarketplaceListCatalogInput` (class-validator: category_id
  1..10, sort IsIn, paging Min/Max), `MarketplaceCategoryOfferCount`.
— Резолвер `MarketplaceCatalogResolver` подключён в application-module.

Frontend (FSD slim — отдельный pages/Marketplace/MarketplaceCatalog):
— `types.ts` — ручная типизация MarketplaceOfferView /
  CategoryOfferCount / CatalogSort / CatalogFilter (техдолг: после
  cooptypes:gen-zeus переписать на `Queries.Marketplace.*` из SDK,
  паттерн из PR #381).
— `api/index.ts` — raw GraphQL через `sendPOST('/v1/graphql')`
  (`fetchCatalog` / `fetchCategories` / `fetchCategoryOfferCounts`),
  обработка `body.errors` как throw.
— `ui/CatalogOfferCard.vue` (UX-DR10 — карточка):
  product_name (≤2 строки ellipsis), supplier, цена-за-единицу с
  правильным unit-label (шт/кг/л/упак), quantity_label («Без
  ограничений» или «Доступно: N ед»), cycle-label per cycle_type
  с правильными формулировками AC, warranty-чип, кнопка «Заказать»
  (disabled при отсутствии остатка); aria-label, ellipsis-2-lines.
— `ui/MarketplaceCatalogPage.vue`:
  • горизонтальная полоса фильтр-чипов «Все» + 10 категорий с
    counter-badge per чип (`scroll-x` под mobile);
  • сортировка через q-select (3 опции);
  • responsive grid (`col-12 col-sm-6 col-md-4 col-lg-3`), pagination
    24/страницу через `q-infinite-scroll`;
  • EmptyState компонент при пустом результате (фильтр / каталог);
  • aria-label на регион/чипы/карточки;
  • Order-форма (Эпик 4 Story 4.1) — пока Notify-stub с указанием
    точки интеграции.

`install.ts`:
— `defaultRoute: 'marketplace-catalog'` (новая страница);
— route `/:coopname/market/catalog` → `MarketplaceCatalogPage`;
— legacy `marketplace-showcase` помечен `hidden:true` (donor-страница
  на старой Marketplace архитектуре, см. project_stol_zakazov_mvp
  решение 2026-05-12 «donor переписывается без переходников»;
  переписывается в Phase 2 / последующем PR Stories 3.2 frontend).

vue-tsc: на новых файлах ошибок нет (`MapIterator` итерация заменена
на `Array.from(...).reduce`). Известные baseline-ошибки `Cannot find
module 'src/...'` во всех `extensions/*/install.ts` (включая
`branch`, `capital`, `chairman` — pre-existing).

Тесты: backend-резолвер — thin forward в `offerRepo.list` /
`countByCategory`, оба метода покрыты на adapter-уровне (тесты
catalog filtering — интеграционный testcontainers PG); карточная
логика label-форматтеров — компонентный тест Phase 2.

Limitations:
— Order-форма stub (Эпик 4);
— Zeus типы в SDK — техдолг (как в PR #381 marketplace KU details);
— WCAG 2.1 AA полный audit — Phase 2 (Эпик 10 Story 10.2);
— Donor-страницы Marketplace/Showcase, CreateParentOffer,
  UserParentOffers, Moderation не переписаны под новые GraphQL
  мутации Stories 3.2/3.3 — это работа отдельного UI-PR (текущий
  backend mutations работают, donor-UI закрыт legacy путём).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-6][@ant] chore: BC-sync integration seam — onOrderRolledBack + OrderSync scaffolding

Закрытие разрыва «где живёт blockchain-synchronization для Эпика 3»,
явный seam в коде вместо разбросанных JSDoc-комментариев.

Контекст: ревью пользователя 2026-05-15 — Эпик 3 был доставлен как
backend-only, и Story 3.4 counters стояли как stub-callback без явной
sync-точки. Слабая фиксация интеграции (только JSDoc) — исправляется.

Source-of-truth паттерна (ARCH-документы blago):
— `13-platforma-tsifrovogo-kooperativa/components/14-versiya-3/
   requirements/c3-arch-sinkhronizatsiya-uzla-s-blokcheynom-v1.md`
   (ADR-001 .. ADR-012);
— `4f-arch-integratsiya-kontrollera-s-parser2-v1.md` (DEC-T01..T12
   ParserClient).

Спецификация интеграции (для Эпика 4):
— `_bmad-output/implementation-artifacts/spec-3-4-bc-integration.md`
  (проект `1-prilozhenie-stol-zakazov`, компонент `3-minimalnyy-produkt`)
  — фиксирует contract Эпик 3 ↔ Эпик 4: какие методы counters-сервиса
  дёргаются на какие canonical actions (#375 PR), какой
  subscriptionId / consumerName / startFromBlock для ParserClient,
  какой ForkRegistry handler, чек-лист what Эпик 4 должен реализовать
  (composite-entity Order, mapper, repository, sync-service,
  subscription, side-effect injection, fork handler, write-mutation
  pool, GraphQL Subscription).

Изменения в коде:

1. `MarketplaceOfferDomainRepository.applyRollbackDelta(offer_id, qty)`
   — новый contract-метод для ADR-005 ForkRegistry handler'а. Реализован
   в TypeORM-adapter'е через SQL UPDATE **без** CAS-проверки
   `blocked>=qty` (rollback может приходить когда счётчик уже в
   consumed-состоянии — это ожидаемо при катастрофе fork-вне-Rollback-
   Horizon; manual reconciliation FR12 ARCH-sync).

2. `MarketplaceOfferCountersService.onOrderRolledBack(offer_id, qty)`
   — публичный метод target-сервиса. Дёргается из
   `MarketplaceOrderSyncService.handleFork` для каждого откатываемого
   Order'а в block-состоянии. Emit `marketplace.offer.counters.changed`
   с op='rollback' (Story 3.5 каталог-subscription и offerer-«Активность»
   получают update).

3. `extensions/marketplace/sync/marketplace-order-sync.service.ts` —
   **scaffolding** `MarketplaceOrderSyncService`. Skeleton-класс с
   тремя методами (`start`, `dispatch`, `handleFork`) — все throw
   `'NOT IMPLEMENTED — Эпик 4'`. В JSDoc — полный контракт:
   — `@DomainKey({primary:'id', sync:'order_id'})`,
   — `@SyncBehaviour({forkPolicy:'rollback-via-versions', dlq:true})`,
   — `@Versioned({strategy:'entity_versions'})`,
   — subscription `controller-${coopname}` `primary` `last_known`,
   — мапинг `p.mkt.supply.createorder` → `onOrderBlocked`,
     `cancelorder`/`expireorder`/`declineorder` → `onOrderUnblocked`,
     `consume`+`consume2` → `onOrderConsumed`,
     FR23 → `onOrderAdjusted`,
   — fork handler → `onOrderRolledBack` per Order;
   — ссылка на spec-3-4-bc-integration.md.

   Это фиксирует **место в коде** для Эпика 4 — а не «JSDoc в любом
   из 5 файлов»; чек-лист реализации виден один в одном файле.

4. Тесты:
   — `applyRollbackDelta` добавлен в моки `MarketplaceOfferDomainRepository`
     во всех 3-х existing test-файлах (offer-service, moderation-service,
     offer-counters-service);
   — новый кейс в `marketplace-offer-counters-service.test.ts`:
     `onOrderRolledBack (ADR-005 ForkRegistry handler) → applyRollbackDelta
     + emit op:rollback` — 12-й тест в этом файле, 67-й в Эпике 3.

`pnpm tsc` чисто; `pnpm jest tests/unit/marketplace/marketplace-offer-
counters-service.test.ts` — 12/12 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-6][@ant] refactor: review feedback PR #382 — coopname, общая пагинация, RU-сообщения, продовольственные категории

— coopname вместо cooperative_id во всём marketplace (поле, колонки, индексы, UNIQUE constraint, DTO, resolvers, миграции, spec); blockchain-aligned именование как в capital/reports.
— общий PaginationInputDTO/createPaginationResult/PaginationResultDomainInterface как в payment-methods и documents; MarketplaceOfferPaginationResultDTO заменил MarketplaceOfferPageDTO; List*InputDTO наследуют PaginationInputDTO с page/limit/sortBy/sortOrder.
— сообщения ошибок на пользовательский русский без терминов available/blocked/offer/withdraw; пользователь видит понятное «Недостаточно свободного количества», «Предложение неактивно», «Можно изменять только свои предложения».
— baseline-категории: 8 продовольственных (овощи/фрукты, молочные, мясо/птица, рыба/морепродукты, хлеб/выпечка, бакалея, напитки, готовая еда) + «Прочее»; удалены услуги ремонта/доставки, хозяйственные, стройматериалы и прочие непродовольственные (вне MVP по PRD 3.2.7).

---------

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [598-13][@ant] feat: добавить токены marketplace и каркас витрины дизайн-системы — Story 10.1 даёт владельцу продукта единую страницу /market/design-system для утверждения 13 UI-компонентов до их применения в функциональных эпиках 1-9 без локального запуска

- src/app/styles/marketplace-tokens.scss — spacing (UX-DR22), touch-targets 44/48 (UX-DR23), per-role layout (UX-DR31), statusbar (UX-DR20), helper-классы
- src/pages/Marketplace/DesignSystem/ — каркас витрины: aside-навигация по 13 компонентам, переключатели роли/темы/breakpoint в URL query, заглушки Placeholder для будущих секций
- TokensSection — превью палитры, типографики, spacing-scale, touch-targets, per-role layout
- extensions/market/install.ts — route marketplace-design-system под /market/design-system (roles: chairman, member)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-13][@ant] feat: добавить CatalogOfferCard (UX-DR10) — карточка предложения в каталоге Витрины становится каноническим компонентом Стола Заказов, обёртка над существующим RequestCard с расширенным API status (draft/moderation/published/paused/sold-out/completed) + slot actions + fallback изображения для применения в эпиках 3 (Витрина) и 4 (Заказ)

- widgets/Marketplace/CatalogOfferCard — типизированный CatalogOffer + 6 статусов + slot actions + per-role POS-увеличение title
- секция CatalogOfferCard в витрине дизайн-системы со всеми 6 статусами + edge cases (нет preview, длинный текст, slot actions)
- статус в каталоге: imported → ready

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-13][@ant] feat: добавить OrderCard (UX-DR9) — карточка заказа с 10 статусами жизненного цикла и per-роль actions для применения в эпиках 4 (Заказ), 5 (Поставка), 6 (Выдача), 9 (Склад) — Стол Заказов получает единый компонент списка заказов вместо ad-hoc вёрстки

- widgets/Marketplace/OrderCard — Order interface с 10 status (draft → issued + dispute/returned) + 4 роли (orderer/offerer/operator/admin) с собственным набором actions
- секция витрины переключает симуляцию роли и показывает все 10 статусов в одной сетке
- статус в каталоге: planned → ready

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-13][@ant] feat: добавить TakeoverDialog (UX-DR7) — full-screen takeover с 4 kind для критических действий выдачи (Эпик 6), отмены заказа (Эпик 4), спора (Эпик 7), списания скоропорта (Эпик 8) даёт единый паттерн необратимых операций со встроенным confirm-loader и slot actions

- widgets/Marketplace/TakeoverDialog — TakeoverKind info/success/warning/danger + цветной header-bar + slot для body и actions + confirm-loader
- секция витрины с 4 пресетами (open by kind) + комментарий + последнее событие
- статус в каталоге: planned → ready

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-13][@ant] feat: добавить WalletTimeline (UX-DR8) — лента движений кошелька с 6 типами операций (пополнение, блокировка, разблокировка, списание, возврат, выплата) для применения в эпиках 4 (Заказ и блокировка средств) и 9 (Склад и отчётность), даёт пайщику читаемую историю и связку с конкретными заказами

- widgets/Marketplace/WalletTimeline — WalletEntry + WalletEntryKind (6 типов) + цветовая семантика + знак суммы + связь с orderId
- секция витрины с фильтром по типам + рендер бок-о-бок normal vs empty state
- статус в каталоге: planned → ready

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-13][@ant] feat: добавить BarcodeScanner (UX-DR11) и BarcodeDisplay (UX-DR12) — mock-сканер с visual feedback вместо звукового пика (UX-DR26) и SVG-рендер штрих-кода для печати на ТТН — операторский стол ПВЗ (Эпик 5 Поставка, Эпик 6 Выдача) получает согласованный паттерн ввода/вывода штрих-кодов до подключения jsbarcode/quagga в функциональной реализации

- widgets/Marketplace/BarcodeScanner — 5 состояний (idle/requesting/scanning/success/error) с lazer-анимацией и flash-эффектом
- widgets/Marketplace/BarcodeDisplay — SVG-генерация с 3 size (sm/md/lg) и опцией без подписи
- секции витрины: успех vs ошибка камеры; sizes-grid + кастомный код в инпуте
- статусы в каталоге: planned → ready (для обоих)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-13][@ant] feat: добавить CorrectionTable (UX-DR13) и TTNPrintPreview (UX-DR15) — таблица корректировки приёмки факт vs план и предпросмотр печатной ТТН А5 со встроенным штрих-кодом — операторский стол ПВЗ (Эпики 5 Поставка и 6 Выдача) получает согласованный паттерн приёмки имущества и сопровождающего печатного документа

- widgets/Marketplace/CorrectionTable — inline-input факт, цветовая подсветка дельты, счётчики совпадений/недостач/избытков
- widgets/Marketplace/TTNPrintPreview — формат А5 (148×210 мм), BarcodeDisplay в шапке, таблица позиций, две подписи, @media print
- секции витрины: CorrectionTable с 5 примерами (совпадение/недостача/избыток/полное отсутствие); TTNPrintPreview с реалистичным mock-документом
- статусы в каталоге: planned → ready

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-13][@ant] feat: добавить ExpeditorGroupingBoard (UX-DR14) WarehouseSummaryGrid (UX-DR16) OnboardingCPPGate (UX-DR17) MultiChannelStatus (UX-DR18) и секцию KUMapWithList — закрыть оставшиеся 5 custom-компонентов Эпика 10, чтобы все 13 имели визуальный канон для эпиков 1 (Онбординг), 5 (Доставка/группировка), 9 (Склад), и cross-cutting NotificationCenter

- ExpeditorGroupingBoard — drag-n-drop карточек между маршрутами + empty-state, событие move
- WarehouseSummaryGrid — admin-плотная таблица приход/расход/остаток с фильтром и сортировкой
- OnboardingCPPGate — L3-gate с required/optional/locked документами, два примера (orderer vs offerer)
- MultiChannelStatus — chip-row push/email/SMS с tooltip-details и 6 status
- KUMapWithList — секция-обёртка над существующим widgets/KUMapWithList с пояснением что реальная карта работает на /market-pvz/list
- все 13 статусов в каталоге переведены в ready

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-13][@ant] feat: добавить Story 10.3 performance baseline и правила для агентов — закрыть Эпик 10 секцией performance в витрине, инструкцией PERFORMANCE.md с эталонными страницами P1-P4 и целевыми метриками MVP/Phase 2, плюс CLAUDE.md в extensions/market/ как канон-директива для всех агентов работающих с marketplace UI

- pages/Marketplace/DesignSystem/PERFORMANCE.md — manual Lighthouse workflow, NFR-P1/P2/P3, целевые метрики
- pages/Marketplace/DesignSystem/ui/sections/PerformanceSection.vue — секция витрины с эталонными страницами и таблицей метрик
- extensions/market/CLAUDE.md — таблица 13 канонических компонентов, 5 жёстких правил для агентов (inline-вёрстка запрещена, токены только через CSS-переменные, per-роль через корневой класс, новый компонент сначала в витрине, расширение API через витрину)
- статус в каталоге: добавлена секция performance (ready)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-13][@ant] fix: восстановить плоские реэкспорты в entities/MarketplaceKUDetails — индекс экспортировал ./model только через namespace MarketplaceKUDetailsModel, из-за чего useMarketplaceKUDetailsStore / IMarketplaceKUDetails / IWorkingHours не резолвились в PvzListPage, KUMapWithList и MarketplaceDetailKUDialog (vue-tsc валился на всех ветках marketplace2) — добавлен `export * from './model'` параллельно с namespace, обратной совместимости не теряем

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-13][@ant] fix: починить ESLint и pug-парсер на marketplace2 — pug-парсер в ShipmentsPage и DisputePage падал на `#{{ id }}` (символ # после текста = id-селектор Pug), из-за чего ESLint считал весь скрипт unused; заменены на `№{{ id }}`; параллельно убраны мелкие unused в WarehousePage, PvzListPage, useDesignSystemState, ExpeditorGroupingBoard — финально npm run lint чист на src/extensions

- pages/Marketplace/ShipmentsPage/ui/ShipmentsPage.vue — `Перевозка #{{ id }}` → `Перевозка №{{ id }}` (pug syntax fix)
- pages/Marketplace/DisputePage/ui/DisputePage.vue — `Претензия #{{ id }}` → `Претензия №{{ id }}` (pug syntax fix)
- pages/Marketplace/WarehousePage/ui/WarehousePage.vue — удалён неиспользуемый import client; параметр _request заглушен через void (ESLint не учитывает underscore-prefix)
- pages/Marketplace/PvzList/ui/PvzListPage.vue — _unused-driver и _pvz-параметр заглушены через void
- pages/Marketplace/DesignSystem/composables/useDesignSystemState.ts — убран лишний import ref
- widgets/Marketplace/ExpeditorGroupingBoard — убрано неиспользуемое `const props =`, defineProps без присваивания

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-13][@ant] refactor: минимализм Айва в токенах и каталоге — нейтральная шкала + статус-чипы с точкой + терминология «заказать/закончилось» — фидбэк владельца продукта 2026-05-15 о попугайных бэйджах, выпадающей кнопке «Купить» и расхождении со словарём ЦПП (это не торговля, а заказы)

* [598-13][@ant] refactor: адаптив карточек на xs/sm — meta-grid в OrderCard и карточный fallback в CorrectionTable с touch-friendly полем «факт» — фидбэк 2026-05-15 что на узких экранах верстка просто сжимается и в поле «факт» невозможно попасть пальцем; теперь meta переносится через auto-fit grid а на <600px CorrectionTable рендерится карточками

* [598-13][@ant] refactor: единый стиль кнопок и статуса в OrderCard — primary unelevated без тени + flat second-level + mp-status-chip вместо разноцветного q-badge — фидбэк 2026-05-15 что «Открыть» (с тенью) и «Подробнее» (flat) выглядят попугайно и не из одного дизайна; теперь все actions одной формы с единым радиусом

* [598-13][@ant] refactor: убрать «попугайную» цветную шапку TakeoverDialog — нейтральный header + 4px accent-stripe слева + цветная иконка — фидбэк 2026-05-15 что info-карточка с залитой primary-шапкой выглядит броско и одинаково для info и success; теперь акцент через 4px полосу (primary/positive/warning/negative)

* [598-13][@ant] fix: контраст BarcodeScanner на тёмной теме — явный rgba-overlay вместо опоры на text-grey-7 + flash-анимация возвращается в полупрозрачный чёрный а не в transparent — фидбэк 2026-05-15 что на dark внизу мелькает белая полоска и текст «Сканер готов» теряет фон

* [598-13][@ant] feat: ExpeditorGroupingBoard — снять инлайн text-color/bg + добавить move-all для оптом-переноса — фидбэк 2026-05-15 что на dark черный текст «3 заявки ожидают группировки» уходит в чёрный фон и что перетаскивать 10 единиц одного заказа по одной — неудобно; теперь токены поверхности + кнопка «Переместить всё из N» рядом с droptarget

* [598-13][@ant] refactor: WarehouseSummaryGrid — снять колонку статус «достаточно/мало» — фидбэк 2026-05-15 что КУ кооператива работает транзитом (пришло — ушло), это не торговая точка с минимальным остатком; ввели подсветку остатка серым при нуле — этого достаточно

* [598-13][@ant] refactor: OnboardingCPPGate — одна оферта ЦПП per-роль и нейтральный «обязательно»-чип — фидбэк 2026-05-15 что в архитектуре нет ПВЗ-логистики/ценовой политики/режима самозанятого и подписки на рассылку; L2 на регистрации уже принимает обработку данных и базовые правила платформы — здесь дублировать нельзя

* [598-13][@ant] refactor: MultiChannelStatus — нейтральные чипы с точкой состояния и нормальные отступы — фидбэк 2026-05-15 что разноцветные q-chip (info/positive/primary/negative) выглядят попугайно и прижаты друг к другу; теперь surface-1 фон + 1px-граница + цветная точка-индикатор + gap 16px

* [598-13][@ant] fix: безопасное переключение light/dark — снять хардкод-фоны на CatalogOfferCard статус-чипе, DesignSystemPage preview-обёртке и заменить bg-grey-2 в секциях на mp-event-banner поверх токенов — фидбэк 2026-05-15 что статус-чип «Распродано» сливается на dark и часть баннеров не реагирует на тему; теперь все surface — через --mp-surface-* и --mp-border-subtle, переключение работает автоматически

---------

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- удалён локальный pages/.../CatalogOfferCard.vue (дублировал вёрстку карточки);
- MarketplaceCatalogPage импортирует canonical CatalogOfferCard из
  widgets/Marketplace/CatalogOfferCard (UX-DR10, Story 10.2.4);
- маппер MarketplaceOfferView → CatalogOffer: title/price/unit/stock/status;
  «Заказать» вынесено в slot actions виджета;
- корневой класс mp-role-orderer + токены --mp-* для spacing/colors,
  отказ от hard-coded color="grey-3/7" в фильтр-чипах;
- empty-state перекрашен через --mp-on-surface-muted.

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(cooptypes): canonical TS-foundation marketplace под Story 11.1 (#385)
Publish Docs / build-and-publish-docs (push) Failing after 15m59s
b3c5c0b1a7
Зеркало TS-сторон контракта marketplace под членскую модель «Стола заказов»
(PR #375). Источник правды — components/contracts/build/contracts/marketplace/marketplace.abi.

Interfaces (src/interfaces/marketplace.ts):
- 18 action-payload интерфейсов: I{CreateOrder, CancelOrder, ExpireOrder,
  AcceptOrder, DeclineOrder, SignSupp, SignChair, SignIss1, SignIss2,
  SubmRetrn, AprRetRem, RejRetRem, AccRetrn, RejRetrn, PropWroff, ExecWroff,
  DeclWroff, Migrate}
- 4 entity-интерфейса: IOrder (с current_warehouse_braname / accept_braname /
  delivery_braname), IReturnRequest, IWriteoffProposal, IWroffItem (с executed)
- Базовые IDocument2 / ISignatureInfo

Contracts (src/contracts/marketplace/):
- Удалены 24 legacy action'а (createOffer/createOrder-old/publishRequest/
  acceptRequest/confirmReceive/coopstock/...) под старую клиринговую модель.
- 18 новых action-обёрток + tables/{orders, retrequests, wroffprops}.
- index.ts реэкспорт по процессам p.mkt.{supply,return,wroff}.

Build: pnpm --filter cooptypes build — green; tsc --noEmit — green.

⚠ Breaking change: legacy desktop-фичи (Request feature, SupplyOrderRequestCard,
OfferPage, CreateChildOrder и др.) сломаются при tsc — их перепишут Stories
эпиков 3.1+/4.x на новые типы. Это ожидаемо: данный PR — pre-foundation.

Discovery Story 4.1 (Эпик 4) показал блокер — без canonical TS Эпик 4 не может
стартовать. Этот PR разблокирует Stories 4.x.

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* [598-7][@ant] feat: добавить создание Order с атомарной серией блока средств — закрыть Story 4.1 Эпика 4

Backend (controller/extensions/marketplace):
- MarketplaceOrderDomainEntity (composite db + bc; IBlockchainSynchronizable) +
  types (cycle_type / status / create_tx snapshot) + repository interface +
  TypeORM entity + mapper + delta-mapper для marketplace::orders +
  adapter (persistAfterBlock / findBySyncKey / sync-fork API).
- MarketplaceCanonicalBlockchainPort + adapter — submit canonical
  Actions.CreateOrder (cooptypes из PR #385 / Story 11.1 PR #375); подпись
  кооператива через VaultDomainService по data.coopname.
- MarketplaceOrderSyncService extends AbstractEntitySyncService: подписка
  на `delta::marketplace::orders` через EventEmitter2 + fork handler с
  компенсирующим offerCounters.onOrderRolledBack для Order'ов в block-state.
- MarketplaceOrderCreateService: guard FR11a (Offer existence/ACTIVE/
  quantity_available); optimistic onOrderBlocked → chain createorder
  (o.wal.conv → o.mkt.assign → o.mkt.block в ledger2::apply) → compensating
  onOrderRolledBack при chain-failure → persist PG с tx_snapshot.
- GraphQL Mutation marketplaceCreateOrder (через DTO/Input + Resolver)
  под Order:create marketplace-access-matrix.
- Wiring marketplace-infrastructure.module + marketplace-application.module.

Tests:
- 7 сценариев composite-entity (db-only, db+bc, sync-key mismatch, update bc
  без затрагивания backend полей, derived флаги для каждого статуса,
  hash normalization, hash валидация).
- 7 сценариев create-service (Guard FR11a 4 ветки, quantity ≤ 0,
  compensating rollback при chain submit fail, happy path с проверкой
  всех 11 полей ICreateOrder payload).

UI (desktop/widgets/Marketplace/CreateOrderDialog):
- TakeoverDialog с формой quantity + KU-выбор через KUMapWithList +
  total-cost preview + mp-role-orderer обёртка; токены только через
  marketplace-tokens.scss; CSS-vars / no hard-coded colors.

Не входит в Story 4.1 (отложено по scope):
- Apollo-codegen интеграция CreateOrderDialog в MarketplaceCatalogPage —
  Story 4.6 («Мои заказы» нужен apollo) подключит mutation marketplaceCreateOrder
  и WalletTimeline mount после успеха.
- GraphQL Subscription marketplaceOrderUpdated — Story 4.6 для live-tracking.
- Удаление legacy controller/application/marketplace/* (interactor/service/
  resolver под клиринг) — отдельный refactor-PR; TSC уже сломан этим
  legacy после PR #385 (canonical cooptypes), не блокирующий Story 4.1
  canonical layer.
- Access-matrix регистрация Order:create permission — в общем update
  Story 4.6.

Spec и контракт интеграции — _bmad-output/implementation-artifacts/
spec-3-4-bc-integration.md (Эпик 3 → Эпик 4) + controller/CLAUDE.md
(Composite-entity / Dispatch pipeline / Fork handling).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-8][@ant] feat: добавить cycle-type-aware агрегацию Order'ов в консолидированную заявку — закрыть Story 4.2 Эпика 4

Backend-only (Locked Decision L10): on-chain представления заявки НЕТ;
агрегация целиком в PG. Order'ы привязываются через
marketplace_order.cycle_id.

Поведение per cycle_type (Locked Decision L11):

- time_based: cron `@Cron(EVERY_5_MINUTES)` сканирует ACTIVE Offer'ы
  с cycle_type='time_based' + cycle_days; для каждого находит cycle_start
  как min(blocked_at) среди unassigned active Order'ов; если now >=
  cycle_start + cycle_days решает:
    sum >= min_threshold (или null) → consolidated_request status=
    'PENDING_SUPPLIER_ACCEPT' + expires_at = now + 48ч + assignToCycle
    Orders → 'ACCEPTED_PENDING_SUPPLIER';
    sum < min_threshold → consolidated_request status='EXPIRED_NO_THRESHOLD'
    БЕЗ assignToCycle (Story 4.3 unblk per-Order'ам пула).

- volume_based: метод `evaluateVolumeBasedAfterCreate` вызывается
  СИНХРОННО из `MarketplaceOrderCreateService.applyCycleTypeHook` после
  persist нового Order'а; если sum >= target_volume → instant
  consolidated_request status='PENDING_SUPPLIER_ACCEPT'. Cron-fallback
  `aggregateVolumeBasedExpired` (EVERY_HOUR) — если объём не накоплен
  и now > cycle_start + max_wait_days → consolidated_request status=
  'EXPIRED_NO_VOLUME' (Story 4.3 unblk).

- open_subscription: backend НЕ создаёт автоматически. Mutation
  `marketplaceTriggerOpenSubscription` (Offer:update:own); поставщик
  жмёт «Запустить» → consolidated_request status='ACCEPTED' сразу,
  Orders пула → 'ACCEPTED' (нажатие = акцепт всего пула).

- individual: backend НЕ агрегирует. `applyCycleTypeHook` в
  OrderCreateService делает Order.status: ACTIVE →
  'ACCEPTED_PENDING_SUPPLIER_INDIVIDUAL' сразу после persist.

Backend (controller/extensions/marketplace):
- domain/entities/marketplace-consolidated-request.{types,entity}.ts +
  spec (4 сценария: PENDING/ACCEPTED/terminal-таблица/open_subscription
  triggered).
- domain/repositories/marketplace-consolidated-request.repository.ts +
  infrastructure (TypeORM entity + mapper + adapter; индексы под
  offerer-стол / pre-aggregator idempotency / cron-scan).
- application/services/marketplace-cycle-aggregator.service.ts —
  @Cron(EVERY_5_MINUTES) aggregateTimeBased + @Cron(EVERY_HOUR)
  aggregateVolumeBasedExpired + evaluateVolumeBasedAfterCreate +
  triggerOpenSubscription. ScheduleModule.forRoot() добавлен в
  MarketplaceExtensionApplicationModule imports.
- application/resolvers/marketplace-cycle.resolver.ts — Mutation
  marketplaceTriggerOpenSubscription + Query marketplaceListConsolidatedRequests
  (Offer:update:own / Offer:read access).
- Расширены MarketplaceOrderDomainRepository (findUnassignedActiveByOffer
  / assignToCycle / sumUnassignedActiveByOffer) и
  MarketplaceOfferDomainRepository (listAllActiveTimeBased /
  listAllActiveVolumeBased).
- MarketplaceOrderCreateService.applyCycleTypeHook — per-cycle_type
  backend hook сразу после persist Order'а; для individual instant
  transition status, для volume_based — instant аггрегация при пороге.

Tests (10 сценариев):
- consolidated-request.entity.spec: PENDING/ACCEPTED/4 terminal/
  open_subscription triggered.
- cycle-aggregator.spec: aggregateTimeBased (3: достигнут + sum >= threshold;
  достигнут + sum < threshold; не достигнут), evaluateVolumeBasedAfterCreate
  (2: ниже target / достигнут), triggerOpenSubscription (4: happy /
  ForbiddenException не владелец / BadRequestException неправильный
  cycle_type / BadRequestException пустой пул).
- OrderCreateService spec обновлён под новый constructor signature
  (cycleAggregator dependency).

Не входит в Story 4.2 (отложено):
- UI для offerer-стола «Консолидированные заявки» (Story 4.5 supplier
  accept/decline UI) — там же добавится списочный widget на канон
  дизайн-системы.
- Mutation marketplaceAcceptConsolidatedRequest / DeclineConsolidatedRequest —
  Story 4.5.
- Auto-decline по expires_at (PENDING > 48ч) — Story 4.3 cron-cleanup.
- Persist полей `acceptance_window_hours` в config кооператива (сейчас
  захардкожено 48ч) — Story 9.x.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-9][@ant] feat: добавить auto-unblk Order'ов при провале триггера и cleanup непринятых заявок — закрыть Story 4.3 Эпика 4

Continuation Story 4.2 (PR #386? коммит 0ba2d51): на cron-агрегаторе и
volume_based-cron при достижении terminal-condition (sum < min_threshold
для time_based, sum < target_volume для volume_based, expires_at < now
для PENDING_SUPPLIER_ACCEPT) теперь автоматически разблокируются средства
каждого Order'а пула — без участия пайщика-заказчика или поставщика.

Что сделано (per acceptance criteria Story 4.3):

  1. Расширен canonical port (`MarketplaceCanonicalBlockchainPort`) +
     adapter (`MarketplaceCanonicalBlockchainAdapter`) методом
     `expireOrder({coopname, order_hash})` — обёртка над C++
     `marketplace::expireorder` (canonical action из PR #385 Story 11.1;
     authorization = кооператив, action_name = 'expireorder', серия
     o.mkt.unblk на total_cost + on-chain Order.status: active → cancelled).

  2. Расширен Order-domain-repository: `findByCycleId(coopname, cycle_id)`
     — все Order'ы привязанные к данной консолидированной заявке. Нужно
     для нового cron-cleanup'а — нужно итерироваться по пулу заявки,
     которая не дождалась ответа поставщика.

  3. `MarketplaceCycleAggregatorService` теперь полноценно завершает
     цикл при terminal-condition:

     - В `processTimeBasedOffer` после persist `consolidated_request`
       статуса `EXPIRED_NO_THRESHOLD` (sum < min_threshold) — серия
       `expireOrder` per-Order пула + counter `onOrderUnblocked` +
       Order.status → `EXPIRED_NO_THRESHOLD`.
     - В `aggregateVolumeBasedExpired` аналогично, но Order.status →
       `EXPIRED_NO_VOLUME` при sum < target_volume + now > cycle_start
       + max_wait_days.
     - Новый `@Cron('*/10 * * * *') expireUnacceptedPending()` — каждые
       10 минут сканирует `PENDING_SUPPLIER_ACCEPT` заявки с
       `expires_at < now` (поставщик не нажал ни Accept ни Decline в
       48-часовом окне): переводит заявку в `EXPIRED_NO_RESPONSE` с
       `decline_reason='expired_no_response'` + серия `expireOrder` пулу
       + Order.status → `CANCELLED_BY_SUPPLIER` с тем же reason.

  4. Private helper `expirePoolOnChain(pool, terminalStatus, reason)` —
     инкапсулирует pattern «per-Order chain.expireOrder → counter
     unblock → applyStatusTransition». Best-effort с partial-fail
     tolerance: chain-fail одного Order'а лог error + продолжаем
     остальные (следующий cron-тик подберёт повторно для time_based /
     volume_based; expireUnacceptedPending же — single-shot, mismatch
     админ-reconciliation Story 9.x). Counter-fail (Offer уже неактивен
     и т.п.) — лог warn + всё равно applyStatusTransition (on-chain unblk
     прошёл; единственное место рассинхронизации — quantity_available
     Offer'а, восстанавливается reconciliation).

  5. Side-by-side с `marketplace-order-delta.mapper.ts` поправлен
     устаревший импорт `Interfaces.IOrderRow` → `Interfaces.Marketplace.IOrder`
     (after PR #385 — единый `IOrder` тип).

Тесты (`marketplace-cycle-aggregator.service.spec.ts`):

  Расширены mocks (chainPort + offerCounters + findByCycleId +
  findExpiredAwaitingResponse + applyStatusTransition на orderRepo).
  Добавлены 5 новых сценариев Story 4.3:

  - **EXPIRED_NO_THRESHOLD pool unblk** — time_based ниже порога:
    `chainPort.expireOrder` per-Order × N, `onOrderUnblocked` per-Order × N,
    `applyStatusTransition(_, 'EXPIRED_NO_THRESHOLD', _)` × N.
  - **EXPIRED_NO_VOLUME pool unblk** — volume_based по истечении
    `max_wait_days` без `target_volume`: аналогично.
  - **partial chain fail tolerance** — один Order падает на `expireOrder`,
    остальные `succeeded`; лог error по failed, applyStatusTransition не
    вызывается на failed Order'е.
  - **counter onOrderUnblocked fail — applyStatusTransition всё равно** —
    Offer удалён / неактивен → counter throws, Order всё равно идёт в
    terminal-status (on-chain unblk уже прошёл).
  - **expireUnacceptedPending happy + empty** — pending > 48ч заявка →
    `EXPIRED_NO_RESPONSE` + Order'ы → `CANCELLED_BY_SUPPLIER`. Пустой
    список — early return без расходов.

  Существующий «EXPIRED_NO_THRESHOLD без assignToCycle» расширен Story 4.3
  ассертами (expireOrder × 2, onOrderUnblocked × 2, applyStatusTransition × 2).

НЕ сделано в Story 4.3 (отложено):

  - **acceptance_window_hours в config кооператива** — захардкожено 48ч,
    задача Story 9.x (общая параметризация кооператива).
  - **Реальные интеграционные тесты с C++ marketplace::expireorder** —
    blockchain-side test через nodeos staging, Story 4.6 / CI e2e.
  - **Manual reconciliation endpoint** для partial-fail случаев,
    которые cron не подобрал — Story 9.x (admin tool).
  - **GraphQL Subscription marketplaceOrderUpdated** — pubsub-канал
    Order при auto-expire должен пушиться, но Story 4.1 sync-сервис
    подписан на `delta::marketplace::orders` (on-chain expire триггерит
    дельту через parser2 → syncer → pubsub). Прямой emit pubsub из
    application-сервиса — anti-pattern (INV-12).

Файлы:

  - `domain/ports/marketplace-canonical-blockchain.port.ts` — +expireOrder
  - `domain/repositories/marketplace-order.repository.ts` — +findByCycleId
  - `infrastructure/adapters/marketplace-canonical-blockchain.adapter.ts`
    — +expireOrder impl
  - `infrastructure/adapters/marketplace-order-repository.adapter.ts`
    — +findByCycleId impl
  - `infrastructure/mappers/marketplace-order-delta.mapper.ts` —
    Interfaces.IOrderRow → Interfaces.Marketplace.IOrder fix
  - `application/services/marketplace-cycle-aggregator.service.ts` —
    constructor +chainPort +offerCounters, processTimeBasedOffer и
    aggregateVolumeBasedExpired вызывают expirePoolOnChain, +cron
    expireUnacceptedPending, +private expirePoolOnChain
  - `application/services/marketplace-cycle-aggregator.service.spec.ts`
    — 5 новых сценариев Story 4.3

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-10][@ant] feat: добавить отмену Order'а заказчиком до акцепта поставщика — закрыть Story 4.4 Эпика 4

Story 4.4 закрывает разрыв между Story 4.1 («заказчик создаёт Order и
блокирует средства») и Story 4.5 («поставщик акцептует или declined'ит»):
до того, как поставщик нажал Accept (либо до того, как time/volume-
agregator закрыл цикл за него), заказчик может отозвать свой Order сам.

Реализация:

  1. Canonical port + adapter: `cancelOrder({coopname, orderer, order_hash})`
     — обёртка над C++ `marketplace::cancelorder` (action_name='cancelorder',
     authorization = кооператив; C++ внутри `require_auth(coopname)` +
     `eosio::check(actor == order.orderer)` + status==ACTIVE).
     Серия ledger2 `UNBLOCK_ON_CANCEL` (o.mkt.unblk на total_cost) +
     on-chain Order.status: ACTIVE → CANCELLED. Сумма возвращается на
     `w.mkt.member.available` пайщика — может быть потрачена на новый
     заказ или выведена через `o.mkt.recall`.

  2. Новый application service `MarketplaceOrderCancelService`:

     **Guard** — Order существует / coopname match / orderer match /
     status='ACTIVE'. Backend разрешает отмену только в ACTIVE (это
     match с C++ check). После того, как:
      - cron `aggregateTimeBased` / `aggregateVolumeBasedExpired`
        перевёл Order в `ACCEPTED_PENDING_SUPPLIER`, или
      - persist-hook `applyCycleTypeHook` перевёл в
        `ACCEPTED_PENDING_SUPPLIER_INDIVIDUAL` (individual cycle),
     заказчик не может отменить сам — поставщик либо акцептует, либо
     declined'ит через Story 4.5. Это защищает поставщика от
     «закаривал-передумал» паттерна на пограничном acceptance-окне.

     **Chain submit** — `chainPort.cancelOrder({coopname, orderer,
     order_hash})`. Любой C++ check fail (например parallel acceptorder
     race) — clean `BadRequestException` с extracted assertion message;
     ни counter, ни Order.status не модифицируются.

     **Counter** — `offerCounters.onOrderUnblocked(offer_id, quantity)`
     best-effort: counter-fail (Offer уже не ACTIVE) → лог warn + всё
     равно applyStatusTransition (on-chain unblk прошёл).

     **Persist** — Order.status: ACTIVE → CANCELLED_BY_ORDERER с reason
     'Отменён заказчиком'. `cancelled_at` = now (через
     applyStatusTransition).

  3. GraphQL Mutation `marketplaceCancelOrder({order_id})` →
     `MarketplaceCancelOrderResult{order, tx_hash}`. ACL
     `@RequireMarketplaceAccess('Order', 'cancel:own')` — marketplace-role
     orderer + owner-check внутри сервиса (через guard orderer match).
     Полная регистрация permission'а — в Story 4.6 access-matrix update.

  4. UI: канон `OrderCard.vue` (`components/desktop/src/widgets/Marketplace/
     OrderCard`) — для orderer-роли в статусе `placed` (mapping канона
     для backend.ACTIVE) добавлена кнопка `{key:'cancel', label:'Отменить',
     kind:'danger'}`. Parent-страница (Story 4.6 «Мои заказы») перехватит
     `@action` событие с `key='cancel'` и оркестрирует confirm dialog +
     apollo `marketplaceCancelOrder` mutation. OrderCard сам по
     дизайн-канону не диалогизирует — лишь публикует event.

Тесты (`marketplace-order-cancel.service.spec.ts`, 8 сценариев):

  - **happy path** — chain.cancelOrder с {coopname, orderer, order_hash} →
    counter.onOrderUnblocked(offer_id, quantity) → applyStatusTransition
    (order.id, 'CANCELLED_BY_ORDERER', 'Отменён заказчиком'); result
    включает tx_hash и Order DTO.
  - **NotFound** — Order не существует.
  - **Forbidden** — Order чужого кооператива.
  - **Forbidden** — отменяет не заказчик-владелец.
  - **BadRequest для статусов** — `ACCEPTED_PENDING_SUPPLIER`,
    `ACCEPTED_PENDING_SUPPLIER_INDIVIDUAL`, `ACCEPTED`, `RECEIVED`,
    `CANCELLED_BY_ORDERER`: jest.each покрывает 5 ветвей "статус ≠ ACTIVE".
  - **chain fail — clean BadRequest** — Order не модифицирован,
    counter не дёрнут.
  - **counter fail — applyStatusTransition всё равно** — best-effort
    (on-chain unblk уже прошёл, counter рассинхрон правится
    reconciliation Story 9.x).

НЕ сделано (отложено):

  - **Confirm dialog UI** — родительская страница «Мои заказы» Story 4.6
    обернёт `@action(cancel)` в `q-dialog` с подтверждением. На Story 4.4
    OrderCard.vue лишь публикует event (per канон-разделение).
  - **GraphQL Subscription marketplaceOrderUpdated** — pubsub Order при
    CANCELLED_BY_ORDERER должен идти по `delta::marketplace::orders`
    (parser2 → syncer → pubsub). Прямой emit из application — anti-pattern
    (controller/CLAUDE.md INV-12).
  - **Apollo client mutation hook + composable** — клиентская часть
    Story 4.6 («Мои заказы»).
  - **Полная регистрация Order:cancel:own permission в access-matrix** —
    Story 4.6 access-matrix-update package.

Файлы:

  - `domain/ports/marketplace-canonical-blockchain.port.ts` — +cancelOrder
  - `infrastructure/adapters/marketplace-canonical-blockchain.adapter.ts`
    — +cancelOrder impl (паттерн как expireOrder/createOrder)
  - `application/services/marketplace-order-cancel.service.ts` — новый
  - `application/services/marketplace-order-cancel.service.spec.ts` —
    новый (8 сценариев)
  - `application/dto/marketplace-order-input.dto.ts` — +MarketplaceCancelOrderInputDTO
  - `application/dto/marketplace-order.dto.ts` — +MarketplaceCancelOrderResultDTO
  - `application/resolvers/marketplace-order.resolver.ts` —
    +marketplaceCancelOrder Mutation
  - `application/marketplace-application.module.ts` — providers + exports
  - `components/desktop/src/widgets/Marketplace/OrderCard/OrderCard.vue`
    — orderer.placed += cancel/danger кнопка

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-11][@ant] feat: добавить accept/decline поставщика по cycle_type — закрыть Story 4.5 Эпика 4

— расширил port + adapter методами acceptOrder/declineOrder (canonical actions из cooptypes PR #385)
— MarketplaceConsolidatedRequestAcceptDeclineService для batch (time_based/volume_based): per-Order chain.acceptOrder | chain.declineOrder + counter.onOrderUnblocked + applyStatusTransition + consolidated_request → ACCEPTED|DECLINED_BY_SUPPLIER (L10)
— MarketplaceOrderSupplierActionService: acceptIndividual / declineIndividual для cycle_type=individual + declineFromOpenPool для частичного отказа из open_subscription до запуска поставки
— 5 GraphQL Mutation: marketplaceAcceptConsolidatedRequest / marketplaceDeclineConsolidatedRequest / marketplaceAcceptIndividualOrder / marketplaceDeclineIndividualOrder / marketplaceDeclineOrderFromOpenPool с ACL Offer:update:own
— DTO: MarketplaceConsolidatedRequestActionResult / MarketplaceSupplierOrderActionResult + 5 input-DTO
— Best-effort partial-fail tolerance для batch: chain-fail per-Order → лог error + продолжаем; counter-fail → warn + applyStatusTransition всё равно
— UI OrderCard.vue: offerer.placed += decline-кнопка (parent оркестрирует confirm-dialog в Story 4.6)
— Spec: 13 сценариев для consolidated (happy / partial-chain-fail / NotFound / Forbidden / cycle_type / status / counter-fail) + 14 для supplier-action (3 группы happy/forbidden/cycle/status/chain-fail)

* [598-12][@ant] feat: добавить orderer-стол «Мои заказы» с жизненным циклом и отменой — закрыть Story 4.6 Эпика 4

— 3 GraphQL Query в MarketplaceOrderResolver: marketplaceListMyOrders / marketplaceListSupplierOrders / marketplaceGetOrder (ACL Order:read:own | read:to-self | read:all)
— MarketplaceListOrdersInput + MarketplaceGetOrderInput + MarketplaceOrderPaginationResult DTO
— toMarketplaceOrderDTO / toMarketplaceOrderCreateTxSnapshotDTO в dto.ts (общие helpers вне resolver)
— access-matrix: admin += Order:read:all (Story 4.6 AC)
— UI desktop: pages/Marketplace/MyOrders с api/index.ts (raw POST /v1/graphql, паттерн каталога) + types.ts + MyOrdersPage.vue
— MyOrdersPage: grid canon OrderCard, фильтр по статусам, polling 10s (Subscription marketplaceOrderUpdated отложена до kernel pubsub Story 9.x), confirm-dialog для cancel (Story 4.4 mutation)
— mp-role-orderer обёртка + STATUS_TO_CARD маппинг доменных статусов на canon OrderCard статусы (UX-DR20 цветные точки)
— Route /:coopname/market/my-orders переключён с legacy UserSuppliesListPage на MyOrdersPage; legacy сохранён под hidden my-supplies-legacy

* [598-13][@ant] feat: cycle_type при публикации Offer'а — закрыть Story 4.7 Эпика 4

Backend (Story 4.7 / L11):
* MarketplaceOfferService.assertCycleConditionals — per cycle_type
  required-валидация для create и update:
    - time_based       → cycle_days REQUIRED (>=1); min_threshold optional
    - volume_based     → target_volume + max_wait_days REQUIRED (>=1)
    - open_subscription → ничего не required
    - individual        → ничего не required
* На update смена cycle_type валидируется по merged-снимку (текущее
  значение из БД + patch), частичный update без касания cycle-полей
  не валидируется заново.
* Reset status → PENDING_MODERATION при любом update уже работает с
  Story 3.2 (существенное изменение покрыто).
* +11 unit-тестов: create per cycle_type (4 invalid + 3 valid) + update
  cycle_type change (4 сценария).

UI (Story 4.7 canon-форма):
* Новая страница `pages/Marketplace/CreateMarketplaceOffer` —
  offerer-форма публикации canonical Offer'а через mutation
  `marketplaceCreateOffer` (раньше роут `create-offer` указывал на
  legacy CreateParentOfferPage с program_id/unit_cost через старый API).
* mp-role-offerer обёртка + токены `marketplace-tokens.scss`.
* q-option-group для cycle_type с подсказкой текущего варианта
  (4 значения, каждый со своей пометкой).
* Conditional cycle-fields:
    - time_based       → cycle_days (required) + min_threshold (опц.)
    - volume_based     → target_volume + max_wait_days (оба required)
    - open_subscription → max_wait_days (опц.)
    - individual        → нет полей
* При переключении cycle_type — defaults и зачистка чужих полей.
* Frontend-валидация дублирует backend per cycle_type (быстрый UX);
  бэкенд — source of truth.
* После успеха — Notify positive + redirect на `marketplace-user-offers`.
* Legacy CreateParentOfferPage спрятан под `create-offer-legacy`
  (hidden=true) на случай fallback'а; полное удаление — техдолг
  marketplace2.

Не вошло (deferred):
* Seed-миграция «legacy Offer'ы без cycle_type → time_based» не
  нужна: до Story 4.7 в БД нет Offer'ов с NULL cycle_type
  (Story 3.2 ввела NOT NULL колонку с дефолтом). Если позднее
  потребуется backfill старых данных — отдельная миграция Phase 2.
* Замена raw POST `/v1/graphql` на Zeus-типизированные Mutations
  отложена до регенерации `@coopenomics/sdk` (общий техдолг
  Marketplace UI).

Тесты:
* `controller`: marketplace-offer-service.test.ts → 35 passed (11 новых).
* TSC моих файлов: 0 ошибок (89 pre-existing legacy не в скоупе Story).

* [598-13][@ant] fix: правки ревью PR #386 Эпика 4 «Заказ и блокировка средств»

— описания GraphQL DTO/resolver переписаны бизнес-языком (без story/epic-меток)
— `cycle_type` / `status` в DTO и сервисах — через ENUM (registerEnumType + companion-константы), вместо строковых литералов
— стандартная пагинация `PaginationInputDTO + createPaginationResult` в `marketplaceListConsolidatedRequests`, `marketplaceListMyOrders`, `marketplaceListSupplierOrders`
— `ASSET_SYMBOL` / `ASSET_DECIMALS` из хардкода RUB/4 убраны в DI через `MARKETPLACE_ASSET_CONFIG` (factory из `config.blockchain.root_govern_symbol/precision`)
— user-facing ошибки в `marketplace-order-supplier-action.service` переписаны на понятный русский язык с уникальными маркерами поиска `[E4SAS-…]`
— комментарий в `marketplace-order-sync.service.afterForkProcessing` явно фиксирует разделение ответственности: сущности откатываются через `deleteByBlockNumGreaterThan` + parser2 replay, hook компенсирует только счётчики Offer'а
— legacy `CreateParentOfferPage` маршрут снесён вместе с зависимостями (`features/Request/CreateParentOffer/`, `pages/Marketplace/CreateParentOffer/`); канон — `CreateMarketplaceOfferPage`
— `frontend MyOrders/api` адаптирован к новому каноническому пагинационному аргументу `options`
— конфликт двух `@ObjectType('MarketplaceCategory')` разрешён переименованием в `category-tree.dto.ts` → `MarketplaceCategoryTreeNode`
— правила зафиксированы: `controller/CLAUDE.md` (описания, ENUM, пагинация), `desktop/extensions/market/CLAUDE.md` (Zeus pipeline)

Zeus regen на схеме блокирован устаревшим legacy `application/marketplace/*` стэком — отдельная story по cleanup'у; raw GraphQL во `CreateMarketplaceOffer/api` и `MyOrders/api` помечен явной TODO-меткой блокера.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-13][@ant] refactor: снести legacy marketplace стэк, regen Zeus, типизированный SDK для Эпика 4

Backend (controller):
— удалены `src/application/marketplace/*` и `src/domain/marketplace/*` (legacy
  cooplace/marketplace resolver, interactor, service, port, ~30 DTO);
— удалён `infrastructure/blockchain/adapters/marketplace-blockchain.adapter.ts`
  и его регистрация в `blockchain.module.ts`/`app.module.ts`;
— удалены `documents-dto/asset-contribution-*` и `documents-dto/return-by-asset-*`
  (6 DTO) — оставались сиротами после сноса legacy resolver'а;
— переименован `@ObjectType('MarketplaceCategory')` в `category-tree.dto.ts`
  → `MarketplaceCategoryTreeNode` (конфликт имени с baseline-категорией Эпика 3);
— `schema.gql` регенерирована.

SDK (`@coopenomics/sdk`):
— удалены 23 legacy zeus-обёртки в `mutations/marketplace/` (publishRequest,
  updateRequest, supplyOnRequest, unpublishRequest, receiveOnRequest,
  prohibitRequest, moderateRequest, declineRequest, deliverOnRequest,
  disputeOnRequest, completeRequest, cancelRequest, completeReceiveOnRequest,
  confirmSupplyOnRequest, acceptChildOrder, createChildOrder, createParentOffer,
  generateAssetContribution*, generateReturnByAsset*);
— Zeus client regenerated (`zeus/`, `types/controller/`);
— добавлены типизированные обёртки для нового стэка: `Mutations.Marketplace.{CreateOrder,
  CancelOrder, CreateOffer, DetailKU, SetKUStatus, RetryKUGeocode}` и
  `Queries.Marketplace.{ListMyOrders, GetOrder, ListCategories, ListKUDetails}`;
— добавлены селекторы `orderSelector` (Order/CreateResult/CancelResult/
  PaginationResult), `offerSelector` (Offer/Category);
— `DetailKUInput`/`SetKUStatusInput` обновлены до `MarketplaceDetailKUInput`/
  `MarketplaceSetKUStatusInput` (новые имена в схеме).

Desktop:
— удалены 16 legacy `features/Request/*` (Accept/Cancel/Complete/ConfirmRecieve/
  ConfirmSupply/CreateChildOrder/Decline/DeliverOn/DisputeOn/Moderate/Prohibit/
  Publish/RecieveOn/SupplyOn/Unpublish/UpdateRequest);
— удалены legacy `entities/Request/`, `widgets/Marketplace/CreateChildOrderCard/`,
  `widgets/Marketplace/SupplyOrderRequestCard/`, `widgets/Marketplace/RequestCard/`;
— удалены legacy страницы `OfferPage/UserSuppliesList/WarehousePage/ShipmentsPage/
  DisputePage/Moderation/Showcase/UserParentOffers/SuppliesList` и их маршруты;
— `pages/Marketplace/CreateMarketplaceOffer/api/index.ts` и `pages/Marketplace/
  MyOrders/api/index.ts` переписаны с raw GraphQL на типизированные
  `Mutations.Marketplace.*` / `Queries.Marketplace.*`;
— `entities/MarketplaceKUDetails/api/index.ts` — те же обёртки под новые
  имена мутаций;
— redirect после создания Offer'а → `marketplace-catalog` (вместо удалённого
  `marketplace-user-offers`);
— `AGENTS.md` обновлён под актуальный набор страниц.

Тесты:
— `tests/unit/marketplace/marketplace-{moderation,offer-counters}-service.test.ts`
  — добавлены `listAllActiveTimeBased` / `listAllActiveVolumeBased` в моки
  репозитория (приведено в соответствие с domain-интерфейсом).

Память: `feedback_graphql_no_raw_strings_desktop.md` обновлён — блокер
regen снят, raw GraphQL запрещён, процедура generate-schema/generate-client/
sdk build документирована.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(marketplace): Эпик 11 техдолг 598-16 — split signchair и lazy payout (L12)

Locked Decision L12 — выплата поставщику должна выполняться только после
фактического банковского перевода кассиром, не атомарно с приёмкой имущества.
Текущая реализация signchair выполняла composite-пару o.mkt.purch + o.mkt.payout
в одной транзакции — ledger закрывал обязательство Кт 51 ДО реального
cashflow, формальное расхождение между bookkeeping и расчётным счётом.

Изменения:

C++ контракт marketplace:
- signchair оставляет только o.mkt.purch (Дт 10 / Кт 86); status
  supply_prepared → accepted_to_coop, current_warehouse_braname = accept_braname.
- Новый action `payout(coopname, order_hash)` с require_auth(coopname) — единственная
  операция o.mkt.payout (Дт 86 / Кт 51); статус Order'а не меняется.
- Order struct +bool payout_done — guard от двойного списания со счёта 51.

YAML стандарт p.mkt.supply:
- В actions добавлен marketplace::payout (actor: backend).
- В transitions: signchair → operations [o.mkt.purch]; новый transition
  accepted_to_coop → accepted_to_coop через payout с operations [o.mkt.payout].
- В operations: o.mkt.payout.triggered_by = marketplace::payout (раньше signchair).
- Сценарий: step 6 — приёмка без оплаты; новый step 6a — подтверждение выплаты.
- Описание состояния accepted_to_coop отражает открытое обязательство выплаты.

cooptypes:
- IPayout interface (coopname, order_hash); actions/payOut.ts wrapper.
- IOrder + payout_done: IBool.
- Index/jsdoc signChair приведён в соответствие.

Backend controller:
- MarketplaceCanonicalBlockchainPort.payOut + adapter с require_auth(coopname).

Что НЕ входит:
- MarketplaceOutgoingPaymentService.confirm — сервиса ещё нет (ожидается
  в Story 5.6 Эпика 5); реализуется отдельно поверх этого port'а.

Сборка:
- cdt build marketplace зелёный, ABI = 19 actions (был 18 + payout),
  order.payout_done присутствует.
- pnpm --filter cooptypes build green.
- pnpm exec tsc --noEmit (controller) green.

Refs: 598-14 / 598-16 (E11 техдолг), Locked Decision L12.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(marketplace): payout проходит через gateway с callback'ами payconfirm/paydecline

Уточнение архитектуры техдолга 598-16: исходящая выплата поставщику не должна
быть единичным action'ом контракта marketplace — она проходит через
существующий contract gateway, который умеет принимать запрос и дёргать
callback'и обратно по факту действия кассира. Перепроектировано:

C++ контракт marketplace:
- `payout(coopname, order_hash)` — backend инициирует исходящий платёж;
  inline-вызовом `Gateway::create_outcome` регистрирует запись в
  `gateway::outcomes` со статусом pending и callback'ами на marketplace.
  Ledger2 не двигается; статус Order'а не меняется.
- `payconfirm(coopname, outcome_hash)` — callback от `gateway::outcomplete`
  (require_auth(_gateway)) после действия кассира. ТОЛЬКО здесь применяется
  `Ledger2::apply(o.mkt.payout, ...)` — Дт 86 / Кт 51.
- `paydecline(coopname, outcome_hash, reason)` — callback от
  `gateway::outdecline` (require_auth(_gateway)). Без ledger-движения;
  обязательство Кт 86 остаётся открытым; reason сохраняется на Order.
- Order struct: убран `bool payout_done`, заменён на
  `eosio::name payout_status` (none/pending/completed/declined) + `string
  payout_decline_reason`. Введён namespace OrderPayoutStatus.

YAML стандарт p.mkt.supply: добавлены три новых action, transitions
accepted_to_coop → accepted_to_coop через payout/payconfirm/paydecline,
scenario 6a/6b + alternative «Кассир отклонил банковский перевод», роль
`gateway` в списке ролей; `o.mkt.payout.triggered_by` = `payconfirm`.

cooptypes:
- IPayout (без изменений по сигнатуре), новые IPayConfirm/IPayDecline.
- IOrder: payout_status + payout_decline_reason (вместо payout_done).
- payOut/payConfirm/payDecline action wrappers; index.ts экспорт.

Controller:
- MarketplaceCanonicalBlockchainPort.payOut — обновлён docstring; backend
  callback'и НЕ отправляет (parser2 подхватывает delta).

Документация:
- `components/contracts/cpp/marketplace/payout-via-gateway.md` — шпаргалка
  для агентов: триплет действий, под-граф payout_status, контрактные
  детали, anti-patterns, источники правды.

Сборка:
- cdt build marketplace зелёный; ABI = 21 action (было 19 + payconfirm +
  paydecline); order.payout_status:name, payout_decline_reason:string.
- pnpm --filter cooptypes build green.
- pnpm exec tsc --noEmit (controller) green.

Refs: 598-14 / 598-16 (E11 техдолг), Locked Decision L12.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* [598-8][@ant] feat: добавить формирование партий поставки из акцептованной заявки с жёстким акцептом состава — закрыть Stories 5.1 и 5.2 Эпика 5

Backend (controller/extensions/marketplace) — старт Эпика 5 «Поставка и
приёмка имущества на КУ». Story 5.1 — pre-shipment группировка Order'ов
консолидированной заявки по КУ-получателю с выбором варианта доставки.
Story 5.2 — жёсткий акцепт состава поставки backend'ом перед формированием
партий.

Domain (новое):
- MarketplaceShipmentDomainEntity + types (delivery_variant A/B, status
  DRAFT/SUPPLY_PREPARED/RECEPTION_IN_PROGRESS/ACCEPTED_TO_COOP/CANCELLED,
  TTNData с полями экспедитора, ТС, погрузки, доставки). Backend-only PG
  без on-chain зеркала: фиксация поставки идёт через АПП-приёмки и
  signsupp/signchair (Stories 5.3/5.4/5.6).
- MarketplaceSupplyValidationLogDomainEntity + типы Outcome/Reason —
  append-only audit-журнал валидаций состава.

Infrastructure (новое):
- MarketplaceShipmentEntity TypeORM с unique-индексом (coopname,
  cycle_id, ku_id) + partial unique-индексом по ttn_number + hot-path
  индексами по offerer/ku/status.
- MarketplaceSupplyValidationLogEntity TypeORM.
- Адаптеры репозиториев + мапперы Row→Domain под обе сущности.

Application (новое):
- MarketplaceShipmentCreateService — guard ACCEPTED + ownership заявки,
  жёсткая валидация (Story 5.2): покрытие всех КУ заявки группами,
  непересечение групп, статус Order'ов ACCEPTED. Атомарное создание
  Shipment'ов per группа + перевод Order'ов в SUPPLY_PREPARED. Для
  Варианта Б — генерация уникального ttn_number и stub-URL ТТН
  (реальная подпись через document factory подключается Story 5.4
  follow-up).
- MarketplaceShipmentResolver — мутация marketplaceCreateShipment под
  Shipment:create:own access-matrix, query marketplaceListShipments
  + marketplaceGetShipment для стола подготовки поставки.

Wiring:
- marketplace-infrastructure.module.ts регистрирует обе TypeORM-сущности,
  адаптеры и мапперы, экспортирует репозиторные токены.
- marketplace-application.module.ts регистрирует create-service и
  resolver.

Не входит в Stories 5.1/5.2 (отложено по scope в follow-up Stories
эпика):
- Apollo-codegen интеграция нового стола «Подготовка поставки» в desktop
  offerer-flow — обвязка через @coopenomics/sdk после generate-schema +
  generate-client (паттерн PR #386 Эпика 4).
- Реальная подпись ТТН через AR33 document factory — закрывается
  Story 5.4 (асинхронная подпись поставщика).
- GraphQL Subscription marketplaceShipmentUpdated для live-tracking
  партий — follow-up по аналогии с Order subscriptions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-8][@ant] feat: добавить маркировку имущества штрих-кодом Code128/EAN-13 на приёмке КУ — закрыть Story 5.5 Эпика 5

Backend (controller/extensions/marketplace) — оператор КУ при приёмке
маркирует имущество внутренним штрих-кодом (стандарт маркетплейсов;
QR-коды в MVP запрещены).

Domain (новое):
- MarketplaceInventoryDomainEntity с конструктор-валидацией формата
  EAN-13 (13 цифр + checksum) и обязательного barcode_value.
- MarketplaceBarcodeFormat (CODE128/EAN13), MarketplaceBarcodeStrategy
  (PER_ORDER/PER_UNIT/PER_PACKAGE), MarketplaceInventoryStatus
  (LABELED/ISSUED/RETURNED/WRITTEN_OFF — последние три задел для Эпиков
  6/7/8).

Infrastructure (новое):
- MarketplaceInventoryEntity TypeORM с unique-индексом по
  (coopname, barcode_value) — поиск сканером при выдаче — плюс hot-path
  индексами по order/ku/shipment + status.
- Адаптер репозитория + маппер Row→Domain.

Application (новое):
- MarketplaceInventoryLabelService — guard перехода статуса Order
  (SUPPLY_PREPARED/READY_TO_RECEIVE/ACCEPTED_TO_COOP), guard повторной
  маркировки (countByOrder>0 → 409), привязка к Shipment через
  cycle_id+ku_id, planLabels по стратегии (PER_ORDER → 1 этикетка;
  PER_UNIT → N=quantity; PER_PACKAGE → ceil(quantity/pack_size)),
  генерация уникального barcode_value с retry-loop против коллизий,
  корректный checksum-digit EAN-13.
- MarketplaceInventoryResolver — мутация marketplaceLabelInventory под
  новый access-matrix permission Inventory:label (роль operator), query
  marketplaceListInventory под Warehouse:read:own-KU для admin-стола.
- Access matrix дополнен `operator → Inventory: ['label']`.

Wiring:
- MarketplaceInventoryEntity подключён в connection «marketplace»,
  адаптер и маппер зарегистрированы в infrastructure-module, сервис
  и резолвер — в application-module.

Не входит в Story 5.5 (отложено по scope):
- Apollo-codegen интеграция BarcodeScanner/BarcodeDisplay в operator-стол
  — обвязка через @coopenomics/sdk после generate-schema +
  generate-client.
- Batch-маркировка одной страницей для всей партии (UX-уровень,
  обвязка вокруг marketplaceLabelInventory).
- barcode_strategy как поле Offer — в MVP стратегия передаётся в
  mutation. Поле на Offer'е — техдолг витрины (Stories 3.x follow-up).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-8][@ant] feat: добавить акт приёмки партии с двухподписной state machine для Вариантов А и Б — закрыть Stories 5.3 и 5.4 Эпика 5

Backend (controller/extensions/marketplace) — АПП приёмки на КУ:
оператор формирует акт, поставщик подписывает первой подписью
(лично — Вариант А или асинхронно через push — Вариант Б), председатель
закрывающей. Backend state machine (PG) + расширение canonical-port
методами signSupp / signChair.

Domain (новое):
- MarketplaceAplReceptionDomainEntity + types: вариант A/B, статусы
  PENDING_SUPPLIER_SIGN → PENDING_CHAIRMAN_RECEPTION_SIGN →
  ACCEPTED_TO_COOP (плюс CANCELLED резерв), fact_quantity_per_order
  json для корректировки Варианта Б, expeditor_data снапшот из
  Shipment.ttn_data.
- canonical-port расширен signSupp / signChair (Marketplace.ISignSupp /
  ISignChair из cooptypes).

Infrastructure (новое):
- MarketplaceAplReceptionEntity TypeORM с unique-индексом по
  (coopname, shipment_id) — одна активная АПП на партию + partial
  unique по ttn_number + hot-path индексами по ku/offerer.
- Адаптер репозитория + маппер Row→Domain.
- MarketplaceCanonicalBlockchainAdapter дополнен submit'ами signsupp
  и signchair (подпись кооператива через VaultDomainService).

Application (новое):
- MarketplaceAplReceptionService — state machine:
  * create(shipment_id, fact_quantity?) — guard статуса партии
    SUPPLY_PREPARED + ownership + одна-АПП-на-партию через unique-
    constraint; для Варианта Б копирует TTN-данные снапшотом;
    fact_quantity по умолчанию = order.quantity; total_amount =
    Σ fact_quantity * price_per_unit. Shipment →
    RECEPTION_IN_PROGRESS.
  * signAsSupplier(reception_id) — переход
    PENDING_SUPPLIER_SIGN → PENDING_CHAIRMAN_RECEPTION_SIGN;
    placeholder tx_hash до подключения on-chain signsupp с реальной
    подписью документа Document2.
  * signAsChairman(reception_id, chairman_account) — переход
    PENDING_CHAIRMAN_RECEPTION_SIGN → ACCEPTED_TO_COOP; Order'ы
    группы переходят в ACCEPTED_TO_COOP (FR19a — выдача
    разблокируется только здесь); Shipment → ACCEPTED_TO_COOP.
- MarketplaceAplReceptionResolver:
  * marketplaceCreateAplReception под Receiving:create.
  * marketplaceSignAplReceptionAsSupplier под Receiving:sign:first
    (теперь принадлежит роли offerer — корректировка матрицы).
  * marketplaceSignAplReceptionAsChairman под Receiving:sign:closing
    (новый permission на роли operator).
  * marketplaceListAplReceptionsByKu — список текущих АПП КУ для
    operator-стола.
  * marketplaceListAplReceptionsAsSupplier — список ждущих подписи
    поставщика для offerer-стола.
- Access-matrix:
  * offerer + Receiving: ['sign:first'] — первая подпись поставщика
    в обоих вариантах.
  * operator: Receiving: ['create', 'sign:closing'] — формирование
    АПП и закрывающая подпись председателя КУ.

Wiring:
- Сущность подключена в connection «marketplace», адаптер/маппер в
  infrastructure-module, сервис/резолвер в application-module.

Не входит в Stories 5.3/5.4 (отложено по scope):
- FR45 signing infrastructure: реальная подпись Document2 (IPFS +
  AR33 document factory) + on-chain submit signsupp/signchair. Сейчас
  поля supplier_signsupp_tx_hash / chairman_signchair_tx_hash
  содержат placeholder-значения; chain-port методы реализованы и
  готовы к вызову, но сервис их пока не дёргает (TODO Story 5.6
  follow-up).
- Push-уведомление поставщику в Варианте Б при создании АПП — обвязка
  через notification-module.
- outgoing_payment и o.mkt.purch / o.mkt.payout — закрываются Story
  5.6 / 5.7 отдельным коммитом эпика.
- GraphQL Subscription marketplaceAplReceptionUpdated — follow-up
  по аналогии с Order subscriptions.
- Apollo-codegen интеграция TakeoverDialog в operator-стол.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-8][@ant] feat: добавить marketplace-реестр исходящих платежей с подтверждением кассиром — закрыть Stories 5.6 и 5.7 Эпика 5

Backend (controller/extensions/marketplace) — на закрывающей подписи
АПП-приёмки (Story 5.3/5.4) backend автоматически создаёт запрос
исходящего платежа поставщику; кассир видит задачу в своём столе,
подтверждает факт банковского перевода, статус переходит в
LEDGER_RECORDED.

Domain (новое):
- MarketplaceOutgoingPaymentRequestDomainEntity + types: статусы
  PENDING_CASHIER_ACTION → CONFIRMED_BY_CASHIER → LEDGER_RECORDED
  (плюс BLOCKED при отказе банка). Поля payment_reference (банковский
  ПП), bank_statement_ref (URL/hash выписки), payout_tx_hash под
  lazy-вариант L12, blocked_reason для аудита отказов.

Infrastructure (новое):
- MarketplaceOutgoingPaymentRequestEntity TypeORM с unique-индексом по
  (coopname, apl_reception_id) — один платёж на одну АПП + hot-path
  индексами по payee и статусу.
- Адаптер репозитория + маппер Row→Domain.

Application (новое):
- MarketplaceAplReceptionService.signAsChairman расширен: после
  закрывающей подписи председателя создаётся запрос платежа со статусом
  PENDING_CASHIER_ACTION. Промах не блокирует основной flow АПП
  (логируется warn, наблюдаемость по статусам реестра).
- MarketplaceOutgoingPaymentService:
  * confirm(payment_request_id, payment_reference, bank_statement_ref?)
    — кассир подтверждает физический перевод; статус → LEDGER_RECORDED
    (MVP-поведение pre-L12, см. ограничения ниже).
  * markBlocked(payment_request_id, reason) — отказ банка; платёж
    помечается, обязательство по 86 остаётся открытым, кооператив
    разбирает вручную.
- MarketplaceOutgoingPaymentResolver:
  * marketplaceConfirmOutgoingPayment — мутация подтверждения кассиром.
  * marketplaceBlockOutgoingPayment — мутация блокировки.
  * marketplaceListOutgoingPaymentsForCashier — стол кассира.
  * marketplaceListOutgoingPaymentsAsSupplier — история выплат для
    offerer-стола.

Wiring:
- Сущность подключена в connection «marketplace», адаптер/маппер в
  infrastructure-module, сервис/резолвер в application-module.

MVP-ограничения и техдолг:
- C++ marketplace::signchair выполняет композитную пару
  o.mkt.purch + o.mkt.payout атомарно (pre-L12 поведение). Поэтому
  в текущей реализации подтверждение кассира трактуется как
  audit-trail банковского перевода (LEDGER_RECORDED сразу), а не как
  триггер lazy on-chain payout. Полная L12 (отдельная транзакция
  o.mkt.payout на confirm) — требует разделения signchair и payout в
  C++ контракте; техдолг PRD/FR57.
- Реестр marketplace-scoped (PG-only). Синхронизация с core
  outgoing_payment (AR35) — отдельным follow-up'ом со связкой
  core/gateway-API.
- On-chain submit signsupp/signchair с реальной подписью Document2
  через AR33 document factory — FR45 техдолг, отдельный коммит после
  настройки инфраструктуры подписания.
- Push-уведомления кассиру при создании запроса и поставщику при
  подтверждении — обвязка через notification-module.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-8][@ant] feat: закрыть техдолги Эпика 5 — barcode_strategy на Offer, AR35 core sync, FR45 signing, UI SDK + каркасы 6 столов — чтобы Эпик 5 был cohesive и не висел недоделками

Закрывает блокеры завершения Эпика 5 «Поставка и приёмка имущества на КУ»
(см. план в `_blago/.../598-8-epik-5-...md`, секция «Sprint-план доделки»).

598-22 — barcode_strategy + pack_size на marketplace_offer:
- ALTER через TypeORM synchronize + bootstrap-v5 миграция для bump версии;
- Domain Offer entity + types + mapper + repo create/update;
- Валидация PER_PACKAGE → pack_size (диапазон 1..1000);
- @InputType расширен enum MarketplaceBarcodeStrategy + pack_size;
- MarketplaceInventoryLabelService теперь читает стратегию из Offer
  (per-call параметр оставлен как admin-override);
- +5 unit-тестов для новых валидаций; toMarketplaceOfferDTO вынесен
  в shared helper, убраны 3 дублирующих локальных toDTO.

598-17 / AR35 — синхронизация marketplace выплат с core outgoing_payment:
- Payment entity дополнен опциональными related_extension /
  related_entity_id, PaymentDomainInterface — теми же полями;
- GatewayInteractor.createSystemOutgoingPayment без statement/method_id
  (идемпотентность через payment_hash, source = extension);
- GatewayInteractorPort + оба адаптера расширены новым методом;
- marketplace_outgoing_payment_request получил core_payment_id колонку
  и applyCorePaymentId / deleteById repository-методы;
- AplReceptionService.createOutgoingPaymentRequest вызывает core
  с compensating delete на core-fail; OutgoingPaymentService на
  confirm/block отзеркаливает статус в core (COMPLETED / CANCELLED).

598-15 — FR45 signing infrastructure для signsupp/signchair:
- MarketplaceAplReceptionDocumentFactory строит unsigned Document2
  per-Order с meta (schema, reception_id, fact_quantity, accept_braname);
- Queries marketplaceAplReceptionSupplierSignablePayloads /
  marketplaceAplReceptionChairmanSignablePayloads возвращают payload'ы
  клиенту для подписания приватным ключом;
- Mutations marketplaceSignAplReception* расширены опциональным
  signed_documents (массив signed Document2 per-Order);
- При наличии signed_documents AplReceptionService.submitOnChainSign*
  отправляет реальные chainPort.signSupp/signChair и сохраняет
  реальный tx_hash; compensating-rollback при on-chain fail (статус
  АПП не меняется, ошибка наверх — клиент решает retry);
- Backwards-compat: без signed_documents сохраняется placeholder tx.

598-18 — SDK regenerated с операциями Эпика 5 + 6 UI-pages каркас:
- pnpm generate-schema + generate-client + sdk build (пятишаговая
  процедура feedback_graphql_no_raw_strings_desktop);
- Selectors: shipment, inventory, aplReception (+signable payload),
  outgoingPayment;
- Mutations.Marketplace: CreateShipment, LabelInventory,
  CreateAplReception, SignAplReceptionAsSupplier,
  SignAplReceptionAsChairman, ConfirmOutgoingPayment,
  BlockOutgoingPayment;
- Queries.Marketplace: ListShipments, GetShipment, ListInventory,
  ListAplReceptionsByKu, ListAplReceptionsAsSupplier,
  ListOutgoingPaymentsForCashier, ListOutgoingPaymentsAsSupplier,
  AplReceptionSupplierSignablePayloads,
  AplReceptionChairmanSignablePayloads;
- 6 pages в pages/Marketplace/{OffererSupplyPreparation,
  OffererPendingAplReceptions, OffererPaymentHistory,
  OperatorReception, OperatorInventoryLabeling,
  CashierOutgoingPayments} — каркасы с импортом @coopenomics/sdk и
  per-role классами; полная интеграция с canonical widgets
  (ExpeditorGroupingBoard, TTNPrintPreview, BarcodeScanner) —
  вторым UI-PR'ом после визуальной приёмки в браузере.

598-21 — batch-маркировка штрих-кодом:
- OperatorInventoryLabeling-стол поддерживает режим «печать партии»:
  все этикетки текущего КУ выводятся в одну CSS Grid с break-inside:
  avoid + @media print 2-колончатая разметка для печати на термопринтере.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-20][@ant] feat: push-уведомления marketplace flow — поставщик/кассир/поставщик в 3 точках Эпика 5 — чтобы вариант Б АПП не висел polling'ом и кассир сразу видел новые задачи

— 3 Novu workflow в @coopenomics/notifications:
  marketplace-apl-supplier-sign-request, marketplace-cashier-new-payment,
  marketplace-supplier-payment-confirmed. Email + InApp + Push шаги,
  payload-схемы (z.object) + slugify(name) → workflowId.
— MarketplaceNotificationService с 3 @OnEvent listener'ами:
  слушает event-bus EventEmitter2 (marketplace.aplReception.b.supplier.signRequested,
  marketplace.outgoingPayment.cashier.newTask,
  marketplace.outgoingPayment.supplier.confirmed),
  получает аккаунт через ACCOUNT_DATA_PORT, дёргает NOVU_WORKFLOW_PORT.
  Ошибки Novu/missing subscriber_id — warn без блокировки flow (INV-12:
  emit ПОСЛЕ save в PG, не блокирующий).
— Emit-точки в marketplace-services после save:
  • MarketplaceAplReceptionService.create — push поставщику для
    Варианта Б (EXPEDITOR), для Варианта А поставщик подписывает на
    стойке оператора — push не нужен.
  • MarketplaceAplReceptionService.signAsChairman — push кассиру
    после createOutgoingPaymentRequest успеха (compensating-rollback
    при core-fail убирает запись — push в этом случае не идёт).
  • MarketplaceOutgoingPaymentService.confirm — push поставщику
    после фиксации в LEDGER_RECORDED.
— MVP-ограничение: кассиром выступает chairman кооператива (role:
  'chairman'); когда появится extension-роль cashier — поменять
  фильтр в MarketplaceNotificationService.handleCashierNewPayment.

Закрывает 598-20. Из 7 техдолгов Эпика 5 в этом PR'е закрыто 6
(598-22, 598-17, 598-15, 598-18, 598-21, 598-20). 598-16 (L12 split
signchair/payout в C++ marketplace::supply) остаётся cross-repo
(mono-ai-5 contracts) — выходит за scope PR #388.

* [598-20][@ant] refactor: централизовать кассира в gateway по L12 — снять marketplace-only ручки confirm/markBlocked и подцепиться к payconfirm/paydecline через delta — кассир работает на весь кооператив, marketplace только зеркалит результат

Поток L12 (PR #389):
- backend marketplace::payout → inline gateway::createoutpay (outcome pending)
- кассир в общем столе gateway подтверждает банковский перевод → callback marketplace::payconfirm
- marketplace::paydecline при отказе кассира с reason
Listener (MarketplacePayoutSyncService) переводит per-Order projection PENDING → COMPLETED/DECLINED + setPaymentStatus в core + emit push поставщику.

Backend:
- entity/repository/mapper marketplace_outgoing_payment_request: 1 запись = 1 Order (order_hash unique), статусы PENDING/COMPLETED/DECLINED
- удалён MarketplaceOutgoingPaymentService.confirm/markBlocked + resolver мутации и query listForCashier — кассирского стола в marketplace нет
- MarketplaceAplReceptionService.signAsChairman: per-Order payOut() через chainPort + createSystemOutgoingPayment в core + projection PENDING (вместо агрегированной заявки)
- Novu workflow marketplace-supplier-payment-declined + событие MARKETPLACE_SUPPLIER_PAYMENT_DECLINED_EVENT

Frontend / SDK:
- удалён desktop стол CashierOutgoingPayments (кассир в gateway-расширении)
- удалены SDK mutations confirmOutgoingPayment/blockOutgoingPayment и query listOutgoingPaymentsForCashier
- selector marketplaceOutgoingPaymentRequest обновлён под per-Order поля
- regenerate schema + zeus client + sdk build зелёный

Resolves: ревью PR #388 (кассир централизован), часть E11 техдолга 598-16

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-20][@ant] refactor: документы АПП приёмки переведены на канон factory+SignedDocument — снять локальную самодельную обвязку Document2 (signature.info, builder hash) и подключиться к GENERATOR_PORT + ядерный SignedDigitalDocumentInputDTO как у participant/capital, плюс обязательная подпись на mutation подписания

Что было не по канону (ревью PR #388):
- локальная фабрика marketplace-apl-reception-document-factory.ts с самописным sha256(meta||doc_hash);
- MarketplaceSignatureInfoInputDTO / MarketplaceSignedDocumentInputDTO дублировали SignedDigitalDocumentInputDTO ядра;
- signed_documents nullable + placeholder-tx fallback на backend (документ мог не передаваться вообще);
- output query — кастомный SignablePayload, не GeneratedDocument из ядра.

Канон (как в participant/capital):
- cooptypes: новый registry 1102.MarketplaceAplReception (Action extends IGenerate, Model + html placeholder + переводы);
- controller/application/document/documents-dto/marketplace-apl-reception-document.dto.ts — Base + Generate + SignedMeta + SignedDocumentInput через IntersectionType с GenerateMetaDocumentInputDTO и MetaDocumentInputDTO;
- preview формируется через DocumentDomainService.generateDocument с registry_id=1102 per-Order;
- сервис принимает MarketplaceAplReceptionSignedDocumentInputDTO[], верифицирует подпись через @wharfkit/antelope (PublicKey.from + Signature.verifyDigest), индексирует документы по meta.order_id;
- signed_documents — обязательное (ArrayMinSize(1)), placeholder-режим удалён;
- resolver возвращает [GeneratedDocument!]!;
- module marketplace-application подключает DocumentDomainModule.

Заодно убран any в extract tx hash (G задача из ревью) — типизирован через узкий object literal.

Frontend OffererPendingAplReceptionsPage временно показывает уведомление «UI подписи в разработке» — backend ждёт реальную подпись пайщика через wif (диалог подписания — следующий этап).

Resolves: ревью PR #388 (документы по канону, A + G), удаляет техдолг FR45 на backend.

* [598-20][@ant] refactor: ku_id → braname в marketplace Эпика 5 — выровнять имена с контрактом, где приёмный КУ описывается полем accept_braname, а сам идентификатор КУ — это просто braname; «ku_id» путал агентов и десинхронизировал backend ↔ on-chain поля

Затронуты все слои Эпика 5: shipment / inventory / apl-reception в controller + sdk + desktop.

- domain entity/types: поле и геттеры braname вместо ku_id;
- TypeORM column + индексы: `IDX_marketplace_shipment_cycle_braname_unique` (был cycle_ku) + per-table covered index по (coopname, braname, status);
- repository: findByBraname / listByBraname (вместо ByKu);
- application services + DTO + resolvers: `marketplaceListAplReceptionsByBraname` (query name), `@Args('braname')`;
- SDK: queries `ListAplReceptionsByBraname` (файл переименован), selectors с полем `braname`;
- Desktop: `fetchInventoryByBraname`, view-интерфейсы (operator-inventory + apl-reception);
- schema.gql + zeus client регенерированы.

В dev-стеке `synchronize: true` подхватит column-rename автоматически; для prod-окружения отдельная двухэтапная миграция (ADD braname → backfill ← copy from ku_id → DROP ku_id) — вне scope этого PR, пока БД stateless для разработки.

Resolves: ревью PR #388 (C — ku_id → braname).

* [598-20][@ant] refactor: канон data/input + pug в шаблонах Эпика 5 — все мутации/queries marketplace принимают единый @Args('data', InputDTO) как в core/meet, голые скаляры обёрнуты в Input; шаблоны 5 столов переведены на pug — выровнять стиль с остальной частью desktop

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-8][@ant] refactor: новые столы Эпика 5 переведены на SuccessAlert/FailAlert/NotifyAlert — убрать сырой Notify.create в пользу канона src/shared/api; ошибки идут через FailAlert(e, 'кастомный текст'), успех через SuccessAlert, заглушки UI-этапа — через NotifyAlert; кейс ревью PR #388 (OffererSupplyPreparationPage:22)

* [598-8][@ant] feat: акт АПП registry 1102 — содержание 1-к-1 из 802.ReturnByAssetAct, переменные адаптированы под marketplace — текст и переводы юр.отдела выверены; никакой самодеятельности с placeholder-шаблоном; в Action добавлены act_id и transmitter, сервис проставляет их при генерации (act_id формируется из reception+order short-id, transmitter — account поставщика для строки «Передал заказ»); кейс ревью PR #388 (1102.MarketplaceAplReception:1)

* [598-8][@ant] feat: Story 5.4 ТТН — реальная генерация через document-factory (registry_id=1103) + локальный реестр marketplace_ttn_document, Shipment.ttn_document_id вместо MVP-stub pdf_url — закрыть замечание ревью «MVP-stub в shipment-create.service.ts:321»: при формировании партии Варианта Б backend факторит документ ТТН тем же путём, что и АПП (DocumentDomainService.generateDocument с Cooperative.Registry.MarketplaceTransportNote.Action), и сохраняет в отдельной marketplace-таблице — общий реестр документов кооператива не трогаем, экспедиторы пока не пайщики и подписывают перевозку вне платформы; из Shipment удалены ttn_pdf_url и ttn_document_registry_id, заменены на ttn_document_id (uuid → marketplace_ttn_document.id); кейс ревью PR #388 (marketplace-shipment-create.service:321)

* [598-8][@ant] chore: regenerate schema.gql + SDK zeus + shipment selector после правок ревью PR #388 — pnpm run generate-schema + generate-client; в SDK zeus теперь нет ttn_pdf_url/ttn_document_registry_id (удалены из MarketplaceShipment DTO), добавлен ttn_document_id; shipmentSelector.ts руками поправлен под новый набор полей; SDK typecheck и build зелёные

* [598-8][@ant] fix: desktop typecheck — починить рассинхрон tsc/vue-tsc + убрать pug-мусор — без vue-tsc и с trailing \ в шаблонах PR не катит тайпчек

— components/desktop/package.json: `typecheck` возвращён на `vue-tsc --noEmit --skipLibCheck` (был «hard update» 2025-10-14 на чистый tsc; tsc не парсит .vue и не видит named re-export типов из них — отсюда 21 ошибка TS2614 на widget index.ts из Эпика 10).
— 4 страницы Marketplace (OffererPaymentHistory/OffererPendingAplReceptions/OffererSupplyPreparation/OperatorReception): убраны trailing `\` внутри `:columns="[...]"` — pug не требует line-continuation внутри `()`, vue-tsc ругался TS1127 Invalid character.
— components/sdk/src/queries/marketplace/listOutgoingPaymentsAsSupplier.ts: `query` приведён к канону остальных queries (объект через `$()`, не функция (input)=>Selector(...)); desktop вызывал как объект — отсюда TS2560 «Did you mean to call it?».
— components/desktop/tsconfig.vue-tsc.tsbuildinfo: удалён tracked билд-кэш vue-tsc.

* [598-8][@ant] feat: паттерн doc_data в @coopenomics/factory + ввести 1102/1103 как канонные документы — приватные данные документа off-chain, on-chain только sha256 hash, шаблон обращается через зарезервированную {{ doc_data.* }}

Закрывает крайнее ревью PR #388 (2026-05-17):

— components/factory/src/Services/DocData/index.ts: новый DocDataService — sha256 + Mongo upsert по hash (идемпотентный save), get по hash (null если запись удалена — graceful degradation).
— components/factory/src/Factory/index.ts: в DocFactory методы saveDocData/getDocData/loadDocData; factory автоматически даёт фабрике помощник для подгрузки приватного payload по data.doc_data_hash.
— components/factory/src/index.ts: Generator-фасад выставляет saveDocData/getDocData в IGenerator.
— components/cooptypes/src/cooperative/document/index.ts: IDocDataRef { doc_data_hash?: string } — зарезервированная ссылка, которую Action любого нового документа расширяет.
— components/factory/README.md + AGENTS.md: раздел «Приватные данные документа: doc_data» с потоком, зарезервированными именами и graceful degradation.
— components/factory/test/doc-data.test.ts: unit — sha256, идемпотентность, стабильный порядок ключей, null при удалении, делегирование через Generator-фасад.

1102.MarketplaceAplReception:
— cooptypes registry/1102: Action упрощён до канона 802.ReturnByAssetAct (registry_id, order_id, order_hash, act_id, transmitter, braname?, doc_data_hash?); семантика — `user` = пайщик-получатель (USERNAME), `transmitter` = USERNAME председателя КУ или доверенного им лица, передающего имущество (ФИО подставляется factory через getUser → getFirstLastMiddleName). Текст и переводы — 1-в-1 с 802 (юр.выверенный текст). Никаких «филиалов».
— factory/Actions/1102, factory/Templates/1102: каноничная фабрика по образцу 802.ReturnByAssetAct (factory.getUser(username) → пайщик, factory.getUser(transmitter) → председатель); decision для marketplace — стаб (нет протокола совета на каждый order).
— controller/extensions/marketplace/.../marketplace-apl-reception.service.ts: при сборке Action в Action идут только канонические поля; ФИО пайщика берётся по order.orderer_account, transmitter = chairman_account ?? created_by_operator_account.

1103.MarketplaceTransportNote:
— cooptypes registry/1103: Action очищен — { ttn_number, cycle_id, shipment_id, accept_braname, supplier_account, total_amount, currency, doc_data_hash }. Все персональные данные экспедитора (ФИО, паспорт, телефон, госномер, адрес погрузки, время доставки) вынесены в interface PrivateData — payload off-chain.
— Шаблон обращается через {{ doc_data.expeditor_full_name }}, {{ doc_data.vehicle_number }} и т.п.
— factory/Actions/1103, factory/Templates/1103: фабрика загружает приватный payload через loadDocData; ошибка если doc_data_hash не задан или payload удалён.
— controller/extensions/marketplace/.../marketplace-shipment-create.service.ts: убран MVP-stub — приватные поля экспедитора сохраняются через documentDomainService.saveDocData(...) → doc_data_hash → in Action; on-chain Action не содержит персональных данных.

Прочие документы (700-1099, 300-304, 50-51, …) остаются как были — паттерн прогрессивный, для новых документов с PII и при явной потребности.

Архитектурный артефакт (_bmad-output/planning-artifacts/architecture.md в blago проекте 1-prilozhenie-stol-zakazov): дополнен разделом «Document Generation Pattern: doc_data (Private Document Data Store)». Story 5.8 заведена в blago через `blago create req`.

---------

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [598-9][@ant] feat: backend Эпика 6 «Выдача пайщику с двойной подписью» (Stories 6.1-6.4) — расширить Order полями выдачи, завести MarketplaceIssuanceService с двумя mutations (signiss1 + signiss2) и push «заказ готов к получению»

— Order entity + typeorm + mapper: добавлены поля выдачи (current_warehouse_braname, issuance_fact, chairman_signed_at/chairman_account/signiss1_tx_hash, orderer_signed_at/delivery_signer_account/signiss2_tx_hash), derived-геттеры awaits_chairman_issue_open/awaits_orderer_issue_final/is_received. На Order живут metadata подписей; жирные Document2 хранятся в on-chain row marketplace::orders.
— Repository: applyIssuanceOpened (статус ACCEPTED_TO_COOP → READY_TO_RECEIVE, проставление chairman_signed_at + signiss1_tx_hash + current_warehouse_braname = delivery_braname), applyIssuanceFinalized (READY_TO_RECEIVE → RECEIVED + issuance_fact + warranty_until), listForIssuanceByBraname (operator), listReadyToReceiveByOrderer (orderer).
— Chain port + adapter: signIss1, signIss2 — обёртки над marketplace::signiss1/signiss2 (контракт уже реализован в cpp/marketplace/src/p.mkt.supply/).
— MarketplaceIssuanceService: openIssuance (председатель КУ открывает выдачу), finalizeIssuance (заказчик закрывает финальной подписью; actual_quantity влияет на корректирующие операции в C++ signiss2 — три ветки FR23), getOpenIssuanceSignablePayload / getFinalizeIssuanceSignablePayload (preview документов через factory registry_id=1102).
— GraphQL DTO + resolver: MarketplaceIssueActSignedDocumentInputDTO, MarketplaceOpenIssuanceInputDTO, MarketplaceFinalizeIssuanceInputDTO, MarketplaceIssuanceResultDTO; mutations marketplaceOpenIssuance / marketplaceFinalizeIssuance; queries marketplaceListIssuancesByBraname / marketplaceListMyReadyToReceive + preview payloads.
— Access matrix: operator получает Issuance:{create,sign:first,read:own-KU}; orderer получает Issuance:{sign:final,read:own}.
— Push notification (Story 6.4 / FR22): новый workflow @coopenomics/notifications/marketplace-order-ready (email + in-app + push) + listener в MarketplaceNotificationService, эмит после applyIssuanceOpened (INV-12 — после save в PG).
— MarketplaceOrderDTO расширен публичными полями выдачи (issuance_fact с enum diff_state, chairman/orderer signatures metadata) — UI карточки заказа в RECEIVED видит фактическое количество и diff_state.

Issue: 598-9 / Эпик 6 MVP Стола Заказов

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-9][@ant] feat: SDK Эпика 6 — обёртки openIssuance/finalizeIssuance + queries listing/preview, обновлены схема и zeus-клиент (Story 6.5)

— schema.gql + zeus regen после расширения marketplace-process-controller: MarketplaceOpenIssuance/FinalizeIssuance/IssuanceResult/IssueActPayload + 4 query/mutation для столa выдачи.
— Selectors: orderSelector расширен полями выдачи (current_warehouse_braname, issuance_fact с diff_state, chairman/orderer signatures metadata) + новый marketplaceOrderIssuanceFactSnapshotSelector + marketplaceIssuanceResultSelector — UI карточки заказа в RECEIVED увидит фактическое количество.
— Mutations: openIssuance.ts, finalizeIssuance.ts — обе через единый marketplaceIssuanceResultSelector.
— Queries: listIssuancesByBraname.ts (operator-стол), listMyReadyToReceive.ts (orderer-стол), issueActChairmanSignablePayload.ts / issueActOrdererSignablePayload.ts (preview документа для подписи).
— Side-fix техдолгов рассинхронизации cooptypes dist ↔ controller, мешавших generate-schema:
  * marketplace-apl-reception.service.ts — добавлено обязательное reception_id в Action 1102.
  * marketplace-shipment-create.service.ts — Action 1103.MarketplaceTransportNote/PrivateData приведены к any (поля экспедитора уже off-chain в privatePayload через doc_data_hash).
  * generator.service.ts — saveDocData/getDocData проброшены через cast (методы из @coopenomics/factory есть в runtime, в типах ещё не публиковались).

Issue: 598-9 / Эпик 6 MVP Стола Заказов

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-9][@ant] feat: каркас столов выдачи в desktop — OperatorIssuance + OrdererReadyToReceive (Stories 6.6-6.7)

Минимальные каркасные страницы по образцу OperatorReception для Эпика 6 — лента заказов на ПВЗ + точки входа в подписи. Полный flow подписи (BarcodeScanner со штрих-кодом заказа, CorrectionTable для сверки факт vs заказ, TakeoverDialog для двух подписей) — следующий UI PR; backend уже принимает signed_document и исполняет три ветки сверки в C++ signiss2 атомарно.

— OperatorIssuance: список заказов на КУ выдачи (ACCEPTED_TO_COOP + READY_TO_RECEIVE) + точки входа «Открыть выдачу» (signiss1) / «Завершить выдачу» (signiss2). API через @coopenomics/sdk — listIssuancesByBraname / openIssuance / finalizeIssuance + getChairman/OrdererSignablePayload.
— OrdererReadyToReceive: лента заказов пайщика в READY_TO_RECEIVE — визуальное продолжение push-уведомления marketplace-order-ready (FR22). API — listMyReadyToReceive через @coopenomics/sdk.
— Никаких raw GraphQL-строк — только типизированные обёртки @coopenomics/sdk, как требует components/desktop/extensions/market/CLAUDE.md.

Issue: 598-9 / Эпик 6 MVP Стола Заказов

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-9][@ant] feat: полный UI flow подписи Эпика 6 через канон-widgets + регистрация в роутинге

Закрываем техдолг Эпика 6, оставленный из предыдущего коммита. Оба диалога — это реальная подпись приватным ключом через @coopenomics/sdk Classes.Document, не моки.

— IssueActOpenDialog (Story 6.1 / FR21): full-screen TakeoverDialog с предварительным актом выдачи (registry_id=1102). Председатель кооперативного участка подписывает первой подписью приватным ключом из useGlobalStore (signatureId=1), backend верифицирует и отправляет on-chain signiss1; статус заказа READY_TO_RECEIVE + push заказчику.
— IssueActFinalizeDialog (Story 6.3 / FR23-FR25): full-screen takeover с тремя шагами через q-stepper:
  * **Шаг 1 — штрих-код.** BarcodeScanner (DR11) сверяет штрих-код заказа.
  * **Шаг 2 — сверка факта.** CorrectionTable (DR13) показывает «план vs факт», оператор корректирует actual_quantity. UI вычисляет fact_cost и diff_state (equal/less/more) и подсказывает, какие корректирующие операции исполнит контракт (o.mkt.unblk при less, o.wal.conv + o.mkt.assign + o.mkt.block при more).
  * **Шаг 3 — двойная подпись.** Заказчик вводит свой WIF (signatureId=1, signer = orderer_account), оператор подписывает вторым ключом из сессии (signatureId=2, delivery_signer = current member). Backend верифицирует обе подписи и отправляет on-chain signiss2 — C++ исполняет три ветки сверки в одной композитной транзакции; при недостатке средств на доплату (more) транзакция падает с человеческим сообщением (L6 guard / FR25), backend пробрасывает его клиенту.
— OperatorIssuancePage переключён на эти диалоги — теперь кнопки «Открыть выдачу» и «Завершить выдачу» делают реальный сценарий, а не NotifyAlert-заглушку.

Регистрация в extensions/market/install.ts:
— `/<coopname>/market-pvz/issuance` — operator-стол выдачи (roles: chairman, иконка handshake).
— `/<coopname>/market/ready-to-receive` — orderer лента «Готово к получению» (roles: все, иконка box-check).

Issue: 598-9 / Эпик 6 MVP Стола Заказов

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-9][@ant] refactor: убрать any/unknown по ревью — пересобрать cooptypes/factory + типизировать desktop api через Queries.X.IOutput и Types.Document

Реакция на ревью PR #390: ревьюер требует строгую типизацию, any/unknown запрещены вне очень редких случаев.

Корневая причина прошлых хаков — рассинхронизация cooptypes/factory dist ↔ source в monorepo. Source 1102 уже без reception_id и 1103 уже с PrivateData, но dist в node_modules был собран из старой версии. Решение: пересборка cooptypes и factory из текущего source — dist приходит в соответствие с типами, controller компилируется без cast'ов.

— marketplace-shipment-create.service.ts: `const action: any` → `Cooperative.Registry.MarketplaceTransportNote.Action`, `privatePayload: any` → `PrivateData`. Все expeditor-поля живут off-chain в privatePayload, как и задумано в архитектуре 1103.
— marketplace-apl-reception.service.ts: убран лишний `reception_id` (его нет в свежей dist Action 1102).
— marketplace-issue-act-document.dto.ts: убрано поле `reception_id` из подписной формы АПП-выдачи — тоже не требуется.
— generator.service.ts: убран `(this.generator as any).saveDocData/getDocData` — методы есть в новой factory dist.
— catch (err: any) → catch (err) с `err instanceof Error ? err.message : String(err)` в marketplace-issuance.service.ts.

Desktop:
— OperatorIssuance/api/index.ts полностью переписан: вместо локальных View-интерфейсов и `as unknown as` — производные типы от SDK: `Queries.Marketplace.ListMyReadyToReceive.IOutput['marketplaceListMyReadyToReceive'][number]` для Order'а в контексте выдачи, `Types.Document.IGeneratedDocument` для preview-документа, `Types.Document.ISignedDocumentInput` для signed_document, `Mutations.Marketplace.OpenIssuance.IOutput['marketplaceOpenIssuance']` для результата. Все client.Query / client.Mutation возвращают строго типизированный `result` через destructuring `const { [Mutations.X.name]: result }` — паттерн из features/User/LoginUser/api/index.ts.
— OrdererReadyToReceive/api/index.ts: то же — без `as unknown as`.
— IssueActOpenDialog/FinalizeDialog: убраны все `generated as unknown as Parameters<...>` и `signed as unknown as SignedDocumentInput` — `docSigner.signDocument(generated, ...)` принимает результат `getXxxSignablePayload` напрямую через `Types.Document.IGeneratedDocument`. catch (err: any) → catch (err) с проверкой `instanceof Error`.

Issue: 598-9 / Эпик 6 MVP Стола Заказов

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [598-10][@ant] feat: backend Эпика 7 «Гарантийный возврат» (Stories 7.1-7.4) — завести MarketplaceReturnClaimService с 5 mutations (submretrn/aprretrem/rejretrem/accretrn/rejretrn) и хранилище фото в bucket stol-zakazov:images

— Domain: ReturnClaim entity + types (5 статусов pendrev/approvvisit/accepted/rejremote/rejatku с маппингом на ReturnStatus::* контракта), decision_log append-only журнал решений председателя, on_site_inspection-снапшот очного осмотра, ledger_snapshot снапшот compensating-forward пары после accretrn. Photo type хранит bucket_key + sha256 content_hash + mime_type — хеш используется как анкер on-chain (параметр photos[] submretrn).
— Repository: create/findById/findByRequestHash/findActiveByOrderId (idempotency check) /listByOrderer/listByDeliveryBraname/applyDecision (append к decision_log + опциональная фиксация on_site_inspection / ledger_snapshot). Partial-unique индекс (coopname, order_id) ограниченный активными статусами — пайщик не может одновременно открыть две заявки на один заказ.
— Canonical blockchain port + adapter: 5 новых методов submRetrn/aprRetRem/rejRetRem/accRetrn/rejRetrn — обёртки над marketplace::submretrn/aprretrem/rejretrem/accretrn/rejretrn (контракты уже реализованы в cpp/marketplace/src/p.mkt.return/). accretrn — композитная транзакция через транзит 91: o.mkt.return + o.mkt.return2 (compensating forward к o.mkt.consum, AR9/AR14 L3).
— MarketplaceReturnClaimService: submitReturnClaim (Story 7.1: валидация warranty_until>now, reason_text 1-500, photos 1-10, request_hash детерминирован от order_hash+orderer+actual_quantity, фото загружаются в bucket до on-chain submit, on-chain photos[] = sha256-хеши), approveReturnVisit/rejectReturnRemote (Story 7.2 FR31: PENDING_CHAIRMAN_REVIEW → APPROVED_FOR_VISIT/REJECTED_REMOTELY, comment обязателен), acceptReturnAtVisit/rejectReturnAtVisit (Story 7.3-7.4 FR32-FR33: APPROVED_FOR_VISIT → ACCEPTED_AT_VISIT/REJECTED_AT_VISIT, при accept атомарно compensating forward o.mkt.return+o.mkt.return2 восстанавливает w.mkt.member.available заказчика и возвращает имущество на склад КУ, barcode-сверка с Order). getReturnClaimSignablePayload — preview через factory registry_id=800 (ReturnByAssetStatement, переиспользован для членской модели по стандарту p.mkt.return.standard.yaml).
— MarketplaceReturnClaimImagesService: @UseBucket('stol-zakazov:images') — конвенция «один сервис на один bucket» из README file-storage. maxBytes 10MB, allowedMime jpeg/png/webp, metadataSchema требует ownerAccount+orderId+claimId. Ключ объекта детерминирован: returns/<claim_id>/<role>/<index>.<ext>; backend хеширует bytes (sha256-hex) — этот же хеш идёт on-chain в submretrn.photos[].
— Resolver + DTO: 5 mutations + 3 queries (marketplaceListMyReturnClaims, marketplaceListReturnClaimsByBraname, marketplaceReturnClaimSignablePayload, marketplaceReturnClaim) + Photo/Decision/Inspection/Ledger ObjectType. GraphQL enum через registerEnumType (MarketplaceReturnClaimStatus / MarketplaceReturnClaimDefectCategory / MarketplaceReturnClaimExpectedResolution). Подписанное заявление приходит как SignedDigitalDocumentInputDTO meta=MarketplaceReturnClaimSignedStatementInputDTO; toDocument() конвертит в Cooperative.Registry.ReturnByAssetStatement.Action для submretrn.statement.
— Signed-document DTO: переиспользуем шаблон 800 «Заявление на возврат паевого взноса имуществом» (по стандарту p.mkt.return — структурно совместим с членской моделью; поля request.hash/title/units/unit_cost/total_cost заполняются из Order). request.hash = backend-computed request_hash → двусторонняя сверка backend ↔ on-chain.
— Access-matrix: orderer ['create:own', 'read:own'] на ReturnClaim, operator ['read:own-KU', 'decide:remote', 'decide:on-site']. resolver-уровень проверка ownership (orderer_account == member.username, braname == председатель чьего КУ — отложено до core coop_ku.chairman_account, в MVP передаётся параметром).
— Регистрация в marketplace-infrastructure.module.ts (TypeORM entity + repository) и marketplace-application.module.ts (FileStorageInfrastructureModule.forFeature + service + resolver).

Why: реализация Эпика 7 «Гарантийный возврат имущества» MVP «Стол Заказов» — пайщик подаёт заявление с фото-доказательством, председатель КУ удалённо рассматривает и при необходимости приглашает на очный осмотр, при принятии возврата атомарно через транзит 91 восстанавливаются средства на программном кошельке заказчика и имущество возвращается на склад участка (compensating forward — отдельное событие в журнале, не revert исходного o.mkt.consum). Возврат поставщику и работа с поставщиком по претензиям — Phase 2 (out of MVP).

* [598-10][@ant] feat: notifications Эпика 7 — 3 Novu workflow и слушатели per-contract event-bus (submitted/decided/finalized) для гарантийного возврата

— @coopenomics/notifications: 3 workflow: marketplace-return-claim-submitted (председателю КУ на новое заявление; email + in-app + push с deep-link на operator-стол), marketplace-return-claim-decided (заказчику на промежуточные решения председателя — invite на очный визит), marketplace-return-claim-finalized (заказчику на финальный исход с восстановленной суммой если accept_at_visit).
— controller: 3 события в marketplace-notification.events.ts — MARKETPLACE_RETURN_CLAIM_SUBMITTED_EVENT/DECIDED/FINALIZED + типы payload. Сервис эмитит их после save в PG (INV-12).
— marketplace-notification.service.ts: 3 listener (handleReturnClaimSubmitted/Decided/Finalized) тянут subscriber_id/email из AccountDataPort, формируют payload с display-name и deep-link через config.frontend_url, шлют через NovuWorkflowPort. Ошибки доставки logged как warn — основной flow возврата не блокируется.

Why: уведомления (push/email/in-app) — обязательный канал коммуникации по UX-DR38; заявление на гарантийный возврат должно дойти до председателя КУ без поляризации UI (председатель не всегда сидит в operator-столе), решение председателя — до заказчика без необходимости вручную проверять статус заявления.

* [598-10][@ant] feat: SDK Эпика 7 — Zeus selectors, 5 mutations и 3 queries для гарантийного возврата + перегенерированная schema.gql

— Selectors: marketplaceReturnClaimSelector (полный набор полей заявления вместе с photos / decision_log / on_site_inspection / ledger_snapshot), marketplaceReturnClaimResultSelector (claim + tx_hash) и поднаборы (photo / decision-entry / inspection / ledger). Валидаторы MakeAllFieldsRequired гарантируют полное покрытие type.
— Mutations: CreateReturnClaim / ApproveReturnVisit / RejectReturnRemote / AcceptReturnAtVisit / RejectReturnAtVisit. Каждая принимает соответствующий Input DTO и возвращает marketplaceReturnClaimResultSelector.
— Queries: ListMyReturnClaims (без args), ListReturnClaimsByBraname (data.delivery_braname), ReturnClaimSignablePayload (data.order_id + actual_quantity? — preview registry_id=800 для подписи пайщиком).
— schema.gql пересчитана; zeus/const.ts + zeus/index.ts перегенерированы. Все ID-поля заявления через @Field(() => String) (Zeus резолвит scalar ID как unknown без глобального scalar-resolver — поэтому Order и ReturnClaim единообразно используют String).

Why: типобезопасный SDK-слой между backend GraphQL и desktop UI; правило «никаких raw GraphQL-строк в desktop» (feedback_graphql_no_raw_strings_desktop) требует прохода через Zeus client.

* [598-10][@ant] feat: desktop UI Эпика 7 — orderer и operator страницы возврата на канон-widgets (TakeoverDialog + BarcodeScanner)

— OrdererReturnClaims (страница /:coopname/market/returns): список заявлений пайщика разделён на «активные» (PENDING_CHAIRMAN_REVIEW + APPROVED_FOR_VISIT) и «архив» (финальные статусы). Подача нового заявления через SubmitReturnClaimDialog — full-screen TakeoverDialog с q-stepper 3 шага: описание (reason_text + defect_category + actual_quantity?) → фото (q-file 1-10 шт jpeg/png/webp до 10МБ → base64) → подпись заявления (preview registry_id=800 + Classes.Document.signDocument приватным ключом из useGlobalStore). Просмотр через ReturnClaimDetailsDialog — фото-thumbnails со ссылками на signed-URL bucket'а, q-timeline decision_log председателя, снапшот compensating-forward при ACCEPTED_AT_VISIT.
— OperatorReturnClaims (страница /:coopname/market-pvz/returns): три ленты — pending (PENDING_CHAIRMAN_REVIEW) с inline-thumbnails фото и кнопкой «Принять решение» → RemoteDecisionDialog, approved (APPROVED_FOR_VISIT) с кнопкой «Очный осмотр» → OnSiteDecisionDialog, archive. RemoteDecisionDialog — q-option-group approve/reject + обязательный комментарий 1-500 симв. OnSiteDecisionDialog — q-stepper: 1) BarcodeScanner сканирует штрих-код имущества для сверки с Order; 2) inspection_result 1-2000 симв; 3) опциональные inspection_photos до 10 шт; 4) accept (compensating forward предупреждение) либо reject (имущество остаётся у заказчика).
— API: типобезопасные обёртки в pages/<page>/api/index.ts через destructuring результата `{ [Queries.X.name]: result } = await client.Query(...)`. Типы выведены из Queries/Mutations.X.IOutput без cast'ов. Фото-загрузка через FileReader→base64 в browser, backend кладёт в bucket stol-zakazov:images.
— DateTime utility: `formatDateTime(value: unknown)` для GraphQL DateTime scalar — Zeus резолвит как unknown без глобального scalar-resolver, простой helper парсит и форматирует через toLocaleString('ru-RU').
— install.ts: 2 новых маршрута зарегистрированы — workspace 'market' (orderer-стол) → '/:coopname/market/returns' (roles []); workspace 'market-pvz' (operator-стол) → '/:coopname/market-pvz/returns' (roles ['chairman']). Иконки fa-rotate-left / fa-clipboard-check; agreements: agreementsBase.

Why: продолжение поведенческой части Эпика 7 — пайщик и председатель кооперативного участка получают функциональные UI для подачи и рассмотрения заявлений на гарантийный возврат, со всеми атрибутами (фото, подпись заявления, барскод-сверка, compensating forward) согласно FR29-FR33. Соблюдён канон дизайн-системы Эпика 10: используются только widgets/Marketplace/* (TakeoverDialog + BarcodeScanner), inline-вёрстка кастомных диалогов запрещена; токены через marketplace-tokens.scss (var(--mp-space-md), mp-role-* классы на корнях страниц).

* [598-10][@ant] feat: добавить registry_id=1104 MarketplaceReturnStatement в cooptypes — отделить документ Эпика 7 «Стол заказов» от системы клиринга

Marketplace по системе членских взносов ведёт свой контур документов рядом
с 1102 «Акт приёма-передачи» и 1103 «ТТН». До этого Эпик 7 ошибочно
переиспользовал registry_id=800 ReturnByAssetStatement из старой системы
клиринга «по структурному совпадению» — это смешало два процесса в одном
signed-document пайплайне. Завожу полноценный собственный документ:
заявление пайщика о гарантийном возврате имущества с marketplace-полями
(order_id / order_hash / fact_cost / actual_quantity / reason_text /
defect_category / braname). Шаблон 800 остаётся как есть на случай
будущего клиринга.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [598-10][@ant] feat: проложить factory-цепочку для registry_id=1104 — Action + Template + регистрация в Registry/index

Action 1104.MarketplaceReturnStatement.Factory повторяет паттерн 1102
(getCooperative/getVars/getUser/getRequest/getProgram/getOrganization),
но кладёт в Model marketplace-поля (fact_cost / actual_quantity /
reason_text / defect_category) и не требует transmitter/decision —
заявление подписывает только пайщик.

Также: убираю tsconfig.tsbuildinfo из индекса (build-артефакт, не должен
жить в git).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [598-10][@ant] feat: переключить controller + SDK на registry_id=1104 для Эпика 7 — заявление о гарантийном возврате имущества

- marketplace-return-statement-document.dto.ts — новый Signed-DTO под
  Cooperative.Registry.MarketplaceReturnStatement.Action; старый
  marketplace-return-claim-document.dto.ts (ссылавшийся на 800
  ReturnByAssetStatement) удалён.
- MarketplaceReturnClaimService.generateStatementDocument теперь строит
  Action.MarketplaceReturnStatement с marketplace-полями (order_id /
  order_hash / fact_cost / reason_text / defect_category / braname),
  а не reuse'ит request{hash,units,unit_cost,total_cost} из старого 800.
- getReturnClaimSignablePayload принимает reason_text — превью теперь
  показывает реальный текст обращения пайщика.
- Регенерированы schema.gql + zeus client + SDK zeus (вход
  MarketplaceReturnStatementSignedInput с marketplace-меткой meta).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [598-10][@ant] feat: desktop SubmitReturnClaimDialog — превью под registry_id=1104 с пробросом reason_text

- API getReturnClaimSignablePayload теперь принимает reason_text и
  defect_category — превью показывает реальный текст обращения пайщика.
- Убрал `result as MarketplaceGeneratedDocumentView` cast (Zeus уже
  выдаёт нужный тип после регенерации schema/SDK).
- Комментарии и подсказки UI обновлены: 1104 MarketplaceReturnStatement
  вместо устаревшего 800 ReturnByAssetStatement.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [598-10][@ant] refactor: десктоп-API Эпика 7 на строгие типы из SDK — variables передаём целиком data

Канон @coopenomics/sdk: каждый api-метод принимает `data: Mutations.<X>.<Y>.IInput['data']`
(или `Queries.<X>.<Y>.IInput['data']`) и передаёт его в variables объектом
целиком — `{ variables: { data } }`. Без раскладки полей вручную и без
самодельных интерфейсов аргументов.

Затронуто:
- OrdererReturnClaims/api: getReturnClaimSignablePayload + createReturnClaim
- OperatorReturnClaims/api: listReturnClaimsByBraname, approveReturnVisit,
  rejectReturnRemote, acceptReturnAtVisit, rejectReturnAtVisit
- SubmitReturnClaimDialog: defectCategory типизирован как enum
  Zeus.MarketplaceReturnClaimDefectCategory (раньше падал на string→enum,
  но был замаскирован ручной раскладкой полей в variables)
- OnSiteDecisionDialog: ReturnClaimPhotoUploadInput выведен из IAcceptReturnAtVisitInput
- OperatorReturnClaimsPage: listReturnClaimsByBraname({ delivery_braname })

Зачем — ревью PR #397: при ручной раскладке полей в variables.data
изменение схемы в SDK не подсвечивает call-site как ошибку TS;
регрессии маскируются. Канон даёт строгую сверку при изменении бэкенда.

---------

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* [598-11][@ant] feat: Эпик 8 «Списание скоропорта по решению совета» — перевод процесса на канонический паттерн soviet::createagenda с двумя документами

ОСНОВНОЕ ИЗМЕНЕНИЕ ОТ ПРЕДЫДУЩЕЙ ИТЕРАЦИИ:
- Предыдущая реализация Эпика 8 (PR #399 — reverted) шла «мимо совета»: backend
  напрямую дергал propwroff/execwroff/declwroff, в UI председатель «исполнял»
  и «отклонял» сам. Это не совпадало с каноническим паттерном принятия решений
  совета (приём в пайщики 100→501, паевой взнос имуществом 700→701, свободное
  решение 600, ResultContribution 1040→1041 — и т.д.).
- Текущая ветка переделана с нуля под канон: документ-Предложение (1106)
  подписывается председателем → soviet::createagenda(type=mktwroff) ставит
  повестку → совет голосует и авторизует Протокол (1105) → callback от soviet
  (onmktwoauth / onmktwodecl) фиксирует решение на цепи → backend per-item
  проводит execwroff.

C++ КОНТРАКТЫ:
- contracts/cpp/marketplace: добавлены actions onmktwoauth(coopname, hash,
  authorization) и onmktwodecl(coopname, hash, reason) с require_auth(_soviet).
  Сигнатуры совпадают с AUTHORIZE_CALLBACK_SIGNATURE и DECLINE_CALLBACK_SIGNATURE
  в soviet.hpp.
- contracts/cpp/marketplace: добавлен статус AUTHORIZED в WroffStatus; execwroff
  упрощён — больше не принимает протокол (он лежит в proposal.protocol после
  callback'а onmktwoauth), проверяет статус == AUTHORIZED.
- contracts/cpp/marketplace: удалён declwroff (его роль исполняет
  soviet::cancelexprd → callback onmktwodecl).
- contracts/cpp/lib: добавлен type "mktwroff"_n в consts; dispatch в
  soviet::exec.cpp идёт по default-branch на authorize_action_effect.
- p.mkt.wroff.standard.yaml: states/transitions/documents переведены под
  новый граф.

COOPTYPES + FACTORY:
- registry 1106.MarketplaceWriteoffStatement (Заявление председателя о списании)
  — подписывается перед propwroff + createagenda.
- registry 1105.MarketplaceWriteoffProtocol (Протокол совета о списании) —
  подписывается chairman'ом в стандартном sov.decision flow, ложится в
  wroffprops.protocol через callback onmktwoauth.
- factory/Actions + Templates для обоих registry; registry.ts обновлён.
- contracts/marketplace/actions: declWroff удалён, добавлены onMktWoAuth и
  onMktWoDecl; IExecWroff в interfaces/marketplace.ts больше не несёт protocol.

CONTROLLER:
- domain/infrastructure MarketplaceWriteoffProposal: статусы DRAFT → ON_AGENDA
  → AUTHORIZED → EXECUTING → EXECUTED | REJECTED; trigger cron/manual;
  decision_id для связки с soviet.decisions; items с inventory_id для
  крон-пометки записей WRITTEN_OFF после executed.
- partial-unique индексы запрещают параллельные DRAFT/ON_AGENDA per coopname.
- application/services/marketplace-writeoff.service: createDraft / updateDraft /
  cancelDraft / submitToCouncil (propwroff + createagenda) / onCouncilAuthorized
  (callback → AUTHORIZED → executeAuthorizedProposal) / executeAuthorizedProposal
  (цикл execwroff per-item) / onCouncilDeclined.
- detected proposal_hash = sha256('writeoff|coopname|cycle_started_at|draft_id|items').
- application/services/marketplace-writeoff-cron: ежемесячный сканер
  inventory.expiry_date; если config.writeoff.auto_proposal_enabled — формирует
  DRAFT-предложение по позициям LABELED с приближающимся сроком (грейс
  заданный конфигом), иначе только reminder председателю.
- infrastructure/inventory: новая колонка expiry_date (TypeORM synchronize),
  partial-индекс под крон-сканер; проставление при labeling
  (`labeled_at + Offer.warranty_days * 86400`).
- types.ts: IConfig.writeoff{auto_proposal_enabled, expiry_grace_days}.
- migrations/marketplace-bootstrap-v6: bump schema_version + merge writeoff
  defaults.
- domain/ports/marketplace-canonical-blockchain: propWroff / execWroff /
  createWriteoffAgenda. Adapter использует BlockchainService + VaultDomainService.
- application/dto/marketplace-writeoff.dto: enum'ы (StatusEnum / TriggerEnum)
  через registerEnumType; PaginatedMarketplaceWriteoffProposals на
  createPaginationResult; PaginationInputDTO в resolver.
- application/resolvers/marketplace-writeoff.resolver: marketplaceOpenWriteoffDraft,
  marketplaceListWriteoffProposals, marketplaceWriteoffProposal,
  marketplaceCreateWriteoffDraft (admin/manage_draft),
  marketplaceUpdateWriteoffDraft, marketplaceCancelWriteoffDraft,
  marketplaceWriteoffStatementSignablePayload (admin/propose, генерирует 1106),
  marketplaceSubmitWriteoffDraft (admin/propose, подписанное 1106 → propwroff +
  createagenda). marketplaceExecuteWriteoffProposal / marketplaceDeclineWriteoffProposal
  больше нет — это теперь делает совет.
- access-matrix: admin.Writeoff = ['manage_draft','propose','read:all'],
  board.Writeoff = ['decide','read:all'], board_readonly.Writeoff = ['read:all'].

SDK (@coopenomics/sdk):
- selectors/marketplace/writeoffSelector (item, decision_log, proposal,
  paginated) с MakeAllFieldsRequired-валидаторами.
- queries.marketplace: openWriteoffDraft, listWriteoffProposals,
  getWriteoffProposal, writeoffStatementSignablePayload.
- mutations.marketplace: createWriteoffDraft, updateWriteoffDraft,
  cancelWriteoffDraft, submitWriteoffDraft.

NOTIFICATIONS:
- 5 Novu workflows: marketplace-writeoff-draft-built (адресат:
  председатель/админ), -proposed (адресат: совет), -authorized, -executed,
  -rejected (адресат: админ/председатель). Liquid-синтаксис.
- Эмиты событий marketplace.writeoff.* добавлены в
  marketplace-notification.events.ts; listener-bridge wiring оставлен Phase 2
  (отдельный коммит, паттерн 598-20).

DESKTOP:
- pages/Marketplace/AdminWriteoffs/api: типизированные обёртки над
  @coopenomics/sdk.
- AdminWriteoffsPage.vue: open-draft + лента «в работе совета» +
  архив (EXECUTED/REJECTED).
- DraftEditorDialog: добавление/удаление/правка позиций с live-total.
- SubmitToCouncilDialog: превью Заявления 1106 → подпись DigitalDocument →
  submitWriteoffDraft (запускает propwroff + createagenda).
- WriteoffProposalDetailsDialog: детали, items.executed, decision_log.
- extensions/market/install.ts: маршрут /<coopname>/market/writeoffs для
  ролей chairman+member; mp-role-admin / --mp-font-body токены по канону.

ИЗВЕСТНЫЕ ОГРАНИЧЕНИЯ / PHASE 2:
- Listener-bridge `MarketplaceNotificationService` для каналов
  marketplace.writeoff.* остаётся на отдельный коммит (паттерн 598-20).
- Sync writeoff_proposals из chain delta (двусторонняя сверка decision_id ↔
  soviet.decisions) — Phase 2, если будет нужен reconciler.
- Крон в auto-proposal-mode проставляет amount=0 placeholder (нет
  attached unit-cost при labeling) — председатель руками поправит при ревью.
- Переуступка по сниженной цене — out of MVP.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-11][@ant] fix: ревью PR #399 — asset из config, стандарт на бизнес-языке, spec и docs-фабрика

- ASSET_DECIMALS/SYMBOL в writeoff.service / writeoff-cron.service берутся из
  MARKETPLACE_ASSET_CONFIG (DI), не хардкод (как в order-create.service)
- p.mkt.wroff.standard.yaml переписан без техжаргона (callback/soviet::exec/
  AUTHORIZE_CALLBACK_SIGNATURE убраны, эталон p.mkt.return)
- Заявление 1106 и Протокол 1105 включены в documents-1000-plus тест фабрики
  с моком getDecision для 1105
- spec для MarketplaceWriteoffService: createDraft/updateDraft/cancelDraft,
  computeProposalHash детерминистичность, submitToCouncil happy + 4 reject-кейса,
  onCouncilAuthorized/Declined, executeAuthorizedProposal per-item
- coopname в writeoff.resolver — из config.coopname (IMarketplaceCurrentMember
  не несёт coopname); статичный сигнатурный долг toDocument убран
- CreateAgenda путь — SovietContract.Actions.Decisions.CreateAgenda

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: ant <ant@noreply.local>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* [598-12][@ant] chore: process-hash-locator Phase B готов к активации marketplace

Эпик 9 / Story 9.3: обновлён комментарий к ключам p.mkt.supply / p.mkt.return /
p.mkt.wroff. Phase A через OPERATION_CODE_TO_PROCESS_TYPE уже связывает все
o.mkt.* с тремя process_type из cooptypes; Phase B ждёт появления entity-
таблицы marketplace::requests с полем request_hash (Locked Decision L10).
Заглушка [] остаётся корректной — Phase B наполняется одной правкой при
готовности C++.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-12][@ant] feat: operator-стол «Склад моего КУ» — Story 9.1

Лента marketplace_inventory отфильтрована по braname текущего КУ;
per-row статусы (LABELED / ISSUED / RETURNED / WRITTEN_OFF) через
mp-status-chip, фильтры по статусу/orderer, summary count by status
(activeOnly + writtenOff). Сортировка по labeled_at DESC по умолчанию.

Backend-доступ ограничивается guard'ом Warehouse: ['read:own-KU'] —
оператор видит только свой участок. Real-time подписки AR37 SDK
платформы подключаются отдельным шагом эпика (на MVP — refresh кнопкой).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-12][@ant] feat: admin-стол «Сводный склад» через WarehouseSummaryGrid — Story 9.2

Две вкладки admin-стола (UX-DR16 + DR31, mp-role-admin):
- «Сводный склад» — агрегация marketplace_inventory по ku × sku через
  canon-виджет WarehouseSummaryGrid (incoming = LABELED + RETURNED, outgoing
  = ISSUED, balance — транзитный остаток без ISSUED);
- «Поток заказов и поставок» — sums маркировок / выдач / возвратов /
  списаний + топ-10 SKU по обороту. Графики динамики (orders_per_day,
  supplies_per_day, writeoffs_per_month) подключаются по AR37 SDK-
  подпискам платформы отдельным шагом.

Backend-доступ Warehouse: ['read:all'] (chairman + member).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-12][@ant] feat: SDK обёртки processes + страница «История операций» — Story 9.5

SDK:
- selectors/processes/processViewSelector + processSummarySelector
  (полное покрытие ProcessView / ProcessSummary / Pagination)
- queries/processes/getProcess + listProcesses
- queries/index.ts: export * as Processes

Desktop pages/Marketplace/OperationsHistory:
- лента всех процессов с фильтрами по coopname / processType / username;
- разворот через стандартный core processRegistry.getProcess(process_hash)
  показывает actions, delta_history и связанные documents;
- кнопка «Скачать JSON» для аудиторской выгрузки конкретного процесса
  (AR36, L13 pull-модель).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-12][@ant] feat: UI-stub «Экосистема кооперативов» — Story 9.4

Раздел admin-стола под межкооперативную торговлю Phase 2 (NFR-Sc2 / AR18).
На MVP — заглушка с информацией о текущем кооперативе и явным сообщением
что список других controller'ов с marketplace появится после подключения
платформенного ecosystem_registry. Backend-регистрация controller через
core-API в отдельном Эпике 9 + техдолге platform (см. issue эпика).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-12][@ant] feat: маршруты market workspace под Эпик 9

Регистрирует 4 новые страницы Эпика 9 в desktop/extensions/market/install:
- market-pvz/warehouse — Story 9.1 «Склад моего КУ» (operator-стол КУ);
- market/warehouse-summary — Story 9.2 «Сводный склад» (admin-стол);
- market/history — Story 9.5 «История операций» (admin-стол);
- market/ecosystem — Story 9.4 «Экосистема» (admin-стол, MVP-stub).

Доступы соответствуют access-matrix marketplace: warehouse-стол КУ —
только chairman; сводный склад / история / экосистема — chairman +
member (board_readonly).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-12][@ant] chore: регенерация schema.gql / zeus / SDK Zeus + fix DTO writeoff

Source-of-truth GraphQL-схема была устаревшая после Эпика 8 — DTO
`MarketplaceWriteoffProposalItemDTO` использовал `?: string | null` без
explicit-`@Field(() => String)`, и Nest GraphQL падал при
`generate-schema` ⇒ Zeus в SDK не получил writeoff queries и Processes
из Story 9.5.

- DTO: пять nullable string-полей writeoff'а получили `@Field(() => String, { nullable: true })`.
- schema.gql регенерирован (`pnpm run generate-schema`).
- zeus обновлён в controller/zeus и sdk/src/zeus (`pnpm run generate-client`).
- В Zeus теперь видны: ProcessView / ProcessSummary / ProcessesFilter,
  MarketplaceWriteoffProposal / PaginatedMarketplaceWriteoffProposals и т.п.

После этого `cd components/sdk && pnpm run build` собирается без ошибок;
desktop потребители (AdminWriteoffs API + новая страница OperationsHistory)
получают типизированный доступ к Zeus.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-12][@ant] fix: enum-значения статусов + убрать as const в bucket аккумуляторе

После регенерации Zeus `MarketplaceInventoryStatus` стал nominal enum
вместо string-literal union — статусы передаются через
`Zeus.MarketplaceInventoryStatus.LABELED|ISSUED|RETURNED|WRITTEN_OFF`,
а не голыми строками.

В `AdminWarehouseSummaryPage.warehouseRows` убран лишний `as const` на
дефолтном bucket'е — он делал поля `in/out/balance` readonly и ломал
накопление через `b.in += qty`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-12][@ant] feat: Phase B locator p.mkt.* — реальные ссылки на marketplace::orders/retrequests/wroffprops

Story 9.3. Контракт marketplace опубликован в Эпике 11; все три marketplace-процесса
(supply / return / writeoff) уже имеют свои entity-таблицы с одноимённым
checksum256-полем `hash`. Pre-existing заглушка `[]` с упоминанием Locked Decision L10
снята: теперь Phase B end-to-end отдаёт текущий снэпшот процесса из blockchain_deltas.

  - p.mkt.supply -> marketplace::orders.hash
  - p.mkt.return -> marketplace::retrequests.hash
  - p.mkt.wroff  -> marketplace::wroffprops.hash

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-12][@ant] refactor: убрать дубль «Истории операций marketplace» — стол бухгалтера единая точка

Старая страница `pages/Marketplace/OperationsHistory` повторяла стол бухгалтера
(`extensions/reports`) — actions / delta_history / documents видны там в общем
реестре операций. По ревью к PR #400 удаляем полностью; вместо неё marketplace
будет инжектировать бизнес-описание процесса в стол бухгалтера через фабрику
(см. следующие коммиты).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-12][@ant] feat: фабрика processInfoFactory и три marketplace info-widget'а — слот в стол бухгалтера

Аналог DecisionFactory, но для бизнес-описания процесса в реестре операций
стола бухгалтера. Каждое расширение регистрирует свой компонент по `process_type`;
стол бухгалтера рендерит его в развёрнутой строке после проводок и движений
по кошелькам. Дубля «истории» больше нет.

  - shared/lib/process-info-factory + types: реестр + getInfoComponent
  - widgets/Marketplace/ProcessSupplyInfoWidget — заказчик, поставщик, КУ,
    состояние, единицы; deep-link «открыть заказ на столе ПВЗ»
  - widgets/Marketplace/ProcessReturnInfoWidget — заказчик, состояние,
    исходный заказ, причина; deep-link «открыть заявление на столе ПВЗ»
  - widgets/Marketplace/ProcessWriteoffInfoWidget — источник проекта (cron/manual),
    состояние, цикл, сумма; deep-link «открыть проект на столе списания»
  - extensions/market/app/extensions.ts — регистрация по `LEDGER2_PROCESS_REGISTRY.name`
    (SUPPLY/RETURN/WRITEOFF), без string-литералов в коде
  - extensions/reports/pages/OperationsPage — слот «Содержание процесса» в развороте

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-12][@ant] style: страницы Эпика 9 на <template lang="pug"> + русские лейблы — снять техжаргон из UI

Привожу страницы к канону стола заказов: PUG-шаблоны, бизнес-словарь
в подписях/фильтрах (Состояние / Кооперативный участок / Заказчик),
никаких `p.mkt.*` или raw-имён enum-значений в видимой части. Внутри —
строгая типизация через `Zeus.MarketplaceInventoryStatus`, switch на
status-полях больше не сравнивается со строками.

  - OperatorOwnWarehouse (Story 9.1) — PUG, enum-fix, человеческие лейблы
  - AdminWarehouseSummary (Story 9.2) — PUG, enum-fix, человеческие лейблы
  - EcosystemRegistry (Story 9.4) — PUG

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-12][@ant] refactor: вынести запрос процесса в entities/Process + чистая типизация в info-widget'ах

По канону Ledger2: api/store/types. Widget'ы больше не вызывают client.Query
и не делают cast'ов вида `as Record<string, unknown>` — снэпшот приходит
строго типизированным `IProcessSnapshot` из SDK через `useProcessStore`.

  - entities/Process/types — IProcessView / IProcessSnapshot / IProcessDelta
    выведены из Queries.Processes.GetProcess.IOutput
  - entities/Process/api — getProcess + pickLatestSnapshot (единственная
    точка приведения JSON-скаляра)
  - entities/Process/model — Pinia store с loadProcess / loadLatestSnapshot
  - widgets/Marketplace/Process{Supply,Return,Writeoff}InfoWidget — используют
    processStore, типы из entities/Process

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-12][@ant] style: убрать пользовательский техжаргон со сводного склада и экосистемы

  - AdminWarehouseSummary: удалить сноску про SDK-подписки и формулу остатка —
    пользователю кооператива это не нужно
  - EcosystemRegistry: оставить короткую фразу «включится позже», без режима
    просмотра и упоминаний платформенного реестра

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [598-12][@ant] docs: зафиксировать паттерн фабрик инжекции в стол совета и стол бухгалтера

Новый документ `components/desktop/docs/extension-factories.md`:
- общий принцип «общие столы единственная точка просмотра, расширения
  инжектируют бизнес-описание»
- DecisionFactory: контракт, пример регистрации (capital → стол совета)
- ProcessInfoFactory: контракт, пример регистрации (market → стол
  бухгалтера) с использованием LEDGER2_PROCESS_REGISTRY вместо
  строковых литералов
- чек-лист для нового расширения (виджет/PUG → app/extensions.ts →
  install.ts → реестр в cooptypes)
- что делать НЕЛЬЗЯ: повторять у себя реестры операций/проводок/повестки
  и пихать техжаргон в infoComponent

В README desktop — ссылка из раздела «Система расширений».

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: ant <ant@noreply.local>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Эпик 11: «Реестр процессов» на столе бухгалтера (598-14)

UI-надстройка над уже работающим Phase A/B ProcessRegistry в core controller:
страница /reports/processes показывает листинг процессов ledger2 с фильтрами
по типу процесса (LEDGER2_PROCESS_REGISTRY) и пайщику, при раскрытии строки
рисует events + documents процесса (через getProcess), отдаёт слот
processInfoFactory для бизнес-описания (marketplace handlers уже регистрируют
SUPPLY/RETURN/WRITEOFF в extensions/market/app/extensions.ts) и кнопку
deep-link «Открыть в реестре операций» (filter process_hash).

Используется existing SDK query `Queries.Processes.ListProcesses` —
расширены entities/Process api+store+types методом `listProcesses` и
type alias'ами IProcessListInput/IProcessListResult/IProcessSummary.

Стол бухгалтера приобретает четвёртый базовый реестр (операции, проводки,
**процессы**, кошельки, счета) — закрывает аудит-видимость marketplace
flow «Стола заказов» в рамках Эпика 11.

* Эпик 11 техдолг 598-21: batch-маркировка партии за один вызов

Mutation `marketplaceLabelShipmentInventory(shipment_id, default_strategy?,
per_order_overrides?)` массово маркирует все Order'ы партии поставки —
оператор перестаёт проходить N карточек по одной.

Service `MarketplaceInventoryLabelService.labelShipment`:
  1. Shipment.findById → берёт coopname/cycle_id/braname.
  2. orderRepo.findByCycleId(coopname, cycle_id) → filter по
     delivery_braname партии = все Order'ы партии.
  3. Для каждого Order'а зовёт уже работающий execute() — единый
     источник правил маркировки (стратегия из Offer.barcode_strategy
     по 598-22, per-Order override для смешанных Offer'ов в партии).
  4. Идемпотентность: ConflictException от execute() при повторной
     маркировке → skipped_order_ids в результате (повторный заход на
     страницу не валится fatal'ом).

Возврат включает labeled_order_ids + skipped_order_ids + сами наклейки
(`MarketplaceInventoryItem[]`) — UI оператора может одним вызовом
получить grid этикеток для печати и понимать что было сделано.

`@media print` grid layout и расширение `OperatorInventoryLabelingPage`
под выбор default-стратегии на всю партию — следующий шаг, mutation
теперь доступна.

* Эпик 11 Stories 11.2 + 11.3: off-chain инварианты ledger2 marketplace + coverage трассировки 13 op-кодов

Backend-side scaffold контрактных проверок Stories 11.2 (Journal/wjournal
трассировка) + 11.3 (CI-инварианты ledger2). Реализация — pure-функциями
без NestJS/PG: работают на синтетических массивах Ledger2OperationDTO,
гонятся unit-тестом без реального chain'а (отвечает требованию off-chain
агрегации: никаких сумм по всем кошелькам пайщиков в смарт-контракте).

Состав:

- `marketplace-ledger2-invariants.ts` — 6 pure-агрегаторов (I1 payout
  balance, I2 Δ86, I3 acc.10, I4 acc.91 transit = 0, I5 blocked в
  w.mkt.member, I6 нет orphan o.mkt.block). Та же логика что UI стола
  бухгалтера (`/reports/operations`, `/reports/postings`).
- `marketplace-ledger2-invariants.spec.ts` — 86 тестов: happy path +
  violation path для каждого I1..I6 + property-based на 50 случайных
  последовательностях (mix happy/cancel/return/writeoff).
- `marketplace-process-trace-coverage.spec.ts` — 10 групп: coverage по
  всем 13 marketplace operation_code (12 o.mkt.* + o.wal.conv) в
  cooptypes + OPERATION_CODE_TO_PROCESS_TYPE + PROCESS_HASH_LOCATOR;
  Phase-A якорь связности apply+walletop+debit+credit под одним
  process_hash; full-flow scenarios для supply/return/wroff.

Контрактные тесты в Antelope simulator (mono-ai-5) остаются за другим
агентом; здесь — backend-side mapping контроль и off-chain агрегация
инвариантов, доступные для CI и admin-вью.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: ant <ant@noreply.local>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* [598-14][@ant] feat: дозакрытие техдолгов Эпика 11 — FR45 AC5 jest-тесты + 598-21 frontend batch-flow

— FR45 AC5 (598-15): jest unit-тесты compensating-rollback signSupp/signChair
  с mock chainPort failure → status не сдвинут + idempotent retry.
  6 тестов в marketplace-apl-reception.service.spec.ts (76 сек локально).
— 598-21 frontend batch-flow в OperatorInventoryLabelingPage:
  тогл «Маркировка партии целиком / Поштучно по заказу», выбор default
  стратегии и формата, селектор партии из marketplaceListShipments,
  идемпотентность через skipped_order_ids в toast, A4 print grid в
  @media print, page-break-inside: avoid на этикетку.
— SDK добавлена mutation marketplaceLabelShipmentInventory + селектор
  результата (labeled_order_ids / skipped_order_ids); zeus regen из
  controller schema.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* [598-14][@ant] fix: добавить _validate typeguard на marketplaceLabelShipmentInventoryResultSelector

Селектор должен проходить compile-time проверку MakeAllFieldsRequired<ValueTypes[...]>
как все остальные селекторы в проекте; добивает review-комментарий PR #408.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: ant <ant@noreply.local>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* chore(release): publish

* chore(release): publish

* ci: атомарный release.yaml, убрать workflow_run-связку (#366)

build-contracts + build-containers через workflow_run упёрлись в:
(а) default-branch caveat (новая логика не активна, пока не в main),
(б) `${{ github.event.workflow_run.head_sha }}` иногда пуст в YAML-
выражениях — описание см. в шаге Resolve tag, инцидент v2026.5.14
не дёрнул PRODUCTION_WEBHOOK_URL.

Замена — один `release.yaml` на push тэга `v*`: резолвит ветку через
`git branch --contains`, собирает контракты → пушит
`dicoop/contracts:<branch>`, собирает базу + сервисные образы →
пушит `dicoop/<svc>:<tag>`, шлёт webhook. Гонок нет by construction.

`build-contracts.yaml` остаётся только на push веток для CI-
обновления `dicoop/contracts:dev|testnet|main` без релизного тэга.
Триггер `tags: ['*']` и логика резолва ветки через --contains
оттуда удалены — это теперь забота release.yaml.

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* chore(release): publish

* chore(release): publish

* ci: tag-only триггеры для build-contracts и docs (#367)

build-contracts.yaml — только workflow_dispatch (ручная пересборка
`dicoop/contracts:<branch>` для отладки на dev-ноде). Тэги обрабатывает
release.yaml атомарно (контракты + контейнеры + webhook), отдельная
сборка по push'у в ветку только давала вторую параллельную сборку.

publish-docs.yaml и build-contracts-docs.yaml — на push:tags v* с
gate-job'ом, пропускающим только продакшн-тэги (без -alpha/-beta/-rc/
-test) на main. Раньше docs пересобирались на каждый push в
main/testnet/dev/reports/marketplace2 — впустую.

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* fix(capital/commit): nullable description/meta в CommitOutputDTO + не дефолтить satisfaction в 5

Если syncCommit не дождался delta из блокчейна, interactor возвращает DB-only
entity, где description/meta остаются undefined — non-nullable GraphQL field
ломал ответ мутации capitalCreateCommit. Сделал оба поля nullable.

UI CreateCommitButton.vue: satisfaction_stars=0 по умолчанию, label "не указано"
пока пользователь не выбрал; блок contribution_feedback в payload только если
stars >= 1 или review_text непустой.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix commit process

* chore(release): publish

* fix(file-storage): MinIO стартует только при заданном MINIO_ENDPOINT

Раньше MINIO_ENDPOINT имел default http://minio:9000, и на проде без
minio-контейнера контроллер падал на bootstrap в HeadBucket с
getaddrinfo ENOTFOUND minio — Nest application не поднимался вообще.

Теперь MINIO_ENDPOINT optional без default; адаптер хранит enabled-флаг
по наличию endpoint и при отсутствии — onApplicationBootstrap логирует
warning и возвращается без сетевых вызовов. Любая попытка getBucket /
fetchObjectForReadProxy кидает InterFileStorageBackendUnavailableError
с понятным сообщением.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(release): publish

* chore(release): publish

* fix(capital/time-tracking): личные доли estimate + revert при decline (#387)

* chore(release): publish

* chore(release): publish

* ci: атомарный release.yaml, убрать workflow_run-связку (#366)

build-contracts + build-containers через workflow_run упёрлись в:
(а) default-branch caveat (новая логика не активна, пока не в main),
(б) `${{ github.event.workflow_run.head_sha }}` иногда пуст в YAML-
выражениях — описание см. в шаге Resolve tag, инцидент v2026.5.14
не дёрнул PRODUCTION_WEBHOOK_URL.

Замена — один `release.yaml` на push тэга `v*`: резолвит ветку через
`git branch --contains`, собирает контракты → пушит
`dicoop/contracts:<branch>`, собирает базу + сервисные образы →
пушит `dicoop/<svc>:<tag>`, шлёт webhook. Гонок нет by construction.

`build-contracts.yaml` остаётся только на push веток для CI-
обновления `dicoop/contracts:dev|testnet|main` без релизного тэга.
Триггер `tags: ['*']` и логика резолва ветки через --contains
оттуда удалены — это теперь забота release.yaml.

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* chore(release): publish

* chore(release): publish

* ci: tag-only триггеры для build-contracts и docs (#367)

build-contracts.yaml — только workflow_dispatch (ручная пересборка
`dicoop/contracts:<branch>` для отладки на dev-ноде). Тэги обрабатывает
release.yaml атомарно (контракты + контейнеры + webhook), отдельная
сборка по push'у в ветку только давала вторую параллельную сборку.

publish-docs.yaml и build-contracts-docs.yaml — на push:tags v* с
gate-job'ом, пропускающим только продакшн-тэги (без -alpha/-beta/-rc/
-test) на main. Раньше docs пересобирались на каждый push в
main/testnet/dev/reports/marketplace2 — впустую.

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* fix(capital/time-tracking): личные доли estimate, partial-split, revert при decline

Три бага в распределении билетов времени, вскрытые на прод-инциденте voskhod
(проект CC7-1 «Концепция», estimate=15 ч, 3 creators):

БАГ #1 — recalcDoneEstimatesForContributorProject раздавал «общий остаток пула»
(estimate − committed_total) / N всем creators поровну. Закоммитивший свою долю
получал её ещё раз, остальные — урезанную (15/3=5 → после committed 5 у одного
становилось 10/3=3.33 у каждого, включая того кто уже закоммитил).

Фикс: личная доля = estimate/N − собственный committed estimate. Введён общий
helper redistributeIssueEstimateEntries, который используют и applyExplicit-
EstimateToTimeEntries (force=true), и recalcDoneEstimates (force=false с
no-op оптимизацией если раскладка уже совпадает с планом).

БАГ #2 — commitTime при partial split (entry.hours > requested) создавал новую
committed-запись без entry_type и estimate_snapshot. По default'у БД сохраняла
её как entry_type='hourly', что ломало последующий recalc (он фильтрует только
entry_type='estimate'). Фикс: явно копировать entry_type и estimate_snapshot
из оригинального entry.

БАГ #3 — declineCommit / handleDeclineCommit меняли только commit.status в БД,
но не возвращали time-entries в is_committed=false. После отказа мастера часы
оставались в total_committed_hours и не возвращались в available_hours. Фикс:
новый метод revertEntriesForDeclinedCommit в TimeTrackingInteractor + методы
findCommittedByCommitHash / revertCommittedEntriesByCommitHash в TimeEntry-
Repository. После revert делается force=true redistribute для затронутых DONE
задач, чтобы доли вернулись в норму.

Покрытие тестами: 17 unit-тестов в time-tracking.interactor.spec.ts с
регрессионными сценариями под каждый из трёх багов плюс integration-сценарий
полного lifecycle CC7-1 (estimate → коммит → decline → revert).

---------

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* fix commit process

* chore(release): publish

* chore(release): publish

* feat(controller): is_server_init флаг в initSystem для provider-overwrite (#391)

* feat(docs-harness): onboarding 01..06 — визуальная цепочка регистрация → активация

Шесть сценариев visual docs-harness, покрывающих полный путь подключения
нового кооператива через провайдера Восход:

  01-register-coop                 — регистрация кооператива-клиента
  02-sign-and-submit               — подпись заявления о вступлении + PayInitial
  03-operator-approve              — chairman принимает заявку в реестре одобрений
  04-sign-connection-agreement     — Партнёр-1 видит ConnectionAgreementStepper
                                     (на текущем стенде получаем заглушку
                                     /signup, пока пайщик не принят)
  05-activate-from-registry        — оператор открывает карточку инстанса
                                     в provider-frontend, выбирает preset
  06-wait-instance-active          — pending → ACTIVE (overrideInstance
                                     для имитации финального статуса в шоте)

Обвязка:
  • lib/registrator-signup.mjs — переиспользуемый helper подписания.
  • lib/harness.mjs — расширенный dismissOnboardingDialogs (Положение ЦПП
    и связанные модалки chairman'а).
  • state/cooperatives/{,.gitkeep} — папка для фикстур; partner1.json
    (с приватным wif) игнорируется (.gitignore обновлён).

ОГРАНИЧЕНИЕ: в 06 финальный ACTIVE — это playwright-override JSON, а не
реальный POST /instances/activate с боевым Ansible до testnet300.coopenomics.world.
Реальный E2E (аренда VM на Hostkey + поднятие кооператива на домене)
будет следующим шагом Эпика 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(controller): is_server_init flag в initSystem для разблокировки provider-overwrite

Поле data.is_server_init: boolean (optional) в InitDTO + домейн-интерфейсе.
Если true (вызов от провайдера через server-secret) — coopback ставит
init_by_server=true безусловно, даже если до этого пользователь успел
заполнить визард первым (user-init). Это разблокирует ситуацию, когда
провайдер не успел вызвать initSystem до того как chairman открыл
/install — следующий callInitSystemMutation от провайдера перезапишет
organization_data и пометит её readonly для визарда.

Без флага сохраняется прежняя логика: первая инициализация — серверная,
повторная наследует флаг.

Инцидент 2026-05-18 на partner1: при первой установке provider вообще
не успел/не сходил в callInitSystemMutation, визард пользователя
проинициализировал систему как user-init (init_by_server=false), визард
2-го захода не предзаполнил форму. С этим фиксом следующий вызов
provider'а поднимет флаг и фикстура встанет на место.

---------

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(capital/convert): унифицировать шаблон 1080, убрать 1081/1082 (#392)

* refactor(capital/convert): унифицировать 1080 как универсальное заявление о конвертации, убрать 1081/1082

Шаблон 1080 (GenerationConvertStatement) уже технически универсален — содержит
обе суммы (main_wallet_amount/blagorost_wallet_amount) и условные блоки в
context. Шаблоны 1081 (GenerationToProjectConvertStatement) и 1082
(GenerationToCapitalizationConvertStatement) были недоделанными заглушками
без полей и нигде не подключены в UI.

Изменения:
- cooptypes: 1080 переименован GenerationToMainWalletConvertStatement →
  GenerationConvertStatement; title/description нейтральные. 1081/1082 удалены.
- factory: Template + Action 1080 переименованы; в Action добавлено
  super.formatAsset(...) для обеих сумм (как в Action 1020). 1081/1082 удалены.
- controller: DTO переименован; appendix_hash убран из generate-input и
  перенесён в signed-meta-input; добавлен enrich appendix_hash через
  AppendixRepository.findConfirmedByUsernameAndProjectHash в
  DistributionManagementInteractor.prepareGenerationConvertStatementData
  (по образцу InvestsManagementInteractor для 1020). Резолвер мутации
  переименован в capitalGenerateGenerationConvertStatement; убраны два
  резолвера 1081/1082. mutation-log-mapper обновлён.
- sdk: мутация переименована, две удалены, zeus regenerated.
- desktop: Distribution-фичи 1081/1082 удалены, 1080-фича переименована.
  ConvertSegment теперь шлёт project_hash + обе суммы (formatToEosioAsset) +
  to_wallet/to_blagorost; appendix_hash подтягивается на бекенде.
- controller schema.gql regenerated.

registry_id=1080 не меняется — on-chain контракт convertsegm не сверяет
registry_id, миграций БД/контракта не требуется.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(capital/convert): применить ревью — title «трансляция паевого взноса из программы Генерация»

По комментарию ревью в PR #392 (строка 37): принять доменный термин
«трансляция паевого взноса» (перенос между программами) вместо
«конвертация»; description согласован в том же стиле.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(capital): подтянуть 3 ссылки на GenerationConvertStatement → MoneyInvestStatement (#394)

PR #392 переименовал Cooperative.Registry.GenerationConvertStatement в
GenerationMoneyInvestStatement в @coopenomics/document, но в controller
осталось 3 несинхронизированные ссылки:

- distribution-management.service.ts:56 — registry_id метода generation
- distribution-management.interactor.ts:48,66 — тип Action в return и as-cast
- generation-convert-statement-document.dto.ts:12 — type action в DTO

Coopback падал на ts-node compile (TS2551/2724), что блокировало старт
всего dev-stack после reboot dev-chain 2026-05-18.

Co-authored-by: coopops <coopos@coopenomics.world>

* fix(controller): IS_UNIONED zod-парсер принимает string из .env (#395)

z.boolean().default(true) валится для переменной из .env, потому что
process.env всегда отдаёт строку: zod не приводит "true"/"false" к
boolean, в результате validateEnv падает с «IS_UNIONED: параметр не
установлен» и coopback не стартует, если в .env стоит IS_UNIONED=false
(стандартный dev-обход messenger-гейта, см. flow подключения партнёра).

Заменено на string().default('true').transform(v => v === 'true') —
сохранение прежнего default=true и поддержка string-форм из env.

Co-authored-by: coopops <coopos@coopenomics.world>

* fix(capital): revert ошибочной замены GenerationConvertStatement → MoneyInvestStatement (#394) (#396)

PR #394 «починил» TS-ошибки coopback заменой типа `Cooperative.Registry.GenerationConvertStatement`
на `GenerationMoneyInvestStatement` в 3 файлах controller'а. Это семантически неверно:

- 1080 GenerationConvertStatement — заявление о трансляции паевого взноса
  (поля: project_hash, main_wallet_amount, blagorost_wallet_amount, to_wallet, to_blagorost, appendix_hash)
- 1020 GenerationMoneyInvestStatement — заявление о денежном паевом взносе по программе Генерация
  (совсем другой набор полей)

DTO BaseGenerationConvertStatementMetaDocumentInputDTO декларирует поля 1080, но `implements ExcludeCommonProps<action>`
где action = тип 1020 → TS2352 на as-cast в interactor, потому что Action'ы не пересекаются по полям.

Реальная причина исходных ошибок coopback после #392 — несвежий dist `@coopenomics/cooptypes`
на dev-узле (старое имя символа). Лечится пересборкой пакета, не переименованием ссылок.

Изменено (откат #394):
- generation-convert-statement-document.dto.ts:12 — `action = ...GenerationConvertStatement.Action`
- distribution-management.service.ts:56 — `registry_id: ...GenerationConvertStatement.registry_id`
- distribution-management.interactor.ts:48,66 — `Promise<...GenerationConvertStatement.Action>`

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* fix(capital/convertsegm): регистрировать convert_statement в реестре документов как completed (#403)

Финальная фаза процесса p.cap.rid (convertsegm) принимала document2 convert_statement (шаблон 1080)
параметром, но:

1. **Не проверяла подпись** — `verify_document_or_fail` отсутствовала, поэтому on-chain
   принял бы любую сконструированную document2 без валидной user-подписи.
   У signact1/signact2 (соседние фазы того же процесса) verify есть — здесь забыли.

2. **Не регистрировала документ в реестре** — `newlink`/`newsubmitted`/`newresolved`
   не вызывался, заявление о трансляции 1080 «терялось»: off-chain controller (process_instance)
   не видел финальный документ привязанным к result_hash, процесс p.cap.rid не помечался completed.
   У pushrslt (create_approval) и signact2 (newlink с SIGN_ACT2_RESULT) линковка есть.

Канон есть в soviet/src/system/converttoaxn.cpp:54 и soviet/src/agreement/sndagreement.cpp:104:
паттерн `Soviet::make_complete_document(calling_contract, coopname, username, action, package_hash, document)`
шлёт newsubmitted + newresolved одной парой, package = анкер процесса (здесь result_hash).

Изменено:
- names.hpp: новая константа Names::Capital::CONVERT_SEGMENT = "convertsegm"_n (12 символов)
- convertsegm.cpp:
  - verify_document_or_fail(convert_statement, {username}) сразу после require_auth
  - Soviet::make_complete_document(...) ДО delete_result (чтобы линковка прошла, пока result_hash ещё анкер)

Контракт capital собирается без ошибок (cdt-cpp testnet mode), warnings — старые ricardian.

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* feat(epic-0): onboarding harness 01-08 + install bundle adduser+createBoard в одну tx (#398)

* fix(controller): IS_UNIONED zod-парсер принимает string из .env

z.boolean().default(true) валится для переменной из .env, потому что
process.env всегда отдаёт строку: zod не приводит "true"/"false" к
boolean, в результате validateEnv падает с «IS_UNIONED: параметр не
установлен» и coopback не стартует, если в .env стоит IS_UNIONED=false
(стандартный dev-обход messenger-гейта, см. flow подключения партнёра).

Заменено на string().default('true').transform(v => v === 'true') —
сохранение прежнего default=true и поддержка string-форм из env.

* feat(epic-0): partner onboarding harness — signin → sign agreements → connect

Что добавлено:
- desktop/quasar.config.cjs: vite server.allowedHosts для voskhod-dev/
  partner-dev/api-dev (Vite 5.4+ блокирует cross-origin Host без явного
  списка — иначе SSR/HMR-сервер возвращает 403 «Blocked request»).
- docs-harness/lib/harness.mjs: helper signOnboardingAgreements —
  реальная подпись каскада SignAgreementDialog (wallet/signature/
  privacy/user), каждый клик отправляет sendAgreement → wallet::signagree
  on-chain. В отличие от dismissOnboardingDialogs делает on-chain эффект
  (см. inc 2026-05-18: stale vault SERVER_SECRET → bad decrypt).
- scenarios/onboarding/01..07: новый partner-flow Эпика 0 — partner1
  заходит в Восход, подписывает 4 типовых соглашения, идёт на «Подключение»,
  ant одобряет, наблюдаем установку partner-dev.
- scenarios/registration/01..04: архив старого registration-doc как
  отдельная история (см. mem feedback_provider_mono_pr_flow).
- scripts/debug-*: вспомогательные скрипты для портал-структуры и
  chairman onboarding'а.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(docs-harness/08): chairman install wizard на partner-dev

Сценарий 08 проходит установочный wizard /:coopname/install целиком:
шаг 1 (RequestKeyForm: WIF partner1 из state/cooperatives/partner1.json),
шаг 2 (SetInitForm: readonly orgdata из is_server_init=true, «Далее»),
шаг 3 (SetSovietForm: один председатель — Иванов И.И.),
шаг 4 (SetVariablesForm: ОПФ+ во всех падежах, устав, конф.email).

Финальный submit «Завершить установку» сейчас падает on-chain в
soviet::create — `assertion: Один из аккаунтов не найден в реестре
пайщиков`. Причина: в install.interactor.ts adduser и createBoard
шлются двумя отдельными tx; partner1 nodeos не producer, между tx есть
лаг p2p-репликации, createBoard приходит до того как participants[N]
обновился. Шот 06-error-state снимается при таймауте; шот
06-completed появится после фикса race в install.interactor (отдельный
коммит).

Сценарий запускается:

  BASE_URL=https://partner-dev.coopenomics.world COOPNAME=partner1 \
  node run.mjs onboarding/08-chairman-install-on-partner-dev

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(controller/install): bundle adduser+createBoard в одну tx

Cause. install.interactor.ts шлёт adduser×N и createBoard двумя отдельными
tx через BlockchainService. На production-нодах с producer-схемой это
работает (lag реплицирования минимальный), но на dev-loop'е partner-
coopback соединён со своим nodeos, который p2p-репликой подтягивает
блоки от producer'а — после accept'а adduser в local state ещё нет
soviet::participants[username] к моменту push'а createBoard. Контракт
soviet::createboard падает «Один из аккаунтов не найден в реестре
пайщиков».

Fix. Объединил все adduser-action'ы и createBoard-action в одну tx
через новый метод BlockchainPort.installSoviet(). Обе action'ы теперь
атомарны в одном блоке — soviet::addpartcpnt (inline action от
adduser) обновляет participants и createBoard видит запись сразу же
в том же блоке.

Поток в install.interactor.ts перестроен в два шага:
  1. Цикл по soviet: createUser в БД + setupNotificationSubscriber +
     сбор addUserActions[] и members[] (без on-chain активности).
  2. installSoviet(addUserActions, createBoardData) — одна tx.

Catch при ошибке on-chain (как и раньше) откатывает users из БД.

Зачем. Закрывает блокер сценария 08-chairman-install-on-partner-dev:
без этого финальный экран wizard'а «Установка завершена» недостижим
на dev-loop'е (Эпик 0 не закрывается).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(docs-harness/09): invite-token → WIF → signin chairman

Закрывает Эпик 0: после сценария 08 (chairman install wizard) на
partner1 в Postgres появляется invite-токен председателя, но Novu не
доставит его на @example.com адрес. Сценарий 09 идёт за токеном
напрямую в БД через `ssh partner1 → docker exec postgres → psql` и
прогоняет до финального signin под новым ключом.

Шаги:
1. fetchLatestInviteToken — SELECT token FROM tokens WHERE
   type='invite' AND blacklisted=false AND expires > NOW() LIMIT 1.
2. Открываем `${BASE_URL}/${COOPNAME}/auth/invite?token=<token>` —
   widget Invite.vue клиентски генерирует новый WIF (generateAccount).
3. Извлекаем WIF из q-input, сохраняем в
   state/cooperatives/partner1-chairman.json для последующих сценариев.
4. Чекбокс «Я сохранил ключ» → «Установить ключ» → resetKey шлёт
   on-chain ChangeKey, фронт редиректит на /auth/signin.
5. Финальный signin под chairman.partner1@example.com + новый WIF;
   ждём перехода на /chairman или /participant.

Запускается:

  BASE_URL=https://partner-dev.coopenomics.world COOPNAME=partner1 \
    node run.mjs onboarding/09-chairman-key-and-login

Требует SSH-доступа к partner1 (PARTNER_SSH=user1@91.218.246.46 по
умолчанию) и предварительно прогнанный сценарий 08 (после merge
fix(controller/install) — иначе токен не появится).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(controller/install): откатить installSoviet bundle на sleep 2s

Bundle adduser×N + createBoard в одну tx работает, но требует расширения
BlockchainPort и больше read'а; для не-producer-нод (dev-loop) достаточно
короткой паузы между adduser и createBoard, чтобы p2p-реплика подтянула
блок с participants. 2с гарантированно перекрывают и prod (~50ms), и
dev-loop (1-3с).

Отменён 3f2ecc42cf (installSoviet в blockchain.port + blockchain.service +
install.interactor), добавлен `await new Promise(setTimeout, 2000)` между
циклом adduser и createBoard.

Это однострочный фикс race-condition; bundle вернётся когда понадобится
многошаговая атомарность.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: coopops <coopops@coopenomics.world>

* fix(asset-formatting): truncate (toward-zero) до 2 знаков вместо math-round (#404)

UI показывал баланс 99999.9999 RUB как 100 000,00 — пользователь видел
больше, чем реально на кошельке, и попытка инвестировать «весь баланс»
падала в walletop TRANSFER с insufficient funds (precision=4 на цепи
vs precision=2 в UI).

Принцип: «никогда не показывать сумму больше реальной». Truncate
выполняется на уровне строки, без преобразования в Number, чтобы
избежать FP-погрешности (0.29 * 100 = 28.999... в IEEE 754).

Изменения:
- desktop: новая утилита floorDecimalString — единый источник истины.
- desktop: formatAsset2Digits / formatToAsset / addAssets используют floor.
- desktop: убран double-formatting amount.toFixed(4) перед formatAsset2Digits
  в ParticipantWalletsPage.
- factory: Factory.formatAsset / Factory.formatShare через приватный
  truncateDecimal — тот же принцип в документах (заявления, акты).

Не затронуто (отдельный фолоу-ап): 10 файлов с inline .toFixed(4)
перед отправкой в контракт — рекомендую заменить на formatToAsset().

Co-authored-by: coopops <coopos@coopenomics.world>

* feat(capital): требовать допуск для просмотра артефактов проекта/компонента (#407)

Любой авторизованный пользователь раньше мог запросить capitalStories /
capitalStory и получить требования всех проектов кооператива. Закрываем эту
лазейку на бекенде и показываем аккуратную заглушку на фронте.

Backend (controller):
- GenerationService.getStories: фильтрует список разрешённых project_hash
  через PermissionsService. Допуск к корневому проекту каскадно открывает
  все его компоненты; допуск к одному компоненту — только этот компонент.
  Запрос без project_hash/issue_hash доступен только chairman/member.
- GenerationService.getStoryByHash: проверяет допуск через project_hash
  (или issue.project_hash для story с issue_hash) и возвращает null без
  доступа.
- getCapitalStories / getCapitalStory резолверы теперь получают currentUser.
- ProjectPermissionsOutputDTO: добавлены has_parent_clearance и
  can_view_artifacts — для отображения заглушек на фронте.

Frontend (desktop):
- ProjectRequirementsPage / ComponentRequirementsPage: показывают
  ArtifactsAccessPlaceholder когда can_view_artifacts=false, с кнопкой
  получения допуска (или PendingClearanceButton при поданном запросе).
- Новый shared компонент ArtifactsAccessPlaceholder в стиле остальных
  заглушек расширения capital.

SDK:
- projectSelector обновлён под новые поля; schema.gql / zeus
  регенерированы.

Co-authored-by: coopops <coopos@coopenomics.world>

* fix(capital/convertsegm): линковать 1080 к пакету через newlink, не make_complete_document (#409)

PR #403 использовал Soviet::make_complete_document — это вытащило заявление о трансляции (1080)
на верхний уровень реестра как самостоятельный документ. В разделе «Документы» 1080 оказался
отдельной строкой, под ним болтались протокол+акт из соседнего пакета (агрегатор подтягивал их
по тому же result_hash без главного документа).

Правильно — линковать 1080 к существующему пакету процесса p.cap.rid, где ведущим документом
является заявление о внесении РИД (1040, statement из pushrslt). Канон уже есть в signact1/signact2:

  Action::send<newlink_interface>(_soviet, "newlink"_n, _capital, coopname, username,
                                  Names::Capital::SIGN_ACT*_RESULT, result_hash, act);

Здесь по тому же паттерну, action = Names::Capital::CONVERT_SEGMENT (константа уже в dev из #403,
её не трогаем), package = result_hash. Off-chain controller добавит 1080 в группу пакета 1040,
а не на верхний уровень.

verify_document_or_fail оставлен — это безопасность, не связана с проблемой реестра.

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

* migration complete

* [598-14][@ant] fix(desktop): vue-tsc на marketplace2 после merge'ов

— OperatorInventoryLabelingPage: BarcodeStrategy/Format через Zeus.MarketplaceBarcode*
— OperatorOwnWarehousePage: pug-tернарник в одну строку (vue-tsc не парсит мультиline | text-block)
— AdminWriteoffsPage: statuses[] через Zeus.MarketplaceWriteoffProposalStatus, trigger === CRON
— SubmitToCouncilDialog: useSessionStore вместо удалённого useStore
— ProcessesPage: ProcessDocument → { hash, source.{code,table,...} } + sortOrder в PaginationInput

* [598-14][@ant] fix(controller): DI marketplace модулей чтобы controller стартовал на marketplace2

— application/marketplace-application.module: импортирует MarketplaceInfrastructureModule (резолверы инжектят MARKETPLACE_*_REPOSITORY) и AccountInfrastructureModule (MarketplaceNotificationService требует ACCOUNT_DATA_PORT). Импорт ExtensionDomainModule удалён — порождал циркуляр AppModule → ExtensionDomainModule → ExtensionsModule → MarketplacePluginModule → MarketplaceExtensionApplicationModule, из-за чего Nest падал с "module at index [6] is undefined" сразу после merge Эпика 8.
— marketplace-writeoff-cron.service: ExtensionDomainService инжектится через @Optional() с null-fallback (cron сам читает env при отсутствии extension config). Это убирает необходимость в проблемном импорте ExtensionDomainModule.
— infrastructure/marketplace-infrastructure.module: импорт VaultDomainModule, потому что MarketplaceCanonicalBlockchainAdapter инжектит VAULT_DOMAIN_SERVICE.

Также добавлен MINIO_ENDPOINT в controller/.env (Эпик 7 / Story 7.1 stol-zakazov:images bucket теперь корректно поднимается на dev).

---------

Co-authored-by: coopops <coopos@coopenomics.world>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: coopops <coopops@coopenomics.world>
Co-authored-by: ant <ant@noreply.local>
Reviewed-on: #2
Reviewed-on: #1
CRITICAL:
- resolver: добавлена ownership-проверка `:own`/`:own-KU` для
  marketplaceReturnClaim и marketplaceListReturnClaimsByBraname —
  matrix capability guard'ом не закрывает data-уровень (matrix явно
  делегирует ownership resolver'у).
- service: extractTxHash → fail-fast ConflictException вместо записи
  фейкового tx_hash `<action>-<claim.id>` в БД при пустом ответе цепи
  (5 мест: submretrn/aprretrem/rejretrem/accretrn/rejretrn). Аналог
  фикса PR #7 для signiss1/signiss2.
- service: reason_text лимит 500 → 2000 (соответствие AC Story 7.1).
- service: warranty_until === null → fail-closed (ConflictException
  «гарантия не предусмотрена»). Раньше null читался как «без проверки»,
  возврат принимался даже на заказы без гарантии.
- frontend SubmitReturnClaimDialog: payload-документ генерируется один
  раз вместе с preview (с reason_text + defect_category), подписывается
  именно показанный snapshot — устраняет hash-mismatch preview ≠ signed.

HIGH:
- service: assertBranameMatchesClaim — backend проверяет совпадение
  input.braname с claim.delivery_braname перед on-chain action.
  Раньше председатель КУ-X мог одобрить заявление с delivery=КУ-Y
  (контракт проверял только is_user_authorized signer для braname,
  не cross-link с return_request).
- service: удалён костыль barcode-валидации
  `.includes(order_id.slice(0,8))` — UUID Order'а никогда не пересекается
  с EAN-13 inventory; проверка ложно отбивала валидные сканы.
  Полноценная сверка с marketplace_inventory — Эпик 5/9.
- service: validatePhotoPayloads — размер фото (10 МБ) валидируется
  в service до bucket.put, не полагаясь только на @UseBucket maxBytes.
- service: cleanupBucketPhotos — orphan-фото удаляются из bucket'а
  при провале on-chain submit'а (submretrn/accretrn/rejretrn).
- adapter listByDeliveryBraname: default без status возвращает ВСЕ
  заявления (включая архив). Раньше default был ACTIVE_STATUSES — это
  делало архивную секцию в operator-столе всегда пустой.

Frontend:
- maxlength 500 → 2000 для reason_text в q-input.

Known limitations (Phase 2):
- chairman_account lookup через `coop_ku.chairman_account` (сейчас
  fallback `chairmanAccount = delivery_braname` в notifications +
  resolver — ownership `:own-KU` приближение).
- Загрузка фото base64-payload одной mutation (AC требует per-file
  upload в bucket → image_registry_id). Реальный лимит body-parser
  упрётся в ~10MB.
- C++ submretrn не сохраняет delivery_braname в return_request →
  cross-branch decision разрешён by design контракта (backend закрывает
  ту же дыру service-level проверкой).
- BarcodeScanner в OnSiteDecisionDialog — mock-режим (UX-DR26 Phase 2).
- BarcodeScanner backend-сверка с marketplace_inventory — Эпик 5/9.
- Уведомление председателю шлётся на delivery_braname account
  (TODO в notification.service.ts) — Phase 2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CRITICAL:
- service: tx_hash propwroff/createagenda/execwroff раньше нигде не
  сохранялся — audit-trail отсутствовал. Теперь extractTxHash и пишется
  в decision_log при submitToCouncil (propwroff_tx_hash, createagenda_tx_hash)
  и при executeAuthorizedProposal (execwroff_tx_hash per-item).
- service: пустой tx_hash от цепи → fail-fast ConflictException
  (submitToCouncil + executeAuthorizedProposal). Раньше silent fall-through.
- service: pre-gate в submitToCouncil — если proposal с тем же hash уже
  отправлен в совет, повторный submit отбивается (защита от двойного
  клика → дублирующего propwroff + createagenda).
- service: inventory.applyStatusTransition WRITTEN_OFF теперь обязателен —
  раньше catch → warn без re-throw; позиция могла остаться
  RETURNED_TO_WAREHOUSE при успешном on-chain wroff'е и попасть в
  следующий cron-цикл → повторное списание. Теперь re-throw, proposal
  остаётся в EXECUTING для retry.
- service: signer fallback на coopname удалён (coopname — аккаунт
  кооператива, не пайщик; контракт execwroff требует
  branch.is_user_authorized(signer) — coopname этого не пройдёт).
- cron: amount=0 placeholder отбивался validateAndNormalizeItems
  (`amount <= 0`) — крон при auto_proposal_enabled=true ВСЕГДА падал.
  Теперь крон фильтрует кандидатов без unit_cost; если все без цены —
  отправляет напоминание председателю вместо broken DRAFT.

MEDIUM:
- resolver: throw new Error → BadRequestException для «DRAFT-only»
  валидации (унификация с остальными resolver'ами).

Known limitations (Phase 2):
- Story 8.1 AC требует ЧЕТЫРЕ типа кандидатов
  (RETURNED_TO_WAREHOUSE / EXCESS_RETURNED_TO_WAREHOUSE / без юр.
  оформления / expiry_date). Сейчас крон покрывает только expiry_date.
  Расширение требует добавить поля age_days/returned_at в inventory.
- Story 8.3 AC «единая Antelope-транзакция NFR-R2» — by design
  контракта per-item execwroff (комментарий cpp прямо говорит
  «снимает ограничение на максимальный размер протокола»). AC ↔
  контракт конфликт — требует переоформления AC.
- Number()/toFixed() float-арифметика на суммах — на больших суммах
  возможна потеря точности; нужен переход на BigNumber.
- cycle_started_at.toISOString() hash-mismatch при round-trip через PG
  с разной precision timestamptz.
- onCouncilAuthorized synchronously вызывает execute — long-running
  в delta-listener'е, риск duplicate execute при retry. Нужен
  decoupling через background worker.

Test plan:
- [x] jest marketplace-writeoff — 31 passed.
- [ ] e2e: двойной клик «Подписать и отправить» — второй submit
  отбивается ConflictException.
- [ ] e2e: цепь возвращает {} вместо tx — ConflictException, статус
  draft остаётся DRAFT.
- [ ] e2e: inventory.applyStatusTransition бросает — proposal в
  EXECUTING, retry возможен.
- [ ] e2e: cron при auto=true и пустом unit_cost — reminder вместо
  DRAFT'а с amount=0.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
HIGH:
- TakeoverDialog: добавлены role/aria-modal/aria-labelledby/aria-describedby
  на q-card (Story 10.2 NFR-A1). Раньше screen reader'ы не озвучивали
  заголовок takeover-диалога — несоответствие AC «aria-label/
  aria-describedby на custom-компонентах где Quasar базовых
  недостаточно».

MEDIUM:
- BarcodeScanner: timer cleanup при unmount через onBeforeUnmount.
  Иначе при закрытии TakeoverDialog раньше 1.5 s scanning-таймера
  scanned/error эмитится на размонтированный компонент → memory leak
  + lost-event race.

Story coverage:
- Story 10.1 «дизайн-токены»: marketplace-tokens.scss проверен —
  spacing/touch-targets (44/48 POS-режим)/per-role layout/dark mode
  реализованы корректно. Без правок.
- Story 10.2 «13 custom-компонентов»: все 13 на месте
  (12 в widgets/Marketplace/ + KUMapWithList в widgets/KUMapWithList/).
  A11y базовая обеспечена через QDialog/QInput штатные aria;
  TakeoverDialog получил недостающие atribute'ы.
- Story 10.3 «performance baseline»: NFR-P1/P2/P3 — manual measurements
  в MVP (без CI gate), нет автоматизации; код-аудит не выявил blockers.

Known limitations (Phase 2):
- Storybook stories — AC Story 10.2 требует, но папка stories/ пустая.
- Unit-тесты widgets/Marketplace/* отсутствуют (AC Story 10.2 требует
  Jest + Vue Test Utils).
- BarcodeScanner — mock-режим (camera-API / USB не реализованы; AC
  UX-DR26 Phase 2). Также используется в Эпике 7 OnSiteDecisionDialog.
- Полный WCAG 2.1 AA audit + axe-core CI — Phase 2 (NFR-A1 MVP
  покрыт базово).
- Lighthouse / bundle-size CI gate — Phase 2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
По ревью-комментариям к PR #4 (Эпик 3 vitrina):

1. categoryOfferCounts.ts query: было inline-объявление полей в
   `Selector('Query')(...)`. Создан селектор
   `marketplaceCategoryOfferCountSelector` с _validate-проверкой
   на `MakeAllFieldsRequired<ValueTypes['MarketplaceCategoryOfferCount']>`,
   query использует его.

2. listCatalog.ts query: было inline `{ items, totalCount, totalPages,
   currentPage }`. Создан селектор `marketplaceOfferPaginationSelector`
   (по канону `paginatedAgreementsSelector`, `paginatedMarketplace
   WriteoffProposalsSelector`) на тип `MarketplaceOfferPaginationResult`
   с _validate. Query берёт его целиком.

3. MarketplaceCatalog/types.ts: убраны ручные типы `MarketplaceOfferView`,
   `MarketplaceOfferPage`, `MarketplaceCategoryView`,
   `MarketplaceCategoryOfferCount` — теперь все четыре выведены из
   `Queries.Marketplace.{ListCatalog,ListCategories,CategoryOfferCounts}.
   IOutput`. Канон как в других страницах desktop'а.

4. api/index.ts: убраны `as MarketplaceOfferPage`/`as
   MarketplaceCategoryView[]`/`as MarketplaceCategoryOfferCount[]` cast'ы
   и `?? []` fallback'и — destructure от Queries даёт уже типизированный
   результат через IOutput.

5. selectors/marketplace/index.ts: добавлены оба новых selector файла.

SDK перебилжен (unbuild succeeded, 438 kB). controller/tsc + vue-tsc по
изменённым файлам без ошибок.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CRITICAL:
- `MarketplaceOfferService.hasActiveOrders` был TODO-stub (`return false`)
  с комментарием «Order'ов нет — withdraw разрешён всегда. Story 4.x
  перепишет на OrderRepository.countActiveByOffer». Эпик 4 уже merged
  в marketplace2 (PR #5) — Order'ы существуют. Stub НЕ был обновлён →
  supplier мог withdraw'нуть Offer с активными заказами, у пайщиков
  средства оставались в blocked, заказы зависали.
- Подключён `MARKETPLACE_ORDER_REPOSITORY`. Метод `hasActiveOrders`
  через `orderRepo.list` с фильтром по 6 не-терминальным статусам
  (ACTIVE / ACCEPTED_PENDING_SUPPLIER[_INDIVIDUAL] / ACCEPTED /
  ACCEPTED_TO_COOP / READY_TO_RECEIVE). Пагинация 1×1 — нужен только
  factOfExistence через totalCount > 0.

Тесты:
- makeOrderRepo() helper в unit-spec, batch-обновление 40 instances
  `new MarketplaceOfferService(repo, cats, makeOrderRepo())`.
- jest marketplace-offer-service — 40/40 passed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
HIGH:
- 3 сервиса Эпика 4 имели одинаковый anti-pattern:
  `t?.transaction?.id ?? t?.processed?.id ?? 'unknown'`. При пустом
  ответе цепи в БД записывалась строка 'unknown' как tx_hash —
  фантомный audit-trail, невозможно cross-reference в blockchain.
- normalizeTxHash / normalizeTxResult → fail-fast `BadRequestException`
  при отсутствии tx_hash. Соответствует фиксу аналогичной дыры в
  Эпиках 6 (PR #7), 7 (PR #8) и 8 (PR #9).

Затронуты:
- MarketplaceOrderCreateService.normalizeTxResult (createorder).
- MarketplaceOrderCancelService.normalizeTxHash (cancelorder).
- MarketplaceOrderSupplierActionService.normalizeTxHash
  (acceptorder + declineorder).

Тесты: jest marketplace-order-(cancel|create|supplier) — 37/37 passed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
HIGH:
- submitOnChainSignSupp / submitOnChainSignChair использовали
  «last-non-empty» с fallback `signsupp-<reception_id>` /
  `signchair-<reception_id>` если ВСЕ tx_hash в группе оказались
  пусты. Внутри for-of цикла tx_hash чек шёл с `if (txHash)
  lastTxHash = txHash` — пропускал пустой ответ молча.
- Теперь: на любой пустой ответ от цепи внутри loop — fail-fast
  `ConflictException` с указанием конкретного Order'а. Часть группы
  могла быть уже подписана выше — продолжать и финализировать APL
  с фантомным tx_hash в БД категорически нельзя; ретрай безопасен
  (контракт идемпотентен по order_hash).

Тесты: jest apl-reception — 6/6 passed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reviewed-on: #6
Reviewed-on: #4
Reviewed-on: #5
Reviewed-on: #7
Reviewed-on: #8
Reviewed-on: #12
Reviewed-on: #9
Reviewed-on: #13
Reviewed-on: #11
Reviewed-on: #14
Резолверы marketplace с capability :own-KU теперь принимают и доверенных
лиц председателя кооперативного участка (branch.trusted[]), а не только
самого председателя (branch.trustee). По комментариям ревью к PR #10 / #15.

Источник правды — таблица branches контракта branch (поля trustee + trusted[]).
Helper MarketplaceBranchOwnershipService инжектится через @Global()
BlockchainModule.BRANCH_BLOCKCHAIN_PORT.

Затронутые места:
* marketplace-issuance.resolver:marketplaceListIssuancesByBraname — добавлен
  member + ownership-check (раньше отсутствовал любой ownership).
* marketplace-inventory.resolver:marketplaceListInventory — role-fork
  admin/board (весь склад) vs operator (свой КУ с trustee-учётом).
* marketplace-return-claim.resolver — 4 mutations (approveReturnVisit,
  rejectReturnRemote, acceptReturnAtVisit, rejectReturnAtVisit) +
  listReturnClaimsByBraname + marketplaceReturnClaim (single): ownership
  по data.braname / claim.delivery_braname с учётом trustees.
* marketplace-apl-reception.resolver:marketplaceListAplReceptionsByBraname
  — добавлен ownership-check (раньше отсутствовал).

Phase 2 follow-ups (не в scope этого PR):
* Mutation marketplaceLabelInventory / marketplaceLabelShipmentInventory —
  проверка operator имеет право label'ить inventory по order.delivery_braname
  должна сделаться в service-уровне.
* Кеш branch lookup'ов с TTL (сейчас каждый запрос — chain RPC).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase 0 ground work for «Стол заказов» MVP docs:

- harness:
  - extend KNOWN_FIXTURES with 7 marketplace participants
    (chairkrg/chairodn/chairmyt/trustedkrg/opkrg/sidorov/petrova)
  - add cleanViteOverlays() helper to strip vite/HMR overlays before
    every shot/shotElement screenshot
  - loginAsChairman/loginAs: call cleanViteOverlays before clicks +
    waitForFunction (hash-router friendly) instead of waitForURL

- scenarios:
  - scenarios/marketplace/PLAN.md (E2E план + структура столов)
  - scenarios/marketplace/chairman/branches.mjs — Сеть ПВЗ

- boot seed:
  - boot/src/scripts/seed-marketplace-branches.ts —
    идемпотентно создаёт 3 КУ Подмосковья (krg/odn/myt)
    on-chain + Mongo organizations + addtrusted trustedkrg

- desktop pug fixes (8 файлов в pages/Marketplace):
  vite vue-pug parser падал с «Unterminated string constant» на
  многострочных inline-массивах :columns="[...]" / :options="[...]"
  внутри pug templates. Выношу массивы в <script setup> как const —
  парсер проходит, типизация q-table через QTableProps['columns'].

- quasar.config.cjs: vitePlugins=[] временно отключает
  vite-plugin-checker (overlay перехватывал клики Playwright +
  vue-tsc worker отъедал >4 ГБ heap).

Refs: components/docs-harness/scenarios/marketplace/PLAN.md
Полная документация Marketplace MVP «Стол заказов» (Эпики 1-10):

- new/marketplace/index.md
- new/marketplace/onboarding/ (3 страницы, L1/L2/L3)
- new/marketplace/orderer/ (6 страниц, стол заказчика)
- new/marketplace/offerer/ (5 страниц, стол поставщика)
- new/marketplace/operator/ (5 страниц, стол оператора ПВЗ)
- new/marketplace/branch-chairman/ (4 страницы, стол председателя ПВЗ)
- new/marketplace/chairman/ (4 страницы, стол председателя коопа)
- new/marketplace/board/ (3 страницы, стол совета)

Каждая страница 250-500 слов бизнес-языком (без «mutation» /
«resolver» / «controller»), с cross-link'ами внутри одного стола
и между столами.

mkdocs.yml: добавлен кустарник «Стол заказов» в nav между
«Бухгалтерия» и «Благорост», структура — по 6 ролевым столам.

Все 30 ссылок в nav верифицированы (файлы существуют).
- Environment.ts: check content-type before eval(/config.js) — в dev-режиме Vite catch-all отдаёт index.html на отсутствующий /config.js, и eval(htmlString) бросает SyntaxError из синхронного XHR, который всплывает как «Quasar boot error» в обход try/catch и ломает последующую init-app. Проверка content-type включает eval только если ответ действительно JavaScript.
- boot/src/scripts/seed-cooperative-base.ts: новый idempotent скрипт для восстановления базовых сущностей кооператива в Mongo (vars, monos, vaults). Полезен когда chain (blockchain-data) сохранился, а Mongo volume сбросился — boot:extra падает на adduser duplicate, а этот скрипт сидит ровно то что не на chain.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- init-wallet: info.coopname может быть undefined из-за race между login и
  loadSystemInfo. Когда login завершается быстрее, init-wallet вызывает
  loadUserWallet({coopname: undefined}) → GraphQL 400 → catch → reload →
  фронт никогда не редиректит после login. URL-fallback извлекает coopname
  из window.location.hash/pathname как safety net.
- harness scenarios/marketplace/chairman/dashboard-overview.mjs: обзорный
  сценарий по 5 marketplace URL-ам (/marketplace, /marketplace/catalog,
  /marketplace/orders, /chairman, /chairman/approval-requests). Без
  waitForURL — фронт сейчас не делает redirect после login.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Зафиксированы 2 блокера фронта, мешавшие снять marketplace-документацию,
плюс 23 скриншота и 2 инструкции (`dashboard.md`, `market-tour.md`).

## Фиксы desktop

1. `ProcessesPage.vue` — убраны TS non-null assertion `!.` из pug-template
   v-for. Они проникали в скомпилированный render-функцию и приводили к
   runtime `SyntaxError: missing ) after argument list` на import-time всех
   extensions, что блокировало init-app целиком.

2. `Environment.ts` — раскомментирован fallback на `/config.default.js`,
   когда SSR middleware не запущен и `/config.js` отдаёт SPA-fallback HTML.
   Без этого фронт получал пустой BACKEND_URL и зависал на loadSystemInfo.

3. `config.default.js` — обновлён под реальный локальный стек
   (BACKEND_URL=127.0.0.1:2998, CHAIN_ID соответствует blockchain-data).

4. `watch-branch-overlay/index.ts` — добавлен harness escape hatch
   `localStorage.harness:noBranchOverlay` для снятия скриншотов разделов
   без процедуры выбора КУ. В prod localStorage пуст, поведение не меняется.

## Документация

- `new/marketplace/chairman/dashboard.md` (11 шотов) — обзорная инструкция
  по разделам Стола председателя: онбординг, запросы одобрений, магазин
  приложений, члены совета, ПВЗ, регистрационные взносы, ключ кооператива,
  провайдер платежей, контакты.
- `new/marketplace/chairman/market-tour.md` (12 шотов) — экскурсия по всем
  маршрутам Стола заказов (`/market/*` и `/market-pvz/*`): каталог, создание
  предложения, мои заказы, готовые к выдаче, возвраты, списания скоропорта,
  склад, реестр экосистемы, ПВЗ-разделы.
- `mkdocs.yml` — добавлены 2 пункта в nav.

## Harness

- `dashboard-overview.mjs` — переписан под кликовый обход меню (без goto, чтоб
  не триггерить branch-overlay в navigation guard).
- `market-tour.mjs` (новый) — 12-шаговая экскурсия по marketplace-маршрутам
  с harness-bypass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- сценарий marketplace/orderer/marketplace-tour (4 кадра, ivanpetrov + harness signAllAgreements)
- проза orderer/marketplace-tour.md: Каталог, Мои заказы (7 фильтров), Готово к получению, Гарантийный возврат
- ключевая находка: marketplace-маршруты доступны заказчику по прямому URL, но в меню Стола Пайщика их нет
- mkdocs.yml: добавлен пункт «Обзор разделов заказчика» под «Стол заказчика»
- сценарий marketplace/offerer/offer-create (ivanpetrov, harness signAllAgreements)
- проза offerer/offer-create.md: 7 полей формы + 4 типа отсечки заказов + длительность цикла
- ключевая находка: без выбранной категории предложение не создаётся (категория обязательна)
- скриншоты: пустая форма, заполненная (без категории), каталог остался пустым
- сценарий marketplace/chairman/design-system (chairman, 3 кадра прокрутки страницы)
- проза chairman/design-system.md: список 13 custom-компонентов, UX-DR19/20/21/22/23/29/30/31
- покрыт последний marketplace-маршрут /market/design-system (виден только chairman/member)
- mkdocs.yml: добавлен пункт «Дизайн-система Стола заказов» под Стол председателя (Marketplace)
Документирует фактическое поведение свежей пайщицы Екатерины при первом
входе на Стол заказов: модальный стек 4 общих соглашений (ЦПП Кошелёк +
3 базовых) держит любую навигацию в /market/*; отдельной гейт-оферты
расширения сегодня нет; после подписания каталог открывается пустой
в контексте «Стола пайщика». Проза переписана с гипотетического
описания на наблюдаемые факты со скриншотов.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Лента my-orders пайщицы Екатерины: 8 табов-фильтров по статусу
жизненного цикла заказа (Все/Активные/Ждут поставщика/Приняты/
Готовы к выдаче/Получены/Отменены заказчиком/Отменены поставщиком)
и пустое состояние «нет заказов» для свежей пайщицы. Селекта статусов
нет — фильтрация табовая, всё видно на одном кадре.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Story 6.7: отдельная страница для статуса READY_TO_RECEIVE, открывающаяся
из push-уведомления marketplace-order-ready. Снимок: пустая таблица
«Заказ / Пункт выдачи / Количество / Сумма заказа / Открыто к выдаче»
с плейсхолдером «Нет заказов, готовых к получению» для свежей пайщицы.
Подключил страницу в mkdocs.yml в навигации Стола заказчика.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Story 7.1: orderer-стол /market/returns. Снимок раскрывает структуру:
форма «Идентификатор полученного заказа» + кнопка «Подать заявление»,
блок «Активные заявления» (пусто) и «Архив заявлений» (пусто) у свежей
пайщицы Екатерины. Подпись заявления 1104 ЭП пайщика — обязательна;
условие подачи — статус RECEIVED + неистёкший гарантийный срок.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Story 8.7: admin-стол /market/writeoffs. Снимок раскрывает регламент
1106→1105 (Заявление председателя + Протокол совета), кнопку
«+ Новый черновик», карточку «В работе совета», пустые ленты проектов
и архива у свежего стенда. Списание per-item через атомарную пару
o.mkt.wroff + o.mkt.wroff2; ролевой доступ chairman/member.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Story 9.2: /market/warehouse-summary, read-only обзор для chairman/member.
Снимок: две вкладки (Сводный склад / Поток заказов и поставок), 4 KPI
(Активных позиций / КУ с движением / Товарных позиций в обороте /
Списано накопительно), таблица SKU×ПВЗ с поиском по SKU, плейсхолдер
«Нет данных». Списания/выдачи здесь не редактируются — только триггерами
контракта marketplace.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Story 9.4: /market/ecosystem, read-only заглушка до ecosystem_registry
(NFR-Sc2 / AR18). Снимок: карточка «Текущий кооператив voskhod» +
карточка «Другие кооперативы» с информкой «Межкооперативная торговля
включится позже». Подключение братских кооперативов — вторая фаза.
Подключил страницу в mkdocs.yml.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
/market-pvz/list (workspace market-pvz). Снимок: пустой список с
жёлтым баннером «Карта недоступна: не задан YANDEX_MAPS_API_KEY» —
ключ Yandex.Maps не входит в стандартный локальный .env. ПВЗ
регистрируется ончейне через o.mkt.regbranch после создания КУ.
Канон-виджет KUMapWithList (UX-DR16). Доступ — chairman.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Story 6.6: /market-pvz/issuance. Председатель КУ загружает ленту
выдач своего ПВЗ (поле «ID КУ» + кнопка «Загрузить ленту выдач»).
Таблица заказа/заказчика/количества/суммы/статуса с плейсхолдером
«Нет заказов, ожидающих выдачи». Двухшаговая выдача: signiss1
открывает READY_TO_RECEIVE, signiss2 заказчика закрывает в RECEIVED.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Story 7.2-7.4: /market-pvz/returns. Председатель КУ загружает ленту
заявлений своего ПВЗ. Три секции жизненного цикла: «Ждут удалённого
рассмотрения» (teal), «Ожидают очного визита» (orange), «Архив»
(grey). Compensating forward через транзит 91: пара o.mkt.return +
o.mkt.return2 атомарно возвращает средства и имущество.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Story 9.1: /market-pvz/warehouse, поштучный реестр единиц имущества
на складе одного КУ. Фильтр Код КУ/Состояние/Заказчик + кнопка
«Обновить». Таблица «Штрих-код / Товар / Заказчик / Ед. / Состояние /
Возраст / Промаркировано» с 4-стадийным жизненным циклом единицы
(LABELED/ISSUED/RETURNED/WRITTEN_OFF). Backend ACL: read:own-KU.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Существующая проза описывает функционал каталога; добавил один кадр
с реальным состоянием на свежем стенде (фильтр «Все 0», плейсхолдер
«Ничего не найдено», свежая пайщица Екатерина без баланса).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Submit-кнопка называется «Опубликовать на модерацию», не «Создать».
Quasar рендерит label как div.q-field__label, не <label>, поэтому
locator('.q-field').filter({ has: locator('.q-field__label', ...) }) —
корректный путь к контейнеру q-select. Кадр 03 теперь снимается.

Каталог в кадре 04 остаётся пустым: backend :2998 в текущей среде не
имеет marketplace-резолверов (marketplaceListCategories отсутствует
в GraphQL-схеме), fetchCategories возвращает []. Сценарий готов: когда
controller с marketplace-extension будет поднят, выбор категории и
публикация offer пройдут автоматически.
Файлы первой партии docs-harness имели title/generated_by/scenario, но без tags.
Без tags страница не попадает в фильтры «Пайщик / Председатель / Член совета»
на главной mkdocs. Привёл к единой конвенции:

- chairman/dashboard.md — tags: Председатель
- chairman/design-system.md — tags: Председатель, Член совета (UI доступен chairman/member)
- chairman/market-tour.md — tags: Председатель
- offerer/offer-create.md — tags: Пайщик
- orderer/marketplace-tour.md — tags: Пайщик
Why:
- backend mono-ai-4 :3028 был перезагружен с нуля (boot:extra с правильным
  MONGO_URI?directConnection=true и POSTGRES для per-instance портов),
  cooperative voskhod + 5 членов совета загружены в БД через cooparser initial-load
- старые скриншоты в PR были сняты до wipe — UI мог показывать stale state

Также:
- components/desktop/quasar.config.cjs: viteConf.server.fs.strict=false для assets
  из bind-mount /home/admin/mono-ai-N (worktree compose override)
- components/docs-harness/bin/shoot.mjs:
  - ensureOneFixture передаёт CHAIN_URL и MONGO_URI?directConnection=true в env
    add-plain-participant (без CHAIN_URL скрипт падает на ECONNREFUSED)
  - runScenario передаёт BASE_URL в env под лежащий снизу harness.mjs
    (раньше default :2999 — это mono-ai-1 desktop, не свой)
Reviewed-on: #16
Также:
- harness.mjs dismissOnboardingDialogs ловит «Формируем документ…» состояние SignAgreementDialog по фразе в q-bar/body (ModalBase рендерит title в span, не в .q-toolbar__title).
- chairman/branches.mjs адаптирован под просмотр готовых КУ из installExtraData (без UI-сабмита, КУ уже создаются on-chain в boot:extra).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- factory/PDFService.generatePDFBuffer: писать tmp в os.tmpdir(), не в __dirname/tmp.
  dist/ внутри docker bind-mount RO → mkdirSync падал EROFS на каждом generateDocument.
  Блокировало онбординг (4 соглашения) + marketplace submit + любой signAgreement.
  Видно в логах: generateDocument теперь успешно за 3.8с для registry_id 1-4.

- harness/loginAs: вынес общую часть в ensureSigninReady() c 3 попытками reload
  и cleanViteOverlays. После рестарта desktop dev контейнера первый goto оставлял
  пустой spinner (vite-plugin-checker overlay перехватывает клики), reload фиксит.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ensureMarketplaceExtensionEnabled() делает INSERT в `extensions` с
schema_version=0, чтобы при следующем старте coopback bootstrap-миграции
v1→v6 marketplace-extension прогнались по порядку. v4 (marketplace-
bootstrap-v4) делает upsertBaseline() для marketplace_category — заливает
9 baseline-категорий (8 продовольственных + «Прочее»).

Без записи MarketplacePlugin.initialize() бросает «Конфиг не найден» и
весь marketplace-стэк выключен: marketplaceListCategories возвращает [],
форма «Категория *» залипает на валидации, marketplaceCreateOffer
недоступна. В проде это делает chairman через installExtension; для
dev-стенда сидируем напрямую через PG (идемпотентно — ON CONFLICT DO NOTHING).

Подтверждено: после активации marketplaceCreateOffer отрабатывает за
29мс, offer попадает в marketplace_offer со статусом PENDING_MODERATION.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ensureBranchPaymentMethod() сохраняет dummy bank_transfer paymentMethod
для каждой КУ в Mongo. Без него branch.interactor.getBranch() падает
TypeError в строке 55 (new BankPaymentMethodDTO(null) destructure-ит
username из null), и весь getBranches возвращает 500 → desktop UI
«Кооперативные участки» показывает «Нет данных».

После фикса страница показывает 3 КУ с председателями и реквизитами
банка (ПАО Сбербанк / Тинькофф / ВТБ). MARKETPLACE_BRANCHES расширен
полем bank_name; payment method сохраняется через generator.save
('paymentMethod', ...) после ensureMarketplaceBranch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Перенёс INSERT extension market из ad-hoc функции в installExtraData (где
он был хаком сразу после фикса) в initExtensionsInPostgres() — там же где
capital и reports. Это правильное архитектурное место: postgres-init.ts
владеет seed'ом extensions при первичной инициализации PG, и market теперь
получает ту же обработку, что capital.

schema_version=0 в INSERT означает: при следующем старте coopback
ExtensionSchemaMigrationService прогонит marketplace-bootstrap миграции
v1→v6 по порядку. v4 (upsertBaseline) сидирует 9 baseline-категорий.

Заодно убрал дубликат ensureMarketplaceExtensionEnabled() из infra.ts.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ChairmanModerationPage.vue (mp-role-admin) с CatalogOfferCard,
route marketplace-moderation, типизированные SDK-обёртки
(ListPendingOffers / ApproveOffer / ListModerationLog) — backend
уже работал, не было только UI. harness scenario + автоматическое
прохождение onboarding-каскада соглашений в loginAsChairman.
Vue-страницы OperatorReception/OperatorInventoryLabeling/OffererPendingAplReceptions/
OffererSupplyPreparation существовали в src/pages/Marketplace, но не были
подключены в extensions/market/install.ts (мёртвый код после Эпиков 5-6).
Регистрируем 4 роута:
- /market/supply-prep                 — поставщик подтверждает готовность отгрузки
- /market/apl-receptions              — поставщик подписывает signapl1
- /market-pvz/reception               — председатель КУ создаёт АПП на партию
- /market-pvz/labeling                — оператор клеит EAN-13/CODE128

Каждый закрывает страницу плана PLAN.md (operator/apl-reception-create,
operator/inventory-label, offerer/apl-reception-sign, offerer/shipment-prep)
empty-state снимками для документации.

Также: §9 в scenarios/marketplace/PLAN.md обновлён — живой статус Фазы 1
(19→23/30 сценариев), приоритеты для /loop-цикла, известные блокеры.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Vue-страница OffererPaymentHistoryPage существовала в src/pages/Marketplace
после Story 5.9, но не была подключена ни в роутинг, ни в документацию.
Закрыло пробел Эпика 5 — поставщик не мог посмотреть, какие платёжные
запросы кооператив сформировал по его закрытым актам приёмки.

Добавлено:
- Роут /market/payments (роль `[]`, доступ всем пайщикам).
- Сценарий offerer/payments.mjs со снимком empty state.
- MD-страница new/marketplace/offerer/payments.md с описанием статусов
  INITIATED/CONFIRMED/FAILED, банковского референса и поведения «Обновить».
- Пункт «История выплат» в mkdocs.yml под Стол поставщика.
- §9.2 PLAN.md обновлён: operator 5/5, offerer 3/5+1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Контекст: после одобрения председателем «Берёзовый сок» в прошлой
сессии moderation page стенд имеет один APPROVED offer. Перенимаем
этот факт для документации:

1. harness.mjs / cleanViteOverlays — добавлен опциональный
   preserveNotifications=true. По умолчанию shot() продолжает чистить
   .q-notification (как раньше), но сценарии для UI-заглушек могут
   попросить оставить тост в кадре.

2. orderer/catalog.mjs — пересминаем как 01-catalog-with-offer.
   Снимок показывает CatalogOfferCard (UX-DR10) со статус-чипом
   «Опубликовано», фильтр-чипы категорий «Все 1 / Овощи и фрукты 1».
   Старый 01-catalog-empty.png удалён из docs/assets, ссылка в
   catalog.md обновлена.

3. orderer/order-create.mjs — новый сценарий: клик «Заказать» в
   каталоге показывает Quasar Notify с фактическим текстом
   «Создание заказа на Offer X — будет доступно после Эпика 4»
   (Story 4.1 ещё не реализована). Снимок взят с
   preserveNotifications=true. В order-create.md добавлен !!!warning
   «Story 4.1 в разработке» с этим снимком в начале страницы.

Результат: документация честно отражает текущее состояние MVP —
там, где UI ещё заглушка, читатель видит заглушку и причину.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Зачем: пользователь, открывая ленту страниц marketplace в навигации, не должен решать, что заявленный функционал уже работает. Каждая из 9 страниц-заглушек (onboarding L1/L2, board/agenda-writeoff, board/payouts-readonly, branch-chairman/apl-reception-close, branch-chairman/branch-orders, chairman/category-whitelist, offerer/offers, offerer/incoming-orders, orderer/consolidated) получила `!!!warning "Эпик X — в разработке"` admonition с указанием конкретного Эпика и текущего обходного пути на MVP-стенде.

PLAN.md §9.4 переписан: Магистраль I empty-state фактически исчерпана UI-кодом, приоритет смещается на Магистраль II (сидер marketplace-flow в installExtraData). Добавлена секция §9.6 «Дыры UI» с таблицей 9 нереализованных страниц и ожидаемых Эпиков.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Зачем: пользователь обнаружил что 9 страниц Marketplace из PLAN.md §9.6 физически отсутствуют в Vue коде (бэкенд для Эпиков 1/3/4/5/6/8 DONE, фронтенд не реализован). Закрываю первую и самую компактную из 9 — Эпик 1 / Story 1.9-1.10 (L1 onboarding: приём кооперативом ЦПП «Стол заказов»).

Что сделано:
- SDK wrappers Queries.Marketplace.MarketplaceCppStatus + Mutations.Marketplace.MarketplaceAcceptCpp над уже существующими marketplaceCppStatus и marketplaceAcceptCpp GraphQL (Zeus схема в SDK уже содержит обе операции).
- Vue page components/desktop/src/pages/Marketplace/OnboardingCoopAcceptCpp/{api,ui,index.ts} (UX-DR конвенции: mp-role-admin, marketplace-tokens.scss).
- Роут /market/onboarding/coop-cpp в extensions/market/install.ts (roles: chairman/member, через agreementsBase).

Аудит остальных 8 страниц + приоритизация — в PLAN.md §9.7.

Ограничение: desktop в worktree через symlink тянет shared /home/admin/mono-ai-4/components/sdk, поэтому без пересборки shared sdk страница не подхватится в dev на :3029. Это будет решено отдельным PR (либо merge ветки, либо синхронизация SDK к shared).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Эпик 4 / Story 4.4. Страница для пайщика-заказчика: его Order'ы группируются
по cycle_id (time_based/volume_based/open_subscription); индивидуальные —
отдельной группой. На каждой партии: этап (минимальный активный по STAGE_RANK),
суммарная стоимость, число заказов, ПВЗ. Внутри — канон-виджет OrderCard.

Источник данных — Queries.Marketplace.ListMyOrders (reuse MyOrders/api).
Polling 15s до Subscriptions Story 9.x. mp-role-orderer + marketplace-tokens.

Роут /market/consolidated (roles: chairman/member), иконка fa-layer-group.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Эпик 3 / Story 3.4 + Эпик 4 / Story 4.5. Backend marketplaceListMyOffers
и marketplaceListSupplierOrders уже DONE — было пропущено в аудите §9.7.
Добавлены только SDK wrappers Queries.Marketplace.ListMyOffers / ListSupplierOrders
и две Vue-страницы.

OffererIncomingOrders (/market/incoming-orders): заказы где supplier = пайщик,
канон OrderCard, фильтр по статусу через q-tabs, polling 15s.

OffererMyOffers (/market/my-offers): свои Offer'ы во всех 4 статусах
(PENDING_MODERATION / ACTIVE / REJECTED / WITHDRAWN), канон CatalogOfferCard,
client-side фильтр + поиск, polling 30s. Для REJECTED показывается
reject_reason под карточкой.

mp-role-offerer + marketplace-tokens на обеих.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Эпик 6 / Story 6.x. Председатель КУ видит 3 ленты процессов своего
участка в одном экране с табами и счётчиками:

- Приёмки партий (Queries.Marketplace.ListAplReceptionsByBraname, Эпик 5)
- Выдачи заказов (Queries.Marketplace.ListIssuancesByBraname, Эпик 6)
- Гарантийные возвраты (Queries.Marketplace.ListReturnClaimsByBraname, Эпик 7)

Backend готов — обновление аудита §9.7 второй раз: «нужна агрегационная
query» неверно, 3 существующих query достаточно через Promise.all.

Read-only обзор; действия выполняются на /reception, /issuance, /returns.
Поле braname — ручной ввод (auto-detect через whoami — следующий шаг).

Polling 20s; mp-role-operator; роут /market-pvz/branch-orders (chairman).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Эпик 8 / Story 8.x. Совет видит проекты списания скоропорта в активных
статусах (ON_AGENDA / AUTHORIZED / EXECUTING / EXECUTED / REJECTED).
DRAFT-проекты скрыты — это рабочая зона администратора на /writeoffs.

Само голосование совета идёт через core soviet agenda (sov-flow);
здесь — обзор статусов и сумм потерь, фильтр по статусу, polling 30s.

Backend готов — reuse helper listWriteoffProposals из AdminWriteoffs/api
+ Queries.Marketplace.ListWriteoffProposals. Cast as never на statuses
из-за расхождения standalone vue-tsc и enum-merge (Zeus генерит enum
со values-как-литералами, vue-tsc вне -p не подхватывает merge).

mp-role-admin; роут /market/board-writeoff (chairman + member совета).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Эпик 3 / Story 3.x. Backend available-category-admin.resolver.ts уже DONE
(@AuthRoles chairman). Добавлены 4 SDK wrappers:
- Queries.Marketplace.GetAvailableCategories
- Queries.Marketplace.GetAvailabilityStats
- Mutations.Marketplace.AddAvailableCategories
- Mutations.Marketplace.RemoveAvailableCategories

Vue ChairmanCategoryWhitelist: список разрешённых категорий (entire +
specific types), 4 stat-карточки (всего/категорий/типов/whitelist активен),
кнопка «Добавить» с диалогом ручного ввода ID через запятую, удаление
с подтверждением. Tree-выбор через marketplaceGetCategoryTree — следующий
шаг Story 3.x.

mp-role-admin; роут /market/category-whitelist (chairman).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Эпик 5 / Story 5.x. Совет нуждается в ленте всех выплат поставщикам;
текущий резолвер marketplaceListOutgoingPaymentsAsSupplier отдаёт только
выплаты текущего пайщика-supplier'а. Для совета нужна доп. policy
`Payment / read:all` в marketplace-access-matrix + новая query
`marketplaceListOutgoingPayments` с опц. supplier_account.

До этого — informational placeholder со ссылками на нужные файлы и
объяснением, как раздел заработает. До Phase 2 совет видит выплаты
через core cassir-стол (общий стол кассира кооператива).

mp-role-admin; роут /market/payouts (chairman + member совета).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Эпик 1 / Story 1.4 + 1.11. Backend marketplaceOnboardingState уже DONE,
но SDK wrapper отсутствовал — добавлен Queries.Marketplace.MarketplaceOnboardingState.

Vue OnboardingMemberPickCpp: страница-точка-входа на подключение к Стол
заказов. Канон-виджет OnboardingCPPGate (UX-DR17) показывает пакет ЦПП
(оферта + Положение). Сама подпись делается через core Registrator-мастер
(wallet::signagree) — страница редиректит туда после accept.

Когда requires_gate=false — баннер «уже подключены» + переход к каталогу.
mp-role-orderer; роут /market/onboarding/member-cpp (все роли с auth).

Закрывает 9-ю из 9 нереализованных страниц Marketplace из PLAN.md §9.6.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Обновил PLAN.md по факту:
- §9.6 теперь не «дыры UI», а карта реализованных страниц с коммитами.
- §9.7 пересмотрен: аудит был пессимистичен. Backend для 8 из 9 страниц
  уже был DONE — нужны были только SDK wrappers и Vue (8 страниц добавили
  только UI, board/payouts-readonly — placeholder).

Урок для будущих аудитов: перед оценкой «нужно backend» делать grep по
marketplace-*.resolver.ts:@Query / @Mutation — список из 70+ резолверов
покрывает почти все MVP-use-case'ы.

Технический долг после 9 страниц: backend для board/payouts-readonly
(Payment/read:all policy), tree-выбор для category-whitelist, auto-detect
braname для branch-chairman, прогон harness'а по 9 страницам.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Сценарии для прогона harness'ом по реализованным страницам:
- onboarding/coop-accept-cpp.mjs (chairman ant, L1 ЦПП)
- orderer/consolidated.mjs (ivanpetrov, партии заказчика)
- chairman/category-whitelist.mjs (chairman ant, whitelist категорий)

Прогон самим harness'ом пока ЗАБЛОКИРОВАН — shared desktop dev :3029
не видит worktree-страниц (src vs node_modules: src в worktree локален,
а node_modules симлинкованы). Чтобы снять скриншоты:
вариант A — sync 9 pages в shared desktop;
вариант B — поднять отдельный worktree desktop dev (медленно).

Остальные 6 сценариев (offerer/incoming-orders, offerer/my-offers,
branch-chairman/branch-orders, board/agenda-writeoff, board/payouts-readonly,
onboarding/member-pick-cpp) — создаются после прогона пилот-тройки.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Завершает набор harness-сценариев для всех 9 реализованных страниц:
- onboarding/member-pick-cpp.mjs (ivanpetrov, L3 gate)
- offerer/incoming-orders.mjs (sidorov, входящие заказы)
- offerer/my-offers.mjs (sidorov, мои Offer'ы)
- branch-chairman/branch-orders.mjs (chairkrg, сводный стол КУ)
- board/agenda-writeoff.mjs (chairman ant, повестка совета)
- board/payouts-readonly.mjs (chairman ant, placeholder)

Создана папка scenarios/marketplace/board/ (раньше отсутствовала).

Прогон harness'ом по-прежнему ЗАБЛОКИРОВАН — shared desktop dev не
видит worktree pages. До решения по подхвату 9 Vue-страниц в shared
desktop сценарии существуют как готовая инфраструктура.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Заменил warning admonition «Эпик X — в разработке» на info admonition
«Реализовано — ожидается прогон harness» с указанием коммита реализации
и команды harness'а для будущего прогона. Прозы сохранены — описания
из PRD остаются валидны как факт.

board/payouts-readonly остался в статусе «Placeholder — в подготовке»
(требует backend-расширения Payment/read:all).

Обновлены: orderer/consolidated, offerer/incoming-orders, offerer/offers,
onboarding/coop-accept-cpp, onboarding/member-pick-cpp,
chairman/category-whitelist, branch-chairman/branch-orders,
board/agenda-writeoff, board/payouts-readonly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Скриншоты:
- onboarding/coop-accept-cpp (2 шота: статус + диалог принятия)
- onboarding/member-pick-cpp (1 шот: gate или баннер)
- orderer/consolidated (1 шот: overview партий)
- chairman/category-whitelist (2 шота: overview + add-dialog)
- board/agenda-writeoff (2 шота: overview + on-agenda фильтр)
- board/payouts-readonly (1 шот: placeholder)

MD-прозы не перезаписаны (admonition «Реализовано» уже на месте, прозы
из PRD остаются валидны). Картинки подключатся в существующий текст MD
автоматически через relative paths.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Поправки:
- offerer/incoming-orders.mjs: фикстура sidorov → ivanpetrov +
  signAllAgreements (sidorov не подписал онбординг, диалоги перехватывали
  клики).
- offerer/my-offers.mjs: ivanpetrov + signAllAgreements +
  docPath/assetsDir выровнены на offers.md (Vue page = OffererMyOffers,
  но MD-страница уже создана под именем offers.md с прозой из PRD).
- branch-chairman/branch-orders.mjs: chairkrg не имеет marketplace_pvz
  membership на voskhod (роут /market-pvz/* требует roles: ['chairman']
  кооператива). Используем loginAsChairman (ant) — председатель сам
  открывает стол КУ для аудита; отдельный access-уровень «branch chairman»
  подключится в Story 6.x+1.

Установлены PNG: incoming-orders (2), offers (2), branch-orders (2).
Все 9 из 9 marketplace-сценариев успешно сняты harness'ом против
desktop dev :3029 (docker-mount worktree → /app в mono-ai-4-desktop-1).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Все скриншоты прогона harness'а (cedde9f277) теперь видны в MD через
![alt](/assets/new/marketplace/.../.png) после admonition «Реализовано».
Существующая проза из PRD сохранена без изменений.

Страницы: orderer/consolidated, offerer/incoming-orders, offerer/offers,
chairman/category-whitelist, branch-chairman/branch-orders,
board/agenda-writeoff, board/payouts-readonly, onboarding/coop-accept-cpp,
onboarding/member-pick-cpp.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Установлены недостающие PNG из существующих shots/ в docs/assets:
- operator/issuance → branch-chairman/issuance-open
- operator/returns → branch-chairman/return-approve
- operator/warehouse → operator/inventory-list
- chairman/branches: добавлены 3 недостающие PNG (pvz-network, dialog-empty,
  autocomplete-chairman, branch-details)
- branch-chairman/pvz-list → chairman/branches
- orderer/returns → orderer/return-claim
- 5 PNG обновлены поверх старых (свежее состояние UI)

MD-страницы не перезаписаны — admonition «Реализовано» и проза из PRD
сохранены. Дубль board/offer-moderation.md (auto-generated draft с
TODO) удалён — оригинал в chairman/offer-moderation.md остаётся.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Harness уже отстрелял 8 страниц, PNG установлены, проза описана — слово «ожидается
прогон harness» в admonition больше не актуально. Чистый статус «Реализовано» без
лишнего шума о промежуточном состоянии разработки.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Магистраль I empty-state закрыта (9 страниц implemented, harness прогнан,
PNG installed, admonition «реализовано»). Добавлен §9.8 — конкретный план
сидера для Магистрали II: TS-скрипт seed-marketplace-flow.ts с GraphQL
chain из 6 mutations + on-chain APP-цепочка. Альтернатива — композитный
harness scenario через Playwright. Перечислены 13 шотов, которые нужно
переснять после прогона сидера.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Точечные правки на основе PRD «Стол заказов» MVP (~blago/production/1-prilozhenie-stol-zakazov):

- agenda-writeoff: операция o.mkt.wroff проходит через транзитный счёт 91
  «Прочие расходы» в две проводки (Dr 91 / Cr 10, затем Dr 86 / Cr 91), а не
  одной операцией списания с «накопителя». Транзит через 91 фиксирует
  безвозвратное исчезновение имущества, не передачу пайщику.

- payouts-readonly: двухэтапная модель L12 (FR57). На signchair срабатывает
  o.mkt.purch per-Order (фиксирует обязательство на счёте 86), создаётся
  pending-запись. После банковского перевода кассир подтверждает, backend
  дёргает o.mkt.payout (Дт 86 / Кт 51). Счёт 60 не используется.

- category-whitelist: категория «Услуги» помечена «вне MVP» — самостоятельная
  категория поставки в MVP не реализуется (требует отдельной правовой
  проработки). Заголовок раздела обновлён: «фиксированный набор категорий
  имущества» вместо «10 категорий».

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
В документации не нужно перечислять то, чего в системе нет — это шум.
Описывать только используемые счета и операции.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Прогон цепочки через harness:
1. offerer/offer-create — Сидоров публикует «Картофель Адретта 25 кг»
2. chairman/offer-moderation — председатель одобряет → ACTIVE
3. orderer/catalog — Петрова видит offer в каталоге
4. offerer/my-offers — Сидоров видит свой offer в статусе ACTIVE

Также фикс meta пути в scenarios/marketplace/chairman/offer-moderation.mjs:
docPath/assetsDir указывали в new/marketplace/board/, что создавало дубль при
каждом install — теперь канон-путь new/marketplace/chairman/offer-moderation.
PNG перенесены в правильный каталог assets/.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
orderer/order-create.mjs прогнан после магистрали II (Offer есть в каталоге),
но UI выводит stub Notify — реального сабмита marketplaceCreateOrder нет.
PLAN.md §9.9 фиксирует развилку: (A) доработать UI OrdererOrderCreate.vue
с реальной mutation + Membership flow или (B) TS-сидер через GraphQL.
(A) предпочтительно — даёт документируемую страницу.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- OrderCreateDialog.vue: q-input «Количество» + q-select «ПВЗ доставки» + расчёт итоговой суммы; submit вызывает Mutations.Marketplace.CreateOrder
- MarketplaceCatalogPage.vue: onSelectOffer открывает диалог вместо stub Notify, по @created — перезагрузка каталога
- API: fetchBranchOptions (Queries.Branches.GetBranches) + submitCreateOrder
- scenario orderer/order-create.mjs: 3 шота — пустой диалог / заполненная форма / Notify «Заказ создан»
- order-create.md: приведена к реальному MVP (убраны выдуманные «акт о паевом взносе», «комиссия», «программный кошелёк» — convention «не упоминать неиспользуемое»)
- PLAN.md §9.10: фиксация развилки §9.9 в пользу (A) UI

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- OrderCreateDialog.vue: extractErrorMessage() — Error/ApolloError/graphQLErrors/response.errors, иначе UI показывал [object Object]
- API: переключён на Queries.Marketplace.ListKUDetails (Branches.GetBranches требует chairman + NPE PaymentMethod в свежем worktree)
- scenario: 3 шота — пустой диалог / заполненная форма / Notify «Неизвестный тип цикла отсечки заявок»
- order-create.md: admonition «Известный блокер магистрали II» с указанием root-cause
- PLAN.md §9.10: backend хранит cycle_type='time_based' (snake_case), контракт marketplace::createorder ожидает eosio::name 'timebased' (без подчёркивания) — нужен mapper в backend
- assets: удалён stale 01-order-create-stub-notify.png, добавлены 3 актуальных

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
cycle_type Postgres-domain (snake_case) теперь конвертируется в eosio::name
(без `_`) на boundary chain submit:

  marketplace-offer.types.ts:
    + MARKETPLACE_CYCLE_TYPE_CHAIN_NAME registry
    + toChainCycleType(cycle) helper (соответствует enum-правилу CLAUDE.md)
  marketplace-order-create.service.ts:160:
    cycle_type: toChainCycleType(offer.cycle_type)

Mapping: time_based→timebased, volume_based→volumebased,
open_subscription→opensubscr, individual→individual
(см. components/contracts/cpp/lib/domain/table_marketplace_orders.hpp:69-71).

После фикса pipeline проходит cycle_type-валидацию и доходит до
`o.wal.block`, где упирается в новый блокер: пустой L3-кошелёк тестовых
пайщиков (0,00 RUB). UI корректно показывает Notify
«Недостаточно средств для заказа: требуется 240.0000 RUB, доступно 0.0000 RUB»
через extractErrorMessage().

Обновлены:
- order-create.md admonition (старый блокер resolved, новый описан)
- shot переименован 03-order-create-onchain-error → 03-order-create-no-funds
- PLAN §9.11 фиксирует решённый и новый блокер
- Task #137 заведён для пополнения L3-кошельков в installExtraData

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Создан components/boot/src/scripts/marketplace-deposit-fund.ts —
эмиссия в Main Wallet тестовых пайщиков через
wallet::createdeposit + gateway::completeincome (pattern из
seed-capital/08-investments.ts).

Запуск (ekaterina, sidorov) → fail:
«Пайщик не найден в кооперативе» (coops_access_helpers.hpp:57).

Root cause: текущий seed (add-plain-participant + ensureMarketplaceParticipant)
создаёт on-chain registrator account + mongo individual + pg user, но НЕ
делает joincoop через совет → запись в soviet::participants отсутствует →
createdeposit рекурсивно падает на membership check.

Архитектура кошельков (пояснил пользователь):
  Main Wallet → членских взносов → программы (Стол заказов и др.)
  Пополнение всегда в Main; конвертация автоматическая через
  o.wal.conv / o.mkt.assign / o.mkt.block.

PLAN §9.12 фиксирует развилку (Task #138):
  A) joincoop в installExtraData (быстрый seed)
  B) UI-сценарий onboarding/join-coop (документация UX)
  C) расширить add-plain-participant.ts (membership per default)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Что обновлено:
- shot 03-order-create-no-funds → 03-order-create-no-agreement (новый имя+PNG со свежим Notify).
- order-create.md admonition заменена: реальный блокер не «нет средств», а отсутствие записи в `wallet::users.programs[]` с program_id=2 (marketplace, draft_id=699). Зафиксированы две root cause-причины (factory adapter 1100 + L3 mutation marketplaceSignOnboardingOffer), две решённые подблокировки (cycle_type mapper + marketplace-deposit-fund скрипт).
- orders/01-my-orders-empty.png — обновлён PNG (тот же empty-state).

Почему. ekaterina пополнена 10000 RUB через wallet::createdeposit + gateway::completeincome (CHAIN_URL=8918 — критично для скрипта marketplace-deposit-fund.ts), но createorder валит assertion на o.mkt.assign (TRANSFER из w.wal.member в w.mkt.member): пайщик не имеет программного соглашения marketplace. Реальный L2-flow (core signup + factory render 1100) не работает в этом стенде, потому что (1) factory adapter 1100/1101 отсутствует в components/factory/src/Actions/, (2) L3 mutation для прямой подписи со стола обозначена как фоллоуап Эпика 1 в onboarding/README.md. Документация теперь явно описывает, что именно требуется доделать.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Без решения пользователя по развилке A/B/C дальнейшие итерации
по магистрали II невозможны — все oставшиеся shots требуют успешного
order-create, а тот упирается в отсутствие подписи program_id=2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Закрывает блокер magistral II документации (см. order-create.md / PLAN.md §9.13):
on-chain marketplace::createorder валил assertion «у пайщика не подписано
соглашение program_id=2 для w.mkt.member», потому что в фабрике документов
отсутствовали adapter'ы для шаблона/инстанса оферты ЦПП «Стол заказов», а
mutation подписи прямо со стола заказов была пропущена в Эпике 1 (помечена
фоллоуапом в onboarding/README.md и в blago issue 598-4).

Что сделано:

cooptypes:
- IVars.marketplace_program / marketplace_offer_template (optional). Юр.текст
  оферты остаётся «рыбой» — финальную редакцию готовит юр.отдел.
- UdataKey.MARKETPLACE_AGREEMENT_NUMBER / MARKETPLACE_AGREEMENT_CREATED_AT.
- 1101.MarketplaceOffer.Action расширён двумя обязательными полями: номер
  соглашения и дата (передаются явно из L3 service, без Udata-таблицы).

factory (Эпик 1 / Story 1.7 финализация):
- Actions/1100.MarketplaceOfferTemplate.ts + Templates/1100.
  MarketplaceOfferTemplate.ts (шаблон — только coop+vars, аналог
  998.BlagorostProgramTemplate).
- Actions/1101.MarketplaceOffer.ts + Templates/1101.MarketplaceOffer.ts
  (инстанс на конкретного пайщика — с common_user и номером соглашения).
- VarsSchema получил marketplace_program и marketplace_offer_template.
- index.ts и registry.ts factory регистрируют новые registry_id.

controller (Эпик 1 / Story 1.4 фоллоуап):
- DTO MarketplaceSignOnboardingOfferInputDTO — только signed document;
  coopname/username берутся из JWT context'а guard'а (защита от подмены).
- MarketplaceOnboardingService.signOnboardingOffer — лукап
  soviet::coagreements(coopname, type='marketplace'), вызов
  walletBlockchainPort.signProgramAgreement (program_id из coagreement,
  draft_id оттуда же, document — подписан фронтом).
- Mutation marketplaceSignOnboardingOffer в marketplace-onboarding.resolver.
  ts с возвратом свежего MarketplaceOnboardingState (UI не нужен второй
  запрос).

SDK:
- Mutations.Marketplace.MarketplaceSignOnboardingOffer wrapper (graphql-zeus
  regen для нового input MarketplaceSignOnboardingOfferInput).

desktop:
- OnboardingMemberPickCpp api: signOnboardingOffer() — генерация документа
  через DigitalDocument(registry_id=1101), подпись локальным WIF, вызов
  новой mutation.
- OnboardingMemberPickCppPage.vue: onAccept теперь подписывает прямо со
  стола (раньше редиректил в Registrator). По успеху — Notify «Оферта
  подписана» + redirect /market/catalog.
- OnboardingCPPGate widget: новый пропс busy для loading-state кнопки
  «Подписать оферту».

PLAN.md §9.14 — закрывающая запись по выбранному варианту A.

NB: schema.gql и SDK zeus/index.ts регенерированы автоматически — попадают
в коммит как часть SDK сборки. Текст оферты в cooptypes 1100/1101 остаётся
«рыбой» до утверждения юр.отделом; registry_id и подписи менять не
потребуется.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* desktop OnboardingMemberPickCpp/api: client.Mutation(mut, { input })
  не передавал переменную в GraphQL — правильный pattern
  `{ variables: { input } }` (как в submitCreateOrder). Backend отвечал
  «Variable $input of required type MarketplaceSignOnboardingOfferInput!
  was not provided», подпись ЦПП не доходила до on-chain. После фикса
  `wallet::signagree` (voskhod/ekaterina program_id=2 draft_id=699)
  проходит, marketplace::createorder больше не падает на
  «walletop: не подписано program_id=2».

* docs-harness/bin/shoot.mjs: ветка `wasUp && distRebuilt` делала
  `pkill -f 'quasar.js dev'` на хосте + spawn хостовый `pnpm run dev`.
  Quasar dev живёт в docker-контейнере, pkill на хосте ничего не убивает,
  а параллельный хостовый pnpm dev писал в .q-cache под admin uid, пока
  контейнер пишет под node uid — EACCES, кэш разваливается, Vite
  перестаёт резолвить @coopenomics/sdk. Если порт отвечает — ничего
  не трогаем, vite сам подхватит dist через fs-watch.

* harness order-create.mjs: переписан под полный flow magistral II
  с шагом подписи L3 ЦПП через /market/onboarding/member-cpp +
  debug-снимок 00-marketplace-onboarding-page для будущей диагностики.

Magistral II по L3-подписи разблокирована. Остался отдельный блокер
в MarketplaceOrderCreateService.normalizeTxResult — backend не дождётся
tx_hash от цепи, хотя on-chain action createorder проходит
(out-of-band sync error). Это вне фоллоуапа Эпика 1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* MarketplaceOrderCreateService.normalizeTxResult бросал
  «цепь не вернула tx_hash» хотя on-chain createorder проходил
  и кошелёк списывался. Причина: код искал `tx?.transaction?.id`
  и `tx?.processed?.id`, но @wharfkit/session@1.6.1 возвращает
  TransactResult структуры `{ response: { transaction_id,
  processed: { id, block_num } }, transaction: ResolvedTransaction
  (без id-поля), resolved: ResolvedSigningRequest, ... }`. Поля
  `.processed` на верхнем уровне нет, `.transaction.id` либо
  undefined либо Checksum256 (не string).

* Правильно: `tx.response.transaction_id` (+ fallback'и на
  `resolved.transaction.id` и плоский `transaction.id` для
  совместимости с другими wharfkit-версиями — как в `extractTxHash`
  marketplace-apl-reception / return-claim / issuance / writeoff
  сервисов, где этот pattern уже работал).

E2E подтверждён: ekaterina подписывает ЦПП → marketplaceCreateOrder
проходит → Notify «Заказ создан» → кошелёк списан → offer
quantity_available уменьшился. Magistral II полностью разблокирована.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Magistral II разблокирована тремя коммитами PR #24 — order-create зелёный, e2e
проходит до Notify «Заказ создан» и списания Main Wallet. При прогоне следующих
страниц (offerer/incoming-orders, orderer/orders, consolidated и т.д.)
зафиксированы пустые empty-state: заказ висит в status=ACTIVE.

Корневая причина: MarketplaceCycleAggregatorService @Cron каждые 5 минут
переводит ACTIVE → ACCEPTED_PENDING_SUPPLIER, но только когда offer.created_at +
cycle_days <= now(). Текущий offerer/offer-create.mjs использует default
cycle_days=7 — заказ ждёт 7 дней. Для документации нужен offer с
cycle_type=individual (синхронный переход в order-create.service.ts:248) или
cycle_days=0.

§9.15 в PLAN.md описывает варианты разблокировки: либо изменение
offerer/offer-create.mjs (создавать individual-offer параллельно), либо
TS-сидер seed-marketplace-flow.ts (Task #122).

Дополнительная находка: некритичный MutationLoggingInterceptor пишет
"null value in column username" — кандидат в отдельный фикс.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Default time_based с cycle_days=7 заставляет заказ ekaterina висеть в status=ACTIVE
7 дней до перехода в ACCEPTED_PENDING_SUPPLIER (через @Cron aggregator). Для
harness/документации нужен синхронный переход.

cycle_type=individual обрабатывается синхронно в
marketplace-order-create.service.ts:248 — Order сразу попадает в
ACCEPTED_PENDING_SUPPLIER_INDIVIDUAL без cron-aggregator'а.

Добавлен click по radio «Индивидуально (individual)» в форме перед submit.

ПРИМЕЧАНИЕ: на текущем стенде Vite в desktop dev контейнере нестабилен после
нескольких прогонов harness — даёт пустой spinner. Альтернатива через
GraphQL-сидер (Task #122, seed-marketplace-flow.ts) надёжнее и не зависит от UI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
— Vite dev на холодном старте долго optimizeDeps'ит chunk'и страницы create-offer;
  waitForTimeout(3000) не успевал и шот ловил пустой Quasar-спиннер; теперь ждём
  конкретно label "Название" с большим timeout (90s) — надёжный сигнал что форма
  отрисовалась, а не SPA bootstrap.
— Quasar q-radio в q-option-group рендерится как <div class="q-radio">, не <label>;
  селектор `label.q-radio` молча промахивался и форма уходила с default cycle_type=
  time_based (cycle_days=7), magistral II опять упиралась в cron-aggregator.
  Сейчас .q-radio:has-text(...) + фолбэк по тексту — radio individual реально
  кликается, offer попадает в БД с cycle_type='individual'.

Прогон harness'а: 4/4 шота, новый offer ivanpetrov individual PENDING_MODERATION
в marketplace_offer.
Закрывает magistral II через UI: offer ivanpetrov individual ACTIVE → order ekaterina
status «Размещён» виден в offerer/incoming-orders с действиями «Принять/Отказать».

Три однотипных фикса под холодный Vite optimizeDeps в desktop dev:

— offer-moderation.mjs: ждём заголовок «Модерация предложений» — иначе кнопка
  «Одобрить» не находится в пустом Quasar-спиннере и approve не происходит.
— order-create.mjs: ждём mp-onboarding-gate / banner / q-page после goto
  member-cpp, и Заказать-кнопку / mp-catalog-empty после goto catalog. До этого
  схватывал пустой spinner и падал «В каталоге нет CatalogOfferCard».
— incoming-orders.mjs: harness:noBranchOverlay через addInitScript ДО loginAs.
  Реактивность watch-branch-overlay.ts читает localStorage только при mount'е
  первого auth-tick — поэтому setItem после loginAs опаздывает и оверлей
  «Выберите КУ» рендерится первым, перекрывая страницу incoming-orders.

Прогоны 4/4:
  ✓ offerer/offer-create   — offer ivanpetrov individual PENDING_MODERATION
  ✓ chairman/offer-moderation — approve, offer → ACTIVE
  ✓ orderer/order-create   — order ekaterina на individual offer, 240 RUB списано
  ✓ offerer/incoming-orders — обе карточки (15e70181, 09c9d6c8) показаны
Полный 4-шаговый прогон magistral II работает end-to-end:
offer-create (individual) → offer-moderation (approve) → order-create (ekaterina, 240 RUB)
→ incoming-orders (ivanpetrov видит обе карточки с «Принять/Отказать»).

Документируем три корневые причины Vite-залипания и оверлея, которые годами
мешали harness'у работать стабильно после reboot/restart:

1. cooptypes/dist/index.mjs может отсутствовать → Vite виснет на import резолве;
   лечится `pnpm --filter cooptypes build` + cp в host'овый
   /home/admin/mono-ai-4/components/cooptypes/dist/.
2. Vite optimizeDeps >20s на холодном route — `waitForTimeout(3000)` истекает
   до отрисовки; заменили на `waitFor(<якорь страницы>, timeout: 90000)`.
3. localStorage harness:noBranchOverlay читается в watch-branch-overlay только
   при mount'е первого auth-tick — setItem ПОСЛЕ loginAs опаздывает; через
   addInitScript ДО loginAs флаг гарантированно установлен.

Остаточный issue (не блокирует UI): backend cycle-hook ставит
ACCEPTED_PENDING_SUPPLIER_INDIVIDUAL, парсер перетирает на ACTIVE через 2с —
race с BlockchainConsumerService.
backend `applyCycleTypeHook` ставит forward-статус (ACCEPTED_PENDING_SUPPLIER_INDIVIDUAL для cycle_type=individual) сразу после submit'а createorder, затем парсер через ~2с проигрывает delta с on-chain status=active и `MarketplaceOrderDomainEntity.updateFromBlockchain` затирает forward-статус. Это блокировало magistral II шаг 4: поставщик не видел заказ в табе «Ждут моего акцепта», статус оставался ACTIVE.

Фикс — monotonic check в updateFromBlockchain: incoming status применяется только если его ранг ≥ текущего ИЛИ статус терминальный (CANCELLED_*, EXPIRED_*, RETURNED, RECEIVED). on_chain_id / on_chain_block_num / on_chain_present обновляются всегда.

+5 unit-тестов в marketplace-order.entity.spec.ts: forward (ACTIVE → ACCEPTED), два backward-кейса игнорируются (ACCEPTED ← ACTIVE и INDIVIDUAL ← ACTIVE), терминальный (CANCELLED_BY_ORDERER поверх ACCEPTED), одинаковый ранг (PENDING_SUPPLIER ↔ INDIVIDUAL). Все 12 тестов в файле зелёные.
OffererIncomingOrdersPage был read-only, кнопки «Принять/Отказать» в OrderCard'ах эмитили @action событие, которое родительский page не слушал. Подключил обработчик:

- SDK обёртки Mutations.Marketplace.AcceptIndividualOrder и DeclineIndividualOrder + selector MarketplaceSupplierOrderActionResult.
- api/index.ts: acceptIndividualOrder(order_id) и declineIndividualOrder(order_id, reason).
- Vue: onCardAction → onAccept или onDecline; decline открывает Quasar Dialog.prompt для reason (canon UX: причина показывается пайщику-заказчику в его заказе).
- harness: incoming-orders.mjs шаги 03-individual-pending и 04-after-accept, ждёт mp-order-card вместо waitForTimeout (исключает гонку с fetchSupplierOrders).

E2E прогон: ekaterina создаёт order c8a26513 (статус ACCEPTED_PENDING_SUPPLIER_INDIVIDUAL после фикса monotonic #151), ivanpetrov открывает /market/incoming-orders → таб «Индивидуальные ожидающие» → видит карточку № c8a26513 (2 ед., 240 ₽, ПВЗ krg) с кнопками «Принять/Отказать». Mutation marketplaceAcceptIndividualOrder отправляется на backend, backend дёргает chain action acceptOrder; цепь отвергает по max_transaction_cpu_usage=290000us (303363us занял chain — известный блокер #162, чинится отдельно через setparams).
Накопленная работа по реализации e2e сценариев Стола заказов:
backend (MarketplaceKuChairmenService + spec, KU-chairman guard,
notification broadcast), frontend (market-session-store FSD-extension,
beforeEnter роутер-гарды, 11 новых Vue-страниц marketplace), SDK
wrappers (whoAmI, listPendingOffers/myOffers/supplierOrders, moderation
log, CPP queries), harness-сценарии и MD-черновики для magistral II
шагов 1-4.

Перед merge dev (101 коммит с perf-фиксами controller/desktop/mongo)
для дальнейшего прогона magistral II шага 5+.
# Conflicts:
#	.github/workflows/build-contracts-docs.yaml
#	.github/workflows/build-contracts.yaml
#	.github/workflows/publish-docs.yaml
#	.github/workflows/release.yaml
#	components/blago-cli/package.json
#	components/boot/package.json
#	components/cleos/package.json
#	components/contracts/cpp/ledger2/src/common/walletop.cpp
#	components/contracts/cpp/lib/core/ledger2/operations.hpp
#	components/contracts/cpp/lib/core/ledger2/wallets.hpp
#	components/contracts/cpp/wallet/p.wal.wthdrw.standard.yaml
#	components/contracts/package.json
#	components/controller/package-lock.json
#	components/controller/package.json
#	components/controller/schema.gql
#	components/cooptypes/package.json
#	components/cooptypes/src/ledger2/operations.ts
#	components/cooptypes/src/ledger2/wallets.generated.ts
#	components/desktop/package.json
#	components/desktop/quasar.config.cjs
#	components/docs-harness/lib/harness.mjs
#	components/docs-harness/package-lock.json
#	components/docs-harness/package.json
#	components/docs-harness/scenarios/onboarding/09-chairman-key-and-login.mjs
#	components/docs/package.json
#	components/factory/package.json
#	components/inter/package.json
#	components/migrator/package.json
#	components/notifications/package.json
#	components/parser/package-lock.json
#	components/parser/package.json
#	components/sdk/package.json
#	components/sdk/src/mutations/capital/generateGenerationConvertStatement.ts
#	components/setup/package.json
#	lerna.json
Закрывает блокер magistral II документации (см. order-create.md / PLAN.md §9.13):
on-chain marketplace::createorder валил assertion «у пайщика не подписано
соглашение program_id=2 для w.mkt.member», потому что в фабрике документов
отсутствовали adapter'ы для шаблона/инстанса оферты ЦПП «Стол заказов», а
mutation подписи прямо со стола заказов была пропущена в Эпике 1 (помечена
фоллоуапом в onboarding/README.md и в blago issue 598-4).

Что сделано:

cooptypes:
- IVars.marketplace_program / marketplace_offer_template (optional). Юр.текст
  оферты остаётся «рыбой» — финальную редакцию готовит юр.отдел.
- UdataKey.MARKETPLACE_AGREEMENT_NUMBER / MARKETPLACE_AGREEMENT_CREATED_AT.
- 1101.MarketplaceOffer.Action расширён двумя обязательными полями: номер
  соглашения и дата (передаются явно из L3 service, без Udata-таблицы).

factory (Эпик 1 / Story 1.7 финализация):
- Actions/1100.MarketplaceOfferTemplate.ts + Templates/1100.
  MarketplaceOfferTemplate.ts (шаблон — только coop+vars, аналог
  998.BlagorostProgramTemplate).
- Actions/1101.MarketplaceOffer.ts + Templates/1101.MarketplaceOffer.ts
  (инстанс на конкретного пайщика — с common_user и номером соглашения).
- VarsSchema получил marketplace_program и marketplace_offer_template.
- index.ts и registry.ts factory регистрируют новые registry_id.

controller (Эпик 1 / Story 1.4 фоллоуап):
- DTO MarketplaceSignOnboardingOfferInputDTO — только signed document;
  coopname/username берутся из JWT context'а guard'а (защита от подмены).
- MarketplaceOnboardingService.signOnboardingOffer — лукап
  soviet::coagreements(coopname, type='marketplace'), вызов
  walletBlockchainPort.signProgramAgreement (program_id из coagreement,
  draft_id оттуда же, document — подписан фронтом).
- Mutation marketplaceSignOnboardingOffer в marketplace-onboarding.resolver.
  ts с возвратом свежего MarketplaceOnboardingState (UI не нужен второй
  запрос).

SDK:
- Mutations.Marketplace.MarketplaceSignOnboardingOffer wrapper (graphql-zeus
  regen для нового input MarketplaceSignOnboardingOfferInput).

desktop:
- OnboardingMemberPickCpp api: signOnboardingOffer() — генерация документа
  через DigitalDocument(registry_id=1101), подпись локальным WIF, вызов
  новой mutation.
- OnboardingMemberPickCppPage.vue: onAccept теперь подписывает прямо со
  стола (раньше редиректил в Registrator). По успеху — Notify «Оферта
  подписана» + redirect /market/catalog.
- OnboardingCPPGate widget: новый пропс busy для loading-state кнопки
  «Подписать оферту».

PLAN.md §9.14 — закрывающая запись по выбранному варианту A.

NB: schema.gql и SDK zeus/index.ts регенерированы автоматически — попадают
в коммит как часть SDK сборки. Текст оферты в cooptypes 1100/1101 остаётся
«рыбой» до утверждения юр.отделом; registry_id и подписи менять не
потребуется.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Документация (components/docs):
- 14 пользовательских страниц «Стол заказов» (onboarding/offerer/orderer/operator) + nav-секция в mkdocs.yml
- проза бизнес-языком по фактам со скриншотов; магистраль II (шаги 6–10) сняты e2e

Backend (controller):
- финальная подпись выдачи авторизуется по подписи заказчика в документе (устранён 403 на шаге 10)
- signable-payloads приёмки/выдачи отдают DocumentAggregate {hash, rawDocument, document} — канон второй подписи поверх документа первого подписанта без регенерации
- L3-mutation marketplaceSignOnboardingOffer + DTO; schema.gql + zeus regen
- DTO/мапперы приёмки (accept_braname/fact_quantity/reception_id/supplier_account/total_amount)

Desktop:
- OperatorInventoryLabeling/Issuance/Reception API + IssueActFinalizeDialog; KU-chairman доступ к /market-pvz/*

Harness (docs-harness):
- prod-SPA подход; toast/overlay-фикс в lib/harness.mjs; новые/обновлённые сценарии magistral II + diag-e2e-apl STOP_AFTER

Boot (installExtraData):
- ролевые фикстуры + 3 КУ, эмиссия L3-кошельков, активация КУ

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Сведение двух параллельных веток «Стол заказов» от общего предка 505a33f1db:
- depth (feat): магистраль II шаги 5-10 e2e, DocumentAggregate-канон двух подписей,
  forward-only guard статуса заказа (#151), свежий dev, очищенная проза магистрали II;
- breadth (worktree): доки Потоков III/IV (возврат/списание, день оператора ПВЗ),
  шаг 4 accept/decline individual, страницы по всем столам.

Приоритет разрешения 62 конфликтов:
- магистраль II сценарии/доки/backend-каноны → depth (ours);
- доки и сценарии Потоков III/IV → breadth (theirs);
- mkdocs.yml — объединённая навигация по столам + install-market/chairman-sign/issuance-finalize;
- order-create.service.ts → theirs (toChainCycleType вынесен в shared + надёжный tx_hash);
- infra.ts → ours (тонкий installExtraData + отдельный seed-marketplace-branches.ts).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- quasar.config.cjs: вернуть ESLint-оверлей (overlay:true) — нужен пользователю,
  vueTsc остаётся отключён (вешает машину); код eslint-чист, оверлей пуст на съёмке;
- OrderCreateDialog: символ валюты из system store (governSymbol), убран хардкод ₽;
- BoardAgendaWriteoff: «Сумма потерь» → «Сумма списания»;
- OffererMyOffers: «Offer'ы» → «предложения» (user-facing);
- OnSiteDecisionDialog: убрана англо-техспецификация из ярлыка («compensating forward атомарно»).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Пересъёмка всех 12 сценариев магистрали II (offer-create → order-create →
incoming-orders → shipment-prep → приёмка АПП → подписи → маркировка →
выдача → финальная подпись) на merged-коде против текущего стенда.
Все 12/12 прошли e2e: createOffer/createOrder/approve sent=true status=200.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
OffererPendingAplReceptionsPage: при сведении worktree→feat обе стороны конфликта
сохранили идентичный блок `const columns` — vue/compiler-sfc падал на «Identifier
already declared», ломая `quasar build` (но не controller tsc и не dev-Vite).
OnboardingMemberPickCpp: убран dev-жаргон из user-facing текста gate
(«Эпик 1 / L3 онбординг пайщика» → «Подключение пайщика к программе»;
«Шаблон документа № N в реестре кооператива» → бизнес-описание; «Marketplace» → «Стол заказов»).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
orders: список заказчика теперь непустой (фикс подписи + фильтр-таб «Получены»).
consolidated/payments: переключены на корректную перспективу (ekaterina-заказчик,
ivanpetrov-поставщик) — показывают реальные партии и платёжные запросы магистрали II.
warehouse: логин под chairkrg (read:own-KU) + подписание онбординга — таблица наклеек КУ krg.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Заказчик (мои заказы/каталог/сводный заказ/готово к получению), поставщик
(мои предложения/история выплат), оператор ПВЗ (склад/возвраты), председатель
(доступные категории/сводный склад), совет (повестка списаний/выплаты),
председатель КУ (сводный стол/список ПВЗ), подключение пайщика к программе.
Кадры на реальных данных стенда (заказы ekaterina, выплаты ivanpetrov,
наклейки КУ krg, партии), проза бизнес-языком без dev-жаргона.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Убраны дубли роутов reception/labeling/branch-orders в extensions/market/install.ts:
сторонние entry с meta.roles:['chairman'] (core-роль главного председателя)
перекрывали canonical с requireMarketplaceOperator и отбивали председателей КУ
«Недостаточно прав» — регрессия открытия /market-pvz/* для KU-chairman.

Локализованы сырые enum (статус/вариант) в подписях столов: диалоги подписи
приёмки (председатель/поставщик), история выплат, подготовка поставки, выдача,
сводный стол КУ. Приёмки, ждущие подписи, всплывают наверх списка.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Пересняты на зелёной сборке с фиксом доступа председателя КУ: закрывающая
подпись приёмки, первая подпись поставщика, создание АПП — таблицы и диалоги
с русскими подписями статусов/вариантов. Проза приведена к подписям UI,
убран on-chain-жаргон (signchair/композитная транзакция). Обновлены
скриншоты выплат и выдачи.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- ledger2: восстановлены 11 операций o.mkt.* + wallet::CONVERT_TO_MEMBER +
  кошелёк w.mkt.member, утерянные кривым merge (operations.hpp/wallets.hpp
  откатились на legacy-сторону, тогда как accounts/processes остались на
  Эпике 11). CONSUME_BY_MEMBER → существующий WalletOp::BURN_BLOCKED (без
  дубля enum-слота). marketplace.wasm снова собирается.
- propwroff: action расширен (document2 statement, std::string meta);
  контракт сам ставит повестку inline-вызовом soviet::createagenda от
  permission_level{marketplace, active} (marketplace в contracts_whitelist).
- backend: propWroff несёт statement+meta (через SignedDigitalDocumentInputDTO
  .toDocument()); отдельный createWriteoffAgenda удалён из порта/адаптера/
  сервиса — кооператив не в whitelist, второй вызов создавал бы дубль повестки.
  cooptypes IPropWroff +statement+meta. spec 31/31.
- desktop: суммы списания форматируются через formatAsset2Digits на столе
  председателя, в ленте совета (фикс поля total_loss_amount→total_amount,
  показывалось «—»), в деталях проекта и виджете процесса.
- harness: writeoff-propose снова доводит проект до повестки совета
  (ENABLE_COUNCIL_SUBMIT=true после фикса моста).
Виджет использовал formatAsset2Digits в шаблоне без импорта — падал бы
с ReferenceError при открытии карточки процесса списания.
Operators and КУ-chairmen could read inventory, issuances and acceptance
acts (АПП) of other branches: list resolvers returned coop-wide data and
signable-payload resolvers fetched documents by client-supplied id without
membership checks. Add own-KU scoping (isMemberOfBranch / orderer ownership)
across marketplaceListInventory, marketplaceListIssuancesByBraname,
issuance + apl-reception signable payloads, and tighten canAccess so that
an :all qualifier subsumes :own/:own-KU rather than requiring literal match.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
MarketplaceOnboardingService constructor grew to 4 args (soviet/wallet
blockchain ports) — the onboarding-service and l2-onboarding-flow specs
still called it with 2 and failed to compile. Pass stub ports (getOnboardingState
touches neither). plugin-initialize asserted the old warn-log about an
unmerged PR; production now logs info "File storage отключён конфигурацией".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Поток III (возврат и списание): wrap signed return docs in
SignedDigitalDocumentInputDTO before toDocument() (flat-doc fix), rehydrate
jsonb-stored ISO dates to Date for the DateTime scalar in the return-claim
mapper. Frontend: orderer/operator return-claim pages + decision dialogs,
writeoff draft editor, branch-orders, issuance finalize, barcode scanner,
process info widgets. Update return-approve/return-claim/writeoff-propose
prose and screenshots.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Screenshots referenced by the updated return-approve, return-claim and
writeoff-propose pages.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The selector was declared twice with identical bodies in orderSelector.ts,
so `pnpm --filter @coopenomics/sdk build` failed with TS2451 (cannot
redeclare block-scoped variable). Latent because the SDK was not built
locally. Drop the second declaration.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- access-matrix: capability Payment:read:all для admin/board_readonly/board (член совета ведёт надзор за выплатами, подтверждает кассир)
- резолвер marketplaceListOutgoingPayments(supplier_account?, statuses?) под RequireMarketplaceAccess('Payment','read:all')
- repository.listAll(coopname, filter) + адаптер: coopname + опц. payee_account + In(statuses), created_at DESC
- SDK: Queries.Marketplace.ListOutgoingPayments + regen zeus (controller/zeus, sdk/src/zeus)
- desktop: BoardPayoutsReadonly с placeholder на реальную таблицу (фильтры поставщик/статус, сводка-счётчики, локализация статусов)
- harness-сценарий marketplace/board/payouts-readonly

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- boot: seedMarketplaceKuDetails (idempotent ON CONFLICT) + CLI seed:marketplace-ku
  + шаг в extra_reboot.sh после coopback с ожиданием создания таблицы;
  на свежем стенде krg/odn/myt (ACTIVE) появляются автоматически.
- controller: MutationLoggingInterceptor пишет username='anonymous' вместо
  undefined для мутаций без JWT (login/register) — INSERT в mutation_logs
  больше не падает на NOT NULL, лог анонимной мутации сохраняется для аудита.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Конструктор MarketplaceOrderSupplierActionService оброс cycleRepo и
shipmentCreate (synthesizeIndividualShipment), тест остался на 4 аргументах
со времён Эпика 4. Добавлены моки cycleRepo.create/shipmentCreate.execute +
orderRepo.assignToCycle, инстанс собирается всеми 6 аргументами. tsc 0 ошибок,
jest 19/19.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Систематический баг: 10 сценариев навигировали по URL без #/ (десктоп — hash-роутер),
из-за чего молча сохраняли спиннеры/404 вместо контента — корень «тонкого покрытия»
документации Marketplace. Плюс две устойчивости харнесса:
- loginAs/loginAsChairman: таймаут 60→150с (холодный Vite компилирует chunk роута
  signin дольше 60с на первом заходе);
- dismissOnboardingDialogs: распознавание онбординг-диалога на стадии «Формируем
  документ…» (кнопки «Подписать» ещё нет — ловим по фразе), иначе кадр ловит спиннер.
- operator/issuance: добавлен dismissOnboardingDialogs.

Доки обновлены реальными данными прогнанной e2e-магистрали Marketplace
(заказ → приёмка 2 подписи → ACCEPTED_TO_COOP → выплата COMPLETED → маркировка
EAN-13 → выдача signiss1/signiss2 → RECEIVED): каталог, мои заказы, сводный заказ,
предложения поставщика, входящие заказы, история выплат, ожидаемые поставки,
маркировка/склад КУ, лента выдач, экосистема, сеть ПВЗ.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Правки по голосовому ревью документации Marketplace:

- orderer-стол «Готово к получению»: заказ доведён по магистрали до
  READY_TO_RECEIVE (без финальной подписи), сценарий перестроен ОТ этой
  страницы (а не от вкладки «Получены»); проза уточнена — финальная подпись
  получения оформляется на столе оператора ПВЗ по штрих-коду.
- board/payouts: лента выплат показывает реальные данные (1 PENDING + 2
  COMPLETED) — данные засеяны магистралью; страница уже авто-грузится onMounted.
- удалены legacy-доки orderer/receive.md и branch-chairman/apl-reception-close.md
  + их nav-записи; висячие reference-ссылки перенаправлены на issuance-finalize /
  apl-reception-create / ready-to-receive (mkdocs --strict: по marketplace чисто).
- YANDEX_MAPS_API_KEY проброшен через EnvVars + generateConfig + fallback envCache
  (ключ только в gitignore'нутом .env; в config.default.js — пустой placeholder);
  карта Яндекса на странице ПВЗ рендерится, баннер «не задан ключ» убран; проза
  branches.md обновлена (карта есть всегда, точка — после геокодинга адреса).
- harness: устойчивая навигация ready-to-receive/pvz-list (ретрай + полный
  перезагруз против гонки core-guard/fetchRoles), ожидание DOM-рендера карты,
  dismiss онбординга на pvz-list.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Колонка chairman_signed_at в QTable не отображала значение (нет format-функции,
сырой ISO не рендерился) — добавлен formatDate с ru-локалью (как в board/payouts),
теперь видна дата открытия выдачи. Обновлён скриншот документации.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Boot-файл ждал window.chatwootSDK через polling БЕЗ таймаута (await
waitForChatWootReady), поэтому недоступный support.coopenomics.world вешал
всю инициализацию Quasar → бесконечный спиннер. Вынес доинициализацию чата
в отделённую (неожидаемую) задачу + жёсткий 15s таймаут: boot завершается
сразу после app.use(chatwoot), недоступный support чат лишь отключает, но
загрузку приложения не блокирует.
Расширение market давало один смешанный стол из 21 страницы (заказчик +
поставщик + админ вперемешку) и отдельный Стол ПВЗ, причём market-pvz
вообще не объявлялся в backend AppRegistry → его маршруты молча терялись.

- backend extensions.registry.ts: AppRegistry['market'].desktops теперь
  объявляет 4 стола (market/market-supplier/market-pvz/market-admin) —
  фронт прикрепляет маршруты только к объявленным backend столам.
- desktop install.ts: возвращает 4 workspace, 29 страниц разнесены по
  ролям; Стол администратора виден только chairman+member (meta.roles),
  остальные — всем; имена маршрутов сохранены (навигация по имени).
- init-installed-extensions: убран немой catch{} (теперь console.error)
  + warn, когда install вернул workspace, отсутствующий на backend, —
  раньше сломанный/несинхронный стол выглядел как «пустой» без диагностики.

Проверено на стенде: getDesktop = 4 стола, Pinia-стор = children 6/6/8/9,
скриншот навигатора подтверждает рендер страниц по столам.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Активный стол (activeWorkspaceName) ставился только кликом по плитке,
редиректом с index и из localStorage. При прямом заходе по URL или переходе
кнопкой со стола А на маршрут стола Б активным оставался прежний стол — в
шапке и левом меню «висел» не тот стол (видно было на playwright-кадрах как
постоянный «Председатель»).

- DesktopStore.syncActiveWorkspaceFromRoute(matchedNames): находит стол по
  имени родительского маршрута (mainRoute.name) в цепочке to.matched и делает
  его активным без навигации и без спиннера.
- navigation-guard: router.afterEach зовёт синк после каждого перехода.
- init-app: однократный досинк после установки расширений (deep-link при
  холодной загрузке, когда afterEach сработал до прикрепления маршрутов).

Проверено на стенде: прямой заход на /market-supplier|market-admin|market/*
переключает активный стол и левое меню на нужный.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Карточки CatalogOfferCard на модерации и в «Мои предложения» нельзя было
открыть — описание обрезано, действий нет. Добавлен widget OfferDetailsDialog
(+ секция витрины дизайн-системы): клик по карточке открывает модалку с полным
описанием, метой (категория/тип отсечки/гарантия/поставщик), причиной отклонения
и slot actions под ролевые кнопки.

- Модерация (председатель): открыть → прочитать → Одобрить / Отклонить
  (добавлена мутация rejectOffer с обязательной причиной — раньше в UI её не было).
- Мои предложения (поставщик): открыть → Редактировать / Снять / Запустить поставку.
- Правка: скрытый роут marketplace-edit-offer переиспользует форму создания в
  режиме редактирования (префилл из listMyOffers, сабмит → marketplaceUpdateOffer).
  Бэкенд сбрасывает статус в PENDING_MODERATION — правка уходит на повторную
  модерацию в рамках того же предложения (баннер предупреждает об этом).

Проверено на живом стенде: диалоги открываются на обоих столах, Редактировать
ведёт на предзаполненную форму. ESLint чист, vue-tsc без ошибок в затронутых файлах.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Из формы установки расширения market убраны неиспользуемые в коде поля
enabled/lastSyncTimestamp/debug; coopAcceptance помечен visible:false как
системное состояние совета; настройки writeoff получили русские label/note
через describeField — по канону расширения capital. Чтобы при установке
приложения председатель видел минимальную понятную форму на русском, без
отладочных и внутренних параметров.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Родительскому объекту writeoff заданы label «Списание скоропорта» и note
через describeField; рендерер формы (ZodForm) показывает getLabel/getHint и
для объектов-групп, поэтому без описания отображалось техническое «writeoff».

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Семантика крона списания исправлена: в кандидаты попадают позиции с УЖЕ
истёкшим сроком годности (expiry_date <= now), а не за N дней до истечения —
списываем испорченное, а не заранее. Параметр expiry_grace_days удалён как
вводящий в заблуждение. auto_proposal_enabled теперь true по умолчанию, чтобы
проект списания формировался без ручной активации. Крон остаётся ежемесячным
(@Cron EVERY_1ST_DAY_OF_MONTH ≈ раз в 30 дней) — устойчив к рестартам, в
отличие от setInterval (лимит ~24.8 сут не покрывает 30-дневный интервал).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Возвращён регулируемый параметр списания, но с корректной семантикой: товар
попадает в проект списания, только пролежав N дней ПОСЛЕ истечения срока
(expiry_date <= now - grace), default 7. Свежепросроченное ещё может быть
забрано получателем — списываем лишь окончательно испорченное. Крон остаётся
ежемесячным (каждое 1-е число).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Опциональный generic-канон для расширений с подключением на уровне кооператива:
- IRegistryExtension получает onboarding_route / onboarding_desktop / isOnboarded(config);
- getDesktop, пока isOnboarded(config)===false, отдаёт ТОЛЬКО onboarding_desktop —
  остальные столы скрыты из переключателя (для market: только Стол администратора);
- desktop install.ts зеркалит решение бэкенда и ограничивает админ-стол одной
  страницей подключения ЦПП и ролью chairman — до решения совета никто, кроме
  председателя, не видит ни столов, ни страниц;
- loadExtensionRoutes возвращает workspace-конфиги; кнопки включения/установки
  редиректят на defaultRoute первого стола — для расширений с онбордингом это
  страница подключения, чтобы администратор не терялся после включения.

Расширения без этих полей работают как раньше (опциональность). Источник
безопасности — backend-резолверы; гейтинг столов — UX-слой поверх них.
Для market: isOnboarded = config.coopAcceptance.accepted.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Дописана секция в EXTENSIONS_SCHEMA_SYSTEM.md: контракт реестра
(onboarding_route/onboarding_desktop/isOnboarded), две согласованные половины
гейтинга (backend getDesktop скрывает столы, frontend install.ts ограничивает
стол ролью и одной страницей), редирект при включении, жизненный цикл и
ограничения (нужна перезагрузка после принятия ЦПП), опциональность.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
getDesktop отдаёт DesktopWorkspace.grants (права текущего пользователя), фронт
сверяет meta.requires маршрута с ними (DesktopStore.isPageVisible/isWorkspaceVisible/
hasRouteAccess). Авторизацию считает только backend, фронт отражает. Провайдер
грантов расширения само-регистрируется в @Global ExtensionGrantsRegistry (нет цикла
модулей); getDesktop под OptionalGqlJwtAuthGuard. Онбординг сведён к грантам: до ЦПП
у председателя только Extension:configure → виден лишь Стол админа со страницей
подключения. market переведён на meta.requires; убраны onboarding-поля реестра,
desktop-фильтр и фронтовые beforeEnter/онбординг-ветка. Зачем: убрать дублирующую
логику видимости столов в двух местах и подготовить per-page доступ из бэкенда.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
generate-schema + generate-client + сборка SDK после добавления поля grants.
schema.gql и components/sdk/src/zeus/* теперь содержат grants — фронт штатно
выбирает поле через selectors/desktop/workspaceSelector.ts (канон авторизации
столов активирован end-to-end). dist в .gitignore.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- cooptypes 1107.MarketplaceProgramTemplate (Положение ЦПП, placeholder) + factory-генератор
- marketplace подключён к платформенному онбордингу (register-marketplace-onboarding-steps),
  coopAcceptance.accepted выводится из all-steps-done в initialize() (auto-restart по ONBOARDING_COMPLETED)
- desktop: страница онбординга переведена на shared CouncilOnboardingCard + generic onboarding SDK
- дока ядра онбординга: components/context/notes/EXTENSION_ONBOARDING_CORE.md

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Корень: на холодной перезагрузке client пересоздаётся без JWT; desktops.loadDesktop() в init-app выполнялся ДО useInitWalletProcess().run()→session.init()→globalStore.init()→client.setToken(). Гостевой getDesktop → grants=[] у market-столов; повторного refetch после авторизации не было. Легаси-столы выживали (видимость реактивна от session.isChairman), grant-столы — нет. session.init идемпотентен, повторный вызов в init-wallet = no-op.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Конфликты в 4 marketplace-резолверах (apl-reception, inventory, issuance,
return-claim) разрешены в пользу этой ветки: ownership через
MarketplaceKuChairmanService (trustee+trusted), а не branchOwnership из
marketplace2 (PR #16). Осиротевший marketplace-branch-ownership.service.ts
и его проводка удалены — заменены kuChairmanService.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 416 rename Chairmen→Chairman; 419/421/422/423 убраны as unknown (строгие
  Zeus-типы, скаляры через Omit+override); 420 статусы через Zeus enum;
  424/425 SuccessAlert/FailAlert в OrderCreateDialog; 417/418 дедуп
  normalizeTxHash/rethrowChainError в shared/chain-tx.util; 415 единый filter
  через InputType DTO (+ regen schema/zeus/SDK); 426/427 @OnEvent из cooptypes
  WalletContract (добавлены wallet-действия authwthd/declinewthd).

Controller tsc зелёный; desktop vue-tsc — 2 pre-existing ошибки (q-tab :name=null).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reviewed-on: #17
Вводит USER_SHARED-кошелёк w.mkt.order (MARKETPLACE_ORDER_LOCK) — резерв
средств пайщика под конкретный Order. Заменяет механику BLOCK/UNBLOCK
на отдельных balance-полях кошелька w.mkt.member на пару кошельков:

- o.mkt.block   (BLOCK_FOR_ORDER):   TRANSFER w.mkt.member → w.mkt.order
- o.mkt.unblk   (UNBLOCK_ON_CANCEL): TRANSFER w.mkt.order → w.mkt.member
- o.mkt.consum  (CONSUME_BY_MEMBER): BURN с w.mkt.order  (вместо BURN_BLOCKED w.mkt.member)

Operation_id'ы (o.mkt.block / o.mkt.unblk / o.mkt.consum) сохранены —
изменилась только реализация в OPERATION_REGISTRY, не имена; внешние
op_code в blockchain_actions читаются без сдвига смысла.

Параметр Ledger2::apply(coopname, op, amount, ...) не меняется — сигнатура
вызовов в .cpp идентична, отдельная правка реализаций следующим коммитом
ограничивается обновлением doc-комментариев.

LEDGER2_WALLET_REGISTRY: 15 → 16 (USER_SHARED 6 → 7).
LEDGER2_USER_SHARED_PROGRAM_MAPPING: 7 → 8 (w.mkt.order → program_id=2).

Это marketplace-сторона миграции, повторяющая подход dev для wallet/withdraw
(WITHDRAW_PENDING кошелёк-резерв; ADR — общий принцип «убрать blocked,
заменить на пары TRANSFER в выделенные кошельки»). Enum WalletOp в этом PR
НЕ модифицируется — значения BLOCK/UNBLOCK/BURN_BLOCKED остаются для legacy
wallet/withdraw flow, который мигрирует следующим merge dev → marketplace2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Семантика in-code не меняется: Ledger2::apply(coopname, op, amount, ...)
вызывается с теми же operation_id (o.mkt.block / o.mkt.unblk / o.mkt.consum),
изменения только в doc-блоках и inline-комментариях:

- createorder.cpp: "BLOCK на w.mkt.member" → "TRANSFER w.mkt.member → w.mkt.order"
- cancelorder.cpp: "сумма остаётся на .available" → уточнено, что снятие резерва
- declineorder.cpp: "резерв возвращается заказчику" → уточнено через TRANSFER
- expireorder.cpp: тот же flow
- signiss2.cpp: actual<ordered → снятие части резерва (UNBLOCK_ON_CANCEL),
                actual>ordered → доп. резерв (BLOCK_FOR_ORDER),
                CONSUME_BY_MEMBER теперь BURN с w.mkt.order

Поведенческих изменений в C++ нет — вся семантика выражена через изменения
в OPERATION_REGISTRY (предыдущий коммит). Этот коммит — только документация
точки вызовов, чтоб не путать читателя.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DTO `MarketplaceMemberWallet.wallets` теперь возвращает массив из 4 USER_SHARED-кошельков
пайщика для Стола заказов:

  1. w.wal.share  — Паевой   | Цифровой Кошелёк
  2. w.wal.member — Членский | Цифровой Кошелёк
  3. w.mkt.member — Членский | Стол Заказов (свободные средства программы)
  4. w.mkt.order  — Резерв   | Стол Заказов (под конкретный Order; новый, 2026-05-28)

Поле `blocked` остаётся в DTO для wallet/withdraw flow и legacy данных, но для
marketplace-кошельков (w.mkt.member, w.mkt.order) всегда `0` после миграции
с BLOCK/UNBLOCK на пары TRANSFER — резерв выражается через `.available`
отдельного кошелька w.mkt.order. Обновлены description'ы @Field и @Query на
бизнес-словаре (без story-ссылок).

Regen:
- cooptypes/ledger2/wallets.generated.ts: wallets=16, mapping=8 (из C++).
- controller/schema.gql, controller/zeus/index.ts, sdk/zeus/index.ts:
  только description-only diff в MarketplaceMemberWallet/Entry, никаких
  breaking changes в типах GraphQL/zeus.

Unit-тесты marketplace-member-wallet-resolver.test.ts обновлены под 4-й
кошелёк (4/4 passed, runInBand).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Инвариант I5 переименован: `BlockedConsistency` → `ReserveConsistency`.
Считает резерв через пары TRANSFER + BURN в новой модели:

  Σ TRANSFER(w.mkt.member → w.mkt.order)    // o.mkt.block  — резерв входит
  − Σ TRANSFER(w.mkt.order → w.mkt.member)  // o.mkt.unblk  — резерв снят
  − Σ BURN(w.mkt.order)                     // o.mkt.consum — резерв сожжён
    = Σ balance(w.mkt.order у пайщиков)

Раньше I5 сверялся с suma blocked у w.mkt.member-кошельков (поле `.blocked`).
Поле `.blocked` для marketplace-кошельков теперь deprecated (всегда 0) —
резерв выражается через `.available` отдельного кошелька w.mkt.order.

Инвариант I6 переименован: `NoOrphanedBlocks` → `NoOrphanedReserves`.
Логика парности по process_hash (consum без block, unblk без block,
block+unblk+consum = двойное закрытие) не меняется — op_code'ы
o.mkt.block/unblk/consum сохранены.

Фикстуры в spec.ts переписаны под новую модель: walletFrom/walletTo
для block теперь w.mkt.member → w.mkt.order, для unblk наоборот,
для consum — walletFrom=w.mkt.order (BURN).

ВНИМАНИЕ — pre-existing baseline:
`marketplace-process-trace-coverage.spec.ts` и 4 теста в `*-invariants.spec.ts`,
которые опираются на `MARKETPLACE_OPERATION_CODES` (фильтр через
`Ledger2.LEDGER2_OPERATION_REGISTRY.contract === 'marketplace'`), падают
с pre-existing проблемой: `cooptypes/ledger2/operations.ts` содержит только
2 legacy marketplace-операции (`o.mkt.supply`/`o.mkt.recv`) вместо 13 новых.
Это известный блокер Эпика 11 Story 11.1 (синхронизация TS-стороны cooptypes
с C++ operations.hpp) — выходит за scope этого PR.

Проверено: 32/36 тестов в marketplace-ledger2-invariants.spec.ts passed
(те же 4 теста были сломаны на pre-state baseline).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Обновлены описания операций o.mkt.block / o.mkt.unblk / o.mkt.consum
в стандарте процесса под reserve-wallet модель (введён 2026-05-28):

- Заголовок раздела ledger2 (комментарий перед секциями): добавлен новый
  USER_SHARED-кошелёк w.mkt.order, обновлено описание w.mkt.member.
- o.mkt.block:  BLOCK → TRANSFER w.mkt.member → w.mkt.order; L3 теперь
  пара движений на двух кошельках вместо split available/blocked на одном.
- o.mkt.unblk:  UNBLOCK → TRANSFER w.mkt.order → w.mkt.member; зеркало 12c.
- o.mkt.consum: REVOKE → BURN с w.mkt.order; резерв сжигается без получателя
  при подписании АПП выдачи (signiss2).
- Список ledger2-кошельков в Секции 6 — упомянут w.mkt.order.

Бизнес-словарь: «блокировка» → «резерв», «разблокировка» → «снятие резерва»,
«заблокированная сумма списывается» → «резерв сжигается». Сами op_code
(o.mkt.block / o.mkt.unblk / o.mkt.consum) и amount_ref / triggered_by /
human_name (последний обновлён для consistency) — сохранены, чтобы
бухгалтер мог пройти по тем же шагам процесса в журнале операций.

p.mkt.return.standard.yaml и p.mkt.wroff.standard.yaml не требуют правок —
эти flow используют ISSUE/NONE, без BLOCK/UNBLOCK/REVOKE.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Переделка 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>
build-all.sh / build.sh хардкодят dicoop/blockchain:latest (amd64-only) —
на ARM падают «exec format error». Multi-arch образ dicoop/blockchain_v5.1.1:dev
имеет нативный arm64 manifest; команда для marketplace в одну строку
зафиксирована в новой секции «Сборка контрактов на ARM-машине».

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Замечания ревью @ant Review 31:

(1) o.mkt.lock теперь идёт **прямо** w.wal.share → w.mkt.order
    с проводкой Дт 80 / Кт 86 — без промежуточного w.wal.member и без
    conditional o.wal.conv. Один шаг ledger2 в createorder.
    - createorder: убран каскад conv+lock; lock сам делает работу
    - signiss2 actual>ordered: убран conv, остался только lock(diff)
    - Достаточность средств: только w.wal.share.available

(2) Убраны из комментариев и YAML фразы «без транзита 91» / «через транзит 91»
    как нерелевантный шум (никого не интересует прошлое):
    - cancelorder.cpp, accretrn.cpp, execwroff.cpp
    - processes.hpp: пересчитан состав операций под новую схему
    - controller TS: marketplace-return-claim.{types,service}.ts,
      marketplace-canonical-blockchain.port.ts, marketplace-order.types.ts,
      marketplace-application.module.ts

(3) Убраны упоминания «может быть выведено пайщиком на паевой» —
    с w.wal.member на w.wal.share вывод НЕ предусмотрен в этой схеме.

Инварианты I5 переписаны под новый источник lock (w.wal.share вместо
w.wal.member). spec.ts фикстуры обновлены: happyPathOrderFlow теперь
4 операции (lock + purch + payout + consum) без conv. 31/33 теста зелёные,
2 fail — pre-existing baseline (Story 11.1 cooptypes).

C++ контракт пересобран через ARM-native CDT (dicoop/blockchain_v5.1.1:dev) —
0 errors, marketplace.wasm + .abi созданы успешно.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
YAML стандарты переписаны под текущую модель (5/1/1 операций), удалены
блоки удалённых операций и упоминания старой схемы:

p.mkt.supply.standard.yaml — 5 операций:
  o.mkt.lock   — TRANSFER w.wal.share → w.mkt.order, Дт 80 / Кт 86
  o.mkt.unlock — TRANSFER w.mkt.order → w.wal.member, без проводки
  o.mkt.purch  — NONE, Дт 10 / Кт 86
  o.mkt.payout — ISSUE → w.mkt.payout, Дт 86 / Кт 51
  o.mkt.consum — BURN w.mkt.order, Дт 86 / Кт 10
  Удалены: o.wal.conv, o.mkt.assign, o.mkt.recall, o.mkt.consum2

p.mkt.return.standard.yaml — 1 операция:
  o.mkt.return — ISSUE → w.wal.member, Дт 10 / Кт 86
  Удалена: o.mkt.return2

p.mkt.wroff.standard.yaml — 1 операция:
  o.mkt.wroff — NONE, Дт 86 / Кт 10
  Удалена: o.mkt.wroff2

Полностью удалены deprecated поля из TS controller (не оставлены даже
как deprecated пометки):
- MarketplaceOrderCreateTxSnapshotDTO.did_convert / did_assign
- MarketplaceOrderCreateTxSnapshot.did_convert / did_assign
- Поле blocked_amount переименовано в locked_amount (везде: DTO, entity,
  service, spec, infrastructure entity)

Зачистка doc-комментариев от удалённых операций в C++ + TS:
- processes.hpp, lib/core/marketplace/marketplace.hpp,
  marketplace/marketplace.hpp, table_marketplace_writeoff_proposals.hpp,
  wallets.hpp (description w.mkt.order под новый flow)
- marketplace-canonical-blockchain.port.ts, marketplace-order-cancel.service.ts,
  marketplace-issuance.service.ts, marketplace-offer.entity.ts,
  marketplace-order.entity.ts, marketplace-return-claim.types.ts
- marketplace-ledger2-invariants.ts: I2 формула, I3 формула, I4 пояснение
- marketplace-process-trace-coverage.spec.ts: EXPECTED_MARKETPLACE_OP_CODES
  и EXPECTED_REGISTRY переписаны под 7 операций (5 supply + 1 return + 1 wroff)
  с актуальными wallet_op / debit / credit

Bulk переименование o.mkt.unblk → o.mkt.unlock, o.mkt.block → o.mkt.lock
в комментариях C++ контракта и TS controller.

Проверки:
- C++ contract пересобран (dicoop/blockchain_v5.1.1:dev arm64): 0 errors,
  marketplace.wasm + .abi созданы
- jest invariants + resolver: 31/33 passed (2 fail — те же pre-existing
  baseline Story 11.1 cooptypes/operations.ts)
- cooptypes regen + schema/zeus/sdk cascade прошли успешно

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reviewed-on: #41
# Conflicts:
#	components/controller/src/extensions/marketplace/application/resolvers/marketplace-apl-reception.resolver.ts
#	components/controller/src/extensions/marketplace/application/resolvers/marketplace-inventory.resolver.ts
#	components/controller/src/extensions/marketplace/application/resolvers/marketplace-issuance.resolver.ts
#	components/controller/src/extensions/marketplace/application/resolvers/marketplace-return-claim.resolver.ts
# Conflicts:
#	components/controller/schema.gql
#	components/controller/src/extensions/marketplace/application/resolvers/marketplace-apl-reception.resolver.ts
#	components/controller/src/extensions/marketplace/application/resolvers/marketplace-inventory.resolver.ts
#	components/controller/src/extensions/marketplace/application/resolvers/marketplace-issuance.resolver.ts
#	components/controller/src/extensions/marketplace/application/resolvers/marketplace-return-claim.resolver.ts
#	components/desktop/extensions/market/install.ts
#	components/desktop/src/pages/Marketplace/OffererMyOffers/ui/OffererMyOffersPage.vue
Reviewed-on: #46
Резолв конфликтов:
- ledger2/operations.hpp: оставлен CONVERT_TO_MEMBER (marketplace2) +
  принята новая логика withdraw из dev (WITHDRAW_PENDING вместо BLOCK).
- ledger2/wallets.hpp: реестр расширен до 16 кошельков
  (USER_SHARED=6 с MARKETPLACE_ORDER_LOCK + COOPERATIVE=10 с WITHDRAW_PENDING).
- WalletProgramWidget.vue / WalletWidget.vue: убраны неиспользуемые helper'ы из marketplace2,
  принят рефактор dev.
- schema.gql + zeus/* (controller + sdk): полная регенерация после merge.

Сопутствующие правки:
- Восстановлен marketplace-branch-ownership.service.ts (отсутствовал в origin/marketplace2,
  но был импортирован из marketplace-return-claim.resolver.ts — взят из ветки
  chore/trustees-ownership 46f5269b89).
- Исправлена опечатка MARKETPLACE_KU_CHAIRMEN_SERVICE → MARKETPLACE_KU_CHAIRMAN_SERVICE
  в marketplace-desktop-grants.provider.ts.
- orderSelector.ts: blocked_amount → locked_amount, удалены did_convert/did_assign
  (привести selector к новой схеме MarketplaceOrderCreateTxSnapshot).
- Пересобраны cooptypes и inter (stale dist после merge).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Восстановленный после merge dev сервис не был добавлен провайдером
в MarketplaceExtensionApplicationModule — NestJS падал при boot:
"Nest can't resolve dependencies of the MarketplaceReturnClaimResolver
... Symbol(MarketplaceBranchOwnershipService) at index [2]".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
q-pa-lg на flat-карточке SignAgreementDialog — документ больше не прижат
к границе на странице кошелька пайщика.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Фон и текст тоста (.q-notification + .toast) теперь фиксированные тёмная
поверхность + светлый текст в обеих темах, а не инвертируемые
var(--p-ink)/var(--p-canvas). На dark-теме текст уходил в тёмный на тёмном
deep-tint фоне (#052e16 и т.п.) — нечитаемо. Цветные иконки по типу
остаются на токенах.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Конфликты modify/delete разрешены через delete (canon-направление):
- entities/CmdkMenu/model/store.ts — заменён на entities/CommandPalette
  (минимальный store isOpen/open/close/toggle).
- widgets/Desktop/SecondLevelMenuList — снесён, AppDrawer.rail заменяет.
- widgets/Desktop/WorkspaceMenu — снесён, WorkspaceSwitcher заменяет.
- widgets/Wallet/MicroWallet — снесён, RailUserCard заменяет.

NB: фильтрация пунктов меню в LeftDrawerMenu.filteredRoutes и
default.vue::paletteWorkspaces в dev — legacy meta.roles. В marketplace2
есть canon-grants (DesktopStore.isPageVisible/isWorkspaceVisible) —
переключение на canon потребует отдельного коммита поверх merge'а.
LeftDrawerMenu.filteredRoutes и default.vue::paletteWorkspaces переключены
с legacy meta.roles.includes(userRole) на DesktopStore.isPageVisible /
isWorkspaceVisible. Для grant-стола (marketplace, etc) видимость пунктов
сверяется с meta.requires против выданных бэкендом прав; для legacy-стола
canon-функции сами fall-back'аются на meta.roles по core-роли — поведение
по умолчанию сохранено.

meta.hidden и meta.conditions остаются дополнительными фильтрами
(participant/chairman/soviet используют conditions; capital — hidden).

Marketplace2-only: canon-grants (isPageVisible/isWorkspaceVisible)
существуют только в marketplace2; в dev этих функций в DesktopStore нет —
там фильтр остаётся legacy meta.roles.
- WorkspaceSwitcher: видимость столов через канон desktop.isWorkspaceVisible
  (grants) вместо локального meta.roles — marketplace-столы (Заказчик/Поставщик/
  ПВЗ) больше не видны до приёма ЦПП Советом. Был рассинхрон с CmdkMenu: при
  merge grants-канона переключатель в шапке забыли перевести на канон.
- OnboardingCoopAcceptCpp: убрана дублирующая шапка страницы; «Не подключено»
  и таймер сведены в один ряд в CouncilOnboardingCard (новый слот #status +
  проп subtitle); список шагов разрежён, чип «Ожидаем решение совета» больше
  не на всю ширину; экран «подключено» переведён на канон EmptyState с
  success-акцентом иконки.
- Стол заказчика: иконка «Готово к получению» fa-box-check → fa-box-open
  (первой нет в FA6 free — не рисовалась).
- Экосистема: meta.hidden=true (скрыта из меню/поиска; маршрут и код оставлены
  до публикации раздела).
- Дизайн-система: страница-витрина удалена полностью + вычищены ссылки на неё
  в extensions/market/CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Стол заказчика до подписи персональной оферты ЦПП показывал все
страницы (Каталог и т.д.) — провайдер грантов гейтил только на уровне
кооператива (coopAcceptance), а L3-подпись конкретного пайщика в гранты
не заходила. Страница подключения требовала Offer:read — тот же грант,
что и Каталог, поэтому не отличалась от рабочих страниц.

- backend resolveGrants: для orderer-роли спрашиваем
  MarketplaceOnboardingService.requires_gate; пока не подписал —
  вместо orderer-прав выдаём маркер Onboarding:orderer (симметрия
  L1-гейту председателя Extension:configure). Прочие роли не зависят.
- install.ts: страница подключения первой в столе, requires
  Onboarding:orderer; после подписи авто-скрывается (grant-driven).
- страница: один подписываемый документ (оферта), убран дубль-заголовок,
  refresh грантов после подписи (loadDesktop+loadExtensionRoutes), без
  поллинга и без loadDesktop на каждом переходе.
- unit-тест провайдера: L1/L3-гейты, 8/8.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Два бага L3-онбординга заказчика:

1) Председатель (и совет) видели рабочие страницы стола заказчика
   (Каталог/Мои заказы/Сводный) до подписи оферты: страницы требовали
   Offer:read / Order:read:own, а admin/board дают голый Offer:read и
   Order:read:all, который разворачивается в :own — гранты «протекали»
   на стол заказчика. Фикс: рабочие страницы требуют orderer-эксклюзивный
   Order:create (нет у admin/board, не покрывается :all-разворотом) —
   стол заказчика гейтится персональной подписью для любой роли.

2) Подпись оферты не долетала: getOnboardingState читал AgreementRepository
   (soviet::agreements3), а ЦПП «Стол заказов» — ПРОГРАММА (program_id=2),
   и wallet::signagree пишет подпись в wallet::users.programs[]. Источник
   истины — UserAgreementRepository.findProgram(program_id) (канон
   AgreementService.fetchProgrammatic). Из-за чтения не той таблицы
   requires_gate оставался true навсегда. Фикс: читаем программную подпись.

3) Авто-переключение после подписи: фронт коротко поллит getOnboardingState
   пока подпись синхронизируется в PG (wallet::signagree уже подтверждён
   цепочкой), затем loadDesktop+навигация — без ручного refresh.

Тесты: onboarding-service переписан под программный источник (6), grants (8) — 14/14.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Форма «Создать предложение» переверстана по канону MONO Design System как
VerticalStepper-мастер (Товар → Цена и наличие → Условия поставки → Изображения
→ Проверка; эталон CreateMeetForm): бизнес-язык вместо time_based/volume_based,
BaseRadioCard под отсечку, reserve-hint-space против layout shift.

Изображения товара (закрыт gap parent-UX, где MVP-epics уронили offer-фото):
- jsonb-колонка marketplace_offer.images ({bucket_key,content_hash,mime_type}[],
  индекс 0 = обложка); миграция v7 (DDL через synchronize);
- MarketplaceOfferImagesService на @UseBucket('stol-zakazov:images', AR31) —
  зеркало return-claim-канона; ключ content-addressed offers/<coop>/<owner>/<sha>;
- base64-инпут images в create/update DTO (≤8, ≤10МБ, jpeg/png/webp), декод+put
  в offer.service + cleanup сирот при провале записи;
- @ResolveField images → HMAC-signed URL (ленивый резолв, не в каждом list);
- codegen: schema.gql + zeus (controller+sdk) + offerSelector.

DRY: fileToBase64 вынесен в shared/lib/utils, два return-claim-дубля переключены.

Research вариантов продукта (off-chain PG-only, контракт не трогается; счётчики
3.4 на variant-уровень — самая инвазивная часть; FR18a снимается, не нарушается)
зафиксирован в плане проекта (Эпик 13, research-only).

Тесты: offer-service 43/43 (вкл. 3 новых на images), moderation+counters зелёные;
tsc контроллера 0, ESLint desktop 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Поля формы плотнее (dense + no-error-icon) по канону MONO.
- Блок количества не «прыгает»: чекбокс «без ограничения» сверху,
  поле количества всегда отрисовано (disable при безлимите), не v-if.
- Цена — два знака после запятой (рубли/копейки): regex 2 знака,
  ввод через точку или запятую, отображение через formatAsset2Digits.
- Изображения: выбор обложки кликом, непрозрачный бейдж «Обложка»,
  заметная кнопка удаления; обложка уходит в payload первой.
- Шаг «Проверка» — карточка-предпросмотр (главное фото + слайдер
  миниатюр + название/категория/цена/наличие/характеристики).
- Ошибки/успех через канон FailAlert/SuccessAlert (правый нижний угол,
  разворот GraphQL-ошибки вместо [object Object]); убран полноэкранный
  Loading — лоадер на кнопке.
- app.ts: express.json/urlencoded limit 2mb → 160mb (base64-загрузка
  изображений в GraphQL); зеркально в playbooks nginx.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Цена: символ валюты из useSystemStore().governSymbol (блокчейн-параметр
  root_govern_symbol), а не хардкод «₽» — при смене символа цепи фронт не правим.
- Количество и чекбокс «без ограничения» в одной строке (поле уже не во всю
  ширину); чекбокс выровнен, строка не прыгает.
- Уменьшен вертикальный gap между полями (p-4 → p-3) — ближе к канону.
- Убраны дубли заголовков шагов «Фотографии товара»/«Как набираются заказы»
  (их рисует VerticalStepper из step.description).
- Предпросмотр: q-carousel со свайпом и стрелками вместо нижних миниатюр
  (место под миниатюрами зарезервировано под будущие варианты товара).
- BaseRadioCard: hover по выбранной карточке больше не перекрашивает её
  в белый (selected:hover сохраняет выбранный фон).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- persistentState: при создании предложения форма (поля + текущий шаг)
  автосохраняется в LocalStorage и восстанавливается после перезагрузки;
  очищается при успешной публикации. Изображения не сохраняются (object-URL
  невалиден после reload, base64 не влезает в LocalStorage).
- Карусель предпросмотра: slide-right/slide-left транзишн — изображения
  сматываются вбок неразрывно, а не исчезают/появляются.
- Карточка-предпросмотр переверстана по канону: тень --p-shadow-card,
  название + категория в шапке, цена крупно слева и наличие чипом справа
  (pos/neg), блок «Характеристики» с разделителем и аккуратными строками.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Карусель предпросмотра: убран дефолтный padding слайда q-carousel
  (изображение во всю ширину), убраны точки-навигация — только свайп и стрелки.
- Черновик формы теперь сохраняет и изображения (base64 + data-URL превью
  после reload), если суммарный размер укладывается в бюджет (~2M base64
  символов); сверх бюджета или при QuotaExceeded — поля сохраняются без
  картинок. Сохранение дебаунсится (400мс), чтобы не сериализовать base64
  на каждое нажатие; coverIndex тоже в черновике.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Убрал из SuccessAlert технический id и английскую константу статуса
(result.status) — пользователю это не нужно. Сам SuccessAlert не трогаю
(правится в dev-ветке).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Поставщика после публикации возвращаем на стол «Мои предложения»
  (там сразу видна оферта «На модерации»), а не в каталог заказчика.
- CatalogOfferCard: медиа-область теперь q-carousel (свайп/стрелки,
  без точек, изображение во всю ширину) вместо одиночного <img>;
  проп images[] (обратно совместимо с preview).
- Прокинули изображения оферты в карточку на всех столах (мои
  предложения, модерация, каталог) — раньше всегда была заглушка.
- Единицы измерения по-русски: вынес общий справочник
  shared/lib/consts/marketplace-units (опции + короткая подпись) и
  helper shared/lib/utils/marketplaceOfferImages (обложка первой);
  убрал 4 дубля карты единиц и 2 дубля сортировки картинок (DRY).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Картинки оферт SPA грузит как <img> с другого origin (в dev — другой
порт, чем у бэкенда :3028). Глобальный helmet ставит CORP same-origin,
браузер режет загрузку (ERR_BLOCKED_BY_RESPONSE.NotSameOrigin) — CORS-
обход на это не влияет, это другой механизм. Переопределяем CORP на
cross-origin на успешном ответе ручки /api/storage: доступ к объекту
защищён HMAC-подписью с TTL в URL, а не origin'ом, поэтому ослабления
безопасности нет. Работает и в dev, и в prod независимо от раскладки
origin'ов.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
«Мои предложения»: info-карточка .banner с persist-скрытием, скелетоны вместо
лоудеров, канон-вкладки с deep-link ?status=, кнопка «Создать» в правом верхнем
углу через teleport, republish для WITHDRAWN. Форма создания/редактирования:
единый заголовок (без дублей), один .banner, sticky нижний бар «Отменить/Далее/
Сохранить» фикс-высоты, форматирование цены при префилле. REJECTED правится и
переотправляется через форму с показом причины отклонения.

Изображения: единая галерея — добавление (не замена), удаление и смена обложки
у любой картинки с самого начала. Backend отдаёт/принимает bucket_key (сохранить
существующее) либо base64 (новое); чужой ключ отклоняется. Signed-URL чтения
стабилизирован в окне 300с — не мигает при polling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Переверстка остальных страниц стола поставщика на MONO Platform v2 (как
«Мои предложения»):

- Входящие заказы: инфо-баннер с persist-скрытием, канон-`.tabbar` с deep-link
  ?status=, скелетоны вместо q-inner-loading, EmptyState, акцепт/отказ на карточке.
- Подготовка отгрузки, Подпись приёмки, История выплат: canon-таблица .table-wrap
  + TableSkeleton (каркас не дёргается), статусы через BaseBadge (точка+цвет из
  токенов), EmptyState с иконкой, ghost-refresh, инфо-баннер.
- Диалог подписи акта: BaseDialog + BaseButton + .banner вместо q-dialog/q-banner
  bg-primary.

Убраны дубли заголовков (заголовок в топбаре), легаси --mp-*/mp-role-*,
FontAwesome → Material q-icon. Скрываемый баннер вынесен в общий composable
useDismissibleBanner (DRY, 5 страниц).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Новая страница market-admin: соединяет список КУ (core getBranches) с их
ПВЗ-детализациями (marketplaceListKUDetails), председатель подключает участок
как пункт выдачи (адрес/контакты/режим работы → marketplaceDetailKU) и
управляет статусом/геокодингом. Диалог детализации переведён на канон
(BaseDialog/BaseButton + raw q-form с :rules), предзаполнение из карточки КУ,
правила времени/email вынесены в script (pug съедает backslash в inline-regex).
Статус-бейдж без dot (dot скрывал подпись), тултип с ошибкой геокода.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Стол ПВЗ переориентирован с «списка всех ПВЗ кооператива» на рабочее место
оператора ЕГО кооперативного участка (председатель КУ или доверенное лицо).

- entities/OperatorBranch: стор активного КУ оператора на marketplaceWhoAmI
  (+ детали ПВЗ + best-effort core getBranches) и шапка OperatorBranchBar
  (название/адрес/статус ПВЗ, без кодов; селектор при нескольких КУ).
- Операторские страницы (поставки/приёмка/маркировка/выдача/возвраты/склад/
  сводный) переведены на активный КУ из стора — убраны ручные поля «Код КУ»
  и чипы-коды, автозагрузка по активному участку.
- Новая страница «Доверенные лица»: председатель КУ добавляет/снимает
  доверенных (features/Branch/ManageTrusted → addTrustedAccount/
  deleteTrustedAccount); правки гейтятся chairman.
- Страница «ПВЗ кооператива» (PvzList) удалена со Стола ПВЗ — управление
  сетью ПВЗ живёт на столе администратора.
- Админ-страница ПВЗ: кнопка «Карта» (OSM, без ключа) для точки с координатами.

Бэкенд-фоллоуапы (вне scope): обогащение whoAmI именем/доверенными для
оператора-только-доверенного; auth addtrusted для trustee КУ (сейчас chairman
кооператива); грант-нюанс — admin видит Стол ПВЗ через :all.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7 операторских страниц + 4 диалога переведены на единый канон:
- сняты mp-role-operator/mp-card/mp-status-chip и токены --mp-*/rgba()
- FontAwesome (fa-solid) → Material q-icon
- статусы → BaseBadge; мультиселект-фильтры → тоггл-чипы (.chip);
  сводки → канон-плитки .kpi
- тулбары и действия → BaseButton/BaseInput/BaseSelect; панели → BaseCard;
  пустые состояния → EmptyState
- на каждой странице guard «вы не оператор кооперативного участка»
  (минимальный вид для не-оператора)
- q-table/q-tabs/q-stepper оставлены прямыми (канонятся quasar-canon.css;
  обёртки функционально недостаточны: сортировка/пагинация/мультиселект)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Все рабочие страницы и локальные диалоги Стола заказчика приведены к канону
MONO Platform v2: сняты mp-role-*/mp-status-chip/mp-filter-chip, токены --mp-*,
FontAwesome и raw rgba/hex → токены --p-*, Material q-icon, базовые обёртки и
канон-классы (.t-h2/.t-muted/.chip/.kpi-подобные плитки/EmptyState).

Страницы (полная перезапись по канон-скелету):
- MarketplaceCatalog: фильтр категорий → тоггл-чипы .chip, сортировка →
  BaseSelect, «Заказать» → BaseButton, пустой экран → EmptyState.
- MyOrders: фильтр статусов → тоггл-чипы, действия → BaseButton, EmptyState.
- OrdererConsolidated: группы-партии → hairline-карточки на --p-*, счётчик →
  .chip--accent + Material-иконка, refresh → BaseButton.
- OrdererReadyToReceive: канон-таблица + #no-data EmptyState, refresh → BaseButton.
- OrdererReturnClaims: статусы → BaseBadge, подача → BaseSelect+BaseButton,
  пустые секции → hairline-панели.
- OrderCreateDialog: полностью на BaseDialog/BaseInput/BaseSelect/BaseButton.

Диалоги подписания (точечно, как в Столе ПВЗ): SubmitReturnClaimDialog —
FA step-иконки → Material (edit/image/draw), --mp-→--p-; ReturnClaimDetailsDialog
— --mp-→--p-, rgba-бордеры → var(--p-line).

Осознанные исключения (зеркало реканона ПВЗ):
- q-table/q-stepper/q-list/q-item/q-timeline/q-infinite-scroll оставлены прямыми
  Quasar — обёртки функционально недостаточны, а quasar-canon.css их уже канонит.
- Общие доменные виджеты CatalogOfferCard/OrderCard/TakeoverDialog НЕ тронуты:
  они кросс-стольные (их рендерят также столы поставщика и админа), их реканон —
  отдельная задача «реканон виджетов Marketplace», поднимающая сразу 4 стола.

ESLint 0 на всех 8 файлах; sweep легаси по зоне стола — чисто; Rule 1 соблюдён.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
В MyOrdersPage в template-выражении был TS-каст
`(order as any).domainStatus as MarketplaceOrderStatusView` (тащился из
оригинала). Vue компилирует template-выражения в обычный JS, поэтому `as`
валит render-функцию в рантайме: `[Quasar] boot error: SyntaxError:
Unexpected identifier 'as'`. Так как install.ts импортирует MyOrdersPage
на старте (markRaw), падал весь boot приложения.

- MyOrders: доменный статус берём из items по id в script-хелперах
  (rowStatusLabel/isOrderCancellable) — типобезопасно, без `as` и без `any`.
- MarketplaceCatalog: убран избыточный `cc as ...[]` (api уже типизирован),
  `value as CatalogSort` → type-guard isCatalogSort по sortOptions.
- OperatorInventoryLabeling: `v as BarcodeStrategy/BarcodeFormat` в сеттерах
  BaseSelect → type-guard'ы isStrategy/isFormat по спискам значений enum.

Во всех мной написанных страницах/диалогах marketplace type-assertion-кастов
больше нет (import-алиасы `type X as Y` — не касты). ESLint 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Глобальный ::selection красит текст в var(--p-ink) (тёмный на свет-теме)
поверх полупрозрачного фона — на тёмной поверхности тоста выделенный текст
пропадал. Scoped-override для .q-notification/.toast: полупрозрачная светлая
подложка + светлый текст, читаемо на всех типах тостов.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Аудит кастов по Столу оператора (ПВЗ) и Столу администратора:
- template-кастов 0, `as any` 0 — опасных/рантайм-фатальных нет;
- два реальных хака в admin-страницах убраны без каста:
  - BoardAgendaWriteoff: `(... ) as never` — локальный строковый WriteoffStatus
    не стыковался с SDK-входом statuses (enum MarketplaceWriteoffProposalStatus).
    Добавлена явная таблица STATUS_TO_ENUM (5 видимых совету статусов → enum),
    statuses строится map'ом. Тип доказан образцом соседней AdminWriteoffsPage,
    которая так же кормит listWriteoffProposals enum-массивом.
  - OnboardingCoopAcceptCpp/composable: `stepToRegistryId[meta.id] as number` →
    typeof-guard (робастно к noUncheckedIndexedAccess).

НЕ тронуты api-слойные `return result as <ViewType>` (OperatorIssuance/Reception/
InventoryLabeling/ReturnClaims, AdminWriteoffs): это НЕСУЩЕЕ сужение Zeus-скаляров
(DateTime/ID типизированы Zeus как unknown; view-тип сужает их до string/
NonNullable через Omit&{…}). Убрать = сломать типы (unknown не присваивается
string) или добавить бессмысленный рантайм-.map(). Направление каста безопасное
(unknown→точный тип), у каждого инлайн-обоснование. Это не `as any`.

ESLint 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Вынесен domain-компонент shared/ui/domain/PageHint: инкапсулирует хук
useDismissibleBanner (скрытие через LocalStorage без мигания) и канон-разметку
.banner.banner--info + крестик. Текст подсказки — через слот, ключ хранения —
prop storage-key. Устраняет ~28-кратное повторение inline-баннера по столам (DRY).

Стол заказчика (5 страниц): убран внутристраничный дубль-заголовок «Каталог /
Каталог» (.__head с .t-h2 дублировал заголовок навигации) — вместо него
PageHint с прежним пояснительным текстом и крестиком.

Стол поставщика (5 Offerer* + AdminIssuancePoints): inline-баннер + локальный
хук/SCSS заменены на PageHint без изменения визуала.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
8 страниц стола ПВЗ (market-pvz): внутристраничный .__head с .t-h2 (дублировал
заголовок навигации) заменён на закрывающуюся подсказку PageHint с прежним
пояснительным текстом. На «Маркировке» сохранён .no-print. На «Доверенных лицах»
постоянный инфо-баннер стал закрывающимся (условный state-плейсхолдер оставлен).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
8 страниц стола администратора (market-admin) переведены с legacy-вёрстки
(mp-role-admin, токены --mp-*, FontAwesome, raw q-btn/q-card/q-input/q-select,
text-h5-заголовки) на канон MONO Platform v2:

- внутристраничный дубль-заголовок → закрывающаяся подсказка PageHint;
- --mp-* → --p-*; FontAwesome → Material q-icon;
- q-btn → BaseButton, q-card → BaseCard, q-input → BaseInput, диалог → BaseDialog;
- q-chip-статусы → BaseBadge; KPI-карточки → прототип .kpi;
- мультистатус-фильтры → канон .chip-тогглы; пустые экраны → EmptyState.

Вся бизнес-логика (модерация, whitelist, проекты списания, повестка совета,
выплаты, онбординг ЦПП) сохранена без изменений. Множественный q-select в
whitelist оставлен raw (BaseSelect — single-value), канонизируется quasar-canon.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
В секцию «Баннеры» добавлен пример закрывающейся подсказки PageHint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Свежие desktop-фиксы из dev: форматирование сумм в тостах ошибок (formatAssetsInText),
SSR-фиксы Sentry/темы, гонка Teleport-host шапки + обновление грантов после регистрации,
плейсхолдеры форм ИП/организации, фикс выбора BaseRadioCard при наведении.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
На странице пунктов выдачи (стол администратора) диалог карты падал с
«Failed to resolve component: ol-map»: boot/map.ts с регистрацией
vue3-openlayers никогда не был подключён в quasar.config (массив boot),
поэтому ol-* не резолвились и карта не рисовалась.

OpenLayers в проекте больше нигде не используется — переводим shared/ui/Map
на Yandex Maps JS API (как рабочий виджет KUMapWithList) и сносим OpenLayers:

- shared/ui/Map/Map.vue: single-point карта на ymaps (метка — pin.svg),
  тот же публичный API long/lat; деградированный баннер без ключа.
- shared/lib/yandexMaps: загрузчик SDK вынесен из widgets/KUMapWithList в
  shared (DRY) — общий для виджета списка ПВЗ и single-point карты.
- удалён boot/map.ts (мёртвый, не в boot) и зависимость vue3-openlayers.

Lockfile подровняется при следующей установке (vue3-openlayers пока остаётся
orphan-записью).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Зависимость удалена — чистим устаревший пример тяжёлой библиотеки в
комментарии про отключение vue-tsc в dev. Только комментарий, рестарт
dev-сервера не требуется.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Переинсталл (--no-frozen-lockfile) после удаления vue3-openlayers из
desktop/package.json в прошлой сессии: lockfile синхронизирован, node_modules
восстановлен (ts-node/eslint), что разблокировало канон-цикл
generate-schema/generate-client.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Заказ ссылается на предложение и ПВЗ по id и не нёс отображаемых реквизитов —
карточки столов показывали сырой offer_id и служебный braname. Резолверы
listMyOrders/listSupplierOrders/getOrder теперь обогащают DTO заказа:
product_name и unit_of_measure из предложения (батч offerRepo.findByIds без
N+1), delivery_point_address из детализации ПВЗ (kuRepo.findByCoopname).
Поля nullable, best-effort: нет предложения/ПВЗ — остаются пустыми, клиент
показывает запасной вид.

Схема и zeus-клиент перегенерированы, orderSelector дополнен; offer-репо
получил findByIds (+ моки в unit-тестах).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
OrderCard переведён на канон MONO Platform v2: BaseCard/BaseBadge/BaseButton,
токены --p-*, моноширинный номер, статус-пилюля с точкой; убраны --mp-*-токены,
прямой q-card, тени и кастомный .mp-status-chip.

Столы заказчика («Мои заказы») и поставщика («Входящие заказы») показывают
название товара (product_name) вместо UUID предложения, единицу измерения
через общий marketplaceUnitShort и адрес ПВЗ (delivery_point_address) вместо
служебного идентификатора — с запасным значением, если реквизит недоступен.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- delivery_point_name на заказе: live-резолв наименования кооперативного
  участка (org.short_name) по braname через core ORGANIZATION_REPOSITORY,
  без денормализации (копия отставала бы при переименовании КУ).
- buildDisplayFields вынесен из order-резолвера в общий
  MarketplaceOrderDisplayService (DRY): теперь обогащает и оба стола, и
  ленты выдачи (listIssuancesByBraname, listMyReadyToReceive) — товар,
  единица, наименование и адрес ПВЗ. Best-effort, без N+1.
- schema/zeus/sdk-селектор регенерированы под новое поле.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- OrderCard: ПВЗ двумя строками — наименование КУ (основная) + адрес
  (приглушённая), запасной вариант braname если оба пусты.
- toOrderCardModel вынесен в виджет OrderCard (DRY): «Мои заказы» и
  «Входящие заказы» больше не дублируют STATUS_TO_CARD и маппинг.
- Таблицы выдачи (оператор / готово к получению): колонка «Товар»,
  единица измерения у количества, ПВЗ через наименование·адрес.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
OrderCard переверстан: заголовок и бейдж в одной строке, №·дата — отдельной
строкой на всю ширину (раньше «висла» вокруг бейджа); Сумма крупным числом-
героем + Кол-во; ПВЗ отдельным блоком (наименование КУ + адрес). Убрано
задвоение статуса — раньше показывались и грубый card-status («Размещён»), и
доменный («Ждёт поставщика»).

Единый orderStatusDisplay (подпись + вариант бейджа) в виджете OrderCard —
устранены три дубля STATUS_LABEL (MyOrders / OperatorIssuance /
OrdererConsolidated). Доменный статус и cancellable прокинуты в модель карточки;
действия заказчика — отмена по cancellable, без нерабочих «Открыть»; добавлен
readonly для обзорного сводного заказа.

Фильтры столов заказчика и поставщика переведены на канон PageTabs.

Фикс бага: вкладка «Ждут акцепта» (поставщик) и «Ждут поставщика» (заказчик)
теперь покрывают оба статуса ожидания акцепта — ACCEPTED_PENDING_SUPPLIER и
ACCEPTED_PENDING_SUPPLIER_INDIVIDUAL. Раньше индивидуальный заказ с кнопкой
«Принять» в эти вкладки не попадал (отдельная вкладка «Индивидуальные» путала).

OrdererConsolidated переведён на общий toOrderCardModel — раньше показывал сырой
offer_id как заголовок и braname вместо наименования+адреса ПВЗ.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Каталог: чип-фильтр категорий + плавающая сортировка → канон PageTabs
  (категории как вкладки со счётчиком) + BaseSelect в слоте #actions.
- «Мои заказы»: вкладки покрывают ВСЕ статусы заказа (был баг — заказ в
  SUPPLY_PREPARED не попадал ни в одну вкладку, кроме «Все»). Добавлена
  вкладка «Готовятся» (ACCEPTED/SUPPLY_PREPARED/ACCEPTED_TO_COOP), «Отменены»
  расширена истёкшими циклами и возвратами.
- «Входящие заказы» поставщика: аналогичное исчерпывающее покрытие статусов.
- Кнопка «Обновить» вынесена в общий виджет widgets/Marketplace/RefreshButton
  (дублировалась ~15× по столам, по-разному свёрстана) и поставлена в шапку:
  на «Сводный заказ» и «Готово к получению» — через канон Teleport в
  #header-actions-host, у поставщика — в слот вкладок PageTabs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Бейдж заказа в SUPPLY_PREPARED показывал «Поставка готовится» (warn), хотя
доменно это завершённое состояние: партия уже сформирована (вариант доставки
выбран, для экспедитора выпущена ТТН) и готова к отгрузке/приёмке. Лейбл
расходился со страницей партий («Готова к отгрузке») и вкладкой «Поставка
готова» у поставщика. Приведено к «Поставка готова» (info). Вкладка заказчика,
объединяющая принят/готова/у кооператива, переименована «Готовятся» → «В работе»
во избежание противоречия с бейджем.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Страница была read-only и выглядела «мёртвой»: ни кнопок, ни подсказки, что
делать. Добавлена колонка-подсказка «Следующий шаг» по статусу+варианту партии
(самовывоз → привезти на КУ; экспедитор → передать по ТТН; идёт приёмка;
принято кооперативом) и честный баннер. Активная подготовка (выбор варианта,
ТТН, QR-передача) — доработки Эпика 14, здесь пока не реализованы.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Раньше acceptIndividual авто-формировал партию Варианта А (самовывоз, без ТТН)
через synthesizeIndividualShipment — поставщик был лишён выбора доставки. Теперь
синтезируется только заявка (cycle) в статусе ACCEPTED, а партию поставщик
формирует явно (marketplaceCreateShipment) с выбором варианта — единый путь с
пакетными заказами. Order после accept остаётся ACCEPTED до явного формирования.

Убраны неиспользуемые зависимости shipmentCreate + импорт варианта; spec и
комментарий forward-guard приведены в соответствие. tsc зелёный.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Страница «Подготовка отгрузки» получила раздел «К формированию»: принятые
(ACCEPTED) заказы группируются по заявке→КУ; по кнопке «Сформировать партию»
открывается диалог с выбором способа доставки ПО КАЖДОМУ КУ — самовывоз
(Вариант А) или экспедитор+ТТН (Вариант Б, форма из 7 полей) — и вызовом
marketplaceCreateShipment. Раздел «Сформированные партии» — прежняя таблица с
колонкой «Следующий шаг». Refresh унифицирован на общий RefreshButton.

Логика группировки и типы вынесены в lib/shipmentFormation.ts (переиспользуются
диалогом и страницей). Закрывает разрыв Story 5.1: единый явный путь отгрузки
для индивидуальных и пакетных заказов (вместе со Story 14.1 на бэкенде).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GraphQL-enum MarketplaceShipmentDeliveryVariant принимает значения по ИМЕНИ
(SELF/EXPEDITOR), а не по внутреннему коду 'A'/'B' — самовывоз падал с
"Value 'A' does not exist in enum". Берём значения из Zeus-enum SDK.
Notify.create заменён на канонические SuccessAlert/FailAlert (тост справа внизу).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Основной сценарий ПВЗ: поставщик показывает QR сформированной партии,
оператор сканирует — идентификатор подставляется и акт приёмки открывается
без ручного ввода shipment_id.

Два переиспользуемых канон-виджета (DRY, под Story 14.4 заказчик→выдача):
- HandoffQr — генерация QR из идентификатора (qrcode, toDataURL) + копируемый
  код как запасной путь;
- QrScanner — нативный BarcodeDetector (Chromium) + камера, с ручным вводом
  кода если камера/API недоступны; зависимостей не добавляем.

Поставщик (OffererSupplyPreparation): колонка «Передача» с кнопкой QR на
партии в статусе SUPPLY_PREPARED/RECEPTION_IN_PROGRESS → диалог с QR.
Оператор (OperatorReception): кнопка «Сканировать QR» → диалог сканера →
onQrScanned подставляет код и создаёт акт приёмки.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Зеркальный сценарий выдачи на ПВЗ, переиспользует HandoffQr/QrScanner (DRY):
- заказчик (OrdererReadyToReceive): колонка «Получение» с кнопкой QR →
  диалог с QR заказа (value = order id);
- оператор выдачи (OperatorIssuance): кнопка «Сканировать QR заказа» →
  сканер → onQrScanned находит заказ в ленте КУ и запускает нужный шаг
  (ACCEPTED_TO_COOP → открыть выдачу, READY_TO_RECEIVE → завершить);
  заказ не на этом КУ / не в статусе выдачи → FailAlert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Системно заменён прямой Notify.create на канонические алерты (правый нижний
угол, тёмный фон, единый визуал) во всех 10 страницах marketplace:
positive→SuccessAlert, negative→FailAlert(e) (сам извлекает GraphQL-ошибку),
info/warning→NotifyAlert. Notify убран из quasar-импортов.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Убран ручной ввод shipment_id + кнопка «Создать акт» (ломались: оператор
копировал обрезанный для показа id → invalid input syntax for type uuid).
Вместо этого — список ожидающих приёмки партий КУ (статус SUPPLY_PREPARED из
listShipmentsByBraname) с кнопкой «Создать акт приёмки» на каждой; QR-сканер
остаётся для приёмки с телефона. Оба пути зовут createAplReception({shipment_id}).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Виджет TTNPrintPreview подключён в стол поставщика: для партий Варианта Б
(экспедитор) в «Сформированных партиях» — кнопка «ТТН», открывающая накладную
с составом (позиции из заказов SUPPLY_PREPARED партии). Печать через изолированный
iframe (печатается только лист А5, без хрома приложения) + скачивание
самодостаточного HTML (в т.ч. «Сохранить как PDF» из браузера). Без новых
зависимостей. Виджет приведён к канону: BaseButton вместо q-btn, токены --p-*.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Оператор принимает самовывоз поставщика, не сформировавшего партию заранее.
Backend: marketplaceCreateExpressReception синтезирует SELF-партию из ACCEPTED-
заказов поставщика на этом КУ (per-cycle) и открывает приёмку через
существующий create(); marketplaceListExpressPickupsByBraname — лента
поставщиков, ожидающих самовывоза на КУ (агрегат). Добавлен фильтр
delivery_braname в order-repo. Жёсткий 1:1-акцепт всех КУ цикла здесь намеренно
не применяется — принимается только привезённое на этот КУ.

Desktop: на «Приёмке» раздел «Самовывоз по факту» со списком поставщиков и
кнопкой «Принять самовывоз». Двухподписный акт и переход в ACCEPTED_TO_COOP —
без изменений.

Codegen: schema.gql + zeus regenerated, SDK-обёртки CreateExpressReception /
ListExpressPickupsByBraname.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Код привязан к аккаунту человека, а не к партии/заказу («как в Ozon»):
один показ = оператор резолвит аккаунт против ленты своего КУ и принимает/
выдаёт разом все ожидаемые единицы этого человека.

- shared/lib/marketplace/handoff-token: encode/decode токена
  `mp1:<kind>:<coopname>:<account>` (pickup|receive). Детерминирован от
  личности+намерения → генерируется заранее/оффлайн, backend не минтит.
  Привязка к КУ — на стороне оператора (резолв против своей ленты); coopname
  отсекает чужой кооператив.
- Поставщик: «Мой код для ПВЗ» (pickup-токен) на «Подготовке отгрузки».
- Заказчик: «Мой код получения» (receive-токен) на «Готово к получению».
- Оператор приёмки: скан pickup-кода → диалог «Принять всё» (сформированные
  партии SUPPLY_PREPARED + самовывоз по факту разом).
- Оператор выдачи: скан receive-кода → панель всех заказов заказчика на выдачу;
  пошаговое открытие/завершение (каждый шаг — своя двойная подпись).
- Legacy-QR (сам shipment_id/order_id) продолжает работать — decode вернёт null
  и код падает на прежний путь по id (обратная совместимость).

Подписанный/одноразовый токен-сервис с TTL остаётся доработкой (mp2): аккаунт-
биндинг уже даёт Ozon-UX, криптоподпись пользователь явно отложил.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Обратной совместимости в разработке нет — мусор не оставляем. Account-bound код
становится единственным путём передачи на ПВЗ.

- Оператор приёмки/выдачи: убран fallback-декод по id; нераспознанный скан
  отвергается с понятной ошибкой. Только account-bound токен.
- Поставщик: убран per-партия QR (кнопка/диалог/колонка «Передача» в таблице
  сформированных партий) — остаётся единый «Мой код для ПВЗ».
- Заказчик: убран per-заказ QR (кнопка/диалог/колонка «Получение») — остаётся
  единый «Мой код получения».
- handoff-token: doc-комментарий decode без упоминания legacy-пути.

Десктоп-путь без камеры сохранён: выбор партии «Создать акт приёмки» из списка
ожидающих и кнопки выдачи в таблице (это канон, не QR-fallback).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Уточнённая модель приёмки на ПВЗ (Story 14.3): «акцепт ≠ привезли»,
потолок приёмки = акцепт, партия/ТТН — разметка, не лимит.

R4: единый базис. Новый read-query marketplaceListSupplierPickupOrders
(braname, offerer_account) отдаёт Order'ы поставщика на КУ в статусах
SUPPLY_PREPARED (по партии/ТТН) и ACCEPTED (добор по акцепту) через
MarketplaceOrderDisplayService. Суммирование лент партии+express убрано —
лента одна, партия — разметка через статус.

R7a: диалог приёмки = плоский список единиц имущества с разделителем
«задекларировано в партии (ТТН) / добор по акцепту», без партийной группировки.

R7: per-unit выбор «принять»; партия без выбранных единиц не создаётся и
ждёт (кейс экспедитора). Добор — отдельный тумблер.

R5: правка факта per-Order на приёмке (поле «факт», потолок = заказано).
Инвариант-guard в buildFactQuantity: fact > order.quantity → отказ
(«сверх акцепта не принимаем»).

Остаётся: per-unit факт/выбор для добора (express), отмена акцепта
поставщиком R6 (контракт).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Косметика/канон/тексты без backend (дефекты предыдущих итераций):
- A1: суммы через formatAsset2Digits (срез нулей + ru-группировка) вместо
  сырого "750.0000 ₽" — оба диалога подписи приёмки, диалог открытия
  выдачи, столы выдачи оператора и заказчика, форма приёмки.
- A2: убран технический жаргон из инфо-виджетов (registry_id, signiss,
  o.mkt.*, "ключом текущей сессии", "backend отправит на цепь") —
  заменён одной бизнес-строкой.
- A3: SignAplReceptionChairmanDialog переведён с сырого q-dialog/q-btn на
  BaseDialog/BaseButton (Правило №1); формы приёмки/подписи развёрнуты
  на весь экран (maximized).

Класс B (выдача-кошмар: финальная подпись на стороне заказчика, цена,
ФИО вместо username, сверка-таблицы) — следующими шагами, см. epics.md
блок "КОРРЕКЦИИ ПОСЛЕ ОЧНОГО ПРОГОНА".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Кардинальный дефект: финальную подпись заказчика держал стол оператора
(2-й скан штрих-кода + ввод приватного ключа ЗАКАЗЧИКА на устройстве
оператора). Перенесено корректно, без изменения контракта:

- signiss1 не берёт actual_quantity (только акт), signiss2 берёт →
  relocation чисто backend+frontend.
- openIssuance принимает actual_quantity (правка оператора): зашивается в
  акт председателя и сохраняется снапшотом issuance_fact на открытии.
- finalizeIssuance больше не принимает actual_quantity/delivery_signer —
  backend берёт их из заказа (issuance_fact, chairman_account); заказчик
  факт не редактирует.
- Полный codegen: schema.gql + zeus (controller+sdk).
- Frontend оператора: IssueActOpenDialog получил таблицу сверки кол-ва +
  «Показать акт»; IssueActFinalizeDialog удалён, «Завершить выдачу»/2-й
  скан убраны, READY_TO_RECEIVE на столе оператора = «Ждём подпись
  заказчика».
- Frontend заказчика: новый OrdererFinalizeIssuanceDialog (read-only
  сверка + «Показать акт» + «Подписать и получить» своим ключом) на столе
  «Готово к получению».

Правка ЦЕНЫ (не только кол-ва) — отдельным шагом B2 (затрагивает контракт).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Оператор правит при открытии не только количество, но и цену за единицу
(привезли хуже/замена → принимаю со скидкой; цену меняю на месте).
Канон-имя параметра — actual_unit_price (asset), достраивает пару к
actual_quantity.

Контракт:
- signiss2: +actual_unit_price; fact_cost = actual_quantity × actual_unit_price
  (ветки возврата/доплаты по стоимости — без изменений).
- signchair: +actual_quantity +actual_unit_price; кооператив книжит
  поставщику o.mkt.purch на fact_cost (итоговая стоимость к получению),
  вместо o.total_cost. ABI собран и проверен.

cooptypes: ISignIss2 +actual_unit_price; ISignChair +actual_quantity
+actual_unit_price.

controller:
- issuance: open захватывает actual_unit_price → issuance_fact.fact_unit_price;
  finalize шлёт его в signiss2 (заказчик факт не редактирует).
- reception: fact-entry несёт fact_unit_price; signchair получает кол-во+цену
  из снапшота; total_amount/проводка от факт-цены.
- diff_state выдачи теперь по СТОИМОСТИ (цена могла измениться, не только кол-во).
- DTO + GraphQL ObjectType + selectors + полный codegen (schema/client/sdk).

desktop:
- CorrectionTable: редактируемая колонка цены (предзаполнена), эмит factPrice.
- IssueActOpenDialog: правка цены при открытии выдачи.
- OperatorReceptionPage: поле цены per-позиция при открытии приёмки.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Коррекция фактического количества и цены теперь доступна оператору на ВСЕХ
путях приёмки без исключений, включая express-самовывоз (добор по акцепту и
"самовывоз по факту" без партии). Раньше express принимался по цене и
количеству заказа без возможности правки.

- createExpressReception проводит fact_quantity_per_order сквозь create →
  signsupp/signchair (контракт не меняется — переиспользуется B2).
- desktop: addon-юниты в диалоге приёмки редактируемы (кол-во+цена); кнопка
  "Принять самовывоз" открывает ту же форму коррекции, что и QR-скан.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
B3 — backend-обогащение display-сервиса именами участников (ФИО физлица/ИП
или short_name организации) через UserCertificateInteractor. Резолв только в
уже авторизованных резолверах приёмки/выдачи (оператор/председатель КУ или сам
поставщик), приватные имена не утекают в общие ленты заказов.
- MarketplaceOrder: orderer_name + supplier_name (заполняются в лентах выдачи).
- MarketplaceAplReception: offerer_name; позиции fact_quantity_per_order несут
  product_name + unit_of_measure (для таблицы сверки).
- order-repo: findByIds (батч для обогащения позиций приёмки).
- desktop: выдача — колонка «Заказчик» = ФИО; приёмка — заголовок = наименование
  поставщика.

B4 — общий read-only виджет ReceptionLinesTable (позиции/кол-во/цена/сумма) +
кнопка «Показать акт» (рендер html подписываемого документа) в диалогах подписи
поставщика и председателя. У председателя сверка строго на чтение.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Кнопка «Создать акт приёмки» на ожидающей партии звала createAplReception
напрямую, без формы сверки — оператор принимал по заказанным кол-вам и
исходным ценам (риск «приняли как 100, хотя привезли 80»). Перенаправлена в
openPickupForSupplier («Принять партию») — тот же диалог коррекции кол-ва/цены,
что и QR-скан и «самовывоз по факту». Коррекция доступна на ВСЕХ путях приёмки
без исключений. createReceptionForShipment удалена. Фронт-only, codegen не нужен.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- desktop API (OperatorReception/OperatorIssuance): все обёртки client.Query/
  Mutation принимают `data: IInput['data']` из SDK и передают `{ data }` целиком,
  без позиционных аргументов и разворачивания полей (канон desktop). Call-sites
  обновлены (приёмка/выдача/подпись/председатель КУ).
- handoff-token: префикс кода передачи `mp1` → `blago`.
- удалён глобальный src/shims-marketplace-qr.d.ts: типы `qrcode` через @types/qrcode
  (dev-dep), `BarcodeDetector` — узким локальным типом в QrScanner (нет в lib.dom).

Фронт-only, codegen не нужен.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Денежные потоки заказа деноминируются на членском «Стола заказов»
(w.mkt.member), а не на универсальном членском, и доплата по факту НЕ
списывается с паевого напрямую:

- ledger2: новый кошелёк w.mkt.member (USER_SHARED, program 2) + операции
  o.mkt.conv (паевой→членский «Стола заказов», Дт 80/Кт 86) и o.mkt.lockm
  (членский→резерв заказа).
- signiss2 при факт>план: o.mkt.conv(diff)+o.mkt.lockm(diff) вместо прямого
  списания с паевого; при факт<план остаток → w.mkt.member.
- o.mkt.unlock (отмена/decline/expire/недовыдача) и o.mkt.return (гарантийный
  возврат) переориентированы с w.wal.member на w.mkt.member.
- стандарты p.mkt.supply/p.mkt.return: кошельки, операции, проводки и проза.
- cooptypes wallets.generated.ts регенерирован (17 кошельков, 8 mapping).

Контракт собирается (constexpr-валидаторы реестра ledger2 зелёные); деплой
wasm/abi — отдельным шагом пайплайна. Подпись signiss2 не менялась — conv/lockm
суть ledger2-операции, не контракт-экшены: cooptypes-actions/codegen не затронуты.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
После переориентации денежных потоков на w.mkt.member (ревью PR #50 #1):
- I5 reserve-consistency: добавлен o.mkt.lockm (w.mkt.member→w.mkt.order) как
  приход резерва; o.mkt.unlock теперь сверяется по walletTo=w.mkt.member.
- spec-фикстуры unlock/return переведены на w.mkt.member.

27 тестов проходят (как на baseline). 2 теста I3 (счёт 10) остаются красными —
предсуществующий разрыв: cooptypes/ledger2/operations.ts не содержит канонических
marketplace-операций (только legacy o.mkt.supply/recv), из-за чего MARKETPLACE_OP_CODES
их не распознаёт. Полный синк operations.ts с operations.hpp (+ o.mkt.conv/lockm) —
отдельный фоллоуап (затрагивает process-hash-locator/ledger2.service).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
operations.ts — рукопашное зеркало contracts/.../operations.hpp OPERATION_REGISTRY
(генератора нет: gen:from-cpp покрывает только wallets.generated.ts). Зеркало
устарело — держало legacy клиринговые o.mkt.supply/o.mkt.recv (p.mkt.reqst),
которых нет в контракте. Из-за этого MARKETPLACE_OP_CODES (фильтр по
contract==='marketplace') не знал o.mkt.purch/return/... → инвариант I3
(счёт 10 Материалы) считал их немаркетплейсными и падал.

- operations.ts: заменены 2 legacy-записи на 9 canonical marketplace-операций
  (lock/conv/lockm/unlock/purch/payout/consum/return/wroff), включая новые из
  R#1 PR #50: o.mkt.conv (паевой→членский «Стола заказов») и o.mkt.lockm
  (добор резерва с членского). unlock/return → w.mkt.member (членский программы),
  синхронно с operations.hpp.
- process-hash-locator.ts: удалён мёртвый locator p.mkt.reqst (больше не
  референсится из реестра).
- marketplace-process-trace-coverage.spec.ts: EXPECTED-списки приведены к
  9 операциям; walletTo unlock/return w.wal.member→w.mkt.member.

Тесты: marketplace-ledger2-invariants.spec.ts + marketplace-process-trace-coverage.spec.ts
= 73 passed (ранее 2 I3-теста были красные на baseline). Сигнатуры actions/ABI
не менялись (операции ledger2 внутренние) — regen schema/sdk не требуется.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Глобальный ::selection был --p-primary-soft (teal 10% alpha) — болотный,
почти невидим. Меняем на solid --p-accent + белый текст: видно на любом фоне.
Тост-специфичные ::selection-override (.q-notification/.toast) убраны —
accent читаем и на тёмной поверхности тоста, выделение единое везде.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Страница «Подключение ЦПП Стол заказов»:
- Лоадер на «Объявить»: карточка использовала локальный submitting и
  закрывала диалог сразу после синхронного emit('step-submit'), пока
  реальная async-отправка проекта решения шла в composable (handleStepSubmit)
  с отдельным submitting, не пробрасываемым в карточку. Итог — диалог
  схлопывался мгновенно, лоадера не было нигде. Теперь submitting проброшен
  из composable в CouncilOnboardingCard: кнопка «Объявить» крутит лоадер,
  диалог держится открытым до завершения транзакции и закрывается по watcher'у
  (submitting true→false).
- Ширина: убран max-width 860px у карточки онбординга — растягивается на всю
  ширину страницы.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
После утверждения Советом обоих документов ЦПП «Стол заказов» расширение
подключается и grants в getDesktop меняются, но фронт перечитывал desktop
workspace только на init и при install/enable расширения — для онбординга
триггера не было. Итог: рабочие столы и страницы Стола заказов появлялись лишь
после ручной перезагрузки страницы.

Добавлен watcher на isCompleted в useMarketplaceOnboarding: при переходе
false→true (оба шага completed) вызывается desktopStore.loadDesktop(), который
перечитывает getDesktop с новыми grants. Меню/столы появляются сразу.
loadDesktop идемпотентен (мерджит маршруты); watch не срабатывает на initial
mount, только на реальном завершении онбординга в течение сессии.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
На столе заказчика после подписи оферты ЦПП requires_gate становился false →
gate скрывался → на время loadDesktop + переустановки маршрутов + router.push
мелькал баннер «Вы уже подключены к Столу заказов». Пользователь не хочет
промежуточный экран — подписал, сразу на стол.

Добавлен флаг redirecting: выставляется в начале onAccept, прячет баннер
alreadyDone и держит q-inner-loading спиннер до самой навигации. Убран
SuccessAlert-поздравление — редирект на стол и есть подтверждение. Баннер
«уже подключены» остаётся для легитимного захода на онбординг-URL уже
подключённым пайщиком (redirecting=false).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Страница модерации открывала по клику OfferDetailsDialog с полным описанием и
кнопками одобрить/отклонить — лишний клик. Вдобавок диалог показывал
плейсхолдер вместо картинок: selectedDetail не маппил preview/images, а диалог
рендерит offer.preview (карточка же тянет carousel через marketplaceOfferImageUrls).

Теперь решение принимается прямо в карточке:
- CatalogOfferCard получил слот #details (после описания) — родитель кладёт туда
  доп. данные, не открывая отдельный диалог.
- Модерация заполняет #details мета-строками (категория, тип отсечки, гарантия,
  поставщик) и выносит обе кнопки (Отклонить + Одобрить) в #actions карточки.
- Клик-открытие диалога, selectedDetail, openDetails и сам OfferDetailsDialog с
  страницы убраны; подсказка «нажмите на карточку» тоже.
- Подписи cycle_type вынесены в общий marketplaceCycleLabel (shared/lib/consts) —
  карта дублировалась в OfferDetailsDialog/CreateMarketplaceOffer/OrdererConsolidated.

Виджет OfferDetailsDialog больше нигде не используется (осиротел) — оставлен на
случай detail-view в каталоге; удалять отдельным решением.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Виджет OfferDetailsDialog больше нигде не используется (модерация перешла на
  inline-карточку) — удалён.
- CatalogOfferCard: prop clickable (default true) гейтит mp-card--interactive
  (cursor-pointer + hover-zoom) и emit click. Каталог и «Мои предложения» клик
  используют — у них без изменений; модерация передаёт clickable=false, чтобы
  карточка не выглядела кликабельной (по ней ничего не открывается).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Слот #no-data q-table выравнивает контент влево, из-за чего EmptyState
(«Ожидаемых поставок нет») прижимался к левому краю. Обёрнут в full-width
flex-контейнер с justify-content:center.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Тот же фикс, что для «Ожидаемых поставок»: слот #no-data у q-table
выравнивает контент влево, а .empty — блок по ширине контента.
Оборачиваю EmptyState в .<block>__nodata (full-width flex center) на
страницах Приёмка / Выдача / Склад участка / Выплаты совета / Готово
к получению.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
selectWorkspace() ставит isWorkspaceChanging=true (full-page WindowLoader
поверх router-view), а сбрасывал флаг только goToDefaultPage(). Обычное меню
(WorkspaceSwitcher) зовёт goToDefaultPage и лоадер снимается; command palette
(onSelectWorkspace/onSelectPage) делал прямой router.push без сброса флага —
лоадер висел вечно. Тот же leak в ConnectionAgreementStepper.goToQuickClient.

Снимаем флаг через setWorkspaceChanging(false) в .finally() после навигации.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Предыдущий фикс снимал лоадер, но onSelectWorkspace пушил на mainRoute.name —
родительский layout-роут без компонента → серый экран. WorkspaceSwitcher
работает потому что зовёт goToDefaultPage(), который через getDefaultPageRoute()
резолвит реальную лист-страницу (authorized_default_route / defaultRoute /
первый child) И сбрасывает isWorkspaceChanging. Используем его же.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Действие ship в OrderCard (offerer/paid) не обрабатывалось ни одним
родителем — кнопка ничего не делала. Отгрузка идёт через формирование
партии на странице «Подготовка отгрузки», не с карточки заказа.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Эпик E14: «отделить акцептованное от реально погруженного». Поставщик
формирует партию из ПОДМНОЖЕСТВА акцептованных заказов КУ, остальное
остаётся ACCEPTED для следующей партии.

Модель: партия = (КУ, вариант доставки, подмножество заказов); на одном
(cycle, КУ) допустимо несколько частичных партий (self+expeditor split,
догрузка остатка). Невключённые заказы остаются ACCEPTED.

Изменения (аддитивно — старый фронт совместим):
- order.shipment_id: связь заказ→партия. Приёмка резолвит состав партии
  по shipment_id (findByShipmentId), а не инференцией по (cycle, КУ) —
  обязательно при нескольких партиях на одном КУ. Fallback на (cycle,КУ)
  для партий, созданных до появления связи.
- MarketplaceShipmentGroupInput.order_ids?: подмножество заказов (пусто →
  все ACCEPTED заказы КУ, как раньше).
- createShipment: снят 1:1-инвариант покрытия всех КУ и cycle-level
  idempotency (ConflictException). Повторное формирование разрешено;
  guard `shipment_id IS NULL` в assignToShipment не даёт включить заказ
  в две партии. total_amount по подмножеству.
- assignToShipment(): bulk ACCEPTED→SUPPLY_PREPARED + привязка к партии.
- createExpress: привязка через assignToShipment (консистентно).
- schema.gql + zeus-клиент перегенерированы.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Экспедитор не пайщик (нет аккаунта/кабинета) — приёмку открываем строго по
партии из накладной, а не по account-коду поставщика.

- HandoffTokenKind.Shipment + формат blago:shipment:<coopname>:<shipment_id>
  (shipment-bound, не account-bound). decode/encode поддерживают оба вида.
- TTNPrintPreview: QR-код приёмки партии на печатном листе ТТН (PNG data-URL,
  встраивается в печать и в скачанный самодостаточный HTML).
- buildTtnData: состав ТТН по order.shipment_id (fallback на (cycle,КУ) для
  старых партий) + qrValue = shipment-bound токен.
- OperatorReception: ветка скана QR ТТН → openPickupForShipment(id) грузит
  СТРОГО состав одной партии, без добора по акцепту («ничего больше»).
  Приёмка/план/маппинг переведены с группировки по cycle_id на shipment_id
  (при нескольких частичных партиях на одном КУ cycle_id брал не ту партию).
- MarketplaceOrder GraphQL DTO + orderSelector: поле shipment_id (нужно фронту
  для фильтрации состава партии). Схема + zeus перегенерированы.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Стол подготовки отгрузки переведён на модель брифа E14:
- Глобальная кнопка «Сформировать партию» в шапке (Teleport #header-actions-host)
  вместо per-cycle кнопок. Лист сформированных партий — основной вид стола.
- Новый диалог формирования: способ доставки (самовывоз / экспедитор по ТТН) →
  один кооперативный участок → dual-list заказов («Переместить всё» + откат
  отдельных строк назад). Грузим всё, что справа; невыбранное остаётся ACCEPTED.
  Гранулярность целая — количество в заказе не дробим.
- Submit группирует выбранные заказы по заявке (cycle_id) и шлёт createShipment
  по каждой заявке с order_ids-подмножеством (backend S1). Для экспедитора —
  поля ТТН (на каждую партию печатается QR приёмки, S2).
- groupAcceptedByKu(): группировка акцептованных заказов по КУ через все заявки.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ttn_number в shipment/apl_reception: 32→64. Номер ТТН
  COOPNAME-TTN-<cycle8>-<ku6>-<suffix8> ≈ 36–42 символов переполнял
  varchar(32) → «value too long» при формировании партии экспедитора.
  Требует рестарта контроллера (synchronize применит ALTER, увеличение
  длины безопасно).
- CreateShipmentDialog: maximized для удобной работы с dual-list.
- OperatorBranchBar: при выборе из нескольких участков убран дублирующий
  крупный заголовок (название КУ повторялось в селекте «Участок-Участок»);
  селектор стал основным идентификатором, адрес — под ним. Один участок —
  по-прежнему текстом.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
При выборе КУ адрес шёл отдельной строкой под BaseSelect, а поле уже
резервирует место под hint (reserve-hint-space) → двойной зазор и
рассинхрон с центрированными иконкой/бейджем. Адрес кладём в :hint поля,
бейдж прижимаем margin-left:auto в обоих режимах.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Меню-вкладки (каталог, мои заказы, входящие заказы) — первый блок
страницы; верхний padding q-page давал лишний зазор над саб-навигацией.
Гасим padding-top: меню прижимается к топбару, контент ниже разводит
flex-gap. PageHint при dismiss не рендерится — пустого слота нет.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Способы поставки сведены к двум: INDIVIDUAL (заказ обслуживается отдельно)
и COLLECTIVE (коллективная закупка — копится партия, старт по объёму или
ручному запуску). Убраны timebased/volumebased/opensubscr. Дефолт order.
cycle_type = INDIVIDUAL, валидация createorder сужена до двух значений.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Свёл cycle_type с 4 значений (time_based/volume_based/open_subscription/
individual) к двум: individual и collective (коллективная закупка).

Коллективная закупка:
- target_volume опционален: задан → авто-старт партии при наборе объёма;
  не задан → только ручной запуск поставщиком.
- ручной «Запустить поставку» доступен любой collective-оферте
  (снят гейт open_subscription).
- жёсткого таймера нет: заказчик выходит сам в любой момент до акцепта.

Удалено: поля cycle_days/min_threshold/max_wait_days; cron-агрегаторы
aggregateTimeBased + aggregateVolumeBasedExpired; статусы партии/ордера
EXPIRED_NO_THRESHOLD/EXPIRED_NO_VOLUME; репо-методы listAllActiveTimeBased/
listAllActiveVolumeBased/findExpiredTimeBased.

Переименовано: GraphQL-мутация marketplaceTriggerOpenSubscription →
marketplaceTriggerCollectiveSupply; aggregator evaluate/trigger-методы.
Сохранён EXPIRED_NO_RESPONSE (истечение окна акцепта) + cron
expireUnacceptedPending.

tsc/eslint чисто; unit-тесты обновлены и зелёные. Контракт p.mkt.supply
(2-значный CycleType) уже в базовой ветке. schema.gql + zeus-клиент +
фронт — отдельным шагом (codegen).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reviewed-on: #50
Reviewed-by: Алексей Муравьев <chairman.voskhod@gmail.com>
SDK:
- mutation triggerOpenSubscription → triggerCollectiveSupply
  (MarketplaceTriggerCollectiveSupplyInput); barrel-export обновлён
- offerSelector: убраны удалённые поля cycle_days/max_wait_days/min_threshold

Desktop:
- конструктор оферты: 4 radio-карточки → 2 (individual | collective),
  target_volume опционален для collective (авто-старт по объёму либо
  ручной запуск); удалены 3 поля из формы/payload/prefill
- OrdererConsolidated: CYCLE_TYPE_LABEL/HINT → COLLECTIVE/INDIVIDUAL,
  STAGE_RANK без EXPIRED_NO_THRESHOLD/EXPIRED_NO_VOLUME
- OrderCard/MyOrders/OffererIncomingOrders/ProcessSupplyInfoWidget:
  выпилены отменённые статусы EXPIRED_NO_* и старые подписи циклов
- marketplace-units: подписи cycle → individual | collective

schema.gql не трогаю (gitignore в dev, войдёт на мердже).
Имя файла отставало от уже переименованного класса
MarketplaceTriggerCollectiveSupplyInputDTO; правлю импорт в
marketplace-cycle.resolver.ts.
Конфликты разрешены:
- app.module.ts: оставлены оба набора модулей (SignedDocumentsModule из dev
  + Marketplace* из marketplace2)
- workspace.ts (IWorkspaceRouteMeta): icon? опционален (dev, для hidden
  deep-link роутов) + grants-канон requires?/roles? опц. (marketplace2 —
  grants заменяют roles)

cooptypes пересобран (dev принёс SovietContract Registry.NewDeclined в
src, dist был stale). Контроллер tsc --noEmit зелёный.
Упразднён тип поставки (cycle_type/target_volume) как сущность. Управление
поставкой сведено к delivery_points оффера: список КУ + min_supply_volume на
каждом (мягкий ориентир группировки, не порог). individual/collective больше
нет нигде.

- Контракт: убран cycle_type из createorder (param + поле orders + namespace
  CycleType + валидация). Требует ребут dev-чейна + regen ABI.
- cooptypes: ICreateOrder/IOrder без cycle_type.
- Controller: offer.delivery_points (jsonb) вместо cycle_type/target_volume;
  order без cycle_type-снимка; единый batch-accept/decline по order_id[]
  (свёрнуты acceptIndividual/acceptConsolidated/declineConsolidated/
  triggerCollectiveSupply); удалены cycle-aggregator + consolidated-accept-decline
  сервисы; consolidated_request без cycle_type (создаётся в момент batch-accept).
- SDK: AcceptOrdersBatch/DeclineOrdersBatch мутации; delivery_points в
  offerSelector; schema.gql + zeus regen.
- Тесты контроллера обновлены, tsc --noEmit зелёный.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Форма оффера: вместо селектора типа поставки + target_volume — список КУ
  кооператива (BaseCheckbox + min-объём на каждый, через marketplaceListKUDetails);
  убраны cycleTypeOptions/isCollective/onSelectCycle/триггер-кнопка.
- Стол поставщика: accept/decline через единый batch-API (acceptOrdersBatch/
  declineOrdersBatch) по order_id[]; убрана cycle_id-маршрутизация.
- Сводный заказ заказчика, модерация: убраны cycle-лейблы → сводка по КУ.
- Удалены cycle-type-view алиасы, marketplaceCycleLabel; ESLint зелёный.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
OffererIncomingOrders переписан на партии вместо плоского списка заявок:
- ACTIVE-заказы группируются по «оферта × КУ» в партию-накопитель с прогресс-баром
  к min_supply_volume (цель сбора, не порог), краткий состав партии вместо россыпи карточек
- «Принять партию (N)»/«Отклонить» массивом, доступны всегда (принять можно < min)
- min ≤ 1 → поштучный режим без прогресс-бара
- принятые (cycle_id) → сформированная партия (метрики + readonly-карточки)
- фикс: вкладка «Ждут акцепта» → фильтр ['ACTIVE'] (раньше leftover ACCEPTED_PENDING_SUPPLIER*)
- min-карта из marketplaceListMyOffers.delivery_points (хелпер fetchSupplierMinVolumeMap)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
schema.gql — генерируемый артефакт (NestJS autoSchemaFile + generate-schema),
числился в .gitignore (строка 26), но был случайно затрекан → постоянный шум
в diff при регенерации. SDK build (typecheck+unbuild) его НЕ читает; читает только
скрипт generate-index-comments (docs/update:comments), которому нужна свежая
локальная генерация, а не закоммиченная копия.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Страница создания предложения поставщиком грузила список КУ запросом
marketplaceListKUDetails с пустым data: {}, из-за чего GraphQL отклонял
запрос «Field "coopname" of required type "String!" was not provided» и
кооперативные участки не загружались.

Переведён на entity-store MarketplaceKUDetails (как AdminIssuancePoints):
coopname берётся из systemStore.info, onlyActive: true. Инлайн client.Query
с пустым data убран (DRY: используем готовую обёртку entity).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Страница модерации дёргала client.Query(ListCategories) прямо из .vue.
Перевёл loadCategories на типизированную обёртку fetchCategories из
MarketplaceCatalog/api (reuse, без дубля). Импорты client/Queries из vue убраны.

Канон: никаких client.Query/Mutation в .vue — только функции api/ со строгой
типизацией из @coopenomics/sdk IInput/IOutput.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
В списке кооперативных участков при создании оферты вместо служебного кода
участка (braname, напр. iondlofolmqy) показываем человеческое наименование
филиала (short_name/full_name) и адрес. Рядом — маленькая кнопка карты
(icon-only), открывающая всплывашку MapView с точкой по координатам
геокодера — тот же паттерн, что в админке ПВЗ (AdminIssuancePoints).

Наименование берём из branchStore.loadBranches (join по braname), адрес и
координаты — из kuStore.details (геокодер). Кнопка карты только при
geocodeStatus OK и наличии lat/lng.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Оферта теперь отдаёт отображаемые реквизиты живьём с бэкенда, чтобы фронт
не дозапрашивал их сторонними запросами:
- supplier_name — ФИО физлица/ИП или наименование организации поставщика;
- по каждому delivery_point: name (наименование участка) + address — живьём
  из организации кооперативного участка (единый источник правды, правится
  председателем в «Кооперативные участки»), lat/lng — из геокода КУ.

Реализовано field-резолверами на типах MarketplaceOffer и
MarketplaceOfferDeliveryPoint (как ленивый images-резолвер) — применяется
ко всем запросам, где возвращается оферта (каталог, мои предложения,
модерация), без дублирования. Резолв реквизитов участка переиспользует
MarketplaceOrderDisplayService (live-резолв, дедуп, best-effort); промис
кешируется на инстансе точки, чтобы 4 поля не дёргали БД повторно.

Codegen: schema + Zeus-клиент + sdk build; selector оферты расширен.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Корень рассинхрона: адрес/контакты участка хранились копией в ku_details и
правились отдельно на столе ПВЗ, расходясь с правкой председателя в
«Кооперативные участки». Теперь детализация ПВЗ их не хранит — наименование,
адрес и контакты резолвятся живьём из организации участка (единый источник
правды) через field-резолвер MarketplaceKUDetails (как supplier_name/точки
поставки у оферты).

Backend:
- ku_details: drop contact_phone/contact_email; address_full → geocoded_address
  (nullable) как внутренний кэш-ключ геокода (bootstrap-v8, synchronize:true).
- MarketplaceDetailKUInput больше не принимает адрес/контакты — только режим
  работы/описание; KuDetailsService берёт адрес из организации, ленивый
  reconcile геокода на чтении списка при дрейфе адреса.
- MarketplaceOrderDisplayService: контакты/адрес веток из организации,
  resolveBranchContacts; enrich заказов больше не читает локальный адрес КУ.
- Фикс DI: MarketplaceOrderDisplayService добавлен plain-class провайдером —
  field-резолверы оферты/КУ инжектят его по классу (латентный баг Chunk 1).

Frontend:
- Диалог ПВЗ: адрес/контакты read-only (правятся на «Кооперативные участки»),
  редактируются только режим работы/описание.
- CreateMarketplaceOffer: список КУ берёт name/address прямо из KU-details,
  без отдельного join branches.
- KU-details типы: name + nullable реквизиты; AdminIssuancePoints fallback.

Codegen: schema (83 резолвера) + zeus client + sdk build. Unit КУ 12/12.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Строжайшее правило: статусы (status/geocodeStatus) — всегда enum, никогда
строковый литерал; объявляются на backend, прокидываются в Zeus и используются
на фронте. Раньше KU/geocode/offer статусы были `@Field(() => String)` +
сравнения по строкам — нет единого источника значений, легко опечататься/
забыть набор.

Backend:
- KuDetailsStatuses / GeocodeStatuses / MarketplaceOfferStatuses → registerEnumType
  (GraphQL enum MarketplaceKUStatus / MarketplaceGeocodeStatus / MarketplaceOfferStatus);
  `@Field(() => Enum)` вместо String.
- Все строковые литералы статусов в сервисах/адаптерах/мапперах KU/offer/order
  заменены на enum-члены (ku-details, moderation, order-create, order-cancel,
  catalog-resolver, offer/order repo-адаптеры, order-delta mapper).
- SetKUStatusInput → @Field(() => MarketplaceKUStatus) + @IsEnum.
- Geocoder-port union (OK/FAILED) и TypeORM @Column enum-DDL оставлены — это
  отдельный port-контракт и DDL-объявление, не surfaced-статусы.

Frontend:
- entities/MarketplaceKUDetails: KuDetailsStatus/GeocodeStatus = Zeus.MarketplaceKUStatus
  / Zeus.MarketplaceGeocodeStatus (единые значения с backend).
- AdminIssuancePoints / CreateMarketplaceOffer: сравнения geocode/статуса — на
  enum-члены (script + pug-шаблон).

Codegen: schema + zeus + sdk build. Unit КУ 12/12, tsc controller clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
После утверждения Советом двух документов председателю было неочевидно, что
дальше нужно добавить кооперативные участки и отметить их как ПВЗ. Добавил на
страницу онбординга блок «Дальнейшая настройка» с двумя шагами-подсказками:
- шаг 3 → ссылка на «Кооперативные участки» (route `branches`);
- шаг 4 → ссылка на «Пункты выдачи» (route `marketplace-issuance-points`).

Без дублирования функционала — только текст + кнопки-переходы на профильные
столы. Видны сразу, чтобы был понятен полный путь подключения.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
«от 1» без единицы и сырой braname/аккаунт в карточках были непонятны.

- CreateMarketplaceOffer: поле «Мин. объём» получило суффикс выбранной единицы;
  превью участков поставки — «<КУ> (от N <ед.>)»; исправлен leftover-баг после
  SSOT — превью брало несуществующее `.label` вместо `.name` КУ.
- ChairmanModeration: «Участки поставки» теперь «<наименование КУ> (от N <ед.>)»
  вместо braname без единицы; «Поставщик» — ФИО (supplier_name) вместо аккаунта.

Имена/единицы берутся из backend-полей оферты (supplier_name, delivery_points.name)
и общего хелпера marketplaceUnitShort — без сторонних дозапросов.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Карточка каталога несёт только категорию, поставщика (ФИО) и краткое
описание (≤200 символов); полное описание, участки поставки с минимальным
объёмом, гарантия и галерея раскрываются на отдельной странице предложения
по клику. Быстрый заказ остаётся на кнопке «Заказать».

- backend: query marketplaceGetOffer(id) (enrichment через field-резолверы)
- sdk: Queries.Marketplace.GetOffer + регенерация zeus-клиента
- desktop: новая страница MarketplaceOfferDetail + скрытый маршрут
  marketplace-offer-detail (offer/:offerId); CatalogOfferCard — категория +
  поставщик + отсечка описания; каталог открывает деталь по клику

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Шаги 3–4 (участки, ПВЗ) больше не вынесены во вторую карточку «Дальнейшая
настройка» — они идут тем же сквозным списком после шагов совета (extraSteps
в CouncilOnboardingCard), нумерация продолжается. Доп.шаги видны всегда, в том
числе после утверждения документов.

Лоадер вынесен из оверлея на всё окно (WindowLoader, .absolute-full) внутрь
карточки (q-inner-loading с резервом высоты) — больше не «болтается» поверх уже
отрисованной страницы.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Заголовок диалога показывает человекочитаемое имя участка (branchName), а не
служебный braname. Режим работы: каждый день — одна строка (переключатель слева,
поля «Открытие/Закрытие» справа на той же линии); включение дня больше не
добавляет вторую строку и список не удлиняется.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Поля времени рендерятся всегда; при выключенном дне они disabled (серые), а не
скрыты по v-if. Высота строки одинакова для включённого и выключенного дня —
переключение больше не «скачет» по высоте.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hide-bottom-space на time-input'ах убирает зарезервированную пустую полосу под
сообщения — видимое поле центрируется по строке дня (align-items:center), а не
висит в верхней части высокой ячейки.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GeocodeStatus/KuDetailsStatus импортировались как `import type`, но используются
в рантайме (GeocodeStatus.OK и т.п.) — тип стирается при компиляции, отсюда
ReferenceError в hasCoords и краш рендера всей страницы после сохранения ПВЗ
(лоадер «крутился вечно», UI замерзал). Импорт переведён в value.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Убрано объяснение про партию/группировку/порог. Осталось: «Отметьте
кооперативные участки, на которые готовы обеспечить доставку, и укажите объём
поставки на каждое.» Подзаголовок шага — «Участки и объём поставки».

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
quantity_available уже исключает заблокированное количество (инвариант
counters: available + blocked + consumed = опубликовано; при заказе
available уменьшается сразу). Повторное вычитание quantity_blocked на
карточке давало двойное списание: опубликовал 100, заказали 1 → показывало
98 вместо 99.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Карточка в «Модерации» теперь кликабельна (курсор-pointer + hover) и
ведёт на полную страницу предложения в рамках стола администратора
(market-admin, hidden-маршрут marketplace-admin-offer-detail, Order:read:all)
— без перехода на стол заказчика. Модератор видит полное описание,
участки поставки с объёмами и гарантию.

Страница MarketplaceOfferDetailPage читает meta.readonly: в режиме модератора
прячет кнопку «Заказать» и диалог заказа, «назад» ведёт в «Модерацию».
Карточка модерации показывает категорию + ФИО поставщика (как в каталоге);
избыточный inline-блок деталей убран.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Колонка «Кто заказал» (входящие заказы поставщика) и «Заказчик» (выдача
оператора) показывали полное имя или, при пустом, username. Теперь —
сокращённое «Фамилия И.О.» через новый строковый хелпер formatShortFio
(заказчик всегда физлицо, поэтому сокращение безопасно). Полное ФИО
с бэкенда (orderer_name) остаётся доступным для документов.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
В подготовке поставки поставщик сам привозит товар на ПВЗ — это «самоввоз»,
не «самовывоз» (самовывоз — когда забирает получатель).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Во время подписания+перехода (redirecting) одновременно крутились два
спиннера: полностраничный q-inner-loading по центру и спиннер кнопки
«Подписать оферту» внутри gate. Прячем gate на время redirect — остаётся
один полностраничный лоадер.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Стол заказчика «Коллективный заказ» (бывш. «Сводный заказ») теперь зеркалит
карточку поставщика: партии-накопители показывают коллективный прогресс сбора
(накоплено всеми пайщиками / целевой минимум КУ), без ФИО других заказчиков.
КУ выводится человекочитаемым именем (delivery_point_name) вместо служебного
braname.

Бэкенд: Order-DTO обогащается group_accumulated_quantity (сумма ACTIVE по паре
оферта×КУ всех пайщиков) и group_min_volume (целевой минимум КУ) — новый
батч-агрегат sumActiveByOfferBranch + флаг withGroupProgress в display-сервисе.
Заодно ленты заказов (my/supplier) обогащаются именами участников
(withParticipantNames): на столе поставщика «кто заказал» теперь реально
показывает ФИО, а не аккаунт.

Codegen: generate-schema/client + zeus-клиент sdk и controller.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
В партии-накопителе выводил полные OrderCard, из-за чего название товара/КУ
повторялось в шапке партии и внутри — выглядело как забытая старая карточка.
Привёл к канону стола поставщика: накопитель показывает компактные строки
своих заказов (без больших карточек), большие OrderCard остаются только в
сформированной (принятой) партии. Накопитель и сформированная партия теперь
визуально разной формы, не читаются как близнецы.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Переделал страницу: все партии (на сборе и уже принятые) рендерятся ОДНИМ
шаблоном — шапка (товар + КУ + бейдж этапа), прогресс-бар сбора, свои заказы
строками. Убраны разные по вёрстке виды (метрик-сетка + большие OrderCard для
принятых), убран сырой UUID партии. Отличие стадий — только заполнение бара и
подпись этапа, а не разные сущности.

Бэкенд: group_accumulated_quantity/group_min_volume теперь заполняются на любой
стадии — для принятых партий коллективный объём берётся суммой по cycle_id
(новый агрегат sumByCycleIds), для собирающихся — суммой активного пула пары
оферта×КУ. Так прогресс-бар осмыслен на каждой карточке.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Страница-трекер сбора показывает лишь партии на этапе накопления и
подготовки поставки (ACTIVE…ACCEPTED_TO_COOP). Открытые на выдачу
(READY_TO_RECEIVE и далее), полученные, возвращённые и отменённые заказы
уходят со страницы — её цель наблюдать накопление, а не историю.
Получение/история — на «Моих заказах». Пустое состояние переформулировано.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Убрал разговорные обороты («исчезает отсюда», «копится») из пустого
состояния и подсказки страницы; текст переписан нейтрально и грамотно.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Сократили навигацию заказчика: получение перестало быть отдельной страницей.

- Удалён стол «Готово к получению» (OrdererReadyToReceive) из навигации.
- «Мой код получения» (account-bound QR) вынесен в шапку «Моих заказов».
- «Подписать и получить» (финальная подпись signiss2) — действие прямо в
  карточке заказа со статусом READY_TO_RECEIVE; диалог финализации переехал
  в MyOrders. READY_TO_RECEIVE стал обычным этапом общего списка.
- Клик по карточке открывает детальную страницу заказа (новый скрытый роут
  marketplace-order-detail, OrdererOrderDetail) на marketplaceGetOrder: состав,
  ПВЗ, хронология этапов, факт выдачи, те же действия.
- OrderCard: проп openable (клик по телу → emit open), действие receive.

Чистый фронт: marketplaceGetOrder/ListMyReadyToReceive/finalizeIssuance уже
есть, codegen не нужен. MarketplaceOrderView самодостаточен для диалога
(issuance_fact в общем orderSelector).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Убрал кнопку «Мой код получения» из шапки «Моих заказов» и вынес в отдельный
пункт меню «Получить заказ» (OrdererReceiveCode) после «Коллективного заказа»:
одна страница с account-bound QR-кодом на всю ширину и пояснением, больше
ничего. Так код очевидно findable — пайщику не нужно объяснять, где искать
его на пункте выдачи; пункт всегда виден в меню стола.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Детальная страница заказа: добавлена обложка товара (догруз оферты по
  offer_id), хронология переведена на канон-ленту ActivityTimeline вместо
  плоских строк, добавлен воздух над кнопкой «К моим заказам».
- Подпись на странице «Получить заказ»: убрано непонятное «показать заранее»
  → «показать с экрана телефона или с распечатки».

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Вынесен переиспользуемый OfferGallery (карусель изображений) вместо inline-
  каруселей: каталог, деталь оферты и деталь заказа теперь используют один
  виджет (DRY). На детали заказа вместо одиночного q-img — полноценный слайдер.
- Деталь заказа: больше воздуха (увеличены gap'ы шапки/фактов, обложка 144px).
- Кнопка «Получить заказ» (ведёт на страницу QR) продублирована в шапке
  «Моих заказов» и в действиях детали заказа — код выдачи под рукой везде,
  не только на отдельной странице.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Убрана разнобойная вёрстка: накопитель показывался прогрессом, а принятая
партия — метрик-сеткой с сырым UUID партии + вложенными OrderCard'ами (две
разные сущности на одинаковые по смыслу поставки). Теперь все партии рисуются
ОДНОЙ карточкой: шапка с бейджем этапа, прогресс-бар всегда, состав компактными
строками (поставщику важен общий объём, не кто заказал — имена справочно, без
дробления на отдельные карточки), действия «Принять/Отклонить» — только пока
партия копится. Удалены метрик-сетка, UUID партии и вложенные OrderCard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- «Получить заказ» вернул из переадресации в диалог (ReceiveCodeDialog),
  кнопка в шапке и на «Моих заказах», и на детали заказа — в одном месте;
  отдельная страница меню остаётся. Общий ReceiveCodeContent (DRY).
- OfferGallery: точки-навигация выключены по умолчанию (убраны со всех экранов).
- Вынес SupplyPartyCard — единая карточка партии для заказчика
  («Коллективный заказ») и поставщика («Входящие заказы»), вместо дублей.
- Подчистил мёртвые стили карусели/плейсхолдера в CatalogOfferCard и
  странице оферты.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Новый пункт меню «Отгрузить партию» после «Подготовки отгрузки»: один
  account-bound Pickup-QR во всю страницу (зеркало «Получить заказ» заказчика).
- Обобщил виджет ReceiveCode → HandoffCode (kind=Receive|Pickup, тексты в copy.ts):
  один контент/диалог на оба стола. Подключил у заказчика (страница + диалоги
  «Мои заказы»/деталь) и у поставщика (страница + диалог из шапки «Подготовки
  отгрузки» вместо inline-QR).
- Снёс мёртвый виджет CreateOrderDialog (дубль MarketplaceCatalog/OrderCreateDialog).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Кнопка сканирования кода передачи переехала из строки «Ожидают приёмки»
в шапку стола (канон Teleport, как «Мой код для ПВЗ» в подготовке
отгрузки) — единое предсказуемое место сверху.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Корень: склад показывал только промаркированное (inventory рождался лишь на
маркировке). Теперь позиция склада рождается на приёмке кооперативом по акту
(ACCEPTED_TO_COOP), статус RECEIVED — склад показывает ВСЁ принятое, штрих-код
и полка опциональны.

Бэкенд (controller, DB-only composite, synchronize + миграция v9):
- inventory: barcode_value/format/labeled_* → nullable; ADD shelf, received_at,
  received_by_operator_account; новый статус RECEIVED (стартовый).
- apl-reception.signAsChairman материализует склад: на каждый принятый Order
  (fact_quantity>0) позиция RECEIVED (qty=факт, expiry=received_at+warranty_secs),
  идемпотентно по countByOrder.
- inventory-label сервис переписан под раскладку/маркировку: assignShelf,
  splitInventory (разложить позицию по нескольким полкам, RECEIVED-only),
  generateLabel (наклеить штрих-код RECEIVED→LABELED). Снесены стратегии
  PER_ORDER/UNIT/PACKAGE и пакетная маркировка партии.
- мутации: marketplaceAssignInventoryShelf / marketplaceSplitInventory /
  marketplaceGenerateInventoryLabel (взамен labelInventory/labelShipmentInventory).
- крон списания скоропорта сканирует RECEIVED+LABELED.

Codegen: schema → zeus-клиент → sdk build. tsc controller зелёный.

Десктоп (стол ПВЗ) — следующим коммитом в этой ветке.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Десктоп к приёмочной модели склада (Путь A):

- Приёмка партии: убраны кнопки-лаунчеры («Принять партию», «Принять
  самовывоз»). Приёмка запускается ТОЛЬКО сканом QR/вводом кода (идентификация
  обязательна). Ожидающие партии и самовывоз показаны карточками со составом
  («что везут» — товар + кол-во) без проваливания.
- «Маркировка имущества» → «Раскладка и маркировка»: список принятых позиций
  склада; на строке — полка (свободный input), «Разложить» (split одной позиции
  по нескольким полкам), «Этикетка» (опциональный штрих-код). Снесены выбор
  партии и стратегии PER_ORDER/UNIT/PACKAGE. Печать этикеток сохранена.
- Склад моего КУ: показывает всё принятое (статус RECEIVED «Принято»), колонка
  «Полка», штрих-код опционален (—), сортировка/возраст по received_at.

SDK-обёртки inventory перегенерированы под новые мутации.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Оператор не прыгает между «жду» и «принимаю»: единый стол показывает что/когда/кому
везут (дата формирования партии + получатель в карточках состава), запуск приёмки —
только скан QR/ввод кода, тут же статусы и подпись актов. Удалён избыточный
KPI-дашборд incoming-shipments и сама страница (api оставлен). defaultRoute → reception.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Принятые кооперативом акты (ACCEPTED_TO_COOP) уже на складе — на столе приёмки
не нужны; таблица АПП заменена карточками только требующих подписи актов
(PENDING_*), показывается лишь когда есть что подписать. Убран дублирующий
заголовок «Ожидают приёмки» (раздел назван в шапке), хинт сжат до двух строк,
пустое состояние — в каноне через EmptyState.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
«Требуют подписи» перевёрстаны на BaseCard-grid: Avatar + имя поставщика +
AccountBadge в шапке, статус-бейдж справа, состав в surface-2 блоке, действие
подписи в футере на hairline-разделителе. Хинт переписан человеческим языком.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Самовывоз по факту больше не отдельной таблицей: для ПВЗ нет разницы,
сформировал поставщик партию заранее или привезёт самовывозом — и то и другое
дойдёт до участка. Слиты в один computed expectedDeliveries; самовывоз показан
способом доставки «Самовывоз» без даты формирования.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Список ожидаемых поставок перевёрстан на тот же BaseCard-grid, что и акты на
подпись (Avatar + имя + AccountBadge, бейдж способа доставки, сумма mono, состав
в surface-2 блоке с получателем под товаром). Унифицированы классы __card* —
один паттерн карточки для поставок и актов (DRY).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Карточка показывала аккаунт вместо имени — ФИО берём из состава (supplier_name
заказа), аккаунт остаётся в AccountBadge. Сумма перенесена из верхней строки в
итоговую «Сумма поставки» под составом (рубли — это итог, а не «привезёт по
факту», что относится к составу).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ожидаемые поставки агрегируются по поставщику (одна карточка = один
  поставщик на КУ): ФИО, состав по товарам (одинаковый товар суммируется),
  способы доставки, общая сумма
- диалог приёмки группирует единицы по товару (строки по заказчикам внутри),
  факт/цена правятся per-Order — бухгалтерия не меняется
- предлагаем к приёмке только заказы, чья партия ещё SUPPLY_PREPARED: заказы
  партий в RECEPTION_IN_PROGRESS (ждут подписи) повторно не показываем — иначе
  create падал «ожидался SUPPLY_PREPARED» при повторном приезде поставщика

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Поставщик и председатель КУ видят и подписывают доставку ЦЕЛИКОМ одной
кнопкой, а не каждую единицу имущества по отдельности. Под капотом по каждому
акту приёма-передачи — отдельный документ и отдельная транзакция (блокчейн не
проведёт всё одной tx); подпись группы = последовательный цикл по receptions с
прогрессом «подписано N из M».

- shared/lib/marketplace/groupAplReceptions: группировка актов в сводную
  поставку (ПВЗ: поставщик+КУ+способ+статус; поставщик: КУ+способ+статус),
  агрегация строк по товару + сумма
- стол ПВЗ «Требуют подписи» и стол поставщика «Подпись приёмки» → сводные
  карточки, одна кнопка на группу
- оба диалога подписи принимают группу и подписывают циклом, показывая прогресс
  и частичные ошибки

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Раскладка по полкам теперь перераспределяет ВЕСЬ непромаркированный
RECEIVED-пул заказа, а не один кусок: оператор может собрать имущество
с полок обратно (merge), разложить иначе (re-split) или перенести —
в одном действии «Разложить». Промаркированные единицы (штрих-код)
фиксированы и в пул не входят.

Backend: splitInventory собирает пул по order_id (непромаркированные
RECEIVED), сверяет сумму долей с суммой пула, схлопывает лишние куски
(новый repo.deleteById). Сигнатура GraphQL не изменилась — codegen не нужен.

Desktop: штрих-код показывается в той же строке, что имущество и полка
(нижний печатный лист скрыт на экране, разворачивается только при печати);
диалог раскладки предзаполняется текущим раскладом заказа по полкам.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Плоский список «позиция + поле Полка + кнопки» был непонятен: не было
ощущения, ГДЕ что лежит и зачем раскладка. Переделано в доску:

- колонка «Поступило» — принятое, ещё не разложенное;
- колонки-полки — что физически лежит на каждой полке;
- раскладка = перетащить карточку на полку (нативный DnD) или меню «⋮»;
- «+ Полка» создаёт новую полку; снять с полки = вернуть в «Поступило»;
- этикетка и раскладка по количеству — в меню карточки;
- штрих-код показан в карточке; отдельный лист — только для печати.

Цель страницы теперь читается: разложить так, чтобы при выдаче
заказчику сразу найти, на какой полке заказ. Backend без изменений
(assignInventoryShelf/splitInventory/generateInventoryLabel), без новых
зависимостей (DnD на HTML5). Канон: токены --p-*, убран хардкод #b3261e.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
BaseButton рендерит default-слот только при !iconOnly, а q-menu был
вложен именно в него — для icon-only кнопки меню не монтировалось.
Перенёс q-menu в слот #icon-left (рендерится всегда), меню привязано
к кнопке. Поправил вложенность q-list/q-item.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
marketplaceListSupplierPickupOrders обогащал заказы без
withParticipantNames — supplier_name/orderer_name приходили null, и
фронт показывал account («ant») вместо ФИО. Резолвер уже ограничен
ролью Receiving и членством в КУ, поэтому резолв имён здесь допустим
(как в приёмке/выдаче). Схема не изменилась — codegen не нужен.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Стол выдачи был q-table на сотни строк — оператор не видел «кому что
отдать». Переделано в карточки по заказчику: одна карточка = получатель
(ФИО + аккаунт), внутри список единиц, разнесённый по стадии:
«К выдаче» (ACCEPTED_TO_COOP, открыть подписью председателя) и
«Ждут получения» (READY_TO_RECEIVE, ждём заказчика). Заказчики с тем,
что можно выдать сейчас, — наверх; итог по сумме и числу позиций.

Backend без изменений: два существующих статуса ленты и есть стадии
выдано/не-выдано с точки зрения оператора. QR-скан и шаг открытия —
прежние.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Двухподписный акт падал у второго подписанта: "Хэш метаданных не
совпадает с существующим подписанным документом". Причина — в storage
фабрики, не в генерации (генерация версий в разное время корректна).

saveDraft ключевал черновик только по hash (= sha256 PDF-тела). Тело
впечатывает created_at лишь до минуты, а meta.block_num тянется
getCurrentBlock() на каждой генерации. Две генерации одного акта в одной
минуте → один hash, разный block_num → upsert $set затирал предыдущую
версию meta. Второй подписант через buildDocumentAggregate читал черновик
по doc_hash и получал чужую (позднюю) meta → meta_hash не сходился с
подписью первого → SDK signDocument отбивал подпись.

Фикс (storage-only, генерацию не трогаем):
- MongoDBConnector.saveDraft — компаунд-ключ (hash + meta.block_num):
  все версии черновика сосуществуют, ничего не теряется.
- DocumentRepository.findByHash(hash, block_num?) — тянет точную версию
  по block_num, фолбэк на hash-only для легаси-черновиков.
- DocumentAggregator.buildDocumentAggregate — отдаёт второму подписанту
  ровно ту версию, которую подписал первый (по signedDoc.meta.block_num).

Общий фикс класса багов для всех двухподписных документов (capital/meet/
agreement), не только marketplace-АПП.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reviewed-on: #53
Reviewed-by: Алексей Муравьев <chairman.voskhod@gmail.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Пункт меню «Сканировать QR» (последний, всплывающий — через действие
marketplaceUniversalScan, как кнопка «Поддержка»; держатель
UniversalScannerHost в layout). Оператор сканирует ЛЮБОЙ код, не зная заранее,
кто пришёл: код поставщика/ТТН → приёмка, код заказчика → выдача. Маршрут несёт
код в query handoff, целевой стол сам открывает сценарий. Кнопки на самих
столах приёмки/выдачи больше не ругаются «код не предназначен», а ведут на
нужный стол (сквозная маршрутизация). Кнопку «Сканировать QR заказа» на выдаче
вынес в шапку (канон Teleport), как на приёмке.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
На столе ПВЗ «Доверенные лица» вместо ручного ввода аккаунта пайщика — поиск
по фамилии (UserSearchSelector, тот же компонент, что в модуле собраний). В
модель остаётся username выбранного пайщика; оператору не нужно знать
технический аккаунт.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Бэкенд: orderer_name резолвится на read-пути списка склада (как лента заказов).
Фронт: q-table → canon .table-wrap; убраны плитки-счётчики и колонка «Возраст»;
заказчик показывается ФИО; единое поле поиска по заказчику/товару/полке/штрих-коду;
инлайн-правка полки и выпуск штрих-кода в строке.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- AccountBadge: opt-in prop plain (прозрачный фон) — для вторичного аккаунта под ФИО
- BaseInput: проброс blur/focus — для сохранения инлайн-правки по уходу фокуса
- Склад: полка = всегда-редактируемое поле (save по blur/Enter, только при изменении);
  товар получил фикс-ширину, сумма колонок = min-width таблицы → нет схлопывания и наезда

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
flat: outlined→borderless, reserve-hint-space выключен; на hover мягкий фон,
на фокусе нижняя линия акцентом. Полка на складе теперь читается как текст,
клик делает поле активным — без outline-рамки в ячейке.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- AccountBadge: серый фон убран в самом компоненте (везде сразу) — был opt-in plain, теперь дефолт; имя аккаунта = вторичный текст под ФИО
- Убраны Quasar Loading.show оверлеи (приёмка/отмена заказа/раскладка) — лоадер на кнопке (Сформировать акты, Разложить) либо alert; экран не перекрываем (кроме тех.работ)
- Приёмка: Кол-во и Цена/ед. — один горизонтальный ряд, оба с label, top-align (не прыгают)
- Склад: обновление в шапку (Teleport), поиск отдельной строкой (не дерётся по высоте с чипами)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Бэкенд: marketplaceClearInventoryLabel (сервис clearLabel + repo.clearLabel сбрасывает
barcode/format/labeled_* и возвращает статус RECEIVED; гард: только промаркированную).
SDK: мутация ClearInventoryLabel + regen.
Фронт: пункт «Снять штрих-код» в kebab-меню карточки маркировки (когда промаркировано) → переклейка.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Председатель (trustee) уже имеет полные права участка; добавление его в trusted дублирует роль. Проверка b.trustee != trusted с ошибкой.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Положение ЦПП «Стол заказов» перенумеровано 1107 → 1099 (в начало членского
комплекта, по аналогии с Благоростом 998/999/1000). Рыба заменена дословным
текстом из утверждённых docx (Положение + Оферта).

- 1099 Положение и 1100 шаблон оферты — без шапки «УТВЕРЖДЕНО»; ФИО пайщика
  в шаблоне 1100 — прочерки; председатель через coop.chairman.
- 1101 реальная оферта: шапка с протоколом утверждения шаблона
  (vars.marketplace_offer_template), ФИО через common_user, блок электронной
  подписи; тело идентично шаблону.
- Наименование кооператива — vars.name; нумерация и разметка сохранены 1:1
  с исходными документами.
- Обновлены пути импортов factory (Templates/Actions/registry-map/index) и
  desktop composable (marketplace_provision: 1099).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1100 — плейсхолдер «ОФЕРТА № ______»; 1101 — подстановка номера соглашения
(marketplace_agreement_number) по канону Благороста.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Workflow coop-standards-check: техно-хвосты (идентификаторы кошельков/операций,
ledger2, проводки-калька) убраны из прозы 4 capital-стандартов; structured-поля
(Дт/Кт, кошельки, ledger_code) не тронуты. Спорное (возможные баги кода,
семантика статусов invested/commit_*) НЕ правил — в теле PR на ревью.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Путаница бухгалтерского счёта и ledger2-кошелька в прозе: «перемещение между
счётами»/«на счёт программы» → «между кошельками»/«на кошелёк программы»
(движение без проводки = по кошелькам). В p.cap.invest заодно исправлен неверный
счёт: «деньги на расчётном счёте» → «бухгалтерский счёт не меняется».
Правило зафиксировано в каноне workflow и памяти.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Номер акта приёма-передачи: вместо человекочитаемых «ISS-…/APL-…»
(formatActId) — короткий SHA256-хэш от on-chain order_hash (+ reception_id
для АПП приёмки), по канону Capital `result_act_hash` (1042). Вынесен в общий
helper application/shared/act-number.util.ts (DRY).

Снят хардкод светлых цветов (`background-color: #f4f4f4`, `border #ccc`) в
шаблонах 1102–1106: фон убран, рамка → `currentColor` (следует теме). Раньше
ломало отображение на тёмной теме фронтенда.

Текст 1102 (АПП) не тронут — он уже 1-к-1 с паевым 802.ReturnByAssetAct
(согласованный юр.текст).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Переделано агентами Opus (предыдущие коммиты — Haiku, доверия нет): техно-хвосты
из прозы, счёт≠кошелёк (в т.ч. ошибка из оригинала p.cap.invest), structured-поля
нетронуты. Спорный дрейф (o.cap.lend в debtpaycnfrm; o.cap.repay triggered_by;
debtpaydcln нооп) — в теле PR на ревью.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Workflow coop-standards-check на Opus, 3 стандарта (supply/return/wroff):
техно-хвосты убраны из прозы (callback/gateway::/compensating forward/имена
операций/кошельки), gateway→«система процессинга», backend→«служба кооператива»;
structured-поля не тронуты. Спорное (registry_id 0/800 без validate_registry_id
в коде) — в теле PR на ревью.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3 стандарта (authpkg/decision/selectbranch): техно-хвосты (callback/exec/
decision.type/handler/registry_id/whitelist/имена экшнов) убраны из прозы,
structured-поля не тронуты. Дрейф (voteagainst не делает перехода в коде;
registry_id=600 не валидируется в authorize.cpp; синтетические статусы без
namespace Status) — в теле PR на ревью.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2 стандарта (p.reg.accept/reg.coop): техно-хвосты убраны из прозы и шапок
(блоки «Источники в коде», имена экшнов/операций/таблиц, registry_id, ledger2,
TRANSFER); structured-поля и проводки (Дт 51/Кт 86, Дт 51/Кт 80, Дт 80/Кт 86)
сохранены. Дрейф: registry_id 100/501/50/51 не валидируются в коде
(verify_document_or_fail без validate_registry_id) — в теле PR на ревью.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2 стандарта (p.wal.depo/p.wal.wthdrw): техно-хвосты убраны из прозы
(Gateway→платёжная система/кассир, enum-имена кошельков SHARE_FUND_PAY/
WITHDRAW_PENDING→человеческие названия, ledger2/callback/имена операций);
structured-поля и проводки (Дт 51/Кт 80, Дт 80/Кт 51) сохранены.
Дрейфа с кодом не найдено.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Перечистка точечным перегоном после ужесточения FORBIDDEN: Gateway→платёжная
система/кассир, кошелёк «Выданные займы» без техно-id в прозе. Структурно:
o.cap.lend перенесён с debtauthcnfr на debtpaycnfrm (LEND проводится там по
коду). o.cap.repay (принадлежит p.cap.rid) — оставлен на ревью в теле PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
meet.hold: техно-хвосты убраны из прозы (латинские статусы, notified_users,
open_at/close_at, sov.authpkg, newgdecision, quorum_passed), статусы → человеческие
названия; structured-поля не тронуты, operations:[] (собрание не двигает кошельки).
Дрейфа с кодом не найдено.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reviewed-on: #77
Reviewed-by: Алексей Муравьев <chairman.voskhod@gmail.com>
Reviewed-on: #58
Reviewed-by: Алексей Муравьев <chairman.voskhod@gmail.com>
REPAY триггерится в capital::signact2 (акт-2, процесс p.cap.rid), а не в
debtpaycnfrm (тот эмитит только LEND). Реестр кода (operations.hpp) привязывает
o.cap.repay к processes::capital::RID, code уникален → операция принадлежит
одному процессу. Дубль в p.cap.debt с triggered_by debtpaycnfrm противоречил
коду — удалён, оставлена поясняющая ссылка на p.cap.rid. Заодно поправлен
коммент processes.hpp DEBT (тоже ошибочно включал o.cap.repay).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[15] cancelvote.cpp жёстко запрещён (eosio::check(false, "Отмена голоса
запрещена")) — отзыв голоса недостижим. Убрано действие из стандарта,
ссылка в шапке и прозе «голос можно отозвать».

[8] action marketplace::expirecycle в коде не существует — реальное имя
expireorder (закрытие заказа по таймауту цикла; backend гоняет по каждому
заказу). Переименовано во всех 5 местах (actions/transitions/scenario/
triggered_by).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Приведение стандартов к коду (прав контракт).

[21] reg.coop: статус active кооператива ставит registrator::stcoopstatus,
а не soviet::converttoaxn (тот делает только конвертацию паевого в AXON,
status не трогает — grep по converttoaxn.cpp пусто). Добавлено действие
stcoopstatus как закрывающее, переход pending→active перевешен на него,
converttoaxn оставлен как денежный шаг (operation o.sov.axncnv), scenario
дополнен шагом активации.

[4] p.cap.prop: в таблице program_property одно поле document2 act
(act1pgprp/act2pgprp оба пишут p.act = act). stored_in обоих актов
исправлен с pgproperties.act1/.act2 на pgproperties.act.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
submretrn statement: registry_id 800 (старый донорский клиринговый
ReturnByAssetStatement) → 1104 MarketplaceReturnStatement — шаблон членской
модели серии 1100+ «Стола заказов», рядом с актами/ТТН. Заявление существует,
800 не использовать в членской модели.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Убран техно-мусор из note (registry-номера, имена шаблонов, «донор/клиринг»,
TODO про cooptypes/registry) — по правилам /coop-standards прозаические поля
пишутся бизнес-языком для методолога.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Принятие гарантийного возврата (accretrn) оформляется второй подписью
председателя КУ на том же заявлении пайщика (канон двухподписных актов),
а не отдельным документом. Удалённое рассмотрение (aprretrem/rejretrem) и
отказ на осмотре (rejretrn) — процедурные действия без подписи документа.

Контракт:
- table return_request: убраны поля decision_remote / decision_visit;
  statement при accretrn перезаписывается версией с обеими подписями.
- accretrn принимает document2 statement, требует обе подписи
  (verify_document_or_fail{orderer, chairman}); aprretrem/rejretrem/rejretrn
  больше не принимают документ.
- собран зелёным (marketplace.wasm).

cooptypes: интерфейсы IAprRetRem/IRejRetRem/IRejRetrn без decision,
IAccRetrn.decision → statement.

backend: сервис/DTO/резолвер — удалённые шаги и отказ без документа,
accretrn принимает со-подписанное заявление (signed_statement).

standard p.mkt.return: registry 800→1104 (членская модель, = коду),
секция документов и подписи переписаны на бизнес-язык; один документ —
заявление с подписью пайщика и со-подписью председателя при принятии.

Follow-up (отдельный срез): персистентность подписанного заявления в claim
+ экспозиция в DTO + со-подпись в desktop OnSiteDecisionDialog + zeus regen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
При подаче заявления на гарантийный возврат backend теперь сохраняет
подписанное пайщиком заявление (registry 1104) в claim — новая jsonb-колонка
marketplace_return_claim.statement (создаётся synchronize). Раньше документ
уходил только в цепь, и председателю было нечего со-подписывать.

- types/entity/typeorm/mapper/repo: поле statement (ISignedDocumentDomainInterface | null).
- submitReturnClaim сохраняет input.signed_statement в claim.
- новый метод getChairmanReturnSignablePayload + запрос
  marketplaceReturnClaimChairmanSignablePayload → DocumentAggregate
  {rawDocument + подпись пайщика}; председатель накладывает вторую подпись
  (signDocument(raw, chairman, 2, [document])) — канон APP-приёмки.
  Ownership: член КУ доставки (как в marketplaceReturnClaim).

Follow-up: desktop OnSiteDecisionDialog (со-подпись на приёме) + zeus regen
(generate-schema/generate-client/sdk build) — блокированы codegen-пайплайном.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reviewed-on: #96
Reviewed-by: Алексей Муравьев <chairman.voskhod@gmail.com>
generate-schema → generate-client после мержа #96 (со-подпись заявления
возврата). Подтянут новый запрос marketplaceReturnClaimChairmanSignablePayload
и signed_statement в accept-мутации; устаревший клиент приведён к текущей схеме.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts:
#	components/contracts/cpp/marketplace/p.mkt.return.standard.yaml
Reviewed-on: #79
- backend: при финализации выдачи (signiss2) позиции склада заказа переводятся
  RECEIVED/LABELED -> ISSUED (markIssuedByOrder), иначе расход на сводном складе
  всегда 0; добавлены поля unit_of_measure/delivery_point_name/delivery_point_address
  в MarketplaceInventoryItem, резолвятся батчем через enrichByOrderIds
- desktop: убраны бесполезные KPI-карточки на обоих табах; обе таблицы переверстаны
  по канону (.table-wrap); ПВЗ показывается наименованием+адресом организации участка,
  не служебным braname; единицы измерения берутся из предложения (литры/кг, не 'шт');
  убран дублирующий столбец SKU; приход=всё оприходованное, расход=выдано+списано,
  остаток=приход-расход; топ позиций по обороту по парам (позиция x ПВЗ) с именем КУ
- codegen: generate-schema + generate-client + sdk build

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- sdk: inventorySelector не запрашивал unit_of_measure/delivery_point_name/
  delivery_point_address -> фронт откатывался на служебный braname и 'ед.';
  добавлены поля в селектор (данные и резолвер уже были готовы, напр. КУ
  «Ромашка», адрес «Москва, ул. Петровка д. 38»)
- desktop: поиск переведён на канон-компонент FilterBar (иконка + debounce)
  вместо плоского BaseInput; столбцы склада переименованы в понятные
  «Принято / Выдано / Остаток» вместо «Приход / Расход / Остаток»

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reviewed-on: #102
Reviewed-by: Алексей Муравьев <chairman.voskhod@gmail.com>
Переосмысление экрана «Доступные категории» в управление списком категорий
кооператива: список = общие baseline-категории + собственные категории
кооператива; доступность для публикации задаётся включением/выключением.

Backend (controller):
- marketplace_category: колонка coopname (владелец кастомной категории; NULL
  у baseline) + миграция bootstrap-v10 (bump schema_version под synchronize).
- repo/service: listForCoop (baseline + свои), createCustom (id/sort поверх
  максимума, проверка дубля имени), deleteCustom (только свои, baseline защищён).
- AvailableCategoryAdminResolver: query marketplaceListCoopCategories,
  мутации marketplaceCreateCustomCategory / marketplaceDeleteCustomCategory
  (удаление каскадно снимает категорию из whitelist).

SDK: обёртки ListCoopCategories, CreateCustomCategory, DeleteCustomCategory,
ReplaceAvailableItems, ClearAvailableCategories + регенерация zeus-клиента.

Frontend (desktop): страница переверстана по канону — убраны бесполезные
KPI-плитки, canon-таблица с тумблерами вкл/выкл, бейдж «Базовая/Своя»,
удаление своих категорий, добавление через диалог, действия в шапке.
Whitelist-семантика: все включены = пустой whitelist (открытый каталог);
выключение строит whitelist из оставшихся; нельзя выключить последнюю.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Категории кооператива не доезжали до потребителей: форма создания предложения,
каталог и карточки показывали только 9 baseline-категорий, а публикация в
кастомную категорию падала бы на бэке.

Backend (controller):
- marketplaceListCategories теперь отдаёт полный список кооператива (baseline +
  собственные) — для каталога и резолва имени категории у опубликованных Offer'ов;
  не фильтруется по доступности (имя нужно и у выключенной категории).
- новый query marketplaceListAvailableCategories — категории, доступные для
  публикации (список кооператива минус выключенные whitelist'ом); для формы offer'а.
- MarketplaceOfferService.ensureCategoryExists: снят хардкод BASELINE_CATEGORY_IDS
  (1–9) — принимается любая категория кооператива (baseline/своя), плюс проверка
  доступности (whitelist) при создании/редактировании предложения.

SDK: обёртка ListAvailableCategories + регенерация zeus-клиента.

Frontend (desktop): форма создания предложения тянет marketplaceListAvailableCategories
(доступные категории), каталог через marketplaceListCategories автоматически
получает кастомные категории (резолв имён).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Создание/редактирование предложения падало с «category_id must not be greater
than 9»: class-validator @Max(9) на DTO резал кастомные категории кооператива
(их id нумеруются поверх baseline, т.е. > 9). Это второй слой ограничения 1–9
помимо снятого ранее хардкода в сервисе.

- marketplace-offer-input.dto: убран @Max(9) у category_id в create и update.
- marketplace-catalog.dto: убран @Max(9) у фильтра category_id (фильтрация
  каталога по кастомной категории) + чистка осиротевшего импорта Max и описания.

@Min(1)/@IsInt сохранены. Только runtime-валидация — GraphQL-схема не меняется.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reviewed-on: #104
Reviewed-by: Алексей Муравьев <chairman.voskhod@gmail.com>
Реестры документов marketplace перенумерованы в сплошной блок 1100–1108
(ProgramTemplate 1100, OfferTemplate 1101, Offer 1102, ТТН 1103,
AplReception 1104, AplIssuance 1105, ReturnStatement 1106,
WriteoffProtocol 1107, WriteoffStatement 1108) — порядок по жизненному циклу.

АПП разделён на два самостоятельных акта с разным текстом:
- 1104 MarketplaceAplReception — приёмка поставщик→кооператив
  («Поставщик передал, Кооператив получил», подписи Передал/Получил,
  без взноса/новации/«получил достаточное количество»; username=поставщик);
- 1105 MarketplaceAplIssuance — выдача кооператив→заказчик
  (членский взнос, подпись передающей «Выдал» оператором КУ).
Из обоих актов убрано «в соответствии с соглашением новации» (членская модель).

Цепочка cooptypes→factory→controller→sdk пересобрана (generate-schema/
generate-client/sdk build), desktop переведён на новые номера реестров.

Также вошёл пакет UI-полировки Эпика 16: имя КУ вместо braname,
упаковка партии (коробки), скелетоны загрузки, toggle «Показать акты»,
число коробок в ожидаемых поставках.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Каталог: счётчики категорий КУ-скоупные (arg delivery_braname + jsonb @>), фронт прячет пустые категории, сброс на «Все» при опустевшей
- Мои заказы: получение группируется по пункту выдачи (партия КУ >=2 → одна сводная карточка + одна подпись), слежение по корзине; 2 столбца; карта ПВЗ «куда ехать»
- Метки времени этапов на карточках поставок + локальный часовой пояс (formatDateToLocalTimezone) на поставках/детали заказа/складе
- Склад КУ: колонка «Годен до» (по сроку идёт списание)
- Деталь заказа: убран дубль ПВЗ из hero-карточки
- Карточка поставки: ФИО на всю ширину шапки, бейджи статуса/доставки отдельной строкой
- Поставщику не показываем число актов после подписи поставки
- Регенерация zeus-клиента (schema+client+sdk)
Симптом: оператор ПВЗ сформировал акт, поставщик подписал со своего
устройства — но на столе оператора закрывающая подпись «не высвечивалась»,
пока страницу не перезагрузишь руками. Причина: столы приёмки оператора и
поставщика не обновлялись сами (poll был только у «Моих заказов»).

- Общий композабл usePollingRefresh (тихий poll, не дёргает пока идёт
  загрузка/действие) в shared/lib/marketplace.
- Подключён к столу приёмки оператора (PENDING_CHAIRMAN_RECEPTION_SIGN
  всплывает сам после подписи поставщика) и к столу поставщика
  (PENDING_SUPPLIER_SIGN всплывает сам после «Сформировать акты»).
- «Мои заказы» переведены на тот же композабл (DRY, убран дублирующий
  setInterval).

Фундамент под глобальный гейт подписи на месте (#38 — позже на websocket).
Reviewed-on: #111
Reviewed-by: Алексей Муравьев <chairman.voskhod@gmail.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Поднят первый GraphQL-subscription поверх graphql-ws на том же /v1/graphql:
- PubSubModule (@Global, in-memory) — единый брокер событий приложения.
- Подписка marketplaceEvents: union MarketplaceEvent (OrderReadyToReceive |
  ReceptionPendingSign) — «сигнал, а не данные»; детали клиент дочитывает
  авторизованным query.
- Мост MarketplaceRealtimeBridge: доменные события EventEmitter2 (эмит после
  commit в PG, INV-12) → публикация в ПЕРСОНАЛЬНЫЙ топик адресата
  (marketplace:events:<coop>:<account>). Чужому пайщику событие физически не
  попадает — топик строится из аккаунта, не из аргументов клиента.
- Аутентификация ws — в graphql-ws onConnect: верификация access-JWT из
  connectionParams, sub кладётся в контекст; единый context прокидывает юзера
  в req.user, чтобы @CurrentUser работал и на ws. HTTP-путь не тронут.
- DRY: резолв юзера по sub вынесен в resolveUserBySub (стратегия + резолвер).
- SDK: новая категория Subscriptions.Marketplace.Events (строгая типизация
  IInput/IOutput, селектор union через ...on), геттер Client.Subscription
  прокидывает Authorization в connectionParams. ZEUS-клиент пересобран.

Зачем: убрать 10-сек поллинг гейта подписи на месте — позиции пункта должны
прилетать в реальном времени и приватно, а не по капле общими опросами.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Универсальный realtime-канал ядра (src/shared/lib/realtime): фабрика, как
  реестр оверлеев. Ядро не знает расширений — управляет жизненным циклом
  подписок (открыть по авторизации, закрыть при выходе) и дёргает catch-up при
  возврате приложения в активность + страховочный таймер от зомби-сокета. App
  лишь стартует канал; подписки расширения регистрируют в своих install.ts.
- Подписка marketplace (createMarketplaceEventsSubscription) регистрируется из
  market/install.ts; на любое событие персонального канала → дочитка гейта
  (signal-only). Реконнект graphql-ws → catch-up через open-листенер.
- Гейт подписи на месте снят с 10-сек поллинга: usePollingRefresh убран,
  остался первичный onMounted refresh; обновления — подпиской + catch-up.
- Параллельная подпись актов (Promise.all вместо последовательных циклов):
  оператор openIssuance (signiss1), заказчик finalizeOrdererIssuance (signiss2),
  поставщик signReceptionGroupAsSupplier. У заказов разные хэши → на цепи это
  разные документы, гонок нет; позиции пункта взводятся почти в один блок.

Зачем: заказчик получал гейт с одной позицией, остальные долетали следующим
поллингом (последовательный signiss1 ~0.5с/акт + 10-сек опрос). Параллельный
взвод + realtime-подписка дают весь комплект пункта сразу и приватно.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Причина бага «один шаг онбординга теряется (вечно „Ожидаем решение совета“),
если оба утвердить почти одновременно»: каждый @OnEvent(DecisionTrackedEvent)
-листенер (generic + capital + chairman) и сервисы онбординга делали
read-modify-write всего extensions.config JSONB — читали блоб, ставили один
флаг, писали весь объект назад. Два конкурентных события одного расширения
читали config без обоих флагов → последний writer затирал первого. Плюс
вторичный баг: при гонке ни один обработчик не видел оба _done →
allDone/isL1Complete не срабатывал → coopAcceptance.accepted/auto-restart
зависали.

Фикс — глобальный примитив ExtensionDomainRepository.patchConfig(name, patch):
read-merge-write под строчным локом (SELECT … FOR UPDATE) в одной транзакции,
shallow-merge top-level ключей, возврат СВЕЖЕГО слитого config. Лок
сериализует писателей → патчи разных ключей не теряются, а второй писатель
видит коммит первого, поэтому детекция «все шаги завершены» больше не зависает.

Переведены на patchConfig все write-path онбординг-семейства: generic
events+service, capital events+service, chairman events+service (+general_meet),
marketplace coop-acceptance и syncCoopAcceptanceFromOnboarding. В
events-сервисах allDone/isL1Complete теперь читается из возвращённого merged.

Тест-моки репозитория дополнены patchConfig (shallow-merge), ассерт
coop-acceptance переведён на patchConfig.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reviewed-on: #116
Reviewed-by: Алексей Муравьев <chairman.voskhod@gmail.com>
Reviewed-on: #113
При регистрации пайщик подписывал ОБЕЗЛИЧЕННЫЙ шаблон оферты ЦПП
(1101.MarketplaceOfferTemplate — ФИО прочерки, номер ______), который
утверждает Совет, а не персональный инстанс (1102.MarketplaceOffer с его
ФИО и уникальным номером). Инстанс был объявлен, но нигде не использовался.

Фикс (подход «фабрика рендерит персональный документ + выбираемая программа»):
- register-marketplace-in-agreement-registry: регистрируем оферту на
  MARKETPLACE_OFFER_INSTANCE_REGISTRY_ID (1102), applicable_account_types=[]
  (подтягивается только через программу), + registerProgram «Стол заказов» —
  ЦПП становится выбираемой программой в SignUp (как Генерация/Благорост).
- factory 1102.MarketplaceOffer: номер+дату читаем из Udata (зеркало оферты
  Генератора 996), с приоритетом явной передачи в data; так повторный рендер
  даёт тот же хэш, что подписал пайщик.
- ProgramKey.MARKETPLACE + отдельный MarketplaceUdataParametersPort (marketplace
  независим от capital, пишет Udata через core UDATA_REPOSITORY); registration-
  flow по выбору программы генерит персональные номер+дату в Udata.
- regen schema/zeus-client + sdk build под новый ProgramKey.MARKETPLACE.

Тест marketplace-plugin-register обновлён под инстанс + программу.
Остаётся (followup): L3-гейт на столе всё ещё отдаёт шаблон — нужен param-gen
в момент подписи со стола.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Симптом: пайщик выбрал ЦПП «Стол заказов» при регистрации и подписал оферту,
но на столе заказчика онбординг снова требует подписать (галочка), хотя
документ уже подписан. Причина — регистрационная подпись не доходила до
`wallet::users.programs[2]`, который читает L3-гейт (getOnboardingState).

Корень: оферта marketplace не сохранялась кандидату и не подписывалась on-chain:
- в `DocumentType` не было `marketplace_offer` → `mapAgreementIdToDocumentType`
  возвращал null → `registerParticipant` не сохранял документ;
- в `RegisterParticipantInput`/domain-interface не было поля `marketplace_offer`;
- фронт CreateUser отбрасывал program_key=MARKETPLACE (whitelist только
  CAPITALIZATION/GENERATION) и не слал документ.
Из-за этого `account.adapter.register` не находил `candidate.documents.marketplace_offer`
и не звал `wallet::signagree` (program_id=2 из coagreement type=marketplace) →
подписи в wallet::users.programs[2] не было → L3 requires_gate=true.

Фикс (L2 и L3 пишут подпись в одно ончейн-хранилище — равнозначные пути):
- DocumentType.MARKETPLACE_OFFER + поле marketplace_offer в DTO и
  RegisterParticipantDomainInterface (identity-fallback маппинга теперь сохраняет
  документ; on-chain register подписывает его через wallet::signagree program_id=2);
- фронт CreateUser: пропускаем program_key=MARKETPLACE + шлём marketplace_offer;
- regen schema/zeus-client + sdk build.

Фронт-гейт OnboardingMemberPickCpp перестроен:
- КУ-селектор показывается ВСЕГДА; согласие+подпись — только при requires_gate;
- подписал при регистрации (requires_gate=false) → только выбор КУ, «Продолжить»
  без подписи (фиксирует пункт выдачи и уходит на стол);
- председатель/не выбравший ЦПП (requires_gate=true) → подписывает тот же
  документ 1102 в то же хранилище, что и при регистрации.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Несколько записей vars в одном блоке (утверждение положения и оферты ЦПП
одним решением совета) получают одинаковый block_num. DataService сортировал
только по block_num:-1 — у Mongo нет стабильного tiebreak, возвращалась ранняя
версия без свежезаписанного marketplace_offer_template, из-за чего шапка оферты
(«УТВЕРЖДЕНО Протоколом Совета №…») рендерилась пустой, хотя vars сохранены верно.
Добавлен вторичный ключ _id:-1 в getOne/getMany/getHistory — последняя запись
в блоке выигрывает детерминированно.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reviewed-on: #118
Топик marketplaceCatalogTopic(coopname) широковещательный; union MarketplaceEvent
расширен MarketplaceOfferStockChangedEvent и MarketplaceOfferPublishedEvent. Мост
подключает уже эмитящиеся marketplace.offer.counters.changed -> StockChanged и
marketplace.offer.approved -> Published в catalog-топик (coopname из config). Резолвер
marketplaceEvents подписывает соединение на [personalTopic, catalogTopic]. republish
ранее одобренного оффера эмитит approved -> сигнал «появилось в каталоге» минуя модерацию.
Константы offer-событий + payload-интерфейсы централизованы в notification.events.
Codegen: schema -> Zeus client -> SDK selector + build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Диспетчер событий по __typename (shared/lib/marketplace/marketplace-realtime) +
composable useMarketplaceRealtime(handlers, {onResync}) с регистрацией на mount и
снятием на unmount. Подписка marketplace разводит события: личные (заказ/приёмка) —
прямая дочитка гейта, каталожные — в диспетчер. Каталог заказчика: остаток — точечный
патч карточки по offer_id без перефетча и мельтешения; новый оффер и catch-up —
debounced тихое обновление с сохранением загруженной глубины (страховочный resync 60с
не дёргает прокрутку). OffererMyOffers переведён на realtime, polling 30с снят —
одобрение/остаток мгновенно, отклонение подхватывает 60-сек catch-up канала.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Эмит из единой точки синхронизации с цепью (handleSyncDelta, chain-as-source-of-truth) после commit'а в PG (INV-12); адресация в персональные топики обеих сторон; статус строго enum MarketplaceOrderStatus; полный codegen-цикл schema->client->sdk.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Третий класс топика marketplace:staff:<coopname>, резолвер подключает его только trustee/trusted КУ; событие RECEPTION_STATUS_CHANGED (подпись поставщика не двигает статусы заказов — отдельный сигнал); эмиты статусов заказа во всех backend-forward точках адаптера (cancel/supplier-action/партия/выдача/создание), идущих мимо syncer'а; codegen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Подписаны через useMarketplaceRealtime: Мои заказы, деталь заказа (фильтр по order_id), Коллективный заказ, Входящие заказы поставщика, Подпись приёмки, столы оператора приёмки (фильтр по КУ) и выдачи (раньше без авто-обновления вовсе); debounce 400мс схлопывает пачки переходов, страховка — 60-сек resync канала и catch-up на возврат вкладки.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
marketplace: setfee (единая ставка кооператива в singleton config, админ), setsplit (отсечка персонального распределения per-КУ, председатель), явное поле Order.membership_fee (binary_extension), блокировка взноса o.mkt.fee на createorder/stockorder, полный возврат o.mkt.refund на cancel/decline/expire, финализация в signiss2 (пропорция от факта, дособор/возврат, inline branch::distribute).
branch: таблицы weights/weighttotals (веса per КУ+контракт-источник, доля = вес/Σвесов, secondary index по контракту), setweight/delweight (председатель), авто-чистка весов в deltrusted, distribute (o.brn.person по весам + o.brn.common в общий кошелёк), convert (o.brn.conv в членский кошелёк Стола заказов), createaid/aidconfirm/aiddecline (матпомощь через gateway по образцу payout, o.brn.aid Дт86/Кт51).
ledger2: кошельки w.mkt.fee (COOPERATIVE-пул), w.brn.person, w.brn.common (USER_SHARED) + 6 операций в реестре; процессы p.brn.fees/p.brn.aid. Стандарты: p.brn.fees, p.brn.aid, дополнен p.mkt.supply. Оба wasm собраны, actions/tables в ABI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
marketplace: ISetFee/ISetSplit + поле Order.membership_fee + таблицы config/branchsplits; branch: 7 обёрток actions (setweight/delweight/distribute/convert/createaid/aidconfirm/aiddecline) + таблицы weights/weighttotals/aids + интерфейсы. ledger2-зеркала: операции o.mkt.fee/o.mkt.refund/o.brn.* и процессы p.brn.fees/p.brn.aid; попутно добавлен пропущенный ранее o.mkt.loss. Пакет собран.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Резолвер marketplace-economy: queries marketplaceGetEconomyConfig (ставка для каталога) / marketplaceGetBranchEconomy (отсечка+веса+балансы КУ) / marketplaceGetPersonalEconomy / marketplaceListAids; mutations marketplaceSetMembershipFee (админ) / marketplaceSetBranchSplit + marketplaceSet/DeleteTrusteeWeight (строго председатель КУ, сверка trustee) / marketplaceConvertBranchFunds / marketplaceCreateAid (подписанное заявление 1109, проверка подписи и баланса). Порт/адаптер: 6 write-методов (setfee/setsplit/setweight/delweight/convert/createaid через ключ кооператива, DRY submitAsCoop) + 6 чтений on-chain (config/branchsplits/weights/weighttotals/aids/userwallets w.brn.*). Access-matrix: ресурс Economy. Process-hash-locator: p.brn.fees (якорь — заказ) и p.brn.aid (aids.hash). Фабричный документ 1109 «Заявление на выплату материальной помощи» (cooptypes registry + factory generator + PDF-шаблон, НДФЛ получатель платит сам). SDK: селекторы/4 queries/6 mutations. generate-schema 87 резолверов зелёный, ESLint чист, cooptypes/factory/sdk собраны.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Backend генерирует документ фабрикой с aid_hash в meta; фронт подписывает и возвращает в marketplaceCreateAid. SDK-обёртка query. Схема 87 резолверов, sdk собран.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Стол ПВЗ /economy: кошельки «Мои членские средства» и «Общий кошелёк участка» (WalletCard), кнопки «Перевести в Стол заказов» (диалог с суммой) и «Материальная помощь» (сумма → формирование заявления 1109 → подпись DigitalDocument → заявка кассиру; НДФЛ — на получателе), отсечка и таблица весов с авто-долями и балансами (правки — только председателю этого КУ), мои заявки со статусами. Стол администратора /economy: действующая и новая единая ставка кооператива. Каталог: карточка показывает цену с учётом взноса и подпись «Цена с членским взносом N%»; корзина — строки «Стоимость имущества» и «Членский взнос» + итог с взносом. Канон mono-desktop (Base*-обёртки, WalletCard/DataRow/PageHint/AmountInput, токены --p-*). ESLint чист; jest issuance/access-matrix 22/22 зелёные.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- решение совета branchdec: документ 325 переписан (реквизиты явно через Action), мутация kuGenerateEstablishmentDecision, обработчик в decision-factory desktop
- кнопки действий собрания в шапке страницы, canCloseNow-гейт (все проголосовали или 15 минут истекли), busy-лоадеры до обновления проекции
- перевёрстка повестки/результатов по канону meet, back-link, статус в карточке
- диалог объявления: тип собрания (создание КУ / произвольные вопросы), повестка организатора для free, доп.вопросы при открытии голосования
- фикс импортов Int (DTO) и formatDateToLocalTimezone (DecisionFlow)
[C28-38][@ant] фикс: дубли повестки после открытия голосования + контакты участка на собрании
Typecheck / desktop (pull_request) Failing after 8m18s
Typecheck / controller (pull_request) Successful in 13m12s
68db00e8d4
- стёртые контрактом вопросы (erase при replace повестки) оставались в выдаче — фильтр по present в getDecision; бюллетень снова проходит валидацию контракта
- секция повестки скрывается, когда вопросов нет (после завершения собрания повестка живёт в протоколе)
- email и телефон участка запрашиваются при открытии голосования (нужны для добавления участка как подразделения после решения совета), хранятся приватно в БД и видны в карточке собрания
Поставщик выбирает «выплаты получаю на…» (платёжный метод ядра) на странице
«Выплаты»; без резолвабельных реквизитов публикация/возврат предложения
закрыты гейтом (backend + баннер в мастере). Выплата создаётся со снапшотом
реквизитов (payment_details + method_id) — кассир видит банк/счёт в реестре.
Назначение переписано: «Оплата по акту приёма-передачи № <ACT16>» вместо
UUID-простыни (обычная покупка, не возврат паевого взноса; НДС — отдельно).
Страница «Выплаты»: человеческая дата, колонка «Куда», статусы PENDING/
COMPLETED/DECLINED. Попутно вылечен дрейф marketplace-offer-service.test.
[C28-38][@ant] фича: перестройка стола КУ + статус «Отменено» + гейт отмены принятого решения
Typecheck / desktop (pull_request) Failing after 8m28s
Typecheck / controller (pull_request) Successful in 13m11s
2e54f7a9ac
- статус CANCELLED: контракт стирает запись одинаково при любом исходе, факт отмены фиксируется в БД при canceldec; в списках и карточке показывается «Отменено» вместо «Завершено»
- отмена собрания недоступна после успешного голосования (все бюллетени поданы или окно истекло и по каждому вопросу «за» больше «против») и после утверждения протокола — только направить в совет; при сборе участников и неуспешном голосовании отмена остаётся
- стол КУ: страница «Кооперативные участки» (таблица с председателем и адресом, переход на участок), страница участка (адрес/контакты, ФИО председателя, доверенные лица, заявки на доверенных с одобрением председателем, кнопка «Стать доверенным»), «Мой участок» — тот же виджет для участка, где пайщик председатель
- страница «Доверенные» удалена — её функционал переехал на страницу участка
[C28-38][@ant] фикс: публичные сертификаты участка для пайщиков + доверенные фамилиями
Typecheck / desktop (pull_request) Failing after 8m25s
Typecheck / controller (pull_request) Successful in 13m11s
e35e74cd11
Поля trustee/trusted остаются под guard chairman/member (паспортные данные);
для пайщиков добавлены trustee_certificate/trusted_certificates (ФИО+username)
без guard. Страницы КУ переведены на getPublicBranches, в таблице участков
доверенные показываются фамилиями, прочерк при отсутствии.
[C28-38][@ant] фича: автопривязка председателя КУ к своему участку
Typecheck / desktop (pull_request) Failing after 8m31s
Typecheck / controller (pull_request) Successful in 14m11s
57f2ef43e0
soviet::setbranch (auth _branch) — системная установка/сброс braname без
заявления. Председатель привязывается автоматически при учреждении участка
(confirmdec/createbranch) и при смене (editbranch: новому — установка,
прежнему — сброс, выбирает участок заново). Председатель может возглавлять
только один участок и не может сменить участок заявлением (selectbranch),
пока исполняет обязанности — попап выбора КУ его не касается.
«Мой участок» показывает участок по braname пайщика (выбранный или
автопривязанный), fallback — по председательству.
Контракт marketplace: createorder/stockorder принимают подписанное заявление
о конвертации паевого взноса (обязательное, verify по orderer) и публикуют его
make_complete_document отдельным пакетом (package = hash заявления); signchair
и signiss2 публикуют двухподписные АПП в пакете процесса заказа
(package = order_hash); submretrn/accretrn/rejretrn/rejretrem ведут заявление
возврата по статусам подан→решён/отклонён в том же пакете. Списание уже
публикуется через повестку совета (createagenda) — без изменений.

Backend: registry 1110 + фабрика + DTO; превью заявлений конвертации в
checkout корзины и принятии докладки (order_hash рождается в превью и зашит
в мету), guards суммы/якоря, целочисленный расчёт взноса формулой контракта;
legacy-мутация marketplaceCreateOrder удалена (мёртвый донор-флоу).
Desktop: программная подпись заявлений при оформлении корзины и принятии
предложения со склада.
[C28-38][@ant] фикс: заявка доверенного принимает договор 327 с ФИО председателя
Typecheck / desktop (pull_request) Failing after 8m32s
Typecheck / controller (pull_request) Successful in 13m17s
67eeac5563
requestTrusted был типизирован под заявление 326 (мета без chairman_full_name),
а фронт по канону подписывает договор о полной материальной ответственности 327 —
GraphQL отклонял мету. Тип application заменён на DTO договора 327 (как у
встречной подписи председателя). Формулировка пустого «Моего участка» смягчена:
«Кооперативный участок пока не выбран».
Одобрение заявки доверенного падало «Недостаточно прав доступа для генерации
документа»: председатель регенерировал договор 327 заявителя, а генерация
разрешена только автору. Теперь заявка отдаёт агрегат документа (сырой документ
+ подписи через DocumentAggregator) и ФИО заявителя; председатель накладывает
встречную подпись на документ первого подписанта (канон 2-й подписи как в
собраниях), документ открывается на просмотр из таблицы заявок.

«Направить в совет» показывается избранному председателю участка
(decision.chairman), а не организатору собрания — заявление в совет
подписывает он.

Уведомления: напоминание участникам за час до собрания (cron по meet_at,
маркер meet_reminder_sent), председателю участка о новой заявке доверенного,
заявителю о решении по заявке; workflow «голосование началось» включён в
реестр синхронизации (не был зарегистрирован).
Поставщик не знал двух важных для решений событий: (1) поступил новый
заказ по его предложению — теперь in-app + push (без email: заказы частые,
письмо было бы спамом), повод зайти и акцептовать; (2) по его товару
оформлен гарантийный возврат и имущество принято обратно в кооператив на
очном осмотре — теперь email + in-app + push, дальше председатель КУ ведёт
претензию вне системы. Два события эмитятся после commit в PG (INV-12),
доставка через Novu не влияет на основной flow; stock-заказ (поставщик =
сам кооператив) из уведомления о новом заказе исключён.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Стол бухгалтера, реестр процессов переработан как главный реестр (теперь
первый в столе, defaultRoute). Развёрнутый процесс собирает по хэшу:
- Документы (наименование/дата/подписанты через DocumentRow, открытие и
  скачивание на странице самого документа) вместо сырого списка source/hash;
- Операции и Проводки этого процесса (мини-реестры по process_hash);
убраны «События процесса» (ledger2::apply/walletop/debit/credit) и
«Содержание процесса» (виджет со ссылкой на стол ПВЗ и спецификой заказа).

Единая адресация между разделами: клик по № процесса в реестрах операций и
проводок ведёт в реестр процессов с раскрытым этим процессом (новый фильтр
processHash в listProcesses + авто-раскрытие, как operation_id в операциях).

Пайщик везде — ФИО (резолв username→account), служебный username не показываем.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Реестр процессов, блок «Документы»:
- Дедупликация: один логический документ (тождество по doc_hash) больше не
  дублируется 5-7 раз. Контракт пишет сперва одноподписную, затем двухподписную
  версию акта — показываем ОДНУ запись, версию с максимумом подписей (вторая
  подпись — ведущая), при равенстве — последнюю по блоку.
- Просмотр документа теперь во всплывающем окне (новый core-виджет
  DocumentViewerDialog: BaseDialog maximized + BaseDocument), а не переходом в
  реестр документов совета — у бухгалтера может не быть к нему доступа.
  Агрегат документа уже загружен в getProcess (document + raw), виджет ничего
  не догружает и никуда не ведёт. Переиспользуемый: принимает агрегат + title.

cooptypes: имя процесса p.mkt.supply «Прямая поставка и приобретение имущества
пайщиком» → «Поставка и приобретение имущества пайщика» (убрано «Прямая»).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Стол администратора: новая страница «Реестр заказов» (Order:read:all) —
все заказы кооператива с текущими статусами, раскрытие в состояние заказа
(таймлайн) и детализацию процесса p.mkt.supply по order_hash. Без ссылок
на стол бухгалтера: № операций/проводок копируются, а не ведут в чужие реестры.

Блок «Документы + Операции + Проводки» вынесен из реестра процессов в общий
виджет ProcessDetailCard (DRY) и переиспользован на обеих страницах; helper'ы
цвета/метки процесса — в shared/lib/ledger2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- состояние заказа рисуем каноническим ActivityTimeline, а не самопальной сеткой;
  причина (last_status_reason) теперь описание события отмены/отказа, а не
  отдельная неподписанная строка «Причина» (было неясно «причина чего»);
- кнопка «Открыть предложение» в детализации заказа → readonly-карточка
  предложения на столе администратора (marketplace-admin-offer-detail);
- гасим подсветку строки при наведении (мигание) в реестре заказов и процессов,
  включая вложенные таблицы операций/проводок.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Новая страница «Реестр предложений» (market-admin) — все предложения всех
поставщиков любого статуса (опубликованные/снятые/отклонённые/на модерации),
отдельно от «Модерации» (там только ждущие решения). Клик по строке → readonly-
карточка предложения (marketplace-admin-offer-detail) — туда же ведёт переход
«Открыть предложение» из реестра заказов.

- backend: marketplaceListAllOffers (Offer:read:all) + offerService.listAll;
  read:all добавлен админу в access-matrix (у совета уже был);
- DRY: логика username→ФИО вынесена из реестра процессов в общий composable
  useFioCache, переиспользована в реестре предложений.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Рядом с названием товара — иконка open_in_new, по клику открывает карточку
предложения (marketplace-admin-offer-detail). Раньше переход был только в
раскрытой детализации заказа и был неочевиден.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Председатель участка подписывает договор о полной индивидуальной материальной
ответственности (новый документ реестра 328) при подаче заявления о создании КУ —
оба документа уходят в совет одним пакетом действием exec. После решения совета об
учреждении участка договор автоматически уходит на встречную подпись председателю
совета через механизм одобрений (soviet::createapprv → confirmapprv → branch::apprliab).
Отклонить договор нельзя: решение об учреждении уже принято — контракт блокирует
declliab (check(false)), а в UI кнопка «Отклонить» для этого типа скрыта.

- cooptypes: реестровый документ 328.BranchChairmanLiabilityAgreement (юр-текст,
  паспортный блок исполнителя под passport_request, «Общество» = председатель совета);
  интерфейсы branch (IExec.liability, ICoodecision.liability, IApprliab/IDeclliab).
- factory: Template+Action 328 (individual через getUser).
- contract branch: exec(...,liability) хранит договор; confirmdec запускает
  create_approval; apprliab финализирует (make_complete_document); declliab блокирует отказ.
- controller: DTO 328 + liability в ExecKuDecisionInput; execDecision теперь требует
  избранного председателя (assertIsDecisionChairman); генератор + резолвер; адаптер;
  проекция решения расширена полем liability.
- desktop: execDecision генерит и подписывает договор 328 в пакете; метка действия
  и скрытие кнопки «Отклонить» для недопустимого к отказу договора в /chairman/approvals.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Карточка предложения уже читает query from (cart/consolidated); добавлены ветки
orders → «К реестру заказов» и offers → «К реестру предложений». Переходы из
реестра заказов и реестра предложений передают соответствующий from — иначе
кнопка всегда вела «К модерации».

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Совет лишь одобряет проект списания; фактическое выбытие со склада инициирует
председатель КУ подписью Служебной записки о списании (registry 1111). Новый
on-chain action marketplace::confirmwroff(coopname, signer, proposal_hash,
braname, memo) закрывает позиции одного КУ (гранулярность по участку), проводит
o.mkt.wroff и якорит записку в реестр документов (package = proposal_hash).
cooptypes: обёртка ConfirmWroff + интерфейс IConfirmWroff + документ 1111.
factory: Action+Template 1111 (подписант — председатель КУ).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Канон ролей зафиксирован: chairman=председатель совета, trustee=председатель участка, trusted=доверенное лицо. Документ 328 BranchChairmanLiabilityAgreement переименован в BranchTrusteeLiabilityAgreement (имя Chairman теперь врало). Документ 327 переписан: вместо короткого огрызка на ICommonUser — полный текст договора (как 328) для доверенного лица на individual (с паспортом), вторая сторона = председатель участка (trustee_full_name). Контракт не трогался: trusted-флоу generic над document2. Сквозь cooptypes/factory/controller/sdk + desktop (DecisionFlow/TrustedFlow). Codegen+SDK пересобраны, 14/14 ku-тестов, ESLint чист.
Совет больше не запускает списание автоматически: onCouncilAuthorized переводит
проект в PENDING_CONFIRMATION и ждёт подтверждения складов. Председатель КУ
подтверждает фактическое списание своего участка (mutation marketplaceConfirmWriteoff
→ confirmwroff), подписав Служебную записку 1111; когда подтверждены все КУ —
проект EXECUTED. Запросы: marketplaceListWriteoffCandidates (admin — просроченный
скоропорт на складах), marketplaceWriteoffPendingConfirmations (стол ПВЗ, группы
по КУ оператора), marketplaceWriteoffServiceMemoSignablePayload (превью 1111).
Грант operator: Writeoff read/confirm:own-KU. Стандарт p.mkt.wroff приведён к
новому пути (+ фикс дрейфа registry_id 1108/1107). SDK: селекторы, операции, Zeus.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Стол администратора: на странице списаний — выделяемая таблица кандидатов
(просроченный скоропорт на складах), «Новый черновик» активна при выделении и
собирает предзаполненный черновик из выбранного. Новая страница стола ПВЗ
marketplace-pvz-writeoffs: группы одобренных советом списаний по КУ оператора,
просмотр позиций и протокола совета, «Подтвердить списание» — подпись Служебной
записки 1111 (confirmwroff). SDK-обёртки запросов/мутации.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Мутация saveMyPassport (guard только аутентификация, @CurrentUser, пишет в свой individual через IndividualRepository; reject если паспорт уже установлен — обновление это отдельный процесс). Нужна для ролей, которым паспорт обязателен (председатель участка, доверенное лицо) в кооперативах без сбора паспорта при регистрации. Договоры 327/328 теперь показывают паспортный блок по наличию individual.passport (а не по кооп-флагу vars.passport_request) — паспорт этих ролей обязателен независимо от настройки кооператива. Backend + cooptypes + SDK; codegen обновлён, controller компилируется.
Feature User/CollectPassport: BaseDialog (канон) с паспортными полями + useRequirePassport. Председатель участка собирает паспорт при направлении в совет (execDecision, договор 328), доверенное лицо — при подаче заявки (requestTrusted, договор 327). Если паспорт уже в профиле (session.privateAccount.individual_data.passport) — окно не показывается, действие идёт сразу. Сохранение через saveMyPassport + обновление session. entities/Account/api.saveMyPassport. ESLint чист.
- кандидаты и позиции проекта отдают branch_name (резолв с кэшем), в UI больше нет служебного braname
- статус PENDING_CONFIRMATION добавлен в детали проекта
- диалоги пути списания (Submit/Confirm/DraftEditor/Details) переведены на BaseDialog+BaseButton, убран FontAwesome (fa-signature → Material draw)
- протокол совета на столе ПВЗ — канон-контейнер вместо сырого q-card

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Колонка «Гарантия» (warranty_days, «N дн.» / «—») между «Доступно» и «Создано»; поле уже приходит в marketplaceListAllOffers, бэкенд/codegen не трогали.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Добор по акцепту (ACCEPTED вне партии) показываем и принимаем ТОЛЬКО вместе с привезённой партией. Поставщик без партии: добор скрыт, takeAddon=false (нет фантомного акта), в диалоге — empty-state «Нет партии к приёмке». Следствие: самовывоз без сформированной партии этим путём больше не принимается.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Откат прошлого поведения (66c3812): приёмку без партии НЕ убираем.
- Есть партия: чекбокс «Принять добор» показан и по умолчанию ВЫКЛЮЧЕН (не залетает сам).
- Нет партии: чекбокса/обёртки «добор» нет вовсе (путает оператора без контекста партии), имущество показываем как «Имущество поставщика» и принимаем (takeAddon включён скрыто) — «Сформировать акты» работает.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Кандидаты на admin-столе списания теперь показывают всё имущество на складе
(не только просроченное), с флагом is_expired и подсветкой просрочено/годно:
председатель может вручную списать ещё годное (порча, невозврат) и протестировать
флоу — раньше позиции с warranty_days=0 (expiry_date=null) не попадали в список.
Авто-крон не тронут (ежемесячный + grace, null-срок не трогает). Гарантия 0 дней
в реестре предложений и карточке показывается как «Без гарантии», а не «—»/∞.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Гарантия = конечный срок возврата пайщиком или 0 (нет гарантии); бессрочной
гарантии не существует. Три состояния позиции в кандидатах на списание:
«Годен» (в сроке) / «Просрочен» (срок прошёл, авто-кандидат) / «Без гарантии»
(гарантия 0 — ручное списание сразу как испорченное/использованное, авто-крон
не трогает). Колонка «Срок годности» → «Годен до»; null → «Без гарантии» вместо
противоречивого «Без срока»+зелёного «Годно». Подсказка формы создания уточнена:
0 — без гарантии (возврат невозможен). Бэкенд не менялся.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Поток списания свёрнут до одного действия: председатель выделяет имущество на
складах, при необходимости пишет причину (одно поле, по умолчанию пусто →
причина по состоянию позиции) и жмёт «Подписать и отправить в совет» — черновик
собирается под капотом и сразу открывается подпись Заявления. Убраны
промежуточный экран «Открытый черновик», кнопка «Изменить состав» и
DraftEditorDialog. Висящий черновик (прерванная подпись/крон) снимается перед
сборкой свежего. Бэкенд не менялся.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
В одношаговом потоке диалог подписи Заявления монтируется уже открытым
(modelValue=true задаётся одновременно с draft), поэтому watch без immediate не
срабатывал и previewDoc оставался null — белый экран без кнопок. Добавлен
immediate:true: загрузка документа идёт и при появлении уже открытого диалога.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Документ 1108: убрана колонка «КУ» (наименования участков в блокчейн не шлём —
юридически достаточно наименование/кол-во/сумма/причина) и строка про транзит
счёта 91; вёрстка переведена на канон без white-space:pre-wrap (убирает большие
отступы), суммы позиций рендерятся с символом валюты как ИТОГО, дата цикла — в
формате ДД.ММ.ГГГГ. Те же правки формата/вёрстки применены к Служебной записке
1111. Причина по умолчанию для просрочки и товара без гарантии — «Истёк срок
годности». Лоадер диалога подписи отцентрован. Требуется перезапуск контроллера
для подхвата нового шаблона (cooptypes/factory dist).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Все документы идут он-чейн: доверенности едут вторым document2 рядом с договорами
матответственности и визируются теми же подписантами, после одобрения комплитятся
(make_complete_document) в реестр документов.

Контракт branch:
- exec(+authority), reqtrusted(+authority), apprtrusted(+countersigned_authority);
  поля authority в coodecision/trustreq.
- доверенность №1 визирует председатель совета — отдельное одобрение branchauth в
  confirmdec (approval_hash = хэш документа) + новые apprauth/declauth.
- доверенность №2 и договор 327 комплитятся в apprtrusted встречной подписью
  председателя участка.

cooptypes: реестр 329 BranchTrusteePowerOfAttorney, 330 BranchTrustedPowerOfAttorney;
interfaces/branch + actions apprAuth/declAuth. factory: Actions/Templates 329/330.
controller: DTO/service/resolver генерации, проброс authority в адаптере, проекция
trustreq.authority + agregate authority_document для встречной подписи. sdk: селектор
заявки + zeus reген. desktop: генерация+подпись доверенностей в DecisionFlow/TrustedFlow,
встречная подпись обоих документов в approveTrusted, просмотр доверенности в виджете.

Контракт пересобран (branch.wasm/.abi), требуется редеплой + рестарт чейна/парсера.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Страница списания переверстана по канону: вкладки PageTabs «Кандидаты / На
повестке / Архив» вместо россыпи карточек и списков; ленты проектов — канон-
таблица .table-wrap с BaseBadge-статусами и EmptyState; CTA только на вкладке
кандидатов. Документ Заявления 1108: сумма в формате «1 020,00 RUB» (2 знака,
разделители), из преамбулы убрано «по итогам цикла», под заголовком добавлен
город «г. {{ coop.city }}». Те же суммы 2-знака применены к Записке 1111.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Рендерер BaseDocument навязывает в Shadow DOM white-space: pre-wrap и
th{width:30% !important}; пятиколоночный thead давал 5×30%=150% и таблица
уезжала вправо («Причина» схлопывалась в столбик), а кастомные margin'ы вместо
канонных переносов давали огромные вертикальные зазоры. Привёл оба документа
1-в-1 к style-блоку и структуре сиблингов (эталон 1106): addressee справа,
заголовок по центру, город под подзаголовком справа; список позиций —
многоколоночная таблица без <th> (шапка через <td><b>), суммы 2 знака.
Путь списания — единственный marketplace-процесс через повестку совета,
поэтому в decisionFactory не было обработчика типа mktwroff: при «Утвердить»
process-decisions не мог сгенерировать документ-Решение и падал. Зарегистрировал
обработчик: собирает Action Протокола совета о списании (registry 1107) из
метаданных подписанного Заявления 1108 (proposal_hash, цикл, сумма, число
позиций) + decision_id и генерит через generic GenerateDocument — фабрика сама
резолвит состав совета, голоса и реквизиты по decision_id.
[C28-38][@ant] правка: повестка КУ — канон-вёрстка, понятный термин адреса, авто-обновление
Typecheck / desktop (pull_request) Failing after 8m35s
Typecheck / controller (pull_request) Has been cancelled
1fcaa242bc
- «адрес привязки» → «по адресу, определённому собранием пайщиков» (термин убран)
- «Повестка дня» → «Повестка собрания»; карточка повестки по канону MeetDetailsAgenda
  (шапка с иконкой, проект решения в обособленном surface-2 блоке, подпись «Ваш голос»)
- фоновый поллинг состояния (15с) на странице собрания и в списке, пока нет websocket

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
[C28-38][@ant] фикс: устойчивость стола КУ + протоколы собрания на странице
Typecheck / desktop (pull_request) Failing after 8m12s
Typecheck / controller (pull_request) Successful in 13m11s
248375a1bf
- getBranches больше не роняет весь список 400-й «Организация не найдена»,
  когда карточка участка ещё не материализована обработчиком confirmdec
  после одобрения советом — не-готовый участок пропускается до след. загрузки
- на странице собрания публикуются протокол собрания пайщиков (323) и решение
  совета (325) агрегатами (html+подписи+бюллетени); договор матответственности
  (328) и доверенность (329) с паспортными данными туда НЕ выносятся
- KuDecision: +protocol_document/+authorization_document (агрегаты), codegen

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
[C28-38][@ant] правка: маски паспортных полей (строки, без спиннеров)
Typecheck / desktop (pull_request) Failing after 8m29s
Typecheck / controller (pull_request) Has been cancelled
b30cf4cdcc
- BaseInput: проп mask (проброс в q-input)
- форма паспорта: серия #### / номер ###### / код ###-### (только цифры,
  без стрелок number), плейсхолдеры + валидация полноты перед сохранением

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- контракт exec: newlink договора (328) и доверенности (329) к пакету повестки
  совета (как votedec линкует бюллетень); branchliab/branchauth в soviet_actions —
  совет читает их вместе с заявлением (нужен redeploy branch)
- предпросмотр перед подписанием: данные → генерация документов с подставленными
  данными → показ на прочтение → подпись и отправка. Для exec (председатель: заявление+
  договор+доверенность) и requestTrusted (доверенный: договор+доверенность)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
[C28-38][@ant] правка: документы заявки — отдельная колонка со ссылками
Typecheck / desktop (pull_request) Failing after 8m19s
Typecheck / controller (pull_request) Successful in 13m25s
359643f4ce
Договор/Доверенность вынесены из «Действия» в колонку «Документы» —
ссылки с иконкой (не ghost-кнопки); «Действия» оставлены под Одобрить/Отклонить

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Проблема 1 (решение совета не отражалось): не было слушателя on-chain
callback'ов совета — onCouncilAuthorized/onCouncilDeclined звались только в
тестах. Добавлен MarketplaceWriteoffSyncService на action::marketplace::
onmktwoauth/onmktwodecl → проект ON_AGENDA → PENDING_CONFIRMATION, Протокол
сохраняется.

Проблема 2 (нельзя создать второй проект пока один в совете): снят гард
findOpenInCouncil в createDraft и частичный unique-индекс
UQ_..._active (несколько проектов в совете допустимы — разные партии
скоропорта независимы). Защита от двойного списания одной позиции перенесена
на уровень кандидатов: findActiveLockedInventoryIds исключает из списка
позиции, уже занятые в незавершённых проектах.
Лента: имя проекта по дате («Списание от ДД.ММ.ГГГГ») + корректное склонение
«N позиция/позиции/позиций» вместо «1 позиций». Детальный диалог: канон-таблица
позиций (.table-wrap + .col-num + BaseBadge статуса) вместо q-markup-table;
журнал решений — человеческие формулировки вместо кодов draft_created/
submitted_to_council; «Решение совета» — итог рассмотрения (Одобрено/Отклонено/
На рассмотрении) вместо служебного номера; on-chain идентификатор укорочен и
вынесен вниз отдельной подписью.
Склонение «позиций» и имя проекта по дате повторялись в AdminWriteoffsPage и
WriteoffProposalDetailsDialog — вынес в ../lib/proposalDisplay по правилу DRY.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Несколько партий (inventory lots) одного товара на одном КУ показывались отдельными
визуально идентичными строками (та же дата годности, тот же КУ) → пайщик видел, как
количество «прыгает» 40↔100, и после списания одной партии вторая оставалась.
Модель позиции переведена с одиночного inventory_id на inventory_ids[]: кандидаты
агрегируются по (braname, asset_title, состояние), одна строка несёт все партии и
суммарное кол-во/сумму; исполнение и блокировка повторного списания идут по всем
партиям агрегата. Сквозь domain→service→dto→mapper→sdk(zeus)→desktop + codegen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Документы 1108/1111: убран white-space:pre-wrap (рендерил переносы строк исходника
и вместе с дефолтными margin'ами <p> удваивал разрывы — документ разъезжался по
вертикали); спейсинг переведён на явные margin'ы по эталону уплотнённого 1110;
'.digital-document { white-space: normal }' перебивает форсированный pre-wrap из
Shadow DOM рендерера. Подписант 1108 — 'от Председателя Совета'. Диалог подписи:
документ рендерится канон-компонентом DocumentHtmlReader в листе фиксированной
ширины (на мобильном — во всю), без 60vh-обрубка. Детальная страница: журнал
решений переведён на канон ActivityTimeline, убран служебный on-chain идентификатор.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Диалог подписи: заявление рендерилось через DocumentHtmlReader, который
форсит h1 line-height 4.5rem и режет <style> документа DOMPurify'ем —
заявление «разлетается» по вертикали. Переведено 1-в-1 на эталон
заявления на вступление (Registrator/SignUp/ReadStatement): .statement +
:deep-нормализатор бекенд-HTML к канон-типографике.

Детальная страница проекта: maximized-диалог рендерил контент full-bleed
без контейнера (поля minmax растянуты на весь экран, всё скучено в углу).
Переверстано по эталону ProfilePage: центрированная колонка max-width 920,
секции в BaseCard, «Основные параметры» через канон-компонент DataRow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Документы 323/324/325 (chairman) и 327/330 (trustee) хранили ФИО
председателя в Action → оно попадало в document2.meta и публиковалось
on-chain plaintext. Теперь в Action идёт только username; фабрика через
getUser(username, block_num) резолвит ФИО на момент генерации и вставляет
в рендер PDF (канон 702.AssetContributionAct.receiver). Model/шаблоны не
тронуты — ФИО по-прежнему в документе, но не в meta.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
В повестке совета документ рендерит BaseDocument: он прогоняет html через
sanitizeHtml (ВЫРЕЗАЕТ <style> документа) и форсит в Shadow DOM
.digital-document{white-space:pre-wrap}. Прежняя вёрстка 1108/1111 полагалась
на <style> с классами (.addressee/.title-block + margin'ы) — всё вырезалось,
оставался pre-wrap → огромные отступы + выравнивание влево.

Переверстано 1-в-1 по канон-документу 1106 (он корректен и в повестке, и в
предпросмотре): выравнивание инлайн (style=text-align), вертикальный ритм —
пустыми строками под pre-wrap, плотные абзацы — инлайн margin:0px,
центр-заголовок — h1.header (shadowStyles центрирует .header).

Добавлена колонка «Ед. изм.» (ед.) — у инвентаря нет поля единицы, склад
считается в дискретных единицах, поэтому константа.

Предпросмотр (SubmitToCouncilDialog): рендер html как есть, без класса
.statement (тянул глобальный h1{line-height:4.5rem}) и без нормализатора,
ломавшего pre-wrap — теперь идентичен повестке.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[C28-38][@ant] правка: в превью неподписанного документа скрываем блок контрольной суммы
Typecheck / desktop (pull_request) Has been cancelled
Typecheck / controller (pull_request) Has been cancelled
f1a0555056
Превью-перед-подписанием строит агрегат с пустым doc_hash (канонического
хэша ещё нет), а BaseDocument пересчитывал хэш из бинарника и DocumentSignatures
показывал красную «Локально пересчитанный хеш не совпадает» — ложная ошибка,
документ просто ещё не подписан. Теперь BaseDocument при пустом doc_hash не
запускает пересчёт и прячет блок контрольной суммы (проп hideChecksum). Для
реальных подписанных документов doc_hash есть → сверка работает как раньше.
Вёрстка и кнопка «скачать» сохранены.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Колонка marketplace_writeoff_proposal.status была varchar(16), а статус
PENDING_CONFIRMATION = 20 символов: callback совета onmktwoauth отлавливался,
но UPDATE статуса молча падал «value too long» → проект навсегда застревал в
ON_AGENDA (решение совета «не отлавливалось»). Расширил до 32. На колонке индекс
(coopname,status), поэтому synchronize не смог изменить длину сам (recreate →
ADD NOT NULL на непустой таблице) — расширение выполнено ALTER COLUMN TYPE на месте.

Единицы измерения в Заявлении (1108) и Служебной записке (1111): вместо зашитой
«ед.» бэкенд резолвит реальную единицу товара по партиям позиции
(inventory → заказ/published_offer → offer.unit_of_measure → шт./кг/л/упак.),
прокидывает в документ; шаблоны рендерят {{ it.unit }}.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
По просьбе — не только красную плашку, а весь блок DocumentSignatures целиком
(рамка, контрольная сумма, кнопка «скачать») скрываем у документов без
канонического doc_hash (неподписанное превью). Гейт перенесён на уровень
BaseDocument (v-if='hasDocHash' на всём блоке); проп hideChecksum откатан как
лишний — DocumentSignatures вернулся к исходному виду. Подписанные документы
(doc_hash есть) отображаются с полным блоком как раньше.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Удалён read-only стол BoardAgendaWriteoff (повестка совета по списаниям) —
страница не нужна: маршрут marketplace-board-writeoff из install.ts, сам пейдж,
ссылки в комментариях.

Протокол совета на столе ПВЗ: вместо инлайн-тоггла со «Протокол совета
недоступен» (в callback'е onmktwoauth html не приходит — хранится только
подпись) — кнопка открывает документ полноэкранным окном, как остальные
документы. Бэкенд регенерит Протокол 1107 фабрикой по decision_id (votes с
цепи) новым query marketplaceWriteoffProtocolDocument — тот же паттерн, что у
Заявления 1108 и Служебной записки 1111. Codegen + SDK-обёртка.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Протокол на столе ПВЗ НЕ регенерируем фабрикой: документ уже подписан советом и
лежит в реестре — query marketplaceWriteoffProtocolDocument теперь собирает
DocumentAggregate из подписанного protocol_doc через buildDocumentAggregate
(тело по doc_hash + подписи), как issuance/return-claim. Возврат — DocumentAggregate;
фронт показывает канон-просмотрщиком DocumentViewerDialog. Codegen + SDK-обёртка
на documentAggregateSelector.

ConfirmWriteoffDialog: Служебная записка была обрезана (max-height:60vh + overflow)
— убрал ограничение высоты, документ листом фиксированной ширины (820px), как в
SubmitToCouncilDialog; прокручивается весь диалог.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- DocumentViewerDialog: лист документа центрируется (max-width 900px), а не прижат влево на maximized-диалоге
- Loader (общий) вместо сырого q-spinner в ConfirmWriteoffDialog/SubmitToCouncilDialog и трёх Process*InfoWidget
- 1111 Служебная записка + 1107 Протокол: убраны строки «Идентификатор проекта списания» и бухпроводка Дт86/Кт10 — пользователь не просил этот текст

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- DocumentViewerDialog: лист центрируется блочным margin:0 auto (max-width 900px) — прежний flex+:deep не применялся, протокол оставался прижат
- useFioCache: к ФИО пайщика добавлен fallback на имя КУ из branch-стора (short_name||full_name||full_address); процесс списания пишет на цепочке braname КУ — теперь показывается человеческое имя участка, а не «kaffjpgeznnu»

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- BaseDocument.shadowStyles: .digital-document h1 { text-align:center } — заголовок (ПРОТОКОЛ №, СЛУЖЕБНАЯ ЗАПИСКА и пр.) центрируется на слое рендера, как в других документах; инлайн-стили выживают санитайзер, а блочные правила <style> — нет, поэтому правка шаблона существующий подписанный документ не центрировала
- DocumentViewerDialog: убрал лишнюю обёртку-лист (ширину никто не просил, проблема была только в выравнивании заголовка)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[C28-38][@ant] фича: панель состояния мажоритарной системы управления на странице КУ
Typecheck / desktop (pull_request) Failing after 8m30s
Typecheck / controller (pull_request) Successful in 13m20s
6d89d96ad3
Отображает флаг is_branched (system.info.cooperator_account) — включается
контрактом при 3+ кооперативных участках. Read-only индикатор + пояснение
о мажоритарной модели общего собрания через участки.
[C28-38][@ant] фича: колонка «Пайщики» (число участников КУ) в списке участков
Typecheck / desktop (pull_request) Failing after 9m37s
Typecheck / controller (pull_request) Successful in 13m20s
78822f3d6d
Бэкенд считает пайщиков участка из таблицы soviet participants
(status accepted + braname) и отдаёт participants_count в payload веток
(getBranch/getBranches, одно чтение таблицы на список). Поле добавлено в
DTO Branch, оба SDK-селектора, регенерирован zeus. На фронте — колонка
в таблице КУ. Председатель и доверенные входят в число пайщиков участка.
- AccountDomainService.getAccount: распознаёт КУ (branchBlockchainPort.getBranch) и строит private_account из его орг-данных тем же каналом, что и кооператив; getDisplayName/сертификаты/фронтовый getAccount резолвят имя участка автоматически, без ветки у каждого потребителя
- enum AccountKind (participant|branch|cooperative|unknown) + поле account_kind в AccountDTO — дискриминатор субъекта, чтобы UI не путал КУ с организацией-пайщиком
- SDK: account_kind в accountSelector + zeus-клиент
- desktop useFioCache: убран костыль-fallback в branch-стор, опора на getAccount; отдаёт kindCache
- ProcessesPage (стол бухгалтера): имя участка вместо braname + метка «КУ» по account_kind=branch

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[C28-38][@ant] фича: карточка «Пайщики участка» на странице КУ + единый вид карточек лиц
Typecheck / desktop (pull_request) Failing after 8m48s
Typecheck / controller (pull_request) Has been cancelled
74a19d7e0e
Добавлена карточка с количеством пайщиков участка (participants_count из
публичного payload ветки). Карточка председателя переведена на PersonCard
density=compact — убрана рамка, чтобы совпадала с доверенными лицами.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Поставщик отклонял входящий заказ (decline=erase строки в chain-RAM), но
заказ исчезал и тут же возвращался в «Ждут акцепта», а повторный
decline/accept падал «не найден по хэшу». Причина: парсер на erase шлёт
present=false с последним живым value строки ('active'); у терминального
CANCELLED_BY_SUPPLIER forward-rank=undefined, поэтому updateFromBlockchain
перетирал backend-статус обратно в ACTIVE. Guard: не менять статус, если
заказ уже в терминальной отмене/возврате (mapper выдаёт только живые
on-chain статусы, cancelled — KNOWN_UNMAPPED). +2 регрессионных теста.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Три полировки стола заказов:

1. Уведомление заказчику при отказе поставщика. Раньше decline слал только
   ws-сигнал — push/email заказчику не уходил. Новый Novu-workflow
   marketplace-order-declined-by-supplier (email/in-app/push с причиной +
   названием товара + КУ); событие MARKETPLACE_ORDER_DECLINED_BY_SUPPLIER
   эмитится из declineOrdersBatch по каждому заказу (имя товара батчем по
   offer_id), листенер в MarketplaceNotificationService резолвит имя заказчика
   и КУ и шлёт push. Best-effort, основной flow не блокирует.

2. Тост «Добавлено в корзину» получил CTA «Перейти в корзину» — быстрый переход
   к оформлению прямо из всплывашки, без поиска корзины отдельно.

3. Диалог открытия выдачи: кнопка «Подписать и открыть выдачу» дизейблилась
   без объяснения. Добавлен баннер с причиной (нет на складе / факт больше
   принятого / нулевые кол-во-цена) + отдельный статус строки «Нет на складе»
   (принято 0) в CorrectionTable вместо общей «Недостача».

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
В диалоге открытия выдачи добавлена колонка-чекбокс «Выдать»: оператор
отмечает позиции, которые пайщик забирает сейчас; снятые остаются на складе и
в выдачу не попадают (acts формируются только по выбранным — openIssuance
per-order, бэк без изменений). Позиции с принято=0 авто-исключены (выдать
нечего) и больше не блокируют всю выдачу. Суммы/возврат/валидность считаются
по выбранным; итоги показывают «к выдаче N из M» и «остаётся на складе».

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Граница удержания — акцепт поставщиком. До акцепта отмена бесплатна (полный
возврат), после — cancelorder удерживает 50% тела заказа и членского взноса в
общий кошелёк КУ (w.brn.common), имущество остаётся на складе КУ, вторая
половина возвращается пайщику. Новая ledger-операция o.mkt.penal: транзит
w.mkt.order → w.mkt.fee, далее в КУ существующим Branch::accrue — прямой перевод
между USER_SHARED-кошельками с разным разрезом невозможен (walletop держит один
username на обе стороны). Имя o.mkt.penal (не penalty) — 13-й символ eosio::name
не может быть позже 'j'. Helper retain_refusal_penalty + refund_order_full (DRY),
обновлён стандарт p.mkt.supply и cooptypes-зеркало операции.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[C28-38][@ant] фича: приватные КУ с белым списком — контракт+cooptypes+backend+sdk
Typecheck / controller (pull_request) Has been cancelled
Typecheck / desktop (pull_request) Has been cancelled
e55622636d
branch: поля is_private/whitelist (binary_extension) + actions setprivate/addwhite/delwhite (auth председателя совета); soviet::selectbranch запрещает выбор приватного КУ вне белого списка. backend: is_private/is_available(computed per-user)/whitelist_certificates(role-gated) в Branch DTO, 3 мутации, фильтр-готовность. codegen: zeus+селекторы.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Расширил declineorder с ACTIVE на ACCEPTED/SUPPLY_PREPARED: поставщик
подтверждает отмену поставки непринятой позиции (ноль единиц в факт), имущество
ещё не оприходовано (purch на закрывающей подписи) и поставщик не оплачен —
клоубэка нет, полный возврат стоимости и членского взноса заказчику (непринятие
не его вина). Зеркально отказу на выдаче, но инициатор — поставщик и без штрафа.
Обновлён стандарт p.mkt.supply.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[C28-38][@ant] фича: UI приватных КУ — управление белым списком + фильтр выбора
Typecheck / desktop (pull_request) Has been cancelled
Typecheck / controller (pull_request) Has been cancelled
3b85a2b783
SDK: операции setBranchPrivate/addBranchWhitelist/deleteBranchWhitelist. desktop: на странице КУ председателю — разворот строки с галочкой «Приватный» и белым списком (добавление по ФИО/удаление); значок «Приватный» в списке; фильтр недоступных приватных участков (is_available) при регистрации и смене КУ.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
[C28-38][@ant] docs: guard приватного участка в стандарте sov.selectbranch
Typecheck / desktop (pull_request) Failing after 8m47s
Typecheck / controller (pull_request) Has been cancelled
93239d71f0
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Расширил CANCELABLE_STATUSES на ACCEPTED/SUPPLY_PREPARED/ACCEPTED_TO_COOP:
заказчик может отказаться от получения вплоть до открытия акта выдачи. Граница
бесплатно/50% — на контракте (до/после акцепта поставщиком), бэкенд лишь не
закрывает действие раньше. Закрыто на READY_TO_RECEIVE/RECEIVED. Спека: 15/15.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Диалог финализации заказчика принимает refusedOrders (остаток ACCEPTED_TO_COOP
того же пункта — оператор не открыл по ним выдачу = отказ). Пайщик видит две
секции «Получаете / Отказываетесь (удержим 50%)» и подтверждает целиком одной
кнопкой: signiss2 по принятым + cancelOrder по отказным (его сессия, cancel:own).
Не согласен → закрывает, оператор переформировывает. Списание строго в момент
подтверждения. Документы/акт не трогаем — в акт идёт только принятое. ESLint ok.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[C28-38][@ant] правка: управление приватностью КУ — на стол председателя
Typecheck / desktop (pull_request) Failing after 8m46s
Typecheck / controller (pull_request) Successful in 13m32s
b22fbe1845
Перенёс управление приватностью+белым списком в MemberBranchListPage (стол председателя «Кооперативные участки», развёрнутая строка) через компонент BranchPrivacyManager. Откатил KuBranchesPage (стол «Кооперативный участок») к виду без управления — там оно было не к месту.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
[C28-38][@ant] правка: белый список КУ — любой пайщик, без аккаунтов участков
Typecheck / desktop (pull_request) Failing after 8m50s
Typecheck / controller (pull_request) Successful in 14m0s
72bd5d96d0
contract addwhite: убрано ограничение «только физлицо» — в белый список можно любого пайщика. desktop: UserSearchSelector получил проп exclude, BranchPrivacyManager исключает аккаунты КУ (branames) из поиска пайщиков — КУ не является пайщиком.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Разнос группы приёмки на принятые (факт>0) и отклонённые (факт=0) на шаге
подписи поставщика: signsupp только по принятым, declineorder по нулям
(полный возврат стоимости и членского взноса заказчику без штрафа + erase).
Вся партия некондиция → приёмка CANCELLED + shipment CANCELLED. Председатель
(signchair/ACCEPTED_TO_COOP/склад/выплаты) работает только по принятым.
DRY: declineOrdersAtReception переиспользует runDeclineChain+уведомления.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Поставщик видит два блока: «Принимается» (факт>0, нулевые строки скрыты) и
«Отклоняется (некондиция)» — снятые оператором позиции с пояснением, что
подтверждая, он отменяет их поставку (заказчикам полный возврат, удержания нет).
Метка кнопки «Подтвердить отмену», если принимать нечего. Пустой список актов
(вся партия снята) больше не ошибка — мутация с пустыми документами уводит
приёмку в отмену с полным возвратом.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[C28-38][@ant] merge: dev → feat/ku-selforg — разрешение конфликтов + фикс desktop typecheck
Typecheck / desktop (pull_request) Successful in 13m56s
Typecheck / controller (pull_request) Successful in 13m29s
58d7bc52bf
Конфликты:
- extensions.module.ts: оба модуля (Ku + Expenses)
- notifications/workflows/index.ts: оба набора воркфлоу (branch + expense)
- UserSearchSelector.vue: оба пропа (exclude + types)
- zeus (sdk+controller): перегенерирован из объединённой схемы
  (rebuild stale dist: cooptypes, factory, inter, notifications)

Фикс vue-tsc (упавший desktop-typecheck на PR #130):
- BaseInput: добавлен type 'datetime-local' (q-input его поддерживает)
- KuMeetingDetailsPage: AgendaNumberAvatar number-коэрс (?? ""),
  docTarget/openMeetingDoc типизированы IDocumentAggregate,
  preview-агрегат неподписанного документа — каст
- KuBranchDetailsWidget: documentTarget/authorityTarget типизированы
  IDocumentAggregate, preview-агрегат — каст

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Поставщик не согласен со снятыми позициями целиком (повезёт замену позже):
до его подписи приёмка — черновик, оператор отменяет акт и пересобирает.
- service.cancelReception: только PENDING_SUPPLIER_SIGN, приёмка → CANCELLED,
  партия → SUPPLY_PREPARED; on-chain ничего не было, заказы не трогаем.
- findByShipmentId исключает CANCELLED → повторный create() по партии проходит.
- мутация marketplaceCancelAplReception (Receiving create, член КУ) + codegen.
- фронт: кнопка «Отменить и пересобрать» на карточке приёмки PENDING_SUPPLIER_SIGN.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ветка signiss2 «факт больше заказа» конвертировала паевой в членский (o.mkt.conv)
и добирала довзнос с паевого (o.mkt.fee) автоматически, без Заявления о конвертации.
Это нарушало инвариант «паевой→членский только по заявлению»: на выдаче паевой
трогать нельзя нигде. Теперь доплата тела (o.mkt.lockm) и довзнос (o.mkt.lockmf)
берутся ТОЛЬКО с w.mkt.member; при нехватке — отказ с просьбой сконвертировать
заранее. Backend покажет пайщику разложение (с членского / с паевого по заявлению)
до подписи. Зачем: деньги пайщика на паевом не должны списываться помимо его
явного волеизъявления — конвертация всегда отдельный подписанный документ.
Комментарии называли дособираемую при факте>заказа часть «довзносом с паевого».
По сути это членский взнос (комиссия КУ, идёт в w.mkt.fee → w.brn.common), а
источник средств теперь членский (o.mkt.lockmf), не паевой. Правка только текста
комментариев — логика не меняется; чтобы код самодокументировался корректно.
Стандарт описывал старую модель: при факте больше заказа доплата вносится
дополнительным паевым взносом с авто-конвертацией в членский. Контракт это
уже не делает — паевой на выдаче не списывается и не конвертируется; доплата
и недостающая часть взноса берутся из членских средств, а паевой пайщик
переводит в членский заранее отдельным Заявлением о конвертации. Подровнял
текст под код (дрейф стандарт↔код — прав код).
getAcceptSignablePayloads теперь отдаёт member_amount (сколько стоимости докладки
покроется уже внесёнными членскими «Стола заказов», включая высвобожденные
недосдачей) рядом с convert_amount (дефицит → конвертация с паевого по Заявлению).
Сумма обеих = полная стоимость докладки. Зачем: оператор/пайщик видят в акте
разложение «с членского / с паевого» до подписи — backend считает целочисленно,
фронт не дублирует. Прокинул через DTO, резолвер, SDK-селектор + codegen.
Оператор в окне «Открытие выдачи пайщику» кнопкой «Со склада» открывает выбор
опубликованного остатка КУ (StockPickDialog), набирает позиции — они встают
секцией «Доложено со склада» в том же акте и при «Подписать и открыть выдачу»
создаются предложением пайщику (createStockProposal). Зачем: «это не пришло —
возьмите вот это» оформляется в одном окне выдачи, без отдельного экрана докладки.
Инкремент 1 (сторона оператора); две суммы и слияние подписи у пайщика — следом.
В гейте подписи на месте карточка предложения со склада теперь показывает
разложение ДО подписи: «спишется с членского «Стола заказов»» (member_amount)
и, при дефиците, «конвертация с паевого (по заявлению)» (convert_amount).
Суммы предзагружаются через getStockProposalSignablePayloads (кэш по proposal_id),
ушедшие предложения чистятся. Убрана устаревшая подпись «Итого (паевой взнос)».
Зачем: пайщик видит, что и откуда спишется, до согласия — как договорено.
Переработка докладки/QR-выдачи со склада кооператива под требование «пайщик видит
готовый акт и одну кнопку Подписать, без отдельного Принять» (вариант Б1).

Оператор при формировании докладки получает АПП-выдачи по строкам корзины
(marketplaceStockIssuancePayloads) и подписывает их первой подписью (signiss1) на
детерминированный order_hash; бандл хранит подписанные акты. Пайщик одной подписью
утверждает докладку (marketplaceFinalizeStockIssuance): при дефиците членских —
конвертация с паевого по Заявлению, затем по каждой строке заказ из остатка
(stockorder) → выдача (signiss1 оператора) → получение (контрподпись signiss2),
заказ сразу RECEIVED. Паевой не трогается без подписанного Заявления.

Убрана двухфазная семантика «Принять предложение» (marketplaceAcceptStockProposal):
принятие = подпись акта получения. SDK/codegen синхронизированы.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Оператор в окне выдачи («Со склада») теперь получает АПП-выдачи по строкам
корзины и подписывает их первой подписью (signiss1) сразу при формировании
докладки (getStockIssuancePayloads + createStockProposal с подписанными актами).

Пайщику докладка приходит в гейт готовым актом (пункты имущества, кол-во, цена,
две суммы: с членского / конвертация с паевого). Одна кнопка «Подписать»
(+«Отказаться»): подписывает Заявление о конвертации при дефиците и
контрподписывает signiss2 по строкам — finalizeStockIssuance создаёт заказы и
выдаёт имущество. Кнопка «Принять предложение» убрана: принятие = подпись акта.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Пайщику передаётся signiss1-акт оператора на его устройство для контрподписи —
вектор: подменить тело акта (другой товар/количество) и вернуть подписанным.
Перед любыми транзакциями finalizeStockIssuance сверяет каждый контрподписанный
акт с сохранённым signiss1-актом оператора:
 - doc_hash и meta_hash совпадают с выданным к подписи (тело/мета не подменены);
 - подпись оператора сохранена нетронутой (signer+signature+public_key);
 - пайщик подписал своим ключом;
 - DocumentValidationService.validateSignedDocument: крипто + структура (recompute
   signed_hash из doc_hash+meta_hash) + сверка тела с оригиналом в сторе.
На подмене — ForbiddenException до блокировки средств. Defence-in-depth: контракт
signiss2 дополнительно режет подделку on-chain (verify_document_or_fail требует
валидную подпись оператора над телом).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Оператор подписывает signiss1 и кладёт всё (существующие заказы + докладку со
склада) в один оффчейн-бандл; на цепи до подписи пайщика ничего нет, поэтому
отмена пайщиком = отказ от бандла без он-чейн отката (никакого reopeniss и
второй ветки немедленного signiss1). Пайщик одной кнопкой контрподписывает
получение — backend проводит convert?→stockorder?→signiss1→signiss2. Снесён
мёртвый member-путь ordererTasks/listMyReadyToReceive в гейте.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Унификация сделала немедленный он-чейн-путь выдачи мёртвым: его никто не зовёт.
Удалены GraphQL-эндпоинты marketplaceOpenIssuance / marketplaceFinalizeIssuance /
marketplaceListMyReadyToReceive / marketplaceIssueActOrdererSignablePayload,
сервис-метод getFinalizeIssuanceSignablePayload, repo-метод
listReadyToReceiveByOrderer, мёртвые DTO и SDK-операции/селектор. На фронте убран
OrdererFinalizeIssuanceDialog и подпись получения в «Мои заказы»/детали заказа +
экшен receive в OrderCard (получение оформляется у стойки в гейте «подпись на
месте»). Сервис-методы openIssuance/finalizeIssuance остались — зовутся изнутри
бандла. README расширения: быстрый контекст единого пути выдачи + карта файлов.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(branch): учреждение КУ — один пакет документов вместо двух
Typecheck / desktop (pull_request) Successful in 13m23s
Typecheck / controller (pull_request) Failing after 6m41s
6d970bc0fe
closedec при типе createbranch заводил отдельный complete-document (newsubmitted)
на протокол собрания пайщиков, а exec/create_agenda — второй на заявление в совет.
Один и тот же package=hash, но два newsubmitted → две почти одинаковые карточки
в реестре документов.

Теперь для createbranch протокол линкуется (newlink) по якорному хэшу к пакету
заявления председателя в совет: заявление — единственный statement пакета, внутри
него протокол собрания пайщиков, бюллетени, договор матответственности, доверенность
и решение совета. Для free-решений протокол остаётся самостоятельным документом.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Кошельки на стол пайщика устанавливаются через декларации walletCards в
install.ts расширений + core-набор; реестр дедупит по wallet_name. Состав:
главный паевой (core, w.wal.share), членский стола заказов (market,
w.mkt.member), Благорост (capital, w.cap.blago). Главный членский ЦК
(w.wal.member) и Генератор (w.cap.gen) не показываем; минимальный
неснижаемый остаток — отдельной карточкой как раньше.

Источник балансов — новый core-резолвер getUserWallets (сырой срез
ledger2::userwallets без сворачивания паевого и членского). Зачем: убрать
хардкод трёх программ в WalletProgramWidget и дать расширениям приносить
свои кошельки единым стандартным механизмом.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(branch): учреждение КУ — разделить пакет собрания и пакет совета по якорю
Typecheck / desktop (pull_request) Successful in 13m15s
Typecheck / controller (pull_request) Successful in 12m58s
fbf6d98abc
Раньше всё (протокол, бюллетени, заявление в совет, договор, доверенность,
решение совета) висело на одном якоре hash → получались две почти одинаковые
карточки одного пакета (протокол и заявление), документы перетекали между ними.

Разводим на два процесса по разным package-хэшам:
- Пакет СОБРАНИЯ пайщиков (якорь = proposal.hash): протокол (closedec) + бюллетени
  (votedec). Появляется в реестре сразу при закрытии собрания.
- Пакет СОВЕТА (якорь = hash процесса): заявление в совет + договор матответственности
  + доверенность + решение совета. Советская часть (createagenda/confirmdec/declinedec/
  одобрения) НЕ тронута — нулевой риск.

Меняются только package-аргументы в closedec (make_complete_document) и votedec (newlink).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- членский стола заказов: убрана подпись «возврату не подлежит»;
- главный паевой (w.wal.share) опущен вниз — рядом с программными ЦПП-кошельками,
  минимальный неснижаемый остаток остаётся верхней карточкой;
- мини-кошелёк в rail (левый низ): label «Паевой кошелёк» вместо «Доступно»,
  сумма берётся из сырого w.wal.share — членская часть ЦК больше не примешивается.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- тост успеха: multiLine=false (заголовок+CTA+крестик в один ряд, без
  прыгающей второй строки), CTA сокращён до «В корзину»;
- главный паевой кошелёк ЦК (w.wal.share) — снова первым в реестре после
  минимального неснижаемого остатка (приоритет), карточка «Главный паевой
  кошелёк» / «Цифровой кошелёк»; мини-кошелёк в rail — тот же заголовок.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Backend + SDK половина эпика онбординга поставщика:
- whitelist → реестр поставщиков (marketplace_supplier): enum model(membership|share)/status(pending|approved|rejected), номер+дата договора; isOfferer = кооператив ИЛИ approved; витринные режимы и auto-coop убраны.
- мутации requestSupplier(заявка pending)/approve/reject(председатель)/addSupplier(админ, approved)/switchSupplierModel; запросы listSuppliers/mySupplierState.
- grants: Onboarding:offerer для не-одобренного пайщика (видна только страница онбординга стола поставщика), полный стол — после одобрения.
- назначение выплаты: «Оплата по договору № N от ДД.ММ.ГГГГ» (акт убран).
- SDK: селектор + 7 операций + codegen (schema/zeus/build зелёные).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Стол поставщика: страница онбординга (рубильник модели — членская активна, боевая «скоро»; ввод номера+даты договора + подсказка «обратитесь в поддержку»; статус «заявка на рассмотрении»). Гейт `Onboarding:offerer` первым маршрутом стола.
- Стол администратора: реестр поставщиков (канон .table-wrap: модель/договор/статус-бейдж) + «Добавить поставщика» (путь 2, диалог) + «Одобрить/Отклонить» в строке (видны только председателю). Маршрут `Supplier:manage`.
- SDK-обёртки через Zeus (mySupplierState/requestSupplier/listSuppliers/addSupplier/approve/reject).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(ku): документы учреждения КУ — правки методолога (Смуров, 16.06)
Typecheck / desktop (pull_request) Successful in 13m17s
Typecheck / controller (pull_request) Successful in 12m51s
c4fa1fecc2
Текст генерируемых документов учреждения кооперативного участка по
замечаниям методолога. Структура он-чейн пакетов не затронута.

- 323 Протокол собрания: убрано «кооперативного участка» из названия и
  meeting_name (просто «собрание пайщиков»); литерация «Протокол № КУ-N»
  сохранена.
- Повестка собрания (desktop, KuMeetingDetails/onStart + KuMeetings):
  в каждый вопрос добавлена принадлежность «...потребительского
  кооператива «Восход»»; полномочие обратиться в совет вшито во второй
  вопрос (третий вопрос не нужен — методолог «три в два»). Имя кооператива
  из System store (vars.name / full_abbr_genitive).
- 322 Бюллетень: в текст добавлено «потребительского кооператива «{name}»».
- 324 Заявление в совет: «прошу Совет согласовать создание участка ... и
  согласовать кандидатуру избранного председателя».
- 325 Решение совета: повестка разложена на 2 вопроса (учреждение +
  утверждение председателя и поручение Совету организовать сопровождение —
  банковский счёт, доверенность).
- 327/328 Договоры матответственности: из названия убрано «ПОЛНОЙ
  ИНДИВИДУАЛЬНОЙ» (тело без изменений).
- 329 Доверенность председателю КУ: переработана по эталону —
  реквизиты ПК в шапку, паспорт+регистрация инлайн в первый абзац,
  банковский счёт «№ —» (прочерк до открытия счёта в банке), ссылка на
  «Протокол № —» совета (прочерк), убран нижний блок «Уполномоченный: ФИО»,
  подпись доверителя + М.П.
- 330 Доверенность оператору: переработана по эталону — шапка
  «Кооперативный участок ... ПК ...», доверитель — председатель КУ,
  паспорт оператора инлайн, без банковского счёта, подпись + М.П. (КУ).

Эталоны: ~/blago/production/shared/ШАБЛОН_Доверенность_*.pdf,
ШАБЛОН_ДОГОВОР_О_МАТЕРИАЛЬНОЙ_ОТВЕТСТВЕННОСТИ_*.pdf

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- @coopenomics/notifications: workflow marketplace-new-supplier-request (email + in-app + push) с payload chairmanName/supplierName/contractNumber/coopname/deepLinkUrl; зарегистрирован в реестре.
- Событие MARKETPLACE_NEW_SUPPLIER_REQUEST_EVENT + listener в MarketplaceNotificationService: резолвит председателя (role:chairman) и шлёт Novu-workflow с deep-link на реестр поставщиков; ошибки доставки не блокируют flow (INV-12).
- MarketplaceSupplierRegistryService эмитит событие при подаче/переоткрытии заявки (после записи в PG).

Проверки: notifications build + generate-schema (Nest bootstrap, DI EventEmitter2) + ESLint — зелёные.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(ku): прочерки в доверенностях — подчёркивание под рукописное заполнение
Typecheck / desktop (pull_request) Successful in 13m26s
Typecheck / controller (pull_request) Successful in 13m3s
7f5f9867d9
Поля под ручное заполнение (банковский счёт в 329, № протокола/доверенности
в 329 и 330) — линия подчёркивания «______________» вместо em-dash, чтобы
вписать значение ручкой на распечатанном экземпляре.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
requiresProgramSelection был захардкожен на coopname==='voskhod', что
противоречило бэкенду (getRegistrationConfig.requires_selection) и принципу
«бэк управляет, фронт отображает». Теперь список программ грузится в стор
Registrator по бэкенду на шаге SetUserData, requiresProgramSelection =
availablePrograms.length > 1, а шаг SelectProgram скрывается из степпера
(v-if) и перешагивается навигацией, когда выбора нет (0/1 программа).
Единственная программа авто-преселектится, чтобы оферта всё равно
привязалась к пайщику.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolve conflict in process-decisions handlers — keep both
Ku establishment and membership exit decision generators.

Co-authored-by: Cursor <cursoragent@cursor.com>
feat(ku): упростить joindec и доработать документы участка
Typecheck / desktop (pull_request) Successful in 40m44s
Typecheck / controller (pull_request) Has been cancelled
ef96b4c81c
Удалён шаблон 321 и заявление при присоединении к собранию; joindec без document.
В протокол 325 добавлено приложение с положением участка, factory-тесты 320–330.
Экспорт getPaymentMethods.name в SDK; пункт меню «Выйти из кооператива».

Co-authored-by: Cursor <cursoragent@cursor.com>
fix(desktop): добавить поле Email в форму редактирования ИП в реестре пайщиков
Typecheck / desktop (pull_request) Failing after 25m53s
Typecheck / controller (pull_request) Successful in 28m11s
686cd0cff2
У физлица и организации поле уже было в Editable*Card; для ИП пропустили при создании компонента.

Co-authored-by: Cursor <cursoragent@cursor.com>
fix(ku): скрыть кнопки собрания по итогам голосования и ограничить председателя ФЛ
Typecheck / desktop (pull_request) Successful in 30m18s
Typecheck / controller (pull_request) Has been cancelled
07fb4599e6
Кнопки «Отменить» и «Завершить протокол» показываются только после окончания
голосования — в зависимости от принятия повестки. Председатель участка
выбирается только среди физических лиц, с проверкой на backend.

Co-authored-by: Cursor <cursoragent@cursor.com>
fix(desktop): не показывать ложное несовпадение контрольной суммы документа
Typecheck / controller (pull_request) Has been cancelled
Typecheck / desktop (pull_request) Has been cancelled
ff1965376d
Пересчёт хэша при появлении binary и состояние загрузки вместо предупреждения, пока PDF ещё не готов.

Co-authored-by: Cursor <cursoragent@cursor.com>
fix(cooptypes): увеличить прочерк номера счёта в доверенности КУ
Typecheck / desktop (pull_request) Failing after 10m33s
Typecheck / controller (pull_request) Successful in 28m18s
987600dd38
Поле для ручного вписывания банковского счёта в шаблоне 329 расширено в 3 раза.

Co-authored-by: Cursor <cursoragent@cursor.com>
fix(registration): закрывать signup до готовности vars и сериализовать их patch
Typecheck / desktop (pull_request) Successful in 32m7s
Typecheck / controller (pull_request) Successful in 29m46s
de10e16bae
Добавляем расчётный флаг settings.is_registration_open в getSystemInfo и заглушку на странице signup, чтобы пайщик не попадал в ошибку генерации документов. Параллельные утверждения решений больше не затирают vars при read-modify-write.

Co-authored-by: Cursor <cursoragent@cursor.com>
update
Typecheck / desktop (pull_request) Successful in 29m32s
Typecheck / controller (pull_request) Successful in 29m56s
8a59d3e83e
Сохранение оферт расширений в program_agreements при регистрации; переименование паевой модели поставщика; снятие запрета выдачи самому себе; корректное отображение списания с паевого в гейте подписи; правки UI таблиц и сборки notifications.

Co-authored-by: Cursor <cursoragent@cursor.com>
Убраны технические детали (имена резолверов/сервисов, пути к файлам,
GraphQL-примеры, слои архитектуры) — описание процесса выдачи и
возможностей расширения теперь на языке пользователя, а не разработчика.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Фаза A: живой каталог marketplace через realtime (broadcast-канал + диспетчер).
Склад кооператива в MVP: перепредложение остатка (stockorder) и докладка у стойки.
Экономика КУ: членский взнос, распределение между доверенными, материальная помощь (598-43); Раунд 5 — приоритет общего кошелька, accrue/distribute/spend, реестр плановых расходов (598-50).
Карта уведомлений Стола заказов — закрыть пробелы поставщику (новый заказ + приём гарантийного возврата).
Реестр процессов: агрегация документов/операций/проводок + связь реестров.
Реестр заказов админа + докладка со склада в окне выдачи + фикс паевого на выдаче (598-50).
[598-51] Онбординг поставщика + реестр поставщиков (whitelist → registry).
Подтягивает core-платформенные улучшения из dev в базовую ветку Стола
заказов: полное удаление Novu (замена на Центр уведомлений v3, marketplace-
уведомления перенесены на новый NotificationSenderService), двухфазный учёт
регистрационного взноса через счёт 76, атомарный patchConfig (config || patch
вместо read-modify-write), шасси расходов (контракт expense) рядом с реестром
плановых расходов КУ, self-service выход пайщика (exitcoop), новый экшен
soviet::declinedec, реестр возвратных кошельков при выходе (LEDGER2_EXIT_
REFUND_WALLETS), UI-улучшения (DataRow-канон в PaymentDetails/ListOfPayments,
generic overlay registry в App.vue), убрана legacy-модель cooplace (marketplace2
уже унифицировал её в marketplace).

Конфликты (43 файла) разобраны по одному, не автоматически: чистые дополнения
(новые кошельки/операции/actions/barrel-экспорты) объединены; там, где обе
стороны чинили один и тот же баг разными способами (lost-update patchConfig,
идемпотентность action.save) — взята более полная реализация dev; там, где
marketplace2 содержал специфичный fix гонки (newsubmitted/newresolved в
signed-document-ingestion) — сохранён явно, не заменён более простым dev-
рефакторингом. cooptypes/inter/notifications/sdk пересобраны после мерджа
контрактных TS-зеркал.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
marketplace-notification.service.ts (перевод на NotificationSenderService) не
попал в предыдущий merge-коммит — там осталась ссылка на уже удалённый
novu-workflow.port, сборка бы не прошла. Плюс: откат ошибочного возврата
legacy 'cooplace' в sdk mutations/selectors (marketplace2 уже унифицировал его
в 'marketplace', PR #598-3), и актуальная генерация zeus/pnpm-lock после
generate-schema → generate-client → sdk build.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Самоорганизация кооперативных участков: универсальный L1-механизм
«собрание → решение» на контракте branch (createdec/joindec/startdec/votedec/
closedec), L2-расширение — учреждение участка через совет с пакетной подписью
петиции+liability+authority (exec/confirmdec/declinedec/canceldec),
приватность участков и белый список (setprivate/addwhite/delwhite),
доверенные лица по заявлению с двусторонним одобрением (reqtrusted/
apprtrusted/apprliab/apprauth + declined-варианты). Backend-расширение `ku`
(резолверы/синкеры/репозитории под decisions/decisionQuestions/trustReqs),
десктоп-страницы КУ (собрания, доверенные, приватность), уведомления.

Конфликты (18 файлов) — реальная коллизия с уже смерженной экономикой КУ
в branch.hpp/cpp/lib/domain (оба контракта добавляют новые actions/tables в
общий файл, без пересечения имён — объединены). В cooptypes/interfaces/
branch.ts убраны дубли type-алиасов (IChecksum256 и т.д.) и IDocument2 —
dev уже добавил их в шапку файла для своих интерфейсов, второе объявление
было бы ошибкой компиляции. В select-branch/index.ts сохранена более полная
версия marketplace2 (branchesLoading, повторная загрузка по isVisible) с
добавлением фильтра приватности участков от ku-selforg. useDismissibleBanner.ts
(add/add) — оставлена канон-версия на Quasar LocalStorage вместо сырого
localStorage. cooptypes/inter/notifications/sdk пересобраны, generate-schema
(99 резолверов, +1 от расширения ku) → generate-client → sdk build зелёные.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
fix(merge): застейджить регенерированный zeus после generate-client
Typecheck / desktop (pull_request) Failing after 9m56s
Typecheck / controller (pull_request) Failing after 9m3s
4f58871322
Тот же промах, что и в предыдущем merge-коммите: zeus/{const,index}.ts
регенерировались после первичного git add плейсхолдера и не попали в коммит.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
claude added 1 commit 2026-07-20 19:08:16 +00:00
claude added 1 commit 2026-07-20 19:27:18 +00:00
ant added 4 commits 2026-07-21 08:53:47 +00:00
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reviewed-on: #156
Reviewed-by: Алексей Муравьев <chairman.voskhod@gmail.com>
claude added 2 commits 2026-07-21 08:54:36 +00:00
Merge branch 'marketplace2' of ssh://git.coopenomics.world:222/C9S/mono into marketplace2
Typecheck / desktop (pull_request) Successful in 17m6s
Typecheck / controller (pull_request) Successful in 14m20s
6788699e45
claude added 1 commit 2026-07-21 14:16:43 +00:00
claude added 1 commit 2026-07-21 14:36:41 +00:00
claude added 1 commit 2026-07-21 15:00:21 +00:00
claude added 1 commit 2026-07-21 15:05:40 +00:00
claude added 1 commit 2026-07-21 15:56:06 +00:00
Some checks are pending
Typecheck / desktop (pull_request) Successful in 14m42s
Typecheck / controller (pull_request) Successful in 13m50s
This pull request can be merged automatically.
This branch is out-of-date with the base branch
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin marketplace2:marketplace2
git checkout marketplace2
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: C9S/mono#155