Phân tích luồng sau chuyển — warehouse/update — vòng 1
F-01 Sửa kho
- Điểm vào: cũ
easyposbackend1:src/main/java/vn/softdreams/easypos/web/rest/WarehouseResource.java:59-67→ mớisrc/main/java/com/softdreams/easypos/warehouse/presentation/web/warehouse/WarehouseController.java:69-80 - Quy tắc: R-01, R-02, R-03, R-04, R-05, R-06, R-07, R-08, R-09, R-10, R-11, R-12, R-13, R-14
Sơ đồ luồng mới (theo code thực tế)
sequenceDiagram
participant C as Client
participant WC as WarehouseController.updateWarehouse
participant G as Guard.validateInput
participant WS as WarehouseManagementService.saveWarehouse (@Transactional)
participant CC as CurrentCompanyService.requireCompanyOfCurrentUser
participant WR as WarehouseRelationalRepository
participant W as Warehouse
participant DP as WarehouseDuplicatePolicy.validate
participant BP as BillRelationalAdapter.countProcessingBills
C->>WC: PUT /update (WarehouseSaveRequest)
WC->>WC: request.id()==null → BadRequestException(ID_NOT_NULL)
WC->>G: validateInput(validator, request) → INVALID_REQUEST (mọi vi phạm)
WC->>WS: saveWarehouse(mapper.toSaveCommand(request))
WS->>CC: requireCompanyOfCurrentUser(comId) → INVALID_COMPANY / COMPANY_NOT_OF_CURRENT_USER / 401
WS->>WR: lockCompanyWarehouses(comId) (SELECT TOP 1 + sp_getapplock)
WS->>WS: WarehouseAudit(findUserId() or 0, now(clock))
WS->>WR: findById(comId,id) → không có: SystemException(WAREHOUSE_NOT_FOUND)
WS->>W: update(command, audit): copyFrom, status=activeStatus, so businessValues; đổi → cleanText + updater/updateTime
WS->>WR: findNameSkus(comId) (native, không ORDER BY)
WS->>WS: duplicateRows: thay dòng id==warehouse.id bằng warehouse.nameSku()
WS->>DP: validate(rows, command) (SKU trước tên, chạm 2 → lỗi)
WS->>W: validateDefaultWarehouse() (code null/sellable null → SystemException EXCEPTION_ERROR)
opt !warehouse.isActive()
WS->>BP: countProcessingBills(comId, command.id) > 0 → BadRequestException(WAREHOUSE_STATUS_FALSE_CHECK_BILL, name)
end
WS->>WR: save(warehouse): findByComIdAndId → mapper.apply(entity, warehouse) → UPDATE lúc commit (@DynamicUpdate, chỉ khi dirty)
WR-->>WS: WarehouseResult (toResult(entity))
WS-->>WC: WarehouseResult
WC-->>C: 200 BaseResponseDTO.ok(WarehouseResponse, SUCCESS_UPDATE)
Đố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 | Interceptor kiểm gói easyposbackend1:…/config/HandleInterceptorCustom.java:86-105 |
(không có) | CHỈ CÓ Ở CŨ, có chủ đích (D-12) |
| 2 | id == null → BaseBadRequestAlertException(ID_NOT_NULL) trước validate, easyposbackend1:…/web/rest/WarehouseResource.java:61-63 |
WarehouseController.java:72-74 |
KHỚP (body lỗi theo starter: D-02) |
| 3 | baseCommon.validateInput WarehouseResource.java:64 |
Guard.validateInput WarehouseController.java:75 |
KHỚP (định dạng INVALID_REQUEST: D-02) |
| 4 | Không kiểm comId theo token (LF-20) | WarehouseManagementService.java:77, CurrentCompanyService.java:30-39 |
KHÁC có chủ đích (D-01) |
| 5 | Không khoá | WarehouseManagementService.java:78, WarehouseRelationalRepository.java:113-122 |
KHÁC có chủ đích (D-05) |
| 6 | findByComIdAndId → BaseInternalServerException(WAREHOUSE_NOT_FOUND) easyposbase:…/BaseWarehouseServiceImpl.java:174-178 |
WarehouseManagementService.java:88-89 |
KHỚP (500, D-04 GIỮ) |
| 7 | BeanUtils.copyProperties (kể cả null, getStatus() null→false) BaseWarehouseServiceImpl.java:182; SKU rỗng → code :183-185; normalizedName(request.name, request.SKU) :189 |
Warehouse.java:97-101,154-164 |
KHỚP (D-06, D-07 GIỮ) |
| 8 | Hibernate flush entity dirty trước native checkDuplicateByComId → @PreUpdate làm sạch + audit, UPDATE ghi ngay BaseWarehouseServiceImpl.java:190 (LF-34) |
Làm sạch + audit trong bộ nhớ khi businessValues khác Warehouse.java:101-106; UPDATE để tới commit WarehouseRelationalRepository.java:129-133 |
Giá trị sau làm sạch và audit KHỚP; thời điểm ghi DB KHÁC (F-logic-1) |
| 9 | Dòng của chính kho trong kết quả kiểm trùng mang giá trị đã flush BaseWarehouseServiceImpl.java:190 |
WarehouseManagementService.java:113-121 (thay bằng nameSku() hiện tại) |
KHỚP (D-09, D-14 GIỮ) |
| 10 | Vòng đếm SKU trước tên, có id thì chạm 2 mới lỗi BaseWarehouseServiceImpl.java:195-205,230-245 |
WarehouseDuplicatePolicy.java:32-59 |
KHỚP (D-08 GIỮ) |
| 11 | checkDataWarehouseDefault: code.equals("WH1"), !status || !sellable (NPE khi null) BaseWarehouseServiceImpl.java:207,219-227 |
Warehouse.java:118-134 |
KHỚP (NPE → SystemException(EXCEPTION_ERROR) 500: D-03, D-10) |
| 12 | !status → checkExistsBillProcessing(comId, request.id), >0 → lỗi kèm warehouse.getName() BaseWarehouseServiceImpl.java:208-213; SQL easyposbase:…/BaseWarehouseRepository.java:84-85 |
WarehouseManagementService.java:95-97,123-130; SQL BillRelationalAdapter.java:27-35,53-59 |
KHỚP (SQL giống từng chữ; tên = tên sau làm sạch nếu có đổi) |
| 13 | warehouseRepository.save(warehouse) (merge, entity managed) BaseWarehouseServiceImpl.java:215 |
WarehouseRelationalRepository.java:125-134 |
KHỚP (UPDATE @DynamicUpdate chỉ khi dirty) |
| 14 | BaseResultDTO(SUCCESS, SUCCESS_UPDATE, true, warehouse) BaseWarehouseServiceImpl.java:216 |
WarehouseController.java:76-79 |
KHỚP (body theo starter: D-02) |
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 findByComIdAndId; đọc select name, SKU … where com_id=?; UPDATE khi dirty, ghi ở lần flush trước câu kiểm trùng |
Đọc SELECT TOP 1 (mở transaction cho khoá); đọc findByComIdAndId (2 lần: tải và trong save); đọc SELECT id,name,SKU … where com_id; UPDATE khi dirty, ghi lúc commit |
Lọc com_id ở mọi câu. Thời điểm ghi khác: F-logic-1 |
bill, bill_product |
COUNT khi status=false |
COUNT khi !isActive(), cùng SQL |
Đọc chung DB, qua BillPort |
| Khoá ứng dụng MSSQL | — | sp_getapplock warehouse:save:<comId>, phạm vi transaction |
D-05 |
| Redis / sequence | — | — (TC-08 khẳng định không gọi sequenceService) |
KHỚP |
| Gói dịch vụ | Interceptor đọc PackageCacheService |
— | D-12 |
Transaction, khoá, idempotency, tenant
- Ranh giới transaction: cũ dùng
@Transactionalcấp class trênBaseWarehouseServiceImpl(easyposbase:…/BaseWarehouseServiceImpl.java:66), kiểm id và validate nằm ngoài, ở controller. Mới dùng@TransactionaltrênWarehouseManagementService.saveWarehouse(WarehouseManagementService.java:75), gọi từ controller qua proxy, không có self-invocation. Kiểm id và validate cũng ở controller, ngoài transaction. Khớp. - Khoá / cập nhật nguyên tử: cũ không khoá; khi entity dirty thì khoá X dòng của chính kho từ lúc flush (trước câu kiểm trùng). Mới khoá applock theo công ty trước khi tải kho; khoá X dòng chỉ có lúc commit. Request của service chạy lần lượt; giữa monolith và service không có bảo vệ (D-05). Đã xét thứ tự khoá monolith ↔ service, không thấy vòng chờ mới.
- Idempotency, thời điểm phát event: không có event.
PUTlặp lại cùng body thìbusinessValueskhông đổi → không làm sạch, không gán audit, Hibernate không UPDATE. - Lọc
comId: có ởfindByComIdAndId,findNameSkusByComId, câu COUNT đơn, câu SELECT trong khoá và tên tài nguyên khoá.comIddùng là giá trị đã kiểm theo token.
Lệch so với luồng mới đã duyệt (spec mục 6)
Không. Code chỉ có thêm WarehouseAudit dựng sau khoá và save tải lại entity trước mapper.apply, cả hai là chi tiết hiện thực của phần tái sử dụng, không đổi thứ tự nghiệp vụ.
Dev cần chú ý khi review
| Vị trí | Vì sao |
|---|---|
WarehouseController.java:72-75 |
Kiểm id phải đứng trước Guard.validateInput (D-13); đảo thứ tự là đổi body lỗi |
Warehouse.java:97-108 |
Thay cho dirty check của Hibernate: tập businessValues phải đúng các cột request có thể đổi (thiếu một cột là audit/làm sạch sai) |
WarehouseManagementService.java:113-121 |
Mô phỏng flush trước câu kiểm trùng (K-01): dòng của chính kho mang giá trị hiện tại; sai ở đây làm lệch D-09/D-14 |
WarehouseManagementService.java:95-97 |
Tham số lỗi là warehouse.getName() sau làm sạch, không phải tên đang lưu |
WarehouseRelationalRepository.java:125-134 |
UPDATE chỉ xảy ra lúc commit, sau mọi kiểm nghiệp vụ (khác thứ tự lỗi DB: F-logic-1); tải lại entity bằng comId của domain |
WarehouseRelationalRepository.java:37-48,113-122 |
sp_getapplock chỉ kiểm bằng mock (K-12, K-13), cần chạy trên MSSQL thật |