Phân tích luồng sau chuyển — rsinoutward/html-preview — vòng 1
logic-reviewer viết dựa trên code đã implement (không phải thiết kế trong spec), đối chiếu với code cũ.
Viết tắt: S = easyposbackend1:src/main/java/vn/softdreams/easypos/service/impl/RsInoutWardManagementServiceImpl.java;
đường dẫn mới tính từ src/main/java/com/softdreams/easypos/warehouse/.
F-01 Kiểm, đọc phiếu, mẫu, separator, trả kết quả
- Điểm vào: cũ
easyposbackend1:src/main/java/vn/softdreams/easypos/web/rest/RsInoutWardManagementResource.java:144-153 → mới presentation/web/rsinoutward/RsInoutwardQueryController.java:115-130
- Quy tắc: R-01, R-02, R-03, R-04, R-05, R-06, R-13, R-14
Sơ đồ luồng mới (theo code thực tế)
sequenceDiagram
participant C as Client
participant Ctl as RsInoutwardQueryController.getHtmlPreview
participant S as RsInoutwardHtmlPreviewQueryService.getHtmlPreview (readOnly tx)
participant U as CurrentUserPort
participant D as RsInoutwardDetailRepository
participant P as PrintTemplateRelationalAdapter
participant K as CompanyConfigRelationalAdapter
participant UP as UserProfileRelationalAdapter
C->>Ctl: GET /api/page/rs-inoutward/html-preview/{id}
Ctl->>S: getHtmlPreview(id) (try: RuntimeException → SystemException EXCEPTION_ERROR)
S->>U: findCompanyId() (rỗng → COMPANY_NOT_EXISTS_CODE)
S->>D: findHeader(id, comId) (rỗng → RS_INOUT_WARD_ID_NOT_FOUND)
S->>D: findLines(id, comId)
S->>P: findSelected(comId, 9) — SELECT TOP 1 content, page_size (rỗng → PRINT_TEMPLATE_NULL)
S->>K: findSeparatorRawValue(comId) (≥2 dòng / value NULL → EXCEPTION_ERROR)
S->>S: PrintSeparatorParser.parse | PrintSeparator.defaults()
S->>U: findUserId()
S->>UP: findFullName(userId) (không có → "")
S->>S: RsInoutwardPrintDataBuilder.build (F-02)
S->>S: PrintTemplateRenderer.render (F-03)
S-->>Ctl: RsInoutwardHtmlPreviewResult(reviewContent, pageSize)
Ctl-->>C: 200 BaseResponseDTO.ok(RsInoutwardPrintTemplateResponse, SUCCESS_LOAD_DATA)
Đố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 |
RsInoutWardManagementResource.java:144-146 GET, @Timed, không kiểm quyền |
RsInoutwardQueryController.java:115-117 |
KHỚP (body lỗi type mismatch theo starter — D-02) |
| 2 |
RsInoutWardManagementResource.java:147-152 bọc mọi Exception → 417 EXCEPTION_ERROR |
RsInoutwardQueryController.java:119-123 bọc RuntimeException → 500 EXCEPTION_ERROR |
KHÁC có chủ đích (D-03, D-04) |
| 3 |
S:8131 getUserWithAuthorities() (user status=1, comId, fullName) |
RsInoutwardHtmlPreviewQueryService.java:71-72 chỉ kiểm comId |
KHÁC có chủ đích (D-01) |
| 4 |
S:8133-8136 getOneById null → lỗi |
RsInoutwardHtmlPreviewQueryService.java:73-74 |
KHỚP |
| 5 |
S:8137-8140 getDetailByRsInoutWardId, rỗng → detail null |
RsInoutwardHtmlPreviewQueryService.java:75 (list rỗng → không dòng) |
KHỚP |
| 6 |
S:8142-8146; PrintTemplateRepository.java:43-44 select top 1, không ORDER BY |
PrintTemplateRelationalAdapter.java:25-29, 39-51 |
KHỚP (D-07) |
| 7 |
S:8148-8154 config separator, mặc định ./,; JSON hỏng/null/value NULL/≥2 dòng → lỗi |
CompanyConfigRelationalAdapter.java:66-95; PrintSeparatorParser.java:30-41 |
KHỚP |
| 8 |
fullName lấy từ user nạp ở bước 3 |
RsInoutwardHtmlPreviewQueryService.java:81 đọc ep_user.full_name theo id |
KHÁC có chủ đích (D-05) |
| 9 |
S:8156-8162 buildPrintData, updateContentForRs |
RsInoutwardHtmlPreviewQueryService.java:82-84 |
KHỚP (chi tiết F-02, F-03) |
| 10 |
S:8164-8168 {reviewContent, pageSize}, SUCCESS_LOAD_DATA |
RsInoutwardHtmlPreviewQueryService.java:85; RsInoutwardQueryController.java:124-129; RsInoutwardPrintTemplateResponse @JsonInclude(ALWAYS) |
KHỚP (text theo starter — D-02, D-11) |
| 11 |
S:8127-8129 kiểm gói, @ReadQuery |
không kiểm gói; @Transactional(readOnly = true) |
KHÁC có chủ đích (D-09, D-10) |
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ú |
rs_inoutward + join, rs_inoutward_detail + join |
đọc, id + comId |
đọc qua RsInoutwardDetailRepository (unit by-id), id + comId |
|
print_template |
đọc select top 1 *, com_id, type_template=9, is_selected=1 |
đọc SELECT TOP 1 content, page_size, cùng điều kiện |
chỉ lấy 2 cột dùng tới |
config |
đọc entity company_id, code='separator' |
đọc native value, cùng điều kiện |
|
ep_user / cache Redis user |
đọc theo username, status=1 (qua cache) |
đọc full_name theo id (không cache, không lọc status) |
D-05 |
| Ghi |
không |
không |
chỉ đọc |
Transaction, khoá, idempotency, tenant
- Ranh giới transaction: cũ
@Transactional(readOnly = true) + @ReadQuery ở S:8127-8130 → mới @Transactional(readOnly = true) ở RsInoutwardHtmlPreviewQueryService.java:69; controller bọc lỗi ngoài proxy nên lỗi mở/commit cũng ra EXCEPTION_ERROR.
- Khoá / cập nhật nguyên tử: không có (chỉ đọc).
- Idempotency, thời điểm phát event: không có event.
- Lọc
comId: header, dòng, print_template, config đều lọc theo comId trong token; ep_user lọc theo id user trong token (cũ theo username — không theo công ty ở cả hai).
Lệch so với luồng mới đã duyệt (spec mục 6)
không — thêm bước UserProfilePort/PrintSeparatorParser nằm trong "comId, phiếu + dòng, mẫu, separator, tên NV" của sơ đồ spec.
Dev cần chú ý khi review
| Vị trí |
Vì sao |
RsInoutwardQueryController.java:119-123 |
Bọc mọi lỗi thành EXCEPTION_ERROR — mã riêng (RS_INOUT_WARD_ID_NOT_FOUND, PRINT_TEMPLATE_NULL) cố ý không ra client (D-04). |
PrintTemplateRelationalAdapter.java:52-60 |
content đọc bằng native query rồi toString(): nếu cột nvarchar(max)/ntext được Hibernate trả về dạng Clob thì toString() sai — chỉ test bằng mock, UNVERIFIED trên MSSQL thật (K-13). |
CompanyConfigRelationalAdapter.java:58-95 |
Tách findRawValues dùng chung với findOverStockRawValue (file của unit trước) — hành vi over_stock giữ nguyên; chạy lại test của unit đó. |
UserProfileRelationalAdapter.java:21-23 |
Truy vấn ep_user theo id, không lọc status — khác cũ có chủ đích (D-05). |
F-02 Dựng dữ liệu in
- Điểm vào: cũ
S:7775 buildPrintData → mới application/rsinoutward/RsInoutwardPrintDataBuilder.java:117 build
- Quy tắc: R-07, R-08
Sơ đồ luồng mới (theo code thực tế)
sequenceDiagram
participant S as RsInoutwardHtmlPreviewQueryService
participant B as RsInoutwardPrintDataBuilder
participant L as LegacyAmount
participant W as VietnameseNumberWords
S->>B: new(employeeName, printTime=now(clock, Asia/Ho_Chi_Minh)).build(header, rows)
B->>B: 50 key = null; gán đầu phiếu (orElse như Optional.ofNullable)
B->>B: putParty (type 1 NCC, type 2 KH ?: NCC, khác "")
B->>L: scaleOrZero(amount / discountAmount / quantity)
B->>B: putLines: sort ổn định theo position (>1 dòng), bỏ dòng rỗng
B->>L: scaleOrZero, vatAmount cho từng dòng
B->>W: toWords(total) (IOOBE → "")
B-->>S: Map tên thuộc tính → giá trị
Đố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 |
S:7778-7790 companyName/address/documentType/billCode/description/employeeName/paymentMethod/area |
RsInoutwardPrintDataBuilder.java:119-130 |
KHỚP (type 3 → "PHIẾU XUẤT KHO" — D-06) |
| 2 |
S:7792-7793 createdDate = now Asia/Ho_Chi_Minh |
RsInoutwardHtmlPreviewQueryService.java:82; RsInoutwardPrintDataBuilder.java:131 |
KHỚP |
| 3 |
S:7795-7799 phone/email/bank công ty, paymentDate = rs.getDate() (ZonedDateTime múi JVM) |
RsInoutwardPrintDataBuilder.java:132-136 (LocalDateTime.atZone(Asia/Ho_Chi_Minh)) |
KHÁC có chủ đích (D-10) — giờ tường như nhau khi JVM cũ GMT+7 |
| 4 |
S:7801-7855 thông tin đối tác theo type, rồi ?: mặc định ("Chưa có địa chỉ", "Không có") |
RsInoutwardPrintDataBuilder.java:137, 146-159 (PARTY_FIELDS) |
KHỚP |
| 5 |
S:7857-7860 amount/discountAmount/totalQuantity qua getter (0.0 / setScale(6) có thể ArithmeticException), taxCode |
RsInoutwardPrintDataBuilder.java:138-141; LegacyAmount.scaleOrZero |
KHỚP |
| 6 |
S:7865-7867 sort theo position khi > 1 dòng |
RsInoutwardPrintDataBuilder.java:163-166 |
KHỚP |
| 7 |
S:7872-7878 bỏ dòng tên+mã rỗng, SL=0, giá=0 |
RsInoutwardPrintDataBuilder.java:183-188 |
KHỚP |
| 8 |
S:7880-7896 quantity/unitPrice max 0 scale 6 HALF_UP; lineAmount; discount âm → 0; VAT; lineTotal |
RsInoutwardPrintDataBuilder.java:190-201 |
KHỚP |
| 9 |
S:7898-7910 dòng in 9 thuộc tính, stt chỉ đếm dòng giữ lại, cộng tổng |
RsInoutwardPrintDataBuilder.java:170-174, 203-213 |
KHỚP |
| 10 |
S:7914-7923 billProducts, totalAmount = totalPreTax = Σ, numberToWords (Throwable → "") |
RsInoutwardPrintDataBuilder.java:176-179, 222-228; VietnameseNumberWords.java:36-56 |
KHỚP |
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ú |
| (không) |
tính trong bộ nhớ |
tính trong bộ nhớ |
|
Transaction, khoá, idempotency, tenant
- Ranh giới transaction: trong transaction readOnly của F-01 ở cả hai.
- Khoá / cập nhật nguyên tử: không.
- Idempotency, thời điểm phát event: không.
- Lọc
comId: không truy vấn.
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 |
RsInoutwardPrintDataBuilder.java:190-201 |
Thứ tự scale/âm → 0/VAT/lineTotal quyết định tiền in; BigDecimal.ZERO (scale 0) cho số âm giữ đúng như cũ. |
RsInoutwardPrintDataBuilder.java:136 |
Ngày phiếu đổi từ LocalDateTime sang ZonedDateTime Asia/Ho_Chi_Minh — phụ thuộc D-10 (múi JVM cũ). |
RsInoutwardPrintDataBuilder.java:222-228 |
Chỉ bắt IndexOutOfBoundsException (cũ Throwable) — đủ cho mọi lỗi của thuật toán hiện tại; sửa VietnameseNumberWords sau này phải giữ điều này. |
F-03 Thay biến mẫu
- Điểm vào: cũ
S:7961 updateContentForRs → mới domain/rsinoutward/model/PrintTemplateRenderer.java:45 render
- Quy tắc: R-09, R-10, R-11, R-12
Sơ đồ luồng mới (theo code thực tế)
sequenceDiagram
participant S as RsInoutwardHtmlPreviewQueryService
participant R as PrintTemplateRenderer
participant V as PrintTemplateVariables
participant T as PrintTableRenderer
participant L as PrintTableLookup
participant F as LegacyNumberFormat
S->>R: render(content, data, separator)
R->>V: defaults() (121 mục, parse 1 lần)
loop mục không isTable
R->>F: formatNumber (BigDecimal) / formatDate / String.valueOf
R->>R: replaceOutsideRow = replaceAll("(?s)(?!<tr.*?)"+quote(var), value)
end
R->>T: render(html, data)
loop billProducts, toppings, surcharges
T->>L: findRowTemplate(probe, html), tableList(data, table)
alt không phải List
T->>T: xoá dòng mẫu nếu có
else không có dòng mẫu
T->>T: joinWithoutRowTemplate (", ")
else danh sách rỗng
T->>T: xoá dòng mẫu
else
T->>T: expandRows (stt, quantity strip, #,##0)
end
end
R-->>S: reviewContent
Đố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 |
S:8160 parse CONTENT_PARAM_DEFAULT (Constants.java:55-62, dòng 61 là comment) |
PrintTemplateVariables.java:26-202 |
KHỚP (121 mục, cùng thứ tự và cờ) |
| 2 |
S:7969 html/configs/data null → trả html |
PrintTemplateRenderer.java:46-48 |
KHỚP |
| 3 |
S:7971-8011 giá trị biến thường: null → "", BigDecimal → currencyFormatterNumber, ngày in/thanh toán theo biến, còn lại String.valueOf |
PrintTemplateRenderer.java:51-56, 69-94; LegacyNumberFormat.java:26-32 |
KHỚP |
| 4 |
S:8013 replaceAll không quote chuỗi thay thế |
PrintTemplateRenderer.java:64-66 |
KHỚP (giữ lỗi $/\ — D-08) |
| 5 |
S:8016-8024 tên bảng theo thứ tự gặp, rỗng → "rows" |
PrintTableRenderer.java:36-47 |
KHỚP |
| 6 |
S:8027-8035 biến dò đầu tiên khác rỗng |
PrintTableRenderer.java:68-79 |
KHỚP |
| 7 |
S:8037; S:7947-7959 getContent dòng mẫu |
PrintTableLookup.java:21-35 |
KHỚP |
| 8 |
S:8039-8049 danh sách + fallback billProducts↔rows; không phải List → xoá dòng mẫu |
PrintTableLookup.java:44-53; PrintTableRenderer.java:54-57 |
KHỚP |
| 9 |
S:8053-8096 nhân dòng: no → stt, quantity strip, BigDecimal #,##0, null → "" ; replaceAll(quote(var), val); rỗng → xoá |
PrintTableRenderer.java:58-64, 81-111 |
KHỚP |
| 10 |
S:8097-8120 không có dòng mẫu: nối ", ", bỏ null, replaceAll như biến thường |
PrintTableRenderer.java:113-141 |
KHỚP |
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ú |
| (không) |
xử lý chuỗi trong bộ nhớ |
xử lý chuỗi trong bộ nhớ |
dữ liệu in là Map thay reflection |
Transaction, khoá, idempotency, tenant
- Ranh giới transaction: trong transaction readOnly của F-01 ở cả hai.
- Khoá / cập nhật nguyên tử: không.
- Idempotency, thời điểm phát event: không.
- Lọc
comId: không truy vấn.
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 |
PrintTemplateRenderer.java:64-66, PrintTableRenderer.java:89 |
Cố ý không dùng Matcher.quoteReplacement (D-08): giá trị chứa $n làm lỗi cả bản in — đừng "sửa" khi refactor. |
LegacyNumberFormat.java:27 |
Locale.ROOT thay locale mặc định JVM của cũ; chỉ khác nếu JVM cũ chạy locale có dấu trừ/chữ số không phải ASCII — xác nhận bằng golden master. |
PrintTemplateVariables.java:26-202 |
Chép tay 121 mục từ Constants.java:55-62; mọi sửa đổi phải giữ thứ tự (quyết định thứ tự bảng và biến dò). |
PrintTableLookup.java:44-53 |
Map thay reflection: tên thuộc tính không có key trả null — đúng khi map dựng đủ 50/9 key như RsInoutwardPrintFields. |