first commit
This commit is contained in:
@@ -0,0 +1,461 @@
|
||||
from tortoise.exceptions import IntegrityError
|
||||
from app.core.crud import CRUDBase
|
||||
from app.schemas.msg import (
|
||||
MsgCreate,
|
||||
MsgUpdate,
|
||||
MsgNewOrder,
|
||||
FollowFormErp,
|
||||
MsgType
|
||||
)
|
||||
import httpx
|
||||
from app.models.msg import Msg, Follow
|
||||
from app.models.weixin import WeixinCustomer, WeixinUser
|
||||
from some_sdk.wk_weixin_sdk.apis.extern_user import get_external_user_chat_info
|
||||
from some_sdk.services.binder import lintao_client, feishu_client
|
||||
from some_sdk.lintao_sdk.biz.by_order import get_order_relative_user
|
||||
from some_sdk.feishu_sdk.apis.doc import batch_create as create_feishu_records
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from app.utils.common import async_split_generator, split_generator, gen_random_str
|
||||
from typing import List
|
||||
import re
|
||||
|
||||
import hashlib
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
import asyncio
|
||||
_sync_to_feishu_lock = asyncio.Lock()
|
||||
|
||||
class MsgController(CRUDBase[Msg, MsgCreate, MsgUpdate]):
|
||||
def __init__(self):
|
||||
super().__init__(model=Msg)
|
||||
|
||||
async def send_to_wexin(self, data:dict ):
|
||||
# pass
|
||||
url = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=57ba9cd5-2c62-43dd-bfea-78c7b4073128'
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.post(url,
|
||||
headers={"Content-Type": "application/json"},
|
||||
json=data,
|
||||
)
|
||||
data = resp.json()
|
||||
print(data)
|
||||
return data
|
||||
|
||||
async def send_order_to_weixin(self, msg: Msg):
|
||||
"""
|
||||
极简订单通知:仅展示店铺、旺旺ID、金额、归属客服
|
||||
"""
|
||||
url = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=57ba9cd5-2c62-43dd-bfea-78c7b4073128'
|
||||
|
||||
order_list = msg.detail or []
|
||||
if not order_list:
|
||||
logger.warning("订单详情为空,跳过发送")
|
||||
return
|
||||
|
||||
order = order_list[0]
|
||||
# users = order.get("users", [])
|
||||
# buyer = next((u for u in users if u.get("role") == "buyer"), None)
|
||||
|
||||
# 关键字段
|
||||
shop_name = order.get("shop_name", "未知店铺")
|
||||
buyer_name = msg.title
|
||||
# buyer_name = buyer.get("name") if buyer else ""
|
||||
# buyer_name = buyer_name or msg.content.split(")", 1)[0].split("(")[1]
|
||||
|
||||
is_refund = order.get("is_refund", 0) > 0
|
||||
msg_title = "💰 用户退款" if is_refund else "🛒 新订单"
|
||||
|
||||
real_payment = order.get("real_payment", "0.00")
|
||||
qiwei_name = msg.owner_name or "未知客服"
|
||||
|
||||
# 极简 Markdown
|
||||
markdown_content = f"""# {msg_title}\n
|
||||
|
||||
**店铺**:{shop_name}
|
||||
**旺旺ID**:`{buyer_name}`
|
||||
**金额**:¥{real_payment}
|
||||
**企微客服**:{qiwei_name}
|
||||
**订单编号**:{order.get("trade_no", "未知订单号")}
|
||||
"""
|
||||
|
||||
data = {
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
"content": markdown_content
|
||||
}
|
||||
}
|
||||
result = await self.send_to_wexin(data)
|
||||
logger.info(f"企业微信极简订单通知发送结果: {result}")
|
||||
|
||||
|
||||
async def parse_order_msg(self, msg: Msg, send_set: set, msg_dict: dict):
|
||||
title = msg.title
|
||||
try:
|
||||
if title not in send_set:
|
||||
send_set.add(title)
|
||||
# try: await self.send_to_wexin(msg)
|
||||
owner_name = '、'.join([ (name or '').split('(')[0].split('vip客服-')[-1].split('-印刷定制')[0] for name in msg_dict[title] ])msg.owner_name = owner_name
|
||||
|
||||
try: await self.send_order_to_weixin(msg)
|
||||
except Exception as e:
|
||||
logger.error(f'发送订单消息到微信失败,{e}')
|
||||
return
|
||||
|
||||
# await self.send_to_wexin(msg, mentioned_list=msg_dict[title])
|
||||
logger.info(f'发送消息到微信成功,{msg.content}')
|
||||
|
||||
msg.is_send = True
|
||||
msg.send_at = datetime.now()
|
||||
await msg.save()
|
||||
except Exception as e:
|
||||
logger.error(f'发送消息到微信失败,{e}')
|
||||
|
||||
async def new_system_msg(self, title: str, content: str, owner_name: str = None):
|
||||
await self.create(
|
||||
dict(
|
||||
hash_id=hashlib.md5(f'{title}{content}{owner_name}'.encode()).hexdigest(),
|
||||
title=title,
|
||||
content=content,
|
||||
type=MsgType.SYSTEM,
|
||||
owner_name=owner_name,
|
||||
)
|
||||
)
|
||||
|
||||
async def send_system_msg(self, msg: Msg):
|
||||
msg.send_at = datetime.now()
|
||||
markdown_content = f"""# 📣 系统通知 \n
|
||||
## {msg.title} \n
|
||||
{msg.content} \n
|
||||
|
||||
{msg.send_at.strftime("%Y-%m-%d %H:%M:%S")}
|
||||
"""
|
||||
|
||||
data = {
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
"content": markdown_content
|
||||
}
|
||||
}
|
||||
result = await self.send_to_wexin(data)
|
||||
logger.info(f"企业微信极简订单通知发送结果: {result}")
|
||||
|
||||
msg.is_send = True
|
||||
await msg.save()
|
||||
|
||||
async def sync_msg(self):
|
||||
msg_list = await self.model.filter(is_send=False)
|
||||
|
||||
msg_dict = {}
|
||||
for msg in msg_list:
|
||||
title = msg.title
|
||||
msg_dict[title] = msg_dict.get(title, []) + [msg.owner_name]
|
||||
|
||||
send_set = set()
|
||||
for msg in msg_list:
|
||||
title = msg.title
|
||||
if msg.type == MsgType.NEW_ORDER:
|
||||
await self.parse_order_msg(msg, send_set, msg_dict)
|
||||
elif msg.type == MsgType.SYSTEM:
|
||||
await self.send_system_msg(msg)
|
||||
|
||||
async def fill_customer_info(self):
|
||||
msg_orm_list = await self.model.filter(id__gt=306)
|
||||
|
||||
update_list = []
|
||||
for msg in msg_orm_list:
|
||||
detail = msg.detail or []
|
||||
taobao_name = msg.title
|
||||
|
||||
detail_list = []
|
||||
for order_item in detail:
|
||||
customer_orm = await WeixinCustomer.filter(taobao_name=taobao_name).first()
|
||||
if not customer_orm:
|
||||
logger.warning(f'未找到淘宝用户{taobao_name}的企微客户')
|
||||
continue
|
||||
|
||||
for user in order_item.get("users", []):
|
||||
if user.get("role") != "buyer": continue
|
||||
user.update(customer_orm.to_dict())
|
||||
break
|
||||
|
||||
detail_list.append(order_item)
|
||||
|
||||
if detail_list:
|
||||
msg.detail = detail_list
|
||||
update_list.append(msg)
|
||||
|
||||
if update_list:
|
||||
await self.model.bulk_update(update_list, fields=['detail'])
|
||||
|
||||
|
||||
async def new_order(self, order_id: str, customer_orm: WeixinCustomer, is_refund: bool = False):
|
||||
|
||||
msg_set = set()
|
||||
msg_list = []
|
||||
now = datetime.now()
|
||||
|
||||
order_list = get_order_relative_user(lintao_client, order_id)
|
||||
order_unique_list = {}
|
||||
async for order in order_list:
|
||||
order_state_string = order.get('order_state_string')
|
||||
trade_no = order.get('trade_no')
|
||||
if not is_refund and order_state_string not in ['待领单', '待抢单']:
|
||||
logger.warning(f'订单{trade_no} 状态为{order_state_string},跳过')
|
||||
continue
|
||||
|
||||
remark = order.get('remark')
|
||||
if not is_refund and remark and '企微联系' in remark:
|
||||
logger.warning(f'订单{trade_no} 状态为{order_state_string}, 备注:{remark}, 已备注企微联系,跳过')
|
||||
continue
|
||||
|
||||
create_time = order.get('create_time')
|
||||
if create_time and not is_refund:
|
||||
create_time = datetime.fromisoformat(create_time.replace("Z", "+00:00"))
|
||||
if (now - create_time).total_seconds() > 3600:
|
||||
logger.warning(f'订单{order_id} 创建时间({create_time})与当前时间({now})相差超过60秒,跳过')
|
||||
continue
|
||||
|
||||
if trade_no not in order_unique_list:
|
||||
order_unique_list[trade_no] = order
|
||||
logger.info(f'订单{order_id} 订单号{trade_no} 创建时间{create_time} 订单状态:{order_state_string} {order.get("title")}')
|
||||
|
||||
# 填充买家信息
|
||||
for user in order.get("users", []):
|
||||
if user.get("role") != "buyer": continue
|
||||
user.update(customer_orm.to_dict())
|
||||
break
|
||||
|
||||
|
||||
if not order_unique_list:
|
||||
logger.warning(f'订单{order_id} 没有符合条件的订单,跳过')
|
||||
return
|
||||
|
||||
chat_info = await get_external_user_chat_info(customer_orm.weixin_id)
|
||||
follow_user_list = chat_info.get('follow_user', [])
|
||||
|
||||
order_list = list(order_unique_list.values())
|
||||
order_info = [
|
||||
f"在({order.get('shop_name')})下单{order.get('price')}元,单号({order.get('trade_no')})"
|
||||
for order in order_list
|
||||
]
|
||||
for follow_user in follow_user_list:
|
||||
follow_userid = follow_user.get('userid')
|
||||
weixin_user_orm: WeixinUser = await WeixinUser.filter(userid=follow_userid).first()
|
||||
owner_name = '未知'
|
||||
if weixin_user_orm: owner_name = weixin_user_orm.english_name or weixin_user_orm.username
|
||||
|
||||
qiwei_customer_name = customer_orm.taobao_name or customer_orm.weixin_name
|
||||
|
||||
content = f"{'退款' if is_refund else ''}订单({qiwei_customer_name}) -({owner_name})- 刚{', '.join(order_info)}"
|
||||
logger.info(f'新订单通知:{content}')
|
||||
msg_hash_id = hashlib.md5(content.encode('utf-8')).hexdigest()
|
||||
if msg_hash_id in msg_set: continue
|
||||
msg_set.add(msg_hash_id)
|
||||
|
||||
msg = self.model(
|
||||
hash_id=msg_hash_id,
|
||||
title=qiwei_customer_name,
|
||||
content=content,
|
||||
detail=order_list,
|
||||
type=MsgType.NEW_ORDER,
|
||||
owner_id=follow_userid,
|
||||
owner_name=owner_name
|
||||
)
|
||||
msg_list.append(msg)
|
||||
|
||||
if msg_list:
|
||||
try: await self.model.bulk_create(msg_list)
|
||||
except IntegrityError as e:
|
||||
if "Duplicate entry" in str(e) and "hash_id" in str(e):
|
||||
logger.info("检测到重复消息,已跳过")
|
||||
# 忽略或处理重复
|
||||
else:
|
||||
# 其他完整性错误(如外键失败),应重新抛出
|
||||
raise
|
||||
|
||||
async def get_order_user_days_before(self, days: int):
|
||||
now = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
start_date, otherMemo = (now - timedelta(days=days)).strftime("%Y-%m-%d %H:%M:%S"), "企微"
|
||||
|
||||
# 琪琪——松屿,小小丽——麦子,小小北——大喜,小泉——苏苏,小洋——东东,蓝莓——小满
|
||||
index = 0
|
||||
update_list, add_list = [], []
|
||||
order_iter_list = [
|
||||
(get_order_relative_user(lintao_client, start_date=start_date, otherMemo=otherMemo), ''),
|
||||
(get_order_relative_user(lintao_client, start_date=start_date, customer='琪琪'), '松屿'),
|
||||
(get_order_relative_user(lintao_client, start_date=start_date, customer='小小丽'), '麦子'),
|
||||
(get_order_relative_user(lintao_client, start_date=start_date, customer='小小北'), '大喜'),
|
||||
(get_order_relative_user(lintao_client, start_date=start_date, customer='小泉'), '苏苏'),
|
||||
(get_order_relative_user(lintao_client, start_date=start_date, customer='小洋'), '东东'),
|
||||
(get_order_relative_user(lintao_client, start_date=start_date, customer='蓝莓'), '小满'),
|
||||
]
|
||||
for order_iter, staff_name in order_iter_list:
|
||||
async for order_list in async_split_generator(order_iter, 10):
|
||||
order_dict = {}
|
||||
for order in order_list:
|
||||
designer_list = list(filter(lambda x: x.get("role") == "desiger", order.get("users", [])))
|
||||
designer = designer_list[0] if designer_list else None
|
||||
if not designer: continue
|
||||
|
||||
name = designer.get("name", "")
|
||||
if not name: continue
|
||||
|
||||
key = tuple([order.get("trade_no", ""), name])
|
||||
if key in order_dict: continue
|
||||
order_dict[key] = order
|
||||
index += 1
|
||||
|
||||
_update_list, _add_list = await self.process_order_dict(order_dict, staff_name=staff_name)
|
||||
|
||||
if _update_list and _add_list:
|
||||
logger.info(f'第{index}个订单 处理 {len(_update_list)} 条更新记录, {len(_add_list)} 条新增记录')
|
||||
update_list.extend(_update_list)
|
||||
add_list.extend(_add_list)
|
||||
|
||||
print(start_date)
|
||||
_update_list, _add_list = [], []
|
||||
|
||||
async with _sync_to_feishu_lock:
|
||||
task_id = gen_random_str()
|
||||
_update_list, _add_list = await self.sync_to_feishu(task_id)
|
||||
|
||||
return {
|
||||
"update_count": len(update_list),
|
||||
"add_count": len(add_list),
|
||||
"add_count_to_feishu": len(_add_list),
|
||||
"update_count_to_feishu": len(_update_list),
|
||||
"start_date": start_date,
|
||||
"remark": otherMemo
|
||||
}
|
||||
|
||||
async def process_order_dict(self, order_dict: dict, staff_name: str = ''):
|
||||
async def to_follow_orm(order_info: dict):
|
||||
follow_orm = Follow(**FollowFormErp(**order_info).model_dump())
|
||||
# print(follow_orm.order_id, order_info.get("users", []))
|
||||
|
||||
for user in order_info.get("users", []):
|
||||
if user.get("role") == "desiger":
|
||||
follow_orm.designer_id = user.get("id", "")
|
||||
follow_orm.designer_name = user.get("name", "")
|
||||
elif user.get("role") == "taobao_kefu":
|
||||
follow_orm.kefu_id = user.get("id", "")
|
||||
follow_orm.kefu_name = user.get("name", "")
|
||||
elif user.get("role") == "buyer":
|
||||
follow_orm.customer_taobao_id = user.get("id", "")
|
||||
follow_orm.customer_name = user.get("name", "")
|
||||
|
||||
CustomerUser = await WeixinCustomer.filter(taobao_id=follow_orm.customer_taobao_id).first()
|
||||
if CustomerUser:
|
||||
follow_orm.customer_id = CustomerUser.id
|
||||
|
||||
# 企微员工
|
||||
if staff_name:
|
||||
follow_orm.staff_name = staff_name
|
||||
staff_orm = await WeixinUser.filter(english_name=f"{follow_orm.staff_name}-印刷定制").first()
|
||||
if staff_orm:
|
||||
follow_orm.staff_id = staff_orm.userid
|
||||
else:
|
||||
remark = follow_orm.remark
|
||||
if remark:
|
||||
staff_name_match = re.search(r"企微联系(.*?)拉群", remark)
|
||||
if staff_name_match:
|
||||
follow_orm.staff_name = staff_name_match.group(1).strip()
|
||||
if follow_orm.staff_name:
|
||||
staff_orm = await WeixinUser.filter(english_name=f"{follow_orm.staff_name}-印刷定制").first()
|
||||
if staff_orm:
|
||||
follow_orm.staff_id = staff_orm.userid
|
||||
|
||||
if not staff_name_match:
|
||||
follow_orm.staff_name = remark
|
||||
|
||||
return follow_orm
|
||||
|
||||
update_list = []
|
||||
follow_list = await Follow.filter(order_id__in=[key[0] for key in order_dict.keys()])
|
||||
keys, update_keys = Follow.get_all_keys(exclude_fields=['id', 'updated_at', 'created_at', 'feishu_record_id', 'is_update_to_feishu', 'is_add_to_feishu']), set()
|
||||
for follow in follow_list:
|
||||
key = tuple([follow.order_id, follow.designer_name])
|
||||
if key not in order_dict: continue
|
||||
|
||||
order_info = order_dict.pop(key, None)
|
||||
follow_orm = await to_follow_orm(order_info)
|
||||
# 比较是否一样,不一样的话,更新
|
||||
updated_dict = {}
|
||||
for key in keys:
|
||||
origin_value = str(getattr(follow, key))
|
||||
value = str(getattr(follow_orm, key))
|
||||
if origin_value != value:
|
||||
update_keys.add(key)
|
||||
setattr(follow, key, value)
|
||||
updated_dict[key] = f'{origin_value} -> {value}'
|
||||
|
||||
if updated_dict:
|
||||
follow_orm.is_update_to_feishu = False
|
||||
update_keys.add('is_update_to_feishu')
|
||||
update_list.append(follow)
|
||||
logger.info(f'订单 {follow.order_id} 设计员 {follow.designer_name} 更新字段 {updated_dict}')
|
||||
|
||||
if update_list:
|
||||
await Follow.bulk_update(update_list, fields=update_keys)
|
||||
|
||||
add_list = []
|
||||
for key, order_info in order_dict.items():
|
||||
follow_orm = await to_follow_orm(order_info)
|
||||
add_list.append(follow_orm)
|
||||
|
||||
if add_list:
|
||||
await Follow.bulk_create(add_list)
|
||||
|
||||
return update_list, add_list
|
||||
|
||||
async def sync_to_feishu(self, task_id: str):
|
||||
|
||||
add_list = []
|
||||
# 需要追加的记录
|
||||
follow_list = await Follow.filter(is_update_to_feishu=False, feishu_record_id__isnull=True)
|
||||
if follow_list:
|
||||
for item_list in split_generator(follow_list, 100):
|
||||
result = await create_feishu_records(
|
||||
feishu_client,
|
||||
app_token="HPiNbbW3YaYhBjsjLQychK9gnbf",
|
||||
table_id="tblQGVfmglJgZuBf",
|
||||
records=[{"fields": item.to_feishu_record(fields={"同步任务号": task_id})["fields"]} for item in item_list],
|
||||
)
|
||||
|
||||
_update_list = []
|
||||
result_list = result.get("data", {}).get("records", [])
|
||||
for record, item in zip(result_list, item_list):
|
||||
item.feishu_record_id = record["record_id"]
|
||||
item.is_add_to_feishu = True
|
||||
item.is_update_to_feishu = True
|
||||
_update_list.append(item)
|
||||
|
||||
if _update_list:
|
||||
await Follow.bulk_update(_update_list, fields=['feishu_record_id', 'is_add_to_feishu', 'is_update_to_feishu'])
|
||||
|
||||
logger.info(f'追加 {len(_update_list)} 条记录到飞书')
|
||||
add_list.extend(_update_list)
|
||||
|
||||
update_list = []
|
||||
# 需要更新的记录
|
||||
# follow_list = await Follow.filter(is_update_to_feishu=False, feishu_record_id__isnull=False)
|
||||
# if follow_list:
|
||||
# for item_list in split_generator(follow_list, 100):
|
||||
# await create_feishu_records(
|
||||
# get_feishu_client,
|
||||
# app_token="JTqnbdjb7aEMm3srrLfcidYcnxc",
|
||||
# table_id="tblXq8AmG22uK5n1",
|
||||
# records=item_list,
|
||||
# )
|
||||
|
||||
return update_list, add_list
|
||||
|
||||
async def set_read(self, id: str):
|
||||
await Msg.filter(id=id).update(is_read=True, read_at=datetime.now())
|
||||
|
||||
msg_controller = MsgController()
|
||||
|
||||
Reference in New Issue
Block a user