This commit is contained in:
2026-06-17 17:31:34 +08:00
parent ae027378f7
commit 72cdae177e
26 changed files with 1676 additions and 366 deletions
@@ -309,9 +309,9 @@ public class SysUserServiceImpl extends BaseServiceImpl<Sys_user> implements Sys
String decodePwd = Base64Decoder.decodeStr(passowrd); String decodePwd = Base64Decoder.decodeStr(passowrd);
String hashedPassword = PwdUtil.getPassword(decodePwd, user.getSalt()); String hashedPassword = PwdUtil.getPassword(decodePwd, user.getSalt());
if (!Strings.sNull(hashedPassword).equalsIgnoreCase(user.getPassword())) { if (!Strings.sNull(hashedPassword).equalsIgnoreCase(user.getPassword())) {
if (Globals.sso) { // if (Globals.sso) {
throw new BaseException("用户名或者密码不正确"); throw new BaseException("用户名或者密码不正确");
} // }
} }
user = this.fetchLinks(user, "unit"); user = this.fetchLinks(user, "unit");
if (Lang.isNotEmpty(user.getUnit()) && StrUtil.isNotBlank(user.getUnit().getUnionId())) { if (Lang.isNotEmpty(user.getUnit()) && StrUtil.isNotBlank(user.getUnit().getUnionId())) {
@@ -115,7 +115,7 @@ public class ActivityBasicScopeController {
"""); """);
try { try {
Cnd cnd = getCnd(pageForm); Cnd cnd = getCnd(pageForm);
cnd.and("u.member", "=", 1); // cnd.and("u.member", "=", 1);
sql.setCondition(cnd); sql.setCondition(cnd);
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination); return Result.success(pagination);
@@ -393,7 +393,7 @@ public class ActivityBasicScopeController {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
cnd.and("u.member", "=", 1); // cnd.and("u.member", "=", 1);
return cnd; return cnd;
} }
@@ -169,16 +169,53 @@ public class TourGroupController {
return Result.success(sql.getList(NutMap.class)); return Result.success(sql.getList(NutMap.class));
} }
/**
* 查询当前事项线路下已经写入疗休养台账的成团信息,供线路成团查看页展示团标签。
*/
@At @At
@SaCheckPermission("tour.group") @SaCheckPermission("tour.group")
public Result signupPageData(PageForm pageForm, String matterId, String keyword, String unionId) { public Result groupOptions(String matterId) {
if (StrUtil.isBlank(matterId)) {
return Result.error("参数错误");
}
Cnd cnd = buildGroupOptionCnd(matterId);
return Result.success(tourLedgerService.queryGroupOptions(cnd));
}
/**
* 自动将当前事项线路下未成团的报名人员按最大成团人数分配成团。
*/
@At
@SaCheckPermission("tour.group")
public Result assignGroup(String matterId) {
if (StrUtil.isBlank(matterId)) {
return Result.error("参数错误");
}
return tourLedgerService.assignGroup(matterId);
}
/**
* 将勾选的教职工报名台账调整到指定成团中,家属随教职工一起调整。
*/
@At
@SaCheckPermission("tour.group")
public Result adjustGroup(String matterId, String groupCode, String ledgerIds) {
if (StrUtil.isBlank(matterId) || StrUtil.isBlank(groupCode) || StrUtil.isBlank(ledgerIds)) {
return Result.error("参数错误");
}
return tourLedgerService.adjustGroup(matterId, groupCode, ledgerIds);
}
@At
@SaCheckPermission("tour.group")
public Result signupPageData(PageForm pageForm, String matterId, String keyword, String unionId, String groupCode) {
if (StrUtil.isBlank(matterId)) { if (StrUtil.isBlank(matterId)) {
return Result.error("参数错误"); return Result.error("参数错误");
} }
if (StrUtil.isBlank(pageForm.getPageOrderName())) { if (StrUtil.isBlank(pageForm.getPageOrderName())) {
pageForm.defaultSortDesc("signupTime"); pageForm.defaultSortAsc("groupCode");
} }
Cnd cnd = buildSignupQueryCnd(matterId, keyword, unionId); Cnd cnd = buildSignupQueryCnd(matterId, keyword, unionId, groupCode);
Sql countSql = Sqls.create(""" Sql countSql = Sqls.create("""
SELECT COUNT(1) SELECT COUNT(1)
FROM tour_ledger t FROM tour_ledger t
@@ -196,16 +233,27 @@ public class TourGroupController {
t.id, t.id,
t.jobNo, t.jobNo,
t.userName, t.userName,
t.gender,
'' AS mobile, '' AS mobile,
t.idCard, t.idCard,
t.unionId, t.unionId,
t.unionName, t.unionName,
t.signupTime t.signupTime,
t.groupCode,
t.groupName,
IFNULL(f.familyCount, 0) AS familyCount,
1 + IFNULL(f.familyCount, 0) AS totalPeople
FROM tour_ledger t FROM tour_ledger t
INNER JOIN tour_matter m ON m.id = t.matterId INNER JOIN tour_matter m ON m.id = t.matterId
INNER JOIN tour_line l ON l.id = m.lineId INNER JOIN tour_line l ON l.id = m.lineId
LEFT JOIN (
SELECT ledgerId, COUNT(1) AS familyCount
FROM tour_ledger_family
WHERE delFlag = 0
GROUP BY ledgerId
) f ON f.ledgerId = t.id
$condition $condition
ORDER BY $orderColumn $orderBy, t.signupTime DESC, t.createdAt DESC ORDER BY $orderColumn $orderBy, t.signupTime ASC, t.createdAt ASC
"""); """);
listSql.setCondition(cnd); listSql.setCondition(cnd);
listSql.setVar("orderColumn", getSignupOrderColumn(pageForm.getPageOrderName())); listSql.setVar("orderColumn", getSignupOrderColumn(pageForm.getPageOrderName()));
@@ -216,11 +264,30 @@ public class TourGroupController {
List<NutMap> list = listSql.getList(NutMap.class); List<NutMap> list = listSql.getList(NutMap.class);
fillSignupMobile(list); fillSignupMobile(list);
fillSignupFamilies(list); flattenSignupFamilies(list);
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list); Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list);
return Result.success(pagination); return Result.success(pagination);
} }
/**
* 构造成团标签查询条件:只取当前事项线路已分配成团编码的台账,并保持与列表一致的数据权限。
*/
private Cnd buildGroupOptionCnd(String matterId) {
Cnd cnd = Cnd.NEW();
cnd.and("t.delFlag", "=", false);
cnd.and("t.matterId", "=", matterId);
cnd.and("t.groupCode", "IS NOT", null);
cnd.and("t.groupCode", "<>", "");
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);
applyMatterDataScope(cnd);
return cnd;
}
@At @At
@Ok("void") @Ok("void")
@SaCheckPermission("tour.group") @SaCheckPermission("tour.group")
@@ -238,7 +305,7 @@ public class TourGroupController {
CommonDownloadUtil.download(lineName + "参加人员名单.xls", workbook, response); CommonDownloadUtil.download(lineName + "参加人员名单.xls", workbook, response);
} }
private Cnd buildSignupQueryCnd(String matterId, String keyword, String unionId) { private Cnd buildSignupQueryCnd(String matterId, String keyword, String unionId, String groupCode) {
Cnd cnd = Cnd.NEW(); Cnd cnd = Cnd.NEW();
cnd.and("t.delFlag", "=", false); cnd.and("t.delFlag", "=", false);
cnd.and("t.matterId", "=", matterId); cnd.and("t.matterId", "=", matterId);
@@ -255,6 +322,8 @@ public class TourGroupController {
cnd.and(keywordGroup); cnd.and(keywordGroup);
} }
cnd.andEX("t.unionId", "=", unionId); cnd.andEX("t.unionId", "=", unionId);
// 团标签联动查询时只筛选当前团;未选择团标签时默认查询全部报名人员。
cnd.andEX("t.groupCode", "=", groupCode);
applyMatterDataScope(cnd); applyMatterDataScope(cnd);
return cnd; return cnd;
} }
@@ -297,7 +366,7 @@ public class TourGroupController {
} }
private List<NutMap> queryExportParticipants(String matterId) { private List<NutMap> queryExportParticipants(String matterId) {
Cnd cnd = buildSignupQueryCnd(matterId, null, null); Cnd cnd = buildSignupQueryCnd(matterId, null, null, null);
Sql sql = Sqls.create(""" Sql sql = Sqls.create("""
SELECT SELECT
t.id AS ledgerId, t.id AS ledgerId,
@@ -515,9 +584,9 @@ public class TourGroupController {
} }
/** /**
* 查看成团报名人员时按台账ID带出家属子列表,避免前端逐行请求家属信息。 * 查看成团报名人员时将教职工和家属平铺在同一个列表,便于前端统一展示。
*/ */
private void fillSignupFamilies(List<NutMap> list) { private void flattenSignupFamilies(List<NutMap> list) {
if (list == null || list.isEmpty()) { if (list == null || list.isEmpty()) {
return; return;
} }
@@ -535,8 +604,9 @@ public class TourGroupController {
Sql sql = Sqls.create(""" Sql sql = Sqls.create("""
SELECT SELECT
ledgerId, ledgerId,
staffJobNo AS jobNo, '' AS jobNo,
familyName AS userName, familyName AS userName,
gender,
'' AS mobile, '' AS mobile,
idCard, idCard,
relationship relationship
@@ -551,11 +621,35 @@ public class TourGroupController {
Map<String, List<NutMap>> familyMap = new HashMap<>(); Map<String, List<NutMap>> familyMap = new HashMap<>();
for (NutMap family : sql.getList(NutMap.class)) { for (NutMap family : sql.getList(NutMap.class)) {
family.put("staffRow", false);
family.put("selectable", false);
family.put("rowTypeName", "家属");
family.put("unionName", "");
family.put("groupCode", "");
family.put("groupName", "");
family.put("totalPeople", "");
familyMap.computeIfAbsent(family.getString("ledgerId", ""), key -> new ArrayList<>()).add(family); familyMap.computeIfAbsent(family.getString("ledgerId", ""), key -> new ArrayList<>()).add(family);
} }
List<NutMap> flatList = new ArrayList<>();
for (NutMap item : list) { for (NutMap item : list) {
item.put("families", familyMap.getOrDefault(item.getString("id", ""), List.of())); // 教职工是后续人员调整的最小可选单位,家属随教职工一起分配。
item.put("staffRow", true);
item.put("selectable", true);
item.put("rowTypeName", "教职工");
item.put("relationship", "");
item.put("relatedStaffName", item.getString("userName", ""));
flatList.add(item);
List<NutMap> families = familyMap.getOrDefault(item.getString("id", ""), List.of());
for (NutMap family : families) {
// 家属行通过关联教职工名称标明所属教职工,便于单层列表中识别家庭关系。
family.put("relatedStaffName", item.getString("userName", ""));
family.put("groupCode", item.getString("groupCode", ""));
family.put("groupName", item.getString("groupName", ""));
}
flatList.addAll(families);
} }
list.clear();
list.addAll(flatList);
} }
private Cnd buildQueryCnd(Integer year, String lineName, String lineType, String unionId) { private Cnd buildQueryCnd(Integer year, String lineName, String lineType, String unionId) {
@@ -662,12 +756,21 @@ public class TourGroupController {
if ("userName".equals(orderName)) { if ("userName".equals(orderName)) {
return "t.userName"; return "t.userName";
} }
if ("gender".equals(orderName)) {
return "t.gender";
}
if ("idCard".equals(orderName)) { if ("idCard".equals(orderName)) {
return "t.idCard"; return "t.idCard";
} }
if ("unionName".equals(orderName)) { if ("unionName".equals(orderName)) {
return "t.unionName"; return "t.unionName";
} }
if ("groupCode".equals(orderName) || "groupName".equals(orderName)) {
return "t.groupCode";
}
if ("totalPeople".equals(orderName)) {
return "totalPeople";
}
return "t.signupTime"; return "t.signupTime";
} }
} }
@@ -1,5 +1,9 @@
package com.budwk.app.zhgh.dayofficework.tour.controller; package com.budwk.app.zhgh.dayofficework.tour.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.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog; import com.budwk.app.base.annotation.SLog;
@@ -7,6 +11,7 @@ import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination; import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm; import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result; import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.sys.models.Sys_dict; import com.budwk.app.sys.models.Sys_dict;
import com.budwk.app.sys.services.SysDictService; import com.budwk.app.sys.services.SysDictService;
import com.budwk.app.sys.views.View_user; import com.budwk.app.sys.views.View_user;
@@ -26,7 +31,10 @@ import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap; import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Ok;
import org.apache.poi.ss.usermodel.Workbook;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -36,8 +44,6 @@ import java.util.stream.Collectors;
@At("/platform/tour/route") @At("/platform/tour/route")
public class TourLineController { public class TourLineController {
private static final String SORT_CODE_PATTERN = "^\\d+$";
@Inject @Inject
private TourLineService tourLineService; private TourLineService tourLineService;
@@ -52,7 +58,7 @@ public class TourLineController {
@At @At
@SaCheckPermission("tour.route") @SaCheckPermission("tour.route")
public Result pageData(PageForm pageForm, Integer year, String lineName, String lotId) { public Result pageData(PageForm pageForm, Integer year, String lineName, String lotId, String lineType) {
if (StrUtil.isBlank(pageForm.getPageOrderName())) { if (StrUtil.isBlank(pageForm.getPageOrderName())) {
pageForm.defaultSortAsc("lineCode"); pageForm.defaultSortAsc("lineCode");
} }
@@ -62,6 +68,8 @@ public class TourLineController {
cnd.andEX("l.`year`", "=", year); cnd.andEX("l.`year`", "=", year);
cnd.and(Cnd.likeEX("l.lineName", lineName)); cnd.and(Cnd.likeEX("l.lineName", lineName));
cnd.andEX("l.lotId", "=", lotId); cnd.andEX("l.lotId", "=", lotId);
// 线路类型查询使用线路表保存的字典名称,和新增/编辑线路时的取值保持一致。
cnd.andEX("l.lineType", "=", lineType);
boolean sysAdmin = AuthUtil.hasRole(RoleConstant.SYSADMIN.name()); boolean sysAdmin = AuthUtil.hasRole(RoleConstant.SYSADMIN.name());
boolean branchUnionScope = !sysAdmin && AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name()); boolean branchUnionScope = !sysAdmin && AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name());
applyDataScope(cnd, sysAdmin, branchUnionScope); applyDataScope(cnd, sysAdmin, branchUnionScope);
@@ -107,6 +115,78 @@ public class TourLineController {
return Result.success(pagination); return Result.success(pagination);
} }
/**
* 导出线路关联事项数据,导出范围复用线路管理列表的筛选条件和数据权限。
*/
@At
@Ok("void")
@SaCheckPermission("tour.route")
public void exportData(Integer year, String lineName, String lotId, String lineType, HttpServletResponse response) {
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);
// 导出范围复用列表筛选条件,线路类型也要同步过滤。
cnd.andEX("l.lineType", "=", lineType);
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 sql = Sqls.create("""
SELECT
l.`year`,
l.lineName,
lot.lotName,
l.lineType,
m.matterName,
CASE
WHEN IFNULL(m.signupStartTime, '') <> '' AND IFNULL(m.signupEndTime, '') <> ''
THEN CONCAT(m.signupStartTime, ' 至 ', m.signupEndTime)
ELSE ''
END AS signupTime,
CASE
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
ELSE ''
END AS travelTime,
m.contactName,
m.contactPhone
FROM tour_line l
LEFT JOIN tour_setting_lot lot ON lot.id = l.lotId
LEFT JOIN tour_matter m ON l.id = m.lineId AND m.delFlag = 0
$creatorJoin
$condition
ORDER BY CAST(l.lineCode AS UNSIGNED) ASC, l.lineCode ASC, m.matterName ASC, l.lineName ASC
""");
sql.setVar("creatorJoin", creatorJoin);
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.maps());
tourLineService.dao().execute(sql);
List<ExcelExportEntity> entities = new ArrayList<>();
entities.add(new ExcelExportEntity("创建年度", "year", 14));
entities.add(new ExcelExportEntity("线路名称", "lineName", 30));
entities.add(new ExcelExportEntity("时间标段", "lotName", 18));
entities.add(new ExcelExportEntity("路线类型", "lineType", 18));
entities.add(new ExcelExportEntity("事项名称", "matterName", 30));
entities.add(new ExcelExportEntity("报名时间", "signupTime", 34));
entities.add(new ExcelExportEntity("出行时间", "travelTime", 24));
entities.add(new ExcelExportEntity("联系人", "contactName", 16));
entities.add(new ExcelExportEntity("联系方式", "contactPhone", 18));
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, sql.getList(NutMap.class));
CommonDownloadUtil.download("线路事项数据.xlsx", workbook, response);
}
@At @At
@SaCheckPermission("tour.route") @SaCheckPermission("tour.route")
public Result detail(String id) { public Result detail(String id) {
@@ -253,11 +333,11 @@ public class TourLineController {
if (StrUtil.isBlank(line.getLineName())) { if (StrUtil.isBlank(line.getLineName())) {
return Result.error("线路名称不能为空"); return Result.error("线路名称不能为空");
} }
if (StrUtil.isBlank(line.getLineCode())) { if (line.getLineCode() == null) {
return Result.error("排序编号不能为空"); return Result.error("排序编号不能为空");
} }
if (!line.getLineCode().matches(SORT_CODE_PATTERN)) { if (line.getLineCode() < 0) {
return Result.error("排序编号必须为整数"); return Result.error("排序编号不能小于0");
} }
if (StrUtil.isBlank(line.getLineType())) { if (StrUtil.isBlank(line.getLineType())) {
return Result.error("线路类型不能为空"); return Result.error("线路类型不能为空");
@@ -1,12 +1,17 @@
package com.budwk.app.zhgh.dayofficework.tour.controller; package com.budwk.app.zhgh.dayofficework.tour.controller;
import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog; import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.constant.RoleConstant; import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination; import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm; import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result; import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.sys.models.Sys_dict; import com.budwk.app.sys.models.Sys_dict;
import com.budwk.app.sys.models.Sys_union; import com.budwk.app.sys.models.Sys_union;
import com.budwk.app.sys.services.SysDictService; import com.budwk.app.sys.services.SysDictService;
@@ -27,7 +32,10 @@ import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap; import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Ok;
import org.apache.poi.ss.usermodel.Workbook;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -53,18 +61,13 @@ public class TourMatterController {
@At @At
@SaCheckPermission("tour.matter") @SaCheckPermission("tour.matter")
public Result pageData(PageForm pageForm, Integer year, String matterName, String unionId, String organizationType) { public Result pageData(PageForm pageForm, Integer year, String matterName, String unionId, String organizationType, String lineType) {
Cnd cnd = Cnd.NEW(); Cnd cnd = buildMatterPageCnd(year, matterName, unionId, organizationType, lineType);
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(""" Sql countSql = Sqls.create("""
SELECT COUNT(1) SELECT COUNT(1)
FROM tour_matter m FROM tour_matter m
LEFT JOIN tour_line l ON l.id = m.lineId
$condition $condition
"""); """);
countSql.setCondition(cnd); countSql.setCondition(cnd);
@@ -91,7 +94,7 @@ public class TourMatterController {
LEFT JOIN sys_dict d ON d.`code` = m.organizationType LEFT JOIN sys_dict d ON d.`code` = m.organizationType
AND d.parentId = (SELECT id FROM sys_dict WHERE `code` = 'organizationType' LIMIT 1) AND d.parentId = (SELECT id FROM sys_dict WHERE `code` = 'organizationType' LIMIT 1)
$condition $condition
ORDER BY $orderColumn $orderBy, m.`year` DESC, m.createdAt DESC ORDER BY $orderColumn $orderBy, CAST(l.lineCode AS UNSIGNED) ASC, m.`year` DESC, m.createdAt DESC
"""); """);
listSql.setCondition(cnd); listSql.setCondition(cnd);
listSql.setVar("orderColumn", getOrderColumn(pageForm.getPageOrderName())); listSql.setVar("orderColumn", getOrderColumn(pageForm.getPageOrderName()));
@@ -104,6 +107,109 @@ public class TourMatterController {
return Result.success(pagination); return Result.success(pagination);
} }
/**
* 导出疗休养事项列表,导出范围复用页面查询条件和事项数据权限。
*/
@At
@Ok("void")
@SaCheckPermission("tour.matter")
public void exportData(Integer year, String matterName, String unionId, String organizationType, String lineType,
HttpServletResponse response) {
Cnd cnd = buildMatterPageCnd(year, matterName, unionId, organizationType, lineType);
Sql sql = Sqls.create("""
SELECT
m.`year`,
m.matterName,
l.lineName,
CASE
WHEN IFNULL(m.signupStartTime, '') <> '' AND IFNULL(m.signupEndTime, '') <> ''
THEN CONCAT(m.signupStartTime, ' 至 ', m.signupEndTime)
ELSE ''
END AS signupTime,
CASE
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
ELSE ''
END AS travelTime,
d.name AS organizationTypeName,
m.contactName,
m.contactPhone,
CASE WHEN m.enabled = 1 THEN '启用' ELSE '禁用' END AS enabledName
FROM tour_matter m
LEFT JOIN tour_line l ON l.id = m.lineId
LEFT JOIN sys_union u ON u.id = m.unionId
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 CAST(l.lineCode AS UNSIGNED) ASC, m.`year` DESC, m.createdAt DESC
""");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.maps());
tourMatterService.dao().execute(sql);
List<NutMap> rows = sql.getList(NutMap.class);
for (int i = 0; i < rows.size(); i++) {
rows.get(i).put("index", i + 1);
}
List<ExcelExportEntity> entities = new ArrayList<>();
entities.add(new ExcelExportEntity("序号", "index", 8));
entities.add(new ExcelExportEntity("年度", "year", 12));
entities.add(new ExcelExportEntity("事项名称", "matterName", 30));
entities.add(new ExcelExportEntity("线路名称", "lineName", 30));
entities.add(new ExcelExportEntity("报名时间", "signupTime", 34));
entities.add(new ExcelExportEntity("出行时间", "travelTime", 24));
entities.add(new ExcelExportEntity("组织形式", "organizationTypeName", 16));
entities.add(new ExcelExportEntity("联系人", "contactName", 16));
entities.add(new ExcelExportEntity("联系方式", "contactPhone", 18));
entities.add(new ExcelExportEntity("事项状态", "enabledName", 12));
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, rows);
CommonDownloadUtil.download("疗休养事项数据.xlsx", workbook, response);
}
/**
* 按当前事项列表查询条件统一赋值报名时间,更新前端筛选范围内的全部事项。
*/
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tour.matter")
@SLog(type = "tour", tag = "疗休养事项", msg = "统一赋值报名时间")
public Result assignSignupTime(Integer year, String matterName, String unionId, String organizationType, String lineType,
String signupStartTime, String signupEndTime) {
if (StrUtil.isBlank(signupStartTime) || StrUtil.isBlank(signupEndTime)) {
return Result.error("报名开始时间和报名结束时间不能为空");
}
if (signupStartTime.compareTo(signupEndTime) >= 0) {
return Result.error("报名开始时间必须小于报名结束时间");
}
// 批量赋值必须严格复用事项列表查询条件,确保“查询哪些数据就赋值哪些事项”。
Cnd cnd = buildMatterPageCnd(year, matterName, unionId, organizationType, lineType);
Sql idSql = Sqls.create("""
SELECT m.id
FROM tour_matter m
LEFT JOIN tour_line l ON l.id = m.lineId
$condition
""");
idSql.setCondition(cnd);
idSql.setCallback(Sqls.callback.maps());
tourMatterService.dao().execute(idSql);
List<String> ids = idSql.getList(NutMap.class).stream()
.map(item -> item.getString("id"))
.filter(StrUtil::isNotBlank)
.collect(Collectors.toList());
if (ids.isEmpty()) {
return Result.success("当前查询条件下暂无可赋值事项");
}
// 只更新报名时间字段,不影响事项的线路、联系人、成团人数等其他配置。
int updateCount = tourMatterService.assignSignupTime(ids, signupStartTime, signupEndTime);
return Result.success("赋值成功,共更新" + updateCount + "条事项");
}
@At @At
@SaCheckPermission("tour.matter") @SaCheckPermission("tour.matter")
public Result detail(String id) { public Result detail(String id) {
@@ -142,17 +248,20 @@ public class TourMatterController {
cnd.and("l.enabled", "=", true); cnd.and("l.enabled", "=", true);
cnd.and("l.openFlag", "=", true); cnd.and("l.openFlag", "=", true);
// 事项选择线路下拉需要展示线路类型和旅行社,因此这里补充旅行社名称返回给前端拼接显示。
Sql sql = Sqls.create(""" Sql sql = Sqls.create("""
SELECT SELECT
l.*, l.*,
a.agencyName AS travelAgencyName,
lot.lotName AS lotName, lot.lotName AS lotName,
lot.lotValue AS lotValue, lot.lotValue AS lotValue,
lot.lotValue AS lotDays, lot.lotValue AS lotDays,
lot.activityCost AS activityCost lot.activityCost AS activityCost
FROM tour_line l FROM tour_line l
LEFT JOIN tour_travel_agency a ON a.id = l.travelAgencyId
LEFT JOIN tour_setting_lot lot ON lot.id = l.lotId LEFT JOIN tour_setting_lot lot ON lot.id = l.lotId
$condition $condition
ORDER BY l.lineCode ASC, l.lineName ASC ORDER BY CAST(l.lineCode AS UNSIGNED) ASC, l.lineName ASC
"""); """);
sql.setCondition(cnd); sql.setCondition(cnd);
sql.setCallback(Sqls.callback.maps()); sql.setCallback(Sqls.callback.maps());
@@ -199,6 +308,17 @@ public class TourMatterController {
return Result.success(list.stream().filter(item -> !item.isDisabled()).collect(Collectors.toList())); return Result.success(list.stream().filter(item -> !item.isDisabled()).collect(Collectors.toList()));
} }
@At
@SaCheckPermission("tour.matter")
public Result lineTypeOptions() {
// 事项列表线路类型查询复用线路管理的 lineType 字典,保证筛选值与线路保存值一致。
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 @At
@Aop(TransAop.READ_COMMITTED) @Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tour.matter") @SaCheckPermission("tour.matter")
@@ -348,16 +468,14 @@ public class TourMatterController {
if (setting == null) { if (setting == null) {
return; return;
} }
if (setting.getMinGroupPeople() != null) { // 配置只作为弹窗缺省值,不能覆盖用户在选择线路页面已经提交的成团人数和报名时间。
if (matter.getMinGroupPeople() == null && setting.getMinGroupPeople() != null) {
matter.setMinGroupPeople(setting.getMinGroupPeople()); matter.setMinGroupPeople(setting.getMinGroupPeople());
} }
if (setting.getMaxGroupPeople() != null) { if (StrUtil.isBlank(matter.getSignupStartTime()) && StrUtil.isNotBlank(setting.getSignupStartTime())) {
matter.setMaxGroupPeople(setting.getMaxGroupPeople());
}
if (StrUtil.isNotBlank(setting.getSignupStartTime())) {
matter.setSignupStartTime(setting.getSignupStartTime()); matter.setSignupStartTime(setting.getSignupStartTime());
} }
if (StrUtil.isNotBlank(setting.getSignupEndTime())) { if (StrUtil.isBlank(matter.getSignupEndTime()) && StrUtil.isNotBlank(setting.getSignupEndTime())) {
matter.setSignupEndTime(setting.getSignupEndTime()); matter.setSignupEndTime(setting.getSignupEndTime());
} }
} }
@@ -429,12 +547,6 @@ public class TourMatterController {
if (matter.getMinGroupPeople() == null || matter.getMinGroupPeople() <= 0) { if (matter.getMinGroupPeople() == null || matter.getMinGroupPeople() <= 0) {
return Result.error("最少成团人数必须大于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) { if (matter.getEstimatedCost() == null || matter.getEstimatedCost().signum() < 0) {
return Result.error("预计费用不能小于0"); return Result.error("预计费用不能小于0");
} }
@@ -462,6 +574,22 @@ public class TourMatterController {
.and(TourLedger::getMatterId, "=", matterId)); .and(TourLedger::getMatterId, "=", matterId));
} }
/**
* 构建事项列表查询条件,批量赋值报名时间时复用该条件保证更新范围与列表查询一致。
*/
private Cnd buildMatterPageCnd(Integer year, String matterName, String unionId, String organizationType, String lineType) {
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);
// 事项列表和批量赋值报名时间共用该条件,按线路类型筛选时必须关联线路表字段。
cnd.andEX("l.lineType", "=", lineType);
applyMatterDataScope(cnd);
return cnd;
}
/** /**
* 事项数据范围:SYSADMIN 查看全部;分工会主席查看本工会事项;其它角色仅查看自己创建的事项。 * 事项数据范围:SYSADMIN 查看全部;分工会主席查看本工会事项;其它角色仅查看自己创建的事项。
*/ */
@@ -512,6 +640,10 @@ public class TourMatterController {
if ("lineName".equals(orderName)) { if ("lineName".equals(orderName)) {
return "l.lineName"; return "l.lineName";
} }
if ("lineCode".equals(orderName)) {
// 线路序号按数字排序,避免 10 排在 2 前面。
return "CAST(l.lineCode AS UNSIGNED)";
}
if ("settingName".equals(orderName)) { if ("settingName".equals(orderName)) {
return "s.configName"; return "s.configName";
} }
@@ -521,6 +653,6 @@ public class TourMatterController {
if ("enabled".equals(orderName)) { if ("enabled".equals(orderName)) {
return "m.enabled"; return "m.enabled";
} }
return "m.`year`"; return "CAST(l.lineCode AS UNSIGNED)";
} }
} }
@@ -444,10 +444,6 @@ public class TourMySignupController {
if (ruleResult != null) { if (ruleResult != null) {
return ruleResult; return ruleResult;
} }
Result maxPeopleResult = checkMaxGroupPeople(matter, oldLedger, familyList);
if (maxPeopleResult != null) {
return maxPeopleResult;
}
TourLine line = tourLedgerService.dao().fetch(TourLine.class, matter.getLineId()); TourLine line = tourLedgerService.dao().fetch(TourLine.class, matter.getLineId());
boolean directFamilyLine = line != null && Boolean.TRUE.equals(line.getDirectFamilyUnitLine()); boolean directFamilyLine = line != null && Boolean.TRUE.equals(line.getDirectFamilyUnitLine());
TourSetting setting = tourLedgerService.dao().fetch(TourSetting.class, matter.getSettingId()); TourSetting setting = tourLedgerService.dao().fetch(TourSetting.class, matter.getSettingId());
@@ -985,46 +981,6 @@ public class TourMySignupController {
&& idCard.matches("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dX]$"); && idCard.matches("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dX]$");
} }
private Result checkMaxGroupPeople(TourMatter matter, TourLedger oldLedger, List<TourLedgerFamily> familyList) {
if (matter == null || matter.getMaxGroupPeople() == null || matter.getMaxGroupPeople() <= 0) {
return null;
}
int currentPeople = 1 + (Lang.isEmpty(familyList) ? 0 : familyList.size());
int signedPeople = countSignupPeople(matter.getId(), oldLedger == null ? null : oldLedger.getId());
int totalPeople = signedPeople + currentPeople;
if (totalPeople > matter.getMaxGroupPeople()) {
return Result.error("当前报名人数 " + signedPeople + " 人,本次报名 " + currentPeople
+ " 人,最多成团人数 " + matter.getMaxGroupPeople() + " 人,报名后将超过人数上限,不能提交");
}
return null;
}
private int countSignupPeople(String matterId, String excludeLedgerId) {
if (StrUtil.isBlank(matterId)) {
return 0;
}
String excludeSql = StrUtil.isBlank(excludeLedgerId) ? "" : " AND t.id <> @excludeLedgerId";
Sql sql = Sqls.create("""
SELECT IFNULL(SUM(1 + IFNULL(f.familyCount, 0)), 0) AS signupPeople
FROM tour_ledger t
LEFT JOIN (
SELECT ledgerId, COUNT(1) AS familyCount
FROM tour_ledger_family
WHERE delFlag = 0
GROUP BY ledgerId
) f ON f.ledgerId = t.id
WHERE t.delFlag = 0
AND t.matterId = @matterId
""" + excludeSql);
sql.setParam("matterId", matterId);
if (StrUtil.isNotBlank(excludeLedgerId)) {
sql.setParam("excludeLedgerId", excludeLedgerId);
}
sql.setCallback(Sqls.callback.integer());
tourLedgerService.dao().execute(sql);
return sql.getInt();
}
private String buildSelectedLineMessage(TourLedger ledger) { private String buildSelectedLineMessage(TourLedger ledger) {
String selectedLineName = getLedgerLineName(ledger); String selectedLineName = getLedgerLineName(ledger);
return "您已经选择【" + selectedLineName + "】,取消后再重新选择。"; return "您已经选择【" + selectedLineName + "】,取消后再重新选择。";
@@ -205,10 +205,6 @@ public class TourSettingController {
if (StrUtil.isBlank(tourSetting.getConfigName())) { if (StrUtil.isBlank(tourSetting.getConfigName())) {
return Result.error("配置名称不能为空"); return Result.error("配置名称不能为空");
} }
if (tourSetting.getMinGroupPeople() != null && tourSetting.getMaxGroupPeople() != null
&& tourSetting.getMinGroupPeople() > tourSetting.getMaxGroupPeople()) {
return Result.error("最少成团人数不能大于最多成团人数");
}
if (StrUtil.isBlank(tourSetting.getSignupStartTime()) || StrUtil.isBlank(tourSetting.getSignupEndTime())) { if (StrUtil.isBlank(tourSetting.getSignupStartTime()) || StrUtil.isBlank(tourSetting.getSignupEndTime())) {
return Result.error("报名时间不能为空"); return Result.error("报名时间不能为空");
} }
@@ -136,7 +136,7 @@ public class TourSignupController {
@SaCheckPermission(value = {"tour.signup", "h5.tour.signup"}, mode = SaMode.OR) @SaCheckPermission(value = {"tour.signup", "h5.tour.signup"}, mode = SaMode.OR)
public Result h5PageData(PageForm pageForm, Integer year, String lineName, String lineType, String unionId, Boolean directFamilyUnitLine) { public Result h5PageData(PageForm pageForm, Integer year, String lineName, String lineType, String unionId, Boolean directFamilyUnitLine) {
if (StrUtil.isBlank(pageForm.getPageOrderName())) { if (StrUtil.isBlank(pageForm.getPageOrderName())) {
pageForm.defaultSortAsc("lineName"); pageForm.defaultSortAsc("lineCode");
} }
Cnd cnd = buildQueryCnd(year, lineName, lineType, unionId); Cnd cnd = buildQueryCnd(year, lineName, lineType, unionId);
cnd.andEX("l.directFamilyUnitLine", "=", directFamilyUnitLine); cnd.andEX("l.directFamilyUnitLine", "=", directFamilyUnitLine);
@@ -222,7 +222,7 @@ public class TourSignupController {
GROUP BY t.matterId, t.jobNo GROUP BY t.matterId, t.jobNo
) my ON my.matterId = m.id ) my ON my.matterId = m.id
$condition $condition
ORDER BY $orderColumn $orderBy, m.`year` DESC, m.travelStartTime ASC, l.lineName ASC, m.createdAt DESC ORDER BY $orderColumn $orderBy, CAST(l.lineCode AS UNSIGNED) ASC, m.`year` DESC, m.travelStartTime ASC, l.lineName ASC, m.createdAt DESC
"""); """);
listSql.setCondition(cnd); listSql.setCondition(cnd);
listSql.setParam("currentJobNo", currentJobNo); listSql.setParam("currentJobNo", currentJobNo);
@@ -241,7 +241,7 @@ public class TourSignupController {
@SaCheckPermission(value = {"tour.signup", "h5.tour.signup"}, mode = SaMode.OR) @SaCheckPermission(value = {"tour.signup", "h5.tour.signup"}, mode = SaMode.OR)
public Result pageData(PageForm pageForm, Integer year, String lineName, String lineType, String unionId) { public Result pageData(PageForm pageForm, Integer year, String lineName, String lineType, String unionId) {
if (StrUtil.isBlank(pageForm.getPageOrderName())) { if (StrUtil.isBlank(pageForm.getPageOrderName())) {
pageForm.defaultSortAsc("lineName"); pageForm.defaultSortAsc("lineCode");
} }
Cnd cnd = buildQueryCnd(year, lineName, lineType, unionId); Cnd cnd = buildQueryCnd(year, lineName, lineType, unionId);
String currentJobNo = currentJobNo(); String currentJobNo = currentJobNo();
@@ -325,7 +325,7 @@ public class TourSignupController {
GROUP BY t.matterId, t.jobNo GROUP BY t.matterId, t.jobNo
) my ON my.matterId = m.id ) my ON my.matterId = m.id
$condition $condition
ORDER BY $orderColumn $orderBy, m.`year` DESC, m.travelStartTime ASC, l.lineName ASC, m.createdAt DESC ORDER BY $orderColumn $orderBy, CAST(l.lineCode AS UNSIGNED) ASC, m.`year` DESC, m.travelStartTime ASC, l.lineName ASC, m.createdAt DESC
"""); """);
listSql.setCondition(cnd); listSql.setCondition(cnd);
listSql.setParam("currentJobNo", currentJobNo); listSql.setParam("currentJobNo", currentJobNo);
@@ -751,10 +751,6 @@ public class TourSignupController {
update = true; update = true;
} }
} }
Result maxPeopleResult = checkMaxGroupPeople(matter, oldLedger, familyList);
if (maxPeopleResult != null) {
return maxPeopleResult;
}
TourLine line = tourLedgerService.dao().fetch(TourLine.class, matter.getLineId()); TourLine line = tourLedgerService.dao().fetch(TourLine.class, matter.getLineId());
boolean directFamilyLine = line != null && Boolean.TRUE.equals(line.getDirectFamilyUnitLine()); boolean directFamilyLine = line != null && Boolean.TRUE.equals(line.getDirectFamilyUnitLine());
TourSetting setting = tourLedgerService.dao().fetch(TourSetting.class, matter.getSettingId()); TourSetting setting = tourLedgerService.dao().fetch(TourSetting.class, matter.getSettingId());
@@ -962,20 +958,6 @@ public class TourSignupController {
return null; return null;
} }
private Result checkMaxGroupPeople(TourMatter matter, TourLedger oldLedger, List<TourLedgerFamily> familyList) {
if (matter == null || matter.getMaxGroupPeople() == null || matter.getMaxGroupPeople() <= 0) {
return null;
}
int currentPeople = 1 + (Lang.isEmpty(familyList) ? 0 : familyList.size());
int signedPeople = countSignupPeople(matter.getId(), oldLedger == null ? null : oldLedger.getId());
int totalPeople = signedPeople + currentPeople;
if (totalPeople > matter.getMaxGroupPeople()) {
return Result.error("当前报名人数 " + signedPeople + " 人,本次报名 " + currentPeople
+ " 人,最多成团人数 " + matter.getMaxGroupPeople() + " 人,报名后将超过人数上限,不能提交");
}
return null;
}
private boolean isValidIdCard(String idCard) { private boolean isValidIdCard(String idCard) {
return StrUtil.isNotBlank(idCard) return StrUtil.isNotBlank(idCard)
&& idCard.matches("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dX]$"); && idCard.matches("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dX]$");
@@ -1252,32 +1234,6 @@ public class TourSignupController {
return sql.getInt(); return sql.getInt();
} }
private int countSignupPeople(String matterId, String excludeLedgerId) {
if (StrUtil.isBlank(matterId)) {
return 0;
}
String excludeSql = StrUtil.isBlank(excludeLedgerId) ? "" : " AND t.id <> @excludeLedgerId";
Sql sql = Sqls.create("""
SELECT IFNULL(SUM(1 + IFNULL(f.familyCount, 0)), 0) AS signupPeople
FROM tour_ledger t
LEFT JOIN (
SELECT ledgerId, COUNT(1) AS familyCount
FROM tour_ledger_family
WHERE delFlag = 0
GROUP BY ledgerId
) f ON f.ledgerId = t.id
WHERE t.delFlag = 0
AND t.matterId = @matterId
""" + excludeSql);
sql.setParam("matterId", matterId);
if (StrUtil.isNotBlank(excludeLedgerId)) {
sql.setParam("excludeLedgerId", excludeLedgerId);
}
sql.setCallback(Sqls.callback.integer());
tourLedgerService.dao().execute(sql);
return sql.getInt();
}
private String buildSelectedLineMessage(TourLedger ledger) { private String buildSelectedLineMessage(TourLedger ledger) {
String selectedLineName = getLedgerLineName(ledger); String selectedLineName = getLedgerLineName(ledger);
return "您已经选择【" + selectedLineName + "】,取消后再重新选择。"; return "您已经选择【" + selectedLineName + "】,取消后再重新选择。";
@@ -1537,6 +1493,10 @@ public class TourSignupController {
} }
private String getOrderColumn(String orderName) { private String getOrderColumn(String orderName) {
if ("lineCode".equals(orderName)) {
// 线路序号按数字排序,避免字符串排序导致 10 排在 2 前面。
return "CAST(l.lineCode AS UNSIGNED)";
}
if ("lineName".equals(orderName)) { if ("lineName".equals(orderName)) {
return "l.lineName"; return "l.lineName";
} }
@@ -1561,6 +1521,6 @@ public class TourSignupController {
if ("signupCount".equals(orderName)) { if ("signupCount".equals(orderName)) {
return "signupCount"; return "signupCount";
} }
return "l.lineName"; return "CAST(l.lineCode AS UNSIGNED)";
} }
} }
@@ -9,7 +9,6 @@ import com.budwk.app.base.result.Result;
import com.budwk.app.sys.models.Sys_union; 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.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil; import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.dayofficework.tour.models.TourMatter;
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService; import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService;
import com.budwk.app.zhgh.dayofficework.tour.service.TourMatterService; import com.budwk.app.zhgh.dayofficework.tour.service.TourMatterService;
import org.nutz.dao.Cnd; import org.nutz.dao.Cnd;
@@ -61,7 +60,17 @@ public class TourUndertakeController {
SELECT COUNT(1) SELECT COUNT(1)
FROM tour_matter m FROM tour_matter m
INNER JOIN tour_line l ON l.id = m.lineId INNER JOIN tour_line l ON l.id = m.lineId
LEFT JOIN sys_union u ON u.id = m.unionId INNER JOIN (
SELECT
t.matterId,
t.groupCode,
MAX(t.undertakeUnionId) AS undertakeUnionId
FROM tour_ledger t
WHERE t.delFlag = 0
AND t.groupCode IS NOT NULL
AND t.groupCode <> ''
GROUP BY t.matterId, t.groupCode
) gs ON gs.matterId = m.id
$condition $condition
"""); """);
countSql.setCondition(cnd); countSql.setCondition(cnd);
@@ -74,14 +83,14 @@ public class TourUndertakeController {
m.id AS matterId, m.id AS matterId,
m.`year`, m.`year`,
m.matterName, m.matterName,
m.unionId, gs.undertakeUnionId AS unionId,
COALESCE(NULLIF(gs.undertakeUnionName, ''), uu.name, '') AS unionName,
CASE CASE
WHEN IFNULL(NULLIF(m.undertakeStatus, ''), 'unclaimed') = 'claimed' THEN COALESCE(u.name, '校工会') WHEN IFNULL(NULLIF(gs.undertakeUnionId, ''), '') <> '' THEN 'claimed'
ELSE '' ELSE 'unclaimed'
END AS unionName, END AS undertakeStatus,
IFNULL(NULLIF(m.undertakeStatus, ''), 'unclaimed') AS undertakeStatus,
CASE CASE
WHEN IFNULL(NULLIF(m.undertakeStatus, ''), 'unclaimed') = 'claimed' THEN '已领' WHEN IFNULL(NULLIF(gs.undertakeUnionId, ''), '') <> '' THEN '已领'
ELSE '未领' ELSE '未领'
END AS undertakeStatusName, END AS undertakeStatusName,
m.lineId, m.lineId,
@@ -95,25 +104,20 @@ public class TourUndertakeController {
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime) THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
ELSE '' ELSE ''
END AS travelPeriod, END AS travelPeriod,
m.minGroupPeople, gs.groupCode,
m.maxGroupPeople, gs.groupName,
IFNULL(sc.signupCount, 0) AS signupCount, gs.signupCount,
IFNULL(usc.currentUnionSignupCount, 0) AS currentUnionSignupCount, IFNULL(ugs.currentUnionSignupCount, 0) AS currentUnionSignupCount
CASE
WHEN m.maxGroupPeople IS NOT NULL AND m.maxGroupPeople > 0 AND IFNULL(sc.signupCount, 0) > m.maxGroupPeople THEN 'over'
WHEN m.minGroupPeople IS NOT NULL AND m.minGroupPeople > 0 AND IFNULL(sc.signupCount, 0) >= m.minGroupPeople THEN 'formed'
ELSE 'unformed'
END AS groupStatus,
CASE
WHEN m.maxGroupPeople IS NOT NULL AND m.maxGroupPeople > 0 AND IFNULL(sc.signupCount, 0) > m.maxGroupPeople THEN '超员'
WHEN m.minGroupPeople IS NOT NULL AND m.minGroupPeople > 0 AND IFNULL(sc.signupCount, 0) >= m.minGroupPeople THEN '已成团'
ELSE '未成团'
END AS groupStatusName
FROM tour_matter m FROM tour_matter m
INNER JOIN tour_line l ON l.id = m.lineId INNER JOIN tour_line l ON l.id = m.lineId
LEFT JOIN sys_union u ON u.id = m.unionId INNER JOIN (
LEFT JOIN ( SELECT
SELECT t.matterId, SUM(1 + IFNULL(f.familyCount, 0)) AS signupCount t.matterId,
t.groupCode,
MAX(t.groupName) AS groupName,
MAX(t.undertakeUnionId) AS undertakeUnionId,
MAX(t.undertakeUnionName) AS undertakeUnionName,
SUM(1 + IFNULL(f.familyCount, 0)) AS signupCount
FROM tour_ledger t FROM tour_ledger t
LEFT JOIN ( LEFT JOIN (
SELECT ledgerId, COUNT(1) AS familyCount SELECT ledgerId, COUNT(1) AS familyCount
@@ -122,12 +126,16 @@ public class TourUndertakeController {
GROUP BY ledgerId GROUP BY ledgerId
) f ON f.ledgerId = t.id ) f ON f.ledgerId = t.id
WHERE t.delFlag = 0 WHERE t.delFlag = 0
AND t.matterId IS NOT NULL AND t.groupCode IS NOT NULL
AND t.matterId <> '' AND t.groupCode <> ''
GROUP BY t.matterId GROUP BY t.matterId, t.groupCode
) sc ON sc.matterId = m.id ) gs ON gs.matterId = m.id
LEFT JOIN sys_union uu ON uu.id = gs.undertakeUnionId
LEFT JOIN ( LEFT JOIN (
SELECT t.matterId, SUM(1 + IFNULL(f.familyCount, 0)) AS currentUnionSignupCount SELECT
t.matterId,
t.groupCode,
SUM(1 + IFNULL(f.familyCount, 0)) AS currentUnionSignupCount
FROM tour_ledger t FROM tour_ledger t
LEFT JOIN ( LEFT JOIN (
SELECT ledgerId, COUNT(1) AS familyCount SELECT ledgerId, COUNT(1) AS familyCount
@@ -136,13 +144,13 @@ public class TourUndertakeController {
GROUP BY ledgerId GROUP BY ledgerId
) f ON f.ledgerId = t.id ) f ON f.ledgerId = t.id
WHERE t.delFlag = 0 WHERE t.delFlag = 0
AND t.matterId IS NOT NULL AND t.groupCode IS NOT NULL
AND t.matterId <> '' AND t.groupCode <> ''
AND t.unionId = @currentUnionId AND t.unionId = @currentUnionId
GROUP BY t.matterId GROUP BY t.matterId, t.groupCode
) usc ON usc.matterId = m.id ) ugs ON ugs.matterId = m.id AND ugs.groupCode = gs.groupCode
$condition $condition
ORDER BY $orderColumn $orderBy, m.`year` DESC, m.travelStartTime ASC, l.lineName ASC, m.createdAt DESC ORDER BY $orderColumn $orderBy, m.`year` DESC, m.travelStartTime ASC, l.lineName ASC, gs.groupCode ASC, m.createdAt DESC
"""); """);
listSql.setCondition(cnd); listSql.setCondition(cnd);
listSql.setParam("currentUnionId", currentUnionId); listSql.setParam("currentUnionId", currentUnionId);
@@ -180,49 +188,35 @@ public class TourUndertakeController {
return Result.success(unionInfo); return Result.success(unionInfo);
} }
/**
* 领办当前成团行,接口必须同时接收事项ID和成团编码,避免按事项误领办全部团。
*/
@At @At
@SaCheckPermission("tour.undertake") @SaCheckPermission("tour.undertake")
public Result undertakeLine(String matterId) { public Result undertakeLine(String matterId, String groupCode) {
if (StrUtil.isBlank(matterId)) { if (StrUtil.isBlank(matterId) || StrUtil.isBlank(groupCode)) {
return Result.error("参数错误"); return Result.error("参数错误");
} }
NutMap unionInfo = currentUnionInfo(); NutMap unionInfo = currentUnionInfo();
if (unionInfo.isEmpty()) { if (unionInfo.isEmpty()) {
return Result.error("未获取到当前登录人工会"); return Result.error("未获取到当前登录人工会");
} }
TourMatter matter = tourMatterService.fetch(matterId); return tourLedgerService.undertakeGroup(matterId, groupCode, unionInfo.getString("unionId"), unionInfo.getString("unionName"));
if (matter == null || Boolean.TRUE.equals(matter.getDelFlag())) {
return Result.error("事项不存在");
}
if ("claimed".equals(matter.getUndertakeStatus())) {
return Result.error("当前线路已领办,不允许修改");
}
matter.setUnionId(unionInfo.getString("unionId"));
matter.setUndertakeStatus("claimed");
tourMatterService.update(matter);
return Result.success(unionInfo);
} }
/**
* 解除当前成团行的领办信息,清空范围只限定到当前事项的当前团。
*/
@At @At
@SaCheckPermission("tour.undertake") @SaCheckPermission("tour.undertake")
public Result releaseUndertakeLine(String matterId) { public Result releaseUndertakeLine(String matterId, String groupCode) {
if (!AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) { if (!AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) {
return Result.error("仅系统管理员可以解除领办"); return Result.error("仅系统管理员可以解除领办");
} }
if (StrUtil.isBlank(matterId)) { if (StrUtil.isBlank(matterId) || StrUtil.isBlank(groupCode)) {
return Result.error("参数错误"); return Result.error("参数错误");
} }
TourMatter matter = tourMatterService.fetch(matterId); return tourLedgerService.releaseUndertakeGroup(matterId, groupCode);
if (matter == null || Boolean.TRUE.equals(matter.getDelFlag())) {
return Result.error("事项不存在");
}
if (!"claimed".equals(matter.getUndertakeStatus())) {
return Result.error("当前线路未领办,无需解除");
}
matter.setUnionId("");
matter.setUndertakeStatus("unclaimed");
tourMatterService.update(matter);
return Result.success();
} }
@At @At
@@ -259,14 +253,14 @@ public class TourUndertakeController {
@At @At
@SaCheckPermission("tour.undertake") @SaCheckPermission("tour.undertake")
public Result signupPageData(PageForm pageForm, String matterId, String keyword, String unionId) { public Result signupPageData(PageForm pageForm, String matterId, String keyword, String unionId, String groupCode) {
if (StrUtil.isBlank(matterId)) { if (StrUtil.isBlank(matterId) || StrUtil.isBlank(groupCode)) {
return Result.error("参数错误"); return Result.error("参数错误");
} }
if (StrUtil.isBlank(pageForm.getPageOrderName())) { if (StrUtil.isBlank(pageForm.getPageOrderName())) {
pageForm.defaultSortDesc("signupTime"); pageForm.defaultSortAsc("signupTime");
} }
Cnd cnd = buildSignupQueryCnd(matterId, keyword, unionId); Cnd cnd = buildSignupQueryCnd(matterId, keyword, unionId, groupCode);
Sql countSql = Sqls.create(""" Sql countSql = Sqls.create("""
SELECT COUNT(1) SELECT COUNT(1)
FROM tour_ledger t FROM tour_ledger t
@@ -284,16 +278,19 @@ public class TourUndertakeController {
t.id, t.id,
t.jobNo, t.jobNo,
t.userName, t.userName,
t.gender,
'' AS mobile, '' AS mobile,
t.idCard, t.idCard,
t.unionId, t.unionId,
t.unionName, t.unionName,
t.signupTime t.signupTime,
t.groupCode,
t.groupName
FROM tour_ledger t FROM tour_ledger t
INNER JOIN tour_matter m ON m.id = t.matterId INNER JOIN tour_matter m ON m.id = t.matterId
INNER JOIN tour_line l ON l.id = m.lineId INNER JOIN tour_line l ON l.id = m.lineId
$condition $condition
ORDER BY $orderColumn $orderBy, t.signupTime DESC, t.createdAt DESC ORDER BY $orderColumn $orderBy, t.signupTime ASC, t.createdAt ASC
"""); """);
listSql.setCondition(cnd); listSql.setCondition(cnd);
listSql.setVar("orderColumn", getSignupOrderColumn(pageForm.getPageOrderName())); listSql.setVar("orderColumn", getSignupOrderColumn(pageForm.getPageOrderName()));
@@ -304,15 +301,19 @@ public class TourUndertakeController {
List<NutMap> list = listSql.getList(NutMap.class); List<NutMap> list = listSql.getList(NutMap.class);
fillSignupMobile(list); fillSignupMobile(list);
fillSignupFamilies(list); flattenSignupFamilies(list);
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list); Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list);
return Result.success(pagination); return Result.success(pagination);
} }
private Cnd buildSignupQueryCnd(String matterId, String keyword, String unionId) { /**
* 领办线路查看以团为单位,报名人员查询必须固定到当前点击的成团编码。
*/
private Cnd buildSignupQueryCnd(String matterId, String keyword, String unionId, String groupCode) {
Cnd cnd = Cnd.NEW(); Cnd cnd = Cnd.NEW();
cnd.and("t.delFlag", "=", false); cnd.and("t.delFlag", "=", false);
cnd.and("t.matterId", "=", matterId); cnd.and("t.matterId", "=", matterId);
cnd.and("t.groupCode", "=", groupCode);
cnd.and("m.delFlag", "=", false); cnd.and("m.delFlag", "=", false);
cnd.and("m.enabled", "=", true); cnd.and("m.enabled", "=", true);
cnd.and("m.lineId", "IS NOT", null); cnd.and("m.lineId", "IS NOT", null);
@@ -360,9 +361,9 @@ public class TourUndertakeController {
} }
/** /**
* 查看成团报名人员时按台账ID带出家属子列表,避免前端逐行请求家属信息。 * 查看单个团报名人员时将教职工和家属平铺到同一列表,便于领办线路复用线路成团的展示方式。
*/ */
private void fillSignupFamilies(List<NutMap> list) { private void flattenSignupFamilies(List<NutMap> list) {
if (list == null || list.isEmpty()) { if (list == null || list.isEmpty()) {
return; return;
} }
@@ -380,8 +381,9 @@ public class TourUndertakeController {
Sql sql = Sqls.create(""" Sql sql = Sqls.create("""
SELECT SELECT
ledgerId, ledgerId,
staffJobNo AS jobNo, '' AS jobNo,
familyName AS userName, familyName AS userName,
gender,
'' AS mobile, '' AS mobile,
idCard, idCard,
relationship relationship
@@ -396,11 +398,29 @@ public class TourUndertakeController {
Map<String, List<NutMap>> familyMap = new HashMap<>(); Map<String, List<NutMap>> familyMap = new HashMap<>();
for (NutMap family : sql.getList(NutMap.class)) { for (NutMap family : sql.getList(NutMap.class)) {
family.put("staffRow", false);
family.put("rowTypeName", "家属");
family.put("unionName", "");
familyMap.computeIfAbsent(family.getString("ledgerId", ""), key -> new ArrayList<>()).add(family); familyMap.computeIfAbsent(family.getString("ledgerId", ""), key -> new ArrayList<>()).add(family);
} }
List<NutMap> flatList = new ArrayList<>();
for (NutMap item : list) { for (NutMap item : list) {
item.put("families", familyMap.getOrDefault(item.getString("id", ""), List.of())); item.put("staffRow", true);
item.put("rowTypeName", "教职工");
item.put("relationship", "");
item.put("relatedStaffName", item.getString("userName", ""));
flatList.add(item);
List<NutMap> families = familyMap.getOrDefault(item.getString("id", ""), List.of());
for (NutMap family : families) {
// 家属行继承教职工成团信息,并标明关联教职工名称。
family.put("relatedStaffName", item.getString("userName", ""));
family.put("groupCode", item.getString("groupCode", ""));
family.put("groupName", item.getString("groupName", ""));
}
flatList.addAll(families);
} }
list.clear();
list.addAll(flatList);
} }
private Cnd buildQueryCnd(Integer year, String lineName, String lineType, String unionId, String undertakeStatus) { private Cnd buildQueryCnd(Integer year, String lineName, String lineType, String unionId, String undertakeStatus) {
@@ -414,14 +434,14 @@ public class TourUndertakeController {
cnd.andEX("m.`year`", "=", year == null ? LocalDate.now().getYear() : year); cnd.andEX("m.`year`", "=", year == null ? LocalDate.now().getYear() : year);
cnd.and(Cnd.likeEX("l.lineName", lineName)); cnd.and(Cnd.likeEX("l.lineName", lineName));
cnd.andEX("l.lineType", "=", lineType); cnd.andEX("l.lineType", "=", lineType);
cnd.andEX("m.unionId", "=", unionId); cnd.andEX("gs.undertakeUnionId", "=", unionId);
if ("claimed".equals(undertakeStatus)) { if ("claimed".equals(undertakeStatus)) {
cnd.and("m.undertakeStatus", "=", "claimed"); cnd.and("gs.undertakeUnionId", "IS NOT", null);
cnd.and("gs.undertakeUnionId", "<>", "");
} else if ("unclaimed".equals(undertakeStatus)) { } else if ("unclaimed".equals(undertakeStatus)) {
SqlExpressionGroup statusGroup = new SqlExpressionGroup(); SqlExpressionGroup statusGroup = new SqlExpressionGroup();
statusGroup.or("m.undertakeStatus", "=", "unclaimed"); statusGroup.or("gs.undertakeUnionId", "IS", null);
statusGroup.or("m.undertakeStatus", "IS", null); statusGroup.or("gs.undertakeUnionId", "=", "");
statusGroup.or("m.undertakeStatus", "=", "");
cnd.and(statusGroup); cnd.and(statusGroup);
} }
applyMatterDataScope(cnd); applyMatterDataScope(cnd);
@@ -489,7 +509,7 @@ public class TourUndertakeController {
return "m.matterName"; return "m.matterName";
} }
if ("unionName".equals(orderName)) { if ("unionName".equals(orderName)) {
return "u.name"; return "unionName";
} }
if ("undertakeStatusName".equals(orderName)) { if ("undertakeStatusName".equals(orderName)) {
return "undertakeStatus"; return "undertakeStatus";
@@ -498,16 +518,10 @@ public class TourUndertakeController {
return "l.lineType"; return "l.lineType";
} }
if ("signupCount".equals(orderName)) { if ("signupCount".equals(orderName)) {
return "signupCount"; return "gs.signupCount";
} }
if ("minGroupPeople".equals(orderName)) { if ("groupName".equals(orderName) || "groupCode".equals(orderName)) {
return "m.minGroupPeople"; return "gs.groupCode";
}
if ("maxGroupPeople".equals(orderName)) {
return "m.maxGroupPeople";
}
if ("groupStatusName".equals(orderName)) {
return "groupStatus";
} }
return "l.lineName"; return "l.lineName";
} }
@@ -519,12 +533,18 @@ public class TourUndertakeController {
if ("userName".equals(orderName)) { if ("userName".equals(orderName)) {
return "t.userName"; return "t.userName";
} }
if ("gender".equals(orderName)) {
return "t.gender";
}
if ("idCard".equals(orderName)) { if ("idCard".equals(orderName)) {
return "t.idCard"; return "t.idCard";
} }
if ("unionName".equals(orderName)) { if ("unionName".equals(orderName)) {
return "t.unionName"; return "t.unionName";
} }
if ("groupName".equals(orderName)) {
return "t.groupCode";
}
return "t.signupTime"; return "t.signupTime";
} }
} }
@@ -101,6 +101,32 @@ public class TourLedger extends BaseModel implements Serializable {
@ColDefine(type = ColType.VARCHAR, width = 50) @ColDefine(type = ColType.VARCHAR, width = 50)
private String lineType; private String lineType;
@Column
@Comment("成团编码")
@ColDefine(type = ColType.VARCHAR, width = 20)
private String groupCode;
@Column
@Comment("成团名称")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String groupName;
/**
* 领办工会编码按团回填到台账,避免领办动作再修改事项归属工会。
*/
@Column
@Comment("领办工会编码")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String undertakeUnionId;
/**
* 领办工会名称与编码一起落台账,列表按团展示时直接读取当前团领办信息。
*/
@Column
@Comment("领办工会")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String undertakeUnionName;
@Column @Column
@Comment("报名酒店") @Comment("报名酒店")
@ColDefine(type = ColType.VARCHAR, width = 100) @ColDefine(type = ColType.VARCHAR, width = 100)
@@ -33,8 +33,8 @@ public class TourLine extends BaseModel implements Serializable {
@Column @Column
@Comment("排序编号") @Comment("排序编号")
@ColDefine(type = ColType.VARCHAR, width = 50) @ColDefine(type = ColType.INT, width = 11)
private String lineCode; private Integer lineCode;
@Column @Column
@Comment("线路名称") @Comment("线路名称")
@@ -1,7 +1,58 @@
package com.budwk.app.zhgh.dayofficework.tour.service; package com.budwk.app.zhgh.dayofficework.tour.service;
import com.budwk.app.base.service.BaseService; import com.budwk.app.base.service.BaseService;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger; import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
import org.nutz.dao.Cnd;
import org.nutz.lang.util.NutMap;
import java.util.List;
public interface TourLedgerService extends BaseService<TourLedger> { public interface TourLedgerService extends BaseService<TourLedger> {
/**
* 按查询条件获取疗休养台账中已分配的成团编码和成团名称,返回值已按成团信息去重。
*
* @param cnd 已包含事项、线路、台账状态和数据权限的查询条件
* @return 去重后的成团信息列表
*/
List<NutMap> queryGroupOptions(Cnd cnd);
/**
* 按事项最大成团人数,将当前事项下未成团的报名教职工及家属整体自动分配到成团中。
*
* @param matterId 疗休养事项ID
* @return 分配结果,包含成功提示或具体业务校验错误
*/
Result assignGroup(String matterId);
/**
* 将选中的教职工报名台账调整到指定成团中,并校验调整后目标团人数不超过事项最大成团人数。
*
* @param matterId 当前疗休养事项ID
* @param groupCode 目标成团编码
* @param ledgerIds 选中的教职工台账ID,多个ID使用逗号分隔
* @return 调整结果,包含成功提示或具体业务校验错误
*/
Result adjustGroup(String matterId, String groupCode, String ledgerIds);
/**
* 按事项和成团编码领办当前团,只回填当前团台账的领办工会信息,不修改事项表归属工会。
*
* @param matterId 当前疗休养事项ID
* @param groupCode 当前成团编码
* @param unionId 当前登录人所属工会编码
* @param unionName 当前登录人所属工会名称
* @return 领办结果,包含参数、重复领办和有效团数据校验
*/
Result undertakeGroup(String matterId, String groupCode, String unionId, String unionName);
/**
* 按事项和成团编码解除当前团领办,只清空当前团台账的领办工会信息。
*
* @param matterId 当前疗休养事项ID
* @param groupCode 当前成团编码
* @return 解除结果,包含参数和当前团是否已领办校验
*/
Result releaseUndertakeGroup(String matterId, String groupCode);
} }
@@ -3,5 +3,17 @@ package com.budwk.app.zhgh.dayofficework.tour.service;
import com.budwk.app.base.service.BaseService; import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.dayofficework.tour.models.TourMatter; import com.budwk.app.zhgh.dayofficework.tour.models.TourMatter;
import java.util.List;
public interface TourMatterService extends BaseService<TourMatter> { public interface TourMatterService extends BaseService<TourMatter> {
/**
* 按事项ID批量更新报名起止时间,仅处理报名时间字段。
*
* @param ids 当前查询条件命中的事项ID集合
* @param signupStartTime 报名开始时间
* @param signupEndTime 报名结束时间
* @return 实际更新数量
*/
int assignSignupTime(List<String> ids, String signupStartTime, String signupEndTime);
} }
@@ -1,10 +1,28 @@
package com.budwk.app.zhgh.dayofficework.tour.service.impl; package com.budwk.app.zhgh.dayofficework.tour.service.impl;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.service.impl.BaseServiceImpl; 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.tour.models.TourLedger; import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService; import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao; import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@IocBean(args = {"refer:dao"}) @IocBean(args = {"refer:dao"})
public class TourLedgerServiceImpl extends BaseServiceImpl<TourLedger> implements TourLedgerService { public class TourLedgerServiceImpl extends BaseServiceImpl<TourLedger> implements TourLedgerService {
@@ -12,4 +30,577 @@ public class TourLedgerServiceImpl extends BaseServiceImpl<TourLedger> implement
public TourLedgerServiceImpl(Dao dao) { public TourLedgerServiceImpl(Dao dao) {
super(dao); super(dao);
} }
/**
* 根据传入条件查询台账中已经存在的成团信息,按成团编码和成团名称去重后返回给页面展示。
*/
@Override
public List<NutMap> queryGroupOptions(Cnd cnd) {
Sql sql = Sqls.create("""
SELECT DISTINCT
t.groupCode,
t.groupName
FROM tour_ledger t
INNER JOIN tour_matter m ON m.id = t.matterId
INNER JOIN tour_line l ON l.id = m.lineId
$condition
ORDER BY t.groupCode ASC, t.groupName ASC
""");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.maps());
dao().execute(sql);
return sql.getList(NutMap.class);
}
/**
* 自动分配当前事项下未成团的教职工台账,分配过程中出现容量不合法时整体中止。
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public Result assignGroup(String matterId) {
if (StrUtil.isBlank(matterId)) {
return Result.error("参数错误");
}
NutMap matter = fetchAssignMatter(matterId);
if (matter == null || matter.isEmpty()) {
return Result.error("事项不存在或线路信息不完整");
}
int maxGroupPeople = mapInt(matter, "maxGroupPeople");
if (maxGroupPeople <= 0) {
return Result.error("未配置最大成团人数,无法分配成团");
}
List<GroupBucket> groups = queryGroupBuckets(matterId);
List<SignupUnit> signupUnits = queryUnassignedSignupUnits(matterId);
if (signupUnits.isEmpty()) {
return Result.success("暂无未成团人员需要分配");
}
for (SignupUnit unit : signupUnits) {
if (unit.peopleCount > maxGroupPeople) {
return Result.error(unit.userName + "含家属共" + unit.peopleCount + "人,超过最大成团人数" + maxGroupPeople + "人,无法分配");
}
}
int nextGroupIndex = nextGroupIndex(groups);
int assignCount = 0;
for (SignupUnit unit : signupUnits) {
GroupBucket targetGroup = findAvailableGroup(groups, unit.peopleCount, maxGroupPeople);
if (targetGroup == null) {
targetGroup = createGroupBucket(nextGroupIndex++);
groups.add(targetGroup);
}
updateLedgerGroup(unit.ledgerId, targetGroup.groupCode, targetGroup.groupName);
targetGroup.peopleCount += unit.peopleCount;
assignCount++;
}
return Result.success("分配成功,共分配" + assignCount + "名教职工");
}
/**
* 将选中的教职工台账调整到目标团,家属随教职工一起计入目标团人数。
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public Result adjustGroup(String matterId, String groupCode, String ledgerIds) {
if (StrUtil.isBlank(matterId) || StrUtil.isBlank(groupCode)) {
return Result.error("参数错误");
}
List<String> ledgerIdList = parseLedgerIds(ledgerIds);
if (ledgerIdList.isEmpty()) {
return Result.error("请选择需要调整的教职工");
}
NutMap matter = fetchAssignMatter(matterId);
if (matter == null || matter.isEmpty()) {
return Result.error("事项不存在或线路信息不完整");
}
int maxGroupPeople = mapInt(matter, "maxGroupPeople");
if (maxGroupPeople <= 0) {
return Result.error("未配置最大成团人数,无法调整成团");
}
NutMap targetGroup = fetchTargetGroup(matterId, groupCode);
if (targetGroup == null || targetGroup.isEmpty()) {
return Result.error("请选择有效的成团信息");
}
List<SignupUnit> selectedUnits = querySelectedSignupUnits(matterId, ledgerIdList);
if (selectedUnits.size() != ledgerIdList.size()) {
return Result.error("所选人员信息无效,请刷新后重试");
}
int selectedPeopleCount = selectedUnits.stream().mapToInt(unit -> unit.peopleCount).sum();
int targetPeopleCount = countTargetGroupPeople(matterId, groupCode, ledgerIdList);
if (targetPeopleCount + selectedPeopleCount > maxGroupPeople) {
return Result.error("调整后目标团共" + (targetPeopleCount + selectedPeopleCount)
+ "人,超过最大成团人数" + maxGroupPeople + "人,无法调整");
}
updateLedgerGroup(matterId, ledgerIdList, groupCode, targetGroup.getString("groupName", ""));
return Result.success("调整成功,共调整" + selectedUnits.size() + "名教职工");
}
/**
* 查询事项基础信息,最大成团人数是自动分配的核心容量上限。
*/
/**
* 当前领办以团为单位执行,写入范围限定为同一事项、同一成团编码下的有效台账。
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public Result undertakeGroup(String matterId, String groupCode, String unionId, String unionName) {
if (StrUtil.isBlank(matterId) || StrUtil.isBlank(groupCode) || StrUtil.isBlank(unionId)) {
return Result.error("参数错误");
}
NutMap group = fetchUndertakeGroup(matterId, groupCode);
if (group == null || group.isEmpty()) {
return Result.error("当前团不存在或未成团,无法领办");
}
if (StrUtil.isNotBlank(group.getString("undertakeUnionId", ""))) {
return Result.error("当前团已领办,不允许修改");
}
Cnd cnd = buildUndertakeGroupCnd(matterId, groupCode);
Chain chain = Chain.make("undertakeUnionId", unionId)
.add("undertakeUnionName", StrUtil.blankToDefault(unionName, ""));
dao().update(TourLedger.class, chain, cnd);
return Result.success(NutMap.NEW().addv("unionId", unionId).addv("unionName", StrUtil.blankToDefault(unionName, "")));
}
/**
* 解除领办同样按团处理,仅清空当前团台账领办字段,保留事项表原有归属信息。
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public Result releaseUndertakeGroup(String matterId, String groupCode) {
if (StrUtil.isBlank(matterId) || StrUtil.isBlank(groupCode)) {
return Result.error("参数错误");
}
NutMap group = fetchUndertakeGroup(matterId, groupCode);
if (group == null || group.isEmpty()) {
return Result.error("当前团不存在或未成团,无法解除");
}
if (StrUtil.isBlank(group.getString("undertakeUnionId", ""))) {
return Result.error("当前团未领办,无需解除");
}
Cnd cnd = buildUndertakeGroupCnd(matterId, groupCode);
dao().update(TourLedger.class, Chain.make("undertakeUnionId", "").add("undertakeUnionName", ""), cnd);
return Result.success();
}
private NutMap fetchAssignMatter(String matterId) {
Cnd cnd = buildAssignMatterCnd(matterId);
Sql sql = Sqls.create("""
SELECT
m.id,
m.maxGroupPeople
FROM tour_matter m
INNER JOIN tour_line l ON l.id = m.lineId
$condition
""");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.map());
dao().execute(sql);
return sql.getObject(NutMap.class);
}
/**
* 查询当前事项下已存在的目标成团信息,人员调整只能选择当前线路台账中已有的团。
*/
private NutMap fetchTargetGroup(String matterId, String groupCode) {
Sql sql = Sqls.create("""
SELECT
t.groupCode,
MAX(t.groupName) AS groupName
FROM tour_ledger t
WHERE t.delFlag = 0
AND t.matterId = @matterId
AND t.groupCode = @groupCode
GROUP BY t.groupCode
""");
sql.setParam("matterId", matterId);
sql.setParam("groupCode", groupCode);
sql.setCallback(Sqls.callback.map());
dao().execute(sql);
return sql.getObject(NutMap.class);
}
/**
* 构造自动分配事项查询条件,确保自动分配的数据范围与线路成团查看列表保持一致。
*/
/**
* 查询当前事项下指定团的领办字段,供领办和解除领办前置校验使用。
*/
private NutMap fetchUndertakeGroup(String matterId, String groupCode) {
Cnd cnd = buildAssignMatterCnd(matterId);
cnd.and("t.delFlag", "=", false);
cnd.and("t.groupCode", "=", groupCode);
Sql sql = Sqls.create("""
SELECT
t.groupCode,
MAX(t.groupName) AS groupName,
MAX(t.undertakeUnionId) AS undertakeUnionId,
MAX(t.undertakeUnionName) AS undertakeUnionName
FROM tour_ledger t
INNER JOIN tour_matter m ON m.id = t.matterId
INNER JOIN tour_line l ON l.id = m.lineId
$condition
GROUP BY t.groupCode
""");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.map());
dao().execute(sql);
return sql.getObject(NutMap.class);
}
/**
* 构造当前团领办字段更新范围,避免同一事项下其它团被误更新。
*/
private Cnd buildUndertakeGroupCnd(String matterId, String groupCode) {
return Cnd.where("matterId", "=", matterId)
.and("groupCode", "=", groupCode)
.and("delFlag", "=", false);
}
private Cnd buildAssignMatterCnd(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);
return cnd;
}
/**
* 查询已存在成团及当前占用人数,后续未成团人员优先从最早团开始补足。
*/
private List<GroupBucket> queryGroupBuckets(String matterId) {
Sql sql = Sqls.create("""
SELECT
t.groupCode,
MAX(t.groupName) AS groupName,
SUM(1 + IFNULL(f.familyCount, 0)) AS peopleCount
FROM tour_ledger t
LEFT JOIN (
SELECT ledgerId, COUNT(1) AS familyCount
FROM tour_ledger_family
WHERE delFlag = 0
GROUP BY ledgerId
) f ON f.ledgerId = t.id
WHERE t.delFlag = 0
AND t.matterId = @matterId
AND t.groupCode IS NOT NULL
AND t.groupCode <> ''
GROUP BY t.groupCode
ORDER BY t.groupCode ASC
""");
sql.setParam("matterId", matterId);
sql.setCallback(Sqls.callback.maps());
dao().execute(sql);
List<GroupBucket> groups = new ArrayList<>();
for (NutMap item : sql.getList(NutMap.class)) {
GroupBucket group = new GroupBucket();
group.groupCode = item.getString("groupCode", "");
group.groupName = item.getString("groupName", "");
group.peopleCount = mapInt(item, "peopleCount");
groups.add(group);
}
return groups;
}
/**
* 查询未成团教职工台账,并合并家属人数,保证教职工和家属作为一个整体分配。
*/
private List<SignupUnit> queryUnassignedSignupUnits(String matterId) {
Sql sql = Sqls.create("""
SELECT
t.id AS ledgerId,
t.userName,
1 + IFNULL(f.familyCount, 0) AS peopleCount
FROM tour_ledger t
LEFT JOIN (
SELECT ledgerId, COUNT(1) AS familyCount
FROM tour_ledger_family
WHERE delFlag = 0
GROUP BY ledgerId
) f ON f.ledgerId = t.id
WHERE t.delFlag = 0
AND t.matterId = @matterId
AND (t.groupCode IS NULL OR t.groupCode = '')
ORDER BY t.signupTime ASC, t.createdAt ASC
""");
sql.setParam("matterId", matterId);
sql.setCallback(Sqls.callback.maps());
dao().execute(sql);
List<SignupUnit> signupUnits = new ArrayList<>();
for (NutMap item : sql.getList(NutMap.class)) {
SignupUnit unit = new SignupUnit();
unit.ledgerId = item.getString("ledgerId", "");
unit.userName = item.getString("userName", "");
unit.peopleCount = mapInt(item, "peopleCount");
signupUnits.add(unit);
}
return signupUnits;
}
/**
* 查询选中教职工台账及其家属人数,确保只调整当前事项下有效的教职工报名记录。
*/
private List<SignupUnit> querySelectedSignupUnits(String matterId, List<String> ledgerIds) {
Sql sql = Sqls.create("""
SELECT
t.id AS ledgerId,
t.userName,
1 + IFNULL(f.familyCount, 0) AS peopleCount
FROM tour_ledger t
LEFT JOIN (
SELECT ledgerId, COUNT(1) AS familyCount
FROM tour_ledger_family
WHERE delFlag = 0
GROUP BY ledgerId
) f ON f.ledgerId = t.id
WHERE t.delFlag = 0
AND t.matterId = @matterId
AND t.id IN (@ledgerIds)
ORDER BY t.signupTime ASC, t.createdAt ASC
""");
sql.setParam("matterId", matterId);
sql.setParam("ledgerIds", ledgerIds.toArray(new String[0]));
sql.setCallback(Sqls.callback.maps());
dao().execute(sql);
List<SignupUnit> signupUnits = new ArrayList<>();
for (NutMap item : sql.getList(NutMap.class)) {
SignupUnit unit = new SignupUnit();
unit.ledgerId = item.getString("ledgerId", "");
unit.userName = item.getString("userName", "");
unit.peopleCount = mapInt(item, "peopleCount");
signupUnits.add(unit);
}
return signupUnits;
}
/**
* 统计目标团调整前人数,排除本次选中人员,避免原本已在目标团的人员被重复计数。
*/
private int countTargetGroupPeople(String matterId, String groupCode, List<String> excludeLedgerIds) {
Sql sql = Sqls.create("""
SELECT IFNULL(SUM(1 + IFNULL(f.familyCount, 0)), 0) AS peopleCount
FROM tour_ledger t
LEFT JOIN (
SELECT ledgerId, COUNT(1) AS familyCount
FROM tour_ledger_family
WHERE delFlag = 0
GROUP BY ledgerId
) f ON f.ledgerId = t.id
WHERE t.delFlag = 0
AND t.matterId = @matterId
AND t.groupCode = @groupCode
AND t.id NOT IN (@excludeLedgerIds)
""");
sql.setParam("matterId", matterId);
sql.setParam("groupCode", groupCode);
sql.setParam("excludeLedgerIds", excludeLedgerIds.toArray(new String[0]));
sql.setCallback(Sqls.callback.map());
dao().execute(sql);
return mapInt(sql.getObject(NutMap.class), "peopleCount");
}
/**
* 从最早的团开始查找可容纳当前报名单位的团,避免前面团未满但后面新建团的情况。
*/
private GroupBucket findAvailableGroup(List<GroupBucket> groups, int peopleCount, int maxGroupPeople) {
for (GroupBucket group : groups) {
if (group.peopleCount + peopleCount <= maxGroupPeople) {
return group;
}
}
return null;
}
/**
* 根据现有最大团序号生成下一个团序号,兼容已有非标准团编码。
*/
private int nextGroupIndex(List<GroupBucket> groups) {
int maxIndex = 0;
for (GroupBucket group : groups) {
maxIndex = Math.max(maxIndex, parseGroupIndex(group.groupCode));
}
return maxIndex + 1;
}
/**
* 创建新的成团桶,编码使用T001递增,名称使用中文序号加“团”。
*/
private GroupBucket createGroupBucket(int index) {
GroupBucket group = new GroupBucket();
group.groupCode = String.format("T%03d", index);
group.groupName = toChineseNumber(index) + "团";
group.peopleCount = 0;
return group;
}
/**
* 自动分配按事项归属控制数据范围:校工会查看全部;分工会查看本工会事项;其它角色仅处理自己创建的事项。
*/
private void applyMatterDataScope(Cnd cnd) {
if (hasSchoolUnionScope() || AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name())) {
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 void updateLedgerGroup(String ledgerId, String groupCode, String groupName) {
dao().update(TourLedger.class, Chain.make("groupCode", groupCode).add("groupName", groupName), Cnd.where("id", "=", ledgerId));
}
/**
* 批量更新选中的教职工台账成团信息,人员调整只写教职工台账,家属展示继续继承教职工。
*/
private void updateLedgerGroup(String matterId, List<String> ledgerIds, String groupCode, String groupName) {
Cnd cnd = Cnd.where("id", "in", ledgerIds)
.and("matterId", "=", matterId)
.and("delFlag", "=", false);
dao().update(TourLedger.class, Chain.make("groupCode", groupCode).add("groupName", groupName), cnd);
}
/**
* 解析前端逗号分隔的台账ID,去重并过滤空值,避免重复计数和重复更新。
*/
private List<String> parseLedgerIds(String ledgerIds) {
Set<String> idSet = new LinkedHashSet<>();
if (StrUtil.isBlank(ledgerIds)) {
return new ArrayList<>();
}
for (String id : ledgerIds.split(",")) {
if (StrUtil.isNotBlank(id)) {
idSet.add(id.trim());
}
}
return new ArrayList<>(idSet);
}
/**
* 解析T001这类标准团编码中的数字序号,非标准编码返回0。
*/
private int parseGroupIndex(String groupCode) {
if (StrUtil.isBlank(groupCode) || !groupCode.startsWith("T")) {
return 0;
}
try {
return Integer.parseInt(groupCode.substring(1));
} catch (NumberFormatException e) {
return 0;
}
}
/**
* 将数字序号转为中文团名序号,支持常见的两位和多位团序号展示。
*/
private String toChineseNumber(int number) {
String[] digits = {"零", "一", "二", "三", "四", "五", "六", "七", "八", "九"};
if (number <= 0) {
return String.valueOf(number);
}
if (number < 10) {
return "第" + digits[number];
}
if (number < 100) {
int ten = number / 10;
int one = number % 10;
String prefix = ten == 1 ? "十" : digits[ten] + "十";
return "第" + prefix + (one == 0 ? "" : digits[one]);
}
String value = String.valueOf(number);
StringBuilder builder = new StringBuilder("第");
for (int i = 0; i < value.length(); i++) {
builder.append(digits[value.charAt(i) - '0']);
}
return builder.toString();
}
/**
* 安全读取SQL返回的整数值,避免不同数据库驱动返回Number或字符串时转换失败。
*/
private int mapInt(NutMap map, String key) {
Object value = map == null ? null : map.get(key);
if (value instanceof Number number) {
return number.intValue();
}
if (value == null || StrUtil.isBlank(String.valueOf(value))) {
return 0;
}
return Integer.parseInt(String.valueOf(value));
}
/**
* 已有成团容量桶,记录每个团当前人数以及后续分配后的占用人数。
*/
private static class GroupBucket {
private String groupCode;
private String groupName;
private int peopleCount;
}
/**
* 待分配报名单位,教职工及其家属作为不可拆分的最小分配单位。
*/
private static class SignupUnit {
private String ledgerId;
private String userName;
private int peopleCount;
}
} }
@@ -3,13 +3,35 @@ package com.budwk.app.zhgh.dayofficework.tour.service.impl;
import com.budwk.app.base.service.impl.BaseServiceImpl; import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.dayofficework.tour.models.TourMatter; import com.budwk.app.zhgh.dayofficework.tour.models.TourMatter;
import com.budwk.app.zhgh.dayofficework.tour.service.TourMatterService; import com.budwk.app.zhgh.dayofficework.tour.service.TourMatterService;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao; import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.ioc.loader.annotation.IocBean;
import java.util.List;
@IocBean(args = {"refer:dao"}) @IocBean(args = {"refer:dao"})
public class TourMatterServiceImpl extends BaseServiceImpl<TourMatter> implements TourMatterService { public class TourMatterServiceImpl extends BaseServiceImpl<TourMatter> implements TourMatterService {
public TourMatterServiceImpl(Dao dao) { public TourMatterServiceImpl(Dao dao) {
super(dao); super(dao);
} }
/**
* 按事项ID批量更新报名起止时间,供事项列表“一键赋值报名时间”使用。
*
* @param ids 当前查询条件命中的事项ID集合
* @param signupStartTime 报名开始时间
* @param signupEndTime 报名结束时间
* @return 实际更新数量
*/
@Override
public int assignSignupTime(List<String> ids, String signupStartTime, String signupEndTime) {
if (ids == null || ids.isEmpty()) {
return 0;
}
return dao().update(TourMatter.class,
Chain.make("signupStartTime", signupStartTime).add("signupEndTime", signupEndTime),
Cnd.where("id", "in", ids));
}
} }
@@ -0,0 +1,7 @@
-- 疗休养台账增加成团信息字段,用于线路成团自动分配和人员调整。
ALTER TABLE `tour_ledger`
ADD COLUMN `groupCode` varchar(20) DEFAULT NULL COMMENT '成团编码' AFTER `lineType`,
ADD COLUMN `groupName` varchar(50) DEFAULT NULL COMMENT '成团名称' AFTER `groupCode`;
ALTER TABLE `tour_ledger`
ADD KEY `idx_tour_ledger_matter_line_group` (`matterId`, `lineId`, `groupCode`, `delFlag`);
@@ -0,0 +1,5 @@
ALTER TABLE tour_ledger
ADD COLUMN undertakeUnionId VARCHAR(32) NULL COMMENT '领办工会编码' AFTER groupName,
ADD COLUMN undertakeUnionName VARCHAR(100) NULL COMMENT '领办工会' AFTER undertakeUnionId;
CREATE INDEX idx_tour_ledger_undertake_union ON tour_ledger (matterId, groupCode, undertakeUnionId);
@@ -0,0 +1,9 @@
-- Convert tour line sort code to numeric type for stable numeric ascending order.
UPDATE tour_line
SET lineCode = '0'
WHERE lineCode IS NULL
OR TRIM(lineCode) = ''
OR lineCode NOT REGEXP '^[0-9]+$';
ALTER TABLE tour_line
MODIFY COLUMN lineCode INT(11) NOT NULL DEFAULT 0 COMMENT '排序编号';
@@ -56,31 +56,16 @@ layout("/layouts/platform.html"){
<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="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="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="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 v-if="false" 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="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"> <el-table-column label="报名人数" prop="signupCount" width="120" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}"> <template slot-scope="{row}">
<el-tag size="mini" :type="Number(row.signupCount || 0) > 0 ? 'success' : 'info'">{{ row.signupCount || 0 }}人</el-tag> <el-tag size="mini" :type="Number(row.signupCount || 0) > 0 ? 'success' : 'info'">{{ row.signupCount || 0 }}人</el-tag>
</template> </template>
</el-table-column> </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"> <el-table-column label="操作" width="160" align="center" header-align="center" fixed="right">
<template slot-scope="{row}"> <template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button> <el-button size="mini" type="primary" @click="openView(row)">组团</el-button>
<el-button size="mini" type="primary" @click="exportLine(row)">导出</el-button> <el-button size="mini" type="primary" @click="exportLine(row)">导出</el-button>
</template> </template>
</el-table-column> </el-table-column>
@@ -120,33 +105,48 @@ layout("/layouts/platform.html"){
</el-card> </el-card>
<el-card class="mt10" shadow="never"> <el-card class="mt10" shadow="never">
<div class="tour-group-view-title">报名人员列表</div> <div class="tour-group-view-header">
<div class="tour-group-view-title">报名人员列表</div>
<div class="tour-group-options">
<el-tag
v-for="item in viewGroupOptions"
:key="item.groupCode"
:class="['tour-group-option', viewForm.groupCode === item.groupCode ? 'is-active' : '']"
size="small"
:type="viewForm.groupCode === item.groupCode ? 'success' : 'primary'"
@click="toggleGroupFilter(item)">
{{ formatGroupOption(item) }}
</el-tag>
</div>
<div class="tour-group-actions">
<el-button size="mini" type="primary" :loading="assignGroupLoading" @click="confirmAssignGroup">分配成团</el-button>
<el-button size="mini" type="primary" @click="openAdjustGroup">人员调整</el-button>
</div>
</div>
<el-table <el-table
ref="viewTable"
v-loading="viewLoading" v-loading="viewLoading"
:data="viewTableData" :data="viewTableData"
:size="tableSize" :size="tableSize"
border border
@selection-change="viewSelectionChange"
@sort-change="viewPageOrder"> @sort-change="viewPageOrder">
<el-table-column type="expand" width="50"> <el-table-column type="selection" width="50" align="center" header-align="center" :selectable="isViewRowSelectable"></el-table-column>
<template slot-scope="{row}">
<div v-if="row.families && row.families.length > 0" class="tour-family-list">
<el-table :data="row.families" :size="tableSize" border>
<el-table-column label="工号" prop="jobNo" width="130" align="center" header-align="center" show-overflow-tooltip></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="relationship" width="120" 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" align="center" header-align="center" show-overflow-tooltip></el-table-column>
</el-table>
</div>
<div v-else class="tour-family-empty">暂无亲属信息</div>
</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="序号" 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="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="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="性别" prop="gender" width="90" 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="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="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-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="rowTypeName" width="90" align="center" header-align="center"></el-table-column>
<el-table-column label="亲属关系" prop="relationship" width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="成团信息" prop="groupName" width="160" sortable="custom" align="center" header-align="center" show-overflow-tooltip>
<template slot-scope="{row}">
<span>{{ formatGroupInfo(row) }}</span>
</template>
</el-table-column>
<el-table-column label="关联教职工名称" prop="relatedStaffName" width="150" align="center" header-align="center" show-overflow-tooltip></el-table-column>
</el-table> </el-table>
<el-row class="el-pagination-container tour-group-view-pagination"> <el-row class="el-pagination-container tour-group-view-pagination">
<el-pagination <el-pagination
@@ -163,6 +163,31 @@ layout("/layouts/platform.html"){
</el-card> </el-card>
</div> </div>
</el-drawer> </el-drawer>
<el-dialog
title="人员调整"
:visible.sync="adjustVisible"
width="420px"
append-to-body
:close-on-click-modal="false"
@closed="resetAdjustForm">
<el-form :model="adjustForm" label-width="90px">
<el-form-item label="成团信息" required>
<el-select v-model="adjustForm.groupCode" filterable placeholder="请选择成团信息" style="width: 100%">
<el-option
v-for="item in viewGroupOptions"
:key="item.groupCode"
:label="formatGroupOption(item)"
:value="item.groupCode">
</el-option>
</el-select>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="adjustVisible = false">取消</el-button>
<el-button type="primary" :loading="adjustLoading" @click="submitAdjustGroup">确认</el-button>
</span>
</el-dialog>
</guava> </guava>
</div> </div>
@@ -181,6 +206,15 @@ layout("/layouts/platform.html"){
viewTitle: "", viewTitle: "",
viewRow: {}, viewRow: {},
viewTableData: [], viewTableData: [],
viewGroupOptions: [],
viewSelection: [],
syncingFamilySelection: false,
assignGroupLoading: false,
adjustVisible: false,
adjustLoading: false,
adjustForm: {
groupCode: ""
},
pageForm: { pageForm: {
pageNumber: 1, pageNumber: 1,
pageSize: 10, pageSize: 10,
@@ -196,10 +230,11 @@ layout("/layouts/platform.html"){
pageNumber: 1, pageNumber: 1,
pageSize: 10, pageSize: 10,
totalCount: 0, totalCount: 0,
pageOrderName: "signupTime", pageOrderName: "groupCode",
pageOrderBy: "descending", pageOrderBy: "ascending",
keyword: "", keyword: "",
unionId: "" unionId: "",
groupCode: ""
} }
} }
}, },
@@ -226,6 +261,14 @@ layout("/layouts/platform.html"){
|| row.directFamilyUnitLine === 1 || row.directFamilyUnitLine === 1
|| row.directFamilyUnitLine === "1" || row.directFamilyUnitLine === "1"
}, },
formatGroupInfo(row) {
if (!row || !row.groupCode) return "未成团"
return row.groupName ? row.groupCode + " " + row.groupName : row.groupCode
},
formatGroupOption(item) {
if (!item || !item.groupCode) return ""
return item.groupName ? item.groupCode + " " + item.groupName : item.groupCode
},
openView(row) { openView(row) {
this.viewRow = row || {} this.viewRow = row || {}
this.viewTitle = this.viewRow.lineName || "报名人员" this.viewTitle = this.viewRow.lineName || "报名人员"
@@ -233,11 +276,17 @@ layout("/layouts/platform.html"){
this.viewForm.pageNumber = 1 this.viewForm.pageNumber = 1
this.viewForm.pageSize = 10 this.viewForm.pageSize = 10
this.viewForm.totalCount = 0 this.viewForm.totalCount = 0
this.viewForm.pageOrderName = "signupTime" this.viewForm.pageOrderName = "groupCode"
this.viewForm.pageOrderBy = "descending" this.viewForm.pageOrderBy = "ascending"
this.viewForm.keyword = "" this.viewForm.keyword = ""
this.viewForm.unionId = "" this.viewForm.unionId = ""
this.viewForm.groupCode = ""
this.viewTableData = [] this.viewTableData = []
this.viewGroupOptions = []
this.viewSelection = []
this.assignGroupLoading = false
this.resetAdjustForm()
this.loadViewGroupOptions()
this.loadViewData() this.loadViewData()
}, },
viewSearch() { viewSearch() {
@@ -247,12 +296,118 @@ layout("/layouts/platform.html"){
viewReset() { viewReset() {
this.viewForm.keyword = "" this.viewForm.keyword = ""
this.viewForm.unionId = "" this.viewForm.unionId = ""
this.viewForm.groupCode = ""
this.viewForm.pageNumber = 1
this.loadViewData()
},
toggleGroupFilter(item) {
const groupCode = item && item.groupCode ? item.groupCode : ""
this.viewForm.groupCode = this.viewForm.groupCode === groupCode ? "" : groupCode
this.viewForm.pageNumber = 1 this.viewForm.pageNumber = 1
this.loadViewData() this.loadViewData()
}, },
viewIndexMethod(index) { viewIndexMethod(index) {
return (this.viewForm.pageNumber - 1) * this.viewForm.pageSize + index + 1 return (this.viewForm.pageNumber - 1) * this.viewForm.pageSize + index + 1
}, },
isViewRowSelectable(row) {
return !!(row && row.selectable)
},
viewSelectionChange(selection) {
this.viewSelection = selection || []
this.syncFamilySelection(selection || [])
},
syncFamilySelection(selection) {
if (this.syncingFamilySelection || !this.$refs.viewTable) {
return
}
this.syncingFamilySelection = true
const selectedStaffIds = selection.filter(item => item && item.staffRow).map(item => item.id)
this.viewTableData.forEach(row => {
if (!row || row.staffRow || !row.ledgerId) {
return
}
this.$refs.viewTable.toggleRowSelection(row, selectedStaffIds.indexOf(row.ledgerId) !== -1)
})
this.$nextTick(() => {
this.syncingFamilySelection = false
})
},
confirmAssignGroup() {
if (!this.viewRow || !this.viewRow.matterId) {
this.$message.warning("线路信息不完整")
return
}
this.$confirm("确认要自动分配未成团人员吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.doAssignGroup()
}).catch(() => {})
},
doAssignGroup() {
this.assignGroupLoading = true
this.$axios.post(loc() + "/assignGroup", {
matterId: this.viewRow.matterId
}).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg || "分配成功")
this.loadViewGroupOptions()
this.loadViewData()
this.pageData()
} else {
this.$message.warning(res.msg || "分配失败")
}
}).finally(() => {
this.assignGroupLoading = false
})
},
openAdjustGroup() {
if (!this.viewSelection.length) {
this.$message.warning("请选择需要调整的教职工")
return
}
if (!this.viewGroupOptions.length) {
this.$message.warning("暂无可调整的成团信息")
return
}
this.adjustVisible = true
},
submitAdjustGroup() {
if (!this.adjustForm.groupCode) {
this.$message.warning("请选择成团信息")
return
}
const ledgerIds = this.viewSelection.filter(item => item && item.staffRow).map(item => item.id).filter(id => id).join(",")
if (!ledgerIds) {
this.$message.warning("请选择需要调整的教职工")
return
}
this.adjustLoading = true
this.$axios.post(loc() + "/adjustGroup", {
matterId: this.viewRow.matterId,
groupCode: this.adjustForm.groupCode,
ledgerIds: ledgerIds
}).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg || "调整成功")
this.adjustVisible = false
this.loadViewGroupOptions()
this.loadViewData()
this.pageData()
} else {
this.$message.warning(res.msg || "调整失败")
}
}).finally(() => {
this.adjustLoading = false
})
},
resetAdjustForm() {
this.adjustLoading = false
this.adjustForm = {
groupCode: ""
}
},
viewSizeChange(size) { viewSizeChange(size) {
this.viewForm.pageSize = size this.viewForm.pageSize = size
this.viewForm.pageNumber = 1 this.viewForm.pageNumber = 1
@@ -271,6 +426,7 @@ layout("/layouts/platform.html"){
loadViewData() { loadViewData() {
if (!this.viewRow || !this.viewRow.matterId) { if (!this.viewRow || !this.viewRow.matterId) {
this.viewTableData = [] this.viewTableData = []
this.viewSelection = []
this.viewForm.totalCount = 0 this.viewForm.totalCount = 0
return return
} }
@@ -280,6 +436,7 @@ layout("/layouts/platform.html"){
matterId: this.viewRow.matterId, matterId: this.viewRow.matterId,
keyword: this.viewForm.keyword, keyword: this.viewForm.keyword,
unionId: this.viewForm.unionId, unionId: this.viewForm.unionId,
groupCode: this.viewForm.groupCode,
pageNumber: this.viewForm.pageNumber, pageNumber: this.viewForm.pageNumber,
pageSize: this.viewForm.pageSize, pageSize: this.viewForm.pageSize,
pageOrderName: this.viewForm.pageOrderName, pageOrderName: this.viewForm.pageOrderName,
@@ -289,6 +446,12 @@ layout("/layouts/platform.html"){
if (res.code === 0) { if (res.code === 0) {
const data = res.data || {} const data = res.data || {}
this.viewTableData = data.list || [] this.viewTableData = data.list || []
this.viewSelection = []
this.$nextTick(() => {
if (this.$refs.viewTable) {
this.$refs.viewTable.clearSelection()
}
})
this.viewForm.totalCount = data.totalCount || 0 this.viewForm.totalCount = data.totalCount || 0
} else { } else {
this.$message.warning(res.msg || "查询失败") this.$message.warning(res.msg || "查询失败")
@@ -299,6 +462,21 @@ layout("/layouts/platform.html"){
} }
}) })
}, },
loadViewGroupOptions() {
if (!this.viewRow || !this.viewRow.matterId) {
this.viewGroupOptions = []
return
}
this.$axios.post(loc() + "/groupOptions", {
matterId: this.viewRow.matterId
}).then((res) => {
if (res.code === 0) {
this.viewGroupOptions = res.data || []
} else {
this.$message.warning(res.msg || "查询成团信息失败")
}
})
},
exportLine(row) { exportLine(row) {
if (!row || !row.matterId) { if (!row || !row.matterId) {
this.$message.warning("线路信息不完整") this.$message.warning("线路信息不完整")
@@ -347,21 +525,52 @@ layout("/layouts/platform.html"){
margin-top: 12px; margin-top: 12px;
text-align: right; text-align: right;
} }
.tour-group-view-header {
align-items: flex-start;
display: flex;
gap: 12px;
justify-content: flex-start;
margin-bottom: 12px;
max-width: 100%;
}
.tour-group-view-title { .tour-group-view-title {
border-left: 4px solid #0079c2; border-left: 4px solid #0079c2;
color: #0079c2; color: #0079c2;
font-size: 14px; font-size: 14px;
font-weight: 600; font-weight: 600;
line-height: 16px; line-height: 16px;
margin-bottom: 12px;
padding-left: 10px; padding-left: 10px;
white-space: nowrap;
} }
.tour-family-list { .tour-group-options {
padding: 8px 32px; display: flex;
flex: 0 1 820px;
flex-wrap: nowrap;
gap: 8px;
justify-content: flex-start;
max-width: calc(100% - 120px);
min-width: 0;
overflow-x: auto;
overflow-y: hidden;
padding-bottom: 4px;
} }
.tour-family-empty { .tour-group-actions {
color: #909399; display: flex;
padding: 8px 32px; flex: 0 0 auto;
gap: 8px;
margin-left: auto;
}
.tour-group-option {
cursor: pointer;
flex: 0 0 auto;
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tour-group-option.is-active {
border-color: #67c23a;
font-weight: 600;
} }
.el-table .direct-family-row > td, .el-table .direct-family-row > td,
.el-table .direct-family-row > td .cell { .el-table .direct-family-row > td .cell {
@@ -108,6 +108,12 @@ layout("/layouts/platform.html"){
<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="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="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="lineType" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="成团信息" prop="groupName" width="160" align="center" header-align="center" show-overflow-tooltip>
<template slot-scope="{row}">
<span>{{ formatGroupInfo(row) }}</span>
</template>
</el-table-column>
<el-table-column label="领办工会" prop="undertakeUnionName" min-width="160" 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="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"> <el-table-column label="携带家属" prop="familyCount" width="120" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}"> <template slot-scope="{row}">
@@ -380,6 +386,10 @@ layout("/layouts/platform.html"){
} }
}, },
methods: { methods: {
formatGroupInfo(row) {
if (!row || !row.groupCode) return ""
return row.groupName ? row.groupCode + " " + row.groupName : row.groupCode
},
toBoolean(value) { toBoolean(value) {
return value === true || value === 1 || value === "1" || value === "true" || value === "TRUE" return value === true || value === 1 || value === "1" || value === "true" || value === "TRUE"
}, },
@@ -34,6 +34,11 @@ layout("/layouts/platform.html"){
<el-option v-for="item in organizationTypeOptions" :key="item.code" :label="item.name" :value="item.code"></el-option> <el-option v-for="item in organizationTypeOptions" :key="item.code" :label="item.name" :value="item.code"></el-option>
</el-select> </el-select>
</search-item> </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.code" :label="item.name" :value="item.name"></el-option>
</el-select>
</search-item>
<div class="search-query" style="margin-left: auto;"> <div class="search-query" style="margin-left: auto;">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button> <el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button> <el-button size="medium" @click="resetSearch">重置</el-button>
@@ -43,10 +48,33 @@ layout("/layouts/platform.html"){
<el-card class="mt10" shadow="never"> <el-card class="mt10" shadow="never">
<table-tool :app="this" label="事项列表"> <table-tool :app="this" label="事项列表">
<div class="tour-matter-signup-tool">
<el-date-picker
v-model="batchSignupTimeForm.signupStartTime"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="报名开始时间"
size="medium">
</el-date-picker>
<span>-</span>
<el-date-picker
v-model="batchSignupTimeForm.signupEndTime"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="报名结束时间"
size="medium">
</el-date-picker>
<el-button
size="medium"
type="primary"
:loading="batchSignupTimeLoading"
@click="assignSignupTime">一键赋值报名时间</el-button>
</div>
<el-button @click="openAdd" size="medium" type="primary"> <el-button @click="openAdd" size="medium" type="primary">
<i class="el-icon-plus"></i> <i class="el-icon-plus"></i>
新增 新增
</el-button> </el-button>
<el-button @click="exportData" size="medium" type="primary" icon="el-icon-download">导出</el-button>
</table-tool> </table-tool>
<el-table <el-table
@@ -54,13 +82,24 @@ layout("/layouts/platform.html"){
:data="tableData" :data="tableData"
:size="tableSize" :size="tableSize"
border border
:default-sort="{prop: 'lineCode', order: 'ascending'}"
:row-class-name="tableRowClassName" :row-class-name="tableRowClassName"
@sort-change="pageOrder"> @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="序号" 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="year" width="110" sortable="custom" align="center" header-align="center"></el-table-column>
<el-table-column label="事项名称" prop="matterName" min-width="240" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column> <el-table-column label="事项名称" prop="matterName" min-width="240" sortable="custom" 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 v-if="false" label="所属工会" prop="unionName" min-width="160" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="线路名称" prop="lineName" min-width="260" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column> <el-table-column label="线路名称" prop="lineName" min-width="260" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="报名时间" min-width="300" align="center" header-align="center" show-overflow-tooltip>
<template slot-scope="{row}">
<span>{{ formatTimeRange(row.signupStartTime, row.signupEndTime) }}</span>
</template>
</el-table-column>
<el-table-column label="出行时间" min-width="220" align="center" header-align="center" show-overflow-tooltip>
<template slot-scope="{row}">
<span>{{ formatTimeRange(row.travelStartTime, row.travelEndTime) }}</span>
</template>
</el-table-column>
<el-table-column label="组织形式" prop="organizationTypeName" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column> <el-table-column label="组织形式" prop="organizationTypeName" width="130" 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="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="contactPhone" width="150" align="center" header-align="center" show-overflow-tooltip></el-table-column>
@@ -201,7 +240,7 @@ layout("/layouts/platform.html"){
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-form-item label="最多成团人数" prop="maxGroupPeople"> <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-input-number v-model="batchForm.maxGroupPeople" :precision="0" controls-position="right" style="width: 100%" @change="peopleChange"></el-input-number>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
@@ -218,6 +257,15 @@ layout("/layouts/platform.html"){
.el-table .direct-family-row > td .cell { .el-table .direct-family-row > td .cell {
color: #f56c6c; color: #f56c6c;
} }
.tour-matter-signup-tool {
display: inline-flex;
align-items: center;
gap: 8px;
margin-right: 12px;
}
.tour-matter-signup-tool .el-date-editor {
width: 210px;
}
</style> </style>
<script nonce="${cspNonce!}"> <script nonce="${cspNonce!}">
@@ -257,9 +305,6 @@ layout("/layouts/platform.html"){
const validatePeople = (rule, value, callback) => { const validatePeople = (rule, value, callback) => {
if (!value || value <= 0) { if (!value || value <= 0) {
callback(new Error("人数必须大于0")) callback(new Error("人数必须大于0"))
} else if (this.batchForm.minGroupPeople && this.batchForm.maxGroupPeople
&& this.batchForm.minGroupPeople > this.batchForm.maxGroupPeople) {
callback(new Error("最少人数不能大于最多人数"))
} else { } else {
callback() callback()
} }
@@ -292,9 +337,15 @@ layout("/layouts/platform.html"){
batchFormVisible: false, batchFormVisible: false,
batchSubDis: false, batchSubDis: false,
batchTitle: "", batchTitle: "",
batchSignupTimeLoading: false,
batchSignupTimeForm: {
signupStartTime: "",
signupEndTime: ""
},
currentMatter: {}, currentMatter: {},
batchForm: {}, batchForm: {},
lineOptions: [], lineOptions: [],
lineTypeOptions: [],
settingOptions: [], settingOptions: [],
unionOptions: [], unionOptions: [],
organizationTypeOptions: [], organizationTypeOptions: [],
@@ -302,10 +353,13 @@ layout("/layouts/platform.html"){
pageNumber: 1, pageNumber: 1,
pageSize: 10, pageSize: 10,
totalCount: 0, totalCount: 0,
pageOrderName: "lineCode",
pageOrderBy: "ascending",
year: currentYear, year: currentYear,
matterName: "", matterName: "",
unionId: "", unionId: "",
organizationType: "" organizationType: "",
lineType: ""
}, },
formData: {}, formData: {},
formRules: { formRules: {
@@ -324,7 +378,6 @@ layout("/layouts/platform.html"){
contactName: [{ required: true, message: "必填", trigger: ["blur", "change"] }], contactName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
contactPhone: [{ validator: validateMobile, trigger: ["blur", "change"] }], contactPhone: [{ validator: validateMobile, trigger: ["blur", "change"] }],
minGroupPeople: [{ validator: validatePeople, trigger: ["blur", "change"] }], minGroupPeople: [{ validator: validatePeople, trigger: ["blur", "change"] }],
maxGroupPeople: [{ validator: validatePeople, trigger: ["blur", "change"] }],
estimatedCost: [{ validator: validateMoney, trigger: ["blur", "change"] }] estimatedCost: [{ validator: validateMoney, trigger: ["blur", "change"] }]
} }
} }
@@ -355,9 +408,65 @@ layout("/layouts/platform.html"){
this.pageForm.matterName = "" this.pageForm.matterName = ""
this.pageForm.unionId = "" this.pageForm.unionId = ""
this.pageForm.organizationType = "" this.pageForm.organizationType = ""
this.pageForm.lineType = ""
this.loadSettings(this.pageForm.year) this.loadSettings(this.pageForm.year)
this.doSearch() this.doSearch()
}, },
formatTimeRange(startTime, endTime) {
if (!startTime && !endTime) return ""
if (!startTime) return endTime || ""
if (!endTime) return startTime || ""
return startTime + " 至 " + endTime
},
exportData() {
const params = new URLSearchParams()
;["year", "matterName", "unionId", "organizationType", "lineType"].forEach(key => {
const value = this.pageForm[key]
if (value !== undefined && value !== null && value !== "") {
params.append(key, value)
}
})
window.location.href = loc() + "/exportData?" + params.toString()
},
// 按当前筛选条件批量更新事项报名时间,提交前必须让用户确认更新范围。
assignSignupTime() {
const startTime = this.batchSignupTimeForm.signupStartTime
const endTime = this.batchSignupTimeForm.signupEndTime
if (!startTime || !endTime) {
this.$message.warning("请选择报名开始时间和报名结束时间")
return
}
if (startTime >= endTime) {
this.$message.warning("报名开始时间必须小于报名结束时间")
return
}
this.$confirm("确认将当前查询条件下的事项报名时间统一赋值为:" + startTime + " 至 " + endTime + "?", "提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.batchSignupTimeLoading = true
this.$axios.post(loc() + "/assignSignupTime", {
year: this.pageForm.year,
matterName: this.pageForm.matterName,
unionId: this.pageForm.unionId,
organizationType: this.pageForm.organizationType,
lineType: this.pageForm.lineType,
signupStartTime: startTime,
signupEndTime: endTime
}).then((res) => {
this.batchSignupTimeLoading = false
if (res.code === 0) {
this.$message.success(res.msg || "赋值成功")
this.pageData()
} else {
this.$message.warning(res.msg || "赋值失败")
}
}).catch(() => {
this.batchSignupTimeLoading = false
})
}).catch(() => {})
},
tableRowClassName({ row }) { tableRowClassName({ row }) {
return this.isDirectFamilyLine(row) ? "direct-family-row" : "" return this.isDirectFamilyLine(row) ? "direct-family-row" : ""
}, },
@@ -478,6 +587,13 @@ layout("/layouts/platform.html"){
} }
}) })
}, },
loadLineTypes() {
this.$axios.post(loc() + "/lineTypeOptions").then((res) => {
if (res.code === 0) {
this.lineTypeOptions = res.data || []
}
})
},
emptyBatchForm() { emptyBatchForm() {
return { return {
id: this.currentMatter.id || "", id: this.currentMatter.id || "",
@@ -530,7 +646,9 @@ layout("/layouts/platform.html"){
if (!item) return "" if (!item) return ""
const lineName = this.lineField(item, "lineName") || "" const lineName = this.lineField(item, "lineName") || ""
const lineType = this.lineField(item, "lineType") || "" const lineType = this.lineField(item, "lineType") || ""
return lineType ? lineName + "(" + lineType + ")" : lineName const travelAgencyName = this.lineField(item, "travelAgencyName") || ""
const lineDesc = [lineType, travelAgencyName].filter(item => item).join("/")
return lineDesc ? lineName + "(" + lineDesc + ")" : lineName
}, },
lineChange() { lineChange() {
this.applySelectedLineDefaults(true) this.applySelectedLineDefaults(true)
@@ -589,10 +707,12 @@ layout("/layouts/platform.html"){
}, },
applySettingSignupTime(data) { applySettingSignupTime(data) {
if (!data) return if (!data) return
if (data.signupStartTime) { // 已保存线路配置时保留事项自身报名时间,避免配置默认值覆盖用户修改。
const hasSavedLineConfig = !!this.batchForm.lineId
if (!hasSavedLineConfig && data.signupStartTime) {
this.$set(this.batchForm, "signupStartTime", data.signupStartTime) this.$set(this.batchForm, "signupStartTime", data.signupStartTime)
} }
if (data.signupEndTime) { if (!hasSavedLineConfig && data.signupEndTime) {
this.$set(this.batchForm, "signupEndTime", data.signupEndTime) this.$set(this.batchForm, "signupEndTime", data.signupEndTime)
} }
this.$nextTick(() => { this.$nextTick(() => {
@@ -603,10 +723,12 @@ layout("/layouts/platform.html"){
}, },
applySettingPeople(data) { applySettingPeople(data) {
if (!data) return if (!data) return
if (data.minGroupPeople !== null && data.minGroupPeople !== undefined && data.minGroupPeople !== "") { // 已保存线路配置时保留事项自身成团人数,配置人数只作为首次选择线路的默认值。
const hasSavedLineConfig = !!this.batchForm.lineId
if (!hasSavedLineConfig && data.minGroupPeople !== null && data.minGroupPeople !== undefined && data.minGroupPeople !== "") {
this.$set(this.batchForm, "minGroupPeople", data.minGroupPeople) this.$set(this.batchForm, "minGroupPeople", data.minGroupPeople)
} }
if (data.maxGroupPeople !== null && data.maxGroupPeople !== undefined && data.maxGroupPeople !== "") { if (!hasSavedLineConfig && data.maxGroupPeople !== null && data.maxGroupPeople !== undefined && data.maxGroupPeople !== "") {
this.$set(this.batchForm, "maxGroupPeople", data.maxGroupPeople) this.$set(this.batchForm, "maxGroupPeople", data.maxGroupPeople)
} }
this.$nextTick(() => { this.$nextTick(() => {
@@ -731,6 +853,7 @@ layout("/layouts/platform.html"){
this.loadSettings(this.pageForm.year) this.loadSettings(this.pageForm.year)
this.loadUnions() this.loadUnions()
this.loadOrganizationTypes() this.loadOrganizationTypes()
this.loadLineTypes()
this.pageData() this.pageData()
} }
}) })
@@ -29,6 +29,11 @@ layout("/layouts/platform.html"){
<el-option v-for="item in lotOptions" :key="item.id" :label="item.lotName" :value="item.id"></el-option> <el-option v-for="item in lotOptions" :key="item.id" :label="item.lotName" :value="item.id"></el-option>
</el-select> </el-select>
</search-item> </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.code" :label="item.name" :value="item.name"></el-option>
</el-select>
</search-item>
<div class="search-query" style="margin-left: auto;"> <div class="search-query" style="margin-left: auto;">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button> <el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button> <el-button size="medium" @click="resetSearch">重置</el-button>
@@ -42,6 +47,7 @@ layout("/layouts/platform.html"){
<i class="el-icon-plus"></i> <i class="el-icon-plus"></i>
新增 新增
</el-button> </el-button>
<el-button @click="exportData" size="medium" type="primary" icon="el-icon-download">导出</el-button>
</table-tool> </table-tool>
<el-table <el-table
@@ -323,7 +329,7 @@ layout("/layouts/platform.html"){
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-form-item label="最多成团人数" prop="maxGroupPeople"> <el-form-item label="最多成团人数" prop="maxGroupPeople">
<el-input-number v-model="batchForm.maxGroupPeople" :min="1" :precision="0" controls-position="right" style="width: 100%" @change="matterPeopleChange"></el-input-number> <el-input-number v-model="batchForm.maxGroupPeople" :precision="0" controls-position="right" style="width: 100%" @change="matterPeopleChange"></el-input-number>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
@@ -386,9 +392,6 @@ layout("/layouts/platform.html"){
const validateMatterPeople = (rule, value, callback) => { const validateMatterPeople = (rule, value, callback) => {
if (!value || value <= 0) { if (!value || value <= 0) {
callback(new Error("人数必须大于0")) callback(new Error("人数必须大于0"))
} else if (this.batchForm.minGroupPeople && this.batchForm.maxGroupPeople
&& this.batchForm.minGroupPeople > this.batchForm.maxGroupPeople) {
callback(new Error("最少人数不能大于最多人数"))
} else { } else {
callback() callback()
} }
@@ -438,7 +441,8 @@ layout("/layouts/platform.html"){
pageOrderBy: "ascending", pageOrderBy: "ascending",
year: currentYear, year: currentYear,
lineName: "", lineName: "",
lotId: "" lotId: "",
lineType: ""
}, },
formData: {}, formData: {},
matterForm: {}, matterForm: {},
@@ -468,7 +472,6 @@ layout("/layouts/platform.html"){
contactName: [{ required: true, message: "必填", trigger: ["blur", "change"] }], contactName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
contactPhone: [{ validator: validateMatterMobile, trigger: ["blur", "change"] }], contactPhone: [{ validator: validateMatterMobile, trigger: ["blur", "change"] }],
minGroupPeople: [{ validator: validateMatterPeople, trigger: ["blur", "change"] }], minGroupPeople: [{ validator: validateMatterPeople, trigger: ["blur", "change"] }],
maxGroupPeople: [{ validator: validateMatterPeople, trigger: ["blur", "change"] }],
estimatedCost: [{ validator: validateMatterMoney, trigger: ["blur", "change"] }] estimatedCost: [{ validator: validateMatterMoney, trigger: ["blur", "change"] }]
} }
} }
@@ -478,9 +481,20 @@ layout("/layouts/platform.html"){
this.pageForm.year = moment().format("YYYY") this.pageForm.year = moment().format("YYYY")
this.pageForm.lineName = "" this.pageForm.lineName = ""
this.pageForm.lotId = "" this.pageForm.lotId = ""
this.pageForm.lineType = ""
this.loadOptions(this.pageForm.year) this.loadOptions(this.pageForm.year)
this.doSearch() this.doSearch()
}, },
exportData() {
const params = new URLSearchParams()
;["year", "lineName", "lotId", "lineType"].forEach(key => {
const value = this.pageForm[key]
if (value !== undefined && value !== null && value !== "") {
params.append(key, value)
}
})
window.location.href = loc() + "/exportData?" + params.toString()
},
tableRowClassName({ row }) { tableRowClassName({ row }) {
return this.isDirectFamilyLine(row) ? "direct-family-row" : "" return this.isDirectFamilyLine(row) ? "direct-family-row" : ""
}, },
@@ -767,10 +781,11 @@ layout("/layouts/platform.html"){
}, },
applyMatterSettingPeople(data) { applyMatterSettingPeople(data) {
if (!data) return if (!data) return
const hasSavedLineConfig = !!this.batchForm.lineId
if (data.minGroupPeople !== null && data.minGroupPeople !== undefined && data.minGroupPeople !== "") { if (data.minGroupPeople !== null && data.minGroupPeople !== undefined && data.minGroupPeople !== "") {
this.$set(this.batchForm, "minGroupPeople", data.minGroupPeople) this.$set(this.batchForm, "minGroupPeople", data.minGroupPeople)
} }
if (data.maxGroupPeople !== null && data.maxGroupPeople !== undefined && data.maxGroupPeople !== "") { if (!hasSavedLineConfig && data.maxGroupPeople !== null && data.maxGroupPeople !== undefined && data.maxGroupPeople !== "") {
this.$set(this.batchForm, "maxGroupPeople", data.maxGroupPeople) this.$set(this.batchForm, "maxGroupPeople", data.maxGroupPeople)
} }
this.$nextTick(() => { this.$nextTick(() => {
@@ -313,15 +313,6 @@ layout("/layouts/platform.html"){
el: "#app", el: "#app",
mixins: [initTableMixins], mixins: [initTableMixins],
data() { data() {
// 前端先做成团人数区间校验,后端也会再兜底校验一次。
const checkGroupPeople = (rule, value, callback) => {
if (this.formData.minGroupPeople !== null && this.formData.maxGroupPeople !== null
&& this.formData.minGroupPeople > this.formData.maxGroupPeople) {
callback(new Error("最少成团人数不能大于最多成团人数"))
} else {
callback()
}
}
const checkSignupTime = (rule, value, callback) => { const checkSignupTime = (rule, value, callback) => {
if (!value) { if (!value) {
callback(new Error("必填")) callback(new Error("必填"))
@@ -369,8 +360,6 @@ layout("/layouts/platform.html"){
formRules: { formRules: {
year: [{required: true, message: "必填", trigger: ["blur", "change"]}], year: [{required: true, message: "必填", trigger: ["blur", "change"]}],
configName: [{required: true, message: "必填", trigger: ["blur", "change"]}], configName: [{required: true, message: "必填", trigger: ["blur", "change"]}],
minGroupPeople: [{validator: checkGroupPeople, trigger: ["blur", "change"]}],
maxGroupPeople: [{validator: checkGroupPeople, trigger: ["blur", "change"]}],
signupStartTime: [{validator: checkSignupTime, trigger: ["blur", "change"]}], signupStartTime: [{validator: checkSignupTime, trigger: ["blur", "change"]}],
signupEndTime: [{validator: checkSignupTime, trigger: ["blur", "change"]}], signupEndTime: [{validator: checkSignupTime, trigger: ["blur", "change"]}],
outProvinceRatioType: [{required: true, message: "必填", trigger: ["blur", "change"]}], outProvinceRatioType: [{required: true, message: "必填", trigger: ["blur", "change"]}],
@@ -49,7 +49,7 @@ layout("/layouts/platform.html"){
:data="tableData" :data="tableData"
:size="tableSize" :size="tableSize"
border border
:default-sort="{prop: 'lineName', order: 'ascending'}" :default-sort="{prop: 'lineCode', order: 'ascending'}"
:row-class-name="tableRowClassName" :row-class-name="tableRowClassName"
@sort-change="pageOrder"> @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="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
@@ -624,7 +624,7 @@ layout("/layouts/platform.html"){
pageNumber: 1, pageNumber: 1,
pageSize: 10, pageSize: 10,
totalCount: 0, totalCount: 0,
pageOrderName: "lineName", pageOrderName: "lineCode",
pageOrderBy: "ascending", pageOrderBy: "ascending",
year: currentYear, year: currentYear,
lineName: "", lineName: "",
@@ -29,8 +29,8 @@ layout("/layouts/platform.html"){
<el-option v-for="item in lineTypeOptions" :key="item.lineType" :label="item.lineType" :value="item.lineType"></el-option> <el-option v-for="item in lineTypeOptions" :key="item.lineType" :label="item.lineType" :value="item.lineType"></el-option>
</el-select> </el-select>
</search-item> </search-item>
<search-item label="所属工会"> <search-item label="领办工会">
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%"> <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-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select> </el-select>
</search-item> </search-item>
@@ -61,35 +61,25 @@ layout("/layouts/platform.html"){
<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="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="travelPeriod" width="260" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column v-if="false" label="事项名称" prop="matterName" min-width="200" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column> <el-table-column v-if="false" 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="unionName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="领办状态" prop="undertakeStatusName" width="150" sortable="custom" align="center" header-align="center"> <el-table-column label="领办状态" prop="undertakeStatusName" width="150" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}"> <template slot-scope="{row}">
<el-button size="mini" :type="isClaimed(row) ? 'info' : 'primary'" plain>{{ row.undertakeStatusName || '未领' }}</el-button> <el-button size="mini" :type="isClaimed(row) ? 'info' : 'primary'" plain>{{ row.undertakeStatusName || '未领' }}</el-button>
</template> </template>
</el-table-column> </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="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"> <el-table-column label="成团信息" prop="groupName" width="160" align="center" header-align="center" show-overflow-tooltip>
<template slot-scope="{row}"> <template slot-scope="{row}">
{{ row.minGroupPeople || 0 }}人 <span>{{ formatGroupInfo(row) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="最多成团人数" prop="maxGroupPeople" width="140" sortable="custom" align="center" header-align="center"> <el-table-column label="报名人数" prop="signupCount" width="160" align="center" header-align="center">
<template slot-scope="{row}">
{{ row.maxGroupPeople || 0 }}人
</template>
</el-table-column>
<el-table-column label="报名人数" prop="signupCount" width="160" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}"> <template slot-scope="{row}">
<el-tag size="mini" :type="Number(row.signupCount || 0) > 0 ? 'success' : 'info'"> <el-tag size="mini" :type="Number(row.signupCount || 0) > 0 ? 'success' : 'info'">
{{ row.signupCount || 0 }}人<span v-if="row.showCurrentUnionSignupCount">({{ row.currentUnionSignupCount || 0 }}人)</span> {{ row.signupCount || 0 }}人<span v-if="row.showCurrentUnionSignupCount">({{ row.currentUnionSignupCount || 0 }}人)</span>
</el-tag> </el-tag>
</template> </template>
</el-table-column> </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="220" align="center" header-align="center" fixed="right"> <el-table-column label="操作" width="220" align="center" header-align="center" fixed="right">
<template slot-scope="{row}"> <template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button> <el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
@@ -141,26 +131,21 @@ layout("/layouts/platform.html"){
:size="tableSize" :size="tableSize"
border border
@sort-change="viewPageOrder"> @sort-change="viewPageOrder">
<el-table-column type="expand" width="50">
<template slot-scope="{row}">
<div v-if="row.families && row.families.length > 0" class="tour-family-list">
<el-table :data="row.families" :size="tableSize" border>
<el-table-column label="工号" prop="jobNo" width="130" align="center" header-align="center" show-overflow-tooltip></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="relationship" width="120" 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" align="center" header-align="center" show-overflow-tooltip></el-table-column>
</el-table>
</div>
<div v-else class="tour-family-empty">暂无亲属信息</div>
</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="序号" 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="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="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="性别" prop="gender" width="90" 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="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="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-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="rowTypeName" width="90" align="center" header-align="center"></el-table-column>
<el-table-column label="亲属关系" prop="relationship" width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="成团信息" prop="groupName" width="160" sortable="custom" align="center" header-align="center" show-overflow-tooltip>
<template slot-scope="{row}">
<span>{{ formatGroupInfo(row) }}</span>
</template>
</el-table-column>
<el-table-column label="关联教职工名称" prop="relatedStaffName" width="150" align="center" header-align="center" show-overflow-tooltip></el-table-column>
</el-table> </el-table>
<el-row class="el-pagination-container tour-group-view-pagination"> <el-row class="el-pagination-container tour-group-view-pagination">
<el-pagination <el-pagination
@@ -194,6 +179,7 @@ layout("/layouts/platform.html"){
viewRequestSeq: 0, viewRequestSeq: 0,
viewTitle: "", viewTitle: "",
viewRow: {}, viewRow: {},
viewGroup: {},
viewTableData: [], viewTableData: [],
currentUnionInfo: null, currentUnionInfo: null,
pageForm: { pageForm: {
@@ -213,9 +199,10 @@ layout("/layouts/platform.html"){
pageSize: 10, pageSize: 10,
totalCount: 0, totalCount: 0,
pageOrderName: "signupTime", pageOrderName: "signupTime",
pageOrderBy: "descending", pageOrderBy: "ascending",
keyword: "", keyword: "",
unionId: "" unionId: "",
groupCode: ""
} }
} }
}, },
@@ -233,11 +220,6 @@ layout("/layouts/platform.html"){
this.pageForm.pageNumber = 1 this.pageForm.pageNumber = 1
this.pageData() this.pageData()
}, },
groupStatusType(status) {
if (status === "formed") return "success"
if (status === "over") return "danger"
return "info"
},
tableRowClassName({ row }) { tableRowClassName({ row }) {
return this.isDirectFamilyLine(row) ? "direct-family-row" : "" return this.isDirectFamilyLine(row) ? "direct-family-row" : ""
}, },
@@ -250,17 +232,30 @@ layout("/layouts/platform.html"){
isClaimed(row) { isClaimed(row) {
return row && row.undertakeStatus === "claimed" return row && row.undertakeStatus === "claimed"
}, },
formatGroupInfo(row) {
if (!row || !row.groupCode) return "未成团"
return row.groupName ? row.groupCode + " " + row.groupName : row.groupCode
},
openView(row) { openView(row) {
if (!row || !row.groupCode) {
this.$message.warning("成团信息不完整")
return
}
this.viewRow = row || {} this.viewRow = row || {}
this.viewTitle = this.viewRow.lineName || "报名人员" this.viewGroup = {
groupCode: row.groupCode,
groupName: row.groupName
}
this.viewTitle = (this.viewRow.lineName || "报名人员") + " - " + (this.viewGroup.groupName || this.viewGroup.groupCode)
this.viewVisible = true this.viewVisible = true
this.viewForm.pageNumber = 1 this.viewForm.pageNumber = 1
this.viewForm.pageSize = 10 this.viewForm.pageSize = 10
this.viewForm.totalCount = 0 this.viewForm.totalCount = 0
this.viewForm.pageOrderName = "signupTime" this.viewForm.pageOrderName = "signupTime"
this.viewForm.pageOrderBy = "descending" this.viewForm.pageOrderBy = "ascending"
this.viewForm.keyword = "" this.viewForm.keyword = ""
this.viewForm.unionId = "" this.viewForm.unionId = ""
this.viewForm.groupCode = this.viewGroup.groupCode || ""
this.viewTableData = [] this.viewTableData = []
this.loadViewData() this.loadViewData()
}, },
@@ -278,24 +273,26 @@ layout("/layouts/platform.html"){
}) })
}, },
undertakeLine(row) { undertakeLine(row) {
if (!row || !row.matterId) { if (!row || !row.matterId || !row.groupCode) {
this.$message.warning("线路信息不完整") this.$message.warning("线路成团信息不完整")
return return
} }
if (this.isClaimed(row)) { if (this.isClaimed(row)) {
this.$message.warning("当前线路已领办,不允许修改") this.$message.warning("当前团已领办,不允许修改")
return return
} }
this.loadCurrentUnion().then((union) => { this.loadCurrentUnion().then((union) => {
const lineName = row.lineName || "当前线路" const lineName = row.lineName || "当前线路"
const groupName = this.formatGroupInfo(row)
const unionName = (union && union.unionName) || "" const unionName = (union && union.unionName) || ""
return this.$confirm(lineName + "由" + unionName + "领办,领办后,系统不允许修改,请确认。", "提示", { return this.$confirm(lineName + "【" + groupName + "】由" + unionName + "领办,领办后,系统不允许修改,请确认。", "提示", {
confirmButtonText: "确定", confirmButtonText: "确定",
cancelButtonText: "取消", cancelButtonText: "取消",
type: "warning" type: "warning"
}).then(() => { }).then(() => {
return this.$axios.post(loc() + "/undertakeLine", { return this.$axios.post(loc() + "/undertakeLine", {
matterId: row.matterId matterId: row.matterId,
groupCode: row.groupCode
}) })
}) })
}).then((res) => { }).then((res) => {
@@ -309,22 +306,24 @@ layout("/layouts/platform.html"){
}).catch(() => {}) }).catch(() => {})
}, },
releaseUndertakeLine(row) { releaseUndertakeLine(row) {
if (!row || !row.matterId) { if (!row || !row.matterId || !row.groupCode) {
this.$message.warning("线路信息不完整") this.$message.warning("线路成团信息不完整")
return return
} }
if (!this.isClaimed(row)) { if (!this.isClaimed(row)) {
this.$message.warning("当前线路未领办,无需解除") this.$message.warning("当前团未领办,无需解除")
return return
} }
const lineName = row.lineName || "当前线路" const lineName = row.lineName || "当前线路"
this.$confirm("确认解除【" + lineName + "】的领办状态吗?解除后该线路将变为未领。", "提示", { const groupName = this.formatGroupInfo(row)
this.$confirm("确认解除【" + lineName + "】" + groupName + "的领办状态吗?解除后该团将变为未领。", "提示", {
confirmButtonText: "确定", confirmButtonText: "确定",
cancelButtonText: "取消", cancelButtonText: "取消",
type: "warning" type: "warning"
}).then(() => { }).then(() => {
return this.$axios.post(loc() + "/releaseUndertakeLine", { return this.$axios.post(loc() + "/releaseUndertakeLine", {
matterId: row.matterId matterId: row.matterId,
groupCode: row.groupCode
}) })
}).then((res) => { }).then((res) => {
if (!res) return if (!res) return
@@ -344,6 +343,7 @@ layout("/layouts/platform.html"){
this.viewForm.keyword = "" this.viewForm.keyword = ""
this.viewForm.unionId = "" this.viewForm.unionId = ""
this.viewForm.pageNumber = 1 this.viewForm.pageNumber = 1
this.viewForm.groupCode = this.viewGroup.groupCode || ""
this.loadViewData() this.loadViewData()
}, },
viewIndexMethod(index) { viewIndexMethod(index) {
@@ -360,12 +360,12 @@ layout("/layouts/platform.html"){
}, },
viewPageOrder({ prop, order }) { viewPageOrder({ prop, order }) {
this.viewForm.pageOrderName = prop || "signupTime" this.viewForm.pageOrderName = prop || "signupTime"
this.viewForm.pageOrderBy = order || "descending" this.viewForm.pageOrderBy = order || "ascending"
this.viewForm.pageNumber = 1 this.viewForm.pageNumber = 1
this.loadViewData() this.loadViewData()
}, },
loadViewData() { loadViewData() {
if (!this.viewRow || !this.viewRow.matterId) { if (!this.viewRow || !this.viewRow.matterId || !this.viewForm.groupCode) {
this.viewTableData = [] this.viewTableData = []
this.viewForm.totalCount = 0 this.viewForm.totalCount = 0
return return
@@ -376,6 +376,7 @@ layout("/layouts/platform.html"){
matterId: this.viewRow.matterId, matterId: this.viewRow.matterId,
keyword: this.viewForm.keyword, keyword: this.viewForm.keyword,
unionId: this.viewForm.unionId, unionId: this.viewForm.unionId,
groupCode: this.viewForm.groupCode,
pageNumber: this.viewForm.pageNumber, pageNumber: this.viewForm.pageNumber,
pageSize: this.viewForm.pageSize, pageSize: this.viewForm.pageSize,
pageOrderName: this.viewForm.pageOrderName, pageOrderName: this.viewForm.pageOrderName,
@@ -445,13 +446,6 @@ layout("/layouts/platform.html"){
margin-bottom: 12px; margin-bottom: 12px;
padding-left: 10px; padding-left: 10px;
} }
.tour-family-list {
padding: 8px 32px;
}
.tour-family-empty {
color: #909399;
padding: 8px 32px;
}
.tour-undertake-tip { .tour-undertake-tip {
margin-top: 8px; margin-top: 8px;
color: #f56c6c; color: #f56c6c;
@@ -534,7 +534,7 @@ layout("/layouts/platform_h5.html"){
lineType: "省内线路", lineType: "省内线路",
unionId: "", unionId: "",
directFamilyUnitLine: null, directFamilyUnitLine: null,
pageOrderName: "lineName", pageOrderName: "lineCode",
pageOrderBy: "ascending" pageOrderBy: "ascending"
} }
} }