1.客户信息修改,将硬编码改为动态取值

2.新增项目批量停用、材料批量修改功能
This commit is contained in:
2026-01-08 10:18:18 +08:00
parent 11e4151395
commit c4c4ccc7e9
7 changed files with 300 additions and 270 deletions
+115 -27
View File
@@ -1,5 +1,5 @@
"""
F6 插件模块
F6 后台执行模块
本模块提供 F6 插件相关的功能,包括:
- 文件上传和校验
@@ -7,6 +7,8 @@ F6 插件模块
- 历史记录删除
- 客户信息管理
- 车辆信息管理
- 项目信息批量启停
- 材料信息批量修改
依赖:
- requests: HTTP 请求
@@ -31,6 +33,10 @@ from app.tasks.delete_tasks import (
delete_customer_background,
delete_car_background
)
from app.tasks.material_tasks import (
batch_disable_projects,
batch_modify_materials,
)
from app.tasks.customer_tasks import modify_customer_info_background
from app.tasks.bi_tasks import bi_task_background
@@ -65,26 +71,26 @@ class F6PluginModule:
Returns:
tuple: 包含文件保存路径和处理后的数据的元组。如果文件保存成功,返回保存路径和数据;如果失败,返回 None 和数据。
"""
data = api_instance.entry_data_get(data=data,replace= True)
data = api_instance.entry_data_get(data=data, replace=True)
print(data)
try:
# 安全地访问附件信息
data_dict = data.get('data', {})
attachments = data_dict.get('附件', [])
if not attachments or len(attachments) == 0:
print('上传url未读取到,或无上传文件: 附件列表为空')
save_path = ''
return save_path, data
first_attachment = attachments[0]
url = first_attachment.get('url')
if not url:
print('上传url未读取到,或无上传文件: URL为空')
save_path = ''
return save_path, data
print(url)
except (KeyError, IndexError, TypeError) as e:
print(f'上传url未读取到,或无上传文件:{e}')
@@ -118,8 +124,7 @@ class F6PluginModule:
else:
return None, data
def check_file(self, data: Dict[str, Any]) -> Dict[str, str]: # 校验上传文件
def check_file(self, data: Dict[str, Any]) -> dict[str, str] | None: # 校验上传文件
"""
校验上传文件。
@@ -143,7 +148,7 @@ class F6PluginModule:
# 安全地获取 Action 字段
data_dict = data1.get('data', {})
action = data_dict.get('Action(隐藏)')
if not action:
return {'msg': '缺少Action字段,无法校验文件'}
@@ -176,7 +181,6 @@ class F6PluginModule:
else:
return {'msg': '当前节点无附件上传', 'check': ''}
@staticmethod
def create_brand(data: Dict[str, Any]) -> Dict[str, str]:
"""
@@ -191,7 +195,7 @@ class F6PluginModule:
Returns:
Dict[str, str]: 包含执行状态的字典,{'msg': '正在执行', 'msg_details': '正在执行,请稍后看结果'}
"""
entry_data = api_instance.entry_data_get(data=data,replace= True)
entry_data = api_instance.entry_data_get(data=data, replace=True)
print('执行 品牌批量新建')
username = entry_data['data']['账号']
password = entry_data['data']['密码']
@@ -232,7 +236,7 @@ class F6PluginModule:
Returns:
Dict[str, str]: 包含执行状态的字典
"""
entry_data = api_instance.entry_data_get(data=data,replace= True)
entry_data = api_instance.entry_data_get(data=data, replace=True)
username = entry_data['data']['账号']
password = entry_data['data']['密码']
company_name = entry_data['data']['公司名称']
@@ -279,7 +283,7 @@ class F6PluginModule:
Dict[str, str]: 包含执行状态的字典
"""
print('执行 删除客户')
entry_data = api_instance.entry_data_get(data=data,replace= True)
entry_data = api_instance.entry_data_get(data=data, replace=True)
username = entry_data['data']['账号']
password = entry_data['data']['密码']
company_name = entry_data['data']['公司名称']
@@ -298,7 +302,8 @@ class F6PluginModule:
thread = threading.Thread(target=delete_customer_background,
args=(data, cookies, total,))
thread.start()
return {'msg': '正在执行中', 'msg_details': f'总计{total}条数据,8-20点3.5s一条数据,其余时间1.5s一条数据'}
return {'msg': '正在执行中',
'msg_details': f'总计{total}条数据,8-20点3.5s一条数据,其余时间1.5s一条数据'}
else:
return {'msg': '未执行', 'msg_details': '无客户信息'}
else:
@@ -318,7 +323,7 @@ class F6PluginModule:
Returns:
Dict[str, str]: 包含执行状态的字典
"""
entry_data = api_instance.entry_data_get(data=data,replace= True)
entry_data = api_instance.entry_data_get(data=data, replace=True)
username = entry_data['data']['账号']
password = entry_data['data']['密码']
company_name = entry_data['data']['公司名称']
@@ -351,20 +356,21 @@ class F6PluginModule:
else:
return {'msg': '未执行', 'msg_details': '登录失败'}
def modify_customer_info(self, data: Dict[str, str]):
@staticmethod
def modify_customer_info(data: Dict[str, str]):
"""
修改客户信息
从简道云获取修改客户信息请求,读取 Excel 文件,并在后台线程中批量修改客户信息。
立即返回"正在执行中"的提示,实际修改在后台线程中执行。
Args:
data: 包含表单ID(api_key)、表单ID(entry_id)、数据ID(data_id)的字典
Returns:
Dict[str, str]: 包含执行状态的字典
"""
entry_data = api_instance.entry_data_get(data=data,replace= True)
entry_data = api_instance.entry_data_get(data=data, replace=True)
username = entry_data['data']['账号']
password = entry_data['data']['密码']
company_name = entry_data['data']['公司名称']
@@ -389,6 +395,90 @@ class F6PluginModule:
else:
return {'msg': '未执行', 'msg_details': 'cookies获取失败'}
@staticmethod
def disable_projects(data: Dict[str, Any]) -> Dict[str, str]:
"""
项目批量启停
从简道云获取项目批量启停请求,读取 Excel 文件,并在后台线程中批量启停项目。
立即返回"正在执行"的提示,实际创建在后台线程中执行。
Args:
data: 包含表单ID(api_key)、表单ID(entry_id)、数据ID(data_id)的字典
Returns:
Dict[str, str]: 包含执行状态的字典,{'msg': '正在执行', 'msg_details': '正在执行,请稍后看结果'}
"""
entry_data = api_instance.entry_data_get(data=data, replace=True)
print('执行 项目批量停用/启用')
username = entry_data['data']['账号']
password = entry_data['data']['密码']
company_name = entry_data['data']['公司名称']
save_path = entry_data['data']['文件保存地址']
option = entry_data['data']['项目材料批量操作']
login_response = F6Module.login_in(username, password, company_name)
if login_response is None:
return {'msg': '登录失败'}
try:
df = pd.read_excel(save_path, sheet_name=0, dtype='string')
except Exception as e:
return {'msg': f'读取Excel文件失败: {str(e)},文件路径:{save_path}'}
cookies = requests.utils.dict_from_cookiejar(login_response.cookies)
try:
thread = threading.Thread(target=batch_disable_projects,
args=(data, cookies, df, save_path,option))
thread.start()
except Exception as e:
print(f'创建线程失败: {str(e)}')
return {'msg': '正在执行', 'msg_details': '正在执行,请稍后看结果'}
@staticmethod
def disable_material(data: Dict[str, Any]) -> Dict[str, str]:
"""
材料批量启停
从简道云获取材料批量启停请求,读取 Excel 文件,并在后台线程中批量启停材料。
立即返回"正在执行"的提示,实际创建在后台线程中执行。
Args:
data: 包含表单ID(api_key)、表单ID(entry_id)、数据ID(data_id)的字典
Returns:
Dict[str, str]: 包含执行状态的字典,{'msg': '正在执行', 'msg_details': '正在执行,请稍后看结果'}
"""
entry_data = api_instance.entry_data_get(data=data, replace=True)
print('执行 材料批量停用/启用')
username = entry_data['data']['账号']
password = entry_data['data']['密码']
company_name = entry_data['data']['公司名称']
save_path = entry_data['data']['文件保存地址']
option = entry_data['data']['项目材料批量操作']
login_response = F6Module.login_in(username, password, company_name)
if login_response is None:
return {'msg': '登录失败'}
try:
df = pd.read_excel(save_path, sheet_name=0, dtype='string')
except Exception as e:
return {'msg': f'读取Excel文件失败: {str(e)},文件路径:{save_path}'}
cookies = requests.utils.dict_from_cookiejar(login_response.cookies)
try:
thread = threading.Thread(target=batch_modify_materials,
args=(data, cookies, df, save_path, option))
thread.start()
except Exception as e:
print(f'创建线程失败: {str(e)}')
return {'msg': '正在执行', 'msg_details': '正在执行,请稍后看结果'}
@staticmethod
def bi_task(data: Dict[str, Any]) -> Dict[str, str]:
"""
@@ -403,15 +493,15 @@ class F6PluginModule:
Returns:
Dict[str, str]: 包含执行状态的字典,{'msg': '正在执行', 'msg_details': '正在执行,请稍后看结果'}
"""
entry_data = api_instance.entry_data_get(data=data,replace= True)
entry_data = api_instance.entry_data_get(data=data, replace=True)
print('执行 BI任务')
# 获取必要的参数(根据实际需求调整)
username = entry_data['data'].get('账号')
password = entry_data['data'].get('密码')
company_name = entry_data['data'].get('公司名称')
save_path = entry_data['data'].get('文件保存地址')
# 如果需要登录F6系统
cookies = None
if username and password and company_name:
@@ -419,7 +509,7 @@ class F6PluginModule:
if login_response is None:
return {'msg': '登录失败', 'msg_details': '无法登录F6系统'}
cookies = requests.utils.dict_from_cookiejar(login_response.cookies)
# 如果需要读取Excel文件
df = None
if save_path:
@@ -427,7 +517,7 @@ class F6PluginModule:
df = pd.read_excel(save_path, sheet_name=0, dtype='string')
except Exception as e:
return {'msg': f'读取Excel文件失败: {str(e)},文件路径:{save_path}'}
# 启动后台线程执行BI任务
try:
thread = threading.Thread(target=bi_task_background,
@@ -438,5 +528,3 @@ class F6PluginModule:
return {'msg': '任务启动失败', 'msg_details': f'无法启动后台任务: {str(e)}'}
return {'msg': '正在执行', 'msg_details': '正在执行,请稍后看结果'}
+1 -1
View File
@@ -1,5 +1,5 @@
"""
F6 系统模块
F6 前端即时响应模块
本模块提供 F6 系统相关的功能,包括:
- 登录和认证
+8 -1
View File
@@ -29,6 +29,11 @@ from app.tasks.customer_tasks import modify_customer_info_background
# BI相关任务
from app.tasks.bi_tasks import bi_task_background
from app.tasks.material_tasks import ( \
batch_modify_materials,
batch_disable_projects
)
__all__ = [
# 通用功能
'update_jiandaoyun',
@@ -43,5 +48,7 @@ __all__ = [
'modify_customer_info_background',
# BI任务
'bi_task_background',
# 项目材料任务
'batch_disable_projects',
'batch_modify_materials',
]
+2 -2
View File
@@ -2,8 +2,8 @@
BI相关后台任务模块
本模块包含BI相关的后台任务,包括:
- BI数据处理
- BI报表生成
- TODO BI数据处理
这些任务在后台线程中执行,不会阻塞主请求。
执行完成后会更新简道云表单并自动提交工作流。
+2 -3
View File
@@ -137,8 +137,6 @@ def execute_failure_handler(data: Dict[str, Any]):
api_instance.data_batch_create(pay_load)
def get_operate_org_id(cookies: Dict[str, str]) -> Optional[str]:
"""
获取操作门店ID
@@ -154,7 +152,7 @@ def get_operate_org_id(cookies: Dict[str, str]) -> Optional[str]:
注意:
如果未获取到门店信息或门店ID为空,会记录错误日志并返回 None
"""
org_url = "https://yunxiu.f6car.cn/hive/org/getPageOrgGroupMembers?currentPage=1&pageSize=10&name="
org_url = "https://yunxiu.f6car.cn/hive/org/getPageOrgGroupMembers?currentPage=1&pageSize=100&name="
try:
org_res = requests.get(url=org_url, cookies=cookies)
@@ -243,3 +241,4 @@ def get_card_list(
logger.error(f"获取会员卡列表时发生错误: {e}")
return card_list
+165 -229
View File
@@ -10,7 +10,7 @@ import logging
import requests
import pandas as pd
import time
import re
import os
from typing import Dict, Any, Optional
from app.tasks.common import update_jiandaoyun, approve_workflow
@@ -20,257 +20,193 @@ logger = logging.getLogger('app')
def modify_customer_info_background(data: Dict[str, Any], cookies: Dict[str, str], df: pd.DataFrame, save_path: str):
"""
修改客户信息后台任务
在后台线程中批量修改客户信息,从 Excel 文件中读取客户手机号和修改信息。
执行完成后会更新简道云表单并自动提交工作流。
Args:
data: 包含表单ID(api_key)、表单ID(entry_id)、数据ID(data_id)的字典
cookies: 用户登录 F6 系统的 cookies 信息
df: Excel 文件读取的内容,DataFrame 格式,第一列为客户手机号
save_path: Excel 文件保存的地址,执行完成后会删除此文件
Returns:
None
注意:
- 根据客户手机号匹配客户信息
- 执行完成后会自动删除上传的文件
- 执行结果会更新到简道云表单
"""
df.where(pd.notnull(df), None)
try:
# 替换 NaN 为 None,便于后续判断
df = df.where(pd.notnull(df), None)
logger.info("获取当前客户下所有客户信息")
logger.info("开始获取当前客户下所有客户信息")
params = {
'pageSize': 100,
'pageNo': '1',
}
res = requests.get(
'https://yunxiu.f6car.cn/member/customer/listForPermission',
params=params,
cookies=cookies,
)
total = int(res.json().get("data").get("total"))
total_pages = (total // params["pageSize"]) + (1 if total % params["pageSize"] > 0 else 0)
print(f"总计{total_pages}")
all_customers = []
max_retries = 10
retry_count = 0
for page in range(1, total_pages + 1):
print(f"正在请求第 {page} 页...")
params["pageNo"] = page
while retry_count < max_retries:
response = requests.get(
'https://yunxiu.f6car.cn/member/customer/listForPermission',
params=params,
cookies=cookies,
timeout=10
)
time.sleep(1)
if response.status_code == 200:
suppliers = response.json().get("data", {}).get("data", [])
all_customers.extend(suppliers)
break
else:
retry_count += 1
print(f"请求第 {page} 页失败,正在重试(第 {retry_count} 次)...")
time.sleep(3)
# 获取专属运营顾问列表
json_data = {
'includeStopedEmployee': False,
'pageSize': 1000,
'filterNullUser': False,
'keyword': '',
'idOwnOrgList': [],
}
response = requests.post(
'https://yunxiu.f6car.cn/hive/employee/searchStaffInGroup',
cookies=cookies,
json=json_data,
)
staff_list = response.json().get("data").get("list")
name_to_userid = {
emp['name']: emp['userId']
for emp in staff_list
if emp['userId'] is not None
}
df['userId'] = df['专属运营顾问'].map(name_to_userid)
def extract_province_city_district(address: Optional[str]) -> Dict[str, Optional[str]]:
"""安全解析省市区信息,所有返回值都可能为None"""
if not address:
return {'': None, '': None, '': None}
try:
pattern = r'(?P<省>(?:[\u4e00-\u9fa5]+(?:省|自治区|特别行政区))?)' \
r'(?P<市>(?:[\u4e00-\u9fa5]+(?:市|自治州|地区|盟))?)' \
r'(?P<区>(?:[\u4e00-\u9fa5]+区|[\u4e00-\u9fa5]+县|[\u4e00-\u9fa5]+旗)?)'
match = re.match(pattern, address.strip())
return {k: v if v else None for k, v in match.groupdict().items()} if match else {'': None, '': None,
'': None}
except Exception:
return {'': None, '': None, '': None}
def safe_get(d: Optional[Dict], *keys, default=None):
"""多层字典安全获取值,始终返回可能为None的值"""
if not isinstance(d, dict):
return default
for key in keys:
d = d.get(key, {})
if not isinstance(d, dict):
break
return d if d != {} else default
def convert_to_request_data(original_data: Optional[Dict[str, Any]], df: pd.DataFrame) -> Dict[str, Any]:
"""
完全安全的字典转换函数
特点:
1. 每个字段的值都可能为None
2. 不会因为任何字段为空而报错
3. 不使用任何默认值,完全保留原始数据的空值状态
"""
customer_info = safe_get(original_data, 'data', 'customerInfo') if original_data else None
address_parts = extract_province_city_district(
safe_get(customer_info, 'provinceCityAreaName') if customer_info else None
# 分页获取全部客户
params = {'pageSize': 100, 'pageNo': '1'}
res = requests.get(
'https://yunxiu.f6car.cn/member/customer/listForPermission',
params=params,
cookies=cookies,
timeout=10
)
res.raise_for_status()
total = int(res.json().get("data", {}).get("total", 0))
total_pages = (total // params["pageSize"]) + (1 if total % params["pageSize"] > 0 else 0)
logger.info(f"总计 {total_pages} 页,共 {total} 个客户")
cell_phone = safe_get(customer_info, 'cellPhone')
all_customers = []
for page in range(1, total_pages + 1):
logger.debug(f"正在请求第 {page} 页...")
params["pageNo"] = page
retry_count = 0
max_retries = 5
while retry_count < max_retries:
try:
response = requests.get(
'https://yunxiu.f6car.cn/member/customer/listForPermission',
params=params,
cookies=cookies,
timeout=10
)
response.raise_for_status()
page_data = response.json().get("data", {}).get("data", [])
all_customers.extend(page_data)
break
except Exception as e:
retry_count += 1
logger.warning(f"请求第 {page} 页失败(第 {retry_count} 次重试): {e}")
time.sleep(3)
else:
logger.error(f"{page} 页请求失败超过最大重试次数,跳过")
exclusive_info = None
df_row = None
if cell_phone and not df.empty:
matched_rows = df[df['客户手机号'] == cell_phone]
if not matched_rows.empty:
df_row = matched_rows.iloc[0]
exclusive_info = {
'userId': df_row.get('userId'),
'name': df_row.get('专属运营顾问')
}
request_data = {
"pkId": safe_get(customer_info, 'idCustomer'),
"idCustomer": safe_get(customer_info, 'idCustomer'),
"name": df_row.get('客户姓名') if df_row is not None and pd.notna(df_row.get('客户姓名')) else safe_get(
customer_info, 'name'),
"sex": safe_get(customer_info, 'sex'),
"customerType": df_row.get('客户类型') if df_row is not None and pd.notna(
df_row.get('客户类型')) else safe_get(
customer_info, 'customerType'),
"customerSource": safe_get(customer_info, 'customerSource'),
"customerSourceName": df_row.get('客户来源') if df_row is not None and pd.notna(
df_row.get('客户来源')) else safe_get(customer_info, 'customerSourceName'),
"companyName": df_row.get('单位名称') if df_row is not None and pd.notna(
df_row.get('单位名称')) else safe_get(
customer_info, 'companyName'),
"cellPhone": cell_phone,
"wechart": safe_get(customer_info, 'wechart'),
"qq": safe_get(customer_info, 'qq'),
"contacts": safe_get(customer_info, 'contacts'),
"contactTelephone": safe_get(customer_info, 'contactTelephone'),
"province": safe_get(customer_info, 'province'),
"city": safe_get(customer_info, 'city'),
"area": safe_get(customer_info, 'area'),
"street": safe_get(customer_info, 'street'),
"address": safe_get(customer_info, 'address'),
"detailAddress": safe_get(customer_info, 'detailAddress'),
"pId": safe_get(customer_info, 'province'),
"cId": safe_get(customer_info, 'city'),
"aId": safe_get(customer_info, 'area'),
"provinceName": address_parts.get(''),
"cityName": address_parts.get(''),
"areaName": address_parts.get(''),
"provinceCityAreaName": safe_get(customer_info, 'provinceCityAreaName'),
"birthday": safe_get(customer_info, 'birthday'),
"creationtime": safe_get(customer_info, 'creationtime'),
"modifiedtime": safe_get(customer_info, 'modifiedtime'),
"creator": safe_get(customer_info, 'creator'),
"creatorName": safe_get(customer_info, 'creatorName'),
"modifier": safe_get(customer_info, 'modifier'),
"idOwnOrg": safe_get(customer_info, 'idOwnOrg'),
"idOwnGroup": safe_get(customer_info, 'idOwnGroup'),
"insuranceCompany": safe_get(customer_info, 'insuranceCompany'),
"maritalStatus": safe_get(customer_info, 'maritalStatus'),
"monthlyIncome": safe_get(customer_info, 'monthlyIncome'),
"idNumber": safe_get(customer_info, 'idNumber'),
"personHobby": safe_get(customer_info, 'personHobby'),
"credentialsType": safe_get(customer_info, 'credentialsType'),
"points": safe_get(customer_info, 'points'),
"maxAccountAmount": safe_get(customer_info, 'maxAccountAmount'),
"pointsEnable": safe_get(customer_info, 'pointsEnable'),
"level": safe_get(customer_info, 'level'),
"memberCardNo": safe_get(customer_info, 'memberCardNo'),
"customerLevel": safe_get(customer_info, 'customerLevel'),
"exclusiveConsultantId": exclusive_info['userId'] if exclusive_info else safe_get(customer_info,
'exclusiveConsultantId'),
"exclusiveConsultantName": exclusive_info['name'] if exclusive_info else safe_get(customer_info,
'exclusiveConsultantName'),
"exclusiveOrgId": safe_get(customer_info, 'exclusiveOrgId'),
"exclusiveOrgName": safe_get(customer_info, 'exclusiveOrgName'),
"customerMemo": df_row.get('客户备注') if df_row is not None and pd.notna(
df_row.get('客户备注')) else safe_get(
customer_info, 'customerMemo'),
"isDel": safe_get(customer_info, 'isDel'),
"idFrom": safe_get(customer_info, 'idFrom'),
"mnemonic": safe_get(customer_info, 'mnemonic'),
"idOrgSource": safe_get(customer_info, 'idOrgSource'),
"firstArrivalIdSourceBill": safe_get(customer_info, 'firstArrivalIdSourceBill'),
"lastArrivalIdSourceBill": safe_get(customer_info, 'lastArrivalIdSourceBill'),
"customerInfoType": safe_get(customer_info, 'customerInfoType'),
"customerInfoCompleteDate": safe_get(customer_info, 'customerInfoCompleteDate'),
"customerInfoCompleteType": safe_get(customer_info, 'customerInfoCompleteType'),
"xczUserId": safe_get(customer_info, 'xczUserId'),
"xczUuid": safe_get(customer_info, 'xczUuid'),
"idWxbCustomer": safe_get(customer_info, 'idWxbCustomer'),
"promoteEmployeeId": safe_get(customer_info, 'promoteEmployeeId'),
"promoteEmployeeName": safe_get(customer_info, 'promoteEmployeeName'),
"promoteMemberId": safe_get(customer_info, 'promoteMemberId'),
"promoteMemberName": safe_get(customer_info, 'promoteMemberName'),
"driverExpiryDate": safe_get(customer_info, 'driverExpiryDate'),
"crmDeleteExclusiveFlag": safe_get(customer_info, 'crmDeleteExclusiveFlag'),
"totalObtainPoints": safe_get(customer_info, 'totalObtainPoints'),
"totalUsedPoints": safe_get(customer_info, 'totalUsedPoints'),
"orgName": safe_get(customer_info, 'orgName'),
"weChatFollower": safe_get(customer_info, 'weChatFollower'),
"pointsEnableConfig": safe_get(customer_info, 'pointsEnableConfig'),
"personalPointsEnableConfig": safe_get(customer_info, 'personalPointsEnableConfig'),
"pointsButtonStatus": safe_get(customer_info, 'pointsButtonStatus'),
"tmallInstallMember": safe_get(customer_info, 'tmallInstallMember'),
"corpId": safe_get(customer_info, 'corpId'),
"thirdCorpId": safe_get(customer_info, 'thirdCorpId'),
# 获取专属运营顾问列表
json_data = {
'includeStopedEmployee': False,
'pageSize': 1000,
'filterNullUser': False,
'keyword': '',
'idOwnOrgList': [],
}
staff_resp = requests.post(
'https://yunxiu.f6car.cn/hive/employee/searchStaffInGroup',
cookies=cookies,
json=json_data,
timeout=10
)
staff_resp.raise_for_status()
staff_list = staff_resp.json().get("data", {}).get("list", [])
name_to_userid = {
emp['name']: emp['userId']
for emp in staff_list
if emp.get('userId') is not None
}
return request_data
# 在 df 中添加 userId 列
df['userId'] = df['专属运营顾问'].map(name_to_userid)
for customer in all_customers:
phone = customer.get("cellPhone")
if phone in df["客户手机号"].tolist():
print("开始修改")
cus_id = customer.get("idCustomer", {})
cus_response = requests.get(f'https://yunxiu.f6car.cn/member/customer/{cus_id}', cookies=cookies)
original_data = cus_response.json()
final_json_data = convert_to_request_data(original_data, df)
response = requests.post(
'https://yunxiu.f6car.cn/member/customer/modifyCustomer',
cookies=cookies,
json=final_json_data,
)
print("修改完成")
# 字段映射:Excel 列名 -> F6 字段名
FIELD_MAPPING = {
'客户姓名': 'name',
'客户类型': 'customerType',
'客户来源': 'customerSourceName',
'单位名称': 'companyName',
'客户备注': 'customerMemo',
'专属运营顾问': 'exclusiveConsultantName', # userId 单独处理
}
time.sleep(1)
def convert_to_request_data(original_data: dict, df_row: pd.Series) -> dict:
"""以原始客户数据为基础,仅覆盖 Excel 中非空字段"""
customer_info = original_data.get("data", {}).get("customerInfo", {}) or {}
request_data = dict(customer_info) # 浅拷贝,足够用
msg = update_jiandaoyun(data, f'修改完成')
# 覆盖指定字段
for excel_col, f6_field in FIELD_MAPPING.items():
value = df_row.get(excel_col)
if pd.notna(value):
request_data[f6_field] = str(value).strip() if isinstance(value, str) else value
if msg.get('msg'):
approve_workflow(data)
print('表单已自动提交至下一步')
# 处理专属顾问 ID
if pd.notna(df_row.get('userId')):
request_data['exclusiveConsultantId'] = df_row['userId']
# 确保必要字段
if 'idCustomer' in customer_info:
request_data['pkId'] = customer_info['idCustomer']
request_data['idCustomer'] = customer_info['idCustomer']
return request_data
# 执行批量更新
updated_count = 0
for customer in all_customers:
phone = customer.get("cellPhone")
if not phone:
continue
matched_rows = df[df['客户手机号'] == phone]
if matched_rows.empty:
continue
df_row = matched_rows.iloc[0]
cus_id = customer.get("idCustomer")
if not cus_id:
logger.warning(f"客户手机号 {phone} 缺少 idCustomer,跳过")
continue
try:
# 获取完整客户信息
detail_resp = requests.get(
f'https://yunxiu.f6car.cn/member/customer/{cus_id}',
cookies=cookies,
timeout=10
)
detail_resp.raise_for_status()
original_data = detail_resp.json()
# 构造更新数据
final_json_data = convert_to_request_data(original_data, df_row)
# 发送更新
update_resp = requests.post(
'https://yunxiu.f6car.cn/member/customer/modifyCustomer',
cookies=cookies,
json=final_json_data,
timeout=10
)
if update_resp.status_code == 200 and update_resp.json().get('success'):
logger.info(f"✅ 客户 {phone} 更新成功")
updated_count += 1
else:
logger.error(f"❌ 客户 {phone} 更新失败: {update_resp.text}")
time.sleep(1) # 避免触发限流
except Exception as e:
logger.exception(f"处理客户 {phone} 时发生异常: {e}")
# 更新简道云状态
msg = update_jiandaoyun(data, f'批量修改完成,共更新 {updated_count} 个客户')
if msg.get('msg'):
approve_workflow(data)
logger.info('✅ 表单已自动提交至下一步')
except Exception as e:
logger.exception("modify_customer_info_background 执行出错:")
# 即使出错也尝试通知简道云
try:
update_jiandaoyun(data, f'执行失败: {str(e)[:200]}')
except:
pass
finally:
# 清理临时文件
try:
if os.path.exists(save_path):
os.remove(save_path)
logger.info(f"临时文件已删除: {save_path}")
except Exception as e:
logger.warning(f"删除临时文件失败: {e}")
+7 -7
View File
@@ -1,11 +1,11 @@
anyio==4.11.0
apscheduler==3.11.1
fastapi==0.121.0
log_config==2.1.1
numpy==2.3.4
anyio==4.12.0
apscheduler==3.11.2
fastapi==0.128.0
numpy==2.4.0
pandas==2.3.3
Pillow==12.0.0
Pillow==12.1.0
pydantic==2.12.5
pytesseract==0.3.13
Requests==2.32.5
tqdm==4.67.1
uvicorn==0.38.0
uvicorn==0.40.0