Bỏ qua

Phía ngân hàng

Onboard tổ chức · nhóm 1 · 13 story · 🟡 chưa viết xong · còn 20 mốc

Chưa viết xong: còn 20 mốc

Phần chưa viết hiển thị tiêu chí dự kiến từ kế hoạch (đã qua cổng PO duyệt) và mốc chờ agent làn docs.

Story Service Thứ tự Ghi chú
E-004/1.1 Schema Tổ chức và hồ sơ admin-be 1 cap/tpp-onboarding · mã số thuế là khoá tự nhiên, loại pháp nhân là enum bắt buộc · ⚠️ PO chốt 11/09 (tại làn admin-be, memlog change tpp-organization-schema @ d810c85): V12 BỎ trang_thaingay_duyet khỏi app.tpp — không SHALL, không design, không test; V11 đã ghi «cột trạng thái là nguồn sự thật thứ hai». Vòng đời/trạng thái hồ sơ là việc của 1.3. ⇒ Câu «trạng thái, ngày duyệt» ở epics.md:485nguyên liệu đã hết hiệu lực (⛔G1: không sửa ngược), hàng này là nơi ghi. Cùng lượt PO chốt: 8 khối Điều 8 đi bằng noi_dung jsonb (hoãn lần hai, có nợ) · tpp_profile chỉ-thêm · portal_app không SELECT trần (lệch AD-20 lần 2, vết ở OAPI-121) · đính kèm chỉ metadata
E-004/1.2 Thẩm định mã số thuế admin-be 2 cap/tpp-onboarding · nhập tay + checker xác nhận
E-004/1.3 Vòng đời hồ sơ admin-be 3 cap/tpp-onboarding · qua khung phê duyệt E-001/3.1
E-004/1.6 Khai bảng SUBMISSION và ma trận quyền portal_app admin-be 5 cap/schema-migration · ⛔ vật mang của E-004/2.2 · AD-14: bảng portal-be GHI vẫn do admin-be KHAI
E-004/1.7 Khai bảng phiên cho người dùng bên thứ ba admin-be 6 cap/schema-migration · ⛔ vật mang của E-004/2.8 · AD-14 · ⚠️ phạm vi chốt bằng số đo của portal-be trước khi mở change
E-004/1.4 Màn hồ sơ tổ chức admin-fe 10 cap/admin-console-ui · ⛔ màn riêng không modal · ô lý do nhắc hiển thị ra ngoài · PO chốt 13/09/2026: M:1.3Đ:1.3 — FE đi trước API vòng đời hồ sơ theo tiền lệ E-001/5.12 (Đ:2.4) và E-001/5.9 (P7, Đ:E-004/2.8): dựng trên gói (OrgList · OrgDetailScreen · OrgCreateModal) + máy chủ giả lập theo nếp port, màn tự khai chỗ chưa nối (§F); ⛔ không coi là xong khi 1.3 chưa archive và chưa nối thật · hợp đồng API 1.3 chưa có (OAPI-224 mới mở) ⇒ làn admin-fe nhắn admin-be xin openapi.yaml sớm (AD-28)
E-004/1.5 Nhập dữ liệu tổ chức đang đấu nối admin-be 13 cap/tpp-onboarding · ⚠️ BỊ CHẶN: chưa rõ có tổ chức nào
E-004/1.8 Khai bảng phiên bản văn bản pháp lý và vết đồng ý admin-be 15 cap/schema-migration · ⛔ vật mang của E-004/2.7 · AD-14 · ⛔ không ghi đè tại chỗ: bảng phải giữ được NHIỀU phiên bản và vết ai đồng ý bản nào
E-004/1.10 API xem người dùng bên thứ ba theo tổ chức admin-be 18 cap/tpp-onboarding · mở 11/09 tối (PO chốt P2: A3 = CHỈ XEM) · liệt kê + chi tiết app.tpp_user gom theo tổ chức (admin_app đã có SELECT từ V3) · ⛔ 2.1 (portal-be) đã archive, không đụng · ⛔ KHÔNG gán vai, KHÔNG khoá/mở trong story này — gán vai chờ nguồn vai TPP — nợ có tên OAPI-205 (openapi-platform#23; ~~E-005~~ ⛔ đo 12/09 E-005 không có story vai TPP, trỏ vào đó là trỏ chỗ trống), khoá/mở là thao tác ràng buộc (K2 + duyệt 3.1) ⇒ nợ có tên, mở story sau khi hai nguồn đó có
E-004/1.9 Màn xem người dùng bên thứ ba (phía quản trị) admin-fe 19 cap/admin-console-ui · mở 11/09 tối (PO chốt P2: A3 = CHỈ XEM) · gói AccountList({scope:"tpp"}) (cột Tổ chức + Vai trò trong tổ chức, ⛔ không nút Tạo mới) + khối «Người dùng thuộc tổ chức» trong OrgDetailScreen · cột Vai trò trong tổ chức ⛔ chưa có nguồn (nợ vai TPP OAPI-205, openapi-platform#23) ⇒ lượt đầu bỏ, ghi nợ cùng nếp OAPI-142 · ⛔ không nút gán vai / khoá / mở (xem 1.10)
E-004/1.11 Khai bảng đính kèm của đơn nộpkho nội dung tệp admin-be 21 cap/schema-migration · mở 12/09 (PO chốt 8 mục — AD-30) · ⛔ vật mang của E-004/2.11 · AD-14 · app.submission_attachment (metadata, nối submission, khuôn V12 tpp_attachment: ten_file · loai_mime · kich_thuoc_byte · bam · duong_dan = khoá kho db:<uuid>) + app.tep_noi_dung (bytea, xoá được — AD-30 mục 1) · GRANT portal_app INSERT/SELECT theo hàng (nếp V5), ⛔ không UPDATE/DELETE · ⛔ hình dạng do 2.11 chốt trong design.md trước, 1.11 không tự đoán — ba nhịp như cặp 1.7/2.8 · trả nợ OAPI-149 (admin-be#53)
E-004/1.12 Hàm lọc đọc byte kho tệp cho portal_app admin-be 23 cap/schema-migration · mở 13/09 (PO chốt OAPI-230-cô-lập-byte-kho-chung cách 1) · nhịp ①/③ thu hồi SELECT trần trên app.tep_noi_dung · hàm trả noi_dung chỉ khi khoá kho thuộc đính kèm của đơn người gọi (join submission_attachment_of_user, nếp V18) · ⛔ SECURITY INVOKER không đủ sau thu hồi ⇒ DEFINER hẹp + SET search_path + REVOKE FROM PUBLIC + GRANT EXECUTE portal_app · ⛔ chưa thu hồi gì ở đây (chiều THÊM — admin-be trước)
E-004/1.13 Thu hồi SELECT trần của portal_app trên app.tep_noi_dung admin-be 25 cap/schema-migration · mở 13/09 (PO chốt OAPI-230 cách 1) · nhịp ③/③ · V2x REVOKE SELECT ON app.tep_noi_dung FROM portal_app + ma trận quyền + lưới · đo has_table_privilege('portal_app','app.tep_noi_dung','SELECT') = false · ⛔ TR: bản portal-be của 2.12 phải chạy trên mọi môi trường TRƯỚC khi migration này chạy — chiều THU HỒI đảo AD-14, án lệ V7/OAPI-83-dat-updated-at-không-revoke-execute

Vì sao làm — BRD

→ Mục tiêu và giá trị nghiệp vụ nằm ở Tổng quan epic. Ở đây chỉ ghi mỗi story góp gì vào mục tiêu đó.

E-004/1.1 Schema Tổ chức và hồ sơ — admin-be

⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩

E-004/1.2 Thẩm định mã số thuế — admin-be

Góp gì vào mục tiêu epic. Phụ lục 01 §1 TT64 định nghĩa định danh bên thứ ba là mã số thuế hợp lệ, còn hoạt động (TT-05), và FR-010 đòi ngân hàng thẩm định mã đó trước khi tổ chức đi tiếp. Chưa có API tra cứu ([ASSUMPTION C7]) nên thẩm định là việc người làm bằng tay: nhân sự tra ở cổng thuế, nhập kết quả kèm bằng chứng, và một người thứ hai xác nhận. Story này là mắt xích thứ hai của đường găng A5 (1.11.21.32.5, PO chốt 13/09 mở cả 1.21.3): không có bản ghi thẩm định thì 1.3 (vòng đời hồ sơ) không có căn cứ để chặn duyệt tổ chức có mã ngừng hoạt động.

  • Là bộ áp nội dung sản xuất đầu tiên của khung phê duyệt E-001/3.1. Khung đã archive với ghi chú «ap_dung_luc chưa bao giờ được đặt cho tới khi có story cắm» — story này là story cắm: một lượt thẩm định = một đề xuất của khung, hai người, áp ngay khi đủ cấp. K3 (tách trách nhiệm) giữ nguyên ở đây — khác nhóm phân quyền GĐ1.
  • Trả ba câu hỏi treo từ 1.1 bằng cấu trúc, không bằng quy ước: (a) mã số thuế đổi được ⇒ thẩm định gắn với giá trị tại lượt, đổi mã là tự mất hiệu lực; (b) bằng chứng ⇒ byte vào kho chung AD-30, siêu dữ liệu ở bảng riêng của tổ chức; (c) ai làm/ai duyệt ⇒ tpp_organization.update (maker) và tpp_organization.approve (checker) từ danh mục quyền 2.4super admin không tự có hai quyền này.
  • Vì sao hôm nay còn thiếu nó. Trước story này, cổng quản trị không có bề mặt HTTP nào cho tổ chức1.1 chỉ khai bảng; đây là sáu đường đầu tiên dưới /api/v1/organizations/{id}, và cổng quyền chức năng đầu tiên cho cả tiền tố ấy (đường của 1.3 sau này tự bị chặn theo, không có đường «quên gắn quyền»).
  • Giới hạn cố ý: ⛔ không tra cứu tự động; ⛔ không quét mã độc (AD-30 mục 3, nợ có cổng); ⛔ không nhận PNG (AD-30 mục 2 chốt PDF + JPEG — ảnh chụp màn hình phải lưu JPG hoặc in PDF); ⛔ không sửa đề xuất bị từ chối — mở đề xuất mới.

E-004/1.3 Vòng đời hồ sơ — admin-be

Góp gì vào mục tiêu epic. Sau 1.1 (bảng tổ chức/hồ sơ) và 1.2 (thẩm định mã số thuế), hồ sơ tổ chức vẫn chưa có trạng thái (V12 cố ý bỏ cột trang_thai — «vòng đời là việc của 1.3») và chưa có bề mặt HTTP nào để tạo tổ chức, gửi hồ sơ đi duyệt, hay trả về/duyệt/từ chối. Story này là bước thứ ba và cuối phía ngân hàng trong đường găng A5/B3 (1.11.21.32.5): không có nó thì đơn nộp từ cổng bên thứ ba (2.5) không có nơi tiếp nhận, và màn hồ sơ tổ chức E-004/1.4 (admin-fe, đang dựng trên máy chủ giả lập) không có gì để nối.

  • Thi hành bốn điều luật một lượt: FR-015 vòng đời Nháp → Chờ duyệt → (Chờ bổ sung | Đã duyệt | Từ chối); FR-016 (Điều 10.1 TT64) trả về Chờ bổ sung kèm lý do bắt buộc; FR-012 (Điều 8, TT-02) thiếu khối nào thì không cho gửi duyệt; FR-019 sửa hồ sơ đã duyệt phải qua hai người, bản cũ vẫn hiệu lực tới khi bản mới được duyệt. K3 (tách trách nhiệm) giữ nguyên cho hồ sơ tổ chức — đúng phạm vi lệch có chủ ý GĐ1 chỉ áp cho phân quyền.
  • Là bộ áp sản xuất thứ hai của khung phê duyệt E-001/3.1 (sau 1.2): loại ho_so_tpp có hàng chính sách từ V11 mà chưa bộ áp nào dùng. Một đề xuất = xin duyệt một phiên bản hồ sơ; kết luận là bản ghi chỉ-thêm — thanh tra thấy cả chuỗi «gửi → trả về vì lý do X → gửi bản mới → duyệt».
  • Cùng nếp 1.2, không dựng lại: trạng thái suy ra (⛔ không cột), đính kèm hồ sơ đi qua kho chung (AD-30 mục 1, 2) với cùng bộ kiểm tệp, cùng cổng quyền ToChucPhanQuyenFilter1.3 chỉ nới có chủ ý ánh xạ quyền.
  • Giới hạn cố ý: ⛔ không sửa thông tin định danh tổ chức (tên pháp lý, giấy phép, mã số thuế — chưa có story); ⛔ không «chấm dứt/ngưng hoạt động» (FR-035, epic khác) — «từ chối» không khoá tổ chức; ⛔ không cột thật cho tám khối Điều 8 (nợ OAPI-148); ⛔ không hàng đợi chờ duyệt (3.3).

E-004/1.6 Khai bảng SUBMISSION và ma trận quyền portal_appadmin-be

Góp gì vào mục tiêu epic. Story này không thêm gì người dùng nhìn thấy. Nó là mắt xích bảng story thiếu — và trong lúc thiếu, nó đang chặn một làn khác đứng im.

  • Chỗ hổng nằm giữa hai luật đều đúng. E-004/2.2 giao portal-be ghi bảng đơn nộp; nhưng ⛔AD-14 cấm portal-be có dòng di trú nào, và không story nào khai bảng ấy. Spine đã trả lời sẵn câu «ai khai»: bảng portal-be ghi vẫn do admin-be khai. ⇒ Đây không phải xung đột kiến trúc, mà là bảng story thiếu một dòng so với chính spine. Làn portal-be đã dừng hẳn công việc của mình để chờ dòng đó.
  • Đơn tự nộp là cửa vào của cả epic. FR-055 cho bên thứ ba có tài khoản trước khi tổ chức được duyệt, FR-056 cho họ nộp hồ sơ bằng đúng bộ trường mà nghiệp vụ dùng. Không có chỗ chứa đơn thì cửa ấy không mở được, và mọi story phía sau — tiếp nhận, thẩm định, phê duyệt — không có vật để làm việc.
  • Nửa dễ quên nhất của story này lại là nửa có răng. portal-be nay có cổng khởi động đo quyền theo cột: một bảng mới mà thiếu quyền đúng cho vai ứng dụng của cổng thì portal-be từ chối khởi động. Đó là hành vi đúng thiết kế, nhưng triệu chứng sẽ trông như lỗi của portal-be chứ không như thiếu sót của lượt khai bảng này — tức mất thời gian tìm ở nhầm chỗ.

Vị trí trong hàng đợi. Đứng ở thứ tự 5 của epic, ngay trước E-004/2.2 (đơn nộp hồ sơ, portal-be) là story bị nó chặn. ⚠️ Nó chạy trước E-004/1.1 (schema Tổ chức và hồ sơ, thứ tự 1, chưa làm) — thứ tự đảo này không phải sơ suất mà là hệ quả của việc gỡ chặn, và nó tạo ra một quyết định về hình dạng dữ liệu mà story 1.1 sẽ phải thừa hưởng hoặc nêu lý do đi khác (xem PRD).

E-004/1.7 Khai bảng phiên cho người dùng bên thứ ba — admin-be

⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩

E-004/1.4 Màn hồ sơ tổ chức — admin-fe

⟨CẦN NGƯỜI VIẾT — agent làn admin-fe viết khi đóng change⟩

E-004/1.5 Nhập dữ liệu tổ chức đang đấu nối — admin-be

⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩

E-004/1.8 Khai bảng phiên bản văn bản pháp lý và vết đồng ýadmin-be

⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩

E-004/1.10 API xem người dùng bên thứ ba theo tổ chức — admin-be

Góp gì vào mục tiêu epic. Màn A3 «Người dùng TPP (phía quản trị)» — PO chốt 11/09 tối là CHỈ XEM — cần một nguồn dữ liệu, và tới trước story này admin-be không đọc app.tpp_user ở đâu cả: không repository, không route, openapi.yaml không có /tpp-users*. Story mở ba đường chỉ đọc để E-004/1.9 (màn người dùng bên thứ ba, admin-fe, (chưa có issue)) và khối «Người dùng thuộc tổ chức» trong màn hồ sơ tổ chức E-004/1.4 (OAPI-225, đang chạy trên máy chủ giả lập) có API thật để nối.

  • Vá một lỗ dữ liệu có sẵn từ V2: tpp_user.tpp_id chưa có khoá ngoại tới app.tpp (V2 hứa «thêm ở change khai TPP», V12 không thêm). Story thêm FK chỉ-thêm, không cascade (AD-4.2 — xoá tổ chức không được âm thầm cắt người thật). Hôm nay mọi tpp_id = NULLchưa story nào ghi — nên «người dùng theo tổ chức» là tập rỗng cho tới khi OAPI-242 chốt ai gắn.
  • Quyền là dữ liệu, không thêm mã quyền: bề mặt /tpp-users dùng user.read («Xem Người dùng MSB + TPP», có sẵn từ V16); super admin không mặc nhiên có. Đường dưới tổ chức kế thừa cổng tpp_organization.read của 1.2.
  • Giới hạn cố ý — đúng chữ PO «chỉ xem»: ⛔ không gán vai (chưa có nguồn vai TPP — nợ OAPI-205); ⛔ không khoá/mở/vô hiệu (thao tác ràng buộc, K2 + duyệt 3.1, story sau); ⛔ không ghi tpp_id; ⛔ không họ tên/điện thoại (cột không tồn tại); ⛔ không đọc phiên hay vết đăng nhập của bên thứ ba (dữ liệu vận hành của portal-be).

E-004/1.9 Màn xem người dùng bên thứ ba (phía quản trị) — admin-fe

⟨CẦN NGƯỜI VIẾT — agent làn admin-fe viết khi đóng change⟩

E-004/1.11 Khai bảng đính kèm của đơn nộpkho nội dung tệpadmin-be

⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩

E-004/1.12 Hàm lọc đọc byte kho tệp cho portal_appadmin-be

⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩

E-004/1.13 Thu hồi SELECT trần của portal_app trên app.tep_noi_dungadmin-be

⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩

Ai dùng, dùng thế nào — URD

Một hành trình cho cả nhóm, kể từ bề mặt người dùng chạm vào.

E-004/1.4 Màn hồ sơ tổ chức — admin-fe

⟨CẦN NGƯỜI VIẾT — agent làn admin-fe viết khi đóng change⟩

E-004/1.9 Màn xem người dùng bên thứ ba (phía quản trị) — admin-fe

⟨CẦN NGƯỜI VIẾT — agent làn admin-fe viết khi đóng change⟩

Hệ làm gì, ràng buộc gì — PRD

Mỗi story một mục, phần máy chủ trước, giao diện sau. Story chưa đóng hiển thị tiêu chí dự kiến từ epics.md.

E-004/1.1 Schema Tổ chức và hồ sơ — admin-be

Chờ hồ sơ. Tiêu chí dự kiến từ kế hoạch:

  • Tổ chức: mã, tên pháp lý, mã ngắn, số giấy phép ĐKKD, mã số thuế, loại pháp nhân, trạng thái, ngày duyệt.
  • Mã số thuế là khoá tự nhiên duy nhất, ràng buộc UNIQUE đặt trên TPP (FR-008, FR-009).
  • Loại pháp nhân là enum bắt buộc: Ngân hàng · Tổ chức trung gian thanh toán · Tổ chức khác (FR-011) — nó là nửa còn lại của AD-21.
  • Hồ sơ có phiên bản, không ghi đè tại chỗ (BL-03); mang tám khối nội dung Điều 8 (FR-012) và trường cấp độ an toàn hệ thống (FR-013).
  • Đính kèm tài liệu (FR-014).

⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩

E-004/1.2 Thẩm định mã số thuế — admin-be

Nguồn hồ sơ. admin-be @ origin/main 0320cf0openspec/changes/archive/2026-09-13-tham-dinh-ma-so-thue/ (proposal.md · design.md D1–D10 + «Review outcome» · specs/tpp-onboarding/spec.md 7 requirement, 38 scenario · specs/approval-workflow/spec.md 2 requirement · specs/iam-internal-users/spec.md 1 requirement · tasks.md · test-cases.md · security.md); bản thảo viết từ nhánh 11a6517, bản cuối soát lại theo archive — proposal/specs/tasks nguyên chữ, design.md chỉ thêm mục «Review outcome» sau dấu duyệt theo protocol đóng change (0 dòng xoá, không đổi quyết định nào; sha artifact ở archive 8abd251, dấu duyệt ghim bản 0644f5b PO đã đọc). Issue OAPI-223-tham-dinh-ma-so-thue (thangvv111/admin-be#73). Change ĐÃ đóng (archive 13/09/2026, merge 0320cf0) ⇒ bản cuối. Vật mang: di trú app/V19 của chính change (chỉ THÊM, AD-14.1).

Yêu cầu và cách đo

Yêu cầu Ràng buộc Đo bằng
Một lượt thẩm định = một đề xuất của khung 3.1, mở-và-gửi trong một giao dịch POST /api/v1/organizations/{id}/tax-code-verification-requests (multipart: phần yeuCau JSON ≤ 16 KiB + bangChung 1–5 tệp): kiểm khuôn → băm từng tệp → mở đề xuất loại tham_dinh_ma_so_thue → ghi byte + siêu dữ liệu → gửi duyệt. Đổ ở đâu cuộn hết: ⛔ không byte mồ côi, ⛔ không đề xuất thiếu bằng chứng. 0 tệp ⇒ 400 — lời khai không bằng chứng không phải thẩm định Scenario nhóm «Thẩm định đi qua hai người»; ca ThamDinhMaSoThueHttpTest
Nội dung đề xuất mang ảnh chụp mã số thuế do máy chủ điền {ma_so_thue (chụp từapp.tpplúc mở — ⛔ không nhận từ client), ket_qua, ten_theo_tra_cuu, nguon_tra_cuu, ghi_chu, bang_chung: [sha256…]} — chỉ dấu vân tệp vào nội dung đề xuất, ⛔ không tên tệp, ⛔ không byte (bốn bảng khung không xoá được) Ca kiểm thân đề xuất; bite: gửi ma_so_thue khác trong yeuCau ⇒ bị bỏ qua
Một đề xuất đang chờ mỗi tổ chức; bị từ chối ⛔ không sửa Đề xuất thứ hai ⇒ 409; phép kiểm tuần tự hoá ở tầng dữ liệu (khoá hàng tổ chức FOR NO KEY UPDATE trước khi hỏi — hai lượt mở đồng thời thì lượt sau xếp hàng). Từ chối ⇒ maker mở đề xuất mới; đề xuất cũ còn nguyên với lý do Scenario «Mỗi tổ chức chỉ một đề xuất đang chờ»; ca hai luồng mở đồng thời
Kết quả là từ vựng đóng, kết quả âm vẫn là lượt thẩm định hợp lệ con_hoat_dong · ngung_hoat_dong · khong_tim_thay (CHECK ở CSDL). Kết quả âm vẫn phải hai người xác nhận và được giữ — bằng chứng «ngân hàng có kiểm» cho thanh tra HinhBangV19Test; scenario kết quả âm
Checker quyết định; đủ cấp ⇒ áp ngay trong giao dịch POST …/{deXuatId}/decisions {tuChoi, lyDo} — người giữ tpp_organization.approve; K3 do khung V11 thi hành (maker ≠ checker) cộng CHECK nguoi_tham_dinh_id <> nguoi_xac_nhan_id ngay trên bảng thẩm định. Bộ áp: so ảnh chụp mã số thuế với mã hiện tại — lệch ⇒ 409, cuộn cả quyết định (đề xuất giữ «chờ duyệt», checker từ chối bằng tay, ⛔ không tự từ chối hộ); băm lại toàn bộ byte bằng chứng so đa tập với dấu vân đã ký — thiếu/lệch ⇒ 409 Scenario «áp khi mã đã đổi» · «bằng chứng đổi sau khi mở»; bite: gỡ phép so ⇒ ca đỏ
Bản ghi thẩm định là chỉ-thêm, không bịa được app.tpp_tax_verification: admin_app chỉ SELECT, INSERT theo cột; UNIQUE(approval_request_id) (một đề xuất áp đúng một lần); trigger BEFORE INSERT buộc hàng trỏ đề xuất đã áp, đúng loại, đúng tổ chức, nguoi_tham_dinh = nguoi_mo, nguoi_xac_nhan có chữ ký duyệt ở vòng hiện tại, ma_so_thue/ket_qua khớp nội dung đề xuất. Trigger chạy quyền người gọi (⛔ không SECURITY DEFINER), hàm REVOKE EXECUTE FROM PUBLIC HinhBangV19Test · MaTranQuyenThamDinhMstTest; bite: admin_app INSERT thẳng hàng không có đề xuất ⇒ CSDL từ chối
Bằng chứng: byte vào kho AD-30, siêu dữ liệu chỉ-thêm gắn tổ chức + đề xuất app.tpp_tax_evidence (tên tệp · MIME theo magic bytes · kích thước · bam · khoá db:<uuid> · người ghi · approval_request_id); admin_app thêm INSERT (id, noi_dung) trên app.tep_noi_dung — admin-be thành bên ghi kho (AD-30 mục 1, 6). portal_app ⛔ không quyền nào trên hai bảng mới Ma trận quyền +4 dòng, sửa 1; MaTranArtifactTest
Kiểm tệp tại điểm nhận theo nội dung thật, trần là cấu hình Magic bytes %PDF- · FF D8 FF; ⛔ không tin Content-Type/đuôi; ngoài whitelist / tệp rỗng ⇒ 400 ADM.REQUEST.VALIDATION_FAILED, ⛔ không lưu tạm. Trần 10 MB/tệp (ADMIN_ATTACHMENT_MAX_FILE_SIZE), request 60 MB, phần ≤ trần nằm trong bộ nhớ (⛔ không đĩa tạm), Tomcat nuốt hết thân bị từ chối ⇒ 413 có tên ADM.REQUEST.PAYLOAD_TOO_LARGE; thân hỏng ⇒ 400 MALFORMED, ⛔ không 500 Lớp miền đọc cùng knob (MockMvc không qua Tomcat) — ca dựng knob nhỏ để bite đo được; trần request đo tay trên Tomcat thật (T5.2)
Trạng thái thẩm định là SUY RA, ⛔ không cột; đổi mã ⇒ mất hiệu lực GET …/tax-code-verification: không hàng ⇒ chua-tham-dinh; hàng gần nhất có ma_so_thue ≠ mã hiện tại ⇒ het-hieu-luc; khớp ⇒ con-hoat-dong · ngung-hoat-dong · khong-tim-thay; kèm thamDinhGanNhatdeXuatDangCho nếu có. ⛔ Không cột da_tham_dinh trên app.tpp (nguồn sự thật thứ hai). Lịch sử GET …/tax-code-verifications phân trang Scenario «đổi mã số thuế làm mất hiệu lực»; 1.3 đọc trạng thái này để chặn duyệt hồ sơ
Tải bằng chứng so lại dấu vân, không inline, mỗi lượt một vết GET …/attachments/{bangChungId}: thuộc đúng đề xuất/tổ chức (khác ⇒ 404) → byte theo khoá (db: khác / đã xoá ⇒ 404) → băm lại, lệch ⇒ 409 ⛔ không trả byte, vết THAT_BAI + lech_dau_van BỀN (commit trước, ném sau) → Content-Type = MIME đã lưu · Content-Disposition: attachment (filename* mã hoá) · nosniff · no-store → vết bang_chung_mst.tai_ve cùng giao dịch (chủ thể người tải; AD-30 mục 5 «ai đã xem giấy tờ của tổ chức X»). HEAD ⇒ 405 (không ghi vết «đã tải» giả) Scenario «Tải bằng chứng…»; bite: sửa byte trong kho ⇒ 409 và vết lech_dau_van còn sau rollback
Bề mặt tổ chức đi qua quyền chức năng, hỏi mỗi lượt ToChucPhanQuyenFilter cho mọi /api/v1/organizations/**: GET/HEADtpp_organization.read; mở đề xuất ⇒ .update; quyết định ⇒ .approve; phương thức ghi khác (kể cả đường chưa tồn tại) ⇒ .approve — fail-closed bằng quyền mạnh nhất. Thiếu ⇒ 403 ADM.AUTH.FORBIDDEN + details[{permission, <mã>}]. Hỏi iam qua API công khai ChuTheVaQuyen (một SELECT EXISTS mỗi lượt, ⛔ không cache — K1); lỗi CSDL trong filter ⇒ 500 fail-loud, ⛔ không nuốt thành 403. Super admin ⛔ không tự có read/update/approve ToChucPhanQuyenFilterTest · RanhGioiGoiToChucTest (onboarding chỉ import iam.ChuTheVaQuyen) · ChuTheVaQuyenTest
Vết miền, tham chiếu, cùng giao dịch bang_chung_mst.ghi (mỗi tệp, maker) · to_chuc.tham_dinh_mst (lúc áp, chủ thể người xác nhận; mang mã số thuế — định danh tổ chức, không phải dữ liệu cá nhân) · bang_chung_mst.tai_ve; ⛔ không tên tệp, ⛔ không ghi_chu/ten_theo_tra_cuu vào vết. Bốn vết de_xuat.* của khung ghi như thường VetThamDinhMstTest
Bề mặt bên thứ ba không chạm siêu dữ liệu bằng chứng và bản ghi thẩm định portal_app không quyền nào trên hai bảng mới — nhưng xem ⚠️ 1 dưới về byte Ma trận quyền; scenario tpp-onboarding cuối

⛔ Cố ý KHÔNG có trong story này

⛔ Vòng đời hồ sơ / trạng thái tổ chức, API tạo·sửa·liệt kê tổ chức — E-004/1.3. ⛔ Đường đổi mã số thuế qua HTTP (chưa story nào mở; change chỉ bảo đảm đổi mã là mất hiệu lực). ⛔ Tra cứu tự động. ⛔ Quét mã độc (AD-30 mục 3). ⛔ Hàng đợi chờ duyệt (3.3 — cần sổ ánh xạ loại đối tượng → đặc quyền duyệt; change này ghim tham_dinh_ma_so_thue → tpp_organization.approveQuyenToChuc). ⛔ Sửa nội dung đề xuất đã gửi. ⛔ Rút đề xuất đang chờ (khung 3.1 không có rut). ⛔ PNG trong whitelist. ⛔ Không đụng app.tpp_attachment (đính kèm hồ sơ Điều 8 — vật khác).

⚠️ Người duyệt cần biết — sáu chỗ

1. Byte bằng chứng nằm ở kho chung mà portal_app còn SELECT mức bảng (V18). Câu «bên thứ ba không chạm bằng chứng» đúng với siêu dữ liệu, sai với byte: vai CSDL của cổng bên thứ ba đọc được byte bằng chứng nội bộ (bản chụp có thể mang tên người) nếu có khoá kho (uuid v4 — siêu dữ liệu mang khoá thì portal_app không đọc được). Mọi cách khép (thu hồi SELECT trần + hàm lọc · kho riêng · RLS) chạm portal-be ⇒ issue quy ước OAPI-230 cô lập byte kho chung — PO chọn một lần, change này không tự đổi quyền của V18.

2. Đề xuất «kẹt chờ duyệt» là chủ đích. Mã số thuế đổi sau khi mở ⇒ lúc duyệt bộ áp ném 409 và cuộn cả quyết định; hệ thống ⛔ không tự từ chối hộ — một quyết định từ chối phải là của một người. Checker phải từ chối kèm lý do rồi maker thẩm định lại. Không có đường rút cho maker mở nhầm — thêm rut là đổi khung, ghi Open Questions.

3. Cổng quyền là guard tầng ứng dụng; K3 mới có lớp CSDL. admin_app bị chiếm vẫn INSERT được ba bảng — lớp bù là vết (cùng vector S8.1(d) của 2.4/2.7). Riêng K3 có lớp CSDL thật: V11 của khung + CHECK/UNIQUE/trigger sáu điều kiện trên bảng thẩm định — một hàng thẩm định không bịa được mà không bịa trọn một đề xuất một chữ ký duyệt.

4. Mặc định fail-closed cho 1.3. Filter khớp cả tiền tố: đường ghi mới của 1.3 mà quên ánh xạ quyền thì maker bị 403 (đòi approve) chứ ⛔ không lọt; 1.3 chỉ nới có chủ ýQuyenToChuc. Người duyệt 1.3 nên đọc lại bảng ánh xạ này.

5. Văn bản tự do trong bảng chỉ-thêm không xoá được. ghi_chu (≤ 4096) và ten_theo_tra_cuu (≤ 254) sống mãi theo K2; người nhập gõ nhầm dữ liệu cá nhân thì không xoá được — muốn xoá được phải tách bảng xoá-có-vết (cùng lớp OAPI-206). Hiện chấp nhận với trần độ dài + thông điệp hợp đồng. Cũng nợ: chu kỳ thẩm định lại theo thời gian (mã không đổi nhưng doanh nghiệp đã ngừng) — chưa có FR, chờ PO.

6. Hai con số là quyết định lượt thi công, người chấm bác được. Tối đa 5 tệp một đề xuất; trần 254/4096 ký tự cho hai trường văn bản — đều ở GioiHanHoSo. Heap: một lượt mở giữ tới 5 × 10 MB, bộ áp đọc lại cùng khối lúc duyệt — chấp nhận ở GĐ1 (tổ chức hàng chục, thẩm định hàng đơn vị), hạ knob nếu chật.

E-004/1.3 Vòng đời hồ sơ — admin-be

Nguồn hồ sơ. admin-be @ origin/vong-doi-ho-so b10fd66 (artifact a4024aa; bản thảo đầu từ 92ac34b, cập nhật theo lens review 13/09) — openspec/changes/vong-doi-ho-so/ (proposal.md · design.md D1–D9 · specs/tpp-onboarding/spec.md 5 requirement ADDED + 1 MODIFIED (quyền), 42 scenario · specs/approval-workflow/spec.md 1 requirement · tasks.md · test-cases.md · security.md). Issue OAPI-224-vong-doi-ho-so (thangvv111/admin-be#74). Change ĐANG mở (artifact đẩy sớm theo AD-25.5, đang thi công) — đây là bản thảo, soát lại theo archive khi đóng. Vật mang: di trú app/V21 của chính change (chỉ THÊM, AD-14.1).

Change thứ hai của story — bo-sung-hop-dong-to-chuc (OAPI-258-bo-sung-hop-dong-be-mat-to-chuc, thangvv111/admin-be#79) — đã đóng (archive 2026-09-14-bo-sung-hop-dong-to-chuc, merge main @ bec973f; bản thảo viết từ nhánh 55cae1f, artifact 3dbcbd3), docs: khong-can. Lens review trước khi đóng thêm duyetLuc vào cả chi tiết tổ chức và nguoiGhiHoTen ở phiên bản hồ sơ — cùng nếp *HoTen đã tả dưới, không đổi hàng nào. PO chốt 14/09 theo bốn số đo của admin-fe khi nối màn 1.4: danh sách thêm soGiayPhepDkkd + duyetLuc; tên hiển thị *HoTen bên mã người; chi tiết gộp capDoAnToanMoiNhat + lichSuDeXuat[]; mã trạng thái nhapban-nhap. Spec requirement «Bề mặt HTTP tổ chức» MODIFIED (+4 scenario). Không di trú, không đổi quyền, không đổi vết — bảng dưới đã cập nhật hai hàng tương ứng.

Yêu cầu và cách đo

Yêu cầu Ràng buộc Đo bằng
Trạng thái hồ sơ SUY RA, ⛔ không cột ban-nhap · cho-duyet · cho-bo-sung · da-duyet · tu-choi (mã nhap đổi thành ban-nhap ở change thứ hai) = trạng thái của đề xuất ho_so_tpp mới nhất đối chiếu với kết luận của nó; đề xuất đã quyết mà không có kết luận ⇒ 500 fail-loud (hàng nội bộ hỏng, ⛔ không đoán). Kèm phienBanHieuLuc (= phiên bản của kết luận da_duyet gần nhất — có thể khác phienBanMoiNhat) và deXuatDangCho. Sau da-duyet, ghi bản mới vẫn da-duyet tới khi mở đề xuất mới (FR-019: bản đã duyệt không đổi ngầm) Scenario nhóm «Trạng thái hồ sơ là suy ra»; ToChucHttpTest
Gửi duyệt = mở một đề xuất cho đúng MỘT phiên bản, một giao dịch POST …/{id}/profile-review-requests {phienBan}: khoá hàng tổ chức → phiên bản thuộc tổ chức → cổng Điều 8 → một đề xuất chờ mỗi tổ chức (còn ⇒ 409) → mở + gửi. Nội dung đề xuất ghim tpp_profile_id · phien_ban · cap_do_an_toan · bam_noi_dung · dinh_kem[sha256…] — ⛔ không chép tám khối vào đề xuất (khung không xoá được, có thể mang tên người). ⛔ Không xin duyệt phiên bản cũ hơn hoặc đang là bản hiệu lực (409 — lùi là ghi bản mới); mỗi phiên bản đi qua đúng một đề xuất — bị trả về thì ⛔ không gửi lại cùng bản (đính kèm của bản ấy đã đóng băng); được xin duyệt bản không phải mới nhất Scenario «Gửi duyệt là mở một đề xuất…»; bite: gửi bản 2 khi bản 3 đã duyệt ⇒ 409
Cổng tám khối Điều 8 (FR-012) ở lượt gửi duyệt, ⛔ không ở lượt ghi nháp noi_dung phải có đủ 8 khoá không rỗng: camKetBaoMat · camKetPhamViDuLieu · thongBaoViPhamNhanSu · dichVuCungCap · phiDichVu · capDoAnToanHeThong · quyenTruyCapDuLieu · dieuKhoanChamDut (thứ tự = khoản Điều 8). Khoản 5 «phí (nếu có)» vẫn bắt buộc có khoá — «không thu phí» là giá trị hợp lệ. Thiếu ⇒ 400 ADM.PROFILE.MISSING_FIELD + details[{field, REQUIRED}] cho mọi khoá thiếu. Khoá lạ được giữ. Một lớp ứng dụng (tên khoá ghim ở KhoiDieu8, ⛔ không CHECK jsonb) — xem ⚠️ 1 KhoiDieu8Test; bite: gỡ cổng ⇒ ca đỏ (S8.2 ghi rõ tắt được bằng refactor)
Ba kết cục trên hai chiều của khung, phân biệt bằng bảng kết luận chỉ-thêm POST …/{deXuatId}/decisions {ketQua: duyet \| cho-bo-sung \| tu-choi, lyDo}. duyetlyDo rỗng; đủ cấp ⇒ bộ áp băm lại nội dung phiên bản + toàn bộ byte đính kèm so với dấu vân đã ký (lệch ⇒ 409, cuộn cả quyết định) rồi ghi kết luận da_duyet. cho-bo-sung/tu-choi ⇒ quyết định tu_choi của khung (vòng mới) + kết luận kèm lý do bắt buộcCHECK đối xứng: không duyệt ⇒ lý do không rỗng (≤ 4096), duyệt ⇒ ly_do IS NULL — là FR-016/FR-021 ở CSDL (trigger đã đòi lý do khớp quyết định đã ký, CHECK là backstop). cap = số quyết định duyệt ở vòng hiện tại + 1 (⛔ không giả định 1 cấp); kết luận da_duyet neo vào chữ ký cấp cuối (cap = so_cap_can). K3 hai mặt: người quyết định ⛔ không phải người gửi (khung) và ⛔ không phải người ghi phiên bản mà đề xuất ghim (service 409 + trigger) — xem ⚠️ 6 Scenario «Kết luận hồ sơ là bản ghi chỉ-thêm…»; HinhBangV21Test; bite: INSERT kết luận không lý do ⇒ 23514
Kết luận không bịa được app.tpp_profile_review: UNIQUE(approval_request_id); trigger BEFORE INSERT sáu vế — đề xuất tồn tại, đúng loại, đúng tổ chức, đúng tpp_profile_id; người quyết định ≠ tpp_profile.nguoi_ghi_id; da_duyet ⇒ đề xuất đã áp + người quyết định có chữ ký duyet cấp cuối ở vòng hiện tại; cho_bo_sung/tu_choi ⇒ có chữ ký tu_choi ở vòng liền trước ly_do khớp lý do checker gõ. admin_app chỉ SELECT, INSERT theo cột; portal_app ⛔ không quyền; hàm trigger REVOKE EXECUTE FROM PUBLIC MaTranQuyenHoSoTest; bite: admin_app INSERT kết luận không có đề xuất/chữ ký ⇒ CSDL từ chối
Sau trả về / từ chối: phiên bản mới + đề xuất mới, ⛔ không gửi lại đề xuất cũ Đề xuất cũ giữ nguyên với lý do; tu-choi không là trạng thái chết — vẫn mở được đề xuất mới (khác cho-bo-sung chỉ ở ngữ nghĩa cho người đọc và cho 2.5/portal). Chấm dứt tổ chức là story khác Scenario chuỗi «trả về → bản mới → duyệt»
Đính kèm hồ sơ qua kho chung, chỉ gắn khi phiên bản chưa từng gửi duyệt POST …/profile-versions/{phienBan}/attachments (một tệp/lượt, PDF/JPEG theo magic bytes, trần = knob multipart; tên tệp ⛔ ký tự điều khiển/dấu phân cách đường dẫn; tối đa 20 tài liệu mỗi phiên bản — không trần thì dinh_kem[] trong đề xuất vượt 64 KiB) → khoá hàng tổ chức (đính kèm xếp hàng sau lượt gửi duyệt đang chạy, không thì bộ áp 409 vĩnh viễn) → kho AD-30app.tpp_attachment → vết. Trigger V21 trên tpp_attachment từ chối INSERT khi phiên bản đã nằm trong một đề xuất (kể cả đề xuất bị trả về) — người duyệt duyệt đúng tập đính kèm đã thấy; muốn thêm tài liệu ⇒ ghi phiên bản mới, đính lại (tài liệu là của phiên bản). Tải về: so lại sha256 (lệch ⇒ 409 + vết THAT_BAI bền) · attachment · nosniff · no-store · HEAD ⇒ 405 — y hệt 1.2 D8, dùng chung TepDinhKem Scenario «Đính kèm hồ sơ…»; ThamDinhMaSoThueHttpTest phải xanh nguyên sau refactor TepDinhKem
Bề mặt HTTP tổ chức đi qua quyền chức năng 11 thao tác dưới /api/v1/organizations: tạo (create; mã số thuế trùng ⇒ 409) · danh sách ?page&size&trangThai&loaiPhapNhan&q (read; q khớp mã số thuế chuẩn / tên pháp lý; hàng mang thêm soGiayPhepDkkd + duyetLuc — mốc kết luận da_duyet gần nhất, suy ra, ⛔ không cột) · chi tiết (read; một đường đủ cho màn: kèm thamDinhMst.trangThai của 1.2, capDoAnToanMoiNhat, lichSuDeXuat[] mọi đề xuất mới trước; bên mỗi mã người có *HoTen chỉ để hiển thị — mã nội bộ vẫn là khoá K4, người không còn ⇒ null, hỏi tên qua API công khai iam) · ghi phiên bản (update) · danh sách phiên bản phân trang ?page&size (read; số lượt ghi không trần) · đính kèm/tải (update/read) · gửi duyệt (update) · liệt kê đề xuất kèm ketLuan (read) · quyết định (approve). ⛔ Không PUT/PATCH /organizations/{id} — gọi ⇒ 403 approve trước cả 404 (fail-closed của 1.2); / cuối vẫn là biến thể lạ ⇒ approve. Spec ghi phần này là MODIFIED của requirement quyền của 1.2, không phải requirement mới ToChucPhanQuyenFilterTest (ánh xạ mới) · RanhGioiGoiToChucTest
Vết tham chiếu, cùng giao dịch ho_so.gui_duyet (maker; de_xuat_id · phien_ban · bam_noi_dung) · ho_so.ket_luan (người quyết định; ket_qua · ket_luan_id — ⛔ không ly_do vào vết: lý do hiển thị cho bên thứ ba và có thể mang tên người, sống ở approval_decision + bảng kết luận) · dinh_kem.tai_ve. Ba vết của 1.1 nay có đường HTTP thật VetHoSoTest

⛔ Cố ý KHÔNG có trong story này

⛔ Sửa thông tin định danh tổ chức qua HTTP (tên pháp lý · giấy phép · mã số thuế — chưa có story; đổi mã số thuế là câu treo từ V12). ⛔ Ngưng hoạt động / chấm dứt hợp đồng (tpp_organization.disable, FR-035 — epic khác). ⛔ Lọc theo «scope đang có» của FR-017 (chưa có scope). ⛔ Hàng đợi chờ duyệt (3.3 — cần thêm cặp ho_so_tpp → tpp_organization.approve vào sổ ánh xạ). ⛔ Rút đề xuất (câu ngỏ từ 1.2). ⛔ Quét mã độc (AD-30 mục 3). ⛔ Cột thật tám khối (OAPI-148). ⛔ Sửa nội dung đề xuất tại chỗ. ⛔ Xoá đính kèm (byte xoá là OAPI-206).

⚠️ Người duyệt cần biết — bảy chỗ

1. Cổng Điều 8 là MỘT lớp ứng dụng, tắt được bằng refactor. Hình dạng thật tám khối là nợ OAPI-148 (chờ mẫu hợp đồng bank); tới lúc đó tên khoá ghim ở mã (KhoiDieu8), ⛔ không CHECK ở CSDL — cố ý, vì ghim tên khoá đoán vào ràng buộc chỉ-thêm là đổi nợ mềm lấy ràng buộc vĩnh viễn (1.1 D3). Bite trong lưới: gỡ cổng ⇒ ca đỏ. Khi OAPI-148 trả, lớp CSDL thay lớp này.

2. «Từ chối» không khoá tổ chức. Tổ chức bị từ chối vẫn mở được đề xuất mới — chủ đích: ngân hàng có thể đổi ý sau khi bên thứ ba sửa. Chấm dứt/ngưng là FR-035 (epic khác). Người đọc màn 1.42.5 cần hiểu tu-choi là «hồ sơ này không đạt», ⛔ không phải «tổ chức bị cấm».

3. Đính kèm hồ sơ chịu cùng biên byte kho chung như bằng chứng 1.2. portal_app còn SELECT mức bảng trên kho (V18) ⇒ OAPI-230 cô lập byte kho chung — PO đã chốt cách 1 (thu hồi SELECT trần + hàm lọc), làn platform lo, V2x về sau; change này không dựng thêm gì lên quyền kho.

4. Lọc theo trạng thái làm trong bộ nhớ. Trạng thái suy ra nên ?trangThai= lọc sau khi suy — đúng ở GĐ1 (tổ chức hàng chục); khi cần, đổi lối đọc sang SQL lateral không đổi hợp đồng.

5. Hai câu ngỏ, không chặn. Khoản 5 «phí (nếu có)»: change bắt buộc có khoá (giá trị chữ, kể cả «không thu phí») — PO muốn cho vắng khoá thì bỏ một dòng ở KhoiDieu8. Chính sách ho_so_tpp đang 1 cấp từ V11 — bank đòi 2 cấp thì chỉnh hàng dữ liệu, bộ áp không giả định số cấp.

6. Nghĩa của K3 cho mọi bộ áp về sau — PO cần chốt. Change này chốt K3 theo nội dung: người ghi phiên bản ⛔ không quyết định đề xuất của phiên bản ấy (A ghi bản, B gửi, A quyết là A ký lên nội dung A viết) — vì trục T_sod nói «việc mình vừa tạo» và AD-9.2 nói «mọi người đã sửa nội dung»; spec 3.1 chỉ nhìn người gửi. Nếu PO muốn K3 chỉ theo người gửi, gỡ một vế trigger + một if; nhưng đây là điểm chốt nghĩa K3 cho mọi bộ áp sau này (lens review 13/09 gọi tên). Hệ quả UAT: ca thẩm định hồ sơ cần hai tài khoản thật sự tách vai.

7. Ba biên chạm story khác, đã ghi ở Impact. (a) Bán kính fail-loud của D1 là toàn danh sách: một tổ chức có đề xuất đã quyết mà không kết luận làm GET /organizations 500 cho mọi người — chủ đích, và đường tạo hàng lệch duy nhất là ghi tay hoặc E-001/3.3 gọi khung.quyetDinh thẳng cho loại ho_so_tpp ⇒ hàng đợi chung ⛔ không được làm thế, kết luận chỉ sinh qua bộ áp của HoSoService. (b) ly_do phải hiển thị cho bên thứ ba (UX-DR7) nhưng portal_app không có quyền nào trên bảng kết luận lẫn approval_decisionE-004/2.5 tiếp nhận đơn nộp (chưa có issue) phải khai lối đọc lý do cho portal (T_iso). (c) Kết luận sai chiều (da_duyet cho đề xuất bị từ chối) cũng fail-loud 500 — chỉ ghi tay mới tạo được.

E-004/1.6 Khai bảng SUBMISSION và ma trận quyền portal_appadmin-be

Nguồn hồ sơ. admin-be @ origin/khai-bang-submission e762657openspec/changes/khai-bang-submission/ (proposal.md · design.md · specs/schema-migration/spec.md · tasks.md · test-cases.md · security.md). Issue OAPI-62-khai-bang-submission (thangvv111/admin-be#16). Story khắc ở epics/E-004-onboard-to-chuc.md @ 512dea7 (PO duyệt 08/09/2026). Change CHƯA đóng (hồ sơ ở in-review) ⇒ bản thảo; dấu duyệt ghim sha7 bản cuối.

Phạm vi: một dòng di trú vùng nghiệp vụ khai bảng đơn nộp, cộng các dòng tương ứng trong artifact ma trận quyền. Không có mã ứng dụng nào.

Yêu cầu và cách đo

Yêu cầu Ràng buộc Đo bằng
Bảng đơn nộp tự chứa, không phụ thuộc tổ chức Không khoá ngoại tới bảng tổ chức — đơn tồn tại trước khi tổ chức tồn tại. Khoá ngoại tới tài khoản người nộp thì có và cần: đó là cột duy nhất trả lời được «đơn này của ai». ⛔ Không ON DELETE CASCADE, không SET NULL Ghi một đơn mang mã số thuế của tổ chức chưa tồn tại ⇒ thành công; cộng phép quét khoá ngoại cả hai chiều (đơn trỏ ra · bảng nào trỏ vào đơn)
Mã số thuế trên đơn ⛔ không mang ràng buộc duy nhất Hai đơn cùng mã số thuế cùng tồn tại được — trùng là việc người thẩm định xử, không phải việc của tầng cơ sở dữ liệu (khác bảng tổ chức, nơi ràng buộc duy nhất là bắt buộc) Chèn hai đơn cùng mã số thuế và khẳng định cả hai vào được — đo sự vắng mặt của ràng buộc bằng hành vi, không bằng cách đọc siêu dữ liệu
Nội dung khai lưu kèm phiên bản của chính nó Nhãn phiên bản nội dung ⛔ không được rỗng; đọc lại một đơn cũ phải biết nó khai theo phiên bản nào Ghi đơn với nhãn rỗng ⇒ bị từ chối ở tầng cơ sở dữ liệu
Loại pháp nhân nhận giá trị đóng Chỉ ba giá trị đã khai; để trống được khi đơn còn là bản nháp. Ràng buộc viết dạng «rỗng hoặc thuộc tập» để không chặn đường lưu nháp Giá trị ngoài tập ⇒ bị từ chối; đơn nháp chưa chọn loại ⇒ vẫn ghi được
Người nộp của một đơn ⛔ không đổi được sau khi ghi Quyền sửa cấp theo từng cột, ⛔ không cấp trần trên bảng. Ba cột nằm ngoài với cả hai vai: định danh đơn · người nộp · mốc cập nhật (do trigger giữ) Từng vai thử sửa cột định danh và cột người nộp ⇒ đều bị từ chối
Đơn có lối đọc lọc theo người nộp Trả về tập đơn của người được hỏi; ⛔ không lộ đơn của người khác kể cả khi người đó có nhiều đơn Người nộp A có hai đơn, hỏi bằng mã của B ⇒ kết quả chỉ có đơn của B

Quyền cấp theo ma trận: đọc · thêm · sửa cho cả hai vai ứng dụng, ⛔ không xoá cho vai nào.

Một quyết định về hình dạng dữ liệu, PO chốt 08/09/2026 — và cái giá của nó

Bộ trường của đơn chia làm hai nhóm, không phải vì khẩu vị mà vì mức chắc chắn khác nhau:

  • Lõi định danh và phân loại (mã số thuế, tên pháp lý, loại pháp nhân, cấp độ an toàn) thành cột thật — các điều khoản khai rõ từng trường, không có giả định nào treo.
  • Tám khối nội dung theo Điều 8 vào một cột dữ liệu có cấu trúc, kèm nhãn phiên bản — vì chính điều khoản quy định chúng tự khai là chưa biết ánh xạ thành bao nhiêu trường thật: chưa có mẫu hợp đồng của ngân hàng. Và E-004/1.1, story lẽ ra chốt hình dạng ấy, chưa làm.

Nhãn phiên bản không phải trang trí. Thiếu nó thì khi mẫu hợp đồng có thật và cấu trúc đổi, không ai đọc được đơn cũ theo luật cũ — mà đó đúng là thứ thanh tra hỏi khi soi một đơn nộp năm ngoái.

⚠️ Cái giá, ghi thẳng chứ không phát hiện sau: khi E-004/1.1 chốt hình dạng thật và cột thật ra đời, sẽ có một quãng hai chỗ chứa cùng nội dung. Đó là giá đã biết trước của việc không đoán.

⛔ Cố ý KHÔNG có trong story này

Không máy trạng thái cho vòng đời đơn — tiếp nhận và thẩm định là E-004/2.5, chưa làm. Thay vì đoán một tập trạng thái rồi buộc story đó phải theo, dùng mốc thời gian nộp: chưa có = còn nháp, có = đã nộp. Một sự thật, không có trạng thái nào phải đồng bộ với nó. ⛔ Không bắt buộc điền trên mã số thuế và loại pháp nhân — biểu mẫu dài phải lưu nháp tự động (E-004/2.4), và cổng bên thứ ba theo thiết kế chỉ kiểm định dạng và ràng buộc schema, không mang luật nghiệp vụ. Luật «đủ tám khối mới cho chuyển trạng thái» sống ở hồ sơ chính thức phía admin-be, không ở bảng đơn. ⛔ Không cấp quyền trước cho mọi bảng tương lai — làm vậy là vô hiệu hoá ma trận trước khi bảng kịp ra đời.

⚠️ Người duyệt cần biết — ba chỗ

1. Ba mã giá trị của loại pháp nhân là do change này ĐẶT, chưa ai phê chuẩn. Điều khoản nguồn khai ba giá trị bằng tiếng Việt có dấu; ba mã kỹ thuật tương ứng là cách viết của làn admin-be. Hai repo khác (portal-be, admin-fe) sẽ phải dùng đúng ba chuỗi đó, và luật di trú chỉ-cho-thêm biến việc đổi chúng về sau thành phá hợp đồng: ràng buộc thì sửa được, dữ liệu đã ghi thì không. ⇒ Muốn bộ mã khác thì đổi TRƯỚC khi có dòng dữ liệu thật. Đây là chỗ đáng để người duyệt dừng lại.

2. Lối đọc lọc theo người nộp là lớp chặn CÓ ĐIỀU KIỆN, không phải cô lập. Nó chỉ đóng ca «đọc trần cả bảng» khi bên gọi chọn dùng nó; lớp chặn thật (cô lập ở tầng dòng cộng đường truyền danh tính xuống cơ sở dữ liệu) là quyết định của một story khác. Change khai đúng mức đó thay vì khai «đã cô lập» — khai quá mức chính là thứ làm một kết luận đạt mất giá trị.

3. Hồ sơ này TỰ SOI, chưa có người soi độc lập. Chính lượt tự soi bắt được hai lỗi cùng một họ — lời khai rộng hơn phép đo — trong đó một lỗi là phiên tự làm mù cổng đo trục của mình: cổng trả về «không chạm trục nào» cho một change chạm cả trục cô lập lẫn trục phân quyền, và mã thoát đó trông y hệt kết quả lành. Cả hai đã vá bằng cách sửa phép đo, không sửa câu chữ. ⇒ Change đáng chấm lại khi có checker: nó chạm hai trục nhạy và khai một hợp đồng mà hai repo khác phải theo.

Về khoá docs: — change khai docs: khong-can, và làn docs đồng ý: story giao một dòng di trú, không thêm màn hình hay thao tác nào người dùng cuối nhìn thấy. Đây là khai có chủ ý, khác hẳn ô để trống ở bảy change đang nằm trong mục [D3].

E-004/1.7 Khai bảng phiên cho người dùng bên thứ ba — admin-be

Chờ hồ sơ. Tiêu chí dự kiến từ kế hoạch:

  • Vật mang của E-004/2.8 — cùng lý do AD-14 như 1.6.
  • ⚠️ Phạm vi chốt bằng số đo của portal-be trước khi mở change (TK:E-004/2.8): hình dạng bảng do bên dùng nêu, bên khai không tự đoán.
  • Bảng phiên của người dùng TPP tách hẳn đường phiên nội bộ — AD-20 cấm portal_app chạm app.internal_user.
  • Cấp GRANT cho portal_app đúng cột thật, ⛔ không cấp ở mức bảng khi chỉ cần mức cột.

⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩

E-004/1.5 Nhập dữ liệu tổ chức đang đấu nối — admin-be

Chờ hồ sơ. Tiêu chí dự kiến từ kế hoạch:

  • ⚠️ Story này bị chặn: chưa rõ có tổ chức nào đang đấu nối trực tiếp không (FR-018, câu C3).

⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩

E-004/1.8 Khai bảng phiên bản văn bản pháp lý và vết đồng ýadmin-be

Chờ hồ sơ. Tiêu chí dự kiến từ kế hoạch:

  • Vật mang của E-004/2.7AD-14.
  • Không ghi đè tại chỗ: bảng phải giữ được nhiều phiên bản của cùng một văn bản, và vết ai đồng ý bản nào, lúc nào.
  • Đây là ràng buộc tuân thủ chứ không phải tiện lợi: khi thanh tra hỏi người dùng X đã đồng ý điều khoản nào, câu trả lời phải trỏ được đúng phiên bản tại đúng thời điểm.

⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩

E-004/1.10 API xem người dùng bên thứ ba theo tổ chức — admin-be

Nguồn hồ sơ. admin-be @ origin/tpp-user-read-api b63cd2e (artifact 722c437) — openspec/changes/tpp-user-read-api/ (proposal.md · design.md D1–D11 · specs/tpp-onboarding/spec.md 4 requirement, 21 scenario · tasks.md · test-cases.md · security.md · docs.md). Issue OAPI-240-api-xem-nguoi-dung-ben-thu-ba (thangvv111/admin-be#77). PO đã duyệt artifact 13/09, change đang thi công — bản thảo, soát lại theo archive khi đóng. ⚠️ epics.md không có mục AC riêng cho 1.10 — hợp đồng hành vi sống ở specs/ của change (PO duyệt). Vật mang: di trú app/V22 (chỉ THÊM một khoá ngoại, AD-14.1).

Yêu cầu và cách đo

Yêu cầu Ràng buộc Đo bằng
Ba đường chỉ đọc GET /api/v1/tpp-users (liệt kê) · GET /api/v1/tpp-users/{id} (chi tiết = đúng hình dạng hàng liệt kê, bảng không có gì thêm để đọc) · GET /api/v1/organizations/{id}/users (cùng truy vấn, ép lọc theo tổ chức). ⛔ Không thao tác ghi nào Ca HTTP; OpenApiHopDongTest (hợp đồng đẩy trước mã, D10)
Hàng dữ liệu không lộ bí mật id · email · xacMinhLuc · voHieuLuc · taoLuc · toChuc {id, maSoThue, tenPhapLy} \| null — ⛔ không password_hash, ⛔ không email_normalized. toChuc = null hiển thị «chưa gắn tổ chức» Scenario «Liệt kê mang tổ chức rỗng cho người chưa gắn»; ca khẳng định vắng hai cột bí mật
Lọc / tìm / phân trang ở SQL q tìm theo email đã chuẩn hoá (so khớp chứa, ESCAPE tường minh cho %/_) · organizationId = uuid hoặc từ khoá none (chưa gắn) — giá trị khác ⇒ 400 nêu tên tham số · trangThai ∈ {hoat-dong, vo-hieu} · xacMinh ∈ {da, chua} · phân trang AD-28 (page từ 1, size ≤ 200, TrangKetQua) · sắp xếp ổn định created_at DESC, id. Hai câu SQL một lượt (đếm + trang), ⛔ không đọc cả bảng rồi cắt Scenario «Lọc…» · «Tìm theo thư điện tử không phân biệt hoa thường» · «Phân trang đúng hợp đồng chung»; ca T7 đo «đúng 2 câu» qua log
Đường dưới tổ chức: 404 khi tổ chức không có, trang rỗng khi «có mà chưa ai gắn» Kiểm tổ chức tồn tại trước khi truy vấn; không phân biệt hai ca này là ẩn lỗi dữ liệu Scenario «Tổ chức có nhưng chưa ai gắn» · «Tổ chức không tồn tại»
Cổng quyền /tpp-users: user.read, hỏi mỗi lượt, super admin không mặc nhiên TppUserPhanQuyenFilter (mẫu ToChucPhanQuyenFilter): GET/HEADuser.read; mọi phương thức khác ⇒ 403 mang user.read (bề mặt không có thao tác ghi để trỏ về quyền mạnh hơn; đường chưa tồn tại không được rơi về 404/405 trước cổng). Thu hồi vai ⇒ hiệu lực ở lượt kế (⛔ không cache — K1). Không phiên ⇒ 401 do PhienFilter Scenario «Thiếu quyền» · «Super admin không mặc nhiên có» · «Thu hồi vai có hiệu lực ở lượt kế» · «Bề mặt chỉ đọc»
Đường dưới tổ chức kế thừa cổng tổ chức /organizations/{id}/users đi qua ToChucPhanQuyenFilter sẵn có ⇒ tpp_organization.read là đủ, ⛔ không cần user.read, ⛔ không viết cổng thứ hai — xem ⚠️ 2 Scenario «Quyền xem tổ chức là đủ, thiếu thì 403»
Khoá ngoại tpp_user.tpp_id → app.tpp(id), chỉ-thêm, không cascade V22: ADD CONSTRAINT … FOREIGN KEY — ⛔ không ON DELETE CASCADE, ⛔ không SET NULL (AD-4.2); ⛔ không NOT VALID (cột toàn NULL ⇒ validate tức thời, hiệu lực ngay); ⛔ không đổi ma trận quyền (đọc đã có SELECT từ V3; portal_app không thêm gì). Cột NULL vẫn hợp lệ HinhBang V22; scenario «Ghi mã tổ chức không tồn tại bị từ chối» · «Xoá tổ chức đang có người bị từ chối» · «Cột rỗng vẫn hợp lệ» · «Ma trận quyền không đổi»; bite đỏ-trước có sẵn: MaTranQuyenPortalAppTest chèn tpp_id giả ở 4 chỗ phải sửa thành tổ chức thật
Mã lỗi tái dùng, ⛔ không mã mới 404 ADM.REQUEST.NOT_FOUND · 400 ADM.REQUEST.VALIDATION_FAILED (details chỉ tên tham số, ⛔ không giá trị người gửi) · 403 ADM.AUTH.FORBIDDEN + details[{permission, <mã>}] · 401 ADM.AUTH.UNAUTHENTICATED Ca phong bì lỗi
Không vết cho lượt đọc Cùng nếp liệt kê tài khoản nội bộ — xem ⚠️ 3 T_audit N/A ở test-cases.md

⛔ Cố ý KHÔNG có trong story này

⛔ Gán vai người dùng TPP (OAPI-205). ⛔ Khoá / mở / vô hiệu từ phía quản trị. ⛔ Ghi tpp_id — gắn người vào tổ chức là OAPI-242 (story nào, có qua duyệt không — PO chốt một lần). ⛔ Họ tên / điện thoại (cột không tồn tại — vật mang ở story khác). ⛔ Sửa E-004/2.1 (portal-be, đã archive) theo hướng đổi cột. ⛔ Đọc tpp_session / tpp_login_attempt của portal-be. ⛔ Giao diện (1.9, OAPI-225).

⚠️ Người duyệt cần biết — bốn chỗ

1. Tập kết quả hôm nay là RỖNG theo tổ chức — và đó là sự thật, không phải lỗi. Chưa story nào ghi tpp_id (portal_app bị cấm cột, admin-be không có mã ghi, 1.3 không chạm) ⇒ GET /organizations/{id}/users trả trang rỗng cho mọi tổ chức, organizationId=none trả tất cả. Màn 1.9/1.4 phải hiển thị đúng «chưa gắn tổ chức», ⛔ không tự bịa quan hệ. Ai gắn, ở story nào, có qua duyệt không — OAPI-242 chờ PO.

2. Người giữ tpp_organization.read xem được email thành viên mà không giữ user.read. Chấp nhận có chủ ý (D6, ghi ở spec): «xem hồ sơ tổ chức» bao gồm xem ai thuộc tổ chức. PO siết được sau bằng đổi SHALL, không đổi hợp đồng.

3. Không vết cho lượt đọc email người dùng TPP. Email là PII; người đọc đã qua cổng quyền và phiên có vết đăng nhập, nên change theo nếp liệt kê nội bộ. Giá: không trả lời được «ai đã xem danh sách người dùng TPP lúc nào». Nếu PO muốn, một vết nguoi_dung_tpp.xem mức thông tin là thêm sau, không đổi hợp đồng — câu ngỏ ở security.md.

4. Số di trú V22 chốt qua tin liên phiên cùng ngày (V20 = don_vi, V21 = vong-doi-ho-so, V22 = change này) sau án lệ V19 trùng số; tasks đo lại ls migration/app | sort -V ngay trước khi tạo file. FK làm đỏ mọi test/seed chèn tpp_id giả — bằng chứng FK load-bearing.

E-004/1.11 Khai bảng đính kèm của đơn nộpkho nội dung tệpadmin-be

Chờ hồ sơ. Tiêu chí dự kiến từ kế hoạch:

  • Vật mang của E-004/2.11AD-14: bảng portal-be ghi vẫn do admin-be khai. ⛔ Hình dạng do 2.11 chốt trong design.md trước (TK:E-004/2.11), bên khai không tự đoán — ba nhịp như cặp 1.7/2.8.
  • Hai bảng: app.submission_attachment (metadata, nối submission, khuôn V12 tpp_attachment: ten_file · loai_mime · kich_thuoc_byte · bam khuôn sha256:<hex> · duong_dan = khoá kho db:<uuid>) và app.tep_noi_dung (bytea, AD-30 mục 1).
  • tep_noi_dung xoá được — khác tpp_attachment: byte là dữ liệu cá nhân (T_pii, Nghị định 13), metadata + bam là bằng chứng và giữ. admin_app DELETE có nhật ký; ⛔ không vai nào UPDATE byte tại chỗ.
  • GRANT portal_app INSERT/SELECT theo hàng trên cả hai bảng (nếp V5 submission), ⛔ không UPDATE/DELETE, ⛔ không SELECT trần; ghi vào ma-tran-quyen.tsv. Trigger giữ mốc phải REVOKE EXECUTE … FROM PUBLIC (cổng khởi động portal-be quét hàm).
  • Trả nợ OAPI-149 (admin-be#53 «kho lưu tệp chưa có chủ»): chủ vòng đời kho là admin-be (AD-30 mục 6).

⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩

E-004/1.12 Hàm lọc đọc byte kho tệp cho portal_appadmin-be

Chờ hồ sơ. Tiêu chí dự kiến từ kế hoạch:

  • Vấn đề (OAPI-230): V18 cấp portal_app SELECT mức bảng trên kho app.tep_noi_dung; E-004/1.2 đưa byte bằng chứng thẩm định nội bộ (T_pii) vào cùng kho ⇒ vai CSDL của cổng bên thứ ba đọc/dump được byte nếu có khoá. PO chọn cách 1: thu hồi SELECT trần, đọc byte qua hàm lọc.
  • Hàm trả noi_dung của một khoá kho, chỉ khi khoá đó là duong_dan của một hàng trong submission_attachment_of_user(p_nguoi_nop_id) (nếp V18, T_iso theo tập người dùng); không khớp ⇒ rỗng, ⛔ không lỗi tiết lộ.
  • SECURITY INVOKER không đủ: sau 1.13 portal_app không còn SELECT trên bảng ⇒ hàm phải SECURITY DEFINER hẹp (chủ hàm = vai chủ bảng, SET search_path = pg_catalog, app, tham số kiểu chặt), REVOKE EXECUTE … FROM PUBLIC, GRANT EXECUTE cho portal_app (và admin_app nếu cần). Đây là đổi nếp V5/V18 cho riêng hàm đọc byte — hàm siêu dữ liệu giữ INVOKER; comment V18 đã ghi «PO quyết» — nay đã quyết.
  • Chưa thu hồi gì trong story này (chiều THÊM — admin-be di trú trước đúng AD-14). Thu hồi ở 1.13.
  • Xong = hàm có trên trunk + ma trận quyền/lưới admin-be khai EXECUTE cho portal_app + has_table_privilege('portal_app','app.tep_noi_dung','SELECT') vẫn true (cố ý).

⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩

E-004/1.13 Thu hồi SELECT trần của portal_app trên app.tep_noi_dungadmin-be

Chờ hồ sơ. Tiêu chí dự kiến từ kế hoạch:

  • V2x: REVOKE SELECT ON app.tep_noi_dung FROM portal_app; ma trận quyền + lưới admin-be cập nhật (portal_app: INSERT theo cột, ⛔ SELECT/UPDATE/DELETE).
  • Đo: SELECT has_table_privilege('portal_app','app.tep_noi_dung','SELECT')false; has_function_privilege('portal_app', '<hàm 1.12>', 'EXECUTE')true.
  • TR:E-004/2.12 — merge tự do sau 2.12, nhưng migration này chỉ được chạy trên môi trường đã chạy bản portal-be của 2.12. Chiều THU HỒI đảo AD-14: bên MẤT quyền triển khai trước bên THU HỒI (án lệ V7/OAPI-83-dat-updated-at-không-revoke-execute, 08/09). uat và người vận hành đọc ô này.
  • OAPI-230 đóng khi story này archive và phép đo trên trả false.

⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩

E-004/1.4 Màn hồ sơ tổ chức — admin-fe

Chờ hồ sơ. Tiêu chí dự kiến từ kế hoạch:

  • Màn riêng, không modal (UX-DR13): tám khối Điều 8 cộng tài liệu không vừa 1080px, cần URL để gửi link cho người duyệt, và chồng hộp xác nhận lên modal là chỗ dễ bấm nhầm nhất.
  • Ô nhập lý do trả về phải nhắc ngay tại chỗ rằng nội dung hiển thị cho bên thứ ba (UX-DR7, KF-1).
  • Ghi chú nội bộ là trường riêng, không hiển thị ra ngoài.
  • Danh sách tổ chức lọc theo trạng thái, loại pháp nhân (FR-017).

⟨CẦN NGƯỜI VIẾT — agent làn admin-fe viết khi đóng change⟩

E-004/1.9 Màn xem người dùng bên thứ ba (phía quản trị) — admin-fe

⟨CẦN NGƯỜI VIẾT — agent làn admin-fe viết khi đóng change⟩