forked from wangziqi/gongxue-base
refactor(server): 待办统计日期懒计算,aislop 豁免注释与文档纠错
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
// aislop-ignore-file: duplicate-block -- 业务实体/工作流为声明式元数据,结构相似但语义不同
|
||||||
import type {
|
import type {
|
||||||
BusinessEntity,
|
BusinessEntity,
|
||||||
BusinessEntityField,
|
BusinessEntityField,
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ interface TaskDefinition {
|
|||||||
sql: (scope: StudentAccessScope) => { sql: string; params: unknown[] };
|
sql: (scope: StudentAccessScope) => { sql: string; params: unknown[] };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SqlBuilder = (scope: StudentAccessScope) => { sql: string; params: unknown[] };
|
||||||
|
|
||||||
function can(context: AgentToolContext, permission: string): boolean {
|
function can(context: AgentToolContext, permission: string): boolean {
|
||||||
return context.isSuperAdmin || context.permissions.includes(permission);
|
return context.isSuperAdmin || context.permissions.includes(permission);
|
||||||
}
|
}
|
||||||
@@ -42,6 +44,16 @@ function teacherParams(scope: StudentAccessScope): unknown[] {
|
|||||||
return scope.type === 'teacher' ? [scope.userId] : [];
|
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 {
|
function today(): string {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const year = now.getFullYear();
|
const year = now.getFullYear();
|
||||||
@@ -67,17 +79,14 @@ const TASKS: readonly TaskDefinition[] = [
|
|||||||
permission: 'student:view',
|
permission: 'student:view',
|
||||||
workflowKeys: ['student_teaching'],
|
workflowKeys: ['student_teaching'],
|
||||||
teacherRestricted: true,
|
teacherRestricted: true,
|
||||||
sql: () => ({
|
sql: countSql(`
|
||||||
sql: `
|
SELECT COUNT(*) AS cnt FROM students s
|
||||||
SELECT COUNT(*) AS cnt FROM students s
|
WHERE s.status = 'active'
|
||||||
WHERE s.status = 'active'
|
AND NOT EXISTS (
|
||||||
AND NOT EXISTS (
|
SELECT 1 FROM class_student cs
|
||||||
SELECT 1 FROM class_student cs
|
WHERE cs.student_id = s.id AND cs.status = 'active'
|
||||||
WHERE cs.student_id = s.id AND cs.status = 'active'
|
)
|
||||||
)
|
`),
|
||||||
`,
|
|
||||||
params: [],
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'students_without_checkin',
|
key: 'students_without_checkin',
|
||||||
@@ -85,8 +94,8 @@ const TASKS: readonly TaskDefinition[] = [
|
|||||||
entity: 'occupancy',
|
entity: 'occupancy',
|
||||||
permission: 'occupancy:view',
|
permission: 'occupancy:view',
|
||||||
workflowKeys: ['dormitory_billing'],
|
workflowKeys: ['dormitory_billing'],
|
||||||
sql: (scope) => ({
|
sql: teacherScopedCountSql(
|
||||||
sql: `
|
(scope) => `
|
||||||
SELECT COUNT(DISTINCT s.id) AS cnt FROM students s
|
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'
|
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'
|
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 : ''}
|
${teacherScoped(scope) ? TEACHER_CLASS_FILTER_SQL : ''}
|
||||||
AND o.id IS NULL
|
AND o.id IS NULL
|
||||||
`,
|
`,
|
||||||
params: teacherParams(scope),
|
),
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'occupancies_without_bill',
|
key: 'occupancies_without_bill',
|
||||||
@@ -103,8 +111,8 @@ const TASKS: readonly TaskDefinition[] = [
|
|||||||
entity: 'bill',
|
entity: 'bill',
|
||||||
permission: 'bill:view',
|
permission: 'bill:view',
|
||||||
workflowKeys: ['dormitory_billing'],
|
workflowKeys: ['dormitory_billing'],
|
||||||
sql: (scope) => ({
|
sql: teacherScopedCountSql(
|
||||||
sql: `
|
(scope) => `
|
||||||
SELECT COUNT(DISTINCT o.id) AS cnt FROM occupancies o
|
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'" : ''}
|
${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
|
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 : ''}
|
${teacherScoped(scope) ? TEACHER_CLASS_FILTER_SQL : ''}
|
||||||
AND b.id IS NULL
|
AND b.id IS NULL
|
||||||
`,
|
`,
|
||||||
params: teacherParams(scope),
|
),
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'rentals_without_contract',
|
key: 'rentals_without_contract',
|
||||||
@@ -121,13 +128,10 @@ const TASKS: readonly TaskDefinition[] = [
|
|||||||
entity: 'rental',
|
entity: 'rental',
|
||||||
permission: 'rental:view',
|
permission: 'rental:view',
|
||||||
workflowKeys: ['classroom_rental'],
|
workflowKeys: ['classroom_rental'],
|
||||||
sql: () => ({
|
sql: countSql(`
|
||||||
sql: `
|
SELECT COUNT(*) AS cnt FROM classroom_rentals r
|
||||||
SELECT COUNT(*) AS cnt FROM classroom_rentals r
|
WHERE r.status = 'active' AND r.contract_path IS NULL
|
||||||
WHERE r.status = 'active' AND r.contract_path IS NULL
|
`),
|
||||||
`,
|
|
||||||
params: [],
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'rentals_ending_soon',
|
key: 'rentals_ending_soon',
|
||||||
@@ -135,13 +139,13 @@ const TASKS: readonly TaskDefinition[] = [
|
|||||||
entity: 'rental',
|
entity: 'rental',
|
||||||
permission: 'rental:view',
|
permission: 'rental:view',
|
||||||
workflowKeys: ['classroom_rental'],
|
workflowKeys: ['classroom_rental'],
|
||||||
sql: () => ({
|
sql: countSql(
|
||||||
sql: `
|
`
|
||||||
SELECT COUNT(*) AS cnt FROM classroom_rentals r
|
SELECT COUNT(*) AS cnt FROM classroom_rentals r
|
||||||
WHERE r.status = 'active' AND r.end_date BETWEEN ? AND ?
|
WHERE r.status = 'active' AND r.end_date BETWEEN ? AND ?
|
||||||
`,
|
`,
|
||||||
params: [today(), inDays(7)],
|
() => [today(), inDays(7)],
|
||||||
}),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// aislop-ignore-file: thin-wrapper -- String() 薄包装:绕开 eslint no-base-to-string 对 unknown 收窄后的误报,避免在各调用点散落 disable
|
||||||
/**
|
/**
|
||||||
* 安全字符串化 unknown。
|
* 安全字符串化 unknown。
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// aislop-ignore-file: file-too-large -- 既有规模(445 行),费用业务方法高度耦合仓储/DTO,拆分作为独立重构任务跟踪
|
||||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { DataSource, In, Repository } from 'typeorm';
|
import { DataSource, In, Repository } from 'typeorm';
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// aislop-ignore-file: file-too-large -- 既有规模(477 行),费用控制器路由与文件导出逻辑集中于此,拆分作为独立重构任务跟踪
|
||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ Agent 的工具全部通过 CASL 权限过滤 + 执行时二次鉴权,只暴
|
|||||||
|
|
||||||
- `render_form` → 用户填写提交 → `create_student` / `update_students`
|
- `render_form` → 用户填写提交 → `create_student` / `update_students`
|
||||||
- `start_import_wizard` → 解析上传 Excel 并生成批量导入向导(students / rooms / transfers / checkins)→ 用户按阶段确认 → 系统按依赖顺序入库:学生 → 宿舍 → 换宿 → 入住
|
- `start_import_wizard` → 解析上传 Excel 并生成批量导入向导(students / rooms / transfers / checkins)→ 用户按阶段确认 → 系统按依赖顺序入库:学生 → 宿舍 → 换宿 → 入住
|
||||||
- Excel 结构探查:`office_analyze`(outline/get/query),不整表读取
|
- Office 附件:上传时系统已自动提取附件文本(Excel 为“工作表名 + tab 分隔行”),无需单独解析工具,不整表读取
|
||||||
|
|
||||||
## 四、当前执行约束(已有)
|
## 四、当前执行约束(已有)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user