体育报名外援优化;体育服装管理整改,关联选择活动;比赛名称录入可选工会补全;沿用保存活动bug修复

This commit is contained in:
2026-10-09 10:59:09 +08:00
parent d6961ebf19
commit d551f8c94e
29 changed files with 1117 additions and 433 deletions
@@ -89,7 +89,11 @@ public class ActivityApplyController {
LEFT JOIN activity_school acts ON acts.id=apply.activityId
LEFT JOIN activity_event ev ON ev.id = apply.eventId $condition
""");
cnd.and("apply.activityUnionId", "=", getUnionId());
// 其他工会出战的外援仍由原报名人维护,重新打开项目时必须回显。
SqlExpressionGroup ownerOrUnion = new SqlExpressionGroup();
ownerOrUnion.or("apply.activityUnionId", "=", getUnionId());
ownerOrUnion.or("apply.applyUser", "=", ShiroUtil.getUserId());
cnd.and(ownerOrUnion);
/* if (!ShiroUtil.hasAnyRoles("A06,sysadmin")) {
SqlExpressionGroup group = new SqlExpressionGroup();
if (ShiroUtil.hasAnyRoles("H01")) {
@@ -239,12 +243,13 @@ public class ActivityApplyController {
WHERE
asa.eventId = @eventId
AND asa.activityId=@activityId
AND asa.activityUnionId = @unionid
AND (asa.activityUnionId = @unionid OR asa.applyUser = @applyUser)
AND asa.`status` != 3
ORDER BY
asa.identity DESC,
asa.sex
""").setParam("eventId", eventId).setParam("unionid", union_id).setParam("activityId", activityId);
""").setParam("eventId", eventId).setParam("unionid", union_id)
.setParam("applyUser", ShiroUtil.getUserId()).setParam("activityId", activityId);
List<ActivitySchoolApply> list = activitySchoolApplyService.listEntity(applySql);
//团体人数
List<ActivitySchoolApply> awardsModeT = list.stream().filter(v -> v.getAwardsMode().equals("2")).collect(Collectors.toList());
@@ -387,20 +392,26 @@ public class ActivityApplyController {
}
/**
* 创建报名专用外援,工号由服务端生成,页面不能指定。
* 创建报名专用人员;工号留空时由服务端生成,手动填写时服务端负责查重。
* @param username 外援姓名
* @param sex 性别,男或女
* @param birthday 出生日期,yyyy-MM-dd
* @param loginname 可为空;留空时自动生成,手动填写时不能与现有账号重复
* @param idcard 18位身份证号,出生日期由服务端从身份证号读取
* @param mobile 手机号码
* @return Sys_user,id 为报名人员 ID,loginname 为 wy 年份流水工号,其他字段用于页面回显
* @param fitForCompetition 身体条件是否适合参赛,只有 true 可创建
* @return Sys_user,id 为报名人员 ID,loginname 为最终工号,其他字段用于页面回显
*/
@At
@ViReturn
@RequiresPermissions("activity.apply")
@Aop(TransAop.READ_COMMITTED)
public Object addExternalUser(String username, String sex, String birthday, String mobile) {
public Object addExternalUser(String username, String loginname, String sex, String idcard, String mobile,
Boolean fitForCompetition) {
if (!Boolean.TRUE.equals(fitForCompetition)) {
throw new IllegalArgumentException("身体条件不适合参赛,不能添加外援");
}
Sys_user operator = (Sys_user) ShiroUtil.getPrincipal();
return activitySchoolApplyService.createExternalUser(username, sex, birthday, mobile, operator.getUnitid());
return activitySchoolApplyService.createExternalUser(username, loginname, sex, idcard, mobile, operator.getUnitid());
}
/** @return String,当前年份的下一个外援工号预览,实际编号以创建接口返回值为准。 */
@@ -765,25 +776,18 @@ public class ActivityApplyController {
return insert.getId();
}
/**
* 查询添加外援时可复用的系统人员。
* @param queryString 姓名或工号片段;空值不查询
* @param userId 不参与返回的用户 ID 列表,可为空
* @return List<NutMap>,每项包含 id、username、loginname、sex、idcard、birthday、mobile、unitid、unitname
*/
@At
@ViReturn
@RequiresPermissions("activity.apply")
@Ok("json:{ignoreNull:true}")
public Object getSysUser(String queryString, String[] userId) {
Cnd cnd = Cnd.NEW();
cnd.andEX("id", "not in", userId);
if (StrUtil.isNotBlank(queryString)) {
SqlExpressionGroup group = new SqlExpressionGroup();
group.andLike("username", queryString).orLike("loginname", queryString);
cnd.and(group);
}
// cnd.and("unionid", "=", Vi.getUnionId());
Sql sql = Sqls.create("select id,loginname,username,sex,mobile,unitid,birthday from `user` $condition LIMIT 30");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.entities());
sql.setEntity(sysUserService.getEntity());
List<Sys_user> list = sysUserService.execute(sql).getList(Sys_user.class);
return list;
return activitySchoolApplyService.searchSystemUsers(queryString, userId);
}
@@ -280,7 +280,7 @@ public class ActivityCommonController {
app.activityUnionName,
app.sex,
COALESCE(NULLIF(app.mobile, ''), u.mobile) mobile,
u.idCard idcard,
COALESCE(NULLIF(app.idCard, ''), u.idCard) idcard,
COALESCE(unit.unitcode, u.unitcode) unitcode,
CASE WHEN u.member = 1 THEN '是' ELSE '否' END isMember,
CASE
@@ -179,8 +179,8 @@ public class ActivityGradesController {
Cnd cnd = Cnd.NEW();
Sql sql = Sqls.create("""
SELECT
us.unitname,
apply.*
apply.*,
COALESCE(NULLIF(apply.unitname, ''), us.unitname) unitname
FROM
activity_school_apply apply
LEFT JOIN `user` us ON apply.userId = us.id $condition
@@ -138,6 +138,10 @@ public class ActivityInfoManageController {
} else if ("four".equals(query)) {
cnd.and(new Static("now() > school.endTime"));
}
// 时间状态快捷查询只展示已提交活动;历史记录的 isSave 可能为空。
if ("one".equals(query) || "two".equals(query) || "three".equals(query) || "four".equals(query)) {
cnd.and(new Static("(school.isSave IS NULL OR school.isSave <> 1)"));
}
if (!ShiroUtil.hasAnyRoles("sysadmin,A06")) {
if (ActivityRoleUtil.isUnionManager()) {
cnd.and("school.belongUnionId", "in", ActivityRoleUtil.getManageUnionStr(ShiroUtil.getUserId()));
@@ -112,42 +112,21 @@ public class ActivityReadingController {
ActivityBasicUnit basicUnit = activitySchoolService.dao().fetch(ActivityBasicUnit.class, Cnd.where("id", "=", unitId));
ActivityBasicUnion basicUnion = activitySchoolService.dao().fetch(ActivityBasicUnion.class, Cnd.where("id", "=", basicUnit.getUnionid()));
ActivitySchool activitySchool = activitySchoolService.fetch(id);
Cnd cnd = Cnd.NEW();
Sql sql = Sqls.create("""
SELECT
app.*,
school.`name` activityName,
school.applyType applyType,
school.belongUnionId belongUnionId,
school.belongClubId belongClubId,
team.`name` team,
COALESCE(NULLIF(app.unitname, ''), unit.`name`) unitname,
ev.allName,
ev.id evid,
ev.projectType
FROM
activity_school_apply app
LEFT JOIN activity_school school ON school.id = app.activityId
LEFT JOIN activity_school_team team ON app.teamId = team.id
LEFT JOIN activity_event ev ON ev.id = app.eventId
LEFT JOIN sys_unit unit ON app.unitId = unit.id
$condition
""");
cnd.and("app.activityId", "=", id);
// cnd.and("YEAR(app.applyDate)", "=", year);
sql.setCondition(cnd);
List<NutMap> applies = activitySchoolApplyService.listRepresentedApplies(id);
if (!ShiroUtil.hasAnyRoles("sysadmin,A06")) {
if ((ActivityRoleUtil.isUnionManager() || ShiroUtil.hasRole("gh08")) && activityLevel.equals("40002")) {
cnd.and("app.activityUnionId", "=", basicUnit.getUnionid());
cnd.and("school.belongUnionId", "=", basicUnit.getUnionid());
// 权限仍按活动所属工会限制;报名分组按外援选中的单位工会计算。
applies = basicUnit.getUnionid().equals(activitySchool.getBelongUnionId())
? applies.stream().filter(v -> basicUnit.getUnionid().equals(v.getString("representedActivityUnionId")))
.collect(Collectors.toList()) : List.of();
}
if (ShiroUtil.hasRole("H02") && activityLevel.equals("40003")) {
cnd.and("school.belongClubId", "in", vi.getMangeClubStr());
if (activitySchoolService.count(Cnd.where("id", "=", id)
.and("belongClubId", "in", vi.getMangeClubStr())) == 0) {
applies = List.of();
}
}
}
cnd.desc("ev.allName").asc("ev.projectType").asc("team.`name`");
List<NutMap> applies = activitySchoolApplyService.listMap(sql);
List<NutMap> apply = new ArrayList();
@@ -157,10 +136,6 @@ public class ActivityReadingController {
unionCnd.and("id", "=", basicUnit.getUnionid());
}
}
if (activityLevel.equals("40002")) {
unionCnd.and("id", "=", activitySchool.getBelongUnionId());
}
unionCnd.and("isEnable", "=", true);
unionCnd.asc("unioncode");
if (!activityLevel.equals("40003")) {
@@ -168,10 +143,20 @@ public class ActivityReadingController {
for (ActivityBasicUnion union : unions) {
String unionId = union.getId();
boolean hasApplies = applies.stream()
.anyMatch(v -> unionId.equals(v.getString("representedActivityUnionId")));
if (!union.isEnable() && !hasApplies) {
continue;
}
// 院级活动保留主办工会,同时展示由所选单位代表其他工会出战的报名。
if (activityLevel.equals("40002") && !unionId.equals(activitySchool.getBelongUnionId())
&& !hasApplies) {
continue;
}
NutMap map = new NutMap();
List<NutMap> applyList = applies.stream().filter(v -> {
return v.getString("activityUnionId").equals(unionId);
return unionId.equals(v.getString("representedActivityUnionId"));
}).collect(Collectors.toList());
map.setv("id", unionId);
map.setv("unionname", union.getUnionname());
@@ -183,7 +168,7 @@ public class ActivityReadingController {
}
} else {
NutMap map = new NutMap();
Sys_club sysClub = sysClubService.fetch(cnd.where("id", "=", activitySchool.getBelongClubId()));
Sys_club sysClub = sysClubService.fetch(Cnd.where("id", "=", activitySchool.getBelongClubId()));
map.setv("id", sysClub.getId());
map.setv("unionname", sysClub.getName());
map.setv("unioncode", sysClub.getCode());
@@ -246,12 +231,16 @@ public class ActivityReadingController {
FROM
`activity_school_apply` apply
LEFT JOIN activity_event `event` ON `event`.id = apply.eventId
LEFT JOIN sys_unit selectedUnit ON selectedUnit.id = apply.unitId
LEFT JOIN activity_basic_unit activityUnit ON activityUnit.id = apply.unitId
LEFT JOIN activity_basic_settings groupSetting ON groupSetting.id = `event`.competitionCategory
LEFT JOIN activity_basic_settings projectNameSetting ON projectNameSetting.id = `event`.sports
LEFT JOIN activity_basic_settings distanceSetting ON distanceSetting.id = `event`.distance
WHERE
apply.activityId = @id
AND apply.unionId = @unionId
AND (CASE WHEN apply.unitname = selectedUnit.name AND selectedUnit.unionid IS NOT NULL
AND activityUnit.unionid IS NOT NULL THEN selectedUnit.unionid
ELSE apply.unionId END) = @unionId
AND JSON_CONTAINS(apply.identity,JSON_Array( '1' ))
ORDER BY
FIELD( apply.sex, '男', '女 ' ),
@@ -23,10 +23,12 @@ import org.nutz.dao.util.cri.Static;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
@@ -139,20 +141,21 @@ public class ActivityRemindController {
WHERE
sur.unionid = un.id
AND sr.id IN (@h04RoleId, @activityRoleId)
) unionleader,
( SELECT COUNT( 1 ) FROM activity_school_apply asp WHERE asp.unionId = un.id AND asp.activityId = @activityId ) num
) unionleader
FROM
`sys_union` un
ORDER BY
un.unioncode
""").setParam("h04RoleId", Roles.UNION_MANGER)
.setParam("activityRoleId", Roles.SPORTS_ACTIVITY_UNION_ADMIN)
.setParam("activityId", activityId);
.setParam("activityRoleId", Roles.SPORTS_ACTIVITY_UNION_ADMIN);
List<Record> unions = sysUnionService.list(sql);
Map<String, List<NutMap>> appliesByUnion = activitySchoolApplyService.groupRepresentedAppliesByUnion(activityId);
unions.forEach(v -> {
v.set("users", activitySchoolApplyService.query(Cnd.where("unionId", "=", v.getString("id")).and("activityId", "=", activityId).desc("identity")));
List<NutMap> users = appliesByUnion.getOrDefault(v.getString("id"), List.of());
v.set("users", users);
v.set("num", users.size());
});
return unions;
@@ -185,20 +188,20 @@ public class ActivityRemindController {
WHERE
sur.unionid = un.id
AND sr.id IN (@h04RoleId, @activityRoleId)
) telephone,
( SELECT COUNT( 1 ) FROM activity_school_apply asp WHERE asp.unionId = un.id AND asp.activityId = @activityId ) num
) telephone
FROM
`sys_union` un
ORDER BY
un.unioncode
""").setParam("h04RoleId", Roles.UNION_MANGER)
.setParam("activityRoleId", Roles.SPORTS_ACTIVITY_UNION_ADMIN)
.setParam("activityId", activityId);
.setParam("activityRoleId", Roles.SPORTS_ACTIVITY_UNION_ADMIN);
List<Record> unions = sysUnionService.list(sql);
Map<String, List<NutMap>> appliesByUnion = activitySchoolApplyService.groupRepresentedAppliesByUnion(activityId);
List<Record> collect = unions.stream().filter(v -> {
return v.getInt("num") <= 0 && v.getString("telephone") != null;
return appliesByUnion.getOrDefault(v.getString("id"), List.of()).isEmpty()
&& v.getString("telephone") != null;
}).collect(Collectors.toList());
collect.forEach(v -> {
@@ -5,6 +5,7 @@ import io.v.nutz.activity.models.ActivityBasicUnion;
import io.v.nutz.activity.models.ActivityBasicUnit;
import io.v.nutz.activity.models.ActivityResults;
import io.v.nutz.activity.models.ActivitySchoolApply;
import io.v.nutz.activity.services.ActivitySchoolApplyService;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.base.utils.DateUtil;
import io.v.nutz.base.utils.Vi;
@@ -47,6 +48,9 @@ public class ActivityResultsController {
@Inject("ActivitySchoolApply")
private ViService<ActivitySchoolApply> activitySchoolApplyViService;
@Inject
private ActivitySchoolApplyService activitySchoolApplyService;
@At("")
@Ok("beetl:platform/activity/school/ActivityResults.html")
@@ -195,32 +199,14 @@ public class ActivityResultsController {
/**
* 查询团体参赛部门工会
*
* @param activityId
* @param eventId
* @return
* activityId、eventId 为页面当前活动和项目ID,仅保留接口参数兼容原调用。
* 返回全部系统工会列表,每项包含 id 和 unionname,供团体成绩录入选择。
*/
@At
@ViReturn
public Object getUnionList(String activityId, String eventId) {
Sql sql = Sqls.create("""
SELECT
un.id,
un.unionname
FROM
activity_school_apply asa
LEFT JOIN sys_union un ON un.id = asa.unionId
WHERE
asa.activityId = @activityId
AND asa.eventId = @eventId
GROUP BY
un.unionname
""").setParam("activityId", activityId).setParam("eventId", eventId);
return activitySchoolApplyViService.list(sql);
return activitySchoolApplyService.getResultUnionOptions();
}
@Inject("Sys_user")
@@ -168,6 +168,9 @@ public class ActivitySchoolApply implements Serializable {
private String mobile;
@Column
@Comment("报名时使用的身份证号,系统资料为空时保存本次手动补充值")
@ColDefine(type = ColType.VARCHAR, width = 50)
@Excel(name = "身份证号", width = 20)
private String idCard;
@@ -13,19 +13,49 @@ import java.util.Map;
*/
public interface ActivitySchoolApplyService extends ViService<ActivitySchoolApply> {
/**
* 获取团体成绩录入可选的全部系统工会,不受活动项目报名情况或启用状态限制。
* @return 工会列表,每项包含 id(成绩记录使用的工会ID)和 unionname(下拉显示名称)
*/
List<NutMap> getResultUnionOptions();
/** @return 当前年份预计分配的外援工号;实际工号以创建接口返回值为准。 */
String previewExternalLoginname();
/**
* 创建仅用于报名的外援用户,按年份生成 wy+年份+流水号的工号。
* 按姓名或工号查找可复用的系统人员。
* @param queryString 搜索内容,空值返回空列表
* @param excludedUserIds 当前页面需要排除的用户 ID
* @return 人员列表,每项包含 id、姓名、工号、性别、身份证号、出生日期、电话和系统单位
*/
List<NutMap> searchSystemUsers(String queryString, String[] excludedUserIds);
/**
* 查询活动报名及其代表出战的工会;历史报名仅在所存单位 ID 与单位名称一致时按该单位归属。
* @param activityId 活动 ID
* @return 报名列表,representedUnionId 是系统工会 ID,representedActivityUnionId 是活动工会 ID,
* 其余字段为报名、活动、项目和队伍的展示字段
*/
List<NutMap> listRepresentedApplies(String activityId);
/**
* 按代表出战的系统工会归集活动报名,供工会提醒名单和报名人数共用。
* @param activityId 活动 ID
* @return 以系统工会 ID 为键、该工会报名记录列表为值的 Map
*/
Map<String, List<NutMap>> groupRepresentedAppliesByUnion(String activityId);
/**
* 创建仅用于报名的用户;工号可采用 wy 年份流水号,也可手动填写。
* @param username 外援姓名
* @param sex 性别,男或女
* @param birthday 出生日期,yyyy-MM-dd
* @param loginname 留空时自动生成的工号;手动填写时不能与系统现有工号重复
* @param idcard 身份证号,出生日期从中读取
* @param mobile 手机号码
* @param unitId 报名人所在单位 ID,用于归属当前报名工会
* @return 已创建的系统用户,包含用户 ID 和自动生成的工号
* @return 已创建的系统用户,包含用户 ID、最终工号和系统资料
*/
Sys_user createExternalUser(String username, String sex, String birthday, String mobile, String unitId);
Sys_user createExternalUser(String username, String loginname, String sex, String idcard, String mobile, String unitId);
void doApplies(List<ActivitySchoolApply> applies);
@@ -1,6 +1,7 @@
package io.v.nutz.activity.services.impl;
import cn.hutool.core.util.IdcardUtil;
import io.v.nutz.activity.models.ActivityBasicUnion;
import io.v.nutz.activity.models.ActivityBasicUnit;
import io.v.nutz.activity.models.ActivitySchoolApply;
@@ -8,11 +9,13 @@ import io.v.nutz.activity.services.ActivitySchoolApplyService;
import io.v.nutz.base.utils.DateUtil;
import io.v.nutz.service.impl.ViServiceImpl;
import io.v.nutz.sys.models.Sys_user;
import io.v.nutz.sys.models.Sys_unit;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
@@ -30,6 +33,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@IocBean(args = {"refer:dao"})
@@ -41,44 +45,122 @@ public class ActivitySchoolApplyServiceImpl extends ViServiceImpl<ActivitySchool
super(dao);
}
@Override
public List<NutMap> getResultUnionOptions() {
// 团体成绩可以选择未报名该项目的工会,因此从系统工会表完整读取。
return listMap(Sqls.create("SELECT id, unionname FROM sys_union ORDER BY unioncode"));
}
@Override
public String previewExternalLoginname() {
String prefix = "wy" + Year.now().getValue();
Sql next = Sqls.create("SELECT COALESCE(MAX(CAST(SUBSTRING(loginname, 7) AS UNSIGNED)), 0) "
+ "FROM sys_user WHERE loginname REGEXP @pattern");
next.setParam("pattern", "^" + prefix + "[0-9]+$");
next.setCallback(Sqls.callback.integer());
dao().execute(next);
int number = ((Number) next.getResult()).intValue() + 1;
return prefix + String.format("%02d", number);
Sql sql = Sqls.create("SELECT loginname FROM sys_user WHERE loginname LIKE @prefix");
sql.setParam("prefix", prefix + "%");
long number = 0;
for (NutMap row : listMap(sql)) {
String loginname = row.getString("loginname");
if (Strings.isBlank(loginname) || !loginname.matches("^" + prefix + "[0-9]+$")) continue;
try {
number = Math.max(number, Long.parseLong(loginname.substring(prefix.length())));
} catch (NumberFormatException ignored) {
// 非法超长工号不参与流水号计算,保留其他有效工号的最大值。
}
}
return prefix + String.format("%02d", number + 1);
}
@Override
public List<NutMap> searchSystemUsers(String queryString, String[] excludedUserIds) {
String query = Strings.sNull(queryString).trim();
if (query.isEmpty()) return List.of();
Sql sql = Sqls.create("SELECT u.id,u.loginname,u.username,u.sex,u.idcard,u.birthday,u.mobile,"
+ "u.unitid,un.name unitname FROM sys_user u "
+ "LEFT JOIN sys_unit un ON un.id=u.unitid $condition LIMIT 30");
Cnd cnd = Cnd.NEW();
cnd.andEX("u.id", "not in", excludedUserIds);
SqlExpressionGroup names = new SqlExpressionGroup();
names.orLike("u.username", query).orLike("u.loginname", query);
cnd.and(names);
sql.setCondition(cnd);
return listMap(sql);
}
@Override
public List<NutMap> listRepresentedApplies(String activityId) {
// 兼容提交时曾写入操作人工会的外援记录;只有保存的单位 ID 和名称一致才采用所选单位的工会。
Sql sql = Sqls.create("""
SELECT app.*, school.name activityName, school.applyType, school.belongUnionId,
school.belongClubId, team.name team,
COALESCE(NULLIF(app.unitname, ''), unit.name) unitname,
ev.allName, ev.id evid, ev.projectType,
CASE WHEN app.unitname = unit.name AND unit.unionid IS NOT NULL
AND basicUnit.unionid IS NOT NULL THEN unit.unionid
ELSE app.unionId END representedUnionId,
CASE WHEN app.unitname = unit.name AND unit.unionid IS NOT NULL
AND basicUnit.unionid IS NOT NULL THEN basicUnit.unionid
ELSE app.activityUnionId END representedActivityUnionId
FROM activity_school_apply app
LEFT JOIN activity_school school ON school.id = app.activityId
LEFT JOIN activity_school_team team ON team.id = app.teamId
LEFT JOIN activity_event ev ON ev.id = app.eventId
LEFT JOIN sys_unit unit ON unit.id = app.unitId
LEFT JOIN activity_basic_unit basicUnit ON basicUnit.id = app.unitId
WHERE app.activityId = @activityId
ORDER BY ev.allName DESC, ev.projectType ASC, team.name ASC
""").setParam("activityId", activityId);
return listMap(sql);
}
@Override
public Map<String, List<NutMap>> groupRepresentedAppliesByUnion(String activityId) {
return listRepresentedApplies(activityId).stream()
.filter(row -> Strings.isNotBlank(row.getString("representedUnionId")))
.collect(Collectors.groupingBy(row -> row.getString("representedUnionId")));
}
@Override
@Aop(TransAop.READ_COMMITTED)
public Sys_user createExternalUser(String username, String sex, String birthday, String mobile, String unitId) {
public Sys_user createExternalUser(String username, String loginname, String sex, String idcard, String mobile, String unitId) {
String name = Strings.sNull(username).trim();
String account = Strings.sNull(loginname).trim();
String card = Strings.sNull(idcard).trim().toUpperCase();
String phone = Strings.sNull(mobile).trim();
if (name.isEmpty() || name.length() > 50 || !("男".equals(sex) || "女".equals(sex))
|| !phone.matches("^1[3456789]\\d{9}$")) {
throw new IllegalArgumentException("请填写外援姓名、性别及正确的手机号码");
}
if (account.length() > 50) {
throw new IllegalArgumentException("工号不能超过50位");
}
if (!card.matches("^[0-9]{17}[0-9X]$") || !IdcardUtil.isValidCard(card)) {
throw new IllegalArgumentException("请填写正确的18位身份证号");
}
String birthday = card.substring(6, 10) + "-" + card.substring(10, 12) + "-" + card.substring(12, 14);
try {
LocalDate.parse(birthday);
} catch (DateTimeParseException | NullPointerException e) {
throw new IllegalArgumentException("请选择正确的出生年月");
throw new IllegalArgumentException("身份证号中的出生日期不正确");
}
ActivityBasicUnit unit = dao().fetch(ActivityBasicUnit.class, unitId);
if (unit == null || Strings.isBlank(unit.getUnionid())) {
throw new IllegalArgumentException("当前报名单位未配置活动工会");
}
// 工号在服务端分配,避免多个浏览器按相同的前端预览号建人。
// 自动工号与手填工号都在同一锁内查重,避免并发创建出相同账号。
synchronized (EXTERNAL_NUMBER_LOCK) {
if (account.isEmpty()) account = previewExternalLoginname();
if (dao().count(Sys_user.class, Cnd.where("loginname", "=", account)) > 0) {
throw new IllegalArgumentException("工号已存在,请更换工号");
}
if (dao().count(Sys_user.class, Cnd.where("idcard", "=", card)) > 0) {
throw new IllegalArgumentException("身份证号已存在,请搜索并选择系统人员");
}
Sys_user user = new Sys_user();
user.setId(R.UU32());
user.setLoginname(previewExternalLoginname());
user.setLoginname(account);
user.setUsername(name);
user.setSex(sex);
user.setBirthday(birthday);
user.setIdcard(card);
user.setMobile(phone);
user.setUnitid(unitId);
user.setUnionid(unit.getUnionid());
@@ -102,17 +184,52 @@ public class ActivitySchoolApplyServiceImpl extends ViServiceImpl<ActivitySchool
public void doApplies(String activity, String union_id, String eventId,List<ActivitySchoolApply> applies) {
for (ActivitySchoolApply apply : applies) {
Sys_user user = Strings.isBlank(apply.getUserId()) ? null : dao().fetch(Sys_user.class, apply.getUserId());
if (Strings.isNotBlank(apply.getIdCard())) {
String card = apply.getIdCard().trim().toUpperCase();
if (!IdcardUtil.isValidCard(card)) {
throw new IllegalArgumentException("报名人员身份证号不正确");
}
apply.setIdCard(card);
}
if (user != null && user.isDisabled() && Strings.isNotBlank(user.getLoginname())
&& user.getLoginname().matches("wy[0-9]{6,}")) {
// 外援资料以创建时保存的用户记录为准,不接受浏览器修改工号或单位名称。
// 工号和姓名保持系统身份;身份证、出生日期和电话保留本次报名的纠错值。
apply.setLoginname(user.getLoginname());
apply.setUsername(user.getUsername());
apply.setSex(user.getSex());
apply.setBirthday(user.getBirthday());
apply.setMobile(user.getMobile());
apply.setUnitId(user.getUnitid());
apply.setUnitname("外援");
if (Strings.isNotBlank(user.getSex())) apply.setSex(user.getSex());
if (Strings.isBlank(apply.getIdCard())) apply.setIdCard(user.getIdcard());
if (Strings.isBlank(apply.getBirthday())) apply.setBirthday(user.getBirthday());
if (Strings.isBlank(apply.getMobile())) apply.setMobile(user.getMobile());
if (Strings.isBlank(apply.getUnitId())) {
apply.setUnitId(user.getUnitid());
}
if (Strings.isBlank(apply.getUnitname())) {
throw new IllegalArgumentException("请填写外援报名单位");
}
}
if (Strings.isNotBlank(apply.getUnitId()) && Strings.isNotBlank(apply.getUnitname())) {
Sys_unit selectedUnit = dao().fetch(Sys_unit.class, apply.getUnitId());
// 单位来自系统下拉时,所属工会就是本次代表出战的工会;手填单位保留报名人工会。
if (selectedUnit != null && apply.getUnitname().trim().equals(selectedUnit.getName())) {
ActivityBasicUnit activityUnit = dao().fetch(ActivityBasicUnit.class, apply.getUnitId());
if (Strings.isBlank(selectedUnit.getUnionid()) || activityUnit == null
|| Strings.isBlank(activityUnit.getUnionid())) {
throw new IllegalArgumentException("所选单位未配置活动工会,不能确定代表出战的工会");
}
ActivityBasicUnion activityUnion = dao().fetch(ActivityBasicUnion.class, activityUnit.getUnionid());
if (activityUnion == null) {
throw new IllegalArgumentException("所选单位对应的活动工会不存在");
}
apply.setUnionId(selectedUnit.getUnionid());
apply.setActivityUnionId(activityUnion.getId());
apply.setActivityUnionName(activityUnion.getUnionname());
}
}
}
// 重存时清理本操作人以前代表其他工会报名的记录,避免换单位后留下重复报名。
if (!applies.isEmpty()) {
clear(Cnd.where("activityId", "=", activity).and("eventId", "=", eventId)
.and("applyUser", "=", applies.get(0).getApplyUser()).and("activityUnionId", "!=", union_id));
}
clear(Cnd.where("activityId", "=", activity).and("activityUnionId", "=", union_id).and("eventId","=",eventId));
doApplies(applies);
@@ -97,7 +97,11 @@ public class ActivitySchoolServiceImpl extends ViServiceImpl<ActivitySchool> imp
@Aop(TransAop.READ_COMMITTED)
public String doReuseAdd(String sourceId, ActivitySchool school, ActivitySchoolEvent[] events) {
findReuseActivity(sourceId);
if (school.getEventsIds() == null || school.getEventsIds().isEmpty()) {
if (school.getEventsIds() == null) {
school.setEventsIds(new java.util.ArrayList<>());
}
boolean isDraft = Integer.valueOf(1).equals(school.getIsSave());
if (!isDraft && school.getEventsIds().isEmpty()) {
throw new IllegalArgumentException("请选择活动项目");
}
ActivitySchoolEvent[] cleanEvents = Arrays.stream(events == null ? new ActivitySchoolEvent[0] : events)
@@ -108,19 +112,22 @@ public class ActivitySchoolServiceImpl extends ViServiceImpl<ActivitySchool> imp
event.setId(null);
event.setActivityId(null);
}).toArray(ActivitySchoolEvent[]::new);
for (String eventId : school.getEventsIds()) {
ActivityEvent event = dao().fetch(ActivityEvent.class, Cnd.where("id", "=", eventId).and("whetherEnable", "=", 1));
if (event == null) {
throw new IllegalArgumentException("所选项目已停用或不存在,请重新选择");
}
if (Arrays.stream(cleanEvents).noneMatch(config -> eventId.equals(config.getEventId()))) {
throw new IllegalArgumentException("项目配置不完整,请进入下一步完善项目信息");
// 草稿允许暂不选择或配置项目;正式提交仍需验证项目可用且配置齐全。
if (!isDraft) {
for (String eventId : school.getEventsIds()) {
ActivityEvent event = dao().fetch(ActivityEvent.class, Cnd.where("id", "=", eventId).and("whetherEnable", "=", 1));
if (event == null) {
throw new IllegalArgumentException("所选项目已停用或不存在,请重新选择");
}
if (Arrays.stream(cleanEvents).noneMatch(config -> eventId.equals(config.getEventId()))) {
throw new IllegalArgumentException("项目配置不完整,请进入下一步完善项目信息");
}
}
}
school.setId(null);
school.setClose(false);
school.setFoundDate(DateUtil.getDate());
if (school.getActivityLevel() == 40002) {
if (Integer.valueOf(40002).equals(school.getActivityLevel())) {
school.setBelongUnionId(ActivityRoleUtil.getCurrentUnionId(dao()));
} else {
school.setBelongUnionId(null);
@@ -166,7 +173,7 @@ public class ActivitySchoolServiceImpl extends ViServiceImpl<ActivitySchool> imp
public String doAdd(ActivitySchool activitySchool, ActivitySchoolEvent[] events) {
activitySchool.setClose(false);
ActivitySchool school = insert(activitySchool);
addTeam(school.getId(), events);
addTeam(school.getId(), events == null ? new ActivitySchoolEvent[0] : events);
return school.getId();
}
@@ -174,7 +181,12 @@ public class ActivitySchoolServiceImpl extends ViServiceImpl<ActivitySchool> imp
@Override
@Aop(TransAop.READ_COMMITTED)
public void doEdit(ActivitySchool activitySchool, ActivitySchoolEvent[] events, String applyEndTime) {
long time = DateUtil.formatDate(applyEndTime);
// 未填写报名结束时间的草稿也可再次保存;没有项目时按空配置处理。
long time = Strings.isBlank(applyEndTime) ? 0 : DateUtil.formatDate(applyEndTime);
if (activitySchool.getEventsIds() == null) {
activitySchool.setEventsIds(new java.util.ArrayList<>());
}
ActivitySchoolEvent[] safeEvents = events == null ? new ActivitySchoolEvent[0] : events;
long l = System.currentTimeMillis();
if (l < time) {
@@ -191,7 +203,7 @@ public class ActivitySchoolServiceImpl extends ViServiceImpl<ActivitySchool> imp
return !activitySchool.getEventsIds().contains(v.getEventId());
}).collect(Collectors.toList());
List<ActivitySchoolEvent> collect1 = Lang.array2list(events).stream().filter(v -> {
List<ActivitySchoolEvent> collect1 = Lang.array2list(safeEvents).stream().filter(v -> {
return !query.stream().map(x -> x.getEventId()).collect(Collectors.toList()).contains(v.getEventId()) && (v.getId() == null || !v.getId().equals("allItems"));
}).collect(Collectors.toList());
@@ -206,7 +218,7 @@ public class ActivitySchoolServiceImpl extends ViServiceImpl<ActivitySchool> imp
if (collect1.size() > 0) {
addTeam(activitySchool.getId(), collect1.toArray(new ActivitySchoolEvent[collect1.size()]));
}
for (ActivitySchoolEvent event : events) {
for (ActivitySchoolEvent event : safeEvents) {
update(event);
}
@@ -21,6 +21,8 @@ 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.aop.interceptor.ioc.TransAop;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
@@ -67,16 +69,17 @@ public class ClothingRecordController {
@At
@RequiresAuthentication
@Aop(TransAop.READ_COMMITTED)
public Object doDelete(String projectId, String year, String userId) {
if(StrUtil.isBlank(projectId)) {
Result.error("项目不能为空!");
return Result.error("项目不能为空!");
}
if(StrUtil.isBlank(year)) {
Result.error("年份不能为空!");
return Result.error("年份不能为空!");
}
if(StrUtil.isBlank(userId)) {
Result.error("登记人不能为空!");
return Result.error("登记人不能为空!");
}
boolean b = clothingRegisterService.doDelete(projectId, year, userId);
return b ? Result.success(): Result.error();
@@ -104,6 +107,10 @@ public class ClothingRecordController {
return b ? Result.success(): Result.error();
}
/**
* year 为活动报名年度,projectId 为活动ID,userId 为登记人ID;旧项目记录按登记年度筛选。
* request 和 response 为当前 HTTP 请求及下载响应;返回值为 void,内容写入响应流。
*/
@At
@Ok("void")
public void doExport(String year, String projectId, String userId, HttpServletRequest request, HttpServletResponse response) throws IOException {
@@ -135,11 +142,13 @@ public class ClothingRecordController {
FROM
clothing_register c
LEFT JOIN `user` u ON u.id = c.buyUserId
LEFT JOIN activity_school s ON s.id = c.projectId
$condition
""");
cnd.and("year(ctime)", "=", year);
cnd.and("projectId", "=", projectId);
cnd.and("userId", "=", userId);
// 导出与台账采用同一业务年度,旧项目记录回退到登记年度。
cnd.and("COALESCE(YEAR(s.applyStartTime), YEAR(c.ctime))", "=", year);
cnd.and("c.projectId", "=", projectId);
cnd.and("c.userId", "=", userId);
sql.setCondition(cnd);
List<Record> buyUserList = clothingRegisterService.list(sql);
for (int i = 0; i < buyUserList.size(); i++) {
@@ -13,6 +13,8 @@ import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.nutz.dao.Cnd;
import org.nutz.dao.Sqls;
import org.nutz.dao.entity.Record;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
@@ -41,30 +43,43 @@ public class ClothingRegisterController {
@RequiresPermissions("clothing.register")
public void index() {}
/** pageForm 为分页和姓名工号条件;projectId 为活动ID;isBuy 为购买状态;sex、unionId 为可选筛选,返回人员分页数据。 */
@At
@ViReturn
public Object pageData(PageForm pageForm, Integer year, String projectId, Boolean isBuy, String sex, String unionId) {
public Object pageData(PageForm pageForm, String projectId, Boolean isBuy, String sex, String unionId) {
return clothingRegisterService.getRegisterUser(pageForm, year, projectId, isBuy, sex, unionId);
return clothingRegisterService.getRegisterUser(pageForm, projectId, isBuy, sex, unionId);
}
/**
* ids 为所选人员ID;projectId 为活动ID,购买限制以该活动的报名年度和已选项目为准;
* isBuy 为购买状态,true 时仅移除当前活动记录,false 时登记;
* 成功返回 null 由 ViReturn 包装,业务校验失败返回含错误说明的 Result。
*/
@At
@ViReturn
public Object doHandler(String[] ids, String projectId, Integer year, Boolean isBuy) {
@Aop(TransAop.READ_COMMITTED)
public Object doHandler(String[] ids, String projectId, Boolean isBuy) {
if(!isBuy) {
clothingRegisterService.add(ids, projectId);
} else {
clothingRegisterService.removeBuyUser(ids, projectId, year);
try {
if(!Boolean.TRUE.equals(isBuy)) {
clothingRegisterService.add(ids, projectId);
} else {
clothingRegisterService.removeBuyUser(ids, projectId);
}
} catch (IllegalArgumentException exception) {
// 重复购衣等业务校验错误直接返回给页面,供提交按钮显示明确原因。
return Result.error(exception.getMessage());
}
return null;
}
/** year 为空时返回全部已提交活动,否则按报名开始时间所属年度筛选;data 数组的每项包含活动 id 和 name。 */
@At
@ViReturn
public Object getBuyClothesProject() {
public Object getBuyClothesProject(Integer year) {
return clothingRegisterService.getBuyClothesProject();
return clothingRegisterService.getBuyClothesProject(year);
}
@At
@@ -1,7 +1,6 @@
package io.v.nutz.clothing.services;
import cn.wizzer.framework.page.Pagination;
import io.v.nutz.activity.models.ActivityBasicSettings;
import io.v.nutz.clothing.models.ClothingRegister;
import io.v.nutz.modules.models.PageForm;
import io.v.nutz.service.ViService;
@@ -20,29 +19,31 @@ public interface ClothingRegisterService extends ViService<ClothingRegister> {
/**
* 列表查询
*
* @param pageForm: 表单
* @param projectId: 项目id
* @param year: 年份
* @return io.v.nutz.base.page.Pagination
* @param pageForm: 分页及查询表单
* @param projectId: 活动ID,可为空
* @param year: 活动报名年度;旧项目记录按登记年度筛选
* @param unionId: 工会ID,可为空
* @return Pagination,list 为按活动、登记人和年度分组的台账,totalCount 为分组总数
* @author JyuHsin
* @date 2022/12/5
*/
Pagination pageData(PageForm pageForm, String projectId, String year, String unionId);
/**
* 获取所有买服装的项目
* @author JyuHsin
* @date 2022/12/5
* @return java.util.List<io.v.nutz.activity.models.ActivityBasicSettings>
* 获取已提交的校级活动,供服装登记、记录和台账选择项目。
* year 为空时返回全部年度,否则按活动报名开始时间所属年度筛选。
* 返回的每项包含活动 id(作为 projectId 保存和筛选)及 name(下拉显示名称)。
*/
List<ActivityBasicSettings> getBuyClothesProject();
List<NutMap> getBuyClothesProject(Integer year);
/**
* 获取项目人员名单
* @author JyuHsin
* @date 2022/12/5
* @param projectId: 项目id
* @return java.util.List<org.nutz.lang.util.NutMap>
* @param projectId: 活动ID
* @param userId: 登记人ID
* @param year: 活动报名年度,可为空;旧项目记录按登记年度筛选
* @return 人员列表,每项包含购买人资料及登记记录
*/
List<NutMap> findBuyClothesUser(String projectId, String userId, String year);
@@ -50,28 +51,25 @@ public interface ClothingRegisterService extends ViService<ClothingRegister> {
* 删除
* @author JyuHsin
* @date 2022/12/5
* @param projectId: 项目id
* @param year: 年份
* @param userId: 登记人
* @return void
* @param projectId: 活动ID
* @param year: 活动报名年度;旧项目记录按登记年度删除
* @param userId: 登记人ID
* @return 是否完成指定活动、年度和登记人的删除
*/
boolean doDelete(String projectId, String year, String userId);
/**
* 服装登记菜单查询用户
* @author JyuHsin
* @date 2022/12/5
* @param pageForm:
* @param year:
* @param projectId:
* @param isBuy:
* @return cn.wizzer.framework.page.Pagination
* 查询服装登记人员。pageForm 包含页码、页大小和姓名或工号查询条件;projectId 为当前活动ID;
* isBuy 为购买状态,true 返回与活动年度相差不超过一年且同类项目已购买的人员,false 返回未购买人员;
* sex 为可选性别,unionId 为可选工会ID。返回 Pagination,list 为人员及可移除状态,totalCount 为总人数。
*/
Pagination getRegisterUser(PageForm pageForm, Integer year, String projectId, Boolean isBuy, String sex, String unionId);
Pagination getRegisterUser(PageForm pageForm, String projectId, Boolean isBuy, String sex, String unionId);
/** ids 为本次登记的人员ID,projectId 为活动ID;两年内同类项目已有购买记录时拒绝提交。 */
Object add(String[] ids, String projectId);
boolean removeBuyUser(String[] ids, String projectId, Integer year);
/** ids 为人员ID,projectId 为当前活动ID;仅移除当前活动的登记记录,不删除其他同类活动的历史记录。 */
boolean removeBuyUser(String[] ids, String projectId);
boolean deleteBuyUser(String id);
}
@@ -2,7 +2,7 @@ package io.v.nutz.clothing.services.impl;
import cn.hutool.core.util.StrUtil;
import cn.wizzer.framework.page.Pagination;
import io.v.nutz.activity.models.ActivityBasicSettings;
import io.v.nutz.activity.models.ActivitySchool;
import io.v.nutz.base.utils.DateUtil;
import io.v.nutz.base.utils.Vi;
import io.v.nutz.clothing.models.ClothingRegister;
@@ -20,13 +20,18 @@ import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.Static;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.util.NutMap;
import org.nutz.trans.Trans;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @Author JyuHsin
@@ -48,14 +53,15 @@ public class ClothingRegisterServiceImpl extends ViServiceImpl<ClothingRegister>
Sql sql = Sqls.create("""
SELECT
c.*,
YEAR ( c.ctime ) AS `year`,
COALESCE(YEAR(s.applyStartTime), YEAR(c.ctime)) AS `year`,
u.username,
u.unionname,
a.name AS projectName,
COALESCE(s.name, a.name) AS projectName,
count(c.buyUserId) AS buyCount
FROM
clothing_register c
LEFT JOIN `user` u ON u.id = c.userId
LEFT JOIN activity_school s ON s.id = c.projectId
LEFT JOIN activity_basic_settings a ON a.CODE = c.projectId
$condition
""");
@@ -65,8 +71,9 @@ public class ClothingRegisterServiceImpl extends ViServiceImpl<ClothingRegister>
}
cnd.andExcludeEmpty("u.unionId", "=", unionId);
cnd.andExcludeEmpty("c.projectId", "=", projectId);
cnd.andExcludeEmpty("year(c.ctime)", "=", year);
cnd.groupBy("c.userId");
// 活动记录按报名年度归档;无法关联活动的旧记录仍按实际登记年度展示。
cnd.andExcludeEmpty("COALESCE(YEAR(s.applyStartTime), YEAR(c.ctime))", "=", year);
cnd.groupBy("c.projectId", "c.userId", "COALESCE(YEAR(s.applyStartTime), YEAR(c.ctime))");
cnd.desc("year").asc("projectId");
sql.setCondition(cnd);
@@ -74,8 +81,17 @@ public class ClothingRegisterServiceImpl extends ViServiceImpl<ClothingRegister>
}
@Override
public List<ActivityBasicSettings> getBuyClothesProject() {
return dao().query(ActivityBasicSettings.class, Cnd.where("clothing", "is not", null));
public List<NutMap> getBuyClothesProject(Integer year) {
// isSave=1 是草稿;历史已提交活动可能为 NULL,需与活动管理页的判定保持一致。
CndPlus cnd = CndPlus.create();
cnd.and(new Static("(isSave IS NULL OR isSave <> 1)"));
cnd.andExcludeEmpty("YEAR(applyStartTime)", "=", year);
cnd.desc("startTime").desc("foundDate");
return listMap(Sqls.create("""
SELECT id, name
FROM activity_school
$condition
""").setCondition(cnd));
}
@Override
@@ -94,26 +110,38 @@ public class ClothingRegisterServiceImpl extends ViServiceImpl<ClothingRegister>
FROM
clothing_register c
LEFT JOIN `user` u ON u.id = c.buyUserId
LEFT JOIN activity_school s ON s.id = c.projectId
$condition
""");
cnd.andExcludeEmpty("c.projectId", "=", projectId);
cnd.andExcludeEmpty("c.userId", "=", userId);
cnd.andExcludeEmpty("year(c.ctime)", "=", year);
cnd.andExcludeEmpty("COALESCE(YEAR(s.applyStartTime), YEAR(c.ctime))", "=", year);
sql.setCondition(cnd);
return listMap(sql);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public boolean doDelete(String projectId, String year, String userId) {
int clearCount = dao().clear(ClothingRegister.class, Cnd.where("projectId", "=", projectId)
.and("year(ctime)", "=", year).and("userId", "=", userId));
Cnd cnd = Cnd.where("projectId", "=", projectId).and("userId", "=", userId);
ActivitySchool school = dao().fetch(ActivitySchool.class, projectId);
String applyStartTime = school == null ? null : school.getApplyStartTime();
// 新活动以报名年度定位整组登记;旧项目没有活动年度时沿用登记时间筛选。
if (Strings.isNotBlank(applyStartTime) && applyStartTime.length() >= 4) {
if (!year.equals(applyStartTime.substring(0, 4))) {
return false;
}
} else {
cnd.and("year(ctime)", "=", year);
}
int clearCount = dao().clear(ClothingRegister.class, cnd);
return clearCount >= 0;
}
@Override
public Pagination getRegisterUser(PageForm pageForm, Integer year, String projectId, Boolean isBuy, String sex, String unionId) {
public Pagination getRegisterUser(PageForm pageForm, String projectId, Boolean isBuy, String sex, String unionId) {
CndPlus cnd = CndPlus.create();
Sql sql = Sqls.create("""
@@ -129,20 +157,105 @@ public class ClothingRegisterServiceImpl extends ViServiceImpl<ClothingRegister>
if (Strings.isNotBlank(pageForm.getSearchName()) && Strings.isNotBlank(pageForm.getSearchKeyword())) {
cnd.where().andLike(pageForm.getSearchName(), pageForm.getSearchKeyword());
}
if(year != null) {
int lastYear = year - 1;
cnd.and(new Static("id %s (select buyUserId from clothing_register where projectId = '%s' and (year(ctime) = '%s' or year(ctime) = '%s'))"
.formatted(isBuy ? "in" : "not in", projectId, year, lastYear)));
if (Strings.isBlank(projectId)) {
// 没有活动时不能把全部会员误显示为该活动的“未购买”人员。
cnd.and(new Static("1 = 0"));
} else {
// 活动名称和ID每年可能变化,购买状态按活动选中项目的运动项目及活动年度判断。
Set<String> purchasedIds = findPurchasedUserIds(projectId);
if (purchasedIds.isEmpty()) {
if (Boolean.TRUE.equals(isBuy)) cnd.and(new Static("1 = 0"));
} else {
cnd.and("u.id", Boolean.TRUE.equals(isBuy) ? "in" : "not in", purchasedIds.toArray(new String[0]));
}
}
sql.setCondition(cnd);
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
Pagination result = listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
if (Boolean.TRUE.equals(isBuy) && Strings.isNotBlank(projectId)) {
// 历史活动中已购买的人员可查看,但只能移除当前活动自身的登记记录。
Set<String> currentIds = listMap(Sqls.create("SELECT DISTINCT buyUserId AS buy_user_id FROM clothing_register WHERE projectId = @projectId")
.setParam("projectId", projectId)).stream()
.map(row -> row.getString("buy_user_id")).collect(Collectors.toSet());
List<NutMap> users = result.getList();
users.forEach(user -> user.setv("canRemove", currentIds.contains(user.getString("id"))));
}
return result;
}
/**
* projectId 为当前活动ID;返回该活动选择的运动项目标识。项目未配置 sports 时以项目ID兜底,
* 避免把不同项目误判为同类;已选项目为空时拒绝登记,防止绕过重复购买限制。
*/
private List<String> getSelectedSportKeys(String projectId) {
List<NutMap> events = listMap(Sqls.create("""
SELECT DISTINCT COALESCE(NULLIF(ae.sports, ''), ae.id) AS sport_key
FROM activity_school_event ase
JOIN activity_event ae ON ae.id = ase.eventId
WHERE ase.activityId = @projectId
""").setParam("projectId", projectId));
List<String> keys = events.stream().map(event -> event.getString("sport_key"))
.filter(Strings::isNotBlank).distinct().collect(Collectors.toList());
if (keys.isEmpty()) throw new IllegalArgumentException("当前活动未配置可识别的活动项目,无法登记服装");
return keys;
}
/**
* projectId 为当前活动ID;返回与活动年度相差不超过一年、购买过任一相同运动项目服装的人员ID。
* 年度取活动报名开始时间,避免跨年补登记时因 clothing_register.ctime 不同而漏判。
*/
private Set<String> findPurchasedUserIds(String projectId) {
ActivitySchool school = dao().fetch(ActivitySchool.class, projectId);
if (school == null || Integer.valueOf(1).equals(school.getIsSave())) {
throw new IllegalArgumentException("活动不存在或尚未提交");
}
String applyStartTime = school.getApplyStartTime();
if (Strings.isBlank(applyStartTime) || applyStartTime.length() < 4) {
throw new IllegalArgumentException("当前活动未设置报名开始时间,无法判断两年购买范围");
}
int activityYear;
try {
activityYear = Integer.parseInt(applyStartTime.substring(0, 4));
} catch (NumberFormatException exception) {
throw new IllegalArgumentException("当前活动报名开始时间格式不正确", exception);
}
List<String> sportKeys = getSelectedSportKeys(projectId);
CndPlus cnd = CndPlus.create();
// 补录历史活动时也要检查后一年的购买记录,避免登记顺序改变查重结果。
cnd.and("YEAR(s.applyStartTime)", "in", new Integer[]{activityYear - 1, activityYear, activityYear + 1});
cnd.and("COALESCE(NULLIF(ae.sports, ''), ae.id)", "in", sportKeys.toArray(new String[0]));
Sql sql = Sqls.create("""
SELECT DISTINCT c.buyUserId AS buy_user_id
FROM clothing_register c
JOIN activity_school s ON s.id = c.projectId
JOIN activity_school_event ase ON ase.activityId = s.id
JOIN activity_event ae ON ae.id = ase.eventId
$condition
""").setCondition(cnd);
return listMap(sql).stream().map(row -> row.getString("buy_user_id"))
.filter(Strings::isNotBlank).collect(Collectors.toSet());
}
@Override
@Aop(TransAop.READ_COMMITTED)
public Object add(String[] ids, String projectId) {
if (ids == null || ids.length == 0 || Strings.isBlank(projectId)) {
throw new IllegalArgumentException("请选择活动和登记人员");
}
List<String> userIds = Arrays.stream(ids).filter(Strings::isNotBlank).distinct().sorted().collect(Collectors.toList());
if (userIds.isEmpty()) throw new IllegalArgumentException("请选择登记人员");
// 锁定人员行后再查重,防止并发提交同时通过两年内同项目的校验。
for (String id : userIds) {
if (listMap(Sqls.create("SELECT id FROM `user` WHERE id = @id FOR UPDATE").setParam("id", id)).isEmpty()) {
throw new IllegalArgumentException("所选人员不存在,请刷新名单");
}
}
Set<String> purchasedIds = findPurchasedUserIds(projectId);
if (userIds.stream().anyMatch(purchasedIds::contains)) {
throw new IllegalArgumentException("所选人员在近两年内已购买同类项目服装,不能重复提交,请刷新名单");
}
List<ClothingRegister> clothingRegisterList = new ArrayList<>();
for (String id : ids) {
for (String id : userIds) {
ClothingRegister clothingRegister = new ClothingRegister();
clothingRegister.setProjectId(projectId);
clothingRegister.setUserId(ShiroUtil.getPrincipalProperty("id").toString());
@@ -154,12 +267,17 @@ public class ClothingRegisterServiceImpl extends ViServiceImpl<ClothingRegister>
}
@Override
public boolean removeBuyUser(String[] ids, String projectId, Integer year) {
@Aop(TransAop.READ_COMMITTED)
public boolean removeBuyUser(String[] ids, String projectId) {
if (ids == null || ids.length == 0 || Strings.isBlank(projectId)) {
throw new IllegalArgumentException("请选择当前活动中需要移除的人员");
}
Trans.exec(() -> {
for (String id : ids) {
dao().clear(ClothingRegister.class, Cnd.where("projectId", "=", projectId)
.and("year(ctime)", "=", year).and("buyUserId", "=", id));
Cnd cnd = Cnd.where("projectId", "=", projectId).and("buyUserId", "=", id);
// 同类历史活动的购买记录仍保留,仅删除当前活动中选中的登记。
dao().clear(ClothingRegister.class, cnd);
}
});
@@ -14,4 +14,9 @@ public interface SysConfigService extends BaseService<Sys_config> {
* @return
*/
List<Sys_config> getAllList();
/**
* 确保系统配置中存在学校待办开关;首次创建时默认关闭。
*/
void ensureTodoTaskConfig();
}
@@ -3,8 +3,10 @@ package io.v.nutz.sys.services.impl;
import io.v.nutz.base.service.impl.BaseServiceImpl;
import io.v.nutz.sys.models.Sys_config;
import io.v.nutz.sys.services.SysConfigService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.List;
@@ -21,4 +23,16 @@ public class SysConfigServiceImpl extends BaseServiceImpl<Sys_config> implements
public List<Sys_config> getAllList() {
return this.query(Cnd.where("delFlag", "=", false));
}
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void ensureTodoTaskConfig() {
if (fetch("TodoTaskEnabled") == null) {
Sys_config config = new Sys_config();
config.setConfigKey("TodoTaskEnabled");
config.setConfigValue("false");
config.setNote("启用学校统一待办推送、更新和删除;关闭时不调用学校待办网关");
insert(config);
}
}
}
@@ -5,6 +5,8 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import io.v.nutz.sys.models.Sys_config;
import io.v.nutz.sys.services.SysConfigService;
import io.v.nutz.web.commons.utils.ShiroUtil;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Dao;
@@ -26,6 +28,17 @@ import java.util.Map;
@Slf4j
public class TodoTaskInterfaceImpl implements TodoTaskInterface {
/**
* 只有系统配置明确开启时,才调用学校统一待办网关。
*/
private boolean isTodoTaskEnabled() {
Sys_config config = sysConfigService.fetch("TodoTaskEnabled");
return config != null && "true".equals(config.getConfigValue());
}
@Inject
private SysConfigService sysConfigService;
@Inject
private RedisService redisService;
@@ -52,6 +65,9 @@ public class TodoTaskInterfaceImpl implements TodoTaskInterface {
@Override
public void pushTask(TodoTask todoTask) {
if (!isTodoTaskEnabled()) {
return;
}
log.info("【推送待办入参】:" + Json.toJson(todoTask));
String accessToken = getAccessToken();
HttpRequest httpRequest = HttpUtil.createPost(TodoTaskConstants.PUSH_TASK_URL);
@@ -69,6 +85,9 @@ public class TodoTaskInterfaceImpl implements TodoTaskInterface {
@Override
public void updateTask(TodoTask todoTask) {
if (!isTodoTaskEnabled()) {
return;
}
log.info("【更新待办入参】:" + Json.toJson(todoTask));
String accessToken = getAccessToken();
HttpRequest httpRequest = HttpUtil.createPost(TodoTaskConstants.UPDATE_TASK_URL);
@@ -86,6 +105,9 @@ public class TodoTaskInterfaceImpl implements TodoTaskInterface {
@Override
public void deleteTask(TodoTask todoTask) {
if (!isTodoTaskEnabled()) {
return;
}
log.info("【删除待办入参】:" + Json.toJson(todoTask));
String accessToken = getAccessToken();
HttpRequest httpRequest = HttpUtil.createPost(TodoTaskConstants.DELETE_TASK_URL);
@@ -34,7 +34,7 @@ public class SysConfController {
@Ok("beetl:/platform/sys/conf/index.html")
@RequiresPermissions("sys.manager.conf")
public void index() {
sysConfigService.ensureTodoTaskConfig();
}
@At
@@ -27,6 +27,8 @@ import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
@@ -98,7 +100,12 @@ public class CondolenceApplyCon {
@At
@ViReturn
@Aop(TransAop.READ_COMMITTED)
public Object doAdd(@Param("data") Condolence condolence, String sign) {
if (condolenceService.hasHospitalCondolenceInCurrentYear(condolence)) {
return Result.error().addMsg("住院慰问每年只能申请一次,请于下一年度再申请");
}
List<Sys_file> files = condolence.getFiles();
if (Lang.isEmpty(files)) {
condolence.setFiles_num("0");
@@ -19,6 +19,14 @@ public interface CondolenceService extends ViService<Condolence> {
*/
NutMap findOne(String id);
/**
* 判断被慰问人在当前自然年是否已有住院慰问申请。
*
* @param application 本次申请,包含慰问类型、被慰问人和编辑时的申请 ID
* @return 已有其他同年住院慰问申请时返回 true,否则返回 false
*/
boolean hasHospitalCondolenceInCurrentYear(Condolence application);
/**
* 添加
*
@@ -2,9 +2,11 @@ package io.v.nutz.zgfw.service.impl;
import cn.wizzer.framework.base.service.BaseService;
import io.v.nutz.base.utils.DateUtil;
import io.v.nutz.modules.services.ReviewService;
import io.v.nutz.service.impl.ViServiceImpl;
import io.v.nutz.zgfw.model.condolence.Condolence;
import io.v.nutz.zgfw.model.condolence.CondolenceType;
import io.v.nutz.zgfw.service.CondolenceService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
@@ -28,9 +30,30 @@ public class CondolenceServiceImpl extends ViServiceImpl<Condolence> implements
private ReviewService reviewService;
@Inject
private BaseService baseService;
private final Dao condolenceDao;
public CondolenceServiceImpl(Dao dao) {
super(dao);
this.condolenceDao = dao;
}
@Override
public boolean hasHospitalCondolenceInCurrentYear(Condolence application) {
CondolenceType type = condolenceDao.fetch(CondolenceType.class, application.getType());
if (type == null || !"C".equals(type.getCode())) {
return false;
}
// 按被慰问人和申请年份限制次数;编辑时排除当前申请,已退回的其他申请仍计入次数。
String year = String.valueOf(DateUtil.getYear());
Cnd cnd = Cnd.where("be_user", "=", application.getBe_user())
.and("type", "=", application.getType())
.and("apply_time", ">=", year + "-01-01")
.and("apply_time", "<", (DateUtil.getYear() + 1) + "-01-01");
if (Strings.isNotBlank(application.getId())) {
cnd.and("id", "!=", application.getId());
}
return count(cnd) > 0;
}
@Override