This commit is contained in:
=
2026-04-24 14:27:55 +08:00
parent c2f93eb4b3
commit 3207e0a594
46 changed files with 1993 additions and 325 deletions
@@ -15,7 +15,6 @@ import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.flow.service.FlowCommonService;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.culture.models.ActivityTissue;
@@ -151,8 +150,7 @@ public class ActivityCultureApplyActivityController {
return Result.success();
}
if (tissue.getIsEnrollSystem()) {
Sys_home_activity sysHomeActivity = tissue.covertToSysHomeActivity();
activityCultureService.insertOrUpdate(sysHomeActivity);
activityCultureService.syncHomeActivity(tissue);
}
return Result.success();
}
@@ -9,7 +9,6 @@ import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.flow.service.FlowCommonService;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.culture.models.ActivityTissue;
import com.budwk.app.zhgh.activity.culture.service.ActivityCultureService;
@@ -144,8 +143,7 @@ public class ActivityCultureAuditActivityController {
tissue.setIsUnseal(true);
activityCultureService.update(tissue);
if (args.getInt("submitType") == 1) {
Sys_home_activity sysHomeActivity = tissue.covertToSysHomeActivity();
activityCultureService.insertOrUpdate(sysHomeActivity);
activityCultureService.syncHomeActivity(tissue);
}
flowCommonService.executeTask(args);
return Result.success();
@@ -74,9 +74,15 @@ public class ActivityCultureInfoManageController {
@SaCheckPermission(value = {"activity.culture.infoManage.school", "activity.culture.infoManage.union", "activity.culture.infoManage.club"}, mode = SaMode.OR)
public Result activityStatusChange(@Valid String id, @Valid Boolean isUnseal) {
activityCultureService.update(Chain.make("isUnseal", isUnseal), Cnd.where("id", "=", id));
activityCultureService.dao().update(Sys_home_activity.class,
Chain.make("enable", isUnseal),
Cnd.where("id", "=", id));
ActivityTissue tissue = activityCultureService.fetch(id);
tissue.setIsUnseal(isUnseal);
if (Boolean.TRUE.equals(isUnseal)) {
activityCultureService.syncHomeActivity(tissue);
} else {
activityCultureService.dao().update(Sys_home_activity.class,
Chain.make("enable", false),
Cnd.where("id", "=", id));
}
return Result.success();
}
@@ -24,4 +24,14 @@ public interface ActivityCultureService extends BaseService<ActivityTissue> {
Object pageData(PageForm page, String year, String name, String unionId, Integer activity_type);
NutMap findOne(String id);
/**
* 同步文化活动到首页活动表。
* 1. 非报名类活动不推送首页,直接清理首页记录。
* 2. 校工会活动没有审核流,提交后默认可推首页。
* 3. 分工会、协会活动仅在已审核通过或已存在首页记录时更新首页信息。
*
* @param tissue 文化活动
*/
void syncHomeActivity(ActivityTissue tissue);
}
@@ -5,6 +5,7 @@ import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.model.Audit;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
@@ -80,6 +81,7 @@ public class ActivityCultureServiceImpl extends BaseServiceImpl<ActivityTissue>
@Override
public void doEditActivity(ActivityTissue tissue) {
update(tissue);
syncHomeActivity(tissue);
}
@Override
@@ -123,6 +125,7 @@ public class ActivityCultureServiceImpl extends BaseServiceImpl<ActivityTissue>
cnd.andEX("tissue.projectTypeCode", "!=", "50004");
cnd.andEX("tissue.unionId", "=", unionId);
cnd.andEX("tissue.activity_type", "=", activity_type);
cnd.groupBy("tissue.id");
if (Strings.isNotBlank(name)) {
cnd.where().andLike("tissue.name", name);
@@ -188,4 +191,38 @@ public class ActivityCultureServiceImpl extends BaseServiceImpl<ActivityTissue>
return nutMap;
}
@Override
public void syncHomeActivity(ActivityTissue tissue) {
if (tissue == null || Strings.isBlank(tissue.getId())) {
return;
}
// 非报名活动不需要出现在首页,编辑成活动管理模式时同步移除首页记录。
if (!Boolean.TRUE.equals(tissue.getIsEnrollSystem())) {
dao().delete(Sys_home_activity.class, tissue.getId());
return;
}
Sys_home_activity oldHomeActivity = dao().fetch(Sys_home_activity.class, tissue.getId());
boolean canCreateHomeActivity = (tissue.getActivity_type() != null
&& tissue.getActivity_type() == 40001)
|| Boolean.TRUE.equals(tissue.getIsUnseal())
|| oldHomeActivity != null;
if (!canCreateHomeActivity) {
return;
}
Sys_home_activity sysHomeActivity = tissue.covertToSysHomeActivity();
// 校工会文化活动没有审核环节,前端未回传启用状态时默认直接上首页。
if (tissue.getActivity_type() != null && tissue.getActivity_type() == 40001 && tissue.getIsUnseal() == null) {
sysHomeActivity.setEnable(true);
}
// 保留首页管理中手工设置的置顶、大图和排序,避免活动编辑时被覆盖。
if (oldHomeActivity != null) {
sysHomeActivity.setTop(oldHomeActivity.getTop());
sysHomeActivity.setPush(oldHomeActivity.getPush());
sysHomeActivity.setSortNo(oldHomeActivity.getSortNo());
}
dao().insertOrUpdate(sysHomeActivity);
}
}
@@ -60,6 +60,7 @@ public class H5ActivityWorksUploadCollectionController {
awc.*
FROM
`activity_works_collection` awc
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.andEX("YEAR(awc.startDateTime)", "=", year);
@@ -68,9 +69,9 @@ public class H5ActivityWorksUploadCollectionController {
cnd.and(new Static("now() > awc.startDateTime and now() < awc.endDateTime"));
}//查询已结束的
else if (activityType == 3) {
cnd.and(new Static("now() > awc.startDateTime"));
cnd.and(new Static("now() >= awc.endDateTime"));
} else if (activityType == 4) {
cnd.and(new Static("now() < awc.endDateTime"));
cnd.and(new Static("now() < awc.startDateTime"));
}
cnd.and("awc.enable", "=", 1);
cnd.and("awc.type", "=", 1);
@@ -28,6 +28,7 @@ import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.web.controllers.open.commons.service.CommonService;
import com.budwk.app.zhgh.club.model.ClubUser;
import com.budwk.app.zhgh.club.model.ClubUserApply;
import com.budwk.app.zhgh.club.service.ClubUserJoinService;
import com.budwk.app.zhgh.staffbenefit.condolence.model.Condolence;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -64,6 +65,8 @@ public class ClubUserJoinApplyController {
private FlowEngine flowEngine;
@Inject
private FlowCommonService flowCommonService;
@Inject
private ClubUserJoinService clubUserJoinService;
@At("/")
@SaCheckPermission("club.join.apply")
@@ -112,6 +115,8 @@ public class ClubUserJoinApplyController {
clubUserApply.setRoleCode(RoleConstant.CLUB_MEMBER.name());
clubUserApply.setMode(true);
clubUserApply.setApplyDate(new Date());
clubUserApply.setJoinTime(clubUserJoinService.resolveJoinTime(clubUserApply.getClubId(), clubUserApply.getUserId()));
clubUserApply.setExitTime(null);
dao.insertOrUpdate(clubUserApply);
return Result.success();
}
@@ -124,12 +129,10 @@ public class ClubUserJoinApplyController {
public Result submit(@Param("data") ClubUserApply clubUserApply,
@Param("mode") Boolean mode) {
if(mode == false) {
clubUserApply = dao.fetch(
ClubUserApply.class,
Cnd.where("userId", "=", SecurityUtil.getUserId())
.and("clubId", "=", clubUserApply.getClubId())
.desc("applyDate")
);
clubUserApply = clubUserJoinService.buildExitApply(clubUserApply.getClubId(), SecurityUtil.getUserId());
if (ObjectUtil.isEmpty(clubUserApply)) {
return Result.error("当前不是该社团成员,无法申请退出!");
}
}
ClubUserApply userApply = dao.fetch(ClubUserApply.class, Cnd.where("userId", "=", clubUserApply.getUserId()).and("clubId", "=", clubUserApply.getClubId()).and("mode", "=", mode).desc(ClubUserApply::getApplyDate));
if (ObjectUtil.isNotEmpty(userApply) && StrUtil.isNotBlank(userApply.getId())) {
@@ -142,9 +145,17 @@ public class ClubUserJoinApplyController {
if(!mode) {
clubUserApply.setId(null);
}
clubUserApply.setRoleCode(RoleConstant.CLUB_MEMBER.name());
clubUserApply.setMode(mode);
clubUserApply.setApplyDate(new Date());
Date applyDate = new Date();
clubUserApply.setApplyDate(applyDate);
if (mode) {
clubUserApply.setRoleCode(RoleConstant.CLUB_MEMBER.name());
clubUserApply.setJoinTime(clubUserJoinService.resolveJoinTime(clubUserApply.getClubId(), clubUserApply.getUserId()));
clubUserApply.setExitTime(null);
} else {
clubUserApply.setJoinTime(clubUserJoinService.resolveJoinTime(clubUserApply.getClubId(), clubUserApply.getUserId()));
clubUserApply.setExitTime(applyDate);
}
dao.insertOrUpdate(clubUserApply);
// 开启流程实例
@@ -22,6 +22,7 @@ import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.club.model.*;
import com.budwk.app.zhgh.club.param.ClubUserPageForm;
import com.budwk.app.zhgh.club.service.SysClubInfoManageService;
import com.budwk.app.zhgh.club.service.SysClubService;
import com.budwk.app.zhgh.club.service.impl.SysClubUserServiceImpl;
import io.swagger.annotations.ApiOperation;
import org.nutz.aop.interceptor.ioc.TransAop;
@@ -64,6 +65,8 @@ public class ClubChangeManagerController {
private FlowCommonService flowCommonService;
@Inject
private SysClubInfoManageService infoManageService;
@Inject
private SysClubService sysClubService;
@At("")
@Ok("beetl:/platform/zhgh/club/infoManage/change/index.html")
@@ -102,7 +105,10 @@ public class ClubChangeManagerController {
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("info.userId", "=", SecurityUtil.getUserId());
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_CLUB_ADMIN.name())) {
List<String> clubIdList = sysClubService.getMyManageClub().stream().map(SysClub::getId).toList();
cnd.and("info.clubId", "in", clubIdList);
}
cnd.andEX("year(info.creatTime)", "=", pageForm.getYear());
if (StrUtil.isNotBlank(pageForm.getClubName())) {
cnd.and("club.clubName", "LIKE", "%" + pageForm.getClubName() + "%");
@@ -112,6 +118,7 @@ public class ClubChangeManagerController {
} else {
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
}
cnd.groupBy("info.id");
sql.setCondition(cnd);
Pagination pagination = infoManageService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
List<NutMap> listMap = pagination.getList(NutMap.class);
@@ -130,6 +137,17 @@ public class ClubChangeManagerController {
@SaCheckPermission("club.infoManage.change")
@SLog(tag = "社团管理系统-信息管理", msg = "提交变更理事机构")
public Object submit(@Param("data") SysClubManager clubManager) {
Cnd cnd = Cnd.NEW();
cnd.and(SysClubManager::getClubId, "=", clubManager.getClubId());
if (StrUtil.isNotBlank(clubManager.getId())) {
cnd.and(SysClubManager::getId, "!=", clubManager.getId());
}
List<SysClubManager> managerList = dao.query(SysClubManager.class, cnd);
List<String> pendingProcessIds = managerList.stream().map(SysClubManager::getId).toList();
int pendingCount = dao.count(ProcessInstance.class, Cnd.where(ProcessInstance::getBusinessNo, "in", pendingProcessIds).and(ProcessInstance::getState, "not in", List.of(ProcessInstanceStateEnum.FINISHED.getCode(), ProcessInstanceStateEnum.REJECT.getCode())));
if (pendingCount > 0) {
return Result.error("您有该社团的申请记录尚未完成,请核对!");
}
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
String errMsg = "校工会审核" + clubManager.getPeriod() + "的换届报告通过后才能变更理事成员";
@@ -5,6 +5,7 @@ import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.PageUtil;
@@ -15,6 +16,7 @@ import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.flow.service.FlowCommonService;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.club.model.SysClub;
import com.budwk.app.zhgh.club.model.SysClubRefresh;
@@ -58,6 +60,8 @@ public class ClubRefreshReportController {
private FlowCommonService flowCommonService;
@Inject
private Dao dao;
@Inject
private SysClubService sysClubService;
@At("")
@Ok("beetl:/platform/zhgh/club/infoManage/refreshReport/index.html")
@@ -96,7 +100,10 @@ public class ClubRefreshReportController {
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("info.userId", "=", SecurityUtil.getUserId());
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_CLUB_ADMIN.name())) {
List<String> clubIdList = sysClubService.getMyManageClub().stream().map(SysClub::getId).toList();
cnd.and("info.clubId", "in", clubIdList);
}
cnd.andEX("year(info.creatTime)", "=", pageForm.getYear());
if (StrUtil.isNotBlank(pageForm.getClubName())) {
cnd.and("club.clubName", "LIKE", "%" + pageForm.getClubName() + "%");
@@ -119,6 +126,7 @@ public class ClubRefreshReportController {
public Object submit(@Param("data") SysClubRefresh clubRefresh) {
Cnd cnd = Cnd.NEW();
cnd.and(SysClubRefresh::getClubId, "=", clubRefresh.getClubId());
if(StrUtil.isNotBlank(clubRefresh.getId())) {
cnd.and(SysClubRefresh::getId, "!=", clubRefresh.getId());
}
@@ -4,6 +4,7 @@ import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result;
@@ -15,6 +16,7 @@ import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.flow.service.FlowCommonService;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.club.model.SysClub;
import com.budwk.app.zhgh.club.model.SysClubRefresh;
@@ -59,6 +61,8 @@ public class ClubRuleUpdateController {
private FlowCommonService flowCommonService;
@Inject
private Dao dao;
@Inject
private SysClubService sysClubService;
@At("")
@Ok("beetl:/platform/zhgh/club/infoManage/ruleUpdate/index.html")
@@ -97,7 +101,10 @@ public class ClubRuleUpdateController {
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("info.userId", "=", SecurityUtil.getUserId());
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_CLUB_ADMIN.name())) {
List<String> clubIdList = sysClubService.getMyManageClub().stream().map(SysClub::getId).toList();
cnd.and("info.clubId", "in", clubIdList);
}
cnd.andEX("year(info.creatTime)", "=", pageForm.getYear());
if (StrUtil.isNotBlank(pageForm.getClubName())) {
cnd.and("club.clubName", "LIKE", "%" + pageForm.getClubName() + "%");
@@ -120,6 +127,7 @@ public class ClubRuleUpdateController {
public Object submit(@Param("data")SysClubRule clubRule) {
Cnd cnd = Cnd.NEW();
cnd.and(SysClubRule::getClubId, "=", clubRule.getClubId());
if(StrUtil.isNotBlank(clubRule.getId())) {
cnd.and(SysClubRule::getId, "!=", clubRule.getId());
}
@@ -85,4 +85,14 @@ public class ClubUserApply extends BaseModel {
@Comment("申请时间")
@ColDefine(type = ColType.DATETIME)
private Date applyDate;
@Column
@Comment("入会时间")
@ColDefine(type = ColType.DATETIME)
private Date joinTime;
@Column
@Comment("退会时间")
@ColDefine(type = ColType.DATETIME)
private Date exitTime;
}
@@ -47,7 +47,7 @@ public class SysClubExamineRegister extends BaseModel {
@Column
@Comment("社团名称")
@ColDefine(type = ColType.VARCHAR, width = 32)
@ColDefine(type = ColType.VARCHAR)
private String clubName;
@Column
@@ -4,4 +4,25 @@ import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.club.model.ClubUserApply;
public interface ClubUserJoinService extends BaseService<ClubUserApply> {
/**
* 构造退会申请数据。
* 当用户没有历史入会申请记录时,使用当前社团成员关系和人员基础信息兜底生成一份退会申请,
* 以保证会长等通过社团注册直接入会的人员也能正常发起退会流程。
*
* @param clubId 社团ID
* @param userId 用户ID
* @return 退会申请对象,不存在当前成员关系时返回null
*/
ClubUserApply buildExitApply(String clubId, String userId);
/**
* 获取社团成员的入会时间,优先取历史有效入会申请记录,
* 没有时兜底为社团创办时间。
*
* @param clubId 社团ID
* @param userId 用户ID
* @return 入会时间
*/
java.util.Date resolveJoinTime(String clubId, String userId);
}
@@ -5,6 +5,7 @@ import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.club.param.ClubUserPageForm;
import com.budwk.app.zhgh.club.vo.ClubCommonPageVo;
import com.budwk.app.zhgh.club.vo.ClubUserCommonPageVo;
import com.budwk.app.zhgh.club.vo.ClubUserJoinVo;
import org.nutz.lang.util.NutMap;
import javax.servlet.http.HttpServletResponse;
@@ -23,8 +24,12 @@ public interface SysClubInfoManageService extends BaseService<ClubCommonPageVo>
Pagination<ClubUserCommonPageVo> infoManageUserPageData(@Valid ClubUserPageForm pageForm);
Pagination<ClubUserCommonPageVo> exitManagePageData(@Valid ClubUserPageForm pageForm);
Pagination<ClubUserCommonPageVo> clubManagePersonAuditPageData(@Valid ClubUserPageForm pageForm);
ClubUserJoinVo exitManageInfo(String id);
List<NutMap> getClubTreeData();
/**
@@ -1,14 +1,94 @@
package com.budwk.app.zhgh.club.service.impl;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import cn.hutool.core.date.DateUtil;
import com.budwk.app.sys.views.View_user;
import com.budwk.app.zhgh.club.model.ClubUser;
import com.budwk.app.zhgh.club.model.ClubUserApply;
import com.budwk.app.zhgh.club.model.SysClub;
import com.budwk.app.zhgh.club.service.ClubUserJoinService;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.Date;
import java.util.List;
@IocBean(args = {"refer:dao"})
public class ClubUserJoinServiceImpl extends BaseServiceImpl<ClubUserApply> implements ClubUserJoinService {
public ClubUserJoinServiceImpl(Dao dao) {
super(dao);
}
@Override
public ClubUserApply buildExitApply(String clubId, String userId) {
// 优先复用最近一次申请记录中的补充信息,避免用户重复填写基础资料。
ClubUserApply latestApply = fetch(
Cnd.where("userId", "=", userId)
.and("clubId", "=", clubId)
.desc(ClubUserApply::getApplyDate)
);
if (latestApply != null) {
latestApply.setId(null);
return latestApply;
}
// 对于会长等注册社团时直接入会的人员,没有申请记录时从当前成员信息兜底生成退会申请。
ClubUser clubUser = dao().fetch(ClubUser.class, Cnd.where("clubId", "=", clubId).and("userId", "=", userId));
if (clubUser == null) {
return null;
}
View_user viewUser = dao().fetch(View_user.class, Cnd.where("id", "=", userId));
ClubUserApply clubUserApply = new ClubUserApply();
clubUserApply.setClubId(clubId);
clubUserApply.setUserId(userId);
clubUserApply.setClubPosition(clubUser.getClubPosition());
clubUserApply.setEmail(clubUser.getEmail());
clubUserApply.setAvatar(clubUser.getAvatar());
clubUserApply.setSameTimeJoinOtherClubSituation(clubUser.getSameTimeJoinOtherClubSituation());
clubUserApply.setAwardsExperience(clubUser.getAwardsExperience());
clubUserApply.setJoinTime(resolveJoinTime(clubId, userId));
List<String> roleCodes = clubUser.getRoleCode();
if (roleCodes != null && !roleCodes.isEmpty()) {
String exitRoleCode = roleCodes.stream()
.filter(roleCode -> !RoleConstant.CLUB_MEMBER.name().equals(roleCode))
.findFirst()
.orElse(RoleConstant.CLUB_MEMBER.name());
clubUserApply.setRoleCode(exitRoleCode);
} else {
clubUserApply.setRoleCode(RoleConstant.CLUB_MEMBER.name());
}
if (viewUser != null) {
clubUserApply.setBirthday(viewUser.getBirthday());
clubUserApply.setMobile(viewUser.getMobile());
if (clubUserApply.getEmail() == null) {
clubUserApply.setEmail(viewUser.getEmail());
}
if (clubUserApply.getAvatar() == null) {
clubUserApply.setAvatar(viewUser.getAvatar());
}
}
return clubUserApply;
}
@Override
public Date resolveJoinTime(String clubId, String userId) {
ClubUserApply latestJoinApply = fetch(
Cnd.where("userId", "=", userId)
.and("clubId", "=", clubId)
.and("mode", "=", true)
.desc(ClubUserApply::getApplyDate)
);
if (latestJoinApply != null && latestJoinApply.getJoinTime() != null) {
return latestJoinApply.getJoinTime();
}
SysClub club = dao().fetch(SysClub.class, clubId);
if (club != null && club.getFoundTime() != null) {
return DateUtil.parse(club.getFoundTime());
}
return null;
}
}
@@ -20,6 +20,7 @@ import com.budwk.app.zhgh.club.service.SysClubInfoManageService;
import com.budwk.app.zhgh.club.service.SysClubService;
import com.budwk.app.zhgh.club.vo.ClubCommonPageVo;
import com.budwk.app.zhgh.club.vo.ClubUserCommonPageVo;
import com.budwk.app.zhgh.club.vo.ClubUserJoinVo;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
@@ -139,11 +140,27 @@ public class SysClubInfoManageServiceImpl extends BaseServiceImpl<ClubCommonPage
u.unitid as unitId,
u.mobile,
club.clubName,
u.unitname as unitName
u.unitname as unitName,
DATE_FORMAT(IFNULL(joinInfo.joinTime, club.foundTime), '%Y-%m-%d %H:%i:%s') as joinTime
FROM
club_user scu
LEFT JOIN sys_club club ON scu.clubId = club.id
RIGHT JOIN `vw_user` u ON scu.userId = u.id
LEFT JOIN (
SELECT
joinApply.userId,
joinApply.clubId,
MAX(joinApply.joinTime) AS joinTime
FROM
club_user_apply joinApply
LEFT JOIN wf_process_instance joinIns ON joinIns.businessNo = joinApply.id
WHERE
joinApply.mode = true
AND joinIns.state = 20
GROUP BY
joinApply.userId,
joinApply.clubId
) joinInfo ON joinInfo.userId = scu.userId AND joinInfo.clubId = scu.clubId
$condition
$order
""");
@@ -206,6 +223,96 @@ public class SysClubInfoManageServiceImpl extends BaseServiceImpl<ClubCommonPage
return listPageVO;
}
@Override
public Pagination<ClubUserCommonPageVo> exitManagePageData(ClubUserPageForm pageForm) {
Sql sql = Sqls.create("""
SELECT
info.id,
info.clubId,
info.userId,
info.roleCode AS applyRoleCode,
info.clubPosition,
info.email,
info.mobile,
info.birthday,
info.avatar,
info.sameTimeJoinOtherClubSituation,
info.awardsExperience,
info.signature,
info.applyDate,
u.username AS userName,
u.loginname AS loginName,
u.sex,
u.personType,
u.userState,
club.clubName,
u.unitname AS unitName,
DATE_FORMAT(IFNULL(info.joinTime, club.foundTime), '%Y-%m-%d %H:%i:%s') AS joinTime,
DATE_FORMAT(info.exitTime, '%Y-%m-%d %H:%i:%s') AS exitTime,
ins.id AS instanceId,
ins.businessNo,
ins.state instanceState,
ins.variable instanceVariable,
ins.processDefineId instanceProcessDefineId
FROM
club_user_apply info
LEFT JOIN sys_club club ON club.id = info.clubId
LEFT JOIN vw_user u ON u.id = info.userId
LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("info.mode", "=", false);
cnd.and("ins.state", "=", ProcessInstanceStateEnum.FINISHED.getCode());
cnd.andEX("info.clubId", "=", pageForm.getClubId());
cnd.andEX("u.personType", "=", pageForm.getPersonType());
cnd.andEX("u.userState", "=", pageForm.getUserState());
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
cnd.and(Cnd.exps("u.loginname", "like", "%" + pageForm.getSearchKeyword() + "%").or("u.username", "like", "%" + pageForm.getSearchKeyword() + "%"));
}
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name(), RoleConstant.SCHOOL_UNION_CLUB_ADMIN.name())) {
List<String> clubIdList = commonService.findUserRoleByRoleCode(List.of(RoleConstant.CLUB_PRESIDENT.name(), RoleConstant.CLUB_VICE_PRESIDENT.name(), RoleConstant.CLUB_SECRETARY.name(), RoleConstant.CLUB_VICE_SECRETARY.name(), RoleConstant.CLUB_OPERATOR.name()));
List<Sys_user_role> clubList = dao().query(Sys_user_role.class, Cnd.where("roleId", "in", clubIdList).and("userId", "=", SecurityUtil.getUserId()).and("clubId", "is not", null));
cnd.and("info.clubId", "in", clubList.stream().map(Sys_user_role::getClubId).toList());
}
cnd.groupBy("info.id");
cnd.desc("info.applyDate");
sql.setCondition(cnd);
Pagination<ClubUserCommonPageVo> pagination = listPageVO(pageForm, sql, ClubUserCommonPageVo.class);
List<ClubUserCommonPageVo> list = pagination.getList(ClubUserCommonPageVo.class);
for (ClubUserCommonPageVo vo : list) {
vo.setRoleName(SysClubUserServiceImpl.convertRoleName(List.of(vo.getApplyRoleCode())));
}
pagination.setList(list);
return pagination;
}
@Override
public ClubUserJoinVo exitManageInfo(String id) {
Sql sql = Sqls.create("""
SELECT
cua.*,
club.clubName,
u.loginname AS loginName,
u.username AS userName,
u.sex,
u.mobile,
u.unitName,
u.unionName,
u.technicalTitle,
u.education,
u.academicDegree,
u.position
FROM
club_user_apply cua
LEFT JOIN vw_user u ON u.id = cua.userId
LEFT JOIN sys_club club ON club.id = cua.clubId
WHERE cua.id = @id
""");
sql.setParam("id", id);
return fetchVO(sql, ClubUserJoinVo.class);
}
@Override
public Pagination<ClubUserCommonPageVo> clubManagePersonAuditPageData(ClubUserPageForm pageForm) {
Sql sql = Sqls.create("""
@@ -29,6 +29,9 @@ public class ClubUserCommonPageVo extends ClubUser {
private String unionName;
private String birthday;
private String unionId;
private String joinTime;
private String exitTime;
private String applyRoleCode;
private String roleName;
}
@@ -80,7 +80,9 @@ public class OutlayManageClubQueryController {
SELECT * FROM `outlay_manage_club` $condition
""");
cnd.andEX("year", "=", year);
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
// if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
// 现逻辑:系统管理员、校工会社团管理员可查看全部社团分页数据,会长仅查看自己负责的社团。
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_CLUB_ADMIN.name())) {
List<String> roleIds = sysRoleService.getRoleIdsByCode(List.of(RoleConstant.CLUB_PRESIDENT, RoleConstant.CLUB_SECRETARY));
List<Sys_user_role> userRoles = clubQueryService.dao().query(Sys_user_role.class,
Cnd.where("userId", "=", SecurityUtil.getUserId())
@@ -75,7 +75,7 @@ public class OutlayManageClub extends BaseModel implements Serializable {
@Column
@Comment("社团名称")
@ColDefine(type = ColType.VARCHAR, width = 32)
@ColDefine(type = ColType.VARCHAR)
private String clubName;
@Column
@@ -27,6 +27,7 @@ import com.budwk.app.zhgh.staffbenefit.condolence.service.CondolenceTypeService;
import com.budwk.app.zhgh.dayofficework.unionReimburse.model.UnionReimburse;
import com.budwk.app.zhgh.staffbenefit.condolence.model.CondolenceType;
import com.budwk.app.zhgh.dayofficework.unionReimburse.service.UnionReimburseService;
import com.budwk.app.zhgh.dayofficework.unionReimburse.vo.UnionReimburseBankHistoryVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
@@ -85,19 +86,7 @@ public class UnionReimburseApplyController {
@SaCheckPermission(value = {"unionReimburse.apply", "h5.unionReimburse.apply"}, mode = SaMode.OR)
@SLog(type = "unionReimburse", tag = "工会报销-报销申请", msg = "保存报销申请")
public Result save(@Param("data") UnionReimburse unionReimburse) {
if (StrUtil.isBlank(unionReimburse.getId())) {
unionReimburse.setCreateTime(new Date());
String documentNo = generateDocumentNo();
unionReimburse.setDocumentNo(documentNo);
}
unionReimburse.setStateId(1);//待提交
if (("UNION_REIMBURSE_PROJECT_1").equals(unionReimburse.getReimburseProject())){
unionReimburse.setRealMoney(unionReimburse.getCondolenceMoney());
}else {
unionReimburse.setRealMoney(unionReimburse.getMoney());
}
dao.insertOrUpdate(unionReimburse);
return Result.success();
return unionReimburseService.saveApply(unionReimburse, 1);
}
@At
@@ -105,18 +94,10 @@ public class UnionReimburseApplyController {
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission(value = {"unionReimburse.apply", "h5.unionReimburse.apply"}, mode = SaMode.OR)
public Result submit(@Param("data") UnionReimburse unionReimburse) {
if (StrUtil.isBlank(unionReimburse.getId())) {
unionReimburse.setCreateTime(new Date());
String documentNo = generateDocumentNo();
unionReimburse.setDocumentNo(documentNo);
Result result = unionReimburseService.saveApply(unionReimburse, 2);
if (result.getCode() != 0) {
return result;
}
unionReimburse.setStateId(2);//待审核
if (("UNION_REIMBURSE_PROJECT_1").equals(unionReimburse.getReimburseProject())){
unionReimburse.setRealMoney(unionReimburse.getCondolenceMoney());
}else {
unionReimburse.setRealMoney(unionReimburse.getMoney());
}
dao.insertOrUpdate(unionReimburse);
// // 开启流程实例
// Dict args = Dict.create();
// args.set(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.APPLY.getCode());
@@ -153,7 +134,7 @@ public class UnionReimburseApplyController {
@SaCheckLogin
@ApiOperation("获取当前登录人的申请信息")
public Result info(@Param("id") String id) {
UnionReimburse unionReimburse = dao.fetch(UnionReimburse.class, id);
UnionReimburse unionReimburse = unionReimburseService.getApplyForm(id);
return Result.success(unionReimburse);
}
@@ -190,14 +171,9 @@ public class UnionReimburseApplyController {
cnd.and(seg);
}
cnd.and(View_user::getId, "!=", SecurityUtil.getUserId());
/*if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
if (AuthUtil.hasRoleOr(RoleConstant.BRANCH_UNION_ADMIN.name(), RoleConstant.BRANCH_UNION_CHAIRMAN.name())) {
cnd.and(View_user::getUnionId, "=", SecurityUtil.getUnionId());
} else {
cnd.and(View_user::getId, "=", SecurityUtil.getUserId());
}
}*/
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
cnd.and(View_user::getUnionId, "=", SecurityUtil.getUnionId());
}
sql.setCondition(cnd);
Pagination pagination = unionReimburseService.listPageMap(1, 50, sql);
return Result.success(pagination.getList());
@@ -215,20 +191,25 @@ public class UnionReimburseApplyController {
@ApiOperation("查询厉害账号信息")
@SaCheckPermission(value = {"unionReimburse.apply", "h5.unionReimburse.apply"}, mode = SaMode.OR)
public Result findUserBankHistory(String payer) {
Sql sql = Sqls.create("""
SELECT DISTINCT
bankCardNumber,
bankOfDeposit
FROM union_reimburse
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.andEX("payer", "=", payer);
sql.setCondition(cnd);
List<NutMap> result = unionReimburseService.listMap(sql);
List<UnionReimburseBankHistoryVO> result = unionReimburseService.getUserBankHistory(payer);
return Result.success(result);
}
@At
@ApiOperation("校验发票号码是否重复")
@SaCheckPermission(value = {"unionReimburse.apply", "h5.unionReimburse.apply"}, mode = SaMode.OR)
public Result checkInvoiceDuplicate(String invoiceNo, String reimburseId) {
Result result = unionReimburseService.checkInvoiceDuplicate(invoiceNo, reimburseId);
return result == null ? Result.success() : result;
}
@At
@ApiOperation("自动识别发票信息")
@SaCheckPermission(value = {"unionReimburse.apply", "h5.unionReimburse.apply"}, mode = SaMode.OR)
public Result recognizeInvoice(String fileId, @Param("verifySwitch") Boolean verifySwitch) {
return unionReimburseService.recognizeInvoice(fileId, verifySwitch);
}
@At
@ApiOperation("查询校工会经费余额")
@@ -281,39 +262,4 @@ public class UnionReimburseApplyController {
// 如果没有找到该社团的经费记录,返回0
return Result.success(0.0);
}
@At
@ApiOperation("生成文件编号")
private String generateDocumentNo() {
int currentYear = DateUtil.thisYear();
String yearPrefix = String.valueOf(currentYear);
org.nutz.dao.Cnd cnd = org.nutz.dao.Cnd.where("documentNo", "LIKE", yearPrefix + "%");
cnd.desc("documentNo");
cnd.limit(1);
List<UnionReimburse> list = dao.query(UnionReimburse.class, cnd);
int nextSeq = 1; // 默认从01开始
if (!list.isEmpty()) {
String maxNo = list.get(0).getDocumentNo();
if (maxNo != null && maxNo.startsWith(yearPrefix)) {
// 提取序号部分(去掉年份前缀)
String seqPart = maxNo.substring(yearPrefix.length());
try {
int currentMaxSeq = Integer.parseInt(seqPart);
nextSeq = currentMaxSeq + 1;
} catch (NumberFormatException e) {
// 如果解析失败,使用默认值1
nextSeq = 1;
}
}
}
// 格式化序号为两位数
String seqStr = String.format("%02d", nextSeq);
return yearPrefix + seqStr;
}
}
@@ -3,11 +3,13 @@ package com.budwk.app.zhgh.dayofficework.unionReimburse.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.dayofficework.unionReimburse.service.UnionReimburseService;
import io.swagger.annotations.Api;
@@ -105,6 +107,9 @@ public class UnionReimburseCertifierUserSignController {
// 工会、单位名称查询条件
cnd.andEX("info.unionId", "=", unionId);
cnd.andEX("info.unitId", "=", unitId);
if (!isAdmin()) {
cnd.andEX("info.unionId", "=", SecurityUtil.getUnionId());
}
// 报销项目查询条件
cnd.andEX("info.reimburseProject", "=", reimburseProject);
@@ -116,10 +121,11 @@ public class UnionReimburseCertifierUserSignController {
seg.orLike("info.loginName",pageForm.getSearchKeyword());
cnd.and(seg);
}
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
String orderField = normalizeSortField(pageForm.getPageOrderName());
if (StrUtil.isAllBlank(orderField, pageForm.getPageOrderBy())) {
cnd.desc("info.createTime");
} else {
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
cnd.orderBy(orderField, PageUtil.getOrder(pageForm.getPageOrderBy()));
}
cnd.groupBy("t.id");
cnd.desc("t.createdAt");
@@ -128,4 +134,21 @@ public class UnionReimburseCertifierUserSignController {
return Result.success(pagination);
}
private boolean isAdmin() {
return AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name());
}
private String normalizeSortField(String pageOrderName) {
if (StrUtil.isBlank(pageOrderName)) {
return null;
}
return switch (pageOrderName) {
case "loginName" -> "info.loginName";
case "unionName" -> "info.unionName";
case "unitName" -> "info.unitName";
case "createTime" -> "info.createTime";
case "reimburseProject" -> "info.reimburseProject";
default -> null;
};
}
}
@@ -12,6 +12,7 @@ import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.sys.models.Sys_dict;
@@ -93,6 +94,9 @@ public class UnionReimburseCollectController {
// 工会、单位名称查询条件
cnd.andEX("info.unionId", "=", unionId);
cnd.andEX("info.unitId", "=", unitId);
if (!isAdmin()) {
cnd.andEX("info.unionId", "=", SecurityUtil.getUnionId());
}
// 报销项目查询条件
cnd.andEX("info.reimburseProject", "=", reimburseProject);
@@ -106,7 +110,7 @@ public class UnionReimburseCollectController {
cnd.and("info.stateId", "in", List.of(3, 2));
cnd.desc("info.createTime");
applyListSort(cnd, pageForm);
sql.setCondition(cnd);
Pagination<NutMap> pagination = unionReimburseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -149,7 +153,7 @@ public class UnionReimburseCollectController {
cnd.andEX("YEAR(rei.createTime)", "=", year);
cnd.andEX("u.unitid", "=", unitId);
cnd.andEX("u.unionid", "=", unionId);
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
if (!isAdmin()) {
cnd.andEX("u.unionid", "=", SecurityUtil.getUnionId());
}
cnd.desc("rei.createTime");
@@ -166,8 +170,10 @@ public class UnionReimburseCollectController {
try{
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment;filename=" + new String(("收款人名册.xls").getBytes("utf-8"), "ISO8859-1"));
Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), excelExportEntities, list);
response.setHeader("Content-Disposition", "attachment;filename=" + new String(("收款人名册.xlsx").getBytes("utf-8"), "ISO8859-1"));
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, excelExportEntities, list);
workbook.write(response.getOutputStream());
}catch (Exception e){
e.printStackTrace();
@@ -197,6 +203,9 @@ public class UnionReimburseCollectController {
Cnd cnd = buildCondition(year, unionId, unitId, reimburseProject, userName, "t1.");
// 只查询流程实例状态为20的数据(已完成状态)
cnd.and("ins.state", "=", 20);
if (!isAdmin()) {
cnd.andEX("t1.unionId", "=", SecurityUtil.getUnionId());
}
cnd.desc("t1.createTime");
sql.setCondition(cnd);
@@ -275,4 +284,34 @@ public class UnionReimburseCollectController {
return cnd;
}
private boolean isAdmin() {
return AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name());
}
/**
* 收集页列表只允许固定字段排序,前端传入原序时回退到默认申请时间倒序。
*/
private void applyListSort(Cnd cnd, PageForm pageForm) {
String orderField = normalizeSortField(pageForm.getPageOrderName());
if (StrUtil.isAllBlank(orderField, pageForm.getPageOrderBy())) {
cnd.desc("info.createTime");
return;
}
cnd.orderBy(orderField, PageUtil.getOrder(pageForm.getPageOrderBy()));
}
private String normalizeSortField(String pageOrderName) {
if (StrUtil.isBlank(pageOrderName)) {
return null;
}
return switch (pageOrderName) {
case "loginName" -> "info.loginName";
case "unionName" -> "info.unionName";
case "unitName" -> "info.unitName";
case "createTime" -> "info.createTime";
case "reimburseProject" -> "info.reimburseProject";
default -> null;
};
}
}
@@ -9,14 +9,17 @@ import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.model.Audit;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.base.utils.MoneyUtil;
import com.budwk.app.base.utils.OfficePlusUtil;
import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.base.utils.SysOfficeTemplateUtil;
import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.dayofficework.unionReimburse.model.UnionReimburse;
import com.budwk.app.zhgh.dayofficework.unionReimburse.service.UnionReimburseService;
@@ -133,6 +136,9 @@ public class UnionReimburseMineController {
// 工会、单位名称查询条件
cnd.andEX("info.unionId", "=", unionId);
cnd.andEX("info.unitId", "=", unitId);
if (!isAdmin()) {
cnd.andEX("info.unionId", "=", SecurityUtil.getUnionId());
}
// 报销项目查询条件
cnd.andEX("info.reimburseProject", "=", reimburseProject);
@@ -143,13 +149,40 @@ public class UnionReimburseMineController {
seg.orLike("info.loginName", "%" + searchKeyword + "%");
cnd.and(seg);
}
cnd.desc("info.createTime");
applyListSort(cnd, pageForm);
cnd.and("info.userId", "=", SecurityUtil.getUserId());
sql.setCondition(cnd);
Pagination<NutMap> pagination = unionReimburseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
}
private boolean isAdmin() {
return AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name());
}
private void applyListSort(Cnd cnd, PageForm pageForm) {
String orderField = normalizeSortField(pageForm.getPageOrderName());
if (StrUtil.isAllBlank(orderField, pageForm.getPageOrderBy())) {
cnd.desc("info.createTime");
return;
}
cnd.orderBy(orderField, PageUtil.getOrder(pageForm.getPageOrderBy()));
}
private String normalizeSortField(String pageOrderName) {
if (StrUtil.isBlank(pageOrderName)) {
return null;
}
return switch (pageOrderName) {
case "loginName" -> "info.loginName";
case "unionName" -> "info.unionName";
case "unitName" -> "info.unitName";
case "createTime" -> "info.createTime";
case "reimburseProject" -> "info.reimburseProject";
default -> null;
};
}
@At
@ApiOperation("删除")
@Aop(TransAop.READ_COMMITTED)
@@ -1,7 +1,5 @@
package com.budwk.app.zhgh.dayofficework.unionReimburse.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.util.StrUtil;
@@ -10,15 +8,12 @@ import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.dayofficework.unionReimburse.model.UnionReimburse;
import com.budwk.app.zhgh.dayofficework.unionReimburse.service.UnionReimburseService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
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;
@@ -35,8 +30,6 @@ import org.nutz.mvc.annotation.Param;
import java.util.Date;
import java.util.List;
import static org.openjdk.nashorn.internal.runtime.regexp.joni.Config.log;
@IocBean
@At("/platform/unionReimburse/review")
@Ok("json:full")
@@ -49,12 +42,12 @@ public class UnionReimburseReviewController {
@Inject
private UnionReimburseService unionReimburseService;
@At("/index")
@Ok("beetl:/platform/zhgh/dayofficework/unionReimburse/review/index.html")
@SaCheckPermission("unionReimburse.review")
public void index() {
}
@At("/h5")
@Ok("beetl:/platform/zhghh5/dayofficework/unionReimburse/review/index.html")
@SaCheckPermission("h5.unionReimburse.review")
@@ -71,37 +64,26 @@ public class UnionReimburseReviewController {
String unitId,
String reimburseProject) {
Sql sql = Sqls.create("""
SELECT
SELECT
info.*
FROM
union_reimburse info
$condition
$condition
""");
Cnd cnd = Cnd.NEW();
if (approval) {
// 已审核:查询 stateId 为 3(报销成功)、4(拒绝)、5(退回)的记录
cnd.and("info.stateId", "in", List.of(3, 4, 5));
} else {
// 未审核:只查询 stateId 为 2(待审核确认)的记录
cnd.and("info.stateId", "=", 2);
}
// 年度查询条件
cnd.andEX("year(info.createTime)", "=", year);
// 工会、单位名称查询条件
cnd.andEX("info.unionId", "=", unionId);
cnd.andEX("info.unitId", "=", unitId);
// 报销项目查询条件
cnd.andEX("info.reimburseProject", "=", reimburseProject);
// 姓名和工号查询条件
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
SqlExpressionGroup seg = new SqlExpressionGroup();
seg.orLike("info.userName",pageForm.getSearchKeyword());
seg.orLike("info.loginName",pageForm.getSearchKeyword());
seg.orLike("info.userName", pageForm.getSearchKeyword());
seg.orLike("info.loginName", pageForm.getSearchKeyword());
cnd.and(seg);
}
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
@@ -119,21 +101,18 @@ public class UnionReimburseReviewController {
@ApiOperation("审核")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission(value = {"unionReimburse.review", "h5.unionReimburse.review"}, mode = SaMode.OR)
@SLog( tag = "审核工会报销", msg = "审核工会报销")
public Result reviewTask(@Param("data") UnionReimburse unionReimburse,String submitType) {
@SLog(tag = "审核工会报销", msg = "审核工会报销")
public Result reviewTask(@Param("data") UnionReimburse unionReimburse, String submitType) {
if (submitType == null || !List.of("3", "4", "5").contains(submitType)) {
return Result.error("无效的审核操作类型");
}
UnionReimburse dbRecord = dao.fetch(UnionReimburse.class, unionReimburse.getId());
if (dbRecord == null) {
return Result.error("记录不存在");
}
dbRecord.setReviewTime(new Date());
dbRecord.setStateId(Integer.parseInt(submitType));
dbRecord.setReviewOpinion(unionReimburse.getReviewOpinion());
dao.update(dbRecord);
return Result.success();
}
@@ -142,7 +121,7 @@ public class UnionReimburseReviewController {
@ApiOperation("一键审核")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission(value = {"unionReimburse.review", "h5.unionReimburse.review"}, mode = SaMode.OR)
@SLog( tag = "一键审核", msg = "一键审核")
@SLog(tag = "一键审核", msg = "一键审核")
public Result allReview() {
try {
List<UnionReimburse> reimbursements = unionReimburseService.query(Cnd.where("stateId", "=", 2));
@@ -158,6 +137,4 @@ public class UnionReimburseReviewController {
return Result.error("一键审核失败: " + e.getMessage());
}
}
}
@@ -6,11 +6,14 @@ import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.sys.services.SysDictService;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.dayofficework.unionReimburse.param.UnionReimbursePageForm;
import com.budwk.app.zhgh.dayofficework.unionReimburse.service.UnionReimburseService;
import io.swagger.annotations.Api;
@@ -93,6 +96,8 @@ public class UnionReimburseStatisticsController {
}
if (Strings.isNotBlank(unionId)) {
sql.setVar("unionIdCnd", String.format("AND us.unionId = '%s' ", unionId));
} else if (!isAdmin()) {
sql.setVar("unionIdCnd", String.format("AND us.unionId = '%s' ", SecurityUtil.getUnionId()));
}
if (Strings.isNotBlank(condolenceTypeId)) {
sql.setVar("typeCnd", String.format("AND con.condolenceTypeId = '%s' ", condolenceTypeId));
@@ -150,4 +155,7 @@ public class UnionReimburseStatisticsController {
}
private boolean isAdmin() {
return AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name());
}
}
@@ -415,4 +415,9 @@ public class UnionReimburse extends BaseModel {
@ColDefine(type = ColType.DATETIME)
private Date reviewTime;
/**
* 发票明细列表仅用于申请页表单回显和提交,不直接映射主表字段。
*/
private List<UnionReimburseInvoiceDetail> invoiceDetails;
}
@@ -1,11 +1,41 @@
package com.budwk.app.zhgh.dayofficework.unionReimburse.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.dayofficework.unionReimburse.model.UnionReimburse;
import com.budwk.app.zhgh.dayofficework.unionReimburse.param.UnionReimbursePageForm;
import com.budwk.app.zhgh.dayofficework.unionReimburse.vo.UnionReimburseBankHistoryVO;
import org.nutz.dao.sql.Sql;
import java.util.List;
public interface UnionReimburseService extends BaseService<UnionReimburse> {
Sql getSql(UnionReimbursePageForm pageForm);
/**
* 保存或提交报销申请,并同步保存发票明细。
*/
Result saveApply(UnionReimburse unionReimburse, int stateId);
/**
* 查询申请表单详情,补齐发票明细回显数据。
*/
UnionReimburse getApplyForm(String id);
/**
* 查询付款人的历史收款信息。
*/
List<UnionReimburseBankHistoryVO> getUserBankHistory(String payer);
/**
* 校验发票号码是否与历史报销记录重复。
*/
Result checkInvoiceDuplicate(String invoiceNo, String reimburseId);
/**
* 识别单张发票文件,并在需要时调用腾讯云验真接口补齐票面信息。
*/
Result recognizeInvoice(String fileId, Boolean verifySwitch);
}
@@ -1,24 +1,69 @@
package com.budwk.app.zhgh.dayofficework.unionReimburse.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.sys.models.Sys_file;
import com.budwk.app.sys.services.SysFileService;
import com.budwk.app.zhgh.dayofficework.unionReimburse.model.UnionReimburse;
import com.budwk.app.zhgh.dayofficework.unionReimburse.model.UnionReimburseInvoiceDetail;
import com.budwk.app.zhgh.dayofficework.unionReimburse.param.UnionReimbursePageForm;
import com.budwk.app.zhgh.dayofficework.unionReimburse.service.UnionReimburseService;
import com.budwk.app.zhgh.dayofficework.unionReimburse.vo.UnionReimburseBankHistoryVO;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import lombok.extern.slf4j.Slf4j;
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.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
import org.nutz.lang.util.NutMap;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.common.profile.ClientProfile;
import com.tencentcloudapi.common.profile.HttpProfile;
import com.tencentcloudapi.ocr.v20181119.OcrClient;
import com.tencentcloudapi.ocr.v20181119.models.TextVatInvoice;
import com.tencentcloudapi.ocr.v20181119.models.VatInvoice;
import com.tencentcloudapi.ocr.v20181119.models.VatInvoiceItem;
import com.tencentcloudapi.ocr.v20181119.models.VatInvoiceOCRRequest;
import com.tencentcloudapi.ocr.v20181119.models.VatInvoiceOCRResponse;
import com.tencentcloudapi.ocr.v20181119.models.VatInvoiceVerifyRequest;
import com.tencentcloudapi.ocr.v20181119.models.VatInvoiceVerifyResponse;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Date;
import java.util.stream.Collectors;
@Slf4j
@IocBean(args = {"refer:dao"})
public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> implements UnionReimburseService {
public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> implements UnionReimburseService {
@Inject
private SysFileService sysFileService;
@Inject
private PropertiesProxy conf;
public UnionReimburseServiceImpl(Dao dao) {
super(dao);
}
@@ -35,30 +80,609 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse>
$condition
""");
Cnd cnd = Cnd.NEW();
// 年度查询条件
cnd.andEX("year(info.createTime)", "=", pageForm.getYear());
// 工会、单位名称查询条件
cnd.andEX("info.condolenceUnionId", "=", pageForm.getUnionId());
cnd.andEX("info.unitId", "=", pageForm.getUnitId());
cnd.andEX("info.condolenceTypeId", "=", pageForm.getCondolenceTypeId());
cnd.andEX("info.way", "=", pageForm.getWay());
// 报销项目查询条件
cnd.andEX("info.reimburseProject", "=", pageForm.getReimburseProject());
// 姓名和工号查询条件
if (!isAdmin()) {
cnd.andEX("info.condolenceUnionId", "=", SecurityUtil.getUnionId());
}
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
SqlExpressionGroup seg = new SqlExpressionGroup();
seg.orLike("info.condolenceUserName",pageForm.getSearchKeyword());
seg.orLike("info.condolenceLoginName",pageForm.getSearchKeyword());
seg.orLike("info.condolenceUserName", pageForm.getSearchKeyword());
seg.orLike("info.condolenceLoginName", pageForm.getSearchKeyword());
cnd.and(seg);
}
cnd.and("info.stateId", "in", List.of(3, 2));
cnd.desc("info.createTime");
applyStatisticsSort(cnd, pageForm);
sql.setCondition(cnd);
return sql;
}
@Override
public Result saveApply(UnionReimburse unionReimburse, int stateId) {
if (unionReimburse == null) {
return Result.error("报销数据不能为空");
}
UnionReimburse oldRecord = null;
if (StrUtil.isNotBlank(unionReimburse.getId())) {
oldRecord = this.fetch(unionReimburse.getId());
if (oldRecord == null) {
return Result.error("未找到对应的报销记录");
}
}
normalizeInvoiceDetails(unionReimburse);
if (needInvoiceDetails(unionReimburse)) {
fillInvoiceSummary(unionReimburse);
}
if (stateId == 2) {
Result validateResult = validateBeforeSubmit(unionReimburse);
if (validateResult != null) {
return validateResult;
}
Result duplicateResult = validateInvoiceDuplicate(unionReimburse);
if (duplicateResult != null) {
return duplicateResult;
}
}
if (oldRecord == null) {
unionReimburse.setCreateTime(DateUtil.date());
unionReimburse.setDocumentNo(generateDocumentNo());
} else {
unionReimburse.setCreateTime(oldRecord.getCreateTime());
unionReimburse.setDocumentNo(oldRecord.getDocumentNo());
}
unionReimburse.setStateId(stateId);
if ("UNION_REIMBURSE_PROJECT_1".equals(unionReimburse.getReimburseProject())) {
unionReimburse.setRealMoney(unionReimburse.getCondolenceMoney());
} else {
unionReimburse.setRealMoney(unionReimburse.getMoney());
}
this.dao().insertOrUpdate(unionReimburse);
saveInvoiceDetails(unionReimburse.getId(), unionReimburse.getInvoiceDetails());
return Result.success(stateId == 2 ? "提交成功" : "保存成功");
}
@Override
public UnionReimburse getApplyForm(String id) {
UnionReimburse unionReimburse = this.fetch(id);
if (unionReimburse == null) {
return null;
}
List<UnionReimburseInvoiceDetail> invoiceDetails = this.dao().query(
UnionReimburseInvoiceDetail.class,
Cnd.where("reimburseId", "=", id).asc("createdAt")
);
unionReimburse.setInvoiceDetails(invoiceDetails == null ? new ArrayList<>() : invoiceDetails);
return unionReimburse;
}
@Override
public List<UnionReimburseBankHistoryVO> getUserBankHistory(String payer) {
if (StrUtil.isBlank(payer)) {
return new ArrayList<>();
}
Sql sql = Sqls.create("""
SELECT
bankUserName,
bankCardNumber,
bankOfDeposit
FROM
union_reimburse
$condition
GROUP BY
bankUserName,
bankCardNumber,
bankOfDeposit
ORDER BY
MAX(createTime) DESC
""");
Cnd cnd = Cnd.NEW();
cnd.and("payer", "=", payer);
cnd.and("bankUserName", "is not", null);
cnd.and("bankCardNumber", "is not", null);
cnd.and("bankOfDeposit", "is not", null);
sql.setCondition(cnd);
return this.listVO(sql, UnionReimburseBankHistoryVO.class);
}
@Override
public Result checkInvoiceDuplicate(String invoiceNo, String reimburseId) {
String currentInvoiceNo = StrUtil.blankToDefault(StrUtil.trim(invoiceNo), "");
if (StrUtil.isBlank(currentInvoiceNo)) {
return Result.error("发票号码不能为空");
}
return queryHistoryInvoiceDuplicate(currentInvoiceNo, reimburseId);
}
@Override
public Result recognizeInvoice(String fileId, Boolean verifySwitch) {
if (StrUtil.isBlank(fileId)) {
return Result.error("请先上传发票文件");
}
Sys_file sysFile = sysFileService.fetch(fileId);
if (sysFile == null) {
return Result.error("发票文件不存在");
}
Credential credential = getInvoiceCredential();
if (credential == null) {
return Result.error("未配置腾讯云发票识别密钥,请先在 application-dev.yaml 中配置 invoice.tencent.secret-id 和 invoice.tencent.secret-key");
}
try {
byte[] fileBytes = sysFileService.download(fileId);
VatInvoiceOCRResponse response = executeOcr(credential, sysFile, fileBytes);
NutMap result = buildOcrResult(response);
String msg = "发票识别成功";
if (Boolean.TRUE.equals(verifySwitch)) {
String verifyMsg = enrichByVerify(credential, result);
msg = StrUtil.isBlank(verifyMsg) ? "发票识别并校验成功" : verifyMsg;
}
return Result.success(msg, result);
} catch (TencentCloudSDKException e) {
log.error("腾讯云发票识别失败,fileId:{}", fileId, e);
return Result.error("腾讯云发票识别失败:" + e.getMessage());
} catch (Exception e) {
log.error("发票识别失败,fileId:{}", fileId, e);
return Result.error("发票识别失败:" + e.getMessage());
}
}
/**
* 统一规整发票明细中的文本和金额,避免空格、空数组等脏数据进入库表。
*/
private void normalizeInvoiceDetails(UnionReimburse unionReimburse) {
if (unionReimburse.getInvoiceDetails() == null) {
unionReimburse.setInvoiceDetails(new ArrayList<>());
return;
}
List<UnionReimburseInvoiceDetail> invoiceDetails = unionReimburse.getInvoiceDetails().stream()
.filter(Objects::nonNull)
.collect(Collectors.toList());
invoiceDetails.forEach(detail -> {
detail.setInvoiceNo(StrUtil.trim(detail.getInvoiceNo()));
detail.setSellerName(StrUtil.trim(detail.getSellerName()));
detail.setItemName(StrUtil.trim(detail.getItemName()));
detail.setRemark(StrUtil.trim(detail.getRemark()));
if (detail.getInvoiceFiles() == null) {
detail.setInvoiceFiles(new ArrayList<>());
}
if (detail.getInvoiceAmount() != null) {
detail.setInvoiceAmount(BigDecimal.valueOf(detail.getInvoiceAmount())
.setScale(2, RoundingMode.HALF_UP)
.doubleValue());
}
});
unionReimburse.setInvoiceDetails(invoiceDetails);
}
/**
* 活动类报销由发票明细自动汇总金额和发票张数,减少手工输入出错。
*/
private void fillInvoiceSummary(UnionReimburse unionReimburse) {
double totalMoney = 0D;
int invoiceFileCount = 0;
for (UnionReimburseInvoiceDetail detail : unionReimburse.getInvoiceDetails()) {
if (detail.getInvoiceAmount() != null) {
totalMoney = BigDecimal.valueOf(totalMoney)
.add(BigDecimal.valueOf(detail.getInvoiceAmount()))
.doubleValue();
}
if (Lang.isNotEmpty(detail.getInvoiceFiles())) {
invoiceFileCount++;
}
}
unionReimburse.setMoney(BigDecimal.valueOf(totalMoney).setScale(2, RoundingMode.HALF_UP).doubleValue());
unionReimburse.setInvoiceNumber(String.valueOf(invoiceFileCount));
}
/**
* 提交前补一层后端校验,防止前端绕过校验直接提交。
*/
private Result validateBeforeSubmit(UnionReimburse unionReimburse) {
if (StrUtil.isBlank(unionReimburse.getMobile())) {
return Result.error("请填写联系方式");
}
if (!isValidContact(unionReimburse.getMobile())) {
return Result.error("请输入正确的联系方式");
}
if (StrUtil.isBlank(unionReimburse.getBankUserName())) {
return Result.error("请填写户名");
}
if (StrUtil.isBlank(unionReimburse.getBankCardNumber())) {
return Result.error("请填写银行账号");
}
if (StrUtil.isBlank(unionReimburse.getBankOfDeposit())) {
return Result.error("请填写开户行");
}
if (StrUtil.isBlank(unionReimburse.getPaymentNotes())) {
return Result.error("请填写支付内容");
}
if (!needInvoiceDetails(unionReimburse)) {
return null;
}
if (StrUtil.isBlank(unionReimburse.getActivityName())) {
return Result.error("请填写活动名称");
}
if (StrUtil.isBlank(unionReimburse.getActivityPlace())) {
return Result.error("请填写活动地点");
}
if (unionReimburse.getActivityTime() == null) {
return Result.error("请选择活动时间");
}
if (Lang.isEmpty(unionReimburse.getFiles())) {
return Result.error("请上传附件");
}
if (Lang.isEmpty(unionReimburse.getInvoiceDetails())) {
return Result.error("请至少维护一条发票明细");
}
for (int i = 0; i < unionReimburse.getInvoiceDetails().size(); i++) {
UnionReimburseInvoiceDetail detail = unionReimburse.getInvoiceDetails().get(i);
int rowIndex = i + 1;
if (Lang.isEmpty(detail.getInvoiceFiles())) {
return Result.error("" + rowIndex + "条发票明细请上传发票文件");
}
if (StrUtil.isBlank(detail.getInvoiceNo())) {
return Result.error("" + rowIndex + "条发票明细请填写发票号码");
}
if (detail.getInvoiceAmount() == null || detail.getInvoiceAmount() <= 0) {
return Result.error("" + rowIndex + "条发票明细请填写正确的发票金额");
}
if (StrUtil.isBlank(detail.getSellerName())) {
return Result.error("" + rowIndex + "条发票明细请填写销售方信息名称");
}
if (StrUtil.isBlank(detail.getItemName())) {
return Result.error("" + rowIndex + "条发票明细请填写项目名称");
}
}
return null;
}
/**
* 同次提交和历史记录都要拦截重复发票,避免重复报销。
*/
private Result validateInvoiceDuplicate(UnionReimburse unionReimburse) {
Map<String, List<Integer>> currentInvoiceMap = new LinkedHashMap<>();
List<UnionReimburseInvoiceDetail> details = unionReimburse.getInvoiceDetails();
for (int i = 0; i < details.size(); i++) {
String invoiceNo = StrUtil.trim(details.get(i).getInvoiceNo());
if (StrUtil.isBlank(invoiceNo)) {
continue;
}
currentInvoiceMap.computeIfAbsent(invoiceNo, key -> new ArrayList<>()).add(i + 1);
}
for (Map.Entry<String, List<Integer>> entry : currentInvoiceMap.entrySet()) {
if (entry.getValue().size() > 1) {
String rowText = entry.getValue().stream().map(String::valueOf).collect(Collectors.joining(""));
return Result.error("发票号码【" + entry.getKey() + "】与本次提交的第" + rowText + "条发票重复,不能提交");
}
}
for (String invoiceNo : currentInvoiceMap.keySet()) {
Result duplicateResult = queryHistoryInvoiceDuplicate(invoiceNo, unionReimburse.getId());
if (duplicateResult != null) {
return duplicateResult;
}
}
return null;
}
/**
* 历史重复校验会排除当前单据自身,避免编辑回显时误判。
*/
private Result queryHistoryInvoiceDuplicate(String invoiceNo, String reimburseId) {
Sql sql = Sqls.create("""
SELECT
d.invoiceNo,
r.activityName
FROM
union_reimburse_invoice_detail d
LEFT JOIN union_reimburse r ON r.id = d.reimburseId
WHERE
d.invoiceNo = @invoiceNo
AND (@reimburseId = '' OR d.reimburseId <> @reimburseId)
LIMIT 1
""");
sql.params().set("invoiceNo", invoiceNo);
sql.params().set("reimburseId", StrUtil.blankToDefault(StrUtil.trim(reimburseId), ""));
List<NutMap> repeatList = this.listMap(sql);
if (Lang.isEmpty(repeatList)) {
return null;
}
String activityName = repeatList.get(0).getString("activityName");
if (StrUtil.isBlank(activityName)) {
activityName = "历史报销记录";
}
return Result.error("发票号码【" + invoiceNo + "】与以往活动名称【" + activityName + "】中提交的发票重复,不能提交");
}
/**
* 每次按当前表单全量覆盖明细,避免旧明细残留。
*/
/**
* 发票识别优先读取当前项目配置的 SecretId/SecretKey,没有时再回退读取用户提供的 csv 文件。
*/
private Credential getInvoiceCredential() {
String secretId = StrUtil.trim(conf.get("invoice.tencent.secret-id"));
String secretKey = StrUtil.trim(conf.get("invoice.tencent.secret-key"));
if (StrUtil.isNotBlank(secretId) && StrUtil.isNotBlank(secretKey)) {
return new Credential(secretId, secretKey);
}
return loadCredentialFromSecretFile();
}
/**
* 兼容腾讯云控制台下载的密钥 csv,便于本地开发时不把明文密钥直接写进配置文件。
*/
private Credential loadCredentialFromSecretFile() {
String secretFile = StrUtil.trim(conf.get("invoice.tencent.secret-file"));
if (StrUtil.isBlank(secretFile)) {
return null;
}
try {
Path path = Paths.get(secretFile);
if (!Files.exists(path)) {
return null;
}
List<String> lines = Files.readAllLines(path);
if (lines.size() < 2) {
return null;
}
String[] values = lines.get(1).split(",", -1);
if (values.length < 2) {
return null;
}
String secretId = StrUtil.trim(values[0]);
String secretKey = StrUtil.trim(values[1]);
if (StrUtil.isBlank(secretId) || StrUtil.isBlank(secretKey)) {
return null;
}
return new Credential(secretId, secretKey);
} catch (Exception e) {
log.warn("读取腾讯云发票识别密钥文件失败,file:{}", secretFile, e);
return null;
}
}
/**
* 统一创建腾讯云 OCR 客户端,避免控制器直接依赖第三方 SDK。
*/
private OcrClient createOcrClient(Credential credential) {
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint("ocr.tencentcloudapi.com");
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
return new OcrClient(credential, "", clientProfile);
}
private VatInvoiceOCRResponse executeOcr(Credential credential, Sys_file sysFile, byte[] fileBytes) throws TencentCloudSDKException {
OcrClient client = createOcrClient(credential);
VatInvoiceOCRRequest request = new VatInvoiceOCRRequest();
request.setImageBase64(Base64.getEncoder().encodeToString(fileBytes));
String fileName = StrUtil.blankToDefault(sysFile.getName(), "").toLowerCase();
if (fileName.endsWith(".pdf")) {
request.setIsPdf(true);
request.setPdfPageNumber(1L);
}
return client.VatInvoiceOCR(request);
}
/**
* 将腾讯云 OCR 返回的票面字段规整成页面可直接回填的键值结构。
*/
private NutMap buildOcrResult(VatInvoiceOCRResponse response) {
NutMap result = NutMap.NEW();
TextVatInvoice[] infos = response.getVatInvoiceInfos();
VatInvoiceItem[] items = response.getItems();
result.setv("invoiceNo", pickValue(infos, "发票号码", "号码", "No"));
result.setv("invoiceCode", pickValue(infos, "发票代码", "代码", "Code"));
result.setv("invoiceDate", normalizeDate(pickValue(infos, "开票日期", "日期", "Date")));
result.setv("invoiceCheckCode", pickValue(infos, "校验码", "校验码后6位", "CheckCode"));
result.setv("invoiceAmount", parseMoney(pickValue(infos, "小写金额", "价税合计(小写)", "价税合计", "合计金额", "AmountWithTax")));
result.setv("sellerName", pickValue(infos, "销售方名称", "销售方信息名称", "销售方", "SellerName"));
result.setv("itemName", joinItemNames(items));
return result;
}
/**
* 自动校验需要票面关键信息,缺字段时只提示人工核对,不阻塞用户继续录入。
*/
private String enrichByVerify(Credential credential, NutMap result) {
String invoiceCode = result.getString("invoiceCode");
String invoiceNo = result.getString("invoiceNo");
String invoiceDate = result.getString("invoiceDate");
String checkCode = lastDigits(result.getString("invoiceCheckCode"), 6);
if (StrUtil.isBlank(checkCode)) {
checkCode = moneyToVerifyValue(result.get("invoiceAmount"));
}
if (StrUtil.hasBlank(invoiceCode, invoiceNo, invoiceDate, checkCode)) {
return "发票识别成功,但自动校验信息不完整,请人工核对后提交";
}
try {
OcrClient client = createOcrClient(credential);
VatInvoiceVerifyRequest request = new VatInvoiceVerifyRequest();
request.setInvoiceCode(invoiceCode);
request.setInvoiceNo(invoiceNo);
request.setInvoiceDate(invoiceDate.replace("-", ""));
request.setAdditional(checkCode);
VatInvoiceVerifyResponse response = client.VatInvoiceVerify(request);
VatInvoice invoice = response.getInvoice();
if (invoice == null) {
return "发票识别成功,但自动校验未返回有效结果,请人工核对后提交";
}
result.setv("invoiceCode", invoice.getCode());
result.setv("invoiceNo", invoice.getNumber());
result.setv("invoiceDate", normalizeDate(invoice.getDate()));
result.setv("invoiceCheckCode", invoice.getCheckCode());
result.setv("sellerName", invoice.getSellerName());
result.setv("invoiceAmount", parseMoney(invoice.getAmountWithTax()));
result.setv("itemName", joinItemNames(invoice.getItems()));
return null;
} catch (Exception e) {
log.warn("腾讯云发票验真失败,invoiceNo:{}", invoiceNo, e);
return "发票识别成功,但自动校验失败,请人工核对后提交";
}
}
private String pickValue(TextVatInvoice[] infos, String... aliases) {
if (infos == null || aliases == null) {
return "";
}
Set<String> aliasSet = new LinkedHashSet<>();
for (String alias : aliases) {
aliasSet.add(normalizeFieldName(alias));
}
for (TextVatInvoice info : infos) {
if (info == null) {
continue;
}
if (aliasSet.contains(normalizeFieldName(info.getName()))) {
return StrUtil.blankToDefault(StrUtil.trim(info.getValue()), "");
}
}
return "";
}
private String normalizeFieldName(String name) {
return StrUtil.blankToDefault(name, "")
.replace("", "")
.replace(":", "")
.replace(" ", "")
.replace("\u00A0", "")
.trim();
}
private String normalizeDate(String dateText) {
String date = StrUtil.blankToDefault(StrUtil.trim(dateText), "");
if (StrUtil.isBlank(date)) {
return "";
}
String digits = date.replaceAll("[^0-9]", "");
if (digits.length() == 8) {
return digits.substring(0, 4) + "-" + digits.substring(4, 6) + "-" + digits.substring(6);
}
return date.replace("", "-").replace("", "-").replace("", "");
}
private Double parseMoney(Object moneyObj) {
String moneyText = StrUtil.blankToDefault(StrUtil.trim(ObjectUtil.defaultIfNull(moneyObj, "").toString()), "");
if (StrUtil.isBlank(moneyText)) {
return null;
}
moneyText = moneyText.replace(",", "").replace("", "").replace("¥", "");
try {
return BigDecimal.valueOf(Double.parseDouble(moneyText)).setScale(2, RoundingMode.HALF_UP).doubleValue();
} catch (Exception e) {
return null;
}
}
private String joinItemNames(VatInvoiceItem[] items) {
if (items == null || items.length == 0) {
return "";
}
return Arrays.stream(items)
.map(VatInvoiceItem::getName)
.filter(StrUtil::isNotBlank)
.distinct()
.collect(Collectors.joining(""));
}
private String lastDigits(String text, int length) {
String digits = StrUtil.blankToDefault(text, "").replaceAll("[^0-9]", "");
if (digits.length() <= length) {
return digits;
}
return digits.substring(digits.length() - length);
}
private String moneyToVerifyValue(Object moneyObj) {
Double money = parseMoney(moneyObj);
if (money == null) {
return "";
}
return BigDecimal.valueOf(money).setScale(2, RoundingMode.HALF_UP).toPlainString();
}
private void saveInvoiceDetails(String reimburseId, List<UnionReimburseInvoiceDetail> invoiceDetails) {
this.dao().clear(UnionReimburseInvoiceDetail.class, Cnd.where("reimburseId", "=", reimburseId));
if (Lang.isEmpty(invoiceDetails)) {
return;
}
invoiceDetails.forEach(detail -> {
detail.setId(null);
detail.setReimburseId(reimburseId);
this.dao().insert(detail);
});
}
/**
* 当前仅活动类报销维护发票明细,慰问类仍按原有逻辑处理金额。
*/
private boolean needInvoiceDetails(UnionReimburse unionReimburse) {
return unionReimburse != null && !"UNION_REIMBURSE_PROJECT_1".equals(unionReimburse.getReimburseProject());
}
/**
* 生成年度流水号,保持与现有单据编号规则一致。
*/
private String generateDocumentNo() {
int currentYear = DateUtil.thisYear();
String yearPrefix = String.valueOf(currentYear);
Cnd cnd = Cnd.where("documentNo", "like", yearPrefix + "%");
cnd.desc("documentNo");
cnd.limit(1);
List<UnionReimburse> list = this.query(cnd);
int nextSeq = 1;
if (Lang.isNotEmpty(list) && StrUtil.isNotBlank(list.get(0).getDocumentNo())) {
String maxNo = list.get(0).getDocumentNo();
if (maxNo.startsWith(yearPrefix)) {
String seqPart = maxNo.substring(yearPrefix.length());
if (StrUtil.isNumeric(seqPart)) {
nextSeq = Integer.parseInt(seqPart) + 1;
}
}
}
return yearPrefix + String.format("%02d", nextSeq);
}
/**
* 统计页排序字段仅允许白名单列,避免传入非法字段。
*/
private void applyStatisticsSort(Cnd cnd, UnionReimbursePageForm pageForm) {
String orderField = normalizeStatisticsSortField(pageForm.getPageOrderName());
if (StrUtil.isBlank(orderField) || StrUtil.isBlank(pageForm.getPageOrderBy())) {
cnd.desc("info.createTime");
return;
}
cnd.orderBy(orderField, "ascending".equals(pageForm.getPageOrderBy()) ? "asc" : "desc");
}
private String normalizeStatisticsSortField(String pageOrderName) {
if (StrUtil.isBlank(pageOrderName)) {
return null;
}
return switch (pageOrderName) {
case "condolenceLoginName" -> "info.condolenceLoginName";
case "condolenceUnionName" -> "info.condolenceUnionName";
case "unitName" -> "info.unitName";
case "newCreateTime", "createTime" -> "info.createTime";
case "newCondolenceTime" -> "info.condolenceTime";
case "reimburseProject" -> "info.reimburseProject";
default -> null;
};
}
private boolean isAdmin() {
return AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name());
}
/**
* 联系方式支持手机号和座机,和前端规则保持一致。
*/
private boolean isValidContact(String mobile) {
return mobile.matches("^(1[3-9]\\d{9}|0\\d{2,3}-?\\d{7,8})$");
}
}
@@ -150,7 +150,7 @@ public class TeacherCongressDelegatePushController {
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tc.delegate.push")
// @SLog(tag = "民主管理-教代会代表推选", msg = "获取本分工会下的非代表用户")
public Result getUnionUser(String sessionId, String unionId) {
public Result getUnionUser(String sessionId, String unionId, String keyword) {
// 查询预选中的代表
Cnd cndx = Cnd.where("sessionId", "=", sessionId);
cndx.andEX("unionId", "=", SecurityUtil.getUnionId());
@@ -173,7 +173,7 @@ public class TeacherCongressDelegatePushController {
`vw_user` u
LEFT JOIN teacher_congress_delegate tcd ON tcd.userId = u.id
AND tcd.sessionId = @sessionId
LEFT JOIN teacher_congress_delegate_push tcdp ON tcdp.userId = tcdp.id
LEFT JOIN teacher_congress_delegate_push tcdp ON tcdp.userId = u.id
AND tcdp.sessionId = @sessionId
$condition
""");
@@ -185,6 +185,14 @@ public class TeacherCongressDelegatePushController {
cnd.and("tcd.userId", "is", null);
cnd.and("tcdp.id", "is", null);
cnd.and("u.unionId", "=", SecurityUtil.getUnionId());
if (StrUtil.isNotBlank(keyword)) {
SqlExpressionGroup group = new SqlExpressionGroup();
group.orLike("u.username", keyword);
group.orLike("u.loginname", keyword);
cnd.and(group);
}
// 搜索候选人时只返回少量命中数据,避免前端大列表筛选导致严重卡顿
cnd.limit(1, 100);
sql.setCondition(cnd);
List<NutMap> userList = baseService.listMap(sql);
@@ -456,6 +456,8 @@ const ACTIVITY_CULTURE_APPLY_ACTIVITY = {
complete_result></file-upload>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="40">
<el-col :span="12">
<el-form-item label="其他" prop="otherFiles">
<file-upload :value.sync="formData.otherFiles" accept=".jpg,.jpeg,.png"
@@ -107,7 +107,7 @@ const ACTIVITY_CULTURE_INFO_MANAGE = {
:disabled="row.projectTypeCode==50004">
编辑
</el-dropdown-item>
<el-dropdown-item
<el-dropdown-item u
v-if="row.canRevoke" :command="{type:'Revoke',row}">
撤回
</el-dropdown-item>
@@ -80,7 +80,7 @@ layout("/layouts/platform.html"){
})
},
openAnnualLimit() {
window.location.href = '/platform/trainSignUp/annualLimit'
commonUtil.pjaxPush('/platform/trainSignUp/annualLimit')
},
openAdd() {
this.$refs.guava.edit(() => {
@@ -571,6 +571,11 @@ layout("/layouts/platform.html"){
{{formatNumber(row.support)}}
</template>
</el-table-column>
<el-table-column label="其他收入(元)" prop="otherIncome">
<template slot-scope="{row}">
{{formatNumber(row.otherIncome)}}
</template>
</el-table-column>
<el-table-column label="年度总支出(元)" prop="totalExpend">
<template slot-scope="{row}">
{{formatNumber(row.totalExpend)}}
@@ -662,7 +667,7 @@ layout("/layouts/platform.html"){
// 财务数据
financeData: {
incomeDetailed: [], // 收支明细
incomeCensus: [{ lastYearSurplus: 0, income: 0, allocate: 0, support: 0, totalExpend: 0, surplus: 0 }] // 收支统计
incomeCensus: [{ lastYearSurplus: 0, income: 0, allocate: 0, support: 0, otherIncome: 0, totalExpend: 0, surplus: 0 }] // 收支统计
},
// 表单校验规则
@@ -680,12 +685,13 @@ layout("/layouts/platform.html"){
{ label: "校工会拨款", value: "校工会拨款" },
{ label: "年度结余", value: "年度结余" },
{ label: "社会赞助", value: "社会赞助" },
{ label: "其他收入", value: "其他收入" },
{ label: "支出", value: "支出" }
];
},
// 收入类型集合
INCOME_TYPES() {
return ["会费收入", "校工会拨款", "社会赞助", "其"];
return ["会费收入", "校工会拨款", "社会赞助", "其他收入"];
},
// 支出类型集合
EXPEND_TYPES() {
@@ -730,8 +736,9 @@ layout("/layouts/platform.html"){
const income = row.income || 0;
const allocate = row.allocate || 0;
const support = row.support || 0;
const otherIncome = row.otherIncome || 0;
const totalExpend = row.totalExpend || 0;
const total = Number(lastYearSurplusVal) + Number(income) + Number(allocate) + Number(support) - Number(totalExpend);
const total = Number(lastYearSurplusVal) + Number(income) + Number(allocate) + Number(support) + Number(otherIncome) - Number(totalExpend);
return this.formatNumber(total);
},
// 表单校验通用方法
@@ -788,7 +795,7 @@ layout("/layouts/platform.html"){
data.year = data.year?.toString() || new Date().getFullYear() + "";
this.formData = data;
// 初始化财务数据
this.financeData.incomeCensus = data.incomeCensus || [{ lastYearSurplus: 0, income: 0, allocate: 0, support: 0, totalExpend: 0, surplus: 0 }];
this.financeData.incomeCensus = data.incomeCensus || [{ lastYearSurplus: 0, income: 0, allocate: 0, support: 0, otherIncome: 0, totalExpend: 0, surplus: 0 }];
// 从统计数据中提取去年年度结余
if (this.financeData.incomeCensus && this.financeData.incomeCensus.length > 0) {
@@ -948,6 +955,8 @@ layout("/layouts/platform.html"){
this.financeData.incomeCensus[0].allocate = (this.financeData.incomeCensus[0].allocate || 0) - income;
} else if (row.incomeType === "社会赞助") {
this.financeData.incomeCensus[0].support = (this.financeData.incomeCensus[0].support || 0) - income;
} else if (row.incomeType === "其他收入") {
this.financeData.incomeCensus[0].otherIncome = (this.financeData.incomeCensus[0].otherIncome || 0) - income;
} else if (row.incomeType === "支出") {
this.financeData.incomeCensus[0].totalExpend = (this.financeData.incomeCensus[0].totalExpend || 0) - expend;
}
@@ -968,6 +977,10 @@ layout("/layouts/platform.html"){
this.$set(row, "expend", null);
this.calcSupportTotal();
break;
case "其他收入":
this.$set(row, "expend", null);
this.calcOtherIncomeTotal();
break;
case "支出":
this.$set(row, "income", null);
this.calcExpendTotal();
@@ -993,6 +1006,8 @@ layout("/layouts/platform.html"){
this.calcAllocateTotal();
} else if (row.incomeType === "会费收入") {
this.calcDueIncomeTotal();
} else if (row.incomeType === "其他收入") {
this.calcOtherIncomeTotal();
}
// 重新计算所有统计数据,确保一致性
this.recalculateCensus();
@@ -1018,6 +1033,13 @@ layout("/layouts/platform.html"){
.reduce((sum, item) => sum + (Number(item.income) || 0), 0);
this.financeData.incomeCensus[0].support = total;
},
// 计算其他收入合计
calcOtherIncomeTotal() {
const total = this.financeData.incomeDetailed
.filter(item => item.incomeType === "其他收入")
.reduce((sum, item) => sum + (Number(item.income) || 0), 0);
this.financeData.incomeCensus[0].otherIncome = total;
},
// 计算会费收入合计
calcDueIncomeTotal() {
const total = this.financeData.incomeDetailed
@@ -1049,6 +1071,11 @@ layout("/layouts/platform.html"){
.filter(item => item.incomeType === "社会赞助")
.reduce((sum, item) => sum + (Number(item.income) || 0), 0);
// 计算其他收入总额
const otherIncomeTotal = this.financeData.incomeDetailed
.filter(item => item.incomeType === "其他收入")
.reduce((sum, item) => sum + (Number(item.income) || 0), 0);
// 计算总支出
const totalExpend = this.financeData.incomeDetailed
.filter(item => item.incomeType === "支出")
@@ -1058,9 +1085,12 @@ layout("/layouts/platform.html"){
this.financeData.incomeCensus[0].income = dueIncomeTotal;
this.financeData.incomeCensus[0].allocate = allocateTotal;
this.financeData.incomeCensus[0].support = supportTotal;
this.financeData.incomeCensus[0].otherIncome = otherIncomeTotal;
this.financeData.incomeCensus[0].totalExpend = totalExpend;
// 确保去年年度结余不丢失
this.financeData.incomeCensus[0].lastYearSurplus = this.lastYearSurplus;
// 同步写回结余,避免保存后统计值与页面展示不一致
this.financeData.incomeCensus[0].surplus = Number(this.calcSurplus(this.financeData.incomeCensus[0]));
// 同步更新全局变量
this.dueIncome = dueIncomeTotal;
@@ -1134,6 +1164,7 @@ layout("/layouts/platform.html"){
// 保存
async handleSave() {
try {
this.recalculateCensus();
const resp = await this.$axios.post("/platform/club/examine/apply/save", {
data: JSON.stringify(this.formData),
incomeDetailed: JSON.stringify(this.financeData.incomeDetailed),
@@ -1150,6 +1181,7 @@ layout("/layouts/platform.html"){
// 提交(新增)
async handleSubmit() {
try {
this.recalculateCensus();
await this.$confirm("确定要提交此申请吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
@@ -1175,6 +1207,7 @@ layout("/layouts/platform.html"){
// 提交(任务处理)
async handleFinishTask() {
try {
this.recalculateCensus();
await this.$confirm("您确定要提交吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
@@ -68,8 +68,12 @@ const EXAMINE_INFO_COMPONENT = {
<el-table-column label="会费收入" prop="income"></el-table-column>
<el-table-column label="校工会拨款" prop="allocate"></el-table-column>
<el-table-column label="社会赞助" prop="support"></el-table-column>
<el-table-column label="其他收入" prop="otherIncome"></el-table-column>
<el-table-column label="年度总支出" prop="totalExpend"></el-table-column>
<el-table-column label="本年度结余" prop="surplus">
<template slot-scope="{row}">
{{ getIncomeCensusSurplus(row) }}
</template>
</el-table-column>
</el-table>
</el-tab-pane>
@@ -132,6 +136,19 @@ const EXAMINE_INFO_COMPONENT = {
}
},
methods: {
// 历史数据可能没有落库存量结余,这里按统计项兜底重算
getIncomeCensusSurplus(row) {
if (!row) {
return 0
}
const lastYearSurplus = Number(row.lastYearSurplus) || 0
const income = Number(row.income) || 0
const allocate = Number(row.allocate) || 0
const support = Number(row.support) || 0
const otherIncome = Number(row.otherIncome) || 0
const totalExpend = Number(row.totalExpend) || 0
return lastYearSurplus + income + allocate + support + otherIncome - totalExpend
},
async onOpen(row) {
this.row = row
const resp = await this.$axios.post("/platform/club/examine/common/findOne", { id: row.id })
@@ -37,6 +37,7 @@ const CLUB_INFO_MANAGE_TEMPLATE = {
<el-table-column label="电话" prop="mobile" show-overflow-tooltip></el-table-column>
<el-table-column label="所属单位" prop="unitName" sortable show-overflow-tooltip></el-table-column>
<el-table-column label="身份" prop="roleName" show-overflow-tooltip></el-table-column>
<el-table-column label="入会时间" prop="joinTime" show-overflow-tooltip sortable></el-table-column>
<el-table-column label="操作">
<template slot-scope="{row}">
<el-dropdown>
@@ -58,7 +58,8 @@ const CLUB_SPONSOR_TEMPLATE = {
`,
data() {
return {
sponsorData: [],
// 发起人至少需要 3 人,默认先展示 3 行,方便录入。
sponsorData: [{}, {}, {}],
userOptions: []
}
},
@@ -91,6 +92,10 @@ const CLUB_SPONSOR_TEMPLATE = {
})
},
sponsorDeleteRow(row, index) {
if (this.sponsorData.length <= 3) {
this.$message.warning("发起人至少保留3行")
return
}
this.sponsorData.splice(index, 1)
}
},
@@ -38,7 +38,7 @@ layout("/layouts/platform.html"){
<el-button v-if="activeName === 1" type="primary" @click="activeName = 0">上一步</el-button>
<el-button v-if="activeName === 2" type="primary" @click="activeName = 1">上一步</el-button>
<el-button v-if="activeName === 0" type="primary" @click="nextStep">下一步</el-button>
<el-button v-if="activeName === 1" type="primary" @click="activeName = 2">下一步</el-button>
<el-button v-if="activeName === 1" type="primary" @click="nextSponsorStep">下一步</el-button>
<el-button type="primary" plain @click="onSave">保存</el-button>
<el-button type="primary" @click="onSubmit" v-if="!taskId">提交</el-button>
@@ -73,6 +73,14 @@ layout("/layouts/platform.html"){
}
},
methods: {
validateSponsorCount() {
const sponsorList = this.$refs.clubSponsorRef.sponsorData.filter((o) => o.userId !== "" && o.userId !== undefined)
if (sponsorList.length < 3) {
this.$message.warning({ title: "温馨提醒", message: "发起人不少于3人" })
return false
}
return true
},
nextStep() {
let valid = true
this.$refs.clubFormRef.$refs.form.validateField(["clubName"], (errMsg) => {
@@ -85,6 +93,13 @@ layout("/layouts/platform.html"){
if (valid) return
this.activeName = 1
},
nextSponsorStep() {
// 第二步进入第三步前,先校验发起人数量,避免遗漏必填人数要求。
if (!this.validateSponsorCount()) {
return
}
this.activeName = 2
},
async onSave() {
let valid = true
this.$refs.clubFormRef.$refs.form.validateField("clubName", (errMsg) => {
@@ -122,8 +137,7 @@ layout("/layouts/platform.html"){
formData.sponsor = this.$refs.clubSponsorRef.sponsorData
.filter((o) => o.userId !== "" && o.userId !== undefined)
.map((o) => o.userId)
if (['onFinishTask', 'onSubmit'].includes(type) && formData.sponsor && formData.sponsor.length < 3) {
this.$message.warning({ title: "警告", message: "发起人要求不少于3人" })
if (['onFinishTask', 'onSubmit'].includes(type) && !this.validateSponsorCount()) {
return
}
let manageValid = false
@@ -200,7 +214,6 @@ layout("/layouts/platform.html"){
let users = resp.data.clubUser.map((o) => o.userId)
let array = []
if (resp.data.sponsor) {
this.sponsorData = []
array = resp.data.sponsor.split(",")
formData.sponsor = array
}
@@ -211,12 +224,16 @@ layout("/layouts/platform.html"){
if (users) {
this.$refs.clubManagerRef.userOptions = await this.queryUserByIds(users)
}
this.$refs.clubSponsorRef.sponsorData = []
array.forEach((item, index) => {
let row = {}
row.userId = item
this.$refs.clubSponsorRef.sponsorPersonChange(item, row, index)
this.$refs.clubSponsorRef.sponsorData.push(row)
})
while (this.$refs.clubSponsorRef.sponsorData.length < 3) {
this.$refs.clubSponsorRef.sponsorData.push({})
}
this.$refs.clubFormRef.formData = formData
this.$refs.clubFormRef.concatPersonChange(resp.data.concatPerson)
}
@@ -31,12 +31,14 @@ layout("/layouts/platform.html"){
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="温馨提示:请先更新拨付金额后再点击核对按钮">
<el-button @click="closeAudit(false)" size="mini" type="primary">
开启核对入口
</el-button>
<el-button @click="closeAudit(true)" size="mini" type="danger">
取消核对入口
</el-button>
<template v-if="$auth.hasRoleOr(['SYSADMIN', 'SCHOOL_UNION_CLUB_ADMIN'])">
<el-button @click="closeAudit(false)" size="mini" type="primary">
开启核对入口
</el-button>
<el-button @click="closeAudit(true)" size="mini" type="danger">
取消核对入口
</el-button>
</template>
</table-tool>
<el-table :data="tableData" style="width: 100%" row-key="id"
v-loading="tableLoading" :size="tableSize" class="vi-table">
@@ -2,6 +2,42 @@
layout("/layouts/platform.html"){
#-->
<style>
.reimbursement-tabs {
margin-top: 10px;
}
.invoice-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 16px;
}
.invoice-toolbar-title {
font-size: 14px;
color: #1867b0;
font-weight: 600;
}
.invoice-summary-tip {
margin-top: 8px;
font-size: 12px;
color: #909399;
}
.invoice-file-name {
color: #1867b0;
cursor: pointer;
}
.invoice-file-name:hover {
text-decoration: underline;
}
</style>
<div id="app" v-cloak>
<el-card shadow="never">
<template #header>
@@ -12,7 +48,9 @@ layout("/layouts/platform.html"){
</template>
<el-form :model="formData" ref="formRef" :rules="formRules" label-width="0" label-suffix=""
class="flow-task-form">
<el-descriptions :column="2" border>
<el-tabs class="reimbursement-tabs" v-model="activeTabName">
<el-tab-pane label="申请基本信息" name="basic">
<el-descriptions :column="2" border>
<el-descriptions-item label="经办人">{{formData.userName}}</el-descriptions-item>
<el-descriptions-item label="工号">{{formData.loginName}}</el-descriptions-item>
<!-- <el-descriptions-item label="报销类别">-->
@@ -26,7 +64,7 @@ layout("/layouts/platform.html"){
<!-- </el-descriptions-item>-->
<el-descriptions-item label="报销项目" span="2">
<el-form-item label="报销项目" prop="reimburseProject">
<el-radio-group v-model="formData.reimburseProject" size="medium">
<el-radio-group v-model="formData.reimburseProject" size="medium" @change="handleReimburseProjectChange">
<el-radio :label="item.code" border v-for="item in reimburseProjectList">
{{item.name}}
</el-radio>
@@ -82,11 +120,32 @@ layout("/layouts/platform.html"){
</el-form-item>
</el-descriptions-item>
<el-descriptions-item label="户名" v-if="formData.paymentWay === 'UNION_REIMBURSE_PAYMENT_WAY_2'">
<el-form-item label="户名" prop="bankUserName">
<el-select
v-model="formData.bankUserName"
@change="handleBankUserNameChange"
clearable
filterable
allow-create
default-first-option
placeholder="请输入户名"
style="width: 100%">
<el-option
v-for="(item, index) in bankHistoryUserNames"
:key="index"
:label="item"
:value="item">
</el-option>
</el-select>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item label="开户行" v-if="formData.paymentWay === 'UNION_REIMBURSE_PAYMENT_WAY_2'">
<el-form-item label="开户行" prop="bankOfDeposit">
<el-select
v-model="formData.bankOfDeposit"
@change="getUserBankHistory"
@change="fillBankInfoFromHistory"
clearable
filterable
allow-create
@@ -107,7 +166,7 @@ layout("/layouts/platform.html"){
<el-form-item label="银行卡号" prop="bankCardNumber" >
<el-select
v-model="formData.bankCardNumber"
@change="getUserBankHistory"
@change="fillBankInfoFromHistory"
clearable
filterable
allow-create
@@ -268,8 +327,7 @@ layout("/layouts/platform.html"){
<el-descriptions-item label="报销金额" v-if="formData.reimburseProject !== 'UNION_REIMBURSE_PROJECT_1'">
<el-form-item label="报销金额" prop="money">
<el-input v-model="formData.money" placeholder="输入报销金额"
type="number"></el-input>
<el-input v-model="formData.money" placeholder="自动汇总发票金额" readonly></el-input>
</el-form-item>
</el-descriptions-item>
@@ -282,15 +340,6 @@ layout("/layouts/platform.html"){
</el-form-item>
</el-descriptions-item>
<el-descriptions-item label="发票张数">
<el-form-item label="发票张数" prop="invoiceNumber">
<el-input-number v-model="formData.invoiceNumber"
:max="1000" :min="0" controls-position="right"
placeholder="请填写发票张数"
style="width: 100%"></el-input-number>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item label="慰问时间" v-if="formData.reimburseProject === 'UNION_REIMBURSE_PROJECT_1'">
<el-form-item label="慰问时间" prop="condolenceTime">
<el-date-picker v-model="formData.condolenceTime"
@@ -396,13 +445,13 @@ layout("/layouts/platform.html"){
type="textarea" :autosize="{ minRows: 4, maxRows: 8}"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item label="报销事由" :span="2">
<el-descriptions-item label="报销事由" :span="2" v-if="formData.reimburseProject === 'UNION_REIMBURSE_PROJECT_1'">
<el-form-item prop="paymentNotes" label="报销事由">
<el-input maxlength="500" v-model="formData.paymentNotes" placeholder="请填写报销事由"
type="textarea" :autosize="{ minRows: 4, maxRows: 8}"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">
<el-descriptions-item label="备注" :span="2" v-if="formData.reimburseProject === 'UNION_REIMBURSE_PROJECT_1'">
<el-form-item prop="notes" label="备注">
<el-input maxlength="500" v-model="formData.notes" placeholder="需要填写:发票编号、开具方、金额"
type="textarea" :autosize="{ minRows: 4, maxRows: 8}"></el-input>
@@ -436,7 +485,83 @@ layout("/layouts/platform.html"){
<!-- <pc-signature v-model="formData.userSign"></pc-signature>-->
<!-- </el-form-item>-->
<!-- </el-descriptions-item>-->
</el-descriptions>
</el-descriptions>
</el-tab-pane>
<el-tab-pane label="发票信息" name="invoice" v-if="showInvoiceTab">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="报销金额" prop="money">
<el-input v-model="formData.money" placeholder="自动汇总发票金额" readonly></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="发票张数" prop="invoiceNumber">
<el-input v-model="formData.invoiceNumber" placeholder="自动统计发票数量" readonly></el-input>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item prop="paymentNotes" label="支付内容">
<el-input maxlength="500" v-model="formData.paymentNotes" placeholder="请填写支付内容"
type="textarea" :autosize="{ minRows: 4, maxRows: 8}"></el-input>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item prop="notes" label="备注">
<el-input maxlength="500" v-model="formData.notes" placeholder="补充说明"
type="textarea" :autosize="{ minRows: 3, maxRows: 6}"></el-input>
</el-form-item>
</el-col>
<el-col :span="24">
<div class="invoice-toolbar">
<div>
<div class="invoice-toolbar-title">发票明细列表</div>
<div class="invoice-summary-tip">
每条明细对应一个发票文件,可上传图片或 PDF;报销金额和发票张数会根据下表自动汇总。
</div>
</div>
<div>
<el-switch
v-model="formData.invoiceVerifyEnabled"
@change="handleVerifySwitchChange"
active-text="自动校验"
inactive-text="手工录入">
</el-switch>
<el-button @click="openInvoiceDialog('add')" plain type="primary" style="margin-left: 12px">新增发票</el-button>
</div>
</div>
<el-table :data="formData.invoiceDetails" border empty-text="暂无发票明细">
<el-table-column align="center" label="序号" type="index" width="70"></el-table-column>
<el-table-column label="文件名称" min-width="170">
<template slot-scope="scope">
<span class="invoice-file-name"
v-if="getInvoiceFile(scope.row)"
@click="previewInvoiceFile(scope.row)">
{{ getInvoiceFile(scope.row).name || getInvoiceFile(scope.row).filename }}
</span>
<span v-else>未上传</span>
</template>
</el-table-column>
<el-table-column label="发票号码" min-width="140" prop="invoiceNo"></el-table-column>
<el-table-column align="right" label="发票金额" min-width="110" prop="invoiceAmount"></el-table-column>
<el-table-column label="销售方信息名称" min-width="180" prop="sellerName"></el-table-column>
<el-table-column label="项目名称" min-width="180" prop="itemName"></el-table-column>
<el-table-column label="备注" min-width="150" prop="remark"></el-table-column>
<el-table-column align="center" fixed="right" label="操作" width="160">
<template slot-scope="scope">
<el-button @click="openInvoiceDialog('edit', scope.row, scope.$index)" size="mini" type="text">
编辑
</el-button>
<el-button @click="removeInvoiceDetail(scope.$index)" size="mini" type="text">
删除
</el-button>
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
</el-form>
<el-row type="flex" justify="end" class="mt20">
<el-button type="primary" plain @click="onSave">保存</el-button>
@@ -444,6 +569,70 @@ layout("/layouts/platform.html"){
<el-button type="primary" @click="onFinishTask" v-else>提交</el-button>
</el-row>
</el-card>
<el-dialog :close-on-click-modal="false"
:title="invoiceDialogMode === 'edit' ? '编辑发票明细' : '新增发票明细'"
:visible.sync="invoiceDialogVisible"
append-to-body
width="820px">
<el-form :model="detailForm" :rules="detailRules" label-position="right"
label-suffix="" label-width="120px" ref="invoiceDetailForm">
<el-row :gutter="30">
<el-col :span="24">
<el-form-item label="发票文件" prop="invoiceFiles">
<file-upload
:value.sync="detailForm.invoiceFiles"
:upload_number="1"
upload_return_id_api="/platform/sys/file/uploadDynamicReturnUrl"
upload_result_type="id"
upload_result_category="array"
complete_result
upload_mode="drag"
accept=".jpg,.jpeg,.png,.pdf">
</file-upload>
<div class="invoice-summary-tip">
上传发票文件后会自动识别发票号码、金额、销售方信息名称和项目名称;如已开启自动校验,还会继续校验发票信息。
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="发票号码" prop="invoiceNo">
<el-input placeholder="请输入发票号码" v-model="detailForm.invoiceNo"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="发票金额" prop="invoiceAmount">
<el-input-number :controls="false" :min="0" :precision="2"
placeholder="请输入发票金额" style="width: 100%"
v-model="detailForm.invoiceAmount"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="销售方信息名称" prop="sellerName">
<el-input placeholder="请输入销售方信息名称" v-model="detailForm.sellerName"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目名称" prop="itemName">
<el-input placeholder="请输入项目名称" v-model="detailForm.itemName"></el-input>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input :autosize="{ minRows: 3, maxRows: 5}"
placeholder="请输入备注"
type="textarea"
v-model="detailForm.remark"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<span class="dialog-footer" slot="footer">
<el-button @click="invoiceDialogVisible = false">取 消</el-button>
<el-button @click="saveInvoiceDetail" type="primary">保 存</el-button>
</span>
</el-dialog>
</div>
@@ -456,12 +645,31 @@ layout("/layouts/platform.html"){
return {
bizId: GetQueryString("bizId"),
taskId: GetQueryString("taskId"),
activeTabName: "basic",
formData: {
reimburseType: "",
hospitalizationTimeRange: [],
reimburseProject:''
reimburseProject:'',
invoiceDetails: [],
invoiceVerifyEnabled: true
},
historyData: [],
invoiceDialogVisible: false,
invoiceDialogMode: "add",
detailFormIndex: -1,
lastRecognizedFileId: "",
recognizingInvoice: false,
detailForm: {
invoiceFiles: [],
invoiceNo: "",
invoiceAmount: null,
sellerName: "",
itemName: "",
remark: "",
invoiceCode: "",
invoiceDate: "",
invoiceCheckCode: ""
},
condolenceWays: [
{label: "慰问金(A", value: "慰问金(A"},
{label: "提货券(B", value: "提货券(B"},
@@ -492,7 +700,10 @@ layout("/layouts/platform.html"){
paymentNotes: [{required: true, message: "请填写支付内容", trigger: ["change", "blur"]}],
activityName: [{required: true, message: "请填写活动名称", trigger: ["change", "blur"]}],
money: [{required: true, message: "请填写报销金额", trigger: ["change", "blur"]}],
mobile: [{required: true, message: "请填写联系方式", trigger: ["change", "blur"]}],
mobile: [
{required: true, message: "请填写联系方式", trigger: ["change", "blur"]},
{pattern: /^(1[3-9]\d{9}|0\d{2,3}-?\d{7,8})$/, message: "请输入正确的联系方式", trigger: "blur"}
],
files: [{required: true, message: "请上传附件", trigger: ["change", "blur"]}],
userSign: [{required: true, message: "请签名", trigger: ["change", "blur"]}],
// certifierUserId: [{required: true, message: "请填写认证人", trigger: ["change", "blur"]}],
@@ -508,6 +719,13 @@ layout("/layouts/platform.html"){
activityTime: [{required: true, message: "请选择活动时间", trigger: ["change", "blur"]}],
activityNumber: [{required: true, message: "请输入活动人数", trigger: ["change", "blur"]}]
},
detailRules: {
invoiceFiles: [{required: true, message: "请上传发票文件", trigger: ["change", "blur"]}],
invoiceNo: [{required: true, message: "请输入发票号码", trigger: ["change", "blur"]}],
invoiceAmount: [{required: true, message: "请输入发票金额", trigger: ["change", "blur"]}],
sellerName: [{required: true, message: "请输入销售方信息名称", trigger: ["change", "blur"]}],
itemName: [{required: true, message: "请输入项目名称", trigger: ["change", "blur"]}]
},
chooseType: {},
userOptions: [],
typeOptions: [],
@@ -519,7 +737,37 @@ layout("/layouts/platform.html"){
payerOptions: []
}
},
computed: {
showInvoiceTab() {
return this.formData.reimburseProject !== "UNION_REIMBURSE_PROJECT_1"
},
bankHistoryUserNames() {
const userNameMap = {}
return (this.historyData || []).reduce((result, item) => {
if (item.bankUserName && !userNameMap[item.bankUserName]) {
userNameMap[item.bankUserName] = true
result.push(item.bankUserName)
}
return result
}, [])
}
},
watch: {
"detailForm.invoiceFiles": {
handler(newVal) {
const file = newVal && newVal.length ? newVal[0] : null
const currentFileId = this.getInvoiceFileId(file)
if (!currentFileId) {
this.lastRecognizedFileId = ""
return
}
if (currentFileId === this.lastRecognizedFileId) {
return
}
this.recognizeCurrentInvoice(false)
},
deep: true
},
"formData.hospitalizationTimeRange"(newVal) {
if (newVal && newVal.length === 2) {
this.$set(this.formData, "hospitalizationStartTime", newVal[0]);
@@ -543,12 +791,38 @@ layout("/layouts/platform.html"){
}
},
methods: {
buildDefaultDetailForm() {
return {
invoiceFiles: [],
invoiceNo: "",
invoiceAmount: null,
sellerName: "",
itemName: "",
remark: "",
invoiceCode: "",
invoiceDate: "",
invoiceCheckCode: ""
}
},
ensureFormData(formData) {
const nextFormData = formData || {}
if (!Array.isArray(nextFormData.invoiceDetails)) {
this.$set(nextFormData, "invoiceDetails", [])
}
if (!Array.isArray(nextFormData.files)) {
this.$set(nextFormData, "files", [])
}
if (typeof nextFormData.invoiceVerifyEnabled !== "boolean") {
this.$set(nextFormData, "invoiceVerifyEnabled", true)
}
return nextFormData
},
createRemoteMethodForPayer(keyword) {
this.selectQueryUserForPayer(keyword, this.payerOptions)
},
selectQueryUserForPayer(keyword, options) {
options.length = 0
this.$axios.post("/platform/UnionReimburse/apply/listUser", {keyword: keyword}).then((res) => {
this.$axios.post("/platform/unionReimburse/apply/listUser", {keyword: keyword}).then((res) => {
if (res.code === 0) {
options.push(...res.data)
}
@@ -585,10 +859,44 @@ layout("/layouts/platform.html"){
}
})
},
fillBankInfoFromHistory() {
const matchItem = this.historyData.find((item) => {
if (this.formData.bankUserName && item.bankUserName !== this.formData.bankUserName) {
return false
}
if (this.formData.bankCardNumber && item.bankCardNumber !== this.formData.bankCardNumber) {
return false
}
if (this.formData.bankOfDeposit && item.bankOfDeposit !== this.formData.bankOfDeposit) {
return false
}
return true
})
if (matchItem) {
this.$set(this.formData, "bankUserName", matchItem.bankUserName)
this.$set(this.formData, "bankCardNumber", matchItem.bankCardNumber)
this.$set(this.formData, "bankOfDeposit", matchItem.bankOfDeposit)
}
},
handleBankUserNameChange(value) {
const matchItem = this.historyData.find((item) => item.bankUserName === value)
if (matchItem) {
this.$set(this.formData, "bankCardNumber", matchItem.bankCardNumber)
this.$set(this.formData, "bankOfDeposit", matchItem.bankOfDeposit)
}
},
handleVerifySwitchChange(value) {
this.$set(this.formData, "invoiceVerifyEnabled", !!value)
if (value && this.invoiceDialogVisible) {
this.recognizeCurrentInvoice(true)
}
},
async payerUserChange(){
if (this.formData.payer) {
await this.getUserBankHistory();
} else {
this.historyData = []
}
const user = this.payerOptions.find(o => o.id === this.formData.payer)
if (user) {
@@ -637,6 +945,253 @@ layout("/layouts/platform.html"){
this.$set(this.formData, "typeName", name)
}
},
handleReimburseProjectChange(value) {
if (value === "UNION_REIMBURSE_PROJECT_1") {
this.$set(this, "activeTabName", "basic")
this.$set(this.formData, "invoiceDetails", [])
this.$set(this.formData, "invoiceNumber", "")
this.$set(this.formData, "money", null)
} else {
this.rebuildInvoiceSummary()
}
},
openInvoiceDialog(mode, row, index) {
this.invoiceDialogMode = mode
this.detailFormIndex = typeof index === "number" ? index : -1
this.lastRecognizedFileId = ""
if (mode === "edit" && row) {
this.$set(this, "detailForm", JSON.parse(JSON.stringify(row)))
} else {
this.$set(this, "detailForm", this.buildDefaultDetailForm())
}
this.invoiceDialogVisible = true
this.$nextTick(() => {
if (this.$refs.invoiceDetailForm) {
this.$refs.invoiceDetailForm.clearValidate()
}
})
},
getInvoiceFile(row) {
if (!row || !row.invoiceFiles || row.invoiceFiles.length === 0) {
return null
}
return row.invoiceFiles[0]
},
/**
* 兼容上传组件返回完整对象、历史地址回显等多种场景,统一提取文件 id 给识别接口使用。
*/
getInvoiceFileId(file) {
if (!file) {
return ""
}
if (file.id) {
return file.id
}
if (file.response && file.response.data) {
if (typeof file.response.data === "string") {
const data = file.response.data
const idIndex = data.indexOf("id=")
if (idIndex > -1) {
return data.substring(idIndex + 3)
}
return data
}
if (file.response.data.id) {
return file.response.data.id
}
}
if (file.downloadPath && typeof file.downloadPath === "string") {
const downloadPath = file.downloadPath
const idIndex = downloadPath.indexOf("id=")
if (idIndex > -1) {
return downloadPath.substring(idIndex + 3)
}
}
if (file.url && typeof file.url === "string") {
const url = file.url
const idIndex = url.indexOf("id=")
if (idIndex > -1) {
return url.substring(idIndex + 3)
}
}
return ""
},
previewInvoiceFile(row) {
const file = this.getInvoiceFile(row)
const fileId = this.getInvoiceFileId(file)
if (!fileId) {
this.$message.warning("暂无发票文件")
return
}
window.open("/platform/sys/file/download?id=" + fileId)
},
recognizeCurrentInvoice(showLoading) {
const invoiceFile = this.getInvoiceFile(this.detailForm)
const invoiceFileId = this.getInvoiceFileId(invoiceFile)
if (!invoiceFileId) {
return Promise.resolve()
}
this.lastRecognizedFileId = invoiceFileId
this.recognizingInvoice = !!showLoading
return this.$axios.get("/platform/unionReimburse/apply/recognizeInvoice", {
params: {
fileId: invoiceFileId,
verifySwitch: this.formData.invoiceVerifyEnabled
}
}).then((resp) => {
if (resp.code !== 0) {
this.$message.warning(resp.msg)
return false
}
const data = resp.data || {}
if (data.invoiceNo) {
this.$set(this.detailForm, "invoiceNo", data.invoiceNo)
}
if (data.invoiceAmount !== null && data.invoiceAmount !== undefined && data.invoiceAmount !== "") {
this.$set(this.detailForm, "invoiceAmount", Number(data.invoiceAmount))
}
if (data.sellerName) {
this.$set(this.detailForm, "sellerName", data.sellerName)
}
if (data.itemName) {
this.$set(this.detailForm, "itemName", data.itemName)
}
if (data.invoiceCode) {
this.$set(this.detailForm, "invoiceCode", data.invoiceCode)
}
if (data.invoiceDate) {
this.$set(this.detailForm, "invoiceDate", data.invoiceDate)
}
if (data.invoiceCheckCode) {
this.$set(this.detailForm, "invoiceCheckCode", data.invoiceCheckCode)
}
if (this.detailForm.invoiceNo) {
return this.validateInvoiceDuplicateBeforeSave()
}
return true
}).catch((error) => {
const errorMsg = error && error.response && error.response.data && error.response.data.msg
? error.response.data.msg
: (error && error.msg ? error.msg : "发票识别请求失败,请稍后重试")
this.$message.error(errorMsg)
return false
}).finally(() => {
this.recognizingInvoice = false
})
},
async validateInvoiceDuplicateBeforeSave() {
const invoiceNo = this.detailForm.invoiceNo ? String(this.detailForm.invoiceNo).trim() : ""
if (!invoiceNo) {
return false
}
const duplicateRows = []
const invoiceDetails = this.formData.invoiceDetails || []
for (let i = 0; i < invoiceDetails.length; i++) {
if (this.detailFormIndex > -1 && i === this.detailFormIndex) {
continue
}
const currentNo = invoiceDetails[i].invoiceNo ? String(invoiceDetails[i].invoiceNo).trim() : ""
if (currentNo && currentNo === invoiceNo) {
duplicateRows.push(i + 1)
}
}
if (duplicateRows.length > 0) {
this.$message.warning("发票号码【" + invoiceNo + "】与本次提交的第" + duplicateRows.join("、") + "条发票重复,不能保存")
return false
}
const resp = await this.$axios.get("/platform/unionReimburse/apply/checkInvoiceDuplicate", {
params: {
invoiceNo: invoiceNo,
reimburseId: this.formData.id || ""
}
})
if (resp.code !== 0) {
this.$message.warning(resp.msg)
return false
}
return true
},
async saveInvoiceDetail() {
this.$refs.invoiceDetailForm.validate(async (valid) => {
if (!valid) {
return
}
const duplicateValid = await this.validateInvoiceDuplicateBeforeSave()
if (!duplicateValid) {
return
}
const detailData = JSON.parse(JSON.stringify(this.detailForm))
if (this.detailFormIndex > -1) {
this.$set(this.formData.invoiceDetails, this.detailFormIndex, detailData)
} else {
this.formData.invoiceDetails.push(detailData)
}
this.rebuildInvoiceSummary()
this.invoiceDialogVisible = false
})
},
removeInvoiceDetail(index) {
this.$confirm("确定删除这条发票明细吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.formData.invoiceDetails.splice(index, 1)
this.rebuildInvoiceSummary()
})
},
rebuildInvoiceSummary() {
if (this.formData.reimburseProject === "UNION_REIMBURSE_PROJECT_1") {
return
}
let totalMoney = 0
let fileCount = 0
const invoiceDetails = this.formData.invoiceDetails || []
for (let i = 0; i < invoiceDetails.length; i++) {
if (invoiceDetails[i].invoiceAmount !== null && invoiceDetails[i].invoiceAmount !== undefined && invoiceDetails[i].invoiceAmount !== "") {
totalMoney = totalMoney + Number(invoiceDetails[i].invoiceAmount)
}
if (invoiceDetails[i].invoiceFiles && invoiceDetails[i].invoiceFiles.length > 0) {
fileCount = fileCount + 1
}
}
this.$set(this.formData, "money", totalMoney ? totalMoney.toFixed(2) : "0.00")
this.$set(this.formData, "invoiceNumber", String(fileCount))
},
validateInvoiceDetailsBeforeSubmit() {
if (this.formData.reimburseProject === "UNION_REIMBURSE_PROJECT_1") {
return true
}
const invoiceDetails = this.formData.invoiceDetails || []
if (!invoiceDetails.length) {
this.$message.warning("请至少维护一条发票明细")
return false
}
for (let i = 0; i < invoiceDetails.length; i++) {
const currentDetail = invoiceDetails[i]
if (!currentDetail.invoiceFiles || currentDetail.invoiceFiles.length === 0) {
this.$message.warning("第" + (i + 1) + "条发票明细请上传发票文件")
return false
}
if (!currentDetail.invoiceNo) {
this.$message.warning("第" + (i + 1) + "条发票明细请填写发票号码")
return false
}
if (!currentDetail.invoiceAmount || Number(currentDetail.invoiceAmount) <= 0) {
this.$message.warning("第" + (i + 1) + "条发票明细请填写正确的发票金额")
return false
}
if (!currentDetail.sellerName) {
this.$message.warning("第" + (i + 1) + "条发票明细请填写销售方信息名称")
return false
}
if (!currentDetail.itemName) {
this.$message.warning("第" + (i + 1) + "条发票明细请填写项目名称")
return false
}
}
return true
},
//
// typeChange(id) {
// this.chooseType = this.typeOptions.find(o => o.id === id)
@@ -647,6 +1202,7 @@ layout("/layouts/platform.html"){
// },
// 保存
onSave() {
this.rebuildInvoiceSummary()
this.$confirm("您确定保存吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
@@ -665,6 +1221,12 @@ layout("/layouts/platform.html"){
return new Promise((resolve) => {
this.$refs.formRef.validate((valid) => {
if (valid) {
if (!this.validateInvoiceDetailsBeforeSubmit()) {
this.$set(this, "activeTabName", "invoice")
resolve(false)
return
}
this.rebuildInvoiceSummary()
resolve(true)
} else {
this.$message.warning("请完善必填信息")
@@ -842,14 +1404,15 @@ layout("/layouts/platform.html"){
if (this.bizId) {
this.$axios.post("/platform/unionReimburse/apply/info", {id: this.bizId}).then(async (res) => {
if (res.code === 0) {
this.formData = res.data
this.formData = this.ensureFormData(res.data)
await this.selectQueryUser(this.formData.certifierLoginName, this.userOptions)
this.rebuildInvoiceSummary()
// this.chooseType = this.typeOptions.find(o => o.id === this.formData.type)
}
})
} else {
const {username, loginname, id, unit, union, mobile} = this.$store.state.user
this.formData = {
this.formData = this.ensureFormData({
userName: username,
loginName: loginname,
unitId: unit?.id,
@@ -861,7 +1424,7 @@ layout("/layouts/platform.html"){
paymentWay: 'UNION_REIMBURSE_PAYMENT_WAY_2',
mobile: mobile,
userId: id,
}
})
}
}
},
@@ -12,7 +12,7 @@ layout("/layouts/platform.html"){
<search-item label="姓名/工号">
<el-input placeholder="请输入姓名或工号" clearable v-model="pageForm.searchKeyword"></el-input>
</search-item>
<search-item label="所属工会">
<search-item label="所属工会" v-if="isAdmin">
<el-select v-model="pageForm.unionId" @change="doSearch" style="width: 100%"
placeholder="请选择所属工会" clearable>
<el-option v-for="item in unionOptions"
@@ -22,7 +22,7 @@ layout("/layouts/platform.html"){
></el-option>
</el-select>
</search-item>
<search-item label="所属单位">
<search-item label="所属单位" v-if="isAdmin">
<el-select v-model="pageForm.unitId" @change="doSearch" style="width: 100%"
placeholder="请选择所属单位" clearable>
<el-option v-for="item in unitOptions"
@@ -53,10 +53,10 @@ layout("/layouts/platform.html"){
</table-tool>
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
<el-table-column prop="loginName" label="经办人工号"></el-table-column>
<el-table-column prop="loginName" label="经办人工号" sortable="custom"></el-table-column>
<el-table-column prop="userName" label="姓名"></el-table-column>
<el-table-column prop="unionName" label="所属工会"></el-table-column>
<el-table-column prop="unitName" label="所属单位"></el-table-column>
<el-table-column prop="unionName" label="所属工会" sortable="custom"></el-table-column>
<el-table-column prop="unitName" label="所属单位" sortable="custom"></el-table-column>
<el-table-column prop="certifierUserName" label="证明人">
<template slot-scope="{row}">
{{row.certifierUserName}}({{row.certifierLoginName}})
@@ -69,7 +69,7 @@ layout("/layouts/platform.html"){
</dict-tag>
</template>
</el-table-column>
<el-table-column prop="reimburseProject" label="报销项目">
<el-table-column prop="reimburseProject" label="报销项目" sortable="custom">
<template slot-scope="{row}">
<dict-tag :options="dict.type.UNION_REIMBURSE_PROJECT"
:value="row.reimburseProject">
@@ -77,7 +77,7 @@ layout("/layouts/platform.html"){
</template>
</el-table-column>
<el-table-column prop="money" label="金额"></el-table-column>
<el-table-column prop="createTime" label="申请时间"></el-table-column>
<el-table-column prop="createTime" label="申请时间" sortable="custom"></el-table-column>
<el-table-column prop="curTaskName" label="当前节点"></el-table-column>
<el-table-column prop="instanceState" label="流程状态">
<template slot-scope="{row}">
@@ -151,6 +151,11 @@ layout("/layouts/platform.html"){
}
}
,
computed: {
isAdmin() {
return this.$auth.hasRoleOr(['SYSADMIN', 'SCHOOL_UNION_ADMIN'])
}
},
methods: {
onView(row) {
this.$refs.guava.edit(() => {
@@ -214,10 +219,10 @@ layout("/layouts/platform.html"){
},
created() {
this.pageData()
//工会查询
this.$businessTool.listUnion().then((res) => (this.unionOptions = res))
//单位查询
this.$businessTool.listUnit().then((res) => (this.unitOptions = res))
if (this.isAdmin) {
this.$businessTool.listUnion().then((res) => (this.unionOptions = res))
this.$businessTool.listUnit().then((res) => (this.unitOptions = res))
}
}
})
</script>
@@ -12,7 +12,7 @@ layout("/layouts/platform.html"){
<search-item label="姓名/工号">
<el-input placeholder="请输入姓名或工号" clearable v-model="pageForm.searchKeyword"></el-input>
</search-item>
<search-item label="所属工会">
<search-item label="所属工会" v-if="isAdmin">
<el-select v-model="pageForm.unionId" @change="doSearch" style="width: 100%"
placeholder="请选择所属工会" clearable>
<el-option v-for="item in unionOptions"
@@ -22,7 +22,7 @@ layout("/layouts/platform.html"){
></el-option>
</el-select>
</search-item>
<search-item label="所属单位">
<search-item label="所属单位" v-if="isAdmin">
<el-select v-model="pageForm.unitId" @change="doSearch" style="width: 100%"
placeholder="请选择所属单位" clearable>
<el-option v-for="item in unitOptions"
@@ -51,12 +51,12 @@ layout("/layouts/platform.html"){
</table-tool>
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
<el-table-column prop="loginName" label="经办人工号"></el-table-column>
<el-table-column prop="loginName" label="经办人工号" sortable="custom"></el-table-column>
<el-table-column prop="userName" label="姓名"></el-table-column>
<el-table-column prop="unionName" label="所属工会"></el-table-column>
<el-table-column prop="unitName" label="所属单位"></el-table-column>
<el-table-column prop="createTime" label="申请时间"></el-table-column>
<el-table-column prop="reimburseProject" label="报销项目">
<el-table-column prop="unionName" label="所属工会" sortable="custom"></el-table-column>
<el-table-column prop="unitName" label="所属单位" sortable="custom"></el-table-column>
<el-table-column prop="createTime" label="申请时间" sortable="custom"></el-table-column>
<el-table-column prop="reimburseProject" label="报销项目" sortable="custom">
<template slot-scope="{row}">
<span v-if="row.reimburseProject === 'UNION_REIMBURSE_PROJECT_1'">慰问</span>
<span v-else-if="row.reimburseProject === 'UNION_REIMBURSE_PROJECT_2'">文体活动</span>
@@ -85,7 +85,7 @@ layout("/layouts/platform.html"){
<el-table-column label="操作" fixed="right" width="350px">
<template slot-scope="{row}">
<el-button @click="onView(row)" size="mini" type="primary">查看</el-button>
<el-button @click="openEdit(row)" size="mini" type="primary">实际金额</el-button>
<el-button v-if="isAdmin" @click="openEdit(row)" size="mini" type="primary">实际金额</el-button>
<el-button v-if="row.stateId == 3" @click="doPrint(row)" size="mini" type="primary">打印</el-button>
<el-button v-if="$auth.hasRole('SYSADMIN')" @click="onDelete(row.id)" size="mini" type="danger">删除</el-button>
</template>
@@ -144,6 +144,11 @@ layout("/layouts/platform.html"){
}
}
,
computed: {
isAdmin() {
return this.$auth.hasRoleOr(['SYSADMIN', 'SCHOOL_UNION_ADMIN'])
}
},
methods: {
onView(row) {
this.$refs.guava.edit(()=>{
@@ -174,30 +179,32 @@ layout("/layouts/platform.html"){
// 弹窗关闭时的处理
handleDialogClose() {
this.$refs.editForm.resetFields();
if (this.$refs.editForm) {
this.$refs.editForm.resetFields();
}
},
// 保存实际报销金额
saveFinallyMoney() {
this.$refs.editForm.validate(async (valid) => {
this.$refs.editForm.validate((valid) => {
if (valid) {
const resp = await this.$axios.post("/platform/unionReimburse/collect/updateActuallyAmount", {
id: this.editFormData.id,
realMoney: this.editFormData.realMoney
}).then((res) => {
if (res.code === 0) {
this.$message.success('保存成功');
this.editDialogVisible = false;
this.$refs.editForm.resetFields();
this.pageData();
}
})
this.$axios.post("/platform/unionReimburse/collect/updateActuallyAmount", {
id: this.editFormData.id,
realMoney: this.editFormData.realMoney
}).then((res) => {
if (res.code === 0) {
this.$message.success('保存成功');
this.editDialogVisible = false;
this.$refs.editForm.resetFields();
this.pageData();
}
})
}
});
},
exportSkr() {
const {year = '', unionId = '', unitId = '', reimburseProject = ''} = this.pageForm
window.open('/platform/unionReimburse/collect/exportSkr?year=' + year + "&unionId=" + unionId + '&unitId=' + unitId + "&reiItemId=" + reimburseProject)
window.open('/platform/unionReimburse/collect/exportSkr?year=' + year + "&unionId=" + unionId + '&unitId=' + unitId + "&reimburseProject=" + reimburseProject)
},
doPrint(row) {
if (row.reimburseProject != "UNION_REIMBURSE_PROJECT_1") {
@@ -212,10 +219,10 @@ layout("/layouts/platform.html"){
},
created() {
this.pageData()
//工会查询
this.$businessTool.listUnion().then((res) => (this.unionOptions = res))
//单位查询
this.$businessTool.listUnit().then((res) => (this.unitOptions = res))
if (this.isAdmin) {
this.$businessTool.listUnion().then((res) => (this.unionOptions = res))
this.$businessTool.listUnit().then((res) => (this.unitOptions = res))
}
}
})
</script>
@@ -12,7 +12,7 @@ layout("/layouts/platform.html"){
<search-item label="姓名/工号">
<el-input placeholder="请输入姓名或工号" clearable v-model="pageForm.searchKeyword"></el-input>
</search-item>
<search-item label="所属工会">
<search-item label="所属工会" v-if="isAdmin">
<el-select v-model="pageForm.unionId" @change="doSearch" style="width: 100%"
placeholder="请选择所属工会" clearable>
<el-option v-for="item in unionOptions"
@@ -22,7 +22,7 @@ layout("/layouts/platform.html"){
></el-option>
</el-select>
</search-item>
<search-item label="所属单位">
<search-item label="所属单位" v-if="isAdmin">
<el-select v-model="pageForm.unitId" @change="doSearch" style="width: 100%"
placeholder="请选择所属单位" clearable>
<el-option v-for="item in unitOptions"
@@ -48,12 +48,12 @@ layout("/layouts/platform.html"){
<table-tool></table-tool>
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
<el-table-column prop="loginName" label="经办人工号"></el-table-column>
<el-table-column prop="loginName" label="经办人工号" sortable="custom"></el-table-column>
<el-table-column prop="userName" label="姓名"></el-table-column>
<el-table-column prop="unionName" label="所属工会"></el-table-column>
<el-table-column prop="unitName" label="所属单位"></el-table-column>
<el-table-column prop="createTime" label="申请时间"></el-table-column>
<el-table-column prop="reimburseProject" label="报销项目">
<el-table-column prop="unionName" label="所属工会" sortable="custom"></el-table-column>
<el-table-column prop="unitName" label="所属单位" sortable="custom"></el-table-column>
<el-table-column prop="createTime" label="申请时间" sortable="custom"></el-table-column>
<el-table-column prop="reimburseProject" label="报销项目" sortable="custom">
<template slot-scope="{row}">
<span v-if="row.reimburseProject === 'UNION_REIMBURSE_PROJECT_1'">慰问</span>
<span v-else-if="row.reimburseProject === 'UNION_REIMBURSE_PROJECT_2'">文体活动</span>
@@ -119,6 +119,11 @@ layout("/layouts/platform.html"){
}
}
,
computed: {
isAdmin() {
return this.$auth.hasRoleOr(['SYSADMIN', 'SCHOOL_UNION_ADMIN'])
}
},
methods: {
onView(row) {
this.$refs.guava.view(() => {
@@ -177,10 +182,10 @@ layout("/layouts/platform.html"){
},
created() {
this.pageData()
//工会查询
this.$businessTool.listUnion().then((res) => (this.unionOptions = res))
//单位查询
this.$businessTool.listUnit().then((res) => (this.unitOptions = res))
if (this.isAdmin) {
this.$businessTool.listUnion().then((res) => (this.unionOptions = res))
this.$businessTool.listUnit().then((res) => (this.unitOptions = res))
}
}
})
</script>
@@ -12,7 +12,7 @@ layout("/layouts/platform.html"){
<search-item label="姓名/工号">
<el-input placeholder="请输入姓名或工号" clearable v-model="pageForm.searchKeyword"></el-input>
</search-item>
<search-item label="所属工会">
<search-item label="所属工会" v-if="isAdmin">
<el-select v-model="pageForm.unionId" @change="doSearch" style="width: 100%"
placeholder="请选择所属工会" clearable>
<el-option v-for="item in unionOptions"
@@ -22,7 +22,7 @@ layout("/layouts/platform.html"){
></el-option>
</el-select>
</search-item>
<search-item label="所属单位">
<search-item label="所属单位" v-if="isAdmin">
<el-select v-model="pageForm.unitId" @change="doSearch" style="width: 100%"
placeholder="请选择所属单位" clearable>
<el-option v-for="item in unitOptions"
@@ -46,7 +46,7 @@ layout("/layouts/platform.html"){
</el-card>
<el-card shadow="never">
<table-tool>
<el-button @click="AllReview" size="medium" type="primary">一键审核</el-button>
<el-button @click="AllReview" size="small" type="primary">一键审核</el-button>
<el-radio-group v-model="pageForm.approval" size="small" @change="doSearch">
<el-radio-button :label="true">已审核</el-radio-button>
<el-radio-button :label="false">未审核</el-radio-button>
@@ -54,12 +54,12 @@ layout("/layouts/platform.html"){
</table-tool>
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
<el-table-column prop="loginName" label="经办人工号"></el-table-column>
<el-table-column prop="loginName" label="经办人工号" sortable="custom"></el-table-column>
<el-table-column prop="userName" label="姓名"></el-table-column>
<el-table-column prop="unionName" label="所属工会"></el-table-column>
<el-table-column prop="unitName" label="所属单位"></el-table-column>
<el-table-column prop="createTime" label="申请时间"></el-table-column>
<el-table-column prop="reimburseProject" label="报销项目">
<el-table-column prop="unionName" label="所属工会" sortable="custom"></el-table-column>
<el-table-column prop="unitName" label="所属单位" sortable="custom"></el-table-column>
<el-table-column prop="createTime" label="申请时间" sortable="custom"></el-table-column>
<el-table-column prop="reimburseProject" label="报销项目" sortable="custom">
<template slot-scope="{row}">
<span v-if="row.reimburseProject === 'UNION_REIMBURSE_PROJECT_1'">慰问</span>
<span v-else-if="row.reimburseProject === 'UNION_REIMBURSE_PROJECT_2'">文体活动</span>
@@ -70,8 +70,9 @@ layout("/layouts/platform.html"){
</el-table-column>
<el-table-column label="备注">
<template slot-scope="{row}">
<span v-if="row.reimburseProject === 'UNION_REIMBURSE_PROJECT_1'">
被慰问人:{{ row.condolenceUserName }}</span>
<span v-if="row.reimburseProject === 'UNION_REIMBURSE_PROJECT_1'">
被慰问人:{{ row.condolenceUserName }}
</span>
<span v-else>活动名称:{{ row.activityName }}</span>
</template>
</el-table-column>
@@ -88,8 +89,7 @@ layout("/layouts/platform.html"){
<el-table-column label="操作" fixed="right" width="300px">
<template slot-scope="{row}">
<el-button @click="onView(row)" size="mini" type="primary">查看</el-button>
<el-button v-if="row.stateId == 2" @click="openAudit(row)" size="mini" type="primary">审核
</el-button>
<el-button v-if="row.stateId == 2" @click="openAudit(row)" size="mini" type="primary">审核</el-button>
</template>
</el-table-column>
</el-table>
@@ -125,14 +125,13 @@ layout("/layouts/platform.html"){
</guava>
</div>
<script nonce="${cspNonce!}">
<!--#include('../info.js'){}#-->
new Vue({
el: "#app",
store,
dicts: ["UNION_REIMBURSE_TYPE", "UNION_REIMBURSE_PROJECT"],
//分页数据
// 分页数据
mixins: [initTableMixins],
components: {
"union-reimburse-info": unionReimburseInfo
@@ -148,8 +147,12 @@ layout("/layouts/platform.html"){
unionOptions: [],
unitOptions: []
}
}
,
},
computed: {
isAdmin() {
return this.$auth.hasRoleOr(['SYSADMIN', 'SCHOOL_UNION_ADMIN'])
}
},
methods: {
onView(row) {
this.$refs.guava.edit(() => {
@@ -159,7 +162,7 @@ layout("/layouts/platform.html"){
},
AllReview() {
this.$confirm('确定所申请都通过线下审核了吗?', '提示', {
this.$confirm('确定所申请都通过线下审核了吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
@@ -168,9 +171,9 @@ layout("/layouts/platform.html"){
if (resp && resp.code === 0) {
this.pageForm.approval = true
this.pageData();
this.$message.success(resp.msg || '一键审核成功');
this.$message.success(resp.msg || '一键审核成功')
} else {
this.$message.error(resp.msg || '一键审核失败');
this.$message.error(resp.msg || '一键审核失败')
}
}).catch(() => {
// 用户取消操作
@@ -209,16 +212,14 @@ layout("/layouts/platform.html"){
})
}
})
},
}
},
created() {
this.pageData()
//工会查询
this.$businessTool.listUnion().then((res) => (this.unionOptions = res))
//单位查询
this.$businessTool.listUnit().then((res) => (this.unitOptions = res))
if (this.isAdmin) {
this.$businessTool.listUnion().then((res) => (this.unionOptions = res))
this.$businessTool.listUnit().then((res) => (this.unitOptions = res))
}
}
})
</script>
@@ -12,7 +12,7 @@ layout("/layouts/platform.html"){
<search-item label="姓名/工号">
<el-input placeholder="请输入慰问对象姓名或工号" clearable v-model="pageForm.searchKeyword"></el-input>
</search-item>
<search-item label="所属工会">
<search-item label="所属工会" v-if="isAdmin">
<el-select v-model="pageForm.unionId" @change="doSearch" style="width: 100%"
placeholder="请选择所属工会" clearable>
<el-option v-for="item in unionOptions"
@@ -80,15 +80,15 @@ layout("/layouts/platform.html"){
<span>{{scope.$index+(pageForm.pageNumber - 1) * pageForm.pageSize + 1}} </span>
</template>
</el-table-column>
<el-table-column prop="condolenceLoginName" label="被慰问人工号"></el-table-column>
<el-table-column prop="condolenceLoginName" label="被慰问人工号" sortable="custom"></el-table-column>
<el-table-column prop="condolenceUserName" label="姓名"></el-table-column>
<el-table-column prop="condolenceUnionCode" label="分工会代码"></el-table-column>
<el-table-column prop="condolenceUnionName" label="分工会名称"></el-table-column>
<el-table-column prop="condolenceUnionName" label="分工会名称" sortable="custom"></el-table-column>
<el-table-column prop="typeName" label="慰问类型"></el-table-column>
<el-table-column prop="way" label="慰问方式"></el-table-column>
<el-table-column prop="condolenceMoney" label="慰问金额"></el-table-column>
<el-table-column prop="newCreateTime" label="慰问时间"></el-table-column>
<el-table-column prop="newCondolenceTime" label="录入时间"></el-table-column>
<el-table-column prop="newCreateTime" label="慰问时间" sortable="custom"></el-table-column>
<el-table-column prop="newCondolenceTime" label="录入时间" sortable="custom"></el-table-column>
<el-table-column label="申请状态">
<template slot-scope="{row}">
<span v-if="row.stateId == 1" style="color: #409eff;">待提交</span>
@@ -170,6 +170,11 @@ layout("/layouts/platform.html"){
}
}
,
computed: {
isAdmin() {
return this.$auth.hasRoleOr(['SYSADMIN', 'SCHOOL_UNION_ADMIN'])
}
},
methods: {
onView(row) {
this.$refs.guava.edit(()=>{
@@ -294,10 +299,10 @@ layout("/layouts/platform.html"){
created() {
this.pageData()
this.queryCondolenceType()
//工会查询
this.$businessTool.listUnion().then((res) => (this.unionOptions = res))
//单位查询
this.$businessTool.listUnit().then((res) => (this.unitOptions = res))
if (this.isAdmin) {
this.$businessTool.listUnion().then((res) => (this.unionOptions = res))
this.$businessTool.listUnit().then((res) => (this.unitOptions = res))
}
this.$nextTick(() => {
setTimeout(() => {
this.getCondolencesType()
@@ -30,7 +30,7 @@ const write = {
<div class="info-box" :class="{ hidden: !isSelected }">
<div class="info-content">
<i class="fa fa-info-circle info-icon"></i>
<!-- <i class="fa fa-info-circle info-icon"></i>-->
<div class="info-text">
<h2 class="info-title" style="font-size: 18px;">推选信息</h2>
<p class="info-desc">您已被预选为{{ sessionName }}代表</p>
@@ -484,7 +484,11 @@ const write = {
}
.header-x .header-right {
display: flex;
align-items: center;
gap: 8px;
color: #64748b;
margin-top: 8px;
}
.notice-panel {
@@ -11,7 +11,7 @@ layout("/layouts/platform_h5.html"){
@search="doSearch"
input-align="left"
placeholder="请输入经办人搜索"
v-model="pageForm.userName"
v-model="pageForm.searchKeyword"
></van-search>
<van-tabs v-model="pageForm.approvalText"
@change="(val)=>{this.pageForm.approval = val==='1';this.doSearch();}">
@@ -39,21 +39,24 @@ layout("/layouts/platform_h5.html"){
</dict-tag>
</table-column>
<table-column label="金额(元)">{{row.money}}</table-column>
<table-column label="当前节点">{{row.curTaskName}}</table-column>
<table-column label="申请状态">
<span v-if="row.stateId == 1" style="color: #409eff;">待提交</span>
<span v-else-if="row.stateId == 2" style="color: #409eff;">待审核确认</span>
<span v-else-if="row.stateId == 3" style="color: #67c23a;">报销成功</span>
<span v-else-if="row.stateId == 4" style="color: #f56c6c;">拒绝</span>
<span v-else-if="row.stateId == 5" style="color: #409eff;">退回</span>
<span v-else>{{row.stateId}}</span>
</table-column>
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<span>查看</span>
</div>
<div class="action-btn" v-if="row.taskState === 10" @click="onAudit(row)">
<div class="action-btn" v-if="row.stateId == 2" @click="onAudit(row)">
<i class="fa fa-edit"></i>
<span>审核</span>
</div>
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)">
<i class="fa fa-undo"></i>
<span>撤回</span>
</div>
</template>
</table-list>
@@ -67,10 +70,10 @@ layout("/layouts/platform_h5.html"){
<van-cell-group title="审批意见">
<van-field label=""
:rules="[{ required: true,message:'请填审批意见' }]"
v-model="formData.tf_opinion"
v-model="formData.reviewOpinion"
required
type="textarea"
name="tf_opinion"
name="reviewOpinion"
rows="4"
autosize
class="more-text"
@@ -78,17 +81,16 @@ layout("/layouts/platform_h5.html"){
</van-cell-group>
<van-cell-group title="电子签名" class="form-section">
<van-field class="more-text" name="tf_userSign" label="" required
v-model="formData.tf_opinion" :rules="[{ required: true,message:'请签名' }]">
v-model="formData.reviewOpinion" :rules="[{ required: true,message:'请签名' }]">
<template #input>
<h5-signature v-model="formData.tf_userSign" slot="input"></h5-signature>
</template>
</van-field>
</van-cell-group>
<!-- 按钮区域 -->
<div class="form-actions">
<van-button type="danger" @click="handleTaskAction(6)">退回到发起人</van-button>
<van-button type="danger" @click="handleTaskAction(2)">拒绝申请</van-button>
<van-button type="primary" @click="handleTaskAction(1)">同意申请</van-button>
<van-button type="danger" @click="handleTaskAction(5)">退回到发起人</van-button>
<van-button type="danger" @click="handleTaskAction(4)">拒绝申请</van-button>
<van-button type="primary" @click="handleTaskAction(3)">同意申请</van-button>
</div>
</van-form>
</div>
@@ -122,33 +124,18 @@ layout("/layouts/platform_h5.html"){
}
},
methods: {
onRevoke(row) {
this.$dialog.confirm({
title: "温馨提示",
message: "您确定要撤回吗?"
}).then(() => {
this.$axios.post("/flow/common/revokeTask", { taskId: row.taskId }).then(res => {
if (res.code === 0) {
this.$toast.success("提交成功")
this.doSearch()
this.infoShow = false
}
})
}).catch(() => {
// on cancel
})
},
handleTaskAction(val) {
this.$refs.formRef.validate().then(() => {
this.$dialog.confirm({
title: "温馨提示",
message: "您确定要提交吗?"
}).then(() => {
this.$axios.post("/flow/common/executeTask", {
this.$axios.post("/platform/unionReimburse/review/reviewTask", {
data: JSON.stringify({
...this.formData,
submitType: val
})
id: this.formData.id,
reviewOpinion: this.formData.reviewOpinion
}),
submitType: val
}).then(res => {
if (res.code === 0) {
this.$toast.success("提交成功")
@@ -157,7 +144,6 @@ layout("/layouts/platform_h5.html"){
}
})
}).catch(() => {
// on cancel
})
}).catch()
},
@@ -168,10 +154,9 @@ layout("/layouts/platform_h5.html"){
onAudit(row) {
this.showApprovalForm = true
this.formData = {
tf_opinion: null,
tf_userSign: null,
processTaskId: row.taskId,
taskName: row.curTaskName
id: row.id,
reviewOpinion: null,
tf_userSign: null
}
this.$refs.unionReimburseInfoRef.onOpen(row)
},