diff --git a/src/main/java/io/v/nutz/web/commons/proc/NutShiroProcessor.java b/src/main/java/io/v/nutz/web/commons/proc/NutShiroProcessor.java index 8f902a3..2eaac3e 100644 --- a/src/main/java/io/v/nutz/web/commons/proc/NutShiroProcessor.java +++ b/src/main/java/io/v/nutz/web/commons/proc/NutShiroProcessor.java @@ -68,11 +68,11 @@ public class NutShiroProcessor extends AbstractProcessor { //不需要认证授权的url String[] authIgnoreUrlArr = Lang.array("/", "/sso/login", -// "/platform/login", + "/platform/login", "/platform/login(/doLogin|/logout|/captcha)", "/platform/qywechat/.*", "/platform/home/(500|403|404|UnknownAccountError|LockedAccountError)", -// "/mobile/login", + "/mobile/login", "/mobile/login/doLogin", "/platform/jsz/login", "/platform/activity/basic/scope/getScopeUser", diff --git a/src/main/java/io/v/nutz/zhgh/activity/controller/basic/ActivityBasicScopeController.java b/src/main/java/io/v/nutz/zhgh/activity/controller/basic/ActivityBasicScopeController.java index d1a570c..7b888ee 100644 --- a/src/main/java/io/v/nutz/zhgh/activity/controller/basic/ActivityBasicScopeController.java +++ b/src/main/java/io/v/nutz/zhgh/activity/controller/basic/ActivityBasicScopeController.java @@ -3,6 +3,7 @@ package io.v.nutz.zhgh.activity.controller.basic; import cn.afterturn.easypoi.excel.ExcelExportUtil; import cn.afterturn.easypoi.excel.entity.ExportParams; import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity; +import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; import cn.hutool.core.util.StrUtil; import cn.wizzer.framework.base.Result; import io.v.nutz.base.utils.ViTool; @@ -31,9 +32,12 @@ import org.nutz.json.Json; import org.nutz.lang.Lang; import org.nutz.lang.util.NutMap; import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.AdaptBy; import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.POST; import org.nutz.mvc.annotation.Param; +import org.nutz.mvc.upload.TempFile; +import org.nutz.mvc.upload.UploadAdaptor; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @@ -105,7 +109,8 @@ public class ActivityBasicScopeController { @Param(value = "clubId", required = false) String clubId, @Param(value = "reverseSelection") boolean reverseSelection, @Param(value = "activityGroupId", required = false) Integer activityGroupId, - @Param(value = "activityUserCnd", required = false) String activityUserCndStr) { + @Param(value = "activityUserCnd", required = false) String activityUserCndStr, + @Param(value = "existsLoginNameRedisKey", required = false) String existsLoginNameRedisKey) { Sql sql = Sqls.create(""" SELECT DISTINCT(u.id) as id, @@ -127,6 +132,7 @@ public class ActivityBasicScopeController { """); try { Cnd cnd = getCnd(pageForm, unionId, unitId, personTypes, userStates, memberTypes, sexTypes, age, teacherMeetingId, roleIds, userId, clubId, reverseSelection, activityGroupId, activityUserCndStr); + activityBasicScopeService.appendImportUserCnd(cnd, existsLoginNameRedisKey); sql.setCondition(cnd); @@ -169,7 +175,8 @@ public class ActivityBasicScopeController { @Param(value = "userId", required = false) String[] userId, @Param(value = "clubId", required = false) String clubId, @Param(value = "reverseSelection") boolean reverseSelection, - @Param(value = "activityUserCnd", required = false) String activityUserCndStr) { + @Param(value = "activityUserCnd", required = false) String activityUserCndStr, + @Param(value = "existsLoginNameRedisKey", required = false) String existsLoginNameRedisKey) { Sql sql = Sqls.create(""" SELECT DISTINCT ( u.id ) AS userId @@ -180,6 +187,7 @@ public class ActivityBasicScopeController { $condition """); Cnd cnd = getCnd(pageForm, unionId, unitId, personTypes, userStates, memberTypes, sexTypes, age, teacherMeetingId, roleIds, userId, clubId, reverseSelection, activityGroupId, activityUserCndStr); + activityBasicScopeService.appendImportUserCnd(cnd, existsLoginNameRedisKey); sql.setCondition(cnd); sql.setCallback(Sqls.callback.entities()); @@ -322,7 +330,7 @@ public class ActivityBasicScopeController { } @At - @ViReturn + @Ok("void") @RequiresPermissions("activity.basic.scope") public void doExportUser(PageForm pageForm, @Param(value = "props", required = false) String props, @@ -339,7 +347,9 @@ public class ActivityBasicScopeController { @Param(value = "clubId", required = false) String clubId, @Param(value = "reverseSelection") boolean reverseSelection, @Param(value = "activityGroupId", required = false) Integer activityGroupId, - @Param(value = "activityUserCnd", required = false) String activityUserCndStr, HttpServletResponse response) { + @Param(value = "activityUserCnd", required = false) String activityUserCndStr, + @Param(value = "existsLoginNameRedisKey", required = false) String existsLoginNameRedisKey, + HttpServletResponse response) { Sql sql = Sqls.create(""" SELECT @@ -363,6 +373,7 @@ public class ActivityBasicScopeController { try { Cnd cnd = getCnd(pageForm, unionId, unitId, personTypes, userStates, memberTypes, sexTypes, age, teacherMeetingId, roleIds, userId, clubId, reverseSelection, activityGroupId, activityUserCndStr); + activityBasicScopeService.appendImportUserCnd(cnd, existsLoginNameRedisKey); sql.setCondition(cnd); @@ -377,14 +388,50 @@ public class ActivityBasicScopeController { entityList.add(new ExcelExportEntity(v, k, 40)); }); - ViTool.excelResponse(response, "人员名单.xls"); - Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), entityList, map); + ViTool.excelResponse(response, "人员名单.xlsx"); + ExportParams exportParams = new ExportParams(); + exportParams.setType(ExcelType.XSSF); + Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entityList, map); workbook.write(response.getOutputStream()); } catch (IOException e) { e.printStackTrace(); } } + @At + @Ok("void") + @RequiresPermissions("activity.basic.scope") + public void downloadImport(HttpServletResponse response) { + try { + ViTool.excelResponse(response, "人员导入模板.xlsx"); + List entityList = new ArrayList<>(); + entityList.add(new ExcelExportEntity("工号", "loginname", 20)); + entityList.add(new ExcelExportEntity("姓名", "username", 20)); + ExportParams exportParams = new ExportParams(); + exportParams.setType(ExcelType.XSSF); + Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entityList, new ArrayList<>()); + workbook.write(response.getOutputStream()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @At + @ViReturn + @RequiresPermissions("activity.basic.scope") + public Object clearSearchCnd(String existsLoginNameRedisKey) { + activityBasicScopeService.clearImportUserCnd(existsLoginNameRedisKey); + return null; + } + + @At + @ViReturn + @AdaptBy(type = UploadAdaptor.class, args = {"ioc:fileUpload"}) + @RequiresPermissions("activity.basic.scope") + public Object doImport(TempFile file) { + return activityBasicScopeService.checkImportUser(file.getFile()); + } + private Cnd getCnd(PageForm pageForm, String unionId, diff --git a/src/main/java/io/v/nutz/zhgh/activity/controller/template/UserTemp.java b/src/main/java/io/v/nutz/zhgh/activity/controller/template/UserTemp.java new file mode 100644 index 0000000..2bf426a --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/activity/controller/template/UserTemp.java @@ -0,0 +1,18 @@ +package io.v.nutz.zhgh.activity.controller.template; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import lombok.Data; + +@Data +public class UserTemp { + + @Excel(name = "工号") + private String loginname; + + @Excel(name = "姓名") + private String username; + + @Excel(name = "备注") + private String remarks; + +} diff --git a/src/main/java/io/v/nutz/zhgh/activity/services/ActivityBasicScopeService.java b/src/main/java/io/v/nutz/zhgh/activity/services/ActivityBasicScopeService.java index 1ed5c2b..1e936f3 100644 --- a/src/main/java/io/v/nutz/zhgh/activity/services/ActivityBasicScopeService.java +++ b/src/main/java/io/v/nutz/zhgh/activity/services/ActivityBasicScopeService.java @@ -4,7 +4,9 @@ import io.v.nutz.zhgh.activity.models.ActivityUserScope; import io.v.nutz.base.service.ViService; import org.nutz.dao.Cnd; import org.nutz.dao.sql.Sql; +import org.nutz.lang.util.NutMap; +import java.io.File; import java.util.List; /** @@ -21,4 +23,27 @@ public interface ActivityBasicScopeService extends ViService */ void largeDataInsert(List list) ; + /** + * 解析人员导入模板,核对系统中存在的工号,并把匹配到的工号暂存为后续查询条件。 + * + * @param file 上传的人员导入 Excel 文件 + * @return 导入总数、成功数、错误数、错误明细和临时查询 key + */ + NutMap checkImportUser(File file); + + /** + * 将导入核对成功的工号追加到当前查询条件中。 + * + * @param cnd 查询条件 + * @param existsLoginNameRedisKey 导入核对成功工号的临时缓存 key + */ + void appendImportUserCnd(Cnd cnd, String existsLoginNameRedisKey); + + /** + * 清理导入人员临时查询条件。 + * + * @param existsLoginNameRedisKey 导入核对成功工号的临时缓存 key + */ + void clearImportUserCnd(String existsLoginNameRedisKey); + } diff --git a/src/main/java/io/v/nutz/zhgh/activity/services/impl/ActivityBasicScopeServiceImpl.java b/src/main/java/io/v/nutz/zhgh/activity/services/impl/ActivityBasicScopeServiceImpl.java index e364afc..93e10d2 100644 --- a/src/main/java/io/v/nutz/zhgh/activity/services/impl/ActivityBasicScopeServiceImpl.java +++ b/src/main/java/io/v/nutz/zhgh/activity/services/impl/ActivityBasicScopeServiceImpl.java @@ -1,24 +1,37 @@ package io.v.nutz.zhgh.activity.services.impl; +import cn.afterturn.easypoi.excel.ExcelImportUtil; +import cn.afterturn.easypoi.excel.entity.ImportParams; import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; import io.v.nutz.zhgh.activity.models.ActivityUserScope; import io.v.nutz.zhgh.activity.services.ActivityBasicScopeService; import io.v.nutz.base.utils.DateUtil; import io.v.nutz.base.service.impl.ViServiceImpl; +import io.v.nutz.zhgh.activity.controller.template.UserTemp; import lombok.extern.slf4j.Slf4j; import org.nutz.dao.Cnd; import org.nutz.dao.Dao; import org.nutz.dao.impl.NutTxDao; +import org.nutz.dao.Sqls; import org.nutz.dao.sql.Sql; +import org.nutz.integration.jedis.RedisService; +import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.Lang; +import org.nutz.lang.util.NutMap; +import java.io.File; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; /** * @author zxy @@ -28,6 +41,11 @@ import java.util.concurrent.atomic.AtomicInteger; @IocBean(args = {"refer:dao"}) @Slf4j public class ActivityBasicScopeServiceImpl extends ViServiceImpl implements ActivityBasicScopeService { + private static final String IMPORT_NO_MATCH_LOGIN_NAME = "__activity_scope_import_no_match__"; + + @Inject + private RedisService redisService; + public ActivityBasicScopeServiceImpl(Dao dao) { super(dao); } @@ -79,4 +97,109 @@ public class ActivityBasicScopeServiceImpl extends ViServiceImpl userImportList = ExcelImportUtil.importExcel(file, UserTemp.class, new ImportParams()); + if (userImportList == null) { + userImportList = new ArrayList<>(); + } + Set sysLoginNames = getSysLoginNames(); + List existsLoginNames = new ArrayList<>(); + + for (UserTemp excelUser : userImportList) { + if (excelUser == null || StrUtil.isBlank(excelUser.getLoginname())) { + if (excelUser != null) { + excelUser.setRemarks("工号不能为空"); + } + continue; + } + if (sysLoginNames.contains(excelUser.getLoginname())) { + existsLoginNames.add(excelUser.getLoginname()); + } else { + excelUser.setRemarks("系统查不到此人"); + } + } + + List errorExcelTempUsers = userImportList.stream() + .filter(v -> v != null && StrUtil.isNotBlank(v.getRemarks())) + .collect(Collectors.toList()); + + NutMap nutMap = NutMap.NEW(); + nutMap.setv("totalCount", userImportList.size()); + nutMap.setv("successCount", existsLoginNames.size()); + nutMap.setv("errorCount", errorExcelTempUsers.size()); + nutMap.setv("errorList", errorExcelTempUsers.stream().map(v -> NutMap.NEW() + .addv("工号", v.getLoginname()) + .addv("姓名", v.getUsername()) + .addv("错误原因", v.getRemarks())).collect(Collectors.toList())); + nutMap.setv("existsLoginNameRedisKey", matchUserLoginNamesKey); + + // 没有匹配人员时写入一个不可能存在的工号,避免后续查询误变成全量。 + String[] cacheLoginNames = Lang.isNotEmpty(existsLoginNames) + ? existsLoginNames.toArray(new String[0]) + : new String[]{IMPORT_NO_MATCH_LOGIN_NAME}; + redisService.lpush(matchUserLoginNamesKey, cacheLoginNames); + redisService.expire(matchUserLoginNamesKey, 60 * 60); + + return nutMap; + } + + /** + * 根据导入核对返回的 Redis key,给现有查询条件追加工号范围。 + * + * @param cnd 查询条件 + * @param existsLoginNameRedisKey 导入核对成功工号的临时缓存 key + */ + @Override + public void appendImportUserCnd(Cnd cnd, String existsLoginNameRedisKey) { + if (StrUtil.isBlank(existsLoginNameRedisKey)) { + return; + } + List loginNames = redisService.lrange(existsLoginNameRedisKey, 0, -1); + if (Lang.isEmpty(loginNames)) { + cnd.and("u.loginname", "=", IMPORT_NO_MATCH_LOGIN_NAME); + return; + } + cnd.andEX("u.loginname", "in", loginNames); + } + + /** + * 清理导入核对成功工号的临时缓存。 + * + * @param existsLoginNameRedisKey 导入核对成功工号的临时缓存 key + */ + @Override + public void clearImportUserCnd(String existsLoginNameRedisKey) { + if (StrUtil.isNotBlank(existsLoginNameRedisKey)) { + redisService.del(existsLoginNameRedisKey); + } + } + + private Set getSysLoginNames() { + Sql sql = Sqls.queryString(""" + SELECT + u.loginname + FROM + `user` u + """); + dao().execute(sql); + String[] sysLoginNames = (String[]) sql.getResult(); + Set loginNameSet = new HashSet<>(); + if (sysLoginNames != null) { + for (String loginName : sysLoginNames) { + if (StrUtil.isNotBlank(loginName)) { + loginNameSet.add(loginName); + } + } + } + return loginNameSet; + } } diff --git a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/baseManage/TheRapyRecuperationBaseManagerController.java b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/baseManage/TheRapyRecuperationBaseManagerController.java index f4d332e..14ed660 100644 --- a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/baseManage/TheRapyRecuperationBaseManagerController.java +++ b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/baseManage/TheRapyRecuperationBaseManagerController.java @@ -21,6 +21,7 @@ import io.v.nutz.base.utils.ViTool; import io.v.nutz.sys.models.SysHoliday; import io.v.nutz.sys.models.User; import io.v.nutz.web.commons.utils.ShiroUtil; +import io.v.nutz.zhgh.therapyRecuperation.constant.TheRapyRecuperationState; import io.v.nutz.zhgh.therapyRecuperation.model.*; import io.v.nutz.zhgh.therapyRecuperation.service.baseManage.TheRapyRecuperationBaseManagerService; import org.apache.poi.ss.usermodel.Workbook; @@ -117,6 +118,7 @@ public class TheRapyRecuperationBaseManagerController { tb.activityStartTime, tb.activityEndTime, tb.files, + tb.maxSignCount, tb.baseContactPerson, tb.baseContactNumber, tb.files as fileId, @@ -286,7 +288,11 @@ public class TheRapyRecuperationBaseManagerController { return Result.error("参数错误"); } - TheRapyRecuperationConfig config = dao.fetch(TheRapyRecuperationConfig.class, Cnd.NEW()); + // 手机端日历只使用最新提交配置,保持活动时间和页面展示一致。 + TheRapyRecuperationConfig config = dao.fetch(TheRapyRecuperationConfig.class, Cnd.where("latestConfig", "=", true)); + if (config == null) { + return Result.error("未找到最新疗休养配置,请联系管理员"); + } //获取线路对应的标段 TheRapyRecuperationBaseManagement management = dao.fetch(TheRapyRecuperationBaseManagement.class, id); //TheRapyRecuperationLot lot = dao.fetch(TheRapyRecuperationLot.class, management.getLotId()); @@ -306,6 +312,7 @@ public class TheRapyRecuperationBaseManagerController { }); List weeks = management.getWeekCheckIn(); + Set fullDays = getFullBaseManagementDays(management); // 解析日期 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); @@ -330,6 +337,10 @@ public class TheRapyRecuperationBaseManagerController { currentDate = currentDate.plusDays(1); continue; } + if (fullDays.contains(currentDateString)) { + currentDate = currentDate.plusDays(1); + continue; + } // 检查是否为需要排除的周几 int dayOfWeek = currentDate.getDayOfWeek().getValue() % 7; // 1是周一,7是周日 if (Lang.isNotEmpty(weeks) && !weeks.contains(dayOfWeek)) { @@ -343,11 +354,77 @@ public class TheRapyRecuperationBaseManagerController { return Map.of("days", resultDates, "holidays", holidays, + "fullDays", fullDays, "lotValue", 0, "minDate", DateUtil.formatDate(config.getFragmentPlayStartTime()), "maxDate", DateUtil.formatDate(config.getFragmentPlayEndTime())); } + /** + * 获取指定酒店报名已满的日期,手机端日历用来过滤不可选入住日。 + * + * @param management 酒店信息 + * @return 已达到每日最高报名人数的日期集合 + */ + private Set getFullBaseManagementDays(TheRapyRecuperationBaseManagement management) { + if (management == null || management.getMaxSignCount() == null || management.getMaxSignCount() <= 0) { + return Collections.emptySet(); + } + List enrolls = dao.query(TheRapyRecuperationEnroll.class, + Cnd.where("takePartInBaseManagementId", "=", management.getId()) + .and("specificTime", "is not", null) + .and("isNormal", "=", true) + .and("stateId", "in", Lang.array(TheRapyRecuperationState.UNIT, TheRapyRecuperationState.LINEUNIT, TheRapyRecuperationState.SCHOOL, TheRapyRecuperationState.PASS)) + .and("YEAR(signingUptime)", "=", DateUtil.thisYear())); + Map dayCountMap = new HashMap<>(); + for (TheRapyRecuperationEnroll enroll : enrolls) { + for (String day : parseSpecificTimeDays(enroll.getSpecificTime())) { + dayCountMap.put(day, dayCountMap.getOrDefault(day, 0) + 1); + } + } + return dayCountMap.entrySet().stream() + .filter(entry -> entry.getValue() >= management.getMaxSignCount()) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + } + + /** + * 把“06月01日-06月03日”格式的报名时间拆成逐日日期。 + * + * @param specificTime 报名时间段 + * @return 报名时间段覆盖的日期集合,格式 yyyy-MM-dd + */ + private List parseSpecificTimeDays(String specificTime) { + if (StrUtil.isBlank(specificTime) || !specificTime.contains("-")) { + return Collections.emptyList(); + } + String[] times = specificTime.split("-"); + if (times.length != 2 || StrUtil.isBlank(times[0]) || StrUtil.isBlank(times[1])) { + return Collections.emptyList(); + } + DateTimeFormatter sourceFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日"); + DateTimeFormatter targetFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + LocalDate start; + LocalDate end; + try { + start = LocalDate.parse(DateUtil.thisYear() + "年" + times[0].trim(), sourceFormatter); + end = LocalDate.parse(DateUtil.thisYear() + "年" + times[1].trim(), sourceFormatter); + } catch (Exception e) { + return Collections.emptyList(); + } + if (end.isBefore(start)) { + end = end.plusYears(1); + } + + List days = new ArrayList<>(); + LocalDate current = start; + while (!current.isAfter(end)) { + days.add(current.format(targetFormatter)); + current = current.plusDays(1); + } + return days; + } + /** * 目的地导入模版 diff --git a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/process/TheRapyRecuperationUserQueryController.java b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/process/TheRapyRecuperationUserQueryController.java index dc2ba00..34c7336 100644 --- a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/process/TheRapyRecuperationUserQueryController.java +++ b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/process/TheRapyRecuperationUserQueryController.java @@ -37,10 +37,12 @@ import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.upload.TempFile; import org.nutz.mvc.upload.UploadAdaptor; +import org.apache.commons.io.IOUtils; import org.springframework.util.CollectionUtils; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -183,6 +185,30 @@ public class TheRapyRecuperationUserQueryController { } + /** + * 下载参加人员导入模板 + * + * @param response 响应 + */ + @At + @Ok("void") + @RequiresAuthentication + public void downloadImportTemplate(HttpServletResponse response) { + String templatePath = "templates/travel/TravelEnrollImport.xlsx"; + try (InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(templatePath)) { + if (inputStream == null) { + response.sendError(HttpServletResponse.SC_NOT_FOUND, "模板文件不存在"); + return; + } + response.setContentType("application/octet-stream"); + response.setHeader("Content-Disposition", "attachment;filename=" + new String(("参加人员导入模板.xlsx").getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1)); + IOUtils.copy(inputStream, response.getOutputStream()); + } catch (Exception e) { + log.error(e.getMessage()); + } + } + + /** * 参加人员导入 * diff --git a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/query/TheRapyRecuperationSchoolUnionUserQueryController.java b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/query/TheRapyRecuperationSchoolUnionUserQueryController.java index b9b6122..b977f49 100644 --- a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/query/TheRapyRecuperationSchoolUnionUserQueryController.java +++ b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/query/TheRapyRecuperationSchoolUnionUserQueryController.java @@ -94,7 +94,10 @@ public class TheRapyRecuperationSchoolUnionUserQueryController { state.stateColor, line.regionalNature, line.lineName, - agency.travelAgencyName, + COALESCE(agency.travelAgencyName, lineAgency.travelAgencyName) AS travelAgencyName, + lot.lotName, + lot.activityCost, + CASE WHEN lineu.playStartTime IS NULL AND lineu.playEndTime IS NULL THEN '' ELSE CONCAT(IFNULL(DATE_FORMAT(lineu.playStartTime,'%Y-%m-%d'), ''), '至', IFNULL(DATE_FORMAT(lineu.playEndTime,'%Y-%m-%d'), '')) END AS travelTime, ma.baseName, enroll.*, lineu.lineId, @@ -107,6 +110,8 @@ public class TheRapyRecuperationSchoolUnionUserQueryController { 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 = enroll.takePartInTravelAgencyId + LEFT JOIN the_rapy_recuperation_travel_agency lineAgency ON lineAgency.id = line.travelAgencyId + LEFT JOIN the_rapy_recuperation_lot lot ON lot.id = line.lotId LEFT JOIN the_rapy_recuperation_base_management ma on ma.id = enroll.takePartInBaseManagementId LEFT JOIN audit_state state ON state.stateId = enroll.stateId $condition @@ -293,7 +298,10 @@ public class TheRapyRecuperationSchoolUnionUserQueryController { state.stateColor, line.regionalNature, line.lineName, - agency.travelAgencyName, + COALESCE(agency.travelAgencyName, lineAgency.travelAgencyName) AS travelAgencyName, + lot.lotName, + lot.activityCost, + CASE WHEN lineu.playStartTime IS NULL AND lineu.playEndTime IS NULL THEN '' ELSE CONCAT(IFNULL(DATE_FORMAT(lineu.playStartTime,'%Y-%m-%d'), ''), '至', IFNULL(DATE_FORMAT(lineu.playEndTime,'%Y-%m-%d'), '')) END AS travelTime, ma.baseName, enroll.*, lineu.lineId, @@ -306,6 +314,8 @@ public class TheRapyRecuperationSchoolUnionUserQueryController { 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 = enroll.takePartInTravelAgencyId + LEFT JOIN the_rapy_recuperation_travel_agency lineAgency ON lineAgency.id = line.travelAgencyId + LEFT JOIN the_rapy_recuperation_lot lot ON lot.id = line.lotId LEFT JOIN the_rapy_recuperation_base_management ma on ma.id = enroll.takePartInBaseManagementId LEFT JOIN audit_state state ON state.stateId = enroll.stateId $condition @@ -378,6 +388,12 @@ public class TheRapyRecuperationSchoolUnionUserQueryController { excelEntities.add(new ExcelExportEntity("工会", "unionName", 20)); excelEntities.add(new ExcelExportEntity("身份证号", "idCard", 30)); excelEntities.add(new ExcelExportEntity("手机号", "mobile", 20)); + // 导出线路关联信息,与列表查询使用同一批筛选条件,确保列表查到什么导出什么。 + excelEntities.add(new ExcelExportEntity("出行时间", "travelTime", 30)); + excelEntities.add(new ExcelExportEntity("标段", "lotName", 20)); + excelEntities.add(new ExcelExportEntity("标段费用", "activityCost", 20)); + excelEntities.add(new ExcelExportEntity("线路", "lineName", 30)); + excelEntities.add(new ExcelExportEntity("旅行社", "travelAgencyName", 30)); if (config.getFamilyInfo() == 2) { excelEntities.add(new ExcelExportEntity("与本人关系", "relation", 10)); excelEntities.add(new ExcelExportEntity("床型", "bedType", 10)); diff --git a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/theRapyConfig/TheRapyRecuperationConfigController.java b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/theRapyConfig/TheRapyRecuperationConfigController.java index c8580d1..cdccb65 100644 --- a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/theRapyConfig/TheRapyRecuperationConfigController.java +++ b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/controller/theRapyConfig/TheRapyRecuperationConfigController.java @@ -1,32 +1,14 @@ package io.v.nutz.zhgh.therapyRecuperation.controller.theRapyConfig; import io.v.nutz.base.annontation.ViReturn; -import io.v.nutz.base.model.msgUser; -import io.v.nutz.base.utils.MsgApi; -import io.v.nutz.zhgh.therapyRecuperation.model.TheRapyRecuperationBaseManagement; import io.v.nutz.zhgh.therapyRecuperation.model.TheRapyRecuperationConfig; -import io.v.nutz.zhgh.therapyRecuperation.model.TheRapyRecuperationLine; -import io.v.nutz.zhgh.therapyRecuperation.model.TheRapyRecuperationLot; +import io.v.nutz.zhgh.therapyRecuperation.service.TheRapyRecuperationConfigService; import org.apache.shiro.authz.annotation.RequiresAuthentication; import org.apache.shiro.authz.annotation.RequiresPermissions; -import org.nutz.aop.interceptor.ioc.TransAop; -import org.nutz.dao.Cnd; -import org.nutz.dao.Dao; -import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; -import org.nutz.lang.Lang; -import org.nutz.lang.Strings; import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.Ok; -import org.nutz.trans.Trans; -import org.springframework.util.CollectionUtils; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; /** * rapy休养配置控制器 @@ -41,9 +23,7 @@ public class TheRapyRecuperationConfigController { @Inject - private Dao dao; - @Inject - private MsgApi msgApi; + private TheRapyRecuperationConfigService configService; @At("") @@ -55,29 +35,23 @@ public class TheRapyRecuperationConfigController { @At @ViReturn @RequiresAuthentication - @Aop(TransAop.READ_COMMITTED) public Object operation(TheRapyRecuperationConfig config, String[] lotDeleteList) { - if (Strings.isNotBlank(config.getId())) { - if (Lang.isNotEmpty(lotDeleteList)) { - dao.clear(TheRapyRecuperationLot.class, Cnd.where("id", "in", lotDeleteList)); - } - dao.updateWith(config, "lots"); - List collect = config.getLots().stream().filter(v -> v.getConfigId() == null).collect(Collectors.toList()); - for (TheRapyRecuperationLot obj : collect) { - obj.setConfigId(config.getId()); - } - dao.insert(collect); - } else { - dao.insertWith(config, "lots"); - } + configService.saveConfig(config, lotDeleteList); return null; } @At @ViReturn @RequiresAuthentication - public Object findOne() { - return dao.fetchLinks(dao.fetch(TheRapyRecuperationConfig.class), "lots",Cnd.NEW().desc("lotValue")); + public Object findOne(Integer year) { + return configService.findByYear(year); + } + + @At + @ViReturn + @RequiresAuthentication + public Object copyLastYear(Integer year) { + return configService.copyLastYear(year); } @@ -90,20 +64,7 @@ public class TheRapyRecuperationConfigController { @ViReturn @RequiresAuthentication public Object getLotsById(String lotId){ - List lineNames = new ArrayList<>(); - List baseNames = new ArrayList<>(); - Map> map = new HashMap<>(); - List lineList = dao.query(TheRapyRecuperationLine.class, Cnd.where("lotId", "=", lotId)); - if (!CollectionUtils.isEmpty(lineList)){ - lineList.forEach(v-> lineNames.add(v.getLineName())); - map.put("line",lineNames); - } - List managementList = dao.query(TheRapyRecuperationBaseManagement.class, Cnd.where("lotId", "=", lotId)); - if (!CollectionUtils.isEmpty(managementList)){ - managementList.forEach(v-> baseNames.add(v.getBaseName())); - map.put("base",baseNames); - } - return map; + return configService.getLotsById(lotId); } @@ -116,19 +77,7 @@ public class TheRapyRecuperationConfigController { @ViReturn @RequiresAuthentication public Object deleteLotById(String lotId){ - Trans.exec(()->{ - dao.clear(TheRapyRecuperationLot.class, Cnd.where("id","=",lotId)); - List lineList = dao.query(TheRapyRecuperationLine.class, Cnd.where("lotId", "=", lotId)); - if (!CollectionUtils.isEmpty(lineList)) { - lineList.forEach(v -> v.setLotId(null)); - dao.update(lineList); - } - List managementList = dao.query(TheRapyRecuperationBaseManagement.class, Cnd.where("lotId", "=", lotId)); - if (!CollectionUtils.isEmpty(managementList)) { - managementList.forEach(v -> v.setLotId(null)); - dao.update(managementList); - } - }); + configService.deleteLotById(lotId); return null; } } diff --git a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/model/TheRapyRecuperationBaseManagement.java b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/model/TheRapyRecuperationBaseManagement.java index e7994cd..c71acaf 100644 --- a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/model/TheRapyRecuperationBaseManagement.java +++ b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/model/TheRapyRecuperationBaseManagement.java @@ -165,6 +165,12 @@ public class TheRapyRecuperationBaseManagement { @Comment("天数") private List allowDay; + @Column + @ColDefine(type = ColType.INT) + @Comment("同一酒店每日最高报名人数") + @Excel(name = "同一酒店每日最高报名人数") + private Integer maxSignCount; + @Column @ColDefine(type = ColType.VARCHAR) @Comment("旅行社地点") diff --git a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/model/TheRapyRecuperationConfig.java b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/model/TheRapyRecuperationConfig.java index 1016d48..654f6e6 100644 --- a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/model/TheRapyRecuperationConfig.java +++ b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/model/TheRapyRecuperationConfig.java @@ -26,6 +26,16 @@ public class TheRapyRecuperationConfig { @Comment("配置名称") private String configName; + @Column + @ColDefine(type = ColType.INT) + @Comment("配置年度") + private Integer configYear; + + @Column + @ColDefine(type = ColType.BOOLEAN) + @Comment("是否最新提交配置") + private Boolean latestConfig; + @Column @ColDefine(type = ColType.INT, width = 32) @Comment("参加人员范围") diff --git a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/service/TheRapyRecuperationConfigService.java b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/service/TheRapyRecuperationConfigService.java new file mode 100644 index 0000000..6d59c27 --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/service/TheRapyRecuperationConfigService.java @@ -0,0 +1,50 @@ +package io.v.nutz.zhgh.therapyRecuperation.service; + +import io.v.nutz.base.service.ViService; +import io.v.nutz.zhgh.therapyRecuperation.model.TheRapyRecuperationConfig; +import org.nutz.lang.util.NutMap; + +/** + * 疗休养基础配置服务。 + */ +public interface TheRapyRecuperationConfigService extends ViService { + + /** + * 按年度查询基础配置;年度为空时使用当前年度。 + * + * @param configYear 配置年度 + * @return 年度配置 + */ + TheRapyRecuperationConfig findByYear(Integer configYear); + + /** + * 保存年度基础配置,已存在配置时更新,不存在时新增。 + * + * @param config 基础配置 + * @param lotDeleteList 需要删除的标段id + */ + void saveConfig(TheRapyRecuperationConfig config, String[] lotDeleteList); + + /** + * 将上一年度配置复制为当前年度配置。 + * + * @param configYear 当前配置年度 + * @return 复制后的当前年度配置 + */ + TheRapyRecuperationConfig copyLastYear(Integer configYear); + + /** + * 查询使用该标段的线路和目的地。 + * + * @param lotId 标段id + * @return 使用情况 + */ + NutMap getLotsById(String lotId); + + /** + * 强制删除标段,并清空已绑定线路和目的地上的标段。 + * + * @param lotId 标段id + */ + void deleteLotById(String lotId); +} diff --git a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/service/impl/TheRapyRecuperationConfigServiceImpl.java b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/service/impl/TheRapyRecuperationConfigServiceImpl.java new file mode 100644 index 0000000..b820815 --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/service/impl/TheRapyRecuperationConfigServiceImpl.java @@ -0,0 +1,236 @@ +package io.v.nutz.zhgh.therapyRecuperation.service.impl; + +import cn.hutool.core.date.DateTime; +import io.v.nutz.base.service.impl.ViServiceImpl; +import io.v.nutz.zhgh.therapyRecuperation.model.TheRapyRecuperationBaseManagement; +import io.v.nutz.zhgh.therapyRecuperation.model.TheRapyRecuperationConfig; +import io.v.nutz.zhgh.therapyRecuperation.model.TheRapyRecuperationLine; +import io.v.nutz.zhgh.therapyRecuperation.model.TheRapyRecuperationLot; +import io.v.nutz.zhgh.therapyRecuperation.service.TheRapyRecuperationConfigService; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.dao.Chain; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.ioc.aop.Aop; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.Lang; +import org.nutz.lang.Strings; +import org.nutz.lang.util.NutMap; +import org.nutz.trans.Trans; +import org.springframework.util.CollectionUtils; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.temporal.ChronoField; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 疗休养基础配置服务实现。 + */ +@IocBean(args = {"refer:dao"}) +public class TheRapyRecuperationConfigServiceImpl extends ViServiceImpl implements TheRapyRecuperationConfigService { + + private static final int DEFAULT_CONFIG_YEAR = 2025; + + public TheRapyRecuperationConfigServiceImpl(Dao dao) { + super(dao); + } + + /** + * 年度为空时统一使用 2025 年,兼容原有无年度的基础配置。 + * + * @param configYear 配置年度 + * @return 有效年度 + */ + private Integer getConfigYear(Integer configYear) { + return configYear == null ? DEFAULT_CONFIG_YEAR : configYear; + } + + @Override + public TheRapyRecuperationConfig findByYear(Integer configYear) { + if (configYear == null) { + return findLatestConfig(); + } + Integer year = getConfigYear(configYear); + TheRapyRecuperationConfig config = fetch(Cnd.where("configYear", "=", year)); + if (config == null && year == DEFAULT_CONFIG_YEAR) { + config = fetch(Cnd.where("configYear", "is", null)); + } + if (config == null) { + return null; + } + return dao().fetchLinks(config, "lots", Cnd.NEW().desc("lotValue")); + } + + /** + * 其他业务页面不传年度时,只使用最新提交的基础配置。 + * + * @return 最新基础配置 + */ + private TheRapyRecuperationConfig findLatestConfig() { + TheRapyRecuperationConfig config = fetch(Cnd.where("latestConfig", "=", true)); + if (config == null) { + return null; + } + return dao().fetchLinks(config, "lots", Cnd.NEW().desc("lotValue")); + } + + @Override + @Aop(TransAop.READ_COMMITTED) + public void saveConfig(TheRapyRecuperationConfig config, String[] lotDeleteList) { + Integer year = getConfigYear(config.getConfigYear()); + config.setConfigYear(year); + config.setLatestConfig(true); + TheRapyRecuperationConfig oldConfig = findByYear(year); + if (Strings.isBlank(config.getId()) && oldConfig != null) { + config.setId(oldConfig.getId()); + } + resetLatestConfig(config.getId()); + if (Strings.isNotBlank(config.getId())) { + if (Lang.isNotEmpty(lotDeleteList)) { + dao().clear(TheRapyRecuperationLot.class, Cnd.where("id", "in", lotDeleteList)); + } + dao().updateWith(config, "lots"); + List lots = config.getLots() == null ? new ArrayList<>() : config.getLots(); + List collect = lots.stream().filter(v -> v.getConfigId() == null).collect(Collectors.toList()); + for (TheRapyRecuperationLot obj : collect) { + obj.setConfigId(config.getId()); + } + if (Lang.isNotEmpty(collect)) { + dao().insert(collect); + } + } else { + dao().insertWith(config, "lots"); + } + } + + /** + * 每次提交年度配置后,仅保留当前配置为最新配置。 + * + * @param currentConfigId 当前配置id + */ + private void resetLatestConfig(String currentConfigId) { + Cnd cnd = Cnd.NEW(); + if (Strings.isNotBlank(currentConfigId)) { + cnd.and("id", "!=", currentConfigId); + } + dao().update(TheRapyRecuperationConfig.class, Chain.make("latestConfig", false), cnd); + } + + @Override + @Aop(TransAop.READ_COMMITTED) + public TheRapyRecuperationConfig copyLastYear(Integer configYear) { + Integer year = getConfigYear(configYear); + TheRapyRecuperationConfig currentConfig = findByYear(year); + TheRapyRecuperationConfig lastConfig = findByYear(year - 1); + if (lastConfig == null) { + return null; + } + lastConfig.setId(currentConfig == null ? null : currentConfig.getId()); + lastConfig.setConfigYear(year); + lastConfig.setLatestConfig(true); + resetConfigTimeYear(lastConfig, year); + resetLatestConfig(null); + if (currentConfig == null) { + copyLotsToNewConfig(lastConfig); + dao().insertWith(lastConfig, "lots"); + } else { + dao().clear(TheRapyRecuperationLot.class, Cnd.where("configId", "=", currentConfig.getId())); + copyLotsToNewConfig(lastConfig); + dao().update(lastConfig); + for (TheRapyRecuperationLot lot : lastConfig.getLots()) { + lot.setConfigId(currentConfig.getId()); + } + if (Lang.isNotEmpty(lastConfig.getLots())) { + dao().insert(lastConfig.getLots()); + } + } + return findByYear(year); + } + + /** + * 沿用上一年度配置时,将所有年度相关时间改为当前配置年度,月日和时分秒保持不变。 + * + * @param config 待复制配置 + * @param year 当前配置年度 + */ + private void resetConfigTimeYear(TheRapyRecuperationConfig config, Integer year) { + config.setCreateLineStartTime(resetDateTimeYear(config.getCreateLineStartTime(), year)); + config.setCreateLineEndTime(resetDateTimeYear(config.getCreateLineEndTime(), year)); + config.setImplodeStartTime(resetDateTimeYear(config.getImplodeStartTime(), year)); + config.setImplodeEndTime(resetDateTimeYear(config.getImplodeEndTime(), year)); + config.setFragmentStartTime(resetDateTimeYear(config.getFragmentStartTime(), year)); + config.setFragmentEndTime(resetDateTimeYear(config.getFragmentEndTime(), year)); + config.setFragmentPlayStartTime(resetDateTimeYear(config.getFragmentPlayStartTime(), year)); + config.setFragmentPlayEndTime(resetDateTimeYear(config.getFragmentPlayEndTime(), year)); + } + + /** + * 替换时间年份;闰年 2 月 29 日复制到平年时,自动落到该年 2 月最后一天。 + * + * @param dateTime 原时间 + * @param year 目标年度 + * @return 替换年份后的时间 + */ + private DateTime resetDateTimeYear(DateTime dateTime, Integer year) { + if (dateTime == null || year == null) { + return dateTime; + } + LocalDateTime sourceTime = LocalDateTime.ofInstant(dateTime.toInstant(), ZoneId.systemDefault()); + int dayOfMonth = (int) Math.min(sourceTime.getDayOfMonth(), sourceTime.withYear(year).range(ChronoField.DAY_OF_MONTH).getMaximum()); + LocalDateTime targetTime = sourceTime.withYear(year).withDayOfMonth(dayOfMonth); + return new DateTime(java.util.Date.from(targetTime.atZone(ZoneId.systemDefault()).toInstant())); + } + + /** + * 复制上一年度标段时清空主键和配置外键,让数据库生成当前年度独立数据。 + * + * @param config 待复制配置 + */ + private void copyLotsToNewConfig(TheRapyRecuperationConfig config) { + if (config.getLots() == null) { + return; + } + for (TheRapyRecuperationLot lot : config.getLots()) { + lot.setId(null); + lot.setConfigId(null); + } + } + + @Override + public NutMap getLotsById(String lotId) { + List lineNames = new ArrayList<>(); + List baseNames = new ArrayList<>(); + NutMap map = new NutMap(); + List lineList = dao().query(TheRapyRecuperationLine.class, Cnd.where("lotId", "=", lotId)); + if (!CollectionUtils.isEmpty(lineList)) { + lineList.forEach(v -> lineNames.add(v.getLineName())); + map.put("line", lineNames); + } + List managementList = dao().query(TheRapyRecuperationBaseManagement.class, Cnd.where("lotId", "=", lotId)); + if (!CollectionUtils.isEmpty(managementList)) { + managementList.forEach(v -> baseNames.add(v.getBaseName())); + map.put("base", baseNames); + } + return map; + } + + @Override + public void deleteLotById(String lotId) { + Trans.exec(() -> { + dao().clear(TheRapyRecuperationLot.class, Cnd.where("id", "=", lotId)); + List lineList = dao().query(TheRapyRecuperationLine.class, Cnd.where("lotId", "=", lotId)); + if (!CollectionUtils.isEmpty(lineList)) { + lineList.forEach(v -> v.setLotId(null)); + dao().update(lineList); + } + List managementList = dao().query(TheRapyRecuperationBaseManagement.class, Cnd.where("lotId", "=", lotId)); + if (!CollectionUtils.isEmpty(managementList)) { + managementList.forEach(v -> v.setLotId(null)); + dao().update(managementList); + } + }); + } +} diff --git a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/service/impl/TheRapyRecuperationEnrollServiceImpl.java b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/service/impl/TheRapyRecuperationEnrollServiceImpl.java index 5c490c2..caec088 100644 --- a/src/main/java/io/v/nutz/zhgh/therapyRecuperation/service/impl/TheRapyRecuperationEnrollServiceImpl.java +++ b/src/main/java/io/v/nutz/zhgh/therapyRecuperation/service/impl/TheRapyRecuperationEnrollServiceImpl.java @@ -35,6 +35,7 @@ import org.nutz.lang.util.NutMap; import java.time.LocalDate; import java.time.Period; +import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; @@ -543,8 +544,11 @@ public class TheRapyRecuperationEnrollServiceImpl extends ViServiceImpl validSignUpInfo(String loginName, TheRapyRecuperationEnroll enrollInfo) { - //配置信息 - TheRapyRecuperationConfig config = dao().fetch(TheRapyRecuperationConfig.class); + // 配置信息只取当前最新配置,避免报名校验和页面展示使用不同年度配置。 + TheRapyRecuperationConfig config = fetchLatestConfig(); + if (config == null) { + return Map.of(false, "未找到最新疗休养配置,请联系管理员"); + } if (StrUtil.isNotBlank(loginName) && loginName.startsWith("2025")) { return Map.of(false, "抱歉,您是今年新入职,不能报名!"); @@ -710,7 +714,10 @@ public class TheRapyRecuperationEnrollServiceImpl extends ViServiceImpl validSignTime(TheRapyRecuperationEnroll currentEnroll) { - TheRapyRecuperationConfig config = dao().fetch(TheRapyRecuperationConfig.class, Cnd.NEW()); + TheRapyRecuperationConfig config = fetchLatestConfig(); + if (config == null) { + return Map.of(false, "未找到最新疗休养配置,请联系管理员"); + } DateTime signUpStartTime = null; DateTime signUpEndTime = null; if (StrUtil.isNotBlank(currentEnroll.getTakePartInLineId())) { @@ -729,7 +736,21 @@ public class TheRapyRecuperationEnrollServiceImpl extends ViServiceImpl days = new ArrayList<>(); + LocalDate current = start; + while (!current.isAfter(end)) { + days.add(current); + current = current.plusDays(1); + } + return days; + } + //判断一年报几次 public Map validCountThisYear(String loginName, int travelFrequency, Boolean validHotel) { Cnd cnd = Cnd.NEW(); diff --git a/src/main/resources/static/components/plugins/UserScope.vue b/src/main/resources/static/components/plugins/UserScope.vue index 91fc574..a024c3a 100644 --- a/src/main/resources/static/components/plugins/UserScope.vue +++ b/src/main/resources/static/components/plugins/UserScope.vue @@ -279,6 +279,10 @@ @@ -405,6 +460,7 @@ module.exports = { roleIds: [], userId: [], activityUserCnd: '', + existsLoginNameRedisKey: '', reverseSelection: false }, activityUnions: [], @@ -434,7 +490,18 @@ module.exports = { setGroupName: [{required: true, message: '请输入分组名称', trigger: ['blur', 'change']}] }, relatedSessionMenus: ['aca4d14498c145ceb5b24ed70776aae2', '733d7266652740a3aeac9da97ab5eeca', 'fe2d0768e26d4a80beeb159306bb8d01'], - roleData: {} + roleData: {}, + dialogVisible: false, + importLoading: false, + importData: { + fileList: [], + }, + errorInfoData: { + totalCount: 0, + successCount: 0, + errorCount: 0, + errorList: [], + }, } }, computed: { @@ -461,6 +528,88 @@ module.exports = { 'user-cnd': httpVueLoader('/components/plugins/UserCnd.vue'), }, methods: { + exportErrors() { + const data = this.errorInfoData.errorList + const workbook = XLSX.utils.book_new() + const worksheet = XLSX.utils.json_to_sheet(data) + XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1') + const excelBuffer = XLSX.write(workbook, {bookType: 'xlsx', type: 'array'}) + const blob = new Blob([excelBuffer], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}) + const url = window.URL.createObjectURL(blob) + const link = document.createElement('a') + link.href = url + link.download = '核对记录.xlsx' + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + window.URL.revokeObjectURL(url) + }, + doImportSearch() { + this.doSearch() + this.dialogVisible = false + }, + doImport() { + if (this.importData.fileList.length === 0) { + this.notifyWarning("请选择文件!") + return + } + const data = new FormData() + this.importData.fileList.forEach((val) => { + data.append("file", val.raw, val.raw.name) + }) + + this.importLoading = true + $.ajax({ + url: "/platform/activity/basic/scope/doImport", + type: "post", + data: data, + processData: false, + contentType: false, + success: (data) => { + this.importLoading = false + if (data.code === 0) { + this.notifySuccess("核对成功") + this.errorInfoData = data.data + this.$set(this.pageForm, "existsLoginNameRedisKey", data.data.existsLoginNameRedisKey) + } else { + this.notifyWarning("核对失败") + } + }, + error: () => { + this.notifyWarning("导入失败") + this.importLoading = false + } + }) + }, + clearSearchCnd() { + const existsLoginNameRedisKey = this.pageForm.existsLoginNameRedisKey + this.$set(this.importData, "fileList", []) + this.errorInfoData = { + totalCount: 0, + successCount: 0, + errorCount: 0, + errorList: [], + } + this.$set(this.pageForm, "existsLoginNameRedisKey", "") + this.doSearch() + this.dialogVisible = false + $.get('/platform/activity/basic/scope/clearSearchCnd', {existsLoginNameRedisKey: existsLoginNameRedisKey}).then(res => { + if (res.code === 0) { + this.$message.success(res.msg) + } else { + this.$message.error(res.msg) + } + }) + }, + openImport() { + this.dialogVisible = true + }, + handleImportFileRemove(file, fileList) { + this.$set(this.importData, "fileList", this.fileHandleRemove(file, fileList)) + }, + handleImportFileChange(file, fileList) { + this.$set(this.importData, "fileList", this.fileHandleChange(file, fileList, {type: ['xls', 'xlsx']})) + }, doExportUser() { const { userId, @@ -476,7 +625,8 @@ module.exports = { activityGroupId, age, reverseSelection, - activityUserCnd + activityUserCnd, + existsLoginNameRedisKey } = this.pageForm let props = {} @@ -495,6 +645,7 @@ module.exports = { "&reverseSelection=" + reverseSelection + "&clubId=" + clubId + "&activityGroupId=" + activityGroupId + + "&existsLoginNameRedisKey=" + existsLoginNameRedisKey + "&teacherMeetingId=" + teacherMeetingId + "&unionId=" + unionId + "&unitId=" + unitId @@ -514,6 +665,7 @@ module.exports = { this.pageForm.clubId = '' this.pageForm.activityGroupId = '' this.pageForm.activityUserCnd = '' + this.pageForm.existsLoginNameRedisKey = '' this.pageForm.age = [0, 0] this.pageForm.reverseSelection = false this.doSearch() diff --git a/src/main/resources/templates/travel/TravelEnrollImport.xlsx b/src/main/resources/templates/travel/TravelEnrollImport.xlsx new file mode 100644 index 0000000..665e586 Binary files /dev/null and b/src/main/resources/templates/travel/TravelEnrollImport.xlsx differ diff --git a/src/main/resources/views/mobile/therapyRecuperation/baseManagement.html b/src/main/resources/views/mobile/therapyRecuperation/baseManagement.html index 2c72a2f..2dde7af 100644 --- a/src/main/resources/views/mobile/therapyRecuperation/baseManagement.html +++ b/src/main/resources/views/mobile/therapyRecuperation/baseManagement.html @@ -527,6 +527,7 @@ layout("/mobile/platform.html"){ minDate: new Date(2010, 0, 1), maxDate: new Date(2010, 0, 31), holidays: [], + fullDays: [], timeVisible: false, active: 0, tarBarActive: 0, @@ -623,6 +624,12 @@ layout("/mobile/platform.html"){ this.$refs.cal.reset(null) break } + if(this.fullDays.includes(afterDateStr)) { + this.$toast(afterDateStr + '报名人数已满,请更换出行时间') + this.chooseDate = [] + this.$refs.cal.reset(null) + break + } this.chooseDate.push(afterDateStr) if(i === (this.lotValue - 1)) { @@ -639,6 +646,10 @@ layout("/mobile/platform.html"){ if(this.timeArray.indexOf(date) === -1) { day.type = 'disabled' } + if(this.fullDays.includes(date)) { + day.type = 'disabled' + day.bottomInfo = '已满' + } if(this.chooseDate.length > 0 && this.chooseDate.includes(date)) { day.className = 'cal_middle' if(date === this.chooseDate[this.chooseDate.length - 1]) { @@ -801,6 +812,7 @@ layout("/mobile/platform.html"){ }) this.timeArray = res.data.days this.holidays = res.data.holidays + this.$set(this, 'fullDays', res.data.fullDays || []) //this.lotValue = Number(res.data.lotValue) this.minDate = new Date(res.data.minDate) this.maxDate = new Date(res.data.maxDate) diff --git a/src/main/resources/views/platform/theRapyRecuperation/baseManage/baseManagement.html b/src/main/resources/views/platform/theRapyRecuperation/baseManage/baseManagement.html index 1fadd06..00fe430 100644 --- a/src/main/resources/views/platform/theRapyRecuperation/baseManage/baseManagement.html +++ b/src/main/resources/views/platform/theRapyRecuperation/baseManage/baseManagement.html @@ -96,6 +96,9 @@ layout("/layouts/platform.html"){ {{item.lotName}} + @@ -202,7 +205,7 @@ layout("/layouts/platform.html"){ - + 省内 @@ -210,7 +213,16 @@ layout("/layouts/platform.html"){ - + + + + + + + + + 不限 @@ -508,6 +520,7 @@ layout("/layouts/platform.html"){ baseContactNumber: '', weekIn: 1, allowDay: [], + maxSignCount: 0, travelAgencyPlace: '', }, tableColumns: [ @@ -515,6 +528,7 @@ layout("/layouts/platform.html"){ {label: '目的地名称', prop: 'baseName', sortable: true}, {label: '承担旅行社', prop: 'travelAgencyName', sortable: true}, //{label: '标段时间', prop: 'lotId', sortable: true}, + {label: '每日最高人数', prop: 'maxSignCount', sortable: true}, {label: '联系人', prop: 'baseContactPerson', sortable: true}, {label: '联系人电话', prop: 'baseContactNumber', sortable: true}, //{label: '活动时间', prop: 'activityTime', sortable: true}, @@ -543,6 +557,7 @@ layout("/layouts/platform.html"){ activityEndTime: [{required: true, validator: validateActivityEndTime, trigger: ['change', 'blur']}], estimatedCost: [{required: true, message: '请输入预计费用', trigger: ['change', 'blur']}], allowDay: [{required: true, message: '请选择天数', trigger: ['change', 'blur']}], + maxSignCount: [{required: false, message: '请输入每日最高人数', trigger: ['change', 'blur']}], travelAgencyPlace: [{required: true, message: '请选择旅行社地点', trigger: ['change', 'blur']}], }, //目的地导入 diff --git a/src/main/resources/views/platform/theRapyRecuperation/schoolUnionUserQuery/index.html b/src/main/resources/views/platform/theRapyRecuperation/schoolUnionUserQuery/index.html index 4e6c154..84d93b7 100644 --- a/src/main/resources/views/platform/theRapyRecuperation/schoolUnionUserQuery/index.html +++ b/src/main/resources/views/platform/theRapyRecuperation/schoolUnionUserQuery/index.html @@ -171,6 +171,8 @@ layout("/layouts/platform.html"){