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.
PENDING_APPROVAL, PENDING_REVIEW,
NOT_YET_TURN, DELEGATED, DELEGATED_BY_ME, PROCESSED).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.
Authorization: Bearer <token>).
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.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.
["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.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.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.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.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).
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.| 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).
|
{
"filter": ["PENDING_APPROVAL"],
"keyword": "0045/2026",
"currentPage": 1
}
{
"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
}
}
}
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).
Authorization: Bearer <token>).
createdBy) hoặc người lập tờ trình
(makerEmail).coRequesters).assignedByEmail) của ít nhất một bước chưa hoàn tất xử lý (status !== 'PROCESSED').assignee) của ít nhất một bước active
trong quy trình.delegatedApprover) của ít nhất một
bước trong quy trình.403 Forbidden.
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.
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.
approvalFlowLocked (0, 1,
2 hoặc 3). Field là mode snapshot từ template tại lần
submit/rework-submit.
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.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).
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.assignedByEmail == null): assignerNote là null; note tiếp tục là ghi chú văn bản thông thường.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.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. |
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. |
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ừ. |
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). |
| 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). |
{
"proposalId": 123
}
{
"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"
}
]
}
}
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.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.
Authorization: Bearer <token>).
403 Forbidden.
REVIEW đối với module phê duyệt tờ
trình.
API này không yêu cầu tham số
payload trong body (truyền JSON rỗng {} hoặc body trống).
{}
{
"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"
}
]
}
}
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).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.
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ệ).
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.").
409 Conflict.
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.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.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.
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.
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.UNDER_APPROVAL.
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.403.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.
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.
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.
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.
perpro_approval_step.assigned_by_email của step mới và trả về field
assignedByEmail.
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.APPROVE/PROCESSING mới được ủy quyền. Người nhận ủy
quyền không được ủy quyền tiếp.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.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.ACTIVE sang REVOKED và ghi audit thu hồi; step
tiếp tục PROCESSING, proposal giữ UNDER_APPROVAL.data.stepId. Chỉ actor trùng assignedByEmail của
root step phát sinh, có PROPOSAL.APPROVE, được thu hồi.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.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.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):
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) |
| 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. |
approve (Phê duyệt){
"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."
}
}
reject (Từ chối){
"proposalId": 123,
"action": "reject",
"data": {
"note": "Không đồng ý phê duyệt do rủi ro tài chính cao."
}
}
need_rework (Yêu cầu làm lại){
"proposalId": 123,
"action": "need_rework",
"data": {
"note": "Đề nghị bổ sung thêm tài liệu đánh giá rủi ro."
}
}
need_review (Yêu cầu thêm người thẩm
định){
"proposalId": 142,
"action": "need_review",
"data": {
"reviewers": [
{
"email": "admin@movi.vn",
"requiresProposalSign": true
}
],
"note": null
}
}
need_checker (Yêu cầu thêm người phê
duyệt){
"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."
}
}
reviewed (Đã thẩm định xong){
"proposalId": 123,
"action": "reviewed",
"data": {
"note": "Đã xem xét và đánh giá hồ sơ đầy đủ."
}
}
documents_request (Yêu cầu bổ sung hồ sơ)
{
"proposalId": 123,
"action": "documents_request",
"data": {
"note": "Bổ sung thêm bản sao kê tài khoản ngân hàng."
}
}
delegate_approver (Ủy quyền bước phê
duyệt){
"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."
}
}
revoke_delegate_approver (Thu hồi ủy
quyền bước phê duyệt){
"proposalId": 123,
"action": "revoke_delegate_approver"
}
revoke_added_step (Thu hồi yêu
cầu bổ sung bước){
"proposalId": 123,
"action": "revoke_added_step",
"data": {
"stepId": 456
}
}
{
"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..." ]
}
}
| 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. |
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.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).
createdBy) mới được phép chỉnh sửa danh sách bước phê duyệt.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).stepType = APPROVE.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.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.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.afterWorkflowChanged).approval_flow_locked = 2 và approval_flow có dữ liệu:
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.stepId, note và các thông tin snapshot không tham gia so sánh.DRAFT và LOCKED.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.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.").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.PROCESSING; các bước còn lại phía sau chuyển sang LOCKED.PROCESSING tiếp theo là APPROVE: Tờ trình tự động chuyển sang UNDER_APPROVAL.PROCESSING tiếp theo là REVIEW: Tờ trình tự động chuyển sang PENDING_REVIEW.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).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).modifiedStepCount (tổng số bước hiệu lực) và deletedStepCount (số bước bị xóa mềm).| 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. |
{
"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
}
]
}
{
"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": []
}
}
| 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). |
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ó). |
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ũ.
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.steps trong request.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.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.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.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.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).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.steps chứa danh sách root steps
mới/chỉnh sửa (không truyền childSteps).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.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 đủ.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.APPROVE.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.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.PROCESSING).PENDING_DOCUMENTS.steps trong request.APPROVE hoặc REVIEW đang ở
PROCESSING với kết quả REQUEST_DOCUMENTS.
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.0/1: Không đối chiếu hoặc đồng bộ root steps với approval_flow.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.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.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.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.").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} |
submit (Gửi duyệt lần đầu){
"proposalId": 123,
"action": "submit"
}
rework-submit (Gửi lại sau khi chỉnh sửa)
{
"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"
}
]
}
additional_documents_submit (Gửi bổ sung
chứng từ){
"proposalId": 123,
"action": "additional_documents_submit"
}
submit & additional_documents_submit{
"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..." ]
}
}
rework-submit (Gửi lại sau điều chỉnh){
"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..." ]
}
}
| 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. |
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).
Authorization: Bearer <token>).403 Forbidden (envelope code: "403").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.404 Not Found.stepType, assignedByEmail, assigneeEmail, status, result, note.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.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.actionContext):
need_review, need_checker, delegate_approver, revoke_delegate_approver, revoke_added_step): Trả về actionContext.targetPerson (gồm email, name, title, department, company).maker_change): Trả về actionContext.workflowChange (gồm kind: added | removed | reordered, beforePosition, afterPosition).yyyy-MM-dd HH:mm:ss).items để client (ApprovalWorkflowPanel) cache một lần và map theo từng step. Nếu chưa có lịch sử, trả items: [].| 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). |
{
"proposalId": 1001
}
{
"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
}
}
]
}
}
| 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. |
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. |
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. |
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). |
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). |
[title, department, company].filter(Boolean).join(' · ').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). |
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.
| Trường | Kiểu | Quy tắc |
|---|---|---|
proposalId | Integer | Bắ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. |
currentPage | Integer | Tù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.
{
"proposalId": 1001,
"currentPage": 1
}
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.
{
"moduleCode": "MODULE_CODE_CUA_UNG_DUNG",
"tranCode": "PP_MY_PROPOSALS_ACTIVITIES",
"payload": {
"proposalId": 1001,
"currentPage": 1
}
}
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.
{
"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
}
}
}
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 JSON | Vai 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.
| Field trong Response | Vị trí hiển thị trên Client | Quy 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}. |
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.
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.
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.{}{
"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".
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.{"proposalId": 1001, "confirmRemoveRejected": false}{
"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.
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.{"proposalId": 1001, "status": "AGREED", "note": null, "requestVersion": 1}{
"proposalStatus": "PENDING_APPROVAL_SUBMISSION",
"coRequestersReturnStatus": "DRAFT",
"coRequesters": [{"email": "candidate@example.com", "status": "AGREED", "note": null, "requestVersion": 1}],
"coRequestersConfirmed": true,
"isMaker": false
}| 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.
|
| 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ự) |
PP_MY_PROPOSALS_DETAIL (endpoint /api/v1/private/my-proposals/detail, payload {"proposalId": 1001}).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).CO_REQUESTERS_PENDING và ownRow.status === "PENDING_CONFIRMATION".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):
{
"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).
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 |
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ý. |
| 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.
|
Ý 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). |
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 |