Compare commits

...

3 Commits

Author SHA1 Message Date
panda 8e57195033 应续约日与过期日对调
更新续约代表数据一致性
2026-03-31 10:41:17 +08:00
panda 25225ce136 续约代办历史记录迁移
展会线索登记
2026-03-25 09:34:48 +08:00
panda ab0813c5ec 续约代办历史记录迁移 2026-03-09 09:24:10 +08:00
21 changed files with 4964 additions and 10277 deletions
@@ -835,7 +835,7 @@ class UpdateAllNGVDataDaily:
'is_camera_service': '_widget_1734062123079',
'is_maintenance_service': '_widget_1734062123080',
'saas_create_time': '_widget_1734062123081',
'expiry_time': '_widget_1734062123082',
'expiry_time': '_widget_1734062123177', # 改过期日
'saas_use_days': '_widget_1734062123083',
'saas_use_year': '_widget_1734062123084',
'is_main_org': '_widget_1734062123085',
@@ -958,7 +958,7 @@ class UpdateAllNGVDataDaily:
# 安装服务
'is_install_service': '_widget_1734062123175',
'install_create_time': '_widget_1734062123176',
'last_end_date': '_widget_1734062123177',
'last_end_date': '_widget_1734062123082',
'renew_date': '_widget_1734062123178',
# 连锁信息
@@ -1005,9 +1005,9 @@ class UpdateAllNGVDataDaily:
# 日期字段(UTC格式)
'date_fmt_date': '_widget_1749000071375',
'saas_create_time_date': '_widget_1749000071377',
'expiry_time_date': '_widget_1749000071382',
'expiry_time_date': '_widget_1749000071389', # 过期日
'install_create_time_date': '_widget_1749000071384',
'last_end_date_date': '_widget_1749000071389',
'last_end_date_date': '_widget_1749000071382',
'renew_date_date': '_widget_1749000071391',
# 人员ID字段
+6
View File
@@ -2599,3 +2599,9 @@
2026-01-06 10:22:45,472 - log_config.py - error_task_logger - ERROR - 任务 高德匹配手机号 (05:00) 超过执行窗口5分钟以上,标记为过期。
2026-01-06 10:22:45,473 - log_config.py - error_task_logger - ERROR - 任务 省市区人员关系表转BI (08:00) 超过执行窗口5分钟以上,标记为过期。
2026-01-06 10:22:45,475 - log_config.py - error_task_logger - ERROR - 任务 续约回访待办 (09:35) 超过执行窗口5分钟以上,标记为过期。
2026-03-26 16:52:07,168 - log_config.py - error_task_logger - ERROR - 同步异常 data_id:69c48f57805eb4de7a4f42c4 实例:fde35fef-ce35-4521-9f01-139b3d15efd0 错误:name 'data' is not defined
Traceback (most recent call last):
File "d:\Idea Project\SaaS_V1.7\test\续约待办一致性-全量同步.py", line 111, in <module>
form = data.get("formData")
^^^^
NameError: name 'data' is not defined
+60
View File
@@ -341,3 +341,63 @@
2026-01-06 10:22:45,476 - utils.py - task_logger - INFO - 任务 续约回访待办 (09:35) 状态已更新为 过期。
2026-01-06 10:22:45,476 - utils.py - task_logger - INFO - 启动任务加载完成。
2026-01-06 10:22:45,476 - main.py - task_logger - INFO - 程序已启动...
2026-03-26 16:33:23,279 - api.py - task_logger - INFO - 获取了155条数据
2026-03-26 16:33:23,281 - 续约待办一致性-全量同步.py - task_logger - INFO - 员工数: 155
2026-03-26 16:33:23,695 - 续约待办一致性-全量同步.py - task_logger - WARNING - 简道云缺少字段: ['门店id']
2026-03-26 16:33:25,026 - 续约待办一致性-全量同步.py - task_logger - INFO - 完成 同步成功:0 失败:0 跳过:1
2026-03-26 16:34:03,221 - api.py - task_logger - INFO - 获取了155条数据
2026-03-26 16:34:03,223 - 续约待办一致性-全量同步.py - task_logger - INFO - 员工数: 155
2026-03-26 16:34:04,502 - 续约待办一致性-全量同步.py - task_logger - INFO - 完成 同步成功:0 失败:0 跳过:1
2026-03-26 16:39:08,055 - api.py - task_logger - INFO - 获取了155条数据
2026-03-26 16:39:08,057 - 续约待办一致性-全量同步.py - task_logger - INFO - 员工数: 155
2026-03-26 16:39:09,383 - 续约待办一致性-全量同步.py - task_logger - INFO - 完成 同步成功:0 失败:0 跳过:1
2026-03-26 16:46:24,134 - api.py - task_logger - INFO - 获取了155条数据
2026-03-26 16:46:24,136 - 续约待办一致性-全量同步.py - task_logger - INFO - 员工数: 155
2026-03-26 16:46:24,510 - 续约待办一致性-全量同步.py - task_logger - WARNING - 已匹配字段: ['120天是否联系上客户', '60天是否联系上客户', '30天是否联系上客户', '区域经理', '服务单号', '门店ID', '公司id', '省', '市'] | 门店ID_widget=_widget_1773305561198 公司id_widget=_widget_1766631811839
2026-03-26 16:46:25,396 - 续约待办一致性-全量同步.py - task_logger - INFO - 完成 同步成功:0 失败:0 跳过:1
2026-03-26 16:47:21,705 - api.py - task_logger - INFO - 获取了155条数据
2026-03-26 16:47:21,707 - 续约待办一致性-全量同步.py - task_logger - INFO - 员工数: 155
2026-03-26 16:47:22,080 - 续约待办一致性-全量同步.py - task_logger - WARNING - 已匹配字段: ['120天是否联系上客户', '60天是否联系上客户', '30天是否联系上客户', '区域经理', '服务单号', '门店ID', '公司id', '省', '市'] | 门店ID_widget=_widget_1773305561198 公司id_widget=_widget_1766631811839
2026-03-26 16:47:22,946 - 续约待办一致性-全量同步.py - task_logger - WARNING - 跳过:宜搭实例无表单数据 instance_id=fde35fef-ce35-4521-9f01-139b3d15efd0 data_id=69c48f57805eb4de7a4f42c4
2026-03-26 16:47:22,947 - 续约待办一致性-全量同步.py - task_logger - INFO - 完成 同步成功:0 失败:0 跳过:1
2026-03-26 16:50:06,754 - api.py - task_logger - INFO - 获取了155条数据
2026-03-26 16:50:06,756 - 续约待办一致性-全量同步.py - task_logger - INFO - 员工数: 155
2026-03-26 16:50:07,181 - 续约待办一致性-全量同步.py - task_logger - WARNING - 已匹配字段: ['120天是否联系上客户', '60天是否联系上客户', '30天是否联系上客户', '区域经理', '服务单号', '门店ID', '公司id', '省', '市'] | 门店ID_widget=_widget_1773305561198 公司id_widget=_widget_1766631811839
2026-03-26 16:50:08,120 - 续约待办一致性-全量同步.py - task_logger - WARNING - 跳过:宜搭实例无表单数据 instance_id=fde35fef-ce35-4521-9f01-139b3d15efd0 data_id=69c48f57805eb4de7a4f42c4
2026-03-26 16:50:08,121 - 续约待办一致性-全量同步.py - task_logger - INFO - 完成 同步成功:0 失败:0 跳过:1
2026-03-26 16:52:05,870 - api.py - task_logger - INFO - 获取了155条数据
2026-03-26 16:52:05,873 - 续约待办一致性-全量同步.py - task_logger - INFO - 员工数: 155
2026-03-26 16:52:06,266 - 续约待办一致性-全量同步.py - task_logger - WARNING - 已匹配字段: ['120天是否联系上客户', '60天是否联系上客户', '30天是否联系上客户', '区域经理', '服务单号', '门店ID', '公司id', '省', '市'] | 门店ID_widget=_widget_1773305561198 公司id_widget=_widget_1766631811839
2026-03-26 16:52:07,170 - 续约待办一致性-全量同步.py - task_logger - INFO - 完成 同步成功:0 失败:1 跳过:0
2026-03-26 16:53:46,238 - api.py - task_logger - INFO - 获取了155条数据
2026-03-26 16:53:46,240 - 续约待办一致性-全量同步.py - task_logger - INFO - 员工数: 155
2026-03-26 16:53:46,618 - 续约待办一致性-全量同步.py - task_logger - WARNING - 已匹配字段: ['120天是否联系上客户', '60天是否联系上客户', '30天是否联系上客户', '区域经理', '服务单号', '门店ID', '公司id', '省', '市'] | 门店ID_widget=_widget_1773305561198 公司id_widget=_widget_1766631811839
2026-03-26 16:53:47,540 - 续约待办一致性-全量同步.py - task_logger - WARNING - 跳过:宜搭实例无表单数据 instance_id=fde35fef-ce35-4521-9f01-139b3d15efd0 data_id=69c48f57805eb4de7a4f42c4
2026-03-26 16:53:47,541 - 续约待办一致性-全量同步.py - task_logger - WARNING - 跳过 data_id:69c48f57805eb4de7a4f42c4 实例:fde35fef-ce35-4521-9f01-139b3d15efd0 | 原因: 120天:值空, 60天:值空, 30天:值空, 区域经理:值空, 服务单号:值空, 门店ID:值空, 省:值空, 市:值空 | 取值: v120= v60= v30= region_name= region_value= service_no= store_id= prov= city=
2026-03-26 16:53:47,542 - 续约待办一致性-全量同步.py - task_logger - INFO - 完成 同步成功:0 失败:0 跳过:2
2026-03-26 16:54:17,105 - api.py - task_logger - INFO - 获取了155条数据
2026-03-26 16:54:17,107 - 续约待办一致性-全量同步.py - task_logger - INFO - 员工数: 155
2026-03-26 16:54:17,496 - 续约待办一致性-全量同步.py - task_logger - WARNING - 已匹配字段: ['120天是否联系上客户', '60天是否联系上客户', '30天是否联系上客户', '区域经理', '服务单号', '门店ID', '公司id', '省', '市'] | 门店ID_widget=_widget_1773305561198 公司id_widget=_widget_1766631811839
2026-03-26 16:54:18,866 - 续约待办一致性-全量同步.py - task_logger - WARNING - 更新失败 data_id:69c48f57805eb4de7a4f42c4 实例:fde35fef-ce35-4521-9f01-139b3d15efd0 返回:{'data': {'_id': '69c48f57805eb4de7a4f42c4', 'creator': {'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}, 'updater': {'name': 'F6汽车科技', 'username': '#admin', 'status': 1, 'type': 0}, 'deleter': None, 'createTime': '2026-03-26T01:43:51.997Z', 'updateTime': '2026-03-26T08:54:20.015Z', 'deleteTime': None, 'flowState': 0, '_widget_1764820541663': '6882b41f9f4c7c716a87c66f', '_widget_1764820541616': '中国石化销售股份有限公司湖北石油分公司', '_widget_1764820541617': '黄石杭州路站店', '_widget_1764820541661': 'CHS202507240304526', '_widget_1764820541618': '易捷养车', '_widget_1766130435561': '全国KAFMVP', '_widget_1764820541672': '2026-07-23T16:00:00.000Z', '_widget_1764820541624': 'SaaS新签:1218', '_widget_1764820541621': '林莉', '_widget_1764820541622': '18986601656', '_widget_1764820541625': {'name': '刘光春', 'username': '171128505620867604', 'status': 1, 'type': 0, 'departments': [122503482, 122472424], 'integrate_id': '171128505620867604'}, '_widget_1766730385209': '基础版-门店管理系统', '_widget_1764820541628': '主动', '_widget_1773383601735': '', '_widget_1764820541634': {'name': '刘光春', 'username': '171128505620867604', 'status': 1, 'type': 0, 'departments': [122503482, 122472424], 'integrate_id': '171128505620867604'}, '_widget_1765352838631': '2026-03-26T07:23:36.642Z', '_widget_1764820541630': '主动', '_widget_1773383601736': '', '_widget_1764820541635': None, '_widget_1765352838632': None, '_widget_1764820541632': '主动', '_widget_1773383601737': '', '_widget_1764820541636': None, '_widget_1765352838633': None, '_widget_1764820541638': '是', '_widget_1764820541641': '暂时无问题', '_widget_1765330820509': '', '_widget_1764820541653': '易捷养车', '_widget_1764820541697': '', '_widget_1764820541657': '无', '_widget_1766633812301': [], '_widget_1764820541659': '', '_widget_1764820541654': '是', '_widget_1764820541700': '', '_widget_1764820541707': '', '_widget_1764820541709': '', '_widget_1764820541711': '', '_widget_1764820541713': '', '_widget_1764820541702': '', '_widget_1764820541717': [], '_widget_1764820541681': '是', '_widget_1765330820391': [], '_widget_1764820541674': '', '_widget_1764820541679': None, '_widget_1764820541676': '', '_widget_1764820541680': '', '_widget_1764820541865': '2026-05-24T16:00:00.000Z', '_widget_1765964381895': '2026-06-23T16:00:00.000Z', '_widget_1765964381896': '2026-07-23T16:00:00.000Z', '_widget_1765964381897': '2026-10-21T16:00:00.000Z', '_widget_1765352838609': '120天节点', '_widget_1774419872766': '120-60中间节点', '_widget_1765352838610': '', '_widget_1765964381952': '连锁', '_widget_1765866283543': '', '_widget_1764820541715': {'name': '孙海程', 'username': '084119070623372941', 'status': 1, 'type': 0, 'departments': [177751223], 'integrate_id': '084119070623372941'}, '_widget_1764820541678': {'name': '刘光春', 'username': '171128505620867604', 'status': 1, 'type': 0, 'departments': [122503482, 122472424], 'integrate_id': '171128505620867604'}, '_widget_1774503299407': {'name': '景东强', 'username': '232229053125844557', 'status': 1, 'type': 0, 'departments': [122472424, 122503482], 'integrate_id': '232229053125844557'}, '_widget_1766376046563': '06364', '_widget_1766469131897': '否', '_widget_1766633812134': '', '_widget_1766631811839': '11240984669918289042', '_widget_1764820541623': '基础版', '_widget_1772777035893': '湖北省', '_widget_1772777035894': '黄石市', '_widget_1772777035895': '', '_widget_1773305561198': '11240984669918400135', '_widget_1773306178551': 'XYFWD20260326047', '_widget_1774339442956': '', 'appId': '675b900991ad2491c69389ca', 'entryId': '6931063d64187eaf6b927557'}}
2026-03-26 16:54:18,868 - 续约待办一致性-全量同步.py - task_logger - INFO - 完成 同步成功:0 失败:1 跳过:0
2026-03-26 16:55:43,423 - api.py - task_logger - INFO - 获取了155条数据
2026-03-26 16:55:43,425 - 续约待办一致性-全量同步.py - task_logger - INFO - 员工数: 155
2026-03-26 16:55:43,836 - 续约待办一致性-全量同步.py - task_logger - WARNING - 已匹配字段: ['120天是否联系上客户', '60天是否联系上客户', '30天是否联系上客户', '区域经理', '服务单号', '门店ID', '公司id', '省', '市'] | 门店ID_widget=_widget_1773305561198 公司id_widget=_widget_1766631811839
2026-03-26 16:55:45,277 - 续约待办一致性-全量同步.py - task_logger - INFO - 完成 同步成功:1 失败:0 跳过:0
2026-03-26 16:58:03,921 - api.py - task_logger - INFO - 获取了155条数据
2026-03-26 16:58:03,923 - 续约待办一致性-全量同步.py - task_logger - INFO - 员工数: 155
2026-03-26 16:58:04,314 - 续约待办一致性-全量同步.py - task_logger - WARNING - 已匹配字段: ['120天是否联系上客户', '60天是否联系上客户', '30天是否联系上客户', '区域经理', '服务单号', '门店ID', '公司id', '省', '市'] | 门店ID_widget=_widget_1773305561198 公司id_widget=_widget_1766631811839
2026-03-26 16:58:06,581 - 续约待办一致性-全量同步.py - task_logger - INFO - 完成 同步成功:2 失败:0 跳过:0
2026-03-26 17:12:59,732 - api.py - task_logger - INFO - 获取了155条数据
2026-03-26 17:12:59,734 - 续约待办一致性-全量同步.py - task_logger - INFO - 员工数: 155
2026-03-26 17:13:00,152 - 续约待办一致性-全量同步.py - task_logger - WARNING - 已匹配字段: ['120天是否联系上客户', '60天是否联系上客户', '30天是否联系上客户', '区域经理', '服务单号', '门店ID', '公司id', '省', '市'] | 门店ID_widget=_widget_1773305561198 公司id_widget=_widget_1766631811839
2026-03-26 17:25:14,168 - 续约待办一致性-全量同步.py - task_logger - WARNING - 门店编码不一致: CHS202209150188636 vs CHS202204060173893 实例:72bb3720-4397-449d-b49e-017e01006cd1 data_id:69c251a92c89654480e8b0de
2026-03-26 17:26:22,204 - 续约待办一致性-全量同步.py - task_logger - WARNING - 门店编码不一致: CHS202204060173893 vs CHS201906290031913 实例:173bde91-5c88-4ef4-b8b7-2201dec1249e data_id:69c251a459268262b711272d
2026-03-26 17:32:28,526 - api.py - task_logger - WARNING - 请求异常: HTTPSConnectionPool(host='api.jiandaoyun.com', port=443): Read timed out. (read timeout=10), 将重新请求
2026-03-26 17:37:08,454 - 续约待办一致性-全量同步.py - task_logger - WARNING - 门店编码不一致: CHS201906290031913 vs CHS202204200174710 实例:a8552472-0660-4191-a671-42c14d3255d9 data_id:69c2516498c69ba7165bcf20
2026-03-26 17:44:16,165 - api.py - task_logger - WARNING - 请求异常: HTTPSConnectionPool(host='api.jiandaoyun.com', port=443): Read timed out. (read timeout=10), 将重新请求
2026-03-26 17:51:49,406 - api.py - task_logger - WARNING - 请求异常: HTTPSConnectionPool(host='api.jiandaoyun.com', port=443): Max retries exceeded with url: /api/v5/app/entry/data/update (Caused by ConnectTimeoutError(<HTTPSConnection(host='api.jiandaoyun.com', port=443) at 0x1f9a83ac190>, 'Connection to api.jiandaoyun.com timed out. (connect timeout=10)')), 将重新请求
2026-03-26 18:00:11,274 - api.py - task_logger - WARNING - 请求异常: HTTPSConnectionPool(host='api.jiandaoyun.com', port=443): Max retries exceeded with url: /api/v5/app/entry/data/update (Caused by ConnectTimeoutError(<HTTPSConnection(host='api.jiandaoyun.com', port=443) at 0x1f9a83ace10>, 'Connection to api.jiandaoyun.com timed out. (connect timeout=10)')), 将重新请求
2026-03-26 18:02:32,510 - 续约待办一致性-全量同步.py - task_logger - WARNING - 门店编码不一致: CHS201905080028126 vs CHS202207120184263 实例:64e0c4dd-f762-49af-8e32-18f6a54fc647 data_id:69ab8f6b88400afd9f2a1e50
2026-03-26 18:21:36,444 - api.py - task_logger - WARNING - 请求异常: HTTPSConnectionPool(host='api.jiandaoyun.com', port=443): Max retries exceeded with url: /api/v5/app/entry/data/update (Caused by ConnectTimeoutError(<HTTPSConnection(host='api.jiandaoyun.com', port=443) at 0x1f9a83ad1d0>, 'Connection to api.jiandaoyun.com timed out. (connect timeout=10)')), 将重新请求
2026-03-26 19:01:09,591 - 续约待办一致性-全量同步.py - task_logger - INFO - 完成 同步成功:5774 失败:0 跳过:0
-104
View File
@@ -1,104 +0,0 @@
{
"result": [
{
"processInstanceId": "fa83e004-64d5-40a8-b2a9-122cc24a548f",
"operateTimeGMT": "2025-02-28T09:07Z",
"showName": "提交申请",
"operateType": "NEW_PROCESS",
"remark": "",
"taskHoldTimeGMT": 0,
"type": "HISTORY",
"operatorName": "宜搭平台",
"actionExit": "submit",
"operatorUserId": "yida_pub_account",
"activityId": "sid-restartevent",
"size": 1,
"dataId": 52359309345,
"domainList": [],
"operatorDisplayName": "宜搭平台",
"action": "提交申请",
"taskId": "null",
"operatorPhotoUrl": "//img.alicdn.com/tfs/TB1mKVJSpXXXXcwaXXXXXXXXXXX-78-80.jpg"
},
{
"processInstanceId": "fa83e004-64d5-40a8-b2a9-122cc24a548f",
"operateTimeGMT": "2025-03-18T10:14Z",
"showName": "120天联系情况",
"operateType": "EXECUTE_TASK_NORMAL",
"remark": "提交",
"taskHoldTimeGMT": 0,
"type": "HISTORY",
"operatorName": "孟祥宇",
"actionExit": "agree",
"operatorUserId": "224616673723465569",
"activityId": "sid-6470221a-82ec-4bdd-a873-245ee47a5605",
"size": 1,
"dataId": 53850072345,
"domainList": [],
"operatorDisplayName": "孟祥宇",
"action": "提交",
"taskId": "52359293348",
"operatorPhotoUrl": "https://static.dingtalk.com/media/lADPDh0cUUaSfHrNAvDNAnA_624_752.jpg"
},
{
"processInstanceId": "fa83e004-64d5-40a8-b2a9-122cc24a548f",
"operateTimeGMT": "2025-04-01T14:08Z",
"showName": "60天联系情况",
"operateType": "EXECUTE_TASK_NORMAL",
"remark": "系统提交",
"taskHoldTimeGMT": 0,
"type": "HISTORY",
"operatorName": "魏子淇",
"actionExit": "agree",
"operatorUserId": "195159084238961158",
"activityId": "sid-ab6374fd-7580-66d5-1628-6b0666bb38ff",
"size": 1,
"dataId": 55294608170,
"domainList": [],
"operatorDisplayName": "魏子淇",
"action": "提交",
"taskId": "53850072349",
"operatorPhotoUrl": "https://static.dingtalk.com/media/lADPD2sQvHoyUEbNAtHNAtA_720_721.jpg"
},
{
"processInstanceId": "fa83e004-64d5-40a8-b2a9-122cc24a548f",
"operateTimeGMT": "2025-04-01T14:08Z",
"showName": "30天联系情况",
"operateType": "EXECUTE_TASK_NORMAL",
"remark": "系统提交",
"taskHoldTimeGMT": 0,
"type": "HISTORY",
"operatorName": "魏子淇",
"actionExit": "agree",
"operatorUserId": "195159084238961158",
"activityId": "sid-e5928800-154e-4e20-6019-1364274afc49",
"size": 1,
"dataId": 55294609293,
"domainList": [],
"operatorDisplayName": "魏子淇",
"action": "提交",
"taskId": "55294608175",
"operatorPhotoUrl": "https://static.dingtalk.com/media/lADPD2sQvHoyUEbNAtHNAtA_720_721.jpg"
},
{
"processInstanceId": "fa83e004-64d5-40a8-b2a9-122cc24a548f",
"operateTimeGMT": "2025-04-01T14:08Z",
"showName": "0天处理情况",
"operateType": "EXECUTE_TASK_NORMAL",
"remark": "系统提交",
"taskHoldTimeGMT": 0,
"type": "HISTORY",
"operatorName": "魏子淇",
"actionExit": "agree",
"operatorUserId": "195159084238961158",
"activityId": "sid-ba12125f-bc3a-2663-ebf0-43b5aeb8c32c",
"size": 1,
"dataId": 55294558885,
"domainList": [],
"operatorDisplayName": "魏子淇",
"action": "同意",
"taskId": "55294609301",
"operatorPhotoUrl": "https://static.dingtalk.com/media/lADPD2sQvHoyUEbNAtHNAtA_720_721.jpg"
}
]
}
-116
View File
@@ -1,116 +0,0 @@
{
"app_id": "6694d3c4fcb69ca9a111a6c4",
"form_id": "693778ee287cfdcc2df85ece",
"form_title": "流程表单结构测试",
"instance_id": "6937dc8d2cbcdd4c466a8381",
"url": "https://dingtalk.jiandaoyun.com/workflow/process_instance/6937dc8d2cbcdd4c466a8381",
"update_time": "2025-12-09T08:23:41.095Z",
"create_time": "2025-12-09T08:23:41.093Z",
"finish_time": None,
"status": 0,
"creator": {
"username": "#admin",
"name": "F6汽车科技",
"type": 0,
"status": 1,
"integrate_id": "#admin"
},
"tasks": [
{
"app_id": "6694d3c4fcb69ca9a111a6c4",
"form_id": "693778ee287cfdcc2df85ece",
"form_title": "流程表单结构测试",
"title": "流程发起节点",
"instance_id": "6937dc8d2cbcdd4c466a8381",
"task_id": "6937dc8d2cbcdd4c466a8398",
"flow_id": 0,
"flow_name": "流程发起节点",
"url": "https://dingtalk.jiandaoyun.com/workflow/process_instance/6937dc8d2cbcdd4c466a8381/task/6937dc8d2cbcdd4c466a8398",
"assignee": {
"username": "#admin",
"name": "F6汽车科技",
"type": 0,
"status": 1,
"integrate_id": "#admin"
},
"creator": {
"username": "#admin",
"name": "F6汽车科技",
"type": 0,
"status": 1,
"integrate_id": "#admin"
},
"create_time": "2025-12-09T08:23:41.094Z",
"create_action": "forward",
"finish_time": "2025-12-09T08:23:41.094Z",
"finish_action": "forward",
"status": 1
},
{
"app_id": "6694d3c4fcb69ca9a111a6c4",
"form_id": "693778ee287cfdcc2df85ece",
"form_title": "流程表单结构测试",
"title": "多跟进人节点",
"instance_id": "6937dc8d2cbcdd4c466a8381",
"task_id": "6937dc8d2cbcdd4c466a83a6",
"flow_id": 2,
"flow_name": "多跟进人节点",
"url": "https://dingtalk.jiandaoyun.com/workflow/process_instance/6937dc8d2cbcdd4c466a8381/task/6937dc8d2cbcdd4c466a83a6",
"assignee": {
"username": "4210192048793363",
"name": "张阳",
"departments": [
449008196
],
"type": 0,
"status": 1,
"integrate_id": "4210192048793363"
},
"creator": {
"username": "#admin",
"name": "F6汽车科技",
"type": 0,
"status": 1,
"integrate_id": "#admin"
},
"create_time": "2025-12-09T08:23:41.095Z",
"create_action": "forward",
"finish_time": None,
"finish_action": None,
"status": 0
},
{
"app_id": "6694d3c4fcb69ca9a111a6c4",
"form_id": "693778ee287cfdcc2df85ece",
"form_title": "流程表单结构测试",
"title": "多跟进人节点",
"instance_id": "6937dc8d2cbcdd4c466a8381",
"task_id": "6937dc8d2cbcdd4c466a83aa",
"flow_id": 2,
"flow_name": "多跟进人节点",
"url": "https://dingtalk.jiandaoyun.com/workflow/process_instance/6937dc8d2cbcdd4c466a8381/task/6937dc8d2cbcdd4c466a83aa",
"assignee": {
"username": "2268275546837446",
"name": "曹伟",
"departments": [
449008196
],
"type": 0,
"status": 1,
"integrate_id": "2268275546837446"
},
"creator": {
"username": "#admin",
"name": "F6汽车科技",
"type": 0,
"status": 1,
"integrate_id": "#admin"
},
"create_time": "2025-12-09T08:23:41.095Z",
"create_action": "forward",
"finish_time": None,
"finish_action": None,
"status": 0
}
]
}
-1639
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,43 @@
{
"cells": [
{
"metadata": {},
"cell_type": "markdown",
"source": "## 全量同步",
"id": "69bf37484b68b727"
},
{
"cell_type": "code",
"execution_count": null,
"id": "initial_id",
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
""
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+1
View File
@@ -401,6 +401,7 @@ try:
ts = int(round(t * 1000))
randint = random.randint(100000000, 999999999)
req = res_new['result'] + "|" + formData['textField_kuntp6fk'] + "|" + formData['textField_kuntp6fl']+ "|" + formData['employeeField_kykw5ege'] + "_" + str(ts) + "_" + str(randint)
# 实例ID|门ID|服务单号|专属运营顾问
str_en = des_encrypt(req)
print(str_en.decode('utf-8'))
req_new = str_en.decode('utf-8')
+43
View File
@@ -0,0 +1,43 @@
{
"cells": [
{
"metadata": {},
"cell_type": "markdown",
"source": "## 重复派发",
"id": "2d5eea6406e5bd27"
},
{
"cell_type": "code",
"execution_count": null,
"id": "initial_id",
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
""
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
File diff suppressed because it is too large Load Diff
@@ -1,59 +1,241 @@
import os
import sys
import pandas as pd
import json
import ast # 👈 新增导入
from datetime import datetime
# 获取上级目录并加入路径
nb_path = os.path.abspath('')
parent_dir = os.path.dirname(nb_path)
sys.path.append(parent_dir)
from back_ground_module import CommonModule
from log_config import configure_task_logger, configure_error_task_logger
from yd_api import YDAPI
from api import API
from tqdm import tqdm
logger = configure_task_logger()
error_task_logger = configure_error_task_logger()
api_instance = API()
yd_api_instance = YDAPI()
common_module = CommonModule()
output_dir = "output"
os.makedirs(output_dir, exist_ok=True)
api_instance = API()
yd_api_instance = YDAPI()
# 加载数据
# df = pd.read_csv(r"D:\Idea Project\SaaS_V1.7\test\output\expanded_yd_data.csv",encoding="gbk").astype(str)
df = pd.read_excel(r"C:\Users\hp_z66\OneDrive\Desktop\门店分析新.xlsx",sheet_name="新建").astype(str)
df2 = pd.read_excel(
r"D:\Idea Project\SaaS_V1.7\test\output\续约服务流程_20260324165743.xlsx"
).fillna('').astype(str)
# 从df中获取流程编码获取流程详细信息 # 测试注释
token = yd_api_instance.generateToken()
FORMID = "FORM-PE866MD1MJMU0WGLYRFLYEN5YN9L1I55Z7ZUK22"
appType = "APP_UYZ0KG6L0CCNV80GZ66O"
systemToken = "XA966F81JAJOFCVVVKO64E9MIIZV1EWE5SFMKJ2"
df = pd.read_csv(os.path.join(output_dir, "converted_yd_data.csv"))
all_instance_data = []
for index, row in tqdm(df.iterrows(), total=len(df)):
instance_id = row["实例ID"]
instance_info = yd_api_instance.processes_instancesInfos(token, instance_id, appType, systemToken)
data = instance_info.get("data")
if data:
# 提取 formData 中的字段并合并到外层
form_data = data.get("formData", {})
if isinstance(form_data, dict):
data.update(form_data)
# 手动注入实例 ID,确保映射能找到
data["实例ID"] = instance_id
all_instance_data.append(data)
# 检查 data 列的第一个非空值类型
sample = df['data'].dropna().iloc[0] if not df['data'].dropna().empty else ""
print("Sample of 'data' column:")
print(repr(sample)[:200]) # 打印前200字符,看是单引号还是双引号
ndf = pd.DataFrame(all_instance_data)
ndf.to_csv(r"D:\Idea Project\SaaS_V1.7\\test\output\yd_process_details.csv", index=False)
# 如果是字符串(且是 Python dict 格式,单引号),用 ast.literal_eval
if isinstance(sample, str):
print("Detected string format, parsing with ast.literal_eval...")
# 读取宜搭流程详情(已提前导出)
ndf = pd.read_csv(r"D:\Idea Project\SaaS_V1.7\test\output\yd_process_details.csv")
# 简道云字段中文名 → 字段ID 映射
jdy_map = {
"门店编码": "_widget_1764820541661",
"120天是否跟进": "_widget_1764820541628",
"120天处理人": "_widget_1764820541634",
"120天跟进时间": "_widget_1765352838631",
"60天是否跟进": "_widget_1764820541630",
"60天处理人": "_widget_1764820541635",
"60天跟进时间": "_widget_1765352838632",
"30天是否跟进": "_widget_1764820541632",
"30天处理人": "_widget_1764820541636",
"30天跟进时间": "_widget_1765352838633",
"是否联系上": "_widget_1764820541638",
"现阶段问题": "_widget_1764820541641",
"联系情况及问题说明": "_widget_1764820541653",
"潜在商机": "_widget_1764820541657",
"商机详情": "_widget_1764820541659",
"不续约原因": "_widget_1764820541700",
"产品问题": "_widget_1764820541707",
"服务问题": "_widget_1764820541709",
"门店问题": "_widget_1764820541711",
"价格问题": "_widget_1764820541713",
"不续约具体情况说明": "_widget_1764820541702",
"宜搭实例ID": "_widget_1774339442956",
}
# 安全地将字符串转为字典
def safe_literal_eval(x):
if pd.isna(x) or x == "":
return {}
try:
return ast.literal_eval(x)
except (ValueError, SyntaxError) as e:
print(f"Parse error on: {repr(x)[:100]}... Error: {e}")
return {}
# 宜搭字段ID → 简道云中文名 映射
yd_field_id_to_jdy_chinese = {
"textField_ksydghqw": "门店编码",
"radioField_kuntp6fm": "120天是否跟进",
"textField_livc8bjj": "120天处理人",
"dateField_lifr1fdv": "120天跟进时间",
"radioField_kurxyhvp": "60天是否跟进",
"textField_livc8bjl": "60天处理人",
"dateField_lifr1fdx": "60天跟进时间",
"radioField_kurxyhvq": "30天是否跟进",
"textField_livc8bjm": "30天处理人",
"dateField_lifr1fdy": "30天跟进时间",
"radioField_l85ppdia": "是否联系上",
"radioField_r3yeqvd": "现阶段问题",
"textAreaField_972lhkt": "联系情况及问题说明",
"radioField_ljqi5we3": "潜在商机",
"textareaField_liviovx0": "商机详情",
"selectField_l31clxfy": "不续约原因",
"selectField_l31clxfz": "产品问题",
"selectField_l31clxg0": "服务问题",
"selectField_l31clxg1": "门店问题",
"selectField_l31clxg2": "价格问题",
"textareaField_l31clxg4": "不续约具体情况说明",
"radioField_l85ppdie": "续约意愿",
"实例ID":"宜搭实例ID"
}
# 值映射(用于标准化选项值)
value_mapping = {
"现阶段问题": {"暂时没有问题": "暂时无问题"},
"不续约原因": {"产品原因": "产品问题", "门店原因": "门店问题"},
"服务问题": {
"联系不上小六": "联系不上运营顾问",
"小六态度问题": "运营顾问态度问题",
"小六业务不专业": "运营顾问业务不专业",
"小六离职未能获取不续约原因": "运营顾问离职未能获取不续约原因"
},
"120天是否跟进": {"小六": "主动", "系统": "自动"},
"60天是否跟进": {"小六": "主动", "系统": "自动"},
"30天是否跟进": {"小六": "主动", "系统": "自动"},
}
df['data'] = df['data'].apply(safe_literal_eval)
# ========================
# 1. 获取员工姓名 → ID 映射
# ========================
payload_staff = {
"api_key": "6694d3c4fcb69ca9a111a6c4", # 注意:应为 app_id,不是 api_key(根据你实际接口调整)
"entry_id": "6769204a1902c9341340a1bc",
}
staff_resp = api_instance.entry_data_list(payload_staff)
staff_id_list = staff_resp.get("data", [])
# 展开 data 列
expanded = pd.json_normalize(df['data'])
# 构建映射字典:姓名 -> 员工ID
name_to_staff_id = {}
for item in staff_id_list:
name = item.get("_widget_1734942794144", "").strip()
staff_id = item.get("_widget_1734942794145", "").strip()
if name and staff_id:
name_to_staff_id[name] = staff_id
# 保留其他列(注意:原列是 'data',不是 'raw_data'
other_cols = df.drop(columns=['data'])
logger.info(f"加载 {len(name_to_staff_id)} 名员工信息")
# 合并
new_df = pd.concat([other_cols.reset_index(drop=True), expanded.reset_index(drop=True)], axis=1).astype(str)
# 过滤进行中
new_df = new_df[new_df["instanceStatus"] == "RUNNING"]
# 订单编码为空
col = "textField_kto3q3ev"
mask2 = (
new_df[col].isnull() |
(new_df[col].astype(str).str.strip() == "")
)
new_df = new_df[mask2]
# 保存结果
new_df.to_csv(os.path.join(output_dir, "expanded_yd_data.csv"), index=False, encoding='utf-8-sig')
print("✅ Expanded data saved to 'expanded_yd_data.csv'")
# ========================
# 2. 定义哪些字段是“人员字段”(需替换为ID)
# ========================
STAFF_COLUMNS_CHINESE = {
"120天处理人",
"60天处理人",
"30天处理人",
"运营顾问",
"运营专家",
"区域客服",
}
# 构建门店编码 → data_id 映射
df2["门店编码_clean"] = df2["门店编码"].astype(str).str.strip().replace('nan', '')
jdy_store_map = df2.set_index("门店编码_clean")["data_id"].to_dict()
date_fields_chinese = {"120天跟进时间", "60天跟进时间", "30天跟进时间"}
update_records = []
for idx, row in ndf.iterrows():
yd_store_code = str(row.get("textField_ksydghqw", "")).strip()
if not yd_store_code or yd_store_code == "nan":
continue
jdy_id = jdy_store_map.get(yd_store_code)
if not jdy_id:
continue
# 构造 data 字段:每个字段必须是 { "value": ... }
data_dict = {}
for yd_field_id, jdy_chinese in yd_field_id_to_jdy_chinese.items():
raw_val = row.get(yd_field_id, "")
if pd.isna(raw_val) or str(raw_val).strip().lower() in {"", "nan", "-", "", "null"}:
continue
# 处理日期字段
if jdy_chinese in date_fields_chinese:
try:
if isinstance(raw_val, (int, float)) or (
isinstance(raw_val, str) and raw_val.replace('.', '', 1).isdigit()):
ts = float(raw_val)
if ts < 1e12:
ts *= 1000
final_value = int(ts)
else:
dt = pd.to_datetime([str(raw_val)], errors='coerce')[0]
if pd.isna(dt):
raise ValueError("Invalid date")
if dt.tz is None:
dt = dt.tz_localize('Asia/Shanghai')
final_value = int(dt.tz_convert('UTC').timestamp() * 1000)
if not (1577836800000 <= final_value <= 1900000000000):
continue
except Exception as e:
logger.error(f"日期转换失败 [{jdy_chinese}]: {raw_val}, {e}")
continue
else:
str_val = str(raw_val)
final_value = value_mapping.get(jdy_chinese, {}).get(str_val, str_val)
# 如果是人员字段,尝试替换为员工ID
if jdy_chinese in STAFF_COLUMNS_CHINESE:
staff_id = name_to_staff_id.get(str_val)
if staff_id:
final_value = staff_id
else:
logger.warning(f"未找到员工ID,保留原姓名 [{jdy_chinese}]: {str_val}")
jdy_field_id = jdy_map.get(jdy_chinese)
if jdy_field_id:
data_dict[jdy_field_id] = {"value": final_value}
if data_dict:
update_records.append({
"data_id": jdy_id,
"data": data_dict
})
# 批量发送更新请求
logger.info(f"共构造 {len(update_records)} 条更新记录")
APP_ID = "675b900991ad2491c69389ca"
# ENTRY_ID = "6965eec36b73376aa0b5bff8"
ENTRY_ID = "6931063d64187eaf6b927557"
for record in tqdm(update_records):
payload = {
"api_key": APP_ID,
"entry_id": ENTRY_ID,
# "transaction_id": str(uuid.uuid4()), # 推荐:保证幂等
"data_id": record["data_id"],
"data": record["data"],
"is_start_trigger": False
}
res = api_instance.entry_data_update(payload)
# print(res)
+445
View File
@@ -0,0 +1,445 @@
# -*- coding: utf-8 -*-
import sys
import io
import imaplib
import email
import re
from datetime import datetime, timedelta
from email.header import decode_header
import pandas as pd
# 假设 api.py 在当前目录下,且包含 API 类
import requests
from typing import Optional, List, Dict, Any
from decimal import Decimal
import time
import numpy as np
from log_config import configure_task_logger, configure_error_task_logger
import json
# === 强制标准输出为 UTF-8 (兼容不同运行环境) ===
# 注意:在部分 IDE 中重新包装 sys.stdout 可能会导致乱码,若报错可注释掉以下两行
try:
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
except AttributeError:
pass
# ================= 配置区域 =================
EMAIL_ACCOUNT = "zhangyang@f6car.cn"
PASSWORD = "RGBdMggmJ4s2FzZK" # ⚠️ 生产环境建议使用环境变量,不要硬编码
IMAP_SERVER = "imap.qiye.aliyun.com"
IMAP_PORT = 993
SUBJECT_KEYWORD = "展会线索登记"
DAYS_TO_SCAN = 30 # 扫描最近30天
OUTPUT_FILE = f"展会线索_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
# 定义标准字段顺序
FIELD_KEYS = ["姓名", "手机号", "", "", "", "公司名称", "备注"]
class API:
def entry_data_list(self, data: dict, replace: bool = False, max_retries: int = 20) -> Dict: # 获取多条表单数据
"""
获取多条表单数据
:param max_retries: 最大重试次数
:param replace: 是否替换字段
:param data:
api_key: 应用id
entry_id: 表单id
:return:
"""
url = 'https://api.jiandaoyun.com/api/v5/app/entry/data/list'
headers = {
'Authorization': "Bearer qygHulymo1fekJk4CIZyNKjyQAzG8CFN", # 曹伟应用api测试 app_key
'Content-Type': 'application/json'
}
all_data_batches = [] # 用于存储每次请求返回的数据批次
last_data_id = None
exit_flag = False
while True:
payload = json.dumps({
"app_id": data['api_key'], # 应用ID
"entry_id": data['entry_id'], # 表单ID
"limit": 90,
"data_id": last_data_id,
"filter": data.get('filter', None)
})
retries = 0
while retries <= max_retries:
data_get = None
try:
res = requests.post(url=url, data=payload, headers=headers, timeout=10)
res.raise_for_status() # 检查HTTP响应状态码,如果不等于200会抛出异常
data_get = res.json()
if data_get["data"]:
all_data_batches.extend(data_get['data'])
last_data_id = data_get['data'][-1].get('_id')
print(f"已获取 {len(all_data_batches)} 条数据")
break # 成功则跳出循环
else:
if 'data' not in data_get or len(data_get['data']) == 0:
exit_flag = True
break
retries += 1
time.sleep(0.5) # 在重试之间稍作停顿
except requests.exceptions.RequestException as e:
retries += 1
time.sleep(0.5) # 在重试之间稍作停顿
if retries > max_retries:
all_data_batches.append(None) # 或者可以选择记录失败的payload以便后续处理
if exit_flag:
break
# 构建最终返回的字典
final_data = {
'data': all_data_batches # 'data' 键对应的值是列表的列表
}
return final_data
@staticmethod
def data_batch_create(data: dict, max_retries: int = 20) -> Optional[requests.Response]: # 新建单条数据
"""
新建单条表单数据
:param max_retries: 最大重试次数
:param data: 应该包含应用id、表单id,以及新建的数据data['data']
:return: 返回创建后简道云返回的信息
"""
url = 'https://api.jiandaoyun.com/api/v5/app/entry/data/create'
headers = {
'Authorization': "Bearer qygHulymo1fekJk4CIZyNKjyQAzG8CFN", # 曹伟应用api测试 app_key
'Content-Type': 'application/json'
}
# noinspection DuplicatedCode
payload = json.dumps({
"app_id": data['api_key'], # 应用ID
"entry_id": data['entry_id'], # 表单ID
"data": data['data'],
"is_start_workflow": data.get('is_start_workflow', "false"),
"is_start_trigger": data.get('is_start_trigger', "false"),
"transaction_id": data.get('transaction_id', "")
}
)
retries = 0
while retries <= max_retries:
try:
res: requests.Response = requests.post(url=url, data=payload, headers=headers, timeout=10)
res.raise_for_status() # 检查HTTP响应状态码,如果不等于200会抛出异常
data_get = res.json()
if res.status_code == 200:
return data_get
else:
retries += 1
time.sleep(3) # 在重试之间稍作停顿
except requests.exceptions.RequestException as e:
retries += 1
time.sleep(3) # 在重试之间稍作停顿
if retries > max_retries:
print(
f"任务 {data['data_list']} 连续{max_retries}次请求失败,放弃此次请求。")
return None
# ===========================================
def decode_mime_words(s):
if not s:
return ""
decoded_parts = []
# decode_header 返回的是 list of (bytes/str, encoding)
for part, encoding in decode_header(s):
if isinstance(part, bytes):
decoded_parts.append(part.decode(encoding or 'utf-8', errors='ignore'))
else:
decoded_parts.append(str(part))
return "".join(decoded_parts)
def extract_data_from_body(body_text):
"""
从邮件正文中提取线索数据。
格式:姓名 | 手机号 | 省 | 市 | 区 | 公司 | 备注
"""
if not body_text:
return []
data_list = []
# 【修复点 1】splitlines() 是方法,需要加括号
lines = body_text.splitlines()
for line in lines:
line = line.strip()
# 如果行中没有分隔符,跳过
if '|' not in line:
continue
# 按 '|' 分割并去除首尾空格
parts = [p.strip() for p in line.split('|')]
# 【关键校验】至少需要前两个字段(姓名、手机号)非空
if len(parts) < 2 or not parts[0] or not parts[1]:
continue
# 构建字典,动态映射
record = {}
for i, key in enumerate(FIELD_KEYS):
if i < len(parts):
record[key] = parts[i]
else:
record[key] = "" # 缺失的字段填空字符串
data_list.append(record)
return data_list
def save_to_excel(leads, filename):
if not leads:
return None
df = pd.DataFrame(leads)
# 定义期望的列顺序
cols = ["姓名", "手机号", "", "", "", "公司名称", "备注", "来源邮件时间"]
# 确保列存在且顺序正确
# 先保留所有现有列中在 cols 里的,按 cols 顺序
ordered_cols = [c for c in cols if c in df.columns]
# 再加上可能存在的其他列(虽然逻辑上不应该有,但以防万一)
other_cols = [c for c in df.columns if c not in cols]
final_cols = ordered_cols + other_cols
df = df[final_cols]
# df.to_excel(filename, index=False)
return df
def main():
print(f"正在连接 IMAP 服务器:{IMAP_SERVER} ...")
mail = None
start_date = datetime.now() - timedelta(days=DAYS_TO_SCAN)
date_str = start_date.strftime("%d-%b-%Y").upper()
all_leads = []
count_processed = 0
try:
mail = imaplib.IMAP4_SSL(IMAP_SERVER, IMAP_PORT)
mail.login(EMAIL_ACCOUNT, PASSWORD)
mail.select("INBOX")
print(f"正在搜索 [{date_str}] 之后的邮件...")
search_query = f'(SINCE "{date_str}")'
status, messages = mail.search(None, search_query)
if status != "OK":
print("❌ 搜索失败")
return
mail_ids = messages[0].split()
if not mail_ids:
print(f"✅ 未找到 {date_str} 之后的新邮件。")
return
print(f"📩 找到 {len(mail_ids)} 封近期邮件,开始详细扫描...")
for mail_id in mail_ids:
try:
status, msg_data = mail.fetch(mail_id, "(RFC822)")
if status != "OK":
continue
raw_email = msg_data[0][1]
if isinstance(raw_email, bytes):
mime_msg = email.message_from_bytes(raw_email)
else:
mime_msg = email.message_from_string(raw_email.decode('utf-8', errors='ignore'))
subject = decode_mime_words(mime_msg.get("Subject"))
if SUBJECT_KEYWORD not in subject:
continue
count_processed += 1
date_str_full = mime_msg.get("Date")
body_content = ""
if mime_msg.is_multipart():
for part in mime_msg.walk():
content_disposition = part.get_content_disposition()
if content_disposition and "attachment" in str(content_disposition):
continue
content_type = part.get_content_type()
if content_type in ["text/plain", "text/html"]:
try:
charset = part.get_content_charset() or 'utf-8'
payload = part.get_payload(decode=True)
if payload:
text = payload.decode(charset, errors='ignore') if isinstance(payload,
bytes) else str(
payload)
if content_type == "text/html":
text = re.sub(r'<[^>]+>', ' ', text)
body_content += text + "\n"
except Exception:
pass
else:
try:
charset = mime_msg.get_content_charset() or 'utf-8'
payload = mime_msg.get_payload(decode=True)
if payload:
body_content = payload.decode(charset, errors='ignore') if isinstance(payload,
bytes) else str(
payload)
except Exception:
pass
leads = extract_data_from_body(body_content)
for lead in leads:
lead["来源邮件时间"] = date_str_full
if leads:
print(f"[{subject}] -> 提取 {len(leads)}")
all_leads.extend(leads)
except Exception as e:
print(f"处理邮件 ID {mail_id} 时出错:{e}")
continue
# ================= 新增:本地数据去重逻辑 =================
original_count = len(all_leads)
if original_count > 0:
seen_phones = set()
unique_leads = []
for lead in all_leads:
phone = str(lead.get("手机号", "")).strip()
# 如果手机号为空,或者已经出现过,则跳过
if not phone or phone in seen_phones:
continue
seen_phones.add(phone)
unique_leads.append(lead)
all_leads = unique_leads
removed_count = original_count - len(all_leads)
if removed_count > 0:
print(
f"\n⚠️ 检测到重复数据,已根据【手机号】去重:原始 {original_count} 条 -> 去重后 {len(all_leads)} 条 (移除 {removed_count} 条)")
else:
print(f"\n✅ 数据检查完成,无重复手机号。共 {len(all_leads)} 条。")
# =======================================================
df = save_to_excel(all_leads, OUTPUT_FILE)
if df is not None:
print(f"\n✅ 成功!共扫描 {count_processed} 封匹配邮件,最终有效线索 {len(all_leads)} 条。")
print(f"文件已保存至:{OUTPUT_FILE}")
else:
print(f"\n⚠️ 扫描完成,但在 {count_processed} 封近期邮件中未找到符合格式的数据。")
return # 如果没有数据,后续同步逻辑无需执行
# 同步至简道云
if all_leads:
print("\n开始同步至简道云...")
api_instance = API()
payload_query = {
"api_key": "66b9678280b37f8a276b1d01",
"entry_id": "69b22dc5434e05c7b6b4b5b2",
}
try:
response = api_instance.entry_data_list(payload_query)
now_data = response.get("data", []) if response else []
existing_phones = set()
phone_widget_id = "_widget_1692928669587"
for item in now_data:
phone_val = item.get(phone_widget_id)
if phone_val:
existing_phones.add(str(phone_val).strip())
print(f"简道云现有手机号数量:{len(existing_phones)}")
new_count = 0
# 此时 df 已经是去重后的数据,且 all_leads 也是去重后的
# 再次遍历 df 确保只提交本地没有的(防止简道云已有但本地没查到的情况,虽然逻辑上上面已经过滤了)
# 为了代码健壮性,这里保留原有的 existing_phones 检查逻辑
for index, row in df.iterrows():
current_phone = str(row["手机号"]).strip()
if not current_phone:
continue
# 双重保险:如果简道云里已经有了,跳过
if current_phone in existing_phones:
print(f"跳过 (云端已存在): {current_phone}")
continue
new_payload = {
"api_key": "66b9678280b37f8a276b1d01",
"entry_id": "69b22dc5434e05c7b6b4b5b2",
"data": {
"_widget_1690785229260": {"value": row.get("姓名", "")},
"_widget_1690785229261": {"value": row.get("公司名称", "")},
"_widget_1692928669587": {"value": row.get("手机号", "")},
"_widget_1690785229266": {"value": row.get("备注", "")},
"_widget_1690785326597": {"value": {"province": row.get("", ""),
"city": row.get("", ""),
"district": row.get("", ""),
"detail": row.get("", "") + row.get("",
"") + row.get(
"", ""),
}
},
"_widget_1690785229279": {"value": row.get("", "")},
"_widget_1773381838511": {"value": row.get("", "")},
"_widget_1692070309987": {"value": row.get("", "")},
}
}
result = api_instance.data_batch_create(new_payload)
if result and (result.get("success") or result.get("code") == 200 or "error" not in result):
new_count += 1
print(f"新增成功:{current_phone} ({row.get('姓名')})")
else:
print(f"提交结果:{current_phone}, 返回:{result}")
# 如果提交成功但返回格式奇怪,也可以考虑计入成功,视具体API文档而定
# 这里保守处理,只有明确成功才算
print(f"\n✅ 同步完成,本次新增 {new_count} 条数据。")
except Exception as api_err:
print(f"\n❌ 简道云 API 交互错误:{api_err}")
import traceback
traceback.print_exc()
except imaplib.IMAP4.error as e:
print("\n❌ IMAP 协议错误:")
print(f"错误详情:{e}")
except Exception as e:
print("\n❌ 发生严重错误:")
import traceback
traceback.print_exc()
finally:
if mail:
try:
mail.close()
mail.logout()
except:
pass
if __name__ == "__main__":
main()
+53
View File
@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
payload ={
"app_id": "675b900991ad2491c69389ca",
"data": {
"_widget_1767851302591": 1772508166000,
"_widget_1767863644355": "何文娟",
"_widget_1767851302590": "1772507997862",
"_widget_1767851302593": [
"064863221620345741"
],
"_widget_1767851302592": "15651815921",
"_widget_1767851302595": [
"122744083333090577"
],
"_widget_1767851302594": "064863221620345741",
"_widget_1767851302597": "废弃测试三月分店",
"_widget_1767851302596": "CHS202603030049192",
"_widget_1767952153422": [
"洗车",
"美容"
],
"_widget_1772264444051": "19999.0",
"_widget_1767851302599": "普通客户(VIP",
"_widget_1767851302598": "zyc测试公司废弃",
"_widget_1768290159749": [
"122744083333090577"
],
"_widget_1768290159748": "华南区域",
"_widget_1768290159747": "15870306745529522117",
"_widget_1767863644358": "15651815921",
"_widget_1768290159746": "16338827489080131642",
"_widget_1767863644357": "何文娟",
"_widget_1767863644356": "15651815921",
"_widget_1767863644582": [
"064863221620345741"
],
"_widget_1767851302585": "XQFWD20260303001",
"_widget_1772264443873": "台湾省",
"_widget_1772703526239": "1",
"_widget_1772264443872": "其它",
"_widget_1767851302600": 1772508361000,
"_widget_1768290159737": 1773112966000,
"_widget_1767851302602": "皇冠版",
"_widget_1772264443811": "C",
"_widget_1772264443812": "5",
"_widget_1767851302604": "30"
}
,
"entry_id": "695f439e3e910f09190d8e99",
"is_start_workflow": true
}
+208
View File
@@ -0,0 +1,208 @@
{
"cells": [
{
"metadata": {},
"cell_type": "markdown",
"source": "新签节点调用格式",
"id": "671b10d308af2bdc"
},
{
"cell_type": "code",
"id": "initial_id",
"metadata": {
"collapsed": true,
"ExecuteTime": {
"end_time": "2026-03-12T09:25:38.245095300Z",
"start_time": "2026-03-12T09:25:36.441415900Z"
}
},
"source": [
"import requests\n",
"url = \"https://manage-pre.f6yc.com/hive-admin/yida/updateNode\"\n",
"\n",
"\n",
"payload = {\n",
" \"nodeCode\": \"ORG_RESEARCH\",\n",
" \"needTraining\": \"是\",\n",
" \"impPrincipal\":\"['171408516124043808']\",\n",
" \"instanceId\": \"69b269d6c193f83cc27f65e7\"\n",
"}\n",
"\n",
"# [{\"_id\":\"69a285f3fa0d9fb1984da9bf\",\"name\":\"张乐乐\",\"username\":\"171408516124043808\",\"status\":1,\"type\":0}]\n",
"\n",
"response = requests.post(url, data=payload)\n",
"response.json()"
],
"outputs": [
{
"data": {
"text/plain": [
"{'code': 200, 'data': None, 'message': 'SUCCESS'}"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"execution_count": 14
},
{
"metadata": {},
"cell_type": "markdown",
"source": "# 续约联调",
"id": "6ce335625a1c1fbb"
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2026-03-26T07:12:43.283568100Z",
"start_time": "2026-03-26T07:12:42.653902400Z"
}
},
"cell_type": "code",
"source": [
"# 可以引用一些第三方库.\n",
"import json\n",
"import requests\n",
"import time\n",
"import random\n",
"import time\n",
"import binascii # 【修正1】添加缺失的 binascii 导入\n",
"from pyDes import des, CBC, PAD_PKCS5\n",
"\n",
"data_id = \"69c4dc44986b303c2ef69d46\" # 数据id\n",
"orgid = \"16058986391127773239\" # 门店id\n",
"order_id = \"XYFWD20260326002\" # 服务单号\n",
"operation_consultant_str = '[{\"_id\":\"69a285f3fa0d9fb1984da9bf\",\"name\":\"张乐乐\",\"username\":\"171408516124043808\",\"status\":1,\"type\":0}]'# 专属人员顾问\n",
"\n",
"url = \"https://manage-pre.f6yc.com/hive-admin/py/yida/renewal/insertRenewalFormsData\"\n",
"\n",
"def des_encrypt(s):\n",
" \"\"\"\n",
" DES 加密\n",
" :param s: 原始字符串\n",
" :return: 加密后字符串,16进制\n",
" \"\"\"\n",
" secret_key = 'HwdMBW8o'\n",
" iv = secret_key\n",
" k = des(secret_key, CBC, iv, pad=None, padmode=PAD_PKCS5)\n",
" en = k.encrypt(s, padmode=PAD_PKCS5)\n",
" return binascii.b2a_base64(en, newline=False)\n",
"\n",
"\n",
"def des_descrypt(s):\n",
" \"\"\"\n",
" DES 解密\n",
" :param s: 加密后的字符串,16进制\n",
" :return: 解密后的字符串\n",
" \"\"\"\n",
" secret_key = 'HwdMBW8o'\n",
" iv = secret_key\n",
" k = des(secret_key, CBC, iv, pad=None, padmode=PAD_PKCS5)\n",
" de = k.decrypt(binascii.a2b_base64(s), padmode=PAD_PKCS5)\n",
" return de\n",
"\n",
"\n",
"impPrincipal_list = []\n",
"\n",
"if operation_consultant_str:\n",
" try:\n",
" # 将字符串 '[{...}]' 转换为 Python 列表 [{...}]\n",
" operation_list = json.loads(operation_consultant_str)\n",
"\n",
" # 确保解析出来的是列表\n",
" if isinstance(operation_list, list):\n",
" for operate in operation_list:\n",
" # 确保每一项是字典且包含 username\n",
" if isinstance(operate, dict) and \"username\" in operate:\n",
" impPrincipal_list.append(operate[\"username\"])\n",
" else:\n",
" # 如果解析出来不是列表(比如是个单对象),做兼容处理\n",
" if isinstance(operation_list, dict) and \"username\" in operation_list:\n",
" impPrincipal_list.append(operation_list[\"username\"])\n",
"\n",
" except json.JSONDecodeError:\n",
" # 如果解析失败,记录错误或保持列表为空\n",
" print(f\"JSON 解析失败: {operation_consultant_str}\")\n",
" impPrincipal_list = []\n",
"else:\n",
" operation_list = []\n",
"\n",
"impPrincipal_value=impPrincipal_list[0]\n",
"\n",
"t = time.time()\n",
"ts = int(round(t * 1000))\n",
"randint = random.randint(100000000, 999999999)\n",
"req = data_id + \"|\" + orgid + \"|\" +order_id+ \"|\" + impPrincipal_value + \"_\" + str(ts) + \"_\" + str(randint)\n",
"# 实例ID|门ID|服务单号|专属运营顾问\n",
"str_en = des_encrypt(req)\n",
"print(str_en.decode('utf-8'))\n",
"req_new = str_en.decode('utf-8')\n",
"payload = {\n",
" 'req':req_new,\n",
" 't':ts,\n",
" 'r':randint\n",
"}\n",
"\n",
"res = requests.post(url,data=payload)\n"
],
"id": "fc98d87aa8b19ce2",
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Lmj1kXumBcGLR5IKAtgFDP2meGj6FgLzq7gaQ/6wDuZ3d43P0mtQCyUdQXkeC4kYxOT8w7n1xNxYhIy9PVq/xUR9emnP5CQweBFe8P3S9tRZdNFVOdipnD+xiof/q9b//C87kgUEIH7JT00nlBBkaQ==\n"
]
}
],
"execution_count": 2
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2026-03-13T08:12:33.285136400Z",
"start_time": "2026-03-13T08:12:33.246808900Z"
}
},
"cell_type": "code",
"source": "impPrincipal_value",
"id": "8edd45ef4657d8ed",
"outputs": [
{
"data": {
"text/plain": [
"'171408516124043808'"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"execution_count": 26
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+10
View File
@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
import pandas as pd
import requests
url = "https://manage-pre.f6yc.com/hive-admin/yida/renewal/updateNode"
payload = {"instanceId":"15348c27-57b7-4285-b93b-87b3d41f5a28","nodeCode":"SIXTY","impPrincipal":"[\"053052302136860181\"]"}
result = requests.post(url, data=payload)
print(result.text)
+249
View File
@@ -0,0 +1,249 @@
# -*- coding: utf-8 -*-
import os
import re
import sys
import pandas as pd
from tqdm import tqdm
# 让 test 脚本可以 import 到项目根目录的模块(api.py / yd_api.py / log_config.py
# 将项目根目录加入模块搜索路径,确保可以 import 根目录下的 api.py/yd_api.py
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_ROOT = os.path.dirname(SCRIPT_DIR)
if PROJECT_ROOT not in sys.path:
sys.path.insert(0, PROJECT_ROOT)
from api import API
from log_config import configure_error_task_logger, configure_task_logger
from yd_api import YDAPI
logger = configure_task_logger()
error_task_logger = configure_error_task_logger()
api_instance = API()
yd_api_instance = YDAPI()
# =========================
# 需要你关注/可能要改的配置
# =========================
# 简道云:续约待办表单(目标表单)
APP_ID = "675b900991ad2491c69389ca"
ENTRY_ID = "6931063d64187eaf6b927557"
# 宜搭:续约服务流程(来源流程)
APP_TYPE = "APP_UYZ0KG6L0CCNV80GZ66O"
SYSTEM_TOKEN = "XA966F81JAJOFCVVVKO64E9MIIZV1EWE5SFMKJ2"
# 简道云:员工表(用于把“区域经理姓名”转成“员工ID”,如果你简道云字段是人员控件,必须传ID)
STAFF_APP_ID = "6694d3c4fcb69ca9a111a6c4"
STAFF_ENTRY_ID = "6769204a1902c9341340a1bc"
STAFF_NAME_WIDGET = "_widget_1734942794144"
STAFF_ID_WIDGET = "_widget_1734942794145"
# 输入数据源必须包含这三列:
# - data_id:简道云待办数据的 data_id
# - 门店编码:用于简单校验(对不上会提示)
# - 宜搭实例ID:用于拉取宜搭实例详情
REQUIRED_INPUT_COLUMNS = ("data_id", "门店编码", "宜搭实例ID")
if __name__ == "__main__":
# 第 1 个命令行参数:你的数据源文件路径(csv/xlsx/xls
df = pd.read_excel(fr"C:\Users\hp_z66\Downloads\续约服务流程_20260326133517.xlsx",sheet_name="简道云-宜搭实例id", dtype=str).fillna("")
# 兼容列名:有的表叫“数据ID/实例ID”,统一成脚本内部使用的列名
df = df.rename(columns={"数据ID": "data_id", "dataId": "data_id", "DataID": "data_id", "实例ID": "宜搭实例ID"})
for c in REQUIRED_INPUT_COLUMNS:
if c not in df.columns:
raise SystemExit(f"缺少必需列: {c}")
for c in REQUIRED_INPUT_COLUMNS:
df[c] = df[c].astype(str).str.strip()
df = df[(df["data_id"] != "") & (df["宜搭实例ID"] != "")]
df = df.drop_duplicates(subset=["data_id"]).reset_index(drop=True)
# 读取员工表:构建 “姓名 -> 员工ID” 映射
staff_resp = api_instance.entry_data_list({"api_key": STAFF_APP_ID, "entry_id": STAFF_ENTRY_ID}) or {}
staff_list = staff_resp.get("data", []) or []
name_to_staff_id = {}
for item in staff_list:
n = str(item.get("_widget_1734942794144", "")).strip()
i = str(item.get("_widget_1734942794145", "")).strip()
if n and i:
name_to_staff_id[n] = i
logger.info(f"员工数: {len(name_to_staff_id)}")
# 读取简道云表单字段:label(中文名) -> name(_widget_xxx)
# 这样你只要改 labels 里的中文字段名,脚本会自动找到对应 widget_id
widget_list = api_instance.entry_widget_list({"api_key": APP_ID, "entry_id": ENTRY_ID}) or {}
widgets = widget_list.get("widgets", []) or []
label_to_name = {}
for w in widgets:
l = str(w.get("label", "")).strip()
n = str(w.get("name", "")).strip()
if l and n:
label_to_name[l] = n
# 需要同步到简道云的字段(中文 label)
# 如果你简道云字段名不是这些(比如“省份/城市/区县”),就在这里改成你表单里实际的 label
labels = ["120天是否联系上客户", "60天是否联系上客户", "30天是否联系上客户", "区域经理", "服务单号", "门店ID", "公司id", "", "",]
jdy_map = {l: label_to_name.get(l) for l in labels if label_to_name.get(l)}
miss = [l for l in labels if l not in jdy_map]
if miss:
logger.warning(f"简道云缺少字段: {miss}")
logger.warning(f"已匹配字段: {list(jdy_map.keys())} | 门店ID_widget={jdy_map.get('门店ID')} 公司id_widget={jdy_map.get('公司id')}")
# 获取宜搭 token(后续拉取实例详情使用)
token = yd_api_instance.generateToken()
ok = 0
fail = 0
skip = 0
for _, row in tqdm(df.iterrows(), total=len(df)):
data_id = str(row.get("data_id", "")).strip()
instance_id = str(row.get("宜搭实例ID", "")).strip()
store_code = str(row.get("门店编码", "")).strip()
if not data_id or not instance_id:
logger.warning(f"跳过:data_id/实例ID 为空 data_id={data_id} instance_id={instance_id} 行数据={row.to_dict()}")
skip += 1
continue
try:
# 拉取宜搭实例详情(里面的 formData 才是字段数据)
info = yd_api_instance.processes_instancesInfos(token, instance_id, APP_TYPE, SYSTEM_TOKEN) or {}
container = info.get("data") if isinstance(info, dict) else None
form = {}
if isinstance(container, dict):
form = container.get("formData") or container
if not isinstance(form, dict) or not form:
logger.warning(f"跳过:宜搭实例无表单数据 instance_id={instance_id} data_id={data_id}")
skip += 1
# 简单校验:输入的门店编码 vs 宜搭表单里的门店编码,不一致就打日志提醒
# 宜搭门店编码字段IDtextField_ksydghqw(来自你旧脚本/导出的 yd_process_details.csv
yd_store = str(form.get("textField_ksydghqw", "")).strip()
if yd_store and store_code and yd_store != store_code:
logger.warning(f"门店编码不一致: {store_code} vs {yd_store} 实例:{instance_id} data_id:{data_id}")
# 从 formData 中按字段ID取值:取到第一个非空值就返回
def pick(keys):
for k in keys:
v = form.get(k)
if v is None:
continue
s = str(v).strip()
if s and s.lower() not in {"nan", "null", "none"}:
return s
return ""
# 宜搭人员字段常见是:["张三(123)"] 这种结构,这里做一个“只拿姓名”的处理
def first_name(v):
s = str(v).strip()
if s.startswith("[") and s.endswith("]"):
inner = s[1:-1].split(",", 1)[0].strip().strip("'").strip('"')
s = inner
m = re.split(r"\(", s, maxsplit=1)
return m[0].strip() if m else s
# =========================
# 下面是“宜搭字段ID -> 业务字段”的取值逻辑(你最常修改的区域)
# =========================
# 说明:
# - 120/60/30 是否联系上客户:优先各节点字段,取不到就回退用统一字段 radioField_l85ppdia
# - 区域经理:employeeField_ksydghre
# - 服务单号:textField_kuntp6fl(你历史数据是 XYFWDxxxx
# - 省/市:textField_kuj8nx00 / textField_kuj8nx01
# - 区:优先 textField_kuhnydmk;取不到就从地址 textField_ksydghrm 里截取
v120 = pick(["radioField_ksydghrf",])
v60 = pick(["radioField_kuhnydmd", ])
v30 = pick(["radioField_kuhnydn0", ])
region_name = first_name(form.get("employeeField_ksydghre", ""))
# 如果简道云“区域经理”字段是人员控件:应传员工ID;否则传姓名也能写入(取决于你表单控件类型)
region_value = name_to_staff_id.get(region_name, region_name) if region_name else ""
service_no = pick(["textField_kuntp6fl"])
# 门店ID(强烈建议你把下面 keys 改成你宜搭里“门店ID/公司id”的真实字段ID)
# 示例:store_id = pick(["textField_orgid", "textField_id_own_org"])
store_id = pick(["textField_kuntp6fk"])
prov = pick(["textField_kuj8nx00"])
city = pick(["textField_kuj8nx01"])
# 拼装简道云更新 payload:每个字段必须是 {"value": 值}
data_dict = {}
if v120 and jdy_map.get("120天是否联系上客户"):
data_dict[jdy_map["120天是否联系上客户"]] = {"value": v120}
if v60 and jdy_map.get("60天是否联系上客户"):
data_dict[jdy_map["60天是否联系上客户"]] = {"value": v60}
if v30 and jdy_map.get("30天是否联系上客户"):
data_dict[jdy_map["30天是否联系上客户"]] = {"value": v30}
if region_value and jdy_map.get("区域经理"):
data_dict[jdy_map["区域经理"]] = {"value": region_value}
if service_no and jdy_map.get("服务单号"):
data_dict[jdy_map["服务单号"]] = {"value": service_no}
# 门店ID优先写入“门店ID”,如果表单没有此字段则回退写入“公司id”
if store_id:
if jdy_map.get("门店ID"):
data_dict[jdy_map["门店ID"]] = {"value": store_id}
elif jdy_map.get("公司id"):
data_dict[jdy_map["公司id"]] = {"value": store_id}
if prov and jdy_map.get(""):
data_dict[jdy_map[""]] = {"value": prov}
if city and jdy_map.get(""):
data_dict[jdy_map[""]] = {"value": city}
if not data_dict:
reasons = []
if not v120:
reasons.append("120天:值空")
elif not jdy_map.get("120天是否联系上客户"):
reasons.append("120天:未映射")
if not v60:
reasons.append("60天:值空")
elif not jdy_map.get("60天是否联系上客户"):
reasons.append("60天:未映射")
if not v30:
reasons.append("30天:值空")
elif not jdy_map.get("30天是否联系上客户"):
reasons.append("30天:未映射")
if not region_value:
reasons.append("区域经理:值空")
elif not jdy_map.get("区域经理"):
reasons.append("区域经理:未映射")
if not service_no:
reasons.append("服务单号:值空")
elif not jdy_map.get("服务单号"):
reasons.append("服务单号:未映射")
if not store_id:
reasons.append("门店ID:值空")
elif not (jdy_map.get("门店ID") or jdy_map.get("公司id")):
reasons.append("门店ID:表单无对应字段(门店ID/公司id)")
if not prov:
reasons.append("省:值空")
elif not jdy_map.get(""):
reasons.append("省:未映射")
if not city:
reasons.append("市:值空")
elif not jdy_map.get(""):
reasons.append("市:未映射")
logger.warning(
f"跳过 data_id:{data_id} 实例:{instance_id} | 原因: {', '.join(reasons)} | "
f"取值: v120={v120} v60={v60} v30={v30} region_name={region_name} region_value={region_value} "
f"service_no={service_no} store_id={store_id} prov={prov} city={city}"
)
skip += 1
continue
# 更新简道云数据(同你旧脚本)
payload = {
"api_key": APP_ID,
"entry_id": ENTRY_ID,
"data_id": data_id,
"data": data_dict,
"is_start_trigger": False,## 目前宜搭有通知
}
res = api_instance.entry_data_update(payload)
# 兼容两种返回格式:有的返回 {'status':'success',...},有的直接返回 {'data':{...}}
if isinstance(res, dict) and (res.get("status") == "success" or isinstance(res.get("data"), dict)):
ok += 1
else:
fail += 1
logger.warning(f"更新失败 data_id:{data_id} 实例:{instance_id} 返回:{res}")
except Exception as e:
fail += 1
error_task_logger.error(f"同步异常 data_id:{data_id} 实例:{instance_id} 错误:{e}", exc_info=True)
logger.info(f"完成 同步成功:{ok} 失败:{fail} 跳过:{skip}")
+2 -1
View File
@@ -193,7 +193,8 @@ class API:
"app_id": data['api_key'], # 应用ID
"entry_id": data['entry_id'], # 表单ID
"data_id": data['data_id'], # 数据ID
"data": data['data']
"data": data['data'],
"is_start_trigger": True
}
)
+2 -1
View File
@@ -449,6 +449,7 @@ class JDYToYDRenewalToDo(object):
"30天是否跟进": "_widget_1764820541632",
"30天处理人": "_widget_1764820541636",
"30天跟进时间": "_widget_1765352838633",
"是否联系上":"_widget_1764820541638",
"数据ID": "_id"
}
@@ -753,7 +754,7 @@ class JDYToYDRenewalToDo(object):
key=lambda x: parse_dt(x.get("operateTimeGMT") or x.get("activeTimeGMT")),
reverse=True,
)
# 优先使用当前待办(type == TODO),否则用最新一条
# 优先使用当前待办(type == "TODO"),否则用最新一条
yd_todo = next((r for r in yd_records if str(r.get("type")).upper() == "TODO"), None)
yd_latest = yd_todo or (yd_records[0] if yd_records else {})
yd_stage = extract_stage_from_text(
File diff suppressed because one or more lines are too long
+41
View File
@@ -1579,6 +1579,47 @@
}
],
"execution_count": 1
},
{
"metadata": {},
"cell_type": "markdown",
"source": "## 商机问题跟进表",
"id": "9e39df3c1ef0ccbc"
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2026-03-26T03:42:42.714525800Z",
"start_time": "2026-03-26T03:37:29.209197400Z"
}
},
"cell_type": "code",
"source": [
"import pandas as pd\n",
"from api import API\n",
"\n",
"api_instance = API()\n",
"df = pd.read_excel(r\"C:\\Users\\hp_z66\\OneDrive\\Desktop\\钉钉文件\\1111111商机问题跟进表_20260326092929.xlsx\",sheet_name=\"问题进行中删除\")\n",
"for index, row in df.iterrows():\n",
" data_id = row[\"data_id\"]\n",
" payload = {\n",
" \"api_key\": \"675b900991ad2491c69389ca\",\n",
" \"entry_id\": \"67f8b1d3307bad317abc3a9a\",\n",
" \"data_id\": data_id,\n",
" }\n",
" api_instance.entry_data_delete(payload)"
],
"id": "9982ace96792b53c",
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"\u001B[92m2026-03-26 11:37:29,793 - api.py - task_logger - INFO - 返回结果:, {'code': 4001, 'msg': 'Data does not exist.'}\u001B[0m\n"
]
}
],
"execution_count": 5
}
],
"metadata": {
+4 -2
View File
@@ -97,7 +97,6 @@ class YDAPI:
break
try:
res = requests.get(api, headers=headers, params=formData)
return res.json()
except (requests.exceptions.RequestException, Exception) as e:
print(f"请求出现异常: {e}, 正在重试({attempt + 1}/{max_retries})...")
@@ -155,7 +154,7 @@ class YDAPI:
systemToken="XA966F81JAJOFCVVVKO64E9MIIZV1EWE5SFMKJ2", instanceStatus="RUNNING",
max_retries=10, delay=2, createFromTimeGMT=None, createToTimeGMT=None,
modifiedFromTimeGMT=None,
modifiedToTimeGMT=None, searchFieldJson={},useAlias=False):
modifiedToTimeGMT=None, searchFieldJson={},useAlias=False, instanceIdList=None):
"""
函数功能读取流程表单的所有数据并加入重试机制
@@ -169,6 +168,7 @@ class YDAPI:
instanceStatus (str): 流程实例状态默认为"RUNNING"
max_retries (int): 最大重试次数默认为10次
delay (int): 每次重试之间的延迟秒数默认为2秒
instanceIdList (list): 实例ID列表用于精确查询
Returns:
dict: 返回从API获取的流程表单实例数据的JSON解析结果
@@ -199,6 +199,8 @@ class YDAPI:
),
"useAlias": useAlias,
}
if instanceIdList:
formData["instanceIdList"] = instanceIdList
# print(formData)
while True: