# MDY（明道云）PlateOrder 同步：任务 / Command / 数据查询说明

## 这份文档解决什么问题

当你忘记：

- PlateOrder（开版数据表）是否有定时同步？
- 手动怎么跑同步？
- 同步的数据落到哪里、怎么查？

直接看本文档即可。

---

## 一、同步“是什么”：从明道云到哪里？

### 1) 同步目标表（本地暂存）

同步会把明道云“开版数据表”的行写入本地暂存表：

- 模型：`api_v1.models.MDYPlateOrderStaging`
- 字段：
  - `mdy_rowid`：明道云 rowid（唯一）
  - `raw`：整行原始数据（JSON）
  - `related`：可选，跨表关联数据（list[dict]，已做碾平）
  - `ctime/utime`：明道云系统时间字段（用于排序/增量）

### 2) 增量游标（checkpoint）

同步进度记录在：

- 模型：`api_v1.models.DataSync`
- 表名枚举：`DataSync.TableName.PLATE_ORDER`

Command 默认会读取/写入 checkpoint；可以通过参数关闭。

---

## 二、有没有“定时任务”（Celery Beat）？

结论：**当前没有把 PlateOrder 同步加入 `CELERY_BEAT_SCHEDULE`**。

目前 `flower/settings.py` 里看到的 mdy 定时任务只有：

- `mdy_product_sync`：每 10 分钟（`api_v1.tasks.sync_mdy_products`）
- `mdy_customer_sync`：每 5 分钟（`api_v1.tasks.sync_mdy_customers`）

但代码里确实存在 PlateOrder 的 Celery task（见下文），只是 **未配置到 beat schedule**。

补充：**与“同步”不同**，项目已新增一条“开版暂存 → 腾讯云 TIIA 图库上传”的定时任务（用于把暂存表中的开版图入库到腾讯图库），它是单独的 beat 项，详见：

- `docs/tiia_image_gallery.md` 的 “四点五、MDY 开版暂存图片上传到图库”
- `flower/settings.py`：`CELERY_BEAT_SCHEDULE['daily_mdy_plate_order_staging_tiia_image_upload']`（默认 03:20）

---

## 三、如何手动运行（推荐：management command）

### Command：`sync_mdy_plate_orders`

入口文件：

- `api_v1/management/commands/sync_mdy_plate_orders.py`

最常用用法：

- 默认增量同步（使用 DataSync checkpoint；默认抓关联表数据）：
  - `uv run python manage.py sync_mdy_plate_orders`

常用参数：

- `--page-size 100`：每页拉取条数（默认 100）
- `--max-pages 2`：本次最多处理多少页（不是最大页码）
- `--max-records 200`：本次最多处理多少条
- `--with-related / --without-related`：是否抓取跨表关联数据（默认 with-related）
- `--max-related-per-type 5`：每种关联表最多拉多少条 rowid
- `--request-interval-seconds 0.02`：每次明道云请求之间最小间隔（限流；0.02≈50qps）
- `--use-checkpoint / --skip-checkpoint`：是否读取 DataSync 游标（默认 use）
- `--update-checkpoint / --skip-checkpoint-write`：是否写入 DataSync 记录（默认写）
- `--sort-direction asc|desc`：按 ctime 升/降序抓取（默认 asc）

示例：

- 只拉主表、不拉关联（更快）：
  - `uv run python manage.py sync_mdy_plate_orders --without-related`
- 从头全量跑一段（不读 checkpoint，且跑完不写 checkpoint）：
  - `uv run python manage.py sync_mdy_plate_orders --skip-checkpoint --skip-checkpoint-write`
- 控制单次任务规模（避免跑太久）：
  - `uv run python manage.py sync_mdy_plate_orders --page-size 100 --max-pages 3 --max-records 250`

---

## 四、如何通过 Celery 运行（可选）

### Celery Task：`api_v1.tasks.sync_mdy_plate_orders`

位置：

- `api_v1/tasks.py`（task 名：`sync_mdy_plate_orders`）

说明：

- 该 task 内部调用 `api_v1.mdy_plate_order_sync.sync_mdy_plate_orders_to_staging`
- 该 task **目前不在 beat schedule**，但可以手动触发

两种触发方式（任选其一）：

1) 通过 celery call（适合已有 worker 环境）：
   - `uv run celery -A flower call api_v1.tasks.sync_mdy_plate_orders --kwargs='{"page_size":100,"max_pages":2,"with_related":true,"request_interval_seconds":0.02}'`

2) 通过 Django shell（适合本地临时验证）：
   - `uv run python manage.py shell -c "from api_v1.tasks import sync_mdy_plate_orders; r=sync_mdy_plate_orders.delay(page_size=100,max_pages=2); print(r.id)"`

> 如果你确实需要“定时同步 PlateOrder”，可以把该 task 加进 `CELERY_BEAT_SCHEDULE`；但这属于策略选择（数据量/频率/接口限流），当前项目默认没开。

---

## 五、同步后如何查询（API）

### 暂存查询接口（只读）

文档：

- `docs/api_v1_mdy_plate_order_staging.md`

接口：

- List：`GET /api/v1/mdy-plate-order-staging/?limit=20&offset=0`
- Detail：`GET /api/v1/mdy-plate-order-staging/{id}/`

说明：

- 支持 LimitOffset 分页
- 支持按“内部字段名”的 query param 过滤（对前端隐藏 JSONField 细节）

---

## 六、相关实现入口（定位用）

- 同步逻辑（写入暂存+checkpoint）：`api_v1/mdy_plate_order_sync.py`
- 拉取明道云 rows：`flower/utils/mingdaoyun/fetch.py`（`fetch_plate_orders_from_mingdaoyun`）
- 明道云客户端：`flower/utils/mingdaoyun/client.py`（当前 appKey/sign 仍是硬编码）
- 暂存查询 API：`api_v1/views/mingdaoyun/plate_order_staging.py`

---

## 七、常见报错：`duplicate key value violates ... api_mdy_plate_order_staging_pkey`

### 这到底是哪种“唯一性冲突”？

`api_mdy_plate_order_staging_pkey` 是 **主键（id）** 的约束名，不是 `mdy_rowid` 的 unique。

这类报错通常意味着：**PostgreSQL 自增序列（sequence）落后于当前表内的最大 id**（常见于 restore/copy/import 后）。

### 当前代码如何处理

`sync_mdy_plate_orders_to_staging` 在写入暂存表时，如果遇到该主键冲突，会：

- 自动重置该表的 id sequence 到 `MAX(id)`（使下一次插入从 `MAX(id)+1` 开始）
- 然后对当前 row 再重试一次写入

对应实现：`api_v1/mdy_plate_order_sync.py` 的 `_reset_mdy_plate_order_staging_id_sequence()`。

### 手动修复（可选）

如果你想一次性手动修复，可在 PostgreSQL 执行（把 sequence 名替换为实际值）：

```sql
SELECT setval(
  pg_get_serial_sequence('api_mdy_plate_order_staging', 'id'),
  COALESCE((SELECT MAX(id) FROM api_mdy_plate_order_staging), 1),
  true
);
```
