Báo cáo sai lệch — warehouse/import-excel-save-data — vòng 3
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 (ghi nhiều dòng, sinh mã, tenant R-03/R-04) → người cần đọc: spec + báo cáo + toàn bộ diff
- Số quy tắc: 10 · PASS 4 · DEVIATION có chủ đích 4 · DEVIATION KHÔNG chủ đích 0 · UNVERIFIED 2
- Coverage thấp nhất: F-01 98,7% dòng / 90,9% nhánh
- Finding logic: CRITICAL 0 · MAJOR 0 còn mở (1 đã sửa) · MINOR 2 · UNSURE 0
- Nghi vấn bug code cũ (mục 3c): 7 (CAO 1) · Quyết định tự chốt cần xác nhận (mục 3d): 11
- Chạy bằng:
/mig-auto (spec do spec-reviewer kiểm, chưa có người duyệt) · Số vòng: 3 verify / lần sửa 2/3
1. Điều kiện đạt
| Mã |
Mục tiêu |
Kết quả |
Bằng chứng |
| G1 |
mvnw verify |
✅ (660 test) |
verify/round-3/build.md |
| G2 |
Test khớp test-plan |
✅ (29/29 TC, 10/10 R) |
verify/round-3/oracle.md |
| G3 |
Coverage |
✅ |
verify/round-3/coverage.md |
| G4 |
Sonar |
CHỜ NGƯỜI |
sonar-ide.md |
| G5 |
0 CRITICAL/MAJOR CONFIRMED |
✅ |
verify/round-3/review-logic.md |
| G6 |
Không lách cổng |
✅ |
verify/round-3/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 |
Lưu import — cập nhật |
98,7% |
90,9% |
| F-02 |
Lưu import — tạo mới |
99,2% |
93,5% |
| F-03 |
Lưu import — type khác 1/2 |
100% |
96,2% |
2. Ma trận truy vết
| Quy tắc |
Tầng |
Code mới |
Test |
Golden |
Kết luận |
| R-01 |
B |
WarehouseController.saveImportData, WarehouseImportSaveRequest/RowRequest |
TC-01, TC-02 |
— |
PASS (dòng null trong data → khác, F-logic-2 MINOR) |
| R-02 |
C |
WarehouseImportService, controller |
TC-03, TC-04 |
— |
DEVIATION (có chủ đích, D-12: "data": null) |
| R-03 |
A |
requireCompanyOfCurrentUser |
TC-05, TC-06 |
— |
DEVIATION (có chủ đích, D-01) |
| R-04 |
A |
controller gán comId request, bỏ id |
TC-01, TC-07 |
— |
DEVIATION (có chủ đích, D-03) |
| R-05 |
A |
applyUpdates, Warehouse.applyImport, findAllBySkuIn, saveAll |
TC-08..TC-15, TC-27, TC-08-EXTRA-1/2 |
GM-01 |
UNVERIFIED (collation, dirty-check thật — K-13) |
| R-06 |
A |
createWarehouses, Warehouse.createFromImport, countBySkuAllCompanies, WarehouseCodeGenerator |
TC-16..TC-23, TC-28 |
GM-01 |
UNVERIFIED (SQL thật, collation) |
| R-07 |
C |
WarehouseImportService |
TC-24 |
— |
PASS |
| R-08 |
B |
controller, toResponses |
TC-25 |
— |
PASS |
| R-09 |
A |
@Transactional saveImportData |
TC-26 |
— |
PASS |
| R-10 |
B |
(không chuyển) |
TC-29 |
— |
DEVIATION (có chủ đích, D-11) |
3. Người cần xem
| # |
Vị trí |
Vì sao cần người xem |
| 1 |
Toàn bộ verify/round-3/changes.diff |
Tầng A |
| 2 |
WarehouseRelationalRepository.countBySkuAllCompanies |
Cố ý không lọc com_id (giữ D-04, nghi bug CAO — 3c B-01) |
| 3 |
WarehouseRelationalRepository.saveAll |
findAllById không lọc com_id trong SQL, an toàn tenant dựa vào phép so comId trong bộ nhớ |
| 4 |
WarehouseManagementService + WarehouseCodeGenerator |
Refactor dùng chung vòng sinh mã với warehouse/create (không đổi hành vi, test cũ xanh) |
| 5 |
WarehousePageReader (mới) |
Phần đọc trang của warehouse/get-with-paging tách nguyên ra khỏi adapter để qua PMD; nằm trong diff của unit này |
| 6 |
WarehouseController.saveImportData |
F-logic-2 MINOR: phần tử null trong data → 500 trước cả kiểm công ty/type (cũ: type khác 1/2 vẫn 200) |
| 7 |
Warehouse.createFromImport/applyImport |
F-logic-3 MINOR: comId lấy từ dòng do controller gán, không truyền comId đã kiểm vào domain (khác chữ ký spec mục 5) |
| 8 |
Native SQL findAllBySkuInAndComId, countBySkuAllCompanies |
Chỉ test mock (K-13) — chạy GM-01 |
| 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 (vòng 3 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ập nhật |
6 KHỚP / 5 KHÁC có chủ đích (D-01, D-02/D-08, D-03, D-11, D-12) / 0 / 0 |
không |
nhánh lỗi SKU khác hoa thường; dirty-check thô; findAllById + so comId |
| F-02 |
Tạo mới |
8 KHỚP / 3 KHÁC có chủ đích (D-03, D-04 417→500, D-02) / 0 / 0 |
không |
countBySkuAllCompanies không lọc com_id; mã Redis tiêu không hoàn lại |
| F-03 |
type khác 1/2 |
2 KHỚP / 3 KHÁC (D-01, D-02, F-logic-2) / 0 / 0 |
không |
map dòng trước khi rẽ type |
3c. Nghi vấn bug / sai ở code cũ (đã GIỮ nguyên khi chuyển)
B-01 Tạo kho qua import bị bỏ qua (hoặc lỗi) nếu công ty khác đã dùng mã kho đó
- Mức nghi ngờ: CAO — câu kiểm tra thiếu điều kiện công ty, trong khi mọi câu khác đều lọc công ty; bước validate chỉ kiểm trong công ty nên báo hợp lệ.
- Ở đâu: code cũ
base:repositories/BaseWarehouseRepository.java:115-116 (findBySku), base:services/impl/BaseWarehouseServiceImpl.java:1121-1122 → code mới (giữ nguyên) WarehouseRelationalRepository.countBySkuAllCompanies, WarehouseImportService.createWarehouses
- Code cũ đang làm gì: trước khi tạo kho, tìm kho có cùng mã (SKU) ở mọi công ty. Có 1 kho → bỏ qua dòng đó, không báo gì. Có từ 2 kho → lỗi hệ thống, không kho nào được tạo.
- Ví dụ cụ thể: công ty B đã có kho mã
KHO01. Công ty A import file có dòng KHO01 — màn validate báo hợp lệ; bấm lưu → báo "Nhập danh sách kho hàng thành công" nhưng không có kho nào được tạo (count=0). Nếu cả công ty B và C đều có KHO01 → công ty A nhận "Lỗi hệ thống" cho cả file. Đúng nghiệp vụ thì kho KHO01 của công ty A phải được tạo.
- Vì sao nghi là sai: mã kho là duy nhất trong một công ty (các API thêm/sửa kho chỉ kiểm trong công ty); câu
findBySkuIn ngay cạnh có com_id.
- Ảnh hưởng nếu đúng là bug: công ty dùng mã phổ biến (
K01, KHO1…) không import tạo kho được, không có thông báo lỗi; khó tự phát hiện.
- Đã xử lý khi chuyển: GIỮ. Test khoá:
TC-17 (WarehouseImportServiceTest#existingSkuAnywhereIsSkipped), TC-18 (#duplicatedSkuAnywhereIsSystemError), TC-28 (WarehouseRelationalRepositoryTest$ImportSku#countBySkuAllCompanies).
- Nếu muốn sửa: thêm
AND com_id = :comId vào countBySkuAllCompanies (đổi tên), báo lỗi/ghi rõ dòng bị bỏ qua; đổi TC-17, TC-18, TC-28. Hệ thống cũ còn chạy song song sẽ lệch kết quả.
- Liên quan: D-04, R-06
B-02 Lưu import không kiểm lại dữ liệu: lưu cả dòng bị đánh dấu lỗi, tên rỗng, SKU trùng
- Mức nghi ngờ: TRUNG BÌNH — dựa hoàn toàn vào client gửi đúng kết quả validate.
- Ở đâu:
backend:web/rest/WarehouseResource.java:117-121; base:services/impl/BaseWarehouseServiceImpl.java:1100-1143 → WarehouseController.saveImportData, WarehouseImportService
- Code cũ đang làm gì: không validate, không lọc dòng
statusImport=false, không kiểm trùng tên/SKU trong file và với DB.
- Ví dụ cụ thể: client gửi dòng
{name:"", sku:"K09", statusImport:false} → tạo kho K09 tên rỗng. Hai dòng cùng SKU K11 → tạo 2 kho trùng SKU. Dòng {sku:"WH7"} và dòng không SKU (mã sinh ra WH7) → 2 kho cùng SKU WH7.
- Vì sao nghi là sai: API thêm kho thường chặn các trường hợp này.
- Ảnh hưởng nếu đúng là bug: dữ liệu kho trùng/rỗng khi client lỗi hoặc request tự dựng.
- Đã xử lý khi chuyển: GIỮ. Test khoá:
TC-01, TC-23.
- Nếu muốn sửa: lọc
statusImport, kiểm lại tên/SKU; đổi TC-01, TC-23.
- Liên quan: D-05, R-01, R-06
B-03 Cập nhật qua import đổi tên kho nhưng không cập nhật "tên tìm kiếm"
- Mức nghi ngờ: TRUNG BÌNH
- Ở đâu:
base:services/impl/BaseWarehouseServiceImpl.java:1114-1115 → Warehouse.applyImport
- Code cũ đang làm gì: chép dữ liệu mới vào kho nhưng giữ cột
normalized_name cũ.
- Ví dụ cụ thể: kho K01 "Kho cũ", import đổi thành "Kho Hà Nội" → ô tìm kiếm danh sách kho gõ "ha noi" không ra K01, gõ "kho cu" vẫn ra.
- Vì sao nghi là sai: thêm/sửa kho thường luôn tính lại tên tìm kiếm.
- Ảnh hưởng nếu đúng là bug: tìm kiếm kho sai sau khi import cập nhật.
- Đã xử lý khi chuyển: GIỮ. Test khoá:
TC-08.
- Nếu muốn sửa: tính lại
normalizedName trong applyImport; đổi TC-08; có thể cần chạy lại tên tìm kiếm cho dữ liệu cũ.
- Liên quan: D-06, R-05
B-04 Import cập nhật có thể ngừng kho mặc định; không ghi trạng thái là ngừng kho
- Mức nghi ngờ: TRUNG BÌNH
- Ở đâu:
base:services/impl/BaseWarehouseServiceImpl.java:1114-1115 → Warehouse.applyImport
- Code cũ đang làm gì: cột trạng thái trống được hiểu là "ngừng hoạt động"; không áp luật kho mặc định
WH1; không kiểm đơn chưa xử lý.
- Ví dụ cụ thể: dòng
{sku:"WH1", name:"Kho tổng"} (không có trạng thái) → kho mặc định bị ngừng hoạt động, "cho bán" thành trống. Lẽ ra bị chặn như API sửa kho (UPDATE_WAREHOUSE_DEFAULT_INVALID).
- Vì sao nghi là sai: API sửa kho chặn đúng trường hợp này; bước validate có chặn nhưng bước lưu không kiểm lại.
- Ảnh hưởng nếu đúng là bug: cửa hàng mất kho bán mặc định.
- Đã xử lý khi chuyển: GIỮ. Test khoá:
TC-09.
- Nếu muốn sửa: gọi
validateDefaultWarehouse sau applyImport; đổi TC-09.
- Liên quan: D-07, R-05
B-05 Mã kho trong file khác hoa thường với mã đang lưu → "Lỗi hệ thống" cả file
- Mức nghi ngờ: THẤP — phụ thuộc collation DB (UNVERIFIED).
- Ở đâu:
base:services/impl/BaseWarehouseServiceImpl.java:1108-1116 → WarehouseImportService.applyUpdates
- Code cũ đang làm gì: DB tìm thấy kho (so không phân biệt hoa thường) nhưng bước ghép dòng so phân biệt hoa thường → lỗi.
- Ví dụ cụ thể: kho lưu
k01, file ghi K01 → cả file lỗi, không dòng nào được cập nhật. Lẽ ra cập nhật kho k01.
- Đã xử lý khi chuyển: GIỮ (500
EXCEPTION_ERROR). Test khoá: TC-13.
- Nếu muốn sửa: ghép không phân biệt hoa thường; đổi TC-13.
- Liên quan: D-08, R-05
B-06 Tạo kho qua import không ghi trạng thái → kho ngừng hoạt động
- Mức nghi ngờ: THẤP — bước validate thường điền trạng thái từ cột Excel.
- Ở đâu:
base:dto/warehouse/WarehouseSaveRequest.java (getStatus) → Warehouse.createFromImport
- Ví dụ cụ thể: dòng
{name:"Kho mới", sku:"K10"} → kho K10 ngừng hoạt động, "cho bán" trống. Thêm kho thường thì luôn hoạt động.
- Đã xử lý khi chuyển: GIỮ. Test khoá:
TC-22.
- Nếu muốn sửa: mặc định hoạt động; đổi TC-22.
- Liên quan: D-09, R-06
B-07 Loại import sai vẫn báo "Nhập thành công"
- Mức nghi ngờ: THẤP
- Ở đâu:
base:services/impl/BaseWarehouseServiceImpl.java:1104-1142 → WarehouseImportService.saveImportData
- Ví dụ cụ thể:
type=3 với 5 dòng → "Nhập danh sách kho hàng thành công", không lưu gì.
- Đã xử lý khi chuyển: GIỮ. Test khoá:
TC-24.
- Nếu muốn sửa: 400 khi
type sai; đổi TC-24.
- Liên quan: D-10, R-07
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-03 (LỖI KỸ THUẬT) |
Mọi kho dùng comId request, CREATE bỏ id dòng |
dòng comId=20 (request 10) → cũ chuyển/tạo kho ở công ty 20 / mới công ty 10; CREATE dòng id=5 → cũ ghi đè kho 5 / mới tạo mới |
Đồng ý (chỉ khác với request tự dựng) |
| 2 |
D-01 (LỖI KỸ THUẬT) |
Kiểm comId của token |
comId công ty khác → cũ ghi được / mới 400; comId:null CREATE → cũ tạo kho với mã sequence null / mới 400 INVALID_COMPANY |
Đồng ý |
| 3 |
D-12 (ADR BẮT KHÁC) |
"data": null như rỗng |
cũ 417 / mới 200 status=false FAIL |
Đồng ý |
| 4 |
D-02 (ADR BẮT KHÁC) |
Body theo starter; 417 → 500 giữ mã |
reason mã thay text; lỗi hệ thống 500 |
Frontend đọc mã |
| 5 |
D-11 (PHỤ THUỘC NGOÀI) |
Không kiểm gói |
công ty hết hạn vẫn import được |
Tầng đảm nhận (chung) |
| 6 |
spec-review |
Spec qua 1 lần review (0 CRITICAL); R-xx sửa theo MAJOR: R-01, R-02 (data null/thiếu), R-05 (dirty-check trên giá trị thô → audit dù sau làm sạch trùng), R-06 (SKU ""/" "/null tách riêng) |
— |
Đọc kỹ R-01, R-02, R-05, R-06 |
| 7 |
Impl (lần sửa 1) |
Tách WarehousePageReader khỏi adapter (PMD độ phức tạp lớp) — code của get-with-paging |
không đổi hành vi (reviewer vòng 2 đối chiếu từng dòng) |
Đồng ý cấu trúc |
| 8 |
Impl |
WarehouseCodeGenerator dùng chung với warehouse/create |
không đổi hành vi |
Đồng ý |
| 9 |
F-logic-2 (MINOR) |
Không sửa |
{type:3, data:[null]} → cũ 200 / mới 500 |
Có cần giữ không |
| 10 |
F-logic-3 (MINOR) |
Không sửa |
không khác kết quả với controller hiện tại |
Có cần truyền comId vào domain như spec mục 5 không |
| 11 |
JSON NON_EMPTY của starter |
(chung với warehouse/get-with-paging F-logic-1b) |
response mất key null/rỗng (ví dụ phone:null trong WarehouseResponse, data:[]) |
Quyết định chung cho mọi API |
4. Finding đã xác nhận
F-logic-1 — MAJOR, data-integrity — CONFIRMED vòng 1 → ĐÃ HẾT vòng 2
Lưu từng kho, mỗi kho cập nhật thêm 1 SELECT (N+1). Sửa: WarehouseRepository.saveAll (một findAllById, một saveAll kho mới); khoá bằng TC-08-EXTRA-1/2.
F-logic-2, F-logic-3 — MINOR (mục 3 #6, #7)
5. Finding bị loại
6. Xử lý của người
| Vấn đề |
Cổng |
Chỉ thị |
| G4 — chưa kiểm Sonar |
G4 |
|
7. So với vòng trước
| Vấn đề |
Vòng 1 |
Vòng 2 |
Vòng 3 |
| F-logic-1 (N+1) |
MAJOR CONFIRMED |
ĐÃ HẾT |
ĐÃ HẾT |
G3 WarehousePageReader chưa gán luồng |
— |
MỚI — do lần sửa |
ĐÃ HẾT |
| F-logic-2, F-logic-3 |
MINOR |
CÒN |
CÒN |
8. Thay đổi trong repo đích
22 file (verify/round-3/changed-files.md): 15 main Java + 2 i18n + 5 test, trong src/main, src/test. Mới: WarehouseCodeGenerator, WarehouseImportService, WarehouseImportCommand, WarehouseImportResult, WarehouseImportSaveRequest, WarehouseImportRowRequest, WarehousePageReader, WarehouseImportServiceTest. Sửa code của unit khác: WarehouseManagementService (dùng generator), WarehouseRelationalRepository (tách đọc trang). Không có file ngoài phạm vi.
9. Đề xuất bổ sung kinh nghiệm
| Đề xuất |
Đưa vào đâu |
PMD giới hạn tổng độ phức tạp cấp lớp 50: adapter dùng chung nhiều unit (như WarehouseRelationalRepository) sẽ chạm trần — tách lớp phụ trợ package-private theo chức năng sớm |
target-cheatsheet mục 4 |
Thêm file src/main mới ở bước sửa → cập nhật flows.json ngay, nếu không G3 trượt ở vòng sau (tốn một lần sửa) |
lessons 1b |
Tuyệt đối không thêm @SuppressWarnings kể cả trong test (G6); dùng ArgumentCaptor.forClass(List.class) chấp nhận cảnh báo biên dịch |
lessons 1c |
Native select * trả entity rồi save từng cái → N+1; dùng saveAll nạp một lần bằng findAllById |
lessons 1b |
Code cũ BeanUtils.copyProperties + dirty-check của Hibernate: audit ghi theo giá trị thô trước CleanTextListener |
legacy-facts mục 4 |