场地预约整改

This commit is contained in:
=
2026-06-17 09:58:15 +08:00
parent ef7ac57bb8
commit 33859d3be0
27 changed files with 2686 additions and 523 deletions
@@ -5,32 +5,26 @@ import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.hutool.json.JSONObject;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.result.Result;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.sys.models.Sys_home_template;
import com.budwk.app.sys.models.Sys_menu;
import com.budwk.app.sys.services.SysHomeNewsService;
import com.budwk.app.sys.services.SysMenuService;
import com.budwk.app.sys.services.SysUserService;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
import io.swagger.annotations.ApiOperation;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.FieldFilter;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.Daos;
import org.nutz.integration.jedis.RedisService;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.lang.Lang;
import org.nutz.lang.Strings;
import org.nutz.lang.util.NutMap;
@@ -42,9 +36,7 @@ import org.nutz.mvc.annotation.Param;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
@IocBean
@@ -58,7 +50,7 @@ public class SysHomeController {
@Inject
private SysUserService sysUserService;
@Inject
private RedisService redisService;
private SysHomeNewsService sysHomeNewsService;
@Inject
private ActivityBasicScopeService activityBasicScopeService;
@@ -285,31 +277,11 @@ public class SysHomeController {
// @CacheDefaults(cacheName = RedisConstant.PLATFORM_REDIS_WKCACHE_PREFIX + "web_news", isHash = true)
// @CacheResult(cacheKey = "news", ignoreNull = true, cacheLiveTime = 60 * 60 * 24)
public Result getNews() {
try {
String url = "https://xgh.cug.edu.cn/";
Document document = Jsoup.connect(url).get();
Elements homeElements = document.select(".gh,.jc,.fc,.jg");
List<Map<String, Object>> news = homeElements.stream().map(homeElement -> {
//获取板块名称
String name = homeElement.selectFirst(".title").text();
Elements liElements = homeElement.select("li");
List<NutMap> ele = liElements.stream().map(liElement -> {
NutMap row = NutMap.NEW();
String href = liElement.selectFirst("a").attr("href");
String time = liElement.selectFirst("span").text();
String title = liElement.selectFirst("a").attr("title");
row.put("href", href);
row.put("time", time);
row.put("text", title);
return row;
}).toList();
return Map.of("label", name, "value", ele);
}).collect(Collectors.toList());
redisService.setex("gonghui_news", 60 * 60 * 24, Json.toJson(news));
return Result.success(news);
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
return Result.success(sysHomeNewsService.getUnionNoticeNews());
} catch (Exception e) {
log.error(e);
return Result.success();
}
}
}
@@ -0,0 +1,15 @@
package com.budwk.app.sys.services;
import cn.hutool.json.JSONObject;
import java.io.IOException;
public interface SysHomeNewsService {
/**
* 获取江苏农牧科技职业学院工会网站通知公告,并保持首页新闻组件需要的数据结构。
*
* @return 首页网站新闻数据,当前使用 grassroots 字段兼容前端组件
*/
JSONObject getUnionNoticeNews() throws IOException;
}
@@ -0,0 +1,70 @@
package com.budwk.app.sys.services.impl;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import com.budwk.app.sys.services.SysHomeNewsService;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.nutz.ioc.loader.annotation.IocBean;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@IocBean
public class SysHomeNewsServiceImpl implements SysHomeNewsService {
private static final String UNION_HOME_URL = "https://www.jsahvc.edu.cn/gh/main.htm";
@Override
public JSONObject getUnionNoticeNews() throws IOException {
JSONObject root = new JSONObject();
List<JSONObject> noticeList = new ArrayList<>();
Document doc = Jsoup.connect(UNION_HOME_URL)
// 学校官网会过滤部分非浏览器请求,这里补充常规浏览器请求头提高抓取稳定性。
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36")
.referrer("https://www.jsahvc.edu.cn/")
.timeout(10000)
.get();
Elements noticeItems = getNoticeItems(doc);
for (Element item : noticeItems) {
Element titleLink = item.selectFirst(".news_title a, a[title], a");
if (titleLink == null) {
continue;
}
String title = StrUtil.blankToDefault(titleLink.attr("title"), titleLink.text()).trim();
String url = titleLink.absUrl("href").trim();
Element meta = item.selectFirst(".news_meta, .date, .news_date, .time");
JSONObject node = new JSONObject();
node.set("title", title);
node.set("date", meta == null ? "" : meta.text().trim());
node.set("url", url);
noticeList.add(node);
}
root.set("grassroots", noticeList);
return root;
}
/**
* 优先按栏目标题查找通知公告列表,找不到时再使用学校官网历史模板中的栏目位兜底。
*
* @param doc 工会网站首页文档
* @return 通知公告列表节点
*/
private Elements getNoticeItems(Document doc) {
for (Element post : doc.select(".post")) {
Element title = post.selectFirst(".tt .title, .tit .title, h3, h2");
if (title != null && title.text().contains("通知公告")) {
return post.select(".news_list li, .wp_article_list li");
}
}
return doc.select(".post-18 .news_list li, .post-18 .wp_article_list li");
}
}
@@ -284,19 +284,52 @@ public class SiteCugApplyController {
private List<int[]> buildDisabledRanges(NutMap site, String today) {
List<NutMap> disabledList = toNutMapList(site.get("notApplyTimeList"));
List<int[]> result = new ArrayList<>();
int targetWeekDay = toWeekDay(DateUtil.parseDate(today));
for (NutMap item : disabledList) {
if (item == null || !StrUtil.equals(today, item.getString("date"))) {
if (item == null || StrUtil.hasBlank(item.getString("startTime"), item.getString("endTime"))) {
continue;
}
Integer start = parseTimeToMinutes(item.getString("startTime"));
Integer end = parseTimeToMinutes(item.getString("endTime"));
if (start != null && end != null && end > start) {
result.add(new int[]{start, end});
// 禁用时间兼容旧的按日期配置,同时支持新增的按星期配置。
if (isDateDisabledRange(item, today) || isWeekDisabledRange(item, targetWeekDay)) {
addDisabledRange(result, item);
}
}
return result;
}
private boolean isDateDisabledRange(NutMap item, String today) {
String type = item.getString("type");
return (StrUtil.isBlank(type) || StrUtil.equals("date", type))
&& StrUtil.equals(today, item.getString("date"));
}
private boolean isWeekDisabledRange(NutMap item, int targetWeekDay) {
Integer weekDay = parseWeekDay(item.get("weekDay"));
return StrUtil.equals("week", item.getString("type"))
&& weekDay != null
&& weekDay == targetWeekDay;
}
private void addDisabledRange(List<int[]> result, NutMap item) {
Integer start = parseTimeToMinutes(item.getString("startTime"));
Integer end = parseTimeToMinutes(item.getString("endTime"));
if (start != null && end != null && end > start) {
result.add(new int[]{start, end});
}
}
private Integer parseWeekDay(Object value) {
if (value == null) {
return null;
}
try {
int weekDay = Integer.parseInt(String.valueOf(value));
return weekDay >= 1 && weekDay <= 7 ? weekDay : null;
} catch (NumberFormatException e) {
return null;
}
}
private List<int[]> buildReservedRanges(List<SiteCugApply> applyList, String today) {
List<int[]> result = new ArrayList<>();
String dayStart = today + " 00:00:00";
@@ -404,6 +437,11 @@ public class SiteCugApplyController {
return week == 0 || week == 6;
}
private int toWeekDay(DateTime date) {
int week = date.dayOfWeek() - 1;
return week == 0 ? 7 : week;
}
private List<NutMap> buildAvailabilityBlocks(SiteCugInfo siteInfo, String day, List<SiteCugApply> applyList, Set<String> holidaySet) {
if (siteInfo.getReserveTimeType() != null && siteInfo.getReserveTimeType() == 2) {
return buildFullDayAvailabilityBlocks(siteInfo, day, applyList, holidaySet);
@@ -802,6 +840,7 @@ public class SiteCugApplyController {
.setReserveStartTime(apply.getReserveStartTime())
.setReserveEndTime(apply.getReserveEndTime())
.setJoinCount(apply.getJoinCount())
.setCoffeeNeeded(apply.getCoffeeNeeded())
.setApplyCause(apply.getApplyCause())
.setBackOption(apply.getBackOption())
.setYearlyReserveEndDate(apply.getYearlyReserveEndDate());
@@ -0,0 +1,66 @@
package com.budwk.app.zhgh.dayofficework.siteCug.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
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.ioc.aop.Aop;
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 org.nutz.mvc.annotation.Param;
@IocBean
@At("/platform/siteCug/branchUnionAudit")
@Api("场馆分工会审核")
@Ok("json:full")
public class SiteCugBranchUnionAuditController {
private static final String AUDIT_TASK_NAME = "c4493b93-75c0-4980-b604-19df2be2c42c";
@Inject
private SiteCugApplyService applyService;
@At("/")
@Ok("beetl:/platform/zhgh/dayofficework/siteCug/branchUnionAudit/index.html")
@SaCheckPermission("siteCug.branchUnionAudit")
public void index() {
}
@At("/h5")
@Ok("beetl:/platform/zhghh5/dayofficework/siteCug/branchUnionAudit/index.html")
@SaCheckPermission("h5.siteCug.branchUnionAudit")
public void h5Index() {
}
@At
@ApiOperation("分页查询")
@SaCheckPermission(value = {"siteCug.branchUnionAudit", "h5.siteCug.branchUnionAudit"}, mode = SaMode.OR)
public Result pageData(PageForm pageForm,
@Param("siteType") String siteType,
@Param("siteId") String siteId,
@Param("approval") Boolean approval) {
return Result.success(applyService.listAuditPage(pageForm, siteType, siteId, approval, AUDIT_TASK_NAME));
}
@At("/executeTask")
@ApiOperation("执行审核任务")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission(value = {"siteCug.branchUnionAudit", "h5.siteCug.branchUnionAudit"}, mode = SaMode.OR)
public Result executeTask(@Param("data") String data) {
return applyService.executeAuditTask(data);
}
@At("/revokeTask")
@ApiOperation("撤回审核任务")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission(value = {"siteCug.branchUnionAudit", "h5.siteCug.branchUnionAudit"}, mode = SaMode.OR)
public Result revokeTask(@Param("taskId") Long taskId, @Param("applyId") String applyId) {
return applyService.revokeAuditTask(taskId, applyId);
}
}
@@ -0,0 +1,66 @@
package com.budwk.app.zhgh.dayofficework.siteCug.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
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.ioc.aop.Aop;
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 org.nutz.mvc.annotation.Param;
@IocBean
@At("/platform/siteCug/deputySchoolUnion")
@Api("场馆校工会副主席审核")
@Ok("json:full")
public class SiteCugDeputySchoolUnionController {
private static final String AUDIT_TASK_NAME = "de7044e4-187b-43c8-ab47-87eb0998302a";
@Inject
private SiteCugApplyService applyService;
@At("/")
@Ok("beetl:/platform/zhgh/dayofficework/siteCug/deputySchoolUnion/index.html")
@SaCheckPermission("siteCug.deputySchoolUnion")
public void index() {
}
@At("/h5")
@Ok("beetl:/platform/zhghh5/dayofficework/siteCug/deputySchoolUnion/index.html")
@SaCheckPermission("h5.siteCug.deputySchoolUnion")
public void h5Index() {
}
@At
@ApiOperation("分页查询")
@SaCheckPermission(value = {"siteCug.deputySchoolUnion", "h5.siteCug.deputySchoolUnion"}, mode = SaMode.OR)
public Result pageData(PageForm pageForm,
@Param("siteType") String siteType,
@Param("siteId") String siteId,
@Param("approval") Boolean approval) {
return Result.success(applyService.listAuditPage(pageForm, siteType, siteId, approval, AUDIT_TASK_NAME));
}
@At("/executeTask")
@ApiOperation("执行审核任务")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission(value = {"siteCug.deputySchoolUnion", "h5.siteCug.deputySchoolUnion"}, mode = SaMode.OR)
public Result executeTask(@Param("data") String data) {
return applyService.executeAuditTask(data);
}
@At("/revokeTask")
@ApiOperation("撤回审核任务")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission(value = {"siteCug.deputySchoolUnion", "h5.siteCug.deputySchoolUnion"}, mode = SaMode.OR)
public Result revokeTask(@Param("taskId") Long taskId, @Param("applyId") String applyId) {
return applyService.revokeAuditTask(taskId, applyId);
}
}
@@ -83,15 +83,11 @@ public class SiteCugManageController {
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);
boolean invalid = notApplyTimeList.stream().anyMatch(this::isInvalidDisabledTime);
if (invalid) {
return Result.error("Invalid disabled time settings");
}
notApplyTimeList.forEach(this::normalizeDisabledTime);
}
if (info.getReserveTimeType() == null) {
@@ -196,6 +192,62 @@ public class SiteCugManageController {
}
}
private boolean isInvalidDisabledTime(NutMap item) {
if (item == null
|| StrUtil.isBlank(item.getString("startTime"))
|| StrUtil.isBlank(item.getString("endTime"))
|| item.getString("startTime").compareTo(item.getString("endTime")) >= 0) {
return true;
}
String type = item.getString("type");
if (StrUtil.isBlank(type) || StrUtil.equals(type, "date")) {
return StrUtil.isBlank(item.getString("date"));
}
if (StrUtil.equals(type, "week")) {
return !isWeekDay(item.get("weekDay"));
}
return true;
}
private void normalizeDisabledTime(NutMap item) {
if (item == null) {
return;
}
if (StrUtil.isBlank(item.getString("type"))) {
item.put("type", "date");
}
if (StrUtil.equals(item.getString("type"), "week")) {
int weekDay = Integer.parseInt(String.valueOf(item.get("weekDay")));
item.put("weekDay", weekDay);
item.put("weekName", getWeekName(weekDay));
}
}
private boolean isWeekDay(Object value) {
if (value == null) {
return false;
}
try {
int weekDay = Integer.parseInt(String.valueOf(value));
return weekDay >= 1 && weekDay <= 7;
} catch (Exception e) {
return false;
}
}
private String getWeekName(int weekDay) {
return switch (weekDay) {
case 1 -> "星期一";
case 2 -> "星期二";
case 3 -> "星期三";
case 4 -> "星期四";
case 5 -> "星期五";
case 6 -> "星期六";
case 7 -> "星期日";
default -> "";
};
}
private NutMap normalizeOpenHourTimeUnit(NutMap item) {
if (item == null) {
return null;
@@ -2,55 +2,29 @@ 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("场馆校工会审核")
@Api("场馆校工会主席审核")
@Ok("json:full")
public class SiteCugSchoolUnionAuditController {
private static final String YEARLY_BATCH_PREFIX = "YEARLY_BATCH:";
private static final String AUDIT_TASK_NAME = "c8c03407-cf26-4e0b-82f1-b2afc9c79996";
@Inject
private SiteCugApplyService applyService;
@Inject
private ProcessTaskService processTaskService;
@Inject
private FlowCommonService flowCommonService;
@At("/")
@Ok("beetl:/platform/zhgh/dayofficework/siteCug/schoolUnionAudit/index.html")
@@ -71,102 +45,7 @@ public class SiteCugSchoolUnionAuditController {
@Param("siteType") String siteType,
@Param("siteId") String siteId,
@Param("approval") Boolean approval) {
Sql sql = Sqls.create("""
SELECT
info.*,
si.name as siteName,
CASE WHEN info.reserveType = 'club' THEN info.clubName ELSE info.applyUnionName END AS reserveTargetName,
ins.id AS instanceId,
ins.businessNo,
ins.state instanceState,
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() + "%");
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(resolveOrderColumn(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);
}
private String resolveOrderColumn(String pageOrderName) {
if (StrUtil.isBlank(pageOrderName)) {
return "info.createdAt";
}
switch (pageOrderName) {
case "siteName":
return "si.name";
case "applyUserName":
return "info.applyUserName";
case "applyLoginName":
return "info.applyLoginName";
case "yearlyBatch":
return "yearlyBatch";
case "reserveTargetName":
return "reserveTargetName";
case "reserveStartTime":
return "info.reserveStartTime";
case "reserveEndTime":
return "info.reserveEndTime";
case "applyMobile":
return "info.applyMobile";
case "curTaskName":
return "curTaskName";
case "instanceState":
return "ins.state";
default:
return "info.createdAt";
}
return Result.success(applyService.listAuditPage(pageForm, siteType, siteId, approval, AUDIT_TASK_NAME));
}
@At("/executeTask")
@@ -174,72 +53,14 @@ public class SiteCugSchoolUnionAuditController {
@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("提交成功");
return applyService.executeAuditTask(data);
}
@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("撤回成功");
return applyService.revokeAuditTask(taskId, applyId);
}
}
@@ -100,6 +100,11 @@ public class SiteCugApply extends BaseModel {
@ColDefine(type = ColType.INT)
private Integer joinCount;
@Column
@Comment("是否需要咖啡")
@ColDefine(type = ColType.INT)
private Integer coffeeNeeded;
@Column
@Comment("预约事由")
@ColDefine(type = ColType.VARCHAR, width = 1000)
@@ -1,10 +1,14 @@
package com.budwk.app.zhgh.dayofficework.siteCug.service;
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.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 org.nutz.lang.util.NutMap;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
@@ -21,4 +25,13 @@ public interface SiteCugApplyService extends BaseService<SiteCugApply> {
// 场馆预约查询页的导出逻辑统一放在 service,controller 只负责接收请求
void exportRecord(SiteCugRecordPageForm pageForm, HttpServletResponse response);
// 场馆预约审核页共用列表查询,按流程节点 taskName 区分不同审核页面
Pagination<NutMap> listAuditPage(PageForm pageForm, String siteType, String siteId, Boolean approval, String auditTaskName);
// 场馆预约审核提交共用同一套任务执行逻辑,兼容全年预约批次一起审核
Result executeAuditTask(String data);
// 场馆预约审核撤回共用同一套任务撤回逻辑,兼容全年预约批次一起撤回
Result revokeAuditTask(Long taskId, String applyId);
}
@@ -4,14 +4,25 @@ 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.dev33.satoken.stp.StpUtil;
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.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.service.impl.BaseServiceImpl;
import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.flow.service.FlowCommonService;
import com.budwk.app.flow.service.ProcessTaskService;
import com.budwk.app.sys.models.SysHoliday;
import com.budwk.app.sys.models.Sys_union;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
@@ -30,6 +41,7 @@ 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.json.Json;
import org.nutz.lang.util.NutMap;
import javax.servlet.http.HttpServletResponse;
@@ -44,8 +56,14 @@ import java.util.stream.Collectors;
@IocBean(args = {"refer:dao"})
public class SiteCugApplyServiceImpl extends BaseServiceImpl<SiteCugApply> implements SiteCugApplyService {
private static final String YEARLY_BATCH_PREFIX = "YEARLY_BATCH:";
@Inject
private SysClubService sysClubService;
@Inject
private ProcessTaskService processTaskService;
@Inject
private FlowCommonService flowCommonService;
public SiteCugApplyServiceImpl(Dao dao) {
super(dao);
@@ -64,6 +82,11 @@ public class SiteCugApplyServiceImpl extends BaseServiceImpl<SiteCugApply> imple
if (StrUtil.isBlank(apply.getReserveStartTime()) || StrUtil.isBlank(apply.getReserveEndTime())) {
return Map.of(false, "预约时间不能为空");
}
if (apply.getCoffeeNeeded() == null) {
apply.setCoffeeNeeded(0);
} else if (apply.getCoffeeNeeded() != 0 && apply.getCoffeeNeeded() != 1) {
return Map.of(false, "是否需要咖啡仅支持是/否");
}
if (!DateUtil.parseDateTime(apply.getReserveEndTime()).isAfter(DateUtil.parseDateTime(apply.getReserveStartTime()))) {
return Map.of(false, "结束时间必须晚于开始时间");
}
@@ -204,6 +227,123 @@ public class SiteCugApplyServiceImpl extends BaseServiceImpl<SiteCugApply> imple
CommonDownloadUtil.download("场馆预约查询.xlsx", workbook, response);
}
@Override
public Pagination<NutMap> listAuditPage(PageForm pageForm, String siteType, String siteId, Boolean approval, String auditTaskName) {
Sql sql = Sqls.create("""
SELECT
info.*,
si.name as siteName,
CASE WHEN info.reserveType = 'club' THEN info.clubName ELSE info.applyUnionName END AS reserveTargetName,
ins.id AS instanceId,
ins.businessNo,
ins.state instanceState,
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() + "%");
cnd.and(seg);
}
cnd.andEX("si.typeId", "=", siteType);
cnd.andEX("info.siteId", "=", siteId);
cnd.and("t.taskName", "=", auditTaskName);
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(resolveAuditOrderColumn(pageForm.getPageOrderName()), PageUtil.getOrder(pageForm.getPageOrderBy()));
} else {
cnd.desc("info.createdAt");
}
cnd.groupBy("t.id");
sql.setCondition(cnd);
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
}
@Override
public Result executeAuditTask(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 = fetch(args.getStr("applyId"));
if (apply == null) {
return Result.error("预约记录不存在");
}
List<ProcessTask> taskList = getBatchDoingTaskList(apply, 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("提交成功");
}
@Override
public Result revokeAuditTask(Long taskId, String applyId) {
ProcessTask currentTask = processTaskService.getById(taskId);
if (currentTask == null) {
return Result.error("撤回任务不存在");
}
SiteCugApply apply = fetch(applyId);
if (apply == null) {
return Result.error("预约记录不存在");
}
List<ProcessTask> taskList = getBatchDoneTaskList(apply, currentTask);
for (ProcessTask task : taskList) {
flowCommonService.revokeTask(task.getId());
}
if (taskList.size() > 1) {
return Result.success("已撤回该全年预约批次的审核任务");
}
return Result.success("撤回成功");
}
// 场馆预约查询导出字段统一在 service 中定义,并与当前列表展示字段保持一致
private List<ExcelExportEntity> buildRecordExportEntities() {
List<ExcelExportEntity> exportEntities = new ArrayList<>();
@@ -218,6 +358,66 @@ public class SiteCugApplyServiceImpl extends BaseServiceImpl<SiteCugApply> imple
return exportEntities;
}
private String resolveAuditOrderColumn(String pageOrderName) {
if (StrUtil.isBlank(pageOrderName)) {
return "info.createdAt";
}
switch (pageOrderName) {
case "siteName":
return "si.name";
case "applyUserName":
return "info.applyUserName";
case "applyLoginName":
return "info.applyLoginName";
case "yearlyBatch":
return "yearlyBatch";
case "reserveTargetName":
return "reserveTargetName";
case "reserveStartTime":
return "info.reserveStartTime";
case "reserveEndTime":
return "info.reserveEndTime";
case "applyMobile":
return "info.applyMobile";
case "curTaskName":
return "curTaskName";
case "instanceState":
return "ins.state";
default:
return "info.createdAt";
}
}
private List<ProcessTask> getBatchDoingTaskList(SiteCugApply apply, ProcessTask currentTask) {
List<ProcessTask> taskList = new ArrayList<>();
if (isYearlyBatchApply(apply)) {
List<SiteCugApply> batchList = 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);
}
return taskList;
}
private List<ProcessTask> getBatchDoneTaskList(SiteCugApply apply, ProcessTask currentTask) {
List<ProcessTask> taskList = new ArrayList<>();
if (isYearlyBatchApply(apply)) {
List<SiteCugApply> batchList = 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);
}
return taskList;
}
private boolean isYearlyBatchApply(SiteCugApply apply) {
return StrUtil.isNotBlank(apply.getBackOption()) && apply.getBackOption().startsWith(YEARLY_BATCH_PREFIX);
}
// 预约时间范围内只要包含周六或周日,就视为不可预约
private boolean containsWeekend(String reserveStartTime, String reserveEndTime) {
Date start = DateUtil.beginOfDay(DateUtil.parseDateTime(reserveStartTime));
@@ -261,19 +461,69 @@ public class SiteCugApplyServiceImpl extends BaseServiceImpl<SiteCugApply> imple
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"))) {
if (range == null || StrUtil.hasBlank(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) {
if (isDateDisabledTime(range) && intersectsDateDisabledTime(range, reserveStart, reserveEnd)) {
return true;
}
if (isWeekDisabledTime(range) && intersectsWeekDisabledTime(range, reserveStart, reserveEnd)) {
return true;
}
}
return false;
}
private boolean isDateDisabledTime(NutMap range) {
String type = range.getString("type");
return (StrUtil.isBlank(type) || StrUtil.equals(type, "date")) && StrUtil.isNotBlank(range.getString("date"));
}
private boolean isWeekDisabledTime(NutMap range) {
return StrUtil.equals(range.getString("type"), "week") && range.get("weekDay") != null;
}
private boolean intersectsDateDisabledTime(NutMap range, DateTime reserveStart, DateTime reserveEnd) {
DateTime disabledStart = DateUtil.parseDateTime(range.getString("date") + " " + range.getString("startTime") + ":00");
DateTime disabledEnd = DateUtil.parseDateTime(range.getString("date") + " " + range.getString("endTime") + ":00");
return reserveStart.isBefore(disabledEnd) && reserveEnd.isAfter(disabledStart);
}
private boolean intersectsWeekDisabledTime(NutMap range, DateTime reserveStart, DateTime reserveEnd) {
int disabledWeekDay = parseWeekDay(range.get("weekDay"));
if (disabledWeekDay == 0) {
return false;
}
Date current = DateUtil.beginOfDay(reserveStart);
Date end = DateUtil.beginOfDay(reserveEnd);
while (current.compareTo(end) <= 0) {
DateTime currentDay = DateUtil.date(current);
if (toWeekDay(currentDay) == disabledWeekDay) {
DateTime disabledStart = DateUtil.parseDateTime(DateUtil.formatDate(currentDay) + " " + range.getString("startTime") + ":00");
DateTime disabledEnd = DateUtil.parseDateTime(DateUtil.formatDate(currentDay) + " " + range.getString("endTime") + ":00");
if (reserveStart.isBefore(disabledEnd) && reserveEnd.isAfter(disabledStart)) {
return true;
}
}
current = DateUtil.offsetDay(current, 1);
}
return false;
}
private int parseWeekDay(Object value) {
try {
int weekDay = Integer.parseInt(String.valueOf(value));
return weekDay >= 1 && weekDay <= 7 ? weekDay : 0;
} catch (Exception e) {
return 0;
}
}
private int toWeekDay(DateTime currentDay) {
int weekDay = currentDay.dayOfWeek() - 1;
return weekDay == 0 ? 7 : weekDay;
}
// 按预约类型补齐最终落库数据,避免前端传错或被绕过
private Map<Boolean, String> fillReserveTypeData(SiteCugApply apply) {
if (StrUtil.isBlank(apply.getReserveType())) {