""" 项目材料相关后台任务模块 本模块包含项目材料相关的后台任务,包括: - 项目信息批量停用 - 材料信息批量修改 这些任务在后台线程中执行,不会阻塞主请求。 """ 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('表单已自动提交至下一步')