fix(marketplace): ownership :own-KU учитывает trustees председателя КУ #16

Merged
ant merged 2 commits from chore/trustees-ownership into marketplace2 2026-05-21 08:16:54 +00:00
Owner

Что и зачем

По комментариям ревью к PR #10 (E9 Sklad) и PR #15 (E6 Issuance): право действовать от имени кооперативного участка имеет не только председатель (branch.trustee), но и его доверенные лица (branch.trusted[]), до 3 individual-аккаунтов; конфигурится через branch::addtrusted / deltrusted.

Сейчас на marketplace2:

  • marketplaceListIssuancesByBraname — ownership-проверки нет.
  • marketplaceListInventory — ownership-проверки нет.
  • marketplaceListReturnClaimsByBraname — ownership-проверки нет.
  • marketplaceReturnClaim (single) — ownership-проверки нет, любой пайщик может прочитать чужое заявление.
  • 4 mutations гарантийного возврата (approveReturnVisit / rejectReturnRemote / acceptReturnAtVisit / rejectReturnAtVisit) — data.braname принимается «как есть».
  • marketplaceListAplReceptionsByBraname — member помечен подчёркиванием, ownership-проверки нет.

После merge: chairman и его trustees могут оперировать; чужие — нет.

Как сделано

  • Новый helper MarketplaceBranchOwnershipService:
    • canActAsBraname(coopname, account, braname) → boolean (non-throwing).
    • assertCanActAsBraname(coopname, account, braname) → throws ForbiddenException либо NotFoundException.
  • Источник правды — таблица branches контракта branch через BRANCH_BLOCKCHAIN_PORT (@Global() BlockchainModule).
  • Регистрируется в MarketplaceExtensionApplicationModule, инжектится в 4 резолвера.
  • marketplaceListInventory дополнительно получает role-fork: admin / board_readonly → весь склад; operator → обязан указать braname + ownership-check.

Что НЕ в scope

  1. Mutation marketplaceLabelInventory / marketplaceLabelShipmentInventory — operator должен иметь право label'ить inventory того order'а, чей delivery_braname ему подчиняется. Это проверка в MarketplaceInventoryLabelService (service-уровень), отдельный фикс.
  2. Кеш branch-lookup'ов с TTL — сейчас каждый запрос дёргает chain RPC. На MVP-нагрузке некритично.
  3. ADR-011 «read-path только из PG» — branches не реплицируются в Postgres (BranchSyncService нет), поэтому helper читает через port. Альтернатива — поднять branch syncer; Phase 2.

Совместимость с открытыми PR

  • PR #15 (re-review E6 issuance ownership) — superseded, тот же фикс в составе этого PR + trustees.
  • PR #10 (review E9 sklad) — marketplaceListInventory role-fork повторно реализован в этом PR с trustee-проверкой.

План проверки

  • Backend поднялся без DI-ошибок (MarketplaceBranchOwnershipService resolved).
  • marketplaceListInventory admin/board — отдаёт весь склад (без braname). operator с своим braname — отдаёт; operator с чужим — ForbiddenException.
  • marketplaceListIssuancesByBraname от chairman'а — OK; от trusted-аккаунта (после branch::addtrusted) — OK; от чужого operator'а — Forbidden.
  • marketplaceReturnClaim от orderer'а — OK; от chairman'а delivery_braname — OK; от чужого пайщика — Forbidden.
  • marketplaceApproveReturnVisit с чужим data.braname — Forbidden.
## Что и зачем По комментариям ревью к PR #10 (E9 Sklad) и PR #15 (E6 Issuance): право действовать от имени кооперативного участка имеет не только председатель (branch.trustee), но и его доверенные лица (branch.trusted[]), до 3 individual-аккаунтов; конфигурится через branch::addtrusted / deltrusted. Сейчас на marketplace2: * marketplaceListIssuancesByBraname — ownership-проверки нет. * marketplaceListInventory — ownership-проверки нет. * marketplaceListReturnClaimsByBraname — ownership-проверки нет. * marketplaceReturnClaim (single) — ownership-проверки нет, любой пайщик может прочитать чужое заявление. * 4 mutations гарантийного возврата (approveReturnVisit / rejectReturnRemote / acceptReturnAtVisit / rejectReturnAtVisit) — data.braname принимается «как есть». * marketplaceListAplReceptionsByBraname — member помечен подчёркиванием, ownership-проверки нет. После merge: chairman _и_ его trustees могут оперировать; чужие — нет. ## Как сделано * Новый helper MarketplaceBranchOwnershipService: * canActAsBraname(coopname, account, braname) → boolean (non-throwing). * assertCanActAsBraname(coopname, account, braname) → throws ForbiddenException либо NotFoundException. * Источник правды — таблица branches контракта branch через BRANCH_BLOCKCHAIN_PORT (@Global() BlockchainModule). * Регистрируется в MarketplaceExtensionApplicationModule, инжектится в 4 резолвера. * marketplaceListInventory дополнительно получает role-fork: admin / board_readonly → весь склад; operator → обязан указать braname + ownership-check. ## Что НЕ в scope 1. Mutation marketplaceLabelInventory / marketplaceLabelShipmentInventory — operator должен иметь право label'ить inventory того order'а, чей delivery_braname ему подчиняется. Это проверка в MarketplaceInventoryLabelService (service-уровень), отдельный фикс. 2. Кеш branch-lookup'ов с TTL — сейчас каждый запрос дёргает chain RPC. На MVP-нагрузке некритично. 3. ADR-011 «read-path только из PG» — branches не реплицируются в Postgres (BranchSyncService нет), поэтому helper читает через port. Альтернатива — поднять branch syncer; Phase 2. ## Совместимость с открытыми PR * PR #15 (re-review E6 issuance ownership) — superseded, тот же фикс в составе этого PR + trustees. * PR #10 (review E9 sklad) — marketplaceListInventory role-fork повторно реализован в этом PR с trustee-проверкой. ## План проверки * Backend поднялся без DI-ошибок (MarketplaceBranchOwnershipService resolved). * marketplaceListInventory admin/board — отдаёт весь склад (без braname). operator с своим braname — отдаёт; operator с чужим — ForbiddenException. * marketplaceListIssuancesByBraname от chairman'а — OK; от trusted-аккаунта (после branch::addtrusted) — OK; от чужого operator'а — Forbidden. * marketplaceReturnClaim от orderer'а — OK; от chairman'а delivery_braname — OK; от чужого пайщика — Forbidden. * marketplaceApproveReturnVisit с чужим data.braname — Forbidden.
ant added 1 commit 2026-05-20 17:22:02 +00:00
Резолверы marketplace с capability :own-KU теперь принимают и доверенных
лиц председателя кооперативного участка (branch.trusted[]), а не только
самого председателя (branch.trustee). По комментариям ревью к PR #10 / #15.

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

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

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

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
claude added 1 commit 2026-05-21 08:15:41 +00:00
ant merged commit a0daf255e2 into marketplace2 2026-05-21 08:16:54 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: C9S/mono#16