PerPro API Documentation

Quy trình Phê duyệt Tờ trình & Mobile Gateway

Tài liệu hướng dẫn tích hợp các API quy trình phê duyệt thông qua Mobile Gateway Transaction Codes (MBGW TranCode) dành cho Website Client và ứng dụng di động.

11 MBGW APIs PP_PROPOSAL_* & PP_MY_PROPOSALS_* TranCodes
3 Nhóm API Danh sách, Phê duyệt & Đồng đề xuất
8 Actions Thao tác Phê duyệt & Review
PER_PRO MBGW Integration System

1. Danh sách Phê duyệt & Review

POST /api/v1/private/proposal-review-inbox/list TranCode: PP_PROPOSAL_CHECK_LIST — Lấy danh sách tờ trình phê duyệt/review PerPro theo 5 tab nghiệp vụ
Inbox
Lấy danh sách các tờ trình trong hộp thư phê duyệt/thẩm định của user hiện tại, phân chia theo 6 tab nghiệp vụ (PENDING_APPROVAL, PENDING_REVIEW, NOT_YET_TURN, DELEGATED, DELEGATED_BY_ME, PROCESSED).
Mặc định (khi không truyền hoặc truyền ALL), hệ thống sẽ lấy hợp nhất 5 nhóm công việc cần xử lý / theo dõi và không bao gồm nhóm đã xử lý (PROCESSED). Mỗi item hiện trả thêm coRequesters: danh sách người đồng đề xuất còn hiệu lực cùng trạng thái phản hồi, ghi chú và requestVersion của từng người.

Quy tắc kiểm tra & Ràng buộc nghiệp vụ (Validation Rules)

1. Phân quyền & Xác thực truy cập
  • Bắt buộc truyền Access Token qua Header (Authorization: Bearer <token>).
  • Quyền IAM yêu cầu: User phải có ít nhất 1 trong 3 quyền: PROPOSAL.APPROVE, PROPOSAL.REVIEW hoặc PROPOSAL.CREATE.
  • Người thực hiện (Actor) được trích xuất tự động từ Access Token của phiên đăng nhập (Client không cần truyền thêm thông tin user định danh trong payload).
2. Ý nghĩa của 6 Filter Tab và ALL
  • PENDING_APPROVAL (Chờ tôi phê duyệt): Các tờ trình có bước Phê duyệt (APPROVE) được gán trực tiếp cho bạn và đang PROCESSING. Nếu bạn đã ủy quyền bước này cho người khác (và ủy quyền còn hiệu lực), tờ trình sẽ tự động chuyển sang tab "Tôi đã ủy quyền".
  • PENDING_REVIEW (Chờ tôi thẩm định): Các tờ trình có bước Thẩm định (REVIEW) được gán trực tiếp cho bạn và đang PROCESSING.
  • NOT_YET_TURN (Chưa tới lượt xử lý): Các bước APPROVE hoặc REVIEW được gán trực tiếp cho bạn nhưng còn LOCKED do bước trước chưa hoàn tất.
  • DELEGATED (Được ủy quyền duyệt thay): Các tờ trình mà bạn được người khác ủy quyền xử lý thay, giấy ủy quyền đang còn hiệu lực và bước đó chưa hoàn tất.
  • DELEGATED_BY_ME (Tôi đã ủy quyền): Danh sách các tờ trình do bạn trực tiếp phụ trách nhưng bạn đã bàn giao ủy quyền cho người khác xử lý (dùng để theo dõi tiến độ công việc đã ủy quyền).
  • PROCESSED (Đã xử lý xong): Danh sách các tờ trình mà bạn đã trực tiếp tham gia xử lý hoàn tất (hoặc nhận ủy quyền và đã xử lý xong). Mỗi tờ trình chỉ hiển thị 1 dòng duy nhất.
    * Lưu ý: Tab này hoạt động độc lập và KHÔNG nằm trong bộ lọc ALL. Client bắt buộc phải truyền rõ ràng filter: ["PROCESSED"] khi muốn xem danh sách này.
  • ALL (hoặc mảng rỗng [] / không truyền): Tự động lấy hợp nhất 5 nhóm công việc cần xử lý / theo dõi (PENDING_APPROVAL, PENDING_REVIEW, NOT_YET_TURN, DELEGATED, DELEGATED_BY_ME) trong một query tối ưu. ALL tuyệt đối không bao gồm PROCESSED.
  • Kết hợp nhiều Filter: Client có thể truyền nhiều filter cùng lúc trong mảng (VD: ["PENDING_APPROVAL", "PENDING_REVIEW"]), hệ thống sẽ tự động gộp và loại bỏ các bước/tờ trình trùng lặp.
  • Trường sourceFilter (Nguồn gốc Filter của bản ghi): Trả về mã tab/bộ lọc nguồn mà bản ghi này xuất phát từ đó (PENDING_APPROVAL, PENDING_REVIEW, NOT_YET_TURN, DELEGATED, DELEGATED_BY_ME, hoặc PROCESSED). Trường này giúp Client/Frontend dễ dàng phân loại, hiển thị badge/tag nguồn hoặc xử lý điều hướng khi người dùng lọc nhiều tab cùng lúc hoặc dùng chế độ tổng hợp ALL.
  • Phân định Người được giao & Người nhận ủy quyền:
    • assigneeEmail và assigneeName: Luôn là snapshot người được giao trực tiếp cho bước duyệt (không bị ghi đè khi có ủy quyền).
    • delegatedApproverEmail và delegatedApproverName: Trả về thông tin của người được ủy quyền khi bước có giấy ủy quyền còn hiệu lực; nếu không có ủy quyền thì trả về null.
    • Lưu ý Frontend: Khi isDelegated = true, UI ưu tiên hiển thị tên người được ủy quyền để người dùng nắm rõ ai đang xử lý thay.
  • Trường canProcess (Quyền thao tác): Trả về true (1) khi bước công việc đang thực sự đến lượt xử lý (status = PROCESSING). Ở nhóm "Tôi đã ủy quyền" và "Đã xử lý", giá trị này luôn trả về false (0).
3. Thông tin người tạo tờ trình (Maker Profile Snapshot)
  • Mỗi bản ghi trong items trả về đầy đủ bộ thông tin định danh snapshot của người tạo tờ trình:
    • makerEmail (String): Email người tạo tờ trình.
    • makerName (String): Họ và tên người tạo tờ trình.
    • makerDepartment (String / null): Phòng ban của người tạo tờ trình.
    • makerTitle (String / null): Chức danh của người tạo tờ trình.
    • makerCompany (String / null): Đơn vị / Công ty của người tạo tờ trình.

Mô tả Payload (Request)

Field Type Description
filter Array<String> Optional Một hoặc nhiều mã: PENDING_APPROVAL, PENDING_REVIEW, NOT_YET_TURN, DELEGATED, DELEGATED_BY_ME, PROCESSED, ALL. Mặc định (hoặc khi chọn ALL), hệ thống lấy 5 nhóm công việc cần xử lý (không bao gồm PROCESSED). Chuỗi đơn vẫn được nhận tạm thời để tương thích client cũ.
keyword String Optional Từ khóa tìm kiếm không phân biệt hoa thường trên Số tờ trình (number) và Trích yếu (subjectMatter).
currentPage Integer Optional Trang hiện tại (mặc định: 1, số bản ghi mỗi trang cố định perPage = 10).

Request Sample

JSON - Request Body
{
    "filter": ["PENDING_APPROVAL"],
    "keyword": "0045/2026",
    "currentPage": 1
}

Response Sample (Thành công 200 OK)

JSON - Response 200 OK
{
    "success": true,
    "code": "200",
    "message": "Lấy danh sách phê duyệt/review thành công.",
    "data": {
        "items": [
            {
                "proposalId": 45,
                "stepId": 120,
                "sourceFilter": "DELEGATED",
                "number": "0045/2026/TT",
                "subjectMatter": "Rà soát chi phí hạ tầng máy chủ",
                "proposalStatus": "UNDER_APPROVAL",
                "makerEmail": "creator@example.com",
                "makerName": "Nguyễn Văn Maker",
                "makerDepartment": "Khối Công nghệ Thông tin",
                "makerTitle": "Chuyên viên Cao cấp",
                "makerCompany": "Công ty Cổ phần PER",
                "coRequesters": [{
                    "email": "dongdexuat@example.com",
                    "status": "AGREED",
                    "note": null,
                    "requestVersion": 1
                }],
                "stepType": "APPROVE",
                "stepStatus": "PROCESSING",
                "stepResult": null,
                "assigneeEmail": "checker@example.com",
                "assigneeName": "Người duyệt gốc",
                "delegatedApproverEmail": "delegate@example.com",
                "delegatedApproverName": "Người được ủy quyền",
                "isDelegated": true,
                "canProcess": true,
                "updatedAt": "2026-08-24T08:30:00+07:00"
            }
        ],
        "pagination": {
            "currentPage": 1,
            "perPage": 10,
            "totalItems": 1,
            "totalPages": 1,
            "hasNext": false,
            "hasPrev": false
        }
    }
}

2. Quy trình Phê duyệt, Thao tác & Nhật ký

POST /api/v1/private/proposal-approvals/available-actions TranCode: PP_PROPOSAL_APPROVALS_AVAILABLE_ACTIONS — Lấy quy trình và thao tác phê duyệt khả dụng PerPro
Core
Lấy thông tin toàn bộ quy trình phê duyệt của tờ trình (dạng cây gồm các bước gốc steps và bước con childSteps), danh sách các bước đang chờ xử lý (currentSteps) và các hành động mà người dùng hiện tại được phép bấm (currentUserAllowedActions).

Quy tắc kiểm tra & Ràng buộc nghiệp vụ (Validation Rules)

1. Xác thực người gọi (Actor & Resource-Level Policy)
  • Bắt buộc truyền Access Token qua Header (Authorization: Bearer <token>).
  • Kiểm tra quyền truy cập: User đang đăng nhập phải có liên quan tới tờ trình mới được phép xem:
    • Là người tạo tờ trình (createdBy) hoặc người lập tờ trình (makerEmail).
    • Là người đồng đề xuất còn hiệu lực của tờ trình (coRequesters).
    • Là người giao việc (assignedByEmail) của ít nhất một bước chưa hoàn tất xử lý (status !== 'PROCESSED').
    • Là người được phân công xử lý (assignee) của ít nhất một bước active trong quy trình.
    • Là người nhận ủy quyền còn hiệu lực (delegatedApprover) của ít nhất một bước trong quy trình.
Nếu user không thuộc bất kỳ vai trò nào trên, hệ thống sẽ trả về mã lỗi 403 Forbidden.
2. Cấu trúc Linked List & Kiểm tra xung đột đa bước
  • steps chỉ chứa các bước gốc theo đúng thứ tự prevStepId / nextStepId. Mỗi bước gốc có mảng childSteps chứa các bước review con.
  • currentSteps luôn là danh sách toàn bộ các bước PROCESSING. Nhiều bước processing chỉ hợp lệ khi tất cả là REVIEW và có cùng parentStepId.
  • Mỗi step trả thêm assignedByEmail. Field này chứa email của actor đã tạo step qua need_review/need_checker; các step thông thường trả null.
  • Mỗi step trả số nguyên approvalFlowLocked (0, 1, 2 hoặc 3). Field là mode snapshot từ template tại lần submit/rework-submit.
  • Mỗi step trả canRevokeRequest. Field chỉ là true khi tờ trình không bị khóa (proposalStatus !== 'LOCKED'), actor có quyền PROPOSAL.APPROVE, step là bước gốc (parentStepId === null), trạng thái chưa hoàn tất (status !== 'PROCESSED'), không phải PROCESSING với kết quả REWORK/REQUEST_DOCUMENTS, và assignedByEmail trùng email của actor.
  • Nếu cùng một User (hoặc người được ủy quyền) được giao xử lý nhiều hơn 1 bước PROCESSING cùng lúc trong cùng tờ trình, API trả về lỗi 409 Conflict (do payload perform-action không truyền stepId).
3. Lưu trữ và hiển thị lý do tham vấn, ý kiến chuyên môn
  • Bước do người khác chỉ định (assignedByEmail != null): Các bước phát sinh từ need_review/need_checker trả về các field tách biệt:
    • assignerNote (chuỗi/null): Lý do tham vấn chuyên môn, lưu ở cột perpro_approval_step.assigner_note.
    • note (chuỗi/null): Ý kiến chuyên môn của người được giao, lưu ở cột perpro_approval_step.note; ban đầu là null.
    • createdAt: Thời điểm giao việc từ created_at của step.
    • updatedAt: Thời điểm xử lý từ updated_at của step, chỉ hiển thị cùng ý kiến khi note có nội dung. Các datetime trong workflow dùng ISO 8601 theo GMT+7.
  • Bước thông thường (assignedByEmail == null): assignerNote là null; note tiếp tục là ghi chú văn bản thông thường.
  • Migration Version20260914000001 thêm cột và tách JSON ghi chú giao việc cũ thành hai cột. Chỉ chuyển bản ghi có đủ bốn key và kiểu dữ liệu hợp lệ; text thường được giữ nguyên. Snapshot lịch sử cũ giữ nguyên để tra cứu.
4. Ý nghĩa & Nhãn hiển thị (Display Text) của proposalStatus, status và result
A. Trường proposalStatus (Trạng thái tổng thể của tờ trình):
Mã Status (API) Hiển thị trên UI Mô tả chi tiết & Vòng đời
DRAFT Nháp Tờ trình mới tạo, người lập đang soạn thảo và chưa gửi duyệt.
CO_REQUESTERS_PENDING Chờ xác nhận Maker đã gửi yêu cầu xác nhận cho người đồng đề xuất; chưa được trình phê duyệt.
PENDING_APPROVAL_SUBMISSION Chờ Trình Phê duyệt Mọi người đồng đề xuất đã phản hồi; Maker xem ghi chú và quyết định bước tiếp theo.
UNDER_APPROVAL Đang duyệt Tờ trình đã gửi đi và đang nằm trong luồng phê duyệt (bước Approve đang hoạt động).
PENDING_REVIEW Chờ đánh giá Người duyệt yêu cầu thêm thẩm định (need_review), tờ trình tạm chuyển sang luồng thẩm định chuyên môn trước khi quay lại người duyệt.
REVISION_REQUIRED Cần sửa lại Người duyệt yêu cầu làm lại (need_rework), người tạo có thể sửa và gửi lại qua action rework-submit.
PENDING_DOCUMENTS Chờ bổ sung chứng từ Người xử lý bước phê duyệt hoặc thẩm định yêu cầu hồ sơ (documents_request), người tạo có thể tải lên chứng từ và gửi qua action additional-documents-submit.
REJECTED Bị từ chối Người duyệt từ chối tờ trình (reject), quy trình kết thúc tại đây và tờ trình không còn hiệu lực.
APPROVED Đã duyệt Tất cả các bước duyệt trong quy trình đã được thông qua thành công (approve), tờ trình có hiệu lực chính thức.
LOCKED Khóa cấu hình Tờ trình bị khóa, không cho phép chỉnh sửa hoặc thực hiện thao tác quy trình.
B. Trường status (Trạng thái của từng bước duyệt trong steps):
Mã Status (API) Hiển thị trên UI Mô tả chi tiết
DRAFT Bản nháp Bước duyệt đang ở dạng nháp (khi tờ trình chưa submit).
LOCKED Chưa đến lượt Bước duyệt đang bị khóa, cần chờ các bước trước đó xử lý xong.
PROCESSING Đang xử lý Bước duyệt đang mở và đến lượt người được phân công xử lý (duyệt/thẩm định/ủy quyền).
PROCESSED Đã xử lý Bước duyệt đã hoàn tất xử lý và có kết quả tương ứng trong trường result.
C. Trường result (Kết quả xử lý của bước duyệt khi đã PROCESSED):
Mã Result (API) Hiển thị trên UI Hành động tạo ra (Action) & Ý nghĩa
null (Chưa có) Bước chưa hoàn tất xử lý (khi status là PROCESSING, LOCKED hoặc DRAFT).
APPROVED Đồng ý / Đã phê duyệt Tạo bởi action approve: Người duyệt chấp thuận nội dung tờ trình.
REJECTED Từ chối Tạo bởi action reject: Người duyệt từ chối tờ trình.
REWORK Yêu cầu điều chỉnh Tạo bởi action need_rework: Người duyệt yêu cầu người tạo chỉnh sửa và gửi lại tờ trình.
REVIEWED Hoàn tất review / Đã thẩm định Tạo bởi action reviewed: Người thẩm định đã hoàn thành việc xem xét, đánh giá.
REQUEST_DOCUMENTS Yêu cầu bổ sung chứng từ Tạo bởi action documents_request: Người phê duyệt hoặc thẩm định yêu cầu người tạo bổ sung hồ sơ chứng từ.
5. Ý nghĩa các giá trị của cột approval_flow_locked

Cột approval_flow_locked (trong bảng perpro_template, snapshot sang perpro_proposal và từng perpro_approval_step dưới dạng số nguyên) quy định ý nghĩa và hành vi áp dụng quy trình phê duyệt như sau:

Giá trị Chế độ Mô tả chi tiết ý nghĩa & Hành vi
0 Không add system step Không add thêm system step vào quy trình phê duyệt.
1 Mặc định add step cuối Mặc định chỉ add step cuối, user thích chỉnh thì chỉnh.
2 Cố định luôn luồng Add step, & cố định luôn luồng không cho thêm bớt.
3 Khóa bước cuối (system_step) Add vào bước cuối & không được chỉnh sửa (system_step).

Mô tả Payload (Request)

Field Type Description
proposalId Integer Required ID của tờ trình cần lấy thông tin quy trình. Phải là số nguyên dương (>= 1).

Request Sample

JSON - Request Body
{
    "proposalId": 123
}

Response Sample (Thành công 200 OK)

JSON - Response 200 OK
{
    "success": true,
    "code": "200",
    "message": "Lấy thông tin quy trình và hành động được phép thành công.",
    "data": {
        "proposalId": 123,
        "proposalStatus": "UNDER_APPROVAL",
        "workflowCompleted": false,
        "requesterPermissions": [
            { "permission": "PROPOSAL.CREATE", "granted": true },
            { "permission": "PROPOSAL.APPROVE", "granted": true },
            { "permission": "PROPOSAL.REVIEW", "granted": false }
        ],
        "steps": [
            {
                "stepId": 1,
                "proposalId": 123,
                "nextStepId": 2,
                "prevStepId": null,
                "parentStepId": null,
                "stepType": "APPROVE",
                "assignedByEmail": null,
                "approvalFlowLocked": 0,
                "status": "PROCESSED",
                "result": "APPROVED",
                "assignerNote": null,
                "note": "Đồng ý phê duyệt",
                "assignee": {
                    "email": "user1@example.com",
                    "name": "Nguyễn Văn A",
                    "title": "Trưởng phòng",
                    "department": "Phòng IT",
                    "company": "Công ty ABC"
                },
                "delegatedApprover": null,
                "isCurrent": false,
                "isAssignedToRequester": false,
                "createdAt": "2026-08-24T08:00:00+07:00",
                "createdBy": "creator@example.com",
                "updatedAt": "2026-08-24T09:00:00+07:00",
                "updatedBy": "user1@example.com",
                "childSteps": []
            },
            {
                "stepId": 2,
                "proposalId": 123,
                "nextStepId": null,
                "prevStepId": 1,
                "parentStepId": null,
                "stepType": "APPROVE",
                "assignedByEmail": "user1@example.com",
                "approvalFlowLocked": 2,
                "status": "PROCESSING",
                "result": null,
                "assignerNote": "Nhờ anh duyệt gấp giúp em",
                "note": null,
                "assignee": {
                    "email": "user2@example.com",
                    "name": "Trần Thị B",
                    "title": "Phó Giám đốc",
                    "department": "Khối Vận hành",
                    "company": "Công ty ABC"
                },
                "delegatedApprover": null,
                "isCurrent": true,
                "isAssignedToRequester": true,
                "createdAt": "2026-08-26T22:45:15+07:00",
                "createdBy": "user1@example.com",
                "updatedAt": null,
                "updatedBy": null,
                "childSteps": []
            }
        ],
        "currentSteps": [
            {
                "stepId": 2,
                "proposalId": 123,
                "stepType": "APPROVE",
                "assignedByEmail": "user1@example.com",
                "approvalFlowLocked": 2,
                "status": "PROCESSING",
                "result": null,
                "assignerNote": "Nhờ anh duyệt gấp giúp em",
                "note": null,
                "assignee": {
                    "email": "user2@example.com",
                    "name": "Trần Thị B",
                    "title": "Phó Giám đốc",
                    "department": "Khối Vận hành",
                    "company": "Công ty ABC"
                },
                "delegatedApprover": null,
                "isCurrent": true,
                "isAssignedToRequester": true,
                "canRevokeRequest": false,
                "requiredPermission": "PROPOSAL.APPROVE",
                "currentUserAllowedActions": [
                    "approve",
                    "reject",
                    "need_rework",
                    "need_review",
                    "need_checker",
                    "documents_request",
                    "delegate_approver"
                ],
                "createdAt": "2026-08-24T08:00:00+07:00",
                "createdBy": "creator@example.com",
                "updatedAt": "2026-08-24T09:00:00+07:00",
                "updatedBy": "user1@example.com"
            }
        ]
    }
}
POST /api/v1/private/proposal-approvals/eligible-reviewers TranCode: PP_PROPOSAL_APPROVALS_ELIGIBLE_REVIEWERS — Lấy danh sách reviewer hợp lệ PerPro từ IAM
Core
Lấy danh sách tất cả các tài khoản có quyền PROPOSAL.REVIEW trên hệ thống IAM để phục vụ chức năng yêu cầu thêm người thẩm định (action need_review) trên màn hình phê duyệt.
Phân biệt với Thêm người duyệt (need_checker): API này chỉ dùng riêng cho action need_review (lọc theo quyền REVIEW). Đối với action thêm cấp phê duyệt (need_checker), client sử dụng API danh bạ người dùng có quyền APPROVE qua endpoint POST /api/v1/private/delegations/eligible-users.

Quy tắc kiểm tra & Ràng buộc nghiệp vụ

1. Quyền IAM yêu cầu & Xác thực
  • Bắt buộc truyền Access Token qua Header (Authorization: Bearer <token>).
  • Quyền IAM yêu cầu: User gọi API bắt buộc phải có quyền PERMISSION:PROPOSAL:APPROVE. Nếu thiếu quyền sẽ bị từ chối với mã lỗi 403 Forbidden.
2. Cơ chế lọc & Sắp xếp danh sách
  • Hệ thống sẽ lấy danh sách các user có quyền REVIEW đối với module phê duyệt tờ trình.
  • Loại bỏ chính người gọi: Tự động lọc bỏ email của User đang đăng nhập khỏi danh sách kết quả.
  • Sắp xếp: Danh sách trả về được sắp xếp theo thứ tự bảng chữ cái dựa trên Tên người dùng (hoặc Email).

Mô tả Payload (Request)

API này không yêu cầu tham số payload trong body (truyền JSON rỗng {} hoặc body trống).

Request Sample

JSON - Request Body
{}

Response Sample (Thành công 200 OK)

JSON - Response 200 OK
{
    "success": true,
    "code": "200",
    "message": "Lấy danh sách reviewer hợp lệ thành công.",
    "data": {
        "items": [
            {
                "email": "reviewer1@example.com",
                "name": "Đặng Thị Thảo",
                "title": "Chuyên viên Thẩm định Rủi ro",
                "department": "Ban Thẩm định",
                "company": "Công ty ABC"
            },
            {
                "email": "reviewer2@example.com",
                "name": "Nguyễn Văn Hùng",
                "title": "Trưởng nhóm Pháp chế",
                "department": "Phòng Pháp chế",
                "company": "Công ty ABC"
            }
        ]
    }
}
POST /api/v1/private/proposal-approvals/perform-action TranCode: PP_PROPOSAL_APPROVALS_PERFORM_ACTION — Thực hiện thao tác phê duyệt/review PerPro (10 actions + note)
Core
Thực hiện hành động phê duyệt trên workflow của tờ trình. Hỗ trợ 10 hành động: approve (Phê duyệt), reject (Từ chối), need_rework (Yêu cầu làm lại), need_review (Yêu cầu thêm thẩm định), need_checker (Yêu cầu thêm người duyệt), reviewed (Đã thẩm định xong), documents_request (Yêu cầu bổ sung hồ sơ), delegate_approver (Ủy quyền xử lý), revoke_delegate_approver (Thu hồi ủy quyền), revoke_added_step (Thu hồi yêu cầu bổ sung bước).
Các hành động nghiệp vụ có ghi chú sử dụng trường chuẩn data.note; revoke_delegate_approver không yêu cầu data. Người đồng đề xuất không được thực hiện thao tác APPROVE hoặc được thêm làm Checker của chính tờ trình này; nếu được phân công bước REVIEW thì vẫn có thể xử lý theo quyền REVIEW.

Quy tắc kiểm tra & Ràng buộc nghiệp vụ (Validation Rules)

1. Quy tắc chung & Cơ chế Khóa đồng thời (Pessimistic Lock)
  • Kiểm tra bước xử lý: Bước phê duyệt/thẩm định hiện tại phải ở trạng thái PROCESSING và chưa có kết quả (result == null). User phải là effective assignee (hoặc người nhận ủy quyền hợp lệ).
  • Chặn khi tờ trình bị khóa: Nếu tờ trình đang ở trạng thái LOCKED, hệ thống từ chối mọi thao tác phê duyệt/thẩm định với lỗi 409 Conflict ("Tờ trình đang bị khóa, không thể thực hiện thao tác.").
  • Cơ chế chống xung đột: Hệ thống đảm bảo tính đồng bộ dữ liệu. Nếu bước phê duyệt đã bị một người dùng khác xử lý trước đó, API sẽ trả về lỗi 409 Conflict.
2. Cơ chế ghi chú công việc (Giao việc & Phản hồi)
  • Khi giao thêm việc (need_review / need_checker): Lời nhắn trong data.note của người giao việc sẽ được hệ thống lưu lại thành ý kiến chỉ đạo kèm thời gian giao.
  • Khi phản hồi / hoàn tất công việc (reviewed / documents_request...): Nhận xét trong data.note của người được giao sẽ thay thế note text hiện tại. Note cũ vẫn xem được trong lịch sử bước; riêng note giao việc dạng JSON cập nhật phần ý kiến phản hồi kèm thời gian hoàn tất.
  • Quy chuẩn gửi dữ liệu: Client chỉ cần gửi nội dung vào trường data.note cho mọi thao tác; hệ thống sẽ tự động xử lý và lưu trữ theo đúng luồng giao việc hay phản hồi.
  • Giới hạn độ dài ghi chú: Nội dung data.note và tổng chuỗi ghi chú của bước phê duyệt không được vượt quá 65.535 ký tự (hoặc byte UTF-8 tương ứng). Nếu vượt quá, API trả về lỗi 400 Bad Request.
  • Lịch sử người được chọn: Các thao tác need_review, need_checker, delegate_approver, revoke_delegate_approver và revoke_added_step lưu thêm email, tên, chức danh, phòng ban và công ty của người được thêm hoặc ủy quyền trong actionContext.targetPerson.
3. Quy tắc cho các hành động bước APPROVE (approve, reject, need_rework, documents_request)
  • Quyền IAM: User bắt buộc phải có quyền PERMISSION:PROPOSAL:APPROVE.
  • Trạng thái tờ trình: Bắt buộc phải là UNDER_APPROVAL.
  • Yêu cầu bổ sung chứng từ: Action documents_request dùng cùng validation actor và quyền PROPOSAL.APPROVE của bước hiện tại; step giữ PROCESSING với result REQUEST_DOCUMENTS và proposal chuyển sang PENDING_DOCUMENTS.
  • Kiểm tra người nhận ủy quyền: Khi người nhận ủy quyền thực hiện phê duyệt, hệ thống sẽ kiểm tra lại hiệu lực của việc ủy quyền. Nếu quyền đã bị thu hồi hoặc xóa, request sẽ bị từ chối với lỗi 403.
Quy tắc chống tự phê duyệt (Chặn tuyệt đối): Người tạo tờ trình (createdBy) KHÔNG ĐƯỢC PHÉP thực hiện phê duyệt (approve) tờ trình của chính mình. Vi phạm sẽ bị trả về mã lỗi 01.
4. Quy tắc cho hành động "need_review" & "need_checker"
  • need_review: Chèn 1 root step REVIEW mới ngay TRƯỚC bước Approve hiện tại, chuyển step Review sang PROCESSING, khóa step Approve sang LOCKED và đưa proposal về PENDING_REVIEW. Reviewer phải có quyền PROPOSAL.REVIEW trong IAM.
  • Cờ yêu cầu ký tờ trình (data.reviewers[].requiresProposalSign): Tùy chọn kiểu Boolean (mặc định false khi không truyền) nằm trong từng đối tượng của mảng data.reviewers cho action need_review. Khi gửi true, bước Review mới được tạo sẽ được đánh dấu yêu cầu ký (perpro_approval_step.requires_proposal_sign = 1) và nhân sự thẩm định này sẽ được hiển thị trên khối chữ ký tờ trình (bản in / bản trình ký PDF và DOCX) sau khi tờ trình hoàn tất phê duyệt.
  • need_checker: Chèn 1 root step APPROVE mới ngay TRƯỚC bước hiện tại, chuyển step mới sang PROCESSING, step hiện tại sang LOCKED; proposal giữ nguyên UNDER_APPROVAL. Checker phải có quyền PROPOSAL.APPROVE trong IAM và KHÔNG ĐƯỢC là người tạo tờ trình.
  • Với cả hai action, backend lấy email của actor đang đăng nhập, lưu vào perpro_approval_step.assigned_by_email của step mới và trả về field assignedByEmail.
  • Không trùng người xử lý: Email của reviewer/checker mới không được trùng với email của bất kỳ bước APPROVE hoặc REVIEW nào đang có trong cùng luồng. Email được so sánh sau khi bỏ khoảng trắng đầu/cuối và không phân biệt hoa thường.
5. Quy tắc cho hành động "delegate_approver" (Ủy quyền)
  • Chỉ assignee gốc của bước APPROVE/PROCESSING mới được ủy quyền. Người nhận ủy quyền không được ủy quyền tiếp.
  • Người nhận ủy quyền phải nằm trong danh bạ perpro_delegation_recipient của actor, có quyền PROPOSAL.APPROVE trong IAM và KHÔNG ĐƯỢC là người tạo tờ trình.
  • Thiết lập ủy quyền thành công sẽ không làm thay đổi trạng thái của bước phê duyệt hiện tại.
6. Quy tắc cho hành động "revoke_delegate_approver" (Thu hồi ủy quyền)
  • Chỉ assignee gốc của bước APPROVE/PROCESSING có mapping ủy quyền đang hiệu lực và có quyền PROPOSAL.APPROVE được thu hồi. Người nhận ủy quyền không được tự thu hồi.
  • Mapping chuyển từ ACTIVE sang REVOKED và ghi audit thu hồi; step tiếp tục PROCESSING, proposal giữ UNDER_APPROVAL.
  • Sau khi thu hồi, quyền xử lý bước được trả lại assignee gốc.
7. Quy tắc cho hành động "revoke_added_step" (Thu hồi yêu cầu)
  • Bắt buộc data.stepId. Chỉ actor trùng assignedByEmail của root step phát sinh, có PROPOSAL.APPROVE, được thu hồi.
  • Không cho phép step PROCESSED, hoặc step PROCESSING có kết quả REWORK/REQUEST_DOCUMENTS (trả errorCode = 409). Với step hợp lệ, backend soft-delete step, thu hồi delegation ACTIVE và nối lại prevStepId/nextStepId.
  • Nếu step đang PROCESSING, step kế tiếp được kích hoạt và proposal chuyển theo loại step kế tiếp. Nếu step chưa tới lượt, proposal/current step giữ nguyên.
8. Cơ chế Push Notification & Thông báo tự động theo từng Action

Sau khi transaction của perform-action commit thành công, backend tự động kích hoạt thông báo (In-App trên Web Portal và Push Notification về Mobile qua PerHub):

  • Thời điểm kích hoạt: Chạy dạng post-commit hook. Nếu transaction DB lỗi/rollback, tuyệt đối không phát thông báo.
  • Cơ chế Fail-safe: Mọi lỗi hạ tầng thông báo (lỗi mạng, token hết hạn, PerHub downtime...) được log an toàn, không làm throw exception hay fail kết quả API (client vẫn nhận 200 OK).
  • Quy tắc Deep Link (navigate): Người nhận là người tạo (Maker) điều hướng về my-proposals/{id}; Checker/Reviewer/Người nhận ủy quyền điều hướng về approvals/{id}. Các action thu hồi không gửi kèm URL.
Action Event Code Người nhận (Recipients) Điều kiện kích hoạt Điều hướng (navigate)
approve STEP_APPROVED Người tạo tờ trình (Maker) Luôn kích hoạt khi approve bước my-proposals/{id}
CHECKER_TURN Checker của bước APPROVE kế tiếp Bước kế tiếp chuyển sang PROCESSING là loại APPROVE approvals/{id}
REVIEWER_ADDED Reviewer của bước REVIEW kế tiếp Bước kế tiếp chuyển sang PROCESSING là loại REVIEW approvals/{id}
PROPOSAL_COMPLETED Người tạo tờ trình (Maker) Bước cuối được duyệt & tờ trình hoàn tất (APPROVED) my-proposals/{id}
reject PROPOSAL_REJECTED Người tạo tờ trình (Maker) Luôn kích hoạt khi từ chối tờ trình my-proposals/{id}
need_rework PROPOSAL_REWORKED Người tạo tờ trình (Maker) Luôn kích hoạt khi yêu cầu sửa lại my-proposals/{id}
need_review REVIEWER_ADDED Reviewer mới được chỉ định Chèn bước Review mới thành công approvals/{id}
need_checker CHECKER_ADDED Checker mới được chỉ định Chèn bước Checker mới thành công approvals/{id}
reviewed CHECKER_TURN Checker của bước APPROVE liền tiếp theo Bước mới được kích hoạt là loại APPROVE approvals/{id}
REVIEWER_ADDED Reviewer của bước REVIEW liền tiếp theo Bước mới được kích hoạt là loại REVIEW approvals/{id}
documents_request SUPPLEMENT_REQUESTED Người tạo tờ trình (Maker) Luôn kích hoạt khi yêu cầu bổ sung chứng từ my-proposals/{id}
delegate_approver APPROVAL_DELEGATED Người nhận ủy quyền Thiết lập ủy quyền xử lý thành công approvals/{id}
revoke_delegate_approver APPROVAL_DELEGATION_REVOKED Người bị thu hồi ủy quyền Thu hồi ủy quyền đang hiệu lực thành công (Không đính kèm URL)
revoke_added_step REVIEWER_RECALLED Reviewer của bước bị thu hồi Bước bị thu hồi là loại REVIEW (Không đính kèm URL)
CHECKER_RECALLED Checker của bước bị thu hồi Bước bị thu hồi là loại APPROVE (Không đính kèm URL)

Mô tả Payload (Request)

Field Type Description
proposalId Integer Required ID của tờ trình. Phải là số nguyên dương.
action String Required Loại hành động: approve, reject, need_rework, need_review, need_checker, reviewed, documents_request, delegate_approver, revoke_delegate_approver, revoke_added_step.
data Object Conditional Đối tượng dữ liệu đính kèm. Bắt buộc truyền khi action là need_review, need_checker, delegate_approver hoặc revoke_added_step. Riêng action thu hồi step bắt buộc data.stepId. Tùy chọn truyền khi muốn gửi note cho các action khác.
data.reviewers[].requiresProposalSign Boolean Optional Cờ yêu cầu ký tờ trình (mặc định false). Chỉ áp dụng khi action = need_review: nằm trong từng đối tượng của data.reviewers[]. Khi được truyền true, người thẩm định mới được thêm sẽ hiển thị trong khối chữ ký tờ trình (bản in / bản trình ký DOCX & PDF) sau khi tờ trình hoàn tất phê duyệt.

Request Sample 1: Action approve (Phê duyệt)

JSON - Request Body
{
    "proposalId": 123,
    "action": "approve",
    "data": {
        "note": "Đồng ý phê duyệt phần hạn mức bổ sung theo biên bản họp."
    }
}

Request Sample 2: Action reject (Từ chối)

JSON - Request Body
{
    "proposalId": 123,
    "action": "reject",
    "data": {
        "note": "Không đồng ý phê duyệt do rủi ro tài chính cao."
    }
}

Request Sample 3: Action need_rework (Yêu cầu làm lại)

JSON - Request Body
{
    "proposalId": 123,
    "action": "need_rework",
    "data": {
        "note": "Đề nghị bổ sung thêm tài liệu đánh giá rủi ro."
    }
}

Request Sample 4: Action need_review (Yêu cầu thêm người thẩm định)

JSON - Request Body
{
    "proposalId": 142,
    "action": "need_review",
    "data": {
        "reviewers": [
            {
                "email": "admin@movi.vn",
                "requiresProposalSign": true
            }
        ],
        "note": null
    }
}

Request Sample 5: Action need_checker (Yêu cầu thêm người phê duyệt)

JSON - Request Body
{
    "proposalId": 123,
    "action": "need_checker",
    "data": {
        "checkers": [
            { "email": "checker@example.com" }
        ],
        "note": "Nhờ anh duyệt bổ sung phần hạn mức tín dụng vượt thẩm quyền."
    }
}

Request Sample 6: Action reviewed (Đã thẩm định xong)

JSON - Request Body
{
    "proposalId": 123,
    "action": "reviewed",
    "data": {
        "note": "Đã xem xét và đánh giá hồ sơ đầy đủ."
    }
}

Request Sample 7: Action documents_request (Yêu cầu bổ sung hồ sơ)

JSON - Request Body
{
    "proposalId": 123,
    "action": "documents_request",
    "data": {
        "note": "Bổ sung thêm bản sao kê tài khoản ngân hàng."
    }
}

Request Sample 8: Action delegate_approver (Ủy quyền bước phê duyệt)

JSON - Request Body
{
    "proposalId": 123,
    "action": "delegate_approver",
    "data": {
        "delegatedApprover": {
            "email": "delegate@example.com"
        },
        "validTo": "2026-12-31T23:59:59+07:00",
        "note": "Đồng ý ủy quyền xử lý bước phê duyệt hiện tại."
    }
}

Request Sample 9: Action revoke_delegate_approver (Thu hồi ủy quyền bước phê duyệt)

JSON - Request Body
{
    "proposalId": 123,
    "action": "revoke_delegate_approver"
}

Request Sample 10: Action revoke_added_step (Thu hồi yêu cầu bổ sung bước)

JSON - Request Body
{
    "proposalId": 123,
    "action": "revoke_added_step",
    "data": {
        "stepId": 456
    }
}

Response Sample

JSON - Response 200 OK
{
    "success": true,
    "code": "200",
    "message": "Phê duyệt bước hiện tại thành công.",
    "data": {
        "proposalId": 123,
        "performedAction": "approve",
        "proposalStatus": "UNDER_APPROVAL",
        "workflowCompleted": false,
        "requesterPermissions": [
            { "permission": "PROPOSAL.CREATE", "granted": true },
            { "permission": "PROPOSAL.APPROVE", "granted": true },
            { "permission": "PROPOSAL.REVIEW", "granted": false }
        ],
        "steps": [ "...danh sách các steps sau khi cập nhật..." ],
        "currentSteps": [ "...danh sách các bước đang chờ xử lý tiếp theo..." ]
    }
}

Mô tả Chi tiết Dữ liệu Phản hồi (Response Data Model)

Trường dữ liệu Kiểu dữ liệu Mô tả chi tiết & Ý nghĩa nghiệp vụ
proposalId Integer ID của tờ trình vừa được xử lý.
performedAction String Mã hành động vừa được backend thực hiện thành công (ví dụ: approve, need_review, delegate_approver...).
proposalStatus String Trạng thái tiến trình mới của tờ trình (UNDER_APPROVAL, PENDING_REVIEW, PENDING_DOCUMENTS, REVISION_REQUIRED, APPROVED, REJECTED...).
workflowCompleted Boolean true nếu quy trình phê duyệt đã kết thúc (đã duyệt hoàn tất hoặc bị từ chối); false nếu luồng vẫn còn các bước tiếp theo.
requesterPermissions Array<Object> Danh sách phân quyền của actor đối với tờ trình này (quyền PROPOSAL.CREATE, PROPOSAL.APPROVE, PROPOSAL.REVIEW).
steps Array<Object> Danh sách đầy đủ tất cả các bước phê duyệt thuộc quy trình sau khi cập nhật.
currentSteps Array<Object> Danh sách các bước hiện đang ở trạng thái PROCESSING chờ xử lý tiếp theo.
addedReviewerStepId Integer | conditional ID của bước REVIEW mới được thêm vào quy trình. Chỉ trả về khi thực hiện action need_review.
addedCheckerStepId Integer | conditional ID của bước APPROVE mới được chèn vào quy trình. Chỉ trả về khi thực hiện action need_checker.
delegatedApprovalStepId Integer | conditional ID của bước phê duyệt được thiết lập ủy quyền. Chỉ trả về khi thực hiện action delegate_approver.
delegationId Integer | conditional ID của bản ghi ủy quyền được tạo mới (perpro_approval_step_delegated_approver.id). Chỉ trả về khi thực hiện action delegate_approver.
revokedApprovalStepId Integer | conditional ID của bước phê duyệt bị thu hồi ủy quyền. Chỉ trả về khi thực hiện action revoke_delegate_approver.
revokedDelegationId Integer | conditional ID của bản ghi ủy quyền bị thu hồi. Chỉ trả về khi thực hiện action revoke_delegate_approver.
revokedAddedStepId Integer | conditional ID của bước bổ sung vừa bị thu hồi (soft-delete). Chỉ trả về khi thực hiện action revoke_added_step.
POST /api/v1/private/proposal-approvals/modify-steps TranCode: PP_PROPOSAL_APPROVALS_MODIFY_STEPS — Chỉnh sửa và lưu danh sách các bước phê duyệt PerPro
Core
Lưu và cập nhật lại toàn bộ danh sách các bước phê duyệt của tờ trình khi ở trạng thái DRAFT hoặc LOCKED; luồng Rework có người đồng đề xuất cũng cho lưu quy trình để xác nhận lại. Hỗ trợ tạo mới, cập nhật, đổi thứ tự và xóa mềm (soft-delete) các bước cũ không nằm trong danh sách truyền lên. Người đồng đề xuất không được là Checker (APPROVE), kể cả Checker được ủy quyền; trùng email trả lỗi 400. Khi đang CO_REQUESTERS_PENDING, thay đổi bị chặn với 409.
Lưu ý Route: Route chính thức và duy nhất đang hoạt động là POST /api/v1/private/proposal-approvals/modify-steps (alias route cũ /approval-steps/create hiện đã được vô hiệu hóa / comment out trên backend).

Quy tắc kiểm tra & Ràng buộc nghiệp vụ

1. Quyền IAM & Người thực hiện (Actor Restriction)
  • Quyền IAM: Bắt buộc có quyền PERMISSION:PROPOSAL:CREATE.
  • Người thực hiện: CHỈ DUY NHẤT NGƯỜI TẠO TỜ TRÌNH (createdBy) mới được phép chỉnh sửa danh sách bước phê duyệt.
2. Ràng buộc danh sách bước (Steps Validation)
  • Trạng thái biên tập phải là DRAFT hoặc LOCKED; nếu tờ trình có người đồng đề xuất, có thể lưu quy trình ở REVISION_REQUIRED để yêu cầu xác nhận lại.
  • steps: Bắt buộc là một mảng JSON danh sách, có ít nhất 1 bước (count >= 1).
  • Bước cuối cùng: Bắt buộc phải có stepType = APPROVE.
  • Không trùng người xử lý: Mỗi email chỉ được xuất hiện một lần trong toàn bộ danh sách steps, không phân biệt stepType = APPROVE hay REVIEW. Email được so sánh sau khi bỏ khoảng trắng đầu/cuối và không phân biệt hoa thường.
  • Chống tự duyệt: Người tạo tờ trình (createdBy) KHÔNG ĐƯỢC gán chính mình vào bất kỳ bước nào có stepType = APPROVE. Vi phạm trả lỗi 01 ngay lập tức.
  • Cờ yêu cầu ký tờ trình (requiresProposalSign): Chỉ áp dụng cho bước có stepType = REVIEW (mặc định false). Khi được bật (true), người thẩm định ở bước này sẽ được hiển thị trên khối chữ ký tờ trình (bản in / bản trình ký PDF và DOCX) sau khi tờ trình hoàn tất phê duyệt. Với bước APPROVE, backend luôn tự động coi là false.
  • Thu hồi Token QR phê duyệt: Khi quy trình được cập nhật và lưu lại thành công, mọi Token QR phê duyệt đã cấp trước đó của tờ trình sẽ tự động bị thu hồi / mất hiệu lực để bảo đảm tính toàn vẹn (afterWorkflowChanged).
  • Hệ thống sẽ tự động đối soát quyền với IAM và lưu lại thông tin hồ sơ (tên, chức danh, phòng ban, công ty).
3. Đồng bộ quy trình mẫu theo mode
  • Khi Template có approval_flow_locked = 2 và approval_flow có dữ liệu:
    • Backend so sánh phần đuôi (suffix) của steps gửi lên với approval_flow. Nếu chưa khớp đủ số lượng, đúng thứ tự, stepType và assigneeEmail, backend sẽ tự động nối toàn bộ approval_flow vào cuối danh sách các bước.
    • Email được so sánh không phân biệt hoa thường; stepId, note và các thông tin snapshot không tham gia so sánh.
    • Không trả lỗi khi client thiếu hoặc thay đổi các bước cấu hình ở cuối; nếu suffix đã khớp đầy đủ thì backend không tạo thêm bước trùng.
    • Áp dụng cho cả tờ trình DRAFT và LOCKED.
    • Nếu approval_flow_locked = 0/1 hoặc approval_flow rỗng/null, backend không validate hoặc tự thêm bước từ template.
  • Kiểm soát thay đổi cấu hình đồng thời: Nếu cấu hình quy trình mặc định của mẫu bị thay đổi trong lúc thao tác, hệ thống trả về lỗi xung đột 409 Conflict ("Cấu hình quy trình phê duyệt mặc định của mẫu đã thay đổi. Vui lòng tải lại dữ liệu.").
4. Quy tắc chỉnh sửa khi Tờ trình bị Khóa (proposalStatus = LOCKED)
  • Bảo toàn bước đã hoàn tất (PROCESSED): Mọi bước đã hoàn thành xử lý trước đó bắt buộc phải được giữ nguyên vẹn ở phần đầu danh sách steps (không được sửa đổi nội dung, không đổi thứ tự, không xóa). Vi phạm sẽ bị từ chối với lỗi 400 Bad Request.
  • Tự động kích hoạt luồng tiếp theo: Bước đầu tiên ngay sau các bước đã xử lý sẽ tự động chuyển sang trạng thái PROCESSING; các bước còn lại phía sau chuyển sang LOCKED.
  • Tự động mở khóa trạng thái tờ trình:
    • Nếu bước PROCESSING tiếp theo là APPROVE: Tờ trình tự động chuyển sang UNDER_APPROVAL.
    • Nếu bước PROCESSING tiếp theo là REVIEW: Tờ trình tự động chuyển sang PENDING_REVIEW.
  • Thông báo thu hồi nhiệm vụ: Nếu nhân sự đang phụ trách bước PROCESSING trước đó bị thay đổi hoặc xóa bỏ khỏi quy trình, backend tự động gửi thông báo thu hồi nhiệm vụ tới nhân sự đó (notifyMakerChange).
  • Ghi nhận lịch sử quy trình (maker_change): Hệ thống ghi nhận các bản ghi lịch sử với action maker_change kèm chi tiết thay đổi vị trí trong actionContext.workflowChange (kind: added, removed, reordered; beforePosition, afterPosition).
  • Thống kê trong phản hồi: Trả về trường modifiedStepCount (tổng số bước hiệu lực) và deletedStepCount (số bước bị xóa mềm).

Mô tả Payload (Request)

Field Type Description
proposalId Integer Required ID của tờ trình.
steps Array<Object> Required Danh sách các bước phê duyệt mới theo đúng thứ tự linked-list. Phải có ít nhất 1 bước.
steps[].stepId Integer | null Optional ID của bước hiện có nếu tái sử dụng/cập nhật bước cũ; để null nếu là bước mới tạo thêm.
steps[].stepType String Required Loại bước: APPROVE (Phê duyệt) hoặc REVIEW (Thẩm định). Bước cuối cùng bắt buộc là APPROVE.
steps[].assignee Object Required Đối tượng chứa thông tin định danh người xử lý: { "email": "..." }.
steps[].note String | null Optional Ghi chú hoặc ý kiến ban đầu cho bước (tối đa 65.535 ký tự).
steps[].approvalFlowLocked Integer Optional Chế độ snapshot khóa quy trình kế thừa từ mẫu (mặc định 0 nếu không truyền; 0: Tự do, 1: Khóa người tạo, 2: Đồng bộ mẫu, 3: Bổ sung đuôi).
steps[].requiresProposalSign Boolean Optional Cờ yêu cầu ký tờ trình (mặc định false). Chỉ áp dụng khi stepType = REVIEW: khi được truyền true, người thẩm định của bước này sẽ được hiển thị trong danh sách người ký duyệt trên khối chữ ký in và bản trình ký (PDF/DOCX) sau khi tờ trình được duyệt. Với bước APPROVE, backend luôn tự động gán là false.

Request Sample

JSON - Request Body
{
    "proposalId": 20,
    "steps": [
        {
            "stepId": 113,
            "stepType": "REVIEW",
            "assignee": {
                "email": "reviewer@example.com"
            },
            "note": "Thẩm định hồ sơ ban đầu",
            "approvalFlowLocked": 0,
            "requiresProposalSign": true
        },
        {
            "stepId": null,
            "stepType": "APPROVE",
            "assignee": {
                "email": "approver@example.com"
            },
            "note": "Phê duyệt tờ trình",
            "approvalFlowLocked": 2
        }
    ]
}

Response Sample

JSON - Response 200 OK
{
    "success": true,
    "code": "200",
    "message": "Lưu danh sách quy trình phê duyệt thành công.",
    "data": {
        "proposalId": 20,
        "modifiedStepCount": 2,
        "deletedStepCount": 0,
        "proposalStatus": "DRAFT",
        "workflowCompleted": false,
        "requesterPermissions": [
            { "permission": "PROPOSAL.CREATE", "granted": true },
            { "permission": "PROPOSAL.APPROVE", "granted": true },
            { "permission": "PROPOSAL.REVIEW", "granted": false }
        ],
        "steps": [
            {
                "stepId": 113,
                "proposalId": 20,
                "nextStepId": 114,
                "prevStepId": null,
                "parentStepId": null,
                "stepType": "REVIEW",
                "assignedByEmail": null,
                "approvalFlowLocked": 0,
                "requiresProposalSign": true,
                "status": "DRAFT",
                "result": null,
                "note": "Thẩm định hồ sơ ban đầu",
                "assignerNote": null,
                "assignee": {
                    "email": "reviewer@example.com",
                    "name": "Đặng Thị Thảo",
                    "title": "Chuyên viên Thẩm định Rủi ro",
                    "department": "Ban Thẩm định",
                    "company": "Công ty ABC"
                },
                "delegatedApprover": null,
                "isCurrent": false,
                "isAssignedToRequester": false,
                "canRevokeRequest": false,
                "createdAt": "2026-09-15T08:00:00+07:00",
                "createdBy": "creator@example.com",
                "updatedAt": "2026-09-15T08:00:00+07:00",
                "updatedBy": "creator@example.com",
                "childSteps": []
            },
            {
                "stepId": 114,
                "proposalId": 20,
                "nextStepId": null,
                "prevStepId": 113,
                "parentStepId": null,
                "stepType": "APPROVE",
                "assignedByEmail": null,
                "approvalFlowLocked": 2,
                "requiresProposalSign": false,
                "status": "DRAFT",
                "result": null,
                "note": "Phê duyệt tờ trình",
                "assignerNote": null,
                "assignee": {
                    "email": "approver@example.com",
                    "name": "Trần Thị B",
                    "title": "Phó Giám đốc",
                    "department": "Khối Vận hành",
                    "company": "Công ty ABC"
                },
                "delegatedApprover": null,
                "isCurrent": false,
                "isAssignedToRequester": false,
                "canRevokeRequest": false,
                "createdAt": "2026-09-15T08:00:00+07:00",
                "createdBy": "creator@example.com",
                "updatedAt": "2026-09-15T08:00:00+07:00",
                "updatedBy": "creator@example.com",
                "childSteps": []
            }
        ],
        "currentSteps": []
    }
}

Mô tả Chi tiết Dữ liệu Phản hồi (Response Data Model)

Trường dữ liệu Kiểu dữ liệu Mô tả chi tiết & Ý nghĩa nghiệp vụ
proposalId Integer ID của tờ trình vừa được cập nhật quy trình.
modifiedStepCount Integer Tổng số bước phê duyệt còn hiệu lực trong quy trình sau khi lưu.
deletedStepCount Integer Số bước phê duyệt cũ đã bị xóa mềm (soft-delete) do không nằm trong danh sách gửi lên.
proposalStatus String Trạng thái của tờ trình sau khi cập nhật: giữ DRAFT nếu đang soạn thảo, hoặc tự động chuyển thành UNDER_APPROVAL / PENDING_REVIEW nếu sửa đổi từ LOCKED.
workflowCompleted Boolean Luôn là false do quy trình chưa hoàn thành.
requesterPermissions Array<Object> Danh sách phân quyền của actor đối với tờ trình (PROPOSAL.CREATE, PROPOSAL.APPROVE, PROPOSAL.REVIEW).
steps Array<Object> Danh sách đầy đủ các bước phê duyệt thuộc quy trình sau khi lưu (chi tiết cấu trúc xem bảng bên dưới).
currentSteps Array<Object> Danh sách các bước hiện đang ở trạng thái PROCESSING (nếu mở lại từ LOCKED) hoặc mảng rỗng [] (nếu tờ trình đang ở DRAFT).

Cấu trúc đối tượng bước phê duyệt trong data.steps[]

Trường dữ liệu Kiểu dữ liệu Mô tả chi tiết & Ý nghĩa nghiệp vụ
stepId Integer ID duy nhất của bước phê duyệt.
proposalId Integer ID của tờ trình sở hữu bước này.
nextStepId / prevStepId Integer | null ID bước kế tiếp / bước liền trước trong chuỗi liên kết (linked-list).
parentStepId Integer | null ID bước cha nếu là bước phát sinh từ review/checker; với bước root thông thường là null.
stepType String Loại bước: APPROVE (Phê duyệt) hoặc REVIEW (Thẩm định).
assignedByEmail String | null Email người chỉ định bước nếu là bước phát sinh (need_review/need_checker); bước thông thường là null.
approvalFlowLocked Integer Chế độ snapshot khóa quy trình kế thừa từ mẫu (0: Tự do, 1: Khóa người tạo, 2: Đồng bộ mẫu, 3: Bổ sung đuôi).
requiresProposalSign Boolean Cờ yêu cầu ký tờ trình: true nếu bước REVIEW được cấu hình hiển thị chữ ký trên bản in / bản trình ký; bước APPROVE luôn trả false.
status String Trạng thái bước: DRAFT (khi tờ trình là Draft), LOCKED (chưa đến lượt), PROCESSING (đang đến lượt xử lý), PROCESSED (đã xử lý xong).
result String | null Kết quả xử lý: APPROVED, REJECTED, REWORK, REVIEWED, REQUEST_DOCUMENTS hoặc null khi chưa hoàn thành.
note String | null Ý kiến/ghi chú của người xử lý bước (hoặc ghi chú ban đầu khi cấu hình bước).
assignerNote String | null Lý do giao việc / tham vấn của người chỉ định (nếu bước được tạo từ need_review / need_checker); bước thông thường là null.
assignee Object Thông tin nhân sự được giao bước gồm: email, name, title, department, company đối soát từ IAM.
delegatedApprover Object | null Thông tin người được ủy quyền đang có hiệu lực (nếu có); ngược lại là null.
isCurrent Boolean true nếu bước đang ở trạng thái PROCESSING.
isAssignedToRequester Boolean true nếu bước đang giao cho chính tài khoản đang gửi request (hoặc người được ủy quyền của tài khoản đó).
canRevokeRequest Boolean Cờ cho phép người chỉ định thu hồi bước phát sinh đã tạo.
childSteps Array<Object> Danh sách các bước con thẩm định/phê duyệt phát sinh đính kèm bước này (nếu có).
POST /api/v1/private/proposal-approvals/trigger-step-flow TranCode: PP_PROPOSAL_APPROVALS_TRIGGER_STEP_FLOW — Khởi chạy quy trình phê duyệt PerPro (submit, rework-submit, additional_documents_submit)
Core
Khởi chạy quy trình phê duyệt lần đầu (submit), gửi lại tờ trình sau khi đã chỉnh sửa (rework-submit), hoặc gửi thông báo đã bổ sung tài liệu/chứng từ (additional_documents_submit). Nếu có người đồng đề xuất, submit và rework-submit chỉ do Maker thực hiện sau khi mọi người phản hồi lượt hiện tại; REVISION_REQUIRED từ người đồng đề xuất phải được sửa và yêu cầu xác nhận lại. Người REJECTED được loại khỏi danh sách khi trình; kết quả có thể có notificationFailedCount. Bổ sung chứng từ giữ lượt xác nhận cũ.

Quy tắc kiểm tra theo từng Action

1. Action "submit" (Gửi duyệt lần đầu)
  • Trạng thái biên tập: DRAFT; khi có người đồng đề xuất đã phản hồi đầy đủ, trạng thái thực tế là PENDING_APPROVAL_SUBMISSION và coRequestersReturnStatus = DRAFT.
  • Payload: CẤM truyền field steps trong request.
  • Đồng bộ quy trình mẫu (approval_flow): Khi approval_flow_locked = 2 và approval_flow có dữ liệu, backend thay thế/đồng bộ toàn bộ root steps để giống template về số lượng, thứ tự, stepType và assigneeEmail.
  • Bổ sung quy trình mẫu (approval_flow_locked = 3): Backend giữ các root step hiện có và nối toàn bộ approval_flow vào cuối nếu suffix chưa khớp đầy đủ về số lượng, thứ tự, stepType và assigneeEmail.
  • Nếu không có approval_flow áp dụng thì phải có ít nhất một active step. Mọi bước hiện có phải đang ở DRAFT và chưa có kết quả; bước cuối cùng sau xử lý bắt buộc là APPROVE.
  • Mỗi email chỉ được xuất hiện một lần trong luồng sau khi đồng bộ/bổ sung bước, không phân biệt vai trò APPROVE hoặc REVIEW; phép so sánh bỏ khoảng trắng đầu/cuối và không phân biệt hoa thường.
  • Mở bước đầu tiên thành PROCESSING, các bước sau thành LOCKED; proposal chuyển thành UNDER_APPROVAL (hoặc PENDING_REVIEW nếu bước đầu là Review).
2. Action "rework-submit" (Gửi lại sau khi chỉnh sửa)
  • Trạng thái biên tập: REVISION_REQUIRED; khi đã xác nhận lại với người đồng đề xuất, trạng thái thực tế là PENDING_APPROVAL_SUBMISSION và coRequestersReturnStatus = REVISION_REQUIRED.
  • Payload: BẮT BUỘC truyền field steps chứa danh sách root steps mới/chỉnh sửa (không truyền childSteps).
  • Đồng bộ quy trình mẫu (approval_flow_locked = 2): Khi approval_flow có dữ liệu, backend bỏ qua định nghĩa step hợp lệ từ payload và thay thế/đồng bộ toàn bộ root steps theo approval_flow.
  • Bổ sung quy trình mẫu (approval_flow_locked = 3): Backend giữ danh sách step client gửi và nối toàn bộ approval_flow vào cuối nếu suffix chưa khớp đầy đủ.
  • Nếu approval_flow_locked = 0/1 hoặc approval_flow rỗng/null, backend không validate, đồng bộ hoặc bổ sung step từ template.
  • Bước cuối cùng của chuỗi hiệu lực bắt buộc phải là APPROVE.
  • Mỗi email chỉ được xuất hiện một lần trong danh sách root steps hiệu lực, không phân biệt vai trò APPROVE hoặc REVIEW; phép so sánh bỏ khoảng trắng đầu/cuối và không phân biệt hoa thường.
  • Ghi chú (note): Step dùng lại được clear note khi rework-submit, kể cả ID được đồng bộ theo template. Step mới giữ note theo định nghĩa mới. History theo dõi riêng assignerNote và note cùng các field bước khác. Xem contract API step-history.
  • Hệ thống tự động thu hồi (revoke) các ủy quyền cũ, cập nhật danh sách bước, reset kết quả và khởi chạy lại từ bước đầu tiên (PROCESSING).
3. Action "additional_documents_submit" (Gửi bổ sung chứng từ)
  • Trạng thái tờ trình: Phải là PENDING_DOCUMENTS.
  • Payload: CẤM truyền field steps trong request.
  • Tờ trình phải có đúng 1 bước APPROVE hoặc REVIEW đang ở PROCESSING với kết quả REQUEST_DOCUMENTS.
  • Reset kết quả bước đó về null (tiếp tục PROCESSING), chuyển tờ trình về UNDER_APPROVAL nếu là bước APPROVE hoặc PENDING_REVIEW nếu là bước REVIEW.
4. Cơ chế đồng bộ quy trình mẫu (approval_flow) & Kiểm soát đồng thời
  • Mode 0/1: Không đối chiếu hoặc đồng bộ root steps với approval_flow.
  • Mode 2: Khi approval_flow có dữ liệu, danh sách root steps sau xử lý luôn giống template về số lượng, thứ tự, stepType và assigneeEmail. Backend tái sử dụng ID theo vị trí, tạo thêm hoặc soft-delete phần chênh lệch.
  • Mode 3: Khi approval_flow có dữ liệu, backend giữ các root step hiện có/client gửi. Nếu toàn bộ flow mẫu chưa khớp suffix theo số lượng, thứ tự, stepType và assigneeEmail, backend nối toàn bộ flow mẫu vào cuối; suffix đã khớp thì không tạo thêm step.
  • Flow rỗng: Không validate, đồng bộ hoặc bổ sung flow, bất kể mode.
  • Snapshot mode: Khi submit hoặc rework-submit thành công, mode từ template được ghi xuống perpro_proposal.approval_flow_locked và mọi perpro_approval_step.approval_flow_locked còn hiệu lực.
  • Kiểm soát thay đổi cấu hình đồng thời: Nếu cấu hình template bị sửa đổi giữa lúc tra cứu IAM và lúc ghi DB, hệ thống trả về lỗi xung đột 409 Conflict ("Cấu hình quy trình phê duyệt mặc định của mẫu đã thay đổi. Vui lòng tải lại dữ liệu.").
5. Cơ chế Push Notification & Thông báo tự động theo từng Action

Khi khởi chạy hoặc kích hoạt luồng qua trigger-step-flow, backend tự động bắn thông báo tới người xử lý bước tiếp theo ngay sau khi transaction commit thành công:

Action Event Code Người nhận (Recipients) Điều kiện kích hoạt Điều hướng (navigate)
submit
(Gửi duyệt lần đầu)
CHECKER_TURN Checker của bước APPROVE đầu tiên (Assignee hoặc Delegated Approver hiệu lực) Bước đầu tiên mở ra là loại APPROVE approvals/{id}
REVIEWER_ADDED Reviewer của bước REVIEW đầu tiên Bước đầu tiên mở ra là loại REVIEW approvals/{id}
rework-submit
(Gửi lại sau chỉnh sửa)
CHECKER_TURN Checker của bước APPROVE đầu tiên Bước đầu tiên khởi động lại là loại APPROVE approvals/{id}
REVIEWER_ADDED Reviewer của bước REVIEW đầu tiên Bước đầu tiên khởi động lại là loại REVIEW approvals/{id}
additional_documents_submit
(Gửi bổ sung chứng từ)
SUPPLEMENT_COMPLETED Người đang phụ trách bước hiện tại (Assignee / Người nhận ủy quyền đang xử lý) Bước đang PROCESSING sau khi reset kết quả chứng từ approvals/{id}

Request Sample 1: Action submit (Gửi duyệt lần đầu)

JSON - Request Body
{
    "proposalId": 123,
    "action": "submit"
}

Request Sample 2: Action rework-submit (Gửi lại sau khi chỉnh sửa)

JSON - Request Body
{
    "proposalId": 123,
    "action": "rework-submit",
    "steps": [
        {
            "stepId": 1,
            "stepType": "APPROVE",
            "assignee": { "email": "approver1@example.com" },
            "note": "Phê duyệt bước 1"
        },
        {
            "stepId": null,
            "stepType": "APPROVE",
            "assignee": { "email": "approver2@example.com" },
            "note": "Phê duyệt bước 2"
        }
    ]
}

Request Sample 3: Action additional_documents_submit (Gửi bổ sung chứng từ)

JSON - Request Body
{
    "proposalId": 123,
    "action": "additional_documents_submit"
}

Response Sample 1: Action submit & additional_documents_submit

JSON - Response 200 OK
{
    "success": true,
    "code": "200",
    "message": "Khởi chạy quy trình phê duyệt thành công.",
    "data": {
        "proposalId": 123,
        "proposalStatus": "UNDER_APPROVAL",
        "workflowCompleted": false,
        "requesterPermissions": [
            { "permission": "PROPOSAL.CREATE", "granted": true },
            { "permission": "PROPOSAL.APPROVE", "granted": true },
            { "permission": "PROPOSAL.REVIEW", "granted": false }
        ],
        "steps": [ "...danh sách các steps sau khi khởi chạy..." ],
        "currentSteps": [ "...danh sách bước đang ở PROCESSING..." ]
    }
}

Response Sample 2: Action rework-submit (Gửi lại sau điều chỉnh)

JSON - Response 200 OK
{
    "success": true,
    "code": "200",
    "message": "Gửi lại tờ trình sau khi hoàn tất chỉnh sửa thành công.",
    "data": {
        "proposalId": 123,
        "modifiedStepCount": 2,
        "deletedStepCount": 0,
        "proposalStatus": "UNDER_APPROVAL",
        "workflowCompleted": false,
        "requesterPermissions": [
            { "permission": "PROPOSAL.CREATE", "granted": true },
            { "permission": "PROPOSAL.APPROVE", "granted": true },
            { "permission": "PROPOSAL.REVIEW", "granted": false }
        ],
        "steps": [ "...danh sách các steps mới..." ],
        "currentSteps": [ "...danh sách bước đầu tiên đang PROCESSING..." ]
    }
}

Mô tả Chi tiết Dữ liệu Phản hồi (Response Data Model)

Trường dữ liệu Kiểu dữ liệu Mô tả chi tiết & Ý nghĩa nghiệp vụ
proposalId Integer ID của tờ trình vừa được khởi chạy quy trình.
proposalStatus String Trạng thái tiến trình của tờ trình (UNDER_APPROVAL nếu bước đầu là Duyệt, hoặc PENDING_REVIEW nếu bước đầu là Thẩm định).
workflowCompleted Boolean Luôn là false do quy trình phê duyệt mới bắt đầu.
requesterPermissions Array<Object> Danh sách phân quyền của actor đối với tờ trình này.
steps Array<Object> Danh sách toàn bộ các bước phê duyệt thuộc quy trình sau khi khởi chạy.
currentSteps Array<Object> Danh sách các bước hiện đang ở trạng thái PROCESSING chờ xử lý.
modifiedStepCount Integer | conditional Tổng số bước phê duyệt còn hiệu lực trong quy trình. Chỉ xuất hiện khi action là rework-submit.
deletedStepCount Integer | conditional Số bước phê duyệt cũ đã bị xóa mềm (soft-delete). Chỉ xuất hiện khi action là rework-submit.
POST /api/v1/private/proposal-approvals/step-history TranCode: PP_PROPOSAL_APPROVALS_STEP_HISTORY — Lấy lịch sử các bước phê duyệt PerPro theo tờ trình
Core
Lấy toàn bộ lịch sử thay đổi của các bước phê duyệt thuộc tờ trình (proposalId), bao gồm cả các bước đã bị xóa mềm, sắp xếp theo thứ tự mới nhất trước (historyId giảm dần). Mỗi bản ghi phản ánh sự thay đổi trên 6 trường được theo dõi (stepType, assignedByEmail, assigneeEmail, status, result, note) kèm theo cặp snapshot trước/sau (snapshot và afterSnapshot), danh sách trường đổi (changedFields), người và thời điểm thao tác (createdBy, createdAt), cùng ngữ cảnh bổ sung trong actionContext (gồm targetPerson khi giao việc/ủy quyền và workflowChange khi người lập chỉnh sửa quy trình maker_change).

Quy tắc kiểm tra & Ràng buộc nghiệp vụ (Validation Rules)

1. Xác thực người gọi (Actor & Resource-Level Policy)
  • Bắt buộc truyền Access Token qua Header (Authorization: Bearer <token>).
  • Kiểm tra quyền truy cập: User đang đăng nhập phải có liên quan tới tờ trình mới được phép xem (dùng chung policy với xem workflow):
    • Người tạo tờ trình (Maker) hoặc Người đồng tạo (Co-maker).
    • Người được phân công xử lý (Assignee) tại bất kỳ bước nào (Duyệt hoặc Thẩm định).
    • Người được ủy quyền hợp lệ (Delegated User) của người phê duyệt.
    • Người thêm bước thẩm định / tư vấn vào quy trình theo policy hiện hành.
  • Nếu user không có quyền liên quan tới tờ trình, hệ thống từ chối với mã lỗi 403 Forbidden (envelope code: "403").
2. Kiểm tra tham số & Tồn tại tờ trình
  • proposalId: Bắt buộc phải là số nguyên dương (>= 1). Thiếu hoặc sai định dạng trả lỗi 400 Bad Request.
  • Tồn tại tờ trình: Tờ trình phải tồn tại trong cơ sở dữ liệu. Nếu không tìm thấy, trả về mã lỗi 404 Not Found.
3. Quy tắc Snapshot & Ghi nhận lịch sử (History Audit Rules)
  • Phạm vi theo dõi: Chỉ so sánh và lưu đúng 6 trường (tên camelCase): stepType, assignedByEmail, assigneeEmail, status, result, note.
  • Cặp snapshot trước / sau: Mỗi lần cập nhật bước sẽ ghi đúng một cặp snapshot snapshot (trạng thái trước thay đổi) và afterSnapshot (trạng thái sau thay đổi). Bản ghi cũ chưa có snapshot sau sẽ trả null.
  • Chỉ ghi khi có thay đổi thực sự: Nếu chỉ thay đổi updatedAt, updatedBy, truyền lại dữ liệu cũ hoặc xóa mềm đơn thuần thì không tạo bản ghi history mới.
  • Ngữ cảnh hành động (actionContext):
    • Đối với các thao tác liên quan nhân sự khác (need_review, need_checker, delegate_approver, revoke_delegate_approver, revoke_added_step): Trả về actionContext.targetPerson (gồm email, name, title, department, company).
    • Đối với thao tác người tạo chỉnh sửa quy trình (maker_change): Trả về actionContext.workflowChange (gồm kind: added | removed | reordered, beforePosition, afterPosition).
  • Định dạng thời gian: Thời gian lưu trữ trong DB theo chuẩn UTC; API tự động chuyển đổi và hiển thị theo múi giờ Việt Nam UTC+7 (định dạng yyyy-MM-dd HH:mm:ss).
  • Không phân trang: API trả toàn bộ danh sách trong mảng items để client (ApprovalWorkflowPanel) cache một lần và map theo từng step. Nếu chưa có lịch sử, trả items: [].

Mô tả Payload (Request)

Field Type Description
proposalId Integer Required ID của tờ trình cần lấy lịch sử các bước phê duyệt. Bắt buộc là số nguyên dương (>= 1).

Request Sample

JSON - Request Body
{
    "proposalId": 1001
}

Response Sample (Thành công 200 OK)

JSON - Response 200 OK
{
    "success": true,
    "code": "200",
    "message": "Lấy lịch sử các bước phê duyệt thành công.",
    "data": {
        "proposalId": 1001,
        "items": [
            {
                "historyId": 3,
                "stepId": 15,
                "action": "maker_change",
                "changedFields": [
                    "status"
                ],
                "actionContext": {
                    "workflowChange": {
                        "kind": "reordered",
                        "beforePosition": 2,
                        "afterPosition": 1
                    }
                },
                "createdAt": "2026-09-07 08:30:00",
                "createdBy": "maker@example.com",
                "snapshot": {
                    "stepType": "APPROVE",
                    "assignedByEmail": null,
                    "assigneeEmail": "approver@example.com",
                    "status": "LOCKED",
                    "result": null,
                    "note": null
                },
                "afterSnapshot": {
                    "stepType": "APPROVE",
                    "assignedByEmail": null,
                    "assigneeEmail": "approver@example.com",
                    "status": "PROCESSING",
                    "result": null,
                    "note": null
                }
            },
            {
                "historyId": 2,
                "stepId": 12,
                "action": "need_review",
                "changedFields": [],
                "actionContext": {
                    "targetPerson": {
                        "email": "reviewer2@example.com",
                        "name": "Nguyễn Văn Hùng",
                        "title": "Trưởng nhóm Pháp chế",
                        "department": "Phòng Pháp chế",
                        "company": "Công ty ABC"
                    }
                },
                "createdAt": "2026-09-07 08:15:00",
                "createdBy": "reviewer@example.com",
                "snapshot": {
                    "stepType": "REVIEW",
                    "assignedByEmail": "reviewer@example.com",
                    "assigneeEmail": "reviewer2@example.com",
                    "status": "PROCESSING",
                    "result": null,
                    "note": null
                },
                "afterSnapshot": {
                    "stepType": "REVIEW",
                    "assignedByEmail": "reviewer@example.com",
                    "assigneeEmail": "reviewer2@example.com",
                    "status": "PROCESSING",
                    "result": null,
                    "note": null
                }
            },
            {
                "historyId": 1,
                "stepId": 11,
                "action": "rework-submit",
                "changedFields": [
                    "result",
                    "note"
                ],
                "actionContext": null,
                "createdAt": "2026-09-07 08:00:00",
                "createdBy": "maker@example.com",
                "snapshot": {
                    "stepType": "REVIEW",
                    "assignedByEmail": null,
                    "assigneeEmail": "reviewer@example.com",
                    "status": "PROCESSING",
                    "result": "REWORK",
                    "note": "Điều chỉnh hồ sơ"
                },
                "afterSnapshot": {
                    "stepType": "REVIEW",
                    "assignedByEmail": null,
                    "assigneeEmail": "reviewer@example.com",
                    "status": "PROCESSING",
                    "result": null,
                    "note": null
                }
            }
        ]
    }
}

Chi tiết Cấu trúc Dữ liệu Phản hồi (Response Data Model)

Trường dữ liệu Kiểu dữ liệu Mô tả chi tiết
proposalId Integer ID của tờ trình được truy vấn lịch sử.
items Array<Object> Danh sách các bản ghi lịch sử, sắp xếp theo thứ tự mới nhất trước (historyId giảm dần).
items[].historyId Integer ID duy nhất của bản ghi lịch sử trong cơ sở dữ liệu (khóa chính perpro_approval_step_history.id).
items[].stepId Integer ID của bước phê duyệt tương ứng (perpro_approval_step.id).
items[].action String Mã thao tác nghiệp vụ tạo ra bản ghi lịch sử (tham khảo bảng Master Data Action bên dưới).
items[].changedFields Array<String> Danh sách tên các trường thực sự có sự thay đổi giữa trước và sau thao tác (thuộc 6 trường được theo dõi).
items[].actionContext Object | null Ngữ cảnh mở rộng của thao tác: chứa targetPerson khi thao tác với nhân sự liên quan (thêm reviewer, checker, ủy quyền, thu hồi) hoặc chứa workflowChange khi người lập chỉnh sửa quy trình (maker_change). Trả null nếu không có ngữ cảnh mở rộng.
items[].createdAt String Thời điểm thao tác được thực hiện, định dạng chuẩn yyyy-MM-dd HH:mm:ss theo múi giờ Việt Nam (UTC+7).
items[].createdBy String Email của người thực hiện thao tác tạo ra bản ghi lịch sử.
items[].snapshot Object Snapshot 6 trường nghiệp vụ của bước trước khi thay đổi: stepType, assignedByEmail, assigneeEmail, status, result, note.
items[].afterSnapshot Object | null Snapshot 6 trường nghiệp vụ của bước sau khi thay đổi hoàn tất. Trả null nếu là bản ghi lịch sử cũ trước khi hỗ trợ lưu sau thay đổi.

1. Master Data: Danh mục Thao tác Nghiệp vụ (items[].action)

Bảng quy chuẩn ánh xạ từ mã action trả về từ API sang nhãn hiển thị Tiếng Việt trên giao diện Web/Mobile (căn cứ theo ACTION_LABELS của frontend):

Mã Action (action) Nhãn hiển thị UI (Tiếng Việt) Mô tả chi tiết & Ý nghĩa nghiệp vụ
approve Phê duyệt Người có thẩm quyền duyệt đồng ý thông qua bước hiện tại.
reject Từ chối Người phê duyệt từ chối tờ trình; quy trình phê duyệt kết thúc.
need_rework Yêu cầu điều chỉnh Người duyệt hoặc thẩm định yêu cầu người tạo chỉnh sửa lại nội dung tờ trình.
reviewed Hoàn tất review Thẩm định viên hoàn thành việc cho ý kiến đánh giá tại bước Review.
documents_request Yêu cầu bổ sung chứng từ Yêu cầu người lập bổ sung thêm các hồ sơ chứng từ còn thiếu.
need_review Yêu cầu thêm review Người phụ trách bước yêu cầu thêm thẩm định viên mới vào quy trình.
need_checker Yêu cầu thêm người phê duyệt Người phụ trách bước yêu cầu thêm cấp phê duyệt mới vào quy trình.
delegate_approver Ủy quyền Người phê duyệt ủy quyền xử lý bước hiện tại cho nhân sự khác.
revoke_delegate_approver Thu hồi ủy quyền Người phê duyệt thu hồi quyền xử lý đã ủy quyền trước đó.
revoke_added_step Thu hồi yêu cầu Thu hồi bước review hoặc checker đã gửi yêu cầu thêm trước khi bước được duyệt.
submit Gửi phê duyệt Người tạo gửi tờ trình lần đầu từ trạng thái bản nháp (DRAFT).
maker_change Người lập sửa quy trình Người tạo chỉnh sửa danh sách, thêm/bớt hoặc đổi thứ tự các bước phê duyệt khi tờ trình bị khóa.
rework-submit Gửi lại sau điều chỉnh Người tạo gửi lại tờ trình sau khi đã hoàn thành các nội dung điều chỉnh.
additional_documents_submit Gửi bổ sung chứng từ Người tạo gửi thông báo xác nhận đã nộp đầy đủ chứng từ được yêu cầu.

2. Master Data: Danh mục Trường theo dõi (changedFields & Snapshot Keys)

Hệ thống chỉ so sánh và ghi nhận lịch sử khi có sự thay đổi trên đúng 6 trường này (căn cứ theo FIELD_LABELS của frontend):

Tên trường (Field Key) Nhãn hiển thị UI (Tiếng Việt) Kiểu dữ liệu Mô tả chi tiết
stepType Loại bước String Phân loại nghiệp vụ của bước: APPROVE (Phê duyệt) hoặc REVIEW (Review/Thẩm định).
assignedByEmail Người thêm bước String | null Email của nhân sự đã tạo ra bước bổ sung này (nếu là bước do review/checker thêm vào).
assigneeEmail Người được phân công String | null Email của nhân sự trực tiếp chịu trách nhiệm xử lý bước phê duyệt hoặc review.
status Trạng thái String Trạng thái tiến trình của bước (DRAFT, LOCKED, PROCESSING, PROCESSED).
result Kết quả String | null Kết quả thực hiện thao tác (APPROVED, REJECTED, REWORK, REVIEWED, REQUEST_DOCUMENTS).
note Ghi chú String | null Nội dung ý kiến chỉ đạo, lý do từ chối/điều chỉnh hoặc thông tin trao đổi chuyên môn.

3. Master Data: Giá trị hiển thị trong Snapshot (snapshot & afterSnapshot)

Quy chuẩn nhãn Tiếng Việt và phong cách màu sắc Badge cho các giá trị trường trạng thái, kết quả và loại bước (căn cứ theo STATUS_LABELS và RESULT_LABELS):

Trường (Field) Giá trị (Code) Nhãn hiển thị UI (Tiếng Việt) Màu sắc & Ý nghĩa nghiệp vụ
stepType
(Loại bước)
APPROVE Phê duyệt Bước chính thức có quyền quyết định thông qua hoặc từ chối tờ trình.
REVIEW Review Bước thẩm định, tư vấn chuyên môn; không có quyền phê duyệt cuối cùng.
status
(Trạng thái)
DRAFT Bản nháp Bước mới được tạo lập, chưa bắt đầu quy trình.
LOCKED Chưa đến lượt Bước đang bị khóa, chờ các bước đứng trước hoàn thành xử lý.
PROCESSING Đang xử lý Bước đang mở tới lượt người được phân công vào thao tác.
PROCESSED Đã xử lý Bước đã hoàn thành xử lý xong và chuyển tiếp quy trình.
result
(Kết quả)
APPROVED Đồng ý Đồng ý phê duyệt nội dung bước.
REJECTED Từ chối Bác bỏ, không chấp thuận tờ trình.
REWORK Yêu cầu điều chỉnh Yêu cầu sửa đổi hồ sơ, tờ trình quay về trạng thái chỉnh sửa.
REVIEWED Hoàn tất review Đã cho ý kiến thẩm định chuyên môn.
REQUEST_DOCUMENTS Yêu cầu bổ sung chứng từ Yêu cầu bổ sung thêm tài liệu/chứng từ minh chứng.
null (hoặc rỗng) Không có / Chưa có Bước chưa có kết quả xử lý (hoặc bị xóa trắng sau rework).

4. Master Data: Ngữ cảnh Hành động (actionContext)

Đối với các hành động tác động tới người khác (thêm reviewer, thêm checker, ủy quyền), object actionContext.targetPerson cung cấp thông tin nhân sự liên quan để hiển thị thẻ thông tin (Card) trên UI:

Mã Action (action) Tiêu đề hiển thị Thẻ (Tiếng Việt) Mục đích & Ý nghĩa hiển thị
need_review Người được thêm để review Hiển thị thông tin thẩm định viên vừa được thêm vào quy trình.
need_checker Người được thêm để phê duyệt Hiển thị thông tin người phê duyệt vừa được bổ sung vào quy trình.
delegate_approver Người được ủy quyền Hiển thị thông tin người nhận ủy quyền thay thế cho người duyệt chính.
revoke_delegate_approver Người bị thu hồi ủy quyền Hiển thị thông tin người vừa bị hủy bỏ quyền xử lý ủy quyền.
revoke_added_step Người thuộc yêu cầu bị thu hồi Hiển thị thông tin người trong bước vừa bị thu hồi yêu cầu.
Các action khác Người liên quan Tiêu đề mặc định dự phòng khi có dữ liệu người liên quan.

Cấu trúc chi tiết của object targetPerson:

Trường (Field) Kiểu dữ liệu Bắt buộc Mô tả & Hướng dẫn hiển thị trên UI
email String Required Email định danh của nhân sự liên quan. Hiển thị dạng chữ màu tím/xanh nổi bật kèm tên.
name String | null Optional Họ và tên đầy đủ của nhân sự. UI ưu tiên in đậm (font-semibold). Nếu không có tên, hiển thị email làm tên chính.
title String | null Optional Chức danh / vị trí công tác của nhân sự (ví dụ: Chuyên viên Thẩm định).
department String | null Optional Phòng ban / bộ phận công tác (ví dụ: Ban Thẩm định Rủi ro).
company String | null Optional Tên công ty / đơn vị trực thuộc (ví dụ: Công ty ABC).
Quy tắc hiển thị thông tin tổ chức của Người liên quan
Mobile/Frontend nối các trường tổ chức có dữ liệu theo định dạng: [title, department, company].filter(Boolean).join(' · ').
Ví dụ: Chuyên viên Thẩm định · Ban Thẩm định Rủi ro · Công ty ABC.

Cấu trúc chi tiết của object actionContext.workflowChange (Action maker_change):

Trường (Field) Kiểu dữ liệu Bắt buộc Mô tả & Ý nghĩa nghiệp vụ
kind String Required Loại thay đổi quy trình: added (Thêm bước mới), removed (Xóa bước cũ), reordered (Thay đổi thứ tự bước).
beforePosition Integer | null Optional Vị trí thứ tự của bước trong quy trình trước khi người lập thay đổi (1-indexed). Trả về null nếu là bước mới thêm (kind = added).
afterPosition Integer | null Optional Vị trí thứ tự của bước trong quy trình sau khi thay đổi (1-indexed). Trả về null nếu là bước bị xóa bỏ (kind = removed).
POST /api/v1/private/my-proposals/activities TranCode: PP_MY_PROPOSALS_ACTIVITIES — Nhật ký hoạt động tờ trình của tôi, có phân trang
Core
Đây là API của tab Nhật ký hoạt động trong trang xem/chỉnh sửa Tờ trình của tôi trên web. API tổng hợp các sự kiện audit liên quan đến tờ trình, bước phê duyệt, bình luận và ủy quyền theo cấu hình PerPro. API này khác proposal-approvals/step-history: nhật ký hoạt động trả timeline có phân trang; step-history trả lịch sử snapshot của riêng các bước phê duyệt. Nhật ký hiện có thể gồm sự kiện yêu cầu/xử lý xác nhận đồng đề xuất và thay đổi trạng thái tương ứng.

Request & quyền truy cập

TrườngKiểuQuy tắc
proposalIdIntegerBắt buộc, số nguyên dương. Actor phải có quyền xem tờ trình; Maker hoặc người đồng đề xuất còn hiệu lực có thể truy cập theo kiểm tra quyền hiện tại.
currentPageIntegerTùy chọn, số nguyên dương; mặc định 1. Server cố định perPage = 20, không nhận tham số perPage.

PerPro yêu cầu access token hợp lệ qua Authorization: Bearer <token>. MBGW cấu hình authentication_require = 1, user_auth = USER_AUTH, integration_system = PER_PRO, method POST và timeout 30 giây. Gateway forward token người dùng; systemId PERPROAPP được migration thêm vào request upstream, mobile không cần gửi trường này. Cần dùng các header ứng dụng/thiết bị mà MBGW hiện yêu cầu cho transaction private.

Request trực tiếp tới PerPro

JSON - Request Body
{
    "proposalId": 1001,
    "currentPage": 1
}

Request từ mobile qua MBGW

Gửi POST /api/pri/transaction-create với moduleCode hiện được cấp cho ứng dụng và cùng access token người dùng. Gateway lấy payload, bỏ moduleCode/tranCode, rồi chuyển tiếp sang API PerPro ở trên.

JSON - MBGW Request Body
{
    "moduleCode": "MODULE_CODE_CUA_UNG_DUNG",
    "tranCode": "PP_MY_PROPOSALS_ACTIVITIES",
    "payload": {
        "proposalId": 1001,
        "currentPage": 1
    }
}

Response thành công

PerPro trả HTTP 200 với envelope errorCode/errorDesc/errorMessage/traceId/data. Khi đi qua transaction-create trên MBGW, gateway lấy phần data của upstream làm dữ liệu phản hồi giao dịch; client đọc danh sách hoạt động và phân trang trong phần dữ liệu của envelope MBGW.

JSON - Response 200 OK
{
    "errorCode": "00",
    "errorDesc": "Lấy nhật ký hoạt động của tờ trình thành công.",
    "errorMessage": "Lấy nhật ký hoạt động của tờ trình thành công.",
    "traceId": null,
    "data": {
        "items": [
            {
                "auditLogId": 508,
                "proposalId": 1001,
                "occurredAt": "2026-09-14T11:45:00+07:00",
                "actorUsername": "director@example.com",
                "actorType": "USER",
                "activity": "Trạng thái tờ trình chuyển từ Đang phê duyệt sang Đã phê duyệt.",
                "description": "Xử lý phê duyệt tờ trình",
                "fields": {
                    "previousStatus": "Đang phê duyệt",
                    "currentStatus": "Đã phê duyệt"
                },
                "changedFields": [
                    {
                        "field": "status",
                        "label": "Trạng thái",
                        "before": "Đang phê duyệt",
                        "after": "Đã phê duyệt"
                    }
                ],
                "source": "HTTP",
                "sourceName": "ProposalApprovalController::performAction",
                "action": "UPDATE",
                "entityName": "perpro_proposal",
                "entityLabel": "Tờ trình"
            },
            {
                "auditLogId": 507,
                "proposalId": 1001,
                "occurredAt": "2026-09-14T11:45:00+07:00",
                "actorUsername": "director@example.com",
                "actorType": "USER",
                "activity": "Đã cập nhật bước của Giám đốc phê duyệt sang Đã xử lý.",
                "description": "Xử lý bước phê duyệt",
                "fields": {
                    "assigneeName": "Giám đốc phê duyệt",
                    "currentStatus": "Đã xử lý"
                },
                "changedFields": [
                    {
                        "field": "status",
                        "label": "Trạng thái",
                        "before": "Đang xử lý",
                        "after": "Đã xử lý"
                    },
                    {
                        "field": "result",
                        "label": "Kết quả",
                        "before": null,
                        "after": "Đã phê duyệt"
                    }
                ],
                "source": "HTTP",
                "sourceName": "ProposalApprovalController::performAction",
                "action": "UPDATE",
                "entityName": "perpro_approval_step",
                "entityLabel": "Bước phê duyệt"
            },
            {
                "auditLogId": 505,
                "proposalId": 1001,
                "occurredAt": "2026-09-14T11:40:00+07:00",
                "actorUsername": "Hệ thống",
                "actorType": "SYSTEM",
                "activity": "Bước của Giám đốc phê duyệt chuyển sang Đang xử lý.",
                "description": "Khởi chạy bước phê duyệt",
                "fields": {
                    "assigneeName": "Giám đốc phê duyệt",
                    "currentStatus": "Đang xử lý"
                },
                "changedFields": [
                    {
                        "field": "status",
                        "label": "Trạng thái",
                        "before": "Chưa đến lượt",
                        "after": "Đang xử lý"
                    }
                ],
                "source": "HTTP",
                "sourceName": "ProposalApprovalController::triggerStepFlow",
                "action": "UPDATE",
                "entityName": "perpro_approval_step",
                "entityLabel": "Bước phê duyệt"
            },
            {
                "auditLogId": 502,
                "proposalId": 1001,
                "occurredAt": "2026-09-14T11:30:00+07:00",
                "actorUsername": "maker@example.com",
                "actorType": "USER",
                "activity": "Đã tạo tờ trình “Mua sắm thiết bị”.",
                "description": "Tạo tờ trình",
                "fields": {
                    "proposalTitle": "Mua sắm thiết bị"
                },
                "changedFields": [],
                "source": "HTTP",
                "sourceName": "MyProposalController::create",
                "action": "CREATE",
                "entityName": "perpro_proposal",
                "entityLabel": "Tờ trình"
            }
        ],
        "pagination": {
            "currentPage": 1,
            "perPage": 20,
            "totalItems": 4,
            "totalPages": 1,
            "hasNext": false,
            "hasPrev": false
        }
    }
}

Cơ chế cấu hình động từ bảng sys_common_code (code = 'MY_PROPOSAL_DETAIL')

Dữ liệu nhật ký hoạt động được điều khiển tập trung bởi cấu hình lưu tại bảng sys_common_code với group_code = 'PROPOSAL_ACTIVITY' và code = 'MY_PROPOSAL_DETAIL' (được khởi tạo bởi migration Version20260910001047.php). Backend (lớp ProposalActivityConfigProvider và ProposalActivityFormatter) đọc chuỗi JSON trong cột value của bản ghi này để định dạng dữ liệu trả về cho client:

Phần cấu hình JSONVai trò và ánh xạ sang dữ liệu trả về
entities[] Khai báo danh mục các thực thể liên quan đến tờ trình cần audit (perpro_proposal, perpro_approval_step, perpro_approval_step_delegated_approver, perpro_proposal_comment, perpro_proposal_comment_mention, perpro_delegation_recipient), kèm label tiếng Việt thân thiện. Giá trị label này được backend gán trực tiếp vào field entityLabel trong từng item trả về cho client.
activities[].textLabel Template câu diễn giải hoạt động (ví dụ: Đã tạo tờ trình “{$proposalTitle}”., Trạng thái tờ trình chuyển từ {$previousStatus} sang {$currentStatus}., Đã thêm bước {$stepType} cho {$assigneeName}.). Backend trích xuất các giá trị từ dữ liệu audit điền vào template để sinh ra field activity cho client hiển thị trực tiếp.
activities[].fields Quy định đường dẫn lấy dữ liệu từ dataBefore/dataAfter trong audit log và từ điển valueLabels để dịch các mã trạng thái, loại bước sang nhãn tiếng Việt (ví dụ: APPROVED → "Đã phê duyệt", PROCESSING → "Đang xử lý", APPROVE → "phê duyệt"). Các giá trị đã dịch được điền vào template textLabel và trả về trong object fields.
activities[].changedFields Quy định các trường cần theo dõi biến động (đường dẫn, label tiếng Việt, từ điển valueLabels). Backend so sánh dữ liệu trước và sau, nếu có biến động sẽ tự động trích xuất thành mảng changedFields gồm {field, label, before, after} đã được dịch sẵn nhãn trường và giá trị.
activities[].description Tên nghiệp vụ chuẩn của hành động (ví dụ: "Tạo tờ trình", "Chỉnh sửa nội dung tờ trình", "Xử lý phê duyệt tờ trình", "Khởi chạy bước phê duyệt", "Thêm bình luận", "Thêm người nhận ủy quyền"...), trả về tại field description.

Ý nghĩa với Client: Toàn bộ câu diễn giải, nhãn trường và giá trị trạng thái đã được backend chuẩn hóa và chuyển đổi sang tiếng Việt dựa theo cấu hình MY_PROPOSAL_DETAIL. Client không cần tự viết logic ghép chuỗi hay duy trì từ điển dịch mã trạng thái, chỉ cần hiển thị theo đúng các field được cung cấp.

Cách hiển thị dữ liệu lên giao diện theo từng Field (Field-to-UI Mapping)

Field trong ResponseVị trí hiển thị trên ClientQuy tắc bóc tách & hiển thị dữ liệu
items[].activity Tiêu đề chính của hoạt động Câu diễn giải đầy đủ về hành động. Client áp dụng quy tắc highlight văn bản:
• Chuỗi nằm trong dấu ngoặc kép “...” (như tên tờ trình): hiển thị in đậm.
• Các từ khóa trạng thái (Đã phê duyệt, Đã xử lý, Đang phê duyệt, Đang xử lý, Chờ đánh giá, Chờ điều chỉnh, Chờ bổ sung chứng từ, Chưa đến lượt, Đã từ chối, Nháp, Đang chỉnh sửa): hiển thị thành các tag/chip trạng thái riêng biệt.
items[].description Mô tả phụ bên dưới tiêu đề Chỉ hiển thị khi description có giá trị và khác với activity (khi description === activity thì không hiển thị dòng phụ này để tránh lặp nội dung).
items[].entityLabel Tag định danh đối tượng Hiển thị thành tag/badge phân loại đối tượng chịu tác động (ví dụ: "Tờ trình", "Bước phê duyệt", "Bình luận", "Ủy quyền phê duyệt"...), lấy trực tiếp từ cấu hình entities của MY_PROPOSAL_DETAIL.
items[].entityName, action, activity Nhãn loại thao tác & Bộ lọc Tab 1. Nhãn loại thao tác (Badge text & icon):
• perpro_approval_step: action === 'CREATE' → "Thêm bước"; action === 'DELETE' → "Xóa bước"; activity chứa "Đã xử lý" → "Đã xử lý"; activity chứa "Đang xử lý" → "Đang xử lý"; trường hợp khác → "Cập nhật bước".
• perpro_proposal: action === 'CREATE' → "Tạo tờ trình"; action === 'DELETE' → "Xóa tờ trình"; activity chứa "Đã phê duyệt" → "Đã duyệt"; activity chứa "Đã từ chối" → "Từ chối"; trường hợp khác → "Đổi trạng thái".
• perpro_proposal_comment: "Bình luận".
• perpro_proposal_comment_mention: "Nhắc tên".
• perpro_delegation_recipient: "Ủy quyền".
• Mặc định khác: action === 'CREATE' → "Tạo mới"; action === 'DELETE' → "Xóa"; khác → "Cập nhật".
2. Bộ lọc tab ở Client (sử dụng entityName):
• Tab Tất cả: lấy mọi item.
• Tab Bước phê duyệt: entityName === 'perpro_approval_step'.
• Tab Tờ trình: entityName === 'perpro_proposal'.
• Tab Bình luận: entityName.includes('comment').
• Tab Ủy quyền: entityName.includes('delegation').
(Lưu ý: Tab bộ lọc chỉ xuất hiện khi số lượng item thuộc tab đó trong trang hiện tại > 0; nếu toàn bộ trang chỉ có 1 danh mục khả dụng thì ẩn cả thanh tab lọc).
items[].changedFields[] Bảng "Chi tiết thay đổi" Chỉ hiển thị khi mảng có phần tử (changedFields.length > 0). Hiển thị nhãn đếm {changedFields.length} trường và bảng gồm 3 cột:
• Nội dung: Hiển thị changedFields[i].label (nhãn tiếng Việt đã dịch từ cấu hình, ví dụ: "Trạng thái", "Loại bước", "Kết quả", "Thời điểm xóa"...).
• Giá trị trước: Hiển thị changedFields[i].before, nếu null hiển thị chữ in nghiêng "Không có".
• Giá trị sau: Hiển thị changedFields[i].after, nếu null hiển thị chữ in nghiêng "Không có".
items[].actorUsername, actorType Thông tin người thực hiện • Nếu actorType === 'SYSTEM' hoặc actorType === 'SERVICE' hoặc actorUsername === 'Hệ thống': Hiển thị tên là "Hệ thống", kèm nhãn "Tự động", avatar biểu tượng máy/Robot.
• Nếu là người dùng thường: Hiển thị tên actorUsername, avatar hiển thị 2 chữ cái viết tắt (Initials trích xuất từ username/email).
items[].occurredAt Nhóm ngày & Thời gian thực hiện Chuỗi thời gian ISO 8601 theo UTC+7, được sử dụng ở 2 mức:
• Nhóm timeline theo ngày: Nhóm các item có cùng ngày YYYY-MM-DD. Hiển thị nhãn ngày: nếu trùng ngày hiện tại thì hiện "Hôm nay", ngày hôm trước hiện "Hôm qua", các ngày khác hiện định dạng DD/MM/YYYY.
• Thời gian trong từng thẻ hoạt động: Hiển thị giờ phút HH:mm (định dạng 24h) đi kèm thời gian tương đối (ví dụ: Vừa xong, X phút trước, X giờ trước, Hôm qua, X ngày trước, DD/MM/YYYY). Tooltip hover hiển thị đầy đủ ngày giờ chính xác DD/MM/YYYY HH:mm:ss.
items[].auditLogId Khóa định danh (Key) ID duy nhất của bản ghi audit log, sử dụng làm unique key khi render danh sách để tối ưu hiệu năng hiển thị.
data.pagination Thông tin phân trang & Tổng số • totalItems: Hiển thị tổng số hoạt động của toàn bộ tờ trình (ví dụ: {totalItems} hoạt động trên header và footer).
• currentPage & totalPages: Điều khiển thanh phân trang và hiển thị Trang {currentPage} / {totalPages}.

Quy tắc điều khiển và xử lý dữ liệu trên Client

1. Cơ chế Cache và nút Làm mới: Client lưu cache theo key {proposalId}:{currentPage}. Khi chuyển qua lại giữa các trang đã tải, client ưu tiên lấy từ cache để hiển thị tức thì. Khi người dùng bấm nút "Làm mới", client xóa cache của key hiện tại và kích hoạt gọi lại API để đồng bộ dữ liệu mới nhất.

2. Xử lý trạng thái rỗng và lỗi:
• Khi totalItems === 0: Hiển thị thông báo "Chưa có hoạt động nào. Các thay đổi liên quan đến tờ trình sẽ xuất hiện tại đây theo thời gian thực."
• Khi có dữ liệu trang nhưng bộ lọc tab hiện tại lọc ra 0 kết quả: Hiển thị "Không có hoạt động nào trong danh mục đã chọn trên trang này" kèm liên kết để đưa bộ lọc quay về tab "Tất cả".
• Khi gọi API thất bại: Hiển thị thông báo lỗi chi tiết lấy từ errorMessage hoặc errorDesc trong response envelope kèm nút "Thử lại".

3. Mã lỗi API: Thiếu hoặc sai proposalId/currentPage trả errorCode = "400"; token thiếu hoặc hết hạn trả 401; tờ trình không tồn tại, đã xóa hoặc không do tài khoản gọi API tạo trả 404; lỗi hệ thống trả 99. Khi gọi qua transaction-create trên MBGW, client cần kiểm tra errorCode trong envelope, không chỉ dựa vào HTTP status.

3. Xác nhận Người đồng đề xuất

Ba API dưới đây nhận JSON object qua POST và yêu cầu access token. Với MBGW, gọi POST /api/pri/transaction-create cùng tranCode, moduleCode và các trường payload của API; gateway dùng USER_AUTH để chuyển tiếp token, bỏ tranCode/moduleCode, thêm systemId của PERPROAPP và gửi tới PerPro. Cấu hình được tạo bởi Version20261007094214.php. Phản hồi trực tiếp của PerPro dùng errorCode, errorDesc, errorMessage, traceId và data; MBGW trả phần data trong envelope giao dịch của gateway.

POST /api/v1/private/my-proposals/co-requesters/eligible-users TranCode: PP_MY_PROPOSALS_CO_REQUESTERS_ELIGIBLE_USERS — Danh sách người có thể đồng đề xuất
Đồng đề xuất
Trả danh bạ người có quyền PROPOSAL:CREATE trong PerPro, loại người gọi khỏi kết quả; đồng thời trả giới hạn maxCoRequesters. Người gọi phải có quyền PERMISSION:PROPOSAL:CREATE. Giá trị mặc định của giới hạn là 5 và có thể thay đổi bằng cấu hình.

Request và response trực tiếp tới PerPro

JSON - Request Body
{}
JSON - Response 200 OK
{
  "errorCode": "00",
  "errorDesc": "Xử lý người đồng đề xuất thành công.",
  "errorMessage": "Xử lý người đồng đề xuất thành công.",
  "traceId": null,
  "data": {
    "items": [{"email": "candidate@example.com", "name": "Người được chọn", "title": null, "department": null, "company": null}],
    "maxCoRequesters": 5
  }
}

Lỗi cần xử lý: 400 nếu body không phải JSON object; 401 khi thiếu/sai token; 403 khi thiếu quyền CREATE; IAM không khả dụng có thể trả HTTP 503 với errorCode = "99".

POST /api/v1/private/my-proposals/co-requesters/notify TranCode: PP_MY_PROPOSALS_CO_REQUESTERS_NOTIFY — Maker yêu cầu xác nhận đồng đề xuất
Đồng đề xuất
Sau khi Maker lưu danh sách coRequesters qua API tạo/sửa tờ trình, gọi API này để bắt đầu lượt xác nhận. Chỉ Maker có quyền PROPOSAL:CREATE được gọi. Gửi thông báo sau khi lưu trạng thái; lỗi gửi thông báo không hủy lượt xác nhận và được phản ánh qua notificationFailedCount.

Request và response trực tiếp tới PerPro

JSON - Request Body
{"proposalId": 1001, "confirmRemoveRejected": false}
JSON - Response 200 OK / data
{
  "proposalStatus": "CO_REQUESTERS_PENDING",
  "coRequestersReturnStatus": "DRAFT",
  "coRequesters": [{"email": "candidate@example.com", "status": "PENDING_CONFIRMATION", "note": null, "requestVersion": 1}],
  "coRequestersConfirmed": false,
  "isMaker": true,
  "notificationFailedCount": 0
}

proposalId phải là số nguyên dương; confirmRemoveRejected là boolean tùy chọn, mặc định false. Nếu lượt trước có người REJECTED, Maker phải xác nhận loại họ bằng true trước khi yêu cầu lại; nếu còn người REVISION_REQUIRED thì phải sửa và lưu trước. Chỉ gọi khi trạng thái biên tập là DRAFT hoặc REVISION_REQUIRED. Lỗi: 400 payload/danh sách sai, 403 không phải Maker, 404 không có tờ trình, 409 sai trạng thái hoặc chưa xác nhận loại người từ chối. Khi đang chờ phản hồi, gọi lại có thể gửi lại thông báo; client cần tránh retry tự động.

POST /api/v1/private/my-proposals/co-requesters/respond TranCode: PP_MY_PROPOSALS_CO_REQUESTERS_RESPOND — Người đồng đề xuất phản hồi lượt xác nhận
Đồng đề xuất
Người đồng đề xuất trong danh sách hiện tại phản hồi lượt yêu cầu xác nhận. API yêu cầu Bearer token của chính người đồng đề xuất đó (không yêu cầu quyền CREATE riêng trên endpoint này). requestVersion lấy từ coRequesters[].requestVersion của chi tiết tờ trình (TranCode PP_MY_PROPOSALS_DETAIL) để bảo đảm đúng lượt và chống race condition.

Request và response trực tiếp tới PerPro

JSON - Request Body
{"proposalId": 1001, "status": "AGREED", "note": null, "requestVersion": 1}
JSON - Response 200 OK / data
{
  "proposalStatus": "PENDING_APPROVAL_SUBMISSION",
  "coRequestersReturnStatus": "DRAFT",
  "coRequesters": [{"email": "candidate@example.com", "status": "AGREED", "note": null, "requestVersion": 1}],
  "coRequestersConfirmed": true,
  "isMaker": false
}

Mô tả Payload (Request)

Field Type Description
proposalId Integer Required ID tờ trình cần phản hồi. Phải là số nguyên dương.
status String Required Trạng thái phản hồi của người đồng đề xuất. Chỉ chấp nhận 1 trong 3 giá trị: AGREED, REJECTED, REVISION_REQUIRED (xem bảng chi tiết bên dưới).
note String | null Conditional Ghi chú hoặc lý do phản hồi (tối đa 10.000 ký tự).
• Bắt buộc khi status là REJECTED hoặc REVISION_REQUIRED (không được rỗng hoặc chỉ có khoảng trắng).
• Tùy chọn khi status là AGREED (có thể truyền null hoặc để trống).
requestVersion Integer Required Phiên bản lượt yêu cầu xác nhận hiện tại (lấy từ trường coRequesters[].requestVersion). Số nguyên dương ≥ 1. Nếu phiên bản gửi lên lệch với phiên bản trên server, API sẽ trả lỗi 409 Conflict.

Bảng Đối chiếu Trạng thái & Giao diện chuẩn (Đồng bộ Web Portal ↔ Mobile)

Mã Status (API) Tùy chọn Portal Mô tả tùy chọn Nút bấm xác nhận Placeholder ô ghi chú Quy định trường note
AGREED Tham gia Đồng ý nội dung và trở thành Đồng đề xuất. Xác nhận Đồng ý Nhập ghi chú hoặc ý kiến đóng góp (không bắt buộc)... Tùy chọn (có thể null)
REJECTED Không tham gia Không đồng ý tham gia tờ trình Xác nhận Từ chối Vui lòng nêu rõ lý do bạn từ chối đồng đề xuất tờ trình này... Bắt buộc (tối đa 10.000 ký tự)
REVISION_REQUIRED Cần chỉnh sửa Không đồng ý với một phần hoặc toàn bộ nội dung và cần Maker chỉnh sửa. Gửi yêu cầu chỉnh sửa Nêu chi tiết những điểm cần Maker điều chỉnh, bổ sung... Bắt buộc (tối đa 10.000 ký tự)

Cơ sở dữ liệu cho Mobile App: Nguồn lấy requestVersion từ TranCode PP_MY_PROPOSALS_DETAIL

Quy trình tích hợp trên Mobile App:
1. Tải chi tiết tờ trình: Khi người dùng mở màn hình tờ trình, Mobile App gọi TranCode PP_MY_PROPOSALS_DETAIL (endpoint /api/v1/private/my-proposals/detail, payload {"proposalId": 1001}).
2. Xác định bản ghi của người dùng: Trong data.coRequesters, Mobile App duyệt tìm bản ghi có email trùng với email của tài khoản đang đăng nhập (gọi là ownRow).
3. Điều kiện hiển thị nút "Xác nhận": Người dùng chỉ có quyền phản hồi khi: trạng thái tờ trình là CO_REQUESTERS_PENDING và ownRow.status === "PENDING_CONFIRMATION".
4. Lấy version gửi lên API respond: Lấy giá trị ownRow.requestVersion để truyền vào payload gọi TranCode PP_MY_PROPOSALS_CO_REQUESTERS_RESPOND. Nếu lượt đã thay đổi trên server, API sẽ trả về lỗi 409 Conflict để Mobile App yêu cầu người dùng reload lại tờ trình.

Dữ liệu mẫu phản hồi từ TranCode PP_MY_PROPOSALS_DETAIL (bổ sung trường coRequesters):

JSON - Response Sample (PP_MY_PROPOSALS_DETAIL)
{
  "errorCode": "00",
  "errorDesc": "Lấy chi tiết tờ trình thành công.",
  "data": {
    "id": 1001,
    "number": "TT-2026-001",
    "subjectMatter": "Đề xuất mua sắm trang thiết bị CNTT quý 4",
    "status": "CO_REQUESTERS_PENDING",
    "statusLabel": "Chờ xác nhận đồng đề xuất",
    "proposalStatus": "CO_REQUESTERS_PENDING",
    "coRequestersReturnStatus": "DRAFT",
    "coRequestersConfirmed": false,
    "isMaker": false,
    "coRequesters": [
      {
        "id": 15,
        "email": "candidate1@example.com",
        "name": "Nguyễn Văn A",
        "title": "Chuyên viên",
        "department": "Ban Công nghệ Thông tin",
        "company": "Công ty Cổ phần PER",
        "status": "PENDING_CONFIRMATION",
        "statusLabel": "Chờ xác nhận",
        "note": null,
        "requestVersion": 2,
        "notifiedAt": "2026-10-07T09:30:00+07:00",
        "respondedAt": null
      },
      {
        "id": 16,
        "email": "candidate2@example.com",
        "name": "Trần Thị B",
        "title": "Trưởng phòng",
        "department": "Ban Tài chính Kế toán",
        "company": "Công ty Cổ phần PER",
        "status": "AGREED",
        "statusLabel": "Đồng ý",
        "note": "Tôi đồng ý đứng tên đồng đề xuất.",
        "requestVersion": 2,
        "notifiedAt": "2026-10-07T09:30:00+07:00",
        "respondedAt": "2026-10-07T10:15:00+07:00"
      }
    ]
  }
}

Cấu trúc các trường trong mảng coRequesters[]:

Field Type Description
id Integer ID định danh duy nhất của bản ghi người đồng đề xuất.
email String Email của người đồng đề xuất. Dùng để Mobile App so khớp với email user đang đăng nhập (không phân biệt hoa thường).
name String Họ tên đầy đủ của người đồng đề xuất.
title String | null Chức danh của người đồng đề xuất.
department String | null Phòng ban / Bộ phận công tác.
company String | null Công ty / Đơn vị thành viên.
status String Mã trạng thái phản hồi của người này: DRAFT, PENDING_CONFIRMATION, AGREED, REJECTED, REVISION_REQUIRED.
statusLabel String Nhãn tiếng Việt của trạng thái: Chờ xác nhận, Đồng ý, Từ chối, Chờ chỉnh sửa (hoặc Chưa xác nhận trên Portal khi còn là DRAFT).
note String | null Ghi chú hoặc ý kiến đóng góp / lý do từ chối mà người này đã nhập khi phản hồi.
requestVersion Integer Phiên bản của lượt yêu cầu xác nhận hiện tại. Mobile App đọc giá trị này từ bản ghi của user và truyền vào tham số requestVersion của API PP_MY_PROPOSALS_CO_REQUESTERS_RESPOND.
notifiedAt String | null Thời điểm gửi yêu cầu xác nhận (chuỗi định dạng ISO 8601 theo múi giờ UTC+7).
respondedAt String | null Thời điểm người này gửi phản hồi (nếu chưa phản hồi thì giá trị là null).

Quy tắc hiển thị Badge trạng thái trong danh sách:
Trường statusLabel trả về từ API/chi tiết tờ trình tương ứng với các trạng thái người đồng đề xuất:
• DRAFT: Nhãn "Chưa xác nhận" (Portal) / "Nháp" (backend), màu Xám (Slate).
• PENDING_CONFIRMATION: Nhãn "Chờ xác nhận", màu Xám/Vàng nhạt.
• AGREED: Nhãn "Đồng ý", màu Xanh lá (Emerald / Success).
• REJECTED: Nhãn "Từ chối", màu Đỏ (Rose / Danger).
• REVISION_REQUIRED: Nhãn "Chờ chỉnh sửa", màu Cam / Hổ phách (Amber / Warning).

Quy tắc nghiệp vụ & Mã lỗi cần xử lý:
• Chuyển trạng thái tờ trình: Khi người đồng đề xuất cuối cùng hoàn tất phản hồi, tờ trình tự động chuyển sang PENDING_APPROVAL_SUBMISSION. Nếu có người phản hồi REVISION_REQUIRED, Maker bắt buộc phải chỉnh sửa nội dung và gửi yêu cầu xác nhận lại trước khi được phép nộp duyệt.
• Idempotency: Gửi lại cùng status, note và requestVersion sẽ trả về trạng thái hiện tại (không báo lỗi).
• HTTP 400: Payload sai định dạng, status không hợp lệ, thiếu ghi chú bắt buộc khi từ chối/yêu cầu sửa, hoặc note vượt quá 10.000 ký tự.
• HTTP 401: Thiếu token hoặc token không hợp lệ.
• HTTP 403: Email trong token không nằm trong danh sách người đồng đề xuất của tờ trình.
• HTTP 404: Không tìm thấy tờ trình.
• HTTP 409: requestVersion không khớp (lượt đã thay đổi) hoặc tờ trình/bản ghi không còn ở trạng thái chờ phản hồi (CO_REQUESTERS_PENDING / PENDING_CONFIRMATION).

4. Bảng Tra cứu Mã lỗi, Trạng thái & Ma trận Chuyển đổi

TRA CỨU Bảng Mã Lỗi & Trạng Thái Hệ Thống — Danh mục MBGW TranCode, mã lỗi, trạng thái tờ trình, trạng thái bước, ma trận transition, ý nghĩa cột approval_flow_locked và ma trận Push Notification
Reference

1. Bảng Tổng hợp Mobile Gateway Transaction Codes (MBGW Mapping)

Danh mục 11 mã giao dịch được mô tả trong tài liệu trên Mobile Gateway ánh xạ tới API PerPro Upstream (cấu hình trong mg_transaction, mg_integration và mg_transaction_component theo migration Version20260825171306.php, Version20260907074630.php, Version20260914084705.php và Version20261007094214.php):

MBGW TranCode (TRANSACTIONS.code) PerPro Upstream Endpoint Tên Transaction & Chức năng nghiệp vụ
PP_PROPOSAL_CHECK_LIST /api/v1/private/proposal-review-inbox/list Lấy danh sách tờ trình phê duyệt/review PerPro
PP_PROPOSAL_APPROVALS_AVAILABLE_ACTIONS /api/v1/private/proposal-approvals/available-actions Lấy quy trình và thao tác phê duyệt khả dụng PerPro
PP_PROPOSAL_APPROVALS_ELIGIBLE_REVIEWERS /api/v1/private/proposal-approvals/eligible-reviewers Lấy danh sách reviewer hợp lệ PerPro
PP_PROPOSAL_APPROVALS_PERFORM_ACTION /api/v1/private/proposal-approvals/perform-action Thực hiện thao tác phê duyệt/review PerPro
PP_PROPOSAL_APPROVALS_MODIFY_STEPS /api/v1/private/proposal-approvals/modify-steps Chỉnh sửa các bước phê duyệt PerPro
PP_PROPOSAL_APPROVALS_TRIGGER_STEP_FLOW /api/v1/private/proposal-approvals/trigger-step-flow Khởi chạy quy trình phê duyệt PerPro
PP_PROPOSAL_APPROVALS_STEP_HISTORY /api/v1/private/proposal-approvals/step-history Lấy lịch sử các bước phê duyệt PerPro
PP_MY_PROPOSALS_ACTIVITIES /api/v1/private/my-proposals/activities Lấy nhật ký hoạt động tờ trình của tôi PerPro, có phân trang
PP_MY_PROPOSALS_CO_REQUESTERS_ELIGIBLE_USERS /api/v1/private/my-proposals/co-requesters/eligible-users Lấy danh sách người đồng đề xuất hợp lệ
PP_MY_PROPOSALS_CO_REQUESTERS_NOTIFY /api/v1/private/my-proposals/co-requesters/notify Maker yêu cầu người đồng đề xuất xác nhận
PP_MY_PROPOSALS_CO_REQUESTERS_RESPOND /api/v1/private/my-proposals/co-requesters/respond Người đồng đề xuất phản hồi lượt yêu cầu

2. Bảng Mã lỗi phản hồi (Error Codes)

Với ba API đồng đề xuất mới, PerPro trả mã trong errorCode và HTTP status tương ứng; MBGW chuyển lỗi sang envelope giao dịch của gateway. Client cần đọc mã lỗi trong envelope, kể cả khi HTTP request tới MBGW thành công:

Mã lỗi (Code) Tên lỗi Mô tả chi tiết & Trường hợp xảy ra
01 Creator Cannot Approve Người tạo tờ trình vi phạm quy tắc chống tự phê duyệt (cố tình thực hiện duyệt, gán mình làm người duyệt hoặc nhận ủy quyền).
400 Bad Request Payload JSON không hợp lệ, thiếu trường bắt buộc, sai định dạng email/validTo, sai kiểu dữ liệu, hoặc assignee không có quyền IAM tương ứng.
401 Unauthorized Phiên đăng nhập không hợp lệ, thiếu Bearer Token hoặc token đã hết hạn.
403 Forbidden Người dùng không có quyền truy cập tờ trình, không phải người tạo tờ trình khi modify/submit, không được giao xử lý bước hiện tại, hoặc cấu hình ủy quyền trong danh bạ đã bị xóa/thu hồi. Với API đồng đề xuất: thiếu quyền CREATE hoặc không phải Maker/người đồng đề xuất phù hợp.
404 Not Found Không tìm thấy tờ trình (Proposal ID không tồn tại hoặc đã bị soft-delete).
409 Conflict Xung đột trạng thái: Bước phê duyệt đã bị người khác xử lý trước đó, trạng thái tờ trình không phù hợp với hành động, user được giao nhiều hơn 1 bước processing, hoặc vi phạm tính liên tục của linked-list. Với đồng đề xuất: lượt requestVersion cũ, chưa sửa theo yêu cầu hoặc chưa xác nhận loại người đã từ chối.
503 Service Unavailable Dịch vụ IAM tạm thời không khả dụng khi xác thực quyền của assignee/reviewer/checker.
99 System Error Lỗi hệ thống không xác định trong quá trình xử lý.

3. Bảng Ma trận Chuyển đổi Trạng thái khi thực hiện Action (State Transitions)

Action Step Type Trạng thái trước Hành vi & Kết quả sau khi thực hiện
approve APPROVE UNDER_APPROVAL Cập nhật note nếu có data.note; Current step thành PROCESSED/APPROVED. Mở bước kế tiếp thành PROCESSING, hoặc chuyển proposal thành APPROVED nếu là bước cuối cùng (kết thúc workflow).
reject APPROVE UNDER_APPROVAL Cập nhật note nếu có data.note; Bước hiện tại thành PROCESSED/REJECTED; các bước chưa xử lý còn lại thành PROCESSED và giữ nguyên result; Proposal chuyển sang REJECTED (kết thúc quy trình).
need_rework APPROVE UNDER_APPROVAL Cập nhật note nếu có data.note; Current step giữ PROCESSING với kết quả result = REWORK; Proposal chuyển sang REVISION_REQUIRED để người tạo sửa lại.
need_review APPROVE UNDER_APPROVAL Lưu data.note vào assigner_note trên step mới, note ban đầu là null; Chèn 1 root step REVIEW/PROCESSING ngay trước step Approve hiện tại; Step Approve chuyển sang LOCKED; Proposal chuyển sang PENDING_REVIEW.
need_checker APPROVE UNDER_APPROVAL Lưu data.note vào assigner_note trên step mới, note ban đầu là null; Chèn 1 root step APPROVE/PROCESSING ngay trước step Approve hiện tại; Step Approve chuyển sang LOCKED; Proposal giữ nguyên UNDER_APPROVAL.
delegate_approver APPROVE UNDER_APPROVAL Cập nhật note nếu có data.note; Tạo mapping ủy quyền có hiệu lực trong bảng perpro_approval_step_delegated_approver; Giữ nguyên trạng thái step và proposal.
revoke_delegate_approver APPROVE UNDER_APPROVAL Assignee gốc thu hồi mapping ủy quyền đang hiệu lực; mapping chuyển sang REVOKED, step/proposal giữ nguyên và quyền xử lý trở lại assignee gốc.
revoke_added_step APPROVE hoặc REVIEW phát sinh Trạng thái hiện tại của proposal Actor trùng assignedByEmail soft-delete step hợp lệ, thu hồi delegation và nối lại workflow. Chặn step PROCESSED và step PROCESSING có kết quả REWORK/REQUEST_DOCUMENTS. Nếu target đang PROCESSING, backend kích hoạt step kế tiếp và cập nhật proposal; nếu target chưa tới lượt thì giữ nguyên proposal/current step.
reviewed REVIEW PENDING_REVIEW Lưu ý kiến chuyên môn vào note và thời điểm xử lý vào updated_at khi có data.note; Current step thành PROCESSED/REVIEWED; Mở lại bước Approve tiếp theo thành PROCESSING; Proposal chuyển về UNDER_APPROVAL.
documents_request APPROVE hoặc REVIEW UNDER_APPROVAL hoặc PENDING_REVIEW Cập nhật note của current step nếu payload có data.note; Current step giữ PROCESSING với kết quả result = REQUEST_DOCUMENTS; Proposal chuyển sang PENDING_DOCUMENTS để người tạo nộp thêm hồ sơ. Permission được kiểm tra theo loại step: PROPOSAL.APPROVE hoặc PROPOSAL.REVIEW.
submit — DRAFT hoặc PENDING_APPROVAL_SUBMISSION với coRequestersReturnStatus = DRAFT Kích hoạt bước đầu tiên thành PROCESSING, các bước sau LOCKED; Proposal chuyển sang UNDER_APPROVAL (hoặc PENDING_REVIEW nếu bước 1 là Review). Nếu có người đồng đề xuất, phải hoàn tất lượt xác nhận hiện tại.
rework-submit — REVISION_REQUIRED hoặc PENDING_APPROVAL_SUBMISSION với coRequestersReturnStatus = REVISION_REQUIRED Thu hồi các ủy quyền cũ, cập nhật lại danh sách steps, reset kết quả; Bước 1 chuyển sang PROCESSING; Proposal chuyển sang UNDER_APPROVAL (hoặc PENDING_REVIEW). Nếu có người đồng đề xuất, phải hoàn tất lượt xác nhận lại.
additional_documents_submit — PENDING_DOCUMENTS Reset kết quả của bước APPROVE hoặc REVIEW đang request documents về null (tiếp tục PROCESSING); Proposal chuyển về UNDER_APPROVAL hoặc PENDING_REVIEW theo loại step.

4. Bảng Ý nghĩa các Giá trị Cột approval_flow_locked

Ý nghĩa và hành vi nghiệp vụ theo từng giá trị của cột approval_flow_locked:

Giá trị Chế độ Mô tả chi tiết ý nghĩa & Hành vi
0 Không add system step Không add thêm system step vào quy trình phê duyệt.
1 Mặc định add step cuối Mặc định chỉ add step cuối, user thích chỉnh thì chỉnh.
2 Cố định luôn luồng Add step, & cố định luôn luồng không cho thêm bớt.
3 Khóa bước cuối (system_step) Add vào bước cuối & không được chỉnh sửa (system_step).

5. Bảng Ma trận Sự kiện Push Notification & Thông báo tự động

Tổng hợp 14 sự kiện thông báo quy trình tờ trình phát sinh từ perform-action và trigger-step-flow. Kênh IN_APP được gửi sang Notification Center của PerHub để hiển thị Web Portal và gửi Mobile Push Notification (FCM/APNs); kênh EMAIL gửi SMTP tự động:

Event Code API & Action phát sinh Đối tượng nhận (Recipient) Tiêu đề thông báo (Title) Deep link (navigate) Kênh gửi
CHECKER_TURN perform-action (approve, reviewed)
trigger-step-flow (submit, rework-submit)
Checker của bước APPROVE hiện tại (hoặc người nhận ủy quyền hợp lệ) Tờ trình cần phê duyệt approvals/{id} In-App / Push Email
REVIEWER_ADDED perform-action (approve, reviewed, need_review)
trigger-step-flow (submit, rework-submit)
Reviewer của bước REVIEW được kích hoạt / thêm mới Bạn được chỉ định làm Reviewer approvals/{id} In-App / Push Email
REVIEWER_RECALLED perform-action (revoke_added_step) Reviewer của bước review bị thu hồi Thu hồi yêu cầu Reviewer (Không kèm URL) In-App / Push Email
CHECKER_ADDED perform-action (need_checker) Checker mới được thêm vào bước duyệt Bạn được thêm làm Checker approvals/{id} In-App / Push Email
CHECKER_RECALLED perform-action (revoke_added_step) Checker của bước approve bị thu hồi Thu hồi yêu cầu Checker (Không kèm URL) In-App / Push Email
STEP_APPROVED perform-action (approve) Người tạo tờ trình (Maker) Một bước của tờ trình đã được phê duyệt my-proposals/{id} In-App / Push Email
PROPOSAL_REJECTED perform-action (reject) Người tạo tờ trình (Maker) Tờ trình bị từ chối my-proposals/{id} In-App / Push Email
PROPOSAL_REWORKED perform-action (need_rework) Người tạo tờ trình (Maker) Tờ trình cần sửa lại my-proposals/{id} In-App / Push Email
SUPPLEMENT_REQUESTED perform-action (documents_request) Người tạo tờ trình (Maker) Yêu cầu bổ sung chứng từ my-proposals/{id} In-App / Push Email
SUPPLEMENT_COMPLETED trigger-step-flow (additional_documents_submit) Assignee / Người nhận ủy quyền đang xử lý bước hiện tại Đã bổ sung chứng từ approvals/{id} In-App / Push
PROPOSAL_COMPLETED perform-action (approve - khi duyệt bước cuối) Người tạo tờ trình (Maker) Tờ trình đã hoàn tất my-proposals/{id} In-App / Push Email
APPROVAL_DELEGATED perform-action (delegate_approver) Người nhận ủy quyền phê duyệt Bạn được ủy quyền phê duyệt approvals/{id} In-App / Push Email
APPROVAL_DELEGATION_REVOKED perform-action (revoke_delegate_approver) Người bị thu hồi ủy quyền phê duyệt Thu hồi ủy quyền phê duyệt (Không kèm URL) In-App / Push Email
Đã sao chép vào bộ nhớ tạm!