```
feat: 添加公司AI管理平台基础架构 添加了完整的FastAPI后端项目结构,包括: - 环境配置文件(.env.example)和项目说明文档(README.md、AGENTS.md) - Dockerfile用于容器化部署 - 核心基础设施:配置管理、数据库连接、调度器、安全认证 - 模块化设计:AI代理、审批流程、审计日志、业务台账等功能模块 - 支持多数据库连接(主库和遗留系统只读库) - AI适配器支持OpenClaw、Hermes、OpenAI兼容接口 - 飞书集成、报表生成、风险监控等企业级功能 - 完整的依赖管理和测试指南 ```
This commit is contained in:
138
app/modules/business/service.py
Normal file
138
app/modules/business/service.py
Normal file
@@ -0,0 +1,138 @@
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy import Select, func, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.modules.audit.schemas import AuditLogCreate
|
||||
from app.modules.audit.service import AuditService
|
||||
from app.modules.approvals.service import ApprovalService
|
||||
from app.modules.business.registry import HIGH_RISK_DOMAINS, get_domain_model
|
||||
|
||||
|
||||
def serialize_model(record: Any) -> dict[str, Any]:
|
||||
"""Convert a SQLAlchemy model instance into a JSON-friendly dictionary."""
|
||||
|
||||
data: dict[str, Any] = {}
|
||||
for column in record.__table__.columns:
|
||||
value = getattr(record, column.name)
|
||||
if isinstance(value, (datetime, date)):
|
||||
data[column.name] = value.isoformat()
|
||||
elif isinstance(value, Decimal):
|
||||
data[column.name] = float(value)
|
||||
else:
|
||||
data[column.name] = value
|
||||
return data
|
||||
|
||||
|
||||
class BusinessService:
|
||||
"""Manage generic CRUD operations across registered business domains."""
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
self.audit = AuditService(db)
|
||||
|
||||
def list_records(
|
||||
self,
|
||||
domain: str,
|
||||
limit: int = 50,
|
||||
offset: int = 0,
|
||||
status_filter: str | None = None,
|
||||
) -> tuple[int, list[dict[str, Any]]]:
|
||||
model = get_domain_model(domain)
|
||||
stmt: Select = select(model)
|
||||
count_stmt = select(func.count()).select_from(model)
|
||||
if status_filter and hasattr(model, "status"):
|
||||
stmt = stmt.where(model.status == status_filter)
|
||||
count_stmt = count_stmt.where(model.status == status_filter)
|
||||
stmt = stmt.order_by(model.id.desc()).limit(min(limit, 500)).offset(max(offset, 0))
|
||||
total = int(self.db.execute(count_stmt).scalar() or 0)
|
||||
return total, [serialize_model(item) for item in self.db.execute(stmt).scalars()]
|
||||
|
||||
def get_record(self, domain: str, record_id: int) -> dict[str, Any]:
|
||||
model = get_domain_model(domain)
|
||||
record = self.db.get(model, record_id)
|
||||
if record is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Record not found")
|
||||
return serialize_model(record)
|
||||
|
||||
def create_record(
|
||||
self,
|
||||
domain: str,
|
||||
data: dict[str, Any],
|
||||
actor: str = "api",
|
||||
) -> dict[str, Any]:
|
||||
model = get_domain_model(domain)
|
||||
allowed = {column.name for column in model.__table__.columns if column.name != "id"}
|
||||
payload = {key: value for key, value in data.items() if key in allowed}
|
||||
record = model(**payload)
|
||||
self.db.add(record)
|
||||
self.db.commit()
|
||||
self.db.refresh(record)
|
||||
result = serialize_model(record)
|
||||
self.audit.log(
|
||||
AuditLogCreate(
|
||||
actor=actor,
|
||||
source="api",
|
||||
action=f"create:{domain}",
|
||||
target_type=domain,
|
||||
target_id=str(record.id),
|
||||
request_payload=data,
|
||||
response_payload=result,
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
def update_record(
|
||||
self,
|
||||
domain: str,
|
||||
record_id: int,
|
||||
data: dict[str, Any],
|
||||
actor: str = "api",
|
||||
approval_ticket_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
if domain in HIGH_RISK_DOMAINS:
|
||||
if not approval_ticket_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="High-risk domain update requires approval_ticket_id",
|
||||
)
|
||||
if not ApprovalService(self.db).is_approved_for(
|
||||
approval_ticket_id,
|
||||
domain,
|
||||
record_id,
|
||||
f"update:{domain}",
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Approval ticket is not approved for this update",
|
||||
)
|
||||
model = get_domain_model(domain)
|
||||
record = self.db.get(model, record_id)
|
||||
if record is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Record not found",
|
||||
)
|
||||
allowed = {column.name for column in model.__table__.columns if column.name != "id"}
|
||||
for key, value in data.items():
|
||||
if key in allowed:
|
||||
setattr(record, key, value)
|
||||
self.db.commit()
|
||||
self.db.refresh(record)
|
||||
result = serialize_model(record)
|
||||
self.audit.log(
|
||||
AuditLogCreate(
|
||||
actor=actor,
|
||||
source="api",
|
||||
action=f"update:{domain}",
|
||||
target_type=domain,
|
||||
target_id=str(record.id),
|
||||
risk_level="high" if domain in HIGH_RISK_DOMAINS else "low",
|
||||
request_payload={"data": data, "approval_ticket_id": approval_ticket_id},
|
||||
response_payload=result,
|
||||
)
|
||||
)
|
||||
return result
|
||||
Reference in New Issue
Block a user