diff --git a/src/main/java/com/budwk/app/MainLauncher.java b/src/main/java/com/budwk/app/MainLauncher.java index 07e198c..dd975e4 100644 --- a/src/main/java/com/budwk/app/MainLauncher.java +++ b/src/main/java/com/budwk/app/MainLauncher.java @@ -12,6 +12,7 @@ import com.budwk.app.web.commons.auth.satoken.StpInterfaceImpl; import com.budwk.app.web.commons.base.Globals; import com.budwk.app.web.commons.ext.pubsub.WebPubSub; import com.budwk.app.zhgh.dayofficework.message.service.GlobalMessageSendService; +import com.budwk.app.zhgh.staffbenefit.difficultSubsidy.service.impl.DifficultSubsidySchemaInitializer; import lombok.extern.slf4j.Slf4j; import org.beetl.core.GroupTemplate; import org.nutz.boot.NbApp; @@ -61,6 +62,8 @@ public class MainLauncher { private SysTaskService sysTaskService; @Inject private Dao dao; + @Inject + private DifficultSubsidySchemaInitializer difficultSubsidySchemaInitializer;//确保特困补助实体迁移随应用启动执行 public static void main(String[] args) throws Exception { NbApp nb = new NbApp().setArgs(args).setPrintProcDoc(true); diff --git a/src/main/java/com/budwk/app/flow/handler/FlowFghzxAssignmentHandler.java b/src/main/java/com/budwk/app/flow/handler/FlowFghzxAssignmentHandler.java index 6c789c8..3b2d9a2 100644 --- a/src/main/java/com/budwk/app/flow/handler/FlowFghzxAssignmentHandler.java +++ b/src/main/java/com/budwk/app/flow/handler/FlowFghzxAssignmentHandler.java @@ -19,25 +19,28 @@ import org.nutz.lang.Lang; import java.util.List; /** - * 获取申请人所在分工会的分工会主席 + * 获取申请人所在分工会的分工会主席和管理员 */ public class FlowFghzxAssignmentHandler implements AssignmentHandler { @Override public List assign(TaskModel model, Execution execution) { Dao dao = ServiceContext.find(Dao.class); SysRoleService sysRoleService = ServiceContext.find(SysRoleService.class); - Sys_role sysRole = sysRoleService.getByCode(RoleConstant.BRANCH_UNION_CHAIRMAN); + Sys_role chairmanRole = sysRoleService.getByCode(RoleConstant.BRANCH_UNION_CHAIRMAN); + Sys_role administratorRole = sysRoleService.getByCode(RoleConstant.BRANCH_UNION_ADMIN); String unionId = StrUtil.blankToDefault(execution.getArgs().getStr(FlowConst.INITIATOR_UNIT_UNION_ID), SecurityUtil.getUnionId()); - List user_roles = dao.query(Sys_user_role.class, Cnd.where(Sys_user_role::getRoleId, "=", sysRole.getId()).and(Sys_user_role::getUnionId, "=", unionId)); + // 同一分工会的主席和管理员均可处理该节点,重复配置的人员只返回一次。 + List user_roles = dao.query(Sys_user_role.class, Cnd.where(Sys_user_role::getRoleId, "in", List.of(chairmanRole.getId(), administratorRole.getId())) + .and(Sys_user_role::getUnionId, "=", unionId)); if (Lang.isEmpty(user_roles)) { - throw new BaseException("分工会主席没有设置,请联系校工会!", sysRole.getCode()); + throw new BaseException("提案人所属分工会的主席和管理员均未设置,请联系校工会!", chairmanRole.getCode()); } return user_roles.stream().map(Sys_user_role::getUserId).distinct().toList(); } @Override public String getMessage() { - return "当前用户所属分工会主席"; + return "提案人所属分工会的主席和管理员"; } @Override diff --git a/src/main/java/com/budwk/app/flow/service/FlowCommonService.java b/src/main/java/com/budwk/app/flow/service/FlowCommonService.java index 5cbb62e..b7aedc7 100644 --- a/src/main/java/com/budwk/app/flow/service/FlowCommonService.java +++ b/src/main/java/com/budwk/app/flow/service/FlowCommonService.java @@ -2,6 +2,7 @@ package com.budwk.app.flow.service; import cn.hutool.core.lang.Dict; import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; import com.budwk.app.base.event.flow.jumptofirsttask.FlowJumpToFirstTaskNodePublisher; import com.budwk.app.base.result.Result; import com.budwk.app.flow.constant.FlowConst; @@ -9,9 +10,11 @@ import com.budwk.app.flow.engine.FlowEngine; import com.budwk.app.flow.engine.event.ProcessEvent; import com.budwk.app.flow.engine.event.ProcessPublisher; import com.budwk.app.flow.entity.ProcessInstance; +import com.budwk.app.flow.entity.ProcessDefine; import com.budwk.app.flow.entity.ProcessTask; import com.budwk.app.flow.enums.*; import com.budwk.app.web.commons.auth.utils.SecurityUtil; +import com.budwk.app.sys.models.Sys_user_signature; import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.dao.Chain; import org.nutz.dao.Cnd; @@ -22,10 +25,17 @@ import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.json.Json; import java.util.List; +import java.util.Set; @IocBean public class FlowCommonService { + /** 教代会提案中从二级工会审核开始,可留存当前办理人的电子签名快照。 */ + private static final Set PROPOSAL_SIGNATURE_TASK_NAMES = Set.of( + "secondaryUnionAudit", "partyOrganizationAudit", "committee", "committeeFilingUnit", + "unit_reply", "schoolLeader", "feedback" + ); + @Inject private FlowEngine flowEngine; @@ -39,6 +49,9 @@ public class FlowCommonService { public void executeTask(Dict args){ Long processTaskId = args.getLong(FlowConst.PROCESS_TASK_ID_KEY); + ProcessTask processTask = flowEngine.processTaskService().getById(processTaskId); + appendProposalSignature(processTask, args); + String operator = SecurityUtil.getUserId(); if (args.containsKey(FlowConst.EXT_ENABLE_OPERATOR)&&args.getBool(FlowConst.EXT_ENABLE_OPERATOR)){ operator = FlowConst.ADMIN_ID; @@ -49,7 +62,6 @@ public class FlowCommonService { submitType = ProcessSubmitTypeEnum.AGREE.getCode(); } - ProcessTask processTask = flowEngine.processTaskService().getById(processTaskId); ProcessTask parentTask = flowEngine.processTaskService().getById(processTask.getTaskParentId()); if(parentTask != null) { Dict taskArgs = Json.fromJson(Dict.class, parentTask.getVariable()); @@ -98,6 +110,34 @@ public class FlowCommonService { } } + /** + * 为教代会提案审核任务保存签字 URL 快照。 + * 仅处理 JDHTA_NC 流程,避免电子签名快照影响系统其他工作流;用户已维护签名时才归档, + * 未维护签名也允许正常提交。快照随任务变量归档,后续用户更新个人签名不会改变已办任务的导出结果。 + * + * @param processTask 当前待办任务 + * @param args 即将写入任务变量的表单参数 + */ + private void appendProposalSignature(ProcessTask processTask, Dict args) { + if (processTask == null || !PROPOSAL_SIGNATURE_TASK_NAMES.contains(processTask.getTaskName())) { + return; + } + ProcessInstance processInstance = dao.fetch(ProcessInstance.class, processTask.getProcessInstanceId()); + if (processInstance == null) { + return; + } + ProcessDefine processDefine = dao.fetch(ProcessDefine.class, processInstance.getProcessDefineId()); + if (processDefine == null || !"JDHTA_NC".equals(processDefine.getName())) { + return; + } + Sys_user_signature userSignature = dao.fetch(Sys_user_signature.class, + Cnd.where(Sys_user_signature::getUserId, "=", SecurityUtil.getUserId())); + if (userSignature == null || StrUtil.isBlank(userSignature.getSignature())) { + return; + } + args.put(FlowConst.TASK_FORM_DATA_PREFIX + "signature", userSignature.getSignature()); + } + @Aop(TransAop.READ_COMMITTED) public Result revokeTask(Long taskId) { // 自己任务 diff --git a/src/main/java/com/budwk/app/flow/service/impl/ProcessTaskServiceImpl.java b/src/main/java/com/budwk/app/flow/service/impl/ProcessTaskServiceImpl.java index 7eb7380..429de6a 100644 --- a/src/main/java/com/budwk/app/flow/service/impl/ProcessTaskServiceImpl.java +++ b/src/main/java/com/budwk/app/flow/service/impl/ProcessTaskServiceImpl.java @@ -120,6 +120,8 @@ public class ProcessTaskServiceImpl extends BaseServiceImpl impleme public List getDoneTaskList(Long processInstanceId, String[] taskNames) { Cnd cnd = Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId).and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.FINISHED.getCode()); cnd.andEX(ProcessTask::getTaskName, "", taskNames); + // 已办记录必须按实际办结时间返回,避免补迁历史任务因写入时间较晚而排在流程末尾。 + cnd.asc(ProcessTask::getFinishTime).asc(ProcessTask::getId); List processTaskList = query(cnd); return processTaskList; } diff --git a/src/main/java/com/budwk/app/sys/controller/SysSignatureController.java b/src/main/java/com/budwk/app/sys/controller/SysSignatureController.java index adbcab6..f4e6978 100644 --- a/src/main/java/com/budwk/app/sys/controller/SysSignatureController.java +++ b/src/main/java/com/budwk/app/sys/controller/SysSignatureController.java @@ -131,7 +131,9 @@ public class SysSignatureController { @Aop(TransAop.READ_COMMITTED) public Result update(@Param("file") TempFile tempFile) { String url = sysFileService.uploadReturnUrl(SysFileEngineTypeEnum.MINIO.getValue(), tempFile); - Sys_user_signature sysUserSignature = dao.fetch(Sys_user_signature.class); + // 每位用户维护独立的电子签名,不能取任意一条记录后覆盖其归属用户。 + Sys_user_signature sysUserSignature = dao.fetch(Sys_user_signature.class, + Cnd.where(Sys_user_signature::getUserId, "=", SecurityUtil.getUserId())); if (ObjectUtil.isNull(sysUserSignature)) { sysUserSignature = new Sys_user_signature(); } diff --git a/src/main/java/com/budwk/app/sys/controller/SysUnionController.java b/src/main/java/com/budwk/app/sys/controller/SysUnionController.java index 403c0e1..0ebc302 100644 --- a/src/main/java/com/budwk/app/sys/controller/SysUnionController.java +++ b/src/main/java/com/budwk/app/sys/controller/SysUnionController.java @@ -259,11 +259,10 @@ public class SysUnionController { @At @SaCheckPermission("sys.manager.union") public Result branchUnionUserPageData(PageForm pageForm, String unionId, @Param("j") String j) { - List branchUnionRoles = sysDictService.getSubListByCode("BRANCH_UNION_ROLES"); - if (Lang.isEmpty(branchUnionRoles)) { + List branchUnionRoleCodes = getBranchUnionRoleCodes(); + if (Lang.isEmpty(branchUnionRoleCodes)) { return Result.success(); } - List branchUnionRoleCodes = branchUnionRoles.stream().map(Sys_dict::getCode).toList(); Sql sql = Sqls.create(""" SELECT @@ -334,11 +333,10 @@ public class SysUnionController { @At @SaCheckPermission("sys.manager.union") public Result branchUnionUserUsedJData(String unionId) { - List branchUnionRoles = sysDictService.getSubListByCode("BRANCH_UNION_ROLES"); - if (Lang.isEmpty(branchUnionRoles)) { + List branchUnionRoleCodes = getBranchUnionRoleCodes(); + if (Lang.isEmpty(branchUnionRoleCodes)) { return Result.success(List.of()); } - List branchUnionRoleCodes = branchUnionRoles.stream().map(Sys_dict::getCode).toList(); Sql sql = Sqls.create(""" SELECT DISTINCT @@ -371,16 +369,46 @@ public class SysUnionController { return Result.success(usedJCodes); } + /** + * 返回分工会干部页面可维护的角色,审核角色不依赖字典重复配置, + * 直接使用系统角色编码,避免单位党委书记在多个字典目录中出现同码数据。 + */ + @At + @SaCheckPermission("sys.manager.union") + @ApiOperation("查询分工会干部可选角色") + public Result branchUnionRoleOptions() { + List options = getBranchUnionRoleCodes().stream() + .map(sysRoleService::getByCode) + .filter(role -> role != null) + .map(role -> NutMap.NEW().addv("code", role.getCode()).addv("name", role.getName())) + .toList(); + return Result.success(options); + } + + /** + * 汇总分工会干部和提案审核所需角色,后续列表、历史届次和新增校验统一使用同一范围。 + */ + private List getBranchUnionRoleCodes() { + List roleCodes = new ArrayList<>(sysDictService.getSubListByCode("BRANCH_UNION_ROLES") + .stream().map(Sys_dict::getCode).toList()); + roleCodes.add(RoleConstant.BRANCH_UNION_ADMIN.name()); + roleCodes.add(RoleConstant.UNIT_PARTY_SECRETARY.name()); + return roleCodes.stream().filter(StrUtil::isNotBlank).distinct().toList(); + } + @At @SaCheckPermission("sys.manager.union.branchOfficer") @ApiOperation("添加分工会人员角色") @Aop(TransAop.READ_COMMITTED) public Result insertBranchUnionUserRole(String userId, String roleCode, String unionId, String j) { + if (!getBranchUnionRoleCodes().contains(roleCode)) { + return Result.error("该角色不允许在分工会干部中维护"); + } Sys_role role = sysRoleService.fetch(Cnd.where("code", "=", roleCode)); if (Lang.isEmpty(role)) { - return Result.success("无法找到" + roleCode + "对应编码的角色"); + return Result.error("无法找到" + roleCode + "对应编码的角色"); } - int count = dao.count(Sys_user_role.class, Cnd.where("roleId", "=", role.getId()).and("userId", "=", userId)); + int count = dao.count(Sys_user_role.class, Cnd.where("roleId", "=", role.getId()).and("userId", "=", userId).and("unionId", "=", unionId)); if (count > 0) { return Result.error("请勿重复添加"); } @@ -404,6 +432,9 @@ public class SysUnionController { // 构建表结构存储 View_user user = dao.fetch(View_user.class, Cnd.where("id", "=", userId)); + if (user == null) { + return Result.error("未找到人员信息"); + } Sys_union_cadre unionCadre = new Sys_union_cadre(); unionCadre.setUserId(userId); diff --git a/src/main/java/com/budwk/app/sys/controller/SysUnitController.java b/src/main/java/com/budwk/app/sys/controller/SysUnitController.java index 1f862dc..b833c61 100644 --- a/src/main/java/com/budwk/app/sys/controller/SysUnitController.java +++ b/src/main/java/com/budwk/app/sys/controller/SysUnitController.java @@ -50,6 +50,8 @@ import javax.validation.Valid; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; ; @@ -305,6 +307,7 @@ public class SysUnitController { Cnd cnd = Cnd.NEW(); cnd.asc("unitcode"); List list = sysUnitService.query(cnd); + Set unitIds = list.stream().map(Sys_unit::getId).collect(Collectors.toSet()); List> nodeList = CollUtil.newArrayList(); for (int i = 0; i < list.size(); i++) { @@ -312,8 +315,12 @@ public class SysUnitController { /* * 单位根节点存在 id 与 parentId 都为 0 的自引用数据。 * 构建树时把当前查询根挂到虚拟根下,避免“中国地质大学”和 parentId=0 的学院被构造成同级。 + * 同时兼容迁移数据中根节点 parentId 为空或父级不存在的情况,确保这些节点可被树组件展示。 */ - String parentId = rootId.equals(unit.getId()) ? virtualRootId : unit.getParentId(); + String parentId = unit.getParentId(); + if (rootId.equals(unit.getId()) || StrUtil.isBlank(parentId) || !unitIds.contains(parentId)) { + parentId = virtualRootId; + } nodeList.add(new TreeNode<>(unit.getId(), parentId, unit.getName(), i) .setExtra( Map.of( diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseMineController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseMineController.java index da4e2f0..7765ebf 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseMineController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseMineController.java @@ -420,7 +420,7 @@ public class UnionReimburseMineController { @At private void exportAsPDF(String templateName, HashMap docData, UnionReimburse unionReimburse, HttpServletResponse response) throws Exception { - String fileName = "中国地质大学(武汉)工会经费日常报销_" + unionReimburse.getUserName() + "_" + + String fileName = "西南财经大学工会经费日常报销_" + unionReimburse.getUserName() + "_" + DateUtil.format(unionReimburse.getCreateTime(), "yyyyMMdd") + ".pdf"; try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { @@ -446,7 +446,7 @@ public class UnionReimburseMineController { @At private void exportAsWord(String templateName, HashMap docData, UnionReimburse unionReimburse, HttpServletResponse response) throws Exception { - String fileName = "中国地质大学(武汉)工会经费日常报销_" + unionReimburse.getUserName() + "_" + + String fileName = "西南财经大学工会经费日常报销_" + unionReimburse.getUserName() + "_" + DateUtil.format(unionReimburse.getCreateTime(), "yyyyMMdd") + ".docx"; try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/export/ProposalExportComprehensiveController.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/export/ProposalExportComprehensiveController.java index fb37eef..e70729a 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/export/ProposalExportComprehensiveController.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/export/ProposalExportComprehensiveController.java @@ -148,6 +148,14 @@ public class ProposalExportComprehensiveController { proposalExportService.exportProposalStatisticsAsDocx(pageForm, response); } + @At + @Ok("void") + @ApiOperation("按综合导出模板导出单条提案") + @SaCheckPermission("proposal.query.comprehensive") + public void exportProposalAsDocx(@Valid String id, HttpServletResponse response) { + proposalExportService.exportProposalAsDocx(id, response); + } + @At @Ok("void") @ApiOperation("导出全部提案反馈表ZIP") diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalCommitteeFilingController.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalCommitteeFilingController.java index f3f487a..e7884b5 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalCommitteeFilingController.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalCommitteeFilingController.java @@ -52,6 +52,16 @@ public class ProposalCommitteeFilingController { public void index() { } + /** + * 提案委员会立案 H5 办理页。 + * H5 权限与 PC 权限独立配置,避免手机端通过 PC 菜单权限访问审核页面。 + */ + @At("/h5") + @Ok("beetl:/platform/zhghh5/democratic/proposal/transact/committeeFiling/index.html") + @SaCheckPermission("h5.proposal.committeeFiling") + public void h5Index() { + } + @At @SaCheckPermission("proposal.committeeFiling") @ApiOperation("分页列表") @@ -116,6 +126,17 @@ public class ProposalCommitteeFilingController { return Result.success(pagination); } + /** + * 提案委员会立案 H5 列表。 + * 列表数据与 PC 保持同一任务、参与人和状态口径,仅切换为 H5 菜单权限。 + */ + @At("/h5/pageData") + @SaCheckPermission("h5.proposal.committeeFiling") + @ApiOperation("提案委员会立案 H5 分页列表") + public Result h5PageData(@Valid ProposalSearchParam pageForm, boolean approval) { + return pageData(pageForm, approval); + } + @At @Ok("void") @SaCheckPermission("proposal.committeeFiling") diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalCommitteeFilingUnitController.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalCommitteeFilingUnitController.java index 38d4af9..f57221d 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalCommitteeFilingUnitController.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalCommitteeFilingUnitController.java @@ -65,6 +65,16 @@ public class ProposalCommitteeFilingUnitController { public void index() { } + /** + * 提案委员会确认承办单位 H5 办理页。 + * H5 权限与 PC 权限独立配置,避免手机端通过 PC 菜单权限访问审核页面。 + */ + @At("/h5") + @Ok("beetl:/platform/zhghh5/democratic/proposal/transact/committeeFilingUnit/index.html") + @SaCheckPermission("h5.proposal.committeeFilingUnit") + public void h5Index() { + } + @At @SaCheckPermission("proposal.committeeFilingUnit") @ApiOperation("分页列表") @@ -129,6 +139,17 @@ public class ProposalCommitteeFilingUnitController { return Result.success(pagination); } + /** + * 提案委员会确认承办单位 H5 列表。 + * 列表数据与 PC 保持同一任务、参与人和状态口径,仅切换为 H5 菜单权限。 + */ + @At("/h5/pageData") + @SaCheckPermission("h5.proposal.committeeFilingUnit") + @ApiOperation("提案委员会确认承办单位 H5 分页列表") + public Result h5PageData(@Valid ProposalSearchParam pageForm, boolean approval) { + return pageData(pageForm, approval); + } + @At @Ok("void") @SaCheckPermission("proposal.committeeFilingUnit") @@ -148,6 +169,20 @@ public class ProposalCommitteeFilingUnitController { return Result.success(); } + /** + * 确认承办单位 H5 提交。 + * 复用原有服务,保证并案提案、承办单位和立案结果的同步规则与 PC 一致。 + */ + @At("/h5/executeTask") + @SaCheckPermission("h5.proposal.committeeFilingUnit") + @ApiOperation("提案委员会确认承办单位 H5 执行任务") + @Aop(TransAop.READ_COMMITTED) + public Result h5ExecuteTask(@Param("data") String data) { + Dict args = Json.fromJson(Dict.class, data); + proposalCommitteeFilingUnitService.executeFlowTask(args); + return Result.success(); + } + @At @SaCheckLogin diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalMineController.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalMineController.java index fa81cbb..1a31ff3 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalMineController.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalMineController.java @@ -26,6 +26,7 @@ import com.budwk.app.zhgh.democratic.proposal.models.ProposalConfig; import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo; import com.budwk.app.zhgh.democratic.proposal.models.ProposalSecond; import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam; +import com.budwk.app.zhgh.democratic.proposal.service.ProposalMessageService; import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -94,6 +95,8 @@ public class ProposalMineController { private ProcessTaskService processTaskService; @Inject private FlowCommonService flowCommonService; + @Inject + private ProposalMessageService proposalMessageService; @At("") @Ok("beetl:/platform/zhgh/democratic/proposal/transact/mine/index.html") @@ -384,6 +387,8 @@ public class ProposalMineController { return seconded; }).toList(); dao.insert(secondeds); + // V3 在邀请附议人保存成功后逐个发送业务短信;实际下发仍由 SmsService 配置开关控制。 + proposalMessageService.sendInviteSeconderMessage(proposalId, userIds); return Result.success(); } diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalPartyOrganizationAuditController.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalPartyOrganizationAuditController.java new file mode 100644 index 0000000..7b84112 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalPartyOrganizationAuditController.java @@ -0,0 +1,65 @@ +package com.budwk.app.zhgh.democratic.proposal.controller.transact; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.budwk.app.base.param.ExportTableColumns; +import com.budwk.app.base.result.Result; +import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam; +import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; + +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; + +/** + * 提案院级党组织审核。 + */ +@IocBean +@At("/platform/proposal/partyOrganizationAudit") +@Ok("json:full") +@Api(tags = "提案办理-院级党组织审核") +public class ProposalPartyOrganizationAuditController { + + @Inject + private ProposalCommonService proposalCommonService; + + @At("") + @Ok("beetl:/platform/zhgh/democratic/proposal/transact/delegation/index.html") + @SaCheckPermission("proposal.partyOrganizationAudit") + public void index() { + } + + @At("/h5") + @Ok("beetl:/platform/zhghh5/democratic/proposal/transact/delegation/index.html") + @SaCheckPermission("h5.proposal.partyOrganizationAudit") + public void h5Index() { + } + + @At + @SaCheckPermission("proposal.partyOrganizationAudit") + @ApiOperation("院级党组织审核分页列表") + public Result pageData(@Valid ProposalSearchParam pageForm, boolean approval) { + return Result.success(proposalCommonService.getWorkflowAuditPage("partyOrganizationAudit", pageForm, approval)); + } + + @At("/h5/pageData") + @SaCheckPermission("h5.proposal.partyOrganizationAudit") + @ApiOperation("院级党组织审核手机端分页列表") + public Result h5PageData(@Valid ProposalSearchParam pageForm, boolean approval) { + return Result.success(proposalCommonService.getWorkflowAuditPage("partyOrganizationAudit", pageForm, approval)); + } + + @At + @Ok("void") + @SaCheckPermission("proposal.partyOrganizationAudit") + @ApiOperation("导出院级党组织审核列表") + public void exportList(@Valid @Param("pageForm") ProposalSearchParam pageForm, boolean approval, + @Param("tableColumns") ExportTableColumns[] tableColumns, HttpServletResponse response) { + proposalCommonService.exportWorkflowList("partyOrganizationAudit", pageForm, approval, tableColumns, response); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalSecondaryUnionAuditController.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalSecondaryUnionAuditController.java new file mode 100644 index 0000000..3e54387 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalSecondaryUnionAuditController.java @@ -0,0 +1,65 @@ +package com.budwk.app.zhgh.democratic.proposal.controller.transact; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.budwk.app.base.param.ExportTableColumns; +import com.budwk.app.base.result.Result; +import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam; +import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; + +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; + +/** + * 提案二级工会审核。 + */ +@IocBean +@At("/platform/proposal/secondaryUnionAudit") +@Ok("json:full") +@Api(tags = "提案办理-二级工会审核") +public class ProposalSecondaryUnionAuditController { + + @Inject + private ProposalCommonService proposalCommonService; + + @At("") + @Ok("beetl:/platform/zhgh/democratic/proposal/transact/delegation/index.html") + @SaCheckPermission("proposal.secondaryUnionAudit") + public void index() { + } + + @At("/h5") + @Ok("beetl:/platform/zhghh5/democratic/proposal/transact/delegation/index.html") + @SaCheckPermission("h5.proposal.secondaryUnionAudit") + public void h5Index() { + } + + @At + @SaCheckPermission("proposal.secondaryUnionAudit") + @ApiOperation("二级工会审核分页列表") + public Result pageData(@Valid ProposalSearchParam pageForm, boolean approval) { + return Result.success(proposalCommonService.getWorkflowAuditPage("secondaryUnionAudit", pageForm, approval)); + } + + @At("/h5/pageData") + @SaCheckPermission("h5.proposal.secondaryUnionAudit") + @ApiOperation("二级工会审核手机端分页列表") + public Result h5PageData(@Valid ProposalSearchParam pageForm, boolean approval) { + return Result.success(proposalCommonService.getWorkflowAuditPage("secondaryUnionAudit", pageForm, approval)); + } + + @At + @Ok("void") + @SaCheckPermission("proposal.secondaryUnionAudit") + @ApiOperation("导出二级工会审核列表") + public void exportList(@Valid @Param("pageForm") ProposalSearchParam pageForm, boolean approval, + @Param("tableColumns") ExportTableColumns[] tableColumns, HttpServletResponse response) { + proposalCommonService.exportWorkflowList("secondaryUnionAudit", pageForm, approval, tableColumns, response); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalSecondedController.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalSecondedController.java index b54cb81..df72f0d 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalSecondedController.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalSecondedController.java @@ -198,19 +198,9 @@ public class ProposalSecondedController { } // 更新 proposal_second,记录当前附议人的最终附议结果、附议意见和附议时间。 - ProposalSecond proposalSecond = dao.fetch( - ProposalSecond.class, - Cnd.where(ProposalSecond::getProposalId, "=", proposalId) - .and(ProposalSecond::getSeconderId, "=", taskActorUserId) - ); - if (proposalSecond == null) { + if (!proposalSecondedService.updateSecondRecord(proposalId, taskActorUserId, opinion, submitType)) { return Result.error("未找到附议记录"); } - proposalSecond.setIsAgree(ObjectUtil.equals(submitType, ProcessSubmitTypeEnum.AGREE.getCode())); - proposalSecond.setOpinion(opinion); - proposalSecond.setIsAgree(submitType == 1); - proposalSecond.setSecondedTime(DateUtil.date()); - dao.update(proposalSecond); // 仅更新本次附议对应的 wf_process_task,不推动实例、也不创建后续任务。 View_user user = dao.fetch(View_user.class, Cnd.where(View_user::getId, "=", taskActorUserId)); @@ -269,7 +259,9 @@ public class ProposalSecondedController { return Result.error("参数错误"); } flowCommonService.executeTask(args); - proposalSecondedService.updateSecondRecord(proposalId, taskActorUserId, opinion, submitType); + if (!proposalSecondedService.updateSecondRecord(proposalId, taskActorUserId, opinion, submitType)) { + return Result.error("未找到附议记录"); + } return Result.success(); } diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/handler/ProposalPartyOrganizationAssignmentHandler.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/handler/ProposalPartyOrganizationAssignmentHandler.java new file mode 100644 index 0000000..67133fc --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/handler/ProposalPartyOrganizationAssignmentHandler.java @@ -0,0 +1,43 @@ +package com.budwk.app.zhgh.democratic.proposal.handler; + +import com.budwk.app.base.constant.RoleConstant; +import com.budwk.app.base.exception.BaseException; +import com.budwk.app.flow.constant.FlowConst; +import com.budwk.app.flow.engine.AssignmentHandler; +import com.budwk.app.flow.engine.core.Execution; +import com.budwk.app.flow.engine.core.ServiceContext; +import com.budwk.app.flow.engine.model.TaskModel; +import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo; +import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService; +import org.nutz.json.Json; +import org.nutz.lang.util.NutMap; + +import java.util.List; + +/** + * 提案院级党组织审核参与人。 + */ +public class ProposalPartyOrganizationAssignmentHandler implements AssignmentHandler { + + @Override + public List assign(TaskModel model, Execution execution) { + NutMap variable = Json.fromJson(NutMap.class, execution.getProcessInstance().getVariable()); + ProposalInfo proposalInfo = variable.getAs(FlowConst.FORM_DATA, ProposalInfo.class); + List userIds = ServiceContext.find(ProposalCommonService.class).getProposalUnionRoleUserIds(proposalInfo, + RoleConstant.UNIT_PARTY_SECRETARY.name()); + if (userIds.isEmpty()) { + throw new BaseException("提案人所属分工会未设置党委书记,无法流转到院级党组织审核"); + } + return userIds; + } + + @Override + public String getMessage() { + return "提案人所属分工会党委书记"; + } + + @Override + public int getOrder() { + return 240; + } +} diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/handler/ProposalSecondaryUnionAssignmentHandler.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/handler/ProposalSecondaryUnionAssignmentHandler.java new file mode 100644 index 0000000..f837c34 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/handler/ProposalSecondaryUnionAssignmentHandler.java @@ -0,0 +1,43 @@ +package com.budwk.app.zhgh.democratic.proposal.handler; + +import com.budwk.app.base.constant.RoleConstant; +import com.budwk.app.base.exception.BaseException; +import com.budwk.app.flow.constant.FlowConst; +import com.budwk.app.flow.engine.AssignmentHandler; +import com.budwk.app.flow.engine.core.Execution; +import com.budwk.app.flow.engine.core.ServiceContext; +import com.budwk.app.flow.engine.model.TaskModel; +import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo; +import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService; +import org.nutz.json.Json; +import org.nutz.lang.util.NutMap; + +import java.util.List; + +/** + * 提案二级工会审核参与人。 + */ +public class ProposalSecondaryUnionAssignmentHandler implements AssignmentHandler { + + @Override + public List assign(TaskModel model, Execution execution) { + NutMap variable = Json.fromJson(NutMap.class, execution.getProcessInstance().getVariable()); + ProposalInfo proposalInfo = variable.getAs(FlowConst.FORM_DATA, ProposalInfo.class); + List userIds = ServiceContext.find(ProposalCommonService.class).getProposalUnionRoleUserIds(proposalInfo, + RoleConstant.BRANCH_UNION_CHAIRMAN.name(), RoleConstant.BRANCH_UNION_VICE_CHAIRMAN.name()); + if (userIds.isEmpty()) { + throw new BaseException("提案人所属分工会未设置主席或副主席,无法流转到二级工会审核"); + } + return userIds; + } + + @Override + public String getMessage() { + return "提案人所属分工会主席或副主席"; + } + + @Override + public int getOrder() { + return 230; + } +} diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/handler/ProposalWyhAssignmentHandler.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/handler/ProposalWyhAssignmentHandler.java index e68a2da..00eebfb 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/handler/ProposalWyhAssignmentHandler.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/handler/ProposalWyhAssignmentHandler.java @@ -18,7 +18,8 @@ import org.nutz.lang.util.NutMap; import java.util.List; /** - * 提案委员会主任 + * 提案委员会办理人。 + * 优先由届次内配置的提案委员会主任办理;历史届次未配置主任时,由副主任兜底办理。 */ public class ProposalWyhAssignmentHandler implements AssignmentHandler { @Override @@ -29,19 +30,26 @@ public class ProposalWyhAssignmentHandler implements AssignmentHandler { String sessionId = info.getSessionId(); SysRoleService sysRoleService = ServiceContext.find(SysRoleService.class); - Sys_role role = sysRoleService.getByCode(RoleConstant.PROPOSAL_COMMITTEE_DIRECTOR); + Sys_role directorRole = sysRoleService.getByCode(RoleConstant.PROPOSAL_COMMITTEE_DIRECTOR); - Sys_user_role user_role = ServiceContext.find(Dao.class).fetch(Sys_user_role.class, Cnd.where(Sys_user_role::getRoleId, "=", role.getId()).and(Sys_user_role::getTcSessionId, "=", sessionId)); - - if (user_role == null) { - throw new RuntimeException("没有设置提案委员会主任,无法流转到下一步,请联系校工会进行设置。"); + Sys_user_role userRole = ServiceContext.find(Dao.class).fetch(Sys_user_role.class, + Cnd.where(Sys_user_role::getRoleId, "=", directorRole.getId()).and(Sys_user_role::getTcSessionId, "=", sessionId)); + if (userRole == null) { + Sys_role deputyDirectorRole = sysRoleService.getByCode(RoleConstant.PROPOSAL_COMMITTEE_DEPUTY_DIRECTOR); + // 历史届次可能仅配置副主任,确保迁入提案可继续完成立案和承办单位确认。 + userRole = ServiceContext.find(Dao.class).fetch(Sys_user_role.class, + Cnd.where(Sys_user_role::getRoleId, "=", deputyDirectorRole.getId()).and(Sys_user_role::getTcSessionId, "=", sessionId)); } - return List.of(user_role.getUserId()); + + if (userRole == null) { + throw new RuntimeException("没有设置提案委员会主任或副主任,无法流转到下一步,请联系校工会进行设置。"); + } + return List.of(userRole.getUserId()); } @Override public String getMessage() { - return "教代会提案委员会主任"; + return "教代会提案委员会主任(未配置时由副主任办理)"; } @Override diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/ProposalExportService.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/ProposalExportService.java index 0015ea1..94ffb61 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/ProposalExportService.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/ProposalExportService.java @@ -66,6 +66,23 @@ public interface ProposalExportService extends BaseService { */ void exportProposalStatisticsAsDocx(ProposalQueryComprehensiveParam pageForm, HttpServletResponse response); + /** + * 按综合导出专用提案模板导出单条提案。 + * 该模板保留提案人及审核环节的电子签名,不影响其他页面使用的通用提案导出模板。 + * + * @param proposalId 提案ID + * @param response HTTP 响应 + */ + void exportProposalAsDocx(String proposalId, HttpServletResponse response); + + /** + * 按综合导出专用提案模板写入单条 DOCX,用于 ZIP 与汇集文档导出。 + * + * @param proposalId 提案ID + * @param outputStream DOCX 输出流 + */ + void exportProposalAsDocx(String proposalId, ByteArrayOutputStream outputStream); + /** * 导出反馈表 * diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/ProposalMessageService.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/ProposalMessageService.java new file mode 100644 index 0000000..da8a4b0 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/ProposalMessageService.java @@ -0,0 +1,26 @@ +package com.budwk.app.zhgh.democratic.proposal.service; + +import java.util.List; + +/** + * 提案业务短信服务。 + * + * 短信是否实际下发由统一的 SmsService 配置开关控制,本服务仅负责还原提案业务的收件人和文案。 + */ +public interface ProposalMessageService { + + /** + * 向本次新增的附议人发送邀请附议短信。 + * + * @param proposalId 提案ID + * @param seconderUserIds 本次新增附议人的用户ID列表 + */ + void sendInviteSeconderMessage(String proposalId, List seconderUserIds); + + /** + * 向提案人所属分工会的主席、副主席发送附议人数达标短信。 + * + * @param proposalId 提案ID + */ + void sendSecondaryUnionMessage(String proposalId); +} diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/ProposalSecondedService.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/ProposalSecondedService.java index fcef3ce..ac45129 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/ProposalSecondedService.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/ProposalSecondedService.java @@ -13,5 +13,5 @@ public interface ProposalSecondedService extends BaseService { * 3. opinion:附议意见,写入附议记录的 opinion 字段。 * 4. submitType:附议结果,1 表示同意,20 表示不同意。 */ - void updateSecondRecord(String proposalId, String taskActorUserId, String opinion, Integer submitType); + boolean updateSecondRecord(String proposalId, String taskActorUserId, String opinion, Integer submitType); } diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/common/ProposalCommonService.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/common/ProposalCommonService.java index 207a0ad..dd69cca 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/common/ProposalCommonService.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/common/ProposalCommonService.java @@ -1,5 +1,6 @@ package com.budwk.app.zhgh.democratic.proposal.service.common; +import com.budwk.app.base.page.Pagination; import com.budwk.app.base.service.BaseService; import com.budwk.app.base.param.PageForm; import com.budwk.app.base.param.ExportTableColumns; @@ -37,6 +38,25 @@ public interface ProposalCommonService extends BaseService { */ boolean applySafeProposalListOrder(Cnd cnd, PageForm pageForm, String pageCode); + /** + * 查询提案流程审核页面的数据,确保页面任务范围、参与人范围和排序规则保持一致。 + * + * @param pageCode 审核页面代码 + * @param pageForm 页面筛选条件 + * @param approval 是否查询已审核记录 + * @return 审核任务分页数据 + */ + Pagination getWorkflowAuditPage(String pageCode, ProposalSearchParam pageForm, boolean approval); + + /** + * 根据提案发起人所属分工会,查询指定角色的有效用户。 + * + * @param proposalInfo 提案信息 + * @param roleCodes 参与人角色 + * @return 可参与流程的用户 ID + */ + List getProposalUnionRoleUserIds(ProposalInfo proposalInfo, String... roleCodes); + /** * 按白名单字段对已汇总的统计结果进行后端排序。 * diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/common/ProposalCommonServiceImpl.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/common/ProposalCommonServiceImpl.java index 4e2188d..f079185 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/common/ProposalCommonServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/common/ProposalCommonServiceImpl.java @@ -13,6 +13,7 @@ import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity; import com.budwk.app.base.constant.RoleConstant; import com.budwk.app.base.exception.BaseException; +import com.budwk.app.base.page.Pagination; import com.budwk.app.base.param.ExportTableColumns; import com.budwk.app.base.param.PageForm; import com.budwk.app.base.service.impl.BaseServiceImpl; @@ -34,6 +35,7 @@ import com.budwk.app.sys.models.Sys_user_role; import com.budwk.app.sys.services.SysDictService; import com.budwk.app.sys.services.SysRoleService; import com.budwk.app.sys.services.SysUserService; +import com.budwk.app.sys.views.View_user; import com.budwk.app.web.commons.auth.utils.AuthUtil; import com.budwk.app.web.commons.auth.utils.SecurityUtil; import com.budwk.app.web.commons.base.Globals; @@ -147,6 +149,114 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl imp return applySafePageOrder(cnd, pageForm, PROPOSAL_LIST_ORDER_COLUMNS.get(pageCode)); } + /** + * 查询二级工会、院级党组织审核页面的任务数据。页面代码由控制器固定传入, + * 防止请求参数篡改任务节点范围。 + */ + @Override + public Pagination getWorkflowAuditPage(String pageCode, ProposalSearchParam pageForm, boolean approval) { + if (!Set.of("secondaryUnionAudit", "partyOrganizationAudit").contains(pageCode)) { + throw new BaseException("不支持的提案审核页面"); + } + + Sql sql = Sqls.create(""" + SELECT + info.*, + type.name AS typeName, + tcs.fullName AS sessionName, + tcd.`name` AS delegationName, + ins.id AS instanceId, + ins.businessNo, + ins.state AS instanceState, + ins.variable AS instanceVariable, + ins.processDefineId AS instanceProcessDefineId, + t.id AS taskId, + t.taskName AS taskKey, + t.displayName AS taskName, + t.taskType, + t.performType AS taskPerformType, + t.taskState, + t.finishTime, + t.taskParentId, + t.variable AS taskVariable, + IFNULL(GROUP_CONCAT(DISTINCT nt.displayName), '结束') AS curTaskName, + IF(rt.id IS NOT NULL, 1, 0) AS canRevoke + FROM wf_process_task t + LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId + LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id + LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10 + LEFT JOIN wf_process_task rt ON rt.processInstanceId = ins.id AND rt.taskParentId = t.id AND rt.taskState = 10 + LEFT JOIN proposal_info info ON info.id = ins.businessNo + LEFT JOIN proposal_type type ON type.id = info.typeId + LEFT JOIN teacher_congress_session tcs ON tcs.id = info.sessionId + LEFT JOIN teacher_congress_delegation tcd ON tcd.id = info.delegationId + $condition + """); + Cnd cnd = Cnd.NEW(); + cnd.and("t.taskName", "=", getWorkflowTaskName(pageCode)); + if (!AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) { + cnd.and("ta.actorId", "=", SecurityUtil.getUserId()); + } + if (approval) { + cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode())); + } else { + cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode()); + } + ProposalSearchParam.buildSearch(cnd, pageForm); + cnd.groupBy("t.id"); + if (!applySafeProposalListOrder(cnd, pageForm, pageCode)) { + cnd.desc("t.createdAt"); + } + sql.setCondition(cnd); + return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); + } + + /** + * 提案未保存分工会字段,流程分派时以提案人当前所属分工会为准, + * 并过滤已删除、已禁用或不存在的人员,避免把无效角色关系写入流程任务。 + */ + @Override + public List getProposalUnionRoleUserIds(ProposalInfo proposalInfo, String... roleCodes) { + if (proposalInfo == null || StrUtil.isBlank(proposalInfo.getCreateUserId())) { + throw new BaseException("提案缺少提案人,无法确定审核人员"); + } + if (roleCodes == null || roleCodes.length == 0) { + throw new BaseException("未配置审核人员角色"); + } + + View_user proposalUser = dao().fetch(View_user.class, + Cnd.where(View_user::getId, "=", proposalInfo.getCreateUserId())); + if (proposalUser == null || StrUtil.isBlank(proposalUser.getUnionId())) { + throw new BaseException("提案人未设置所属分工会,无法确定审核人员"); + } + + List roleIds = Arrays.stream(roleCodes) + .filter(StrUtil::isNotBlank) + .map(sysRoleService::getByCode) + .filter(Objects::nonNull) + .map(Sys_role::getId) + .toList(); + if (roleIds.isEmpty()) { + throw new BaseException("审核人员角色不存在,请联系校工会进行设置"); + } + + List userRoles = dao().query(Sys_user_role.class, + Cnd.where(Sys_user_role::getRoleId, "in", roleIds) + .and(Sys_user_role::getUnionId, "=", proposalUser.getUnionId())); + return userRoles.stream() + .map(Sys_user_role::getUserId) + .filter(StrUtil::isNotBlank) + .distinct() + .filter(this::isEnabledUser) + .toList(); + } + + /** 校验流程参与人仍是可用系统用户。 */ + private boolean isEnabledUser(String userId) { + Sys_user user = dao().fetch(Sys_user.class, userId); + return user != null && !Boolean.TRUE.equals(user.getDelFlag()) && !user.isDisabled(); + } + /** * 创建各列表页独立排序白名单,显示字段与真实SQL字段或固定查询别名一一对应。 * @@ -163,6 +273,8 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl imp pages.put("commissioner", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "sessionName", "tcs.fullName", "delegationName", "tcd.name", "CONFIRM_FILING_COUNT", "CONFIRM_FILING_COUNT", "SUGGESTION_COUNT", "SUGGESTION_COUNT", "NOT_COUNT", "NOT_COUNT", "taskName", "taskName", "instanceState", "ins.state")); pages.put("feedbackEvaluation", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "merge", "merge", "curTaskName", "curTaskName", "instanceState", "ins.state")); pages.put("delegation", standardOrderColumns()); + pages.put("secondaryUnionAudit", standardOrderColumns()); + pages.put("partyOrganizationAudit", standardOrderColumns()); pages.put("control", standardOrderColumns()); pages.put("schoolLeaderApproval", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "undertakeUnits", "masterUnitName", "merge", "merge", "curTaskName", "curTaskName", "auditUser", "auditUser", "instanceState", "ins.state")); pages.put("committeeFilingUnit", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "sessionName", "tcs.fullName", "caseFilingResult", "info.caseFilingResult", "delegationName", "tcd.name", "curTaskName", "curTaskName", "instanceState", "ins.state")); @@ -252,7 +364,7 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl imp private Sql buildWorkflowExportSql(String pageCode, ProposalSearchParam pageForm, boolean approval) { boolean isUnitReply = "unitReply".equals(pageCode); boolean isSchoolLeaderApproval = "schoolLeaderApproval".equals(pageCode); - boolean requiresUnionFilter = Set.of("preAudit", "committeeFiling", "committeeFilingUnit", + boolean requiresUnionFilter = Set.of("preAudit", "secondaryUnionAudit", "partyOrganizationAudit", "committeeFiling", "committeeFilingUnit", "schoolLeaderApproval", "caseCheck").contains(pageCode); String taskName = getWorkflowTaskName(pageCode); @@ -341,6 +453,8 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl imp Map taskNames = Map.of( "delegation", "delegation", "preAudit", "preAudit", + "secondaryUnionAudit", "secondaryUnionAudit", + "partyOrganizationAudit", "partyOrganizationAudit", "committeeFiling", "committee", "committeeFilingUnit", "committeeFilingUnit", "schoolLeaderApproval", "schoolLeader", @@ -414,6 +528,8 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl imp Map> columns = new HashMap<>(); columns.put("delegation", commonColumns); columns.put("preAudit", commonColumns); + columns.put("secondaryUnionAudit", commonColumns); + columns.put("partyOrganizationAudit", commonColumns); columns.put("caseCheck", Set.of("code", "caseFilingCode", "name", "createUserName", "typeName", "sessionName", "delegationName", "curTaskName", "instanceState", "finishTime")); columns.put("committeeFiling", Set.of("code", "caseFilingCode", "name", "typeName", "sessionName", diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/impl/ProposalExportServiceImpl.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/impl/ProposalExportServiceImpl.java index 721fdc8..77e2733 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/impl/ProposalExportServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/impl/ProposalExportServiceImpl.java @@ -5,6 +5,8 @@ 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.bean.BeanUtil; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.Dict; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HtmlUtil; import com.budwk.app.base.exception.BaseException; @@ -580,7 +582,7 @@ public class ProposalExportServiceImpl extends BaseServiceImpl imp ZipOutputStream zipOutputStream = new ZipOutputStream(bos); for (NutMap proposalRow : list) { try (ByteArrayOutputStream docxByteArrayOutputStream = new ByteArrayOutputStream()) { - proposalCommonService.exportProposalAsDocx(proposalRow.getString("id"), docxByteArrayOutputStream); + exportProposalAsDocx(proposalRow.getString("id"), docxByteArrayOutputStream); ZipEntry zipEntry = new ZipEntry(proposalRow.getString("code") + "-" + sanitizeZipFileName(proposalRow.getString("name")) + ".docx"); zipOutputStream.putNextEntry(zipEntry); docxByteArrayOutputStream.writeTo(zipOutputStream); @@ -611,7 +613,7 @@ public class ProposalExportServiceImpl extends BaseServiceImpl imp try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { for (NutMap proposalRow : list) { try (ByteArrayOutputStream oneDocOut = new ByteArrayOutputStream()) { - proposalCommonService.exportProposalAsDocx(proposalRow.getString("id"), oneDocOut); + exportProposalAsDocx(proposalRow.getString("id"), oneDocOut); if (mergedDoc == null) { mergedDoc = new XWPFDocument(new ByteArrayInputStream(oneDocOut.toByteArray())); } else { @@ -642,6 +644,182 @@ public class ProposalExportServiceImpl extends BaseServiceImpl imp } } + @Override + public void exportProposalAsDocx(String proposalId, HttpServletResponse response) { + ProposalInfo proposalInfo = fetch(proposalId); + if (proposalInfo == null) { + throw new BaseException("提案信息不存在"); + } + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + exportProposalAsDocx(proposalId, outputStream); + CommonDownloadUtil.download(proposalInfo.getCode() + "-" + proposalInfo.getName() + ".docx", + outputStream.toByteArray(), response); + } catch (IOException e) { + log.error("综合导出单条提案失败,proposalId={}", proposalId, e); + throw new BaseException("导出失败"); + } + } + + @Override + public void exportProposalAsDocx(String proposalId, ByteArrayOutputStream outputStream) { + NutMap proposal = queryProposalTemplateData(proposalId); + if (proposal == null) { + throw new BaseException("提案信息不存在"); + } + proposal.put("sign", sysOfficeTemplateUtil.createPictureRenderData(proposal.getString("signature"))); + proposal.put("secondedUserName", queryAgreedSecondedUserNames(proposalId)); + proposal.put("brief", sysOfficeTemplateUtil.convertRichTextToDocText(proposal.getString("brief"))); + proposal.put("measures", sysOfficeTemplateUtil.convertRichTextToDocText(proposal.getString("measures"))); + + HtmlRenderPolicy htmlRenderPolicy = new HtmlRenderPolicy(); + htmlRenderPolicy.getConfig().setShowDefaultTableBorderInTableCell(true); + Configure config = Configure.builder() + .bind("proposal.brief", htmlRenderPolicy) + .bind("proposal.measures", htmlRenderPolicy) + .bind("opinion", htmlRenderPolicy) + .build(); + + Map renderData = new HashMap<>(); + renderData.put("schoolName", Globals.AppName); + renderData.put("proposal", proposal); + renderData.put("secondaryUnitAuditList", queryLatestProposalAudit(proposalId, "secondaryUnionAudit", proposal)); + renderData.put("partyOrganizationAuditList", queryLatestProposalAudit(proposalId, "partyOrganizationAudit", proposal)); + renderData.put("caseUnitRecordList", queryLatestProposalAudit(proposalId, "committee", proposal)); + try { + XWPFTemplate.compile(sysOfficeTemplateUtil.getTemplate("proposal"), config) + .render(renderData) + .writeAndClose(outputStream); + } catch (IOException e) { + log.error("综合导出提案模板渲染失败,proposalId={}", proposalId, e); + throw new BaseException("导出失败"); + } + } + + /** + * 查询模板使用的提案基础信息。字段名称与 SWUFE 提案模板保持一致, + * 使模板替换后无需改动业务代码中的占位符命名。 + */ + private NutMap queryProposalTemplateData(String proposalId) { + Sql sql = Sqls.create(""" + SELECT + info.code AS proposalCode, + info.caseFilingCode, + info.name AS proposalName, + info.createUserName AS username, + info.unitName, + COALESCE(NULLIF(info.mobile, ''), user.mobile) AS mobile, + user.email, + info.signature, + info.caseFilingResult, + info.brief, + info.measures, + DATE_FORMAT(info.createTime, '%Y年%m月%d日') AS createTime + FROM proposal_info info + LEFT JOIN sys_user user ON user.id = info.createUserId + WHERE info.id = @proposalId + """); + sql.setParam("proposalId", proposalId); + sql.setCallback(Sqls.callback.map()); + execute(sql); + return (NutMap) sql.getResult(); + } + + /** 查询已同意的附议人姓名,按附议完成时间保持稳定排序。 */ + private String queryAgreedSecondedUserNames(String proposalId) { + Sql sql = Sqls.create(""" + SELECT GROUP_CONCAT(userName ORDER BY secondedTime SEPARATOR '、') AS secondedUserName + FROM proposal_second + WHERE proposalId = @proposalId AND isAgree = 1 + """); + sql.setParam("proposalId", proposalId); + sql.setCallback(Sqls.callback.str()); + execute(sql); + return StrUtil.blankToDefault(sql.getString(), ""); + } + + /** + * 读取指定审核节点最后一次已办记录,并从任务变量中获取办理人、意见和签字快照。 + * 历史退回后重新审核时,模板只展示最终有效的一次审核结果,避免重复打印旧意见。 + */ + private List queryLatestProposalAudit(String proposalId, String taskName, NutMap proposal) { + Sql sql = Sqls.create(""" + SELECT task.variable, task.finishTime + FROM wf_process_task task + INNER JOIN wf_process_instance instance ON instance.id = task.processInstanceId + WHERE instance.businessNo = @proposalId + AND task.taskName = @taskName + AND task.taskState = @finishedState + ORDER BY task.finishTime DESC, task.id DESC + LIMIT 1 + """); + sql.setParam("proposalId", proposalId); + sql.setParam("taskName", taskName); + sql.setParam("finishedState", ProcessTaskStateEnum.FINISHED.getCode()); + sql.setCallback(Sqls.callback.map()); + execute(sql); + NutMap task = (NutMap) sql.getResult(); + if (task == null) { + return List.of(); + } + Dict variable = parseTaskVariable(task.getString("variable")); + NutMap audit = new NutMap(); + audit.put("username", getTaskVariableString(variable, "userName")); + audit.put("opinion", getTaskVariableString(variable, "opinion")); + audit.put("auditTime", task.getTime("finishTime") == null ? "" : DateUtil.formatDate(task.getTime("finishTime"))); + audit.put("sign", sysOfficeTemplateUtil.createPictureRenderData(getTaskVariableString(variable, "signature"))); + if ("committee".equals(taskName)) { + audit.put("resultAudit", buildCommitteeResultAudit(variable, proposal)); + } + return List.of(audit); + } + + /** 兼容当前流程变量与已迁移 V3 历史任务变量的字段前缀。 */ + private String getTaskVariableString(Dict variable, String fieldName) { + String taskFormValue = variable.getStr(FlowConst.TASK_FORM_DATA_PREFIX + fieldName); + if (StrUtil.isNotBlank(taskFormValue)) { + return taskFormValue; + } + return StrUtil.blankToDefault(variable.getStr("f_" + fieldName), ""); + } + + /** 流程变量损坏或为空时按空字典处理,确保单条导出不会因历史脏数据中断。 */ + private Dict parseTaskVariable(String variableJson) { + if (StrUtil.isBlank(variableJson)) { + return Dict.create(); + } + try { + return Json.fromJson(Dict.class, variableJson); + } catch (Exception e) { + log.warn("提案审核任务变量解析失败,将按空变量导出"); + return Dict.create(); + } + } + + /** 构建提案委员会立案结论,沿用 V3 模板中“结论 + 审核意见”的展示方式。 */ + private String buildCommitteeResultAudit(Dict variable, NutMap proposal) { + String caseFilingResult = StrUtil.blankToDefault(getTaskVariableString(variable, "caseFilingResult"), + proposal.getString("caseFilingResult")); + if ("CONFIRM_FILING".equals(caseFilingResult)) { + String masterUnitName = getTaskVariableString(variable, "masterUnitNameStr"); + String slaveUnitName = getTaskVariableString(variable, "slaveUnitNameStr"); + String result = "经提案工作委员会研究,符合提案立案条件,予以立案"; + if (StrUtil.isNotBlank(masterUnitName)) { + result += ",建议由" + masterUnitName + "办理"; + } + if (StrUtil.isNotBlank(slaveUnitName)) { + result += "," + slaveUnitName + "协办"; + } + return result + "。"; + } + if ("SUGGESTION".equals(caseFilingResult)) { + return "作为意见建议转送参考。"; + } + if ("NOT".equals(caseFilingResult)) { + return "该提案不予立案。"; + } + return ""; + } + private List queryProposalBaseList(ProposalQueryComprehensiveParam pageForm) { Sql sql = Sqls.create(""" SELECT diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/impl/ProposalMessageServiceImpl.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/impl/ProposalMessageServiceImpl.java new file mode 100644 index 0000000..5dc2bbb --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/impl/ProposalMessageServiceImpl.java @@ -0,0 +1,116 @@ +package com.budwk.app.zhgh.democratic.proposal.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.budwk.app.base.constant.RoleConstant; +import com.budwk.app.base.sms.SmsService; +import com.budwk.app.sys.models.Sys_user; +import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo; +import com.budwk.app.zhgh.democratic.proposal.service.ProposalMessageService; +import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService; +import lombok.extern.slf4j.Slf4j; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; + +import java.util.List; + +/** + * 提案业务短信实现。 + * + * 发送前不重复判断短信平台开关,统一交由 SmsService 按单点登录、系统参数和平台开关处理, + * 避免提案模块和公共短信模块出现两套配置规则。 + */ +@IocBean(args = {"refer:dao"}) +@Slf4j +public class ProposalMessageServiceImpl implements ProposalMessageService { + + private static final String INVITE_SECONDER_TEMPLATE = "{}代表,您好,{}代表的提案《{}》,邀请您作为附议人,请您登陆学校智慧工会系统进行附议。"; + private static final String SECONDARY_UNION_TEMPLATE = "{}您好,{}代表的提案《{}》,已经完成附议,请您登陆学校智慧工会系统进行审核。"; + + private final Dao dao; + + @Inject + private SmsService smsService; + @Inject + private ProposalCommonService proposalCommonService; + + public ProposalMessageServiceImpl(Dao dao) { + this.dao = dao; + } + + @Override + public void sendInviteSeconderMessage(String proposalId, List seconderUserIds) { + ProposalInfo proposalInfo = getProposal(proposalId); + if (proposalInfo == null || seconderUserIds == null || seconderUserIds.isEmpty()) { + return; + } + Sys_user proposalCreator = dao.fetch(Sys_user.class, proposalInfo.getCreateUserId()); + if (proposalCreator == null) { + log.warn("提案邀请附议短信未发送,未找到提案人,proposalId={}", proposalId); + return; + } + List seconders = dao.query(Sys_user.class, + Cnd.where(Sys_user::getId, "in", seconderUserIds)); + for (Sys_user seconder : seconders) { + String content = StrUtil.format(INVITE_SECONDER_TEMPLATE, seconder.getUsername(), + proposalCreator.getUsername(), proposalInfo.getName()); + sendSms(seconder, content, proposalId, "邀请附议人"); + } + } + + @Override + public void sendSecondaryUnionMessage(String proposalId) { + ProposalInfo proposalInfo = getProposal(proposalId); + if (proposalInfo == null) { + return; + } + Sys_user proposalCreator = dao.fetch(Sys_user.class, proposalInfo.getCreateUserId()); + if (proposalCreator == null) { + log.warn("提案附议达标短信未发送,未找到提案人,proposalId={}", proposalId); + return; + } + try { + List reviewerIds = proposalCommonService.getProposalUnionRoleUserIds(proposalInfo, + RoleConstant.BRANCH_UNION_CHAIRMAN.name(), RoleConstant.BRANCH_UNION_VICE_CHAIRMAN.name()); + if (reviewerIds.isEmpty()) { + log.warn("提案附议达标短信未发送,提案人所属分工会未设置主席或副主席,proposalId={}", proposalId); + return; + } + List reviewers = dao.query(Sys_user.class, Cnd.where(Sys_user::getId, "in", reviewerIds)); + for (Sys_user reviewer : reviewers) { + String content = StrUtil.format(SECONDARY_UNION_TEMPLATE, reviewer.getUsername(), + proposalCreator.getUsername(), proposalInfo.getName()); + sendSms(reviewer, content, proposalId, "附议人数达标"); + } + } catch (RuntimeException e) { + // 短信通知失败不能影响已完成的附议业务和流程流转。 + log.error("提案附议达标短信发送失败,proposalId={}", proposalId, e); + } + } + + /** + * 发送单个提案业务短信。 + * + * SmsService 会按运行配置决定是否真实下发;没有工号的人员不能被短信平台识别,因此只记录并跳过。 + */ + private void sendSms(Sys_user user, String content, String proposalId, String scene) { + if (StrUtil.isBlank(user.getLoginname())) { + log.warn("提案{}短信未发送,收件人未配置工号,proposalId={},userId={}", scene, proposalId, user.getId()); + return; + } + try { + smsService.sendSmMsg(user.getLoginname(), content); + } catch (RuntimeException e) { + // 短信平台异常不能影响提案业务提交,异常留在日志中供后续排查。 + log.error("提案{}短信发送失败,proposalId={},userId={}", scene, proposalId, user.getId(), e); + } + } + + private ProposalInfo getProposal(String proposalId) { + if (StrUtil.isBlank(proposalId)) { + return null; + } + return dao.fetch(ProposalInfo.class, proposalId); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/impl/ProposalSecondedServiceImpl.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/impl/ProposalSecondedServiceImpl.java index 5f1b204..26c44e4 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/impl/ProposalSecondedServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/impl/ProposalSecondedServiceImpl.java @@ -4,34 +4,67 @@ import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjectUtil; import com.budwk.app.base.service.impl.BaseServiceImpl; import com.budwk.app.flow.enums.ProcessSubmitTypeEnum; +import com.budwk.app.zhgh.democratic.proposal.models.ProposalConfig; import com.budwk.app.zhgh.democratic.proposal.models.ProposalSecond; +import com.budwk.app.zhgh.democratic.proposal.service.ProposalMessageService; import com.budwk.app.zhgh.democratic.proposal.service.ProposalSecondedService; import org.nutz.dao.Cnd; import org.nutz.dao.Dao; +import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.lang.Strings; @IocBean(args = {"refer:dao"}) public class ProposalSecondedServiceImpl extends BaseServiceImpl implements ProposalSecondedService { + @Inject + private ProposalMessageService proposalMessageService; + public ProposalSecondedServiceImpl(Dao dao) { super(dao); } @Override - public void updateSecondRecord(String proposalId, String taskActorUserId, String opinion, Integer submitType) { + public boolean updateSecondRecord(String proposalId, String taskActorUserId, String opinion, Integer submitType) { if (Strings.isBlank(proposalId) || Strings.isBlank(taskActorUserId) || submitType == null) { - return; + return false; } // 仅更新当前提案、当前附议人的附议结果,不处理流程任务表。 ProposalSecond proposalSecond = this.fetch(Cnd.where(ProposalSecond::getProposalId, "=", proposalId) .and(ProposalSecond::getSeconderId, "=", taskActorUserId)); if (proposalSecond == null) { - return; + return false; } - proposalSecond.setIsAgree(ObjectUtil.equals(submitType, ProcessSubmitTypeEnum.AGREE.getCode())); + boolean wasAgree = Boolean.TRUE.equals(proposalSecond.getIsAgree()); + boolean isAgree = ObjectUtil.equals(submitType, ProcessSubmitTypeEnum.AGREE.getCode()); + int agreedCountBefore = 0; + if (isAgree && !wasAgree) { + agreedCountBefore = this.count(Cnd.where(ProposalSecond::getProposalId, "=", proposalId) + .and(ProposalSecond::getIsAgree, "=", true)); + } + proposalSecond.setIsAgree(isAgree); proposalSecond.setOpinion(opinion); proposalSecond.setSecondedTime(DateUtil.date()); this.update(proposalSecond); + notifySecondaryUnionWhenThresholdReached(proposalId, isAgree, wasAgree, agreedCountBefore); + return true; + } + + /** + * 仅在同意附议人数首次达到配置数量时发送二级工会审核短信,防止重复提交附议结果导致重复通知。 + */ + private void notifySecondaryUnionWhenThresholdReached(String proposalId, boolean isAgree, boolean wasAgree, + int agreedCountBefore) { + if (!isAgree || wasAgree) { + return; + } + ProposalConfig proposalConfig = this.dao().fetch(ProposalConfig.class, Cnd.NEW()); + if (proposalConfig == null || proposalConfig.getSeconderNum() == null || proposalConfig.getSeconderNum() <= 0) { + return; + } + if (agreedCountBefore < proposalConfig.getSeconderNum() + && agreedCountBefore + 1 >= proposalConfig.getSeconderNum()) { + proposalMessageService.sendSecondaryUnionMessage(proposalId); + } } } diff --git a/src/main/java/com/budwk/app/zhgh/huazhu/constant/HuaZhuConstant.java b/src/main/java/com/budwk/app/zhgh/huazhu/constant/HuaZhuConstant.java new file mode 100644 index 0000000..62ee676 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/huazhu/constant/HuaZhuConstant.java @@ -0,0 +1,38 @@ +package com.budwk.app.zhgh.huazhu.constant; + +public class HuaZhuConstant { + /** + * 华住接口请求超时,单位毫秒。 + */ + public static final int TIMEOUT = 20000; + + public static final String BASEURL = "https://htravelserver.huazhu.com"; + + public static final String OAUTH2_URL = "https://oauth2-api.huazhu.com"; + + /** + * 公司卡号 + */ + public static final String CARD_ID = "VCENTCRM1169099135"; + + /** + * 公司密钥 + */ + public static final String CARD_PASSWORD = "47CD8C9C-A4CD-4CBD-A30F-41C41BDF7E99"; + + /** + * AES密钥 + */ + public static final String AES_KEY = "JAE4YZJLXZW5HGBU"; + + /** + * 客户端ID + */ + public static final String CLIENT_ID = "5e8651d8-a5a8-4358-9986-91f426e191c8"; + + /** + * 客户端密钥 + */ + public static final String CLIENT_SECRET = "2d20a7f3125b52b8e90936c3b30fb4b62b443a52716a090e55e302cf40f32b66d592ef1e98c1cf0a8e5ec3f9c444a9b4f53a337b1be57263d4fbb71044f67f12"; + +} diff --git a/src/main/java/com/budwk/app/zhgh/huazhu/controller/HuaZhuManageController.java b/src/main/java/com/budwk/app/zhgh/huazhu/controller/HuaZhuManageController.java new file mode 100644 index 0000000..3c03adc --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/huazhu/controller/HuaZhuManageController.java @@ -0,0 +1,64 @@ +package com.budwk.app.zhgh.huazhu.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.hutool.core.util.StrUtil; +import com.budwk.app.base.result.Result; +import com.budwk.app.zhgh.huazhu.param.HuaZhuEmployeePageForm; +import com.budwk.app.zhgh.huazhu.service.HuaZhuEmployeeService; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; + +import javax.validation.Valid; +import java.util.Arrays; +import java.util.Collections; + +/** + * PC 端华住员工同步管理。 + */ +@IocBean +@Ok("json:full") +@At("/platform/huazhu") +public class HuaZhuManageController { + + @Inject + private HuaZhuEmployeeService huaZhuEmployeeService; + + @At("") + @Ok("beetl:/platform/zhgh/huazhu/index.html") + @SaCheckPermission("huazhu.manage") + public void index() { + } + + @At + @SaCheckPermission("huazhu.manage") + public Result pageData(@Valid HuaZhuEmployeePageForm pageForm) { + return Result.success(huaZhuEmployeeService.pageUsers(pageForm)); + } + + @At + @SaCheckPermission("huazhu.manage") + public Result syncUsers(@Param("userIds") String[] userIds) { + if (userIds == null || userIds.length == 0) { + return Result.error("请先选择需要同步的员工"); + } + return Result.success(huaZhuEmployeeService.syncUsers(Arrays.asList(userIds))); + } + + @At + @SaCheckPermission("huazhu.manage") + public Result queryStatus(@Param("userId") String userId) { + if (StrUtil.isBlank(userId)) { + return Result.error("用户不能为空"); + } + try { + return Result.success(huaZhuEmployeeService.queryStatus(userId)); + } catch (IllegalArgumentException | IllegalStateException e) { + return Result.error(e.getMessage()); + } catch (Exception e) { + return Result.error("查询华住状态失败,请稍后重试"); + } + } +} diff --git a/src/main/java/com/budwk/app/zhgh/huazhu/h5Controller/H5HuaZhuController.java b/src/main/java/com/budwk/app/zhgh/huazhu/h5Controller/H5HuaZhuController.java new file mode 100644 index 0000000..26f46ef --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/huazhu/h5Controller/H5HuaZhuController.java @@ -0,0 +1,37 @@ +package com.budwk.app.zhgh.huazhu.h5Controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.budwk.app.web.commons.auth.utils.SecurityUtil; +import com.budwk.app.zhgh.huazhu.service.HuaZhuLoginService; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * H5 幸福工荟中的华住免密登录入口。 + */ +@IocBean +@At("/platform/h5/huazhu") +public class H5HuaZhuController { + + @Inject + private HuaZhuLoginService huaZhuLoginService; + + /** + * 校验幸福工荟访问权限后跳转至华住企业登录页面。 + * + * @param response HTTP 响应,用于返回 302 跳转 + * @throws IOException 响应输出异常 + */ + @At("/basicLogin") + @Ok("void") + @SaCheckPermission("h5.inclusive.benefit") + public void basicLogin(HttpServletResponse response) throws IOException { + response.setHeader("Cache-Control", "no-store"); + response.sendRedirect(huaZhuLoginService.buildBasicLoginUrl(SecurityUtil.getUserLoginname())); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/huazhu/param/HuaZhuEmployeePageForm.java b/src/main/java/com/budwk/app/zhgh/huazhu/param/HuaZhuEmployeePageForm.java new file mode 100644 index 0000000..086c9e0 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/huazhu/param/HuaZhuEmployeePageForm.java @@ -0,0 +1,14 @@ +package com.budwk.app.zhgh.huazhu.param; + +import com.budwk.app.base.param.PageForm; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 华住员工管理分页参数。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class HuaZhuEmployeePageForm extends PageForm { + private String searchKeyword; +} diff --git a/src/main/java/com/budwk/app/zhgh/huazhu/service/HuaZhuEmployeeService.java b/src/main/java/com/budwk/app/zhgh/huazhu/service/HuaZhuEmployeeService.java new file mode 100644 index 0000000..0d16713 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/huazhu/service/HuaZhuEmployeeService.java @@ -0,0 +1,39 @@ +package com.budwk.app.zhgh.huazhu.service; + +import com.budwk.app.base.page.Pagination; +import com.budwk.app.zhgh.huazhu.param.HuaZhuEmployeePageForm; +import com.budwk.app.zhgh.huazhu.vo.HuaZhuEmployeeStatus; +import com.budwk.app.zhgh.huazhu.vo.HuaZhuSyncResult; +import org.nutz.lang.util.NutMap; + +import java.util.List; + +/** + * 华住员工同步与状态查询服务。 + */ +public interface HuaZhuEmployeeService { + + /** + * 分页查询可同步的本项目员工。 + * + * @param pageForm 查询参数 + * @return 员工分页数据 + */ + Pagination pageUsers(HuaZhuEmployeePageForm pageForm); + + /** + * 将选中的本项目员工同步到华住。 + * + * @param userIds 本项目用户主键列表 + * @return 每位员工的同步结果 + */ + List syncUsers(List userIds); + + /** + * 查询指定本项目员工在华住的实时状态。 + * + * @param userId 本项目用户主键 + * @return 华住员工状态 + */ + HuaZhuEmployeeStatus queryStatus(String userId); +} diff --git a/src/main/java/com/budwk/app/zhgh/huazhu/service/HuaZhuLoginService.java b/src/main/java/com/budwk/app/zhgh/huazhu/service/HuaZhuLoginService.java new file mode 100644 index 0000000..dcb6a69 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/huazhu/service/HuaZhuLoginService.java @@ -0,0 +1,15 @@ +package com.budwk.app.zhgh.huazhu.service; + +/** + * 华住免密登录服务。 + */ +public interface HuaZhuLoginService { + + /** + * 按华住协议为当前职工工号生成免密登录跳转地址。 + * + * @param loginName 当前登录职工的工号 + * @return 华住免密登录地址 + */ + String buildBasicLoginUrl(String loginName); +} diff --git a/src/main/java/com/budwk/app/zhgh/huazhu/service/impl/HuaZhuEmployeeServiceImpl.java b/src/main/java/com/budwk/app/zhgh/huazhu/service/impl/HuaZhuEmployeeServiceImpl.java new file mode 100644 index 0000000..772d59b --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/huazhu/service/impl/HuaZhuEmployeeServiceImpl.java @@ -0,0 +1,232 @@ +package com.budwk.app.zhgh.huazhu.service.impl; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.json.JSONUtil; +import cn.hutool.json.JSONObject; +import com.budwk.app.base.page.Pagination; +import com.budwk.app.sys.models.Sys_user; +import com.budwk.app.zhgh.huazhu.constant.HuaZhuConstant; +import com.budwk.app.zhgh.huazhu.param.HuaZhuEmployeePageForm; +import com.budwk.app.zhgh.huazhu.service.HuaZhuEmployeeService; +import com.budwk.app.zhgh.huazhu.vo.HuaZhuEmployeeStatus; +import com.budwk.app.zhgh.huazhu.vo.HuaZhuSyncResult; +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 org.nutz.integration.jedis.RedisService; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collections; +import java.util.List; + +/** + * 华住员工同步与状态查询实现。 + */ +@IocBean(args = {"refer:dao"}) +public class HuaZhuEmployeeServiceImpl implements HuaZhuEmployeeService { + + private static final String TOKEN_CACHE_KEY = "zhgh:huazhu:oauth-token"; + + private final Dao dao; + + @Inject + private RedisService redisService; + + public HuaZhuEmployeeServiceImpl(Dao dao) { + this.dao = dao; + } + + @Override + public Pagination pageUsers(HuaZhuEmployeePageForm pageForm) { + String keyword = StrUtil.trim(pageForm.getSearchKeyword()); + String condition = " WHERE u.delFlag = 0 "; + if (StrUtil.isNotBlank(keyword)) { + condition += " AND (u.loginname LIKE @keyword OR u.username LIKE @keyword) "; + } + Sql countSql = Sqls.create("SELECT COUNT(1) FROM sys_user u" + condition); + Sql listSql = Sqls.create("SELECT u.id, u.loginname, u.username, unit.name AS unitName " + + "FROM sys_user u LEFT JOIN sys_unit unit ON unit.id = u.unitId " + condition + + "ORDER BY u.createdAt DESC"); + if (StrUtil.isNotBlank(keyword)) { + String likeKeyword = "%" + keyword + "%"; + countSql.setParam("keyword", likeKeyword); + listSql.setParam("keyword", likeKeyword); + } + countSql.setCallback(Sqls.callback.integer()); + dao.execute(countSql); + int totalCount = countSql.getInt(); + listSql.setPager(dao.createPager(pageForm.getPageNumber(), pageForm.getPageSize())); + listSql.setCallback(Sqls.callback.maps()); + dao.execute(listSql); + return new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), totalCount, listSql.getList(NutMap.class)); + } + + @Override + public List syncUsers(List userIds) { + if (userIds == null || userIds.isEmpty()) { + return Collections.emptyList(); + } + List results = new ArrayList<>(); + for (String userId : userIds) { + Sys_user user = findAvailableUser(userId); + HuaZhuSyncResult result = new HuaZhuSyncResult() + .setUserId(user.getId()) + .setLoginName(user.getLoginname()) + .setUserName(user.getUsername()); + try { + request("/userInfo/addUser", buildEmployeeRequest(user.getLoginname())); + result.setSuccess(true).setMessage("同步成功"); + } catch (Exception e) { + result.setSuccess(false).setMessage("同步失败:" + safeMessage(e)); + } + results.add(result); + } + return results; + } + + @Override + public HuaZhuEmployeeStatus queryStatus(String userId) { + Sys_user user = findAvailableUser(userId); + String body = request("/userInfo/queryUser", buildEmployeeRequest(user.getLoginname())); + JSONObject payload = extractQueryPayload(body); + try { + return JSONUtil.toBean(payload, HuaZhuEmployeeStatus.class); + } catch (Exception e) { + throw new IllegalStateException("华住查询接口返回格式无法识别", e); + } + } + + /** + * 兼容华住接口直接返回员工对象,以及以 data 或 result 包装员工对象的两种响应格式。 + * 响应中不包含员工状态时,只返回经筛选后的业务提示,避免透出第三方完整响应。 + * + * @param body 华住查询接口响应正文 + * @return 可转换为员工状态的 JSON 对象 + */ + private JSONObject extractQueryPayload(String body) { + JSONObject response; + try { + response = JSONUtil.parseObj(body); + } catch (Exception e) { + throw new IllegalStateException("华住查询接口未返回有效数据", e); + } + JSONObject payload = extractObject(response, "data"); + if (payload == null) { + payload = extractObject(response, "result"); + } + if (payload != null) { + return payload; + } + if (response.containsKey("statusCode") || response.containsKey("userNumber") || response.containsKey("userName")) { + return response; + } + String message = StrUtil.blankToDefault(response.getStr("message"), response.getStr("msg")); + throw new IllegalStateException(StrUtil.isBlank(message) ? "华住未返回该员工状态" : "华住查询失败:" + message); + } + + /** + * 从包装响应中提取对象;部分网关会将 data/result 再序列化为 JSON 字符串。 + * + * @param response 华住响应对象 + * @param key 包装字段名 + * @return 包装内的 JSON 对象,缺失或格式不匹配时返回 null + */ + private JSONObject extractObject(JSONObject response, String key) { + Object value = response.get(key); + if (value instanceof JSONObject) { + return (JSONObject) value; + } + if (value instanceof CharSequence && StrUtil.isNotBlank(value.toString())) { + try { + return JSONUtil.parseObj(value.toString()); + } catch (Exception ignored) { + return null; + } + } + return null; + } + + /** + * 调用华住员工接口,并统一处理 OAuth Token 与非成功状态。 + * + * @param path 华住接口路径 + * @param requestBody 请求体 + * @return 华住响应正文 + */ + private String request(String path, NutMap requestBody) { + HttpResponse response = HttpRequest.post(trimTrailingSlash(HuaZhuConstant.BASEURL) + path) + .header("Authorization", getAccessToken()) + .header("accept", "application/json") + .contentType("application/json; charset=UTF-8") + .body(JSONUtil.toJsonStr(requestBody)) + .timeout(HuaZhuConstant.TIMEOUT) + .execute(); + if (!response.isOk()) { + throw new IllegalStateException("华住接口调用失败,状态码:" + response.getStatus()); + } + return response.body(); + } + + /** + * 获取华住 OAuth Token,优先使用 Redis 缓存以避免频繁请求第三方认证接口。 + * + * @return HTTP Authorization 头的 Bearer Token + */ + private String getAccessToken() { + String cachedToken = redisService.get(TOKEN_CACHE_KEY); + if (StrUtil.isNotBlank(cachedToken)) { + return cachedToken; + } + String credential = Base64.getEncoder().encodeToString((HuaZhuConstant.CLIENT_ID + ":" + HuaZhuConstant.CLIENT_SECRET).getBytes(StandardCharsets.UTF_8)); + HttpResponse response = HttpRequest.post(trimTrailingSlash(HuaZhuConstant.OAUTH2_URL) + + "/oauth/token?scope=ALL&grant_type=client_credentials") + .header("Authorization", "Basic " + credential) + .timeout(HuaZhuConstant.TIMEOUT) + .execute(); + if (!response.isOk()) { + throw new IllegalStateException("获取华住认证 Token 失败,状态码:" + response.getStatus()); + } + JSONObject tokenData = JSONUtil.parseObj(response.body()); + String accessToken = tokenData.getStr("access_token"); + String tokenType = StrUtil.blankToDefault(tokenData.getStr("token_type"), "Bearer"); + if (StrUtil.isBlank(accessToken)) { + throw new IllegalStateException("华住认证接口未返回 Token"); + } + String authorization = tokenType + " " + accessToken; + redisService.setex(TOKEN_CACHE_KEY, 60 * 58, authorization); + return authorization; + } + + private NutMap buildEmployeeRequest(String loginName) { + return NutMap.NEW().addv("cardId", HuaZhuConstant.CARD_ID) + .addv("cardPassWord", HuaZhuConstant.CARD_PASSWORD) + .addv("userNumber", loginName); + } + + private Sys_user findAvailableUser(String userId) { + Sys_user user = dao.fetch(Sys_user.class, Cnd.where("id", "=", userId).and("delFlag", "=", false)); + if (user == null) { + throw new IllegalArgumentException("用户不存在或已删除"); + } + if (StrUtil.isBlank(user.getLoginname())) { + throw new IllegalArgumentException("用户缺少工号,无法同步华住"); + } + return user; + } + + private String trimTrailingSlash(String value) { + return value.endsWith("/") ? value.substring(0, value.length() - 1) : value; + } + + private String safeMessage(Exception e) { + return StrUtil.isBlank(e.getMessage()) ? "请检查华住配置和网络连接" : e.getMessage(); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/huazhu/service/impl/HuaZhuLoginServiceImpl.java b/src/main/java/com/budwk/app/zhgh/huazhu/service/impl/HuaZhuLoginServiceImpl.java new file mode 100644 index 0000000..879a966 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/huazhu/service/impl/HuaZhuLoginServiceImpl.java @@ -0,0 +1,68 @@ +package com.budwk.app.zhgh.huazhu.service.impl; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.budwk.app.zhgh.huazhu.constant.HuaZhuConstant; +import com.budwk.app.zhgh.huazhu.service.HuaZhuLoginService; +import org.nutz.ioc.loader.annotation.IocBean; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +/** + * 华住免密登录服务实现。 + */ +@IocBean +public class HuaZhuLoginServiceImpl implements HuaZhuLoginService { + + private static final String TRAVEL_TYPE = "YS"; + + @Override + public String buildBasicLoginUrl(String loginName) { + if (StrUtil.isBlank(loginName)) { + throw new IllegalArgumentException("当前登录用户缺少工号,无法进入华住"); + } + + // 华住约定使用 AES/ECB/PKCS5Padding 对公司卡号、工号和当前时间生成签名。 + String authorizeSign = encrypt(HuaZhuConstant.CARD_ID + "&" + loginName + "&" + DateUtil.now(), HuaZhuConstant.AES_KEY); + return trimTrailingSlash(HuaZhuConstant.BASEURL) + "/authentication/basicLogin?cardId=" + encode(HuaZhuConstant.CARD_ID) + + "&travelType=" + TRAVEL_TYPE + + "&userNumber=" + encode(loginName) + + "&invoiceNo=" + + "&authorizeSign=" + encode(authorizeSign); + } + + /** + * 按华住协议生成 AES 签名,不记录签名内容或密钥。 + * + * @param source 待签名内容 + * @param aesKey 华住提供的 16 字节 AES 密钥 + * @return Base64 编码后的签名 + */ + private String encrypt(String source, String aesKey) { + try { + byte[] keyBytes = aesKey.getBytes(StandardCharsets.UTF_8); + if (keyBytes.length != 16) { + throw new IllegalStateException("华住 AES 密钥必须为 16 字节"); + } + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyBytes, "AES")); + return Base64.getEncoder().encodeToString(cipher.doFinal(source.getBytes(StandardCharsets.UTF_8))); + } catch (IllegalStateException e) { + throw e; + } catch (Exception e) { + throw new IllegalStateException("生成华住登录签名失败", e); + } + } + + private String encode(String value) { + return URLEncoder.encode(value, StandardCharsets.UTF_8); + } + + private String trimTrailingSlash(String value) { + return value.endsWith("/") ? value.substring(0, value.length() - 1) : value; + } +} diff --git a/src/main/java/com/budwk/app/zhgh/huazhu/vo/HuaZhuEmployeeStatus.java b/src/main/java/com/budwk/app/zhgh/huazhu/vo/HuaZhuEmployeeStatus.java new file mode 100644 index 0000000..66b87ce --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/huazhu/vo/HuaZhuEmployeeStatus.java @@ -0,0 +1,32 @@ +package com.budwk.app.zhgh.huazhu.vo; + +import lombok.Data; + +import java.util.List; + +/** + * 华住查询员工接口的展示结果。 + */ +@Data +public class HuaZhuEmployeeStatus { + private String statusCode; + private String userNumber; + private String userName; + private String userDep; + private String costCenter; + private String rank; + private String leader; + private String mobile; + private String email; + private List permissions; + + /** + * 华住返回的预订权限。 + */ + @Data + public static class Permission { + private Boolean hasPermission; + private String permissionCode; + private String permissionValue; + } +} diff --git a/src/main/java/com/budwk/app/zhgh/huazhu/vo/HuaZhuSyncResult.java b/src/main/java/com/budwk/app/zhgh/huazhu/vo/HuaZhuSyncResult.java new file mode 100644 index 0000000..70cf5eb --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/huazhu/vo/HuaZhuSyncResult.java @@ -0,0 +1,17 @@ +package com.budwk.app.zhgh.huazhu.vo; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 单个员工同步到华住的执行结果。 + */ +@Data +@Accessors(chain = true) +public class HuaZhuSyncResult { + private String userId; + private String loginName; + private String userName; + private Boolean success; + private String message; +} diff --git a/src/main/java/com/budwk/app/zhgh/inclusive/controller/InclusiveBenefitController.java b/src/main/java/com/budwk/app/zhgh/inclusive/controller/InclusiveBenefitController.java new file mode 100644 index 0000000..99acc5b --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/inclusive/controller/InclusiveBenefitController.java @@ -0,0 +1,77 @@ +package com.budwk.app.zhgh.inclusive.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.hutool.core.util.StrUtil; +import com.budwk.app.base.result.Result; +import com.budwk.app.zhgh.inclusive.model.InclusiveBenefit; +import com.budwk.app.zhgh.inclusive.param.InclusiveBenefitPageForm; +import com.budwk.app.zhgh.inclusive.service.InclusiveBenefitService; +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; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; + +import javax.validation.Valid; + +/** + * PC 端南航专惠管理。 + */ +@IocBean +@Ok("json:full") +@At("/platform/inclusive") +public class InclusiveBenefitController { + + @Inject + private InclusiveBenefitService inclusiveBenefitService; + + @At("") + @Ok("beetl:/platform/zhgh/inclusive/index.html") + @SaCheckPermission("inclusive.benefit") + public void index() { + } + + @At + @SaCheckPermission("inclusive.benefit") + public Result pageData(@Valid InclusiveBenefitPageForm pageForm) { + return Result.success(inclusiveBenefitService.pageData(pageForm)); + } + + @At + @SaCheckPermission("inclusive.benefit") + public Result findOne(@Param("id") String id) { + return Result.success(inclusiveBenefitService.findVO(id, false)); + } + + @At + @Aop(TransAop.READ_COMMITTED) + @SaCheckPermission("inclusive.benefit") + public Result save(InclusiveBenefit benefit, @Param("status") Integer status) { + if (benefit == null || StrUtil.isBlank(benefit.getTitle())) { + return Result.error("标题不能为空"); + } + if (StrUtil.isBlank(benefit.getTagType())) { + return Result.error("标签类型不能为空"); + } + inclusiveBenefitService.saveBenefit(benefit, status); + return Result.success(); + } + + @At + @Aop(TransAop.READ_COMMITTED) + @SaCheckPermission("inclusive.benefit") + public Result updateStatus(@Param("id") String id, @Param("status") Integer status) { + inclusiveBenefitService.updateStatus(id, status); + return Result.success(); + } + + @At + @Aop(TransAop.READ_COMMITTED) + @SaCheckPermission("inclusive.benefit") + public Result delete(@Param("id") String id) { + inclusiveBenefitService.deleteBenefit(id); + return Result.success(); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/inclusive/h5Controller/H5InclusiveBenefitController.java b/src/main/java/com/budwk/app/zhgh/inclusive/h5Controller/H5InclusiveBenefitController.java new file mode 100644 index 0000000..2878267 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/inclusive/h5Controller/H5InclusiveBenefitController.java @@ -0,0 +1,55 @@ +package com.budwk.app.zhgh.inclusive.h5Controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.budwk.app.base.result.Result; +import com.budwk.app.zhgh.inclusive.param.InclusiveBenefitPageForm; +import com.budwk.app.zhgh.inclusive.service.InclusiveBenefitService; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; + +import javax.validation.Valid; + +/** + * H5 端南航专惠浏览。 + */ +@IocBean +@Ok("json:full") +@At("/platform/h5/inclusive") +public class H5InclusiveBenefitController { + + @Inject + private InclusiveBenefitService inclusiveBenefitService; + + @At("") + @Ok("beetl:/platform/zhghh5/inclusive/index.html") + @SaCheckPermission("h5.inclusive.benefit") + public void index() { + } + + @At("/detail") + @Ok("beetl:/platform/zhghh5/inclusive/detail.html") + @SaCheckPermission("h5.inclusive.benefit") + public void detail() { + } + + @At + @SaCheckPermission("h5.inclusive.benefit") + public Result pageData(@Valid InclusiveBenefitPageForm pageForm) { + return Result.success(inclusiveBenefitService.pageData(pageForm)); + } + + @At + @SaCheckPermission("h5.inclusive.benefit") + public Result findOne(@Param("id") String id) { + return Result.success(inclusiveBenefitService.findVO(id, true)); + } + + @At + @SaCheckPermission("h5.inclusive.benefit") + public Result tagOptions() { + return Result.success(inclusiveBenefitService.tagOptions()); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/inclusive/model/InclusiveBenefit.java b/src/main/java/com/budwk/app/zhgh/inclusive/model/InclusiveBenefit.java new file mode 100644 index 0000000..ef47657 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/inclusive/model/InclusiveBenefit.java @@ -0,0 +1,82 @@ +package com.budwk.app.zhgh.inclusive.model; + +import com.budwk.app.base.model.BaseModel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.nutz.dao.entity.annotation.ColDefine; +import org.nutz.dao.entity.annotation.ColType; +import org.nutz.dao.entity.annotation.Column; +import org.nutz.dao.entity.annotation.Comment; +import org.nutz.dao.entity.annotation.EL; +import org.nutz.dao.entity.annotation.Name; +import org.nutz.dao.entity.annotation.Table; +import org.nutz.dao.interceptor.annotation.PrevInsert; + +import java.io.Serializable; + +/** + * 南航专惠内容。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Table("inclusive_benefit") +@Comment("南航专惠") +public class InclusiveBenefit extends BaseModel implements Serializable { + + @Name + @Column + @Comment("主键") + @ColDefine(type = ColType.VARCHAR, width = 32) + @PrevInsert(els = @EL("uuid()")) + private String id; + + @Column + @Comment("年度") + @ColDefine(type = ColType.INT) + private Integer year; + + @Column + @Comment("标题") + @ColDefine(type = ColType.VARCHAR, width = 100) + private String title; + + @Column + @Comment("摘要") + @ColDefine(type = ColType.VARCHAR, width = 500) + private String summary; + + @Column + @Comment("标签类型字典编码") + @ColDefine(type = ColType.VARCHAR, width = 50) + private String tagType; + + @Column + @Comment("富文本内容") + @ColDefine(type = ColType.TEXT) + private String content; + + @Column + @Comment("性别限制:0全部开放,1男性,2女性") + @ColDefine(type = ColType.INT) + private Integer sexLimit; + + @Column + @Comment("封面图") + @ColDefine(type = ColType.VARCHAR, width = 255) + private String cover; + + @Column + @Comment("外链地址") + @ColDefine(type = ColType.VARCHAR, width = 500) + private String linkUrl; + + @Column + @Comment("发布状态:0草稿,1已发布") + @ColDefine(type = ColType.INT) + private Integer status; + + @Column + @Comment("发布时间") + @ColDefine(type = ColType.VARCHAR, width = 20) + private String publishTime; +} diff --git a/src/main/java/com/budwk/app/zhgh/inclusive/param/InclusiveBenefitPageForm.java b/src/main/java/com/budwk/app/zhgh/inclusive/param/InclusiveBenefitPageForm.java new file mode 100644 index 0000000..837521b --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/inclusive/param/InclusiveBenefitPageForm.java @@ -0,0 +1,16 @@ +package com.budwk.app.zhgh.inclusive.param; + +import com.budwk.app.base.param.PageForm; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 南航专惠分页查询参数。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class InclusiveBenefitPageForm extends PageForm { + private Integer year; + private String title; + private String tagType; +} diff --git a/src/main/java/com/budwk/app/zhgh/inclusive/service/InclusiveBenefitService.java b/src/main/java/com/budwk/app/zhgh/inclusive/service/InclusiveBenefitService.java new file mode 100644 index 0000000..84f96d1 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/inclusive/service/InclusiveBenefitService.java @@ -0,0 +1,28 @@ +package com.budwk.app.zhgh.inclusive.service; + +import com.budwk.app.base.page.Pagination; +import com.budwk.app.base.service.BaseService; +import com.budwk.app.zhgh.inclusive.model.InclusiveBenefit; +import com.budwk.app.zhgh.inclusive.param.InclusiveBenefitPageForm; +import com.budwk.app.zhgh.inclusive.vo.InclusiveBenefitVO; +import org.nutz.lang.util.NutMap; + +import java.util.List; + +/** + * 南航专惠业务服务。 + */ +public interface InclusiveBenefitService extends BaseService { + + Pagination pageData(InclusiveBenefitPageForm pageForm); + + InclusiveBenefitVO findVO(String id, boolean onlyPublished); + + List tagOptions(); + + void saveBenefit(InclusiveBenefit benefit, Integer status); + + void updateStatus(String id, Integer status); + + void deleteBenefit(String id); +} diff --git a/src/main/java/com/budwk/app/zhgh/inclusive/service/impl/InclusiveBenefitServiceImpl.java b/src/main/java/com/budwk/app/zhgh/inclusive/service/impl/InclusiveBenefitServiceImpl.java new file mode 100644 index 0000000..ad55d39 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/inclusive/service/impl/InclusiveBenefitServiceImpl.java @@ -0,0 +1,170 @@ +package com.budwk.app.zhgh.inclusive.service.impl; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.budwk.app.base.page.Pagination; +import com.budwk.app.base.service.impl.BaseServiceImpl; +import com.budwk.app.sys.models.Sys_dict; +import com.budwk.app.zhgh.inclusive.model.InclusiveBenefit; +import com.budwk.app.zhgh.inclusive.param.InclusiveBenefitPageForm; +import com.budwk.app.zhgh.inclusive.service.InclusiveBenefitService; +import com.budwk.app.zhgh.inclusive.vo.InclusiveBenefitVO; +import org.nutz.dao.Chain; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.lang.Strings; +import org.nutz.lang.util.NutMap; +import org.nutz.ioc.loader.annotation.IocBean; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 南航专惠业务实现。 + */ +@IocBean(args = {"refer:dao"}) +public class InclusiveBenefitServiceImpl extends BaseServiceImpl implements InclusiveBenefitService { + + private static final String TAG_DICT_CODE = "INCLUSIVE_BENEFIT_TYPE"; + + public InclusiveBenefitServiceImpl(Dao dao) { + super(dao); + } + + @Override + public Pagination pageData(InclusiveBenefitPageForm pageForm) { + Cnd cnd = Cnd.where("delFlag", "=", false); + if (pageForm.getYear() != null) { + cnd.and("year", "=", pageForm.getYear()); + } + if (Strings.isNotBlank(pageForm.getTitle())) { + cnd.and(Cnd.likeEX("title", pageForm.getTitle())); + } + if (Strings.isNotBlank(pageForm.getTagType())) { + cnd.and("tagType", "=", pageForm.getTagType()); + } + cnd.desc("publishTime").desc("createdAt"); + Pagination pagination = listPage(pageForm.getPageNumber(), pageForm.getPageSize(), cnd); + Map tagNameMap = getTagNameMap(); + List list = pagination.getList().stream() + .map(benefit -> toVO(benefit, tagNameMap)) + .collect(Collectors.toList()); + return new Pagination<>(pagination.getPageNo(), pagination.getPageSize(), pagination.getTotalCount(), list); + } + + @Override + public InclusiveBenefitVO findVO(String id, boolean onlyPublished) { + Cnd cnd = Cnd.where("id", "=", id).and("delFlag", "=", false); + if (onlyPublished) { + cnd.and("status", "=", 1); + } + return toVO(fetch(cnd), getTagNameMap()); + } + + @Override + public List tagOptions() { + Sys_dict parent = dao().fetch(Sys_dict.class, Cnd.where("code", "=", TAG_DICT_CODE).and("delFlag", "=", false)); + if (parent == null) { + return Collections.emptyList(); + } + return dao().query(Sys_dict.class, Cnd.where("parentId", "=", parent.getId()) + .and("disabled", "=", false).and("delFlag", "=", false).asc("location")) + .stream() + .map(dict -> NutMap.NEW().addv("text", dict.getName()).addv("value", dict.getCode())) + .collect(Collectors.toList()); + } + + @Override + public void saveBenefit(InclusiveBenefit benefit, Integer status) { + InclusiveBenefit target; + if (StrUtil.isBlank(benefit.getId())) { + target = new InclusiveBenefit(); + target.setYear(benefit.getYear() == null ? DateUtil.year(DateUtil.date()) : benefit.getYear()); + } else { + target = fetch(Cnd.where("id", "=", benefit.getId()).and("delFlag", "=", false)); + if (target == null) { + throw new IllegalArgumentException("南航专惠不存在或已删除"); + } + target.setYear(benefit.getYear() == null ? target.getYear() : benefit.getYear()); + } + target.setTitle(benefit.getTitle()); + target.setSummary(benefit.getSummary()); + target.setTagType(benefit.getTagType()); + target.setContent(benefit.getContent()); + target.setSexLimit(benefit.getSexLimit() == null ? 0 : benefit.getSexLimit()); + target.setCover(benefit.getCover()); + target.setLinkUrl(benefit.getLinkUrl()); + target.setStatus(status == null ? 0 : status); + if (Integer.valueOf(1).equals(target.getStatus())) { + target.setPublishTime(DateUtil.now()); + } + if (StrUtil.isBlank(target.getId())) { + insert(target); + } else { + update(target); + } + } + + @Override + public void updateStatus(String id, Integer status) { + InclusiveBenefit benefit = fetch(Cnd.where("id", "=", id).and("delFlag", "=", false)); + if (benefit == null) { + throw new IllegalArgumentException("南航专惠不存在或已删除"); + } + Integer targetStatus = status == null ? 0 : status; + Chain chain = Chain.make("status", targetStatus); + if (Integer.valueOf(1).equals(targetStatus)) { + chain.add("publishTime", DateUtil.now()); + } + update(chain, Cnd.where("id", "=", id)); + } + + @Override + public void deleteBenefit(String id) { + InclusiveBenefit benefit = fetch(Cnd.where("id", "=", id).and("delFlag", "=", false)); + if (benefit == null) { + throw new IllegalArgumentException("南航专惠不存在或已删除"); + } + vDelete(id); + } + + private InclusiveBenefitVO toVO(InclusiveBenefit benefit, Map tagNameMap) { + if (benefit == null) { + return null; + } + return new InclusiveBenefitVO() + .setId(benefit.getId()) + .setYear(benefit.getYear()) + .setTitle(benefit.getTitle()) + .setSummary(benefit.getSummary()) + .setTagType(benefit.getTagType()) + .setTagTypeName(tagNameMap.get(benefit.getTagType())) + .setContent(benefit.getContent()) + .setSexLimit(benefit.getSexLimit()) + .setSexLimitName(getSexLimitName(benefit.getSexLimit())) + .setCover(benefit.getCover()) + .setLinkUrl(benefit.getLinkUrl()) + .setStatus(benefit.getStatus()) + .setStatusName(Integer.valueOf(1).equals(benefit.getStatus()) ? "已发布" : "草稿") + .setCreatedAt(benefit.getCreatedAt()) + .setUpdatedAt(benefit.getUpdatedAt()) + .setPublishTime(benefit.getPublishTime()); + } + + private Map getTagNameMap() { + List options = tagOptions(); + return options.stream().collect(Collectors.toMap(option -> option.getString("value"), option -> option.getString("text"))); + } + + private String getSexLimitName(Integer sexLimit) { + if (Integer.valueOf(1).equals(sexLimit)) { + return "男性"; + } + if (Integer.valueOf(2).equals(sexLimit)) { + return "女性"; + } + return "全部开放"; + } +} diff --git a/src/main/java/com/budwk/app/zhgh/inclusive/vo/InclusiveBenefitVO.java b/src/main/java/com/budwk/app/zhgh/inclusive/vo/InclusiveBenefitVO.java new file mode 100644 index 0000000..df1bacb --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/inclusive/vo/InclusiveBenefitVO.java @@ -0,0 +1,28 @@ +package com.budwk.app.zhgh.inclusive.vo; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 南航专惠列表和详情展示对象。 + */ +@Data +@Accessors(chain = true) +public class InclusiveBenefitVO { + private String id; + private Integer year; + private String title; + private String summary; + private String tagType; + private String tagTypeName; + private String content; + private Integer sexLimit; + private String sexLimitName; + private String cover; + private String linkUrl; + private Integer status; + private String statusName; + private Long createdAt; + private Long updatedAt; + private String publishTime; +} diff --git a/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/constants/DifficultSubsidyState.java b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/constants/DifficultSubsidyState.java new file mode 100644 index 0000000..3230c56 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/constants/DifficultSubsidyState.java @@ -0,0 +1,15 @@ +package com.budwk.app.zhgh.staffbenefit.difficultSubsidy.constants; + +/** + * 困难补助申请的业务状态。 + * + * 仅用于草稿及流程结果的业务快照;审批中的真实状态以 TKBZ 工作流实例为准。 + */ +public interface DifficultSubsidyState { + + int TO_BE_SUBMITTED = 7000; + int IN_PROCESS = 7010; + int SCHOOL_REFUSE = 7050; + int SCHOOL_BACK = 7060; + int PASS = 7070; +} diff --git a/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/controller/DifficultSubsidyCommonController.java b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/controller/DifficultSubsidyCommonController.java new file mode 100644 index 0000000..f51b693 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/controller/DifficultSubsidyCommonController.java @@ -0,0 +1,42 @@ +package com.budwk.app.zhgh.staffbenefit.difficultSubsidy.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaMode; +import com.budwk.app.base.result.Result; +import com.budwk.app.zhgh.staffbenefit.difficultSubsidy.service.DifficultSubsidyService; +import io.swagger.annotations.ApiOperation; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; + +/** + * 困难补助的公共查询接口。 + */ +@IocBean +@At("/platform/difficultSubsidy/common") +@Ok("json") +public class DifficultSubsidyCommonController { + + @Inject + private DifficultSubsidyService difficultSubsidyService; + + @At + @ApiOperation("查询申请详情及审核记录") + @SaCheckPermission(value = {"difficultSubsidy.apply", "difficultSubsidy.mine", + "difficultSubsidy.schoolAudit", "difficultSubsidy.query"}, mode = SaMode.OR) + public Result findOne(String id) { + return Result.success(difficultSubsidyService.findDetail(id)); + } + + @At + @ApiOperation("查询补助项目及疾病类型") + @SaCheckPermission(value = {"difficultSubsidy.apply", "difficultSubsidy.mine", + "difficultSubsidy.schoolAudit", "difficultSubsidy.query"}, mode = SaMode.OR) + public Result projectInfo() { + java.util.Map data = new java.util.HashMap<>(); + data.put("projectList", difficultSubsidyService.projectList()); + data.put("diseaseList", difficultSubsidyService.diseaseList(null, null)); + return Result.success(data); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/controller/basicSetting/DifficultSubsidyProjectController.java b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/controller/basicSetting/DifficultSubsidyProjectController.java new file mode 100644 index 0000000..30ef9b7 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/controller/basicSetting/DifficultSubsidyProjectController.java @@ -0,0 +1,70 @@ +package com.budwk.app.zhgh.staffbenefit.difficultSubsidy.controller.basicSetting; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.budwk.app.base.result.Result; +import com.budwk.app.zhgh.staffbenefit.difficultSubsidy.models.DifficultSubsidyDiseaseType; +import com.budwk.app.zhgh.staffbenefit.difficultSubsidy.models.DifficultSubsidyProject; +import com.budwk.app.zhgh.staffbenefit.difficultSubsidy.service.DifficultSubsidyService; +import io.swagger.annotations.ApiOperation; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; + +/** + * 困难补助项目与疾病类型配置。 + */ +@IocBean +@At("/platform/difficultSubsidy/basicSetting/project") +@Ok("json") +public class DifficultSubsidyProjectController { + + @Inject + private DifficultSubsidyService difficultSubsidyService; + + @At("") + @SaCheckPermission("difficultSubsidy.basicSetting.project") + @Ok("beetl:/platform/zhgh/staffbenefit/difficultSubsidy/basicSetting/project/index.html") + public void index() { + } + + @At + @ApiOperation("补助项目列表") + @SaCheckPermission("difficultSubsidy.basicSetting.project") + public Result projectList() { + return Result.success(difficultSubsidyService.projectList()); + } + + @At + @SaCheckPermission("difficultSubsidy.basicSetting.project") + public Result saveProject(@Param("data") DifficultSubsidyProject project) { + return Result.success(difficultSubsidyService.saveProject(project)); + } + + @At + @SaCheckPermission("difficultSubsidy.basicSetting.project") + public Result deleteProject(Integer id) { + difficultSubsidyService.deleteProject(id); + return Result.success(); + } + + @At + @SaCheckPermission("difficultSubsidy.basicSetting.project") + public Result diseaseList(Integer projectId, String keyword) { + return Result.success(difficultSubsidyService.diseaseList(projectId, keyword)); + } + + @At + @SaCheckPermission("difficultSubsidy.basicSetting.project") + public Result saveDisease(@Param("data") DifficultSubsidyDiseaseType diseaseType) { + return Result.success(difficultSubsidyService.saveDisease(diseaseType)); + } + + @At + @SaCheckPermission("difficultSubsidy.basicSetting.project") + public Result deleteDisease(Integer id) { + difficultSubsidyService.deleteDisease(id); + return Result.success(); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/controller/process/DifficultSubsidyApplyController.java b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/controller/process/DifficultSubsidyApplyController.java new file mode 100644 index 0000000..d0fe4eb --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/controller/process/DifficultSubsidyApplyController.java @@ -0,0 +1,67 @@ +package com.budwk.app.zhgh.staffbenefit.difficultSubsidy.controller.process; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.budwk.app.base.result.Result; +import com.budwk.app.zhgh.staffbenefit.difficultSubsidy.models.DifficultSubsidyApply; +import com.budwk.app.zhgh.staffbenefit.difficultSubsidy.service.DifficultSubsidyService; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; + +/** + * 特困补助申请表。 + */ +@IocBean +@At("/platform/difficultSubsidy/apply") +@Ok("json") +public class DifficultSubsidyApplyController { + + @Inject + private DifficultSubsidyService difficultSubsidyService; + + @At("") + @SaCheckPermission("difficultSubsidy.apply") + @Ok("beetl:/platform/zhgh/staffbenefit/difficultSubsidy/process/apply/index.html") + public void index() { + } + + /** + * 保留旧的表单子路径,避免已有链接失效;正式菜单入口仍为 /apply。 + */ + @At("/form") + @SaCheckPermission("difficultSubsidy.apply") + @Ok("beetl:/platform/zhgh/staffbenefit/difficultSubsidy/process/apply/index.html") + public void form() { + } + + @At + @Ok("json") + @SaCheckPermission("difficultSubsidy.apply") + public Result save(@Param("data") DifficultSubsidyApply apply) { + return Result.success(difficultSubsidyService.saveDraft(apply)); + } + + @At + @Ok("json") + @SaCheckPermission("difficultSubsidy.apply") + public Result submit(@Param("data") DifficultSubsidyApply apply) { + return Result.success(difficultSubsidyService.submit(apply)); + } + + /** + * 查询当前登录人有权选择的补助申请人。 + */ + @At + @SaCheckPermission("difficultSubsidy.apply") + public Result queryRecipients(String key) { + return Result.success(difficultSubsidyService.listRecipients(key)); + } + + @At + @SaCheckPermission("difficultSubsidy.apply") + public Result findOne(String id) { + return Result.success(difficultSubsidyService.findDetail(id)); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/controller/process/DifficultSubsidyMineController.java b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/controller/process/DifficultSubsidyMineController.java new file mode 100644 index 0000000..e9ddca8 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/controller/process/DifficultSubsidyMineController.java @@ -0,0 +1,53 @@ +package com.budwk.app.zhgh.staffbenefit.difficultSubsidy.controller.process; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.budwk.app.base.result.Result; +import com.budwk.app.base.utils.CommonDownloadUtil; +import com.budwk.app.web.commons.auth.utils.SecurityUtil; +import com.budwk.app.zhgh.staffbenefit.difficultSubsidy.service.DifficultSubsidyService; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; + +import javax.servlet.http.HttpServletResponse; + +/** + * 当前登录人的特困补助申请记录。 + */ +@IocBean +@At("/platform/difficultSubsidy/mine") +@Ok("json") +public class DifficultSubsidyMineController { + + @Inject + private DifficultSubsidyService difficultSubsidyService; + + @At("") + @SaCheckPermission("difficultSubsidy.mine") + @Ok("beetl:/platform/zhgh/staffbenefit/difficultSubsidy/process/mine/index.html") + public void index() { + } + + @At + @SaCheckPermission("difficultSubsidy.mine") + public Result pageData(Integer pageNumber, Integer pageSize, Integer year) { + return Result.success(difficultSubsidyService.pageMine( + pageNumber, pageSize, SecurityUtil.getUserId(), year)); + } + + @At + @SaCheckPermission("difficultSubsidy.mine") + public Result delete(String id) { + difficultSubsidyService.deleteMineApplication(id, SecurityUtil.getUserId()); + return Result.success(); + } + + @At + @Ok("void") + @SaCheckPermission("difficultSubsidy.mine") + public void exportApplyInfo(String id, HttpServletResponse response) { + CommonDownloadUtil.download("特困补助申请登记表.docx", + difficultSubsidyService.exportApplyWord(id), response); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/controller/process/DifficultSubsidyQueryController.java b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/controller/process/DifficultSubsidyQueryController.java new file mode 100644 index 0000000..b0a5424 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/controller/process/DifficultSubsidyQueryController.java @@ -0,0 +1,57 @@ +package com.budwk.app.zhgh.staffbenefit.difficultSubsidy.controller.process; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.budwk.app.base.result.Result; +import com.budwk.app.base.utils.CommonDownloadUtil; +import com.budwk.app.zhgh.staffbenefit.difficultSubsidy.service.DifficultSubsidyService; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; + +import javax.servlet.http.HttpServletResponse; + +/** + * 已通过特困补助综合查询与汇总导出。 + */ +@IocBean +@At("/platform/difficultSubsidy/query") +@Ok("json") +public class DifficultSubsidyQueryController { + + @Inject + private DifficultSubsidyService difficultSubsidyService; + + @At("") + @SaCheckPermission("difficultSubsidy.query") + @Ok("beetl:/platform/zhgh/staffbenefit/difficultSubsidy/query/index.html") + public void index() { + } + + @At + @SaCheckPermission("difficultSubsidy.query") + public Result pageData(Integer pageNumber, Integer pageSize, + Integer year, String startMonth, String endMonth, + String searchKeyword, String unionId, String unitId) { + return Result.success(difficultSubsidyService.pageQuery(pageNumber, pageSize, + year, startMonth, endMonth, searchKeyword, unionId, unitId)); + } + + @At + @Ok("void") + @SaCheckPermission("difficultSubsidy.query") + public void exportSummaryExcel(Integer year, String startMonth, String endMonth, + HttpServletResponse response) { + CommonDownloadUtil.download("特困补助人员名单.xlsx", + difficultSubsidyService.exportSummaryExcel(year, startMonth, endMonth), response); + } + + @At + @Ok("void") + @SaCheckPermission("difficultSubsidy.query") + public void exportSummary(Integer year, String startMonth, String endMonth, + HttpServletResponse response) { + CommonDownloadUtil.download("特困补助汇总表.docx", + difficultSubsidyService.exportSummaryWord(year, startMonth, endMonth), response); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/controller/process/DifficultSubsidySchoolAuditController.java b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/controller/process/DifficultSubsidySchoolAuditController.java new file mode 100644 index 0000000..0759426 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/controller/process/DifficultSubsidySchoolAuditController.java @@ -0,0 +1,100 @@ +package com.budwk.app.zhgh.staffbenefit.difficultSubsidy.controller.process; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.budwk.app.base.result.Result; +import com.budwk.app.base.utils.CommonDownloadUtil; +import com.budwk.app.web.commons.auth.utils.SecurityUtil; +import com.budwk.app.zhgh.staffbenefit.difficultSubsidy.models.DifficultSubsidyApply; +import com.budwk.app.zhgh.staffbenefit.difficultSubsidy.service.DifficultSubsidyService; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; + +import javax.servlet.http.HttpServletResponse; + +/** + * 特困补助校工会审核。 + */ +@IocBean +@At("/platform/difficultSubsidy/schoolAudit") +@Ok("json") +public class DifficultSubsidySchoolAuditController { + + @Inject + private DifficultSubsidyService difficultSubsidyService; + + @At("") + @SaCheckPermission("difficultSubsidy.schoolAudit") + @Ok("beetl:/platform/zhgh/staffbenefit/difficultSubsidy/process/schoolAudit/index.html") + public void index() { + } + + /** + * 复用申请表页面展示校工会修改表单,实际保存使用校工会专用接口。 + */ + @At("/edit") + @SaCheckPermission("difficultSubsidy.schoolAudit") + @Ok("beetl:/platform/zhgh/staffbenefit/difficultSubsidy/process/apply/index.html") + public void edit() { + } + + @At + @SaCheckPermission("difficultSubsidy.schoolAudit") + public Result pageData(Integer pageNumber, Integer pageSize, + Integer year, String startMonth, String endMonth, + String searchKeyword, String unionId, String unitId, Integer isAudit) { + return Result.success(difficultSubsidyService.pageSchoolAudit(pageNumber, pageSize, + SecurityUtil.getUserId(), year, startMonth, endMonth, + searchKeyword, unionId, unitId, isAudit)); + } + + @At + @SaCheckPermission("difficultSubsidy.schoolAudit") + public Result calculateSubsidy(String id) { + return Result.success(difficultSubsidyService.calculateSubsidy(id)); + } + + /** + * 修改校工会待审核申请的基础表单信息,不推进工作流任务。 + */ + @At + @SaCheckPermission("difficultSubsidy.schoolAudit") + public Result modifyApply(Long processTaskId, @Param("data") DifficultSubsidyApply apply) { + difficultSubsidyService.modifySchoolAuditApply(processTaskId, apply); + return Result.success(); + } + + @At + @SaCheckPermission("difficultSubsidy.schoolAudit") + public Result executeTask(Long processTaskId, Integer submitType, String opinion, + Float finalSubsidyAmount, String hospitalRecords) { + difficultSubsidyService.executeSchoolAudit(processTaskId, submitType, opinion, + finalSubsidyAmount, hospitalRecords); + return Result.success(); + } + + @At + @SaCheckPermission("difficultSubsidy.schoolAudit") + public Result revoke(Long processTaskId) { + difficultSubsidyService.revokeSchoolAudit(processTaskId, SecurityUtil.getUserId()); + return Result.success(); + } + + @At + @SaCheckPermission("difficultSubsidy.schoolAudit") + public Result modifyFinalSubsidyAmount(String id, Float finalSubsidyAmount) { + difficultSubsidyService.modifyFinalSubsidyAmount(id, finalSubsidyAmount); + return Result.success(); + } + + @At + @Ok("void") + @SaCheckPermission("difficultSubsidy.schoolAudit") + public void exportZip(Integer year, String startMonth, String endMonth, + HttpServletResponse response) { + CommonDownloadUtil.download("特困补助审核材料.zip", + difficultSubsidyService.exportApprovedZip(year, startMonth, endMonth), response); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/models/DifficultSubsidyApply.java b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/models/DifficultSubsidyApply.java new file mode 100644 index 0000000..71035d8 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/models/DifficultSubsidyApply.java @@ -0,0 +1,148 @@ +package com.budwk.app.zhgh.staffbenefit.difficultSubsidy.models; + +import com.budwk.app.base.model.BaseModel; +import cn.hutool.json.JSONObject; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.nutz.dao.entity.annotation.ColDefine; +import org.nutz.dao.entity.annotation.ColType; +import org.nutz.dao.entity.annotation.Column; +import org.nutz.dao.entity.annotation.Comment; +import org.nutz.dao.entity.annotation.Name; +import org.nutz.dao.interceptor.annotation.PrevInsert; +import org.nutz.dao.entity.annotation.Table; +import org.nutz.dao.entity.annotation.TableMeta; + +import java.util.Date; +import java.util.List; + +/** + * 困难补助申请。申请提交后通过特困补助工作流处理,流程实例以本记录主键作为业务编号关联。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Table("difficult_subsidy_apply") +@TableMeta("{'mysql-charset':'utf8mb4'}") +public class DifficultSubsidyApply extends BaseModel { + + @Name + @PrevInsert(uu32 = true) + @Comment("主键") + @ColDefine(type = ColType.VARCHAR, width = 32) + private String id; + + @Comment("申请人用户ID") + @Column + @ColDefine(type = ColType.VARCHAR, width = 32) + private String applyUserId; + + @Comment("申请人姓名") + @Column + @ColDefine(type = ColType.VARCHAR, width = 100) + private String applyUserName; + + @Comment("申请人工号") + @Column + @ColDefine(type = ColType.VARCHAR, width = 100) + private String applyLoginName; + + @Comment("受助人用户ID") + @Column + @ColDefine(type = ColType.VARCHAR, width = 32) + private String userId; + + @Comment("受助人姓名") + @Column + @ColDefine(type = ColType.VARCHAR, width = 100) + private String userName; + + @Comment("受助人工号") + @Column + @ColDefine(type = ColType.VARCHAR, width = 100) + private String loginName; + + @Comment("性别") + @Column + @ColDefine(type = ColType.VARCHAR, width = 10) + private String sex; + + @Comment("年龄") + @Column + @ColDefine(type = ColType.INT) + private Integer age; + + @Comment("单位ID") + @Column + @ColDefine(type = ColType.VARCHAR, width = 32) + private String unitId; + + @Comment("单位名称") + @Column + @ColDefine(type = ColType.VARCHAR, width = 500) + private String unitName; + + @Comment("分工会ID") + @Column + @ColDefine(type = ColType.VARCHAR, width = 32) + private String unionId; + + @Comment("分工会名称") + @Column + @ColDefine(type = ColType.VARCHAR, width = 500) + private String unionName; + + @Comment("家庭住址") + @Column + @ColDefine(type = ColType.VARCHAR, width = 500) + private String homeAddress; + + @Comment("联系电话") + @Column + @ColDefine(type = ColType.VARCHAR, width = 50) + private String mobile; + + @Comment("补助项目ID集合") + @Column + @ColDefine(type = ColType.MYSQL_JSON) + private List subsidyType; + + @Comment("申请补助原因") + @Column + @ColDefine(type = ColType.VARCHAR, width = 1000) + private String reason; + + @Comment("证明材料") + @Column + @ColDefine(type = ColType.MYSQL_JSON) + private List files; + + @Comment("纸质申请表") + @Column + @ColDefine(type = ColType.MYSQL_JSON) + private List applyFile; + + @Comment("住院记录") + @Column + @ColDefine(type = ColType.MYSQL_JSON) + private List hospitalRecords; + + @Comment("申请时间") + @Column + @ColDefine(type = ColType.DATETIME) + private Date applyDate; + + @Comment("审核状态") + @Column + @ColDefine(type = ColType.INT) + private Integer stateId; + + @Comment("建议补助金额") + @Column + @ColDefine(type = ColType.FLOAT, width = 10) + private Float subsidyAmount; + + @Comment("最终补助金额") + @Column + @ColDefine(type = ColType.FLOAT, width = 10) + private Float finalSubsidyAmount; +} diff --git a/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/models/DifficultSubsidyDiseaseType.java b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/models/DifficultSubsidyDiseaseType.java new file mode 100644 index 0000000..1bbdd3c --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/models/DifficultSubsidyDiseaseType.java @@ -0,0 +1,42 @@ +package com.budwk.app.zhgh.staffbenefit.difficultSubsidy.models; + +import com.budwk.app.base.model.BaseModel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.nutz.dao.entity.annotation.ColDefine; +import org.nutz.dao.entity.annotation.ColType; +import org.nutz.dao.entity.annotation.Column; +import org.nutz.dao.entity.annotation.Comment; +import org.nutz.dao.entity.annotation.Id; +import org.nutz.dao.entity.annotation.Table; +import org.nutz.dao.entity.annotation.TableMeta; + +/** + * 困难补助项目下的疾病及金额上限配置。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Table("difficult_subsidy_disease_type") +@TableMeta("{'mysql-charset':'utf8mb4'}") +public class DifficultSubsidyDiseaseType extends BaseModel { + + @Id(auto = true) + @Comment("主键") + @ColDefine(type = ColType.INT) + private Integer id; + + @Comment("补助项目ID") + @Column + @ColDefine(type = ColType.INT) + private Integer projectId; + + @Comment("疾病名称") + @Column + @ColDefine(type = ColType.VARCHAR, width = 200) + private String diseaseName; + + @Comment("补助金额上限") + @Column + @ColDefine(type = ColType.VARCHAR, width = 50) + private String subsidyAmountUpperLimit; +} diff --git a/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/models/DifficultSubsidyProject.java b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/models/DifficultSubsidyProject.java new file mode 100644 index 0000000..2601c39 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/models/DifficultSubsidyProject.java @@ -0,0 +1,41 @@ +package com.budwk.app.zhgh.staffbenefit.difficultSubsidy.models; + +import com.budwk.app.base.model.BaseModel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.nutz.dao.entity.annotation.ColDefine; +import org.nutz.dao.entity.annotation.ColType; +import org.nutz.dao.entity.annotation.Column; +import org.nutz.dao.entity.annotation.Comment; +import org.nutz.dao.entity.annotation.Id; +import org.nutz.dao.entity.annotation.Many; +import org.nutz.dao.entity.annotation.Table; +import org.nutz.dao.entity.annotation.TableMeta; + +import java.util.List; + +/** + * 困难补助项目配置。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Table("difficult_subsidy_project") +@TableMeta("{'mysql-charset':'utf8mb4'}") +public class DifficultSubsidyProject extends BaseModel { + + @Id(auto = true) + @Comment("主键") + @ColDefine(type = ColType.INT) + private Integer id; + + @Comment("补助项目名称") + @Column + @ColDefine(type = ColType.VARCHAR, width = 100) + private String projectName; + + /** + * 当前补助范围下的疾病或事项说明,仅用于关联查询,不单独落入项目表。 + */ + @Many(target = DifficultSubsidyDiseaseType.class, field = "projectId", key = "id") + private List diseaseTypeList; +} diff --git a/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/service/DifficultSubsidyService.java b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/service/DifficultSubsidyService.java new file mode 100644 index 0000000..e6b1775 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/service/DifficultSubsidyService.java @@ -0,0 +1,105 @@ +package com.budwk.app.zhgh.staffbenefit.difficultSubsidy.service; + +import com.budwk.app.base.page.Pagination; +import com.budwk.app.base.service.BaseService; +import com.budwk.app.zhgh.staffbenefit.difficultSubsidy.models.DifficultSubsidyApply; +import com.budwk.app.zhgh.staffbenefit.difficultSubsidy.models.DifficultSubsidyDiseaseType; +import com.budwk.app.zhgh.staffbenefit.difficultSubsidy.models.DifficultSubsidyProject; +import org.apache.poi.ss.usermodel.Workbook; +import org.nutz.lang.util.NutMap; + +import java.util.List; + +/** + * 特困补助申请、审核、查询、配置和导出公共业务服务。 + */ +public interface DifficultSubsidyService extends BaseService { + + DifficultSubsidyApply saveDraft(DifficultSubsidyApply apply); + + DifficultSubsidyApply submit(DifficultSubsidyApply apply); + + /** + * 按当前用户的数据权限查询可选择的补助申请人。 + * + * @param keyword 姓名或工号关键字 + * @return 最多十条申请人基础信息 + */ + List listRecipients(String keyword); + + /** + * 查询当前登录人的申请记录及最新工作流状态。 + */ + Pagination pageMine(Integer pageNumber, Integer pageSize, String applyUserId, Integer year); + + /** + * 查询当前审核人的校工会待办或已办记录,申请年份和月份范围共同约束申请时间。 + */ + Pagination pageSchoolAudit(Integer pageNumber, Integer pageSize, String auditorId, + Integer year, String startMonth, String endMonth, + String searchKeyword, String unionId, + String unitId, Integer isAudit); + + /** + * 查询指定申请年份、月份范围内最终通过的补助申请。 + */ + Pagination pageQuery(Integer pageNumber, Integer pageSize, Integer year, + String startMonth, String endMonth, + String searchKeyword, String unionId, String unitId); + + /** + * 返回申请、项目配置和工作流审核记录组成的完整查看数据。 + */ + NutMap findDetail(String id); + + List projectList(); + + List diseaseList(Integer projectId, String keyword); + + DifficultSubsidyProject saveProject(DifficultSubsidyProject project); + + DifficultSubsidyDiseaseType saveDisease(DifficultSubsidyDiseaseType diseaseType); + + void deleteProject(Integer id); + + void deleteDisease(Integer id); + + /** + * 删除当前登录人的特困补助申请,并同步清理该申请对应的流程实例、任务及待办办理人。 + * + * @param id 申请记录ID + * @param applyUserId 当前登录用户ID,用于校验申请归属 + */ + void deleteMineApplication(String id, String applyUserId); + + /** + * 校工会审核期间修改申请基础表单信息,不变更申请人、受助人身份或流程状态。 + * + * @param processTaskId 当前校工会审核待办任务ID + * @param apply 页面提交的申请基础表单数据 + */ + void modifySchoolAuditApply(Long processTaskId, DifficultSubsidyApply apply); + + /** + * 保存逐条补助项目审核结果与金额,并执行校工会工作流任务。 + */ + void executeSchoolAudit(Long processTaskId, Integer submitType, String opinion, + Float finalSubsidyAmount, String hospitalRecords); + + /** + * 撤销本人已经完成的校工会审核任务并重新激活该节点。 + */ + void revokeSchoolAudit(Long processTaskId, String auditorId); + + void modifyFinalSubsidyAmount(String id, Float finalSubsidyAmount); + + NutMap calculateSubsidy(String id); + + Workbook exportSummaryExcel(Integer year, String startMonth, String endMonth); + + byte[] exportSummaryWord(Integer year, String startMonth, String endMonth); + + byte[] exportApplyWord(String id); + + byte[] exportApprovedZip(Integer year, String startMonth, String endMonth); +} diff --git a/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/service/impl/DifficultSubsidySchemaInitializer.java b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/service/impl/DifficultSubsidySchemaInitializer.java new file mode 100644 index 0000000..0ab2acb --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/service/impl/DifficultSubsidySchemaInitializer.java @@ -0,0 +1,83 @@ +package com.budwk.app.zhgh.staffbenefit.difficultSubsidy.service.impl; + +import com.budwk.app.zhgh.staffbenefit.difficultSubsidy.models.DifficultSubsidyApply; +import com.budwk.app.zhgh.staffbenefit.difficultSubsidy.models.DifficultSubsidyDiseaseType; +import com.budwk.app.zhgh.staffbenefit.difficultSubsidy.models.DifficultSubsidyProject; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.dao.util.Daos; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; + +/** + * 特困补助模块表结构与基础补助范围初始化。 + * + * 项目公共启动器只在 DEBUG 日志级别下执行实体迁移,因此本模块单独按实体补齐自己的三张表, + * 不修改公共表,也不依赖额外 SQL 文件。基础数据仅在对应表为空时写入,避免覆盖管理员后续维护的数据。 + */ +@IocBean(create = "init") +public class DifficultSubsidySchemaInitializer { + + @Inject + private Dao dao; + + /** + * 根据实体补齐模块字段,并在全新配置表中写入参考项目的默认补助范围。 + */ + public void init() { + Daos.migration(dao, DifficultSubsidyApply.class, true, false); + Daos.migration(dao, DifficultSubsidyProject.class, true, false); + Daos.migration(dao, DifficultSubsidyDiseaseType.class, true, false); + initDefaultProjects(); + } + + /** + * 仅当项目和说明表都为空时初始化默认数据,显式保留编号以兼容参考项目的补助测算规则。 + */ + private void initDefaultProjects() { + if (dao.count(DifficultSubsidyProject.class) > 0 || dao.count(DifficultSubsidyDiseaseType.class) > 0) { + return; + } + insertProject(1, "重大疾病补助"); + insertProject(2, "门诊第二类特殊疾病补助"); + insertProject(3, "重大变故补助"); + insertProject(4, "住院护理补助"); + + insertDisease(1, 1, "重大器官移植术或造血干细胞移植术"); + insertDisease(2, 1, "恶性肿瘤"); + insertDisease(3, 1, "重型再生障碍性贫血"); + insertDisease(4, 1, "严重系统性红斑狼疮性肾病"); + insertDisease(5, 1, "持续植物人状态"); + insertDisease(6, 1, "其他自生病起本年度内自费超过5万的重大疾病"); + insertDisease(7, 2, "恶性肿瘤病人的补充放化疗及手术后门诊支持性治疗"); + insertDisease(8, 2, "慢性白血病"); + insertDisease(9, 2, "红斑狼疮"); + insertDisease(10, 2, "慢性肾功能衰竭的透析治疗"); + insertDisease(11, 2, "重大器官移植术后抗免疫排斥药物治疗"); + insertDisease(12, 2, "口服靶向药物治疗"); + insertDisease(13, 3, "本年度内,教职工家庭遭受地震、泥石流、火灾等自然灾害(财产损失价值20000元及以上),造成生活困难"); + insertDisease(14, 3, "本年度内,教职工本人或直系亲属(教职工子女、配偶和教职工本人父母)遭受突发性意外伤害(工伤等除外),个人承担治疗费用较大(减除保险、医保及责任方赔付以外20000元及以上),造成生活困难"); + insertDisease(15, 3, "本年度内,教职工本人或直系亲属(教职工子女、配偶和教职工本人父母)患严重疾病,教职工个人承担治疗费用较大(减除保险、医保以外20000元及以上),造成生活困难"); + insertDisease(18, 4, "普通疾病"); + insertDisease(19, 4, "重大疾病"); + } + + private void insertProject(Integer id, String name) { + if (dao.fetch(DifficultSubsidyProject.class, Cnd.where("id", "=", id)) == null) { + DifficultSubsidyProject project = new DifficultSubsidyProject(); + project.setId(id); + project.setProjectName(name); + dao.insert(project); + } + } + + private void insertDisease(Integer id, Integer projectId, String name) { + if (dao.fetch(DifficultSubsidyDiseaseType.class, Cnd.where("id", "=", id)) == null) { + DifficultSubsidyDiseaseType disease = new DifficultSubsidyDiseaseType(); + disease.setId(id); + disease.setProjectId(projectId); + disease.setDiseaseName(name); + dao.insert(disease); + } + } +} diff --git a/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/service/impl/DifficultSubsidyServiceImpl.java b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/service/impl/DifficultSubsidyServiceImpl.java new file mode 100644 index 0000000..682e7ed --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficultSubsidy/service/impl/DifficultSubsidyServiceImpl.java @@ -0,0 +1,1061 @@ +package com.budwk.app.zhgh.staffbenefit.difficultSubsidy.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.lang.Dict; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.budwk.app.base.utils.SysOfficeTemplateUtil; +import com.budwk.app.base.page.Pagination; +import com.budwk.app.base.service.impl.BaseServiceImpl; +import com.budwk.app.base.constant.RoleConstant; +import com.budwk.app.flow.constant.FlowConst; +import com.budwk.app.flow.engine.FlowEngine; +import com.budwk.app.flow.entity.ProcessDefine; +import com.budwk.app.flow.entity.ProcessCcInstance; +import com.budwk.app.flow.entity.ProcessInstance; +import com.budwk.app.flow.entity.ProcessTask; +import com.budwk.app.flow.entity.ProcessTaskActor; +import com.budwk.app.flow.enums.ProcessInstanceStateEnum; +import com.budwk.app.flow.enums.ProcessSubmitTypeEnum; +import com.budwk.app.flow.enums.ProcessTaskStateEnum; +import com.budwk.app.flow.service.FlowCommonService; +import com.budwk.app.flow.vo.ProcessTaskVO; +import com.budwk.app.sys.services.SysFileService; +import com.budwk.app.web.commons.auth.utils.SecurityUtil; +import com.budwk.app.web.commons.auth.utils.AuthUtil; +import com.budwk.app.zhgh.staffbenefit.difficultSubsidy.constants.DifficultSubsidyState; +import com.budwk.app.zhgh.staffbenefit.difficultSubsidy.models.DifficultSubsidyApply; +import com.budwk.app.zhgh.staffbenefit.difficultSubsidy.models.DifficultSubsidyDiseaseType; +import com.budwk.app.zhgh.staffbenefit.difficultSubsidy.models.DifficultSubsidyProject; +import com.budwk.app.zhgh.staffbenefit.difficultSubsidy.service.DifficultSubsidyService; +import com.deepoove.poi.XWPFTemplate; +import com.deepoove.poi.config.Configure; +import com.deepoove.poi.data.PictureRenderData; +import com.deepoove.poi.data.Pictures; +import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy; +import org.apache.poi.ss.usermodel.Workbook; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.dao.Chain; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.dao.Sqls; +import org.nutz.dao.sql.Sql; +import org.nutz.dao.util.cri.SqlExpressionGroup; +import org.nutz.ioc.aop.Aop; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.Lang; +import org.nutz.lang.Strings; +import org.nutz.lang.util.NutMap; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + * 特困补助申请、配置、审核、测算和导出服务实现。 + */ +@IocBean(args = {"refer:dao"}) +public class DifficultSubsidyServiceImpl extends BaseServiceImpl implements DifficultSubsidyService { + + private static final String PROCESS_KEY = "TKBZ"; + private static final String SCHOOL_AUDIT_TASK_NAME = "校工会审核"; + private static final String START_TASK_NAME = "startTask"; + + @Inject + private FlowEngine flowEngine; + + @Inject + private FlowCommonService flowCommonService; + + @Inject + private SysFileService sysFileService; + + @Inject + private SysOfficeTemplateUtil sysOfficeTemplateUtil; + + public DifficultSubsidyServiceImpl(Dao dao) { + super(dao); + } + + @Override + @Aop(TransAop.READ_COMMITTED) + public DifficultSubsidyApply saveDraft(DifficultSubsidyApply apply) { + fillApplicantSnapshot(apply); + normalizeFormCollections(apply); + if (apply.getApplyDate() == null) { + apply.setApplyDate(new Date()); + } + if (apply.getStateId() == null) { + apply.setStateId(DifficultSubsidyState.TO_BE_SUBMITTED); + } + dao().insertOrUpdate(apply); + return apply; + } + + @Override + public List listRecipients(String keyword) { + Sql sql = Sqls.create("SELECT id, loginname, username, birthday, mobile, sex, " + + "unitName, unionName, unionId, unitId FROM vw_user $condition"); + Cnd cnd = Cnd.NEW(); + + // 校工会管理员可选择全校人员,分工会管理员仅可选择本工会人员,普通用户仅可选择本人。 + if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) { + if (AuthUtil.hasRoleOr(RoleConstant.BRANCH_UNION_CHAIRMAN.name(), + RoleConstant.BRANCH_UNION_ADMIN.name(), RoleConstant.BRANCH_UNION_OPERATOR.name())) { + cnd.and("unionId", "=", SecurityUtil.getUnionId()); + } else { + cnd.and("id", "=", SecurityUtil.getUserId()); + } + } + if (Strings.isNotBlank(keyword)) { + SqlExpressionGroup keywordGroup = new SqlExpressionGroup(); + keywordGroup.orLike("username", keyword); + keywordGroup.orLike("loginname", keyword); + cnd.and(keywordGroup); + } + sql.setCondition(cnd); + return listPageMap(1, 10, sql).getList(); + } + + @Override + @Aop(TransAop.READ_COMMITTED) + public DifficultSubsidyApply submit(DifficultSubsidyApply apply) { + validateApplication(apply); + DifficultSubsidyApply saved = saveDraft(apply); + ProcessInstance activeInstance = latestActiveInstance(saved.getId()); + Dict args = buildSubmitArgs(saved); + + if (activeInstance != null) { + ProcessTask returnedStartTask = dao().fetch(ProcessTask.class, + Cnd.where(ProcessTask::getProcessInstanceId, "=", activeInstance.getId()) + .and(ProcessTask::getTaskName, "=", START_TASK_NAME) + .and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.DOING.getCode())); + if (returnedStartTask == null) { + throw new IllegalStateException("该申请正在校工会审核中,不能重复提交"); + } + flowEngine.executeProcessTask(returnedStartTask.getId(), SecurityUtil.getUserId(), args); + } else { + ProcessInstance instance = flowEngine.startProcessInstanceByKey( + PROCESS_KEY, saved.getId(), SecurityUtil.getUserId(), args); + List startTasks = flowEngine.processTaskService().getDoingTaskList(instance.getId(), null); + for (ProcessTask task : startTasks) { + if (START_TASK_NAME.equals(task.getTaskName())) { + flowEngine.executeProcessTask(task.getId(), SecurityUtil.getUserId(), args); + } + } + } + saved.setStateId(DifficultSubsidyState.IN_PROCESS); + dao().update(saved, "^(stateId|updatedBy|updatedAt)$"); + return saved; + } + + @Override + public Pagination pageMine(Integer pageNumber, Integer pageSize, String applyUserId, Integer year) { + Sql sql = Sqls.create("SELECT info.*, ins.id AS instanceId, ins.state AS instanceState, " + + "MAX(CASE WHEN t.taskName = 'startTask' THEN t.id END) AS startTaskId, " + + "MAX(CASE WHEN t.taskName = 'startTask' AND t.taskState = 10 THEN t.id END) AS returnedStartTaskId, " + // 草稿没有流程实例;已经发起但没有进行中任务时,按本项目其他流程页面显示为结束。 + + "CASE WHEN ins.id IS NULL THEN '未提交' ELSE IFNULL(GROUP_CONCAT(DISTINCT nt.displayName), '结束') END AS currentTaskName " + + "FROM difficult_subsidy_apply info " + + "LEFT JOIN wf_process_instance ins ON ins.id = (SELECT MAX(i2.id) FROM wf_process_instance i2 WHERE i2.businessNo = info.id) " + + "LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id " + + "LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10 " + + "$condition GROUP BY info.id ORDER BY info.applyDate DESC"); + Cnd cnd = Cnd.NEW(); + cnd.and("info.applyUserId", "=", applyUserId); + if (year != null) { + cnd.andEX("YEAR(info.applyDate)", "=", year); + } + sql.setCondition(cnd); + Pagination pagination = listPageMap(defaultPage(pageNumber), defaultPageSize(pageSize), sql); + normalizeListJsonFields(pagination); + return pagination; + } + + @Override + public Pagination pageSchoolAudit(Integer pageNumber, Integer pageSize, String auditorId, + Integer year, String startMonth, String endMonth, + String searchKeyword, String unionId, + String unitId, Integer isAudit) { + Sql sql = Sqls.create("SELECT info.*, ins.id AS instanceId, ins.state AS instanceState, " + + "t.id AS taskId, t.taskState, t.displayName AS taskName, t.finishTime, " + + "IFNULL(GROUP_CONCAT(DISTINCT nt.displayName), '结束') AS currentTaskName " + + "FROM wf_process_task t " + + "INNER JOIN wf_process_instance ins ON ins.id = t.processInstanceId " + + "INNER JOIN wf_process_define pd ON pd.id = ins.processDefineId " + + "INNER JOIN difficult_subsidy_apply info ON info.id = ins.businessNo " + + "INNER JOIN wf_process_task_actor ta ON ta.processTaskId = t.id " + + "LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10 " + + "$condition GROUP BY t.id ORDER BY t.createdAt DESC"); + Cnd cnd = Cnd.NEW(); + cnd.and("pd.name", "=", PROCESS_KEY); + cnd.and("t.displayName", "=", SCHOOL_AUDIT_TASK_NAME); + cnd.and("ta.actorId", "=", auditorId); + appendSearchCondition(cnd, "info.", year, startMonth, endMonth, + searchKeyword, unionId, unitId); + if (Objects.equals(isAudit, -1)) { + cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode()); + } else if (Objects.equals(isAudit, 1)) { + cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), + ProcessTaskStateEnum.WITHDRAW.getCode())); + } + sql.setCondition(cnd); + Pagination pagination = listPageMap(defaultPage(pageNumber), defaultPageSize(pageSize), sql); + normalizeListJsonFields(pagination); + return pagination; + } + + @Override + public Pagination pageQuery(Integer pageNumber, Integer pageSize, + Integer year, String startMonth, String endMonth, + String searchKeyword, + String unionId, String unitId) { + Cnd cnd = Cnd.NEW(); + appendSearchCondition(cnd, "", year, startMonth, endMonth, + searchKeyword, unionId, unitId); + cnd.and("stateId", "=", DifficultSubsidyState.PASS); + cnd.desc("applyDate"); + return listPage(defaultPage(pageNumber), defaultPageSize(pageSize), cnd); + } + + @Override + public NutMap findDetail(String id) { + DifficultSubsidyApply apply = dao().fetch(DifficultSubsidyApply.class, id); + if (apply == null) { + throw new IllegalArgumentException("申请记录不存在"); + } + normalizeFormCollections(apply); + NutMap data = Lang.obj2nutmap(apply); + ProcessInstance instance = latestInstance(id); + if (instance != null) { + data.addv("instanceId", instance.getId()); + data.addv("instanceState", instance.getState()); + // 详情页只展示已经实际办理完成的节点,避免把待办、撤回等任务误当成审核记录。 + List approvalRecords = flowEngine.processInstanceService().approvalRecord(instance.getId()) + .stream() + .filter(task -> Objects.equals(task.getTaskState(), ProcessTaskStateEnum.FINISHED.getCode())) + .filter(task -> task.getFinishTime() != null) + .collect(Collectors.toList()); + data.addv("approvalRecords", approvalRecords); + } else { + data.addv("approvalRecords", Collections.emptyList()); + } + return data; + } + + @Override + public List projectList() { + List projects = dao().query(DifficultSubsidyProject.class, Cnd.NEW().asc("id")); + dao().fetchLinks(projects, "diseaseTypeList"); + return projects; + } + + @Override + public List diseaseList(Integer projectId, String keyword) { + Cnd cnd = Cnd.NEW(); + if (projectId != null) { + cnd.and("projectId", "=", projectId); + } + if (Strings.isNotBlank(keyword)) { + cnd.and("diseaseName", "like", "%" + keyword + "%"); + } + cnd.asc("id"); + return dao().query(DifficultSubsidyDiseaseType.class, cnd); + } + + @Override + public DifficultSubsidyProject saveProject(DifficultSubsidyProject project) { + if (Strings.isBlank(project.getProjectName())) { + throw new IllegalArgumentException("补助范围名称不能为空"); + } + dao().insertOrUpdate(project); + return project; + } + + @Override + public DifficultSubsidyDiseaseType saveDisease(DifficultSubsidyDiseaseType diseaseType) { + if (diseaseType.getProjectId() == null || Strings.isBlank(diseaseType.getDiseaseName())) { + throw new IllegalArgumentException("补助范围和说明不能为空"); + } + dao().insertOrUpdate(diseaseType); + return diseaseType; + } + + @Override + @Aop(TransAop.READ_COMMITTED) + public void deleteProject(Integer id) { + if (dao().count(DifficultSubsidyDiseaseType.class, Cnd.where("projectId", "=", id)) > 0) { + throw new IllegalStateException("请先删除该补助范围下的说明"); + } + dao().delete(DifficultSubsidyProject.class, id); + } + + @Override + public void deleteDisease(Integer id) { + dao().delete(DifficultSubsidyDiseaseType.class, id); + } + + @Override + @Aop(TransAop.READ_COMMITTED) + public void deleteMineApplication(String id, String applyUserId) { + DifficultSubsidyApply apply = dao().fetch(DifficultSubsidyApply.class, id); + if (apply == null || !Objects.equals(applyUserId, apply.getApplyUserId())) { + throw new IllegalArgumentException("无权删除该申请"); + } + + ProcessInstance processInstance = dao().fetch(ProcessInstance.class, + Cnd.where(ProcessInstance::getBusinessNo, "=", id)); + if (processInstance != null) { + List processTasks = dao().query(ProcessTask.class, + Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstance.getId())); + List processTaskIds = processTasks.stream().map(ProcessTask::getId).collect(Collectors.toList()); + // 公共流程删除只处理任务和实例,这里先清除办理人和抄送数据,避免残留待办关联。 + if (!processTaskIds.isEmpty()) { + dao().clear(ProcessTaskActor.class, + Cnd.where(ProcessTaskActor::getProcessTaskId, "in", processTaskIds)); + } + dao().clear(ProcessCcInstance.class, + Cnd.where(ProcessCcInstance::getProcessInstanceId, "=", processInstance.getId())); + flowEngine.processInstanceService().deleteProcessInstanceByBusinessKey(id); + } + dao().delete(DifficultSubsidyApply.class, id); + } + + /** + * 校工会审核人仅可修改待审核申请的基础表单字段。申请人与受助人身份字段由原记录保留, + * 不调用工作流提交接口,确保流程实例及其申请人保持不变。 + * + * @param processTaskId 当前校工会审核待办任务ID + * @param apply 页面提交的申请基础表单数据 + */ + @Override + @Aop(TransAop.READ_COMMITTED) + public void modifySchoolAuditApply(Long processTaskId, DifficultSubsidyApply apply) { + ProcessTask task = validateSchoolTask(processTaskId, ProcessTaskStateEnum.DOING.getCode()); + ProcessInstance instance = dao().fetch(ProcessInstance.class, task.getProcessInstanceId()); + DifficultSubsidyApply current = instance == null ? null + : dao().fetch(DifficultSubsidyApply.class, instance.getBusinessNo()); + if (current == null || apply == null || !Objects.equals(current.getId(), apply.getId())) { + throw new IllegalArgumentException("申请记录不存在或与当前审核任务不匹配"); + } + + // 仅同步允许编辑的基础表单字段,申请人、受助人身份和流程相关字段全部沿用原记录。 + current.setAge(apply.getAge()); + current.setHomeAddress(apply.getHomeAddress()); + current.setMobile(apply.getMobile()); + current.setSubsidyType(apply.getSubsidyType()); + current.setReason(apply.getReason()); + current.setApplyFile(apply.getApplyFile()); + current.setFiles(apply.getFiles()); + current.setHospitalRecords(apply.getHospitalRecords()); + normalizeFormCollections(current); + validateApplication(current); + dao().update(current, "^(age|homeAddress|mobile|subsidyType|reason|applyFile|files|hospitalRecords|updatedBy|updatedAt)$"); + } + + @Override + @Aop(TransAop.READ_COMMITTED) + public void executeSchoolAudit(Long processTaskId, Integer submitType, String opinion, + Float finalSubsidyAmount, String hospitalRecords) { + ProcessTask task = validateSchoolTask(processTaskId, ProcessTaskStateEnum.DOING.getCode()); + ProcessInstance instance = dao().fetch(ProcessInstance.class, task.getProcessInstanceId()); + DifficultSubsidyApply apply = dao().fetch(DifficultSubsidyApply.class, instance.getBusinessNo()); + if (Strings.isNotBlank(hospitalRecords)) { + apply.setHospitalRecords(JSONUtil.toList(hospitalRecords, JSONObject.class)); + } + NutMap money = calculateSubsidy(apply); + apply.setSubsidyAmount(money.getFloat("totalMoney")); + + if (ProcessSubmitTypeEnum.AGREE.getCode().equals(submitType)) { + if (finalSubsidyAmount == null || finalSubsidyAmount < 0) { + throw new IllegalArgumentException("请填写不小于0的最终补助金额"); + } + apply.setFinalSubsidyAmount(finalSubsidyAmount); + } + dao().update(apply, "^(hospitalRecords|subsidyAmount|finalSubsidyAmount|updatedBy|updatedAt)$"); + + Dict args = Dict.create(); + args.set(FlowConst.PROCESS_TASK_ID_KEY, processTaskId); + args.set(FlowConst.SUBMIT_TYPE, submitType); + args.set(FlowConst.APPROVAL_COMMENT, opinion); + args.set(FlowConst.TASK_FORM_DATA_PREFIX + "subsidyAmount", apply.getSubsidyAmount()); + args.set(FlowConst.TASK_FORM_DATA_PREFIX + "finalSubsidyAmount", finalSubsidyAmount); + flowCommonService.executeTask(args); + dao().update(DifficultSubsidyApply.class, Chain.make("stateId", resolveAuditState(submitType)), + Cnd.where("id", "=", instance.getBusinessNo())); + } + + @Override + @Aop(TransAop.READ_COMMITTED) + public void revokeSchoolAudit(Long processTaskId, String auditorId) { + ProcessTask task = validateSchoolTask(processTaskId, ProcessTaskStateEnum.FINISHED.getCode()); + int actorCount = dao().count("wf_process_task_actor", + Cnd.where("processTaskId", "=", processTaskId).and("actorId", "=", auditorId)); + if (actorCount == 0 && !Objects.equals(task.getOperator(), auditorId)) { + throw new IllegalArgumentException("只能撤销本人完成的校工会审核"); + } + ProcessInstance instance = dao().fetch(ProcessInstance.class, task.getProcessInstanceId()); + flowCommonService.revokeTask(processTaskId); + dao().update(DifficultSubsidyApply.class, Chain.make("stateId", DifficultSubsidyState.IN_PROCESS), + Cnd.where("id", "=", instance.getBusinessNo())); + } + + @Override + public void modifyFinalSubsidyAmount(String id, Float finalSubsidyAmount) { + if (finalSubsidyAmount == null || finalSubsidyAmount < 0) { + throw new IllegalArgumentException("最终补助金额不能小于0"); + } + dao().update(DifficultSubsidyApply.class, + Chain.make("finalSubsidyAmount", finalSubsidyAmount), Cnd.where("id", "=", id)); + } + + @Override + public NutMap calculateSubsidy(String id) { + DifficultSubsidyApply apply = dao().fetch(DifficultSubsidyApply.class, id); + if (apply == null) { + throw new IllegalArgumentException("申请记录不存在"); + } + return calculateSubsidy(apply); + } + + @Override + public Workbook exportSummaryExcel(Integer year, String startMonth, String endMonth) { + List list = approvedList(year, startMonth, endMonth); + List columns = new ArrayList<>(); + ExcelExportEntity index = new ExcelExportEntity("序号", "index", 10); + columns.add(index); + columns.add(new ExcelExportEntity("工号", "loginName", 18)); + columns.add(new ExcelExportEntity("姓名", "userName", 15)); + columns.add(new ExcelExportEntity("单位", "unitName", 35)); + columns.add(new ExcelExportEntity("所属工会", "unionName", 30)); + columns.add(new ExcelExportEntity("建议补助金额", "subsidyAmount", 18)); + columns.add(new ExcelExportEntity("最终补助金额", "finalSubsidyAmount", 18)); + + List> rows = new ArrayList<>(); + for (int i = 0; i < list.size(); i++) { + DifficultSubsidyApply apply = list.get(i); + Map row = new LinkedHashMap<>(); + row.put("index", i + 1); + row.put("loginName", apply.getLoginName()); + row.put("userName", apply.getUserName()); + row.put("unitName", apply.getUnitName()); + row.put("unionName", apply.getUnionName()); + row.put("subsidyAmount", apply.getSubsidyAmount()); + row.put("finalSubsidyAmount", apply.getFinalSubsidyAmount()); + rows.add(row); + } + ExportParams exportParams = new ExportParams(); + exportParams.setType(ExcelType.XSSF); + exportParams.setTitle(yearTitle(year) + "特困补助人员名单"); + exportParams.setSheetName("特困补助人员名单"); + return ExcelExportUtil.exportExcel(exportParams, columns, rows); + } + + @Override + public byte[] exportSummaryWord(Integer year, String startMonth, String endMonth) { + List list = approvedList(year, startMonth, endMonth); + NutMap renderData = buildSummaryRenderData(year, startMonth, endMonth, list); + LoopRowTableRenderPolicy loopRowPolicy = new LoopRowTableRenderPolicy(); + Configure config = Configure.builder() + .bind("majorDiseasesList", loopRowPolicy) + .bind("outpatient2SpecialDiseasesList", loopRowPolicy) + .bind("severeCalamityList", loopRowPolicy) + .bind("inpatientCareList", loopRowPolicy) + .build(); + try (InputStream input = sysOfficeTemplateUtil.getTemplate("swufe_difficult_summary"); + ByteArrayOutputStream output = new ByteArrayOutputStream()) { + XWPFTemplate.compile(input, config).render(renderData).writeAndClose(output); + return output.toByteArray(); + } catch (IOException e) { + throw new IllegalStateException("生成特困补助汇总表失败", e); + } + } + + @Override + public byte[] exportApplyWord(String id) { + DifficultSubsidyApply apply = dao().fetch(DifficultSubsidyApply.class, id); + if (apply == null) { + throw new IllegalArgumentException("申请记录不存在"); + } + return renderApplyWord(apply); + } + + @Override + public byte[] exportApprovedZip(Integer year, String startMonth, String endMonth) { + List list = approvedList(year, startMonth, endMonth); + try (ByteArrayOutputStream output = new ByteArrayOutputStream(); ZipOutputStream zip = new ZipOutputStream(output)) { + for (DifficultSubsidyApply apply : list) { + String folder = safeName(apply.getUserName()) + "-" + safeName(apply.getUnionName()) + "/"; + String fileName = safeName(subsidyTypeNames(apply.getSubsidyType())) + + DateUtil.format(apply.getApplyDate(), "yyyy年MM月dd日HH时mm分ss秒") + "申请.docx"; + putZipEntry(zip, folder + fileName, renderApplyWord(apply)); + } + zip.finish(); + return output.toByteArray(); + } catch (IOException e) { + throw new IllegalStateException("生成特困补助材料压缩包失败", e); + } + } + + /** + * 计算当前申请的补助金额,并在返回的住院记录中写入逐条测算结果。 + */ + private NutMap calculateSubsidy(DifficultSubsidyApply apply) { + normalizeFormCollections(apply); + long totalSubsidyMoney = 0; + long totalHospitalMoney = 0; + boolean organTransplantAdded = false; + boolean majorDiseaseAdded = false; + boolean outpatientAdded = false; + boolean accidentAdded = false; + + for (JSONObject record : apply.getHospitalRecords()) { + List diseaseDetails = record.getBeanList("diseaseDetail", Integer.class); + if (diseaseDetails == null) { + diseaseDetails = Collections.emptyList(); + } + long subsidyMoney = 0; + if (diseaseDetails.contains(1) && !organTransplantAdded) { + subsidyMoney = 20000; + organTransplantAdded = true; + } else if (containsAny(diseaseDetails, List.of(2, 3, 4, 5, 6)) && !majorDiseaseAdded) { + subsidyMoney = 15000; + majorDiseaseAdded = true; + } else if (containsAny(diseaseDetails, List.of(7, 8, 9, 10, 11, 12)) && !outpatientAdded) { + subsidyMoney = 8000; + outpatientAdded = true; + } else if (containsAny(diseaseDetails, List.of(13, 14, 15)) + && record.getDouble("selfFundedAmount", 0D) >= 20000 && !accidentAdded) { + subsidyMoney = 2000; + accidentAdded = true; + } + + long hospitalMoney = 0; + if (Objects.equals(record.getInt("subsidyType"), 4)) { + Date startDate = record.getDate("startDate"); + Date endDate = record.getDate("endDate"); + if (startDate != null && endDate != null && !endDate.before(startDate)) { + long days = DateUtil.betweenDay(startDate, endDate, true) + 1; + hospitalMoney = calculateHospitalMoney(days, !diseaseDetails.contains(19)); + } + } + if (Boolean.FALSE.equals(record.getBool("isPass"))) { + subsidyMoney = 0; + hospitalMoney = 0; + } + record.set("subsidyMoney", subsidyMoney); + record.set("hospitalMoney", hospitalMoney); + totalSubsidyMoney += subsidyMoney; + totalHospitalMoney += hospitalMoney; + } + + List> historyHospitalRecords = dao().query(DifficultSubsidyApply.class, + Cnd.where("id", "!=", apply.getId()).and("loginName", "=", apply.getLoginName()) + .and("applyDate", "<", apply.getApplyDate()).desc("applyDate")) + .stream().map(DifficultSubsidyApply::getHospitalRecords).filter(Objects::nonNull) + .collect(Collectors.toList()); + return NutMap.NEW().addv("hospitalRecords", apply.getHospitalRecords()) + .addv("historyHospitalRecords", historyHospitalRecords) + .addv("totalSubsidyMoney", totalSubsidyMoney) + .addv("totalHospitalMoney", totalHospitalMoney) + .addv("totalMoney", totalSubsidyMoney + totalHospitalMoney); + } + + /** + * 住院1至15天按半个月、16至30天按一个月计算,超过30天按整月和剩余天数累加。 + */ + private int calculateHospitalMoney(long hospitalDays, boolean generalDisease) { + if (hospitalDays <= 0) { + return 0; + } + double months = Math.floor(hospitalDays / 30D); + long remain = hospitalDays % 30; + if (remain > 0 && remain <= 15) { + months += 0.5D; + } else if (remain > 15) { + months += 1D; + } + return (int) ((generalDisease ? 400 : 800) * months); + } + + private void appendSearchCondition(Cnd cnd, String prefix, Integer year, + String startMonth, String endMonth, + String searchKeyword, String unionId, String unitId) { + if (year != null) { + cnd.andEX("YEAR(" + prefix + "applyDate)", "=", year); + } + appendApplyMonthCondition(cnd, prefix, year, startMonth, endMonth); + if (Strings.isNotBlank(searchKeyword)) { + SqlExpressionGroup group = new SqlExpressionGroup(); + group.orLike(prefix + "userName", searchKeyword); + group.orLike(prefix + "loginName", searchKeyword); + cnd.and(group); + } + if (Strings.isNotBlank(unionId)) { + cnd.and(prefix + "unionId", "=", unionId); + } + if (Strings.isNotBlank(unitId)) { + cnd.and(prefix + "unitId", "=", unitId); + } + } + + /** + * 将申请月份范围转换为左闭右开的日期条件,避免对申请时间列使用 DATE_FORMAT。 + * + * @param cnd 查询条件 + * @param prefix 表别名前缀 + * @param year 页面选择的申请年份 + * @param startMonth 开始月份,格式 yyyy-MM + * @param endMonth 结束月份,格式 yyyy-MM + */ + private void appendApplyMonthCondition(Cnd cnd, String prefix, Integer year, + String startMonth, String endMonth) { + if (Strings.isBlank(startMonth) && Strings.isBlank(endMonth)) { + return; + } + if (!isValidMonth(startMonth) || !isValidMonth(endMonth)) { + throw new IllegalArgumentException("申请月份格式不正确"); + } + int startYear = Integer.parseInt(startMonth.substring(0, 4)); + int endYear = Integer.parseInt(endMonth.substring(0, 4)); + if (startYear != endYear || (year != null && (year != startYear || year != endYear))) { + throw new IllegalArgumentException("开始月份和结束月份必须属于申请年份"); + } + Date startDate = DateUtil.parse(startMonth + "-01", "yyyy-MM-dd"); + Date endMonthStart = DateUtil.parse(endMonth + "-01", "yyyy-MM-dd"); + if (startDate.after(endMonthStart)) { + throw new IllegalArgumentException("开始月份不能晚于结束月份"); + } + Date endDateExclusive = DateUtil.offsetMonth(endMonthStart, 1); + cnd.and(prefix + "applyDate", ">=", startDate); + cnd.and(prefix + "applyDate", "<", endDateExclusive); + } + + private boolean isValidMonth(String month) { + return Strings.isNotBlank(month) && month.matches("\\d{4}-(0[1-9]|1[0-2])"); + } + + /** + * Word 模板只有一个季度占位符;跨季度范围使用“1-2”形式展示。 + */ + private String resolveQuarterText(String startMonth, String endMonth) { + if (!isValidMonth(startMonth) || !isValidMonth(endMonth)) { + return String.valueOf(DateUtil.quarter(new Date())); + } + int startQuarter = (Integer.parseInt(startMonth.substring(5, 7)) - 1) / 3 + 1; + int endQuarter = (Integer.parseInt(endMonth.substring(5, 7)) - 1) / 3 + 1; + return startQuarter == endQuarter + ? String.valueOf(startQuarter) : startQuarter + "-" + endQuarter; + } + + /** + * 原生 SQL 分页会把 MySQL JSON 字段放入字符串,统一恢复为页面约定的数组类型。 + * + * @param pagination 申请列表分页结果 + */ + private void normalizeListJsonFields(Pagination pagination) { + if (pagination == null || pagination.getList() == null) { + return; + } + for (NutMap row : pagination.getList()) { + Object subsidyType = row.get("subsidyType"); + if (subsidyType instanceof List) { + continue; + } + if (subsidyType instanceof String && JSONUtil.isTypeJSONArray((String) subsidyType)) { + row.setv("subsidyType", JSONUtil.toList((String) subsidyType, Integer.class)); + } else { + row.setv("subsidyType", Collections.emptyList()); + } + } + } + + private ProcessTask validateSchoolTask(Long processTaskId, Integer expectedState) { + if (processTaskId == null) { + throw new IllegalArgumentException("审核任务不能为空"); + } + ProcessTask task = dao().fetch(ProcessTask.class, processTaskId); + if (task == null || !Objects.equals(task.getTaskState(), expectedState)) { + throw new IllegalStateException("当前审核任务状态已变化,请刷新后重试"); + } + ProcessInstance instance = dao().fetch(ProcessInstance.class, task.getProcessInstanceId()); + ProcessDefine define = instance == null ? null : dao().fetch(ProcessDefine.class, instance.getProcessDefineId()); + if (define == null || !PROCESS_KEY.equals(define.getName()) + || !SCHOOL_AUDIT_TASK_NAME.equals(task.getDisplayName())) { + throw new IllegalArgumentException("该任务不属于特困补助校工会审核环节"); + } + return task; + } + + private ProcessInstance latestActiveInstance(String businessId) { + return dao().fetch(ProcessInstance.class, + Cnd.where(ProcessInstance::getBusinessNo, "=", businessId) + .and(ProcessInstance::getState, "=", ProcessInstanceStateEnum.DOING.getCode()).desc("id")); + } + + private ProcessInstance latestInstance(String businessId) { + return dao().fetch(ProcessInstance.class, + Cnd.where(ProcessInstance::getBusinessNo, "=", businessId).desc("id")); + } + + private Dict buildSubmitArgs(DifficultSubsidyApply apply) { + Dict args = Dict.create(); + args.set(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.APPLY.getCode()); + args.set(FlowConst.FORM_DATA, apply); + args.set(FlowConst.PROCESS_INSTANCE_NAME, apply.getUserName() + "特困补助申请"); + return args; + } + + /** + * 补全申请人快照。受助人的姓名、工号、单位等数据来自页面选择结果,不更新 sys_user。 + */ + private void fillApplicantSnapshot(DifficultSubsidyApply apply) { + apply.setApplyUserId(SecurityUtil.getUserId()); + apply.setApplyUserName(SecurityUtil.getUserUsername()); + apply.setApplyLoginName(SecurityUtil.getUserLoginname()); + if (Strings.isBlank(apply.getUserId())) { + apply.setUserId(apply.getApplyUserId()); + apply.setUserName(apply.getApplyUserName()); + apply.setLoginName(apply.getApplyLoginName()); + } + if (Strings.isBlank(apply.getUnitId())) { + apply.setUnitId(SecurityUtil.getUnitId()); + } + if (Strings.isBlank(apply.getUnionId())) { + apply.setUnionId(SecurityUtil.getUnionId()); + } + } + + private void normalizeFormCollections(DifficultSubsidyApply apply) { + if (apply.getSubsidyType() == null) { + apply.setSubsidyType(new ArrayList<>()); + } + if (apply.getHospitalRecords() == null) { + apply.setHospitalRecords(new ArrayList<>()); + } + if (apply.getApplyFile() == null) { + apply.setApplyFile(new ArrayList<>()); + } + if (apply.getFiles() == null) { + apply.setFiles(new ArrayList<>()); + } + } + + private void validateApplication(DifficultSubsidyApply apply) { + if (Strings.isBlank(apply.getUserName()) || Strings.isBlank(apply.getLoginName())) { + throw new IllegalArgumentException("请选择申请补助的教职工"); + } + if (apply.getAge() == null || Strings.isBlank(apply.getMobile())) { + throw new IllegalArgumentException("年龄和联系电话不能为空"); + } + if (apply.getSubsidyType() == null || apply.getSubsidyType().isEmpty()) { + throw new IllegalArgumentException("请选择申请补助类别"); + } + if (apply.getHospitalRecords() == null || apply.getHospitalRecords().isEmpty()) { + throw new IllegalArgumentException("请填写住院或补助事项记录"); + } + if (Strings.isBlank(apply.getReason())) { + throw new IllegalArgumentException("申请补助原因不能为空"); + } + if (apply.getApplyFile() == null || apply.getApplyFile().isEmpty() + || apply.getFiles() == null || apply.getFiles().isEmpty()) { + throw new IllegalArgumentException("请上传纸质申请表和证明材料"); + } + } + + private Integer resolveAuditState(Integer submitType) { + if (ProcessSubmitTypeEnum.AGREE.getCode().equals(submitType)) { + return DifficultSubsidyState.PASS; + } + if (ProcessSubmitTypeEnum.REJECT.getCode().equals(submitType)) { + return DifficultSubsidyState.SCHOOL_REFUSE; + } + return DifficultSubsidyState.SCHOOL_BACK; + } + + private List approvedList(Integer year, String startMonth, String endMonth) { + Cnd cnd = Cnd.where("stateId", "=", DifficultSubsidyState.PASS); + if (year != null) { + cnd.andEX("YEAR(applyDate)", "=", year); + } + appendApplyMonthCondition(cnd, "", year, startMonth, endMonth); + cnd.asc("unionName").asc("unitName").asc("loginName"); + return dao().query(DifficultSubsidyApply.class, cnd); + } + + /** + * 使用系统模板管理中的申请表模板生成单份申请材料,保持模板原有版式。 + */ + private byte[] renderApplyWord(DifficultSubsidyApply apply) { + NutMap renderData = buildApplyRenderData(apply); + try (InputStream input = sysOfficeTemplateUtil.getTemplate("swufe_difficult_apply"); + ByteArrayOutputStream output = new ByteArrayOutputStream()) { + XWPFTemplate.compile(input).render(renderData).writeAndClose(output); + return output.toByteArray(); + } catch (IOException e) { + throw new IllegalStateException("生成特困补助申请登记表失败", e); + } + } + + /** + * 将申请实体、证明材料和工作流审核意见转换为申请表模板所需字段。 + */ + private NutMap buildApplyRenderData(DifficultSubsidyApply apply) { + normalizeFormCollections(apply); + NutMap renderData = NutMap.NEW() + .addv("userName", StrUtil.blankToDefault(apply.getUserName(), "")) + .addv("sex", StrUtil.blankToDefault(apply.getSex(), "")) + .addv("age", apply.getAge() == null ? "" : apply.getAge()) + .addv("unitName", StrUtil.blankToDefault(apply.getUnitName(), "")) + .addv("homeAddress", StrUtil.blankToDefault(apply.getHomeAddress(), "")) + .addv("mobile", StrUtil.blankToDefault(apply.getMobile(), "")) + .addv("subsidyTypeNames", subsidyTypeNames(apply.getSubsidyType())) + .addv("reason", StrUtil.blankToDefault(apply.getReason(), "")) + .addv("picFiles", buildProofPictureData(apply.getFiles())) + .addv("unitOpinion", "") + .addv("unitAuditDate", "") + .addv("schoolOpinion", "") + .addv("schoolAuditDate", ""); + fillTemplateAuditData(apply.getId(), renderData); + return renderData; + } + + /** + * 证明材料按模板图片循环块处理;历史附件失效或不是可读取图片时跳过该附件。 + */ + private List> buildProofPictureData(List files) { + List> pictures = new ArrayList<>(); + if (files == null) { + return pictures; + } + for (JSONObject file : files) { + String id = extractFileId(file); + if (Strings.isBlank(id)) { + continue; + } + try { + byte[] bytes = sysFileService.download(id); + PictureRenderData picture = Pictures.ofBytes(bytes).size(100, 100).create(); + Map pictureData = new HashMap<>(); + pictureData.put("file", picture); + pictureData.put("fileName", StrUtil.blankToDefault(file.getStr("name"), + StrUtil.blankToDefault(file.getStr("filename"), "证明材料"))); + pictures.add(pictureData); + } catch (Exception ignored) { + // 单个历史附件无法读取时不影响同一申请中其他证明材料的导出。 + } + } + return pictures; + } + + /** + * 模板保留原有单位和学校意见栏;新流程没有分工会节点,因此新申请的单位意见为空。 + */ + private void fillTemplateAuditData(String applyId, NutMap renderData) { + ProcessInstance instance = latestInstance(applyId); + if (instance == null) { + return; + } + List records = flowEngine.processInstanceService().approvalRecord(instance.getId()); + if (records == null) { + return; + } + Date latestUnitAuditDate = null; + Date latestSchoolAuditDate = null; + for (ProcessTaskVO task : records) { + if (task.getFinishTime() == null || Strings.isBlank(task.getDisplayName())) { + continue; + } + String opinion = task.getTaskFormData() == null ? "" + : StrUtil.blankToDefault(task.getTaskFormData().getStr("approvalComment"), + StrUtil.blankToDefault(task.getTaskFormData().getStr("opinion"), "")); + String auditDate = DateUtil.format(task.getFinishTime(), "yyyy-MM-dd"); + if ((task.getDisplayName().contains("分工会") || task.getDisplayName().contains("单位")) + && (latestUnitAuditDate == null || task.getFinishTime().after(latestUnitAuditDate))) { + renderData.put("unitOpinion", opinion); + renderData.put("unitAuditDate", auditDate); + latestUnitAuditDate = task.getFinishTime(); + } else if (task.getDisplayName().contains("校工会") + && (latestSchoolAuditDate == null || task.getFinishTime().after(latestSchoolAuditDate))) { + renderData.put("schoolOpinion", opinion); + renderData.put("schoolAuditDate", auditDate); + latestSchoolAuditDate = task.getFinishTime(); + } + } + } + + /** + * 按模板的四个补助类别和原项目优先级组织汇总数据,同一申请只进入一个类别。 + */ + private NutMap buildSummaryRenderData(Integer year, String startMonth, String endMonth, + List list) { + Map> groups = new LinkedHashMap<>(); + groups.put(1, new ArrayList<>()); + groups.put(2, new ArrayList<>()); + groups.put(3, new ArrayList<>()); + groups.put(4, new ArrayList<>()); + for (DifficultSubsidyApply apply : list) { + normalizeFormCollections(apply); + for (Integer projectId : List.of(1, 2, 3, 4)) { + if (apply.getSubsidyType().contains(projectId)) { + groups.get(projectId).add(apply); + break; + } + } + } + + List majorDiseases = buildSummaryRows(groups.get(1)); + List outpatientDiseases = buildSummaryRows(groups.get(2)); + List severeCalamities = buildSummaryRows(groups.get(3)); + List inpatientCare = buildSummaryRows(groups.get(4)); + int exportYear = year == null ? DateUtil.thisYear() : year; + return NutMap.NEW() + .addv("year", exportYear) + .addv("quarter", resolveQuarterText(startMonth, endMonth)) + .addv("majorDiseasesCount", majorDiseases.size()) + .addv("majorDiseasesList", majorDiseases) + .addv("outpatient2SpecialDiseasesCount", outpatientDiseases.size()) + .addv("outpatient2SpecialDiseasesList", outpatientDiseases) + .addv("severeCalamityCount", severeCalamities.size()) + .addv("severeCalamityList", severeCalamities) + .addv("inpatientCareCount", inpatientCare.size()) + .addv("inpatientCareList", inpatientCare) + .addv("totalNum", list.stream().map(DifficultSubsidyApply::getLoginName) + .filter(Strings::isNotBlank).distinct().count()) + .addv("totalMoney", list.stream().map(DifficultSubsidyApply::getSubsidyAmount) + .filter(Objects::nonNull).mapToDouble(Float::doubleValue).sum()) + .addv("exportDate", DateUtil.format(new Date(), "yyyy年MM月dd日")); + } + + private List buildSummaryRows(List applications) { + List rows = new ArrayList<>(); + for (int i = 0; i < applications.size(); i++) { + DifficultSubsidyApply apply = applications.get(i); + calculateSubsidy(apply); + rows.add(NutMap.NEW() + .addv("index", i + 1) + .addv("userName", StrUtil.blankToDefault(apply.getUserName(), "")) + .addv("loginName", StrUtil.blankToDefault(apply.getLoginName(), "")) + .addv("sex", StrUtil.blankToDefault(apply.getSex(), "")) + .addv("age", apply.getAge() == null ? "" : apply.getAge()) + .addv("unitName", StrUtil.blankToDefault(apply.getUnitName(), "")) + .addv("reason", StrUtil.blankToDefault(apply.getReason(), "")) + .addv("disease", subsidyTypeNames(apply.getSubsidyType())) + .addv("hospitalizationDays", hospitalizationDays(apply.getHospitalRecords())) + .addv("imitateSubsidyAmount", calculatedProjectMoney(apply.getHospitalRecords())) + .addv("InpatientCareSubsidyAmount", calculatedHospitalMoney(apply.getHospitalRecords()))); + } + return rows; + } + + private String hospitalizationDays(List records) { + List days = new ArrayList<>(); + List dateRanges = new ArrayList<>(); + for (JSONObject record : records) { + Date startDate = record.getDate("startDate"); + Date endDate = record.getDate("endDate"); + if (startDate == null || endDate == null || endDate.before(startDate)) { + continue; + } + days.add((DateUtil.betweenDay(startDate, endDate, true) + 1) + "天"); + dateRanges.add(DateUtil.format(startDate, "yyyy年MM月dd日") + "-" + + DateUtil.format(endDate, "yyyy年MM月dd日")); + } + return days.isEmpty() ? "" : "住院天数:" + String.join("+", days) + + "(" + String.join(",", dateRanges) + ")"; + } + + private String calculatedProjectMoney(List records) { + List descriptions = new ArrayList<>(); + for (JSONObject record : records) { + long money = record.getLong("subsidyMoney", 0L); + if (money <= 0) { + continue; + } + List diseaseIds = record.getBeanList("diseaseDetail", Integer.class); + diseaseIds = diseaseIds == null ? Collections.emptyList() : diseaseIds; + String name = diseaseIds.contains(1) ? "重大疾病(器官移植)" + : containsAny(diseaseIds, List.of(2, 3, 4, 5, 6)) ? "重大疾病" + : containsAny(diseaseIds, List.of(7, 8, 9, 10, 11, 12)) ? "门诊第二类特殊疾病" + : "重大变故"; + descriptions.add(name + "补助" + money + "元"); + } + return String.join("、", descriptions); + } + + private String calculatedHospitalMoney(List records) { + List amounts = records.stream().map(item -> item.getLong("hospitalMoney", 0L)) + .filter(amount -> amount > 0).collect(Collectors.toList()); + if (amounts.isEmpty()) { + return ""; + } + if (amounts.size() == 1) { + return "住院护理补助" + amounts.get(0) + "元"; + } + long total = amounts.stream().mapToLong(Long::longValue).sum(); + return "住院护理补助" + amounts.stream().map(amount -> amount + "元") + .collect(Collectors.joining("+")) + "=" + total + "元"; + } + + private String subsidyTypeNames(List ids) { + if (ids == null) { + return ""; + } + Map names = projectList().stream().collect(Collectors.toMap( + DifficultSubsidyProject::getId, DifficultSubsidyProject::getProjectName, (a, b) -> a)); + return ids.stream().map(names::get).filter(Objects::nonNull).collect(Collectors.joining("、")); + } + + private void putZipEntry(ZipOutputStream zip, String name, byte[] bytes) throws IOException { + zip.putNextEntry(new ZipEntry(name)); + zip.write(bytes); + zip.closeEntry(); + } + + private String extractFileId(JSONObject file) { + JSONObject response = file.getJSONObject("response"); + String value = response == null ? file.getStr("id") : response.getStr("data"); + if (value != null && value.contains("=")) { + value = value.substring(value.lastIndexOf('=') + 1); + } + return value; + } + + private boolean containsAny(List source, List targets) { + return targets.stream().anyMatch(source::contains); + } + + private int defaultPage(Integer pageNumber) { + return pageNumber == null ? 1 : pageNumber; + } + + private int defaultPageSize(Integer pageSize) { + return pageSize == null ? 10 : pageSize; + } + + private String yearTitle(Integer year) { + return (year == null ? DateUtil.thisYear() : year) + "年"; + } + + private String safeName(String value) { + return StrUtil.blankToDefault(value, "未命名").replaceAll("[\\\\/:*?\"<>|]", "_"); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/staffbenefit/difficulthelp/controller/DifficultHelpReadingController.java b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficulthelp/controller/DifficultHelpReadingController.java index db9fd30..ae1b103 100644 --- a/src/main/java/com/budwk/app/zhgh/staffbenefit/difficulthelp/controller/DifficultHelpReadingController.java +++ b/src/main/java/com/budwk/app/zhgh/staffbenefit/difficulthelp/controller/DifficultHelpReadingController.java @@ -299,7 +299,7 @@ public class DifficultHelpReadingController { docData.put("bankCardNum", record.getString("bankCardNum")); docData.put("reason", record.getString("reason")); docData.put("applyTime", DateUtil.format(DateUtil.parse(record.getString("applyTime")), "yyyy年MM月dd日")); - docData.put("schoolname", "中国地质大学(武汉)"); + docData.put("schoolname", "西南财经大学"); docData.put("year", DateUtil.format(DateUtil.parse(record.getString("applyTime")), "yyyy")); String familyListStr = record.getString("familyList"); @@ -350,7 +350,7 @@ public class DifficultHelpReadingController { docData.put("bankCardNum", difficultHelp.getBankCardNum()); docData.put("reason", difficultHelp.getReason()); docData.put("applyTime", DateUtil.format(difficultHelp.getApplyTime(), "yyyy-MM-dd")); - docData.put("schoolname","中国地质大学(武汉)"); + docData.put("schoolname","西南财经大学"); docData.put("year",DateUtil.format(difficultHelp.getApplyTime(), "yyyy")); docData.put("familyList", difficultHelp.getFamilyList() != null ? difficultHelp.getFamilyList() : new ArrayList<>()); @@ -371,7 +371,7 @@ public class DifficultHelpReadingController { @At private void exportAsPDF(String templateName, HashMap docData,Configure config, DifficultHelpInfo difficultHelp, HttpServletResponse response) throws Exception { - String fileName = "中国地质大学(武汉)困难慰问申请表_" + difficultHelp.getUserName() + "_" + + String fileName = "西南财经大学困难慰问申请表_" + difficultHelp.getUserName() + "_" + DateUtil.format(difficultHelp.getApplyTime(), "yyyyMMdd") + ".pdf"; try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { @@ -396,7 +396,7 @@ public class DifficultHelpReadingController { @At private void exportAsWord(String templateName, HashMap docData,Configure config, DifficultHelpInfo difficultHelp, HttpServletResponse response) throws Exception { - String fileName = "中国地质大学(武汉)困难慰问申请表_" + difficultHelp.getUserName() + "_" + + String fileName = "西南财经大学困难慰问申请表_" + difficultHelp.getUserName() + "_" + DateUtil.format(difficultHelp.getApplyTime(), "yyyyMMdd") + ".docx"; try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { diff --git a/src/main/resources/static/components/zhgh/difficultSubsidy/ProcessInfo.vue b/src/main/resources/static/components/zhgh/difficultSubsidy/ProcessInfo.vue new file mode 100644 index 0000000..5b6bb4c --- /dev/null +++ b/src/main/resources/static/components/zhgh/difficultSubsidy/ProcessInfo.vue @@ -0,0 +1,192 @@ + + + + + diff --git a/src/main/resources/views/platform/sys/union/branchUnionUserManage.js b/src/main/resources/views/platform/sys/union/branchUnionUserManage.js index 87e1ce6..9af1a5f 100644 --- a/src/main/resources/views/platform/sys/union/branchUnionUserManage.js +++ b/src/main/resources/views/platform/sys/union/branchUnionUserManage.js @@ -75,7 +75,14 @@ const branchUnionUserManage = { - + + + @@ -143,6 +150,7 @@ const branchUnionUserManage = { id: "", leaveDate: "" }, + roleOptions: [], jOptions: [], usedJCodes: [] } @@ -154,6 +162,13 @@ const branchUnionUserManage = { } }, methods: { + loadRoleOptions() { + return $.get("/platform/sys/union/branchUnionRoleOptions").then((res) => { + if (res.code === 0) { + this.roleOptions = res.data || [] + } + }) + }, loadJOptions() { return $.get("/open/common/dictOptions", { code: "TEACHER_CONGRESS_J" }).then((res) => { this.jOptions = res.data || [] @@ -279,6 +294,7 @@ const branchUnionUserManage = { } }, created() { + this.loadRoleOptions() this.initJSearch() }, style: /*language=CSS*/ ` diff --git a/src/main/resources/views/platform/zhgh/activity/sports/ActivityPrize/index.html b/src/main/resources/views/platform/zhgh/activity/sports/ActivityPrize/index.html index d582b9f..e19af66 100644 --- a/src/main/resources/views/platform/zhgh/activity/sports/ActivityPrize/index.html +++ b/src/main/resources/views/platform/zhgh/activity/sports/ActivityPrize/index.html @@ -56,7 +56,7 @@ layout("/layouts/platform.html"){ + :label="pageForm.status==='2'?('西南财经大学'+pageForm.year+'年教职工运动会获奖个数汇总表'):'奖品名单'"> - 中国地质大学(武汉){{ activityList.find(v=>v.id === pageForm.activityId).name }} + 西南财经大学{{ activityList.find(v=>v.id === pageForm.activityId).name }} ({{unionName}})获奖名单 diff --git a/src/main/resources/views/platform/zhgh/democratic/proposal/common/info.js b/src/main/resources/views/platform/zhgh/democratic/proposal/common/info.js index 5032cca..5c7e1d7 100644 --- a/src/main/resources/views/platform/zhgh/democratic/proposal/common/info.js +++ b/src/main/resources/views/platform/zhgh/democratic/proposal/common/info.js @@ -149,7 +149,9 @@ const PROPOSAL_INFO = { - + + + @@ -271,9 +273,37 @@ const PROPOSAL_INFO = { this.$axios.post("/flow/common/doneTasks", {bizId: this.row.id}).then((res) => { if (res.code === 0) { const tasks = res.data || [] - this.doneTasks = tasks + // 提案历史按定义节点顺序展示,迁入任务不受数据库补写时间影响。 + const taskOrders = { + startTask: 10, + invite: 20, + second: 30, + secondaryUnionAudit: 40, + partyOrganizationAudit: 50, + committee: 60, + committeeFiling: 60, + committeeFilingUnit: 70, + unit_reply: 80, + unitReply: 80, + schoolLeader: 90, + feedback: 100, + feedbackEvaluation: 100 + } + tasks.sort((first, second) => { + const orderDifference = (taskOrders[first.taskName] || 999) - (taskOrders[second.taskName] || 999) + if (orderDifference !== 0) { + return orderDifference + } + const firstTime = first.finishTime || '' + const secondTime = second.finishTime || '' + if (firstTime !== secondTime) { + return firstTime.localeCompare(secondTime) + } + return first.id - second.id + }) + this.$set(this, 'doneTasks', tasks) // 审核记录加载完成后默认全部折叠,由用户按需展开查看。 - this.activeTaskId = null + this.$set(this, 'activeTaskId', null) // 通知外层页面已办节点数据已加载完成,便于当前环节做表单回显。 this.$emit("done-tasks", tasks) } diff --git a/src/main/resources/views/platform/zhgh/democratic/proposal/export/comprehensive/index.html b/src/main/resources/views/platform/zhgh/democratic/proposal/export/comprehensive/index.html index 8441e7a..d758245 100644 --- a/src/main/resources/views/platform/zhgh/democratic/proposal/export/comprehensive/index.html +++ b/src/main/resources/views/platform/zhgh/democratic/proposal/export/comprehensive/index.html @@ -285,7 +285,7 @@ layout("/layouts/platform.html"){ }) }, exportDocx(id) { - this.$downLoad("/platform/proposal/common/exportProposalAsDocx", {id}) + this.$downLoad("/platform/proposal/export/comprehensive/exportProposalAsDocx", {id}) }, exportSummaryAsExcel() { this.$downLoad("/platform/proposal/export/comprehensive/exportSummaryAsExcel", { diff --git a/src/main/resources/views/platform/zhgh/democratic/proposal/transact/caseCheck/index.html b/src/main/resources/views/platform/zhgh/democratic/proposal/transact/caseCheck/index.html index 6984019..56e1247 100644 --- a/src/main/resources/views/platform/zhgh/democratic/proposal/transact/caseCheck/index.html +++ b/src/main/resources/views/platform/zhgh/democratic/proposal/transact/caseCheck/index.html @@ -148,7 +148,6 @@ layout("/layouts/platform.html"){ return { tableColumns: [ {label: "提案编号", prop: "code"}, - {label: "立案编号", prop: "caseFilingCode"}, {label: "提案名称", prop: "name", width: "200px"}, {label: "提案人", prop: "createUserName", width: "80px"}, {label: "提案类别", prop: "typeName"}, diff --git a/src/main/resources/views/platform/zhgh/democratic/proposal/transact/committeeFiling/index.html b/src/main/resources/views/platform/zhgh/democratic/proposal/transact/committeeFiling/index.html index 272d0ca..6b10f80 100644 --- a/src/main/resources/views/platform/zhgh/democratic/proposal/transact/committeeFiling/index.html +++ b/src/main/resources/views/platform/zhgh/democratic/proposal/transact/committeeFiling/index.html @@ -201,7 +201,6 @@ layout("/layouts/platform.html"){ return { tableColumns: [ { label: "提案编号", prop: "code" }, - { label: "立案编号", prop: "caseFilingCode" }, { label: "提案名称", prop: "name", width: "200px" }, { label: "提案类别", prop: "typeName" }, { label: "届次", prop: "sessionName" }, diff --git a/src/main/resources/views/platform/zhgh/democratic/proposal/transact/committeeFilingUnit/index.html b/src/main/resources/views/platform/zhgh/democratic/proposal/transact/committeeFilingUnit/index.html index fc101a0..98f52e8 100644 --- a/src/main/resources/views/platform/zhgh/democratic/proposal/transact/committeeFilingUnit/index.html +++ b/src/main/resources/views/platform/zhgh/democratic/proposal/transact/committeeFilingUnit/index.html @@ -209,7 +209,6 @@ layout("/layouts/platform.html"){ return { tableColumns: [ {label: "提案编号", prop: "code"}, - {label: "立案编号", prop: "caseFilingCode"}, {label: "提案名称", prop: "name", width: "200px"}, {label: "提案类别", prop: "typeName"}, {label: "届次", prop: "sessionName"}, diff --git a/src/main/resources/views/platform/zhgh/democratic/proposal/transact/delegation/index.html b/src/main/resources/views/platform/zhgh/democratic/proposal/transact/delegation/index.html index 04d3bd1..1580994 100644 --- a/src/main/resources/views/platform/zhgh/democratic/proposal/transact/delegation/index.html +++ b/src/main/resources/views/platform/zhgh/democratic/proposal/transact/delegation/index.html @@ -108,7 +108,6 @@ layout("/layouts/platform.html"){ return { tableColumns: [ {label: "提案编号", prop: "code"}, - {label: "立案编号", prop: "caseFilingCode"}, {label: "提案名称", prop: "name", width: "200px"}, {label: "提案人", prop: "createUserName", width: "80px"}, {label: "提案类别", prop: "typeName"}, diff --git a/src/main/resources/views/platform/zhgh/democratic/proposal/transact/feedback/index.html b/src/main/resources/views/platform/zhgh/democratic/proposal/transact/feedback/index.html index f72b54e..fdd647d 100644 --- a/src/main/resources/views/platform/zhgh/democratic/proposal/transact/feedback/index.html +++ b/src/main/resources/views/platform/zhgh/democratic/proposal/transact/feedback/index.html @@ -120,7 +120,6 @@ layout("/layouts/platform.html"){ return { tableColumns: [ {label: "提案编号", prop: "code"}, - {label: "立案编号", prop: "caseFilingCode"}, {label: "提案名称", prop: "name", width: "200px"}, {label: "提案类别", prop: "typeName"}, {label: "是否并案", prop: "merge"}, diff --git a/src/main/resources/views/platform/zhgh/democratic/proposal/transact/preAudit/index.html b/src/main/resources/views/platform/zhgh/democratic/proposal/transact/preAudit/index.html index 11b1c3f..dabf695 100644 --- a/src/main/resources/views/platform/zhgh/democratic/proposal/transact/preAudit/index.html +++ b/src/main/resources/views/platform/zhgh/democratic/proposal/transact/preAudit/index.html @@ -112,7 +112,6 @@ layout("/layouts/platform.html"){ return { tableColumns: [ {label: "提案编号", prop: "code"}, - {label: "立案编号", prop: "caseFilingCode"}, {label: "提案名称", prop: "name", width: "200px"}, {label: "提案人", prop: "createUserName", width: "80px"}, {label: "提案类别", prop: "typeName"}, diff --git a/src/main/resources/views/platform/zhgh/democratic/proposal/transact/schoolLeaderApproval/index.html b/src/main/resources/views/platform/zhgh/democratic/proposal/transact/schoolLeaderApproval/index.html index 85cbd8e..af765e7 100644 --- a/src/main/resources/views/platform/zhgh/democratic/proposal/transact/schoolLeaderApproval/index.html +++ b/src/main/resources/views/platform/zhgh/democratic/proposal/transact/schoolLeaderApproval/index.html @@ -146,7 +146,6 @@ layout("/layouts/platform.html"){ return { tableColumns: [ {label: "提案编号", prop: "code"}, - {label: "立案编号", prop: "caseFilingCode"}, {label: "提案名称", prop: "name", width: "200px"}, {label: "提案类别", prop: "typeName"}, {label: "办理单位", prop: "undertakeUnits", width: "260px"}, diff --git a/src/main/resources/views/platform/zhgh/democratic/proposal/transact/unitReply/index.html b/src/main/resources/views/platform/zhgh/democratic/proposal/transact/unitReply/index.html index ad3ffac..8a6fa10 100644 --- a/src/main/resources/views/platform/zhgh/democratic/proposal/transact/unitReply/index.html +++ b/src/main/resources/views/platform/zhgh/democratic/proposal/transact/unitReply/index.html @@ -289,7 +289,6 @@ layout("/layouts/platform.html"){ return { tableColumns: [ {label: "提案编号", prop: "code"}, - {label: "立案编号", prop: "caseFilingCode"}, {label: "提案名称", prop: "name", width: "200px"}, {label: "提案类别", prop: "typeName"}, {label: "立案结果", prop: "caseFilingResult"}, diff --git a/src/main/resources/views/platform/zhgh/huazhu/index.html b/src/main/resources/views/platform/zhgh/huazhu/index.html new file mode 100644 index 0000000..1d095f9 --- /dev/null +++ b/src/main/resources/views/platform/zhgh/huazhu/index.html @@ -0,0 +1,82 @@ + +
+ + + +
+ + diff --git a/src/main/resources/views/platform/zhgh/inclusive/index.html b/src/main/resources/views/platform/zhgh/inclusive/index.html new file mode 100644 index 0000000..57df336 --- /dev/null +++ b/src/main/resources/views/platform/zhgh/inclusive/index.html @@ -0,0 +1,136 @@ + +
+ + + + +
+ + diff --git a/src/main/resources/views/platform/zhgh/staffbenefit/difficultSubsidy/basicSetting/project/index.html b/src/main/resources/views/platform/zhgh/staffbenefit/difficultSubsidy/basicSetting/project/index.html new file mode 100644 index 0000000..7f6f455 --- /dev/null +++ b/src/main/resources/views/platform/zhgh/staffbenefit/difficultSubsidy/basicSetting/project/index.html @@ -0,0 +1,240 @@ + + +
+ + + + + 新增补助范围 + +
+
+ {{ item.projectName }} + + 编辑 + 删除 + +
+
+
+
+ + + + + + 查询 + + + 新增子类 + + + + + + + + + + + +
+ + + + + + + +
+ 取消 + 确定 +
+
+ + + + + + + +
+ 取消 + 确定 +
+
+
+ + diff --git a/src/main/resources/views/platform/zhgh/staffbenefit/difficultSubsidy/process/apply/index.html b/src/main/resources/views/platform/zhgh/staffbenefit/difficultSubsidy/process/apply/index.html new file mode 100644 index 0000000..2716262 --- /dev/null +++ b/src/main/resources/views/platform/zhgh/staffbenefit/difficultSubsidy/process/apply/index.html @@ -0,0 +1,454 @@ + + +
+ + + + + + + {{ formData.applyUserName }}({{ formData.applyLoginName }}) + + + + + + + + + + + + + + + + + + 年龄 + + + + + + + + + 联系电话 + + + + + + 申请补助类别 + + + + {{ item.projectName }} + + + + + + 住院记录 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 申请补助原因 + + + + + + 纸质申请表 + + + + + + 证明材料 + + +
申请补助范围第1—3项的教职工需提交材料:
+
1. 医院疾病证明;
+
+ 2. 住院证明等相关就医辅助材料,其中住院15天及以下天数按半个月计算,住院16天至30天按一个月计算,每次出院及住院为一个时间段计算天数。 +
+
申请补助范围第4项重大变故项目的教职工需提交材料:
+
+ 1. 遭受自然灾害需提交当地消防部门、公安部门及社区的相关证明材料,相关财产损失评估报告; +
+
+ 2. 遭受意外伤害需提交公安、消防、交通等相关部门证明材料,医院疾病证明和住院证明,保险、医保结算清单复印件; +
+
+ 3. 教职工直系亲属患严重疾病的需提交《户口薄》复印件,医院疾病证明,住院和治疗结算清单复印件,保险、医保结算清单复印件。 +
+
+ +
+
+
+
+ + 保存 + 提交 + +
+
+ + diff --git a/src/main/resources/views/platform/zhgh/staffbenefit/difficultSubsidy/process/mine/index.html b/src/main/resources/views/platform/zhgh/staffbenefit/difficultSubsidy/process/mine/index.html new file mode 100644 index 0000000..ccd2137 --- /dev/null +++ b/src/main/resources/views/platform/zhgh/staffbenefit/difficultSubsidy/process/mine/index.html @@ -0,0 +1,166 @@ + +
+ + + + + + + + + + + 发起申请 + + + + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/src/main/resources/views/platform/zhgh/staffbenefit/difficultSubsidy/process/schoolAudit/index.html b/src/main/resources/views/platform/zhgh/staffbenefit/difficultSubsidy/process/schoolAudit/index.html new file mode 100644 index 0000000..9811f10 --- /dev/null +++ b/src/main/resources/views/platform/zhgh/staffbenefit/difficultSubsidy/process/schoolAudit/index.html @@ -0,0 +1,494 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + 导出审核材料 + + 全部 + 已审核 + 未审核 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ 取消 + 确定 +
+
+
+
+ + + diff --git a/src/main/resources/views/platform/zhgh/staffbenefit/difficultSubsidy/query/index.html b/src/main/resources/views/platform/zhgh/staffbenefit/difficultSubsidy/query/index.html new file mode 100644 index 0000000..f222474 --- /dev/null +++ b/src/main/resources/views/platform/zhgh/staffbenefit/difficultSubsidy/query/index.html @@ -0,0 +1,211 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + 导出汇总表 Excel + 导出汇总表 Word + + + + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/src/main/resources/views/platform/zhgh/staffbenefit/medicalMutualAid/aidFund/apply/index.html b/src/main/resources/views/platform/zhgh/staffbenefit/medicalMutualAid/aidFund/apply/index.html index 4748eda..ea5c86a 100644 --- a/src/main/resources/views/platform/zhgh/staffbenefit/medicalMutualAid/aidFund/apply/index.html +++ b/src/main/resources/views/platform/zhgh/staffbenefit/medicalMutualAid/aidFund/apply/index.html @@ -63,7 +63,7 @@ layout("/layouts/platform.html"){
- 中国地质大学(武汉)教职工重大疾病互助基金管理办法 + 西南财经大学教职工重大疾病互助基金管理办法
@@ -137,7 +137,7 @@ layout("/layouts/platform.html"){ this.$commonUtil.previewFile({ suffix: "pdf", downloadPath: "/platform/sys/file/download?id=orpgde84uggjmpflbob83tadan", - name: "中国地质大学(武汉)教职工重大疾病互助基金管理办法.pdf", + name: "西南财经大学教职工重大疾病互助基金管理办法.pdf", id: "orpgde84uggjmpflbob83tadan" }) }, diff --git a/src/main/resources/views/platform/zhghh5/democratic/proposal/transact/committeeFiling/index.html b/src/main/resources/views/platform/zhghh5/democratic/proposal/transact/committeeFiling/index.html new file mode 100644 index 0000000..1f53701 --- /dev/null +++ b/src/main/resources/views/platform/zhghh5/democratic/proposal/transact/committeeFiling/index.html @@ -0,0 +1,277 @@ + +
+ + + + + + + + + + + + + + + + + + +
+
{{formData.taskName}}
+ + + + + + + +
+
{{masterUnitLabel}}
+ + + + + + + + 确定 +
+
+ + +
+
协办单位
+ + + + + + + + 确定 +
+
+ +
+
+ 退回提案人 + 提交 +
+
+
+
+ + + diff --git a/src/main/resources/views/platform/zhghh5/democratic/proposal/transact/committeeFilingUnit/index.html b/src/main/resources/views/platform/zhghh5/democratic/proposal/transact/committeeFilingUnit/index.html new file mode 100644 index 0000000..35736fb --- /dev/null +++ b/src/main/resources/views/platform/zhghh5/democratic/proposal/transact/committeeFilingUnit/index.html @@ -0,0 +1,322 @@ + +
+ + + + + + + + + + + + + + + + + + +
+
{{formData.taskName}}
+ + + + + + + + +
+
{{masterUnitLabel}}
+ + + + + + + + 确定 +
+
+ + +
+
协办单位
+ + + + + + + + 确定 +
+
+ +
+
+ 取消 + 提交 +
+
+
+
+ + + diff --git a/src/main/resources/views/platform/zhghh5/democratic/proposal/transact/delegation/index.html b/src/main/resources/views/platform/zhghh5/democratic/proposal/transact/delegation/index.html index b3475db..dd18289 100644 --- a/src/main/resources/views/platform/zhghh5/democratic/proposal/transact/delegation/index.html +++ b/src/main/resources/views/platform/zhghh5/democratic/proposal/transact/delegation/index.html @@ -2,7 +2,7 @@ layout("/layouts/platform_h5.html"){ #-->
- + -