Phân tích luồng sau chuyển — warehouse/get-with-paging — vòng 2
F-01 Danh sách kho theo trang
- Điểm vào: cũ
easyposbackend1:src/main/java/vn/softdreams/easypos/web/rest/WarehouseResource.java:69-78→ mớisrc/main/java/com/softdreams/easypos/warehouse/presentation/web/warehouse/WarehouseController.java:119-139 - Quy tắc: R-01 … R-11
Sơ đồ luồng mới (theo code thực tế)
sequenceDiagram
participant C as Client
participant WC as WarehouseController.getWithPaging
participant QS as WarehouseQueryService.getWarehousePage (@Transactional, không readOnly)
participant CC as CurrentCompanyService.requireCompanyOfCurrentUser
participant WR as WarehouseRelationalRepository.findPage
participant IQ as InventoryQueryService.countActiveProductsByWarehouses
participant IR as InventoryRelationalRepository.countActiveProductsByWarehouses
C->>WC: GET ?page&size&comId&keyword&status&countProduct (Pageable + @RequestParam comId + record filter)
WC->>QS: WarehousePageQuery(comId, keyword, status, countProduct, pageable.getOffset(), pageSize)
QS->>CC: requireCompanyOfCurrentUser(comId) (null/<=0 → INVALID_COMPANY; khác token → COMPANY_NOT_OF_CURRENT_USER)
QS->>QS: keywordPattern: null/"" → null, còn lại "%"+WarehouseText.normalizedName([kw])+"%"
QS->>WR: findPage(WarehousePageFilter)
WR->>WR: native SELECT COUNT(id) FROM warehouse WHERE com_id [+normalized_name LIKE][+status =]
alt count > 0
WR->>WR: (int) offset; âm → SystemException(EXCEPTION_ERROR)
WR->>WR: native SELECT id,…,is_sellable … setFirstResult/setMaxResults, không ORDER BY
end
WR->>WR: pageTotal theo quy tắc PageImpl (long); PersistenceException → SystemException(EXCEPTION_ERROR)
WR-->>QS: WarehousePage(items countProduct=null, total)
opt items không rỗng và Boolean.TRUE.equals(countProduct)
QS->>IQ: countActiveProductsByWarehouses(comId, ids)
IQ->>IR: native COUNT(DISTINCT product_id) … GROUP BY warehouse_id → Map
QS->>QS: item.withCountProduct(counts.get(id)) (null → 0)
end
WC-->>C: 200 BaseResponseDTO.count(total).ok(items, SUCCESS_GET_LIST) (serialize bằng ObjectMapper NON_EMPTY của starter)
Đố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 | Bind Pageable mặc định, @RequestParam comId/keyword/status/countProduct — easyposbackend1:…/web/rest/WarehouseResource.java:70-76 |
Pageable mặc định, @RequestParam comId, record WarehousePageFilterRequest — WarehouseController.java:121-125 |
KHÁC (F-logic-4): body lỗi khi sai kiểu; mã 400 giữ nguyên; body thuộc D-02 |
| 2 | Không kiểm công ty — easyposbase:…/BaseWarehouseRepositoryImpl.java:31-32, LF-20 |
CurrentCompanyService.requireCompanyOfCurrentUser — WarehouseQueryService.java:68 |
KHÁC có chủ đích (D-01) |
| 3 | Kiểm gói dịch vụ qua interceptor — LF-21 | không có | KHÁC có chủ đích (D-07) |
| 4 | !Strings.isNullOrEmpty(keyword) → '%'+normalizedName+'%' — BaseWarehouseRepositoryImpl.java:34-37 |
keywordPattern — WarehouseQueryService.java:86-91; WarehouseRelationalRepository.pageWhere/setPageParameters |
KHỚP (D-03, D-04 GIỮ) |
| 5 | status khác null → status = :status — BaseWarehouseRepositoryImpl.java:39-45 |
PAGE_STATUS_SQL khi khác null |
KHỚP |
| 6 | SELECT COUNT(id) trước; count>0 mới lấy trang — BaseWarehouseRepositoryImpl.java:47-62 |
WarehouseRelationalRepository.java:178-185 |
KHỚP |
| 7 | setFirstResult((int) offset), setMaxResults, không ORDER BY — easyposmodel:…/util/BaseCommon.java:384-385 |
findPageItems — WarehouseRelationalRepository.java:192-204 (offset âm → SystemException trước khi tạo query) |
KHỚP về kết quả (D-05, D-08); 417→500 là D-03 |
| 8 | Map ConstructorResult (Boolean cho status/is_sellable) — easyposmodel:…/domain/BaseWarehouse.java:21-38 |
toListItem/toBoolean |
KHỚP với dữ liệu đúng kiểu; lỗi map → KHÁC (F-logic-3) |
| 9 | PageImpl(content, pageable, count.longValue()) — BaseWarehouseRepositoryImpl.java:64 |
pageTotal — WarehouseRelationalRepository.java:228-233 |
KHỚP |
| 10 | Đếm SP khi trang không rỗng và countProduct == TRUE, toMap, setCountProduct null→0 — easyposbase:…/BaseWarehouseServiceImpl.java:251-262; SQL BaseWarehouseRepositoryImpl.java:70-95 |
WarehouseQueryService.java:74-82; InventoryRelationalRepository.java:101-120; WarehouseListItem.withCountProduct |
KHỚP (cột 0 = count, cột 1 = warehouse_id map đúng) |
| 11 | new BaseResultDTO(SUCCESS, SUCCESS_GET_LIST, true, warehouses, (int) totalElements) — BaseWarehouseServiceImpl.java:263 |
BaseResponseDTO.count(page.total()).ok(…, SUCCESS_GET_LIST) — WarehouseController.java:136-138 |
reason là D-02; (int) bị bỏ → KHÁC (F-logic-2) |
| 12a | Serialize phần tử WarehouseGetList bằng mapper Boot ALWAYS (giữ key null) — easyposmodel:…/dto/WarehouseGetList.java:35-121 |
WarehouseListItemResponse.java:13 @JsonInclude(ALWAYS) ghi đè NON_EMPTY của starter |
KHỚP (đã sửa ở vòng 2) |
| 12b | data: [] khi không có kho — BaseWarehouseServiceImpl.java:263, BaseResultDTO.java:10 |
BaseResponseDTO.data theo NON_EMPTY → bỏ key data — WarehouseController.java:136-138 |
KHÁC (F-logic-1, còn mở — UNSURE, cần người chọn cách) |
| 13 | Lỗi SQL → 417 EXCEPTION_ERROR — LF-13 |
PersistenceException → SystemException(EXCEPTION_ERROR) 500 (WarehouseRelationalRepository.java:186-189, InventoryRelationalRepository.java:109-112) |
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ú |
|---|---|---|---|
warehouse |
đọc: com_id = ? [+ normalized_name like ?] [+ status = ?], COUNT rồi SELECT trang |
đọc: giống hệt, cùng điều kiện và cùng thứ tự | không ghi |
inventory + join product |
đọc khi countProduct=TRUE và trang không rỗng: i.com_id, p.com_id, p.active = TRUE, i.is_primary = 1, warehouse_id in (…) |
đọc cùng điều kiện, active bind Boolean.TRUE |
IN :warehouseIds không ngoặc như cũ; chỉ test bằng mock (K-13) |
| Redis gói dịch vụ | đọc (interceptor) | không | D-07 |
Transaction, khoá, idempotency, tenant
- Ranh giới transaction: cũ là
@Transactionalcấp class củaBaseWarehouseServiceImpl(LF-33), đọc DB chính. Mới là@TransactionalkhôngreadOnlyởWarehouseQueryService.getWarehousePage(gọi từ controller qua proxy, không tự gọi nội bộ), định tuyến về primary (D-06). - Khoá / cập nhật nguyên tử: không có ở cả hai (chỉ đọc).
- Idempotency, thời điểm phát event: không có event, không ghi.
- Lọc
comId: mọi câu (COUNT, SELECT trang, đếm SP ở cảinventoryvàproduct) đều cócomId.comIddùng trong truy vấn là giá trị đã kiểm với token (D-01). - N+1: không có. Mỗi request tối đa 3 câu, giống code cũ.
Lệch so với luồng mới đã duyệt (spec mục 6)
Không lệch về thứ tự bước. Chỉ lệch ở cách bind tham số lọc (record thay cho @RequestParam, F-logic-4) và cách serialize response (F-logic-1, do hạ tầng starter).
Dev cần chú ý khi review
| Vị trí | Vì sao |
|---|---|
WarehouseController.java:136-138 + DataConverterAutoConfiguration.java:51 (starter-model) |
Mapper NON_EMPTY làm mất key null và mất data khi rỗng (F-logic-1); count không ép int (F-logic-2) |
WarehouseRelationalRepository.java:192-204 |
Ép (int) offset và nhánh offset âm (D-08); không ORDER BY, Hibernate 6 trên MSSQL tự chèn order by @@version (D-05) |
WarehouseRelationalRepository.java:228-233 |
Tổng theo quy tắc PageImpl, phải khớp tuyệt đối với Spring Data |
WarehouseRelationalRepository.java toListItem/toBoolean |
Ép kiểu thủ công thay cho ConstructorResult; kiểu cột thật trên MSSQL chưa kiểm (F-logic-3, K-13) |
InventoryRelationalRepository.java:101-120 |
IN :warehouseIds không có ngoặc (Hibernate 6 phải tự bọc); thứ tự cột columns[0]=count, columns[1]=warehouse_id; chỉ test bằng mock |
WarehouseQueryService.java:66-68 |
Transaction không readOnly (D-06, trái database-performance.md:290); kiểm công ty trước mọi truy vấn (D-01) |
Vòng 2 (thu hẹp): chỉ dòng 12 viết lại theo
logic-reviewervòng 2; các bước khác, dữ liệu, transaction không đổi so với vòng 1. Dev chú ý thêm:WarehouseListItemResponse.java:13—ALWAYSở mức class là chỗ duy nhất giữ key null; gỡ đi thì mất key (TC-01-EXTRA-1 khoá).