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 new file mode 100644 index 00000000..13848bbb --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugApplyController.java @@ -0,0 +1,264 @@ +package com.budwk.app.zhgh.dayofficework.siteCug.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaMode; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.StrUtil; +import com.budwk.app.base.annotation.SLog; +import com.budwk.app.base.page.Pagination; +import com.budwk.app.base.param.PageForm; +import com.budwk.app.base.result.Result; +import com.budwk.app.base.utils.PageUtil; +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.ProcessSubmitTypeEnum; +import com.budwk.app.sys.models.SysHoliday; +import com.budwk.app.web.commons.auth.utils.SecurityUtil; +import com.budwk.app.zhgh.dayofficework.siteCug.model.SiteCugApply; +import com.budwk.app.zhgh.dayofficework.siteCug.model.SiteCugFunctionType; +import com.budwk.app.zhgh.dayofficework.siteCug.model.SiteCugInfo; +import com.budwk.app.zhgh.dayofficework.siteCug.service.SiteCugApplyService; +import com.budwk.app.zhgh.dayofficework.siteCug.service.SiteCugInfoService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +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.util.cri.SqlExpressionGroup; +import org.nutz.ioc.aop.Aop; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.util.NutMap; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; + +import java.time.Year; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.UUID; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@IocBean +@Ok("json:full") +@Api(tags = "场地预约-场馆") +@At("/platform/siteCug/apply") +public class SiteCugApplyController { + + private static final String YEARLY_BATCH_PREFIX = "YEARLY_BATCH:"; + + @Inject + private Dao dao; + @Inject + private SiteCugInfoService infoService; + @Inject + private SiteCugApplyService applyService; + @Inject + private FlowEngine flowEngine; + + @At("/") + @SaCheckPermission("siteCug.apply") + @Ok("beetl:/platform/zhgh/dayofficework/siteCug/apply/index.html") + 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() {} + + @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() {} + + @At + @ApiOperation("分页查询场地") + @SaCheckPermission(value = {"siteCug.apply", "h5.siteCug.apply"}, mode = SaMode.OR) + public Result pageData(PageForm pageForm, @Param("type") String type) { + Cnd cnd = Cnd.NEW(); + cnd.and(SiteCugInfo::getState, "=", true); + cnd.andEX(SiteCugInfo::getTypeId, "=", type); + if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) { + SqlExpressionGroup seg = new SqlExpressionGroup(); + seg.or(SiteCugInfo::getName, "like", "%" + pageForm.getSearchKeyword() + "%"); + seg.or(SiteCugInfo::getAddress, "like", "%" + pageForm.getSearchKeyword() + "%"); + cnd.and(seg); + } + if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) { + cnd.asc("sortNum * 1"); + } else { + cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy())); + } + Pagination pagination = infoService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), cnd); + List listMap = pagination.getList(NutMap.class); + + List typeList = dao.query(SiteCugFunctionType.class, Cnd.NEW()); + Map typeMap = typeList.stream().collect(Collectors.toMap(SiteCugFunctionType::getId, SiteCugFunctionType::getName)); + for (NutMap map : listMap) { + map.put("typeName", typeMap.get(map.getString("typeId"))); + } + return Result.success(pagination); + } + + @At + @ApiOperation("提交预约") + @SaCheckPermission(value = {"siteCug.apply", "h5.siteCug.apply"}, mode = SaMode.OR) + @SLog(tag = "场馆功能管理-场地预约", msg = "提交场地预约") + public Result submit(@Param("data") SiteCugApply apply) { + Map validate = applyService.validateApply(apply); + if (validate.containsKey(false)) { + return Result.error(validate.get(false)); + } + submitSingleApply(apply); + return Result.success(); + } + + @At("/submitYearly") + @ApiOperation("按本年后续同星期同时间段批量提交预约") + @Aop(TransAop.READ_COMMITTED) + @SaCheckPermission(value = {"siteCug.apply", "h5.siteCug.apply"}, mode = SaMode.OR) + @SLog(tag = "场馆功能管理-场地预约", msg = "批量提交本年场地预约") + public Result submitYearly(@Param("data") SiteCugApply apply) { + List applyList = buildYearlyApplyList(apply); + if (applyList.isEmpty()) { + return Result.error("未生成可预约的日期"); + } + for (SiteCugApply item : applyList) { + Map validate = applyService.validateApply(item); + if (validate.containsKey(false)) { + return Result.error(String.format("%s 预约失败:%s", item.getReserveStartTime(), validate.get(false))); + } + } + String yearlyBatchNo = YEARLY_BATCH_PREFIX + UUID.randomUUID(); + for (SiteCugApply item : applyList) { + item.setBackOption(yearlyBatchNo); + submitSingleApply(item); + } + return Result.success(String.format("已成功预约本年剩余%d个时间段", applyList.size())); + } + + @At + @ApiOperation("查询预约限制配置") + @SaCheckPermission(value = {"siteCug.apply", "h5.siteCug.apply"}, mode = SaMode.OR) + public Result timeLimitConfig(@Param("siteId") String siteId) { + if (StrUtil.isBlank(siteId)) { + return Result.success(NutMap.NEW().addv("filterHolidays", false).addv("holidayList", new ArrayList<>()).addv("notApplyTimeList", new ArrayList<>())); + } + SiteCugInfo info = infoService.fetch(siteId); + if (info == null) { + return Result.success(NutMap.NEW().addv("filterHolidays", false).addv("holidayList", new ArrayList<>()).addv("notApplyTimeList", new ArrayList<>())); + } + List holidayList = new ArrayList<>(); + if (Boolean.TRUE.equals(info.getFilterHolidays())) { + int currentYear = Year.now().getValue(); + List yearPrefixList = List.of(currentYear + "-", (currentYear + 1) + "-"); + List holidays = dao.query(SysHoliday.class, Cnd.NEW()); + holidayList = holidays.stream() + .map(SysHoliday::getDay) + .filter(day -> day != null && yearPrefixList.stream().anyMatch(day::startsWith)) + .distinct() + .collect(Collectors.toList()); + } + return Result.success(NutMap.NEW() + .addv("filterHolidays", Boolean.TRUE.equals(info.getFilterHolidays())) + .addv("holidayList", holidayList) + .addv("notApplyTimeList", info.getNotApplyTimeList() == null ? new ArrayList<>() : info.getNotApplyTimeList())); + } + + private List buildYearlyApplyList(SiteCugApply apply) { + if (apply == null || StrUtil.hasBlank(apply.getReserveStartTime(), apply.getReserveEndTime())) { + return new ArrayList<>(); + } + DateTime start = DateUtil.parseDateTime(apply.getReserveStartTime()); + DateTime end = DateUtil.parseDateTime(apply.getReserveEndTime()); + if (!end.isAfter(start)) { + return new ArrayList<>(); + } + SiteCugInfo siteInfo = infoService.fetch(apply.getSiteId()); + if (siteInfo == null) { + return new ArrayList<>(); + } + List holidayList = new ArrayList<>(); + if (Boolean.TRUE.equals(siteInfo.getFilterHolidays())) { + int currentYear = start.year(); + List yearPrefixList = List.of(currentYear + "-", (currentYear + 1) + "-"); + List holidays = dao.query(SysHoliday.class, Cnd.NEW()); + holidayList = holidays.stream() + .map(SysHoliday::getDay) + .filter(day -> day != null && yearPrefixList.stream().anyMatch(day::startsWith)) + .distinct() + .collect(Collectors.toList()); + } + int targetWeek = start.dayOfWeek() - 1; + int startHour = start.hour(true); + int startMinute = start.minute(); + int startSecond = start.second(); + int endHour = end.hour(true); + int endMinute = end.minute(); + int endSecond = end.second(); + Date current = DateUtil.beginOfDay(start); + Date endOfYear = DateUtil.endOfYear(start); + List result = new ArrayList<>(); + while (current.compareTo(endOfYear) <= 0) { + DateTime currentDate = DateUtil.date(current); + String currentDay = DateUtil.formatDate(currentDate); + if (currentDate.dayOfWeek() - 1 == targetWeek && !holidayList.contains(currentDay)) { + SiteCugApply item = copyApply(apply); + item.setId(null); + item.setReserveStartTime(DateUtil.formatDateTime(buildDateTime(currentDate, startHour, startMinute, startSecond))); + item.setReserveEndTime(DateUtil.formatDateTime(buildDateTime(currentDate, endHour, endMinute, endSecond))); + result.add(item); + } + current = DateUtil.offsetDay(current, 1); + } + return result; + } + + 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)); + } + + private SiteCugApply copyApply(SiteCugApply apply) { + return new SiteCugApply() + .setSiteId(apply.getSiteId()) + .setApplyUserId(apply.getApplyUserId()) + .setApplyUserName(apply.getApplyUserName()) + .setApplySex(apply.getApplySex()) + .setApplyLoginName(apply.getApplyLoginName()) + .setApplyUnitId(apply.getApplyUnitId()) + .setApplyUnitName(apply.getApplyUnitName()) + .setReserveType(apply.getReserveType()) + .setApplyUnionId(apply.getApplyUnionId()) + .setApplyUnionName(apply.getApplyUnionName()) + .setClubId(apply.getClubId()) + .setClubName(apply.getClubName()) + .setApplyMobile(apply.getApplyMobile()) + .setReserveStartTime(apply.getReserveStartTime()) + .setReserveEndTime(apply.getReserveEndTime()) + .setJoinCount(apply.getJoinCount()) + .setApplyCause(apply.getApplyCause()) + .setBackOption(apply.getBackOption()); + } + + private void submitSingleApply(SiteCugApply apply) { + dao.insertOrUpdate(apply); + + Dict args = Dict.create(); + args.set(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.APPLY.getCode()); + args.set(FlowConst.FORM_DATA, apply); + ProcessInstance instance = flowEngine.startProcessInstanceByKey("CDYY", apply.getId(), SecurityUtil.getUserId(), args); + + List doingTaskList = flowEngine.processTaskService().getDoingTaskList(instance.getId(), null); + for (ProcessTask task : doingTaskList) { + 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/SiteCugFunctionTypeController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugFunctionTypeController.java new file mode 100644 index 00000000..65c19078 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugFunctionTypeController.java @@ -0,0 +1,85 @@ +package com.budwk.app.zhgh.dayofficework.siteCug.controller; + +import cn.dev33.satoken.annotation.SaCheckLogin; +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.hutool.core.util.StrUtil; +import com.budwk.app.base.annotation.SLog; +import com.budwk.app.base.page.Pagination; +import com.budwk.app.base.param.PageForm; +import com.budwk.app.base.result.Result; +import com.budwk.app.base.utils.PageUtil; +import com.budwk.app.zhgh.dayofficework.siteCug.model.SiteCugFunctionType; +import com.budwk.app.zhgh.dayofficework.siteCug.service.SiteCugFunctionTypeService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.nutz.dao.Cnd; +import org.nutz.dao.util.cri.SqlExpressionGroup; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; + +import java.util.List; + +@Slf4j +@IocBean +@Ok("json:full") +@Api(tags = "功能类型") +@At("/platform/siteCug/function/type") +public class SiteCugFunctionTypeController { + + @Inject + private SiteCugFunctionTypeService typeService; + + @At("") + @SaCheckPermission("siteCug.function.type") + @Ok("beetl:/platform/zhgh/dayofficework/siteCug/type/index.html") + public void index() {} + + @At + @ApiOperation("分页查询") + @SaCheckPermission("siteCug.function.type") + public Result pageData(PageForm pageForm) { + Cnd cnd = Cnd.NEW(); + if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) { + SqlExpressionGroup seg = new SqlExpressionGroup(); + seg.or(SiteCugFunctionType::getName, "like", "%" + pageForm.getSearchKeyword() + "%"); + seg.or(SiteCugFunctionType::getCode, "like", "%" + pageForm.getSearchKeyword() + "%"); + cnd.and(seg); + } + if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) { + cnd.asc("code"); + } else { + cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy())); + } + Pagination pagination = typeService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), cnd); + return Result.success(pagination); + } + + @At + @ApiOperation("新增/修改功能类型") + @SaCheckPermission("siteCug.function.type") + @SLog(tag = "场馆功能管理-功能类型", msg = "新增/修改功能类型") + public Object submit(SiteCugFunctionType type) { + typeService.insertOrUpdate(type); + return Result.success(); + } + + @At + @ApiOperation("删除功能类型") + @SaCheckPermission("siteCug.function.type") + @SLog(tag = "场馆功能管理-功能类型", msg = "删除功能类型") + public Object delete(String id) { + typeService.delete(id); + return Result.success(); + } + + @At + @ApiOperation("查询功能类型") + @SaCheckLogin + public Result queryFunctionType() { + List list = typeService.query(Cnd.where(SiteCugFunctionType::getEnable, "=", true).asc(SiteCugFunctionType::getCode)); + return Result.success(list); + } +} 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 new file mode 100644 index 00000000..41dc9c90 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugManageController.java @@ -0,0 +1,132 @@ +package com.budwk.app.zhgh.dayofficework.siteCug.controller; + +import cn.dev33.satoken.annotation.SaCheckLogin; +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.hutool.core.util.StrUtil; +import com.budwk.app.base.annotation.SLog; +import com.budwk.app.base.page.Pagination; +import com.budwk.app.base.param.PageForm; +import com.budwk.app.base.result.Result; +import com.budwk.app.base.utils.PageUtil; +import com.budwk.app.zhgh.dayofficework.siteCug.model.SiteCugFunctionType; +import com.budwk.app.zhgh.dayofficework.siteCug.model.SiteCugInfo; +import com.budwk.app.zhgh.dayofficework.siteCug.service.SiteCugInfoService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.dao.util.cri.SqlExpressionGroup; +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 org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@IocBean +@Ok("json:full") +@Api(tags = "场地管理-场馆") +@At("/platform/siteCug/manage") +public class SiteCugManageController { + + @Inject + private Dao dao; + @Inject + private SiteCugInfoService infoService; + + @At("") + @SaCheckPermission("siteCug.manage") + @Ok("beetl:/platform/zhgh/dayofficework/siteCug/manage/index.html") + public void index() {} + + @At + @ApiOperation("分页查询") + @SaCheckPermission("siteCug.manage") + public Result pageData(PageForm pageForm, @Param("type") String type) { + Cnd cnd = Cnd.NEW(); + cnd.andEX(SiteCugInfo::getTypeId, "=", type); + if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) { + SqlExpressionGroup seg = new SqlExpressionGroup(); + seg.or(SiteCugInfo::getName, "like", "%" + pageForm.getSearchKeyword() + "%"); + seg.or(SiteCugInfo::getAddress, "like", "%" + pageForm.getSearchKeyword() + "%"); + cnd.and(seg); + } + if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) { + cnd.asc("sortNum * 1"); + } else { + cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy())); + } + Pagination pagination = infoService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), cnd); + List listMap = pagination.getList(NutMap.class); + + List typeList = dao.query(SiteCugFunctionType.class, Cnd.NEW()); + Map typeMap = typeList.stream().collect(Collectors.toMap(SiteCugFunctionType::getId, SiteCugFunctionType::getName)); + + for (NutMap map : listMap) { + map.put("typeName", typeMap.get(map.getString("typeId"))); + } + return Result.success(pagination); + } + + @At + @ApiOperation("新增/修改场地") + @SaCheckPermission("siteCug.manage") + @SLog(tag = "场馆功能管理-场地管理", msg = "新增/修改场地") + public Object submit(@Param("data") SiteCugInfo info) { + List notApplyTimeList = info.getNotApplyTimeList(); + if (notApplyTimeList != null) { + boolean invalid = notApplyTimeList.stream().anyMatch(item -> + item == null + || StrUtil.isBlank(item.getString("date")) + || StrUtil.isBlank(item.getString("startTime")) + || StrUtil.isBlank(item.getString("endTime")) + || item.getString("startTime").compareTo(item.getString("endTime")) >= 0); + if (invalid) { + return Result.error("禁用时间设置有误"); + } + } + if (info.getFilterHolidays() == null) { + info.setFilterHolidays(false); + } + infoService.insertOrUpdate(info); + return Result.success(); + } + + @At + @ApiOperation("删除场地") + @SaCheckPermission("siteCug.manage") + @SLog(tag = "场馆功能管理-场地管理", msg = "删除场地") + public Object delete(String id) { + infoService.delete(id); + return Result.success(); + } + + @At + @ApiOperation("查询场地") + @SaCheckLogin + public Result querySites() { + List list = infoService.query(Cnd.where(SiteCugInfo::getState, "=", true).desc(SiteCugInfo::getSortNum)); + return Result.success(list); + } + + @At + @ApiOperation("查询单个场地") + @SaCheckLogin + public Result info(String id) { + SiteCugInfo info = infoService.fetch(id); + if (info == null) { + return Result.success(null); + } + NutMap map = Lang.obj2nutmap(info); + SiteCugFunctionType type = dao.fetch(SiteCugFunctionType.class, info.getTypeId()); + map.put("typeName", type == null ? "" : type.getName()); + return Result.success(map); + } +} 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 new file mode 100644 index 00000000..c02f9a8d --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugMineController.java @@ -0,0 +1,184 @@ +package com.budwk.app.zhgh.dayofficework.siteCug.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaMode; +import cn.hutool.core.util.StrUtil; +import com.budwk.app.base.annotation.SLog; +import com.budwk.app.base.page.Pagination; +import com.budwk.app.base.param.PageForm; +import com.budwk.app.base.result.Result; +import com.budwk.app.base.utils.PageUtil; +import com.budwk.app.flow.engine.FlowEngine; +import com.budwk.app.web.commons.auth.utils.SecurityUtil; +import com.budwk.app.zhgh.dayofficework.siteCug.model.SiteCugApply; +import com.budwk.app.zhgh.dayofficework.siteCug.service.SiteCugApplyService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.dao.Cnd; +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; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.util.NutMap; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; + +import java.util.ArrayList; +import java.util.List; + +@Slf4j +@IocBean +@Ok("json:full") +@Api(tags = "场地预约-我的预约") +@At("/platform/siteCug/mine") +public class SiteCugMineController { + + private static final String YEARLY_BATCH_PREFIX = "YEARLY_BATCH:"; + + @Inject + private FlowEngine flowEngine; + @Inject + private SiteCugApplyService applyService; + + @At("/") + @Ok("beetl:/platform/zhgh/dayofficework/siteCug/mine/index.html") + @SaCheckPermission("siteCug.mine") + public void index() { + } + + @At("/h5") + @Ok("beetl:/platform/zhghh5/dayofficework/siteCug/mine/index.html") + @SaCheckPermission(value = {"siteCug.mine", "h5.siteCug.mine"}, mode = SaMode.OR) + public void h5Index() { + } + + @At + @ApiOperation("分页查询我的预约") + @SaCheckPermission(value = {"siteCug.mine", "h5.siteCug.mine"}, mode = SaMode.OR) + public Result pageData(PageForm pageForm, + @Param("siteType") String siteType, + @Param("siteId") String siteId) { + Sql sql = Sqls.create(""" + SELECT + info.*, + si.name as siteName, + ins.id AS instanceId, + ins.businessNo, + ins.state instanceState, + ins.variable instanceVariable, + ins.processDefineId instanceProcessDefineId, + t.id taskId, + t.taskName AS taskKey, + t.displayName taskName, + t.taskType, + t.performType taskPerformType, + t.taskState, + t.finishTime, + t.taskParentId, + t.variable taskVariable, + CASE + WHEN ins.state in (10, 20) AND (t.displayName LIKE '%校工会允许%' OR t.taskName = 'c8c03407-cf26-4e0b-82f1-b2afc9c79996') + THEN 1 + ELSE 0 + END canCancel, + IF((SELECT taskName FROM wf_process_task tt WHERE tt.id = t.taskParentId) = 'startTask', 1, 0) canRevoke, + (select MAX(id) from wf_process_task where processInstanceId = ins.id and taskName = 'startTask') AS startTaskId, + CASE WHEN info.backOption LIKE 'YEARLY_BATCH:%' THEN 1 ELSE 0 END AS yearlyBatch, + CASE WHEN info.backOption LIKE 'YEARLY_BATCH:%' THEN ( + SELECT COUNT(1) FROM site_cug_apply batch_info WHERE batch_info.backOption = info.backOption AND batch_info.createdBy = info.createdBy + ) ELSE 1 END AS batchDeleteCount + FROM + site_cug_apply info + LEFT JOIN site_cug_info si ON info.siteId = si.id + LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id + LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id AND t.taskState = 10 + LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id + $condition + """); + Cnd cnd = Cnd.NEW(); + if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) { + 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); + cnd.andEX("info.siteId", "=", siteId); + cnd.and("info.createdBy", "=", SecurityUtil.getUserId()); + + if (StrUtil.isNotBlank(pageForm.getPageOrderName()) && StrUtil.isNotBlank(pageForm.getPageOrderBy())) { + cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy())); + } else { + cnd.desc("info.createdAt"); + } + cnd.groupBy("info.id"); + sql.setCondition(cnd); + Pagination pagination = applyService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); + return Result.success(pagination); + } + + @At + @ApiOperation("取消我的预约") + @Aop(TransAop.READ_COMMITTED) + @SaCheckPermission(value = {"siteCug.mine", "h5.siteCug.mine"}, mode = SaMode.OR) + @SLog(tag = "场馆功能管理-我的预约", msg = "取消场地预约") + public Result delete(@Param("id") String id) { + SiteCugApply apply = applyService.fetch(id); + if (apply == null) { + return Result.error("预约记录不存在"); + } + if (!StrUtil.equals(apply.getCreatedBy(), SecurityUtil.getUserId())) { + return Result.error("无权取消该预约"); + } + + List deleteIds = new ArrayList<>(); + if (StrUtil.isNotBlank(apply.getBackOption()) && apply.getBackOption().startsWith(YEARLY_BATCH_PREFIX)) { + List batchList = applyService.query(Cnd.where("backOption", "=", apply.getBackOption()).and("createdBy", "=", SecurityUtil.getUserId())); + deleteIds.addAll(batchList.stream().map(SiteCugApply::getId).toList()); + } else { + deleteIds.add(id); + } + if (deleteIds.isEmpty()) { + return Result.error("未找到可取消的预约记录"); + } + + for (String deleteId : deleteIds) { + Sql sql = Sqls.create(""" + SELECT + CASE + WHEN ins.state in (10, 20) AND (t.displayName LIKE '%校工会允许%' OR t.taskName = 'c8c03407-cf26-4e0b-82f1-b2afc9c79996') + THEN 1 + ELSE 0 + END canCancel + FROM + site_cug_apply info + LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id + LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id AND t.taskState = 10 + WHERE info.id = @id + """); + sql.params().set("id", deleteId); + sql.setCallback(Sqls.callback.maps()); + applyService.dao().execute(sql); + List list = sql.getList(NutMap.class); + boolean canCancel = list != null && !list.isEmpty() && list.get(0).getInt("canCancel", 0) == 1; + if (!canCancel) { + return Result.error("当前批次中存在不可取消的预约,无法整批删除"); + } + } + + for (String deleteId : deleteIds) { + applyService.delete(deleteId); + flowEngine.processInstanceService().deleteProcessInstanceByBusinessKey(deleteId); + } + if (deleteIds.size() > 1) { + return Result.success("已取消该批次下全部预约记录"); + } + return Result.success("取消成功"); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugRecordController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugRecordController.java new file mode 100644 index 00000000..6baeb458 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugRecordController.java @@ -0,0 +1,98 @@ +package com.budwk.app.zhgh.dayofficework.siteCug.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.budwk.app.base.page.Pagination; +import com.budwk.app.base.result.Result; +import com.budwk.app.flow.engine.FlowEngine; +import com.budwk.app.flow.enums.ProcessInstanceStateEnum; +import com.budwk.app.zhgh.dayofficework.siteCug.model.SiteCugApply; +import com.budwk.app.zhgh.dayofficework.siteCug.param.SiteCugRecordPageForm; +import com.budwk.app.zhgh.dayofficework.siteCug.service.SiteCugApplyService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.dao.Cnd; +import org.nutz.dao.Sqls; +import org.nutz.dao.sql.Sql; +import org.nutz.ioc.aop.Aop; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.util.NutMap; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +@IocBean +@Ok("json:full") +@Api(tags = "场馆预约查询") +@At("/platform/siteCug/record") +public class SiteCugRecordController { + + @Inject + private FlowEngine flowEngine; + @Inject + private SiteCugApplyService applyService; + + @At("") + @Ok("beetl:/platform/zhgh/dayofficework/siteCug/record/index.html") + @SaCheckPermission("siteCug.record") + public void index() { + } + + @At + @ApiOperation("分页查询") + @SaCheckPermission("siteCug.record") + public Result pageData(SiteCugRecordPageForm pageForm) { + // 查询页只负责调用 service 公共查询方法,controller 不再维护重复 SQL/Cnd 逻辑 + Sql sql = applyService.buildRecordSql(); + Cnd cnd = applyService.buildRecordCnd(pageForm); + sql.setCondition(cnd); + Pagination pageVO = applyService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); + return Result.success(pageVO); + } + + @At + @ApiOperation("删除预约记录") + @Aop(TransAop.READ_COMMITTED) + @SaCheckPermission("siteCug.record") + public Result delete(@Param("id") String id) { + SiteCugApply apply = applyService.fetch(id); + if (apply == null) { + return Result.error("记录不存在"); + } + + Sql sql = Sqls.create(""" + SELECT + ins.state AS instanceState + FROM + site_cug_apply info + LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id + WHERE info.id = @id + """); + sql.params().set("id", id); + sql.setCallback(Sqls.callback.maps()); + applyService.dao().execute(sql); + List list = sql.getList(NutMap.class); + Integer state = list != null && !list.isEmpty() ? list.get(0).getInt("instanceState") : null; + // 预约查询页只展示审核结束数据,这里也只允许删除已完成/已拒绝的流程记录 + if (state == null || !List.of(ProcessInstanceStateEnum.FINISHED.getCode(), ProcessInstanceStateEnum.REJECT.getCode()).contains(state)) { + return Result.error("仅可删除审核结束的预约记录"); + } + + applyService.delete(id); + flowEngine.processInstanceService().deleteProcessInstanceByBusinessKey(id); + return Result.success(); + } + + @At + @Ok("void") + @ApiOperation("导出预约记录") + @SaCheckPermission("siteCug.record") + public void doExport(SiteCugRecordPageForm pageForm, HttpServletResponse response) { + // 导出逻辑统一下沉到 service,导出结果和当前筛选条件保持一致 + applyService.exportRecord(pageForm, response); + } +} 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 new file mode 100644 index 00000000..1ebe3d28 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugSchoolUnionAuditController.java @@ -0,0 +1,215 @@ +package com.budwk.app.zhgh.dayofficework.siteCug.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaMode; +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.StrUtil; +import com.budwk.app.base.constant.RoleConstant; +import com.budwk.app.base.page.Pagination; +import com.budwk.app.base.param.PageForm; +import com.budwk.app.base.result.Result; +import com.budwk.app.base.utils.PageUtil; +import com.budwk.app.flow.constant.FlowConst; +import com.budwk.app.flow.entity.ProcessTask; +import com.budwk.app.flow.enums.ProcessTaskStateEnum; +import com.budwk.app.flow.service.FlowCommonService; +import com.budwk.app.flow.service.ProcessTaskService; +import com.budwk.app.web.commons.auth.utils.SecurityUtil; +import com.budwk.app.zhgh.dayofficework.siteCug.model.SiteCugApply; +import com.budwk.app.zhgh.dayofficework.siteCug.service.SiteCugApplyService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.dao.Cnd; +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; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.util.NutMap; +import org.nutz.json.Json; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; + +import java.util.ArrayList; +import java.util.List; + +@IocBean +@At("/platform/siteCug/schoolUnionAudit") +@Api("场馆校工会审核") +@Ok("json:full") +public class SiteCugSchoolUnionAuditController { + + private static final String YEARLY_BATCH_PREFIX = "YEARLY_BATCH:"; + + @Inject + private SiteCugApplyService applyService; + @Inject + private ProcessTaskService processTaskService; + @Inject + private FlowCommonService flowCommonService; + + @At("/") + @Ok("beetl:/platform/zhgh/dayofficework/siteCug/schoolUnionAudit/index.html") + @SaCheckPermission("siteCug.schoolUnionAudit") + public void index() { + } + + @At("/h5") + @Ok("beetl:/platform/zhghh5/dayofficework/siteCug/schoolUnionAudit/index.html") + @SaCheckPermission("h5.siteCug.schoolUnionAudit") + public void h5Index() { + } + + @At + @ApiOperation("分页查询") + @SaCheckPermission(value = {"siteCug.schoolUnionAudit", "h5.siteCug.schoolUnionAudit"}, mode = SaMode.OR) + public Result pageData(PageForm pageForm, + @Param("siteType") String siteType, + @Param("siteId") String siteId, + @Param("approval") Boolean approval) { + Sql sql = Sqls.create(""" + SELECT + info.*, + si.name as siteName, + ins.id AS instanceId, + ins.businessNo, + ins.state instanceState, + ins.variable instanceVariable, + ins.processDefineId instanceProcessDefineId, + t.id taskId, + t.taskName AS taskKey, + t.displayName taskName, + t.taskType, + t.performType taskPerformType, + t.taskState, + t.finishTime, + t.taskParentId, + t.variable taskVariable, + IFNULL(GROUP_CONCAT(DISTINCT nt.displayName),'结束') curTaskName, + CASE WHEN EXISTS ( + SELECT 1 FROM wf_process_task rt + WHERE rt.processInstanceId = ins.id AND rt.taskParentId = t.id + ) THEN 1 ELSE 0 END AS canRevoke, + CASE WHEN info.backOption LIKE 'YEARLY_BATCH:%' THEN 1 ELSE 0 END AS yearlyBatch, + CASE WHEN info.backOption LIKE 'YEARLY_BATCH:%' THEN ( + SELECT COUNT(1) FROM site_cug_apply batch_info WHERE batch_info.backOption = info.backOption AND batch_info.createdBy = info.createdBy + ) ELSE 1 END AS batchAuditCount + FROM + wf_process_task t + LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId + LEFT JOIN site_cug_apply info ON info.id = ins.businessNo + LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id + LEFT JOIN site_cug_info si ON info.siteId = si.id + LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10 + LEFT JOIN wf_process_task rt ON rt.processInstanceId = ins.id AND rt.taskParentId = t.id + $condition + """); + Cnd cnd = Cnd.NEW(); + if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) { + 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); + cnd.andEX("info.siteId", "=", siteId); + cnd.and("t.taskName", "=", "c8c03407-cf26-4e0b-82f1-b2afc9c79996"); + if (!StpUtil.hasRole(RoleConstant.SYSADMIN.name())) { + cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId())); + } + if (Boolean.TRUE.equals(approval)) { + cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode())); + } else { + cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode()); + } + + if (StrUtil.isNotBlank(pageForm.getPageOrderName()) && StrUtil.isNotBlank(pageForm.getPageOrderBy())) { + cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy())); + } else { + cnd.desc("info.createdAt"); + } + cnd.groupBy("t.id"); + sql.setCondition(cnd); + Pagination pageVO = applyService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); + return Result.success(pageVO); + } + + @At("/executeTask") + @ApiOperation("执行审核任务") + @Aop(TransAop.READ_COMMITTED) + @SaCheckPermission(value = {"siteCug.schoolUnionAudit", "h5.siteCug.schoolUnionAudit"}, mode = SaMode.OR) + public Result executeTask(@Param("data") String data) { + Dict args = Json.fromJson(Dict.class, data); + Long processTaskId = args.getLong(FlowConst.PROCESS_TASK_ID_KEY); + ProcessTask currentTask = processTaskService.getById(processTaskId); + if (currentTask == null) { + return Result.error("审核任务不存在"); + } + + SiteCugApply apply = applyService.fetch(args.getStr("applyId")); + if (apply == null) { + return Result.error("预约记录不存在"); + } + + List taskList = new ArrayList<>(); + if (StrUtil.isNotBlank(apply.getBackOption()) && apply.getBackOption().startsWith(YEARLY_BATCH_PREFIX)) { + List batchList = applyService.query(Cnd.where("backOption", "=", apply.getBackOption()).and("createdBy", "=", apply.getCreatedBy())); + List bizIds = batchList.stream().map(SiteCugApply::getId).toList(); + taskList = processTaskService.getDoingTaskByBizIdTaskName(bizIds, currentTask.getTaskName()); + } + if (taskList.isEmpty()) { + taskList.add(currentTask); + } + + for (ProcessTask task : taskList) { + Dict cloneArgs = args.clone(); + cloneArgs.put(FlowConst.PROCESS_TASK_ID_KEY, task.getId()); + flowCommonService.executeTask(cloneArgs); + } + if (taskList.size() > 1) { + return Result.success("已完成该全年预约批次的审核"); + } + return Result.success("提交成功"); + } + + + @At("/revokeTask") + @ApiOperation("撤回审核任务") + @Aop(TransAop.READ_COMMITTED) + @SaCheckPermission(value = {"siteCug.schoolUnionAudit", "h5.siteCug.schoolUnionAudit"}, mode = SaMode.OR) + public Result revokeTask(@Param("taskId") Long taskId, @Param("applyId") String applyId) { + ProcessTask currentTask = processTaskService.getById(taskId); + if (currentTask == null) { + return Result.error("撤回任务不存在"); + } + + SiteCugApply apply = applyService.fetch(applyId); + if (apply == null) { + return Result.error("预约记录不存在"); + } + + List taskList = new ArrayList<>(); + if (StrUtil.isNotBlank(apply.getBackOption()) && apply.getBackOption().startsWith(YEARLY_BATCH_PREFIX)) { + List batchList = applyService.query(Cnd.where("backOption", "=", apply.getBackOption()).and("createdBy", "=", apply.getCreatedBy())); + List bizIds = batchList.stream().map(SiteCugApply::getId).toList(); + taskList = processTaskService.getDoneTaskByBizIdTaskName(bizIds, currentTask.getTaskName()); + } + if (taskList.isEmpty()) { + taskList.add(currentTask); + } + + for (ProcessTask task : taskList) { + flowCommonService.revokeTask(task.getId()); + } + if (taskList.size() > 1) { + return Result.success("已撤回该全年预约批次的审核任务"); + } + return Result.success("撤回成功"); + } + +} 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 new file mode 100644 index 00000000..d332d548 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/model/SiteCugApply.java @@ -0,0 +1,112 @@ +package com.budwk.app.zhgh.dayofficework.siteCug.model; + +import com.budwk.app.base.model.BaseModel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.nutz.dao.entity.annotation.*; +import org.nutz.dao.interceptor.annotation.PrevInsert; + +@Data +@Comment("场地预约-场馆") +@Accessors(chain = true) +@Table("site_cug_apply") +@EqualsAndHashCode(callSuper = true) +public class SiteCugApply extends BaseModel { + + @Name + @Comment("ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + @PrevInsert(uu32 = true) + private String id; + + @Column + @Comment("场地id") + @ColDefine(type = ColType.VARCHAR, width = 32) + private String siteId; + + @Column + @Comment("预约人") + @ColDefine(type = ColType.VARCHAR, width = 32) + private String applyUserId; + + @Column + @Comment("预约人姓名") + @ColDefine(type = ColType.VARCHAR, width = 30) + private String applyUserName; + + @Column + @Comment("性别") + @ColDefine(type = ColType.VARCHAR, width = 10) + private String applySex; + + @Column + @Comment("预约人工号") + @ColDefine(type = ColType.VARCHAR, width = 30) + private String applyLoginName; + + @Column + @Comment("预约人单位id") + @ColDefine(type = ColType.VARCHAR, width = 32) + private String applyUnitId; + + @Column + @Comment("预约人单位") + @ColDefine(type = ColType.VARCHAR, width = 100) + private String applyUnitName; + + @Column + @Comment("预约类型") + @ColDefine(type = ColType.VARCHAR, width = 20) + private String reserveType; + + @Column + @Comment("分工会id") + @ColDefine(type = ColType.VARCHAR, width = 32) + private String applyUnionId; + + @Column + @Comment("分工会名称") + @ColDefine(type = ColType.VARCHAR, width = 100) + private String applyUnionName; + + @Column + @Comment("协会id") + @ColDefine(type = ColType.VARCHAR, width = 32) + private String clubId; + + @Column + @Comment("协会名称") + @ColDefine(type = ColType.VARCHAR, width = 100) + private String clubName; + + @Column + @Comment("预约人联系方式") + @ColDefine(type = ColType.VARCHAR, width = 20) + private String applyMobile; + + @Column + @Comment("预约开始时间") + @ColDefine(type = ColType.VARCHAR, width = 30) + private String reserveStartTime; + + @Column + @Comment("预约结束时间") + @ColDefine(type = ColType.VARCHAR, width = 30) + private String reserveEndTime; + + @Column + @Comment("预约人数") + @ColDefine(type = ColType.INT) + private Integer joinCount; + + @Column + @Comment("预约事由") + @ColDefine(type = ColType.VARCHAR, width = 1000) + private String applyCause; + + @Column + @Comment("反馈意见") + @ColDefine(type = ColType.VARCHAR, width = 200) + private String backOption; +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/model/SiteCugFunctionType.java b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/model/SiteCugFunctionType.java new file mode 100644 index 00000000..60271de5 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/model/SiteCugFunctionType.java @@ -0,0 +1,43 @@ +package com.budwk.app.zhgh.dayofficework.siteCug.model; + +import com.budwk.app.base.model.BaseModel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.nutz.dao.entity.annotation.*; +import org.nutz.dao.interceptor.annotation.PrevInsert; + +@Data +@Comment("功能类型") +@Accessors(chain = true) +@Table("site_cug_function_type") +@EqualsAndHashCode(callSuper = true) +public class SiteCugFunctionType extends BaseModel { + + @Name + @Comment("ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + @PrevInsert(uu32 = true) + private String id; + + @Column + @ColDefine(type = ColType.VARCHAR, width = 30) + @Comment("类型编码") + private String code; + + @Column + @ColDefine(type = ColType.VARCHAR, width = 100) + @Comment("类型名称") + private String name; + + @Column + @ColDefine(type = ColType.BOOLEAN) + @Comment("是否启用") + @Default("1") + private Boolean enable; + + @Column + @ColDefine(type = ColType.INT) + @Comment("排序编号") + private Integer sortNum; +} 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 new file mode 100644 index 00000000..2e491ca9 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/model/SiteCugInfo.java @@ -0,0 +1,97 @@ +package com.budwk.app.zhgh.dayofficework.siteCug.model; + +import com.budwk.app.base.model.BaseModel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.nutz.dao.entity.annotation.*; +import org.nutz.dao.interceptor.annotation.PrevInsert; +import org.nutz.lang.util.NutMap; + +import java.util.List; + +@Data +@Comment("场地信息-场馆") +@Accessors(chain = true) +@Table("site_cug_info") +@EqualsAndHashCode(callSuper = true) +public class SiteCugInfo extends BaseModel { + + @Name + @Comment("ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + @PrevInsert(uu32 = true) + private String id; + + @Column + @Comment("创建人ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + private String createUserId; + + @Column + @Comment("创建人") + @ColDefine(type = ColType.VARCHAR, width = 20) + private String createUserName; + + @Column + @Comment("排序编号") + @ColDefine(type = ColType.INT) + private Integer sortNum; + + @Column + @Comment("场地名称") + @ColDefine(type = ColType.VARCHAR, width = 100) + private String name; + + @Column + @Comment("场地地址") + @ColDefine(type = ColType.VARCHAR, width = 255) + private String address; + + @Column + @Comment("联系人") + @ColDefine(type = ColType.VARCHAR, width = 32) + private String contactName; + + @Column + @Comment("联系电话") + @ColDefine(type = ColType.VARCHAR, width = 30) + private String contactPhone; + + @Column + @Comment("可容纳人数") + @ColDefine(type = ColType.INT) + private Integer maxNum; + + @Column + @Comment("场地类型") + @ColDefine(type = ColType.VARCHAR, width = 32) + private String typeId; + + @Column + @Comment("性别限制(0不限,1男,2女)") + @ColDefine(type = ColType.INT) + private Integer sexLimit; + + @Column + @Comment("场地的禁用时间") + @ColDefine(type = ColType.MYSQL_JSON) + private List notApplyTimeList; + + @Column + @Comment("开启状态") + @ColDefine(type = ColType.BOOLEAN) + @Default("1") + private Boolean state; + + @Column + @Comment("排除节假日") + @ColDefine(type = ColType.BOOLEAN) + @Default("0") + private Boolean filterHolidays; + + @Column + @Comment("场地介绍") + @ColDefine(type = ColType.TEXT) + private String introduce; +} \ No newline at end of file diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/param/SiteCugRecordPageForm.java b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/param/SiteCugRecordPageForm.java new file mode 100644 index 00000000..c0a7c4fe --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/param/SiteCugRecordPageForm.java @@ -0,0 +1,31 @@ +package com.budwk.app.zhgh.dayofficework.siteCug.param; + +import com.budwk.app.base.param.PageForm; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@ApiModel("场馆预约查询参数") +@EqualsAndHashCode(callSuper = true) +@Data +public class SiteCugRecordPageForm extends PageForm { + + @ApiModelProperty("场地类型ID") + private String siteType; + + @ApiModelProperty("场地ID") + private String siteId; + + @ApiModelProperty("预约类型") + private String reserveType; + + @ApiModelProperty("预约单位关键字") + private String reserveTargetKeyword; + + @ApiModelProperty("预约时间开始") + private String reserveTimeStart; + + @ApiModelProperty("预约时间结束") + private String reserveTimeEnd; +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/service/SiteCugApplyService.java b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/service/SiteCugApplyService.java new file mode 100644 index 00000000..1be3c7a1 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/service/SiteCugApplyService.java @@ -0,0 +1,24 @@ +package com.budwk.app.zhgh.dayofficework.siteCug.service; + +import com.budwk.app.base.service.BaseService; +import com.budwk.app.zhgh.dayofficework.siteCug.model.SiteCugApply; +import com.budwk.app.zhgh.dayofficework.siteCug.param.SiteCugRecordPageForm; +import org.nutz.dao.Cnd; +import org.nutz.dao.sql.Sql; + +import javax.servlet.http.HttpServletResponse; +import java.util.Map; + +public interface SiteCugApplyService extends BaseService { + + Map validateApply(SiteCugApply apply); + + // 场馆预约查询页和导出共用同一套查询字段定义,统一由 service 生成 SQL + Sql buildRecordSql(); + + // 场馆预约查询页和导出的筛选条件统一由 service 组装,避免 controller 重复维护 + Cnd buildRecordCnd(SiteCugRecordPageForm pageForm); + + // 场馆预约查询页的导出逻辑统一放在 service,controller 只负责接收请求 + void exportRecord(SiteCugRecordPageForm pageForm, HttpServletResponse response); +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/service/SiteCugFunctionTypeService.java b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/service/SiteCugFunctionTypeService.java new file mode 100644 index 00000000..a8dd6c6b --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/service/SiteCugFunctionTypeService.java @@ -0,0 +1,7 @@ +package com.budwk.app.zhgh.dayofficework.siteCug.service; + +import com.budwk.app.base.service.BaseService; +import com.budwk.app.zhgh.dayofficework.siteCug.model.SiteCugFunctionType; + +public interface SiteCugFunctionTypeService extends BaseService { +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/service/SiteCugInfoService.java b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/service/SiteCugInfoService.java new file mode 100644 index 00000000..1fa2d0bc --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/service/SiteCugInfoService.java @@ -0,0 +1,7 @@ +package com.budwk.app.zhgh.dayofficework.siteCug.service; + +import com.budwk.app.base.service.BaseService; +import com.budwk.app.zhgh.dayofficework.siteCug.model.SiteCugInfo; + +public interface SiteCugInfoService extends BaseService { +} 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 new file mode 100644 index 00000000..a2357ac1 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/service/impl/SiteCugApplyServiceImpl.java @@ -0,0 +1,321 @@ +package com.budwk.app.zhgh.dayofficework.siteCug.service.impl; + +import cn.afterturn.easypoi.excel.ExcelExportUtil; +import cn.afterturn.easypoi.excel.entity.ExportParams; +import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; +import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.budwk.app.base.service.impl.BaseServiceImpl; +import com.budwk.app.base.utils.CommonDownloadUtil; +import com.budwk.app.base.utils.PageUtil; +import com.budwk.app.flow.entity.ProcessInstance; +import com.budwk.app.flow.enums.ProcessInstanceStateEnum; +import com.budwk.app.sys.models.SysHoliday; +import com.budwk.app.sys.models.Sys_union; +import com.budwk.app.web.commons.auth.utils.SecurityUtil; +import com.budwk.app.zhgh.club.model.SysClub; +import com.budwk.app.zhgh.club.service.SysClubService; +import com.budwk.app.zhgh.dayofficework.siteCug.model.SiteCugApply; +import com.budwk.app.zhgh.dayofficework.siteCug.model.SiteCugInfo; +import com.budwk.app.zhgh.dayofficework.siteCug.param.SiteCugRecordPageForm; +import com.budwk.app.zhgh.dayofficework.siteCug.service.SiteCugApplyService; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.Workbook; +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.loader.annotation.IocBean; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.lang.util.NutMap; + +import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Slf4j +@IocBean(args = {"refer:dao"}) +public class SiteCugApplyServiceImpl extends BaseServiceImpl implements SiteCugApplyService { + + @Inject + private SysClubService sysClubService; + + public SiteCugApplyServiceImpl(Dao dao) { + super(dao); + } + + @Override + public Map validateApply(SiteCugApply apply) { + // 预约类型校验和自动回填: + // 1. 分工会预约时,强制读取当前登录人的分工会信息 + // 2. 协会预约时,只允许选择当前登录人实际管理的协会 + Map reserveTypeValidate = fillReserveTypeData(apply); + if (reserveTypeValidate.containsKey(false)) { + return reserveTypeValidate; + } + + if (StrUtil.isBlank(apply.getReserveStartTime()) || StrUtil.isBlank(apply.getReserveEndTime())) { + return Map.of(false, "预约时间不能为空"); + } + if (!DateUtil.parseDateTime(apply.getReserveEndTime()).isAfter(DateUtil.parseDateTime(apply.getReserveStartTime()))) { + return Map.of(false, "结束时间必须晚于开始时间"); + } + if (!DateUtil.parseDateTime(apply.getReserveStartTime()).isAfter(DateUtil.date())) { + return Map.of(false, "预约开始时间必须晚于当前时间"); + } + + SiteCugInfo siteInfo = dao().fetch(SiteCugInfo.class, apply.getSiteId()); + if (siteInfo == null) { + return Map.of(false, "场地不存在"); + } + if (!Boolean.TRUE.equals(siteInfo.getState())) { + return Map.of(false, "该场地未开启预约"); + } + if (apply.getJoinCount() == null || apply.getJoinCount() <= 0) { + return Map.of(false, "预约人数必须大于0"); + } + if (apply.getJoinCount() > siteInfo.getMaxNum()) { + return Map.of(false, "预约人数最多为%s人".formatted(siteInfo.getMaxNum())); + } + // 周六、周日整天不可预约,避免只做前端限制被绕过 + if (containsWeekend(apply.getReserveStartTime(), apply.getReserveEndTime())) { + return Map.of(false, "预约时间不能落在周六或周日"); + } + // 开启排除节假日后,节假日整天不可预约 + if (Boolean.TRUE.equals(siteInfo.getFilterHolidays()) && containsHoliday(apply.getReserveStartTime(), apply.getReserveEndTime())) { + return Map.of(false, "预约时间不能落在节假日"); + } + // 只要预约时间范围和禁用时间段有交叉,就不允许预约 + if (intersectsDisabledTime(siteInfo, apply.getReserveStartTime(), apply.getReserveEndTime())) { + return Map.of(false, "预约时间落在禁用时间内"); + } + + 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(SiteCugApply::getSiteId, "=", apply.getSiteId()); + cnd.andEX("sa.id", "!=", apply.getId()); + List stateList = List.of(ProcessInstanceStateEnum.DOING.getCode(), ProcessInstanceStateEnum.FINISHED.getCode(), ProcessInstanceStateEnum.PENDING.getCode()); + cnd.and(ProcessInstance::getState, "in", stateList); + sql.setCondition(cnd); + List applyList = listEntity(sql); + + // 只要已有预约和当前申请时间段有交叉,并且流程状态是待审核/办理中/已通过,就直接判定冲突 + for (SiteCugApply item : applyList) { + boolean overlap = DateUtil.parseDateTime(item.getReserveStartTime()).isBefore(DateUtil.parseDateTime(apply.getReserveEndTime())) + && DateUtil.parseDateTime(item.getReserveEndTime()).isAfter(DateUtil.parseDateTime(apply.getReserveStartTime())); + if (overlap) { + if (StrUtil.isBlank(apply.getId()) && StrUtil.equals(item.getApplyUserId(), SecurityUtil.getUserId())) { + return Map.of(false, "该时间段您已存在预约"); + } + return Map.of(false, String.format("预约时间冲突,%s 至 %s 已被预约,请重新选择", item.getReserveStartTime(), item.getReserveEndTime())); + } + } + return Map.of(true, ""); + } + + @Override + public Sql buildRecordSql() { + // 场馆预约查询页和导出页共用同一套返回字段,统一在 service 中维护,避免 controller 重复拼 SQL + return Sqls.create(""" + SELECT + info.*, + si.name AS siteName, + ins.id AS instanceId, + ins.processDefineId instanceProcessDefineId, + 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 + FROM + site_cug_apply info + LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id + LEFT JOIN site_cug_info si ON info.siteId = si.id + $condition + """); + } + + @Override + public Cnd buildRecordCnd(SiteCugRecordPageForm pageForm) { + // 场馆预约查询页和导出共用同一套筛选条件,保证列表看见什么,导出就拿到什么 + Cnd cnd = Cnd.NEW(); + if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) { + 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())) { + // 预约单位支持同时匹配分工会名称和协会名称,方便直接查某个预约主体的全部记录 + SqlExpressionGroup reserveTargetSeg = new SqlExpressionGroup(); + reserveTargetSeg.or("info.applyUnionName", "like", "%" + pageForm.getReserveTargetKeyword() + "%"); + reserveTargetSeg.or("info.clubName", "like", "%" + pageForm.getReserveTargetKeyword() + "%"); + cnd.and(reserveTargetSeg); + } + if (StrUtil.isNotBlank(pageForm.getReserveType())) { + cnd.andEX("info.reserveType", "=", pageForm.getReserveType()); + } + if (StrUtil.isNotBlank(pageForm.getReserveTimeStart())) { + // 按预约时间查询时,开始边界取“预约结束时间大于等于筛选开始”,这样跨段预约也能查出来 + cnd.andEX("info.reserveEndTime", ">=", pageForm.getReserveTimeStart()); + } + if (StrUtil.isNotBlank(pageForm.getReserveTimeEnd())) { + // 按预约时间查询时,结束边界取“预约开始时间小于等于筛选结束”,保证时间段有交叉就能命中 + cnd.andEX("info.reserveStartTime", "<=", pageForm.getReserveTimeEnd()); + } + cnd.andEX("si.typeId", "=", pageForm.getSiteType()); + cnd.andEX("info.siteId", "=", pageForm.getSiteId()); + // 预约查询页固定只看审核结束记录;导出复用同一条件,实现所见即所得 + cnd.and("ins.state", "in", List.of(ProcessInstanceStateEnum.FINISHED.getCode(), ProcessInstanceStateEnum.REJECT.getCode())); + if (StrUtil.isNotBlank(pageForm.getPageOrderName()) && StrUtil.isNotBlank(pageForm.getPageOrderBy())) { + cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy())); + } else { + cnd.desc("info.createdAt"); + } + cnd.groupBy("info.id"); + return cnd; + } + + @Override + public void exportRecord(SiteCugRecordPageForm pageForm, HttpServletResponse response) { + // 导出直接复用列表同一套查询条件和字段定义,保证导出结果与当前列表查询结果一致 + Sql sql = buildRecordSql(); + Cnd cnd = buildRecordCnd(pageForm); + sql.setCondition(cnd); + sql.setCallback(Sqls.callback.maps()); + dao().execute(sql); + List list = sql.getList(NutMap.class); + ExportParams exportParams = new ExportParams("预约查询", "预约查询"); + exportParams.setType(ExcelType.XSSF); + Workbook workbook = ExcelExportUtil.exportExcel(exportParams, buildRecordExportEntities(), list); + CommonDownloadUtil.download("场馆预约查询.xlsx", workbook, response); + } + + // 场馆预约查询导出字段统一在 service 中定义,并与当前列表展示字段保持一致 + private List buildRecordExportEntities() { + List exportEntities = new ArrayList<>(); + exportEntities.add(new ExcelExportEntity("场地名称", "siteName", 20)); + exportEntities.add(new ExcelExportEntity("预约人", "applyUserName", 20)); + exportEntities.add(new ExcelExportEntity("预约类型", "reserveTypeName", 20)); + exportEntities.add(new ExcelExportEntity("预约单位", "reserveTargetName", 20)); + exportEntities.add(new ExcelExportEntity("所属单位", "applyUnitName", 20)); + exportEntities.add(new ExcelExportEntity("预约开始时间", "reserveStartTime", 24)); + exportEntities.add(new ExcelExportEntity("预约结束时间", "reserveEndTime", 24)); + exportEntities.add(new ExcelExportEntity("联系方式", "applyMobile", 20)); + return exportEntities; + } + + // 预约时间范围内只要包含周六或周日,就视为不可预约 + private boolean containsWeekend(String reserveStartTime, String reserveEndTime) { + Date start = DateUtil.beginOfDay(DateUtil.parseDateTime(reserveStartTime)); + Date end = DateUtil.beginOfDay(DateUtil.parseDateTime(reserveEndTime)); + Date current = start; + while (current.compareTo(end) <= 0) { + DateTime currentDay = DateUtil.date(current); + int week = currentDay.dayOfWeek() - 1; + if (week == 0 || week == 6) { + return true; + } + current = DateUtil.offsetDay(current, 1); + } + return false; + } + + // 预约时间范围内只要包含节假日,就视为不可预约 + private boolean containsHoliday(String reserveStartTime, String reserveEndTime) { + Date start = DateUtil.beginOfDay(DateUtil.parseDateTime(reserveStartTime)); + Date end = DateUtil.beginOfDay(DateUtil.parseDateTime(reserveEndTime)); + List dayList = new ArrayList<>(); + Date current = start; + while (current.compareTo(end) <= 0) { + dayList.add(DateUtil.formatDate(current)); + current = DateUtil.offsetDay(current, 1); + } + if (dayList.isEmpty()) { + return false; + } + List holidays = dao().query(SysHoliday.class, Cnd.NEW()); + Set holidaySet = holidays.stream().map(SysHoliday::getDay).filter(StrUtil::isNotBlank).collect(Collectors.toSet()); + return dayList.stream().anyMatch(holidaySet::contains); + } + + // 预约时间范围只要与后台配置的禁用时间段有交叉,就直接拦截 + private boolean intersectsDisabledTime(SiteCugInfo siteInfo, String reserveStartTime, String reserveEndTime) { + List timeRanges = siteInfo.getNotApplyTimeList(); + if (timeRanges == null || timeRanges.isEmpty()) { + return false; + } + DateTime reserveStart = DateUtil.parseDateTime(reserveStartTime); + DateTime reserveEnd = DateUtil.parseDateTime(reserveEndTime); + for (NutMap range : timeRanges) { + if (range == null || StrUtil.hasBlank(range.getString("date"), range.getString("startTime"), range.getString("endTime"))) { + continue; + } + DateTime disabledStart = DateUtil.parseDateTime(range.getString("date") + " " + range.getString("startTime") + ":00"); + DateTime disabledEnd = DateUtil.parseDateTime(range.getString("date") + " " + range.getString("endTime") + ":00"); + boolean overlap = reserveStart.isBefore(disabledEnd) && reserveEnd.isAfter(disabledStart); + if (overlap) { + return true; + } + } + return false; + } + + // 按预约类型补齐最终落库数据,避免前端传错或被绕过 + private Map fillReserveTypeData(SiteCugApply apply) { + if (StrUtil.isBlank(apply.getReserveType())) { + return Map.of(false, "请选择预约类型"); + } + if (StrUtil.equals(apply.getReserveType(), "union")) { + String unionId = SecurityUtil.getUnionId(); + if (StrUtil.isBlank(unionId)) { + return Map.of(false, "当前用户未关联分工会,不能发起分工会预约"); + } + Sys_union union = dao().fetch(Sys_union.class, unionId); + if (union == null) { + return Map.of(false, "当前用户分工会信息不存在,请联系管理员核查"); + } + apply.setApplyUnionId(union.getId()); + apply.setApplyUnionName(union.getName()); + apply.setClubId(""); + apply.setClubName(""); + return Map.of(true, ""); + } + if (StrUtil.equals(apply.getReserveType(), "club")) { + if (StrUtil.isBlank(apply.getClubId())) { + return Map.of(false, "请选择协会"); + } + // 协会预约直接复用项目现成能力;getMyManageClub() 对 SYSADMIN 已放开,因此超级管理员也能代任意协会预约 + List myManageClub = sysClubService.getMyManageClub(); + SysClub club = myManageClub.stream() + .filter(item -> StrUtil.equals(item.getId(), apply.getClubId())) + .findFirst() + .orElse(null); + if (club == null) { + return Map.of(false, "所选协会不是您当前管理的协会"); + } + apply.setClubName(club.getClubName()); + apply.setApplyUnionId(""); + apply.setApplyUnionName(""); + return Map.of(true, ""); + } + return Map.of(false, "预约类型不合法"); + } +} + + + + diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/service/impl/SiteCugFunctionTypeServiceImpl.java b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/service/impl/SiteCugFunctionTypeServiceImpl.java new file mode 100644 index 00000000..3e53f6c0 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/service/impl/SiteCugFunctionTypeServiceImpl.java @@ -0,0 +1,17 @@ +package com.budwk.app.zhgh.dayofficework.siteCug.service.impl; + +import com.budwk.app.base.service.impl.BaseServiceImpl; +import com.budwk.app.zhgh.dayofficework.siteCug.model.SiteCugFunctionType; +import com.budwk.app.zhgh.dayofficework.siteCug.service.SiteCugFunctionTypeService; +import lombok.extern.slf4j.Slf4j; +import org.nutz.dao.Dao; +import org.nutz.ioc.loader.annotation.IocBean; + +@Slf4j +@IocBean(args = {"refer:dao"}) +public class SiteCugFunctionTypeServiceImpl extends BaseServiceImpl implements SiteCugFunctionTypeService { + + public SiteCugFunctionTypeServiceImpl(Dao dao) { + super(dao); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/service/impl/SiteCugInfoServiceImpl.java b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/service/impl/SiteCugInfoServiceImpl.java new file mode 100644 index 00000000..0292fb6c --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/service/impl/SiteCugInfoServiceImpl.java @@ -0,0 +1,17 @@ +package com.budwk.app.zhgh.dayofficework.siteCug.service.impl; + +import com.budwk.app.base.service.impl.BaseServiceImpl; +import com.budwk.app.zhgh.dayofficework.siteCug.model.SiteCugInfo; +import com.budwk.app.zhgh.dayofficework.siteCug.service.SiteCugInfoService; +import lombok.extern.slf4j.Slf4j; +import org.nutz.dao.Dao; +import org.nutz.ioc.loader.annotation.IocBean; + +@Slf4j +@IocBean(args = {"refer:dao"}) +public class SiteCugInfoServiceImpl extends BaseServiceImpl implements SiteCugInfoService { + + public SiteCugInfoServiceImpl(Dao dao) { + super(dao); + } +} diff --git a/src/main/resources/views/platform/zhgh/dayofficework/siteCug/apply/apply.js b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/apply/apply.js new file mode 100644 index 00000000..f8849b6c --- /dev/null +++ b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/apply/apply.js @@ -0,0 +1,531 @@ +const apply = { + template: /*language=HTML*/ ` +
+ + +
+ 您正在预约【{{ row.name }}】活动场地 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + 分工会预约 + 协会预约 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ 预约本年后续每周同一时段 + + 例如先选某个周一 09:00-11:00,勾选后会自动预约本年剩余所有周一的这个时段。 + +
+
+ {{ yearlyReserveSummary }} +
+
+
+ + + + + +
+
+ + + 取消 + 提交预约 + +
+ `, + store, + data() { + return { + row: {}, + clubOptions: [], + yearlyReserve: false, + timeLimitConfig: { + filterHolidays: false, + holidayList: [], + notApplyTimeList: [], + }, + pickerRefreshKey: 0, + formData: { + reserveType: 'union', + applyUnionId: '', + applyUnionName: '', + clubId: '', + clubName: '', + reserveStartTime: '', + reserveEndTime: '', + joinCount: 1, + applyCause: '', + }, + formRules: { + applyUserName: [{required: true, message: '必填', trigger: ['blur', 'change']}], + applySex: [{required: true, message: '必填', trigger: ['blur', 'change']}], + applyLoginName: [{required: true, message: '必填', trigger: ['blur', 'change']}], + applyUnitName: [{required: true, message: '必填', trigger: ['blur', 'change']}], + reserveType: [{required: true, message: '必填', trigger: ['blur', 'change']}], + applyMobile: [{required: true, message: '必填', trigger: ['blur', 'change']}], + reserveStartTime: [{required: true, message: '必填', trigger: ['blur', 'change']}], + reserveEndTime: [{required: true, message: '必填', trigger: ['blur', 'change']}], + joinCount: [{required: true, message: '必填', trigger: ['blur', 'change']}], + applyCause: [{required: true, message: '必填', trigger: ['blur', 'change']}], + clubId: [{ validator: (rule, value, callback) => { + if (this.formData.reserveType === 'club' && !value) { + callback(new Error('请选择您管理的协会')) + return + } + callback() + }, trigger: ['blur', 'change']}], + }, + } + }, + computed: { + startPickerOptions() { + return { + disabledDate: (time) => this.isDisabledDate(time), + selectableRange: this.buildSelectableRange(this.formData.reserveStartTime), + } + }, + endPickerOptions() { + return { + disabledDate: (time) => this.isDisabledDate(time), + selectableRange: this.buildSelectableRange(this.formData.reserveEndTime), + } + }, + yearlyReserveDates() { + if (!this.yearlyReserve || !this.formData.reserveStartTime || !this.formData.reserveEndTime) { + return [] + } + const start = this.$moment(this.formData.reserveStartTime) + const end = this.$moment(this.formData.reserveEndTime) + if (!start.isValid() || !end.isValid() || !end.isAfter(start)) { + return [] + } + const result = [] + const current = start.clone().startOf('day') + const endOfYear = start.clone().endOf('year').startOf('day') + const targetWeekDay = start.day() + const startClock = start.format('HH:mm:ss') + const endClock = end.format('HH:mm:ss') + while (current.isSameOrBefore(endOfYear, 'day')) { + if (current.day() === targetWeekDay) { + const day = current.format('YYYY-MM-DD') + const startTime = day + ' ' + startClock + const endTime = day + ' ' + endClock + if (this.validateTimeLimit(false, startTime, endTime)) { + result.push(day) + } + } + current.add(1, 'day') + } + return result + }, + yearlyReserveSummary() { + if (!this.yearlyReserve) { + return '' + } + if (!this.formData.reserveStartTime || !this.formData.reserveEndTime) { + return '请先选择开始和结束时间后,再批量预约本年同星期时段' + } + if (!this.yearlyReserveDates.length) { + return '当前时间段无法生成本年批量预约日期' + } + return '将从 ' + this.yearlyReserveDates[0] + ' 开始,自动预约到 ' + this.yearlyReserveDates[this.yearlyReserveDates.length - 1] + ',共 ' + this.yearlyReserveDates.length + ' 个时段' + }, + }, + methods: { + async onOpen(row) { + this.row = row + this.clubOptions = [] + this.yearlyReserve = false + this.formData = { + siteId: row.id, + applyUserId: this.$store.state.user.id, + applyUserName: this.$store.state.user.username, + applySex: this.$store.state.user.sex, + applyLoginName: this.$store.state.user.loginname, + applyUnitId: this.$store.state.user?.unit?.id, + applyUnitName: this.$store.state.user?.unit?.name, + reserveType: 'union', + applyUnionId: this.$store.state.user?.union?.id, + applyUnionName: this.$store.state.user?.union?.name, + clubId: '', + clubName: '', + applyMobile: this.$store.state.user.mobile, + reserveStartTime: '', + reserveEndTime: '', + joinCount: 1, + applyCause: '', + } + await this.queryTimeLimitConfig() + }, + async queryTimeLimitConfig() { + const fallback = { + filterHolidays: !!this.row.filterHolidays, + holidayList: [], + notApplyTimeList: Array.isArray(this.row.notApplyTimeList) ? this.row.notApplyTimeList : [], + } + try { + const res = await this.$axios.post('/platform/siteCug/apply/timeLimitConfig', { + siteId: this.row.id, + }) + if (res.code === 0 && res.data) { + this.timeLimitConfig = { + filterHolidays: !!res.data.filterHolidays, + holidayList: Array.isArray(res.data.holidayList) ? res.data.holidayList : [], + notApplyTimeList: Array.isArray(res.data.notApplyTimeList) ? res.data.notApplyTimeList : [], + } + } else { + this.timeLimitConfig = fallback + } + } catch (e) { + this.timeLimitConfig = fallback + } + this.pickerRefreshKey += 1 + }, + async reserveTypeChange(value) { + if (value === 'union') { + this.formData.applyUnionId = this.$store.state.user?.union?.id || '' + this.formData.applyUnionName = this.$store.state.user?.union?.name || '' + this.formData.clubId = '' + this.formData.clubName = '' + } + if (value === 'club') { + this.formData.applyUnionId = '' + this.formData.applyUnionName = '' + this.formData.clubId = '' + this.formData.clubName = '' + await this.loadManagedClubs() + } + this.$nextTick(() => { + this.$refs.formRef?.clearValidate(['applyUnionName', 'clubId']) + }) + }, + async loadManagedClubs() { + try { + const res = await this.$axios.post('/platform/club/examine/apply/getClubsByRole') + this.clubOptions = Array.isArray(res.data) ? res.data : [] + if (!this.clubOptions.length) { + this.$message.warning('您当前没有可预约的协会管理权限') + } + } catch (e) { + this.clubOptions = [] + this.$message.warning('协会列表加载失败,请稍后重试') + } + }, + clubChange(clubId) { + const club = this.clubOptions.find(item => item.id === clubId) + this.formData.clubName = club ? club.clubName : '' + }, + async normalizeReserveTypeData() { + if (this.formData.reserveType === 'union') { + this.formData.applyUnionId = this.$store.state.user?.union?.id || '' + this.formData.applyUnionName = this.$store.state.user?.union?.name || '' + this.formData.clubId = '' + this.formData.clubName = '' + return true + } + if (this.formData.reserveType === 'club') { + if (!this.clubOptions.length) { + await this.loadManagedClubs() + } + const club = this.clubOptions.find(item => item.id === this.formData.clubId) + this.formData.clubName = club ? club.clubName : '' + this.formData.applyUnionId = '' + this.formData.applyUnionName = '' + return true + } + return false + }, + isDisabledDate(time) { + const day = this.$moment(time).format('YYYY-MM-DD') + const today = this.$moment().startOf('day') + const currentDay = this.$moment(day) + if (currentDay.isBefore(today)) { + return true + } + if (currentDay.day() === 0 || currentDay.day() === 6) { + return true + } + return this.timeLimitConfig.filterHolidays && this.timeLimitConfig.holidayList.includes(day) + }, + normalizeTime(timeStr) { + if (!timeStr) { + return '00:00:00' + } + return timeStr.length === 5 ? timeStr + ':00' : timeStr + }, + getDisabledRangesByDay(dayStr) { + const list = Array.isArray(this.timeLimitConfig.notApplyTimeList) ? this.timeLimitConfig.notApplyTimeList : [] + return list + .filter(item => item && item.date && this.$moment(item.date).format('YYYY-MM-DD') === dayStr) + .sort((a, b) => this.normalizeTime(a.startTime).localeCompare(this.normalizeTime(b.startTime))) + }, + buildSelectableRange(dateTimeValue) { + if (!dateTimeValue) { + return ['00:00:00 - 23:59:59'] + } + const dayStr = this.$moment(dateTimeValue).format('YYYY-MM-DD') + const disabledRanges = this.getDisabledRangesByDay(dayStr) + if (!disabledRanges.length) { + return ['00:00:00 - 23:59:59'] + } + const result = [] + let cursor = '00:00:00' + disabledRanges.forEach(item => { + const start = this.normalizeTime(item.startTime) + const end = this.normalizeTime(item.endTime) + if (cursor < start) { + result.push(cursor + ' - ' + start) + } + if (cursor < end) { + cursor = end + } + }) + if (cursor < '23:59:59') { + result.push(cursor + ' - 23:59:59') + } + return result.length ? result : ['00:00:00 - 00:00:00'] + }, + isDateTimeBlocked(dateTimeStr) { + if (!dateTimeStr) { + return false + } + const target = this.$moment(dateTimeStr) + const dayStr = target.format('YYYY-MM-DD') + if (this.timeLimitConfig.filterHolidays && this.timeLimitConfig.holidayList.includes(dayStr)) { + return true + } + const disabledRanges = this.getDisabledRangesByDay(dayStr) + return disabledRanges.some(item => { + const start = this.$moment(dayStr + ' ' + this.normalizeTime(item.startTime)) + const end = this.$moment(dayStr + ' ' + this.normalizeTime(item.endTime)) + return target.isSameOrAfter(start) && target.isBefore(end) + }) + }, + hasHolidayInRange(start, end) { + const current = start.clone().startOf('day') + const endDay = end.clone().startOf('day') + while (current.isSameOrBefore(endDay)) { + if (current.day() === 0 || current.day() === 6) { + return true + } + if (this.timeLimitConfig.filterHolidays && this.timeLimitConfig.holidayList.includes(current.format('YYYY-MM-DD'))) { + return true + } + current.add(1, 'day') + } + return false + }, + overlapsDisabledRange(start, end) { + const list = Array.isArray(this.timeLimitConfig.notApplyTimeList) ? this.timeLimitConfig.notApplyTimeList : [] + return list.some(item => { + if (!item || !item.date || !item.startTime || !item.endTime) { + return false + } + const rangeStart = this.$moment(this.$moment(item.date).format('YYYY-MM-DD') + ' ' + this.normalizeTime(item.startTime)) + const rangeEnd = this.$moment(this.$moment(item.date).format('YYYY-MM-DD') + ' ' + this.normalizeTime(item.endTime)) + return start.isBefore(rangeEnd) && end.isAfter(rangeStart) + }) + }, + validateTimeLimit(showMessage = true, startTime = this.formData.reserveStartTime, endTime = this.formData.reserveEndTime) { + if (!startTime || !endTime) { + return true + } + const start = this.$moment(startTime) + const end = this.$moment(endTime) + if (this.isDateTimeBlocked(startTime) || this.isDateTimeBlocked(endTime)) { + if (showMessage) { + this.$message.warning('预约时间不能选择周末、节假日或禁用时间') + } + return false + } + if (this.hasHolidayInRange(start, end)) { + if (showMessage) { + this.$message.warning('预约时间范围内包含周末或节假日,请重新选择') + } + return false + } + if (this.overlapsDisabledRange(start, end)) { + if (showMessage) { + this.$message.warning('预约时间范围与禁用时间冲突,请重新选择') + } + return false + } + return true + }, + timeFieldChange(field) { + const value = this.formData[field] + if (!value) { + return + } + if (this.isDateTimeBlocked(value)) { + this.$message.warning('该时间点不可预约,请重新选择') + this.$set(this.formData, field, '') + return + } + if (this.formData.reserveStartTime && this.formData.reserveEndTime && !this.validateTimeLimit()) { + this.$set(this.formData, field, '') + } + }, + validateYearlyReserve() { + if (!this.yearlyReserve) { + return true + } + if (!this.yearlyReserveDates.length) { + this.$message.warning('?????????????????????') + return false + } + return true + }, + buildSubmitApi() { + return this.yearlyReserve ? '/platform/siteCug/apply/submitYearly' : '/platform/siteCug/apply/submit' + }, + buildSubmitConfirmMessage() { + if (!this.yearlyReserve) { + return '您确定要提交吗?' + } + return '将一次性预约本年后续 ' + this.yearlyReserveDates.length + ' 个同星期时段,确认提交吗?' + }, + onSubmit() { + this.$refs.formRef.validate(async (valid) => { + if (!valid) return + if (!this.formData.reserveType) { + this.$message.warning('请选择预约类型') + return + } + await this.normalizeReserveTypeData() + if (this.formData.reserveType === 'union' && !this.formData.applyUnionId) { + this.$message.warning('当前用户未关联分工会,不能发起分工会预约') + return + } + if (this.formData.reserveType === 'club' && !this.formData.clubId) { + this.$message.warning('请选择您管理的协会') + return + } + if (!this.formData.reserveStartTime || !this.formData.reserveEndTime) { + this.$message.warning('请选择预约开始和结束时间') + return + } + const start = this.$moment(this.formData.reserveStartTime) + const end = this.$moment(this.formData.reserveEndTime) + if (!start.isAfter(this.$moment())) { + this.$message.warning('预约开始时间必须晚于当前时间') + return + } + if (!end.isAfter(start)) { + this.$message.warning('预约结束时间必须晚于开始时间') + return + } + if (!this.validateTimeLimit()) { + return + } + if (!this.validateYearlyReserve()) { + return + } + + this.$confirm(this.buildSubmitConfirmMessage(), '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + this.$axios.post(this.buildSubmitApi(), { + data: JSON.stringify(this.formData), + }).then((resp) => { + if (resp.code === 0) { + this.$message.success(resp.msg || '提交成功') + this.$emit('refresh') + } else { + this.$message.warning(resp.msg || '提交失败') + } + }).catch((err) => { + this.$message.warning((err && err.msg) || '提交失败') + }) + }).catch(() => {}) + }) + }, + }, +}; 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 new file mode 100644 index 00000000..647f949e --- /dev/null +++ b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/apply/index.html @@ -0,0 +1,125 @@ + + +
+ + + + + + +
+ + + + diff --git a/src/main/resources/views/platform/zhgh/dayofficework/siteCug/common/applyInfo.js b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/common/applyInfo.js new file mode 100644 index 00000000..c549a4f3 --- /dev/null +++ b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/common/applyInfo.js @@ -0,0 +1,80 @@ +const siteCugApplyInfo = { + template: /*language=HTML*/ ` +
+
+ 申请信息 + 点击查看流程图 +
+ + {{ viewData.siteName }} + {{ viewData.applyUserName }} + {{ viewData.applyLoginName }} + {{ viewData.applyUnitName }} + {{ reserveTypeLabel }} + {{ reserveTargetName }} + {{ viewData.reserveStartTime }} + {{ viewData.reserveEndTime }} + {{ viewData.applyMobile }} + {{ viewData.joinCount }} + {{ viewData.applyCause }} + + + + +
+ `, + store, + dicts: ["PROCESS_TASK_SUBMIT_TYPE"], + data() { + return { + viewData: {}, + doneTasks: [], + row: null + } + }, + computed: { + reserveTypeLabel() { + return this.viewData.reserveType === 'club' ? '协会预约' : '分工会预约' + }, + reserveTargetName() { + return this.viewData.reserveType === 'club' ? (this.viewData.clubName || '-') : (this.viewData.applyUnionName || '-') + }, + }, + methods: { + onOpen(row) { + this.row = row + this.viewData = row + this.getDoneTasks() + }, + getDoneTasks() { + this.$axios.post('/flow/common/doneTasks', {bizId: this.row.id}).then((res) => { + if (res.code === 0) { + this.doneTasks = res.data + } + }) + }, + openChart() { + this.$refs.snakerChartRef.onOpenFull(this.row.instanceProcessDefineId, this.row.instanceId) + } + }, +} 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 new file mode 100644 index 00000000..0e51662a --- /dev/null +++ b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/manage/basicForm.js @@ -0,0 +1,341 @@ +const basicForm = { + template: /*language=HTML*/ ` +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 不限制 + + + + + + + + + 开启 + 禁用 + + + + + + + + + + + + + + + + + 点击设置禁用时间 + 您已设置{{ formData.notApplyTimeList ? formData.notApplyTimeList.length : 0 }}个禁用时间 + + + + + + + + + + 取消 + 提交 + + + +
选择日期
+ + +
设置禁用时间
+ + + + + + 一键设置开始/结束时间 + + + + + + + + + + + + + + + + + + 取消 + 确定 + +
+
+ `, + props: { + typeList: { + type: Array, + required: false, + default: [], + } + }, + store, + data() { + return { + formData: { + state: true, + sexLimit: 0, + filterHolidays: false, + notApplyTimeList: [], + createUserName: this.$store.state.user.username, + createUserId: this.$store.state.user.id, + }, + formRules: { + createUserName: [{required: true, message: '必填', trigger: ['blur', 'change']}], + sortNum: [{required: true, message: '必填', trigger: ['blur', 'change']}], + name: [{required: true, message: '必填', trigger: ['blur', 'change']}], + address: [{required: true, message: '必填', trigger: ['blur', 'change']}], + contactName: [{required: true, message: '必填', trigger: ['blur', 'change']}], + contactPhone: [{required: true, message: '必填', trigger: ['blur', 'change']}], + maxNum: [{required: true, message: '必填', trigger: ['blur', 'change']}], + typeId: [{required: true, message: '必填', trigger: ['blur', 'change']}], + sexLimit: [{required: true, message: '必填', trigger: ['blur', 'change']}], + filterHolidays: [{required: true, message: '必填', trigger: ['blur', 'change']}], + state: [{required: true, message: '必填', trigger: ['blur', 'change']}], + }, + setUpTimeDialog: false, + timeOneKeySet: { + startTime: "", + endTime: "" + }, + multipleSelection: [], + } + }, + methods: { + doConfirmSetUpCourse() { + const timeList = this.formData.notApplyTimeList + if (timeList && timeList.length > 0) { + const valid = timeList.every(v => v.date && v.startTime && v.endTime && (v.startTime < v.endTime)) + if (!valid) { + this.$message.warning("时间不完整或者有误") + return + } + } + this.setUpTimeDialog = false + }, + removeSetUpTableRow(row, index) { + this.formData.notApplyTimeList.splice(index, 1) + const dateArray = this.formData.notApplyTimeList.map(v => this.$moment(v.date).format("YYYY-MM-DD")) + const setUpDate = this.formData.setUpDate || [] + this.formData.setUpDate = setUpDate.filter(v => { + return dateArray.includes(this.$moment(v).format("YYYY-MM-DD")) + }) + }, + handleSelectionChange(val) { + this.multipleSelection = val + }, + oneKeySetStartEndTime() { + if (this.multipleSelection.length === 0) { + this.$message.warning('请在下面表格多选框中选择需要一键设置的时间') + return + } + const { startTime, endTime } = this.timeOneKeySet + this.formData.notApplyTimeList.forEach(v => { + const selected = this.multipleSelection.find(o => o.date === v.date && o.startTime === v.startTime && o.endTime === v.endTime) + if (selected) { + this.$set(v, "startTime", startTime) + this.$set(v, "endTime", endTime) + } + }) + this.$forceUpdate() + }, + setUpDateChange(val) { + if (!val) { + this.formData.notApplyTimeList = [] + return + } + if (this.formData.notApplyTimeList === undefined) { + this.$set(this.formData, "notApplyTimeList", []) + } + const dateSet = new Set(this.formData.notApplyTimeList.map(v => this.$moment(v.date).format("YYYY-MM-DD"))) + val.forEach(v => { + if (!dateSet.has(v)) { + this.formData.notApplyTimeList.push({ + date: v, startTime: "", endTime: "" + }) + } + }) + this.formData.notApplyTimeList = this.formData.notApplyTimeList.filter(v => { + return val.includes(this.$moment(v.date).format("YYYY-MM-DD")) + }) + this.formData.notApplyTimeList.sort((a, b) => { + return Date.parse(a.date) - Date.parse(b.date) + }) + }, + openSetUpTime() { + if (this.formData.notApplyTimeList) { + this.$set(this.formData, 'setUpDate', this.formData.notApplyTimeList.map(o => o.date)) + } + this.setUpTimeDialog = true + }, + onOpen(row) { + if (row && row.id) { + this.formData = clone(row) + if (!Array.isArray(this.formData.notApplyTimeList)) { + this.$set(this.formData, 'notApplyTimeList', []) + } + } else { + this.formData = { + state: true, + sexLimit: 0, + filterHolidays: false, + notApplyTimeList: [], + createUserName: this.$store.state.user.username, + createUserId: this.$store.state.user.id, + } + } + }, + onSubmit() { + this.$refs.formRef.validate((valid) => { + if (valid) { + this.$confirm("您确定要提交吗?", "提示", { + confirmButtonText: "确定", + cancelButtonText: "取消", + type: "warning" + }).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) + } + }) + } + }) + }, + }, + style: /*language=CSS*/ ` + + ` +}; 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 new file mode 100644 index 00000000..8e319045 --- /dev/null +++ b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/manage/index.html @@ -0,0 +1,180 @@ + + + + +
+ + + + + + +
+ + \ No newline at end of file diff --git a/src/main/resources/views/platform/zhgh/dayofficework/siteCug/manage/info.js b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/manage/info.js new file mode 100644 index 00000000..42f28335 --- /dev/null +++ b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/manage/info.js @@ -0,0 +1,59 @@ +const siteInfo = { + template: /*language=HTML*/ ` +
+ + {{ viewData.createUserName }} + {{ viewData.sortNum }} + {{ viewData.name }} + {{ viewData.address }} + {{ viewData.contactName }} + {{ viewData.contactPhone }} + {{ viewData.maxNum }} + {{ viewData.typeName }} + + + + + + + + + + + 暂无禁用时间 + + + 不限制 + + + + + 开启 + 禁用 + + +
+
暂无场地介绍
+
+
+
+ `, + data() { + return { + viewData: {}, + } + }, + methods: { + onOpen(row) { + this.viewData = row + }, + }, + style: /*language=CSS*/ ` + .el-descriptions-item__label { + width: 200px; + min-width: 200px; + max-width: 200px; + } + ` +}; diff --git a/src/main/resources/views/platform/zhgh/dayofficework/siteCug/mine/index.html b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/mine/index.html new file mode 100644 index 00000000..c696ccac --- /dev/null +++ b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/mine/index.html @@ -0,0 +1,140 @@ + +
+ + + + + +
+ + + + diff --git a/src/main/resources/views/platform/zhgh/dayofficework/siteCug/record/index.html b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/record/index.html new file mode 100644 index 00000000..5b3987cc --- /dev/null +++ b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/record/index.html @@ -0,0 +1,187 @@ + +
+ + + + + +
+ + + + diff --git a/src/main/resources/views/platform/zhgh/dayofficework/siteCug/schoolUnionAudit/index.html b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/schoolUnionAudit/index.html new file mode 100644 index 00000000..982e8987 --- /dev/null +++ b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/schoolUnionAudit/index.html @@ -0,0 +1,237 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + 已审核 + 未审核 + + + + + + + + + + + + + + + + + + + +
+ + + + diff --git a/src/main/resources/views/platform/zhgh/dayofficework/siteCug/type/basicForm.js b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/type/basicForm.js new file mode 100644 index 00000000..07040d9d --- /dev/null +++ b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/type/basicForm.js @@ -0,0 +1,68 @@ +const basicForm = { + template: /*language=HTML*/ ` +
+ + + + + + + + + + + + + + 取消 + 提交 + +
+ `, + data() { + return { + formData: { + enable: true + }, + formRules: { + name: [{required: true, message: '必填', trigger: ['blur', 'change']}], + code: [{required: true, message: '必填', trigger: ['blur', 'change']}], + enable: [{required: true, message: '必填', trigger: ['blur', 'change']}], + }, + } + }, + methods: { + onOpen(row) { + if (row && row.id) { + this.formData = clone(row) + } + }, + onSubmit() { + this.$refs.formRef.validate((valid) => { + if (valid) { + this.$confirm("您确定要提交吗?", "提示", { + confirmButtonText: "确定", + cancelButtonText: "取消", + type: "warning" + }).then(async () => { + const resp = await this.$axios.post("/platform/siteCug/function/type/submit", this.formData) + if (resp.code === 0) { + this.$message.success(resp.msg) + this.$emit('refresh') + } else { + this.$message.warning(resp.msg) + } + }) + } + }) + }, + }, + style: /*language=CSS*/ ` + + ` +} diff --git a/src/main/resources/views/platform/zhgh/dayofficework/siteCug/type/index.html b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/type/index.html new file mode 100644 index 00000000..dff17b7f --- /dev/null +++ b/src/main/resources/views/platform/zhgh/dayofficework/siteCug/type/index.html @@ -0,0 +1,139 @@ + + + + +
+ + + + + +
+ + diff --git a/src/main/resources/views/platform/zhghh5/dayofficework/siteCug/apply/form/apply.js b/src/main/resources/views/platform/zhghh5/dayofficework/siteCug/apply/form/apply.js new file mode 100644 index 00000000..db73c047 --- /dev/null +++ b/src/main/resources/views/platform/zhghh5/dayofficework/siteCug/apply/form/apply.js @@ -0,0 +1,887 @@ +new Vue({ + el: '#app', + store, + data() { + return { + siteId: GetQueryString('siteId'), + row: {}, + siteLoaded: false, + clubOptions: [], + timeLimitConfig: { + filterHolidays: false, + holidayList: [], + notApplyTimeList: [], + }, + formData: { + reserveType: 'union', + applyUnionId: '', + applyUnionName: '', + clubId: '', + clubName: '', + applyMobile: '', + reserveStartTime: '', + reserveEndTime: '', + joinCount: 1, + applyCause: '', + }, + showReserveTypePicker: false, + showClubPicker: false, + showTimePicker: false, + timePickerField: '', + timePickerColumns: [], + timePickerParts: { + date: '', + hour: null, + minute: null, + }, + timePickerSyncing: false, + pickerFilterCache: {}, + } + }, + computed: { + reserveTypeColumns() { + return ['分工会预约', '协会预约'] + }, + reserveTypeText() { + if (this.formData.reserveType === 'club') { + return '协会预约' + } + if (this.formData.reserveType === 'union') { + return '分工会预约' + } + return '' + }, + clubColumns() { + return this.clubOptions.map(item => item.clubName) + }, + timePickerTitle() { + return this.timePickerField === 'reserveEndTime' ? '选择预约结束时间' : '选择预约开始时间' + }, + }, + methods: { + historyBack, + clearTimePickerCache() { + this.pickerFilterCache = {} + }, + buildDefaultFormData() { + const user = this.$store.state.user || {} + return { + siteId: this.row.id || '', + applyUserId: user.id || '', + applyUserName: user.username || '', + applySex: user.sex || '', + applyLoginName: user.loginname || '', + applyUnitId: user.unit ? user.unit.id : '', + applyUnitName: user.unit ? user.unit.name : '', + reserveType: 'union', + applyUnionId: user.union ? user.union.id : '', + applyUnionName: user.union ? user.union.name : '', + clubId: '', + clubName: '', + applyMobile: user.mobile || '', + reserveStartTime: '', + reserveEndTime: '', + joinCount: 1, + applyCause: '', + } + }, + getNormalizedMoment(value) { + return this.$moment(value).seconds(0).milliseconds(0) + }, + getFieldMinMoment(field) { + const now = this.$moment().add(1, 'minute').seconds(0).milliseconds(0) + if (field === 'reserveEndTime' && this.formData.reserveStartTime) { + const start = this.getNormalizedMoment(this.formData.reserveStartTime).add(1, 'minute') + return start.isAfter(now) ? start : now + } + return now + }, + getFieldLatestMoment(field) { + if (field === 'reserveStartTime' && this.formData.reserveEndTime) { + return this.getNormalizedMoment(this.formData.reserveEndTime).subtract(1, 'minute') + } + return null + }, + getFieldMaxMoment(field) { + const defaultMax = this.$moment().add(365, 'day').endOf('day').seconds(0).milliseconds(0) + const latest = this.getFieldLatestMoment(field) + if (latest && latest.isBefore(defaultMax)) { + return latest.clone().seconds(0).milliseconds(0) + } + return defaultMax + }, + getContinuousEndBounds() { + if (!this.formData.reserveStartTime) { + return null + } + const minMoment = this.getFieldMinMoment('reserveEndTime') + let maxMoment = this.getFieldMaxMoment('reserveEndTime') + if (maxMoment.isBefore(minMoment)) { + return null + } + let boundary = null + const blockedDayCursor = minMoment.clone().startOf('day').add(1, 'day') + while (blockedDayCursor.isSameOrBefore(maxMoment, 'day')) { + if (this.isBlockedDay(blockedDayCursor)) { + boundary = blockedDayCursor.clone().startOf('day') + break + } + blockedDayCursor.add(1, 'day') + } + const list = Array.isArray(this.timeLimitConfig.notApplyTimeList) ? this.timeLimitConfig.notApplyTimeList : [] + list.forEach(item => { + if (!item || !item.date || !item.startTime || !item.endTime) { + return + } + const dayStr = this.$moment(item.date).format('YYYY-MM-DD') + const rangeStart = this.$moment(dayStr + ' ' + this.normalizeTime(item.startTime)) + const rangeEnd = this.$moment(dayStr + ' ' + this.normalizeTime(item.endTime)) + if (rangeEnd.isSameOrBefore(minMoment)) { + return + } + if (rangeStart.isSameOrBefore(minMoment) && rangeEnd.isAfter(minMoment)) { + boundary = minMoment.clone() + return + } + if (rangeStart.isAfter(minMoment) && rangeStart.isSameOrBefore(maxMoment)) { + if (!boundary || rangeStart.isBefore(boundary)) { + boundary = rangeStart.clone() + } + } + }) + if (boundary && boundary.isSameOrBefore(maxMoment)) { + maxMoment = boundary.clone().subtract(1, 'minute') + } + if (maxMoment.isBefore(minMoment)) { + return null + } + return { + minMoment: minMoment, + maxMoment: maxMoment, + } + }, + getEffectiveFieldMaxMoment(field) { + if (field === 'reserveEndTime' && this.formData.reserveStartTime) { + const bounds = this.getContinuousEndBounds() + return bounds ? bounds.maxMoment.clone() : this.getFieldMinMoment(field).clone().subtract(1, 'minute') + } + return this.getFieldMaxMoment(field) + }, + buildPickerMoment(parts) { + if (parts && parts.date) { + return this.$moment(parts.date, 'YYYY-MM-DD') + .hour(parts.hour || 0) + .minute(parts.minute || 0) + .second(0) + .millisecond(0) + } + return this.$moment({ + year: parts.year, + month: parts.month - 1, + date: parts.day, + hour: parts.hour, + minute: parts.minute, + second: 0, + millisecond: 0, + }) + }, + extractTimePickerPartsFromMoment(momentValue) { + const pickerMoment = this.getNormalizedMoment(momentValue) + return { + date: pickerMoment.format('YYYY-MM-DD'), + hour: pickerMoment.hour(), + minute: pickerMoment.minute(), + } + }, + getPickerPartValue(item, fallback) { + if (item && typeof item === 'object' && item !== null && typeof item.value !== 'undefined') { + return item.value + } + if (item === '' || item === null || typeof item === 'undefined') { + return fallback + } + return item + }, + extractTimePickerParts(values) { + const fallback = this.timePickerParts || {} + return { + date: this.getPickerPartValue(values && values[0], fallback.date), + hour: Number(this.getPickerPartValue(values && values[1], fallback.hour)), + minute: Number(this.getPickerPartValue(values && values[2], fallback.minute)), + } + }, + isValidCalendarMoment(momentValue, month, day) { + return momentValue.isValid() && momentValue.month() + 1 === month && momentValue.date() === day + }, + isBlockedDay(momentValue) { + if (!momentValue || !momentValue.isValid()) { + return true + } + if (momentValue.day() === 0 || momentValue.day() === 6) { + return true + } + return this.timeLimitConfig.filterHolidays && this.timeLimitConfig.holidayList.includes(momentValue.format('YYYY-MM-DD')) + }, + isRangeSelectable(start, end) { + if (!start || !end || !start.isValid() || !end.isValid()) { + return false + } + if (!end.isAfter(start)) { + return false + } + if (this.hasHolidayInRange(start, end)) { + return false + } + if (this.overlapsDisabledRange(start, end)) { + return false + } + return true + }, + isExactTimeSelectable(momentValue, field) { + if (!momentValue || !momentValue.isValid()) { + return false + } + const candidate = momentValue.clone().seconds(0).milliseconds(0) + const minMoment = this.getFieldMinMoment(field) + if (candidate.isBefore(minMoment)) { + return false + } + const latestMoment = this.getFieldLatestMoment(field) + if (latestMoment && candidate.isAfter(latestMoment)) { + return false + } + if (field === 'reserveEndTime' && this.formData.reserveStartTime) { + const bounds = this.getContinuousEndBounds() + if (!bounds) { + return false + } + return candidate.isSameOrAfter(bounds.minMoment) && candidate.isSameOrBefore(bounds.maxMoment) + } + if (this.isDateTimeBlocked(candidate.format('YYYY-MM-DD HH:mm:ss'))) { + return false + } + if (field === 'reserveStartTime' && this.formData.reserveEndTime) { + const end = this.getNormalizedMoment(this.formData.reserveEndTime) + if (!candidate.isBefore(end)) { + return false + } + return this.isRangeSelectable(candidate, end) + } + return true + }, + dayHasAvailableTime(year, month, day, field) { + const cacheKey = ['day', field, year, month, day, this.formData.reserveStartTime || '', this.formData.reserveEndTime || ''].join('|') + if (cacheKey in this.pickerFilterCache) { + return this.pickerFilterCache[cacheKey] + } + const dayMoment = this.buildPickerMoment({ year: year, month: month, day: day, hour: 0, minute: 0 }) + if (!this.isValidCalendarMoment(dayMoment, month, day) || this.isBlockedDay(dayMoment)) { + this.pickerFilterCache[cacheKey] = false + return false + } + const minMoment = this.getFieldMinMoment(field) + const maxMoment = this.getEffectiveFieldMaxMoment(field) + const dayStart = dayMoment.clone().startOf('day') + const dayEnd = dayMoment.clone().endOf('day') + if (dayEnd.isBefore(minMoment) || dayStart.isAfter(maxMoment)) { + this.pickerFilterCache[cacheKey] = false + return false + } + if (field === 'reserveEndTime' && this.formData.reserveStartTime) { + this.pickerFilterCache[cacheKey] = true + return true + } + for (let hour = 0; hour < 24; hour++) { + if (this.hourHasAvailableTime(year, month, day, hour, field)) { + this.pickerFilterCache[cacheKey] = true + return true + } + } + this.pickerFilterCache[cacheKey] = false + return false + }, + hourHasAvailableTime(year, month, day, hour, field) { + const cacheKey = ['hour', field, year, month, day, hour, this.formData.reserveStartTime || '', this.formData.reserveEndTime || ''].join('|') + if (cacheKey in this.pickerFilterCache) { + return this.pickerFilterCache[cacheKey] + } + const hourMoment = this.buildPickerMoment({ year: year, month: month, day: day, hour: hour, minute: 0 }) + if (!this.isValidCalendarMoment(hourMoment, month, day)) { + this.pickerFilterCache[cacheKey] = false + return false + } + const minMoment = this.getFieldMinMoment(field) + const maxMoment = this.getEffectiveFieldMaxMoment(field) + const hourStart = hourMoment.clone().startOf('hour') + const hourEnd = hourMoment.clone().endOf('hour') + if (hourEnd.isBefore(minMoment) || hourStart.isAfter(maxMoment)) { + this.pickerFilterCache[cacheKey] = false + return false + } + if (field === 'reserveEndTime' && this.formData.reserveStartTime) { + this.pickerFilterCache[cacheKey] = true + return true + } + for (let minute = 0; minute < 60; minute++) { + const candidate = this.buildPickerMoment({ year: year, month: month, day: day, hour: hour, minute: minute }) + if (this.isExactTimeSelectable(candidate, field)) { + this.pickerFilterCache[cacheKey] = true + return true + } + } + this.pickerFilterCache[cacheKey] = false + return false + }, + findFirstSelectableDateTime(field, baseMoment) { + let startMoment = this.getNormalizedMoment(baseMoment) + const minMoment = this.getFieldMinMoment(field) + const maxMoment = this.getEffectiveFieldMaxMoment(field) + if (maxMoment.isBefore(minMoment)) { + return null + } + if (startMoment.isBefore(minMoment)) { + startMoment = minMoment.clone() + } + for (let dayOffset = 0; dayOffset <= 366; dayOffset++) { + const dayMoment = startMoment.clone().startOf('day').add(dayOffset, 'day') + if (dayMoment.isAfter(maxMoment, 'day')) { + break + } + if (this.isBlockedDay(dayMoment)) { + continue + } + const startHour = dayMoment.isSame(startMoment, 'day') ? startMoment.hour() : 0 + for (let hour = startHour; hour < 24; hour++) { + const startMinute = dayMoment.isSame(startMoment, 'day') && hour === startMoment.hour() ? startMoment.minute() : 0 + for (let minute = startMinute; minute < 60; minute++) { + const candidate = dayMoment.clone().hour(hour).minute(minute).second(0).millisecond(0) + if (candidate.isAfter(maxMoment)) { + return null + } + if (this.isExactTimeSelectable(candidate, field)) { + return candidate + } + } + } + } + return null + }, + formatPickerNumber(value) { + return value < 10 ? '0' + value : '' + value + }, + getWeekdayText(momentValue) { + const weekdayList = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'] + return weekdayList[momentValue.day()] + }, + createPickerOption(text, value, disabled) { + return { + text: text, + value: value, + disabled: !!disabled, + } + }, + findNearestEnabledIndex(options, preferredValue) { + if (!Array.isArray(options) || !options.length) { + return -1 + } + const enabledIndexes = [] + options.forEach((item, index) => { + if (!item.disabled) { + enabledIndexes.push(index) + } + }) + if (!enabledIndexes.length) { + return -1 + } + if (preferredValue === '' || preferredValue === null || typeof preferredValue === 'undefined' || Number.isNaN(preferredValue)) { + return enabledIndexes[0] + } + let targetIndex = enabledIndexes[0] + let targetDistance = Math.abs(Number(options[targetIndex].value) - Number(preferredValue)) + enabledIndexes.forEach(index => { + const distance = Math.abs(Number(options[index].value) - Number(preferredValue)) + if (distance < targetDistance) { + targetIndex = index + targetDistance = distance + } + }) + return targetIndex + }, + buildDateOptions(field) { + const minMoment = this.getFieldMinMoment(field).clone().startOf('day') + const maxMoment = this.getEffectiveFieldMaxMoment(field).clone().startOf('day') + const options = [] + const cursor = minMoment.clone() + while (cursor.isSameOrBefore(maxMoment, 'day')) { + const text = cursor.format('MM-DD') + ' ' + this.getWeekdayText(cursor) + options.push(this.createPickerOption(text, cursor.format('YYYY-MM-DD'), !this.dayHasAvailableTime(cursor.year(), cursor.month() + 1, cursor.date(), field))) + cursor.add(1, 'day') + } + return options + }, + buildHourOptionsByDate(field, dateValue) { + const dateMoment = this.$moment(dateValue, 'YYYY-MM-DD') + const year = dateMoment.year() + const month = dateMoment.month() + 1 + const day = dateMoment.date() + const options = [] + for (let hour = 0; hour < 24; hour++) { + options.push(this.createPickerOption(this.formatPickerNumber(hour) + '时', hour, !this.hourHasAvailableTime(year, month, day, hour, field))) + } + return options + }, + buildMinuteOptionsByDateHour(field, dateValue, hour) { + const dateMoment = this.$moment(dateValue, 'YYYY-MM-DD') + const options = [] + for (let minute = 0; minute < 60; minute++) { + const candidate = this.buildPickerMoment({ + year: dateMoment.year(), + month: dateMoment.month() + 1, + day: dateMoment.date(), + hour: hour, + minute: minute, + }) + options.push(this.createPickerOption(this.formatPickerNumber(minute) + '分', minute, !this.isExactTimeSelectable(candidate, field))) + } + return options + }, + buildTimePickerColumns(field, preferredParts) { + const dateOptions = this.buildDateOptions(field) + const enabledDateIndex = dateOptions.findIndex(item => !item.disabled) + if (enabledDateIndex < 0) { + return null + } + let dateIndex = enabledDateIndex + if (preferredParts && preferredParts.date) { + const exactIndex = dateOptions.findIndex(item => item.value === preferredParts.date && !item.disabled) + if (exactIndex >= 0) { + dateIndex = exactIndex + } + } + const selectedDate = dateOptions[dateIndex].value + + const hourOptions = this.buildHourOptionsByDate(field, selectedDate) + const hourIndex = this.findNearestEnabledIndex(hourOptions, preferredParts ? preferredParts.hour : null) + if (hourIndex < 0) { + return null + } + const selectedHour = hourOptions[hourIndex].value + + const minuteOptions = this.buildMinuteOptionsByDateHour(field, selectedDate, selectedHour) + const minuteIndex = this.findNearestEnabledIndex(minuteOptions, preferredParts ? preferredParts.minute : null) + if (minuteIndex < 0) { + return null + } + const selectedMinute = minuteOptions[minuteIndex].value + + return { + parts: { + date: selectedDate, + hour: selectedHour, + minute: selectedMinute, + }, + indexes: [dateIndex, hourIndex, minuteIndex], + columns: [ + { values: dateOptions, defaultIndex: dateIndex }, + { values: hourOptions, defaultIndex: hourIndex }, + { values: minuteOptions, defaultIndex: minuteIndex }, + ], + } + }, + sameTimePickerParts(left, right) { + if (!left || !right) { + return false + } + return left.date === right.date + && left.hour === right.hour + && left.minute === right.minute + }, + syncTimePickerColumns(picker, built) { + if (!picker || !built) { + this.timePickerSyncing = false + return + } + this.timePickerSyncing = true + built.columns.forEach((column, index) => { + picker.setColumnValues(index, column.values) + }) + picker.setIndexes(built.indexes) + this.timePickerParts = built.parts + setTimeout(() => { + this.timePickerSyncing = false + }, 0) + }, + applyTimePickerState(built, picker, syncColumns = true) { + if (!built) { + return false + } + const currentPicker = picker || this.$refs.timePickerRef + if (syncColumns) { + this.timePickerColumns = built.columns + this.$nextTick(() => { + this.syncTimePickerColumns(currentPicker || this.$refs.timePickerRef, built) + }) + return true + } + this.syncTimePickerColumns(currentPicker, built) + return true + }, + async init() { + if (!this.siteId) { + this.$toast('未获取到场馆信息') + this.historyBack() + return + } + const siteRes = await this.$axios.post('/platform/siteCug/manage/info', { id: this.siteId }) + if (siteRes.code !== 0 || !siteRes.data) { + this.$toast(siteRes.msg || '场馆信息加载失败') + this.historyBack() + return + } + this.row = siteRes.data + this.formData = this.buildDefaultFormData() + await this.queryTimeLimitConfig() + this.siteLoaded = true + }, + async queryTimeLimitConfig() { + const fallback = { + filterHolidays: !!this.row.filterHolidays, + holidayList: [], + notApplyTimeList: Array.isArray(this.row.notApplyTimeList) ? this.row.notApplyTimeList : [], + } + try { + const res = await this.$axios.post('/platform/siteCug/apply/timeLimitConfig', { + siteId: this.row.id, + }) + if (res.code === 0 && res.data) { + this.timeLimitConfig = { + filterHolidays: !!res.data.filterHolidays, + holidayList: Array.isArray(res.data.holidayList) ? res.data.holidayList : [], + notApplyTimeList: Array.isArray(res.data.notApplyTimeList) ? res.data.notApplyTimeList : [], + } + this.clearTimePickerCache() + return + } + } catch (e) { + } + this.timeLimitConfig = fallback + this.clearTimePickerCache() + }, + async reserveTypeChange(value) { + const user = this.$store.state.user || {} + if (value === 'union') { + this.formData.applyUnionId = user.union ? user.union.id : '' + this.formData.applyUnionName = user.union ? user.union.name : '' + this.formData.clubId = '' + this.formData.clubName = '' + return + } + if (value === 'club') { + this.formData.applyUnionId = '' + this.formData.applyUnionName = '' + this.formData.clubId = '' + this.formData.clubName = '' + await this.loadManagedClubs() + } + }, + async loadManagedClubs() { + try { + const res = await this.$axios.post('/platform/club/examine/apply/getClubsByRole') + this.clubOptions = Array.isArray(res.data) ? res.data : [] + if (!this.clubOptions.length) { + this.$toast('您当前没有可预约的协会管理权限') + } + } catch (e) { + this.clubOptions = [] + this.$toast('协会列表加载失败,请稍后重试') + } + }, + onReserveTypeConfirm(value) { + this.showReserveTypePicker = false + const reserveType = value === '协会预约' ? 'club' : 'union' + this.$set(this.formData, 'reserveType', reserveType) + this.reserveTypeChange(reserveType) + }, + async openClubPicker() { + if (this.formData.reserveType !== 'club') { + this.$toast('请先选择协会预约') + return + } + if (!this.clubOptions.length) { + await this.loadManagedClubs() + } + if (!this.clubOptions.length) { + return + } + this.showClubPicker = true + }, + onClubConfirm(value, index) { + this.showClubPicker = false + const club = this.clubOptions[index] + this.formData.clubId = club ? club.id : '' + this.formData.clubName = club ? club.clubName : value + }, + openTimePicker(field) { + this.timePickerField = field + this.timePickerSyncing = false + this.clearTimePickerCache() + let defaultTime = this.formData[field] ? this.getNormalizedMoment(this.formData[field]) : null + if (!defaultTime || !defaultTime.isValid()) { + defaultTime = field === 'reserveEndTime' && this.formData.reserveStartTime + ? this.getNormalizedMoment(this.formData.reserveStartTime).add(1, 'hour') + : this.$moment().add(1, 'hour').startOf('hour') + } + const availableTime = this.findFirstSelectableDateTime(field, defaultTime) + if (!availableTime) { + this.$toast('当前没有可预约的时间') + return + } + const built = this.buildTimePickerColumns(field, this.extractTimePickerPartsFromMoment(availableTime)) + if (!built) { + this.$toast('当前没有可预约的时间') + return + } + this.showTimePicker = true + this.applyTimePickerState(built, null, true) + }, + onTimePickerChange(picker, values) { + if (!this.timePickerField || this.timePickerSyncing) { + return + } + const parts = this.extractTimePickerParts(values) + const built = this.buildTimePickerColumns(this.timePickerField, parts) + if (!built) { + return + } + if (this.sameTimePickerParts(parts, built.parts)) { + this.timePickerParts = built.parts + return + } + this.applyTimePickerState(built, picker, false) + }, + onTimeConfirm(values) { + const parts = this.extractTimePickerParts(values) + const candidate = this.buildPickerMoment(parts) + if (!this.isExactTimeSelectable(candidate, this.timePickerField)) { + this.$toast('该时间不可预约,请重新选择') + const built = this.buildTimePickerColumns(this.timePickerField, parts) + this.applyTimePickerState(built, this.$refs.timePickerRef) + return + } + const formatted = candidate.format('YYYY-MM-DD HH:mm:ss') + this.timePickerSyncing = false + this.showTimePicker = false + this.$set(this.formData, this.timePickerField, formatted) + this.clearTimePickerCache() + this.timeFieldChange(this.timePickerField) + }, + async normalizeReserveTypeData() { + const user = this.$store.state.user || {} + if (this.formData.reserveType === 'union') { + this.formData.applyUnionId = user.union ? user.union.id : '' + this.formData.applyUnionName = user.union ? user.union.name : '' + this.formData.clubId = '' + this.formData.clubName = '' + return true + } + if (this.formData.reserveType === 'club') { + if (!this.clubOptions.length) { + await this.loadManagedClubs() + } + const club = this.clubOptions.find(item => item.id === this.formData.clubId) + this.formData.clubName = club ? club.clubName : '' + this.formData.applyUnionId = '' + this.formData.applyUnionName = '' + return true + } + return false + }, + normalizeTime(timeStr) { + if (!timeStr) { + return '00:00:00' + } + return timeStr.length === 5 ? timeStr + ':00' : timeStr + }, + getDisabledRangesByDay(dayStr) { + const list = Array.isArray(this.timeLimitConfig.notApplyTimeList) ? this.timeLimitConfig.notApplyTimeList : [] + return list + .filter(item => item && item.date && this.$moment(item.date).format('YYYY-MM-DD') === dayStr) + .sort((a, b) => this.normalizeTime(a.startTime).localeCompare(this.normalizeTime(b.startTime))) + }, + isDateTimeBlocked(dateTimeStr) { + if (!dateTimeStr) { + return false + } + const target = this.$moment(dateTimeStr) + const dayStr = target.format('YYYY-MM-DD') + if (target.day() === 0 || target.day() === 6) { + return true + } + if (this.timeLimitConfig.filterHolidays && this.timeLimitConfig.holidayList.includes(dayStr)) { + return true + } + const disabledRanges = this.getDisabledRangesByDay(dayStr) + return disabledRanges.some(item => { + const start = this.$moment(dayStr + ' ' + this.normalizeTime(item.startTime)) + const end = this.$moment(dayStr + ' ' + this.normalizeTime(item.endTime)) + return target.isSameOrAfter(start) && target.isBefore(end) + }) + }, + hasHolidayInRange(start, end) { + const current = start.clone().startOf('day') + const endDay = end.clone().startOf('day') + while (current.isSameOrBefore(endDay)) { + if (current.day() === 0 || current.day() === 6) { + return true + } + if (this.timeLimitConfig.filterHolidays && this.timeLimitConfig.holidayList.includes(current.format('YYYY-MM-DD'))) { + return true + } + current.add(1, 'day') + } + return false + }, + overlapsDisabledRange(start, end) { + const list = Array.isArray(this.timeLimitConfig.notApplyTimeList) ? this.timeLimitConfig.notApplyTimeList : [] + return list.some(item => { + if (!item || !item.date || !item.startTime || !item.endTime) { + return false + } + const rangeStart = this.$moment(this.$moment(item.date).format('YYYY-MM-DD') + ' ' + this.normalizeTime(item.startTime)) + const rangeEnd = this.$moment(this.$moment(item.date).format('YYYY-MM-DD') + ' ' + this.normalizeTime(item.endTime)) + return start.isBefore(rangeEnd) && end.isAfter(rangeStart) + }) + }, + showSubmitError(message) { + return this.$dialog.alert({ + title: '提示', + message: message || '提交失败', + }).catch(() => {}) + }, + validateTimeLimit(showMessage = true, useDialog = false) { + const reserveStartTime = this.formData.reserveStartTime + const reserveEndTime = this.formData.reserveEndTime + if (!reserveStartTime || !reserveEndTime) { + return true + } + const start = this.$moment(reserveStartTime) + const end = this.$moment(reserveEndTime) + const showError = (message) => { + if (!showMessage) { + return + } + if (useDialog) { + this.showSubmitError(message) + return + } + this.$toast(message) + } + if (this.isDateTimeBlocked(reserveStartTime) || this.isDateTimeBlocked(reserveEndTime)) { + showError('预约时间不能选择周末、节假日或禁用时间') + return false + } + if (this.hasHolidayInRange(start, end)) { + showError('预约时间范围内包含周末或节假日,请重新选择') + return false + } + if (this.overlapsDisabledRange(start, end)) { + showError('预约时间范围与禁用时间冲突,请重新选择') + return false + } + return true + }, + timeFieldChange(field) { + const value = this.formData[field] + this.clearTimePickerCache() + if (!value) { + return + } + if (this.isDateTimeBlocked(value)) { + this.$toast('该时间点不可预约,请重新选择') + this.$set(this.formData, field, '') + return + } + if (field === 'reserveStartTime' && this.formData.reserveEndTime) { + const start = this.$moment(value) + const end = this.$moment(this.formData.reserveEndTime) + if (!end.isAfter(start)) { + this.$set(this.formData, 'reserveEndTime', '') + this.$toast('结束时间需晚于开始时间,请重新选择') + return + } + } + if (field === 'reserveEndTime' && this.formData.reserveStartTime) { + const start = this.$moment(this.formData.reserveStartTime) + const end = this.$moment(value) + if (!end.isAfter(start)) { + this.$toast('预约结束时间必须晚于开始时间') + this.$set(this.formData, field, '') + return + } + } + if (this.formData.reserveStartTime && this.formData.reserveEndTime && !this.validateTimeLimit()) { + this.$set(this.formData, field, '') + } + }, + onSubmit() { + this.$refs.formRef.validate().then(async () => { + if (!this.formData.reserveType) { + await this.showSubmitError('请选择预约类型') + return + } + await this.normalizeReserveTypeData() + if (this.formData.reserveType === 'union' && !this.formData.applyUnionId) { + await this.showSubmitError('当前用户未关联分工会,不能发起分工会预约') + return + } + if (this.formData.reserveType === 'club' && !this.formData.clubId) { + await this.showSubmitError('请选择您管理的协会') + return + } + if (!this.formData.reserveStartTime || !this.formData.reserveEndTime) { + await this.showSubmitError('请选择预约开始和结束时间') + return + } + const start = this.$moment(this.formData.reserveStartTime) + const end = this.$moment(this.formData.reserveEndTime) + if (!start.isAfter(this.$moment())) { + await this.showSubmitError('预约开始时间必须晚于当前时间') + return + } + if (!end.isAfter(start)) { + await this.showSubmitError('预约结束时间必须晚于开始时间') + return + } + if (!this.validateTimeLimit(true, true)) { + return + } + this.$dialog.confirm({ + title: '提示', + message: '您确定要提交吗?', + }).then(() => { + const loading = this.$toast.loading({ + message: '提交中...', + forbidClick: true, + overlay: true, + duration: 0, + }) + this.$axios.post('/platform/siteCug/apply/submit', { + data: JSON.stringify(this.formData), + }).then((res) => { + if (res.code === 0) { + this.$toast.success(res.msg || '提交成功') + this.$pjaxReplace('/platform/siteCug/apply/h5') + } + }).catch((err) => { + this.showSubmitError((err && err.msg) || '提交失败') + }).finally(() => { + loading.close() + }) + }).catch(() => {}) + }).catch(() => {}) + }, + }, + created() { + this.init() + } +}) diff --git a/src/main/resources/views/platform/zhghh5/dayofficework/siteCug/apply/form/index.html b/src/main/resources/views/platform/zhghh5/dayofficework/siteCug/apply/form/index.html new file mode 100644 index 00000000..74254bca --- /dev/null +++ b/src/main/resources/views/platform/zhghh5/dayofficework/siteCug/apply/form/index.html @@ -0,0 +1,167 @@ + + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ + + + diff --git a/src/main/resources/views/platform/zhghh5/dayofficework/siteCug/apply/index.html b/src/main/resources/views/platform/zhghh5/dayofficework/siteCug/apply/index.html new file mode 100644 index 00000000..e839135c --- /dev/null +++ b/src/main/resources/views/platform/zhghh5/dayofficework/siteCug/apply/index.html @@ -0,0 +1,106 @@ + + + + +
+ + + + + + + + + + + + + + + +
+ + + + diff --git a/src/main/resources/views/platform/zhghh5/dayofficework/siteCug/common/applyInfo.js b/src/main/resources/views/platform/zhghh5/dayofficework/siteCug/common/applyInfo.js new file mode 100644 index 00000000..b864ce5e --- /dev/null +++ b/src/main/resources/views/platform/zhghh5/dayofficework/siteCug/common/applyInfo.js @@ -0,0 +1,80 @@ +const siteCugApplyInfoH5 = { + template: + /*language=HTML*/ + ` + +
+ + {{ viewData.siteName }} + {{ viewData.applyUserName }} + {{ viewData.applyLoginName }} + {{ viewData.applyUnitName }} + {{ reserveTypeLabel }} + {{ reserveTargetName }} + {{ viewData.reserveStartTime }} + {{ viewData.reserveEndTime }} + {{ viewData.applyMobile }} + {{ viewData.applyCause }} + + +
+ +
+ `, + dicts: ['PROCESS_TASK_SUBMIT_TYPE'], + data() { + return { + visible: false, + viewData: {}, + doneTasks: [], + row: null, + } + }, + computed: { + reserveTypeLabel() { + return this.viewData.reserveType === 'club' ? '协会预约' : '分工会预约' + }, + reserveTargetName() { + return this.viewData.reserveType === 'club' ? (this.viewData.clubName || '-') : (this.viewData.applyUnionName || '-') + }, + sheetTitle() { + return this.visible && this.$slots.default ? '审核预约' : '预约详情' + }, + }, + methods: { + onOpen(row) { + this.row = row + this.visible = true + this.viewData = row + this.getDoneTasks() + }, + onClose() { + this.visible = false + }, + getDoneTasks() { + this.$axios.post('/flow/common/doneTasks', { bizId: this.row.id }).then((res) => { + if (res.code === 0) { + this.doneTasks = res.data + } + }) + }, + }, +}; diff --git a/src/main/resources/views/platform/zhghh5/dayofficework/siteCug/common/siteInfo.js b/src/main/resources/views/platform/zhghh5/dayofficework/siteCug/common/siteInfo.js new file mode 100644 index 00000000..c6f5674c --- /dev/null +++ b/src/main/resources/views/platform/zhghh5/dayofficework/siteCug/common/siteInfo.js @@ -0,0 +1,105 @@ +const siteInfo = { + template: /*language=HTML*/ ` +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
日期开始时间结束时间
{{ item.date }}{{ item.startTime }}{{ item.endTime }}
+
+
+
+
+ `, + data() { + return { + viewData: {}, + visible: false, + } + }, + methods: { + onOpen(row) { + this.viewData = row + this.visible = true + }, + }, + style: /*language=CSS*/ ` + /deep/ .direction-column-cell .van-cell__value { + white-space: normal; + text-align: left; + } + /deep/ .table-class { + width: 100%; + border-radius: 5px; + overflow: hidden; + line-height: 1.5rem; + font-size: 13px; + table-layout: fixed; + border-collapse: collapse; + } + /deep/ .table-class th { + background-color: #f2f2f2; + border: 1px solid #dddddd; + } + /deep/ .table-class tr { + text-align: center; + border-bottom: 1px solid #dddddd; + } + /deep/ .table-class td { + border: 1px solid #dddddd; + } + ` +} diff --git a/src/main/resources/views/platform/zhghh5/dayofficework/siteCug/mine/index.html b/src/main/resources/views/platform/zhghh5/dayofficework/siteCug/mine/index.html new file mode 100644 index 00000000..15c3c95d --- /dev/null +++ b/src/main/resources/views/platform/zhghh5/dayofficework/siteCug/mine/index.html @@ -0,0 +1,151 @@ + + + + +
+ + + + + + + + + + + + + + + +
+ + + + diff --git a/src/main/resources/views/platform/zhghh5/dayofficework/siteCug/schoolUnionAudit/index.html b/src/main/resources/views/platform/zhghh5/dayofficework/siteCug/schoolUnionAudit/index.html new file mode 100644 index 00000000..b2cbb4ee --- /dev/null +++ b/src/main/resources/views/platform/zhghh5/dayofficework/siteCug/schoolUnionAudit/index.html @@ -0,0 +1,272 @@ + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+
{{formData.taskName}}
+ + + +
+ 退回 + 不同意 + 同意 +
+
+
+
+ + + +