From 8a7ce3d7849b1e87b5f6f8b84458f1e0a2d1e75d Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 19 Jul 2026 13:20:06 +0000 Subject: [PATCH] feat(approvals): declare decision actions on sys_approval_request (objectui#2678 P2-4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Server-declared approve / reject / reassign actions on the request object, so the console's generic action runtime renders and executes them wherever the object is surfaced — the approvals inbox included. Each is a `type:'api'` action targeting the existing REST route ({id} resolves from the row; actorId defaults to the caller server-side), with typed params (comment; reassign's `to`), a pending-only `visible` gate (display-only — the service's pending-approver check stays authoritative), and a confirm on reject. This is the framework half of the P2-4 SDUI migration: future decision capabilities (enterprise act-as included) ship as metadata on this object, not as hand-written inbox buttons. 137 tests green. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_016ypkQikZ55oWUHUnMebwXA --- .../src/sys-approval-request.object.ts | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/packages/plugins/plugin-approvals/src/sys-approval-request.object.ts b/packages/plugins/plugin-approvals/src/sys-approval-request.object.ts index 2aae9109c0..a91c2e7e2c 100644 --- a/packages/plugins/plugin-approvals/src/sys-approval-request.object.ts +++ b/packages/plugins/plugin-approvals/src/sys-approval-request.object.ts @@ -229,4 +229,62 @@ export const SysApprovalRequest = ObjectSchema.create({ { fields: ['status', 'updated_at'] }, { fields: ['submitter_id', 'status'] }, ], + + // Server-declared decision actions (objectui#2678 P2-4). The console's + // generic action runtime renders and executes these wherever this object is + // surfaced — the approvals inbox included — so new decision capabilities + // (and their params) ship as metadata, not as hand-written buttons. Each + // targets the existing approvals REST route; `{id}` resolves from the row + // and `actorId` defaults to the caller server-side. The service remains the + // authority on who may act (pending-approver check) — `visible` only trims + // the obvious non-pending case. + actions: [ + { + name: 'approval_approve', + label: 'Approve', + icon: 'check-circle', + type: 'api', + method: 'POST', + target: '/api/v1/approvals/requests/{id}/approve', + params: [ + { name: 'comment', label: 'Comment', type: 'textarea', required: false }, + ], + visible: 'record.status == "pending"', + locations: ['record_section', 'list_item'], + successMessage: 'Approved.', + refreshAfter: true, + }, + { + name: 'approval_reject', + label: 'Reject', + icon: 'x-circle', + type: 'api', + method: 'POST', + target: '/api/v1/approvals/requests/{id}/reject', + params: [ + { name: 'comment', label: 'Comment', type: 'textarea', required: false }, + ], + visible: 'record.status == "pending"', + confirmText: 'Reject this request? A rejection is final for every approver.', + locations: ['record_section', 'list_item'], + successMessage: 'Rejected.', + refreshAfter: true, + }, + { + name: 'approval_reassign', + label: 'Reassign', + icon: 'arrow-right-left', + type: 'api', + method: 'POST', + target: '/api/v1/approvals/requests/{id}/reassign', + params: [ + { name: 'to', label: 'New approver (user id)', type: 'text', required: true }, + { name: 'comment', label: 'Comment', type: 'textarea', required: false }, + ], + visible: 'record.status == "pending"', + locations: ['record_section'], + successMessage: 'Reassigned.', + refreshAfter: true, + }, + ], });