南大三十年教职工疗休养

This commit is contained in:
2026-06-26 14:32:53 +08:00
parent a1547b133e
commit 1b888b4fce
10 changed files with 403 additions and 26 deletions
@@ -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) {
@@ -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)
@@ -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));
}
/**
@@ -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<NutMap> 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<List<LedgerExportImage>> 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<NutMap> 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<String> 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<List<LedgerExportImage>> snapshotLedgerRowImages(List<NutMap> rows) {
List<List<LedgerExportImage>> rowImages = new ArrayList<>();
if (rows == null || rows.isEmpty()) {
return rowImages;
}
for (NutMap row : rows) {
List<LedgerExportImage> 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<List<LedgerExportImage>> 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<LedgerExportImage> 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<LedgerExportImage> resolveLedgerImages(String photoFiles) {
List<LedgerExportImage> images = new ArrayList<>();
List<String> 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<String> 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<NutMap> 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);
@@ -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));
}
/**
@@ -75,10 +75,11 @@ public interface ThirtyTeachTourUserAssignmentService extends BaseService<Thirty
* @param userIds 指定候选人员ID列表,人员选择器多选查询时使用
* @param summerJoinedFilter 今年是否已参加暑期疗休养过滤:true 是,false 否,null 不过滤
* @param lastThirtyJoinedFilter 去年是否参加30年教龄疗休养过滤:true 是,false 否,null 不过滤
* @param retiringSoonFilter 即将退休人员过滤:true 是,false 否,null 不过滤
* @return 可分配候选人分页数据
*/
Pagination<NutMap> schoolCandidatePage(PageForm pageForm, String settingId, String unionId, String keyword, List<String> userIds,
Boolean summerJoinedFilter, Boolean lastThirtyJoinedFilter);
Boolean summerJoinedFilter, Boolean lastThirtyJoinedFilter, Boolean retiringSoonFilter);
/**
* 分页查询当前登录人所在分工会的人员分配记录,列表只读取人员分配表,不读取报名台账。
@@ -115,9 +116,11 @@ public interface ThirtyTeachTourUserAssignmentService extends BaseService<Thirty
* @param settingId 疗休养配置ID
* @param keyword 姓名或工号关键字
* @param userIds 指定候选人员ID列表,人员选择器多选查询时使用
* @param retiringSoonFilter 即将退休人员过滤:true 是,false 否,null 不过滤
* @return 当前分工会可分配候选人分页数据
*/
Pagination<NutMap> branchCandidatePage(PageForm pageForm, String settingId, String keyword, List<String> userIds);
Pagination<NutMap> branchCandidatePage(PageForm pageForm, String settingId, String keyword, List<String> userIds,
Boolean retiringSoonFilter);
/**
* 查询启用的疗休养配置选项,供人员分配列表筛选和分配弹窗复用。
@@ -127,13 +127,14 @@ public class ThirtyTeachTourUserAssignmentServiceImpl extends BaseServiceImpl<Th
@Override
public Pagination<NutMap> schoolCandidatePage(PageForm pageForm, String settingId, String unionId, String keyword, List<String> 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<String> normalizedUserIds = normalizeUserIds(userIds);
// 人员选择器多选查询时,候选范围仍受活动组、分工会、已分配排除规则约束,再按指定人员ID精确过滤。
if (!Lang.isEmpty(normalizedUserIds)) {
@@ -171,12 +172,13 @@ public class ThirtyTeachTourUserAssignmentServiceImpl extends BaseServiceImpl<Th
}
@Override
public Pagination<NutMap> branchCandidatePage(PageForm pageForm, String settingId, String keyword, List<String> userIds) {
public Pagination<NutMap> branchCandidatePage(PageForm pageForm, String settingId, String keyword, List<String> 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<Th
// 保存前重新按“配置人员范围 + 未分配”查询,防止前端绕过候选列表提交范围外人员。
Sql userSql = buildCandidateSql(settingId, setting.getYear());
Cnd userCnd = buildCandidateCnd(setting.getActivityGroupId(), null, null, false, false);
Cnd userCnd = buildCandidateCnd(setting.getActivityGroupId(), null, null, false, false, null);
userCnd.and("u.id", "in", distinctUserIds);
userSql.setCondition(userCnd);
List<NutMap> candidateUsers = listMap(userSql);
@@ -440,7 +442,7 @@ public class ThirtyTeachTourUserAssignmentServiceImpl extends BaseServiceImpl<Th
// 保存前重新按“配置人员范围 + 未分配”查询,防止前端绕过候选列表提交范围外人员。
Sql userSql = buildCandidateSql(settingId, setting.getYear());
Cnd userCnd = buildCandidateCnd(setting.getActivityGroupId(), null, null, false, false);
Cnd userCnd = buildCandidateCnd(setting.getActivityGroupId(), null, null, false, false, null);
userCnd.and("u.id", "in", userIds);
userSql.setCondition(userCnd);
List<NutMap> candidateUsers = listMap(userSql);
@@ -598,7 +600,7 @@ public class ThirtyTeachTourUserAssignmentServiceImpl extends BaseServiceImpl<Th
// 保存前重新按“配置人员范围 + 当前分工会 + 未分配”查询,防止前端提交范围外或其它分工会人员。
Sql userSql = buildCandidateSql(settingId, setting.getYear());
Cnd userCnd = buildCandidateCnd(setting.getActivityGroupId(), unionId, null, false, false);
Cnd userCnd = buildCandidateCnd(setting.getActivityGroupId(), unionId, null, false, false, null);
userCnd.and("u.id", "in", distinctUserIds);
userSql.setCondition(userCnd);
List<NutMap> candidateUsers = listMap(userSql);
@@ -1241,10 +1243,12 @@ public class ThirtyTeachTourUserAssignmentServiceImpl extends BaseServiceImpl<Th
u.unitname AS unitName,
u.unionid AS unionId,
u.unionname AS unionName,
IFNULL(su.retiringSoon, 0) AS retiringSoon,
CASE WHEN IFNULL(summer_joined.joinedCount, 0) > 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<Th
* 组装候选人过滤条件,参游记录过滤为null时表示不限制该条件。
*/
private Cnd buildCandidateCnd(String activityGroupId, String unionId, String keyword,
Boolean summerJoinedFilter, Boolean lastThirtyJoinedFilter) {
Boolean summerJoinedFilter, Boolean lastThirtyJoinedFilter,
Boolean retiringSoonFilter) {
Cnd cnd = Cnd.NEW();
cnd.and("aus.groupId", "=", activityGroupId);
cnd.and("u.id", "is not", null);
@@ -1282,6 +1287,7 @@ public class ThirtyTeachTourUserAssignmentServiceImpl extends BaseServiceImpl<Th
cnd.andEX("u.unionid", "=", unionId);
appendJoinedFilter(cnd, "summer_joined.joinedCount", summerJoinedFilter);
appendJoinedFilter(cnd, "last_thirty_joined.joinedCount", lastThirtyJoinedFilter);
appendRetiringSoonFilter(cnd, retiringSoonFilter);
if (StrUtil.isNotBlank(keyword)) {
SqlExpressionGroup group = new SqlExpressionGroup();
group.orLike("u.username", keyword);
@@ -1301,6 +1307,16 @@ public class ThirtyTeachTourUserAssignmentServiceImpl extends BaseServiceImpl<Th
cnd.and("IFNULL(" + countColumn + ", 0)", joinedFilter ? ">" : "=", 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()
@@ -0,0 +1 @@
ALTER TABLE sys_user ADD COLUMN retiringSoon INT DEFAULT 0 COMMENT '即将退休人员(是:1;否:0';
@@ -141,6 +141,14 @@ layout("/layouts/platform.html"){
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="即将退休人员">
<el-select v-model="candidateForm.retiringSoon" clearable placeholder="请选择" style="width: 100%" @change="candidateSearch">
<el-option label="是" value="true"></el-option>
<el-option label="否" value="false"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="人员类型" prop="personType">
<el-radio-group v-model="assignForm.personType" @change="assignPersonTypeChange">
@@ -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) {
@@ -194,6 +194,14 @@ layout("/layouts/platform.html"){
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="即将退休人员">
<el-select v-model="candidateForm.retiringSoon" clearable placeholder="请选择" style="width: 100%" @change="candidateSearch">
<el-option label="是" value="true"></el-option>
<el-option label="否" value="false"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
@@ -201,14 +209,6 @@ layout("/layouts/platform.html"){
<el-select v-model="candidateForm.unionId" clearable filterable placeholder="所属分工会" style="width: 220px" @change="candidateSearch">
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
<el-select v-model="candidateForm.summerJoined" clearable placeholder="今年是否已有暑期疗休养台账" style="width: 230px" @change="candidateSearch">
<el-option label="否" value="false"></el-option>
<el-option label="是" value="true"></el-option>
</el-select>
<el-select v-model="candidateForm.lastThirtyJoined" clearable placeholder="去年是否参加30年教龄疗休养" style="width: 250px" @change="candidateSearch">
<el-option label="否" value="false"></el-option>
<el-option label="是" value="true"></el-option>
</el-select>
<el-select
v-model="selectedCandidateIds"
multiple
@@ -492,6 +492,7 @@ layout("/layouts/platform.html"){
pageSize: 10,
totalCount: 0,
unionId: "",
retiringSoon: "true",
summerJoined: "false",
lastThirtyJoined: "false",
keyword: ""
@@ -532,6 +533,7 @@ layout("/layouts/platform.html"){
pageSize: 10,
totalCount: 0,
unionId: "",
retiringSoon: "true",
summerJoined: "false",
lastThirtyJoined: "false",
keyword: ""
@@ -803,6 +805,7 @@ layout("/layouts/platform.html"){
pageSize: this.candidateForm.pageSize,
settingId: this.assignForm.settingId,
unionId: this.candidateForm.unionId,
retiringSoon: this.candidateForm.retiringSoon,
summerJoined: this.candidateForm.summerJoined,
lastThirtyJoined: this.candidateForm.lastThirtyJoined,
keyword: (this.selectedCandidateIds || []).length > 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 || ""