供给方(supplier)业务架构
本文档为供给方域的业务架构真相源。技术契约细节(zod/service/repo 模板、CQRS 读写模板、ErrorCode、CURD/Detail 骨架、地址三元组契约、文件令牌链、拖拽 reorder)不在此复述,以 specRefs 指向的 x-api / x-data / x-file / x-address / x-map / x-curd / x-detail / x-auth / x-order / x-exception 十 spec 为准(单一真相源,防第二真相源)。本文只讲架构、双聚合、供给方生命周期、CQRS 数据流、四端消费、上下游边界、诚实风险。所有断言均带现网file:行号证据。
真相源:全部结论基于四端真实源码 file:行号实证 + 两次 CloudBase MCP 知识库核实(供应商地址 geocode 走通用能力 / 证书图 cloudID getTempFileURL 官方背书,均无供给方专属注意点)。
⚠️ 双聚合根:本域是双聚合根合并域——supplier_base(法人主体·主聚合,挂 contact/certificate/brand 三子表)+brand_base(消费者认知贴牌·次聚合,有自身 CRUD + parentBrandId 自树),靠supplier_brand_relation(纯 2 字段绑定表)缝合。二者是"谁在供货 / 贴牌"的一体两面,非两个独立限界上下文(详见 §一、§六)。
一、领域边界与全景架构图
1.1 Bounded Context 定义
供给方是电商域的上游供给侧主数据域(Upstream Supply-side Master Data),其边界职责唯一:把「谁在提供服务(法人供应商 supplier)」与「贴什么品牌交付(消费者认知 brand)」建模为两个可维护的主数据聚合,并通过多对多绑定表缝合成一个「供给方」概念,向商品核心(spu)供给 SUP/BRD 多态引用锚点。
双聚合语义是理解全域的钥匙(brand_base.md:5,49,187,228-249):
| 聚合 | 表 | 主键/前缀 | 语义 | 下游消费视角 |
|---|---|---|---|---|
| 供应商(法人主体) | supplier_base | supplierId / SUP | 一个可派单/供货的法人实体(工商信息+服务能力+合作管理) | Offer supplierId SUP 前缀多态 · 小厂直供场景 |
| 品牌(消费者认知贴牌) | brand_base | brandId / BRD | 消费者认知的品牌方(可自引用真父子品牌树,小米→米家) | Offer supplierId BRD 前缀多态 · 大厂品牌交付场景 |
| 法人↔品牌绑定 | supplier_brand_relation | _id | 谁的法人主体持有哪些品牌(纯 2 字段 ID 集合) | 大厂 SPU 绑 BRD,可经此表反查法人 supplier |
**边界铁律(brand_base.md:5,49,228-249)**:parentBrandId 只表达消费者认知的真父子品牌树(小米→米家);集团法人控股(海尔智家→海尔/卡萨帝)禁用父子,由 supplier_brand_relation 承载。brand=消费者认知贴牌,supplier=法人主体,两轴分离,靠关系表缝合。
1.2 四端全景架构图
上游主数据边界(Upstream · 旁系,本域不直接依赖)
┌──────────────────────────────────────────────────────────────────────┐
│ category_base(商品分类主数据)—— 与供给方无直接 FK;两条 FK 轴在 spu 层汇合 │
└────────────────────────────────────────────────────────────────────────┘
┌════════════════════════════════════════════════════════════════════════┐
║ 供给方域(DATA 真相源 · 5 表族均已发布 2026-06-12) ║
║ ║
║ supplier_base(主聚合根 · supplierId/SUP · 40业务字段 · 双地址三元组) ║
║ │1 ║
║ ├──N→ supplier_contact_node (联系人 · SCON · 12字段 · orderNo拖拽)║
║ ├──N→ supplier_certificate_node(资质证照 · SCRT · 13字段 · orderNo拖拽║
║ │ · attachmentUrl cloudID) ║
║ │N ║
║ ▼ supplier_brand_relation(纯2字段ID集合 · uniq_supplier_brand) ║
║ ▲ ↑ v1.1 索引层 DDL 补建 UNIQUE(模型层解耦,线上曾缺,§⑧) ║
║ │N ║
║ brand_base(次聚合根 · brandId/BRD · 15业务字段 · parentBrandId 自树) ║
║ └──self──▶ parentBrandId(仅真品牌延伸,禁集团控股) ║
╚══════════════════════════════════════════════════════════════════════════╝
│ camelCase 全链路透传(数据透传原则)
┌──────────┴───────────────────────┐
▼ ▼
┌─────────────────────────────┐ ┌──────────────────────────────────────┐
│ SERVER(CQRS 读写二分) │ │ 地址派生边界(geocode 在 admin BFF) │
│ 写:ORM(create+自增回查) │ │ server 零 geocode,三字段纯字符串透传 │
│ 读:runReadSQL/$runSQL(MySQL) │ │ registered/office 双组各一次 │
│ supplier-read/create/update/ │ │ geocodeAddressTriple(admin 前端) │
│ delete/order-update · │ └──────────────────────────────────────┘
│ info-read · import/export · │
│ contacts×4 · certificates×4 ·│
│ brands-bind/unbind/read · │
│ brand-create/update/delete/ │──(反向读 spu-suppliers-read 归 ecommerce/spu/ARCH.md)──▶
│ read(树·手工排序)· │
│ import/export·status-toggle │
└────────┬─────────────────────┘
▼
┌──────────────────┐ ┌────────────────────────────────────────────┐
│ ADMIN(管理台) │ │ CLIENT(微信小程序) │
│ supplier.tsx │ │ ✗ 无独立供给方模块 │
│ MasterList+Detail│ │ 供给方展示名(providerType/shopName)由 │
│ ├ contacts(inline)│ │ spu 商品核心 service-browse-read │
│ ├ certs(inline) │ │ loadSupplierMap 多态派生(归 ecommerce/spu/ARCH.md) │
│ └ brands(Transfer)│ │ goods-own-adapter.js:150 仅取 shopName │
│ brand.tsx (CURD树) │ │ (client 从不直接读 supplier_base/brand_base)│
└──────────────────┘ └────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ 下游供给锚点(Downstream · SUP/BRD 多态引用 · 单向供给) │
│ spu_supplier_relation.supplierId ^(SUP|BRD)\d{29}$(接缝) │
│ ├─► sku_maintenance_node.supplierId(Offer 供给单元 · 商品核心) │
│ ├─► order_base.supplierId(订单快照冻结 · 订单域) │
│ └─► sop_step_node((spuId,supplierId) 联合键 · 工单域) │
└────────────────────────────────────────────────────────────────┘
数据流方向要点:
- 上游 → 供给方:category 无直接引用(旁系,两条 FK 轴在 spu 层才汇合,
spu_base.md:17,121)。 - 供给方 → 下游:单向多态供给,接缝唯一在
spu_supplier_relation.supplierId(SUP/BRD 双前缀,spu_supplier_relation.md:14,32,137-143),继续下沉商品/订单/工单三核心。 - 地址坐标派生边界:server 零 geocode(
supplier-update/controller.js:26-31三字段纯字符串 zod),坐标由 admin BFFgeocodeAddressTriple派生(supplier.bff.ts:232-237)。
二、数据模型(实体与聚合)
2.1 supplier_base — 供应商主聚合根(40 业务字段 + 8 系统字段,已发布 2026-06-12)
三大字段区(supplier_base.md:11-53):
| 区 | 代表字段 | 出处 |
|---|---|---|
| 工商信息 | fullName / creditCode / legalPerson / companyType(enum) / companyScale(enum) / registeredCapital / foundDate / licenseExpiry | md:11-24 |
| 服务能力 | serviceCategories / serviceMode(enum) / coverageArea(*Area 多选) / staffCount / dailyCapacity / responseTime(enum) / equipmentList | md:31-45 |
| 合作管理 | cooperationStatus(enum) / supplierLevel(enum) / supplierRating(enum) / supplierSource(enum) / dispatchPriority(enum) / handler / regionalManager | md:46-53 |
| 维度 | 值 | 出处 |
|---|---|---|
| 主键 | supplierId 自动编号 SUP{DATETIMEUTC:yyyyMMddHHmmss}{SEQNUM:15} / varchar32 · maxLength32 | md:13 / json:141-156 / 主数据ID清单.md:346-360 |
| required(8) | fullName, shortName, orderNo, companyType, serviceCategories, serviceMode, coverageArea, cooperationStatus | json:22-31 |
| 枚举字段(9) | companyType/companyScale/serviceMode/responseTime/cooperationStatus/supplierLevel/supplierRating/supplierSource/dispatchPriority(各 x-option-name) | json 内 x-option-name 恰 9 次 |
| 排序 | orderNo x-system:true = 拖拽模式(白名单 supplier-order-update 驱动) | json:252-260 类比 |
| 索引 | PK supplierId + 6 单列 IDX(fullName/shortName/companyType/serviceMode/cooperationStatus/orderNo) | md:60-61 |
| 唯一约束 | 仅主键唯一(无其他单列/联合 UNIQUE) | json:154 |
2.2 brand_base — 品牌次聚合根(15 业务字段 + 8 系统字段,已发布 2026-06-12)
| 维度 | 值 | 出处 |
|---|---|---|
| 主键 | brandId 自动编号 BRD{DATETIMEUTC:yyyyMMddHHmmss}{SEQNUM:15} / varchar32 · maxLength32 | md:13 / json:138-152 / 主数据ID清单.md:156-170 |
| 15 业务字段 | brandName/brandNameEn/brandAlias/brandDescription/brandOrigin/parentBrandId/brandType/brandLogo/remark/officialWebsite/authStatus/licenseImage/licenseExpireDate/status/orderNo | md:13-28 |
| required(5) | brandName, brandType, authStatus, status, orderNo | json:22-28 |
| 自关联父 FK | parentBrandId varchar32 自引用 brandId(仅真品牌延伸,禁集团控股) | md:19,48-49 / json:179-186 |
| 排序 | orderNo 无 x-system = 手工模式(前端 ProFormDigit 输入,无 order-update 云函数) | json:266-272 |
| 索引 | PK + 5 单列 IDX + 组合索引 idxTypeStatus(brandType,status) | md:34-36 |
| 唯一约束 | 仅主键唯一 | json:151 |
2.3 supplier_contact_node — 联系人子表(12 业务字段,已发布 2026-06-12)
- 主键
supplierContactId前缀SCON{...}{SEQNUM:14}(注意 14 位自增,非 15)——md:13 / json:143 / 主数据ID清单.md:373-387 - 父 FK:
supplierIdvarchar32(md:14 / json:154-162) - 字段:contactName/contactRole(enum)/contactPhone/contactEmail/contactWechat/isPrimary(bool)/contactDepartment/contactPosition/availableTimeDesc/remark(md:15-24)
- required(5):supplierId, contactName, contactRole, contactPhone, isPrimary(json:22-28)
- 组合索引
idx_supplierPrimary(supplierId, isPrimary)(md:34) orderNox-system:true = 拖拽(supplier-contacts-create 初始化末尾 + supplier-contacts-order-update 批量重写,json:252-260 / md:25)- 无 file 字段
2.4 supplier_certificate_node — 资质证照子表(13 业务字段,已发布 2026-06-12)
- 主键
supplierCertificateId前缀SCRT{...}{SEQNUM:14}(14 位自增)——md:13 / json:142 / 主数据ID清单.md:400-414 - 父 FK:
supplierIdvarchar32(md:14 / json:153-161) - 字段:certificateType(enum)/certificateName/certificateNo/issuer/issueDate/expiryDate/coverageAmount/certificateLevel/attachmentUrl(array cloudID)/certificateStatus(enum)/remark(md:15-25)
- required(4):supplierId, certificateType, certificateName, certificateStatus(json:22-27)
- 组合索引
idx_supplierType(supplierId, certificateType)(md:35) orderNox-system:true = 拖拽(supplier-certificates-create/-order-update,json:260-267 / md:26)
2.5 supplier_brand_relation — 法人↔品牌绑定层(纯 2 FK 零业务字段,已发布 2026-06-12)
| 象限 | 字段 | 出处 |
|---|---|---|
| FK | supplierId(→supplier_base.supplierId) | md:13 / json:140-151 |
| FK | brandId(→brand_base.brandId) | md:14 / json:152-163 |
| 系统 | _id/createdAt/updatedAt/owner/createBy/updateBy/_mainDep/_openid | json:27-139 |
| 业务字段 | 零(纯 ID 集合,无 supplierName/logo 冗余) | — |
- PK:
_id系统主键(md:21);联合唯一uniq_supplier_brand(supplierId, brandId)防重复绑定(md:22,30) - 单列 IDX:idx_supplierId + idx_brandId(md:23)
- 零冗余铁律:对照
spu_supplier_relationv3.0 曾删 7 冗余字段的教训(md:114-128),本表从建表即纯 ID 集合。
2.6 地址三元组(仅 supplier_base 承载 · 双组 6 字段)
brand / contact / certificate 三表均无地址字段。supplier_base 双组:
registered 组(注册地址):
registeredRegion (region · varchar+x-json) ──▶ {codes,label} AreaCascader
registeredDetail (detail · varchar255) ──▶ 门牌手填 textarea
registeredCoordinate (coordinate · varchar128) ──▶ {lat,lng} GCJ02 BFF 零录入派生
office 组(办公地址):
officeRegion / officeDetail / officeCoordinate 同上三角色
↑ x-address-group:registered/office 各三角色成套(标记驱动,x-address NS-DATA-0001)
- 逐字标记齐全:json:264-265(registered/region) / 274-275(registered/detail) / 284-285(registered/coordinate) / 294-295(office/region) 等(md:25-30 / json:257-316)。
- coordinate 由 admin BFF
geocodeAddressTriple自动派生(md:27,30),用户零录入。 - 迁移史(md:206 v0.8):从旧
registeredAddress/officeAddress单 freeform 拆三字段,存量经腾讯 geocoder 迁移 144/145 成功,走已发布表场景B 控制台重导入。 - ⚠️
coverageArea(md:36 text JSON{codes,label}多选)是*Area多选域,与地址 region 三元组正交(x-address-rule 明示 *Area 不受 region 约束)。
2.7 文件字段(array cloudID · 全符合 x-data file 铁律)
type:array + items:{type:string} + x-storage-type:cloudID(无 format,物理 json 列):
| 表 | 字段 | 出处 |
|---|---|---|
| supplier_base | logo(供应商 Logo) | md:16 / json:175-183 |
| brand_base | brandLogo(品牌 Logo,文件 ID 前缀 {brandId}LOGO{...} 路径 wowo/brand/logos/) | md:21 / json:202-210 / 主数据ID清单.md:1129-1147 |
| brand_base | licenseImage(授权证书) | md:25 / json:237-245 |
| supplier_certificate_node | attachmentUrl(证照附件) | md:23 / json:231-239 |
2.8 关键字段语义
- SUP vs BRD 多态前缀:supplierId(SUP)= 法人直供锚点;brandId(BRD)= 品牌交付锚点。下游
spu_supplier_relation.supplierId单字段多态承载两者(^(SUP|BRD)\d{29}$),SUP=小厂直供 / BRD=大厂品牌交付。 - orderNo 三态并存:supplier_base / contact_node / certificate_node = 拖拽(x-system:true);brand_base = 手工(非 x-system)——供给方内部两种排序范式并存的实证(x-order NS-DATA-0002)。
- parentBrandId 语义唯一性:只做消费者认知真父子树,集团控股走 supplier_brand_relation(§一 边界铁律)。
三、供给方生命周期
建供应商 ──▶ 双地址 geocode ──▶ 联系人/证书 ──▶ 绑品牌 ──▶ 拖拽排序
│ │ │ │ │
supplier- admin BFF supplier- supplier- supplier-
create geocodeAddress contacts- brands-bind order-update
(MasterList Triple×2 create (Transfer (reorderByPk
内联仅 (registered+ supplier- 穿梭框·查重) CASE WHEN)
fullName+ office 各一次) certificates- contacts/certs
shortName) 坐标派生 create 各自 order-update
│ (attachmentUrl 单图)
└─▶ cooperationStatus/level/rating 合作管理
brand 独立线:
brand-create(手工 orderNo)
→ parentBrandId 挂真父子树
① 建供应商:MasterList 内联 create 仅 fullName+shortName 两字段起建(supplier.tsx:663-684),随后 Detail 补全其余 40 业务字段。create 后 ORM 回查拿自增 supplierId(supplier-create/repo.js:65-68)。
② 双地址 geocode:admin BFF 对 registered + office 各调一次 geocodeAddressTriple(supplier.bff.ts:232-237),派生 coordinate;serializeAreaFields(['registeredRegion','officeRegion']) 序列化 region object→JSON(supplier.bff.ts:239)。server 零 geocode(supplier-update/controller.js:26-31)。
③ 联系人/证书:inline-edit 子表行内新增(NEW_ 前缀),create 时 appendNextOrderNo(db, table, {supplierId}) 按父分组初始化排序(supplier-contacts-create/repo.js:32 / supplier-certificates-create/repo.js:33);证书 attachmentUrl 单图 create 分支先建后传再 update 回填(supplier.bff.ts:134-156)。
④ 绑品牌:Transfer 穿梭框(relation-select)受控返回变更集 → BFF supplier-brands-bind 先 queryExistingBindings 查重再 batchBind(createMany 分片,supplier-brands-bind/service.js:6-12);unbind 走 batchUnbind(deleteMany,supplier-brands-unbind/repo.js:5-27)。
⑤ 拖拽排序:supplier/contacts/certificates 三表拖拽 → reorderByPk(db, table, pkField, orderedIds) → sys $runSQL CASE WHEN 批量重写 orderNo(supplier-order-update/repo.js:16 等);brand 无拖拽,手工 ProFormDigit 输入 orderNo(brand.tsx:518-524)。
四、CQRS 数据流(读 runReadSQL/$runSQL · 写 ORM)
总体二分:读端一律声明式 runReadSQL(STD-SERVER-0008)或裸 $runSQL(STD-SERVER-0010);写端一律 ORM(create 后回查拿自动编号主键)。describes.server glob 下共 28 个 live 云函数(supplier 21 + brand 7,全 controller/service 真实函数,非空模板):下表列举主表 / 关系 / 读导出等主 CQRS 函数,contacts / certificates 子表各 4 个写函数(create / delete / update / order-update)见 §三 生命周期 + §5.1 权限声明。
| 云函数 | 端 | 读/写 | 数据层实现(证据) |
|---|---|---|---|
supplier-read | admin | 读·单表 | runReadSQL(mode:'list') JSON_FIELDS=[logo,registeredRegion,officeRegion],orderBy orderNo ASC(supplier-read/repo.js:44-52) |
supplier-info-read | admin | 读·单查 | runReadSQL(mode:'one', uniqueKey:'supplierId') findBySupplierId,JSON_FIELDS=[logo,registeredRegion,officeRegion],未命中抛 SUPPLIER_NOT_FOUND(repo.js:12-19 / service.js:9-10)——供应商详情读,即 §5.1 supplierDetailRead BFF 的服务端 |
supplier-create/update | admin | 写 | ORM create+回查(supplier-create/repo.js:65-68)/ updateMany(supplier-update/repo.js:69-72);update service resolveFileValue+x-file STD-SERVER-0007 三阶段差集清理(logo:READ diffRemovedFiles→MUTATE→CLEANUP deleteFiles,supplier-update/service.js:5-23) |
supplier-delete | admin | 写·三阶段 | READ(级联读 supplier logo + 证书 attachment,collectCloudUrls(records,['logo'])/(['attachmentUrl']),repo.js:29,52)→ MUTATE deleteMany BATCH_SIZE=200 → CLEANUP deleteFiles 委托 sys(service.js:6-25) |
supplier-order-update | admin | 写·拖拽 | reorderByPk(db,'supplier_base','supplierId',orderedIds)(repo.js:16) |
supplier-import | admin | 写·导入 | service 复合实体编排:brands 字符串数组经 resolveBrandIdsByName 反查 brandId → importOne(create/update + brandIds 写关联表,service.js:7-14;返回模板归 x-api STD-SERVER-0003) |
supplier-export | admin | 读·导出编排 | exportAll → buildSupplierExport:主表 runReadSQL list + 3 子表 $runSQL IN 并行 + brandName 反查 + 内存挂载 contacts/certificates/brands(repo.js:62-104,分组按 supplierIds 全集预建空数组,取值处零兜底直取,STD-SERVER-0006) |
supplier-contacts-read | admin | 读·单表 | runReadSQL(mode:'list'),buildOrderBy 返回 ORDER BY orderNo ASC(拖拽序生效,repo.js:13-14) |
supplier-certificates-read | admin | 读·单表 | 同上,buildOrderBy ORDER BY orderNo ASC,JSON_FIELDS=[attachmentUrl](repo.js:17-18/25) |
brand-read | admin | 读·树 | buildBrandTree = runReadSQL(mode:'list') 全量 + 内存 normalizeRecord/filterRecords/buildTree/sortTree(消 while),JSON_FIELDS=[brandLogo,licenseImage,brandType](brand-read/repo.js:84-94) |
brand-create/update/delete | admin | 写 | ORM;update service resolveFileValue+x-file STD-SERVER-0007 三阶段差集清理双字段(brandLogo+licenseImage,brand-update/service.js:5-27);delete 三阶段(brand-delete/service.js:12-27) |
brand-status-toggle | admin | 写·状态切换 | service updateManyStatus(brandIds, status) 批量(service.js:4-7)——即 §5.2 toggleStatus 的服务端(brandIds 数组 + status 批量重写) |
brand-import | admin | 写·导入 | service importOne(params, stats)(create/update 统计,service.js:5;返回模板归 x-api STD-SERVER-0003) |
brand-export | admin | 读·导出编排 | exportAll → buildBrandExport:runReadSQL list 全量 + normalize/filter 扁平(不建树),JSON_FIELDS=[brandLogo,licenseImage,brandType](repo.js:75-79,STD-SERVER-0006) |
supplier-brands-read | admin | 读·JOIN | 裸 $runSQL JOIN supplier_brand_relation × brand_base 投 14 列,双守卫 + length>2000 溢出 + parseJsonColumns([brandLogo,brandType]),ORDER BY b.brandName ASC(repo.js:8-30) |
supplier-brands-bind/unbind | admin | 写 | bind 查重 + createMany 分片 / unbind deleteMany(supplier-brands-bind/repo.js:5-25) |
反向读归属声明:spu-suppliers-read(x-server/admin/ecommerce/spu-supplier/,多态 CASE JOIN brand_base/supplier_base 派生 supplierName/logo)归 ecommerce/spu/ARCH.md(describes.server 含 x-server/admin/ecommerce/spu-supplier/**),本域 describes 不纳入,避免双真相源。
文件生命周期二态(x-file):
- CREATE 不 resolveFileValue(两步流豁免):
supplier-create/repo.js:20(logo) /supplier-certificates-create/repo.js:28(attachmentUrl) /brand-create/repo.js:25,27(brandLogo/licenseImage) 直存。 - UPDATE = resolveFileValue(xfile:→cloud://)+ x-file STD-SERVER-0007 三阶段差集清理(READ diffRemovedFiles 写库前算差集 → MUTATE 写库 → CLEANUP deleteFiles 删被移除旧文件):supplier logo / certificate attachmentUrl / brand 双字段。
- DELETE = 三阶段 READ→MUTATE→CLEANUP,deleteFiles 委托 sys(x-file STD-SERVER-0003);清理侧收集用 sys 原子
collectCloudUrls(records,[字段])正确按数组处理(NS-SERVER-0011),原数组契约风险已闭环(§八-1)。
五、四端消费
5.1 ADMIN 端 — supplier(MasterList + Detail 双栏)
模块位于 x-admin/src/xModules/supplier/(顶层,非 ecommerce 子目录)。
a) 页面形态(supplier.tsx:629-815):.x-page-layout() + flex-direction:row(supplier.less:3-9)master-detail 双栏:
- **左栏
<MasterList<SupplierBase, SupplierWithRelations>>**(supplier.tsx:632-773):width250 / idKey supplierId / titleKey shortName / subtitleKey fullName / filterFn 前端过滤 / statusClass(合作状态→绿/红/黄色条,:655-661)/ 内建 create(内联仅 fullName+shortName,:663-684)/ dragSort(x-order,:685-692)/ export+import(YAML,:694-772)。 - **右栏
<Detail>**(supplier.tsx:775-812,x-detail STD-ADMIN-0001 便捷封装,内部useDetail,非裸钩子):onLoad 先access.canCallFunction(AUTH_META.infoRead.id)预检 403 再supplierDetailReadBFF(:779-784);column3 / fileFields[logo] / logo preset / sections(zone upper 企业名片区 12 字段 + zone lower 下半区,:261-264)/ tables / onSave adapter / onDelete / permissions{read,update,delete}。
b) 嵌套子表(DetailTable inline-edit,非 DetailSteps)(supplier.tsx:527-615):
- contacts(
:547-579):mode inline-edit / idKey supplierContactId / contactFields 8 字段 / newRowTemplate NEW_ 前缀 / permissions{create,update,delete,orderUpdate}。 - certificates(
:581-614):idKey supplierCertificateId / fileFields[attachmentUrl] / certFields 9 字段(首字段 attachmentUrl image preset certificate 单图非图墙,:281)。 - 拖拽落库:BFF
supplier.bff.ts:271-287两处DetailBFFReorder(contacts.toReorder/certs.toReorder → supplierContactsOrderUpdate/supplierCertificatesOrderUpdate,!id.startsWith('NEW_')守卫)。
c) 品牌关联(Transfer 穿梭框 relation-select)(supplier.tsx:529-544):dataKey brands / idKey brandId / brandFields 11 列 / fileFields[brandLogo] / selectComponent SupplierBrandSelectAdapter / permissions{read,bind,unbind}。适配器(:317-331)→ <SupplierBrandTransfer>(supplier-brand-transfer.tsx:106-128,<Transfer<BrandTransferItem>> 受控,只 onChange 返回变更集不直调 bind/unbind;候选用 brandReadFlat tree→flat 修子品牌不可见 BUG,:48-60)。bind/unbind 真调延迟到 Detail 保存的 BFF(supplier.bff.ts:175-185);契约守卫 brands.mode!=='relation-select' → XSystemError(:254-258)。
d) 地址消费:register/office 双组 DetailSection fieldType:'address-triple'(:91-101 span3 / :226-236);coverageArea 内联 <AreaCascader mode="multiple">(:241-257,getValueProps/getValueFromEvent JSON 双向,只读 areaValueToLabel);预加载 loadDistrictTree()(:622-624)。无 AddressWithMap/MapPreviewModal(走 address-triple 声明式 + BFF geocode,x-map Admin 端 Phase 2 迁移中)。
e) 文件:logo Detail preset(:789-790)+ resolveAdapterFiles(supplier.bff.ts:218-225);certificate attachmentUrl 单图 BFF 手工编排(:132-173);读令牌 resolveFileToken 3 处(:68-70)。全单图无图墙。
f) AUTH_META:supplier.tsx:48-75 声明供给方全操作 func(base 主操作 + contacts + certificates + brands 关联)——**逐条以 supplier.tsx:48-75 源码为准**(func 声明 X-RULE 标记 + AUTH_META.xxx.id 消费闭环由 x-auth NS-ADMIN-0004/0005 管辖)。
5.2 ADMIN 端 — brand(CURD 树形 + 手工 orderNo)
模块 x-admin/src/xModules/brand/(顶层,平级 supplier)。brand.tsx:349-1101 单栏 <Curd<BrandItem>>(x-curd STD-ADMIN-0001):
- 树形:pidKey parentBrandId + cascadeDownOnly + usePagination false(
:371-373);request 后 flattenTree 扁平统计(:405-412)。 - 手工 orderNo(非拖拽):新增/编辑/行级新增均
<ProFormDigit name="orderNo" required>(:518-524/730-735/925-930),列 valueType digit(:326-333)。无 brand-order-update 云函数,无 dragSort。 - 全套操作:create(STD-ADMIN-0007)/ delete cascade(STD-ADMIN-0011)/ 树形行级新增(STD-ADMIN-0002)/ update / 行级删除 / export(xlsx+yaml)/ import / notice 统计栏 / toggleStatus(列内 Switch modal.confirm,
:252-299)。 - 文件:brandLogo(preset logo) + licenseImage(preset certificate) 双图,create/update 内 fileUploadBff + brandUpdate 回填
[cloudUrl](:562-595/959-1006),列用<ImageView>。 - brandType 多选:列 render 消费已 parse 数组(
:164),表单 ProFormRadio.Group + transform 单值转数组(:507-517,业务单选存数组)。
5.3 类型契约(x-type @x-data-table 标记)
| 类型 | @x-data-table | 出处 |
|---|---|---|
| SupplierBase | supplier_base | supplier.types.ts:24-28 |
| SupplierContact | supplier_contact_node | supplier.types.ts:81-85 |
| SupplierCertificate | supplier_certificate_node | supplier.types.ts:108-112 |
| BrandItem | brand_base | brand.types.ts:24-29 |
| SupplierBrand / SupplierBrandTransfer / BrandTransferItem | 无标记(派生 VO) | supplier.types.ts:541-558 等 |
- 地址 region object 契约(x-address NS-ADMIN-0002):
registeredRegion?/officeRegion?: AreaSingleValue({codes,label},geo-area.types.ts:36-40,前端全链路 object 零 parse,supplier.types.ts:42,45);coordinate/detail 是 string;coverageArea 是 string(*Area 多选 JSON 域,与 region object 轴正交,:53)。 - 聚合导出:
SupplierWithRelations = SupplierBase & {contacts[]; certificates[]; brands: string[]}(:646-654);SupplierUpdateParams = Partial<SupplierBase> & {supplierId}(x-api NS-COMMON-0036 合规形态,:208-212)。 - BrandItem file 字段
brandLogo?/licenseImage?: string[]|null(x-file NS-ADMIN-0029 数组形态),brandType?: string[]。
5.4 CLIENT 端 — 无独立供给方模块
x-client/**/{supplier,brand}/**Glob 零匹配——client 下不存在供给方 xModules 模块或消费页。- 供给方到达小程序唯一间接路径已归属 spu 商品核心 arch:server 侧
service-browse-read/repo.js:30-56的loadSupplierMap按sid.startsWith('BRD'/'SUP')多态分流读 brand_base/supplier_base,派生node.providerType(:160)——此函数在 ecommerce/spu/ARCH.mddescribes.server:["x-server/client/mall/service/**"]名下。 - client 侧
goods-own-adapter.js:150**仅取shop.shopName**(来自 Offersku_maintenance_node.shopName,非 supplier_base),:25注释明写"shop 主数据表未来才建立"。client 从不直接读 supplier_base/brand_base。 - 本域 describes.client = null(对齐 dispatch 范式);供给方展示名归 ecommerce/spu/ARCH.md,本域不重复纳管(防第二真相源)。
六、上下游 Context Map(DDD)
上游主数据(旁系 · 本域不直接依赖)
┌─────────────────────────────────────────────┐
│ category_base(商品分类)—— 与供给方无直接 FK │
│ 两条 FK 轴(spu.categoryId 分类 · spu-supplier │
│ .supplierId 供给)在 spu 层才汇合 │
└──────────────────┬──────────────────────────┘
│(无引用箭头指向供给方)
╔══════════════════▼══════════════════════════════════════╗
║ 供给方域(本域 · 真相源 · 双聚合根) ║
║ supplier_base ─1:N→ contact_node / certificate_node ║
║ supplier_base ─N:M(supplier_brand_relation)N:M─ brand_base ║
║ brand_base ─self→ parentBrandId(真父子品牌树) ║
╚═══════════════════════════┬═════════════════════════════╝
spu_supplier_relation.supplierId ^(SUP|BRD)\d{29}$(唯一接缝)
┌──────────────────┬────────┴───────────┬──────────────────────┐
▼ ▼ ▼ ▼
┌────────────────┐ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────┐
│ spu 商品核心 │ │ sku_maintenance_ │ │ order 订单域 │ │ sop 工单域 │
│ spu_supplier_ │ │ node(Offer供给 │ │ order_base. │ │ sop_step_node │
│ relation(下游 │ │ 单元).supplierId │ │ supplierId 多态 │ │ (spuId, │
│ 接缝·多态锚点) │ │ 多态·(spuId, │ │ 快照冻结 │ │ supplierId) │
│ Upstream │ │ supplierId)UNIQUE│ │ │ │ 联合键 │
│ Supplier │ │ │ │ │ │ │
└────────────────┘ └──────────────────┘ └──────────────────┘ └──────────────┘
| 关系 | 方向/模式 | 契约(证据) |
|---|---|---|
| category → 供给方 | 旁系·无直接引用 | category 经 spu_base.categoryId(spu_base.md:17,121)进商品树,与 supplier/brand 在 spu 层汇合,两条 FK 轴独立;供给方域不依赖 category |
| 供给方 → spu 商品核心 | 下游·Upstream Supplier | 唯一接缝 spu_supplier_relation.supplierId 多态 ^(SUP|BRD)\d{29}$(spu_supplier_relation.md:14,32,137-143);大厂 SPU 绑 BRD,可经 supplier_brand_relation 反查法人 supplier(md:151);ecommerce/spu/ARCH.md §六 已把 supplier/brand 标为"上游供给方·未来独立 arch"——本域正是那个 arch,两文档边界自洽衔接 |
| 供给方 → 商品核心 Offer | 下游·多态 | sku_maintenance_node.supplierId 多态,业务键 (spuId,supplierId) UNIQUE 单条定位 Offer(sku_maintenance_node.md:15,29,38,41) |
| 供给方 → order | 下游·快照冻结 | order_base.supplierId 多态(引用锚点之一),订单快照冻结(order_base.md:20,123,143) |
| 供给方 → sop | 下游·联合键 | sop_step_node 经 (spuId,supplierId) 联合键关联(spu_supplier_relation.md:99) |
| brand ↔ supplier(域内) | 多对多 | supplier_brand_relation 唯一显式桥(纯 ID 集合,防集团控股误建父子) |
未落地下游(诚实标注):spu_base.brandId 暂未实现(brand_base.md:184 明示"待业务建模",grep 证实 spu_base.md 无 brandId 字段);service_base.brandId 亦未建表(brand_base.md:185)。brand 当前只经 spu_supplier_relation 的 BRD 多态间接下游。
七、specRefs(只引不复述,逐条标管辖)
| 规范 | 归类 | 管辖(它管什么) | 本域证据 |
|---|---|---|---|
x-api | 🟢核心 | CQRS 读写模板(STD-SERVER-0008 单表 runReadSQL / STD-0010 $runSQL JOIN·树 / 写端 ORM)、原子服务 STD-ADMIN-0001、BFF STD-ADMIN-0004 | supplier-read 单表 / supplier-brands-read JOIN / brand-read 树 / supplier.bff.ts |
x-data | 🟢核心 | 主表/嵌套/关系表 STD-DATA-0001/0002/0004、发布闸、场景B 演化 | 5 表族均已发布 2026-06-12(含 relation v1.1 索引层 DDL 补 uniq) |
x-file | 🟢核心 | array(cloudID) 生命周期、xfile: 令牌化、resolveFileValue/diffRemovedFiles、STD-SERVER-0002/0007 三阶段 | supplier.logo + certificate.attachmentUrl + brand.brandLogo/licenseImage |
x-address | 🟢核心 | 地址三元组契约(NS-DATA-0001 标记驱动 region+detail+coordinate)、region 读链路 object | supplier_base 双组 registered/office 各三字段成套 |
x-map | 🟢核心 | geocode SN 签名能力(STD-SERVER-0001 map-geocode-read) | 双地址组 BFF geocodeAddressTriple 派生坐标(能力归 x-map,数据归 x-address) |
x-curd | 🟢核心 | 列表 CURD 骨架、树形 CURD | brand.tsx CURD 树形模式 |
x-detail | 🟢核心 | Detail 模式(MasterList+Detail 四向绑定)、双模式子表(inline-edit / relation-select)、位置编码 | supplier.tsx 是 x-detail 双模式子表标杆(contacts/certificates=inline-edit,brands=relation-select),x-detail-rule 全程以 supplier 为例 |
x-auth | 🟢核心 | AUTH_META func 声明、L2 func 门、聚合读放行 | supplier / brand 两侧 AUTH_META;supplier-brands-read 聚合读 |
x-order | 🟢核心(非仅豁免) | 拖拽模式(x-system:true + reorderByPk)+ 手工模式(非 x-system) | supplier/contacts/certificates 真拖拽(supplier-order-update 等);brand_base 手工 orderNo——供给方内部两范式并存 |
x-exception | 🟢核心 | dto ErrorCode 枚举、ServerBusinessError、三级异常 | supplier/brand 两侧 *-delete errors.js + 绑定校验 |
明确排除(诚实,防第二真相源,对齐 spu/customer 范式):x-type(跨端通用规范,ecommerce/spu/ARCH.md/customer.md 均未列 specRefs)、x-i18n/x-structure/x-styles/x-logging/x-antd(通用规范每模块都用,不入 specRefs)。
八、诚实边界与风险
| # | 类别 | 描述 | 处置 |
|---|---|---|---|
| 1 | ~~delete 清理侧数组契约不符~~(已闭环) | 原风险:supplier_base.logo / brand_base.brandLogo/licenseImage / certificate.attachmentUrl 均 array(cloudID)(string[]),若 delete 清理侧手搓 url.startsWith('cloud://') 作用于数组会 startsWith is not a function 崩溃或恒 false 泄漏。**现网已收敛 sys 原子 collectCloudUrls**:supplier-delete/repo.js:29,52(collectCloudUrls(records,['logo']) / (['attachmentUrl']))、brand-delete/repo.js:23((['brandLogo','licenseImage']))、supplier-certificates-delete/repo.js:29((['attachmentUrl'])),业务代码零裸 startsWith(sys/file-storage.js 内的 filter 有 Array.isArray 守卫,非 bug);NS-SERVER-0011 detector 强制此收敛 | ✅ 已闭环(v0.1.1 修复,与版本日志一致,本次 prose 同步去陈旧) |
| 2 | ~~嵌套读排序未用 orderNo~~(已闭环) | 原风险:两嵌套读 buildOrderBy 返回 createdAt DESC,与拖拽写 orderNo 不一致 → 拖拽序经 read 重载不体现。现网已改:supplier-contacts-read/repo.js:13-14 + supplier-certificates-read/repo.js:17-18 buildOrderBy 均 return 'ORDER BY orderNo ASC',与 supplier-read 一致,拖拽序生效 | ✅ 已闭环(v0.1.1 修复,与版本日志一致,本次 prose 同步去陈旧) |
| 3 | ~~bind 查重用裸 Error~~(已闭环) | 原风险:supplier-brands-bind/service.js bind 查重用裸 throw new Error('…已绑定…') → controllerSystem 兜底退化 SYS_500,中文提示丢失。现网已改:service.js:12 throw new ServerBusinessError(ErrorCode.BRAND_ALREADY_BOUND, '以下品牌已绑定…')(v0.1.1 修复,file:12 实证),NS-SERVER-0006 迁移后全域业务代码零裸 Error 保证 | ✅ 已闭环(v0.1.1 + NS-SERVER-0006 双重保障) |
| 4 | relation 表 uniq 索引曾线上缺失 | supplier_brand_relation.md 自建即声称 uniq_supplier_brand,但线上 DB 从未建(MCP 核实仅 PRIMARY)→ 防重复绑定物理闸虚设。v1.1(md:83 2026-07-05)已 MCP ALTER TABLE ADD UNIQUE INDEX 补建(索引层与模型层解耦,线上 203 行 0 重复后安全) | 已闭环;与「文档宣称 UNIQUE 物理闸实际不存在」审计同类 |
| 5 | relation 表数据源漂移 | json x-primary-column:"supplierId"(json:7)与 .md PK=_id(md:21)矛盾(关系表主键应 _id,误配为 FK);系统字段 x-id 用 snake_case(created_at/owner_id)而三 node 表用 sys_createdAt——命名不统一(早期存量建表遗留) | 演化走 x-data 场景B 时校正;非功能性阻塞 |
| 6 | brandId 商品下游未建模 | spu_base.brandId(brand_base.md:184)+ service_base.brandId(185)尚未建表,brand 当前只经 spu_supplier_relation 的 BRD 多态间接下游 | 现状;未来业务建模后补 |
| 7 | 反向读归属声明 | spu-suppliers-read(多态 CASE JOIN)物理在 x-server/admin/ecommerce/spu-supplier/,功能上读供给方,但 describes 归 ecommerce/spu/ARCH.md | 本域 describes 不纳入,防双真相源 |
| 8 | client 无独立模块 | 供给方展示名 providerType/shopName 由 spu 商品核心 service-browse-read loadSupplierMap 派生,client 从不直接读 supplier_base/brand_base | describes.client=null;归 ecommerce/spu/ARCH.md(边界纪律) |
| 9 | 发布闸约束 | 5 表族均已发布 2026-06-12,演化必走 x-data 场景B(.md/.json 手工同步,禁 generate-json),已发布字段禁改 format(需五步破坏性迁移) | 常规约束 |
| 10 | 审计边界(诚实声明) | 全文基于四端静态只读抽取 + file:行号 + 两次 MCP KB 核实;未执行 MCP 线上表结构核对(除 relation uniq 已核,其余 x-unique/字段类型/联合索引与线上 DB 一致性未验证),未跑 -m 规范检测 | 归 x-data-dev verify-db-consistency 运行时核查范畴 |
边界纪律(arch 层铁律):供给方是纯业务领域。系统机制(认证/CQRS 模板/文件令牌/地址三元组契约/类型契约/异常码/CURD-Detail 骨架/拖拽 reorder)只经 specRefs 引用,绝不在本文复述其条款——跨切面机制归系统架构三件套(spec + rule + 内嵌 detector),本文若复述即成第二真相源,与 detector 逐字比对的真相源分岔。本文只承载:双聚合结构、供给方生命周期、数据流方向、四端消费编排、上下游边界、诚实风险。
相关规范(specRefs,只引用不复述)
| 规范 | 管辖 |
|---|---|
x-api-spec | CQRS 读写模板 STD-SERVER-0008/0010 + 写端 ORM、原子服务 STD-ADMIN-0001、BFF STD-ADMIN-0004 |
x-data-spec | 主表/嵌套/关系表 STD-DATA-0001/0002/0004、发布闸、场景B 演化 |
x-file-spec | array(cloudID) 生命周期、xfile: 令牌化、resolveFileValue/diffRemovedFiles、STD-SERVER-0002/0007 三阶段 |
x-address-spec | NS-DATA-0001 三元组标记驱动、NS-SERVER-0001/0002、STD-ADMIN-0001 geocodeAddressTriple |
x-map-spec | STD-SERVER-0001 SN 签名 map-geocode-read geocode 能力 |
x-curd-spec | 列表/树形 CURD 骨架 |
x-detail-spec | Detail 双模式子表标杆(inline-edit / relation-select)、四向绑定、位置编码 |
x-auth-spec | AUTH_META func、L2 func 门、聚合读放行 |
x-order-spec | 拖拽模式(x-system + reorderByPk)+ 手工模式(非 x-system)两范式并存 |
x-exception-spec | ErrorCode 枚举、ServerBusinessError、三级异常 |
版本日志 → [CHANGELOG.md](./CHANGELOG.md)(本域历史条目已自 frontmatter 迁出)