Phân tích luồng sau chuyển — warehouse/import-excel-validate — vòng 1
Viết tắt: SVC = easyposbase:src/main/java/sds/ep/baseservice/services/impl/BaseWarehouseServiceImpl.java. Đường dẫn mới tính từ C:/Projects/warehouse-service/src/main/java/com/softdreams/easypos/warehouse/. VS = application/warehouse/WarehouseImportValidationService.java, XR = infrastructure/excel/PoiExcelImportReader.java, CK = domain/warehouse/policy/WarehouseImportSheetChecker.java, EP = domain/warehouse/policy/WarehouseImportExistingPolicy.java, WC = presentation/web/warehouse/WarehouseController.java.
F-01 Đọc file Excel (adapter POI)
- Điểm vào: cũ
easyposbackend1:src/main/java/vn/softdreams/easypos/web/rest/WarehouseResource.java:98-107 → mới WC:206-230
- Quy tắc: R-01, R-02, R-03, R-04, R-05, R-06
Sơ đồ luồng mới (theo code thực tế)
sequenceDiagram
participant C as Client
participant WC as WarehouseController.validateImport
participant VS as WarehouseImportValidationService.validateImport
participant XR as PoiExcelImportReader
participant SH as PoiExcelSheet
C->>WC: file, comId (@RequestParam), WarehouseImportSheetRequest(indexSheet, type)
WC->>WC: indexSheet null → BadRequestException(INVALID_REQUEST)
WC->>VS: WarehouseImportValidateCommand(comId, indexSheet, type, file::getInputStream)
VS->>XR: open(file, sheetIndex)
XR->>XR: load: try(stream; WorkbookFactory.create) → đóng ngay; IOException|RuntimeException → Optional.empty
XR->>XR: sheetOf: getSheetAt; IllegalArgumentException → SystemException(EXCEPTION_ERROR)
VS-->>WC: empty → failed(INVALID_FILE)
VS->>SH: lastRowNum() == 0 → failed(LIST_EMPTY)
VS->>SH: headerCellCount(): rỗng hoặc ≠ 7 → BadRequestException(WAREHOUSE_IMPORT_INVALID_FILE)
loop rowIndex = 1..min(lastRowNum, 2000), khi reading
VS->>SH: isMergedRow(i) → bỏ qua
VS->>SH: readRow(i, 7): getRow null → missingRow; mỗi ô evaluate (lỗi → SystemException) + rawValue + present/numeric
end
Đối chiếu từng bước
| # |
Luồng cũ |
Luồng mới |
Kết luận |
| 1 |
Tham số @RequestParam file/comId/indexSheet/type, không validate (WarehouseResource.java:99-105) |
WC:208-216: indexSheet/type lấy từ record model-attribute, indexSheet null → 400 INVALID_REQUEST |
KHÁC có chủ đích (D-02); khác về hình thức binding, xem "Lệch mục 5" |
| 2 |
WorkbookFactory.create + close(), catch Exception → FAIL/INVALID_FILE (SVC:850-856) |
XR:61-67 try-with-resources đóng workbook rồi đóng stream, bắt IOException | RuntimeException |
KHỚP (create chỉ khai báo IOException; đóng thêm stream không làm đổi kết quả) |
| 3 |
getSheetAt sai chỉ số → 417 (SVC:858) |
XR:49-57 → 500 EXCEPTION_ERROR |
KHÁC có chủ đích (D-02, K-11) |
| 4 |
getLastRowNum()==0 → LIST_EMPTY (SVC:860-861) |
VS:70-73 |
KHỚP |
| 5 |
dòng 0 null → 400; đếm ô tiêu đề !BLANK && !toString().trim().isEmpty() ≠ 7 → 400 (SVC:864-877) |
XR:115-127 (hasTextAfterTrim = còn ký tự > ' ', tương đương trim().isEmpty()), VS:74-77 |
KHỚP |
| 6 |
vòng i<=getMaxRowNumberImportExcel(lastRowNum), bỏ dòng gộp ô (SVC:889-895,1091-1098) |
VS:87-106, XR:129-136 |
KHỚP |
| 7 |
dòng null → NPE ở row.getCell bị bắt → check=false → dừng (SVC:904-909,994-996) |
XR:139-149 missingRow → CK.check trả rỗng → reading=false |
KHỚP |
| 8 |
evaluate + getRawValueExcel cho từng ô, xen kẽ với các bước kiểm (SVC:911-913; easyposmodel:.../BaseCommon.java:1977-2000) |
XR:80-96,151-165: đọc đủ 7 ô trước rồi mới kiểm. Lỗi evaluate → 500 |
KHỚP (thứ tự đổi nhưng không có side effect ở giữa); lỗi 417 → 500 theo D-02 |
| 9 |
present = cell!=null && !toString().isBlank() && cellString khác rỗng; numeric = getCellType()==NUMERIC (SVC:950,953) |
XR:153-154 |
KHỚP |
Dữ liệu và side effect
| Bảng / topic / cache |
Cũ |
Mới |
Ghi chú |
| File multipart |
đọc toàn bộ vào bộ nhớ |
đọc toàn bộ vào bộ nhớ |
không ghi |
Transaction, khoá, idempotency, tenant
- Ranh giới transaction: cũ là transaction cấp class bao cả phần đọc POI → mới
@Transactional ở VS:63, cũng bao cả phần đọc POI (D-14).
- Khoá / cập nhật nguyên tử: không có ở cả hai bên.
- Idempotency, event: không có.
- Lọc
comId: F-01 không truy vấn DB.
Lệch so với luồng mới đã duyệt (spec mục 6)
indexSheet/type nhận qua WarehouseImportSheetRequest thay vì @RequestParam.
Dev cần chú ý khi review
| Vị trí |
Vì sao |
XR:61-67 |
Workbook bị đóng trước khi đọc sheet (port cố ý từ code cũ). Lỗi khi đóng cũng bị coi là INVALID_FILE |
XR:49-57, XR:168-174 |
Hai chỗ bọc exception của POI thành SystemException; chỉ bắt đúng IllegalArgumentException ở getSheetAt |
WC:208-216 |
Binding model-attribute: sai kiểu số đi qua handler lỗi binding của starter, không phải MethodArgumentTypeMismatchException (D-02) |
F-02 Kiểm dòng — tạo mới (type=1)
- Điểm vào: cũ
SVC:849 → mới VS:63-84
- Quy tắc: R-05, R-07, R-08, R-09, R-11, R-13, R-14, R-15
Sơ đồ luồng mới (theo code thực tế)
sequenceDiagram
participant VS as WarehouseImportValidationService
participant CK as WarehouseImportSheetChecker.check
participant CC as CurrentCompanyService
participant WR as WarehouseRepository
participant EP as WarehouseImportExistingPolicy.checkCreate
participant WC as WarehouseController
loop mỗi dòng
VS->>CK: check(row): checkRawValue (Mã: trùng Set; Tên: rỗng/trùng Set; Trạng thái/Bán: thô rỗng → true) rồi assign(cleaned) nếu present
end
VS->>VS: lines rỗng → failed(LIST_ERROR)
VS->>VS: skus/names của dòng hợp lệ; cả hai rỗng → bỏ qua
VS->>CC: requireCompanyOfCurrentUser(comId)
VS->>WR: findAllByNameIn(comId, nonEmpty(names)) (rỗng → SystemException)
VS->>WR: findAllBySkuIn(comId, nonEmpty(skus)) (rỗng → SystemException)
VS->>EP: checkCreate: toMap (trùng khoá → SystemException); tên có → NAME_EXISTS; còn hợp lệ và SKU có → SKU_EXISTS
VS-->>WC: success(lines) → count, countValid/countInvalid
WC->>WC: toImportValidateResponse (status null → false, sellable giữ nguyên)
Đối chiếu từng bước
| # |
Luồng cũ |
Luồng mới |
Kết luận |
| 1 |
Kiểm Tên (rỗng/trùng nameSet) và Mã (trùng skuSet, CREATE) trên giá trị thô (SVC:915-937) |
CK:82-117 |
KHỚP |
| 2 |
Trạng thái/Bán trực tiếp: giá trị thô rỗng → true (SVC:938-949) |
CK:86-95 |
KHỚP |
| 3 |
present → gộp \s{2,}, trim, bỏ .0 cho cột 0-2 khi NUMERIC; gán field; SĐT \d+ (SVC:950-987) |
CK:119-148 |
KHỚP |
| 4 |
!check → dừng; ngược lại thêm dòng (SVC:993-1003) |
CK:64-79, VS:96-104 |
KHỚP |
| 5 |
dataResponse rỗng → FAIL/LIST_ERROR (SVC:1006-1008) |
VS:79-81, WC:220-226 |
KHỚP |
| 6 |
skuList/nameList của dòng hợp lệ, khác rỗng; điều kiện !sku.isEmpty() || !name.isEmpty() (SVC:1010-1024) |
VS:110-116,128-137 |
KHỚP |
| 7 |
— |
VS:117 requireCompanyOfCurrentUser |
CHỈ CÓ Ở MỚI, có chủ đích (D-01) |
| 8 |
findByNameIn rồi findBySkuIn, danh sách rỗng vẫn gọi (SVC:1055-1059; BaseWarehouseRepository.java:113-118) |
VS:121-122 + nonEmpty ném lỗi; SQL native giống hệt (WarehouseJpaRepository name in :names and com_id = :comId) |
KHÁC có chủ đích (D-04 GIỮ: lỗi hệ thống 417 → 500) |
| 9 |
Collectors.toMap trùng khoá → IllegalStateException 417 (SVC:1056,1059) |
EP:72-77 → 500 |
KHÁC có chủ đích (D-09, D-02) |
| 10 |
Tên có → lỗi; còn hợp lệ và SKU có → lỗi, cập nhật đếm (SVC:1064-1078) |
EP:54-69; đếm lại ở WarehouseImportValidation.success |
KHỚP (đếm lại cho cùng kết quả vì mỗi dòng chỉ bị đánh dấu sai một lần) |
| 11 |
BaseResultDTO(SUCCESS, VALIDATE_SUCCESS, true, ValidateImportResponse, countTotal) (SVC:1081-1087) |
WC:227-229, WarehouseWebMapper.java:42-70 |
KHỚP về giá trị; key null/rỗng bị bỏ theo D-02 |
Dữ liệu và side effect
| Bảng / topic / cache |
Cũ |
Mới |
Ghi chú |
warehouse |
đọc name in + SKU in, com_id = ? |
đọc như cũ, cùng thứ tự |
không ghi; ≤ 2000 tham số |
| Redis gói dịch vụ |
interceptor đọc |
không |
D-03 |
Transaction, khoá, idempotency, tenant
- Ranh giới transaction: cũ cấp class → mới
@Transactional (không readOnly) ở VS:63, D-14.
- Khoá: không có.
- Idempotency, event: không có.
- Lọc
comId: cả hai truy vấn đều có com_id; mới thêm bước kiểm công ty của người đăng nhập (D-01).
Lệch so với luồng mới đã duyệt (spec mục 6)
Phần đánh dấu lỗi DB tách sang WarehouseImportExistingPolicy (domain), không nằm trong application service.
Dev cần chú ý khi review
| Vị trí |
Vì sao |
VS:117-123, VS:140-145 |
Thứ tự: kiểm công ty → nonEmpty(names) → truy vấn tên → nonEmpty(skus) → truy vấn mã. Cố ý tái hiện lỗi IN () (D-04, UNVERIFIED) |
EP:72-77 |
Hàm gộp ném SystemException để giữ D-09 |
CK:82-117 vs CK:119-148 |
Kiểm trên giá trị thô, gán giá trị đã làm sạch; ô chỉ có khoảng trắng Unicode không được gán |
F-03 Kiểm dòng — cập nhật (type=2)
- Điểm vào: cũ
SVC:849 → mới VS:63-84
- Quy tắc: R-05, R-07, R-08, R-09, R-10, R-13, R-14, R-15
Sơ đồ luồng mới (theo code thực tế)
sequenceDiagram
participant VS as WarehouseImportValidationService
participant CK as WarehouseImportSheetChecker.check
participant CC as CurrentCompanyService
participant WR as WarehouseRepository
participant EP as WarehouseImportExistingPolicy.checkUpdate
loop mỗi dòng
VS->>CK: check(row): Mã thô rỗng → SKU_EMPTY_ON_UPDATE; Tên rỗng; không kiểm trùng trong file
end
VS->>VS: lines rỗng → LIST_ERROR; skus/names rỗng cả hai → bỏ qua
VS->>CC: requireCompanyOfCurrentUser(comId)
VS->>WR: findAllBySkuIn(comId, nonEmpty(skus)) (rỗng → SystemException)
VS->>EP: checkUpdate: toMap; hợp lệ và không có SKU → SKU_NOT_FOUND; có SKU → code null? SystemException; WH1 && hợp lệ && !status && sellable null? SystemException : !sellable → DEFAULT_WAREHOUSE_LOCKED
Đối chiếu từng bước
| # |
Luồng cũ |
Luồng mới |
Kết luận |
| 1 |
Mã thô rỗng khi UPDATE → lỗi (SVC:926-931) |
CK:110-113 |
KHỚP |
| 2 |
findBySkuIn(skuList, comId) (SVC:1028) |
VS:119 (sau kiểm công ty; skus rỗng → SystemException) |
KHÁC có chủ đích (D-01); trường hợp skus rỗng xem F-logic-1 |
| 3 |
toMap trùng → 417 (SVC:1029) |
EP:72-77 → 500 |
KHÁC có chủ đích (D-09) |
| 4 |
Hợp lệ và không có SKU trong map → "Không tìm thấy" (SVC:1034-1039) |
EP:34-37 |
KHỚP |
| 5 |
containsKey && getCode().equals(WH1) && statusImport && !getStatus() && !getSellable() (NPE ở code null / sellable null) (SVC:1041-1048) |
EP:39-42,79-93, !Boolean.TRUE.equals(status) tương đương getStatus() null→false (WarehouseSaveRequest.java:75-77) |
KHỚP (NPE → 500, D-02) |
Dữ liệu và side effect
| Bảng / topic / cache |
Cũ |
Mới |
Ghi chú |
warehouse |
đọc SKU in, com_id = ? |
như cũ |
không ghi |
Transaction, khoá, idempotency, tenant
- Ranh giới transaction: như F-02 (D-14).
- Khoá: không có.
- Idempotency, event: không có.
- Lọc
comId: có; thêm kiểm công ty (D-01).
Lệch so với luồng mới đã duyệt (spec mục 6)
Như F-02. Ngoài ra nhánh UPDATE cũng dùng nonEmpty(skus), spec không mô tả (F-logic-1).
Dev cần chú ý khi review
| Vị trí |
Vì sao |
VS:119 |
nonEmpty(skus) ở UPDATE: chỉ xảy ra khi ô Mã chỉ chứa khoảng trắng Unicode (F-logic-1) |
EP:39-42 |
Thứ tự ngắn mạch giữ đúng như cũ: getCode() được gọi cả với dòng đã lỗi (D-06, R-10) |
F-04 Kiểm dòng — type khác / không gửi
- Điểm vào: cũ
SVC:849 → mới VS:63-84
- Quy tắc: R-05, R-07, R-08, R-12, R-13, R-15
Sơ đồ luồng mới (theo code thực tế)
sequenceDiagram
participant VS as WarehouseImportValidationService
participant CK as WarehouseImportSheetChecker.check
participant WC as WarehouseController
loop mỗi dòng
VS->>CK: check(row): chỉ kiểm Tên rỗng và SĐT
end
VS->>VS: lines rỗng → LIST_ERROR
VS->>VS: checkExisting: !update && !create → return (không kiểm công ty, không truy vấn)
VS-->>WC: success(lines)
Đối chiếu từng bước
| # |
Luồng cũ |
Luồng mới |
Kết luận |
| 1 |
Không kiểm trùng trong file, không kiểm mã rỗng (SVC:919,926,932) |
CK:102-117 (so Integer.valueOf(TYPE).equals(type), null an toàn) |
KHỚP |
| 2 |
Không vào nhánh UPDATE/CREATE (SVC:1026,1053) |
VS:114-116 |
KHỚP |
| 3 |
Response thành công (SVC:1081-1087) |
WC:227-229 |
KHỚP (D-02 về key rỗng) |
Dữ liệu và side effect
| Bảng / topic / cache |
Cũ |
Mới |
Ghi chú |
warehouse |
không đọc |
không đọc |
— |
Transaction, khoá, idempotency, tenant
- Ranh giới transaction:
@Transactional mở nhưng không có truy vấn.
- Khoá: không có.
- Idempotency, event: không có.
- Lọc
comId: không truy vấn nên không kiểm công ty, đúng D-01.
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 |
VS:114-116 |
Điều kiện bỏ qua kiểm DB gộp cả "không có giá trị" và "type lạ". Đặt trước bước kiểm công ty nên comId sai không bị chặn (giống cũ, D-01) |