跳转至

DIP1 阶段 2 技术设计:一线操作系统

文档编号:DOC-D01-D2 / 简称:DIP1-P2 版本:V2.0 创建日期:2026-08-09 最近更新:2026-08-09 维护人:TL / DT 关联文档TND-EDIP1-ARC V2.0 架构设计QMDCPT-DMDS-DOPS-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 设计原则

  1. 命名:表名 snake_case 复数(如 qsv_quotes),主键 id(ULID 26 字符 char),外键 xxx_id
  2. 审计列:每张表含 created_atupdated_atcreated_byupdated_by
  3. 软删除:用 deleted_at TIMESTAMPTZ NULL 实现,查询时自动过滤
  4. 乐观锁:用 version INT DEFAULT 0,并发写 WHERE version = $ver 失败抛异常
  5. 索引:外键自动建 btree;高频查询组合建 GIN/BRIN(日期、数组)
  6. 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 基础设施设计 配套使用。