feat(schedules,classisland,server,plugin,shared): 课表行加来源字段,插件上报可真正同步删除
课表行新增 Schedule.source(manual / classisland):插件每次全量上报时,服务端 只清理「source=classisland 且本次未再上报」的行。原先 merge 只做 upsert,老师在 ClassIsland 里删掉或挪走一节,服务端会永远留着那行幽灵课;现在能真正同步下来, 而老师在小助手这边手排的课(manual)一律不删。 配套改动: - 插件上报时带回 machineKey,服务端回填到设备记录(避开 [classId, deviceKey] 唯一约束); - 新增迁移 20260930120000_add_schedule_source,存量数据保守地按 manual 处理; - verify:classisland 补「幽灵行清理」「手排课不被误删」用例。 另:.gitignore 补上 .zcode/ 与 .zcodeignore(AI 会话产物,不属于产品代码)。
This commit is contained in:
1 parent
9c678b3730
commit
7ea0e0f7c9
17 files changed
+620
-44
No files matched your search
@@ -422,6 +422,9 @@ export async function applyReport(
|
||||
const now = new Date();
|
||||
const week = resolveCurrentWeek(env.termStartDate, now);
|
||||
|
||||
// 0) 机器码回填(设备创建时只能用 pending-… 占位,首次上报才知道它到底是哪台机器)
|
||||
if (input.deviceKey) await syncDeviceKey(device, input.deviceKey);
|
||||
|
||||
// 1) 状态快照(无论课表是否上报都更新,Web 端据此显示"现在上什么课")
|
||||
const state = input.state;
|
||||
await prisma.integrationDevice.update({
|
||||
@@ -518,6 +521,39 @@ export async function applyReport(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 把插件上报的机器码回填到设备记录。
|
||||
*
|
||||
* `IntegrationDevice` 上有 `@@unique([classId, deviceKey])`,因此要避开两个坑:
|
||||
* 1) 同一台机器在这个班已有另一条设备记录(例如管理员重复建过设备)时直接写会撞唯一约束 → 500,
|
||||
* 这里改为记 warning 并保留原值;
|
||||
* 2) 值没变时不必白写一次库。
|
||||
*/
|
||||
async function syncDeviceKey(device: DeviceContext, reportedKey: string): Promise<void> {
|
||||
const key = reportedKey.trim();
|
||||
if (!key) return;
|
||||
|
||||
const current = await prisma.integrationDevice.findUnique({
|
||||
where: { id: device.deviceId },
|
||||
select: { deviceKey: true },
|
||||
});
|
||||
if (!current || current.deviceKey === key) return;
|
||||
|
||||
const taken = await prisma.integrationDevice.findFirst({
|
||||
where: { classId: device.classId, deviceKey: key, id: { not: device.deviceId } },
|
||||
select: { name: true },
|
||||
});
|
||||
if (taken) {
|
||||
logger.warn(
|
||||
`ClassIsland 上报的机器码「${key}」已被本班设备「${taken.name}」占用,本次不回填(设备=${device.name})`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await prisma.integrationDevice.update({ where: { id: device.deviceId }, data: { deviceKey: key } });
|
||||
logger.info(`ClassIsland 设备机器码已回填:${device.name} -> ${key}`);
|
||||
}
|
||||
|
||||
/** 该设备/该班级最近一条尚未确认、未过期的下发提醒 */
|
||||
async function pendingNotificationFor(
|
||||
deviceId: string,
|
||||
|
||||
Reference in new issue
Block a user