From b3c07b8bb5381b04ae075772f29a169ec69afd8c Mon Sep 17 00:00:00 2001 From: zhangrui Date: Thu, 15 Oct 2026 09:24:52 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BE=85=E7=A1=AE=E8=AE=A4=E4=BA=BA=E5=91=98?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=EF=BC=9A=E5=AF=BC=E5=85=A5=E5=8F=98=E6=9B=B4?= =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PendingConfirmUserController.java | 25 +++ .../PendingConfirmUserImportExcelMode.java | 46 ++++++ .../service/PendingConfirmUserService.java | 11 ++ .../impl/PendingConfirmUserServiceImpl.java | 152 ++++++++++++++++++ .../sourcechange/pending/index.html | 43 +++++ 5 files changed, 277 insertions(+) create mode 100644 src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/mode/PendingConfirmUserImportExcelMode.java diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/controller/PendingConfirmUserController.java b/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/controller/PendingConfirmUserController.java index 9cd8aa03..0d4a0238 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/controller/PendingConfirmUserController.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/controller/PendingConfirmUserController.java @@ -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 + "人"); + } + /** * 直接提交待确认人员变更,不创建会员变更申请及审批流程。 * diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/mode/PendingConfirmUserImportExcelMode.java b/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/mode/PendingConfirmUserImportExcelMode.java new file mode 100644 index 00000000..ce0719dc --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/mode/PendingConfirmUserImportExcelMode.java @@ -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; +} diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/service/PendingConfirmUserService.java b/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/service/PendingConfirmUserService.java index 7874217d..12060e27 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/service/PendingConfirmUserService.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/service/PendingConfirmUserService.java @@ -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 { */ void exportExcel(PendingConfirmUserPageForm pageForm, HttpServletResponse response); + /** + * 导入待确认人员变更。 + * Excel 必须包含“工号”和“编码”:工号定位系统人员,编码匹配人员属性字典的业务编码; + * 返回值为成功更新人员类型和人员属性的人员数量。 + * + * @param importFile 用户上传的 xls 或 xlsx 文件 + * @return 成功更新的人员数量 + */ + int importChange(File importFile); + /** * 直接提交待确认人员变更,不生成会员变更申请及流程数据。 * diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/service/impl/PendingConfirmUserServiceImpl.java b/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/service/impl/PendingConfirmUserServiceImpl.java index fa7e0d6f..49c786e3 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/service/impl/PendingConfirmUserServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/service/impl/PendingConfirmUserServiceImpl.java @@ -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 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 ALLOWED_PERSON_TYPE_NAMES = Set.of("离退休人员", "其他"); @@ -103,6 +112,8 @@ public class PendingConfirmUserServiceImpl extends BaseServiceImpl 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 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 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 validImportRows = new ArrayList<>(); + Map 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 attributeDictByBusinessCode = buildAttributeDictByBusinessCode(); + Map userByLoginname = buildImportUserMap(validImportRows); + Set 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 buildAttributeDictByBusinessCode() { + List attributeDicts = sysDictService.getSubListByCode(USER_ATTRIBUTE_DICT_CODE); + Map 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 buildImportUserMap(List importRows) { + LinkedHashSet 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 users = dao().query(Sys_user.class, + Cnd.where("loginname", "in", new ArrayList<>(loginnames)).and(activeUserCondition)); + Map result = new HashMap<>(); + for (Sys_user user : users) { + result.put(user.getLoginname(), user); + } + return result; + } + /** * 构建分页查询和导出共用的人员 SQL,确保筛选条件、确认状态及排序口径一致。 * diff --git a/src/main/resources/views/platform/zhgh/staffmanage/sourcechange/pending/index.html b/src/main/resources/views/platform/zhgh/staffmanage/sourcechange/pending/index.html index cbcdc39f..8049f70b 100644 --- a/src/main/resources/views/platform/zhgh/staffmanage/sourcechange/pending/index.html +++ b/src/main/resources/views/platform/zhgh/staffmanage/sourcechange/pending/index.html @@ -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"){ 导出 + + 导入变更 + { + 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)