first commit

This commit is contained in:
2026-06-25 17:41:06 +08:00
commit b2f23a933b
370 changed files with 30526 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
from .base import *
+23
View File
@@ -0,0 +1,23 @@
from pydantic import BaseModel, Field
from app.models.enums import MethodType
class BaseApi(BaseModel):
path: str = Field(..., description="API路径", example="/api/v1/user/list")
summary: str = Field("", description="API简介", example="查看用户列表")
method: MethodType = Field(..., description="API方法", example="GET")
tags: str = Field(..., description="API标签", example="User")
class ApiCreate(BaseApi): ...
class ApiUpdate(BaseApi):
id: int
class Paginate(BaseModel):
page: int = Field(1, alias="pageNo", description="页码", example=1)
page_size: int = Field(20, alias="pageSize", description="每页数量", example=20)
+164
View File
@@ -0,0 +1,164 @@
# app/schemas/automation.py
from datetime import datetime
from typing import List, Optional, Dict, Any
from pydantic import BaseModel, Field
from app.models.enums import TaskStatus, ScenarioScope, ActionType
# ----------------------------
# Scenario Schemas
# ----------------------------
class ScenarioBase(BaseModel):
title: str = Field(..., max_length=255, description="场景标题")
trigger: Dict[str, Any] = Field(..., description="触发条件(事件/时间)")
actions: List[Dict[str, Any]] = Field(default_factory=list, description="执行动作模板列表")
notes: Optional[str] = Field(None, description="备注")
is_global: bool = Field(True, description="是否全局场景")
owner_user_id: Optional[str] = Field(None, max_length=64, description="归属用户ID(非全局时有效)")
enabled: bool = Field(True, description="是否启用")
scope: ScenarioScope = Field(ScenarioScope.ALL, description="作用角色范围")
class ScenarioCreate(ScenarioBase):
pass
class ScenarioUpdate(BaseModel):
id: int = Field(..., description="场景ID")
title: Optional[str] = Field(None, max_length=255)
trigger: Optional[Dict[str, Any]] = None
actions: Optional[List[Dict[str, Any]]] = None
notes: Optional[str] = None
is_global: Optional[bool] = None
owner_user_id: Optional[str] = Field(None, max_length=64)
enabled: Optional[bool] = None
scope: Optional[ScenarioScope] = None
# model_config = {"extra": "forbid"} # 禁止传入未定义字段
class ScenarioCopy(BaseModel):
id: int = Field(..., description="场景ID")
# model_config = {"extra": "forbid"} # 禁止传入未定义字段
class ScenarioRead(ScenarioBase):
id: int
created_at: datetime
updated_at: datetime
model_config = {"from_attributes": True}
# ----------------------------
# Task Schemas
# ----------------------------
class TaskBase(BaseModel):
title: str = Field(..., max_length=255, description="待办标题")
# actions: List[Dict[str, Any]] = Field(default_factory=list, description="动作列表(动态组件渲染用)")
event_data: Optional[Dict[str, Any]] = Field(default_factory=dict, description="事件数据")
notes: Optional[str] = Field(None, description="备注")
reason: Optional[str] = Field(None, description="触发原因")
# 关联字段
source_scenario_id: Optional[int] = Field(None, description="来源场景ID")
owner_user_id: Optional[str] = Field(None, max_length=64, description="归属用户ID")
# 分配与状态
assignee_user_id: Optional[str] = Field(None, max_length=64, description="指派人用户ID")
assignee_username: Optional[str] = Field(None, max_length=64, description="指派人用户名")
status: TaskStatus = Field(TaskStatus.PENDING, description="任务状态")
# 自动化控制
due_at: Optional[datetime] = Field(None, description="截止时间")
auto_closeable: bool = Field(True, description="是否允许自动消除")
closed_by: Optional[str] = Field(None, max_length=32, description="关闭方式: manual/auto/expired")
class TaskCreate(TaskBase):
pass
class TaskUpdate(BaseModel):
title: Optional[str] = Field(None, max_length=255)
content: Optional[List[Dict[str, Any]]] = None
notes: Optional[str] = None
source_scenario_id: Optional[int] = None
related_order_id: Optional[str] = Field(None, max_length=64)
related_customer_id: Optional[str] = Field(None, max_length=64)
group_id: Optional[str] = Field(None, max_length=64)
assignee_user_id: Optional[str] = Field(None, max_length=64)
assignee_username: Optional[str] = Field(None, max_length=64)
status: Optional[TaskStatus] = None
due_at: Optional[datetime] = None
auto_closeable: Optional[bool] = None
closed_by: Optional[str] = Field(None, max_length=32)
completed_at: Optional[datetime] = None
# model_config = {"extra": "forbid"}
class TaskRead(TaskBase):
id: int
created_at: datetime
updated_at: datetime
completed_at: Optional[datetime] = None
model_config = {"from_attributes": True}
# ----------------------------
# Action Schemas
# ----------------------------
class ActionBase(BaseModel):
type: Optional[ActionType] = Field(None, description="操作类型")
detail: Dict[str, Any] = Field(default_factory=dict, description="操作详情")
done: bool = Field(False, description="是否已完成")
done_at: Optional[datetime] = Field(None, description="完成时间")
result: Dict[str, Any] = Field(default_factory=dict, description="操作结果")
userid: Optional[str] = Field(None, max_length=64, description="操作用户ID")
username: Optional[str] = Field(None, max_length=64, description="操作用户名称")
notes: Optional[str] = Field(None, description="操作备注")
task_id: int = Field(..., description="所属任务ID")
action_template_id: Optional[str] = Field(None, max_length=64, description="操作模板ID")
class ActionCreate(ActionBase):
pass
class ActionUpdate(BaseModel):
type: Optional[ActionType] = None
detail: Optional[Dict[str, Any]] = None
done: Optional[bool] = None
done_at: Optional[datetime] = None
result: Optional[Dict[str, Any]] = None
userid: Optional[str] = Field(None, max_length=64)
username: Optional[str] = Field(None, max_length=64)
notes: Optional[str] = None
task_id: Optional[int] = None
action_template_id: Optional[str] = Field(None, max_length=64)
model_config = {"extra": "forbid"}
class ActionRead(ActionBase):
id: int
created_at: datetime
updated_at: datetime
model_config = {"from_attributes": True}
class NotifyAction(BaseModel):
platform: str = Field(..., description="平台类型")
webhook_url: str = Field(..., description="Webhook URL")
template: str = Field(..., description="模板内容")
need_at: bool = Field(False, description="是否需要@用户")
__meta_mapping__ = {
"template": {"type": "text"}
}
+52
View File
@@ -0,0 +1,52 @@
from typing import Any, Optional
from fastapi.responses import JSONResponse
class Success(JSONResponse):
def __init__(
self,
code: int = 200,
msg: Optional[str] = "OK",
data: Optional[Any] = None,
**kwargs,
):
content = {"code": code, "msg": msg, "data": data}
content.update(kwargs)
super().__init__(content=content, status_code=code)
class Fail(JSONResponse):
def __init__(
self,
code: int = 400,
msg: Optional[str] = None,
data: Optional[Any] = None,
**kwargs,
):
content = {"code": code, "msg": msg, "data": data}
content.update(kwargs)
super().__init__(content=content, status_code=code)
class SuccessExtra(JSONResponse):
def __init__(
self,
code: int = 200,
msg: Optional[str] = None,
data: Optional[Any] = None,
total: int = 0,
page: int = 1,
page_size: int = 20,
**kwargs,
):
content = {
"code": code,
"msg": msg,
"data": data,
"total": total,
"page": page,
"page_size": page_size,
}
content.update(kwargs)
super().__init__(content=content, status_code=code)
+48
View File
@@ -0,0 +1,48 @@
from datetime import datetime
from typing import Optional, Union
from pydantic import BaseModel, Field
class BaseCodegen(BaseModel):
name: Optional[str] = Field(description="名称")
author: Optional[str] = Field(description="作者", default='')
description: Optional[str] = Field(description="描述", default='')
version: Optional[str] = Field(description="版本", default='')
fields: Optional[Union[str, dict]] = Field(description="字段", default='{}')
relations: Optional[Union[str, dict]] = Field(description="关系", default='{}')
api: Optional[Union[str, dict]] = Field(description="API", default='{}')
menu: Optional[Union[str, dict]] = Field(description="菜单", default='{}')
class CodegenCreate(BaseCodegen):
name: str = Field(description="名称")
fields: str = Field(description="字段")
class CodegenUpdate(BaseModel):
id: int = Field(example=1)
name: str = Field(example="管理员")
desc: str = Field("", example="管理员角色")
class CodegenUpdateMenusApis(BaseModel):
id: int
menu_ids: list[int] = []
api_infos: list[dict] = []
class DatasourceInfo(BaseModel):
id: int = Field(..., description="ID")
tableName: Optional[str] = Field(..., description="名称")
tableComment: Optional[str] = Field(..., description="注释")
created_at: Optional[datetime]
updated_at: Optional[datetime]
class DatasourceCreate(DatasourceInfo):
tableName: str = Field(..., description="名称")
tableComment: str = Field(..., description="注释")
class DatasourceUpdate(DatasourceInfo):
id: int = Field(..., description="ID")
+170
View File
@@ -0,0 +1,170 @@
from pydantic import BaseModel, Field, field_validator
from typing import Optional, Any
from datetime import datetime # 如果需要 datetime 对象
# 如果你有 TimestampMixin,需要在这里导入或定义
# class TimestampMixin(BaseModel):
# created_at: Optional[datetime] = None
# updated_at: Optional[datetime] = None
class CrmCustomerBase(BaseModel):
# 保留 JSON 中存在的字段,并添加 alias
customer_name: Optional[str] = Field(default=None, alias="customerName")
province: Optional[str] = Field(default=None, alias="province")
city: Optional[str] = Field(default=None, alias="city")
area: Optional[str] = Field(default=None, alias="area")
clinch_order: Optional[str] = Field(default=None, alias="clinchOrder")
clinch_price: Optional[str] = Field(default=None, alias="clinchPrice")
first_clinch_time: Optional[str] = Field(default=None, alias="firstClinchTime")
last_clinch_time: Optional[str] = Field(default=None, alias="lastClinchTime")
refund_order_times: Optional[str] = Field(default=None, alias="refundOrderTimes")
refund_amount: Optional[str] = Field(default=None, alias="refundAmount")
private_flag: Optional[int] = Field(default=None, alias="privateFlag") # JSON 中是 int
touch_flag: Optional[int] = Field(default=None, alias="touchFlag") # JSON 中是 int
open_id: Optional[str] = Field(default=None, alias="openId")
xy_id: Optional[int] = Field(default=None, alias="xyId") # JSON 中是 int
last_touch_time: Optional[str] = Field(default=None, alias="lastTouchTime")
add_wechat_time: Optional[str] = Field(default=None, alias="addWeChatTime")
mobile: Optional[str] = Field(default=None, alias="mobile")
source: Optional[str] = Field(default=None, alias="source")
we_chat_image: Optional[str] = Field(default=None, alias="weChatImage")
cid: Optional[int] = Field(default=None, alias="cid") # JSON 中是 int
last_trade_time: Optional[str] = Field(default=None, alias="lastTradeTime")
private_jump: Optional[int] = Field(default=None, alias="privateJump") # JSON 中是 int
lst_pay_ord_amt: Optional[float] = Field(default=None, alias="lstPayOrdAmt") # JSON 中是 float
pay_avg_ord_amt_td: Optional[float] = Field(default=None, alias="payAvgOrdAmtTd") # JSON 中是 float
refund_ord_rate_td: Optional[str] = Field(default=None, alias="refundOrdRateTd") # JSON 中是 string like '0.00%'
touch_cnt_td: Optional[int] = Field(default=None, alias="touchCntTd") # JSON 中是 int
pay_ord_day_cnt_1y: Optional[int] = Field(default=None, alias="payOrdDayCnt1y") # JSON 中是 int
pay_same_item_max_cnt_td: Optional[int] = Field(default=None, alias="paySameItemMaxCntTd") # JSON 中是 int
pay_diff_item_cnt_td: Optional[int] = Field(default=None, alias="payDiffItemCntTd") # JSON 中是 int
pay_item_max_cnt_td: Optional[int] = Field(default=None, alias="payItemMaxCntTd") # JSON 中是 int
user_type_90d: Optional[int] = Field(default=None, alias="userType90d") # JSON 中是 int
# shop_id 和 shop_name 不在 JSON 中,但根据你的 Tortoise 模型定义,它们是 CrmCustomer 特有的,所以不加 alias
shop_id: Optional[int] = Field(default=None) # 没有 alias,因为它在 JSON 中不存在
shop_name: Optional[str] = Field(default=None) # 没有 alias,因为它在 JSON 中不存在
class Config:
populate_by_name = True # 允许使用别名或字段名创建实例
class CrmCustomerCreate(CrmCustomerBase):
pass
class CrmCustomerUpdate(BaseModel):
# 这个模型的字段与 JSON 结构无关,保留原有定义
customer_name: Optional[str] = None
customer_remark: Optional[str] = None
customer_desc: Optional[str] = None
mobiles: Optional[list[str]] = None
labels: Optional[list[dict[str, Any]]] = None
follow_member_name: Optional[str] = None
follow_member_mobile: Optional[str] = None
follow_member_dept: Optional[str] = None
source_tips: Optional[str] = None
cus_channel: Optional[str] = None
class CrmCustomer(CrmCustomerBase):
id: int = Field(alias="id") # JSON 中的 'id' 字段
data_source: Optional[str] = None
last_updated_from: Optional[str] = None
# updated_at: Optional[datetime] = Field(default=None, alias="updatedAt") # 如果需要 datetime 对象
updated_at: Optional[str] = Field(default=None, alias="updatedAt") # 使用 str 匹配 JSON 格式
class Config:
from_attributes = True
populate_by_name = True
def convert_to_str(v) -> Optional[str]:
"""将输入值转换为字符串"""
if v is None:
return v
return str(v)
class CrmCustomerBase(BaseModel):
# 对应 Tortoise 模型字段,并使用 JSON 中的键作为 alias
cid: Optional[int] = Field(default=None, alias="cid", description="CID")
customer_name: Optional[str] = Field(default=None, max_length=64, alias="customerName", description="客户名称")
clinch_order: Optional[str] = Field(default=None, max_length=64, alias="clinchOrder", description="成交订单数")
clinch_price: Optional[str] = Field(default=None, max_length=64, alias="clinchPrice", description="成交金额")
first_clinch_time: Optional[datetime] = Field(default=None, alias="firstClinchTime", description="首次成交时间")
last_clinch_time: Optional[datetime] = Field(default=None, alias="lastClinchTime", description="最后成交时间")
refund_order_times: Optional[str] = Field(default=None, max_length=64, alias="refundOrderTimes", description="退款订单数")
refund_amount: Optional[str] = Field(default=None, max_length=64, alias="refundAmount", description="退款金额")
private_flag: Optional[bool] = Field(default=False, alias="privateFlag", description="是否私有")
touch_flag: Optional[bool] = Field(default=False, alias="touchFlag", description="是否触达")
open_id: Optional[str] = Field(default=None, max_length=64, alias="openId", description="微信openid")
xy_id: Optional[int] = Field(default=None, alias="xyId", description="星云有客中的用户ID", index=True)
last_touch_time: Optional[datetime] = Field(default=None, alias="lastTouchTime", description="最后触达时间")
add_wechat_time: Optional[datetime] = Field(default=None, alias="addWeChatTime", description="添加微信时间")
mobile: Optional[str] = Field(default=None, max_length=64, alias="mobile", description="手机号")
source: Optional[str] = Field(default=None, max_length=64, alias="source", description="来源")
we_chat_image: Optional[str] = Field(default=None, max_length=512, alias="weChatImage", description="微信头像")
last_trade_time: Optional[datetime] = Field(default=None, alias="lastTradeTime", description="最后交易时间")
private_jump: Optional[bool] = Field(default=False, alias="privateJump", description="是否跳转")
lst_pay_ord_amt: Optional[str] = Field(default=None, alias="lstPayOrdAmt", description="最近一次支付金额")
pay_avg_ord_amt_td: Optional[str] = Field(default=None, alias="payAvgOrdAmtTd", description="最近30天平均支付金额")
refund_ord_rate_td: Optional[str] = Field(default=None, alias="refundOrdRateTd", description="最近30天退款订单数")
touch_cnt_td: Optional[int] = Field(default=None, alias="touchCntTd", description="最近30天触达次数")
pay_ord_day_cnt_1y: Optional[int] = Field(default=None, alias="payOrdDayCnt1y", description="最近一年支付订单数")
pay_same_item_max_cnt_td: Optional[int] = Field(default=None, alias="paySameItemMaxCntTd", description="最近30天相同商品最大购买数量")
pay_diff_item_cnt_td: Optional[int] = Field(default=None, alias="payDiffItemCntTd", description="最近30天不同商品购买数量")
pay_item_max_cnt_td: Optional[int] = Field(default=None, alias="payItemMaxCntTd", description="最近30天最大购买商品数量")
user_type_90d: Optional[int] = Field(default=None, alias="userType90d", description="最近90天用户类型")
# shop_id 和 shop_name 在 JSON 中不存在,因此不设置 alias
shop_id: Optional[int] = Field(default=None, description="店铺ID", index=True)
shop_name: Optional[str] = Field(default=None, max_length=64, description="店铺名称")
@field_validator('lst_pay_ord_amt', 'pay_avg_ord_amt_td', 'refund_ord_rate_td', mode='before')
def validate_and_convert_to_str(cls, v):
return convert_to_str(v)
class Config:
populate_by_name = True # 允许使用别名或字段名创建实例
class CrmCustomerCreate(CrmCustomerBase):
pass
class CrmCustomerUpdate(BaseModel):
# 保留原始定义(如果需要)
customer_name: Optional[str] = None
customer_remark: Optional[str] = None
customer_desc: Optional[str] = None
mobiles: Optional[list[str]] = None
labels: Optional[list[dict[str, Any]]] = None
follow_member_name: Optional[str] = None
follow_member_mobile: Optional[str] = None
follow_member_dept: Optional[str] = None
source_tips: Optional[str] = None
cus_channel: Optional[str] = None
class BaseCrmBindInfo(BaseModel):
platform: Optional[str]= None
customer_id: Optional[int]= None
orderId: Optional[str]= None
buyer_id: Optional[str]= None
userid: Optional[str]= None
name: Optional[str]= None
nick_name: Optional[str]= None
avatar: Optional[str]= None
platform_id: Optional[str]= None
need_confirm: Optional[bool]= None
tenant_name: Optional[str]= None
tenant_id: Optional[str]= None
remark: Optional[str]= None
extra: Optional[str]= None
class CrmBindInfoCreate(BaseCrmBindInfo):
id: Optional[int] = None
pass
class CrmBindInfoUpdate(BaseCrmBindInfo):
id: int = Field(..., description="绑定信息ID")
orderId: str = Field(..., description="订单ID")
customer_id: int = Field(..., description="客户ID")
name: str = Field(..., description="客户名称")
+61
View File
@@ -0,0 +1,61 @@
from pydantic import BaseModel
from typing import Optional, List, Dict, Any
from datetime import datetime
class CustomerBase(BaseModel):
customer_id: str
customer_name: Optional[str] = None
nick_name: Optional[str] = None
head_url: Optional[str] = None
gender: Optional[int] = None
customer_remark: Optional[str] = None
customer_desc: Optional[str] = None
mobiles: Optional[List[str]] = None
labels: Optional[List[Dict[str, Any]]] = None
source_type: Optional[int] = None
apply_mode: Optional[int] = None
source_tips: Optional[str] = None
source_room_id: Optional[str] = None
source_room_name: Optional[str] = None
source_room_image: Optional[str] = None
follow_member_vid: str
follow_member_name: Optional[str] = None
follow_member_mobile: Optional[str] = None
follow_member_dept: Optional[str] = None
follow_member_avatar: Optional[str] = None
create_time: Optional[int] = None
update_time: Optional[int] = None
tag_time: Optional[int] = None
add_time: Optional[int] = None
friend_flag: Optional[int] = None
relation_flag: Optional[int] = None
cus_channel: Optional[str] = None
seq: Optional[str] = None
kh_type: Optional[int] = None
room_count: Optional[int] = 0
user_count: Optional[int] = 0
data_version: Optional[str] = None
class CustomerCreate(CustomerBase):
pass
class CustomerUpdate(BaseModel):
customer_name: Optional[str] = None
customer_remark: Optional[str] = None
customer_desc: Optional[str] = None
mobiles: Optional[List[str]] = None
labels: Optional[List[Dict[str, Any]]] = None
follow_member_name: Optional[str] = None
follow_member_mobile: Optional[str] = None
follow_member_dept: Optional[str] = None
source_tips: Optional[str] = None
cus_channel: Optional[str] = None
class Customer(CustomerBase):
id: int
data_source: Optional[str] = None
last_updated_from: Optional[str] = None
updated_at: Optional[datetime] = None
class Config:
from_attributes = True
+18
View File
@@ -0,0 +1,18 @@
from pydantic import BaseModel, Field
class BaseDept(BaseModel):
name: str = Field(..., description="部门名称", example="研发中心")
desc: str = Field("", description="备注", example="研发中心")
order: int = Field(0, description="排序")
parent_id: int = Field(0, description="父部门ID")
class DeptCreate(BaseDept): ...
class DeptUpdate(BaseDept):
id: int
def update_dict(self):
return self.model_dump(exclude_unset=True, exclude={"id"})
+20
View File
@@ -0,0 +1,20 @@
from datetime import datetime
from pydantic import BaseModel, Field
class CredentialsSchema(BaseModel):
username: str = Field(..., description="用户名称", example="admin")
password: str = Field(..., description="密码", example="123456")
class JWTOut(BaseModel):
access_token: str
username: str
class JWTPayload(BaseModel):
user_id: int
username: str
is_superuser: bool
exp: datetime
+52
View File
@@ -0,0 +1,52 @@
from enum import StrEnum
from typing import Optional
from pydantic import BaseModel, Field
class MenuType(StrEnum):
CATALOG = "catalog" # 目录
MENU = "menu" # 菜单
class BaseMenu(BaseModel):
id: int
name: str
path: str
remark: Optional[dict]
menu_type: Optional[MenuType]
icon: Optional[str]
order: int
parent_id: int
is_hidden: bool
component: str
keepalive: bool
redirect: Optional[str]
children: Optional[list["BaseMenu"]]
class MenuCreate(BaseModel):
menu_type: MenuType = Field(default=MenuType.CATALOG.value)
name: str = Field(example="用户管理")
icon: Optional[str] = "ph:user-list-bold"
path: str = Field(example="/system/user")
order: Optional[int] = Field(example=1)
parent_id: Optional[int] = Field(example=0, default=0)
is_hidden: Optional[bool] = False
component: str = Field(default="Layout", example="/system/user")
keepalive: Optional[bool] = True
redirect: Optional[str] = ""
class MenuUpdate(BaseModel):
id: int
menu_type: Optional[MenuType] = Field(example=MenuType.CATALOG.value)
name: Optional[str] = Field(example="用户管理")
icon: Optional[str] = "ph:user-list-bold"
path: Optional[str] = Field(example="/system/user")
order: Optional[int] = Field(example=1)
parent_id: Optional[int] = Field(example=0)
is_hidden: Optional[bool] = False
component: str = Field(example="/system/user")
keepalive: Optional[bool] = False
redirect: Optional[str] = ""
+99
View File
@@ -0,0 +1,99 @@
from typing import Optional
from datetime import datetime
from pydantic import BaseModel, Field
from app.models.enums import MsgType, ActionType
class BaseMsg(BaseModel):
hash_id: Optional[str] = Field(description="消息哈希ID")
title: Optional[str] = Field(description="消息标题")
content: Optional[str] = Field(description="消息内容")
detail: Optional[str] = Field(default={}, description="消息详情")
is_read: Optional[bool] = Field(default=False, description="是否已读")
read_at: Optional[datetime] = Field(default=None, description="已读时间")
is_delete: Optional[bool] = Field(default=False, description="是否已删除")
delete_at: Optional[datetime] = Field(default=None, description="删除时间")
is_send: Optional[bool] = Field(default=False, description="是否已发送")
send_at: Optional[datetime] = Field(default=None, description="发送时间")
type: Optional[MsgType] = Field(default=MsgType.SYSTEM, description="消息类型")
owner_id: Optional[str] = Field(description="消息所有者ID")
owner_name: Optional[str] = Field(description="消息所有者名称")
class MsgCreate(BaseMsg):
hash_id: str = Field(description="消息哈希ID")
title: str = Field(description="消息标题")
content: str = Field(description="消息内容")
type: MsgType = Field(default=MsgType.SYSTEM, description="消息类型")
owner_id: str = Field(description="消息所有者ID")
owner_name: str = Field(description="消息所有者名称")
class MsgUpdate(BaseModel):
id: int
is_read: Optional[bool] = Field(default=None, description="是否已读")
is_delete: Optional[bool] = Field(default=None, description="是否已删除")
is_send: Optional[bool] = Field(default=None, description="是否已发送")
class MsgNewOrder(BaseModel):
type: Optional[MsgType] = Field(default=MsgType.NEW_ORDER, description="消息类型")
order_id: str = Field(description="订单ID")
order_status: str = Field(description="订单状态")
is_refund: Optional[bool] = Field(default=False, alias="order_type", description="是否退款: 0-普通订单,1-退款订单")
buyer_id: str = Field(description="买家ID")
buyer_nick: str = Field(description="买家名称")
def __str__(self):
return f'订单:{self.order_id} {self.order_status},买家:{self.buyer_nick or self.buyer_id} {"退款" if self.is_refund else ""}'
class MsgFilter(BaseModel):
type: Optional[MsgType] = Field(default=None, description="消息类型")
is_read: Optional[bool] = Field(default=None, description="是否已读")
is_refund: Optional[bool] = Field(default=None, description="是否退款订单")
class BaseAction(BaseModel):
type: Optional[ActionType] = Field(default=None, description="操作类型")
detail: Optional[dict] = Field(default={}, description="操作详情")
user_id: Optional[str] = Field(default=None, description="操作用户ID")
user_name: Optional[str] = Field(default=None, description="操作用户名称")
class ActionCreate(BaseAction):
done: Optional[bool] = Field(default=False, description="是否完成")
type: ActionType = Field(description="操作类型")
detail: dict = Field(description="操作详情")
userid: str = Field(description="操作用户ID")
username: str = Field(description="操作用户名称")
class ActionUpdate(BaseAction):
id: int = Field(description="操作ID")
class FollowFormErp(BaseModel):
order_id: str = Field(description="订单ID", alias="trade_no")
order_title: str = Field(description="订单标题", alias="title")
pay_time: str = Field(description="支付时间", alias="pay_time")
pay_price: str = Field(description="支付金额", alias="price")
remark: str = Field(description="erp额外备注", alias="remark")
shop_name: str = Field(description="店铺名称", alias="shop_name")
"""
customer_id = fields.CharField(max_length=64, null=True, description="客户ID", index=True)
customer_name = fields.CharField(max_length=64, null=True, description="客户名称", index=True)
customer_taobao_id = fields.CharField(max_length=64, null=True, description="客户淘宝ID", index=True)
staff_id = fields.CharField(max_length=64, null=True, description="企微员工ID", index=True)
staff_name = fields.CharField(max_length=64, null=True, description="企微员工名称", index=True)
staff_time = fields.DatetimeField(null=True, description="添加时间", index=True)
designer_id = fields.CharField(max_length=64, null=True, description="设计师ID", index=True)
designer_name = fields.CharField(max_length=64, null=True, description="设计师名称", index=True)
kefu_id = fields.CharField(max_length=64, null=True, description="淘宝客服ID", index=True)
kefu_name = fields.CharField(max_length=64, null=True, description="淘宝客服名称", index=True)
"""
class FeishuEvent(BaseModel):
challenge: str = Field(description="验证challenge")
+27
View File
@@ -0,0 +1,27 @@
from fastapi import Query, Request
from tortoise.expressions import Q
class QueryParams:
def __init__(self,
request: Request,
page: int = Query(1, description="页码"),
page_size: int = Query(10, description="每页数量"),
):
self.page = page
self.page_size = page_size
self.query_kwargs = dict(request.query_params)
self.q = Q()
for key, value in self.query_kwargs.items():
if key not in ['page', 'page_size']:
# 使用 contains 方法实现包含查询
self.q &= Q(**{f"{key}__contains": value})
def get_offset(self):
return (self.page - 1) * self.page_size
def search_query(self, model, order=('-created_at',)):
query = model.filter(self.q)
return query, query.offset((self.page - 1) * self.page_size).limit(self.page_size).order_by(*order)
+32
View File
@@ -0,0 +1,32 @@
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, Field
class BaseRole(BaseModel):
id: int
name: str
desc: str = ""
users: Optional[list] = []
menus: Optional[list] = []
apis: Optional[list] = []
created_at: Optional[datetime]
updated_at: Optional[datetime]
class RoleCreate(BaseModel):
name: str = Field(example="管理员")
desc: str = Field("", example="管理员角色")
class RoleUpdate(BaseModel):
id: int = Field(example=1)
name: str = Field(example="管理员")
desc: str = Field("", example="管理员角色")
class RoleUpdateMenusApis(BaseModel):
id: int
menu_ids: list[int] = []
api_infos: list[dict] = []
+75
View File
@@ -0,0 +1,75 @@
from typing import Optional
from typing import Dict, Any, Union
from datetime import datetime
from pydantic import BaseModel, Field, ConfigDict
from app.models.enums import TaskStatus, TaskType
class DecodeTaskParams(BaseModel):
filename: str = Field(..., description="文件名")
sheet_name: str = Field(..., description="工作表名称")
decode_type: str = Field(..., description="解码类型")
header: str = Field(..., description="解析列")
model: Optional[str] = Field(None, description="模型名称")
class DecodeTaskResult(BaseModel):
filename: str = Field(..., description="结果保存文件名")
spend: float = Field(..., description="任务耗时")
rows: int = Field(..., description="解析行数")
class TaskResponse(BaseModel):
id: int
name: str
type: TaskType # ← Pydantic 会自动转为字符串
params: dict
status: TaskStatus # ← 同上
result: Optional[dict]
class Config:
from_attributes=True
use_enum_values = True # ← 关键!将 Enum 转为值(如 'pending'
# 或者在 Pydantic v2 中:
# model_config = ConfigDict(use_enum_values=True)
class TaskModel(BaseModel):
name: str
type: TaskType # Enum: e.g., "decode", "upload", etc.
params: Dict[str, Any] # 存储原始参数字典
status: TaskStatus
result: Optional[Dict[str, Any]] = None
@classmethod
def create_decode_task(
cls,
name: str,
params: DecodeTaskParams,
status: TaskStatus = TaskStatus.PENDING
) -> "TaskModel":
"""创建解码任务的工厂方法"""
return cls(
name=name,
type=TaskType.DECODE_ORDER,
params=params.dict(),
status=status
)
def get_params(self) -> BaseModel:
"""反序列化 params 为具体模型"""
if self.type == TaskType.DECODE_ORDER:
return DecodeTaskParams(**self.params)
raise ValueError(f"Unknown task type: {self.type}")
def set_result(self, result: Union[DecodeTaskResult]) -> None:
"""设置结果(自动序列化)"""
if self.type == TaskType.DECODE_ORDER:
self.result = result.dict()
else:
raise ValueError(f"Unsupported result for task type: {self.type}")
def get_result(self) -> Optional[BaseModel]:
"""反序列化 result 为具体模型"""
if self.result is None:
return None
if self.type == TaskType.DECODE_ORDER:
return DecodeTaskResult(**self.result)
raise ValueError(f"Unknown task type: {self.type}")
+49
View File
@@ -0,0 +1,49 @@
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel, EmailStr, Field
class BaseUser(BaseModel):
id: int
email: Optional[EmailStr] = None
username: Optional[str] = None
is_active: Optional[bool] = True
is_superuser: Optional[bool] = False
created_at: Optional[datetime]
updated_at: Optional[datetime]
last_login: Optional[datetime]
roles: Optional[list] = []
class UserCreate(BaseModel):
email: EmailStr = Field(example="admin@qq.com")
username: str = Field(example="admin")
password: str = Field(example="123456")
is_active: Optional[bool] = True
is_superuser: Optional[bool] = False
role_ids: Optional[List[int]] = []
dept_id: Optional[int] = Field(0, description="部门ID")
def create_dict(self):
return self.model_dump(exclude_unset=True, exclude={"role_ids"})
class UserUpdate(BaseModel):
id: int
email: EmailStr
username: str
is_active: Optional[bool] = True
is_superuser: Optional[bool] = False
role_ids: Optional[List[int]] = []
dept_id: Optional[int] = 0
class UserUpdateOnline(BaseModel):
id: int
email: EmailStr
username: str
class UpdatePassword(BaseModel):
old_password: str = Field(description="旧密码")
new_password: str = Field(description="新密码")
+220
View File
@@ -0,0 +1,220 @@
from datetime import datetime
from typing import List, Optional, Union
from pydantic import BaseModel, EmailStr, Field
class BaseWeixinUser(BaseModel):
userid: Optional[str] = None
corp_id: Optional[str] = None
vid: Optional[str] = None
wx_id_hash: Optional[str] = None
is_quit: Optional[bool] = None
crmid: Optional[int] = None
depart_ids: Optional[List[int]] = None
position: Optional[str] = None
role: Optional[str] = None
realname: Optional[str] = None
username: Optional[str] = None
english_name: Optional[str] = None
name: Optional[str] = None
# 将所有属性映射为可选属性
alias: Optional[str] = None
avatar: Optional[str] = None
department: Optional[int] = None
mobile: Optional[str] = None
email: Optional[EmailStr] = None
gender: Optional[int] = None
has_external_user_permit: Optional[bool] = None
erpid: Optional[int] = None
erp_name: Optional[str] = None
crmid: Optional[int] = None
crm_name: Optional[str] = None
class WeixinUserCreate(BaseWeixinUser):
userid: str = Field(example="julia")
name: str = Field(example='用户姓名')
role: str = Field(example='用户角色')
class WeixinUserUpdate(BaseModel):
id: int
crmid: Optional[int] = None
erpid: Optional[int] = None
is_quit: Optional[bool] = None
class Member(BaseModel):
userid: Optional[str] = Field(description="用户ID")
name: Optional[str] = Field(description="用户姓名")
type: Optional[int] = Field(description="用户类型: 1-外部成员, 2-内部成员")
join_time: Optional[int] = Field(description="加入时间")
# join_scene: Optional[int] = Field(description="加入场景")
# unionid: Optional[str] = Field(description="微信UnionID")
# invitor: Optional[dict] = Field(description="邀请人")
group_nickname: Optional[str] = Field(description="群内昵称")
class BaseWeixinGroupChat(BaseModel):
chat_id: Optional[str] = Field(description="企业微信中的群聊ID")
name: Optional[str] = Field(description="群聊名称")
admin_list: Optional[List[Member]] = Field(description="群聊管理员列表")
create_time: Optional[int] = Field(description="群聊创建时间")
owner: Optional[str] = Field(description="群聊群主")
member_version: Optional[str] = Field(description="群聊成员版本")
external_member_list: Optional[List[Member]] = Field(description="群聊外部成员列表")
internal_member_list: Optional[List[Member]] = Field(description="群聊内部成员列表")
class WeixinGroupChatEvent(BaseWeixinGroupChat):
external_user_count: Optional[int] = Field(description="群聊外部成员数量")
internal_member_count: Optional[int] = Field(description="群聊内部成员数量")
__exclude_filter__ = ["admin_list", "member_version", "id"]
class TriggerWeixinGroupChat(BaseModel):
group_name: str = Field(alias="groupName", description="群聊名称")
order_id: Optional[str] = Field(alias="orderId", description="订单ID", default=None)
user_ids: Optional[List[str]] = Field(alias="userIds", description="群聊成员列表")
external_user_ids: Optional[List[str]] = Field(alias="externalUserIds", description="群聊外部成员列表")
class WeixinGroupChatCreate(BaseWeixinGroupChat):
pass
class WeixinGroupChatUpdate(BaseWeixinGroupChat):
pass
class BaseWeixinGroupChatXingyun(BaseModel):
xingyun_chat_id: Optional[int] = Field(alias="chatId")
avatars: Optional[List[Union[str, None]]] = Field(alias="avatars")
chat_id: Optional[str] = Field(alias="groupChatId")
name: Optional[str] = Field(alias="chatName")
admin_list: Optional[List[str]] = Field(alias="admins")
create_time: Optional[int] = Field(alias="createTime")
owner: Optional[str] = Field(alias="ownerName")
member_version: Optional[str] = Field(default=None)
external_member_list: Optional[List[str]] = Field(default=None)
internal_member_list: Optional[List[str]] = Field(default=None)
class WeixinGroupChatXingyunCreate(BaseWeixinGroupChatXingyun):
chat_id: str = Field(alias="groupChatId")
name: str = Field(alias="chatName")
xingyun_chat_id: int = Field(alias="chatId")
class BaseWeixinCustomer(BaseModel):
order_id: Optional[str] = Field(alias="orderId", description="订单ID", default=None)
shop_name: Optional[str] = Field(alias="shopName", description="店铺名称", default=None)
weixin_id: Optional[str] = Field(alias="weixinId", description="微信用户ID", default=None)
weixin_name: Optional[str] = Field(alias="weixinName", description="微信用户名", default=None)
weixin_unionid: Optional[str] = Field(alias="weixinUnionid", description="微信用户UnionID", default=None)
weixin_avatar: Optional[str] = Field(alias="weixinAvatar", description="微信用户头像", default=None)
xingyun_id: Optional[int] = Field(alias="xingyunId", description="星云用户ID", default=None)
xingyun_sex: Optional[int] = Field(alias="xingyunSex", description="星云用户性别", default=None)
xingyun_name: Optional[str] = Field(alias="xingyunName", description="星云用户名", default=None)
xingyun_avatar: Optional[str] = Field(alias="xingyunAvatar", description="星云用户头像", default=None)
xingyun_tags: Optional[list] = Field(alias="xingyunTags", description="星云用户标签", default=None)
xingyun_external_userid: Optional[str] = Field(alias="xingyunExternalUserid", description="星云用户外部ID", default=None)
xingyun_sync: Optional[bool] = Field(alias="xingyunSync", description="是否已同步到星云", default=False)
taobao_id: Optional[str] = Field(alias="taobaoId", description="淘宝用户ID", default=None)
taobao_name: Optional[str] = Field(alias="taobaoName", description="淘宝用户名", default=None)
taobao_avatar: Optional[str] = Field(alias="taobaoAvatar", description="淘宝用户头像", default=None)
need_confirm: Optional[bool] = Field(alias="needConfirm", description="是否需要确认", default=None)
extra: Optional[dict] = Field(alias="extra", description="额外信息", default=None)
class WeixinCustomerCreate(BaseWeixinCustomer):
pass
class WeixinCustomerUpdate(BaseWeixinCustomer):
pass
class WeixinCustomerFilter(BaseModel):
order_id: Optional[str] = Field(description="订单ID", default=None)
has_order: Optional[bool] = Field(description="是否有订单", default=None)
shop_name: Optional[str] = Field(description="店铺名称", default=None)
# weixin_id: Optional[str] = Field(description="微信用户ID", default=None)
# weixin_unionid: Optional[str] = Field(description="微信用户UnionID", default=None)
# xingyun_id: Optional[int] = Field(description="星云用户ID", default=None)
# xingyun_name: Optional[str] = Field(description="星云用户名", default=None)
xingyun_tags: Optional[str] = Field(description="星云用户标签", default=None)
xingyun_sync: Optional[bool] = Field(description="是否已同步到星云", default=False)
# taobao_id: Optional[str] = Field(description="淘宝用户ID", default=None)
# taobao_name: Optional[str] = Field(description="淘宝用户名", default=None)
union_id: Optional[str] = Field(description="联合用户ID", default=None)
union_name: Optional[str] = Field(description="联合用户名", default=None)
class BaseWeixinCustomerXingyun(BaseModel):
weixin_id: Optional[str] = Field(alias="external_userid", description="微信用户ID", default=None)
weixin_name: Optional[str] = Field(alias="weixin_name", description="微信用户名", default=None)
xingyun_id: Optional[int] = Field(alias="cid", description="星云用户ID")
xingyun_name: Optional[str] = Field(alias="name", description="星云用户名")
xingyun_avatar: Optional[str] = Field(alias="avatar", description="星云用户头像")
xingyun_external_userid: Optional[str] = Field(alias="externalUserid", description="星云用户外部ID")
xingyun_sex: Optional[int] = Field(alias="sex", description="星云用户性别", default=None)
xingyun_tags: Optional[list] = Field(alias="tags", description="星云用户标签", default=None)
# 移除这些字段,因为它们将被存储在 extra 中
exclusive_member_info: Optional[dict] = Field(alias="exclusiveMemberInfo", description=" exclusiveMemberInfo", default=None)
extra: Optional[dict] = Field(alias="extra", description="额外信息", default=None)
def transform_to_extra(self):
new_obj = self.model_dump(exclude_unset=True, by_alias=True)
extra = {
'exclusive_member_info': new_obj.pop('exclusiveMemberInfo', None),
}
new_name = new_obj.get('name', None)
if new_name: new_obj['weixin_name'] = new_name
return self.__class__(**new_obj, extra=extra)
class WeixinCustomerXingyunCreate(BaseWeixinCustomerXingyun):
pass
class WeixinUserBindInfo(BaseModel):
userid: str = Field(description="微信用户ID")
username: Optional[str] = Field(description="微信用户名", alias="name")
role: Optional[str] = Field(description="用户角色", default=None)
erp_id: Optional[int] = Field(description="ERP用户ID", default=None)
avatar: Optional[str] = Field(description="微信用户头像", default=None)
taobao_id: Optional[str] = None
class WeixinOrderBindInfo(BaseModel):
userid: str = Field(description="微信用户ID")
order_id: str = Field(description="订单ID")
remark: Optional[str] = Field(description="企业微信向ERP同步订单时的备注", default=None)
class WeixinOrderRemark(BaseModel):
order_id: str = Field(description="订单ID")
remark: Optional[str] = Field(description="企业微信向ERP同步订单时的备注", default=None)
class OrderUser(BaseModel):
role: Optional[str] = Field(description="用户角色", default=None)
taobao_id: Optional[str] = Field(description="旺旺名称", alias="id", default=None)
taobao_name: Optional[str] = Field(description="旺旺名称", alias="name", default=None)
userid: Optional[str] = Field(description="微信用户ID", default=None)
class BindOrderResultEvent(BaseModel):
shop_name: Optional[str] = Field(description="店铺名称")
price: Optional[int] = Field(description="订单金额")
discount_fee: Optional[int] = Field(description="折扣金额")
real_payment: Optional[int] = Field(description="实付金额")
remark: Optional[str] = Field(description="企业微信向ERP同步订单时的备注", default=None)
status_string: Optional[str] = Field(description="订单状态")
pay_time: Optional[str] = Field(description="支付时间")
users: Optional[List[OrderUser]] = Field(description="绑定的用户信息")
# __exclude_filter__ = ["member_version", "id"]
class CustomerRepeatPurchaseEvent(BindOrderResultEvent):
has_service_group: Optional[bool] = Field(description="是否有服务群", default=None)
__exclude_filter__ = ["remark", "status_string", "pay_time"]
# CUSTOMER_SERVICE_ASSIGN_ORDER
class CustomerAssignOrderEvent(BindOrderResultEvent):
__exclude_filter__ = ["remark", "status_string"]
# CUSTOMER_SERVICE_ASSIGN_ORDER
class CustomerRefundOrderEvent(BindOrderResultEvent):
__exclude_filter__ = ["status_string"]