Compare commits
2 Commits
5059e57f19
...
c15f3e9925
| Author | SHA1 | Date | |
|---|---|---|---|
| c15f3e9925 | |||
| 27b6276cdc |
131
.trae/documents/估值审核联动与估值次数扣减实现方案.md
Normal file
131
.trae/documents/估值审核联动与估值次数扣减实现方案.md
Normal file
@ -0,0 +1,131 @@
|
||||
## 目标概述
|
||||
|
||||
* 加强“交易管理-发票附件与发送邮箱”能力,完善数据记录与事务保障。
|
||||
|
||||
* 改进“开票弹窗附件上传”支持多文件上传(后端存储结构)。
|
||||
|
||||
* 优化“用户管理备注与操作记录”,区分备注维度并完善日志。
|
||||
|
||||
* 覆盖单元/集成测试、数据库迁移、API文档与审计日志。
|
||||
|
||||
## 数据库与模型变更
|
||||
|
||||
* EmailSendLog
|
||||
|
||||
* 新增:`extra: JSONField(null=True)` 完整记录发送邮箱的请求数据(收件人、主题、正文、附件列表、重试信息等)。
|
||||
|
||||
* Invoice(或与开票弹窗相关的业务模型)
|
||||
|
||||
* 新增:`attachments: JSONField(null=True)` 支持多个附件URL(与弹窗上传对应)。
|
||||
|
||||
* <br />
|
||||
|
||||
* 迁移脚本:创建/修改上述字段;保留历史数据不丢失。
|
||||
|
||||
## 事务与原子性
|
||||
|
||||
* 发送邮箱流程(交易管理)
|
||||
|
||||
* 封装在 `tortoise.transactions.in_transaction()` 中:邮件发送、EmailSendLog写入(含attachments/extra)原子提交;失败回滚。
|
||||
|
||||
* extra写入内容:完整`SendEmailRequest`(收件人、主题、正文、附件URL/文件名、重试次数、客户端UA等)。
|
||||
|
||||
* 多文件上传至发票附件(开票弹窗)
|
||||
|
||||
* 更新发票的 `attachments` 字段在同一事务内写入;如任一URL校验失败则回滚。
|
||||
|
||||
## 后端接口改造
|
||||
|
||||
* 上传组件(后端)
|
||||
|
||||
* 新增:`POST /api/v1/upload/files` 接收 `files: List[UploadFile]`,统一返回 `urls: string[]`;保留现有单文件接口向后兼容。
|
||||
|
||||
* 交易管理(管理员)
|
||||
|
||||
* `POST /api/v1/transactions/send-email` 扩展:
|
||||
|
||||
* 入参支持 `file_urls: string[]`(与/或单文件),服务端聚合附件;
|
||||
|
||||
* 在事务中记录 EmailSendLog(含attachments与extra),返回log\_id与状态;
|
||||
|
||||
* 回显接口(详情/列表)新增 `extra` 字段完整展示发送记录。
|
||||
|
||||
* 开票弹窗附件(管理员/或对应端)
|
||||
|
||||
* 新增/改造:`PUT /api/v1/invoice/{id}/attachments` 入参 `urls: string[]`,更新发票 `attachments`。
|
||||
|
||||
* 列表/详情回显 `attachments: string[]`。
|
||||
|
||||
* 用户管理备注优化(管理员端)
|
||||
|
||||
* 新接口:`PUT /api/v1/app-user-admin/{user_id}/notes`
|
||||
|
||||
* 入参:`system_notes?: string`、`user_notes?: string`(可选择性更新)
|
||||
|
||||
* 逻辑:仅更新提供的字段;不影响其他字段。
|
||||
|
||||
* 修复:`POST /api/v1/app-user-admin/quota` 仅调整次数,不再自动写入 `user.notes`。
|
||||
|
||||
* 操作日志:在调整配额、更新备注、停用启用等操作时写入 `AppUserOperationLog`。
|
||||
|
||||
## 前端改造(要点)
|
||||
|
||||
* 多文件上传组件
|
||||
|
||||
* 改为多选/拖拽支持;对每个文件显示上传进度与失败重试;
|
||||
|
||||
* 成功后收集URL数组写入发票 `attachments` 或作为邮件附件来源;
|
||||
|
||||
* 兼容旧接口:若后端仅返回单URL,前端仍正常显示(降级为单文件模式)。
|
||||
|
||||
* 开票弹窗
|
||||
|
||||
* 支持附件列表预览与移除;提交时调用 `PUT /invoice/{id}/attachments`;
|
||||
|
||||
* 邮件发送弹窗
|
||||
|
||||
* 选择附件来源(已上传的附件/本地文件再上传);提交后在详情页面完整回显 `extra`(含附件清单与正文等)。
|
||||
|
||||
## 审计与日志
|
||||
|
||||
* 关键操作:邮件发送、发票附件更新、用户备注更新、配额调整
|
||||
|
||||
* 统一调用审计记录(路由中间件已存在,补充结构化日志:`logger.info()` + DB审计表/操作日志表写入)。
|
||||
|
||||
## 测试方案
|
||||
|
||||
* 单元测试
|
||||
|
||||
* Email发送控制器:事务成功/失败回滚(模拟抛错)
|
||||
|
||||
* 多文件上传:文件类型校验、URL数组返回、尾随空白处理
|
||||
|
||||
* 备注更新:选择性字段更新、不影响其他字段
|
||||
|
||||
* 集成测试(FastAPI + httpx.AsyncClient)
|
||||
|
||||
* 发送邮箱:请求→持久化校验(attachments/extra)→回显接口校验
|
||||
|
||||
* 附件上传:批量上传、更新发票、列表/详情回显
|
||||
|
||||
* 用户备注:接口调用→DB值校验→操作日志存在
|
||||
|
||||
## 迁移与兼容
|
||||
|
||||
* 使用现有迁移工具(如Aerich)生成并应用迁移:新增JSON/Text字段;
|
||||
|
||||
* 前端保留旧接口兼容:若上传仍走单文件,后端返回数组长度1;
|
||||
|
||||
* API文档(OpenAPI)
|
||||
|
||||
* 补充/更新以上端点的schema说明、示例请求/响应、错误码约定;
|
||||
|
||||
## 实施步骤与交付
|
||||
|
||||
1. 数据模型与迁移脚本编写与应用(EmailSendLog、Invoice、AppUser/OperationLog)。
|
||||
2. 后端接口改造与事务封装(邮件发送、多文件上传、发票附件更新、备注接口)。
|
||||
3. 前端组件与弹窗改造(多文件上传、进度与错误处理、回显extra)。
|
||||
4. 审计日志与结构化日志补充。
|
||||
5. 单元与集成测试编写,覆盖核心路径。
|
||||
6. 更新接口文档与部署回归验证。
|
||||
|
||||
34
.trae/documents/修复添加发票抬头必填项校验.md
Normal file
34
.trae/documents/修复添加发票抬头必填项校验.md
Normal file
@ -0,0 +1,34 @@
|
||||
## 目标
|
||||
|
||||
* 添加抬头时将 `公司名称`、`公司税号`、`电子邮箱`设为必填,其他字段可为空。
|
||||
|
||||
* 现状确认(代码引用)
|
||||
|
||||
- 后端必填:`app/schemas/invoice.py:6–12` 中 `InvoiceHeaderCreate` 已要求 `company_name`、`tax_number` 为必填,`email: EmailStr` 为必填。
|
||||
|
||||
- API 入口:`app/api/v1/app_invoices/app_invoices.py:55–58` 新增抬头接口使用 `InvoiceHeaderCreate`,后端将严格校验三项必填。
|
||||
|
||||
## 修改方案
|
||||
|
||||
1. 统一前端校验文案
|
||||
|
||||
* 统一三项必填的错误提示为简洁中文,如:“请输入公司名称 / 公司税号 / 电子邮箱”。
|
||||
|
||||
* 邮箱格式提示统一为:“请输入有效的电子邮箱”。
|
||||
|
||||
2. 后端校验与返回确认
|
||||
|
||||
* 保持 `InvoiceHeaderCreate` 的必填与格式限制不变(`app/schemas/invoice.py:6–12`)。
|
||||
|
||||
* 确认更新接口 `InvoiceHeaderUpdate`(`app/schemas/invoice.py:32–39`)允许局部更新、但不影响创建必填逻辑。
|
||||
|
||||
3. 验证与测试
|
||||
|
||||
* 后端接口验证:对 `POST /app-invoices/headers`(`app/api/v1/app_invoices/app_invoices.py:55–58`)进行用例:缺失任一必填字段应返回 422;全部正确应 200/201。
|
||||
|
||||
* 可补充最小化单元测试:Pydantic 校验用例覆盖必填与格式。
|
||||
|
||||
## 交付内容。
|
||||
|
||||
* 完成基本交互与接口验证,确保行为符合预期。
|
||||
|
||||
@ -301,7 +301,28 @@ async def calculate_valuation(
|
||||
"""
|
||||
|
||||
try:
|
||||
# 添加后台任务
|
||||
from app.models.user import AppUser, AppUserQuotaLog
|
||||
user = await AppUser.filter(id=user_id).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="用户不存在")
|
||||
if (user.remaining_quota or 0) < 1:
|
||||
raise HTTPException(status_code=400, detail="估值次数不足")
|
||||
before = user.remaining_quota or 0
|
||||
user.remaining_quota = before - 1
|
||||
await user.save()
|
||||
try:
|
||||
await AppUserQuotaLog.create(
|
||||
app_user_id=user_id,
|
||||
operator_id=user_id,
|
||||
operator_name=user.alias or user.username or user.phone or "",
|
||||
before_count=before,
|
||||
after_count=before - 1,
|
||||
op_type="consume",
|
||||
remark="发起估值"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
background_tasks.add_task(_perform_valuation_calculation, user_id, data)
|
||||
|
||||
logger.info("valuation.task_queued user_id={} asset_name={} industry={}",
|
||||
|
||||
@ -46,10 +46,10 @@ class InvoiceCreate(BaseModel):
|
||||
email: EmailStr
|
||||
company_name: str = Field(..., min_length=1, max_length=128)
|
||||
tax_number: str = Field(..., min_length=1, max_length=32)
|
||||
register_address: str = Field(..., min_length=1, max_length=256)
|
||||
register_phone: str = Field(..., min_length=1, max_length=32)
|
||||
bank_name: str = Field(..., min_length=1, max_length=128)
|
||||
bank_account: str = Field(..., min_length=1, max_length=64)
|
||||
register_address: str = Field(..., max_length=256)
|
||||
register_phone: str = Field(..., max_length=32)
|
||||
bank_name: str = Field(..., max_length=128)
|
||||
bank_account: str = Field(..., max_length=64)
|
||||
app_user_id: Optional[int] = None
|
||||
header_id: Optional[int] = None
|
||||
wechat: Optional[str] = None
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user