|
|
@@ -59,7 +59,8 @@ export const campuses: MockCampus[] = [
|
|
|
{ campusId: 13, institutionId: 1, name: '高新校区' },
|
|
|
];
|
|
|
|
|
|
-const ulid = (n: number): string => `01J2M${String(n).padStart(19, '0')}`.slice(0, 26);
|
|
|
+// 契约 §1.5:canonical_user_id 恒 26 位 ULID(E8:原生成 23~24 位,补到 26)。
|
|
|
+const ulid = (n: number): string => `01J2M${String(n).padStart(21, '0')}`;
|
|
|
|
|
|
export const users: MockUser[] = [
|
|
|
{ userId: ulid(101), realName: '张婷', personType: 'staff', phone: '135****3322', campusId: 11, role: 'teacher' },
|
|
|
@@ -151,7 +152,8 @@ export interface ExpenseLog {
|
|
|
/** 金额(分,含方向符号:充值/坏账 +,消费/退费/清零 −) */
|
|
|
money: number;
|
|
|
balanceAfter: number;
|
|
|
- createAt: string;
|
|
|
+ /** 业务时间(契约 C1:`createAt` 是第三种写法,正确名为 `createTime` ← legacy `create_time`) */
|
|
|
+ createTime: string;
|
|
|
remark: string;
|
|
|
requestId?: string;
|
|
|
}
|
|
|
@@ -185,7 +187,7 @@ const logs: ExpenseLog[] = [];
|
|
|
const positive = type === 100;
|
|
|
const money = positive ? yuan * 100 : -yuan * 100;
|
|
|
running = Math.max(running + money, -200000);
|
|
|
- const createAt = rfc3339(BASE_MS - i * 3600_000 * randInt(1, 30));
|
|
|
+ const createTime = rfc3339(BASE_MS - i * 3600_000 * randInt(1, 30));
|
|
|
logs.push({
|
|
|
id: ++logSeq,
|
|
|
userId: u.userId,
|
|
|
@@ -194,7 +196,7 @@ const logs: ExpenseLog[] = [];
|
|
|
type,
|
|
|
money,
|
|
|
balanceAfter: running,
|
|
|
- createAt,
|
|
|
+ createTime,
|
|
|
remark: LOG_TYPE_REMARK[type] ?? '流水',
|
|
|
});
|
|
|
}
|
|
|
@@ -219,12 +221,12 @@ export function queryLogs(f: LogFilter): ExpenseLog[] {
|
|
|
if (f.type !== undefined && l.type !== f.type) return false;
|
|
|
if (f.institutionId && f.institutionId !== 1) return false;
|
|
|
if (f.campusId && f.campusId !== 0 && l.campusId !== f.campusId) return false;
|
|
|
- const t = Date.parse(l.createAt);
|
|
|
+ const t = Date.parse(l.createTime);
|
|
|
if (from !== null && t < from) return false;
|
|
|
if (to !== null && t > to) return false;
|
|
|
return true;
|
|
|
})
|
|
|
- .sort((a, b) => (a.createAt < b.createAt ? 1 : a.createAt > b.createAt ? -1 : b.id - a.id));
|
|
|
+ .sort((a, b) => (a.createTime < b.createTime ? 1 : a.createTime > b.createTime ? -1 : b.id - a.id));
|
|
|
}
|
|
|
|
|
|
/** 追加一条流水(写端点共用;返回新 logId)。 */
|
|
|
@@ -240,7 +242,7 @@ export function appendLog(userId: string, campusId: number, type: number, money:
|
|
|
type,
|
|
|
money,
|
|
|
balanceAfter: a.totalBalance,
|
|
|
- createAt: rfc3339(BASE_MS),
|
|
|
+ createTime: rfc3339(BASE_MS),
|
|
|
remark,
|
|
|
};
|
|
|
if (requestId) log.requestId = requestId;
|
|
|
@@ -261,7 +263,10 @@ export interface BalanceRequest {
|
|
|
applicantId: string;
|
|
|
approverId: string | null;
|
|
|
remark: string;
|
|
|
- createAt: string;
|
|
|
+ /** 申请时间(业务时间 ← request_time;v1.6 B2:`createdAt` 是迁移时生成的新列,不下发) */
|
|
|
+ requestTime: string;
|
|
|
+ /** 审批时间(业务时间 ← response_time;未审批为 null) */
|
|
|
+ responseTime: string | null;
|
|
|
}
|
|
|
const requestTypes = [100, 101, 202, 203, 300] as const;
|
|
|
let reqSeq = 5000;
|
|
|
@@ -270,6 +275,7 @@ const requests: BalanceRequest[] = [];
|
|
|
for (let i = 0; i < 60; i++) {
|
|
|
const u = pick(users.filter((x) => x.personType === 'student'));
|
|
|
const approved = pick([0, 0, 1, -1, -2] as const);
|
|
|
+ const requestTime = rfc3339(BASE_MS - i * 7200_000);
|
|
|
requests.push({
|
|
|
id: ++reqSeq,
|
|
|
bizNo: `REQ${(700000 + i).toString()}`,
|
|
|
@@ -282,10 +288,11 @@ const requests: BalanceRequest[] = [];
|
|
|
applicantId: u.userId,
|
|
|
approverId: approved === 0 ? null : ulid(302),
|
|
|
remark: '工单',
|
|
|
- createAt: rfc3339(BASE_MS - i * 7200_000),
|
|
|
+ requestTime,
|
|
|
+ responseTime: approved === 0 ? null : rfc3339(Date.parse(requestTime) + 3600_000),
|
|
|
});
|
|
|
}
|
|
|
- requests.sort((a, b) => (a.createAt < b.createAt ? 1 : -1));
|
|
|
+ requests.sort((a, b) => (a.requestTime < b.requestTime ? 1 : -1));
|
|
|
})();
|
|
|
export function queryRequests(f: { type?: number; approved?: number; userId?: string; institutionId?: number; campusId?: number }): BalanceRequest[] {
|
|
|
return requests.filter((r) => {
|
|
|
@@ -313,7 +320,8 @@ export function createRequest(type: number, userId: string, campusId: number, mo
|
|
|
applicantId,
|
|
|
approverId: null,
|
|
|
remark,
|
|
|
- createAt: rfc3339(BASE_MS),
|
|
|
+ requestTime: rfc3339(BASE_MS),
|
|
|
+ responseTime: null,
|
|
|
};
|
|
|
requests.unshift(r);
|
|
|
return r;
|
|
|
@@ -327,7 +335,8 @@ export interface ActivitySign {
|
|
|
status: number; // 0 未审核 / 1 通过 / -1 取消
|
|
|
recommendUserId: string | null;
|
|
|
money: number; // 分
|
|
|
- createAt: string;
|
|
|
+ /** 报名业务时间(契约 C1:`createAt`→`createTime`;端点 24/28 signeds 行) */
|
|
|
+ createTime: string;
|
|
|
}
|
|
|
export interface Activity {
|
|
|
id: number;
|
|
|
@@ -339,19 +348,23 @@ export interface Activity {
|
|
|
numberSign: number;
|
|
|
total: number;
|
|
|
signed: number;
|
|
|
+ /** 活动金额(分;v1.6 E6:活动级 `money`,充值赠送额度/老带新奖励额/特殊费用额,区别于 signed 行 money) */
|
|
|
+ money: number;
|
|
|
signable: boolean;
|
|
|
- startAt: string;
|
|
|
- endAt: string;
|
|
|
+ /** 开始时间(契约 C2:源列 start_time,活动与排班统一 startTime,撤回 startAt 方案) */
|
|
|
+ startTime: string;
|
|
|
+ /** 结束时间(契约 C2:源列 end_time,统一 endTime) */
|
|
|
+ endTime: string;
|
|
|
signeds: ActivitySign[];
|
|
|
}
|
|
|
let signSeq = 3000;
|
|
|
function mkSign(activityId: number, userId: string, status: number, recommendUserId: string | null, money: number): ActivitySign {
|
|
|
- return { id: ++signSeq, activityId, userId, status, recommendUserId, money, createAt: rfc3339(BASE_MS - randInt(1, 30) * 86400_000) };
|
|
|
+ return { id: ++signSeq, activityId, userId, status, recommendUserId, money, createTime: rfc3339(BASE_MS - randInt(1, 30) * 86400_000) };
|
|
|
}
|
|
|
const activities: Activity[] = [
|
|
|
{
|
|
|
id: 1, institutionId: 1, campusId: 11, type: 2, name: '老带新推荐(数学)', status: 1, numberSign: 1,
|
|
|
- total: 100, signed: 6, signable: true, startAt: rfc3339(BASE_MS - 40 * 86400_000), endAt: rfc3339(BASE_MS + 20 * 86400_000),
|
|
|
+ total: 100, signed: 6, money: 20000, signable: true, startTime: rfc3339(BASE_MS - 40 * 86400_000), endTime: rfc3339(BASE_MS + 20 * 86400_000),
|
|
|
signeds: [
|
|
|
mkSign(1, ulid(201), 1, null, 20000),
|
|
|
mkSign(1, ulid(202), 1, ulid(201), 20000),
|
|
|
@@ -363,22 +376,22 @@ const activities: Activity[] = [
|
|
|
},
|
|
|
{
|
|
|
id: 2, institutionId: 1, campusId: 12, type: 3, name: '充值赠送 10%', status: 1, numberSign: 1,
|
|
|
- total: 200, signed: 3, signable: true, startAt: rfc3339(BASE_MS - 20 * 86400_000), endAt: rfc3339(BASE_MS + 40 * 86400_000),
|
|
|
+ total: 200, signed: 3, money: 10000, signable: true, startTime: rfc3339(BASE_MS - 20 * 86400_000), endTime: rfc3339(BASE_MS + 40 * 86400_000),
|
|
|
signeds: [mkSign(2, ulid(203), 1, null, 10000), mkSign(2, ulid(205), 1, null, 15000), mkSign(2, ulid(204), 0, null, 8000)],
|
|
|
},
|
|
|
{
|
|
|
id: 3, institutionId: 1, campusId: 11, type: 4, name: '特殊费用减免', status: 0, numberSign: 1,
|
|
|
- total: 50, signed: 1, signable: false, startAt: rfc3339(BASE_MS - 60 * 86400_000), endAt: rfc3339(BASE_MS - 5 * 86400_000),
|
|
|
+ total: 50, signed: 1, money: 5000, signable: false, startTime: rfc3339(BASE_MS - 60 * 86400_000), endTime: rfc3339(BASE_MS - 5 * 86400_000),
|
|
|
signeds: [mkSign(3, ulid(202), 1, null, 5000)],
|
|
|
},
|
|
|
{
|
|
|
id: 4, institutionId: 1, campusId: 13, type: 3, name: '新学期充值有礼', status: 1, numberSign: 1,
|
|
|
- total: 150, signed: 0, signable: true, startAt: rfc3339(BASE_MS - 5 * 86400_000), endAt: rfc3339(BASE_MS + 35 * 86400_000),
|
|
|
+ total: 150, signed: 0, money: 15000, signable: true, startTime: rfc3339(BASE_MS - 5 * 86400_000), endTime: rfc3339(BASE_MS + 35 * 86400_000),
|
|
|
signeds: [],
|
|
|
},
|
|
|
{
|
|
|
id: 5, institutionId: 1, campusId: 12, type: 2, name: '老带新推荐(物理)', status: 1, numberSign: 1,
|
|
|
- total: 80, signed: 2, signable: true, startAt: rfc3339(BASE_MS - 15 * 86400_000), endAt: rfc3339(BASE_MS + 25 * 86400_000),
|
|
|
+ total: 80, signed: 2, money: 30000, signable: true, startTime: rfc3339(BASE_MS - 15 * 86400_000), endTime: rfc3339(BASE_MS + 25 * 86400_000),
|
|
|
signeds: [mkSign(5, ulid(203), 1, null, 30000), mkSign(5, ulid(205), 1, ulid(203), 30000)],
|
|
|
},
|
|
|
];
|
|
|
@@ -402,11 +415,15 @@ export interface CourseRecord {
|
|
|
courseId: number;
|
|
|
teacherId: string;
|
|
|
campusId: number;
|
|
|
- room: string;
|
|
|
+ /** 地址(v1.8:源列 `address`;**无 room 列**——地址≠教室号,不得顶替) */
|
|
|
+ address: string;
|
|
|
+ /** 周(排班规则源列 week VARCHAR(50) DEFAULT '-1';"-1"=全部周 / "0".."6" / "1,3,5" 多值) */
|
|
|
+ week: string;
|
|
|
startTime: string; // RFC3339
|
|
|
endTime: string;
|
|
|
status: 0 | 1 | 2; // 0 未开始 / 1 进行中 / 2 已结束
|
|
|
- students: { userId: string; attendance: 0 | 1; payCost: number; giveCost: number }[];
|
|
|
+ /** 逐学生(C3:`attendance/payCost`→`type/pay/cost/giveCost`) */
|
|
|
+ students: { userId: string; type: number; pay: number; cost: number; giveCost: number }[];
|
|
|
}
|
|
|
export interface Course {
|
|
|
id: number;
|
|
|
@@ -418,21 +435,23 @@ export interface Course {
|
|
|
records: CourseRecord[];
|
|
|
}
|
|
|
let recordSeq = 6000;
|
|
|
-function mkRecord(courseId: number, teacherId: string, campusId: number, room: string, offsetDays: number, status: 0 | 1 | 2): CourseRecord {
|
|
|
+function mkRecord(courseId: number, teacherId: string, campusId: number, address: string, week: string, offsetDays: number, status: 0 | 1 | 2): CourseRecord {
|
|
|
const start = BASE_MS + offsetDays * 86400_000;
|
|
|
+ const present = (on: boolean): number => (status === 2 && on ? 1 : 0); // 已结束课才有出勤 type=1(正常)
|
|
|
return {
|
|
|
id: ++recordSeq,
|
|
|
courseId,
|
|
|
teacherId,
|
|
|
campusId,
|
|
|
- room,
|
|
|
+ address,
|
|
|
+ week,
|
|
|
startTime: rfc3339(start),
|
|
|
endTime: rfc3339(start + 90 * 60_000),
|
|
|
status,
|
|
|
students: [
|
|
|
- { userId: ulid(201), attendance: status === 2 ? 1 : 0, payCost: 10000, giveCost: 0 },
|
|
|
- { userId: ulid(202), attendance: status === 2 ? 1 : 0, payCost: 10000, giveCost: 0 },
|
|
|
- { userId: ulid(204), attendance: status === 2 ? 0 : 0, payCost: 10000, giveCost: 0 },
|
|
|
+ { userId: ulid(201), type: present(true), pay: 10000, cost: 10000, giveCost: 0 },
|
|
|
+ { userId: ulid(202), type: present(true), pay: 10000, cost: 10000, giveCost: 0 },
|
|
|
+ { userId: ulid(204), type: present(false), pay: 10000, cost: 10000, giveCost: 0 },
|
|
|
],
|
|
|
};
|
|
|
}
|
|
|
@@ -440,19 +459,19 @@ const courses: Course[] = [
|
|
|
{
|
|
|
id: 1, institutionId: 1, name: '数学 8 班', classify: '数学', status: 1, teacherIds: [ulid(101)],
|
|
|
records: [
|
|
|
- mkRecord(1, ulid(101), 11, 'A203', -3, 2),
|
|
|
- mkRecord(1, ulid(101), 11, 'A203', 0, 0),
|
|
|
- mkRecord(1, ulid(101), 11, 'A205', 2, 0),
|
|
|
- mkRecord(1, ulid(101), 11, 'A203', 4, 0),
|
|
|
+ mkRecord(1, ulid(101), 11, '滨江校区 3 楼 305 教室', '1,3,5', -3, 2),
|
|
|
+ mkRecord(1, ulid(101), 11, '滨江校区 3 楼 305 教室', '1,3,5', 0, 0),
|
|
|
+ mkRecord(1, ulid(101), 11, '滨江校区 5 楼 502 教室', '2,4', 2, 0),
|
|
|
+ mkRecord(1, ulid(101), 11, '滨江校区 3 楼 305 教室', '-1', 4, 0),
|
|
|
],
|
|
|
},
|
|
|
{
|
|
|
id: 2, institutionId: 1, name: '物理 3 班', classify: '物理', status: 1, teacherIds: [ulid(102)],
|
|
|
- records: [mkRecord(2, ulid(102), 12, 'B102', -1, 1), mkRecord(2, ulid(102), 12, 'B102', 1, 0)],
|
|
|
+ records: [mkRecord(2, ulid(102), 12, '城西校区 1 楼 102 教室', '3', -1, 1), mkRecord(2, ulid(102), 12, '城西校区 1 楼 102 教室', '3', 1, 0)],
|
|
|
},
|
|
|
{
|
|
|
id: 3, institutionId: 1, name: '英语 1 班', classify: '英语', status: 0, teacherIds: [ulid(101)],
|
|
|
- records: [mkRecord(3, ulid(101), 11, 'C101', -7, 2)],
|
|
|
+ records: [mkRecord(3, ulid(101), 11, '滨江校区 4 楼 401 教室', '6', -7, 2)],
|
|
|
},
|
|
|
];
|
|
|
export function queryCourses(f: { classify?: string; status?: number; keyword?: string; institutionId?: number; campusId?: number }): Course[] {
|
|
|
@@ -477,24 +496,25 @@ export interface DivideNode {
|
|
|
userId: string;
|
|
|
realName: string;
|
|
|
parentUserId: string | null;
|
|
|
- quota: number; // 0~1 小数
|
|
|
+ /** 每次分成金额(元;A2:`quota` 是金额非 0~1 比率,源列 DECIMAL(12,2),与 total_income 同族;0.00 = 该链不分成) */
|
|
|
+ quota: number;
|
|
|
cumulativeIncome: number; // 分
|
|
|
children: DivideNode[];
|
|
|
}
|
|
|
const divideTree: DivideNode[] = [
|
|
|
{
|
|
|
- userId: ulid(303), realName: '超级管理员', parentUserId: null, quota: 1, cumulativeIncome: 0,
|
|
|
+ userId: ulid(303), realName: '超级管理员', parentUserId: null, quota: 500, cumulativeIncome: 0,
|
|
|
children: [
|
|
|
{
|
|
|
- userId: ulid(101), realName: '张婷', parentUserId: ulid(303), quota: 0.3, cumulativeIncome: 1200000,
|
|
|
+ userId: ulid(101), realName: '张婷', parentUserId: ulid(303), quota: 150, cumulativeIncome: 1200000,
|
|
|
children: [
|
|
|
- { userId: ulid(201), realName: '王梓涵', parentUserId: ulid(101), quota: 0.1, cumulativeIncome: 300000, children: [] },
|
|
|
+ { userId: ulid(201), realName: '王梓涵', parentUserId: ulid(101), quota: 50, cumulativeIncome: 300000, children: [] },
|
|
|
{ userId: ulid(202), realName: '陈一诺', parentUserId: ulid(101), quota: 0, cumulativeIncome: 0, children: [] }, // quota=0(新节点默认)
|
|
|
],
|
|
|
},
|
|
|
{
|
|
|
- userId: ulid(102), realName: '李强', parentUserId: ulid(303), quota: 0.2, cumulativeIncome: 800000,
|
|
|
- children: [{ userId: ulid(203), realName: '李沐宸', parentUserId: ulid(102), quota: 0.15, cumulativeIncome: 200000, children: [] }],
|
|
|
+ userId: ulid(102), realName: '李强', parentUserId: ulid(303), quota: 100, cumulativeIncome: 800000,
|
|
|
+ children: [{ userId: ulid(203), realName: '李沐宸', parentUserId: ulid(102), quota: 30, cumulativeIncome: 200000, children: [] }],
|
|
|
},
|
|
|
],
|
|
|
},
|
|
|
@@ -514,29 +534,63 @@ export function findDivideNode(userId: string): DivideNode | undefined {
|
|
|
return found;
|
|
|
}
|
|
|
|
|
|
-/* ================= 课酬方案 ================= */
|
|
|
-export interface PayrollPlan {
|
|
|
- id: number;
|
|
|
- teacherId: string;
|
|
|
- schoolId: number;
|
|
|
- campusId: number;
|
|
|
+/* ================= 课酬方案(v1.9 两级结构:方案 + 明细) ================= */
|
|
|
+/** 课酬明细行(t_course_payment_detail;金额存分,教龄/比例为 number,percentage 为百分比数 0~100)。 */
|
|
|
+export interface PayrollDetail {
|
|
|
+ detailId: number;
|
|
|
+ /** 老师(可空;null = 机构级通用档) */
|
|
|
+ teacherId: string | null;
|
|
|
classifyId: number;
|
|
|
- teacherExp: number;
|
|
|
+ classType: number;
|
|
|
+ gradeId: number;
|
|
|
+ subjectId: number;
|
|
|
+ teacherCert: number;
|
|
|
+ /** 教龄区间(年,3 位小数;源列两列,不得合并为单值) */
|
|
|
+ teacherExpMin: number;
|
|
|
+ teacherExpMax: number;
|
|
|
+ /** 课酬比例(百分比数 0~100,1 位小数;A3:非 0~1 小数) */
|
|
|
percentage: number;
|
|
|
+ /** 金额上下限 / 奖励(分) */
|
|
|
min: number;
|
|
|
max: number;
|
|
|
reward: number;
|
|
|
- actived: 0 | 1;
|
|
|
+ /** 计算方式名(明细级 name;方案级为 schemeName) */
|
|
|
+ displayName: string | null;
|
|
|
+ /** 明细级启停(方案级为 actived) */
|
|
|
+ detailActived: 0 | 1;
|
|
|
+}
|
|
|
+/** 课酬方案(t_course_payment;两级结构顶层)。 */
|
|
|
+export interface PayrollPlan {
|
|
|
+ id: number;
|
|
|
+ institutionId: number;
|
|
|
+ campusId: number; // 0 = 机构级方案
|
|
|
+ schemeName: string;
|
|
|
effectDate: string;
|
|
|
+ /** 方案级启停(整案停用 = 不参与匹配) */
|
|
|
+ actived: 0 | 1;
|
|
|
+ details: PayrollDetail[];
|
|
|
}
|
|
|
+let detailSeq = 90000;
|
|
|
const payrollPlans: PayrollPlan[] = [
|
|
|
- { id: 1, teacherId: ulid(101), schoolId: 1, campusId: 11, classifyId: 1, teacherExp: 8, percentage: 0.5, min: 50000, max: 200000, reward: 10000, actived: 1, effectDate: '2026-09-01' },
|
|
|
- { id: 2, teacherId: ulid(102), schoolId: 1, campusId: 12, classifyId: 2, teacherExp: 5, percentage: 0.4, min: 40000, max: 150000, reward: 8000, actived: 1, effectDate: '2026-09-01' },
|
|
|
+ {
|
|
|
+ id: 1, institutionId: 1, campusId: 11, schemeName: '滨江校区 2026 秋季课酬方案', effectDate: '2026-09-01', actived: 1,
|
|
|
+ details: [
|
|
|
+ { detailId: ++detailSeq, teacherId: ulid(101), classifyId: 1, classType: 1, gradeId: 1, subjectId: 1, teacherCert: 1, teacherExpMin: 5, teacherExpMax: 15, percentage: 85, min: 90000, max: 150000, reward: 10000, displayName: '数学 5~15 年教龄', detailActived: 1 },
|
|
|
+ { detailId: ++detailSeq, teacherId: null, classifyId: 1, classType: 1, gradeId: -1, subjectId: -1, teacherCert: 0, teacherExpMin: 0, teacherExpMax: 5, percentage: 70, min: 60000, max: 90000, reward: 0, displayName: '数学 5 年内教龄(通用)', detailActived: 1 },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 2, institutionId: 1, campusId: 12, schemeName: '城西校区 2026 秋季课酬方案', effectDate: '2026-09-01', actived: 1,
|
|
|
+ details: [
|
|
|
+ { detailId: ++detailSeq, teacherId: ulid(102), classifyId: 2, classType: 1, gradeId: 2, subjectId: 2, teacherCert: 0, teacherExpMin: 3, teacherExpMax: 10, percentage: 80, min: 80000, max: 120000, reward: 8000, displayName: '物理 3~10 年教龄', detailActived: 1 },
|
|
|
+ { detailId: ++detailSeq, teacherId: null, classifyId: 2, classType: 1, gradeId: -1, subjectId: -1, teacherCert: 0, teacherExpMin: 0, teacherExpMax: 3, percentage: 60, min: 50000, max: 80000, reward: 0, displayName: '物理 3 年内教龄(通用)', detailActived: 0 },
|
|
|
+ ],
|
|
|
+ },
|
|
|
];
|
|
|
export function queryPayrollPlans(f: { actived?: number; classifyId?: number }): PayrollPlan[] {
|
|
|
return payrollPlans.filter((p) => {
|
|
|
if (f.actived !== undefined && p.actived !== f.actived) return false;
|
|
|
- if (f.classifyId !== undefined && p.classifyId !== f.classifyId) return false;
|
|
|
+ if (f.classifyId !== undefined && !p.details.some((d) => d.classifyId === f.classifyId)) return false;
|
|
|
return true;
|
|
|
});
|
|
|
}
|
|
|
@@ -544,24 +598,24 @@ export function findPayrollPlan(id: number): PayrollPlan | undefined {
|
|
|
return payrollPlans.find((p) => p.id === id);
|
|
|
}
|
|
|
|
|
|
-/* ================= 收支类型字典 + 收支记录 ================= */
|
|
|
+/* ================= 收支类型字典(v1.9:type 1=支出/2=收入,删 direction;补 id/parentId) ================= */
|
|
|
export interface ExpenditureType {
|
|
|
+ id: number;
|
|
|
+ /** 收支方向编码:1=支出 / 2=收入(与流水 type 同事实,两表均无 direction 列) */
|
|
|
type: number;
|
|
|
name: string;
|
|
|
- direction: 1 | -1; // 1 收 / -1 支
|
|
|
+ /** 上级类型 id(0 = 顶级) */
|
|
|
+ parentId: number;
|
|
|
}
|
|
|
export const expenditureTypes: ExpenditureType[] = [
|
|
|
- { type: 101, name: '课程消费(校区)', direction: -1 },
|
|
|
- { type: 104, name: '课程消费(学校)', direction: -1 },
|
|
|
- { type: 105, name: '课程消费(学校)', direction: -1 },
|
|
|
- { type: 201, name: '课程消费(校区)', direction: -1 },
|
|
|
- { type: 204, name: '课程消费(学校)', direction: -1 },
|
|
|
- { type: 205, name: '课程消费(学校)', direction: -1 },
|
|
|
- { type: 300, name: '线下收入', direction: 1 },
|
|
|
- { type: 301, name: '线上收入', direction: 1 },
|
|
|
+ { id: 1, type: 1, name: '课程消费', parentId: 0 },
|
|
|
+ { id: 2, type: 1, name: '课程消费(校区)', parentId: 1 },
|
|
|
+ { id: 3, type: 1, name: '课程消费(学校)', parentId: 1 },
|
|
|
+ { id: 4, type: 2, name: '线下收入', parentId: 0 },
|
|
|
+ { id: 5, type: 2, name: '线上收入', parentId: 0 },
|
|
|
];
|
|
|
-export function expenditureTypeOf(type: number): ExpenditureType | undefined {
|
|
|
- return expenditureTypes.find((t) => t.type === type);
|
|
|
+export function expenditureTypeOf(id: number): ExpenditureType | undefined {
|
|
|
+ return expenditureTypes.find((t) => t.id === id);
|
|
|
}
|
|
|
|
|
|
/* ================= 幂等存储(写端点重复提交 → repeated=true) ================= */
|