commit
This commit is contained in:
@@ -156,6 +156,8 @@ public class SysHomeController {
|
||||
List<Sys_menu> menus = sysUserService.getMenus(SecurityUtil.getUserId());
|
||||
List<String> allMenuIds = menus.stream().map(Sys_menu::getId).toList();
|
||||
List<Sys_menu> sysMenus = list.stream()
|
||||
// 推荐项必须同时属于当前用户已授权菜单,避免首页展示无权限入口。
|
||||
.filter(menu -> allMenuIds.contains(menu.getId()))
|
||||
.sorted(Comparator.comparing(Sys_menu::getLocation, Comparator.nullsLast(Integer::compareTo))
|
||||
.thenComparing(Sys_menu::getId))
|
||||
.toList();
|
||||
@@ -174,8 +176,12 @@ public class SysHomeController {
|
||||
);
|
||||
List<Sys_menu> menus = sysUserService.getMenus(SecurityUtil.getUserId());
|
||||
List<String> allMenuIds = menus.stream().map(Sys_menu::getId).toList();
|
||||
//List<Sys_menu> sysMenus = list.stream().filter(m -> allMenuIds.contains(m.getId())).sorted(Comparator.comparingInt(Sys_menu::getLocation)).toList();
|
||||
List<Sys_menu> sysMenus = list.stream().sorted(Comparator.comparingInt(Sys_menu::getLocation)).toList();
|
||||
List<Sys_menu> sysMenus = list.stream()
|
||||
// 推荐项必须同时属于当前用户已授权菜单,避免首页展示无权限入口。
|
||||
.filter(menu -> allMenuIds.contains(menu.getId()))
|
||||
.sorted(Comparator.comparing(Sys_menu::getLocation, Comparator.nullsLast(Integer::compareTo))
|
||||
.thenComparing(Sys_menu::getId))
|
||||
.toList();
|
||||
return Result.success(sysMenus);
|
||||
}
|
||||
|
||||
|
||||
@@ -527,8 +527,8 @@ public class SysUnionController {
|
||||
@ApiOperation("分工会组成单位穿梭框数据")
|
||||
@SaCheckPermission("sys.manager.union.partUnit")
|
||||
public Result branchUnionPartUnitTransferData(String unionId) {
|
||||
// List<Sys_unit> units = dao.query(Sys_unit.class, Cnd.where("unitLevel", "=", 2).asc("unitcode"));
|
||||
List<Sys_unit> units = dao.query(Sys_unit.class, Cnd.where("unitTypeCode", "=", "1").asc("unitcode"));
|
||||
// 组成单位仅允许选择二级单位,单位类别不能替代层级判断。
|
||||
List<Sys_unit> units = dao.query(Sys_unit.class, Cnd.where("unitLevel", "=", 2).asc("unitcode"));
|
||||
List<String> selectUnitIds = units.stream().filter(unit -> StrUtil.isNotBlank(unit.getUnionId()) && unit.getUnionId().equals(unionId)).map(Sys_unit::getId).toList();
|
||||
List<Sys_unit> matchUnits = units.stream().filter(unit -> StrUtil.isBlank(unit.getUnionId()) || (StrUtil.isNotBlank(unit.getUnionId()) && unit.getUnionId().equals(unionId))).toList();
|
||||
NutMap transferData = NutMap.NEW().addv("selectUnitIds", selectUnitIds).addv("allUnits", matchUnits);
|
||||
@@ -542,7 +542,8 @@ public class SysUnionController {
|
||||
public Result branchUnionPartUnitSet(String unionId, @Param("unitIds") String[] unitIds) {
|
||||
sysUnitService.update(Chain.make("unionId", null), Cnd.where("unionId", "=", unionId));
|
||||
if (Lang.isNotEmpty(unitIds)) {
|
||||
sysUnitService.update(Chain.make("unionId", unionId), Cnd.where("id", "in", unitIds));
|
||||
// 保存时再次限制单位层级,避免绕过穿梭框提交三级或其他层级单位。
|
||||
sysUnitService.update(Chain.make("unionId", unionId), Cnd.where("id", "in", unitIds).and("unitLevel", "=", 2));
|
||||
}
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
+2
-1
@@ -245,7 +245,8 @@ public class ActivityBasicScopeController {
|
||||
COLUMN_COMMENT
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_NAME = 'sys_user'
|
||||
AND TABLE_SCHEMA = 'budwk_v5_mini'
|
||||
-- 使用当前连接数据库,避免环境切换后查询不到字段元数据。
|
||||
AND TABLE_SCHEMA = DATABASE()
|
||||
""");
|
||||
List<NutMap> sqlDataList = activityBasicScopeService.listMap(sql);
|
||||
// sqlDataList.forEach(v -> v.put("DATA_TYPE", new String((byte[]) v.get("DATA_TYPE"))));
|
||||
|
||||
+8
-4
@@ -3,7 +3,7 @@ package com.budwk.app.zhgh.dayofficework.healthCheckup.service.impl;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.base.utils.ManyAddOrRenewUtil;
|
||||
import com.budwk.app.sys.views.View_user;
|
||||
import com.budwk.app.zhgh.activity.basic.models.ActivityUserScope;
|
||||
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
|
||||
import com.budwk.app.zhgh.dayofficework.healthCheckup.model.*;
|
||||
import com.budwk.app.zhgh.dayofficework.healthCheckup.service.HealthCheckupProjectService;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
@@ -36,6 +36,9 @@ public class HealthCheckupProjectServiceImpl extends BaseServiceImpl<HealthCheck
|
||||
@Inject
|
||||
private ManyAddOrRenewUtil manyAddOrRenewUtil;
|
||||
|
||||
@Inject
|
||||
private ActivityBasicScopeService activityBasicScopeService;
|
||||
|
||||
public HealthCheckupProjectServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
@@ -97,9 +100,10 @@ public class HealthCheckupProjectServiceImpl extends BaseServiceImpl<HealthCheck
|
||||
|
||||
public List<HealthCheckupUser> getHealthCheckupUserListByProject(HealthCheckupProject healthCheckupProject) {
|
||||
String projectId = healthCheckupProject.getId();
|
||||
List<ActivityUserScope> groupUsers = dao().query(ActivityUserScope.class, Cnd.where("groupId", "=", healthCheckupProject.getActivityGroupId()));
|
||||
List<String> userIds = groupUsers.stream().map(ActivityUserScope::getUserId).collect(Collectors.toList());
|
||||
List<View_user> userMapList = dao().query(View_user.class, Cnd.where("id", "in", userIds));
|
||||
// 可报名人员范围同时支持人员结果分组和 SQL 条件分组,统一通过分组服务动态解析人员,
|
||||
// 避免 SQL 条件分组因未保存 userId 明细而生成空名单。
|
||||
List<View_user> userMapList = dao().query(View_user.class, Cnd.where("id", "in",
|
||||
activityBasicScopeService.buildGroupUserIdSubSql(healthCheckupProject.getActivityGroupId())));
|
||||
return userMapList.stream().map(v -> {
|
||||
HealthCheckupUser user = new HealthCheckupUser();
|
||||
user.setId(R.UU32());
|
||||
|
||||
+15
@@ -97,6 +97,21 @@ public class OutlayManageClubQueryController {
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出社团拨付核对汇总表。
|
||||
*
|
||||
* @param year 统计年度,未传时默认当前年度
|
||||
* @param clubId 社团主键,可为空;为空时导出当前用户权限范围内的全部社团
|
||||
* @param response 下载响应,返回 XSSF Excel 文件流
|
||||
*/
|
||||
@At
|
||||
@Ok("void")
|
||||
@ApiOperation("导出社团拨付核对汇总表")
|
||||
@SaCheckPermission("outlay.outlayManage.club.query")
|
||||
public void exportSummary(Integer year, String clubId, HttpServletResponse response) {
|
||||
clubQueryService.exportSummary(year, clubId, response);
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@ApiOperation("提交核对")
|
||||
|
||||
+16
@@ -29,6 +29,7 @@ import org.nutz.lang.Strings;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -90,6 +91,21 @@ public class OutlayManageClubUseDetailController {
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出社团预算使用情况汇总表。
|
||||
*
|
||||
* @param year 统计年度,未传时默认当前年度
|
||||
* @param clubId 社团主键,可为空;为空时导出当前用户权限范围内的全部社团
|
||||
* @param response 下载响应,返回 XSSF Excel 文件流
|
||||
*/
|
||||
@At
|
||||
@Ok("void")
|
||||
@ApiOperation("导出社团预算使用情况")
|
||||
@SaCheckPermission("outlay.outlayManage.club.useDetail")
|
||||
public void doExport(Integer year, String clubId, HttpServletResponse response) {
|
||||
outlayUseDetailService.exportClubUseDetail(year, clubId, response);
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@ApiOperation("某个社团预算使用详情")
|
||||
|
||||
+11
@@ -5,9 +5,20 @@ import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
public interface OutlayManageClubQueryService extends BaseService {
|
||||
|
||||
void doMoney(String clubId);
|
||||
|
||||
Sql backUserSql(PageForm pageForm, String clubId, String type, Integer year, String payed);
|
||||
|
||||
/**
|
||||
* 导出社团拨付核对汇总表。
|
||||
*
|
||||
* @param year 统计年度,未传时默认当前年度
|
||||
* @param clubId 社团主键,可为空;为空时导出当前用户权限范围内的全部社团
|
||||
* @param response 下载响应,用于向浏览器输出 XSSF Excel 文件
|
||||
*/
|
||||
void exportSummary(Integer year, String clubId, HttpServletResponse response);
|
||||
}
|
||||
|
||||
+11
@@ -3,6 +3,8 @@ package com.budwk.app.zhgh.dayofficework.outlay.outlayManage.service;
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.model.OutlayUseDetail;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
public interface OutlayUseDetailService extends BaseService<OutlayUseDetail> {
|
||||
|
||||
|
||||
@@ -13,4 +15,13 @@ public interface OutlayUseDetailService extends BaseService<OutlayUseDetail> {
|
||||
*/
|
||||
void doDeleteDetail(String id,String outlayType);
|
||||
void doEditDetail(OutlayUseDetail outlayUseDetail,String outlayType);
|
||||
|
||||
/**
|
||||
* 导出社团预算使用情况汇总表。
|
||||
*
|
||||
* @param year 统计年度
|
||||
* @param clubId 社团主键,可为空;为空时导出当前用户权限范围内的全部社团
|
||||
* @param response 下载响应,用于向浏览器输出 XSSF Excel 文件
|
||||
*/
|
||||
void exportClubUseDetail(Integer year, String clubId, HttpServletResponse response);
|
||||
}
|
||||
|
||||
+80
@@ -1,5 +1,9 @@
|
||||
package com.budwk.app.zhgh.dayofficework.outlay.outlayManage.service.impl;
|
||||
|
||||
import cn.afterturn.easypoi.excel.ExcelExportUtil;
|
||||
import cn.afterturn.easypoi.excel.entity.ExportParams;
|
||||
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
|
||||
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
@@ -7,6 +11,11 @@ import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.base.utils.CommonDownloadUtil;
|
||||
import com.budwk.app.sys.models.Sys_user_role;
|
||||
import com.budwk.app.sys.services.SysRoleService;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.club.service.impl.SysClubUserServiceImpl;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.club.model.OutlayClubUserBackHistory;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.club.model.OutlayManageClub;
|
||||
@@ -19,10 +28,15 @@ import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -36,6 +50,10 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class OutlayManageClubQueryServiceImpl extends BaseServiceImpl implements OutlayManageClubQueryService {
|
||||
|
||||
@Inject
|
||||
private SysRoleService sysRoleService;
|
||||
|
||||
public OutlayManageClubQueryServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
@@ -189,4 +207,66 @@ public class OutlayManageClubQueryServiceImpl extends BaseServiceImpl implements
|
||||
sql.setCondition(cnd);
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出当前用户可查询的社团拨付核对汇总数据。
|
||||
* 年审人数、可拨付人数和不可拨付人数分别取当前年度记录中的历史统计值;
|
||||
* 拨付标准依据现有规则计算:0 人为 0 元,1 至 49 人为 3000 元,50 人及以上为 5000 元。
|
||||
*
|
||||
* @param year 统计年度,未传时使用当前年度
|
||||
* @param clubId 社团主键,可为空
|
||||
* @param response 下载响应,用于输出 Excel 文件
|
||||
*/
|
||||
@Override
|
||||
public void exportSummary(Integer year, String clubId, HttpServletResponse response) {
|
||||
Integer queryYear = year == null ? DateUtil.thisYear() : year;
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("year", "=", queryYear);
|
||||
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_CLUB_ADMIN.name())) {
|
||||
List<String> roleIds = sysRoleService.getRoleIdsByCode(List.of(RoleConstant.CLUB_PRESIDENT, RoleConstant.CLUB_SECRETARY));
|
||||
List<Sys_user_role> userRoles = dao().query(Sys_user_role.class,
|
||||
Cnd.where("userId", "=", SecurityUtil.getUserId())
|
||||
.and("roleId", "in", roleIds));
|
||||
List<String> myClubIds = userRoles.stream().map(Sys_user_role::getClubId).toList();
|
||||
cnd.andEX("clubId", "in", myClubIds);
|
||||
}
|
||||
cnd.andEX("clubId", "=", clubId);
|
||||
cnd.asc("clubCode");
|
||||
|
||||
Sql summarySql = Sqls.create("SELECT * FROM `outlay_manage_club` $condition");
|
||||
summarySql.setCondition(cnd);
|
||||
List<NutMap> summaryList = listMap(summarySql);
|
||||
for (NutMap summary : summaryList) {
|
||||
int giveMoneyNum = summary.getInt("historyGiveMoneyNum");
|
||||
summary.put("standard", getAllocateStandard(giveMoneyNum));
|
||||
summary.put("isCheckName", summary.getBoolean("isCheck") ? "已核对" : "未核对");
|
||||
}
|
||||
|
||||
List<ExcelExportEntity> exportEntities = new ArrayList<>();
|
||||
exportEntities.add(new ExcelExportEntity("社团名称", "clubName", 25));
|
||||
exportEntities.add(new ExcelExportEntity("年审人数", "historyUserNum", 15));
|
||||
exportEntities.add(new ExcelExportEntity("可拨付人数", "historyGiveMoneyNum", 15));
|
||||
exportEntities.add(new ExcelExportEntity("不可拨付人数", "historyNotGiveMoneyNum", 15));
|
||||
exportEntities.add(new ExcelExportEntity("标准", "standard", 15));
|
||||
exportEntities.add(new ExcelExportEntity("拨付总额", "totalQuota", 15));
|
||||
exportEntities.add(new ExcelExportEntity("是否核对", "isCheckName", 15));
|
||||
|
||||
ExportParams exportParams = new ExportParams();
|
||||
exportParams.setType(ExcelType.XSSF);
|
||||
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, exportEntities, summaryList);
|
||||
CommonDownloadUtil.download(queryYear + "年社团拨付核对汇总.xlsx", workbook, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据可拨付人数返回社团年度拨付标准。
|
||||
*
|
||||
* @param giveMoneyNum 可拨付人数
|
||||
* @return 对应的拨付标准金额
|
||||
*/
|
||||
private BigDecimal getAllocateStandard(int giveMoneyNum) {
|
||||
if (giveMoneyNum == 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return giveMoneyNum >= 50 ? BigDecimal.valueOf(5000) : BigDecimal.valueOf(3000);
|
||||
}
|
||||
}
|
||||
|
||||
+74
@@ -1,13 +1,35 @@
|
||||
package com.budwk.app.zhgh.dayofficework.outlay.outlayManage.service.impl;
|
||||
|
||||
import cn.afterturn.easypoi.excel.ExcelExportUtil;
|
||||
import cn.afterturn.easypoi.excel.entity.ExportParams;
|
||||
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
|
||||
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.base.utils.CommonDownloadUtil;
|
||||
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.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.club.model.OutlayManageClub;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.model.OutlayUseDetail;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.school.model.OutlayManageSchool;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.service.OutlayUseDetailService;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.union.model.OutlayManageUnion;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
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.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author zhf
|
||||
@@ -16,6 +38,10 @@ import org.nutz.ioc.loader.annotation.IocBean;
|
||||
*/
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class OutlayUseDetailServiceImpl extends BaseServiceImpl<OutlayUseDetail> implements OutlayUseDetailService {
|
||||
|
||||
@Inject
|
||||
private SysRoleService sysRoleService;
|
||||
|
||||
public OutlayUseDetailServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
@@ -34,6 +60,54 @@ public class OutlayUseDetailServiceImpl extends BaseServiceImpl<OutlayUseDetail>
|
||||
update(outlayUseDetail);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按社团预算使用页面的查询口径导出汇总数据。
|
||||
* 剩余额度由分配总额度减已使用额度计算,避免导出内容与页面展示不一致。
|
||||
*
|
||||
* @param year 统计年度,未传时使用当前年度
|
||||
* @param clubId 社团主键,可为空
|
||||
* @param response 下载响应,用于输出 Excel 文件
|
||||
*/
|
||||
@Override
|
||||
public void exportClubUseDetail(Integer year, String clubId, HttpServletResponse response) {
|
||||
Integer queryYear = year == null ? DateUtil.thisYear() : year;
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("totalQuota", "IS NOT", null);
|
||||
cnd.and("year", "=", queryYear);
|
||||
|
||||
if (AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
|
||||
cnd.andEX("clubId", "=", clubId);
|
||||
} else {
|
||||
Sys_role sysRole = sysRoleService.getByCode(RoleConstant.CLUB_PRESIDENT);
|
||||
List<Sys_user_role> userRoles = dao().query(Sys_user_role.class,
|
||||
Cnd.where("userId", "=", SecurityUtil.getUserId())
|
||||
.and("roleId", "=", sysRole.getId()));
|
||||
List<String> myClubIds = userRoles.stream().map(Sys_user_role::getClubId).toList();
|
||||
cnd.andEX("clubId", "in", myClubIds);
|
||||
}
|
||||
cnd.asc("clubCode");
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT *, COALESCE(totalQuota, 0) - COALESCE(usedQuota, 0) AS surplusQuota
|
||||
FROM outlay_manage_club $condition
|
||||
""");
|
||||
sql.setCondition(cnd);
|
||||
List<NutMap> clubUseDetailList = listMap(sql);
|
||||
|
||||
List<ExcelExportEntity> exportEntities = new ArrayList<>();
|
||||
exportEntities.add(new ExcelExportEntity("年度", "year", 15));
|
||||
exportEntities.add(new ExcelExportEntity("社团名称", "clubName", 25));
|
||||
exportEntities.add(new ExcelExportEntity("社团编码", "clubCode", 20));
|
||||
exportEntities.add(new ExcelExportEntity("分配总额度(元)", "totalQuota", 18));
|
||||
exportEntities.add(new ExcelExportEntity("已使用额度(元)", "usedQuota", 18));
|
||||
exportEntities.add(new ExcelExportEntity("剩余额度(元)", "surplusQuota", 18));
|
||||
|
||||
ExportParams exportParams = new ExportParams();
|
||||
exportParams.setType(ExcelType.XSSF);
|
||||
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, exportEntities, clubUseDetailList);
|
||||
CommonDownloadUtil.download(queryYear + "年社团预算使用情况.xlsx", workbook, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步预算主表的已使用额度,保证校工会、分工会、协会三类台账口径一致。
|
||||
*
|
||||
|
||||
+37
-6
@@ -265,6 +265,7 @@ public class ProposalMineController {
|
||||
Cnd cnd = Cnd.where("t1.sessionId", "=", sessionId);
|
||||
cnd.andEX("t1.delegationId", "=", delegationId);
|
||||
cnd.and("t1.roleId", "=", "72e24b5b4c4f4e90a6641e6af8487e42");
|
||||
cnd.and("t1.delFlag", "!=", 1);
|
||||
cnd.and("t1.loginName", "!=", SecurityUtil.getUserLoginname());
|
||||
|
||||
if (isInvite) {
|
||||
@@ -286,11 +287,33 @@ public class ProposalMineController {
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
/**
|
||||
* 邀请当前提案所属教代会届次内的正式代表参与附议。
|
||||
*
|
||||
* @param seconders 附议人工号数组,数组中的每个工号必须属于当前提案届次的有效正式代表
|
||||
* @param proposalId 提案ID,用于确定流程实例及可信的教代会届次
|
||||
* @return Result;邀请成功返回 success,提案不存在、代表信息无效或人数不足时返回错误信息
|
||||
*/
|
||||
@At
|
||||
@SaCheckPermission("proposal.mine")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SLog(tag = "提案", msg = "邀请附议人")
|
||||
public Result inviteSeconder(@Param("seconders") String[] seconders, @Valid String proposalId) {
|
||||
if (seconders == null || seconders.length == 0) {
|
||||
return Result.error("请选择附议人");
|
||||
}
|
||||
ProposalInfo proposalInfo = dao.fetch(ProposalInfo.class, proposalId);
|
||||
if (proposalInfo == null || StrUtil.isBlank(proposalInfo.getSessionId())) {
|
||||
return Result.error("提案信息不存在");
|
||||
}
|
||||
List<String> requestedLoginNames = List.of(seconders).stream()
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.distinct()
|
||||
.toList();
|
||||
if (requestedLoginNames.isEmpty()) {
|
||||
return Result.error("请选择附议人");
|
||||
}
|
||||
|
||||
// 查询已经邀请的附议人
|
||||
ProcessInstance processInstance = dao.fetch(ProcessInstance.class, Cnd.where(ProcessInstance::getBusinessNo, "=", proposalId));
|
||||
|
||||
@@ -319,11 +342,7 @@ public class ProposalMineController {
|
||||
|
||||
ProposalConfig proposalConfig = dao.fetch(ProposalConfig.class, Cnd.NEW());
|
||||
|
||||
if (tasks.size() + seconders.length < proposalConfig.getSeconderNum()) {
|
||||
return Result.error("至少邀请" + proposalConfig.getSeconderNum() + "个附议人");
|
||||
}
|
||||
|
||||
|
||||
// 必须使用提案自身的届次查询,不能只按工号匹配,否则同一代表的历史届次记录会污染代表团等快照信息。
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
t1.id,
|
||||
@@ -340,8 +359,20 @@ public class ProposalMineController {
|
||||
LEFT JOIN teacher_congress_delegation t2 ON t2.id = t1.delegationId
|
||||
$condition
|
||||
""");
|
||||
sql.setCondition(Cnd.where("t1.loginName", "in", seconders).groupBy("t1.loginName"));
|
||||
Cnd seconderCnd = Cnd.where("t1.sessionId", "=", proposalInfo.getSessionId())
|
||||
.and("t1.loginName", "in", requestedLoginNames)
|
||||
.and("t1.roleId", "=", "72e24b5b4c4f4e90a6641e6af8487e42")
|
||||
.and("t1.delFlag", "!=", 1);
|
||||
sql.setCondition(seconderCnd);
|
||||
List<NutMap> list = proposalCommonService.listMap(sql);
|
||||
// 每个工号在当前届次必须且只能匹配一条有效代表记录,防止无效工号或重复基础数据进入流程。
|
||||
if (list.size() != requestedLoginNames.size()) {
|
||||
return Result.error("部分附议人不属于当前提案届次或代表信息重复,请重新选择");
|
||||
}
|
||||
if (tasks.size() + list.size() < proposalConfig.getSeconderNum()) {
|
||||
return Result.error("至少邀请" + proposalConfig.getSeconderNum() + "个附议人");
|
||||
}
|
||||
|
||||
// 附议人ID
|
||||
List<String> userIds = list.stream().map(item -> item.getString("userId")).toList();
|
||||
// 获取正在执行的任务(邀请附议人||提案附议)
|
||||
|
||||
+37
-3
@@ -45,6 +45,7 @@ import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@IocBean(args = {"refer:dao"})
|
||||
@@ -76,14 +77,24 @@ public class ProposalCommitteeFilingUnitServiceImpl extends BaseServiceImpl<Prop
|
||||
throw new BaseException("没有进行中的流程任务");
|
||||
}
|
||||
|
||||
String proposalId = StrUtil.trim(args.getStr("proposalId"));
|
||||
if (StrUtil.isBlank(proposalId) || dao().fetch(ProposalInfo.class, proposalId) == null) {
|
||||
throw new BaseException("提案信息不存在");
|
||||
}
|
||||
args.put("proposalId", proposalId);
|
||||
validateAndNormalizeCaseFilingCode(args);
|
||||
|
||||
String proposalId = args.getStr("proposalId");
|
||||
List<String> proposalIds = proposalCommonService.mergeProposal(proposalId);
|
||||
if (ObjectUtil.isEmpty(proposalIds)) {
|
||||
proposalIds = List.of(proposalId);
|
||||
}
|
||||
|
||||
/*
|
||||
* 先同步 proposal_info 和承办单位,再执行 WF 流转,使查看页面及下一节点读取到的都是本次最新数据。
|
||||
* 当前方法处于同一事务中,后续流程执行失败时这里的业务数据会一并回滚。
|
||||
*/
|
||||
syncCommitteeFilingUnitData(proposalIds, args);
|
||||
|
||||
/*
|
||||
* 并案场景下,同一个节点会在每条提案实例上各有一条待办。
|
||||
* 这里统一按 taskName 找出整组正在办理中的任务,逐条执行,确保并案提案的流程状态保持一致。
|
||||
@@ -97,8 +108,6 @@ public class ProposalCommitteeFilingUnitServiceImpl extends BaseServiceImpl<Prop
|
||||
cloneArgs.put(FlowConst.PROCESS_TASK_ID_KEY, executeTask.getId());
|
||||
flowCommonService.executeTask(cloneArgs);
|
||||
}
|
||||
|
||||
syncCommitteeFilingUnitData(proposalIds, args);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,8 +119,10 @@ public class ProposalCommitteeFilingUnitServiceImpl extends BaseServiceImpl<Prop
|
||||
private void validateAndNormalizeCaseFilingCode(Dict args) {
|
||||
String caseFilingResultKey = FlowConst.TASK_FORM_DATA_PREFIX + "caseFilingResult";
|
||||
String caseFilingCodeKey = FlowConst.TASK_FORM_DATA_PREFIX + "caseFilingCode";
|
||||
String caseFilingTypeKey = FlowConst.TASK_FORM_DATA_PREFIX + "caseFilingType";
|
||||
if (!"CONFIRM_FILING".equals(args.getStr(caseFilingResultKey))) {
|
||||
args.put(caseFilingCodeKey, "");
|
||||
args.put(caseFilingTypeKey, "");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -139,6 +150,7 @@ public class ProposalCommitteeFilingUnitServiceImpl extends BaseServiceImpl<Prop
|
||||
.add("caseFilingResult", caseFilingResult)
|
||||
.add("caseFilingType", caseFilingType),
|
||||
Cnd.where(ProposalInfo::getId, "in", proposalIds));
|
||||
verifyCommitteeFilingUnitData(proposalIds, caseFilingCode, caseFilingResult, caseFilingType);
|
||||
|
||||
dao().clear(ProposalReplyUnit.class, Cnd.where(ProposalReplyUnit::getProposalId, "in", proposalIds));
|
||||
if (!List.of("CONFIRM_FILING", "SUGGESTION").contains(caseFilingResult)) {
|
||||
@@ -175,6 +187,28 @@ public class ProposalCommitteeFilingUnitServiceImpl extends BaseServiceImpl<Prop
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验本次立案字段是否已同步到全部提案,避免流程变量更新成功但 info 基础信息仍保留旧值。
|
||||
*/
|
||||
private void verifyCommitteeFilingUnitData(List<String> proposalIds, String caseFilingCode,
|
||||
String caseFilingResult, String caseFilingType) {
|
||||
List<ProposalInfo> proposalInfos = dao().query(ProposalInfo.class,
|
||||
Cnd.where(ProposalInfo::getId, "in", proposalIds));
|
||||
if (proposalInfos.size() != new LinkedHashSet<>(proposalIds).size()) {
|
||||
throw new BaseException("部分提案信息不存在,无法同步立案结果");
|
||||
}
|
||||
boolean syncFailed = proposalInfos.stream().anyMatch(proposalInfo ->
|
||||
!Objects.equals(StrUtil.nullToDefault(proposalInfo.getCaseFilingCode(), ""),
|
||||
StrUtil.nullToDefault(caseFilingCode, ""))
|
||||
|| !Objects.equals(StrUtil.nullToDefault(proposalInfo.getCaseFilingResult(), ""),
|
||||
StrUtil.nullToDefault(caseFilingResult, ""))
|
||||
|| !Objects.equals(StrUtil.nullToDefault(proposalInfo.getCaseFilingType(), ""),
|
||||
StrUtil.nullToDefault(caseFilingType, "")));
|
||||
if (syncFailed) {
|
||||
throw new BaseException("立案结果同步失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 一个提案可以配置多个主办单位和多个协办单位。
|
||||
* 这里按页面提交顺序逐条生成承办单位记录,兼容多主办的业务场景。
|
||||
|
||||
+1
@@ -95,6 +95,7 @@ public class SuggestionBoxMineController {
|
||||
}
|
||||
cnd.andEX("YEAR(info.submitTime)", "=", year);
|
||||
cnd.desc("info.submitTime");
|
||||
cnd.groupBy("info.id");
|
||||
sql.setCondition(cnd);
|
||||
Pagination<NutMap> pagination = suggestionBoxService.listPageMap(pageNumber, pageSize, sql);
|
||||
return Result.success(pagination);
|
||||
|
||||
+63
-7
@@ -20,6 +20,7 @@ import com.budwk.app.zhgh.learning.models.LearningCourseOutline;
|
||||
import com.budwk.app.zhgh.learning.models.LearningCourseType;
|
||||
import com.budwk.app.zhgh.learning.models.LearningOutlineResource;
|
||||
import com.budwk.app.zhgh.learning.models.LearningStudyRecord;
|
||||
import com.budwk.app.zhgh.learning.models.LearningStudyRule;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
@@ -29,6 +30,7 @@ import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.lang.Strings;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
@@ -59,7 +61,7 @@ public class LearningCourseDisplayController {
|
||||
private SysDictService sysDictService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/Learning/courseDisplay/index.html")
|
||||
@Ok("beetl:/platform/zhgh/Learning/courseDisplay/indexV2.html")
|
||||
@SaCheckPermission("learning.course.display")
|
||||
public void index() {
|
||||
}
|
||||
@@ -71,7 +73,7 @@ public class LearningCourseDisplayController {
|
||||
}
|
||||
|
||||
@At("/study")
|
||||
@Ok("beetl:/platform/zhgh/Learning/courseDisplay/study.html")
|
||||
@Ok("beetl:/platform/zhgh/Learning/courseDisplay/studyV2.html")
|
||||
@SaCheckPermission("learning.course.display")
|
||||
public void study() {
|
||||
}
|
||||
@@ -79,7 +81,12 @@ public class LearningCourseDisplayController {
|
||||
@At
|
||||
@ApiOperation("课程展示列表")
|
||||
@SaCheckPermission("learning.course.display")
|
||||
public Result pageData(PageForm pageForm, String keyword, String courseTypeId, String recommendFlag) {
|
||||
/**
|
||||
* 查询课程展示列表。
|
||||
* pageForm 传分页页码和每页条数;keyword、courseTypeId、recommendFlag 分别用于关键词、分类和推荐标识筛选;
|
||||
* sortType 支持 default(综合排序)和 view_count(观看次数倒序)。返回值为 Pagination,list 中包含课程基础字段、课节数、总时长和观看次数。
|
||||
*/
|
||||
public Result pageData(PageForm pageForm, String keyword, String courseTypeId, String recommendFlag, String sortType) {
|
||||
Cnd cnd = Cnd.where("c.status", "=", "published");
|
||||
if (StrUtil.isNotBlank(keyword)) {
|
||||
cnd.and(Cnd.exps("c.courseName", "like", "%" + keyword + "%")
|
||||
@@ -103,13 +110,22 @@ public class LearningCourseDisplayController {
|
||||
|
||||
Sql listSql = Sqls.create("""
|
||||
SELECT c.id, c.courseName, c.courseIntro, c.startTime, c.endTime, c.openType, c.cover,
|
||||
c.recommendFlags, c.courseTypeId, c.lecturerName, c.sortNum, t.typeName AS courseTypeName
|
||||
c.recommendFlags, c.courseTypeId, c.lecturerName, c.sortNum,
|
||||
COALESCE(c.viewCount, 0) AS viewCount, t.typeName AS courseTypeName,
|
||||
(SELECT COUNT(1) FROM learning_outline_resource r
|
||||
WHERE r.courseId = c.id AND r.status = 'enabled') AS resourceCount,
|
||||
COALESCE((SELECT SUM(r.durationSeconds) FROM learning_outline_resource r
|
||||
WHERE r.courseId = c.id AND r.status = 'enabled'), 0) AS totalDurationSeconds
|
||||
FROM learning_course c
|
||||
LEFT JOIN learning_course_type t ON t.id = c.courseTypeId
|
||||
$condition
|
||||
ORDER BY c.sortNum ASC, c.createdAt DESC
|
||||
ORDER BY $orderBy
|
||||
""");
|
||||
listSql.setCondition(cnd);
|
||||
// 排序字段由固定选项映射,禁止将前端参数直接拼接进SQL。
|
||||
listSql.setVar("orderBy", "view_count".equals(sortType)
|
||||
? "COALESCE(c.viewCount, 0) DESC, c.sortNum ASC, c.createdAt DESC"
|
||||
: "c.sortNum ASC, c.createdAt DESC");
|
||||
listSql.setPager(dao.createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
|
||||
listSql.setCallback(Sqls.callback.maps());
|
||||
dao.execute(listSql);
|
||||
@@ -126,13 +142,24 @@ public class LearningCourseDisplayController {
|
||||
@At
|
||||
@ApiOperation("课程学习基础信息")
|
||||
@SaCheckPermission("learning.course.display")
|
||||
/**
|
||||
* 查询学习页课程信息。
|
||||
* id 传课程主键;返回 NutMap,包含课程介绍、讲师、分类、课节数、总时长和后台配置的观看次数。
|
||||
*/
|
||||
public Result courseInfo(String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return Result.error("请选择课程");
|
||||
}
|
||||
// 学习页复用课程配置和资源统计数据,观看次数由课程管理后台配置,不在访问时自动累加。
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT c.id, c.courseName, c.courseIntro, c.startTime, c.endTime, c.openType, c.cover,
|
||||
c.recommendFlags, c.lecturerName, t.typeName AS courseTypeName
|
||||
SELECT c.id, c.courseName, c.courseIntro, c.suitablePeople, c.learningGoal, c.coursePeriod,
|
||||
c.courseTags, c.startTime, c.endTime, c.openType, c.cover, c.recommendFlags,
|
||||
c.lecturerName, c.lecturerInfo, COALESCE(c.viewCount, 0) AS viewCount,
|
||||
t.typeName AS courseTypeName,
|
||||
(SELECT COUNT(1) FROM learning_outline_resource r
|
||||
WHERE r.courseId = c.id AND r.status = 'enabled') AS resourceCount,
|
||||
COALESCE((SELECT SUM(r.durationSeconds) FROM learning_outline_resource r
|
||||
WHERE r.courseId = c.id AND r.status = 'enabled'), 0) AS totalDurationSeconds
|
||||
FROM learning_course c
|
||||
LEFT JOIN learning_course_type t ON t.id = c.courseTypeId
|
||||
WHERE c.id = @id
|
||||
@@ -161,12 +188,17 @@ public class LearningCourseDisplayController {
|
||||
List<LearningCourseOutline> outlines = dao.query(LearningCourseOutline.class, Cnd.where("courseId", "=", courseId).and("status", "=", "enabled").asc("sortOrder").asc("createdAt"));
|
||||
List<LearningOutlineResource> resources = dao.query(LearningOutlineResource.class, Cnd.where("courseId", "=", courseId).and("status", "=", "enabled").asc("sortOrder").asc("createdAt"));
|
||||
List<LearningStudyRecord> records = dao.query(LearningStudyRecord.class, Cnd.where("courseId", "=", courseId).and("userId", "=", SecurityUtil.getUserId()));
|
||||
List<LearningStudyRule> outlineRules = dao.query(LearningStudyRule.class, Cnd.where("courseId", "=", courseId).and("targetType", "=", "outline").and("status", "=", "enabled"));
|
||||
Map<String, List<NutMap>> childrenMap = new HashMap<>();
|
||||
List<NutMap> roots = new ArrayList<>();
|
||||
Map<String, LearningStudyRecord> recordMap = new HashMap<>();
|
||||
Map<String, LearningStudyRule> outlineRuleMap = new HashMap<>();
|
||||
for (LearningStudyRecord record : records) {
|
||||
recordMap.put(record.getOutlineId(), record);
|
||||
}
|
||||
for (LearningStudyRule outlineRule : outlineRules) {
|
||||
outlineRuleMap.put(outlineRule.getTargetId(), outlineRule);
|
||||
}
|
||||
|
||||
Map<String, NutMap> outlineNodeMap = new HashMap<>();
|
||||
for (LearningCourseOutline outline : outlines) {
|
||||
@@ -194,6 +226,9 @@ public class LearningCourseDisplayController {
|
||||
}
|
||||
}
|
||||
for (LearningOutlineResource resource : resources) {
|
||||
// 章节学习规则控制其下媒体播放行为;未配置规则时保持原有的可拖动行为。
|
||||
LearningStudyRule outlineRule = outlineRuleMap.get(resource.getOutlineId());
|
||||
boolean allowDrag = outlineRule == null || !Boolean.FALSE.equals(outlineRule.getAllowDrag());
|
||||
NutMap node = NutMap.NEW()
|
||||
.addv("id", resource.getId())
|
||||
.addv("type", "resource")
|
||||
@@ -207,6 +242,8 @@ public class LearningCourseDisplayController {
|
||||
.addv("progressPercent", recordMap.containsKey(resource.getOutlineId()) ? recordMap.get(resource.getOutlineId()).getProgressPercent() : 0)
|
||||
.addv("studySeconds", recordMap.containsKey(resource.getOutlineId()) ? recordMap.get(resource.getOutlineId()).getStudySeconds() : 0)
|
||||
.addv("lastPositionSeconds", recordMap.containsKey(resource.getOutlineId()) ? value(recordMap.get(resource.getOutlineId()).getLastPositionSeconds()) : 0)
|
||||
.addv("maxPositionSeconds", resourceMaxPosition(recordMap.get(resource.getOutlineId()), resource.getId()))
|
||||
.addv("allowDrag", allowDrag)
|
||||
.addv("completeStatus", recordMap.containsKey(resource.getOutlineId()) ? recordMap.get(resource.getOutlineId()).getCompleteStatus() : "not_started");
|
||||
childrenMap.computeIfAbsent(resource.getOutlineId(), key -> new ArrayList<>()).add(node);
|
||||
}
|
||||
@@ -294,4 +331,23 @@ public class LearningCourseDisplayController {
|
||||
private int value(Integer value) {
|
||||
return value == null ? 0 : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取指定资源的最高已观看秒数。该值用于两端限制快进,最近播放位置仍单独用于续播。
|
||||
*/
|
||||
private int resourceMaxPosition(LearningStudyRecord record, String resourceId) {
|
||||
if (record == null || StrUtil.isBlank(resourceId)) {
|
||||
return 0;
|
||||
}
|
||||
if (StrUtil.isBlank(record.getResourceMaxPositionData())) {
|
||||
return value(record.getLastPositionSeconds());
|
||||
}
|
||||
try {
|
||||
NutMap positions = Json.fromJson(NutMap.class, record.getResourceMaxPositionData());
|
||||
return Math.max(0, positions.getInt(resourceId, 0));
|
||||
} catch (Exception ignored) {
|
||||
// 历史记录未保存资源级位置时,从零开始建立最高观看位置。
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
@@ -18,6 +18,8 @@ import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
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.Strings;
|
||||
@@ -113,6 +115,8 @@ public class LearningCourseManageController {
|
||||
@ApiOperation("新增课程")
|
||||
@SaCheckPermission("learning.course.manage")
|
||||
@SLog(tag = "新增课程", msg = "课程名称:${args[0].courseName}")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
/** course 传课程表单字段,viewCount 为非负整数;返回 Result 表示新增是否成功。 */
|
||||
public Result doAdd(LearningCourse course) {
|
||||
Result checkResult = checkCourse(course, null);
|
||||
if (checkResult != null) {
|
||||
@@ -121,6 +125,9 @@ public class LearningCourseManageController {
|
||||
if (StrUtil.isBlank(course.getStatus())) {
|
||||
course.setStatus("draft");
|
||||
}
|
||||
if (course.getViewCount() == null) {
|
||||
course.setViewCount(0);
|
||||
}
|
||||
dao.insert(course);
|
||||
return Result.success();
|
||||
}
|
||||
@@ -129,6 +136,8 @@ public class LearningCourseManageController {
|
||||
@ApiOperation("编辑课程")
|
||||
@SaCheckPermission("learning.course.manage")
|
||||
@SLog(tag = "编辑课程", msg = "课程ID:${args[0].id}")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
/** course 传课程主键及需要更新的表单字段;返回 Result 表示编辑是否成功。 */
|
||||
public Result doEdit(LearningCourse course) {
|
||||
if (StrUtil.isBlank(course.getId())) {
|
||||
return Result.error("请选择要编辑的数据");
|
||||
@@ -145,6 +154,8 @@ public class LearningCourseManageController {
|
||||
@ApiOperation("删除课程")
|
||||
@SaCheckPermission("learning.course.manage")
|
||||
@SLog(tag = "删除课程", msg = "课程ID:${args[0]}")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
/** id 传课程主键;返回 Result,存在章节内容时返回不可删除提示。 */
|
||||
public Result doDelete(String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return Result.error("请选择要删除的数据");
|
||||
@@ -200,6 +211,9 @@ public class LearningCourseManageController {
|
||||
if (course.getSortNum() == null) {
|
||||
return Result.error("排序编码不能为空");
|
||||
}
|
||||
if (course.getViewCount() != null && course.getViewCount() < 0) {
|
||||
return Result.error("观看次数不能小于0");
|
||||
}
|
||||
if (!"long_term".equals(course.getOpenType()) && (course.getStartTime() == null || course.getEndTime() == null)) {
|
||||
return Result.error("请选择开课时间");
|
||||
}
|
||||
@@ -227,6 +241,7 @@ public class LearningCourseManageController {
|
||||
columns.put("status", "c.status");
|
||||
columns.put("recommendFlags", "c.recommendFlags");
|
||||
columns.put("sortNum", "c.sortNum");
|
||||
columns.put("viewCount", "c.viewCount");
|
||||
return columns.get(prop);
|
||||
}
|
||||
}
|
||||
|
||||
+143
-12
@@ -24,8 +24,11 @@ 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.json.Json;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
@@ -151,6 +154,11 @@ public class LearningStudyRecordController {
|
||||
@At
|
||||
@ApiOperation("开始学习")
|
||||
@SaCheckPermission("learning.course.display")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
/**
|
||||
* courseId、resourceId 分别传课程和资源主键,positionSeconds 传当前播放秒数;
|
||||
* 返回学习记录、学习时段、学习进度和本次自增后的课程观看次数。
|
||||
*/
|
||||
public Result start(@Param("courseId") String courseId,
|
||||
@Param("resourceId") String resourceId,
|
||||
@Param("positionSeconds") Integer positionSeconds) {
|
||||
@@ -176,18 +184,30 @@ public class LearningStudyRecordController {
|
||||
.and("outlineId", "=", outline.getId()));
|
||||
if (record == null) {
|
||||
record = buildRecord(userId, course, outline, now);
|
||||
record.setLastPositionSeconds(sanitizePosition(positionSeconds, 0));
|
||||
int startPositionSeconds = sanitizePosition(positionSeconds, 0);
|
||||
record.setLastPositionSeconds(startPositionSeconds);
|
||||
record.setResourceMaxPositionData(resourceMaxPositionData(resourceId, startPositionSeconds));
|
||||
dao.insert(record);
|
||||
} else {
|
||||
int requiredSeconds = requiredSeconds(outline.getId());
|
||||
dao.update(LearningStudyRecord.class, Chain.make("latestStudyTime", now)
|
||||
int startPositionSeconds = sanitizePosition(positionSeconds, 0);
|
||||
Map<String, Integer> resourceMaxPositions = resourceMaxPositions(record.getResourceMaxPositionData());
|
||||
int maxPositionSeconds = value(resourceMaxPositions.get(resourceId));
|
||||
boolean needUpdateMaxPosition = !resourceMaxPositions.containsKey(resourceId) || startPositionSeconds > maxPositionSeconds;
|
||||
if (needUpdateMaxPosition) {
|
||||
resourceMaxPositions.put(resourceId, Math.max(startPositionSeconds, maxPositionSeconds));
|
||||
}
|
||||
Chain chain = Chain.make("latestStudyTime", now)
|
||||
.add("courseName", course.getCourseName())
|
||||
.add("outlineName", outline.getTitle())
|
||||
.add("lastPositionSeconds", sanitizePosition(positionSeconds, record.getLastPositionSeconds()))
|
||||
.add("requiredSeconds", requiredSeconds)
|
||||
.add("progressPercent", progress(record.getStudySeconds(), requiredSeconds))
|
||||
.add("completeStatus", "completed".equals(record.getCompleteStatus()) ? "completed" : "studying"),
|
||||
Cnd.where("id", "=", record.getId()));
|
||||
.add("completeStatus", "completed".equals(record.getCompleteStatus()) ? "completed" : "studying");
|
||||
if (needUpdateMaxPosition) {
|
||||
chain.add("resourceMaxPositionData", Json.toJson(resourceMaxPositions));
|
||||
}
|
||||
dao.update(LearningStudyRecord.class, chain, Cnd.where("id", "=", record.getId()));
|
||||
record = dao.fetch(LearningStudyRecord.class, record.getId());
|
||||
}
|
||||
|
||||
@@ -204,19 +224,28 @@ public class LearningStudyRecordController {
|
||||
segment.setState("studying");
|
||||
dao.insert(segment);
|
||||
|
||||
// 每次成功创建学习时段记为一次观看,使用原子自增避免并发开始学习时丢失计数。
|
||||
dao.update(LearningCourse.class,
|
||||
Chain.makeSpecial("viewCount", "IFNULL(viewCount, 0) + 1"),
|
||||
Cnd.where("id", "=", courseId));
|
||||
LearningCourse updatedCourse = dao.fetch(LearningCourse.class, courseId);
|
||||
|
||||
return Result.success(NutMap.NEW()
|
||||
.addv("recordId", record.getId())
|
||||
.addv("segmentId", segment.getId())
|
||||
.addv("studySeconds", record.getStudySeconds())
|
||||
.addv("lastPositionSeconds", record.getLastPositionSeconds())
|
||||
.addv("maxPositionSeconds", resourceMaxPosition(record, resourceId))
|
||||
.addv("requiredSeconds", record.getRequiredSeconds())
|
||||
.addv("progressPercent", record.getProgressPercent())
|
||||
.addv("completeStatus", record.getCompleteStatus()));
|
||||
.addv("completeStatus", record.getCompleteStatus())
|
||||
.addv("viewCount", value(updatedCourse == null ? null : updatedCourse.getViewCount())));
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("学习心跳")
|
||||
@SaCheckPermission("learning.course.display")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result heartbeat(@Param("segmentId") String segmentId,
|
||||
@Param("activeSeconds") Integer activeSeconds,
|
||||
@Param("positionSeconds") Integer positionSeconds) {
|
||||
@@ -225,21 +254,27 @@ public class LearningStudyRecordController {
|
||||
return Result.error("学习时段已结束,请重新开始学习");
|
||||
}
|
||||
int seconds = Math.max(0, Math.min(activeSeconds == null ? 0 : activeSeconds, MAX_HEARTBEAT_SECONDS));
|
||||
return Result.success(addActiveSeconds(segment, seconds, false, positionSeconds));
|
||||
return Result.success(addActiveSeconds(segment, seconds, false, false, positionSeconds));
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("结束学习")
|
||||
@SaCheckPermission("learning.course.display")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
/**
|
||||
* segmentId 传当前学习时段主键,activeSeconds 传本次尚未上报的有效学习秒数,positionSeconds 传当前播放位置;
|
||||
* completed 仅在视频或音频自然播放结束时传 true。返回值为 NutMap,包含累计学习时长、进度和完成状态。
|
||||
*/
|
||||
public Result finish(@Param("segmentId") String segmentId,
|
||||
@Param("activeSeconds") Integer activeSeconds,
|
||||
@Param("positionSeconds") Integer positionSeconds) {
|
||||
@Param("positionSeconds") Integer positionSeconds,
|
||||
@Param("completed") Boolean completed) {
|
||||
LearningStudySegment segment = fetchOwnStudyingSegment(segmentId);
|
||||
if (segment == null) {
|
||||
return Result.success();
|
||||
}
|
||||
int seconds = Math.max(0, Math.min(activeSeconds == null ? 0 : activeSeconds, MAX_HEARTBEAT_SECONDS));
|
||||
NutMap result = addActiveSeconds(segment, seconds, true, positionSeconds);
|
||||
NutMap result = addActiveSeconds(segment, seconds, true, Boolean.TRUE.equals(completed), positionSeconds);
|
||||
dao.update(LearningStudySegment.class, Chain.make("endTime", new Date()).add("state", "finished"), Cnd.where("id", "=", segmentId));
|
||||
return Result.success(result);
|
||||
}
|
||||
@@ -247,6 +282,7 @@ public class LearningStudyRecordController {
|
||||
@At
|
||||
@ApiOperation("保存播放位置")
|
||||
@SaCheckPermission("learning.course.display")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result position(@Param("courseId") String courseId,
|
||||
@Param("resourceId") String resourceId,
|
||||
@Param("positionSeconds") Integer positionSeconds) {
|
||||
@@ -321,20 +357,28 @@ public class LearningStudyRecordController {
|
||||
record.setLatestStudyTime(now);
|
||||
record.setStudySeconds(0);
|
||||
record.setLastPositionSeconds(0);
|
||||
record.setResourceMaxPositionData("{}");
|
||||
record.setRequiredSeconds(requiredSeconds);
|
||||
record.setProgressPercent(0);
|
||||
record.setCompleteStatus("studying");
|
||||
return record;
|
||||
}
|
||||
|
||||
private NutMap addActiveSeconds(LearningStudySegment segment, int activeSeconds, boolean finish, Integer positionSeconds) {
|
||||
private NutMap addActiveSeconds(LearningStudySegment segment, int activeSeconds, boolean finish, boolean completed, Integer positionSeconds) {
|
||||
Date now = new Date();
|
||||
int segmentSeconds = value(segment.getActiveSeconds()) + activeSeconds;
|
||||
LearningStudyRecord record = dao.fetch(LearningStudyRecord.class, segment.getRecordId());
|
||||
LearningOutlineResource resource = dao.fetch(LearningOutlineResource.class, segment.getResourceId());
|
||||
int studyIncrementSeconds = effectiveStudySeconds(record, resource, segment.getResourceId(), activeSeconds, positionSeconds);
|
||||
int segmentSeconds = value(segment.getActiveSeconds()) + studyIncrementSeconds;
|
||||
dao.update(LearningStudySegment.class, Chain.make("activeSeconds", segmentSeconds).add("lastHeartbeatTime", now), Cnd.where("id", "=", segment.getId()));
|
||||
|
||||
LearningStudyRecord record = dao.fetch(LearningStudyRecord.class, segment.getRecordId());
|
||||
int studySeconds = value(record.getStudySeconds()) + activeSeconds;
|
||||
int studySeconds = value(record.getStudySeconds()) + studyIncrementSeconds;
|
||||
int requiredSeconds = requiredSeconds(record.getOutlineId());
|
||||
int maxPositionSeconds = resourceMaxPosition(record, segment.getResourceId());
|
||||
// 自然播放结束且当前章节仅有一个媒体资源时,补齐定时器未统计到的尾部秒数,避免跨端状态不一致。
|
||||
if (completed && canCompleteByNaturalEnd(segment, positionSeconds, requiredSeconds)) {
|
||||
studySeconds = Math.max(studySeconds, requiredSeconds);
|
||||
}
|
||||
int progressPercent = progress(studySeconds, requiredSeconds);
|
||||
String completeStatus = completeStatus(studySeconds, requiredSeconds, finish);
|
||||
if ("completed".equals(completeStatus)) {
|
||||
@@ -350,6 +394,13 @@ public class LearningStudyRecordController {
|
||||
lastPositionSeconds = positionSeconds;
|
||||
chain.add("lastPositionSeconds", positionSeconds);
|
||||
}
|
||||
if (resource != null && isMediaResource(resource)) {
|
||||
Map<String, Integer> resourceMaxPositions = resourceMaxPositions(record.getResourceMaxPositionData());
|
||||
int currentPositionSeconds = Math.max(0, positionSeconds == null ? 0 : positionSeconds);
|
||||
maxPositionSeconds = Math.max(value(resourceMaxPositions.get(segment.getResourceId())), currentPositionSeconds);
|
||||
resourceMaxPositions.put(segment.getResourceId(), maxPositionSeconds);
|
||||
chain.add("resourceMaxPositionData", Json.toJson(resourceMaxPositions));
|
||||
}
|
||||
if ("completed".equals(completeStatus) && record.getCompletedAt() == null) {
|
||||
chain.add("completedAt", now);
|
||||
}
|
||||
@@ -358,6 +409,7 @@ public class LearningStudyRecordController {
|
||||
.addv("studySeconds", studySeconds)
|
||||
.addv("studyTimeText", formatStudySeconds(studySeconds))
|
||||
.addv("lastPositionSeconds", lastPositionSeconds)
|
||||
.addv("maxPositionSeconds", maxPositionSeconds)
|
||||
.addv("requiredSeconds", requiredSeconds)
|
||||
.addv("progressPercent", progressPercent)
|
||||
.addv("completeStatus", completeStatus);
|
||||
@@ -396,6 +448,85 @@ public class LearningStudyRecordController {
|
||||
return Math.max(0, sql.getInt());
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验媒体自然播放结束能否完成当前章节。
|
||||
* segment 表示当前用户的学习时段;positionSeconds 必须到达资源末尾附近;requiredSeconds 为章节要求学习时长。
|
||||
* 仅单资源章节且配置时长不超过媒体时长时返回 true,防止一个资源结束误完成包含多个资源或设置了更长时长的章节。
|
||||
*/
|
||||
private boolean canCompleteByNaturalEnd(LearningStudySegment segment, Integer positionSeconds, int requiredSeconds) {
|
||||
if (positionSeconds == null || requiredSeconds <= 0) {
|
||||
return false;
|
||||
}
|
||||
LearningOutlineResource resource = dao.fetch(LearningOutlineResource.class, segment.getResourceId());
|
||||
if (resource == null || !("video".equals(resource.getResourceType()) || "audio".equals(resource.getResourceType()))) {
|
||||
return false;
|
||||
}
|
||||
int durationSeconds = value(resource.getDurationSeconds());
|
||||
if (durationSeconds <= 0 || positionSeconds < Math.max(0, durationSeconds - 2) || requiredSeconds > durationSeconds) {
|
||||
return false;
|
||||
}
|
||||
Sql countSql = Sqls.create("SELECT COUNT(1) FROM learning_outline_resource WHERE outlineId = @outlineId AND status = 'enabled'");
|
||||
countSql.params().set("outlineId", segment.getOutlineId());
|
||||
countSql.setCallback(Sqls.callback.integer());
|
||||
dao.execute(countSql);
|
||||
return countSql.getInt() == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅统计当前资源超过历史最高观看位置的新增区间,回退复习不会重复累计学习时长。
|
||||
* record 保存各资源最高位置,resource 用于区分媒体和非媒体资料,positionSeconds 为本次心跳时的播放器位置。
|
||||
*/
|
||||
private int effectiveStudySeconds(LearningStudyRecord record, LearningOutlineResource resource, String resourceId, int activeSeconds, Integer positionSeconds) {
|
||||
if (!isMediaResource(resource)) {
|
||||
return activeSeconds;
|
||||
}
|
||||
int currentPositionSeconds = Math.max(0, positionSeconds == null ? 0 : positionSeconds);
|
||||
int maxPositionSeconds = resourceMaxPosition(record, resourceId);
|
||||
return Math.min(activeSeconds, Math.max(0, currentPositionSeconds - maxPositionSeconds));
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取学习记录中按资源保存的最高已观看秒数。历史记录或异常 JSON 视为未保存,避免影响已有记录的使用。
|
||||
*/
|
||||
private int resourceMaxPosition(LearningStudyRecord record, String resourceId) {
|
||||
return value(resourceMaxPositions(record == null ? null : record.getResourceMaxPositionData()).get(resourceId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将资源 ID 与最高观看秒数转换为可持久化数据;键为资源 ID,值为该资源累计到达过的最远秒数。
|
||||
*/
|
||||
private String resourceMaxPositionData(String resourceId, int positionSeconds) {
|
||||
Map<String, Integer> positions = new HashMap<>();
|
||||
positions.put(resourceId, Math.max(0, positionSeconds));
|
||||
return Json.toJson(positions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析资源最高位置 JSON。返回值为资源 ID 到秒数的映射,空值或异常数据返回空映射。
|
||||
*/
|
||||
private Map<String, Integer> resourceMaxPositions(String data) {
|
||||
Map<String, Integer> positions = new HashMap<>();
|
||||
if (StrUtil.isBlank(data)) {
|
||||
return positions;
|
||||
}
|
||||
try {
|
||||
NutMap source = Json.fromJson(NutMap.class, data);
|
||||
for (String resourceId : source.keySet()) {
|
||||
positions.put(resourceId, Math.max(0, source.getInt(resourceId, 0)));
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// 历史记录可能没有该字段或保存了异常内容,按未记录最高位置处理。
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断资料是否使用播放器计时。视频和音频按未重复观看区间累计,其他资料沿用活跃时长累计。
|
||||
*/
|
||||
private boolean isMediaResource(LearningOutlineResource resource) {
|
||||
return resource != null && ("video".equals(resource.getResourceType()) || "audio".equals(resource.getResourceType()));
|
||||
}
|
||||
|
||||
private int progress(Integer studySeconds, int requiredSeconds) {
|
||||
if (requiredSeconds <= 0) {
|
||||
return 0;
|
||||
|
||||
@@ -110,6 +110,11 @@ public class LearningCourse extends BaseModel {
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer sortNum;
|
||||
|
||||
@Column
|
||||
@Comment("观看次数")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer viewCount;
|
||||
|
||||
@Column
|
||||
@Comment("课程封面")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 500)
|
||||
|
||||
@@ -97,6 +97,11 @@ public class LearningStudyRecord extends BaseModel {
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer lastPositionSeconds;
|
||||
|
||||
@Column
|
||||
@Comment("各资源最高已观看位置(JSON)")
|
||||
@ColDefine(type = ColType.TEXT)
|
||||
private String resourceMaxPositionData;
|
||||
|
||||
@Column
|
||||
@Comment("要求学习时长(秒)")
|
||||
@ColDefine(type = ColType.INT)
|
||||
|
||||
+13
@@ -17,6 +17,7 @@ import com.budwk.app.zhgh.staffmanage.member.constant.MemberChangeType;
|
||||
import com.budwk.app.zhgh.staffmanage.member.models.MemberChangeRecord;
|
||||
import com.budwk.app.zhgh.staffmanage.member.param.pageform.MemberChangePageForm;
|
||||
import com.budwk.app.zhgh.staffmanage.member.service.MemberCommonService;
|
||||
import com.budwk.app.sys.models.Sys_unit;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
@@ -155,6 +156,12 @@ public class MemberChangeMineController {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询会员变更记录详情。
|
||||
*
|
||||
* @param id 会员变更记录 ID。
|
||||
* @return 包含变更记录基础字段、三级单位名称及变更明细的 JSON 对象。
|
||||
*/
|
||||
@At
|
||||
@ApiOperation("根据id查询变更记录")
|
||||
@SaCheckPermission(value = {"member.change.mine", "member.change.unionGroup.audit"}, mode = SaMode.OR)
|
||||
@@ -162,6 +169,12 @@ public class MemberChangeMineController {
|
||||
MemberChangeRecord record = dao.fetch(MemberChangeRecord.class, id);
|
||||
NutMap nutMap = Lang.obj2nutmap(record);
|
||||
|
||||
// 变更记录只保存三级单位 ID,查看时补充当前单位名称供页面展示。
|
||||
if (StrUtil.isNotBlank(record.getThreeUnitId())) {
|
||||
Sys_unit threeUnit = dao.fetch(Sys_unit.class, record.getThreeUnitId());
|
||||
nutMap.put("threeUnitName", threeUnit == null ? "" : threeUnit.getName());
|
||||
}
|
||||
|
||||
List<NutMap> changeInfos = memberCommonService.getChangeInfos(nutMap.getString("id"));
|
||||
nutMap.put("changeInfos", changeInfos);
|
||||
return Result.success().addData(nutMap);
|
||||
|
||||
@@ -177,6 +177,11 @@ public class MemberChangeRecord extends BaseModel {
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String campus;
|
||||
|
||||
@Column
|
||||
@Comment("三级单位ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String threeUnitId;
|
||||
|
||||
@Column
|
||||
@Comment("退休日期")
|
||||
@ColDefine(type = ColType.DATE)
|
||||
|
||||
+4
@@ -30,6 +30,9 @@ public class MemberManagePageForm extends PageForm {
|
||||
|
||||
private String unitId;
|
||||
|
||||
/** 所属二级单位下的三级单位 ID。 */
|
||||
private String threeUnitId;
|
||||
|
||||
private String sex;
|
||||
|
||||
private String personType;
|
||||
@@ -91,6 +94,7 @@ public class MemberManagePageForm extends PageForm {
|
||||
cnd.andEX("u.member", "=", pageForm.getIsMember());
|
||||
cnd.andEX("u.welfareMember", "=", pageForm.getIsWelfareMember());
|
||||
cnd.andEX("u.unitId", "=", pageForm.getUnitId());
|
||||
cnd.andEX("u.threeUnitId", "=", pageForm.getThreeUnitId());
|
||||
cnd.andEX("u.userState", "=", pageForm.getUserState());
|
||||
cnd.andEX("u.preparedBy", "=", pageForm.getPreparedBy());
|
||||
cnd.andEX("u.sex", "=", pageForm.getSex());
|
||||
|
||||
+6
@@ -257,6 +257,12 @@ public class MemberManageServiceImpl extends BaseServiceImpl<Sys_user> implement
|
||||
Set<String> allowChangeFieldNames = map.getAs("allowChangeFieldNames", Set.class);
|
||||
Map<String, String> dictMap = map.getAs("dictMap", Map.class);
|
||||
|
||||
// 三级单位仅由高级管理页面传入,未传入时不参与比较,避免其他共用页面误判为清空。
|
||||
if (record.getThreeUnitId() != null) {
|
||||
allowChangeFieldNames.add("threeUnitId");
|
||||
dictMap.put("threeUnitId", "三级单位");
|
||||
}
|
||||
|
||||
// 新数据
|
||||
NutMap newMap = Lang.obj2nutmap(record);
|
||||
// 原数据
|
||||
|
||||
+8
@@ -11,6 +11,7 @@ import com.budwk.app.zhgh.welfare.model.WelfareList;
|
||||
import com.budwk.app.zhgh.welfare.model.WelfareUserSelection;
|
||||
import com.budwk.app.zhgh.welfare.param.WelfareSelectionSituationPageForm;
|
||||
import com.budwk.app.zhgh.welfare.service.WelfareListService;
|
||||
import com.budwk.app.zhgh.welfare.service.WelfareProjectService;
|
||||
import com.budwk.app.zhgh.welfare.service.WelfareSelectionSituationService;
|
||||
import com.budwk.app.zhgh.welfare.service.WelfareStatisticsService;
|
||||
import io.swagger.annotations.Api;
|
||||
@@ -47,6 +48,8 @@ public class WelfareSelectionSituationController {
|
||||
private WelfareStatisticsService welfareStatisticsService;
|
||||
@Inject
|
||||
private WelfareSelectionSituationService situationService;
|
||||
@Inject
|
||||
private WelfareProjectService welfareProjectService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/welfare/selectionSituation/index.html")
|
||||
@@ -86,6 +89,11 @@ public class WelfareSelectionSituationController {
|
||||
return Result.error("此用户没有选择的权限");
|
||||
}
|
||||
|
||||
String defaultOptionValidationMessage = welfareProjectService.validateSystemDefaultOptionSelection(projectId, selections);
|
||||
if (defaultOptionValidationMessage != null) {
|
||||
return Result.error(defaultOptionValidationMessage);
|
||||
}
|
||||
|
||||
//删除上次选择的
|
||||
dao.clear(WelfareUserSelection.class, Cnd.where(WelfareUserSelection::getWelfareId, "=", projectId).and(WelfareUserSelection::getSelectUserId, "=", userId));
|
||||
for (WelfareUserSelection welfareUserSelection : selections) {
|
||||
|
||||
@@ -89,6 +89,11 @@ public class WelfareUserSelectController {
|
||||
}
|
||||
WelfareProject project = dao.fetch(WelfareProject.class, projectId);
|
||||
|
||||
String defaultOptionValidationMessage = welfareProjectService.validateSystemDefaultOptionSelection(projectId, selections);
|
||||
if (defaultOptionValidationMessage != null) {
|
||||
return Result.error(defaultOptionValidationMessage);
|
||||
}
|
||||
|
||||
int sum = Arrays.stream(selections).mapToInt(selection -> Objects.requireNonNullElse(selection.getSelectNum(), 0)).sum();
|
||||
if (sum > project.getMultiSelectNum()) {
|
||||
return Result.error("选择的数量不能超过" + project.getMultiSelectNum());
|
||||
|
||||
@@ -37,8 +37,11 @@ public class WelfareFilterUserPageForm extends PageForm {
|
||||
@ApiModelProperty("工会名称")
|
||||
private String unionName;
|
||||
|
||||
@ApiModelProperty("单位id")
|
||||
private String[] unitIds;
|
||||
@ApiModelProperty("所属单位ID")
|
||||
private String unitId;
|
||||
|
||||
@ApiModelProperty("三级单位ID")
|
||||
private String threeUnitId;
|
||||
|
||||
@ApiModelProperty("单位名称")
|
||||
private String unitName;
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.budwk.app.zhgh.welfare.service;
|
||||
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.zhgh.welfare.model.WelfareProject;
|
||||
import com.budwk.app.zhgh.welfare.model.WelfareUserSelection;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -16,6 +17,15 @@ public interface WelfareProjectService extends BaseService<WelfareProject> {
|
||||
*/
|
||||
WelfareProject projectInfo(String projectId);
|
||||
|
||||
/**
|
||||
* 校验福利选择是否保留系统默认福利。
|
||||
*
|
||||
* @param projectId 福利项目 ID,用于查询该项目配置的系统默认福利
|
||||
* @param selections 用户提交的福利选择数组,每项需包含 selectOptionId 和大于 0 的 selectNum
|
||||
* @return 校验通过返回 {@code null};未保留系统默认福利时返回错误提示文本
|
||||
*/
|
||||
String validateSystemDefaultOptionSelection(String projectId, WelfareUserSelection[] selections);
|
||||
|
||||
/**
|
||||
* 保存项目信息
|
||||
* @param project
|
||||
|
||||
@@ -471,7 +471,8 @@ public class WelfareListServiceImpl extends BaseServiceImpl<WelfareList> impleme
|
||||
cnd.andEX("MONTH(u.birthday)", "in", pageForm.getBirthMonths());
|
||||
}
|
||||
cnd.andEX("u.unionId", "=", pageForm.getUnionId());
|
||||
cnd.andEX("u.unitId", "in", pageForm.getUnitIds());
|
||||
cnd.andEX("u.unitId", "=", pageForm.getUnitId());
|
||||
cnd.andEX("u.threeUnitId", "=", pageForm.getThreeUnitId());
|
||||
cnd.andEX("u.personType", "in", pageForm.getPersonTypes());
|
||||
cnd.andEX("u.userAttribute", "in", pageForm.getUserAttributes());
|
||||
cnd.andEX("u.preparedBy", "in", pageForm.getPreparedBys());
|
||||
@@ -524,7 +525,8 @@ public class WelfareListServiceImpl extends BaseServiceImpl<WelfareList> impleme
|
||||
cnd.andEX("MONTH(birthday)", "in", pageForm.getBirthMonths());
|
||||
}
|
||||
cnd.andEX("unionId", "=", pageForm.getUnionId());
|
||||
cnd.andEX("unitId", "in", pageForm.getUnitIds());
|
||||
cnd.andEX("unitId", "=", pageForm.getUnitId());
|
||||
cnd.andEX("threeUnitId", "=", pageForm.getThreeUnitId());
|
||||
cnd.andEX("personType", "in", pageForm.getPersonTypes());
|
||||
cnd.andEX("userAttribute", "in", pageForm.getUserAttributes());
|
||||
cnd.andEX("preparedBy", "in", pageForm.getPreparedBys());
|
||||
|
||||
@@ -49,6 +49,30 @@ public class WelfareProjectServiceImpl extends BaseServiceImpl<WelfareProject> i
|
||||
return project;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String validateSystemDefaultOptionSelection(String projectId, WelfareUserSelection[] selections) {
|
||||
List<WelfareProjectSubjectOption> systemDefaultOptions = dao().query(WelfareProjectSubjectOption.class,
|
||||
Cnd.where("welfareId", "=", projectId).and("isSystemDefault", "=", true));
|
||||
if (systemDefaultOptions.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Set<String> selectedOptionIds = Arrays.stream(selections == null ? new WelfareUserSelection[0] : selections)
|
||||
.filter(Objects::nonNull)
|
||||
.filter(selection -> selection.getSelectNum() != null && selection.getSelectNum() > 0)
|
||||
.map(WelfareUserSelection::getSelectOptionId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
List<String> missingOptionNames = systemDefaultOptions.stream()
|
||||
.filter(option -> !selectedOptionIds.contains(option.getId()))
|
||||
.map(WelfareProjectSubjectOption::getOptionName)
|
||||
.collect(Collectors.toList());
|
||||
if (missingOptionNames.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return "系统默认福利“" + String.join("、", missingOptionNames) + "”不可取消";
|
||||
}
|
||||
|
||||
private void fillOptionRank(String projectId, List<WelfareProjectSubjectOption> options) {
|
||||
if (options == null || options.isEmpty()) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user