diff --git a/src/main/java/com/budwk/app/base/sms/impl/ncu/SmsNcuServiceImpl.java b/src/main/java/com/budwk/app/base/sms/impl/ncu/SmsNcuServiceImpl.java index 5ccaebf4..d50b92ec 100644 --- a/src/main/java/com/budwk/app/base/sms/impl/ncu/SmsNcuServiceImpl.java +++ b/src/main/java/com/budwk/app/base/sms/impl/ncu/SmsNcuServiceImpl.java @@ -40,9 +40,9 @@ public class SmsNcuServiceImpl implements SmsService { @Inject private Dao dao; - private static final String SEND_MSG_URL = "https://poa.ncu.edu.cn/apis/messagecenter/v1/poaMessage/messageSend"; - private static final String APP_ID = "72e34a101d3011f1a74cbae5193504f4"; - private static final String TEMPLATE_ID = "MT1773223005000"; + private static final String SEND_MSG_URL = ""; + private static final String APP_ID = ""; + private static final String TEMPLATE_ID = ""; @Override public void sendSingleMessage(String loginName, String content) { diff --git a/src/main/java/com/budwk/app/sys/models/Sys_user.java b/src/main/java/com/budwk/app/sys/models/Sys_user.java index 095cb154..f9efcb30 100644 --- a/src/main/java/com/budwk/app/sys/models/Sys_user.java +++ b/src/main/java/com/budwk/app/sys/models/Sys_user.java @@ -225,6 +225,12 @@ public class Sys_user extends BaseModel implements Serializable { @ColDefine(type = ColType.DATE) private Date retireDate; + @Column + @Comment("即将退休人员(状态)") + @ColDefine(type = ColType.INT) + @Default("0") + private Integer retiringSoon; + @Column @Comment("博士后进站时间") @ColDefine(type = ColType.DATE) diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/thirtyTeachTour/controller/ThirtyTeachTourBranchUserAssignmentController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/thirtyTeachTour/controller/ThirtyTeachTourBranchUserAssignmentController.java index cd2fd766..c480ff0a 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/thirtyTeachTour/controller/ThirtyTeachTourBranchUserAssignmentController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/thirtyTeachTour/controller/ThirtyTeachTourBranchUserAssignmentController.java @@ -78,8 +78,10 @@ public class ThirtyTeachTourBranchUserAssignmentController { */ @At @SaCheckPermission("thirtyTeachTour.branchUserAssignment.assign") - public Result candidatePageData(PageForm pageForm, String settingId, String keyword, @Param("userIds") String userIds) { - return Result.success(tourUserAssignmentService.branchCandidatePage(pageForm, settingId, keyword, parseUserIds(userIds))); + public Result candidatePageData(PageForm pageForm, String settingId, String keyword, @Param("userIds") String userIds, + Boolean retiringSoon) { + return Result.success(tourUserAssignmentService.branchCandidatePage(pageForm, settingId, keyword, + parseUserIds(userIds), retiringSoon)); } /** diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/thirtyTeachTour/controller/ThirtyTeachTourLedgerController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/thirtyTeachTour/controller/ThirtyTeachTourLedgerController.java index 37f508e2..372d229d 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/thirtyTeachTour/controller/ThirtyTeachTourLedgerController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/thirtyTeachTour/controller/ThirtyTeachTourLedgerController.java @@ -16,8 +16,13 @@ import com.budwk.app.base.param.PageForm; import com.budwk.app.base.result.Result; import com.budwk.app.base.utils.CommonDownloadUtil; import com.budwk.app.flow.enums.ProcessInstanceStateEnum; +import com.budwk.app.sys.enums.SysFileEngineTypeEnum; +import com.budwk.app.sys.models.Sys_file; import com.budwk.app.sys.models.Sys_union; +import com.budwk.app.sys.services.SysFileService; +import com.budwk.app.sys.utils.SysFileMinIoUtil; import com.budwk.app.sys.views.View_user; +import com.budwk.app.web.commons.base.Globals; import com.budwk.app.web.commons.auth.utils.AuthUtil; import com.budwk.app.web.commons.auth.utils.SecurityUtil; import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.mode.ThirtyTeachTourLedgerImportExcelMode; @@ -30,11 +35,15 @@ import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourL import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourLedgerFamilyService; import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourLedgerService; import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourUserAssignmentService; +import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.BorderStyle; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.ClientAnchor; +import org.apache.poi.ss.usermodel.CreationHelper; +import org.apache.poi.ss.usermodel.Drawing; import org.apache.poi.ss.usermodel.FillPatternType; import org.apache.poi.ss.usermodel.Font; import org.apache.poi.ss.usermodel.HorizontalAlignment; @@ -62,8 +71,16 @@ import org.nutz.mvc.upload.TempFile; import org.nutz.mvc.upload.UploadAdaptor; import javax.servlet.http.HttpServletResponse; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; import java.net.URLEncoder; +import java.net.URLDecoder; +import java.nio.file.Files; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.time.LocalDate; @@ -83,6 +100,7 @@ import java.util.zip.ZipOutputStream; @IocBean @Ok("json:full") @At("/platform/thirtyTeachTour/ledger") +@Slf4j public class ThirtyTeachTourLedgerController { @Inject @@ -97,6 +115,9 @@ public class ThirtyTeachTourLedgerController { @Inject private ThirtyTeachTourUserAssignmentService tourUserAssignmentService; + @Inject + private SysFileService sysFileService; + @At("") @Ok("beetl:/platform/zhgh/dayofficework/ThirtyTeachTour/ledger/index.html") @SaCheckPermission("thirtyTeachTour.ledger") @@ -367,24 +388,334 @@ public class ThirtyTeachTourLedgerController { entities.add(new ExcelExportEntity("携带家属", "familyCountText", 14)); entities.add(new ExcelExportEntity("是否参加", "joinedText", 14)); entities.add(new ExcelExportEntity("报销超出费用", "overCostReimbursedText", 18)); - entities.add(new ExcelExportEntity("图片材料", "photoFiles", 36)); List rows = queryLedgerExportRows(startYear, endYear, keyword, unionId, lineId, travelPeriod, lineType, directFamilyOnly, overCostOnly); + int imageColumnStartIndex = entities.size(); + int maxPhotoCount = maxPhotoCount(rows); + log.info("三十年教龄疗休养台账导出图片列准备:rows={}, maxPhotoCount={}, appDomain={}", + rows.size(), maxPhotoCount, Globals.AppDomain); + for (int i = 1; i <= maxPhotoCount; i++) { + ExcelExportEntity imageEntity = new ExcelExportEntity("图片" + i, "photoCell" + i, 18); + entities.add(imageEntity); + } rows.forEach(row -> { Integer familyCount = row.getInt("familyCount"); row.put("familyCountText", (familyCount == null ? 0 : familyCount) + "人"); row.put("joinedText", Boolean.TRUE.equals(row.getBoolean("joined")) ? "是" : "否"); row.put("overCostReimbursedText", Boolean.TRUE.equals(row.getBoolean("overCostReimbursed")) ? "是" : "否"); + fillLedgerPhotoRowImages(row, maxPhotoCount); }); + List> rowImages = snapshotLedgerRowImages(rows); ExportParams exportParams = new ExportParams(); exportParams.setSheetName("疗休养台账"); exportParams.setType(ExcelType.XSSF); Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, rows); + resizeLedgerPhotoRows(workbook, rowImages.size(), imageColumnStartIndex, maxPhotoCount); + int insertedPictureCount = insertLedgerPhotoImages(workbook, rowImages, imageColumnStartIndex); + log.info("三十年教龄疗休养台账导出Excel图片写入完成:insertedPictureCount={}, workbookPictureCount={}", + insertedPictureCount, workbook.getAllPictures().size()); CommonDownloadUtil.download("疗休养台账数据.xlsx", workbook, response); } + /** + * 统计本次导出台账中最大的图片数量,用于动态生成图片1、图片2等横向列。 + */ + private int maxPhotoCount(List rows) { + int maxPhotoCount = 0; + for (NutMap row : rows) { + maxPhotoCount = Math.max(maxPhotoCount, splitPhotoFiles(row.getString("photoFiles", "")).size()); + } + return maxPhotoCount; + } + + /** + * 将每条台账的图片材料解析为可写入 Excel 的图片对象,图片列文本保持为空,后续由 POI 手动插入图片。 + */ + private void fillLedgerPhotoRowImages(NutMap row, int maxPhotoCount) { + List photoFiles = splitPhotoFiles(row.getString("photoFiles", "")); + if (!photoFiles.isEmpty()) { + log.info("三十年教龄疗休养台账图片字段读取:jobNo={}, userName={}, photoCount={}, photoFiles={}", + row.getString("jobNo", ""), row.getString("userName", ""), photoFiles.size(), row.getString("photoFiles", "")); + } + row.put("photoImages", resolveLedgerImages(row.getString("photoFiles", ""))); + for (int i = 1; i <= maxPhotoCount; i++) { + row.put("photoCell" + i, ""); + } + } + + /** + * 图片列由 EasyPOI 写入后,统一调整行高和图片列宽,避免图片被压缩到不可见。 + */ + private void resizeLedgerPhotoRows(Workbook workbook, int dataRowCount, int imageColumnStartIndex, int maxPhotoCount) { + if (workbook == null || dataRowCount <= 0 || maxPhotoCount <= 0) { + return; + } + Sheet sheet = workbook.getSheetAt(0); + for (int photoIndex = 0; photoIndex < maxPhotoCount; photoIndex++) { + sheet.setColumnWidth(imageColumnStartIndex + photoIndex, 18 * 256); + } + for (int rowIndex = 0; rowIndex < dataRowCount; rowIndex++) { + int excelRowIndex = rowIndex + 1; + Row row = sheet.getRow(excelRowIndex); + if (row != null) { + row.setHeightInPoints(90); + } + } + } + + /** + * 在 EasyPOI 导出前复制每行图片对象,避免 exportExcel 内部消费 rows 后导致图片数据丢失。 + */ + private List> snapshotLedgerRowImages(List rows) { + List> rowImages = new ArrayList<>(); + if (rows == null || rows.isEmpty()) { + return rowImages; + } + for (NutMap row : rows) { + List images = new ArrayList<>(); + Object photoImageData = row.get("photoImages"); + if (photoImageData instanceof List) { + for (Object image : (List) photoImageData) { + if (image instanceof LedgerExportImage) { + images.add((LedgerExportImage) image); + } + } + } + rowImages.add(images); + } + return rowImages; + } + + /** + * 将已解析到的图片快照使用 POI 手动嵌入到每个教职工所在行的图片列。 + */ + private int insertLedgerPhotoImages(Workbook workbook, List> rowImages, int imageColumnStartIndex) { + if (workbook == null || rowImages == null || rowImages.isEmpty()) { + return 0; + } + Sheet sheet = workbook.getSheetAt(0); + Drawing drawing = sheet.createDrawingPatriarch(); + CreationHelper creationHelper = workbook.getCreationHelper(); + int insertedPictureCount = 0; + for (int rowIndex = 0; rowIndex < rowImages.size(); rowIndex++) { + int excelRowIndex = rowIndex + 1; + Row row = sheet.getRow(excelRowIndex); + if (row == null) { + row = sheet.createRow(excelRowIndex); + } + List images = rowImages.get(rowIndex); + for (int photoIndex = 0; photoIndex < images.size(); photoIndex++) { + LedgerExportImage image = images.get(photoIndex); + int pictureIndex = workbook.addPicture(image.bytes, image.pictureType); + ClientAnchor anchor = creationHelper.createClientAnchor(); + anchor.setCol1(imageColumnStartIndex + photoIndex); + anchor.setRow1(excelRowIndex); + anchor.setCol2(imageColumnStartIndex + photoIndex + 1); + anchor.setRow2(excelRowIndex + 1); + drawing.createPicture(anchor, pictureIndex); + insertedPictureCount++; + log.info("三十年教龄疗休养台账图片写入Excel成功:rowIndex={}, photoIndex={}, fileId={}, bytes={}, pictureType={}", + excelRowIndex, photoIndex + 1, image.fileId, image.bytes.length, image.pictureType); + } + } + return insertedPictureCount; + } + + /** + * 将台账图片字段解析成可插入 Excel 的图片对象,当前优先支持系统文件下载路径。 + */ + private List resolveLedgerImages(String photoFiles) { + List images = new ArrayList<>(); + List photoPaths = splitPhotoFiles(photoFiles); + for (int photoIndex = 0; photoIndex < photoPaths.size(); photoIndex++) { + String originalPath = photoPaths.get(photoIndex); + LedgerExportImage image = resolveLedgerImage(originalPath); + if (image != null) { + images.add(image); + } else { + log.warn("三十年教龄疗休养台账图片解析失败:photoIndex={}, originalPath={}", photoIndex + 1, originalPath); + } + } + return images; + } + + /** + * 根据后台保存的下载路径定位 sys_file,并读取真实图片字节及 POI 可识别的图片类型。 + */ + private LedgerExportImage resolveLedgerImage(String originalPath) { + String normalizedPath = normalizePhotoPath(originalPath); + String fileId = extractIdParam(normalizedPath); + Sys_file sysFile = fetchSysFile(fileId, normalizedPath); + if (sysFile == null) { + log.warn("三十年教龄疗休养台账图片文件记录不存在:fileId={}, originalPath={}, normalizedPath={}", + fileId, originalPath, normalizedPath); + return null; + } + byte[] bytes = readSysFileBytes(sysFile); + int pictureType = detectPictureType(bytes); + if (bytes != null && bytes.length > 0 && pictureType <= 0) { + bytes = convertImageBytesToPng(bytes); + pictureType = bytes == null || bytes.length == 0 ? -1 : Workbook.PICTURE_TYPE_PNG; + } + if (bytes == null || bytes.length == 0 || pictureType <= 0) { + log.warn("三十年教龄疗休养台账图片字节无效:fileId={}, engine={}, storagePath={}, bytes={}, pictureType={}", + sysFile.getId(), sysFile.getEngine(), sysFile.getStoragePath(), bytes == null ? 0 : bytes.length, pictureType); + return null; + } + log.info("三十年教龄疗休养台账图片读取成功:fileId={}, engine={}, bucket={}, storagePath={}, bytes={}, pictureType={}", + sysFile.getId(), sysFile.getEngine(), sysFile.getBucket(), sysFile.getStoragePath(), bytes.length, pictureType); + return new LedgerExportImage(sysFile.getId(), normalizedPath, bytes, pictureType); + } + + /** + * 优先按文件ID查询,失败时按 downloadPath 查询,兼容后台保存的是完整下载路径的场景。 + */ + private Sys_file fetchSysFile(String fileId, String normalizedPath) { + Sys_file sysFile = null; + if (StrUtil.isNotBlank(fileId)) { + sysFile = sysFileService.fetch(fileId); + } + if (sysFile == null && StrUtil.isNotBlank(normalizedPath)) { + sysFile = sysFileService.fetch(Cnd.where(Sys_file::getDownloadPath, "=", normalizedPath)); + } + return sysFile; + } + + /** + * 根据图片字节头判断 POI 图片类型,无法识别时尝试通过 ImageIO 转为 PNG。 + */ + private int detectPictureType(byte[] bytes) { + if (bytes == null || bytes.length < 4) { + return -1; + } + if ((bytes[0] & 0xFF) == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4E && bytes[3] == 0x47) { + return Workbook.PICTURE_TYPE_PNG; + } + if ((bytes[0] & 0xFF) == 0xFF && (bytes[1] & 0xFF) == 0xD8) { + return Workbook.PICTURE_TYPE_JPEG; + } + return -1; + } + + /** + * 当图片字节头无法直接识别时,通过输入流转为 PNG 字节,作为 Excel 写图兜底。 + */ + private byte[] convertImageBytesToPng(byte[] imageBytes) { + if (imageBytes == null || imageBytes.length == 0) { + return null; + } + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + BufferedImage image = ImageIO.read(inputStream); + if (image == null) { + log.warn("三十年教龄疗休养台账图片流转换失败:无法识别图片格式,bytes={}", imageBytes.length); + return null; + } + ImageIO.write(image, "png", outputStream); + return outputStream.toByteArray(); + } catch (Exception e) { + log.warn("三十年教龄疗休养台账图片流转换异常:bytes={}, msg={}", imageBytes.length, e.getMessage()); + return null; + } + } + + /** + * 根据 sys_file 存储引擎读取真实文件字节,避免通过下载接口 URL 触发鉴权、重定向或附件下载响应。 + */ + private byte[] readSysFileBytes(Sys_file sysFile) { + if (sysFile == null || StrUtil.isBlank(sysFile.getStoragePath())) { + return null; + } + try { + if (SysFileEngineTypeEnum.MINIO.getValue().equals(sysFile.getEngine())) { + return SysFileMinIoUtil.getFileBytes(sysFile.getBucket(), sysFile.getStoragePath()); + } + if (SysFileEngineTypeEnum.LOCAL.getValue().equals(sysFile.getEngine())) { + File file = new File(sysFile.getStoragePath()); + return file.exists() && file.isFile() ? Files.readAllBytes(file.toPath()) : null; + } + return sysFileService.download(sysFile.getId()); + } catch (IOException e) { + log.warn("三十年教龄疗休养台账图片本地读取失败:fileId={}, msg={}", sysFile.getId(), e.getMessage()); + return null; + } catch (Exception e) { + log.warn("三十年教龄疗休养台账图片存储读取失败:fileId={}, msg={}", sysFile.getId(), e.getMessage()); + return null; + } + } + + /** + * 解析逗号分隔的图片材料,过滤空值,保留图片原始顺序。 + */ + private List splitPhotoFiles(String photoFiles) { + if (StrUtil.isBlank(photoFiles)) { + return Collections.emptyList(); + } + return StrUtil.splitTrim(photoFiles, ",").stream() + .filter(StrUtil::isNotBlank) + .collect(Collectors.toList()); + } + + /** + * 解析 URL 查询参数中的 id 值,例如 /platform/sys/file/download?id=xxx&name=demo。 + */ + private String extractIdParam(String path) { + if (StrUtil.isBlank(path)) { + return ""; + } + String idMarker = "id="; + int idIndex = path.indexOf(idMarker); + if (idIndex < 0) { + return ""; + } + String id = path.substring(idIndex + idMarker.length()); + int nextParamIndex = id.indexOf("&"); + id = nextParamIndex >= 0 ? id.substring(0, nextParamIndex) : id; + return StrUtil.trimToEmpty(id); + } + + /** + * 将完整URL规整为站内相对路径,避免导出时重复拼接系统域名。 + */ + private String normalizePhotoPath(String photoFile) { + String path = StrUtil.trimToEmpty(photoFile); + if (StrUtil.isBlank(path)) { + return ""; + } + try { + path = URLDecoder.decode(path, StandardCharsets.UTF_8.toString()); + } catch (Exception ignored) { + } + if (StrUtil.isNotBlank(Globals.AppDomain) && path.startsWith(Globals.AppDomain)) { + path = path.substring(Globals.AppDomain.length()); + } + int protocolIndex = path.indexOf("://"); + if (protocolIndex > 0) { + int pathStartIndex = path.indexOf("/", protocolIndex + 3); + path = pathStartIndex >= 0 ? path.substring(pathStartIndex) : ""; + } + return path; + } + + /** + * 台账导出图片对象,保存 POI 写图所需的图片字节、类型和来源文件信息。 + */ + private static class LedgerExportImage { + private final String fileId; + private final String originalPath; + private final byte[] bytes; + private final int pictureType; + + private LedgerExportImage(String fileId, String originalPath, byte[] bytes, int pictureType) { + this.fileId = fileId; + this.originalPath = originalPath; + this.bytes = bytes; + this.pictureType = pictureType; + } + } + private List queryLedgerExportRows(Integer startYear, Integer endYear, String keyword, String unionId, String lineId, String travelPeriod, String lineType, Boolean directFamilyOnly, Boolean overCostOnly) { Cnd cnd = buildQueryCnd(startYear, endYear, keyword, unionId, lineId, travelPeriod, lineType, directFamilyOnly, overCostOnly); diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/thirtyTeachTour/controller/ThirtyTeachTourSchoolUserAssignmentController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/thirtyTeachTour/controller/ThirtyTeachTourSchoolUserAssignmentController.java index 8db4f084..8c060682 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/thirtyTeachTour/controller/ThirtyTeachTourSchoolUserAssignmentController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/thirtyTeachTour/controller/ThirtyTeachTourSchoolUserAssignmentController.java @@ -103,9 +103,9 @@ public class ThirtyTeachTourSchoolUserAssignmentController { @At @SaCheckPermission("thirtyTeachTour.schoolUserAssignment") public Result candidatePageData(PageForm pageForm, String settingId, String unionId, String keyword, @Param("userIds") String userIds, - Boolean summerJoined, Boolean lastThirtyJoined) { + Boolean summerJoined, Boolean lastThirtyJoined, Boolean retiringSoon) { return Result.success(tourUserAssignmentService.schoolCandidatePage(pageForm, settingId, unionId, keyword, - parseUserIds(userIds), summerJoined, lastThirtyJoined)); + parseUserIds(userIds), summerJoined, lastThirtyJoined, retiringSoon)); } /** diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/thirtyTeachTour/service/ThirtyTeachTourUserAssignmentService.java b/src/main/java/com/budwk/app/zhgh/dayofficework/thirtyTeachTour/service/ThirtyTeachTourUserAssignmentService.java index 5c5b31ee..854d835b 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/thirtyTeachTour/service/ThirtyTeachTourUserAssignmentService.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/thirtyTeachTour/service/ThirtyTeachTourUserAssignmentService.java @@ -75,10 +75,11 @@ public interface ThirtyTeachTourUserAssignmentService extends BaseService schoolCandidatePage(PageForm pageForm, String settingId, String unionId, String keyword, List userIds, - Boolean summerJoinedFilter, Boolean lastThirtyJoinedFilter); + Boolean summerJoinedFilter, Boolean lastThirtyJoinedFilter, Boolean retiringSoonFilter); /** * 分页查询当前登录人所在分工会的人员分配记录,列表只读取人员分配表,不读取报名台账。 @@ -115,9 +116,11 @@ public interface ThirtyTeachTourUserAssignmentService extends BaseService branchCandidatePage(PageForm pageForm, String settingId, String keyword, List userIds); + Pagination branchCandidatePage(PageForm pageForm, String settingId, String keyword, List userIds, + Boolean retiringSoonFilter); /** * 查询启用的疗休养配置选项,供人员分配列表筛选和分配弹窗复用。 diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/thirtyTeachTour/service/impl/ThirtyTeachTourUserAssignmentServiceImpl.java b/src/main/java/com/budwk/app/zhgh/dayofficework/thirtyTeachTour/service/impl/ThirtyTeachTourUserAssignmentServiceImpl.java index 4038b41c..f37fbdd9 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/thirtyTeachTour/service/impl/ThirtyTeachTourUserAssignmentServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/thirtyTeachTour/service/impl/ThirtyTeachTourUserAssignmentServiceImpl.java @@ -127,13 +127,14 @@ public class ThirtyTeachTourUserAssignmentServiceImpl extends BaseServiceImpl schoolCandidatePage(PageForm pageForm, String settingId, String unionId, String keyword, List userIds, - Boolean summerJoinedFilter, Boolean lastThirtyJoinedFilter) { + Boolean summerJoinedFilter, Boolean lastThirtyJoinedFilter, Boolean retiringSoonFilter) { ThirtyTeachTourSetting setting = fetchSettingForAssignment(settingId); if (setting == null || StrUtil.isBlank(setting.getActivityGroupId())) { return emptyPagination(pageForm); } Sql sql = buildCandidateSql(settingId, setting.getYear()); - Cnd cnd = buildCandidateCnd(setting.getActivityGroupId(), unionId, keyword, summerJoinedFilter, lastThirtyJoinedFilter); + Cnd cnd = buildCandidateCnd(setting.getActivityGroupId(), unionId, keyword, summerJoinedFilter, + lastThirtyJoinedFilter, retiringSoonFilter); List normalizedUserIds = normalizeUserIds(userIds); // 人员选择器多选查询时,候选范围仍受活动组、分工会、已分配排除规则约束,再按指定人员ID精确过滤。 if (!Lang.isEmpty(normalizedUserIds)) { @@ -171,12 +172,13 @@ public class ThirtyTeachTourUserAssignmentServiceImpl extends BaseServiceImpl branchCandidatePage(PageForm pageForm, String settingId, String keyword, List userIds) { + public Pagination branchCandidatePage(PageForm pageForm, String settingId, String keyword, List userIds, + Boolean retiringSoonFilter) { String unionId = SecurityUtil.getUnionId(); if (StrUtil.isBlank(unionId)) { return emptyPagination(pageForm); } - return schoolCandidatePage(pageForm, settingId, unionId, keyword, userIds, false, false); + return schoolCandidatePage(pageForm, settingId, unionId, keyword, userIds, false, false, retiringSoonFilter); } /** @@ -372,7 +374,7 @@ public class ThirtyTeachTourUserAssignmentServiceImpl extends BaseServiceImpl candidateUsers = listMap(userSql); @@ -440,7 +442,7 @@ public class ThirtyTeachTourUserAssignmentServiceImpl extends BaseServiceImpl candidateUsers = listMap(userSql); @@ -598,7 +600,7 @@ public class ThirtyTeachTourUserAssignmentServiceImpl extends BaseServiceImpl candidateUsers = listMap(userSql); @@ -1241,10 +1243,12 @@ public class ThirtyTeachTourUserAssignmentServiceImpl extends BaseServiceImpl 0 THEN 1 ELSE 0 END AS summerJoined, CASE WHEN IFNULL(last_thirty_joined.joinedCount, 0) > 0 THEN 1 ELSE 0 END AS lastThirtyJoined FROM activity_user_scope aus LEFT JOIN vw_user u ON u.id = aus.userId + LEFT JOIN sys_user su ON su.id = aus.userId LEFT JOIN thirty_teach_tour_user_assignment a ON a.settingId = @settingId AND a.userId = aus.userId @@ -1274,7 +1278,8 @@ public class ThirtyTeachTourUserAssignmentServiceImpl extends BaseServiceImpl" : "=", 0); } + /** + * 按系统用户表中的“即将退休人员”标记过滤候选人;前端默认传 true,只保留即将退休人员。 + */ + private void appendRetiringSoonFilter(Cnd cnd, Boolean retiringSoonFilter) { + if (retiringSoonFilter == null) { + return; + } + cnd.and("IFNULL(su.retiringSoon, 0)", "=", retiringSoonFilter ? 1 : 0); + } + private NutMap assignmentDeleteInfo(ThirtyTeachTourUserAssignment assignment) { int ledgerCount = countAssignmentLedgers(assignment); return NutMap.NEW() diff --git a/src/main/resources/db/sys_user_add_retiring_soon.sql b/src/main/resources/db/sys_user_add_retiring_soon.sql new file mode 100644 index 00000000..530e86e3 --- /dev/null +++ b/src/main/resources/db/sys_user_add_retiring_soon.sql @@ -0,0 +1 @@ +ALTER TABLE sys_user ADD COLUMN retiringSoon INT DEFAULT 0 COMMENT '即将退休人员(是:1;否:0)'; diff --git a/src/main/resources/views/platform/zhgh/dayofficework/ThirtyTeachTour/branchUserAssignment/index.html b/src/main/resources/views/platform/zhgh/dayofficework/ThirtyTeachTour/branchUserAssignment/index.html index 4b8a3b80..fddd90f7 100644 --- a/src/main/resources/views/platform/zhgh/dayofficework/ThirtyTeachTour/branchUserAssignment/index.html +++ b/src/main/resources/views/platform/zhgh/dayofficework/ThirtyTeachTour/branchUserAssignment/index.html @@ -141,6 +141,14 @@ layout("/layouts/platform.html"){ + + + + + + + + @@ -494,6 +502,7 @@ layout("/layouts/platform.html"){ pageNumber: 1, pageSize: 10, totalCount: 0, + retiringSoon: "true", keyword: "" }, selectMatterForm: { @@ -528,6 +537,7 @@ layout("/layouts/platform.html"){ pageNumber: 1, pageSize: 10, totalCount: 0, + retiringSoon: "true", keyword: "" } }, @@ -808,6 +818,7 @@ layout("/layouts/platform.html"){ pageNumber: this.candidateForm.pageNumber, pageSize: this.candidateForm.pageSize, settingId: this.assignForm.settingId, + retiringSoon: this.candidateForm.retiringSoon, keyword: (this.selectedCandidateIds || []).length > 0 ? "" : this.candidateForm.keyword, userIds: JSON.stringify(this.selectedCandidateIds || []) }).then((res) => { @@ -831,6 +842,7 @@ layout("/layouts/platform.html"){ this.loadCandidatePageData() }, resetCandidateSearch() { + this.candidateForm.retiringSoon = "true" this.candidateForm.keyword = "" this.candidateForm.pageNumber = 1 this.selectedCandidateIds = [] @@ -864,6 +876,7 @@ layout("/layouts/platform.html"){ pageNumber: 1, pageSize: 20, settingId: this.assignForm.settingId, + retiringSoon: this.candidateForm.retiringSoon, keyword: keyword || "" }).then((res) => { if (res.code === 0) { diff --git a/src/main/resources/views/platform/zhgh/dayofficework/ThirtyTeachTour/schoolUserAssignment/index.html b/src/main/resources/views/platform/zhgh/dayofficework/ThirtyTeachTour/schoolUserAssignment/index.html index 52fccae7..3dde4469 100644 --- a/src/main/resources/views/platform/zhgh/dayofficework/ThirtyTeachTour/schoolUserAssignment/index.html +++ b/src/main/resources/views/platform/zhgh/dayofficework/ThirtyTeachTour/schoolUserAssignment/index.html @@ -194,6 +194,14 @@ layout("/layouts/platform.html"){ + + + + + + + + @@ -201,14 +209,6 @@ layout("/layouts/platform.html"){ - - - - - - - - 0 ? "" : this.candidateForm.keyword, @@ -835,6 +838,7 @@ layout("/layouts/platform.html"){ }, resetCandidateSearch() { this.candidateForm.unionId = "" + this.candidateForm.retiringSoon = "true" this.candidateForm.summerJoined = "false" this.candidateForm.lastThirtyJoined = "false" this.candidateForm.keyword = "" @@ -882,6 +886,7 @@ layout("/layouts/platform.html"){ pageSize: 20, settingId: this.assignForm.settingId, unionId: this.candidateForm.unionId, + retiringSoon: this.candidateForm.retiringSoon, summerJoined: this.candidateForm.summerJoined, lastThirtyJoined: this.candidateForm.lastThirtyJoined, keyword: keyword || ""