南大三十年教职工疗休养

This commit is contained in:
2026-06-24 10:51:45 +08:00
parent f355f5e7a1
commit 3d0bcabc65
111 changed files with 29665 additions and 0 deletions
@@ -0,0 +1,243 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.controller;
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog;
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.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourUserAssignmentService;
import org.apache.poi.ss.usermodel.Workbook;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
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 javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@IocBean
@Ok("json:full")
@At("/platform/thirtyTeachTour/branchUserAssignment")
public class ThirtyTeachTourBranchUserAssignmentController {
@Inject
private ThirtyTeachTourUserAssignmentService tourUserAssignmentService;
/**
* 分工会人员分配列表入口。
*/
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/ThirtyTeachTour/branchUserAssignment/index.html")
@SaCheckPermission("thirtyTeachTour.branchUserAssignment")
public void index() {
}
/**
* 分页查询当前登录人所在分工会的分配记录,列表数据只来自人员分配表。
*/
@At
@SaCheckPermission("thirtyTeachTour.branchUserAssignment")
public Result pageData(PageForm pageForm, Integer year, String settingId, String matterId,
String personType, String keyword) {
return Result.success(tourUserAssignmentService.branchAssignmentPage(pageForm, year, settingId, matterId,
personType, keyword));
}
/**
* 按分工会人员分配页面当前查询条件导出数据,导出范围不受分页限制。
*/
@At
@SaCheckPermission("thirtyTeachTour.branchUserAssignment")
@Ok("void")
public void exportData(PageForm pageForm, Integer year, String settingId, String matterId,
String personType, String keyword, HttpServletResponse response) {
List<NutMap> rows = tourUserAssignmentService.branchAssignmentExportRows(pageForm, year, settingId, matterId,
personType, keyword);
ExportParams exportParams = new ExportParams();
exportParams.setSheetName("分工会人员分配");
exportParams.setType(ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, assignmentExportEntities(), rows);
CommonDownloadUtil.download("分工会人员分配.xlsx", workbook, response);
}
/**
* 分页查询候选人员,候选范围由疗休养配置可参加人员范围和当前登录人所在分工会共同决定;userIds 仅作为人员选择器筛选条件。
*/
@At
@SaCheckPermission("thirtyTeachTour.branchUserAssignment.assign")
public Result candidatePageData(PageForm pageForm, String settingId, String keyword, @Param("userIds") String userIds) {
return Result.success(tourUserAssignmentService.branchCandidatePage(pageForm, settingId, keyword, parseUserIds(userIds)));
}
/**
* 查询可用于分配的疗休养配置。
*/
@At
@SaCheckPermission("thirtyTeachTour.branchUserAssignment")
public Result settingOptions(Integer year) {
return Result.success(tourUserAssignmentService.listEnabledSettingOptions(year));
}
/**
* 查询分工会可分配线路选项,选项携带事项、线路和旅行社快照信息。
*/
@At
@SaCheckPermission("thirtyTeachTour.branchUserAssignment")
public Result matterOptions(String settingId) {
return Result.success(tourUserAssignmentService.listSignupOpenMatterOptions(settingId));
}
/**
* 查询当前登录人所在分工会在指定配置下的名额使用情况。
*/
@At
@SaCheckPermission("thirtyTeachTour.branchUserAssignment.assign")
public Result quotaInfo(String settingId) {
return Result.success(tourUserAssignmentService.branchQuotaInfo(settingId));
}
/**
* 保存分工会人员分配,保存时数据来源固定为 BRANCH_UNION。
*/
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("thirtyTeachTour.branchUserAssignment.assign")
@SLog(type = "tour", tag = "疗休养分工会人员分配", msg = "保存分工会人员分配")
public Result doAssign(String settingId, String matterId, String personType, @Param("userIds") String userIds) {
List<String> parsedUserIds;
try {
parsedUserIds = parseUserIds(userIds);
} catch (Exception e) {
return Result.error("人员参数错误");
}
try {
return Result.success(tourUserAssignmentService.assignBranchUsers(settingId, matterId, personType, parsedUserIds));
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
}
/**
* 切换当前分工会人员分配记录的正式/替补状态,具体名额和台账保护规则由 service 统一处理。
*/
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("thirtyTeachTour.branchUserAssignment.switchPersonType")
@SLog(type = "tour", tag = "疗休养分工会人员分配", msg = "切换分工会人员类型")
public Result switchPersonType(String id, String personType) {
try {
return Result.success(tourUserAssignmentService.switchCurrentBranchPersonType(id, personType));
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
}
/**
* 给当前分工会已分配正式人员补选分配路线,具体快照回写、代报名和最大成团人数校验由 service 统一处理。
*/
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("thirtyTeachTour.branchUserAssignment.selectMatter")
@SLog(type = "tour", tag = "疗休养分工会人员分配", msg = "选择分工会分配事项")
public Result selectMatter(String id, String matterId) {
try {
return Result.success(tourUserAssignmentService.selectCurrentBranchAssignmentMatter(id, matterId));
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
}
/**
* 恢复当前分工会已取消退出的人员分配记录,只恢复状态,不恢复已删除台账。
*/
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("thirtyTeachTour.branchUserAssignment.cancelRestore")
@SLog(type = "tour", tag = "疗休养分工会人员分配", msg = "取消退出分工会人员分配")
public Result restoreCancel(String id) {
try {
tourUserAssignmentService.restoreCurrentBranchCancelledAssignment(id);
return Result.success();
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
}
/**
* 删除前检查该分配记录是否已经存在对应报名台账。
*/
@At
@SaCheckPermission("thirtyTeachTour.branchUserAssignment.delete")
public Result deleteInfo(String id) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
return Result.success(tourUserAssignmentService.branchDeleteInfo(id));
}
/**
* 删除分工会人员分配记录,只删除 BRANCH_UNION 来源的数据。
*/
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("thirtyTeachTour.branchUserAssignment.delete")
@SLog(type = "tour", tag = "疗休养分工会人员分配", msg = "删除分工会人员分配")
public Result doDelete(String id, Boolean deleteLedger) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
try {
tourUserAssignmentService.deleteCurrentBranchAssignment(id, Boolean.TRUE.equals(deleteLedger));
return Result.success();
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
}
private List<String> parseUserIds(String userIds) {
if (StrUtil.isBlank(userIds)) {
return Collections.emptyList();
}
List<String> list = Json.fromJsonAsList(String.class, userIds);
return Lang.isEmpty(list) ? Collections.emptyList() : list;
}
/**
* 定义人员分配导出字段,保持与页面列表核心字段一致并补充人员基础信息。
*/
private List<ExcelExportEntity> assignmentExportEntities() {
List<ExcelExportEntity> entities = new ArrayList<>();
entities.add(new ExcelExportEntity("年度", "year", 12));
entities.add(new ExcelExportEntity("疗休养配置", "settingName", 24));
entities.add(new ExcelExportEntity("姓名", "userName", 14));
entities.add(new ExcelExportEntity("工号", "loginName", 16));
entities.add(new ExcelExportEntity("性别", "gender", 10));
entities.add(new ExcelExportEntity("年龄", "age", 10));
entities.add(new ExcelExportEntity("身份证号", "idCard", 24));
entities.add(new ExcelExportEntity("手机号", "mobile", 18));
entities.add(new ExcelExportEntity("所属分工会", "unionName", 24));
entities.add(new ExcelExportEntity("所在单位", "unitName", 24));
entities.add(new ExcelExportEntity("线路", "lineName", 30));
entities.add(new ExcelExportEntity("出行时间", "travelPeriod", 26));
entities.add(new ExcelExportEntity("乘车地点", "boardingPlace", 18));
entities.add(new ExcelExportEntity("分配类别", "assignSourceText", 16));
entities.add(new ExcelExportEntity("人员类型", "personTypeText", 14));
entities.add(new ExcelExportEntity("是否退出", "cancelledText", 14));
entities.add(new ExcelExportEntity("分配时间", "assignedAt", 22));
entities.add(new ExcelExportEntity("分配人", "assignedName", 14));
return entities;
}
}
@@ -0,0 +1,641 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.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.thirtyTeachTour.service.ThirtyTeachTourLedgerService;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourMatterService;
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/thirtyTeachTour/group")
public class ThirtyTeachTourGroupController {
@Inject
private ThirtyTeachTourMatterService tourMatterService;
@Inject
private ThirtyTeachTourLedgerService tourLedgerService;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/ThirtyTeachTour/group/index.html")
@SaCheckPermission("thirtyTeachTour.group")
public void index() {
}
@At
@SaCheckPermission("thirtyTeachTour.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 thirty_teach_tour_matter m
INNER JOIN thirty_teach_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 thirty_teach_tour_matter m
INNER JOIN thirty_teach_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 thirty_teach_tour_ledger t
LEFT JOIN (
SELECT ledgerId, COUNT(1) AS familyCount
FROM thirty_teach_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("thirtyTeachTour.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("thirtyTeachTour.group")
public Result lineTypeOptions(Integer year) {
Cnd cnd = buildQueryCnd(year, null, null, null);
Sql sql = Sqls.create("""
SELECT DISTINCT l.lineType
FROM thirty_teach_tour_matter m
INNER JOIN thirty_teach_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("thirtyTeachTour.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 thirty_teach_tour_ledger t
INNER JOIN thirty_teach_tour_matter m ON m.id = t.matterId
INNER JOIN thirty_teach_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,
COALESCE(NULLIF(t.mobile, ''), NULLIF(vu.mobile, ''), '') AS mobile,
COALESCE(NULLIF(t.idCard, ''), NULLIF(vu.idCard, ''), '') AS idCard,
t.unionId,
t.unionName,
t.signupTime
FROM thirty_teach_tour_ledger t
INNER JOIN thirty_teach_tour_matter m ON m.id = t.matterId
INNER JOIN thirty_teach_tour_line l ON l.id = m.lineId
LEFT JOIN vw_user vu ON vu.loginname = t.jobNo
$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);
fillSignupFamilies(list);
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list);
return Result.success(pagination);
}
@At
@Ok("void")
@SaCheckPermission("thirtyTeachTour.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 thirty_teach_tour_matter m
INNER JOIN thirty_teach_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,
COALESCE(NULLIF(t.idCard, ''), NULLIF(vu.idCard, ''), '') AS idCard,
COALESCE(NULLIF(t.mobile, ''), NULLIF(vu.mobile, ''), '') AS 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 thirty_teach_tour_ledger t
INNER JOIN thirty_teach_tour_matter m ON m.id = t.matterId
INNER JOIN thirty_teach_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 thirty_teach_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()));
}
}
/**
* 给报名人员列表挂载家属子列表;当前家属台账未保存手机号,mobile 字段先返回空值供前端占位。
*/
private void fillSignupFamilies(List<NutMap> list) {
if (list == null || list.isEmpty()) {
return;
}
List<String> ledgerIds = new ArrayList<>();
for (NutMap item : list) {
String ledgerId = item.getString("id", "");
if (StrUtil.isNotBlank(ledgerId)) {
ledgerIds.add(ledgerId);
}
}
if (ledgerIds.isEmpty()) {
return;
}
Sql sql = Sqls.create("""
SELECT
ledgerId,
familyName,
age,
gender,
'' AS mobile,
idCard,
relationship
FROM thirty_teach_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("id", ""), 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 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,78 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourLeaveApplyService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
@IocBean
@Ok("json:full")
@At("/platform/thirtyTeachTour/leaveApply")
public class ThirtyTeachTourLeaveApplyController {
@Inject
private ThirtyTeachTourLeaveApplyService leaveApplyService;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/ThirtyTeachTour/leaveApply/index.html")
@SaCheckPermission("thirtyTeachTour.leaveApply")
public void index() {
}
/**
* 查询人员分配表中的退出取消数据,旧退出申请表不再作为业务来源。
*/
@At
@SaCheckPermission("thirtyTeachTour.leaveApply")
public Result pageData(PageForm pageForm, String keyword, String unionName, String status) {
return Result.success(leaveApplyService.pageData(pageForm, keyword, unionName, status));
}
/**
* 查询当前登录人在取消管理页的可操作权限,供前端控制按钮显示。
*/
@At
@SaCheckPermission("thirtyTeachTour.leaveApply")
public Result permissionInfo() {
return Result.success(leaveApplyService.permissionInfo());
}
/**
* 取消人员分配记录:标记已退出,并同步删除该人员对应路线的报名台账。
*/
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("thirtyTeachTour.leaveApply")
@SLog(type = "tour", tag = "疗休养退出取消", msg = "取消疗休养人员分配")
public Result doCancel(String id) {
try {
leaveApplyService.cancelAssignment(id);
return Result.success();
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
}
/**
* 恢复人员分配记录退出状态,只恢复人员分配表状态,不恢复已删除台账。
*/
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("thirtyTeachTour.branchUserAssignment.cancelRestore")
@SLog(type = "tour", tag = "疗休养退出取消", msg = "恢复疗休养人员分配退出状态")
public Result doRestore(String id) {
try {
leaveApplyService.restoreAssignment(id);
return Result.success();
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
}
}
@@ -0,0 +1,297 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.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.thirtyTeachTour.models.ThirtyTeachTourLine;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourTravelAgency;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourLineService;
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/thirtyTeachTour/route")
public class ThirtyTeachTourLineController {
@Inject
private ThirtyTeachTourLineService tourLineService;
@Inject
private SysDictService sysDictService;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/ThirtyTeachTour/route/index.html")
@SaCheckPermission("thirtyTeachTour.route")
public void index() {
}
@At
@SaCheckPermission("thirtyTeachTour.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 thirty_teach_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 thirty_teach_tour_line l
LEFT JOIN thirty_teach_tour_travel_agency a ON a.id = l.travelAgencyId
LEFT JOIN thirty_teach_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("thirtyTeachTour.route")
public Result detail(String id) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
ThirtyTeachTourLine line = tourLineService.fetch(id);
return line == null ? Result.error("线路不存在") : Result.success(line);
}
@At
@SaCheckPermission("thirtyTeachTour.route")
public Result travelAgencyOptions(Integer year) {
Cnd cnd = Cnd.where(ThirtyTeachTourTravelAgency::getEnabled, "=", true);
cnd.andEX(ThirtyTeachTourTravelAgency::getYear, "=", year);
cnd.asc(ThirtyTeachTourTravelAgency::getAgencyCode).asc(ThirtyTeachTourTravelAgency::getAgencyName);
return Result.success(tourLineService.dao().query(ThirtyTeachTourTravelAgency.class, cnd));
}
@At
@SaCheckPermission("thirtyTeachTour.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 thirty_teach_tour_setting_lot lot
INNER JOIN thirty_teach_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("thirtyTeachTour.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("thirtyTeachTour.route")
@SLog(type = "tour", tag = "线路管理", msg = "保存线路信息")
public Result doSubmit(ThirtyTeachTourLine line) {
Result checkResult = check(line);
if (checkResult != null) {
return checkResult;
}
Cnd sameCodeCnd = Cnd.where(ThirtyTeachTourLine::getYear, "=", line.getYear())
.and(ThirtyTeachTourLine::getLineCode, "=", line.getLineCode());
if (StrUtil.isNotBlank(line.getId())) {
sameCodeCnd.and(ThirtyTeachTourLine::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("thirtyTeachTour.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(ThirtyTeachTourLine 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(ThirtyTeachTourLine line, ThirtyTeachTourLine 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,455 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.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.thirtyTeachTour.models.ThirtyTeachTourLedger;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourMatter;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourSetting;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourMatterService;
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/thirtyTeachTour/matter")
public class ThirtyTeachTourMatterController {
@Inject
private ThirtyTeachTourMatterService tourMatterService;
@Inject
private SysDictService sysDictService;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/ThirtyTeachTour/matter/index.html")
@SaCheckPermission("thirtyTeachTour.matter")
public void index() {
}
@At
@SaCheckPermission("thirtyTeachTour.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 thirty_teach_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 thirty_teach_tour_matter m
LEFT JOIN thirty_teach_tour_setting s ON s.id = m.settingId
LEFT JOIN sys_union u ON u.id = m.unionId
LEFT JOIN thirty_teach_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("thirtyTeachTour.matter")
public Result detail(String id) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
ThirtyTeachTourMatter matter = tourMatterService.fetch(id);
return matter == null ? Result.error("事项不存在") : Result.success(matter);
}
@At
@SaCheckPermission("thirtyTeachTour.matter")
public Result settingOptions(Integer year) {
Cnd cnd = Cnd.where(ThirtyTeachTourSetting::getEnabled, "=", true);
cnd.andEX(ThirtyTeachTourSetting::getYear, "=", year);
cnd.desc(ThirtyTeachTourSetting::getCreatedAt);
cnd.desc(ThirtyTeachTourSetting::getUpdatedAt);
cnd.asc(ThirtyTeachTourSetting::getConfigName);
return Result.success(tourMatterService.dao().query(ThirtyTeachTourSetting.class, cnd));
}
@At
@SaCheckPermission("thirtyTeachTour.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("thirtyTeachTour.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 thirty_teach_tour_line l
LEFT JOIN thirty_teach_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("thirtyTeachTour.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("thirtyTeachTour.matter")
@SLog(type = "tour", tag = "疗休养事项", msg = "保存疗休养事项")
public Result doSubmit(ThirtyTeachTourMatter matter) {
Result checkResult = check(matter);
if (checkResult != null) {
return checkResult;
}
Cnd sameNameCnd = Cnd.where(ThirtyTeachTourMatter::getYear, "=", matter.getYear())
.and(ThirtyTeachTourMatter::getMatterName, "=", matter.getMatterName());
if (StrUtil.isNotBlank(matter.getId())) {
sameNameCnd.and(ThirtyTeachTourMatter::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("thirtyTeachTour.matter")
@SLog(type = "tour", tag = "疗休养事项", msg = "删除疗休养事项")
public Result doDelete(String id) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
ThirtyTeachTourMatter 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("thirtyTeachTour.matter")
public Result deleteInfo(String id) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
ThirtyTeachTourMatter 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("thirtyTeachTour.matter")
public Result lineConfig(String matterId) {
if (StrUtil.isBlank(matterId)) {
return Result.error("参数错误");
}
ThirtyTeachTourMatter matter = tourMatterService.fetch(matterId);
return matter == null ? Result.error("事项不存在") : Result.success(matter);
}
@At
@SaCheckPermission("thirtyTeachTour.matter")
public Result settingPeople(String matterId) {
if (StrUtil.isBlank(matterId)) {
return Result.error("参数错误");
}
ThirtyTeachTourMatter matter = tourMatterService.fetch(matterId);
if (matter == null || StrUtil.isBlank(matter.getSettingId())) {
return Result.success(NutMap.NEW());
}
ThirtyTeachTourSetting setting = tourMatterService.dao().fetch(ThirtyTeachTourSetting.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
@SaCheckPermission("thirtyTeachTour.matter")
public Result settingBoardingPlaces(String settingId) {
if (StrUtil.isBlank(settingId)) {
return Result.error("参数错误");
}
ThirtyTeachTourSetting setting = tourMatterService.dao().fetch(ThirtyTeachTourSetting.class, settingId);
if (setting == null) {
return Result.error("疗休养配置不存在");
}
return Result.success(NutMap.NEW().addv("boardingPlace", StrUtil.blankToDefault(setting.getBoardingPlace(), "[]")));
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("thirtyTeachTour.matter")
@SLog(type = "tour", tag = "疗休养事项", msg = "保存事项线路配置")
public Result lineConfigDoSubmit(ThirtyTeachTourMatter matter) {
Result checkResult = checkLineConfig(matter);
if (checkResult != null) {
return checkResult;
}
ThirtyTeachTourMatter oldMatter = tourMatterService.fetch(matter.getId());
if (oldMatter == null) {
return Result.error("事项不存在");
}
oldMatter.setLineId(matter.getLineId());
// 默认乘车地点来自疗休养配置中的乘车地点列表,后续代报名和自主报名会优先带出该值。
oldMatter.setDefaultBoardingPlace(matter.getDefaultBoardingPlace());
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(ThirtyTeachTourMatter 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(ThirtyTeachTourMatter 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())) {
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(ThirtyTeachTourMatter matter) {
if (StrUtil.isBlank(matter.getId())) {
matter.setCreatorUserId(SecurityUtil.getUserId());
matter.setCreatorName(SecurityUtil.getUserUsername());
} else {
ThirtyTeachTourMatter 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(ThirtyTeachTourLedger.class, Cnd.where(ThirtyTeachTourLedger::getDelFlag, "=", false)
.and(ThirtyTeachTourLedger::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.thirtyTeachTour.controller;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
/**
* 分阶段建设的菜单占位入口。
*/
@IocBean
@At("/platform/thirtyTeachTour")
public class ThirtyTeachTourPlaceholderController {
}
@@ -0,0 +1,600 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.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.sys.views.View_user;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourLedger;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourLedgerDirectRelative;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourLedgerFamily;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourLedgerDirectRelativeService;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourLedgerFamilyService;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourLedgerService;
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/thirtyTeachTour/schoolUnionApproval")
public class ThirtyTeachTourSchoolUnionApprovalController {
private static final String WORKFLOW_KEY = "LXYBZXQSXL";
private static final String TASK_DISPLAY_NAME = "校工会审核";
@Inject
private ThirtyTeachTourLedgerService tourLedgerService;
@Inject
private ThirtyTeachTourLedgerFamilyService tourLedgerFamilyService;
@Inject
private ThirtyTeachTourLedgerDirectRelativeService tourLedgerDirectRelativeService;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/ThirtyTeachTour/schoolUnionApproval/index.html")
@SaCheckPermission(value = {"thirtyTeachTour.schoolUnionApproval", "h5.thirtyTeachTour.schoolUnionApproval"}, mode = SaMode.OR)
public void index() {
}
@At("/h5")
@Ok("beetl:/platform/zhghh5/dayofficework/thirtyTeachTour/schoolUnionApproval/index.html")
@SaCheckPermission(value = {"thirtyTeachTour.schoolUnionApproval", "h5.thirtyTeachTour.schoolUnionApproval"}, mode = SaMode.OR)
public void h5() {
}
@At
@SaCheckPermission(value = {"thirtyTeachTour.schoolUnionApproval", "h5.thirtyTeachTour.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 thirty_teach_tour_ledger t ON t.id = ins.businessNo
LEFT JOIN thirty_teach_tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN thirty_teach_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 thirty_teach_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 thirty_teach_tour_ledger_family
WHERE delFlag = 0
GROUP BY ledgerId
) f ON f.ledgerId = t.id
LEFT JOIN thirty_teach_tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN thirty_teach_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 thirty_teach_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 = {"thirtyTeachTour.schoolUnionApproval", "h5.thirtyTeachTour.schoolUnionApproval"}, mode = SaMode.OR)
public Result detail(String id) {
ThirtyTeachTourLedger ledger = fetchAuditLedger(id);
if (ledger == null) {
return Result.error("报名记录不存在或无权查看");
}
fillLedgerContactFallback(ledger);
ledger.setLineName(getCurrentLineName(ledger));
Cnd familyCnd = Cnd.where(ThirtyTeachTourLedgerFamily::getLedgerId, "=", ledger.getId())
.and(ThirtyTeachTourLedgerFamily::getDelFlag, "=", false);
familyCnd.asc(ThirtyTeachTourLedgerFamily::getCreatedAt);
ThirtyTeachTourLedgerDirectRelative directRelative = tourLedgerDirectRelativeService.fetch(
Cnd.where(ThirtyTeachTourLedgerDirectRelative::getLedgerId, "=", ledger.getId())
.and(ThirtyTeachTourLedgerDirectRelative::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 = {"thirtyTeachTour.schoolUnionApproval", "h5.thirtyTeachTour.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 thirty_teach_tour_ledger t ON t.id = ins.businessNo
LEFT JOIN thirty_teach_tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN thirty_teach_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 = {"thirtyTeachTour.schoolUnionApproval", "h5.thirtyTeachTour.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 thirty_teach_tour_ledger t ON t.id = ins.businessNo
LEFT JOIN thirty_teach_tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN thirty_teach_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 = {"thirtyTeachTour.schoolUnionApproval", "h5.thirtyTeachTour.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 thirty_teach_tour_ledger t ON t.id = ins.businessNo
LEFT JOIN thirty_teach_tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN thirty_teach_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 = {"thirtyTeachTour.schoolUnionApproval", "h5.thirtyTeachTour.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 thirty_teach_tour_ledger t ON t.id = ins.businessNo
LEFT JOIN thirty_teach_tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN thirty_teach_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 ThirtyTeachTourLedger 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 thirty_teach_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 void fillLedgerContactFallback(ThirtyTeachTourLedger ledger) {
if (ledger == null || (StrUtil.isNotBlank(ledger.getIdCard()) && StrUtil.isNotBlank(ledger.getMobile()))) {
return;
}
// Approval detail keeps ledger contact data first; blank contact fields use the user profile snapshot for display only.
View_user user = tourLedgerService.dao().fetch(View_user.class, Cnd.where(View_user::getLoginname, "=", ledger.getJobNo()));
if (user == null) {
return;
}
if (StrUtil.isBlank(ledger.getIdCard())) {
ledger.setIdCard(user.getIdCard());
}
if (StrUtil.isBlank(ledger.getMobile())) {
ledger.setMobile(user.getMobile());
}
}
private String getCurrentLineName(ThirtyTeachTourLedger 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 thirty_teach_tour_ledger t
LEFT JOIN thirty_teach_tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN thirty_teach_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(ThirtyTeachTourLedger 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 thirty_teach_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 thirty_teach_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(ThirtyTeachTourLedger 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 thirty_teach_tour_ledger t
LEFT JOIN thirty_teach_tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN thirty_teach_tour_setting s ON s.id = m.settingId AND s.delFlag = 0
LEFT JOIN thirty_teach_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,279 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.controller;
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog;
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.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourUserAssignment;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourUserAssignmentService;
import org.apache.poi.ss.usermodel.Workbook;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
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 javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@IocBean
@Ok("json:full")
@At("/platform/thirtyTeachTour/schoolUserAssignment")
public class ThirtyTeachTourSchoolUserAssignmentController {
@Inject
private ThirtyTeachTourUserAssignmentService tourUserAssignmentService;
/**
* 校工会人员分配列表入口。
*/
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/ThirtyTeachTour/schoolUserAssignment/index.html")
@SaCheckPermission("thirtyTeachTour.schoolUserAssignment")
public void index() {
}
/**
* 分页查询人员分配记录,列表数据只来自人员分配表,默认由前端传入校工会分配类别。
*/
@At
@SaCheckPermission("thirtyTeachTour.schoolUserAssignment")
public Result pageData(PageForm pageForm, Integer year, String settingId, String matterId, String unionId,
String personType, String keyword, String assignSource, Boolean cancelled) {
return Result.success(tourUserAssignmentService.schoolAssignmentPage(pageForm, year, settingId, matterId,
unionId, personType, keyword, assignSource, cancelled));
}
/**
* 按校工会人员分配页面当前查询条件导出数据,导出范围不受分页限制。
*/
@At
@SaCheckPermission("thirtyTeachTour.schoolUserAssignment")
@Ok("void")
public void exportData(PageForm pageForm, Integer year, String settingId, String matterId, String unionId,
String personType, String keyword, String assignSource, Boolean cancelled, HttpServletResponse response) {
List<NutMap> rows = tourUserAssignmentService.schoolAssignmentExportRows(pageForm, year, settingId, matterId,
unionId, personType, keyword, assignSource, cancelled);
ExportParams exportParams = new ExportParams();
exportParams.setSheetName("校工会人员分配");
exportParams.setType(ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, assignmentExportEntities(), rows);
CommonDownloadUtil.download("校工会人员分配.xlsx", workbook, response);
}
/**
* 按页面当前查询条件给人员分配列表中的可报名人员发送提醒。
* 参数包括列表筛选条件和 content 提醒内容;返回 Resultdata.receiverCount 表示实际接收人数。
*/
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("thirtyTeachTour.schoolUserAssignment")
@SLog(type = "tour", tag = "疗休养校工会人员分配", msg = "发送校工会人员分配提醒")
public Result sendReminder(PageForm pageForm, Integer year, String settingId, String matterId, String unionId,
String personType, String keyword, String assignSource, Boolean cancelled, String content) {
if (StrUtil.isBlank(content)) {
return Result.error("请输入提醒内容");
}
try {
return Result.success(tourUserAssignmentService.sendSchoolAssignmentReminder(pageForm, year, settingId,
matterId, unionId, personType, keyword, assignSource, cancelled, content));
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
}
/**
* 分页查询候选人员,候选范围由疗休养配置的可参加人员范围决定;userIds 仅作为人员选择器筛选条件。
*/
@At
@SaCheckPermission("thirtyTeachTour.schoolUserAssignment")
public Result candidatePageData(PageForm pageForm, String settingId, String unionId, String keyword, @Param("userIds") String userIds) {
return Result.success(tourUserAssignmentService.schoolCandidatePage(pageForm, settingId, unionId, keyword, parseUserIds(userIds)));
}
/**
* 查询可用于分配的疗休养配置。
*/
@At
@SaCheckPermission("thirtyTeachTour.schoolUserAssignment")
public Result settingOptions(Integer year) {
return Result.success(tourUserAssignmentService.listEnabledSettingOptions(year));
}
/**
* 查询校工会可分配线路选项,选项携带事项、线路和旅行社快照信息。
*/
@At
@SaCheckPermission("thirtyTeachTour.schoolUserAssignment")
public Result matterOptions(String settingId) {
return Result.success(tourUserAssignmentService.listMatterOptions(settingId));
}
/**
* 查询分工会选项,供筛选和候选人员过滤使用。
*/
@At
@SaCheckPermission("thirtyTeachTour.schoolUserAssignment")
public Result unionOptions() {
return Result.success(tourUserAssignmentService.listUnionOptions());
}
/**
* 保存校工会人员分配,保存时数据来源固定为 SCHOOL_UNION。
*/
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("thirtyTeachTour.schoolUserAssignment")
@SLog(type = "tour", tag = "疗休养校工会人员分配", msg = "保存校工会人员分配")
public Result doAssign(String settingId, String matterId, String personType, @Param("userIds") String userIds) {
List<String> parsedUserIds;
try {
parsedUserIds = parseUserIds(userIds);
} catch (Exception e) {
return Result.error("人员参数错误");
}
try {
return Result.success(tourUserAssignmentService.assignSchoolUsers(settingId, matterId, personType, parsedUserIds));
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
}
/**
* 保存校工会人员分配明细,支持弹窗候选列表中每个人单独选择分配线路。
*/
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("thirtyTeachTour.schoolUserAssignment")
@SLog(type = "tour", tag = "疗休养校工会人员分配", msg = "保存校工会人员分配明细")
public Result doAssignItems(String settingId, @Param("assignItems") String assignItems) {
List<NutMap> parsedItems;
try {
parsedItems = parseAssignItems(assignItems);
} catch (Exception e) {
return Result.error("人员分配明细参数错误");
}
try {
return Result.success(tourUserAssignmentService.assignSchoolUsers(settingId, parsedItems));
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
}
/**
* 给校工会已分配人员补选分配线路,具体回写分配表和写台账逻辑由 service 统一处理。
*/
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("thirtyTeachTour.schoolUserAssignment")
@SLog(type = "tour", tag = "疗休养校工会人员分配", msg = "选择校工会分配事项")
public Result selectMatter(String id, String matterId) {
try {
return Result.success(tourUserAssignmentService.selectSchoolAssignmentMatter(id, matterId));
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
}
/**
* 恢复校工会已取消退出的人员分配记录,只恢复状态,不恢复已删除台账。
*/
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("thirtyTeachTour.schoolUserAssignment.cancelRestore")
@SLog(type = "tour", tag = "疗休养校工会人员分配", msg = "取消退出校工会人员分配")
public Result restoreCancel(String id) {
try {
tourUserAssignmentService.restoreCancelledAssignmentBySource(id, ThirtyTeachTourUserAssignment.ASSIGN_SOURCE_SCHOOL_UNION);
return Result.success();
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
}
/**
* 删除前检查该分配记录是否已经存在对应报名台账。
*/
@At
@SaCheckPermission("thirtyTeachTour.schoolUserAssignment")
public Result deleteInfo(String id) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
return Result.success(tourUserAssignmentService.schoolDeleteInfo(id));
}
/**
* 删除校工会人员分配记录,只删除 SCHOOL_UNION 来源的数据。
*/
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("thirtyTeachTour.schoolUserAssignment")
@SLog(type = "tour", tag = "疗休养校工会人员分配", msg = "删除校工会人员分配")
public Result doDelete(String id, Boolean deleteLedger) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
try {
tourUserAssignmentService.deleteBySource(id, ThirtyTeachTourUserAssignment.ASSIGN_SOURCE_SCHOOL_UNION, Boolean.TRUE.equals(deleteLedger));
return Result.success();
} catch (IllegalArgumentException e) {
return Result.error(e.getMessage());
}
}
private List<String> parseUserIds(String userIds) {
if (StrUtil.isBlank(userIds)) {
return Collections.emptyList();
}
List<String> list = Json.fromJsonAsList(String.class, userIds);
return Lang.isEmpty(list) ? Collections.emptyList() : list;
}
private List<NutMap> parseAssignItems(String assignItems) {
if (StrUtil.isBlank(assignItems)) {
return Collections.emptyList();
}
List<NutMap> list = Json.fromJsonAsList(NutMap.class, assignItems);
return Lang.isEmpty(list) ? Collections.emptyList() : list;
}
/**
* 定义人员分配导出字段,保持与页面列表核心字段一致并补充人员基础信息。
*/
private List<ExcelExportEntity> assignmentExportEntities() {
List<ExcelExportEntity> entities = new ArrayList<>();
entities.add(new ExcelExportEntity("年度", "year", 12));
entities.add(new ExcelExportEntity("疗休养配置", "settingName", 24));
entities.add(new ExcelExportEntity("姓名", "userName", 14));
entities.add(new ExcelExportEntity("工号", "loginName", 16));
entities.add(new ExcelExportEntity("性别", "gender", 10));
entities.add(new ExcelExportEntity("年龄", "age", 10));
entities.add(new ExcelExportEntity("身份证号", "idCard", 24));
entities.add(new ExcelExportEntity("手机号", "mobile", 18));
entities.add(new ExcelExportEntity("所属分工会", "unionName", 24));
entities.add(new ExcelExportEntity("所在单位", "unitName", 24));
entities.add(new ExcelExportEntity("线路", "lineName", 30));
entities.add(new ExcelExportEntity("出行时间", "travelPeriod", 26));
entities.add(new ExcelExportEntity("乘车地点", "boardingPlace", 18));
entities.add(new ExcelExportEntity("分配类别", "assignSourceText", 16));
entities.add(new ExcelExportEntity("人员类型", "personTypeText", 14));
entities.add(new ExcelExportEntity("是否退出", "cancelledText", 14));
entities.add(new ExcelExportEntity("分配时间", "assignedAt", 22));
entities.add(new ExcelExportEntity("分配人", "assignedName", 14));
return entities;
}
}
@@ -0,0 +1,342 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.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.thirtyTeachTour.models.ThirtyTeachTourSetting;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourSettingLot;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourSettingService;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourUserAssignmentService;
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.lang.util.NutMap;
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/thirtyTeachTour/setting")
public class ThirtyTeachTourSettingController {
private static final String SIGNUP_ELIGIBILITY_MODE_SCOPE_GROUP = "SCOPE_GROUP";
private static final String SIGNUP_ELIGIBILITY_MODE_ASSIGNED_USER = "ASSIGNED_USER";
@Inject
private ThirtyTeachTourSettingService tourSettingService;
@Inject
private ThirtyTeachTourUserAssignmentService tourUserAssignmentService;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/ThirtyTeachTour/setting/index.html")
@SaCheckPermission("thirtyTeachTour.setting")
public void index() {
}
@At
@SaCheckPermission("thirtyTeachTour.setting")
public Result pageData(PageForm pageForm, Integer year, String configName) {
Cnd cnd = Cnd.NEW();
cnd.andEX(ThirtyTeachTourSetting::getYear, "=", year);
cnd.and(Cnd.likeEX(ThirtyTeachTourSetting::getConfigName, configName));
cnd.desc(ThirtyTeachTourSetting::getYear).asc(ThirtyTeachTourSetting::getSortNo).desc(ThirtyTeachTourSetting::getCreatedAt);
Pagination<ThirtyTeachTourSetting> pagination = tourSettingService.listPage(
pageForm.getPageNumber(),
pageForm.getPageSize(),
ThirtyTeachTourSetting.class,
cnd
);
return Result.success(pagination);
}
@At
@SaCheckPermission("thirtyTeachTour.setting")
public Result detail(String id) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
ThirtyTeachTourSetting tourSetting = tourSettingService.fetch(id);
if (tourSetting == null) {
return Result.error("配置不存在");
}
// 编辑页面需要一起带出标段,按标段值倒序保持与老疗休养配置一致。
Cnd lotCnd = Cnd.NEW();
lotCnd.desc(ThirtyTeachTourSettingLot::getLotValue);
tourSettingService.dao().fetchLinks(tourSetting, "lots", lotCnd);
tourSetting.setUnionQuotas(tourSettingService.listUnionQuotaRows(id));
return Result.success(tourSetting);
}
@At
@SaCheckPermission("thirtyTeachTour.setting")
public Result previousYearInfo(Integer year) {
if (year == null) {
return Result.error("请先选择年度");
}
List<ThirtyTeachTourSetting> settings = tourSettingService.query(Cnd.where(ThirtyTeachTourSetting::getYear, "=", year - 1)
.and(ThirtyTeachTourSetting::getDelFlag, "=", false)
.desc(ThirtyTeachTourSetting::getUpdatedAt)
.desc(ThirtyTeachTourSetting::getCreatedAt));
if (Lang.isEmpty(settings)) {
return Result.error("未找到上一年度配置");
}
ThirtyTeachTourSetting tourSetting = settings.get(0);
Cnd lotCnd = Cnd.NEW();
lotCnd.desc(ThirtyTeachTourSettingLot::getLotValue);
tourSettingService.dao().fetchLinks(tourSetting, "lots", lotCnd);
tourSetting.setUnionQuotas(tourSettingService.listUnionQuotaRows(tourSetting.getId()));
return Result.success(tourSetting);
}
@At
@SaCheckPermission("thirtyTeachTour.setting")
public Result unionQuotaRows(String settingId) {
// 新增配置时 settingId 为空,service 会返回所有分工会的空名额行;编辑时合并已保存名额。
return Result.success(tourSettingService.listUnionQuotaRows(settingId));
}
@At
@SaCheckPermission("thirtyTeachTour.setting")
public Result unionQuotaOverview(String settingId) {
int schoolFormalAssignedCount = tourUserAssignmentService.countSchoolFormalAssignedUsers(settingId);
return Result.success(NutMap.NEW().addv("schoolFormalAssignedCount", schoolFormalAssignedCount));
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("thirtyTeachTour.setting")
@SLog(type = "tour", tag = "疗休养设置", msg = "保存疗休养配置")
public Result doSubmit(ThirtyTeachTourSetting tourSetting,
@Param(value = "lots") String lots,
@Param(value = "unionQuotas") String unionQuotas,
@Param(value = "lotDeleteList") String[] lotDeleteList) {
Result checkResult = check(tourSetting);
if (checkResult != null) {
return checkResult;
}
Cnd sameNameCnd = Cnd.where(ThirtyTeachTourSetting::getYear, "=", tourSetting.getYear())
.and(ThirtyTeachTourSetting::getConfigName, "=", tourSetting.getConfigName());
if (StrUtil.isNotBlank(tourSetting.getId())) {
sameNameCnd.and(ThirtyTeachTourSetting::getId, "<>", tourSetting.getId());
}
if (tourSettingService.count(sameNameCnd) > 0) {
return Result.error("同年度下配置名称已存在");
}
// 布尔值给默认值,避免前端未传时出现空状态。
int schoolFormalAssignedCount = tourUserAssignmentService.countSchoolFormalAssignedUsers(tourSetting.getId());
int branchTotalQuota = Math.max(defaultInt(tourSetting.getTravelPeopleQuota()) - schoolFormalAssignedCount, 0);
String quotaLimitMessage = tourSettingService.checkBranchFormalQuotaLimit(unionQuotas, branchTotalQuota);
if (StrUtil.isNotBlank(quotaLimitMessage)) {
return Result.error(quotaLimitMessage);
}
if (tourSetting.getEnabled() == null) {
tourSetting.setEnabled(true);
}
if (tourSetting.getAllowFamily() == null) {
tourSetting.setAllowFamily(false);
}
if (tourSetting.getFillBedInfo() == null) {
tourSetting.setFillBedInfo(true);
}
if (tourSetting.getHomeSignupEntryEnabled() == null) {
tourSetting.setHomeSignupEntryEnabled(false);
}
if (!Boolean.TRUE.equals(tourSetting.getHomeSignupEntryEnabled())) {
tourSetting.setHomeSignupEntryImage("");
} else {
tourSetting.setHomeSignupEntryImage(normalizeSingleHomeSignupEntryImage(tourSetting.getHomeSignupEntryImage()));
}
// 报名资格校验方式默认按人员分配表校验,后续报名校验切换会读取该配置。
if (StrUtil.isBlank(tourSetting.getSignupEligibilityMode())) {
tourSetting.setSignupEligibilityMode(SIGNUP_ELIGIBILITY_MODE_ASSIGNED_USER);
}
if (StrUtil.isBlank(tourSetting.getOutProvinceRatioType())) {
tourSetting.setOutProvinceRatioType("当年报名人数");
}
if (!"固定人数".equals(tourSetting.getOutProvinceRatioType())) {
tourSetting.setOutProvinceFixedPeople(0);
}
// 前端按项目既有约定把子表数组序列化提交,这里显式解析,避免自动绑定漏掉标段。
if (StrUtil.isNotBlank(lots)) {
try {
tourSetting.setLots(Json.fromJsonAsList(ThirtyTeachTourSettingLot.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");
}
tourSettingService.saveUnionQuotas(tourSetting.getId(), unionQuotas);
} else {
// 编辑时先处理页面删除的标段,再保存配置和当前标段行。
if (Lang.isNotEmpty(lotDeleteList)) {
tourSettingService.dao().clear(ThirtyTeachTourSettingLot.class, Cnd.where(ThirtyTeachTourSettingLot::getId, "in", lotDeleteList));
}
tourSettingService.updateIgnoreNull(tourSetting);
saveLots(tourSetting);
tourSettingService.saveUnionQuotas(tourSetting.getId(), unionQuotas);
}
return Result.success();
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("thirtyTeachTour.setting")
@SLog(type = "tour", tag = "疗休养设置", msg = "删除疗休养配置")
public Result doDelete(String id) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
tourSettingService.dao().clear(ThirtyTeachTourSettingLot.class, Cnd.where(ThirtyTeachTourSettingLot::getSettingId, "=", id));
tourSettingService.clearUnionQuotas(id);
tourUserAssignmentService.clearBySettingId(id);
tourSettingService.delete(id);
return Result.success();
}
private List<ThirtyTeachTourSettingLot> normalizeLots(ThirtyTeachTourSetting 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 String normalizeSingleHomeSignupEntryImage(String image) {
// 首页报名入口图片仅允许保存一张,兼容逗号分隔的历史多图路径时只保留第一张。
if (StrUtil.isBlank(image)) {
return "";
}
List<String> images = StrUtil.splitTrim(image, ",");
return Lang.isEmpty(images) ? "" : images.get(0);
}
private void saveLots(ThirtyTeachTourSetting tourSetting) {
List<ThirtyTeachTourSettingLot> lots = tourSetting.getLots();
if (Lang.isEmpty(lots)) {
return;
}
lots.forEach(item -> item.setSettingId(tourSetting.getId()));
tourSettingService.dao().insertOrUpdate(lots);
}
private Result check(ThirtyTeachTourSetting tourSetting) {
if (tourSetting == null) {
return Result.error("参数错误");
}
if (tourSetting.getYear() == null) {
return Result.error("年度不能为空");
}
if (StrUtil.isBlank(tourSetting.getConfigName())) {
return Result.error("配置名称不能为空");
}
Result boardingPlaceCheck = normalizeBoardingPlace(tourSetting);
if (boardingPlaceCheck != null) {
return boardingPlaceCheck;
}
if (tourSetting.getTravelPeopleQuota() != null && tourSetting.getTravelPeopleQuota() < 0) {
return Result.error("出行人数指标不能小于0");
}
if (StrUtil.isNotBlank(tourSetting.getSignupEligibilityMode())
&& !SIGNUP_ELIGIBILITY_MODE_SCOPE_GROUP.equals(tourSetting.getSignupEligibilityMode())
&& !SIGNUP_ELIGIBILITY_MODE_ASSIGNED_USER.equals(tourSetting.getSignupEligibilityMode())) {
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 int defaultInt(Integer value) {
return value == null || value < 0 ? 0 : value;
}
private Result normalizeBoardingPlace(ThirtyTeachTourSetting tourSetting) {
if (StrUtil.isBlank(tourSetting.getBoardingPlace())) {
tourSetting.setBoardingPlace("[]");
return null;
}
try {
List<NutMap> rows = Json.fromJsonAsList(NutMap.class, tourSetting.getBoardingPlace());
if (Lang.isEmpty(rows)) {
tourSetting.setBoardingPlace("[]");
return null;
}
// 乘车路线以 JSON 数组保存,只保留有效路线名称,避免页面空行写入配置。
List<NutMap> normalizedRows = rows.stream()
.filter(item -> item != null && StrUtil.isNotBlank(item.getString("name")))
.map(item -> NutMap.NEW().addv("name", item.getString("name").trim()))
.collect(Collectors.toList());
tourSetting.setBoardingPlace(Json.toJson(normalizedRows));
return null;
} catch (Exception e) {
return Result.error("乘车路线格式不正确");
}
}
private Result checkLots(List<ThirtyTeachTourSettingLot> lots) {
if (Lang.isEmpty(lots)) {
return null;
}
for (int i = 0; i < lots.size(); i++) {
ThirtyTeachTourSettingLot 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.thirtyTeachTour.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.thirtyTeachTour.models.ThirtyTeachTourTravelAgency;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourTravelAgencyService;
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/thirtyTeachTour/travelAgency")
public class ThirtyTeachTourTravelAgencyController {
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 ThirtyTeachTourTravelAgencyService travelAgencyService;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/ThirtyTeachTour/travelAgency/index.html")
@SaCheckPermission("thirtyTeachTour.travelAgency")
public void index() {
}
@At
@SaCheckPermission("thirtyTeachTour.travelAgency")
public Result pageData(PageForm pageForm, Integer year, String agencyName, String contactName, String contactPhone) {
Cnd cnd = Cnd.NEW();
cnd.andEX(ThirtyTeachTourTravelAgency::getYear, "=", year);
cnd.and(Cnd.likeEX(ThirtyTeachTourTravelAgency::getAgencyName, agencyName));
cnd.and(Cnd.likeEX(ThirtyTeachTourTravelAgency::getContactName, contactName));
cnd.and(Cnd.likeEX(ThirtyTeachTourTravelAgency::getContactPhone, contactPhone));
applyOrder(cnd, pageForm);
Pagination<ThirtyTeachTourTravelAgency> pagination = travelAgencyService.listPage(
pageForm.getPageNumber(),
pageForm.getPageSize(),
ThirtyTeachTourTravelAgency.class,
cnd
);
return Result.success(pagination);
}
@At
@SaCheckPermission("thirtyTeachTour.travelAgency")
public Result detail(String id) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
ThirtyTeachTourTravelAgency agency = travelAgencyService.fetch(id);
return agency == null ? Result.error("旅行社不存在") : Result.success(agency);
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("thirtyTeachTour.travelAgency")
@SLog(type = "tour", tag = "旅行社管理", msg = "保存旅行社信息")
public Result doSubmit(ThirtyTeachTourTravelAgency agency) {
Result checkResult = check(agency);
if (checkResult != null) {
return checkResult;
}
Cnd sameCodeCnd = Cnd.where(ThirtyTeachTourTravelAgency::getYear, "=", agency.getYear())
.and(ThirtyTeachTourTravelAgency::getAgencyCode, "=", agency.getAgencyCode());
if (StrUtil.isNotBlank(agency.getId())) {
sameCodeCnd.and(ThirtyTeachTourTravelAgency::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("thirtyTeachTour.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(ThirtyTeachTourTravelAgency 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(ThirtyTeachTourTravelAgency::getYear).asc(ThirtyTeachTourTravelAgency::getAgencyCode).desc(ThirtyTeachTourTravelAgency::getCreatedAt);
return;
}
boolean descending = "descending".equals(orderBy);
if ("year".equals(orderName)) {
if (descending) {
cnd.desc(ThirtyTeachTourTravelAgency::getYear);
} else {
cnd.asc(ThirtyTeachTourTravelAgency::getYear);
}
} else if ("agencyCode".equals(orderName)) {
if (descending) {
cnd.desc(ThirtyTeachTourTravelAgency::getAgencyCode);
} else {
cnd.asc(ThirtyTeachTourTravelAgency::getAgencyCode);
}
} else {
cnd.asc(ThirtyTeachTourTravelAgency::getYear).asc(ThirtyTeachTourTravelAgency::getAgencyCode).desc(ThirtyTeachTourTravelAgency::getCreatedAt);
}
}
}
@@ -0,0 +1,600 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.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.sys.views.View_user;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourLedger;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourLedgerDirectRelative;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourLedgerFamily;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourLedgerDirectRelativeService;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourLedgerFamilyService;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourLedgerService;
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/thirtyTeachTour/unionApproval")
public class ThirtyTeachTourUnionApprovalController {
private static final String WORKFLOW_KEY = "LXYBZXQSXL";
private static final String TASK_DISPLAY_NAME = "分工会审核";
@Inject
private ThirtyTeachTourLedgerService tourLedgerService;
@Inject
private ThirtyTeachTourLedgerFamilyService tourLedgerFamilyService;
@Inject
private ThirtyTeachTourLedgerDirectRelativeService tourLedgerDirectRelativeService;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/ThirtyTeachTour/unionApproval/index.html")
@SaCheckPermission(value = {"thirtyTeachTour.unionApproval", "h5.thirtyTeachTour.unionApproval"}, mode = SaMode.OR)
public void index() {
}
@At("/h5")
@Ok("beetl:/platform/zhghh5/dayofficework/thirtyTeachTour/unionApproval/index.html")
@SaCheckPermission(value = {"thirtyTeachTour.unionApproval", "h5.thirtyTeachTour.unionApproval"}, mode = SaMode.OR)
public void h5() {
}
@At
@SaCheckPermission(value = {"thirtyTeachTour.unionApproval", "h5.thirtyTeachTour.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 thirty_teach_tour_ledger t ON t.id = ins.businessNo
LEFT JOIN thirty_teach_tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN thirty_teach_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 thirty_teach_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 thirty_teach_tour_ledger_family
WHERE delFlag = 0
GROUP BY ledgerId
) f ON f.ledgerId = t.id
LEFT JOIN thirty_teach_tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN thirty_teach_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 thirty_teach_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 = {"thirtyTeachTour.unionApproval", "h5.thirtyTeachTour.unionApproval"}, mode = SaMode.OR)
public Result detail(String id) {
ThirtyTeachTourLedger ledger = fetchAuditLedger(id);
if (ledger == null) {
return Result.error("报名记录不存在或无权查看");
}
fillLedgerContactFallback(ledger);
ledger.setLineName(getCurrentLineName(ledger));
Cnd familyCnd = Cnd.where(ThirtyTeachTourLedgerFamily::getLedgerId, "=", ledger.getId())
.and(ThirtyTeachTourLedgerFamily::getDelFlag, "=", false);
familyCnd.asc(ThirtyTeachTourLedgerFamily::getCreatedAt);
ThirtyTeachTourLedgerDirectRelative directRelative = tourLedgerDirectRelativeService.fetch(
Cnd.where(ThirtyTeachTourLedgerDirectRelative::getLedgerId, "=", ledger.getId())
.and(ThirtyTeachTourLedgerDirectRelative::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 = {"thirtyTeachTour.unionApproval", "h5.thirtyTeachTour.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 thirty_teach_tour_ledger t ON t.id = ins.businessNo
LEFT JOIN thirty_teach_tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN thirty_teach_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 = {"thirtyTeachTour.unionApproval", "h5.thirtyTeachTour.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 thirty_teach_tour_ledger t ON t.id = ins.businessNo
LEFT JOIN thirty_teach_tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN thirty_teach_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 = {"thirtyTeachTour.unionApproval", "h5.thirtyTeachTour.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 thirty_teach_tour_ledger t ON t.id = ins.businessNo
LEFT JOIN thirty_teach_tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN thirty_teach_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 = {"thirtyTeachTour.unionApproval", "h5.thirtyTeachTour.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 thirty_teach_tour_ledger t ON t.id = ins.businessNo
LEFT JOIN thirty_teach_tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN thirty_teach_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 ThirtyTeachTourLedger 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 thirty_teach_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 void fillLedgerContactFallback(ThirtyTeachTourLedger ledger) {
if (ledger == null || (StrUtil.isNotBlank(ledger.getIdCard()) && StrUtil.isNotBlank(ledger.getMobile()))) {
return;
}
// Approval detail keeps ledger contact data first; blank contact fields use the user profile snapshot for display only.
View_user user = tourLedgerService.dao().fetch(View_user.class, Cnd.where(View_user::getLoginname, "=", ledger.getJobNo()));
if (user == null) {
return;
}
if (StrUtil.isBlank(ledger.getIdCard())) {
ledger.setIdCard(user.getIdCard());
}
if (StrUtil.isBlank(ledger.getMobile())) {
ledger.setMobile(user.getMobile());
}
}
private String getCurrentLineName(ThirtyTeachTourLedger 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 thirty_teach_tour_ledger t
LEFT JOIN thirty_teach_tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN thirty_teach_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(ThirtyTeachTourLedger 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 thirty_teach_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 thirty_teach_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(ThirtyTeachTourLedger 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 thirty_teach_tour_ledger t
LEFT JOIN thirty_teach_tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN thirty_teach_tour_setting s ON s.id = m.settingId AND s.delFlag = 0
LEFT JOIN thirty_teach_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,912 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.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.thirtyTeachTour.models.ThirtyTeachTourLedger;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourLedgerDirectRelative;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourLedgerFamily;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourLedgerDirectRelativeService;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourLedgerFamilyService;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourLedgerService;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourUserAssignmentService;
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/thirtyTeachTour/unionledger")
public class ThirtyTeachTourUnionLedgerController {
@Inject
private ThirtyTeachTourLedgerService tourLedgerService;
@Inject
private ThirtyTeachTourLedgerFamilyService tourLedgerFamilyService;
@Inject
private ThirtyTeachTourLedgerDirectRelativeService tourLedgerDirectRelativeService;
@Inject
private ThirtyTeachTourUserAssignmentService tourUserAssignmentService;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/ThirtyTeachTour/unionledger/index.html")
@SaCheckPermission("thirtyTeachTour.unionledger")
public void index() {
}
@At
@SaCheckPermission("thirtyTeachTour.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 thirty_teach_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 thirty_teach_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 thirty_teach_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 thirty_teach_tour_ledger_family
WHERE delFlag = 0
GROUP BY ledgerId
) f ON f.ledgerId = t.id
LEFT JOIN thirty_teach_tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN thirty_teach_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 thirty_teach_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("thirtyTeachTour.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("thirtyTeachTour.unionledger")
public Result detail(String id) {
ThirtyTeachTourLedger ledger = fetchScopedLedger(id);
if (ledger == null) {
return Result.error("台账记录不存在或无权查看");
}
ledger.setLineName(getCurrentLineName(ledger));
Cnd familyCnd = Cnd.where(ThirtyTeachTourLedgerFamily::getLedgerId, "=", id)
.and(ThirtyTeachTourLedgerFamily::getDelFlag, "=", false);
familyCnd.asc(ThirtyTeachTourLedgerFamily::getCreatedAt);
ThirtyTeachTourLedgerDirectRelative directRelative = tourLedgerDirectRelativeService.fetch(
Cnd.where(ThirtyTeachTourLedgerDirectRelative::getDelFlag, "=", false)
.and(ThirtyTeachTourLedgerDirectRelative::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("thirtyTeachTour.unionledger")
@SLog(type = "tour", tag = "分工会疗休养台账", msg = "删除分工会疗休养台账")
public Result doDelete(String id) {
ThirtyTeachTourLedger ledger = fetchScopedLedger(id);
if (ledger == null) {
return Result.error("台账记录不存在或无权删除");
}
// 删除分工会台账时同步清理台账明细和人员分配中的线路快照,避免人员分配列表继续显示已删除报名的路线信息。
tourLedgerFamilyService.clear(Cnd.where(ThirtyTeachTourLedgerFamily::getLedgerId, "=", ledger.getId()));
tourLedgerDirectRelativeService.clear(Cnd.where(ThirtyTeachTourLedgerDirectRelative::getLedgerId, "=", ledger.getId()));
tourLedgerService.clear(Cnd.where(ThirtyTeachTourLedger::getId, "=", ledger.getId()));
tourUserAssignmentService.clearExistingAssignmentMatterAfterLedgerDelete(ledger.getMatterId(), ledger.getJobNo());
return Result.success();
}
@At
@SaCheckPermission("thirtyTeachTour.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 thirty_teach_tour_ledger t
LEFT JOIN thirty_teach_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("thirtyTeachTour.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 thirty_teach_tour_ledger t
LEFT JOIN thirty_teach_tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN thirty_teach_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("thirtyTeachTour.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 thirty_teach_tour_ledger t
LEFT JOIN thirty_teach_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("thirtyTeachTour.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 thirty_teach_tour_ledger t
LEFT JOIN thirty_teach_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("thirtyTeachTour.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 thirty_teach_tour_ledger t
LEFT JOIN thirty_teach_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 thirty_teach_tour_ledger t
INNER JOIN thirty_teach_tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN (
SELECT ledgerId, COUNT(1) AS familyCount
FROM thirty_teach_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,
COALESCE(NULLIF(t.idCard, ''), NULLIF(vu.idCard, ''), '') AS idCard,
t.unionName,
COALESCE(NULLIF(t.mobile, ''), NULLIF(vu.mobile, ''), '') AS 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 thirty_teach_tour_ledger t
LEFT JOIN thirty_teach_tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN thirty_teach_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 thirty_teach_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 ThirtyTeachTourLedger fetchScopedLedger(String id) {
if (StrUtil.isBlank(id) || StrUtil.isBlank(currentUnionId())) {
return null;
}
Sql sql = Sqls.create("""
SELECT COUNT(1)
FROM thirty_teach_tour_ledger t
LEFT JOIN thirty_teach_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(ThirtyTeachTourLedger 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 thirty_teach_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 thirty_teach_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(ThirtyTeachTourLedger ledger) {
if (ledger == null || StrUtil.isBlank(ledger.getLineId())) {
return false;
}
Sql sql = Sqls.create("""
SELECT IFNULL(directFamilyUnitLine, 0)
FROM thirty_teach_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(ThirtyTeachTourLedger ledger) {
if (ledger == null || StrUtil.isBlank(ledger.getMatterId())) {
return true;
}
Sql sql = Sqls.create("""
SELECT IFNULL(MAX(s.fillBedInfo), 1)
FROM thirty_teach_tour_ledger t
LEFT JOIN thirty_teach_tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN thirty_teach_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(ThirtyTeachTourLedger 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 thirty_teach_tour_ledger t
LEFT JOIN thirty_teach_tour_matter m ON m.id = t.matterId AND m.delFlag = 0
LEFT JOIN thirty_teach_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.thirtyTeachTour.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 ThirtyTeachTourLedgerImportExcelMode 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,103 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.ColDefine;
import org.nutz.dao.entity.annotation.ColType;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Name;
import org.nutz.dao.entity.annotation.Table;
import org.nutz.dao.entity.annotation.TableMeta;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serializable;
/**
* 疗休养退出申请。
* 仅作为不能参加人员的维护台账,不直接关联报名、疗休养台账、人员分配等业务流程。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("thirty_teach_tour_leave_apply")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("疗休养退出申请")
public class ThirtyTeachTourLeaveApply extends BaseModel implements Serializable {
public static final String STATUS_PENDING = "PENDING";
public static final String STATUS_APPROVED = "APPROVED";
public static final String STATUS_REJECTED = "REJECTED";
@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 matterId;
@Column
@Comment("线路ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String lineId;
@Column
@Comment("工号")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String jobNo;
@Column
@Comment("姓名")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String userName;
@Column
@Comment("用户ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String userId;
@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.TEXT)
private String reason;
@Column
@Comment("状态")
@ColDefine(type = ColType.VARCHAR, width = 30)
private String status;
@Column
@Comment("审核人ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String auditBy;
@Column
@Comment("审核人姓名")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String auditName;
@Column
@Comment("审核时间")
@ColDefine(type = ColType.VARCHAR, width = 30)
private String auditTime;
@Column
@Comment("审核备注")
@ColDefine(type = ColType.TEXT)
private String auditRemark;
}
@@ -0,0 +1,167 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.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("thirty_teach_tour_ledger")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("普惠疗休养台账")
public class ThirtyTeachTourLedger 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.INT, width = 3)
private Integer age;
@Column
@Comment("身份证号")
@ColDefine(type = ColType.VARCHAR, width = 30)
private String idCard;
@Column
@Comment("手机号")
@ColDefine(type = ColType.VARCHAR, width = 30)
private String mobile;
@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.VARCHAR, width = 100)
private String boardingPlace;
@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.thirtyTeachTour.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("thirty_teach_tour_ledger_direct_relative")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("普惠疗休养直系亲属线路报名信息")
public class ThirtyTeachTourLedgerDirectRelative 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.thirtyTeachTour.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("thirty_teach_tour_ledger_family")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("普惠疗休养台账家属信息")
public class ThirtyTeachTourLedgerFamily 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.thirtyTeachTour.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("thirty_teach_tour_line")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("普惠疗休养线路")
public class ThirtyTeachTourLine 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,129 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.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("thirty_teach_tour_matter")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("普惠疗休养事项")
public class ThirtyTeachTourMatter 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 = 100)
private String defaultBoardingPlace;
@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,167 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.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("thirty_teach_tour_setting")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("普惠疗休养配置")
public class ThirtyTeachTourSetting 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("乘车地点列表JSON")
@ColDefine(type = ColType.TEXT)
private String boardingPlace;
@Column
@Comment("出行人数指标")
@ColDefine(type = ColType.INT)
@Default("0")
private Integer travelPeopleQuota;
@Column
@Comment("可参加人员范围ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String activityGroupId;
@Column
@Comment("报名资格校验方式")
@ColDefine(type = ColType.VARCHAR, width = 30)
@Default("ASSIGNED_USER")
private String signupEligibilityMode;
@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;
@Column
@Comment("是否展示PC首页报名入口")
@ColDefine(type = ColType.BOOLEAN)
@Default("0")
private Boolean homeSignupEntryEnabled;
@Column
@Comment("PC首页报名入口图片")
@ColDefine(type = ColType.VARCHAR, width = 500)
private String homeSignupEntryImage;
/**
* 标段管理沿用老疗休养配置的子表设计,后续线路、旅行社等模块可通过标段ID继续关联。
*/
@Many(field = "settingId")
private List<ThirtyTeachTourSettingLot> lots;
/**
* 分工会名额分配,仅用于配置弹窗回显与提交,不作为 thirty_teach_tour_setting 表字段保存。
*/
private List<ThirtyTeachTourSettingUnionQuota> unionQuotas;
@Column
@Comment("服务须知")
@ColDefine(type = ColType.TEXT)
private String serviceNotice;
}
@@ -0,0 +1,53 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.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("thirty_teach_tour_setting_lot")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("普惠疗休养配置标段")
public class ThirtyTeachTourSettingLot 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,66 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.ColDefine;
import org.nutz.dao.entity.annotation.ColType;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Default;
import org.nutz.dao.entity.annotation.Name;
import org.nutz.dao.entity.annotation.Table;
import org.nutz.dao.entity.annotation.TableMeta;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serializable;
/**
* 疗休养配置下的分工会名额分配。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("thirty_teach_tour_setting_union_quota")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("疗休养配置分工会名额分配")
public class ThirtyTeachTourSettingUnionQuota 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("分工会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.INT)
@Default("0")
private Integer formalQuota;
@Column
@Comment("替补人员名额")
@ColDefine(type = ColType.INT)
@Default("0")
private Integer backupQuota;
/**
* 页面展示用实时会员数,不落库。
*/
private Integer memberCount;
}
@@ -0,0 +1,74 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.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("thirty_teach_tour_travel_agency")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("普惠疗休养旅行社")
public class ThirtyTeachTourTravelAgency 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,165 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.ColDefine;
import org.nutz.dao.entity.annotation.ColType;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Default;
import org.nutz.dao.entity.annotation.Name;
import org.nutz.dao.entity.annotation.Table;
import org.nutz.dao.entity.annotation.TableMeta;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serializable;
/**
* 疗休养人员分配表。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("thirty_teach_tour_user_assignment")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("疗休养人员分配")
public class ThirtyTeachTourUserAssignment extends BaseModel implements Serializable {
public static final String ASSIGN_SOURCE_SCHOOL_UNION = "SCHOOL_UNION";
public static final String ASSIGN_SOURCE_BRANCH_UNION = "BRANCH_UNION";
public static final String PERSON_TYPE_FORMAL = "FORMAL";
public static final String PERSON_TYPE_BACKUP = "BACKUP";
@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("疗休养事项ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String matterId;
@Column
@Comment("疗休养事项名称")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String matterName;
@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("线路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 = 100)
private String boardingPlace;
@Column
@Comment("人员ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String userId;
@Column
@Comment("工号")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String loginName;
@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.INT, width = 3)
private Integer age;
@Column
@Comment("身份证号")
@ColDefine(type = ColType.VARCHAR, width = 30)
private String idCard;
@Column
@Comment("手机号")
@ColDefine(type = ColType.VARCHAR, width = 30)
private String mobile;
@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 assignSource;
@Column
@Comment("人员类型")
@ColDefine(type = ColType.VARCHAR, width = 30)
private String personType;
@Column
@Comment("是否已退出")
@ColDefine(type = ColType.BOOLEAN)
@Default("0")
private Boolean cancelled;
@Column
@Comment("分配人ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String assignedBy;
@Column
@Comment("分配人姓名")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String assignedName;
@Column
@Comment("分配时间")
@ColDefine(type = ColType.VARCHAR, width = 30)
private String assignedAt;
}
@@ -0,0 +1,31 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourLeaveApply;
import org.nutz.lang.util.NutMap;
public interface ThirtyTeachTourLeaveApplyService extends BaseService<ThirtyTeachTourLeaveApply> {
/**
* 分页查询人员分配表中的退出/取消管理数据,关键词同时匹配工号和姓名。
* 数据权限:系统管理员、校工会主席看全部;分工会主席看本分工会;普通用户看本人。
*/
Pagination<NutMap> pageData(PageForm pageForm, String keyword, String unionName, String status);
/**
* 查询当前登录人在退出取消管理页的按钮权限。
*/
NutMap permissionInfo();
/**
* 取消指定人员分配记录,同时删除对应报名台账并标记已退出。
*/
void cancelAssignment(String id);
/**
* 恢复指定人员分配记录的退出状态,不恢复已删除台账。
*/
void restoreAssignment(String id);
}
@@ -0,0 +1,7 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourLedgerDirectRelative;
public interface ThirtyTeachTourLedgerDirectRelativeService extends BaseService<ThirtyTeachTourLedgerDirectRelative> {
}
@@ -0,0 +1,7 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourLedgerFamily;
public interface ThirtyTeachTourLedgerFamilyService extends BaseService<ThirtyTeachTourLedgerFamily> {
}
@@ -0,0 +1,7 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourLedger;
public interface ThirtyTeachTourLedgerService extends BaseService<ThirtyTeachTourLedger> {
}
@@ -0,0 +1,7 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourLine;
public interface ThirtyTeachTourLineService extends BaseService<ThirtyTeachTourLine> {
}
@@ -0,0 +1,7 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourMatter;
public interface ThirtyTeachTourMatterService extends BaseService<ThirtyTeachTourMatter> {
}
@@ -0,0 +1,42 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourSetting;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourSettingUnionQuota;
import java.util.List;
public interface ThirtyTeachTourSettingService extends BaseService<ThirtyTeachTourSetting> {
/**
* 查询所有分工会在指定疗休养配置下的名额,实时补充当前工会会员数供页面分配时参考。
*
* @param settingId 疗休养配置ID,新增时可为空
* @return 按分工会编码排序后的名额列表
*/
List<ThirtyTeachTourSettingUnionQuota> listUnionQuotaRows(String settingId);
/**
* 保存指定疗休养配置下的分工会名额,前端传入的是 JSON 数组字符串。
*
* @param settingId 疗休养配置ID
* @param unionQuotas 分工会名额 JSON
*/
void saveUnionQuotas(String settingId, String unionQuotas);
/**
* 校验分工会正式人员名额合计是否超过分工会总名额。
*
* @param unionQuotas 分工会名额 JSON
* @param branchTotalQuota 分工会总名额
* @return 通过返回空字符串;不通过返回业务提示
*/
String checkBranchFormalQuotaLimit(String unionQuotas, int branchTotalQuota);
/**
* 清理指定疗休养配置下的分工会名额。
*
* @param settingId 疗休养配置ID
*/
void clearUnionQuotas(String settingId);
}
@@ -0,0 +1,7 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourTravelAgency;
public interface ThirtyTeachTourTravelAgencyService extends BaseService<ThirtyTeachTourTravelAgency> {
}
@@ -0,0 +1,406 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourUserAssignment;
import org.nutz.lang.util.NutMap;
import java.util.List;
public interface ThirtyTeachTourUserAssignmentService extends BaseService<ThirtyTeachTourUserAssignment> {
/**
* 分页查询校工会人员分配记录,列表只读取人员分配表,不读取报名台账。
*
* @param pageForm 分页、排序参数
* @param year 疗休养年度
* @param settingId 疗休养配置ID
* @param matterId 疗休养事项ID
* @param unionId 所属分工会ID
* @param personType 人员类型:FORMAL 正式人员,BACKUP 替补人员
* @param keyword 姓名或工号关键字
* @param assignSource 分配类别:SCHOOL_UNION 校工会分配,BRANCH_UNION 分工会分配;为空时查询全部
* @param cancelled 是否退出:true 已退出,false 未退出,空时查询全部
* @return 校工会分配记录分页数据
*/
Pagination<NutMap> schoolAssignmentPage(PageForm pageForm, Integer year, String settingId, String matterId,
String unionId, String personType, String keyword, String assignSource,
Boolean cancelled);
/**
* 按校工会人员分配页面当前筛选条件查询导出数据,不做分页截断。
*
* @param pageForm 排序参数,导出顺序与页面当前排序保持一致
* @param year 疗休养年度
* @param settingId 疗休养配置ID
* @param matterId 疗休养事项ID
* @param unionId 所属分工会ID
* @param personType 人员类型:FORMAL 正式人员,BACKUP 替补人员
* @param keyword 姓名或工号关键字
* @param assignSource 分配类别:SCHOOL_UNION 校工会分配,BRANCH_UNION 分工会分配;为空时查询全部
* @param cancelled 是否退出:true 已退出,false 未退出,空时查询全部
* @return 符合当前页面查询条件的人员分配导出数据
*/
List<NutMap> schoolAssignmentExportRows(PageForm pageForm, Integer year, String settingId, String matterId,
String unionId, String personType, String keyword, String assignSource,
Boolean cancelled);
/**
* 按校工会人员分配页面当前筛选条件发送报名提醒。
*
* @param pageForm 排序参数,提醒接收人范围与当前列表筛选口径保持一致,不按当前分页截断
* @param year 疗休养年度
* @param settingId 疗休养配置ID
* @param matterId 疗休养事项ID
* @param unionId 所属分工会ID
* @param personType 人员类型:FORMAL 正式人员,BACKUP 替补人员
* @param keyword 姓名或工号关键字
* @param assignSource 分配类别:SCHOOL_UNION 校工会分配,BRANCH_UNION 分工会分配;为空时查询全部
* @param cancelled 是否退出:true 已退出,false 未退出,空时查询全部
* @param content 提醒内容,由页面弹框输入,不能为空
* @return 发送结果,receiverCount 表示本次去重后的接收人数
*/
NutMap sendSchoolAssignmentReminder(PageForm pageForm, Integer year, String settingId, String matterId,
String unionId, String personType, String keyword, String assignSource,
Boolean cancelled, String content);
/**
* 分页查询校工会可分配候选人,候选人来自疗休养配置的可参加人员范围,并排除同一配置下已分配人员。
*
* @param pageForm 分页、排序参数
* @param settingId 疗休养配置ID
* @param unionId 所属分工会ID
* @param keyword 姓名或工号关键字
* @param userIds 指定候选人员ID列表,人员选择器多选查询时使用
* @return 可分配候选人分页数据
*/
Pagination<NutMap> schoolCandidatePage(PageForm pageForm, String settingId, String unionId, String keyword, List<String> userIds);
/**
* 分页查询当前登录人所在分工会的人员分配记录,列表只读取人员分配表,不读取报名台账。
*
* @param pageForm 分页、排序参数
* @param year 疗休养年度
* @param settingId 疗休养配置ID
* @param matterId 疗休养事项ID
* @param personType 人员类型:FORMAL 正式人员,BACKUP 替补人员
* @param keyword 姓名或工号关键字
* @return 分工会分配记录分页数据
*/
Pagination<NutMap> branchAssignmentPage(PageForm pageForm, Integer year, String settingId, String matterId,
String personType, String keyword);
/**
* 按分工会人员分配页面当前筛选条件查询导出数据,不做分页截断。
*
* @param pageForm 排序参数,导出顺序与页面当前排序保持一致
* @param year 疗休养年度
* @param settingId 疗休养配置ID
* @param matterId 疗休养事项ID
* @param personType 人员类型:FORMAL 正式人员,BACKUP 替补人员
* @param keyword 姓名或工号关键字
* @return 当前登录人所在分工会下符合页面查询条件的人员分配导出数据
*/
List<NutMap> branchAssignmentExportRows(PageForm pageForm, Integer year, String settingId, String matterId,
String personType, String keyword);
/**
* 分页查询当前登录人所在分工会可分配候选人,候选人来自疗休养配置的可参加人员范围。
*
* @param pageForm 分页、排序参数
* @param settingId 疗休养配置ID
* @param keyword 姓名或工号关键字
* @param userIds 指定候选人员ID列表,人员选择器多选查询时使用
* @return 当前分工会可分配候选人分页数据
*/
Pagination<NutMap> branchCandidatePage(PageForm pageForm, String settingId, String keyword, List<String> userIds);
/**
* 查询启用的疗休养配置选项,供人员分配列表筛选和分配弹窗复用。
*
* @param year 疗休养年度
* @return 配置选项列表
*/
List<NutMap> listEnabledSettingOptions(Integer year);
/**
* 查询指定配置下已配置线路的分配线路选项,用于人员分配时指定事项、线路和旅行社。
*
* @param settingId 疗休养配置ID
* @return 事项选项列表
*/
List<NutMap> listMatterOptions(String settingId);
/**
* 查询指定配置下当前处于报名时间内的分配线路选项,供分工会人员选择线路时使用。
*
* @param settingId 疗休养配置ID
* @return 当前报名时间内的事项选项列表
*/
List<NutMap> listSignupOpenMatterOptions(String settingId);
/**
* 查询分工会选项,供校工会分配候选人和列表筛选使用。
*
* @return 分工会选项列表
*/
List<NutMap> listUnionOptions();
/**
* 保存校工会人员分配。保存前会重新按配置可参加人员范围过滤,防止写入范围外人员。
* 分配线路为可选;正式人员选择线路时视为代报名并同步写入疗休养台账,替补人员不能分配线路。
*
* @param settingId 疗休养配置ID
* @param matterId 疗休养事项ID,可为空
* @param personType 人员类型:FORMAL 正式人员,BACKUP 替补人员
* @param userIds 待分配人员ID列表
* @return 保存结果,包含实际分配数量、跳过数量和代报名台账写入数量
*/
NutMap assignSchoolUsers(String settingId, String matterId, String personType, List<String> userIds);
/**
* 保存校工会人员分配,支持每个候选人员单独选择分配线路。
* assignItems 中每项包含 userId、matterIdmatterId 为空时只保存人员分配,不写台账。
*
* @param settingId 疗休养配置ID
* @param assignItems 人员和分配线路明细
* @return 保存结果,包含实际分配数量、跳过数量和代报名台账写入数量
*/
NutMap assignSchoolUsers(String settingId, List<NutMap> assignItems);
/**
* 给校工会已分配的正式人员补选分配线路,并按代报名写入疗休养台账。
* 只处理 SCHOOL_UNION 来源且尚未选择线路的分配记录,避免覆盖既有台账。
*
* @param id 人员分配记录ID
* @param matterId 疗休养事项ID
* @return 处理结果,包含台账写入数量
*/
NutMap selectSchoolAssignmentMatter(String id, String matterId);
/**
* 给当前分工会已分配的正式人员补选分配路线,并按代报名写入疗休养台账。
* 只处理当前登录人所在分工会、BRANCH_UNION 来源且尚未选择路线的记录;写台账前校验最多成团人数。
*
* @param id 人员分配记录ID
* @param matterId 疗休养事项ID
* @return 处理结果,包含台账写入数量
*/
NutMap selectCurrentBranchAssignmentMatter(String id, String matterId);
/**
* 保存分工会人员分配。保存前会重新按配置可参加人员范围和当前登录人所在分工会过滤,并校验正式/替补名额。
* 疗休养事项为可选;正式人员选择事项时视为代报名并同步写入疗休养台账,替补人员不能分配事项。
*
* @param settingId 疗休养配置ID
* @param matterId 疗休养事项ID,可为空
* @param personType 人员类型:FORMAL 正式人员,BACKUP 替补人员
* @param userIds 待分配人员ID列表
* @return 保存结果,包含实际分配数量、跳过数量、代报名台账写入数量和剩余名额
*/
NutMap assignBranchUsers(String settingId, String matterId, String personType, List<String> userIds);
/**
* 切换当前分工会人员分配记录的正式/替补状态。
* 切换时会校验当前登录人所在分工会、分配来源和目标类型剩余名额;已分配事项的正式人员不能切换为替补。
*
* @param id 人员分配记录ID
* @param personType 目标人员类型:FORMAL 正式人员,BACKUP 替补人员
* @return 切换后的人员类型和最新名额信息
*/
NutMap switchCurrentBranchPersonType(String id, String personType);
/**
* 查询移动端疗休养信息确认页所需的当前用户信息。
* 优先读取当前年度人员分配表;无分配记录时读取 vw_user 基础信息用于页面展示。
*
* @return 当前登录人的信息确认数据和乘车地点选项
*/
NutMap currentH5ConfirmInfo();
/**
* 保存移动端信息确认页填写的人员基础信息到当前年度人员分配表。
* 仅更新既有人员分配记录,不新增分配名单,避免绕过疗休养报名资格控制。
*
* @param userName 姓名
* @param gender 性别
* @param age 年龄
* @param idCard 身份证号
* @param mobile 手机号
* @param boardingPlace 乘车地点
*/
void saveCurrentH5ConfirmInfo(String userName, String gender, Integer age, String idCard, String mobile, String boardingPlace);
/**
* 查询指定配置下当前登录人的人员分配确认信息。
* 报名详情和最终提交报名时使用该信息回填台账,保证移动端确认信息能落到报名台账。
*
* @param settingId 疗休养配置ID
* @return 人员分配确认信息;不存在时返回空 Map
*/
NutMap currentUserAssignmentInfo(String settingId);
/**
* 用户报名写入台账后,回填该用户在同一疗休养配置下已存在的人员分配记录。
* 仅更新事项、线路、旅行社快照字段,不新增记录,不修改 assignSource 和人员类型。
*
* @param settingId 疗休养配置ID
* @param matterId 疗休养事项ID
* @param userId 报名用户ID
* @param boardingPlace 用户报名时最终选择的乘车地点
* @return 更新记录数
*/
int backfillExistingAssignmentAfterSignup(String settingId, String matterId, String userId, String boardingPlace);
/**
* 用户取消报名删除台账后,清空该用户在同一疗休养配置下已存在人员分配记录的事项快照。
* 仅清空事项、线路、旅行社字段,不删除记录,不修改 assignSource 和人员类型。
*
* @param settingId 疗休养配置ID
* @param userId 报名用户ID
* @return 更新记录数
*/
int clearExistingAssignmentMatterAfterCancel(String settingId, String userId);
/**
* 台账管理删除报名台账后,按原台账的事项和工号清空对应人员分配记录的事项快照。
* 仅清空事项、线路、旅行社和乘车地点字段;出行时间由事项关联展示,清空事项后页面不再显示原出行时间。
*
* @param matterId 台账原报名事项ID
* @param jobNo 台账原报名人工号
* @return 更新记录数
*/
int clearExistingAssignmentMatterAfterLedgerDelete(String matterId, String jobNo);
/**
* 查询当前登录人所在分工会在指定疗休养配置下的名额使用情况。
*
* @param settingId 疗休养配置ID
* @return 正式/替补名额、已用名额和剩余名额
*/
NutMap branchQuotaInfo(String settingId);
/**
* 查询校工会人员分配记录是否已存在对应报名台账,用于删除前二次确认。
*
* @param id 人员分配记录ID
* @return hasLedger 表示是否存在台账,ledgerCount 表示对应台账数量
*/
NutMap schoolDeleteInfo(String id);
/**
* 查询当前分工会人员分配记录是否已存在对应报名台账,用于删除前二次确认。
*
* @param id 人员分配记录ID
* @return hasLedger 表示是否存在台账,ledgerCount 表示对应台账数量
*/
NutMap branchDeleteInfo(String id);
/**
* 删除指定来源的人员分配记录,避免校工会页面误删分工会分配的数据。
*
* @param id 人员分配记录ID
* @param assignSource 分配来源
*/
void deleteBySource(String id, String assignSource);
/**
* 删除指定来源的人员分配记录,可选择是否同步删除该分配记录对应的报名台账。
*
* @param id 人员分配记录ID
* @param assignSource 分配来源
* @param deleteLedger 是否同步删除 matterId + loginName 对应的台账及明细
*/
void deleteBySource(String id, String assignSource, boolean deleteLedger);
/**
* 删除当前登录人所在分工会的人员分配记录,避免分工会页面删除其它分工会的数据。
*
* @param id 人员分配记录ID
*/
void deleteCurrentBranchAssignment(String id);
/**
* 删除当前登录人所在分工会的人员分配记录,可选择是否同步删除该分配记录对应的报名台账。
*
* @param id 人员分配记录ID
* @param deleteLedger 是否同步删除 matterId + loginName 对应的台账及明细
*/
void deleteCurrentBranchAssignment(String id, boolean deleteLedger);
/**
* 判断用户是否已存在于指定疗休养配置的人员分配表中。
*
* @param settingId 疗休养配置ID
* @param userId 用户ID
* @return 存在返回 true,不存在返回 false
*/
boolean existsAssignedUser(String settingId, String userId);
/**
* 校验用户是否为指定疗休养配置下的正式分配人员。
* 用于报名资格方式为 ASSIGNED_USER 时,拦截未分配人员和替补人员。
* 已退出人员也会被拦截,避免取消后再次报名。
*
* @param settingId 疗休养配置ID
* @param userId 用户ID
* @return canApply 表示是否可报名,message 表示不可报名原因
*/
NutMap checkFormalAssignedUser(String settingId, String userId);
/**
* 实时统计指定疗休养配置下校工会来源的正式分配人数。
* 已退出人员不再占用工会名额,统计时需要排除。
*
* @param settingId 疗休养配置ID
* @return 当前配置下校工会正式人员分配数量
*/
int countSchoolFormalAssignedUsers(String settingId);
/**
* 将人员分配记录标记为已退出,并删除该记录对应事项下的报名台账。
* 取消前会校验路线出行开始时间,只有出行开始前指定天数之前允许取消。
*
* @param id 人员分配记录ID
*/
void cancelAssignment(String id);
/**
* 查询疗休养退出取消的出行开始前限制天数,供移动端按钮展示和实际退出校验保持一致。
*
* @return 出行开始前允许退出的天数
*/
int getCancelDeadlineDays();
/**
* 将人员分配记录从已退出恢复为未退出。
* 只恢复人员分配表状态,不自动恢复已删除的报名台账。
*
* @param id 人员分配记录ID
*/
void restoreCancelledAssignment(String id);
/**
* 按分配来源恢复已退出人员,避免校工会和分工会页面互相恢复对方数据。
*
* @param id 人员分配记录ID
* @param assignSource 分配来源
*/
void restoreCancelledAssignmentBySource(String id, String assignSource);
/**
* 恢复当前登录人所在分工会的已退出人员分配记录。
* 用于分工会人员分配列表的“取消退出”按钮,防止跨分工会恢复数据。
*
* @param id 人员分配记录ID
*/
void restoreCurrentBranchCancelledAssignment(String id);
/**
* 按疗休养配置清理人员分配数据,供删除配置或后续重置分配时复用。
*
* @param settingId 疗休养配置ID
*/
void clearBySettingId(String settingId);
}
@@ -0,0 +1,169 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.impl;
import cn.dev33.satoken.stp.StpUtil;
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.service.impl.BaseServiceImpl;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourLeaveApply;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourUserAssignment;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourLeaveApplyService;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourUserAssignmentService;
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.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
@IocBean(args = {"refer:dao"})
public class ThirtyTeachTourLeaveApplyServiceImpl extends BaseServiceImpl<ThirtyTeachTourLeaveApply> implements ThirtyTeachTourLeaveApplyService {
@Inject
private ThirtyTeachTourUserAssignmentService tourUserAssignmentService;
public ThirtyTeachTourLeaveApplyServiceImpl(Dao dao) {
super(dao);
}
@Override
public Pagination<NutMap> pageData(PageForm pageForm, String keyword, String unionName, String status) {
Sql sql = Sqls.create("""
SELECT
a.*,
s.`year` AS `year`,
s.configName AS settingName,
CASE
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
ELSE ''
END AS travelPeriod,
m.travelStartTime AS travelStartTime,
IF(IFNULL(a.cancelled, 0) = 1, 'CANCELLED', 'NORMAL') AS cancelStatus
FROM thirty_teach_tour_user_assignment a
LEFT JOIN thirty_teach_tour_setting s ON s.id = a.settingId
LEFT JOIN thirty_teach_tour_matter m ON m.id = a.matterId AND m.delFlag = 0
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("a.delFlag", "=", false);
// 退出取消只针对已选路线的数据,未分配路线的人员没有出行时间和台账可处理。
cnd.and("a.matterId", "is not", null);
cnd.and("a.matterId", "<>", "");
cnd.and("a.lineId", "is not", null);
cnd.and("a.lineId", "<>", "");
cnd.and("a.personType", "=", ThirtyTeachTourUserAssignment.PERSON_TYPE_FORMAL);
applyDataPermission(cnd);
if (StrUtil.isNotBlank(keyword)) {
SqlExpressionGroup keywordGroup = new SqlExpressionGroup();
keywordGroup.orLike("a.loginName", keyword);
keywordGroup.orLike("a.userName", keyword);
cnd.and(keywordGroup);
}
cnd.and(Cnd.likeEX("a.unionName", unionName));
if ("CANCELLED".equals(status)) {
cnd.and("a.cancelled", "=", true);
} else if ("NORMAL".equals(status)) {
cnd.and(Cnd.exps("a.cancelled", "=", false).or("a.cancelled", "is", null));
}
applyOrder(cnd, pageForm);
sql.setCondition(cnd);
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
}
@Override
public NutMap permissionInfo() {
return NutMap.NEW()
.addv("isAllDataRole", isAllDataRole())
.addv("isBranchUnionChairman", AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name()))
.addv("canRestore", StpUtil.hasPermission("thirtyTeachTour.branchUserAssignment.cancelRestore"));
}
@Override
public void cancelAssignment(String id) {
checkCanOperateAssignment(id);
tourUserAssignmentService.cancelAssignment(id);
}
@Override
public void restoreAssignment(String id) {
checkCanOperateAssignment(id);
tourUserAssignmentService.restoreCancelledAssignment(id);
}
/**
* 退出取消管理读取人员分配表,按用户角色收窄可见范围。
*/
private void applyDataPermission(Cnd cnd) {
if (isAllDataRole()) {
return;
}
if (AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name())) {
cnd.andEX("a.unionId", "=", SecurityUtil.getUnionId());
return;
}
cnd.and("a.userId", "=", SecurityUtil.getUserId());
}
private boolean isAllDataRole() {
return AuthUtil.hasRole(RoleConstant.SYSADMIN.name())
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_CHAIRMAN.name());
}
/**
* 操作前按同一数据权限校验,避免前端绕过列表直接提交其它人员记录。
*/
private void checkCanOperateAssignment(String id) {
if (StrUtil.isBlank(id)) {
throw new IllegalArgumentException("参数错误");
}
Cnd cnd = Cnd.where(ThirtyTeachTourUserAssignment::getId, "=", id)
.and(ThirtyTeachTourUserAssignment::getDelFlag, "=", false);
if (!isAllDataRole()) {
if (AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name())) {
cnd.and(ThirtyTeachTourUserAssignment::getUnionId, "=", SecurityUtil.getUnionId());
} else {
cnd.and(ThirtyTeachTourUserAssignment::getUserId, "=", SecurityUtil.getUserId());
}
}
if (dao().count(ThirtyTeachTourUserAssignment.class, cnd) <= 0) {
throw new IllegalArgumentException("人员分配记录不存在或无权操作");
}
}
/**
* 仅开放页面使用到的排序字段,避免前端传入任意字段名影响查询。
*/
private void applyOrder(Cnd cnd, PageForm pageForm) {
String orderName = pageForm.getPageOrderName();
String orderBy = pageForm.getPageOrderBy();
boolean descending = "descending".equals(orderBy);
if ("loginName".equals(orderName)) {
order(cnd, "a.loginName", descending);
} else if ("userName".equals(orderName)) {
order(cnd, "a.userName", descending);
} else if ("unionName".equals(orderName)) {
order(cnd, "a.unionName", descending);
} else if ("cancelStatus".equals(orderName)) {
order(cnd, "a.cancelled", descending);
} else if ("assignedAt".equals(orderName)) {
order(cnd, "a.assignedAt", descending);
} else {
cnd.desc("a.assignedAt");
cnd.desc("a.createdAt");
}
}
private void order(Cnd cnd, String field, boolean descending) {
if (descending) {
cnd.desc(field);
} else {
cnd.asc(field);
}
}
}
@@ -0,0 +1,15 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.impl;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourLedgerDirectRelative;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourLedgerDirectRelativeService;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean;
@IocBean(args = {"refer:dao"})
public class ThirtyTeachTourLedgerDirectRelativeServiceImpl extends BaseServiceImpl<ThirtyTeachTourLedgerDirectRelative> implements ThirtyTeachTourLedgerDirectRelativeService {
public ThirtyTeachTourLedgerDirectRelativeServiceImpl(Dao dao) {
super(dao);
}
}
@@ -0,0 +1,15 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.impl;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourLedgerFamily;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourLedgerFamilyService;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean;
@IocBean(args = {"refer:dao"})
public class ThirtyTeachTourLedgerFamilyServiceImpl extends BaseServiceImpl<ThirtyTeachTourLedgerFamily> implements ThirtyTeachTourLedgerFamilyService {
public ThirtyTeachTourLedgerFamilyServiceImpl(Dao dao) {
super(dao);
}
}
@@ -0,0 +1,15 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.impl;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourLedger;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourLedgerService;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean;
@IocBean(args = {"refer:dao"})
public class ThirtyTeachTourLedgerServiceImpl extends BaseServiceImpl<ThirtyTeachTourLedger> implements ThirtyTeachTourLedgerService {
public ThirtyTeachTourLedgerServiceImpl(Dao dao) {
super(dao);
}
}
@@ -0,0 +1,15 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.impl;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourLine;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourLineService;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean;
@IocBean(args = {"refer:dao"})
public class ThirtyTeachTourLineServiceImpl extends BaseServiceImpl<ThirtyTeachTourLine> implements ThirtyTeachTourLineService {
public ThirtyTeachTourLineServiceImpl(Dao dao) {
super(dao);
}
}
@@ -0,0 +1,15 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.impl;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourMatter;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourMatterService;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean;
@IocBean(args = {"refer:dao"})
public class ThirtyTeachTourMatterServiceImpl extends BaseServiceImpl<ThirtyTeachTourMatter> implements ThirtyTeachTourMatterService {
public ThirtyTeachTourMatterServiceImpl(Dao dao) {
super(dao);
}
}
@@ -0,0 +1,133 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.impl;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.sys.models.Sys_union;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourSetting;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourSettingUnionQuota;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourSettingService;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.lang.Lang;
import org.nutz.lang.util.NutMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@IocBean(args = {"refer:dao"})
public class ThirtyTeachTourSettingServiceImpl extends BaseServiceImpl<ThirtyTeachTourSetting> implements ThirtyTeachTourSettingService {
public ThirtyTeachTourSettingServiceImpl(Dao dao) {
super(dao);
}
@Override
public List<ThirtyTeachTourSettingUnionQuota> listUnionQuotaRows(String settingId) {
// 一次性查出所有分工会、已保存名额和实时会员数,避免页面打开时按分工会循环统计造成 N+1 查询。
Sql sql = Sqls.create("""
SELECT
quota.id AS id,
@settingId AS settingId,
un.id AS unionId,
un.name AS unionName,
COALESCE(quota.formalQuota, 0) AS formalQuota,
COALESCE(quota.backupQuota, 0) AS backupQuota,
COALESCE(user_count.memberCount, 0) AS memberCount
FROM sys_union un
LEFT JOIN thirty_teach_tour_setting_union_quota quota
ON quota.unionId = un.id
AND quota.settingId = @settingId
LEFT JOIN (
SELECT unionId, COUNT(1) AS memberCount
FROM vw_user
WHERE member = 1
GROUP BY unionId
) user_count ON user_count.unionId = un.id
ORDER BY un.unionCode ASC
""");
sql.setParam("settingId", settingId == null ? "" : settingId);
List<NutMap> rows = listMap(sql);
return rows.stream().map(row -> {
ThirtyTeachTourSettingUnionQuota quota = new ThirtyTeachTourSettingUnionQuota();
quota.setId(row.getString("id"));
quota.setSettingId(row.getString("settingId"));
quota.setUnionId(row.getString("unionId"));
quota.setUnionName(row.getString("unionName"));
quota.setFormalQuota(defaultInt(row.getInt("formalQuota")));
quota.setBackupQuota(defaultInt(row.getInt("backupQuota")));
quota.setMemberCount(defaultInt(row.getInt("memberCount")));
return quota;
}).collect(Collectors.toList());
}
@Override
public void saveUnionQuotas(String settingId, String unionQuotas) {
if (StrUtil.isBlank(settingId)) {
return;
}
clearUnionQuotas(settingId);
if (StrUtil.isBlank(unionQuotas)) {
return;
}
List<ThirtyTeachTourSettingUnionQuota> quotaList = Json.fromJsonAsList(ThirtyTeachTourSettingUnionQuota.class, unionQuotas);
if (Lang.isEmpty(quotaList)) {
return;
}
Map<String, Sys_union> unionMap = dao().query(Sys_union.class, Cnd.NEW()).stream()
.collect(Collectors.toMap(Sys_union::getId, item -> item, (a, b) -> a));
List<ThirtyTeachTourSettingUnionQuota> saveList = quotaList.stream()
.filter(item -> item != null && StrUtil.isNotBlank(item.getUnionId()))
.map(item -> normalizeUnionQuota(settingId, item, unionMap))
.filter(item -> item.getFormalQuota() > 0 || item.getBackupQuota() > 0)
.collect(Collectors.toList());
if (Lang.isNotEmpty(saveList)) {
dao().insert(saveList);
}
}
@Override
public String checkBranchFormalQuotaLimit(String unionQuotas, int branchTotalQuota) {
if (StrUtil.isBlank(unionQuotas)) {
return "";
}
List<ThirtyTeachTourSettingUnionQuota> quotaList = Json.fromJsonAsList(ThirtyTeachTourSettingUnionQuota.class, unionQuotas);
if (Lang.isEmpty(quotaList)) {
return "";
}
int formalTotal = quotaList.stream()
.filter(item -> item != null)
.mapToInt(item -> defaultInt(item.getFormalQuota()))
.sum();
if (formalTotal > branchTotalQuota) {
return "当前正式人员总数 " + formalTotal + ",分工会总名额 " + branchTotalQuota + ",分配后总人数不能超过分工会总名额";
}
return "";
}
@Override
public void clearUnionQuotas(String settingId) {
if (StrUtil.isNotBlank(settingId)) {
dao().clear(ThirtyTeachTourSettingUnionQuota.class, Cnd.where(ThirtyTeachTourSettingUnionQuota::getSettingId, "=", settingId));
}
}
private ThirtyTeachTourSettingUnionQuota normalizeUnionQuota(String settingId, ThirtyTeachTourSettingUnionQuota item, Map<String, Sys_union> unionMap) {
Sys_union union = unionMap.get(item.getUnionId());
ThirtyTeachTourSettingUnionQuota quota = new ThirtyTeachTourSettingUnionQuota();
quota.setSettingId(settingId);
quota.setUnionId(item.getUnionId());
quota.setUnionName(union == null ? item.getUnionName() : union.getName());
quota.setFormalQuota(defaultInt(item.getFormalQuota()));
quota.setBackupQuota(defaultInt(item.getBackupQuota()));
return quota;
}
private Integer defaultInt(Integer value) {
return value == null || value < 0 ? 0 : value;
}
}
@@ -0,0 +1,15 @@
package com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.impl;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.models.ThirtyTeachTourTravelAgency;
import com.budwk.app.zhgh.dayofficework.thirtyTeachTour.service.ThirtyTeachTourTravelAgencyService;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean;
@IocBean(args = {"refer:dao"})
public class ThirtyTeachTourTravelAgencyServiceImpl extends BaseServiceImpl<ThirtyTeachTourTravelAgency> implements ThirtyTeachTourTravelAgencyService {
public ThirtyTeachTourTravelAgencyServiceImpl(Dao dao) {
super(dao);
}
}
@@ -0,0 +1,39 @@
-- 30年教龄疗休养移动端菜单。
-- H5 菜单挂载在 30年教龄疗休养根菜单下,权限统一使用 h5.thirtyTeachTour.*,避免与 PC 端权限混用。
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 'a9a8c4f3d3bh5a10001', p.id, CONCAT(p.path, '0101'), '30年教龄疗休养报名', 'Thirty Teach Tour Signup H5', 'menu', '/platform/thirtyTeachTour/signup/h5', 'data-pjax', '', 1, 0, 'h5.thirtyTeachTour.signup', NULL, 101, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'H5', NULL, NULL, 's', 0, 0
FROM sys_menu p
WHERE p.permission = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'h5.thirtyTeachTour.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 'a9a8c4f3d3bh5a10002', p.id, CONCAT(p.path, '0102'), '我的30年教龄疗休养', 'My Thirty Teach Tour H5', 'menu', '/platform/thirtyTeachTour/mysignup/h5', 'data-pjax', '', 1, 0, 'h5.thirtyTeachTour.mysignup', NULL, 102, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'H5', NULL, NULL, 'w', 0, 0
FROM sys_menu p
WHERE p.permission = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'h5.thirtyTeachTour.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 'a9a8c4f3d3bh5a10003', p.id, CONCAT(p.path, '0103'), '分工会审核', 'Union Approval H5', 'menu', '/platform/thirtyTeachTour/unionApproval/h5', 'data-pjax', '', 1, 0, 'h5.thirtyTeachTour.unionApproval', NULL, 103, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'H5', NULL, NULL, 'f', 0, 0
FROM sys_menu p
WHERE p.permission = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'h5.thirtyTeachTour.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 'a9a8c4f3d3bh5a10004', p.id, CONCAT(p.path, '0104'), '校工会审核', 'School Union Approval H5', 'menu', '/platform/thirtyTeachTour/schoolUnionApproval/h5', 'data-pjax', '', 1, 0, 'h5.thirtyTeachTour.schoolUnionApproval', NULL, 104, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'H5', NULL, NULL, 'x', 0, 0
FROM sys_menu p
WHERE p.permission = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'h5.thirtyTeachTour.schoolUnionApproval') 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 (
'h5.thirtyTeachTour.signup',
'h5.thirtyTeachTour.mysignup',
'h5.thirtyTeachTour.unionApproval',
'h5.thirtyTeachTour.schoolUnionApproval'
)
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,170 @@
-- 30年教龄疗休养平台电脑端菜单。
-- 使用 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
'a8c4f3d3bb1a10001',
'',
LPAD(IFNULL(MAX(CAST(path AS UNSIGNED)), 0) + 1, 4, '0'),
'30年教龄疗休养',
'ThirtyTeachTour',
'menu',
'',
'',
'ti-map-alt',
1,
0,
'thirtyTeachTour',
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 = 'thirtyTeachTour') 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 'a8c4f3d3bb1a10002', p.id, CONCAT(p.path, '0001'), '疗休养设置', 'ThirtyTeachTour Setting', 'menu', '/platform/thirtyTeachTour/setting', 'data-pjax', '', 1, 0, 'thirtyTeachTour.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 = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.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 'a8c4f3d3bb1a10003', p.id, CONCAT(p.path, '0002'), '旅行社管理', 'Travel Agency', 'menu', '/platform/thirtyTeachTour/travelAgency', 'data-pjax', '', 1, 0, 'thirtyTeachTour.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 = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.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 'a8c4f3d3bb1a10004', p.id, CONCAT(p.path, '0003'), '线路管理', 'Route Manage', 'menu', '/platform/thirtyTeachTour/route', 'data-pjax', '', 1, 0, 'thirtyTeachTour.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 = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.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 'a8c4f3d3bb1a10005', p.id, CONCAT(p.path, '0004'), '疗休养事项', 'ThirtyTeachTour Matter', 'menu', '/platform/thirtyTeachTour/matter', 'data-pjax', '', 1, 0, 'thirtyTeachTour.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 = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.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 'a8c4f3d3bb1a10006', p.id, CONCAT(p.path, '0005'), '疗休养报名', 'ThirtyTeachTour Signup', 'menu', '/platform/thirtyTeachTour/signup', 'data-pjax', '', 1, 0, 'thirtyTeachTour.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 = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.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 'a8c4f3d3bb1a10008', p.id, CONCAT(p.path, '0006'), '我的报名', 'My Signup', 'menu', '/platform/thirtyTeachTour/mysignup', 'data-pjax', '', 1, 0, 'thirtyTeachTour.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 = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.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 'a8c4f3d3bb1a10009', p.id, CONCAT(p.path, '0007'), '分工会查询', 'Union Ledger', 'menu', '/platform/thirtyTeachTour/unionledger', 'data-pjax', '', 1, 0, 'thirtyTeachTour.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 = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.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 'a8c4f3d3bb1a10011', p.id, CONCAT(p.path, '0010'), '分工会审核', 'Union Approval', 'menu', '/platform/thirtyTeachTour/unionApproval', 'data-pjax', '', 1, 0, 'thirtyTeachTour.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 = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.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 'a8c4f3d3bb1a10012', p.id, CONCAT(p.path, '0011'), '校工会审核', 'School Union Approval', 'menu', '/platform/thirtyTeachTour/schoolUnionApproval', 'data-pjax', '', 1, 0, 'thirtyTeachTour.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 = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.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 'a8c4f3d3bb1a10007', p.id, CONCAT(p.path, '0008'), '疗休养台账', 'ThirtyTeachTour Ledger', 'menu', '/platform/thirtyTeachTour/ledger', 'data-pjax', '', 1, 0, 'thirtyTeachTour.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 = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.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 'a8c4f3d3bb1a10010', p.id, CONCAT(p.path, '0009'), '线路成团', 'ThirtyTeachTour Group', 'menu', '/platform/thirtyTeachTour/group', 'data-pjax', '', 1, 0, 'thirtyTeachTour.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 = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.group') 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 'a8c4f3d3bb1a10013', p.id, CONCAT(p.path, '0012'), '分工会人员分配', 'Branch User Assignment', 'menu', '/platform/thirtyTeachTour/branchUserAssignment', 'data-pjax', '', 1, 0, 'thirtyTeachTour.branchUserAssignment', NULL, 12, 1, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'f', 0, 0
FROM sys_menu p
WHERE p.permission = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.branchUserAssignment') 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 'a8c4f3d3bb1a10101', p.id, CONCAT(p.path, '0001'), '人员分配', 'Assign', 'data', '', '', '', 0, 0, 'thirtyTeachTour.branchUserAssignment.assign', NULL, 1, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'r', 0, 0
FROM sys_menu p
WHERE p.permission = 'thirtyTeachTour.branchUserAssignment'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.branchUserAssignment.assign') 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 'a8c4f3d3bb1a10102', p.id, CONCAT(p.path, '0002'), '选择路线', 'Select Matter', 'data', '', '', '', 0, 0, 'thirtyTeachTour.branchUserAssignment.selectMatter', NULL, 2, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'x', 0, 0
FROM sys_menu p
WHERE p.permission = 'thirtyTeachTour.branchUserAssignment'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.branchUserAssignment.selectMatter') 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 'a8c4f3d3bb1a10103', p.id, CONCAT(p.path, '0003'), '切换人员类型', 'Switch Person Type', 'data', '', '', '', 0, 0, 'thirtyTeachTour.branchUserAssignment.switchPersonType', NULL, 3, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'q', 0, 0
FROM sys_menu p
WHERE p.permission = 'thirtyTeachTour.branchUserAssignment'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.branchUserAssignment.switchPersonType') 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 'a8c4f3d3bb1a10104', p.id, CONCAT(p.path, '0004'), '取消退出', 'Cancel Restore', 'data', '', '', '', 0, 0, 'thirtyTeachTour.branchUserAssignment.cancelRestore', NULL, 4, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'q', 0, 0
FROM sys_menu p
WHERE p.permission = 'thirtyTeachTour.branchUserAssignment'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.branchUserAssignment.cancelRestore') 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 'a8c4f3d3bb1a10105', p.id, CONCAT(p.path, '0005'), '删除', 'Delete', 'data', '', '', '', 0, 0, 'thirtyTeachTour.branchUserAssignment.delete', NULL, 5, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 's', 0, 0
FROM sys_menu p
WHERE p.permission = 'thirtyTeachTour.branchUserAssignment'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.branchUserAssignment.delete') t);
UPDATE sys_menu p
JOIN (SELECT DISTINCT parentId FROM sys_menu WHERE type = 'data' AND delFlag = 0) c ON c.parentId = p.id
SET p.hasChildren = 1
WHERE p.permission = 'thirtyTeachTour.branchUserAssignment';
-- 给系统管理员默认授权,其他角色可在角色管理中按需分配。
INSERT INTO sys_role_menu (roleId, menuId)
SELECT r.id, m.id
FROM sys_role r
JOIN sys_menu m ON m.permission IN (
'thirtyTeachTour',
'thirtyTeachTour.setting',
'thirtyTeachTour.travelAgency',
'thirtyTeachTour.route',
'thirtyTeachTour.matter',
'thirtyTeachTour.signup',
'thirtyTeachTour.group',
'thirtyTeachTour.mysignup',
'thirtyTeachTour.unionledger',
'thirtyTeachTour.unionApproval',
'thirtyTeachTour.schoolUnionApproval',
'thirtyTeachTour.ledger',
'thirtyTeachTour.branchUserAssignment',
'thirtyTeachTour.branchUserAssignment.assign',
'thirtyTeachTour.branchUserAssignment.selectMatter',
'thirtyTeachTour.branchUserAssignment.switchPersonType',
'thirtyTeachTour.branchUserAssignment.cancelRestore',
'thirtyTeachTour.branchUserAssignment.delete'
)
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,13 @@
-- 疗休养乘车地点字段升级脚本。
-- 配置表保存乘车地点列表 JSON;事项、人员分配、台账保存最终/默认乘车地点。
ALTER TABLE `thirty_teach_tour_setting`
MODIFY COLUMN `boardingPlace` text COMMENT '乘车地点列表JSON';
ALTER TABLE `thirty_teach_tour_matter`
ADD COLUMN `defaultBoardingPlace` varchar(100) DEFAULT NULL COMMENT '默认乘车地点' AFTER `lineId`;
ALTER TABLE `thirty_teach_tour_user_assignment`
ADD COLUMN `boardingPlace` varchar(100) DEFAULT NULL COMMENT '乘车地点' AFTER `lineName`;
ALTER TABLE `thirty_teach_tour_ledger`
ADD COLUMN `boardingPlace` varchar(100) DEFAULT NULL COMMENT '乘车地点' AFTER `travelAgencyName`;
@@ -0,0 +1,253 @@
-- 普惠疗休养系统字典初始化。
-- 执行后可在 /platform/sys/dict 页面看到:疗休养(ThirtyTeachTour) -> 组织形式/疗休养类型/线路类型/床型。
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'c30a63f0c73a44a9a8c4f3d3bdict001', '', '9500', '疗休养', '普惠疗休养平台字典', 'ThirtyTeachTour', 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` = 'ThirtyTeachTour') t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'c30a63f0c73a44a9a8c4f3d3bdict002', 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` = 'ThirtyTeachTour'
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
'c30a63f0c73a44a9a8c4f3d3bdict003', 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
'c30a63f0c73a44a9a8c4f3d3bdict004', 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
'c30a63f0c73a44a9a8c4f3d3bdict005', 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
'c30a63f0c73a44a9a8c4f3d3bdict006', 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` = 'ThirtyTeachTour'
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
'c30a63f0c73a44a9a8c4f3d3bdict007', p.id, CONCAT(p.path, '0001'), '普惠性疗休养', '疗休养类型', 'inclusiveThirtyTeachTour', 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` = 'inclusiveThirtyTeachTour' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'c30a63f0c73a44a9a8c4f3d3bdict008', p.id, CONCAT(p.path, '0002'), '优秀职工疗休养', '疗休养类型', 'excellentWorkerThirtyTeachTour', 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` = 'excellentWorkerThirtyTeachTour' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'c30a63f0c73a44a9a8c4f3d3bdict009', 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` = 'ThirtyTeachTour'
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
'c30a63f0c73a44a9a8c4f3d3bdict010', 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
'c30a63f0c73a44a9a8c4f3d3bdict011', 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
'c30a63f0c73a44a9a8c4f3d3bdict020', 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` = 'ThirtyTeachTour'
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
'c30a63f0c73a44a9a8c4f3d3bdict021', 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
'c30a63f0c73a44a9a8c4f3d3bdict022', 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
'c30a63f0c73a44a9a8c4f3d3bdict023', 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
'c30a63f0c73a44a9a8c4f3d3bdict012', '', '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
'c30a63f0c73a44a9a8c4f3d3bdict013', 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
'c30a63f0c73a44a9a8c4f3d3bdict014', 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
'c30a63f0c73a44a9a8c4f3d3bdict015', 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
'c30a63f0c73a44a9a8c4f3d3bdict016', 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
'c30a63f0c73a44a9a8c4f3d3bdict017', 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
'c30a63f0c73a44a9a8c4f3d3bdict018', 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
'c30a63f0c73a44a9a8c4f3d3bdict019', 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 ('ThirtyTeachTour', 'organizationType', 'tourType', 'lineType', 'bedType', 'familyRelationship', 'directRelative');
@@ -0,0 +1,29 @@
-- 疗休养退出申请表。
CREATE TABLE IF NOT EXISTS `thirty_teach_tour_leave_apply` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`matterId` varchar(32) DEFAULT NULL COMMENT '疗休养事项ID',
`lineId` varchar(32) DEFAULT NULL COMMENT '线路ID',
`jobNo` varchar(50) DEFAULT NULL COMMENT '工号',
`userName` varchar(100) DEFAULT NULL COMMENT '姓名',
`userId` varchar(32) DEFAULT NULL COMMENT '用户ID',
`unionId` varchar(32) DEFAULT NULL COMMENT '所属工会ID',
`unionName` varchar(100) DEFAULT NULL COMMENT '所属工会',
`reason` text COMMENT '不能参加原因',
`status` varchar(30) DEFAULT NULL COMMENT '状态',
`auditBy` varchar(32) DEFAULT NULL COMMENT '审核人ID',
`auditName` varchar(100) DEFAULT NULL COMMENT '审核人姓名',
`auditTime` varchar(30) DEFAULT NULL COMMENT '审核时间',
`auditRemark` 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_thirty_teach_tour_leave_apply_user` (`userId`),
KEY `idx_thirty_teach_tour_leave_apply_job_no` (`jobNo`),
KEY `idx_thirty_teach_tour_leave_apply_union` (`unionId`),
KEY `idx_thirty_teach_tour_leave_apply_status` (`status`),
KEY `idx_thirty_teach_tour_leave_apply_matter` (`matterId`),
KEY `idx_thirty_teach_tour_leave_apply_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='疗休养退出申请';
@@ -0,0 +1,3 @@
-- Add signup ledger age field for mobile confirmation snapshot.
ALTER TABLE `thirty_teach_tour_ledger`
ADD COLUMN `age` int DEFAULT NULL COMMENT '年龄' AFTER `gender`;
@@ -0,0 +1,7 @@
-- Add matter reference so signup records are unique per travel period/matter.
ALTER TABLE `thirty_teach_tour_ledger`
ADD COLUMN `matterId` varchar(32) DEFAULT NULL COMMENT '报名事项ID' AFTER `signupTime`;
ALTER TABLE `thirty_teach_tour_ledger`
ADD KEY `idx_thirty_teach_tour_ledger_matter` (`matterId`),
ADD KEY `idx_thirty_teach_tour_ledger_matter_job` (`matterId`, `jobNo`);
@@ -0,0 +1,3 @@
-- Add signup ledger mobile field; signup mobile is stored with the ledger and does not update user profile data.
ALTER TABLE `thirty_teach_tour_ledger`
ADD COLUMN `mobile` varchar(30) DEFAULT NULL COMMENT '手机号' AFTER `idCard`;
@@ -0,0 +1,2 @@
ALTER TABLE `thirty_teach_tour_ledger`
ADD COLUMN `overCostReimbursed` tinyint(1) DEFAULT 0 COMMENT '报销超出费用' AFTER `reimbursed`;
@@ -0,0 +1,9 @@
-- 疗休养台账查询加速索引。
ALTER TABLE `thirty_teach_tour_ledger`
ADD KEY `idx_thirty_teach_tour_ledger_year_signup` (`year`, `delFlag`, `signupTime`),
ADD KEY `idx_thirty_teach_tour_ledger_year_line` (`year`, `lineId`, `delFlag`),
ADD KEY `idx_thirty_teach_tour_ledger_year_union_type` (`year`, `unionId`, `lineType`, `delFlag`),
ADD KEY `idx_thirty_teach_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,44 @@
-- 30年教龄疗休养教职工报名台账表。
-- 本表对应 ThirtyTeachTourLedger 模型,用于保存最终报名成功后的教职工报名快照。
CREATE TABLE IF NOT EXISTS `thirty_teach_tour_ledger` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`year` int DEFAULT NULL COMMENT '年度',
`jobNo` varchar(50) DEFAULT NULL COMMENT '工号',
`userName` varchar(100) DEFAULT NULL COMMENT '姓名',
`gender` varchar(20) DEFAULT NULL COMMENT '性别',
`age` int DEFAULT NULL COMMENT '年龄',
`idCard` varchar(30) DEFAULT NULL COMMENT '身份证号',
`mobile` varchar(30) DEFAULT NULL COMMENT '手机号',
`unitId` varchar(32) DEFAULT NULL COMMENT '所在单位ID',
`unitName` varchar(100) DEFAULT NULL COMMENT '所在单位',
`unionId` varchar(32) DEFAULT NULL COMMENT '所在工会ID',
`unionName` varchar(100) DEFAULT NULL COMMENT '所在工会',
`signupTime` varchar(30) DEFAULT NULL COMMENT '报名时间',
`matterId` varchar(32) DEFAULT NULL COMMENT '报名事项ID',
`lineId` varchar(32) DEFAULT NULL COMMENT '报名线路ID',
`lineName` varchar(100) DEFAULT NULL COMMENT '报名线路',
`lineType` varchar(50) DEFAULT NULL COMMENT '线路类型',
`hotelName` varchar(100) DEFAULT NULL COMMENT '报名酒店',
`travelAgencyId` varchar(32) DEFAULT NULL COMMENT '旅行社ID',
`travelAgencyName` varchar(100) DEFAULT NULL COMMENT '旅行社',
`boardingPlace` varchar(100) DEFAULT NULL COMMENT '乘车地点',
`hasFamily` tinyint(1) DEFAULT 0 COMMENT '是否携带家属',
`intendedRoommate` varchar(100) DEFAULT NULL COMMENT '意向拼床人',
`bedType` varchar(50) DEFAULT NULL COMMENT '床型',
`bedInfo` varchar(100) DEFAULT NULL COMMENT '床位信息',
`joined` tinyint(1) DEFAULT 0 COMMENT '是否参加',
`reimbursed` tinyint(1) DEFAULT 0 COMMENT '是否报销',
`overCostReimbursed` 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_thirty_teach_tour_ledger_year_signup` (`year`, `delFlag`, `signupTime`),
KEY `idx_thirty_teach_tour_ledger_year_line` (`year`, `lineId`, `delFlag`),
KEY `idx_thirty_teach_tour_ledger_year_union_type` (`year`, `unionId`, `lineType`, `delFlag`),
KEY `idx_thirty_teach_tour_ledger_year_job` (`year`, `jobNo`, `delFlag`),
KEY `idx_thirty_teach_tour_ledger_matter` (`matterId`),
KEY `idx_thirty_teach_tour_ledger_matter_job` (`matterId`, `jobNo`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='30年教龄疗休养台账';
@@ -0,0 +1,21 @@
-- 直系亲属线路报名信息表,独立于普通携带亲属信息表。
CREATE TABLE IF NOT EXISTS `thirty_teach_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_thirty_teach_tour_ledger_direct_relative_ledger` (`ledgerId`),
KEY `idx_thirty_teach_tour_ledger_direct_relative_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养直系亲属线路报名信息';
@@ -0,0 +1,25 @@
-- 30年教龄疗休养台账家属信息表。
-- 本表对应 ThirtyTeachTourLedgerFamily 模型,用于保存报名台账关联的随行家属快照。
CREATE TABLE IF NOT EXISTS `thirty_teach_tour_ledger_family` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`ledgerId` varchar(32) DEFAULT NULL COMMENT '台账ID',
`staffJobNo` varchar(50) DEFAULT NULL COMMENT '教职工工号',
`staffName` varchar(100) DEFAULT NULL COMMENT '教职工姓名',
`familyName` varchar(100) DEFAULT NULL COMMENT '家属姓名',
`gender` varchar(20) DEFAULT NULL COMMENT '性别',
`idCard` varchar(30) DEFAULT NULL COMMENT '身份证号码',
`age` int DEFAULT NULL COMMENT '年龄',
`bedType` varchar(50) DEFAULT NULL COMMENT '床型',
`bedInfo` varchar(100) DEFAULT NULL COMMENT '床位',
`intendedRoommate` varchar(100) DEFAULT NULL COMMENT '意向拼床人',
`relationship` varchar(50) 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_thirty_teach_tour_ledger_family_ledger` (`ledgerId`),
KEY `idx_thirty_teach_tour_ledger_family_staff` (`staffJobNo`),
KEY `idx_thirty_teach_tour_ledger_family_id_card` (`idCard`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='30年教龄疗休养台账家属信息';
@@ -0,0 +1,10 @@
-- 线路管理增加创建人和所在单位业务字段。
ALTER TABLE `thirty_teach_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 `thirty_teach_tour_line`
ADD KEY `idx_thirty_teach_tour_line_creator` (`creatorUserId`),
ADD KEY `idx_thirty_teach_tour_line_unit` (`unitId`);
@@ -0,0 +1,3 @@
-- 线路管理增加是否直系亲属线路字段,默认否。
ALTER TABLE `thirty_teach_tour_line`
ADD COLUMN `directFamilyUnitLine` tinyint(1) DEFAULT 0 COMMENT '是否直系亲属线路' AFTER `openFlag`;
@@ -0,0 +1,3 @@
-- 线路管理增加是否对外开放字段,默认是。
ALTER TABLE `thirty_teach_tour_line`
ADD COLUMN `openFlag` tinyint(1) DEFAULT 1 COMMENT '是否对外开放' AFTER `mobileThumb`;
@@ -0,0 +1,7 @@
-- 疗休养事项增加创建人字段。
ALTER TABLE `thirty_teach_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 `thirty_teach_tour_matter`
ADD KEY `idx_thirty_teach_tour_matter_creator` (`creatorUserId`);
@@ -0,0 +1,26 @@
-- 疗休养事项批次表。
CREATE TABLE IF NOT EXISTS `thirty_teach_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_thirty_teach_tour_matter_batch_matter` (`matterId`),
KEY `idx_thirty_teach_tour_matter_batch_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养事项批次';
@@ -0,0 +1,37 @@
-- 疗休养事项表。
CREATE TABLE IF NOT EXISTS `thirty_teach_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 '移动端缩略图',
`lineId` varchar(32) DEFAULT NULL COMMENT '线路ID',
`defaultBoardingPlace` 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 '出行结束时间',
`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`),
UNIQUE KEY `uk_thirty_teach_tour_matter_year_name` (`year`, `matterName`),
KEY `idx_thirty_teach_tour_matter_year` (`year`),
KEY `idx_thirty_teach_tour_matter_creator` (`creatorUserId`),
KEY `idx_thirty_teach_tour_matter_setting` (`settingId`),
KEY `idx_thirty_teach_tour_matter_union` (`unionId`),
KEY `idx_thirty_teach_tour_matter_org_type` (`organizationType`),
KEY `idx_thirty_teach_tour_matter_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养事项';
@@ -0,0 +1,6 @@
-- Add activity user scope reference for tour settings.
ALTER TABLE `thirty_teach_tour_setting`
ADD COLUMN `activityGroupId` varchar(32) DEFAULT NULL COMMENT '可参加人员范围ID' AFTER `tourType`;
ALTER TABLE `thirty_teach_tour_setting`
ADD KEY `idx_thirty_teach_tour_setting_activity_group` (`activityGroupId`);
@@ -0,0 +1,4 @@
-- 疗休养配置新增乘车地点、出行人数指标。
ALTER TABLE `thirty_teach_tour_setting`
ADD COLUMN `boardingPlace` text COMMENT '乘车地点列表JSON' AFTER `tourType`,
ADD COLUMN `travelPeopleQuota` int DEFAULT 0 COMMENT '出行人数指标' AFTER `boardingPlace`;
@@ -0,0 +1,3 @@
-- 疗休养配置增加周期允许次数字段。
ALTER TABLE `thirty_teach_tour_setting`
ADD COLUMN `cycleAllowedTimes` int DEFAULT NULL COMMENT '周期允许次数' AFTER `cycleTotalCost`;
@@ -0,0 +1,2 @@
ALTER TABLE `thirty_teach_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 `thirty_teach_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 `thirty_teach_tour_setting`
ADD COLUMN `fillBedInfo` tinyint(1) DEFAULT 1 COMMENT '是否填报床位信息' AFTER `allowFamily`;
@@ -0,0 +1,4 @@
-- 疗休养配置增加PC首页报名浮动入口控制字段。
ALTER TABLE `thirty_teach_tour_setting`
ADD COLUMN `homeSignupEntryEnabled` tinyint(1) DEFAULT 0 COMMENT '是否展示PC首页报名入口' AFTER `enabled`,
ADD COLUMN `homeSignupEntryImage` varchar(500) DEFAULT NULL COMMENT 'PC首页报名入口图片' AFTER `homeSignupEntryEnabled`;
@@ -0,0 +1,3 @@
ALTER TABLE `thirty_teach_tour_setting`
ADD COLUMN `outProvinceRatioType` varchar(50) DEFAULT '当年报名人数' COMMENT '省外人数占比类型' AFTER `outProvinceRatio`,
ADD COLUMN `outProvinceFixedPeople` int DEFAULT 0 COMMENT '省外固定人数' AFTER `outProvinceRatioType`;
@@ -0,0 +1,2 @@
ALTER TABLE `thirty_teach_tour_setting`
ADD COLUMN `signupEligibilityMode` varchar(30) DEFAULT 'ASSIGNED_USER' COMMENT '报名资格校验方式' AFTER `activityGroupId`;
@@ -0,0 +1,261 @@
-- 普惠疗休养基础配置表。
-- 若生产环境未开启 Nutz 自动建表,请先执行本脚本再使用“疗休养设置”菜单。
CREATE TABLE IF NOT EXISTS `thirty_teach_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 '疗休养类型',
`boardingPlace` text COMMENT '乘车地点列表JSON',
`travelPeopleQuota` int DEFAULT 0 COMMENT '出行人数指标',
`activityGroupId` varchar(32) DEFAULT NULL COMMENT '可参加人员范围ID',
`signupEligibilityMode` varchar(30) DEFAULT 'ASSIGNED_USER' COMMENT '报名资格校验方式',
`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 '是否启用',
`homeSignupEntryEnabled` tinyint(1) DEFAULT 0 COMMENT '是否展示PC首页报名入口',
`homeSignupEntryImage` varchar(500) DEFAULT NULL COMMENT 'PC首页报名入口图片',
`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_thirty_teach_tour_setting_year` (`year`),
KEY `idx_thirty_teach_tour_setting_activity_group` (`activityGroupId`),
KEY `idx_thirty_teach_tour_setting_sort` (`sortNo`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养配置';
-- 疗休养配置标段表。
-- 标段先挂在配置上维护,后续线路、旅行社、报名等模块可继续通过 lotId 做业务关联。
CREATE TABLE IF NOT EXISTS `thirty_teach_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_thirty_teach_tour_setting_lot_setting` (`settingId`),
KEY `idx_thirty_teach_tour_setting_lot_value` (`lotValue`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养配置标段';
-- 疗休养配置分工会名额分配表。
CREATE TABLE IF NOT EXISTS `thirty_teach_tour_setting_union_quota` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`settingId` varchar(32) DEFAULT NULL COMMENT '所属疗休养配置ID',
`unionId` varchar(32) DEFAULT NULL COMMENT '分工会ID',
`unionName` varchar(100) DEFAULT NULL COMMENT '分工会名称',
`formalQuota` int DEFAULT 0 COMMENT '正式人员名额',
`backupQuota` int 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_thirty_teach_tour_setting_union_quota_setting` (`settingId`),
KEY `idx_thirty_teach_tour_setting_union_quota_union` (`unionId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='疗休养配置分工会名额分配';
-- 疗休养人员分配表。
CREATE TABLE IF NOT EXISTS `thirty_teach_tour_user_assignment` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`settingId` varchar(32) DEFAULT NULL COMMENT '疗休养配置ID',
`matterId` varchar(32) DEFAULT NULL COMMENT '疗休养事项ID',
`matterName` varchar(100) DEFAULT NULL COMMENT '疗休养事项名称',
`travelAgencyId` varchar(32) DEFAULT NULL COMMENT '旅行社ID',
`travelAgencyName` varchar(100) DEFAULT NULL COMMENT '旅行社名称',
`lineId` varchar(32) DEFAULT NULL COMMENT '线路ID',
`lineName` varchar(100) DEFAULT NULL COMMENT '线路名称',
`boardingPlace` varchar(100) DEFAULT NULL COMMENT '乘车地点',
`userId` varchar(32) DEFAULT NULL COMMENT '人员ID',
`loginName` varchar(50) DEFAULT NULL COMMENT '工号',
`userName` varchar(100) DEFAULT NULL COMMENT '姓名',
`gender` varchar(20) DEFAULT NULL COMMENT '性别',
`age` int DEFAULT NULL COMMENT '年龄',
`idCard` varchar(30) DEFAULT NULL COMMENT '身份证号',
`mobile` varchar(30) DEFAULT NULL COMMENT '手机号',
`unitId` varchar(32) DEFAULT NULL COMMENT '所在单位ID',
`unitName` varchar(100) DEFAULT NULL COMMENT '所在单位',
`unionId` varchar(32) DEFAULT NULL COMMENT '所在分工会ID',
`unionName` varchar(100) DEFAULT NULL COMMENT '所在分工会',
`assignSource` varchar(30) DEFAULT NULL COMMENT '分配来源',
`personType` varchar(30) DEFAULT NULL COMMENT '人员类型',
`cancelled` tinyint(1) DEFAULT 0 COMMENT '是否已退出',
`assignedBy` varchar(32) DEFAULT NULL COMMENT '分配人ID',
`assignedName` varchar(100) DEFAULT NULL COMMENT '分配人姓名',
`assignedAt` varchar(30) 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`),
UNIQUE KEY `uk_thirty_teach_tour_user_assignment_setting_user` (`settingId`, `userId`),
KEY `idx_thirty_teach_tour_user_assignment_setting` (`settingId`),
KEY `idx_thirty_teach_tour_user_assignment_user` (`userId`),
KEY `idx_thirty_teach_tour_user_assignment_union` (`unionId`),
KEY `idx_thirty_teach_tour_user_assignment_source` (`assignSource`),
KEY `idx_thirty_teach_tour_user_assignment_person_type` (`personType`),
KEY `idx_thirty_teach_tour_user_assignment_cancelled` (`cancelled`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='疗休养人员分配';
-- 旅行社管理表。
CREATE TABLE IF NOT EXISTS `thirty_teach_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_thirty_teach_tour_travel_agency_year_code` (`year`, `agencyCode`),
KEY `idx_thirty_teach_tour_travel_agency_name` (`agencyName`),
KEY `idx_thirty_teach_tour_travel_agency_contact` (`contactName`, `contactPhone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养旅行社';
-- 线路管理表。
CREATE TABLE IF NOT EXISTS `thirty_teach_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_thirty_teach_tour_line_year_code` (`year`, `lineCode`),
KEY `idx_thirty_teach_tour_line_year` (`year`),
KEY `idx_thirty_teach_tour_line_name` (`lineName`),
KEY `idx_thirty_teach_tour_line_agency` (`travelAgencyId`),
KEY `idx_thirty_teach_tour_line_creator` (`creatorUserId`),
KEY `idx_thirty_teach_tour_line_unit` (`unitId`),
KEY `idx_thirty_teach_tour_line_lot` (`lotId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养线路';
-- 疗休养事项表。
CREATE TABLE IF NOT EXISTS `thirty_teach_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 '移动端缩略图',
`lineId` varchar(32) DEFAULT NULL COMMENT '线路ID',
`defaultBoardingPlace` 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 '出行结束时间',
`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`),
UNIQUE KEY `uk_thirty_teach_tour_matter_year_name` (`year`, `matterName`),
KEY `idx_thirty_teach_tour_matter_year` (`year`),
KEY `idx_thirty_teach_tour_matter_creator` (`creatorUserId`),
KEY `idx_thirty_teach_tour_matter_setting` (`settingId`),
KEY `idx_thirty_teach_tour_matter_union` (`unionId`),
KEY `idx_thirty_teach_tour_matter_org_type` (`organizationType`),
KEY `idx_thirty_teach_tour_matter_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养事项';
-- 疗休养事项批次表。
CREATE TABLE IF NOT EXISTS `thirty_teach_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_thirty_teach_tour_matter_batch_matter` (`matterId`),
KEY `idx_thirty_teach_tour_matter_batch_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养事项批次';
-- 直系亲属线路报名信息表。
CREATE TABLE IF NOT EXISTS `thirty_teach_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_thirty_teach_tour_ledger_direct_relative_ledger` (`ledgerId`),
KEY `idx_thirty_teach_tour_ledger_direct_relative_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养直系亲属线路报名信息';
@@ -0,0 +1,2 @@
ALTER TABLE `thirty_teach_tour_setting_lot`
ADD COLUMN `allowOverReimbursement` tinyint(1) DEFAULT 0 COMMENT '允许超出报销' AFTER `activityCost`;
@@ -0,0 +1,17 @@
-- 疗休养配置分工会名额分配表。
CREATE TABLE IF NOT EXISTS `thirty_teach_tour_setting_union_quota` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`settingId` varchar(32) DEFAULT NULL COMMENT '所属疗休养配置ID',
`unionId` varchar(32) DEFAULT NULL COMMENT '分工会ID',
`unionName` varchar(100) DEFAULT NULL COMMENT '分工会名称',
`formalQuota` int DEFAULT 0 COMMENT '正式人员名额',
`backupQuota` int 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_thirty_teach_tour_setting_union_quota_setting` (`settingId`),
KEY `idx_thirty_teach_tour_setting_union_quota_union` (`unionId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='疗休养配置分工会名额分配';
@@ -0,0 +1,4 @@
-- 疗休养人员分配新增退出状态。
ALTER TABLE `thirty_teach_tour_user_assignment`
ADD COLUMN `cancelled` tinyint(1) DEFAULT 0 COMMENT '是否已退出' AFTER `personType`,
ADD KEY `idx_thirty_teach_tour_user_assignment_cancelled` (`cancelled`);
@@ -0,0 +1,4 @@
-- Add mobile H5 signup confirmation fields to tour user assignment.
ALTER TABLE `thirty_teach_tour_user_assignment`
ADD COLUMN `age` int DEFAULT NULL COMMENT '年龄' AFTER `gender`,
ADD COLUMN `idCard` varchar(30) DEFAULT NULL COMMENT '身份证号' AFTER `age`;
@@ -0,0 +1,42 @@
-- 疗休养人员分配表。
CREATE TABLE IF NOT EXISTS `thirty_teach_tour_user_assignment` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`settingId` varchar(32) DEFAULT NULL COMMENT '疗休养配置ID',
`matterId` varchar(32) DEFAULT NULL COMMENT '疗休养事项ID',
`matterName` varchar(100) DEFAULT NULL COMMENT '疗休养事项名称',
`travelAgencyId` varchar(32) DEFAULT NULL COMMENT '旅行社ID',
`travelAgencyName` varchar(100) DEFAULT NULL COMMENT '旅行社名称',
`lineId` varchar(32) DEFAULT NULL COMMENT '线路ID',
`lineName` varchar(100) DEFAULT NULL COMMENT '线路名称',
`boardingPlace` varchar(100) DEFAULT NULL COMMENT '乘车地点',
`userId` varchar(32) DEFAULT NULL COMMENT '人员ID',
`loginName` varchar(50) DEFAULT NULL COMMENT '工号',
`userName` varchar(100) DEFAULT NULL COMMENT '姓名',
`gender` varchar(20) DEFAULT NULL COMMENT '性别',
`age` int DEFAULT NULL COMMENT '年龄',
`idCard` varchar(30) DEFAULT NULL COMMENT '身份证号',
`mobile` varchar(30) DEFAULT NULL COMMENT '手机号',
`unitId` varchar(32) DEFAULT NULL COMMENT '所在单位ID',
`unitName` varchar(100) DEFAULT NULL COMMENT '所在单位',
`unionId` varchar(32) DEFAULT NULL COMMENT '所在分工会ID',
`unionName` varchar(100) DEFAULT NULL COMMENT '所在分工会',
`assignSource` varchar(30) DEFAULT NULL COMMENT '分配来源',
`personType` varchar(30) DEFAULT NULL COMMENT '人员类型',
`cancelled` tinyint(1) DEFAULT 0 COMMENT '是否已退出',
`assignedBy` varchar(32) DEFAULT NULL COMMENT '分配人ID',
`assignedName` varchar(100) DEFAULT NULL COMMENT '分配人姓名',
`assignedAt` varchar(30) 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`),
UNIQUE KEY `uk_thirty_teach_tour_user_assignment_setting_user` (`settingId`, `userId`),
KEY `idx_thirty_teach_tour_user_assignment_setting` (`settingId`),
KEY `idx_thirty_teach_tour_user_assignment_user` (`userId`),
KEY `idx_thirty_teach_tour_user_assignment_union` (`unionId`),
KEY `idx_thirty_teach_tour_user_assignment_source` (`assignSource`),
KEY `idx_thirty_teach_tour_user_assignment_person_type` (`personType`),
KEY `idx_thirty_teach_tour_user_assignment_cancelled` (`cancelled`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='疗休养人员分配';
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

@@ -0,0 +1,824 @@
<!--#
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%"
@change="pageYearChange">
</el-date-picker>
</search-item>
<search-item label="疗休养配置">
<el-select v-model="pageForm.settingId" clearable filterable placeholder="请选择配置" style="width: 100%" @change="pageSettingChange">
<el-option v-for="item in settingOptions" :key="item.id" :label="item.configName" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="分配路线">
<el-select v-model="pageForm.matterId" clearable filterable placeholder="请选择分配路线" style="width: 100%">
<el-option v-for="item in pageMatterOptions" :key="item.matterId" :label="matterOptionLabel(item)" :value="item.matterId"></el-option>
</el-select>
</search-item>
<search-item label="人员类型">
<el-select v-model="pageForm.personType" clearable placeholder="请选择人员类型" style="width: 100%">
<el-option label="正式人员" value="FORMAL"></el-option>
<el-option label="替补人员" value="BACKUP"></el-option>
</el-select>
</search-item>
<search-item label="姓名/工号">
<el-input
v-model="pageForm.keyword"
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 v-if="$auth.hasPermission('thirtyTeachTour.branchUserAssignment.assign')" type="primary" size="medium" @click="openAssign">
<i class="el-icon-user"></i>
人员分配
</el-button>
<el-button type="primary" size="medium" icon="el-icon-download" @click="exportData">导出</el-button>
</table-tool>
<el-table
v-loading="tableLoading"
:data="tableData"
:size="tableSize"
border
class="vi-table"
@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="userName" width="110" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="工号" prop="loginName" width="120" 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="200" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="出行时间" prop="travelPeriod" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="乘车地点" prop="boardingPlace" min-width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="人员类型" prop="personType" width="110" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="row.personType === 'BACKUP' ? 'warning' : 'success'">{{ personTypeText(row.personType) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="是否退出" prop="cancelled" width="110" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="isCancelled(row) ? 'danger' : 'success'">{{ isCancelled(row) ? '已退出' : '未退出' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="分配时间" prop="assignedAt" width="170" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" width="380" align="center" header-align="center" fixed="right">
<template slot-scope="{row}">
<el-button v-if="!row.matterId && row.personType !== 'BACKUP' && $auth.hasPermission('thirtyTeachTour.branchUserAssignment.selectMatter')" size="mini" type="primary" @click="openSelectMatter(row)">选择路线</el-button>
<el-button v-if="$auth.hasPermission('thirtyTeachTour.branchUserAssignment.switchPersonType')" size="mini" type="primary" :loading="personTypeSwitching === row.id" @click="switchPersonType(row, row.personType === 'BACKUP' ? 'FORMAL' : 'BACKUP')">{{ row.personType === 'BACKUP' ? '转为正式' : '转为替补' }}</el-button>
<el-button v-if="isCancelled(row) && $auth.hasPermission('thirtyTeachTour.branchUserAssignment.cancelRestore')" size="mini" type="warning" @click="restoreCancel(row)">取消退出</el-button>
<el-button v-if="$auth.hasPermission('thirtyTeachTour.branchUserAssignment.delete')" size="mini" type="danger" @click="doDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</guava>
<el-dialog
custom-class="tour-user-assignment-dialog"
title="人员分配"
:visible.sync="assignDialogVisible"
:close-on-click-modal="false"
width="76%"
@closed="resetAssignDialog">
<el-form :model="assignForm" :rules="assignRules" ref="assignFormRef" label-width="110px">
<el-row :gutter="16">
<el-col :span="6">
<el-form-item label="年度" prop="year">
<el-date-picker
v-model="assignForm.year"
type="year"
value-format="yyyy"
placeholder="请选择年度"
style="width: 100%"
@change="assignYearChange">
</el-date-picker>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="疗休养配置" prop="settingId">
<el-select v-model="assignForm.settingId" clearable filterable placeholder="请选择配置" style="width: 100%" @change="assignSettingChange">
<el-option v-for="item in assignSettingOptions" :key="item.id" :label="item.configName" :value="item.id"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6" v-if="$auth.hasPermission('thirtyTeachTour.branchUserAssignment.selectMatter')">
<el-form-item label="分配路线" prop="matterId">
<el-select v-model="assignForm.matterId" clearable filterable placeholder="可选,选择后代报名" style="width: 100%" :disabled="assignForm.personType === 'BACKUP'">
<el-option v-for="item in assignMatterOptions" :key="item.matterId" :label="matterOptionLabel(item)" :value="item.matterId"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col>
<el-form-item label="人员类型" prop="personType">
<el-radio-group v-model="assignForm.personType" @change="assignPersonTypeChange">
<el-radio-button label="FORMAL">正式人员</el-radio-button>
<el-radio-button label="BACKUP">替补人员</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="quota-bar">
<div class="quota-item">
<span class="quota-label">正式名额</span>
<span>{{ quotaInfo.formalQuota }}</span>
<span class="quota-muted">已分配 {{ quotaInfo.formalUsed }},剩余 {{ quotaInfo.formalRemaining }}</span>
</div>
<div class="quota-item">
<span class="quota-label">替补名额</span>
<span>{{ quotaInfo.backupQuota }}</span>
<span class="quota-muted">已分配 {{ quotaInfo.backupUsed }},剩余 {{ quotaInfo.backupRemaining }}</span>
</div>
</div>
<div class="candidate-toolbar">
<el-select
v-model="selectedCandidateIds"
multiple
filterable
remote
clearable
collapse-tags
reserve-keyword
:remote-method="remoteCandidateSearch"
:loading="candidateSelectLoading"
placeholder="请选择姓名/工号"
style="width: 360px"
@change="candidateUserSelectChange">
<el-option v-for="item in candidateUserOptions" :key="item.userId" :label="candidateOptionLabel(item)" :value="item.userId"></el-option>
</el-select>
<el-button type="primary" icon="el-icon-search" @click="candidateSearch">查询</el-button>
<el-button @click="resetCandidateSearch">重置</el-button>
<span class="candidate-selected">已选 {{ selectedCandidates.length }} 人</span>
</div>
<el-table
ref="candidateTable"
v-loading="candidateLoading"
:data="candidateData"
row-key="userId"
border
size="mini"
height="420"
@selection-change="candidateSelectionChange">
<el-table-column type="selection" width="48" :reserve-selection="true" align="center" header-align="center"></el-table-column>
<el-table-column label="姓名" prop="userName" width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="工号" prop="loginName" width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="性别" prop="gender" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="手机号" prop="mobile" width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所属分工会" prop="unionName" min-width="160" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所在单位" prop="unitName" min-width="220" align="center" header-align="center" show-overflow-tooltip></el-table-column>
</el-table>
<el-row class="el-pagination-container candidate-pagination">
<el-pagination
background
:current-page="candidateForm.pageNumber"
:page-sizes="[10, 20, 50, 100]"
:page-size="candidateForm.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="candidateForm.totalCount"
@size-change="candidateSizeChange"
@current-change="candidatePageChange">
</el-pagination>
</el-row>
<span slot="footer" class="dialog-footer">
<el-button @click="assignDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="assignSubmitting" @click="doAssign">保存分配</el-button>
</span>
</el-dialog>
<el-dialog
title="选择分配路线"
:visible.sync="selectMatterDialogVisible"
:close-on-click-modal="false"
width="520px"
@closed="resetSelectMatterDialog">
<el-form :model="selectMatterForm" :rules="selectMatterRules" ref="selectMatterFormRef" label-width="110px">
<el-form-item label="分配人员">
<el-input v-model="selectMatterForm.userName" disabled></el-input>
</el-form-item>
<el-form-item label="分配路线" prop="matterId">
<el-select v-model="selectMatterForm.matterId" clearable filterable placeholder="请选择分配路线" style="width: 100%">
<el-option v-for="item in selectMatterOptions" :key="item.matterId" :label="matterOptionLabel(item)" :value="item.matterId"></el-option>
</el-select>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="selectMatterDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="selectMatterSubmitting" @click="doSelectMatter">保存</el-button>
</span>
</el-dialog>
</div>
<style>
#app .search .search-item {
width: calc((100% - 150px) / 4);
}
.tour-user-assignment-dialog .el-dialog__body {
max-height: 72vh;
overflow-y: auto;
}
.quota-bar {
display: flex;
gap: 12px;
margin-bottom: 12px;
}
.quota-item {
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 10px 12px;
min-width: 220px;
color: #303133;
background: #fafafa;
}
.quota-label {
font-weight: 600;
margin-right: 10px;
}
.quota-muted {
margin-left: 10px;
color: #909399;
}
.candidate-toolbar {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.candidate-selected {
color: #606266;
white-space: nowrap;
}
.candidate-pagination {
margin-top: 12px;
margin-bottom: 0;
text-align: right;
}
@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);
}
.quota-bar {
flex-direction: column;
}
}
@media screen and (max-width: 992px) {
#app .search .search-item {
width: 100%;
}
.candidate-selected {
margin-left: 0;
}
}
</style>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
const currentYear = moment().format("YYYY")
return {
settingOptions: [],
pageMatterOptions: [],
assignSettingOptions: [],
assignMatterOptions: [],
quotaInfo: {
formalQuota: 0,
backupQuota: 0,
formalUsed: 0,
backupUsed: 0,
formalRemaining: 0,
backupRemaining: 0
},
assignDialogVisible: false,
candidateLoading: false,
candidateSelectLoading: false,
candidateData: [],
candidateUserOptions: [],
selectedCandidates: [],
selectedCandidateIds: [],
assignSubmitting: false,
selectMatterDialogVisible: false,
selectMatterSubmitting: false,
selectMatterOptions: [],
personTypeSwitching: "",
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "assignedAt",
pageOrderBy: "descending",
year: currentYear,
settingId: "",
matterId: "",
personType: "",
keyword: ""
},
assignForm: {
year: currentYear,
settingId: "",
matterId: "",
personType: "FORMAL"
},
candidateForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
keyword: ""
},
selectMatterForm: {
id: "",
settingId: "",
matterId: "",
userName: ""
},
assignRules: {
year: [{required: true, message: "请选择年度", trigger: ["change", "blur"]}],
settingId: [{required: true, message: "请选择疗休养配置", trigger: ["change", "blur"]}],
personType: [{required: true, message: "请选择人员类型", trigger: ["change", "blur"]}]
},
selectMatterRules: {
matterId: [{required: true, message: "请选择分配路线", trigger: ["change", "blur"]}]
}
}
},
methods: {
defaultAssignForm(currentYear) {
return {
year: currentYear || moment().format("YYYY"),
settingId: "",
matterId: "",
personType: "FORMAL"
}
},
defaultCandidateForm() {
return {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
keyword: ""
}
},
defaultQuotaInfo() {
return {
formalQuota: 0,
backupQuota: 0,
formalUsed: 0,
backupUsed: 0,
formalRemaining: 0,
backupRemaining: 0
}
},
defaultSelectMatterForm() {
return {
id: "",
settingId: "",
matterId: "",
userName: ""
}
},
resetSearch() {
const currentYear = moment().format("YYYY")
this.pageForm.year = currentYear
this.pageForm.settingId = ""
this.pageForm.matterId = ""
this.pageForm.personType = ""
this.pageForm.keyword = ""
this.loadSettingOptions()
},
pageYearChange() {
this.pageForm.settingId = ""
this.pageForm.matterId = ""
this.pageMatterOptions = []
this.loadSettingOptions()
},
pageSettingChange() {
this.pageForm.matterId = ""
this.loadPageMatterOptions()
},
loadSettingOptions() {
this.$axios.post(loc() + "/settingOptions", {year: this.pageForm.year}).then((res) => {
if (res.code === 0) {
this.settingOptions = res.data || []
// 主页面默认选择当前年度第一条配置,避免进入页面后查询范围为空。
if (!this.pageForm.settingId && this.settingOptions.length > 0) {
this.pageForm.settingId = this.settingOptions[0].id
this.loadPageMatterOptions()
this.doSearch()
} else {
this.pageMatterOptions = []
this.doSearch()
}
}
})
},
loadPageMatterOptions() {
if (!this.pageForm.settingId) {
this.pageMatterOptions = []
return
}
this.$axios.post(loc() + "/matterOptions", {settingId: this.pageForm.settingId}).then((res) => {
if (res.code === 0) {
this.pageMatterOptions = res.data || []
}
})
},
exportData() {
const params = new URLSearchParams()
;["year", "settingId", "matterId", "personType", "keyword", "pageOrderName", "pageOrderBy"].forEach(key => {
const value = this.pageForm[key]
if (value !== undefined && value !== null && value !== "") {
params.append(key, value)
}
})
window.location.href = loc() + "/exportData?" + params.toString()
},
openAssign() {
const year = this.pageForm.year || moment().format("YYYY")
this.assignForm = this.defaultAssignForm(year)
this.candidateForm = this.defaultCandidateForm()
this.quotaInfo = this.defaultQuotaInfo()
this.candidateData = []
this.selectedCandidates = []
this.selectedCandidateIds = []
this.candidateUserOptions = []
this.assignDialogVisible = true
this.loadAssignSettingOptions()
},
resetAssignDialog() {
this.assignForm = this.defaultAssignForm(moment().format("YYYY"))
this.candidateForm = this.defaultCandidateForm()
this.assignMatterOptions = []
this.quotaInfo = this.defaultQuotaInfo()
this.candidateData = []
this.selectedCandidates = []
this.selectedCandidateIds = []
this.candidateUserOptions = []
this.assignSubmitting = false
},
assignYearChange() {
this.assignForm.settingId = ""
this.assignForm.matterId = ""
this.assignMatterOptions = []
this.quotaInfo = this.defaultQuotaInfo()
this.selectedCandidateIds = []
this.candidateUserOptions = []
this.candidateForm.keyword = ""
this.clearCandidateSelection()
this.loadAssignSettingOptions()
},
assignSettingChange() {
this.assignForm.matterId = ""
this.assignMatterOptions = []
this.selectedCandidateIds = []
this.candidateUserOptions = []
this.candidateForm.keyword = ""
this.clearCandidateSelection()
this.loadAssignMatterOptions()
this.loadQuotaInfo()
this.loadCandidatePageData()
},
assignPersonTypeChange() {
if (this.assignForm.personType === "BACKUP") {
this.assignForm.matterId = ""
}
},
loadAssignSettingOptions() {
this.$axios.post(loc() + "/settingOptions", {year: this.assignForm.year}).then((res) => {
if (res.code === 0) {
this.assignSettingOptions = res.data || []
// 人员分配弹窗独立默认取当前年度第一条配置,再加载名额、线路和候选人员。
if (!this.assignForm.settingId && this.assignSettingOptions.length > 0) {
this.assignForm.settingId = this.assignSettingOptions[0].id
this.loadAssignMatterOptions()
this.loadQuotaInfo()
this.loadCandidatePageData()
} else if (this.assignForm.settingId) {
this.loadAssignMatterOptions()
this.loadQuotaInfo()
this.loadCandidatePageData()
} else {
this.assignMatterOptions = []
this.quotaInfo = this.defaultQuotaInfo()
this.candidateData = []
this.candidateForm.totalCount = 0
}
}
})
},
loadAssignMatterOptions() {
if (!this.assignForm.settingId) {
this.assignMatterOptions = []
return
}
this.$axios.post(loc() + "/matterOptions", {settingId: this.assignForm.settingId}).then((res) => {
if (res.code === 0) {
this.assignMatterOptions = res.data || []
}
})
},
loadQuotaInfo() {
if (!this.assignForm.settingId) {
this.quotaInfo = this.defaultQuotaInfo()
return
}
this.$axios.post(loc() + "/quotaInfo", {settingId: this.assignForm.settingId}).then((res) => {
if (res.code === 0) {
this.quotaInfo = Object.assign(this.defaultQuotaInfo(), res.data || {})
}
})
},
loadCandidatePageData() {
if (!this.assignForm.settingId) {
this.candidateData = []
this.candidateForm.totalCount = 0
return
}
this.candidateLoading = true
this.$axios.post(loc() + "/candidatePageData", {
pageNumber: this.candidateForm.pageNumber,
pageSize: this.candidateForm.pageSize,
settingId: this.assignForm.settingId,
keyword: (this.selectedCandidateIds || []).length > 0 ? "" : this.candidateForm.keyword,
userIds: JSON.stringify(this.selectedCandidateIds || [])
}).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.candidateData = data.list || []
this.candidateForm.totalCount = data.totalCount || 0
this.mergeCandidateOptions(this.candidateData)
} else {
this.$message.warning(res.msg || "候选人员查询失败")
}
}).finally(() => {
this.candidateLoading = false
})
},
candidateSearch() {
this.candidateForm.pageNumber = 1
this.clearCandidateSelection()
this.loadCandidatePageData()
},
resetCandidateSearch() {
this.candidateForm.keyword = ""
this.candidateForm.pageNumber = 1
this.selectedCandidateIds = []
this.selectedCandidates = []
this.candidateUserOptions = []
this.clearCandidateSelection()
this.loadCandidatePageData()
},
candidateSizeChange(size) {
this.candidateForm.pageSize = size
this.candidateForm.pageNumber = 1
this.loadCandidatePageData()
},
candidatePageChange(pageNumber) {
this.candidateForm.pageNumber = pageNumber
this.loadCandidatePageData()
},
candidateSelectionChange(rows) {
this.selectedCandidates = rows || []
this.mergeCandidateOptions(this.selectedCandidates)
},
remoteCandidateSearch(keyword) {
if (!this.assignForm.settingId) {
this.candidateUserOptions = []
return
}
// 人员选择器复用候选人员接口,后端会限定为当前登录人所在分工会会员。
this.candidateForm.keyword = keyword || ""
this.candidateSelectLoading = true
this.$axios.post(loc() + "/candidatePageData", {
pageNumber: 1,
pageSize: 20,
settingId: this.assignForm.settingId,
keyword: keyword || ""
}).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.candidateUserOptions = this.mergeOptionLists(this.selectedCandidates, data.list || [])
}
}).finally(() => {
this.candidateSelectLoading = false
})
},
candidateUserSelectChange(userIds) {
// 人员选择器仅作为查询条件;最终分配人员仍由下方列表勾选决定。
this.selectedCandidateIds = userIds || []
},
candidateOptionLabel(item) {
if (!item) {
return ""
}
return (item.userName || "") + (item.loginName ? "" + item.loginName + "" : "")
},
mergeCandidateOptions(list) {
this.candidateUserOptions = this.mergeOptionLists(this.candidateUserOptions, list || [])
},
mergeOptionLists(first, second) {
const map = {}
;(first || []).concat(second || []).forEach(item => {
if (item && item.userId) {
map[item.userId] = Object.assign({}, map[item.userId] || {}, item)
}
})
return Object.keys(map).map(key => map[key])
},
clearCandidateSelection() {
this.selectedCandidates = []
if (this.$refs.candidateTable) {
this.$refs.candidateTable.clearSelection()
}
},
doAssign() {
this.$refs.assignFormRef.validate((valid) => {
if (!valid) return
if (this.selectedCandidates.length <= 0) {
this.$message.warning("请选择需要分配的人员")
return
}
this.$confirm("确定保存当前人员分配吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
const userIds = this.selectedCandidates.map(item => item.userId)
this.assignSubmitting = true
this.$axios.post(loc() + "/doAssign", {
settingId: this.assignForm.settingId,
matterId: this.assignForm.personType === "BACKUP" ? "" : this.assignForm.matterId,
personType: this.assignForm.personType,
userIds: JSON.stringify(userIds)
}).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.$message.success("分配成功" + (data.ledgerCount > 0 ? ",已代报名" + data.ledgerCount + "人" : "") + (data.skipCount > 0 ? ",已跳过" + data.skipCount + "人" : ""))
this.assignDialogVisible = false
this.doSearch()
} else {
this.$message.warning(res.msg || "分配失败")
}
}).finally(() => {
this.assignSubmitting = false
})
}).catch(() => {})
})
},
switchPersonType(row, targetType) {
this.personTypeSwitching = row.id
this.$axios.post(loc() + "/switchPersonType", {
id: row.id,
personType: targetType
}).then((res) => {
if (res.code === 0) {
this.$message.success("切换成功")
this.doSearch()
} else {
this.$message.warning(res.msg || "切换失败")
}
}).finally(() => {
this.personTypeSwitching = ""
})
},
openSelectMatter(row) {
this.selectMatterForm = {
id: row.id,
settingId: row.settingId,
matterId: "",
userName: row.userName || ""
}
this.selectMatterOptions = []
this.selectMatterDialogVisible = true
this.loadSelectMatterOptions()
},
resetSelectMatterDialog() {
this.selectMatterForm = this.defaultSelectMatterForm()
this.selectMatterOptions = []
this.selectMatterSubmitting = false
},
loadSelectMatterOptions() {
if (!this.selectMatterForm.settingId) {
this.selectMatterOptions = []
return
}
this.$axios.post(loc() + "/matterOptions", {settingId: this.selectMatterForm.settingId}).then((res) => {
if (res.code === 0) {
this.selectMatterOptions = res.data || []
}
})
},
doSelectMatter() {
this.$refs.selectMatterFormRef.validate((valid) => {
if (!valid) return
this.selectMatterSubmitting = true
this.$axios.post(loc() + "/selectMatter", {
id: this.selectMatterForm.id,
matterId: this.selectMatterForm.matterId
}).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.$message.success("分配路线选择成功" + (data.ledgerCount > 0 ? ",已代报名" + data.ledgerCount + "人" : ""))
this.selectMatterDialogVisible = false
this.doSearch()
} else {
this.$message.warning(res.msg || "分配路线选择失败")
}
}).finally(() => {
this.selectMatterSubmitting = false
})
})
},
doDelete(row) {
this.$axios.post(loc() + "/deleteInfo", {id: row.id}).then((infoRes) => {
if (infoRes.code !== 0) {
this.$message.warning(infoRes.msg || "删除校验失败")
return
}
const info = infoRes.data || {}
const hasLedger = !!info.hasLedger
const message = hasLedger ? "该人员已报名,删除分配记录将同时删除台账数据,是否继续?" : "确定删除该人员分配记录吗?"
this.$confirm(message, "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post(loc() + "/doDelete", {
id: row.id,
deleteLedger: hasLedger
}).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg || "删除成功")
this.doSearch()
} else {
this.$message.warning(res.msg || "删除失败")
}
})
}).catch(() => {})
})
},
personTypeText(personType) {
return personType === "BACKUP" ? "替补人员" : "正式人员"
},
isCancelled(row) {
return row && (row.cancelled === true || row.cancelled === 1)
},
restoreCancel(row) {
this.$confirm("确定将【" + row.userName + "】恢复为未退出状态吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post(loc() + "/restoreCancel", { id: row.id }).then((res) => {
if (res.code === 0) {
this.$message.success("取消退出成功")
this.doSearch()
} else {
this.$message.warning(res.msg || "取消退出失败")
}
})
}).catch(() => {})
},
// 分配路线选项按业务事项展示,线路名称仅作为历史数据缺失事项名称时的兜底。
matterOptionLabel(item) {
return item.matterName || item.lineName || ""
}
},
mounted() {
this.loadSettingOptions()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,374 @@
<!--#
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">
<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">
<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 type="expand" width="50">
<template slot-scope="{row}">
<el-table
:data="row.families || []"
border
size="mini"
class="tour-family-sub-table"
empty-text="暂无家属信息">
<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="age" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="性别" prop="gender" width="90" align="center" header-align="center"></el-table-column>
<el-table-column label="手机号" prop="mobile" width="130" align="center" header-align="center" show-overflow-tooltip></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="relationship" width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
</el-table>
</template>
</el-table-column>
<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;
}
.tour-family-sub-table {
margin: 8px 24px;
width: calc(100% - 48px);
}
.el-table .direct-family-row > td,
.el-table .direct-family-row > td .cell {
color: #f56c6c;
}
</style>
<!--#
}
#-->
@@ -0,0 +1,192 @@
<!--#
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-input
v-model="pageForm.keyword"
clearable
placeholder="请输入姓名或工号"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="所属工会">
<el-input
v-model="pageForm.unionName"
clearable
placeholder="请输入所属工会"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="状态">
<el-select v-model="pageForm.status" clearable placeholder="请选择状态" style="width: 100%">
<el-option label="未取消" value="NORMAL"></el-option>
<el-option label="已取消" value="CANCELLED"></el-option>
</el-select>
</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="疗休养退出取消列表"></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="loginName" 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="unionName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="疗休养配置" prop="settingName" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="路线" prop="lineName" min-width="200" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="出行时间" prop="travelPeriod" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="乘车地点" prop="boardingPlace" min-width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="人员类型" prop="personType" width="110" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="small" :type="row.personType === 'BACKUP' ? 'warning' : 'success'">{{ personTypeText(row.personType) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" prop="cancelStatus" width="110" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="small" :type="isCancelled(row) ? 'danger' : 'success'">{{ isCancelled(row) ? '已取消' : '未取消' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="分配来源" prop="assignSource" width="120" align="center" header-align="center">
<template slot-scope="{row}">
{{ assignSourceText(row.assignSource) }}
</template>
</el-table-column>
<el-table-column label="分配时间" prop="assignedAt" width="170" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" width="170" align="center" header-align="center" fixed="right">
<template slot-scope="{row}">
<el-button v-if="!isCancelled(row)" size="mini" type="danger" @click="doCancel(row)">退出</el-button>
<el-button v-if="isCancelled(row) && permissionInfo.canRestore" size="mini" type="primary" @click="doRestore(row)">取消退出</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</guava>
</div>
<style>
#app .search .search-item {
width: calc((100% - 150px) / 4);
}
@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%;
}
}
</style>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
return {
permissionInfo: {
canRestore: false
},
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
keyword: "",
unionName: "",
status: "",
pageOrderName: "assignedAt",
pageOrderBy: "descending"
}
}
},
methods: {
resetSearch() {
this.pageForm.keyword = ""
this.pageForm.unionName = ""
this.pageForm.status = ""
this.doSearch()
},
loadPermissionInfo() {
this.$axios.post(loc() + "/permissionInfo").then((res) => {
if (res.code === 0) {
this.permissionInfo = Object.assign({ canRestore: false }, res.data || {})
}
})
},
isCancelled(row) {
return row && (row.cancelStatus === "CANCELLED" || row.cancelled === true || row.cancelled === 1)
},
personTypeText(personType) {
return personType === "BACKUP" ? "替补人员" : "正式人员"
},
assignSourceText(assignSource) {
if (assignSource === "SCHOOL_UNION") {
return "校工会分配"
}
if (assignSource === "BRANCH_UNION") {
return "分工会分配"
}
return assignSource || ""
},
doCancel(row) {
this.$confirm("确定取消【" + row.userName + "】的疗休养资格吗?取消后将同步删除该人员对应台账数据。", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post(loc() + "/doCancel", { id: row.id }).then((res) => {
if (res.code === 0) {
this.$message.success("取消成功")
this.pageData()
} else {
this.$message.warning(res.msg || "取消失败")
}
})
}).catch(() => {})
},
doRestore(row) {
this.$confirm("确定将【" + row.userName + "】恢复为未取消状态吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post(loc() + "/doRestore", { id: row.id }).then((res) => {
if (res.code === 0) {
this.$message.success("取消退出成功")
this.pageData()
} else {
this.$message.warning(res.msg || "取消退出失败")
}
})
}).catch(() => {})
}
},
mounted() {
this.loadPermissionInfo()
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,686 @@
<!--#
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="所属工会" v-if="canSelectUnion()">
<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">
<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="exportLedgerData">导出台账数据</el-button>
<el-button size="medium" type="primary" icon="el-icon-download" @click="exportUnionSignupZip">导出分工会报名压缩包</el-button>
</div>
<div v-if="false" 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="gender" width="90" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="年龄" prop="age" width="90" align="center" header-align="center" show-overflow-tooltip></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="mobile" width="130" 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="boardingPlace" min-width="140" 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/thirtyTeachTour/ledger/importParticipants"
template_url="/platform/thirtyTeachTour/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,
currentUnionId: "",
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 || []
},
canSelectUnion() {
return this.$auth.hasRoleOr(["SYSADMIN", "SCHOOL_UNION_ADMIN"])
},
isBranchUnionChairmanOnly() {
return !this.canSelectUnion() && this.$auth.hasRole("BRANCH_UNION_CHAIRMAN")
},
applyUnionScope() {
if (this.isBranchUnionChairmanOnly() && this.currentUnionId) {
this.pageForm.unionId = this.currentUnionId
}
},
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()
},
exportLedgerData() {
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)
}
})
window.location.href = loc() + "/exportLedgerData?" + 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.isBranchUnionChairmanOnly() ? this.currentUnionId : ""
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 || []
if (this.isBranchUnionChairmanOnly()) {
this.currentUnionId = this.unionOptions.length ? this.unionOptions[0].id : ""
this.applyUnionScope()
}
}
})
},
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.applyUnionScope()
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,757 @@
<!--#
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">
<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-form-item label="默认乘车地点" prop="defaultBoardingPlace">
<el-select v-model="batchForm.defaultBoardingPlace" clearable filterable placeholder="请选择默认乘车地点" style="width: 100%">
<el-option v-for="item in boardingPlaceOptions" :key="item" :label="item" :value="item"></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="30" 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 validateContactPhone = (rule, value, callback) => {
// 线路联系人电话允许填写座机、分机或其他联系说明,此处只校验必填。
if (!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: [],
boardingPlaceOptions: [],
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: validateContactPhone, 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: "",
defaultBoardingPlace: "",
contactName: "",
contactPhone: "",
minGroupPeople: null,
maxGroupPeople: null,
estimatedCost: ""
}
},
openBatchForm(row) {
this.currentMatter = row
this.loadLines(row.year)
this.loadBoardingPlaceOptions(row.settingId)
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.ensureBatchBoardingPlace()
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)
}
})
},
loadBoardingPlaceOptions(settingId) {
this.boardingPlaceOptions = []
if (!settingId) {
return
}
this.$axios.post(loc() + "/settingBoardingPlaces", { settingId: settingId }).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.boardingPlaceOptions = this.parseBoardingPlaceOptions(data.boardingPlace || "")
this.ensureBatchBoardingPlace()
}
})
},
parseBoardingPlaceOptions(value) {
if (!value) {
return []
}
try {
const list = JSON.parse(value)
if (!Array.isArray(list)) {
return []
}
return list.map((item) => {
if (typeof item === "string") {
return item
}
return item && item.name ? item.name : ""
}).filter((item) => item)
} catch (e) {
return String(value).split(",").map((item) => item.trim()).filter((item) => item)
}
},
ensureBatchBoardingPlace() {
if (!this.batchForm || !this.boardingPlaceOptions.length || this.batchForm.defaultBoardingPlace) {
return
}
this.$set(this.batchForm, "defaultBoardingPlace", this.boardingPlaceOptions[0])
},
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>
<!--#
}
#-->
@@ -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">
<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,546 @@
<!--#
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">
<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);
}
@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: [
"\n",
" <div>\n",
" <div class=\"process-title\">\n",
" 报名信息\n",
" <el-link type=\"primary\" @click=\"openChart\">点击查看流程图</el-link>\n",
" </div>\n",
" <el-descriptions :column=\"3\" border class=\"tour-approval-section\">\n",
" <el-descriptions-item label=\"工号\">{{ detail.jobNo || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"姓名\">{{ detail.userName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"性别\">{{ detail.gender || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"身份证号\">{{ detail.idCard || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"所在单位\">{{ detail.unitName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"所在工会\">{{ detail.unionName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"报名时间\">{{ detail.signupTime || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"报名线路\">{{ detail.lineName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"出行时段\">{{ detail.travelPeriod || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"线路类型\">{{ detail.lineType || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"报名酒店\">{{ detail.hotelName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"旅行社\">{{ detail.travelAgencyName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"是否携带家属\">\n",
" {{ familyText() }}\n",
" </el-descriptions-item>\n",
" <el-descriptions-item label=\"报销超出费用\">{{ detail.overCostReimbursed ? '是' : '否' }}</el-descriptions-item>\n",
" <el-descriptions-item v-if=\"fillBedInfo\" label=\"意向拼床人\">{{ detail.intendedRoommate || '' }}</el-descriptions-item>\n",
" <el-descriptions-item v-if=\"fillBedInfo\" label=\"床型\">{{ detail.bedType || '' }}</el-descriptions-item>\n",
" <el-descriptions-item v-if=\"fillBedInfo\" label=\"床位信息\">{{ detail.bedInfo || '' }}</el-descriptions-item>\n",
" </el-descriptions>\n",
"\n",
" <div v-if=\"isDirectFamilyLine()\" class=\"tour-approval-section\">\n",
" <div class=\"process-title\">直系亲属线路</div>\n",
" <el-descriptions :column=\"3\" border>\n",
" <el-descriptions-item label=\"亲属姓名\">{{ directRelative.relativeName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"所在单位\">{{ directRelative.unitName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"亲属关系\">{{ directRelative.relationshipName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"线路名称\">{{ directRelative.lineName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"出行开始日期\">{{ directRelative.travelStartTime || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"出行结束日期\">{{ directRelative.travelEndTime || '' }}</el-descriptions-item>\n",
" </el-descriptions>\n",
" </div>\n",
"\n",
" <div v-if=\"showFamilySection()\" class=\"tour-approval-section\">\n",
" <div class=\"process-title\">亲属信息</div>\n",
" <el-table :data=\"familyData\" border size=\"mini\" empty-text=\"暂无亲属信息\" class=\"tour-approval-table\">\n",
" <el-table-column label=\"序号\" type=\"index\" width=\"70\" align=\"center\" header-align=\"center\"></el-table-column>\n",
" <el-table-column label=\"家属姓名\" prop=\"familyName\" min-width=\"120\" align=\"center\" header-align=\"center\" show-overflow-tooltip></el-table-column>\n",
" <el-table-column label=\"性别\" prop=\"gender\" width=\"90\" align=\"center\" header-align=\"center\"></el-table-column>\n",
" <el-table-column label=\"身份证号码\" prop=\"idCard\" min-width=\"180\" align=\"center\" header-align=\"center\" show-overflow-tooltip></el-table-column>\n",
" <el-table-column label=\"年龄\" prop=\"age\" width=\"90\" align=\"center\" header-align=\"center\"></el-table-column>\n",
" <el-table-column label=\"关系\" prop=\"relationship\" width=\"110\" align=\"center\" header-align=\"center\" show-overflow-tooltip></el-table-column>\n",
" <el-table-column v-if=\"fillBedInfo\" label=\"床型\" prop=\"bedType\" width=\"110\" align=\"center\" header-align=\"center\" show-overflow-tooltip></el-table-column>\n",
" <el-table-column v-if=\"fillBedInfo\" label=\"床位\" prop=\"bedInfo\" min-width=\"120\" align=\"center\" header-align=\"center\" show-overflow-tooltip></el-table-column>\n",
" <el-table-column v-if=\"fillBedInfo\" label=\"意向拼床人\" prop=\"intendedRoommate\" min-width=\"130\" align=\"center\" header-align=\"center\" show-overflow-tooltip></el-table-column>\n",
" </el-table>\n",
" </div>\n",
"\n",
" <template v-for=\"task in doneTasks\">\n",
" <div class=\"mt10\">\n",
" <div class=\"process-title\">{{ task.displayName }}</div>\n",
" <el-descriptions border class=\"flow-task-form\" :column=\"3\" :key=\"task.id\" v-if=\"task.ext.isFirstTaskNode\">\n",
" <el-descriptions-item label=\"申请用户\">\n",
" {{ task.ext.initiatorName }}({{ task.ext.initiatorAccount }})\n",
" </el-descriptions-item>\n",
" <el-descriptions-item label=\"申请时间\">{{ task.finishTime }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"办理结果\">\n",
" <dict-tag :options=\"dict.type.PROCESS_TASK_SUBMIT_TYPE\" :value=\"task.ext.submitType\"></dict-tag>\n",
" </el-descriptions-item>\n",
" </el-descriptions>\n",
"\n",
" <el-descriptions border class=\"flow-task-form\" :column=\"3\" :key=\"task.id\" v-else>\n",
" <el-descriptions-item label=\"办理用户\">\n",
" {{ task.taskFormData.userName }}({{ task.taskFormData.loginName }})\n",
" </el-descriptions-item>\n",
" <el-descriptions-item label=\"办理时间\">{{ task.finishTime }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"办理结果\">\n",
" <dict-tag :options=\"dict.type.PROCESS_TASK_SUBMIT_TYPE\" :value=\"task.ext.submitType\"></dict-tag>\n",
" </el-descriptions-item>\n",
" <el-descriptions-item label=\"办理意见\" :span=\"3\">\n",
" {{ task.taskFormData.opinion || task.taskFormData.tf_opinion || '' }}\n",
" </el-descriptions-item>\n",
" </el-descriptions>\n",
" </div>\n",
" </template>\n",
"\n",
" <slot></slot>\n",
"\n",
" <snaker-chart ref=\"snakerChartRef\"></snaker-chart>\n",
" </div>\n",
" \n"
].join(""),
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,928 @@
<!--#
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%"
@change="pageYearChange">
</el-date-picker>
</search-item>
<search-item label="疗休养配置">
<el-select v-model="pageForm.settingId" clearable filterable placeholder="请选择配置" style="width: 100%" @change="pageSettingChange">
<el-option v-for="item in settingOptions" :key="item.id" :label="item.configName" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="分配线路">
<el-select v-model="pageForm.matterId" clearable filterable placeholder="请选择分配线路" style="width: 100%">
<el-option v-for="item in pageMatterOptions" :key="item.matterId" :label="matterOptionLabel(item)" :value="item.matterId"></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>
<search-item label="人员类型">
<el-select v-model="pageForm.personType" clearable placeholder="请选择人员类型" style="width: 100%">
<el-option label="正式人员" value="FORMAL"></el-option>
<el-option label="替补人员" value="BACKUP"></el-option>
</el-select>
</search-item>
<search-item label="分配类别">
<el-select v-model="pageForm.assignSource" clearable placeholder="请选择分配类别" style="width: 100%">
<el-option v-for="item in assignSourceOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select>
</search-item>
<search-item label="是否退出">
<el-select v-model="pageForm.cancelled" clearable placeholder="请选择是否退出" style="width: 100%">
<el-option label="已退出" value="true"></el-option>
<el-option label="未退出" value="false"></el-option>
</el-select>
</search-item>
<search-item label="姓名/工号">
<el-input
v-model="pageForm.keyword"
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="openAssign">
<i class="el-icon-user"></i>
人员分配
</el-button>
<el-button type="warning" size="medium" icon="el-icon-message" @click="openRemindDialog">发送提醒</el-button>
<el-button type="primary" size="medium" icon="el-icon-download" @click="exportData">导出</el-button>
</table-tool>
<el-table
v-loading="tableLoading"
:data="tableData"
:size="tableSize"
border
class="vi-table"
@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="userName" width="110" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="工号" prop="loginName" width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所属分工会" prop="unionName" min-width="160" 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="200" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="出行时间" prop="travelPeriod" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="乘车地点" prop="boardingPlace" min-width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="分配类别" prop="assignSource" width="120" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="row.assignSource === 'BRANCH_UNION' ? 'warning' : 'primary'">{{ assignSourceText(row.assignSource) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="人员类型" prop="personType" width="110" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="row.personType === 'BACKUP' ? 'warning' : 'success'">{{ personTypeText(row.personType) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="是否退出" prop="cancelled" width="110" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="isCancelled(row) ? 'danger' : 'success'">{{ isCancelled(row) ? '已退出' : '未退出' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="分配时间" prop="assignedAt" width="170" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" width="320" align="center" header-align="center" fixed="right">
<template slot-scope="{row}">
<el-button v-if="isSchoolUnionAssignment(row) && !row.matterId && row.personType !== 'BACKUP'" size="mini" type="primary" @click="openSelectMatter(row)">选择线路</el-button>
<el-button v-if="isSchoolUnionAssignment(row) && isCancelled(row) && $auth.hasPermission('thirtyTeachTour.schoolUserAssignment.cancelRestore')" size="mini" type="warning" @click="restoreCancel(row)">取消退出</el-button>
<el-button v-if="isSchoolUnionAssignment(row)" 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="发送提醒"
:visible.sync="remindDialogVisible"
:close-on-click-modal="false"
width="560px"
@closed="resetRemindDialog">
<el-form :model="remindForm" :rules="remindRules" ref="remindFormRef" label-width="90px">
<el-form-item label="提醒内容" prop="content">
<el-input
v-model="remindForm.content"
type="textarea"
:rows="5"
maxlength="500"
show-word-limit
placeholder="请输入需要发送给当前查询结果人员的提醒内容">
</el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="remindDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="remindSubmitting" @click="doSendReminder">发送</el-button>
</span>
</el-dialog>
<el-dialog
custom-class="tour-user-assignment-dialog"
title="人员分配"
:visible.sync="assignDialogVisible"
:close-on-click-modal="false"
width="78%"
@closed="resetAssignDialog">
<el-form :model="assignForm" :rules="assignRules" ref="assignFormRef" label-width="110px">
<el-row :gutter="16">
<el-col :span="6">
<el-form-item label="年度" prop="year">
<el-date-picker
v-model="assignForm.year"
type="year"
value-format="yyyy"
placeholder="请选择年度"
style="width: 100%"
@change="assignYearChange">
</el-date-picker>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="疗休养配置" prop="settingId">
<el-select v-model="assignForm.settingId" clearable filterable placeholder="请选择配置" style="width: 100%" @change="assignSettingChange">
<el-option v-for="item in assignSettingOptions" :key="item.id" :label="item.configName" :value="item.id"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="分配线路" prop="matterId">
<el-select
v-model="assignForm.matterId"
clearable
filterable
:disabled="selectedCandidates.length <= 0"
placeholder="请先勾选人员"
style="width: 100%"
@change="assignMatterChange">
<el-option v-for="item in assignMatterOptions" :key="item.matterId" :label="matterOptionLabel(item)" :value="item.matterId"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="candidate-toolbar">
<el-select v-model="candidateForm.unionId" clearable filterable placeholder="所属分工会" style="width: 220px" @change="candidateSearch">
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
<el-select
v-model="selectedCandidateIds"
multiple
filterable
remote
clearable
collapse-tags
reserve-keyword
:remote-method="remoteCandidateSearch"
:loading="candidateSelectLoading"
placeholder="请选择姓名/工号"
style="width: 360px"
@change="candidateUserSelectChange">
<el-option v-for="item in candidateUserOptions" :key="item.userId" :label="candidateOptionLabel(item)" :value="item.userId"></el-option>
</el-select>
<el-button type="primary" icon="el-icon-search" @click="candidateSearch">查询</el-button>
<el-button @click="resetCandidateSearch">重置</el-button>
<span class="candidate-selected">已选 {{ selectedCandidates.length }} 人</span>
</div>
<el-table
ref="candidateTable"
v-loading="candidateLoading"
:data="candidateData"
row-key="userId"
border
size="mini"
height="420"
@selection-change="candidateSelectionChange">
<el-table-column type="selection" width="48" :reserve-selection="true" align="center" header-align="center"></el-table-column>
<el-table-column label="姓名" prop="userName" width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="工号" prop="loginName" width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="性别" prop="gender" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="手机号" prop="mobile" width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所属分工会" prop="unionName" min-width="160" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所在单位" prop="unitName" min-width="220" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="分配线路" min-width="260" align="center" header-align="center">
<template slot-scope="{row}">
<el-select
v-model="row.assignmentMatterId"
clearable
filterable
:disabled="!isCandidateSelected(row)"
placeholder="请先勾选人员"
style="width: 100%">
<el-option v-for="item in assignMatterOptions" :key="item.matterId" :label="matterOptionLabel(item)" :value="item.matterId"></el-option>
</el-select>
</template>
</el-table-column>
</el-table>
<el-row class="el-pagination-container candidate-pagination">
<el-pagination
background
:current-page="candidateForm.pageNumber"
:page-sizes="[10, 20, 50, 100]"
:page-size="candidateForm.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="candidateForm.totalCount"
@size-change="candidateSizeChange"
@current-change="candidatePageChange">
</el-pagination>
</el-row>
<span slot="footer" class="dialog-footer">
<el-button @click="assignDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="assignSubmitting" @click="doAssign">保存分配</el-button>
</span>
</el-dialog>
<el-dialog
title="选择分配线路"
:visible.sync="selectMatterDialogVisible"
:close-on-click-modal="false"
width="520px"
@closed="resetSelectMatterDialog">
<el-form :model="selectMatterForm" :rules="selectMatterRules" ref="selectMatterFormRef" label-width="110px">
<el-form-item label="分配人员">
<el-input v-model="selectMatterForm.userName" disabled></el-input>
</el-form-item>
<el-form-item label="分配线路" prop="matterId">
<el-select v-model="selectMatterForm.matterId" clearable filterable placeholder="请选择分配线路" style="width: 100%">
<el-option v-for="item in selectMatterOptions" :key="item.matterId" :label="matterOptionLabel(item)" :value="item.matterId"></el-option>
</el-select>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="selectMatterDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="selectMatterSubmitting" @click="doSelectMatter">保存</el-button>
</span>
</el-dialog>
</div>
<style>
#app .search .search-item {
width: calc((100% - 150px) / 4);
}
.tour-user-assignment-dialog .el-dialog__body {
max-height: 72vh;
overflow-y: auto;
}
.candidate-toolbar {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.candidate-selected {
color: #606266;
white-space: nowrap;
}
.candidate-pagination {
margin-top: 12px;
margin-bottom: 0;
text-align: right;
}
@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%;
}
.candidate-selected {
margin-left: 0;
}
}
</style>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
const currentYear = moment().format("YYYY")
return {
settingOptions: [],
pageMatterOptions: [],
assignSourceOptions: [
{label: "校工会分配", value: "SCHOOL_UNION"},
{label: "分工会分配", value: "BRANCH_UNION"}
],
assignSettingOptions: [],
assignMatterOptions: [],
unionOptions: [],
assignDialogVisible: false,
remindDialogVisible: false,
remindSubmitting: false,
candidateLoading: false,
candidateSelectLoading: false,
candidateData: [],
candidateUserOptions: [],
selectedCandidates: [],
selectedCandidateIds: [],
assignSubmitting: false,
selectMatterDialogVisible: false,
selectMatterSubmitting: false,
selectMatterOptions: [],
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "assignedAt",
pageOrderBy: "descending",
year: currentYear,
settingId: "",
matterId: "",
unionId: "",
personType: "",
assignSource: "SCHOOL_UNION",
cancelled: "false",
keyword: ""
},
assignForm: {
year: currentYear,
settingId: "",
matterId: ""
},
candidateForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
unionId: "",
keyword: ""
},
selectMatterForm: {
id: "",
settingId: "",
matterId: "",
userName: ""
},
remindForm: {
content: ""
},
assignRules: {
year: [{required: true, message: "请选择年度", trigger: ["change", "blur"]}],
settingId: [{required: true, message: "请选择疗休养配置", trigger: ["change", "blur"]}]
},
selectMatterRules: {
matterId: [{required: true, message: "请选择分配线路", trigger: ["change", "blur"]}]
},
remindRules: {
content: [{required: true, message: "请输入提醒内容", trigger: ["blur"]}]
}
}
},
methods: {
defaultAssignForm(currentYear) {
return {
year: currentYear || moment().format("YYYY"),
settingId: "",
matterId: ""
}
},
defaultCandidateForm() {
return {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
unionId: "",
keyword: ""
}
},
defaultSelectMatterForm() {
return {
id: "",
settingId: "",
matterId: "",
userName: ""
}
},
defaultRemindForm() {
return {
content: ""
}
},
resetSearch() {
const currentYear = moment().format("YYYY")
this.pageForm.year = currentYear
this.pageForm.settingId = ""
this.pageForm.matterId = ""
this.pageForm.unionId = ""
this.pageForm.personType = ""
// 页面打开和重置后默认查看校工会分配,清空该筛选项时后端会查询全部来源。
this.pageForm.assignSource = "SCHOOL_UNION"
this.pageForm.cancelled = "false"
this.pageForm.keyword = ""
this.loadSettingOptions()
},
pageYearChange() {
this.pageForm.settingId = ""
this.pageForm.matterId = ""
this.pageMatterOptions = []
this.loadSettingOptions()
},
pageSettingChange() {
this.pageForm.matterId = ""
this.loadPageMatterOptions()
},
loadSettingOptions() {
this.$axios.post(loc() + "/settingOptions", {year: this.pageForm.year}).then((res) => {
if (res.code === 0) {
this.settingOptions = res.data || []
// 主页面查询条件默认选择当前年度第一条配置,并以该配置刷新列表。
if (!this.pageForm.settingId && this.settingOptions.length > 0) {
this.pageForm.settingId = this.settingOptions[0].id
this.loadPageMatterOptions()
this.doSearch()
} else {
this.pageMatterOptions = []
this.doSearch()
}
}
})
},
loadPageMatterOptions() {
if (!this.pageForm.settingId) {
this.pageMatterOptions = []
return
}
this.$axios.post(loc() + "/matterOptions", {settingId: this.pageForm.settingId}).then((res) => {
if (res.code === 0) {
this.pageMatterOptions = res.data || []
}
})
},
loadUnionOptions() {
this.$axios.post(loc() + "/unionOptions").then((res) => {
if (res.code === 0) {
this.unionOptions = res.data || []
}
})
},
pageData() {
const params = Object.assign({}, this.pageForm)
if (params.cancelled === "") {
delete params.cancelled
}
this.tableLoading = true
this.$axios.post(loc() + "/pageData", params).then((res) => {
if (res.code === 0) {
this.tableData = res.data.list
this.pageForm.totalCount = res.data.totalCount
}
}).finally(() => {
this.tableLoading = false
})
},
exportData() {
const params = new URLSearchParams()
;["year", "settingId", "matterId", "unionId", "personType", "assignSource", "cancelled", "keyword", "pageOrderName", "pageOrderBy"].forEach(key => {
const value = this.pageForm[key]
if (value !== undefined && value !== null && value !== "") {
params.append(key, value)
}
})
window.location.href = loc() + "/exportData?" + params.toString()
},
buildCurrentQueryParams() {
const params = {}
;["year", "settingId", "matterId", "unionId", "personType", "assignSource", "cancelled", "keyword", "pageOrderName", "pageOrderBy"].forEach(key => {
const value = this.pageForm[key]
if (value !== undefined && value !== null && value !== "") {
params[key] = value
}
})
return params
},
openRemindDialog() {
if (!this.pageForm.settingId) {
this.$message.warning("请先选择疗休养配置")
return
}
this.remindForm = this.defaultRemindForm()
this.remindDialogVisible = true
},
resetRemindDialog() {
this.remindForm = this.defaultRemindForm()
this.remindSubmitting = false
if (this.$refs.remindFormRef) {
this.$refs.remindFormRef.clearValidate()
}
},
doSendReminder() {
this.$refs.remindFormRef.validate((valid) => {
if (!valid) return
const params = this.buildCurrentQueryParams()
params.content = this.remindForm.content
this.$confirm("确定给当前查询结果中的人员发送提醒吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.remindSubmitting = true
this.$axios.post(loc() + "/sendReminder", params).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.$message.success("提醒发送成功,共发送" + (data.receiverCount || 0) + "人")
this.remindDialogVisible = false
} else {
this.$message.warning(res.msg || "提醒发送失败")
}
}).finally(() => {
this.remindSubmitting = false
})
}).catch(() => {})
})
},
openAssign() {
const year = this.pageForm.year || moment().format("YYYY")
this.assignForm = this.defaultAssignForm(year)
this.assignForm.matterId = ""
this.candidateForm = this.defaultCandidateForm()
this.candidateData = []
this.selectedCandidates = []
this.selectedCandidateIds = []
this.candidateUserOptions = []
this.assignDialogVisible = true
this.loadAssignSettingOptions()
},
resetAssignDialog() {
this.assignForm = this.defaultAssignForm(moment().format("YYYY"))
this.candidateForm = this.defaultCandidateForm()
this.assignMatterOptions = []
this.candidateData = []
this.selectedCandidates = []
this.selectedCandidateIds = []
this.candidateUserOptions = []
this.assignSubmitting = false
},
assignYearChange() {
this.assignForm.settingId = ""
this.assignForm.matterId = ""
this.assignMatterOptions = []
this.selectedCandidateIds = []
this.candidateUserOptions = []
this.candidateForm.keyword = ""
this.clearCandidateSelection()
this.loadAssignSettingOptions()
this.loadCandidatePageData()
},
assignSettingChange() {
this.assignForm.matterId = ""
this.assignMatterOptions = []
this.selectedCandidateIds = []
this.candidateUserOptions = []
this.candidateForm.keyword = ""
this.clearCandidateSelection()
this.loadAssignMatterOptions()
this.loadCandidatePageData()
},
loadAssignSettingOptions() {
this.$axios.post(loc() + "/settingOptions", {year: this.assignForm.year}).then((res) => {
if (res.code === 0) {
this.assignSettingOptions = res.data || []
// 人员分配弹窗独立于主列表筛选,默认取当前年度第一条可用配置。
if (!this.assignForm.settingId && this.assignSettingOptions.length > 0) {
this.assignForm.settingId = this.assignSettingOptions[0].id
this.loadAssignMatterOptions()
this.loadCandidatePageData()
} else if (this.assignForm.settingId) {
this.loadAssignMatterOptions()
this.loadCandidatePageData()
} else {
this.assignMatterOptions = []
this.candidateData = []
this.candidateForm.totalCount = 0
}
}
})
},
loadAssignMatterOptions() {
if (!this.assignForm.settingId) {
this.assignMatterOptions = []
return
}
this.$axios.post(loc() + "/matterOptions", {settingId: this.assignForm.settingId}).then((res) => {
if (res.code === 0) {
this.assignMatterOptions = res.data || []
}
})
},
loadCandidatePageData() {
if (!this.assignForm.settingId) {
this.candidateData = []
this.candidateForm.totalCount = 0
return
}
this.candidateLoading = true
this.$axios.post(loc() + "/candidatePageData", {
pageNumber: this.candidateForm.pageNumber,
pageSize: this.candidateForm.pageSize,
settingId: this.assignForm.settingId,
unionId: this.candidateForm.unionId,
keyword: (this.selectedCandidateIds || []).length > 0 ? "" : this.candidateForm.keyword,
userIds: JSON.stringify(this.selectedCandidateIds || [])
}).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.candidateData = (data.list || []).map(item => Object.assign({}, item, {
assignmentMatterId: ""
}))
this.candidateForm.totalCount = data.totalCount || 0
this.mergeCandidateOptions(this.candidateData)
} else {
this.$message.warning(res.msg || "候选人员查询失败")
}
}).finally(() => {
this.candidateLoading = false
})
},
assignMatterChange(value) {
;(this.selectedCandidates || []).forEach(row => {
this.$set(row, "assignmentMatterId", value || "")
})
},
candidateSearch() {
this.candidateForm.pageNumber = 1
this.clearCandidateSelection()
this.loadCandidatePageData()
},
resetCandidateSearch() {
this.candidateForm.unionId = ""
this.candidateForm.keyword = ""
this.candidateForm.pageNumber = 1
this.selectedCandidateIds = []
this.selectedCandidates = []
this.candidateUserOptions = []
this.clearCandidateSelection()
this.loadCandidatePageData()
},
candidateSizeChange(size) {
this.candidateForm.pageSize = size
this.candidateForm.pageNumber = 1
this.loadCandidatePageData()
},
candidatePageChange(pageNumber) {
this.candidateForm.pageNumber = pageNumber
this.loadCandidatePageData()
},
candidateSelectionChange(rows) {
const selectedIds = (rows || []).map(item => item.userId)
;(this.candidateData || []).forEach(row => {
if (!selectedIds.includes(row.userId)) {
this.$set(row, "assignmentMatterId", "")
} else if (!row.assignmentMatterId && this.assignForm.matterId) {
this.$set(row, "assignmentMatterId", this.assignForm.matterId)
}
})
this.selectedCandidates = rows || []
this.mergeCandidateOptions(this.selectedCandidates)
if (this.selectedCandidates.length <= 0) {
this.assignForm.matterId = ""
}
},
remoteCandidateSearch(keyword) {
if (!this.assignForm.settingId) {
this.candidateUserOptions = []
return
}
// 人员选择器复用候选人员接口,校工会可按分工会过滤,也可不选分工会查询全部会员。
this.candidateForm.keyword = keyword || ""
this.candidateSelectLoading = true
this.$axios.post(loc() + "/candidatePageData", {
pageNumber: 1,
pageSize: 20,
settingId: this.assignForm.settingId,
unionId: this.candidateForm.unionId,
keyword: keyword || ""
}).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.candidateUserOptions = this.mergeOptionLists(this.selectedCandidates, data.list || [])
}
}).finally(() => {
this.candidateSelectLoading = false
})
},
candidateUserSelectChange(userIds) {
// 人员选择器仅作为查询条件;最终分配人员仍由下方列表勾选决定。
this.selectedCandidateIds = userIds || []
},
candidateOptionLabel(item) {
if (!item) {
return ""
}
return (item.userName || "") + (item.loginName ? "" + item.loginName + "" : "")
},
mergeCandidateOptions(list) {
this.candidateUserOptions = this.mergeOptionLists(this.candidateUserOptions, list || [])
},
mergeOptionLists(first, second) {
const map = {}
;(first || []).concat(second || []).forEach(item => {
if (item && item.userId) {
map[item.userId] = Object.assign({}, map[item.userId] || {}, item)
}
})
return Object.keys(map).map(key => map[key])
},
isCandidateSelected(row) {
return !!row && this.selectedCandidates.some(item => item.userId === row.userId)
},
clearCandidateSelection() {
this.selectedCandidates = []
if (this.$refs.candidateTable) {
this.$refs.candidateTable.clearSelection()
}
},
doAssign() {
this.$refs.assignFormRef.validate((valid) => {
if (!valid) return
if (this.selectedCandidates.length <= 0) {
this.$message.warning("请选择需要分配的人员")
return
}
this.$confirm("确定保存当前人员分配吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
const assignItems = this.selectedCandidates.map(item => {
return {
userId: item.userId,
matterId: item.assignmentMatterId || this.assignForm.matterId || ""
}
})
this.assignSubmitting = true
this.$axios.post(loc() + "/doAssignItems", {
settingId: this.assignForm.settingId,
assignItems: JSON.stringify(assignItems)
}).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.$message.success("分配成功" + (data.ledgerCount > 0 ? ",已代报名" + data.ledgerCount + "人" : "") + (data.skipCount > 0 ? ",已跳过" + data.skipCount + "人" : ""))
this.assignDialogVisible = false
this.doSearch()
} else {
this.$message.warning(res.msg || "分配失败")
}
}).finally(() => {
this.assignSubmitting = false
})
}).catch(() => {})
})
},
openSelectMatter(row) {
this.selectMatterForm = {
id: row.id,
settingId: row.settingId,
matterId: "",
userName: row.userName || ""
}
this.selectMatterOptions = []
this.selectMatterDialogVisible = true
this.loadSelectMatterOptions()
},
resetSelectMatterDialog() {
this.selectMatterForm = this.defaultSelectMatterForm()
this.selectMatterOptions = []
this.selectMatterSubmitting = false
},
loadSelectMatterOptions() {
if (!this.selectMatterForm.settingId) {
this.selectMatterOptions = []
return
}
this.$axios.post(loc() + "/matterOptions", {settingId: this.selectMatterForm.settingId}).then((res) => {
if (res.code === 0) {
this.selectMatterOptions = res.data || []
}
})
},
doSelectMatter() {
this.$refs.selectMatterFormRef.validate((valid) => {
if (!valid) return
this.selectMatterSubmitting = true
this.$axios.post(loc() + "/selectMatter", {
id: this.selectMatterForm.id,
matterId: this.selectMatterForm.matterId
}).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.$message.success("分配线路选择成功" + (data.ledgerCount > 0 ? ",已代报名" + data.ledgerCount + "人" : ""))
this.selectMatterDialogVisible = false
this.doSearch()
} else {
this.$message.warning(res.msg || "分配线路选择失败")
}
}).finally(() => {
this.selectMatterSubmitting = false
})
})
},
doDelete(row) {
this.$axios.post(loc() + "/deleteInfo", {id: row.id}).then((infoRes) => {
if (infoRes.code !== 0) {
this.$message.warning(infoRes.msg || "删除校验失败")
return
}
const info = infoRes.data || {}
const hasLedger = !!info.hasLedger
const message = hasLedger ? "该人员已报名,删除分配记录将同时删除台账数据,是否继续?" : "确定删除该人员分配记录吗?"
this.$confirm(message, "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post(loc() + "/doDelete", {
id: row.id,
deleteLedger: hasLedger
}).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg || "删除成功")
this.doSearch()
} else {
this.$message.warning(res.msg || "删除失败")
}
})
}).catch(() => {})
})
},
personTypeText(personType) {
return personType === "BACKUP" ? "替补人员" : "正式人员"
},
assignSourceText(assignSource) {
if (assignSource === "SCHOOL_UNION") {
return "校工会分配"
}
if (assignSource === "BRANCH_UNION") {
return "分工会分配"
}
return assignSource || ""
},
isSchoolUnionAssignment(row) {
return row && row.assignSource === "SCHOOL_UNION"
},
isCancelled(row) {
return row && (row.cancelled === true || row.cancelled === 1)
},
restoreCancel(row) {
this.$confirm("确定将【" + row.userName + "】恢复为未退出状态吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post(loc() + "/restoreCancel", { id: row.id }).then((res) => {
if (res.code === 0) {
this.$message.success("取消退出成功")
this.doSearch()
} else {
this.$message.warning(res.msg || "取消退出失败")
}
})
}).catch(() => {})
},
// 分配线路选项按业务事项展示,线路名称仅作为历史数据缺失事项名称时的兜底。
matterOptionLabel(item) {
return item.matterName || item.lineName || ""
}
},
mounted() {
this.loadSettingOptions()
this.loadUnionOptions()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,335 @@
<!--#
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">
<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) => {
if (!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, required: true, trigger: ["blur", "change"] }],
email: [{ validator: validateEmail, required: true, 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>
<!--#
}
#-->
@@ -0,0 +1,546 @@
<!--#
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">
<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);
}
@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: [
"\n",
" <div>\n",
" <div class=\"process-title\">\n",
" 报名信息\n",
" <el-link type=\"primary\" @click=\"openChart\">点击查看流程图</el-link>\n",
" </div>\n",
" <el-descriptions :column=\"3\" border class=\"tour-approval-section\">\n",
" <el-descriptions-item label=\"工号\">{{ detail.jobNo || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"姓名\">{{ detail.userName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"性别\">{{ detail.gender || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"身份证号\">{{ detail.idCard || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"所在单位\">{{ detail.unitName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"所在工会\">{{ detail.unionName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"报名时间\">{{ detail.signupTime || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"报名线路\">{{ detail.lineName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"出行时段\">{{ detail.travelPeriod || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"线路类型\">{{ detail.lineType || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"报名酒店\">{{ detail.hotelName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"旅行社\">{{ detail.travelAgencyName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"是否携带家属\">\n",
" {{ familyText() }}\n",
" </el-descriptions-item>\n",
" <el-descriptions-item label=\"报销超出费用\">{{ detail.overCostReimbursed ? '是' : '否' }}</el-descriptions-item>\n",
" <el-descriptions-item v-if=\"fillBedInfo\" label=\"意向拼床人\">{{ detail.intendedRoommate || '' }}</el-descriptions-item>\n",
" <el-descriptions-item v-if=\"fillBedInfo\" label=\"床型\">{{ detail.bedType || '' }}</el-descriptions-item>\n",
" <el-descriptions-item v-if=\"fillBedInfo\" label=\"床位信息\">{{ detail.bedInfo || '' }}</el-descriptions-item>\n",
" </el-descriptions>\n",
"\n",
" <div v-if=\"isDirectFamilyLine()\" class=\"tour-approval-section\">\n",
" <div class=\"process-title\">直系亲属线路</div>\n",
" <el-descriptions :column=\"3\" border>\n",
" <el-descriptions-item label=\"亲属姓名\">{{ directRelative.relativeName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"所在单位\">{{ directRelative.unitName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"亲属关系\">{{ directRelative.relationshipName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"线路名称\">{{ directRelative.lineName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"出行开始日期\">{{ directRelative.travelStartTime || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"出行结束日期\">{{ directRelative.travelEndTime || '' }}</el-descriptions-item>\n",
" </el-descriptions>\n",
" </div>\n",
"\n",
" <div v-if=\"showFamilySection()\" class=\"tour-approval-section\">\n",
" <div class=\"process-title\">亲属信息</div>\n",
" <el-table :data=\"familyData\" border size=\"mini\" empty-text=\"暂无亲属信息\" class=\"tour-approval-table\">\n",
" <el-table-column label=\"序号\" type=\"index\" width=\"70\" align=\"center\" header-align=\"center\"></el-table-column>\n",
" <el-table-column label=\"家属姓名\" prop=\"familyName\" min-width=\"120\" align=\"center\" header-align=\"center\" show-overflow-tooltip></el-table-column>\n",
" <el-table-column label=\"性别\" prop=\"gender\" width=\"90\" align=\"center\" header-align=\"center\"></el-table-column>\n",
" <el-table-column label=\"身份证号码\" prop=\"idCard\" min-width=\"180\" align=\"center\" header-align=\"center\" show-overflow-tooltip></el-table-column>\n",
" <el-table-column label=\"年龄\" prop=\"age\" width=\"90\" align=\"center\" header-align=\"center\"></el-table-column>\n",
" <el-table-column label=\"关系\" prop=\"relationship\" width=\"110\" align=\"center\" header-align=\"center\" show-overflow-tooltip></el-table-column>\n",
" <el-table-column v-if=\"fillBedInfo\" label=\"床型\" prop=\"bedType\" width=\"110\" align=\"center\" header-align=\"center\" show-overflow-tooltip></el-table-column>\n",
" <el-table-column v-if=\"fillBedInfo\" label=\"床位\" prop=\"bedInfo\" min-width=\"120\" align=\"center\" header-align=\"center\" show-overflow-tooltip></el-table-column>\n",
" <el-table-column v-if=\"fillBedInfo\" label=\"意向拼床人\" prop=\"intendedRoommate\" min-width=\"130\" align=\"center\" header-align=\"center\" show-overflow-tooltip></el-table-column>\n",
" </el-table>\n",
" </div>\n",
"\n",
" <template v-for=\"task in doneTasks\">\n",
" <div class=\"mt10\">\n",
" <div class=\"process-title\">{{ task.displayName }}</div>\n",
" <el-descriptions border class=\"flow-task-form\" :column=\"3\" :key=\"task.id\" v-if=\"task.ext.isFirstTaskNode\">\n",
" <el-descriptions-item label=\"申请用户\">\n",
" {{ task.ext.initiatorName }}({{ task.ext.initiatorAccount }})\n",
" </el-descriptions-item>\n",
" <el-descriptions-item label=\"申请时间\">{{ task.finishTime }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"办理结果\">\n",
" <dict-tag :options=\"dict.type.PROCESS_TASK_SUBMIT_TYPE\" :value=\"task.ext.submitType\"></dict-tag>\n",
" </el-descriptions-item>\n",
" </el-descriptions>\n",
"\n",
" <el-descriptions border class=\"flow-task-form\" :column=\"3\" :key=\"task.id\" v-else>\n",
" <el-descriptions-item label=\"办理用户\">\n",
" {{ task.taskFormData.userName }}({{ task.taskFormData.loginName }})\n",
" </el-descriptions-item>\n",
" <el-descriptions-item label=\"办理时间\">{{ task.finishTime }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"办理结果\">\n",
" <dict-tag :options=\"dict.type.PROCESS_TASK_SUBMIT_TYPE\" :value=\"task.ext.submitType\"></dict-tag>\n",
" </el-descriptions-item>\n",
" <el-descriptions-item label=\"办理意见\" :span=\"3\">\n",
" {{ task.taskFormData.opinion || task.taskFormData.tf_opinion || '' }}\n",
" </el-descriptions-item>\n",
" </el-descriptions>\n",
" </div>\n",
" </template>\n",
"\n",
" <slot></slot>\n",
"\n",
" <snaker-chart ref=\"snakerChartRef\"></snaker-chart>\n",
" </div>\n",
" \n"
].join(""),
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>
<!--#
}
#-->

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