@open-mercato/core 0.6.5-develop.4516.1.88e6ab71a9 → 0.6.5-develop.4534.1.b459babe6d
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/step_instance/index.js +2 -0
- package/dist/generated/entities/step_instance/index.js.map +2 -2
- package/dist/generated/entities/user_task/index.js +2 -0
- package/dist/generated/entities/user_task/index.js.map +2 -2
- package/dist/generated/entities/workflow_branch_instance/index.js +39 -0
- package/dist/generated/entities/workflow_branch_instance/index.js.map +7 -0
- package/dist/generated/entities/workflow_event/index.js +2 -0
- package/dist/generated/entities/workflow_event/index.js.map +2 -2
- package/dist/generated/entities/workflow_instance/index.js +2 -0
- package/dist/generated/entities/workflow_instance/index.js.map +2 -2
- package/dist/generated/entities.ids.generated.js +1 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +24 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/progress/api/jobs/[id]/route.js +7 -1
- package/dist/modules/progress/api/jobs/[id]/route.js.map +2 -2
- package/dist/modules/shipping_carriers/api/cancel/route.js +2 -2
- package/dist/modules/shipping_carriers/api/cancel/route.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/status-sync.js +8 -1
- package/dist/modules/shipping_carriers/lib/status-sync.js.map +2 -2
- package/dist/modules/workflows/components/NodeEditDialog.js +3 -1
- package/dist/modules/workflows/components/NodeEditDialog.js.map +2 -2
- package/dist/modules/workflows/components/WorkflowGraphImpl.js +4 -2
- package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +2 -2
- package/dist/modules/workflows/components/nodes/ParallelForkNode.js +49 -0
- package/dist/modules/workflows/components/nodes/ParallelForkNode.js.map +7 -0
- package/dist/modules/workflows/components/nodes/ParallelJoinNode.js +49 -0
- package/dist/modules/workflows/components/nodes/ParallelJoinNode.js.map +7 -0
- package/dist/modules/workflows/components/nodes/index.js +4 -0
- package/dist/modules/workflows/components/nodes/index.js.map +2 -2
- package/dist/modules/workflows/data/entities.js +81 -0
- package/dist/modules/workflows/data/entities.js.map +2 -2
- package/dist/modules/workflows/data/validators.js +146 -1
- package/dist/modules/workflows/data/validators.js.map +2 -2
- package/dist/modules/workflows/events.js +7 -1
- package/dist/modules/workflows/events.js.map +2 -2
- package/dist/modules/workflows/lib/activity-executor.js +4 -2
- package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
- package/dist/modules/workflows/lib/activity-queue-types.js.map +2 -2
- package/dist/modules/workflows/lib/event-logger.js +2 -0
- package/dist/modules/workflows/lib/event-logger.js.map +2 -2
- package/dist/modules/workflows/lib/execution-token.js +98 -0
- package/dist/modules/workflows/lib/execution-token.js.map +7 -0
- package/dist/modules/workflows/lib/node-type-icons.js +14 -5
- package/dist/modules/workflows/lib/node-type-icons.js.map +2 -2
- package/dist/modules/workflows/lib/parallel-handler.js +364 -0
- package/dist/modules/workflows/lib/parallel-handler.js.map +7 -0
- package/dist/modules/workflows/lib/signal-handler.js +63 -1
- package/dist/modules/workflows/lib/signal-handler.js.map +2 -2
- package/dist/modules/workflows/lib/step-handler.js +74 -30
- package/dist/modules/workflows/lib/step-handler.js.map +2 -2
- package/dist/modules/workflows/lib/task-handler.js +26 -0
- package/dist/modules/workflows/lib/task-handler.js.map +2 -2
- package/dist/modules/workflows/lib/timer-handler.js +26 -1
- package/dist/modules/workflows/lib/timer-handler.js.map +2 -2
- package/dist/modules/workflows/lib/transition-handler.js +33 -21
- package/dist/modules/workflows/lib/transition-handler.js.map +2 -2
- package/dist/modules/workflows/lib/workflow-executor.js +39 -1
- package/dist/modules/workflows/lib/workflow-executor.js.map +2 -2
- package/dist/modules/workflows/migrations/Migration20260602120000.js +24 -0
- package/dist/modules/workflows/migrations/Migration20260602120000.js.map +7 -0
- package/dist/modules/workflows/workers/workflow-activities.worker.js +8 -4
- package/dist/modules/workflows/workers/workflow-activities.worker.js.map +2 -2
- package/generated/entities/step_instance/index.ts +1 -0
- package/generated/entities/user_task/index.ts +1 -0
- package/generated/entities/workflow_branch_instance/index.ts +18 -0
- package/generated/entities/workflow_event/index.ts +1 -0
- package/generated/entities/workflow_instance/index.ts +1 -0
- package/generated/entities.ids.generated.ts +1 -0
- package/generated/entity-fields-registry.ts +24 -0
- package/package.json +7 -7
- package/src/modules/progress/api/jobs/[id]/route.ts +7 -0
- package/src/modules/shipping_carriers/api/cancel/route.ts +2 -2
- package/src/modules/shipping_carriers/lib/status-sync.ts +19 -0
- package/src/modules/workflows/components/NodeEditDialog.tsx +2 -0
- package/src/modules/workflows/components/WorkflowGraphImpl.tsx +3 -1
- package/src/modules/workflows/components/nodes/ParallelForkNode.tsx +66 -0
- package/src/modules/workflows/components/nodes/ParallelJoinNode.tsx +66 -0
- package/src/modules/workflows/components/nodes/index.ts +6 -0
- package/src/modules/workflows/data/entities.ts +109 -0
- package/src/modules/workflows/data/validators.ts +223 -0
- package/src/modules/workflows/events.ts +7 -0
- package/src/modules/workflows/i18n/de.json +12 -0
- package/src/modules/workflows/i18n/en.json +12 -0
- package/src/modules/workflows/i18n/es.json +12 -0
- package/src/modules/workflows/i18n/pl.json +12 -0
- package/src/modules/workflows/lib/activity-executor.ts +8 -2
- package/src/modules/workflows/lib/activity-queue-types.ts +3 -0
- package/src/modules/workflows/lib/event-logger.ts +3 -0
- package/src/modules/workflows/lib/execution-token.ts +166 -0
- package/src/modules/workflows/lib/node-type-icons.ts +11 -2
- package/src/modules/workflows/lib/parallel-handler.ts +575 -0
- package/src/modules/workflows/lib/signal-handler.ts +72 -1
- package/src/modules/workflows/lib/step-handler.ts +94 -34
- package/src/modules/workflows/lib/task-handler.ts +32 -0
- package/src/modules/workflows/lib/timer-handler.ts +30 -1
- package/src/modules/workflows/lib/transition-handler.ts +56 -24
- package/src/modules/workflows/lib/workflow-executor.ts +53 -1
- package/src/modules/workflows/migrations/.snapshot-open-mercato.json +263 -0
- package/src/modules/workflows/migrations/Migration20260602120000.ts +25 -0
- package/src/modules/workflows/workers/workflow-activities.worker.ts +9 -4
|
@@ -2,13 +2,32 @@ import type { UnifiedShipmentStatus } from './adapter'
|
|
|
2
2
|
import type { CarrierShipment } from '../data/entities'
|
|
3
3
|
import type { ShippingEventId } from '../events'
|
|
4
4
|
|
|
5
|
+
// Use Symbol.for so the marker survives module duplication across bundle
|
|
6
|
+
// boundaries — production builds can split this class into separate chunks,
|
|
7
|
+
// which breaks `instanceof` (see isCrudHttpError in
|
|
8
|
+
// @open-mercato/shared/lib/crud/errors for the same pattern).
|
|
9
|
+
const SHIPMENT_CANCEL_NOT_ALLOWED_MARKER = Symbol.for('@open-mercato/shipping_carriers/ShipmentCancelNotAllowedError')
|
|
10
|
+
|
|
5
11
|
export class ShipmentCancelNotAllowedError extends Error {
|
|
12
|
+
readonly [SHIPMENT_CANCEL_NOT_ALLOWED_MARKER] = true
|
|
13
|
+
|
|
6
14
|
constructor(status: string) {
|
|
7
15
|
super(`Shipment cannot be cancelled in its current status: ${status}`)
|
|
8
16
|
this.name = 'ShipmentCancelNotAllowedError'
|
|
9
17
|
}
|
|
10
18
|
}
|
|
11
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Type-safe check that works across module/bundle boundaries. Prefer this over
|
|
22
|
+
* `instanceof ShipmentCancelNotAllowedError` in route handlers, where the thrown
|
|
23
|
+
* error may originate from a different bundle than the one performing the check.
|
|
24
|
+
*/
|
|
25
|
+
export function isShipmentCancelNotAllowedError(error: unknown): error is ShipmentCancelNotAllowedError {
|
|
26
|
+
return !!error
|
|
27
|
+
&& typeof error === 'object'
|
|
28
|
+
&& (error as Record<symbol, unknown>)[SHIPMENT_CANCEL_NOT_ALLOWED_MARKER] === true
|
|
29
|
+
}
|
|
30
|
+
|
|
12
31
|
const VALID_SHIPPING_TRANSITIONS: Record<string, UnifiedShipmentStatus[]> = {
|
|
13
32
|
label_created: ['picked_up', 'in_transit', 'cancelled'],
|
|
14
33
|
picked_up: ['in_transit', 'cancelled'],
|
|
@@ -499,6 +499,8 @@ export function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }: Node
|
|
|
499
499
|
waitForSignal: t('workflows.nodeTypes.waitForSignal'),
|
|
500
500
|
waitForTimer: t('workflows.nodeTypes.waitForTimer'),
|
|
501
501
|
subWorkflow: t('workflows.nodeTypes.subWorkflow'),
|
|
502
|
+
parallelFork: t('workflows.nodeTypes.parallelFork'),
|
|
503
|
+
parallelJoin: t('workflows.nodeTypes.parallelJoin'),
|
|
502
504
|
}[node.type || 'automated']
|
|
503
505
|
|
|
504
506
|
// START nodes are partially editable (pre-conditions only), END nodes are not editable
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
ConnectionMode,
|
|
20
20
|
MarkerType,
|
|
21
21
|
} from '@xyflow/react'
|
|
22
|
-
import {StartNode, EndNode, UserTaskNode, AutomatedNode, SubWorkflowNode, WaitForSignalNode, WaitForTimerNode} from './nodes'
|
|
22
|
+
import {StartNode, EndNode, UserTaskNode, AutomatedNode, SubWorkflowNode, WaitForSignalNode, WaitForTimerNode, ParallelForkNode, ParallelJoinNode} from './nodes'
|
|
23
23
|
import { WorkflowTransitionEdge } from './WorkflowTransitionEdge'
|
|
24
24
|
import { STATUS_COLORS } from '../lib/status-colors'
|
|
25
25
|
import { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'
|
|
@@ -133,6 +133,8 @@ export default function WorkflowGraphImpl({
|
|
|
133
133
|
subWorkflow: SubWorkflowNode,
|
|
134
134
|
waitForSignal: WaitForSignalNode,
|
|
135
135
|
waitForTimer: WaitForTimerNode,
|
|
136
|
+
parallelFork: ParallelForkNode,
|
|
137
|
+
parallelJoin: ParallelJoinNode,
|
|
136
138
|
}),
|
|
137
139
|
[]
|
|
138
140
|
)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Handle, Position, NodeProps } from '@xyflow/react'
|
|
4
|
+
import { WorkflowNodeCard } from '../WorkflowNodeCard'
|
|
5
|
+
import { WorkflowStatus } from '../../lib/status-colors'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* ParallelForkNode display data.
|
|
9
|
+
*
|
|
10
|
+
* A PARALLEL_FORK splits execution into N branches (one per outgoing `auto`
|
|
11
|
+
* transition) that run concurrently and converge at the paired PARALLEL_JOIN
|
|
12
|
+
* referenced by `joinStepId`.
|
|
13
|
+
*/
|
|
14
|
+
export interface ParallelForkNodeData {
|
|
15
|
+
label: string
|
|
16
|
+
description?: string
|
|
17
|
+
joinStepId?: string
|
|
18
|
+
status?: 'pending' | 'running' | 'completed' | 'error' | 'not_started' | 'in_progress'
|
|
19
|
+
stepNumber?: number
|
|
20
|
+
badge?: string
|
|
21
|
+
tooltip?: string
|
|
22
|
+
executionStatus?: 'completed' | 'active' | 'pending' | 'failed' | 'skipped'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function mapStatus(status?: string): WorkflowStatus {
|
|
26
|
+
if (!status || status === 'pending') return 'not_started'
|
|
27
|
+
if (status === 'running' || status === 'in_progress') return 'in_progress'
|
|
28
|
+
if (status === 'completed') return 'completed'
|
|
29
|
+
return 'not_started'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* ParallelForkNode - splits the workflow into concurrent branches.
|
|
34
|
+
* One target handle (in); one source handle (out) that fans out to each branch.
|
|
35
|
+
*/
|
|
36
|
+
export function ParallelForkNode({ data, isConnectable, selected }: NodeProps) {
|
|
37
|
+
const nodeData = data as unknown as ParallelForkNodeData
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className="parallel-fork-node" title={nodeData.tooltip}>
|
|
41
|
+
<Handle
|
|
42
|
+
type="target"
|
|
43
|
+
position={Position.Top}
|
|
44
|
+
id="target"
|
|
45
|
+
isConnectable={isConnectable}
|
|
46
|
+
className="!w-3 !h-3 !bg-primary !border-2 !border-background"
|
|
47
|
+
/>
|
|
48
|
+
|
|
49
|
+
<WorkflowNodeCard
|
|
50
|
+
title={nodeData.label}
|
|
51
|
+
description={nodeData.description}
|
|
52
|
+
status={mapStatus(nodeData.status)}
|
|
53
|
+
nodeType="parallelFork"
|
|
54
|
+
selected={selected}
|
|
55
|
+
/>
|
|
56
|
+
|
|
57
|
+
<Handle
|
|
58
|
+
type="source"
|
|
59
|
+
position={Position.Bottom}
|
|
60
|
+
id="source"
|
|
61
|
+
isConnectable={isConnectable}
|
|
62
|
+
className="!w-3 !h-3 !bg-primary !border-2 !border-background"
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Handle, Position, NodeProps } from '@xyflow/react'
|
|
4
|
+
import { WorkflowNodeCard } from '../WorkflowNodeCard'
|
|
5
|
+
import { WorkflowStatus } from '../../lib/status-colors'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* ParallelJoinNode display data.
|
|
9
|
+
*
|
|
10
|
+
* A PARALLEL_JOIN synchronizes the branches created by its paired
|
|
11
|
+
* PARALLEL_FORK (`forkStepId`) using wait-all semantics, then continues the
|
|
12
|
+
* single outgoing transition once every branch has completed.
|
|
13
|
+
*/
|
|
14
|
+
export interface ParallelJoinNodeData {
|
|
15
|
+
label: string
|
|
16
|
+
description?: string
|
|
17
|
+
forkStepId?: string
|
|
18
|
+
status?: 'pending' | 'running' | 'completed' | 'error' | 'not_started' | 'in_progress'
|
|
19
|
+
stepNumber?: number
|
|
20
|
+
badge?: string
|
|
21
|
+
tooltip?: string
|
|
22
|
+
executionStatus?: 'completed' | 'active' | 'pending' | 'failed' | 'skipped'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function mapStatus(status?: string): WorkflowStatus {
|
|
26
|
+
if (!status || status === 'pending') return 'not_started'
|
|
27
|
+
if (status === 'running' || status === 'in_progress') return 'in_progress'
|
|
28
|
+
if (status === 'completed') return 'completed'
|
|
29
|
+
return 'not_started'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* ParallelJoinNode - synchronizes concurrent branches (wait-all).
|
|
34
|
+
* One target handle (in) collecting all branches; one source handle (out).
|
|
35
|
+
*/
|
|
36
|
+
export function ParallelJoinNode({ data, isConnectable, selected }: NodeProps) {
|
|
37
|
+
const nodeData = data as unknown as ParallelJoinNodeData
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className="parallel-join-node" title={nodeData.tooltip}>
|
|
41
|
+
<Handle
|
|
42
|
+
type="target"
|
|
43
|
+
position={Position.Top}
|
|
44
|
+
id="target"
|
|
45
|
+
isConnectable={isConnectable}
|
|
46
|
+
className="!w-3 !h-3 !bg-primary !border-2 !border-background"
|
|
47
|
+
/>
|
|
48
|
+
|
|
49
|
+
<WorkflowNodeCard
|
|
50
|
+
title={nodeData.label}
|
|
51
|
+
description={nodeData.description}
|
|
52
|
+
status={mapStatus(nodeData.status)}
|
|
53
|
+
nodeType="parallelJoin"
|
|
54
|
+
selected={selected}
|
|
55
|
+
/>
|
|
56
|
+
|
|
57
|
+
<Handle
|
|
58
|
+
type="source"
|
|
59
|
+
position={Position.Bottom}
|
|
60
|
+
id="source"
|
|
61
|
+
isConnectable={isConnectable}
|
|
62
|
+
className="!w-3 !h-3 !bg-primary !border-2 !border-background"
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
@@ -18,3 +18,9 @@ export type { WaitForSignalNodeData } from './WaitForSignalNode'
|
|
|
18
18
|
|
|
19
19
|
export { WaitForTimerNode } from './WaitForTimerNode'
|
|
20
20
|
export type { WaitForTimerNodeData } from './WaitForTimerNode'
|
|
21
|
+
|
|
22
|
+
export { ParallelForkNode } from './ParallelForkNode'
|
|
23
|
+
export type { ParallelForkNodeData } from './ParallelForkNode'
|
|
24
|
+
|
|
25
|
+
export { ParallelJoinNode } from './ParallelJoinNode'
|
|
26
|
+
export type { ParallelJoinNodeData } from './ParallelJoinNode'
|
|
@@ -31,6 +31,15 @@ export type WorkflowInstanceStatus =
|
|
|
31
31
|
| 'COMPENSATING'
|
|
32
32
|
| 'COMPENSATED'
|
|
33
33
|
| 'WAITING_FOR_ACTIVITIES'
|
|
34
|
+
| 'FORKED'
|
|
35
|
+
|
|
36
|
+
export type WorkflowBranchInstanceStatus =
|
|
37
|
+
| 'ACTIVE'
|
|
38
|
+
| 'PAUSED'
|
|
39
|
+
| 'WAITING_FOR_ACTIVITIES'
|
|
40
|
+
| 'COMPLETED'
|
|
41
|
+
| 'FAILED'
|
|
42
|
+
| 'CANCELLED'
|
|
34
43
|
|
|
35
44
|
export type StepInstanceStatus =
|
|
36
45
|
| 'PENDING'
|
|
@@ -275,6 +284,11 @@ export class WorkflowInstance {
|
|
|
275
284
|
timestamp: Date
|
|
276
285
|
} | null
|
|
277
286
|
|
|
287
|
+
// When the instance is FORKED, points at the open PARALLEL_FORK step whose
|
|
288
|
+
// branches are currently executing. Null for single-token instances.
|
|
289
|
+
@Property({ name: 'active_fork_step_id', type: 'varchar', length: 100, nullable: true })
|
|
290
|
+
activeForkStepId?: string | null
|
|
291
|
+
|
|
278
292
|
@Property({ name: 'retry_count', type: 'integer', default: 0 })
|
|
279
293
|
retryCount: number = 0
|
|
280
294
|
|
|
@@ -294,6 +308,88 @@ export class WorkflowInstance {
|
|
|
294
308
|
deletedAt?: Date | null
|
|
295
309
|
}
|
|
296
310
|
|
|
311
|
+
// ============================================================================
|
|
312
|
+
// Entity: WorkflowBranchInstance
|
|
313
|
+
// ============================================================================
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* WorkflowBranchInstance entity
|
|
317
|
+
*
|
|
318
|
+
* A single parallel branch token created by a PARALLEL_FORK step. Each branch
|
|
319
|
+
* advances independently (interleaved under the instance lock) with its own
|
|
320
|
+
* private context namespace, and converges to the paired PARALLEL_JOIN step.
|
|
321
|
+
* Branches are tenant/org scoped and never cross-tenant.
|
|
322
|
+
*/
|
|
323
|
+
@Entity({ tableName: 'workflow_branch_instances' })
|
|
324
|
+
@Index({ name: 'workflow_branch_instances_instance_status_idx', properties: ['workflowInstanceId', 'status'] })
|
|
325
|
+
@Index({ name: 'workflow_branch_instances_instance_fork_idx', properties: ['workflowInstanceId', 'forkStepId'] })
|
|
326
|
+
@Index({ name: 'workflow_branch_instances_tenant_org_idx', properties: ['tenantId', 'organizationId'] })
|
|
327
|
+
export class WorkflowBranchInstance {
|
|
328
|
+
[OptionalProps]?: 'createdAt' | 'updatedAt'
|
|
329
|
+
|
|
330
|
+
@PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
|
|
331
|
+
id!: string
|
|
332
|
+
|
|
333
|
+
@Property({ name: 'workflow_instance_id', type: 'uuid' })
|
|
334
|
+
workflowInstanceId!: string
|
|
335
|
+
|
|
336
|
+
@Property({ name: 'fork_step_id', type: 'varchar', length: 100 })
|
|
337
|
+
forkStepId!: string
|
|
338
|
+
|
|
339
|
+
@Property({ name: 'join_step_id', type: 'varchar', length: 100 })
|
|
340
|
+
joinStepId!: string
|
|
341
|
+
|
|
342
|
+
// The transitionId of the FORK's outgoing transition that created this branch.
|
|
343
|
+
@Property({ name: 'branch_key', type: 'varchar', length: 100 })
|
|
344
|
+
branchKey!: string
|
|
345
|
+
|
|
346
|
+
// Reserved for nested-fork support (always null this iteration; validator blocks nesting).
|
|
347
|
+
@Property({ name: 'parent_branch_id', type: 'uuid', nullable: true })
|
|
348
|
+
parentBranchId?: string | null
|
|
349
|
+
|
|
350
|
+
@Property({ name: 'current_step_id', type: 'varchar', length: 100 })
|
|
351
|
+
currentStepId!: string
|
|
352
|
+
|
|
353
|
+
@Property({ name: 'status', type: 'varchar', length: 30 })
|
|
354
|
+
status!: WorkflowBranchInstanceStatus
|
|
355
|
+
|
|
356
|
+
// The branch's private write scope; merged back into instance.context at JOIN.
|
|
357
|
+
@Property({ name: 'context_namespace', type: 'jsonb' })
|
|
358
|
+
contextNamespace!: Record<string, any>
|
|
359
|
+
|
|
360
|
+
// Per-branch equivalent of WorkflowInstance.pendingTransition (async activities).
|
|
361
|
+
@Property({ name: 'pending_transition', type: 'jsonb', nullable: true })
|
|
362
|
+
pendingTransition?: {
|
|
363
|
+
toStepId: string
|
|
364
|
+
activityResults: any[]
|
|
365
|
+
timestamp: Date
|
|
366
|
+
} | null
|
|
367
|
+
|
|
368
|
+
@Property({ name: 'error_message', type: 'text', nullable: true })
|
|
369
|
+
errorMessage?: string | null
|
|
370
|
+
|
|
371
|
+
@Property({ name: 'error_details', type: 'jsonb', nullable: true })
|
|
372
|
+
errorDetails?: any | null
|
|
373
|
+
|
|
374
|
+
@Property({ name: 'started_at', type: Date, nullable: true })
|
|
375
|
+
startedAt?: Date | null
|
|
376
|
+
|
|
377
|
+
@Property({ name: 'completed_at', type: Date, nullable: true })
|
|
378
|
+
completedAt?: Date | null
|
|
379
|
+
|
|
380
|
+
@Property({ name: 'tenant_id', type: 'uuid' })
|
|
381
|
+
tenantId!: string
|
|
382
|
+
|
|
383
|
+
@Property({ name: 'organization_id', type: 'uuid' })
|
|
384
|
+
organizationId!: string
|
|
385
|
+
|
|
386
|
+
@Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
|
|
387
|
+
createdAt: Date = new Date()
|
|
388
|
+
|
|
389
|
+
@Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })
|
|
390
|
+
updatedAt: Date = new Date()
|
|
391
|
+
}
|
|
392
|
+
|
|
297
393
|
// ============================================================================
|
|
298
394
|
// Entity: StepInstance
|
|
299
395
|
// ============================================================================
|
|
@@ -317,6 +413,10 @@ export class StepInstance {
|
|
|
317
413
|
@Property({ name: 'workflow_instance_id', type: 'uuid' })
|
|
318
414
|
workflowInstanceId!: string
|
|
319
415
|
|
|
416
|
+
// Set when this step executes inside a parallel branch; null for single-token.
|
|
417
|
+
@Property({ name: 'branch_instance_id', type: 'uuid', nullable: true })
|
|
418
|
+
branchInstanceId?: string | null
|
|
419
|
+
|
|
320
420
|
@Property({ name: 'step_id', type: 'varchar', length: 100 })
|
|
321
421
|
stepId!: string
|
|
322
422
|
|
|
@@ -390,6 +490,10 @@ export class UserTask {
|
|
|
390
490
|
@Property({ name: 'step_instance_id', type: 'uuid' })
|
|
391
491
|
stepInstanceId!: string
|
|
392
492
|
|
|
493
|
+
// Set when this task belongs to a parallel branch; null for single-token instances.
|
|
494
|
+
@Property({ name: 'branch_instance_id', type: 'uuid', nullable: true })
|
|
495
|
+
branchInstanceId?: string | null
|
|
496
|
+
|
|
393
497
|
@Property({ name: 'task_name', type: 'varchar', length: 255 })
|
|
394
498
|
taskName!: string
|
|
395
499
|
|
|
@@ -472,6 +576,10 @@ export class WorkflowEvent {
|
|
|
472
576
|
@Property({ name: 'step_instance_id', type: 'uuid', nullable: true })
|
|
473
577
|
stepInstanceId?: string | null
|
|
474
578
|
|
|
579
|
+
// Set when the event was logged within a parallel branch; null otherwise.
|
|
580
|
+
@Property({ name: 'branch_instance_id', type: 'uuid', nullable: true })
|
|
581
|
+
branchInstanceId?: string | null
|
|
582
|
+
|
|
475
583
|
@Property({ name: 'event_type', type: 'varchar', length: 50 })
|
|
476
584
|
eventType!: string
|
|
477
585
|
|
|
@@ -560,6 +668,7 @@ export class WorkflowEventTrigger {
|
|
|
560
668
|
export default [
|
|
561
669
|
WorkflowDefinition,
|
|
562
670
|
WorkflowInstance,
|
|
671
|
+
WorkflowBranchInstance,
|
|
563
672
|
StepInstance,
|
|
564
673
|
UserTask,
|
|
565
674
|
WorkflowEvent,
|
|
@@ -69,9 +69,20 @@ export const workflowInstanceStatusSchema = z.enum([
|
|
|
69
69
|
'COMPENSATING',
|
|
70
70
|
'COMPENSATED',
|
|
71
71
|
'WAITING_FOR_ACTIVITIES',
|
|
72
|
+
'FORKED',
|
|
72
73
|
])
|
|
73
74
|
export type WorkflowInstanceStatus = z.infer<typeof workflowInstanceStatusSchema>
|
|
74
75
|
|
|
76
|
+
export const workflowBranchInstanceStatusSchema = z.enum([
|
|
77
|
+
'ACTIVE',
|
|
78
|
+
'PAUSED',
|
|
79
|
+
'WAITING_FOR_ACTIVITIES',
|
|
80
|
+
'COMPLETED',
|
|
81
|
+
'FAILED',
|
|
82
|
+
'CANCELLED',
|
|
83
|
+
])
|
|
84
|
+
export type WorkflowBranchInstanceStatus = z.infer<typeof workflowBranchInstanceStatusSchema>
|
|
85
|
+
|
|
75
86
|
export const stepInstanceStatusSchema = z.enum([
|
|
76
87
|
'PENDING',
|
|
77
88
|
'ACTIVE',
|
|
@@ -376,6 +387,210 @@ export const workflowDefinitionTriggerSchema = z.object({
|
|
|
376
387
|
})
|
|
377
388
|
export type WorkflowDefinitionTrigger = z.infer<typeof workflowDefinitionTriggerSchema>
|
|
378
389
|
|
|
390
|
+
// ============================================================================
|
|
391
|
+
// PARALLEL_FORK / PARALLEL_JOIN definition validation
|
|
392
|
+
// ============================================================================
|
|
393
|
+
|
|
394
|
+
// Error codes surfaced by FORK/JOIN definition validation. Stable identifiers
|
|
395
|
+
// so the visual editor and tests can match on them.
|
|
396
|
+
export type ForkJoinValidationCode =
|
|
397
|
+
| 'MISSING_JOIN_STEP_ID'
|
|
398
|
+
| 'JOIN_STEP_NOT_FOUND'
|
|
399
|
+
| 'JOIN_STEP_WRONG_TYPE'
|
|
400
|
+
| 'MISSING_FORK_STEP_ID'
|
|
401
|
+
| 'FORK_JOIN_MISMATCH'
|
|
402
|
+
| 'FORK_TOO_FEW_BRANCHES'
|
|
403
|
+
| 'JOIN_TOO_FEW_INCOMING'
|
|
404
|
+
| 'DUPLICATE_BRANCH_KEY'
|
|
405
|
+
| 'NESTED_FORK_NOT_SUPPORTED'
|
|
406
|
+
| 'NO_CONVERGENCE_TO_JOIN'
|
|
407
|
+
| 'FORK_JOIN_CYCLE'
|
|
408
|
+
| 'UNPAIRED_JOIN'
|
|
409
|
+
|
|
410
|
+
export interface ForkJoinValidationIssue {
|
|
411
|
+
code: ForkJoinValidationCode
|
|
412
|
+
message: string
|
|
413
|
+
stepId?: string
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
interface ForkJoinStepLike {
|
|
417
|
+
stepId: string
|
|
418
|
+
stepType: string
|
|
419
|
+
config?: Record<string, unknown> | null
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
interface ForkJoinTransitionLike {
|
|
423
|
+
transitionId: string
|
|
424
|
+
fromStepId: string
|
|
425
|
+
toStepId: string
|
|
426
|
+
trigger: string
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
interface ForkJoinDefinitionLike {
|
|
430
|
+
steps: ForkJoinStepLike[]
|
|
431
|
+
transitions: ForkJoinTransitionLike[]
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Validates PARALLEL_FORK / PARALLEL_JOIN structure of a workflow definition.
|
|
436
|
+
* Pure and side-effect-free so it can be unit tested and reused by the editor.
|
|
437
|
+
*
|
|
438
|
+
* Rules (this iteration — wait-all, no nesting):
|
|
439
|
+
* 1. Every FORK declares config.joinStepId pointing at an existing PARALLEL_JOIN.
|
|
440
|
+
* 2. The paired JOIN back-references the fork via config.forkStepId.
|
|
441
|
+
* 3. A FORK has >= 2 outgoing `auto` transitions (branch keys unique); a JOIN has >= 2 incoming.
|
|
442
|
+
* 4. Every path from a FORK converges to its JOIN — no END inside a branch, no dead ends,
|
|
443
|
+
* no path bypassing the JOIN, no path to a different JOIN.
|
|
444
|
+
* 5. No nesting: no FORK appears on a path between a FORK and its JOIN.
|
|
445
|
+
* 6. No cycles back to the FORK within its branch region.
|
|
446
|
+
* 7. Every PARALLEL_JOIN is paired with exactly one FORK.
|
|
447
|
+
*/
|
|
448
|
+
export function validateParallelForkJoin(definition: ForkJoinDefinitionLike): ForkJoinValidationIssue[] {
|
|
449
|
+
const issues: ForkJoinValidationIssue[] = []
|
|
450
|
+
const steps = definition.steps ?? []
|
|
451
|
+
const transitions = definition.transitions ?? []
|
|
452
|
+
|
|
453
|
+
const stepById = new Map<string, ForkJoinStepLike>()
|
|
454
|
+
for (const step of steps) stepById.set(step.stepId, step)
|
|
455
|
+
|
|
456
|
+
const outgoingByStep = new Map<string, ForkJoinTransitionLike[]>()
|
|
457
|
+
const incomingCountByStep = new Map<string, number>()
|
|
458
|
+
for (const transition of transitions) {
|
|
459
|
+
const list = outgoingByStep.get(transition.fromStepId) ?? []
|
|
460
|
+
list.push(transition)
|
|
461
|
+
outgoingByStep.set(transition.fromStepId, list)
|
|
462
|
+
incomingCountByStep.set(transition.toStepId, (incomingCountByStep.get(transition.toStepId) ?? 0) + 1)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const forkSteps = steps.filter((step) => step.stepType === 'PARALLEL_FORK')
|
|
466
|
+
const joinSteps = steps.filter((step) => step.stepType === 'PARALLEL_JOIN')
|
|
467
|
+
|
|
468
|
+
// Track which JOIN steps are paired with a FORK so we can flag orphan joins.
|
|
469
|
+
const pairedJoinIds = new Set<string>()
|
|
470
|
+
|
|
471
|
+
for (const fork of forkSteps) {
|
|
472
|
+
const joinStepId = (fork.config?.joinStepId as string | undefined) ?? undefined
|
|
473
|
+
if (!joinStepId) {
|
|
474
|
+
issues.push({ code: 'MISSING_JOIN_STEP_ID', stepId: fork.stepId, message: `PARALLEL_FORK "${fork.stepId}" must declare config.joinStepId` })
|
|
475
|
+
continue
|
|
476
|
+
}
|
|
477
|
+
const joinStep = stepById.get(joinStepId)
|
|
478
|
+
if (!joinStep) {
|
|
479
|
+
issues.push({ code: 'JOIN_STEP_NOT_FOUND', stepId: fork.stepId, message: `PARALLEL_FORK "${fork.stepId}" references missing join step "${joinStepId}"` })
|
|
480
|
+
continue
|
|
481
|
+
}
|
|
482
|
+
if (joinStep.stepType !== 'PARALLEL_JOIN') {
|
|
483
|
+
issues.push({ code: 'JOIN_STEP_WRONG_TYPE', stepId: fork.stepId, message: `Step "${joinStepId}" referenced by fork "${fork.stepId}" is not a PARALLEL_JOIN` })
|
|
484
|
+
continue
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
pairedJoinIds.add(joinStepId)
|
|
488
|
+
|
|
489
|
+
const backForkStepId = (joinStep.config?.forkStepId as string | undefined) ?? undefined
|
|
490
|
+
if (!backForkStepId) {
|
|
491
|
+
issues.push({ code: 'MISSING_FORK_STEP_ID', stepId: joinStepId, message: `PARALLEL_JOIN "${joinStepId}" must declare config.forkStepId` })
|
|
492
|
+
} else if (backForkStepId !== fork.stepId) {
|
|
493
|
+
issues.push({ code: 'FORK_JOIN_MISMATCH', stepId: joinStepId, message: `PARALLEL_JOIN "${joinStepId}" back-reference forkStepId "${backForkStepId}" does not match fork "${fork.stepId}"` })
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const autoBranches = (outgoingByStep.get(fork.stepId) ?? []).filter((transition) => transition.trigger === 'auto')
|
|
497
|
+
if (autoBranches.length < 2) {
|
|
498
|
+
issues.push({ code: 'FORK_TOO_FEW_BRANCHES', stepId: fork.stepId, message: `PARALLEL_FORK "${fork.stepId}" must have at least 2 outgoing auto transitions (found ${autoBranches.length})` })
|
|
499
|
+
}
|
|
500
|
+
const branchKeys = new Set<string>()
|
|
501
|
+
for (const branch of autoBranches) {
|
|
502
|
+
if (branchKeys.has(branch.transitionId)) {
|
|
503
|
+
issues.push({ code: 'DUPLICATE_BRANCH_KEY', stepId: fork.stepId, message: `PARALLEL_FORK "${fork.stepId}" has duplicate branch key "${branch.transitionId}"` })
|
|
504
|
+
}
|
|
505
|
+
branchKeys.add(branch.transitionId)
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if ((incomingCountByStep.get(joinStepId) ?? 0) < 2) {
|
|
509
|
+
issues.push({ code: 'JOIN_TOO_FEW_INCOMING', stepId: joinStepId, message: `PARALLEL_JOIN "${joinStepId}" must have at least 2 incoming transitions` })
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Convergence + no-nesting + no-cycle traversal over the branch region.
|
|
513
|
+
const fullyExplored = new Set<string>()
|
|
514
|
+
const onStack = new Set<string>()
|
|
515
|
+
let reportedNesting = false
|
|
516
|
+
let reportedNoConvergence = false
|
|
517
|
+
let reportedCycle = false
|
|
518
|
+
|
|
519
|
+
const visit = (stepId: string): void => {
|
|
520
|
+
if (stepId === joinStepId) return // converged
|
|
521
|
+
if (stepId === fork.stepId) {
|
|
522
|
+
if (!reportedCycle) {
|
|
523
|
+
issues.push({ code: 'FORK_JOIN_CYCLE', stepId: fork.stepId, message: `A branch of fork "${fork.stepId}" loops back to the fork before reaching join "${joinStepId}"` })
|
|
524
|
+
reportedCycle = true
|
|
525
|
+
}
|
|
526
|
+
return
|
|
527
|
+
}
|
|
528
|
+
const step = stepById.get(stepId)
|
|
529
|
+
if (!step) {
|
|
530
|
+
if (!reportedNoConvergence) {
|
|
531
|
+
issues.push({ code: 'NO_CONVERGENCE_TO_JOIN', stepId: fork.stepId, message: `A branch of fork "${fork.stepId}" reaches missing step "${stepId}" instead of join "${joinStepId}"` })
|
|
532
|
+
reportedNoConvergence = true
|
|
533
|
+
}
|
|
534
|
+
return
|
|
535
|
+
}
|
|
536
|
+
if (step.stepType === 'END') {
|
|
537
|
+
if (!reportedNoConvergence) {
|
|
538
|
+
issues.push({ code: 'NO_CONVERGENCE_TO_JOIN', stepId: fork.stepId, message: `A branch of fork "${fork.stepId}" reaches an END step before join "${joinStepId}"` })
|
|
539
|
+
reportedNoConvergence = true
|
|
540
|
+
}
|
|
541
|
+
return
|
|
542
|
+
}
|
|
543
|
+
if (step.stepType === 'PARALLEL_FORK') {
|
|
544
|
+
if (!reportedNesting) {
|
|
545
|
+
issues.push({ code: 'NESTED_FORK_NOT_SUPPORTED', stepId: fork.stepId, message: `Nested PARALLEL_FORK "${stepId}" inside fork "${fork.stepId}" is not supported` })
|
|
546
|
+
reportedNesting = true
|
|
547
|
+
}
|
|
548
|
+
return
|
|
549
|
+
}
|
|
550
|
+
if (step.stepType === 'PARALLEL_JOIN') {
|
|
551
|
+
// Reached a join that is not this fork's join → it does not converge correctly.
|
|
552
|
+
if (!reportedNoConvergence) {
|
|
553
|
+
issues.push({ code: 'NO_CONVERGENCE_TO_JOIN', stepId: fork.stepId, message: `A branch of fork "${fork.stepId}" reaches join "${stepId}" instead of its own join "${joinStepId}"` })
|
|
554
|
+
reportedNoConvergence = true
|
|
555
|
+
}
|
|
556
|
+
return
|
|
557
|
+
}
|
|
558
|
+
if (onStack.has(stepId)) {
|
|
559
|
+
if (!reportedCycle) {
|
|
560
|
+
issues.push({ code: 'FORK_JOIN_CYCLE', stepId: fork.stepId, message: `A branch of fork "${fork.stepId}" contains a cycle at step "${stepId}"` })
|
|
561
|
+
reportedCycle = true
|
|
562
|
+
}
|
|
563
|
+
return
|
|
564
|
+
}
|
|
565
|
+
if (fullyExplored.has(stepId)) return
|
|
566
|
+
|
|
567
|
+
const outgoing = outgoingByStep.get(stepId) ?? []
|
|
568
|
+
if (outgoing.length === 0) {
|
|
569
|
+
if (!reportedNoConvergence) {
|
|
570
|
+
issues.push({ code: 'NO_CONVERGENCE_TO_JOIN', stepId: fork.stepId, message: `A branch of fork "${fork.stepId}" dead-ends at step "${stepId}" without reaching join "${joinStepId}"` })
|
|
571
|
+
reportedNoConvergence = true
|
|
572
|
+
}
|
|
573
|
+
return
|
|
574
|
+
}
|
|
575
|
+
onStack.add(stepId)
|
|
576
|
+
for (const transition of outgoing) visit(transition.toStepId)
|
|
577
|
+
onStack.delete(stepId)
|
|
578
|
+
fullyExplored.add(stepId)
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
for (const branch of autoBranches) visit(branch.toStepId)
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Any PARALLEL_JOIN not paired with a fork is an orphan.
|
|
585
|
+
for (const join of joinSteps) {
|
|
586
|
+
if (!pairedJoinIds.has(join.stepId)) {
|
|
587
|
+
issues.push({ code: 'UNPAIRED_JOIN', stepId: join.stepId, message: `PARALLEL_JOIN "${join.stepId}" is not paired with any PARALLEL_FORK` })
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
return issues
|
|
592
|
+
}
|
|
593
|
+
|
|
379
594
|
// Workflow definition data (JSONB structure)
|
|
380
595
|
export const workflowDefinitionDataSchema = z.object({
|
|
381
596
|
steps: z.array(workflowStepSchema).min(2, 'Workflow must have at least START and END steps'),
|
|
@@ -384,6 +599,14 @@ export const workflowDefinitionDataSchema = z.object({
|
|
|
384
599
|
queries: z.array(z.any()).optional(), // For Phase 7
|
|
385
600
|
signals: z.array(z.any()).optional(), // For Phase 9
|
|
386
601
|
timers: z.array(z.any()).optional(), // For Phase 9
|
|
602
|
+
}).superRefine((definition, ctx) => {
|
|
603
|
+
for (const issue of validateParallelForkJoin(definition as ForkJoinDefinitionLike)) {
|
|
604
|
+
ctx.addIssue({
|
|
605
|
+
code: 'custom',
|
|
606
|
+
path: ['steps'],
|
|
607
|
+
message: `[${issue.code}] ${issue.message}`,
|
|
608
|
+
})
|
|
609
|
+
}
|
|
387
610
|
})
|
|
388
611
|
|
|
389
612
|
// Workflow metadata
|
|
@@ -35,6 +35,13 @@ const events = [
|
|
|
35
35
|
{ id: 'workflows.trigger.created', label: 'Trigger Created', entity: 'trigger', category: 'crud' },
|
|
36
36
|
{ id: 'workflows.trigger.updated', label: 'Trigger Updated', entity: 'trigger', category: 'crud' },
|
|
37
37
|
{ id: 'workflows.trigger.deleted', label: 'Trigger Deleted', entity: 'trigger', category: 'crud' },
|
|
38
|
+
|
|
39
|
+
// Parallel Fork / Join (branch lifecycle)
|
|
40
|
+
{ id: 'workflows.branch.opened', label: 'Parallel Branch Opened', entity: 'branch', category: 'lifecycle' },
|
|
41
|
+
{ id: 'workflows.branch.completed', label: 'Parallel Branch Completed', entity: 'branch', category: 'lifecycle' },
|
|
42
|
+
{ id: 'workflows.branch.cancelled', label: 'Parallel Branch Cancelled', entity: 'branch', category: 'lifecycle' },
|
|
43
|
+
{ id: 'workflows.branch.failed', label: 'Parallel Branch Failed', entity: 'branch', category: 'lifecycle' },
|
|
44
|
+
{ id: 'workflows.join.completed', label: 'Parallel Join Completed', entity: 'branch', category: 'lifecycle' },
|
|
38
45
|
] as const
|
|
39
46
|
|
|
40
47
|
export const eventsConfig = createModuleEvents({
|
|
@@ -913,6 +913,8 @@
|
|
|
913
913
|
"workflows.nodeTypes.automated": "AUTOMATISIERT",
|
|
914
914
|
"workflows.nodeTypes.decision": "ENTSCHEIDUNG",
|
|
915
915
|
"workflows.nodeTypes.end": "ENDE",
|
|
916
|
+
"workflows.nodeTypes.parallelFork": "Parallele Verzweigung",
|
|
917
|
+
"workflows.nodeTypes.parallelJoin": "Parallele Zusammenführung",
|
|
916
918
|
"workflows.nodeTypes.start": "START",
|
|
917
919
|
"workflows.nodeTypes.subWorkflow": "SUB-WORKFLOW",
|
|
918
920
|
"workflows.nodeTypes.userTask": "BENUTZERAUFGABE",
|
|
@@ -937,6 +939,16 @@
|
|
|
937
939
|
"workflows.orderApproval.requestApproval": "Genehmigung anfordern",
|
|
938
940
|
"workflows.orderApproval.startError": "Genehmigungsworkflow konnte nicht gestartet werden.",
|
|
939
941
|
"workflows.orderApproval.submitDecision": "Entscheidung einreichen",
|
|
942
|
+
"workflows.parallel.branch.label": "Zweig",
|
|
943
|
+
"workflows.parallel.branch.status.ACTIVE": "Aktiv",
|
|
944
|
+
"workflows.parallel.branch.status.CANCELLED": "Abgebrochen",
|
|
945
|
+
"workflows.parallel.branch.status.COMPLETED": "Abgeschlossen",
|
|
946
|
+
"workflows.parallel.branch.status.FAILED": "Fehlgeschlagen",
|
|
947
|
+
"workflows.parallel.branch.status.PAUSED": "Pausiert",
|
|
948
|
+
"workflows.parallel.branch.status.WAITING_FOR_ACTIVITIES": "Warten auf Aktivitäten",
|
|
949
|
+
"workflows.parallel.fork.label": "Parallele Verzweigung",
|
|
950
|
+
"workflows.parallel.join.label": "Parallele Zusammenführung",
|
|
951
|
+
"workflows.parallel.validation.title": "Validierung der parallelen Verzweigung/Zusammenführung fehlgeschlagen",
|
|
940
952
|
"workflows.signals.awaiting": "Wartet auf Signal",
|
|
941
953
|
"workflows.signals.correlationKey": "Korrelationsschlüssel",
|
|
942
954
|
"workflows.signals.payload": "Signal-Nutzlast",
|
|
@@ -913,6 +913,8 @@
|
|
|
913
913
|
"workflows.nodeTypes.automated": "AUTOMATED",
|
|
914
914
|
"workflows.nodeTypes.decision": "DECISION",
|
|
915
915
|
"workflows.nodeTypes.end": "END",
|
|
916
|
+
"workflows.nodeTypes.parallelFork": "Parallel Fork",
|
|
917
|
+
"workflows.nodeTypes.parallelJoin": "Parallel Join",
|
|
916
918
|
"workflows.nodeTypes.start": "START",
|
|
917
919
|
"workflows.nodeTypes.subWorkflow": "SUB-WORKFLOW",
|
|
918
920
|
"workflows.nodeTypes.userTask": "USER TASK",
|
|
@@ -937,6 +939,16 @@
|
|
|
937
939
|
"workflows.orderApproval.requestApproval": "Request Approval",
|
|
938
940
|
"workflows.orderApproval.startError": "Failed to start approval workflow.",
|
|
939
941
|
"workflows.orderApproval.submitDecision": "Submit Decision",
|
|
942
|
+
"workflows.parallel.branch.label": "Branch",
|
|
943
|
+
"workflows.parallel.branch.status.ACTIVE": "Active",
|
|
944
|
+
"workflows.parallel.branch.status.CANCELLED": "Cancelled",
|
|
945
|
+
"workflows.parallel.branch.status.COMPLETED": "Completed",
|
|
946
|
+
"workflows.parallel.branch.status.FAILED": "Failed",
|
|
947
|
+
"workflows.parallel.branch.status.PAUSED": "Paused",
|
|
948
|
+
"workflows.parallel.branch.status.WAITING_FOR_ACTIVITIES": "Waiting for activities",
|
|
949
|
+
"workflows.parallel.fork.label": "Parallel Fork",
|
|
950
|
+
"workflows.parallel.join.label": "Parallel Join",
|
|
951
|
+
"workflows.parallel.validation.title": "Parallel fork/join validation failed",
|
|
940
952
|
"workflows.signals.awaiting": "Waiting for signal",
|
|
941
953
|
"workflows.signals.correlationKey": "Correlation Key",
|
|
942
954
|
"workflows.signals.payload": "Signal Payload",
|