Báo cáo sai lệch — batch/job-daily-update-status — vòng 1
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: 6 · PASS 3 (R-03, R-04, R-05) · DEVIATION có chủ đích 3 (R-01 D-01, R-02 D-02/D-03, R-06 D-09/D-10) · DEVIATION KHÔNG chủ đích 0 · UNVERIFIED 1 (R-05 cột UPDATE thực tế, K-13)
- Coverage thấp nhất: F-01 92.5% dòng / 100% nhánh
- Finding logic: CRITICAL 0 · MAJOR 0 · MINOR 2 (1
nghi-bug-cu) — không cần xác minh
- Nghi vấn bug code cũ (mục 3c): 4 (CAO 1) · Quyết định tự chốt cần xác nhận (mục 3d): 9
- Chạy bằng:
/mig-auto · Số vòng: 1 / lần sửa 0/3
1. Điều kiện đạt
| Mã |
Mục tiêu |
Kết quả |
Bằng chứng |
| G1 |
mvnw verify |
✅ |
verify/round-1/build.md (729 test) |
| G2 |
Test khớp test-plan |
✅ |
verify/round-1/tc-map.md (13/13), oracle.md |
| G3 |
Coverage |
✅ |
verify/round-1/coverage.md |
| G4 |
Sonar |
CHỜ NGƯỜI |
sonar-ide.md |
| G5 |
Finding CRITICAL/MAJOR |
✅ |
verify/round-1/review-logic.md (2 MINOR) |
| G6 |
Lách cổng |
✅ |
verify/round-1/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 — câu UPDATE @DynamicUpdate, giờ ghi, khoá Redis 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 |
BatchStatusUpdateScheduler, application.yml |
TC-01 |
— |
DEVIATION có chủ đích (D-01) |
| R-02 |
A |
BatchStatusUpdateScheduler |
TC-02, 03 |
— |
DEVIATION có chủ đích (D-02, D-03); log khác (F-logic-1) |
| R-03 |
A |
BatchStatusWriter.findWithExpiryDate |
TC-11 |
GM-01 |
PASS (giữ D-05) |
| R-04 |
A |
BatchStatusUpdateService, BatchStatusPolicy, BatchExpiryWarning |
TC-05, 06, 07 |
GM-01 |
PASS (giữ D-04, D-08) |
| R-05 |
A |
BatchStatusUpdateService, BatchStatusWriter.updateStatuses |
TC-05, 08, 09, 12, 13 |
GM-01 |
PASS (giữ D-07; D-06); UNVERIFIED cột UPDATE |
| R-06 |
A |
service @Transactional, try/catch ở scheduler |
TC-04, 06, 10 |
— |
DEVIATION có chủ đích (D-09, D-10) |
3. Người cần xem
| # |
Vị trí |
Vì sao cần người xem |
| 1 |
Toàn bộ diff (11 file, verify/round-1/changed-files.md) |
Tầng A: job ghi batches của mọi công ty mỗi 4 giờ |
| 2 |
B-01 (CAO) — lô nhập kho thiếu cấu hình cảnh báo |
Cũ và mới đều NPE, không lô nào được cập nhật; cần kiểm DB prod trước khi bật job |
| 3 |
BatchStatusWriter:46-52 |
Tự làm audit (updater 0, update_time) và làm sạch chuỗi thay listener JPA của monolith; cột UPDATE thật chưa chạy MSSQL (GM-01) |
| 4 |
jdbc.time_zone |
Chỉ khai ở profile dev/uat/prod của repo đích — profile khác đọc/ghi giờ khác (ghi chú reviewer) |
| 5 |
F-logic-1 (MINOR) |
Log "Another node ( {} )…" in tên khoá thay vì clientId node giữ khoá — DistributedLockManager.acquireLock của starter chỉ trả boolean |
| 6 |
Job BatchExpiryWarningScheduler (unit batch/job-daily-warning-out-date) |
Chưa bọc try/catch như rule docs/rules/performance.md:109-111 mà unit này áp dụng (D-10) — người quyết có thống nhất không |
| 7 |
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
| 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ập nhật trạng thái hạn dùng của lô |
15 bước: KHỚP 8 / KHÁC 7 (6 có chủ đích D-01, D-02, D-03, D-06, D-09, D-10; 1 F-logic-1 — bước 4 vừa theo D-03, vừa có dòng log lệch) / CHỈ CÓ Ở CŨ 0 / CHỈ CÓ Ở MỚI 0 |
không |
BatchStatusWriter:46-52 tự làm audit và làm sạch chuỗi (cột UPDATE chưa xác minh trên MSSQL); jdbc.time_zone chỉ khai ở profile dev/uat/prod; NPE có chủ đích theo D-04 ở BatchExpiryWarning; khoá nằm ngoài transaction và try/catch nằm ngoài proxy ở BatchStatusUpdateScheduler:47-55 |
3c. Nghi vấn bug / sai ở code cũ (đã GIỮ nguyên khi chuyển)
B-01 Lô nhập kho thiếu cấu hình cảnh báo làm job không bao giờ cập nhật được
- Mức nghi ngờ: CAO
- Ở đâu:
backend:service/impl/BatchesManagementServiceImpl.java:415-429,551-568; nguồn dữ liệu backend:service/impl/RsInoutWardManagementServiceImpl.java:8849-8869,9486-9503 → BatchStatusUpdateService, BatchExpiryWarning
- Code cũ đang làm gì: tính mốc cảnh báo bằng
switch trên đơn vị cảnh báo kiểu Integer; đơn vị NULL → NullPointerException, cả job rollback. Nhập kho Excel/XML tạo lô có HSD nhưng không đặt đơn vị/giá trị cảnh báo.
- Ví dụ cụ thể: chỉ cần 1 lô nhập từ Excel (id 77, HSD 01/12/2026, đơn vị NULL) ở bất kỳ công ty nào → mỗi 4 giờ job lỗi, không lô nào của mọi công ty đổi trạng thái; lô hết hạn hôm qua vẫn hiện "còn hạn".
- Vì sao nghi là sai: một dòng dữ liệu hợp lệ theo đường nhập kho chặn toàn hệ thống; prod nhiều khả năng đang hỏng mọi lượt.
- Ảnh hưởng nếu đúng là bug: trạng thái lô (lọc "đã hết hạn"/"sắp hết hạn" ở các màn) sai cho mọi khách hàng.
- Đã xử lý khi chuyển: GIỮ. Test khoá:
TC-06 (BatchStatusUpdateServiceTest#importedBatchWithoutWarning), TC-07.
- Nếu muốn sửa: coi đơn vị/giá trị NULL như
Period.ZERO trong BatchExpiryWarning.period() (hoặc bỏ qua dòng lỗi); đổi TC-06, TC-07. Kiểm trước: SELECT COUNT(*) FROM batches WHERE exp_date IS NOT NULL AND expiry_warning_unit IS NULL.
- Liên quan: D-04, R-04, R-06
B-02 Nạp mọi lô của mọi công ty vào bộ nhớ mỗi 4 giờ
- Mức nghi ngờ: THẤP
- Ở đâu:
backend:repository/BatchesRepository.java:124, BMS:414 → BatchStatusWriter.findWithExpiryDate
- Code cũ đang làm gì: nạp đủ entity mọi lô có HSD, không phân trang, không lọc công ty/gói.
- Ví dụ cụ thể: 2 triệu lô → 2 triệu entity trong một transaction; có thể vượt 10 phút khoá.
- Vì sao nghi là sai: hiệu năng tăng theo dữ liệu.
- Ảnh hưởng nếu đúng là bug: chậm, tốn bộ nhớ; lượt chồng lượt khi khác node.
- Đã xử lý khi chuyển: GIỮ. Test khoá:
TC-11.
- Nếu muốn sửa: UPDATE set-based theo SQL hoặc chia trang; ảnh hưởng audit/làm sạch chuỗi (B-03).
- Liên quan: D-05, R-03
B-03 Job trạng thái sửa luôn chuỗi code/lot_no/normalized_name
- Mức nghi ngờ: THẤP
- Ở đâu:
backend:domain/Batches.java:13-16 (CleanTextListener + @DynamicUpdate) → BatchStatusWriter.updateStatuses
- Code cũ đang làm gì: dòng đổi status bị listener làm sạch chuỗi, cột chuỗi đổi được ghi theo.
- Ví dụ cụ thể:
lot_no = "L 01" (2 dấu cách) sang ngày hết hạn → ghi status = 0, lot_no = 'L 01'; lô cùng chuỗi nhưng status không đổi giữ "L 01".
- Vì sao nghi là sai: job trạng thái sửa dữ liệu văn bản ngoài ý muốn.
- Ảnh hưởng nếu đúng là bug: dữ liệu chuỗi đổi không có người sửa; kiểm trùng số lô có thể khác.
- Đã xử lý khi chuyển: GIỮ. Test khoá:
TC-12.
- Nếu muốn sửa: bỏ 3 dòng
LegacyCleanText trong BatchStatusWriter; đổi TC-12.
- Liên quan: D-07, R-05
B-04 Job ghi đè trạng thái tính từ dữ liệu cũ khi người dùng sửa lô giữa chừng
- Mức nghi ngờ: THẤP
- Ở đâu:
BMS:414-429 → BatchStatusUpdateService, BatchStatusWriter (F-logic-2)
- Code cũ đang làm gì: nạp đầu transaction, ghi lúc commit, không
@Version/khoá dòng.
- Ví dụ cụ thể: 04:25:00 job nạp lô 50 (HSD 04/11/2026, status 1); 04:25:10 người dùng sửa HSD thành 01/06/2027 (API lưu status 1); 04:25:30 job commit status 0 → lô hạn 2027 hiện "đã hết hạn" tới lượt 08:25.
- Vì sao nghi là sai: mất cập nhật (lost update).
- Ảnh hưởng nếu đúng là bug: sai tạm thời ≤ 4 giờ, tự khỏi.
- Đã xử lý khi chuyển: GIỮ (không có D-xx — phát hiện ở review). Test khoá: không (cần chạy song song thật).
- Nếu muốn sửa: UPDATE có điều kiện
exp_date/cấu hình chưa đổi, hoặc khoá lạc quan.
- Liên quan: R-05, R-06
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-status-update.enabled mặc định false |
SCHEDULER_ENABLED=true → cũ chạy / mới không chạy tới khi bật |
Khi nào bật; chung Redis hay tắt job monolith |
| 2 |
D-02 (PHỤ THUỘC NGOÀI) |
Bỏ whitelist clientCanDoSchedule |
node ngoài whitelist → cũ không chạy / mới tranh khoá |
Chấp nhận |
| 3 |
D-03 (MƠ HỒ) |
Không nhả khoá; nhánh race theo starter |
khoá hết hạn đúng lúc → cũ chạy / mới không |
Chấp nhận (UNVERIFIED) |
| 4 |
D-06 (MƠ HỒ) |
update_time = một now lấy sau khi tính xong |
cũ: thời điểm flush từng dòng (khác vài ms) / mới: cùng giá trị |
Chấp nhận |
| 5 |
D-08 (MƠ HỒ) |
today, trừ tháng/năm theo múi JVM như cũ |
— |
Múi JVM prod |
| 6 |
D-09 (LỖI KỸ THUẬT) |
Khoá Redis ngoài transaction |
DB lỗi lúc bắt đầu → cũ: không đặt khoá, node khác chạy / mới: giữ khoá 10 phút, lượt đó không node nào chạy |
Chấp nhận |
| 7 |
D-10 (ADR BẮT KHÁC) |
try/catch RuntimeException ở scheduler (ngoài transaction) |
chỉ khác dòng log |
Chấp nhận; cân nhắc áp cho BatchExpiryWarningScheduler |
| 8 |
F-logic-1 (MINOR) |
Log in tên khoá thay vì clientId node giữ khoá |
chỉ khác log (starter không trả owner) |
Chấp nhận |
| 9 |
spec-review |
Tầng A, lần 1 không có CRITICAL → không chạy lần 2; sửa theo MAJOR F-spec-1: D-04 (mức CAO), R-04; MINOR: R-02, R-06, D-01, D-02, D-03, D-09, D-10, mục 5/6 |
— |
Đọc kỹ D-04, R-06, mục 5 |
4. Finding đã xác nhận
Không có CRITICAL/MAJOR. MINOR: F-logic-1 (mục 3d #8), F-logic-2 nghi-bug-cu (mục 3c B-04).
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òng đầu.
8. Thay đổi trong repo đích
11 file (verify/round-1/changed-files.md): mới scheduler, service, BatchStatusCandidate, BatchStatusChange, BatchStatusWriter + 3 test; sửa BatchRepository, BatchRelationalRepository, application.yml.
9. Đề xuất bổ sung kinh nghiệm
| Đề xuất |
Đưa vào đâu |
Nguồn dữ liệu của bảng không chỉ là API đang chuyển: lần mọi chỗ new <Entity>()/save (import Excel/XML, ETL) trước khi đánh giá "dữ liệu NULL hiếm" (F-spec-1) |
lessons.md 1a |
@Transactional cấp class + method @Scheduled public → transaction bao cả bước khoá Redis; mới đặt khoá ngoài transaction cần D-xx |
legacy-facts.md mục 4 |
Rule docs/rules/performance.md:109-111: method @Scheduled phải try/catch — đặt ngoài service @Transactional |
target-cheatsheet.md |
DistributedLockManager.acquireLock chỉ trả boolean → không log được owner như monolith |
target-cheatsheet.md |