模块移植:场地预约、理论学习()

This commit is contained in:
2026-05-22 15:01:28 +08:00
parent 9bd1e865b9
commit a9b6c782ed
127 changed files with 30528 additions and 38 deletions
@@ -27,13 +27,14 @@ import java.util.Map;
@IocBean
public class SmsJshvcServiceImpl implements SmsService {
private static final String APPID = "";
private static final String APP_SECRET = "";
private static final String TOKEN_URL = "";
private static final String MSG_URL = "";
private static final String REDIS_KEY_MSG_ACCESS_TOKEN = "";
private static final String APPID = "1430576717768122368";
private static final String APP_SECRET = "19A0ACB03FBQHL4R4XQD";
private static final String TOKEN_URL = "https://gateway.jshvc.edu.cn/token/gateway/accessToken";
private static final String MSG_URL = "https://gateway.jshvc.edu.cn/mp/restful/v2/message/send";
private static final String REDIS_KEY_MSG_ACCESS_TOKEN = "msg:token:";
@Inject
@Inject
private RedisService redisService;
@Override
@@ -3,19 +3,38 @@ package com.budwk.app.flow.service;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
import com.budwk.app.base.event.flow.jumptofirsttask.FlowJumpToFirstTaskNodePublisher;
import com.budwk.app.base.result.Result;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.flow.engine.event.ProcessEvent;
import com.budwk.app.flow.engine.event.ProcessPublisher;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessEventTypeEnum;
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
import com.budwk.app.flow.enums.ProcessTaskPerformTypeEnum;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.List;
@IocBean
public class FlowCommonService {
@Inject
private FlowEngine flowEngine;
@Inject
private Dao dao;
/**
* 执行任务
* @param args
@@ -70,4 +89,44 @@ public class FlowCommonService {
}
}
/**
* 撤销已流转到下一步的任务,并重新激活当前任务和流程实例。
* 场馆预约等业务在校工会审核撤回时复用这里的流程状态处理。
*/
@Aop(TransAop.READ_COMMITTED)
public Result revokeTask(Long taskId) {
// 自己任务
ProcessTask selfTask = dao.fetch(ProcessTask.class, taskId);
selfTask.setTaskState(ProcessTaskStateEnum.DOING.getCode());
// 撤销任务
List<ProcessTask> taskList = dao.query(ProcessTask.class, Cnd.where(ProcessTask::getTaskParentId, "=", taskId));
for (ProcessTask task : taskList) {
task.setTaskState(ProcessTaskStateEnum.WITHDRAW.getCode());
dao.update(task);
}
// 会签并行任务 撤销后续任务
if (selfTask.getPerformType().equals(ProcessTaskPerformTypeEnum.COUNTERSIGN.getCode())) {
List<ProcessTask> doingTasks = dao.query(ProcessTask.class, Cnd.where(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.DOING.getCode())
.and(ProcessTask::getProcessInstanceId, "=", selfTask.getProcessInstanceId()));
for (ProcessTask doingTask : doingTasks) {
doingTask.setTaskState(ProcessTaskStateEnum.WITHDRAW.getCode());
dao.update(doingTask);
}
}
// 激活
dao.update(selfTask);
// 流程激活
dao.update(ProcessInstance.class, Chain.make("state", ProcessInstanceStateEnum.DOING.getCode()), Cnd.where(ProcessInstance::getId, "=", selfTask.getProcessInstanceId()));
// 发送任务撤回事件,保证业务监听能同步处理已撤回节点。
for (ProcessTask task : taskList) {
ProcessPublisher.notify(ProcessEvent.builder().eventType(ProcessEventTypeEnum.PROCESS_TASK_REVOKE).sourceId(task.getId()).build());
}
return Result.success();
}
}
@@ -211,4 +211,13 @@ public interface ProcessTaskService extends BaseService<ProcessTask> {
*/
List<ProcessTask> getDoingTaskByBizIdTaskName(List<String> bizIds, String taskName);
/**
* 获取已结束的任务
*
* @param bizIds 业务ID
* @param taskName 任务名称
* @return
*/
List<ProcessTask> getDoneTaskByBizIdTaskName(List<String> bizIds, String taskName);
}
@@ -87,6 +87,16 @@ public class ProcessTaskServiceImpl extends BaseServiceImpl<ProcessTask> impleme
return vo;
}
@Override
public List<ProcessTask> getDoneTaskByBizIdTaskName(List<String> bizIds, String taskName) {
List<ProcessInstance> instances = dao().query(ProcessInstance.class, Cnd.where(ProcessInstance::getBusinessNo, "in", bizIds));
List<Long> instanceIds = instances.stream().map(ProcessInstance::getId).toList();
List<ProcessTask> tasks = dao().query(ProcessTask.class, Cnd.where(ProcessTask::getProcessInstanceId, "in", instanceIds)
.and(ProcessTask::getTaskName, "=", taskName)
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.FINISHED.getCode()));
return tasks;
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void saveProcessTask(ProcessTask processTask) {
@@ -20,7 +20,8 @@ import java.io.Serializable;
@Comment("活动人员范围设置")
@TableIndexes({
@Index(name = "INDEX_ACTIVITY_USER_SCOPE_GROUPID", fields = {"groupId"}, unique = false),
@Index(name = "INDEX_ACTIVITY_USER_SCOPE_USERID", fields = {"userId"}, unique = false)
@Index(name = "INDEX_ACTIVITY_USER_SCOPE_USERID", fields = {"userId"}, unique = false),
@Index(name = "INDEX_ACTIVITY_USER_SCOPE_GROUPID_USERID", fields = {"groupId", "userId"}, unique = false)
})
public class ActivityUserScope extends BaseModel implements Serializable {
@@ -46,6 +47,16 @@ public class ActivityUserScope extends BaseModel implements Serializable {
@Comment("userid")
private String userId;
@Column
@ColDefine(type = ColType.INT, width = 2)
@Comment("分组类型 1.结果分组 2.SQL条件分组")
private Integer groupType;
@Column
@ColDefine(customType = "longtext")
@Comment("SQL条件分组保存的查询SQL")
private String groupSql;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("创建人")
@@ -21,4 +21,49 @@ public interface ActivityBasicScopeService extends BaseService<ActivityUserScope
*/
void largeDataInsert(List<ActivityUserScope> list) ;
/**
* 查询分组基础信息。
* SQL条件分组与结果分组都只从同一张 activity_user_scope 表读取。
*/
ActivityUserScope getGroupInfo(Integer groupId);
/**
* 查询分组类型,未配置时默认按结果分组处理。
*/
Integer getGroupType(Integer groupId);
/**
* 构造分组对应的人员ID子查询。
* 结果分组直接取 activity_user_scope.userId,SQL条件分组则按保存的 groupSql 动态生成查询。
*/
Sql buildGroupUserIdSubSql(Integer groupId);
/**
* 构造可直接拼接到业务SQL中的人员ID子查询文本。
* 仅用于已有大量原生SQL场景,避免每个模块重复拼接 groupSql 逻辑。
*/
String buildGroupUserIdSubSqlText(Integer groupId);
/**
* 查询分组当前命中的人员ID列表。
*/
List<String> listGroupUserIds(Integer groupId);
/**
* 判断指定用户是否在某个分组内。
* SQL条件分组会按保存的 groupSql 动态校验,而不是依赖落库的 userId 明细。
*/
boolean isUserInGroup(Integer groupId, String userId);
/**
* 批量过滤出当前用户可见的分组ID。
* 用于列表页先查活动、再按活动分组做二次权限过滤的场景。
*/
List<Integer> filterGroupIdsByUser(List<Integer> groupIds, String userId);
/**
* SQL条件分组删除人员时,不再删除主表记录,而是把排除条件追加到已保存的 groupSql 中。
*/
void excludeUsersFromSqlGroup(Integer groupId, List<String> userIds);
}
@@ -1,6 +1,7 @@
package com.budwk.app.zhgh.activity.basic.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.base.utils.DateUtil;
import com.budwk.app.zhgh.activity.basic.models.ActivityUserScope;
@@ -8,17 +9,20 @@ import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.impl.NutTxDao;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
* @author zxy
@@ -29,6 +33,7 @@ import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
public class ActivityBasicScopeServiceImpl extends BaseServiceImpl<ActivityUserScope> implements ActivityBasicScopeService {
private static final int GROUP_TYPE_RESULT = 1;
public ActivityBasicScopeServiceImpl(Dao dao) {
super(dao);
@@ -80,4 +85,122 @@ public class ActivityBasicScopeServiceImpl extends BaseServiceImpl<ActivityUserS
}
log.info("结束时间" + DateUtil.getDateTime());
}
@Override
public ActivityUserScope getGroupInfo(Integer groupId) {
if (groupId == null) {
return null;
}
List<ActivityUserScope> list = dao().query(ActivityUserScope.class, Cnd.where("groupId", "=", groupId).desc("id"));
return CollectionUtil.isEmpty(list) ? null : list.get(0);
}
@Override
public Integer getGroupType(Integer groupId) {
ActivityUserScope userScope = getGroupInfo(groupId);
if (userScope == null || userScope.getGroupType() == null) {
return GROUP_TYPE_RESULT;
}
return userScope.getGroupType();
}
@Override
public Sql buildGroupUserIdSubSql(Integer groupId) {
return Sqls.create(buildGroupUserIdSubSqlText(groupId));
}
@Override
public String buildGroupUserIdSubSqlText(Integer groupId) {
ActivityUserScope groupInfo = getGroupInfo(groupId);
if (groupInfo == null) {
return "SELECT NULL AS userId WHERE 1 = 0";
}
if (GROUP_TYPE_RESULT == getGroupType(groupId)) {
return "SELECT userId FROM activity_user_scope WHERE groupId = " + groupId + " AND userId IS NOT NULL";
}
if (StrUtil.isBlank(groupInfo.getGroupSql())) {
return "SELECT NULL AS userId WHERE 1 = 0";
}
return """
SELECT DISTINCT
u.id AS userId
FROM
`vw_user` u
LEFT JOIN sys_user_role sur ON sur.userid = u.id
LEFT JOIN club_user clubuser ON clubuser.userid = u.id
WHERE
""" + groupInfo.getGroupSql();
}
@Override
public List<String> listGroupUserIds(Integer groupId) {
Sql sql = Sqls.queryString(buildGroupUserIdSubSqlText(groupId));
dao().execute(sql);
String[] userIds = (String[]) sql.getResult();
return userIds == null ? new ArrayList<>() : List.of(userIds);
}
@Override
public boolean isUserInGroup(Integer groupId, String userId) {
if (groupId == null || StrUtil.isBlank(userId)) {
return false;
}
if (GROUP_TYPE_RESULT == getGroupType(groupId)) {
return dao().count(ActivityUserScope.class, Cnd.where("groupId", "=", groupId).and("userId", "=", userId)) > 0;
}
ActivityUserScope groupInfo = getGroupInfo(groupId);
if (groupInfo == null || StrUtil.isBlank(groupInfo.getGroupSql())) {
return false;
}
Sql sql = Sqls.create("""
SELECT
COUNT(1)
FROM
`vw_user` u
LEFT JOIN sys_user_role sur ON sur.userid = u.id
LEFT JOIN club_user clubuser ON clubuser.userid = u.id
WHERE
u.id = @userId
AND
""" + "(" + groupInfo.getGroupSql() + ")");
sql.setParam("userId", userId);
return count(sql) > 0;
}
@Override
public List<Integer> filterGroupIdsByUser(List<Integer> groupIds, String userId) {
LinkedHashSet<Integer> result = new LinkedHashSet<>();
if (CollectionUtil.isEmpty(groupIds) || StrUtil.isBlank(userId)) {
return new ArrayList<>(result);
}
groupIds.stream()
.filter(groupId -> groupId != null)
.distinct()
.forEach(groupId -> {
if (isUserInGroup(groupId, userId)) {
result.add(groupId);
}
});
return new ArrayList<>(result);
}
@Override
public void excludeUsersFromSqlGroup(Integer groupId, List<String> userIds) {
ActivityUserScope groupInfo = getGroupInfo(groupId);
if (groupInfo == null || getGroupType(groupId) != 2 || CollectionUtil.isEmpty(userIds) || StrUtil.isBlank(groupInfo.getGroupSql())) {
return;
}
String excludeUserSql = userIds.stream()
.filter(StrUtil::isNotBlank)
.distinct()
.map(userId -> "'" + StrUtil.replace(userId, "'", "''") + "'")
.collect(Collectors.joining(","));
if (StrUtil.isBlank(excludeUserSql)) {
return;
}
// SQL分组只有一条主记录,删除人员时通过追加排除条件持久化删除结果。
String groupSql = "(" + groupInfo.getGroupSql() + ") AND u.id NOT IN (" + excludeUserSql + ")";
groupInfo.setGroupSql(groupSql);
dao().updateIgnoreNull(groupInfo);
}
}
@@ -0,0 +1,823 @@
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.ProcessInstanceStateEnum;
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.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.time.Year;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@Slf4j
@IocBean
@Ok("json:full")
@Api(tags = "\u573a\u5730\u9884\u7ea6-\u573a\u9986")
@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("\u5206\u9875\u67e5\u8be2\u573a\u5730")
@SaCheckPermission(value = {"siteCug.apply", "h5.siteCug.apply"}, mode = SaMode.OR)
public Result pageData(PageForm pageForm, @Param("type") String type) {
Cnd cnd = Cnd.NEW();
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));
List<String> siteIds = listMap.stream().map(item -> item.getString("id")).filter(StrUtil::isNotBlank).toList();
String today = DateUtil.today();
String dayStart = today + " 00:00:00";
String dayEnd = today + " 23:59:59";
Map<String, List<SiteCugApply>> applyMap = queryTodayApplyMap(siteIds, dayStart, dayEnd);
Set<String> holidaySet = dao.query(SysHoliday.class, Cnd.NEW()).stream()
.map(SysHoliday::getDay)
.filter(StrUtil::isNotBlank)
.collect(Collectors.toSet());
for (NutMap map : listMap) {
map.put("typeName", typeMap.get(map.getString("typeId")));
map.put("reserveTimeTypeName", getReserveTimeTypeName(map.getInt("reserveTimeType")));
map.put("timelineSegments", buildTimelineSegments(map, applyMap.getOrDefault(map.getString("id"), new ArrayList<>()), today, holidaySet));
}
return Result.success(pagination);
}
@At
@ApiOperation("\u67e5\u8be2\u573a\u5730\u53ef\u9884\u7ea6\u65f6\u6bb5")
@SaCheckPermission(value = {"siteCug.apply", "h5.siteCug.apply"}, mode = SaMode.OR)
public Result availability(@Param("siteId") String siteId, @Param("date") String date) {
if (StrUtil.isBlank(siteId)) {
return Result.error("\u573a\u5730\u4e0d\u80fd\u4e3a\u7a7a");
}
String targetDate = StrUtil.isBlank(date) ? DateUtil.today() : date;
if (!targetDate.matches("^\\d{4}-\\d{2}-\\d{2}$")) {
return Result.error("\u9884\u7ea6\u65e5\u671f\u683c\u5f0f\u4e0d\u6b63\u786e");
}
SiteCugInfo siteInfo = infoService.fetch(siteId);
if (siteInfo == null) {
return Result.error("\u573a\u5730\u4e0d\u5b58\u5728");
}
Set<String> holidaySet = queryHolidaySet();
List<SiteCugApply> applyList = queryDayApplyList(siteId, targetDate + " 00:00:00", targetDate + " 23:59:59");
return Result.success(NutMap.NEW()
.addv("date", targetDate)
.addv("reserveTimeType", siteInfo.getReserveTimeType())
.addv("reserveTimeTypeName", getReserveTimeTypeName(siteInfo.getReserveTimeType()))
.addv("blocks", buildAvailabilityBlocks(siteInfo, targetDate, applyList, holidaySet)));
}
private Map<String, List<SiteCugApply>> queryTodayApplyMap(List<String> siteIds, String dayStart, String dayEnd) {
if (siteIds == null || siteIds.isEmpty()) {
return Map.of();
}
Sql sql = Sqls.create("""
select
sa.*
from
site_cug_apply sa
left join wf_process_instance ins on ins.businessNo = sa.id
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("sa.siteId", "in", siteIds);
cnd.and("ins.state", "in", List.of(
ProcessInstanceStateEnum.DOING.getCode(),
ProcessInstanceStateEnum.FINISHED.getCode(),
ProcessInstanceStateEnum.PENDING.getCode()
));
cnd.and("sa.reserveEndTime", ">=", dayStart);
cnd.and("sa.reserveStartTime", "<=", dayEnd);
sql.setCondition(cnd);
sql.setEntity(dao.getEntity(SiteCugApply.class));
sql.setCallback(Sqls.callback.entities());
dao.execute(sql);
List<SiteCugApply> applyList = sql.getList(SiteCugApply.class);
if (applyList == null || applyList.isEmpty()) {
return Map.of();
}
return applyList.stream()
.filter(Objects::nonNull)
.collect(Collectors.groupingBy(SiteCugApply::getSiteId));
}
private List<SiteCugApply> queryDayApplyList(String siteId, String dayStart, String dayEnd) {
if (StrUtil.isBlank(siteId)) {
return new ArrayList<>();
}
Sql sql = Sqls.create("""
select
sa.*
from
site_cug_apply sa
left join wf_process_instance ins on ins.businessNo = sa.id
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("sa.siteId", "=", siteId);
cnd.and("ins.state", "in", List.of(
ProcessInstanceStateEnum.DOING.getCode(),
ProcessInstanceStateEnum.FINISHED.getCode(),
ProcessInstanceStateEnum.PENDING.getCode()
));
cnd.and("sa.reserveEndTime", ">=", dayStart);
cnd.and("sa.reserveStartTime", "<=", dayEnd);
sql.setCondition(cnd);
sql.setEntity(dao.getEntity(SiteCugApply.class));
sql.setCallback(Sqls.callback.entities());
dao.execute(sql);
List<SiteCugApply> applyList = sql.getList(SiteCugApply.class);
return applyList == null ? new ArrayList<>() : applyList;
}
private Set<String> queryHolidaySet() {
return dao.query(SysHoliday.class, Cnd.NEW()).stream()
.map(SysHoliday::getDay)
.filter(StrUtil::isNotBlank)
.collect(Collectors.toSet());
}
private String getReserveTimeTypeName(Integer reserveTimeType) {
if (reserveTimeType != null && reserveTimeType == 2) {
return "\u5168\u5929\u5019\u9884\u7ea6";
}
return "\u5206\u6bb5\u9884\u7ea6";
}
private List<NutMap> buildTimelineSegments(NutMap site, List<SiteCugApply> applyList, String today, Set<String> holidaySet) {
List<NutMap> segments = new ArrayList<>();
boolean isWeekend = isWeekend(today);
boolean isHoliday = holidaySet.contains(today);
List<int[]> openRanges = buildOpenRanges(site, isWeekend, isHoliday);
List<int[]> disabledRanges = buildDisabledRanges(site, today);
List<int[]> reservedRanges = buildReservedRanges(applyList, today);
int totalMinutes = 24 * 60;
int segmentMinutes = 30;
for (int start = 0; start < totalMinutes; start += segmentMinutes) {
int end = start + segmentMinutes;
String status = "closed";
if (intersectsAny(start, end, openRanges)) {
status = "available";
}
if (intersectsAny(start, end, disabledRanges)) {
status = "closed";
}
if (!"closed".equals(status) && intersectsAny(start, end, reservedRanges)) {
status = "reserved";
}
segments.add(NutMap.NEW()
.addv("status", status)
.addv("startMinute", start)
.addv("endMinute", end));
}
return segments;
}
private List<int[]> buildOpenRanges(NutMap site, boolean isWeekend, boolean isHoliday) {
if (isWeekend) {
return new ArrayList<>();
}
if (Boolean.TRUE.equals(site.getBoolean("filterHolidays")) && isHoliday) {
return new ArrayList<>();
}
Integer reserveTimeType = site.getInt("reserveTimeType");
if (reserveTimeType != null && reserveTimeType == 2) {
NutMap fullDayOpenHour = toNutMap(site.get("fullDayOpenHour"));
if (fullDayOpenHour.isEmpty()) {
List<NutMap> openHours = toNutMapList(site.get("openHours"));
fullDayOpenHour = openHours.isEmpty() ? NutMap.NEW() : openHours.get(0);
}
return parseTimeRanges(List.of(fullDayOpenHour));
}
List<NutMap> segmentedOpenHours = toNutMapList(site.get("segmentedOpenHours"));
if (segmentedOpenHours.isEmpty()) {
segmentedOpenHours = toNutMapList(site.get("openHours"));
}
return parseTimeRanges(segmentedOpenHours);
}
private List<int[]> buildDisabledRanges(NutMap site, String today) {
List<NutMap> disabledList = toNutMapList(site.get("notApplyTimeList"));
List<int[]> result = new ArrayList<>();
for (NutMap item : disabledList) {
if (item == null || !StrUtil.equals(today, item.getString("date"))) {
continue;
}
Integer start = parseTimeToMinutes(item.getString("startTime"));
Integer end = parseTimeToMinutes(item.getString("endTime"));
if (start != null && end != null && end > start) {
result.add(new int[]{start, end});
}
}
return result;
}
private List<int[]> buildReservedRanges(List<SiteCugApply> applyList, String today) {
List<int[]> result = new ArrayList<>();
String dayStart = today + " 00:00:00";
String dayEnd = today + " 23:59:59";
DateTime startOfDay = DateUtil.parseDateTime(dayStart);
DateTime endOfDay = DateUtil.parseDateTime(dayEnd);
for (SiteCugApply item : applyList) {
if (item == null || StrUtil.hasBlank(item.getReserveStartTime(), item.getReserveEndTime())) {
continue;
}
DateTime reserveStart = DateUtil.parseDateTime(item.getReserveStartTime());
DateTime reserveEnd = DateUtil.parseDateTime(item.getReserveEndTime());
if (!reserveEnd.isAfter(reserveStart)) {
continue;
}
DateTime actualStart = reserveStart.isBefore(startOfDay) ? startOfDay : reserveStart;
DateTime actualEnd = reserveEnd.isAfter(endOfDay) ? endOfDay : reserveEnd;
int startMinute = actualStart.hour(true) * 60 + actualStart.minute();
int endMinute = actualEnd.hour(true) * 60 + actualEnd.minute();
if (actualEnd.second() > 0) {
endMinute = Math.min(24 * 60, endMinute + 1);
}
if (endMinute > startMinute) {
result.add(new int[]{startMinute, endMinute});
}
}
return result;
}
private List<int[]> parseTimeRanges(List<NutMap> sourceList) {
List<int[]> result = new ArrayList<>();
for (NutMap item : sourceList) {
if (item == null) {
continue;
}
Integer start = parseTimeToMinutes(item.getString("startTime"));
Integer end = parseTimeToMinutes(item.getString("endTime"));
if (start != null && end != null && end > start) {
result.add(new int[]{start, end});
}
}
return result;
}
private NutMap toNutMap(Object value) {
if (value instanceof NutMap nutMap) {
return nutMap;
}
if (value instanceof Map<?, ?> map) {
NutMap nutMap = NutMap.NEW();
map.forEach((key, val) -> nutMap.put(String.valueOf(key), val));
return nutMap;
}
return NutMap.NEW();
}
private List<NutMap> toNutMapList(Object value) {
List<NutMap> result = new ArrayList<>();
if (!(value instanceof List<?> list)) {
return result;
}
for (Object item : list) {
result.add(toNutMap(item));
}
return result;
}
private Integer parseTimeToMinutes(String timeText) {
if (StrUtil.isBlank(timeText)) {
return null;
}
List<String> parts = Arrays.stream(timeText.split(":"))
.filter(StrUtil::isNotBlank)
.toList();
if (parts.size() < 2) {
return null;
}
try {
int hour = Integer.parseInt(parts.get(0));
int minute = Integer.parseInt(parts.get(1));
if (hour < 0 || hour > 24 || minute < 0 || minute > 59) {
return null;
}
if (hour == 24 && minute > 0) {
return null;
}
return hour * 60 + minute;
} catch (NumberFormatException e) {
return null;
}
}
private boolean intersectsAny(int start, int end, List<int[]> ranges) {
for (int[] range : ranges) {
if (range != null && start < range[1] && end > range[0]) {
return true;
}
}
return false;
}
private boolean isWeekend(String dateText) {
DateTime date = DateUtil.parseDate(dateText);
int week = date.dayOfWeek() - 1;
return week == 0 || week == 6;
}
private List<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);
}
return buildSegmentedAvailabilityBlocks(siteInfo, day, applyList, holidaySet);
}
private List<NutMap> buildSegmentedAvailabilityBlocks(SiteCugInfo siteInfo, String day, List<SiteCugApply> applyList, Set<String> holidaySet) {
List<NutMap> sourceList = siteInfo.getSegmentedOpenHours();
if (sourceList == null || sourceList.isEmpty()) {
sourceList = siteInfo.getOpenHours();
}
List<NutMap> result = new ArrayList<>();
List<int[]> reservedRanges = buildReservedRanges(applyList, day);
List<int[]> disabledRanges = buildDisabledRanges(toSiteNutMap(siteInfo), day);
boolean siteClosed = isSiteClosed(siteInfo, day, holidaySet);
for (int i = 0; i < sourceList.size(); i++) {
NutMap item = toNutMap(sourceList.get(i));
Integer slotStart = parseTimeToMinutes(item.getString("startTime"));
Integer slotEnd = parseTimeToMinutes(item.getString("endTime"));
int unitMinutes = parseTimeUnitMinutes(item.get("timeUnit"));
if (slotStart == null || slotEnd == null || slotEnd <= slotStart || unitMinutes <= 0) {
continue;
}
String groupLabel = "\u573a\u6b21" + (i + 1) + " " + formatMinutes(slotStart) + "-" + formatMinutes(slotEnd) + " / " + formatUnitText(unitMinutes);
for (int cursor = slotStart; cursor + unitMinutes <= slotEnd; cursor += unitMinutes) {
int blockEnd = cursor + unitMinutes;
result.add(buildAvailabilityBlock(day, cursor, blockEnd, reservedRanges, disabledRanges, siteClosed, i, groupLabel));
}
}
return result;
}
private List<NutMap> buildFullDayAvailabilityBlocks(SiteCugInfo siteInfo, String day, List<SiteCugApply> applyList, Set<String> holidaySet) {
NutMap source = getFullDayOpenHourConfig(siteInfo);
Integer slotStart = parseTimeToMinutes(source.getString("startTime"));
Integer slotEnd = parseTimeToMinutes(source.getString("endTime"));
int unitMinutes = parseTimeUnitMinutes(source.get("timeUnit"));
if (slotStart == null || slotEnd == null || slotEnd <= slotStart || unitMinutes <= 0) {
return new ArrayList<>();
}
List<NutMap> result = new ArrayList<>();
List<int[]> reservedRanges = buildReservedRanges(applyList, day);
List<int[]> disabledRanges = buildDisabledRanges(toSiteNutMap(siteInfo), day);
boolean siteClosed = isSiteClosed(siteInfo, day, holidaySet);
String groupLabel = "\u5168\u5929\u5019\u65f6\u6bb5 / " + formatUnitText(unitMinutes);
for (int cursor = slotStart; cursor + unitMinutes <= slotEnd; cursor += unitMinutes) {
int blockEnd = cursor + unitMinutes;
result.add(buildAvailabilityBlock(day, cursor, blockEnd, reservedRanges, disabledRanges, siteClosed, 0, groupLabel));
}
return result;
}
/**
* 当预约日期就是今天时,已经过了开始时间的时间块直接标记为不可预约,
* 这样全天候和分段预约在前端展示时都会统一变成禁用状态。
*/
private NutMap buildAvailabilityBlock(String day, int startMinute, int endMinute, List<int[]> reservedRanges, List<int[]> disabledRanges, boolean siteClosed, int groupIndex, String groupLabel) {
String status = "available";
if (siteClosed || isExpiredAvailabilityBlock(day, startMinute) || intersectsAny(startMinute, endMinute, disabledRanges)) {
status = "closed";
} else if (intersectsAny(startMinute, endMinute, reservedRanges)) {
status = "reserved";
}
return NutMap.NEW()
.addv("key", day + "_" + startMinute + "_" + endMinute)
.addv("groupIndex", groupIndex)
.addv("groupLabel", groupLabel)
.addv("status", status)
.addv("startMinute", startMinute)
.addv("endMinute", endMinute)
.addv("startTime", formatMinutes(startMinute))
.addv("endTime", formatMinutes(endMinute))
.addv("label", formatMinutes(startMinute) + "-" + formatMinutes(endMinute))
.addv("startDateTime", day + " " + formatMinutes(startMinute) + ":00")
.addv("endDateTime", day + " " + formatMinutes(endMinute) + ":00");
}
/**
* 仅在当天按“时间块开始时间是否已经过去”判断是否失效,
* 避免用户继续选中已经开始或已经结束的预约时段。
*/
private boolean isExpiredAvailabilityBlock(String day, int startMinute) {
String today = DateUtil.today();
if (!StrUtil.equals(today, day)) {
return false;
}
DateTime blockStart = DateUtil.parseDateTime(day + " " + formatMinutes(startMinute) + ":00");
return !blockStart.isAfter(DateUtil.date());
}
private boolean isSiteClosed(SiteCugInfo siteInfo, String day, Set<String> holidaySet) {
if (isWeekend(day)) {
return true;
}
return Boolean.TRUE.equals(siteInfo.getFilterHolidays()) && holidaySet.contains(day);
}
private NutMap toSiteNutMap(SiteCugInfo siteInfo) {
return NutMap.NEW()
.addv("notApplyTimeList", siteInfo.getNotApplyTimeList())
.addv("filterHolidays", siteInfo.getFilterHolidays())
.addv("reserveTimeType", siteInfo.getReserveTimeType())
.addv("openHours", siteInfo.getOpenHours())
.addv("segmentedOpenHours", siteInfo.getSegmentedOpenHours())
.addv("fullDayOpenHour", siteInfo.getFullDayOpenHour());
}
private NutMap getFullDayOpenHourConfig(SiteCugInfo siteInfo) {
NutMap source = toNutMap(siteInfo.getFullDayOpenHour());
if (!source.isEmpty()) {
return source;
}
List<NutMap> openHours = siteInfo.getOpenHours();
return openHours == null || openHours.isEmpty() ? NutMap.NEW() : toNutMap(openHours.get(0));
}
private int parseTimeUnitMinutes(Object value) {
if (value == null) {
return 60;
}
try {
double unit = Double.parseDouble(String.valueOf(value));
if (unit <= 0) {
return 60;
}
return (int) Math.round(unit);
} catch (NumberFormatException e) {
return 60;
}
}
private String formatMinutes(int totalMinutes) {
int hour = totalMinutes / 60;
int minute = totalMinutes % 60;
return String.format("%02d:%02d", hour, minute);
}
private String formatUnitText(int unitMinutes) {
return unitMinutes + "\u5206\u949f";
}
private Result validateApplySelectionBySchedule(SiteCugApply apply) {
if (apply == null || StrUtil.hasBlank(apply.getSiteId(), apply.getReserveStartTime(), apply.getReserveEndTime())) {
return Result.success();
}
SiteCugInfo siteInfo = infoService.fetch(apply.getSiteId());
if (siteInfo == null) {
return Result.error("\u573a\u5730\u4e0d\u5b58\u5728");
}
DateTime start = DateUtil.parseDateTime(apply.getReserveStartTime());
DateTime end = DateUtil.parseDateTime(apply.getReserveEndTime());
String startDay = DateUtil.formatDate(start);
String endDay = DateUtil.formatDate(end);
if (!StrUtil.equals(startDay, endDay)) {
return Result.error("\u9884\u7ea6\u65f6\u95f4\u5fc5\u987b\u5728\u540c\u4e00\u5929\u5185");
}
Set<String> holidaySet = queryHolidaySet();
List<SiteCugApply> applyList = queryDayApplyList(apply.getSiteId(), startDay + " 00:00:00", startDay + " 23:59:59").stream()
.filter(item -> item != null && !StrUtil.equals(item.getId(), apply.getId()))
.collect(Collectors.toList());
if (siteInfo.getReserveTimeType() != null && siteInfo.getReserveTimeType() == 2) {
if (!isWithinOpenRanges(siteInfo, startDay, start, end, holidaySet)) {
return Result.error("\u5168\u5929\u5019\u9884\u7ea6\u8bf7\u5728\u5f53\u5929\u5f00\u653e\u65f6\u95f4\u5185\u6309\u9884\u7ea6\u65f6\u95f4\u5355\u4f4d\u9009\u62e9\u4e00\u4e2a\u6216\u591a\u4e2a\u8fde\u7eed\u65f6\u6bb5");
}
if (!isAlignedWithFullDayTimeUnit(siteInfo, start, end)) {
return Result.error("\u5168\u5929\u5019\u9884\u7ea6\u7684\u5f00\u59cb\u548c\u7ed3\u675f\u65f6\u95f4\u5fc5\u987b\u7b26\u5408\u9884\u7ea6\u65f6\u95f4\u5355\u4f4d");
}
return Result.success();
}
List<NutMap> blocks = buildSegmentedAvailabilityBlocks(siteInfo, startDay, applyList, holidaySet);
String startText = apply.getReserveStartTime();
String endText = apply.getReserveEndTime();
for (int i = 0; i < blocks.size(); i++) {
NutMap block = blocks.get(i);
if (!"available".equals(block.getString("status"))) {
continue;
}
if (!StrUtil.equals(startText, block.getString("startDateTime"))) {
continue;
}
String currentEnd = block.getString("endDateTime");
if (StrUtil.equals(endText, currentEnd)) {
return Result.success();
}
for (int j = i + 1; j < blocks.size(); j++) {
NutMap nextBlock = blocks.get(j);
NutMap prevBlock = blocks.get(j - 1);
if (!"available".equals(nextBlock.getString("status"))) {
break;
}
if (!StrUtil.equals(prevBlock.getString("endDateTime"), nextBlock.getString("startDateTime"))) {
break;
}
currentEnd = nextBlock.getString("endDateTime");
if (StrUtil.equals(endText, currentEnd)) {
return Result.success();
}
}
}
return Result.error("\u5206\u6bb5\u9884\u7ea6\u8bf7\u6309\u573a\u6b21\u65f6\u95f4\u5355\u4f4d\u9009\u62e9\u4e00\u4e2a\u6216\u591a\u4e2a\u8fde\u7eed\u65f6\u6bb5");
}
private boolean isWithinOpenRanges(SiteCugInfo siteInfo, String day, DateTime start, DateTime end, Set<String> holidaySet) {
if (isSiteClosed(siteInfo, day, holidaySet)) {
return false;
}
List<int[]> openRanges = buildOpenRanges(toSiteNutMap(siteInfo), false, false);
List<int[]> disabledRanges = buildDisabledRanges(toSiteNutMap(siteInfo), day);
int startMinute = start.hour(true) * 60 + start.minute();
int endMinute = end.hour(true) * 60 + end.minute();
if (end.second() > 0) {
endMinute = Math.min(24 * 60, endMinute + 1);
}
final int finalEndMinute = endMinute;
boolean inOpenRange = openRanges.stream().anyMatch(range -> startMinute >= range[0] && finalEndMinute <= range[1]);
return inOpenRange && !intersectsAny(startMinute, finalEndMinute, disabledRanges);
}
private boolean isAlignedWithFullDayTimeUnit(SiteCugInfo siteInfo, DateTime start, DateTime end) {
if (start == null || end == null || start.second() > 0 || end.second() > 0) {
return false;
}
NutMap source = getFullDayOpenHourConfig(siteInfo);
Integer slotStart = parseTimeToMinutes(source.getString("startTime"));
Integer slotEnd = parseTimeToMinutes(source.getString("endTime"));
int unitMinutes = parseTimeUnitMinutes(source.get("timeUnit"));
if (slotStart == null || slotEnd == null || slotEnd <= slotStart || unitMinutes <= 0) {
return false;
}
int startMinute = start.hour(true) * 60 + start.minute();
int endMinute = end.hour(true) * 60 + end.minute();
return startMinute >= slotStart
&& endMinute <= slotEnd
&& endMinute > startMinute
&& (startMinute - slotStart) % unitMinutes == 0
&& (endMinute - slotStart) % unitMinutes == 0;
}
@At
@ApiOperation("\u63d0\u4ea4\u9884\u7ea6")
@SaCheckPermission(value = {"siteCug.apply", "h5.siteCug.apply"}, mode = SaMode.OR)
@SLog(tag = "\u573a\u9986\u529f\u80fd\u7ba1\u7406-\u573a\u5730\u9884\u7ea6", msg = "\u63d0\u4ea4\u573a\u5730\u9884\u7ea6")
public Result submit(@Param("data") SiteCugApply apply) {
Result scheduleValidate = validateApplySelectionBySchedule(apply);
if (scheduleValidate.getCode() != 0) {
return scheduleValidate;
}
Map<Boolean, String> validate = applyService.validateApply(apply);
if (validate.containsKey(false)) {
return Result.error(validate.get(false));
}
submitSingleApply(apply);
return Result.success();
}
@At("/submitYearly")
@ApiOperation("\u6309\u672c\u5e74\u540e\u7eed\u540c\u661f\u671f\u540c\u65f6\u95f4\u6bb5\u6279\u91cf\u63d0\u4ea4\u9884\u7ea6")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission(value = {"siteCug.apply", "h5.siteCug.apply"}, mode = SaMode.OR)
@SLog(tag = "\u573a\u9986\u529f\u80fd\u7ba1\u7406-\u573a\u5730\u9884\u7ea6", msg = "\u6279\u91cf\u63d0\u4ea4\u672c\u5e74\u573a\u5730\u9884\u7ea6")
public Result submitYearly(@Param("data") SiteCugApply apply) {
List<SiteCugApply> applyList = buildYearlyApplyList(apply);
if (applyList.isEmpty()) {
return Result.error("\u672a\u751f\u6210\u53ef\u9884\u7ea6\u7684\u65e5\u671f");
}
for (SiteCugApply item : applyList) {
Result scheduleValidate = validateApplySelectionBySchedule(item);
if (scheduleValidate.getCode() != 0) {
return scheduleValidate;
}
Map<Boolean, String> validate = applyService.validateApply(item);
if (validate.containsKey(false)) {
return Result.error(String.format("%s \u9884\u7ea6\u5931\u8d25\uff1a%s", item.getReserveStartTime(), validate.get(false)));
}
}
String yearlyBatchNo = YEARLY_BATCH_PREFIX + UUID.randomUUID();
for (SiteCugApply item : applyList) {
item.setBackOption(yearlyBatchNo);
submitSingleApply(item);
}
return Result.success(String.format("\u5df2\u6210\u529f\u9884\u7ea6\u622a\u81f3%s\u5171%d\u4e2a\u65f6\u95f4\u6bb5", apply.getYearlyReserveEndDate(), applyList.size()));
}
@At
@ApiOperation("\u67e5\u8be2\u9884\u7ea6\u9650\u5236\u914d\u7f6e")
@SaCheckPermission(value = {"siteCug.apply", "h5.siteCug.apply"}, mode = SaMode.OR)
public Result timeLimitConfig(@Param("siteId") String siteId) {
if (StrUtil.isBlank(siteId)) {
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(), apply.getYearlyReserveEndDate())) {
return new ArrayList<>();
}
DateTime start = DateUtil.parseDateTime(apply.getReserveStartTime());
DateTime end = DateUtil.parseDateTime(apply.getReserveEndTime());
if (!end.isAfter(start)) {
return new ArrayList<>();
}
DateTime reserveEndDate = parseReserveEndDate(apply.getYearlyReserveEndDate());
if (reserveEndDate == null || reserveEndDate.isBefore(DateUtil.beginOfDay(start))) {
return new ArrayList<>();
}
SiteCugInfo siteInfo = infoService.fetch(apply.getSiteId());
if (siteInfo == null) {
return new ArrayList<>();
}
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 batchEndDate = DateUtil.endOfDay(reserveEndDate);
List<SiteCugApply> result = new ArrayList<>();
while (current.compareTo(batchEndDate) <= 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 parseReserveEndDate(String endDate) {
if (StrUtil.isBlank(endDate)) {
return null;
}
try {
return DateUtil.parseDate(endDate);
} catch (Exception e) {
return null;
}
}
private DateTime buildDateTime(DateTime date, int hour, int minute, int second) {
return DateUtil.parseDateTime(String.format("%s %02d:%02d:%02d", DateUtil.formatDate(date), hour, minute, second));
}
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())
.setYearlyReserveEndDate(apply.getYearlyReserveEndDate());
}
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,242 @@
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.Objects;
import java.util.stream.Collectors;
@Slf4j
@IocBean
@Ok("json:full")
@Api(tags = "siteCug manage")
@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("page data")
@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("submit")
@SaCheckPermission("siteCug.manage")
@SLog(tag = "siteCug.manage", msg = "submit site")
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("Invalid disabled time settings");
}
}
if (info.getReserveTimeType() == null) {
info.setReserveTimeType(1);
}
List<NutMap> segmentedOpenHours = info.getSegmentedOpenHours();
NutMap fullDayOpenHour = info.getFullDayOpenHour();
if ((segmentedOpenHours == null || segmentedOpenHours.isEmpty()) && info.getReserveTimeType() == 1) {
segmentedOpenHours = info.getOpenHours();
}
if ((fullDayOpenHour == null || fullDayOpenHour.isEmpty()) && info.getReserveTimeType() == 2 && info.getOpenHours() != null && !info.getOpenHours().isEmpty()) {
fullDayOpenHour = info.getOpenHours().get(0);
}
if (info.getReserveTimeType() == 1) {
if (segmentedOpenHours == null || segmentedOpenHours.isEmpty()) {
return Result.error("Please add at least one booking slot");
}
boolean invalid = segmentedOpenHours.stream().anyMatch(item ->
item == null
|| StrUtil.isBlank(item.getString("startTime"))
|| StrUtil.isBlank(item.getString("endTime"))
|| item.getString("startTime").compareTo(item.getString("endTime")) >= 0
|| !isPositiveNumber(item.get("timeUnit")));
if (invalid) {
return Result.error("Invalid segmented booking slot settings");
}
if (hasOverlap(segmentedOpenHours)) {
return Result.error("Segmented booking slots cannot overlap");
}
segmentedOpenHours = segmentedOpenHours.stream()
.filter(Objects::nonNull)
.map(this::normalizeOpenHourTimeUnit)
.collect(Collectors.toList());
info.setSegmentedOpenHours(segmentedOpenHours);
info.setOpenHours(segmentedOpenHours);
} else if (info.getReserveTimeType() == 2) {
if (fullDayOpenHour == null || fullDayOpenHour.isEmpty()) {
return Result.error("Please set the full-day booking time range");
}
NutMap item = fullDayOpenHour;
if (item == null
|| StrUtil.isBlank(item.getString("startTime"))
|| StrUtil.isBlank(item.getString("endTime"))
|| item.getString("startTime").compareTo(item.getString("endTime")) >= 0) {
return Result.error("Invalid full-day booking time range");
}
if (!isPositiveNumber(item.get("timeUnit"))) {
return Result.error("Invalid full-day booking time unit");
}
fullDayOpenHour = normalizeOpenHourTimeUnit(fullDayOpenHour);
info.setFullDayOpenHour(fullDayOpenHour);
info.setOpenHours(List.of(fullDayOpenHour));
}
if (info.getFilterHolidays() == null) {
info.setFilterHolidays(false);
}
infoService.insertOrUpdate(info);
SiteCugInfo savedInfo = infoService.fetch(info.getId());
return Result.success(savedInfo == null ? info : savedInfo);
}
@At
@ApiOperation("delete")
@SaCheckPermission("siteCug.manage")
@SLog(tag = "siteCug.manage", msg = "delete site")
public Object delete(String id) {
infoService.delete(id);
return Result.success();
}
@At
@ApiOperation("query sites")
@SaCheckLogin
public Result querySites() {
List<SiteCugInfo> list = infoService.query(Cnd.where(SiteCugInfo::getState, "=", true).desc(SiteCugInfo::getSortNum));
return Result.success(list);
}
@At
@ApiOperation("query site info")
@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);
}
private boolean isPositiveNumber(Object value) {
if (value == null) {
return false;
}
try {
return Double.parseDouble(String.valueOf(value)) > 0;
} catch (Exception e) {
return false;
}
}
private NutMap normalizeOpenHourTimeUnit(NutMap item) {
if (item == null) {
return null;
}
item.put("timeUnit", normalizeTimeUnitMinutes(item.get("timeUnit")));
return item;
}
private int normalizeTimeUnitMinutes(Object value) {
if (value == null) {
return 60;
}
try {
double parsed = Double.parseDouble(String.valueOf(value));
if (parsed <= 0) {
return 60;
}
return (int) Math.round(parsed);
} catch (Exception e) {
return 60;
}
}
private boolean hasOverlap(List<NutMap> openHours) {
if (openHours == null || openHours.size() < 2) {
return false;
}
List<NutMap> validHours = openHours.stream()
.filter(item -> item != null
&& StrUtil.isNotBlank(item.getString("startTime"))
&& StrUtil.isNotBlank(item.getString("endTime"))
&& item.getString("startTime").compareTo(item.getString("endTime")) < 0)
.sorted((a, b) -> a.getString("startTime").compareTo(b.getString("startTime")))
.collect(Collectors.toList());
for (int i = 1; i < validHours.size(); i++) {
NutMap prev = validHours.get(i - 1);
NutMap current = validHours.get(i);
if (current.getString("startTime").compareTo(prev.getString("endTime")) < 0) {
return true;
}
}
return false;
}
}
@@ -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 info.reserveType = 'club' THEN info.clubName ELSE info.applyUnionName END AS reserveTargetName,
CASE
WHEN ins.state in (10, 20) AND (t.displayName LIKE '%校工会允许%' OR t.taskName = 'c8c03407-cf26-4e0b-82f1-b2afc9c79996')
THEN 1
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() + "%");
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,245 @@
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,
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";
}
}
@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,114 @@
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;
private String yearlyReserveEndDate;
}
@@ -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,123 @@
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("预约时间段类型(1分段预约,2全天候预约)")
@ColDefine(type = ColType.INT)
@Default("1")
private Integer reserveTimeType;
@Column
@Comment("场次信息")
@ColDefine(type = ColType.MYSQL_JSON)
private List<NutMap> openHours;
@Column
@Comment("分段预约场次信息")
@ColDefine(type = ColType.MYSQL_JSON)
private List<NutMap> segmentedOpenHours;
@Column
@Comment("全天候预约时间段")
@ColDefine(type = ColType.MYSQL_JSON)
private NutMap fullDayOpenHour;
@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;
@Column
@Comment("场地照片")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String sitePhoto;
}
@@ -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,323 @@
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.backOption LIKE 'YEARLY_BATCH:%' THEN 1 ELSE 0 END AS yearlyBatch,
CASE WHEN info.reserveType = 'club' THEN '协会预约' ELSE '分工会预约' END AS reserveTypeName,
CASE WHEN info.reserveType = 'club' THEN info.clubName ELSE info.applyUnionName END AS reserveTargetName,
DATE_FORMAT(info.createdAt, '%Y-%m-%d %H:%i:%s') AS applyTime
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() + "%");
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.setTitle("预约查询");
exportParams.setSheetName("预约查询");
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);
}
}
@@ -0,0 +1,625 @@
package com.budwk.app.zhgh.dayofficework.tour.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
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.CommonDownloadUtil;
import com.budwk.app.sys.models.Sys_union;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService;
import com.budwk.app.zhgh.dayofficework.tour.service.TourMatterService;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
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.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 javax.servlet.http.HttpServletResponse;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@IocBean
@Ok("json:full")
@At("/platform/tour/group")
public class TourGroupController {
@Inject
private TourMatterService tourMatterService;
@Inject
private TourLedgerService tourLedgerService;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/Tour/group/index.html")
@SaCheckPermission("tour.group")
public void index() {
}
@At
@SaCheckPermission("tour.group")
public Result pageData(PageForm pageForm, Integer year, String lineName, String lineType, String unionId) {
if (StrUtil.isBlank(pageForm.getPageOrderName())) {
pageForm.defaultSortAsc("lineName");
}
Cnd cnd = buildQueryCnd(year, lineName, lineType, unionId);
Sql countSql = Sqls.create("""
SELECT COUNT(1)
FROM tour_matter m
INNER JOIN tour_line l ON l.id = m.lineId
LEFT JOIN sys_union u ON u.id = m.unionId
$condition
""");
countSql.setCondition(cnd);
countSql.setCallback(Sqls.callback.integer());
tourMatterService.dao().execute(countSql);
int count = countSql.getInt();
Sql listSql = Sqls.create("""
SELECT
m.id AS matterId,
m.`year`,
m.matterName,
m.unionId,
COALESCE(u.name, '校工会') AS unionName,
m.lineId,
l.lineName,
l.lineType,
l.directFamilyUnitLine,
m.travelStartTime,
m.travelEndTime,
CASE
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
ELSE ''
END AS travelPeriod,
m.minGroupPeople,
m.maxGroupPeople,
IFNULL(sc.signupCount, 0) AS signupCount,
CASE
WHEN m.maxGroupPeople IS NOT NULL AND m.maxGroupPeople > 0 AND IFNULL(sc.signupCount, 0) > m.maxGroupPeople THEN 'over'
WHEN m.minGroupPeople IS NOT NULL AND m.minGroupPeople > 0 AND IFNULL(sc.signupCount, 0) >= m.minGroupPeople THEN 'formed'
ELSE 'unformed'
END AS groupStatus,
CASE
WHEN m.maxGroupPeople IS NOT NULL AND m.maxGroupPeople > 0 AND IFNULL(sc.signupCount, 0) > m.maxGroupPeople THEN '超员'
WHEN m.minGroupPeople IS NOT NULL AND m.minGroupPeople > 0 AND IFNULL(sc.signupCount, 0) >= m.minGroupPeople THEN '已成团'
ELSE '未成团'
END AS groupStatusName
FROM tour_matter m
INNER JOIN tour_line l ON l.id = m.lineId
LEFT JOIN sys_union u ON u.id = m.unionId
LEFT JOIN (
SELECT t.matterId, SUM(1 + IFNULL(f.familyCount, 0)) AS signupCount
FROM tour_ledger t
LEFT JOIN (
SELECT ledgerId, COUNT(1) AS familyCount
FROM tour_ledger_family
WHERE delFlag = 0
GROUP BY ledgerId
) f ON f.ledgerId = t.id
WHERE t.delFlag = 0
AND t.matterId IS NOT NULL
AND t.matterId <> ''
GROUP BY t.matterId
) sc ON sc.matterId = m.id
$condition
ORDER BY $orderColumn $orderBy, m.`year` DESC, m.travelStartTime ASC, l.lineName ASC, m.createdAt DESC
""");
listSql.setCondition(cnd);
listSql.setVar("orderColumn", getOrderColumn(pageForm.getPageOrderName()));
listSql.setVar("orderBy", "descending".equals(pageForm.getPageOrderBy()) ? "desc" : "asc");
listSql.setPager(tourMatterService.dao().createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
listSql.setCallback(Sqls.callback.maps());
tourMatterService.dao().execute(listSql);
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, listSql.getList(NutMap.class));
return Result.success(pagination);
}
@At
@SaCheckPermission("tour.group")
public Result unionOptions() {
Cnd cnd = Cnd.NEW();
cnd.asc("unionCode");
cnd.asc("name");
return Result.success(tourMatterService.dao().query(Sys_union.class, cnd));
}
@At
@SaCheckPermission("tour.group")
public Result lineTypeOptions(Integer year) {
Cnd cnd = buildQueryCnd(year, null, null, null);
Sql sql = Sqls.create("""
SELECT DISTINCT l.lineType
FROM tour_matter m
INNER JOIN tour_line l ON l.id = m.lineId
LEFT JOIN sys_union u ON u.id = m.unionId
$condition
ORDER BY l.lineType ASC
""");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.maps());
tourMatterService.dao().execute(sql);
return Result.success(sql.getList(NutMap.class));
}
@At
@SaCheckPermission("tour.group")
public Result signupPageData(PageForm pageForm, String matterId, String keyword, String unionId) {
if (StrUtil.isBlank(matterId)) {
return Result.error("参数错误");
}
if (StrUtil.isBlank(pageForm.getPageOrderName())) {
pageForm.defaultSortDesc("signupTime");
}
Cnd cnd = buildSignupQueryCnd(matterId, keyword, unionId);
Sql countSql = Sqls.create("""
SELECT COUNT(1)
FROM tour_ledger t
INNER JOIN tour_matter m ON m.id = t.matterId
INNER JOIN tour_line l ON l.id = m.lineId
$condition
""");
countSql.setCondition(cnd);
countSql.setCallback(Sqls.callback.integer());
tourLedgerService.dao().execute(countSql);
int count = countSql.getInt();
Sql listSql = Sqls.create("""
SELECT
t.id,
t.jobNo,
t.userName,
'' AS mobile,
t.idCard,
t.unionId,
t.unionName,
t.signupTime
FROM tour_ledger t
INNER JOIN tour_matter m ON m.id = t.matterId
INNER JOIN tour_line l ON l.id = m.lineId
$condition
ORDER BY $orderColumn $orderBy, t.signupTime DESC, t.createdAt DESC
""");
listSql.setCondition(cnd);
listSql.setVar("orderColumn", getSignupOrderColumn(pageForm.getPageOrderName()));
listSql.setVar("orderBy", "ascending".equals(pageForm.getPageOrderBy()) ? "asc" : "desc");
listSql.setPager(tourLedgerService.dao().createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
listSql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(listSql);
List<NutMap> list = listSql.getList(NutMap.class);
fillSignupMobile(list);
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list);
return Result.success(pagination);
}
@At
@Ok("void")
@SaCheckPermission("tour.group")
public void exportParticipants(String matterId, HttpServletResponse response) {
if (StrUtil.isBlank(matterId)) {
return;
}
NutMap matter = fetchExportMatter(matterId);
if (matter == null || matter.isEmpty()) {
return;
}
List<NutMap> list = queryExportParticipants(matterId);
Workbook workbook = buildParticipantsWorkbook(matter, list);
String lineName = StrUtil.blankToDefault(matter.getString("lineName", ""), "线路");
CommonDownloadUtil.download(lineName + "参加人员名单.xls", workbook, response);
}
private Cnd buildSignupQueryCnd(String matterId, String keyword, String unionId) {
Cnd cnd = Cnd.NEW();
cnd.and("t.delFlag", "=", false);
cnd.and("t.matterId", "=", matterId);
cnd.and("m.delFlag", "=", false);
cnd.and("m.enabled", "=", true);
cnd.and("m.lineId", "IS NOT", null);
cnd.and("m.lineId", "<>", "");
cnd.and("l.delFlag", "=", false);
cnd.and("l.enabled", "=", true);
if (StrUtil.isNotBlank(keyword)) {
SqlExpressionGroup keywordGroup = new SqlExpressionGroup();
keywordGroup.orLike("t.userName", keyword.trim());
keywordGroup.orLike("t.jobNo", keyword.trim());
cnd.and(keywordGroup);
}
cnd.andEX("t.unionId", "=", unionId);
applyMatterDataScope(cnd);
return cnd;
}
private NutMap fetchExportMatter(String matterId) {
Cnd cnd = Cnd.NEW();
cnd.and("m.delFlag", "=", false);
cnd.and("m.enabled", "=", true);
cnd.and("m.id", "=", matterId);
cnd.and("m.lineId", "IS NOT", null);
cnd.and("m.lineId", "<>", "");
cnd.and("l.delFlag", "=", false);
cnd.and("l.enabled", "=", true);
applyMatterDataScope(cnd);
Sql sql = Sqls.create("""
SELECT
m.id AS matterId,
m.`year`,
m.unionId,
COALESCE(u.name, '校工会') AS unionName,
l.lineName,
l.lineType,
m.travelStartTime,
m.travelEndTime,
CASE
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
ELSE ''
END AS travelPeriod
FROM tour_matter m
INNER JOIN tour_line l ON l.id = m.lineId
LEFT JOIN sys_union u ON u.id = m.unionId
$condition
""");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.map());
tourMatterService.dao().execute(sql);
return sql.getObject(NutMap.class);
}
private List<NutMap> queryExportParticipants(String matterId) {
Cnd cnd = buildSignupQueryCnd(matterId, null, null);
Sql sql = Sqls.create("""
SELECT
t.id AS ledgerId,
t.jobNo,
t.userName,
t.idCard,
vu.mobile,
t.unionName,
l.lineName,
CASE
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
ELSE ''
END AS travelPeriod,
t.signupTime
FROM tour_ledger t
INNER JOIN tour_matter m ON m.id = t.matterId
INNER JOIN tour_line l ON l.id = m.lineId
LEFT JOIN vw_user vu ON vu.loginname = t.jobNo
$condition
ORDER BY t.signupTime DESC, t.createdAt DESC
""");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(sql);
List<NutMap> list = sql.getList(NutMap.class);
fillExportFamilies(list);
return list;
}
private void fillExportFamilies(List<NutMap> list) {
if (list == null || list.isEmpty()) {
return;
}
List<String> ledgerIds = new ArrayList<>();
for (NutMap item : list) {
String ledgerId = item.getString("ledgerId", "");
if (StrUtil.isNotBlank(ledgerId)) {
ledgerIds.add(ledgerId);
}
}
if (ledgerIds.isEmpty()) {
return;
}
Sql sql = Sqls.create("""
SELECT
ledgerId,
familyName,
relationship,
idCard
FROM tour_ledger_family
WHERE delFlag = 0
AND ledgerId IN (@ledgerIds)
ORDER BY createdAt ASC
""");
sql.setParam("ledgerIds", ledgerIds.toArray(new String[0]));
sql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(sql);
Map<String, List<NutMap>> familyMap = new HashMap<>();
for (NutMap family : sql.getList(NutMap.class)) {
familyMap.computeIfAbsent(family.getString("ledgerId", ""), key -> new ArrayList<>()).add(family);
}
for (NutMap item : list) {
item.put("families", familyMap.getOrDefault(item.getString("ledgerId", ""), List.of()));
}
}
private Workbook buildParticipantsWorkbook(NutMap matter, List<NutMap> list) {
Workbook workbook = new HSSFWorkbook();
Sheet sheet = workbook.createSheet("参加人员名单");
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 10));
sheet.addMergedRegion(new CellRangeAddress(1, 1, 0, 10));
double[] widths = {6, 14, 12, 22, 16, 24, 34, 26, 12, 12, 16};
for (int i = 0; i < widths.length; i++) {
sheet.setColumnWidth(i, (int) (widths[i] * 256));
}
CellStyle titleStyle = createStyle(workbook, "黑体", (short) 16, true, HorizontalAlignment.CENTER, false, false);
CellStyle unionStyle = createStyle(workbook, "宋体", (short) 12, true, HorizontalAlignment.LEFT, false, false);
CellStyle headerStyle = createStyle(workbook, "宋体", (short) 11, true, HorizontalAlignment.CENTER, true, true);
CellStyle bodyStyle = createStyle(workbook, "宋体", (short) 11, false, HorizontalAlignment.CENTER, true, true);
Row titleRow = sheet.createRow(0);
titleRow.setHeightInPoints(30);
setCell(titleRow, 0, StrUtil.blankToDefault(matter.getString("lineName", ""), ""), titleStyle);
fillMergedCells(titleRow, 1, 10, titleStyle);
Row unionRow = sheet.createRow(1);
unionRow.setHeightInPoints(26);
setCell(unionRow, 0, "分工会:" + StrUtil.blankToDefault(matter.getString("unionName", ""), ""), unionStyle);
fillMergedCells(unionRow, 1, 10, unionStyle);
String[] headers = {"序号", "工号", "姓名", "身份证号码", "电话号码", "所属分工会", "所选线路名称", "疗休养时间", "亲属关系", "姓名", "备注"};
Row headerRow = sheet.createRow(2);
headerRow.setHeightInPoints(35);
for (int i = 0; i < headers.length; i++) {
setCell(headerRow, i, headers[i], headerStyle);
}
int rowIndex = 3;
int seq = 1;
for (NutMap item : list == null ? List.<NutMap>of() : list) {
Row staffRow = sheet.createRow(rowIndex++);
staffRow.setHeightInPoints(24);
setParticipantRow(staffRow, seq++, item.getString("jobNo", ""), item.getString("userName", ""),
item.getString("idCard", ""), item.getString("mobile", ""), item.getString("unionName", ""),
item.getString("lineName", matter.getString("lineName", "")), item.getString("travelPeriod", matter.getString("travelPeriod", "")),
"", item.getString("userName", ""), bodyStyle);
Object familiesObj = item.get("families");
if (familiesObj instanceof List<?> families) {
for (Object obj : families) {
if (!(obj instanceof NutMap family)) {
continue;
}
Row familyRow = sheet.createRow(rowIndex++);
familyRow.setHeightInPoints(24);
setParticipantRow(familyRow, seq++, "", family.getString("familyName", ""),
family.getString("idCard", ""), "", "",
item.getString("lineName", matter.getString("lineName", "")),
item.getString("travelPeriod", matter.getString("travelPeriod", "")),
family.getString("relationship", ""), item.getString("userName", ""), bodyStyle);
}
}
}
int minRows = Math.max(rowIndex, 23);
while (rowIndex < minRows) {
Row row = sheet.createRow(rowIndex++);
row.setHeightInPoints(24);
for (int i = 0; i < headers.length; i++) {
setCell(row, i, "", bodyStyle);
}
}
return workbook;
}
private void setParticipantRow(Row row, int seq, String jobNo, String userName, String idCard, String mobile,
String unionName, String lineName, String travelPeriod, String relationship,
String participantName, CellStyle style) {
setCell(row, 0, String.valueOf(seq), style);
setCell(row, 1, jobNo, style);
setCell(row, 2, userName, style);
setCell(row, 3, idCard, style);
setCell(row, 4, mobile, style);
setCell(row, 5, unionName, style);
setCell(row, 6, lineName, style);
setCell(row, 7, travelPeriod, style);
setCell(row, 8, relationship, style);
setCell(row, 9, participantName, style);
setCell(row, 10, "", style);
}
private CellStyle createStyle(Workbook workbook, String fontName, short fontSize, boolean bold, HorizontalAlignment alignment, boolean wrap, boolean border) {
Font font = workbook.createFont();
font.setFontName(fontName);
font.setFontHeightInPoints(fontSize);
font.setBold(bold);
CellStyle style = workbook.createCellStyle();
style.setFont(font);
style.setAlignment(alignment);
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setWrapText(wrap);
if (border) {
style.setBorderLeft(BorderStyle.THIN);
style.setBorderTop(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
}
return style;
}
private void setCell(Row row, int col, String value, CellStyle style) {
Cell cell = row.createCell(col);
cell.setCellValue(StrUtil.blankToDefault(value, ""));
cell.setCellStyle(style);
}
private void fillMergedCells(Row row, int startCol, int endCol, CellStyle style) {
for (int i = startCol; i <= endCol; i++) {
setCell(row, i, "", style);
}
}
private void fillSignupMobile(List<NutMap> list) {
if (list == null || list.isEmpty()) {
return;
}
List<String> jobNos = new ArrayList<>();
for (NutMap item : list) {
String jobNo = item.getString("jobNo", "");
if (StrUtil.isNotBlank(jobNo) && !jobNos.contains(jobNo)) {
jobNos.add(jobNo);
}
}
if (jobNos.isEmpty()) {
return;
}
Sql sql = Sqls.create("SELECT loginname, mobile FROM vw_user WHERE loginname IN (@jobNos)");
sql.setParam("jobNos", jobNos.toArray(new String[0]));
sql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(sql);
Map<String, String> mobileMap = new HashMap<>();
for (NutMap user : sql.getList(NutMap.class)) {
mobileMap.put(user.getString("loginname", ""), user.getString("mobile", ""));
}
for (NutMap item : list) {
item.put("mobile", mobileMap.getOrDefault(item.getString("jobNo", ""), ""));
}
}
private Cnd buildQueryCnd(Integer year, String lineName, String lineType, String unionId) {
Cnd cnd = Cnd.NEW();
cnd.and("m.delFlag", "=", false);
cnd.and("m.enabled", "=", true);
cnd.and("m.lineId", "IS NOT", null);
cnd.and("m.lineId", "<>", "");
cnd.and("l.delFlag", "=", false);
cnd.and("l.enabled", "=", true);
cnd.andEX("m.`year`", "=", year == null ? LocalDate.now().getYear() : year);
cnd.and(Cnd.likeEX("l.lineName", lineName));
cnd.andEX("l.lineType", "=", lineType);
cnd.andEX("m.unionId", "=", unionId);
applyMatterDataScope(cnd);
return cnd;
}
/**
* 线路成团按事项归属控制数据范围:校工会查看全部;分工会查看本工会事项;其它角色仅查看自己创建的事项。
*/
private void applyMatterDataScope(Cnd cnd) {
if (hasSchoolUnionScope()) {
return;
}
if (hasBranchUnionScope()) {
cnd.and("m.unionId", "=", SecurityUtil.getUnionId());
return;
}
SqlExpressionGroup creatorGroup = new SqlExpressionGroup();
creatorGroup.or("m.creatorUserId", "=", SecurityUtil.getUserId());
creatorGroup.or("m.createdBy", "=", SecurityUtil.getUserId());
cnd.and(creatorGroup);
}
private boolean hasSchoolUnionScope() {
return AuthUtil.hasRole(RoleConstant.SYSADMIN.name())
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_ADMIN.name())
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_CHAIRMAN.name())
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_VICE_CHAIRMAN.name())
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_WELFARE_ADMIN.name())
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_MEMBER_ADMIN.name())
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_TC_ADMIN.name())
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_WC_ADMIN.name())
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_DC_ADMIN.name())
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_PROPOSAL_ADMIN.name())
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_CLUB_ADMIN.name())
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_ACTIVITY_ADMIN.name());
}
private boolean hasBranchUnionScope() {
return AuthUtil.hasRole(RoleConstant.BRANCH_UNION_ADMIN.name())
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name())
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_VICE_CHAIRMAN.name())
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_OPERATOR.name())
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_GROUP_LEADER.name())
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_ARTICLE_WRITER.name())
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_WY.name())
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_ZUZHI_WY.name())
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_XUANCHUAN_WY.name())
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_NVGONG_WY.name())
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_QINGNIAN_WY.name())
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_WENTI_WY.name())
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_SHENGGHUO_WY.name())
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_TIAOJIE_WY.name())
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_WENTI_SPORTS.name());
}
private String getOrderColumn(String orderName) {
if ("lineName".equals(orderName)) {
return "l.lineName";
}
if ("travelPeriod".equals(orderName)) {
return "m.travelStartTime";
}
if ("matterName".equals(orderName)) {
return "m.matterName";
}
if ("unionName".equals(orderName)) {
return "u.name";
}
if ("lineType".equals(orderName)) {
return "l.lineType";
}
if ("signupCount".equals(orderName)) {
return "signupCount";
}
if ("minGroupPeople".equals(orderName)) {
return "m.minGroupPeople";
}
if ("maxGroupPeople".equals(orderName)) {
return "m.maxGroupPeople";
}
if ("groupStatusName".equals(orderName)) {
return "groupStatus";
}
return "l.lineName";
}
private String getSignupOrderColumn(String orderName) {
if ("jobNo".equals(orderName)) {
return "t.jobNo";
}
if ("userName".equals(orderName)) {
return "t.userName";
}
if ("idCard".equals(orderName)) {
return "t.idCard";
}
if ("unionName".equals(orderName)) {
return "t.unionName";
}
return "t.signupTime";
}
}
@@ -0,0 +1,959 @@
package com.budwk.app.zhgh.dayofficework.tour.controller;
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.model.ExcelImportRes;
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.CommonDownloadUtil;
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
import com.budwk.app.sys.models.Sys_union;
import com.budwk.app.sys.views.View_user;
import com.budwk.app.zhgh.dayofficework.tour.mode.TourLedgerImportExcelMode;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerDirectRelative;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerFamily;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLine;
import com.budwk.app.zhgh.dayofficework.tour.models.TourTravelAgency;
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerDirectRelativeService;
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerFamilyService;
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.util.CellRangeAddress;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
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.json.Json;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.AdaptBy;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import org.nutz.mvc.upload.TempFile;
import org.nutz.mvc.upload.UploadAdaptor;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@IocBean
@Ok("json:full")
@At("/platform/tour/ledger")
public class TourLedgerController {
@Inject
private TourLedgerService tourLedgerService;
@Inject
private TourLedgerFamilyService tourLedgerFamilyService;
@Inject
private TourLedgerDirectRelativeService tourLedgerDirectRelativeService;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/Tour/ledger/index.html")
@SaCheckPermission("tour.ledger")
public void index() {
}
@At
@SaCheckPermission("tour.ledger")
public Result pageData(PageForm pageForm, Integer startYear, Integer endYear, String keyword, String unionId, String lineId, String travelPeriod, String lineType, Boolean directFamilyOnly, Boolean overCostOnly) {
Cnd cnd = buildQueryCnd(startYear, endYear, keyword, unionId, lineId, travelPeriod, lineType, directFamilyOnly, overCostOnly);
Sql countSql = Sqls.create("""
SELECT COUNT(DISTINCT t.id)
FROM tour_ledger t
LEFT JOIN wf_process_instance ins ON ins.businessNo = t.id
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
LEFT JOIN tour_ledger_direct_relative dr ON dr.ledgerId = t.id AND dr.delFlag = 0
$condition
""");
countSql.setCondition(cnd);
countSql.setCallback(Sqls.callback.integer());
tourLedgerService.dao().execute(countSql);
int count = countSql.getInt();
Sql listSql = Sqls.create("""
SELECT
t.*,
ins.id AS instanceId,
ins.state AS instanceState,
ins.processDefineId AS instanceProcessDefineId,
IF(IFNULL(l.directFamilyUnitLine, 0) = 1 OR dr.id IS NOT NULL, 1, 0) AS directFamilyUnitLine,
dr.id AS directRelativeId,
COALESCE(NULLIF(l.lineName, ''), t.lineName) AS currentLineName,
IFNULL(f.familyCount, 0) AS familyCount,
CASE
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
ELSE tp.travelPeriod
END AS travelPeriod
FROM tour_ledger t
LEFT JOIN wf_process_instance ins ON ins.businessNo = t.id
LEFT JOIN (
SELECT ledgerId, COUNT(1) AS familyCount
FROM tour_ledger_family
WHERE delFlag = 0
GROUP BY ledgerId
) f ON f.ledgerId = t.id
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
LEFT JOIN tour_ledger_direct_relative dr ON dr.ledgerId = t.id AND dr.delFlag = 0
LEFT JOIN (
SELECT
`year`,
lineId,
GROUP_CONCAT(
DISTINCT CASE
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
ELSE NULL
END
ORDER BY travelStartTime ASC
SEPARATOR ';'
) AS travelPeriod
FROM tour_matter
WHERE delFlag = 0
AND lineId IS NOT NULL
AND lineId <> ''
GROUP BY `year`, lineId
) tp ON (t.matterId IS NULL OR t.matterId = '') AND tp.`year` = t.`year` AND tp.lineId = t.lineId
$condition
GROUP BY t.id
ORDER BY $orderColumn $orderBy, t.`year` DESC, t.signupTime DESC, t.createdAt DESC
""");
listSql.setCondition(cnd);
listSql.setVar("orderColumn", getOrderColumn(pageForm.getPageOrderName()));
listSql.setVar("orderBy", "descending".equals(pageForm.getPageOrderBy()) ? "desc" : "asc");
listSql.setPager(tourLedgerService.dao().createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
listSql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(listSql);
var list = listSql.getList(NutMap.class);
list.forEach(item -> item.put("lineName", item.getString("currentLineName")));
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list);
return Result.success(pagination);
}
@At
@SaCheckPermission("tour.ledger")
public Result detail(String id) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
TourLedger ledger = tourLedgerService.fetch(id);
if (ledger == null) {
return Result.error("台账记录不存在");
}
ledger.setLineName(getCurrentLineName(ledger));
Cnd familyCnd = Cnd.NEW();
familyCnd.and(TourLedgerFamily::getLedgerId, "=", id);
familyCnd.and(TourLedgerFamily::getDelFlag, "=", false);
familyCnd.asc(TourLedgerFamily::getCreatedAt);
TourLedgerDirectRelative directRelative = tourLedgerDirectRelativeService.fetch(
Cnd.where(TourLedgerDirectRelative::getDelFlag, "=", false)
.and(TourLedgerDirectRelative::getLedgerId, "=", ledger.getId()));
return Result.success(NutMap.NEW()
.addv("ledger", ledger)
.addv("travelPeriod", getTravelPeriod(ledger))
.addv("families", tourLedgerFamilyService.query(familyCnd))
.addv("directRelative", directRelative)
.addv("fillBedInfo", isFillBedInfo(ledger))
.addv("directFamilyUnitLine", isDirectFamilyUnitLine(ledger)));
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tour.ledger")
@SLog(type = "tour", tag = "疗休养台账", msg = "删除疗休养台账")
public Result doDelete(String id) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
TourLedger ledger = tourLedgerService.fetch(id);
if (ledger == null) {
return Result.error("台账记录不存在");
}
tourLedgerFamilyService.clear(Cnd.where(TourLedgerFamily::getLedgerId, "=", id));
tourLedgerService.delete(id);
return Result.success();
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tour.ledger")
@SLog(type = "tour", tag = "疗休养台账", msg = "设置参加人员")
public Result setParticipants(@Param("ids") String ids) {
List<String> idList = StrUtil.isBlank(ids) ? Collections.emptyList() : Json.fromJsonAsList(String.class, ids);
if (idList.isEmpty()) {
return Result.error("请选择要设置的参加人员");
}
tourLedgerService.dao().update(TourLedger.class, Chain.make("joined", true), Cnd.where("id", "in", idList));
return Result.success();
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tour.ledger")
@SLog(type = "tour", tag = "疗休养台账", msg = "参加人员导入")
@AdaptBy(type = UploadAdaptor.class, args = {"ioc:fileUpload"})
public Result importParticipants(@Param("file") TempFile file) {
if (file == null) {
return Result.error("请选择导入文件");
}
List<TourLedgerImportExcelMode> importRows = ExcelImportUtil.importExcel(file.getFile(), TourLedgerImportExcelMode.class, new ImportParams());
ExcelImportRes<TourLedgerImportExcelMode> excelImportRes = new ExcelImportRes<>();
excelImportRes.setTotalRecords(importRows.size());
for (int i = 0; i < importRows.size(); i++) {
TourLedgerImportExcelMode row = importRows.get(i);
if (!validateImportRow(row, i)) {
continue;
}
Boolean joined = parseJoined(row.getJoined());
if (joined == null) {
row.setErrInfo("是否参加只能填写1、0、是、否、true、false、已参加、未参加", i);
continue;
}
TourLine line = fetchImportLine(row);
if (line == null) {
row.setErrInfo("未找到唯一匹配的线路", i);
continue;
}
List<TourLedger> ledgers = queryImportLedgerCandidates(row);
if (ledgers.isEmpty()) {
Result createResult = insertImportLedger(row, line, joined);
if (createResult.isSuccess()) {
continue;
}
row.setErrInfo(createResult.getMsg(), i);
continue;
}
TourLedger ledger = resolveImportLedger(row, ledgers, line);
if (ledger == null) {
row.setErrInfo("匹配到多条台账记录,报名时间或线路未能唯一匹配,请核对年度、工号、报名时间、线路名称", i);
continue;
}
Chain chain = Chain.make("joined", joined)
.add("lineId", line.getId())
.add("lineName", line.getLineName())
.add("lineType", line.getLineType());
String signupTime = normalizeSignupTime(row.getSignupTime());
if (StrUtil.isNotBlank(signupTime)) {
chain.add("signupTime", signupTime);
}
tourLedgerService.dao().update(TourLedger.class, chain, Cnd.where("id", "=", ledger.getId()));
}
excelImportRes.setErrorDetails(importRows.stream().filter(s -> StrUtil.isNotBlank(s.getErrMsg())).collect(Collectors.toList()));
excelImportRes.setFailedCount(excelImportRes.getErrorDetails().size());
excelImportRes.setSuccessCount(Math.max(importRows.size() - excelImportRes.getFailedCount(), 0));
return Result.success(excelImportRes);
}
@At
@SaCheckPermission("tour.ledger")
@Ok("void")
public void downloadTemplate(HttpServletResponse response) {
List<ExcelExportEntity> entities = new ArrayList<>();
entities.add(new ExcelExportEntity("年度", "year", 12));
entities.add(new ExcelExportEntity("工号", "jobNo", 18));
entities.add(new ExcelExportEntity("姓名", "userName", 18));
entities.add(new ExcelExportEntity("报名时间", "signupTime", 24));
entities.add(new ExcelExportEntity("线路名称", "lineName", 30));
entities.add(new ExcelExportEntity("线路类型", "lineType", 18));
entities.add(new ExcelExportEntity("是否参加", "joined", 14));
ExportParams exportParams = new ExportParams();
exportParams.setSheetName("tour_ledger");
exportParams.setType(ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, Collections.emptyList());
CommonDownloadUtil.download("参加人员导入模板.xlsx", workbook, response);
}
@At
@SaCheckPermission("tour.ledger")
@Ok("void")
public void exportUnionSignupZip(Integer startYear, Integer endYear, String keyword, String unionId, String lineId,
String travelPeriod, String lineType, Boolean directFamilyOnly, Boolean overCostOnly,
HttpServletResponse response) throws Exception {
List<NutMap> rows = queryUnionSignupRows(startYear, endYear, keyword, unionId, lineId, travelPeriod, lineType,
directFamilyOnly, overCostOnly);
Map<String, List<NutMap>> unionRows = groupRowsByUnion(rows);
String zipName = URLEncoder.encode("疗休养分工会报名汇总表.zip", StandardCharsets.UTF_8.toString());
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment;filename=" + zipName + ";filename*=UTF-8''" + zipName);
try (ZipOutputStream zipOutputStream = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream()))) {
if (unionRows.isEmpty()) {
zipOutputStream.putNextEntry(new ZipEntry("无数据报名汇总表.xls"));
try (Workbook workbook = buildUnionSignupWorkbook("无数据", Collections.emptyList())) {
workbook.write(zipOutputStream);
}
zipOutputStream.closeEntry();
} else {
for (Map.Entry<String, List<NutMap>> entry : unionRows.entrySet()) {
String unionName = entry.getValue().isEmpty() ? entry.getKey() : entry.getValue().get(0).getString("unionName", entry.getKey());
String fileName = safeFileName(unionName + "报名汇总表.xls");
zipOutputStream.putNextEntry(new ZipEntry(fileName));
try (Workbook workbook = buildUnionSignupWorkbook(unionName, entry.getValue())) {
workbook.write(zipOutputStream);
}
zipOutputStream.closeEntry();
}
}
zipOutputStream.flush();
}
}
private List<NutMap> queryUnionSignupRows(Integer startYear, Integer endYear, String keyword, String unionId, String lineId,
String travelPeriod, String lineType, Boolean directFamilyOnly, Boolean overCostOnly) {
Cnd cnd = buildQueryCnd(startYear, endYear, keyword, unionId, lineId, travelPeriod, lineType, directFamilyOnly, overCostOnly);
Sql sql = Sqls.create("""
SELECT
t.id,
t.jobNo,
t.userName,
t.idCard,
t.unionId,
COALESCE(NULLIF(t.unionName, ''), su.name, vu.unionName, '') AS unionName,
COALESCE(NULLIF(su.unionCode, ''), vu.unionCode, '') AS unionCode,
COALESCE(NULLIF(vu.mobile, ''), '') AS mobile,
COALESCE(NULLIF(l.lineName, ''), t.lineName, '') AS lineName,
CASE
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
ELSE tp.travelPeriod
END AS travelPeriod
FROM tour_ledger t
LEFT JOIN wf_process_instance ins ON ins.businessNo = t.id
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
LEFT JOIN tour_ledger_direct_relative dr ON dr.ledgerId = t.id AND dr.delFlag = 0
LEFT JOIN vw_user vu ON vu.loginname = t.jobNo
LEFT JOIN sys_union su ON su.id = t.unionId
LEFT JOIN (
SELECT
`year`,
lineId,
GROUP_CONCAT(
DISTINCT CASE
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
ELSE NULL
END
ORDER BY travelStartTime ASC
SEPARATOR ';'
) AS travelPeriod
FROM tour_matter
WHERE delFlag = 0
AND lineId IS NOT NULL
AND lineId <> ''
GROUP BY `year`, lineId
) tp ON (t.matterId IS NULL OR t.matterId = '') AND tp.`year` = t.`year` AND tp.lineId = t.lineId
$condition
GROUP BY t.id
ORDER BY unionCode ASC, unionName ASC, t.signupTime DESC, t.createdAt DESC
""");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(sql);
return sql.getList(NutMap.class);
}
private Map<String, List<NutMap>> groupRowsByUnion(List<NutMap> rows) {
Map<String, List<NutMap>> unionRows = new LinkedHashMap<>();
for (NutMap row : rows) {
String unionName = StrUtil.blankToDefault(row.getString("unionName"), "未分工会");
String unionCode = StrUtil.blankToDefault(row.getString("unionCode"), "");
String key = StrUtil.isBlank(unionCode) ? unionName : unionCode + "_" + unionName;
unionRows.computeIfAbsent(key, item -> new ArrayList<>()).add(row);
}
return unionRows;
}
private Workbook buildUnionSignupWorkbook(String unionName, List<NutMap> rows) {
HSSFWorkbook workbook = new HSSFWorkbook();
Sheet sheet = workbook.createSheet("报名汇总表");
sheet.setColumnWidth(0, 8 * 256);
sheet.setColumnWidth(1, 14 * 256);
sheet.setColumnWidth(2, 14 * 256);
sheet.setColumnWidth(3, 24 * 256);
sheet.setColumnWidth(4, 16 * 256);
sheet.setColumnWidth(5, 24 * 256);
sheet.setColumnWidth(6, 36 * 256);
sheet.setColumnWidth(7, 26 * 256);
sheet.setColumnWidth(8, 18 * 256);
CellStyle titleStyle = createUnionSignupTitleStyle(workbook);
CellStyle headerStyle = createUnionSignupHeaderStyle(workbook);
CellStyle bodyStyle = createUnionSignupBodyStyle(workbook);
Row titleRow = sheet.createRow(0);
titleRow.setHeightInPoints(28);
Cell titleCell = titleRow.createCell(0);
titleCell.setCellValue(StrUtil.blankToDefault(unionName, "未分工会") + "报名汇总表");
titleCell.setCellStyle(titleStyle);
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 8));
for (int i = 1; i <= 8; i++) {
titleRow.createCell(i).setCellStyle(titleStyle);
}
Row headerRow = sheet.createRow(1);
headerRow.setHeightInPoints(24);
String[] headers = {"序号", "工号", "姓名", "身份证号码", "电话号码", "所属分工会", "所选线路名称", "疗休养时间", "备注"};
for (int i = 0; i < headers.length; i++) {
setCell(headerRow, i, headers[i], headerStyle);
}
int rowIndex = 2;
int seq = 1;
for (NutMap row : rows) {
Row dataRow = sheet.createRow(rowIndex++);
dataRow.setHeightInPoints(22);
setCell(dataRow, 0, String.valueOf(seq++), bodyStyle);
setCell(dataRow, 1, row.getString("jobNo", ""), bodyStyle);
setCell(dataRow, 2, row.getString("userName", ""), bodyStyle);
setCell(dataRow, 3, row.getString("idCard", ""), bodyStyle);
setCell(dataRow, 4, row.getString("mobile", ""), bodyStyle);
setCell(dataRow, 5, row.getString("unionName", ""), bodyStyle);
setCell(dataRow, 6, row.getString("lineName", ""), bodyStyle);
setCell(dataRow, 7, row.getString("travelPeriod", ""), bodyStyle);
setCell(dataRow, 8, "", bodyStyle);
}
return workbook;
}
private CellStyle createUnionSignupTitleStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setBorderTop(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
Font font = workbook.createFont();
font.setBold(true);
font.setFontHeightInPoints((short) 16);
style.setFont(font);
return style;
}
private CellStyle createUnionSignupHeaderStyle(Workbook workbook) {
CellStyle style = createUnionSignupBodyStyle(workbook);
style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
Font font = workbook.createFont();
font.setBold(true);
style.setFont(font);
return style;
}
private CellStyle createUnionSignupBodyStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setWrapText(true);
style.setBorderTop(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
return style;
}
private void setCell(Row row, int index, String value, CellStyle style) {
Cell cell = row.createCell(index);
cell.setCellValue(StrUtil.blankToDefault(value, ""));
cell.setCellStyle(style);
}
private String safeFileName(String fileName) {
String safeName = StrUtil.blankToDefault(fileName, "报名汇总表.xls")
.replaceAll("[\\\\/:*?\"<>|]", "、");
return safeName.length() > 120 ? safeName.substring(0, 120) : safeName;
}
private boolean validateImportRow(TourLedgerImportExcelMode row, int index) {
if (row.getYear() == null) {
row.setErrInfo("年度为空", index);
return false;
}
if (StrUtil.isBlank(row.getJobNo())) {
row.setErrInfo("工号为空", index);
return false;
}
if (StrUtil.isBlank(row.getLineName())) {
row.setErrInfo("线路名称为空", index);
return false;
}
if (StrUtil.isBlank(row.getLineType())) {
row.setErrInfo("线路类型为空", index);
return false;
}
if (StrUtil.isBlank(row.getJoined())) {
row.setErrInfo("是否参加为空", index);
return false;
}
return true;
}
private TourLine fetchImportLine(TourLedgerImportExcelMode row) {
Cnd cnd = Cnd.where(TourLine::getDelFlag, "=", false)
.and(TourLine::getYear, "=", row.getYear())
.and(TourLine::getLineName, "=", row.getLineName().trim())
.and(TourLine::getLineType, "=", row.getLineType().trim());
List<TourLine> lines = tourLedgerService.dao().query(TourLine.class, cnd);
return lines.size() == 1 ? lines.get(0) : null;
}
private List<TourLedger> queryImportLedgerCandidates(TourLedgerImportExcelMode row) {
Cnd cnd = Cnd.where(TourLedger::getDelFlag, "=", false)
.and(TourLedger::getYear, "=", row.getYear())
.and(TourLedger::getJobNo, "=", row.getJobNo().trim());
return tourLedgerService.query(cnd);
}
private TourLedger resolveImportLedger(TourLedgerImportExcelMode row, List<TourLedger> ledgers, TourLine line) {
if (ledgers.size() == 1) {
return ledgers.get(0);
}
List<TourLedger> matched = ledgers;
if (row.getSignupTime() != null) {
String importSignupTime = normalizeSignupTime(row.getSignupTime());
matched = matched.stream()
.filter(item -> signupTimeEquals(importSignupTime, item.getSignupTime()))
.collect(Collectors.toList());
if (matched.size() == 1) {
return matched.get(0);
}
}
matched = ledgers.stream()
.filter(item -> ledgerLineEquals(item, line))
.collect(Collectors.toList());
return matched.size() == 1 ? matched.get(0) : null;
}
private boolean ledgerLineEquals(TourLedger ledger, TourLine line) {
if (ledger == null || line == null) {
return false;
}
if (StrUtil.isNotBlank(ledger.getLineId()) && ledger.getLineId().equals(line.getId())) {
return true;
}
return StrUtil.equals(ledger.getLineName(), line.getLineName())
&& StrUtil.equals(ledger.getLineType(), line.getLineType());
}
private Result insertImportLedger(TourLedgerImportExcelMode row, TourLine line, Boolean joined) {
View_user user = fetchImportUser(row.getJobNo());
if (user == null) {
return Result.error("系统用户中未找到该工号");
}
TourLedger ledger = new TourLedger();
ledger.setYear(row.getYear());
ledger.setJobNo(StrUtil.blankToDefault(user.getLoginname(), row.getJobNo().trim()));
ledger.setUserName(StrUtil.blankToDefault(user.getUsername(), row.getUserName()));
ledger.setGender(user.getSex());
ledger.setIdCard(user.getIdCard());
ledger.setUnitId(user.getUnitId());
ledger.setUnitName(user.getUnitName());
ledger.setUnionId(user.getUnionId());
ledger.setUnionName(user.getUnionName());
ledger.setSignupTime(normalizeSignupTime(row.getSignupTime()));
ledger.setLineId(line.getId());
ledger.setLineName(line.getLineName());
ledger.setLineType(line.getLineType());
ledger.setTravelAgencyId(line.getTravelAgencyId());
ledger.setTravelAgencyName(getTravelAgencyName(line.getTravelAgencyId()));
ledger.setHasFamily(false);
ledger.setJoined(joined);
ledger.setReimbursed(false);
ledger.setOverCostReimbursed(false);
tourLedgerService.insert(ledger);
return Result.success();
}
private View_user fetchImportUser(String jobNo) {
if (StrUtil.isBlank(jobNo)) {
return null;
}
return tourLedgerService.dao().fetch(View_user.class, Cnd.where(View_user::getLoginname, "=", jobNo.trim()));
}
private String getTravelAgencyName(String travelAgencyId) {
if (StrUtil.isBlank(travelAgencyId)) {
return "";
}
TourTravelAgency agency = tourLedgerService.dao().fetch(TourTravelAgency.class, travelAgencyId);
return agency == null ? "" : StrUtil.blankToDefault(agency.getAgencyName(), "");
}
private boolean signupTimeEquals(String importSignupTime, String ledgerSignupTime) {
if (StrUtil.isBlank(importSignupTime) || StrUtil.isBlank(ledgerSignupTime)) {
return false;
}
String ledgerTime = normalizeSignupTime(ledgerSignupTime);
if (importSignupTime.equals(ledgerTime)) {
return true;
}
return importSignupTime.length() == 16 && ledgerTime.startsWith(importSignupTime)
|| ledgerTime.length() == 16 && importSignupTime.startsWith(ledgerTime);
}
private String normalizeSignupTime(Object value) {
if (value instanceof Date date) {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
}
String text = StrUtil.trimToEmpty(value == null ? "" : String.valueOf(value)).replace('T', ' ');
if (StrUtil.isBlank(text)) {
return "";
}
text = text.replaceAll("\\.0$", "");
String[] dateTimePatterns = {
"yyyy-MM-dd HH:mm:ss",
"yyyy-MM-dd HH:mm",
"yyyy/M/d H:mm:ss",
"yyyy/M/d H:mm",
"M/d/yy H:mm:ss",
"M/d/yy H:mm",
"M/d/yyyy H:mm:ss",
"M/d/yyyy H:mm"
};
for (String pattern : dateTimePatterns) {
try {
LocalDateTime dateTime = LocalDateTime.parse(text, DateTimeFormatter.ofPattern(pattern));
return dateTime.format(DateTimeFormatter.ofPattern(pattern.contains(":ss") ? "yyyy-MM-dd HH:mm:ss" : "yyyy-MM-dd HH:mm"));
} catch (DateTimeParseException ignored) {
}
}
String[] datePatterns = {"yyyy-MM-dd", "yyyy/M/d", "M/d/yy", "M/d/yyyy"};
for (String pattern : datePatterns) {
try {
LocalDate date = LocalDate.parse(text, DateTimeFormatter.ofPattern(pattern));
return date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
} catch (DateTimeParseException ignored) {
}
}
return text;
}
private Boolean parseJoined(String value) {
String joined = StrUtil.trimToEmpty(value).toLowerCase();
if ("1".equals(joined) || "1.0".equals(joined) || "是".equals(joined) || "true".equals(joined) || "已参加".equals(joined)) {
return true;
}
if ("0".equals(joined) || "0.0".equals(joined) || "否".equals(joined) || "false".equals(joined) || "未参加".equals(joined)) {
return false;
}
return null;
}
@At
@SaCheckPermission("tour.ledger")
public Result unionOptions() {
Cnd cnd = Cnd.NEW();
cnd.asc("unionCode");
cnd.asc("name");
return Result.success(tourLedgerService.dao().query(Sys_union.class, cnd));
}
@At
@SaCheckPermission("tour.ledger")
public Result lineOptions(Integer startYear, Integer endYear, String travelPeriod, String lineType, String unionId, String keyword, Boolean directFamilyOnly, Boolean overCostOnly) {
Cnd cnd = Cnd.NEW();
cnd.and("t.delFlag", "=", false);
cnd.andEX("t.`year`", ">=", startYear);
cnd.andEX("t.`year`", "<=", endYear == null ? LocalDate.now().getYear() : endYear);
cnd.and("t.lineName", "IS NOT", null);
cnd.and("t.lineName", "<>", "");
cnd.andEX("t.unionId", "=", unionId);
cnd.andEX("t.lineType", "=", lineType);
appendTravelPeriodFilter(cnd, travelPeriod);
appendKeywordFilter(cnd, keyword);
appendScopeFilter(cnd, directFamilyOnly, overCostOnly);
appendApprovedWorkflowFilter(cnd);
Sql sql = Sqls.create("""
SELECT DISTINCT
CASE
WHEN IFNULL(t.lineId, '') <> '' THEN t.lineId
ELSE CONCAT('legacy:', t.lineName)
END AS lineId,
COALESCE(NULLIF(l.lineName, ''), t.lineName) AS lineName
FROM tour_ledger t
LEFT JOIN wf_process_instance ins ON ins.businessNo = t.id
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
LEFT JOIN tour_ledger_direct_relative dr ON dr.ledgerId = t.id AND dr.delFlag = 0
$condition
ORDER BY lineName ASC
""");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(sql);
return Result.success(sql.getList(NutMap.class));
}
@At
@SaCheckPermission("tour.ledger")
public Result travelPeriodOptions(Integer startYear, Integer endYear, String lineId, String lineType, String unionId, String keyword, Boolean directFamilyOnly, Boolean overCostOnly) {
Cnd cnd = Cnd.NEW();
cnd.and("t.delFlag", "=", false);
cnd.andEX("t.`year`", ">=", startYear);
cnd.andEX("t.`year`", "<=", endYear == null ? LocalDate.now().getYear() : endYear);
cnd.and("m.travelStartTime", "IS NOT", null);
cnd.and("m.travelStartTime", "<>", "");
cnd.and("m.travelEndTime", "IS NOT", null);
cnd.and("m.travelEndTime", "<>", "");
cnd.andEX("t.unionId", "=", unionId);
cnd.andEX("t.lineType", "=", lineType);
appendLineFilter(cnd, lineId);
appendKeywordFilter(cnd, keyword);
appendScopeFilter(cnd, directFamilyOnly, overCostOnly);
appendApprovedWorkflowFilter(cnd);
Sql sql = Sqls.create("""
SELECT DISTINCT CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime) AS travelPeriod
FROM tour_ledger t
LEFT JOIN wf_process_instance ins ON ins.businessNo = t.id
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
LEFT JOIN tour_ledger_direct_relative dr ON dr.ledgerId = t.id AND dr.delFlag = 0
$condition
ORDER BY travelPeriod ASC
""");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(sql);
return Result.success(sql.getList(NutMap.class));
}
private Cnd buildQueryCnd(Integer startYear, Integer endYear, String keyword, String unionId, String lineId, String travelPeriod, String lineType, Boolean directFamilyOnly, Boolean overCostOnly) {
Cnd cnd = Cnd.NEW();
cnd.and("t.delFlag", "=", false);
cnd.andEX("t.`year`", ">=", startYear);
cnd.andEX("t.`year`", "<=", endYear == null ? LocalDate.now().getYear() : endYear);
cnd.andEX("t.unionId", "=", unionId);
cnd.andEX("t.lineType", "=", lineType);
appendLineFilter(cnd, lineId);
appendTravelPeriodFilter(cnd, travelPeriod);
appendKeywordFilter(cnd, keyword);
appendScopeFilter(cnd, directFamilyOnly, overCostOnly);
appendApprovedWorkflowFilter(cnd);
return cnd;
}
private void appendApprovedWorkflowFilter(Cnd cnd) {
SqlExpressionGroup group = new SqlExpressionGroup();
group.or("ins.id", "IS", null);
group.or("ins.state", "=", ProcessInstanceStateEnum.FINISHED.getCode());
cnd.and(group);
}
private void appendScopeFilter(Cnd cnd, Boolean directFamilyOnly, Boolean overCostOnly) {
if (Boolean.TRUE.equals(directFamilyOnly)) {
cnd.and("IF(IFNULL(l.directFamilyUnitLine, 0) = 1 OR dr.id IS NOT NULL, 1, 0)", "=", 1);
}
if (Boolean.TRUE.equals(overCostOnly)) {
cnd.and("t.overCostReimbursed", "=", true);
}
}
private void appendKeywordFilter(Cnd cnd, String keyword) {
if (StrUtil.isNotBlank(keyword)) {
SqlExpressionGroup group = new SqlExpressionGroup();
group.orLike("t.userName", keyword.trim());
group.orLike("t.jobNo", keyword.trim());
cnd.and(group);
}
}
private String getOrderColumn(String orderName) {
if ("year".equals(orderName)) {
return "t.`year`";
}
if ("jobNo".equals(orderName)) {
return "t.jobNo";
}
if ("userName".equals(orderName)) {
return "t.userName";
}
if ("lineName".equals(orderName)) {
return "COALESCE(NULLIF(l.lineName, ''), t.lineName)";
}
if ("travelPeriod".equals(orderName)) {
return "COALESCE(m.travelStartTime, tp.travelPeriod)";
}
if ("lineType".equals(orderName)) {
return "t.lineType";
}
if ("signupTime".equals(orderName)) {
return "t.signupTime";
}
if ("familyCount".equals(orderName)) {
return "familyCount";
}
if ("joined".equals(orderName)) {
return "t.joined";
}
return "t.`year`";
}
private String getTravelPeriod(TourLedger ledger) {
if (ledger == null || ledger.getYear() == null || StrUtil.isBlank(ledger.getLineId())) {
return "";
}
if (StrUtil.isNotBlank(ledger.getMatterId())) {
Sql exactSql = Sqls.create("""
SELECT CASE
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
ELSE ''
END AS travelPeriod
FROM tour_matter
WHERE delFlag = 0
AND id = @matterId
""");
exactSql.setParam("matterId", ledger.getMatterId());
exactSql.setCallback(Sqls.callback.str());
tourLedgerService.dao().execute(exactSql);
return exactSql.getString();
}
Sql sql = Sqls.create("""
SELECT GROUP_CONCAT(
DISTINCT CASE
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
ELSE NULL
END
ORDER BY travelStartTime ASC
SEPARATOR ';'
) AS travelPeriod
FROM tour_matter
WHERE delFlag = 0
AND `year` = @year
AND lineId = @lineId
""");
sql.setParam("year", ledger.getYear());
sql.setParam("lineId", ledger.getLineId());
sql.setCallback(Sqls.callback.str());
tourLedgerService.dao().execute(sql);
return sql.getString();
}
private boolean isDirectFamilyUnitLine(TourLedger ledger) {
if (ledger == null || StrUtil.isBlank(ledger.getLineId())) {
return false;
}
Sql sql = Sqls.create("""
SELECT IFNULL(directFamilyUnitLine, 0)
FROM tour_line
WHERE delFlag = 0
AND id = @lineId
""");
sql.setParam("lineId", ledger.getLineId());
sql.setCallback(Sqls.callback.integer());
tourLedgerService.dao().execute(sql);
return sql.getInt() > 0;
}
private boolean isFillBedInfo(TourLedger ledger) {
if (ledger == null || StrUtil.isBlank(ledger.getMatterId())) {
return true;
}
Sql sql = Sqls.create("""
SELECT IFNULL(MAX(s.fillBedInfo), 1)
FROM tour_ledger t
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_setting s ON s.id = m.settingId AND s.delFlag = 0
WHERE t.id = @ledgerId
""");
sql.setParam("ledgerId", ledger.getId());
sql.setCallback(Sqls.callback.integer());
tourLedgerService.dao().execute(sql);
return sql.getInt() > 0;
}
private void appendTravelPeriodFilter(Cnd cnd, String travelPeriod) {
if (StrUtil.isBlank(travelPeriod)) {
return;
}
cnd.and("CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)", "=", travelPeriod);
}
private void appendLineFilter(Cnd cnd, String lineId) {
if (StrUtil.isBlank(lineId)) {
return;
}
if (lineId.startsWith("legacy:")) {
SqlExpressionGroup legacyGroup = new SqlExpressionGroup();
legacyGroup.or("t.lineId", "IS", null);
legacyGroup.or("t.lineId", "=", "");
cnd.and(legacyGroup);
cnd.and("t.lineName", "=", lineId.substring("legacy:".length()));
return;
}
cnd.and("t.lineId", "=", lineId);
}
private String getCurrentLineName(TourLedger ledger) {
if (ledger == null) {
return "";
}
if (StrUtil.isBlank(ledger.getLineId())) {
return ledger.getLineName();
}
Sql sql = Sqls.create("""
SELECT lineName
FROM tour_line
WHERE delFlag = 0
AND id = @lineId
""");
sql.setParam("lineId", ledger.getLineId());
sql.setCallback(Sqls.callback.str());
tourLedgerService.dao().execute(sql);
return StrUtil.blankToDefault(sql.getString(), ledger.getLineName());
}
}
@@ -0,0 +1,297 @@
package com.budwk.app.zhgh.dayofficework.tour.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog;
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.sys.models.Sys_dict;
import com.budwk.app.sys.services.SysDictService;
import com.budwk.app.sys.views.View_user;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLine;
import com.budwk.app.zhgh.dayofficework.tour.models.TourTravelAgency;
import com.budwk.app.zhgh.dayofficework.tour.service.TourLineService;
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 java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@IocBean
@Ok("json:full")
@At("/platform/tour/route")
public class TourLineController {
@Inject
private TourLineService tourLineService;
@Inject
private SysDictService sysDictService;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/Tour/route/index.html")
@SaCheckPermission("tour.route")
public void index() {
}
@At
@SaCheckPermission("tour.route")
public Result pageData(PageForm pageForm, Integer year, String lineName, String lotId) {
if (StrUtil.isBlank(pageForm.getPageOrderName())) {
pageForm.defaultSortDesc("enabled");
}
Cnd cnd = Cnd.NEW();
cnd.and("l.delFlag", "=", false);
cnd.andEX("l.`year`", "=", year);
cnd.and(Cnd.likeEX("l.lineName", lineName));
cnd.andEX("l.lotId", "=", lotId);
boolean sysAdmin = AuthUtil.hasRole(RoleConstant.SYSADMIN.name());
boolean branchUnionScope = !sysAdmin && AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name());
applyDataScope(cnd, sysAdmin, branchUnionScope);
String creatorJoin = branchUnionScope ? """
LEFT JOIN sys_unit lineUnit ON lineUnit.id = l.unitId
LEFT JOIN sys_user creatorUser ON creatorUser.id = COALESCE(NULLIF(l.creatorUserId, ''), l.createdBy)
LEFT JOIN sys_unit creatorUnit ON creatorUnit.id = creatorUser.unitId
""" : "";
Sql countSql = Sqls.create("""
SELECT COUNT(1)
FROM tour_line l
$creatorJoin
$condition
""");
countSql.setVar("creatorJoin", creatorJoin);
countSql.setCondition(cnd);
countSql.setCallback(Sqls.callback.integer());
tourLineService.dao().execute(countSql);
int count = countSql.getInt();
Sql listSql = Sqls.create("""
SELECT
l.*,
a.agencyName AS travelAgencyName,
lot.lotName AS lotName
FROM tour_line l
LEFT JOIN tour_travel_agency a ON a.id = l.travelAgencyId
LEFT JOIN tour_setting_lot lot ON lot.id = l.lotId
$creatorJoin
$condition
ORDER BY $orderColumn $orderBy, l.`year` DESC, l.lineCode ASC, l.createdAt DESC
""");
listSql.setVar("creatorJoin", creatorJoin);
listSql.setCondition(cnd);
listSql.setVar("orderColumn", getOrderColumn(pageForm.getPageOrderName()));
listSql.setVar("orderBy", "descending".equals(pageForm.getPageOrderBy()) ? "desc" : "asc");
listSql.setPager(tourLineService.dao().createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
listSql.setCallback(Sqls.callback.maps());
tourLineService.dao().execute(listSql);
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, listSql.getList(NutMap.class));
return Result.success(pagination);
}
@At
@SaCheckPermission("tour.route")
public Result detail(String id) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
TourLine line = tourLineService.fetch(id);
return line == null ? Result.error("线路不存在") : Result.success(line);
}
@At
@SaCheckPermission("tour.route")
public Result travelAgencyOptions(Integer year) {
Cnd cnd = Cnd.where(TourTravelAgency::getEnabled, "=", true);
cnd.andEX(TourTravelAgency::getYear, "=", year);
cnd.asc(TourTravelAgency::getAgencyCode).asc(TourTravelAgency::getAgencyName);
return Result.success(tourLineService.dao().query(TourTravelAgency.class, cnd));
}
@At
@SaCheckPermission("tour.route")
public Result lotOptions(Integer year) {
Cnd cnd = Cnd.NEW();
cnd.andEX("s.`year`", "=", year);
Sql sql = Sqls.create("""
SELECT lot.id, lot.lotName, lot.lotValue, lot.activityCost
FROM tour_setting_lot lot
INNER JOIN tour_setting s ON s.id = lot.settingId
$condition
ORDER BY lot.lotValue DESC, lot.lotName ASC
""");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.maps());
tourLineService.dao().execute(sql);
return Result.success(sql.getList(NutMap.class));
}
@At
@SaCheckPermission("tour.route")
public Result lineTypeOptions() {
List<Sys_dict> list = sysDictService.getSubListByCode("lineType");
if (list == null) {
return Result.success(Collections.emptyList());
}
return Result.success(list.stream().filter(item -> !item.isDisabled()).collect(Collectors.toList()));
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tour.route")
@SLog(type = "tour", tag = "线路管理", msg = "保存线路信息")
public Result doSubmit(TourLine line) {
Result checkResult = check(line);
if (checkResult != null) {
return checkResult;
}
Cnd sameCodeCnd = Cnd.where(TourLine::getYear, "=", line.getYear())
.and(TourLine::getLineCode, "=", line.getLineCode());
if (StrUtil.isNotBlank(line.getId())) {
sameCodeCnd.and(TourLine::getId, "<>", line.getId());
}
if (tourLineService.count(sameCodeCnd) > 0) {
return Result.error("同年度下线路编号已存在");
}
if (line.getEnabled() == null) {
line.setEnabled(true);
}
if (line.getOpenFlag() == null) {
line.setOpenFlag(true);
}
if (line.getDirectFamilyUnitLine() == null) {
line.setDirectFamilyUnitLine(false);
}
if (StrUtil.isBlank(line.getId())) {
fillCreatorInfo(line, null);
tourLineService.insert(line);
} else {
fillCreatorInfo(line, tourLineService.fetch(line.getId()));
tourLineService.updateIgnoreNull(line);
}
return Result.success();
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tour.route")
@SLog(type = "tour", tag = "线路管理", msg = "删除线路信息")
public Result doDelete(String id) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
tourLineService.delete(id);
return Result.success();
}
private Result check(TourLine line) {
if (line == null) {
return Result.error("参数错误");
}
if (line.getYear() == null) {
return Result.error("创建年度不能为空");
}
if (StrUtil.isBlank(line.getTravelAgencyId())) {
return Result.error("旅行社名称不能为空");
}
if (StrUtil.isBlank(line.getLineName())) {
return Result.error("线路名称不能为空");
}
if (StrUtil.isBlank(line.getLineCode())) {
return Result.error("线路编号不能为空");
}
if (StrUtil.isBlank(line.getLineType())) {
return Result.error("线路类型不能为空");
}
if (StrUtil.isBlank(line.getLotId())) {
return Result.error("时间标段不能为空");
}
if (StrUtil.isBlank(line.getMobileThumb())) {
return Result.error("移动端缩略图不能为空");
}
return null;
}
/**
* 创建人和所在单位由当前登录人生成,编辑时保留原创建信息。
*/
private void fillCreatorInfo(TourLine line, TourLine oldLine) {
if (oldLine != null) {
line.setCreatorUserId(defaultIfBlank(oldLine.getCreatorUserId(), SecurityUtil.getUserId()));
line.setCreatorName(defaultIfBlank(oldLine.getCreatorName(), SecurityUtil.getUserUsername()));
line.setUnitId(defaultIfBlank(oldLine.getUnitId(), SecurityUtil.getUnitId()));
line.setUnitName(defaultIfBlank(oldLine.getUnitName(), getCurrentUnitName()));
return;
}
line.setCreatorUserId(SecurityUtil.getUserId());
line.setCreatorName(SecurityUtil.getUserUsername());
line.setUnitId(SecurityUtil.getUnitId());
line.setUnitName(getCurrentUnitName());
}
private String getCurrentUnitName() {
View_user user = tourLineService.dao().fetch(View_user.class, Cnd.where("id", "=", SecurityUtil.getUserId()));
return user == null ? "" : defaultIfBlank(user.getUnitName(), "");
}
private String defaultIfBlank(String value, String defaultValue) {
return StrUtil.isBlank(value) ? defaultValue : value;
}
/**
* 线路数据范围:
* SYSADMIN 可查看全部;分工会主席查看本工会创建的线路;其它角色保持原逻辑,仅查看自己创建的线路。
*/
private void applyDataScope(Cnd cnd, boolean sysAdmin, boolean branchUnionScope) {
if (sysAdmin) {
return;
}
if (branchUnionScope) {
SqlExpressionGroup unionGroup = new SqlExpressionGroup();
unionGroup.or("lineUnit.unionId", "=", SecurityUtil.getUnionId());
unionGroup.or("creatorUnit.unionId", "=", SecurityUtil.getUnionId());
cnd.and(unionGroup);
return;
}
SqlExpressionGroup creatorGroup = new SqlExpressionGroup();
creatorGroup.or("l.creatorUserId", "=", SecurityUtil.getUserId());
creatorGroup.or("l.createdBy", "=", SecurityUtil.getUserId());
cnd.and(creatorGroup);
}
private String getOrderColumn(String orderName) {
if ("year".equals(orderName)) {
return "l.`year`";
}
if ("lineCode".equals(orderName)) {
return "l.lineCode";
}
if ("lotName".equals(orderName)) {
return "lot.lotName";
}
if ("lineType".equals(orderName)) {
return "l.lineType";
}
if ("enabled".equals(orderName)) {
return "l.enabled";
}
return "l.`year`";
}
}
@@ -0,0 +1,441 @@
package com.budwk.app.zhgh.dayofficework.tour.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog;
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.sys.models.Sys_dict;
import com.budwk.app.sys.models.Sys_union;
import com.budwk.app.sys.services.SysDictService;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
import com.budwk.app.zhgh.dayofficework.tour.models.TourMatter;
import com.budwk.app.zhgh.dayofficework.tour.models.TourSetting;
import com.budwk.app.zhgh.dayofficework.tour.service.TourMatterService;
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 java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@IocBean
@Ok("json:full")
@At("/platform/tour/matter")
public class TourMatterController {
private static final String MOBILE_PATTERN = "^1[3-9]\\d{9}$";
@Inject
private TourMatterService tourMatterService;
@Inject
private SysDictService sysDictService;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/Tour/matter/index.html")
@SaCheckPermission("tour.matter")
public void index() {
}
@At
@SaCheckPermission("tour.matter")
public Result pageData(PageForm pageForm, Integer year, String matterName, String unionId, String organizationType) {
Cnd cnd = Cnd.NEW();
cnd.and("m.delFlag", "=", false);
cnd.andEX("m.`year`", "=", year);
cnd.and(Cnd.likeEX("m.matterName", matterName));
cnd.andEX("m.unionId", "=", unionId);
cnd.andEX("m.organizationType", "=", organizationType);
applyMatterDataScope(cnd);
Sql countSql = Sqls.create("""
SELECT COUNT(1)
FROM tour_matter m
$condition
""");
countSql.setCondition(cnd);
countSql.setCallback(Sqls.callback.integer());
tourMatterService.dao().execute(countSql);
int count = countSql.getInt();
Sql listSql = Sqls.create("""
SELECT
m.*,
s.configName AS settingName,
u.name AS unionName,
d.name AS organizationTypeName,
l.lineName AS lineName,
l.lineType AS lineType,
l.directFamilyUnitLine AS directFamilyUnitLine
FROM tour_matter m
LEFT JOIN tour_setting s ON s.id = m.settingId
LEFT JOIN sys_union u ON u.id = m.unionId
LEFT JOIN tour_line l ON l.id = m.lineId
LEFT JOIN sys_dict d ON d.`code` = m.organizationType
AND d.parentId = (SELECT id FROM sys_dict WHERE `code` = 'organizationType' LIMIT 1)
$condition
ORDER BY $orderColumn $orderBy, m.`year` DESC, m.createdAt DESC
""");
listSql.setCondition(cnd);
listSql.setVar("orderColumn", getOrderColumn(pageForm.getPageOrderName()));
listSql.setVar("orderBy", "descending".equals(pageForm.getPageOrderBy()) ? "desc" : "asc");
listSql.setPager(tourMatterService.dao().createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
listSql.setCallback(Sqls.callback.maps());
tourMatterService.dao().execute(listSql);
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, listSql.getList(NutMap.class));
return Result.success(pagination);
}
@At
@SaCheckPermission("tour.matter")
public Result detail(String id) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
TourMatter matter = tourMatterService.fetch(id);
return matter == null ? Result.error("事项不存在") : Result.success(matter);
}
@At
@SaCheckPermission("tour.matter")
public Result settingOptions(Integer year) {
Cnd cnd = Cnd.where(TourSetting::getEnabled, "=", true);
cnd.andEX(TourSetting::getYear, "=", year);
cnd.desc(TourSetting::getCreatedAt);
cnd.desc(TourSetting::getUpdatedAt);
cnd.asc(TourSetting::getConfigName);
return Result.success(tourMatterService.dao().query(TourSetting.class, cnd));
}
@At
@SaCheckPermission("tour.matter")
public Result unionOptions() {
Cnd cnd = Cnd.NEW();
cnd.asc("unionCode");
cnd.asc("name");
return Result.success(tourMatterService.dao().query(Sys_union.class, cnd));
}
@At
@SaCheckPermission("tour.matter")
public Result lineOptions(Integer year) {
Cnd cnd = Cnd.NEW();
cnd.and("l.delFlag", "=", false);
cnd.and("l.enabled", "=", true);
cnd.and("l.openFlag", "=", true);
Sql sql = Sqls.create("""
SELECT
l.*,
lot.lotName AS lotName,
lot.lotValue AS lotValue,
lot.lotValue AS lotDays,
lot.activityCost AS activityCost
FROM tour_line l
LEFT JOIN tour_setting_lot lot ON lot.id = l.lotId
$condition
ORDER BY l.lineCode ASC, l.lineName ASC
""");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.maps());
tourMatterService.dao().execute(sql);
return Result.success(sql.getList(NutMap.class));
}
@At
@SaCheckPermission("tour.matter")
public Result organizationTypeOptions() {
List<Sys_dict> list = sysDictService.getSubListByCode("organizationType");
if (list == null) {
return Result.success(Collections.emptyList());
}
return Result.success(list.stream().filter(item -> !item.isDisabled()).collect(Collectors.toList()));
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tour.matter")
@SLog(type = "tour", tag = "疗休养事项", msg = "保存疗休养事项")
public Result doSubmit(TourMatter matter) {
Result checkResult = check(matter);
if (checkResult != null) {
return checkResult;
}
Cnd sameNameCnd = Cnd.where(TourMatter::getYear, "=", matter.getYear())
.and(TourMatter::getMatterName, "=", matter.getMatterName());
if (StrUtil.isNotBlank(matter.getId())) {
sameNameCnd.and(TourMatter::getId, "<>", matter.getId());
}
if (tourMatterService.count(sameNameCnd) > 0) {
return Result.error("同年度下事项名称已存在");
}
if (matter.getEnabled() == null) {
matter.setEnabled(true);
}
fillCreatorAndUnion(matter);
if (StrUtil.isBlank(matter.getId())) {
tourMatterService.insert(matter);
} else {
tourMatterService.updateIgnoreNull(matter);
}
return Result.success();
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tour.matter")
@SLog(type = "tour", tag = "疗休养事项", msg = "删除疗休养事项")
public Result doDelete(String id) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
TourMatter matter = tourMatterService.fetch(id);
if (matter == null) {
return Result.error("事项不存在");
}
long signupCount = countSignup(id);
if (signupCount > 0) {
return Result.error("当前事项对应线路已有人员报名,不能删除");
}
tourMatterService.delete(id);
return Result.success();
}
@At
@SaCheckPermission("tour.matter")
public Result deleteInfo(String id) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
TourMatter matter = tourMatterService.fetch(id);
if (matter == null) {
return Result.error("事项不存在");
}
long signupCount = countSignup(id);
return Result.success(NutMap.NEW()
.addv("signupCount", signupCount)
.addv("canDelete", signupCount <= 0)
.addv("message", signupCount > 0 ? "当前事项已有人员报名,不能删除" : "当前事项暂无人员报名,可删除"));
}
@At
@SaCheckPermission("tour.matter")
public Result lineConfig(String matterId) {
if (StrUtil.isBlank(matterId)) {
return Result.error("参数错误");
}
TourMatter matter = tourMatterService.fetch(matterId);
return matter == null ? Result.error("事项不存在") : Result.success(matter);
}
@At
@SaCheckPermission("tour.matter")
public Result settingPeople(String matterId) {
if (StrUtil.isBlank(matterId)) {
return Result.error("参数错误");
}
TourMatter matter = tourMatterService.fetch(matterId);
if (matter == null || StrUtil.isBlank(matter.getSettingId())) {
return Result.success(NutMap.NEW());
}
TourSetting setting = tourMatterService.dao().fetch(TourSetting.class, matter.getSettingId());
if (setting == null) {
return Result.success(NutMap.NEW());
}
return Result.success(NutMap.NEW()
.addv("minGroupPeople", setting.getMinGroupPeople())
.addv("maxGroupPeople", setting.getMaxGroupPeople()));
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tour.matter")
@SLog(type = "tour", tag = "疗休养事项", msg = "保存事项线路配置")
public Result lineConfigDoSubmit(TourMatter matter) {
Result checkResult = checkLineConfig(matter);
if (checkResult != null) {
return checkResult;
}
TourMatter oldMatter = tourMatterService.fetch(matter.getId());
if (oldMatter == null) {
return Result.error("事项不存在");
}
oldMatter.setLineId(matter.getLineId());
oldMatter.setSignupStartTime(matter.getSignupStartTime());
oldMatter.setSignupEndTime(matter.getSignupEndTime());
oldMatter.setTravelStartTime(matter.getTravelStartTime());
oldMatter.setTravelEndTime(matter.getTravelEndTime());
oldMatter.setContactName(matter.getContactName());
oldMatter.setContactPhone(matter.getContactPhone());
oldMatter.setMinGroupPeople(matter.getMinGroupPeople());
oldMatter.setMaxGroupPeople(matter.getMaxGroupPeople());
oldMatter.setEstimatedCost(matter.getEstimatedCost());
tourMatterService.update(oldMatter);
return Result.success();
}
private Result check(TourMatter matter) {
if (matter == null) {
return Result.error("参数错误");
}
if (matter.getYear() == null) {
return Result.error("年度不能为空");
}
if (StrUtil.isBlank(matter.getMatterName())) {
return Result.error("事项名称不能为空");
}
if (StrUtil.isBlank(matter.getSettingId())) {
return Result.error("疗休养配置不能为空");
}
if (StrUtil.isBlank(matter.getOrganizationType())) {
return Result.error("组织形式不能为空");
}
if (!"schoolUnion".equals(matter.getOrganizationType()) && StrUtil.isBlank(matter.getUnionId())) {
return Result.error("所属工会不能为空");
}
return null;
}
private Result checkLineConfig(TourMatter matter) {
if (matter == null) {
return Result.error("参数错误");
}
if (StrUtil.isBlank(matter.getId())) {
return Result.error("事项不能为空");
}
if (StrUtil.isBlank(matter.getLineId())) {
return Result.error("线路不能为空");
}
if (StrUtil.isBlank(matter.getSignupStartTime()) || StrUtil.isBlank(matter.getSignupEndTime())
|| StrUtil.isBlank(matter.getTravelStartTime()) || StrUtil.isBlank(matter.getTravelEndTime())) {
return Result.error("时间不能为空");
}
if (matter.getSignupStartTime().compareTo(matter.getSignupEndTime()) >= 0) {
return Result.error("报名开始时间必须小于报名结束时间");
}
if (matter.getTravelStartTime().compareTo(matter.getTravelEndTime()) > 0) {
return Result.error("出行开始时间不能晚于出行结束时间");
}
if (matter.getSignupEndTime().compareTo(matter.getTravelStartTime()) >= 0) {
return Result.error("报名结束时间必须小于出行开始时间");
}
if (StrUtil.isBlank(matter.getContactName())) {
return Result.error("联系人不能为空");
}
if (StrUtil.isBlank(matter.getContactPhone()) || !matter.getContactPhone().matches(MOBILE_PATTERN)) {
return Result.error("联系方式格式不正确");
}
if (matter.getMinGroupPeople() == null || matter.getMinGroupPeople() <= 0) {
return Result.error("最少成团人数必须大于0");
}
if (matter.getMaxGroupPeople() == null || matter.getMaxGroupPeople() <= 0) {
return Result.error("最多成团人数必须大于0");
}
if (matter.getMinGroupPeople() > matter.getMaxGroupPeople()) {
return Result.error("最少成团人数不能大于最多成团人数");
}
if (matter.getEstimatedCost() == null || matter.getEstimatedCost().signum() < 0) {
return Result.error("预计费用不能小于0");
}
return null;
}
private void fillCreatorAndUnion(TourMatter matter) {
if (StrUtil.isBlank(matter.getId())) {
matter.setCreatorUserId(SecurityUtil.getUserId());
matter.setCreatorName(SecurityUtil.getUserUsername());
} else {
TourMatter oldMatter = tourMatterService.fetch(matter.getId());
if (oldMatter != null) {
matter.setCreatorUserId(oldMatter.getCreatorUserId());
matter.setCreatorName(oldMatter.getCreatorName());
}
}
if ("schoolUnion".equals(matter.getOrganizationType())) {
matter.setUnionId("");
}
}
private long countSignup(String matterId) {
return tourMatterService.dao().count(TourLedger.class, Cnd.where(TourLedger::getDelFlag, "=", false)
.and(TourLedger::getMatterId, "=", matterId));
}
/**
* 事项数据范围:SYSADMIN 查看全部;分工会主席查看本工会事项;其它角色仅查看自己创建的事项。
*/
private void applyMatterDataScope(Cnd cnd) {
if (AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) {
return;
}
if (AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name())) {
cnd.and("m.unionId", "=", SecurityUtil.getUnionId());
return;
}
SqlExpressionGroup creatorGroup = new SqlExpressionGroup();
creatorGroup.or("m.creatorUserId", "=", SecurityUtil.getUserId());
creatorGroup.or("m.createdBy", "=", SecurityUtil.getUserId());
cnd.and(creatorGroup);
}
/**
* 事项批次选择线路时沿用线路管理的数据范围,避免在事项页面选到权限外线路。
*/
private void applyLineDataScope(Cnd cnd, boolean sysAdmin, boolean branchUnionScope) {
if (sysAdmin) {
return;
}
if (branchUnionScope) {
SqlExpressionGroup unionGroup = new SqlExpressionGroup();
unionGroup.or("lineUnit.unionId", "=", SecurityUtil.getUnionId());
unionGroup.or("creatorUnit.unionId", "=", SecurityUtil.getUnionId());
cnd.and(unionGroup);
return;
}
SqlExpressionGroup creatorGroup = new SqlExpressionGroup();
creatorGroup.or("l.creatorUserId", "=", SecurityUtil.getUserId());
creatorGroup.or("l.createdBy", "=", SecurityUtil.getUserId());
cnd.and(creatorGroup);
}
private String getOrderColumn(String orderName) {
if ("year".equals(orderName)) {
return "m.`year`";
}
if ("matterName".equals(orderName)) {
return "m.matterName";
}
if ("unionName".equals(orderName)) {
return "u.name";
}
if ("lineName".equals(orderName)) {
return "l.lineName";
}
if ("settingName".equals(orderName)) {
return "s.configName";
}
if ("organizationTypeName".equals(orderName)) {
return "d.location";
}
if ("enabled".equals(orderName)) {
return "m.enabled";
}
return "m.`year`";
}
}
@@ -0,0 +1,13 @@
package com.budwk.app.zhgh.dayofficework.tour.controller;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
/**
* 分阶段建设的菜单占位入口。
*/
@IocBean
@At("/platform/tour")
public class TourPlaceholderController {
}
@@ -0,0 +1,581 @@
package com.budwk.app.zhgh.dayofficework.tour.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.util.StrUtil;
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.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerDirectRelative;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerFamily;
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerDirectRelativeService;
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerFamilyService;
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService;
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.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 java.time.LocalDate;
import java.util.List;
@IocBean
@Ok("json:full")
@At("/platform/tour/schoolUnionApproval")
public class TourSchoolUnionApprovalController {
private static final String WORKFLOW_KEY = "LXYBZXQSXL";
private static final String TASK_DISPLAY_NAME = "校工会审核";
@Inject
private TourLedgerService tourLedgerService;
@Inject
private TourLedgerFamilyService tourLedgerFamilyService;
@Inject
private TourLedgerDirectRelativeService tourLedgerDirectRelativeService;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/Tour/schoolUnionApproval/index.html")
@SaCheckPermission(value = {"tour.schoolUnionApproval", "h5.tour.schoolUnionApproval"}, mode = SaMode.OR)
public void index() {
}
@At("/h5")
@Ok("beetl:/platform/zhghh5/dayofficework/tour/schoolUnionApproval/index.html")
@SaCheckPermission(value = {"tour.schoolUnionApproval", "h5.tour.schoolUnionApproval"}, mode = SaMode.OR)
public void h5() {
}
@At
@SaCheckPermission(value = {"tour.schoolUnionApproval", "h5.tour.schoolUnionApproval"}, mode = SaMode.OR)
public Result pageData(PageForm pageForm, Boolean audit, Integer startYear, Integer endYear, String keyword, String unionId, String lineId, String travelPeriod, String lineType) {
Cnd cnd = buildAuditCnd(audit, startYear, endYear, keyword, unionId, lineId, travelPeriod, lineType);
Sql countSql = Sqls.create("""
SELECT COUNT(DISTINCT task.id)
FROM wf_process_task task
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
INNER JOIN tour_ledger t ON t.id = ins.businessNo
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
$condition
""");
countSql.setCondition(cnd);
countSql.setCallback(Sqls.callback.integer());
tourLedgerService.dao().execute(countSql);
int count = countSql.getInt();
Sql listSql = Sqls.create("""
SELECT
t.*,
ins.id AS instanceId,
ins.businessNo,
ins.state AS instanceState,
ins.variable AS instanceVariable,
ins.processDefineId AS instanceProcessDefineId,
task.id AS taskId,
task.taskName AS taskKey,
task.displayName AS taskName,
task.taskType,
task.performType AS taskPerformType,
task.taskState,
task.finishTime,
task.taskParentId,
task.variable AS taskVariable,
IFNULL(GROUP_CONCAT(DISTINCT nt.displayName), '结束') AS curTaskName,
CASE
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
ELSE CONCAT(
COALESCE(NULLIF(l.lineName, ''), t.lineName),
'(',
CASE
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
ELSE IFNULL(mu.name, '')
END,
')'
)
END AS currentLineName,
CASE
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
ELSE tp.travelPeriod
END AS travelPeriod,
IFNULL(f.familyCount, 0) AS familyCount
FROM wf_process_task task
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
INNER JOIN tour_ledger t ON t.id = ins.businessNo
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
LEFT JOIN (
SELECT ledgerId, COUNT(1) AS familyCount
FROM tour_ledger_family
WHERE delFlag = 0
GROUP BY ledgerId
) f ON f.ledgerId = t.id
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
LEFT JOIN sys_union mu ON mu.id = m.unionId
LEFT JOIN (
SELECT
`year`,
lineId,
GROUP_CONCAT(
DISTINCT CASE
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
ELSE NULL
END
ORDER BY travelStartTime ASC
SEPARATOR ';'
) AS travelPeriod
FROM tour_matter
WHERE delFlag = 0
AND lineId IS NOT NULL
AND lineId <> ''
GROUP BY `year`, lineId
) tp ON (t.matterId IS NULL OR t.matterId = '') AND tp.`year` = t.`year` AND tp.lineId = t.lineId
$condition
GROUP BY task.id
ORDER BY $orderColumn $orderBy, task.createdAt DESC, t.signupTime DESC, t.createdAt DESC
""");
listSql.setCondition(cnd);
listSql.setVar("orderColumn", getOrderColumn(pageForm.getPageOrderName()));
listSql.setVar("orderBy", "ascending".equals(pageForm.getPageOrderBy()) ? "asc" : "desc");
listSql.setPager(tourLedgerService.dao().createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
listSql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(listSql);
List<NutMap> list = listSql.getList(NutMap.class);
list.forEach(item -> item.put("lineName", item.getString("currentLineName")));
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list);
return Result.success(pagination);
}
@At
@SaCheckPermission(value = {"tour.schoolUnionApproval", "h5.tour.schoolUnionApproval"}, mode = SaMode.OR)
public Result detail(String id) {
TourLedger ledger = fetchAuditLedger(id);
if (ledger == null) {
return Result.error("报名记录不存在或无权查看");
}
ledger.setLineName(getCurrentLineName(ledger));
Cnd familyCnd = Cnd.where(TourLedgerFamily::getLedgerId, "=", ledger.getId())
.and(TourLedgerFamily::getDelFlag, "=", false);
familyCnd.asc(TourLedgerFamily::getCreatedAt);
TourLedgerDirectRelative directRelative = tourLedgerDirectRelativeService.fetch(
Cnd.where(TourLedgerDirectRelative::getLedgerId, "=", ledger.getId())
.and(TourLedgerDirectRelative::getDelFlag, "=", false));
NutMap signupConfig = getSignupConfig(ledger);
return Result.success(NutMap.NEW()
.addv("ledger", ledger)
.addv("travelPeriod", getTravelPeriod(ledger))
.addv("families", tourLedgerFamilyService.query(familyCnd))
.addv("directRelative", directRelative)
.addv("allowFamily", signupConfig.getBoolean("allowFamily", false))
.addv("fillBedInfo", signupConfig.getBoolean("fillBedInfo", true))
.addv("directFamilyUnitLine", signupConfig.getBoolean("directFamilyUnitLine", false)));
}
@At
@SaCheckPermission(value = {"tour.schoolUnionApproval", "h5.tour.schoolUnionApproval"}, mode = SaMode.OR)
public Result unionOptions(Boolean audit, Integer startYear, Integer endYear, String keyword, String lineId, String travelPeriod, String lineType) {
Cnd cnd = buildAuditCnd(audit, startYear, endYear, keyword, null, lineId, travelPeriod, lineType);
cnd.and("t.unionId", "IS NOT", null);
cnd.and("t.unionId", "<>", "");
Sql sql = Sqls.create("""
SELECT DISTINCT
t.unionId AS id,
COALESCE(NULLIF(u.name, ''), t.unionName) AS name,
u.unionCode
FROM wf_process_task task
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
INNER JOIN tour_ledger t ON t.id = ins.businessNo
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
LEFT JOIN sys_union u ON u.id = t.unionId
$condition
ORDER BY u.unionCode ASC, name ASC
""");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(sql);
return Result.success(sql.getList(NutMap.class));
}
@At
@SaCheckPermission(value = {"tour.schoolUnionApproval", "h5.tour.schoolUnionApproval"}, mode = SaMode.OR)
public Result lineOptions(Boolean audit, Integer startYear, Integer endYear, String travelPeriod, String lineType, String unionId, String keyword) {
Cnd cnd = buildAuditCnd(audit, startYear, endYear, keyword, unionId, null, travelPeriod, lineType);
cnd.and("t.lineName", "IS NOT", null);
cnd.and("t.lineName", "<>", "");
Sql sql = Sqls.create("""
SELECT DISTINCT
CASE
WHEN IFNULL(t.lineId, '') <> '' THEN CONCAT(t.lineId, '|', IFNULL(m.unionId, ''))
ELSE CONCAT('legacy:', t.lineName, '|', IFNULL(m.unionId, ''))
END AS lineId,
CASE
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
ELSE CONCAT(
COALESCE(NULLIF(l.lineName, ''), t.lineName),
'(',
CASE
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
ELSE IFNULL(mu.name, '')
END,
')'
)
END AS lineName
FROM wf_process_task task
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
INNER JOIN tour_ledger t ON t.id = ins.businessNo
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
LEFT JOIN sys_union mu ON mu.id = m.unionId
$condition
ORDER BY lineName ASC
""");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(sql);
return Result.success(sql.getList(NutMap.class));
}
@At
@SaCheckPermission(value = {"tour.schoolUnionApproval", "h5.tour.schoolUnionApproval"}, mode = SaMode.OR)
public Result travelPeriodOptions(Boolean audit, Integer startYear, Integer endYear, String lineId, String lineType, String unionId, String keyword) {
Cnd cnd = buildAuditCnd(audit, startYear, endYear, keyword, unionId, lineId, null, lineType);
cnd.and("m.travelStartTime", "IS NOT", null);
cnd.and("m.travelStartTime", "<>", "");
cnd.and("m.travelEndTime", "IS NOT", null);
cnd.and("m.travelEndTime", "<>", "");
Sql sql = Sqls.create("""
SELECT DISTINCT CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime) AS travelPeriod
FROM wf_process_task task
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
INNER JOIN tour_ledger t ON t.id = ins.businessNo
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
$condition
ORDER BY travelPeriod ASC
""");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(sql);
return Result.success(sql.getList(NutMap.class));
}
@At
@SaCheckPermission(value = {"tour.schoolUnionApproval", "h5.tour.schoolUnionApproval"}, mode = SaMode.OR)
public Result lineTypeOptions(Boolean audit, Integer startYear, Integer endYear, String lineId, String travelPeriod, String unionId, String keyword) {
Cnd cnd = buildAuditCnd(audit, startYear, endYear, keyword, unionId, lineId, travelPeriod, null);
cnd.and("t.lineType", "IS NOT", null);
cnd.and("t.lineType", "<>", "");
Sql sql = Sqls.create("""
SELECT DISTINCT t.lineType
FROM wf_process_task task
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
INNER JOIN tour_ledger t ON t.id = ins.businessNo
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
$condition
ORDER BY t.lineType ASC
""");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(sql);
return Result.success(sql.getList(NutMap.class));
}
private Cnd buildAuditCnd(Boolean audit, Integer startYear, Integer endYear, String keyword, String unionId, String lineId, String travelPeriod, String lineType) {
Cnd cnd = Cnd.NEW();
cnd.and("t.delFlag", "=", false);
cnd.and("def.name", "=", WORKFLOW_KEY);
cnd.and("task.displayName", "=", TASK_DISPLAY_NAME);
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
appendAuditStateFilter(cnd, audit);
cnd.andEX("t.`year`", ">=", startYear);
cnd.andEX("t.`year`", "<=", endYear == null ? LocalDate.now().getYear() : endYear);
cnd.andEX("t.unionId", "=", unionId);
cnd.andEX("t.lineType", "=", lineType);
appendLineFilter(cnd, lineId);
appendTravelPeriodFilter(cnd, travelPeriod);
appendKeywordFilter(cnd, keyword);
return cnd;
}
private void appendAuditStateFilter(Cnd cnd, Boolean audit) {
if (Boolean.TRUE.equals(audit)) {
cnd.and("task.taskState", "in", List.of(
ProcessTaskStateEnum.FINISHED.getCode(),
ProcessTaskStateEnum.WITHDRAW.getCode(),
ProcessTaskStateEnum.INTERRUPT.getCode()));
return;
}
cnd.and("task.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
}
private void appendKeywordFilter(Cnd cnd, String keyword) {
if (StrUtil.isBlank(keyword)) {
return;
}
SqlExpressionGroup group = new SqlExpressionGroup();
group.orLike("t.userName", keyword.trim());
group.orLike("t.jobNo", keyword.trim());
group.orLike("t.lineName", keyword.trim());
group.orLike("l.lineName", keyword.trim());
cnd.and(group);
}
private void appendTravelPeriodFilter(Cnd cnd, String travelPeriod) {
if (StrUtil.isBlank(travelPeriod)) {
return;
}
cnd.and("CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)", "=", travelPeriod);
}
private void appendLineFilter(Cnd cnd, String lineId) {
if (StrUtil.isBlank(lineId)) {
return;
}
LineFilter lineFilter = parseLineFilter(lineId);
if (lineFilter.getLineId().startsWith("legacy:")) {
SqlExpressionGroup legacyGroup = new SqlExpressionGroup();
legacyGroup.or("t.lineId", "IS", null);
legacyGroup.or("t.lineId", "=", "");
cnd.and(legacyGroup);
cnd.and("t.lineName", "=", lineFilter.getLineId().substring("legacy:".length()));
} else {
cnd.and("t.lineId", "=", lineFilter.getLineId());
}
appendMatterUnionFilter(cnd, lineFilter.getUnionId());
}
private LineFilter parseLineFilter(String value) {
int delimiterIndex = value.indexOf("|");
if (delimiterIndex < 0) {
return new LineFilter(value, null);
}
return new LineFilter(value.substring(0, delimiterIndex), value.substring(delimiterIndex + 1));
}
private void appendMatterUnionFilter(Cnd cnd, String unionId) {
if (unionId == null) {
return;
}
if (StrUtil.isBlank(unionId)) {
SqlExpressionGroup group = new SqlExpressionGroup();
group.or("m.unionId", "IS", null);
group.or("m.unionId", "=", "");
cnd.and(group);
return;
}
cnd.and("m.unionId", "=", unionId);
}
private String getOrderColumn(String orderName) {
if ("jobNo".equals(orderName)) {
return "t.jobNo";
}
if ("userName".equals(orderName)) {
return "t.userName";
}
if ("lineName".equals(orderName)) {
return """
CASE
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
ELSE CONCAT(
COALESCE(NULLIF(l.lineName, ''), t.lineName),
'(',
CASE
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
ELSE IFNULL(mu.name, '')
END,
')'
)
END
""";
}
if ("travelPeriod".equals(orderName)) {
return "COALESCE(m.travelStartTime, tp.travelPeriod)";
}
if ("lineType".equals(orderName)) {
return "t.lineType";
}
if ("signupTime".equals(orderName)) {
return "t.signupTime";
}
if ("familyCount".equals(orderName)) {
return "familyCount";
}
if ("instanceState".equals(orderName)) {
return "ins.state";
}
return "task.createdAt";
}
private TourLedger fetchAuditLedger(String id) {
if (StrUtil.isBlank(id)) {
return null;
}
Sql sql = Sqls.create("""
SELECT COUNT(1)
FROM wf_process_task task
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
INNER JOIN tour_ledger t ON t.id = ins.businessNo
WHERE t.delFlag = 0
AND t.id = @id
AND def.name = @workflowKey
AND task.displayName = @taskDisplayName
AND ta.actorId = @actorId
""");
sql.setParam("id", id);
sql.setParam("workflowKey", WORKFLOW_KEY);
sql.setParam("taskDisplayName", TASK_DISPLAY_NAME);
sql.setParam("actorId", SecurityUtil.getUserId());
sql.setCallback(Sqls.callback.integer());
tourLedgerService.dao().execute(sql);
return sql.getInt() > 0 ? tourLedgerService.fetch(id) : null;
}
private String getCurrentLineName(TourLedger ledger) {
if (ledger == null) {
return "";
}
Sql sql = Sqls.create("""
SELECT CASE
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
ELSE CONCAT(
COALESCE(NULLIF(l.lineName, ''), t.lineName),
'(',
CASE
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
ELSE IFNULL(mu.name, '')
END,
')'
)
END AS lineName
FROM tour_ledger t
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
LEFT JOIN sys_union mu ON mu.id = m.unionId
WHERE t.id = @ledgerId
""");
sql.setParam("ledgerId", ledger.getId());
sql.setCallback(Sqls.callback.str());
tourLedgerService.dao().execute(sql);
return StrUtil.blankToDefault(sql.getString(), ledger.getLineName());
}
private String getTravelPeriod(TourLedger ledger) {
if (ledger == null || ledger.getYear() == null || StrUtil.isBlank(ledger.getLineId())) {
return "";
}
if (StrUtil.isNotBlank(ledger.getMatterId())) {
Sql exactSql = Sqls.create("""
SELECT CASE
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
ELSE ''
END AS travelPeriod
FROM tour_matter
WHERE delFlag = 0
AND id = @matterId
""");
exactSql.setParam("matterId", ledger.getMatterId());
exactSql.setCallback(Sqls.callback.str());
tourLedgerService.dao().execute(exactSql);
return exactSql.getString();
}
Sql sql = Sqls.create("""
SELECT GROUP_CONCAT(
DISTINCT CASE
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
ELSE NULL
END
ORDER BY travelStartTime ASC
SEPARATOR ';'
) AS travelPeriod
FROM tour_matter
WHERE delFlag = 0
AND `year` = @year
AND lineId = @lineId
""");
sql.setParam("year", ledger.getYear());
sql.setParam("lineId", ledger.getLineId());
sql.setCallback(Sqls.callback.str());
tourLedgerService.dao().execute(sql);
return sql.getString();
}
private NutMap getSignupConfig(TourLedger ledger) {
if (ledger == null || StrUtil.isBlank(ledger.getLineId())) {
return NutMap.NEW().addv("allowFamily", false).addv("fillBedInfo", true).addv("directFamilyUnitLine", false);
}
Sql sql = Sqls.create("""
SELECT
IFNULL(s.allowFamily, 0) AS allowFamily,
IFNULL(s.fillBedInfo, 1) AS fillBedInfo,
IFNULL(l.directFamilyUnitLine, 0) AS directFamilyUnitLine
FROM tour_ledger t
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_setting s ON s.id = m.settingId AND s.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
WHERE t.id = @ledgerId
""");
sql.setParam("ledgerId", ledger.getId());
sql.setCallback(Sqls.callback.map());
tourLedgerService.dao().execute(sql);
NutMap map = sql.getObject(NutMap.class);
if (map == null || map.isEmpty()) {
return NutMap.NEW().addv("allowFamily", false).addv("fillBedInfo", true).addv("directFamilyUnitLine", false);
}
return map;
}
private static class LineFilter {
private final String lineId;
private final String unionId;
private LineFilter(String lineId, String unionId) {
this.lineId = lineId;
this.unionId = unionId;
}
private String getLineId() {
return lineId;
}
private String getUnionId() {
return unionId;
}
}
}
@@ -0,0 +1,246 @@
package com.budwk.app.zhgh.dayofficework.tour.controller;
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.zhgh.dayofficework.tour.models.TourSetting;
import com.budwk.app.zhgh.dayofficework.tour.models.TourSettingLot;
import com.budwk.app.zhgh.dayofficework.tour.service.TourSettingService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.ioc.aop.Aop;
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.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@IocBean
@Ok("json:full")
@At("/platform/tour/setting")
public class TourSettingController {
@Inject
private TourSettingService tourSettingService;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/Tour/setting/index.html")
@SaCheckPermission("tour.setting")
public void index() {
}
@At
@SaCheckPermission("tour.setting")
public Result pageData(PageForm pageForm, Integer year, String configName) {
Cnd cnd = Cnd.NEW();
cnd.andEX(TourSetting::getYear, "=", year);
cnd.and(Cnd.likeEX(TourSetting::getConfigName, configName));
cnd.desc(TourSetting::getYear).asc(TourSetting::getSortNo).desc(TourSetting::getCreatedAt);
Pagination<TourSetting> pagination = tourSettingService.listPage(
pageForm.getPageNumber(),
pageForm.getPageSize(),
TourSetting.class,
cnd
);
return Result.success(pagination);
}
@At
@SaCheckPermission("tour.setting")
public Result detail(String id) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
TourSetting tourSetting = tourSettingService.fetch(id);
if (tourSetting == null) {
return Result.error("配置不存在");
}
// 编辑页面需要一起带出标段,按标段值倒序保持与老疗休养配置一致。
Cnd lotCnd = Cnd.NEW();
lotCnd.desc(TourSettingLot::getLotValue);
tourSettingService.dao().fetchLinks(tourSetting, "lots", lotCnd);
return Result.success(tourSetting);
}
@At
@SaCheckPermission("tour.setting")
public Result previousYearInfo(Integer year) {
if (year == null) {
return Result.error("请先选择年度");
}
List<TourSetting> settings = tourSettingService.query(Cnd.where(TourSetting::getYear, "=", year - 1)
.and(TourSetting::getDelFlag, "=", false)
.desc(TourSetting::getUpdatedAt)
.desc(TourSetting::getCreatedAt));
if (Lang.isEmpty(settings)) {
return Result.error("未找到上一年度配置");
}
TourSetting tourSetting = settings.get(0);
Cnd lotCnd = Cnd.NEW();
lotCnd.desc(TourSettingLot::getLotValue);
tourSettingService.dao().fetchLinks(tourSetting, "lots", lotCnd);
return Result.success(tourSetting);
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tour.setting")
@SLog(type = "tour", tag = "疗休养设置", msg = "保存疗休养配置")
public Result doSubmit(TourSetting tourSetting,
@Param(value = "lots") String lots,
@Param(value = "lotDeleteList") String[] lotDeleteList) {
Result checkResult = check(tourSetting);
if (checkResult != null) {
return checkResult;
}
Cnd sameNameCnd = Cnd.where(TourSetting::getYear, "=", tourSetting.getYear())
.and(TourSetting::getConfigName, "=", tourSetting.getConfigName());
if (StrUtil.isNotBlank(tourSetting.getId())) {
sameNameCnd.and(TourSetting::getId, "<>", tourSetting.getId());
}
if (tourSettingService.count(sameNameCnd) > 0) {
return Result.error("同年度下配置名称已存在");
}
// 布尔值给默认值,避免前端未传时出现空状态。
if (tourSetting.getEnabled() == null) {
tourSetting.setEnabled(true);
}
if (tourSetting.getAllowFamily() == null) {
tourSetting.setAllowFamily(false);
}
if (tourSetting.getFillBedInfo() == null) {
tourSetting.setFillBedInfo(true);
}
if (StrUtil.isBlank(tourSetting.getOutProvinceRatioType())) {
tourSetting.setOutProvinceRatioType("当年报名人数");
}
if (!"固定人数".equals(tourSetting.getOutProvinceRatioType())) {
tourSetting.setOutProvinceFixedPeople(0);
}
// 前端按项目既有约定把子表数组序列化提交,这里显式解析,避免自动绑定漏掉标段。
if (StrUtil.isNotBlank(lots)) {
try {
tourSetting.setLots(Json.fromJsonAsList(TourSettingLot.class, lots));
} catch (Exception e) {
return Result.error("标段值和标段费用必须为整数");
}
}
tourSetting.setLots(normalizeLots(tourSetting));
Result lotCheckResult = checkLots(tourSetting.getLots());
if (lotCheckResult != null) {
return lotCheckResult;
}
if (StrUtil.isBlank(tourSetting.getId())) {
if (Lang.isEmpty(tourSetting.getLots())) {
tourSettingService.insert(tourSetting);
} else {
tourSettingService.insertWith(tourSetting, "lots");
}
} else {
// 编辑时先处理页面删除的标段,再保存配置和当前标段行。
if (Lang.isNotEmpty(lotDeleteList)) {
tourSettingService.dao().clear(TourSettingLot.class, Cnd.where(TourSettingLot::getId, "in", lotDeleteList));
}
tourSettingService.updateIgnoreNull(tourSetting);
saveLots(tourSetting);
}
return Result.success();
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tour.setting")
@SLog(type = "tour", tag = "疗休养设置", msg = "删除疗休养配置")
public Result doDelete(String id) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
tourSettingService.dao().clear(TourSettingLot.class, Cnd.where(TourSettingLot::getSettingId, "=", id));
tourSettingService.delete(id);
return Result.success();
}
private List<TourSettingLot> normalizeLots(TourSetting tourSetting) {
if (tourSetting == null || Lang.isEmpty(tourSetting.getLots())) {
return Collections.emptyList();
}
// 过滤前端空行,避免误写入只有ID或空字段的标段记录。
return tourSetting.getLots().stream()
.filter(item -> item != null
&& (StrUtil.isNotBlank(item.getLotName())
|| StrUtil.isNotBlank(item.getLotValue())
|| item.getActivityCost() != null))
.peek(item -> item.setSettingId(tourSetting.getId()))
.collect(Collectors.toList());
}
private void saveLots(TourSetting tourSetting) {
List<TourSettingLot> lots = tourSetting.getLots();
if (Lang.isEmpty(lots)) {
return;
}
lots.forEach(item -> item.setSettingId(tourSetting.getId()));
tourSettingService.dao().insertOrUpdate(lots);
}
private Result check(TourSetting tourSetting) {
if (tourSetting == null) {
return Result.error("参数错误");
}
if (tourSetting.getYear() == null) {
return Result.error("年度不能为空");
}
if (StrUtil.isBlank(tourSetting.getConfigName())) {
return Result.error("配置名称不能为空");
}
if (tourSetting.getMinGroupPeople() != null && tourSetting.getMaxGroupPeople() != null
&& tourSetting.getMinGroupPeople() > tourSetting.getMaxGroupPeople()) {
return Result.error("最少成团人数不能大于最多成团人数");
}
if (tourSetting.getCycleStartYear() != null && tourSetting.getCycleEndYear() != null
&& tourSetting.getCycleStartYear() > tourSetting.getCycleEndYear()) {
return Result.error("周期开始年度不能大于周期结束年度");
}
if (StrUtil.isNotBlank(tourSetting.getOutProvinceRatioType())
&& !"当年参加人数".equals(tourSetting.getOutProvinceRatioType())
&& !"当年报名人数".equals(tourSetting.getOutProvinceRatioType())
&& !"可参加教职工人数".equals(tourSetting.getOutProvinceRatioType())
&& !"固定人数".equals(tourSetting.getOutProvinceRatioType())) {
return Result.error("省外人数占比类型不正确");
}
if ("固定人数".equals(tourSetting.getOutProvinceRatioType())
&& (tourSetting.getOutProvinceFixedPeople() == null || tourSetting.getOutProvinceFixedPeople() < 0)) {
return Result.error("固定人数必须大于等于0");
}
return null;
}
private Result checkLots(List<TourSettingLot> lots) {
if (Lang.isEmpty(lots)) {
return null;
}
for (int i = 0; i < lots.size(); i++) {
TourSettingLot lot = lots.get(i);
String rowNo = "第" + (i + 1) + "行";
if (StrUtil.isBlank(lot.getLotValue()) || !lot.getLotValue().matches("^\\d+$")) {
return Result.error(rowNo + "标段值必须为整数");
}
if (lot.getActivityCost() == null || lot.getActivityCost() < 0) {
return Result.error(rowNo + "标段费用必须为整数");
}
}
return null;
}
}
@@ -0,0 +1,160 @@
package com.budwk.app.zhgh.dayofficework.tour.controller;
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.zhgh.dayofficework.tour.models.TourTravelAgency;
import com.budwk.app.zhgh.dayofficework.tour.service.TourTravelAgencyService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
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;
@IocBean
@Ok("json:full")
@At("/platform/tour/travelAgency")
public class TourTravelAgencyController {
private static final String MOBILE_PATTERN = "^1[3-9]\\d{9}$";
private static final String EMAIL_PATTERN = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$";
@Inject
private TourTravelAgencyService travelAgencyService;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/Tour/travelAgency/index.html")
@SaCheckPermission("tour.travelAgency")
public void index() {
}
@At
@SaCheckPermission("tour.travelAgency")
public Result pageData(PageForm pageForm, Integer year, String agencyName, String contactName, String contactPhone) {
Cnd cnd = Cnd.NEW();
cnd.andEX(TourTravelAgency::getYear, "=", year);
cnd.and(Cnd.likeEX(TourTravelAgency::getAgencyName, agencyName));
cnd.and(Cnd.likeEX(TourTravelAgency::getContactName, contactName));
cnd.and(Cnd.likeEX(TourTravelAgency::getContactPhone, contactPhone));
applyOrder(cnd, pageForm);
Pagination<TourTravelAgency> pagination = travelAgencyService.listPage(
pageForm.getPageNumber(),
pageForm.getPageSize(),
TourTravelAgency.class,
cnd
);
return Result.success(pagination);
}
@At
@SaCheckPermission("tour.travelAgency")
public Result detail(String id) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
TourTravelAgency agency = travelAgencyService.fetch(id);
return agency == null ? Result.error("旅行社不存在") : Result.success(agency);
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tour.travelAgency")
@SLog(type = "tour", tag = "旅行社管理", msg = "保存旅行社信息")
public Result doSubmit(TourTravelAgency agency) {
Result checkResult = check(agency);
if (checkResult != null) {
return checkResult;
}
Cnd sameCodeCnd = Cnd.where(TourTravelAgency::getYear, "=", agency.getYear())
.and(TourTravelAgency::getAgencyCode, "=", agency.getAgencyCode());
if (StrUtil.isNotBlank(agency.getId())) {
sameCodeCnd.and(TourTravelAgency::getId, "<>", agency.getId());
}
if (travelAgencyService.count(sameCodeCnd) > 0) {
return Result.error("同年度下旅行社编号已存在");
}
if (agency.getEnabled() == null) {
agency.setEnabled(true);
}
if (StrUtil.isBlank(agency.getId())) {
travelAgencyService.insert(agency);
} else {
travelAgencyService.updateIgnoreNull(agency);
}
return Result.success();
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tour.travelAgency")
@SLog(type = "tour", tag = "旅行社管理", msg = "删除旅行社信息")
public Result doDelete(String id) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
travelAgencyService.delete(id);
return Result.success();
}
private Result check(TourTravelAgency agency) {
if (agency == null) {
return Result.error("参数错误");
}
if (agency.getYear() == null) {
return Result.error("年度不能为空");
}
if (StrUtil.isBlank(agency.getAgencyName())) {
return Result.error("旅行社名称不能为空");
}
if (StrUtil.isBlank(agency.getAgencyCode())) {
return Result.error("旅行社编号不能为空");
}
if (StrUtil.isBlank(agency.getContactName())) {
return Result.error("联系人不能为空");
}
if (StrUtil.isBlank(agency.getContactPhone())) {
return Result.error("联系人手机不能为空");
}
if (!agency.getContactPhone().matches(MOBILE_PATTERN)) {
return Result.error("联系人手机格式不正确");
}
if (StrUtil.isBlank(agency.getEmail())) {
return Result.error("邮箱不能为空");
}
if (!agency.getEmail().matches(EMAIL_PATTERN)) {
return Result.error("邮箱格式不正确");
}
return null;
}
private void applyOrder(Cnd cnd, PageForm pageForm) {
String orderName = pageForm.getPageOrderName();
String orderBy = pageForm.getPageOrderBy();
if (StrUtil.isBlank(orderName)) {
cnd.asc(TourTravelAgency::getYear).asc(TourTravelAgency::getAgencyCode).desc(TourTravelAgency::getCreatedAt);
return;
}
boolean descending = "descending".equals(orderBy);
if ("year".equals(orderName)) {
if (descending) {
cnd.desc(TourTravelAgency::getYear);
} else {
cnd.asc(TourTravelAgency::getYear);
}
} else if ("agencyCode".equals(orderName)) {
if (descending) {
cnd.desc(TourTravelAgency::getAgencyCode);
} else {
cnd.asc(TourTravelAgency::getAgencyCode);
}
} else {
cnd.asc(TourTravelAgency::getYear).asc(TourTravelAgency::getAgencyCode).desc(TourTravelAgency::getCreatedAt);
}
}
}
@@ -0,0 +1,581 @@
package com.budwk.app.zhgh.dayofficework.tour.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.util.StrUtil;
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.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerDirectRelative;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerFamily;
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerDirectRelativeService;
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerFamilyService;
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService;
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.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 java.time.LocalDate;
import java.util.List;
@IocBean
@Ok("json:full")
@At("/platform/tour/unionApproval")
public class TourUnionApprovalController {
private static final String WORKFLOW_KEY = "LXYBZXQSXL";
private static final String TASK_DISPLAY_NAME = "分工会审核";
@Inject
private TourLedgerService tourLedgerService;
@Inject
private TourLedgerFamilyService tourLedgerFamilyService;
@Inject
private TourLedgerDirectRelativeService tourLedgerDirectRelativeService;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/Tour/unionApproval/index.html")
@SaCheckPermission(value = {"tour.unionApproval", "h5.tour.unionApproval"}, mode = SaMode.OR)
public void index() {
}
@At("/h5")
@Ok("beetl:/platform/zhghh5/dayofficework/tour/unionApproval/index.html")
@SaCheckPermission(value = {"tour.unionApproval", "h5.tour.unionApproval"}, mode = SaMode.OR)
public void h5() {
}
@At
@SaCheckPermission(value = {"tour.unionApproval", "h5.tour.unionApproval"}, mode = SaMode.OR)
public Result pageData(PageForm pageForm, Boolean audit, Integer startYear, Integer endYear, String keyword, String unionId, String lineId, String travelPeriod, String lineType) {
Cnd cnd = buildAuditCnd(audit, startYear, endYear, keyword, unionId, lineId, travelPeriod, lineType);
Sql countSql = Sqls.create("""
SELECT COUNT(DISTINCT task.id)
FROM wf_process_task task
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
INNER JOIN tour_ledger t ON t.id = ins.businessNo
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
$condition
""");
countSql.setCondition(cnd);
countSql.setCallback(Sqls.callback.integer());
tourLedgerService.dao().execute(countSql);
int count = countSql.getInt();
Sql listSql = Sqls.create("""
SELECT
t.*,
ins.id AS instanceId,
ins.businessNo,
ins.state AS instanceState,
ins.variable AS instanceVariable,
ins.processDefineId AS instanceProcessDefineId,
task.id AS taskId,
task.taskName AS taskKey,
task.displayName AS taskName,
task.taskType,
task.performType AS taskPerformType,
task.taskState,
task.finishTime,
task.taskParentId,
task.variable AS taskVariable,
IFNULL(GROUP_CONCAT(DISTINCT nt.displayName), '结束') AS curTaskName,
CASE
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
ELSE CONCAT(
COALESCE(NULLIF(l.lineName, ''), t.lineName),
'(',
CASE
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
ELSE IFNULL(mu.name, '')
END,
')'
)
END AS currentLineName,
CASE
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
ELSE tp.travelPeriod
END AS travelPeriod,
IFNULL(f.familyCount, 0) AS familyCount
FROM wf_process_task task
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
INNER JOIN tour_ledger t ON t.id = ins.businessNo
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
LEFT JOIN (
SELECT ledgerId, COUNT(1) AS familyCount
FROM tour_ledger_family
WHERE delFlag = 0
GROUP BY ledgerId
) f ON f.ledgerId = t.id
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
LEFT JOIN sys_union mu ON mu.id = m.unionId
LEFT JOIN (
SELECT
`year`,
lineId,
GROUP_CONCAT(
DISTINCT CASE
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
ELSE NULL
END
ORDER BY travelStartTime ASC
SEPARATOR ';'
) AS travelPeriod
FROM tour_matter
WHERE delFlag = 0
AND lineId IS NOT NULL
AND lineId <> ''
GROUP BY `year`, lineId
) tp ON (t.matterId IS NULL OR t.matterId = '') AND tp.`year` = t.`year` AND tp.lineId = t.lineId
$condition
GROUP BY task.id
ORDER BY $orderColumn $orderBy, task.createdAt DESC, t.signupTime DESC, t.createdAt DESC
""");
listSql.setCondition(cnd);
listSql.setVar("orderColumn", getOrderColumn(pageForm.getPageOrderName()));
listSql.setVar("orderBy", "ascending".equals(pageForm.getPageOrderBy()) ? "asc" : "desc");
listSql.setPager(tourLedgerService.dao().createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
listSql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(listSql);
List<NutMap> list = listSql.getList(NutMap.class);
list.forEach(item -> item.put("lineName", item.getString("currentLineName")));
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list);
return Result.success(pagination);
}
@At
@SaCheckPermission(value = {"tour.unionApproval", "h5.tour.unionApproval"}, mode = SaMode.OR)
public Result detail(String id) {
TourLedger ledger = fetchAuditLedger(id);
if (ledger == null) {
return Result.error("报名记录不存在或无权查看");
}
ledger.setLineName(getCurrentLineName(ledger));
Cnd familyCnd = Cnd.where(TourLedgerFamily::getLedgerId, "=", ledger.getId())
.and(TourLedgerFamily::getDelFlag, "=", false);
familyCnd.asc(TourLedgerFamily::getCreatedAt);
TourLedgerDirectRelative directRelative = tourLedgerDirectRelativeService.fetch(
Cnd.where(TourLedgerDirectRelative::getLedgerId, "=", ledger.getId())
.and(TourLedgerDirectRelative::getDelFlag, "=", false));
NutMap signupConfig = getSignupConfig(ledger);
return Result.success(NutMap.NEW()
.addv("ledger", ledger)
.addv("travelPeriod", getTravelPeriod(ledger))
.addv("families", tourLedgerFamilyService.query(familyCnd))
.addv("directRelative", directRelative)
.addv("allowFamily", signupConfig.getBoolean("allowFamily", false))
.addv("fillBedInfo", signupConfig.getBoolean("fillBedInfo", true))
.addv("directFamilyUnitLine", signupConfig.getBoolean("directFamilyUnitLine", false)));
}
@At
@SaCheckPermission(value = {"tour.unionApproval", "h5.tour.unionApproval"}, mode = SaMode.OR)
public Result unionOptions(Boolean audit, Integer startYear, Integer endYear, String keyword, String lineId, String travelPeriod, String lineType) {
Cnd cnd = buildAuditCnd(audit, startYear, endYear, keyword, null, lineId, travelPeriod, lineType);
cnd.and("t.unionId", "IS NOT", null);
cnd.and("t.unionId", "<>", "");
Sql sql = Sqls.create("""
SELECT DISTINCT
t.unionId AS id,
COALESCE(NULLIF(u.name, ''), t.unionName) AS name,
u.unionCode
FROM wf_process_task task
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
INNER JOIN tour_ledger t ON t.id = ins.businessNo
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
LEFT JOIN sys_union u ON u.id = t.unionId
$condition
ORDER BY u.unionCode ASC, name ASC
""");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(sql);
return Result.success(sql.getList(NutMap.class));
}
@At
@SaCheckPermission(value = {"tour.unionApproval", "h5.tour.unionApproval"}, mode = SaMode.OR)
public Result lineOptions(Boolean audit, Integer startYear, Integer endYear, String travelPeriod, String lineType, String unionId, String keyword) {
Cnd cnd = buildAuditCnd(audit, startYear, endYear, keyword, unionId, null, travelPeriod, lineType);
cnd.and("t.lineName", "IS NOT", null);
cnd.and("t.lineName", "<>", "");
Sql sql = Sqls.create("""
SELECT DISTINCT
CASE
WHEN IFNULL(t.lineId, '') <> '' THEN CONCAT(t.lineId, '|', IFNULL(m.unionId, ''))
ELSE CONCAT('legacy:', t.lineName, '|', IFNULL(m.unionId, ''))
END AS lineId,
CASE
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
ELSE CONCAT(
COALESCE(NULLIF(l.lineName, ''), t.lineName),
'(',
CASE
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
ELSE IFNULL(mu.name, '')
END,
')'
)
END AS lineName
FROM wf_process_task task
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
INNER JOIN tour_ledger t ON t.id = ins.businessNo
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
LEFT JOIN sys_union mu ON mu.id = m.unionId
$condition
ORDER BY lineName ASC
""");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(sql);
return Result.success(sql.getList(NutMap.class));
}
@At
@SaCheckPermission(value = {"tour.unionApproval", "h5.tour.unionApproval"}, mode = SaMode.OR)
public Result travelPeriodOptions(Boolean audit, Integer startYear, Integer endYear, String lineId, String lineType, String unionId, String keyword) {
Cnd cnd = buildAuditCnd(audit, startYear, endYear, keyword, unionId, lineId, null, lineType);
cnd.and("m.travelStartTime", "IS NOT", null);
cnd.and("m.travelStartTime", "<>", "");
cnd.and("m.travelEndTime", "IS NOT", null);
cnd.and("m.travelEndTime", "<>", "");
Sql sql = Sqls.create("""
SELECT DISTINCT CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime) AS travelPeriod
FROM wf_process_task task
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
INNER JOIN tour_ledger t ON t.id = ins.businessNo
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
$condition
ORDER BY travelPeriod ASC
""");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(sql);
return Result.success(sql.getList(NutMap.class));
}
@At
@SaCheckPermission(value = {"tour.unionApproval", "h5.tour.unionApproval"}, mode = SaMode.OR)
public Result lineTypeOptions(Boolean audit, Integer startYear, Integer endYear, String lineId, String travelPeriod, String unionId, String keyword) {
Cnd cnd = buildAuditCnd(audit, startYear, endYear, keyword, unionId, lineId, travelPeriod, null);
cnd.and("t.lineType", "IS NOT", null);
cnd.and("t.lineType", "<>", "");
Sql sql = Sqls.create("""
SELECT DISTINCT t.lineType
FROM wf_process_task task
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
INNER JOIN tour_ledger t ON t.id = ins.businessNo
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
$condition
ORDER BY t.lineType ASC
""");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(sql);
return Result.success(sql.getList(NutMap.class));
}
private Cnd buildAuditCnd(Boolean audit, Integer startYear, Integer endYear, String keyword, String unionId, String lineId, String travelPeriod, String lineType) {
Cnd cnd = Cnd.NEW();
cnd.and("t.delFlag", "=", false);
cnd.and("def.name", "=", WORKFLOW_KEY);
cnd.and("task.displayName", "=", TASK_DISPLAY_NAME);
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
appendAuditStateFilter(cnd, audit);
cnd.andEX("t.`year`", ">=", startYear);
cnd.andEX("t.`year`", "<=", endYear == null ? LocalDate.now().getYear() : endYear);
cnd.andEX("t.unionId", "=", unionId);
cnd.andEX("t.lineType", "=", lineType);
appendLineFilter(cnd, lineId);
appendTravelPeriodFilter(cnd, travelPeriod);
appendKeywordFilter(cnd, keyword);
return cnd;
}
private void appendAuditStateFilter(Cnd cnd, Boolean audit) {
if (Boolean.TRUE.equals(audit)) {
cnd.and("task.taskState", "in", List.of(
ProcessTaskStateEnum.FINISHED.getCode(),
ProcessTaskStateEnum.WITHDRAW.getCode(),
ProcessTaskStateEnum.INTERRUPT.getCode()));
return;
}
cnd.and("task.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
}
private void appendKeywordFilter(Cnd cnd, String keyword) {
if (StrUtil.isBlank(keyword)) {
return;
}
SqlExpressionGroup group = new SqlExpressionGroup();
group.orLike("t.userName", keyword.trim());
group.orLike("t.jobNo", keyword.trim());
group.orLike("t.lineName", keyword.trim());
group.orLike("l.lineName", keyword.trim());
cnd.and(group);
}
private void appendTravelPeriodFilter(Cnd cnd, String travelPeriod) {
if (StrUtil.isBlank(travelPeriod)) {
return;
}
cnd.and("CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)", "=", travelPeriod);
}
private void appendLineFilter(Cnd cnd, String lineId) {
if (StrUtil.isBlank(lineId)) {
return;
}
LineFilter lineFilter = parseLineFilter(lineId);
if (lineFilter.getLineId().startsWith("legacy:")) {
SqlExpressionGroup legacyGroup = new SqlExpressionGroup();
legacyGroup.or("t.lineId", "IS", null);
legacyGroup.or("t.lineId", "=", "");
cnd.and(legacyGroup);
cnd.and("t.lineName", "=", lineFilter.getLineId().substring("legacy:".length()));
} else {
cnd.and("t.lineId", "=", lineFilter.getLineId());
}
appendMatterUnionFilter(cnd, lineFilter.getUnionId());
}
private LineFilter parseLineFilter(String value) {
int delimiterIndex = value.indexOf("|");
if (delimiterIndex < 0) {
return new LineFilter(value, null);
}
return new LineFilter(value.substring(0, delimiterIndex), value.substring(delimiterIndex + 1));
}
private void appendMatterUnionFilter(Cnd cnd, String unionId) {
if (unionId == null) {
return;
}
if (StrUtil.isBlank(unionId)) {
SqlExpressionGroup group = new SqlExpressionGroup();
group.or("m.unionId", "IS", null);
group.or("m.unionId", "=", "");
cnd.and(group);
return;
}
cnd.and("m.unionId", "=", unionId);
}
private String getOrderColumn(String orderName) {
if ("jobNo".equals(orderName)) {
return "t.jobNo";
}
if ("userName".equals(orderName)) {
return "t.userName";
}
if ("lineName".equals(orderName)) {
return """
CASE
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
ELSE CONCAT(
COALESCE(NULLIF(l.lineName, ''), t.lineName),
'(',
CASE
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
ELSE IFNULL(mu.name, '')
END,
')'
)
END
""";
}
if ("travelPeriod".equals(orderName)) {
return "COALESCE(m.travelStartTime, tp.travelPeriod)";
}
if ("lineType".equals(orderName)) {
return "t.lineType";
}
if ("signupTime".equals(orderName)) {
return "t.signupTime";
}
if ("familyCount".equals(orderName)) {
return "familyCount";
}
if ("instanceState".equals(orderName)) {
return "ins.state";
}
return "task.createdAt";
}
private TourLedger fetchAuditLedger(String id) {
if (StrUtil.isBlank(id)) {
return null;
}
Sql sql = Sqls.create("""
SELECT COUNT(1)
FROM wf_process_task task
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
INNER JOIN tour_ledger t ON t.id = ins.businessNo
WHERE t.delFlag = 0
AND t.id = @id
AND def.name = @workflowKey
AND task.displayName = @taskDisplayName
AND ta.actorId = @actorId
""");
sql.setParam("id", id);
sql.setParam("workflowKey", WORKFLOW_KEY);
sql.setParam("taskDisplayName", TASK_DISPLAY_NAME);
sql.setParam("actorId", SecurityUtil.getUserId());
sql.setCallback(Sqls.callback.integer());
tourLedgerService.dao().execute(sql);
return sql.getInt() > 0 ? tourLedgerService.fetch(id) : null;
}
private String getCurrentLineName(TourLedger ledger) {
if (ledger == null) {
return "";
}
Sql sql = Sqls.create("""
SELECT CASE
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
ELSE CONCAT(
COALESCE(NULLIF(l.lineName, ''), t.lineName),
'(',
CASE
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
ELSE IFNULL(mu.name, '')
END,
')'
)
END AS lineName
FROM tour_ledger t
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
LEFT JOIN sys_union mu ON mu.id = m.unionId
WHERE t.id = @ledgerId
""");
sql.setParam("ledgerId", ledger.getId());
sql.setCallback(Sqls.callback.str());
tourLedgerService.dao().execute(sql);
return StrUtil.blankToDefault(sql.getString(), ledger.getLineName());
}
private String getTravelPeriod(TourLedger ledger) {
if (ledger == null || ledger.getYear() == null || StrUtil.isBlank(ledger.getLineId())) {
return "";
}
if (StrUtil.isNotBlank(ledger.getMatterId())) {
Sql exactSql = Sqls.create("""
SELECT CASE
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
ELSE ''
END AS travelPeriod
FROM tour_matter
WHERE delFlag = 0
AND id = @matterId
""");
exactSql.setParam("matterId", ledger.getMatterId());
exactSql.setCallback(Sqls.callback.str());
tourLedgerService.dao().execute(exactSql);
return exactSql.getString();
}
Sql sql = Sqls.create("""
SELECT GROUP_CONCAT(
DISTINCT CASE
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
ELSE NULL
END
ORDER BY travelStartTime ASC
SEPARATOR ';'
) AS travelPeriod
FROM tour_matter
WHERE delFlag = 0
AND `year` = @year
AND lineId = @lineId
""");
sql.setParam("year", ledger.getYear());
sql.setParam("lineId", ledger.getLineId());
sql.setCallback(Sqls.callback.str());
tourLedgerService.dao().execute(sql);
return sql.getString();
}
private NutMap getSignupConfig(TourLedger ledger) {
if (ledger == null || StrUtil.isBlank(ledger.getLineId())) {
return NutMap.NEW().addv("allowFamily", false).addv("fillBedInfo", true).addv("directFamilyUnitLine", false);
}
Sql sql = Sqls.create("""
SELECT
IFNULL(s.allowFamily, 0) AS allowFamily,
IFNULL(s.fillBedInfo, 1) AS fillBedInfo,
IFNULL(l.directFamilyUnitLine, 0) AS directFamilyUnitLine
FROM tour_ledger t
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_setting s ON s.id = m.settingId AND s.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
WHERE t.id = @ledgerId
""");
sql.setParam("ledgerId", ledger.getId());
sql.setCallback(Sqls.callback.map());
tourLedgerService.dao().execute(sql);
NutMap map = sql.getObject(NutMap.class);
if (map == null || map.isEmpty()) {
return NutMap.NEW().addv("allowFamily", false).addv("fillBedInfo", true).addv("directFamilyUnitLine", false);
}
return map;
}
private static class LineFilter {
private final String lineId;
private final String unionId;
private LineFilter(String lineId, String unionId) {
this.lineId = lineId;
this.unionId = unionId;
}
private String getLineId() {
return lineId;
}
private String getUnionId() {
return unionId;
}
}
}
@@ -0,0 +1,905 @@
package com.budwk.app.zhgh.dayofficework.tour.controller;
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.CommonDownloadUtil;
import com.budwk.app.sys.models.Sys_union;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerDirectRelative;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerFamily;
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerDirectRelativeService;
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerFamilyService;
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
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 javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.List;
@IocBean
@Ok("json:full")
@At("/platform/tour/unionledger")
public class TourUnionLedgerController {
@Inject
private TourLedgerService tourLedgerService;
@Inject
private TourLedgerFamilyService tourLedgerFamilyService;
@Inject
private TourLedgerDirectRelativeService tourLedgerDirectRelativeService;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/Tour/unionledger/index.html")
@SaCheckPermission("tour.unionledger")
public void index() {
}
@At
@SaCheckPermission("tour.unionledger")
public Result pageData(PageForm pageForm, Integer startYear, Integer endYear, String keyword, String unionId, String lineId, String travelPeriod, String lineType, String scopeType, Boolean overCostOnly) {
String currentUnionId = currentUnionId();
if (StrUtil.isBlank(currentUnionId)) {
return Result.error("当前登录人所属工会不能为空");
}
Cnd cnd = buildQueryCnd(currentUnionId, startYear, endYear, keyword, unionId, lineId, travelPeriod, lineType, scopeType, overCostOnly);
Sql countSql = Sqls.create("""
SELECT COUNT(DISTINCT t.id)
FROM tour_ledger t
LEFT JOIN wf_process_instance ins ON ins.businessNo = t.id
LEFT JOIN wf_process_task task ON task.processInstanceId = ins.id AND task.taskState = 10
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
$condition
""");
countSql.setCondition(cnd);
countSql.setCallback(Sqls.callback.integer());
tourLedgerService.dao().execute(countSql);
int count = countSql.getInt();
Sql listSql = Sqls.create("""
SELECT
t.*,
ins.id AS instanceId,
ins.state AS instanceState,
ins.processDefineId AS instanceProcessDefineId,
IFNULL(l.directFamilyUnitLine, 0) AS directFamilyUnitLine,
IFNULL(GROUP_CONCAT(DISTINCT task.displayName), IF(ins.id IS NULL, '', '结束')) AS curTaskName,
CASE
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
ELSE CONCAT(
COALESCE(NULLIF(l.lineName, ''), t.lineName),
'(',
CASE
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
ELSE IFNULL(mu.name, '')
END,
')'
)
END AS currentLineName,
IFNULL(f.familyCount, 0) AS familyCount,
CASE
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
ELSE tp.travelPeriod
END AS travelPeriod
FROM tour_ledger t
LEFT JOIN wf_process_instance ins ON ins.businessNo = t.id
LEFT JOIN wf_process_task task ON task.processInstanceId = ins.id AND task.taskState = 10
LEFT JOIN (
SELECT ledgerId, COUNT(1) AS familyCount
FROM tour_ledger_family
WHERE delFlag = 0
GROUP BY ledgerId
) f ON f.ledgerId = t.id
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
LEFT JOIN sys_union mu ON mu.id = m.unionId
LEFT JOIN (
SELECT
`year`,
lineId,
GROUP_CONCAT(
DISTINCT CASE
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
ELSE NULL
END
ORDER BY travelStartTime ASC
SEPARATOR ';'
) AS travelPeriod
FROM tour_matter
WHERE delFlag = 0
AND lineId IS NOT NULL
AND lineId <> ''
GROUP BY `year`, lineId
) tp ON (t.matterId IS NULL OR t.matterId = '') AND tp.`year` = t.`year` AND tp.lineId = t.lineId
$condition
GROUP BY t.id
ORDER BY $orderColumn $orderBy, t.`year` DESC, t.signupTime DESC, t.createdAt DESC
""");
listSql.setCondition(cnd);
listSql.setVar("orderColumn", getOrderColumn(pageForm.getPageOrderName()));
listSql.setVar("orderBy", "descending".equals(pageForm.getPageOrderBy()) ? "desc" : "asc");
listSql.setPager(tourLedgerService.dao().createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
listSql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(listSql);
var list = listSql.getList(NutMap.class);
list.forEach(item -> item.put("lineName", item.getString("currentLineName")));
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list);
return Result.success(pagination);
}
@At
@Ok("void")
@SaCheckPermission("tour.unionledger")
public void exportOverCostSummary(PageForm pageForm, Integer startYear, Integer endYear, String keyword, String unionId, String lineId, String travelPeriod, String lineType, String scopeType, HttpServletResponse response) {
String currentUnionId = currentUnionId();
if (StrUtil.isBlank(currentUnionId)) {
return;
}
Cnd cnd = buildQueryCnd(currentUnionId, startYear, endYear, keyword, unionId, lineId, travelPeriod, lineType, scopeType, true);
List<NutMap> list = queryOverCostSummaryList(cnd, pageForm);
Workbook workbook = buildOverCostSummaryWorkbook(currentUnionName(), list);
CommonDownloadUtil.download("5天外超出部分疗休养费用由单位承担申请人员汇总表.xls", workbook, response);
}
@At
@SaCheckPermission("tour.unionledger")
public Result detail(String id) {
TourLedger ledger = fetchScopedLedger(id);
if (ledger == null) {
return Result.error("台账记录不存在或无权查看");
}
ledger.setLineName(getCurrentLineName(ledger));
Cnd familyCnd = Cnd.where(TourLedgerFamily::getLedgerId, "=", id)
.and(TourLedgerFamily::getDelFlag, "=", false);
familyCnd.asc(TourLedgerFamily::getCreatedAt);
TourLedgerDirectRelative directRelative = tourLedgerDirectRelativeService.fetch(
Cnd.where(TourLedgerDirectRelative::getDelFlag, "=", false)
.and(TourLedgerDirectRelative::getLedgerId, "=", ledger.getId()));
return Result.success(NutMap.NEW()
.addv("ledger", ledger)
.addv("travelPeriod", getTravelPeriod(ledger))
.addv("families", tourLedgerFamilyService.query(familyCnd))
.addv("directRelative", directRelative)
.addv("fillBedInfo", isFillBedInfo(ledger))
.addv("directFamilyUnitLine", isDirectFamilyUnitLine(ledger)));
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tour.unionledger")
@SLog(type = "tour", tag = "分工会疗休养台账", msg = "删除分工会疗休养台账")
public Result doDelete(String id) {
TourLedger ledger = fetchScopedLedger(id);
if (ledger == null) {
return Result.error("台账记录不存在或无权删除");
}
tourLedgerFamilyService.clear(Cnd.where(TourLedgerFamily::getLedgerId, "=", ledger.getId()));
tourLedgerService.clear(Cnd.where(TourLedger::getId, "=", ledger.getId()));
return Result.success();
}
@At
@SaCheckPermission("tour.unionledger")
public Result unionOptions(Integer startYear, Integer endYear) {
String currentUnionId = currentUnionId();
if (StrUtil.isBlank(currentUnionId)) {
return Result.error("当前登录人所属工会不能为空");
}
Cnd cnd = Cnd.NEW();
cnd.and("t.delFlag", "=", false);
cnd.andEX("t.`year`", ">=", startYear);
cnd.andEX("t.`year`", "<=", endYear == null ? LocalDate.now().getYear() : endYear);
cnd.and("t.unionId", "IS NOT", null);
cnd.and("t.unionId", "<>", "");
applyUnionLedgerScope(cnd, currentUnionId);
Sql sql = Sqls.create("""
SELECT DISTINCT
t.unionId AS id,
COALESCE(NULLIF(u.name, ''), t.unionName) AS name,
u.unionCode
FROM tour_ledger t
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN sys_union u ON u.id = t.unionId
$condition
ORDER BY
CASE WHEN t.unionId = @currentUnionId THEN 0 ELSE 1 END,
u.unionCode ASC,
name ASC
""");
sql.setCondition(cnd);
sql.setParam("currentUnionId", currentUnionId);
sql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(sql);
return Result.success(sql.getList(NutMap.class));
}
@At
@SaCheckPermission("tour.unionledger")
public Result lineOptions(Integer startYear, Integer endYear, String travelPeriod, String lineType, String unionId, String keyword, String scopeType) {
String currentUnionId = currentUnionId();
if (StrUtil.isBlank(currentUnionId)) {
return Result.error("当前登录人所属工会不能为空");
}
Cnd cnd = Cnd.NEW();
cnd.and("t.delFlag", "=", false);
cnd.andEX("t.`year`", ">=", startYear);
cnd.andEX("t.`year`", "<=", endYear == null ? LocalDate.now().getYear() : endYear);
cnd.and("t.lineName", "IS NOT", null);
cnd.and("t.lineName", "<>", "");
cnd.andEX("t.unionId", "=", unionId);
cnd.andEX("t.lineType", "=", lineType);
appendTravelPeriodFilter(cnd, travelPeriod);
appendScopeFilter(cnd, currentUnionId, scopeType);
appendKeywordFilter(cnd, keyword);
Sql sql = Sqls.create("""
SELECT DISTINCT
CASE
WHEN IFNULL(t.lineId, '') <> '' THEN CONCAT(t.lineId, '|', IFNULL(m.unionId, ''))
ELSE CONCAT('legacy:', t.lineName, '|', IFNULL(m.unionId, ''))
END AS lineId,
CASE
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
ELSE CONCAT(
COALESCE(NULLIF(l.lineName, ''), t.lineName),
'(',
CASE
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
ELSE IFNULL(mu.name, '')
END,
')'
)
END AS lineName
FROM tour_ledger t
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
LEFT JOIN sys_union mu ON mu.id = m.unionId
$condition
ORDER BY lineName ASC
""");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(sql);
return Result.success(sql.getList(NutMap.class));
}
@At
@SaCheckPermission("tour.unionledger")
public Result travelPeriodOptions(Integer startYear, Integer endYear, String lineId, String lineType, String unionId, String keyword, String scopeType) {
String currentUnionId = currentUnionId();
if (StrUtil.isBlank(currentUnionId)) {
return Result.error("当前登录人所属工会不能为空");
}
Cnd cnd = Cnd.NEW();
cnd.and("t.delFlag", "=", false);
cnd.andEX("t.`year`", ">=", startYear);
cnd.andEX("t.`year`", "<=", endYear == null ? LocalDate.now().getYear() : endYear);
cnd.and("m.travelStartTime", "IS NOT", null);
cnd.and("m.travelStartTime", "<>", "");
cnd.and("m.travelEndTime", "IS NOT", null);
cnd.and("m.travelEndTime", "<>", "");
cnd.andEX("t.unionId", "=", unionId);
cnd.andEX("t.lineType", "=", lineType);
appendLineFilter(cnd, lineId);
appendScopeFilter(cnd, currentUnionId, scopeType);
appendKeywordFilter(cnd, keyword);
Sql sql = Sqls.create("""
SELECT DISTINCT CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime) AS travelPeriod
FROM tour_ledger t
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
$condition
ORDER BY travelPeriod ASC
""");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(sql);
return Result.success(sql.getList(NutMap.class));
}
@At
@SaCheckPermission("tour.unionledger")
public Result lineTypeOptions(Integer startYear, Integer endYear, String lineId, String travelPeriod, String unionId, String keyword, String scopeType) {
String currentUnionId = currentUnionId();
if (StrUtil.isBlank(currentUnionId)) {
return Result.error("当前登录人所属工会不能为空");
}
Cnd cnd = Cnd.NEW();
cnd.and("t.delFlag", "=", false);
cnd.andEX("t.`year`", ">=", startYear);
cnd.andEX("t.`year`", "<=", endYear == null ? LocalDate.now().getYear() : endYear);
cnd.and("t.lineType", "IS NOT", null);
cnd.and("t.lineType", "<>", "");
cnd.andEX("t.unionId", "=", unionId);
appendLineFilter(cnd, lineId);
appendTravelPeriodFilter(cnd, travelPeriod);
appendScopeFilter(cnd, currentUnionId, scopeType);
appendKeywordFilter(cnd, keyword);
Sql sql = Sqls.create("""
SELECT DISTINCT t.lineType
FROM tour_ledger t
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
$condition
ORDER BY t.lineType ASC
""");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(sql);
return Result.success(sql.getList(NutMap.class));
}
@At
@SaCheckPermission("tour.unionledger")
public Result summaryStats(Integer startYear, Integer endYear, String unionId, String lineId, String travelPeriod, String lineType) {
String currentUnionId = currentUnionId();
if (StrUtil.isBlank(currentUnionId)) {
return Result.error("当前登录人所属工会不能为空");
}
Cnd ownCnd = buildSummaryCnd(currentUnionId, startYear, endYear, unionId, lineId, travelPeriod, lineType, "ownUnionJoined");
Cnd organizedCnd = buildSummaryCnd(currentUnionId, startYear, endYear, unionId, lineId, travelPeriod, lineType, "organizedLineJoined");
Sql ownSql = Sqls.create("""
SELECT COUNT(1)
FROM tour_ledger t
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
$condition
""");
ownSql.setCondition(ownCnd);
ownSql.setCallback(Sqls.callback.integer());
tourLedgerService.dao().execute(ownSql);
Sql organizedSql = Sqls.create("""
SELECT CAST(IFNULL(SUM(1 + IFNULL(f.familyCount, 0)), 0) AS SIGNED)
FROM tour_ledger t
INNER JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN (
SELECT ledgerId, COUNT(1) AS familyCount
FROM tour_ledger_family
WHERE delFlag = 0
GROUP BY ledgerId
) f ON f.ledgerId = t.id
$condition
""");
organizedSql.setCondition(organizedCnd);
organizedSql.setCallback(Sqls.callback.integer());
tourLedgerService.dao().execute(organizedSql);
return Result.success(NutMap.NEW()
.addv("ownUnionJoinedStaffCount", ownSql.getInt())
.addv("organizedLineJoinedTotalCount", organizedSql.getInt()));
}
private List<NutMap> queryOverCostSummaryList(Cnd cnd, PageForm pageForm) {
Sql sql = Sqls.create("""
SELECT
t.id,
t.userName,
t.idCard,
t.unionName,
vu.mobile,
m.travelStartTime,
m.travelEndTime,
m.estimatedCost,
CASE
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
ELSE tp.travelPeriod
END AS travelPeriod,
CASE
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
ELSE CONCAT(
COALESCE(NULLIF(l.lineName, ''), t.lineName),
'(',
CASE
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
ELSE IFNULL(mu.name, '')
END,
')'
)
END AS currentLineName
FROM tour_ledger t
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
LEFT JOIN sys_union mu ON mu.id = m.unionId
LEFT JOIN vw_user vu ON vu.loginname = t.jobNo
LEFT JOIN (
SELECT
`year`,
lineId,
GROUP_CONCAT(
DISTINCT CASE
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
ELSE NULL
END
ORDER BY travelStartTime ASC
SEPARATOR ';'
) AS travelPeriod
FROM tour_matter
WHERE delFlag = 0
AND lineId IS NOT NULL
AND lineId <> ''
GROUP BY `year`, lineId
) tp ON (t.matterId IS NULL OR t.matterId = '') AND tp.`year` = t.`year` AND tp.lineId = t.lineId
$condition
ORDER BY $orderColumn $orderBy, t.`year` DESC, t.signupTime DESC, t.createdAt DESC
""");
sql.setCondition(cnd);
sql.setVar("orderColumn", getOrderColumn(pageForm == null ? null : pageForm.getPageOrderName()));
sql.setVar("orderBy", pageForm != null && "ascending".equals(pageForm.getPageOrderBy()) ? "asc" : "desc");
sql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(sql);
List<NutMap> list = sql.getList(NutMap.class);
for (NutMap item : list) {
item.put("lineName", item.getString("currentLineName"));
item.put("totalDays", calcDays(item.getString("travelStartTime", ""), item.getString("travelEndTime", "")));
item.put("estimatedCostText", formatAmount(item.get("estimatedCost")));
}
return list;
}
private Workbook buildOverCostSummaryWorkbook(String unionName, List<NutMap> list) {
Workbook workbook = new HSSFWorkbook();
Sheet sheet = workbook.createSheet("汇总表");
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 9));
sheet.addMergedRegion(new CellRangeAddress(1, 1, 0, 9));
sheet.addMergedRegion(new CellRangeAddress(2, 2, 0, 9));
double[] widths = {5.14, 14.14, 21.29, 15.71, 27, 40, 50.43, 13.71, 22.57, 15.43};
for (int i = 0; i < widths.length; i++) {
sheet.setColumnWidth(i, (int) (widths[i] * 256));
}
CellStyle attachStyle = createStyle(workbook, "宋体", (short) 12, false, HorizontalAlignment.LEFT, false, false);
CellStyle titleStyle = createStyle(workbook, "黑体", (short) 18, true, HorizontalAlignment.CENTER, false, false);
CellStyle unionStyle = createStyle(workbook, "仿宋_GB2312", (short) 14, true, HorizontalAlignment.LEFT, false, false);
unionStyle.setBorderBottom(BorderStyle.THIN);
CellStyle headerStyle = createStyle(workbook, "宋体", (short) 12, true, HorizontalAlignment.CENTER, true, true);
CellStyle bodyStyle = createStyle(workbook, "宋体", (short) 12, false, HorizontalAlignment.CENTER, false, true);
Row row1 = sheet.createRow(0);
row1.setHeightInPoints(32);
setCell(row1, 0, "附件12", attachStyle);
fillMergedCells(row1, 1, 9, attachStyle);
Row row2 = sheet.createRow(1);
row2.setHeightInPoints(32);
setCell(row2, 0, "5天外超出部分疗休养费用由单位承担申请人员汇总表", titleStyle);
fillMergedCells(row2, 1, 9, titleStyle);
Row row3 = sheet.createRow(2);
row3.setHeightInPoints(32);
setCell(row3, 0, "分工会:" + StrUtil.blankToDefault(unionName, ""), unionStyle);
fillMergedCells(row3, 1, 9, unionStyle);
String[] headers = {"序号", "姓名", "身份证号码", "电话号码", "所属分工会", "所选线路名称", "疗休养时间", "疗休养时长", "疗休养费用总额(元)", "备注"};
Row header = sheet.createRow(3);
header.setHeightInPoints(43);
for (int i = 0; i < headers.length; i++) {
setCell(header, i, headers[i], headerStyle);
}
int rowCount = Math.max(list == null ? 0 : list.size(), 20);
for (int i = 0; i < rowCount; i++) {
Row row = sheet.createRow(i + 4);
row.setHeightInPoints(25);
NutMap item = list != null && i < list.size() ? list.get(i) : null;
setCell(row, 0, String.valueOf(i + 1), bodyStyle);
setCell(row, 1, item == null ? "" : item.getString("userName", ""), bodyStyle);
setCell(row, 2, item == null ? "" : item.getString("idCard", ""), bodyStyle);
setCell(row, 3, item == null ? "" : item.getString("mobile", ""), bodyStyle);
setCell(row, 4, item == null ? "" : item.getString("unionName", ""), bodyStyle);
setCell(row, 5, item == null ? "" : item.getString("lineName", ""), bodyStyle);
setCell(row, 6, item == null ? "" : item.getString("travelPeriod", ""), bodyStyle);
setCell(row, 7, item == null ? "" : item.getString("totalDays", ""), bodyStyle);
setCell(row, 8, item == null ? "" : item.getString("estimatedCostText", ""), bodyStyle);
setCell(row, 9, "", bodyStyle);
}
return workbook;
}
private CellStyle createStyle(Workbook workbook, String fontName, short fontSize, boolean bold, HorizontalAlignment alignment, boolean wrap, boolean border) {
Font font = workbook.createFont();
font.setFontName(fontName);
font.setFontHeightInPoints(fontSize);
font.setBold(bold);
CellStyle style = workbook.createCellStyle();
style.setFont(font);
style.setAlignment(alignment);
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setWrapText(wrap);
if (border) {
style.setBorderLeft(BorderStyle.THIN);
style.setBorderTop(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
}
return style;
}
private void setCell(Row row, int col, String value, CellStyle style) {
Cell cell = row.createCell(col);
cell.setCellValue(StrUtil.blankToDefault(value, ""));
cell.setCellStyle(style);
}
private void fillMergedCells(Row row, int startCol, int endCol, CellStyle style) {
for (int i = startCol; i <= endCol; i++) {
setCell(row, i, "", style);
}
}
private Integer calcDays(String start, String end) {
if (StrUtil.isBlank(start) || StrUtil.isBlank(end)) {
return null;
}
try {
LocalDate startDate = LocalDate.parse(start.substring(0, 10));
LocalDate endDate = LocalDate.parse(end.substring(0, 10));
return Math.toIntExact(ChronoUnit.DAYS.between(startDate, endDate) + 1);
} catch (Exception e) {
return null;
}
}
private String formatAmount(Object value) {
if (value == null) {
return "";
}
try {
BigDecimal amount = new BigDecimal(String.valueOf(value));
return amount.stripTrailingZeros().toPlainString();
} catch (Exception e) {
return String.valueOf(value);
}
}
private Cnd buildQueryCnd(String currentUnionId, Integer startYear, Integer endYear, String keyword, String unionId, String lineId, String travelPeriod, String lineType, String scopeType, Boolean overCostOnly) {
Cnd cnd = Cnd.NEW();
cnd.and("t.delFlag", "=", false);
cnd.andEX("t.`year`", ">=", startYear);
cnd.andEX("t.`year`", "<=", endYear == null ? LocalDate.now().getYear() : endYear);
cnd.andEX("t.unionId", "=", unionId);
cnd.andEX("t.lineType", "=", lineType);
if (Boolean.TRUE.equals(overCostOnly)) {
cnd.and("t.overCostReimbursed", "=", true);
}
appendLineFilter(cnd, lineId);
appendTravelPeriodFilter(cnd, travelPeriod);
appendScopeFilter(cnd, currentUnionId, scopeType);
appendKeywordFilter(cnd, keyword);
return cnd;
}
private Cnd buildSummaryCnd(String currentUnionId, Integer startYear, Integer endYear, String unionId, String lineId, String travelPeriod, String lineType, String scopeType) {
Cnd cnd = Cnd.NEW();
cnd.and("t.delFlag", "=", false);
cnd.andEX("t.`year`", ">=", startYear);
cnd.andEX("t.`year`", "<=", endYear == null ? LocalDate.now().getYear() : endYear);
cnd.andEX("t.unionId", "=", unionId);
cnd.andEX("t.lineType", "=", lineType);
appendLineFilter(cnd, lineId);
appendTravelPeriodFilter(cnd, travelPeriod);
appendScopeFilter(cnd, currentUnionId, scopeType);
return cnd;
}
private void appendKeywordFilter(Cnd cnd, String keyword) {
if (StrUtil.isNotBlank(keyword)) {
SqlExpressionGroup group = new SqlExpressionGroup();
group.orLike("t.userName", keyword.trim());
group.orLike("t.jobNo", keyword.trim());
cnd.and(group);
}
}
private void applyUnionLedgerScope(Cnd cnd, String currentUnionId) {
SqlExpressionGroup group = new SqlExpressionGroup();
group.or("t.unionId", "=", currentUnionId);
group.or("m.unionId", "=", currentUnionId);
cnd.and(group);
}
private void appendScopeFilter(Cnd cnd, String currentUnionId, String scopeType) {
if ("ownUnionJoined".equals(scopeType)) {
cnd.and("t.unionId", "=", currentUnionId);
return;
}
if ("organizedLineJoined".equals(scopeType)) {
cnd.and("m.unionId", "=", currentUnionId);
return;
}
applyUnionLedgerScope(cnd, currentUnionId);
}
private String buildYearCondition(Integer startYear, Integer endYear) {
StringBuilder builder = new StringBuilder();
if (startYear != null) {
builder.append(" AND t.`year` >= ").append(startYear);
}
if (endYear != null) {
builder.append(" AND t.`year` <= ").append(endYear);
}
return builder.toString();
}
private TourLedger fetchScopedLedger(String id) {
if (StrUtil.isBlank(id) || StrUtil.isBlank(currentUnionId())) {
return null;
}
Sql sql = Sqls.create("""
SELECT COUNT(1)
FROM tour_ledger t
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
WHERE t.delFlag = 0
AND t.id = @id
AND (t.unionId = @unionId OR m.unionId = @unionId)
""");
sql.setParam("id", id);
sql.setParam("unionId", currentUnionId());
sql.setCallback(Sqls.callback.integer());
tourLedgerService.dao().execute(sql);
return sql.getInt() > 0 ? tourLedgerService.fetch(id) : null;
}
private String getTravelPeriod(TourLedger ledger) {
if (ledger == null || ledger.getYear() == null || StrUtil.isBlank(ledger.getLineId())) {
return "";
}
if (StrUtil.isNotBlank(ledger.getMatterId())) {
Sql exactSql = Sqls.create("""
SELECT CASE
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
ELSE ''
END AS travelPeriod
FROM tour_matter
WHERE delFlag = 0
AND id = @matterId
""");
exactSql.setParam("matterId", ledger.getMatterId());
exactSql.setCallback(Sqls.callback.str());
tourLedgerService.dao().execute(exactSql);
return exactSql.getString();
}
Sql sql = Sqls.create("""
SELECT GROUP_CONCAT(
DISTINCT CASE
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
ELSE NULL
END
ORDER BY travelStartTime ASC
SEPARATOR ';'
) AS travelPeriod
FROM tour_matter
WHERE delFlag = 0
AND `year` = @year
AND lineId = @lineId
""");
sql.setParam("year", ledger.getYear());
sql.setParam("lineId", ledger.getLineId());
sql.setCallback(Sqls.callback.str());
tourLedgerService.dao().execute(sql);
return sql.getString();
}
private boolean isDirectFamilyUnitLine(TourLedger ledger) {
if (ledger == null || StrUtil.isBlank(ledger.getLineId())) {
return false;
}
Sql sql = Sqls.create("""
SELECT IFNULL(directFamilyUnitLine, 0)
FROM tour_line
WHERE delFlag = 0
AND id = @lineId
""");
sql.setParam("lineId", ledger.getLineId());
sql.setCallback(Sqls.callback.integer());
tourLedgerService.dao().execute(sql);
return sql.getInt() > 0;
}
private boolean isFillBedInfo(TourLedger ledger) {
if (ledger == null || StrUtil.isBlank(ledger.getMatterId())) {
return true;
}
Sql sql = Sqls.create("""
SELECT IFNULL(MAX(s.fillBedInfo), 1)
FROM tour_ledger t
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_setting s ON s.id = m.settingId AND s.delFlag = 0
WHERE t.id = @ledgerId
""");
sql.setParam("ledgerId", ledger.getId());
sql.setCallback(Sqls.callback.integer());
tourLedgerService.dao().execute(sql);
return sql.getInt() > 0;
}
private void appendTravelPeriodFilter(Cnd cnd, String travelPeriod) {
if (StrUtil.isBlank(travelPeriod)) {
return;
}
cnd.and("CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)", "=", travelPeriod);
}
private void appendLineFilter(Cnd cnd, String lineId) {
if (StrUtil.isBlank(lineId)) {
return;
}
LineFilter lineFilter = parseLineFilter(lineId);
if (lineFilter.getLineId().startsWith("legacy:")) {
SqlExpressionGroup legacyGroup = new SqlExpressionGroup();
legacyGroup.or("t.lineId", "IS", null);
legacyGroup.or("t.lineId", "=", "");
cnd.and(legacyGroup);
cnd.and("t.lineName", "=", lineFilter.getLineId().substring("legacy:".length()));
} else {
cnd.and("t.lineId", "=", lineFilter.getLineId());
}
appendMatterUnionFilter(cnd, lineFilter.getUnionId());
}
private LineFilter parseLineFilter(String value) {
int delimiterIndex = value.indexOf("|");
if (delimiterIndex < 0) {
return new LineFilter(value, null);
}
return new LineFilter(value.substring(0, delimiterIndex), value.substring(delimiterIndex + 1));
}
private void appendMatterUnionFilter(Cnd cnd, String unionId) {
if (unionId == null) {
return;
}
if (StrUtil.isBlank(unionId)) {
SqlExpressionGroup group = new SqlExpressionGroup();
group.or("m.unionId", "IS", null);
group.or("m.unionId", "=", "");
cnd.and(group);
return;
}
cnd.and("m.unionId", "=", unionId);
}
private String getCurrentLineName(TourLedger ledger) {
if (ledger == null) {
return "";
}
Sql sql = Sqls.create("""
SELECT CASE
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
ELSE CONCAT(
COALESCE(NULLIF(l.lineName, ''), t.lineName),
'(',
CASE
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
ELSE IFNULL(mu.name, '')
END,
')'
)
END AS lineName
FROM tour_ledger t
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
LEFT JOIN sys_union mu ON mu.id = m.unionId
WHERE t.id = @ledgerId
""");
sql.setParam("ledgerId", ledger.getId());
sql.setCallback(Sqls.callback.str());
tourLedgerService.dao().execute(sql);
return StrUtil.blankToDefault(sql.getString(), ledger.getLineName());
}
private static class LineFilter {
private final String lineId;
private final String unionId;
private LineFilter(String lineId, String unionId) {
this.lineId = lineId;
this.unionId = unionId;
}
private String getLineId() {
return lineId;
}
private String getUnionId() {
return unionId;
}
}
private String currentUnionId() {
return SecurityUtil.getUnionId();
}
private String currentUnionName() {
String unionId = currentUnionId();
if (StrUtil.isBlank(unionId)) {
return "";
}
Sys_union union = tourLedgerService.dao().fetch(Sys_union.class, unionId);
return union == null ? "" : StrUtil.blankToDefault(union.getName(), "");
}
private String getOrderColumn(String orderName) {
if ("year".equals(orderName)) {
return "t.`year`";
}
if ("jobNo".equals(orderName)) {
return "t.jobNo";
}
if ("userName".equals(orderName)) {
return "t.userName";
}
if ("lineName".equals(orderName)) {
return """
CASE
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
ELSE CONCAT(
COALESCE(NULLIF(l.lineName, ''), t.lineName),
'(',
CASE
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
ELSE IFNULL(mu.name, '')
END,
')'
)
END
""";
}
if ("travelPeriod".equals(orderName)) {
return "COALESCE(m.travelStartTime, tp.travelPeriod)";
}
if ("lineType".equals(orderName)) {
return "t.lineType";
}
if ("signupTime".equals(orderName)) {
return "t.signupTime";
}
if ("familyCount".equals(orderName)) {
return "familyCount";
}
if ("joined".equals(orderName)) {
return "t.joined";
}
return "t.`year`";
}
}
@@ -0,0 +1,34 @@
package com.budwk.app.zhgh.dayofficework.tour.mode;
import cn.afterturn.easypoi.excel.annotation.Excel;
import com.budwk.app.base.model.ExcelImportError;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
@EqualsAndHashCode(callSuper = true)
@Data
public class TourLedgerImportExcelMode extends ExcelImportError {
@Excel(name = "年度")
private Integer year;
@Excel(name = "工号")
private String jobNo;
@Excel(name = "姓名")
private String userName;
@Excel(name = "报名时间", format = "yyyy-MM-dd HH:mm:ss")
private Date signupTime;
@Excel(name = "线路名称")
private String lineName;
@Excel(name = "线路类型")
private String lineType;
@Excel(name = "是否参加")
private String joined;
}
@@ -0,0 +1,152 @@
package com.budwk.app.zhgh.dayofficework.tour.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serializable;
/**
* 疗休养教职工报名台账。
* 后续报名模块完成后,将已报名或已参加的教职工写入本表,台账页负责跨年度查询和详情查看。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("tour_ledger")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("普惠疗休养台账")
public class TourLedger extends BaseModel implements Serializable {
@Column
@Name
@Comment("ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("年度")
@ColDefine(type = ColType.INT, width = 4)
private Integer year;
@Column
@Comment("工号")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String jobNo;
@Column
@Comment("姓名")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String userName;
@Column
@Comment("性别")
@ColDefine(type = ColType.VARCHAR, width = 20)
private String gender;
@Column
@Comment("身份证号")
@ColDefine(type = ColType.VARCHAR, width = 30)
private String idCard;
@Column
@Comment("所在单位ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String unitId;
@Column
@Comment("所在单位")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String unitName;
@Column
@Comment("所在工会ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String unionId;
@Column
@Comment("所在工会")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String unionName;
@Column
@Comment("报名时间")
@ColDefine(type = ColType.VARCHAR, width = 30)
private String signupTime;
@Column
@Comment("报名事项ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String matterId;
@Column
@Comment("报名线路ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String lineId;
@Column
@Comment("报名线路")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String lineName;
@Column
@Comment("线路类型")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String lineType;
@Column
@Comment("报名酒店")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String hotelName;
@Column
@Comment("旅行社ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String travelAgencyId;
@Column
@Comment("旅行社")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String travelAgencyName;
@Column
@Comment("是否携带家属")
@ColDefine(type = ColType.BOOLEAN)
@Default("0")
private Boolean hasFamily;
@Column
@Comment("意向拼床人")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String intendedRoommate;
@Column
@Comment("床型")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String bedType;
@Column
@Comment("床位信息")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String bedInfo;
@Column
@Comment("是否参加")
@ColDefine(type = ColType.BOOLEAN)
@Default("0")
private Boolean joined;
@Column
@Comment("是否报销")
@ColDefine(type = ColType.BOOLEAN)
@Default("0")
private Boolean reimbursed;
@Column
@Comment("报销超出费用")
@ColDefine(type = ColType.BOOLEAN)
@Default("0")
private Boolean overCostReimbursed;
}
@@ -0,0 +1,73 @@
package com.budwk.app.zhgh.dayofficework.tour.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serializable;
/**
* 直系亲属线路报名信息。
* 与普通携带亲属信息分表存放,用于直系亲属线路的专属申请信息。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("tour_ledger_direct_relative")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("普惠疗休养直系亲属线路报名信息")
public class TourLedgerDirectRelative extends BaseModel implements Serializable {
@Column
@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 ledgerId;
@Column
@Comment("亲属姓名")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String relativeName;
@Column
@Comment("所在单位")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String unitName;
@Column
@Comment("亲属关系编码")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String relationshipCode;
@Column
@Comment("亲属关系")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String relationshipName;
@Column
@Comment("线路ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String lineId;
@Column
@Comment("线路名称")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String lineName;
@Column
@Comment("出行开始日期")
@ColDefine(type = ColType.VARCHAR, width = 20)
private String travelStartTime;
@Column
@Comment("出行结束日期")
@ColDefine(type = ColType.VARCHAR, width = 20)
private String travelEndTime;
}
@@ -0,0 +1,83 @@
package com.budwk.app.zhgh.dayofficework.tour.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serializable;
/**
* 疗休养台账家属信息。
* 与教职工台账通过 ledgerId 关联,用于查看教职工携带家属的历史记录。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("tour_ledger_family")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("普惠疗休养台账家属信息")
public class TourLedgerFamily extends BaseModel implements Serializable {
@Column
@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 ledgerId;
@Column
@Comment("教职工工号")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String staffJobNo;
@Column
@Comment("教职工姓名")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String staffName;
@Column
@Comment("家属姓名")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String familyName;
@Column
@Comment("性别")
@ColDefine(type = ColType.VARCHAR, width = 20)
private String gender;
@Column
@Comment("身份证号码")
@ColDefine(type = ColType.VARCHAR, width = 30)
private String idCard;
@Column
@Comment("年龄")
@ColDefine(type = ColType.INT, width = 3)
private Integer age;
@Column
@Comment("床型")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String bedType;
@Column
@Comment("床位")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String bedInfo;
@Column
@Comment("意向拼床人")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String intendedRoommate;
@Column
@Comment("关系")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String relationship;
}
@@ -0,0 +1,106 @@
package com.budwk.app.zhgh.dayofficework.tour.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serializable;
/**
* 疗休养线路管理。
* 当前阶段先维护线路基础信息,后续报名、台账等模块可通过线路ID继续关联。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("tour_line")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("普惠疗休养线路")
public class TourLine extends BaseModel implements Serializable {
@Column
@Name
@Comment("ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("创建年度")
@ColDefine(type = ColType.INT, width = 4)
private Integer year;
@Column
@Comment("线路编号")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String lineCode;
@Column
@Comment("线路名称")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String lineName;
@Column
@Comment("旅行社ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String travelAgencyId;
@Column
@Comment("创建人ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String creatorUserId;
@Column
@Comment("创建人")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String creatorName;
@Column
@Comment("所在单位ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String unitId;
@Column
@Comment("所在单位")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String unitName;
@Column
@Comment("线路类型")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String lineType;
@Column
@Comment("时间标段ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String lotId;
@Column
@Comment("线路内容")
@ColDefine(type = ColType.TEXT)
private String lineContent;
@Column
@Comment("移动端缩略图")
@ColDefine(type = ColType.VARCHAR, width = 500)
private String mobileThumb;
@Column
@Comment("是否对外开放")
@ColDefine(type = ColType.BOOLEAN)
@Default("1")
private Boolean openFlag;
@Column
@Comment("是否直系亲属线路")
@ColDefine(type = ColType.BOOLEAN)
@Default("0")
private Boolean directFamilyUnitLine;
@Column
@Comment("激活状态")
@ColDefine(type = ColType.BOOLEAN)
@Default("1")
private Boolean enabled;
}
@@ -0,0 +1,124 @@
package com.budwk.app.zhgh.dayofficework.tour.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 疗休养事项。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("tour_matter")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("普惠疗休养事项")
public class TourMatter extends BaseModel implements Serializable {
@Column
@Name
@Comment("ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("年度")
@ColDefine(type = ColType.INT, width = 4)
private Integer year;
@Column
@Comment("事项名称")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String matterName;
@Column
@Comment("疗休养配置ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String settingId;
@Column
@Comment("创建人ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String creatorUserId;
@Column
@Comment("创建人")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String creatorName;
@Column
@Comment("所属工会ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String unionId;
@Column
@Comment("组织形式")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String organizationType;
@Column
@Comment("移动端缩略图")
@ColDefine(type = ColType.VARCHAR, width = 500)
private String mobileThumb;
@Column
@Comment("线路ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String lineId;
@Column
@Comment("报名开始时间")
@ColDefine(type = ColType.VARCHAR, width = 20)
private String signupStartTime;
@Column
@Comment("报名结束时间")
@ColDefine(type = ColType.VARCHAR, width = 20)
private String signupEndTime;
@Column
@Comment("出行开始时间")
@ColDefine(type = ColType.VARCHAR, width = 20)
private String travelStartTime;
@Column
@Comment("出行结束时间")
@ColDefine(type = ColType.VARCHAR, width = 20)
private String travelEndTime;
@Column
@Comment("联系人")
@ColDefine(type = ColType.VARCHAR, width = 30)
private String contactName;
@Column
@Comment("联系方式")
@ColDefine(type = ColType.VARCHAR, width = 30)
private String contactPhone;
@Column
@Comment("最少成团人数")
@ColDefine(type = ColType.INT)
private Integer minGroupPeople;
@Column
@Comment("最多成团人数")
@ColDefine(type = ColType.INT)
private Integer maxGroupPeople;
@Column
@Comment("预计费用")
@ColDefine(customType = "decimal(10,2)")
private BigDecimal estimatedCost;
@Column
@Comment("事项状态")
@ColDefine(type = ColType.BOOLEAN)
@Default("1")
private Boolean enabled;
}
@@ -0,0 +1,134 @@
package com.budwk.app.zhgh.dayofficework.tour.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
/**
* 疗休养基础配置。
* 这里先沉淀创建事项会复用的基础字段,后续线路、报名等阶段可以继续通过配置ID关联扩展。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("tour_setting")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("普惠疗休养配置")
public class TourSetting extends BaseModel implements Serializable {
@Column
@Name
@Comment("ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("年度")
@ColDefine(type = ColType.INT, width = 4)
private Integer year;
@Column
@Comment("疗休养配置名称")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String configName;
@Column
@Comment("疗休养类型")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String tourType;
@Column
@Comment("可参加人员范围ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String activityGroupId;
@Column
@Comment("排序编号")
@ColDefine(type = ColType.INT)
private Integer sortNo;
@Column
@Comment("最少成团人数")
@ColDefine(type = ColType.INT)
private Integer minGroupPeople;
@Column
@Comment("最多成团人数")
@ColDefine(type = ColType.INT)
private Integer maxGroupPeople;
@Column
@Comment("省外几年去一次")
@ColDefine(type = ColType.INT)
private Integer outProvinceYears;
@Column
@Comment("省外人数占比")
@ColDefine(customType = "decimal(10,2)")
private BigDecimal outProvinceRatio;
@Column
@Comment("省外人数占比类型")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String outProvinceRatioType;
@Column
@Comment("省外固定人数")
@ColDefine(type = ColType.INT)
private Integer outProvinceFixedPeople;
@Column
@Comment("周期开始年度")
@ColDefine(type = ColType.INT, width = 4)
private Integer cycleStartYear;
@Column
@Comment("周期结束年度")
@ColDefine(type = ColType.INT, width = 4)
private Integer cycleEndYear;
@Column
@Comment("周期内总费用")
@ColDefine(type = ColType.INT)
private Integer cycleTotalCost;
@Column
@Comment("周期允许次数")
@ColDefine(type = ColType.INT)
private Integer cycleAllowedTimes;
@Column
@Comment("是否允许携带家属")
@ColDefine(type = ColType.BOOLEAN)
@Default("0")
private Boolean allowFamily;
@Column
@Comment("是否填报床位信息")
@ColDefine(type = ColType.BOOLEAN)
@Default("1")
private Boolean fillBedInfo;
@Column
@Comment("是否启用")
@ColDefine(type = ColType.BOOLEAN)
@Default("1")
private Boolean enabled;
/**
* 标段管理沿用老疗休养配置的子表设计,后续线路、旅行社等模块可通过标段ID继续关联。
*/
@Many(field = "settingId")
private List<TourSettingLot> lots;
@Column
@Comment("服务须知")
@ColDefine(type = ColType.TEXT)
private String serviceNotice;
}
@@ -0,0 +1,53 @@
package com.budwk.app.zhgh.dayofficework.tour.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serializable;
/**
* 疗休养配置标段。
* 标段从基础配置中拆成子表,便于后续线路、目的地、报名等模块复用同一标段口径。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("tour_setting_lot")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("普惠疗休养配置标段")
public class TourSettingLot extends BaseModel implements Serializable {
@Column
@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 settingId;
@Column
@Comment("标段名称")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String lotName;
@Column
@Comment("标段值")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String lotValue;
@Column
@Comment("标段费用")
@ColDefine(type = ColType.INT)
private Integer activityCost;
@Column
@Comment("允许超出报销")
@ColDefine(type = ColType.BOOLEAN)
private Boolean allowOverReimbursement;
}
@@ -0,0 +1,74 @@
package com.budwk.app.zhgh.dayofficework.tour.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serializable;
/**
* 旅行社管理。
* 先维护疗休养线路创建会复用的旅行社基础信息,后续线路模块可通过旅行社ID关联。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("tour_travel_agency")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("普惠疗休养旅行社")
public class TourTravelAgency extends BaseModel implements Serializable {
@Column
@Name
@Comment("ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("年度")
@ColDefine(type = ColType.INT, width = 4)
private Integer year;
@Column
@Comment("旅行社编号")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String agencyCode;
@Column
@Comment("旅行社名称")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String agencyName;
@Column
@Comment("联系人")
@ColDefine(type = ColType.VARCHAR, width = 30)
private String contactName;
@Column
@Comment("联系人手机")
@ColDefine(type = ColType.VARCHAR, width = 30)
private String contactPhone;
@Column
@Comment("邮箱")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String email;
@Column
@Comment("备注")
@ColDefine(type = ColType.TEXT)
private String remark;
@Column
@Comment("移动端缩略图")
@ColDefine(type = ColType.VARCHAR, width = 500)
private String mobileThumb;
@Column
@Comment("激活状态")
@ColDefine(type = ColType.BOOLEAN)
@Default("1")
private Boolean enabled;
}
@@ -0,0 +1,7 @@
package com.budwk.app.zhgh.dayofficework.tour.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerDirectRelative;
public interface TourLedgerDirectRelativeService extends BaseService<TourLedgerDirectRelative> {
}
@@ -0,0 +1,7 @@
package com.budwk.app.zhgh.dayofficework.tour.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerFamily;
public interface TourLedgerFamilyService extends BaseService<TourLedgerFamily> {
}
@@ -0,0 +1,7 @@
package com.budwk.app.zhgh.dayofficework.tour.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
public interface TourLedgerService extends BaseService<TourLedger> {
}
@@ -0,0 +1,7 @@
package com.budwk.app.zhgh.dayofficework.tour.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLine;
public interface TourLineService extends BaseService<TourLine> {
}
@@ -0,0 +1,7 @@
package com.budwk.app.zhgh.dayofficework.tour.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.dayofficework.tour.models.TourMatter;
public interface TourMatterService extends BaseService<TourMatter> {
}
@@ -0,0 +1,7 @@
package com.budwk.app.zhgh.dayofficework.tour.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.dayofficework.tour.models.TourSetting;
public interface TourSettingService extends BaseService<TourSetting> {
}
@@ -0,0 +1,7 @@
package com.budwk.app.zhgh.dayofficework.tour.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.dayofficework.tour.models.TourTravelAgency;
public interface TourTravelAgencyService extends BaseService<TourTravelAgency> {
}
@@ -0,0 +1,15 @@
package com.budwk.app.zhgh.dayofficework.tour.service.impl;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerDirectRelative;
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerDirectRelativeService;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean;
@IocBean(args = {"refer:dao"})
public class TourLedgerDirectRelativeServiceImpl extends BaseServiceImpl<TourLedgerDirectRelative> implements TourLedgerDirectRelativeService {
public TourLedgerDirectRelativeServiceImpl(Dao dao) {
super(dao);
}
}
@@ -0,0 +1,15 @@
package com.budwk.app.zhgh.dayofficework.tour.service.impl;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerFamily;
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerFamilyService;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean;
@IocBean(args = {"refer:dao"})
public class TourLedgerFamilyServiceImpl extends BaseServiceImpl<TourLedgerFamily> implements TourLedgerFamilyService {
public TourLedgerFamilyServiceImpl(Dao dao) {
super(dao);
}
}
@@ -0,0 +1,15 @@
package com.budwk.app.zhgh.dayofficework.tour.service.impl;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean;
@IocBean(args = {"refer:dao"})
public class TourLedgerServiceImpl extends BaseServiceImpl<TourLedger> implements TourLedgerService {
public TourLedgerServiceImpl(Dao dao) {
super(dao);
}
}
@@ -0,0 +1,15 @@
package com.budwk.app.zhgh.dayofficework.tour.service.impl;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLine;
import com.budwk.app.zhgh.dayofficework.tour.service.TourLineService;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean;
@IocBean(args = {"refer:dao"})
public class TourLineServiceImpl extends BaseServiceImpl<TourLine> implements TourLineService {
public TourLineServiceImpl(Dao dao) {
super(dao);
}
}
@@ -0,0 +1,15 @@
package com.budwk.app.zhgh.dayofficework.tour.service.impl;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.dayofficework.tour.models.TourMatter;
import com.budwk.app.zhgh.dayofficework.tour.service.TourMatterService;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean;
@IocBean(args = {"refer:dao"})
public class TourMatterServiceImpl extends BaseServiceImpl<TourMatter> implements TourMatterService {
public TourMatterServiceImpl(Dao dao) {
super(dao);
}
}
@@ -0,0 +1,15 @@
package com.budwk.app.zhgh.dayofficework.tour.service.impl;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.dayofficework.tour.models.TourSetting;
import com.budwk.app.zhgh.dayofficework.tour.service.TourSettingService;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean;
@IocBean(args = {"refer:dao"})
public class TourSettingServiceImpl extends BaseServiceImpl<TourSetting> implements TourSettingService {
public TourSettingServiceImpl(Dao dao) {
super(dao);
}
}
@@ -0,0 +1,15 @@
package com.budwk.app.zhgh.dayofficework.tour.service.impl;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.dayofficework.tour.models.TourTravelAgency;
import com.budwk.app.zhgh.dayofficework.tour.service.TourTravelAgencyService;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean;
@IocBean(args = {"refer:dao"})
public class TourTravelAgencyServiceImpl extends BaseServiceImpl<TourTravelAgency> implements TourTravelAgencyService {
public TourTravelAgencyServiceImpl(Dao dao) {
super(dao);
}
}