Báo cáo sai lệch — inventory/enable-inventory-tracking — 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
- Tầng rủi ro: A (ghi
product,product_product_unit,inventory, tạo phiếu nhập, gửi Kafka) → người cần đọc: spec + báo cáo + toàn bộ diff - Số quy tắc: 13 · PASS 4 (R-04, R-06, R-07, R-10) · DEVIATION có chủ đích 8 (R-01, R-02, R-03, R-05, R-08, R-09, R-11, R-13 — xem mục 2) · DEVIATION KHÔNG chủ đích 0 · UNVERIFIED 1 (R-12) + một phần R-08 (D-11, DEFAULT cột mới)
- Coverage thấp nhất: F-02 99.5% dòng / 84.6% nhánh
- Finding logic: CRITICAL 0 · MAJOR 0 (1 đã sửa ở vòng 1) · MINOR 2 · UNSURE 0
- Nghi vấn bug code cũ (mục 3c): 5 (CAO 1) · Quyết định tự chốt cần xác nhận (mục 3d): 6
- Chạy bằng:
/mig-autolượt20261105-1351(spec qua 2 lầnspec-reviewer, chưa có người duyệt) · Số vòng: 2 verify / lần sửa 1/3 - Phạm vi API còn nghi vấn (❓) — API thuộc màn sản phẩm (
/api/client/page/product/...), chuyển vì người yêu cầu đích danh; ghi bảng của context sản phẩm (D-01).
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 (712 test, SpotBugs 0, ArchUnit 36/36) |
| G2 | Test khớp test-plan | ✅ | verify/round-2/oracle.md (28/28 TC) |
| G3 | Coverage mỗi luồng | ✅ | verify/round-2/coverage.md |
| G4 | Sonar | CHỜ NGƯỜI | sonar-ide.md |
| G5 | 0 CRITICAL/MAJOR | ✅ | verify/round-2/review-logic.md (không cần xác minh); F-logic-1 vòng 1 đã sửa |
| G6 | Không lách cổng | ✅ | verify/round-2/gates.md; 33 file đều có trong impl-notes.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 — tầng A, nên chạy trước khi commit)
Coverage theo luồng
| Luồng | Tên | % dòng | % nhánh |
|---|---|---|---|
| F-01 | Kiểm yêu cầu bật theo dõi tồn | 100.0% (96/96) | 95.8% (23/24) |
| F-02 | Tính và ghi sản phẩm, tồn, phiếu khởi tạo | 99.5% (192/193) | 84.6% (44/52) |
2. Ma trận truy vết
| Quy tắc | Tầng | Code mới | Test | Golden | Kết luận |
|---|---|---|---|---|---|
| R-01 | A | InventoryTrackingController, EnableInventoryTrackingRequest |
TC-01, 02, 03 | GM-01 | DEVIATION (có chủ đích, D-02 — body lỗi theo starter) |
| R-02 | A | InventoryTrackingService.requireCompany |
TC-04, 05 | — | DEVIATION (có chủ đích, D-04) |
| R-03 | A | InitialStockRelationalRepository.findDefaultWarehouse |
TC-06, 20 | GM-01 | DEVIATION (có chủ đích, D-05 — giữ kết quả, không try/catch) |
| R-04 | A | InventoryTrackingService.validateRequest |
TC-06, 07 | — | PASS |
| R-05 | A | InventoryTrackingService, ProductTrackingRelationalAdapter.findUnit |
TC-08, 17 | — | DEVIATION (có chủ đích, D-03 — 417 → 500) |
| R-06 | A | ProductTrackingRelationalAdapter.findActiveProduct |
TC-09, 17 | — | PASS |
| R-07 | A | InventoryTrackingPlan |
TC-10, 11 | GM-01 | PASS |
| R-08 | A | ProductTrackingRelationalAdapter, InitialStockRelationalRepository, InitialRsInoutwardService |
TC-12, 14, 17–19, 21–23, 25, 26 | GM-01 | DEVIATION (có chủ đích, D-09 ngày giờ VN); D-06, D-10, D-11 GIỮ; D-11 + DEFAULT type_doc/ref_id UNVERIFIED |
| R-09 | A | InventoryTrackingService (@Transactional, ifPresent) |
TC-13, 15, 16, 28 | — | DEVIATION (có chủ đích, D-05) |
| R-10 | A | LegacyInventoryLogKafkaProducer, LegacyInventoryLog |
TC-24, TC-24-EXTRA-1 | GM-01 | PASS (D-07 GIỮ) |
| R-11 | A | InventoryTrackingController |
TC-01, 27 | — | DEVIATION (có chủ đích, D-02) |
| R-12 | A | — | — | — | UNVERIFIED (D-08 không chuyển kiểm gói) |
| R-13 | A | InventoryTrackingPlan.requireFlag/divide, InitialRsInoutwardService |
TC-11, 15, 16 | — | DEVIATION (có chủ đích, D-03) |
3. Người cần xem
| # | Vị trí | Vì sao cần người xem |
|---|---|---|
| 1 | Toàn bộ diff (verify/round-2/changes.diff, 33 file) |
Tầng A |
| 2 | …/infrastructure/relational/product/adapter/ProductTrackingRelationalAdapter.java |
Ghi thẳng bảng của context sản phẩm (D-01); mô phỏng dirty check/làm sạch chuỗi của Hibernate (D-10, D-11 UNVERIFIED) — nên chạy GM-01 |
| 3 | …/infrastructure/messaging/LegacyInventoryLogKafkaProducer.java |
Gửi message định dạng cũ lên inventory-events (D-07); template riêng StringSerializer (F-logic-1 đã sửa) |
| 4 | …/application/inventory/InventoryTrackingService.java:95-96 |
Không có WH1 vẫn báo thành công (D-05) |
| 5 | F-logic-2 (MINOR) | Câu thông báo PRODUCT_NOT_FOUND của repo đích có {0} không được điền; câu cũ "Không tìm thấy thông tin sản phẩm" — mã/HTTP giữ đúng |
| 6 | F-logic-3 (MINOR) | UPDATE inventory/product/product_product_unit chỉ theo id (không thêm com_id) — id luôn lấy từ truy vấn đã lọc công ty, như cũ |
| 7 | R-12 UNVERIFIED | Không kiểm gói dịch vụ (D-08) |
| 8 | spec.md:210 |
Câu "mới chỉ cập nhật cột đổi (D-11)" trong mục Khác biệt viết sót trước khi D-11 chốt GIỮ; code làm theo D-11 (ghi cả cột chuỗi bị làm sạch) |
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 | Kiểm yêu cầu bật theo dõi tồn | 4 / 3 (D-02, D-04, D-03 + câu thông báo F-logic-2) / 1 (kiểm gói D-08) / 0 | không | thứ tự token → kho → kiểm request; câu PRODUCT_NOT_FOUND |
| F-02 | Tính và ghi sản phẩm, tồn, phiếu khởi tạo | 9 / 2 (D-11, D-05) / 1 (task log rỗng, không side effect) / 0 | không | template Kafka riêng; nhánh không có WH1; sinh NK mọi ppu chính chỉ lưu phiếu đầu; UPDATE theo id |
3c. Nghi vấn bug / sai ở code cũ (đã GIỮ nguyên khi chuyển)
B-01 Bật theo dõi tồn mà sản phẩm chưa có dòng tồn ở kho chính thì số tồn đầu không được ghi vào kho
- Mức nghi ngờ: CAO — sản phẩm chưa bật theo dõi tồn thường chưa có dòng
inventorynào, nên trường hợp này dễ là trường hợp phổ biến. - Ở đâu: code cũ
easyposbase:services/impl/BaseInventoryServiceImpl.java:609-701→ code mới (giữ nguyên)…/application/inventory/InventoryTrackingService.java:106-118,…/infrastructure/relational/inventory/adapter/InitialStockRelationalRepository.java:65-85 - Code cũ đang làm gì: chỉ sửa số tồn của dòng tồn đã có sẵn ở kho WH1 và không có lô; không tạo dòng mới. Phiếu nhập "Khởi tạo kho" thì luôn được tạo.
- Ví dụ cụ thể: sản phẩm 70 chưa có dòng tồn nào ở WH1, bật với tồn 50 giá 20.000 → có phiếu NK42 "nhập 50" nhưng bảng
inventorykhông có tồn 50. Ngoài ra ppu có 2 dòng tồn không lô trùng nhau → chỉ một dòng (bất kỳ) được đặt 50; sản phẩm có 2 ppu chính → tiêu 2 mã NK, chỉ lưu 1 phiếu. - Vì sao nghi là sai: phiếu khởi tạo và tồn thực tế lệch nhau ngay sau khi bật.
- Ảnh hưởng nếu đúng là bug: tồn trên màn hình kho = 0 trong khi có phiếu nhập 50; báo cáo nhập-xuất-tồn lệch.
- Đã xử lý khi chuyển: GIỮ nguyên hành vi cũ. Test khoá: TC-21 (
InitialStockRelationalRepositoryTest#stockRows), TC-14 (InventoryTrackingServiceTest#twoPrimaryUnits). - Nếu muốn sửa: thêm tạo dòng
inventorykhi thiếu trongInitialStockRelationalRepository/service, đổi TC-21; monolith còn chạy song song sẽ khác hành vi. - Liên quan: D-06, R-08
B-02 Công ty không có kho WH1: bật theo dõi tồn vẫn báo thành công nhưng không có tồn, không có phiếu
- Mức nghi ngờ: TRUNG BÌNH — chỉ xảy ra khi công ty không có kho mã WH1 (hiếm), nhưng sau đó không có cách tạo lại phiếu.
- Ở đâu: code cũ
easyposbackend1:…/service/impl/ProductManagementServiceImpl.java:3347-3349,3357-3367,3396-3399→ code mới…/application/inventory/InventoryTrackingService.java:95-96 - Code cũ đang làm gì: lỗi do thiếu kho WH1 bị nuốt; sản phẩm/đơn vị tính đã được bật và lưu tồn ở ppu, API trả thành công.
- Ví dụ cụ thể: công ty 10 không có WH1, bật ppu 701 tồn 50 →
product.inventory_tracking = 1, ppu 701on_hand = 50, không có phiếu, không có tồn kho; gọi lại → 400PRODUCT_ENABLE_TRACKING_INVALID. - Vì sao nghi là sai: người dùng được báo thành công trong khi dữ liệu kho thiếu.
- Ảnh hưởng nếu đúng là bug: sản phẩm "đã theo dõi tồn" nhưng không có tồn đầu.
- Đã xử lý khi chuyển: GIỮ. Test khoá: TC-13 (
InventoryTrackingServiceTest#noDefaultWarehouse). - Nếu muốn sửa: ném lỗi khi không có WH1 (rollback hết), đổi TC-13.
- Liên quan: D-05, R-03, R-09
B-03 Message log tồn mang loại "xuất bán" và số -99
- Mức nghi ngờ: TRUNG BÌNH — consumer cũ đang tắt nên hiện chưa ai đọc.
- Ở đâu: code cũ
easyposbase:mapper/InventoryLogMessageMapper.java:17-30,55-66→ code mới…/domain/inventory/model/LegacyInventoryLog.java - Code cũ đang làm gì: khi đặt tồn khởi tạo, gửi message
type BILL_COMPLETION,direction 2(xuất),onHandReq/onHandCurrent -99; gửi trước khi lưu và commit. - Ví dụ cụ thể: đặt tồn 50 cho dòng 3001 → message
{type:"BILL_COMPLETION", direction:2, onHandReq:-99, …, inventoryId:3001}; lưu phiếu lỗi sau đó → DB rollback nhưng message đã đi. - Vì sao nghi là sai: đây là nhập khởi tạo +50, không phải xuất bán.
- Ảnh hưởng nếu đúng là bug: nếu bật lại consumer, log tồn ghi sai.
- Đã xử lý khi chuyển: GIỮ. Test khoá: TC-24 (
LegacyInventoryLogKafkaProducerTest#sendsLegacyMessage). - Nếu muốn sửa: gửi
InventoryLogsEventđúng nghĩa quaInventoryLogPortcủa repo đích, sau commit. - Liên quan: D-07, R-10
B-04 Bật theo dõi tồn làm đổi tên đơn vị tính (gộp khoảng trắng, bỏ xuống dòng)
- Mức nghi ngờ: THẤP — thay đổi nhỏ về chữ.
- Ở đâu: code cũ
easyposbackend1:domain/ProductProductUnit.java:15-18(CleanTextListener) → code mới…/ProductTrackingRelationalAdapter.java:102-133 - Code cũ đang làm gì: dòng ppu có đổi tồn/giá được ghi lại toàn bộ, các cột chữ bị làm sạch.
- Ví dụ cụ thể: ppu 702
unit_name = "Thùng 12"→ sau khi bật thành"Thùng 12". - Vì sao nghi là sai: dữ liệu chữ bị đổi ngoài ý người dùng.
- Ảnh hưởng nếu đúng là bug: tìm kiếm/đối chiếu theo tên cũ có thể lệch.
- Đã xử lý khi chuyển: GIỮ. Test khoá: TC-18 (
ProductTrackingRelationalAdapterTest#updateUnits). - Nếu muốn sửa: chỉ UPDATE
on_hand,purchase_price, audit; đổi TC-18. - Liên quan: D-10, R-08
B-05 Bật theo dõi tồn làm đổi tên/mô tả sản phẩm (gộp khoảng trắng, bỏ xuống dòng)
- Mức nghi ngờ: THẤP — hành vi suy ra từ cách Hibernate 5.6 xử lý
@DynamicUpdate+ listener, UNVERIFIED. - Ở đâu: code cũ
easyposbackend1:domain/Product.java:29-32→ code mới…/ProductTrackingRelationalAdapter.java:136-157 - Code cũ đang làm gì: khi lưu sản phẩm, mọi cột chữ bị làm sạch khác giá trị cũ được ghi lại.
- Ví dụ cụ thể:
name = "Áo thun",description = "dòng 1\ndòng 2"→"Áo thun","dòng 1dòng 2"(mất ngắt dòng). - Vì sao nghi là sai: mô tả nhiều dòng bị dính liền sau khi bật tồn.
- Ảnh hưởng nếu đúng là bug: mô tả sản phẩm hiển thị sai.
- Đã xử lý khi chuyển: GIỮ. Test khoá: TC-19 (
ProductTrackingRelationalAdapterTest#enableTracking). - Nếu muốn sửa: bỏ vòng làm sạch trong
enableTracking; đổi TC-19. Cần chạy GM-01 để xác nhận code cũ thật sự làm vậy. - Liên quan: D-11, R-08
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 | Port + adapter transitional ghi thẳng product, product_product_unit |
không khác dữ liệu | Có chấp nhận warehouse-service ghi bảng của context sản phẩm tạm thời không |
| 2 | D-02 | Body lỗi/thành công theo starter | thành công → cũ reason là câu tiếng Việt / mới mã UPDATE_PRODUCT_SUCCESS |
Frontend đọc được định dạng mới |
| 3 | D-03 | Lỗi hệ thống 417 → 500 EXCEPTION_ERROR |
ppu phụ formula null → cũ 417 / mới 500 |
Chấp nhận đổi HTTP |
| 4 | D-04 | Chỉ giữ 2 kiểm token/công ty | token cũ sau đổi mật khẩu → cũ 500 TOKEN_EXPIRED / mới chạy tiếp |
Gateway/auth đã chặn các trường hợp còn lại chưa |
| 5 | D-08 | Không kiểm gói dịch vụ | hết gói → cũ 400 ACCOUNT_EXPIRED / mới vẫn bật |
Có cần kiểm gói ở gateway không |
| 6 | D-09 | Mã NK qua SequenceService (không kiểm trùng), ngày phiếu = hôm nay 00:00 giờ VN |
06:00 06/11 giờ VN, JVM cũ UTC → cũ 2026-11-05 07:00 / mới 2026-11-06 00:00 |
Múi giờ JVM của monolith prod |
Spec qua 2 lần review (lần 1 có CRITICAL); mọi finding đã CHẤP NHẬN, không còn CHƯA QUYẾT. Không có auto-changes.md.
4. Finding đã xác nhận
F-logic-1 (vòng 1, ĐÃ SỬA ở lần sửa 1)
- Mức độ: MAJOR · Loại: parity · Quy tắc: R-10
- Mô tả: message
inventory-eventsđi quaJsonSerializermặc định của repo đích → payload bị bọc thành chuỗi JSON, khác code cũ (StringSerializer). - Xác minh: CONFIRMED (
verify/round-1/verification.md). Sửa: template riêng ghi đèStringSerializer; khoá bằng TC-24-EXTRA-1. Vòng 2 reviewer xác nhận đã sửa đúng.
F-logic-2 — MINOR (chưa sửa)
Xem verify/round-1/review-logic.md#F-logic-2: câu thông báo PRODUCT_NOT_FOUND có {0} không điền.
F-logic-3 — MINOR (chưa sửa)
Xem verify/round-1/review-logic.md#F-logic-3: UPDATE theo id không lọc com_id.
5. Finding bị loại
| Finding | Lý do loại |
|---|---|
| — | — |
6. Xử lý của người
| Vấn đề | Cổng | Chỉ thị |
|---|---|---|
Kiểm Sonar trong IDE (sonar-ide.md) |
G4 |
7. So với vòng trước
| Vấn đề | Vòng trước | Vòng này |
|---|---|---|
| F-logic-1 (MAJOR) | MỚI | ĐÃ HẾT |
| F-logic-2 (MINOR) | MỚI | CÒN |
| F-logic-3 (MINOR) | MỚI | CÒN |
8. Thay đổi trong repo đích
33 file (verify/round-2/changed-files.md): 19 file main mới/sửa trong presentation/web/inventory, application/inventory, application/rsinoutward,
domain/inventory, domain/port/{product,messaging,businesstype}, domain/rsinoutward/command, infrastructure/relational/{product,inventory,businesstype},
infrastructure/messaging; i18n vi/en; 8 lớp test mới. Không có file ngoài phạm vi dự kiến. Chưa thêm khoá app.kafka.topics.legacy-inventory-log vào yml (mặc định inventory-events).
9. Đề xuất bổ sung kinh nghiệm (/mig-learn gom vào proposal để người duyệt)
| Đề xuất | Đưa vào đâu |
|---|---|
Producer Kafka gửi message định dạng monolith (chuỗi JSON tự serialize) phải dùng template riêng ghi đè StringSerializer như TaskLogKafkaProducer — value serializer mặc định của warehouse-service là JsonSerializer |
knowledge/target-cheatsheet.md / lessons.md |
Test dùng ArgumentCaptor generic: khai báo @Captor field thay vì forClass + @SuppressWarnings (G6 chặn @SuppressWarnings) |
lessons.md |
BaseCommon.cleanTextReturnNull(" ") trả "" (không phải null) — ghi vào LF làm sạch chuỗi |
legacy-facts.md |