Phân tích luồng sau chuyển — batch/get-with-paging — vòng 1
logic-reviewerviết dựa trên code đã implement (worktreeC:/Projects/warehouse-service-wt/ws2), đối chiếu với code cũeasyposbackend1(backend:=src/main/java/vn/softdreams/easypos/). Đường dẫn mới viết tắt từsrc/main/java/com/softdreams/easypos/warehouse/.
F-01 Danh sách lô theo trang
- Điểm vào: cũ
easyposbackend1:src/main/java/vn/softdreams/easypos/web/rest/BatchesManagementResource.java:33-38→ mớipresentation/web/batch/BatchController.java:49-56 - Quy tắc: R-01 … R-13
Sơ đồ luồng mới (theo code thực tế)
sequenceDiagram
participant C as Client
participant BC as BatchController.getWithPaging
participant M as BatchWebMapper
participant QS as BatchPageQueryService.getBatchPage (@Transactional, không readOnly)
participant CU as CurrentUserPort.findCompanyId
participant RR as BatchRelationalRepository.findPage
participant PR as BatchPageReader.read
C->>BC: POST body BatchPageRequest
BC->>BC: Guard.validateInput (comId @NotNull → 400 COMPANY_ID_NOT_NULL)
BC->>M: toPageQuery
BC->>QS: getBatchPage(BatchPageQuery)
QS->>CU: findCompanyId → rỗng: 400 TOKEN_EXPIRED; khác comId: 500 COMPANY_NOT_EXISTS_CODE
QS->>QS: validatePage: paged && (page<0 || size<1) → 500 EXCEPTION_ERROR
QS->>RR: findPage
RR->>PR: read
PR->>PR: BatchKeyword.likePattern; pageWhere (FROM/WHERE/[keyword]/[status]/GROUP BY/[HAVING])
PR->>PR: COUNT(DISTINCT results.id) (bind comId [+keyword])
PR->>PR: findRows: SELECT [+ppu] … ORDER BY [ppu]…; paged: (int)(page*size) <0 → 500 EXCEPTION_ERROR, setFirstResult/setMaxResults
PR->>PR: toItem từng dòng; pageTotal theo quy tắc PageImpl
PR-->>BC: BatchPage(items, total)
BC-->>C: 200 BaseResponseDTO.count(total).ok(items, 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 | Đọc body SearchBatchRequest, mặc định status=3, inventoryQuantityStatus=0 (backend:dto/batches/SearchBatchRequest.java:8-19) |
BatchPageRequest record, không mặc định (presentation/web/batch/dto/request/BatchPageRequest.java:21-29) |
KHỚP (null cũng = không lọc như 3/0, R-01) |
| 2 | Body hỏng → 400 body rỗng (LF-03) | Starter → 400 INVALID_REQUEST |
KHÁC có chủ đích (D-02) |
| 3 | baseCommon.validateInput (BatchesManagementResource.java:35) |
Guard.validateInput (BatchController.java:51) |
KHỚP (mã COMPANY_ID_NOT_NULL; body lỗi theo starter — D-02) |
| 4 | getUserWithAuthorities(comId): JWT không có companyId → 400 TOKEN_EXPIRED; khác → 500 COMPANY_NOT_EXISTS_CODE (backend:service/UserService.java:1844-1851) |
requireCompany (application/batch/BatchPageQueryService.java:46-52) |
KHỚP |
| 5 | Nạp user, kiểm active/lần đăng nhập đầu/password version (UserService.java:1853-1900) |
— | CHỈ CÓ Ở CŨ — D-01 |
| 6 | Kiểm gói dịch vụ qua interceptor (LF-21) | — | CHỈ CÓ Ở CŨ — D-11 |
| 7 | request.getPageable() → PageRequest.of ném IAE → 417 EXCEPTION_ERROR (backend:dto/CommonPageable.java:11-16; BatchesManagementServiceImpl.java:152) |
validatePage (BatchPageQueryService.java:55-59), sau kiểm công ty, trước SQL |
KHÁC có chủ đích (D-03: 417→500, giữ mã) |
| 8 | Keyword !isNullOrEmpty → LIKE '%'+normalizedName+'%' (BatchRepositoryImpl.java:43-46; Common.java:1131-1139,1315-1329) |
BatchKeyword.likePattern/normalize (domain/batch/model/BatchKeyword.java:29-46) |
KHỚP (khác locale toLowerCase — F-logic-2) |
| 9 | Điều kiện status 0/2/1 nguyên văn (BatchRepositoryImpl.java:48-107) |
statusCondition + WARNING_DATE_SQL (BatchPageReader.java:43-67,188-199) |
KHỚP (SQL so từng dòng; tràn DATEADD âm là nghi-bug-cu F-logic-4) |
| 10 | GROUP BY + HAVING theo inventoryQuantityStatus (BatchRepositoryImpl.java:108-117) |
GROUP_BY_SQL, stockCondition (BatchPageReader.java:68-75,201-209) |
KHỚP |
| 11 | COUNT trước, bind comId [+keyword] (BatchRepositoryImpl.java:118-123) |
read (BatchPageReader.java:129-131) |
KHỚP |
| 12 | ORDER BY có/không ppuId, bind ppuId (BatchRepositoryImpl.java:125-143) |
ORDER_SQL/ORDER_PPU_SQL, findRows (BatchPageReader.java:78-86,142-155) |
KHỚP |
| 13 | SELECT 10/11 cột, mapping BatchItem/BatchItemWithPpuOnHand (BatchRepositoryImpl.java:145-179; backend:domain/Batches.java:19-59) |
SELECT_SQL/SELECT_PPU_SQL, toItem (BatchPageReader.java:87-99,230-257) |
KHỚP (số Number.intValue() cắt phần lẻ — D-04; ngày toZonedDateTime — D-12) |
| 14 | Phân trang setFirstResult((int) offset) (âm → IAE → 417), setMaxResults (Common.java:267-281) |
applyPage tự kiểm (int) âm → SystemException(EXCEPTION_ERROR) (BatchPageReader.java:158-165) |
KHỚP thứ tự (sau COUNT, trước SELECT); HTTP 500 theo D-03 |
| 15 | Có pageable: PageImpl(content, pageable, count); không: PageImpl(content); (int) getTotalElements() (BatchRepositoryImpl.java:181-189; BatchesManagementServiceImpl.java:154-160) |
pageTotal (BatchPageReader.java:219-228) |
KHỚP (offset tính long, ép int cuối như cũ) |
| 16 | Lỗi SQL/map không bắt → 417 EXCEPTION_ERROR (LF-13) |
catch (PersistenceException | ClassCastException) → SystemException.builder().errorCode(EXCEPTION_ERROR).error(e) (BatchPageReader.java:136-139) |
KHÁC có chủ đích (D-03) |
| 17 | 200 BaseResultDTO(SUCCESS, SUCCESS_GET_LIST, true, content, count), data=[] khi rỗng (LF-10) |
BaseResponseDTO.count(total).ok(items, SUCCESS_GET_LIST) (BatchController.java:53-55); item @JsonInclude(ALWAYS) |
KHÁC có chủ đích (D-02: mất key data khi rỗng) |
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ú |
|---|---|---|---|
batches b |
đọc, b.com_id = :comId [+ keyword, status] |
đọc, cùng điều kiện | KHỚP |
batches_detail bd |
LEFT JOIN bd.batch_id = b.id, không lọc com_id |
như cũ | KHỚP |
product_product_unit ppu |
LEFT JOIN bd.ppu_id = ppu.id AND ppu.is_primary = 1 |
như cũ | KHỚP |
Cache/bảng user, JWT |
đọc (getUserWithAuthorities) |
chỉ đọc claim companyId của principal |
D-01 |
| Redis gói dịch vụ | đọc (LF-21) | không | D-11 |
Không ghi bảng, không phát event, không ghi cache ở cả hai bên.
Transaction, khoá, idempotency, tenant
- Ranh giới transaction: cũ
@Transactionalcấp class service đọc-ghi (BatchesManagementServiceImpl.java:55-57) → mới@Transactional(khôngreadOnly) trênBatchPageQueryService.getBatchPage(BatchPageQueryService.java:38-43). Hai câu SQL chạy trong cùng một transaction, đọc DB chính (D-10). Lỗi trong transaction chỉ dẫn tới rollback rỗng. - Khoá / cập nhật nguyên tử: không có ở cả hai bên (chỉ đọc).
- Idempotency, thời điểm phát event: không áp dụng.
- Lọc
comId:b.com_id = :comIdcó ở cả COUNT và SELECT.comIdđã được kiểm bằng claim của token trước khi chạy SQL.bd/ppukhông lọccom_id, giống code cũ.
Lệch so với luồng mới đã duyệt (spec mục 6)
Application service là class mới BatchPageQueryService, không phải BatchQueryService như sơ đồ mục 6 và bảng mục 5 (F-logic-1). Thứ tự các bước giống sơ đồ.
Dev cần chú ý khi review
| Vị trí | Vì sao |
|---|---|
infrastructure/relational/batch/adapter/BatchPageReader.java:35-99 (các hằng SQL) |
SQL phải giống hệt code cũ nhưng chỉ test bằng mock (K-13). Đọc kỹ chỗ ghép text block (TOTAL_ON_HAND_SQL có dấu """ cùng dòng, ORDER_TAIL_SQL, SELECT_PPU_SQL nối ", SUM…") và chạy thử trên MSSQL. |
BatchPageReader.java:125-140 (read) |
Ranh giới try/catch: chỉ PersistenceException/ClassCastException thành EXCEPTION_ERROR; SystemException từ applyPage đi thẳng ra. Lỗi khác (vd IllegalArgumentException của Hibernate) sẽ ra UNCERTAIN_ERROR. |
BatchPageReader.java:158-165,219-228 |
Phép (int) offset và tính count theo PageImpl (K-09): offset tràn dương lấy từ vị trí bị cắt, count tính bằng long rồi ép. |
BatchPageReader.java:230-257 + BatchRelationalRepository.java:93-107 |
Kiểu Java Hibernate 6 trả cho mfg_date/exp_date/status/SUM chưa kiểm trên MSSQL. Nếu cột là SQL date thì java.sql.Date không thuộc nhánh nào của toZonedDateTime → ClassCastException → 500, trong khi cũ đọc được. Spec giả định cột là datetime. |
application/batch/BatchPageQueryService.java:46-59 |
Thứ tự kiểm token → công ty → trang phải giữ đúng (R-02 trước R-03). |
domain/batch/model/BatchKeyword.java:29-46 |
likePattern trả %% với từ khoá toàn dấu cách (D-08); Locale.ROOT (F-logic-2). |