南大三十年教职工疗休养
This commit is contained in:
@@ -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)
|
||||
|
||||
+4
-2
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+332
-1
@@ -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);
|
||||
|
||||
+2
-2
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+5
-2
@@ -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);
|
||||
|
||||
/**
|
||||
* 查询启用的疗休养配置选项,供人员分配列表筛选和分配弹窗复用。
|
||||
|
||||
+24
-8
@@ -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)';
|
||||
+13
@@ -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) {
|
||||
|
||||
+13
-8
@@ -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 || ""
|
||||
|
||||
Reference in New Issue
Block a user