待确认人员记录:导入变更调整
This commit is contained in:
+25
@@ -2,6 +2,7 @@ package com.budwk.app.zhgh.staffmanage.sourcechange.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.annotation.RepeatSubmit;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
import com.budwk.app.zhgh.staffmanage.member.models.MemberChangeRecord;
|
||||
@@ -13,7 +14,10 @@ import io.swagger.annotations.ApiOperation;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.AdaptBy;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.upload.TempFile;
|
||||
import org.nutz.mvc.upload.UploadAdaptor;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@@ -66,6 +70,27 @@ public class PendingConfirmUserController {
|
||||
pendingConfirmUserService.exportExcel(pageForm, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入人员类型和人员属性变更。
|
||||
*
|
||||
* @param file xls 或 xlsx 文件;表格中的“工号”定位人员,“编码”匹配人员属性字典配置
|
||||
* @return JSON 结果;成功时 msg 包含实际更新人数
|
||||
*/
|
||||
@At
|
||||
@Ok("json")
|
||||
@RepeatSubmit
|
||||
@SLog(tag = "待确认人员记录", msg = "导入人员类型和人员属性变更")
|
||||
@ApiOperation("导入待确认人员变更")
|
||||
@SaCheckPermission("staff.sourcechange.pending")
|
||||
@AdaptBy(type = UploadAdaptor.class, args = {"ioc:fileUpload"})
|
||||
public Result importChange(TempFile file) {
|
||||
if (file == null || file.getFile() == null || !file.getFile().isFile()) {
|
||||
throw new BaseException("请选择需要导入的Excel文件");
|
||||
}
|
||||
int changedCount = pendingConfirmUserService.importChange(file.getFile());
|
||||
return Result.success("已成功导入变更" + changedCount + "人");
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接提交待确认人员变更,不创建会员变更申请及审批流程。
|
||||
*
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.budwk.app.zhgh.staffmanage.sourcechange.mode;
|
||||
|
||||
import cn.afterturn.easypoi.excel.annotation.Excel;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 待确认人员导入变更 Excel 行数据。
|
||||
*
|
||||
* 模板沿用列表导出字段,导入业务只采纳工号和编码,其他字段仅用于人工核对。
|
||||
*/
|
||||
@Data
|
||||
public class PendingConfirmUserImportExcelMode {
|
||||
|
||||
@Excel(name = "序号")
|
||||
private String index;
|
||||
|
||||
@Excel(name = "工号")
|
||||
private String loginname;
|
||||
|
||||
@Excel(name = "姓名")
|
||||
private String username;
|
||||
|
||||
@Excel(name = "性别")
|
||||
private String sex;
|
||||
|
||||
@Excel(name = "所属工会")
|
||||
private String unionName;
|
||||
|
||||
@Excel(name = "所属单位")
|
||||
private String unitName;
|
||||
|
||||
@Excel(name = "人员类型")
|
||||
private String personType;
|
||||
|
||||
@Excel(name = "人员属性")
|
||||
private String userAttribute;
|
||||
|
||||
@Excel(name = "在职状态")
|
||||
private String userState;
|
||||
|
||||
@Excel(name = "确认状态")
|
||||
private String pendingConfirmStatusName;
|
||||
|
||||
@Excel(name = "编码")
|
||||
private String code;
|
||||
}
|
||||
+11
@@ -8,6 +8,7 @@ import com.budwk.app.zhgh.staffmanage.sourcechange.param.PendingConfirmUserBatch
|
||||
import com.budwk.app.zhgh.staffmanage.sourcechange.param.PendingConfirmUserPageForm;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 待确认人员记录业务服务。
|
||||
@@ -30,6 +31,16 @@ public interface PendingConfirmUserService extends BaseService<Sys_user> {
|
||||
*/
|
||||
void exportExcel(PendingConfirmUserPageForm pageForm, HttpServletResponse response);
|
||||
|
||||
/**
|
||||
* 导入待确认人员变更。
|
||||
* Excel 必须包含“工号”和“编码”:工号定位系统人员,编码匹配人员属性字典的业务编码;
|
||||
* 返回值为成功更新人员类型和人员属性的人员数量。
|
||||
*
|
||||
* @param importFile 用户上传的 xls 或 xlsx 文件
|
||||
* @return 成功更新的人员数量
|
||||
*/
|
||||
int importChange(File importFile);
|
||||
|
||||
/**
|
||||
* 直接提交待确认人员变更,不生成会员变更申请及流程数据。
|
||||
*
|
||||
|
||||
+152
@@ -1,7 +1,9 @@
|
||||
package com.budwk.app.zhgh.staffmanage.sourcechange.service.impl;
|
||||
|
||||
import cn.afterturn.easypoi.excel.ExcelExportUtil;
|
||||
import cn.afterturn.easypoi.excel.ExcelImportUtil;
|
||||
import cn.afterturn.easypoi.excel.entity.ExportParams;
|
||||
import cn.afterturn.easypoi.excel.entity.ImportParams;
|
||||
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
|
||||
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
@@ -23,8 +25,10 @@ import com.budwk.app.zhgh.staffmanage.member.service.MemberCommonService;
|
||||
import com.budwk.app.zhgh.staffmanage.sourcechange.param.PendingConfirmUserBatchChangeParam;
|
||||
import com.budwk.app.zhgh.staffmanage.sourcechange.param.PendingConfirmUserPageForm;
|
||||
import com.budwk.app.zhgh.staffmanage.sourcechange.service.PendingConfirmUserService;
|
||||
import com.budwk.app.zhgh.staffmanage.sourcechange.mode.PendingConfirmUserImportExcelMode;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Chain;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
@@ -38,6 +42,10 @@ import org.nutz.lang.util.NutMap;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -51,6 +59,7 @@ public class PendingConfirmUserServiceImpl extends BaseServiceImpl<Sys_user> imp
|
||||
|
||||
private static final int STATUS_PENDING = 1;
|
||||
private static final int STATUS_CONFIRMED = 2;
|
||||
private static final String USER_ATTRIBUTE_DICT_CODE = "USER_ATTRIBUTE";
|
||||
/** 待确认人员只允许转为以下两个字典名称对应的编码。 */
|
||||
private static final Set<String> ALLOWED_PERSON_TYPE_NAMES = Set.of("离退休人员", "其他");
|
||||
|
||||
@@ -103,6 +112,8 @@ public class PendingConfirmUserServiceImpl extends BaseServiceImpl<Sys_user> imp
|
||||
exportEntities.add(new ExcelExportEntity("人员属性", "userAttribute", 20));
|
||||
exportEntities.add(new ExcelExportEntity("在职状态", "userState", 15));
|
||||
exportEntities.add(new ExcelExportEntity("确认状态", "pendingConfirmStatusName", 15));
|
||||
// 编码仅作为导入填写列,导出时不设置行数据。
|
||||
exportEntities.add(new ExcelExportEntity("编码", "changeCode", 20));
|
||||
|
||||
ExportParams exportParams = new ExportParams();
|
||||
exportParams.setType(ExcelType.XSSF);
|
||||
@@ -110,6 +121,147 @@ public class PendingConfirmUserServiceImpl extends BaseServiceImpl<Sys_user> imp
|
||||
CommonDownloadUtil.download("待确认人员记录.xlsx", workbook, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 Excel 工号和人员属性业务编码更新人员类型、人员属性。
|
||||
* 所有行先统一校验,任一行异常时不执行更新;Excel 中的其他展示字段不参与变更。
|
||||
*
|
||||
* @param importFile 包含“工号”和“编码”列的 xls 或 xlsx 文件
|
||||
* @return 实际更新的人员数量
|
||||
*/
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public int importChange(File importFile) {
|
||||
if (importFile == null || !importFile.isFile()) {
|
||||
throw new BaseException("请选择需要导入的Excel文件");
|
||||
}
|
||||
|
||||
List<PendingConfirmUserImportExcelMode> importRows;
|
||||
try {
|
||||
ImportParams importParams = new ImportParams();
|
||||
importRows = ExcelImportUtil.importExcel(importFile, PendingConfirmUserImportExcelMode.class, importParams);
|
||||
} catch (Exception e) {
|
||||
throw new BaseException("Excel文件解析失败,请使用待确认人员列表导出的模板");
|
||||
}
|
||||
if (importRows == null || importRows.isEmpty()) {
|
||||
throw new BaseException("导入文件中没有人员数据");
|
||||
}
|
||||
|
||||
// 导出的列表可能包含人工保留的空行;空工号行不参与人员查询、字典校验和更新。
|
||||
List<PendingConfirmUserImportExcelMode> validImportRows = new ArrayList<>();
|
||||
Map<PendingConfirmUserImportExcelMode, Integer> excelRowNumberByRow = new IdentityHashMap<>();
|
||||
for (int index = 0; index < importRows.size(); index++) {
|
||||
PendingConfirmUserImportExcelMode row = importRows.get(index);
|
||||
if (row == null || StrUtil.isBlank(row.getLoginname())) {
|
||||
continue;
|
||||
}
|
||||
validImportRows.add(row);
|
||||
excelRowNumberByRow.put(row, index + 2);
|
||||
}
|
||||
if (validImportRows.isEmpty()) {
|
||||
throw new BaseException("导入文件中没有有效工号数据");
|
||||
}
|
||||
|
||||
Map<String, Sys_dict> attributeDictByBusinessCode = buildAttributeDictByBusinessCode();
|
||||
Map<String, Sys_user> userByLoginname = buildImportUserMap(validImportRows);
|
||||
Set<String> importedLoginnames = new HashSet<>();
|
||||
|
||||
// 全量校验必须在更新前完成,防止错误文件只修改其中一部分人员。
|
||||
for (PendingConfirmUserImportExcelMode row : validImportRows) {
|
||||
int excelRowNumber = excelRowNumberByRow.get(row);
|
||||
String loginname = StrUtil.trim(row.getLoginname());
|
||||
String changeCode = StrUtil.trim(row.getCode());
|
||||
if (StrUtil.isBlank(changeCode)) {
|
||||
throw new BaseException("第" + excelRowNumber + "行编码不能为空");
|
||||
}
|
||||
if (!importedLoginnames.add(loginname)) {
|
||||
throw new BaseException("第" + excelRowNumber + "行工号重复:" + loginname);
|
||||
}
|
||||
if (!userByLoginname.containsKey(loginname)) {
|
||||
throw new BaseException("第" + excelRowNumber + "行未查询到工号:" + loginname);
|
||||
}
|
||||
Sys_user importUser = userByLoginname.get(loginname);
|
||||
if (!Integer.valueOf(STATUS_PENDING).equals(importUser.getPendingConfirmStatus())) {
|
||||
throw new BaseException("第" + excelRowNumber + "行人员已不属于待确认状态:" + loginname);
|
||||
}
|
||||
Sys_dict attributeDict = attributeDictByBusinessCode.get(changeCode);
|
||||
if (attributeDict == null) {
|
||||
throw new BaseException("第" + excelRowNumber + "行编码未匹配到启用的人员属性:" + changeCode);
|
||||
}
|
||||
if (StrUtil.isBlank(attributeDict.getPersonType())) {
|
||||
throw new BaseException("第" + excelRowNumber + "行编码未配置人员类型:" + changeCode);
|
||||
}
|
||||
if (StrUtil.isBlank(attributeDict.getCode())) {
|
||||
throw new BaseException("第" + excelRowNumber + "行编码对应的人员属性字典值为空:" + changeCode);
|
||||
}
|
||||
}
|
||||
|
||||
int changedCount = 0;
|
||||
for (PendingConfirmUserImportExcelMode row : validImportRows) {
|
||||
String loginname = StrUtil.trim(row.getLoginname());
|
||||
Sys_dict attributeDict = attributeDictByBusinessCode.get(StrUtil.trim(row.getCode()));
|
||||
Sys_user user = userByLoginname.get(loginname);
|
||||
Chain updateFields = Chain.make("personType", attributeDict.getPersonType())
|
||||
.add("userAttribute", attributeDict.getCode())
|
||||
.add("pendingConfirmStatus", STATUS_CONFIRMED);
|
||||
int updated = dao().update(Sys_user.class, updateFields,
|
||||
Cnd.where("id", "=", user.getId())
|
||||
.and("pendingConfirmStatus", "=", STATUS_PENDING));
|
||||
if (updated != 1) {
|
||||
throw new BaseException("工号" + loginname + "的待确认状态已变化,请刷新列表后重新导出");
|
||||
}
|
||||
changedCount += updated;
|
||||
}
|
||||
return changedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建人员属性业务编码映射,只接纳 USER_ATTRIBUTE 下启用且业务编码唯一的直属字典项。
|
||||
*
|
||||
* @return key 为 userAttributeCode,value 为人员属性字典项
|
||||
*/
|
||||
private Map<String, Sys_dict> buildAttributeDictByBusinessCode() {
|
||||
List<Sys_dict> attributeDicts = sysDictService.getSubListByCode(USER_ATTRIBUTE_DICT_CODE);
|
||||
Map<String, Sys_dict> result = new HashMap<>();
|
||||
for (Sys_dict attributeDict : attributeDicts) {
|
||||
String businessCode = StrUtil.trim(attributeDict.getUserAttributeCode());
|
||||
if (StrUtil.isBlank(businessCode)) {
|
||||
continue;
|
||||
}
|
||||
if (result.putIfAbsent(businessCode, attributeDict) != null) {
|
||||
throw new BaseException("人员属性字典存在重复编码:" + businessCode);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量查询导入文件涉及的人员,避免逐行访问数据库。
|
||||
*
|
||||
* @param importRows Excel 解析后的数据行,仅使用工号字段
|
||||
* @return key 为工号,value 为未删除的系统人员
|
||||
*/
|
||||
private Map<String, Sys_user> buildImportUserMap(List<PendingConfirmUserImportExcelMode> importRows) {
|
||||
LinkedHashSet<String> loginnames = new LinkedHashSet<>();
|
||||
for (PendingConfirmUserImportExcelMode row : importRows) {
|
||||
if (StrUtil.isNotBlank(row.getLoginname())) {
|
||||
loginnames.add(StrUtil.trim(row.getLoginname()));
|
||||
}
|
||||
}
|
||||
if (loginnames.isEmpty()) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
// 历史人员的 delFlag 可能为空;与列表查询口径一致,将 0 和 NULL 都视为未删除。
|
||||
SqlExpressionGroup activeUserCondition = Cnd.exps("delFlag", "=", false)
|
||||
.or("delFlag", "is", null);
|
||||
List<Sys_user> users = dao().query(Sys_user.class,
|
||||
Cnd.where("loginname", "in", new ArrayList<>(loginnames)).and(activeUserCondition));
|
||||
Map<String, Sys_user> result = new HashMap<>();
|
||||
for (Sys_user user : users) {
|
||||
result.put(user.getLoginname(), user);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建分页查询和导出共用的人员 SQL,确保筛选条件、确认状态及排序口径一致。
|
||||
*
|
||||
|
||||
@@ -29,6 +29,11 @@ layout("/layouts/platform.html"){
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.pending-list-toolbar-actions .pending-import-upload {
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/* 在列表标题旁说明待确认人员的来源及可能情况。 */
|
||||
.pending-confirm-tip {
|
||||
color: #f56c6c;
|
||||
@@ -128,6 +133,16 @@ layout("/layouts/platform.html"){
|
||||
<el-button class="pending-batch-change-button" type="primary"
|
||||
icon="el-icon-download" size="small"
|
||||
@click="exportExcel">导出</el-button>
|
||||
<el-upload class="pending-import-upload"
|
||||
v-if="pageForm.pendingConfirmStatus === 1"
|
||||
action="#"
|
||||
accept=".xls,.xlsx"
|
||||
:show-file-list="false"
|
||||
:before-upload="validateImportFile"
|
||||
:http-request="importChange">
|
||||
<el-button type="primary" icon="el-icon-upload2" size="small"
|
||||
:loading="importLoading" :disabled="importLoading">导入变更</el-button>
|
||||
</el-upload>
|
||||
<el-button class="pending-batch-change-button" type="primary"
|
||||
icon="el-icon-edit" size="small"
|
||||
v-if="pageForm.pendingConfirmStatus === 1"
|
||||
@@ -275,6 +290,7 @@ layout("/layouts/platform.html"){
|
||||
selectedPendingUsers: [],
|
||||
batchDialogVisible: false,
|
||||
batchSubmitting: false,
|
||||
importLoading: false,
|
||||
batchChangeForm: {
|
||||
personType: "",
|
||||
userState: "",
|
||||
@@ -378,6 +394,33 @@ layout("/layouts/platform.html"){
|
||||
exportExcel() {
|
||||
this.$downLoad("/platform/sourcechange/pending/exportExcel", this.buildQueryPageForm())
|
||||
},
|
||||
/** 只接收 Excel 文件,避免无效文件进入后端解析。 */
|
||||
validateImportFile(file) {
|
||||
const fileName = file && file.name ? file.name.toLowerCase() : ""
|
||||
const valid = fileName.endsWith(".xls") || fileName.endsWith(".xlsx")
|
||||
if (!valid) {
|
||||
this.$message.warning("只能上传xls或xlsx文件")
|
||||
}
|
||||
return valid
|
||||
},
|
||||
/** 上传填写了编码的列表文件,后端仅按工号和编码执行字典映射变更。 */
|
||||
importChange(uploadRequest) {
|
||||
const formData = new FormData()
|
||||
formData.append("file", uploadRequest.file, uploadRequest.file.name)
|
||||
this.importLoading = true
|
||||
this.$axios.post("/platform/sourcechange/pending/importChange", formData).then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
this.doSearch()
|
||||
} else {
|
||||
this.$message.error(resp.msg)
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$message.error("导入变更失败,请稍后重试")
|
||||
}).finally(() => {
|
||||
this.importLoading = false
|
||||
})
|
||||
},
|
||||
/** 保存当前页勾选的待确认人员,供批量变更使用。 */
|
||||
handleSelectionChange(selection) {
|
||||
this.selectedPendingUsers = (selection || []).filter((item) => item.pendingConfirmStatus === 1)
|
||||
|
||||
Reference in New Issue
Block a user