v0.2.0: CRM/ERP 系统升级 - 清理 .gitignore 并移除误提交的 venv/env/db 文件

- 更新 .gitignore:全面覆盖环境变量、数据库、日志、缓存、上传文件
- 移除误跟踪的 server/venv/、crm_data.db、.env 文件
- 新增 server/.env.example 模板
- 新增合同管理、利润核算、AI教练等功能模块
- 新增 Playwright e2e 测试套件
- 前后端多项功能升级和 bug 修复
This commit is contained in:
hankin
2026-05-11 07:24:19 +00:00
parent 0f4c6b7924
commit 815cbf9d8c
2526 changed files with 11875 additions and 804148 deletions
+99 -5
View File
@@ -22,6 +22,7 @@ async def create_log(
customer_id: str | None = None,
contact_ids: list[str] | None = None,
log_date: date | None = None,
company_ids: list[uuid.UUID] | None = None,
) -> dict:
"""创建销售日志"""
log = SalesLog(
@@ -30,6 +31,7 @@ async def create_log(
contact_ids=contact_ids or [],
content=content,
log_date=log_date or date.today(),
involved_company_ids=company_ids or [],
)
db.add(log)
await db.commit()
@@ -46,9 +48,17 @@ async def list_logs(
user_id: str | None = None,
start_date: str | None = None,
end_date: str | None = None,
company_id: uuid.UUID | None = None,
) -> dict:
"""查询销售日志列表"""
"""查询销售日志列表(按 involved_company_ids 包含过滤)"""
from sqlalchemy.orm import aliased
from app.models.crm import CrmCustomer
from app.models.sys import SysUser
conditions = [SalesLog.is_deleted.is_(False)]
if company_id:
# ARRAY contains: 过滤涉及当前公司的日志
conditions.append(SalesLog.involved_company_ids.any(company_id))
# 数据权限
if user.data_scope == "self":
@@ -69,24 +79,107 @@ async def list_logs(
count_stmt = select(func.count()).select_from(SalesLog).where(where)
total = (await db.execute(count_stmt)).scalar() or 0
# data
# data — LEFT JOIN customer + user to get names
Author = aliased(SysUser)
stmt = (
select(SalesLog)
select(
SalesLog,
CrmCustomer.name.label("customer_name"),
Author.real_name.label("author_name"),
)
.outerjoin(CrmCustomer, SalesLog.customer_id == CrmCustomer.id)
.outerjoin(Author, SalesLog.salesperson_id == Author.id)
.where(where)
.order_by(desc(SalesLog.created_at))
.offset((page - 1) * size)
.limit(size)
)
rows = (await db.execute(stmt)).scalars().all()
rows = (await db.execute(stmt)).all()
items = []
for log, cust_name, auth_name in rows:
d = _to_dict(log)
d["customer_name"] = cust_name
d["author_name"] = auth_name
items.append(d)
return {
"total": total,
"page": page,
"size": size,
"items": [_to_dict(r) for r in rows],
"items": items,
}
async def update_log(
db: AsyncSession,
user: CurrentUserPayload,
log_id: uuid.UUID,
content: str | None = None,
customer_id: str | None = None,
contact_ids: list[str] | None = None,
log_date: str | None = None,
company_id: uuid.UUID | None = None,
) -> dict:
"""编辑销售日志 — 员工只能改自己的,管理员可改所有"""
from app.models.crm import CrmCustomer
from app.models.sys import SysUserCompany
log = await db.get(SalesLog, log_id)
if not log or log.is_deleted:
raise Exception("日志不存在")
# 权限检查
if user.data_scope != "all" and log.salesperson_id != user.user_id:
raise Exception("您无权编辑此日志")
if content is not None:
log.content = content
if contact_ids is not None:
log.contact_ids = contact_ids
if log_date is not None:
log.log_date = date.fromisoformat(log_date)
# 更新客户关联 + 自动重算 involved_company_ids
if customer_id is not None:
log.customer_id = uuid.UUID(customer_id) if customer_id else None
# 重新关联公司
resolved = set(log.involved_company_ids or [])
if company_id:
resolved.add(company_id)
if customer_id:
cust = await db.get(CrmCustomer, uuid.UUID(customer_id))
if cust and cust.owner_id:
stmt = select(SysUserCompany.company_id).where(
SysUserCompany.user_id == cust.owner_id
)
rows = (await db.execute(stmt)).scalars().all()
for cid in rows:
resolved.add(cid)
log.involved_company_ids = list(resolved)
await db.commit()
await db.refresh(log)
return _to_dict(log)
async def delete_log(
db: AsyncSession,
user: CurrentUserPayload,
log_id: uuid.UUID,
) -> None:
"""软删除销售日志 — 员工只能删自己的,管理员可删所有"""
log = await db.get(SalesLog, log_id)
if not log or log.is_deleted:
raise Exception("日志不存在")
if user.data_scope != "all" and log.salesperson_id != user.user_id:
raise Exception("您无权删除此日志")
log.is_deleted = True
await db.commit()
async def trigger_persona_workflow(
log_id: uuid.UUID,
customer_id: uuid.UUID,
@@ -157,6 +250,7 @@ def _to_dict(log: SalesLog) -> dict:
"salesperson_id": str(log.salesperson_id),
"customer_id": str(log.customer_id) if log.customer_id else None,
"contact_ids": log.contact_ids or [],
"involved_company_ids": [str(c) for c in (log.involved_company_ids or [])],
"content": log.content,
"log_date": log.log_date.isoformat() if log.log_date else None,
"ai_processed": log.ai_processed,