Báo cáo sai lệch — warehouse/get-with-paging — vòng 2
0. Kết luận nhanh
- Điều kiện đạt:
G1 ✅ G2 ✅ G3 ✅ G4 — G5 ✅ G6 ✅→ CHỜ KIỂM SONAR (máy đạt hết, chờ người kiểm G4) - Trạng thái: SẴN SÀNG CHO NGƯỜI XEM — có 1 finding MAJOR UNSURE (key
datamất khi danh sách rỗng) cần người chọn cách, xem mục 3d #1 - Tầng rủi ro: A (R-02 phân quyền; còn lại C chỉ đọc) → người cần đọc: spec + báo cáo + toàn bộ diff
- Số quy tắc: 11 · PASS 5 · DEVIATION có chủ đích 4 · DEVIATION KHÔNG chủ đích 0 · UNVERIFIED 2
- Coverage thấp nhất: F-01 97,9% dòng / 91,7% nhánh
- Finding logic: CRITICAL 0 · MAJOR 0 CONFIRMED còn mở (1 đã sửa) · MINOR 3 · UNSURE 1 (MAJOR)
- 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): 11
- Chạy bằng:
/mig-auto(spec dospec-reviewerkiểm, chưa có người duyệt) · Số vòng: 2 verify / 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 |
✅ (627 test, 0 lỗi) | verify/round-2/build.md |
| G2 | Test khớp test-plan, mọi R-xx có test | ✅ (24/24 TC, 11/11 R; TC-02 khác hình thức annotation — mục 3 #3) | verify/round-2/oracle.md |
| G3 | Coverage mỗi luồng | ✅ | verify/round-2/coverage.md |
| G4 | 0 issue Sonar | CHỜ NGƯỜI | sonar-ide.md |
| G5 | 0 CRITICAL/MAJOR CONFIRMED | ✅ (F-logic-1a đã sửa; F-logic-1b UNSURE không tính) | verify/round-2/review-logic.md, verification.md |
| G6 | Không 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)
Coverage theo luồng
| Luồng | Tên | % dòng | % nhánh |
|---|---|---|---|
| F-01 | Danh sách kho theo trang | 97,9% | 91,7% |
2. Ma trận truy vết
| Quy tắc | Tầng | Code mới | Test | Golden | Kết luận |
|---|---|---|---|---|---|
| R-01 | C | WarehouseController.getWithPaging, WarehousePageFilterRequest |
TC-01, TC-02, TC-20 | — | PASS (bind bộ lọc qua record — mục 3 #3; body 400 khác — D-02) |
| R-02 | A | WarehouseQueryService.getWarehousePage → CurrentCompanyService |
TC-03, TC-04 | — | DEVIATION (có chủ đích, D-01) |
| R-03 | C | WarehouseQueryService.keywordPattern, WarehouseRelationalRepository.pageWhere |
TC-05..TC-08, TC-16, TC-17 | GM-01 | PASS |
| R-04 | C | WarehouseRelationalRepository.pageWhere |
TC-09, TC-16, TC-17 | — | PASS |
| R-05 | C | WarehouseRelationalRepository.findPage/pageTotal |
TC-15, TC-16, TC-19, TC-20 | GM-01 | UNVERIFIED (SQL thật, thứ tự không ORDER BY — D-05, K-13) |
| R-06 | C | toListItem/toBoolean, WarehouseListItemResponse (@JsonInclude(ALWAYS)) |
TC-01, TC-01-EXTRA-1, TC-18 | — | PASS |
| R-07 | C | WarehouseQueryService, InventoryRelationalRepository.countActiveProductsByWarehouses |
TC-10..TC-12, TC-22 | GM-01 | UNVERIFIED (SQL thật — K-13) |
| R-08 | C | WarehouseController (count, SUCCESS_GET_LIST) |
TC-01, TC-14 | — | PASS — trừ data rỗng mất key (F-logic-1b UNSURE, 3d #1) |
| R-09 | C | @Transactional không readOnly |
TC-13 | — | DEVIATION (có chủ đích, D-06 — giữ đọc DB chính) |
| R-10 | C | (không chuyển) | TC-24 | — | DEVIATION (có chủ đích, D-07) |
| R-11 | C | bọc PersistenceException ở 2 adapter |
TC-20, TC-21, TC-23 | — | PASS (mã giữ, 417→500 — D-03); lỗi ép kiểu khi map dòng chưa bọc (F-logic-3 MINOR) |
3. Người cần xem
| # | Vị trí | Vì sao cần người xem |
|---|---|---|
| 1 | Toàn bộ verify/round-2/changes.diff |
Tầng A (R-02) |
| 2 | WarehouseController.getWithPaging + starter-model BaseResponseDTO |
F-logic-1b UNSURE MAJOR: data: [] → mất key data (3d #1) |
| 3 | dto/request/WarehousePageFilterRequest.java, TC-02 |
Lệch hình thức spec mục 5/TC-02: keyword, status, countProduct bind qua record (Checkstyle tối đa 3 tham số) thay @RequestParam(required=false); hành vi giữ, nhưng sai kiểu thì reason lộ thông điệp convert của Spring (F-logic-4 MINOR) |
| 4 | WarehouseController dòng .count(page.total()) |
F-logic-2 MINOR: cũ ép (int) tổng; chỉ khác khi offset tràn int mà trang có dữ liệu (D-08) |
| 5 | WarehouseRelationalRepository.toListItem |
F-logic-3 MINOR: ép kiểu thủ công ngoài khối bắt lỗi → kiểu cột lạ ra UNCERTAIN_ERROR thay EXCEPTION_ERROR; kiểu cột thật chưa kiểm |
| 6 | WarehouseQueryService.getWarehousePage |
@Transactional không readOnly (D-06) — trái docs/rules/database-performance.md:290 |
| 7 | Native SQL ở WarehouseRelationalRepository.findPage, InventoryRelationalRepository.countActiveProductsByWarehouses |
Chỉ test mock (K-13): IN :warehouseIds, setFirstResult không ORDER BY trên MSSQL — chạy GM-01 |
| 8 | Constructor WarehouseQueryService (thêm 2 dependency), WarehouseController (thêm WarehouseQueryService), hằng PARAM_COM_ID thay chuỗi ở lockCompanyWarehouses/deleteById |
Chạm code dùng chung của unit khác — không đổi hành vi |
| 9 | G4 | Người chạy SonarQube for IDE |
3b. Phân tích luồng sau chuyển
Chi tiết: verify/round-2/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 | Danh sách kho theo trang | 8 KHỚP / 6 KHÁC (D-01, D-07, D-03 có chủ đích; F-logic-4, F-logic-2, F-logic-1b) / 0 / 0 | Không lệch thứ tự; lệch cách bind bộ lọc và serialize data rỗng |
mapper NON_EMPTY của starter; ép (int) offset; tổng PageImpl; ép kiểu thủ công khi map; IN :warehouseIds; transaction không readOnly |
3c. Nghi vấn bug / sai ở code cũ (đã GIỮ nguyên khi chuyển)
B-01 Phân trang không có thứ tự cố định, tham số sort bị bỏ qua
- Mức nghi ngờ: TRUNG BÌNH — phân trang không
ORDER BYlà lỗi kinh điển; có thể chưa ai thấy vì công ty ít kho (< 20). - Ở đâu: code cũ
base:repositories/impl/BaseWarehouseRepositoryImpl.java:51-58,model:util/BaseCommon.java:372-386→ code mới (giữ nguyên)WarehouseRelationalRepository.findPageItems - Code cũ đang làm gì: lấy từng trang kho mà không sắp xếp; DB trả theo thứ tự tuỳ ý. Client gửi
sort=name,asccũng không có tác dụng. Cỡ trang tối đa 2000. - Ví dụ cụ thể: công ty 45 kho, xem trang 1 (20 kho) rồi trang 2 → có thể có kho xuất hiện ở cả hai trang và có kho không xuất hiện ở trang nào. Lẽ ra trang ổn định theo
idhoặc theosort. - Vì sao nghi là sai:
Pageablecósortnhưng code bỏ qua; repo đích quy định tối đa 200/trang. - Ảnh hưởng nếu đúng là bug: công ty nhiều kho thấy danh sách nhảy, sót/lặp khi chuyển trang.
- Đã xử lý khi chuyển: GIỮ. Test khoá:
TC-16(WarehouseRelationalRepositoryTest$FindPage#buildsFilteredPagedSql, assert không cóORDER BY),TC-02. - Nếu muốn sửa: thêm
ORDER BY id(và/hoặc theosort), giới hạn cỡ trang; đổi TC-16. - Liên quan: D-05, R-01, R-05
B-02 Từ khoá toàn dấu cách trả mọi kho trừ kho chưa có tên chuẩn hoá
- Mức nghi ngờ: THẤP
- Ở đâu:
base:repositories/impl/BaseWarehouseRepositoryImpl.java:34-37→WarehouseQueryService.keywordPattern - Code cũ đang làm gì:
keyword=""không lọc;keyword=" "lọcnormalized_name like '%%'→ bỏ các kho cónormalized_namenull. - Ví dụ cụ thể: 5 kho, 1 kho nhập từ hệ thống cũ có
normalized_name = NULL: ô tìm kiếm trống → 5 kho; gõ vài dấu cách → 4 kho. Lẽ ra cả hai đều 5. - Vì sao nghi là sai: hai từ khoá "trống" cho kết quả khác nhau.
- Ảnh hưởng nếu đúng là bug: hiếm, chỉ với kho có
normalized_namenull. - Đã xử lý khi chuyển: GIỮ. Test khoá:
TC-07. - Nếu muốn sửa: coi từ khoá rỗng sau chuẩn hoá là không lọc; đổi TC-07.
- Liên quan: D-03, R-03
B-03 Ký tự %, _ trong từ khoá không được escape
- Mức nghi ngờ: THẤP
- Ở đâu:
base:repositories/impl/BaseWarehouseRepositoryImpl.java:36→WarehouseQueryService.keywordPattern - Code cũ đang làm gì:
_khớp một ký tự bất kỳ,%khớp mọi chuỗi. - Ví dụ cụ thể: kho "Kho A1", "Kho AB"; gõ
a_→ ra cả 2 kho. Lẽ ra không kho nào. Gõ%→ ra mọi kho. - Vì sao nghi là sai: người dùng gõ ký tự thường nhận kết quả rộng hơn. (Không phải SQL injection — tham số bind.)
- Ảnh hưởng nếu đúng là bug: hiếm, chỉ khi gõ ký tự đặc biệt.
- Đã xử lý khi chuyển: GIỮ. Test khoá:
TC-08. - Nếu muốn sửa: escape
%,_,[; đổi TC-08. - Liên quan: D-04, R-03
B-04 Số trang cực lớn làm tràn số → lỗi hệ thống hoặc trả nhầm trang
- Mức nghi ngờ: THẤP — chỉ với request bất thường.
- Ở đâu:
model:util/BaseCommon.java:384→WarehouseRelationalRepository.findPageItems - Code cũ đang làm gì: vị trí bắt đầu
page × sizebị ép về số nguyên 32 bit. - Ví dụ cụ thể:
page=1073742&size=2000→ lỗi hệ thống (cũ 417, mới 500).page=2147484&size=2000với công ty 800 kho → trả 96 kho từ vị trí 704. Lẽ ra trả rỗng. - Vì sao nghi là sai: tràn số.
- Ảnh hưởng nếu đúng là bug: gần như không gặp khi dùng bình thường.
- Đã xử lý khi chuyển: GIỮ. Test khoá:
TC-20. - Nếu muốn sửa: dùng
long/chặn số trang; đổi TC-20. - Liên quan: D-08, R-01, R-05
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 | F-logic-1b (MAJOR, UNSURE) | Không sửa | công ty chưa có kho → cũ {…,"data":[],"count":0} / mới {…,"count":0} (không có key data) — do ObjectMapper NON_EMPTY của starter (DataConverterAutoConfiguration.java:51) trên BaseResponseDTO.data. Cùng gốc: warehouse/delete data:null cũng mất key; các API đã chuyển khác cũng vậy |
Chọn: (a) chấp nhận, ghi D-xx ADR BẮT KHÁC chung; hoặc (b) chỉnh JSON cấp service/starter (MixIn @JsonInclude(ALWAYS) cho data hoặc ObjectMapper riêng) — ảnh hưởng mọi API; chạy thử với công ty không có kho |
| 2 | D-01 (LỖI KỸ THUẬT) | Kiểm comId của token |
comId=20 (user công ty 10) → cũ trả kho công ty 20 / mới 400 COMPANY_NOT_OF_CURRENT_USER; comId=0 → cũ 200 rỗng / mới 400 INVALID_COMPANY |
Đồng ý |
| 3 | D-02 (ADR BẮT KHÁC) | Body theo starter | reason "Lấy dữ liệu thành công" → "SUCCESS_GET_LIST"; thiếu comId → cũ 400 rỗng / mới 400 body starter |
Frontend đọc mã, không đọc reason |
| 4 | D-03 (ADR BẮT KHÁC) | Lỗi DB → 500 EXCEPTION_ERROR |
cũ 417 / mới 500, giữ mã | Đồng ý |
| 5 | D-06 (MƠ HỒ) | @Transactional không readOnly → đọc DB chính |
readOnly theo quy tắc repo đích sẽ đọc replica (kho vừa tạo có thể chưa thấy) | Chọn giữ DB chính (như cũ) hay theo database-performance.md:290; xác nhận cũ đọc DB chính (suy từ cấu hình) |
| 6 | D-07 (PHỤ THUỘC NGOÀI) | Không kiểm gói dịch vụ | công ty hết hạn → cũ 400 / mới 200 | Tầng đảm nhận (chung các unit) |
| 7 | spec-review | Spec qua 1 lần review (0 CRITICAL), R-xx sửa theo MAJOR: R-01 (offset ép int), R-05, R-09 (đọc DB chính), R-11 (mới, giữ EXCEPTION_ERROR) |
— | Đọc kỹ R-01, R-05, R-09, R-11 |
| 8 | Impl (TC-02, F-logic-4 MINOR) | Bộ lọc không bắt buộc bind qua record WarehousePageFilterRequest |
status=abc → cũ 400 rỗng / mới 400, reason chứa thông điệp convert của Spring |
Đồng ý cách bind; có cần ẩn thông điệp lỗi convert không |
| 9 | F-logic-2 (MINOR) | Không sửa | offset tràn int mà trang có dữ liệu → cũ count ép int (800) / mới long (4294968096) |
Có cần giữ ép (int) không (theo D-08) |
| 10 | F-logic-3 (MINOR) | Không sửa | kiểu cột lạ khi map dòng → cũ 417 EXCEPTION_ERROR / mới 500 UNCERTAIN_ERROR |
Kiểm kiểu cột warehouse trên MSSQL (GM-01) |
| 11 | Sự cố quy trình | Lỡ chạy git add -N . trong repo đích; đã gỡ bằng git rm --cached cho 11 file mới |
index về 0 file staged, working tree nguyên | Kiểm git status trước khi commit (file mới phải là ??) |
4. Finding đã xác nhận
F-logic-1a — MAJOR, parity — CONFIRMED vòng 1 → ĐÃ HẾT vòng 2
Key null trong phần tử data bị mapper NON_EMPTY bỏ. Sửa: @JsonInclude(ALWAYS) trên WarehouseListItemResponse; khoá bằng TC-01-EXTRA-1 (dùng mapper thật của starter). Reviewer vòng 2 xác nhận đúng.
F-logic-1b — MAJOR — UNSURE (mục 3d #1)
F-logic-2, F-logic-3, F-logic-4 — MINOR (mục 3 #3–#5)
5. Finding bị loại
| Finding | Lý do loại |
|---|---|
| — | — |
6. Xử lý của người
| Vấn đề | Cổng | Chỉ thị |
|---|---|---|
| G4 — chưa kiểm Sonar | G4 | |
F-logic-1b — data rỗng mất key (UNSURE) |
G5 (không tính) |
7. So với vòng trước
| Vấn đề | Vòng trước | Vòng này |
|---|---|---|
| F-logic-1a (key null phần tử) | MAJOR CONFIRMED | ĐÃ HẾT |
F-logic-1b (data rỗng) |
UNSURE | CÒN (UNSURE) |
| F-logic-2, F-logic-3, F-logic-4 | MINOR | CÒN (không sửa trong vòng lặp) |
8. Thay đổi trong repo đích
22 file (verify/round-2/changed-files.md): 15 main Java + 2 i18n + 5 test, đều trong src/main, src/test. Mới: WarehousePageQuery, WarehousePageFilter, WarehouseListItem, WarehousePage, WarehousePageFilterRequest, WarehouseListItemResponse, WarehouseQueryServiceTest. Sửa test của context khác: WarehouseTransferServiceTest (constructor). Không có file ngoài phạm vi.
9. Đề xuất bổ sung kinh nghiệm
| Đề xuất | Đưa vào đâu |
|---|---|
Starter đăng ký ObjectMapper NON_EMPTY: response mới mất key null/rỗng so với monolith (ALWAYS). DTO response mỗi phần tử cần @JsonInclude(ALWAYS); data rỗng/null của BaseResponseDTO mất key ở mọi API — cần quyết định chung |
legacy-facts (mục 2) + lessons 1b; review-criteria |
Test serialize response phải dùng DataConverterAutoConfiguration.buildObjectMapper() của starter, không tự dựng ObjectMapper |
lessons 1c |
Monolith: @Transactional cấp class + provider_disables_autocommit: false → đọc DB chính dù DataSourceAspect đặt READ_ONLY; repo đích readOnly=true đi replica — đọc-sau-ghi khác nhau |
legacy-facts (mục 4) |
Method controller tối đa 3 tham số: API list có nhiều @RequestParam → spec mục 5 phải thiết kế record bộ lọc ngay từ đầu (tránh lệch TC) |
target-cheatsheet mục 4 |
Bọc lỗi hạ tầng bằng SystemException.builder().errorCode(...).error(e).build() (PMD PreserveStackTrace) |
target-cheatsheet mục 3 |
Không chạy lệnh git ghi index (git add -N) trong repo đích để xem diff; dùng git diff <snapshot tree> + liệt kê ?? |
lessons 1b / CLAUDE.md |