Hệ thống đối soát tự động (Pha 1–4) + audit độ chính xác giấy tờ#25
Merged
Conversation
…X + tên VĐV Audit adversarial (21 findings confirmed) → fix nhóm "sai tiền/dữ liệu trên biên bản ký gửi merchant": - #1/#5/#6: trích resolveFeeParams() dùng chung preview/create/batchCreate/cron. batchCreate + cron trước đây bỏ qua event_fee_overrides (áp merchant default thay vì rate override race); create() thiếu rate → fee=0 âm thầm. Nay cascade event override (LATEST effective_from) > merchant > fallback 5.5% (Danny chốt 2026-07-09), không bao giờ rơi 0%. +14 unit test. - #8: XLSX Section-3 cột "Tổng cộng" thiếu trừ discount → grand total > Section-1 GMV khi có giảm giá. Fix tie-out (CHANGE_COURSE dùng subtotal net). +test. - #11: manual participant_name = mã đơn (o.name) thay vì tên VĐV → ưu tiên first+last name. +test. - #2: thêm filter financial_status='paid' (defense-in-depth; verify PROD 2026-07-09: 100% đơn COMPLETE đều paid → no-op, chốt bất biến). Refuted (đã hết): parsePeriod off-by-1, reviewed_by, triggerByIds ObjectId 500. Deferred Pha 2: #3/#4/#7/#9/#10 (nhất quán fee.service/analytics), #12 (half-open interval — đụng shared util + fee.service/analytics). Toàn bộ 121 test reconciliation pass, build xanh. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- xlsx.service: đúng 2 sheet ('Tổng quan' + 'raw'; bỏ Pivot + 3 sheet thừa).
Tổng quan khớp mẫu cell-by-cell: A3 "Thời gian đối soát từ .. -> hết ngày ..";
Section 2 "GIAO DỊCH THỦ CÔNG"; Section 3 header gộp "Add-on products";
cột add-on/giảm giá để trống khi 0; nhãn 'Tổng cộng'; footer Phí bán vé /
Phí dịch vụ / Hoàn trả merchant (VAT chỉ hiện khi >0). Sửa typo formula mẫu.
- reconciliation-query: SELECT thêm id/modified_on/create_by/name/
order_modified_on/code → sheet raw đủ 25 cột khớp mẫu.
- Verify: generate từ fixture T6 khớp template cell-by-cell (1.5M/105K/1.395M).
- +8 test xlsx (khớp mẫu + tie-out #8 + VAT conditional + raw 25 cột). 128 pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- docx.service viết lại theo mẫu T6:
· Title "BIÊN BẢN ĐỐI SOÁT" (bỏ "QUYẾT TOÁN"); dòng kỳ "(từ .. đến ..)".
· Preamble 'Biên Bản Quyết Toán này ... ("Ngày Hiệu Lực"), bởi và giữa:'.
· Bên A label "Bên Sử Dụng"/"Đơn Vị Tổ Chức"; Bên B thêm "Tên tài khoản",
bỏ "Điện thoại", label "Bên Cung cấp"/"5BIB".
· Đánh số 1. NỘI DUNG NGHIỆM THU (a/b) + 2. KẾT LUẬN (a/b/c) đúng câu chữ.
· Bảng doanh thu 6 cột mẫu (STT|Giai đoạn|Hạng vé|SL vé qua 5BIB|Giá niêm
yết|Thành tiền); bỏ sub-header công thức + dòng add-on; Thành tiền = net
(tie-out #8). 3 dòng tổng (1)/(2)/(3)=(1)-(2) + 3 dòng "(Bằng chữ)".
· Giữ logo-less (F-037 v4) + viewer-compat (fixed layout/tblGrid/cantSplit)
+ MerchantConfig priority cho Bên A.
- config: PROVIDER_BANK_* default → BIDV Mỹ Đình 2600398986 theo mẫu thật
(env VPS vẫn override — cần verify khớp trước khi phát).
- Verify: generate từ fixture → pandoc khớp mẫu (party/bảng/số/bằng chữ).
- docx spec viết lại (khớp mẫu + regression MC/viewer). 129 test recon pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- athlete-list.service: mapper 43 cột + xlsx (sheet "Sheet1") khớp mẫu; parse customize_fields JSON cho sub-time/thành tích/giám hộ; dictionary VN cho giới tính/trạng thái vé/VĐV (không render raw enum). - reconciliation-query: queryAthletesForPeriod (athletes⋈codes⋈order⋈subinfo ⋈ticket_type⋈race_course), lọc COMPLETE/paid trong kỳ (cùng periodRangeUtc như queryOrders), DATE_FORMAT ngày trong SQL tránh lệch TZ. - endpoint GET /reconciliations/:id/download/athletes (admin-guard, PII không log) + register AthleteListService trong module. - Verify: query race 194 kỳ T6 → row VĐV khớp CHÍNH XÁC file mẫu (mã đơn/mã vé/DOB/giới tính/nhóm máu/trạng thái...). +5 unit test mapper. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Gọi GET /api/reconciliations/:id/download/athletes (cùng downloadWithAuth như XLSX/DOCX). next build xanh. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… 7h (F-082) Verify DB thật (race 194): order_metadata.processed_on lưu GIỜ VN wall-clock (raw '2026-06-30 19:05:35'), KHÔNG phải UTC. F-082 endOfPeriodMs trừ 7h coi là UTC → biên trên kỳ = 16:59:59 → RỚT đơn đặt 17:00-23:59 tối ngày cuối kỳ sang kỳ SAU (đơn 850k @19:05 mất khỏi T6 → chỉ tính 1/2 VĐV). Sửa: endOfPeriodMs dùng calendar ICT thuần '23:59:59', so trực tiếp với cột. - Kỳ cũ (< T6/2026) vốn đã calendar thuần → số đã ký KHÔNG đổi; chỉ kỳ >= T6 được sửa đúng. Fix propagate qua periodRangeUtc → queryOrders + queryAthletesForPeriod + fee.service + preflight cùng lúc. - Verify e2e: race 194 kỳ T6 giờ trả 2 VĐV (khớp mẫu), trước fix chỉ 1. - Specs: ict-date.util + f082-boundary cập nhật + REGRESSION đơn 19:05 thuộc T6. 157 test pass. - DEFERRED Pha 2-B: ictDayRangeUtc (analytics daily) có CÙNG bug TZ. - CẦN audit recon đã sinh kỳ >= T6 trước khi re-issue biên bản đã ký (Danny chốt). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ackend) Pha 3 — MerchantConfig thêm reconciliation_emails (To) + reconciliation_cc_emails (CC); admin nhập ở tab "Công ty đối tác" (chuỗi email cách dấu phẩy → mảng). DTO @isemail each, service whitelist + mergeFormat echo. Pha 4 — gửi email đối soát cho BTC: - MailService.sendReconciliation (nhiều attachment + CC pattern Mandrill). - reconciliation-email.helper: subject "[5BIB] Đối soát bán vé sự kiện <giải> — <kỳ>" + HTML body (Dear team + 3 bảng tóm tắt khớp mẫu email T6). - reconciliation.service.sendReconciliationEmail: gom 3 file (biên bản DOCX + bảng XLSX + DS VĐV XLSX) on-the-fly, resolve người nhận từ MerchantConfig; chỉ mark status='sent' + metadata KHI gửi thật OK. Batch review-then-send + markMilestone (email_confirmed/paper_sent/signed/invoice_issued). - Endpoints POST /:id/send-email, POST /send-email/batch, PATCH /:id/milestone. - Schema recon thêm email_sent_at/by/recipients + confirmed/paper/invoice_at. - +11 test (email helper 4 + send flow 3 + config). 141 recon test pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… vòng đời - Detail page: nút "Gửi email cho BTC" (dialog review-then-send) → POST /:id/send-email; hiển thị đã gửi + người nhận; nút vòng đời (BTC xác nhận / gửi bản in / nhận bản ký / xuất HĐ) → PATCH /:id/milestone. - List page: "Gửi email (N)" hàng loạt cho đối soát đã chọn (dialog duyệt + kết quả từng cái, per-item lỗi hiện lý do) → POST /send-email/batch. - next build xanh. Cần click-test live trên DEV sau deploy (cần Logto auth). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… lập phí" giả Bug phát hiện khi test DEV (E2E): preflight flag ERROR "Merchant chưa thiết lập service_fee_rate" → recon bị 'flagged'/"Có vấn đề" phải Approve anyway, NGAY CẢ khi race CÓ event override 7% — và trái quyết định Pha 1 (fallback 5.5%, KHÔNG hard-block). Fix: resolveFeeParams per-race (override → merchant → 5.5%); chỉ WARNING khi rơi mặc định 5.5%, có override/merchant rate → im. estimated_fee dùng rate đã resolve. Áp cả run() + runRange(). +3 test. 141 recon test pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tus forward-only + audit actor QC gatekeeper adversarial (12/13 confirmed). Fix: - [HIGH blocker] docx (3) chỉ = net - feeInclVat, QUÊN trừ phí thủ công + cộng adjustment → biên bản ký lệch payout thật khi có đơn thu hộ (manual). Fix: thêm dòng "Trừ phí giao dịch thủ công" + "Điều chỉnh" + "(4) Số tiền 5BIB thanh toán cho Bên A" = payout_amount (khớp email + XLSX + số thực chuyển). Case không manual giữ nguyên (3) khớp mẫu SCB. Verify e2e: 1.395.000 - 15.000 = 1.380.000. +2 test. - [MEDIUM] gửi LẠI email downgrade status signed/completed → sent: forward-only, chỉ nâng khi đang trước 'sent'. +1 test. - [MEDIUM] email_sent_by luôn null (req.user?.id sai field) → userId ?? sub (khớp create()). Debt còn lại (LOW, QC "nên-làm-sau"): idempotency lock gửi email, strip raw_* khỏi findOne response, findOneAndUpdate atomic lifecycle, response DTO + SDK gen. 147 recon test pass, build xanh. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…er đóng, LOW debt tracked) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ayout âm/tie-out Audit đối kháng (19 confirmed) trên biên bản DOCX / bảng XLSX / email: - CRITICAL số bằng chữ: numToWords bỏ "không trăm"/"lẻ" cho nhóm không dẫn đầu có hàng trăm=0 → 1.000.005 in "Một triệu năm đồng" (nghe thành 1,5tr). Viết lại theo chuẩn kế toán VN: nhóm giữa/cuối trăm=0 → "không trăm [lẻ]". Fix luôn số âm viết hoa "M" giữa câu + Math.round đầu hàm. PASS 32/32 battery. - HIGH chức vụ: bỏ hardcode default "Tổng Giám đốc" (bịa chức danh pháp lý khi MerchantConfig/metadata không có). Để trống → điền tay. - HIGH VAT: bullet "(2) = (1) × X%" sai số học khi feeVatRate>0 (feeInclVat gồm VAT). Nay khi VAT>0 in "= (1) × rate% + VAT vat%" + dòng tách phí/VAT; VAT=0 giữ nguyên khớp mẫu SCB. - HIGH tie-out: XLSX + email thiếu dòng "Điều chỉnh" (manual_adjustment) → số không tự cộng-trừ ra payout, lệch DOCX. Thêm dòng khi ≠0. - MEDIUM payout âm: prose "5BIB thanh toán cho Bên A: -X" vô nghĩa → đảo chiều "Bên A thanh toán cho 5BIB" + trị tuyệt đối khi payout<0. - Thêm WARN docx_party_incomplete khi thiếu tên pháp nhân/MST/STK Bên A. +10 test (6 docx, 2 xlsx, 2 email). 157/157 recon pass, build xanh. Giữ nguyên nhãn trùng "Phí dịch vụ" (Section 2) + Bên B env.provider (đã có default BIDV thật) — bám mẫu T6, refuted trong audit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… số bằng chữ Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…) (Danny chốt sửa rõ nghĩa) - Mục 2 (email+xlsx): bỏ nhãn trùng "Phí dịch vụ"/"Phí dịch vụ" → "Đơn giá phí/vé" (cột 3) + "Thành tiền" (cột 4). Cập nhật test lock C11. - DOCX: khi có phí thủ công/điều chỉnh, diễn giải định nghĩa "(3)=(1)-(2)" + liệt kê "Trừ phí giao dịch thủ công"/"Điều chỉnh" trước mốc (4) → truy vết đầy đủ, không nhảy (2)→(4). - Danny chốt: thiếu định danh Bên A chỉ CẢNH BÁO (giữ WARN, không chặn cứng). +2 test. 159/159 recon pass, build xanh. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…oát 22 kỳ đối soát thật Audit 45 file DOCX/XLSX hồ sơ đối soát tay T4/T5/T6 2026 (13 giải, 9 merchant): 86 lỗi con người (8 CRITICAL). Nổi bật: - Biên bản Hoi An Heritage T5+T6 đều mang nhầm tên "Danang Mountain Trail" - Danang T6: 76.945đ viết chữ thành "Bảy mươi sáu triệu..." (gấp 1000 lần) - Cao Bằng T5: số 0đ nhưng 3 dòng bằng chữ residue 61,7tr từ giải khác - Mẫu canonical SCB T5+T6 THIẾU "nghìn" (1.395.000 → "Một triệu ba trăm chín mươi lăm đồng") — hệ mới không replicate - Đường Đua Mới 4/4 kỳ DOCX bỏ sót phí thủ công (lệch XLSX 25K–105K) Fix áp ngay: numToWords "lẻ" → "linh" — 100% hồ sơ thật dùng "linh" (0 "lẻ"). 159/159 test pass. Báo cáo đầy đủ: docs/recon-automation/06-real-docs-review.md (registry 9 merchant seed MerchantConfig + gap analysis VTV LPBank/giảm giá/ change-course + 2 phiên bản bank Bên B). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…BANK (Danny chốt 2026-07-11)
1. Bên B (5BIB) trên biên bản: BIDV Mỹ Đình → MBBANK Thụy Khuê 110398986
(Danny chốt; 2 biên bản thật mới nhất đã dùng MB). Verify VPS dev+prod
KHÔNG set env PROVIDER_BANK_* → Joi default mới tự hiệu lực khi deploy.
2. Kỳ đối soát theo giai đoạn bán vé (ngày mở → ngày đóng, vd VTV LPbank
18-20/05) — nhiều giải đối soát theo đợt chứ không theo tháng:
- Validator: IsPeriodDate thay IsPeriodBoundaryDate (ngày bất kỳ, bắt
ngày không tồn tại 2026-02-30, span ≤ 366 ngày) — preview + preflight/range.
- flexPeriodRangeUtc: kỳ tròn-tháng giữ NGUYÊN periodRangeUtc (seam
continuity F-082, số kỳ đã ký không đổi từng byte); kỳ giai đoạn dùng
biên ICT thuần [start 00:00:00, end 23:59:59] khớp processed_on wall-clock.
- renderPeriodLabel/filenamePeriodSegment: "từ 18/05/2026 đến hết
20/05/2026" + "2026_05_18_den_2026_05_20".
- Preflight run() nhận rangeOverride: đếm đơn đúng khoảng ngày, bỏ check
so-sánh-tháng-trước (vô nghĩa với khoảng tuỳ ý).
- Admin form tạo đối soát: toggle "Theo tháng | Theo giai đoạn" + 2 date
picker (mở/đóng giai đoạn).
+13 test (flex range, label, preflight override, docx render giai đoạn).
235/235 recon+common pass; backend + admin build xanh.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t 7/14 kỳ thật có giảm giá) Kỳ có giảm giá → bảng 7 cột thêm "Giảm giá" trước "Thành tiền" để dòng tự cộng được trên giấy ký (SL × Giá niêm yết − Giảm = Thành tiền — nặng nhất Vu Media giảm 71-100% mã TAM26-*). Kỳ không giảm giá giữ NGUYÊN 6 cột khớp mẫu SCB T6. Dòng tổng colspan động theo số cột. Danny chốt 2026-07-11: phí theo kênh Payoo (VTV 7.5%/6.5%) KHÔNG làm — đặc thù 1 giải, phần phí kênh làm tay; kỳ giai đoạn (Pha 5-A) vẫn dùng được. +2 test. 237/237 recon+common pass, build xanh. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… đoạn/bỏ Payoo/cột Giảm giá) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…yyyy (thay vì 'Tháng X') Nit từ E2E live Pha 5-A: formatPeriod list + formatPeriodLabel helper chỉ nhìn tháng nên bản giai đoạn 30/06→30/06 hiển thị 'Tháng 6/2026' gây hiểu nhầm kỳ tròn tháng. Nay không tròn-tháng → in đủ ngày. Trang chi tiết vốn đã đúng. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1. Người gửi "Đối soát 5BIB <ketoan@5bib.com>" (env RECON_FROM_EMAIL/NAME, Joi default) + LUÔN CC danny@5bib.com (RECON_ALWAYS_CC, dedupe). 2. Thân email thêm điều khoản: sau 05 (năm) ngày không phản hồi → số liệu mặc định được xác nhận và đồng ý. 3. Kỳ 1 ngày hết in "từ 30/06 -> hết ngày 30/06" → "ngày 30/06/2026" (email + subject + docx + xlsx + admin list + filename segment). 4. Notification nhắc kế toán xuất hóa đơn (RECON_ACCOUNTING_EMAIL = ketoan@5bib.com): (a) ngay sau khi gửi đối soát thành công — email "Đã gửi đối soát... theo dõi xuất hóa đơn" kèm phí căn cứ HĐ; (b) cron 08:30 ICT hằng ngày — recon đã gửi QUÁ 5 ngày chưa phản hồi + chưa xuất HĐ → email "Đến hạn xuất hóa đơn" (idempotent qua invoice_reminder_sent_at, schema field mới). +5 test. 243/243 recon+common pass, build backend + admin xanh. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…oán) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…(Danny 2026-07-11)
1. Raw data cross-check — nhân viên tự lấy không cần Danny:
- queryRawData: NGUYÊN VĂN câu SQL Danny chạy tay hằng tháng (dump thô
đơn COMPLETE trong kỳ, 25 cột, ORDER BY oli.id — KHÔNG filter
financial_status, KHÔNG categorize)
- GET /reconciliations/raw-data/download?mysql_race_id&period_start&
period_end (+race_title) → XLSX 1 sheet raw
- Admin UI: nút "Tải raw data (cross-check)" ở trang tạo đối soát (dùng
picker merchant/race/kỳ sẵn có — cả kỳ giai đoạn) + nút "Raw data" ở
trang chi tiết (dùng kỳ của bản đó)
2. RBAC module đối soát — LogtoReconciliationGuard (union reconciliation ∪
staff ∪ finance ∪ admin):
- Role MỚI "reconciliation" (tạo trên Logto Dashboard): CHỈ vào được
module đối soát — controller khác vẫn LogtoStaffGuard nên không với tới
- Role "finance" (Tài chính kế toán) được THÊM quyền module đối soát
- Frontend: isReconciliation flag + nav requireRole "reconciliation" +
sidebar filter + page guard 4 trang (vá luôn gap: trang new trước đây
KHÔNG có guard)
+9 test guard. 251/251 pass, build backend + admin xanh.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Nâng cấp module đối soát thành hệ thống tự động: tính đối soát → xuất 3 file (biên bản DOCX / bảng XLSX / danh sách VĐV) → gửi email đầu mối BTC (batch review-then-send) → vòng đời đến xuất hóa đơn. Kèm audit độ chính xác + audit giấy tờ pháp lý.
Nội dung chính
Pha 1 — độ chính xác:
resolveFeeParams()cascade thống nhất (admin override → event override LATEST → merchant default → fallback 5.5%) dùng chung preview/create/batch/cron; tie-out XLSX #8; fix tên VĐV; fix biên kỳ ICT (processed_onlưu wall-clock VN → bỏ trừ 7h; audit 0 recon bị ảnh hưởng).Pha 2 — khớp mẫu T6: viết lại DOCX + XLSX bám 100% mẫu Standard Chartered T6; export Danh sách VĐV 43 cột từ platform MySQL (PII admin-guarded).
Pha 3 — config email: MerchantConfig thêm
reconciliation_emails(To) +reconciliation_cc_emails(CC).Pha 4 — automation: gửi email kèm 3 file qua Mandrill (batch có duyệt), lifecycle + milestone.
QC + Audit (adversarial workflow)
docs/recon-automation/04-qc-report.mddocs/recon-automation/05-docs-audit.md:numToWordsbỏ "không trăm/lẻ" →1.000.005in "Một triệu năm đồng" (nghe = 1,5tr). Viết lại chuẩn kế toán VN (32/32 battery).Kiểm thử
reconciliation.controller.spec.tsfail-to-load = lỗi guard pre-existing (fail trên clean tree, không do PR này).Trước khi lên PROD
MAILCHIMP_API_KEY(Mandrill) trên stack production để gửi email thật.PROVIDER_BANK_*= BIDV 2600398986 — đã có Joi default giá trị thật.raw_*khỏi response, atomic lifecycle, response DTO + gen SDK.🤖 Generated with Claude Code