Browse Source

Merge remote-tracking branch 'origin/master'

master
xueyinfei 3 weeks ago
parent
commit
619ffdef1c
  1. 22
      vue-fastapi-backend/module_admin/controller/datastd_controller.py
  2. 32
      vue-fastapi-backend/module_admin/dao/datastd_dao.py
  3. 244
      vue-fastapi-backend/module_admin/service/datastd_service.py
  4. 2
      vue-fastapi-backend/module_admin/service/metasecurity_service.py
  5. 78
      vue-fastapi-frontend/src/views/datastd/stdcode/index.vue
  6. 22
      vue-fastapi-frontend/src/views/metadataConfig/bizConfig/index.vue
  7. 2
      vue-fastapi-frontend/src/views/system/flow/dataStdCodeAppr.vue

22
vue-fastapi-backend/module_admin/controller/datastd_controller.py

@ -879,3 +879,25 @@ async def save_tsmcb(request: Request,
current_user: CurrentUserModel = Depends(LoginService.get_current_user)): current_user: CurrentUserModel = Depends(LoginService.get_current_user)):
result = await DataStdService.edit_std_main_status(query_db, saveSscfModel) result = await DataStdService.edit_std_main_status(query_db, saveSscfModel)
return ResponseUtil.success(msg=result.message) return ResponseUtil.success(msg=result.message)
@datastdController.post('/stdCode/importTemplate', dependencies=[Depends(CheckUserInterfaceAuth('system:user:import'))])
async def export_std_code_template(request: Request, query_db: AsyncSession = Depends(get_db)):
code_import_template_result = await DataStdService.get_code_import_template_services()
logger.info('获取成功')
return ResponseUtil.streaming(data=bytes2file_response(code_import_template_result))
@datastdController.post('/stdCode/importData', dependencies=[Depends(CheckUserInterfaceAuth('system:user:import'))])
@Log(title='数据标准', business_type=BusinessType.IMPORT)
async def batch_import_stdCode(
request: Request,
file: UploadFile = File(...),
query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
):
batch_import_result = await DataStdService.batch_import_code_std_services(
request, query_db, file, current_user
)
logger.info(batch_import_result.message)
return ResponseUtil.success(msg=batch_import_result.message)

32
vue-fastapi-backend/module_admin/dao/datastd_dao.py

@ -1342,5 +1342,37 @@ class DataStdDao:
approval_result = await db.execute(approval_query) approval_result = await db.execute(approval_query)
approval = approval_result.scalars().first() approval = approval_result.scalars().first()
# 返回 FlowApproval 对象或布尔值
return approval # 或 return approval is not None
@classmethod
async def check_std_code_waiting(cls, oldInstId: str, db: AsyncSession):
"""
检查 std_main_appr 的第一条记录是否存在待审批状态pending waiting
"""
# 1. 查询 DataStdDictAppr 中的所有记录(按时间倒序)
query = (
select(DataStdCodeAppr)
.where(DataStdCodeAppr.oldInstId == oldInstId)
.order_by(desc(DataStdCodeAppr.create_time))
)
result = await db.execute(query)
first_record = result.scalars().first()
if not first_record:
return None # 或 raise 自定义异常,例如“未找到审批记录”
# 2. 获取 instid,去 FlowApproval 中查询是否存在 pending 或 waiting 状态
instid = first_record.flowId
approval_query = (
select(FlowApproval)
.where(
FlowApproval.businessId == instid,
FlowApproval.status.in_(["pending", "waiting"])
)
)
approval_result = await db.execute(approval_query)
approval = approval_result.scalars().first()
# 返回 FlowApproval 对象或布尔值 # 返回 FlowApproval 对象或布尔值
return approval # 或 return approval is not None return approval # 或 return approval is not None

244
vue-fastapi-backend/module_admin/service/datastd_service.py

@ -1773,7 +1773,21 @@ class DataStdService:
) )
return binary_data 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 @classmethod
async def batch_import_std_services( async def batch_import_std_services(
@ -2138,4 +2152,234 @@ class DataStdService:
await query_db.rollback() await query_db.rollback()
raise ServiceException(message=f"导入失败:{str(e)}") 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)}")

2
vue-fastapi-backend/module_admin/service/metasecurity_service.py

@ -681,7 +681,7 @@ async def replace_table_with_subquery(ctrSqlDict, oldStrSql):
"USER", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "USER", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP",
} }
# 动态获取子查询 # 动态获取子查询
if original_table in ctrSqlDict and alias_name not in sql_keywords: if original_table in ctrSqlDict and alias_name and alias_name.upper().split()[0] not in sql_keywords:
# 使用 ctrSqlDict 中的子查询替换表名 # 使用 ctrSqlDict 中的子查询替换表名
replaced = f"{keyword} ({ctrSqlDict[original_table]}) {alias_part}" replaced = f"{keyword} ({ctrSqlDict[original_table]}) {alias_part}"
else: else:

78
vue-fastapi-frontend/src/views/datastd/stdcode/index.vue

@ -67,6 +67,14 @@
v-hasPermi="['meta:metaSecurityCol:edit']" v-hasPermi="['meta:metaSecurityCol:edit']"
>修改</el-button> >修改</el-button>
</el-col> </el-col>
<el-col :span="1.5">
<el-button
type="info"
plain
icon="Upload"
@click="handleImport"
>导入</el-button>
</el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button
type="danger" type="danger"
@ -172,6 +180,38 @@
<code-map ref="showMapCodeDialog" v-if="mapVisible" :codeId="codeMapId"/> <code-map ref="showMapCodeDialog" v-if="mapVisible" :codeId="codeMapId"/>
</el-dialog> </el-dialog>
<el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
<el-upload
ref="uploadRef"
:limit="1"
accept=".xlsx, .xls"
:headers="upload.headers"
:action="upload.url + '?updateSupport=' + upload.updateSupport"
:disabled="upload.isUploading"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess"
:auto-upload="false"
drag
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip text-center">
<!-- <div class="el-upload__tip">
<el-checkbox v-model="upload.updateSupport" />是否更新已经存在的用户数据
</div> -->
<span>仅允许导入xlsxlsx格式文件</span>
<el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link>
</div>
</template>
</el-upload>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitFileForm"> </el-button>
<el-button @click="upload.open = false"> </el-button>
</div>
</template>
</el-dialog>
<el-dialog v-model="mapStdVisible" width="1200px"> <el-dialog v-model="mapStdVisible" width="1200px">
<!-- 引入第三个页面组件 --> <!-- 引入第三个页面组件 -->
<code-std-map ref="showMapCodeStdDialog" :rowData="selectedRow" v-if="mapStdVisible" /> <code-std-map ref="showMapCodeStdDialog" :rowData="selectedRow" v-if="mapStdVisible" />
@ -189,6 +229,7 @@ import CodeItem from './codeItem.vue'; // 引入第二个页面组件
import codeItemCommon from './codeItemCommon.vue'; // import codeItemCommon from './codeItemCommon.vue'; //
import codeMap from './codeMap'; // import codeMap from './codeMap'; //
import codeStdMap from './codeStdMap.vue'; import codeStdMap from './codeStdMap.vue';
import { getToken } from "@/utils/auth";
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();
const { std_code_status,std_code_appr } = proxy.useDict("std_code_status","std_code_appr"); const { std_code_status,std_code_appr } = proxy.useDict("std_code_status","std_code_appr");
@ -214,12 +255,41 @@ const defaultProps = {
const dialogVisible2 = ref(false); const dialogVisible2 = ref(false);
const dialogTitle2 = ref('标准代码'); const dialogTitle2 = ref('标准代码');
const selectedRow = ref(null); // codeItem const selectedRow = ref(null); // codeItem
function handleImport() {
upload.title = "代码导入";
upload.open = true;
};
const getSrcSysName = (id) => { const getSrcSysName = (id) => {
const match = dbResourceOldList.value.find(item => item.id === id); const match = dbResourceOldList.value.find(item => item.id === id);
return match ? match.name : id; return match ? match.name : id;
}; };
function submitFileForm() {
proxy.$refs["uploadRef"].submit();
};
const handleFileUploadProgress = (event, file, fileList) => {
upload.isUploading = true;
};
const handleFileSuccess = (response, file, fileList) => {
upload.open = false;
upload.isUploading = false;
proxy.$refs["uploadRef"].handleRemove(file);
proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true });
getList();
};
const upload = reactive({
//
open: false,
//
title: "",
//
isUploading: false,
//
updateSupport: 0,
//
headers: { Authorization: "Bearer " + getToken() },
//
url: import.meta.env.VITE_APP_BASE_API + "/datastd/stdCode/importData"
});
const filterNode = (value, data) => { const filterNode = (value, data) => {
if (!value) return true; if (!value) return true;
return data.name.indexOf(value) !== -1; return data.name.indexOf(value) !== -1;
@ -235,6 +305,10 @@ const handleNodeClick = (data) => {
handleQuery(); handleQuery();
}; };
function importTemplate() {
proxy.download("datastd/stdCode/importTemplate", {
}, `标准代码_template_${new Date().getTime()}.xlsx`);
};
watch(dbResoursName, (val) => { watch(dbResoursName, (val) => {
proxy.$refs["tree"].filter(val); proxy.$refs["tree"].filter(val);

22
vue-fastapi-frontend/src/views/metadataConfig/bizConfig/index.vue

@ -168,7 +168,15 @@
@selection-change="handleLeftSelect" @selection-change="handleLeftSelect"
> >
<el-table-column type="selection" width="50" /> <el-table-column type="selection" width="50" />
<el-table-column prop="ssysCd" label="系统名" /> <el-table-column prop="ssysId" label="系统名" >
<template #default="scope">
<span >
{{
getNameById(scope.row.ssysId)
}}
</span>
</template>
</el-table-column>
<el-table-column prop="mdlName" label="模式名" /> <el-table-column prop="mdlName" label="模式名" />
<el-table-column prop="tabEngName" label="表名" /> <el-table-column prop="tabEngName" label="表名" />
</el-table> </el-table>
@ -198,8 +206,15 @@
@selection-change="handleRightSelect" @selection-change="handleRightSelect"
> >
<el-table-column type="selection" width="50" /> <el-table-column type="selection" width="50" />
<el-table-column prop="ssysCd" label="系统名" /> <el-table-column prop="ssysId" label="系统名" >
<el-table-column prop="mdlName" label="模式名" /> <template #default="scope">
<span >
{{
getNameById(scope.row.ssysId)
}}
</span>
</template>
</el-table-column> <el-table-column prop="mdlName" label="模式名" />
<el-table-column prop="tabEngName" label="表名" /> <el-table-column prop="tabEngName" label="表名" />
</el-table> </el-table>
</el-col> </el-col>
@ -217,6 +232,7 @@ import { ref, reactive, onMounted } from 'vue'
import { ElMessageBox, ElMessage } from 'element-plus' import { ElMessageBox, ElMessage } from 'element-plus'
import useUserStore from '@/store/modules/user'; // import useUserStore from '@/store/modules/user'; //
import {getMetaDataList} from "@/api/meta/metaInfo" import {getMetaDataList} from "@/api/meta/metaInfo"
import { getNameById, getIdByName } from '@/utils/dsSysUtils';
const userStore = useUserStore(); // const userStore = useUserStore(); //
const dsSysList = userStore.dsSysList; // 访 const dsSysList = userStore.dsSysList; // 访

2
vue-fastapi-frontend/src/views/system/flow/dataStdCodeAppr.vue

@ -12,7 +12,7 @@
<el-table-column label="归属" align="center" width="200"> <el-table-column label="归属" align="center" width="200">
<template #default="{ row }"> <template #default="{ row }">
{{ {{
row.dataDictVest === 'company' row.cdType === 'company'
? '公司级' ? '公司级'
: '系统级(' + getSrcSysName(row.srcSys) + ')' : '系统级(' + getSrcSysName(row.srcSys) + ')'
}} }}

Loading…
Cancel
Save