# Miniapp Operation Report Sync Design

## Goal

解决“生产执行 · 工序报工”每次刷新都要等待十几秒的问题。页面刷新只负责查看小程序报工列表，ERP 同步改成显式按钮触发；同步范围由系统固定为“从上次同步成功时间起自动补齐”，不允许用户手工选择同步时间，避免漏同步。

## Problem

当前前端进入页面或点击刷新时，请求 `/production/miniapp-operation-reports` 会带上 `sync_to_erp=true` 和 `limit=800`。后端接口默认也会执行同步逻辑，对每条小程序报工逐条匹配生产台账或旧工单、工序、员工、ERP 报工记录，并更新累计值。数据库在远程 MySQL 上，逐条查询和写入会被网络往返延迟放大，所以一次刷新可能需要十几秒。

## Business Rules

- `刷新列表` 只读取小程序报工数据，使用页面日期筛选范围，不能创建或更新 ERP 报工。
- `同步到 ERP` 不使用页面日期筛选范围。
- 同步范围固定为“上次同步成功时间之后，到本次同步开始时间止”。
- 如果没有上次同步成功时间，首次同步从现有小程序报工数据中最早的记录开始。
- 同步成功后更新上次同步成功时间。
- 同步失败时不更新上次同步成功时间，下次点击同步继续从旧时间补齐。
- 同步按钮在同步过程中显示加载状态并禁用，防止用户连续点击造成重复请求。
- 同一条小程序报工重复同步必须幂等，不能重复累计 ERP 报工数量。
- 页面日期筛选只影响“看哪些数据”，绝不影响“同步哪些数据”。

## Backend Design

新增一个明确的同步接口：

```text
POST /production/miniapp-operation-reports/sync
```

该接口不接受用户传入的开始日期和结束日期。服务端读取系统配置中的上次同步成功时间，计算本次同步窗口：

```text
sync_from = 上次同步成功时间，如果不存在则为空
sync_to = 当前服务端时间
```

查询范围使用小程序报工的更新时间或创建时间，保证历史日期的补报、修改、驳回也能被纳入同步。接口循环调用现有 `_sync_miniapp_item_to_erp()`，复用当前 ERP 报工幂等差额逻辑。同步全部成功后，更新上次同步成功时间为 `sync_to`。

同步状态存储复用 `sys_system_config`：

```text
config_code: MINIAPP_OPERATION_REPORT_LAST_SYNCED_AT
config_name: 小程序报工上次同步成功时间
config_value: 2026-06-11 10:30:00
```

为了降低重复请求风险，同步接口在更新配置前对该配置行加数据库锁。前端禁用按钮解决正常用户重复点击，后端锁解决异常并发请求。

## Frontend Design

工序报工页面顶部保留日期筛选和刷新按钮，并新增同步区：

- `刷新列表`：请求 `/production/miniapp-operation-reports?sync_to_erp=false`，只读加载当前日期范围内的小程序报工。
- `同步到 ERP`：请求 `POST /production/miniapp-operation-reports/sync`，按钮进入 `同步中...` 状态并禁用。
- 同步完成后自动调用 `刷新列表`。
- 页面显示上次同步成功时间。
- 成功提示展示本次同步范围和结果数量。
- 失败提示说明“同步失败，上次同步成功时间未更新，可稍后重试”。

## Response Shape

同步接口返回：

```json
{
  "sync_from": "2026-06-01 10:00:00",
  "sync_to": "2026-06-11 15:30:00",
  "last_synced_at": "2026-06-11 15:30:00",
  "total": 120,
  "synced": 115,
  "skipped": 5,
  "failed": 0
}
```

`skipped` 表示小程序报工无法匹配有效生产台账、旧工单、有效工序，或没有有效数量。`failed` 初版只在单条同步被捕获为可恢复失败时使用；如果发生数据库级错误，接口直接失败并不更新同步时间。

## Error Handling

- 同步过程中出现数据库异常时，事务回滚，不更新 `MINIAPP_OPERATION_REPORT_LAST_SYNCED_AT`。
- 如果没有需要同步的数据，接口返回 `total=0`，并仍可更新上次同步成功时间为本次 `sync_to`，表示系统已经确认这一段时间没有新变化。
- 如果小程序报工被驳回，沿用现有 `_sync_miniapp_item_to_erp()` 对已同步报工的解绑逻辑。
- 前端同步失败时解除按钮禁用，并展示错误提示。

## Verification

- 后端测试覆盖列表接口 `sync_to_erp=false` 时不写 ERP。
- 后端测试覆盖同步接口从上次同步成功时间自动补齐，且不接受前端筛选范围。
- 后端测试覆盖同步成功后更新上次同步成功时间，失败时不更新。
- 前端静态测试覆盖列表刷新传 `sync_to_erp=false`。
- 前端静态测试覆盖同步按钮调用独立同步接口、同步中禁用、同步完成后刷新列表。
- 运行后端相关测试、前端静态脚本和前端构建。
