feat: 工会报销优化
This commit is contained in:
+4
-1
@@ -49,4 +49,7 @@ Thumbs.db
|
||||
.trae/pr-creator/SKILL.md
|
||||
.trae/update-docs/SKILL.md
|
||||
.trae/webapp-testing/SKILL.md
|
||||
outputs
|
||||
outputs
|
||||
|
||||
semgrep.json
|
||||
trivy.json
|
||||
Binary file not shown.
@@ -147,12 +147,9 @@ public class SysLoginController {
|
||||
// 解密密码
|
||||
LoginRsaKey rsaKey = LOGIN_RSA_KEY_RING.getPrivateKey(keyId);
|
||||
if (rsaKey == null) {
|
||||
throw new BaseException("Login key expired, please refresh the login page");
|
||||
throw new BaseException("登录凭证已过期,请刷新登录页面。");
|
||||
}
|
||||
String decryptPwd = RsaUtils.decrypt(password, rsaKey.privateKey);
|
||||
if (decryptPwd == null) {
|
||||
throw new BaseException("用户登录失败");
|
||||
}
|
||||
decryptPwd = Base64Encoder.encode(decryptPwd);
|
||||
|
||||
// 用户名密码校验
|
||||
|
||||
@@ -84,6 +84,7 @@ public class SysUnitController {
|
||||
// cnd.and("parentId", "is not", null).andEX("unitLevel", "=", unitLevel).asc("unitLevel").asc("unitcode");
|
||||
cnd.and("parentId", "is not", null);
|
||||
cnd.andEX("parentId", "=", unitId);
|
||||
cnd.andEX("id", "!=", unitId);
|
||||
// cnd.and("unitTypeCode", "=", "1");
|
||||
cnd.asc("unitcode");
|
||||
cnd.and(Cnd.likeEX("name", unitName));
|
||||
@@ -313,15 +314,17 @@ public class SysUnitController {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.asc("unitcode");
|
||||
List<Sys_unit> list = sysUnitService.query(cnd);
|
||||
boolean defaultRoot = "0".equals(rootId);
|
||||
boolean hasDefaultRoot = defaultRoot && list.stream().anyMatch(unit -> rootId.equals(unit.getId()));
|
||||
|
||||
List<TreeNode<String>> nodeList = CollUtil.newArrayList();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
Sys_unit unit = list.get(i);
|
||||
/*
|
||||
* 单位根节点存在 id 与 parentId 都为 0 的自引用数据。
|
||||
* 构建树时把当前查询根挂到虚拟根下,避免“中国地质大学”和 parentId=0 的学院被构造成同级。
|
||||
*/
|
||||
String parentId = rootId.equals(unit.getId()) ? virtualRootId : unit.getParentId();
|
||||
String parentId = unit.getParentId();
|
||||
if (rootId.equals(unit.getId()) || (defaultRoot && (StrUtil.isBlank(parentId)
|
||||
|| (!hasDefaultRoot && rootId.equals(parentId))))) {
|
||||
parentId = virtualRootId;
|
||||
}
|
||||
nodeList.add(new TreeNode<>(unit.getId(), parentId, unit.getName(), i)
|
||||
.setExtra(
|
||||
Map.of(
|
||||
|
||||
+110
-58
@@ -5,6 +5,7 @@ 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 com.budwk.app.zhgh.dayofficework.outlay.outlayManage.union.vo.OutlayUnionAllocateImportVo;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
@@ -14,6 +15,8 @@ import org.nutz.lang.util.NutMap;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Year;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.UUID;
|
||||
|
||||
@IocBean(args = {"refer:dao"})
|
||||
@@ -37,17 +40,54 @@ public class OutlayAnnualAllocateService extends BaseServiceImpl<OutlayManageUni
|
||||
throw new IllegalArgumentException("经费类型错误");
|
||||
}
|
||||
|
||||
public Sql pageSql(String type, Integer year, String unionId, String projectName) {
|
||||
public Sql pageSql(String type, Integer year, String unionId, String projectName, String amountMatch) {
|
||||
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)" : "") +
|
||||
("special".equals(type) ? " AND (@projectName='' OR LOCATE(@projectName,a.projectName)>0)" : " AND a.status<>2" + amountMatchCondition(amountMatch)) +
|
||||
" ORDER BY un.unionCode,a.createdAt DESC",
|
||||
"year", year, "unionId", StrUtil.blankToDefault(unionId, ""),
|
||||
"projectName", StrUtil.blankToDefault(StrUtil.trim(projectName), ""));
|
||||
}
|
||||
|
||||
private String amountMatchCondition(String amountMatch) {
|
||||
if ("equal".equals(amountMatch)) return " AND a.calculatedMoney=a.allocateMoney";
|
||||
if ("different".equals(amountMatch)) return " AND a.calculatedMoney<>a.allocateMoney";
|
||||
return "";
|
||||
}
|
||||
|
||||
public Result importBaseAmounts(Integer year, String unionId, List<OutlayUnionAllocateImportVo> items, String remark) {
|
||||
if (year == null || items == null || items.isEmpty() || StrUtil.isBlank(remark) || remark.length() > 1000) {
|
||||
return Result.error("请选择年度和导入文件,并填写1000字以内的调整说明");
|
||||
}
|
||||
Map<String, NutMap> records = new TreeMap<>();
|
||||
Map<String, BigDecimal> amounts = new TreeMap<>();
|
||||
for (OutlayUnionAllocateImportVo item : items) {
|
||||
if (item == null || StrUtil.isBlank(item.getUnionCode()) || !moneyValid(item.getAllocateMoney())) {
|
||||
return Result.error("工会编码和分配额度必填,金额须为有效的非负数且最多两位小数");
|
||||
}
|
||||
List<NutMap> matches = listMap(sql("""
|
||||
SELECT a.id,a.unionId,a.standard,a.version FROM outlay_annual_base a
|
||||
JOIN sys_union un ON un.id=a.unionId
|
||||
WHERE a.year=@year AND a.status IN (0,1) AND un.unionCode=@code AND (@unionId='' OR a.unionId=@unionId)
|
||||
""", "year", year, "code", item.getUnionCode().trim(), "unionId", StrUtil.blankToDefault(unionId, "")));
|
||||
if (matches.size() != 1) return Result.error("工会编码 " + item.getUnionCode() + " 未匹配到唯一有效基础经费记录");
|
||||
NutMap row = matches.get(0);
|
||||
String uid = row.getString("unionId");
|
||||
if (records.putIfAbsent(uid, row) != null) return Result.error("工会编码 " + item.getUnionCode() + " 重复");
|
||||
amounts.put(uid, item.getAllocateMoney());
|
||||
}
|
||||
for (Map.Entry<String, NutMap> entry : records.entrySet()) {
|
||||
String uid = entry.getKey();
|
||||
NutMap row = entry.getValue();
|
||||
Result result = save("base", row.getString("id"), year, uid, null, null,
|
||||
decimal(row.get("standard")), amounts.get(uid), remark.trim(), row.getInt("version"));
|
||||
if (result.getCode() != 0) throw new IllegalArgumentException(result.getMsg());
|
||||
}
|
||||
return Result.success("已导入" + records.size() + "条记录");
|
||||
}
|
||||
|
||||
public List<NutMap> summary(Integer year, String unionId) {
|
||||
return listMap(sql("""
|
||||
SELECT un.id, un.name unionName, COALESCE(b.money,0) baseMoney,
|
||||
@@ -61,6 +101,17 @@ public class OutlayAnnualAllocateService extends BaseServiceImpl<OutlayManageUni
|
||||
""", "year", year, "unionId", StrUtil.blankToDefault(unionId, "")));
|
||||
}
|
||||
|
||||
public List<NutMap> confirmedRecords(Integer year, String unionId) {
|
||||
return listMap(sql("""
|
||||
SELECT id,'base' allocateType,'年度基础经费' projectName,allocateMoney
|
||||
FROM outlay_annual_base WHERE year=@year AND unionId=@unionId AND status=1
|
||||
UNION ALL
|
||||
SELECT id,'special' allocateType,projectName,allocateMoney
|
||||
FROM outlay_annual_special WHERE year=@year AND unionId=@unionId AND status=1
|
||||
ORDER BY allocateType,projectName,id
|
||||
""", "year", year, "unionId", unionId));
|
||||
}
|
||||
|
||||
public List<NutMap> historyRecords(Integer year, String unionId) {
|
||||
return listMap(sql("""
|
||||
SELECT a.year,un.name unionName,a.quarterly,a.allocateHeadMoney,a.allocateMoney
|
||||
@@ -82,20 +133,33 @@ public class OutlayAnnualAllocateService extends BaseServiceImpl<OutlayManageUni
|
||||
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 old = first(sql("SELECT * FROM outlay_annual_base WHERE year=@year AND unionId=@unionId", "year", year, "unionId", uid));
|
||||
if (old != null && old.getInt("status") != 2) 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)), "生成年度人数快照");
|
||||
syncBudget(year, uid, calculated);
|
||||
String id = old == null ? UUID.randomUUID().toString().replace("-", "") : old.getString("id");
|
||||
if (old == null) {
|
||||
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,'',1,0,@now,@actor,@now,@actor)
|
||||
""", "id", id, "year", year, "unionId", uid, "count", members, "standard", standard,
|
||||
"money", calculated, "now", System.currentTimeMillis(), "actor", StpUtil.getLoginIdAsString()));
|
||||
} else {
|
||||
// 复用已重置记录,保留原操作日志及年度、分工会唯一约束。
|
||||
execute(sql("""
|
||||
UPDATE outlay_annual_base SET memberCount=@count,standard=@standard,calculatedMoney=@money,
|
||||
allocateMoney=@money,remark='',status=1,version=version+1,updatedAt=@now,updatedBy=@actor
|
||||
WHERE id=@id
|
||||
""", "count", members, "standard", standard, "money", calculated, "id", id,
|
||||
"now", System.currentTimeMillis(), "actor", StpUtil.getLoginIdAsString()));
|
||||
}
|
||||
audit("base", id, old == null ? "生成" : "重新生成", old,
|
||||
first(sql("SELECT * FROM outlay_annual_base WHERE id=@id", "id", id)), "生成年度人数快照");
|
||||
added++;
|
||||
}
|
||||
return Result.success("已生成" + added + "条记录,已有记录保持不变");
|
||||
@@ -130,7 +194,7 @@ public class OutlayAnnualAllocateService extends BaseServiceImpl<OutlayManageUni
|
||||
boolean confirmed = old != null && old.getInt("status") == 1;
|
||||
BigDecimal calculated = null;
|
||||
if ("base".equals(type)) {
|
||||
if (old == null) return Result.error("请先生成基础经费记录");
|
||||
if (old == null || old.getInt("status") == 2) return Result.error("请先生成基础经费记录");
|
||||
memberCount = old.getInt("memberCount");
|
||||
if (confirmed) {
|
||||
standard = new BigDecimal(old.get("standard").toString());
|
||||
@@ -142,85 +206,73 @@ public class OutlayAnnualAllocateService extends BaseServiceImpl<OutlayManageUni
|
||||
} 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())));
|
||||
if (confirmed && StrUtil.isBlank(remark)) return Result.error("调整已计入预算的经费必须填写原因");
|
||||
BigDecimal budgetDelta = allocateMoney.subtract(confirmed ? decimal(old.get("allocateMoney")) : BigDecimal.ZERO);
|
||||
if (!confirmed || budgetDelta.signum() != 0) syncBudget(year, unionId, budgetDelta);
|
||||
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)
|
||||
VALUES (@id,@year,@unionId,@name,@money,@remark,1,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" +
|
||||
execute(sql("UPDATE " + table + " SET allocateMoney=@money,remark=@remark,status=1,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);
|
||||
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), "")));
|
||||
public Result resetBaseRecords(Integer year, String unionId, String amountMatch) {
|
||||
if (year == null || year != Year.now().getValue()) return Result.error("只能重置当前年度的分配记录");
|
||||
List<NutMap> candidates = listMap(sql("""
|
||||
SELECT id,unionId,version FROM outlay_annual_base a
|
||||
WHERE year=@year AND status IN (0,1) AND (@unionId='' OR unionId=@unionId)
|
||||
""" + amountMatchCondition(amountMatch) + " ORDER BY unionId,id", "year", year, "unionId", StrUtil.blankToDefault(unionId, "")));
|
||||
int count = 0;
|
||||
for (NutMap candidate : pending) {
|
||||
for (NutMap candidate : candidates) {
|
||||
String id = candidate.getString("id");
|
||||
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);
|
||||
NutMap row = first(sql("SELECT * FROM outlay_annual_base WHERE id=@id FOR UPDATE", "id", id));
|
||||
if (row == null || row.getInt("status") == 2) continue;
|
||||
if (row.getInt("version") != candidate.getInt("version")) {
|
||||
throw new IllegalArgumentException("分配记录发生变更,本次重置已取消,请刷新后重试");
|
||||
}
|
||||
BigDecimal amount = row.getInt("status") == 1 ? decimal(row.get("allocateMoney")) : BigDecimal.ZERO;
|
||||
if (amount.signum() != 0) syncBudget(year, row.getString("unionId"), amount.negate());
|
||||
execute(sql("""
|
||||
UPDATE outlay_annual_base
|
||||
SET status=2,updatedAt=@now,updatedBy=@actor,version=version+1
|
||||
WHERE id=@id
|
||||
""", "id", id, "now", System.currentTimeMillis(), "actor", StpUtil.getLoginIdAsString()));
|
||||
NutMap after = first(sql("SELECT * FROM outlay_annual_base WHERE id=@id", "id", id));
|
||||
audit("base", id, "重置分配记录", row, after, "扣回年度预算 " + amount.toPlainString() + " 元");
|
||||
count++;
|
||||
}
|
||||
return Result.success(count == 0 ? "当前筛选范围没有待下拨记录" : "成功下拨" + 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) {
|
||||
private void syncBudget(int year, String unionId, 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")));
|
||||
"year", year, "unionId", 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")));
|
||||
"year", year - 1, "unionId", 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()));
|
||||
""", "id", UUID.randomUUID().toString().replace("-", ""), "year", year,
|
||||
"unionId", 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);
|
||||
|
||||
+8
-18
@@ -70,20 +70,18 @@ public class OutlayManageUnionStatisticsServiceImpl extends BaseServiceImpl impl
|
||||
}
|
||||
|
||||
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.totalQuota,0) - (COALESCE(b.money,0) + COALESCE(sp.money,0)) AS beginMoney,
|
||||
COALESCE(b.money,0) AS baseMoney,
|
||||
COALESCE(sp.money,0) AS specialMoney,
|
||||
COALESCE(b.money,0) + COALESCE(sp.money,0) AS incomeMoney,
|
||||
0 AS feeJanApr,
|
||||
0 AS feeMayAug,
|
||||
0 AS feeSepDec,
|
||||
0 AS awardMoney,
|
||||
COALESCE(mu.usedQuota,0) AS usedMoney,
|
||||
COALESCE(mu.totalQuota,0)-COALESCE(mu.usedQuota,0) AS remainMoney
|
||||
FROM outlay_manage_union mu
|
||||
@@ -96,14 +94,6 @@ public class OutlayManageUnionStatisticsServiceImpl extends BaseServiceImpl impl
|
||||
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
|
||||
""";
|
||||
}
|
||||
|
||||
+11
-7
@@ -10,6 +10,8 @@ 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 org.nutz.json.Json;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.union.vo.OutlayUnionAllocateImportVo;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -38,9 +40,9 @@ public class OutlayAnnualAllocateController {
|
||||
|
||||
@At
|
||||
@SaCheckPermission("outlay.outlayManage.union.allocate")
|
||||
public Result pageData(PageForm pageForm, String type, Integer year, String unionId, String projectName) {
|
||||
public Result pageData(PageForm pageForm, String type, Integer year, String unionId, String projectName, String amountMatch) {
|
||||
return Result.success(outlayAnnualAllocateService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(),
|
||||
outlayAnnualAllocateService.pageSql(type, year, unionId, projectName)));
|
||||
outlayAnnualAllocateService.pageSql(type, year, unionId, projectName, amountMatch)));
|
||||
}
|
||||
|
||||
@At
|
||||
@@ -117,16 +119,18 @@ public class OutlayAnnualAllocateController {
|
||||
@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);
|
||||
public Result resetBaseRecords(Integer year, String unionId, String amountMatch) {
|
||||
return outlayAnnualAllocateService.resetBaseRecords(year, unionId, amountMatch);
|
||||
}
|
||||
|
||||
@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);
|
||||
@SaCheckPermission("outlay.outlayManage.union.allocate")
|
||||
public Result importBaseAmounts(Integer year, String unionId, String data, String remark) {
|
||||
if (data == null || data.isBlank()) return Result.error("请先上传导入文件");
|
||||
return outlayAnnualAllocateService.importBaseAmounts(year, unionId,
|
||||
Json.fromJsonAsList(OutlayUnionAllocateImportVo.class, data), remark);
|
||||
}
|
||||
|
||||
@At
|
||||
|
||||
+10
@@ -13,6 +13,7 @@ import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.model.OutlayUseDetail;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.service.OutlayUseDetailService;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.service.OutlayManageUnionStatisticsService;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.service.impl.OutlayAnnualAllocateService;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.union.model.OutLayAllocateUnion;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
@@ -49,6 +50,8 @@ public class OutlayManageUnionUseDetailController {
|
||||
private OutlayUseDetailService outlayUseDetailService;
|
||||
@Inject
|
||||
private OutlayManageUnionStatisticsService outlayManageUnionStatisticsService;
|
||||
@Inject
|
||||
private OutlayAnnualAllocateService outlayAnnualAllocateService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/outlay/outlayManage/union/useDetail/index.html")
|
||||
@@ -128,6 +131,13 @@ public class OutlayManageUnionUseDetailController {
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("查询分工会年度已下拨经费明细")
|
||||
@SaCheckPermission("outlay.outlayManage.union.useDetail")
|
||||
public Result queryAnnualAllocateList(Integer year, String unionId) {
|
||||
return Result.success(outlayAnnualAllocateService.confirmedRecords(year, unionId));
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("查询分工会今年每个分配项目的分配情况")
|
||||
@SaCheckPermission("outlay.outlayManage.union.useDetail")
|
||||
|
||||
+29
-22
@@ -122,9 +122,9 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存、提交及重新提交共用支付信息校验,并在同一事务内保存申请和发票明细。
|
||||
* 在同一事务内保存申请和发票明细,正式提交及重新提交时校验支付信息。
|
||||
* @param unionReimburse 申请表单,paymentDetails 为支付方式、金额及对私收款资料组成的明细列表
|
||||
* @param stateId 1 表示保存草稿,2 表示提交;均要求支付信息完整,发票选填,上传后须通过服务端验真
|
||||
* @param stateId 1 表示保存草稿,2 表示提交;提交时至少需要一张发票
|
||||
* @return Result 成功或失败结果,msg 包含失败明细的行号及原因
|
||||
*/
|
||||
@Override
|
||||
@@ -133,23 +133,27 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
|
||||
if (unionReimburse == null) {
|
||||
return Result.error("报销数据不能为空");
|
||||
}
|
||||
// 发票选填;保存、提交及重提均对已上传的发票检查完整性。
|
||||
if (stateId == 2 && Lang.isEmpty(unionReimburse.getInvoiceDetails())) {
|
||||
return Result.error("提交前请至少上传一张发票");
|
||||
}
|
||||
// 保存、提交及重提均对已上传的发票检查完整性。
|
||||
Result invoiceResult = validateInvoiceDetails(unionReimburse);
|
||||
if (invoiceResult != null) {
|
||||
return invoiceResult;
|
||||
}
|
||||
Result paymentResult = validatePaymentDetails(unionReimburse);
|
||||
if (paymentResult != null) {
|
||||
return paymentResult;
|
||||
}
|
||||
// 保存、提交及重提均在写入前检查支付总额,不能只依赖页面的金额限制。
|
||||
Result amountResult = validatePaymentAmountLimit(unionReimburse);
|
||||
if (amountResult != null) {
|
||||
return amountResult;
|
||||
}
|
||||
Result activityAmountResult = validateActivityApplyMoney(unionReimburse);
|
||||
if (activityAmountResult != null) {
|
||||
return activityAmountResult;
|
||||
if (stateId == 2) {
|
||||
Result paymentResult = validatePaymentDetails(unionReimburse);
|
||||
if (paymentResult != null) {
|
||||
return paymentResult;
|
||||
}
|
||||
Result amountResult = validatePaymentAmountLimit(unionReimburse);
|
||||
if (amountResult != null) {
|
||||
return amountResult;
|
||||
}
|
||||
Result activityAmountResult = validateActivityApplyMoney(unionReimburse);
|
||||
if (activityAmountResult != null) {
|
||||
return activityAmountResult;
|
||||
}
|
||||
}
|
||||
UnionReimburse oldRecord = null;
|
||||
if (StrUtil.isNotBlank(unionReimburse.getId())) {
|
||||
@@ -760,15 +764,18 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
|
||||
invoiceFileCount++;
|
||||
}
|
||||
}
|
||||
// 无发票申请从服务端已校验的支付明细计算金额,不能信任前端传入的报销总额。
|
||||
if (Lang.isEmpty(unionReimburse.getInvoiceDetails())) {
|
||||
// 无发票时由支付明细汇总,草稿中未填完的金额暂不计入。
|
||||
if (Lang.isEmpty(unionReimburse.getInvoiceDetails()) && Lang.isNotEmpty(unionReimburse.getPaymentDetails())) {
|
||||
BigDecimal paymentTotal = BigDecimal.ZERO;
|
||||
for (JSONObject detail : unionReimburse.getPaymentDetails()) {
|
||||
paymentTotal = paymentTotal.add(new BigDecimal(detail.get("money").toString()));
|
||||
String money = detail == null ? "" : detail.getStr("money", "");
|
||||
if (money.matches("[0-9]+(\\.[0-9]{1,2})?")) {
|
||||
paymentTotal = paymentTotal.add(new BigDecimal(money));
|
||||
}
|
||||
}
|
||||
totalMoney = paymentTotal.setScale(2, RoundingMode.HALF_UP).doubleValue();
|
||||
}
|
||||
// 三类活动使用已校验的手填申请金额,不能被发票或支付合计覆盖。
|
||||
// 三类活动保留手填申请金额。
|
||||
if (!isManualReimburseAmount(unionReimburse)) {
|
||||
unionReimburse.setMoney(BigDecimal.valueOf(totalMoney).setScale(2, RoundingMode.HALF_UP).doubleValue());
|
||||
}
|
||||
@@ -783,8 +790,8 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验支付明细,防止绕过 PC/H5 校验直接保存。对公只需方式和金额,对私必须补齐人员及银行资料。
|
||||
* @param form 本次保存的申请,paymentDetails 必须至少包含一条完整支付明细
|
||||
* 校验提交的支付明细。对公只需方式和金额,对私必须补齐人员及银行资料。
|
||||
* @param form 本次提交的申请,paymentDetails 必须至少包含一条完整支付明细
|
||||
* @return 校验通过返回 null,失败返回带行号和原因的 Result
|
||||
*/
|
||||
private Result validatePaymentDetails(UnionReimburse form) {
|
||||
@@ -916,7 +923,7 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
|
||||
}
|
||||
|
||||
/**
|
||||
* 发票列表允许为空;已有明细必须且只能包含一张可定位的发票文件。
|
||||
* 校验已填写的发票明细,每条明细必须且只能包含一张可定位的发票文件。
|
||||
*/
|
||||
private Result validateInvoiceDetails(UnionReimburse unionReimburse) {
|
||||
if (Lang.isEmpty(unionReimburse.getInvoiceDetails())) {
|
||||
|
||||
@@ -80,3 +80,10 @@ custom:
|
||||
encrypted-secret-key: ''
|
||||
connect-timeout: 5000
|
||||
read-timeout: 10000
|
||||
|
||||
invoice:
|
||||
tencent:
|
||||
# 发票 OCR 使用腾讯云 SecretId/SecretKey;为空时会回退读取 secret-file 指向的 csv。
|
||||
secret-id: AKID1473y253saFsMelgS4TjnfLofMrAHDyX
|
||||
secret-key: TlrlTzIRNjtIj296riPuISbmqplnhQDy
|
||||
secret-file: C:/Users/Administrator/Desktop
|
||||
@@ -10,9 +10,8 @@ CREATE TABLE IF NOT EXISTS `outlay_annual_base` (
|
||||
`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已下拨',
|
||||
`status` tinyint NOT NULL DEFAULT 0 COMMENT '0待下拨 1已下拨 2已重置',
|
||||
`version` int NOT NULL DEFAULT 0,
|
||||
`allocatedAt` bigint DEFAULT NULL,
|
||||
`createdAt` bigint NOT NULL,
|
||||
`createdBy` varchar(32) NOT NULL,
|
||||
`updatedAt` bigint NOT NULL,
|
||||
@@ -30,7 +29,6 @@ CREATE TABLE IF NOT EXISTS `outlay_annual_special` (
|
||||
`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,
|
||||
@@ -53,6 +51,23 @@ CREATE TABLE IF NOT EXISTS `outlay_annual_allocate_log` (
|
||||
KEY `idx_annual_log_record_time` (`allocateType`,`allocateId`,`createdAt`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='经费下拨及调整记录';
|
||||
|
||||
-- 兼容已执行旧版建表脚本的数据库。
|
||||
SET @drop_annual_allocated_at = IF(EXISTS (
|
||||
SELECT 1 FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'outlay_annual_base' AND COLUMN_NAME = 'allocatedAt'
|
||||
), 'ALTER TABLE `outlay_annual_base` DROP COLUMN `allocatedAt`', 'DO 0');
|
||||
PREPARE drop_annual_allocated_at FROM @drop_annual_allocated_at;
|
||||
EXECUTE drop_annual_allocated_at;
|
||||
DEALLOCATE PREPARE drop_annual_allocated_at;
|
||||
|
||||
SET @drop_annual_allocated_at = IF(EXISTS (
|
||||
SELECT 1 FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'outlay_annual_special' AND COLUMN_NAME = 'allocatedAt'
|
||||
), 'ALTER TABLE `outlay_annual_special` DROP COLUMN `allocatedAt`', 'DO 0');
|
||||
PREPARE drop_annual_allocated_at FROM @drop_annual_allocated_at;
|
||||
EXECUTE drop_annual_allocated_at;
|
||||
DEALLOCATE PREPARE drop_annual_allocated_at;
|
||||
|
||||
-- 沿用现有权限与入口,仅调整菜单名称。
|
||||
UPDATE sys_menu SET name='年度经费下拨', aliasName='年度经费下拨'
|
||||
WHERE permission='outlay.outlayManage.union.allocate';
|
||||
|
||||
@@ -177,7 +177,7 @@ layout("/layouts/platform.html"){
|
||||
getTreeData() {
|
||||
this.$set(this, "treeLoading", true)
|
||||
// tree 返回单位树数组;失败时保留现有内容,成功后按 ID 恢复当前单位。
|
||||
return this.$axios.post(loc() + "/tree").then((res) => {
|
||||
return this.$axios.post("/platform/sys/unit/tree").then((res) => {
|
||||
if (res.code !== 0 || !Array.isArray(res.data)) return
|
||||
const selectedId = this.currentTreeData?.id
|
||||
this.$set(this, "treeData", res.data)
|
||||
|
||||
+115
-24
@@ -6,6 +6,14 @@ layout("/layouts/platform.html"){
|
||||
.fund-log-dialog .el-dialog { width: 94%; max-width: 1800px; height: 86vh; display: flex; flex-direction: column; }
|
||||
.fund-log-dialog .el-dialog__body { padding: 16px 24px 24px; flex: 1; min-height: 0; display: flex; flex-direction: column; }
|
||||
.fund-log-dialog .el-pagination { margin-top: 16px; text-align: right; flex: 0 0 auto; }
|
||||
.fund-import-dialog .el-dialog { max-width: calc(100vw - 32px); }
|
||||
.fund-import-dialog .el-dialog__body { padding: 16px 24px; max-height: 68vh; overflow-y: auto; }
|
||||
.fund-import-dialog .el-dialog__footer { border-top: 1px solid #ebeef5; }
|
||||
.fund-import-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 16px 0 8px; }
|
||||
.fund-import-upload .el-upload,
|
||||
.fund-import-upload .el-upload-dragger { display: block; width: 100%; }
|
||||
.fund-import-upload .el-upload-dragger { height: 140px; }
|
||||
.fund-import-upload .el-icon-upload { margin: 16px 0 8px; }
|
||||
.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; }
|
||||
@@ -23,6 +31,14 @@ layout("/layouts/platform.html"){
|
||||
.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; }
|
||||
.fund-adjust-dialog .el-dialog__header { padding-bottom: 8px; }
|
||||
.fund-adjust-dialog .el-dialog__body { padding: 12px 24px; }
|
||||
.fund-adjust-dialog .el-dialog__footer { padding: 12px 24px; }
|
||||
.fund-adjust-dialog .el-alert { margin-bottom: 12px; }
|
||||
.fund-adjust-dialog .fund-edit-form { gap: 10px 20px; }
|
||||
.fund-adjust-dialog .fund-edit-section { padding-bottom: 6px; }
|
||||
.fund-adjust-dialog .el-form-item__label { padding-bottom: 4px; line-height: 20px; }
|
||||
.fund-adjust-dialog .fund-edit-difference { padding: 6px 12px; }
|
||||
@media (max-width: 600px) {
|
||||
.fund-edit-form { grid-template-columns: minmax(0, 1fr); }
|
||||
}
|
||||
@@ -35,6 +51,13 @@ layout("/layouts/platform.html"){
|
||||
<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-item v-if="tab==='base'" label="金额一致性">
|
||||
<el-select v-model="query.amountMatch" @change="doSearch">
|
||||
<el-option label="全部" value=""></el-option>
|
||||
<el-option label="一致" value="equal"></el-option>
|
||||
<el-option label="不一致" value="different"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
</search>
|
||||
</el-card>
|
||||
<el-card class="outlay-union-fixed-list-card mt10" shadow="never">
|
||||
@@ -47,8 +70,9 @@ layout("/layouts/platform.html"){
|
||||
<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==='base' && isCurrentYear" size="small" type="danger" plain @click="resetBaseRecords" :disabled="busy || loading">重置分配记录</el-button>
|
||||
<el-button v-if="tab==='summary' || tab==='history'" size="small" type="primary" @click="doExport" :disabled="loading || busy">导出Excel</el-button>
|
||||
<el-button v-if="tab==='base'" size="small" type="primary" plain @click="openBaseImport" :disabled="busy || loading">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>
|
||||
@@ -71,14 +95,11 @@ layout("/layouts/platform.html"){
|
||||
</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>
|
||||
@@ -89,7 +110,7 @@ layout("/layouts/platform.html"){
|
||||
</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-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>
|
||||
@@ -97,8 +118,40 @@ layout("/layouts/platform.html"){
|
||||
</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-dialog title="Excel导入年度基础经费金额" :visible.sync="baseImportVisible" width="780px" top="6vh" class="fund-import-dialog" :close-on-click-modal="false" :show-close="!busy" destroy-on-close>
|
||||
<el-alert :title="query.year+'年 / '+selectedUnionName" type="info" show-icon :closable="false">
|
||||
请在模板中仅保留需调整的分工会并填写分配额度。导入会更新已有基础经费记录并同步年度预算。任一行未匹配到唯一有效记录时,整批导入失败。
|
||||
</el-alert>
|
||||
<div class="fund-import-toolbar">
|
||||
<strong>导入文件</strong>
|
||||
<el-button size="small" @click="downloadBaseImportTemplate">下载模板</el-button>
|
||||
</div>
|
||||
<el-upload class="fund-import-upload" action="/platform/outlay/outlayManage/unionAllocate/readImportExcel" drag accept=".xls,.xlsx" :limit="1"
|
||||
:before-upload="validateBaseImportFile" :on-exceed="onBaseImportExceed"
|
||||
:on-success="onBaseImportPreview" :on-error="onBaseImportError" :on-remove="clearBaseImportPreview" :disabled="busy">
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">将 Excel 文件拖到此处,或<em>点击上传</em></div>
|
||||
<div slot="tip" class="el-upload__tip">支持 .xls、.xlsx,每次上传一个文件</div>
|
||||
</el-upload>
|
||||
<el-form label-position="top" class="mt10">
|
||||
<el-form-item label="调整说明" required>
|
||||
<el-input v-model="baseImportRemark" type="textarea" :rows="2" maxlength="1000" show-word-limit></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table v-if="baseImportRows.length" :data="baseImportRows" max-height="300" border>
|
||||
<el-table-column prop="unionCode" label="工会编码" width="120"></el-table-column>
|
||||
<el-table-column prop="unionName" label="分工会" min-width="180"></el-table-column>
|
||||
<el-table-column prop="allocateMoney" label="实际下拨金额(元)" min-width="150"></el-table-column>
|
||||
<el-table-column label="校验结果" min-width="150"><template slot-scope="{row}">{{row.errMsg || '通过'}}</template></el-table-column>
|
||||
</el-table>
|
||||
<span slot="footer">
|
||||
<el-button @click="baseImportVisible=false" :disabled="busy">取消</el-button>
|
||||
<el-button type="primary" @click="saveBaseImport" :loading="busy" :disabled="!baseImportRows.length">确认导入</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
<el-dialog :title="form.status===1?'调整经费':'编辑经费'" :visible.sync="editVisible" width="860px" class="fund-edit-dialog" :class="{'fund-adjust-dialog': form.status===1}" 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-alert v-else title="保存后金额将计入年度预算。" type="info" 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>
|
||||
@@ -139,12 +192,14 @@ layout("/layouts/platform.html"){
|
||||
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},
|
||||
tab: 'base', query: {year: this.$moment().format('YYYY'), unionId: '', projectName: '', amountMatch: '', 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,
|
||||
baseImportVisible: false, baseImportRows: [], baseImportRemark: '',
|
||||
logsVisible: false, logsLoading: false, logs: [], logPage: 1, logTotal: 0, logRecord: null
|
||||
}},
|
||||
computed: {
|
||||
isCurrentYear() { return this.query.year===this.$moment().format('YYYY') },
|
||||
calculated() { return ((this.form.memberCount || 0) * (this.form.standard || 0)).toFixed(2) },
|
||||
amountDifference() {
|
||||
const amount=Number(this.form.allocateMoney)
|
||||
@@ -153,13 +208,17 @@ layout("/layouts/platform.html"){
|
||||
return Number((amount-reference).toFixed(2))
|
||||
},
|
||||
remarkRequired() { return this.form.status===1 || (this.tab==='base' && this.amountDifference!==0) },
|
||||
amountMatchScope() {
|
||||
if (!this.query.amountMatch) return ''
|
||||
return this.query.amountMatch==='equal'?' / 金额一致':' / 金额不一致'
|
||||
},
|
||||
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?'已下拨':'待下拨') }
|
||||
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)===2?'已重置':Number(v.status)===1?'已下拨':'待下拨') }
|
||||
catch(e) { return value }
|
||||
},
|
||||
doExport() {
|
||||
@@ -221,27 +280,59 @@ layout("/layouts/platform.html"){
|
||||
try { await this.request('save',payload); 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 }
|
||||
openBaseImport() {
|
||||
this.clearBaseImportPreview()
|
||||
this.baseImportRemark=''
|
||||
this.baseImportVisible=true
|
||||
},
|
||||
downloadBaseImportTemplate() {
|
||||
window.open('/platform/outlay/outlayManage/unionAllocate/downloadImportTemplate')
|
||||
},
|
||||
clearBaseImportPreview() { this.baseImportRows=[] },
|
||||
validateBaseImportFile(file) {
|
||||
if (/\.(xls|xlsx)$/i.test(file.name)) return true
|
||||
this.$message.warning('请上传 .xls 或 .xlsx 文件')
|
||||
return false
|
||||
},
|
||||
onBaseImportExceed() {
|
||||
this.$message.warning('每次只能上传一个 Excel 文件,更换时请先移除已选文件')
|
||||
},
|
||||
onBaseImportPreview(resp) {
|
||||
this.clearBaseImportPreview()
|
||||
if (resp.code===0) this.baseImportRows=resp.data || []
|
||||
else this.$message.error(resp.msg || 'Excel读取失败')
|
||||
},
|
||||
onBaseImportError() {
|
||||
this.clearBaseImportPreview()
|
||||
this.$message.error('Excel上传失败')
|
||||
},
|
||||
async saveBaseImport() {
|
||||
if (this.busy || !this.baseImportRows.length) return
|
||||
if (this.baseImportRows.some(row=>row.errMsg)) { this.$message.warning('请修正校验未通过的记录后重新上传'); return }
|
||||
if (!this.baseImportRemark.trim()) { this.$message.warning('请填写调整说明'); return }
|
||||
this.busy=true
|
||||
try {
|
||||
await this.$confirm('确认一键下拨【'+params.year+'年 / '+scope+'】的全部待下拨'+typeName+'?范围包含所有分页,已下拨记录自动跳过,请先核对金额及历史下拨记录。','一键下拨',{type:'warning'})
|
||||
const resp=await this.request('batchConfirm',params)
|
||||
const resp=await this.request('importBaseAmounts',{
|
||||
year:this.query.year,unionId:this.query.unionId || '',
|
||||
data:JSON.stringify(this.baseImportRows),remark:this.baseImportRemark.trim()
|
||||
})
|
||||
this.$message.success(resp.msg)
|
||||
this.baseImportVisible=false
|
||||
this.doSearch()
|
||||
} catch(e) {} finally { this.busy=false }
|
||||
},
|
||||
async resetBaseRecords() {
|
||||
if (this.busy || !this.isCurrentYear) return
|
||||
const params={year:this.query.year,unionId:this.query.unionId || '',amountMatch:this.query.amountMatch}
|
||||
const scope=params.year+'年 / '+this.selectedUnionName+this.amountMatchScope
|
||||
this.busy=true
|
||||
try {
|
||||
await this.$confirm('确认重置【'+scope+'】全部分页中的基础经费分配记录?已计入预算的金额将扣回,重置后可重新生成。','重置分配记录',{type:'warning',confirmButtonText:'确认重置'})
|
||||
const resp=await this.request('resetBaseRecords',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
|
||||
|
||||
+6
-12
@@ -4,22 +4,15 @@ let OUTLAY_MANAGE_UNION_QUARTERLY_ALLOCATE = {
|
||||
`
|
||||
<div>
|
||||
<template>
|
||||
<table-tool label="分配记录"></table-tool>
|
||||
<table-tool label="年度已下拨记录"></table-tool>
|
||||
<el-table :data="tableData" style="width: 100%" row-key="id" class="vi-table">
|
||||
<el-table-column type="index" label="序号" width="80px"></el-table-column>
|
||||
<el-table-column label="分配项目" prop="quarterly">
|
||||
<template v-slot="{row}">
|
||||
<dict-tag :options="dict.type.OUTLAY_QUARTERLY"
|
||||
:value="row.quarterly"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="年初数" prop="allocateHeadMoney"></el-table-column>
|
||||
<el-table-column label="经费额度" prop="allocateMoney"></el-table-column>
|
||||
<el-table-column label="分配项目" prop="projectName"></el-table-column>
|
||||
<el-table-column label="实际下拨金额(元)" prop="allocateMoney"></el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
</div>
|
||||
`,
|
||||
dicts: ["OUTLAY_QUARTERLY"],
|
||||
data() {
|
||||
return {
|
||||
rowData: {},
|
||||
@@ -29,11 +22,12 @@ let OUTLAY_MANAGE_UNION_QUARTERLY_ALLOCATE = {
|
||||
methods: {
|
||||
open(row) {
|
||||
this.rowData = row
|
||||
this.$axios.post("/platform/outlay/outlayManage/unionUseDetail/queryQuarterlyList", {
|
||||
this.tableData = []
|
||||
this.$axios.post("/platform/outlay/outlayManage/unionUseDetail/queryAnnualAllocateList", {
|
||||
year: row.year,
|
||||
unionId: row.unionId
|
||||
}).then(resp => {
|
||||
if (resp.code === 0) {
|
||||
if (this.rowData === row && resp.code === 0) {
|
||||
this.tableData = resp.data
|
||||
}
|
||||
})
|
||||
|
||||
+40
-26
@@ -52,6 +52,21 @@ layout("/layouts/platform.html"){
|
||||
.payment-info-table .el-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.invoice-detail-dialog {
|
||||
max-width: calc(100vw - 32px);
|
||||
}
|
||||
|
||||
.invoice-detail-dialog .el-dialog__body {
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
padding: 12px 24px;
|
||||
}
|
||||
|
||||
.invoice-detail-dialog .el-form-item__label {
|
||||
line-height: 22px;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
@@ -266,7 +281,7 @@ layout("/layouts/platform.html"){
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="报销金额" v-if="isManualReimburseAmount">
|
||||
<el-form-item label="报销金额" prop="money">
|
||||
<el-input v-model="formData.money" placeholder="请输入报销金额,最多两位小数"></el-input>
|
||||
<el-input :value="formData.money" @input="updateReimburseAmount" placeholder="请输入报销金额,最多两位小数"></el-input>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="所属协会"
|
||||
@@ -605,11 +620,11 @@ layout("/layouts/platform.html"){
|
||||
<div>
|
||||
<div class="invoice-toolbar-title">发票明细列表</div>
|
||||
<div class="invoice-summary-tip">
|
||||
{{ isManualReimburseAmount ? '发票选填;有发票时报销金额不得超过发票总额,无发票仍可提交。' : '发票选填;无发票时按支付信息汇总报销金额。' }}上传后须通过验真,每条明细对应一张发票图片或单页 PDF。
|
||||
提交时至少上传一张发票,上传后须通过验真。每条明细对应一张发票图片或单页 PDF。
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<el-tag type="info">发票选填,上传须验真</el-tag>
|
||||
<el-tag type="info">提交时必填</el-tag>
|
||||
<el-button @click="openInvoiceDialog('add')" plain type="primary"
|
||||
style="margin-left: 12px">新增发票
|
||||
</el-button>
|
||||
@@ -677,10 +692,11 @@ layout("/layouts/platform.html"){
|
||||
:title="invoiceDialogMode === 'edit' ? '编辑发票明细' : '新增发票明细'"
|
||||
:visible.sync="invoiceDialogVisible"
|
||||
append-to-body
|
||||
width="820px">
|
||||
<el-form :model="detailForm" :rules="detailRules" label-position="right"
|
||||
label-suffix=":" label-width="120px" ref="invoiceDetailForm">
|
||||
<el-row :gutter="30">
|
||||
custom-class="invoice-detail-dialog"
|
||||
top="5vh"
|
||||
width="1000px">
|
||||
<el-form :model="detailForm" :rules="detailRules" label-position="top" ref="invoiceDetailForm">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="发票文件" prop="invoiceFiles">
|
||||
<file-upload
|
||||
@@ -700,41 +716,41 @@ layout("/layouts/platform.html"){
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-col :xs="24" :sm="12">
|
||||
<el-form-item label="发票号码" prop="invoiceNo">
|
||||
<el-input placeholder="验真后自动回填" v-model="detailForm.invoiceNo" readonly></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-col :xs="24" :sm="12">
|
||||
<el-form-item label="开票日期">
|
||||
<el-input placeholder="识别后自动回填" v-model="detailForm.invoiceDate" readonly></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-col :xs="24" :sm="8">
|
||||
<el-form-item label="价税合计" prop="invoiceAmount">
|
||||
<el-input-number :controls="false" :min="0" :precision="2"
|
||||
placeholder="验真后自动回填" style="width: 100%"
|
||||
v-model="detailForm.invoiceAmount" disabled></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-col :xs="24" :sm="8">
|
||||
<el-form-item label="税前金额">
|
||||
<el-input-number :controls="false" :precision="2" style="width: 100%"
|
||||
v-model="detailForm.amountWithoutTax" disabled></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-col :xs="24" :sm="8">
|
||||
<el-form-item label="税额">
|
||||
<el-input-number :controls="false" :precision="2" style="width: 100%"
|
||||
v-model="detailForm.taxAmount" disabled></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-col :xs="24" :sm="12">
|
||||
<el-form-item label="销售方信息名称" prop="sellerName">
|
||||
<el-input placeholder="验真后自动回填" v-model="detailForm.sellerName" readonly></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-col :xs="24" :sm="12">
|
||||
<el-form-item label="项目名称" prop="itemName">
|
||||
<el-input placeholder="验真后自动回填" v-model="detailForm.itemName" readonly></el-input>
|
||||
</el-form-item>
|
||||
@@ -1647,7 +1663,10 @@ layout("/layouts/platform.html"){
|
||||
return false
|
||||
}
|
||||
const invoiceDetails = this.formData.invoiceDetails || []
|
||||
// 发票可不上传;已经添加的发票仍须填写完整并沿用验真流程。
|
||||
if (!invoiceDetails.length) {
|
||||
this.$message.warning("提交前请至少上传一张发票")
|
||||
return false
|
||||
}
|
||||
for (let i = 0; i < invoiceDetails.length; i++) {
|
||||
const currentDetail = invoiceDetails[i]
|
||||
if (!currentDetail.invoiceFiles || currentDetail.invoiceFiles.length === 0) {
|
||||
@@ -1710,6 +1729,11 @@ layout("/layouts/platform.html"){
|
||||
this.syncLegacyPaymentFields(submitData.paymentDetails, submitData)
|
||||
return submitData
|
||||
},
|
||||
updateReimburseAmount(value) {
|
||||
this.$set(this.formData, "money", value)
|
||||
const details = this.formData.paymentDetails || []
|
||||
if (details.length === 1) this.$set(details[0], "money", value)
|
||||
},
|
||||
// 金额输入允许编辑中的空值和末尾小数点;非法字符或超出两位小数时恢复原值,避免改变金额含义。
|
||||
inputPaymentMoney(row, value) {
|
||||
const previous = row.money
|
||||
@@ -1831,18 +1855,8 @@ layout("/layouts/platform.html"){
|
||||
// 保存
|
||||
onSave() {
|
||||
if (this.formLoading || this.recognizingInvoice) return
|
||||
if (!this.validateInvoiceDetailsBeforeSubmit()) {
|
||||
this.$set(this, "activeTabName", "invoice")
|
||||
return
|
||||
}
|
||||
this.rebuildInvoiceSummary()
|
||||
this.normalizeFundBalance()
|
||||
const paymentDetails = this.preparePaymentDetailsForSubmit()
|
||||
// 保存草稿也必须填写完整支付信息,避免非法金额和空明细入库。
|
||||
if (!this.validatePaymentDetailsBeforeSubmit(paymentDetails)) {
|
||||
this.$set(this, "activeTabName", "invoice")
|
||||
return
|
||||
}
|
||||
this.$confirm("您确定保存吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
@@ -1850,7 +1864,7 @@ layout("/layouts/platform.html"){
|
||||
}).then(() => {
|
||||
if (this.formLoading) return
|
||||
this.$set(this, "formLoading", true)
|
||||
this.$axios.post("/platform/unionReimburse/apply/save", {data: JSON.stringify(this.buildSubmitFormData(paymentDetails))}).then(res => {
|
||||
this.$axios.post("/platform/unionReimburse/apply/save", {data: JSON.stringify(this.buildSubmitFormData())}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
commonUtil.pjaxPush("/platform/unionReimburse/mine/index")
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
const unionReimburseInfo = {
|
||||
template: /*language=HTML*/ `
|
||||
<div>
|
||||
<div class="process-title">
|
||||
申请信息
|
||||
<div class="process-title" style="display: flex; align-items: center; justify-content: space-between; gap: 12px;">
|
||||
<span>申请信息</span>
|
||||
<el-link type="primary" @click="openChart">点击查看流程图</el-link>
|
||||
</div>
|
||||
|
||||
|
||||
+14
-11
@@ -682,7 +682,8 @@ layout("/layouts/platform_h5.html"){
|
||||
></van-field>
|
||||
<van-field v-if="isManualReimburseAmount"
|
||||
label="报销金额" name="money"
|
||||
v-model="formData.money"
|
||||
:model-value="formData.money"
|
||||
@update:model-value="updateReimburseAmount"
|
||||
placeholder="请输入报销金额,最多两位小数"
|
||||
:rules="[{ required: true, message: '请填写报销金额' }, { validator: () => !getReimburseAmountMessage(), message: getReimburseAmountMessage }]"
|
||||
required></van-field>
|
||||
@@ -773,8 +774,8 @@ layout("/layouts/platform_h5.html"){
|
||||
</van-action-sheet>
|
||||
</van-cell-group>
|
||||
<van-cell-group class="form-section" v-if="formData.reimburseProject">
|
||||
<div class="form-section__title">电子发票(选填)</div>
|
||||
<div class="invoice-tip">{{ isManualReimburseAmount ? '有发票时报销金额不得超过发票总额,无发票仍可提交;上传的发票仍须通过验真。' : '无发票时按支付信息汇总报销金额;上传的发票仍须通过验真。' }}</div>
|
||||
<div class="form-section__title">电子发票(提交时必填)</div>
|
||||
<div class="invoice-tip">提交时至少上传一张发票,上传后须通过验真。</div>
|
||||
<van-field label="报销金额"
|
||||
v-model="formData.money"
|
||||
:placeholder="isManualReimburseAmount ? '请在报销信息填写报销金额' : '有发票按发票汇总,无发票按支付信息汇总'"
|
||||
@@ -1942,7 +1943,10 @@ layout("/layouts/platform_h5.html"){
|
||||
return false
|
||||
}
|
||||
const invoiceDetails = this.formData.invoiceDetails || []
|
||||
// 发票可不上传;已经添加的发票仍须填写完整并沿用验真流程。
|
||||
if (!invoiceDetails.length) {
|
||||
this.$toast.fail("提交前请至少上传一张发票")
|
||||
return false
|
||||
}
|
||||
for (let i = 0; i < invoiceDetails.length; i++) {
|
||||
const currentDetail = invoiceDetails[i]
|
||||
if (!currentDetail.invoiceFiles || currentDetail.invoiceFiles.length === 0) {
|
||||
@@ -2190,6 +2194,11 @@ layout("/layouts/platform_h5.html"){
|
||||
this.syncLegacyPaymentFields(submitData.paymentDetails, submitData)
|
||||
return submitData
|
||||
},
|
||||
updateReimburseAmount(value) {
|
||||
this.$set(this.formData, "money", value)
|
||||
const details = this.formData.paymentDetails || []
|
||||
if (details.length === 1) this.$set(details[0], "money", value)
|
||||
},
|
||||
// 金额输入允许编辑中的空值和末尾小数点;非法字符或超出两位小数时恢复原值,避免改变金额含义。
|
||||
inputPaymentMoney(row, value) {
|
||||
const previous = row.money
|
||||
@@ -2323,17 +2332,11 @@ layout("/layouts/platform_h5.html"){
|
||||
},
|
||||
onSave() {
|
||||
if (this.formLoading || this.recognizingInvoice) return
|
||||
if (!this.validateInvoiceDetailsBeforeSubmit()) return
|
||||
this.normalizeContactFields()
|
||||
this.normalizeCondolenceDateFields()
|
||||
this.rebuildInvoiceSummary()
|
||||
const paymentDetails = this.preparePaymentDetailsForSubmit()
|
||||
// 保存草稿也必须填写完整支付信息,避免非法金额和空明细入库。
|
||||
if (!this.validatePaymentDetailsBeforeSubmit(paymentDetails)) {
|
||||
return
|
||||
}
|
||||
this.$set(this, "formLoading", true)
|
||||
this.$axios.post("/platform/unionReimburse/apply/save", { data: JSON.stringify(this.buildSubmitFormData(paymentDetails)) })
|
||||
this.$axios.post("/platform/unionReimburse/apply/save", { data: JSON.stringify(this.buildSubmitFormData()) })
|
||||
.then((res) => {
|
||||
this.$toast.clear()
|
||||
if (res.code === 0) {
|
||||
|
||||
Reference in New Issue
Block a user