Files
hankin 423baff73b v0.1.0: CRM/ERP 系统内测版本 - 安全加固完成
- Docker bridge 网络隔离(8000 端口封死)
- Gunicorn 4 Worker 多进程
- Alembic 数据库迁移基线
- 日志轮转 20m×3
- JWT 密钥 + DB 密码 + CORS 收紧
- 3-2-1 备份链路(NAS + R740-B 冷备)
- 连接池 pool_pre_ping + pool_recycle=3600
2026-03-16 07:31:37 +00:00

133 lines
4.1 KiB
Python

# -*- coding: utf-8 -*-
"""
CRM 业务数据模型
定义客户沟通日志、标签、跟进待办、销售机会四张业务表。
所有主键均为 UUID,与 User/KnowledgeChunk 保持一致的 ID 策略。
"""
import uuid
from datetime import datetime
from sqlalchemy import ForeignKey, String, Text, Numeric, func
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
class CustomerLog(Base):
"""
客户沟通日志表
记录每次与客户的沟通内容(电话/拜访/微信等),
新增日志时会触发后台 AI 任务自动提取标签和待办。
"""
__tablename__ = "customer_logs"
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4,
)
customer_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("clients.id", ondelete="CASCADE"),
nullable=False,
index=True,
comment="关联客户 ID",
)
content: Mapped[str] = mapped_column(
Text, nullable=False, comment="沟通日志内容",
)
created_at: Mapped[datetime] = mapped_column(
server_default=func.now(), comment="记录时间",
)
class CustomerTag(Base):
"""
客户标签表
由 AI 从沟通日志中自动提取,也支持手动添加。
同一客户下的标签名唯一(通过业务逻辑控制去重)。
"""
__tablename__ = "customer_tags"
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4,
)
customer_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("clients.id", ondelete="CASCADE"),
nullable=False,
index=True,
comment="关联客户 ID",
)
tag_name: Mapped[str] = mapped_column(
String(100), nullable=False, comment="标签名称,如'价格敏感''决策周期长'",
)
created_at: Mapped[datetime] = mapped_column(
server_default=func.now(),
)
class FollowUpToDo(Base):
"""
跟进待办表
由 AI 根据沟通日志自动生成下一步行动建议,
也可由用户手动创建。status 为简单的二态: pending / done。
"""
__tablename__ = "follow_up_todos"
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4,
)
customer_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("clients.id", ondelete="CASCADE"),
nullable=False,
index=True,
comment="关联客户 ID",
)
task_desc: Mapped[str] = mapped_column(
Text, nullable=False, comment="待办任务描述",
)
status: Mapped[str] = mapped_column(
String(20), nullable=False, default="pending",
comment="状态: pending(待处理) / done(已完成)",
)
created_at: Mapped[datetime] = mapped_column(
server_default=func.now(),
)
class SalesOpportunity(Base):
"""
销售机会表
跟踪每个客户的销售漏斗阶段和金额,用于经营看板和复盘报告。
stage 四阶段: 意向 → 谈判 → 成交 → 流失
"""
__tablename__ = "sales_opportunities"
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4,
)
customer_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("clients.id", ondelete="CASCADE"),
nullable=False,
index=True,
comment="关联客户 ID",
)
amount: Mapped[float] = mapped_column(
Numeric(12, 2), nullable=False, default=0,
comment="预估/实际金额 (元)",
)
stage: Mapped[str] = mapped_column(
String(20), nullable=False, default="意向",
comment="漏斗阶段: 意向 / 谈判 / 成交 / 流失",
)
created_at: Mapped[datetime] = mapped_column(
server_default=func.now(),
)