Phân tích luồng sau chuyển — batch/by-id — vòng 1
logic-reviewerviết dựa trên code đã implement, đối chiếu với code cũ. Viết tắt:backend:=easyposbackend1:src/main/java/vn/softdreams/easypos/,BRI=backend:repository/impl/BatchRepositoryImpl.java,PSI=backend:service/impl/ProductManagementServiceImpl.java,new:=src/main/java/com/softdreams/easypos/warehouse/,PBR=new:infrastructure/relational/batch/adapter/ProductBatchReader.java,BQS=new:application/batch/BatchQueryService.java.
F-01 Lô và tồn theo lô của sản phẩm
- Điểm vào: cũ
backend:web/rest/ProductManagementResource.java:967-972→ mớinew:presentation/web/batch/ProductBatchController.java:42 - Quy tắc: R-01, R-02, R-03, R-04, R-05, R-06, R-07
Sơ đồ luồng mới (theo code thực tế)
sequenceDiagram
participant C as Client
participant PC as ProductBatchController.byId
participant M as BatchWebMapper
participant QS as BatchQueryService.getProductBatches (@Transactional)
participant CU as CurrentUserPort.findCompanyId
participant BR as BatchRelationalRepository.findProductBatches
participant RD as ProductBatchReader.read/doRead
C->>PC: POST ProductBatchRequest (không validate)
PC->>M: toProductBatchQuery
PC->>QS: getProductBatches(query)
QS->>CU: findCompanyId → rỗng: BadRequestException(TOKEN_EXPIRED)
QS->>QS: comId null → SystemException(EXCEPTION_ERROR, cause NPE)
QS->>QS: comId ≠ token → SystemException(COMPANY_NOT_EXISTS_CODE)
QS->>QS: requireValidPage: page<0 / size<1 → SystemException(EXCEPTION_ERROR, cause IAE)
QS->>BR: findProductBatches(tokenCompanyId, query)
BR->>RD: read(comId, query)
RD->>RD: dựng FROM/WHERE + status + keyword (BaseNormalizedName.of)
RD->>RD: COUNT(1) (null → "")
RD->>RD: PageRequest.of nếu page & size; SELECT (không phân trang: null → ""; có phân trang: bind null, firstResult (int) offset, âm → IAE)
RD->>RD: map Object[] → ProductBatchLine; count = size hoặc PageImpl.getTotalElements
RD-->>RD: lỗi Persistence/IAE/ClassCast → SystemException(EXCEPTION_ERROR)
PC->>M: toProductBatchResponses (ngày yyyy-MM-dd)
PC-->>C: 200 count((long) n).ok(data, SUCCESS_GET_LIST)
Đối chiếu từng bước
| # | Luồng cũ (repo:path:dòng) |
Luồng mới (path:dòng) |
Kết luận |
|---|---|---|---|
| 1 | Nhận body GetProductBatchRequest, validateInput không có ràng buộc — backend:web/rest/ProductManagementResource.java:968-969 |
ProductBatchController.java:42, không @Valid |
KHỚP |
| 2 | Body hỏng → 400 body rỗng (LF-03) | Starter 400 INVALID_REQUEST |
KHÁC có chủ đích (D-02) |
| 3 | getUserWithAuthorities(comId): JWT không có công ty → 400 TOKEN_EXPIRED — backend:service/UserService.java:1844-1847 |
BQS:60-61 |
KHỚP |
| 4 | comId null → NPE ở companyId.equals → 417 — UserService.java:1849 |
BQS:62-68 500 EXCEPTION_ERROR |
KHÁC có chủ đích (D-03) |
| 5 | comId ≠ token → 500 COMPANY_NOT_EXISTS_CODE — UserService.java:1849-1851 |
BQS:69-71 |
KHỚP |
| 6 | Kiểm user/cache user, mật khẩu đổi — UserService.java:1853-1906; kiểm gói (LF-21) |
không có | KHÁC có chủ đích (D-01, D-08) |
| 7 | request.getPageable() → PageRequest.of ném IAE trước câu đếm — PSI:2326, backend:dto/CommonPageable.java:11-16 |
BQS:72,77-87 ném trước khi gọi repository; 500 EXCEPTION_ERROR |
KHỚP (mã HTTP: D-03) |
| 8 | FROM/WHERE nguyên văn, comId từ request — BRI:210-218 |
PBR:30-37,85-87, comId từ token (đã bằng request ở bước 5) |
KHỚP |
| 9 | status != null → and b.status = :status — BRI:219-222 |
PBR:89-92 |
KHỚP (D-04 giữ) |
| 10 | keyword không null/rỗng → like "%" + normalizedName + "%" — BRI:223-226, model:util/BaseCommon.java:1371-1389 |
PBR:93-96, BaseNormalizedName.of |
KHỚP với một phần tử; port lệch ở trường hợp nhiều phần tử (F-logic-1, không ảnh hưởng API) |
| 11 | COUNT(1), setParams null → "" — BRI:228-230, BaseCommon.java:332-341 |
PBR:97-99 |
KHỚP |
| 12 | Câu dữ liệu 8 cột, không ORDER BY — BRI:231-243 |
PBR:39-42,104 |
KHỚP (D-05 giữ) |
| 13 | Có phân trang: setParamsWithPageable bind thẳng null, setFirstResult((int) offset), setMaxResults — BRI:244-247, BaseCommon.java:372-386 |
PBR:107-115; offset âm → IAE trước setFirstResult (cũ do Hibernate ném) |
KHỚP |
| 14 | Không phân trang: setParams, new PageImpl<>(content) — BRI:248-252 |
PBR:105-106,118 count = lines.size() |
KHỚP |
| 15 | count = (int) PageImpl(content, pageable, total).getTotalElements() — BRI:247, PSI:2327 |
PBR:118 (cùng pageable long offset nên trường hợp quay vòng cũng khớp) |
KHỚP |
| 16 | Map @SqlResultSetMapping "ProductBatchDetail": id Integer, ngày ZonedDateTime, onHand BigDecimal, status type=String — backend:domain/Batches.java:60-77 |
PBR:122-140 theo vị trí; status String.valueOf; ngày qua BatchDateTimes |
KHỚP (D-06 giữ) |
| 17 | JSON ProductBatchDetail, ngày @JsonFormat yyyy-MM-dd — backend:dto/batches/ProductBatchDetail.java:14-18 |
BatchWebMapper.java:79-81,91-94, ProductBatchResponse @JsonInclude(ALWAYS) |
KHỚP |
| 18 | BaseResultDTO(SUCCESS, SUCCESS_GET_LIST, true, content, count) — PSI:2327 |
ProductBatchController.java:44-46; rỗng → mất key data |
KHÁC có chủ đích (D-02) |
| 19 | Lỗi SQL/map → 417 EXCEPTION_ERROR (LF-13) |
PBR:76-81 → 500 EXCEPTION_ERROR giữ cause |
KHÁC có chủ đích (D-03) |
Dữ liệu và side effect
| Bảng / topic / cache | Cũ (đọc/ghi, điều kiện) | Mới (đọc/ghi, điều kiện) | Ghi chú |
|---|---|---|---|
inventory, product_product_unit, batches_detail, batches, warehouse |
Đọc; mọi bảng com_id = :comId, ppu.is_primary = 1, ppu.product_id = :productId |
Đọc, cùng SQL | Không ghi |
| Cache/bảng user | Đọc (getUserWithAuthorities) |
Không | D-01 |
Transaction, khoá, idempotency, tenant
- Ranh giới transaction: cũ
@Transactionalcấp class củaProductManagementServiceImpl→ mới@Transactional(không readOnly) trênBQS.getProductBatches, gọi từ controller qua proxy. Chỉ đọc. - Khoá / cập nhật nguyên tử: không có ở cả hai.
- Idempotency, thời điểm phát event: không áp dụng (không ghi, không event).
- Lọc
comId: cả 5 bảng trong JOIN và WHERE; giá trị là công ty token, đã kiểm bằngcomIdrequest. - Nguồn đọc: cũ EntityManager
readOnlyPersistenceUnit(BRI:27-29) → mới EntityManager mặc định (D-07 GIỮ DB chính, UNVERIFIED).
Lệch so với luồng mới đã duyệt (spec mục 6)
không
Dev cần chú ý khi review
| Vị trí | Vì sao |
|---|---|
PBR:98 và PBR:105-108 |
Hai cách bind null khác nhau giữa câu đếm và câu dữ liệu có phân trang — cố ý giữ như code cũ (R-03); đừng "sửa" cho thống nhất |
PBR:110-113 |
Ép (int) offset, ném IAE khi âm — cố ý giữ quay vòng như cũ (R-04) |
PBR:118 |
PageImpl dùng pageable gốc (offset long) — giữ đúng count của cũ ở trường hợp tràn |
PBR:126-135 |
Ép kiểu theo vị trí (BigDecimal, String, Number); kiểu cột Hibernate 6 trả về chỉ test bằng mock (K-13) |
BQS:62-71 |
Thứ tự token → comId null → khác công ty → phân trang phải giữ như cũ |
new:domain/batch/model/BaseNormalizedName.java:36 |
F-logic-1 |