260 lines
12 KiB
Python
260 lines
12 KiB
Python
from tortoise import fields
|
|
|
|
from .base import BaseModel, TimestampMixin
|
|
from .enums import RoleType
|
|
|
|
class WeixinUser(BaseModel, TimestampMixin):
|
|
# 自动成为订单关联方
|
|
auto_relate = fields.BooleanField(default=False, description="是否自动成为订单关联方")
|
|
userid = fields.CharField(max_length=64, description="用户微信id", index=True) # acctid
|
|
corp_id = fields.CharField(max_length=64, description="企业微信id", default="1970325009092879", index=True)
|
|
vid = fields.CharField(max_length=64, description="用户微信id", default='', index=True)
|
|
wx_id_hash = fields.CharField(max_length=64, null=True, description="微信id hash")
|
|
is_quit = fields.BooleanField(default=False, description="是否离职")
|
|
has_external_user_permit = fields.BooleanField(default=False, description="是否具有对外联系权限")
|
|
wx_nick_name = fields.CharField(max_length=64, null=True, description="微信昵称")
|
|
account = fields.CharField(max_length=64, null=True, description="账号")
|
|
|
|
position = fields.CharField(max_length=64, null=True, description="职位")
|
|
role = fields.CharEnumField(RoleType, null=True, description="用户角色", index=True)
|
|
depart_ids = fields.JSONField(default=[], description="部门ID列表")
|
|
|
|
username = fields.CharField(max_length=64, null=True, description="用户微信名称", index=True)
|
|
english_name = fields.CharField(max_length=64, null=True, description="英文名")
|
|
name = fields.CharField(max_length=64, null=True, description="用户名称", index=True)
|
|
realname = fields.CharField(max_length=64, null=True, description="用户真实名字", index=True)
|
|
alias = fields.CharField(max_length=30, null=True, description="别名")
|
|
avatar = fields.CharField(max_length=512, null=True, description="头像")
|
|
mobile = fields.CharField(max_length=11, null=True, description="手机号")
|
|
email = fields.CharField(max_length=64, null=True, description="邮箱")
|
|
gender = fields.IntField(null=True, description="性别")
|
|
|
|
erp_id = fields.IntField(null=True, description="ERP中的用户ID", index=True)
|
|
erp_name = fields.CharField(max_length=64, null=True, description="ERP中的用户名称")
|
|
crm_id = fields.IntField(null=True, description="星云有客中的用户ID", index=True)
|
|
crm_name = fields.CharField(max_length=64, null=True, description="星云有客中的用户名称")
|
|
|
|
def to_dict(self, exclude_fields=None, include_sensitive=False):
|
|
"""
|
|
自定义字典转换行为
|
|
|
|
Args:
|
|
exclude_fields: 要排除的字段列表
|
|
include_sensitive: 是否包含敏感信息(如手机号、邮箱等)
|
|
|
|
Returns:
|
|
dict: 转换后的字典
|
|
"""
|
|
exclude_fields = exclude_fields or ['staff_memberships']
|
|
data = {}
|
|
|
|
# 获取所有字段名
|
|
model_fields = self._meta.fields_map.keys()
|
|
|
|
for field_name in model_fields:
|
|
if field_name in exclude_fields:
|
|
continue
|
|
|
|
# 如果不包含敏感信息,则跳过敏感字段
|
|
if not include_sensitive and field_name in ['mobile', 'email']:
|
|
continue
|
|
|
|
value = getattr(self, field_name)
|
|
|
|
# 处理枚举字段
|
|
if field_name == 'role' and value is not None:
|
|
data[field_name] = value.value if hasattr(value, 'value') else value
|
|
# 处理布尔字段的默认值显示
|
|
elif field_name == 'is_quit':
|
|
data[field_name] = bool(value) if value is not None else False
|
|
elif field_name == 'has_external_user_permit':
|
|
data[field_name] = bool(value) if value is not None else False
|
|
else:
|
|
data[field_name] = value
|
|
|
|
return data
|
|
|
|
def to_public_dict(self):
|
|
"""
|
|
返回公开信息的字典(不包含敏感信息)
|
|
"""
|
|
sensitive_fields = ['mobile', 'email']
|
|
exclude_fields = ['wx_id_hash'] # 可能还有其他不想暴露的字段
|
|
all_exclude = sensitive_fields + exclude_fields
|
|
|
|
return self.to_dict(exclude_fields=all_exclude, include_sensitive=False)
|
|
|
|
def to_detail_dict(self):
|
|
"""
|
|
返回详细信息的字典(包含所有信息)
|
|
"""
|
|
return self.to_dict(include_sensitive=True)
|
|
|
|
def to_safe_dict(self, visible_fields=None):
|
|
"""
|
|
返回指定字段的安全字典
|
|
|
|
Args:
|
|
visible_fields: 指定要包含的字段列表,如果为None则使用默认安全字段
|
|
"""
|
|
if visible_fields is None:
|
|
# 默认的安全字段(不包含敏感信息)
|
|
visible_fields = [
|
|
'userid', 'vid', 'wx_nick_name', 'username', 'name',
|
|
'realname', 'alias', 'avatar', 'position', 'role',
|
|
'depart_ids', 'erpid', 'crmid', 'created_at', 'updated_at'
|
|
]
|
|
|
|
data = {}
|
|
for field_name in visible_fields:
|
|
if hasattr(self, field_name):
|
|
value = getattr(self, field_name)
|
|
if field_name == 'role' and value is not None:
|
|
data[field_name] = value.value if hasattr(value, 'value') else value
|
|
else:
|
|
data[field_name] = value
|
|
|
|
return data
|
|
|
|
class Meta:
|
|
table = "weixin_user"
|
|
|
|
class WeixinGroupChat(BaseModel, TimestampMixin):
|
|
chat_id = fields.CharField(max_length=64, description="群聊ID", index=True)
|
|
xingyun_chat_id = fields.IntField(null=True, description="星云中的群聊ID", index=True)
|
|
name = fields.CharField(max_length=64, null=True, description="群聊名称")
|
|
create_time = fields.IntField(description="创建时间")
|
|
admin_list = fields.JSONField(default=[], description="所有管理员")
|
|
owner = fields.CharField(max_length=64, null=True, description="群主")
|
|
member_version = fields.CharField(max_length=64, null=True, description="群成员版本")
|
|
external_user_count = fields.IntField(null=True, description="外部群成员数量")
|
|
external_member_list = fields.JSONField(default=[], description="外部群成员列表")
|
|
internal_member_count = fields.IntField(null=True, description="内部群成员数量")
|
|
internal_member_list = fields.JSONField(default=[], description="内部群成员列表")
|
|
avatars = fields.JSONField(default=[], description="群成员头像")
|
|
|
|
class Meta:
|
|
table = "weixin_group_chat"
|
|
|
|
class WeixinCustomer(BaseModel, TimestampMixin):
|
|
order_id = fields.CharField(max_length=64, null=True, description="订单ID", index=True)
|
|
shop_name = fields.CharField(max_length=64, null=True, description="店铺名称")
|
|
weixin_id = fields.CharField(max_length=64, null=True, description="微信体系中的id", index=True)
|
|
weixin_name = fields.CharField(max_length=64, null=True, description="微信体系中的用户名")
|
|
weixin_unionid = fields.CharField(max_length=64, null=True, description="微信体系中的unionid", index=True)
|
|
weixin_avatar = fields.CharField(max_length=512, null=True, description="微信头像")
|
|
xingyun_id = fields.IntField(null=True, description="星云有客中的用户ID", index=True)
|
|
xingyun_sex = fields.IntField(null=True, description="星云有客中的用户性别", index=True)
|
|
xingyun_name = fields.CharField(max_length=64, null=True, description="星云有客中的用户名")
|
|
xingyun_avatar = fields.CharField(max_length=512, null=True, description="星云有客头像")
|
|
xingyun_tags = fields.JSONField(default=[], null=True, description="星云用户标签")
|
|
xingyun_external_userid = fields.CharField(max_length=64, null=True, description="星云有客中的外部用户ID")
|
|
xingyun_sync = fields.BooleanField(default=False, description="是否已同步到星云", index=True)
|
|
# erp_id = fields.IntField(null=True, description="ERP中的客户ID", index=True)
|
|
# erp_name = fields.CharField(max_length=64, null=True, description="ERP中的客户名称")
|
|
# erp_avatar = fields.CharField(max_length=512, null=True, description="ERP头像")
|
|
taobao_id = fields.CharField(max_length=64, null=True, description="淘宝中的用户ID", index=True)
|
|
taobao_name = fields.CharField(max_length=64, null=True, description="淘宝中的用户名")
|
|
taobao_avatar = fields.CharField(max_length=512, null=True, description="淘宝头像")
|
|
need_confirm = fields.BooleanField(null=True, description="是否需要确认")
|
|
extra = fields.JSONField(default={}, description="额外信息")
|
|
|
|
# 使用 through 指向自定义中间模型
|
|
groups = fields.ManyToManyField(
|
|
"models.WeixinGroupChat",
|
|
through="customer_group", # 必须是中间模型的 table 名(或模型名)
|
|
related_name="customers"
|
|
)
|
|
|
|
@classmethod
|
|
def orm_format(cls, data: dict):
|
|
# 将不在字段中的字段保存到extra字段中
|
|
all_fields = set(cls._meta.fields_map.keys())
|
|
|
|
extra = {}
|
|
data_in = {"extra": extra}
|
|
for k, v in data.copy().items():
|
|
if k not in all_fields:
|
|
extra[k] = v
|
|
else:
|
|
data_in[k] = v
|
|
|
|
id = data_in.pop('id', None)
|
|
if id: data_in['platform_id'] = id
|
|
return data_in
|
|
|
|
def to_dict(self, *args, **kwargs):
|
|
# 类似于WeixinUser参数
|
|
return {
|
|
"id": self.id,
|
|
"userid": self.weixin_id,
|
|
"name": self.taobao_name,
|
|
"avatar": self.xingyun_avatar,
|
|
"weixin_name": self.weixin_name,
|
|
"role": 'buyer',
|
|
"is_customer": True,
|
|
}
|
|
# data = await super().to_dict(*args, **kwargs)
|
|
# data['id'] = data.pop('platform_id', None)
|
|
# data.update(data.pop('extra', {}))
|
|
# return data
|
|
|
|
def dump_dict(self):
|
|
all_fields = set(self._meta.fields_map.keys())
|
|
data = {}
|
|
for k, v in self.__dict__.items():
|
|
if k in all_fields:
|
|
data[k] = v
|
|
return data
|
|
|
|
@classmethod
|
|
def create_bind(cls, data: dict):
|
|
# 将不在字段中的字段保存到extra字段中
|
|
data_in = cls.orm_format(data)
|
|
return cls(**data_in)
|
|
|
|
class Meta:
|
|
table = "weixin_customer"
|
|
|
|
# === 中间模型:CustomerGroup ===
|
|
class CustomerGroup(BaseModel, TimestampMixin):
|
|
"""顾客与群聊的关联关系(带额外信息)"""
|
|
|
|
# 外键指向顾客
|
|
customer = fields.ForeignKeyField(
|
|
"models.WeixinCustomer",
|
|
related_name="group_memberships" # 从 Customer 反向查关系
|
|
)
|
|
|
|
# 外键指向群聊
|
|
group = fields.ForeignKeyField(
|
|
"models.WeixinGroupChat",
|
|
related_name="customer_memberships" # 从 Group 反向查关系
|
|
)
|
|
|
|
# 外键指向群聊
|
|
staff = fields.ForeignKeyField(
|
|
"models.WeixinUser",
|
|
related_name="staff_memberships" # 从 User 反向查关系
|
|
)
|
|
|
|
# 额外字段
|
|
join_time = fields.DatetimeField(auto_now_add=True, description="入群时间", index=True)
|
|
role = fields.CharField(
|
|
max_length=32,
|
|
default="member",
|
|
description="群内角色:member / admin / owner",
|
|
index=True
|
|
)
|
|
|
|
staff_userid = fields.CharField(max_length=64, null=True, description="客服ID", index=True)
|
|
customer_userid = fields.CharField(max_length=64, null=True, description="顾客的ID", index=True)
|
|
group_chatid = fields.CharField(max_length=64, null=True, description="群聊ID", index=True)
|
|
order_id = fields.CharField(max_length=64, null=True, description="订单ID", index=True)
|
|
shop_name = fields.CharField(max_length=64, null=True, description="店铺名称", index=True)
|
|
remark = fields.CharField(max_length=255, null=True, description="备注")
|
|
|
|
class Meta:
|
|
table = "customer_group"
|
|
# 确保同一个顾客不能重复加入同一个群
|
|
unique_together = ("customer", "group") |