This commit is contained in:
2026-04-01 18:53:08 +08:00
parent dc71dc22d8
commit d799d11f69
34 changed files with 5509 additions and 0 deletions
@@ -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<NutMap> listMap = pagination.getList(NutMap.class);
List<SiteCugFunctionType> typeList = dao.query(SiteCugFunctionType.class, Cnd.NEW());
Map<String, String> 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<Boolean, String> 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<SiteCugApply> applyList = buildYearlyApplyList(apply);
if (applyList.isEmpty()) {
return Result.error("未生成可预约的日期");
}
for (SiteCugApply item : applyList) {
Map<Boolean, String> 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<String> holidayList = new ArrayList<>();
if (Boolean.TRUE.equals(info.getFilterHolidays())) {
int currentYear = Year.now().getValue();
List<String> yearPrefixList = List.of(currentYear + "-", (currentYear + 1) + "-");
List<SysHoliday> 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<SiteCugApply> 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<String> holidayList = new ArrayList<>();
if (Boolean.TRUE.equals(siteInfo.getFilterHolidays())) {
int currentYear = start.year();
List<String> yearPrefixList = List.of(currentYear + "-", (currentYear + 1) + "-");
List<SysHoliday> 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<SiteCugApply> 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<ProcessTask> doingTaskList = flowEngine.processTaskService().getDoingTaskList(instance.getId(), null);
for (ProcessTask task : doingTaskList) {
flowEngine.executeProcessTask(task.getId(), SecurityUtil.getUserId(), args);
}
}
}
@@ -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<SiteCugFunctionType> list = typeService.query(Cnd.where(SiteCugFunctionType::getEnable, "=", true).asc(SiteCugFunctionType::getCode));
return Result.success(list);
}
}
@@ -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<NutMap> listMap = pagination.getList(NutMap.class);
List<SiteCugFunctionType> typeList = dao.query(SiteCugFunctionType.class, Cnd.NEW());
Map<String, String> 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<NutMap> 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<SiteCugInfo> 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);
}
}
@@ -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<NutMap> 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<String> deleteIds = new ArrayList<>();
if (StrUtil.isNotBlank(apply.getBackOption()) && apply.getBackOption().startsWith(YEARLY_BATCH_PREFIX)) {
List<SiteCugApply> 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<NutMap> 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("取消成功");
}
}
@@ -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<NutMap> 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<NutMap> 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);
}
}
@@ -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<NutMap> 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<ProcessTask> taskList = new ArrayList<>();
if (StrUtil.isNotBlank(apply.getBackOption()) && apply.getBackOption().startsWith(YEARLY_BATCH_PREFIX)) {
List<SiteCugApply> batchList = applyService.query(Cnd.where("backOption", "=", apply.getBackOption()).and("createdBy", "=", apply.getCreatedBy()));
List<String> 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<ProcessTask> taskList = new ArrayList<>();
if (StrUtil.isNotBlank(apply.getBackOption()) && apply.getBackOption().startsWith(YEARLY_BATCH_PREFIX)) {
List<SiteCugApply> batchList = applyService.query(Cnd.where("backOption", "=", apply.getBackOption()).and("createdBy", "=", apply.getCreatedBy()));
List<String> 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("撤回成功");
}
}
@@ -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;
}
@@ -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;
}
@@ -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<NutMap> 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;
}
@@ -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;
}
@@ -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<SiteCugApply> {
Map<Boolean, String> validateApply(SiteCugApply apply);
// 场馆预约查询页和导出共用同一套查询字段定义,统一由 service 生成 SQL
Sql buildRecordSql();
// 场馆预约查询页和导出的筛选条件统一由 service 组装,避免 controller 重复维护
Cnd buildRecordCnd(SiteCugRecordPageForm pageForm);
// 场馆预约查询页的导出逻辑统一放在 service,controller 只负责接收请求
void exportRecord(SiteCugRecordPageForm pageForm, HttpServletResponse response);
}
@@ -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<SiteCugFunctionType> {
}
@@ -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<SiteCugInfo> {
}
@@ -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<SiteCugApply> implements SiteCugApplyService {
@Inject
private SysClubService sysClubService;
public SiteCugApplyServiceImpl(Dao dao) {
super(dao);
}
@Override
public Map<Boolean, String> validateApply(SiteCugApply apply) {
// 预约类型校验和自动回填:
// 1. 分工会预约时,强制读取当前登录人的分工会信息
// 2. 协会预约时,只允许选择当前登录人实际管理的协会
Map<Boolean, String> 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<Integer> stateList = List.of(ProcessInstanceStateEnum.DOING.getCode(), ProcessInstanceStateEnum.FINISHED.getCode(), ProcessInstanceStateEnum.PENDING.getCode());
cnd.and(ProcessInstance::getState, "in", stateList);
sql.setCondition(cnd);
List<SiteCugApply> 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<NutMap> 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<ExcelExportEntity> buildRecordExportEntities() {
List<ExcelExportEntity> 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<String> 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<SysHoliday> holidays = dao().query(SysHoliday.class, Cnd.NEW());
Set<String> 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<NutMap> 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<Boolean, String> 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<SysClub> 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, "预约类型不合法");
}
}
@@ -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<SiteCugFunctionType> implements SiteCugFunctionTypeService {
public SiteCugFunctionTypeServiceImpl(Dao dao) {
super(dao);
}
}
@@ -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<SiteCugInfo> implements SiteCugInfoService {
public SiteCugInfoServiceImpl(Dao dao) {
super(dao);
}
}