462 lines
19 KiB
Python
462 lines
19 KiB
Python
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()
|
||
|