feat(sellers): typed domain models for future Seller Management - no logic, no API, no auth changes
Typed models only, per mission. Nothing outside the new files reads
or writes any of this yet.
New core/sellers/models/ (mirrors core/products/models,
core/auth/models convention):
- MarketplaceRef - minimal {id,slug,name} reference from a seller
back to its marketplace, distinct from bootstrap's TenantConfig.
- SellerStatus - 'pending'|'active'|'suspended'|'disabled', no
transition logic.
- SellerScope - {sellerId, marketplaceId}, domain-level counterpart
to BootstrapConfig.seller (SellerConfig from the ADR-011 pass).
- SellerBranding (+SellerContact/SellerAddress/SellerThemeOverrides)
- logo/banner/description/contacts/address/theme overrides, every
field optional. Marketplace branding/theme remain default;
nothing consumes this yet.
- SellerPermissionRole/SellerPermissions - marketplaceOwner/seller/
sellerStaff/platformAdmin. Separate vocabulary from the existing
AdminRole (core/auth/models/permission.model.ts) - not merged, not
wired into any guard, zero auth behavior change.
- Seller - the eventual entity, composed from the above.
Changed (optional-only, verified backward compatible):
- Item (models/item.model.ts) gained sellerId?: string
- AdminProduct (features/admin/products/models/) gained
sellerId?: string
- AdminOrder (features/admin/orders/models/) gained sellerId?: string
Absent means marketplace-owned in every case, exactly like every
existing product/order today. No consumer of any of these three
models needed updating. AdminOrderItem (per-line-item ownership) and
the existing PermissionsConfig/AdminRole system were deliberately not
touched - out of scope for this pass.
Added docs/architecture/foundation/Seller-Management-Domain-Models.md
documenting every new type, every changed field, and the explicit
non-goals list. Linked from the foundation README alongside ADR-011
and the diagrams doc.
tsc --noEmit clean, arch:check (boundaries + cycles) clean.
This commit is contained in:
@@ -47,6 +47,12 @@ It is a platform runtime that must support unlimited tenants from one Angular ap
|
|||||||
- [ADR-010](adr/ADR-010-backward-compatibility-for-auth-payment-authorization.md)
|
- [ADR-010](adr/ADR-010-backward-compatibility-for-auth-payment-authorization.md)
|
||||||
- [ADR-011](adr/ADR-011-optional-seller-management-module.md)
|
- [ADR-011](adr/ADR-011-optional-seller-management-module.md)
|
||||||
|
|
||||||
|
### Seller Management (optional, in preparation — not built)
|
||||||
|
|
||||||
|
- [ADR-011](adr/ADR-011-optional-seller-management-module.md) — decision record
|
||||||
|
- [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
|
||||||
|
|
||||||
### Engineering Rule Documents
|
### Engineering Rule Documents
|
||||||
|
|
||||||
- [Folder Blueprint](Folder-Blueprint.md)
|
- [Folder Blueprint](Folder-Blueprint.md)
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# Seller Management — Domain Models (Preparation)
|
||||||
|
|
||||||
|
Companion to [ADR-011](adr/ADR-011-optional-seller-management-module.md) and
|
||||||
|
[Seller-Management-Diagrams.md](Seller-Management-Diagrams.md). This document
|
||||||
|
covers the second preparation pass: typed domain models for a future Seller
|
||||||
|
entity, and optional seller-ownership fields on existing Product/Order
|
||||||
|
models. **Typed models only — no repository, gateway, facade, CRUD, API, or
|
||||||
|
authentication/authorization change exists as a result of this work.**
|
||||||
|
|
||||||
|
## New: `core/sellers/models/`
|
||||||
|
|
||||||
|
A new domain model group, mirroring the existing `core/products/models/`,
|
||||||
|
`core/auth/models/` convention. Nothing outside this directory imports from
|
||||||
|
it yet — these types exist for future work to build against.
|
||||||
|
|
||||||
|
| Type | File | Purpose |
|
||||||
|
|---|---|---|
|
||||||
|
| `MarketplaceRef` | `marketplace-ref.model.ts` | Minimal `{id, slug, name}` reference from a seller record back to its owning marketplace. Not a replacement for `TenantConfig` (bootstrap's runtime tenant contract, ADR-001) — just enough to say which marketplace a seller belongs to. |
|
||||||
|
| `SellerStatus` | `seller-status.model.ts` | Lifecycle vocabulary: `'pending' \| 'active' \| 'suspended' \| 'disabled'`. No transition logic. |
|
||||||
|
| `SellerScope` | `seller-scope.model.ts` | `{sellerId, marketplaceId}` — the domain-level counterpart to `BootstrapConfig.seller` (`SellerConfig`). Backend-resolved only, same rule as tenant resolution (ADR-001, ADR-011). |
|
||||||
|
| `SellerBranding` (+ `SellerContact`, `SellerAddress`, `SellerThemeOverrides`) | `seller-branding.model.ts` | Logo, banner, description, contacts, address, theme overrides — **all fields optional**. Absent means marketplace branding/theme applies, unchanged (`BrandingConfig`/`ThemeConfig`). Nothing consumes this yet. |
|
||||||
|
| `SellerPermissionRole`, `SellerPermissions` | `seller-permissions.model.ts` | Four future roles: `marketplaceOwner`, `seller`, `sellerStaff`, `platformAdmin`. **A separate vocabulary from the existing `AdminRole`** (Owner/Manager/Support/ReadOnly, `core/auth/models/permission.model.ts`) — not merged, not wired into any guard, no auth behavior change. |
|
||||||
|
| `Seller` | `seller.model.ts` | The eventual entity: `id`, `marketplace: MarketplaceRef`, `name`, `slug`, `status: SellerStatus`, optional `branding: SellerBranding`, `createdAt`/`updatedAt`. |
|
||||||
|
|
||||||
|
All exported via `core/sellers/models/index.ts`.
|
||||||
|
|
||||||
|
## Changed: optional seller ownership on existing entities
|
||||||
|
|
||||||
|
Three existing entities gained one new **optional** field each. In every
|
||||||
|
case: absent = marketplace-owned (today's only reality for every existing
|
||||||
|
product/order), nothing reads the field yet, no consumer needed updating,
|
||||||
|
`tsc`/`arch:check` both verified clean after the change.
|
||||||
|
|
||||||
|
| Entity | File | Field added |
|
||||||
|
|---|---|---|
|
||||||
|
| `Item` (storefront product) | `models/item.model.ts` | `sellerId?: string` |
|
||||||
|
| `AdminProduct` (admin product editor) | `features/admin/products/models/admin-product.model.ts` | `sellerId?: string` |
|
||||||
|
| `AdminOrder` (admin order editor) | `features/admin/orders/models/admin-order.model.ts` | `sellerId?: string` |
|
||||||
|
|
||||||
|
Deliberately **not** touched: `AdminOrderItem` (per-line-item seller
|
||||||
|
ownership is a finer-grained decision than this preparation pass covers —
|
||||||
|
order-level `sellerId` is enough for now), and both existing bootstrap
|
||||||
|
`PermissionsConfig`/`AdminRole` (no authentication change, per mission).
|
||||||
|
|
||||||
|
## Non-goals (explicitly out of scope)
|
||||||
|
|
||||||
|
- No repository, gateway, facade, or API call reads or writes `sellerId`,
|
||||||
|
`Seller`, or any type in this document.
|
||||||
|
- No route, guard, or UI surfaces any of this.
|
||||||
|
- No change to `AdminRole`, `ROLE_PERMISSIONS`, or any existing
|
||||||
|
authentication/authorization code path.
|
||||||
|
- No change to marketplace branding/theme defaults or precedence — a
|
||||||
|
marketplace with no sellers, or a seller with no branding overrides,
|
||||||
|
behaves exactly as today.
|
||||||
|
|
||||||
|
## What this unblocks later
|
||||||
|
|
||||||
|
Once Seller Management is actually implemented (its own ADR/implementation
|
||||||
|
pass, per ADR-011 §"Scope of this ADR"): a `SellerRepository`/`SellerGateway`
|
||||||
|
can return `Seller` objects instead of inventing a shape; product/order
|
||||||
|
CRUD can start populating `sellerId` without a breaking schema change;
|
||||||
|
permission guards can consume `SellerPermissionRole` once a real role system
|
||||||
|
decision is made; branding resolution can check `Seller.branding` before
|
||||||
|
falling back to marketplace `BrandingConfig`/`ThemeConfig`.
|
||||||
6
src/app/core/sellers/models/index.ts
Normal file
6
src/app/core/sellers/models/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export * from './marketplace-ref.model';
|
||||||
|
export * from './seller-branding.model';
|
||||||
|
export * from './seller-permissions.model';
|
||||||
|
export * from './seller-scope.model';
|
||||||
|
export * from './seller-status.model';
|
||||||
|
export * from './seller.model';
|
||||||
14
src/app/core/sellers/models/marketplace-ref.model.ts
Normal file
14
src/app/core/sellers/models/marketplace-ref.model.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { UUID } from '../../../shared/types/primitive.types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimal marketplace reference as seen from a Seller record - not the full
|
||||||
|
* runtime `TenantConfig` (shared/models/config/tenant.model.ts), which stays
|
||||||
|
* the bootstrap-time source of truth for the active marketplace (ADR-001).
|
||||||
|
* This is just enough to say "this seller belongs to that marketplace"
|
||||||
|
* without pulling in the whole tenant contract.
|
||||||
|
*/
|
||||||
|
export interface MarketplaceRef {
|
||||||
|
id: UUID;
|
||||||
|
slug: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
34
src/app/core/sellers/models/seller-branding.model.ts
Normal file
34
src/app/core/sellers/models/seller-branding.model.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* Future per-seller branding overrides - all optional. Absent fields fall
|
||||||
|
* back to marketplace branding (shared/models/config/branding.model.ts,
|
||||||
|
* BrandingConfig) and marketplace theme (theme.model.ts, ThemeConfig).
|
||||||
|
* Marketplace branding remains the default in every case; nothing here is
|
||||||
|
* consumed anywhere yet.
|
||||||
|
*/
|
||||||
|
export interface SellerContact {
|
||||||
|
email?: string;
|
||||||
|
phone?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SellerAddress {
|
||||||
|
line1?: string;
|
||||||
|
line2?: string;
|
||||||
|
city?: string;
|
||||||
|
region?: string;
|
||||||
|
postalCode?: string;
|
||||||
|
country?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SellerThemeOverrides {
|
||||||
|
primaryColor?: string;
|
||||||
|
accentColor?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SellerBranding {
|
||||||
|
logoUrl?: string;
|
||||||
|
bannerUrl?: string;
|
||||||
|
description?: string;
|
||||||
|
contacts?: SellerContact;
|
||||||
|
address?: SellerAddress;
|
||||||
|
themeOverrides?: SellerThemeOverrides;
|
||||||
|
}
|
||||||
16
src/app/core/sellers/models/seller-permissions.model.ts
Normal file
16
src/app/core/sellers/models/seller-permissions.model.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Future permission roles for the Seller Management module - a separate
|
||||||
|
* vocabulary from the existing platform admin roles (core/auth/models/
|
||||||
|
* permission.model.ts, AdminRole: Owner/Manager/Support/ReadOnly). No
|
||||||
|
* authentication or authorization change is introduced by this file; these
|
||||||
|
* types are unused until Seller Management is actually implemented.
|
||||||
|
*/
|
||||||
|
export type SellerPermissionRole =
|
||||||
|
| 'marketplaceOwner'
|
||||||
|
| 'seller'
|
||||||
|
| 'sellerStaff'
|
||||||
|
| 'platformAdmin';
|
||||||
|
|
||||||
|
export interface SellerPermissions {
|
||||||
|
role: SellerPermissionRole;
|
||||||
|
}
|
||||||
14
src/app/core/sellers/models/seller-scope.model.ts
Normal file
14
src/app/core/sellers/models/seller-scope.model.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { UUID } from '../../../shared/types/primitive.types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolved seller-scoped request context - the domain-level counterpart to
|
||||||
|
* `BootstrapConfig.seller` (`SellerConfig`, shared/models/config/seller.model.ts).
|
||||||
|
* That type is the bootstrap wire shape; this one is what seller-aware
|
||||||
|
* domain code (products, orders, permissions) references once it exists.
|
||||||
|
* Backend-resolved only, same as tenant resolution (ADR-001, ADR-011) - no
|
||||||
|
* frontend code constructs this itself.
|
||||||
|
*/
|
||||||
|
export interface SellerScope {
|
||||||
|
sellerId: UUID;
|
||||||
|
marketplaceId: UUID;
|
||||||
|
}
|
||||||
6
src/app/core/sellers/models/seller-status.model.ts
Normal file
6
src/app/core/sellers/models/seller-status.model.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Lifecycle status of a seller record. No transition logic exists yet - this
|
||||||
|
* is the typed vocabulary only, for future Seller Management CRUD/moderation
|
||||||
|
* to build against.
|
||||||
|
*/
|
||||||
|
export type SellerStatus = 'pending' | 'active' | 'suspended' | 'disabled';
|
||||||
20
src/app/core/sellers/models/seller.model.ts
Normal file
20
src/app/core/sellers/models/seller.model.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { ISODateTime, UUID } from '../../../shared/types/primitive.types';
|
||||||
|
import { MarketplaceRef } from './marketplace-ref.model';
|
||||||
|
import { SellerBranding } from './seller-branding.model';
|
||||||
|
import { SellerStatus } from './seller-status.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A seller record - the future entity Seller Management CRUD will operate
|
||||||
|
* on. No repository, gateway, or facade exists yet; this is the typed shape
|
||||||
|
* only, so future work has a settled model instead of inventing one ad hoc.
|
||||||
|
*/
|
||||||
|
export interface Seller {
|
||||||
|
id: UUID;
|
||||||
|
marketplace: MarketplaceRef;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
status: SellerStatus;
|
||||||
|
branding?: SellerBranding;
|
||||||
|
createdAt: ISODateTime;
|
||||||
|
updatedAt: ISODateTime;
|
||||||
|
}
|
||||||
@@ -51,6 +51,12 @@ export interface AdminOrder {
|
|||||||
archived: boolean;
|
archived: boolean;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
/**
|
||||||
|
* Future optional seller ownership (Seller Management preparation).
|
||||||
|
* Absent means marketplace-owned, exactly like every order today -
|
||||||
|
* nothing reads this field yet, no behavior change.
|
||||||
|
*/
|
||||||
|
sellerId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminOrderListFilters {
|
export interface AdminOrderListFilters {
|
||||||
|
|||||||
@@ -112,6 +112,12 @@ export interface AdminProduct {
|
|||||||
questions: AdminProductQuestion[];
|
questions: AdminProductQuestion[];
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
/**
|
||||||
|
* Future optional seller ownership (Seller Management preparation).
|
||||||
|
* Absent means marketplace-owned, exactly like every product today -
|
||||||
|
* nothing reads this field yet, nothing breaks by it being undefined.
|
||||||
|
*/
|
||||||
|
sellerId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminProductListFilters {
|
export interface AdminProductListFilters {
|
||||||
|
|||||||
@@ -176,6 +176,13 @@ export interface Item {
|
|||||||
comments?: Comment[];
|
comments?: Comment[];
|
||||||
visits?: number;
|
visits?: number;
|
||||||
itemDetails?: ItemDetail[];
|
itemDetails?: ItemDetail[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Future optional seller ownership (Seller Management preparation).
|
||||||
|
* Absent means marketplace-owned, exactly like every product today -
|
||||||
|
* nothing reads this field yet, nothing breaks by it being undefined.
|
||||||
|
*/
|
||||||
|
sellerId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CartItem extends Item {
|
export interface CartItem extends Item {
|
||||||
|
|||||||
Reference in New Issue
Block a user