Files
marketplaces/docs/architecture/foundation/Seller-Management-Backoffice-Readiness-Audit.md
sdarbinyan f2499df6a9 docs: Backoffice readiness audit for future Seller Management
Audit only, no code changed - every fact gathered by reading current
facades/gateways/components on this branch, not assumed. Covers all
13 admin modules (Dashboard, Products, Categories, Orders, Customers,
Users, Analytics, Reviews/Moderation, Media, CMS, Builder, Settings,
Monitoring, Transactions).

Per module: answers the 3 readiness questions (does Marketplace Owner
see everything / would Seller see only their own / would Seller Staff
be limited), documents where a future scope would be injected (an
existing method/interface parameter to extend - no "if seller" checks
introduced anywhere), lists components that currently assume global
ownership, and classifies Ready / Needs scope / Needs permissions /
Needs API change.

Key findings:
- Only 3 of 13 gateways (Categories, Dashboard-metrics, Media) are
  DI-token-swappable today; everything else needs that seam added
  first, independent of seller scoping.
- Orders is the load-bearing blocker: Customers, Transactions, and
  half of Analytics all derive from its same unscoped full-fetch order
  list, and AdminOrderItem has no per-item seller attribution at all -
  the concrete gap behind Seller-Management.md's open Unified-vs-Split-
  Orders question.
- Users already carries an AdminUserScope/AdminRole concept (label-
  only today) - the natural future home for the Marketplace Owner/
  Seller/Seller Staff/Platform Admin role vocabulary.
- CMS/Static Pages and Builder/Project Editor are structurally not
  about data scoping at all (marketplace-wide content, single global
  config document respectively) - seller-level work there is new
  product surface, not an extension.
- No admin module anywhere does role-based hiding of buttons or data
  today - confirmed, not assumed.

Linked from docs/architecture/foundation/README.md alongside the
other Seller Management docs.
2026-07-26 22:22:22 +04:00

16 KiB

Seller Management — Backoffice Readiness Audit

Audit only. No code changed by this pass — every fact below was gathered by reading current source (facades, gateway interfaces, local implementations, components) on feature/seller-management-foundation, not inferred or assumed. Companion to Seller-Management.md §5 (Roles & Permissions) and §6 (Seller Ownership).

How to read this

For each module: three readiness questions, then where a future scope would be injected (not "if seller" conditionals — an injection point in an existing method signature or facade call), then components that currently assume there is exactly one owner of the whole dataset, then a classification.

Classification legend:

  • Ready — either already scope-injectable with no structural change, or conceptually marketplace-wide data that a seller scope shouldn't apply to at all.
  • Needs scope — a filters object or DI-token seam already exists; adding a scope field is additive, not structural.
  • Needs permissions — the open question is who sees this at all (Marketplace Owner vs Seller vs Seller Staff vs Platform Admin), not data filtering.
  • Needs API change — the method signature itself has no parameter to extend (bare no-arg calls), the data model has no owner attribution field to filter on, or the module's whole premise assumes one global record.

Repo-wide baseline established by this audit: only Categories, Dashboard- metrics, and Media have a DI-token-swappable gateway today (per BACKEND.md §8's pattern — interface + *LocalGateway + *ApiGateway + InjectionToken). Every other admin domain's facade injects its *LocalGateway concretely; that seam has to be added before any real scoping work lands, independent of the scoping question itself. No module currently does role-based hiding of any button or data — Users displays role/permission labels only, nothing gates on them.

Module-by-module

Dashboard

  • Owner sees everything? Yes — today's only mode.
  • Seller sees only their own? Not possible today — loadMetrics() has no parameters at all.
  • Seller Staff limited? Undetermined — no permission model touches this yet.
  • Injection point: AdminDashboardMetricsGateway.loadMetrics() would need a new parameter (interface change, not a filters-object field — there is no object to extend).
  • Assumes global ownership: admin-dashboard-metrics.local.gateway.ts computes categoriesCount/productsCount as raw .length over the entire catalog.
  • Classification: Needs API change.

Products

  • Owner sees everything? Yes.
  • Seller sees only their own? Not yet, but the shape is close — filters object already exists.
  • Seller Staff limited? Undetermined.
  • Injection point: AdminProductListFilters (already has search/categoryId/visibility/stock/includeArchived/sort/page/pageSize) — an optional sellerId field slots in next to the existing ones; one more .filter() line in AdminProductsLocalGateway.
  • Assumes global ownership: loadDashboardStats() calls loadProducts({page:1, pageSize:100000, includeArchived:true, ...}) to sum stock/health stats with no per-seller split.
  • Classification: Needs scope (small, once the DI-token seam this module still lacks is added — see baseline note above).

Categories

  • Owner sees everything? Yes.
  • Seller sees only their own? Categories are conceptually a shared, marketplace-wide taxonomy — the real future question is "which products in category X belong to seller Y," not "which categories belong to seller Y." Likely Owner-only editable regardless of seller scope.
  • Seller Staff limited? Undetermined.
  • Injection point: AdminCategoryListFilters already has search/visibility/includeDeleted and the gateway is already DI-token- swappable (ADMIN_CATEGORIES_GATEWAY) — but the facade's loadList() currently calls the gateway with a hardcoded {search:'', visibility:'all', includeDeleted:true} and does all real filtering client-side in filteredCategories()/visibleTreeRows() to preserve tree parent/child chains. A sellerId filter passed to the gateway would be silently bypassed unless this hardcoded call is updated too.
  • Assumes global ownership: loadDashboardStats() sums the whole category tree.
  • Classification: Needs scope — gateway/interface layer is Ready, the facade's full-fetch-then-client-filter pattern is the actual gap.

Orders

  • Owner sees everything? Yes.
  • Seller sees only their own? Not modeled at allAdminOrderItem has no seller/vendor attribution field; a multi-vendor order (one order, items from several sellers) has no representation today. This is the concrete blocker behind the Unified-vs-Split-Orders open question in Seller-Management.md §6.
  • Seller Staff limited? Undetermined.
  • Injection point: AdminOrderListFilters has search/status/page/ pageSize — a sellerId field is syntactically cheap to add, but filtering by it means nothing until orders/order-items carry seller attribution in the data model.
  • Assumes global ownership: loadDashboardStats() fetches pageSize:100000 and sums orders/revenue/customers globally; this same full-fetch feeds Customers, Transactions, and Analytics (see below), compounding the single-owner assumption across four modules.
  • Classification: Needs API change — the data-model gap (per-item seller attribution, unified-vs-split decision) is the real blocker, not the filter object.

Customers

  • Owner sees everything? Yes.
  • Seller sees only their own? Not possible today — "customer" is a derived aggregate grouping the full order list by email in-memory; there is no first-class Customers gateway to add a filter to at all.
  • Seller Staff limited? Undetermined.
  • Injection point: none exists yet. Either (a) derive from an already-scoped Orders call once Orders itself supports sellerId, or (b) introduce a first-class AdminCustomersGatewayBACKEND.md already recommends the latter independent of Seller Management.
  • Assumes global ownership: buildCustomers() groups the entire unscoped order list; hardcoded pageSize:100000 fetch, "search" is client-side only.
  • Classification: Needs API change.

Users

  • Owner sees everything? Yes.
  • Seller sees only their own? Not modeled — loadUsers() is bare no-arg, no filters object exists to extend.
  • Seller Staff limited? This is where the answer will actually liveAdminUser already carries an AdminUserScope field ('marketplace' | 'office' in seed data) and an AdminRole with a permission-array shape. This is the closest existing hook to the future SellerPermissionRole vocabulary (marketplaceOwner/seller/sellerStaff/platformAdmin, Seller-Management.md §5) — but today it's used for labels only (roleLabel/permissionLabel in the page component), nothing gates actions or visibility on it anywhere in the app.
  • Injection point: loadUsers() needs a parameter added (interface change — no object to extend); AdminUserScope is the natural place a seller-scope value would eventually live.
  • Assumes global ownership: loadAll() fetches every user unconditionally, no per-seller user set exists.
  • Classification: Needs API change (data fetch) + Needs permissions (this module is the eventual home of the Marketplace Owner / Seller / Seller Staff / Platform Admin distinction — right now it's purely informational).

Analytics

  • Owner sees everything? Yes.
  • Seller sees only their own? Not possible today — every number (revenue, top products, health, recommendations) is summed across the entire orders+products+categories+reviews corpus with no per-owner dimension anywhere, and there's no gateway of its own to add a filter to (it composes five other gateways/facades directly).
  • Seller Staff limited? Undetermined.
  • Injection point: none today — depends entirely on Orders/Products/ Categories/Moderation each supporting sellerId first, then every one of this facade's ~6 nested subscribe calls would need the field passed through, plus every aggregate builder (buildSeries, buildTopProducts, buildCustomerAnalytics) reworked to partition by seller instead of summing globally.
  • Assumes global ownership: the strongest case in the audit — literally every displayed number.
  • Classification: Needs API changeBACKEND.md already independently flags this as the last domain to get a real backend; Seller Management scoping compounds on top of that, not ahead of it.

Reviews / Moderation

  • Owner sees everything? Yes.
  • Seller sees only their own? Reviews carry productId/productName — a seller would see reviews on their own products, which means joining through product ownership (once Products has sellerId), not a direct seller field on the review itself. Reports (loadReports()) has no filters object at all today, separate code path from reviews.
  • Seller Staff limited? Undetermined.
  • Injection point: AdminReviewListFilters (search/status/rating/page/pageSize) — cheap field add, correctness depends on a product-ownership join. loadReports() needs a signature change first (no params exist).
  • Assumes global ownership: loadDashboardStats() sums the full review queue with pageSize:100000, no per-product/per-seller split.
  • Classification: Needs scope (reviews, small, blocked on Products) + Needs API change (reports, no params today).

Media

  • Owner sees everything? Yes.
  • Seller sees only their own? Not modeled — MediaAsset has no uploader/owner field at all (filename/mimeType/size/tags/folder only); "folder" is the closest existing scoping primitive, used generically today.
  • Seller Staff limited? Undetermined.
  • Injection point: MediaListParams (page/pageSize/search/folder/ kind/sort) is already a rich optional-params object — a sellerId field is a cheap addition, and the repository is already DI-token-bound (MediaRepository abstract class, mock vs API swap already established). MediaAsset itself needs an owner field added for the filter to mean anything.
  • Assumes global ownership: none beyond the missing owner field itself.
  • Classification: Needs scope (small — best-positioned module in the audit alongside Categories).

CMS / Static Pages

  • Owner sees everything? Yes — and likely always will.
  • Seller sees only their own? Conceptually doesn't apply. Static pages (legal, about, etc.) are inherently marketplace-wide; there is no per-seller "static page" concept in the domain, and no fetch method exists to add a filter to in the first place — this module reads/writes BootstrapConfig.staticPages in-memory, no gateway, no HTTP call (confirmed by BACKEND.md: "has no backend call today").
  • Seller Staff limited? Not applicable.
  • Injection point: none — would require inventing an entirely new data source, not adding a filter to an existing one.
  • Assumes global ownership: the whole module's premise, but appropriately so — this is marketplace-wide content by nature.
  • Classification: Ready — no scoping work belongs here; flag if product strategy later decides sellers need their own static pages, which is a new capability, not a gap in this one.

Builder / Project Editor

  • Owner sees everything? Yes — the entire module edits one global BootstrapConfig document.
  • Seller sees only their own? Not modeled, and not a filter question at all — there is no load*/list* gateway method anywhere in this module to extend; save()/publish()/resetDraft() all operate on the single in-memory config object, with no backend write path yet either (BACKEND.md: "no client write call exists today").
  • Seller Staff limited? Not applicable at this structural level.
  • Injection point: none exists. A seller-scoped builder (per-seller storefront layout/branding) would be a different product concept, not an extension of this module's current single-document model.
  • Assumes global ownership: total and structural — the single strongest one-owner assumption in the codebase.
  • Classification: Needs API change — biggest structural gap in the audit if seller-level storefront customization is ever wanted; this is new work, not scoping.

Settings

No route exists — admin-nav.model.ts marks it comingSoon: true, no component backs it. Skipped, nothing to audit.

Monitoring

  • Owner sees everything? Yes.
  • Seller sees only their own? Likely shouldn't — events/queues/webhooks (logins, API health, queue depth) are platform-operational data. A seller has no legitimate reason to see other users' login events or system queue depth regardless of any future scoping.
  • Seller Staff limited? Same reasoning — this looks like a Marketplace Owner / Platform Admin-only surface once roles exist, not something a Seller role should reach at all.
  • Injection point: AdminMonitoringEventFilters (category/search) exists for events; loadQueues()/loadWebhooks() are bare no-arg.
  • Assumes global ownership: appropriately so — this is genuinely system-wide data.
  • Classification: Needs permissions — the open question is route-level visibility per role, not data filtering.

Transactions

  • Owner sees everything? Yes.
  • Seller sees only their own? Not modeled — transactions are derived 1:1 from the same unscoped global order list as Customers, inheriting the same seller-attribution gap.
  • Seller Staff limited? Undetermined.
  • Injection point: AdminTransactionListFilters (search/status/type/page/pageSize) exists — cheap field syntactically, but real correctness is blocked on Orders resolving per-item seller attribution first.
  • Assumes global ownership: derives wholesale from AdminOrdersLocalGateway, same pattern as Customers.
  • Classification: Needs scope (small syntactically, blocked on Orders' Needs API change classification for real correctness).

Summary table

Module Classification
Dashboard Needs API change
Products Needs scope
Categories Needs scope
Orders Needs API change
Customers Needs API change
Users Needs API change + Needs permissions
Analytics Needs API change
Reviews / Moderation Needs scope (reviews) + Needs API change (reports)
Media Needs scope
CMS / Static Pages Ready (not applicable)
Builder / Project Editor Needs API change
Settings N/A — no route
Monitoring Needs permissions
Transactions Needs scope (blocked on Orders)

Nothing in this repo is currently classified "Ready" for actual seller data scoping — CMS/Static Pages is "Ready" only in the sense that it correctly needs no scoping at all. Categories and Media are the best-positioned modules for a future scope field (filters object + DI seam either fully or mostly in place already). Orders is the load-bearing blocker — Customers, Transactions, and half of Analytics all derive from it, so its data-model gap (no per-item seller attribution, unified-vs-split undecided) should be resolved before scoping any of its three dependents.

Cross-cutting findings

  • No admin module anywhere does role-based hiding of buttons or data today. Users is the only module with a role concept in its data (AdminRole, permission arrays) and even there it's label-only.
  • Only 3 of 13 audited gateways are DI-token-swappable today (Categories, Dashboard-metrics, Media) — everything else needs that seam added before any scoping work, independent of Seller Management.
  • The heaviest single dependency chain: Orders → Customers, Transactions, and Analytics all derive from the same unscoped, full-fetch order list. Fixing Orders' data model is the one change with the largest downstream effect.
  • Two modules are structurally not about data scoping at all: CMS/ Static Pages (marketplace-wide by nature) and Builder/Project Editor (single global document, no query surface) — seller-level work here would be new product surface, not an extension.
  • No "if seller" conditional exists anywhere in the codebase — this audit deliberately did not introduce any. Every injection point above is described as a parameter/field addition to an existing method or interface, never a runtime branch.