DIP1 阶段 2 技术设计:一线操作系统¶
文档编号:DOC-D01-D2 / 简称:DIP1-P2 版本:V2.0 创建日期:2026-08-09 最近更新:2026-08-09 维护人:TL / DT 关联文档:TND-E、DIP1-ARC V2.0 架构设计、QMD、CPT-D、MDS-D、OPS-D
V2.0 重大变更:跳过阶段1 飞书对接,直接实施阶段2;前端升级为 React Native(师傅端+客户端原生)+ Next.js(运营后台);新增客户端全流程;工作量从 73 人天重估为 96 人天(后端49+前端47)。详见 ADR-002/006/007/008。
一、文档定位与目标¶
1.1 阶段 2 定义¶
阶段 2 对应 TND-E「一线操作系统」,是 DIP1 子项目的核心交付期:直接构建全定制开发系统作为一线团队的日常操作平台,完成业务流程数字化闭环(线索→报价→派单→安装→验收→回访→口碑)。V2.0 跳过阶段1 飞书对接,直接实施阶段2。
graph TD
subgraph Phase0["阶段 0(可选·未来按需启动)"]
FS["飞书多维表格<br/>MVP-FS 方案"]
BR["DIP1-P1 飞书对接层<br/>⏸ 暂不实施"]
end
subgraph Phase2["阶段 2(一线操作系统)⭐本文档·直接实施"]
subgraph Domain["领域层 (DDD)"]
QSV["QSV 报价域"]
CPT["CPT 订单跟踪域"]
MDS["MDS 主数据域"]
OPS["OPS 运营域"]
DSP["DSP 调度域"]
PAY["PAY 支付域"]
SHARED["SharedKernel<br/>通用值对象/枚举"]
end
subgraph Application["应用层"]
UC["用例编排<br/>Use Cases"]
DTO["DTO/输入校验"]
end
subgraph Infrastructure["基础设施层"]
REPO["仓储实现<br/>PostgreSQL/Redis"]
MSG["消息总线<br/>事件驱动"]
AI["AI 适配层<br/>听写助手 API"]
PUSH["推送服务<br/>Expo Notifications"]
end
subgraph Interfaces["接口层"]
REST["REST API<br/>FastAPI 65 端点"]
ADMIN["运营后台<br/>Next.js opr-admin<br/>PC Web"]
WKR["师傅端<br/>React Native wkr-app<br/>iOS+Android 原生"]
CST["客户端<br/>React Native cst-app<br/>iOS+Android 原生"]
end
end
Phase0 -.->|"未来阶段0启动后<br/>按需数据迁移"| Phase2
style Domain fill:#004046,stroke:#004046,color:#fff;
style Application fill:#D4AF78,stroke:#004046,color:#004046;
style Infrastructure fill:#2a9d8f,stroke:#004046,color:#fff;
style Interfaces fill:#457b9d,stroke:#004046,color:#fff;
style Phase0 fill:#F2F0EB,stroke:#D4AF78,stroke-dasharray:5 5,color:#004046;
1.2 设计目标¶
| 目标 | 含义 | 验证标准 |
|---|---|---|
| 功能对齐 CPT 8 阶段 | 覆盖 L1~L8 全部电子表单能力(CPT-F 183 字段 100%) | 字段覆盖率 100%,流程跑通 50 单零报错 |
| 报价引擎对齐 QMD | 8 大类参数 + 32 项因子 + 封顶价机制 100% 实现 | 回归 1000 组测试用例,与 QMD 公式偏差 < 0.1% |
| 主数据四库完整 | SCL/BPL/WPL/CPL 四库 + TSMM 标签体系落地 | 每库 CRUD + 高级查询 + 批量导入导出 |
| RBAC 权限严格 | 6 类角色(SPO/BO/TL/OL/ML/FL)+ CST 客户行级/列级/操作级控制 | 权限矩阵覆盖 100% 接口,越权测试通过率 100% |
| 移动端原生体验 | WKR/CST React Native 原生 App(离线/推送/相机/生物识别) | iOS+Android 双端审核通过;崩溃率 < 0.1%;冷启动 ≤ 2s |
| 客户端全流程 | CST 客户端覆盖询价→下单→跟踪→评价→推荐完整闭环 | 8 页面 + 12 API 端点;客户端 UAT 20 单跑通 |
| 推送闭环 | 订单状态变更自动推送至 WKR/CST App | 推送送达率 ≥ 95%;关键节点 8 类推送模板 |
| 并发与性能 | 支撑日均 100 单、峰值 300 单/天(含客户端流量) | API P95 < 200ms,并发 1000 用户无报错 |
二、核心域模型设计(Domain Layer)¶
2.1 域划分与上下文映射¶
| 子域 | 英文 | 核心职责 | 对应设计文档 |
|---|---|---|---|
| 报价域 | qsv |
报价生成、参数校准、封顶价 | QMD V2.1 / QSV-D V1.0 |
| 订单跟踪域 | cpt |
8 阶段生命周期、跟踪池、采集物 | CPT-D V2.3 / CPT-F V1.0 |
| 主数据域 | mds |
四库(SCL/BPL/WPL/CPL)+ 标签体系 | MDS-D V1.1 / TSMM V1.1 |
| 调度域 | dsp |
师傅派单、负载均衡、自动指派 | RQD-B DSS 需求 |
| 支付域 | pay |
FPS/信用卡月结、对账、退款 | RQD-B PAY 需求 |
| 运营域 | ops |
获客场景、物料、口碑营销、推荐关系 | OPS-D V1.1 / OPS-S V1.0 |
| 共享内核 | shared |
值对象、枚举、基类、领域事件 | 通用 |
graph LR
CPT -- "依赖(询价/报价)" --> QSV
CPT -- "依赖(主数据引用)" --> MDS
CPT -- "依赖(派单/调师傅)" --> DSP
CPT -- "依赖(收款状态)" --> PAY
OPS -- "依赖(线索入池)" --> CPT
OPS -- "依赖(客户画像/口碑)" --> MDS
DSP -- "依赖(师傅技能/负载)" --> MDS
QSV -- "依赖(品类费率/楼宇系数)" --> MDS
SHARED["shared"] -.-> QSV
SHARED -.-> CPT
SHARED -.-> MDS
SHARED -.-> DSP
SHARED -.-> PAY
SHARED -.-> OPS
2.2 SharedKernel 通用基类与值对象¶
# app/domain/shared/base.py
class AggregateRoot:
"""聚合根基类,持有领域事件"""
id: EntityId
created_at: datetime
updated_at: datetime
_events: list[DomainEvent] = field(default_factory=list)
class EntityId(str):
"""强类型 ID,采用 ULID 格式(可排序 + 无冲突)"""
@classmethod
def new(cls, prefix: str) -> "EntityId":
# ORD-01AN4Z07BY79KA1307SR9X4MV3
return cls(f"{prefix}-{ulid.new()}")
# app/domain/shared/value_objects.py
@dataclass(frozen=True)
class Money:
"""金额值对象(币种 + 十进制)"""
amount: Decimal
currency: str = "HKD"
def __add__(self, other: "Money") -> "Money": ...
def __mul__(self, factor: Decimal) -> "Money": ...
@dataclass(frozen=True)
class Address:
"""地址值对象(香港地址结构化)"""
district: str # 区域:黄大仙/九龙塘/...
estate: str | None # 屋苑
building: str | None # 楼栋
floor_unit: str | None # 楼层室号
detail: str # 完整地址文本
@dataclass(frozen=True)
class Contact:
"""联系方式值对象"""
whatsapp: str | None
phone: str | None
email: str | None
2.3 QSV 报价域模型¶
对应 QMD V2.1:8 大类参数、32 项因子、封顶价机制
# app/domain/qsv/quote.py
class Quote(AggregateRoot):
"""报价聚合根"""
quote_id: EntityId # QTE-前缀 ULID
order_id: EntityId | None # 关联 CPT 订单
category_id: EntityId # → SCL 品类
customer_id: EntityId # → CPL 客户
# QMD 8 大类参数(核心因子)
base_fee: Money # P1 品类基础费(来自 SCL)
complexity: Factor # P2 复杂度系数 1.0~2.0
building_factor: Factor # P3 楼宇系数(来自 BPL)
worker_level_factor: Factor # P4 师傅等级系数(L1=0.9/L2=1.0/L3=1.15)
floor_factor: Factor # P5 楼层附加(无电梯每层 +x)
area_factor: Factor # P6 面积系数(大型家具额外)
distance_factor: Factor # P7 距离系数(离仓偏远附加)
peak_season_factor: Factor # P8 旺季系数(节假日 1.1~1.3)
# 附加项与调整
extra_items: list[ExtraItem] # 增项费(拆旧/搬运/辅材等)
discount: Money # 优惠折扣
cap_price: Money # 封顶价(来自 SCL)
# 计算结果
subtotal: Money
final_amount: Money # = MIN(subtotal - discount, cap_price)
# 状态流
status: QuoteStatus # DRAFT / CONFIRMED / REJECTED / EXPIRED
created_by: UserId
confirmed_at: datetime | None
# 行为
def recalculate(self) -> None:
"""按 QMD 公式重算,确保模型内聚"""
product = (
self.base_fee
* self.complexity
* self.building_factor
* self.worker_level_factor
* self.floor_factor
* self.area_factor
* self.distance_factor
* self.peak_season_factor
)
extra_sum = sum(i.amount for i in self.extra_items, start=Money(0))
self.subtotal = product + extra_sum
self.final_amount = min(self.subtotal - self.discount, self.cap_price)
def confirm(self, actor: UserId) -> None:
if self.status != QuoteStatus.DRAFT:
raise InvalidStateError("非草稿状态不可确认")
self.status = QuoteStatus.CONFIRMED
self.confirmed_at = now()
self._add_event(QuoteConfirmed(quote_id=self.id, amount=self.final_amount))
# app/domain/qsv/ports.py
class QuoteRepository(Protocol):
"""QSV 仓储端口(由 Infrastructure 实现)"""
async def add(self, quote: Quote) -> None: ...
async def get(self, quote_id: EntityId) -> Quote: ...
async def find_by_order(self, order_id: EntityId) -> list[Quote]: ...
class QuoteParamProvider(Protocol):
"""QMD 参数供给端口(对接 MDS SCL/BPL)"""
async def get_base_fee(self, category_id: EntityId) -> Money: ...
async def get_building_factor(self, building_id: EntityId) -> Factor: ...
2.4 CPT 订单跟踪域模型¶
对应 CPT-D V2.3:8 阶段生命周期、跟踪池、L6 作业规范子流程
# app/domain/cpt/order.py
class CPTOrder(AggregateRoot):
"""订单跟踪聚合根,封装 L1→L8 完整生命周期"""
order_id: EntityId # ORD-前缀
stage: CPTStage # L1..L8 / EXCEPTION / DONE
status: OrderStatus # IN_PROGRESS / COMPLETED / CANCELLED
current_pool: TrackingPool # 待联系/待报价/待派单/待安装/异常池
# —— L1 线索 ——
lead: LeadInfo
# —— L2 需求 ——
demand: DemandInfo
# —— L3 报价 ——
active_quote_id: EntityId | None
quote_history: list[EntityId]
# —— L4 签约 ——
contract: ContractInfo | None
assignment: AssignmentInfo | None
# —— L5 配送 ——
delivery: DeliveryInfo | None
# —— L6 安装(含 L6 作业规范 6 节点)——
install: InstallRecord | None
# —— L7 验收 ——
acceptance: AcceptanceRecord | None
# —— L8 回访 ——
followup: FollowupRecord | None
# 关系引用
customer_id: EntityId
building_id: EntityId | None
worker_id: EntityId | None
category_id: EntityId
# ===== 状态流转行为(CPT-D 状态机核心)=====
def transition_to(self, next_stage: CPTStage, actor: UserId, **ctx) -> None:
self._validate_transition(self.stage, next_stage)
match next_stage:
case CPTStage.L2:
self.demand = DemandInfo(**ctx["demand"])
case CPTStage.L3:
pass # QSV 聚合根独立处理,此处仅挂 quote_id
case CPTStage.L4:
self.contract = ContractInfo(**ctx["contract"])
self.assignment = AssignmentInfo(**ctx["assignment"])
self._add_event(WorkerAssignedEvent(self.worker_id, self.order_id))
case CPTStage.L6:
self.install = InstallRecord(**ctx["install"])
self._validate_l6_operations(self.install) # L6 作业规范校验
case CPTStage.L8:
self.followup = FollowupRecord(**ctx["followup"])
self._add_event(NPSCollectedEvent(self.customer_id, self.followup.nps))
self.stage = next_stage
self.updated_at = now()
def mark_exception(self, reason: str, actor: UserId) -> None:
self.stage = CPTStage.EXCEPTION
self.exception_reason = reason
self._add_event(OrderExceptionEvent(self.id, reason))
# 安装记录(含 L6 作业规范 6 节点)
@dataclass
class InstallRecord:
departed_at: datetime
arrived_at: datetime
completed_at: datetime
actual_hours: Decimal
# L6 作业规范 6 节点(6S 合规)
op_wore_shoe_cover: bool # N1 穿鞋套
op_floor_protected: bool # N2 地面保护
op_workbench_clean: bool # N3 工作台整洁
op_trash_removed: bool # N4 垃圾带走
op_product_cleaned: bool # N5 产品清洁
op_before_after_photos: bool # N6 前后对比照
site_photos: list[Attachment] # 入户前/安装中/完工后
extra_work_note: str | None
exception_note: str | None
2.5 MDS 主数据域模型¶
对应 MDS-D V1.1:四库 + TSMM 标签体系元模型
# app/domain/mds/category.py (SCL)
class ServiceCategory(AggregateRoot):
category_id: EntityId # CAT-xxx
name: str # 衣柜/橱柜/床/...
sub_categories: list[SubCategory] # 细分:PAX/整体定制/儿童床...
base_fee: Money # 基础费(QSV 引用)
base_work_hours: Decimal # 基础工时
cap_price: Money # 封顶价
skill_required: WorkerLevel # 最低技能等级要求
product_params: list[ProductParam] # 产品参数映射表
# app/domain/mds/building.py (BPL)
class Building(AggregateRoot):
building_id: EntityId # BLD-xxx
estate_name: str # 屋苑名称
address: Address # 结构化地址
building_type: BuildingType # 公屋/居屋/私楼/村屋/商厦/工业/工厦 + 扩展10类
floor_factor: Factor # 楼层系数
has_elevator: bool
parking_available: bool
access_requirement: str | None # 物业准入要求
verified_by_worker_at: datetime | None # 师傅现场核实时间
# app/domain/mds/worker.py (WPL)
class WorkerProfile(AggregateRoot):
worker_id: EntityId # WKR-xxx
name: str
contact: Contact
level: WorkerLevel # L1 初级 / L2 中级 / L3 高级
skills: list[CategorySkill] # 技能矩阵:品类 + 能力评级
onboarding_date: date
total_orders: int
avg_nps: float
monthly_load: Decimal # 当月负载(用于派单平衡)
status: WorkerStatus # AVAILABLE / BUSY / SUSPENDED
tags: list[TagRef] # TSMM 标签引用
# app/domain/mds/customer.py (CPL)
class CustomerProfile(AggregateRoot):
customer_id: EntityId # CUS-xxx
name: str
contact: Contact
address: Address | None
first_lead_source: LeadSource
total_orders: int
lifetime_value: Money
last_nps: int | None
tags: list[TagRef] # 五维标签:价值/品类/推荐/复购/风险
trust_badges: list[TrustBadge]
referrer_id: EntityId | None # 推荐关系链
status: CustomerStatus
# TSMM 标签体系元模型
class TagDefinition(AggregateRoot):
tag_code: str # 全局唯一编码 VAL_HIGH_001
category: TagCategory # 基础属性 / 能力画像 / 场景适配
applicable_entity: TagEntity # WKR / CUS / BLD / CAT
display_name: str
data_type: TagDataType # ENUM / NUMBER / TEXT / BOOLEAN
enum_options: list[str] | None
privacy_level: PrivacyLevel # PDPO 合规级别:公开/内部/敏感/绝密
is_manual: bool # True=人工打标 / False=自动打标
auto_rule: str | None # 自动打标规则(DSL 表达式)
2.6 OPS 运营域模型¶
对应 OPS-D V1.1:6 类获客场景、7 组销售物料、口碑闭环
# app/domain/ops/lead.py
class MarketingLead(AggregateRoot):
lead_id: EntityId
scene: AcquisitionScene # SCENE01-SCENE06
source_detail: str
contact: Contact
demand_brief: str
customer_tags_suggested: list[str]
converted_order_id: EntityId | None
status: LeadStatus # NEW / CONTACTED / CONVERTED / LOST
# app/domain/ops/referral.py
class ReferralRelation(AggregateRoot):
"""推荐关系链,OPS 口碑营销核心"""
referrer_id: EntityId # → CPL 推荐人
referee_id: EntityId # → CPL 被推荐人
source_order_id: EntityId | None
reward_amount: Money | None
reward_status: RewardStatus # PENDING / PAID / EXPIRED
created_at: datetime
# 口碑素材(CPT L8 NPS ≥ 9 自动触发)
class WordOfMouthAsset(AggregateRoot):
asset_id: EntityId
order_id: EntityId
customer_id: EntityId
nps: int
testimonial_text: str | None
photo_urls: list[str]
status: AssetStatus # DRAFT / CUSTOMER_APPROVED / PUBLISHED
2.7 领域事件与跨域协同¶
# app/domain/shared/events.py
class DomainEvent:
event_id: str
occurred_at: datetime
class QuoteConfirmed(DomainEvent): # QSV → CPT
quote_id: EntityId; order_id: EntityId; amount: Money
class WorkerAssignedEvent(DomainEvent): # CPT → DSP
worker_id: EntityId; order_id: EntityId; scheduled_at: datetime
class NPSCollectedEvent(DomainEvent): # CPT → OPS + MDS
order_id: EntityId; customer_id: EntityId; nps: int
class OrderCompletedEvent(DomainEvent): # CPT → QSV + MDS
order_id: EntityId; actual_hours: Decimal; final_amount: Money
跨域协同采用 "最终一致性 + 事件驱动":事件发布 → Outbox 模式 → 消息总线(Redis Stream)→ 各子域消费处理。
三、REST API 设计(Interfaces Layer)¶
3.1 API 总体规范¶
| 项 | 规范 |
|---|---|
| Base URL | /api/v1 |
| 版本 | URL 路径版本(/v1, /v2),不使用 Header 版本 |
| 认证 | Authorization: Bearer <JWT>,JWT 内含 user_id + roles + tenant_id |
| 响应包装 | {"code": 0, "msg": "ok", "data": {...}, "trace_id": "..."} |
| 错误码 | 4 位数字:1xxx 认证/权限、2xxx 参数校验、3xxx 业务逻辑、4xxx 资源未找到、5xxx 系统错误 |
| 分页 | ?page=1&page_size=20,响应 {total, items} |
| 过滤 | ?field__op=value(op: eq/ne/gt/ge/lt/le/in/contains) |
| 幂等 | 写接口支持 Idempotency-Key Header,24 小时内重复请求返回原响应 |
3.2 QSV 报价域 API¶
POST /api/v1/qsv/quotes 创建报价(触发 QMD 公式计算)
GET /api/v1/qsv/quotes/{quote_id} 查询报价详情
GET /api/v1/qsv/quotes 报价列表(按状态/订单/客户过滤)
POST /api/v1/qsv/quotes/{id}/confirm 确认报价
POST /api/v1/qsv/quotes/{id}/reject 拒绝报价
POST /api/v1/qsv/calculator/preview 报价预览(不存库,仅前端试算)
GET /api/v1/qsv/params/latest 获取最新 QMD 参数版本号(缓存校验用)
POST /api/v1/qsv/quotes 请求/响应示例:
// Request
{
"order_id": "ORD-01AN4Z07BY79KA1307SR9X4MV3",
"category_id": "CAT-01",
"customer_id": "CUS-00012",
"building_id": "BLD-00038",
"worker_level": "L2",
"product_params": {"size_m2": 3.5, "drawers": 6},
"extra_items": [{"name": "拆旧衣柜", "amount": 280}]
}
// Response 200
{
"code": 0,
"data": {
"quote_id": "QTE-01AN4Z2X8F24K5TSZJJWABV6DY",
"subtotal": "HKD 1,320.00",
"final_amount": "HKD 1,200.00",
"applied_cap_price": true,
"detail_json": {"factors": [...], "items": [...]}
}
}
3.3 CPT 订单跟踪域 API¶
POST /api/v1/cpt/orders L1 新建线索订单
GET /api/v1/cpt/orders/{id} 订单详情(全阶段)
GET /api/v1/cpt/orders 订单列表(看板/筛选/分页)
PATCH /api/v1/cpt/orders/{id}/stage 阶段流转(L1→L2、L2→L3 ...)
POST /api/v1/cpt/orders/{id}/exception 标记异常(进入异常池)
POST /api/v1/cpt/orders/{id}/recover 异常恢复(返回指定阶段)
# —— 分阶段专属接口 ——
POST /api/v1/cpt/orders/{id}/l6/record L6 提交安装记录(含 6S 节点校验)
POST /api/v1/cpt/orders/{id}/l7/accept L7 提交验收结果 + 签字照
POST /api/v1/cpt/orders/{id}/l8/followup L8 提交 NPS + 回访备注
# —— 统计看板 ——
GET /api/v1/cpt/dashboard/stage_counts 各阶段订单数量(看板)
GET /api/v1/cpt/dashboard/funnel 转化漏斗(L1→L8 转化率)
GET /api/v1/cpt/dashboard/worker_load 师傅负载(派单辅助)
3.4 MDS 主数据域 API¶
# WPL 师傅库
POST /api/v1/mds/workers 新建师傅档案
GET /api/v1/mds/workers/{id}
GET /api/v1/mds/workers
PATCH /api/v1/mds/workers/{id}
POST /api/v1/mds/workers/{id}/tags 批量打/撤标签
# CPL 客户库
POST /api/v1/mds/customers
GET /api/v1/mds/customers/{id}
GET /api/v1/mds/customers 高级查询(五维标签组合过滤)
# SCL 品类库
GET /api/v1/mds/categories 全部品类(报价/派单下拉)
GET /api/v1/mds/categories/{id}/params 品类产品参数定义
# BPL 楼宇库
POST /api/v1/mds/buildings
GET /api/v1/mds/buildings/autocomplete 地址输入联想
# TSMM 标签体系
GET /api/v1/mds/tags/definitions 标签定义(按实体分类)
POST /api/v1/mds/tags/batch-apply 批量打标(运营工具)
3.5 OPS 运营域 API¶
POST /api/v1/ops/leads 录入获客线索(6 场景)
GET /api/v1/ops/leads 线索池(SCENE01-SCENE06 分池)
POST /api/v1/ops/leads/{id}/convert 线索→订单(转 CPT L1)
GET /api/v1/ops/materials 销售物料(OPS-M 7 组 32 项)
GET /api/v1/ops/referrals/network 推荐关系网络图谱(可视化)
GET /api/v1/ops/wom/assets 口碑素材池(审批流)
POST /api/v1/ops/wom/assets/{id}/publish 客户确认后发布
3.6 认证与用户 API(含移动端认证)¶
POST /api/v1/auth/login 登录(账密/WhatsApp OTP 二选一)
POST /api/v1/auth/refresh Token 刷新(含 refresh token 轮换)
POST /api/v1/auth/logout 登出(吊销 refresh token)
POST /api/v1/auth/biometric/setup 生物识别绑定(移动端)
GET /api/v1/auth/me 当前用户信息 + 权限
GET /api/v1/users 用户列表(管理)
POST /api/v1/roles 角色配置
POST /api/v1/permissions/batch-grant 批量授权(RBAC 管理)
3.7 CST 客户端 API(V2.0 新增)¶
POST /api/v1/cst/auth/otp-send WhatsApp OTP 发送
POST /api/v1/cst/auth/otp-verify OTP 验证登录
GET /api/v1/cst/categories 品类浏览(8 大品类 + 子类)
POST /api/v1/cst/inquiries 提交询价(自动触发 QSV 预览)
GET /api/v1/cst/orders 我的订单列表
GET /api/v1/cst/orders/{id} 订单详情与 L1-L8 进度
POST /api/v1/cst/orders/{id}/review 提交评价(L8 NPS + 文字)
POST /api/v1/cst/referrals/share 生成推荐码/链接
GET /api/v1/cst/referrals/rewards 我的推荐奖励记录
GET /api/v1/cst/profile 个人档案
PATCH /api/v1/cst/profile 更新档案
POST /api/v1/cst/devices 注册推送 token(多设备)
3.8 推送通知 API(V2.0 新增)¶
POST /api/v1/notifications/preferences 通知偏好设置(按类型开关)
GET /api/v1/notifications/unread 未读通知列表
POST /api/v1/notifications/{id}/read 标记已读
API 总数:原 52 端点 - 4(飞书 webhook 移除)+ 17(CST 12 + 认证 3 + 推送 2)= 65 端点。详见 DIP1-API V2.0 OpenAPI 规格。
四、数据库表结构设计¶
4.1 设计原则¶
- 命名:表名
snake_case复数(如qsv_quotes),主键id(ULID 26 字符 char),外键xxx_id - 审计列:每张表含
created_at、updated_at、created_by、updated_by - 软删除:用
deleted_at TIMESTAMPTZ NULL实现,查询时自动过滤 - 乐观锁:用
version INT DEFAULT 0,并发写WHERE version = $ver失败抛异常 - 索引:外键自动建 btree;高频查询组合建 GIN/BRIN(日期、数组)
- JSONB:灵活字段(如
detail_json)用 JSONB,避免 EAV
4.2 QSV 域表¶
-- 报价主表
CREATE TABLE qsv_quotes (
id CHAR(26) PRIMARY KEY, -- ULID
order_id CHAR(26) REFERENCES cpt_orders(id),
customer_id CHAR(26) NOT NULL REFERENCES mds_customers(id),
category_id CHAR(26) NOT NULL REFERENCES mds_categories(id),
building_id CHAR(26) REFERENCES mds_buildings(id),
base_fee_amount NUMERIC(12,2) NOT NULL,
complexity DECIMAL(4,3) NOT NULL,
building_factor DECIMAL(4,3) NOT NULL,
worker_level_factor DECIMAL(4,3) NOT NULL,
floor_factor DECIMAL(4,3) NOT NULL,
area_factor DECIMAL(4,3) NOT NULL,
distance_factor DECIMAL(4,3) NOT NULL,
peak_factor DECIMAL(4,3) NOT NULL,
extra_items_json JSONB NOT NULL DEFAULT '[]',
discount_amount NUMERIC(12,2) NOT NULL DEFAULT 0,
cap_price_amount NUMERIC(12,2) NOT NULL,
subtotal NUMERIC(12,2) NOT NULL,
final_amount NUMERIC(12,2) NOT NULL,
status VARCHAR(16) NOT NULL, -- DRAFT/CONFIRMED/REJECTED/EXPIRED
version INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by CHAR(26) NOT NULL,
deleted_at TIMESTAMPTZ NULL
);
CREATE INDEX idx_qsv_quotes_order ON qsv_quotes(order_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_qsv_quotes_customer ON qsv_quotes(customer_id, created_at DESC);
CREATE INDEX idx_qsv_quotes_status ON qsv_quotes(status) WHERE deleted_at IS NULL;
-- QMD 参数快照表(每次报价时的参数版本,避免未来参数变动影响历史)
CREATE TABLE qsv_param_snapshots (
id CHAR(26) PRIMARY KEY,
quote_id CHAR(26) NOT NULL UNIQUE REFERENCES qsv_quotes(id),
params_json JSONB NOT NULL, -- 完整 8 大类 + 32 因子快照
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
4.3 CPT 域表¶
-- 订单主表(8 阶段聚合根)
CREATE TABLE cpt_orders (
id CHAR(26) PRIMARY KEY,
display_no VARCHAR(32) UNIQUE NOT NULL, -- 对外展示 ORD-YYYYMMDD-NNN
stage VARCHAR(16) NOT NULL, -- L1..L8/EXCEPTION/DONE
status VARCHAR(16) NOT NULL, -- IN_PROGRESS/COMPLETED/CANCELLED
pool VARCHAR(32) NOT NULL, -- 跟踪池编码
customer_id CHAR(26) NOT NULL REFERENCES mds_customers(id),
building_id CHAR(26) REFERENCES mds_buildings(id),
worker_id CHAR(26) REFERENCES mds_workers(id),
category_id CHAR(26) REFERENCES mds_categories(id),
active_quote_id CHAR(26) REFERENCES qsv_quotes(id),
-- L1~L8 业务字段(结构化拆分,JSONB 存弹性字段)
lead_json JSONB NOT NULL DEFAULT '{}',
demand_json JSONB NOT NULL DEFAULT '{}',
contract_json JSONB,
assignment_json JSONB,
delivery_json JSONB,
install_json JSONB, -- L6 含 6S 节点
acceptance_json JSONB,
followup_json JSONB,
--
exception_reason TEXT,
version INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ NULL
);
CREATE INDEX idx_cpt_orders_stage_status ON cpt_orders(stage, status) WHERE deleted_at IS NULL;
CREATE INDEX idx_cpt_orders_worker ON cpt_orders(worker_id, created_at DESC);
CREATE INDEX idx_cpt_orders_customer ON cpt_orders(customer_id, created_at DESC);
CREATE INDEX idx_cpt_orders_gin_lead ON cpt_orders USING GIN(lead_json);
-- 订单阶段流转审计表(WLG 追溯用)
CREATE TABLE cpt_stage_transitions (
id CHAR(26) PRIMARY KEY,
order_id CHAR(26) NOT NULL REFERENCES cpt_orders(id),
from_stage VARCHAR(16),
to_stage VARCHAR(16) NOT NULL,
actor_id CHAR(26) NOT NULL,
reason TEXT,
context_json JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_cpt_trans_order ON cpt_stage_transitions(order_id, created_at);
-- L6 附件表(多对一)
CREATE TABLE cpt_install_attachments (
id CHAR(26) PRIMARY KEY,
order_id CHAR(26) NOT NULL REFERENCES cpt_orders(id),
photo_type VARCHAR(16) NOT NULL, -- BEFORE/IN_PROGRESS/AFTER/SIGNATURE
r2_key VARCHAR(255) NOT NULL, -- Cloudflare R2 object key
width INT,
height INT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
4.4 MDS 域表¶
-- SCL 品类
CREATE TABLE mds_categories (
id CHAR(26) PRIMARY KEY,
code VARCHAR(32) UNIQUE NOT NULL,
name VARCHAR(64) NOT NULL,
sub_categories JSONB NOT NULL DEFAULT '[]',
base_fee_amount NUMERIC(12,2) NOT NULL,
base_work_hours DECIMAL(4,1) NOT NULL,
cap_price NUMERIC(12,2) NOT NULL,
skill_required SMALLINT NOT NULL, -- 1=L1/2=L2/3=L3
product_params JSONB NOT NULL DEFAULT '[]',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ NULL
);
-- BPL 楼宇
CREATE TABLE mds_buildings (
id CHAR(26) PRIMARY KEY,
estate_name VARCHAR(128) NOT NULL,
address_district VARCHAR(32),
address_detail TEXT NOT NULL,
building_type VARCHAR(32) NOT NULL,
floor_factor DECIMAL(4,3) NOT NULL DEFAULT 1.0,
has_elevator BOOLEAN NOT NULL DEFAULT TRUE,
parking_note TEXT,
access_requirement TEXT,
verified_by_id CHAR(26) REFERENCES mds_workers(id),
verified_at TIMESTAMPTZ,
tags_json JSONB NOT NULL DEFAULT '[]',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ NULL
);
CREATE INDEX idx_mds_buildings_estate ON mds_buildings(estate_name) WHERE deleted_at IS NULL;
-- WPL 师傅
CREATE TABLE mds_workers (
id CHAR(26) PRIMARY KEY,
name VARCHAR(64) NOT NULL,
whatsapp VARCHAR(32),
phone VARCHAR(32),
level SMALLINT NOT NULL DEFAULT 1,
skills_json JSONB NOT NULL DEFAULT '[]',
onboarding_date DATE NOT NULL,
total_orders INT NOT NULL DEFAULT 0,
avg_nps DECIMAL(3,1),
monthly_load_hours DECIMAL(5,1),
status VARCHAR(16) NOT NULL DEFAULT 'AVAILABLE',
tags_json JSONB NOT NULL DEFAULT '[]',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ NULL
);
-- CPL 客户
CREATE TABLE mds_customers (
id CHAR(26) PRIMARY KEY,
name VARCHAR(64) NOT NULL,
contact_json JSONB NOT NULL, -- whatsapp/phone/email
address_json JSONB,
first_source VARCHAR(32),
total_orders INT NOT NULL DEFAULT 0,
ltv_amount NUMERIC(14,2) NOT NULL DEFAULT 0,
last_nps SMALLINT,
tags_json JSONB NOT NULL DEFAULT '[]',
badges_json JSONB NOT NULL DEFAULT '[]',
referrer_id CHAR(26) REFERENCES mds_customers(id),
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ NULL
);
CREATE INDEX idx_mds_customers_referrer ON mds_customers(referrer_id) WHERE deleted_at IS NULL;
-- TSMM 标签定义
CREATE TABLE mds_tag_definitions (
tag_code VARCHAR(32) PRIMARY KEY,
category VARCHAR(32) NOT NULL, -- BASE/ABILITY/SCENARIO
applicable_entity VARCHAR(8) NOT NULL, -- WKR/CUS/BLD/CAT
display_name VARCHAR(64) NOT NULL,
data_type VARCHAR(8) NOT NULL, -- ENUM/NUM/TEXT/BOOL
enum_options_json JSONB,
privacy_level SMALLINT NOT NULL, -- 1/2/3/4 PDPO
is_manual BOOLEAN NOT NULL DEFAULT TRUE,
auto_rule_dsl TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
4.5 OPS 域表 + RBAC 权限表¶
-- OPS 线索
CREATE TABLE ops_leads (
id CHAR(26) PRIMARY KEY,
scene VARCHAR(8) NOT NULL, -- S01-S06
source_detail VARCHAR(128),
contact_json JSONB NOT NULL,
demand_brief TEXT,
suggested_tags VARCHAR(32)[] NOT NULL DEFAULT '{}',
converted_order_id CHAR(26) REFERENCES cpt_orders(id),
status VARCHAR(16) NOT NULL DEFAULT 'NEW',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ NULL
);
-- OPS 推荐关系
CREATE TABLE ops_referrals (
id CHAR(26) PRIMARY KEY,
referrer_id CHAR(26) NOT NULL REFERENCES mds_customers(id),
referee_id CHAR(26) NOT NULL REFERENCES mds_customers(id),
source_order_id CHAR(26) REFERENCES cpt_orders(id),
reward_amount NUMERIC(12,2),
reward_status VARCHAR(16) NOT NULL DEFAULT 'PENDING',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- === RBAC 权限表(跨域通用)===
CREATE TABLE auth_users (
id CHAR(26) PRIMARY KEY,
username VARCHAR(64) UNIQUE NOT NULL,
password_hash VARCHAR(255),
feishu_open_id VARCHAR(64) UNIQUE, -- 飞书免登
display_name VARCHAR(64) NOT NULL,
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE auth_roles (
id CHAR(26) PRIMARY KEY,
role_code VARCHAR(16) UNIQUE NOT NULL, -- SPO/BO/PO/TL/OL/ML/FL
name VARCHAR(32) NOT NULL,
description TEXT
);
CREATE TABLE auth_permissions (
id CHAR(26) PRIMARY KEY,
perm_code VARCHAR(64) UNIQUE NOT NULL, -- cpt:order:view / qsv:quote:create ...
resource VARCHAR(32) NOT NULL,
action VARCHAR(16) NOT NULL,
description TEXT
);
CREATE TABLE auth_user_roles (
user_id CHAR(26) REFERENCES auth_users(id),
role_id CHAR(26) REFERENCES auth_roles(id),
PRIMARY KEY (user_id, role_id)
);
CREATE TABLE auth_role_permissions (
role_id CHAR(26) REFERENCES auth_roles(id),
perm_id CHAR(26) REFERENCES auth_permissions(id),
PRIMARY KEY (role_id, perm_id)
);
-- 行级策略表(行级权限控制:师傅只能看自己的订单等)
CREATE TABLE auth_row_policies (
id CHAR(26) PRIMARY KEY,
role_id CHAR(26) NOT NULL REFERENCES auth_roles(id),
resource VARCHAR(32) NOT NULL, -- cpt_orders/mds_workers ...
condition_sql TEXT NOT NULL, -- "worker_id = current_setting('app.user_id')"
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
五、RBAC 权限模型¶
5.1 角色-权限矩阵(对齐 PJM RACI)¶
| 权限编码 | 资源 | 操作 | SPO | BO | PO(TL) | OL | ML | FL(师傅) |
|---|---|---|---|---|---|---|---|---|
cpt:order:view_all |
订单 | 查看全部 | ✅ | ✅ | ✅ | ✅ | ⬜(仅线索) | ❌ |
cpt:order:view_self |
订单 | 查看自己的 | — | — | — | — | — | ✅(仅 worker_id 匹配) |
cpt:order:create |
订单 | L1 创建 | ❌ | ⬜ | ✅ | ✅ | ✅ | ❌ |
cpt:order:l6_record |
订单 | L6 填安装记录 | ❌ | ❌ | ✅ | ✅ | ❌ | ✅(仅自己被派单) |
cpt:order:l7_accept |
订单 | L7 验收 | ❌ | ❌ | ✅ | ✅ | ❌ | ✅(仅自己被派单) |
qsv:quote:create |
报价 | 创建/确认 | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
qsv:quote:view |
报价 | 查看报价 | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
qsv:params:edit |
QMD 参数 | 编辑校准 | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
mds:worker:manage |
WPL | 增删改师傅 | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
mds:worker:view_all |
WPL | 查看全部师傅 | ✅ | ✅ | ✅ | ✅ | ✅ | ⬜(仅基本信息) |
mds:customer:manage |
CPL | 客户档案管理 | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ |
mds:tag:batch_apply |
标签 | 批量打标 | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ |
ops:lead:manage |
线索 | 线索录入/跟进 | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ |
ops:referral:payout |
推荐 | 奖励发放 | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
auth:role:manage |
系统 | 角色权限管理 | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
sys:audit:view |
系统 | 审计日志 | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
5.2 行级权限(RLS 策略)¶
采用 PostgreSQL Row Level Security 实现物理级数据隔离,示例:
-- 启用 RLS
ALTER TABLE cpt_orders ENABLE ROW LEVEL SECURITY;
-- FL 师傅:仅能查看/修改派给自己的订单
CREATE POLICY fl_worker_see_own ON cpt_orders
FOR ALL TO app_fl
USING (worker_id = current_setting('app.current_user_id')::char(26))
WITH CHECK (worker_id = current_setting('app.current_user_id')::char(26));
-- OL 运营:可见全部订单,但不能改 QMD 参数(列级由 GRANT 控制)
CREATE POLICY ol_see_all ON cpt_orders FOR ALL TO app_ol USING (true);
REVOKE UPDATE(base_fee_amount, cap_price_amount) ON qsv_quotes FROM app_ol;
六、阶段 2 交付物与里程碑(V2.0 重估)¶
6.1 后端交付物(49 人天)¶
| 交付物 | 代码位置 | 工作量(人天) |
|---|---|---|
| 领域层模型(QSV/CPT/MDS/OPS/DSP/PAY/Shared) | domain/*/*.py |
8 |
| 应用层用例(Use Cases + DTO) | application/*/*.py |
6 |
| 基础设施层(仓储/消息/AI适配) | infrastructure/*/*.py |
6 |
| REST API 接口(FastAPI Routers,65 端点) | interfaces/api/*.py |
6 |
| 数据库迁移脚本(Alembic 0001 + 0002) | alembic/versions/*.py |
3 |
| RBAC 权限实现(FastAPI Depends + PG RLS) | infrastructure/auth/ |
3 |
| 推送服务(Expo Notifications + 模板渲染 + 8 类触发) | infrastructure/push/ |
2 |
| 移动端认证(refresh token 轮换 + 设备管理 + 生物识别) | infrastructure/auth/mobile/ |
1 |
| 单元测试(领域层 + 应用层) | tests/domain/ + tests/application/ |
8 |
| 集成测试(API + DB) | tests/interfaces/ |
6 |
6.2 前端交付物(42 人天)¶
| 交付物 | 代码位置 | 工作量(人天) |
|---|---|---|
| 运营后台 opr-admin(Next.js Web,15 页) | frontend/apps/opr-admin/ |
15 |
| 师傅端 wkr-app(React Native,8+ 页) | frontend/apps/wkr-app/ |
15 |
| 客户端 cst-app(React Native,8 页) | frontend/apps/cst-app/ |
12 |
6.3 移动端基建交付物(5 人天)¶
| 交付物 | 代码位置 | 工作量(人天) |
|---|---|---|
| EAS Build 配置 + 双端构建流水线 | eas.json + .github/workflows/ |
1 |
| Expo Updates OTA 热更新配置 | app.json + EAS Update |
1 |
| App Store Connect + Google Play 提审配置 | fastlane/ + 证书管理 |
2 |
| 推送配置(APNs Key + FCM)+ 测试 | Expo Notifications Console | 1 |
6.4 工作量汇总¶
| 类别 | 人天 |
|---|---|
| 后端业务 | 49 |
| 前端三端 | 42 |
| 移动端基建 | 5 |
| 环境基建(Docker + PG + Redis + Expo + EAS) | 9 |
| 合计 | 105 人天(原 85 人天,跳过飞书 12 + 新增客户端/移动基建 32 = +20) |
实施模式:Code Agent 全栈实施后端 + 前端;TL 专注审核与关键决策;日历工期约 3-4 个月(含测试与 UAT)。详见 DIP1-IMP V2.0 实施指南。
附录 B:Alembic 迁移流程¶
# 生成迁移(修改 SQLAlchemy models 后)
alembic revision --autogenerate -m "add cpt_install_attachments table"
# 本地执行
alembic upgrade head
# 生成部署用 SQL(DBA 审核)
alembic upgrade head --sql > migrations/v12.sql
# 回滚(出错时)
alembic downgrade -1
修订记录¶
| 版本 | 日期 | 修订人 | 修订内容 |
|---|---|---|---|
| V1.0 | 2026-08-09 | DT | 初始化:6 子域领域模型 + 52 端点 API + 12+ 表 DDL + RBAC 权限模型 + 阶段1→阶段2 迁移 |
| V2.0 | 2026-08-09 | DT | 原生 App 升级:阶段2定义图移除 Phase1 改为直接实施(Phase0 虚线标注未来按需);设计目标移除飞书迁移,新增移动端原生体验/客户端全流程/推送闭环 3 项;API 新增 §3.7 CST 客户端 12 端点 + §3.8 推送 2 端点 + 移动端认证 3 端点(总计 65 端点);交付物重估为 105 人天(后端49 + 前端42 + 移动基建5 + 环境9) |
本文件为 DIP1 阶段 2 一线操作系统技术设计(DOC-D01-D2 V2.0),前端采用 React Native(师傅端+客户端)+ Next.js(运营后台),与 DIP1-P3 基础设施设计 配套使用。