This commit is contained in:
2026-07-14 16:40:42 +08:00
parent 33687bbd66
commit 2168888ca7
149 changed files with 2784 additions and 692 deletions
@@ -209,7 +209,7 @@ public class TourLineController {
return Result.error("创建年度不能为空");
}
if (StrUtil.isBlank(line.getTravelAgencyId())) {
return Result.error("旅行社名称不能为空");
return Result.error("服务单位名称不能为空");
}
if (StrUtil.isBlank(line.getLineName())) {
return Result.error("线路名称不能为空");
@@ -466,7 +466,13 @@ public class TourMySignupController {
ledger.setSignupTime(defaultIfBlank(oldLedger.getSignupTime(), ledger.getSignupTime()));
fillStaffInfo(ledger);
tourLedgerService.updateIgnoreNull(ledger);
// 修改报名也重新锁定并校验当前分工会名额,兼容人员所属分工会发生变化的场景。
String quotaMessage = tourLedgerService.saveWithUnionSignupQuota(
ledger, matter.getSettingId(), matter.getId(), SecurityUtil.getUserId(), true);
if (StrUtil.isNotBlank(quotaMessage)) {
return Result.error(quotaMessage);
}
tourLedgerFamilyService.clear(Cnd.where(TourLedgerFamily::getLedgerId, "=", ledger.getId()));
tourLedgerDirectRelativeService.clear(Cnd.where(TourLedgerDirectRelative::getLedgerId, "=", ledger.getId()));
if (Lang.isNotEmpty(familyList)) {
@@ -69,6 +69,8 @@ public class TourSettingController {
Cnd lotCnd = Cnd.NEW();
lotCnd.desc(TourSettingLot::getLotValue);
tourSettingService.dao().fetchLinks(tourSetting, "lots", lotCnd);
// 编辑时同时返回分工会人员数量,确保页面按当前组织和人员总数完整回显。
tourSetting.setUnionQuotas(tourSettingService.listUnionQuotaRows(id));
return Result.success(tourSetting);
}
@@ -89,15 +91,30 @@ public class TourSettingController {
Cnd lotCnd = Cnd.NEW();
lotCnd.desc(TourSettingLot::getLotValue);
tourSettingService.dao().fetchLinks(tourSetting, "lots", lotCnd);
// 延用上一年配置时带出原分配数量,前端会清除子表ID后作为新配置提交。
tourSetting.setUnionQuotas(tourSettingService.listUnionQuotaRows(tourSetting.getId()));
return Result.success(tourSetting);
}
/**
* 查询人员分配页签所需的全部分工会、当前人员总数和已保存人员数量。
*
* @param settingId 疗休养配置ID,新增时为空
* @return 分工会人员分配行
*/
@At
@SaCheckPermission("tour.setting")
public Result unionQuotaRows(String settingId) {
return Result.success(tourSettingService.listUnionQuotaRows(settingId));
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tour.setting")
@SLog(type = "tour", tag = "疗休养设置", msg = "保存疗休养配置")
public Result doSubmit(TourSetting tourSetting,
@Param(value = "lots") String lots,
@Param(value = "unionQuotas") String unionQuotas,
@Param(value = "lotDeleteList") String[] lotDeleteList) {
Result checkResult = check(tourSetting);
if (checkResult != null) {
@@ -113,6 +130,14 @@ public class TourSettingController {
return Result.error("同年度下配置名称已存在");
}
// 后端再次校验分配总量,避免绕过页面直接提交超出出行人数指标的数据。
int travelPeopleQuota = tourSetting.getTravelPeopleQuota() == null
? 0 : tourSetting.getTravelPeopleQuota();
String quotaLimitMessage = tourSettingService.checkUnionQuotaLimit(unionQuotas, travelPeopleQuota);
if (StrUtil.isNotBlank(quotaLimitMessage)) {
return Result.error(quotaLimitMessage);
}
// 布尔值给默认值,避免前端未传时出现空状态。
if (tourSetting.getEnabled() == null) {
tourSetting.setEnabled(true);
@@ -148,6 +173,7 @@ public class TourSettingController {
} else {
tourSettingService.insertWith(tourSetting, "lots");
}
tourSettingService.saveUnionQuotas(tourSetting.getId(), unionQuotas);
} else {
// 编辑时先处理页面删除的标段,再保存配置和当前标段行。
if (Lang.isNotEmpty(lotDeleteList)) {
@@ -155,6 +181,7 @@ public class TourSettingController {
}
tourSettingService.updateIgnoreNull(tourSetting);
saveLots(tourSetting);
tourSettingService.saveUnionQuotas(tourSetting.getId(), unionQuotas);
}
return Result.success();
}
@@ -168,6 +195,8 @@ public class TourSettingController {
return Result.error("参数错误");
}
tourSettingService.dao().clear(TourSettingLot.class, Cnd.where(TourSettingLot::getSettingId, "=", id));
// 配置删除时同步清理人员分配,避免遗留不可达的子表数据。
tourSettingService.clearUnionQuotas(id);
tourSettingService.delete(id);
return Result.success();
}
@@ -205,6 +234,9 @@ public class TourSettingController {
if (StrUtil.isBlank(tourSetting.getConfigName())) {
return Result.error("配置名称不能为空");
}
if (tourSetting.getTravelPeopleQuota() == null || tourSetting.getTravelPeopleQuota() < 0) {
return Result.error("出行人数指标必须为非负整数");
}
if (tourSetting.getMinGroupPeople() != null && tourSetting.getMaxGroupPeople() != null
&& tourSetting.getMinGroupPeople() > tourSetting.getMaxGroupPeople()) {
return Result.error("最少成团人数不能大于最多成团人数");
@@ -699,12 +699,16 @@ public class TourSignupController {
ledger.setOverCostReimbursed(Boolean.TRUE.equals(ledger.getOverCostReimbursed()));
fillStaffInfo(ledger);
// PC和移动端共用本接口;在写入台账前锁定分工会名额,防止并发报名超过人员分配数量。
String quotaMessage = tourLedgerService.saveWithUnionSignupQuota(
ledger, matter.getSettingId(), matter.getId(), SecurityUtil.getUserId(), update);
if (StrUtil.isNotBlank(quotaMessage)) {
return Result.error(quotaMessage);
}
if (update) {
tourLedgerService.updateIgnoreNull(ledger);
tourLedgerFamilyService.clear(Cnd.where(TourLedgerFamily::getLedgerId, "=", ledger.getId()));
tourLedgerDirectRelativeService.clear(Cnd.where(TourLedgerDirectRelative::getLedgerId, "=", ledger.getId()));
} else {
tourLedgerService.insert(ledger);
}
if (Lang.isNotEmpty(familyList)) {
familyList.forEach(item -> {
@@ -58,13 +58,13 @@ public class TourTravelAgencyController {
return Result.error("参数错误");
}
TourTravelAgency agency = travelAgencyService.fetch(id);
return agency == null ? Result.error("旅行社不存在") : Result.success(agency);
return agency == null ? Result.error("服务单位不存在") : Result.success(agency);
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tour.travelAgency")
@SLog(type = "tour", tag = "旅行社管理", msg = "保存旅行社信息")
@SLog(type = "tour", tag = "服务单位管理", msg = "保存服务单位信息")
public Result doSubmit(TourTravelAgency agency) {
Result checkResult = check(agency);
if (checkResult != null) {
@@ -77,7 +77,7 @@ public class TourTravelAgencyController {
sameCodeCnd.and(TourTravelAgency::getId, "<>", agency.getId());
}
if (travelAgencyService.count(sameCodeCnd) > 0) {
return Result.error("同年度下旅行社编号已存在");
return Result.error("同年度下服务单位编号已存在");
}
if (agency.getEnabled() == null) {
agency.setEnabled(true);
@@ -93,7 +93,7 @@ public class TourTravelAgencyController {
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tour.travelAgency")
@SLog(type = "tour", tag = "旅行社管理", msg = "删除旅行社信息")
@SLog(type = "tour", tag = "服务单位管理", msg = "删除服务单位信息")
public Result doDelete(String id) {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
@@ -110,10 +110,10 @@ public class TourTravelAgencyController {
return Result.error("年度不能为空");
}
if (StrUtil.isBlank(agency.getAgencyName())) {
return Result.error("旅行社名称不能为空");
return Result.error("服务单位名称不能为空");
}
if (StrUtil.isBlank(agency.getAgencyCode())) {
return Result.error("旅行社编号不能为空");
return Result.error("服务单位编号不能为空");
}
if (StrUtil.isBlank(agency.getContactName())) {
return Result.error("联系人不能为空");
@@ -102,12 +102,12 @@ public class TourLedger extends BaseModel implements Serializable {
private String hotelName;
@Column
@Comment("旅行社ID")
@Comment("服务单位ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String travelAgencyId;
@Column
@Comment("旅行社")
@Comment("服务单位")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String travelAgencyName;
@@ -42,7 +42,7 @@ public class TourLine extends BaseModel implements Serializable {
private String lineName;
@Column
@Comment("旅行社ID")
@Comment("服务单位ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String travelAgencyId;
@@ -43,6 +43,12 @@ public class TourSetting extends BaseModel implements Serializable {
@ColDefine(type = ColType.VARCHAR, width = 50)
private String tourType;
@Column
@Comment("出行人数指标")
@ColDefine(type = ColType.INT)
@Default("0")
private Integer travelPeopleQuota;
@Column
@Comment("可参加人员范围ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@@ -122,11 +128,16 @@ public class TourSetting extends BaseModel implements Serializable {
private Boolean enabled;
/**
* 标段管理沿用老疗休养配置的子表设计后续线路旅行社等模块可通过标段ID继续关联
* 标段管理沿用老疗休养配置的子表设计后续线路服务单位等模块可通过标段ID继续关联
*/
@Many(field = "settingId")
private List<TourSettingLot> lots;
/**
* 分工会人员分配仅用于配置弹窗回显与提交不作为 tour_setting 表字段保存
*/
private List<TourSettingUnionQuota> unionQuotas;
@Column
@Comment("服务须知")
@ColDefine(type = ColType.TEXT)
@@ -0,0 +1,60 @@
package com.budwk.app.zhgh.dayofficework.tour.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.ColDefine;
import org.nutz.dao.entity.annotation.ColType;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Default;
import org.nutz.dao.entity.annotation.Name;
import org.nutz.dao.entity.annotation.Table;
import org.nutz.dao.entity.annotation.TableMeta;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serializable;
/**
* 疗休养配置下的分工会人员数量分配
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("tour_setting_union_quota")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("疗休养配置分工会人员分配")
public class TourSettingUnionQuota extends BaseModel implements Serializable {
@Column
@Name
@Comment("ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
@Column
@Comment("所属疗休养配置ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String settingId;
@Column
@Comment("分工会ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String unionId;
@Column
@Comment("分工会名称")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String unionName;
@Column
@Comment("人员数量")
@ColDefine(type = ColType.INT)
@Default("0")
private Integer peopleQuota;
/**
* 当前分工会人员总数仅用于页面分配参考不写入数据库
*/
private Integer personCount;
}
@@ -9,14 +9,14 @@ import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serializable;
/**
* 旅行社管理
* 先维护疗休养线路创建会复用的旅行社基础信息后续线路模块可通过旅行社ID关联
* 服务单位管理
* 先维护疗休养线路创建会复用的服务单位基础信息后续线路模块可通过服务单位ID关联
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("tour_travel_agency")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("普惠疗休养旅行社")
@Comment("普惠疗休养服务单位")
public class TourTravelAgency extends BaseModel implements Serializable {
@Column
@@ -32,12 +32,12 @@ public class TourTravelAgency extends BaseModel implements Serializable {
private Integer year;
@Column
@Comment("旅行社编号")
@Comment("服务单位编号")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String agencyCode;
@Column
@Comment("旅行社名称")
@Comment("服务单位名称")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String agencyName;
@@ -3,5 +3,24 @@ package com.budwk.app.zhgh.dayofficework.tour.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
/**
* 疗休养报名台账服务统一处理报名记录及并发名额校验
*/
public interface TourLedgerService extends BaseService<TourLedger> {
/**
* 在同一事务中锁定分工会名额校验并发容量并写入报名台账
*
* @param ledger 待新增或修改的报名台账
* @param settingId 疗休养配置ID
* @param matterId 当前报名事项ID
* @param userId 当前登录用户ID
* @param update true 表示修改已有报名false 表示新增报名
* @return 保存成功返回空字符串否则返回业务提示
*/
String saveWithUnionSignupQuota(TourLedger ledger,
String settingId,
String matterId,
String userId,
boolean update);
}
@@ -2,6 +2,44 @@ package com.budwk.app.zhgh.dayofficework.tour.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.dayofficework.tour.models.TourSetting;
import com.budwk.app.zhgh.dayofficework.tour.models.TourSettingUnionQuota;
import java.util.List;
/**
* 疗休养配置服务统一处理配置及分工会人员数量分配业务
*/
public interface TourSettingService extends BaseService<TourSetting> {
/**
* 查询全部分工会在指定配置下的人员数量并实时补充分工会人员总数
*
* @param settingId 疗休养配置ID新增配置时允许为空
* @return 按分工会编码排序的人员分配行
*/
List<TourSettingUnionQuota> listUnionQuotaRows(String settingId);
/**
* 保存指定配置的分工会人员数量提交内容为 JSON 数组字符串
*
* @param settingId 疗休养配置ID
* @param unionQuotas 分工会人员分配 JSON
*/
void saveUnionQuotas(String settingId, String unionQuotas);
/**
* 校验各分工会人员数量是否均为非负整数且合计不超过出行人数指标
*
* @param unionQuotas 分工会人员分配 JSON
* @param travelPeopleQuota 出行人数指标
* @return 校验通过返回空字符串否则返回业务提示
*/
String checkUnionQuotaLimit(String unionQuotas, int travelPeopleQuota);
/**
* 清除指定配置下的全部分工会人员分配
*
* @param settingId 疗休养配置ID
*/
void clearUnionQuotas(String settingId);
}
@@ -1,15 +1,136 @@
package com.budwk.app.zhgh.dayofficework.tour.service.impl;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.sys.views.View_user;
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.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
/**
* 疗休养报名台账服务实现
*/
@IocBean(args = {"refer:dao"})
public class TourLedgerServiceImpl extends BaseServiceImpl<TourLedger> implements TourLedgerService {
public TourLedgerServiceImpl(Dao dao) {
super(dao);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public String saveWithUnionSignupQuota(TourLedger ledger,
String settingId,
String matterId,
String userId,
boolean update) {
if (ledger == null) {
return "报名数据不能为空";
}
String quotaMessage = lockAndCheckUnionSignupQuota(
settingId, matterId, userId, update ? ledger.getId() : null);
if (StrUtil.isNotBlank(quotaMessage)) {
return quotaMessage;
}
// 名额校验通过后立即在同一事务内写入台账事务提交前始终持有分工会名额行锁
if (update) {
updateIgnoreNull(ledger);
} else {
insert(ledger);
}
return "";
}
/**
* 锁定当前配置和登录人所属分工会的名额行并校验报名后是否超过人员数量
*/
private String lockAndCheckUnionSignupQuota(String settingId,
String matterId,
String userId,
String excludeLedgerId) {
if (StrUtil.isBlank(settingId) || StrUtil.isBlank(matterId)) {
return "疗休养配置或报名事项不存在";
}
// 分工会信息从用户视图读取不接受报名表单传入的工会ID防止跨工会占用名额
View_user user = dao().fetch(View_user.class, Cnd.where(View_user::getId, "=", userId));
if (user == null || StrUtil.isBlank(user.getUnionId()) || StrUtil.isBlank(user.getLoginname())) {
return "未查询到您所属的分工会,不能报名";
}
// FOR UPDATE 串行化同一配置同一分工会的抢名额请求不同分工会之间互不阻塞
Sql quotaSql = Sqls.create("""
SELECT peopleQuota
FROM tour_setting_union_quota
WHERE settingId = @settingId
AND unionId = @unionId
AND delFlag = 0
LIMIT 1
FOR UPDATE
""");
quotaSql.setParam("settingId", settingId);
quotaSql.setParam("unionId", user.getUnionId());
quotaSql.setCallback(Sqls.callback.map());
dao().execute(quotaSql);
NutMap quotaRow = quotaSql.getObject(NutMap.class);
if (quotaRow == null || quotaRow.isEmpty()) {
return "您所在分工会未分配报名名额";
}
int peopleQuota = quotaRow.getInt("peopleQuota", 0);
if (peopleQuota <= 0) {
return "您所在分工会的报名名额已满";
}
// 取得名额锁后再次检查同事项重复报名关闭双击或并发请求绕过前置查询的时间窗口
String excludeSql = StrUtil.isBlank(excludeLedgerId) ? "" : " AND id <> @excludeLedgerId";
Sql duplicateSql = Sqls.create("""
SELECT COUNT(1)
FROM tour_ledger
WHERE delFlag = 0
AND matterId = @matterId
AND jobNo = @jobNo
""" + excludeSql);
duplicateSql.setParam("matterId", matterId);
duplicateSql.setParam("jobNo", user.getLoginname());
if (StrUtil.isNotBlank(excludeLedgerId)) {
duplicateSql.setParam("excludeLedgerId", excludeLedgerId);
}
duplicateSql.setCallback(Sqls.callback.integer());
dao().execute(duplicateSql);
if (duplicateSql.getInt() > 0) {
return "您已报名当前出行时段,请勿重复提交";
}
// 名额按配置统计覆盖该配置下的全部事项待审核报名也占用名额取消后因台账删除自动释放
String countExcludeSql = StrUtil.isBlank(excludeLedgerId) ? "" : " AND ledger.id <> @excludeLedgerId";
Sql countSql = Sqls.create("""
SELECT COUNT(1)
FROM tour_ledger ledger
INNER JOIN tour_matter matter
ON matter.id = ledger.matterId
AND matter.delFlag = 0
WHERE ledger.delFlag = 0
AND matter.settingId = @settingId
AND ledger.unionId = @unionId
""" + countExcludeSql);
countSql.setParam("settingId", settingId);
countSql.setParam("unionId", user.getUnionId());
if (StrUtil.isNotBlank(excludeLedgerId)) {
countSql.setParam("excludeLedgerId", excludeLedgerId);
}
countSql.setCallback(Sqls.callback.integer());
dao().execute(countSql);
int signupCount = countSql.getInt();
if (signupCount + 1 > peopleQuota) {
return "您所在分工会的报名名额已满(已报名 " + signupCount
+ " 人,分配 " + peopleQuota + " 人)";
}
return "";
}
}
@@ -1,15 +1,172 @@
package com.budwk.app.zhgh.dayofficework.tour.service.impl;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.sys.models.Sys_union;
import com.budwk.app.zhgh.dayofficework.tour.models.TourSetting;
import com.budwk.app.zhgh.dayofficework.tour.models.TourSettingUnionQuota;
import com.budwk.app.zhgh.dayofficework.tour.service.TourSettingService;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.lang.Lang;
import org.nutz.lang.util.NutMap;
import java.util.List;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 疗休养配置服务实现负责分工会人员分配的查询校验和持久化
*/
@IocBean(args = {"refer:dao"})
public class TourSettingServiceImpl extends BaseServiceImpl<TourSetting> implements TourSettingService {
public TourSettingServiceImpl(Dao dao) {
super(dao);
}
@Override
public List<TourSettingUnionQuota> listUnionQuotaRows(String settingId) {
// 一次查询分工会已保存数量和实时人员总数避免逐个工会统计产生 N+1 查询
Sql sql = Sqls.create("""
SELECT
quota.id AS id,
@settingId AS settingId,
un.id AS unionId,
un.name AS unionName,
COALESCE(quota.peopleQuota, 0) AS peopleQuota,
COALESCE(user_count.personCount, 0) AS personCount
FROM sys_union un
LEFT JOIN tour_setting_union_quota quota
ON quota.unionId = un.id
AND quota.settingId = @settingId
AND COALESCE(quota.delFlag, 0) = 0
LEFT JOIN (
SELECT unionId, COUNT(1) AS personCount
FROM vw_user
GROUP BY unionId
) user_count ON user_count.unionId = un.id
WHERE COALESCE(un.delFlag, 0) = 0
ORDER BY un.unionCode ASC
""");
sql.setParam("settingId", StrUtil.blankToDefault(settingId, ""));
List<NutMap> rows = listMap(sql);
return rows.stream().map(row -> {
TourSettingUnionQuota quota = new TourSettingUnionQuota();
quota.setId(row.getString("id"));
quota.setSettingId(row.getString("settingId"));
quota.setUnionId(row.getString("unionId"));
quota.setUnionName(row.getString("unionName"));
quota.setPeopleQuota(toNonNegativeInteger(row.getInt("peopleQuota")));
quota.setPersonCount(toNonNegativeInteger(row.getInt("personCount")));
return quota;
}).collect(Collectors.toList());
}
@Override
public void saveUnionQuotas(String settingId, String unionQuotas) {
if (StrUtil.isBlank(settingId)) {
return;
}
clearUnionQuotas(settingId);
if (StrUtil.isBlank(unionQuotas)) {
return;
}
List<TourSettingUnionQuota> quotaList = Json.fromJsonAsList(TourSettingUnionQuota.class, unionQuotas);
if (Lang.isEmpty(quotaList)) {
return;
}
// 工会名称以当前组织表为准防止前端篡改或历史名称继续写入
Map<String, Sys_union> unionMap = dao().query(Sys_union.class,
Cnd.where(Sys_union::getDelFlag, "=", false)).stream()
.collect(Collectors.toMap(Sys_union::getId, item -> item, (first, second) -> first));
List<TourSettingUnionQuota> saveList = quotaList.stream()
.filter(item -> item != null
&& StrUtil.isNotBlank(item.getUnionId())
&& unionMap.containsKey(item.getUnionId()))
.map(item -> normalizeUnionQuota(settingId, item, unionMap))
.filter(item -> item.getPeopleQuota() > 0)
.collect(Collectors.toList());
if (Lang.isNotEmpty(saveList)) {
dao().insert(saveList);
}
}
@Override
public String checkUnionQuotaLimit(String unionQuotas, int travelPeopleQuota) {
if (StrUtil.isBlank(unionQuotas)) {
return "";
}
final List<TourSettingUnionQuota> quotaList;
try {
quotaList = Json.fromJsonAsList(TourSettingUnionQuota.class, unionQuotas);
} catch (Exception e) {
return "人员分配数据格式不正确";
}
if (Lang.isEmpty(quotaList)) {
return "";
}
long total = 0L;
Set<String> unionIds = new HashSet<>();
Set<String> validUnionIds = dao().query(Sys_union.class,
Cnd.where(Sys_union::getDelFlag, "=", false)).stream()
.map(Sys_union::getId)
.collect(Collectors.toSet());
for (int index = 0; index < quotaList.size(); index++) {
TourSettingUnionQuota quota = quotaList.get(index);
if (quota == null || quota.getPeopleQuota() == null) {
continue;
}
// 工会ID必须来自当前组织表且不能重复防止伪造行绕过总量和组织范围校验
if (StrUtil.isBlank(quota.getUnionId())
|| !validUnionIds.contains(quota.getUnionId())
|| !unionIds.add(quota.getUnionId())) {
return "" + (index + 1) + "行分工会信息无效或重复";
}
if (quota.getPeopleQuota() < 0) {
return "" + (index + 1) + "行人员数量必须为非负整数";
}
total += quota.getPeopleQuota();
}
if (total > travelPeopleQuota) {
return "当前人员数量合计 " + total + ",不能超过出行人数指标 " + travelPeopleQuota;
}
return "";
}
@Override
public void clearUnionQuotas(String settingId) {
if (StrUtil.isNotBlank(settingId)) {
dao().clear(TourSettingUnionQuota.class,
Cnd.where(TourSettingUnionQuota::getSettingId, "=", settingId));
}
}
/**
* 将前端分配行转换为可信的持久化对象只接受当前组织表中的工会名称
*/
private TourSettingUnionQuota normalizeUnionQuota(String settingId,
TourSettingUnionQuota item,
Map<String, Sys_union> unionMap) {
Sys_union union = unionMap.get(item.getUnionId());
TourSettingUnionQuota quota = new TourSettingUnionQuota();
quota.setSettingId(settingId);
quota.setUnionId(item.getUnionId());
quota.setUnionName(union.getName());
quota.setPeopleQuota(toNonNegativeInteger(item.getPeopleQuota()));
return quota;
}
/**
* 将空值或负数统一转换为零保证页面统计和数据库保存使用相同口径
*/
private Integer toNonNegativeInteger(Integer value) {
return value == null || value < 0 ? 0 : value;
}
}
@@ -8,6 +8,7 @@ import com.budwk.app.base.result.Result;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalType;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
@@ -33,6 +34,8 @@ public class ProposalConfigTypeController {
private Dao dao;
@Inject
private BaseService baseService;
@Inject
private ProposalCommonService proposalCommonService;
@At("")
@Ok("beetl:/platform/zhgh/democratic/proposal/config/type/index.html")
@@ -47,6 +50,8 @@ public class ProposalConfigTypeController {
Sql sql = Sqls.create("select * from proposal_type $condition");
Cnd cnd = Cnd.NEW();
cnd.and(Cnd.likeEX("name", name));
// 提案类型列表只允许名称和编码参与排序
proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "configType");
sql.setCondition(cnd);
Pagination pagination = baseService.listPageVO(pageForm, sql, ProposalType.class);
return Result.success(pagination);
@@ -19,6 +19,7 @@ import com.budwk.app.sys.services.SysRoleService;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalConfig;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalUndertake;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import lombok.extern.slf4j.Slf4j;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
@@ -54,6 +55,8 @@ public class ProposalConfigUnitController {
private BaseService baseService;
@Inject
private SysRoleService sysRoleService;
@Inject
private ProposalCommonService proposalCommonService;
@At("")
@Ok("beetl:/platform/zhgh/democratic/proposal/config/unit/index.html")
@@ -99,7 +102,10 @@ public class ProposalConfigUnitController {
cnd.where().orLike("u2.loginname", pageForm.getSearchKeyword());
cnd.where().orLike("t1.name", pageForm.getSearchKeyword());
}
cnd.asc("t1.code");
// 承办单位列表未指定有效排序时继续按单位编码升序
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "configUnit")) {
cnd.asc("t1.code");
}
sql.setCondition(cnd);
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -28,6 +28,7 @@ import org.nutz.mvc.annotation.Param;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@IocBean
@At("/platform/proposal/dashboard")
@@ -35,6 +36,23 @@ import java.util.List;
@Ok("json:full")
public class ProposalDashboardController {
/**
* 工作台表格允许排序的字段映射值为查询中的真实字段或安全别名
*/
private static final Map<String, String> DASHBOARD_ORDER_COLUMNS = Map.ofEntries(
Map.entry("code", "info.code"),
Map.entry("name", "info.name"),
Map.entry("createUserName", "info.createUserName"),
Map.entry("typeName", "typeName"),
Map.entry("delegationName", "delegationName"),
Map.entry("caseFilingResult", "info.caseFilingResult"),
Map.entry("isConsolidation", "info.isConsolidation"),
Map.entry("masterUnitName", "masterUnitName"),
Map.entry("slaveUnitNames", "slaveUnitNames"),
Map.entry("curTaskName", "curTaskName"),
Map.entry("instanceState", "instanceState")
);
@Inject
private ProposalCommonService proposalCommonService;
@Inject
@@ -178,6 +196,8 @@ public class ProposalDashboardController {
}
}
// 根据工作台字段白名单追加排序未选择排序时保持原有查询顺序
proposalCommonService.applySafePageOrder(cnd, pageForm, DASHBOARD_ORDER_COLUMNS);
sql.setCondition(cnd);
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -7,6 +7,7 @@ import com.budwk.app.base.param.ExportTableColumns;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.democratic.proposal.param.ProposalQueryComprehensiveParam;
import com.budwk.app.zhgh.democratic.proposal.service.ProposalExportService;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Cnd;
@@ -29,6 +30,8 @@ public class ProposalExportComprehensiveController {
@Inject
private ProposalExportService proposalExportService;
@Inject
private ProposalCommonService proposalCommonService;
@At("")
@Ok("beetl:/platform/zhgh/democratic/proposal/export/comprehensive/index.html")
@@ -73,6 +76,8 @@ public class ProposalExportComprehensiveController {
}
ProposalQueryComprehensiveParam.buildSearch(cnd, pageForm);
cnd.groupBy("info.id");
// 综合导出主列表的聚合字段仅通过固定查询别名排序
proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "exportComprehensive");
sql.setCondition(cnd);
Pagination pagination = proposalExportService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -17,6 +17,7 @@ import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.flow.vo.ProcessTaskVO;
import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
@@ -63,6 +64,8 @@ public class ProposalExportSingleCustomController {
@Inject
private BaseService baseService;
@Inject
private ProposalCommonService proposalCommonService;
@Inject
private FlowEngine flowEngine;
@@ -100,7 +103,10 @@ public class ProposalExportSingleCustomController {
Cnd cnd = Cnd.NEW();
ProposalSearchParam.buildSearch(cnd, pageForm);
cnd.groupBy("info.id");
cnd.desc("info.createdAt");
// 自定义导出列表未指定有效排序时保留提案创建时间倒序
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "exportSingleCustom")) {
cnd.desc("info.createdAt");
}
sql.setCondition(cnd);
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -6,11 +6,13 @@ 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.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.service.BaseService;
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.zhgh.democratic.proposal.service.common.ProposalCommonService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
@@ -31,6 +33,7 @@ import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@IocBean
@@ -40,10 +43,26 @@ import java.util.stream.Collectors;
@Api(tags = "提案统计分析")
public class ProposalQueryAnalysisController {
/**
* 统计数据表允许排序的字段白名单
*/
private static final Set<String> STATISTICS_ORDER_COLUMNS = Set.of(
"dimension", "itemName", "count", "rate"
);
/**
* 分析数据表允许排序的字段白名单
*/
private static final Set<String> ANALYSIS_ORDER_COLUMNS = Set.of(
"dimension", "total", "categoryCount", "topItem", "topCount", "topRate", "analysis"
);
@Inject
private BaseService baseService;
@Inject
private SysDictService sysDictService;
@Inject
private ProposalCommonService proposalCommonService;
@At("")
@Ok("beetl:/platform/zhgh/democratic/proposal/query/analysis/index.html")
@@ -51,36 +70,54 @@ public class ProposalQueryAnalysisController {
public void index() {
}
/**
* 查询提案统计数据
*
* @param sessionId 教代会届次ID
* @param dimension 统计维度为空时返回全部维度
* @param statisticsOrderName 统计表排序字段
* @param statisticsOrderBy 统计表排序方向
* @return 统计行列表包含维度分类项数量和占比
*/
@At
@ApiOperation("统计数据")
@SaCheckPermission("proposal.query.analysis")
public Result statisticsData(String sessionId, String dimension) {
/*
* 查询参数说明
* sessionId教代会届次ID用于限定统计提案范围
* dimension统计维度可传提案人单位提案类型代表团立案结果满意度为空时返回全部维度
* 返回值Result.data 为统计行列表包含 dimensionitemNamecountrate
*/
public Result statisticsData(String sessionId, String dimension, String statisticsOrderName, String statisticsOrderBy) {
if (StrUtil.isBlank(sessionId)) {
return Result.error("请先选择教代会届次");
}
return Result.success(buildStatisticsRows(sessionId, dimension));
List<NutMap> rows = buildStatisticsRows(sessionId, dimension);
PageForm orderForm = new PageForm();
orderForm.setPageOrderName(statisticsOrderName);
orderForm.setPageOrderBy(statisticsOrderBy);
// 统计数据生成后按独立白名单执行后端排序
proposalCommonService.sortStatisticsRows(rows, orderForm, STATISTICS_ORDER_COLUMNS);
return Result.success(rows);
}
/**
* 查询提案分析数据
*
* @param sessionId 教代会届次ID
* @param dimension 统计维度为空时返回全部维度
* @param analysisOrderName 分析表排序字段
* @param analysisOrderBy 分析表排序方向
* @return 分析行列表包含总量最高项最高占比和分析结论
*/
@At
@ApiOperation("分析数据")
@SaCheckPermission("proposal.query.analysis")
public Result analysisData(String sessionId, String dimension) {
/*
* 查询参数说明
* sessionId教代会届次ID用于限定分析提案范围
* dimension统计维度可传提案人单位提案类型代表团立案结果满意度为空时返回全部维度
* 返回值Result.data 为分析行列表包含 dimensiontotalcategoryCounttopItemtopCounttopRateanalysis
*/
public Result analysisData(String sessionId, String dimension, String analysisOrderName, String analysisOrderBy) {
if (StrUtil.isBlank(sessionId)) {
return Result.error("请先选择教代会届次");
}
return Result.success(buildAnalysisRows(sessionId, dimension));
List<NutMap> rows = buildAnalysisRows(sessionId, dimension);
PageForm orderForm = new PageForm();
orderForm.setPageOrderName(analysisOrderName);
orderForm.setPageOrderBy(analysisOrderBy);
// 分析数据生成后按独立白名单执行后端排序
proposalCommonService.sortStatisticsRows(rows, orderForm, ANALYSIS_ORDER_COLUMNS);
return Result.success(rows);
}
@At
@@ -94,9 +131,9 @@ public class ProposalQueryAnalysisController {
entities.add(new ExcelExportEntity("分类项", "itemName", 30));
entities.add(new ExcelExportEntity("数量", "count", 12));
entities.add(new ExcelExportEntity("占比", "rate", 12));
ExportParams params = new ExportParams();
params.setType(ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(params, entities, list);
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, list);
CommonDownloadUtil.download("提案统计数据.xlsx", workbook, response);
}
@@ -114,9 +151,9 @@ public class ProposalQueryAnalysisController {
entities.add(new ExcelExportEntity("最高数量", "topCount", 12));
entities.add(new ExcelExportEntity("最高占比", "topRate", 12));
entities.add(new ExcelExportEntity("分析结论", "analysis", 60));
ExportParams params = new ExportParams();
params.setType(ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(params, entities, list);
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, list);
CommonDownloadUtil.download("提案分析数据.xlsx", workbook, response);
}
@@ -27,6 +27,7 @@ import org.nutz.mvc.annotation.Param;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@IocBean
@@ -36,6 +37,20 @@ import java.util.List;
@Api(tags = "征集进度查询")
public class ProposalQueryCollectProgressController {
/**
* 征集进度表格允许排序的字段映射值为查询中的真实字段或安全别名
*/
private static final Map<String, String> COLLECT_PROGRESS_ORDER_COLUMNS = Map.ofEntries(
Map.entry("code", "info.code"),
Map.entry("name", "info.name"),
Map.entry("createUserName", "info.createUserName"),
Map.entry("delegationName", "delegationName"),
Map.entry("inviteCount", "inviteCount"),
Map.entry("finishCount", "finishCount"),
Map.entry("curTaskName", "curTaskName"),
Map.entry("instanceState", "instanceState")
);
static List<NutMap> states = new ArrayList<>() {{
add(NutMap.NEW().addv("code", 10).addv("id", 10).addv("name", "撰写提案"));
add(NutMap.NEW().addv("code", 20).addv("id", 20).addv("name", "附议提案"));
@@ -83,8 +98,9 @@ public class ProposalQueryCollectProgressController {
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id AND t.taskState = 10
$condition
""");
sql.setParam("sessionId", pageForm.getSessionId());
Cnd cnd = Cnd.NEW();
// 征集进度必须按页面选中的教代会过滤避免共享参数未处理sessionId导致跨届次查询
cnd.andEX("info.sessionId", "=", pageForm.getSessionId());
ProposalQueryComprehensiveParam.buildSearch(cnd, pageForm);
SqlExpressionGroup seg = new SqlExpressionGroup();
@@ -106,6 +122,8 @@ public class ProposalQueryCollectProgressController {
cnd.and(seg);
}
cnd.groupBy("info.id");
// 征集进度包含关联字段和人数统计别名只允许白名单字段进入排序条件
proposalCommonService.applySafePageOrder(cnd, pageForm, COLLECT_PROGRESS_ORDER_COLUMNS);
sql.setCondition(cnd);
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
List<NutMap> list = (List<NutMap>) pagination.getList();
@@ -25,6 +25,7 @@ import org.nutz.mvc.annotation.Param;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.Arrays;
import java.util.Map;
@IocBean
@At("/platform/proposal/query/comprehensive")
@@ -33,6 +34,23 @@ import java.util.Arrays;
@Api(tags = "提案综合查询")
public class ProposalQueryComprehensiveController {
/**
* 综合查询表格允许排序的字段映射值为查询中的真实字段或安全别名
*/
private static final Map<String, String> COMPREHENSIVE_ORDER_COLUMNS = Map.ofEntries(
Map.entry("code", "info.code"),
Map.entry("name", "info.name"),
Map.entry("createUserName", "info.createUserName"),
Map.entry("typeName", "typeName"),
Map.entry("delegationName", "delegationName"),
Map.entry("caseFilingResult", "info.caseFilingResult"),
Map.entry("caseFilingType", "info.caseFilingType"),
Map.entry("merge", "merge"),
Map.entry("undertakeUnits", "undertakeUnitsOrder"),
Map.entry("curTaskName", "curTaskName"),
Map.entry("instanceState", "instanceState")
);
@Inject
private BaseService baseService;
@Inject
@@ -62,7 +80,8 @@ public class ProposalQueryComprehensiveController {
ins.processDefineId instanceProcessDefineId,
t.displayName curTaskName,
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 1 THEN pru.unitName END) AS masterUnitName,
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 0 THEN pru.unitName END) AS slaveUnitNames
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 0 THEN pru.unitName END) AS slaveUnitNames,
GROUP_CONCAT(DISTINCT pru.unitName ORDER BY pru.isMaster DESC, pru.unitName) AS undertakeUnitsOrder
FROM
proposal_info info
LEFT JOIN proposal_type type on type.id = info.typeId
@@ -120,6 +139,8 @@ public class ProposalQueryComprehensiveController {
}
ProposalQueryComprehensiveParam.buildSearch(cnd, pageForm);
cnd.groupBy("info.id");
// 综合查询包含多表字段及聚合别名只允许白名单字段进入排序条件
proposalCommonService.applySafePageOrder(cnd, pageForm, COMPREHENSIVE_ORDER_COLUMNS);
sql.setCondition(cnd);
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -1,13 +1,16 @@
package com.budwk.app.zhgh.democratic.proposal.controller.query;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.sys.models.Sys_dict;
import com.budwk.app.sys.services.SysDictService;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
@@ -19,8 +22,10 @@ import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import java.util.Map;
@IocBean
@At("/platform/proposal/query/delegation")
@@ -29,9 +34,34 @@ import java.util.stream.Collectors;
@Api(tags = "代表团提案统计")
public class ProposalQueryDelegationController {
/**
* 代表团汇总表固定列的安全排序映射动态立案结果列在查询时追加
*/
private static final Map<String, String> DELEGATION_SUMMARY_ORDER_COLUMNS = Map.of(
"dbtName", "dbt.name",
"TOTAL", "TOTAL",
"caseRate", "caseRateOrder"
);
/**
* 代表团提案明细表允许排序的字段映射
*/
private static final Map<String, String> DELEGATION_PROPOSAL_ORDER_COLUMNS = Map.ofEntries(
Map.entry("code", "info.code"),
Map.entry("name", "info.name"),
Map.entry("createUserName", "info.createUserName"),
Map.entry("typeName", "typeName"),
Map.entry("sessionName", "sessionName"),
Map.entry("delegationName", "delegationName"),
Map.entry("curTaskName", "curTaskName")
);
@Inject
private BaseService baseService;
@Inject
private ProposalCommonService proposalCommonService;
@Inject
private SysDictService sysDictService;
@@ -46,28 +76,45 @@ public class ProposalQueryDelegationController {
@At
@ApiOperation("代表团提案统计")
@SaCheckPermission("proposal.query.delegation")
public Result pageData(String sessionId) {
public Result pageData(PageForm pageForm, String sessionId) {
List<Sys_dict> dictList = sysDictService.getSubListByCode("PROPOSAL_CASE_FILING_RESULT");
Sql sql = Sqls.create("""
SELECT
dbt.id,
dbt.`name` AS dbtName,
(select count(1) from proposal_info info where info.delegationId = dbt.id and info.sessionId = dbt.sessionId ) as TOTAL,
(select count(1) from proposal_info info where info.delegationId = dbt.id and info.sessionId = dbt.sessionId and info.isSubmit = 1 ) as SUBMIT_COUNT,
$resultSql
(select count(1) from proposal_info info where info.delegationId = dbt.id and info.sessionId = dbt.sessionId and info.isSubmit = 1 ) as SUBMIT_COUNT
$resultSql,
(select count(1) from proposal_info info where info.delegationId = dbt.id and info.sessionId = dbt.sessionId and info.caseFilingResult = @confirmFiling)
/ NULLIF((select count(1) from proposal_info info where info.delegationId = dbt.id and info.sessionId = dbt.sessionId), 0) AS caseRateOrder
FROM
teacher_congress_delegation dbt
$condition
""");
String resultStr = dictList.stream().map(v -> {
return "( select count(1) from proposal_info info where info.delegationId = dbt.id and info.sessionId = dbt.sessionId and info.caseFilingResult= '%s' ) as `%s`".formatted(v.getCode(), v.getCode());
}).collect(Collectors.joining(","));
sql.setVar("resultSql", resultStr);
Map<String, String> summaryOrderColumns = new HashMap<>(DELEGATION_SUMMARY_ORDER_COLUMNS);
List<String> resultSqlParts = new ArrayList<>();
int resultIndex = 0;
for (Sys_dict dict : dictList) {
String resultCode = dict.getCode();
// 动态别名仅允许字母数字和下划线查询值使用参数绑定避免字典内容进入SQL结构
if (StrUtil.isBlank(resultCode) || !resultCode.matches("[A-Za-z0-9_]+")) {
continue;
}
String paramName = "caseFilingResult" + resultIndex++;
resultSqlParts.add(", (select count(1) from proposal_info info where info.delegationId = dbt.id and info.sessionId = dbt.sessionId and info.caseFilingResult = @" + paramName + ") AS `" + resultCode + "`");
sql.setParam(paramName, resultCode);
summaryOrderColumns.put(resultCode, "`" + resultCode + "`");
}
sql.setVar("resultSql", String.join("", resultSqlParts));
sql.setParam("confirmFiling", "CONFIRM_FILING");
Cnd cnd = Cnd.NEW();
cnd.andEX("dbt.sessionId", "=", sessionId);
if (!AuthUtil.hasRoleOr("SYSADMIN", "SCHOOL_UNION_ADMIN")) {
}
cnd.asc("dbt.`code`");
// 未选择排序时保留原有代表团编码升序选择后仅应用白名单字段
if (!proposalCommonService.applySafePageOrder(cnd, pageForm, summaryOrderColumns)) {
cnd.asc("dbt.`code`");
}
sql.setCondition(cnd);
return Result.success(baseService.listMap(sql));
}
@@ -101,7 +148,18 @@ public class ProposalQueryDelegationController {
$condition
""");
Cnd cnd = Cnd.NEW();
ProposalSearchParam.buildSearch(cnd, pageForm);
// 先应用明细字段白名单再隔离公共搜索中的原始排序处理阻止任意字段进入SQL
proposalCommonService.applySafePageOrder(cnd, pageForm, DELEGATION_PROPOSAL_ORDER_COLUMNS);
String requestedOrderName = pageForm.getPageOrderName();
String requestedOrderBy = pageForm.getPageOrderBy();
pageForm.setPageOrderName(null);
pageForm.setPageOrderBy(null);
try {
ProposalSearchParam.buildSearch(cnd, pageForm);
} finally {
pageForm.setPageOrderName(requestedOrderName);
pageForm.setPageOrderBy(requestedOrderBy);
}
cnd.groupBy("info.id");
sql.setCondition(cnd);
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
@@ -5,6 +5,7 @@ import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.democratic.proposal.param.ProposalQueryComprehensiveParam;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
@@ -18,6 +19,7 @@ import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import javax.validation.Valid;
import java.util.Map;
@IocBean
@At("/platform/proposal/query/history")
@@ -26,8 +28,27 @@ import javax.validation.Valid;
@Api(tags = "提案管理系统-查询统计-历史提案查询")
public class ProposalQueryHistoryController {
/**
* 历史提案表格允许排序的字段映射值为查询中的真实字段或安全别名
*/
private static final Map<String, String> HISTORY_ORDER_COLUMNS = Map.ofEntries(
Map.entry("code", "info.code"),
Map.entry("name", "info.name"),
Map.entry("createUserName", "info.createUserName"),
Map.entry("sessionName", "sessionName"),
Map.entry("delegationName", "delegationName"),
Map.entry("caseFilingResult", "info.caseFilingResult"),
Map.entry("caseFilingType", "info.caseFilingType"),
Map.entry("merge", "merge"),
Map.entry("undertakeUnits", "undertakeUnitsOrder"),
Map.entry("curTaskName", "curTaskName"),
Map.entry("instanceState", "instanceState")
);
@Inject
private BaseService baseService;
@Inject
private ProposalCommonService proposalCommonService;
@At("")
@Ok("beetl:/platform/zhgh/democratic/proposal/query/history/index.html")
@@ -53,7 +74,8 @@ public class ProposalQueryHistoryController {
ins.processDefineId instanceProcessDefineId,
t.displayName curTaskName,
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 1 THEN pru.unitName END) AS masterUnitName,
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 0 THEN pru.unitName END) AS slaveUnitNames
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 0 THEN pru.unitName END) AS slaveUnitNames,
GROUP_CONCAT(DISTINCT pru.unitName ORDER BY pru.isMaster DESC, pru.unitName) AS undertakeUnitsOrder
FROM
proposal_info info
LEFT JOIN proposal_type type on type.id = info.typeId
@@ -69,6 +91,8 @@ public class ProposalQueryHistoryController {
Cnd cnd = Cnd.NEW();
ProposalQueryComprehensiveParam.buildSearch(cnd, pageForm);
cnd.groupBy("info.id");
// 历史查询包含多表字段及聚合别名只允许白名单字段进入排序条件
proposalCommonService.applySafePageOrder(cnd, pageForm, HISTORY_ORDER_COLUMNS);
sql.setCondition(cnd);
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -1,6 +1,7 @@
package com.budwk.app.zhgh.democratic.proposal.controller.query;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.flow.engine.util.FlowUtil;
import com.budwk.app.flow.entity.ProcessTask;
@@ -31,6 +32,15 @@ import java.util.stream.Collectors;
@Slf4j
@Ok("json:full")
public class ProposalQueryUnitReplyController {
/**
* 承办单位办理统计允许排序的字段白名单
*/
private static final Set<String> UNIT_REPLY_ORDER_COLUMNS = Set.of(
"unitName", "sum", "masterSum", "masterReplySum", "masterNoReplySum",
"slaveSum", "slaveReplySum", "slaveNoReplySum"
);
@Inject
private Dao dao;
@Inject
@@ -45,9 +55,10 @@ public class ProposalQueryUnitReplyController {
@At
@SaCheckPermission("proposal.query.unitReply")
public Result data(String sessionId, String undertakeUnitId, String caseFilingResult) {
public Result data(PageForm pageForm, String sessionId, String undertakeUnitId, String caseFilingResult) {
/*
* 查询参数说明
* pageForm表格排序字段和排序方向
* sessionId教代会届次ID用于限定统计的提案范围
* undertakeUnitId承办单位ID用于只统计某个承办单位为空时统计全部承办单位
* caseFilingResult立案结果字典值对应 proposal_info.caseFilingResult为空时不限制立案结果
@@ -139,6 +150,8 @@ public class ProposalQueryUnitReplyController {
tableData.add(tableRow);
});
// 统计结果生成后在服务层按白名单字段排序避免前端字段参与任意业务处理
proposalCommonService.sortStatisticsRows(tableData, pageForm, UNIT_REPLY_ORDER_COLUMNS);
return Result.success().addData(Map.of("tableData", tableData, "slaveNeedReply", slaveNeedReply));
}
}
@@ -21,6 +21,7 @@ import org.nutz.mvc.annotation.Param;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.Map;
@IocBean
@At("/platform/proposal/query/yearReport")
@@ -29,6 +30,22 @@ import javax.validation.Valid;
@Api(tags = "提案年度报告")
public class ProposalQueryYearReportController {
/**
* 年度报告表格允许排序的字段映射值为查询中的真实字段或安全别名
*/
private static final Map<String, String> YEAR_REPORT_ORDER_COLUMNS = Map.ofEntries(
Map.entry("code", "info.code"),
Map.entry("name", "info.name"),
Map.entry("createUserName", "info.createUserName"),
Map.entry("typeName", "typeName"),
Map.entry("delegationName", "delegationName"),
Map.entry("caseFilingResult", "info.caseFilingResult"),
Map.entry("isConsolidation", "info.isConsolidation"),
Map.entry("undertakeUnits", "undertakeUnitsOrder"),
Map.entry("curTaskName", "curTaskName"),
Map.entry("instanceState", "instanceState")
);
@Inject
private ProposalCommonService proposalCommonService;
@@ -56,7 +73,8 @@ public class ProposalQueryYearReportController {
ins.processDefineId instanceProcessDefineId,
t.displayName curTaskName,
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 1 THEN pru.unitName END) AS masterUnitName,
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 0 THEN pru.unitName END) AS slaveUnitNames
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 0 THEN pru.unitName END) AS slaveUnitNames,
GROUP_CONCAT(DISTINCT pru.unitName ORDER BY pru.isMaster DESC, pru.unitName) AS undertakeUnitsOrder
FROM
proposal_info info
LEFT JOIN proposal_type type on type.id = info.typeId
@@ -70,8 +88,12 @@ public class ProposalQueryYearReportController {
$condition
""");
Cnd cnd = Cnd.NEW();
// 年度报告必须按页面选中的教代会过滤避免共享参数未处理sessionId导致跨届次查询
cnd.andEX("info.sessionId", "=", pageForm.getSessionId());
ProposalQueryComprehensiveParam.buildSearch(cnd, pageForm);
cnd.groupBy("info.id");
// 年度报告包含多表字段及聚合别名只允许白名单字段进入排序条件
proposalCommonService.applySafePageOrder(cnd, pageForm, YEAR_REPORT_ORDER_COLUMNS);
sql.setCondition(cnd);
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(),pageForm.getPageSize(),sql);
return Result.success(pagination);
@@ -1,6 +1,8 @@
package com.budwk.app.zhgh.democratic.proposal.controller.query;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.flow.engine.util.FlowUtil;
import com.budwk.app.flow.entity.ProcessInstance;
@@ -10,6 +12,7 @@ import com.budwk.app.sys.models.Sys_dict;
import com.budwk.app.sys.services.SysDictService;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalConfig;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalReplyUnit;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import io.swagger.annotations.Api;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
@@ -30,10 +33,19 @@ import java.util.stream.Collectors;
@Api(tags = "提案承办单位满意度")
public class ProposalUnderTakeSatisfactionController {
/**
* 满意度统计固定列的排序白名单动态满意度列在查询时追加
*/
private static final Set<String> SATISFACTION_ORDER_COLUMNS = Set.of(
"unitName", "sum", "masterSum", "slaveSum"
);
@Inject
private Dao dao;
@Inject
private SysDictService sysDictService;
@Inject
private ProposalCommonService proposalCommonService;
@At("")
@Ok("beetl:/platform/zhgh/democratic/proposal/query/underTakeSatisfaction/index.html")
@@ -42,10 +54,24 @@ public class ProposalUnderTakeSatisfactionController {
}
/**
* 查询指定教代会和承办单位的满意度统计结果
*
* @param pageForm 表格排序参数
* @param sessionId 教代会届次ID
* @param undertakeUnitId 承办单位ID
* @return 满意度统计结果
*/
@At
@SaCheckPermission("proposal.query.underTake.satisfaction")
public Result data(String sessionId, String undertakeUnitId) {
public Result data(PageForm pageForm, String sessionId, String undertakeUnitId) {
List<Sys_dict> feedbackCodes = sysDictService.getSubListByCode("PROPOSAL_FEEDBACK");
Set<String> orderColumns = new HashSet<>(SATISFACTION_ORDER_COLUMNS);
// 动态满意度列仅从后端字典加入白名单不接受前端自行扩展字段
feedbackCodes.stream()
.map(Sys_dict::getCode)
.filter(StrUtil::isNotBlank)
.forEach(orderColumns::add);
// 当前届次所有的提案ID
Sql sql = Sqls.create("select id from proposal_info where sessionId = @sessionId").setParam("sessionId", sessionId);
@@ -109,6 +135,8 @@ public class ProposalUnderTakeSatisfactionController {
tableData.add(tableRow);
});
// 汇总完成后在服务层执行白名单排序数字列按数值顺序处理
proposalCommonService.sortStatisticsRows(tableData, pageForm, orderColumns);
return Result.success(tableData);
}
@@ -111,7 +111,10 @@ public class ProposalExpeditingController {
ProposalSearchParam.buildSearch(cnd, pageForm);
cnd.groupBy("t.id");
cnd.desc("t.createdAt");
// 提案催办列表未指定有效排序时保留任务创建时间倒序
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "expediting")) {
cnd.desc("t.createdAt");
}
sql.setCondition(cnd);
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -78,6 +78,8 @@ public class ProposalSeniorBasicController {
Cnd cnd = Cnd.NEW();
ProposalQueryComprehensiveParam.buildSearch(cnd, pageForm);
cnd.groupBy("info.id");
// 内容修改列表仅允许页面展示字段参与排序
proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "seniorBasic");
sql.setCondition(cnd);
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -78,6 +78,8 @@ public class ProposalSeniorDeleteController {
}
ProposalQueryComprehensiveParam.buildSearch(cnd, pageForm);
cnd.groupBy("info.id");
// 高级删除列表仅允许页面展示字段参与排序
proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "seniorDelete");
sql.setCondition(cnd);
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -93,6 +93,8 @@ public class ProposalSeniorFeedBackController {
ProposalQueryComprehensiveParam.buildSearch(cnd, pageForm);
// cnd.and("latestFeedBack.id","is not",null);
cnd.groupBy("info.id");
// 反馈评分修改列表包含反馈计算列使用页面独立白名单排序
proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "seniorFeedback");
sql.setCondition(cnd);
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -82,6 +82,8 @@ public class ProposalSeniorTypeController {
}
ProposalQueryComprehensiveParam.buildSearch(cnd, pageForm);
cnd.groupBy("info.id");
// 类型修改列表仅允许页面展示字段参与排序
proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "seniorType");
sql.setCondition(cnd);
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -5,23 +5,23 @@ import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Cnd;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.Static;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import javax.validation.Valid;
import java.util.List;
/**
* @version 1.0
@@ -39,6 +39,8 @@ public class ProposalAllFinishedController {
@Inject
private BaseService baseService;
@Inject
private ProposalCommonService proposalCommonService;
@At("")
@Ok("beetl:/platform/zhgh/democratic/proposal/transact/allFinished/index.html")
@@ -62,7 +64,9 @@ public class ProposalAllFinishedController {
ins.variable instanceVariable,
ins.processDefineId instanceProcessDefineId
FROM wf_process_instance ins
LEFT JOIN proposal_info info ON info.id = ins.businessNo
/* Limit all-finished data to proposal workflow instances and discard other module instances. */
INNER JOIN wf_process_define def ON def.id = ins.processDefineId AND def.name = 'JDHTA_NC'
INNER JOIN proposal_info info ON info.id = ins.businessNo
LEFT JOIN proposal_type type on type.id = info.typeId
LEFT JOIN teacher_congress_session tcs on tcs.id = info.sessionId
LEFT JOIN teacher_congress_delegation tcd on tcd.id = info.delegationId
@@ -72,9 +76,12 @@ public class ProposalAllFinishedController {
cnd.and("ins.state", "=", "20");
if(!AuthUtil.hasRole(RoleConstant.SYSADMIN.name())){
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
// Non-admin users can only see proposal flows where they participated in at least one task.
cnd.and(new Static("EXISTS (SELECT 1 FROM wf_process_task wt INNER JOIN wf_process_task_actor wta ON wta.processTaskId = wt.id WHERE wt.processInstanceId = ins.id AND wta.actorId = '" + SecurityUtil.getUserId() + "')"));
}
ProposalSearchParam.buildSearch(cnd, pageForm);
// 已办结列表仅允许页面展示字段参与排序
proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "allFinished");
sql.setCondition(cnd);
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(),pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -10,6 +10,7 @@ import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam;
import com.budwk.app.zhgh.democratic.proposal.service.ProposalCaseCheckService;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
@@ -42,6 +43,8 @@ public class ProposalCaseCheckController {
private BaseService baseService;
@Inject
private ProposalCaseCheckService proposalCaseCheckService;
@Inject
private ProposalCommonService proposalCommonService;
@At("")
@Ok("beetl:/platform/zhgh/democratic/proposal/transact/caseCheck/index.html")
@@ -104,7 +107,10 @@ public class ProposalCaseCheckController {
cnd.andEX("vu.unionId", "=", pageForm.getUnionId());
ProposalSearchParam.buildSearch(cnd, pageForm);
cnd.groupBy("t.id");
cnd.desc("t.createdAt");
// 委员会审查列表未指定有效排序时保留任务创建时间倒序
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "caseCheck")) {
cnd.desc("t.createdAt");
}
sql.setCondition(cnd);
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(),pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -85,7 +85,10 @@ public class ProposalCommissionerController {
ProposalSearchParam.buildSearch(cnd, pageForm);
cnd.and("pco.id", approval ? "is not" : "is", null);
cnd.groupBy("t.id");
cnd.desc("t.createdAt");
// 委员查询包含统计列未指定有效排序时保留任务创建时间倒序
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "commissioner")) {
cnd.desc("t.createdAt");
}
sql.setCondition(cnd);
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -10,6 +10,7 @@ import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam;
import com.budwk.app.zhgh.democratic.proposal.service.ProposalCommitteeFilingService;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
@@ -40,6 +41,8 @@ public class ProposalCommitteeFilingController {
private Dao dao;
@Inject
private ProposalCommitteeFilingService proposalCommitteeFilingService;
@Inject
private ProposalCommonService proposalCommonService;
@At("")
@Ok("beetl:/platform/zhgh/democratic/proposal/transact/committeeFiling/index.html")
@@ -102,7 +105,10 @@ public class ProposalCommitteeFilingController {
cnd.andEX("vu.unionId", "=", pageForm.getUnionId());
ProposalSearchParam.buildSearch(cnd, pageForm);
cnd.groupBy("t.id");
cnd.desc("t.createdAt");
// 立案审核列表未指定有效排序时保留任务创建时间倒序
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "committeeFiling")) {
cnd.desc("t.createdAt");
}
sql.setCondition(cnd);
Pagination pagination = proposalCommitteeFilingService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -118,7 +118,10 @@ public class ProposalCommitteeFilingUnitController {
cnd.andEX("vu.unionId", "=", pageForm.getUnionId());
ProposalSearchParam.buildSearch(cnd, pageForm);
cnd.groupBy("t.id");
cnd.desc("t.createdAt");
// 确认承办单位列表未指定有效排序时保留任务创建时间倒序
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "committeeFilingUnit")) {
cnd.desc("t.createdAt");
}
sql.setCondition(cnd);
Pagination pagination = proposalCommitteeFilingUnitService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -10,6 +10,7 @@ import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
@@ -33,6 +34,8 @@ public class ProposalControlController {
@Inject
private BaseService baseService;
@Inject
private ProposalCommonService proposalCommonService;
@At("")
@Ok("beetl:/platform/zhgh/democratic/proposal/transact/control/index.html")
@@ -84,7 +87,10 @@ public class ProposalControlController {
ProposalSearchParam.buildSearch(cnd, pageForm);
cnd.groupBy("t.id");
cnd.desc("t.createdAt");
// 状态调整列表未指定有效排序时保留任务创建时间倒序
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "control")) {
cnd.desc("t.createdAt");
}
sql.setCondition(cnd);
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -118,7 +118,10 @@ public class ProposalDelegationController {
}
ProposalSearchParam.buildSearch(cnd, pageForm);
cnd.groupBy("t.id");
cnd.desc("t.createdAt");
// 团长审核列表未指定有效排序时保留任务创建时间倒序
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "delegation")) {
cnd.desc("t.createdAt");
}
sql.setCondition(cnd);
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(),pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -107,7 +107,10 @@ public class ProposalFeedbackEvaluationController {
}
ProposalSearchParam.buildSearch(cnd, pageForm);
cnd.groupBy("t.id");
cnd.desc("t.createdAt");
// 反馈评分列表未指定有效排序时保留任务创建时间倒序
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "feedbackEvaluation")) {
cnd.desc("t.createdAt");
}
sql.setCondition(cnd);
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -6,7 +6,6 @@ import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
@@ -99,10 +98,9 @@ public class ProposalInviteController {
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
}
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
// 页面排序仅使用邀请列表白名单未指定有效字段时保留创建时间倒序
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "invite")) {
cnd.desc("info.createdAt");
} else {
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
}
cnd.groupBy("t.id");
sql.setCondition(cnd);
@@ -62,6 +62,28 @@ import java.util.Objects;
@Ok("json:full")
@Api(tags = "提案管理-我的提案")
public class ProposalMineController {
/** 我的提案列表允许排序的页面字段与 SQL 字段映射。 */
private static final Map<String, String> MINE_ORDER_COLUMNS = Map.of(
"code", "info.code",
"name", "info.name",
"createTime", "info.createTime",
"typeName", "type.name",
"sourceName", "sd.name",
"sessionName", "tcs.fullName",
"taskName", "t.displayName",
"instanceState", "ins.state"
);
/** 邀请附议人列表允许排序的页面字段与 SQL 字段映射。 */
private static final Map<String, String> SECONDER_ORDER_COLUMNS = Map.of(
"loginName", "t1.loginName",
"userName", "t1.userName",
"sex", "t1.sex",
"unitName", "t1.unitName",
"unionName", "t1.unionName",
"delegationName", "t2.name"
);
@Inject
private Dao dao;
@Inject
@@ -128,6 +150,8 @@ public class ProposalMineController {
cnd.and("info.createdBy", "=", SecurityUtil.getUserId());
ProposalSearchParam.buildSearch(cnd, pageForm);
cnd.andEX("info.sessionId", "=", sessionId);
// 排序字段必须经过白名单映射避免前端参数直接参与 SQL 排序
proposalCommonService.applySafePageOrder(cnd, pageForm, MINE_ORDER_COLUMNS);
cnd.groupBy("info.id");
sql.setCondition(cnd);
Pagination<NutMap> pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
@@ -255,6 +279,8 @@ public class ProposalMineController {
seg.orLike("t1.userName", pageForm.getSearchKeyword());
cnd.and(seg);
}
// 邀请列表与主列表使用独立白名单确保关联表字段排序准确且安全
proposalCommonService.applySafePageOrder(cnd, pageForm, SECONDER_ORDER_COLUMNS);
sql.setCondition(cnd);
Pagination<ProposalInviteSeconderVO> pagination = proposalCommonService.listPageVO(pageForm, sql, ProposalInviteSeconderVO.class);
return Result.success(pagination);
@@ -9,6 +9,7 @@ import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
@@ -39,6 +40,8 @@ public class ProposalPreAuditController {
@Inject
private BaseService baseService;
@Inject
private ProposalCommonService proposalCommonService;
@At("")
@Ok("beetl:/platform/zhgh/democratic/proposal/transact/preAudit/index.html")
@@ -102,7 +105,10 @@ public class ProposalPreAuditController {
cnd.andEX("vu.unionId", "=", pageForm.getUnionId());
ProposalSearchParam.buildSearch(cnd, pageForm);
cnd.groupBy("t.id");
cnd.desc("t.createdAt");
// 预审核列表未指定有效排序时保留任务创建时间倒序
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "preAudit")) {
cnd.desc("t.createdAt");
}
sql.setCondition(cnd);
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(),pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -135,7 +135,10 @@ public class ProposalSchoolLeaderApprovalController {
cnd.andEX("vu.unionId", "=", pageForm.getUnionId());
ProposalSearchParam.buildSearch(cnd, pageForm);
cnd.groupBy("t.id");
cnd.desc("t.createdAt");
// 分管领导审批列表未指定有效排序时保留任务创建时间倒序
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "schoolLeaderApproval")) {
cnd.desc("t.createdAt");
}
sql.setCondition(cnd);
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -21,6 +21,7 @@ import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalSecond;
import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam;
import com.budwk.app.zhgh.democratic.proposal.service.ProposalSecondedService;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
@@ -41,6 +42,7 @@ import org.nutz.mvc.annotation.Param;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
/**
* 附议提案
@@ -51,6 +53,17 @@ import java.util.List;
@Ok("json:full")
@Api(tags = "提案管理系统-提案附议")
public class ProposalSecondedController {
/** 提案附议列表允许排序的页面字段与 SQL 字段或查询别名映射。 */
private static final Map<String, String> ORDER_COLUMNS = Map.of(
"code", "info.code",
"name", "info.name",
"createUserName", "info.createUserName",
"typeName", "type.name",
"delegationName", "tcd.name",
"taskActorName", "taskActorName",
"curTaskName", "curTaskName",
"instanceState", "ins.state"
);
@Inject
private Dao dao;
@@ -59,6 +72,8 @@ public class ProposalSecondedController {
@Inject
private ProposalSecondedService proposalSecondedService;
@Inject
private ProposalCommonService proposalCommonService;
@Inject
private FlowCommonService flowCommonService;
@At("")
@@ -141,7 +156,10 @@ public class ProposalSecondedController {
}
ProposalSearchParam.buildSearch(cnd, pageForm);
cnd.groupBy("t.id");
cnd.desc("t.createdAt");
// 用户未指定有效排序字段时继续沿用原有任务创建时间倒序
if (!proposalCommonService.applySafePageOrder(cnd, pageForm, ORDER_COLUMNS)) {
cnd.desc("t.createdAt");
}
sql.setCondition(cnd);
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -96,6 +96,8 @@ public class ProposalUnSubmitQueryController {
cnd.and("info.sessionId", "=", pageForm.getSessionId());
}
// 未提交查询包含子查询统计列统一通过页面独立白名单排序
proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "unSubmitQuery");
sql.setCondition(cnd);
Pagination<NutMap> pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -137,6 +137,8 @@ public class ProposalUnderTakeReadController {
ProposalSearchParam.buildSearch(cnd, pageForm);
cnd.groupBy("info.id","task.id","JSON_EXTRACT( unit_data.DATA, '$.underTakeId' )");
// 初步阅览列表的计算字段使用固定查询别名排序
proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "unitRead");
// cnd.groupBy("task.id");
// cnd.groupBy("JSON_EXTRACT( unit_data.DATA, '$.underTakeId' ) ");
sql.setCondition(cnd);
@@ -148,7 +148,10 @@ public class ProposalUnderTakeReplyController {
}
ProposalSearchParam.buildSearch(cnd, pageForm);
cnd.groupBy("t.id");
cnd.desc("t.createdAt");
// 承办答复列表未指定有效排序时继续按任务创建时间倒序
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "unitReply")) {
cnd.desc("t.createdAt");
}
sql.setCondition(cnd);
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
@@ -100,7 +100,10 @@ public class ProposalUndertakeSuggestionController {
cnd.groupBy("t.id");
cnd.having(Cnd.where("count", approval ? ">" : "=", 0));
cnd.desc("t.createdAt");
// 承办意见列表未指定有效排序时继续按任务创建时间倒序
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "suggestion")) {
cnd.desc("t.createdAt");
}
sql.setCondition(cnd);
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -80,6 +80,8 @@ public class ProposalViceDelegationController {
cnd.and("info.delegationId", "in", proposalCommonService.getSelfManageDelegationIds());
}
ProposalSearchParam.buildSearch(cnd, pageForm);
// 副团长查阅列表仅允许页面已展示字段参与排序
proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "viceDelegation");
cnd.groupBy("info.id");
sql.setCondition(cnd);
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
@@ -21,6 +21,8 @@ public class ProposalQueryComprehensiveParam extends PageForm {
private String code;
@ApiModelProperty(name = "教代会ID")
private String sessionId;
@ApiModelProperty(name = "search session id")
private String searchSessionId;
@ApiModelProperty(name = "教代会ID")
private String[] sessionIds;
@ApiModelProperty(name = "代表团ID")
@@ -79,6 +81,8 @@ public class ProposalQueryComprehensiveParam extends PageForm {
} else {
// cnd.andEX("info.sessionId", "=", searchParam.getSessionId());
}
// Isolated single-session filter for pages that should not reuse sessionId semantics.
cnd.andEX("info.sessionId", "=", searchParam.getSearchSessionId());
//提案名称
if (StrUtil.isNotBlank(searchParam.getName())) {
@@ -1,17 +1,49 @@
package com.budwk.app.zhgh.democratic.proposal.service.common;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalUndertake;
import org.nutz.dao.Cnd;
import org.nutz.lang.util.NutMap;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.util.List;
import java.util.Map;
import java.util.Set;
public interface ProposalCommonService extends BaseService<ProposalInfo> {
/**
* 按页面允许的字段白名单添加排序条件避免前端字段直接进入SQL
*
* @param cnd 查询条件
* @param pageForm 分页及排序参数
* @param allowedOrderColumns 前端字段与数据库字段或查询别名的映射
* @return 是否成功添加安全排序条件
*/
boolean applySafePageOrder(Cnd cnd, PageForm pageForm, Map<String, String> allowedOrderColumns);
/**
* 按提案列表页面注册的独立字段白名单添加排序条件
*
* @param cnd 查询条件
* @param pageForm 分页及排序参数
* @param pageCode 提案列表页面代码
* @return 是否成功添加安全排序条件
*/
boolean applySafeProposalListOrder(Cnd cnd, PageForm pageForm, String pageCode);
/**
* 按白名单字段对已汇总的统计结果进行后端排序
*
* @param rows 统计结果行
* @param pageForm 排序字段及方向参数
* @param allowedOrderColumns 允许排序的统计字段
*/
void sortStatisticsRows(List<NutMap> rows, PageForm pageForm, Set<String> allowedOrderColumns);
/**
* 查询提案信息
*/
@@ -9,8 +9,10 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.base.utils.SysOfficeTemplateUtil;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.engine.FlowEngine;
@@ -58,7 +60,9 @@ import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.text.Collator;
import java.util.*;
import java.util.stream.Collectors;
@@ -66,6 +70,9 @@ import java.util.stream.Collectors;
@IocBean(args = {"refer:dao"})
public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> implements ProposalCommonService {
/** PC端提案列表页面的排序字段白名单,页面代码由对应Controller固定传入。 */
private static final Map<String, Map<String, String>> PROPOSAL_LIST_ORDER_COLUMNS = createProposalListOrderColumns();
//找出富文本里面上传的图片
static String IMG_SRC_REGEX = "<img\\s+[^>]*src\\s*=\\s*([\"'])(/platform/sys/file/download\\?id=.*?)\\1[^>]*>";
@@ -88,6 +95,178 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
super(dao);
}
/**
* 使用业务页面提供的字段白名单构建排序条件非法字段或非法排序方向将被忽略
*
* @param cnd 查询条件
* @param pageForm 分页及排序参数
* @param allowedOrderColumns 前端字段与数据库字段或查询别名的映射
* @return 是否成功添加安全排序条件
*/
@Override
public boolean applySafePageOrder(Cnd cnd, PageForm pageForm, Map<String, String> allowedOrderColumns) {
if (cnd == null || pageForm == null || allowedOrderColumns == null) {
return false;
}
// 仅允许白名单中的字段参与排序防止构造任意SQL排序字段
String orderColumn = allowedOrderColumns.get(pageForm.getPageOrderName());
String orderBy = PageUtil.getOrder(pageForm.getPageOrderBy());
if (StrUtil.isBlank(orderColumn) || StrUtil.isBlank(orderBy)) {
return false;
}
cnd.orderBy(orderColumn, orderBy);
return true;
}
/**
* 根据页面代码读取独立白名单并应用排序未注册页面不会接受前端排序字段
*
* @param cnd 查询条件
* @param pageForm 分页及排序参数
* @param pageCode 提案列表页面代码
* @return 是否成功添加安全排序条件
*/
@Override
public boolean applySafeProposalListOrder(Cnd cnd, PageForm pageForm, String pageCode) {
if (StrUtil.isBlank(pageCode)) {
return false;
}
return applySafePageOrder(cnd, pageForm, PROPOSAL_LIST_ORDER_COLUMNS.get(pageCode));
}
/**
* 创建各列表页独立排序白名单显示字段与真实SQL字段或固定查询别名一一对应
*
* @return 不可变的页面排序白名单
*/
private static Map<String, Map<String, String>> createProposalListOrderColumns() {
Map<String, Map<String, String>> pages = new HashMap<>();
pages.put("invite", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "sessionName", "tcs.fullName", "delegationName", "tcd.name", "curTaskName", "curTaskName", "instanceState", "ins.state"));
pages.put("viceDelegation", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "sessionName", "tcs.fullName", "delegationName", "tcd.name", "curTaskName", "curTaskName", "instanceState", "ins.state"));
pages.put("unSubmitQuery", orderColumns("proposalCode", "info.code", "proposalName", "info.name", "username", "vu.username", "mobile", "vu.mobile", "unitName", "vu.unitName", "delegationName", "tcd.name", "typeName", "pt.name", "mannerName", "manner.name", "secondedNum", "secondedNum", "secondedAgreeNum", "secondedAgreeNum", "delegationAudit", "delegationAudit"));
pages.put("unitReply", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "caseFilingResult", "info.caseFilingResult", "merge", "merge", "underTakeName", "underTakeName", "underTakeIsMaster", "underTakeIsMaster", "curTaskName", "curTaskName", "auditUser", "auditUser", "instanceState", "ins.state", "transfer", "transferUserName"));
pages.put("unitRead", orderColumns("code", "info.code", "name", "info.name", "createUserName", "info.createUserName", "delegationName", "tcd.name", "caseFilingResult", "info.caseFilingResult", "isConsolidation", "isConsolidation", "isMasterUnderTake", "isMasterUnderTake", "underTakeName", "underTakeName", "processInstanceNodeName", "inst.processInstanceNodeName"));
pages.put("suggestion", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "sessionName", "tcs.fullName", "delegationName", "tcd.name", "suggestUnits", "info.suggestUnits", "count", "count", "curTaskName", "curTaskName", "instanceState", "ins.state"));
pages.put("commissioner", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "sessionName", "tcs.fullName", "delegationName", "tcd.name", "CONFIRM_FILING_COUNT", "CONFIRM_FILING_COUNT", "SUGGESTION_COUNT", "SUGGESTION_COUNT", "NOT_COUNT", "NOT_COUNT", "taskName", "taskName", "instanceState", "ins.state"));
pages.put("feedbackEvaluation", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "merge", "merge", "curTaskName", "curTaskName", "instanceState", "ins.state"));
pages.put("delegation", standardOrderColumns());
pages.put("control", standardOrderColumns());
pages.put("schoolLeaderApproval", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "undertakeUnits", "masterUnitName", "merge", "merge", "curTaskName", "curTaskName", "auditUser", "auditUser", "instanceState", "ins.state"));
pages.put("committeeFilingUnit", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "sessionName", "tcs.fullName", "caseFilingResult", "info.caseFilingResult", "delegationName", "tcd.name", "curTaskName", "curTaskName", "instanceState", "ins.state"));
pages.put("allFinished", orderColumns("code", "info.code", "name", "info.name", "createUserName", "info.createUserName", "typeName", "type.name", "sessionName", "tcs.fullName", "delegationName", "tcd.name", "instanceState", "ins.state"));
pages.put("preAudit", standardOrderColumns());
pages.put("committeeFiling", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "sessionName", "tcs.fullName", "caseFilingResult", "info.caseFilingResult", "delegationName", "tcd.name", "curTaskName", "curTaskName", "instanceState", "ins.state"));
pages.put("caseCheck", orderColumns("code", "info.code", "name", "info.name", "createUserName", "info.createUserName", "typeName", "type.name", "sessionName", "tcs.fullName", "delegationName", "tcd.name", "curTaskName", "curTaskName", "instanceState", "ins.state", "finishTime", "t.finishTime"));
pages.put("exportComprehensive", orderColumns("code", "info.code", "name", "info.name", "createUserName", "info.createUserName", "typeName", "type.name", "sessionName", "tcs.fullName", "delegationName", "tcd.name", "caseFilingResult", "info.caseFilingResult", "caseFilingType", "info.caseFilingType", "merge", "merge", "brief", "info.brief", "measures", "info.measures", "undertakeUnits", "masterUnitName", "curTaskName", "curTaskName", "instanceState", "ins.state"));
pages.put("exportSingleCustom", standardOrderColumns());
pages.put("configUnit", orderColumns("name", "t1.name", "code", "t1.code", "unitLeader", "u2.username"));
pages.put("configType", orderColumns("name", "name", "code", "code"));
pages.put("seniorBasic", seniorOrderColumns());
pages.put("expediting", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "delegationName", "tcd.name", "caseFilingResult", "info.caseFilingResult", "curTaskName", "curTaskName", "underTakeName", "underTakeName", "taskName", "t.displayName", "actorName", "ta.actorName", "instanceState", "ins.state"));
pages.put("seniorDelete", seniorOrderColumns());
pages.put("seniorFeedback", mergeOrderColumns(seniorOrderColumns(), orderColumns("tf_feedback", "tf_feedback")));
pages.put("seniorType", seniorOrderColumns());
return Collections.unmodifiableMap(pages);
}
/** 创建常规流程列表共用的字段映射。 */
private static Map<String, String> standardOrderColumns() {
return orderColumns("code", "info.code", "name", "info.name", "createUserName", "info.createUserName", "typeName", "type.name", "sessionName", "tcs.fullName", "delegationName", "tcd.name", "curTaskName", "curTaskName", "instanceState", "ins.state");
}
/** 创建高级管理列表共用的字段映射。 */
private static Map<String, String> seniorOrderColumns() {
return orderColumns("code", "info.code", "name", "info.name", "createUserName", "info.createUserName", "typeName", "type.name", "delegationName", "tcd.name", "caseFilingResult", "info.caseFilingResult", "isConsolidation", "merge", "curTaskName", "curTaskName", "instanceState", "ins.state");
}
/**
* 按前端字段SQL字段成对创建不可变映射
*
* @param mappings 交替排列的前端字段和SQL字段
* @return 不可变字段映射
*/
private static Map<String, String> orderColumns(String... mappings) {
if (mappings == null || mappings.length % 2 != 0) {
throw new IllegalArgumentException("排序字段映射必须成对配置");
}
Map<String, String> columns = new LinkedHashMap<>();
for (int i = 0; i < mappings.length; i += 2) {
columns.put(mappings[i], mappings[i + 1]);
}
return Collections.unmodifiableMap(columns);
}
/** 合并基础字段和页面扩展字段,并返回不可变映射。 */
private static Map<String, String> mergeOrderColumns(Map<String, String> base, Map<String, String> extension) {
Map<String, String> columns = new LinkedHashMap<>(base);
columns.putAll(extension);
return Collections.unmodifiableMap(columns);
}
/**
* 按白名单字段对统计结果执行后端排序数字按数值比较文本按中文区域规则比较
*
* @param rows 统计结果行
* @param pageForm 排序字段及方向参数
* @param allowedOrderColumns 允许排序的统计字段
*/
@Override
public void sortStatisticsRows(List<NutMap> rows, PageForm pageForm, Set<String> allowedOrderColumns) {
if (rows == null || pageForm == null || allowedOrderColumns == null) {
return;
}
String orderName = pageForm.getPageOrderName();
String orderBy = pageForm.getPageOrderBy();
boolean ascending = "ascending".equals(orderBy);
boolean descending = "descending".equals(orderBy);
if (StrUtil.isBlank(orderName) || !allowedOrderColumns.contains(orderName) || (!ascending && !descending)) {
return;
}
// 空值始终排在末尾降序只反转有效值的比较结果
rows.sort((leftRow, rightRow) -> {
Object leftValue = leftRow.get(orderName);
Object rightValue = rightRow.get(orderName);
if (leftValue == null && rightValue == null) {
return 0;
}
if (leftValue == null) {
return 1;
}
if (rightValue == null) {
return -1;
}
int result = compareStatisticsValue(leftValue, rightValue);
return descending ? -result : result;
});
}
/**
* 比较统计字段值兼容不同整数类型以及中文文本
*
* @param leftValue 左侧字段值
* @param rightValue 右侧字段值
* @return 标准比较结果
*/
private int compareStatisticsValue(Object leftValue, Object rightValue) {
if (leftValue instanceof Number && rightValue instanceof Number) {
BigDecimal leftNumber = new BigDecimal(leftValue.toString());
BigDecimal rightNumber = new BigDecimal(rightValue.toString());
return leftNumber.compareTo(rightNumber);
}
String leftText = String.valueOf(leftValue);
String rightText = String.valueOf(rightValue);
String leftPercent = StrUtil.removeSuffix(leftText, "%");
String rightPercent = StrUtil.removeSuffix(rightText, "%");
// 百分比展示值按实际数值比较避免字符串顺序导致10%排在9%之前
if (leftText.endsWith("%") && rightText.endsWith("%")
&& NumberUtil.isNumber(leftPercent) && NumberUtil.isNumber(rightPercent)) {
return new BigDecimal(leftPercent).compareTo(new BigDecimal(rightPercent));
}
return Collator.getInstance(Locale.CHINA).compare(leftText, rightText);
}
@Override
public NutMap info(String id) {
if (StrUtil.isBlank(id)) {
@@ -118,7 +118,11 @@ public class ProposalWriteServiceImpl extends BaseServiceImpl<ProposalInfo> impl
@Override
public List<Sys_dict> listSourceByCode(String code) {
Sys_dict dict = sysDictService.fetch(Cnd.where("code", "=", code));
return dict == null ? new ArrayList<>() : sysDictService.query(Cnd.where("parentId", "=", Strings.sNull(dict.getId())).asc("location"));
return dict == null ? new ArrayList<>() : sysDictService.query(
Cnd.NEW().where("parentId", "=", Strings.sNull(dict.getId()))
.and("disabled","=",0)
.asc("location")
);
}
@Override
@@ -7,7 +7,6 @@ import cn.hutool.core.lang.tree.TreeNode;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
@@ -20,15 +19,14 @@ import com.budwk.app.sys.services.SysUserService;
import com.budwk.app.zhgh.democratic.teachercongress.delegation.models.Teacher_congress_delegation;
import com.budwk.app.zhgh.democratic.teachercongress.delegation.models.Teacher_congress_delegation_union;
import com.budwk.app.zhgh.democratic.teachercongress.delegation.models.Teacher_congress_delegation_unit;
import com.budwk.app.zhgh.democratic.teachercongress.delegation.service.TeacherCongressDelegationService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
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.entity.Record;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.ioc.aop.Aop;
@@ -43,7 +41,6 @@ import org.nutz.mvc.annotation.Param;
import javax.validation.Valid;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@IocBean
@At("/platform/teacherCongress/delegation")
@@ -63,6 +60,9 @@ public class TeacherCongressDelegationController {
@Inject
private SysRoleService sysRoleService;
@Inject
private TeacherCongressDelegationService teacherCongressDelegationService;
@At("")
@Ok("beetl:/platform/zhgh/democratic/teachercongress/delegation/index.html")
@SaCheckPermission("tc.delegation")
@@ -288,97 +288,50 @@ public class TeacherCongressDelegationController {
}
/**
* 查询团长
* 查询代表团负责人副团长返回多条以兼容V3多个副团长
*
* @param sessionId
* @param delegationId
* @return
* @param sessionId 届次ID
* @param delegationId 代表团ID
* @param type 负责人角色编码
* @return 负责人列表
*/
@At
@SaCheckPermission("tc.delegation")
public Result headUser(@Valid String sessionId, @Valid String delegationId, @Param(value = "type", df = "TEACHER_CONGRESS_DELEGATION_HEAD") String type) {
Sys_role role = null;
if (type.equals(RoleConstant.TEACHER_CONGRESS_DELEGATION_HEAD.name())) {
role = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_HEAD);
} else if (type.equals(RoleConstant.TEACHER_CONGRESS_VICE_DELEGATION_HEAD.name())) {
role = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_VICE_DELEGATION_HEAD);
} else if (type.equals(RoleConstant.TEACHER_CONGRESS_DELEGATION_CONTACT.name())) {
role = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_CONTACT);
} else {
throw new BaseException("参数错误");
}
Record record = dao.fetch("sys_user_role", Cnd.where("tcSessionId", "=", sessionId).and("tcDelegationId", "=", delegationId).and("roleId", "=", role.getId()));
String userId = Optional.ofNullable(record).map(r -> r.getString("userId")).orElse(null);
Sql sql = Sqls.create("""
SELECT
u.id as userId,
u.loginname as loginName,
u.username as userName,
u.mobile,
n.name as unitName
FROM
sys_user u
LEFT JOIN sys_unit n on n.id = u.unitId
WHERE
u.id = @userId
""");
sql.setParam("userId", userId);
sql.setCallback(Sqls.callback.map());
dao.execute(sql);
NutMap user = (NutMap) sql.getResult();
return Result.success().addData(user);
List<NutMap> users = teacherCongressDelegationService.listHeadUsers(sessionId, delegationId, type);
return Result.success().addData(users);
}
/**
* 设置团长
* 设置代表团团长副团长和联络人
*
* @param delegationId 代表团ID
* @param sessionId 届次ID
* @param userId 团长id
* @param viceUserId 副团长id
* @param contactUserId 联络人
* @return
* @param userId 团长ID
* @param viceUserIds 副团长ID数组
* @param contactUserId 联络人ID
* @return 操作结果
*/
@At
@SaCheckPermission("tc.delegation")
@Aop(TransAop.READ_COMMITTED)
public Result insertHead(@Valid String delegationId, @Valid String sessionId, @Valid String userId, @Valid String viceUserId, String contactUserId) {
// 团长
Sys_role role = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_HEAD);
dao.clear(Sys_user_role.class, Cnd.where(Sys_user_role::getTcDelegationId, "=", delegationId).and(Sys_user_role::getTcSessionId, "=", sessionId).and(Sys_user_role::getRoleId, "=", role.getId()));
dao.insert("sys_user_role", Chain.make("userId", userId).add("roleId", role.getId()).add("tcDelegationId", delegationId).add("tcSessionId", sessionId));
// 副团长
Sys_role role2 = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_VICE_DELEGATION_HEAD);
dao.clear(Sys_user_role.class, Cnd.where(Sys_user_role::getTcDelegationId, "=", delegationId).and(Sys_user_role::getTcSessionId, "=", sessionId).and(Sys_user_role::getRoleId, "=", role2.getId()));
dao.insert("sys_user_role", Chain.make("userId", viceUserId).add("roleId", role2.getId()).add("tcDelegationId", delegationId).add("tcSessionId", sessionId));
// 联络人
Sys_role role3 = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_CONTACT);
dao.clear(Sys_user_role.class, Cnd.where(Sys_user_role::getTcDelegationId, "=", delegationId).and(Sys_user_role::getTcSessionId, "=", sessionId).and(Sys_user_role::getRoleId, "=", role3.getId()));
dao.insert("sys_user_role", Chain.make("userId", contactUserId).add("roleId", role3.getId()).add("tcDelegationId", delegationId).add("tcSessionId", sessionId));
sysUserService.clearCache();
public Result insertHead(@Valid String delegationId, @Valid String sessionId, @Valid String userId, @Param("viceUserIds") String[] viceUserIds, String contactUserId) {
teacherCongressDelegationService.setHeadUsers(sessionId, delegationId, userId, viceUserIds, contactUserId);
return Result.success();
}
/**
* 删除团长角色
* 删除代表团负责人角色
*
* @param delegationId
* @param sessionId
* @param userId
* @return
* @param delegationId 代表团ID
* @param sessionId 届次ID
* @param userId 用户ID
* @param type 负责人角色编码
* @return 操作结果
*/
@At
@SaCheckPermission("tc.delegation")
public Result deleteHead(@Valid String delegationId, @Valid String sessionId, @Valid String userId) {
Sys_role role = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_HEAD);
//先删除角色
dao.clear(Sys_user_role.class, Cnd.where(Sys_user_role::getTcDelegationId, "=", delegationId).and(Sys_user_role::getTcSessionId, "=", sessionId).and(Sys_user_role::getRoleId, "=", role.getId()).and(Sys_user_role::getUserId, "=", userId));
sysUserService.clearCache();
public Result deleteHead(@Valid String delegationId, @Valid String sessionId, @Valid String userId, @Param(value = "type", df = "TEACHER_CONGRESS_DELEGATION_HEAD") String type) {
teacherCongressDelegationService.deleteHeadUser(sessionId, delegationId, userId, type);
return Result.success();
}
}
@@ -0,0 +1,44 @@
package com.budwk.app.zhgh.democratic.teachercongress.delegation.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.democratic.teachercongress.delegation.models.Teacher_congress_delegation;
import org.nutz.lang.util.NutMap;
import java.util.List;
/**
* 教代会代表团业务服务
*/
public interface TeacherCongressDelegationService extends BaseService<Teacher_congress_delegation> {
/**
* 按角色查询代表团负责人信息
*
* @param sessionId 届次ID
* @param delegationId 代表团ID
* @param type 负责人角色编码
* @return 负责人用户列表
*/
List<NutMap> listHeadUsers(String sessionId, String delegationId, String type);
/**
* 设置代表团团长副团长和联络人
*
* @param sessionId 届次ID
* @param delegationId 代表团ID
* @param userId 团长用户ID
* @param viceUserIds 副团长用户ID列表
* @param contactUserId 联络人用户ID
*/
void setHeadUsers(String sessionId, String delegationId, String userId, String[] viceUserIds, String contactUserId);
/**
* 删除代表团指定负责人角色
*
* @param sessionId 届次ID
* @param delegationId 代表团ID
* @param userId 用户ID
* @param type 负责人角色编码
*/
void deleteHeadUser(String sessionId, String delegationId, String userId, String type);
}
@@ -0,0 +1,164 @@
package com.budwk.app.zhgh.democratic.teachercongress.delegation.service.impl;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.sys.models.Sys_role;
import com.budwk.app.sys.models.Sys_user_role;
import com.budwk.app.sys.services.SysRoleService;
import com.budwk.app.sys.services.SysUserService;
import com.budwk.app.zhgh.democratic.teachercongress.delegation.models.Teacher_congress_delegation;
import com.budwk.app.zhgh.democratic.teachercongress.delegation.service.TeacherCongressDelegationService;
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.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
import java.util.Arrays;
import java.util.List;
/**
* 教代会代表团业务服务实现
*/
@IocBean(args = {"refer:dao"})
public class TeacherCongressDelegationServiceImpl extends BaseServiceImpl<Teacher_congress_delegation> implements TeacherCongressDelegationService {
@Inject
private SysRoleService sysRoleService;
@Inject
private SysUserService sysUserService;
public TeacherCongressDelegationServiceImpl(Dao dao) {
super(dao);
}
/**
* 按角色查询代表团负责人信息副团长允许返回多条记录以兼容V3多副团长数据
*
* @param sessionId 届次ID
* @param delegationId 代表团ID
* @param type 负责人角色编码
* @return 负责人用户列表
*/
@Override
public List<NutMap> listHeadUsers(String sessionId, String delegationId, String type) {
Sys_role role = getHeadRole(type);
Sql sql = Sqls.create("""
SELECT
u.id as userId,
u.loginname as loginName,
u.username as userName,
u.mobile,
n.name as unitName
FROM
sys_user_role sur
LEFT JOIN sys_user u ON u.id = sur.userId
LEFT JOIN sys_unit n ON n.id = u.unitId
WHERE
sur.tcSessionId = @sessionId
AND sur.tcDelegationId = @delegationId
AND sur.roleId = @roleId
ORDER BY u.loginname
""");
sql.setParam("sessionId", sessionId);
sql.setParam("delegationId", delegationId);
sql.setParam("roleId", role.getId());
return listMap(sql);
}
/**
* 设置代表团团长副团长和联络人保存前按角色清理旧值避免同一角色残留脏数据
*
* @param sessionId 届次ID
* @param delegationId 代表团ID
* @param userId 团长用户ID
* @param viceUserIds 副团长用户ID列表
* @param contactUserId 联络人用户ID
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public void setHeadUsers(String sessionId, String delegationId, String userId, String[] viceUserIds, String contactUserId) {
// 团长仍然保持单人设置
Sys_role headRole = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_HEAD);
replaceRoleUsers(sessionId, delegationId, headRole.getId(), new String[]{userId});
// 副团长支持多人设置完整保留V3迁移后的多个副团长关系
Sys_role viceHeadRole = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_VICE_DELEGATION_HEAD);
replaceRoleUsers(sessionId, delegationId, viceHeadRole.getId(), viceUserIds);
// 联络人保持单人设置未选择时只清理旧联络人
Sys_role contactRole = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_CONTACT);
replaceRoleUsers(sessionId, delegationId, contactRole.getId(), new String[]{contactUserId});
sysUserService.clearCache();
}
/**
* 删除代表团指定负责人角色按角色类型精确删除避免删除副团长时影响团长
*
* @param sessionId 届次ID
* @param delegationId 代表团ID
* @param userId 用户ID
* @param type 负责人角色编码
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public void deleteHeadUser(String sessionId, String delegationId, String userId, String type) {
Sys_role role = getHeadRole(type);
dao().clear(Sys_user_role.class, Cnd.where(Sys_user_role::getTcDelegationId, "=", delegationId)
.and(Sys_user_role::getTcSessionId, "=", sessionId)
.and(Sys_user_role::getRoleId, "=", role.getId())
.and(Sys_user_role::getUserId, "=", userId));
sysUserService.clearCache();
}
/**
* 根据负责人类型解析系统角色集中校验避免 controller 层出现业务分支
*
* @param type 负责人角色编码
* @return 系统角色
*/
private Sys_role getHeadRole(String type) {
if (RoleConstant.TEACHER_CONGRESS_DELEGATION_HEAD.name().equals(type)) {
return sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_HEAD);
}
if (RoleConstant.TEACHER_CONGRESS_VICE_DELEGATION_HEAD.name().equals(type)) {
return sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_VICE_DELEGATION_HEAD);
}
if (RoleConstant.TEACHER_CONGRESS_DELEGATION_CONTACT.name().equals(type)) {
return sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_CONTACT);
}
throw new BaseException("参数错误");
}
/**
* 替换指定角色用户关系先清理再按去重后的用户ID批量插入
*
* @param sessionId 届次ID
* @param delegationId 代表团ID
* @param roleId 角色ID
* @param userIds 用户ID数组
*/
private void replaceRoleUsers(String sessionId, String delegationId, String roleId, String[] userIds) {
dao().clear(Sys_user_role.class, Cnd.where(Sys_user_role::getTcDelegationId, "=", delegationId)
.and(Sys_user_role::getTcSessionId, "=", sessionId)
.and(Sys_user_role::getRoleId, "=", roleId));
if (userIds == null || userIds.length == 0) {
return;
}
Arrays.stream(userIds)
.filter(StrUtil::isNotBlank)
.distinct()
.forEach(userId -> dao().insert("sys_user_role", Chain.make("userId", userId)
.add("roleId", roleId)
.add("tcDelegationId", delegationId)
.add("tcSessionId", sessionId)));
}
}
@@ -0,0 +1,18 @@
package com.budwk.app.zhgh.democratic.teachercongress.prepare.controller.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* 届次提案类型下拉选项
*/
@Data
@AllArgsConstructor
@ApiModel("届次提案类型选项")
public class TeacherCongressSessionProposalTypeVO {
@ApiModelProperty("提案类型,自定义类型保存后也会成为后续届次的可选项")
private String proposalType;
}
@@ -98,6 +98,7 @@ public class MemberApplyBranchUnionApprovalController {
wf_process_task t
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
LEFT JOIN member_apply_record info ON info.id = ins.businessNo
LEFT JOIN vw_user u ON u.id = info.userId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
LEFT JOIN wf_process_task rt ON rt.processInstanceId = ins.id AND rt.taskParentId = t.id AND rt.taskState = 10
@@ -113,6 +113,7 @@ public class MemberApplySchoolUnionApprovalController {
wf_process_task t
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
LEFT JOIN member_apply_record info ON info.id = ins.businessNo
LEFT JOIN vw_user u ON u.id = info.userId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
LEFT JOIN wf_process_task rt ON rt.processInstanceId = ins.id AND rt.taskParentId = t.id AND rt.taskState = 10
@@ -87,6 +87,7 @@ public class MemberApplyUnionGroupApprovalController {
wf_process_task t
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
LEFT JOIN member_apply_record info ON info.id = ins.businessNo
LEFT JOIN vw_user u ON u.id = info.userId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
LEFT JOIN wf_process_task rt ON rt.processInstanceId = ins.id AND rt.taskParentId = t.id AND rt.taskState = 10
@@ -34,6 +34,9 @@ public class MemberApplyPageForm extends PageForm {
private String userAttribute; //人员属性
/** 人员属性多选值,供开启多选查询的页面使用。 */
private List<String> userAttributes;
public static void buildSearch(Cnd cnd, MemberApplyPageForm pageForm) {
if (cnd == null) {
cnd = Cnd.NEW();
@@ -50,6 +53,11 @@ public class MemberApplyPageForm extends PageForm {
cnd.andEX("info.personType","in",pageForm.getPersonTypes());
cnd.andEX("info.preparedBy","in",pageForm.getPreparedBys());
cnd.andEX("u.aidFundMemberUserType","=",pageForm.getAidFundMemberUserType());
cnd.andEX("u.userAttribute","=",pageForm.getUserAttribute());
// 多选值优先使用IN查询未传多选值时保留原单值查询兼容其他调用页面
if (pageForm.getUserAttributes() != null && !pageForm.getUserAttributes().isEmpty()) {
cnd.and("u.userAttribute", "in", pageForm.getUserAttributes());
} else {
cnd.andEX("u.userAttribute", "=", pageForm.getUserAttribute());
}
}
}
@@ -47,6 +47,9 @@ public class MemberChangePageForm extends PageForm {
@ApiModelProperty("人员属性")
private String userAttribute;
@ApiModelProperty("人员属性组")
private List<String> userAttributes;
public void buildSearch(Cnd cnd, String prefix){
if (cnd == null) {
@@ -66,6 +69,11 @@ public class MemberChangePageForm extends PageForm {
cnd.andEX(prefix + "preparedBy", "in", this.getPreparedBys());
cnd.andEX(prefix + "personType", "in", this.getPersonTypes());
cnd.andEX(prefix + "aidFundMemberUserType", "=", this.getAidFundMemberUserType());
cnd.andEX(prefix + "userAttribute", "=", this.getUserAttribute());
// 多选条件优先兼容仍按单值提交人员属性的旧页面
if (this.getUserAttributes() != null && !this.getUserAttributes().isEmpty()) {
cnd.and(prefix + "userAttribute", "in", this.getUserAttributes());
} else {
cnd.andEX(prefix + "userAttribute", "=", this.getUserAttribute());
}
}
}
@@ -162,10 +162,11 @@ public class MemberInfoPageForm extends PageForm {
} else if (Lang.isNotEmpty(this.getPersonTypes())) {
cnd.and(prefix + "personType", "in", this.getPersonTypes());
}
if (StrUtil.isNotBlank(this.getUserAttribute())) {
cnd.and(prefix + "userAttribute", "=", this.getUserAttribute());
} else if (Lang.isNotEmpty(this.getUserAttributes())) {
// 多选条件优先兼容仍按单值提交人员属性的旧页面
if (Lang.isNotEmpty(this.getUserAttributes())) {
cnd.and(prefix + "userAttribute", "in", this.getUserAttributes());
} else if (StrUtil.isNotBlank(this.getUserAttribute())) {
cnd.and(prefix + "userAttribute", "=", this.getUserAttribute());
}
if (StrUtil.isNotBlank(this.getAidFundMemberUserType())) {
@@ -42,6 +42,9 @@ public class MemberManagePageForm extends PageForm {
private String userAttribute;
/** 人员属性多选查询条件。 */
private List<String> userAttributes;
private String changeType;
//变更状态数组
@@ -93,7 +96,12 @@ public class MemberManagePageForm extends PageForm {
cnd.andEX("u.sex", "=", pageForm.getSex());
cnd.andEX("u.personType", "=", pageForm.getPersonType());
cnd.andEX("u.aidFundMemberUserType", "=", pageForm.getAidFundMemberUserType());
cnd.andEX("u.userAttribute", "=", pageForm.getUserAttribute());
// 多选条件优先兼容仍按单值提交人员属性的旧页面
if (Lang.isNotEmpty(pageForm.getUserAttributes())) {
cnd.and("u.userAttribute", "in", pageForm.getUserAttributes());
} else {
cnd.andEX("u.userAttribute", "=", pageForm.getUserAttribute());
}
if (StrUtil.isNotBlank(pageForm.getChangeDateBefore()) && StrUtil.isNotBlank(pageForm.getChangeDateEnd())) {
cnd.and(new Static(String.format("Date(his.changeTime) >= '%s' and Date(his.changeTime) <= '%s'", pageForm.getChangeDateBefore(), pageForm.getChangeDateEnd())));
@@ -148,7 +156,12 @@ public class MemberManagePageForm extends PageForm {
cnd.andEX("record.preparedBy", "=", pageForm.getPreparedBy());
cnd.andEX("record.personType", "=", pageForm.getPersonType());
cnd.andEX("record.aidFundMemberUserType", "=", pageForm.getAidFundMemberUserType());
cnd.andEX("record.userAttribute", "=", pageForm.getUserAttribute());
// 历史记录查询与当前会员查询使用一致的人员属性多选规则
if (Lang.isNotEmpty(pageForm.getUserAttributes())) {
cnd.and("record.userAttribute", "in", pageForm.getUserAttributes());
} else {
cnd.andEX("record.userAttribute", "=", pageForm.getUserAttribute());
}
cnd.groupBy("record.id,task.id");
}
@@ -396,6 +396,12 @@ public class MemberInfoServiceImpl extends BaseServiceImpl<Sys_user> implements
cnd.andEX("mh.personType", "=", pageForm.getPersonType());
cnd.andEX("mh.preparedBy", "=", pageForm.getPreparedBy());
cnd.andEX("mh.userState", "=", pageForm.getUserState());
// 历史台账未复用通用 buildSearch需要在此显式处理人员属性多选条件
if (Lang.isNotEmpty(pageForm.getUserAttributes())) {
cnd.and("mh.userAttribute", "in", pageForm.getUserAttributes());
} else {
cnd.andEX("mh.userAttribute", "=", pageForm.getUserAttribute());
}
if (StrUtil.isAllNotBlank(pageForm.getMemberSearchName(), pageForm.getMemberSearchKeyWord())) {
cnd.and(new SqlExpressionGroup().andLike("u." + pageForm.getMemberSearchName(), pageForm.getMemberSearchKeyWord()));
}
@@ -175,6 +175,12 @@ public class MemberManageServiceImpl extends BaseServiceImpl<Sys_user> implement
cnd.andEX("his.unitId", "=", pageForm.getUnitId());
cnd.andEX("his.personType", "=", pageForm.getPersonType());
cnd.andEX("his.userState", "=", pageForm.getUserState());
// 变更记录查询自行拼接历史表条件需要单独补充人员属性多选及旧单值兼容
if (Lang.isNotEmpty(pageForm.getUserAttributes())) {
cnd.and("his.userAttribute", "in", pageForm.getUserAttributes());
} else {
cnd.andEX("his.userAttribute", "=", pageForm.getUserAttribute());
}
if (StrUtil.isNotBlank(pageForm.getChangeType())){
cnd.and(new Static("JSON_CONTAINS(his.changeTypes, JSON_ARRAY('%s'), '$')".formatted(pageForm.getChangeType())));
}
@@ -170,6 +170,16 @@ module.exports = {
type: Number,
default: 300
},
// wangEditor
placeholder: {
type: String,
default: ""
},
//
topTip: {
type: String,
default: ""
},
menus: {
type: Array,
default: () => {
@@ -205,13 +215,18 @@ module.exports = {
data() {
return {
editor: null,
cursorPos: 0
cursorPos: 0,
topTipElement: null
}
},
mounted() {
this.initEditor()
},
beforeDestroy() {
this.removeTopTip()
},
watch: {
value: {
handler: function (newValue) {
@@ -229,6 +244,13 @@ module.exports = {
})
},
immediate: true
},
topTip: {
handler: function () {
this.$nextTick(() => {
this.renderTopTip()
})
}
}
},
@@ -266,7 +288,43 @@ module.exports = {
this.editor.config.pasteFilterStyle = false
this.editor.config.height = this.height
this.editor.config.menus = this.menus
// wangEditor placeholder create
this.editor.config.placeholder = this.placeholder
this.editor.create()
this.renderTopTip()
},
renderTopTip() {
if (!this.editor || !this.$refs.editorRef) {
return
}
const textContainer = this.$refs.editorRef.querySelector(".w-e-text-container")
if (!textContainer) {
return
}
if (!this.topTip) {
this.removeTopTip()
return
}
if (!this.topTipElement) {
this.topTipElement = document.createElement("div")
this.topTipElement.setAttribute("contenteditable", "false")
this.topTipElement.style.color = "#f56c6c"
this.topTipElement.style.fontSize = "12px"
this.topTipElement.style.lineHeight = "20px"
this.topTipElement.style.padding = "6px 10px 0"
this.topTipElement.style.userSelect = "none"
this.topTipElement.style.pointerEvents = "none"
}
//
this.topTipElement.innerText = this.topTip
if (textContainer.firstChild !== this.topTipElement) {
textContainer.insertBefore(this.topTipElement, textContainer.firstChild)
}
},
removeTopTip() {
if (this.topTipElement && this.topTipElement.parentNode) {
this.topTipElement.parentNode.removeChild(this.topTipElement)
}
},
uploadFiles(resultFiles) {
return resultFiles.map(async (file) => {
@@ -133,6 +133,12 @@ module.exports = {
default: 1024 * 1024 * 10,
required: false
},
//
limit_upload_size: {
type: Boolean,
default: true,
required: false
},
//
upload_text: {
type: String,
@@ -216,7 +222,7 @@ module.exports = {
} else {
tips = tips + '不限格式;'
}
if (fileSize) {
if (this.limit_upload_size && fileSize) {
tips = tips + `单个文件大小不能超过<span style="color: red">${fileSize / 1024 / 1024}</span>M`
}
return tips
@@ -313,7 +319,8 @@ module.exports = {
// image
beforeUpload(file) {
if (file.size > this.upload_size) {
//
if (this.limit_upload_size && file.size > this.upload_size) {
this.$message.error("文件过大,请上传小于" + (this.upload_size / 1024 / 1024).toFixed(0) + "M的文件!")
return false
}
@@ -19,7 +19,7 @@ const branchUnionGroupManage = {
</el-row>
</el-card>
<el-card shadow="never" style="border: 1px solid var(--border-color-lighter); margin-top: 10px">
<el-table :data="tableData" border>
<el-table :data="tableData" :height="table_height ? Math.max(200, table_height - 90) : null" border>
<el-table-column type="index" width="50" label="序号"></el-table-column>
<el-table-column prop="code" label="小组编码" width="120px"></el-table-column>
<el-table-column prop="name" label="小组名称" width="200px"></el-table-column>
@@ -85,6 +85,11 @@ const branchUnionGroupManage = {
union_id: {
type: String,
required: true
},
// 工会小组列表包含分页,父页面传入高度后预留分页区域。
table_height: {
type: Number,
default: null
}
},
data() {
@@ -16,7 +16,7 @@ const branchUnionPartUnitManage = {
</el-row>
</el-card>
<el-card shadow="never" style="border: 1px solid var(--border-color-lighter); margin-top: 10px">
<el-table :data="tableData" border>
<el-table :data="tableData" :height="table_height ? Math.max(200, table_height - 40) : null" border>
<el-table-column type="index" width="50" label="序号"></el-table-column>
<el-table-column prop="unitcode" label="单位编码"></el-table-column>
<el-table-column prop="name" label="单位名称"></el-table-column>
@@ -48,6 +48,11 @@ const branchUnionPartUnitManage = {
union_id: {
type: String,
required: true
},
// 父页面可传入可用高度;默认值保持组件在其他页面的原有展示能力。
table_height: {
type: Number,
default: null
}
},
data() {
@@ -45,7 +45,7 @@ const branchUnionUserManage = {
</el-row>
</el-card>
<el-card shadow="never" style="border: 1px solid var(--border-color-lighter); margin-top: 10px">
<el-table :data="tableData" border size="small">
<el-table :data="tableData" :height="table_height ? Math.max(200, table_height - 40) : null" border size="small">
<el-table-column prop="loginName" label="工号"></el-table-column>
<el-table-column prop="userName" label="姓名"></el-table-column>
<el-table-column prop="mobile" label="联系方式"></el-table-column>
@@ -123,6 +123,11 @@ const branchUnionUserManage = {
union_id: {
type: String,
required: true
},
// 父页面可传入可用高度;默认值保持组件在其他页面的原有展示能力。
table_height: {
type: Number,
default: null
}
},
data() {
@@ -174,7 +174,7 @@ layout("/layouts/platform.html"){
<el-descriptions-item label="报名线路">{{ detail.lineName || '' }}</el-descriptions-item>
<el-descriptions-item label="线路类型">{{ detail.lineType || '' }}</el-descriptions-item>
<el-descriptions-item label="报名酒店">{{ detail.hotelName || '' }}</el-descriptions-item>
<el-descriptions-item label="报名旅行社">{{ detail.travelAgencyName || '' }}</el-descriptions-item>
<el-descriptions-item label="报名服务单位">{{ detail.travelAgencyName || '' }}</el-descriptions-item>
<el-descriptions-item label="出行时间">{{ detail.travelPeriod || '' }}</el-descriptions-item>
<el-descriptions-item v-if="fillBedInfo" label="床型">{{ detail.bedType || '' }}</el-descriptions-item>
<el-descriptions-item v-if="fillBedInfo" label="床位信息">{{ detail.bedInfo || '' }}</el-descriptions-item>
@@ -114,7 +114,7 @@ layout("/layouts/platform.html"){
<el-descriptions-item label="报名线路">{{ detail.lineName || '' }}</el-descriptions-item>
<el-descriptions-item label="线路类型">{{ detail.lineType || '' }}</el-descriptions-item>
<el-descriptions-item label="报名酒店">{{ detail.hotelName || '' }}</el-descriptions-item>
<el-descriptions-item label="报名旅行社">{{ detail.travelAgencyName || '' }}</el-descriptions-item>
<el-descriptions-item label="报名服务单位">{{ detail.travelAgencyName || '' }}</el-descriptions-item>
<el-descriptions-item label="出行时间">{{ detail.travelPeriod || '' }}</el-descriptions-item>
<el-descriptions-item v-if="detailFillBedInfo" label="床型">{{ detail.bedType || '' }}</el-descriptions-item>
<el-descriptions-item v-if="detailFillBedInfo" label="床位信息">{{ detail.bedInfo || '' }}</el-descriptions-item>
@@ -255,7 +255,7 @@ layout("/layouts/platform.html"){
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="报名旅行社">
<el-form-item label="报名服务单位">
<el-input v-model="signupForm.travelAgencyName" :disabled="isDirectFamilyLine(signupForm)" readonly></el-input>
</el-form-item>
</el-col>
@@ -112,8 +112,8 @@ layout("/layouts/platform.html"){
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="旅行社名称" prop="travelAgencyId">
<el-select v-model="formData.travelAgencyId" clearable filterable placeholder="请选择旅行社" style="width: 100%">
<el-form-item label="服务单位名称" prop="travelAgencyId">
<el-select v-model="formData.travelAgencyId" clearable filterable placeholder="请选择服务单位" style="width: 100%">
<el-option v-for="item in travelAgencyOptions" :key="item.id" :label="item.agencyName" :value="item.id"></el-option>
</el-select>
</el-form-item>
@@ -187,7 +187,7 @@ layout("/layouts/platform.html"){
<text-editor v-model="formData.lineContent"></text-editor>
</el-form-item>
<el-form-item label="移动端缩略图" prop="mobileThumb">
<!--旅行社管理保持一致:移动端缩略图使用单图上传并保存URL。 -->
<!--服务单位管理保持一致:移动端缩略图使用单图上传并保存URL。 -->
<file-upload
style="--upload-width: 200px;--upload-height:108px"
:upload_number="1"
@@ -191,7 +191,7 @@ layout("/layouts/platform.html"){
<el-descriptions-item label="出行时段">{{ detail.travelPeriod || '' }}</el-descriptions-item>
<el-descriptions-item label="线路类型">{{ detail.lineType || '' }}</el-descriptions-item>
<el-descriptions-item label="报名酒店">{{ detail.hotelName || '' }}</el-descriptions-item>
<el-descriptions-item label="旅行社">{{ detail.travelAgencyName || '' }}</el-descriptions-item>
<el-descriptions-item label="服务单位">{{ detail.travelAgencyName || '' }}</el-descriptions-item>
<el-descriptions-item label="是否携带家属">
{{ familyText() }}
</el-descriptions-item>
@@ -83,6 +83,7 @@ layout("/layouts/platform.html"){
:visible.sync="dialogVisible"
:close-on-click-modal="false"
width="64.8%"
custom-class="tour-setting-dialog"
@closed="destroyEditor">
<el-form :model="formData" :rules="formRules" label-width="120px" ref="form">
<div v-if="title === '新建疗休养配置'" class="tour-setting-inherit">
@@ -145,6 +146,11 @@ layout("/layouts/platform.html"){
<el-input-number v-model="formData.maxGroupPeople" :controls="false" :min="0" :precision="0" placeholder="请输入最多成团人数" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="出行人数指标" prop="travelPeopleQuota">
<el-input-number v-model="formData.travelPeopleQuota" :controls="false" :min="0" :precision="0" placeholder="请输入出行人数指标" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="24">
<div class="tour-setting-basic-divider"></div>
</el-col>
@@ -267,6 +273,47 @@ layout("/layouts/platform.html"){
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="人员分配" name="unionQuota">
<div class="tour-tab-fill">
<div class="tour-quota-toolbar">
<div class="tour-quota-stat">
<span>出行人数指标</span>
<strong>{{ travelPeopleQuota }}</strong>
</div>
<div class="tour-quota-stat">
<span>分工会总人数</span>
<strong>{{ branchTotalPersonCount }}</strong>
</div>
<el-input-number
v-model="quotaAllocate.ratio"
:controls="false"
:min="0"
:precision="2"
placeholder="比例"
class="tour-quota-input">
</el-input-number>
<el-button type="primary" size="medium" @click="allocateQuotaByRatio">按比例分配</el-button>
</div>
<el-table
:data="formData.unionQuotas"
border
class="vi-table tour-quota-table tour-tab-table"
empty-text="暂无分工会"
height="100%"
show-summary
:summary-method="unionQuotaSummary"
style="width: 100%">
<el-table-column label="序号" type="index" width="70" align="center" header-align="center"></el-table-column>
<el-table-column label="分工会" prop="unionName" min-width="220" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="当前人数" prop="personCount" width="140" align="center" header-align="center"></el-table-column>
<el-table-column label="人员数量" width="180" align="center" header-align="center">
<template slot-scope="{row}">
<el-input-number v-model="row.peopleQuota" :controls="false" :min="0" :precision="0" style="width: 100%"></el-input-number>
</template>
</el-table-column>
</el-table>
</div>
</el-tab-pane>
<el-tab-pane label="服务须知" name="notice">
<el-form-item prop="serviceNotice" label-width="0">
<text-editor v-model="formData.serviceNotice"></text-editor>
@@ -331,10 +378,14 @@ layout("/layouts/platform.html"){
activityGroupList: [],
formData: {},
lotDeleteList: [],
quotaAllocate: {
ratio: null
},
inheritLoading: false,
formRules: {
year: [{required: true, message: "必填", trigger: ["blur", "change"]}],
configName: [{required: true, message: "必填", trigger: ["blur", "change"]}],
travelPeopleQuota: [{required: true, message: "必填", trigger: ["blur", "change"]}],
minGroupPeople: [{validator: checkGroupPeople, trigger: ["blur", "change"]}],
maxGroupPeople: [{validator: checkGroupPeople, trigger: ["blur", "change"]}],
outProvinceRatioType: [{required: true, message: "必填", trigger: ["blur", "change"]}],
@@ -347,12 +398,117 @@ layout("/layouts/platform.html"){
components: {
"drawer-user-scope": httpVueLoader("/components/module/activity/DrawerUserScope.vue")
},
computed: {
// 出行人数指标统一按非负整数参与页面统计和分配上限校验。
travelPeopleQuota() {
return this.toNonNegativeInteger(this.formData.travelPeopleQuota)
},
// 人员总数使用后端实时统计结果,仅作为按比例分配的计算基数。
branchTotalPersonCount() {
const rows = this.formData.unionQuotas || []
return rows.reduce((sum, row) => sum + this.toNonNegativeInteger(row.personCount), 0)
}
},
watch: {
"formData.travelPeopleQuota": function() {
this.refreshQuotaRatio()
}
},
methods: {
async getActivityGroup() {
const {data} = await this.$axios.post("/platform/activity/basic/scope/getActivityUserScopeGroup")
this.activityGroupList = (data || []).map((item) => {
// 加载分工会人员分配行;新增时传空配置ID,编辑时合并已保存数量。
loadUnionQuotaRows(settingId) {
this.$axios.post(loc() + "/unionQuotaRows", {settingId: settingId || ""}).then((res) => {
if (res.code === 0) {
this.$set(this.formData, "unionQuotas", this.normalizeUnionQuotaRows(res.data || []))
this.refreshQuotaRatio()
} else {
this.$message.warning(res.msg || "查询人员分配失败")
}
})
},
// 将接口和历史数据中的空值规范为非负整数,避免合计结果出现 NaN。
normalizeUnionQuotaRows(rows) {
return (rows || []).map(item => {
return Object.assign({}, item, {
groupId: item.groupId === null || item.groupId === undefined ? "" : String(item.groupId)
peopleQuota: this.toNonNegativeInteger(item.peopleQuota),
personCount: this.toNonNegativeInteger(item.personCount)
})
})
},
toNonNegativeInteger(value) {
const numberValue = parseInt(value, 10)
if (isNaN(numberValue) || numberValue < 0) {
return 0
}
return numberValue
},
toNumber(value) {
const numberValue = parseFloat(value)
return isNaN(numberValue) ? null : numberValue
},
truncateDecimal(value, precision) {
const times = Math.pow(10, precision)
return Math.floor(value * times) / times
},
// 默认比例为出行人数指标除以分工会总人数,并按参考项目保留两位后直接截断。
refreshQuotaRatio() {
if (this.branchTotalPersonCount <= 0 || this.travelPeopleQuota <= 0) {
this.quotaAllocate.ratio = null
return
}
this.quotaAllocate.ratio = this.truncateDecimal(this.travelPeopleQuota / this.branchTotalPersonCount, 2)
},
unionQuotaSummary(param) {
const columns = param.columns || []
const rows = this.formData.unionQuotas || []
return columns.map((column, index) => {
if (index === 0) {
return "合计"
}
if (column.property === "personCount") {
return rows.reduce((sum, row) => sum + this.toNonNegativeInteger(row.personCount), 0)
}
if (column.label === "人员数量") {
return rows.reduce((sum, row) => sum + this.toNonNegativeInteger(row.peopleQuota), 0)
}
return ""
})
},
allocateQuotaByRatio() {
const rows = this.formData.unionQuotas || []
const ratio = this.toNumber(this.quotaAllocate.ratio)
if (!rows.length) {
this.$message.warning("暂无分工会数据")
return
}
if (ratio === null || this.quotaAllocate.ratio === "" || this.quotaAllocate.ratio === undefined) {
this.$message.warning("比例为空,未分配")
return
}
if (ratio < 0) {
this.$message.warning("比例不能为负数")
return
}
// 按比例分配时小数直接舍去,不补余数,保持与参考项目一致。
const nextQuotas = rows.map(row => {
return Math.floor(ratio * this.toNonNegativeInteger(row.personCount))
})
const quotaTotal = nextQuotas.reduce((sum, value) => sum + value, 0)
if (quotaTotal > this.travelPeopleQuota) {
this.$message.warning("当前人员数量合计 " + quotaTotal + ",不能超过出行人数指标 " + this.travelPeopleQuota)
return
}
nextQuotas.forEach((peopleQuota, index) => {
this.$set(rows[index], "peopleQuota", peopleQuota)
})
},
getActivityGroup() {
return this.$axios.post("/platform/activity/basic/scope/getActivityUserScopeGroup").then((res) => {
const data = res.data
this.activityGroupList = (data || []).map((item) => {
return Object.assign({}, item, {
groupId: item.groupId === null || item.groupId === undefined ? "" : String(item.groupId)
})
})
})
},
@@ -378,6 +534,7 @@ layout("/layouts/platform.html"){
year: moment().format("YYYY"),
configName: "",
tourType: "",
travelPeopleQuota: 0,
activityGroupId: "",
sortNo: 0,
minGroupPeople: 0,
@@ -394,6 +551,7 @@ layout("/layouts/platform.html"){
fillBedInfo: true,
enabled: true,
lots: [],
unionQuotas: [],
serviceNotice: ""
}
},
@@ -404,6 +562,7 @@ layout("/layouts/platform.html"){
// 新建时给出默认年度和开关值,减少校工会管理员录入成本。
this.formData = this.defaultFormData()
this.dialogVisible = true
this.loadUnionQuotaRows("")
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
},
inheritPreviousYearInfo() {
@@ -428,11 +587,19 @@ layout("/layouts/platform.html"){
allowOverReimbursement: !!item.allowOverReimbursement
}
})
// 延用上一年分配数量,但清除原配置和子表ID,保存时生成当前年度数据。
const inheritedUnionQuotas = this.normalizeUnionQuotaRows(previous.unionQuotas || []).map(item => {
return Object.assign({}, item, {
id: "",
settingId: ""
})
})
this.formData = Object.assign(this.defaultFormData(), previous, {
year: String(currentYear),
cycleStartYear: previous.cycleStartYear ? String(previous.cycleStartYear) : "",
cycleEndYear: previous.cycleEndYear ? String(previous.cycleEndYear) : "",
lots: inheritedLots
lots: inheritedLots,
unionQuotas: inheritedUnionQuotas
})
delete this.formData.id
delete this.formData.createdAt
@@ -463,6 +630,7 @@ layout("/layouts/platform.html"){
this.$axios.post(loc() + "/detail", {id: row.id}).then((res) => {
if (res.code === 0) {
this.formData = Object.assign({
travelPeopleQuota: 0,
activityGroupId: "",
outProvinceRatioType: "当年报名人数",
outProvinceFixedPeople: 0,
@@ -474,6 +642,7 @@ layout("/layouts/platform.html"){
fillBedInfo: true,
enabled: true,
lots: [],
unionQuotas: [],
serviceNotice: ""
}, res.data || {})
this.formData.year = this.formData.year ? String(this.formData.year) : ""
@@ -491,6 +660,7 @@ layout("/layouts/platform.html"){
this.formData.lots = (this.formData.lots || []).map(item => Object.assign({}, item, {
allowOverReimbursement: !!item.allowOverReimbursement
}))
this.formData.unionQuotas = this.normalizeUnionQuotaRows(this.formData.unionQuotas || [])
this.dialogVisible = true
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
} else {
@@ -502,6 +672,7 @@ layout("/layouts/platform.html"){
this.$refs.form.validate((valid) => {
if (!valid) return
if (!this.validateLots()) return
if (!this.validateUnionQuotas()) return
this.submitLoading = true
// Nutz 对子表集合按字符串化 JSON 绑定更稳定,和体检项目维护的提交方式保持一致。
const submitData = JSON.parse(JSON.stringify(this.formData))
@@ -509,6 +680,7 @@ layout("/layouts/platform.html"){
submitData.outProvinceFixedPeople = 0
}
submitData.lots = JSON.stringify(this.formData.lots || [])
submitData.unionQuotas = JSON.stringify(this.formData.unionQuotas || [])
submitData.lotDeleteList = JSON.stringify(this.lotDeleteList)
this.$axios.post(loc() + "/doSubmit", submitData).then((res) => {
this.submitLoading = false
@@ -562,6 +734,29 @@ layout("/layouts/platform.html"){
}
return true
},
// 提交前统一人员数量格式,并校验分配合计不超过出行人数指标。
validateUnionQuotas() {
const rows = this.formData.unionQuotas || []
for (let i = 0; i < rows.length; i++) {
const row = rows[i]
if (row.peopleQuota === null || row.peopleQuota === undefined || row.peopleQuota === "") {
row.peopleQuota = 0
}
if (!/^\d+$/.test(String(row.peopleQuota))) {
this.$message.warning("第" + (i + 1) + "行人员数量必须为非负整数")
return false
}
this.$set(row, "peopleQuota", this.toNonNegativeInteger(row.peopleQuota))
}
const quotaTotal = rows.reduce((sum, row) => {
return sum + this.toNonNegativeInteger(row.peopleQuota)
}, 0)
if (quotaTotal > this.travelPeopleQuota) {
this.$message.warning("当前人员数量合计 " + quotaTotal + ",不能超过出行人数指标 " + this.travelPeopleQuota)
return false
}
return true
},
deleteLot(index, row) {
this.$confirm("确定删除该标段吗?", "提示", {
confirmButtonText: "确定",
@@ -593,6 +788,9 @@ layout("/layouts/platform.html"){
destroyEditor() {
this.formData = {}
this.lotDeleteList = []
this.quotaAllocate = {
ratio: null
}
this.inheritLoading = false
this.activeTab = "basic"
}
@@ -627,6 +825,94 @@ layout("/layouts/platform.html"){
border-top: 1px dashed #dcdfe6;
margin: 2px 0 18px;
}
.tour-quota-toolbar {
align-items: center;
display: flex;
flex-shrink: 0;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 10px;
}
.tour-quota-input {
width: 160px;
}
.tour-quota-stat {
align-items: center;
border: 1px solid #dcdfe6;
border-radius: 4px;
display: inline-flex;
gap: 8px;
height: 36px;
padding: 0 10px;
}
.tour-quota-stat span {
color: #606266;
}
.tour-quota-stat strong {
color: #303133;
font-weight: 600;
}
.tour-tab-fill {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
}
.tour-tab-table {
flex: 1;
min-height: 0;
}
.tour-setting-dialog {
display: flex;
flex-direction: column;
height: 85vh;
max-height: 85vh;
}
.tour-setting-dialog .el-dialog__header,
.tour-setting-dialog .el-dialog__footer,
.tour-setting-dialog .el-tabs__header {
flex-shrink: 0;
}
.tour-setting-dialog .el-dialog__body {
flex: 1;
min-height: 0;
overflow: hidden;
padding-bottom: 0;
}
.tour-setting-dialog .el-form,
.tour-setting-dialog .el-tabs {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
}
.tour-setting-dialog .el-tabs {
flex: 1;
}
.tour-setting-dialog .el-tabs__content {
flex: 1;
min-height: 0;
overflow-x: hidden;
overflow-y: auto;
padding: 0 6px 18px 0;
}
.tour-setting-dialog .el-tab-pane {
height: 100%;
}
</style>
<!--#
@@ -154,7 +154,7 @@ layout("/layouts/platform.html"){
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="报名旅行社">
<el-form-item label="报名服务单位">
<el-input v-model="signupForm.travelAgencyName" :disabled="isDirectFamilyLine(signupForm)" readonly></el-input>
</el-form-item>
</el-col>
@@ -360,7 +360,7 @@ layout("/layouts/platform.html"){
<el-descriptions-item label="时间标段">{{ lineDetail.lotName || '' }}</el-descriptions-item>
<el-descriptions-item label="线路类型">{{ lineDetail.lineType || '' }}</el-descriptions-item>
<el-descriptions-item label="直系亲属线路">{{ isDirectFamilyLine(lineDetail) ? '是' : '否' }}</el-descriptions-item>
<el-descriptions-item label="旅行社">{{ lineDetail.travelAgencyName || '' }}</el-descriptions-item>
<el-descriptions-item label="服务单位">{{ lineDetail.travelAgencyName || '' }}</el-descriptions-item>
<el-descriptions-item label="创建人">{{ lineDetail.creatorName || '' }}</el-descriptions-item>
<el-descriptions-item label="所在单位">{{ lineDetail.unitName || '' }}</el-descriptions-item>
<el-descriptions-item label="是否对外开放">{{ lineDetail.openFlag ? '是' : '否' }}</el-descriptions-item>
@@ -15,11 +15,11 @@ layout("/layouts/platform.html"){
style="width: 100%">
</el-date-picker>
</search-item>
<search-item label="旅行社名称">
<search-item label="服务单位名称">
<el-input
v-model="pageForm.agencyName"
clearable
placeholder="请输入旅行社名称"
placeholder="请输入服务单位名称"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
@@ -50,7 +50,7 @@ layout("/layouts/platform.html"){
</el-card>
<el-card class="mt10" shadow="never">
<table-tool :app="this" label="旅行社列表">
<table-tool :app="this" label="服务单位列表">
<el-button @click="openAdd" size="medium" type="primary">
<i class="el-icon-plus"></i>
新增
@@ -65,8 +65,8 @@ layout("/layouts/platform.html"){
@sort-change="pageOrder">
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="年度" prop="year" width="110" sortable="custom" align="center" header-align="center"></el-table-column>
<el-table-column label="旅行社编号" prop="agencyCode" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="旅行社名称" prop="agencyName" min-width="220" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="服务单位编号" prop="agencyCode" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="服务单位名称" prop="agencyName" min-width="220" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="联系人" prop="contactName" width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="联系电话" prop="contactPhone" width="150" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="激活状态" prop="enabled" width="160" align="center" header-align="center">
@@ -111,15 +111,15 @@ layout("/layouts/platform.html"){
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="旅行社名称" prop="agencyName">
<el-input v-model="formData.agencyName" maxlength="30" show-word-limit placeholder="请输入旅行社名称"></el-input>
<el-form-item label="服务单位名称" prop="agencyName">
<el-input v-model="formData.agencyName" maxlength="30" show-word-limit placeholder="请输入服务单位名称"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="旅行社编号" prop="agencyCode">
<el-input v-model="formData.agencyCode" maxlength="50" placeholder="请输入旅行社编号"></el-input>
<el-form-item label="服务单位编号" prop="agencyCode">
<el-input v-model="formData.agencyCode" maxlength="50" placeholder="请输入服务单位编号"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
@@ -248,19 +248,19 @@ layout("/layouts/platform.html"){
}
},
openAdd() {
this.title = "新增旅行社信息"
this.title = "新增服务单位信息"
this.viewMode = false
this.formData = this.emptyForm()
this.dialogVisible = true
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
},
openEdit(row) {
this.title = "编辑旅行社信息"
this.title = "编辑服务单位信息"
this.viewMode = false
this.loadDetail(row.id)
},
openView(row) {
this.title = "查看旅行社信息"
this.title = "查看服务单位信息"
this.viewMode = true
this.loadDetail(row.id)
},
@@ -191,7 +191,7 @@ layout("/layouts/platform.html"){
<el-descriptions-item label="出行时段">{{ detail.travelPeriod || '' }}</el-descriptions-item>
<el-descriptions-item label="线路类型">{{ detail.lineType || '' }}</el-descriptions-item>
<el-descriptions-item label="报名酒店">{{ detail.hotelName || '' }}</el-descriptions-item>
<el-descriptions-item label="旅行社">{{ detail.travelAgencyName || '' }}</el-descriptions-item>
<el-descriptions-item label="服务单位">{{ detail.travelAgencyName || '' }}</el-descriptions-item>
<el-descriptions-item label="是否携带家属">
{{ familyText() }}
</el-descriptions-item>
@@ -164,7 +164,7 @@ layout("/layouts/platform.html"){
<el-descriptions-item label="报名线路">{{ detail.lineName || '' }}</el-descriptions-item>
<el-descriptions-item label="线路类型">{{ detail.lineType || '' }}</el-descriptions-item>
<el-descriptions-item label="报名酒店">{{ detail.hotelName || '' }}</el-descriptions-item>
<el-descriptions-item label="报名旅行社">{{ detail.travelAgencyName || '' }}</el-descriptions-item>
<el-descriptions-item label="报名服务单位">{{ detail.travelAgencyName || '' }}</el-descriptions-item>
<el-descriptions-item label="出行时间">{{ detail.travelPeriod || '' }}</el-descriptions-item>
<el-descriptions-item v-if="fillBedInfo" label="床型">{{ detail.bedType || '' }}</el-descriptions-item>
<el-descriptions-item v-if="fillBedInfo" label="床位信息">{{ detail.bedInfo || '' }}</el-descriptions-item>
@@ -15,10 +15,10 @@ layout("/layouts/platform.html"){
<table-tool>
<el-button type="primary" size="mini" icon="el-icon-plus" @click="openAdd">新增</el-button>
</table-tool>
<el-table :data="tableData" border stripe>
<el-table :data="tableData" border stripe @sort-change="pageOrder">
<el-table-column label="序号" type="index" :index="indexMethod" width="50px"></el-table-column>
<el-table-column prop="name" label="名称"></el-table-column>
<el-table-column prop="code" label="编码"></el-table-column>
<el-table-column prop="name" label="名称" sortable="custom"></el-table-column>
<el-table-column prop="code" label="编码" sortable="custom"></el-table-column>
<el-table-column label="操作" width="150px">
<template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openEdit(row)">编辑</el-button>
@@ -23,12 +23,12 @@ layout("/layouts/platform.html"){
<table-tool>
<el-button type="primary" size="small" icon="el-icon-refresh" @click="syncSysUnit">同步系统单位</el-button>
</table-tool>
<el-table :data="tableData" border ref="tableRef" height="100%">
<el-table :data="tableData" border ref="tableRef" height="100%" @sort-change="pageOrder">
<el-table-column label="序号" type="index" :index="indexMethod" width="60"></el-table-column>
<el-table-column prop="name" label="单位名称" show-overflow-tooltip></el-table-column>
<el-table-column prop="code" label="单位编码" width="150px"></el-table-column>
<el-table-column prop="name" label="单位名称" sortable="custom" show-overflow-tooltip></el-table-column>
<el-table-column prop="code" label="单位编码" sortable="custom" width="150px"></el-table-column>
<!-- <el-table-column prop="branchSchoolLeader" label="分管校领导及联系方式" width="180px"></el-table-column>-->
<el-table-column prop="unitLeader" label="单位领导及联系方式" width="180px" show-overflow-tooltip></el-table-column>
<el-table-column prop="unitLeader" label="单位领导及联系方式" sortable="custom" width="180px" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" width="350px">
<template slot-scope="{row}">
<!-- <el-button size="mini" type="primary" @click="$refs.userRef.onOpen(row.id,true)">设置分管校领导</el-button>-->
@@ -196,26 +196,26 @@ layout("/layouts/platform.html"){
</div>
<el-table :data="tableData" @sort-change="pageOrder" header-align="center" style="width: 100%" row-key="id">
<el-table-column label="序号" width="50" type="index" :index="indexMethod"></el-table-column>
<el-table-column label="提案编号" prop="code" width="100px" show-overflow-tooltip></el-table-column>
<el-table-column label="提案名称" prop="name" show-overflow-tooltip></el-table-column>
<el-table-column label="提案人" prop="createUserName"></el-table-column>
<el-table-column label="提案类别" prop="typeName"></el-table-column>
<el-table-column label="代表团" prop="delegationName"></el-table-column>
<el-table-column label="立案结果" prop="caseFilingResult">
<el-table-column label="提案编号" prop="code" sortable="custom" width="100px" show-overflow-tooltip></el-table-column>
<el-table-column label="提案名称" prop="name" sortable="custom" show-overflow-tooltip></el-table-column>
<el-table-column label="提案人" prop="createUserName" sortable="custom"></el-table-column>
<el-table-column label="提案类别" prop="typeName" sortable="custom"></el-table-column>
<el-table-column label="代表团" prop="delegationName" sortable="custom"></el-table-column>
<el-table-column label="立案结果" prop="caseFilingResult" sortable="custom">
<template scope="{row}">
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_RESULT" :value="row.caseFilingResult"></dict-tag>
</template>
</el-table-column>
<el-table-column label="是否并案" prop="isConsolidation" width="100">
<el-table-column label="是否并案" prop="isConsolidation" sortable="custom" width="100">
<template scope="{row}">
<el-tag size="mini" v-if="row.isConsolidation" type="success"></el-tag>
<el-tag size="mini" v-else type="danger"></el-tag>
</template>
</el-table-column>
<el-table-column label="主办单位" prop="masterUnitName" show-overflow-tooltip></el-table-column>
<el-table-column label="协办单位" prop="slaveUnitNames" show-overflow-tooltip></el-table-column>
<el-table-column prop="curTaskName" label="当前节点"></el-table-column>
<el-table-column prop="instanceState" label="流程状态">
<el-table-column label="主办单位" prop="masterUnitName" sortable="custom" show-overflow-tooltip></el-table-column>
<el-table-column label="协办单位" prop="slaveUnitNames" sortable="custom" show-overflow-tooltip></el-table-column>
<el-table-column prop="curTaskName" label="当前节点" sortable="custom"></el-table-column>
<el-table-column prop="instanceState" label="流程状态" sortable="custom">
<template slot-scope="{row}">
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message"
size="small"></enum-tag>
@@ -137,6 +137,7 @@ layout("/layouts/platform.html"){
:key="column.key"
:min-width="column.width"
:fixed="column.fixed"
sortable="custom"
show-overflow-tooltip
v-for="column in tableColumns"
v-if="column.visible !== false"
@@ -1,4 +1,4 @@
<!--#
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
@@ -31,7 +31,8 @@ layout("/layouts/platform.html"){
:prop="column.prop"
:key="column.key"
:min-width="column.width"
:fixed="column.fixed"
:fixed="column.fixed"
sortable="custom"
show-overflow-tooltip
v-for="column in tableColumns"
v-if="column.visible !== false"
@@ -1,4 +1,4 @@
<!--#
<!--#
layout("/layouts/platform.html"){
#-->
<style>
@@ -204,11 +204,12 @@ layout("/layouts/platform.html"){
</div>
<el-button size="mini" type="primary" @click="exportStatistics">导出统计数据</el-button>
</div>
<el-table :data="statisticsRows" border stripe style="width: 100%" v-loading="tableLoading">
<el-table-column prop="dimension" label="统计维度" width="180"></el-table-column>
<el-table-column prop="itemName" label="分类项" min-width="260" show-overflow-tooltip></el-table-column>
<el-table-column prop="count" label="数量" width="120"></el-table-column>
<el-table-column prop="rate" label="占比" width="120"></el-table-column>
<el-table :data="statisticsRows" border stripe style="width: 100%" v-loading="tableLoading"
@sort-change="statisticsOrder">
<el-table-column prop="dimension" label="统计维度" sortable="custom" width="180"></el-table-column>
<el-table-column prop="itemName" label="分类项" sortable="custom" min-width="260" show-overflow-tooltip></el-table-column>
<el-table-column prop="count" label="数量" sortable="custom" width="120"></el-table-column>
<el-table-column prop="rate" label="占比" sortable="custom" width="120"></el-table-column>
</el-table>
</el-card>
@@ -220,14 +221,15 @@ layout("/layouts/platform.html"){
</div>
<el-button size="mini" type="primary" @click="exportAnalysis">导出分析数据</el-button>
</div>
<el-table :data="analysisRows" border stripe style="width: 100%" v-loading="tableLoading">
<el-table-column prop="dimension" label="统计维度" width="160"></el-table-column>
<el-table-column prop="total" label="总量" width="100"></el-table-column>
<el-table-column prop="categoryCount" label="分类数量" width="120"></el-table-column>
<el-table-column prop="topItem" label="最高项" min-width="180" show-overflow-tooltip></el-table-column>
<el-table-column prop="topCount" label="最高数量" width="120"></el-table-column>
<el-table-column prop="topRate" label="最高占比" width="120"></el-table-column>
<el-table-column prop="analysis" label="分析结论" min-width="320" show-overflow-tooltip></el-table-column>
<el-table :data="analysisRows" border stripe style="width: 100%" v-loading="tableLoading"
@sort-change="analysisOrder">
<el-table-column prop="dimension" label="统计维度" sortable="custom" width="160"></el-table-column>
<el-table-column prop="total" label="量" sortable="custom" width="100"></el-table-column>
<el-table-column prop="categoryCount" label="分类数量" sortable="custom" width="120"></el-table-column>
<el-table-column prop="topItem" label="最高项" sortable="custom" min-width="180" show-overflow-tooltip></el-table-column>
<el-table-column prop="topCount" label="最高数量" sortable="custom" width="120"></el-table-column>
<el-table-column prop="topRate" label="最高占比" sortable="custom" width="120"></el-table-column>
<el-table-column prop="analysis" label="分析结论" sortable="custom" min-width="320" show-overflow-tooltip></el-table-column>
</el-table>
</el-card>
@@ -283,7 +285,11 @@ layout("/layouts/platform.html"){
return {
pageForm: {
sessionId: "",
dimension: ""
dimension: "",
statisticsOrderName: "",
statisticsOrderBy: "",
analysisOrderName: "",
analysisOrderBy: ""
},
dimensionOptions: [
{label: "提案人单位", value: "提案人单位"},
@@ -320,6 +326,18 @@ layout("/layouts/platform.html"){
this.listSession()
},
methods: {
// 统计数据表使用独立排序参数,避免与分析数据表的排序状态相互覆盖。
statisticsOrder(column) {
this.pageForm.statisticsOrderName = column.prop
this.pageForm.statisticsOrderBy = column.order
this.doSearch()
},
// 分析数据表使用独立排序参数,两个接口刷新时分别应用各自白名单。
analysisOrder(column) {
this.pageForm.analysisOrderName = column.prop
this.pageForm.analysisOrderBy = column.order
this.doSearch()
},
toggleChartView() {
this.showChart = !this.showChart
if (this.showChart) {
@@ -89,6 +89,7 @@ layout("/layouts/platform.html"){
:key="column.key"
:min-width="column.width"
:fixed="column.fixed"
sortable="custom"
show-overflow-tooltip
v-for="column in tableColumns"
v-if="column.visible !== false"
@@ -206,6 +206,7 @@ layout("/layouts/platform.html"){
:key="column.key"
:min-width="column.width"
:fixed="column.fixed"
sortable="custom"
show-overflow-tooltip
v-for="column in tableColumns"
v-if="column.visible !== false"
@@ -214,6 +215,11 @@ layout("/layouts/platform.html"){
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_RESULT"
:value="row.caseFilingResult"></dict-tag>
</template>
<!-- 立案类型在数据库中保存字典编码,列表统一转换为中文名称展示。 -->
<template v-else-if="column.prop === 'caseFilingType'" scope="{row}">
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_TYPE"
:value="row.caseFilingType"></dict-tag>
</template>
<template v-else-if="column.prop === 'merge'" scope="{row}">
<el-tag v-if="row.merge" size="small"></el-tag>
<el-tag v-else type="warning" size="small"></el-tag>
@@ -14,7 +14,8 @@ layout("/layouts/platform.html"){
<el-card shadow="never" class="mt10">
<table-tool label="代表团列表" :columns.sync="tableColumns"></table-tool>
<el-table :data="tableData" ref="tableRef" header-align="center" style="width: 100%" row-key="id" show-summary>
<el-table :data="tableData" ref="tableRef" header-align="center" style="width: 100%" row-key="id"
show-summary @sort-change="pageOrder">
<el-table-column
:index="indexMethod"
label="序号"
@@ -28,6 +29,7 @@ layout("/layouts/platform.html"){
:key="column.key"
:min-width="column.width"
:fixed="column.fixed"
sortable="custom"
show-overflow-tooltip
v-for="column in tableColumns"
v-if="column.visible !== false"
@@ -61,6 +63,30 @@ layout("/layouts/platform.html"){
</div>
</el-dialog>
</div>
<!-- 提案明细组件模板独立放在页面中,避免在JavaScript中使用模板字符串。 -->
<script type="text/x-template" id="proposal-table-template" nonce="${cspNonce!}">
<div>
<el-table :data="tableData" ref="tableRef" header-align="center" style="width: 100%" row-key="id"
@sort-change="pageOrder">
<el-table-column label="序号" width="50px" type="index" :index="indexMethod"></el-table-column>
<el-table-column label="提案编号" prop="code" sortable="custom" width="200px"></el-table-column>
<el-table-column label="提案名称" prop="name" sortable="custom"></el-table-column>
<el-table-column label="提案人" prop="createUserName" sortable="custom"></el-table-column>
<el-table-column label="提案类别" prop="typeName" sortable="custom"></el-table-column>
<el-table-column label="届次" prop="sessionName" sortable="custom"></el-table-column>
<el-table-column label="代表团" prop="delegationName" sortable="custom"></el-table-column>
<el-table-column label="当前节点" prop="curTaskName" sortable="custom"></el-table-column>
<el-table-column label="操作" fixed="right" width="100px">
<template scope="{row}">
<el-button size="mini" type="primary" @click="$emit('view-single-proposal',row)">查看</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</div>
</script>
<script nonce="${cspNonce!}">
<!--#include("proposalTable.js"){}#-->
<!--#include("../../common/info.js"){}#-->
@@ -128,7 +154,9 @@ layout("/layouts/platform.html"){
pageData() {
this.$axios
.post(loc() + "/pageData", {
sessionId: this.sessionId
sessionId: this.sessionId,
pageOrderName: this.pageForm.pageOrderName,
pageOrderBy: this.pageForm.pageOrderBy
})
.then((res) => {
if (res.code === 0) {
@@ -1,24 +1,6 @@
const PROPOSAL_TABLE = {
template: `
<div>
<el-table :data="tableData" ref="tableRef" header-align="center" style="width: 100%" row-key="id" @sort-change="pageOrder">
<el-table-column label="序号" width="50px" type="index" :index="indexMethod"></el-table-column>
<el-table-column label="提案编号" prop="code" width="200px"></el-table-column>
<el-table-column label="提案名称" prop="name"></el-table-column>
<el-table-column label="提案人" prop="createUserName"></el-table-column>
<el-table-column label="提案类别" prop="typeName"></el-table-column>
<el-table-column label="届次" prop="sessionName"></el-table-column>
<el-table-column label="代表团" prop="delegationName"></el-table-column>
<el-table-column label="当前节点" prop="curTaskName"></el-table-column>
<el-table-column label="操作" fixed="right" width="100px">
<template scope="{row}">
<el-button size="mini" type="primary" @click="$emit('view-single-proposal',row)">查看</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</div>
`,
// 组件模板由页面中的text/x-template节点提供,避免使用JavaScript模板字符串。
template: "#proposal-table-template",
props: {
sessionId: {
type: String,
@@ -94,35 +94,35 @@ layout("/layouts/platform.html"){
<table-tool></table-tool>
<el-table :data="tableData" @sort-change="pageOrder" header-align="center" style="width: 100%" row-key="id">
<el-table-column label="序号" width="50" type="index" :index="indexMethod"></el-table-column>
<el-table-column label="提案编号" prop="code" width="120px" sortable></el-table-column>
<el-table-column label="提案名称" prop="name" show-overflow-tooltip></el-table-column>
<el-table-column label="提案人" prop="createUserName"></el-table-column>
<el-table-column label="届次" prop="sessionName"></el-table-column>
<el-table-column label="代表团" prop="delegationName"></el-table-column>
<el-table-column label="立案结果" prop="caseFilingResult">
<el-table-column label="提案编号" prop="code" width="120px" sortable="custom"></el-table-column>
<el-table-column label="提案名称" prop="name" sortable="custom" show-overflow-tooltip></el-table-column>
<el-table-column label="提案人" prop="createUserName" sortable="custom"></el-table-column>
<el-table-column label="届次" prop="sessionName" sortable="custom"></el-table-column>
<el-table-column label="代表团" prop="delegationName" sortable="custom"></el-table-column>
<el-table-column label="立案结果" prop="caseFilingResult" sortable="custom">
<template scope="{row}">
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_RESULT" :value="row.caseFilingResult"></dict-tag>
</template>
</el-table-column>
<el-table-column label="立案类型" prop="caseFilingType">
<el-table-column label="立案类型" prop="caseFilingType" sortable="custom">
<template scope="{row}">
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_TYPE"
:value="row.caseFilingType"></dict-tag>
</template>
</el-table-column>
<el-table-column label="是否并案" prop="merge">
<el-table-column label="是否并案" prop="merge" sortable="custom">
<template slot-scope="{row}">
<el-tag v-if="row.merge" size="small"></el-tag>
<el-tag v-else type="warning" size="small"></el-tag>
</template>
</el-table-column>
<el-table-column label="承办单位" prop="undertakeUnits" show-overflow-tooltip>
<el-table-column label="承办单位" prop="undertakeUnits" sortable="custom" show-overflow-tooltip>
<template slot-scope="{row}">
{{ getUndertakeDisplay(row) }}
</template>
</el-table-column>
<el-table-column prop="curTaskName" label="当前节点"></el-table-column>
<el-table-column prop="instanceState" label="流程状态">
<el-table-column prop="curTaskName" label="当前节点" sortable="custom"></el-table-column>
<el-table-column prop="instanceState" label="流程状态" sortable="custom">
<template slot-scope="{row}">
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message"
size="small"></enum-tag>
@@ -24,19 +24,19 @@ layout("/layouts/platform.html"){
</el-card>
<el-card shadow="never">
<el-table :data="tableData">
<el-table :data="tableData" @sort-change="pageOrder">
<el-table-column label="序号" type="index" width="50"></el-table-column>
<el-table-column label="单位名称" prop="unitName" width="400" sortable show-overflow-tooltip></el-table-column>
<el-table-column label="提案总数" prop="sum" sortable></el-table-column>
<el-table-column label="单位名称" prop="unitName" width="400" sortable="custom" show-overflow-tooltip></el-table-column>
<el-table-column label="提案总数" prop="sum" sortable="custom"></el-table-column>
<el-table-column label="主办提案">
<el-table-column label="提案数量" prop="masterSum" sortable></el-table-column>
<el-table-column label="已答复" prop="masterReplySum" sortable></el-table-column>
<el-table-column label="未答复" prop="masterNoReplySum" sortable></el-table-column>
<el-table-column label="提案数量" prop="masterSum" sortable="custom"></el-table-column>
<el-table-column label="已答复" prop="masterReplySum" sortable="custom"></el-table-column>
<el-table-column label="未答复" prop="masterNoReplySum" sortable="custom"></el-table-column>
</el-table-column>
<el-table-column label="协办提案">
<el-table-column label="提案数量" prop="slaveSum" sortable></el-table-column>
<el-table-column label="已答复" prop="slaveReplySum" sortable v-if="slaveNeedReply"></el-table-column>
<el-table-column label="未答复" prop="slaveNoReplySum" sortable v-if="slaveNeedReply"></el-table-column>
<el-table-column label="提案数量" prop="slaveSum" sortable="custom"></el-table-column>
<el-table-column label="已答复" prop="slaveReplySum" sortable="custom" v-if="slaveNeedReply"></el-table-column>
<el-table-column label="未答复" prop="slaveNoReplySum" sortable="custom" v-if="slaveNeedReply"></el-table-column>
</el-table-column>
</el-table>
</el-card>
@@ -22,14 +22,14 @@ layout("/layouts/platform.html"){
</el-card>
<el-card shadow="never">
<el-table :data="tableData">
<el-table :data="tableData" @sort-change="pageOrder">
<el-table-column label="序号" type="index" width="50"></el-table-column>
<el-table-column label="单位名称" prop="unitName" width="400" sortable show-overflow-tooltip></el-table-column>
<el-table-column label="提案总数" prop="sum" sortable></el-table-column>
<el-table-column label="主办提案数" prop="masterSum" sortable></el-table-column>
<el-table-column label="协办提案数" prop="slaveSum" sortable></el-table-column>
<el-table-column label="单位名称" prop="unitName" width="400" sortable="custom" show-overflow-tooltip></el-table-column>
<el-table-column label="提案总数" prop="sum" sortable="custom"></el-table-column>
<el-table-column label="主办提案数" prop="masterSum" sortable="custom"></el-table-column>
<el-table-column label="协办提案数" prop="slaveSum" sortable="custom"></el-table-column>
<template v-for="dict in dict.type.PROPOSAL_FEEDBACK">
<el-table-column :label="dict.label" :prop="dict.code" sortable></el-table-column>
<el-table-column :label="dict.label" :prop="dict.code" sortable="custom"></el-table-column>
</template>
</el-table>
</el-card>
@@ -96,29 +96,29 @@ layout("/layouts/platform.html"){
</table-tool>
<el-table :data="tableData" @sort-change="pageOrder" header-align="center" style="width: 100%" row-key="id">
<el-table-column label="序号" width="50" type="index" :index="indexMethod"></el-table-column>
<el-table-column label="提案编号" prop="code" width="100px" show-overflow-tooltip></el-table-column>
<el-table-column label="提案名称" prop="name" show-overflow-tooltip></el-table-column>
<el-table-column label="提案人" prop="createUserName"></el-table-column>
<el-table-column label="提案类别" prop="typeName"></el-table-column>
<el-table-column label="代表团" prop="delegationName"></el-table-column>
<el-table-column label="立案结果" prop="caseFilingResult">
<el-table-column label="提案编号" prop="code" sortable="custom" width="100px" show-overflow-tooltip></el-table-column>
<el-table-column label="提案名称" prop="name" sortable="custom" show-overflow-tooltip></el-table-column>
<el-table-column label="提案人" prop="createUserName" sortable="custom"></el-table-column>
<el-table-column label="提案类别" prop="typeName" sortable="custom"></el-table-column>
<el-table-column label="代表团" prop="delegationName" sortable="custom"></el-table-column>
<el-table-column label="立案结果" prop="caseFilingResult" sortable="custom">
<template scope="{row}">
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_RESULT" :value="row.caseFilingResult"></dict-tag>
</template>
</el-table-column>
<el-table-column label="是否并案" prop="isConsolidation" width="100">
<el-table-column label="是否并案" prop="isConsolidation" sortable="custom" width="100">
<template scope="{row}">
<el-tag size="mini" v-if="row.isConsolidation" type="success"></el-tag>
<el-tag size="mini" v-else type="danger"></el-tag>
</template>
</el-table-column>
<el-table-column label="承办单位" prop="undertakeUnits" show-overflow-tooltip>
<el-table-column label="承办单位" prop="undertakeUnits" sortable="custom" show-overflow-tooltip>
<template slot-scope="{row}">
{{ getUndertakeDisplay(row) }}
</template>
</el-table-column>
<el-table-column prop="curTaskName" label="当前节点"></el-table-column>
<el-table-column prop="instanceState" label="流程状态">
<el-table-column prop="curTaskName" label="当前节点" sortable="custom"></el-table-column>
<el-table-column prop="instanceState" label="流程状态" sortable="custom">
<template slot-scope="{row}">
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message"
size="small"></enum-tag>
@@ -76,25 +76,25 @@ layout("/layouts/platform.html"){
<table-tool></table-tool>
<el-table :data="tableData" @sort-change="pageOrder" header-align="center" style="width: 100%" row-key="id">
<el-table-column label="序号" width="50" type="index" :index="indexMethod"></el-table-column>
<el-table-column label="提案编号" prop="code" width="100px" show-overflow-tooltip></el-table-column>
<el-table-column label="提案名称" prop="name" show-overflow-tooltip></el-table-column>
<el-table-column label="提案人" prop="createUserName"></el-table-column>
<el-table-column label="提案类别" prop="typeName"></el-table-column>
<el-table-column label="代表团" prop="delegationName"></el-table-column>
<el-table-column label="立案结果" prop="caseFilingResult">
<el-table-column label="提案编号" prop="code" sortable="custom" width="100px" show-overflow-tooltip></el-table-column>
<el-table-column label="提案名称" prop="name" sortable="custom" show-overflow-tooltip></el-table-column>
<el-table-column label="提案人" prop="createUserName" sortable="custom"></el-table-column>
<el-table-column label="提案类别" prop="typeName" sortable="custom"></el-table-column>
<el-table-column label="代表团" prop="delegationName" sortable="custom"></el-table-column>
<el-table-column label="立案结果" prop="caseFilingResult" sortable="custom">
<template scope="{row}">
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_RESULT"
:value="row.caseFilingResult"></dict-tag>
</template>
</el-table-column>
<el-table-column label="是否并案" prop="isConsolidation" width="100">
<el-table-column label="是否并案" prop="isConsolidation" sortable="custom" width="100">
<template scope="{row}">
<el-tag size="mini" v-if="row.isConsolidation" type="success"></el-tag>
<el-tag size="mini" v-else type="warning"></el-tag>
</template>
</el-table-column>
<el-table-column prop="curTaskName" label="当前节点"></el-table-column>
<el-table-column prop="instanceState" label="流程状态">
<el-table-column prop="curTaskName" label="当前节点" sortable="custom"></el-table-column>
<el-table-column prop="instanceState" label="流程状态" sortable="custom">
<template slot-scope="{row}">
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message"
size="small"></enum-tag>
@@ -9,7 +9,7 @@ layout("/layouts/platform.html"){
<el-card shadow="never">
<search @search="doSearch">
<search-item label="教代会">
<el-select @change="meetingChange" clearable filterable placeholder="所属教代会" v-model="pageForm.sessionId">
<el-select @change="meetingChange" clearable filterable placeholder="所属教代会" v-model="pageForm.searchSessionId">
<el-option :key="item.id" :label="item.fullName" :value="item.id" v-for="item in sessionOptions"></el-option>
</el-select>
</search-item>
@@ -70,24 +70,24 @@ layout("/layouts/platform.html"){
<table-tool></table-tool>
<el-table :data="tableData" @sort-change="pageOrder" header-align="center" style="width: 100%" row-key="id">
<el-table-column label="序号" width="50" type="index" :index="indexMethod"></el-table-column>
<el-table-column label="提案编号" prop="code" width="100px" show-overflow-tooltip></el-table-column>
<el-table-column label="提案名称" prop="name" show-overflow-tooltip></el-table-column>
<el-table-column label="提案人" prop="createUserName"></el-table-column>
<el-table-column label="提案类别" prop="typeName"></el-table-column>
<el-table-column label="代表团" prop="delegationName"></el-table-column>
<el-table-column label="立案结果" prop="caseFilingResult">
<el-table-column label="提案编号" prop="code" sortable="custom" width="100px" show-overflow-tooltip></el-table-column>
<el-table-column label="提案名称" prop="name" sortable="custom" show-overflow-tooltip></el-table-column>
<el-table-column label="提案人" prop="createUserName" sortable="custom"></el-table-column>
<el-table-column label="提案类别" prop="typeName" sortable="custom"></el-table-column>
<el-table-column label="代表团" prop="delegationName" sortable="custom"></el-table-column>
<el-table-column label="立案结果" prop="caseFilingResult" sortable="custom">
<template scope="{row}">
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_RESULT" :value="row.caseFilingResult"></dict-tag>
</template>
</el-table-column>
<el-table-column label="是否并案" prop="isConsolidation" width="100">
<el-table-column label="是否并案" prop="isConsolidation" sortable="custom" width="100">
<template scope="{row}">
<el-tag size="mini" v-if="row.isConsolidation" type="success"></el-tag>
<el-tag size="mini" v-else type="danger"></el-tag>
</template>
</el-table-column>
<el-table-column prop="curTaskName" label="当前节点"></el-table-column>
<el-table-column prop="instanceState" label="流程状态">
<el-table-column prop="curTaskName" label="当前节点" sortable="custom"></el-table-column>
<el-table-column prop="instanceState" label="流程状态" sortable="custom">
<template slot-scope="{row}">
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
</template>
@@ -157,7 +157,7 @@ layout("/layouts/platform.html"){
this.listDelegation()
},
listDelegation() {
this.$axios.post("/platform/proposal/common/listDelegation", { sessionId: this.pageForm.sessionId }).then((res) => {
this.$axios.post("/platform/proposal/common/listDelegation", { sessionId: this.pageForm.searchSessionId }).then((res) => {
if (res.code === 0) {
this.delegationOptions = res.data
}
@@ -168,7 +168,7 @@ layout("/layouts/platform.html"){
if (res.code === 0) {
this.sessionOptions = res.data
if (this.sessionOptions) {
this.$set(this.pageForm, "sessionId", this.sessionOptions[0].id)
this.$set(this.pageForm, "searchSessionId", this.sessionOptions[0].id)
this.listDelegation()
this.pageData()
}
@@ -58,7 +58,7 @@ layout("/layouts/platform.html"){
:prop="column.prop"
:key="column.prop"
:width="column.width"
:sortable="column.sortable"
sortable="custom"
align="center"
header-align="center"
show-overflow-tooltip
@@ -95,7 +95,7 @@ layout("/layouts/platform.html"){
:prop="column.prop"
:key="column.prop"
:width="column.width"
:sortable="column.sortable"
sortable="custom"
align="center"
header-align="center"
show-overflow-tooltip
@@ -76,31 +76,31 @@ layout("/layouts/platform.html"){
<table-tool></table-tool>
<el-table :data="tableData" @sort-change="pageOrder" header-align="center" style="width: 100%" row-key="id">
<el-table-column label="序号" width="50" type="index" :index="indexMethod"></el-table-column>
<el-table-column label="提案编号" prop="code" width="100px" show-overflow-tooltip></el-table-column>
<el-table-column label="提案名称" prop="name" show-overflow-tooltip></el-table-column>
<el-table-column label="提案人" prop="createUserName"></el-table-column>
<el-table-column label="提案类别" prop="typeName"></el-table-column>
<el-table-column label="代表团" prop="delegationName"></el-table-column>
<el-table-column label="立案结果" prop="caseFilingResult">
<el-table-column label="提案编号" prop="code" sortable="custom" width="100px" show-overflow-tooltip></el-table-column>
<el-table-column label="提案名称" prop="name" sortable="custom" show-overflow-tooltip></el-table-column>
<el-table-column label="提案人" prop="createUserName" sortable="custom"></el-table-column>
<el-table-column label="提案类别" prop="typeName" sortable="custom"></el-table-column>
<el-table-column label="代表团" prop="delegationName" sortable="custom"></el-table-column>
<el-table-column label="立案结果" prop="caseFilingResult" sortable="custom">
<template scope="{row}">
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_RESULT"
:value="row.caseFilingResult"></dict-tag>
</template>
</el-table-column>
<el-table-column label="是否并案" prop="isConsolidation" width="100">
<el-table-column label="是否并案" prop="isConsolidation" sortable="custom" width="100">
<template scope="{row}">
<el-tag size="mini" v-if="row.isConsolidation" type="success"></el-tag>
<el-tag size="mini" v-else type="warning"></el-tag>
</template>
</el-table-column>
<el-table-column prop="curTaskName" label="当前节点"></el-table-column>
<el-table-column prop="instanceState" label="流程状态">
<el-table-column prop="curTaskName" label="当前节点" sortable="custom"></el-table-column>
<el-table-column prop="instanceState" label="流程状态" sortable="custom">
<template slot-scope="{row}">
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message"
size="small"></enum-tag>
</template>
</el-table-column>
<el-table-column label="反馈评价" prop="tf_feedback" width="150px">
<el-table-column label="反馈评价" prop="tf_feedback" sortable="custom" width="150px">
<template slot-scope="{row}">
<dict-tag :options="dict.type.PROPOSAL_FEEDBACK" :value="row.tf_feedback"></dict-tag>
</template>
@@ -70,24 +70,24 @@ layout("/layouts/platform.html"){
<table-tool></table-tool>
<el-table :data="tableData" @sort-change="pageOrder" header-align="center" style="width: 100%" row-key="id">
<el-table-column label="序号" width="50" type="index" :index="indexMethod"></el-table-column>
<el-table-column label="提案编号" prop="code" width="100px" show-overflow-tooltip></el-table-column>
<el-table-column label="提案名称" prop="name" show-overflow-tooltip></el-table-column>
<el-table-column label="提案人" prop="createUserName"></el-table-column>
<el-table-column label="提案类别" prop="typeName"></el-table-column>
<el-table-column label="代表团" prop="delegationName"></el-table-column>
<el-table-column label="立案结果" prop="caseFilingResult">
<el-table-column label="提案编号" prop="code" sortable="custom" width="100px" show-overflow-tooltip></el-table-column>
<el-table-column label="提案名称" prop="name" sortable="custom" show-overflow-tooltip></el-table-column>
<el-table-column label="提案人" prop="createUserName" sortable="custom"></el-table-column>
<el-table-column label="提案类别" prop="typeName" sortable="custom"></el-table-column>
<el-table-column label="代表团" prop="delegationName" sortable="custom"></el-table-column>
<el-table-column label="立案结果" prop="caseFilingResult" sortable="custom">
<template scope="{row}">
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_RESULT" :value="row.caseFilingResult"></dict-tag>
</template>
</el-table-column>
<el-table-column label="是否并案" prop="isConsolidation" width="100">
<el-table-column label="是否并案" prop="isConsolidation" sortable="custom" width="100">
<template scope="{row}">
<el-tag size="mini" v-if="row.isConsolidation" type="success"></el-tag>
<el-tag size="mini" v-else type="danger"></el-tag>
</template>
</el-table-column>
<el-table-column prop="curTaskName" label="当前节点"></el-table-column>
<el-table-column prop="instanceState" label="流程状态">
<el-table-column prop="curTaskName" label="当前节点" sortable="custom"></el-table-column>
<el-table-column prop="instanceState" label="流程状态" sortable="custom">
<template slot-scope="{row}">
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message"
size="small"></enum-tag>

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