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

Kho · warehouse/create · api

POST/api/client/page/warehouse/create

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

Phân tích luồng sau chuyển — warehouse/create — vòng 2

Kết quả nguyên văn của agent logic-reviewer (phần sau dòng === PHÂN TÍCH LUỒNG ===).

F-01 Tạo kho mới (id không gửi)

  • Điểm vào: cũ easyposbackend1:src/main/java/vn/softdreams/easypos/web/rest/WarehouseResource.java:52-57 → mới src/main/java/com/softdreams/easypos/warehouse/presentation/web/warehouse/WarehouseController.java:50-58
  • 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-15, R-16

Sơ đồ luồng mới (theo code thực tế)

sequenceDiagram
    participant C as Client
    participant WC as WarehouseController.createWarehouse
    participant WS as WarehouseManagementService.saveWarehouse (@Transactional)
    participant CC as CurrentCompanyService.requireCompanyOfCurrentUser
    participant WR as WarehouseRelationalRepository
    participant SQ as SequenceService.nextCode
    participant SS as SequenceSeedJdbcRepository.findSeed
    participant LR as LegacyRedisSequenceReader
    participant RC as RedisSequenceCounter
    participant W as Warehouse / WarehouseDuplicatePolicy
    C->>WC: POST /create
    WC->>WC: Guard.validateInput(validator, request)
    WC->>WS: saveWarehouse(toSaveCommand(request))
    WS->>CC: requireCompanyOfCurrentUser(comId)
    WS->>WR: lockCompanyWarehouses (SELECT TOP 1 + sp_getapplock, 10s)
    WS->>WS: WarehouseAudit(findUserId().orElse(0), now(clock))
    loop generateCode ≤ 10
        WS->>SQ: nextCode(comId,"WH")
        SQ->>RC: current (HGET {comId}_SEQ_NEW WH)
        alt chưa có
            SQ->>SS: findSeed → warehouseSeed
            SS->>LR: findWarehouseSequence (GET {comId}_SEQ)
            SS->>SS: không có → SELECT COUNT(*) warehouse +1
            SQ->>RC: initialize (HSET)
        else đã có
            SQ->>RC: increase (HINCRBY 1)
        end
        WS->>WR: existsBySku(comId, code)
    end
    WS->>W: Warehouse.create(command, code, audit) (copyFrom, status=true, cleanText, audit)
    WS->>WR: findNameSkus(comId)
    WS->>W: WarehouseDuplicatePolicy.validate(rows, command)
    WS->>W: validateDefaultWarehouse()
    WS->>WR: save → JpaRepository.save(toEntity) (INSERT identity)
    WS-->>WC: WarehouseResult
    WC-->>C: BaseResponseDTO.ok(WarehouseResponse, SUCCESS_CREATE)

Đố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 Bean Validation baseCommon.validateInput — easyposbackend1:.../web/rest/WarehouseResource.java:54, easyposbase:.../dto/warehouse/WarehouseSaveRequest.java:12-15 WarehouseController.java:52, dto/request/WarehouseSaveRequest.java (@NotNull comId, @NotEmpty name) KHỚP về mã lỗi; định dạng body khác có chủ đích (D-02)
2 Không kiểm comId theo token WarehouseManagementService.java:76 → CurrentCompanyService.java:30-39 KHÁC có chủ đích (D-01)
3 Không khoá WarehouseManagementService.java:77 → WarehouseRelationalRepository.java:37-49,113-121 CHỈ CÓ Ở MỚI (D-05)
4 genCode: retry 10 lần, genCodeByRedis, existsByComIdAndSKU — easyposbase:.../services/BaseSequenceService.java:51-72,253-289,436-443 WarehouseManagementService.java:101-109, SequenceService.java:23-36, RedisSequenceCounter.java:31-44 KHỚP (D-06, D-14 GIỮ)
5 Mốc: {comId}_SEQ.warehouse + 1 (int), không có thì count+1; lỗi count bị nuốt thành "1" — BaseSequenceService.java:266-285,331-335, BaseUserRepositoryImpl.java:35-50 SequenceSeedJdbcRepository.java:52-54,62-69, LegacyRedisSequenceReader.java:48-73 KHỚP ở nhánh thường; lỗi DB không nuốt (D-03); dữ liệu cũ hỏng → lỗi (D-07); tràn int khác (F-logic-1)
6 BeanUtils.copyProperties, SKU rỗng → code, status=true, normalizedName(name, request.SKU) — BaseWarehouseServiceImpl.java:182-189 Warehouse.java:76-86,154-164, WarehouseText.java KHỚP
7 Làm sạch chuỗi ở @PrePersist (sau kiểm trùng) — CleanTextListener.java:14-34 Warehouse.create → cleanText() (Warehouse.java:80,170-178), chạy trước kiểm trùng KHỚP về kết quả: kiểm trùng dùng giá trị thô của command, luật WH1 dùng code/status/sellable (không bị làm sạch đổi)
8 checkDuplicateByComId (không ORDER BY), F-01 trùng đầu tiên → lỗi, SKU trước tên — BaseWarehouseServiceImpl.java:190-205,229-245, BaseWarehouseRepository.java:37-38 WarehouseManagementService.java:92,112-120, WarehouseDuplicatePolicy.java:32-59, WarehouseJpaRepository native SELECT id, name, SKU … WHERE com_id KHỚP (D-11 GIỮ)
9 checkDataWarehouseDefault — BaseWarehouseServiceImpl.java:207,219-227 Warehouse.java:118-134 KHỚP; NPE (sellable null) → SystemException(EXCEPTION_ERROR) 500 (D-03, D-13)
10 warehouseRepository.save INSERT, audit @CreatedBy/@LastModifiedBy/@LastModifiedDate — BaseWarehouseServiceImpl.java:215, BaseAbstractAuditingEntity.java:31-52 WarehouseRelationalRepository.java:125-127, WarehouseJpaMapper.toEntity KHỚP (một thời điểm Clock cho cả hai cột thời gian — spec §7)
11 BaseResultDTO(SUCCESS, SUCCESS_CREATE, true, warehouse) WarehouseController.java:53-57, WarehouseResponse KHỚP về dữ liệu; reason khác có chủ đích (D-02)
12 Interceptor kiểm gói dịch vụ — HandleInterceptorCustom.java:86-105 (chưa có) CHỈ CÓ Ở CŨ (D-16)

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: count (khi Redis chưa có WH), exists SKU ≤10 lần, select name,SKU; ghi: INSERT 1 dòng đọc thêm SELECT TOP (1) trong lệnh khoá; count qua JdbcTemplate trên DataSource @Primary (cùng transaction); exists, select id,name,SKU; ghi: INSERT 1 dòng, mọi cột tường minh Lọc com_id ở mọi câu
Redis {comId}_SEQ_NEW field WH HGET; HSET mốc / HINCRBY 1 mỗi lần thử như cũ (RedisSequenceCounter, vùng lõi không đổi) Không hoàn số khi rollback (D-08). Hash key serializer mặc định (JDK) giống nhau ở cả hai bên: backend:config/RedisConfig.java:52-67 và starter-config:RedisAutoConfiguration.java:89-92
Redis {comId}_SEQ GET (Jackson) khi chưa có WH GET byte thô + readTree D-07; F-logic-1
MSSQL applock warehouse:save:{comId} — giữ tới hết transaction D-05
Kafka / outbox / cache không không —

Transaction, khoá, idempotency, tenant

  • Ranh giới transaction: cũ: class @Transactional BaseWarehouseServiceImpl.java:66 bao sinh mã, kiểm trùng và INSERT → mới: @Transactional ở WarehouseManagementService.java:74, bao khoá, đọc và INSERT. Controller gọi qua proxy, không có self-invocation. Redis nằm ngoài transaction ở cả hai bên.
  • Khoá / cập nhật nguyên tử: cũ không khoá → mới dùng sp_getapplock Exclusive, owner Transaction, timeout 10s (kết quả < 0 → 500). Khoá chỉ bảo vệ giữa các request của service; monolith không lấy khoá này (D-05).
  • Idempotency, thời điểm phát event: không có event; REST đồng bộ.
  • Lọc comId: có ở mọi truy vấn (count, exists, findNameSkus, lệnh khoá) và ở Redis key.

Lệch so với luồng mới đã duyệt (spec mục 6)

Không có lệch thực chất. cleanText() của F-01 chạy trong Warehouse.create (Warehouse.java:80) chứ không chạy ngay trước save như sơ đồ. Kết quả lưu không đổi.

Dev cần chú ý khi review

Vị trí Vì sao
WarehouseRelationalRepository.java:37-49,113-121 Khoá chạy được hay không phụ thuộc implicit transaction của mssql-jdbc (auto-commit: false, provider_disables_autocommit: true). Lệnh là batch nhiều câu chạy qua getSingleResult của Hibernate 6. Mới có test mock, cần chạy một lần trên MSSQL thật.
WarehouseManagementService.java:77-85 Thứ tự: khoá → Redis → INSERT. Khoá được giữ suốt thời gian gọi Redis.
SequenceSeedJdbcRepository.java:62-69, LegacyRedisSequenceReader.java:48-73 Nhánh Redis cũ (D-07), lỗi count không nuốt (D-03), tràn int (F-logic-1).
Warehouse.java:118-134 WH1 + sellable null → 500 EXCEPTION_ERROR. Lưu ý: ví dụ trong decisions.md#D-13 vẫn ghi NULL_EXCEPTION, còn R-10/D-03 và code dùng EXCEPTION_ERROR. Người cần sửa câu chữ của D-13.

F-02 /create gửi kèm id → sửa kho

  • Điểm vào: cũ easyposbackend1:src/main/java/vn/softdreams/easypos/web/rest/WarehouseResource.java:52-57 → mới src/main/java/com/softdreams/easypos/warehouse/presentation/web/warehouse/WarehouseController.java:50-58
  • Quy tắc: R-01, R-02, R-05, R-06, R-08, R-09, R-10, R-11, R-12, R-13, R-14, R-15, R-16

Sơ đồ luồng mới (theo code thực tế)

sequenceDiagram
    participant C as Client
    participant WC as WarehouseController.createWarehouse
    participant WS as WarehouseManagementService.saveWarehouse (@Transactional)
    participant CC as CurrentCompanyService
    participant WR as WarehouseRelationalRepository
    participant W as Warehouse / WarehouseDuplicatePolicy
    participant BP as BillRelationalAdapter
    C->>WC: POST /create {id,...}
    WC->>WC: Guard.validateInput
    WC->>WS: saveWarehouse(command)
    WS->>CC: requireCompanyOfCurrentUser(comId)
    WS->>WR: lockCompanyWarehouses(comId)
    WS->>WR: findById(comId,id) → JPA findByComIdAndId → toDomain (không có → SystemException WAREHOUSE_NOT_FOUND)
    WS->>W: warehouse.update(command, audit): copyFrom, status=activeStatus, so businessValues; đổi → cleanText + updater/updateTime
    WS->>WR: findNameSkus(comId)
    WS->>WS: duplicateRows: thay dòng id của kho bằng warehouse.nameSku()
    WS->>W: WarehouseDuplicatePolicy.validate (đếm, chạm 2 → lỗi)
    WS->>W: validateDefaultWarehouse()
    opt !isActive
        WS->>BP: countProcessingBills(comId, id) > 0 → BadRequest(WAREHOUSE_STATUS_FALSE_CHECK_BILL, tên hiện tại)
    end
    WS->>WR: save → findByComIdAndId (instance đã managed) → mapper.apply → toResult
    Note over WR: UPDATE (@DynamicUpdate) lúc commit, chỉ cột thay đổi
    WC-->>C: BaseResponseDTO.ok(..., 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 Validate — WarehouseResource.java:54 WarehouseController.java:52 KHỚP (D-02)
2 Không kiểm comId theo token WarehouseManagementService.java:76 KHÁC có chủ đích (D-01)
3 Không khoá WarehouseManagementService.java:77 CHỈ CÓ Ở MỚI (D-05)
4 findByComIdAndId → BaseInternalServerException 500 — BaseWarehouseServiceImpl.java:174-178 WarehouseManagementService.java:87-88, WarehouseRelationalRepository.java:96-98 KHỚP (D-04 GIỮ)
5 copyProperties toàn bộ (kể cả null), getStatus() null→false, SKU rỗng → code hiện có, normalizedName từ request — BaseWarehouseServiceImpl.java:182-189, WarehouseSaveRequest.java:75-77 Warehouse.java:97-101,154-164 KHỚP (D-09, D-10 GIỮ)
6 Hibernate auto-flush trước native checkDuplicateByComId: entity dirty → @PreUpdate làm sạch + audit + UPDATE; dòng của chính kho trả giá trị mới — BaseWarehouseRepository.java:37-38, CleanTextListener.java:14-34 Warehouse.java:101-107 (so businessValues trước khi làm sạch), WarehouseManagementService.java:112-120 (thay dòng của chính kho) KHỚP theo mô phỏng (D-12 GIỮ). Hành vi Hibernate 5 chưa được kiểm trên DB thật
7 Đếm trùng SKU/tên, chạm 2 → lỗi — BaseWarehouseServiceImpl.java:195-205,235-245 WarehouseDuplicatePolicy.java:32-59 KHỚP
8 checkDataWarehouseDefault (code null hoặc sellable null → NPE) — BaseWarehouseServiceImpl.java:219-227 Warehouse.java:118-134 KHỚP; NPE → 500 EXCEPTION_ERROR (D-03, D-13)
9 status=false → checkExistsBillProcessing(comId, request.id) > 0 → lỗi kèm tên đã làm sạch — BaseWarehouseServiceImpl.java:208-213, BaseWarehouseRepository.java:84-85 WarehouseManagementService.java:94-96,122-129, BillRelationalAdapter.java:27-35,53-59 KHỚP (SQL giống hệt; tham số tên = tên của model, đã làm sạch nếu có thay đổi)
10 UPDATE xảy ra ở lần flush trước câu kiểm trùng; không thay đổi thì không UPDATE và audit giữ nguyên WarehouseRelationalRepository.java:125-134: chép vào entity managed, UPDATE lúc commit, creator/createTime không cập nhật KHỚP về dữ liệu cuối. Thời điểm ghi khác (lúc commit thay vì giữa transaction) nhưng không có câu nào sau đó đọc lại dòng này
11 SUCCESS_UPDATE, data = entity sau flush WarehouseController.java:56, mapper.toResult(entity) KHỚP (D-02 cho reason)
12 Interceptor gói dịch vụ (chưa có) CHỈ CÓ Ở CŨ (D-16)

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, select name,SKU; ghi UPDATE cột thay đổi khi entity dirty đọc SELECT TOP (1) (khoá), findByComIdAndId 2 lần (lần 2 trả về instance đã managed), select id,name,SKU; ghi UPDATE cột thay đổi lúc commit khi có thay đổi Lọc com_id ở mọi câu
bill, bill_product COUNT khi status=false như cũ, qua BillPort Đọc chéo context bán hàng qua port
Redis không không F-02 không sinh mã
MSSQL applock — giữ tới hết transaction D-05

Transaction, khoá, idempotency, tenant

  • Ranh giới transaction: một @Transactional bao mọi thao tác, ở cả cũ và mới. Lỗi kiểm trùng, WH1 hoặc bill → rollback. Bên cũ rollback cả UPDATE đã flush; bên mới chưa ghi gì.
  • Khoá / cập nhật nguyên tử: cũ không khoá, ghi đè toàn bộ bằng dữ liệu request → mới khoá theo công ty trước khi đọc kho (D-05). Không có đọc-sửa-ghi số lượng.
  • Idempotency, event: không có.
  • Lọc comId: findByComIdAndId, findNameSkus, COUNT bill đều có com_id.

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
Warehouse.java:97-108,166-168 Mô phỏng dirty check của Hibernate 5: so 9 trường trước khi làm sạch, chỉ làm sạch và gán audit khi có thay đổi. Sai ở đây làm F-02 báo trùng sai (D-12) hoặc cập nhật sai updater/updateTime.
WarehouseManagementService.java:112-120 Dòng của chính kho trong danh sách kiểm trùng phải mang giá trị hiện tại của model. Đây là cơ sở của luật "chạm 2" ở F-02.
WarehouseRelationalRepository.java:125-134 UPDATE dựa vào dirty check của Hibernate 6 trên entity đã nạp ở findById, và mapper.apply chép đè cả cột null (mặc định SET_TO_NULL của MapStruct). Đây là chỗ ghi duy nhất của F-02.
WarehouseJpaMapper.java (toStatus/toStatusColumn) Cột status kiểu bit được map sang Integer: null giữ null, 1 thành true, còn lại thành false. Bước so thay đổi ở F-02 dựa vào chuyển đổi này.
BillRelationalAdapter.java:27-35,53-59 Nhánh lỗi ngừng kho khi còn đơn chưa xử lý; tham số tên kho lấy từ model sau khi làm sạch.

Nguồn của trang này

  • Phân tích luồng migrations/warehouse/create/verify/round-2/flow-analysis.md vòng 2 · sha256 b19c8ca8665a

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