Merge branch 'feature_周何峰'

This commit is contained in:
2026-09-03 10:33:00 +08:00
8 changed files with 421 additions and 29 deletions
@@ -157,12 +157,14 @@ public class RecuperationEvaluateStatisticsController {
List<ExcelExportEntity> columns = new ArrayList<>();
columns.add(new ExcelExportEntity("序号", "no", 10));
columns.add(new ExcelExportEntity("报名方式", "typeName", 12));
columns.add(new ExcelExportEntity("方案名称", "schemeName", 24));
columns.add(new ExcelExportEntity("方案名称", "projectName", 24));
columns.add(new ExcelExportEntity("旅行社", "travelAgencyName", 22));
columns.add(new ExcelExportEntity("评价人", "userName", 14));
columns.add(new ExcelExportEntity("所属单位", "unitName", 24));
columns.add(new ExcelExportEntity("旅行社评分", "evaluationForTravelAgency", 14));
columns.add(new ExcelExportEntity("住宿/酒店评分", "evaluationForAccommodation", 16));
String accommodationLabel = "线路".equals(typeName) ? "住宿评分"
: ("灵活组团".equals(typeName) || "定点".equals(typeName) ? "酒店评分" : "住宿/酒店评分");
columns.add(new ExcelExportEntity(accommodationLabel, "evaluationForAccommodation", 16));
columns.add(new ExcelExportEntity("行程评分", "evaluationForJourney", 14));
columns.add(new ExcelExportEntity("餐饮评分", "evaluationForDining", 14));
columns.add(new ExcelExportEntity("交通评分", "evaluationForTransportation", 14));
@@ -62,10 +62,11 @@ public class RecuperationFlexibleGroupQueryController {
return Result.success(flexibleGroupService.querySignUsers(pageForm, groupId, groupLeaderUserId, groupLeaderLoginName, noGroupLeader));
}
/** pageForm 传分页信息,groupId 传灵活组团 ID;data 返回全部报名人员的分页结构。 */
@At
@SaCheckLogin
public Result getAllSignUser(PageForm pageForm, String groupId) {
return Result.success(flexibleGroupService.querySignUsers(pageForm, groupId, null, null, false));
return Result.success(flexibleGroupService.queryAllSignUsers(pageForm, groupId));
}
@At
@@ -98,16 +99,16 @@ public class RecuperationFlexibleGroupQueryController {
String searchKeyword, HttpServletResponse response) {
List<ExcelExportEntity> columns = new ArrayList<>();
columns.add(new ExcelExportEntity("序号", "no", 10));
columns.add(new ExcelExportEntity("工号", "loginName", 18));
columns.add(new ExcelExportEntity("姓名", "userName", 14));
columns.add(new ExcelExportEntity("联系电话", "mobile", 18));
columns.add(new ExcelExportEntity("身份证号", "idCard", 24));
columns.add(new ExcelExportEntity("单位", "unitName", 24));
columns.add(new ExcelExportEntity("工会", "unionName", 20));
columns.add(new ExcelExportEntity("旅行社", "travelAgencyName", 22));
columns.add(new ExcelExportEntity("工号", "loginName", 20));
columns.add(new ExcelExportEntity("姓名", "userName", 20));
columns.add(new ExcelExportEntity("联系电话", "mobile", 20));
columns.add(new ExcelExportEntity("身份证号", "idCard", 30));
columns.add(new ExcelExportEntity("单位", "unitName", 30));
columns.add(new ExcelExportEntity("工会", "unionName", 30));
columns.add(new ExcelExportEntity("旅行社", "travelAgencyName", 30));
columns.add(new ExcelExportEntity("报名时间", "signingUptime", 20));
columns.add(new ExcelExportEntity("团长", "leaderName", 20));
columns.add(new ExcelExportEntity("是否成团", "formedTeamState", 14));
columns.add(new ExcelExportEntity("团长", "leaderName", 25));
columns.add(new ExcelExportEntity("是否成团", "formedTeamState", 15));
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, columns,
@@ -13,6 +13,9 @@ import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/** PC 端线路报名情况及成团通知。 */
@IocBean
@At("/platform/recuperation/lineStatistics")
@@ -48,6 +51,24 @@ public class RecuperationLineStatisticsController {
return Result.success(statisticsService.lineOptions(startYear, endYear, signUpMode, regionalNature));
}
/** lineId 为线路 IDdata 中 selectId 表示具体出行批次,times 为时间及报名人数说明。 */
@At
@SaCheckPermission("recuperation.lineStatistics")
public Result getLinePlayTimes(String lineId, Integer startYear, Integer endYear) {
return Result.success(statisticsService.linePlayTimeOptions(lineId, startYear, endYear));
}
/** 按当前筛选条件导出达到最低成团人数的线路人员,并按线路生成 Excel 压缩包。 */
@At
@Ok("void")
@SaCheckPermission("recuperation.lineStatistics")
public void doExport(Integer startYear, Integer endYear, String unionId, String takePartInLineId,
String lotId, String selectId, String regionalNature,
HttpServletResponse response) throws IOException {
statisticsService.exportFormedLineUsers(startYear, endYear, unionId, takePartInLineId,
lotId, selectId, regionalNature, response);
}
@At
@SaCheckPermission("recuperation.lineStatistics")
public Result countSuccessNotice(String id) { return Result.success(enrollService.countLineGroupNoticeUsers(id, true)); }
@@ -4,6 +4,8 @@ import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import org.nutz.lang.util.NutMap;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
/** PC 端线路成团统计查询。 */
@@ -14,4 +16,30 @@ public interface RecuperationLineStatisticsService {
Pagination userPage(PageForm pageForm, String lineSelectId, String searchKeyword, String unionId, String unitId);
List<NutMap> lineOptions(Integer startYear, Integer endYear, String signUpMode, String regionalNature);
/**
* 查询指定线路在年度范围内的出行批次选项。
*
* @param lineId 线路 ID
* @param startYear 开始年度
* @param endYear 结束年度
* @return selectId 为工会选线记录 ID,times 为出行时间及报名人数说明
*/
List<NutMap> linePlayTimeOptions(String lineId, Integer startYear, Integer endYear);
/**
* 导出达到最低成团人数的线路报名人员。
*
* @param startYear 开始年度
* @param endYear 结束年度
* @param unionId 报名人员所属工会 ID
* @param lineId 线路 ID
* @param lotId 标段 ID
* @param selectId 工会选线记录 ID,用于限定具体出行批次
* @param regionalNature 线路类型,可传省内、省外或空值
* @param response 返回 ZIP 文件的 HTTP 响应
*/
void exportFormedLineUsers(Integer startYear, Integer endYear, String unionId, String lineId,
String lotId, String selectId, String regionalNature,
HttpServletResponse response) throws IOException;
}
@@ -105,6 +105,15 @@ public interface RecuperationProvinceFlexibleGroupService extends BaseService<Re
Pagination querySignUsers(PageForm pageForm, String groupId, String groupLeaderUserId,
String groupLeaderLoginName, Boolean noGroupLeader);
/**
* 查询一个灵活组团的全部报名人员,并按团长、团内身份和报名时间排序。
*
* @param pageForm 分页信息
* @param groupId 灵活组团 ID
* @return 报名人员分页数据
*/
Pagination queryAllSignUsers(PageForm pageForm, String groupId);
/** 查询年度区间内可用旅行社。 */
List<NutMap> queryTravelAgencyOptions(Integer startYear, Integer endYear);
@@ -1594,7 +1594,7 @@ public class RecuperationEnrollServiceImpl extends BaseServiceImpl<RecuperationE
AND rating.evaluationForAccommodation IS NOT NULL AND rating.evaluationForDining IS NOT NULL
AND rating.evaluationForTransportation IS NOT NULL THEN rating.loginName END) AS ratingCount
FROM the_rapy_recuperation_travel_agency agency
LEFT JOIN ($ratingSql) rating ON rating.travelAgencyId=agency.id
LEFT JOIN ($ratingSql) rating ON rating.travelAgencyId=agency.id AND rating.statisticYear=@year
WHERE agency.year=@year
GROUP BY agency.id, agency.travelAgencyName
ORDER BY CASE WHEN avg IS NULL THEN 1 ELSE 0 END, avg DESC, ratingCount DESC, agency.travelAgencyName
@@ -1623,7 +1623,7 @@ public class RecuperationEnrollServiceImpl extends BaseServiceImpl<RecuperationE
AND rating.evaluationForAccommodation IS NOT NULL AND rating.evaluationForDining IS NOT NULL AND rating.evaluationForTransportation IS NOT NULL THEN rating.loginName
WHEN rating.typeName IN ('灵活组团','定点') AND rating.evaluationForTravelAgency IS NOT NULL AND rating.evaluationForAccommodation IS NOT NULL THEN rating.loginName END) AS ratingCount
FROM ($ratingSql) rating
WHERE IFNULL(rating.schemeId,'')<>''
WHERE rating.statisticYear=@year AND IFNULL(rating.schemeId,'')<>''
GROUP BY rating.typeName, rating.schemeId, rating.schemeName, rating.travelAgencyName
ORDER BY FIELD(rating.typeName,'线路','灵活组团','定点'), rating.schemeName
""");
@@ -1635,12 +1635,20 @@ public class RecuperationEnrollServiceImpl extends BaseServiceImpl<RecuperationE
@Override
public List<NutMap> satisfactionFeedbackDetails(Integer year, String typeName) {
Sql sql = Sqls.create("""
SELECT rating.*,
SELECT rating.*, rating.schemeName AS projectName,
ROUND(CASE WHEN rating.typeName='线路' THEN
(rating.evaluationForTravelAgency+rating.evaluationForJourney+rating.evaluationForAccommodation+rating.evaluationForDining+rating.evaluationForTransportation)/5
ELSE (rating.evaluationForTravelAgency+rating.evaluationForAccommodation)/2 END,1) AS compositeScore
FROM ($ratingSql) rating
WHERE rating.feedbackContent IS NOT NULL AND rating.feedbackContent<>''
WHERE rating.statisticYear=@year AND (
rating.evaluationForTravelAgency IS NOT NULL
OR rating.evaluationForJourney IS NOT NULL
OR rating.evaluationForAccommodation IS NOT NULL
OR rating.evaluationForDining IS NOT NULL
OR rating.evaluationForTransportation IS NOT NULL
OR rating.evaluationForLine IS NOT NULL
OR IFNULL(rating.feedbackContent,'')<>''
)
ORDER BY rating.signingUptime DESC
""");
sql.setVar("ratingSql", satisfactionBaseSql()).setParam("year", year == null ? DateUtil.thisYear() : year);
@@ -1652,9 +1660,10 @@ public class RecuperationEnrollServiceImpl extends BaseServiceImpl<RecuperationE
/** 满意度统计与明细共用同一报名方式、方案和旅行社映射。 */
private String satisfactionBaseSql() {
return """
SELECT enroll.id, enroll.loginName, enroll.userName, enroll.unitName, enroll.signingUptime, enroll.feedbackContent,
SELECT enroll.id, enroll.loginName, enroll.userName, enroll.unitName, enroll.signingUptime,
YEAR(enroll.signingUptime) AS statisticYear, enroll.feedbackContent,
enroll.evaluationForTravelAgency, enroll.evaluationForJourney, enroll.evaluationForAccommodation,
enroll.evaluationForDining, enroll.evaluationForTransportation,
enroll.evaluationForDining, enroll.evaluationForTransportation, enroll.evaluationForLine,
CASE WHEN enroll.takePartInLineId IS NOT NULL THEN '线路' WHEN enroll.takePartInBaseManagementId IS NOT NULL THEN '定点' ELSE '灵活组团' END AS typeName,
CASE WHEN enroll.takePartInLineId IS NOT NULL THEN COALESCE(line.id,directLine.id) WHEN enroll.takePartInBaseManagementId IS NOT NULL THEN base.id ELSE COALESCE(flexible.id,enroll.takePartInTravelAgencyId) END AS schemeId,
CASE WHEN enroll.takePartInLineId IS NOT NULL THEN COALESCE(line.lineName,directLine.lineName) WHEN enroll.takePartInBaseManagementId IS NOT NULL THEN base.baseName ELSE COALESCE(flexible.groupName, directAgency.travelAgencyName) END AS schemeName,
@@ -1676,7 +1685,7 @@ public class RecuperationEnrollServiceImpl extends BaseServiceImpl<RecuperationE
AND firstFlexible.year=YEAR(enroll.signingUptime) AND firstFlexible.isDisabled=false
ORDER BY firstFlexible.sortNumber, firstFlexible.id LIMIT 1
)
WHERE enroll.isNormal=true AND enroll.isTakePartIn=true AND YEAR(enroll.signingUptime)=@year
WHERE enroll.isNormal=true AND enroll.isTakePartIn=true
""";
}
}
@@ -1,13 +1,20 @@
package com.budwk.app.zhgh.staffbenefit.recuperation.service.impl;
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.staffbenefit.recuperation.common.RecuperationCommon;
import com.budwk.app.zhgh.staffbenefit.recuperation.model.RecuperationConfig;
import com.budwk.app.zhgh.staffbenefit.recuperation.model.RecuperationEnroll;
import com.budwk.app.zhgh.staffbenefit.recuperation.service.RecuperationLineStatisticsService;
import org.apache.poi.ss.usermodel.Workbook;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
@@ -15,7 +22,21 @@ import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/** 线路统计服务,统一限定审核通过且未调出的报名记录。 */
@IocBean(args = {"refer:dao"})
@@ -26,11 +47,12 @@ public class RecuperationLineStatisticsServiceImpl extends BaseServiceImpl<Recup
public Pagination pageData(PageForm pageForm, Integer startYear, Integer endYear, String unionId,
String lineId, String lotId, String signUpMode, String selectId, String regionalNature) {
Sql sql = Sqls.create("""
SELECT line.id, line.lineName, line.regionalNature, lineu.id AS lineUId, lineu.signUpMode,
SELECT line.id, line.id AS lineId, line.lineName, line.regionalNature, lineu.id AS lineUId, lineu.signUpMode,
YEAR(lineu.selectTime) AS year,
CONCAT(DATE_FORMAT(lineu.playStartTime,'%m月%d日'),'-',DATE_FORMAT(lineu.playEndTime,'%m月%d日')) AS linePlayTime,
agency.travelAgencyName, agency.contact, agency.contactMobileNumber,
IF(lineu.signUpMode=2,'校工会',un.name) AS unionName, lot.lotName,
MAX(enroll.takePartInUnionId) AS usUnionId,
COALESCE((SELECT outsideQuota FROM the_rapy_recuperation_config LIMIT 1),0) AS minimumGroupSize,
COUNT(DISTINCT enroll.loginName) AS lineNum,
IF(COALESCE((SELECT familyInfo FROM the_rapy_recuperation_config LIMIT 1),1)=2,
@@ -94,4 +116,193 @@ public class RecuperationLineStatisticsServiceImpl extends BaseServiceImpl<Recup
sql.setCondition(cnd);
return listMap(sql);
}
@Override
public List<NutMap> linePlayTimeOptions(String lineId, Integer startYear, Integer endYear) {
Sql sql = Sqls.create("""
SELECT lineu.id AS selectId,
CONCAT(
DATE_FORMAT(lineu.playStartTime,'%m月%d日'),'-',DATE_FORMAT(lineu.playEndTime,'%m月%d日'),
'(报名人数:',
COUNT(DISTINCT enroll.loginName) +
IF(COALESCE((SELECT familyInfo FROM the_rapy_recuperation_config LIMIT 1),1)=2,
(SELECT COUNT(1)
FROM the_rapy_recuperation_enroll_companion companion
WHERE companion.trreId IN (
SELECT signed.id
FROM the_rapy_recuperation_enroll signed
WHERE signed.takePartInLineId=lineu.id
AND signed.isNormal=true
AND signed.stateId NOT IN (2715,2725,2735)
)),
COALESCE(SUM(enroll.familyNumber),0)),
',其中家属:',
IF(COALESCE((SELECT familyInfo FROM the_rapy_recuperation_config LIMIT 1),1)=2,
(SELECT COUNT(1)
FROM the_rapy_recuperation_enroll_companion companion
WHERE companion.trreId IN (
SELECT signed.id
FROM the_rapy_recuperation_enroll signed
WHERE signed.takePartInLineId=lineu.id
AND signed.isNormal=true
AND signed.stateId NOT IN (2715,2725,2735)
)),
COALESCE(SUM(enroll.familyNumber),0)),
'人)'
) AS times
FROM the_rapy_recuperation_line_union_select lineu
LEFT JOIN the_rapy_recuperation_enroll enroll
ON enroll.takePartInLineId=lineu.id
AND enroll.isNormal=true
AND enroll.stateId NOT IN (2715,2725,2735)
$condition
""");
Cnd cnd = Cnd.where("lineu.lineId", "=", lineId)
.andEX("YEAR(lineu.selectTime)", ">=", startYear)
.andEX("YEAR(lineu.selectTime)", "<=", endYear);
if (!AuthUtil.hasRoleOr("sysadmin", "H06", "A06")) {
cnd.and("lineu.unionId", "=", SecurityUtil.getUnionId());
}
cnd.groupBy("lineu.id", "lineu.playStartTime", "lineu.playEndTime");
cnd.asc("lineu.playStartTime");
sql.setCondition(cnd);
return listMap(sql);
}
@Override
public void exportFormedLineUsers(Integer startYear, Integer endYear, String unionId, String lineId,
String lotId, String selectId, String regionalNature,
HttpServletResponse response) throws IOException {
List<NutMap> exportRows = queryLineExportRows(startYear, endYear, unionId, lineId, lotId, selectId, regionalNature);
RecuperationConfig config = dao().fetch(RecuperationConfig.class, Cnd.NEW());
Integer minimumGroupSize = config == null ? null : config.getOutsideQuota();
Map<String, List<NutMap>> batchGroups = exportRows.stream().collect(Collectors.groupingBy(
row -> StrUtil.blankToDefault(row.getString("takePartInLineId"), ""),
LinkedHashMap::new,
Collectors.toList()));
List<NutMap> formedRows = batchGroups.values().stream()
.filter(rows -> isFormedGroup(rows, minimumGroupSize))
.flatMap(List::stream)
.collect(Collectors.toList());
Map<String, List<NutMap>> lineGroups = formedRows.stream().collect(Collectors.groupingBy(
row -> StrUtil.blankToDefault(row.getString("lineId"), ""),
LinkedHashMap::new,
Collectors.toList()));
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment;filename="
+ URLEncoder.encode("疗休养集体线路报名人员名单.zip", StandardCharsets.UTF_8));
Set<String> zipEntryNames = new HashSet<>();
try (ZipOutputStream zipOutputStream = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream()))) {
for (List<NutMap> rows : lineGroups.values()) {
String lineName = rows.isEmpty() ? "未命名" : StrUtil.blankToDefault(rows.get(0).getString("lineName"), "未命名");
writeExcelToZip(zipOutputStream, zipEntryNames, lineName, rows);
}
zipOutputStream.flush();
}
response.flushBuffer();
}
/** 查询导出所需的审核通过线路报名人员,并按当前登录角色限制可见工会。 */
private List<NutMap> queryLineExportRows(Integer startYear, Integer endYear, String unionId, String lineId,
String lotId, String selectId, String regionalNature) {
Sql sql = Sqls.create("""
SELECT line.id AS lineId, line.lineName, line.regionalNature, agency.travelAgencyName,
enroll.*, DATE_FORMAT(lineu.playStartTime,'%Y-%m-%d') AS playStartTime,
IF(COALESCE((SELECT familyInfo FROM the_rapy_recuperation_config LIMIT 1),1)=2,
(SELECT COUNT(1) FROM the_rapy_recuperation_enroll_companion companion WHERE companion.trreId=enroll.id),
COALESCE(enroll.familyNumber,0)) AS familyCount,
(SELECT GROUP_CONCAT(
companion.userName,'-',companion.sex,'',companion.idcard,'、',companion.mobile,'-',
IFNULL(bed.bedType,''),',家属备注:',IFNULL(companion.remark,'')
SEPARATOR '')
FROM the_rapy_recuperation_enroll_companion companion
LEFT JOIN the_rapy_recuperation_enroll_bed bed ON bed.id=companion.bedInfoId
WHERE companion.trreId=enroll.id) AS companionInfo,
user_info.arrivalAtSchoolDate AS schoolTime
FROM the_rapy_recuperation_enroll enroll
LEFT JOIN sys_user user_info ON user_info.loginname=enroll.loginName
LEFT JOIN the_rapy_recuperation_line_union_select lineu ON lineu.id=enroll.takePartInLineId
LEFT JOIN the_rapy_recuperation_line line ON line.id=lineu.lineId
LEFT JOIN the_rapy_recuperation_travel_agency agency ON agency.id=lineu.travelAgencyId
$condition
""");
Cnd cnd = Cnd.where("enroll.stateId", "=", 2750)
.and("enroll.isNormal", "=", true)
.and("enroll.takePartInLineId", "is not", null)
.and("enroll.takePartInLineId", "!=", "")
.andEX("YEAR(enroll.signingUptime)", ">=", startYear)
.andEX("YEAR(enroll.signingUptime)", "<=", endYear)
.andEX("line.id", "=", lineId)
.andEX("line.lotId", "=", lotId)
.andEX("lineu.id", "=", selectId)
.andEX("line.regionalNature", "=", regionalNature);
if (AuthUtil.hasRoleOr("sysadmin", "H06", "A06")) {
cnd.andEX("enroll.selfUnionId", "=", unionId);
} else {
cnd.and(Cnd.exps("enroll.takePartInUnionId", "=", SecurityUtil.getUnionId())
.or("enroll.selfUnionId", "=", SecurityUtil.getUnionId()));
}
cnd.asc("line.lineName").asc("lineu.playStartTime").asc("enroll.unionName");
sql.setCondition(cnd);
List<NutMap> rows = listMap(sql);
rows.forEach(row -> row.setv("remark", RecuperationCommon.getRemarkBySchoolTime(row.getString("schoolTime"))));
return rows;
}
/** 最低成团人数同时计算去重后的教职工人数和家属人数。 */
private boolean isFormedGroup(List<NutMap> rows, Integer minimumGroupSize) {
if (minimumGroupSize == null || minimumGroupSize <= 0) {
return false;
}
long teacherCount = rows.stream().map(row -> row.getString("loginName"))
.filter(StrUtil::isNotBlank).distinct().count();
int familyCount = rows.stream().mapToInt(row -> row.getInt("familyCount", 0)).sum();
return teacherCount + familyCount >= minimumGroupSize;
}
/** 每条线路生成一个 XSSF 工作簿后写入 ZIP,避免工作簿内部流与外层 ZIP 冲突。 */
private void writeExcelToZip(ZipOutputStream zipOutputStream, Set<String> zipEntryNames,
String rawLineName, List<NutMap> rows) throws IOException {
String safeLineName = rawLineName.replaceAll("[\\\\/:*?\"<>|]", "_").replaceAll("[\\r\\n]", "").trim();
String entryName = "集体线路报名人员/" + StrUtil.blankToDefault(safeLineName, "未命名") + ".xlsx";
int index = 1;
while (!zipEntryNames.add(entryName)) {
entryName = "集体线路报名人员/" + StrUtil.blankToDefault(safeLineName, "未命名") + "-" + index++ + ".xlsx";
}
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
try (Workbook workbook = ExcelExportUtil.exportExcel(exportParams, lineExportColumns(), rows);
ByteArrayOutputStream excelOutputStream = new ByteArrayOutputStream()) {
workbook.write(excelOutputStream);
zipOutputStream.putNextEntry(new ZipEntry(entryName));
zipOutputStream.write(excelOutputStream.toByteArray());
zipOutputStream.closeEntry();
}
}
/** 旧版成团线路人员导出的列结构。 */
private List<ExcelExportEntity> lineExportColumns() {
List<ExcelExportEntity> columns = new ArrayList<>();
columns.add(new ExcelExportEntity("姓名", "userName", 20));
columns.add(new ExcelExportEntity("工号", "loginName", 20));
columns.add(new ExcelExportEntity("性别", "sex", 10));
columns.add(new ExcelExportEntity("所属单位", "unitName", 20));
columns.add(new ExcelExportEntity("所属工会", "unionName", 20));
columns.add(new ExcelExportEntity("身份证号", "idCard", 30));
columns.add(new ExcelExportEntity("手机号", "mobile", 15));
columns.add(new ExcelExportEntity("备注", "remark", 30));
ExcelExportEntity signingTime = new ExcelExportEntity("报名时间", "signingUptime", 20);
signingTime.setFormat("yyyy-MM-dd HH:mm:ss");
columns.add(signingTime);
columns.add(new ExcelExportEntity("出行时间", "playStartTime", 20));
columns.add(new ExcelExportEntity("线路名称", "lineName", 20));
columns.add(new ExcelExportEntity("旅行社", "travelAgencyName", 20));
columns.add(new ExcelExportEntity("家属人数", "familyCount", 12));
ExcelExportEntity companionInfo = new ExcelExportEntity("家属信息", "companionInfo", 50);
companionInfo.setWrap(true);
columns.add(companionInfo);
return columns;
}
}
@@ -380,6 +380,9 @@ public class RecuperationProvinceFlexibleGroupServiceImpl extends BaseServiceImp
@Override
public Pagination queryPage(PageForm pageForm, Integer startYear, Integer endYear, String groupName,
String travelAgencyId, String searchKeyword) {
if (StrUtil.isNotBlank(searchKeyword)) {
return queryPersonMatchedGroups(pageForm, startYear, endYear, groupName, travelAgencyId, searchKeyword);
}
Sql sql = Sqls.create("""
SELECT fg.id, fg.groupName, fg.year, ta.travelAgencyName,
COUNT(DISTINCT CASE WHEN enroll.groupLeaderUserId IS NOT NULL AND enroll.groupLeaderUserId <> ''
@@ -412,6 +415,53 @@ public class RecuperationProvinceFlexibleGroupServiceImpl extends BaseServiceImp
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
}
/** 姓名或工号查询时按匹配人员展开,并保留组团总团队数和总报名人数。 */
private Pagination queryPersonMatchedGroups(PageForm pageForm, Integer startYear, Integer endYear,
String groupName, String travelAgencyId, String searchKeyword) {
Sql sql = Sqls.create("""
SELECT fg.id, CONCAT(fg.id,'_',enroll.id) AS rowKey, fg.groupName, fg.year,
ta.travelAgencyName, enroll.userName AS signUserName, enroll.loginName AS signLoginName,
(SELECT COUNT(DISTINCT CASE
WHEN grouped.groupLeaderUserId IS NOT NULL AND grouped.groupLeaderUserId<>'' THEN grouped.groupLeaderUserId
WHEN grouped.groupLeaderLoginName IS NOT NULL AND grouped.groupLeaderLoginName<>'' THEN grouped.groupLeaderLoginName
END)
FROM the_rapy_recuperation_enroll grouped
WHERE grouped.takePartInTravelAgencyId=fg.travelAgencyId
AND YEAR(grouped.signingUptime)=fg.year
AND grouped.isNormal=true
AND grouped.stateId NOT IN ($auditFailStates)) AS groupCount,
(SELECT COUNT(DISTINCT grouped.loginName)
FROM the_rapy_recuperation_enroll grouped
WHERE grouped.takePartInTravelAgencyId=fg.travelAgencyId
AND YEAR(grouped.signingUptime)=fg.year
AND grouped.isNormal=true
AND grouped.stateId NOT IN ($auditFailStates)) AS signCount
FROM the_rapy_recuperation_province_flexible_group fg
LEFT JOIN the_rapy_recuperation_travel_agency ta ON ta.id=fg.travelAgencyId
LEFT JOIN the_rapy_recuperation_enroll enroll
ON enroll.takePartInTravelAgencyId=fg.travelAgencyId
AND YEAR(enroll.signingUptime)=fg.year
AND enroll.isNormal=true
AND enroll.stateId NOT IN ($auditFailStates)
$condition
""");
Cnd cnd = Cnd.where("ta.signUpTravelAgency", "=", true)
.andEX("fg.year", ">=", startYear)
.andEX("fg.year", "<=", endYear)
.andEX("fg.travelAgencyId", "=", travelAgencyId)
.and(Cnd.likeEX("fg.groupName", groupName))
.and(Cnd.exps("enroll.userName", "like", "%" + searchKeyword + "%")
.or("enroll.loginName", "like", "%" + searchKeyword + "%"));
if (!AuthUtil.hasRoleOr("sysadmin", "A06") && AuthUtil.hasRoleOr("H04")) {
cnd.and("fg.createUnionId", "=", SecurityUtil.getUnionId());
}
cnd.groupBy("fg.id", "enroll.id");
cnd.desc("fg.year").asc("fg.groupName").asc("enroll.userName");
sql.setVar("auditFailStates", auditFailStates());
sql.setCondition(cnd);
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
}
@Override
public List<NutMap> queryGroupInfo(String groupId) {
Sql sql = Sqls.create("""
@@ -462,11 +512,44 @@ public class RecuperationProvinceFlexibleGroupServiceImpl extends BaseServiceImp
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
}
@Override
public Pagination queryAllSignUsers(PageForm pageForm, String groupId) {
Sql sql = Sqls.create("""
SELECT enroll.id, enroll.loginName, enroll.userName,
CASE WHEN enroll.groupLeaderLoginName IS NOT NULL
AND enroll.groupLeaderLoginName<>''
AND enroll.loginName=enroll.groupLeaderLoginName
THEN '团长' ELSE '队员' END AS identity,
enroll.unionName, enroll.unitName, enroll.sex, enroll.mobile,
(SELECT COUNT(1) FROM the_rapy_recuperation_enroll_companion WHERE trreId=enroll.id) AS isFamily
FROM the_rapy_recuperation_enroll enroll
WHERE enroll.takePartInTravelAgencyId=(
SELECT travelAgencyId FROM the_rapy_recuperation_province_flexible_group WHERE id=@groupId)
AND YEAR(enroll.signingUptime)=(
SELECT year FROM the_rapy_recuperation_province_flexible_group WHERE id=@groupId)
AND enroll.isNormal=true
AND enroll.stateId NOT IN ($auditFailStates)
ORDER BY enroll.groupLeaderUserName,
CASE WHEN enroll.groupLeaderLoginName IS NOT NULL
AND enroll.groupLeaderLoginName<>''
AND enroll.loginName=enroll.groupLeaderLoginName
THEN 0 ELSE 1 END,
enroll.signingUptime
""");
sql.setParam("groupId", groupId).setVar("auditFailStates", auditFailStates());
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
}
/** 过滤浏览器可能传入的 undefined/null 文本,避免错误命中团长条件。 */
private boolean isValidGroupLeaderParam(String value) {
return StrUtil.isNotBlank(value) && !"undefined".equalsIgnoreCase(value) && !"null".equalsIgnoreCase(value);
}
/** 灵活组团查询排除分工会、线路工会和校工会审核失败状态。 */
private String auditFailStates() {
return RecuperationState.UNITFAIL + "," + RecuperationState.LINEUNITFAIL + "," + RecuperationState.SCHOOLFAIL;
}
@Override
public List<NutMap> queryTravelAgencyOptions(Integer startYear, Integer endYear) {
Sql sql = Sqls.create("""
@@ -498,24 +581,52 @@ public class RecuperationProvinceFlexibleGroupServiceImpl extends BaseServiceImp
Sql sql = Sqls.create("""
SELECT enroll.loginName, enroll.userName, enroll.mobile, enroll.idCard, enroll.unitName, enroll.unionName,
ta.travelAgencyName, DATE_FORMAT(enroll.signingUptime,'%Y-%m-%d %H:%i:%s') AS signingUptime,
CONCAT(IFNULL(enroll.groupLeaderUserName,''), IF(enroll.groupLeaderLoginName IS NULL OR enroll.groupLeaderLoginName='', '', CONCAT('',enroll.groupLeaderLoginName,''))) AS leaderName,
CASE WHEN (SELECT COUNT(DISTINCT sameLeader.loginName) FROM the_rapy_recuperation_enroll sameLeader
WHERE sameLeader.takePartInTravelAgencyId=enroll.takePartInTravelAgencyId
AND sameLeader.groupLeaderLoginName=enroll.groupLeaderLoginName AND sameLeader.isNormal=true
AND YEAR(sameLeader.signingUptime)=YEAR(enroll.signingUptime))>=3 THEN '已成团' ELSE '未成团' END AS formedTeamState
CASE
WHEN (enroll.groupLeaderUserName IS NULL OR enroll.groupLeaderUserName='')
AND (enroll.groupLeaderLoginName IS NULL OR enroll.groupLeaderLoginName='') THEN '未选择'
WHEN enroll.groupLeaderLoginName IS NULL OR enroll.groupLeaderLoginName='' THEN enroll.groupLeaderUserName
WHEN enroll.groupLeaderUserName IS NULL OR enroll.groupLeaderUserName='' THEN enroll.groupLeaderLoginName
ELSE CONCAT(enroll.groupLeaderUserName,'',enroll.groupLeaderLoginName,'')
END AS leaderName,
CASE WHEN (
SELECT COUNT(DISTINCT sameLeader.loginName)
FROM the_rapy_recuperation_enroll sameLeader
WHERE sameLeader.takePartInTravelAgencyId=enroll.takePartInTravelAgencyId
AND ((enroll.groupLeaderUserId IS NOT NULL AND enroll.groupLeaderUserId<>''
AND sameLeader.groupLeaderUserId=enroll.groupLeaderUserId)
OR (enroll.groupLeaderLoginName IS NOT NULL AND enroll.groupLeaderLoginName<>''
AND sameLeader.groupLeaderLoginName=enroll.groupLeaderLoginName))
AND YEAR(sameLeader.signingUptime)=YEAR(enroll.signingUptime)
AND sameLeader.isNormal=true
AND sameLeader.stateId NOT IN ($auditFailStates)
)>=3 THEN '已成团' ELSE '未成团' END AS formedTeamState
FROM the_rapy_recuperation_enroll enroll
LEFT JOIN the_rapy_recuperation_travel_agency ta ON ta.id=enroll.takePartInTravelAgencyId
LEFT JOIN the_rapy_recuperation_province_flexible_group fg ON fg.travelAgencyId=enroll.takePartInTravelAgencyId AND fg.year=YEAR(enroll.signingUptime)
LEFT JOIN the_rapy_recuperation_province_flexible_group fg ON fg.id=(
SELECT configured.id
FROM the_rapy_recuperation_province_flexible_group configured
WHERE configured.travelAgencyId=enroll.takePartInTravelAgencyId
AND configured.year=YEAR(enroll.signingUptime)
AND configured.isDisabled=false
ORDER BY configured.id
LIMIT 1)
$condition
""");
Cnd cnd = Cnd.where("fg.year", ">=", startYear).and("fg.year", "<=", endYear)
.and("enroll.isNormal", "=", true);
Cnd cnd = Cnd.where("ta.signUpTravelAgency", "=", true)
.and("fg.id", "is not", null)
.andEX("fg.year", ">=", startYear)
.andEX("fg.year", "<=", endYear)
.and("enroll.isNormal", "=", true)
.and("enroll.stateId", "not in", Lang.array(RecuperationState.UNITFAIL,
RecuperationState.LINEUNITFAIL, RecuperationState.SCHOOLFAIL));
cnd.andEX("fg.travelAgencyId", "=", travelAgencyId).and(Cnd.likeEX("fg.groupName", groupName));
if (StrUtil.isNotBlank(searchKeyword)) {
cnd.and(Cnd.exps("enroll.userName", "like", "%" + searchKeyword + "%").or("enroll.loginName", "like", "%" + searchKeyword + "%"));
}
if (!AuthUtil.hasRoleOr("sysadmin", "A06") && AuthUtil.hasRoleOr("H04")) cnd.and("fg.createUnionId", "=", SecurityUtil.getUnionId());
cnd.asc("fg.year").asc("ta.serialNumber * 1").asc("enroll.groupLeaderUserName").asc("enroll.signingUptime");
cnd.asc("ta.serialNumber * 1").asc("ta.travelAgencyName").asc("enroll.groupLeaderUserName")
.asc("enroll.groupLeaderLoginName").asc("fg.year").asc("enroll.signingUptime");
sql.setVar("auditFailStates", auditFailStates());
sql.setCondition(cnd);
List<NutMap> list = listMap(sql);
for (int i = 0; i < list.size(); i++) list.get(i).setv("no", i + 1);