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

320 lines
16 KiB
Markdown

# 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.