# 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](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 all** — `AdminOrderItem` 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 `AdminCustomersGateway` — `BACKEND.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 live** — `AdminUser` 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 change** — `BACKEND.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.