diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/service/OutlayManageUnionStatisticsService.java b/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/service/OutlayManageUnionStatisticsService.java index eb1bb37..7e6aa7a 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/service/OutlayManageUnionStatisticsService.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/service/OutlayManageUnionStatisticsService.java @@ -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); } diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/service/impl/OutlayAnnualAllocateService.java b/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/service/impl/OutlayAnnualAllocateService.java new file mode 100644 index 0000000..b0302ac --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/service/impl/OutlayAnnualAllocateService.java @@ -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 { + 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 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 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 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 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 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 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())); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/service/impl/OutlayManageUnionStatisticsServiceImpl.java b/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/service/impl/OutlayManageUnionStatisticsServiceImpl.java index 29a70f5..c11ace8 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/service/impl/OutlayManageUnionStatisticsServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/service/impl/OutlayManageUnionStatisticsServiceImpl.java @@ -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 + """; + } + /** * 构造两个列表共用的预算主表过滤条件,保证汇总和分页列表权限口径一致。 */ diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/union/controller/OutlayAnnualAllocateController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/union/controller/OutlayAnnualAllocateController.java new file mode 100644 index 0000000..eec68e7 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/union/controller/OutlayAnnualAllocateController.java @@ -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 rows; + List 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 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))); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/union/controller/OutlayManageUnionAllocateController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/union/controller/OutlayManageUnionAllocateController.java index 9f0c9ff..04d9cba 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/union/controller/OutlayManageUnionAllocateController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/union/controller/OutlayManageUnionAllocateController.java @@ -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() { } diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/union/controller/OutlayManageUnionIncomeExpenseController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/union/controller/OutlayManageUnionIncomeExpenseController.java index e67b7a4..960050c 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/union/controller/OutlayManageUnionIncomeExpenseController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/union/controller/OutlayManageUnionIncomeExpenseController.java @@ -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 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) { diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/union/vo/OutlayUnionIncomeExpenseVO.java b/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/union/vo/OutlayUnionIncomeExpenseVO.java index f4abf90..d674987 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/union/vo/OutlayUnionIncomeExpenseVO.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/outlay/outlayManage/union/vo/OutlayUnionIncomeExpenseVO.java @@ -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; diff --git a/src/main/resources/db/script/20261103/annual_union_allocate.sql b/src/main/resources/db/script/20261103/annual_union_allocate.sql new file mode 100644 index 0000000..a089f32 --- /dev/null +++ b/src/main/resources/db/script/20261103/annual_union_allocate.sql @@ -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'; diff --git a/src/main/resources/views/platform/zhgh/dayofficework/outlay/outlayManage/union/allocate/annual.html b/src/main/resources/views/platform/zhgh/dayofficework/outlay/outlayManage/union/allocate/annual.html new file mode 100644 index 0000000..49753a0 --- /dev/null +++ b/src/main/resources/views/platform/zhgh/dayofficework/outlay/outlayManage/union/allocate/annual.html @@ -0,0 +1,248 @@ + + +
+ +
+ + diff --git a/src/main/resources/views/platform/zhgh/dayofficework/outlay/outlayManage/union/incomeExpense/index.html b/src/main/resources/views/platform/zhgh/dayofficework/outlay/outlayManage/union/incomeExpense/index.html index dc2efbc..0fb80e8 100644 --- a/src/main/resources/views/platform/zhgh/dayofficework/outlay/outlayManage/union/incomeExpense/index.html +++ b/src/main/resources/views/platform/zhgh/dayofficework/outlay/outlayManage/union/incomeExpense/index.html @@ -61,52 +61,19 @@ layout("/layouts/platform.html"){ {{formatMoney(row.beginMoney)}} - - - - - - - - - - + + + + + + + + - - -