feat: 经费预算管理
This commit is contained in:
+3
@@ -2,6 +2,7 @@ package com.budwk.app.zhgh.dayofficework.outlay.outlayManage.service;
|
||||
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
|
||||
/**
|
||||
* 分工会预算统计服务。
|
||||
@@ -28,4 +29,6 @@ public interface OutlayManageUnionStatisticsService extends BaseService {
|
||||
* @return 包含各项收入、支出和余额字段的合计数据
|
||||
*/
|
||||
NutMap queryIncomeExpenseSummary(Integer year, String unionId);
|
||||
|
||||
Sql buildIncomeExpenseSql(Integer year, String unionId);
|
||||
}
|
||||
|
||||
+243
@@ -0,0 +1,243 @@
|
||||
package com.budwk.app.zhgh.dayofficework.outlay.outlayManage.service.impl;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.union.model.OutlayManageUnion;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Year;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class OutlayAnnualAllocateService extends BaseServiceImpl<OutlayManageUnion> {
|
||||
public OutlayAnnualAllocateService(Dao dao) { super(dao); }
|
||||
|
||||
public Sql sql(String text, Object... args) {
|
||||
Sql sql = Sqls.create(text);
|
||||
for (int i = 0; i < args.length; i += 2) sql.params().set(args[i].toString(), args[i + 1]);
|
||||
return sql;
|
||||
}
|
||||
|
||||
private NutMap first(Sql sql) {
|
||||
List<NutMap> rows = listMap(sql);
|
||||
return rows.isEmpty() ? null : rows.get(0);
|
||||
}
|
||||
|
||||
private String table(String type) {
|
||||
if ("base".equals(type)) return "outlay_annual_base";
|
||||
if ("special".equals(type)) return "outlay_annual_special";
|
||||
throw new IllegalArgumentException("经费类型错误");
|
||||
}
|
||||
|
||||
public Sql pageSql(String type, Integer year, String unionId, String projectName) {
|
||||
String table = table(type);
|
||||
return sql("SELECT a.*, un.name unionName, creator.username operatorName FROM " + table +
|
||||
" a LEFT JOIN sys_union un ON un.id=a.unionId LEFT JOIN sys_user creator ON creator.id=a.updatedBy " +
|
||||
"WHERE a.year=@year AND (@unionId='' OR a.unionId=@unionId)" +
|
||||
("special".equals(type) ? " AND (@projectName='' OR LOCATE(@projectName,a.projectName)>0)" : "") +
|
||||
" ORDER BY un.unionCode,a.createdAt DESC",
|
||||
"year", year, "unionId", StrUtil.blankToDefault(unionId, ""),
|
||||
"projectName", StrUtil.blankToDefault(StrUtil.trim(projectName), ""));
|
||||
}
|
||||
|
||||
public List<NutMap> summary(Integer year, String unionId) {
|
||||
return listMap(sql("""
|
||||
SELECT un.id, un.name unionName, COALESCE(b.money,0) baseMoney,
|
||||
COALESCE(s.money,0) specialMoney, COALESCE(b.money,0)+COALESCE(s.money,0) totalMoney
|
||||
FROM sys_union un
|
||||
LEFT JOIN (SELECT unionId,SUM(allocateMoney) money FROM outlay_annual_base
|
||||
WHERE year=@year AND status=1 GROUP BY unionId) b ON b.unionId=un.id
|
||||
LEFT JOIN (SELECT unionId,SUM(allocateMoney) money FROM outlay_annual_special
|
||||
WHERE year=@year AND status=1 GROUP BY unionId) s ON s.unionId=un.id
|
||||
WHERE (@unionId='' OR un.id=@unionId) ORDER BY un.unionCode
|
||||
""", "year", year, "unionId", StrUtil.blankToDefault(unionId, "")));
|
||||
}
|
||||
|
||||
public List<NutMap> historyRecords(Integer year, String unionId) {
|
||||
return listMap(sql("""
|
||||
SELECT a.year,un.name unionName,a.quarterly,a.allocateHeadMoney,a.allocateMoney
|
||||
FROM outlay_allocate_union a LEFT JOIN sys_union un ON un.id=a.unionId
|
||||
WHERE a.year=@year AND a.delFlag=0 AND (@unionId='' OR a.unionId=@unionId)
|
||||
ORDER BY un.unionCode,a.quarterly,a.id
|
||||
""", "year", year, "unionId", StrUtil.blankToDefault(unionId, "")));
|
||||
}
|
||||
|
||||
public Result generate(Integer year, BigDecimal standard, String unionId) {
|
||||
if (year == null || year < 1900 || year > Year.now().getValue() || !moneyValid(standard))
|
||||
return Result.error("请选择有效年度和人均标准(最多两位小数)");
|
||||
boolean current = year == Year.now().getValue();
|
||||
if (!current && first(sql("SELECT id FROM member_history WHERE year=@year LIMIT 1", "year", year)) == null)
|
||||
return Result.error("该年度没有会员历史快照,无法生成统计人数");
|
||||
List<NutMap> unions = listMap(sql("SELECT id FROM sys_union WHERE (@unionId='' OR id=@unionId) ORDER BY id",
|
||||
"unionId", StrUtil.blankToDefault(unionId, "")));
|
||||
int added = 0;
|
||||
for (NutMap union : unions) {
|
||||
String uid = union.getString("id");
|
||||
lockUnion(uid);
|
||||
if (first(sql("SELECT id FROM outlay_annual_base WHERE year=@year AND unionId=@unionId", "year", year, "unionId", uid)) != null) continue;
|
||||
NutMap count = first(sql("SELECT COUNT(DISTINCT id) memberCount FROM " + (current ? "vw_user" : "member_history") +
|
||||
" WHERE member=1 AND unionId=@unionId" + (current ? "" : " AND year=@year"), "unionId", uid, "year", year));
|
||||
int members = count.getInt("memberCount");
|
||||
BigDecimal calculated = standard.multiply(BigDecimal.valueOf(members));
|
||||
if (!moneyValid(calculated)) throw new IllegalArgumentException("计算金额超出允许范围");
|
||||
String id = UUID.randomUUID().toString().replace("-", "");
|
||||
execute(sql("""
|
||||
INSERT INTO outlay_annual_base
|
||||
(id,year,unionId,memberCount,standard,calculatedMoney,allocateMoney,remark,status,version,createdAt,createdBy,updatedAt,updatedBy)
|
||||
VALUES (@id,@year,@unionId,@count,@standard,@money,@money,'',0,0,@now,@actor,@now,@actor)
|
||||
""", "id", id, "year", year, "unionId", uid, "count", members, "standard", standard,
|
||||
"money", calculated, "now", System.currentTimeMillis(), "actor", StpUtil.getLoginIdAsString()));
|
||||
audit("base", id, "生成", null, first(sql("SELECT * FROM outlay_annual_base WHERE id=@id", "id", id)), "生成年度人数快照");
|
||||
added++;
|
||||
}
|
||||
return Result.success("已生成" + added + "条记录,已有记录保持不变");
|
||||
}
|
||||
|
||||
private void lockUnion(String unionId) {
|
||||
if (first(sql("SELECT id FROM sys_union WHERE id=@id FOR UPDATE", "id", unionId)) == null)
|
||||
throw new IllegalArgumentException("分工会不存在");
|
||||
}
|
||||
|
||||
private BigDecimal decimal(Object value) {
|
||||
return value == null ? BigDecimal.ZERO : new BigDecimal(value.toString());
|
||||
}
|
||||
|
||||
private boolean moneyValid(BigDecimal money) {
|
||||
return money != null && money.signum() >= 0 && money.scale() <= 2
|
||||
&& money.compareTo(new BigDecimal("99999999.99")) <= 0;
|
||||
}
|
||||
|
||||
public Result save(String type, String id, Integer year, String unionId, String projectName,
|
||||
Integer memberCount, BigDecimal standard, BigDecimal allocateMoney, String remark, Integer version) {
|
||||
String table = table(type);
|
||||
if (StrUtil.isBlank(id) || !id.matches("[a-fA-F0-9]{32}") || year == null || year < 1900 || year > 9999
|
||||
|| StrUtil.isBlank(unionId) || !moneyValid(allocateMoney) || remark != null && remark.length() > 1000)
|
||||
return Result.error("请填写有效年度、分工会和金额,说明最多1000字");
|
||||
lockUnion(unionId);
|
||||
NutMap old = first(sql("SELECT * FROM " + table + " WHERE id=@id FOR UPDATE", "id", id));
|
||||
if (old != null && (old.getInt("year") != year || !unionId.equals(old.getString("unionId"))))
|
||||
return Result.error("已有记录不能变更年度和分工会");
|
||||
if (old != null && (version == null || old.getInt("version") != version))
|
||||
return Result.error("记录已更新,请刷新后重试");
|
||||
boolean confirmed = old != null && old.getInt("status") == 1;
|
||||
BigDecimal calculated = null;
|
||||
if ("base".equals(type)) {
|
||||
if (old == null) return Result.error("请先生成基础经费记录");
|
||||
if (confirmed) {
|
||||
memberCount = old.getInt("memberCount");
|
||||
standard = new BigDecimal(old.get("standard").toString());
|
||||
}
|
||||
if (memberCount == null || memberCount < 0 || !moneyValid(standard)) return Result.error("人数和标准不正确");
|
||||
calculated = standard.multiply(BigDecimal.valueOf(memberCount));
|
||||
if (!moneyValid(calculated)) return Result.error("计算金额超出允许范围");
|
||||
if (allocateMoney.compareTo(calculated) != 0 && StrUtil.isBlank(remark)) return Result.error("实际金额与计算金额不一致,请填写调整说明");
|
||||
} else if (StrUtil.isBlank(projectName) || projectName.trim().length() > 100 || allocateMoney.signum() <= 0) {
|
||||
return Result.error("请填写专项名称(最多100字)和大于0的金额");
|
||||
}
|
||||
if (confirmed && StrUtil.isBlank(remark)) return Result.error("调整已下拨记录必须填写原因");
|
||||
if (confirmed) syncBudget(old, allocateMoney.subtract(new BigDecimal(old.get("allocateMoney").toString())));
|
||||
long now = System.currentTimeMillis();
|
||||
String actor = StpUtil.getLoginIdAsString();
|
||||
if (old == null) {
|
||||
execute(sql("""
|
||||
INSERT INTO outlay_annual_special
|
||||
(id,year,unionId,projectName,allocateMoney,remark,status,version,createdAt,createdBy,updatedAt,updatedBy)
|
||||
VALUES (@id,@year,@unionId,@name,@money,@remark,0,0,@now,@actor,@now,@actor)
|
||||
""", "id", id, "year", year, "unionId", unionId, "name", projectName.trim(), "money", allocateMoney,
|
||||
"remark", remark, "now", now, "actor", actor));
|
||||
} else {
|
||||
execute(sql("UPDATE " + table + " SET allocateMoney=@money,remark=@remark,updatedAt=@now,updatedBy=@actor,version=version+1" +
|
||||
("base".equals(type) ? ",memberCount=@count,standard=@standard,calculatedMoney=@calculated" : ",projectName=@name") + " WHERE id=@id",
|
||||
"money", allocateMoney, "remark", remark, "now", now, "actor", actor, "count", memberCount,
|
||||
"standard", standard, "calculated", calculated, "name", StrUtil.trim(projectName), "id", id));
|
||||
}
|
||||
audit(type, id, confirmed ? "调整下拨" : "保存", old, first(sql("SELECT * FROM " + table + " WHERE id=@id", "id", id)), remark);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
public Result confirm(String type, String id, Integer version) {
|
||||
String table = table(type);
|
||||
NutMap snapshot = first(sql("SELECT * FROM " + table + " WHERE id=@id", "id", id));
|
||||
if (snapshot == null) return Result.error("记录不存在");
|
||||
lockUnion(snapshot.getString("unionId"));
|
||||
NutMap row = first(sql("SELECT * FROM " + table + " WHERE id=@id FOR UPDATE", "id", id));
|
||||
if (row.getInt("status") == 1) return Result.success("该记录已下拨,无需重复操作");
|
||||
if (version == null || row.getInt("version") != version) return Result.error("记录已更新,请刷新后重试");
|
||||
confirmRecord(type, table, row);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
public Result batchConfirm(String type, Integer year, String unionId, String projectName) {
|
||||
String table = table(type);
|
||||
if (year == null || year < 1900 || year > 9999) return Result.error("请选择有效年度");
|
||||
List<NutMap> pending = listMap(sql("SELECT * FROM " + table +
|
||||
" WHERE year=@year AND status=0 AND (@unionId='' OR unionId=@unionId)" +
|
||||
("special".equals(type) ? " AND (@projectName='' OR LOCATE(@projectName,projectName)>0)" : "") +
|
||||
" ORDER BY unionId,id",
|
||||
"year", year, "unionId", StrUtil.blankToDefault(unionId, ""),
|
||||
"projectName", StrUtil.blankToDefault(StrUtil.trim(projectName), "")));
|
||||
int count = 0;
|
||||
for (NutMap candidate : pending) {
|
||||
lockUnion(candidate.getString("unionId"));
|
||||
NutMap row = first(sql("SELECT * FROM " + table + " WHERE id=@id FOR UPDATE", "id", candidate.getString("id")));
|
||||
if (row == null || row.getInt("status") == 1) continue;
|
||||
if (row.getInt("version") != candidate.getInt("version"))
|
||||
throw new IllegalArgumentException("待下拨记录已被修改,本次操作已取消,请刷新后重试");
|
||||
confirmRecord(type, table, row);
|
||||
count++;
|
||||
}
|
||||
return Result.success(count == 0 ? "当前筛选范围没有待下拨记录" : "成功下拨" + count + "条记录");
|
||||
}
|
||||
|
||||
private void confirmRecord(String type, String table, NutMap row) {
|
||||
String id = row.getString("id");
|
||||
syncBudget(row, new BigDecimal(row.get("allocateMoney").toString()));
|
||||
execute(sql("UPDATE " + table + " SET status=1,allocatedAt=@now,updatedAt=@now,updatedBy=@actor,version=version+1 WHERE id=@id",
|
||||
"id", id, "now", System.currentTimeMillis(), "actor", StpUtil.getLoginIdAsString()));
|
||||
audit(type, id, "确认下拨", row, first(sql("SELECT * FROM " + table + " WHERE id=@id", "id", id)), "确认下拨");
|
||||
}
|
||||
|
||||
private void syncBudget(NutMap row, BigDecimal delta) {
|
||||
List<NutMap> budgets = listMap(sql("SELECT * FROM outlay_manage_union WHERE year=@year AND unionId=@unionId FOR UPDATE",
|
||||
"year", row.getInt("year"), "unionId", row.getString("unionId")));
|
||||
if (budgets.size() > 1) throw new IllegalArgumentException("该分工会年度预算存在重复记录,不能下拨");
|
||||
if (budgets.isEmpty()) {
|
||||
if (delta.signum() < 0) throw new IllegalArgumentException("年度预算不存在,不能扣减");
|
||||
// 新建预算承接上年结余;已有预算仅增加本次差额,避免重复结转。
|
||||
NutMap previous = first(sql("SELECT COALESCE(totalQuota,0)-COALESCE(usedQuota,0) surplus FROM outlay_manage_union WHERE year=@year AND unionId=@unionId",
|
||||
"year", row.getInt("year") - 1, "unionId", row.getString("unionId")));
|
||||
BigDecimal carry = previous == null ? BigDecimal.ZERO : new BigDecimal(previous.get("surplus").toString());
|
||||
if (!moneyValid(carry.add(delta))) throw new IllegalArgumentException("下拨后年度总额度超出允许范围");
|
||||
execute(sql("""
|
||||
INSERT INTO outlay_manage_union (id,year,unionId,unionName,unionCode,totalQuota,usedQuota,delFlag,createdAt,updatedAt,createdBy,updatedBy)
|
||||
SELECT @id,@year,id,name,unionCode,@money,0,0,@now,@now,@actor,@actor FROM sys_union WHERE id=@unionId
|
||||
""", "id", UUID.randomUUID().toString().replace("-", ""), "year", row.getInt("year"),
|
||||
"unionId", row.getString("unionId"), "money", carry.add(delta), "now", System.currentTimeMillis(), "actor", StpUtil.getLoginIdAsString()));
|
||||
} else {
|
||||
NutMap budget = budgets.get(0);
|
||||
BigDecimal total = decimal(budget.get("totalQuota")).add(delta);
|
||||
if (!moneyValid(total)) throw new IllegalArgumentException("下拨后年度总额度超出允许范围");
|
||||
if (total.compareTo(decimal(budget.get("usedQuota"))) < 0)
|
||||
throw new IllegalArgumentException("调整后总额度不能低于已使用额度");
|
||||
execute(sql("UPDATE outlay_manage_union SET totalQuota=COALESCE(totalQuota,0)+@delta,updatedAt=@now,updatedBy=@actor WHERE id=@id",
|
||||
"delta", delta, "now", System.currentTimeMillis(), "actor", StpUtil.getLoginIdAsString(), "id", budget.getString("id")));
|
||||
}
|
||||
}
|
||||
|
||||
private void audit(String type, String id, String action, NutMap before, NutMap after, String remark) {
|
||||
execute(sql("""
|
||||
INSERT INTO outlay_annual_allocate_log (id,allocateType,allocateId,action,beforeData,afterData,remark,createdAt,createdBy)
|
||||
VALUES (@id,@type,@allocateId,@action,@before,@after,@remark,@now,@actor)
|
||||
""", "id", UUID.randomUUID().toString().replace("-", ""), "type", type, "allocateId", id,
|
||||
"action", action, "before", before == null ? null : Json.toJson(before), "after", Json.toJson(after),
|
||||
"remark", remark, "now", System.currentTimeMillis(), "actor", StpUtil.getLoginIdAsString()));
|
||||
}
|
||||
}
|
||||
+60
-27
@@ -42,39 +42,72 @@ public class OutlayManageUnionStatisticsServiceImpl extends BaseServiceImpl impl
|
||||
|
||||
@Override
|
||||
public NutMap queryIncomeExpenseSummary(Integer year, String unionId) {
|
||||
Cnd cnd = buildUnionManageCondition(year, unionId, true);
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
COALESCE(SUM(t.beginMoney), 0) AS beginMoney,
|
||||
COALESCE(SUM(t.feeJanApr), 0) AS feeJanApr,
|
||||
COALESCE(SUM(t.feeMayAug), 0) AS feeMayAug,
|
||||
COALESCE(SUM(t.feeSepDec), 0) AS feeSepDec,
|
||||
COALESCE(SUM(t.awardMoney), 0) AS awardMoney,
|
||||
COALESCE(SUM(t.usedMoney), 0) AS usedMoney,
|
||||
COALESCE(SUM(t.remainMoney), 0) AS remainMoney
|
||||
COALESCE(SUM(t.beginMoney),0) AS beginMoney,
|
||||
COALESCE(SUM(t.baseMoney),0) AS baseMoney,
|
||||
COALESCE(SUM(t.specialMoney),0) AS specialMoney,
|
||||
COALESCE(SUM(t.incomeMoney),0) AS incomeMoney,
|
||||
COALESCE(SUM(t.feeJanApr),0) AS feeJanApr,
|
||||
COALESCE(SUM(t.feeMayAug),0) AS feeMayAug,
|
||||
COALESCE(SUM(t.feeSepDec),0) AS feeSepDec,
|
||||
COALESCE(SUM(t.awardMoney),0) AS awardMoney,
|
||||
COALESCE(SUM(t.usedMoney),0) AS usedMoney,
|
||||
COALESCE(SUM(t.remainMoney),0) AS remainMoney
|
||||
FROM (
|
||||
SELECT
|
||||
mu.unionId,
|
||||
COALESCE(SUM(CASE WHEN au.quarterly = 1 THEN au.allocateHeadMoney ELSE 0 END), 0) AS beginMoney,
|
||||
COALESCE(SUM(CASE WHEN au.quarterly = 1 THEN au.allocateMoney ELSE 0 END), 0) AS feeJanApr,
|
||||
COALESCE(SUM(CASE WHEN au.quarterly = 2 THEN au.allocateMoney ELSE 0 END), 0) AS feeMayAug,
|
||||
COALESCE(SUM(CASE WHEN au.quarterly = 3 THEN au.allocateMoney ELSE 0 END), 0) AS feeSepDec,
|
||||
COALESCE(SUM(CASE WHEN au.quarterly = 4 THEN au.allocateMoney ELSE 0 END), 0) AS awardMoney,
|
||||
COALESCE(mu.usedQuota, 0) AS usedMoney,
|
||||
COALESCE(mu.totalQuota, 0) - COALESCE(mu.usedQuota, 0) AS remainMoney
|
||||
FROM outlay_manage_union mu
|
||||
LEFT JOIN outlay_allocate_union au ON au.year = mu.year
|
||||
AND au.unionId = mu.unionId
|
||||
AND au.delFlag = 0
|
||||
LEFT JOIN sys_union su ON su.id = mu.unionId
|
||||
$condition
|
||||
GROUP BY mu.year, mu.unionId, mu.usedQuota, mu.totalQuota
|
||||
) t
|
||||
""");
|
||||
sql.setCondition(cnd);
|
||||
""" + incomeExpenseQuery() + ") t");
|
||||
sql.params().set("incomeYear", year == null ? DateUtil.thisYear() : year);
|
||||
sql.setCondition(buildUnionManageCondition(year, unionId, true));
|
||||
return executeMap(sql);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Sql buildIncomeExpenseSql(Integer year, String unionId) {
|
||||
Sql sql = Sqls.create(incomeExpenseQuery() + " ORDER BY COALESCE(mu.unionCode,su.unionCode)");
|
||||
sql.params().set("incomeYear", year == null ? DateUtil.thisYear() : year);
|
||||
sql.setCondition(buildUnionManageCondition(year, unionId, true));
|
||||
return sql;
|
||||
}
|
||||
|
||||
private String incomeExpenseQuery() {
|
||||
// 历史会费并入年度基础经费,历史奖励并入专项经费;分别汇总避免关联后重复累计。
|
||||
// 年初数由年度总额度扣除当年各类下拨计算,兼容没有历史第一期记录的新流程。
|
||||
return """
|
||||
SELECT mu.year, mu.unionId,
|
||||
COALESCE(mu.unionName,su.name) AS unionName,
|
||||
COALESCE(mu.unionCode,su.unionCode) AS unionCode,
|
||||
COALESCE(mu.totalQuota,0) - (COALESCE(b.money,0) + COALESCE(sp.money,0) + COALESCE(au.feeJanApr,0) + COALESCE(au.feeMayAug,0) + COALESCE(au.feeSepDec,0) + COALESCE(au.awardMoney,0)) AS beginMoney,
|
||||
COALESCE(b.money,0) + COALESCE(au.feeJanApr,0) + COALESCE(au.feeMayAug,0) + COALESCE(au.feeSepDec,0) AS baseMoney,
|
||||
COALESCE(sp.money,0) + COALESCE(au.awardMoney,0) AS specialMoney,
|
||||
(COALESCE(b.money,0) + COALESCE(sp.money,0) + COALESCE(au.feeJanApr,0) + COALESCE(au.feeMayAug,0) + COALESCE(au.feeSepDec,0) + COALESCE(au.awardMoney,0)) AS incomeMoney,
|
||||
COALESCE(au.feeJanApr,0) AS feeJanApr,
|
||||
COALESCE(au.feeMayAug,0) AS feeMayAug,
|
||||
COALESCE(au.feeSepDec,0) AS feeSepDec,
|
||||
COALESCE(au.awardMoney,0) AS awardMoney,
|
||||
COALESCE(mu.usedQuota,0) AS usedMoney,
|
||||
COALESCE(mu.totalQuota,0)-COALESCE(mu.usedQuota,0) AS remainMoney
|
||||
FROM outlay_manage_union mu
|
||||
LEFT JOIN sys_union su ON su.id=mu.unionId
|
||||
LEFT JOIN (
|
||||
SELECT year,unionId,SUM(allocateMoney) money FROM outlay_annual_base
|
||||
WHERE year=@incomeYear AND status=1 GROUP BY year,unionId
|
||||
) b ON b.year=mu.year AND b.unionId=mu.unionId
|
||||
LEFT JOIN (
|
||||
SELECT year,unionId,SUM(allocateMoney) money FROM outlay_annual_special
|
||||
WHERE year=@incomeYear AND status=1 GROUP BY year,unionId
|
||||
) sp ON sp.year=mu.year AND sp.unionId=mu.unionId
|
||||
LEFT JOIN (
|
||||
SELECT year,unionId,
|
||||
SUM(CASE WHEN quarterly=1 THEN allocateMoney ELSE 0 END) feeJanApr,
|
||||
SUM(CASE WHEN quarterly=2 THEN allocateMoney ELSE 0 END) feeMayAug,
|
||||
SUM(CASE WHEN quarterly=3 THEN allocateMoney ELSE 0 END) feeSepDec,
|
||||
SUM(CASE WHEN quarterly=4 THEN allocateMoney ELSE 0 END) awardMoney
|
||||
FROM outlay_allocate_union WHERE year=@incomeYear AND delFlag=0 GROUP BY year,unionId
|
||||
) au ON au.year=mu.year AND au.unionId=mu.unionId
|
||||
$condition
|
||||
""";
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造两个列表共用的预算主表过滤条件,保证汇总和分页列表权限口径一致。
|
||||
*/
|
||||
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
package com.budwk.app.zhgh.dayofficework.outlay.outlayManage.union.controller;
|
||||
|
||||
import cn.afterturn.easypoi.excel.ExcelExportUtil;
|
||||
import cn.afterturn.easypoi.excel.entity.ExportParams;
|
||||
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
|
||||
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import com.budwk.app.base.utils.CommonDownloadUtil;
|
||||
import com.budwk.app.sys.models.Sys_dict;
|
||||
import com.budwk.app.sys.services.SysDictService;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.service.impl.OutlayAnnualAllocateService;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.mvc.annotation.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.UUID;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/outlay/outlayManage/unionAllocate/annual")
|
||||
@Ok("json:full")
|
||||
public class OutlayAnnualAllocateController {
|
||||
@Inject
|
||||
private OutlayAnnualAllocateService outlayAnnualAllocateService;
|
||||
@Inject
|
||||
private SysDictService sysDictService;
|
||||
|
||||
@At
|
||||
@SaCheckPermission("outlay.outlayManage.union.allocate")
|
||||
public Result pageData(PageForm pageForm, String type, Integer year, String unionId, String projectName) {
|
||||
return Result.success(outlayAnnualAllocateService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(),
|
||||
outlayAnnualAllocateService.pageSql(type, year, unionId, projectName)));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("outlay.outlayManage.union.allocate")
|
||||
public Result summary(Integer year, String unionId) {
|
||||
return Result.success(outlayAnnualAllocateService.summary(year, unionId));
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@SaCheckPermission("outlay.outlayManage.union.allocate")
|
||||
public void doExport(String type, Integer year, String unionId, HttpServletResponse response) {
|
||||
int queryYear = year == null ? DateUtil.thisYear() : year;
|
||||
if (queryYear < 1900 || queryYear > 9999) throw new IllegalArgumentException("请选择有效年度");
|
||||
List<NutMap> rows;
|
||||
List<ExcelExportEntity> columns = new ArrayList<>();
|
||||
columns.add(new ExcelExportEntity("年度", "year", 12));
|
||||
columns.add(new ExcelExportEntity("分工会", "unionName", 30));
|
||||
String title;
|
||||
if ("summary".equals(type)) {
|
||||
title = "下拨汇总";
|
||||
rows = outlayAnnualAllocateService.summary(queryYear, unionId);
|
||||
for (NutMap row : rows) row.put("year", queryYear);
|
||||
columns.add(new ExcelExportEntity("基础经费已下拨(元)", "baseMoney", 24));
|
||||
columns.add(new ExcelExportEntity("专项经费已下拨(元)", "specialMoney", 24));
|
||||
columns.add(new ExcelExportEntity("新流程合计下拨(元)", "totalMoney", 24));
|
||||
} else if ("history".equals(type)) {
|
||||
title = "历史季度记录";
|
||||
rows = outlayAnnualAllocateService.historyRecords(queryYear, unionId);
|
||||
Map<String, String> projectNames = new HashMap<>();
|
||||
for (Sys_dict dict : sysDictService.getSubListByCode("OUTLAY_QUARTERLY")) {
|
||||
projectNames.put(dict.getCode(), dict.getName());
|
||||
}
|
||||
for (NutMap row : rows) {
|
||||
String code = row.getString("quarterly");
|
||||
row.put("projectName", projectNames.getOrDefault(code, code));
|
||||
}
|
||||
columns.add(new ExcelExportEntity("分配项目", "projectName", 24));
|
||||
columns.add(new ExcelExportEntity("分配前额度(元)", "allocateHeadMoney", 24));
|
||||
columns.add(new ExcelExportEntity("分配金额(元)", "allocateMoney", 24));
|
||||
} else {
|
||||
throw new IllegalArgumentException("不支持的导出类型");
|
||||
}
|
||||
ExportParams params = new ExportParams();
|
||||
params.setType(ExcelType.XSSF);
|
||||
Workbook workbook = ExcelExportUtil.exportExcel(params, columns, rows);
|
||||
CommonDownloadUtil.download(queryYear + "年" + title + ".xlsx", workbook, response);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("outlay.outlayManage.union.allocate")
|
||||
public Result newId() {
|
||||
return Result.success("", UUID.randomUUID().toString().replace("-", ""));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("outlay.outlayManage.union.allocate")
|
||||
@POST
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result generate(Integer year, @Param("standard") BigDecimal standard, String unionId) {
|
||||
return outlayAnnualAllocateService.generate(year, standard, unionId);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("outlay.outlayManage.union.allocate")
|
||||
@POST
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result save(String type, String id, Integer year, String unionId, String projectName,
|
||||
Integer memberCount, @Param("standard") BigDecimal standard, @Param("allocateMoney") BigDecimal allocateMoney, String remark, Integer version) {
|
||||
return outlayAnnualAllocateService.save(type, id, year, unionId, projectName, memberCount, standard, allocateMoney, remark, version);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("outlay.outlayManage.union.allocate")
|
||||
@POST
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result confirm(String type, String id, Integer version) {
|
||||
return outlayAnnualAllocateService.confirm(type, id, version);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("outlay.outlayManage.union.allocate")
|
||||
@POST
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result batchConfirm(String type, Integer year, String unionId, String projectName) {
|
||||
return outlayAnnualAllocateService.batchConfirm(type, year, unionId, projectName);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("outlay.outlayManage.union.allocate")
|
||||
public Result logs(PageForm pageForm, String type, String id) {
|
||||
return Result.success(outlayAnnualAllocateService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), outlayAnnualAllocateService.sql("""
|
||||
SELECT l.*,u.username operatorName FROM outlay_annual_allocate_log l
|
||||
LEFT JOIN sys_user u ON u.id=l.createdBy
|
||||
WHERE l.allocateType=@type AND l.allocateId=@id ORDER BY l.createdAt DESC,l.id
|
||||
""", "type", type, "id", id)));
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -53,7 +53,7 @@ public class OutlayManageUnionAllocateController {
|
||||
private OutlayManageUnionAllocateService outlayManageUnionAllocateService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/outlay/outlayManage/union/allocate/index.html")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/outlay/outlayManage/union/allocate/annual.html")
|
||||
@SaCheckPermission("outlay.outlayManage.union.allocate")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
+5
-55
@@ -6,22 +6,17 @@ import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
|
||||
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
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.service.BaseService;
|
||||
import com.budwk.app.base.utils.CommonDownloadUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.service.OutlayManageUnionStatisticsService;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.union.vo.OutlayUnionIncomeExpenseVO;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
@@ -85,11 +80,10 @@ public class OutlayManageUnionIncomeExpenseController {
|
||||
ArrayList<ExcelExportEntity> entities = new ArrayList<>();
|
||||
entities.add(new ExcelExportEntity("单位", "unionName", 25));
|
||||
entities.add(new ExcelExportEntity("年初数", "beginMoney", 20));
|
||||
entities.add(new ExcelExportEntity("1-4月会费", "feeJanApr", 20));
|
||||
entities.add(new ExcelExportEntity("5-8月会费", "feeMayAug", 20));
|
||||
entities.add(new ExcelExportEntity("9-12月会费", "feeSepDec", 20));
|
||||
entities.add(new ExcelExportEntity("评优奖励", "awardMoney", 20));
|
||||
entities.add(new ExcelExportEntity("1-12月支出", "usedMoney", 20));
|
||||
entities.add(new ExcelExportEntity("年度基础经费", "baseMoney", 20));
|
||||
entities.add(new ExcelExportEntity("专项经费", "specialMoney", 20));
|
||||
entities.add(new ExcelExportEntity("合计下拨", "incomeMoney", 20));
|
||||
entities.add(new ExcelExportEntity("年度支出", "usedMoney", 20));
|
||||
entities.add(new ExcelExportEntity("经费余额", "remainMoney", 20));
|
||||
|
||||
ExportParams exportParams = new ExportParams();
|
||||
@@ -98,52 +92,8 @@ public class OutlayManageUnionIncomeExpenseController {
|
||||
CommonDownloadUtil.download(queryYear + "年二级分工会经费收支使用情况表.xlsx", workbook, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计参数说明:
|
||||
* year 为统计年度,未传时默认当前年份;unionId 为二级分工会主键,管理员可为空查询全部。
|
||||
* 返回 VO 中 beginMoney 是年初数,feeJanApr/feeMayAug/feeSepDec 是三个会费收入段,
|
||||
* awardMoney 是评优奖励,usedMoney 是年度支出,remainMoney 是当前经费余额。
|
||||
*/
|
||||
private Sql buildIncomeExpenseSql(Integer year, String unionId) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
mu.year AS year,
|
||||
mu.unionId AS unionId,
|
||||
COALESCE(mu.unionName, su.name) AS unionName,
|
||||
COALESCE(mu.unionCode, su.unionCode) AS unionCode,
|
||||
COALESCE(SUM(CASE WHEN au.quarterly = 1 THEN au.allocateHeadMoney ELSE 0 END), 0) AS beginMoney,
|
||||
COALESCE(SUM(CASE WHEN au.quarterly = 1 THEN au.allocateMoney ELSE 0 END), 0) AS feeJanApr,
|
||||
COALESCE(SUM(CASE WHEN au.quarterly = 2 THEN au.allocateMoney ELSE 0 END), 0) AS feeMayAug,
|
||||
COALESCE(SUM(CASE WHEN au.quarterly = 3 THEN au.allocateMoney ELSE 0 END), 0) AS feeSepDec,
|
||||
COALESCE(SUM(CASE WHEN au.quarterly = 4 THEN au.allocateMoney ELSE 0 END), 0) AS awardMoney,
|
||||
COALESCE(mu.usedQuota, 0) AS usedMoney,
|
||||
COALESCE(mu.totalQuota, 0) - COALESCE(mu.usedQuota, 0) AS remainMoney
|
||||
FROM outlay_manage_union mu
|
||||
LEFT JOIN sys_union su ON su.id = mu.unionId
|
||||
LEFT JOIN outlay_allocate_union au ON au.year = mu.year
|
||||
AND au.unionId = mu.unionId
|
||||
AND au.delFlag = 0
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("mu.delFlag", "=", false);
|
||||
cnd.and("mu.year", "=", getQueryYear(year));
|
||||
cnd.and("mu.totalQuota", "IS NOT", null);
|
||||
cnd.andEX("mu.unionId", "=", unionId);
|
||||
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
|
||||
cnd.and("mu.unionId", "=", SecurityUtil.getUnionId());
|
||||
}
|
||||
cnd.groupBy("mu.year");
|
||||
cnd.groupBy("mu.unionId");
|
||||
cnd.groupBy("mu.unionName");
|
||||
cnd.groupBy("mu.unionCode");
|
||||
cnd.groupBy("su.name");
|
||||
cnd.groupBy("su.unionCode");
|
||||
cnd.groupBy("mu.usedQuota");
|
||||
cnd.groupBy("mu.totalQuota");
|
||||
cnd.asc("mu.unionCode");
|
||||
sql.setCondition(cnd);
|
||||
return sql;
|
||||
return outlayManageUnionStatisticsService.buildIncomeExpenseSql(year, unionId);
|
||||
}
|
||||
|
||||
private Integer getQueryYear(Integer year) {
|
||||
|
||||
+6
@@ -20,6 +20,12 @@ public class OutlayUnionIncomeExpenseVO {
|
||||
|
||||
private BigDecimal beginMoney;
|
||||
|
||||
private BigDecimal baseMoney;
|
||||
|
||||
private BigDecimal specialMoney;
|
||||
|
||||
private BigDecimal incomeMoney;
|
||||
|
||||
private BigDecimal feeJanApr;
|
||||
|
||||
private BigDecimal feeMayAug;
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
-- 年度基础经费与专项经费下拨;原 outlay_allocate_union 历史记录及余额保持不变。
|
||||
-- 人数口径沿用会员统计:本年度 vw_user.member=1,历史年度 member_history.member=1。
|
||||
-- 新表仅记录新流程数据,不自动将历史季度下拨转为年度基础经费,避免重复计入预算。
|
||||
CREATE TABLE IF NOT EXISTS `outlay_annual_base` (
|
||||
`id` varchar(32) NOT NULL,
|
||||
`year` int NOT NULL,
|
||||
`unionId` varchar(32) NOT NULL,
|
||||
`memberCount` int NOT NULL,
|
||||
`standard` decimal(10,2) NOT NULL DEFAULT 800.00,
|
||||
`calculatedMoney` decimal(10,2) NOT NULL,
|
||||
`allocateMoney` decimal(10,2) NOT NULL,
|
||||
`remark` varchar(1000) DEFAULT NULL,
|
||||
`status` tinyint NOT NULL DEFAULT 0 COMMENT '0待下拨 1已下拨',
|
||||
`version` int NOT NULL DEFAULT 0,
|
||||
`allocatedAt` bigint DEFAULT NULL,
|
||||
`createdAt` bigint NOT NULL,
|
||||
`createdBy` varchar(32) NOT NULL,
|
||||
`updatedAt` bigint NOT NULL,
|
||||
`updatedBy` varchar(32) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_annual_base_year_union` (`year`,`unionId`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='年度基础经费下拨';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `outlay_annual_special` (
|
||||
`id` varchar(32) NOT NULL COMMENT '同时作为提交幂等标识',
|
||||
`year` int NOT NULL,
|
||||
`unionId` varchar(32) NOT NULL,
|
||||
`projectName` varchar(100) NOT NULL,
|
||||
`allocateMoney` decimal(10,2) NOT NULL,
|
||||
`remark` varchar(1000) DEFAULT NULL,
|
||||
`status` tinyint NOT NULL DEFAULT 0 COMMENT '0待下拨 1已下拨',
|
||||
`version` int NOT NULL DEFAULT 0,
|
||||
`allocatedAt` bigint DEFAULT NULL,
|
||||
`createdAt` bigint NOT NULL,
|
||||
`createdBy` varchar(32) NOT NULL,
|
||||
`updatedAt` bigint NOT NULL,
|
||||
`updatedBy` varchar(32) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_annual_special_year_union` (`year`,`unionId`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='专项经费下拨';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `outlay_annual_allocate_log` (
|
||||
`id` varchar(32) NOT NULL,
|
||||
`allocateType` varchar(10) NOT NULL,
|
||||
`allocateId` varchar(32) NOT NULL,
|
||||
`action` varchar(30) NOT NULL,
|
||||
`beforeData` text DEFAULT NULL COMMENT '操作前完整快照',
|
||||
`afterData` text NOT NULL COMMENT '操作后完整快照',
|
||||
`remark` varchar(1000) DEFAULT NULL,
|
||||
`createdAt` bigint NOT NULL,
|
||||
`createdBy` varchar(32) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_annual_log_record_time` (`allocateType`,`allocateId`,`createdAt`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='经费下拨及调整记录';
|
||||
|
||||
-- 沿用现有权限与入口,仅调整菜单名称。
|
||||
UPDATE sys_menu SET name='年度经费下拨', aliasName='年度经费下拨'
|
||||
WHERE permission='outlay.outlayManage.union.allocate';
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<style>
|
||||
<!--#include("../fixedListLayout.css"){}#-->
|
||||
.fund-edit-dialog .el-dialog { max-width: calc(100vw - 32px); }
|
||||
.fund-edit-dialog .el-dialog__body { padding: 20px 28px; max-height: 65vh; overflow-y: auto; }
|
||||
.fund-edit-dialog .el-dialog__footer { padding: 16px 28px; border-top: 1px solid #ebeef5; }
|
||||
.fund-edit-dialog .el-alert { margin-bottom: 20px; }
|
||||
.fund-edit-section { grid-column: 1 / -1; font-weight: 600; color: #303133; border-bottom: 1px solid #ebeef5; padding-bottom: 10px; }
|
||||
.fund-edit-hint { color: #909399; font-size: 12px; line-height: 20px; }
|
||||
.fund-edit-difference { grid-column: 1 / -1; background: #fdf6ec; color: #b88230; border-radius: 4px; padding: 10px 14px; line-height: 22px; }
|
||||
.fund-edit-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px 24px; }
|
||||
.fund-edit-form .el-form-item { margin-bottom: 0; min-width: 0; }
|
||||
.fund-edit-form .el-form-item__label { padding-bottom: 6px; line-height: 24px; }
|
||||
.fund-edit-form .el-select,
|
||||
.fund-edit-form .el-date-editor,
|
||||
.fund-edit-form .el-input-number { width: 100%; }
|
||||
.fund-edit-form .fund-edit-wide { grid-column: 1 / -1; }
|
||||
.fund-edit-amount { display: flex; align-items: center; flex-wrap: wrap; gap: 16px; }
|
||||
.fund-edit-amount .el-input-number { flex: 1; min-width: 200px; }
|
||||
.fund-edit-calculated { display: block; padding: 0 15px; background: #f5f7fa; border: 1px solid #e4e7ed; border-radius: 4px; font-size: 20px; font-weight: 600; color: #303133; }
|
||||
@media (max-width: 600px) {
|
||||
.fund-edit-form { grid-template-columns: minmax(0, 1fr); }
|
||||
}
|
||||
</style>
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava"><template>
|
||||
<div class="outlay-union-fixed-list-page">
|
||||
<el-card class="outlay-union-fixed-query-card" shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="年度"><el-date-picker v-model="query.year" type="year" value-format="yyyy" :clearable="false" @change="doSearch"></el-date-picker></search-item>
|
||||
<search-item label="分工会"><el-select v-model="query.unionId" clearable filterable @change="doSearch"><el-option v-for="u in unionList" :key="u.id" :value="u.id" :label="u.name"></el-option></el-select></search-item>
|
||||
<search-item v-if="tab==='special'" label="专项名称"><el-input v-model="query.projectName" clearable placeholder="请输入专项名称" @keyup.enter.native="doSearch" @clear="doSearch"></el-input></search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
<el-card class="outlay-union-fixed-list-card mt10" shadow="never">
|
||||
<el-tabs class="outlay-union-summary-table" v-model="tab" @tab-click="changeTab">
|
||||
<el-tab-pane label="年度基础经费" name="base"></el-tab-pane>
|
||||
<el-tab-pane label="专项经费" name="special"></el-tab-pane>
|
||||
<el-tab-pane label="下拨汇总" name="summary"></el-tab-pane>
|
||||
<el-tab-pane label="历史季度记录" name="history"></el-tab-pane>
|
||||
</el-tabs>
|
||||
<table-tool label="年度经费下拨">
|
||||
<el-button v-if="tab==='base'" size="small" type="primary" @click="generateVisible=true" :disabled="busy">生成年度基础经费</el-button>
|
||||
<el-button v-if="tab==='special'" size="small" type="primary" @click="addSpecial" :disabled="busy">新增专项经费</el-button>
|
||||
<el-button v-if="tab==='base' || tab==='special'" size="small" type="success" @click="batchConfirm" :loading="busy">一键下拨</el-button>
|
||||
<el-button v-if="tab==='summary' || tab==='history'" size="small" type="primary" @click="doExport" :disabled="loading || busy">导出Excel</el-button>
|
||||
</table-tool>
|
||||
<el-table :key="tab" ref="tableRef" :data="rows" :height="tableHeight" v-loading="loading" border row-key="id">
|
||||
<el-table-column v-if="tab!=='summary'" prop="year" label="年度" width="85"></el-table-column>
|
||||
<el-table-column prop="unionName" label="分工会" min-width="170"></el-table-column>
|
||||
<template v-if="tab==='base'">
|
||||
<el-table-column prop="memberCount" label="统计人数" width="100"></el-table-column>
|
||||
<el-table-column prop="standard" label="人均标准(元/年)" width="150"></el-table-column>
|
||||
<el-table-column prop="calculatedMoney" label="计算金额(元)" width="140"></el-table-column>
|
||||
</template>
|
||||
<el-table-column v-if="tab==='special'" prop="projectName" label="专项名称" min-width="150"></el-table-column>
|
||||
<template v-if="tab==='summary'">
|
||||
<el-table-column prop="baseMoney" label="基础经费已下拨(元)" min-width="160"></el-table-column>
|
||||
<el-table-column prop="specialMoney" label="专项经费已下拨(元)" min-width="160"></el-table-column>
|
||||
<el-table-column prop="totalMoney" label="新流程合计下拨(元)" min-width="160"></el-table-column>
|
||||
</template>
|
||||
<template v-else-if="tab==='history'">
|
||||
<el-table-column label="分配项目" min-width="140"><template slot-scope="{row}"><dict-tag :options="dict.type.OUTLAY_QUARTERLY" :value="row.quarterly"></dict-tag></template></el-table-column>
|
||||
<el-table-column prop="allocateHeadMoney" label="分配前额度(元)" min-width="140"></el-table-column>
|
||||
<el-table-column prop="allocateMoney" label="分配金额(元)" min-width="140"></el-table-column>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-table-column prop="allocateMoney" label="实际下拨金额(元)" width="160"></el-table-column>
|
||||
<el-table-column label="状态" width="100"><template slot-scope="{row}">{{Number(row.status)===1?'已下拨':'待下拨'}}</template></el-table-column>
|
||||
<el-table-column prop="remark" label="说明" min-width="150" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="下拨时间" width="165"><template slot-scope="{row}">{{time(row.allocatedAt)}}</template></el-table-column>
|
||||
<el-table-column prop="operatorName" label="操作人" width="100"></el-table-column>
|
||||
<el-table-column label="最后操作时间" width="165"><template slot-scope="{row}">{{time(row.updatedAt)}}</template></el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="220"><template slot-scope="{row}">
|
||||
<el-button type="text" :disabled="busy" @click="edit(row)">{{Number(row.status)===1?'调整':'编辑'}}</el-button>
|
||||
<el-button v-if="Number(row.status)===0" type="text" :disabled="busy" @click="confirm(row)">确认下拨</el-button>
|
||||
<el-button type="text" @click="showLogs(row)">操作记录</el-button>
|
||||
</template></el-table-column>
|
||||
</template>
|
||||
</el-table>
|
||||
<div class="el-pagination-container" v-if="tab!=='summary'">
|
||||
<el-pagination :current-page.sync="query.pageNumber" :page-size.sync="query.pageSize" :total="total" layout="total, sizes, prev, pager, next" @current-change="load" @size-change="doSearch"></el-pagination>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<el-dialog title="生成年度基础经费" :visible.sync="generateVisible" width="550px" :close-on-click-modal="false">
|
||||
<el-alert title="按所选年度、分工会生成;已有记录不覆盖。人数沿用会员统计口径并保存快照,确认下拨前请核对。" type="info" :closable="false"></el-alert>
|
||||
<el-form label-width="130px" class="mt10">
|
||||
<el-form-item label="年度">{{query.year}}</el-form-item>
|
||||
<el-form-item label="分工会">{{selectedUnionName}}</el-form-item>
|
||||
<el-form-item label="人均标准(元/年)"><el-input-number v-model="standard" :min="0" :max="99999999.99" :precision="2"></el-input-number></el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer"><el-button @click="generateVisible=false" :disabled="busy">取消</el-button><el-button type="primary" :loading="busy" @click="generate">生成</el-button></span>
|
||||
</el-dialog>
|
||||
<el-dialog :title="form.status===1?'调整已下拨经费':'编辑经费'" :visible.sync="editVisible" width="860px" class="fund-edit-dialog" top="6vh" :close-on-click-modal="false" :show-close="!busy">
|
||||
<el-alert v-if="form.status===1" title="当前经费已下拨,保存后将按新旧金额差额调整年度预算,请填写调整原因。" type="warning" show-icon :closable="false"></el-alert>
|
||||
<el-form class="fund-edit-form" label-position="top">
|
||||
<div class="fund-edit-section">基础信息 · {{tab==='base'?'年度基础经费':'专项经费'}}</div>
|
||||
<el-form-item label="年度"><el-date-picker v-model="form.year" type="year" value-format="yyyy" :clearable="false" :disabled="!isNew"></el-date-picker></el-form-item>
|
||||
<el-form-item label="分工会"><el-select v-model="form.unionId" filterable :disabled="!isNew"><el-option v-for="u in unionList" :key="u.id" :value="u.id" :label="u.name"></el-option></el-select></el-form-item>
|
||||
<div class="fund-edit-section">金额核算</div>
|
||||
<template v-if="tab==='base'">
|
||||
<el-form-item label="统计人数"><el-input-number v-model="form.memberCount" :min="0" :max="99999999" :precision="0" :disabled="form.status===1"></el-input-number></el-form-item>
|
||||
<el-form-item label="人均标准(元/年)"><el-input-number v-model="form.standard" :min="0" :max="99999999.99" :precision="2" :disabled="form.status===1"></el-input-number></el-form-item>
|
||||
<el-form-item label="计算金额(元)"><span class="fund-edit-calculated">{{calculated}} <small>元</small></span><div class="fund-edit-hint">统计人数 × 人均年度标准</div></el-form-item>
|
||||
</template>
|
||||
<el-form-item v-else label="专项名称" class="fund-edit-wide" required><el-input v-model="form.projectName" maxlength="100" placeholder="如:一会一品、评优奖励"></el-input></el-form-item>
|
||||
<el-form-item label="实际下拨金额(元)" :class="{'fund-edit-wide': tab!=='base'}" required><div class="fund-edit-amount"><el-input-number v-model="form.allocateMoney" :min="0" :max="99999999.99" :precision="2" controls-position="right"></el-input-number><el-button v-if="tab==='base' && form.status===0" type="text" @click="form.allocateMoney=Number(calculated)">使用计算金额</el-button></div></el-form-item>
|
||||
<div v-if="form.status===1 || amountDifference!==0" class="fund-edit-difference">
|
||||
<template v-if="form.status===1">原下拨金额:{{Number(originalAllocateMoney).toFixed(2)}} 元;本次调整:{{amountDifference>0?'+':''}}{{amountDifference.toFixed(2)}} 元。</template>
|
||||
<template v-else>实际下拨金额较计算金额{{amountDifference>0?'增加':'减少'}} {{Math.abs(amountDifference).toFixed(2)}} 元,请填写原因。</template>
|
||||
</div>
|
||||
<el-form-item :label="form.status===1?'调整原因':'说明'" class="fund-edit-wide" :required="remarkRequired"><el-input v-model="form.remark" type="textarea" :rows="3" maxlength="1000" show-word-limit :placeholder="remarkRequired?'请填写本次金额调整的具体原因':'可填写经费用途或其他说明'"></el-input></el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer"><el-button @click="editVisible=false" :disabled="busy">取消</el-button><el-button type="primary" :loading="busy" @click="save">{{form.status===1?'保存调整':'保存'}}</el-button></span>
|
||||
</el-dialog>
|
||||
<el-dialog title="经费操作记录" :visible.sync="logsVisible" width="900px">
|
||||
<el-table :data="logs" border v-loading="logsLoading">
|
||||
<el-table-column prop="action" label="操作" width="100"></el-table-column>
|
||||
<el-table-column prop="operatorName" label="操作人" width="100"></el-table-column>
|
||||
<el-table-column label="时间" width="165"><template slot-scope="{row}">{{time(row.createdAt)}}</template></el-table-column>
|
||||
<el-table-column label="操作前" min-width="180"><template slot-scope="{row}">{{snapshot(row.beforeData)}}</template></el-table-column>
|
||||
<el-table-column label="操作后" min-width="180"><template slot-scope="{row}">{{snapshot(row.afterData)}}</template></el-table-column>
|
||||
<el-table-column prop="remark" label="说明" min-width="140"></el-table-column>
|
||||
</el-table>
|
||||
<el-pagination :current-page.sync="logPage" :page-size="10" :total="logTotal" layout="total,prev,pager,next" @current-change="loadLogs"></el-pagination>
|
||||
</el-dialog>
|
||||
</template></guava>
|
||||
</div>
|
||||
<script nonce="${cspNonce!}">
|
||||
<!--#include("../fixedListLayout.js"){}#-->
|
||||
new Vue({
|
||||
el: '#app', store,
|
||||
mixins: [OUTLAY_UNION_FIXED_LIST_MIXIN],
|
||||
dicts: ['OUTLAY_QUARTERLY'],
|
||||
data() { return {
|
||||
tab: 'base', query: {year: this.$moment().format('YYYY'), unionId: '', projectName: '', pageNumber: 1, pageSize: 20},
|
||||
unionList: [], rows: [], total: 0, loading: false, busy: false, requestSequence: 0,
|
||||
standard: 800, generateVisible: false, editVisible: false, isNew: false, form: {}, originalAllocateMoney: 0,
|
||||
logsVisible: false, logsLoading: false, logs: [], logPage: 1, logTotal: 0, logRecord: null
|
||||
}},
|
||||
computed: {
|
||||
calculated() { return ((this.form.memberCount || 0) * (this.form.standard || 0)).toFixed(2) },
|
||||
amountDifference() {
|
||||
const amount=Number(this.form.allocateMoney)
|
||||
if (!Number.isFinite(amount)) return 0
|
||||
const reference=this.form.status===1?Number(this.originalAllocateMoney):(this.tab==='base'?Number(this.calculated):amount)
|
||||
return Number((amount-reference).toFixed(2))
|
||||
},
|
||||
remarkRequired() { return this.form.status===1 || (this.tab==='base' && this.amountDifference!==0) },
|
||||
selectedUnionName() { const u=this.unionList.find(u=>u.id===this.query.unionId); return u?u.name:'全部分工会' }
|
||||
},
|
||||
methods: {
|
||||
time(value) { return value?this.$moment(Number(value)).format('YYYY-MM-DD HH:mm:ss'):'—' },
|
||||
snapshot(value) {
|
||||
if (!value) return '—'
|
||||
try { const v=JSON.parse(value); return v.year+'年 / '+(v.memberCount==null?(v.projectName || ''):v.memberCount+'人 × '+v.standard+'元,计算 '+v.calculatedMoney+'元')+' / 实际 '+v.allocateMoney+'元 / '+(Number(v.status)===1?'已下拨':'待下拨') }
|
||||
catch(e) { return value }
|
||||
},
|
||||
doExport() {
|
||||
if (!this.query.year) { this.$message.warning('请选择年度'); return }
|
||||
const params='type='+encodeURIComponent(this.tab)+'&year='+encodeURIComponent(this.query.year)+'&unionId='+encodeURIComponent(this.query.unionId || '')
|
||||
window.open('/platform/outlay/outlayManage/unionAllocate/annual/doExport?'+params)
|
||||
},
|
||||
async request(action, data) {
|
||||
const resp=await this.$axios.post('/platform/outlay/outlayManage/unionAllocate/annual/'+action, data)
|
||||
if (resp.code!==0) { this.$message.error(resp.msg || '操作失败'); throw new Error(resp.msg) }
|
||||
return resp
|
||||
},
|
||||
changeTab() {
|
||||
this.rows=[]
|
||||
this.total=0
|
||||
this.doSearch()
|
||||
this.updateOutlayUnionTableHeight()
|
||||
},
|
||||
doSearch() { this.query.pageNumber=1; this.load() },
|
||||
async load() {
|
||||
const sequence=++this.requestSequence
|
||||
this.loading=true
|
||||
try {
|
||||
let resp
|
||||
if (this.tab==='history') resp=await this.$axios.post('/platform/outlay/outlayManage/unionAllocate/pageData', this.query)
|
||||
else resp=await this.request(this.tab==='summary'?'summary':'pageData', Object.assign({},this.query,{type:this.tab}))
|
||||
if (sequence!==this.requestSequence) return
|
||||
if (resp.code===0) { this.rows=this.tab==='summary'?(resp.data || []):(resp.data.list || []); this.total=this.tab==='summary'?0:resp.data.totalCount }
|
||||
} catch(e) { if(sequence===this.requestSequence) this.rows=[] }
|
||||
finally { if(sequence===this.requestSequence) { this.loading=false; this.updateOutlayUnionTableHeight() } }
|
||||
},
|
||||
async generate() {
|
||||
this.busy=true
|
||||
try { const resp=await this.request('generate',Object.assign({},this.query,{standard:this.standard})); this.$message.success(resp.msg); this.generateVisible=false; this.doSearch() }
|
||||
catch(e) {} finally { this.busy=false }
|
||||
},
|
||||
edit(row) {
|
||||
this.isNew=false
|
||||
this.originalAllocateMoney=Number(row.allocateMoney)
|
||||
this.form=Object.assign({},row,{year:String(row.year), status:Number(row.status), memberCount:Number(row.memberCount), standard:Number(row.standard), allocateMoney:Number(row.allocateMoney)})
|
||||
this.editVisible=true
|
||||
},
|
||||
async addSpecial() {
|
||||
this.busy=true
|
||||
try { const resp=await this.request('newId',{}); this.isNew=true; this.form={id:resp.data,year:this.query.year,unionId:this.query.unionId,projectName:'',allocateMoney:0,remark:'',status:0}; this.editVisible=true }
|
||||
catch(e) {} finally { this.busy=false }
|
||||
},
|
||||
async save() {
|
||||
if (!this.form.year || !this.form.unionId || this.form.allocateMoney==null) { this.$message.warning('请填写年度、分工会和金额'); return }
|
||||
if (this.busy) return
|
||||
if (this.remarkRequired && !(this.form.remark || '').trim()) { this.$message.warning('请填写金额调整原因'); return }
|
||||
this.busy=true
|
||||
try { await this.request('save',Object.assign({},this.form,{type:this.tab})); this.$message.success('保存成功'); this.editVisible=false; this.doSearch() }
|
||||
catch(e) {} finally { this.busy=false }
|
||||
},
|
||||
async batchConfirm() {
|
||||
if (this.busy) return
|
||||
const params={type:this.tab,year:this.query.year,unionId:this.query.unionId || '',projectName:this.tab==='special'?(this.query.projectName || '').trim():''}
|
||||
const typeName=this.tab==='base'?'年度基础经费':'专项经费'
|
||||
const scope=this.selectedUnionName+(params.projectName?' / 专项名称包含:'+params.projectName:'')
|
||||
if (!params.year) { this.$message.warning('请选择年度'); return }
|
||||
this.busy=true
|
||||
try {
|
||||
await this.$confirm('确认一键下拨【'+params.year+'年 / '+scope+'】的全部待下拨'+typeName+'?范围包含所有分页,已下拨记录自动跳过,请先核对金额及历史下拨记录。','一键下拨',{type:'warning'})
|
||||
const resp=await this.request('batchConfirm',params)
|
||||
this.$message.success(resp.msg)
|
||||
this.load()
|
||||
} catch(e) {} finally { this.busy=false }
|
||||
},
|
||||
async confirm(row) {
|
||||
try { await this.$confirm('确认向【'+row.unionName+'】下拨 '+row.allocateMoney+' 元?此操作将计入年度预算,请核对历史下拨记录。','确认下拨',{type:'warning'}) }
|
||||
catch(e) { return }
|
||||
this.busy=true
|
||||
try { await this.request('confirm',{type:this.tab,id:row.id,version:row.version}); this.$message.success('下拨成功'); this.load() }
|
||||
catch(e) {} finally { this.busy=false }
|
||||
},
|
||||
showLogs(row) { this.logRecord={id:row.id,type:this.tab}; this.logPage=1; this.logsVisible=true; this.loadLogs() },
|
||||
async loadLogs() {
|
||||
this.logsLoading=true
|
||||
try { const resp=await this.request('logs',Object.assign({},this.logRecord,{pageNumber:this.logPage,pageSize:10})); this.logs=resp.data.list || []; this.logTotal=resp.data.totalCount }
|
||||
catch(e) {} finally { this.logsLoading=false }
|
||||
}
|
||||
},
|
||||
created() { this.$businessTool.listUnion().then(data=>{this.unionList=data}); this.load() }
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+16
-52
@@ -61,52 +61,19 @@ layout("/layouts/platform.html"){
|
||||
{{formatMoney(row.beginMoney)}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" header-align="center" :label="pageForm.year + '年会费收入'">
|
||||
<el-table-column
|
||||
align="center"
|
||||
header-align="center"
|
||||
label="1-4月会费"
|
||||
prop="feeJanApr"
|
||||
min-width="120">
|
||||
<template v-slot="{row}">
|
||||
{{formatMoney(row.feeJanApr)}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
header-align="center"
|
||||
label="5-8月会费"
|
||||
prop="feeMayAug"
|
||||
min-width="120">
|
||||
<template v-slot="{row}">
|
||||
{{formatMoney(row.feeMayAug)}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
header-align="center"
|
||||
label="9-12月会费"
|
||||
prop="feeSepDec"
|
||||
min-width="120">
|
||||
<template v-slot="{row}">
|
||||
{{formatMoney(row.feeSepDec)}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="年度基础经费" prop="baseMoney" min-width="120">
|
||||
<template v-slot="{row}">{{formatMoney(row.baseMoney)}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="专项经费" prop="specialMoney" min-width="120">
|
||||
<template v-slot="{row}">{{formatMoney(row.specialMoney)}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="合计下拨" prop="incomeMoney" min-width="120">
|
||||
<template v-slot="{row}">{{formatMoney(row.incomeMoney)}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
header-align="center"
|
||||
label="评优奖励"
|
||||
prop="awardMoney"
|
||||
min-width="120">
|
||||
<template v-slot="{row}">
|
||||
{{formatMoney(row.awardMoney)}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
header-align="center"
|
||||
label="1-12月支出"
|
||||
label="年度支出"
|
||||
prop="usedMoney"
|
||||
min-width="120">
|
||||
<template v-slot="{row}">
|
||||
@@ -141,19 +108,16 @@ layout("/layouts/platform.html"){
|
||||
<el-table-column align="center" header-align="center" label="年初数" prop="beginMoney" min-width="120">
|
||||
<template v-slot="{row}">{{summaryValue('beginMoney', row)}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="1-4月会费" prop="feeJanApr" min-width="120">
|
||||
<template v-slot="{row}">{{summaryValue('feeJanApr', row)}}</template>
|
||||
<el-table-column align="center" header-align="center" label="年度基础经费" prop="baseMoney" min-width="120">
|
||||
<template v-slot="{row}">{{summaryValue('baseMoney', row)}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="5-8月会费" prop="feeMayAug" min-width="120">
|
||||
<template v-slot="{row}">{{summaryValue('feeMayAug', row)}}</template>
|
||||
<el-table-column align="center" header-align="center" label="专项经费" prop="specialMoney" min-width="120">
|
||||
<template v-slot="{row}">{{summaryValue('specialMoney', row)}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="9-12月会费" prop="feeSepDec" min-width="120">
|
||||
<template v-slot="{row}">{{summaryValue('feeSepDec', row)}}</template>
|
||||
<el-table-column align="center" header-align="center" label="合计下拨" prop="incomeMoney" min-width="120">
|
||||
<template v-slot="{row}">{{summaryValue('incomeMoney', row)}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="评优奖励" prop="awardMoney" min-width="120">
|
||||
<template v-slot="{row}">{{summaryValue('awardMoney', row)}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="本年累计支出" prop="usedMoney" min-width="120">
|
||||
<el-table-column align="center" header-align="center" label="年度支出" prop="usedMoney" min-width="120">
|
||||
<template v-slot="{row}">{{summaryValue('usedMoney', row)}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="经费余额" prop="remainMoney" min-width="120">
|
||||
|
||||
Reference in New Issue
Block a user