Files
mgm-daylight-mcrm-api-mobile/docs/merge_daylight_dl_reservation_dedup.md
T
2026-09-08 15:12:29 +08:00

102 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Daylight 合并与 `dl_reservation_id` 去重说明
本文档说明 `BookingService` 中为减少「同一 Daylight 预订在 `bookings` 表出现多行」所做的修改及调用关系。
---
## 1. 涉及文件与入口
| 位置 | 说明 |
|------|------|
| `BookingService.java` | `mergeDaylightRecordToDatabaseInfo`、`insertDaylightBooking`、`consolidateDuplicateDlReservations` 及相关私有辅助方法 |
| `BookingController`(recordSearch 等) | 在拉取 Daylight 列表后调用 `mergeDaylightRecordToDatabaseInfo` |
---
## 2. `mergeDaylightRecordToDatabaseInfo` 的改动要点
### 2.1 预查询:按 `dlReservationId` 收集(不再依赖确认号)
**原问题**:仅当 `confirmationNo` 非空时才把 `dlReservationId` 放进 `findByDlReservationIdIn` 的入参。若接口有 `dl_reservation_id` 但尚未返回确认号,则查不到库里已有行,会误判为「不存在」而走插入,产生重复。
**现逻辑**:对本次 `result`(Daylight 返回列表)中每条记录,只要 `dlReservationId` 非空白即加入 `LinkedHashSet`,再转为 `List` 做 `findByDlReservationIdIn`。
### 2.2 合并后物理去重:`consolidateDuplicateDlReservations`
在 `updateBookingList` 执行 `bookingDao.saveAll` **之后**,对本次涉及到的每个 `dl_reservation_id` 再查库:
- 若同一 `dl_reservation_id` 仍对应 **≥2 行**(例如一条前台 `PN`、一条同步误插入的 `N`),则:
- 按评分规则选出 **keeper(主行)**,其余为 **duplicate(次行)**;
- 将次行上可对齐的字段并入主行后 **`delete` 次行**。
**主行评分** `keeperScoreForDlDedup`(分数高者优先):
- 有 `player_id`:+1000
- `approve_status == "PN"`:+100
- 有 `confirmation_no`:+10
- 有 `member`:+5
同分按 **较小 `id`** 靠前(作为 keeper)。
**不合并、不删除** `shouldSkipConsolidatingDuplicateRow`:
- 次行 `approve_status` 以 `W` 开头(候补等)
- 次行 `revision_of` 指向 keeper(待审批修订子单)
- keeper `revision_of` 指向次行
**字段合并** `mergeDuplicateBookingIntoKeeper`:
- 确认号、`resv_id`、Opera 状态、酒店/客源/房价码、`player_id`、`member` 与姓名字段、房/房型、`booking_type_id`、`chain_code`、`block_id` 等在主行为空或可从次行安全补齐时写入主行;**`last_opera_status`** 若次行有值则写入主行(与 Opera 同步一致)。
日志前缀:`[consolidateDuplicateDlReservations]`。
---
## 3. `insertDaylightBooking` 的改动要点
### 3.1 按 `dl_reservation_id` 兜底防插
在组好 `Bookings book` 并准备 `save` **之前**,若 `operaBooking.getDlReservationId()` 非空,则先 `findByDlReservationIdIn`:
- **若已存在任一行**:**不再 `save` 新单**,打日志 `[insertDaylightBooking] skip insert, existing dl_reservation_id=...`,并设置返回视图上的 `id` 等为已有记录,直接 `return operaBooking`。
避免「合并阶段漏匹配」时再次插入第二条同 `dl_reservation_id`。
### 3.2 按确认号防重(空列表才插入)
- 仅当 `confirmationNo` 非空时执行 `findByConfirmationNo`;
- **仅当查询结果为空** 时才 `bookingDao.save(book)`;
- 若已有同确认号记录,则复用其 `id`,**不重复插入**。
---
## 4. 根因归纳(与旧版对比)
| 原因 | 说明 |
|------|------|
| 预查漏掉无确认号的 `dl_reservation_id` | 已用 `LinkedHashSet` + 非空 `dlReservationId` 修复 |
| 仅合并不删重复行 | 已用 `consolidateDuplicateDlReservations` 在当次同步末尾收敛 |
| 插入路径无 `dl_reservation_id` 防重 / 确认号逻辑不当 | 已在 `insertDaylightBooking` 中补齐 |
| 数据库无唯一约束 | **仍建议在库上对非空 `dl_reservation_id` 做过滤唯一索引**(上线前清理历史重复) |
| 并发双写 | 当前以逻辑合并 + 完成后去重为主;多实例强一致可再加分布式锁或 DB 唯一约束 |
---
## 5. 运维与验证
1. **验证**:部署后对曾出现双行的会员再触发一次会走 `mergeDaylightRecordToDatabaseInfo` 的查询(如 recordSearch);观察日志中 `consolidateDuplicateDlReservations` 是否合并并删除次行。
2. **历史数据**:长期未再走合并流程的重复行,需一次性脚本或手工按业务规则保留主行、删除/归档次行。
3. **外键**:若 `delete` 次行失败(子表引用),需先迁移或删除子表记录后再删。
---
## 6. 可选后续增强(未列入当前分支必选)
- 合并匹配改为 `Strings.CI.equals`(忽略 `dl_reservation_id` 大小写),与接口/库实际存储一致。
- 对 `mergeDaylightRecordToDatabaseInfo` 增加 `@Transactional(rollbackFor = Exception.class)`,使 `saveAll` 与 `consolidateDuplicateDlReservations` 同事务回滚。
- 高并发下对同一 `dl_reservation_id` 的插入加应用层锁或依赖唯一索引 + 捕获冲突后改走更新。
---
*文档与 `daylight_1.0.0` 分支中 `BookingService` 当前实现对应;若行号随重构变化请以方法名为准。*