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
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# 在此处导入所有 ORM 模型,供 Alembic 自动检测
|
||||
from app.models.user import User # noqa: F401
|
||||
from app.models.client import Client # noqa: F401
|
||||
from app.models.crm_business import ( # noqa: F401
|
||||
CustomerLog,
|
||||
CustomerTag,
|
||||
FollowUpToDo,
|
||||
SalesOpportunity,
|
||||
)
|
||||
@@ -0,0 +1,54 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
客户主表模型
|
||||
CRM 的核心实体,所有业务表 (日志/标签/待办/销售机会) 均通过外键关联到此表。
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import String, Text, func
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
|
||||
class Client(Base):
|
||||
"""
|
||||
客户信息表 (clients)
|
||||
记录客户基本信息,作为 CRM 业务数据的核心关联实体。
|
||||
"""
|
||||
|
||||
__tablename__ = "clients"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4,
|
||||
)
|
||||
name: Mapped[str] = mapped_column(
|
||||
String(200), nullable=False, index=True,
|
||||
comment="客户名称 (公司名/个人名)",
|
||||
)
|
||||
contact_person: Mapped[str | None] = mapped_column(
|
||||
String(100), nullable=True,
|
||||
comment="联系人姓名",
|
||||
)
|
||||
phone: Mapped[str | None] = mapped_column(
|
||||
String(30), nullable=True,
|
||||
comment="联系电话",
|
||||
)
|
||||
address: Mapped[str | None] = mapped_column(
|
||||
String(500), nullable=True,
|
||||
comment="地址",
|
||||
)
|
||||
notes: Mapped[str | None] = mapped_column(
|
||||
Text, nullable=True,
|
||||
comment="备注",
|
||||
)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
server_default=func.now(),
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
server_default=func.now(),
|
||||
onupdate=func.now(),
|
||||
)
|
||||
@@ -0,0 +1,132 @@
|
||||
# -*- 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(),
|
||||
)
|
||||
@@ -0,0 +1,46 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
用户 ORM 模型
|
||||
对应数据库 users 表,使用 SQLAlchemy 2.0 Mapped 注解风格。
|
||||
"""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from sqlalchemy import String, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
|
||||
class User(Base):
|
||||
"""用户表 - 存储账号、密码哈希、角色权限"""
|
||||
|
||||
__tablename__ = "users"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
username: Mapped[str] = mapped_column(
|
||||
String(50), unique=True, nullable=False, index=True, comment="登录用户名"
|
||||
)
|
||||
password_hash: Mapped[str] = mapped_column(
|
||||
String(255), nullable=False, comment="bcrypt 哈希密码"
|
||||
)
|
||||
role: Mapped[str] = mapped_column(
|
||||
String(20), nullable=False, default="user", comment="角色: admin / user"
|
||||
)
|
||||
permissions: Mapped[str] = mapped_column(
|
||||
String(200), nullable=False, default="view,edit", comment="逗号分隔权限列表"
|
||||
)
|
||||
is_active: Mapped[bool] = mapped_column(
|
||||
default=True, comment="账户是否启用"
|
||||
)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
server_default=func.now(), comment="创建时间"
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
server_default=func.now(),
|
||||
onupdate=lambda: datetime.now(timezone.utc),
|
||||
comment="最后更新时间",
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<User(id={self.id}, username='{self.username}', role='{self.role}')>"
|
||||
Reference in New Issue
Block a user