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.
This commit is contained in:
@@ -54,6 +54,7 @@ It is a platform runtime that must support unlimited tenants from one Angular ap
|
||||
- [Seller-Management-Diagrams.md](Seller-Management-Diagrams.md) — hierarchy, bootstrap gate, type diagram
|
||||
- [Seller-Management-Domain-Models.md](Seller-Management-Domain-Models.md) — typed models, optional sellerId fields
|
||||
- [Seller-Management-UX-Review.md](Seller-Management-UX-Review.md) — UX/accessibility review of the Phase 1 UI
|
||||
- [Seller-Management-Backoffice-Readiness-Audit.md](Seller-Management-Backoffice-Readiness-Audit.md) — per-module scoping/permissions readiness audit
|
||||
|
||||
### Engineering Rule Documents
|
||||
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user