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

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 @IocBean
public class SmsJshvcServiceImpl implements SmsService { public class SmsJshvcServiceImpl implements SmsService {
private static final String APPID = ""; private static final String APPID = "1430576717768122368";
private static final String APP_SECRET = ""; private static final String APP_SECRET = "19A0ACB03FBQHL4R4XQD";
private static final String TOKEN_URL = ""; private static final String TOKEN_URL = "https://gateway.jshvc.edu.cn/token/gateway/accessToken";
private static final String MSG_URL = ""; 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 = ""; private static final String REDIS_KEY_MSG_ACCESS_TOKEN = "msg:token:";
@Inject
@Inject
private RedisService redisService; private RedisService redisService;
@Override @Override
@@ -3,19 +3,38 @@ package com.budwk.app.flow.service;
import cn.hutool.core.lang.Dict; import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.budwk.app.base.event.flow.jumptofirsttask.FlowJumpToFirstTaskNodePublisher; 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.constant.FlowConst;
import com.budwk.app.flow.engine.FlowEngine; 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.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.web.commons.auth.utils.SecurityUtil; 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.Inject;
import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.ioc.loader.annotation.IocBean;
import java.util.List;
@IocBean @IocBean
public class FlowCommonService { public class FlowCommonService {
@Inject @Inject
private FlowEngine flowEngine; private FlowEngine flowEngine;
@Inject
private Dao dao;
/** /**
* 执行任务 * 执行任务
* @param args * @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); 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; 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 @Override
@Aop(TransAop.READ_COMMITTED) @Aop(TransAop.READ_COMMITTED)
public void saveProcessTask(ProcessTask processTask) { public void saveProcessTask(ProcessTask processTask) {
@@ -20,7 +20,8 @@ import java.io.Serializable;
@Comment("活动人员范围设置") @Comment("活动人员范围设置")
@TableIndexes({ @TableIndexes({
@Index(name = "INDEX_ACTIVITY_USER_SCOPE_GROUPID", fields = {"groupId"}, unique = false), @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 { public class ActivityUserScope extends BaseModel implements Serializable {
@@ -46,6 +47,16 @@ public class ActivityUserScope extends BaseModel implements Serializable {
@Comment("userid") @Comment("userid")
private String 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 @Column
@ColDefine(type = ColType.VARCHAR, width = 32) @ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("创建人") @Comment("创建人")
@@ -21,4 +21,49 @@ public interface ActivityBasicScopeService extends BaseService<ActivityUserScope
*/ */
void largeDataInsert(List<ActivityUserScope> list) ; 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; package com.budwk.app.zhgh.activity.basic.service.impl;
import cn.hutool.core.collection.CollectionUtil; 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.service.impl.BaseServiceImpl;
import com.budwk.app.base.utils.DateUtil; import com.budwk.app.base.utils.DateUtil;
import com.budwk.app.zhgh.activity.basic.models.ActivityUserScope; 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 lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Cnd; import org.nutz.dao.Cnd;
import org.nutz.dao.Dao; import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.impl.NutTxDao; import org.nutz.dao.impl.NutTxDao;
import org.nutz.dao.sql.Sql; import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.ioc.loader.annotation.IocBean;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/** /**
* @author zxy * @author zxy
@@ -29,6 +33,7 @@ import java.util.concurrent.atomic.AtomicInteger;
@Slf4j @Slf4j
public class ActivityBasicScopeServiceImpl extends BaseServiceImpl<ActivityUserScope> implements ActivityBasicScopeService { public class ActivityBasicScopeServiceImpl extends BaseServiceImpl<ActivityUserScope> implements ActivityBasicScopeService {
private static final int GROUP_TYPE_RESULT = 1;
public ActivityBasicScopeServiceImpl(Dao dao) { public ActivityBasicScopeServiceImpl(Dao dao) {
super(dao); super(dao);
@@ -80,4 +85,122 @@ public class ActivityBasicScopeServiceImpl extends BaseServiceImpl<ActivityUserS
} }
log.info("结束时间" + DateUtil.getDateTime()); 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);
}
}
@@ -0,0 +1,122 @@
-- 普惠疗休养平台电脑端菜单。
-- 使用 permission 做幂等判断,避免同一环境重复执行时插入重复菜单。
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT
'b7c63f0c73a44a9a8c4f3d3bb1a10001',
'',
LPAD(IFNULL(MAX(CAST(path AS UNSIGNED)), 0) + 1, 4, '0'),
'普惠疗休养',
'Tour',
'menu',
'',
'',
'ti-map-alt',
1,
0,
'tour',
NULL,
991,
1,
'',
UNIX_TIMESTAMP(NOW()) * 1000,
'',
UNIX_TIMESTAMP(NOW()) * 1000,
0,
'PC',
NULL,
NULL,
'p',
0,
0
FROM sys_menu
WHERE (parentId = '' OR parentId IS NULL)
AND CHAR_LENGTH(path) = 4
HAVING NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10002', p.id, CONCAT(p.path, '0001'), '疗休养设置', 'Tour Setting', 'menu', '/platform/tour/setting', 'data-pjax', '', 1, 0, 'tour.setting', NULL, 1, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'l', 0, 0
FROM sys_menu p
WHERE p.permission = 'tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.setting') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10003', p.id, CONCAT(p.path, '0002'), '旅行社管理', 'Travel Agency', 'menu', '/platform/tour/travelAgency', 'data-pjax', '', 1, 0, 'tour.travelAgency', NULL, 2, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'l', 0, 0
FROM sys_menu p
WHERE p.permission = 'tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.travelAgency') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10004', p.id, CONCAT(p.path, '0003'), '线路管理', 'Route Manage', 'menu', '/platform/tour/route', 'data-pjax', '', 1, 0, 'tour.route', NULL, 3, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'x', 0, 0
FROM sys_menu p
WHERE p.permission = 'tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.route') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10005', p.id, CONCAT(p.path, '0004'), '疗休养事项', 'Tour Matter', 'menu', '/platform/tour/matter', 'data-pjax', '', 1, 0, 'tour.matter', NULL, 4, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'l', 0, 0
FROM sys_menu p
WHERE p.permission = 'tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.matter') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10006', p.id, CONCAT(p.path, '0005'), '疗休养报名', 'Tour Signup', 'menu', '/platform/tour/signup', 'data-pjax', '', 1, 0, 'tour.signup', NULL, 5, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'l', 0, 0
FROM sys_menu p
WHERE p.permission = 'tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.signup') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10008', p.id, CONCAT(p.path, '0006'), '我的报名', 'My Signup', 'menu', '/platform/tour/mysignup', 'data-pjax', '', 1, 0, 'tour.mysignup', NULL, 6, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'w', 0, 0
FROM sys_menu p
WHERE p.permission = 'tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.mysignup') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10009', p.id, CONCAT(p.path, '0007'), '分工会查询', 'Union Ledger', 'menu', '/platform/tour/unionledger', 'data-pjax', '', 1, 0, 'tour.unionledger', NULL, 7, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'f', 0, 0
FROM sys_menu p
WHERE p.permission = 'tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.unionledger') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10011', p.id, CONCAT(p.path, '0010'), '分工会审核', 'Union Approval', 'menu', '/platform/tour/unionApproval', 'data-pjax', '', 1, 0, 'tour.unionApproval', NULL, 8, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'f', 0, 0
FROM sys_menu p
WHERE p.permission = 'tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.unionApproval') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10012', p.id, CONCAT(p.path, '0011'), '校工会审核', 'School Union Approval', 'menu', '/platform/tour/schoolUnionApproval', 'data-pjax', '', 1, 0, 'tour.schoolUnionApproval', NULL, 9, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'x', 0, 0
FROM sys_menu p
WHERE p.permission = 'tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.schoolUnionApproval') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10007', p.id, CONCAT(p.path, '0008'), '疗休养台账', 'Tour Ledger', 'menu', '/platform/tour/ledger', 'data-pjax', '', 1, 0, 'tour.ledger', NULL, 10, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'l', 0, 0
FROM sys_menu p
WHERE p.permission = 'tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.ledger') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10010', p.id, CONCAT(p.path, '0009'), '线路成团', 'Tour Group', 'menu', '/platform/tour/group', 'data-pjax', '', 1, 0, 'tour.group', NULL, 11, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'x', 0, 0
FROM sys_menu p
WHERE p.permission = 'tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.group') t);
-- 给系统管理员默认授权,其他角色可在角色管理中按需分配。
INSERT INTO sys_role_menu (roleId, menuId)
SELECT r.id, m.id
FROM sys_role r
JOIN sys_menu m ON m.permission IN (
'tour',
'tour.setting',
'tour.travelAgency',
'tour.route',
'tour.matter',
'tour.signup',
'tour.group',
'tour.mysignup',
'tour.unionledger',
'tour.unionApproval',
'tour.schoolUnionApproval',
'tour.ledger'
)
LEFT JOIN sys_role_menu rm ON rm.roleId = r.id AND rm.menuId = m.id
WHERE r.code = 'SYSADMIN'
AND rm.roleId IS NULL;
@@ -0,0 +1,3 @@
ALTER TABLE `site_cug_info`
ADD COLUMN `segmented_open_hours` JSON NULL COMMENT '分段预约场次信息' AFTER `open_hours`,
ADD COLUMN `full_day_open_hour` JSON NULL COMMENT '全天候预约时间段' AFTER `segmented_open_hours`;
@@ -0,0 +1,3 @@
ALTER TABLE `site_cug_info`
ADD COLUMN `reserve_time_type` INT NULL DEFAULT 1 COMMENT '预约时间段类型(1分段预约,2全天候预约)' AFTER `not_apply_time_list`,
ADD COLUMN `open_hours` JSON NULL COMMENT '场次信息' AFTER `reserve_time_type`;
@@ -0,0 +1,2 @@
ALTER TABLE `site_cug_info`
ADD COLUMN `site_photo` VARCHAR(255) NULL COMMENT '场地照片' AFTER `introduce`;
+253
View File
@@ -0,0 +1,253 @@
-- 普惠疗休养系统字典初始化。
-- 执行后可在 /platform/sys/dict 页面看到:疗休养(Tour) -> 组织形式/疗休养类型/线路类型/床型。
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict001', '', '9500', '疗休养', '普惠疗休养平台字典', 'Tour', 0, 9500, 1,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'Tour') t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict002', p.id, CONCAT(p.path, '0001'), '组织形式', '疗休养组织形式', 'organizationType', 0, 1, 1,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'Tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'organizationType' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict003', p.id, CONCAT(p.path, '0001'), '校工会组织', '疗休养组织形式', 'schoolUnion', 0, 1, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'organizationType'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'schoolUnion' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict004', p.id, CONCAT(p.path, '0002'), '分工会组织', '疗休养组织形式', 'branchUnion', 0, 2, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'organizationType'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'branchUnion' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict005', p.id, CONCAT(p.path, '0003'), '个人组织', '疗休养组织形式', 'personal', 0, 3, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'organizationType'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'personal' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict006', p.id, CONCAT(p.path, '0002'), '疗休养类型', '疗休养类型', 'tourType', 0, 2, 1,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'Tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'tourType' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict007', p.id, CONCAT(p.path, '0001'), '普惠性疗休养', '疗休养类型', 'inclusiveTour', 0, 1, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'tourType'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'inclusiveTour' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict008', p.id, CONCAT(p.path, '0002'), '优秀职工疗休养', '疗休养类型', 'excellentWorkerTour', 0, 2, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'tourType'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'excellentWorkerTour' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict009', p.id, CONCAT(p.path, '0003'), '线路类型', '线路类型', 'lineType', 0, 3, 1,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'Tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'lineType' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict010', p.id, CONCAT(p.path, '0001'), '省内线路', '线路类型', 'inProvinceLine', 0, 1, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'lineType'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'inProvinceLine' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict011', p.id, CONCAT(p.path, '0002'), '省外线路', '线路类型', 'outProvinceLine', 0, 2, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'lineType'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'outProvinceLine' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict020', p.id, CONCAT(p.path, '0004'), '床型', '床型', 'bedType', 0, 4, 1,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'Tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'bedType' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict021', p.id, CONCAT(p.path, '0001'), '双人床', '床型', 'doubleBed', 0, 1, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'bedType'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'doubleBed' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict022', p.id, CONCAT(p.path, '0002'), '单人床', '床型', 'singleBed', 0, 2, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'bedType'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'singleBed' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict023', p.id, CONCAT(p.path, '0003'), '大床', '床型', 'kingBed', 0, 3, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'bedType'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'kingBed' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict012', '', '9600', '亲属关系', '亲属关系', 'familyRelationship', 0, 9600, 1,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'familyRelationship') t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict013', p.id, CONCAT(p.path, '0001'), '直系亲属', '亲属关系', 'directRelative', 0, 1, 1,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'familyRelationship'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'directRelative' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict014', p.id, CONCAT(p.path, '0001'), '父亲', '直系亲属', 'father', 0, 1, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'directRelative'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'father' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict015', p.id, CONCAT(p.path, '0002'), '母亲', '直系亲属', 'mother', 0, 2, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'directRelative'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'mother' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict016', p.id, CONCAT(p.path, '0003'), '丈夫', '直系亲属', 'husband', 0, 3, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'directRelative'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'husband' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict017', p.id, CONCAT(p.path, '0004'), '妻子', '直系亲属', 'wife', 0, 4, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'directRelative'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'wife' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict018', p.id, CONCAT(p.path, '0005'), '儿子', '直系亲属', 'son', 0, 5, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'directRelative'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'son' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict019', p.id, CONCAT(p.path, '0006'), '女儿', '直系亲属', 'daughter', 0, 6, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'directRelative'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'daughter' AND `parentId` = p.id) t);
UPDATE `sys_dict` SET `hasChildren` = 1 WHERE `code` IN ('Tour', 'organizationType', 'tourType', 'lineType', 'bedType', 'familyRelationship', 'directRelative');
@@ -0,0 +1,7 @@
-- Add matter reference so signup records are unique per travel period/matter.
ALTER TABLE `tour_ledger`
ADD COLUMN `matterId` varchar(32) DEFAULT NULL COMMENT '报名事项ID' AFTER `signupTime`;
ALTER TABLE `tour_ledger`
ADD KEY `idx_tour_ledger_matter` (`matterId`),
ADD KEY `idx_tour_ledger_matter_job` (`matterId`, `jobNo`);
@@ -0,0 +1,2 @@
ALTER TABLE `tour_ledger`
ADD COLUMN `overCostReimbursed` tinyint(1) DEFAULT 0 COMMENT '报销超出费用' AFTER `reimbursed`;
@@ -0,0 +1,9 @@
-- 疗休养台账查询加速索引。
ALTER TABLE `tour_ledger`
ADD KEY `idx_tour_ledger_year_signup` (`year`, `delFlag`, `signupTime`),
ADD KEY `idx_tour_ledger_year_line` (`year`, `lineId`, `delFlag`),
ADD KEY `idx_tour_ledger_year_union_type` (`year`, `unionId`, `lineType`, `delFlag`),
ADD KEY `idx_tour_ledger_year_job` (`year`, `jobNo`, `delFlag`);
ALTER TABLE `wf_process_instance`
ADD KEY `idx_wf_process_instance_business_state` (`businessNo`, `state`);
@@ -0,0 +1,21 @@
-- 直系亲属线路报名信息表,独立于普通携带亲属信息表。
CREATE TABLE IF NOT EXISTS `tour_ledger_direct_relative` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`ledgerId` varchar(32) DEFAULT NULL COMMENT '台账ID',
`relativeName` varchar(100) DEFAULT NULL COMMENT '亲属姓名',
`unitName` varchar(100) DEFAULT NULL COMMENT '所在单位',
`relationshipCode` varchar(50) DEFAULT NULL COMMENT '亲属关系编码',
`relationshipName` varchar(50) DEFAULT NULL COMMENT '亲属关系',
`lineId` varchar(32) DEFAULT NULL COMMENT '线路ID',
`lineName` varchar(100) DEFAULT NULL COMMENT '线路名称',
`travelStartTime` varchar(20) DEFAULT NULL COMMENT '出行开始日期',
`travelEndTime` varchar(20) DEFAULT NULL COMMENT '出行结束日期',
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
`updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人',
`updatedAt` bigint DEFAULT NULL COMMENT '修改时间',
`delFlag` tinyint(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
KEY `idx_tour_ledger_direct_relative_ledger` (`ledgerId`),
KEY `idx_tour_ledger_direct_relative_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养直系亲属线路报名信息';
@@ -0,0 +1,10 @@
-- 线路管理增加创建人和所在单位业务字段。
ALTER TABLE `tour_line`
ADD COLUMN `creatorUserId` varchar(32) DEFAULT NULL COMMENT '创建人ID' AFTER `travelAgencyId`,
ADD COLUMN `creatorName` varchar(100) DEFAULT NULL COMMENT '创建人' AFTER `creatorUserId`,
ADD COLUMN `unitId` varchar(32) DEFAULT NULL COMMENT '所在单位ID' AFTER `creatorName`,
ADD COLUMN `unitName` varchar(100) DEFAULT NULL COMMENT '所在单位' AFTER `unitId`;
ALTER TABLE `tour_line`
ADD KEY `idx_tour_line_creator` (`creatorUserId`),
ADD KEY `idx_tour_line_unit` (`unitId`);
@@ -0,0 +1,3 @@
-- 线路管理增加是否直系亲属线路字段,默认否。
ALTER TABLE `tour_line`
ADD COLUMN `directFamilyUnitLine` tinyint(1) DEFAULT 0 COMMENT '是否直系亲属线路' AFTER `openFlag`;
@@ -0,0 +1,3 @@
-- 线路管理增加是否对外开放字段,默认是。
ALTER TABLE `tour_line`
ADD COLUMN `openFlag` tinyint(1) DEFAULT 1 COMMENT '是否对外开放' AFTER `mobileThumb`;
@@ -0,0 +1,7 @@
-- 疗休养事项增加创建人字段。
ALTER TABLE `tour_matter`
ADD COLUMN `creatorUserId` varchar(32) DEFAULT NULL COMMENT '创建人ID' AFTER `settingId`,
ADD COLUMN `creatorName` varchar(100) DEFAULT NULL COMMENT '创建人' AFTER `creatorUserId`;
ALTER TABLE `tour_matter`
ADD KEY `idx_tour_matter_creator` (`creatorUserId`);
@@ -0,0 +1,26 @@
-- 疗休养事项批次表。
CREATE TABLE IF NOT EXISTS `tour_matter_batch` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`matterId` varchar(32) DEFAULT NULL COMMENT '事项ID',
`lineId` varchar(32) DEFAULT NULL COMMENT '线路ID',
`batchName` varchar(100) DEFAULT NULL COMMENT '批次名称',
`signupStartTime` varchar(20) DEFAULT NULL COMMENT '报名开始时间',
`signupEndTime` varchar(20) DEFAULT NULL COMMENT '报名结束时间',
`travelStartTime` varchar(20) DEFAULT NULL COMMENT '出行开始时间',
`travelEndTime` varchar(20) DEFAULT NULL COMMENT '出行结束时间',
`changeDeadline` varchar(20) DEFAULT NULL COMMENT '变更截止时间',
`contactName` varchar(30) DEFAULT NULL COMMENT '联系人',
`contactPhone` varchar(30) DEFAULT NULL COMMENT '联系方式',
`minGroupPeople` int DEFAULT NULL COMMENT '最少成团人数',
`maxGroupPeople` int DEFAULT NULL COMMENT '最多成团人数',
`estimatedCost` decimal(10,2) DEFAULT NULL COMMENT '预计费用',
`enabled` tinyint(1) DEFAULT 1 COMMENT '状态',
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
`updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人',
`updatedAt` bigint DEFAULT NULL COMMENT '修改时间',
`delFlag` tinyint(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
KEY `idx_tour_matter_batch_matter` (`matterId`),
KEY `idx_tour_matter_batch_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养事项批次';
@@ -0,0 +1,25 @@
-- 疗休养事项表。
CREATE TABLE IF NOT EXISTS `tour_matter` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`year` int DEFAULT NULL COMMENT '年度',
`matterName` varchar(100) DEFAULT NULL COMMENT '事项名称',
`settingId` varchar(32) DEFAULT NULL COMMENT '疗休养配置ID',
`creatorUserId` varchar(32) DEFAULT NULL COMMENT '创建人ID',
`creatorName` varchar(100) DEFAULT NULL COMMENT '创建人',
`unionId` varchar(32) DEFAULT NULL COMMENT '所属工会ID',
`organizationType` varchar(100) DEFAULT NULL COMMENT '组织形式',
`mobileThumb` varchar(500) DEFAULT NULL COMMENT '移动端缩略图',
`enabled` tinyint(1) DEFAULT 1 COMMENT '事项状态',
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
`updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人',
`updatedAt` bigint DEFAULT NULL COMMENT '修改时间',
`delFlag` tinyint(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tour_matter_year_name` (`year`, `matterName`),
KEY `idx_tour_matter_year` (`year`),
KEY `idx_tour_matter_creator` (`creatorUserId`),
KEY `idx_tour_matter_setting` (`settingId`),
KEY `idx_tour_matter_union` (`unionId`),
KEY `idx_tour_matter_org_type` (`organizationType`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养事项';
@@ -0,0 +1,6 @@
-- Add activity user scope reference for tour settings.
ALTER TABLE `tour_setting`
ADD COLUMN `activityGroupId` varchar(32) DEFAULT NULL COMMENT '可参加人员范围ID' AFTER `tourType`;
ALTER TABLE `tour_setting`
ADD KEY `idx_tour_setting_activity_group` (`activityGroupId`);
@@ -0,0 +1,3 @@
-- 疗休养配置增加周期允许次数字段。
ALTER TABLE `tour_setting`
ADD COLUMN `cycleAllowedTimes` int DEFAULT NULL COMMENT '周期允许次数' AFTER `cycleTotalCost`;
@@ -0,0 +1,2 @@
ALTER TABLE `tour_setting`
ADD COLUMN `cycleTotalCost` int DEFAULT NULL COMMENT '周期内总费用' AFTER `cycleEndYear`;
@@ -0,0 +1,4 @@
-- Add optional cycle year range for tour settings.
ALTER TABLE `tour_setting`
ADD COLUMN `cycleStartYear` int DEFAULT NULL COMMENT '周期开始年度' AFTER `outProvinceRatio`,
ADD COLUMN `cycleEndYear` int DEFAULT NULL COMMENT '周期结束年度' AFTER `cycleStartYear`;
@@ -0,0 +1,3 @@
-- 疗休养配置增加是否填报床位信息字段,默认开启以兼容历史配置。
ALTER TABLE `tour_setting`
ADD COLUMN `fillBedInfo` tinyint(1) DEFAULT 1 COMMENT '是否填报床位信息' AFTER `allowFamily`;
@@ -0,0 +1,3 @@
ALTER TABLE `tour_setting`
ADD COLUMN `outProvinceRatioType` varchar(50) DEFAULT '当年报名人数' COMMENT '省外人数占比类型' AFTER `outProvinceRatio`,
ADD COLUMN `outProvinceFixedPeople` int DEFAULT 0 COMMENT '省外固定人数' AFTER `outProvinceRatioType`;
@@ -0,0 +1,183 @@
-- 普惠疗休养基础配置表。
-- 若生产环境未开启 Nutz 自动建表,请先执行本脚本再使用“疗休养设置”菜单。
CREATE TABLE IF NOT EXISTS `tour_setting` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`year` int DEFAULT NULL COMMENT '年度',
`configName` varchar(100) DEFAULT NULL COMMENT '疗休养配置名称',
`tourType` varchar(50) DEFAULT NULL COMMENT '疗休养类型',
`activityGroupId` varchar(32) DEFAULT NULL COMMENT '可参加人员范围ID',
`sortNo` int DEFAULT NULL COMMENT '排序编号',
`minGroupPeople` int DEFAULT NULL COMMENT '最少成团人数',
`maxGroupPeople` int DEFAULT NULL COMMENT '最多成团人数',
`outProvinceYears` int DEFAULT NULL COMMENT '省外几年去一次',
`outProvinceRatio` decimal(10,2) DEFAULT NULL COMMENT '省外人数占比',
`outProvinceRatioType` varchar(50) DEFAULT '当年报名人数' COMMENT '省外人数占比类型',
`outProvinceFixedPeople` int DEFAULT 0 COMMENT '省外固定人数',
`cycleStartYear` int DEFAULT NULL COMMENT '周期开始年度',
`cycleEndYear` int DEFAULT NULL COMMENT '周期结束年度',
`cycleTotalCost` int DEFAULT NULL COMMENT '周期内总费用',
`cycleAllowedTimes` int DEFAULT NULL COMMENT '周期允许次数',
`allowFamily` tinyint(1) DEFAULT 0 COMMENT '是否允许携带家属',
`fillBedInfo` tinyint(1) DEFAULT 1 COMMENT '是否填报床位信息',
`enabled` tinyint(1) DEFAULT 1 COMMENT '是否启用',
`serviceNotice` text COMMENT '服务须知',
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
`updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人',
`updatedAt` bigint DEFAULT NULL COMMENT '修改时间',
`delFlag` tinyint(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
KEY `idx_tour_setting_year` (`year`),
KEY `idx_tour_setting_activity_group` (`activityGroupId`),
KEY `idx_tour_setting_sort` (`sortNo`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养配置';
-- 疗休养配置标段表。
-- 标段先挂在配置上维护,后续线路、旅行社、报名等模块可继续通过 lotId 做业务关联。
CREATE TABLE IF NOT EXISTS `tour_setting_lot` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`settingId` varchar(32) DEFAULT NULL COMMENT '所属配置ID',
`lotName` varchar(50) DEFAULT NULL COMMENT '标段名称',
`lotValue` varchar(50) DEFAULT NULL COMMENT '标段值',
`activityCost` int DEFAULT NULL COMMENT '标段费用',
`allowOverReimbursement` tinyint(1) DEFAULT 0 COMMENT '允许超出报销',
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
`updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人',
`updatedAt` bigint DEFAULT NULL COMMENT '修改时间',
`delFlag` tinyint(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
KEY `idx_tour_setting_lot_setting` (`settingId`),
KEY `idx_tour_setting_lot_value` (`lotValue`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养配置标段';
-- 旅行社管理表。
CREATE TABLE IF NOT EXISTS `tour_travel_agency` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`year` int DEFAULT NULL COMMENT '年度',
`agencyCode` varchar(50) DEFAULT NULL COMMENT '旅行社编号',
`agencyName` varchar(100) DEFAULT NULL COMMENT '旅行社名称',
`contactName` varchar(30) DEFAULT NULL COMMENT '联系人',
`contactPhone` varchar(30) DEFAULT NULL COMMENT '联系人手机',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`remark` text COMMENT '备注',
`mobileThumb` varchar(500) DEFAULT NULL COMMENT '移动端缩略图',
`enabled` tinyint(1) DEFAULT 1 COMMENT '激活状态',
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
`updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人',
`updatedAt` bigint DEFAULT NULL COMMENT '修改时间',
`delFlag` tinyint(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tour_travel_agency_year_code` (`year`, `agencyCode`),
KEY `idx_tour_travel_agency_name` (`agencyName`),
KEY `idx_tour_travel_agency_contact` (`contactName`, `contactPhone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养旅行社';
-- 线路管理表。
CREATE TABLE IF NOT EXISTS `tour_line` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`year` int DEFAULT NULL COMMENT '创建年度',
`lineCode` varchar(50) DEFAULT NULL COMMENT '线路编号',
`lineName` varchar(100) DEFAULT NULL COMMENT '线路名称',
`travelAgencyId` varchar(32) DEFAULT NULL COMMENT '旅行社ID',
`creatorUserId` varchar(32) DEFAULT NULL COMMENT '创建人ID',
`creatorName` varchar(100) DEFAULT NULL COMMENT '创建人',
`unitId` varchar(32) DEFAULT NULL COMMENT '所在单位ID',
`unitName` varchar(100) DEFAULT NULL COMMENT '所在单位',
`lineType` varchar(50) DEFAULT NULL COMMENT '线路类型',
`lotId` varchar(32) DEFAULT NULL COMMENT '时间标段ID',
`lineContent` text COMMENT '线路内容',
`mobileThumb` varchar(500) DEFAULT NULL COMMENT '移动端缩略图',
`openFlag` tinyint(1) DEFAULT 1 COMMENT '是否对外开放',
`directFamilyUnitLine` tinyint(1) DEFAULT 0 COMMENT '是否直系亲属线路',
`enabled` tinyint(1) DEFAULT 1 COMMENT '激活状态',
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
`updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人',
`updatedAt` bigint DEFAULT NULL COMMENT '修改时间',
`delFlag` tinyint(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tour_line_year_code` (`year`, `lineCode`),
KEY `idx_tour_line_year` (`year`),
KEY `idx_tour_line_name` (`lineName`),
KEY `idx_tour_line_agency` (`travelAgencyId`),
KEY `idx_tour_line_creator` (`creatorUserId`),
KEY `idx_tour_line_unit` (`unitId`),
KEY `idx_tour_line_lot` (`lotId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养线路';
-- 疗休养事项表。
CREATE TABLE IF NOT EXISTS `tour_matter` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`year` int DEFAULT NULL COMMENT '年度',
`matterName` varchar(100) DEFAULT NULL COMMENT '事项名称',
`settingId` varchar(32) DEFAULT NULL COMMENT '疗休养配置ID',
`creatorUserId` varchar(32) DEFAULT NULL COMMENT '创建人ID',
`creatorName` varchar(100) DEFAULT NULL COMMENT '创建人',
`unionId` varchar(32) DEFAULT NULL COMMENT '所属工会ID',
`organizationType` varchar(100) DEFAULT NULL COMMENT '组织形式',
`mobileThumb` varchar(500) DEFAULT NULL COMMENT '移动端缩略图',
`enabled` tinyint(1) DEFAULT 1 COMMENT '事项状态',
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
`updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人',
`updatedAt` bigint DEFAULT NULL COMMENT '修改时间',
`delFlag` tinyint(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tour_matter_year_name` (`year`, `matterName`),
KEY `idx_tour_matter_year` (`year`),
KEY `idx_tour_matter_creator` (`creatorUserId`),
KEY `idx_tour_matter_setting` (`settingId`),
KEY `idx_tour_matter_union` (`unionId`),
KEY `idx_tour_matter_org_type` (`organizationType`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养事项';
-- 疗休养事项批次表。
CREATE TABLE IF NOT EXISTS `tour_matter_batch` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`matterId` varchar(32) DEFAULT NULL COMMENT '事项ID',
`lineId` varchar(32) DEFAULT NULL COMMENT '线路ID',
`batchName` varchar(100) DEFAULT NULL COMMENT '批次名称',
`signupStartTime` varchar(20) DEFAULT NULL COMMENT '报名开始时间',
`signupEndTime` varchar(20) DEFAULT NULL COMMENT '报名结束时间',
`travelStartTime` varchar(20) DEFAULT NULL COMMENT '出行开始时间',
`travelEndTime` varchar(20) DEFAULT NULL COMMENT '出行结束时间',
`changeDeadline` varchar(20) DEFAULT NULL COMMENT '变更截止时间',
`contactName` varchar(30) DEFAULT NULL COMMENT '联系人',
`contactPhone` varchar(30) DEFAULT NULL COMMENT '联系方式',
`minGroupPeople` int DEFAULT NULL COMMENT '最少成团人数',
`maxGroupPeople` int DEFAULT NULL COMMENT '最多成团人数',
`estimatedCost` decimal(10,2) DEFAULT NULL COMMENT '预计费用',
`enabled` tinyint(1) DEFAULT 1 COMMENT '状态',
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
`updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人',
`updatedAt` bigint DEFAULT NULL COMMENT '修改时间',
`delFlag` tinyint(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
KEY `idx_tour_matter_batch_matter` (`matterId`),
KEY `idx_tour_matter_batch_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养事项批次';
-- 直系亲属线路报名信息表。
CREATE TABLE IF NOT EXISTS `tour_ledger_direct_relative` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`ledgerId` varchar(32) DEFAULT NULL COMMENT '台账ID',
`relativeName` varchar(100) DEFAULT NULL COMMENT '亲属姓名',
`unitName` varchar(100) DEFAULT NULL COMMENT '所在单位',
`relationshipCode` varchar(50) DEFAULT NULL COMMENT '亲属关系编码',
`relationshipName` varchar(50) DEFAULT NULL COMMENT '亲属关系',
`lineId` varchar(32) DEFAULT NULL COMMENT '线路ID',
`lineName` varchar(100) DEFAULT NULL COMMENT '线路名称',
`travelStartTime` varchar(20) DEFAULT NULL COMMENT '出行开始日期',
`travelEndTime` varchar(20) DEFAULT NULL COMMENT '出行结束日期',
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
`updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人',
`updatedAt` bigint DEFAULT NULL COMMENT '修改时间',
`delFlag` tinyint(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
KEY `idx_tour_ledger_direct_relative_ledger` (`ledgerId`),
KEY `idx_tour_ledger_direct_relative_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养直系亲属线路报名信息';
@@ -0,0 +1,2 @@
ALTER TABLE `tour_setting_lot`
ADD COLUMN `allowOverReimbursement` tinyint(1) DEFAULT 0 COMMENT '允许超出报销' AFTER `activityCost`;
+1 -1
View File
@@ -32,7 +32,7 @@ var ioc={
// 单个文件最大尺寸(大约的值,单位为字节,即 209715200 为 200M) // 单个文件最大尺寸(大约的值,单位为字节,即 209715200 为 200M)
maxFileSize : 209715200, maxFileSize : 209715200,
// 正则表达式匹配可以支持的文件名 // 正则表达式匹配可以支持的文件名
nameFilter : '^(.+[.])(gif||svg|jpg|png|jpeg|doc|docx|xls|xlsx|rar|zip|7z|txt|pdf|pptx|ppt|mp4)$' nameFilter : '^(.+[.])(gif||svg|jpg|png|jpeg|doc|docx|xls|xlsx|rar|zip|7z|txt|pdf|pptx|ppt|flv|swf|mkv|avi|rm|rmvb|wmv|mp4|mov|mpg|mpeg|webm|m4v|3gp|3g2|ts|mts|m2ts|ogv)$'
} }
}, },
fileUpload : { fileUpload : {
Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

@@ -322,29 +322,51 @@ module.exports = {
return true return true
} }
// 去除前后空格并转换为数组 const valid = this.isAcceptFile(file)
const acceptTypes = this.fileAccept
.trim()
.split(",")
.map((type) => type.trim().toUpperCase())
let fileType
const dotIndex = file.name.lastIndexOf(".")
if (dotIndex !== -1) {
fileType = file.name.substring(dotIndex).toUpperCase()
} else {
this.$message.error("文件必须包含有效的扩展名!")
return false
}
const valid = acceptTypes.includes(fileType)
if (valid) { if (valid) {
return true return true
} }
this.$message.error(`文件只能是 ${this.fileAccept} 格式!`) this.$message.error("文件只能是 " + this.fileAccept + " 格式!")
return false return false
}, },
// 兼容 accept="video/*" 这类 MIME 通配写法,同时保留原有扩展名白名单校验。
isAcceptFile(file) {
if (!this.fileAccept || typeof this.fileAccept !== "string") {
return true
}
const acceptTypes = this.getAcceptTypes()
const fileName = file.name || ""
const dotIndex = fileName.lastIndexOf(".")
const fileType = dotIndex !== -1 ? fileName.substring(dotIndex).toUpperCase() : ""
const mimeType = (file.type || "").toUpperCase()
if (!fileType) {
this.$message.error("文件必须包含有效的扩展名!")
return false
}
return acceptTypes.some((acceptType) => {
if (acceptType.endsWith("/*")) {
return mimeType.startsWith(acceptType.substring(0, acceptType.length - 1)) || this.matchVideoWildcard(acceptType, fileType)
}
return acceptType === fileType
})
},
// 部分浏览器或系统无法识别视频 MIME,此处用视频后缀兜底保证 video/* 可覆盖常见视频格式。
matchVideoWildcard(acceptType, fileType) {
const videoExtTypes = [".FLV", ".SWF", ".MKV", ".AVI", ".RM", ".RMVB", ".WMV", ".MP4", ".MOV", ".MPG", ".MPEG", ".WEBM", ".M4V", ".3GP", ".3G2", ".TS", ".MTS", ".M2TS", ".OGV"]
return acceptType === "VIDEO/*" && videoExtTypes.includes(fileType)
},
getAcceptTypes() {
return this.fileAccept
.trim()
.split(",")
.map((type) => type.trim().toUpperCase())
},
// 预览图片 // 预览图片
handlePreview(file) { handlePreview(file) {
const suffix = file.name.substring(file.name.lastIndexOf(".") + 1).toLowerCase() const suffix = file.name.substring(file.name.lastIndexOf(".") + 1).toLowerCase()
@@ -571,24 +593,13 @@ module.exports = {
} }
if (this.fileAccept) { if (this.fileAccept) {
// 去除前后空格并转换为数组
const acceptTypes = this.fileAccept
.trim()
.split(",")
.map((type) => type.trim().toUpperCase())
const validFiles = data.files.filter((file) => { const validFiles = data.files.filter((file) => {
let fileType return this.isAcceptFile(file)
const dotIndex = file.name.lastIndexOf(".")
if (dotIndex !== -1) {
fileType = file.name.substring(dotIndex).toUpperCase()
}
return acceptTypes.includes(fileType)
}) })
if (validFiles.length < data.files.length) { if (validFiles.length < data.files.length) {
data.files = validFiles data.files = validFiles
this.$message.error(`文件只能是 ${this.fileAccept} 格式,已自动过滤掉不符合要求文件!`) this.$message.error("文件只能是 " + this.fileAccept + " 格式,已自动过滤掉不符合要求文件!")
} }
} }
@@ -0,0 +1,353 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<search :is-search-button="false">
<search-item label="年度">
<el-date-picker
v-model="pageForm.year"
type="year"
value-format="yyyy"
placeholder="请选择年度"
style="width: 100%">
</el-date-picker>
</search-item>
<search-item label="线路名称">
<el-input
v-model="pageForm.lineName"
clearable
placeholder="请输入线路名称"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="线路类型">
<el-select v-model="pageForm.lineType" clearable filterable placeholder="请选择线路类型" style="width: 100%">
<el-option v-for="item in lineTypeOptions" :key="item.lineType" :label="item.lineType" :value="item.lineType"></el-option>
</el-select>
</search-item>
<search-item label="所属工会">
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%">
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<div class="search-query" style="margin-left: auto;">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool :app="this" label="线路列表"></table-tool>
<el-table
v-loading="tableLoading"
:data="tableData"
:size="tableSize"
border
:default-sort="{prop: 'lineName', order: 'ascending'}"
:row-class-name="tableRowClassName"
@sort-change="pageOrder">
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="线路名称" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="出行时段" prop="travelPeriod" width="260" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="事项名称" prop="matterName" min-width="200" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所属工会" prop="unionName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="线路类型" prop="lineType" width="140" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="最少成团人数" prop="minGroupPeople" width="140" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
{{ row.minGroupPeople || 0 }}人
</template>
</el-table-column>
<el-table-column label="最多成团人数" prop="maxGroupPeople" width="140" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
{{ row.maxGroupPeople || 0 }}人
</template>
</el-table-column>
<el-table-column label="报名人数" prop="signupCount" width="120" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="Number(row.signupCount || 0) > 0 ? 'success' : 'info'">{{ row.signupCount || 0 }}人</el-tag>
</template>
</el-table-column>
<el-table-column label="成团状态" prop="groupStatusName" width="120" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="groupStatusType(row.groupStatus)">{{ row.groupStatusName || '未成团' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="160" align="center" header-align="center" fixed="right">
<template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
<el-button size="mini" type="primary" @click="exportLine(row)">导出</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<el-drawer
:title="viewTitle"
:visible.sync="viewVisible"
direction="rtl"
size="72%"
custom-class="tour-group-view-drawer"
:close-on-click-modal="false">
<div class="tour-group-view">
<el-card shadow="never">
<search :is-search-button="false">
<search-item label="姓名/工号">
<el-input
v-model="viewForm.keyword"
clearable
placeholder="请输入姓名或工号"
style="width: 100%"
@keyup.enter.native="viewSearch">
</el-input>
</search-item>
<search-item label="所属工会">
<el-select v-model="viewForm.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%">
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<div class="search-query" style="margin-left: auto;">
<el-button size="medium" type="primary" icon="el-icon-search" @click="viewSearch">搜索</el-button>
<el-button size="medium" @click="viewReset">重置</el-button>
</div>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<div class="tour-group-view-title">报名人员列表</div>
<el-table
v-loading="viewLoading"
:data="viewTableData"
:size="tableSize"
border
@sort-change="viewPageOrder">
<el-table-column label="序号" type="index" :index="viewIndexMethod" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="手机号码" prop="mobile" width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="身份证号" prop="idCard" min-width="190" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所属工会" prop="unionName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
</el-table>
<el-row class="el-pagination-container tour-group-view-pagination">
<el-pagination
background
:current-page="viewForm.pageNumber"
:page-size="viewForm.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="viewForm.totalCount"
layout="total, sizes, prev, pager, next, jumper"
@size-change="viewSizeChange"
@current-change="viewCurrentChange">
</el-pagination>
</el-row>
</el-card>
</div>
</el-drawer>
</guava>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
const currentYear = moment().format("YYYY")
return {
unionOptions: [],
lineTypeOptions: [],
viewVisible: false,
viewLoading: false,
viewRequestSeq: 0,
viewTitle: "",
viewRow: {},
viewTableData: [],
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "lineName",
pageOrderBy: "ascending",
year: currentYear,
lineName: "",
lineType: "",
unionId: ""
},
viewForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "signupTime",
pageOrderBy: "descending",
keyword: "",
unionId: ""
}
}
},
methods: {
resetSearch() {
this.pageForm.year = moment().format("YYYY")
this.pageForm.lineName = ""
this.pageForm.lineType = ""
this.pageForm.unionId = ""
this.loadLineTypeOptions()
this.doSearch()
},
groupStatusType(status) {
if (status === "formed") return "success"
if (status === "over") return "danger"
return "info"
},
tableRowClassName({ row }) {
return this.isDirectFamilyLine(row) ? "direct-family-row" : ""
},
isDirectFamilyLine(row) {
if (!row) return false
return row.directFamilyUnitLine === true
|| row.directFamilyUnitLine === 1
|| row.directFamilyUnitLine === "1"
},
openView(row) {
this.viewRow = row || {}
this.viewTitle = this.viewRow.lineName || "报名人员"
this.viewVisible = true
this.viewForm.pageNumber = 1
this.viewForm.pageSize = 10
this.viewForm.totalCount = 0
this.viewForm.pageOrderName = "signupTime"
this.viewForm.pageOrderBy = "descending"
this.viewForm.keyword = ""
this.viewForm.unionId = ""
this.viewTableData = []
this.loadViewData()
},
viewSearch() {
this.viewForm.pageNumber = 1
this.loadViewData()
},
viewReset() {
this.viewForm.keyword = ""
this.viewForm.unionId = ""
this.viewForm.pageNumber = 1
this.loadViewData()
},
viewIndexMethod(index) {
return (this.viewForm.pageNumber - 1) * this.viewForm.pageSize + index + 1
},
viewSizeChange(size) {
this.viewForm.pageSize = size
this.viewForm.pageNumber = 1
this.loadViewData()
},
viewCurrentChange(page) {
this.viewForm.pageNumber = page
this.loadViewData()
},
viewPageOrder({ prop, order }) {
this.viewForm.pageOrderName = prop || "signupTime"
this.viewForm.pageOrderBy = order || "descending"
this.viewForm.pageNumber = 1
this.loadViewData()
},
loadViewData() {
if (!this.viewRow || !this.viewRow.matterId) {
this.viewTableData = []
this.viewForm.totalCount = 0
return
}
this.viewLoading = true
const requestSeq = ++this.viewRequestSeq
this.$axios.post(loc() + "/signupPageData", {
matterId: this.viewRow.matterId,
keyword: this.viewForm.keyword,
unionId: this.viewForm.unionId,
pageNumber: this.viewForm.pageNumber,
pageSize: this.viewForm.pageSize,
pageOrderName: this.viewForm.pageOrderName,
pageOrderBy: this.viewForm.pageOrderBy
}).then((res) => {
if (requestSeq !== this.viewRequestSeq) return
if (res.code === 0) {
const data = res.data || {}
this.viewTableData = data.list || []
this.viewForm.totalCount = data.totalCount || 0
} else {
this.$message.warning(res.msg || "查询失败")
}
}).finally(() => {
if (requestSeq === this.viewRequestSeq) {
this.viewLoading = false
}
})
},
exportLine(row) {
if (!row || !row.matterId) {
this.$message.warning("线路信息不完整")
return
}
window.location.href = loc() + "/exportParticipants?matterId=" + encodeURIComponent(row.matterId)
},
loadUnionOptions() {
this.$axios.post(loc() + "/unionOptions").then((res) => {
if (res.code === 0) {
this.unionOptions = res.data || []
}
})
},
loadLineTypeOptions() {
this.$axios.post(loc() + "/lineTypeOptions", {
year: this.pageForm.year
}).then((res) => {
if (res.code === 0) {
this.lineTypeOptions = (res.data || []).filter(item => item.lineType)
}
})
}
},
mounted() {
this.loadUnionOptions()
this.loadLineTypeOptions()
this.pageData()
},
watch: {
"pageForm.year"() {
this.pageForm.lineType = ""
this.loadLineTypeOptions()
}
}
})
</script>
<style>
.tour-group-view-drawer .el-drawer__body {
background: #f5f7fa;
padding: 12px;
overflow: auto;
}
.tour-group-view-pagination {
margin-top: 12px;
text-align: right;
}
.tour-group-view-title {
border-left: 4px solid #0079c2;
color: #0079c2;
font-size: 14px;
font-weight: 600;
line-height: 16px;
margin-bottom: 12px;
padding-left: 10px;
}
.el-table .direct-family-row > td,
.el-table .direct-family-row > td .cell {
color: #f56c6c;
}
</style>
<!--#
}
#-->
@@ -0,0 +1,653 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<search :is-search-button="false">
<search-item label="开始年度">
<el-date-picker
v-model="pageForm.startYear"
type="year"
value-format="yyyy"
placeholder="请选择开始年度"
style="width: 100%">
</el-date-picker>
</search-item>
<search-item label="结束年度">
<el-date-picker
v-model="pageForm.endYear"
type="year"
value-format="yyyy"
placeholder="请选择结束年度"
style="width: 100%">
</el-date-picker>
</search-item>
<search-item label="姓名/工号">
<el-input
v-model="pageForm.keyword"
clearable
placeholder="请输入姓名或工号"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="所属工会">
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%">
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="线路">
<el-select v-model="pageForm.lineId" clearable filterable placeholder="请选择线路" style="width: 100%">
<el-option v-for="item in lineOptions" :key="item.lineId" :label="item.lineName" :value="item.lineId"></el-option>
</el-select>
</search-item>
<search-item label="出行时段">
<el-select v-model="pageForm.travelPeriod" clearable filterable placeholder="请选择出行时段" style="width: 100%">
<el-option v-for="item in travelPeriodOptions" :key="item.travelPeriod" :label="item.travelPeriod" :value="item.travelPeriod"></el-option>
</el-select>
</search-item>
<search-item label="线路类型">
<dict-select
v-model="pageForm.lineType"
code="lineType"
option_value="name"
placeholder="请选择线路类型">
</dict-select>
</search-item>
<div class="search-query" style="margin-left: auto;">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<div class="tour-ledger-toolbar">
<div class="tour-ledger-toolbar-title">
<table-tool :app="this" label="台账列表"></table-tool>
</div>
<div class="tour-ledger-actions">
<el-button size="medium" type="primary" icon="el-icon-upload2" @click="showImportDialog = true">参加人员导入</el-button>
<el-button size="medium" type="primary" icon="el-icon-check" @click="setParticipants">设置参加人员</el-button>
<el-button size="medium" type="primary" icon="el-icon-download" @click="exportUnionSignupZip">导出分工会报名压缩包</el-button>
</div>
<div class="tour-ledger-scope">
<el-button
class="tour-scope-btn"
size="medium"
:class="{'is-active': pageForm.directFamilyOnly}"
@click="setDirectFamilyOnly">
参加直系亲属单位线路人员
</el-button>
<el-button
class="tour-scope-btn"
size="medium"
:class="{'is-active': pageForm.overCostOnly}"
@click="setOverCostOnly">
申请超出费用由单位承担人员
</el-button>
</div>
</div>
<el-table
ref="tableRef"
v-loading="tableLoading"
:data="tableData"
:size="tableSize"
border
row-key="id"
:row-class-name="tableRowClassName"
@selection-change="handleSelectionChange"
@sort-change="pageOrder">
<el-table-column type="selection" width="55" align="center" header-align="center"></el-table-column>
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所在单位" prop="unitName" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="报名线路" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="出行时段" prop="travelPeriod" min-width="260" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="线路类型" prop="lineType" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="报名时间" prop="signupTime" width="170" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="携带家属" prop="familyCount" width="120" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="Number(row.familyCount || 0) > 0 ? 'success' : 'info'">{{ row.familyCount || 0 }}人</el-tag>
</template>
</el-table-column>
<el-table-column label="是否参加" prop="joined" width="120" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="row.joined ? 'success' : 'info'">{{ row.joined ? '是' : '否' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="报销超出费用" prop="overCostReimbursed" width="140" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="row.overCostReimbursed ? 'success' : 'info'">{{ row.overCostReimbursed ? '是' : '否' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="160" align="center" header-align="center" fixed="right">
<template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
<el-button size="mini" type="danger" @click="doDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
<div class="tour-ledger-tip">温馨提醒:走审核流程的报名,审核通过后才在台账中显示</div>
</el-card>
</guava>
<excel-import
ref="excelImportRef"
url="/platform/tour/ledger/importParticipants"
template_url="/platform/tour/ledger/downloadTemplate"
:visible.sync="showImportDialog"
title="参加人员导入"
width="700px"
@import-success="afterImport"
:extra_params="{}">
</excel-import>
<el-dialog
title="台账详情"
:visible.sync="detailVisible"
:close-on-click-modal="false"
width="72%">
<div class="tour-ledger-section">
<div class="tour-ledger-title">教职工信息</div>
<el-descriptions :column="3" border size="medium">
<el-descriptions-item label="工号">{{ detail.jobNo || '' }}</el-descriptions-item>
<el-descriptions-item label="姓名">{{ detail.userName || '' }}</el-descriptions-item>
<el-descriptions-item label="性别">{{ detail.gender || '' }}</el-descriptions-item>
<el-descriptions-item label="身份证号">{{ detail.idCard || '' }}</el-descriptions-item>
<el-descriptions-item label="所在单位">{{ detail.unitName || '' }}</el-descriptions-item>
<el-descriptions-item label="所在工会">{{ detail.unionName || '' }}</el-descriptions-item>
</el-descriptions>
</div>
<div class="tour-ledger-section mt10">
<div class="tour-ledger-title">
报名信息
<el-link v-if="detailHasWorkflow()" type="primary" @click="openChart">点击查看流程图</el-link>
</div>
<el-descriptions :column="3" border size="medium">
<el-descriptions-item label="报名时间">{{ detail.signupTime || '' }}</el-descriptions-item>
<el-descriptions-item label="报名线路">{{ detail.lineName || '' }}</el-descriptions-item>
<el-descriptions-item label="线路类型">{{ detail.lineType || '' }}</el-descriptions-item>
<el-descriptions-item label="报名酒店">{{ detail.hotelName || '' }}</el-descriptions-item>
<el-descriptions-item label="报名旅行社">{{ detail.travelAgencyName || '' }}</el-descriptions-item>
<el-descriptions-item label="出行时间">{{ detail.travelPeriod || '' }}</el-descriptions-item>
<el-descriptions-item v-if="fillBedInfo" label="床型">{{ detail.bedType || '' }}</el-descriptions-item>
<el-descriptions-item v-if="fillBedInfo" label="床位信息">{{ detail.bedInfo || '' }}</el-descriptions-item>
<el-descriptions-item v-if="fillBedInfo" label="意向拼床人">{{ detail.intendedRoommate || '' }}</el-descriptions-item>
<el-descriptions-item label="是否携带家属">{{ familyText() }}</el-descriptions-item>
<el-descriptions-item label="是否参加">{{ detail.joined ? '是' : '否' }}</el-descriptions-item>
<el-descriptions-item label="是否报销">{{ detail.reimbursed ? '是' : '否' }}</el-descriptions-item>
<el-descriptions-item label="报销超出费用">{{ detail.overCostReimbursed ? '是' : '否' }}</el-descriptions-item>
</el-descriptions>
</div>
<div v-if="isDirectFamilyLine()" class="tour-ledger-section mt10">
<div class="tour-ledger-title">直系亲属线路</div>
<el-descriptions :column="3" border size="medium">
<el-descriptions-item label="亲属姓名">{{ directRelative.relativeName || '' }}</el-descriptions-item>
<el-descriptions-item label="所在单位">{{ directRelative.unitName || '' }}</el-descriptions-item>
<el-descriptions-item label="亲属关系">{{ directRelative.relationshipName || '' }}</el-descriptions-item>
<el-descriptions-item label="线路名称">{{ directRelative.lineName || '' }}</el-descriptions-item>
<el-descriptions-item label="出行开始日期">{{ directRelative.travelStartTime || '' }}</el-descriptions-item>
<el-descriptions-item label="出行结束日期">{{ directRelative.travelEndTime || '' }}</el-descriptions-item>
</el-descriptions>
</div>
<div v-if="showFamilySection()" class="tour-ledger-section mt10">
<div class="tour-ledger-title">家属信息</div>
<el-table :data="familyData" border :size="tableSize" empty-text="暂无家属信息">
<el-table-column label="序号" type="index" width="70" align="center" header-align="center"></el-table-column>
<el-table-column label="家属姓名" prop="familyName" min-width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="性别" prop="gender" width="90" align="center" header-align="center"></el-table-column>
<el-table-column label="身份证号码" prop="idCard" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="年龄" prop="age" width="90" align="center" header-align="center"></el-table-column>
<el-table-column label="关系" prop="relationship" width="110" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column v-if="fillBedInfo" label="床型" prop="bedType" width="110" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column v-if="fillBedInfo" label="床位" prop="bedInfo" min-width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column v-if="fillBedInfo" label="意向拼床人" prop="intendedRoommate" min-width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
</el-table>
</div>
<template v-if="detailHasWorkflow()" v-for="task in doneTasks">
<div class="tour-ledger-section mt10" :key="task.id">
<div class="tour-ledger-title">{{ task.displayName }}</div>
<el-descriptions border :column="3" v-if="task.ext && task.ext.isFirstTaskNode">
<el-descriptions-item label="申请用户">
{{ task.ext.initiatorName }}({{ task.ext.initiatorAccount }})
</el-descriptions-item>
<el-descriptions-item label="申请时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
</el-descriptions-item>
</el-descriptions>
<el-descriptions border :column="3" v-else>
<el-descriptions-item label="办理用户">
{{ (task.taskFormData && task.taskFormData.userName) || '' }}({{ (task.taskFormData && task.taskFormData.loginName) || '' }})
</el-descriptions-item>
<el-descriptions-item label="办理时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext && task.ext.submitType"></dict-tag>
</el-descriptions-item>
<el-descriptions-item label="办理意见" :span="3">
{{ (task.taskFormData && (task.taskFormData.opinion || task.taskFormData.tf_opinion)) || '' }}
</el-descriptions-item>
</el-descriptions>
</div>
</template>
<snaker-chart ref="snakerChartRef"></snaker-chart>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="detailVisible = false">确定</el-button>
</span>
</el-dialog>
</div>
<style>
.tour-ledger-section {
padding: 0 2px;
}
.tour-ledger-title {
border-left: 4px solid #0079c2;
color: #0079c2;
font-size: 14px;
font-weight: 600;
line-height: 16px;
margin-bottom: 14px;
padding-left: 10px;
}
.tour-ledger-toolbar {
display: flex;
align-items: center;
flex-wrap: nowrap;
gap: 24px;
margin-bottom: 12px;
white-space: nowrap;
}
.tour-ledger-toolbar-title {
flex: 0 0 auto;
}
.tour-ledger-actions {
display: flex;
align-items: center;
flex: 0 0 auto;
gap: 10px;
white-space: nowrap;
}
.tour-ledger-actions .el-button {
margin: 0;
}
.tour-ledger-scope {
display: flex;
align-items: center;
flex: 0 0 auto;
flex-wrap: nowrap;
gap: 12px;
margin: 0;
white-space: nowrap;
}
.tour-ledger-scope .el-button {
margin: 0;
}
.tour-ledger-scope .el-button + .el-button {
margin-left: 0;
}
.tour-ledger-scope .tour-scope-btn {
background: #ecf5ff;
border-color: #b3d8ff;
border-radius: 4px;
color: #0079c2;
font-weight: 600;
height: 34px;
line-height: 1;
padding: 8px 18px;
}
.tour-ledger-scope .tour-scope-btn:hover,
.tour-ledger-scope .tour-scope-btn:focus {
background: #d9ecff;
border-color: #66b1ff;
color: #006bb0;
}
.tour-ledger-scope .tour-scope-btn.is-active {
background: #0079c2;
border-color: #0079c2;
box-shadow: 0 2px 6px rgba(0, 121, 194, 0.24);
color: #fff;
}
.tour-ledger-scope .tour-scope-btn.is-active:hover,
.tour-ledger-scope .tour-scope-btn.is-active:focus {
background: #006bb0;
border-color: #006bb0;
color: #fff;
}
.tour-ledger-tip {
color: #f56c6c;
font-family: SimSun, "宋体", serif;
font-size: 9pt;
line-height: 1.6;
margin-top: -4px;
text-align: center;
}
.el-table .direct-family-row > td,
.el-table .direct-family-row > td .cell {
color: #f56c6c;
}
</style>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
data() {
const currentYear = moment().format("YYYY")
return {
detailVisible: false,
unionOptions: [],
lineOptions: [],
travelPeriodOptions: [],
detail: {},
familyData: [],
directRelative: {},
directFamilyUnitLine: false,
fillBedInfo: true,
detailRow: {},
doneTasks: [],
showImportDialog: false,
multipleSelection: [],
filterOptionsTimer: null,
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "signupTime",
pageOrderBy: "descending",
startYear: currentYear,
endYear: currentYear,
keyword: "",
unionId: "",
lineId: "",
travelPeriod: "",
lineType: "",
directFamilyOnly: false,
overCostOnly: false
}
}
},
methods: {
toBoolean(value) {
return value === true || value === 1 || value === "1" || value === "true" || value === "TRUE"
},
tableRowClassName({ row }) {
return this.isDirectFamilyRow(row) ? "direct-family-row" : ""
},
isDirectFamilyRow(row) {
return this.toBoolean(row && row.directFamilyUnitLine) || !!(row && row.directRelativeId)
},
handleSelectionChange(val) {
this.multipleSelection = val || []
},
clearTableSelection() {
this.multipleSelection = []
if (this.$refs.tableRef) {
this.$refs.tableRef.clearSelection()
}
},
setParticipants() {
if (!this.multipleSelection.length) {
this.$message.warning("请选择要设置的参加人员")
return
}
this.$confirm("确定将选中的" + this.multipleSelection.length + "条台账设置为已参加吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post(loc() + "/setParticipants", {
ids: JSON.stringify(this.multipleSelection.map(item => item.id))
}).then((res) => {
if (res.code === 0) {
this.$message.success("设置成功")
this.clearTableSelection()
this.pageData()
} else {
this.$message.warning(res.msg || "设置失败")
}
})
})
},
exportUnionSignupZip() {
const params = new URLSearchParams()
;["startYear", "endYear", "keyword", "unionId", "lineId", "travelPeriod", "lineType"].forEach(key => {
const value = this.pageForm[key]
if (value !== undefined && value !== null && value !== "") {
params.append(key, value)
}
})
if (this.pageForm.directFamilyOnly) {
params.append("directFamilyOnly", "true")
}
if (this.pageForm.overCostOnly) {
params.append("overCostOnly", "true")
}
window.location.href = loc() + "/exportUnionSignupZip?" + params.toString()
},
afterImport() {
this.showImportDialog = false
this.clearTableSelection()
this.pageData()
this.loadFilterOptions()
},
isDirectFamilyLine() {
return this.directFamilyUnitLine
|| this.toBoolean(this.detail && this.detail.directFamilyUnitLine)
|| !!(this.directRelative && this.directRelative.id)
},
detailHasWorkflow() {
return !!(this.detailRow && this.detailRow.instanceId)
},
hasFamily() {
return this.toBoolean(this.detail && this.detail.hasFamily) || this.familyData.length > 0
},
familyText() {
if (this.isDirectFamilyLine()) {
return "否"
}
if (!this.hasFamily()) {
return "否"
}
const count = Number(this.familyData.length || 0)
return count > 0 ? count + "人" : "是"
},
showFamilySection() {
return this.hasFamily() && !this.isDirectFamilyLine()
},
loadDoneTasks() {
this.$axios.post("/flow/common/doneTasks", { bizId: this.detailRow.id }).then((res) => {
if (res.code === 0) {
this.doneTasks = res.data || []
}
})
},
openChart() {
if (!this.detailRow.instanceProcessDefineId || !this.detailRow.instanceId) {
this.$message.warning("暂无流程图信息")
return
}
this.$refs.snakerChartRef.onOpenFull(this.detailRow.instanceProcessDefineId, this.detailRow.instanceId)
},
resetSearch() {
this.pageForm.startYear = moment().format("YYYY")
this.pageForm.endYear = moment().format("YYYY")
this.pageForm.keyword = ""
this.pageForm.unionId = ""
this.pageForm.lineId = ""
this.pageForm.travelPeriod = ""
this.pageForm.lineType = ""
this.pageForm.directFamilyOnly = false
this.pageForm.overCostOnly = false
this.doSearch()
},
openView(row) {
this.detailRow = row || {}
this.detail = {}
this.familyData = []
this.directRelative = {}
this.directFamilyUnitLine = false
this.fillBedInfo = true
this.doneTasks = []
this.$axios.post(loc() + "/detail", { id: row.id }).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.detail = data.ledger || {}
this.detail.travelPeriod = data.travelPeriod || ""
this.familyData = data.families || []
this.directRelative = data.directRelative || {}
this.directFamilyUnitLine = this.toBoolean(data.directFamilyUnitLine)
this.fillBedInfo = data.fillBedInfo === undefined || data.fillBedInfo === null || this.toBoolean(data.fillBedInfo)
this.detailVisible = true
if (this.detailHasWorkflow()) {
this.loadDoneTasks()
}
} else {
this.$message.warning(res.msg || "查询失败")
}
})
},
doDelete(row) {
this.$confirm("确定删除【" + row.userName + "】的报名台账吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post(loc() + "/doDelete", { id: row.id }).then((res) => {
if (res.code === 0) {
this.$message.success("删除成功")
this.pageData()
this.loadFilterOptions()
} else {
this.$message.warning(res.msg || "删除失败")
}
})
})
},
loadUnionOptions() {
this.$axios.post(loc() + "/unionOptions").then((res) => {
if (res.code === 0) {
this.unionOptions = res.data || []
}
})
},
loadFilterOptions() {
this.loadLineOptions()
this.loadTravelPeriodOptions()
},
scheduleFilterOptions(delay) {
if (this.filterOptionsTimer) {
clearTimeout(this.filterOptionsTimer)
}
this.filterOptionsTimer = setTimeout(() => {
this.loadFilterOptions()
this.filterOptionsTimer = null
}, delay === undefined ? 80 : delay)
},
loadLineOptions() {
this.$axios.post(loc() + "/lineOptions", {
startYear: this.pageForm.startYear,
endYear: this.pageForm.endYear,
travelPeriod: this.pageForm.travelPeriod,
lineType: this.pageForm.lineType,
unionId: this.pageForm.unionId,
keyword: this.pageForm.keyword,
directFamilyOnly: this.pageForm.directFamilyOnly,
overCostOnly: this.pageForm.overCostOnly
}).then((res) => {
if (res.code === 0) {
this.lineOptions = res.data || []
if (this.pageForm.lineId && !this.lineOptions.some(item => item.lineId === this.pageForm.lineId)) {
this.pageForm.lineId = ""
}
}
})
},
loadTravelPeriodOptions() {
this.$axios.post(loc() + "/travelPeriodOptions", {
startYear: this.pageForm.startYear,
endYear: this.pageForm.endYear,
lineId: this.pageForm.lineId,
lineType: this.pageForm.lineType,
unionId: this.pageForm.unionId,
keyword: this.pageForm.keyword,
directFamilyOnly: this.pageForm.directFamilyOnly,
overCostOnly: this.pageForm.overCostOnly
}).then((res) => {
if (res.code === 0) {
this.travelPeriodOptions = res.data || []
if (this.pageForm.travelPeriod && !this.travelPeriodOptions.some(item => item.travelPeriod === this.pageForm.travelPeriod)) {
this.pageForm.travelPeriod = ""
}
}
})
},
doSearch() {
this.tableKey = new Date().getTime()
this.pageForm.pageNumber = 1
this.clearTableSelection()
this.pageData()
this.scheduleFilterOptions(0)
},
setDirectFamilyOnly() {
this.pageForm.directFamilyOnly = !this.pageForm.directFamilyOnly
this.pageForm.pageNumber = 1
this.clearTableSelection()
this.pageData()
this.scheduleFilterOptions(0)
},
setOverCostOnly() {
this.pageForm.overCostOnly = !this.pageForm.overCostOnly
this.pageForm.pageNumber = 1
this.clearTableSelection()
this.pageData()
this.scheduleFilterOptions(0)
}
},
mounted() {
this.loadUnionOptions()
this.pageData()
this.scheduleFilterOptions(150)
},
watch: {
"pageForm.startYear"() {
this.pageForm.lineId = ""
this.pageForm.travelPeriod = ""
this.scheduleFilterOptions()
},
"pageForm.endYear"() {
this.pageForm.lineId = ""
this.pageForm.travelPeriod = ""
this.scheduleFilterOptions()
},
"pageForm.lineId"() {
this.scheduleFilterOptions()
},
"pageForm.travelPeriod"() {
this.scheduleFilterOptions()
},
"pageForm.lineType"() {
this.scheduleFilterOptions()
},
"pageForm.unionId"() {
this.scheduleFilterOptions()
}
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,712 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<search :is-search-button="false">
<search-item label="年度">
<el-date-picker
v-model="pageForm.year"
type="year"
value-format="yyyy"
placeholder="请选择年度"
style="width: 100%">
</el-date-picker>
</search-item>
<search-item label="所属工会">
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%">
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="事项名称">
<el-input
v-model="pageForm.matterName"
clearable
placeholder="请输入事项名称"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="组织形式">
<el-select v-model="pageForm.organizationType" clearable filterable placeholder="请选择组织形式" style="width: 100%">
<el-option v-for="item in organizationTypeOptions" :key="item.code" :label="item.name" :value="item.code"></el-option>
</el-select>
</search-item>
<div class="search-query" style="margin-left: auto;">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool :app="this" label="事项列表">
<el-button @click="openAdd" size="medium" type="primary">
<i class="el-icon-plus"></i>
新增
</el-button>
</table-tool>
<el-table
v-loading="tableLoading"
:data="tableData"
:size="tableSize"
border
:row-class-name="tableRowClassName"
@sort-change="pageOrder">
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="年度" prop="year" width="110" sortable="custom" align="center" header-align="center"></el-table-column>
<el-table-column label="事项名称" prop="matterName" min-width="200" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所属工会" prop="unionName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="线路名称" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="组织形式" prop="organizationTypeName" width="150" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="联系人" prop="contactName" width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="联系方式" prop="contactPhone" width="150" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="事项状态" prop="enabled" width="160" align="center" header-align="center">
<template slot-scope="{row}">
<el-switch
v-model="row.enabled"
active-text="启用"
inactive-text="禁用"
@change="toggleEnabled(row)">
</el-switch>
</template>
</el-table-column>
<el-table-column label="操作" width="260" align="center" header-align="center">
<template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openBatchForm(row)">选择线路</el-button>
<el-button size="mini" type="primary" @click="openEdit(row)">编辑</el-button>
<el-button size="mini" type="danger" :loading="row.deleteLoading" @click="doDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</guava>
<el-dialog
:title="title"
:visible.sync="dialogVisible"
:close-on-click-modal="false"
width="54%"
@closed="destroyForm">
<el-form :model="formData" :rules="formRules" label-width="110px" ref="form" :disabled="viewMode">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="年度" prop="year">
<el-date-picker
v-model="formData.year"
type="year"
value-format="yyyy"
placeholder="请选择年度"
style="width: 100%"
@change="yearChange">
</el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="事项状态" prop="enabled">
<el-radio-group v-model="formData.enabled">
<el-radio :label="true">启用</el-radio>
<el-radio :label="false">禁用</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="事项名称" prop="matterName">
<el-input v-model="formData.matterName" maxlength="30" show-word-limit placeholder="请输入事项名称"></el-input>
</el-form-item>
<el-form-item label="创建人">
<el-input v-model="formData.creatorName" readonly placeholder="当前登录人"></el-input>
</el-form-item>
<el-form-item label="疗休养配置" prop="settingId">
<el-select v-model="formData.settingId" clearable filterable placeholder="请选择配置" style="width: 100%">
<el-option v-for="item in settingOptions" :key="item.id" :label="item.configName" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="组织形式" prop="organizationType">
<el-select v-model="formData.organizationType" clearable filterable placeholder="请选择组织形式" style="width: 100%" @change="organizationTypeChange">
<el-option v-for="item in organizationTypeOptions" :key="item.code" :label="item.name" :value="item.code"></el-option>
</el-select>
</el-form-item>
<el-form-item label="所属工会" prop="unionId">
<el-select v-model="formData.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%" :disabled="isUnionDisabled()">
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button v-if="!viewMode" type="primary" :loading="subDis" @click="doSubmit">保存</el-button>
</span>
</el-dialog>
<el-dialog
:title="batchTitle"
:visible.sync="batchFormVisible"
:close-on-click-modal="false"
width="74%">
<el-form :model="batchForm" :rules="batchRules" label-width="110px" ref="batchFormRef">
<el-form-item label="线路" prop="lineId">
<el-select v-model="batchForm.lineId" clearable filterable placeholder="请选择线路" style="width: 100%" @change="lineChange">
<el-option v-for="item in lineOptions" :key="item.id" :label="lineOptionLabel(item)" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="报名开始时间" prop="signupStartTime">
<el-date-picker v-model="batchForm.signupStartTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择时间" style="width: 100%"></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="报名结束时间" prop="signupEndTime">
<el-date-picker v-model="batchForm.signupEndTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择时间" style="width: 100%"></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="出行开始时间" prop="travelStartTime">
<el-date-picker v-model="batchForm.travelStartTime" type="date" value-format="yyyy-MM-dd" placeholder="选择日期" style="width: 100%" @change="travelStartChange"></el-date-picker>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="出行结束时间" prop="travelEndTime">
<el-date-picker v-model="batchForm.travelEndTime" type="date" value-format="yyyy-MM-dd" placeholder="选择日期" style="width: 100%" @change="travelEndChange"></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="联系人" prop="contactName">
<el-input v-model="batchForm.contactName" maxlength="10" placeholder="请输入联系人"></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="联系方式" prop="contactPhone">
<el-input v-model="batchForm.contactPhone" maxlength="11" placeholder="请输入联系方式"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="预计费用" prop="estimatedCost">
<el-input v-model="batchForm.estimatedCost" maxlength="10" placeholder="请输入预计费用"></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="最少成团人数" prop="minGroupPeople">
<el-input-number v-model="batchForm.minGroupPeople" :min="1" :precision="0" controls-position="right" style="width: 100%" @change="peopleChange"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="最多成团人数" prop="maxGroupPeople">
<el-input-number v-model="batchForm.maxGroupPeople" :min="1" :precision="0" controls-position="right" style="width: 100%" @change="peopleChange"></el-input-number>
</el-form-item>
</el-col>
</el-row>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="batchFormVisible = false">取消</el-button>
<el-button type="primary" :loading="batchSubDis" @click="submitBatch">确定</el-button>
</span>
</el-dialog>
</div>
<style>
.el-table .direct-family-row > td,
.el-table .direct-family-row > td .cell {
color: #f56c6c;
}
</style>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
store,
mixins: [initTableMixins],
data() {
const currentYear = moment().format("YYYY")
const validateUnion = (rule, value, callback) => {
if (this.formData.organizationType === "schoolUnion" || value) {
callback()
} else {
callback(new Error("必填"))
}
}
const validateMobile = (rule, value, callback) => {
const mobileReg = /^1[3-9]\d{9}$/
if (!value) {
callback(new Error("必填"))
} else if (!mobileReg.test(value)) {
callback(new Error("手机号格式不正确"))
} else {
callback()
}
}
const validateMoney = (rule, value, callback) => {
const moneyReg = /^(0|[1-9]\d*)(\.\d{1,2})?$/
if (value === "" || value === null || value === undefined) {
callback(new Error("必填"))
} else if (!moneyReg.test(String(value))) {
callback(new Error("请输入非负金额,最多两位小数"))
} else {
callback()
}
}
const validatePeople = (rule, value, callback) => {
if (!value || value <= 0) {
callback(new Error("人数必须大于0"))
} else if (this.batchForm.minGroupPeople && this.batchForm.maxGroupPeople
&& this.batchForm.minGroupPeople > this.batchForm.maxGroupPeople) {
callback(new Error("最少人数不能大于最多人数"))
} else {
callback()
}
}
const validateBatchTime = (rule, value, callback) => {
if (!value) {
callback(new Error("必填"))
return
}
const form = this.batchForm
if (form.signupStartTime && form.signupEndTime && form.signupStartTime >= form.signupEndTime) {
callback(new Error("报名开始时间必须小于报名结束时间"))
return
}
if (form.travelStartTime && form.travelEndTime && form.travelStartTime > form.travelEndTime) {
callback(new Error("出行开始时间不能晚于出行结束时间"))
return
}
if (form.signupEndTime && form.travelStartTime && form.signupEndTime >= form.travelStartTime) {
callback(new Error("报名结束时间必须小于出行开始时间"))
return
}
callback()
}
return {
title: "",
dialogVisible: false,
viewMode: false,
subDis: false,
batchFormVisible: false,
batchSubDis: false,
batchTitle: "",
currentMatter: {},
batchForm: {},
lineOptions: [],
settingOptions: [],
unionOptions: [],
organizationTypeOptions: [],
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
year: currentYear,
matterName: "",
unionId: "",
organizationType: ""
},
formData: {},
formRules: {
year: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
matterName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
settingId: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
unionId: [{ validator: validateUnion, trigger: ["blur", "change"] }],
organizationType: [{ required: true, message: "必填", trigger: ["blur", "change"] }]
},
batchRules: {
lineId: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
signupStartTime: [{ validator: validateBatchTime, trigger: ["blur", "change"] }],
signupEndTime: [{ validator: validateBatchTime, trigger: ["blur", "change"] }],
travelStartTime: [{ validator: validateBatchTime, trigger: ["blur", "change"] }],
travelEndTime: [{ validator: validateBatchTime, trigger: ["blur", "change"] }],
contactName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
contactPhone: [{ validator: validateMobile, trigger: ["blur", "change"] }],
minGroupPeople: [{ validator: validatePeople, trigger: ["blur", "change"] }],
maxGroupPeople: [{ validator: validatePeople, trigger: ["blur", "change"] }],
estimatedCost: [{ validator: validateMoney, trigger: ["blur", "change"] }]
}
}
},
watch: {
"pageForm.year"(year) {
this.loadSettings(year)
},
"batchForm.lineId"() {
if (this.batchFormVisible) {
this.applySelectedLineDefaults(true)
}
},
"batchForm.travelStartTime"() {
if (this.batchFormVisible) {
this.fillTravelEndByLot()
}
},
"batchForm.travelEndTime"() {
if (this.batchFormVisible) {
this.validateTravelFields()
}
}
},
methods: {
resetSearch() {
this.pageForm.year = moment().format("YYYY")
this.pageForm.matterName = ""
this.pageForm.unionId = ""
this.pageForm.organizationType = ""
this.loadSettings(this.pageForm.year)
this.doSearch()
},
tableRowClassName({ row }) {
return this.isDirectFamilyLine(row) ? "direct-family-row" : ""
},
isDirectFamilyLine(row) {
if (!row) return false
return row.directFamilyUnitLine === true
|| row.directFamilyUnitLine === 1
|| row.directFamilyUnitLine === "1"
},
emptyForm() {
const user = this.currentUser()
return {
year: moment().format("YYYY"),
matterName: "",
settingId: "",
creatorUserId: user.id || "",
creatorName: user.username || "",
unionId: "",
organizationType: "",
enabled: true
}
},
openAdd() {
this.title = "新增事项信息"
this.viewMode = false
this.formData = this.emptyForm()
this.loadSettings(this.formData.year, true)
this.dialogVisible = true
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
},
openEdit(row) {
this.title = "编辑事项信息"
this.viewMode = false
this.loadDetail(row.id)
},
openView(row) {
this.title = "查看事项信息"
this.viewMode = true
this.loadDetail(row.id)
},
loadDetail(id) {
this.$axios.post(loc() + "/detail", { id }).then((res) => {
if (res.code === 0) {
this.formData = Object.assign(this.emptyForm(), res.data || {})
this.formData.year = this.formData.year ? String(this.formData.year) : ""
this.ensureSelectedUnionOption()
this.loadSettings(this.formData.year)
this.dialogVisible = true
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
} else {
this.$message.warning(res.msg || "查询失败")
}
})
},
currentUser() {
return (this.$store && this.$store.state && this.$store.state.user) || {}
},
currentUnion() {
return this.currentUser().union || {}
},
organizationTypeChange() {
this.applyOrganizationTypeRule()
this.$nextTick(() => this.$refs.form && this.$refs.form.validateField("unionId"))
},
applyOrganizationTypeRule() {
if (this.formData.organizationType === "schoolUnion") {
this.formData.unionId = ""
return
}
if (this.formData.organizationType === "branchUnion" || this.formData.organizationType === "personal") {
const union = this.currentUnion()
this.formData.unionId = union.id || ""
this.ensureCurrentUnionOption(union)
}
},
ensureCurrentUnionOption(union) {
if (!union || !union.id) return
const exists = this.unionOptions.some(item => item.id === union.id)
if (!exists) {
this.unionOptions.push({ id: union.id, name: union.name || "" })
}
},
ensureSelectedUnionOption() {
if (!this.formData.unionId) return
const exists = this.unionOptions.some(item => item.id === this.formData.unionId)
if (!exists) {
this.unionOptions.push({ id: this.formData.unionId, name: this.formData.unionName || "" })
}
},
isUnionDisabled() {
return ["schoolUnion", "branchUnion", "personal"].includes(this.formData.organizationType)
},
yearChange(year) {
this.formData.settingId = ""
this.loadSettings(year, true)
},
loadSettings(year, autoSelectLatest) {
this.$axios.post(loc() + "/settingOptions", { year }).then((res) => {
if (res.code === 0) {
this.settingOptions = res.data || []
if (autoSelectLatest && !this.formData.settingId && this.settingOptions.length > 0) {
this.formData.settingId = this.settingOptions[0].id
}
}
})
},
loadUnions() {
this.$axios.post(loc() + "/unionOptions").then((res) => {
if (res.code === 0) {
this.unionOptions = res.data || []
}
})
},
loadOrganizationTypes() {
this.$axios.post(loc() + "/organizationTypeOptions").then((res) => {
if (res.code === 0) {
this.organizationTypeOptions = res.data || []
}
})
},
emptyBatchForm() {
return {
id: this.currentMatter.id || "",
lineId: "",
signupStartTime: "",
signupEndTime: "",
travelStartTime: "",
travelEndTime: "",
contactName: "",
contactPhone: "",
minGroupPeople: null,
maxGroupPeople: null,
estimatedCost: ""
}
},
openBatchForm(row) {
this.currentMatter = row
this.loadLines(row.year)
this.batchTitle = "选择线路"
this.batchForm = this.emptyBatchForm()
this.$axios.post(loc() + "/lineConfig", { matterId: row.id }).then((res) => {
if (res.code === 0) {
this.batchForm = Object.assign(this.emptyBatchForm(), res.data || {})
this.batchForm.travelStartTime = this.dateOnly(this.batchForm.travelStartTime)
this.batchForm.travelEndTime = this.dateOnly(this.batchForm.travelEndTime)
this.applySelectedLineDefaults(false)
if (!this.batchForm.minGroupPeople || !this.batchForm.maxGroupPeople) {
this.loadSettingPeople((data) => {
this.batchForm.minGroupPeople = data.minGroupPeople || null
this.batchForm.maxGroupPeople = data.maxGroupPeople || null
})
}
this.batchFormVisible = true
this.$nextTick(() => {
this.applySelectedLineDefaults(true)
this.$refs.batchFormRef && this.$refs.batchFormRef.clearValidate()
})
} else {
this.$message.warning(res.msg || "查询失败")
}
})
},
loadLines(year) {
this.$axios.post(loc() + "/lineOptions", { year }).then((res) => {
if (res.code === 0) {
this.lineOptions = res.data || []
this.applySelectedLineDefaults(true)
}
})
},
lineOptionLabel(item) {
if (!item) return ""
const lineName = this.lineField(item, "lineName") || ""
const lineType = this.lineField(item, "lineType") || ""
return lineType ? lineName + "(" + lineType + ")" : lineName
},
lineChange() {
this.applySelectedLineDefaults(true)
},
travelStartChange() {
this.fillTravelEndByLot()
},
travelEndChange() {
this.validateTravelFields()
},
selectedLine() {
return (this.lineOptions || []).find((item) => this.lineField(item, "id") === this.batchForm.lineId) || null
},
applySelectedLineDefaults(recalculateEnd) {
const line = this.selectedLine()
if (!line) return
const activityCost = this.lineField(line, "activityCost")
if (activityCost !== null && activityCost !== undefined && activityCost !== "") {
this.batchForm.estimatedCost = activityCost
}
if (recalculateEnd) {
this.fillTravelEndByLot()
}
},
fillTravelEndByLot() {
const line = this.selectedLine()
const days = this.lineLotDays(line)
if (!this.batchForm.travelStartTime || !days) return
this.$set(this.batchForm, "travelEndTime", moment(this.batchForm.travelStartTime).add(days - 1, "days").format("YYYY-MM-DD"))
this.validateTravelFields()
},
lineLotDays(line) {
if (!line) return null
const lotDays = this.lineField(line, "lotDays") || this.lineField(line, "lotValue")
if (!/^\d+$/.test(String(lotDays || ""))) return null
const days = parseInt(lotDays, 10)
return days > 0 ? days : null
},
lineField(line, field) {
if (!line) return null
if (line[field] !== undefined) return line[field]
const lowerField = field.toLowerCase()
const matchedKey = Object.keys(line).find((key) => key.toLowerCase() === lowerField)
return matchedKey ? line[matchedKey] : null
},
validateTravelFields() {
this.$nextTick(() => {
if (this.$refs.batchFormRef) {
this.$refs.batchFormRef.validateField(["travelStartTime", "travelEndTime"])
}
})
},
dateOnly(value) {
if (!value) return ""
return String(value).substring(0, 10)
},
loadSettingPeople(callback) {
this.$axios.post(loc() + "/settingPeople", { matterId: this.currentMatter.id }).then((res) => {
if (res.code === 0) {
const data = res.data || {}
callback && callback(data)
}
})
},
peopleChange() {
this.$nextTick(() => {
if (this.$refs.batchFormRef) {
this.$refs.batchFormRef.validateField(["minGroupPeople", "maxGroupPeople"])
}
})
},
submitBatch() {
this.$refs.batchFormRef.validate((valid) => {
if (!valid) return
this.batchSubDis = true
this.$axios.post(loc() + "/lineConfigDoSubmit", this.batchForm).then((res) => {
this.batchSubDis = false
if (res.code === 0) {
this.$message.success("保存成功")
this.batchFormVisible = false
this.pageData()
} else {
this.$message.warning(res.msg || "保存失败")
}
}).catch(() => {
this.batchSubDis = false
})
})
},
doSubmit() {
this.$refs.form.validate((valid) => {
if (!valid) return
this.subDis = true
this.$axios.post(loc() + "/doSubmit", this.formData).then((res) => {
this.subDis = false
if (res.code === 0) {
this.$message.success("保存成功")
this.dialogVisible = false
this.pageData()
} else {
this.$message.warning(res.msg || "保存失败")
}
}).catch(() => {
this.subDis = false
})
})
},
toggleEnabled(row) {
this.$axios.post(loc() + "/doSubmit", row).then((res) => {
if (res.code === 0) {
this.$message.success("状态已更新")
} else {
row.enabled = !row.enabled
this.$message.warning(res.msg || "状态更新失败")
}
}).catch(() => {
row.enabled = !row.enabled
})
},
doDelete(row) {
this.$set(row, "deleteLoading", true)
this.$axios.post(loc() + "/deleteInfo", { id: row.id }).then((res) => {
this.$set(row, "deleteLoading", false)
if (res.code !== 0) {
this.$message.warning(res.msg || "删除检查失败")
return
}
const data = res.data || {}
const signupCount = Number(data.signupCount || 0)
const matterName = row.matterName || ""
if (!data.canDelete) {
this.$alert("事项【" + matterName + "】已有 " + signupCount + " 人报名,不能删除。", "删除提醒", {
confirmButtonText: "知道了",
type: "warning"
})
return
}
this.$confirm("事项【" + matterName + "】暂无人员报名,确认删除吗?", "删除提醒", {
confirmButtonText: "确认删除",
cancelButtonText: "取消",
type: "warning",
confirmButtonClass: "el-button--danger"
}).then(() => {
this.$set(row, "deleteLoading", true)
this.$axios.post(loc() + "/doDelete", { id: row.id }).then((deleteRes) => {
this.$set(row, "deleteLoading", false)
if (deleteRes.code === 0) {
this.$message.success("删除成功")
this.pageData()
} else {
this.$message.warning(deleteRes.msg || "删除失败")
}
}).catch(() => {
this.$set(row, "deleteLoading", false)
})
}).catch(() => {})
}).catch(() => {
this.$set(row, "deleteLoading", false)
})
},
destroyForm() {
this.formData = {}
this.viewMode = false
}
},
mounted() {
this.loadSettings(this.pageForm.year)
this.loadUnions()
this.loadOrganizationTypes()
this.pageData()
}
})
</script>
<!--#
}
#-->
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,23 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<el-empty description="${title!'功能建设中'}">
<template slot="description">
<span>${title!'功能建设中'}正在分阶段建设中</span>
</template>
</el-empty>
</el-card>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app"
})
</script>
<!--#
}
#-->
@@ -0,0 +1,432 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<search :is-search-button="false">
<search-item label="创建年度">
<el-date-picker
v-model="pageForm.year"
type="year"
value-format="yyyy"
placeholder="请选择创建年度"
style="width: 100%">
</el-date-picker>
</search-item>
<search-item label="线路名称">
<el-input
v-model="pageForm.lineName"
clearable
placeholder="请输入线路名称"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="时间标段">
<el-select v-model="pageForm.lotId" clearable filterable placeholder="请选择时间标段" style="width: 100%">
<el-option v-for="item in lotOptions" :key="item.id" :label="item.lotName" :value="item.id"></el-option>
</el-select>
</search-item>
<div class="search-query" style="margin-left: auto;">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool :app="this" label="线路列表">
<el-button @click="openAdd" size="medium" type="primary">
<i class="el-icon-plus"></i>
新增
</el-button>
</table-tool>
<el-table
v-loading="tableLoading"
:data="tableData"
:size="tableSize"
border
:default-sort="{prop: 'enabled', order: 'descending'}"
:row-class-name="tableRowClassName"
@sort-change="pageOrder">
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="创建年度" prop="year" width="120" sortable="custom" align="center" header-align="center"></el-table-column>
<el-table-column label="线路编号" prop="lineCode" width="140" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="线路名称" prop="lineName" min-width="220" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="时间标段" prop="lotName" width="140" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="线路类型" prop="lineType" width="140" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="创建人" prop="creatorName" width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="是否对外开放" prop="openFlag" width="160" align="center" header-align="center">
<template slot-scope="{row}">
<el-switch
v-model="row.openFlag"
active-text="是"
inactive-text="否"
@change="toggleOpenFlag(row)">
</el-switch>
</template>
</el-table-column>
<el-table-column label="激活状态" prop="enabled" width="160" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-switch
v-model="row.enabled"
active-text="启用"
inactive-text="禁用"
@change="toggleEnabled(row)">
</el-switch>
</template>
</el-table-column>
<el-table-column label="操作" width="260" align="center" header-align="center">
<template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
<el-button size="mini" type="primary" @click="openEdit(row)">编辑</el-button>
<el-button size="mini" type="danger" @click="doDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</guava>
<el-dialog
:title="title"
:visible.sync="dialogVisible"
:close-on-click-modal="false"
width="72%"
@closed="destroyEditor">
<el-form :model="formData" :rules="formRules" label-width="110px" ref="form" :disabled="viewMode">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="创建年度" prop="year">
<el-date-picker
v-model="formData.year"
type="year"
value-format="yyyy"
placeholder="请选择创建年度"
style="width: 100%"
@change="yearChange">
</el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="旅行社名称" prop="travelAgencyId">
<el-select v-model="formData.travelAgencyId" clearable filterable placeholder="请选择旅行社" style="width: 100%">
<el-option v-for="item in travelAgencyOptions" :key="item.id" :label="item.agencyName" :value="item.id"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="线路名称" prop="lineName">
<el-input v-model="formData.lineName" maxlength="30" show-word-limit placeholder="请输入线路名称"></el-input>
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="创建人">
<el-input v-model="formData.creatorName" readonly placeholder="当前登录人"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所在单位">
<el-input v-model="formData.unitName" readonly placeholder="当前登录人所在单位"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="线路编号" prop="lineCode">
<el-input v-model="formData.lineCode" maxlength="50" placeholder="建议年度加序号,如202601"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="激活状态" prop="enabled">
<el-radio-group v-model="formData.enabled">
<el-radio :label="true">启用</el-radio>
<el-radio :label="false">禁用</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="是否对外开放" prop="openFlag">
<el-radio-group v-model="formData.openFlag">
<el-radio :label="true">是</el-radio>
<el-radio :label="false">否</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="直系亲属线路" prop="directFamilyUnitLine">
<el-radio-group v-model="formData.directFamilyUnitLine">
<el-radio :label="true">是</el-radio>
<el-radio :label="false">否</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="线路类型" prop="lineType">
<el-select v-model="formData.lineType" clearable placeholder="请选择线路类型" style="width: 100%">
<el-option v-for="item in lineTypeOptions" :key="item.code" :label="item.name" :value="item.name"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="时间标段" prop="lotId">
<el-select v-model="formData.lotId" clearable filterable placeholder="请选择时间标段" style="width: 100%">
<el-option v-for="item in lotOptions" :key="item.id" :label="item.lotName" :value="item.id"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="线路内容" prop="lineContent">
<text-editor v-model="formData.lineContent"></text-editor>
</el-form-item>
<el-form-item label="移动端缩略图" prop="mobileThumb">
<!-- 与旅行社管理保持一致:移动端缩略图使用单图上传并保存URL。 -->
<file-upload
style="--upload-width: 200px;--upload-height:108px"
:upload_number="1"
:upload_size="20971520"
:value.sync="formData.mobileThumb"
accept=".jpg,.jpeg,.png"
complete_result
upload_mode="image"
upload_result_category="interval">
</file-upload>
<div class="el-upload__tip">支持jpg、jpeg、png格式,大小不超过20MB。</div>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button v-if="!viewMode" type="primary" :loading="submitLoading" @click="doSubmit">保存</el-button>
</span>
</el-dialog>
</div>
<style>
.el-table .direct-family-row > td,
.el-table .direct-family-row > td .cell {
color: #f56c6c;
}
</style>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
store,
mixins: [initTableMixins],
data() {
const currentYear = moment().format("YYYY")
return {
title: "",
dialogVisible: false,
viewMode: false,
submitLoading: false,
travelAgencyOptions: [],
lotOptions: [],
lineTypeOptions: [],
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "enabled",
pageOrderBy: "descending",
year: currentYear,
lineName: "",
lotId: ""
},
formData: {},
formRules: {
year: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
travelAgencyId: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
lineName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
lineCode: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
lineType: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
lotId: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
mobileThumb: [{ required: true, message: "请上传移动端缩略图", trigger: ["blur", "change"] }]
}
}
},
methods: {
resetSearch() {
this.pageForm.year = moment().format("YYYY")
this.pageForm.lineName = ""
this.pageForm.lotId = ""
this.loadOptions(this.pageForm.year)
this.doSearch()
},
tableRowClassName({ row }) {
return this.isDirectFamilyLine(row) ? "direct-family-row" : ""
},
isDirectFamilyLine(row) {
if (!row) return false
return row.directFamilyUnitLine === true
|| row.directFamilyUnitLine === 1
|| row.directFamilyUnitLine === "1"
},
emptyForm() {
const user = (this.$store && this.$store.state && this.$store.state.user) || {}
return {
year: moment().format("YYYY"),
travelAgencyId: "",
creatorUserId: user.id || "",
creatorName: user.username || "",
unitId: user.unit?.id || "",
unitName: user.unit?.name || "",
lineName: "",
lineCode: "",
enabled: true,
openFlag: true,
directFamilyUnitLine: false,
lineType: "",
lotId: "",
lineContent: "",
mobileThumb: ""
}
},
openAdd() {
this.title = "新增线路信息"
this.viewMode = false
this.formData = this.emptyForm()
this.loadOptions(this.formData.year)
this.dialogVisible = true
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
},
openEdit(row) {
this.title = "编辑线路信息"
this.viewMode = false
this.loadDetail(row.id)
},
openView(row) {
this.title = "查看线路信息"
this.viewMode = true
this.loadDetail(row.id)
},
loadDetail(id) {
this.$axios.post(loc() + "/detail", { id }).then((res) => {
if (res.code === 0) {
this.formData = Object.assign(this.emptyForm(), res.data || {})
this.formData.year = this.formData.year ? String(this.formData.year) : ""
this.loadOptions(this.formData.year)
this.dialogVisible = true
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
} else {
this.$message.warning(res.msg || "查询失败")
}
})
},
yearChange(year) {
this.formData.travelAgencyId = ""
this.formData.lotId = ""
this.loadOptions(year)
},
loadOptions(year) {
this.loadTravelAgencies(year)
this.loadLots(year)
this.loadLineTypes()
},
loadTravelAgencies(year) {
this.$axios.post(loc() + "/travelAgencyOptions", { year }).then((res) => {
if (res.code === 0) {
this.travelAgencyOptions = res.data || []
}
})
},
loadLots(year) {
this.$axios.post(loc() + "/lotOptions", { year }).then((res) => {
if (res.code === 0) {
this.lotOptions = res.data || []
}
})
},
loadLineTypes() {
this.$axios.post(loc() + "/lineTypeOptions").then((res) => {
if (res.code === 0) {
this.lineTypeOptions = res.data || []
}
})
},
doSubmit() {
this.$refs.form.validate((valid) => {
if (!valid) return
this.submitLoading = true
this.$axios.post(loc() + "/doSubmit", this.formData).then((res) => {
this.submitLoading = false
if (res.code === 0) {
this.$message.success("保存成功")
this.dialogVisible = false
this.pageData()
} else {
this.$message.warning(res.msg || "保存失败")
}
}).catch(() => {
this.submitLoading = false
})
})
},
toggleEnabled(row) {
this.$axios.post(loc() + "/doSubmit", row).then((res) => {
if (res.code === 0) {
this.$message.success("状态已更新")
} else {
row.enabled = !row.enabled
this.$message.warning(res.msg || "状态更新失败")
}
}).catch(() => {
row.enabled = !row.enabled
})
},
toggleOpenFlag(row) {
this.$axios.post(loc() + "/doSubmit", row).then((res) => {
if (res.code === 0) {
this.$message.success("对外开放状态已更新")
} else {
row.openFlag = !row.openFlag
this.$message.warning(res.msg || "对外开放状态更新失败")
}
}).catch(() => {
row.openFlag = !row.openFlag
})
},
doDelete(row) {
this.$confirm("确定删除【" + row.lineName + "】吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post(loc() + "/doDelete", { id: row.id }).then((res) => {
if (res.code === 0) {
this.$message.success("删除成功")
this.pageData()
} else {
this.$message.warning(res.msg || "删除失败")
}
})
})
},
destroyEditor() {
this.formData = {}
this.viewMode = false
}
},
mounted() {
this.loadOptions(this.pageForm.year)
this.pageData()
},
watch: {
"pageForm.year"(year) {
this.pageForm.lotId = ""
this.loadOptions(year)
}
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,547 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<search :is-search-button="false">
<search-item label="开始年度">
<el-date-picker
v-model="pageForm.startYear"
type="year"
value-format="yyyy"
placeholder="请选择开始年度"
style="width: 100%">
</el-date-picker>
</search-item>
<search-item label="结束年度">
<el-date-picker
v-model="pageForm.endYear"
type="year"
value-format="yyyy"
placeholder="请选择结束年度"
style="width: 100%">
</el-date-picker>
</search-item>
<search-item label="姓名/工号">
<el-input
v-model="pageForm.keyword"
clearable
placeholder="请输入姓名或工号"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="所属工会">
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%">
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="线路">
<el-select v-model="pageForm.lineId" clearable filterable placeholder="请选择线路" style="width: 100%">
<el-option v-for="item in lineOptions" :key="item.lineId" :label="item.lineName" :value="item.lineId"></el-option>
</el-select>
</search-item>
<search-item label="出行时段">
<el-select v-model="pageForm.travelPeriod" clearable filterable placeholder="请选择出行时段" style="width: 100%">
<el-option v-for="item in travelPeriodOptions" :key="item.travelPeriod" :label="item.travelPeriod" :value="item.travelPeriod"></el-option>
</el-select>
</search-item>
<search-item label="线路类型">
<el-select v-model="pageForm.lineType" clearable filterable placeholder="请选择线路类型" style="width: 100%">
<el-option v-for="item in lineTypeOptions" :key="item.lineType" :label="item.lineType" :value="item.lineType"></el-option>
</el-select>
</search-item>
<div class="search-query" style="margin-left: auto;">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool :app="this" label="校工会审核">
<el-radio-group class="mr5" size="small" v-model="pageForm.audit" @change="changeAudit">
<el-radio-button :label="true">已审核</el-radio-button>
<el-radio-button :label="false">未审核</el-radio-button>
</el-radio-group>
</table-tool>
<el-table
v-loading="tableLoading"
:data="tableData"
:size="tableSize"
border
@sort-change="pageOrder">
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所在单位" prop="unitName" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="报名线路" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="出行时段" prop="travelPeriod" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="线路类型" prop="lineType" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="报名时间" prop="signupTime" width="170" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="携带家属" prop="familyCount" width="120" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="Number(row.familyCount || 0) > 0 ? 'success' : 'info'">{{ row.familyCount || 0 }}人</el-tag>
</template>
</el-table-column>
<el-table-column label="报销超出费用" prop="overCostReimbursed" width="140" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="row.overCostReimbursed ? 'success' : 'info'">{{ row.overCostReimbursed ? '是' : '否' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="当前节点" prop="curTaskName" width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="流程状态" prop="instanceState" width="120" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="160" align="center" header-align="center" fixed="right">
<template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
<el-button v-if="row.taskState === 10" size="mini" type="primary" @click="openApproval(row)">审核</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<template #edit>
<tour-approval-info ref="infoRef">
<div v-if="showApprovalForm">
<div class="process-title">
{{ formData.taskName }}
</div>
<el-form
:model="formData"
ref="formRef"
label-width="0"
label-suffix=":"
class="flow-task-form">
<el-form-item
label="审批意见"
prop="tf_opinion"
:rules="[{required:true,message:'必填',trigger:['change','blur']}]">
<user-opinion-textarea v-model="formData.tf_opinion"></user-opinion-textarea>
</el-form-item>
</el-form>
<el-row type="flex" justify="end">
<el-button @click="$refs.guava.index()" size="small">取消</el-button>
<el-button @click="handleTaskAction(6)" size="small" type="info">退回到发起人</el-button>
<el-button @click="handleTaskAction(2)" size="small" type="danger">拒绝申请</el-button>
<el-button @click="handleTaskAction(1)" size="small" type="primary">同意申请</el-button>
</el-row>
</div>
</tour-approval-info>
</template>
</guava>
</div>
<style>
#app .search .search-item {
width: calc((100% - 150px) / 4);
}
#app .search .search-query {
margin-left: auto;
}
@media screen and (max-width: 1350px) {
#app .search .search-item {
width: calc((100% - 100px) / 3);
}
}
@media screen and (max-width: 1200px) {
#app .search .search-item {
width: calc((100% - 50px) / 2);
}
}
@media screen and (max-width: 992px) {
#app .search .search-item {
width: 100%;
}
}
.tour-approval-section {
margin-bottom: 14px;
}
.tour-approval-table {
width: 100%;
}
.tour-approval-empty {
color: #909399;
}
</style>
<script nonce="${cspNonce!}">
const TOUR_APPROVAL_INFO = {
template: /*language=HTML*/ `
<div>
<div class="process-title">
报名信息
<el-link type="primary" @click="openChart">点击查看流程图</el-link>
</div>
<el-descriptions :column="3" border class="tour-approval-section">
<el-descriptions-item label="工号">{{ detail.jobNo || '' }}</el-descriptions-item>
<el-descriptions-item label="姓名">{{ detail.userName || '' }}</el-descriptions-item>
<el-descriptions-item label="性别">{{ detail.gender || '' }}</el-descriptions-item>
<el-descriptions-item label="身份证号">{{ detail.idCard || '' }}</el-descriptions-item>
<el-descriptions-item label="所在单位">{{ detail.unitName || '' }}</el-descriptions-item>
<el-descriptions-item label="所在工会">{{ detail.unionName || '' }}</el-descriptions-item>
<el-descriptions-item label="报名时间">{{ detail.signupTime || '' }}</el-descriptions-item>
<el-descriptions-item label="报名线路">{{ detail.lineName || '' }}</el-descriptions-item>
<el-descriptions-item label="出行时段">{{ detail.travelPeriod || '' }}</el-descriptions-item>
<el-descriptions-item label="线路类型">{{ detail.lineType || '' }}</el-descriptions-item>
<el-descriptions-item label="报名酒店">{{ detail.hotelName || '' }}</el-descriptions-item>
<el-descriptions-item label="旅行社">{{ detail.travelAgencyName || '' }}</el-descriptions-item>
<el-descriptions-item label="是否携带家属">
{{ familyText() }}
</el-descriptions-item>
<el-descriptions-item label="报销超出费用">{{ detail.overCostReimbursed ? '是' : '否' }}</el-descriptions-item>
<el-descriptions-item v-if="fillBedInfo" label="意向拼床人">{{ detail.intendedRoommate || '' }}</el-descriptions-item>
<el-descriptions-item v-if="fillBedInfo" label="床型">{{ detail.bedType || '' }}</el-descriptions-item>
<el-descriptions-item v-if="fillBedInfo" label="床位信息">{{ detail.bedInfo || '' }}</el-descriptions-item>
</el-descriptions>
<div v-if="isDirectFamilyLine()" class="tour-approval-section">
<div class="process-title">直系亲属线路</div>
<el-descriptions :column="3" border>
<el-descriptions-item label="亲属姓名">{{ directRelative.relativeName || '' }}</el-descriptions-item>
<el-descriptions-item label="所在单位">{{ directRelative.unitName || '' }}</el-descriptions-item>
<el-descriptions-item label="亲属关系">{{ directRelative.relationshipName || '' }}</el-descriptions-item>
<el-descriptions-item label="线路名称">{{ directRelative.lineName || '' }}</el-descriptions-item>
<el-descriptions-item label="出行开始日期">{{ directRelative.travelStartTime || '' }}</el-descriptions-item>
<el-descriptions-item label="出行结束日期">{{ directRelative.travelEndTime || '' }}</el-descriptions-item>
</el-descriptions>
</div>
<div v-if="showFamilySection()" class="tour-approval-section">
<div class="process-title">亲属信息</div>
<el-table :data="familyData" border size="mini" empty-text="暂无亲属信息" class="tour-approval-table">
<el-table-column label="序号" type="index" width="70" align="center" header-align="center"></el-table-column>
<el-table-column label="家属姓名" prop="familyName" min-width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="性别" prop="gender" width="90" align="center" header-align="center"></el-table-column>
<el-table-column label="身份证号码" prop="idCard" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="年龄" prop="age" width="90" align="center" header-align="center"></el-table-column>
<el-table-column label="关系" prop="relationship" width="110" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column v-if="fillBedInfo" label="床型" prop="bedType" width="110" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column v-if="fillBedInfo" label="床位" prop="bedInfo" min-width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column v-if="fillBedInfo" label="意向拼床人" prop="intendedRoommate" min-width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
</el-table>
</div>
<template v-for="task in doneTasks">
<div class="mt10">
<div class="process-title">{{ task.displayName }}</div>
<el-descriptions border class="flow-task-form" :column="3" :key="task.id" v-if="task.ext.isFirstTaskNode">
<el-descriptions-item label="申请用户">
{{ task.ext.initiatorName }}({{ task.ext.initiatorAccount }})
</el-descriptions-item>
<el-descriptions-item label="申请时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
</el-descriptions-item>
</el-descriptions>
<el-descriptions border class="flow-task-form" :column="3" :key="task.id" v-else>
<el-descriptions-item label="办理用户">
{{ task.taskFormData.userName }}({{ task.taskFormData.loginName }})
</el-descriptions-item>
<el-descriptions-item label="办理时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
</el-descriptions-item>
<el-descriptions-item label="办理意见" :span="3">
{{ task.taskFormData.opinion || task.taskFormData.tf_opinion || '' }}
</el-descriptions-item>
</el-descriptions>
</div>
</template>
<slot></slot>
<snaker-chart ref="snakerChartRef"></snaker-chart>
</div>
`,
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
data() {
return {
row: {},
detail: {},
familyData: [],
directRelative: {},
allowFamily: false,
fillBedInfo: true,
directFamilyUnitLine: false,
doneTasks: []
}
},
methods: {
onOpen(row) {
this.row = row || {}
this.detail = {}
this.familyData = []
this.directRelative = {}
this.allowFamily = false
this.fillBedInfo = true
this.directFamilyUnitLine = false
this.doneTasks = []
this.info()
this.getDoneTasks()
},
info() {
this.$axios.post(loc() + "/detail", { id: this.row.id }).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.detail = data.ledger || {}
this.detail.travelPeriod = data.travelPeriod || this.row.travelPeriod || ""
this.familyData = data.families || []
this.directRelative = data.directRelative || {}
this.allowFamily = this.toBoolean(data.allowFamily)
this.fillBedInfo = data.fillBedInfo === undefined || data.fillBedInfo === null || this.toBoolean(data.fillBedInfo)
this.directFamilyUnitLine = this.toBoolean(data.directFamilyUnitLine)
} else {
this.$message.warning(res.msg || "查询失败")
}
})
},
toBoolean(value) {
return value === true || value === 1 || value === "1"
},
isDirectFamilyLine() {
return this.directFamilyUnitLine
|| this.toBoolean(this.detail.directFamilyUnitLine)
|| (this.directRelative && this.directRelative.id)
},
hasFamily() {
return this.toBoolean(this.detail.hasFamily) || this.familyData.length > 0
},
showFamilySection() {
return this.allowFamily && this.hasFamily() && !this.isDirectFamilyLine()
},
familyText() {
if (this.isDirectFamilyLine()) {
return "否"
}
if (!this.allowFamily) {
return "否"
}
return this.hasFamily() ? "是" : "否"
},
getDoneTasks() {
this.$axios.post("/flow/common/doneTasks", { bizId: this.row.id }).then((res) => {
if (res.code === 0) {
this.doneTasks = res.data || []
}
})
},
openChart() {
if (!this.row.instanceProcessDefineId || !this.row.instanceId) {
this.$message.warning("暂无流程图信息")
return
}
this.$refs.snakerChartRef.onOpenFull(this.row.instanceProcessDefineId, this.row.instanceId)
}
}
}
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
const currentYear = moment().format("YYYY")
return {
unionOptions: [],
lineOptions: [],
travelPeriodOptions: [],
lineTypeOptions: [],
showApprovalForm: false,
formData: {
tf_opinion: ""
},
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "signupTime",
pageOrderBy: "descending",
audit: false,
startYear: currentYear,
endYear: currentYear,
keyword: "",
unionId: "",
lineId: "",
travelPeriod: "",
lineType: ""
}
}
},
methods: {
resetSearch() {
this.pageForm.startYear = moment().format("YYYY")
this.pageForm.endYear = moment().format("YYYY")
this.pageForm.keyword = ""
this.pageForm.unionId = ""
this.pageForm.lineId = ""
this.pageForm.travelPeriod = ""
this.pageForm.lineType = ""
this.loadOptions()
this.doSearch()
},
changeAudit() {
this.pageForm.pageNumber = 1
this.clearCascadeFilters()
this.loadOptions()
this.pageData()
},
clearCascadeFilters() {
this.pageForm.unionId = ""
this.pageForm.lineId = ""
this.pageForm.travelPeriod = ""
this.pageForm.lineType = ""
},
openView(row) {
this.$refs.guava.edit(() => {
this.showApprovalForm = false
this.formData = {
tf_opinion: ""
}
this.$refs.infoRef.onOpen(row)
})
},
openApproval(row) {
this.$refs.guava.edit(() => {
this.showApprovalForm = true
this.formData = {
processTaskId: row.taskId,
taskName: row.curTaskName || row.taskName,
tf_opinion: ""
}
this.$refs.infoRef.onOpen(row)
})
},
handleTaskAction(val) {
this.$refs.formRef.validate((valid) => {
if (!valid) return
this.$confirm("您确定要提交吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
const loading = createLoading()
this.$axios.post("/flow/common/executeTask", {
data: JSON.stringify({
...this.formData,
submitType: val
})
}).then((res) => {
if (res.code === 0) {
this.$refs.guava.index()
this.$message.success(res.msg || "操作成功")
this.doSearch()
} else {
this.$message.warning(res.msg || "操作失败")
}
}).finally(() => {
loading.close()
})
}).catch(() => {})
})
},
loadOptions() {
this.loadUnionOptions()
this.loadLineOptions()
this.loadTravelPeriodOptions()
this.loadLineTypeOptions()
},
baseOptionParams() {
return {
audit: this.pageForm.audit,
startYear: this.pageForm.startYear,
endYear: this.pageForm.endYear,
keyword: this.pageForm.keyword,
unionId: this.pageForm.unionId,
lineId: this.pageForm.lineId,
travelPeriod: this.pageForm.travelPeriod,
lineType: this.pageForm.lineType
}
},
loadUnionOptions() {
this.$axios.post(loc() + "/unionOptions", this.baseOptionParams()).then((res) => {
if (res.code === 0) {
this.unionOptions = res.data || []
if (this.pageForm.unionId && !this.unionOptions.some(item => item.id === this.pageForm.unionId)) {
this.pageForm.unionId = ""
}
}
})
},
loadLineOptions() {
this.$axios.post(loc() + "/lineOptions", this.baseOptionParams()).then((res) => {
if (res.code === 0) {
this.lineOptions = res.data || []
if (this.pageForm.lineId && !this.lineOptions.some(item => item.lineId === this.pageForm.lineId)) {
this.pageForm.lineId = ""
}
}
})
},
loadTravelPeriodOptions() {
this.$axios.post(loc() + "/travelPeriodOptions", this.baseOptionParams()).then((res) => {
if (res.code === 0) {
this.travelPeriodOptions = res.data || []
if (this.pageForm.travelPeriod && !this.travelPeriodOptions.some(item => item.travelPeriod === this.pageForm.travelPeriod)) {
this.pageForm.travelPeriod = ""
}
}
})
},
loadLineTypeOptions() {
this.$axios.post(loc() + "/lineTypeOptions", this.baseOptionParams()).then((res) => {
if (res.code === 0) {
this.lineTypeOptions = res.data || []
if (this.pageForm.lineType && !this.lineTypeOptions.some(item => item.lineType === this.pageForm.lineType)) {
this.pageForm.lineType = ""
}
}
})
}
},
mounted() {
this.loadOptions()
this.pageData()
},
components: {
"tour-approval-info": TOUR_APPROVAL_INFO
},
watch: {
"pageForm.startYear"() {
this.clearCascadeFilters()
this.loadOptions()
},
"pageForm.endYear"() {
this.clearCascadeFilters()
this.loadOptions()
},
"pageForm.lineId"() {
this.loadTravelPeriodOptions()
this.loadLineTypeOptions()
},
"pageForm.travelPeriod"() {
this.loadLineOptions()
this.loadLineTypeOptions()
},
"pageForm.lineType"() {
this.loadLineOptions()
this.loadTravelPeriodOptions()
},
"pageForm.unionId"() {
this.loadLineOptions()
this.loadTravelPeriodOptions()
this.loadLineTypeOptions()
}
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,636 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<search :is-search-button="false">
<search-item label="年度">
<el-date-picker
v-model="pageForm.year"
type="year"
value-format="yyyy"
placeholder="请选择年度"
style="width: 100%">
</el-date-picker>
</search-item>
<search-item label="配置名称">
<el-input
v-model="pageForm.configName"
clearable
placeholder="请输入疗休养配置名称"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<div class="search-query">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool :app="this" label="疗休养配置">
<el-button type="primary" size="medium" @click="openAdd">
<i class="el-icon-plus"></i>
新建
</el-button>
</table-tool>
<el-table
:data="tableData"
:size="tableSize"
border
class="vi-table"
ref="table"
row-key="id"
v-loading="tableLoading"
@sort-change="pageOrder"
>
<el-table-column label="序号" type="index" :index="indexMethod" width="70" align="center" header-align="center"></el-table-column>
<el-table-column label="年度" prop="year" width="100" align="center" header-align="center"></el-table-column>
<el-table-column label="疗休养配置名称" prop="configName" min-width="260" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="周期开始年度" prop="cycleStartYear" width="130" align="center" header-align="center"></el-table-column>
<el-table-column label="最少成团人数" prop="minGroupPeople" width="120" align="center" header-align="center"></el-table-column>
<el-table-column label="最多成团人数" prop="maxGroupPeople" width="120" align="center" header-align="center"></el-table-column>
<el-table-column label="是否携带家属" prop="allowFamily" width="120" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" type="success" v-if="row.allowFamily">允许</el-tag>
<el-tag size="mini" type="info" v-else>不允许</el-tag>
</template>
</el-table-column>
<el-table-column label="是否启用" prop="enabled" width="100" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" type="success" v-if="row.enabled">启用</el-tag>
<el-tag size="mini" type="info" v-else>停用</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" header-align="center">
<template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openEdit(row)">编辑</el-button>
<el-button size="mini" type="danger" @click="doDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</guava>
<el-dialog
:title="title"
:visible.sync="dialogVisible"
:close-on-click-modal="false"
width="64.8%"
@closed="destroyEditor">
<el-form :model="formData" :rules="formRules" label-width="120px" ref="form">
<div v-if="title === '新建疗休养配置'" class="tour-setting-inherit">
<el-button type="primary" plain size="medium" icon="el-icon-copy-document" :loading="inheritLoading" @click="inheritPreviousYearInfo">延用上一年信息</el-button>
</div>
<el-tabs v-model="activeTab" type="card">
<el-tab-pane label="基本信息" name="basic">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="年度" prop="year">
<el-date-picker
v-model="formData.year"
type="year"
value-format="yyyy"
placeholder="请选择年度"
style="width: 100%"
></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="配置名称" prop="configName">
<el-input v-model="formData.configName" maxlength="100" placeholder="请输入配置名称"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="疗休养类型" prop="tourType">
<el-select v-model="formData.tourType" clearable allow-create filterable placeholder="请选择或输入类型" style="width: 100%">
<el-option label="省内疗休养" value="省内疗休养"></el-option>
<el-option label="省外疗休养" value="省外疗休养"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="可参加人员" prop="activityGroupId">
<div class="tour-user-scope">
<el-select
v-model="formData.activityGroupId"
placeholder="请选择可参加人员"
filterable
clearable
style="width: 100%">
<el-option
v-for="item in activityGroupList"
:key="item.groupId"
:label="item.groupName"
:value="item.groupId">
</el-option>
</el-select>
<el-button type="primary" @click="openUserScope">设置</el-button>
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="最少成团人数" prop="minGroupPeople">
<el-input-number v-model="formData.minGroupPeople" :controls="false" :min="0" :precision="0" placeholder="请输入最少成团人数" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="最多成团人数" prop="maxGroupPeople">
<el-input-number v-model="formData.maxGroupPeople" :controls="false" :min="0" :precision="0" placeholder="请输入最多成团人数" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="24">
<div class="tour-setting-basic-divider"></div>
</el-col>
<el-col :span="12">
<el-form-item label="省外几年去一次" prop="outProvinceYears">
<el-input-number v-model="formData.outProvinceYears" :controls="false" :min="0" :precision="0" placeholder="请输入省外间隔年限" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="省外人数占比" prop="outProvinceRatio">
<el-input-number v-model="formData.outProvinceRatio" :controls="false" :min="0" :max="100" :precision="2" placeholder="请输入省外人数占比" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="省外占比类型" prop="outProvinceRatioType">
<el-select v-model="formData.outProvinceRatioType" placeholder="请选择省外占比类型" style="width: 100%" @change="outProvinceRatioTypeChange">
<el-option label="当年报名人数" value="当年报名人数"></el-option>
<el-option label="可参加教职工人数" value="可参加教职工人数"></el-option>
<el-option label="固定人数" value="固定人数"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col v-if="formData.outProvinceRatioType === '固定人数'" :span="12">
<el-form-item label="固定人数" prop="outProvinceFixedPeople">
<el-input-number v-model="formData.outProvinceFixedPeople" :controls="false" :min="0" :precision="0" placeholder="请输入固定人数" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="24">
<div class="tour-setting-basic-divider"></div>
</el-col>
<el-col :span="12">
<el-form-item label="周期开始年度" prop="cycleStartYear">
<el-date-picker
v-model="formData.cycleStartYear"
type="year"
value-format="yyyy"
clearable
placeholder="请选择周期开始年度"
style="width: 100%">
</el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="周期结束年度" prop="cycleEndYear">
<el-date-picker
v-model="formData.cycleEndYear"
type="year"
value-format="yyyy"
clearable
placeholder="请选择周期结束年度"
style="width: 100%">
</el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="周期内总费用" prop="cycleTotalCost">
<el-input-number v-model="formData.cycleTotalCost" :controls="false" :min="0" :precision="0" placeholder="请输入周期内总费用" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="周期允许次数" prop="cycleAllowedTimes">
<el-input-number v-model="formData.cycleAllowedTimes" :controls="false" :min="0" :precision="0" placeholder="请输入周期允许次数" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="24">
<div class="tour-setting-basic-divider"></div>
</el-col>
<el-col :span="12">
<el-form-item label="允许携带家属" prop="allowFamily">
<el-switch v-model="formData.allowFamily" active-text="允许" inactive-text="不允许"></el-switch>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="填报床位信息" prop="fillBedInfo">
<el-switch v-model="formData.fillBedInfo" active-text="是" inactive-text="否"></el-switch>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否启用" prop="enabled">
<el-switch v-model="formData.enabled" active-text="启用" inactive-text="停用"></el-switch>
</el-form-item>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="标段管理" name="lots">
<div style="text-align: right; margin-bottom: 10px;">
<el-button type="primary" size="medium" icon="el-icon-plus" @click="addLot">增加一条</el-button>
</div>
<el-table
:data="formData.lots"
border
class="vi-table"
empty-text="暂无标段"
style="width: 100%">
<el-table-column label="序号" type="index" width="70" align="center" header-align="center"></el-table-column>
<el-table-column label="标段名称" min-width="180" align="center" header-align="center">
<template slot-scope="{row}">
<el-input v-model="row.lotName" maxlength="50" placeholder="请输入标段名称"></el-input>
</template>
</el-table-column>
<el-table-column label="标段值" min-width="160" align="center" header-align="center">
<template slot-scope="{row}">
<el-input v-model="row.lotValue" maxlength="10" placeholder="请输入整数" @input="integerInput(row, 'lotValue', $event)"></el-input>
</template>
</el-table-column>
<el-table-column label="标段费用" min-width="150" align="center" header-align="center">
<template slot-scope="{row}">
<el-input v-model="row.activityCost" maxlength="10" placeholder="请输入整数" @input="integerInput(row, 'activityCost', $event)"></el-input>
</template>
</el-table-column>
<el-table-column label="允许超出报销" width="140" align="center" header-align="center">
<template slot-scope="{row}">
<el-checkbox v-model="row.allowOverReimbursement"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="操作" width="90" align="center" header-align="center">
<template slot-scope="scope">
<el-button type="danger" size="mini" icon="el-icon-delete" @click="deleteLot(scope.$index, scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="服务须知" name="notice">
<el-form-item prop="serviceNotice" label-width="0">
<text-editor v-model="formData.serviceNotice"></text-editor>
</el-form-item>
</el-tab-pane>
</el-tabs>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="doSubmit">确认</el-button>
</span>
</el-dialog>
<drawer-user-scope
ref="drawerUserScope"
:group_id.sync="formData.activityGroupId"
@group_change="handleActivityGroupChange">
</drawer-user-scope>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
// 前端先做成团人数区间校验,后端也会再兜底校验一次。
const checkGroupPeople = (rule, value, callback) => {
if (this.formData.minGroupPeople !== null && this.formData.maxGroupPeople !== null
&& this.formData.minGroupPeople > this.formData.maxGroupPeople) {
callback(new Error("最少成团人数不能大于最多成团人数"))
} else {
callback()
}
}
const checkCycleYear = (rule, value, callback) => {
const startYear = parseInt(this.formData.cycleStartYear, 10)
const endYear = parseInt(this.formData.cycleEndYear, 10)
if (!isNaN(startYear) && !isNaN(endYear) && startYear > endYear) {
callback(new Error("周期开始年度不能大于周期结束年度"))
} else {
callback()
}
}
const checkOutProvinceFixedPeople = (rule, value, callback) => {
if (this.formData.outProvinceRatioType === "固定人数"
&& (value === null || value === undefined || value === "" || value < 0)) {
callback(new Error("固定人数必须大于等于0"))
} else {
callback()
}
}
return {
title: "",
dialogVisible: false,
activeTab: "basic",
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
year: "",
configName: ""
},
activityGroupList: [],
formData: {},
lotDeleteList: [],
inheritLoading: false,
formRules: {
year: [{required: true, message: "必填", trigger: ["blur", "change"]}],
configName: [{required: true, message: "必填", trigger: ["blur", "change"]}],
minGroupPeople: [{validator: checkGroupPeople, trigger: ["blur", "change"]}],
maxGroupPeople: [{validator: checkGroupPeople, trigger: ["blur", "change"]}],
outProvinceRatioType: [{required: true, message: "必填", trigger: ["blur", "change"]}],
outProvinceFixedPeople: [{validator: checkOutProvinceFixedPeople, trigger: ["blur", "change"]}],
cycleStartYear: [{validator: checkCycleYear, trigger: ["blur", "change"]}],
cycleEndYear: [{validator: checkCycleYear, trigger: ["blur", "change"]}]
}
}
},
components: {
"drawer-user-scope": httpVueLoader("/components/module/activity/DrawerUserScope.vue")
},
methods: {
getActivityGroup() {
return this.$axios.post("/platform/activity/basic/scope/getActivityUserScopeGroup").then((res) => {
const data = res.data
this.activityGroupList = (data || []).map((item) => {
return Object.assign({}, item, {
groupId: item.groupId === null || item.groupId === undefined ? "" : String(item.groupId)
})
})
})
},
openUserScope() {
if (this.$refs.drawerUserScope) {
this.$refs.drawerUserScope.groupId = this.formData.activityGroupId || ""
this.$refs.drawerUserScope.userScopeDialog = true
}
},
async handleActivityGroupChange() {
if (this.formData.activityGroupId !== null && this.formData.activityGroupId !== undefined) {
this.formData.activityGroupId = String(this.formData.activityGroupId)
}
await this.getActivityGroup()
},
resetSearch() {
this.pageForm.year = ""
this.pageForm.configName = ""
this.doSearch()
},
defaultFormData() {
return {
year: moment().format("YYYY"),
configName: "",
tourType: "",
activityGroupId: "",
sortNo: 0,
minGroupPeople: 0,
maxGroupPeople: 0,
outProvinceYears: 0,
outProvinceRatio: 0,
outProvinceRatioType: "当年报名人数",
outProvinceFixedPeople: 0,
cycleStartYear: "",
cycleEndYear: "",
cycleTotalCost: null,
cycleAllowedTimes: null,
allowFamily: false,
fillBedInfo: true,
enabled: true,
lots: [],
serviceNotice: ""
}
},
openAdd() {
this.title = "新建疗休养配置"
this.lotDeleteList = []
this.activeTab = "basic"
// 新建时给出默认年度和开关值,减少校工会管理员录入成本。
this.formData = this.defaultFormData()
this.dialogVisible = true
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
},
inheritPreviousYearInfo() {
const currentYear = parseInt(this.formData.year, 10)
if (isNaN(currentYear)) {
this.$message.warning("请先选择年度")
return
}
this.inheritLoading = true
this.$axios.post(loc() + "/previousYearInfo", {year: currentYear}).then((res) => {
this.inheritLoading = false
if (res.code !== 0) {
this.$message.warning(res.msg || "未找到上一年度配置")
return
}
const previous = res.data || {}
const inheritedLots = (previous.lots || []).map(item => {
return {
lotName: item.lotName || "",
lotValue: item.lotValue || "",
activityCost: item.activityCost,
allowOverReimbursement: !!item.allowOverReimbursement
}
})
this.formData = Object.assign(this.defaultFormData(), previous, {
year: String(currentYear),
cycleStartYear: previous.cycleStartYear ? String(previous.cycleStartYear) : "",
cycleEndYear: previous.cycleEndYear ? String(previous.cycleEndYear) : "",
lots: inheritedLots
})
delete this.formData.id
delete this.formData.createdAt
delete this.formData.createdBy
delete this.formData.updatedAt
delete this.formData.updatedBy
delete this.formData.delFlag
if (!this.formData.outProvinceRatioType) {
this.$set(this.formData, "outProvinceRatioType", "当年报名人数")
}
if (this.formData.outProvinceFixedPeople === null || this.formData.outProvinceFixedPeople === undefined) {
this.$set(this.formData, "outProvinceFixedPeople", 0)
}
if (this.formData.fillBedInfo === null || this.formData.fillBedInfo === undefined) {
this.$set(this.formData, "fillBedInfo", true)
}
this.lotDeleteList = []
this.$message.success("已延用上一年配置信息")
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
}).catch(() => {
this.inheritLoading = false
})
},
openEdit(row) {
this.title = "编辑疗休养配置"
this.lotDeleteList = []
this.activeTab = "basic"
this.$axios.post(loc() + "/detail", {id: row.id}).then((res) => {
if (res.code === 0) {
this.formData = Object.assign({
activityGroupId: "",
outProvinceRatioType: "当年报名人数",
outProvinceFixedPeople: 0,
cycleStartYear: "",
cycleEndYear: "",
cycleTotalCost: null,
cycleAllowedTimes: null,
allowFamily: false,
fillBedInfo: true,
enabled: true,
lots: [],
serviceNotice: ""
}, res.data || {})
this.formData.year = this.formData.year ? String(this.formData.year) : ""
if (!this.formData.outProvinceRatioType) {
this.$set(this.formData, "outProvinceRatioType", "当年报名人数")
}
if (this.formData.outProvinceFixedPeople === null || this.formData.outProvinceFixedPeople === undefined) {
this.$set(this.formData, "outProvinceFixedPeople", 0)
}
if (this.formData.fillBedInfo === null || this.formData.fillBedInfo === undefined) {
this.$set(this.formData, "fillBedInfo", true)
}
this.formData.cycleStartYear = this.formData.cycleStartYear ? String(this.formData.cycleStartYear) : ""
this.formData.cycleEndYear = this.formData.cycleEndYear ? String(this.formData.cycleEndYear) : ""
this.formData.lots = (this.formData.lots || []).map(item => Object.assign({}, item, {
allowOverReimbursement: !!item.allowOverReimbursement
}))
this.dialogVisible = true
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
} else {
this.$message.warning(res.msg || "查询失败")
}
})
},
doSubmit() {
this.$refs.form.validate((valid) => {
if (!valid) return
if (!this.validateLots()) return
this.submitLoading = true
// Nutz 对子表集合按字符串化 JSON 绑定更稳定,和体检项目维护的提交方式保持一致。
const submitData = JSON.parse(JSON.stringify(this.formData))
if (submitData.outProvinceRatioType !== "固定人数") {
submitData.outProvinceFixedPeople = 0
}
submitData.lots = JSON.stringify(this.formData.lots || [])
submitData.lotDeleteList = JSON.stringify(this.lotDeleteList)
this.$axios.post(loc() + "/doSubmit", submitData).then((res) => {
this.submitLoading = false
if (res.code === 0) {
this.$message.success("保存成功")
this.dialogVisible = false
this.pageData()
} else {
this.$message.warning(res.msg || "保存失败")
}
}).catch(() => {
this.submitLoading = false
})
})
},
addLot() {
if (!this.formData.lots) {
this.$set(this.formData, "lots", [])
}
this.formData.lots.push({
lotName: "",
lotValue: "",
activityCost: null,
allowOverReimbursement: false
})
},
integerInput(row, field, value) {
const nextValue = String(value || "").replace(/[^\d]/g, "")
this.$set(row, field, nextValue)
},
outProvinceRatioTypeChange(value) {
if (value !== "固定人数") {
this.$set(this.formData, "outProvinceFixedPeople", 0)
}
this.$nextTick(() => this.$refs.form && this.$refs.form.validateField("outProvinceFixedPeople"))
},
validateLots() {
const lots = this.formData.lots || []
for (let i = 0; i < lots.length; i++) {
const row = lots[i]
const hasContent = row && (row.lotName || row.lotValue || row.activityCost !== null && row.activityCost !== undefined && row.activityCost !== "")
if (!hasContent) continue
if (!/^\d+$/.test(String(row.lotValue || ""))) {
this.$message.warning("第" + (i + 1) + "行标段值必须为整数")
return false
}
if (!/^\d+$/.test(String(row.activityCost === null || row.activityCost === undefined ? "" : row.activityCost))) {
this.$message.warning("第" + (i + 1) + "行标段费用必须为整数")
return false
}
}
return true
},
deleteLot(index, row) {
this.$confirm("确定删除该标段吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
if (row && row.id) {
this.lotDeleteList.push(row.id)
}
this.formData.lots.splice(index, 1)
})
},
doDelete(row) {
this.$confirm("确定删除【" + row.configName + "】吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post(loc() + "/doDelete", {id: row.id}).then((res) => {
if (res.code === 0) {
this.$message.success("删除成功")
this.pageData()
} else {
this.$message.warning(res.msg || "删除失败")
}
})
})
},
destroyEditor() {
this.formData = {}
this.lotDeleteList = []
this.inheritLoading = false
this.activeTab = "basic"
}
},
mounted() {
this.getActivityGroup()
this.pageData()
}
})
</script>
<style>
.tour-user-scope {
display: flex;
align-items: center;
gap: 10px;
}
.tour-user-scope .el-select {
flex: 1;
}
.tour-setting-inherit {
margin-bottom: -40px;
padding-right: 96px;
position: relative;
text-align: right;
z-index: 1;
}
.tour-setting-basic-divider {
border-top: 1px dashed #dcdfe6;
margin: 2px 0 18px;
}
</style>
<!--#
}
#-->
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,338 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<search :is-search-button="false">
<search-item label="年度">
<el-date-picker
v-model="pageForm.year"
type="year"
value-format="yyyy"
placeholder="请选择年度"
style="width: 100%">
</el-date-picker>
</search-item>
<search-item label="旅行社名称">
<el-input
v-model="pageForm.agencyName"
clearable
placeholder="请输入旅行社名称"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="联系人">
<el-input
v-model="pageForm.contactName"
clearable
placeholder="请输入联系人"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="联系电话">
<el-input
v-model="pageForm.contactPhone"
clearable
placeholder="请输入联系电话"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<div class="search-query" style="margin-left: auto;">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool :app="this" label="旅行社列表">
<el-button @click="openAdd" size="medium" type="primary">
<i class="el-icon-plus"></i>
新增
</el-button>
</table-tool>
<el-table
v-loading="tableLoading"
:data="tableData"
:size="tableSize"
border
@sort-change="pageOrder">
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="年度" prop="year" width="110" sortable="custom" align="center" header-align="center"></el-table-column>
<el-table-column label="旅行社编号" prop="agencyCode" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="旅行社名称" prop="agencyName" min-width="220" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="联系人" prop="contactName" width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="联系电话" prop="contactPhone" width="150" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="激活状态" prop="enabled" width="160" align="center" header-align="center">
<template slot-scope="{row}">
<el-switch
v-model="row.enabled"
active-text="启用"
inactive-text="禁用"
@change="toggleEnabled(row)">
</el-switch>
</template>
</el-table-column>
<el-table-column label="操作" width="260" align="center" header-align="center">
<template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
<el-button size="mini" type="primary" @click="openEdit(row)">编辑</el-button>
<el-button size="mini" type="danger" @click="doDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</guava>
<el-dialog
:title="title"
:visible.sync="dialogVisible"
:close-on-click-modal="false"
width="72%"
@closed="destroyForm">
<el-form :model="formData" :rules="formRules" label-width="110px" ref="form" :disabled="viewMode">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="年度" prop="year">
<el-date-picker
v-model="formData.year"
type="year"
value-format="yyyy"
placeholder="请选择年度"
style="width: 100%">
</el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="旅行社名称" prop="agencyName">
<el-input v-model="formData.agencyName" maxlength="30" show-word-limit placeholder="请输入旅行社名称"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="旅行社编号" prop="agencyCode">
<el-input v-model="formData.agencyCode" maxlength="50" placeholder="请输入旅行社编号"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="激活状态" prop="enabled">
<el-radio-group v-model="formData.enabled">
<el-radio :label="true">启用</el-radio>
<el-radio :label="false">禁用</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="联系人" prop="contactName">
<el-input v-model="formData.contactName" maxlength="10" show-word-limit placeholder="请输入联系人"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系人手机" prop="contactPhone">
<el-input v-model="formData.contactPhone" maxlength="30" placeholder="请输入联系人手机"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="邮箱" prop="email">
<el-input v-model="formData.email" maxlength="100" placeholder="请输入邮箱"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" type="textarea" maxlength="100" :rows="3" show-word-limit placeholder="请输入备注"></el-input>
</el-form-item>
<el-form-item label="移动端缩略图" prop="mobileThumb">
<!-- 复用课程管理的封面上传逻辑:单图上传,保存URL,供移动端列表展示使用。 -->
<file-upload
style="--upload-width: 200px;--upload-height:108px"
:upload_number="1"
:upload_size="20971520"
:value.sync="formData.mobileThumb"
accept=".jpg,.jpeg,.png"
complete_result
upload_mode="image"
upload_result_category="interval">
</file-upload>
<div class="el-upload__tip">支持jpg、jpeg、png格式,大小不超过20MB。</div>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button v-if="!viewMode" type="primary" :loading="subDis" @click="doSubmit">保存</el-button>
</span>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
const currentYear = moment().format("YYYY")
const validateMobile = (rule, value, callback) => {
const mobileReg = /^1[3-9]\d{9}$/
if (!value) {
callback(new Error("必填"))
} else if (!mobileReg.test(value)) {
callback(new Error("手机号格式不正确"))
} else {
callback()
}
}
const validateEmail = (rule, value, callback) => {
const emailReg = /^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$/
if (!value) {
callback(new Error("必填"))
} else if (!emailReg.test(value)) {
callback(new Error("邮箱格式不正确"))
} else {
callback()
}
}
return {
title: "",
dialogVisible: false,
viewMode: false,
subDis: false,
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
year: currentYear,
agencyName: "",
contactName: "",
contactPhone: ""
},
formData: {},
formRules: {
year: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
agencyName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
agencyCode: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
contactName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
contactPhone: [{ validator: validateMobile, trigger: ["blur", "change"] }],
email: [{ validator: validateEmail, trigger: ["blur", "change"] }]
}
}
},
methods: {
resetSearch() {
this.pageForm.year = moment().format("YYYY")
this.pageForm.agencyName = ""
this.pageForm.contactName = ""
this.pageForm.contactPhone = ""
this.doSearch()
},
emptyForm() {
return {
year: moment().format("YYYY"),
agencyName: "",
agencyCode: "",
contactName: "",
contactPhone: "",
email: "",
remark: "",
mobileThumb: "",
enabled: true
}
},
openAdd() {
this.title = "新增旅行社信息"
this.viewMode = false
this.formData = this.emptyForm()
this.dialogVisible = true
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
},
openEdit(row) {
this.title = "编辑旅行社信息"
this.viewMode = false
this.loadDetail(row.id)
},
openView(row) {
this.title = "查看旅行社信息"
this.viewMode = true
this.loadDetail(row.id)
},
loadDetail(id) {
this.$axios.post(loc() + "/detail", { id }).then((res) => {
if (res.code === 0) {
this.formData = Object.assign(this.emptyForm(), res.data || {})
this.formData.year = this.formData.year ? String(this.formData.year) : ""
this.dialogVisible = true
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
} else {
this.$message.warning(res.msg || "查询失败")
}
})
},
doSubmit() {
this.$refs.form.validate((valid) => {
if (!valid) return
this.subDis = true
this.$axios.post(loc() + "/doSubmit", this.formData).then((res) => {
this.subDis = false
if (res.code === 0) {
this.$message.success("保存成功")
this.dialogVisible = false
this.pageData()
} else {
this.$message.warning(res.msg || "保存失败")
}
}).catch(() => {
this.subDis = false
})
})
},
toggleEnabled(row) {
this.$axios.post(loc() + "/doSubmit", row).then((res) => {
if (res.code === 0) {
this.$message.success("状态已更新")
} else {
row.enabled = !row.enabled
this.$message.warning(res.msg || "状态更新失败")
}
}).catch(() => {
row.enabled = !row.enabled
})
},
doDelete(row) {
this.$confirm("确定删除【" + row.agencyName + "】吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post(loc() + "/doDelete", { id: row.id }).then((res) => {
if (res.code === 0) {
this.$message.success("删除成功")
this.pageData()
} else {
this.$message.warning(res.msg || "删除失败")
}
})
})
},
destroyForm() {
this.formData = {}
this.viewMode = false
}
},
mounted() {
this.pageData()
}
})
</script>
<!--#
}
#-->

Some files were not shown because too many files have changed in this diff Show More