|
|
@@ -97,11 +97,9 @@ try {
|
|
|
const deniedCourse = await ops({ operation: 'ops/appointments/preflight', payload: { studentId: memberWithUnboundCourse.member.userId, courseId: memberWithUnboundCourse.course.courseId, coachId: 0, classType: 1, date: schedule.dataDateTo || today, startTime: '09:00', recurrence: 'once', occurrences: 1 } }, 409);
|
|
|
if (!String(deniedCourse.message || '').includes('未绑定到当前会员')) throw new Error('排课预检没有阻止选择其他会员的课程');
|
|
|
}
|
|
|
- const memberForPreflight = schedule.members.find((member) => Number(member.userId) > 0 && !member.readOnly && ((Number(member.periods30) >= 2 && Number(member.userPoint) >= 1) || (Number(member.periods60) >= 2 && Number(member.userPoint) >= 2) || Number(member.userPoint) >= 2));
|
|
|
- const boundCourseForPreflight = memberForPreflight && schedule.courses.find((course) => Number(course.courseId) > 0 && (course.availableToAll === true || course.memberIds.includes(memberForPreflight.userId)));
|
|
|
- if (memberForPreflight && boundCourseForPreflight) {
|
|
|
- const classType = Number(memberForPreflight.periods30) >= 2 && Number(memberForPreflight.userPoint) >= 1 ? 1 : Number(memberForPreflight.periods60) >= 2 && Number(memberForPreflight.userPoint) >= 2 ? 2 : 4;
|
|
|
- const preflight = (await ops({ operation: 'ops/appointments/preflight', payload: { studentId: memberForPreflight.userId, courseId: boundCourseForPreflight.courseId, coachId: 0, classType, date: '2099-01-05', startTime: '09:00', recurrence: 'weekly', occurrences: 2 } })).data;
|
|
|
+ const preflightPair = schedule.members.filter((member) => Number(member.userId) > 0 && !member.readOnly).flatMap((member) => schedule.courses.filter((course) => Number(course.courseId) > 0 && course.mappingConfirmed && (course.availableToAll === true || course.memberIds.includes(member.userId))).map((course) => { const classType = course.categoryKey === 'trial' && Number(member.trialPeriods) >= 2 ? 3 : course.supportedDurations?.includes(30) && Number(member.periods30) >= 2 && Number(member.userPoint) >= 1 ? 1 : course.supportedDurations?.includes(60) && Number(member.periods60) >= 2 && Number(member.userPoint) >= 2 ? 2 : 0; return { member, course, classType }; })).find((item) => item.classType);
|
|
|
+ if (preflightPair) {
|
|
|
+ const preflight = (await ops({ operation: 'ops/appointments/preflight', payload: { studentId: preflightPair.member.userId, courseId: preflightPair.course.courseId, coachId: 0, classType: preflightPair.classType, date: '2099-01-05', startTime: '09:00', recurrence: 'weekly', occurrences: 2 } })).data;
|
|
|
if (!Array.isArray(preflight.candidates) || !Array.isArray(preflight.conflicts) || !Array.isArray(preflight.conflictIndexes) || typeof preflight.issueCount !== 'number' || preflight.availableCount + preflight.conflictCount !== preflight.candidates.length || preflight.conflictCount !== preflight.conflictIndexes.length) throw new Error('批量排课逐项预检结构或按课次去重统计无效');
|
|
|
}
|
|
|
if (!schedule.totalAppointments || !schedule.dataDateFrom || !schedule.dataDateTo) throw new Error(`历史排课总量或数据日期范围缺失:${JSON.stringify({ keys: Object.keys(schedule), totalAppointments: schedule.totalAppointments, dataDateFrom: schedule.dataDateFrom, dataDateTo: schedule.dataDateTo })}`);
|
|
|
@@ -116,6 +114,21 @@ try {
|
|
|
if (!rates.items?.some((item) => item.classType === expected.type && item.amount === expected.amount && item.durationHours === expected.hours)) throw new Error(`原系统课酬规则缺失:课型 ${expected.type}`);
|
|
|
}
|
|
|
if (rates.items?.some((item) => item.classType === 4 && item.isDefault)) throw new Error('课型4不应存在默认工资规则');
|
|
|
+ const teachingStandards = (await ops({ operation: 'ops/teaching-rules/list' })).data;
|
|
|
+ const expectedTeachingMatrix = [
|
|
|
+ ['word', 30, 15, 20], ['word', 60, 30, 40], ['trial', 60, 30, 40],
|
|
|
+ ['primary_writing', 30, 20, 25], ['middle_writing', 30, 25, 30], ['high_writing', 30, 32.5, 37.5],
|
|
|
+ ['primary_writing', 60, 40, 50], ['middle_writing', 60, 50, 60], ['high_writing', 60, 65, 75],
|
|
|
+ ['word_self_study', 60, 20, 30],
|
|
|
+ ];
|
|
|
+ const currentRule = (categoryKey, durationMinutes, deliveryMode) => teachingStandards.rules
|
|
|
+ .filter((rule) => rule.active && rule.categoryKey === categoryKey && rule.durationMinutes === durationMinutes && rule.deliveryMode === deliveryMode && rule.effectiveFrom <= today)
|
|
|
+ .sort((a, b) => b.effectiveFrom.localeCompare(a.effectiveFrom))[0];
|
|
|
+ for (const [categoryKey, durationMinutes, onlinePay, offlinePay] of expectedTeachingMatrix) {
|
|
|
+ if (currentRule(categoryKey, durationMinutes, 'online')?.teacherPay !== onlinePay || currentRule(categoryKey, durationMinutes, 'offline')?.teacherPay !== offlinePay) throw new Error(`课程工资标准不符合新价目表:${categoryKey} ${durationMinutes}分钟`);
|
|
|
+ }
|
|
|
+ const databaseStages = new Set(teachingStandards.mappings.map((item) => item.stageName));
|
|
|
+ if (!teachingStandards.mappings.every((item) => item.stageConfirmed && item.stageName) || !['小学', '初中', '高中'].every((name) => databaseStages.has(name)) || teachingStandards.mappings.some((item) => item.confirmed && !item.categoryKey)) throw new Error('历史课程没有原样返回数据库大类');
|
|
|
const payrolls = (await ops({ operation: 'ops/payroll/list', page: 1, pageSize: 20 })).data;
|
|
|
if (!Array.isArray(payrolls.items) || typeof payrolls.total !== 'number') throw new Error('工资批次未返回统一分页结构');
|
|
|
const payrollOverview = (await ops({ operation: 'ops/payroll/overview', month: '2026-08', storeId: 0, search: '', refresh: true })).data;
|
|
|
@@ -164,7 +177,8 @@ try {
|
|
|
const learningReports = (await ops({ operation: 'ops/learning-reports/list', page: 1, pageSize: 20, search: '', filters: { month: '2026-09' }, refresh: true })).data;
|
|
|
if (!Array.isArray(learningReports.items) || typeof learningReports.total !== 'number' || !learningReports.summary || !learningReports.sourceStatus) throw new Error('学习报表列表、摘要或同步状态结构无效');
|
|
|
if (learningReports.sourceStatus.state === 'not_configured') throw new Error('学习记录持续同步桥尚未配置,无法保证报表包含最新数据');
|
|
|
- if (!learningReports.items.some((item) => Number(item.generalId) === 855751) || !learningReports.items.some((item) => String(item.studyDate || '') >= '2026-09-04')) throw new Error(`学习报表没有同步旧系统 2026-09-04 样本:${JSON.stringify({ total: learningReports.total, sourceStatus: learningReports.sourceStatus, ids: learningReports.items.map((item) => item.generalId) })}`);
|
|
|
+ const historicalReport = (await ops({ operation: 'ops/learning-reports/detail', targetId: '855751' })).data;
|
|
|
+ if (Number(historicalReport.generalId) !== 855751 || !learningReports.items.some((item) => String(item.studyDate || '') >= '2026-09-04')) throw new Error(`学习报表没有同步旧系统样本或最新记录:${JSON.stringify({ total: learningReports.total, sourceStatus: learningReports.sourceStatus, historicalId: historicalReport.generalId, ids: learningReports.items.map((item) => item.generalId) })}`);
|
|
|
const studentReports = (await ops({ operation: 'ops/learning-reports/list', page: 1, pageSize: 20, search: '熊诗琦', filters: { month: '2026-09' } })).data;
|
|
|
if (!studentReports.total || studentReports.items.some((item) => !String(item.studentName || '').includes('熊诗琦'))) throw new Error(`学习报表学员姓名筛选无效:${JSON.stringify({ total: studentReports.total, names: studentReports.items.map((item) => item.studentName) })}`);
|
|
|
const coachReports = (await ops({ operation: 'ops/learning-reports/list', page: 1, pageSize: 20, search: '', filters: { month: '2026-09', coachSearch: '樊颖' } })).data;
|