From b65f5718be2dac3a60e5c55a6f227344a59a0f79 Mon Sep 17 00:00:00 2001 From: Til Weber Date: Tue, 12 May 2026 14:06:59 +0200 Subject: [PATCH 1/8] initial commit --- lib/handlers/processService.ts | 25 +++++++++++++++++++++++ lib/processImport/csnBuilder.ts | 22 +++++++++++++++++++++ srv/BTPProcessService.cds | 11 +++++++++++ srv/BTPProcessService.ts | 25 +++++++++++++++++++++++ srv/localProcessService.ts | 35 +++++++++++++++++++++++++++++++++ 5 files changed, 118 insertions(+) diff --git a/lib/handlers/processService.ts b/lib/handlers/processService.ts index 597659e4..4010140e 100644 --- a/lib/handlers/processService.ts +++ b/lib/handlers/processService.ts @@ -25,6 +25,7 @@ export function registerProcessServiceHandlers(service: cds.Service): void { registerGetInstancesByBusinessKeyHandler(service, definitionId); registerGetAttributesHandler(service, definitionId); registerGetOutputsHandler(service, definitionId); + registerUpdateInstanceStatusHandler(service, definitionId); } function registerStartHandler(service: cds.Service, definitionId: string): void { @@ -171,3 +172,27 @@ function registerGetOutputsHandler(service: cds.Service, definitionId: string): return result; }); } + +function registerUpdateInstanceStatusHandler(service: cds.Service, definitionId: string): void { + service.on('updateInstanceStatus', async (req) => { + LOG.debug(`Updating instance status for process: ${definitionId}`); + + const { instanceId, status, cascade } = req.data; + if (!instanceId) { + return req.reject({ status: 400, message: 'Missing required parameter: instanceId' }); + } + if (!status) { + return req.reject({ status: 400, message: 'Missing required parameter: status' }); + } + const validStatuses = Object.values(WorkflowStatus); + if (!validStatuses.includes(status)) { + return req.reject({ + status: 400, + message: `Invalid status: ${status}. Valid values are: ${validStatuses.join(', ')}`, + }); + } + + const processService = await cds.connect.to(PROCESS_SERVICE); + return processService.send('updateInstanceStatus', { instanceId, status, cascade }); + }); +} diff --git a/lib/processImport/csnBuilder.ts b/lib/processImport/csnBuilder.ts index a700aabd..d00b5c95 100644 --- a/lib/processImport/csnBuilder.ts +++ b/lib/processImport/csnBuilder.ts @@ -85,6 +85,16 @@ function addProcessTypes( const instanceName = fqn(serviceName, 'ProcessInstance'); const instancesName = fqn(serviceName, 'ProcessInstances'); + const updateStatusResultName = fqn(serviceName, 'UpdateStatusResult'); + definitions[updateStatusResultName] = { + kind: 'type', + name: updateStatusResultName, + elements: { + id: { type: csn.CdsBuiltinType.String }, + success: { type: csn.CdsBuiltinType.Boolean }, + }, + }; + definitions[inputsName] = buildTypeFromSchema( inputsName, ensureObjectSchema(process.header?.inputs), @@ -149,6 +159,7 @@ function addProcessActions( const outputsType = fqn(serviceName, 'ProcessOutputs'); const attributesType = fqn(serviceName, 'ProcessAttributes'); const instancesType = fqn(serviceName, 'ProcessInstances'); + const updateStatusResultType = fqn(serviceName, 'UpdateStatusResult'); // Start action — three tiers: // 1. No input properties: start() with no params @@ -201,6 +212,17 @@ function addProcessActions( returns: { type: instancesType }, }; + definitions[fqn(serviceName, 'updateInstanceStatus')] = { + kind: 'function', + name: fqn(serviceName, 'updateInstanceStatus'), + params: { + instanceId: { type: csn.CdsBuiltinType.String, notNull: true }, + status: { type: csn.CdsBuiltinType.String, notNull: true }, + cascade: { type: csn.CdsBuiltinType.Boolean }, + }, + returns: { type: updateStatusResultType }, + }; + // Lifecycle actions for (const action of ['suspend', 'resume', 'cancel']) { definitions[fqn(serviceName, action)] = { diff --git a/srv/BTPProcessService.cds b/srv/BTPProcessService.cds index 6e066e46..75b27460 100644 --- a/srv/BTPProcessService.cds +++ b/srv/BTPProcessService.cds @@ -2,6 +2,10 @@ type AnyType {} type AttributesReturn : many AnyType; type InstancesReturn : many AnyType; +type UpdateStatusResult { + id : String(256); + success : Boolean; +} @protocol: 'none' @impl: './BTPProcessService' @@ -39,4 +43,11 @@ service ProcessService { @mandatory businessKey : String(256), status : many String(256) )returns InstancesReturn; + + function updateInstanceStatus( + @mandatory instanceId : String(256), + @mandatory status : String(256), + cascade : Boolean + ) returns UpdateStatusResult; } + diff --git a/srv/BTPProcessService.ts b/srv/BTPProcessService.ts index e3f05af1..46a20935 100644 --- a/srv/BTPProcessService.ts +++ b/srv/BTPProcessService.ts @@ -142,6 +142,31 @@ class ProcessService extends cds.ApplicationService { return outputs; }); + this.on('updateInstanceStatus', async (request: cds.Request) => { + const { instanceId, status, cascade } = request.data; + + if (!instanceId) { + return request.reject({ status: 400, message: 'Missing required parameter: instanceId' }); + } + if (!status) { + return request.reject({ status: 400, message: 'Missing required parameter: status' }); + } + const validStatuses = Object.values(WorkflowStatus); + if (!validStatuses.includes(status as WorkflowStatus)) { + return request.reject({ + status: 400, + message: `Invalid status: ${status}. Valid values are: ${validStatuses.join(', ')}`, + }); + } + + LOG.info('Updating instance status', instanceId, '->', status); + return this.workflowInstanceClient.updateWorkflowStatus( + instanceId, + status as WorkflowStatus, + cascade ?? false, + ); + }); + return super.init(); } diff --git a/srv/localProcessService.ts b/srv/localProcessService.ts index 7148dabe..4ecf4016 100644 --- a/srv/localProcessService.ts +++ b/srv/localProcessService.ts @@ -213,6 +213,41 @@ class ProcessService extends cds.ApplicationService { return outputs; }); + this.on('updateInstanceStatus', async (req: cds.Request) => { + const { instanceId, status } = req.data; + LOG.info('Updating instance status', instanceId, '->', status); + + LOG.debug( + `==============================================================\n` + + `Update instance status for ${instanceId} to ${status}\n` + + `==============================================================`, + ); + + if (!instanceId) { + return req.reject({ status: 400, message: 'Missing required parameter: instanceId' }); + } + if (!status) { + return req.reject({ status: 400, message: 'Missing required parameter: status' }); + } + const validStatuses = Object.values(WorkflowStatus); + if (!validStatuses.includes(status as WorkflowStatus)) { + return req.reject({ + status: 400, + message: `Invalid status: ${status}. Valid values are: ${validStatuses.join(', ')}`, + }); + } + + const result = localWorkflowStore.updateStatus(instanceId, status as WorkflowStatus); + + if (!result.success) { + LOG.warn(`Workflow instance not found: ${instanceId}`); + return req.reject({ status: 404, message: 'Workflow instance not found' }); + } + + LOG.debug(`Updated status for instance: ${instanceId} to ${status}`); + return result; + }); + return super.init(); } } From 2228a65450aef73236917211a0a29ebb984efdb0 Mon Sep 17 00:00:00 2001 From: Til Weber Date: Tue, 12 May 2026 16:33:57 +0200 Subject: [PATCH 2/8] reimport --- ...ybridtest.annotation_Lifecycle_Process.cds | 13 ++- ...dtest.annotation_Lifecycle_Process_Two.cds | 13 ++- ...t.importProcess_Attributes_And_Outputs.cds | 13 ++- ...ybridtest.importProcess_Complex_Inputs.cds | 13 ++- ...hybridtest.importProcess_Simple_Inputs.cds | 13 ++- ...ridtest.programmatic_Lifecycle_Process.cds | 13 ++- ...hybridtest.programmatic_Output_Process.cds | 13 ++- ....importProcess_Attributes_And_Outputs.json | 94 ++++++++----------- ...bridtest.importProcess_Complex_Inputs.json | 73 +++++++------- ...ybridtest.importProcess_Simple_Inputs.json | 36 ++++--- ...t.importProcess_Attributes_And_Outputs.cds | 13 ++- ...ybridtest.importProcess_Complex_Inputs.cds | 13 ++- ...hybridtest.importProcess_Simple_Inputs.cds | 13 ++- 13 files changed, 210 insertions(+), 123 deletions(-) diff --git a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process.cds b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process.cds index 02198360..224e0739 100644 --- a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process.cds +++ b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process.cds @@ -1,10 +1,15 @@ -/* checksum : 2d9b04f7d100bb10cefeaa255ec0b188 */ +/* checksum : 929834125a8e086a53c589f6745b7d1e */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @protocol : 'none' @bpm.process : 'eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process' service Annotation_Lifecycle_ProcessService { + type UpdateStatusResult { + id : String; + success : Boolean; + }; + type ProcessInputs { ID : String not null; }; @@ -48,6 +53,12 @@ service Annotation_Lifecycle_ProcessService { status : many String ) returns ProcessInstances; + function updateInstanceStatus( + instanceId : String not null, + status : String not null, + cascade : Boolean + ) returns UpdateStatusResult; + action suspend( businessKey : String not null, cascade : Boolean diff --git a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process_Two.cds b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process_Two.cds index 5329f538..3ab3886c 100644 --- a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process_Two.cds +++ b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process_Two.cds @@ -1,10 +1,15 @@ -/* checksum : 15602da859ed8169e46688286553aafe */ +/* checksum : d597187f9ac06787e1d29604bbdd3030 */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @protocol : 'none' @bpm.process : 'eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process_Two' service Annotation_Lifecycle_Process_TwoService { + type UpdateStatusResult { + id : String; + success : Boolean; + }; + type ProcessInputs { id : String not null; }; @@ -48,6 +53,12 @@ service Annotation_Lifecycle_Process_TwoService { status : many String ) returns ProcessInstances; + function updateInstanceStatus( + instanceId : String not null, + status : String not null, + cascade : Boolean + ) returns UpdateStatusResult; + action suspend( businessKey : String not null, cascade : Boolean diff --git a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.cds b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.cds index c11851f6..98238ff5 100644 --- a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.cds +++ b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.cds @@ -1,4 +1,4 @@ -/* checksum : b2be28c9da2617d511526b2f68e5e6b0 */ +/* checksum : 174881d9ba3b813f1b588771022a248c */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @@ -29,6 +29,11 @@ service ImportProcess_Attributes_And_OutputsService { StringType : ImportProcess_Complex_DataType_StringType; }; + type UpdateStatusResult { + id : String; + success : Boolean; + }; + type ProcessInputs_complexe_Array : many ImportProcess_Complex_DataType; type ProcessInputs { @@ -84,6 +89,12 @@ service ImportProcess_Attributes_And_OutputsService { status : many String ) returns ProcessInstances; + function updateInstanceStatus( + instanceId : String not null, + status : String not null, + cascade : Boolean + ) returns UpdateStatusResult; + action suspend( businessKey : String not null, cascade : Boolean diff --git a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.cds b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.cds index 93483826..8ca39df7 100644 --- a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.cds +++ b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.cds @@ -1,4 +1,4 @@ -/* checksum : b0ced28bb4d1bef714f6714bff14642e */ +/* checksum : 51e0b15fafd9e8341ce207e93eb99b1f */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @@ -29,6 +29,11 @@ service ImportProcess_Complex_InputsService { StringType : ImportProcess_Complex_DataType_StringType; }; + type UpdateStatusResult { + id : String; + success : Boolean; + }; + type ProcessInputs_complexlist_Array : many String; type ProcessInputs { @@ -76,6 +81,12 @@ service ImportProcess_Complex_InputsService { status : many String ) returns ProcessInstances; + function updateInstanceStatus( + instanceId : String not null, + status : String not null, + cascade : Boolean + ) returns UpdateStatusResult; + action suspend( businessKey : String not null, cascade : Boolean diff --git a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.cds b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.cds index d26fccaf..4c5e36f4 100644 --- a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.cds +++ b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.cds @@ -1,10 +1,15 @@ -/* checksum : 7e054e53e107a7f5c8375eb51a454e7f */ +/* checksum : 8135be4a2f3327de91ef1e82acf027cb */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @protocol : 'none' @bpm.process : 'eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs' service ImportProcess_Simple_InputsService { + type UpdateStatusResult { + id : String; + success : Boolean; + }; + type ProcessInputs { string : String not null; number : DecimalFloat not null; @@ -53,6 +58,12 @@ service ImportProcess_Simple_InputsService { status : many String ) returns ProcessInstances; + function updateInstanceStatus( + instanceId : String not null, + status : String not null, + cascade : Boolean + ) returns UpdateStatusResult; + action suspend( businessKey : String not null, cascade : Boolean diff --git a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Lifecycle_Process.cds b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Lifecycle_Process.cds index 177eecb0..c01a3acb 100644 --- a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Lifecycle_Process.cds +++ b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Lifecycle_Process.cds @@ -1,10 +1,15 @@ -/* checksum : 721729e92c5456fb13b5e792ac205215 */ +/* checksum : e3d3d8b3c8826decf15f5b31ac2cd307 */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @protocol : 'none' @bpm.process : 'eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Lifecycle_Process' service Programmatic_Lifecycle_ProcessService { + type UpdateStatusResult { + id : String; + success : Boolean; + }; + type ProcessInputs { ID : String not null; attribute1 : String; @@ -50,6 +55,12 @@ service Programmatic_Lifecycle_ProcessService { status : many String ) returns ProcessInstances; + function updateInstanceStatus( + instanceId : String not null, + status : String not null, + cascade : Boolean + ) returns UpdateStatusResult; + action suspend( businessKey : String not null, cascade : Boolean diff --git a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Output_Process.cds b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Output_Process.cds index 8a937cd8..ac1e3bad 100644 --- a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Output_Process.cds +++ b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Output_Process.cds @@ -1,10 +1,15 @@ -/* checksum : 5d4deaa24aac52f7afcf274a034ff450 */ +/* checksum : ff7f2f85755a461155203b777bf3077a */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @protocol : 'none' @bpm.process : 'eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Output_Process' service Programmatic_Output_ProcessService { + type UpdateStatusResult { + id : String; + success : Boolean; + }; + type ProcessInputs { ID : String not null; mandatory_datetime : Timestamp not null; @@ -57,6 +62,12 @@ service Programmatic_Output_ProcessService { status : many String ) returns ProcessInstances; + function updateInstanceStatus( + instanceId : String not null, + status : String not null, + cascade : Boolean + ) returns UpdateStatusResult; + action suspend( businessKey : String not null, cascade : Boolean diff --git a/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.json b/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.json index 72264a98..339d47f3 100644 --- a/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.json +++ b/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.json @@ -1,9 +1,10 @@ { "uid": "5edc02ff-ce9a-4aaf-94bb-440b0c981a20", "name": "ImportProcess_Attributes_And_Outputs", - "identifier": "importProcess_Attributes_And_Outputs", + "description": "", "type": "bpi.process", - "projectId": "eu12.cdsmunich.capprocesspluginhybridtest", + "createdAt": "2026-03-18T10:23:31.648909Z", + "updatedAt": "2026-03-18T12:48:22.484258Z", "header": { "inputs": { "title": "inputs", @@ -12,28 +13,23 @@ "definitions": { "date": { "type": "string", - "format": "date", - "title": "date" + "format": "date" }, "dateTime": { "type": "string", - "format": "date-time", - "title": "dateTime" + "format": "date-time" }, "password": { "type": "string", - "password": true, - "title": "password" + "password": true }, "time": { "type": "string", - "format": "time", - "title": "time" + "format": "time" }, "documentFolder": { "type": "string", - "format": "document-folder", - "title": "documentFolder" + "format": "document-folder" } }, "properties": { @@ -54,8 +50,7 @@ "refName": "ImportProcess_Complex_DataType" }, "title": "Complexe", - "description": "", - "refName": "ImportProcess_Complex_DataType" + "description": "" }, "optionalcomplexe": { "$ref": "$.11cbfe86-2e73-4198-868a-d7a2115d82c1", @@ -77,28 +72,23 @@ "definitions": { "date": { "type": "string", - "format": "date", - "title": "date" + "format": "date" }, "dateTime": { "type": "string", - "format": "date-time", - "title": "dateTime" + "format": "date-time" }, "password": { "type": "string", - "password": true, - "title": "password" + "password": true }, "time": { "type": "string", - "format": "time", - "title": "time" + "format": "time" }, "documentFolder": { "type": "string", - "format": "document-folder", - "title": "documentFolder" + "format": "document-folder" } }, "properties": { @@ -125,8 +115,7 @@ "refName": "ImportProcess_Complex_DataType" }, "title": "Complexe", - "description": "", - "refName": "ImportProcess_Complex_DataType" + "description": "" } }, "required": [ @@ -135,6 +124,8 @@ ] }, "processAttributes": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "processAttributes", "type": "object", "properties": {}, "required": [] @@ -146,12 +137,17 @@ "type": "both" } ], + "identifier": "importProcess_Attributes_And_Outputs", + "valid": true, + "projectId": "eu12.cdsmunich.capprocesspluginhybridtest", "dataTypes": [ { "uid": "11cbfe86-2e73-4198-868a-d7a2115d82c1", "name": "ImportProcess_Complex_DataType", - "identifier": "ImportProcess_Complex_DataType", + "description": "", "type": "datatype", + "createdAt": "2026-03-18T10:28:54.794097Z", + "updatedAt": "2026-03-18T10:31:37.975015Z", "header": { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", @@ -164,13 +160,11 @@ "properties": { "SubString1": { "type": "string", - "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6", - "title": "SubString1" + "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6" }, "Substring2": { "type": "string", - "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94", - "title": "Substring2" + "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94" } }, "required": [ @@ -178,8 +172,7 @@ "Substring2" ] }, - "type": "array", - "title": "StringList" + "type": "array" }, "StringType": { "type": "object", @@ -194,27 +187,21 @@ "uid": "f3dd8f45-fcd2-4fe2-9d2f-868e51f52b64", "properties": { "SubSubSubDate": { - "type": "string", - "format": "date", - "title": "SubSubSubDate" + "uid": "361a2e90-c32d-421d-8077-6a412436f031", + "$ref": "#/definitions/date" }, "SubSubSubPassword": { - "type": "string", - "password": true, - "title": "SubSubSubPassword" + "uid": "9901ea3d-4e0f-4421-981e-22fbc0d35cc6", + "$ref": "#/definitions/password" }, "SubSubSubAny": { - "uid": "99fd32c0-7ce9-4c22-bb85-890160827365", - "title": "SubSubSubAny" + "uid": "99fd32c0-7ce9-4c22-bb85-890160827365" } - }, - "title": "SubSubStringType" + } } - }, - "title": "SubStringType" + } } - }, - "title": "StringType" + } } }, "required": [ @@ -223,18 +210,17 @@ "definitions": { "password": { "type": "string", - "password": true, - "title": "password" + "password": true }, "date": { "type": "string", - "format": "date", - "title": "date" + "format": "date" } }, - "version": 1, - "description": "" - } + "version": 1 + }, + "identifier": "importProcess_Complex_DataType", + "valid": true } ] } \ No newline at end of file diff --git a/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.json b/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.json index 1d011fae..155ebcc8 100644 --- a/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.json +++ b/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.json @@ -1,9 +1,10 @@ { "uid": "edb7b7fd-8a1c-4c51-a19f-b357aece8428", "name": "ImportProcess_Complex_Inputs", - "identifier": "importProcess_Complex_Inputs", + "description": "", "type": "bpi.process", - "projectId": "eu12.cdsmunich.capprocesspluginhybridtest", + "createdAt": "2026-03-18T10:23:06.672697Z", + "updatedAt": "2026-03-19T11:43:58.132360Z", "header": { "inputs": { "title": "inputs", @@ -12,28 +13,23 @@ "definitions": { "date": { "type": "string", - "format": "date", - "title": "date" + "format": "date" }, "dateTime": { "type": "string", - "format": "date-time", - "title": "dateTime" + "format": "date-time" }, "password": { "type": "string", - "password": true, - "title": "password" + "password": true }, "time": { "type": "string", - "format": "time", - "title": "time" + "format": "time" }, "documentFolder": { "type": "string", - "format": "document-folder", - "title": "documentFolder" + "format": "document-folder" } }, "properties": { @@ -70,6 +66,8 @@ "properties": {} }, "processAttributes": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "processAttributes", "type": "object", "properties": {}, "required": [] @@ -81,12 +79,17 @@ "type": "both" } ], + "identifier": "importProcess_Complex_Inputs", + "valid": true, + "projectId": "eu12.cdsmunich.capprocesspluginhybridtest", "dataTypes": [ { "uid": "11cbfe86-2e73-4198-868a-d7a2115d82c1", "name": "ImportProcess_Complex_DataType", - "identifier": "ImportProcess_Complex_DataType", + "description": "", "type": "datatype", + "createdAt": "2026-03-18T10:28:54.794097Z", + "updatedAt": "2026-03-18T10:31:37.975015Z", "header": { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", @@ -99,13 +102,11 @@ "properties": { "SubString1": { "type": "string", - "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6", - "title": "SubString1" + "uid": "567555b3-ffee-41d4-a650-e9006ca9c5f6" }, "Substring2": { "type": "string", - "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94", - "title": "Substring2" + "uid": "794440a0-a06e-4fa6-a703-1f8fe4bc9d94" } }, "required": [ @@ -113,8 +114,7 @@ "Substring2" ] }, - "type": "array", - "title": "StringList" + "type": "array" }, "StringType": { "type": "object", @@ -129,27 +129,21 @@ "uid": "f3dd8f45-fcd2-4fe2-9d2f-868e51f52b64", "properties": { "SubSubSubDate": { - "type": "string", - "format": "date", - "title": "SubSubSubDate" + "uid": "361a2e90-c32d-421d-8077-6a412436f031", + "$ref": "#/definitions/date" }, "SubSubSubPassword": { - "type": "string", - "password": true, - "title": "SubSubSubPassword" + "uid": "9901ea3d-4e0f-4421-981e-22fbc0d35cc6", + "$ref": "#/definitions/password" }, "SubSubSubAny": { - "uid": "99fd32c0-7ce9-4c22-bb85-890160827365", - "title": "SubSubSubAny" + "uid": "99fd32c0-7ce9-4c22-bb85-890160827365" } - }, - "title": "SubSubStringType" + } } - }, - "title": "SubStringType" + } } - }, - "title": "StringType" + } } }, "required": [ @@ -158,18 +152,17 @@ "definitions": { "password": { "type": "string", - "password": true, - "title": "password" + "password": true }, "date": { "type": "string", - "format": "date", - "title": "date" + "format": "date" } }, - "version": 1, - "description": "" - } + "version": 1 + }, + "identifier": "importProcess_Complex_DataType", + "valid": true } ] } \ No newline at end of file diff --git a/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.json b/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.json index 13169e36..19d87987 100644 --- a/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.json +++ b/tests/bookshop/srv/workflows/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.json @@ -1,9 +1,10 @@ { "uid": "0fc541c5-5266-458e-801a-9fe7fbfc32e5", "name": "ImportProcess_Simple_Inputs", - "identifier": "importProcess_Simple_Inputs", + "description": "", "type": "bpi.process", - "projectId": "eu12.cdsmunich.capprocesspluginhybridtest", + "createdAt": "2026-03-18T10:22:42.550371Z", + "updatedAt": "2026-03-18T10:25:09.164955Z", "header": { "inputs": { "title": "inputs", @@ -12,28 +13,23 @@ "definitions": { "date": { "type": "string", - "format": "date", - "title": "date" + "format": "date" }, "dateTime": { "type": "string", - "format": "date-time", - "title": "dateTime" + "format": "date-time" }, "password": { "type": "string", - "password": true, - "title": "password" + "password": true }, "time": { "type": "string", - "format": "time", - "title": "time" + "format": "time" }, "documentFolder": { "type": "string", - "format": "document-folder", - "title": "documentFolder" + "format": "document-folder" } }, "properties": { @@ -53,20 +49,17 @@ "description": "" }, "date": { - "type": "string", - "format": "date", + "$ref": "#/definitions/date", "title": "Date", "description": "" }, "datetime": { - "type": "string", - "format": "date-time", + "$ref": "#/definitions/dateTime", "title": "DateTime", "description": "" }, "documentfolder": { - "type": "string", - "format": "document-folder", + "$ref": "#/definitions/documentFolder", "title": "DocumentFolder", "description": "" } @@ -87,9 +80,14 @@ "properties": {} }, "processAttributes": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "processAttributes", "type": "object", "properties": {}, "required": [] } - } + }, + "identifier": "importProcess_Simple_Inputs", + "valid": true, + "projectId": "eu12.cdsmunich.capprocesspluginhybridtest" } \ No newline at end of file diff --git a/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.cds b/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.cds index c11851f6..98238ff5 100644 --- a/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.cds +++ b/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.cds @@ -1,4 +1,4 @@ -/* checksum : b2be28c9da2617d511526b2f68e5e6b0 */ +/* checksum : 174881d9ba3b813f1b588771022a248c */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @@ -29,6 +29,11 @@ service ImportProcess_Attributes_And_OutputsService { StringType : ImportProcess_Complex_DataType_StringType; }; + type UpdateStatusResult { + id : String; + success : Boolean; + }; + type ProcessInputs_complexe_Array : many ImportProcess_Complex_DataType; type ProcessInputs { @@ -84,6 +89,12 @@ service ImportProcess_Attributes_And_OutputsService { status : many String ) returns ProcessInstances; + function updateInstanceStatus( + instanceId : String not null, + status : String not null, + cascade : Boolean + ) returns UpdateStatusResult; + action suspend( businessKey : String not null, cascade : Boolean diff --git a/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.cds b/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.cds index 93483826..8ca39df7 100644 --- a/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.cds +++ b/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.cds @@ -1,4 +1,4 @@ -/* checksum : b0ced28bb4d1bef714f6714bff14642e */ +/* checksum : 51e0b15fafd9e8341ce207e93eb99b1f */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @@ -29,6 +29,11 @@ service ImportProcess_Complex_InputsService { StringType : ImportProcess_Complex_DataType_StringType; }; + type UpdateStatusResult { + id : String; + success : Boolean; + }; + type ProcessInputs_complexlist_Array : many String; type ProcessInputs { @@ -76,6 +81,12 @@ service ImportProcess_Complex_InputsService { status : many String ) returns ProcessInstances; + function updateInstanceStatus( + instanceId : String not null, + status : String not null, + cascade : Boolean + ) returns UpdateStatusResult; + action suspend( businessKey : String not null, cascade : Boolean diff --git a/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.cds b/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.cds index d26fccaf..4c5e36f4 100644 --- a/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.cds +++ b/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.cds @@ -1,10 +1,15 @@ -/* checksum : 7e054e53e107a7f5c8375eb51a454e7f */ +/* checksum : 8135be4a2f3327de91ef1e82acf027cb */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @protocol : 'none' @bpm.process : 'eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs' service ImportProcess_Simple_InputsService { + type UpdateStatusResult { + id : String; + success : Boolean; + }; + type ProcessInputs { string : String not null; number : DecimalFloat not null; @@ -53,6 +58,12 @@ service ImportProcess_Simple_InputsService { status : many String ) returns ProcessInstances; + function updateInstanceStatus( + instanceId : String not null, + status : String not null, + cascade : Boolean + ) returns UpdateStatusResult; + action suspend( businessKey : String not null, cascade : Boolean From ecba0570d6ca3dd93444ab43725c0f5e9b10b665 Mon Sep 17 00:00:00 2001 From: Til Weber Date: Tue, 12 May 2026 16:34:07 +0200 Subject: [PATCH 3/8] tests --- tests/bookshop/srv/programmatic-service.cds | 5 ++ tests/bookshop/srv/programmatic-service.ts | 10 +++ .../genericProgrammaticApproach.test.ts | 69 +++++++++++++++++++ .../integration/programmaticApproach.test.ts | 27 ++++++++ 4 files changed, 111 insertions(+) diff --git a/tests/bookshop/srv/programmatic-service.cds b/tests/bookshop/srv/programmatic-service.cds index 90685fe4..99ef1e8f 100644 --- a/tests/bookshop/srv/programmatic-service.cds +++ b/tests/bookshop/srv/programmatic-service.cds @@ -50,4 +50,9 @@ service ProgrammaticService { status: many String) returns many ProcessInstance; action genericGetAttributes(processInstanceId: String) returns many ProcessAttribute; action genericGetOutputs(processInstanceId: String) returns ProcessOutputs; + + type UpdateStatusResult { id: String; success: Boolean; }; + action genericUpdateInstanceStatus(instanceId: String, status: String, cascade: Boolean) returns UpdateStatusResult; + + action updateInstanceStatusViaProcess(instanceId: String, status: String) returns UpdateStatusResult; } diff --git a/tests/bookshop/srv/programmatic-service.ts b/tests/bookshop/srv/programmatic-service.ts index d038f650..9972375e 100644 --- a/tests/bookshop/srv/programmatic-service.ts +++ b/tests/bookshop/srv/programmatic-service.ts @@ -148,6 +148,16 @@ class ProgrammaticService extends cds.ApplicationService { return result; }); + this.on('genericUpdateInstanceStatus', async (req: cds.Request) => { + const { instanceId, status, cascade } = req.data; + return processService.send('updateInstanceStatus', { instanceId, status, cascade }); + }); + + this.on('updateInstanceStatusViaProcess', async (req: cds.Request) => { + const { instanceId, status } = req.data; + return programmaticLifecycleProcess.send('updateInstanceStatus', { instanceId, status }); + }); + await super.init(); } } diff --git a/tests/integration/genericProgrammaticApproach.test.ts b/tests/integration/genericProgrammaticApproach.test.ts index eb939e3f..cbba3b61 100644 --- a/tests/integration/genericProgrammaticApproach.test.ts +++ b/tests/integration/genericProgrammaticApproach.test.ts @@ -221,4 +221,73 @@ describe('Generic ProcessService Integration Tests', () => { expect(foundMessages[0].data.context.number).toEqual(42); }); }); + + describe('Update Instance Status', () => { + async function getInstanceId(businessKey: string): Promise { + const res = await POST('/odata/v4/programmatic/genericGetInstancesByBusinessKey', { + businessKey, + }); + return res.data.value[0].id; + } + + async function updateInstanceStatus(instanceId: string, status: string, cascade?: boolean) { + return POST('/odata/v4/programmatic/genericUpdateInstanceStatus', { + instanceId, + status, + cascade, + }); + } + + it('should return { id, success: true } when updating a valid instance', async () => { + const businessKey = generateID(); + await genericStart(businessKey); + await (cds as any).flush(); + + const instanceId = await getInstanceId(businessKey); + const response = await updateInstanceStatus(instanceId, 'SUSPENDED'); + + expect(response.status).toBe(200); + expect(response.data.id).toBe(instanceId); + expect(response.data.success).toBe(true); + }); + + it('should reflect the new status when queried after update', async () => { + const businessKey = generateID(); + await genericStart(businessKey); + await (cds as any).flush(); + + const instanceId = await getInstanceId(businessKey); + await updateInstanceStatus(instanceId, 'SUSPENDED'); + + const res = await POST('/odata/v4/programmatic/genericGetInstancesByBusinessKey', { + businessKey, + status: ['SUSPENDED'], + }); + expect(res.data.value.some((i: any) => i.id === instanceId)).toBe(true); + }); + + it('should return 400 for an invalid status value', async () => { + const businessKey = generateID(); + await genericStart(businessKey); + await (cds as any).flush(); + + const instanceId = await getInstanceId(businessKey); + + try { + await updateInstanceStatus(instanceId, 'INVALID'); + fail('Expected request to be rejected'); + } catch (error: any) { + expect(error.response.status).toBe(400); + } + }); + + it('should return 404 for a non-existent instance ID', async () => { + try { + await updateInstanceStatus('non-existent-id', 'SUSPENDED'); + fail('Expected request to be rejected'); + } catch (error: any) { + expect(error.response.status).toBe(404); + } + }); + }); }); diff --git a/tests/integration/programmaticApproach.test.ts b/tests/integration/programmaticApproach.test.ts index 29d6bb3c..871b7e07 100644 --- a/tests/integration/programmaticApproach.test.ts +++ b/tests/integration/programmaticApproach.test.ts @@ -238,4 +238,31 @@ describe('Programmatic Approach Integration Tests', () => { expect(foundMessages[0].data.context.optional_datetime).toEqual(optional_datetime); }); }); + + describe('Update Instance Status via imported process service', () => { + it('should update instance status and return { id, success: true }', async () => { + const ID = generateID(); + await POST('/odata/v4/programmatic/genericStart', { + definitionId: 'eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Lifecycle_Process', + businessKey: ID, + context: JSON.stringify({ ID }), + }); + await (cds as any).flush(); + + const instancesRes = await POST('/odata/v4/programmatic/genericGetInstancesByBusinessKey', { + businessKey: ID, + status: ['RUNNING'], + }); + const instanceId = instancesRes.data.value[0].id; + + const response = await POST('/odata/v4/programmatic/updateInstanceStatusViaProcess', { + instanceId, + status: 'SUSPENDED', + }); + + expect(response.status).toBe(200); + expect(response.data.id).toBe(instanceId); + expect(response.data.success).toBe(true); + }); + }); }); From 18c131772a03abfc93c06ab0725442d8200ce1e0 Mon Sep 17 00:00:00 2001 From: Til Weber Date: Tue, 12 May 2026 16:37:36 +0200 Subject: [PATCH 4/8] changelog entry --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index de45ef46..a440944b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ - The format is based on [Keep a Changelog](https://keepachangelog.com/). - This project adheres to [Semantic Versioning](https://semver.org/). +## Version 0.2.2 - tbd + +### Added + +- `updateInstanceStatus` function on `ProcessService` and imported process services to update a workflow instance by its ID to a given status (`RUNNING`, `SUSPENDED`, `CANCELED`, `ERRONEOUS`, `COMPLETED`), with optional `cascade` support + ## Version 0.2.1 - 2026-04-20 ### Fixed From b3a6ea6c7c12ee0ca2ceb63c04e6594abb200a03 Mon Sep 17 00:00:00 2001 From: Til Weber Date: Wed, 13 May 2026 10:25:59 +0200 Subject: [PATCH 5/8] test fix --- .../genericProgrammaticApproach.test.ts | 11 +++++---- .../integration/programmaticApproach.test.ts | 24 ++++++++++++++----- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/tests/integration/genericProgrammaticApproach.test.ts b/tests/integration/genericProgrammaticApproach.test.ts index cbba3b61..6e8463c9 100644 --- a/tests/integration/genericProgrammaticApproach.test.ts +++ b/tests/integration/genericProgrammaticApproach.test.ts @@ -223,11 +223,15 @@ describe('Generic ProcessService Integration Tests', () => { }); describe('Update Instance Status', () => { - async function getInstanceId(businessKey: string): Promise { + async function getInstanceId(businessKey: string, maxRetries = 10): Promise { + await (cds as any).flush(); const res = await POST('/odata/v4/programmatic/genericGetInstancesByBusinessKey', { businessKey, }); - return res.data.value[0].id; + if (res.data.value?.length > 0) return res.data.value[0].id; + if (maxRetries <= 0) throw new Error(`No instance found for businessKey: ${businessKey}`); + await new Promise((r) => setTimeout(r, 1000)); + return getInstanceId(businessKey, maxRetries - 1); } async function updateInstanceStatus(instanceId: string, status: string, cascade?: boolean) { @@ -241,7 +245,6 @@ describe('Generic ProcessService Integration Tests', () => { it('should return { id, success: true } when updating a valid instance', async () => { const businessKey = generateID(); await genericStart(businessKey); - await (cds as any).flush(); const instanceId = await getInstanceId(businessKey); const response = await updateInstanceStatus(instanceId, 'SUSPENDED'); @@ -254,7 +257,6 @@ describe('Generic ProcessService Integration Tests', () => { it('should reflect the new status when queried after update', async () => { const businessKey = generateID(); await genericStart(businessKey); - await (cds as any).flush(); const instanceId = await getInstanceId(businessKey); await updateInstanceStatus(instanceId, 'SUSPENDED'); @@ -269,7 +271,6 @@ describe('Generic ProcessService Integration Tests', () => { it('should return 400 for an invalid status value', async () => { const businessKey = generateID(); await genericStart(businessKey); - await (cds as any).flush(); const instanceId = await getInstanceId(businessKey); diff --git a/tests/integration/programmaticApproach.test.ts b/tests/integration/programmaticApproach.test.ts index 871b7e07..8c909b64 100644 --- a/tests/integration/programmaticApproach.test.ts +++ b/tests/integration/programmaticApproach.test.ts @@ -240,6 +240,23 @@ describe('Programmatic Approach Integration Tests', () => { }); describe('Update Instance Status via imported process service', () => { + async function waitForInstance( + businessKey: string, + status: string[], + maxRetries = 10, + ): Promise { + await (cds as any).flush(); + const res = await POST('/odata/v4/programmatic/genericGetInstancesByBusinessKey', { + businessKey, + status, + }); + if (res.data.value?.length > 0) return res.data.value[0].id; + if (maxRetries <= 0) + throw new Error(`No instance found for businessKey: ${businessKey} with status: ${status}`); + await new Promise((r) => setTimeout(r, 1000)); + return waitForInstance(businessKey, status, maxRetries - 1); + } + it('should update instance status and return { id, success: true }', async () => { const ID = generateID(); await POST('/odata/v4/programmatic/genericStart', { @@ -247,13 +264,8 @@ describe('Programmatic Approach Integration Tests', () => { businessKey: ID, context: JSON.stringify({ ID }), }); - await (cds as any).flush(); - const instancesRes = await POST('/odata/v4/programmatic/genericGetInstancesByBusinessKey', { - businessKey: ID, - status: ['RUNNING'], - }); - const instanceId = instancesRes.data.value[0].id; + const instanceId = await waitForInstance(ID, ['RUNNING']); const response = await POST('/odata/v4/programmatic/updateInstanceStatusViaProcess', { instanceId, From cc4f5caed3bc3a881994455bcb1c4ba2a60fb0e8 Mon Sep 17 00:00:00 2001 From: Til Weber Date: Wed, 13 May 2026 13:38:34 +0200 Subject: [PATCH 6/8] make update instance action --- lib/handlers/index.ts | 2 +- lib/handlers/processService.ts | 13 +++++++++--- lib/handlers/utils.ts | 13 ++++++++++-- lib/processImport/csnBuilder.ts | 14 +------------ lib/types/csn-extensions.ts | 7 ++++++- srv/BTPProcessService.cds | 16 ++++++-------- srv/BTPProcessService.ts | 17 +-------------- srv/localProcessService.ts | 17 +-------------- ...ybridtest.annotation_Lifecycle_Process.cds | 11 +++------- ...dtest.annotation_Lifecycle_Process_Two.cds | 11 +++------- ...t.importProcess_Attributes_And_Outputs.cds | 11 +++------- ...ybridtest.importProcess_Complex_Inputs.cds | 11 +++------- ...hybridtest.importProcess_Simple_Inputs.cds | 11 +++------- ...ridtest.programmatic_Lifecycle_Process.cds | 11 +++------- ...hybridtest.programmatic_Output_Process.cds | 11 +++------- tests/bookshop/srv/programmatic-service.cds | 5 ++--- tests/bookshop/srv/programmatic-service.ts | 19 +++++++++++++++-- ...t.importProcess_Attributes_And_Outputs.cds | 11 +++------- ...ybridtest.importProcess_Complex_Inputs.cds | 11 +++------- ...hybridtest.importProcess_Simple_Inputs.cds | 11 +++------- .../genericProgrammaticApproach.test.ts | 17 ++++----------- .../integration/programmaticApproach.test.ts | 21 +++++++++++++++---- 22 files changed, 107 insertions(+), 164 deletions(-) diff --git a/lib/handlers/index.ts b/lib/handlers/index.ts index bcad0e21..b697e51e 100644 --- a/lib/handlers/index.ts +++ b/lib/handlers/index.ts @@ -6,5 +6,5 @@ export { createProcessActionHandler } from './processActionHandler'; export { registerProcessServiceHandlers } from './processService'; export { buildAnnotationCache } from './annotationCache'; export { registerAnnotationHandlers } from './annotationHandlers'; -export type { EntityRow, ProcessStartPayload, ProcessLifecyclePayload } from './utils'; +export type { EntityRow, ProcessStartPayload, ProcessLifecyclePayload, ProcessUpdateStatusPayload } from './utils'; export type { ProcessDeleteRequest } from './onDeleteUtils'; diff --git a/lib/handlers/processService.ts b/lib/handlers/processService.ts index 4010140e..80cc9a5d 100644 --- a/lib/handlers/processService.ts +++ b/lib/handlers/processService.ts @@ -1,6 +1,6 @@ import cds from '@sap/cds'; import { PROCESS_LOGGER_PREFIX, PROCESS_PREFIX, PROCESS_SERVICE } from '../constants'; -import { emitProcessEvent, ProcessLifecyclePayload, ProcessStartPayload } from './utils'; +import { emitProcessEvent, ProcessLifecyclePayload, ProcessStartPayload, ProcessUpdateStatusPayload } from './utils'; import { WorkflowStatus } from '../api'; const LOG = cds.log(PROCESS_LOGGER_PREFIX); @@ -192,7 +192,14 @@ function registerUpdateInstanceStatusHandler(service: cds.Service, definitionId: }); } - const processService = await cds.connect.to(PROCESS_SERVICE); - return processService.send('updateInstanceStatus', { instanceId, status, cascade }); + const payload: ProcessUpdateStatusPayload = { instanceId, status, cascade: cascade ?? false }; + await emitProcessEvent( + 'updateInstanceStatus', + req, + payload, + `Failed to update instance status for instanceId: ${instanceId}`, + ); + + LOG.debug(`Instance status update queued: instanceId=${instanceId}, status=${status}`); }); } diff --git a/lib/handlers/utils.ts b/lib/handlers/utils.ts index 52ad4dc1..a0f88002 100644 --- a/lib/handlers/utils.ts +++ b/lib/handlers/utils.ts @@ -6,7 +6,7 @@ const LOG = cds.log(PROCESS_LOGGER_PREFIX); /** * Process event types supported by the system */ -type ProcessEventType = 'start' | 'cancel' | 'suspend' | 'resume'; +type ProcessEventType = 'start' | 'cancel' | 'suspend' | 'resume' | 'updateInstanceStatus'; /** * A row of entity data with string-keyed fields @@ -31,6 +31,15 @@ export interface ProcessLifecyclePayload { cascade: boolean; } +/** + * Payload for updateInstanceStatus events + */ +export interface ProcessUpdateStatusPayload { + instanceId: string; + status: string; + cascade: boolean; +} + async function fetchEntity( results: EntityRow, request: cds.Request, @@ -164,7 +173,7 @@ export async function resolveEntityRowOrReject( export async function emitProcessEvent( event: ProcessEventType, req: cds.Request, - payload: ProcessStartPayload | ProcessLifecyclePayload, + payload: ProcessStartPayload | ProcessLifecyclePayload | ProcessUpdateStatusPayload, processEventFailedMsg: string, businessKeyValue?: string, ): Promise { diff --git a/lib/processImport/csnBuilder.ts b/lib/processImport/csnBuilder.ts index d00b5c95..4f92b979 100644 --- a/lib/processImport/csnBuilder.ts +++ b/lib/processImport/csnBuilder.ts @@ -85,16 +85,6 @@ function addProcessTypes( const instanceName = fqn(serviceName, 'ProcessInstance'); const instancesName = fqn(serviceName, 'ProcessInstances'); - const updateStatusResultName = fqn(serviceName, 'UpdateStatusResult'); - definitions[updateStatusResultName] = { - kind: 'type', - name: updateStatusResultName, - elements: { - id: { type: csn.CdsBuiltinType.String }, - success: { type: csn.CdsBuiltinType.Boolean }, - }, - }; - definitions[inputsName] = buildTypeFromSchema( inputsName, ensureObjectSchema(process.header?.inputs), @@ -159,7 +149,6 @@ function addProcessActions( const outputsType = fqn(serviceName, 'ProcessOutputs'); const attributesType = fqn(serviceName, 'ProcessAttributes'); const instancesType = fqn(serviceName, 'ProcessInstances'); - const updateStatusResultType = fqn(serviceName, 'UpdateStatusResult'); // Start action — three tiers: // 1. No input properties: start() with no params @@ -213,14 +202,13 @@ function addProcessActions( }; definitions[fqn(serviceName, 'updateInstanceStatus')] = { - kind: 'function', + kind: 'action', name: fqn(serviceName, 'updateInstanceStatus'), params: { instanceId: { type: csn.CdsBuiltinType.String, notNull: true }, status: { type: csn.CdsBuiltinType.String, notNull: true }, cascade: { type: csn.CdsBuiltinType.Boolean }, }, - returns: { type: updateStatusResultType }, }; // Lifecycle actions diff --git a/lib/types/csn-extensions.ts b/lib/types/csn-extensions.ts index 9de4c4ff..3c9083fc 100644 --- a/lib/types/csn-extensions.ts +++ b/lib/types/csn-extensions.ts @@ -58,11 +58,12 @@ export type CsnDefinition = | CsnService | CsnAction | CsnFunction + | CsnEvent | CsnAnnotation; export interface CsnBaseDefinition extends CsnAnnotations { name?: string; doc?: string; - kind: 'entity' | 'type' | 'service' | 'action' | 'function' | 'annotation'; + kind: 'entity' | 'type' | 'service' | 'action' | 'function' | 'event' | 'annotation'; } // // ────────────────────────────────────────────────────────────── @@ -132,6 +133,10 @@ export interface CsnFunction extends CsnBaseDefinition { params?: Record; returns?: CsnType | CsnElement; } +export interface CsnEvent extends CsnBaseDefinition { + kind: 'event'; + elements?: Record; +} // // ────────────────────────────────────────────────────────────── // ANNOTATION diff --git a/srv/BTPProcessService.cds b/srv/BTPProcessService.cds index 75b27460..04a99d52 100644 --- a/srv/BTPProcessService.cds +++ b/srv/BTPProcessService.cds @@ -2,10 +2,6 @@ type AnyType {} type AttributesReturn : many AnyType; type InstancesReturn : many AnyType; -type UpdateStatusResult { - id : String(256); - success : Boolean; -} @protocol: 'none' @impl: './BTPProcessService' @@ -31,6 +27,12 @@ service ProcessService { cascade : Boolean } + event updateInstanceStatus { + @mandatory instanceId : String(256); + @mandatory status : String(256); + cascade : Boolean + } + function getAttributes( @mandatory processInstanceId : String(256) )returns AttributesReturn; @@ -43,11 +45,5 @@ service ProcessService { @mandatory businessKey : String(256), status : many String(256) )returns InstancesReturn; - - function updateInstanceStatus( - @mandatory instanceId : String(256), - @mandatory status : String(256), - cascade : Boolean - ) returns UpdateStatusResult; } diff --git a/srv/BTPProcessService.ts b/srv/BTPProcessService.ts index 46a20935..88f195f0 100644 --- a/srv/BTPProcessService.ts +++ b/srv/BTPProcessService.ts @@ -144,23 +144,8 @@ class ProcessService extends cds.ApplicationService { this.on('updateInstanceStatus', async (request: cds.Request) => { const { instanceId, status, cascade } = request.data; - - if (!instanceId) { - return request.reject({ status: 400, message: 'Missing required parameter: instanceId' }); - } - if (!status) { - return request.reject({ status: 400, message: 'Missing required parameter: status' }); - } - const validStatuses = Object.values(WorkflowStatus); - if (!validStatuses.includes(status as WorkflowStatus)) { - return request.reject({ - status: 400, - message: `Invalid status: ${status}. Valid values are: ${validStatuses.join(', ')}`, - }); - } - LOG.info('Updating instance status', instanceId, '->', status); - return this.workflowInstanceClient.updateWorkflowStatus( + await this.workflowInstanceClient.updateWorkflowStatus( instanceId, status as WorkflowStatus, cascade ?? false, diff --git a/srv/localProcessService.ts b/srv/localProcessService.ts index 4ecf4016..73586d9d 100644 --- a/srv/localProcessService.ts +++ b/srv/localProcessService.ts @@ -223,29 +223,14 @@ class ProcessService extends cds.ApplicationService { `==============================================================`, ); - if (!instanceId) { - return req.reject({ status: 400, message: 'Missing required parameter: instanceId' }); - } - if (!status) { - return req.reject({ status: 400, message: 'Missing required parameter: status' }); - } - const validStatuses = Object.values(WorkflowStatus); - if (!validStatuses.includes(status as WorkflowStatus)) { - return req.reject({ - status: 400, - message: `Invalid status: ${status}. Valid values are: ${validStatuses.join(', ')}`, - }); - } - const result = localWorkflowStore.updateStatus(instanceId, status as WorkflowStatus); if (!result.success) { LOG.warn(`Workflow instance not found: ${instanceId}`); - return req.reject({ status: 404, message: 'Workflow instance not found' }); + return; } LOG.debug(`Updated status for instance: ${instanceId} to ${status}`); - return result; }); return super.init(); diff --git a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process.cds b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process.cds index 224e0739..891bb0b1 100644 --- a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process.cds +++ b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process.cds @@ -1,15 +1,10 @@ -/* checksum : 929834125a8e086a53c589f6745b7d1e */ +/* checksum : 3f6145d8ec63b84aa16c320a4868fcd9 */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @protocol : 'none' @bpm.process : 'eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process' service Annotation_Lifecycle_ProcessService { - type UpdateStatusResult { - id : String; - success : Boolean; - }; - type ProcessInputs { ID : String not null; }; @@ -53,11 +48,11 @@ service Annotation_Lifecycle_ProcessService { status : many String ) returns ProcessInstances; - function updateInstanceStatus( + action updateInstanceStatus( instanceId : String not null, status : String not null, cascade : Boolean - ) returns UpdateStatusResult; + ); action suspend( businessKey : String not null, diff --git a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process_Two.cds b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process_Two.cds index 3ab3886c..38f5c322 100644 --- a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process_Two.cds +++ b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process_Two.cds @@ -1,15 +1,10 @@ -/* checksum : d597187f9ac06787e1d29604bbdd3030 */ +/* checksum : 85ab0177d589131840ae18bca7300e41 */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @protocol : 'none' @bpm.process : 'eu12.cdsmunich.capprocesspluginhybridtest.annotation_Lifecycle_Process_Two' service Annotation_Lifecycle_Process_TwoService { - type UpdateStatusResult { - id : String; - success : Boolean; - }; - type ProcessInputs { id : String not null; }; @@ -53,11 +48,11 @@ service Annotation_Lifecycle_Process_TwoService { status : many String ) returns ProcessInstances; - function updateInstanceStatus( + action updateInstanceStatus( instanceId : String not null, status : String not null, cascade : Boolean - ) returns UpdateStatusResult; + ); action suspend( businessKey : String not null, diff --git a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.cds b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.cds index 98238ff5..23a2fb6d 100644 --- a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.cds +++ b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.cds @@ -1,4 +1,4 @@ -/* checksum : 174881d9ba3b813f1b588771022a248c */ +/* checksum : de8a712854c579e0f68840ddc6c7e1da */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @@ -29,11 +29,6 @@ service ImportProcess_Attributes_And_OutputsService { StringType : ImportProcess_Complex_DataType_StringType; }; - type UpdateStatusResult { - id : String; - success : Boolean; - }; - type ProcessInputs_complexe_Array : many ImportProcess_Complex_DataType; type ProcessInputs { @@ -89,11 +84,11 @@ service ImportProcess_Attributes_And_OutputsService { status : many String ) returns ProcessInstances; - function updateInstanceStatus( + action updateInstanceStatus( instanceId : String not null, status : String not null, cascade : Boolean - ) returns UpdateStatusResult; + ); action suspend( businessKey : String not null, diff --git a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.cds b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.cds index 8ca39df7..bb3a5ac2 100644 --- a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.cds +++ b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.cds @@ -1,4 +1,4 @@ -/* checksum : 51e0b15fafd9e8341ce207e93eb99b1f */ +/* checksum : c7adfadbf57db190b8e967f4eabf8094 */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @@ -29,11 +29,6 @@ service ImportProcess_Complex_InputsService { StringType : ImportProcess_Complex_DataType_StringType; }; - type UpdateStatusResult { - id : String; - success : Boolean; - }; - type ProcessInputs_complexlist_Array : many String; type ProcessInputs { @@ -81,11 +76,11 @@ service ImportProcess_Complex_InputsService { status : many String ) returns ProcessInstances; - function updateInstanceStatus( + action updateInstanceStatus( instanceId : String not null, status : String not null, cascade : Boolean - ) returns UpdateStatusResult; + ); action suspend( businessKey : String not null, diff --git a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.cds b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.cds index 4c5e36f4..8679f701 100644 --- a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.cds +++ b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.cds @@ -1,15 +1,10 @@ -/* checksum : 8135be4a2f3327de91ef1e82acf027cb */ +/* checksum : 980ec47ccf335af79644904cc3144504 */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @protocol : 'none' @bpm.process : 'eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs' service ImportProcess_Simple_InputsService { - type UpdateStatusResult { - id : String; - success : Boolean; - }; - type ProcessInputs { string : String not null; number : DecimalFloat not null; @@ -58,11 +53,11 @@ service ImportProcess_Simple_InputsService { status : many String ) returns ProcessInstances; - function updateInstanceStatus( + action updateInstanceStatus( instanceId : String not null, status : String not null, cascade : Boolean - ) returns UpdateStatusResult; + ); action suspend( businessKey : String not null, diff --git a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Lifecycle_Process.cds b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Lifecycle_Process.cds index c01a3acb..a123baa3 100644 --- a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Lifecycle_Process.cds +++ b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Lifecycle_Process.cds @@ -1,15 +1,10 @@ -/* checksum : e3d3d8b3c8826decf15f5b31ac2cd307 */ +/* checksum : a2b3149ef0e5da23a6b84e0c53043379 */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @protocol : 'none' @bpm.process : 'eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Lifecycle_Process' service Programmatic_Lifecycle_ProcessService { - type UpdateStatusResult { - id : String; - success : Boolean; - }; - type ProcessInputs { ID : String not null; attribute1 : String; @@ -55,11 +50,11 @@ service Programmatic_Lifecycle_ProcessService { status : many String ) returns ProcessInstances; - function updateInstanceStatus( + action updateInstanceStatus( instanceId : String not null, status : String not null, cascade : Boolean - ) returns UpdateStatusResult; + ); action suspend( businessKey : String not null, diff --git a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Output_Process.cds b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Output_Process.cds index ac1e3bad..8eb277ac 100644 --- a/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Output_Process.cds +++ b/tests/bookshop/srv/external/eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Output_Process.cds @@ -1,15 +1,10 @@ -/* checksum : ff7f2f85755a461155203b777bf3077a */ +/* checksum : 6fb3b83b7c518c1c26dd8b55ce090cc4 */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @protocol : 'none' @bpm.process : 'eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Output_Process' service Programmatic_Output_ProcessService { - type UpdateStatusResult { - id : String; - success : Boolean; - }; - type ProcessInputs { ID : String not null; mandatory_datetime : Timestamp not null; @@ -62,11 +57,11 @@ service Programmatic_Output_ProcessService { status : many String ) returns ProcessInstances; - function updateInstanceStatus( + action updateInstanceStatus( instanceId : String not null, status : String not null, cascade : Boolean - ) returns UpdateStatusResult; + ); action suspend( businessKey : String not null, diff --git a/tests/bookshop/srv/programmatic-service.cds b/tests/bookshop/srv/programmatic-service.cds index 99ef1e8f..1c605096 100644 --- a/tests/bookshop/srv/programmatic-service.cds +++ b/tests/bookshop/srv/programmatic-service.cds @@ -51,8 +51,7 @@ service ProgrammaticService { action genericGetAttributes(processInstanceId: String) returns many ProcessAttribute; action genericGetOutputs(processInstanceId: String) returns ProcessOutputs; - type UpdateStatusResult { id: String; success: Boolean; }; - action genericUpdateInstanceStatus(instanceId: String, status: String, cascade: Boolean) returns UpdateStatusResult; + action genericUpdateInstanceStatus(instanceId: String, status: String, cascade: Boolean); - action updateInstanceStatusViaProcess(instanceId: String, status: String) returns UpdateStatusResult; + action updateInstanceStatusViaProcess(instanceId: String, status: String); } diff --git a/tests/bookshop/srv/programmatic-service.ts b/tests/bookshop/srv/programmatic-service.ts index 9972375e..0fad3379 100644 --- a/tests/bookshop/srv/programmatic-service.ts +++ b/tests/bookshop/srv/programmatic-service.ts @@ -1,6 +1,7 @@ import cds from '@sap/cds'; import Programmatic_Lifecycle_ProcessService from '#cds-models/eu12/cdsmunich/capprocesspluginhybridtest/Programmatic_Lifecycle_ProcessService'; import Programmatic_Outputs_ProcessService from '#cds-models/eu12/cdsmunich/capprocesspluginhybridtest/Programmatic_Output_ProcessService'; +import { WorkflowStatus } from '@cap-js/process/lib/api'; class ProgrammaticService extends cds.ApplicationService { async init() { @@ -150,12 +151,26 @@ class ProgrammaticService extends cds.ApplicationService { this.on('genericUpdateInstanceStatus', async (req: cds.Request) => { const { instanceId, status, cascade } = req.data; - return processService.send('updateInstanceStatus', { instanceId, status, cascade }); + const validStatuses = Object.values(WorkflowStatus); + if (!validStatuses.includes(status as WorkflowStatus)) { + return req.reject(400, `Invalid status: ${status}. Valid values are: ${validStatuses.join(', ')}`); + } + const queuedProcessService = cds.queued(processService); + await queuedProcessService.emit('updateInstanceStatus', { + instanceId, + status, + cascade: cascade ?? false, + }); }); this.on('updateInstanceStatusViaProcess', async (req: cds.Request) => { const { instanceId, status } = req.data; - return programmaticLifecycleProcess.send('updateInstanceStatus', { instanceId, status }); + const queuedProgrammaticLifecycle = cds.queued(programmaticLifecycleProcess); + await queuedProgrammaticLifecycle.emit('updateInstanceStatus', { + instanceId, + status, + cascade: false, + }); }); await super.init(); diff --git a/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.cds b/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.cds index 98238ff5..23a2fb6d 100644 --- a/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.cds +++ b/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Attributes_And_Outputs.cds @@ -1,4 +1,4 @@ -/* checksum : 174881d9ba3b813f1b588771022a248c */ +/* checksum : de8a712854c579e0f68840ddc6c7e1da */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @@ -29,11 +29,6 @@ service ImportProcess_Attributes_And_OutputsService { StringType : ImportProcess_Complex_DataType_StringType; }; - type UpdateStatusResult { - id : String; - success : Boolean; - }; - type ProcessInputs_complexe_Array : many ImportProcess_Complex_DataType; type ProcessInputs { @@ -89,11 +84,11 @@ service ImportProcess_Attributes_And_OutputsService { status : many String ) returns ProcessInstances; - function updateInstanceStatus( + action updateInstanceStatus( instanceId : String not null, status : String not null, cascade : Boolean - ) returns UpdateStatusResult; + ); action suspend( businessKey : String not null, diff --git a/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.cds b/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.cds index 8ca39df7..bb3a5ac2 100644 --- a/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.cds +++ b/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Complex_Inputs.cds @@ -1,4 +1,4 @@ -/* checksum : 51e0b15fafd9e8341ce207e93eb99b1f */ +/* checksum : c7adfadbf57db190b8e967f4eabf8094 */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @@ -29,11 +29,6 @@ service ImportProcess_Complex_InputsService { StringType : ImportProcess_Complex_DataType_StringType; }; - type UpdateStatusResult { - id : String; - success : Boolean; - }; - type ProcessInputs_complexlist_Array : many String; type ProcessInputs { @@ -81,11 +76,11 @@ service ImportProcess_Complex_InputsService { status : many String ) returns ProcessInstances; - function updateInstanceStatus( + action updateInstanceStatus( instanceId : String not null, status : String not null, cascade : Boolean - ) returns UpdateStatusResult; + ); action suspend( businessKey : String not null, diff --git a/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.cds b/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.cds index 4c5e36f4..8679f701 100644 --- a/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.cds +++ b/tests/hybrid/importedCDS/eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs.cds @@ -1,15 +1,10 @@ -/* checksum : 8135be4a2f3327de91ef1e82acf027cb */ +/* checksum : 980ec47ccf335af79644904cc3144504 */ namespace eu12.cdsmunich.capprocesspluginhybridtest; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @protocol : 'none' @bpm.process : 'eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs' service ImportProcess_Simple_InputsService { - type UpdateStatusResult { - id : String; - success : Boolean; - }; - type ProcessInputs { string : String not null; number : DecimalFloat not null; @@ -58,11 +53,11 @@ service ImportProcess_Simple_InputsService { status : many String ) returns ProcessInstances; - function updateInstanceStatus( + action updateInstanceStatus( instanceId : String not null, status : String not null, cascade : Boolean - ) returns UpdateStatusResult; + ); action suspend( businessKey : String not null, diff --git a/tests/integration/genericProgrammaticApproach.test.ts b/tests/integration/genericProgrammaticApproach.test.ts index 6e8463c9..5dc9e7f6 100644 --- a/tests/integration/genericProgrammaticApproach.test.ts +++ b/tests/integration/genericProgrammaticApproach.test.ts @@ -242,16 +242,15 @@ describe('Generic ProcessService Integration Tests', () => { }); } - it('should return { id, success: true } when updating a valid instance', async () => { + it('should emit an updateInstanceStatus event to the outbox', async () => { const businessKey = generateID(); await genericStart(businessKey); const instanceId = await getInstanceId(businessKey); const response = await updateInstanceStatus(instanceId, 'SUSPENDED'); - expect(response.status).toBe(200); - expect(response.data.id).toBe(instanceId); - expect(response.data.success).toBe(true); + expect(response.status).toBe(204); + expect(foundMessages.some((m: any) => m.event === 'updateInstanceStatus')).toBe(true); }); it('should reflect the new status when queried after update', async () => { @@ -260,6 +259,7 @@ describe('Generic ProcessService Integration Tests', () => { const instanceId = await getInstanceId(businessKey); await updateInstanceStatus(instanceId, 'SUSPENDED'); + await (cds as any).flush(); const res = await POST('/odata/v4/programmatic/genericGetInstancesByBusinessKey', { businessKey, @@ -281,14 +281,5 @@ describe('Generic ProcessService Integration Tests', () => { expect(error.response.status).toBe(400); } }); - - it('should return 404 for a non-existent instance ID', async () => { - try { - await updateInstanceStatus('non-existent-id', 'SUSPENDED'); - fail('Expected request to be rejected'); - } catch (error: any) { - expect(error.response.status).toBe(404); - } - }); }); }); diff --git a/tests/integration/programmaticApproach.test.ts b/tests/integration/programmaticApproach.test.ts index 8c909b64..a60b5fe4 100644 --- a/tests/integration/programmaticApproach.test.ts +++ b/tests/integration/programmaticApproach.test.ts @@ -257,7 +257,7 @@ describe('Programmatic Approach Integration Tests', () => { return waitForInstance(businessKey, status, maxRetries - 1); } - it('should update instance status and return { id, success: true }', async () => { + it('should emit an updateInstanceStatus event via imported process service', async () => { const ID = generateID(); await POST('/odata/v4/programmatic/genericStart', { definitionId: 'eu12.cdsmunich.capprocesspluginhybridtest.programmatic_Lifecycle_Process', @@ -272,9 +272,22 @@ describe('Programmatic Approach Integration Tests', () => { status: 'SUSPENDED', }); - expect(response.status).toBe(200); - expect(response.data.id).toBe(instanceId); - expect(response.data.success).toBe(true); + expect(response.status).toBe(204); + + // Flush up to 3 times and poll: imported service outbox → ProcessService outbox → handler + let suspended = false; + for (let i = 0; i < 3; i++) { + await (cds as any).flush(); + const res = await POST('/odata/v4/programmatic/genericGetInstancesByBusinessKey', { + businessKey: ID, + status: ['SUSPENDED'], + }); + if (res.data.value.some((j: any) => j.id === instanceId)) { + suspended = true; + break; + } + } + expect(suspended).toBe(true); }); }); }); From 1ef3930b48f6628ba2a60a181a90bbff687e6106 Mon Sep 17 00:00:00 2001 From: Til Weber Date: Wed, 13 May 2026 14:02:20 +0200 Subject: [PATCH 7/8] lint fix --- tests/integration/programmaticApproach.test.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/integration/programmaticApproach.test.ts b/tests/integration/programmaticApproach.test.ts index a60b5fe4..6b306ca5 100644 --- a/tests/integration/programmaticApproach.test.ts +++ b/tests/integration/programmaticApproach.test.ts @@ -275,19 +275,17 @@ describe('Programmatic Approach Integration Tests', () => { expect(response.status).toBe(204); // Flush up to 3 times and poll: imported service outbox → ProcessService outbox → handler - let suspended = false; - for (let i = 0; i < 3; i++) { + async function waitForSuspended(id: string, businessKey: string, maxRetries = 3): Promise { await (cds as any).flush(); const res = await POST('/odata/v4/programmatic/genericGetInstancesByBusinessKey', { - businessKey: ID, + businessKey, status: ['SUSPENDED'], }); - if (res.data.value.some((j: any) => j.id === instanceId)) { - suspended = true; - break; - } + if (res.data.value.some((j: any) => j.id === id)) return true; + if (maxRetries <= 0) return false; + return waitForSuspended(id, businessKey, maxRetries - 1); } - expect(suspended).toBe(true); + expect(await waitForSuspended(instanceId, ID)).toBe(true); }); }); }); From 3c656a9a6be23e3c7cff3735e1da89f5e17d6a2a Mon Sep 17 00:00:00 2001 From: Til Weber Date: Wed, 13 May 2026 14:07:03 +0200 Subject: [PATCH 8/8] prettier --- lib/handlers/index.ts | 7 ++++++- lib/handlers/processService.ts | 7 ++++++- tests/bookshop/srv/programmatic-service.ts | 5 ++++- tests/integration/programmaticApproach.test.ts | 6 +++++- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/handlers/index.ts b/lib/handlers/index.ts index b697e51e..7097f957 100644 --- a/lib/handlers/index.ts +++ b/lib/handlers/index.ts @@ -6,5 +6,10 @@ export { createProcessActionHandler } from './processActionHandler'; export { registerProcessServiceHandlers } from './processService'; export { buildAnnotationCache } from './annotationCache'; export { registerAnnotationHandlers } from './annotationHandlers'; -export type { EntityRow, ProcessStartPayload, ProcessLifecyclePayload, ProcessUpdateStatusPayload } from './utils'; +export type { + EntityRow, + ProcessStartPayload, + ProcessLifecyclePayload, + ProcessUpdateStatusPayload, +} from './utils'; export type { ProcessDeleteRequest } from './onDeleteUtils'; diff --git a/lib/handlers/processService.ts b/lib/handlers/processService.ts index 80cc9a5d..70e9c492 100644 --- a/lib/handlers/processService.ts +++ b/lib/handlers/processService.ts @@ -1,6 +1,11 @@ import cds from '@sap/cds'; import { PROCESS_LOGGER_PREFIX, PROCESS_PREFIX, PROCESS_SERVICE } from '../constants'; -import { emitProcessEvent, ProcessLifecyclePayload, ProcessStartPayload, ProcessUpdateStatusPayload } from './utils'; +import { + emitProcessEvent, + ProcessLifecyclePayload, + ProcessStartPayload, + ProcessUpdateStatusPayload, +} from './utils'; import { WorkflowStatus } from '../api'; const LOG = cds.log(PROCESS_LOGGER_PREFIX); diff --git a/tests/bookshop/srv/programmatic-service.ts b/tests/bookshop/srv/programmatic-service.ts index 0fad3379..7ce1791d 100644 --- a/tests/bookshop/srv/programmatic-service.ts +++ b/tests/bookshop/srv/programmatic-service.ts @@ -153,7 +153,10 @@ class ProgrammaticService extends cds.ApplicationService { const { instanceId, status, cascade } = req.data; const validStatuses = Object.values(WorkflowStatus); if (!validStatuses.includes(status as WorkflowStatus)) { - return req.reject(400, `Invalid status: ${status}. Valid values are: ${validStatuses.join(', ')}`); + return req.reject( + 400, + `Invalid status: ${status}. Valid values are: ${validStatuses.join(', ')}`, + ); } const queuedProcessService = cds.queued(processService); await queuedProcessService.emit('updateInstanceStatus', { diff --git a/tests/integration/programmaticApproach.test.ts b/tests/integration/programmaticApproach.test.ts index 6b306ca5..552c2d28 100644 --- a/tests/integration/programmaticApproach.test.ts +++ b/tests/integration/programmaticApproach.test.ts @@ -275,7 +275,11 @@ describe('Programmatic Approach Integration Tests', () => { expect(response.status).toBe(204); // Flush up to 3 times and poll: imported service outbox → ProcessService outbox → handler - async function waitForSuspended(id: string, businessKey: string, maxRetries = 3): Promise { + async function waitForSuspended( + id: string, + businessKey: string, + maxRetries = 3, + ): Promise { await (cds as any).flush(); const res = await POST('/odata/v4/programmatic/genericGetInstancesByBusinessKey', { businessKey,