Skip to content

Commit dd3050c

Browse files
committed
feat(approvals): declare file attachments on approve/reject decisions
Add an optional multi-file `attachments` param to the declared `approval_approve` / `approval_reject` actions on sys_approval_request. The console now renders `type:'file'` action params through the shared upload widget (objectui ADR-0059) and POSTs the resolved `attachments: string[]`, so a reviewer can attach supporting files to a decision through the generic declared-action dialog — no bespoke composer. No service/route change: the decision route already forwards `body.attachments` to `ApprovalService.decide`, and the `sys_approval_action.attachments` column (file, multiple) already persists them (#3266/#3274). This just surfaces the capability as declared metadata so the approvals inbox can retire its hand-wired attachment composer (objectui#2698). Contract test pins the new param (type file, multiple, optional). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ypkQikZ55oWUHUnMebwXA
1 parent 16ebe1a commit dd3050c

2 files changed

Lines changed: 13 additions & 0 deletions

File tree

packages/plugins/plugin-approvals/src/sys-approval-request.object.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,12 @@ describe('sys_approval_request declared actions', () => {
9191
expect(c.required ?? false).toBe(false);
9292
}
9393
});
94+
95+
it('approve/reject collect optional multi-file decision attachments', () => {
96+
for (const name of ['approval_approve', 'approval_reject']) {
97+
const att = byName(name).params.find((p: any) => p.name === 'attachments');
98+
expect(att, `${name}.attachments`).toMatchObject({ type: 'file', multiple: true });
99+
expect(att.required ?? false).toBe(false);
100+
}
101+
});
94102
});

packages/plugins/plugin-approvals/src/sys-approval-request.object.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,10 @@ export const SysApprovalRequest = ObjectSchema.create({
248248
target: '/api/v1/approvals/requests/{id}/approve',
249249
params: [
250250
{ name: 'comment', label: 'Comment', type: 'textarea', required: false },
251+
// Decision attachments (#3266). The console renders `type:'file'` params
252+
// through the shared upload widget and POSTs the resolved `attachments:
253+
// string[]`; the decision route persists them on `sys_approval_action`.
254+
{ name: 'attachments', label: 'Attachments', type: 'file', multiple: true, required: false },
251255
],
252256
visible: 'record.status == "pending"',
253257
locations: ['record_section', 'list_item'],
@@ -263,6 +267,7 @@ export const SysApprovalRequest = ObjectSchema.create({
263267
target: '/api/v1/approvals/requests/{id}/reject',
264268
params: [
265269
{ name: 'comment', label: 'Comment', type: 'textarea', required: false },
270+
{ name: 'attachments', label: 'Attachments', type: 'file', multiple: true, required: false },
266271
],
267272
visible: 'record.status == "pending"',
268273
confirmText: 'Reject this request? A rejection is final for every approver.',

0 commit comments

Comments
 (0)