From 3938c820b5373ca0e8372b7a75835d155beae047 Mon Sep 17 00:00:00 2001 From: panda <1415243231@qq.com> Date: Thu, 8 Jan 2026 10:18:51 +0800 Subject: [PATCH] =?UTF-8?q?1.=E5=AE=A2=E6=88=B7=E4=BF=A1=E6=81=AF=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=EF=BC=8C=E5=B0=86=E7=A1=AC=E7=BC=96=E7=A0=81=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E5=8A=A8=E6=80=81=E5=8F=96=E5=80=BC=202.=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E9=A1=B9=E7=9B=AE=E6=89=B9=E9=87=8F=E5=81=9C=E7=94=A8?= =?UTF-8?q?=E3=80=81=E6=9D=90=E6=96=99=E6=89=B9=E9=87=8F=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/tasks/material_tasks.py | 281 ++++++++++++++++++++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 app/tasks/material_tasks.py diff --git a/app/tasks/material_tasks.py b/app/tasks/material_tasks.py new file mode 100644 index 0000000..9e9b0e9 --- /dev/null +++ b/app/tasks/material_tasks.py @@ -0,0 +1,281 @@ +""" +项目材料相关后台任务模块 + +本模块包含项目材料相关的后台任务,包括: +- 项目信息批量停用 +- 材料信息批量修改 + +这些任务在后台线程中执行,不会阻塞主请求。 +""" +import logging +import traceback +import requests +import time +from typing import Dict, Any, List, Optional +from datetime import datetime +from tqdm import tqdm +from app.tasks.common import update_jiandaoyun, approve_workflow, get_operate_org_id, get_card_list, \ + execute_failure_handler +import pandas as pd +import os + +logger = logging.getLogger('app') + + +def batch_disable_projects(data: Dict[str, Any], cookies: Dict[str, str], df: pd.DataFrame, save_path: str, + option) -> None: + """ + 项目批量停用后台任务 + + 在后台线程中批量停用项目,从 Excel 文件中读取项目编码。 + 执行完成后会更新简道云表单并自动提交工作流。 + + Args: + data: 包含表单ID(api_key)、表单ID(entry_id)、数据ID(data_id)的字典 + cookies: 用户登录 F6 系统的 cookies 信息 + df: Excel 文件读取的内容,DataFrame 格式,第一列为项目编码 + save_path: Excel 文件保存的地址,执行完成后会删除此文件 + option: 批量启用、批量停用 + + Returns: + None + + 注意: + - 无效的项目(None、空字符串)会被跳过 + - 执行完成后会自动删除上传的文件 + - 执行结果会更新到简道云表单 + """ + if option == "批量启用": + type_ = 0 # 1 停用,0启用 + else: + type_ = 1 + df = df.where(pd.notnull(df), None) + + # 获取门店id + org_id = get_operate_org_id(data) + # 获取项目信息 + json_data = { + 'param': '', + 'name': '', + 'customCode': '', + 'currentPage': 1, + 'pageSize': 100, + 'isDel': -type_, + 'customInvoiceCategory': 0, + 'idOwnOrg': org_id, + } + + response = requests.post( + 'https://ids-goods.f6car.cn/f6-ids-goods/service/getServiceList', + cookies=cookies, + json=json_data, + ) + all_project_list = [] + total_pages = response.json().get("data", {}).get("totalPages", "") + for page in tqdm(range(1, total_pages + 1)): + json_data['currentPage'] = str(page) + response = requests.post( + 'https://ids-goods.f6car.cn/f6-ids-goods/service/getServiceList', + cookies=cookies, + json=json_data, + ) + project_list = response.json().get("data", {}).get("records", []) + all_project_list.extend(project_list) + + # 遍历获取到的项目信息停用文件中的项目 + code_list = df.iloc[:, 0].dropna().astype(str).tolist() + res_data_list = [] + results = [] + for item in tqdm(all_project_list): + custom_code = item.get("customCode") + if not custom_code or str(custom_code) not in code_list or not code_list: + continue + info_id = item.get("infoId") + pk_id = item.get("pkId") + json_data = { + "orgIdList": [ + org_id, + ], + "isDel": type_, # 1 停用 0启用 + "infoId": info_id, + "pkId": pk_id, + "type": 1, + "idOwnOrg": org_id + } + try: + response = requests.post( + 'https://ids-goods.f6car.cn/f6-ids-goods/service/editAttributeByType', + cookies=cookies, + json=json_data, + ) + res_data_list.append(response.json()) + response.raise_for_status() # 抛出HTTP错误 + results.append({'材料编码': custom_code, '状态': '停用/启用成功'}) + except requests.exceptions.RequestException as e: + results.append({'材料编码': custom_code, '状态': f'停用/启用失败: {str(e)}'}) + pass + + print({'msg': '已执行', 'msg_details': f'{results}'}) + logger.info(f"停用/启用结果: {results}") + os.remove(save_path) + print(f'{save_path}已删除') + # 调用api回写改掉 执行明细与执行状态 + msg = update_jiandaoyun(data, f'{results}') + + if msg.get('msg'): + approve_workflow(data) + print('表单已自动提交至下一步') + + +def batch_modify_materials(data: Dict[str, Any], cookies: Dict[str, str], df: pd.DataFrame, save_path: str, + ) -> None: + """ + 材料批量修改后台任务 + + 在后台线程中批量修改材料,从 Excel 文件中读取材料编码。 + 执行完成后会更新简道云表单并自动提交工作流。 + + Args: + data: 包含表单ID(api_key)、表单ID(entry_id)、数据ID(data_id)的字典 + cookies: 用户登录 F6 系统的 cookies 信息 + df: Excel 文件读取的内容,DataFrame 格式,第一列为材料编码 + save_path: Excel 文件保存的地址,执行完成后会删除此文件 + + Returns: + None + + 注意: + - 无效的材料编码(None、空字符串)会被跳过 + - 执行完成后会自动删除上传的文件 + - 执行结果会更新到简道云表单 + """ + + # 获取门店id + org_id = get_operate_org_id(data) + # 获取材料信息 + json_data = { + 'keyWord': '', + 'idOwnOrg': org_id, + 'currentPage': 1, + 'pageSize': 100, + 'name': '', + 'brand': '', + 'supplierCode': '', + 'customCode': '', + 'categoryName': '', + 'categoryId': '', + 'labelId': '', + 'labelName': '', + 'spec': '', + 'applyModel': '', + 'sellPurchaseStatuses': [ + 2, + 3, + 4, + 5, + ], + 'customInvoiceCategory': 0, + 'getThirdPlatformCode': 0, + } + + response = requests.post( + 'https://ids-goods.f6car.com/f6-ids-goods/part/getExactPartStockInfo', + cookies=cookies, + json=json_data, + ) + all_materials_list = [] + total_pages = response.json().get("data", {}).get("totalPages", "") + for page in tqdm(range(1, total_pages + 1)): + json_data['currentPage'] = str(page) + response = requests.post( + 'https://ids-goods.f6car.com/f6-ids-goods/part/getExactPartStockInfo', + cookies=cookies, + json=json_data, + ) + materials_list = response.json().get("data", {}).get("records", []) + all_materials_list.extend(materials_list) + + # 构建更新映射:{原customCode: {new_customCode, brand, name, spec}} + update_map = {} + for _, row in df.iterrows(): + orig_code = str(row.iloc[0]).strip() if pd.notna(row.iloc[0]) else None + if not orig_code: + continue + update_map[orig_code] = { + "customCode": str(row.iloc[1]).strip() if pd.notna(row.iloc[1]) else "", + "brand": str(row.iloc[2]).strip() if pd.notna(row.iloc[2]) else "", + "name": str(row.iloc[3]).strip() if pd.notna(row.iloc[3]) else "", + "spec": str(row.iloc[4]).strip() if pd.notna(row.iloc[4]) else "", + } + + # 遍历获取到的材料信息修改材料属性 + code_list = df.iloc[:, 0].dropna().astype(str).tolist() + res_data_list = [] + results = [] + for item in tqdm(all_materials_list): + custom_code = item.get("customCode") + # 判断当前材料编码是否在文件中 + if not custom_code or str(custom_code) not in update_map: + continue + part_id = item.get("partId") + try: + # 获取材料明细 + params = { + 'partId': part_id, + 'customInvoiceCategory': '0', + 'getThirdPlatformCode': '0', + } + + materials_response = requests.get( + 'https://ids-goods.f6car.com/f6-ids-goods/part/getPartInfo', + params=params, + cookies=cookies, + ) + if materials_response.status_code != 200: + results.append({'材料编码': custom_code, '状态': '获取明细失败'}) + continue + + detail = materials_response.json().get("data") + if not detail: + results.append({'材料编码': custom_code, '状态': '明细为空'}) + continue + + updates = update_map[custom_code] + # === 关键:强制覆盖,空值也写入 === + detail["customCode"] = updates["customCode"] # 可能是 "" + detail["brand"] = updates["brand"] # 可能是 "" + detail["name"] = updates["name"] # 可能是 "" + detail["showName"] = updates["name"] # 同步 showName(重要!) + detail["spec"] = updates["spec"] # 可能是 "" + + # 修复价格和数组(必须!) + for f in ["purchasePrice", "sellPrice"]: + if isinstance(detail.get(f), (int, float)): + detail[f] = f"{detail[f]:.2f}" + if detail.get("partBarCodeVos") is None: + detail["partBarCodeVos"] = [] + + update_resp = requests.post( + 'https://ids-goods.f6car.com/f6-ids-goods/part/updateAreaPartBasicInfo', + cookies=cookies, + json=detail + ) + if update_resp.status_code == 200 and update_resp.json().get("code") == 200: + results.append({'材料编码': custom_code, '状态': '修改成功'}) + else: + msg = update_resp.json().get("message", "未知错误") + results.append({'材料编码': custom_code, '状态': f'修改失败: {msg}'}) + except requests.exceptions.RequestException as e: + results.append({'材料编码': custom_code, '状态': f'修改属性失败: {str(e)}'}) + pass + + print({'msg': '已执行', 'msg_details': f'{results}'}) + logger.info(f"批量修改结果: {results}") + os.remove(save_path) + print(f'{save_path}已删除') + # 调用api回写改掉 执行明细与执行状态 + msg = update_jiandaoyun(data, f'{results}') + + if msg.get('msg'): + approve_workflow(data) + print('表单已自动提交至下一步')