commit
This commit is contained in:
+29
@@ -22,6 +22,7 @@ import com.budwk.app.zhgh.club.service.SysClubService;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.activityBudget.models.ActivityBudget;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.activityBudget.service.ActivityBudgetService;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.activityBudget.template.ActivityBudgetTemp;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.activityBudget.vo.ActivityBudgetQuotaInfoVo;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -91,6 +92,10 @@ public class ActivityBudgetApplyController {
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SLog( tag = "年度预算申报-预算申报", msg = "重新提交申请")
|
||||
public Result submitAgain(@Param("data") ActivityBudget activityBudget, @Param("taskId") Long taskId) {
|
||||
Result quotaValidateResult = activityBudgetService.validateQuota(activityBudget);
|
||||
if (quotaValidateResult.getCode() != 0) {
|
||||
return quotaValidateResult;
|
||||
}
|
||||
activityBudgetService.insertOrUpdate(activityBudget);
|
||||
|
||||
Dict dict = Dict.create();
|
||||
@@ -100,6 +105,30 @@ public class ActivityBudgetApplyController {
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询预算额度占用情况。
|
||||
* 参数说明:
|
||||
* 1. outlayManageSource:预算类型,当前仅分工会预算需要查询额度。
|
||||
* 2. unionId:分工会ID。
|
||||
* 3. applyDate:申报时间,用来确定预算所属年度。
|
||||
* 返回值字段:
|
||||
* 1. totalQuota:本年度总额度。
|
||||
* 2. applyingQuota:申请中额度。
|
||||
* 3. approvedQuota:已通过额度。
|
||||
* 4. occupiedQuota:已占用额度。
|
||||
* 5. remainQuota:剩余额度。
|
||||
*
|
||||
* @param activityBudget 预算申报参数
|
||||
* @return 当前预算主体的额度统计
|
||||
*/
|
||||
@At
|
||||
@ApiOperation("查询年度预算额度信息")
|
||||
@SaCheckPermission("activity.budget.apply")
|
||||
public Result getQuotaInfo(@Param("data") ActivityBudget activityBudget) {
|
||||
ActivityBudgetQuotaInfoVo quotaInfoVo = activityBudgetService.getQuotaInfo(activityBudget);
|
||||
return Result.success(quotaInfoVo);
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@SaCheckPermission("activity.budget.apply")
|
||||
|
||||
+36
@@ -4,6 +4,7 @@ import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.activityBudget.models.ActivityBudget;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.activityBudget.vo.ActivityBudgetQuotaInfoVo;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
@@ -22,6 +23,41 @@ public interface ActivityBudgetService extends BaseService<ActivityBudget> {
|
||||
*/
|
||||
Result submit(ActivityBudget activityBudget);
|
||||
|
||||
/**
|
||||
* 校验预算申报额度。
|
||||
* 参数说明:
|
||||
* 1. activityBudget.outlayManageSource:预算类型,分工会/协会/校工会。
|
||||
* 2. activityBudget.unionId:分工会预算时传对应分工会ID。
|
||||
* 4. activityBudget.declareTotalBudgetMoney:本次申报金额。
|
||||
* 5. activityBudget.applyDate:申报时间,用于判定所属年度;为空时按当前年份处理。
|
||||
* 返回值:
|
||||
* Result.success 表示可提交;当前仅分工会预算会触发年度额度校验;
|
||||
* Result.error 表示超额或年度额度未配置,message 会直接返回给前端提示。
|
||||
*
|
||||
* @param activityBudget 预算申报数据
|
||||
* @return 校验结果
|
||||
*/
|
||||
Result validateQuota(ActivityBudget activityBudget);
|
||||
|
||||
/**
|
||||
* 查询预算额度占用信息。
|
||||
* 参数说明:
|
||||
* 1. activityBudget.outlayManageSource:预算类型;当前仅分工会预算需要返回额度占用信息。
|
||||
* 2. activityBudget.unionId:分工会预算时用于定位对应年度额度记录。
|
||||
* 3. activityBudget.applyDate:用于计算预算所属年度;为空时按当前年份处理。
|
||||
* 返回值字段:
|
||||
* 1. year:预算所属年度。
|
||||
* 2. totalQuota:本年度总额度。
|
||||
* 3. applyingQuota:申请中额度,除已通过和已拒绝外的流程金额总和。
|
||||
* 4. approvedQuota:已通过额度。
|
||||
* 5. occupiedQuota:当前已占用额度,除已拒绝外全部计入。
|
||||
* 6. remainQuota:当前剩余额度。
|
||||
*
|
||||
* @param activityBudget 预算申报数据
|
||||
* @return 预算额度统计信息
|
||||
*/
|
||||
ActivityBudgetQuotaInfoVo getQuotaInfo(ActivityBudget activityBudget);
|
||||
|
||||
NutMap findOne(String id);
|
||||
|
||||
|
||||
|
||||
+188
@@ -4,7 +4,9 @@ import cn.afterturn.easypoi.excel.ExcelExportUtil;
|
||||
import cn.afterturn.easypoi.excel.entity.ExportParams;
|
||||
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
|
||||
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.lang.Dict;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.model.Audit;
|
||||
@@ -16,6 +18,7 @@ import com.budwk.app.flow.constant.FlowConst;
|
||||
import com.budwk.app.flow.engine.FlowEngine;
|
||||
import com.budwk.app.flow.entity.ProcessInstance;
|
||||
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.sys.models.Sys_dict;
|
||||
import com.budwk.app.sys.services.SysDictService;
|
||||
@@ -26,6 +29,8 @@ import com.budwk.app.zhgh.club.service.SysClubService;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.activityBudget.models.ActivityBudget;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.activityBudget.models.ActivityBudgetDetails;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.activityBudget.service.ActivityBudgetService;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.activityBudget.vo.ActivityBudgetQuotaInfoVo;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.union.model.OutlayManageUnion;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
@@ -37,6 +42,7 @@ import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -140,6 +146,10 @@ public class ActivityBudgetServiceImpl extends BaseServiceImpl<ActivityBudget> i
|
||||
}
|
||||
}
|
||||
}
|
||||
Result quotaValidateResult = validateQuota(activityBudget);
|
||||
if (quotaValidateResult != null && quotaValidateResult.getCode() != 0) {
|
||||
return quotaValidateResult;
|
||||
}
|
||||
//删除预算详情表
|
||||
if (StrUtil.isNotBlank(activityBudget.getId()) ) {
|
||||
dao().clear(ActivityBudgetDetails.class, Cnd.where("budgetId", "=", activityBudget.getId()));
|
||||
@@ -227,6 +237,80 @@ public class ActivityBudgetServiceImpl extends BaseServiceImpl<ActivityBudget> i
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result validateQuota(ActivityBudget activityBudget) {
|
||||
if (ObjectUtil.isEmpty(activityBudget) || StrUtil.isBlank(activityBudget.getOutlayManageSource())) {
|
||||
return Result.error("预算类型不能为空");
|
||||
}
|
||||
if (!"ACTIVITY_BUDGET_TYPE_TWO".equals(activityBudget.getOutlayManageSource())) {
|
||||
// 当前业务仅分工会预算保留年度额度校验,校工会和协会预算直接跳过。
|
||||
return Result.success();
|
||||
}
|
||||
if (ObjectUtil.isEmpty(activityBudget.getDeclareTotalBudgetMoney())) {
|
||||
return Result.error("预算金额不能为空");
|
||||
}
|
||||
|
||||
ActivityBudgetQuotaInfoVo quotaInfo = getQuotaInfo(activityBudget);
|
||||
if (!quotaInfo.getSelectedTarget()) {
|
||||
return Result.error(quotaInfo.getMessage());
|
||||
}
|
||||
if (ObjectUtil.isEmpty(quotaInfo.getTotalQuota())) {
|
||||
return Result.error(quotaInfo.getMessage());
|
||||
}
|
||||
|
||||
BigDecimal remainQuota = quotaInfo.getRemainQuota();
|
||||
BigDecimal declareTotalBudgetMoney = activityBudget.getDeclareTotalBudgetMoney();
|
||||
if (remainQuota.compareTo(declareTotalBudgetMoney) < 0) {
|
||||
return Result.error("本年度预算额度不足,剩余额度:" + remainQuota
|
||||
+ "元,申请中额度:" + quotaInfo.getApplyingQuota()
|
||||
+ "元,已通过额度:" + quotaInfo.getApprovedQuota() + "元");
|
||||
}
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActivityBudgetQuotaInfoVo getQuotaInfo(ActivityBudget activityBudget) {
|
||||
if (ObjectUtil.isEmpty(activityBudget)) {
|
||||
activityBudget = new ActivityBudget();
|
||||
}
|
||||
String outlayManageSource = activityBudget.getOutlayManageSource();
|
||||
Integer year = getBudgetYear(activityBudget);
|
||||
ActivityBudgetQuotaInfoVo quotaInfo = new ActivityBudgetQuotaInfoVo();
|
||||
quotaInfo.setYear(year);
|
||||
quotaInfo.setNeedLimit("ACTIVITY_BUDGET_TYPE_TWO".equals(outlayManageSource));
|
||||
quotaInfo.setSelectedTarget(true);
|
||||
quotaInfo.setTotalQuota(null);
|
||||
quotaInfo.setApplyingQuota(BigDecimal.ZERO);
|
||||
quotaInfo.setApprovedQuota(BigDecimal.ZERO);
|
||||
quotaInfo.setOccupiedQuota(BigDecimal.ZERO);
|
||||
quotaInfo.setRemainQuota(BigDecimal.ZERO);
|
||||
|
||||
if (!"ACTIVITY_BUDGET_TYPE_TWO".equals(outlayManageSource)) {
|
||||
return quotaInfo;
|
||||
}
|
||||
|
||||
BigDecimal totalQuota = getCurrentYearTotalQuota(activityBudget, year, quotaInfo);
|
||||
if (!quotaInfo.getSelectedTarget()) {
|
||||
return quotaInfo;
|
||||
}
|
||||
if (ObjectUtil.isEmpty(totalQuota)) {
|
||||
quotaInfo.setMessage("该年度未配置预算额度,请先维护年度预算");
|
||||
return quotaInfo;
|
||||
}
|
||||
|
||||
NutMap quotaUseInfo = getQuotaUseInfo(activityBudget, year);
|
||||
BigDecimal occupiedQuota = getMapBigDecimal(quotaUseInfo, "occupiedQuota");
|
||||
BigDecimal approvedQuota = getMapBigDecimal(quotaUseInfo, "approvedQuota");
|
||||
BigDecimal applyingQuota = getMapBigDecimal(quotaUseInfo, "applyingQuota");
|
||||
|
||||
quotaInfo.setTotalQuota(totalQuota);
|
||||
quotaInfo.setOccupiedQuota(occupiedQuota);
|
||||
quotaInfo.setApprovedQuota(approvedQuota);
|
||||
quotaInfo.setApplyingQuota(applyingQuota);
|
||||
quotaInfo.setRemainQuota(totalQuota.subtract(occupiedQuota));
|
||||
return quotaInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NutMap findOne(String id) {
|
||||
Sql sql = Sqls.create("""
|
||||
@@ -277,4 +361,108 @@ public class ActivityBudgetServiceImpl extends BaseServiceImpl<ActivityBudget> i
|
||||
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, mapList);
|
||||
CommonDownloadUtil.download("预算申报汇总表.xlsx", workbook, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析预算所属年度,页面没有单独年度字段时默认取申报时间中的年份。
|
||||
*
|
||||
* @param activityBudget 预算申报数据
|
||||
* @return 预算所属年度
|
||||
*/
|
||||
private Integer getBudgetYear(ActivityBudget activityBudget) {
|
||||
if (ObjectUtil.isEmpty(activityBudget) || StrUtil.isBlank(activityBudget.getApplyDate())) {
|
||||
return DateUtil.thisYear();
|
||||
}
|
||||
try {
|
||||
return DateUtil.parse(activityBudget.getApplyDate()).year();
|
||||
} catch (Exception e) {
|
||||
return DateUtil.thisYear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前预算类型在所属年度的总额度配置。
|
||||
*
|
||||
* @param activityBudget 预算申报数据
|
||||
* @param year 预算所属年度
|
||||
* @param quotaInfo 返回前端的额度信息对象,用于补充缺失主体的提示
|
||||
* @return 年度总额度;未配置时返回 null
|
||||
*/
|
||||
private BigDecimal getCurrentYearTotalQuota(ActivityBudget activityBudget, Integer year, ActivityBudgetQuotaInfoVo quotaInfo) {
|
||||
String outlayManageSource = activityBudget.getOutlayManageSource();
|
||||
if ("ACTIVITY_BUDGET_TYPE_TWO".equals(outlayManageSource)) {
|
||||
String unionId = StrUtil.blankToDefault(activityBudget.getUnionId(), SecurityUtil.getUnionId());
|
||||
if (StrUtil.isBlank(unionId)) {
|
||||
quotaInfo.setSelectedTarget(false);
|
||||
quotaInfo.setMessage("请选择分工会");
|
||||
return null;
|
||||
}
|
||||
OutlayManageUnion outlayManageUnion = dao().fetch(OutlayManageUnion.class,
|
||||
Cnd.where("year", "=", year).and("unionId", "=", unionId));
|
||||
return ObjectUtil.isEmpty(outlayManageUnion) ? null : outlayManageUnion.getTotalQuota();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计当前主体在本年度已经占用的预算。
|
||||
* 统计规则:
|
||||
* 1. 只统计已进入流程的申请;
|
||||
* 2. 已拒绝不计入额度;
|
||||
* 3. 其他状态全部计入占用额度;
|
||||
* 4. 申请中额度 = 非已通过、非已拒绝的金额合计。
|
||||
*
|
||||
* @param activityBudget 预算申报数据
|
||||
* @param year 预算所属年度
|
||||
* @return occupiedQuota/applyingQuota/approvedQuota 统计结果
|
||||
*/
|
||||
private NutMap getQuotaUseInfo(ActivityBudget activityBudget, Integer year) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
IFNULL(SUM(ab.declareTotalBudgetMoney), 0) occupiedQuota,
|
||||
IFNULL(SUM(CASE WHEN ins.state = @finishedState THEN ab.declareTotalBudgetMoney ELSE 0 END), 0) approvedQuota,
|
||||
IFNULL(SUM(CASE WHEN ins.state != @finishedState AND ins.state != @rejectState THEN ab.declareTotalBudgetMoney ELSE 0 END), 0) applyingQuota
|
||||
FROM
|
||||
activity_budget ab
|
||||
LEFT JOIN wf_process_instance ins ON ins.businessNo = ab.id
|
||||
$condition
|
||||
""");
|
||||
sql.setParam("finishedState", ProcessInstanceStateEnum.FINISHED.getCode());
|
||||
sql.setParam("rejectState", ProcessInstanceStateEnum.REJECT.getCode());
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.andEX("YEAR(ab.applyDate)", "=", year);
|
||||
cnd.and("ab.outlayManageSource", "=", activityBudget.getOutlayManageSource());
|
||||
cnd.and("ins.id", "IS NOT", null);
|
||||
cnd.and("ins.state", "!=", ProcessInstanceStateEnum.REJECT.getCode());
|
||||
if (StrUtil.isNotBlank(activityBudget.getId())) {
|
||||
// 重新提交或编辑当前记录时,先排除本条数据,避免把旧记录金额重复计入占用额度。
|
||||
cnd.and("ab.id", "!=", activityBudget.getId());
|
||||
}
|
||||
if ("ACTIVITY_BUDGET_TYPE_TWO".equals(activityBudget.getOutlayManageSource())) {
|
||||
cnd.and("ab.unionId", "=", StrUtil.blankToDefault(activityBudget.getUnionId(), SecurityUtil.getUnionId()));
|
||||
} else if ("ACTIVITY_BUDGET_TYPE_THREE".equals(activityBudget.getOutlayManageSource())) {
|
||||
cnd.and("ab.clubId", "=", activityBudget.getClubId());
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
sql.setCallback(Sqls.callback.map());
|
||||
execute(sql);
|
||||
return (NutMap) sql.getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一把数据库聚合结果转成 BigDecimal,避免前后端展示时出现 null。
|
||||
*
|
||||
* @param map 聚合结果
|
||||
* @param key 字段名
|
||||
* @return 对应金额,默认 0
|
||||
*/
|
||||
private BigDecimal getMapBigDecimal(NutMap map, String key) {
|
||||
Object value = ObjectUtil.isEmpty(map) ? null : map.get(key);
|
||||
if (ObjectUtil.isEmpty(value)) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
if (value instanceof BigDecimal) {
|
||||
return (BigDecimal) value;
|
||||
}
|
||||
return new BigDecimal(String.valueOf(value));
|
||||
}
|
||||
}
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package com.budwk.app.zhgh.dayofficework.outlay.activityBudget.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* @author zhf
|
||||
* @date 2026/4/20
|
||||
* @description 预算申报额度信息
|
||||
*/
|
||||
@Data
|
||||
public class ActivityBudgetQuotaInfoVo {
|
||||
/**
|
||||
* 预算所属年度。
|
||||
*/
|
||||
private Integer year;
|
||||
|
||||
/**
|
||||
* 当前预算类型是否需要做额度限制。
|
||||
*/
|
||||
private Boolean needLimit = false;
|
||||
|
||||
/**
|
||||
* 是否已经选择了有效的预算主体。
|
||||
* 分工会预算需要分工会ID,协会预算需要协会ID。
|
||||
*/
|
||||
private Boolean selectedTarget = true;
|
||||
|
||||
/**
|
||||
* 本年度总额度;未配置时为 null。
|
||||
*/
|
||||
private BigDecimal totalQuota;
|
||||
|
||||
/**
|
||||
* 当前申请中额度。
|
||||
*/
|
||||
private BigDecimal applyingQuota = BigDecimal.ZERO;
|
||||
|
||||
/**
|
||||
* 当前已通过额度。
|
||||
*/
|
||||
private BigDecimal approvedQuota = BigDecimal.ZERO;
|
||||
|
||||
/**
|
||||
* 当前已占用额度,除已拒绝外全部计入。
|
||||
*/
|
||||
private BigDecimal occupiedQuota = BigDecimal.ZERO;
|
||||
|
||||
/**
|
||||
* 当前剩余额度。
|
||||
*/
|
||||
private BigDecimal remainQuota = BigDecimal.ZERO;
|
||||
|
||||
/**
|
||||
* 当前额度提示信息。
|
||||
*/
|
||||
private String message;
|
||||
}
|
||||
+44
@@ -16,12 +16,15 @@ 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.dayofficework.outlay.outlayManage.club.model.OutlayManageClub;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.club.service.OutlayManageClubService;
|
||||
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.Cnd;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
@@ -49,6 +52,8 @@ public class OutlayManageClubController {
|
||||
|
||||
@Inject
|
||||
private SysRoleService sysRoleService;
|
||||
@Inject
|
||||
private OutlayManageClubService outlayManageClubService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/outlay/outlayManage/club/manage/index.html")
|
||||
@@ -89,6 +94,45 @@ public class OutlayManageClubController {
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询某个年度已经分配预算的协会ID。
|
||||
* 参数说明:
|
||||
* 1. year:预算年度。
|
||||
* 返回值说明:
|
||||
* 1. 返回当前年度已存在预算分配记录的协会ID列表。
|
||||
*
|
||||
* @param year 预算年度
|
||||
* @return 已分配预算的协会ID列表
|
||||
*/
|
||||
@At
|
||||
@ApiOperation("查询某个年度已分配预算的协会ID")
|
||||
@SaCheckPermission("outlay.outlayManage.club.manage")
|
||||
public Result getAllocatedClubIds(Integer year) {
|
||||
return Result.success(outlayManageClubService.getAllocatedClubIds(year));
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动新增协会预算分配。
|
||||
* 参数说明:
|
||||
* 1. outlayManageClub.year:预算年度。
|
||||
* 2. outlayManageClub.clubId:协会ID。
|
||||
* 3. outlayManageClub.totalQuota:分配总额度。
|
||||
* 返回值说明:
|
||||
* 1. 返回新增成功结果,失败时返回业务提示信息。
|
||||
*
|
||||
* @param outlayManageClub 新增分配数据
|
||||
* @return 新增结果
|
||||
*/
|
||||
@At
|
||||
@ApiOperation("手动新增协会预算分配")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("outlay.outlayManage.club.manage")
|
||||
@SLog(tag = "协会预算分配", msg = "手动新增了协会预算分配,年度:${args[0].year},协会:${args[0].clubId},总额度:${args[0].totalQuota}")
|
||||
public Result addAllocation(OutlayManageClub outlayManageClub) {
|
||||
outlayManageClubService.addAllocation(outlayManageClub);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@ApiOperation("查看是否分配")
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package com.budwk.app.zhgh.dayofficework.outlay.outlayManage.club.service;
|
||||
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.club.model.OutlayManageClub;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author zhf
|
||||
* @date 2026/4/20
|
||||
* @description 协会预算分配公共方法
|
||||
*/
|
||||
public interface OutlayManageClubService extends BaseService<OutlayManageClub> {
|
||||
|
||||
/**
|
||||
* 查询某个年度已经分配过预算的协会ID列表。
|
||||
* 参数说明:
|
||||
* 1. year:预算年度,查询该年度已存在的协会预算记录。
|
||||
* 返回值说明:
|
||||
* 1. 返回当前年度在 outlay_manage_club 表中已经存在的 clubId 集合。
|
||||
* 2. 前端可基于该结果过滤“审核通过但未分配预算”的协会下拉。
|
||||
*
|
||||
* @param year 预算年度
|
||||
* @return 已分配预算的协会ID列表
|
||||
*/
|
||||
List<String> getAllocatedClubIds(Integer year);
|
||||
|
||||
/**
|
||||
* 手动新增协会预算分配。
|
||||
* 参数说明:
|
||||
* 1. outlayManageClub.year:预算年度。
|
||||
* 2. outlayManageClub.clubId:协会ID。
|
||||
* 3. outlayManageClub.totalQuota:本次分配的总额度。
|
||||
* 返回值说明:
|
||||
* 1. 返回已补全协会名称、协会编码后的预算分配实体。
|
||||
* 2. 如果年度、协会、总额度为空,或该年度该协会已存在预算记录,则抛出业务异常。
|
||||
*
|
||||
* @param outlayManageClub 手动新增的预算分配数据
|
||||
* @return 新增后的预算分配实体
|
||||
*/
|
||||
OutlayManageClub addAllocation(OutlayManageClub outlayManageClub);
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package com.budwk.app.zhgh.dayofficework.outlay.outlayManage.club.service.impl;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.zhgh.club.model.SysClub;
|
||||
import com.budwk.app.zhgh.club.service.SysClubService;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.club.model.OutlayManageClub;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.club.service.OutlayManageClubService;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author zhf
|
||||
* @date 2026/4/20
|
||||
* @description 协会预算分配公共方法实现
|
||||
*/
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class OutlayManageClubServiceImpl extends BaseServiceImpl<OutlayManageClub> implements OutlayManageClubService {
|
||||
public OutlayManageClubServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
|
||||
@Inject
|
||||
private SysClubService sysClubService;
|
||||
|
||||
@Override
|
||||
public List<String> getAllocatedClubIds(Integer year) {
|
||||
if (ObjectUtil.isEmpty(year)) {
|
||||
return List.of();
|
||||
}
|
||||
List<OutlayManageClub> outlayManageClubList = query(Cnd.where("year", "=", year));
|
||||
return outlayManageClubList.stream()
|
||||
.map(OutlayManageClub::getClubId)
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutlayManageClub addAllocation(OutlayManageClub outlayManageClub) {
|
||||
if (ObjectUtil.isEmpty(outlayManageClub) || ObjectUtil.isEmpty(outlayManageClub.getYear())) {
|
||||
throw new BaseException("预算年度不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(outlayManageClub.getClubId())) {
|
||||
throw new BaseException("协会不能为空");
|
||||
}
|
||||
if (ObjectUtil.isEmpty(outlayManageClub.getTotalQuota())) {
|
||||
throw new BaseException("总额度不能为空");
|
||||
}
|
||||
if (outlayManageClub.getTotalQuota().compareTo(BigDecimal.ZERO) < 0) {
|
||||
throw new BaseException("总额度不能小于0");
|
||||
}
|
||||
|
||||
int count = count(Cnd.where("year", "=", outlayManageClub.getYear()).and("clubId", "=", outlayManageClub.getClubId()));
|
||||
if (count > 0) {
|
||||
throw new BaseException("该年度该协会已分配预算,请勿重复新增");
|
||||
}
|
||||
|
||||
SysClub sysClub = sysClubService.fetch(outlayManageClub.getClubId());
|
||||
if (ObjectUtil.isEmpty(sysClub)) {
|
||||
throw new BaseException("未找到对应协会信息");
|
||||
}
|
||||
|
||||
outlayManageClub.setClubName(sysClub.getClubName());
|
||||
outlayManageClub.setClubCode(sysClub.getClubCode());
|
||||
if (ObjectUtil.isEmpty(outlayManageClub.getUsedQuota())) {
|
||||
outlayManageClub.setUsedQuota(BigDecimal.ZERO);
|
||||
}
|
||||
return insert(outlayManageClub);
|
||||
}
|
||||
}
|
||||
+119
-14
@@ -90,6 +90,17 @@ layout("/layouts/platform.html"){
|
||||
v-model="formData.declareTotalBudgetMoney"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="quotaInfo.needLimit" label="年度额度提示" :span="2">
|
||||
<div v-if="quotaInfo.selectedTarget && quotaInfo.totalQuota !== null">
|
||||
<div>本年度总额度:{{quotaInfo.totalQuota}}元,申请中额度:{{quotaInfo.applyingQuota}}元,已通过额度:{{quotaInfo.approvedQuota}}元,当前剩余额度:{{quotaInfo.remainQuota}}元</div>
|
||||
<div v-if="isQuotaExceeded()" style="color: #F56C6C; margin-top: 6px;">
|
||||
当前申报金额已超出剩余额度,请先调整预算金额。
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #E6A23C;">
|
||||
{{quotaInfo.message}}
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="预算条目" span="2">
|
||||
<el-form-item
|
||||
prop="budgetReimburseType" label="预算条目"
|
||||
@@ -186,6 +197,16 @@ layout("/layouts/platform.html"){
|
||||
formData: {
|
||||
budgetDetails: []
|
||||
},
|
||||
quotaInfo: {
|
||||
needLimit: false,
|
||||
selectedTarget: true,
|
||||
totalQuota: null,
|
||||
applyingQuota: 0,
|
||||
approvedQuota: 0,
|
||||
occupiedQuota: 0,
|
||||
remainQuota: 0,
|
||||
message: ""
|
||||
},
|
||||
dialogVisible: false,
|
||||
editType: "apply"
|
||||
}
|
||||
@@ -200,57 +221,65 @@ layout("/layouts/platform.html"){
|
||||
return resp.data
|
||||
}
|
||||
},
|
||||
init() {
|
||||
async init() {
|
||||
if (this.bizId) {
|
||||
this.findOne(this.bizId).then(data => {
|
||||
this.formData = data
|
||||
})
|
||||
const data = await this.findOne(this.bizId)
|
||||
this.$set(this, "formData", data)
|
||||
} else {
|
||||
this.formData = {
|
||||
this.$set(this, "formData", {
|
||||
isSchoolBudget: false,
|
||||
isRepeatReimburse: true,
|
||||
budgetDetails: [],
|
||||
applyDate: this.$moment().format('YYYY-MM-DD'),
|
||||
userName: this.$store.state.user.username,
|
||||
mobile: this.$store.state.user.mobile,
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
clubChange(val) {
|
||||
const club = this.clubOption.find(c => c.id === val)
|
||||
this.formData.helpUnitName = club.clubName
|
||||
this.$set(this.formData, "helpUnitName", club ? club.clubName : "")
|
||||
this.refreshQuotaInfo()
|
||||
},
|
||||
unionChange(id) {
|
||||
if (id) {
|
||||
const union = this.unionList.find(c => c.id === id)
|
||||
this.$set(this.formData, "helpUnitName", union.unionname)
|
||||
this.$set(this.formData, "helpUnitName", union ? (union.name || union.unionname) : "")
|
||||
} else {
|
||||
this.$set(this.formData, "helpUnitName", '')
|
||||
}
|
||||
this.refreshQuotaInfo()
|
||||
},
|
||||
budgetTypeCodeChange(val) {
|
||||
if (val === "ACTIVITY_BUDGET_TYPE_ONE") {
|
||||
this.$set(this.formData, "helpUnitName", "校工会")
|
||||
this.$set(this.formData, "unionId", "")
|
||||
this.$set(this.formData, "clubId", "")
|
||||
} else if (val === "ACTIVITY_BUDGET_TYPE_TWO") {
|
||||
this.$set(this.formData, "helpUnitName", this.$store.state.user.union.name)
|
||||
this.$set(this.formData, "unionId", this.$store.state.user.union.id)
|
||||
this.$set(this.formData, "clubId", "")
|
||||
} else if (["ACTIVITY_BUDGET_TYPE_THREE"].includes(val)) {
|
||||
this.$set(this.formData, "helpUnitName", '')
|
||||
this.$set(this.formData, "clubId", '')
|
||||
this.$set(this.formData, "unionId", "")
|
||||
} else {
|
||||
this.$set(this.formData, "helpUnitName", "校工会")
|
||||
this.$set(this.formData, "unionId", "")
|
||||
this.$set(this.formData, "clubId", "")
|
||||
}
|
||||
if (val) {
|
||||
const budgetType = this.budgetTypeOption.find(b => b.code === val)
|
||||
this.$set(this.formData, "budgetTypeId", budgetType.code)
|
||||
}
|
||||
this.refreshQuotaInfo()
|
||||
},
|
||||
async getActivityBudgetType() {
|
||||
const data = await this.$businessTool.getDictOptions("ACTIVITY_BUDGET_TYPE")
|
||||
let budgetTypeOption = []
|
||||
if (this.$auth.hasRoleOr(['SYSADMIN'])) {
|
||||
this.budgetTypeOption = data
|
||||
this.$set(this, "budgetTypeOption", data)
|
||||
} else {
|
||||
if (this.$auth.hasRoleOr(['SCHOOL_UNION_ADMIN', 'SCHOOL_UNION_CHAIRMAN', 'SCHOOL_UNION_VICE_CHAIRMAN'])) {
|
||||
data.map(v => {
|
||||
@@ -273,7 +302,7 @@ layout("/layouts/platform.html"){
|
||||
}
|
||||
})
|
||||
}
|
||||
this.budgetTypeOption = budgetTypeOption
|
||||
this.$set(this, "budgetTypeOption", budgetTypeOption)
|
||||
if (this.budgetTypeOption.length === 1){
|
||||
this.$set(this.formData, "outlayManageSource", this.budgetTypeOption[0].code)
|
||||
this.budgetTypeCodeChange(this.budgetTypeOption[0].code)
|
||||
@@ -283,10 +312,81 @@ layout("/layouts/platform.html"){
|
||||
getSchoolBudget() {
|
||||
this.$axios.post("/platform/activity/budget/apply/getSchoolBudget").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.activityList = res.data
|
||||
this.$set(this, "activityList", res.data)
|
||||
}
|
||||
})
|
||||
},
|
||||
clearQuotaInfo() {
|
||||
this.$set(this, "quotaInfo", {
|
||||
needLimit: false,
|
||||
selectedTarget: true,
|
||||
totalQuota: null,
|
||||
applyingQuota: 0,
|
||||
approvedQuota: 0,
|
||||
occupiedQuota: 0,
|
||||
remainQuota: 0,
|
||||
message: ""
|
||||
})
|
||||
},
|
||||
refreshQuotaInfo() {
|
||||
const outlayManageSource = this.formData.outlayManageSource
|
||||
if (!outlayManageSource || outlayManageSource !== "ACTIVITY_BUDGET_TYPE_TWO") {
|
||||
this.clearQuotaInfo()
|
||||
return
|
||||
}
|
||||
if (!this.formData.unionId) {
|
||||
this.$set(this, "quotaInfo", {
|
||||
needLimit: true,
|
||||
selectedTarget: false,
|
||||
totalQuota: null,
|
||||
applyingQuota: 0,
|
||||
approvedQuota: 0,
|
||||
occupiedQuota: 0,
|
||||
remainQuota: 0,
|
||||
message: "请选择分工会"
|
||||
})
|
||||
return
|
||||
}
|
||||
this.$axios.post("/platform/activity/budget/apply/getQuotaInfo", {
|
||||
data: JSON.stringify({
|
||||
id: this.formData.id,
|
||||
applyDate: this.formData.applyDate,
|
||||
outlayManageSource: this.formData.outlayManageSource,
|
||||
unionId: this.formData.unionId,
|
||||
clubId: this.formData.clubId
|
||||
})
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$set(this, "quotaInfo", {
|
||||
needLimit: res.data.needLimit,
|
||||
selectedTarget: res.data.selectedTarget,
|
||||
totalQuota: res.data.totalQuota,
|
||||
applyingQuota: res.data.applyingQuota,
|
||||
approvedQuota: res.data.approvedQuota,
|
||||
occupiedQuota: res.data.occupiedQuota,
|
||||
remainQuota: res.data.remainQuota,
|
||||
message: res.data.message || ""
|
||||
})
|
||||
} else {
|
||||
this.$set(this, "quotaInfo", {
|
||||
needLimit: true,
|
||||
selectedTarget: false,
|
||||
totalQuota: null,
|
||||
applyingQuota: 0,
|
||||
approvedQuota: 0,
|
||||
occupiedQuota: 0,
|
||||
remainQuota: 0,
|
||||
message: res.message || "预算额度查询失败"
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
isQuotaExceeded() {
|
||||
if (!this.quotaInfo.needLimit || !this.quotaInfo.selectedTarget || this.quotaInfo.totalQuota === null) {
|
||||
return false
|
||||
}
|
||||
return Number(this.formData.declareTotalBudgetMoney || 0) > Number(this.quotaInfo.remainQuota || 0)
|
||||
},
|
||||
// 保存
|
||||
onSave() {
|
||||
this.$confirm("您确定保存吗?", "提示", {
|
||||
@@ -316,6 +416,8 @@ layout("/layouts/platform.html"){
|
||||
if (res.code === 0) {
|
||||
this.$message.success("提交成功")
|
||||
commonUtil.pjaxPush('/platform/activity/budget/applyList')
|
||||
} else {
|
||||
this.$message.error(res.message)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -333,6 +435,8 @@ layout("/layouts/platform.html"){
|
||||
if (res.code === 0) {
|
||||
this.$message.success("提交成功")
|
||||
commonUtil.pjaxPush('/platform/activity/budget/applyList')
|
||||
} else {
|
||||
this.$message.error(res.message)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -340,11 +444,12 @@ layout("/layouts/platform.html"){
|
||||
|
||||
},
|
||||
async created() {
|
||||
this.init()
|
||||
await this.init()
|
||||
await this.getActivityBudgetType()
|
||||
await this.getSchoolBudget()
|
||||
this.unionList = await this.$businessTool.listUnion(this.$store.state.user.union.id)
|
||||
this.clubOption = await this.$businessTool.listCLubByRole()
|
||||
this.$set(this, "unionList", await this.$businessTool.listUnion(this.$store.state.user.union.id))
|
||||
this.$set(this, "clubOption", await this.$businessTool.listCLubByRole())
|
||||
this.refreshQuotaInfo()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
+120
-1
@@ -24,6 +24,9 @@ layout("/layouts/platform.html"){
|
||||
</el-card>
|
||||
<el-card shadow="never" class="mt10">
|
||||
<table-tool label="预算分配">
|
||||
<el-button @click="openAddDialog" size="small" type="primary">
|
||||
新增分配
|
||||
</el-button>
|
||||
<template v-if="pageForm.year===$moment().format('YYYY')">
|
||||
<el-button @click="issuedOutlay" size="small" type="primary" v-if="!isAllocation">年度分配
|
||||
</el-button>
|
||||
@@ -83,6 +86,40 @@ layout("/layouts/platform.html"){
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
<el-dialog title="新增分配" :visible.sync="addDialogVisible" width="520px">
|
||||
<el-form :model="addForm" :rules="addFormRules" ref="addFormRef" label-width="90px">
|
||||
<el-form-item label="年度" prop="year">
|
||||
<el-date-picker
|
||||
@change="addYearChange"
|
||||
:picker-options="addYearPickerOptions"
|
||||
placeholder="选择年度"
|
||||
style="width: 100%" type="year"
|
||||
v-model="addForm.year"
|
||||
:clearable="false"
|
||||
value-format="yyyy">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="协会" prop="clubId">
|
||||
<el-select v-model="addForm.clubId" filterable clearable placeholder="请选择协会" style="width: 100%;">
|
||||
<el-option v-for="item in addClubOption"
|
||||
:key="item.id"
|
||||
:label="item.clubName"
|
||||
:value="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="总额度" prop="totalQuota">
|
||||
<el-input-number v-model="addForm.totalQuota"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
placeholder="请输入总额度"
|
||||
style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="closeAddDialog">取消</el-button>
|
||||
<el-button type="primary" @click="submitAddAllocation">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
</guava>
|
||||
</div>
|
||||
@@ -102,11 +139,70 @@ layout("/layouts/platform.html"){
|
||||
pageForm: {
|
||||
year: this.$moment().format("YYYY")
|
||||
},
|
||||
isAllocation: false
|
||||
isAllocation: false,
|
||||
addDialogVisible: false,
|
||||
addClubOption: [],
|
||||
addForm: {
|
||||
year: this.$moment().format("YYYY"),
|
||||
clubId: "",
|
||||
totalQuota: undefined
|
||||
},
|
||||
addFormRules: {
|
||||
year: [{required: true, message: '必填', trigger: ['change', 'blur']}],
|
||||
clubId: [{required: true, message: '必填', trigger: ['change', 'blur']}],
|
||||
totalQuota: [{required: true, message: '必填', trigger: ['change', 'blur']}]
|
||||
},
|
||||
addYearPickerOptions: {
|
||||
disabledDate(time) {
|
||||
const currentYear = Number(moment().format("YYYY"))
|
||||
return time.getFullYear() < currentYear
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
components: {},
|
||||
methods: {
|
||||
openAddDialog() {
|
||||
this.$set(this, "addDialogVisible", true)
|
||||
this.$set(this, "addForm", {
|
||||
year: this.pageForm.year,
|
||||
clubId: "",
|
||||
totalQuota: undefined
|
||||
})
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.addFormRef) {
|
||||
this.$refs.addFormRef.clearValidate()
|
||||
}
|
||||
})
|
||||
this.loadAvailableClubOption()
|
||||
},
|
||||
closeAddDialog() {
|
||||
this.$set(this, "addDialogVisible", false)
|
||||
},
|
||||
addYearChange() {
|
||||
this.$set(this.addForm, "clubId", "")
|
||||
this.loadAvailableClubOption()
|
||||
},
|
||||
loadAvailableClubOption() {
|
||||
if (!this.addForm.year) {
|
||||
this.$set(this, "addClubOption", [])
|
||||
return
|
||||
}
|
||||
Promise.all([
|
||||
this.$businessTool.listCLubByRole(),
|
||||
this.$axios.post("/platform/outlay/outlayManage/clubManage/getAllocatedClubIds", {
|
||||
year: this.addForm.year
|
||||
})
|
||||
]).then((res) => {
|
||||
const allClubOption = res[0] || []
|
||||
const allocatedClubIds = res[1].code === 0 ? res[1].data : []
|
||||
const availableClubOption = allClubOption.filter(item => !allocatedClubIds.includes(item.id))
|
||||
this.$set(this, "addClubOption", availableClubOption)
|
||||
if (availableClubOption.length === 0) {
|
||||
this.$message.warning("当前年度没有可新增分配的协会");
|
||||
}
|
||||
})
|
||||
},
|
||||
doSubmit(row) {
|
||||
this.$confirm('您确定要提交此条记录吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
@@ -125,6 +221,29 @@ layout("/layouts/platform.html"){
|
||||
})
|
||||
})
|
||||
},
|
||||
submitAddAllocation() {
|
||||
this.$refs.addFormRef.validate((valid) => {
|
||||
if (valid) {
|
||||
this.$confirm('您确定要新增这条分配记录吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$axios.post("/platform/outlay/outlayManage/clubManage/addAllocation", this.addForm)
|
||||
.then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
this.$message.success("新增成功");
|
||||
this.closeAddDialog()
|
||||
this.doSearch()
|
||||
this.getIsAllocation()
|
||||
} else {
|
||||
this.$message.error(resp.message)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
resetOutlay() {
|
||||
this.$confirm('您确定要重置今年的记录吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
|
||||
Reference in New Issue
Block a user