## [ERR-20260427-010] series/templates/outlook 创建会议绕过 fire-and-forget 钩子

**日期**: 2026-04-27
**类别**: 架构 / 副作用钩子
**严重度**: 高（会议系列 / 模板 / Outlook 同步路径建的会议都不会自动标 PTO）

### 问题描述
PTO 自动标记钩子只在 `MeetingsService.create()` 和 `MeetingsService.update()` 里调了 `applyForMeetingFireAndForget`。但项目里**还有 3 个 service 直接 `prisma.meeting.create/update`**：
- `series.service.ts`（会议系列）
- `templates.service.ts`（会议模板）
- `outlook-sync.service.ts`（Outlook 日历同步）

这些路径建的会议**完全绕过 PTO 标记钩子**。结果：你建的 10 个测试会议（系列）没一个自动标 Aaron PTO。

### 修复（后置补救方案）
不在每个 prisma.meeting.create/update 都加钩子（脆弱、易漏），改在 **PTO sync 完成后**扫一遍：
```ts
// adp-pto-sync.service.ts 末尾
const meetingsToMark = await this.prisma.meeting.findMany({
  where: {
    status: { not: 'CANCELLED' },
    startTime: { lt: windowEnd },
    endTime:   { gt: windowStart },
    requiredAttendees: {
      some: {
        user: {
          adpAoid: { not: null },
          adpPtoSchedules: {
            some: { startTime: { lt: windowEnd }, endTime: { gt: windowStart } },
          },
        },
      },
    },
  },
  select: { id: true },
});
for (const m of meetingsToMark) {
  this.meetingPtoMarking.applyForMeetingFireAndForget(m.id);
}
```

### 为什么后置补救比前置防御好
- 前置（每个 service 都加钩子）：**易漏**（未来又加新 service 又会忘），分散
- 后置（一处扫描）：**集中**，覆盖所有过去/未来路径，PTO sync 本来就是定期跑的

但前置 + 后置同时做最稳。当前先做后置（一处改动 cover 全场景）。

### 启示
- "副作用钩子放在数据写入端"虽然直观，但需要写入端纪律性。复杂项目里多个 service 写同一张表是常态，钩子很容易被新代码绕过
- **倒过来思考**：当 X 数据变化要触发 Y 副作用时，能不能在 X 端做"**完整扫描重建**"而不是"事件触发"？牺牲一点性能换正确性，特别适合定期同步类任务
- 设计审查时要 grep 所有 `prisma.<table>.create` 调用点，看副作用钩子是否真的覆盖

---
