* 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>
* [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>
* Эпик 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-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>
* [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-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-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-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>
* [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>
* 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-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>
[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>
* 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>
* 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>
- Подключить 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>
- 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>
- 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>
- 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>
В §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>
После мержа 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>
Учётная модель 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>
Добавлены кооперативные стандарты для процессов контракта 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>
Цель — убрать дублирующее имя '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 там никогда не было)
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
Поправки на 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.
Перенесено с 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
Сборка пока не проверена — следующий шаг.
2026-04-25 11:10:29 +00:00
1127 changed files with 92978 additions and 16980 deletions
inlineconstexpreosio::nameREQUEST_WITHDRAW="o.wal.wthreq"_n;///< Запрос на возврат паевого: BLOCK на SHARE_FUND_PAY (без Dr/Cr).
inlineconstexpreosio::nameDECLINE_WITHDRAW="o.wal.wthdec"_n;///< Отклонение запроса на возврат: UNBLOCK на SHARE_FUND_PAY (без Dr/Cr).
inlineconstexpreosio::nameCONVERT_TO_MEMBER="o.wal.conv"_n;///< Конвертация цифрового рубля в универсальный членский кошелёк (Dr 80 / Cr 86, TRANSFER SHARE_FUND_PAY → CK_MEMBER). Conditional-шаг серии createorder в «Столе заказов».
}
// capital
@@ -82,10 +83,26 @@ namespace operations {
inlineconstexpreosio::nameCONVERT_TO_BLAGO="o.cap.cnvbl"_n;///< Конвертация сегмента: РИД → ЦПП «Благорост» (TRANSFER GENERATOR_FUND → BLAGOROST_FUND, без Dr/Cr — бухпроводка уже была сделана в ACCEPT_RID).
}
// marketplace
// marketplace — членская модель «Стола заказов» (refactor 2026-05-11; старые
inlineconstexpreosio::nameCONFIRM_RECEIPT="o.mkt.recv"_n;///< Подтверждение получения (Dr 80 / Cr 51, TRANSFER SHARE_FUND_PAY → SUPPLIER_PAYMENTS).
inlineconstexpreosio::nameASSIGN_TO_PROGRAM="o.mkt.assign"_n;///< Целевое назначение членского взноса пайщика в программу Marketplace (TRANSFER CK_MEMBER → MARKETPLACE_MEMBER, без проводки — оба кошелька на счёте 86). Conditional-шаг серии createorder.
inlineconstexpreosio::nameBLOCK_FOR_ORDER="o.mkt.block"_n;///< Блокировка членского взноса заказчика под конкретный Order (BLOCK на MARKETPLACE_MEMBER, без Dr/Cr).
inlineconstexpreosio::nameUNBLOCK_ON_CANCEL="o.mkt.unblk"_n;///< Разблокировка членского взноса при отмене Order'а (UNBLOCK на MARKETPLACE_MEMBER, без Dr/Cr). Сумма остаётся на .available и может быть потрачена на следующий заказ в программе.
inlineconstexpreosio::nameRECALL_TO_UNIVERSAL="o.mkt.recall"_n;///< Вывод программного членского взноса в универсальный членский кошелёк пайщика (TRANSFER MARKETPLACE_MEMBER → CK_MEMBER, без проводки — оба кошелька на счёте 86). Явное действие пайщика, не часть авто-flow отмены.
inlineconstexpreosio::namePURCHASE_FROM_SUPPLIER="o.mkt.purch"_n;///< Приёмка имущества кооперативом по АПП приёмки от поставщика (Dr 10 / Cr 86, NONE — только бухпроводка, кошельки не двигаются; имущество — аналитика по 10). Атомарно с PAY_SUPPLIER на закрывающей подписи председателя.
inlineconstexpreosio::namePAY_SUPPLIER="o.mkt.payout"_n;///< Оплата поставщику с расчётного счёта по факту приёмки (Dr 86 / Cr 51, ISSUE ∅ → SUPPLIER_PAYMENTS). Атомарно с PURCHASE_FROM_SUPPLIER.
inlineconstexpreosio::nameCONSUME_BY_MEMBER="o.mkt.consum"_n;///< Выдача имущества пайщику по АПП выдачи (часть 1 композитной проводки через транзит 91): Dr 91 / Cr 10, REVOKE MARKETPLACE_MEMBER.blocked → 0 — выбытие имущества со склада на «прочие». Закрытие транзита — отдельной операцией CONSUME_TRANSIT_CLOSE, атомарно в той же транзакции signiss2.
inlineconstexpreosio::nameCONSUME_TRANSIT_CLOSE="o.mkt.consum2"_n;///< Выдача имущества пайщику (часть 2 композитной проводки): Dr 86 / Cr 91, NONE — закрытие транзита 91 на счёт ЦФ программы. Срабатывает после CONSUME_BY_MEMBER в той же транзакции.
inlineconstexpreosio::nameRETURN_BY_MEMBER="o.mkt.return"_n;///< Гарантийный возврат имущества пайщиком — compensating forward к CONSUME_BY_MEMBER (часть 1 композитной проводки): Dr 91 / Cr 86, ISSUE ∅ → MARKETPLACE_MEMBER — восстановление «прочих» за счёт ЦФ + восстановление .available заказчика. Реверты ledger2::revert в Столе заказов не используются.
inlineconstexpreosio::nameRETURN_TRANSIT_CLOSE="o.mkt.return2"_n;///< Гарантийный возврат (часть 2 композитной проводки): Dr 10 / Cr 91, NONE — имущество назад на склад через закрытие транзита. Срабатывает после RETURN_BY_MEMBER в той же транзакции decretvisit.
inlineconstexpreosio::nameWRITE_OFF_PERISHABLE="o.mkt.wroff"_n;///< Утилизация скоропорта со склада (часть 1 композитной проводки): Dr 91 / Cr 10, NONE — выбытие со склада на «прочие». По протоколу совета.
inlineconstexpreosio::nameWRITE_OFF_TRANSIT_CLOSE="o.mkt.wroff2"_n;///< Утилизация скоропорта (часть 2 композитной проводки): Dr 86 / Cr 91, NONE — закрытие транзита 91 на счёт ЦФ программы. Срабатывает после WRITE_OFF_PERISHABLE в той же транзакции execwroff.
}
// soviet
@@ -134,8 +151,9 @@ enum class WalletOp : uint8_t {
TRANSFER=1,///< перемещение wallet_from → wallet_to (с Dr/Cr ИЛИ без — по парам account_id)
BLOCK=2,///< available-=amount, blocked+=amount на wallet_from
UNBLOCK=3,///< blocked-=amount, available+=amount на wallet_from
BURN=4,///< изъятие amount с wallet_from, без wallet_to. Покрывает оба кейса: (a) штатное сжигание как бизнес-операция в OPERATION_REGISTRY; (b) зеркало ISSUE при `ledger2::revert` (различие — через operation_code: `o.adj.rev` для adjustment-mirror).
BURN=4,///< изъятие amount с wallet_from.available, без wallet_to. Покрывает оба кейса: (a) штатное сжигание как бизнес-операция в OPERATION_REGISTRY; (b) зеркало ISSUE при `ledger2::revert` (различие — через operation_code: `o.adj.rev` для adjustment-mirror).
NONE=5,///< только бухпроводка без перемещения средств (wallet_from = empty, wallet_to = empty, debit ≠ 0, credit ≠ 0). Покрывает кейсы внутрибалансовых проводок типа Dr 04 / Cr 08 (приём РИД в НМА), когда кошелёк уже на нужном программном фонде.
REVOKE=6,///< изъятие amount с wallet_from.blocked в пустоту, без wallet_to. Обязательная пара Dr/Cr. Отличие от BURN — списание из blocked, а не из available; применяется на целевом расходовании предварительно заблокированной суммы (например, выдача имущества пайщику по АПП выдачи). Введён 2026-05-11 под членскую модель «Стола заказов» (o.mkt.consum).
* `capital::INVEST`, `soviet::AXN_CONVERT` (process_type совпадает с
@@ -57,7 +61,9 @@ namespace processes {
// marketplace
namespacemarketplace{
inlineconstexpreosio::nameREQUEST="p.mkt.reqst"_n;///< Цикл запроса маркетплейса (o.mkt.supply + o.mkt.recv).
inlineconstexpreosio::nameSUPPLY="p.mkt.supply"_n;///< Прямая поставка-приобретение имущества (9 операций: o.wal.conv + o.mkt.assign + o.mkt.block + o.mkt.unblk + o.mkt.recall + o.mkt.purch + o.mkt.payout + o.mkt.consum + o.mkt.consum2 — последние две композитная проводка через транзит 91). Старый p.mkt.reqst (клиринговый) удалён 2026-05-11.
inlineconstexpreosio::nameRETURN="p.mkt.return"_n;///< Гарантийный возврат имущества пайщиком — compensating forward к o.mkt.consum (o.mkt.return + o.mkt.return2 — композитная проводка через транзит 91).
inlineconstexpreosio::nameWRITEOFF="p.mkt.wroff"_n;///< Утилизация скоропорта со склада КУ (o.mkt.wroff + o.mkt.wroff2 — композитная проводка через транзит 91, по протоколу совета).
staticconstexpreosio::nameGENERATOR_FUND="w.cap.gen"_n;///< Генератор — единый агрегированный кошелёк программы (USER_SHARED; ADR-009)
staticconstexpreosio::namePREIMP_FUND="w.cap.preimp"_n;///< Первичный учёт РИД-взносов до перехода на электронный учёт (USER_SHARED; o.cap.preimp / o.cap.drppre)
return"Утилизация скоропорта по решению совета № "+std::to_string(proposal_id)+", позиция "+std::to_string(item_index+1)+": списание целевого назначения";
eosio::namepayout_status=OrderPayoutStatus::NONE;///< Locked Decision L12 — состояние выплаты поставщику через gateway (см. namespace OrderPayoutStatus)
std::stringpayout_decline_reason;///< Заполняется только при payout_status == DECLINED (текст причины из gateway::outdecline)
uint64_treturn_request_id=0;///< 0 если активного гарантийного возврата нет
// Все timestamp'ы переходов состояний (createorder/accepted/received_to_coop/
// ready/received/cancelled) восстанавливаются на бэкенде из blockchain_actions[at]
// по соответствующим action'ам — нет смысла держать их в RAM-таблице.
// Единственное исключение — warranty_until (выше): нужен on-chain для
// submretrn guard `now() < warranty_until` без cross-action lookup.
Заказчик подаёт заявление на гарантийный возврат имущества:
указывает причину обращения, прикладывает фотографии товара,
ссылается на акт приёма-передачи, по которому получал имущество.
Подать заявление
можно только пока не истёк гарантийный срок, заданный
поставщиком.
pre:
- Заказ закрыт, имущество выдано заказчику.
- Гарантийный срок ещё не истёк.
- Приложены фотографии товара и указана причина обращения.
post:
- Заявление зарегистрировано и направлено председателю на рассмотрение.
- step:2
title:Удалённое рассмотрение — приглашение на очный визит
actor:chairman
action:marketplace::aprretrem
description:>
Председатель участка изучает заявление и приложенные материалы
и решает, что для разбора обращения нужен очный осмотр товара.
Заказчику предстоит прийти на участок с продукцией.
pre:
- Заявление на рассмотрении.
post:
- Очный визит одобрен.
- step:3
title:Очный осмотр — принятие возврата
actor:chairman
action:marketplace::accretrn
description:>
Заказчик приходит на участок с продукцией. Председатель очно
осматривает товар и принимает гарантийный возврат: товар
остаётся на складе участка, сумма заказа возвращается
заказчику на программный членский кошелёк Стола заказов.
pre:
- Очный визит одобрен.
- Заказчик прибыл на участок с продукцией.
post:
- Товар остаётся на складе участка.
- Сумма заказа возвращена заказчику на программный членский кошелёк.
alternatives:
- branch:Гарантийный срок истёк
at_step:1
action:null
actor:orderer
description:>
Если гарантийный срок поставщика истёк, заявление на гарантийный
возврат подать нельзя.
- branch:Удалённое рассмотрение — отказ
at_step:2
action:marketplace::rejretrem
actor:chairman
description:>
Председатель отказывает в гарантийном возврате удалённо с
указанием причины — например, обращение очевидно не подпадает
под гарантию. Решение финальное, движений по имуществу и
средствам не происходит.
- branch:Очный осмотр — отказ
at_step:3
action:marketplace::rejretrn
actor:chairman
description:>
Председатель отказывает в гарантийном возврате по результатам
очного осмотра с указанием причины — например, характер
повреждений не покрывается гарантией. Заказчик забирает товар
обратно. Решение финальное, движений по имуществу и средствам
не происходит.
# ── Секция 5. Документы и подписи ───────────────────────────────────────────
# В процессе подписывается заявление пайщика на возврат с приложениями
# (фото товара). Решение председателя — процедурное действие в системе,
# отдельным документом не оформляется в MVP (фиксируется в стейт-машине
# return_request с кем и когда принято решение). Если регулятор / устав
# потребуют документного оформления решения председателя — это будет
# отдельным шаблоном (TODO).
documents:
- action:marketplace::submretrn
title:Заявление пайщика на гарантийный возврат имущества
registry_id:800
signed_by:[orderer]
stored_in:return_requests.statement
note:"Используется существующий шаблон 800.ReturnByAssetStatement из cooptypes/cooperative/registry/. Оригинально создан под клиринговую модель donor'а («Заявление на возврат паевого взноса имуществом»); форма заявления пайщика на возврат структурно подходит и для членской модели гарантийного возврата. При необходимости методолог может создать специализированный шаблон в новой серии (1100+) — тогда registry_id обновится."
- action:marketplace::accretrn
title:Решение председателя КУ о принятии гарантийного возврата
registry_id:0
signed_by:[chairman]
stored_in:return_requests.chairman_decision
note:"TODO: в MVP решение принимается единолично председателем КУ, не советом — существующий шаблон 801.ReturnByAssetDecision не подходит (рассчитан на коллегиальное решение совета по новации). Создать специализированный шаблон в registry либо оформлять как in-system запись без отдельного документа."
# ── Секция 6. Операции (Ledger2) ────────────────────────────────────────────
# Одна ledger2-операция — compensating forward к o.mkt.consum (без
# использования ledger2::revert). Атомарно: восстановление .available
title:Формирование и внесение проекта на рассмотрение совета
actor:chairman
action:marketplace::propwroff
description:>
Кооператив по расписанию (по умолчанию раз в месяц; точную дату
согласовывает бухгалтер) опрашивает склады участков и собирает
позиции, которые невозможно выдать заказчику: просроченные и не
востребованные, повреждённые, малоценные. Председатель оформляет
проект списания со списком позиций, подписывает заявление и
вносит проект на рассмотрение совета.
pre:
- Наступил регламентный срок очередного цикла списания.
- На складах участков найдены позиции, удовлетворяющие критериям списания.
- Председатель подписал Заявление о списании скоропорта.
post:
- Проект списания сформирован и подписан председателем.
- Проект внесён на рассмотрение совета.
- step:2
title:Рассмотрение проекта советом
actor:council
action:null
description:>
Совет рассматривает заявление председателя по типовому процессу
решения совета — на очном заседании или в форме заочного
голосования — и принимает решение. При положительном решении
председатель подписывает протокол о списании. При отрицательном —
проект отклоняется.
pre:
- Проект внесён на рассмотрение совета.
post:
- Совет принял решение по проекту и подписал протокол.
- step:3
title:Исполнение списания
actor:chairman
action:marketplace::execwroff
description:>
По принятому советом протоколу кооператив последовательно
списывает каждую позицию проекта со склада участка. Имущество
безвозвратно выбывает из учёта; сумма закрывается на счёт
целевого финансирования программы через транзит счёта 91. По
кошелькам заказчиков движений нет.
pre:
- Совет принял положительное решение по проекту.
- Протокол совета о списании подписан.
post:
- Все позиции проекта списаны со складов участков.
- Имущество ушло из учёта как безвозвратные потери.
alternatives:
- branch:Совет отклонил проект
at_step:2
action:null
actor:council
description:>
Совет отклонил проект списания — например, требуется
дополнительная экспертиза — либо отложил рассмотрение. Позиции
остаются на складах участков и могут попасть в следующий цикл
списания. Движений по имуществу и средствам не происходит.
- branch:Альтернатива — переуступка имущества по сниженной цене
at_step:1
action:null
actor:chairman
description:>
Альтернативный путь для имущества, ещё пригодного к выдаче —
например, накануне истечения срока годности: кооператив сам
выступает поставщиком в новом предложении по сниженной цене и
ставит имущество на следующий цикл отсечки заявок. В MVP только
помечен; реализуется отдельным процессом в более поздней фазе.
# ── Секция 5. Документы и подписи ───────────────────────────────────────────
# Заявление председателя — собственный документ процесса (вносится советом
# на рассмотрение). Протокол совета — документ типового процесса решения
# совета (по стандарту sov.decision), к которому привязан проект списания.
documents:
- action:marketplace::propwroff
title:Заявление о списании скоропорта
registry_id:1106
signed_by:[chairman]
stored_in:writeoff_proposals.statement
note:"Подписывается председателем перед внесением проекта на рассмотрение совета. Содержит список позиций к списанию (участок, наименование, количество, сумма, причина). Источник правды — components/cooptypes/src/cooperative/registry/1106.MarketplaceWriteoffStatement"
- action:null
title:Протокол совета о списании скоропорта
registry_id:1105
signed_by:[chairman]
stored_in:writeoff_proposals.protocol
note:"Протокол по типовому процессу решения совета (стандарт sov.decision). Подписывается председателем после принятия советом положительного решения. Источник правды — components/cooptypes/src/cooperative/registry/1105.MarketplaceWriteoffProtocol"
# ── Секция 6. Операции (Ledger2) ────────────────────────────────────────────
# Композитная бухгалтерская проводка через транзит счёта 91 «Прочие доходы
# и расходы» — разбита на пару операций, срабатывающих атомарно по каждой
# позиции проекта в одной транзакции исполнения списания.
operations:
- ledger_code:o.mkt.wroff
human_name:Утилизация скоропорта — выбытие со склада
wallet_op:NONE # только бухпроводка без движения по кошелькам
@details Данный метод позволяет пользователю, который получил предложение по своей заявке, подтвердить свою готовность его принять и выполнить. При этом формируется пакет документов, который отправляется в совет на утверждение.
@param username Имя пользователя, подтверждающего готовность выполнить предложение.
@param exchange_id ID предложения, которое следует подтвердить.
@note Авторизация требуется от аккаунта: @p username
@details Позволяет пользователю отменить родительскую или дочернюю заявку, а также обеспечивает возврат токенов владельцу (если применимо). При отмене проверяется наличие заявки и её текущий статус.
@param username Имя пользователя, инициировавшего отмену.
@param exchange_id Идентификатор заявки для отмены.
@note Авторизация требуется от аккаунта: @p username
\brief Подписание акта о приёме-передаче имущества.
* @details После успешного получения товара, получатель подписывает акт о приёме-передаче, что свидетельствует о юридическом завершении сделки. Этот акт делает пакет документов по данной сделке полным. После проведения ряда проверок, обновляются статусы и количество объектов в основной заявке и предложении. Если все объекты основной заявки обработаны, заявка удаляется из публикации. В зависимости от типа предложения, может осуществляться перевод токенов.
* @param username Имя пользователя-получателя товара.
* @param exchange_id ID предложения, под которым следует подписать акт.
* @note Авторизация требуется от аккаунта: @p username
eosio::check(parent_change!=exchange.end(),"Родительская заявка не найдена");
// eosio::check(change -> username == username, "Вы не можете подтвердить исполнение заявки");
eosio::check(change->status=="recieved2"_n,"Заявка находится в неверном статусе для утверждения обмена");
eosio::check(change->warranty_delay_until.sec_since_epoch()<eosio::current_time_point().sec_since_epoch(),"Время гарантийной задержки еще не истекло");
* @details Этот метод позволяет пользователю отклонить предложение, представленное к его заявке.
* Выполняются следующие проверки:
* - Существование предложения с указанным ID.
* - Существование основной заявки.
* - Предложение находится в статусе "ожидание".
*
* Если отклонено предложение к заявке типа "order", осуществляется возврат токенов пользователю, которому были заблокированы токены при создании предложения.
*
* @param username Имя пользователя, отклоняющего предложение.
* @param exchange_id ID предложения, которое следует отклонить.
* @param meta Дополнительные метаданные, связанные с отказом.
*
* @note Авторизация требуется от аккаунта: @p username
eosio::check(change_opt.has_value(),"Заявка не найдена");
autochange=change_opt.value();
// Проверяем что заявка в подходящем статусе (после поставки или перемещения со склада)
eosio::check(change.status=="supplied2"_n||change.status=="shiprecvd"_n,"Перевод в статус delivered возможен только после поставки (supplied2) или после получения на склад (shiprecvd)");
// Обновляем заявку - просто перевод статуса
autochange_itr=requests.find(change.id);
eosio::check(change_itr!=requests.end(),"Заявка не найдена для обновления");
eosio::check(change!=exchange.end(),"Объявление не найдено");
eosio::check(change->username==username||(persona!=staff.end()&&persona->has_right(_marketplace,"unpublish"_n)),"У вас нет права на публикацию данной заявки");
eosio::check(change->status=="unpublished"_n||change->status=="prohibit"_n,"Неверный статус для публикации");
eosio::check(change_opt.has_value(),"Заявка не найдена");
autochange=change_opt.value();
eosio::check(change.status=="supplied2"_n||change.status=="shiprecvd"_n||change.status=="delivered"_n,"Перевозить можно только заявки в статусе supplied2, shiprecvd или delivered");
eosio::check(change.type=="orderoffer"_n,"Перевозить можно только заявки типа orderoffer");
eosio::check(change.warehouse==source_braname,"Заявка должна находиться на складе КУ отправителя");
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.