Báo cáo chuyển đổi warehouse-service

Kho · warehouse/import-excel-validate · api

POST/api/client/page/warehouse/import-excel/validate

Vòng
1
Cổng G1–G6
PENDING_SONAR
Bước
sonar-check
Nhánh đích
feat/thongpd/migrate-warehouse

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)

Nguồn của trang này

  • Phân tích luồng migrations/warehouse/import-excel-validate/verify/round-1/flow-analysis.md vòng 1 · sha256 63dfd5fb91ca

v1 dựng 2026-11-06 00:42 · base commit đích 61e71d89fd