Báo cáo chuyển đổi warehouse-service

batch · batch/job-daily-warning-out-date · job

SCHEDULER@Scheduled(cron = "0 7 8 * * ?") dailyWarningOutDate

Vòng
2
Cổng G1–G6
PENDING_SONAR
Bước
sonar-check
Nhánh đích
feat/thongpd/migrate-batch2

Báo cáo sai lệch — batch/job-daily-warning-out-date — vòng 2

0. Kết luận nhanh

  • Điều kiện đạt: G1 ✅ G2 ✅ G3 ✅ G4 — G5 ✅ G6 ✅ → CHỜ KIỂM SONAR
  • Trạng thái: SẴN SÀNG CHO NGƯỜI XEM
  • Tầng rủi ro: A → người cần đọc: spec + toàn bộ diff + chạy GM-01
  • Số quy tắc: 8 · PASS 4 (R-03, R-04, R-05, R-08) · DEVIATION có chủ đích 2 (R-01 D-01, R-02 D-06/D-07) · DEVIATION KHÔNG chủ đích 0 · UNVERIFIED 2 (R-06 ngưỡng IN D-08, R-07 giờ +7/KeyHolder trên MSSQL)
  • Coverage thấp nhất: F-01 95.2% dòng / 90.0% nhánh
  • Finding logic: vòng 1 MINOR 2; vòng 2 (thu hẹp) MINOR 2 (vẫn là 2 finding cũ) — không cần xác minh
  • Nghi vấn bug code cũ (mục 3c): 4 (CAO 0) · Quyết định tự chốt cần xác nhận (mục 3d): 8
  • Chạy bằng: /mig-auto · Số vòng: 2 / lần sửa 1/3

1. Điều kiện đạt

Mã Mục tiêu Kết quả Bằng chứng
G1 mvnw verify ✅ verify/round-2/build.md (716 test)
G2 Test khớp test-plan ✅ verify/round-2/tc-map.md (14/14), oracle.md
G3 Coverage ✅ verify/round-2/coverage.md
G4 Sonar CHỜ NGƯỜI sonar-ide.md
G5 Finding CRITICAL/MAJOR ✅ verify/round-2/review-logic.md (thu hẹp, 0 CRITICAL/MAJOR)
G6 Lách cổng ✅ verify/round-2/gates.md

Không chặn nhưng cần người: Integration test CHƯA CHẠY · Golden master CHƯA CHẠY (GM-01 — SQL native, khoá Redis, KeyHolder, giờ ghi chỉ khoá bằng mock, K-13)

2. Ma trận truy vết

Quy tắc Tầng Code mới Test Golden Kết luận
R-01 A BatchExpiryWarningScheduler, application.yml TC-01 — DEVIATION có chủ đích (D-01: cờ riêng mặc định tắt)
R-02 A BatchExpiryWarningScheduler TC-02, 03 — DEVIATION có chủ đích (D-06 bỏ whitelist IP; D-07 nhánh race theo starter)
R-03 A BatchExpiryWarningService, BatchExpiryReader TC-04, 06 GM-01 PASS (giữ D-03); SQL mock
R-04 A BatchExpiryReader TC-05 GM-01 PASS; SQL mock
R-05 A BatchExpiryWarningService TC-06 — PASS
R-06 A BatchExpiryWarningService, CompanyUserRelationalAdapter TC-07, 08 GM-01 PASS (giữ D-04, D-08); UNVERIFIED ngưỡng IN
R-07 A BatchExpiryNotificationPlan, NotificationRelationalAdapter TC-09..12 GM-01 PASS (giữ D-05); UNVERIFIED giờ +7/KeyHolder trên MSSQL; F-logic-2
R-08 A BatchExpiryWarningService.warn @Transactional TC-13, 14 (+EXTRA 1, 2) — PASS

3. Người cần xem

# Vị trí Vì sao cần người xem
1 Toàn bộ diff (20 file, verify/round-2/changed-files.md) Tầng A: job ghi dữ liệu hằng ngày cho mọi công ty
2 NotificationRelationalAdapter Ghi thẳng bảng notification/notification_user của monolith bằng JDBC (D-02); giờ Timestamp.valueOf(giờ +7), KeyHolder.getKey() trên mssql-jdbc — UNVERIFIED, chạy GM-01
3 application.yml app.scheduler.batch-expiry-warning.enabled Mặc định tắt; chỉ bật khi đã tắt job bên monolith, hoặc khi hai bên chung Redis (cùng khoá job-lock-daily-warning) — khác Redis thì sinh thông báo trùng
4 F-logic-1 (MINOR) D-08 mô tả sai: code mới cũng qua Hibernate có in_clause_parameter_padding → ngưỡng lỗi IN như cũ (không phải "mới chạy được 2049–2099 công ty")
5 F-logic-2 (MINOR) Một now cho mọi dòng, lấy trước các truy vấn — giờ ghi sớm hơn cũ vài giây; TC-10 đang khoá điều này
6 G4 Chưa kiểm Sonar

3b. Phân tích luồng sau chuyển

Chi tiết: verify/round-1/flow-analysis.md (vòng 2: verify/round-2/flow-analysis.md, không đổi)

Luồng Tên Số bước KHỚP / KHÁC / CHỈ CÓ Ở CŨ / CHỈ CÓ Ở MỚI Lệch luồng đã duyệt (spec mục 6) Điểm dev cần chú ý
F-01 Cảnh báo lô sắp/đã hết hạn 17 bước: KHỚP 12 (gồm bước 3 khớp về kết quả và bước 12 khớp nhưng D-08 mô tả sai, F-logic-1) / KHÁC có chủ đích 3 (D-01, D-06, D-02) / KHÁC một phần 2 (bước 6: nhánh race theo D-07; bước 16: giờ ghi, F-logic-2) / CHỈ CÓ Ở CŨ 1 (whitelist clientCanDoSchedule, D-06 — bước 4 và 5 cùng thuộc D-06) / CHỈ CÓ Ở MỚI 0 không JDBC phải dùng DataSource @Primary để chung transaction; KeyHolder.getKey() trên mssql-jdbc; giờ +7 ghi bằng Timestamp.valueOf (UNVERIFIED); một now cho mọi dòng; phải bật cả hai cờ; dùng chung khoá Redis với monolith (khác Redis thì sinh thông báo trùng)

3c. Nghi vấn bug / sai ở code cũ (đã GIỮ nguyên khi chuyển)

B-01 Một câu IN cho mọi công ty — nhiều công ty thì cả job lỗi

  • Mức nghi ngờ: TRUNG BÌNH
  • Ở đâu: backend:repository/CompanyUserRepository.java:49, BMS:492 → CompanyUserRelationalAdapter.findByCompanyIds
  • Code cũ đang làm gì: đọc company_user của mọi công ty có lô cần cảnh báo bằng một câu IN, không chia lô.
  • Ví dụ cụ thể: 2500 công ty có lô sắp/đã hết hạn → vượt 2100 tham số SQL Server → lỗi → rollback, không công ty nào nhận thông báo, ngày nào cũng vậy. Có padding lũy thừa 2 nên từ khoảng 2049 công ty đã lỗi (cả cũ và mới — F-logic-1).
  • Vì sao nghi là sai: số khách hàng tăng thì job lặng lẽ ngừng hoạt động.
  • Ảnh hưởng nếu đúng là bug: mất toàn bộ cảnh báo hết hạn.
  • Đã xử lý khi chuyển: GIỮ. Test khoá: TC-08 (CompanyUserRelationalAdapterTest#findByCompanyIds).
  • Nếu muốn sửa: chia companyIds thành lô ≤ 1000 trong adapter (K-27); đổi TC-08.
  • Liên quan: D-08, R-06

B-02 Gửi cho cả user đã ngừng hoạt động

  • Mức nghi ngờ: THẤP
  • Ở đâu: backend:repository/CompanyUserRepository.java:49 → CompanyUserRelationalAdapter
  • Code cũ đang làm gì: lấy mọi dòng company_user của công ty, không lọc status.
  • Ví dụ cụ thể: công ty 10 có 3 user, 1 đã nghỉ → cả 3 nhận thông báo mỗi loại.
  • Vì sao nghi là sai: thông báo cho người không còn làm việc.
  • Ảnh hưởng nếu đúng là bug: dữ liệu thừa, không ai đọc.
  • Đã xử lý khi chuyển: GIỮ. Test khoá: TC-08, TC-07.
  • Nếu muốn sửa: thêm điều kiện status hoạt động vào SQL adapter; đổi TC-08.
  • Liên quan: D-04, R-06

B-03 Thông báo lặp lại mỗi ngày, mỗi user một bản ghi

  • Mức nghi ngờ: THẤP
  • Ở đâu: backend:service/impl/NotificationManagementServiceImpl.java:166-188 → BatchExpiryNotificationPlan, NotificationRelationalAdapter
  • Code cũ đang làm gì: mỗi ngày tạo lại thông báo, không chống trùng; mỗi user một dòng notification riêng.
  • Ví dụ cụ thể: lô hết hạn còn tồn 30 ngày, công ty 50 user → mỗi user 30 thông báo "đã hết hạn"; mỗi ngày 50 dòng notification giống nhau.
  • Vì sao nghi là sai: phình bảng, làm phiền.
  • Ảnh hưởng nếu đúng là bug: hiệu năng bảng notification, trải nghiệm.
  • Đã xử lý khi chuyển: GIỮ. Test khoá: TC-09, TC-12.
  • Nếu muốn sửa: cần quyết định nghiệp vụ (một notification + nhiều notification_user, hoặc chống trùng theo ngày).
  • Liên quan: D-05, R-07

B-04 "Hôm nay" theo giờ JVM nhưng mốc cảnh báo theo giờ DB

  • Mức nghi ngờ: THẤP
  • Ở đâu: backend:repository/BatchesRepository.java:81-122 → BatchExpiryReader, BatchExpiryWarningService
  • Code cũ đang làm gì: ?1 = today (JVM) so với exp_date; điều kiện mốc cảnh báo dùng GETDATE() của DB.
  • Ví dụ cụ thể: JVM +7, DB UTC; lô HSD 2026-11-10 08:00, cảnh báo 4 ngày (mốc 06/11 08:00). Job 06/11 08:07 (+7), GETDATE() = 01:07 → chưa qua mốc, tới 07/11 mới cảnh báo; DB +7 thì cảnh báo ngay 06/11.
  • Vì sao nghi là sai: hai nguồn giờ; kết quả phụ thuộc múi giờ server DB.
  • Ảnh hưởng nếu đúng là bug: cảnh báo trễ một ngày.
  • Đã xử lý khi chuyển: GIỮ (giữ GETDATE() trong SQL). Test khoá: TC-04, TC-06.
  • Nếu muốn sửa: truyền mốc thời gian từ Clock thay GETDATE(); đổi TC-04.
  • Liên quan: D-03, R-03

3d. Quyết định AI tự chốt và thay đổi tự động (cần người xác nhận)

# Nguồn AI đã làm gì Khác code cũ ở điểm nào (input → cũ / mới) Người cần xác nhận gì
1 D-01 (ADR BẮT KHÁC) Cờ riêng app.scheduler.batch-expiry-warning.enabled mặc định false deploy với SCHEDULER_ENABLED=true → cũ chạy / mới không chạy cho tới khi bật cờ riêng Khi nào bật; chung Redis với monolith hay tắt job bên monolith
2 D-02 (PHỤ THUỘC NGOÀI) Port NotificationPort, CompanyUserPort + adapter SQL native ghi/đọc bảng monolith cùng cột/giá trị; thứ tự id giữa các công ty có thể khác (HashMap → LinkedHashMap) Chấp nhận ghi thẳng bảng của context khác
3 D-06 (PHỤ THUỘC NGOÀI) Bỏ whitelist IP clientCanDoSchedule node không có trong whitelist → cũ không chạy / mới tranh khoá Chấp nhận
4 D-07 (MƠ HỒ) Không nhả khoá; nhánh race (SETNX hỏng + GET null) theo starter khoá hết hạn đúng lúc → cũ chạy / mới không chạy Chấp nhận (UNVERIFIED)
5 F-logic-1 (MINOR) — D-08 mô tả sai Không sửa code; không sửa decisions.md (sau bước kiểm spec không được sửa) không khác: cả hai cùng padding IN, cùng ngưỡng lỗi Sửa câu cuối "Ví dụ cụ thể" của D-08 khi rà soát
6 F-logic-2 (MINOR) Một now từ Clock đầu warn() cho mọi dòng create_time cũ ≈ sau các truy vấn, mỗi dòng khác vài ms / mới cùng một giá trị, sớm hơn vài giây Chấp nhận, hoặc chỉ thị lấy now ngay trước createAll
7 spec-review Tầng A: 2 lần review (lần 1 có CRITICAL F-spec-1 giờ ghi +7); đổi R-01, R-03, R-04, R-06, R-07, D-01, D-03, D-08 (mới), mục 3/5/7 — Đọc kỹ R-07 (giờ +7) và D-08
8 Hạ tầng NotificationRelationalAdapter tự tạo NamedParameterJdbcTemplate từ DataSource @Primary (chung transaction JPA) — Xem diff

4. Finding đã xác nhận

Không có CRITICAL/MAJOR. MINOR F-logic-1, F-logic-2 — ở mục 3/3d.

5. Finding bị loại

Không có.

6. Xử lý của người

Vấn đề Cổng Chỉ thị
Chưa kiểm Sonar G4

7. So với vòng trước

Vấn đề Vòng trước Vòng này
TC-10 nới (G2) MỚI ĐÃ HẾT
F-logic-1 (MINOR) D-08 mô tả sai MỚI CÒN
F-logic-2 (MINOR) một now MỚI CÒN

8. Thay đổi trong repo đích

20 file (verify/round-2/changed-files.md): mới scheduler, service, plan, port company/notification, BatchExpiryReader, 2 adapter + 6 test; sửa BatchRepository, BatchRelationalRepository, application.yml.

9. Đề xuất bổ sung kinh nghiệm

Đề xuất Đưa vào đâu
Job ghi bảng monolith bằng JDBC: Timestamp.valueOf(giờ treo tường +7) thay cho jdbc.time_zone của Hibernate; NamedParameterJdbcTemplate tạo từ DataSource @Primary để chung transaction target-cheatsheet.md; lessons.md 1b
Repo đích cũng bật in_clause_parameter_padding → native query qua EntityManager cũng độn IN như monolith legacy-facts.md / target-cheatsheet.md
Test service: assert toàn bộ danh sách (containsExactly) thay vì hasSize + vài trường — G2 bắt "nới" lessons.md 1c

Nguồn của trang này

  • Báo cáo sai lệch migrations/batch/job-daily-warning-out-date/report.md vòng 2 · sha256 d70f41bc5df2

v1 dựng 2026-11-09 14:46 · base commit đích 661efe9b8c