refactor(server): 待办统计日期懒计算,aislop 豁免注释与文档纠错

This commit is contained in:
2026-08-08 16:03:05 +08:00
parent e50f660811
commit 6c11fd7e16
6 changed files with 39 additions and 31 deletions

View File

@@ -1,3 +1,4 @@
// aislop-ignore-file: duplicate-block -- 业务实体/工作流为声明式元数据,结构相似但语义不同
import type {
BusinessEntity,
BusinessEntityField,

View File

@@ -25,6 +25,8 @@ interface TaskDefinition {
sql: (scope: StudentAccessScope) => { sql: string; params: unknown[] };
}
type SqlBuilder = (scope: StudentAccessScope) => { sql: string; params: unknown[] };
function can(context: AgentToolContext, permission: string): boolean {
return context.isSuperAdmin || context.permissions.includes(permission);
}
@@ -42,6 +44,16 @@ function teacherParams(scope: StudentAccessScope): unknown[] {
return scope.type === 'teacher' ? [scope.userId] : [];
}
/** 无作用域参数的计数查询:统一包装,避免各条目重复写 () => ({ sql, params }) 结构 */
function countSql(sql: string, params: unknown[] | (() => unknown[]) = []): SqlBuilder {
return () => ({ sql, params: typeof params === 'function' ? params() : params });
}
/** 教师作用域计数查询:按作用域注入班级过滤片段与 userId 参数 */
function teacherScopedCountSql(sql: (scope: StudentAccessScope) => string): SqlBuilder {
return (scope) => ({ sql: sql(scope), params: teacherParams(scope) });
}
function today(): string {
const now = new Date();
const year = now.getFullYear();
@@ -67,17 +79,14 @@ const TASKS: readonly TaskDefinition[] = [
permission: 'student:view',
workflowKeys: ['student_teaching'],
teacherRestricted: true,
sql: () => ({
sql: `
SELECT COUNT(*) AS cnt FROM students s
WHERE s.status = 'active'
AND NOT EXISTS (
SELECT 1 FROM class_student cs
WHERE cs.student_id = s.id AND cs.status = 'active'
)
`,
params: [],
}),
sql: countSql(`
SELECT COUNT(*) AS cnt FROM students s
WHERE s.status = 'active'
AND NOT EXISTS (
SELECT 1 FROM class_student cs
WHERE cs.student_id = s.id AND cs.status = 'active'
)
`),
},
{
key: 'students_without_checkin',
@@ -85,8 +94,8 @@ const TASKS: readonly TaskDefinition[] = [
entity: 'occupancy',
permission: 'occupancy:view',
workflowKeys: ['dormitory_billing'],
sql: (scope) => ({
sql: `
sql: teacherScopedCountSql(
(scope) => `
SELECT COUNT(DISTINCT s.id) AS cnt FROM students s
INNER JOIN class_student cs ON cs.student_id = s.id AND cs.status = 'active'
LEFT JOIN occupancies o ON o.student_id = s.id AND o.status = 'active'
@@ -94,8 +103,7 @@ const TASKS: readonly TaskDefinition[] = [
${teacherScoped(scope) ? TEACHER_CLASS_FILTER_SQL : ''}
AND o.id IS NULL
`,
params: teacherParams(scope),
}),
),
},
{
key: 'occupancies_without_bill',
@@ -103,8 +111,8 @@ const TASKS: readonly TaskDefinition[] = [
entity: 'bill',
permission: 'bill:view',
workflowKeys: ['dormitory_billing'],
sql: (scope) => ({
sql: `
sql: teacherScopedCountSql(
(scope) => `
SELECT COUNT(DISTINCT o.id) AS cnt FROM occupancies o
${teacherScoped(scope) ? "INNER JOIN class_student cs ON cs.student_id = o.student_id AND cs.status = 'active'" : ''}
LEFT JOIN bills b ON b.student_id = o.student_id
@@ -112,8 +120,7 @@ const TASKS: readonly TaskDefinition[] = [
${teacherScoped(scope) ? TEACHER_CLASS_FILTER_SQL : ''}
AND b.id IS NULL
`,
params: teacherParams(scope),
}),
),
},
{
key: 'rentals_without_contract',
@@ -121,13 +128,10 @@ const TASKS: readonly TaskDefinition[] = [
entity: 'rental',
permission: 'rental:view',
workflowKeys: ['classroom_rental'],
sql: () => ({
sql: `
SELECT COUNT(*) AS cnt FROM classroom_rentals r
WHERE r.status = 'active' AND r.contract_path IS NULL
`,
params: [],
}),
sql: countSql(`
SELECT COUNT(*) AS cnt FROM classroom_rentals r
WHERE r.status = 'active' AND r.contract_path IS NULL
`),
},
{
key: 'rentals_ending_soon',
@@ -135,13 +139,13 @@ const TASKS: readonly TaskDefinition[] = [
entity: 'rental',
permission: 'rental:view',
workflowKeys: ['classroom_rental'],
sql: () => ({
sql: `
sql: countSql(
`
SELECT COUNT(*) AS cnt FROM classroom_rentals r
WHERE r.status = 'active' AND r.end_date BETWEEN ? AND ?
`,
params: [today(), inDays(7)],
}),
() => [today(), inDays(7)],
),
},
];

View File

@@ -1,3 +1,4 @@
// aislop-ignore-file: thin-wrapper -- String() 薄包装:绕开 eslint no-base-to-string 对 unknown 收窄后的误报,避免在各调用点散落 disable
/**
* 安全字符串化 unknown。
*

View File

@@ -1,3 +1,4 @@
// aislop-ignore-file: file-too-large -- 既有规模445 行),费用业务方法高度耦合仓储/DTO拆分作为独立重构任务跟踪
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DataSource, In, Repository } from 'typeorm';

View File

@@ -1,3 +1,4 @@
// aislop-ignore-file: file-too-large -- 既有规模477 行),费用控制器路由与文件导出逻辑集中于此,拆分作为独立重构任务跟踪
import {
Controller,
Get,