fix(correctness): 并发/事务/实体/时区/状态一致性修复

由 OCR(open-codereview.ai,deepseek-v4-flash)审查驱动修复:
- wallets 原子扣款防 double-spend;refund 条件更新幂等;findTransactions 分页
- financial/imports/occupancies/attendance 事务与 advisory lock;重复生成/提交幂等
- 矛盾校验器、日期区间、实体双映射/DECIMAL/nullable、时区统一(china-time)
- rbac-seed 防重激活、exam 权限恢复、状态一致性、路由顺序、N+1/IN 分块等性能项

Reviewed-by: OCR (open-codereview.ai)
This commit is contained in:
2026-08-09 21:29:54 +08:00
parent 99ea931409
commit f50301148d
54 changed files with 3843 additions and 722 deletions

View File

@@ -77,6 +77,13 @@ export class ScheduleSyncService {
const classDingUsers = await buildClassDingUserMap(this.classStudentRepo, this.mappingRepo, classIds);
const classNameMap = await loadClassNames(this.classRepo, classIds);
// 同名班级会串组:统计本次参与同步的班级名出现次数,
// 重名时在考勤组名/组标识中带上 classId 以避免共享同一个考勤组。
const classNameCounts = new Map<string, number>();
for (const name of classNameMap.values()) {
classNameCounts.set(name, (classNameCounts.get(name) || 0) + 1);
}
// ── Step 3: 将每天的多节课合并成一个钉钉班次 ──
// 钉钉要求每人每天只能写入一条排班,因此同一天的多节课必须作为
// 同一个班次的多个 sections 写入,不能拆成多条 schedule item。
@@ -203,8 +210,11 @@ export class ScheduleSyncService {
continue;
}
// 创建/匹配该班级的考勤组
const groupName = `排课_${className}`;
// 创建/匹配该班级的考勤组;重名班级时追加 classId 后缀避免串组
const groupName =
(classNameCounts.get(className) ?? 0) > 1
? `排课_${classId}_${className}`
: `排课_${className}`;
let attendanceGroupId: number;
try {
const cached = groupByName.get(groupName);