From 86091a4742ed335855946d14a5205cdda6e6904e Mon Sep 17 00:00:00 2001 From: sdarbinyan Date: Sun, 26 Jul 2026 21:55:11 +0400 Subject: [PATCH] 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. --- docs/architecture/foundation/README.md | 6 ++ .../Seller-Management-Domain-Models.md | 64 +++++++++++++++++++ src/app/core/sellers/models/index.ts | 6 ++ .../sellers/models/marketplace-ref.model.ts | 14 ++++ .../sellers/models/seller-branding.model.ts | 34 ++++++++++ .../models/seller-permissions.model.ts | 16 +++++ .../core/sellers/models/seller-scope.model.ts | 14 ++++ .../sellers/models/seller-status.model.ts | 6 ++ src/app/core/sellers/models/seller.model.ts | 20 ++++++ .../admin/orders/models/admin-order.model.ts | 6 ++ .../products/models/admin-product.model.ts | 6 ++ src/app/models/item.model.ts | 7 ++ 12 files changed, 199 insertions(+) create mode 100644 docs/architecture/foundation/Seller-Management-Domain-Models.md create mode 100644 src/app/core/sellers/models/index.ts create mode 100644 src/app/core/sellers/models/marketplace-ref.model.ts create mode 100644 src/app/core/sellers/models/seller-branding.model.ts create mode 100644 src/app/core/sellers/models/seller-permissions.model.ts create mode 100644 src/app/core/sellers/models/seller-scope.model.ts create mode 100644 src/app/core/sellers/models/seller-status.model.ts create mode 100644 src/app/core/sellers/models/seller.model.ts diff --git a/docs/architecture/foundation/README.md b/docs/architecture/foundation/README.md index d502c70..f1bbdd0 100644 --- a/docs/architecture/foundation/README.md +++ b/docs/architecture/foundation/README.md @@ -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-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 - [Folder Blueprint](Folder-Blueprint.md) diff --git a/docs/architecture/foundation/Seller-Management-Domain-Models.md b/docs/architecture/foundation/Seller-Management-Domain-Models.md new file mode 100644 index 0000000..824e294 --- /dev/null +++ b/docs/architecture/foundation/Seller-Management-Domain-Models.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`. diff --git a/src/app/core/sellers/models/index.ts b/src/app/core/sellers/models/index.ts new file mode 100644 index 0000000..c11ce5a --- /dev/null +++ b/src/app/core/sellers/models/index.ts @@ -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'; diff --git a/src/app/core/sellers/models/marketplace-ref.model.ts b/src/app/core/sellers/models/marketplace-ref.model.ts new file mode 100644 index 0000000..1c58d92 --- /dev/null +++ b/src/app/core/sellers/models/marketplace-ref.model.ts @@ -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; +} diff --git a/src/app/core/sellers/models/seller-branding.model.ts b/src/app/core/sellers/models/seller-branding.model.ts new file mode 100644 index 0000000..799639d --- /dev/null +++ b/src/app/core/sellers/models/seller-branding.model.ts @@ -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; +} diff --git a/src/app/core/sellers/models/seller-permissions.model.ts b/src/app/core/sellers/models/seller-permissions.model.ts new file mode 100644 index 0000000..374b45f --- /dev/null +++ b/src/app/core/sellers/models/seller-permissions.model.ts @@ -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; +} diff --git a/src/app/core/sellers/models/seller-scope.model.ts b/src/app/core/sellers/models/seller-scope.model.ts new file mode 100644 index 0000000..de2de7d --- /dev/null +++ b/src/app/core/sellers/models/seller-scope.model.ts @@ -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; +} diff --git a/src/app/core/sellers/models/seller-status.model.ts b/src/app/core/sellers/models/seller-status.model.ts new file mode 100644 index 0000000..08f27ca --- /dev/null +++ b/src/app/core/sellers/models/seller-status.model.ts @@ -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'; diff --git a/src/app/core/sellers/models/seller.model.ts b/src/app/core/sellers/models/seller.model.ts new file mode 100644 index 0000000..1294b72 --- /dev/null +++ b/src/app/core/sellers/models/seller.model.ts @@ -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; +} diff --git a/src/app/features/admin/orders/models/admin-order.model.ts b/src/app/features/admin/orders/models/admin-order.model.ts index afb4d51..7dc06d9 100644 --- a/src/app/features/admin/orders/models/admin-order.model.ts +++ b/src/app/features/admin/orders/models/admin-order.model.ts @@ -51,6 +51,12 @@ export interface AdminOrder { archived: boolean; createdAt: 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 { diff --git a/src/app/features/admin/products/models/admin-product.model.ts b/src/app/features/admin/products/models/admin-product.model.ts index e3ee7f8..0ac5e45 100644 --- a/src/app/features/admin/products/models/admin-product.model.ts +++ b/src/app/features/admin/products/models/admin-product.model.ts @@ -112,6 +112,12 @@ export interface AdminProduct { questions: AdminProductQuestion[]; createdAt: 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 { diff --git a/src/app/models/item.model.ts b/src/app/models/item.model.ts index 38b308e..cc2df22 100644 --- a/src/app/models/item.model.ts +++ b/src/app/models/item.model.ts @@ -176,6 +176,13 @@ export interface Item { comments?: Comment[]; visits?: number; 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 {