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
+103 -15
View File
@@ -1,5 +1,5 @@
""" """
F6 插件模块 F6 后台执行模块
本模块提供 F6 插件相关的功能,包括: 本模块提供 F6 插件相关的功能,包括:
- 文件上传和校验 - 文件上传和校验
@@ -7,6 +7,8 @@ F6 插件模块
- 历史记录删除 - 历史记录删除
- 客户信息管理 - 客户信息管理
- 车辆信息管理 - 车辆信息管理
- 项目信息批量启停
- 材料信息批量修改
依赖: 依赖:
- requests: HTTP 请求 - requests: HTTP 请求
@@ -31,6 +33,10 @@ from app.tasks.delete_tasks import (
delete_customer_background, delete_customer_background,
delete_car_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.customer_tasks import modify_customer_info_background
from app.tasks.bi_tasks import bi_task_background from app.tasks.bi_tasks import bi_task_background
@@ -65,7 +71,7 @@ class F6PluginModule:
Returns: Returns:
tuple: 包含文件保存路径和处理后的数据的元组。如果文件保存成功,返回保存路径和数据;如果失败,返回 None 和数据。 tuple: 包含文件保存路径和处理后的数据的元组。如果文件保存成功,返回保存路径和数据;如果失败,返回 None 和数据。
""" """
data = api_instance.entry_data_get(data=data,replace= True) data = api_instance.entry_data_get(data=data, replace=True)
print(data) print(data)
try: try:
# 安全地访问附件信息 # 安全地访问附件信息
@@ -118,8 +124,7 @@ class F6PluginModule:
else: else:
return None, data return None, data
def check_file(self, data: Dict[str, Any]) -> dict[str, str] | None: # 校验上传文件
def check_file(self, data: Dict[str, Any]) -> Dict[str, str]: # 校验上传文件
""" """
校验上传文件。 校验上传文件。
@@ -176,7 +181,6 @@ class F6PluginModule:
else: else:
return {'msg': '当前节点无附件上传', 'check': ''} return {'msg': '当前节点无附件上传', 'check': ''}
@staticmethod @staticmethod
def create_brand(data: Dict[str, Any]) -> Dict[str, str]: def create_brand(data: Dict[str, Any]) -> Dict[str, str]:
""" """
@@ -191,7 +195,7 @@ class F6PluginModule:
Returns: Returns:
Dict[str, str]: 包含执行状态的字典,{'msg': '正在执行', 'msg_details': '正在执行,请稍后看结果'} 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('执行 品牌批量新建') print('执行 品牌批量新建')
username = entry_data['data']['账号'] username = entry_data['data']['账号']
password = entry_data['data']['密码'] password = entry_data['data']['密码']
@@ -232,7 +236,7 @@ class F6PluginModule:
Returns: Returns:
Dict[str, str]: 包含执行状态的字典 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']['账号'] username = entry_data['data']['账号']
password = entry_data['data']['密码'] password = entry_data['data']['密码']
company_name = entry_data['data']['公司名称'] company_name = entry_data['data']['公司名称']
@@ -279,7 +283,7 @@ class F6PluginModule:
Dict[str, str]: 包含执行状态的字典 Dict[str, str]: 包含执行状态的字典
""" """
print('执行 删除客户') 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']['账号'] username = entry_data['data']['账号']
password = entry_data['data']['密码'] password = entry_data['data']['密码']
company_name = entry_data['data']['公司名称'] company_name = entry_data['data']['公司名称']
@@ -298,7 +302,8 @@ class F6PluginModule:
thread = threading.Thread(target=delete_customer_background, thread = threading.Thread(target=delete_customer_background,
args=(data, cookies, total,)) args=(data, cookies, total,))
thread.start() 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: else:
return {'msg': '未执行', 'msg_details': '无客户信息'} return {'msg': '未执行', 'msg_details': '无客户信息'}
else: else:
@@ -318,7 +323,7 @@ class F6PluginModule:
Returns: Returns:
Dict[str, str]: 包含执行状态的字典 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']['账号'] username = entry_data['data']['账号']
password = entry_data['data']['密码'] password = entry_data['data']['密码']
company_name = entry_data['data']['公司名称'] company_name = entry_data['data']['公司名称']
@@ -351,7 +356,8 @@ class F6PluginModule:
else: else:
return {'msg': '未执行', 'msg_details': '登录失败'} return {'msg': '未执行', 'msg_details': '登录失败'}
def modify_customer_info(self, data: Dict[str, str]): @staticmethod
def modify_customer_info(data: Dict[str, str]):
""" """
修改客户信息 修改客户信息
@@ -364,7 +370,7 @@ class F6PluginModule:
Returns: Returns:
Dict[str, str]: 包含执行状态的字典 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']['账号'] username = entry_data['data']['账号']
password = entry_data['data']['密码'] password = entry_data['data']['密码']
company_name = entry_data['data']['公司名称'] company_name = entry_data['data']['公司名称']
@@ -389,6 +395,90 @@ class F6PluginModule:
else: else:
return {'msg': '未执行', 'msg_details': 'cookies获取失败'} 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 @staticmethod
def bi_task(data: Dict[str, Any]) -> Dict[str, str]: def bi_task(data: Dict[str, Any]) -> Dict[str, str]:
""" """
@@ -403,7 +493,7 @@ class F6PluginModule:
Returns: Returns:
Dict[str, str]: 包含执行状态的字典,{'msg': '正在执行', 'msg_details': '正在执行,请稍后看结果'} 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任务') print('执行 BI任务')
# 获取必要的参数(根据实际需求调整) # 获取必要的参数(根据实际需求调整)
@@ -438,5 +528,3 @@ class F6PluginModule:
return {'msg': '任务启动失败', 'msg_details': f'无法启动后台任务: {str(e)}'} return {'msg': '任务启动失败', 'msg_details': f'无法启动后台任务: {str(e)}'}
return {'msg': '正在执行', 'msg_details': '正在执行,请稍后看结果'} return {'msg': '正在执行', 'msg_details': '正在执行,请稍后看结果'}
+1 -1
View File
@@ -1,5 +1,5 @@
""" """
F6 系统模块 F6 前端即时响应模块
本模块提供 F6 系统相关的功能,包括: 本模块提供 F6 系统相关的功能,包括:
- 登录和认证 - 登录和认证
+8 -1
View File
@@ -29,6 +29,11 @@ from app.tasks.customer_tasks import modify_customer_info_background
# BI相关任务 # BI相关任务
from app.tasks.bi_tasks import bi_task_background from app.tasks.bi_tasks import bi_task_background
from app.tasks.material_tasks import ( \
batch_modify_materials,
batch_disable_projects
)
__all__ = [ __all__ = [
# 通用功能 # 通用功能
'update_jiandaoyun', 'update_jiandaoyun',
@@ -43,5 +48,7 @@ __all__ = [
'modify_customer_info_background', 'modify_customer_info_background',
# BI任务 # BI任务
'bi_task_background', 'bi_task_background',
# 项目材料任务
'batch_disable_projects',
'batch_modify_materials',
] ]
+2 -2
View File
@@ -2,8 +2,8 @@
BI相关后台任务模块 BI相关后台任务模块
本模块包含BI相关的后台任务,包括: 本模块包含BI相关的后台任务,包括:
- BI数据处理 - TODO BI数据处理
- BI报表生成
这些任务在后台线程中执行,不会阻塞主请求。 这些任务在后台线程中执行,不会阻塞主请求。
执行完成后会更新简道云表单并自动提交工作流。 执行完成后会更新简道云表单并自动提交工作流。
+2 -3
View File
@@ -137,8 +137,6 @@ def execute_failure_handler(data: Dict[str, Any]):
api_instance.data_batch_create(pay_load) api_instance.data_batch_create(pay_load)
def get_operate_org_id(cookies: Dict[str, str]) -> Optional[str]: def get_operate_org_id(cookies: Dict[str, str]) -> Optional[str]:
""" """
获取操作门店ID 获取操作门店ID
@@ -154,7 +152,7 @@ def get_operate_org_id(cookies: Dict[str, str]) -> Optional[str]:
注意: 注意:
如果未获取到门店信息或门店ID为空,会记录错误日志并返回 None 如果未获取到门店信息或门店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: try:
org_res = requests.get(url=org_url, cookies=cookies) org_res = requests.get(url=org_url, cookies=cookies)
@@ -243,3 +241,4 @@ def get_card_list(
logger.error(f"获取会员卡列表时发生错误: {e}") logger.error(f"获取会员卡列表时发生错误: {e}")
return card_list return card_list
+115 -179
View File
@@ -10,7 +10,7 @@ import logging
import requests import requests
import pandas as pd import pandas as pd
import time import time
import re import os
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
from app.tasks.common import update_jiandaoyun, approve_workflow from app.tasks.common import update_jiandaoyun, approve_workflow
@@ -38,48 +38,49 @@ def modify_customer_info_background(data: Dict[str, Any], cookies: Dict[str, str
- 执行完成后会自动删除上传的文件 - 执行完成后会自动删除上传的文件
- 执行结果会更新到简道云表单 - 执行结果会更新到简道云表单
""" """
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',
}
# 分页获取全部客户
params = {'pageSize': 100, 'pageNo': '1'}
res = requests.get( res = requests.get(
'https://yunxiu.f6car.cn/member/customer/listForPermission', 'https://yunxiu.f6car.cn/member/customer/listForPermission',
params=params, params=params,
cookies=cookies, cookies=cookies,
timeout=10
) )
res.raise_for_status()
total = int(res.json().get("data").get("total")) total = int(res.json().get("data", {}).get("total", 0))
total_pages = (total // params["pageSize"]) + (1 if total % params["pageSize"] > 0 else 0) total_pages = (total // params["pageSize"]) + (1 if total % params["pageSize"] > 0 else 0)
print(f"总计{total_pages}") logger.info(f"总计 {total_pages} 页,共 {total} 个客户")
all_customers = [] all_customers = []
max_retries = 10
retry_count = 0
for page in range(1, total_pages + 1): for page in range(1, total_pages + 1):
print(f"正在请求第 {page} 页...") logger.debug(f"正在请求第 {page} 页...")
params["pageNo"] = page params["pageNo"] = page
retry_count = 0
max_retries = 5
while retry_count < max_retries: while retry_count < max_retries:
try:
response = requests.get( response = requests.get(
'https://yunxiu.f6car.cn/member/customer/listForPermission', 'https://yunxiu.f6car.cn/member/customer/listForPermission',
params=params, params=params,
cookies=cookies, cookies=cookies,
timeout=10 timeout=10
) )
time.sleep(1) response.raise_for_status()
if response.status_code == 200: page_data = response.json().get("data", {}).get("data", [])
suppliers = response.json().get("data", {}).get("data", []) all_customers.extend(page_data)
all_customers.extend(suppliers)
break break
else: except Exception as e:
retry_count += 1 retry_count += 1
print(f"请求第 {page} 页失败,正在重试(第 {retry_count}...") logger.warning(f"请求第 {page} 页失败(第 {retry_count}重试): {e}")
time.sleep(3) time.sleep(3)
else:
logger.error(f"{page} 页请求失败超过最大重试次数,跳过")
# 获取专属运营顾问列表 # 获取专属运营顾问列表
json_data = { json_data = {
@@ -89,188 +90,123 @@ def modify_customer_info_background(data: Dict[str, Any], cookies: Dict[str, str
'keyword': '', 'keyword': '',
'idOwnOrgList': [], 'idOwnOrgList': [],
} }
staff_resp = requests.post(
response = requests.post(
'https://yunxiu.f6car.cn/hive/employee/searchStaffInGroup', 'https://yunxiu.f6car.cn/hive/employee/searchStaffInGroup',
cookies=cookies, cookies=cookies,
json=json_data, json=json_data,
timeout=10
) )
staff_resp.raise_for_status()
staff_list = response.json().get("data").get("list") staff_list = staff_resp.json().get("data", {}).get("list", [])
name_to_userid = { name_to_userid = {
emp['name']: emp['userId'] emp['name']: emp['userId']
for emp in staff_list for emp in staff_list
if emp['userId'] is not None if emp.get('userId') is not None
} }
# 在 df 中添加 userId 列
df['userId'] = df['专属运营顾问'].map(name_to_userid) df['userId'] = df['专属运营顾问'].map(name_to_userid)
def extract_province_city_district(address: Optional[str]) -> Dict[str, Optional[str]]: # 字段映射:Excel 列名 -> F6 字段名
"""安全解析省市区信息,所有返回值都可能为None""" FIELD_MAPPING = {
if not address: '客户姓名': 'name',
return {'': None, '': None, '': None} '客户类型': 'customerType',
'客户来源': 'customerSourceName',
try: '单位名称': 'companyName',
pattern = r'(?P<省>(?:[\u4e00-\u9fa5]+(?:省|自治区|特别行政区))?)' \ '客户备注': 'customerMemo',
r'(?P<市>(?:[\u4e00-\u9fa5]+(?:市|自治州|地区|盟))?)' \ '专属运营顾问': 'exclusiveConsultantName', # userId 单独处理
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
)
cell_phone = safe_get(customer_info, 'cellPhone')
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 = { def convert_to_request_data(original_data: dict, df_row: pd.Series) -> dict:
"pkId": safe_get(customer_info, 'idCustomer'), """以原始客户数据为基础,仅覆盖 Excel 中非空字段"""
"idCustomer": safe_get(customer_info, 'idCustomer'), customer_info = original_data.get("data", {}).get("customerInfo", {}) or {}
"name": df_row.get('客户姓名') if df_row is not None and pd.notna(df_row.get('客户姓名')) else safe_get( request_data = dict(customer_info) # 浅拷贝,足够用
customer_info, 'name'),
"sex": safe_get(customer_info, 'sex'), # 覆盖指定字段
"customerType": df_row.get('客户类型') if df_row is not None and pd.notna( for excel_col, f6_field in FIELD_MAPPING.items():
df_row.get('客户类型')) else safe_get( value = df_row.get(excel_col)
customer_info, 'customerType'), if pd.notna(value):
"customerSource": safe_get(customer_info, 'customerSource'), request_data[f6_field] = str(value).strip() if isinstance(value, str) else value
"customerSourceName": df_row.get('客户来源') if df_row is not None and pd.notna(
df_row.get('客户来源')) else safe_get(customer_info, 'customerSourceName'), # 处理专属顾问 ID
"companyName": df_row.get('单位名称') if df_row is not None and pd.notna( if pd.notna(df_row.get('userId')):
df_row.get('单位名称')) else safe_get( request_data['exclusiveConsultantId'] = df_row['userId']
customer_info, 'companyName'),
"cellPhone": cell_phone, # 确保必要字段
"wechart": safe_get(customer_info, 'wechart'), if 'idCustomer' in customer_info:
"qq": safe_get(customer_info, 'qq'), request_data['pkId'] = customer_info['idCustomer']
"contacts": safe_get(customer_info, 'contacts'), request_data['idCustomer'] = customer_info['idCustomer']
"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'),
}
return request_data return request_data
# 执行批量更新
updated_count = 0
for customer in all_customers: for customer in all_customers:
phone = customer.get("cellPhone") phone = customer.get("cellPhone")
if phone in df["客户手机号"].tolist(): if not phone:
print("开始修改") continue
cus_id = customer.get("idCustomer", {})
cus_response = requests.get(f'https://yunxiu.f6car.cn/member/customer/{cus_id}', cookies=cookies) matched_rows = df[df['客户手机号'] == phone]
original_data = cus_response.json() if matched_rows.empty:
final_json_data = convert_to_request_data(original_data, df) continue
response = requests.post(
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', 'https://yunxiu.f6car.cn/member/customer/modifyCustomer',
cookies=cookies, cookies=cookies,
json=final_json_data, json=final_json_data,
timeout=10
) )
print("修改完成")
time.sleep(1) 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}")
msg = update_jiandaoyun(data, f'修改完成') time.sleep(1) # 避免触发限流
except Exception as e:
logger.exception(f"处理客户 {phone} 时发生异常: {e}")
# 更新简道云状态
msg = update_jiandaoyun(data, f'批量修改完成,共更新 {updated_count} 个客户')
if msg.get('msg'): if msg.get('msg'):
approve_workflow(data) approve_workflow(data)
print('表单已自动提交至下一步') 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 anyio==4.12.0
apscheduler==3.11.1 apscheduler==3.11.2
fastapi==0.121.0 fastapi==0.128.0
log_config==2.1.1 numpy==2.4.0
numpy==2.3.4
pandas==2.3.3 pandas==2.3.3
Pillow==12.0.0 Pillow==12.1.0
pydantic==2.12.5
pytesseract==0.3.13 pytesseract==0.3.13
Requests==2.32.5 Requests==2.32.5
tqdm==4.67.1 tqdm==4.67.1
uvicorn==0.38.0 uvicorn==0.40.0