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:
LoveChengke committed 2026-10-02 00:24:58 +08:00
1 parent 9c678b3730
commit 7ea0e0f7c9
17 files changed
+620 -44

No files matched your search

@@ -75,6 +75,12 @@ export const reportTimeLayoutItemSchema = z.object({
export const classIslandReportSchema = z.object({
pluginVersion: z.string().trim().max(40).optional(),
classIslandVersion: z.string().trim().max(40).optional(),
/**
* 插件生成的机器码。设备创建时服务端还不知道它是谁,只能用 `pending-…` 占位,
* 因此插件每次上报都带上它,服务端据此回填,Web 端设备列表才能与教室机器对上
* (见 integrations.service 的 syncDeviceKey)。
*/
deviceKey: z.string().trim().max(64).optional(),
state: z
.object({
inClass: z.boolean().optional(),