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 hashedPassword = PwdUtil.getPassword(decodePwd, user.getSalt());
if (!Strings.sNull(hashedPassword).equalsIgnoreCase(user.getPassword())) {
if (Globals.sso) {
// if (Globals.sso) {
throw new BaseException("用户名或者密码不正确");
}
// }
}
user = this.fetchLinks(user, "unit");
if (Lang.isNotEmpty(user.getUnit()) && StrUtil.isNotBlank(user.getUnit().getUnionId())) {
@@ -115,7 +115,7 @@ public class ActivityBasicScopeController {
""");
try {
Cnd cnd = getCnd(pageForm);
cnd.and("u.member", "=", 1);
// cnd.and("u.member", "=", 1);
sql.setCondition(cnd);
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -393,7 +393,7 @@ public class ActivityBasicScopeController {
throw new RuntimeException(e);
}
cnd.and("u.member", "=", 1);
// cnd.and("u.member", "=", 1);
return cnd;
}
@@ -169,16 +169,53 @@ public class TourGroupController {
return Result.success(sql.getList(NutMap.class));
}
/**
* 查询当前事项线路下已经写入疗休养台账的成团信息,供线路成团查看页展示团标签。
*/
@At
@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)) {
return Result.error("参数错误");
}
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("""
SELECT COUNT(1)
FROM tour_ledger t
@@ -196,16 +233,27 @@ public class TourGroupController {
t.id,
t.jobNo,
t.userName,
t.gender,
'' AS mobile,
t.idCard,
t.unionId,
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
INNER JOIN tour_matter m ON m.id = t.matterId
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
ORDER BY $orderColumn $orderBy, t.signupTime DESC, t.createdAt DESC
ORDER BY $orderColumn $orderBy, t.signupTime ASC, t.createdAt ASC
""");
listSql.setCondition(cnd);
listSql.setVar("orderColumn", getSignupOrderColumn(pageForm.getPageOrderName()));
@@ -216,11 +264,30 @@ public class TourGroupController {
List<NutMap> list = listSql.getList(NutMap.class);
fillSignupMobile(list);
fillSignupFamilies(list);
flattenSignupFamilies(list);
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list);
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
@Ok("void")
@SaCheckPermission("tour.group")
@@ -238,7 +305,7 @@ public class TourGroupController {
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.and("t.delFlag", "=", false);
cnd.and("t.matterId", "=", matterId);
@@ -255,6 +322,8 @@ public class TourGroupController {
cnd.and(keywordGroup);
}
cnd.andEX("t.unionId", "=", unionId);
// 团标签联动查询时只筛选当前团;未选择团标签时默认查询全部报名人员。
cnd.andEX("t.groupCode", "=", groupCode);
applyMatterDataScope(cnd);
return cnd;
}
@@ -297,7 +366,7 @@ public class TourGroupController {
}
private List<NutMap> queryExportParticipants(String matterId) {
Cnd cnd = buildSignupQueryCnd(matterId, null, null);
Cnd cnd = buildSignupQueryCnd(matterId, null, null, null);
Sql sql = Sqls.create("""
SELECT
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()) {
return;
}
@@ -535,8 +604,9 @@ public class TourGroupController {
Sql sql = Sqls.create("""
SELECT
ledgerId,
staffJobNo AS jobNo,
'' AS jobNo,
familyName AS userName,
gender,
'' AS mobile,
idCard,
relationship
@@ -551,11 +621,35 @@ public class TourGroupController {
Map<String, List<NutMap>> familyMap = new HashMap<>();
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);
}
List<NutMap> flatList = new ArrayList<>();
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) {
@@ -662,12 +756,21 @@ public class TourGroupController {
if ("userName".equals(orderName)) {
return "t.userName";
}
if ("gender".equals(orderName)) {
return "t.gender";
}
if ("idCard".equals(orderName)) {
return "t.idCard";
}
if ("unionName".equals(orderName)) {
return "t.unionName";
}
if ("groupCode".equals(orderName) || "groupName".equals(orderName)) {
return "t.groupCode";
}
if ("totalPeople".equals(orderName)) {
return "totalPeople";
}
return "t.signupTime";
}
}
@@ -1,5 +1,9 @@
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.hutool.core.util.StrUtil;
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.param.PageForm;
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.services.SysDictService;
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.mvc.annotation.At;
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.List;
import java.util.stream.Collectors;
@@ -36,8 +44,6 @@ import java.util.stream.Collectors;
@At("/platform/tour/route")
public class TourLineController {
private static final String SORT_CODE_PATTERN = "^\\d+$";
@Inject
private TourLineService tourLineService;
@@ -52,7 +58,7 @@ public class TourLineController {
@At
@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())) {
pageForm.defaultSortAsc("lineCode");
}
@@ -62,6 +68,8 @@ public class TourLineController {
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);
@@ -107,6 +115,78 @@ public class TourLineController {
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
@SaCheckPermission("tour.route")
public Result detail(String id) {
@@ -253,11 +333,11 @@ public class TourLineController {
if (StrUtil.isBlank(line.getLineName())) {
return Result.error("线路名称不能为空");
}
if (StrUtil.isBlank(line.getLineCode())) {
if (line.getLineCode() == null) {
return Result.error("排序编号不能为空");
}
if (!line.getLineCode().matches(SORT_CODE_PATTERN)) {
return Result.error("排序编号必须为整数");
if (line.getLineCode() < 0) {
return Result.error("排序编号不能小于0");
}
if (StrUtil.isBlank(line.getLineType())) {
return Result.error("线路类型不能为空");
@@ -1,12 +1,17 @@
package com.budwk.app.zhgh.dayofficework.tour.controller;
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 com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.sys.models.Sys_dict;
import com.budwk.app.sys.models.Sys_union;
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.mvc.annotation.At;
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.List;
import java.util.stream.Collectors;
@@ -53,18 +61,13 @@ public class TourMatterController {
@At
@SaCheckPermission("tour.matter")
public Result pageData(PageForm pageForm, Integer year, String matterName, String unionId, String organizationType) {
Cnd cnd = Cnd.NEW();
cnd.and("m.delFlag", "=", false);
cnd.andEX("m.`year`", "=", year);
cnd.and(Cnd.likeEX("m.matterName", matterName));
cnd.andEX("m.unionId", "=", unionId);
cnd.andEX("m.organizationType", "=", organizationType);
applyMatterDataScope(cnd);
public Result pageData(PageForm pageForm, Integer year, String matterName, String unionId, String organizationType, String lineType) {
Cnd cnd = buildMatterPageCnd(year, matterName, unionId, organizationType, lineType);
Sql countSql = Sqls.create("""
SELECT COUNT(1)
FROM tour_matter m
LEFT JOIN tour_line l ON l.id = m.lineId
$condition
""");
countSql.setCondition(cnd);
@@ -91,7 +94,7 @@ public class TourMatterController {
LEFT JOIN sys_dict d ON d.`code` = m.organizationType
AND d.parentId = (SELECT id FROM sys_dict WHERE `code` = 'organizationType' LIMIT 1)
$condition
ORDER BY $orderColumn $orderBy, m.`year` DESC, m.createdAt DESC
ORDER BY $orderColumn $orderBy, CAST(l.lineCode AS UNSIGNED) ASC, m.`year` DESC, m.createdAt DESC
""");
listSql.setCondition(cnd);
listSql.setVar("orderColumn", getOrderColumn(pageForm.getPageOrderName()));
@@ -104,6 +107,109 @@ public class TourMatterController {
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
@SaCheckPermission("tour.matter")
public Result detail(String id) {
@@ -142,17 +248,20 @@ public class TourMatterController {
cnd.and("l.enabled", "=", true);
cnd.and("l.openFlag", "=", true);
// 事项选择线路下拉需要展示线路类型和旅行社,因此这里补充旅行社名称返回给前端拼接显示。
Sql sql = Sqls.create("""
SELECT
l.*,
a.agencyName AS travelAgencyName,
lot.lotName AS lotName,
lot.lotValue AS lotValue,
lot.lotValue AS lotDays,
lot.activityCost AS activityCost
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
$condition
ORDER BY l.lineCode ASC, l.lineName ASC
ORDER BY CAST(l.lineCode AS UNSIGNED) ASC, l.lineName ASC
""");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.maps());
@@ -199,6 +308,17 @@ public class TourMatterController {
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
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tour.matter")
@@ -348,16 +468,14 @@ public class TourMatterController {
if (setting == null) {
return;
}
if (setting.getMinGroupPeople() != null) {
// 配置只作为弹窗缺省值,不能覆盖用户在选择线路页面已经提交的成团人数和报名时间。
if (matter.getMinGroupPeople() == null && setting.getMinGroupPeople() != null) {
matter.setMinGroupPeople(setting.getMinGroupPeople());
}
if (setting.getMaxGroupPeople() != null) {
matter.setMaxGroupPeople(setting.getMaxGroupPeople());
}
if (StrUtil.isNotBlank(setting.getSignupStartTime())) {
if (StrUtil.isBlank(matter.getSignupStartTime()) && StrUtil.isNotBlank(setting.getSignupStartTime())) {
matter.setSignupStartTime(setting.getSignupStartTime());
}
if (StrUtil.isNotBlank(setting.getSignupEndTime())) {
if (StrUtil.isBlank(matter.getSignupEndTime()) && StrUtil.isNotBlank(setting.getSignupEndTime())) {
matter.setSignupEndTime(setting.getSignupEndTime());
}
}
@@ -429,12 +547,6 @@ public class TourMatterController {
if (matter.getMinGroupPeople() == null || matter.getMinGroupPeople() <= 0) {
return Result.error("最少成团人数必须大于0");
}
if (matter.getMaxGroupPeople() == null || matter.getMaxGroupPeople() <= 0) {
return Result.error("最多成团人数必须大于0");
}
if (matter.getMinGroupPeople() > matter.getMaxGroupPeople()) {
return Result.error("最少成团人数不能大于最多成团人数");
}
if (matter.getEstimatedCost() == null || matter.getEstimatedCost().signum() < 0) {
return Result.error("预计费用不能小于0");
}
@@ -462,6 +574,22 @@ public class TourMatterController {
.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 查看全部;分工会主席查看本工会事项;其它角色仅查看自己创建的事项。
*/
@@ -512,6 +640,10 @@ public class TourMatterController {
if ("lineName".equals(orderName)) {
return "l.lineName";
}
if ("lineCode".equals(orderName)) {
// 线路序号按数字排序,避免 10 排在 2 前面。
return "CAST(l.lineCode AS UNSIGNED)";
}
if ("settingName".equals(orderName)) {
return "s.configName";
}
@@ -521,6 +653,6 @@ public class TourMatterController {
if ("enabled".equals(orderName)) {
return "m.enabled";
}
return "m.`year`";
return "CAST(l.lineCode AS UNSIGNED)";
}
}
@@ -444,10 +444,6 @@ public class TourMySignupController {
if (ruleResult != null) {
return ruleResult;
}
Result maxPeopleResult = checkMaxGroupPeople(matter, oldLedger, familyList);
if (maxPeopleResult != null) {
return maxPeopleResult;
}
TourLine line = tourLedgerService.dao().fetch(TourLine.class, matter.getLineId());
boolean directFamilyLine = line != null && Boolean.TRUE.equals(line.getDirectFamilyUnitLine());
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]$");
}
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) {
String selectedLineName = getLedgerLineName(ledger);
return "您已经选择【" + selectedLineName + "】,取消后再重新选择。";
@@ -205,10 +205,6 @@ public class TourSettingController {
if (StrUtil.isBlank(tourSetting.getConfigName())) {
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())) {
return Result.error("报名时间不能为空");
}
@@ -136,7 +136,7 @@ public class TourSignupController {
@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) {
if (StrUtil.isBlank(pageForm.getPageOrderName())) {
pageForm.defaultSortAsc("lineName");
pageForm.defaultSortAsc("lineCode");
}
Cnd cnd = buildQueryCnd(year, lineName, lineType, unionId);
cnd.andEX("l.directFamilyUnitLine", "=", directFamilyUnitLine);
@@ -222,7 +222,7 @@ public class TourSignupController {
GROUP BY t.matterId, t.jobNo
) my ON my.matterId = m.id
$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.setParam("currentJobNo", currentJobNo);
@@ -241,7 +241,7 @@ public class TourSignupController {
@SaCheckPermission(value = {"tour.signup", "h5.tour.signup"}, mode = SaMode.OR)
public Result pageData(PageForm pageForm, Integer year, String lineName, String lineType, String unionId) {
if (StrUtil.isBlank(pageForm.getPageOrderName())) {
pageForm.defaultSortAsc("lineName");
pageForm.defaultSortAsc("lineCode");
}
Cnd cnd = buildQueryCnd(year, lineName, lineType, unionId);
String currentJobNo = currentJobNo();
@@ -325,7 +325,7 @@ public class TourSignupController {
GROUP BY t.matterId, t.jobNo
) my ON my.matterId = m.id
$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.setParam("currentJobNo", currentJobNo);
@@ -751,10 +751,6 @@ public class TourSignupController {
update = true;
}
}
Result maxPeopleResult = checkMaxGroupPeople(matter, oldLedger, familyList);
if (maxPeopleResult != null) {
return maxPeopleResult;
}
TourLine line = tourLedgerService.dao().fetch(TourLine.class, matter.getLineId());
boolean directFamilyLine = line != null && Boolean.TRUE.equals(line.getDirectFamilyUnitLine());
TourSetting setting = tourLedgerService.dao().fetch(TourSetting.class, matter.getSettingId());
@@ -962,20 +958,6 @@ public class TourSignupController {
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) {
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]$");
@@ -1252,32 +1234,6 @@ public class TourSignupController {
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) {
String selectedLineName = getLedgerLineName(ledger);
return "您已经选择【" + selectedLineName + "】,取消后再重新选择。";
@@ -1537,6 +1493,10 @@ public class TourSignupController {
}
private String getOrderColumn(String orderName) {
if ("lineCode".equals(orderName)) {
// 线路序号按数字排序,避免字符串排序导致 10 排在 2 前面。
return "CAST(l.lineCode AS UNSIGNED)";
}
if ("lineName".equals(orderName)) {
return "l.lineName";
}
@@ -1561,6 +1521,6 @@ public class TourSignupController {
if ("signupCount".equals(orderName)) {
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.web.commons.auth.utils.AuthUtil;
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.TourMatterService;
import org.nutz.dao.Cnd;
@@ -61,7 +60,17 @@ public class TourUndertakeController {
SELECT COUNT(1)
FROM tour_matter m
INNER JOIN tour_line l ON l.id = m.lineId
LEFT JOIN sys_union u ON u.id = m.unionId
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
""");
countSql.setCondition(cnd);
@@ -74,14 +83,14 @@ public class TourUndertakeController {
m.id AS matterId,
m.`year`,
m.matterName,
m.unionId,
gs.undertakeUnionId AS unionId,
COALESCE(NULLIF(gs.undertakeUnionName, ''), uu.name, '') AS unionName,
CASE
WHEN IFNULL(NULLIF(m.undertakeStatus, ''), 'unclaimed') = 'claimed' THEN COALESCE(u.name, '校工会')
ELSE ''
END AS unionName,
IFNULL(NULLIF(m.undertakeStatus, ''), 'unclaimed') AS undertakeStatus,
WHEN IFNULL(NULLIF(gs.undertakeUnionId, ''), '') <> '' THEN 'claimed'
ELSE 'unclaimed'
END AS undertakeStatus,
CASE
WHEN IFNULL(NULLIF(m.undertakeStatus, ''), 'unclaimed') = 'claimed' THEN '已领'
WHEN IFNULL(NULLIF(gs.undertakeUnionId, ''), '') <> '' THEN '已领'
ELSE '未领'
END AS undertakeStatusName,
m.lineId,
@@ -95,25 +104,20 @@ public class TourUndertakeController {
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
ELSE ''
END AS travelPeriod,
m.minGroupPeople,
m.maxGroupPeople,
IFNULL(sc.signupCount, 0) AS signupCount,
IFNULL(usc.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
gs.groupCode,
gs.groupName,
gs.signupCount,
IFNULL(ugs.currentUnionSignupCount, 0) AS currentUnionSignupCount
FROM tour_matter m
INNER JOIN tour_line l ON l.id = m.lineId
LEFT JOIN sys_union u ON u.id = m.unionId
LEFT JOIN (
SELECT t.matterId, SUM(1 + IFNULL(f.familyCount, 0)) AS signupCount
INNER JOIN (
SELECT
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
LEFT JOIN (
SELECT ledgerId, COUNT(1) AS familyCount
@@ -122,12 +126,16 @@ public class TourUndertakeController {
GROUP BY ledgerId
) f ON f.ledgerId = t.id
WHERE t.delFlag = 0
AND t.matterId IS NOT NULL
AND t.matterId <> ''
GROUP BY t.matterId
) sc ON sc.matterId = m.id
AND t.groupCode IS NOT NULL
AND t.groupCode <> ''
GROUP BY t.matterId, t.groupCode
) gs ON gs.matterId = m.id
LEFT JOIN sys_union uu ON uu.id = gs.undertakeUnionId
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
LEFT JOIN (
SELECT ledgerId, COUNT(1) AS familyCount
@@ -136,13 +144,13 @@ public class TourUndertakeController {
GROUP BY ledgerId
) f ON f.ledgerId = t.id
WHERE t.delFlag = 0
AND t.matterId IS NOT NULL
AND t.matterId <> ''
AND t.groupCode IS NOT NULL
AND t.groupCode <> ''
AND t.unionId = @currentUnionId
GROUP BY t.matterId
) usc ON usc.matterId = m.id
GROUP BY t.matterId, t.groupCode
) ugs ON ugs.matterId = m.id AND ugs.groupCode = gs.groupCode
$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.setParam("currentUnionId", currentUnionId);
@@ -180,49 +188,35 @@ public class TourUndertakeController {
return Result.success(unionInfo);
}
/**
* 领办当前成团行,接口必须同时接收事项ID和成团编码,避免按事项误领办全部团。
*/
@At
@SaCheckPermission("tour.undertake")
public Result undertakeLine(String matterId) {
if (StrUtil.isBlank(matterId)) {
public Result undertakeLine(String matterId, String groupCode) {
if (StrUtil.isBlank(matterId) || StrUtil.isBlank(groupCode)) {
return Result.error("参数错误");
}
NutMap unionInfo = currentUnionInfo();
if (unionInfo.isEmpty()) {
return Result.error("未获取到当前登录人工会");
}
TourMatter matter = tourMatterService.fetch(matterId);
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);
return tourLedgerService.undertakeGroup(matterId, groupCode, unionInfo.getString("unionId"), unionInfo.getString("unionName"));
}
/**
* 解除当前成团行的领办信息,清空范围只限定到当前事项的当前团。
*/
@At
@SaCheckPermission("tour.undertake")
public Result releaseUndertakeLine(String matterId) {
public Result releaseUndertakeLine(String matterId, String groupCode) {
if (!AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) {
return Result.error("仅系统管理员可以解除领办");
}
if (StrUtil.isBlank(matterId)) {
if (StrUtil.isBlank(matterId) || StrUtil.isBlank(groupCode)) {
return Result.error("参数错误");
}
TourMatter matter = tourMatterService.fetch(matterId);
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();
return tourLedgerService.releaseUndertakeGroup(matterId, groupCode);
}
@At
@@ -259,14 +253,14 @@ public class TourUndertakeController {
@At
@SaCheckPermission("tour.undertake")
public Result signupPageData(PageForm pageForm, String matterId, String keyword, String unionId) {
if (StrUtil.isBlank(matterId)) {
public Result signupPageData(PageForm pageForm, String matterId, String keyword, String unionId, String groupCode) {
if (StrUtil.isBlank(matterId) || StrUtil.isBlank(groupCode)) {
return Result.error("参数错误");
}
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("""
SELECT COUNT(1)
FROM tour_ledger t
@@ -284,16 +278,19 @@ public class TourUndertakeController {
t.id,
t.jobNo,
t.userName,
t.gender,
'' AS mobile,
t.idCard,
t.unionId,
t.unionName,
t.signupTime
t.signupTime,
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 $orderColumn $orderBy, t.signupTime DESC, t.createdAt DESC
ORDER BY $orderColumn $orderBy, t.signupTime ASC, t.createdAt ASC
""");
listSql.setCondition(cnd);
listSql.setVar("orderColumn", getSignupOrderColumn(pageForm.getPageOrderName()));
@@ -304,15 +301,19 @@ public class TourUndertakeController {
List<NutMap> list = listSql.getList(NutMap.class);
fillSignupMobile(list);
fillSignupFamilies(list);
flattenSignupFamilies(list);
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list);
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.and("t.delFlag", "=", false);
cnd.and("t.matterId", "=", matterId);
cnd.and("t.groupCode", "=", groupCode);
cnd.and("m.delFlag", "=", false);
cnd.and("m.enabled", "=", true);
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()) {
return;
}
@@ -380,8 +381,9 @@ public class TourUndertakeController {
Sql sql = Sqls.create("""
SELECT
ledgerId,
staffJobNo AS jobNo,
'' AS jobNo,
familyName AS userName,
gender,
'' AS mobile,
idCard,
relationship
@@ -396,11 +398,29 @@ public class TourUndertakeController {
Map<String, List<NutMap>> familyMap = new HashMap<>();
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);
}
List<NutMap> flatList = new ArrayList<>();
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) {
@@ -414,14 +434,14 @@ public class TourUndertakeController {
cnd.andEX("m.`year`", "=", year == null ? LocalDate.now().getYear() : year);
cnd.and(Cnd.likeEX("l.lineName", lineName));
cnd.andEX("l.lineType", "=", lineType);
cnd.andEX("m.unionId", "=", unionId);
cnd.andEX("gs.undertakeUnionId", "=", unionId);
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)) {
SqlExpressionGroup statusGroup = new SqlExpressionGroup();
statusGroup.or("m.undertakeStatus", "=", "unclaimed");
statusGroup.or("m.undertakeStatus", "IS", null);
statusGroup.or("m.undertakeStatus", "=", "");
statusGroup.or("gs.undertakeUnionId", "IS", null);
statusGroup.or("gs.undertakeUnionId", "=", "");
cnd.and(statusGroup);
}
applyMatterDataScope(cnd);
@@ -489,7 +509,7 @@ public class TourUndertakeController {
return "m.matterName";
}
if ("unionName".equals(orderName)) {
return "u.name";
return "unionName";
}
if ("undertakeStatusName".equals(orderName)) {
return "undertakeStatus";
@@ -498,16 +518,10 @@ public class TourUndertakeController {
return "l.lineType";
}
if ("signupCount".equals(orderName)) {
return "signupCount";
return "gs.signupCount";
}
if ("minGroupPeople".equals(orderName)) {
return "m.minGroupPeople";
}
if ("maxGroupPeople".equals(orderName)) {
return "m.maxGroupPeople";
}
if ("groupStatusName".equals(orderName)) {
return "groupStatus";
if ("groupName".equals(orderName) || "groupCode".equals(orderName)) {
return "gs.groupCode";
}
return "l.lineName";
}
@@ -519,12 +533,18 @@ public class TourUndertakeController {
if ("userName".equals(orderName)) {
return "t.userName";
}
if ("gender".equals(orderName)) {
return "t.gender";
}
if ("idCard".equals(orderName)) {
return "t.idCard";
}
if ("unionName".equals(orderName)) {
return "t.unionName";
}
if ("groupName".equals(orderName)) {
return "t.groupCode";
}
return "t.signupTime";
}
}
@@ -101,6 +101,32 @@ public class TourLedger extends BaseModel implements Serializable {
@ColDefine(type = ColType.VARCHAR, width = 50)
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
@Comment("报名酒店")
@ColDefine(type = ColType.VARCHAR, width = 100)
@@ -33,8 +33,8 @@ public class TourLine extends BaseModel implements Serializable {
@Column
@Comment("排序编号")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String lineCode;
@ColDefine(type = ColType.INT, width = 11)
private Integer lineCode;
@Column
@Comment("线路名称")
@@ -1,7 +1,58 @@
package com.budwk.app.zhgh.dayofficework.tour.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.base.result.Result;
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> {
/**
* 按查询条件获取疗休养台账中已分配的成团编码和成团名称,返回值已按成团信息去重。
*
* @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.zhgh.dayofficework.tour.models.TourMatter;
import java.util.List;
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;
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.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.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.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.lang.util.NutMap;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@IocBean(args = {"refer:dao"})
public class TourLedgerServiceImpl extends BaseServiceImpl<TourLedger> implements TourLedgerService {
@@ -12,4 +30,577 @@ public class TourLedgerServiceImpl extends BaseServiceImpl<TourLedger> implement
public TourLedgerServiceImpl(Dao 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.zhgh.dayofficework.tour.models.TourMatter;
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.ioc.loader.annotation.IocBean;
import java.util.List;
@IocBean(args = {"refer:dao"})
public class TourMatterServiceImpl extends BaseServiceImpl<TourMatter> implements TourMatterService {
public TourMatterServiceImpl(Dao 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="travelPeriod" width="260" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="事项名称" prop="matterName" min-width="200" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所属工会" prop="unionName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column 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="minGroupPeople" width="140" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
{{ row.minGroupPeople || 0 }}人
</template>
</el-table-column>
<el-table-column label="最多成团人数" prop="maxGroupPeople" width="140" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
{{ row.maxGroupPeople || 0 }}人
</template>
</el-table-column>
<el-table-column label="报名人数" prop="signupCount" width="120" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="Number(row.signupCount || 0) > 0 ? 'success' : 'info'">{{ row.signupCount || 0 }}人</el-tag>
</template>
</el-table-column>
<el-table-column label="成团状态" prop="groupStatusName" width="120" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="groupStatusType(row.groupStatus)">{{ row.groupStatusName || '未成团' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="160" align="center" header-align="center" fixed="right">
<template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
<el-button size="mini" type="primary" @click="openView(row)">组团</el-button>
<el-button size="mini" type="primary" @click="exportLine(row)">导出</el-button>
</template>
</el-table-column>
@@ -120,33 +105,48 @@ layout("/layouts/platform.html"){
</el-card>
<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
ref="viewTable"
v-loading="viewLoading"
:data="viewTableData"
:size="tableSize"
border
@selection-change="viewSelectionChange"
@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 type="selection" width="50" align="center" header-align="center" :selectable="isViewRowSelectable"></el-table-column>
<el-table-column label="序号" type="index" :index="viewIndexMethod" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="性别" prop="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="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="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-row class="el-pagination-container tour-group-view-pagination">
<el-pagination
@@ -163,6 +163,31 @@ layout("/layouts/platform.html"){
</el-card>
</div>
</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>
</div>
@@ -181,6 +206,15 @@ layout("/layouts/platform.html"){
viewTitle: "",
viewRow: {},
viewTableData: [],
viewGroupOptions: [],
viewSelection: [],
syncingFamilySelection: false,
assignGroupLoading: false,
adjustVisible: false,
adjustLoading: false,
adjustForm: {
groupCode: ""
},
pageForm: {
pageNumber: 1,
pageSize: 10,
@@ -196,10 +230,11 @@ layout("/layouts/platform.html"){
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "signupTime",
pageOrderBy: "descending",
pageOrderName: "groupCode",
pageOrderBy: "ascending",
keyword: "",
unionId: ""
unionId: "",
groupCode: ""
}
}
},
@@ -226,6 +261,14 @@ layout("/layouts/platform.html"){
|| 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) {
this.viewRow = row || {}
this.viewTitle = this.viewRow.lineName || "报名人员"
@@ -233,11 +276,17 @@ layout("/layouts/platform.html"){
this.viewForm.pageNumber = 1
this.viewForm.pageSize = 10
this.viewForm.totalCount = 0
this.viewForm.pageOrderName = "signupTime"
this.viewForm.pageOrderBy = "descending"
this.viewForm.pageOrderName = "groupCode"
this.viewForm.pageOrderBy = "ascending"
this.viewForm.keyword = ""
this.viewForm.unionId = ""
this.viewForm.groupCode = ""
this.viewTableData = []
this.viewGroupOptions = []
this.viewSelection = []
this.assignGroupLoading = false
this.resetAdjustForm()
this.loadViewGroupOptions()
this.loadViewData()
},
viewSearch() {
@@ -247,12 +296,118 @@ layout("/layouts/platform.html"){
viewReset() {
this.viewForm.keyword = ""
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.loadViewData()
},
viewIndexMethod(index) {
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) {
this.viewForm.pageSize = size
this.viewForm.pageNumber = 1
@@ -271,6 +426,7 @@ layout("/layouts/platform.html"){
loadViewData() {
if (!this.viewRow || !this.viewRow.matterId) {
this.viewTableData = []
this.viewSelection = []
this.viewForm.totalCount = 0
return
}
@@ -280,6 +436,7 @@ layout("/layouts/platform.html"){
matterId: this.viewRow.matterId,
keyword: this.viewForm.keyword,
unionId: this.viewForm.unionId,
groupCode: this.viewForm.groupCode,
pageNumber: this.viewForm.pageNumber,
pageSize: this.viewForm.pageSize,
pageOrderName: this.viewForm.pageOrderName,
@@ -289,6 +446,12 @@ layout("/layouts/platform.html"){
if (res.code === 0) {
const data = res.data || {}
this.viewTableData = data.list || []
this.viewSelection = []
this.$nextTick(() => {
if (this.$refs.viewTable) {
this.$refs.viewTable.clearSelection()
}
})
this.viewForm.totalCount = data.totalCount || 0
} else {
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) {
if (!row || !row.matterId) {
this.$message.warning("线路信息不完整")
@@ -347,21 +525,52 @@ layout("/layouts/platform.html"){
margin-top: 12px;
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 {
border-left: 4px solid #0079c2;
color: #0079c2;
font-size: 14px;
font-weight: 600;
line-height: 16px;
margin-bottom: 12px;
padding-left: 10px;
white-space: nowrap;
}
.tour-family-list {
padding: 8px 32px;
.tour-group-options {
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 {
color: #909399;
padding: 8px 32px;
.tour-group-actions {
display: flex;
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 .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="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="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="familyCount" width="120" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
@@ -380,6 +386,10 @@ layout("/layouts/platform.html"){
}
},
methods: {
formatGroupInfo(row) {
if (!row || !row.groupCode) return ""
return row.groupName ? row.groupCode + " " + row.groupName : row.groupCode
},
toBoolean(value) {
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-select>
</search-item>
<search-item label="线路类型">
<el-select v-model="pageForm.lineType" clearable filterable placeholder="请选择线路类型" style="width: 100%">
<el-option v-for="item in lineTypeOptions" :key="item.code" :label="item.name" :value="item.name"></el-option>
</el-select>
</search-item>
<div class="search-query" style="margin-left: auto;">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
@@ -43,10 +48,33 @@ layout("/layouts/platform.html"){
<el-card class="mt10" shadow="never">
<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">
<i class="el-icon-plus"></i>
新增
</el-button>
<el-button @click="exportData" size="medium" type="primary" icon="el-icon-download">导出</el-button>
</table-tool>
<el-table
@@ -54,13 +82,24 @@ layout("/layouts/platform.html"){
:data="tableData"
:size="tableSize"
border
:default-sort="{prop: 'lineCode', order: 'ascending'}"
:row-class-name="tableRowClassName"
@sort-change="pageOrder">
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="年度" prop="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="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="报名时间" 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="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>
@@ -201,7 +240,7 @@ layout("/layouts/platform.html"){
</el-col>
<el-col :span="8">
<el-form-item label="最多成团人数" prop="maxGroupPeople">
<el-input-number v-model="batchForm.maxGroupPeople" :min="1" :precision="0" controls-position="right" style="width: 100%" @change="peopleChange"></el-input-number>
<el-input-number v-model="batchForm.maxGroupPeople" :precision="0" controls-position="right" style="width: 100%" @change="peopleChange"></el-input-number>
</el-form-item>
</el-col>
</el-row>
@@ -218,6 +257,15 @@ layout("/layouts/platform.html"){
.el-table .direct-family-row > td .cell {
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>
<script nonce="${cspNonce!}">
@@ -257,9 +305,6 @@ layout("/layouts/platform.html"){
const validatePeople = (rule, value, callback) => {
if (!value || value <= 0) {
callback(new Error("人数必须大于0"))
} else if (this.batchForm.minGroupPeople && this.batchForm.maxGroupPeople
&& this.batchForm.minGroupPeople > this.batchForm.maxGroupPeople) {
callback(new Error("最少人数不能大于最多人数"))
} else {
callback()
}
@@ -292,9 +337,15 @@ layout("/layouts/platform.html"){
batchFormVisible: false,
batchSubDis: false,
batchTitle: "",
batchSignupTimeLoading: false,
batchSignupTimeForm: {
signupStartTime: "",
signupEndTime: ""
},
currentMatter: {},
batchForm: {},
lineOptions: [],
lineTypeOptions: [],
settingOptions: [],
unionOptions: [],
organizationTypeOptions: [],
@@ -302,10 +353,13 @@ layout("/layouts/platform.html"){
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "lineCode",
pageOrderBy: "ascending",
year: currentYear,
matterName: "",
unionId: "",
organizationType: ""
organizationType: "",
lineType: ""
},
formData: {},
formRules: {
@@ -324,7 +378,6 @@ layout("/layouts/platform.html"){
contactName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
contactPhone: [{ validator: validateMobile, trigger: ["blur", "change"] }],
minGroupPeople: [{ validator: validatePeople, trigger: ["blur", "change"] }],
maxGroupPeople: [{ validator: validatePeople, trigger: ["blur", "change"] }],
estimatedCost: [{ validator: validateMoney, trigger: ["blur", "change"] }]
}
}
@@ -355,9 +408,65 @@ layout("/layouts/platform.html"){
this.pageForm.matterName = ""
this.pageForm.unionId = ""
this.pageForm.organizationType = ""
this.pageForm.lineType = ""
this.loadSettings(this.pageForm.year)
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 }) {
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() {
return {
id: this.currentMatter.id || "",
@@ -530,7 +646,9 @@ layout("/layouts/platform.html"){
if (!item) return ""
const lineName = this.lineField(item, "lineName") || ""
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() {
this.applySelectedLineDefaults(true)
@@ -589,10 +707,12 @@ layout("/layouts/platform.html"){
},
applySettingSignupTime(data) {
if (!data) return
if (data.signupStartTime) {
// 已保存线路配置时保留事项自身报名时间,避免配置默认值覆盖用户修改。
const hasSavedLineConfig = !!this.batchForm.lineId
if (!hasSavedLineConfig && data.signupStartTime) {
this.$set(this.batchForm, "signupStartTime", data.signupStartTime)
}
if (data.signupEndTime) {
if (!hasSavedLineConfig && data.signupEndTime) {
this.$set(this.batchForm, "signupEndTime", data.signupEndTime)
}
this.$nextTick(() => {
@@ -603,10 +723,12 @@ layout("/layouts/platform.html"){
},
applySettingPeople(data) {
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)
}
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.$nextTick(() => {
@@ -731,6 +853,7 @@ layout("/layouts/platform.html"){
this.loadSettings(this.pageForm.year)
this.loadUnions()
this.loadOrganizationTypes()
this.loadLineTypes()
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-select>
</search-item>
<search-item label="线路类型">
<el-select v-model="pageForm.lineType" clearable filterable placeholder="请选择线路类型" style="width: 100%">
<el-option v-for="item in lineTypeOptions" :key="item.code" :label="item.name" :value="item.name"></el-option>
</el-select>
</search-item>
<div class="search-query" style="margin-left: auto;">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
@@ -42,6 +47,7 @@ layout("/layouts/platform.html"){
<i class="el-icon-plus"></i>
新增
</el-button>
<el-button @click="exportData" size="medium" type="primary" icon="el-icon-download">导出</el-button>
</table-tool>
<el-table
@@ -323,7 +329,7 @@ layout("/layouts/platform.html"){
</el-col>
<el-col :span="8">
<el-form-item label="最多成团人数" prop="maxGroupPeople">
<el-input-number v-model="batchForm.maxGroupPeople" :min="1" :precision="0" controls-position="right" style="width: 100%" @change="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-col>
</el-row>
@@ -386,9 +392,6 @@ layout("/layouts/platform.html"){
const validateMatterPeople = (rule, value, callback) => {
if (!value || value <= 0) {
callback(new Error("人数必须大于0"))
} else if (this.batchForm.minGroupPeople && this.batchForm.maxGroupPeople
&& this.batchForm.minGroupPeople > this.batchForm.maxGroupPeople) {
callback(new Error("最少人数不能大于最多人数"))
} else {
callback()
}
@@ -438,7 +441,8 @@ layout("/layouts/platform.html"){
pageOrderBy: "ascending",
year: currentYear,
lineName: "",
lotId: ""
lotId: "",
lineType: ""
},
formData: {},
matterForm: {},
@@ -468,7 +472,6 @@ layout("/layouts/platform.html"){
contactName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
contactPhone: [{ validator: validateMatterMobile, trigger: ["blur", "change"] }],
minGroupPeople: [{ validator: validateMatterPeople, trigger: ["blur", "change"] }],
maxGroupPeople: [{ validator: validateMatterPeople, 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.lineName = ""
this.pageForm.lotId = ""
this.pageForm.lineType = ""
this.loadOptions(this.pageForm.year)
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 }) {
return this.isDirectFamilyLine(row) ? "direct-family-row" : ""
},
@@ -767,10 +781,11 @@ layout("/layouts/platform.html"){
},
applyMatterSettingPeople(data) {
if (!data) return
const hasSavedLineConfig = !!this.batchForm.lineId
if (data.minGroupPeople !== null && data.minGroupPeople !== undefined && 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.$nextTick(() => {
@@ -313,15 +313,6 @@ layout("/layouts/platform.html"){
el: "#app",
mixins: [initTableMixins],
data() {
// 前端先做成团人数区间校验,后端也会再兜底校验一次。
const checkGroupPeople = (rule, value, callback) => {
if (this.formData.minGroupPeople !== null && this.formData.maxGroupPeople !== null
&& this.formData.minGroupPeople > this.formData.maxGroupPeople) {
callback(new Error("最少成团人数不能大于最多成团人数"))
} else {
callback()
}
}
const checkSignupTime = (rule, value, callback) => {
if (!value) {
callback(new Error("必填"))
@@ -369,8 +360,6 @@ layout("/layouts/platform.html"){
formRules: {
year: [{required: true, message: "必填", trigger: ["blur", "change"]}],
configName: [{required: true, message: "必填", trigger: ["blur", "change"]}],
minGroupPeople: [{validator: checkGroupPeople, trigger: ["blur", "change"]}],
maxGroupPeople: [{validator: checkGroupPeople, trigger: ["blur", "change"]}],
signupStartTime: [{validator: checkSignupTime, trigger: ["blur", "change"]}],
signupEndTime: [{validator: checkSignupTime, trigger: ["blur", "change"]}],
outProvinceRatioType: [{required: true, message: "必填", trigger: ["blur", "change"]}],
@@ -49,7 +49,7 @@ layout("/layouts/platform.html"){
:data="tableData"
:size="tableSize"
border
:default-sort="{prop: 'lineName', order: 'ascending'}"
:default-sort="{prop: 'lineCode', order: 'ascending'}"
:row-class-name="tableRowClassName"
@sort-change="pageOrder">
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
@@ -624,7 +624,7 @@ layout("/layouts/platform.html"){
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "lineName",
pageOrderName: "lineCode",
pageOrderBy: "ascending",
year: currentYear,
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-select>
</search-item>
<search-item label="所属工会">
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%">
<search-item label="领办工会">
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择领办工会" style="width: 100%">
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
@@ -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="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 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">
<template slot-scope="{row}">
<el-button size="mini" :type="isClaimed(row) ? 'info' : 'primary'" plain>{{ row.undertakeStatusName || '未领' }}</el-button>
</template>
</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}">
{{ row.minGroupPeople || 0 }}人
<span>{{ formatGroupInfo(row) }}</span>
</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="160" sortable="custom" align="center" header-align="center">
<el-table-column label="报名人数" prop="signupCount" width="160" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="Number(row.signupCount || 0) > 0 ? 'success' : 'info'">
{{ row.signupCount || 0 }}人<span v-if="row.showCurrentUnionSignupCount">({{ row.currentUnionSignupCount || 0 }}人)</span>
</el-tag>
</template>
</el-table-column>
<el-table-column label="成团状态" prop="groupStatusName" width="120" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="groupStatusType(row.groupStatus)">{{ row.groupStatusName || '未成团' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="220" align="center" header-align="center" fixed="right">
<template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
@@ -141,26 +131,21 @@ layout("/layouts/platform.html"){
:size="tableSize"
border
@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="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="性别" prop="gender" width="90" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="手机号码" prop="mobile" width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="身份证号" prop="idCard" min-width="190" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所属工会" prop="unionName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-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-row class="el-pagination-container tour-group-view-pagination">
<el-pagination
@@ -194,6 +179,7 @@ layout("/layouts/platform.html"){
viewRequestSeq: 0,
viewTitle: "",
viewRow: {},
viewGroup: {},
viewTableData: [],
currentUnionInfo: null,
pageForm: {
@@ -213,9 +199,10 @@ layout("/layouts/platform.html"){
pageSize: 10,
totalCount: 0,
pageOrderName: "signupTime",
pageOrderBy: "descending",
pageOrderBy: "ascending",
keyword: "",
unionId: ""
unionId: "",
groupCode: ""
}
}
},
@@ -233,11 +220,6 @@ layout("/layouts/platform.html"){
this.pageForm.pageNumber = 1
this.pageData()
},
groupStatusType(status) {
if (status === "formed") return "success"
if (status === "over") return "danger"
return "info"
},
tableRowClassName({ row }) {
return this.isDirectFamilyLine(row) ? "direct-family-row" : ""
},
@@ -250,17 +232,30 @@ layout("/layouts/platform.html"){
isClaimed(row) {
return row && row.undertakeStatus === "claimed"
},
formatGroupInfo(row) {
if (!row || !row.groupCode) return "未成团"
return row.groupName ? row.groupCode + " " + row.groupName : row.groupCode
},
openView(row) {
if (!row || !row.groupCode) {
this.$message.warning("成团信息不完整")
return
}
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.viewForm.pageNumber = 1
this.viewForm.pageSize = 10
this.viewForm.totalCount = 0
this.viewForm.pageOrderName = "signupTime"
this.viewForm.pageOrderBy = "descending"
this.viewForm.pageOrderBy = "ascending"
this.viewForm.keyword = ""
this.viewForm.unionId = ""
this.viewForm.groupCode = this.viewGroup.groupCode || ""
this.viewTableData = []
this.loadViewData()
},
@@ -278,24 +273,26 @@ layout("/layouts/platform.html"){
})
},
undertakeLine(row) {
if (!row || !row.matterId) {
this.$message.warning("线路信息不完整")
if (!row || !row.matterId || !row.groupCode) {
this.$message.warning("线路成团信息不完整")
return
}
if (this.isClaimed(row)) {
this.$message.warning("当前线路已领办,不允许修改")
this.$message.warning("当前团已领办,不允许修改")
return
}
this.loadCurrentUnion().then((union) => {
const lineName = row.lineName || "当前线路"
const groupName = this.formatGroupInfo(row)
const unionName = (union && union.unionName) || ""
return this.$confirm(lineName + "由" + unionName + "领办,领办后,系统不允许修改,请确认。", "提示", {
return this.$confirm(lineName + "【" + groupName + "】由" + unionName + "领办,领办后,系统不允许修改,请确认。", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
return this.$axios.post(loc() + "/undertakeLine", {
matterId: row.matterId
matterId: row.matterId,
groupCode: row.groupCode
})
})
}).then((res) => {
@@ -309,22 +306,24 @@ layout("/layouts/platform.html"){
}).catch(() => {})
},
releaseUndertakeLine(row) {
if (!row || !row.matterId) {
this.$message.warning("线路信息不完整")
if (!row || !row.matterId || !row.groupCode) {
this.$message.warning("线路成团信息不完整")
return
}
if (!this.isClaimed(row)) {
this.$message.warning("当前线路未领办,无需解除")
this.$message.warning("当前团未领办,无需解除")
return
}
const lineName = row.lineName || "当前线路"
this.$confirm("确认解除【" + lineName + "】的领办状态吗?解除后该线路将变为未领。", "提示", {
const groupName = this.formatGroupInfo(row)
this.$confirm("确认解除【" + lineName + "】" + groupName + "的领办状态吗?解除后该团将变为未领。", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
return this.$axios.post(loc() + "/releaseUndertakeLine", {
matterId: row.matterId
matterId: row.matterId,
groupCode: row.groupCode
})
}).then((res) => {
if (!res) return
@@ -344,6 +343,7 @@ layout("/layouts/platform.html"){
this.viewForm.keyword = ""
this.viewForm.unionId = ""
this.viewForm.pageNumber = 1
this.viewForm.groupCode = this.viewGroup.groupCode || ""
this.loadViewData()
},
viewIndexMethod(index) {
@@ -360,12 +360,12 @@ layout("/layouts/platform.html"){
},
viewPageOrder({ prop, order }) {
this.viewForm.pageOrderName = prop || "signupTime"
this.viewForm.pageOrderBy = order || "descending"
this.viewForm.pageOrderBy = order || "ascending"
this.viewForm.pageNumber = 1
this.loadViewData()
},
loadViewData() {
if (!this.viewRow || !this.viewRow.matterId) {
if (!this.viewRow || !this.viewRow.matterId || !this.viewForm.groupCode) {
this.viewTableData = []
this.viewForm.totalCount = 0
return
@@ -376,6 +376,7 @@ layout("/layouts/platform.html"){
matterId: this.viewRow.matterId,
keyword: this.viewForm.keyword,
unionId: this.viewForm.unionId,
groupCode: this.viewForm.groupCode,
pageNumber: this.viewForm.pageNumber,
pageSize: this.viewForm.pageSize,
pageOrderName: this.viewForm.pageOrderName,
@@ -445,13 +446,6 @@ layout("/layouts/platform.html"){
margin-bottom: 12px;
padding-left: 10px;
}
.tour-family-list {
padding: 8px 32px;
}
.tour-family-empty {
color: #909399;
padding: 8px 32px;
}
.tour-undertake-tip {
margin-top: 8px;
color: #f56c6c;
@@ -534,7 +534,7 @@ layout("/layouts/platform_h5.html"){
lineType: "省内线路",
unionId: "",
directFamilyUnitLine: null,
pageOrderName: "lineName",
pageOrderName: "lineCode",
pageOrderBy: "ascending"
}
}