Bỏ qua

Phê duyệt hai người

Nền quản trị · nhóm 3 · 3 story · 🟡 chưa viết xong · còn 6 mốc

Chưa viết xong: còn 6 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-001/3.1 Khung phê duyệt và tách trách nhiệm admin-be 12 cap/approval-workflow · AD-9 bất biến
E-001/3.2 Cấu hình luồng phê duyệt admin-be 15 cap/approval-workflow · đổi cấu hình tự nó phải qua duyệt
E-001/3.3 Hàng đợi chờ duyệt admin-be 16 cap/approval-workflow · ✅ PO chốt 11/09 tối: VÀO GĐ1 — nguồn của màn hàng đợi 5.14 (và khối «Yêu cầu chờ duyệt» của E-011 sau này). API liệt kê yêu cầu chờ người đang đăng nhập duyệt được, lọc theo loại đối tượng · ⚠️ 12/09: sau khi PO bỏ duyệt cho phân quyền, hàng đợi GĐ1 không có loại vai/quyền/gán vai/tài khoản nội bộ — chỉ còn hồ sơ tổ chức (E-004/1.3), yêu cầu đăng ký (E-005), cấu hình luồng (3.2)

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-001/3.1 Khung phê duyệt và tách trách nhiệm — admin-be

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

E-001/3.2 Cấu hình luồng phê duyệt — admin-be

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

E-001/3.3 Hàng đợi chờ duyệt — admin-be

Góp gì vào mục tiêu epic. Khung phê duyệt E-001/3.1OAPI-118 đã cho mỗi miền một đường gửi duyệt riêng, nhưng người duyệt không có chỗ nào để biết «có gì đang chờ tôi»: đo trên admin-be @ origin/main trước change, muốn thấy đề xuất chờ phải quét từng tổ chức qua hai đường của miền (…/organizations/{id}/tax-code-verification-requests, …/profile-review-requests). FR-022 đòi một hàng đợi lọc theo loại; người soạn cũng cần thấy đề xuất của mình đang ở đâu.

  • Đây là nguồn của màn hàng đợi E-001/5.14OAPI-244. Màn đi trước bằng port giữ chỗ và đã gửi bốn hình dạng máy chủ phải trả (13/09): cờ «của tôi» · kết luận duyệt được hay không, vì sao do máy chủ phán · mã yêu cầu kế tiếp để «duyệt rồi sang cái tiếp» · đường đếm tách khỏi liệt kê, kèm danh mục loại có nguồn. Story này là bên trả lời.
  • Nó cố ý chỉ ĐỌC. Hàng đợi không có nút duyệt/từ chối: quyết định vẫn đi qua bộ áp của từng loại (1.2 thẩm định mã số thuế, 1.3 vòng đời hồ sơ) — nơi giữ K3 theo nội dung, băm lại nội dung, vết ho_so.ket_luan. Nếu hàng đợi gọi thẳng khung thì mỗi loại có hai đường quyết định, và đường tắt sẽ bỏ qua đúng lớp kiểm mà miền dựng lên. Màn bấm hàng ⇒ sang màn đối tượng để quyết; hàng đợi chỉ cho mã kế tiếp.
  • Sổ ánh xạ loại → đặc quyền duyệt thành MỘT sổ. Trước đó mỗi miền tự ghim cặp «loại → quyền»; số đo 13/09 từ 1.3 đã đặt yêu cầu này về 3.3. Nay sổ dựng từ plugin mỗi loại đăng ký; trùng loại ⇒ từ chối khởi động. Loại mới (E-005 yêu cầu đăng ký, 3.2 cấu hình luồng) = thêm một plugin, ⛔ không đổi bề mặt.

Phạm vi giai đoạn 1 theo PO (12–13/09). Hàng đợi không có loại vai / quyền / gán vai / tài khoản nội bộ (PO bỏ duyệt cho phân quyền); loại có nguồn hôm nay là hồ sơ tổ chức (ho_so_tpp) và thẩm định mã số thuế (tham_dinh_ma_so_thue), cả hai đòi tpp_organization.approve.

Vị trí trong hàng đợi. Thứ tự 16; PO chốt 11/09 tối «vào giai đoạn 1». Change hang-doi-cho-duyetOAPI-243 đã đóng 14/09/2026 (archive, merge main @ 21f4162); docs: khong-can — trang hướng dẫn thuộc màn 5.14.

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

Không có bề mặt người dùng

Nhóm này chỉ có phần máy chủ hoặc hạ tầng — không cần URD (khong-can, có vết ở đây). Hành trình người dùng nếu có nằm ở nhóm có màn tương ứng.

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-001/3.1 Khung phê duyệt và tách trách nhiệm — admin-be

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

  • Mọi thay đổi ràng buộc qua đúng hai người: một đề xuất, một duyệt (FR-019).
  • Checker chỉ duyệt hoặc từ chối, không sửa được nội dung (FR-020); từ chối bắt buộc kèm lý do (FR-021).
  • Tách trách nhiệm là bất biến, không cấu hình tắt được. "Người soạn" định nghĩa kiểm được bằng máy: tập mọi người đã sửa nội dung kể từ lần gửi duyệt gần nhất; checker phải nằm ngoài tập đó (AD-9.2).
  • Bản ghi phê duyệt bất biến: ai, khi nào, nội dung trước và sau (FR-023).

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

E-001/3.2 Cấu hình luồng phê duyệt — admin-be

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

  • Với mỗi loại đối tượng, khai được cần mấy cấp duyệt và vai nào duyệt cấp nào (FR-051).
  • Đổi cấu hình luồng tự nó phải qua phê duyệt và ghi nhật ký (FR-052) — không thì hạ số cấp xuống 1, làm việc, nâng lại là vô hiệu hoá mô hình trong vài phút mà không để lại vết.

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

E-001/3.3 Hàng đợi chờ duyệt — admin-be

Nguồn hồ sơ. admin-be @ origin/main 21f4162openspec/changes/archive/2026-09-14-hang-doi-cho-duyet/ (proposal.md · design.md D1–D9 + §Review outcome · specs/approval-workflow/spec.md · tasks.md · test-cases.md · security.md · spec-oapi-243-1.md hồ sơ lens review). Issue OAPI-243-hàng-đợi-chờ-duyệt (thangvv111/admin-be#78). Change ĐÃ đóng (verdict PASS @ 36af186 tự chấm; lens review 14/09 bốn lớp: 22 patch · 4 reject có lý do; mutation-bite lượt 3 13/13 đỏ) ⇒ bản cuối.

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

Yêu cầu Ràng buộc Đo bằng
Một bề mặt đọc chung cho mọi loại — /api/v1/approval-requests (tag hang-doi), ba thao tác, chỉ đọc GET / liệt kê tập thấy được của người đang đăng nhập, lọc trangThai (cho-duyet mặc định · da-duyet · tu-choi · tat-ca), loai, q (mã/tên đối tượng, tiêu đề); phân trang AD-28 mục 3; thứ tự chờ duyệt cũ trước (FIFO — xử theo thứ tự đến), trạng thái khác mới trước. GET /summary đếm toàn tập thấy được, ⛔ không theo trang: theoTrangThai + loai[] {ma, nhan, soCho} = đúng danh mục loại có nguồn (GĐ1 hai loại), ⛔ không loại rỗng giả. GET /{id} chi tiết. ⛔ Không POST, không NhatKyService trong gói — ranh giới pin bằng test Người có quyền liệt kê ⇒ đúng thứ tự FIFO; summary bằng tổng mọi trang; loai[] đúng hai mã; gói hangdoi/ không có @PostMapping (RanhGioiGoiHangDoiTest)
Tập thấy được = đặc quyền duyệt của loại chính mình mở Với mỗi loại có plugin: yêu cầu đã gửi của loại mà người giữ maQuyenDuyet(loại) — hỏi quyền mỗi lượt, ⛔ không cache; cộng mọi yêu cầu có nguoi_mo_id = người («người soạn thấy đề xuất của mình đang ở đâu», cờ laCuaToi). Đang soạn (chưa gửi) ⛔ không vào hàng đợi. Ngoài tập ⇒ 404 vô hình, ⛔ không 403 (cùng nếp cô lập phía quản trị). Không phiên ⇒ 401 Người không đặc quyền, không mở gì ⇒ danh sách rỗng, đếm 0, chi tiết bất kỳ ⇒ 404; thu hồi vai ⇒ lượt kế mất hàng (T2.8); người mở không có đặc quyền vẫn thấy yêu cầu của mình
Quyền duyệt do máy chủ phán, màn chỉ phản ánh — quyetDinh {duyetDuoc, vi} Thứ tự kiểm: (1) trạng thái ≠ chờ duyệt ⇒ da-xu-ly; (2) đã ký vòng hiện tại ⇒ da-xu-ly; (3) là người soạn của vòng (K3 lớp khung, AD-9 mục 2) hoặc plugin của loại nói không (K3 theo nội dung1.3: tác giả phiên bản, qua HoSoService.laTacGiaPhienBan) ⇒ cua-toi; (4) không giữ đặc quyền ⇒ khong-co-quyen; còn lại duyetDuoc = true. «Của tôi» đứng trước «không có quyền» — lý do gần mình nhất Tác giả phiên bản hồ sơ mở chi tiết ⇒ cua-toi dù giữ approve; người đã ký ⇒ da-xu-ly; ba mutation bite b3/b4/b10 đỏ khi gỡ từng lớp
Kế tiếpkeTiepId để «duyệt và sang cái tiếp» Trong tập chờ duyệt cùng loại, FIFO, phần tử đầu tiên sau yêu cầu hiện tại (quay vòng về đầu) mà người này duyetDuoc; bỏ qua chính nó và yêu cầu không duyệt được; không có ⇒ null. Tính ở lượt đọc chi tiết, ⛔ không cache. Kèm soCapCan/capDaKy cho stepper — số cấp là của lượt, ⛔ không giả định 1 Ba yêu cầu chờ, người này là tác giả cái thứ hai ⇒ từ cái đầu keTiepId = cái thứ ba; từ cái cuối ⇒ quay về cái đầu
Ảnh trước/sau là ảnh của khung, ⛔ không đọc lại đối tượng truoc[]/sau[] sinh từ noi_dung_truoc/sau (jsonb cấp 1; object/array ⇒ chuỗi JSON gọn) qua từ điển nhãn của plugin (khoá lạ ⇒ nhãn = khoá); mỗi dòng {nhan, giaTri, co ∈ plain·added·removed·changed}. Ảnh chụp là bằng chứng (FR-023) — màn phản ánh đúng ảnh, kể cả khi đối tượng đã đổi Đổi tổ chức sau khi gửi ⇒ chi tiết vẫn hiện giá trị lúc gửi (HangDoiServiceTest.t42, bite b9)
Sổ ánh xạ loại → đặc quyền là MỘT sổ, dựng từ plugin SoAnhXaLoai dựng từ List<NguonHangDoi>; mỗi plugin khai loai · nhan · maQuyenDuyet · doiTuong(id) · hanhDong · tieuDe · anhHuong · nhanTruong · lyDoKhongDuyetDuoc. Trùng loại hoặc thiếu nhãn ⇒ từ chối khởi động (fail-closed). Lỗi trong plugin (đối tượng biến mất) ⇒ trường mô tả null, ⛔ không 500 cả hàng đợi. GĐ1 pin đúng hai loại → tpp_organization.approve; thêm loại là đổi pin có chủ ý Hai bean cùng loaiIllegalStateException lúc khởi động (T1.3/T1.4, bite b6); tổ chức bị xoá khỏi bảng ⇒ hàng vẫn hiện, ten null
Ranh giới góihangdoi/ là miền bề mặt tổng hợp Chỉ import kiểu công khai của approval (KhungPheDuyet …), iam.ChuTheVaQuyen, shared/**; ⛔ không lớp nội bộ miền nào — miền cắm vào bằng bean plugin, hangdoi không biết onboarding. approval/ giữ nguyên «chỉ gọi audit/shared» (t11). Khung mở thêm hai lối đọc: deXuatDaGui(loại) (kể cả đã quyết) và deXuatCuaNguoiMo(người) RanhGioiGoiHangDoiTest + RanhGioiGoiApprovalTest.t11 xanh; RanhGioiGoiToChucTest chỉ nới đúng hai kiểu plugin (T1.2)
Chỉ đọc ⇒ không vết; tên người chỉ ở DTO Không hàng audit.action_event cho lượt xem; nguoiGui.hoTen lấy một lô/trang qua ChuTheVaQuyen.hoTenCua, ⛔ không vào log. Không di trú, không đổi ma trận quyền, openapi.yaml +3 thao tác Sau lượt liệt kê/chi tiết ⇒ bảng nhật ký không thêm hàng (T2.6)

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

Không quyết định ở hàng đợi — duyệt/từ chối là của bộ áp từng loại (1.2, 1.3); ⛔ không thao tác hàng loạt ⇒ màn không vẽ nút. ⛔ Không loại vai / quyền / gán vai / tài khoản nội bộ (PO 12/09). ⛔ Không cắm E-005 / 3.2 — mỗi loại một plugin khi tới. ⛔ Không phân trang ở CSDL — lọc trong bộ nhớ như lietKe của 1.3 (tập GĐ1 nhỏ). ⛔ Không từ điển giá trị (chỉ nhãn khoá): cap_3, ban-nhap, mã ket_qua là mã thô — màn dùng từ điển API sẵn có của 1.2/1.3.

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

1. Đặc quyền duyệt ngụ ý được xem ảnh đề xuất — dù không có quyền đọc của miền (D8, câu chờ PO). Người giữ tpp_organization.approve nhưng thiếu tpp_organization.read thấy hàng và ảnh trước/sau ở hàng đợi, bấm sang màn đối tượng thì 403. Change chọn «đặc quyền duyệt ngụ ý được xem thứ mình phải duyệt» — mở rộng so với màn miền, phát hiện ở lens review. Muốn siết thì hàng đợi lọc thêm quyền đọc qua plugin (maQuyenXem) — một chỗ.

2. «Đã gửi» ở khung là submitted_at IS NOT NULL OR vong > 1 — vì lượt từ chối XOÁ mốc gửi. Khung 3.1 (D7) mở vòng mới khi từ chối và xoá submitted_at; nếu chỉ lọc theo mốc, đề xuất đã từ chối biến mất khỏi hàng đợi của chính người soạn. Phát hiện ở lưới T1.1/T2.2 ngày 14/09; mốc hàng (guiLuc, thứ tự) của những đề xuất ấy lùi về mốc mở.

3. Trạng thái là của KHUNG, không phải của miền. Kết luận «trả về chờ bổ sung» của hồ sơ 1.3 ghi vào khung bằng tu_choi ⇒ ở hàng đợi hiện là tu-choi; chỉ màn miền mới phân biệt ba kết cục. Nếu PO muốn hàng đợi phân biệt, plugin cần thêm lối «kết luận theo loại».

4. «Của tôi» = người MỞ, không phải tập người soạn theo vòng. GĐ1 không có luồng gửi lại nên hai tập trùng nhau; khi E-005 có người sửa-và-gửi-lại không phải người mở, người ấy không thấy đề xuất ở vế «của tôi» ⇒ mở rộng lúc đó. Cùng lúc: lọc/phân trang trong bộ nhớ và keTiepId quét ứng viên (laNguoiSoan + plugin mỗi ứng viên) — đủ cho GĐ1, cần lối đọc theo lô ở khung khi có hàng nghìn đơn.

5. Thư mục miền hangdoi/ nằm ngoài danh sách 11 thư mục của AD-24 mục 5 — PO chốt tên/chỗ. Nó không thuộc engine approval/ (phá t11) và không thuộc một miền đối tượng (E-005/3.2 sau này không phải onboarding). Đổi là git mv một gói + hai regex pin ranh giới + README. Câu K3 «theo nội dung hay theo người gửi» cho mọi bộ áp vẫn chờ PO từ 1.3 — hàng đợi phản ánh qua plugin nên đổi ở một chỗ.