|
|
|
@ -1773,7 +1773,21 @@ class DataStdService: |
|
|
|
) |
|
|
|
|
|
|
|
return binary_data |
|
|
|
@staticmethod |
|
|
|
async def get_code_import_template_services(): |
|
|
|
""" |
|
|
|
获取导入模板service |
|
|
|
|
|
|
|
:return: 导入模板excel的二进制数据 |
|
|
|
""" |
|
|
|
header_list = ['代码归属','归属系统', '标准代码编号', '标准代码名称', '代码值', '代码含义'] |
|
|
|
selector_header_list = ['代码归属'] |
|
|
|
option_list = [{'代码归属': ['公司级', '系统级']}] |
|
|
|
binary_data = get_excel_template( |
|
|
|
header_list=header_list, selector_header_list=selector_header_list, option_list=option_list |
|
|
|
) |
|
|
|
|
|
|
|
return binary_data |
|
|
|
|
|
|
|
@classmethod |
|
|
|
async def batch_import_std_services( |
|
|
|
@ -2138,4 +2152,234 @@ class DataStdService: |
|
|
|
await query_db.rollback() |
|
|
|
raise ServiceException(message=f"导入失败:{str(e)}") |
|
|
|
|
|
|
|
@classmethod |
|
|
|
async def batch_import_code_std_services( |
|
|
|
cls, |
|
|
|
request: Request, |
|
|
|
query_db: AsyncSession, |
|
|
|
file: UploadFile, |
|
|
|
current_user: CurrentUserModel |
|
|
|
): |
|
|
|
""" |
|
|
|
批量导入标准代码(带审批)。 |
|
|
|
Excel 表头(中文)必须包含: |
|
|
|
['代码归属','归属系统','标准代码编号','标准代码名称','代码值','代码含义'] |
|
|
|
|
|
|
|
规则: |
|
|
|
- 每行表示一个 codeItem(子项),必须包含标准代码编号(父级) |
|
|
|
- 按 标准代码编号 分组,父级取组首行 |
|
|
|
- 父/子存在走修改审批,不存在走新增审批 |
|
|
|
- 整批使用同一 flowId,写审批表后发起审批 |
|
|
|
""" |
|
|
|
import io |
|
|
|
import pandas as pd |
|
|
|
import uuid |
|
|
|
from datetime import datetime |
|
|
|
from fastapi import HTTPException |
|
|
|
|
|
|
|
# 读 Excel |
|
|
|
content = await file.read() |
|
|
|
try: |
|
|
|
df = pd.read_excel(io.BytesIO(content)) |
|
|
|
except Exception: |
|
|
|
raise HTTPException(status_code=400, detail="Excel 文件解析失败") |
|
|
|
|
|
|
|
if df.empty: |
|
|
|
raise HTTPException(status_code=400, detail="导入文件内容为空") |
|
|
|
|
|
|
|
# 中文表头 -> 内部列名 |
|
|
|
header_dict = { |
|
|
|
'代码归属': 'code_vest', |
|
|
|
'归属系统': 'source_system', |
|
|
|
'标准代码编号': 'std_code_no', |
|
|
|
'标准代码名称': 'std_code_name', |
|
|
|
'代码值': 'code_value', |
|
|
|
'代码含义': 'code_meaning' |
|
|
|
} |
|
|
|
df.rename(columns=header_dict, inplace=True) |
|
|
|
|
|
|
|
# 归属映射 |
|
|
|
vest_mapping = { |
|
|
|
'公司级': 'company', |
|
|
|
'系统级': 'sys' |
|
|
|
} |
|
|
|
|
|
|
|
def safe_str(val): |
|
|
|
if pd.isna(val): |
|
|
|
return "" |
|
|
|
return str(val).strip() |
|
|
|
|
|
|
|
# 获取系统树 |
|
|
|
data_tree_result = await MetataskService.get_data_source_tree(request, current_user) |
|
|
|
|
|
|
|
# 获取系统中已有父级标准代码 |
|
|
|
std_codes_all = await DataStdDao.get_std_code_list_all(query_db) # 返回 DataStdCode 对象列表(class_id == 'code') |
|
|
|
|
|
|
|
batch_flow_id = str(uuid.uuid4()) |
|
|
|
|
|
|
|
# 基本校验 |
|
|
|
if 'std_code_no' not in df.columns: |
|
|
|
raise HTTPException(status_code=400, detail="导入文件缺少列:标准代码编号") |
|
|
|
|
|
|
|
# 统一空值并按 std_code_no 分组 |
|
|
|
df['std_code_no'] = df['std_code_no'].fillna("").astype(str) |
|
|
|
grouped = df.groupby(df['std_code_no']) |
|
|
|
|
|
|
|
try: |
|
|
|
for std_code_no, group in grouped: |
|
|
|
# 跳过空组或空行 |
|
|
|
if not std_code_no or std_code_no.strip() == "": |
|
|
|
first_idx = int(group.index[0]) |
|
|
|
raise HTTPException(status_code=400, detail=f"第 {first_idx + 2} 行导入失败:缺少标准代码编号") |
|
|
|
|
|
|
|
# 匹配系统中是否已有该父级(匹配 cd_no) |
|
|
|
matched_std = next( |
|
|
|
(s for s in std_codes_all if getattr(s, 'cd_no', None) == std_code_no), |
|
|
|
None |
|
|
|
) |
|
|
|
|
|
|
|
# 取组首行作为父级信息来源 |
|
|
|
first_row = group.iloc[0] |
|
|
|
parent_src_input = safe_str(first_row.get('source_system')) |
|
|
|
matched_source = next( |
|
|
|
(ds for ds in data_tree_result if getattr(ds, 'name', None) == parent_src_input or str(getattr(ds, 'id', '')) == parent_src_input), |
|
|
|
None |
|
|
|
) |
|
|
|
if matched_source: |
|
|
|
src_sys_id = int(getattr(matched_source, 'id')) |
|
|
|
elif not parent_src_input: |
|
|
|
src_sys_id = 10000 |
|
|
|
else: |
|
|
|
first_idx = int(group.index[0]) |
|
|
|
raise HTTPException(status_code=400, detail=f"第 {first_idx + 2} 行导入失败:来源系统 [{parent_src_input}] 不存在") |
|
|
|
|
|
|
|
# 构造父级 model(class_id = 'code') |
|
|
|
parent_model = DataStdCodeModel( |
|
|
|
onum=(matched_std.onum if matched_std else None), |
|
|
|
cdNo=std_code_no, |
|
|
|
cdValCnMean=safe_str(first_row.get('std_code_name')), |
|
|
|
cdType=vest_mapping.get(safe_str(first_row.get('code_vest')), 'company'), |
|
|
|
cdVal_stat="1", |
|
|
|
srcSys=src_sys_id, |
|
|
|
classId="code", |
|
|
|
) |
|
|
|
|
|
|
|
# 判断父级是否存在正式表(使用 DAO.get_data_code_by_info) |
|
|
|
exist_parent = await DataStdDao.get_data_code_by_info(query_db, DataStdCodeModel(cdNo=parent_model.cd_no, classId='code')) |
|
|
|
|
|
|
|
# 父级审批准备(存在->update,不存在->add) |
|
|
|
if exist_parent: |
|
|
|
# 如果项目中存在检查“是否有待审批”函数,应该在此检查以避免重复发起审批(见下方需补充清单) |
|
|
|
waiting = False |
|
|
|
if hasattr(DataStdDao, "check_std_code_waiting"): |
|
|
|
waiting = await DataStdDao.check_std_code_waiting(exist_parent.onum, query_db) |
|
|
|
if waiting: |
|
|
|
raise ServiceException(message=f"标准代码 [{std_code_no}] 正在审批中,请等待审批完成") |
|
|
|
parent_model.onum = exist_parent.onum |
|
|
|
parent_model.create_by = current_user.user.user_name |
|
|
|
parent_model.create_time = datetime.now() |
|
|
|
parent_appr = DataStdCodeApprModel(**parent_model.model_dump(exclude_unset=True, by_alias=True)) |
|
|
|
last_appr = await DataStdDao.get_last_std_code_appr_by_id(query_db, parent_model.onum) |
|
|
|
parent_appr.changeType = "update" |
|
|
|
parent_appr.onum = str(uuid.uuid4()) |
|
|
|
parent_appr.compareId = last_appr.onum if last_appr else parent_model.onum |
|
|
|
parent_appr.oldInstId = parent_model.onum |
|
|
|
parent_appr.approStatus = "waiting" |
|
|
|
parent_appr.flowId = batch_flow_id |
|
|
|
await DataStdDao.add_std_code_appr(query_db, parent_appr) |
|
|
|
parent_appr_onum = parent_appr.onum |
|
|
|
else: |
|
|
|
# 父级新增审批 |
|
|
|
parent_model.onum = str(uuid.uuid4()) |
|
|
|
parent_model.create_by = current_user.user.user_name |
|
|
|
parent_model.create_time = datetime.now() |
|
|
|
|
|
|
|
parent_appr = DataStdCodeApprModel(**parent_model.model_dump(exclude_unset=True, by_alias=True)) |
|
|
|
parent_appr.changeType = "add" |
|
|
|
parent_appr.compareId = parent_model.onum |
|
|
|
parent_appr.oldInstId = parent_model.onum |
|
|
|
parent_appr.approStatus = "waiting" |
|
|
|
parent_appr.flowId = batch_flow_id |
|
|
|
await DataStdDao.add_std_code_appr(query_db, parent_appr) |
|
|
|
parent_appr_onum = parent_appr.onum |
|
|
|
|
|
|
|
# 处理代码项(组内每行) |
|
|
|
for row_idx, row in group.iterrows(): |
|
|
|
row_display = int(row_idx) + 2 |
|
|
|
try: |
|
|
|
code_value = safe_str(row.get('code_value')) |
|
|
|
code_meaning = safe_str(row.get('code_meaning')) |
|
|
|
|
|
|
|
if code_value == "": |
|
|
|
raise HTTPException(status_code=400, detail=f"第 {row_display} 行导入失败:代码值为空") |
|
|
|
|
|
|
|
item_model = DataStdCodeModel( |
|
|
|
cdNo=code_value, |
|
|
|
cdValCnMean=code_meaning, |
|
|
|
cdType=vest_mapping.get(safe_str(row.get('code_vest')), parent_model.cd_type), |
|
|
|
cdValStat="1", |
|
|
|
srcSys=src_sys_id, |
|
|
|
parentId=parent_model.onum, |
|
|
|
classId="codeItem", |
|
|
|
) |
|
|
|
|
|
|
|
# 查询正式表中该代码项是否存在(按 parent_id + cd_no) |
|
|
|
exist_item = await DataStdDao.get_data_code_by_info(query_db, DataStdCodeModel(parentId=item_model.parent_id, cdNo=item_model.cd_no, classId='codeItem')) |
|
|
|
|
|
|
|
if exist_item: |
|
|
|
waiting_item = False |
|
|
|
waiting_item = await DataStdDao.check_std_code_waiting(exist_item.onum, query_db) |
|
|
|
if waiting_item: |
|
|
|
raise ServiceException(message=f"第 {row_display} 行导入失败:代码项 [{code_value}] 正在审批中,请等待审批完成") |
|
|
|
|
|
|
|
item_model.onum = exist_item.onum |
|
|
|
item_model.create_by = current_user.user.user_name |
|
|
|
item_model.create_time = datetime.now() |
|
|
|
|
|
|
|
item_appr = DataStdCodeApprModel(**item_model.model_dump(exclude_unset=True, by_alias=True)) |
|
|
|
last_item_appr = await DataStdDao.get_last_std_code_appr_by_id(query_db, item_model.onum) |
|
|
|
item_appr.changeType = "update" |
|
|
|
item_appr.onum = str(uuid.uuid4()) |
|
|
|
item_appr.compareId = last_item_appr.onum if last_item_appr else item_model.onum |
|
|
|
item_appr.oldInstId = item_model.onum |
|
|
|
item_appr.parent_id = parent_appr_onum |
|
|
|
item_appr.approStatus = "waiting" |
|
|
|
item_appr.flowId = batch_flow_id |
|
|
|
await DataStdDao.add_std_code_appr(query_db, item_appr) |
|
|
|
else: |
|
|
|
# 新增子项审批 |
|
|
|
item_model.onum = str(uuid.uuid4()) |
|
|
|
item_model.create_by = current_user.user.user_name |
|
|
|
item_model.create_time = datetime.now() |
|
|
|
|
|
|
|
item_appr = DataStdCodeApprModel(**item_model.model_dump(exclude_unset=True, by_alias=True)) |
|
|
|
item_appr.changeType = "add" |
|
|
|
item_appr.compareId = item_model.onum |
|
|
|
item_appr.oldInstId = item_model.onum |
|
|
|
item_appr.parent_id = parent_appr_onum |
|
|
|
item_appr.approStatus = "waiting" |
|
|
|
item_appr.flowId = batch_flow_id |
|
|
|
await DataStdDao.add_std_code_appr(query_db, item_appr) |
|
|
|
|
|
|
|
except Exception as row_err: |
|
|
|
# 行级校验失败 -> 抛出以触发外层 rollback |
|
|
|
raise HTTPException(status_code=400, detail=f"第 {row_display} 行导入失败:{str(row_err)}") |
|
|
|
|
|
|
|
# 全部行处理完,发起审批(单一 flow) |
|
|
|
apply_model = ApplyModel() |
|
|
|
apply_model.businessType = "dataStdCode" |
|
|
|
apply_model.businessId = batch_flow_id |
|
|
|
apply_model.applicant = current_user.user.user_name |
|
|
|
await ApprovalService.apply_services(query_db, apply_model, 'dataStdCode') |
|
|
|
|
|
|
|
await query_db.commit() |
|
|
|
return CrudResponseModel(is_success=True, message="批量导入标准代码成功(已提交审批)") |
|
|
|
|
|
|
|
except Exception as e: |
|
|
|
await query_db.rollback() |
|
|
|
if isinstance(e, HTTPException): |
|
|
|
raise e |
|
|
|
if isinstance(e, ServiceException): |
|
|
|
raise e |
|
|
|
raise ServiceException(message=f"导入失败:{str(e)}") |
|
|
|
|