From 6e246854fb88c29b3c6a216c5a23d0b5ffd24b4c Mon Sep 17 00:00:00 2001 From: zhouhefeng Date: Fri, 24 Apr 2026 16:39:05 +0800 Subject: [PATCH] Changes --- .../FlowSchoolUnionActivityAdminHandler.java | 44 + .../controller/SiteCugApplyController.java | 580 +++++++- .../controller/SiteCugManageController.java | 130 +- .../controller/SiteCugMineController.java | 2 +- .../SiteCugSchoolUnionAuditController.java | 34 +- .../siteCug/model/SiteCugApply.java | 2 + .../siteCug/model/SiteCugInfo.java | 28 +- .../service/impl/SiteCugApplyServiceImpl.java | 2 +- .../dayofficework/siteCug/apply/index.html | 465 +++++- .../dayofficework/siteCug/manage/basicForm.js | 603 ++++++-- .../dayofficework/siteCug/manage/index.html | 30 +- .../zhgh/dayofficework/siteCug/manage/info.js | 120 +- .../dayofficework/siteCug/mine/index.html | 164 ++- .../dayofficework/siteCug/record/index.html | 248 +++- .../siteCug/schoolUnionAudit/index.html | 249 +++- .../dayofficework/siteCug/apply/form/apply.js | 1250 +++++++++-------- .../siteCug/apply/form/index.html | 564 +++++++- .../dayofficework/siteCug/apply/index.html | 396 +++++- .../dayofficework/siteCug/common/applyInfo.js | 4 +- .../dayofficework/siteCug/common/siteInfo.js | 49 +- .../dayofficework/siteCug/mine/index.html | 7 +- 21 files changed, 3931 insertions(+), 1040 deletions(-) create mode 100644 src/main/java/com/budwk/app/flow/handler/FlowSchoolUnionActivityAdminHandler.java diff --git a/src/main/java/com/budwk/app/flow/handler/FlowSchoolUnionActivityAdminHandler.java b/src/main/java/com/budwk/app/flow/handler/FlowSchoolUnionActivityAdminHandler.java new file mode 100644 index 00000000..62f22786 --- /dev/null +++ b/src/main/java/com/budwk/app/flow/handler/FlowSchoolUnionActivityAdminHandler.java @@ -0,0 +1,44 @@ +package com.budwk.app.flow.handler; + +import com.budwk.app.base.constant.RoleConstant; +import com.budwk.app.flow.engine.AssignmentHandler; +import com.budwk.app.flow.engine.core.Execution; +import com.budwk.app.flow.engine.core.ServiceContext; +import com.budwk.app.flow.engine.model.TaskModel; +import org.nutz.dao.Dao; +import org.nutz.dao.Sqls; +import org.nutz.dao.sql.Sql; + +import java.util.List; + +public class FlowSchoolUnionActivityAdminHandler implements AssignmentHandler { + + @Override + public List assign(TaskModel model, Execution execution) { + Dao dao = ServiceContext.find(Dao.class); + Sql sql = Sqls.create(""" + SELECT + userRole.userId + FROM + `sys_user_role` userRole + LEFT JOIN sys_role role ON role.id = userRole.roleId + WHERE + role.`code` = @roleCode + GROUP BY + userRole.userId + """).setParam("roleCode", RoleConstant.SCHOOL_UNION_ACTIVITY_ADMIN.name()); + sql.setCallback(Sqls.callback.strList()); + dao.execute(sql); + return sql.getList(String.class); + } + + @Override + public String getMessage() { + return "\u83b7\u53d6\u6821\u5de5\u4f1a\u6d3b\u52a8\u7ba1\u7406\u5458"; + } + + @Override + public int getOrder() { + return AssignmentHandler.super.getOrder(); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugApplyController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugApplyController.java index 13848bbb..a7923c08 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugApplyController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugApplyController.java @@ -15,6 +15,7 @@ import com.budwk.app.flow.constant.FlowConst; import com.budwk.app.flow.engine.FlowEngine; import com.budwk.app.flow.entity.ProcessInstance; import com.budwk.app.flow.entity.ProcessTask; +import com.budwk.app.flow.enums.ProcessInstanceStateEnum; import com.budwk.app.flow.enums.ProcessSubmitTypeEnum; import com.budwk.app.sys.models.SysHoliday; import com.budwk.app.web.commons.auth.utils.SecurityUtil; @@ -29,6 +30,8 @@ import lombok.extern.slf4j.Slf4j; import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.dao.Cnd; import org.nutz.dao.Dao; +import org.nutz.dao.Sqls; +import org.nutz.dao.sql.Sql; import org.nutz.dao.util.cri.SqlExpressionGroup; import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; @@ -40,16 +43,19 @@ import org.nutz.mvc.annotation.Param; import java.time.Year; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.List; -import java.util.UUID; import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; import java.util.stream.Collectors; @Slf4j @IocBean @Ok("json:full") -@Api(tags = "场地预约-场馆") +@Api(tags = "\u573a\u5730\u9884\u7ea6-\u573a\u9986") @At("/platform/siteCug/apply") public class SiteCugApplyController { @@ -67,20 +73,23 @@ public class SiteCugApplyController { @At("/") @SaCheckPermission("siteCug.apply") @Ok("beetl:/platform/zhgh/dayofficework/siteCug/apply/index.html") - public void index() {} + public void index() { + } @At("/h5") @SaCheckPermission(value = {"siteCug.apply", "h5.siteCug.apply"}, mode = SaMode.OR) @Ok("beetl:/platform/zhghh5/dayofficework/siteCug/apply/index.html") - public void h5Index() {} + public void h5Index() { + } @At("/form/h5") @SaCheckPermission(value = {"siteCug.apply", "h5.siteCug.apply"}, mode = SaMode.OR) @Ok("beetl:/platform/zhghh5/dayofficework/siteCug/apply/form/index.html") - public void h5Form() {} + public void h5Form() { + } @At - @ApiOperation("分页查询场地") + @ApiOperation("\u5206\u9875\u67e5\u8be2\u573a\u5730") @SaCheckPermission(value = {"siteCug.apply", "h5.siteCug.apply"}, mode = SaMode.OR) public Result pageData(PageForm pageForm, @Param("type") String type) { Cnd cnd = Cnd.NEW(); @@ -102,17 +111,530 @@ public class SiteCugApplyController { List typeList = dao.query(SiteCugFunctionType.class, Cnd.NEW()); Map typeMap = typeList.stream().collect(Collectors.toMap(SiteCugFunctionType::getId, SiteCugFunctionType::getName)); + List siteIds = listMap.stream().map(item -> item.getString("id")).filter(StrUtil::isNotBlank).toList(); + String today = DateUtil.today(); + String dayStart = today + " 00:00:00"; + String dayEnd = today + " 23:59:59"; + Map> applyMap = queryTodayApplyMap(siteIds, dayStart, dayEnd); + Set holidaySet = dao.query(SysHoliday.class, Cnd.NEW()).stream() + .map(SysHoliday::getDay) + .filter(StrUtil::isNotBlank) + .collect(Collectors.toSet()); for (NutMap map : listMap) { map.put("typeName", typeMap.get(map.getString("typeId"))); + map.put("reserveTimeTypeName", getReserveTimeTypeName(map.getInt("reserveTimeType"))); + map.put("timelineSegments", buildTimelineSegments(map, applyMap.getOrDefault(map.getString("id"), new ArrayList<>()), today, holidaySet)); } return Result.success(pagination); } @At - @ApiOperation("提交预约") + @ApiOperation("\u67e5\u8be2\u573a\u5730\u53ef\u9884\u7ea6\u65f6\u6bb5") @SaCheckPermission(value = {"siteCug.apply", "h5.siteCug.apply"}, mode = SaMode.OR) - @SLog(tag = "场馆功能管理-场地预约", msg = "提交场地预约") + public Result availability(@Param("siteId") String siteId, @Param("date") String date) { + if (StrUtil.isBlank(siteId)) { + return Result.error("\u573a\u5730\u4e0d\u80fd\u4e3a\u7a7a"); + } + String targetDate = StrUtil.isBlank(date) ? DateUtil.today() : date; + if (!targetDate.matches("^\\d{4}-\\d{2}-\\d{2}$")) { + return Result.error("\u9884\u7ea6\u65e5\u671f\u683c\u5f0f\u4e0d\u6b63\u786e"); + } + SiteCugInfo siteInfo = infoService.fetch(siteId); + if (siteInfo == null) { + return Result.error("\u573a\u5730\u4e0d\u5b58\u5728"); + } + Set holidaySet = queryHolidaySet(); + List applyList = queryDayApplyList(siteId, targetDate + " 00:00:00", targetDate + " 23:59:59"); + return Result.success(NutMap.NEW() + .addv("date", targetDate) + .addv("reserveTimeType", siteInfo.getReserveTimeType()) + .addv("reserveTimeTypeName", getReserveTimeTypeName(siteInfo.getReserveTimeType())) + .addv("blocks", buildAvailabilityBlocks(siteInfo, targetDate, applyList, holidaySet))); + } + + private Map> queryTodayApplyMap(List siteIds, String dayStart, String dayEnd) { + if (siteIds == null || siteIds.isEmpty()) { + return Map.of(); + } + Sql sql = Sqls.create(""" + select + sa.* + from + site_cug_apply sa + left join wf_process_instance ins on ins.businessNo = sa.id + $condition + """); + Cnd cnd = Cnd.NEW(); + cnd.and("sa.siteId", "in", siteIds); + cnd.and("ins.state", "in", List.of( + ProcessInstanceStateEnum.DOING.getCode(), + ProcessInstanceStateEnum.FINISHED.getCode(), + ProcessInstanceStateEnum.PENDING.getCode() + )); + cnd.and("sa.reserveEndTime", ">=", dayStart); + cnd.and("sa.reserveStartTime", "<=", dayEnd); + sql.setCondition(cnd); + sql.setEntity(dao.getEntity(SiteCugApply.class)); + sql.setCallback(Sqls.callback.entities()); + dao.execute(sql); + List applyList = sql.getList(SiteCugApply.class); + if (applyList == null || applyList.isEmpty()) { + return Map.of(); + } + return applyList.stream() + .filter(Objects::nonNull) + .collect(Collectors.groupingBy(SiteCugApply::getSiteId)); + } + + private List queryDayApplyList(String siteId, String dayStart, String dayEnd) { + if (StrUtil.isBlank(siteId)) { + return new ArrayList<>(); + } + Sql sql = Sqls.create(""" + select + sa.* + from + site_cug_apply sa + left join wf_process_instance ins on ins.businessNo = sa.id + $condition + """); + Cnd cnd = Cnd.NEW(); + cnd.and("sa.siteId", "=", siteId); + cnd.and("ins.state", "in", List.of( + ProcessInstanceStateEnum.DOING.getCode(), + ProcessInstanceStateEnum.FINISHED.getCode(), + ProcessInstanceStateEnum.PENDING.getCode() + )); + cnd.and("sa.reserveEndTime", ">=", dayStart); + cnd.and("sa.reserveStartTime", "<=", dayEnd); + sql.setCondition(cnd); + sql.setEntity(dao.getEntity(SiteCugApply.class)); + sql.setCallback(Sqls.callback.entities()); + dao.execute(sql); + List applyList = sql.getList(SiteCugApply.class); + return applyList == null ? new ArrayList<>() : applyList; + } + + private Set queryHolidaySet() { + return dao.query(SysHoliday.class, Cnd.NEW()).stream() + .map(SysHoliday::getDay) + .filter(StrUtil::isNotBlank) + .collect(Collectors.toSet()); + } + + private String getReserveTimeTypeName(Integer reserveTimeType) { + if (reserveTimeType != null && reserveTimeType == 2) { + return "\u5168\u5929\u5019\u9884\u7ea6"; + } + return "\u5206\u6bb5\u9884\u7ea6"; + } + + private List buildTimelineSegments(NutMap site, List applyList, String today, Set holidaySet) { + List segments = new ArrayList<>(); + boolean isWeekend = isWeekend(today); + boolean isHoliday = holidaySet.contains(today); + List openRanges = buildOpenRanges(site, isWeekend, isHoliday); + List disabledRanges = buildDisabledRanges(site, today); + List reservedRanges = buildReservedRanges(applyList, today); + int totalMinutes = 24 * 60; + int segmentMinutes = 30; + for (int start = 0; start < totalMinutes; start += segmentMinutes) { + int end = start + segmentMinutes; + String status = "closed"; + if (intersectsAny(start, end, openRanges)) { + status = "available"; + } + if (intersectsAny(start, end, disabledRanges)) { + status = "closed"; + } + if (!"closed".equals(status) && intersectsAny(start, end, reservedRanges)) { + status = "reserved"; + } + segments.add(NutMap.NEW() + .addv("status", status) + .addv("startMinute", start) + .addv("endMinute", end)); + } + return segments; + } + + private List buildOpenRanges(NutMap site, boolean isWeekend, boolean isHoliday) { + if (isWeekend) { + return new ArrayList<>(); + } + if (Boolean.TRUE.equals(site.getBoolean("filterHolidays")) && isHoliday) { + return new ArrayList<>(); + } + Integer reserveTimeType = site.getInt("reserveTimeType"); + if (reserveTimeType != null && reserveTimeType == 2) { + NutMap fullDayOpenHour = toNutMap(site.get("fullDayOpenHour")); + if (fullDayOpenHour.isEmpty()) { + List openHours = toNutMapList(site.get("openHours")); + fullDayOpenHour = openHours.isEmpty() ? NutMap.NEW() : openHours.get(0); + } + return parseTimeRanges(List.of(fullDayOpenHour)); + } + List segmentedOpenHours = toNutMapList(site.get("segmentedOpenHours")); + if (segmentedOpenHours.isEmpty()) { + segmentedOpenHours = toNutMapList(site.get("openHours")); + } + return parseTimeRanges(segmentedOpenHours); + } + + private List buildDisabledRanges(NutMap site, String today) { + List disabledList = toNutMapList(site.get("notApplyTimeList")); + List result = new ArrayList<>(); + for (NutMap item : disabledList) { + if (item == null || !StrUtil.equals(today, item.getString("date"))) { + continue; + } + Integer start = parseTimeToMinutes(item.getString("startTime")); + Integer end = parseTimeToMinutes(item.getString("endTime")); + if (start != null && end != null && end > start) { + result.add(new int[]{start, end}); + } + } + return result; + } + + private List buildReservedRanges(List applyList, String today) { + List result = new ArrayList<>(); + String dayStart = today + " 00:00:00"; + String dayEnd = today + " 23:59:59"; + DateTime startOfDay = DateUtil.parseDateTime(dayStart); + DateTime endOfDay = DateUtil.parseDateTime(dayEnd); + for (SiteCugApply item : applyList) { + if (item == null || StrUtil.hasBlank(item.getReserveStartTime(), item.getReserveEndTime())) { + continue; + } + DateTime reserveStart = DateUtil.parseDateTime(item.getReserveStartTime()); + DateTime reserveEnd = DateUtil.parseDateTime(item.getReserveEndTime()); + if (!reserveEnd.isAfter(reserveStart)) { + continue; + } + DateTime actualStart = reserveStart.isBefore(startOfDay) ? startOfDay : reserveStart; + DateTime actualEnd = reserveEnd.isAfter(endOfDay) ? endOfDay : reserveEnd; + int startMinute = actualStart.hour(true) * 60 + actualStart.minute(); + int endMinute = actualEnd.hour(true) * 60 + actualEnd.minute(); + if (actualEnd.second() > 0) { + endMinute = Math.min(24 * 60, endMinute + 1); + } + if (endMinute > startMinute) { + result.add(new int[]{startMinute, endMinute}); + } + } + return result; + } + + private List parseTimeRanges(List sourceList) { + List result = new ArrayList<>(); + for (NutMap item : sourceList) { + if (item == null) { + continue; + } + Integer start = parseTimeToMinutes(item.getString("startTime")); + Integer end = parseTimeToMinutes(item.getString("endTime")); + if (start != null && end != null && end > start) { + result.add(new int[]{start, end}); + } + } + return result; + } + + private NutMap toNutMap(Object value) { + if (value instanceof NutMap nutMap) { + return nutMap; + } + if (value instanceof Map map) { + NutMap nutMap = NutMap.NEW(); + map.forEach((key, val) -> nutMap.put(String.valueOf(key), val)); + return nutMap; + } + return NutMap.NEW(); + } + + private List toNutMapList(Object value) { + List result = new ArrayList<>(); + if (!(value instanceof List list)) { + return result; + } + for (Object item : list) { + result.add(toNutMap(item)); + } + return result; + } + + private Integer parseTimeToMinutes(String timeText) { + if (StrUtil.isBlank(timeText)) { + return null; + } + List parts = Arrays.stream(timeText.split(":")) + .filter(StrUtil::isNotBlank) + .toList(); + if (parts.size() < 2) { + return null; + } + try { + int hour = Integer.parseInt(parts.get(0)); + int minute = Integer.parseInt(parts.get(1)); + if (hour < 0 || hour > 24 || minute < 0 || minute > 59) { + return null; + } + if (hour == 24 && minute > 0) { + return null; + } + return hour * 60 + minute; + } catch (NumberFormatException e) { + return null; + } + } + + private boolean intersectsAny(int start, int end, List ranges) { + for (int[] range : ranges) { + if (range != null && start < range[1] && end > range[0]) { + return true; + } + } + return false; + } + + private boolean isWeekend(String dateText) { + DateTime date = DateUtil.parseDate(dateText); + int week = date.dayOfWeek() - 1; + return week == 0 || week == 6; + } + + private List buildAvailabilityBlocks(SiteCugInfo siteInfo, String day, List applyList, Set holidaySet) { + if (siteInfo.getReserveTimeType() != null && siteInfo.getReserveTimeType() == 2) { + return buildFullDayAvailabilityBlocks(siteInfo, day, applyList, holidaySet); + } + return buildSegmentedAvailabilityBlocks(siteInfo, day, applyList, holidaySet); + } + + private List buildSegmentedAvailabilityBlocks(SiteCugInfo siteInfo, String day, List applyList, Set holidaySet) { + List sourceList = siteInfo.getSegmentedOpenHours(); + if (sourceList == null || sourceList.isEmpty()) { + sourceList = siteInfo.getOpenHours(); + } + List result = new ArrayList<>(); + List reservedRanges = buildReservedRanges(applyList, day); + List disabledRanges = buildDisabledRanges(toSiteNutMap(siteInfo), day); + boolean siteClosed = isSiteClosed(siteInfo, day, holidaySet); + for (int i = 0; i < sourceList.size(); i++) { + NutMap item = toNutMap(sourceList.get(i)); + Integer slotStart = parseTimeToMinutes(item.getString("startTime")); + Integer slotEnd = parseTimeToMinutes(item.getString("endTime")); + int unitMinutes = parseTimeUnitMinutes(item.get("timeUnit")); + if (slotStart == null || slotEnd == null || slotEnd <= slotStart || unitMinutes <= 0) { + continue; + } + String groupLabel = "\u573a\u6b21" + (i + 1) + " " + formatMinutes(slotStart) + "-" + formatMinutes(slotEnd) + " / " + formatUnitText(unitMinutes); + for (int cursor = slotStart; cursor + unitMinutes <= slotEnd; cursor += unitMinutes) { + int blockEnd = cursor + unitMinutes; + result.add(buildAvailabilityBlock(day, cursor, blockEnd, reservedRanges, disabledRanges, siteClosed, i, groupLabel)); + } + } + return result; + } + + private List buildFullDayAvailabilityBlocks(SiteCugInfo siteInfo, String day, List applyList, Set holidaySet) { + NutMap source = getFullDayOpenHourConfig(siteInfo); + Integer slotStart = parseTimeToMinutes(source.getString("startTime")); + Integer slotEnd = parseTimeToMinutes(source.getString("endTime")); + int unitMinutes = parseTimeUnitMinutes(source.get("timeUnit")); + if (slotStart == null || slotEnd == null || slotEnd <= slotStart || unitMinutes <= 0) { + return new ArrayList<>(); + } + List result = new ArrayList<>(); + List reservedRanges = buildReservedRanges(applyList, day); + List disabledRanges = buildDisabledRanges(toSiteNutMap(siteInfo), day); + boolean siteClosed = isSiteClosed(siteInfo, day, holidaySet); + String groupLabel = "\u5168\u5929\u5019\u65f6\u6bb5 / " + formatUnitText(unitMinutes); + for (int cursor = slotStart; cursor + unitMinutes <= slotEnd; cursor += unitMinutes) { + int blockEnd = cursor + unitMinutes; + result.add(buildAvailabilityBlock(day, cursor, blockEnd, reservedRanges, disabledRanges, siteClosed, 0, groupLabel)); + } + return result; + } + + private NutMap buildAvailabilityBlock(String day, int startMinute, int endMinute, List reservedRanges, List disabledRanges, boolean siteClosed, int groupIndex, String groupLabel) { + String status = "available"; + if (siteClosed || intersectsAny(startMinute, endMinute, disabledRanges)) { + status = "closed"; + } else if (intersectsAny(startMinute, endMinute, reservedRanges)) { + status = "reserved"; + } + return NutMap.NEW() + .addv("key", day + "_" + startMinute + "_" + endMinute) + .addv("groupIndex", groupIndex) + .addv("groupLabel", groupLabel) + .addv("status", status) + .addv("startMinute", startMinute) + .addv("endMinute", endMinute) + .addv("startTime", formatMinutes(startMinute)) + .addv("endTime", formatMinutes(endMinute)) + .addv("label", formatMinutes(startMinute) + "-" + formatMinutes(endMinute)) + .addv("startDateTime", day + " " + formatMinutes(startMinute) + ":00") + .addv("endDateTime", day + " " + formatMinutes(endMinute) + ":00"); + } + + private boolean isSiteClosed(SiteCugInfo siteInfo, String day, Set holidaySet) { + if (isWeekend(day)) { + return true; + } + return Boolean.TRUE.equals(siteInfo.getFilterHolidays()) && holidaySet.contains(day); + } + + private NutMap toSiteNutMap(SiteCugInfo siteInfo) { + return NutMap.NEW() + .addv("notApplyTimeList", siteInfo.getNotApplyTimeList()) + .addv("filterHolidays", siteInfo.getFilterHolidays()) + .addv("reserveTimeType", siteInfo.getReserveTimeType()) + .addv("openHours", siteInfo.getOpenHours()) + .addv("segmentedOpenHours", siteInfo.getSegmentedOpenHours()) + .addv("fullDayOpenHour", siteInfo.getFullDayOpenHour()); + } + + private NutMap getFullDayOpenHourConfig(SiteCugInfo siteInfo) { + NutMap source = toNutMap(siteInfo.getFullDayOpenHour()); + if (!source.isEmpty()) { + return source; + } + List openHours = siteInfo.getOpenHours(); + return openHours == null || openHours.isEmpty() ? NutMap.NEW() : toNutMap(openHours.get(0)); + } + + private int parseTimeUnitMinutes(Object value) { + if (value == null) { + return 60; + } + try { + double unit = Double.parseDouble(String.valueOf(value)); + if (unit <= 0) { + return 60; + } + return (int) Math.round(unit); + } catch (NumberFormatException e) { + return 60; + } + } + + private String formatMinutes(int totalMinutes) { + int hour = totalMinutes / 60; + int minute = totalMinutes % 60; + return String.format("%02d:%02d", hour, minute); + } + + private String formatUnitText(int unitMinutes) { + return unitMinutes + "\u5206\u949f"; + } + + private Result validateApplySelectionBySchedule(SiteCugApply apply) { + if (apply == null || StrUtil.hasBlank(apply.getSiteId(), apply.getReserveStartTime(), apply.getReserveEndTime())) { + return Result.success(); + } + SiteCugInfo siteInfo = infoService.fetch(apply.getSiteId()); + if (siteInfo == null) { + return Result.error("\u573a\u5730\u4e0d\u5b58\u5728"); + } + DateTime start = DateUtil.parseDateTime(apply.getReserveStartTime()); + DateTime end = DateUtil.parseDateTime(apply.getReserveEndTime()); + String startDay = DateUtil.formatDate(start); + String endDay = DateUtil.formatDate(end); + if (!StrUtil.equals(startDay, endDay)) { + return Result.error("\u9884\u7ea6\u65f6\u95f4\u5fc5\u987b\u5728\u540c\u4e00\u5929\u5185"); + } + Set holidaySet = queryHolidaySet(); + List applyList = queryDayApplyList(apply.getSiteId(), startDay + " 00:00:00", startDay + " 23:59:59").stream() + .filter(item -> item != null && !StrUtil.equals(item.getId(), apply.getId())) + .collect(Collectors.toList()); + if (siteInfo.getReserveTimeType() != null && siteInfo.getReserveTimeType() == 2) { + if (!isWithinOpenRanges(siteInfo, startDay, start, end, holidaySet)) { + return Result.error("\u5168\u5929\u5019\u9884\u7ea6\u8bf7\u5728\u5f53\u5929\u5f00\u653e\u65f6\u95f4\u5185\u6309\u9884\u7ea6\u65f6\u95f4\u5355\u4f4d\u9009\u62e9\u4e00\u4e2a\u6216\u591a\u4e2a\u8fde\u7eed\u65f6\u6bb5"); + } + if (!isAlignedWithFullDayTimeUnit(siteInfo, start, end)) { + return Result.error("\u5168\u5929\u5019\u9884\u7ea6\u7684\u5f00\u59cb\u548c\u7ed3\u675f\u65f6\u95f4\u5fc5\u987b\u7b26\u5408\u9884\u7ea6\u65f6\u95f4\u5355\u4f4d"); + } + return Result.success(); + } + List blocks = buildSegmentedAvailabilityBlocks(siteInfo, startDay, applyList, holidaySet); + String startText = apply.getReserveStartTime(); + String endText = apply.getReserveEndTime(); + for (int i = 0; i < blocks.size(); i++) { + NutMap block = blocks.get(i); + if (!"available".equals(block.getString("status"))) { + continue; + } + if (!StrUtil.equals(startText, block.getString("startDateTime"))) { + continue; + } + String currentEnd = block.getString("endDateTime"); + if (StrUtil.equals(endText, currentEnd)) { + return Result.success(); + } + for (int j = i + 1; j < blocks.size(); j++) { + NutMap nextBlock = blocks.get(j); + NutMap prevBlock = blocks.get(j - 1); + if (!"available".equals(nextBlock.getString("status"))) { + break; + } + if (!StrUtil.equals(prevBlock.getString("endDateTime"), nextBlock.getString("startDateTime"))) { + break; + } + currentEnd = nextBlock.getString("endDateTime"); + if (StrUtil.equals(endText, currentEnd)) { + return Result.success(); + } + } + } + return Result.error("\u5206\u6bb5\u9884\u7ea6\u8bf7\u6309\u573a\u6b21\u65f6\u95f4\u5355\u4f4d\u9009\u62e9\u4e00\u4e2a\u6216\u591a\u4e2a\u8fde\u7eed\u65f6\u6bb5"); + } + + private boolean isWithinOpenRanges(SiteCugInfo siteInfo, String day, DateTime start, DateTime end, Set holidaySet) { + if (isSiteClosed(siteInfo, day, holidaySet)) { + return false; + } + List openRanges = buildOpenRanges(toSiteNutMap(siteInfo), false, false); + List disabledRanges = buildDisabledRanges(toSiteNutMap(siteInfo), day); + int startMinute = start.hour(true) * 60 + start.minute(); + int endMinute = end.hour(true) * 60 + end.minute(); + if (end.second() > 0) { + endMinute = Math.min(24 * 60, endMinute + 1); + } + final int finalEndMinute = endMinute; + boolean inOpenRange = openRanges.stream().anyMatch(range -> startMinute >= range[0] && finalEndMinute <= range[1]); + return inOpenRange && !intersectsAny(startMinute, finalEndMinute, disabledRanges); + } + + private boolean isAlignedWithFullDayTimeUnit(SiteCugInfo siteInfo, DateTime start, DateTime end) { + if (start == null || end == null || start.second() > 0 || end.second() > 0) { + return false; + } + NutMap source = getFullDayOpenHourConfig(siteInfo); + Integer slotStart = parseTimeToMinutes(source.getString("startTime")); + Integer slotEnd = parseTimeToMinutes(source.getString("endTime")); + int unitMinutes = parseTimeUnitMinutes(source.get("timeUnit")); + if (slotStart == null || slotEnd == null || slotEnd <= slotStart || unitMinutes <= 0) { + return false; + } + int startMinute = start.hour(true) * 60 + start.minute(); + int endMinute = end.hour(true) * 60 + end.minute(); + return startMinute >= slotStart + && endMinute <= slotEnd + && endMinute > startMinute + && (startMinute - slotStart) % unitMinutes == 0 + && (endMinute - slotStart) % unitMinutes == 0; + } + + @At + @ApiOperation("\u63d0\u4ea4\u9884\u7ea6") + @SaCheckPermission(value = {"siteCug.apply", "h5.siteCug.apply"}, mode = SaMode.OR) + @SLog(tag = "\u573a\u9986\u529f\u80fd\u7ba1\u7406-\u573a\u5730\u9884\u7ea6", msg = "\u63d0\u4ea4\u573a\u5730\u9884\u7ea6") public Result submit(@Param("data") SiteCugApply apply) { + Result scheduleValidate = validateApplySelectionBySchedule(apply); + if (scheduleValidate.getCode() != 0) { + return scheduleValidate; + } Map validate = applyService.validateApply(apply); if (validate.containsKey(false)) { return Result.error(validate.get(false)); @@ -122,19 +644,23 @@ public class SiteCugApplyController { } @At("/submitYearly") - @ApiOperation("按本年后续同星期同时间段批量提交预约") + @ApiOperation("\u6309\u672c\u5e74\u540e\u7eed\u540c\u661f\u671f\u540c\u65f6\u95f4\u6bb5\u6279\u91cf\u63d0\u4ea4\u9884\u7ea6") @Aop(TransAop.READ_COMMITTED) @SaCheckPermission(value = {"siteCug.apply", "h5.siteCug.apply"}, mode = SaMode.OR) - @SLog(tag = "场馆功能管理-场地预约", msg = "批量提交本年场地预约") + @SLog(tag = "\u573a\u9986\u529f\u80fd\u7ba1\u7406-\u573a\u5730\u9884\u7ea6", msg = "\u6279\u91cf\u63d0\u4ea4\u672c\u5e74\u573a\u5730\u9884\u7ea6") public Result submitYearly(@Param("data") SiteCugApply apply) { List applyList = buildYearlyApplyList(apply); if (applyList.isEmpty()) { - return Result.error("未生成可预约的日期"); + return Result.error("\u672a\u751f\u6210\u53ef\u9884\u7ea6\u7684\u65e5\u671f"); } for (SiteCugApply item : applyList) { + Result scheduleValidate = validateApplySelectionBySchedule(item); + if (scheduleValidate.getCode() != 0) { + return scheduleValidate; + } Map validate = applyService.validateApply(item); if (validate.containsKey(false)) { - return Result.error(String.format("%s 预约失败:%s", item.getReserveStartTime(), validate.get(false))); + return Result.error(String.format("%s \u9884\u7ea6\u5931\u8d25\uff1a%s", item.getReserveStartTime(), validate.get(false))); } } String yearlyBatchNo = YEARLY_BATCH_PREFIX + UUID.randomUUID(); @@ -142,11 +668,11 @@ public class SiteCugApplyController { item.setBackOption(yearlyBatchNo); submitSingleApply(item); } - return Result.success(String.format("已成功预约本年剩余%d个时间段", applyList.size())); + return Result.success(String.format("\u5df2\u6210\u529f\u9884\u7ea6\u622a\u81f3%s\u5171%d\u4e2a\u65f6\u95f4\u6bb5", apply.getYearlyReserveEndDate(), applyList.size())); } @At - @ApiOperation("查询预约限制配置") + @ApiOperation("\u67e5\u8be2\u9884\u7ea6\u9650\u5236\u914d\u7f6e") @SaCheckPermission(value = {"siteCug.apply", "h5.siteCug.apply"}, mode = SaMode.OR) public Result timeLimitConfig(@Param("siteId") String siteId) { if (StrUtil.isBlank(siteId)) { @@ -174,7 +700,7 @@ public class SiteCugApplyController { } private List buildYearlyApplyList(SiteCugApply apply) { - if (apply == null || StrUtil.hasBlank(apply.getReserveStartTime(), apply.getReserveEndTime())) { + if (apply == null || StrUtil.hasBlank(apply.getReserveStartTime(), apply.getReserveEndTime(), apply.getYearlyReserveEndDate())) { return new ArrayList<>(); } DateTime start = DateUtil.parseDateTime(apply.getReserveStartTime()); @@ -182,6 +708,10 @@ public class SiteCugApplyController { if (!end.isAfter(start)) { return new ArrayList<>(); } + DateTime reserveEndDate = parseReserveEndDate(apply.getYearlyReserveEndDate()); + if (reserveEndDate == null || reserveEndDate.isBefore(DateUtil.beginOfDay(start))) { + return new ArrayList<>(); + } SiteCugInfo siteInfo = infoService.fetch(apply.getSiteId()); if (siteInfo == null) { return new ArrayList<>(); @@ -205,9 +735,9 @@ public class SiteCugApplyController { int endMinute = end.minute(); int endSecond = end.second(); Date current = DateUtil.beginOfDay(start); - Date endOfYear = DateUtil.endOfYear(start); + Date batchEndDate = DateUtil.endOfDay(reserveEndDate); List result = new ArrayList<>(); - while (current.compareTo(endOfYear) <= 0) { + while (current.compareTo(batchEndDate) <= 0) { DateTime currentDate = DateUtil.date(current); String currentDay = DateUtil.formatDate(currentDate); if (currentDate.dayOfWeek() - 1 == targetWeek && !holidayList.contains(currentDay)) { @@ -222,6 +752,17 @@ public class SiteCugApplyController { return result; } + private DateTime parseReserveEndDate(String endDate) { + if (StrUtil.isBlank(endDate)) { + return null; + } + try { + return DateUtil.parseDate(endDate); + } catch (Exception e) { + return null; + } + } + private DateTime buildDateTime(DateTime date, int hour, int minute, int second) { return DateUtil.parseDateTime(String.format("%s %02d:%02d:%02d", DateUtil.formatDate(date), hour, minute, second)); } @@ -245,7 +786,8 @@ public class SiteCugApplyController { .setReserveEndTime(apply.getReserveEndTime()) .setJoinCount(apply.getJoinCount()) .setApplyCause(apply.getApplyCause()) - .setBackOption(apply.getBackOption()); + .setBackOption(apply.getBackOption()) + .setYearlyReserveEndDate(apply.getYearlyReserveEndDate()); } private void submitSingleApply(SiteCugApply apply) { @@ -261,4 +803,4 @@ public class SiteCugApplyController { flowEngine.executeProcessTask(task.getId(), SecurityUtil.getUserId(), args); } } -} \ No newline at end of file +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugManageController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugManageController.java index 41dc9c90..d95de001 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugManageController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugManageController.java @@ -27,12 +27,13 @@ import org.nutz.mvc.annotation.Param; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; @Slf4j @IocBean @Ok("json:full") -@Api(tags = "场地管理-场馆") +@Api(tags = "siteCug manage") @At("/platform/siteCug/manage") public class SiteCugManageController { @@ -47,7 +48,7 @@ public class SiteCugManageController { public void index() {} @At - @ApiOperation("分页查询") + @ApiOperation("page data") @SaCheckPermission("siteCug.manage") public Result pageData(PageForm pageForm, @Param("type") String type) { Cnd cnd = Cnd.NEW(); @@ -76,9 +77,9 @@ public class SiteCugManageController { } @At - @ApiOperation("新增/修改场地") + @ApiOperation("submit") @SaCheckPermission("siteCug.manage") - @SLog(tag = "场馆功能管理-场地管理", msg = "新增/修改场地") + @SLog(tag = "siteCug.manage", msg = "submit site") public Object submit(@Param("data") SiteCugInfo info) { List notApplyTimeList = info.getNotApplyTimeList(); if (notApplyTimeList != null) { @@ -89,27 +90,81 @@ public class SiteCugManageController { || StrUtil.isBlank(item.getString("endTime")) || item.getString("startTime").compareTo(item.getString("endTime")) >= 0); if (invalid) { - return Result.error("禁用时间设置有误"); + return Result.error("Invalid disabled time settings"); } } + + if (info.getReserveTimeType() == null) { + info.setReserveTimeType(1); + } + List segmentedOpenHours = info.getSegmentedOpenHours(); + NutMap fullDayOpenHour = info.getFullDayOpenHour(); + if ((segmentedOpenHours == null || segmentedOpenHours.isEmpty()) && info.getReserveTimeType() == 1) { + segmentedOpenHours = info.getOpenHours(); + } + if ((fullDayOpenHour == null || fullDayOpenHour.isEmpty()) && info.getReserveTimeType() == 2 && info.getOpenHours() != null && !info.getOpenHours().isEmpty()) { + fullDayOpenHour = info.getOpenHours().get(0); + } + if (info.getReserveTimeType() == 1) { + if (segmentedOpenHours == null || segmentedOpenHours.isEmpty()) { + return Result.error("Please add at least one booking slot"); + } + boolean invalid = segmentedOpenHours.stream().anyMatch(item -> + item == null + || StrUtil.isBlank(item.getString("startTime")) + || StrUtil.isBlank(item.getString("endTime")) + || item.getString("startTime").compareTo(item.getString("endTime")) >= 0 + || !isPositiveNumber(item.get("timeUnit"))); + if (invalid) { + return Result.error("Invalid segmented booking slot settings"); + } + if (hasOverlap(segmentedOpenHours)) { + return Result.error("Segmented booking slots cannot overlap"); + } + segmentedOpenHours = segmentedOpenHours.stream() + .filter(Objects::nonNull) + .map(this::normalizeOpenHourTimeUnit) + .collect(Collectors.toList()); + info.setSegmentedOpenHours(segmentedOpenHours); + info.setOpenHours(segmentedOpenHours); + } else if (info.getReserveTimeType() == 2) { + if (fullDayOpenHour == null || fullDayOpenHour.isEmpty()) { + return Result.error("Please set the full-day booking time range"); + } + NutMap item = fullDayOpenHour; + if (item == null + || StrUtil.isBlank(item.getString("startTime")) + || StrUtil.isBlank(item.getString("endTime")) + || item.getString("startTime").compareTo(item.getString("endTime")) >= 0) { + return Result.error("Invalid full-day booking time range"); + } + if (!isPositiveNumber(item.get("timeUnit"))) { + return Result.error("Invalid full-day booking time unit"); + } + fullDayOpenHour = normalizeOpenHourTimeUnit(fullDayOpenHour); + info.setFullDayOpenHour(fullDayOpenHour); + info.setOpenHours(List.of(fullDayOpenHour)); + } + if (info.getFilterHolidays() == null) { info.setFilterHolidays(false); } infoService.insertOrUpdate(info); - return Result.success(); + SiteCugInfo savedInfo = infoService.fetch(info.getId()); + return Result.success(savedInfo == null ? info : savedInfo); } @At - @ApiOperation("删除场地") + @ApiOperation("delete") @SaCheckPermission("siteCug.manage") - @SLog(tag = "场馆功能管理-场地管理", msg = "删除场地") + @SLog(tag = "siteCug.manage", msg = "delete site") public Object delete(String id) { infoService.delete(id); return Result.success(); } @At - @ApiOperation("查询场地") + @ApiOperation("query sites") @SaCheckLogin public Result querySites() { List list = infoService.query(Cnd.where(SiteCugInfo::getState, "=", true).desc(SiteCugInfo::getSortNum)); @@ -117,7 +172,7 @@ public class SiteCugManageController { } @At - @ApiOperation("查询单个场地") + @ApiOperation("query site info") @SaCheckLogin public Result info(String id) { SiteCugInfo info = infoService.fetch(id); @@ -129,4 +184,59 @@ public class SiteCugManageController { map.put("typeName", type == null ? "" : type.getName()); return Result.success(map); } + + private boolean isPositiveNumber(Object value) { + if (value == null) { + return false; + } + try { + return Double.parseDouble(String.valueOf(value)) > 0; + } catch (Exception e) { + return false; + } + } + + private NutMap normalizeOpenHourTimeUnit(NutMap item) { + if (item == null) { + return null; + } + item.put("timeUnit", normalizeTimeUnitMinutes(item.get("timeUnit"))); + return item; + } + + private int normalizeTimeUnitMinutes(Object value) { + if (value == null) { + return 60; + } + try { + double parsed = Double.parseDouble(String.valueOf(value)); + if (parsed <= 0) { + return 60; + } + return (int) Math.round(parsed); + } catch (Exception e) { + return 60; + } + } + + private boolean hasOverlap(List openHours) { + if (openHours == null || openHours.size() < 2) { + return false; + } + List validHours = openHours.stream() + .filter(item -> item != null + && StrUtil.isNotBlank(item.getString("startTime")) + && StrUtil.isNotBlank(item.getString("endTime")) + && item.getString("startTime").compareTo(item.getString("endTime")) < 0) + .sorted((a, b) -> a.getString("startTime").compareTo(b.getString("startTime"))) + .collect(Collectors.toList()); + for (int i = 1; i < validHours.size(); i++) { + NutMap prev = validHours.get(i - 1); + NutMap current = validHours.get(i); + if (current.getString("startTime").compareTo(prev.getString("endTime")) < 0) { + return true; + } + } + return false; + } } diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugMineController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugMineController.java index c02f9a8d..3f33fc89 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugMineController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugMineController.java @@ -81,6 +81,7 @@ public class SiteCugMineController { t.finishTime, t.taskParentId, t.variable taskVariable, + CASE WHEN info.reserveType = 'club' THEN info.clubName ELSE info.applyUnionName END AS reserveTargetName, CASE WHEN ins.state in (10, 20) AND (t.displayName LIKE '%校工会允许%' OR t.taskName = 'c8c03407-cf26-4e0b-82f1-b2afc9c79996') THEN 1 @@ -105,7 +106,6 @@ public class SiteCugMineController { SqlExpressionGroup seg = new SqlExpressionGroup(); seg.or(SiteCugApply::getApplyUserName, "like", "%" + pageForm.getSearchKeyword() + "%"); seg.or(SiteCugApply::getApplyLoginName, "like", "%" + pageForm.getSearchKeyword() + "%"); - seg.or("si.name", "like", "%" + pageForm.getSearchKeyword() + "%"); cnd.and(seg); } cnd.andEX("si.typeId", "=", siteType); diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugSchoolUnionAuditController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugSchoolUnionAuditController.java index 1ebe3d28..b14d1353 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugSchoolUnionAuditController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugSchoolUnionAuditController.java @@ -75,6 +75,7 @@ public class SiteCugSchoolUnionAuditController { SELECT info.*, si.name as siteName, + CASE WHEN info.reserveType = 'club' THEN info.clubName ELSE info.applyUnionName END AS reserveTargetName, ins.id AS instanceId, ins.businessNo, ins.state instanceState, @@ -113,7 +114,6 @@ public class SiteCugSchoolUnionAuditController { SqlExpressionGroup seg = new SqlExpressionGroup(); seg.or(SiteCugApply::getApplyUserName, "like", "%" + pageForm.getSearchKeyword() + "%"); seg.or(SiteCugApply::getApplyLoginName, "like", "%" + pageForm.getSearchKeyword() + "%"); - seg.or("si.name", "like", "%" + pageForm.getSearchKeyword() + "%"); cnd.and(seg); } cnd.andEX("si.typeId", "=", siteType); @@ -129,7 +129,7 @@ public class SiteCugSchoolUnionAuditController { } if (StrUtil.isNotBlank(pageForm.getPageOrderName()) && StrUtil.isNotBlank(pageForm.getPageOrderBy())) { - cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy())); + cnd.orderBy(resolveOrderColumn(pageForm.getPageOrderName()), PageUtil.getOrder(pageForm.getPageOrderBy())); } else { cnd.desc("info.createdAt"); } @@ -139,6 +139,36 @@ public class SiteCugSchoolUnionAuditController { return Result.success(pageVO); } + private String resolveOrderColumn(String pageOrderName) { + if (StrUtil.isBlank(pageOrderName)) { + return "info.createdAt"; + } + switch (pageOrderName) { + case "siteName": + return "si.name"; + case "applyUserName": + return "info.applyUserName"; + case "applyLoginName": + return "info.applyLoginName"; + case "yearlyBatch": + return "yearlyBatch"; + case "reserveTargetName": + return "reserveTargetName"; + case "reserveStartTime": + return "info.reserveStartTime"; + case "reserveEndTime": + return "info.reserveEndTime"; + case "applyMobile": + return "info.applyMobile"; + case "curTaskName": + return "curTaskName"; + case "instanceState": + return "ins.state"; + default: + return "info.createdAt"; + } + } + @At("/executeTask") @ApiOperation("执行审核任务") @Aop(TransAop.READ_COMMITTED) diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/model/SiteCugApply.java b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/model/SiteCugApply.java index d332d548..4857fa3d 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/model/SiteCugApply.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/model/SiteCugApply.java @@ -109,4 +109,6 @@ public class SiteCugApply extends BaseModel { @Comment("反馈意见") @ColDefine(type = ColType.VARCHAR, width = 200) private String backOption; + + private String yearlyReserveEndDate; } diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/model/SiteCugInfo.java b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/model/SiteCugInfo.java index 2e491ca9..96be2a49 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/model/SiteCugInfo.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/model/SiteCugInfo.java @@ -78,6 +78,27 @@ public class SiteCugInfo extends BaseModel { @ColDefine(type = ColType.MYSQL_JSON) private List notApplyTimeList; + @Column + @Comment("预约时间段类型(1分段预约,2全天候预约)") + @ColDefine(type = ColType.INT) + @Default("1") + private Integer reserveTimeType; + + @Column + @Comment("场次信息") + @ColDefine(type = ColType.MYSQL_JSON) + private List openHours; + + @Column + @Comment("分段预约场次信息") + @ColDefine(type = ColType.MYSQL_JSON) + private List segmentedOpenHours; + + @Column + @Comment("全天候预约时间段") + @ColDefine(type = ColType.MYSQL_JSON) + private NutMap fullDayOpenHour; + @Column @Comment("开启状态") @ColDefine(type = ColType.BOOLEAN) @@ -94,4 +115,9 @@ public class SiteCugInfo extends BaseModel { @Comment("场地介绍") @ColDefine(type = ColType.TEXT) private String introduce; -} \ No newline at end of file + + @Column + @Comment("场地照片") + @ColDefine(type = ColType.VARCHAR, width = 255) + private String sitePhoto; +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/service/impl/SiteCugApplyServiceImpl.java b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/service/impl/SiteCugApplyServiceImpl.java index a2357ac1..dba66f65 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/service/impl/SiteCugApplyServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/service/impl/SiteCugApplyServiceImpl.java @@ -136,6 +136,7 @@ public class SiteCugApplyServiceImpl extends BaseServiceImpl imple si.name AS siteName, ins.id AS instanceId, ins.processDefineId instanceProcessDefineId, + CASE WHEN info.backOption LIKE 'YEARLY_BATCH:%' THEN 1 ELSE 0 END AS yearlyBatch, CASE WHEN info.reserveType = 'club' THEN '协会预约' ELSE '分工会预约' END AS reserveTypeName, CASE WHEN info.reserveType = 'club' THEN info.clubName ELSE info.applyUnionName END AS reserveTargetName, DATE_FORMAT(info.createdAt, '%Y-%m-%d %H:%i:%s') AS applyTime @@ -155,7 +156,6 @@ public class SiteCugApplyServiceImpl extends BaseServiceImpl imple SqlExpressionGroup seg = new SqlExpressionGroup(); seg.or(SiteCugApply::getApplyUserName, "like", "%" + pageForm.getSearchKeyword() + "%"); seg.or(SiteCugApply::getApplyLoginName, "like", "%" + pageForm.getSearchKeyword() + "%"); - seg.or("si.name", "like", "%" + pageForm.getSearchKeyword() + "%"); cnd.and(seg); } if (StrUtil.isNotBlank(pageForm.getReserveTargetKeyword())) { diff --git a/src/main/resources/views/platform/zhgh/dayofficework/siteCug/apply/index.html b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/apply/index.html index 647f949e..fa460360 100644 --- a/src/main/resources/views/platform/zhgh/dayofficework/siteCug/apply/index.html +++ b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/apply/index.html @@ -27,27 +27,71 @@ layout("/layouts/platform.html"){ - - - - - - - - +
+
+
+ + +
+ + 暂无场地照片 +
+
+
+
+
+
{{ row.name || '--' }}
+
+ + {{ row.address || '暂无场地地址' }} +
+
+
+ {{ row.reserveTimeTypeName || '分段预约' }} + 场地详情 + 预约 +
+
+ +
+ 场地类型:{{ row.typeName || '--' }} + 容纳人数:{{ row.maxNum || 0 }}人 + 联系人:{{ row.contactName || '--' }} +
+ +
+
今日预约情况
+
+ + +
+
+ {{ hour }} +
+
+ + 未预约 + + + 已预约 + + + 未开放 + +
+
+
+
+
+
@@ -63,7 +107,7 @@ layout("/layouts/platform.html"){ + + diff --git a/src/main/resources/views/platform/zhgh/dayofficework/siteCug/manage/basicForm.js b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/manage/basicForm.js index 0e51662a..14bd8abd 100644 --- a/src/main/resources/views/platform/zhgh/dayofficework/siteCug/manage/basicForm.js +++ b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/manage/basicForm.js @@ -2,109 +2,226 @@ template: /*language=HTML*/ `
- - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - 不限制 - - - + + - - - - - 开启 - 禁用 - - - - - - - - - - - + + + - - - - 点击设置禁用时间 - 您已设置{{ formData.notApplyTimeList ? formData.notApplyTimeList.length : 0 }}个禁用时间 - - - + - - - + + + + + + 开启 + 禁用 + + + + + + + 不限制 + + + + + + + + + + + + 分段预约 + 全天候预约 + + + + + + + + + + + + + + 点击设置禁用时间 + 您已设置{{ formData.notApplyTimeList ? formData.notApplyTimeList.length : 0 }}个禁用时间 + + + + + 场次信息 + +
+
分段预约:可设置一个或多个场次,预约人按拆分后的场次进行预约。
+ + + + + + + + + + + + + + + + +
+ +
+
全天候预约:设置可预约的开始时间、结束时间和预约时间单位(分钟),预约人按设定单位选择连续时段。
+ + + + + + + + + + + + + + + + + + + + +
+
+
+
+ 暂存:保存当前填写内容并留在本页继续编辑。正式提交:保存后返回列表页。 +
取消 - 提交 + 暂存并继续编辑 + 正式提交 @@ -202,10 +319,17 @@ store, data() { return { + activeTab: 'basicInfo', + segmentedOpenHoursCache: [{ startTime: '', endTime: '', timeUnit: 30 }], + fullDayOpenHourCache: { startTime: '', endTime: '', timeUnit: 30 }, formData: { state: true, sexLimit: 0, filterHolidays: false, + reserveTimeType: 1, + openHours: [{ startTime: '', endTime: '', timeUnit: 30 }], + segmentedOpenHours: [{ startTime: '', endTime: '', timeUnit: 30 }], + fullDayOpenHour: { startTime: '', endTime: '', timeUnit: 30 }, notApplyTimeList: [], createUserName: this.$store.state.user.username, createUserId: this.$store.state.user.id, @@ -222,6 +346,7 @@ sexLimit: [{required: true, message: '必填', trigger: ['blur', 'change']}], filterHolidays: [{required: true, message: '必填', trigger: ['blur', 'change']}], state: [{required: true, message: '必填', trigger: ['blur', 'change']}], + reserveTimeType: [{required: true, message: '必填', trigger: ['blur', 'change']}], }, setUpTimeDialog: false, timeOneKeySet: { @@ -231,7 +356,218 @@ multipleSelection: [], } }, + computed: { + fullDayOpenHour() { + if (!Array.isArray(this.formData.openHours)) { + this.$set(this.formData, 'openHours', []) + } + if (this.formData.openHours.length === 0) { + this.formData.openHours.push({ startTime: '', endTime: '', timeUnit: 30 }) + } + if (!this.formData.openHours[0].timeUnit || this.formData.openHours[0].timeUnit <= 0) { + this.$set(this.formData.openHours[0], 'timeUnit', 30) + } + return this.formData.openHours[0] + } + }, methods: { + normalizeTimeUnit(value, defaultValue = 30) { + const num = Number(value) + if (!Number.isFinite(num) || num <= 0) { + return defaultValue + } + return Math.round(num) + }, + syncOpenHoursCache() { + if (this.formData.reserveTimeType === 1) { + this.segmentedOpenHoursCache = Array.isArray(this.formData.openHours) && this.formData.openHours.length > 0 + ? clone(this.formData.openHours).map(item => ({ + startTime: item.startTime || '', + endTime: item.endTime || '', + timeUnit: this.normalizeTimeUnit(item.timeUnit), + })) + : [{ startTime: '', endTime: '', timeUnit: 30 }] + this.formData.segmentedOpenHours = clone(this.segmentedOpenHoursCache) + } else { + const first = (this.formData.openHours && this.formData.openHours[0]) || {} + this.fullDayOpenHourCache = { + startTime: first.startTime || '', + endTime: first.endTime || '', + timeUnit: this.normalizeTimeUnit(first.timeUnit), + } + this.formData.fullDayOpenHour = clone(this.fullDayOpenHourCache) + } + }, + buildSubmitFormData() { + this.syncOpenHoursCache() + const submitData = clone(this.formData) + submitData.segmentedOpenHours = clone(this.segmentedOpenHoursCache || []) + submitData.fullDayOpenHour = clone(this.fullDayOpenHourCache || { startTime: '', endTime: '', timeUnit: 30 }) + if (submitData.reserveTimeType === 1) { + submitData.openHours = clone(this.segmentedOpenHoursCache || []) + } else { + submitData.openHours = [clone(this.fullDayOpenHourCache || { startTime: '', endTime: '', timeUnit: 60 })] + } + return submitData + }, + initOpenHoursCache() { + const segmentedOpenHours = Array.isArray(this.formData.segmentedOpenHours) && this.formData.segmentedOpenHours.length > 0 + ? clone(this.formData.segmentedOpenHours) + : (Array.isArray(this.formData.openHours) ? clone(this.formData.openHours) : []) + const fullDayOpenHour = this.formData.fullDayOpenHour && Object.keys(this.formData.fullDayOpenHour).length > 0 + ? clone(this.formData.fullDayOpenHour) + : ((Array.isArray(this.formData.openHours) && this.formData.openHours.length > 0) ? clone(this.formData.openHours[0]) : {}) + if (this.formData.reserveTimeType === 2) { + this.fullDayOpenHourCache = { + startTime: fullDayOpenHour.startTime || '', + endTime: fullDayOpenHour.endTime || '', + timeUnit: this.normalizeTimeUnit(fullDayOpenHour.timeUnit), + } + this.segmentedOpenHoursCache = segmentedOpenHours && segmentedOpenHours.length > 0 + ? segmentedOpenHours.map(item => ({ + startTime: item.startTime || '', + endTime: item.endTime || '', + timeUnit: this.normalizeTimeUnit(item.timeUnit), + })) + : [{ startTime: '', endTime: '', timeUnit: 30 }] + this.formData.openHours = [clone(this.fullDayOpenHourCache)] + } else { + this.segmentedOpenHoursCache = segmentedOpenHours.length > 0 + ? segmentedOpenHours.map(item => ({ + startTime: item.startTime || '', + endTime: item.endTime || '', + timeUnit: this.normalizeTimeUnit(item.timeUnit), + })) + : [{ startTime: '', endTime: '', timeUnit: 30 }] + const first = this.segmentedOpenHoursCache[0] || {} + this.fullDayOpenHourCache = { + startTime: fullDayOpenHour.startTime || first.startTime || '', + endTime: fullDayOpenHour.endTime || first.endTime || '', + timeUnit: this.normalizeTimeUnit(fullDayOpenHour.timeUnit || first.timeUnit), + } + this.formData.openHours = clone(this.segmentedOpenHoursCache) + } + this.formData.segmentedOpenHours = clone(this.segmentedOpenHoursCache) + this.formData.fullDayOpenHour = clone(this.fullDayOpenHourCache) + }, + addOpenHour() { + if (!Array.isArray(this.formData.openHours)) { + this.$set(this.formData, 'openHours', []) + } + this.formData.openHours.push({ startTime: '', endTime: '', timeUnit: 30 }) + this.segmentedOpenHoursCache = clone(this.formData.openHours) + }, + hasSegmentedOpenHoursOverlap(openHours) { + const validHours = (openHours || []) + .map((item, index) => ({ ...item, index })) + .filter(item => item.startTime && item.endTime && item.startTime < item.endTime) + .sort((a, b) => a.startTime.localeCompare(b.startTime)) + for (let i = 1; i < validHours.length; i++) { + const prev = validHours[i - 1] + const current = validHours[i] + if (current.startTime < prev.endTime) { + return { + overlap: true, + prevIndex: prev.index, + currentIndex: current.index, + } + } + } + return { overlap: false } + }, + checkSegmentedOpenHoursOverlap() { + if (this.formData.reserveTimeType !== 1) { + return false + } + const result = this.hasSegmentedOpenHoursOverlap(this.formData.openHours) + if (result.overlap) { + this.$message.warning('第' + (result.prevIndex + 1) + '条和第' + (result.currentIndex + 1) + '条场次时间有重叠,请调整后再保存') + return true + } + return false + }, + handleReserveTimeTypeChange(val) { + if (!Array.isArray(this.formData.openHours)) { + this.$set(this.formData, 'openHours', []) + } + if (val === 1) { + const currentFullDay = this.formData.openHours[0] || this.fullDayOpenHourCache || {} + this.fullDayOpenHourCache = { + startTime: currentFullDay.startTime || '', + endTime: currentFullDay.endTime || '', + timeUnit: this.normalizeTimeUnit(currentFullDay.timeUnit), + } + if (!Array.isArray(this.segmentedOpenHoursCache) || this.segmentedOpenHoursCache.length === 0) { + this.segmentedOpenHoursCache = [{ + startTime: this.fullDayOpenHourCache.startTime || '', + endTime: this.fullDayOpenHourCache.endTime || '', + timeUnit: this.normalizeTimeUnit(this.fullDayOpenHourCache.timeUnit), + }] + } + this.formData.openHours = clone(this.segmentedOpenHoursCache) + } else if (val === 2) { + this.segmentedOpenHoursCache = Array.isArray(this.formData.openHours) && this.formData.openHours.length > 0 + ? clone(this.formData.openHours) + : this.segmentedOpenHoursCache + const first = this.fullDayOpenHourCache && (this.fullDayOpenHourCache.startTime || this.fullDayOpenHourCache.endTime) + ? this.fullDayOpenHourCache + : (this.segmentedOpenHoursCache[0] || {}) + this.fullDayOpenHourCache = { + startTime: first.startTime || '', + endTime: first.endTime || '', + timeUnit: this.normalizeTimeUnit(first.timeUnit || this.fullDayOpenHourCache.timeUnit), + } + this.formData.openHours = [clone(this.fullDayOpenHourCache)] + } + }, + validateOpenHours() { + const openHours = this.formData.openHours || [] + if (this.formData.reserveTimeType === 1) { + if (openHours.length === 0) { + this.$message.warning('请至少添加一条场次信息') + return false + } + for (let i = 0; i < openHours.length; i++) { + const item = openHours[i] || {} + if (!item.startTime) { + this.$message.warning('第' + (i + 1) + '条场次信息中,开始时间必填') + return false + } + if (!item.endTime) { + this.$message.warning('第' + (i + 1) + '条场次信息中,结束时间必填') + return false + } + if (item.startTime >= item.endTime) { + this.$message.warning('第' + (i + 1) + '条场次信息中,开始时间必须早于结束时间') + return false + } + if (!item.timeUnit || item.timeUnit <= 0) { + this.$message.warning('第' + (i + 1) + '条场次信息中,预约时间单位必须大于0分钟') + return false + } + } + const overlapResult = this.hasSegmentedOpenHoursOverlap(openHours) + if (overlapResult.overlap) { + this.$message.warning('第' + (overlapResult.prevIndex + 1) + '条和第' + (overlapResult.currentIndex + 1) + '条场次时间有重叠,请调整后再保存') + return false + } + } else { + const item = openHours[0] || {} + if (!item.startTime || !item.endTime) { + this.$message.warning('请设置全天候预约的开始时间和结束时间') + return false + } + if (item.startTime >= item.endTime) { + this.$message.warning('全天候预约的开始时间必须早于结束时间') + return false + } + if (!item.timeUnit || item.timeUnit <= 0) { + this.$message.warning('全天候预约的预约时间单位必须大于0分钟') + return false + } + } + return true + }, doConfirmSetUpCourse() { const timeList = this.formData.notApplyTimeList if (timeList && timeList.length > 0) { @@ -296,40 +632,103 @@ if (this.formData.notApplyTimeList) { this.$set(this.formData, 'setUpDate', this.formData.notApplyTimeList.map(o => o.date)) } + this.activeTab = 'siteSetting' this.setUpTimeDialog = true }, onOpen(row) { + this.activeTab = 'basicInfo' if (row && row.id) { this.formData = clone(row) if (!Array.isArray(this.formData.notApplyTimeList)) { this.$set(this.formData, 'notApplyTimeList', []) } + if (!this.formData.reserveTimeType) { + this.$set(this.formData, 'reserveTimeType', 1) + } + if (!Array.isArray(this.formData.openHours) || this.formData.openHours.length === 0) { + this.$set(this.formData, 'openHours', this.formData.reserveTimeType === 2 ? [{ startTime: '', endTime: '', timeUnit: 30 }] : [{ startTime: '', endTime: '', timeUnit: 30 }]) + } + if (!Array.isArray(this.formData.segmentedOpenHours) || this.formData.segmentedOpenHours.length === 0) { + this.$set(this.formData, 'segmentedOpenHours', [{ startTime: '', endTime: '', timeUnit: 30 }]) + } + if (!this.formData.fullDayOpenHour) { + this.$set(this.formData, 'fullDayOpenHour', { startTime: '', endTime: '', timeUnit: 30 }) + } + this.initOpenHoursCache() } else { this.formData = { state: true, sexLimit: 0, filterHolidays: false, + reserveTimeType: 1, + openHours: [{ startTime: '', endTime: '', timeUnit: 30 }], + segmentedOpenHours: [{ startTime: '', endTime: '', timeUnit: 30 }], + fullDayOpenHour: { startTime: '', endTime: '', timeUnit: 30 }, notApplyTimeList: [], createUserName: this.$store.state.user.username, createUserId: this.$store.state.user.id, } + this.segmentedOpenHoursCache = [{ startTime: '', endTime: '', timeUnit: 30 }] + this.fullDayOpenHourCache = { startTime: '', endTime: '', timeUnit: 30 } } }, + async doSave(stayOnPage) { + if (!this.validateOpenHours()) { + this.activeTab = 'siteSetting' + return + } + const submitData = this.buildSubmitFormData() + const resp = await this.$axios.post("/platform/siteCug/manage/submit", {data: JSON.stringify(submitData)}) + if (resp.code === 0) { + this.$message.success(stayOnPage ? '暂存成功,您可以继续编辑当前内容' : '正式提交成功,已返回列表页') + if (resp.data) { + this.formData = clone(resp.data) + if (!Array.isArray(this.formData.notApplyTimeList)) { + this.$set(this.formData, 'notApplyTimeList', []) + } + if (!this.formData.reserveTimeType) { + this.$set(this.formData, 'reserveTimeType', 1) + } + if (!Array.isArray(this.formData.openHours) || this.formData.openHours.length === 0) { + this.$set(this.formData, 'openHours', this.formData.reserveTimeType === 2 ? [{ startTime: '', endTime: '', timeUnit: 30 }] : [{ startTime: '', endTime: '', timeUnit: 30 }]) + } + if (!Array.isArray(this.formData.segmentedOpenHours) || this.formData.segmentedOpenHours.length === 0) { + this.$set(this.formData, 'segmentedOpenHours', [{ startTime: '', endTime: '', timeUnit: 30 }]) + } + if (!this.formData.fullDayOpenHour) { + this.$set(this.formData, 'fullDayOpenHour', { startTime: '', endTime: '', timeUnit: 30 }) + } + this.initOpenHoursCache() + } + if (!stayOnPage) { + this.$emit('refresh') + } + } else { + this.$message.warning(resp.msg) + } + }, + onTempSave() { + this.$refs.formRef.validate(async (valid) => { + if (valid) { + this.$confirm("确认暂存当前内容,并继续留在本页编辑吗?", "暂存确认", { + confirmButtonText: "确认暂存", + cancelButtonText: "取消", + type: "warning" + }).then(async () => { + await this.doSave(true) + }).catch(() => {}) + } + }) + }, onSubmit() { this.$refs.formRef.validate((valid) => { if (valid) { - this.$confirm("您确定要提交吗?", "提示", { - confirmButtonText: "确定", + this.$confirm("确认正式提交当前内容吗?提交成功后将返回列表页。", "正式提交确认", { + confirmButtonText: "确认提交", cancelButtonText: "取消", - type: "warning" + type: "primary" }).then(async () => { - const resp = await this.$axios.post("/platform/siteCug/manage/submit", {data: JSON.stringify(this.formData)}) - if (resp.code === 0) { - this.$message.success(resp.msg) - this.$emit('refresh') - } else { - this.$message.warning(resp.msg) - } + await this.doSave(false) }) } }) diff --git a/src/main/resources/views/platform/zhgh/dayofficework/siteCug/manage/index.html b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/manage/index.html index 8e319045..ce353561 100644 --- a/src/main/resources/views/platform/zhgh/dayofficework/siteCug/manage/index.html +++ b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/manage/index.html @@ -39,15 +39,15 @@ layout("/layouts/platform.html"){ +