@open-mercato/core 0.4.2-canary-15e78de280 → 0.4.2-canary-ed15f2e753
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/dist/generated/entities/workflow_event_trigger/index.js +33 -0
- package/dist/generated/entities/workflow_event_trigger/index.js.map +7 -0
- 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 +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/auth/events.js +30 -0
- package/dist/modules/auth/events.js.map +7 -0
- package/dist/modules/business_rules/api/execute/[ruleId]/route.js +145 -0
- package/dist/modules/business_rules/api/execute/[ruleId]/route.js.map +7 -0
- package/dist/modules/business_rules/data/validators.js +34 -0
- package/dist/modules/business_rules/data/validators.js.map +2 -2
- package/dist/modules/business_rules/index.js +21 -1
- package/dist/modules/business_rules/index.js.map +2 -2
- package/dist/modules/business_rules/lib/rule-engine.js +182 -1
- package/dist/modules/business_rules/lib/rule-engine.js.map +2 -2
- package/dist/modules/catalog/events.js +34 -0
- package/dist/modules/catalog/events.js.map +7 -0
- package/dist/modules/customers/events.js +49 -0
- package/dist/modules/customers/events.js.map +7 -0
- package/dist/modules/directory/events.js +23 -0
- package/dist/modules/directory/events.js.map +7 -0
- package/dist/modules/sales/acl.js +1 -0
- package/dist/modules/sales/acl.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +12 -0
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +62 -0
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/events.js +63 -0
- package/dist/modules/sales/events.js.map +7 -0
- package/dist/modules/sales/lib/dictionaries.js +3 -0
- package/dist/modules/sales/lib/dictionaries.js.map +2 -2
- package/dist/modules/sales/lib/frontend/documentDataEvents.js +25 -0
- package/dist/modules/sales/lib/frontend/documentDataEvents.js.map +7 -0
- package/dist/modules/workflows/acl.js +2 -0
- package/dist/modules/workflows/acl.js.map +2 -2
- package/dist/modules/workflows/api/instances/route.js +18 -6
- package/dist/modules/workflows/api/instances/route.js.map +2 -2
- package/dist/modules/workflows/api/tasks/route.js +6 -1
- package/dist/modules/workflows/api/tasks/route.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/[id]/page.js +9 -1
- package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/[id]/page.meta.js +1 -1
- package/dist/modules/workflows/backend/definitions/[id]/page.meta.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/create/page.js +24 -15
- package/dist/modules/workflows/backend/definitions/create/page.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/create/page.meta.js +1 -1
- package/dist/modules/workflows/backend/definitions/create/page.meta.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js +150 -132
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/visual-editor/page.meta.js +1 -1
- package/dist/modules/workflows/backend/definitions/visual-editor/page.meta.js.map +2 -2
- package/dist/modules/workflows/backend/events/[id]/page.js +1 -1
- package/dist/modules/workflows/backend/events/[id]/page.js.map +2 -2
- package/dist/modules/workflows/backend/events/[id]/page.meta.js +2 -2
- package/dist/modules/workflows/backend/events/[id]/page.meta.js.map +2 -2
- package/dist/modules/workflows/backend/instances/[id]/page.meta.js +2 -2
- package/dist/modules/workflows/backend/instances/[id]/page.meta.js.map +2 -2
- package/dist/modules/workflows/backend/tasks/[id]/page.js +1 -1
- package/dist/modules/workflows/backend/tasks/[id]/page.js.map +2 -2
- package/dist/modules/workflows/backend/tasks/[id]/page.meta.js +2 -2
- package/dist/modules/workflows/backend/tasks/[id]/page.meta.js.map +2 -2
- package/dist/modules/workflows/backend/tasks/page.js +5 -6
- package/dist/modules/workflows/backend/tasks/page.js.map +2 -2
- package/dist/modules/workflows/cli.js +81 -3
- package/dist/modules/workflows/cli.js.map +3 -3
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js +481 -0
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +7 -0
- package/dist/modules/workflows/components/EventTriggersEditor.js +553 -0
- package/dist/modules/workflows/components/EventTriggersEditor.js.map +7 -0
- package/dist/modules/workflows/data/entities.js +64 -1
- package/dist/modules/workflows/data/entities.js.map +2 -2
- package/dist/modules/workflows/data/validators.js +115 -0
- package/dist/modules/workflows/data/validators.js.map +2 -2
- package/dist/modules/workflows/events.js +38 -0
- package/dist/modules/workflows/events.js.map +7 -0
- package/dist/modules/workflows/examples/checkout-demo-definition.json +1 -5
- package/dist/modules/workflows/examples/order-approval-definition.json +257 -0
- package/dist/modules/workflows/examples/order-approval-guard-rules.json +32 -0
- package/dist/modules/workflows/lib/activity-executor.js +75 -13
- package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
- package/dist/modules/workflows/lib/event-trigger-service.js +308 -0
- package/dist/modules/workflows/lib/event-trigger-service.js.map +7 -0
- package/dist/modules/workflows/lib/graph-utils.js +71 -2
- package/dist/modules/workflows/lib/graph-utils.js.map +2 -2
- package/dist/modules/workflows/lib/seeds.js +22 -5
- package/dist/modules/workflows/lib/seeds.js.map +2 -2
- package/dist/modules/workflows/lib/start-validator.js +33 -23
- package/dist/modules/workflows/lib/start-validator.js.map +2 -2
- package/dist/modules/workflows/lib/transition-handler.js +157 -45
- package/dist/modules/workflows/lib/transition-handler.js.map +3 -3
- package/dist/modules/workflows/migrations/Migration20260123143500.js +36 -0
- package/dist/modules/workflows/migrations/Migration20260123143500.js.map +7 -0
- package/dist/modules/workflows/subscribers/event-trigger.js +78 -0
- package/dist/modules/workflows/subscribers/event-trigger.js.map +7 -0
- package/dist/modules/workflows/widgets/injection/order-approval/widget.client.js +323 -0
- package/dist/modules/workflows/widgets/injection/order-approval/widget.client.js.map +7 -0
- package/dist/modules/workflows/widgets/injection/order-approval/widget.js +17 -0
- package/dist/modules/workflows/widgets/injection/order-approval/widget.js.map +7 -0
- package/dist/modules/workflows/widgets/injection-table.js +19 -0
- package/dist/modules/workflows/widgets/injection-table.js.map +7 -0
- package/generated/entities/workflow_event_trigger/index.ts +15 -0
- package/generated/entities.ids.generated.ts +1 -0
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +2 -2
- package/src/modules/auth/events.ts +39 -0
- package/src/modules/business_rules/api/execute/[ruleId]/route.ts +163 -0
- package/src/modules/business_rules/data/validators.ts +40 -0
- package/src/modules/business_rules/index.ts +25 -0
- package/src/modules/business_rules/lib/rule-engine.ts +281 -1
- package/src/modules/catalog/events.ts +45 -0
- package/src/modules/customers/events.ts +63 -0
- package/src/modules/directory/events.ts +31 -0
- package/src/modules/sales/acl.ts +1 -0
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +16 -0
- package/src/modules/sales/commands/documents.ts +74 -1
- package/src/modules/sales/events.ts +82 -0
- package/src/modules/sales/lib/dictionaries.ts +3 -0
- package/src/modules/sales/lib/frontend/documentDataEvents.ts +28 -0
- package/src/modules/workflows/acl.ts +2 -0
- package/src/modules/workflows/api/__tests__/instances.route.test.ts +5 -2
- package/src/modules/workflows/api/instances/route.ts +21 -7
- package/src/modules/workflows/api/tasks/route.ts +7 -1
- package/src/modules/workflows/backend/definitions/[id]/page.meta.ts +1 -1
- package/src/modules/workflows/backend/definitions/[id]/page.tsx +9 -0
- package/src/modules/workflows/backend/definitions/create/page.meta.ts +1 -1
- package/src/modules/workflows/backend/definitions/create/page.tsx +9 -0
- package/src/modules/workflows/backend/definitions/visual-editor/page.meta.ts +1 -1
- package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +21 -3
- package/src/modules/workflows/backend/events/[id]/page.meta.ts +2 -2
- package/src/modules/workflows/backend/events/[id]/page.tsx +1 -1
- package/src/modules/workflows/backend/instances/[id]/page.meta.ts +2 -2
- package/src/modules/workflows/backend/tasks/[id]/page.meta.ts +2 -2
- package/src/modules/workflows/backend/tasks/[id]/page.tsx +1 -1
- package/src/modules/workflows/backend/tasks/page.tsx +5 -6
- package/src/modules/workflows/cli.ts +111 -0
- package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +581 -0
- package/src/modules/workflows/components/EventTriggersEditor.tsx +664 -0
- package/src/modules/workflows/data/entities.ts +124 -0
- package/src/modules/workflows/data/validators.ts +138 -0
- package/src/modules/workflows/events.ts +49 -0
- package/src/modules/workflows/examples/checkout-demo-definition.json +1 -5
- package/src/modules/workflows/examples/order-approval-definition.json +257 -0
- package/src/modules/workflows/examples/order-approval-guard-rules.json +32 -0
- package/src/modules/workflows/i18n/en.json +71 -0
- package/src/modules/workflows/lib/__tests__/activity-executor.test.ts +43 -36
- package/src/modules/workflows/lib/__tests__/transition-handler.test.ts +170 -90
- package/src/modules/workflows/lib/activity-executor.ts +129 -16
- package/src/modules/workflows/lib/event-trigger-service.ts +557 -0
- package/src/modules/workflows/lib/graph-utils.ts +117 -2
- package/src/modules/workflows/lib/seeds.ts +34 -8
- package/src/modules/workflows/lib/start-validator.ts +38 -28
- package/src/modules/workflows/lib/transition-handler.ts +208 -55
- package/src/modules/workflows/migrations/Migration20260123143500.ts +38 -0
- package/src/modules/workflows/subscribers/event-trigger.ts +109 -0
- package/src/modules/workflows/widgets/injection/order-approval/widget.client.tsx +446 -0
- package/src/modules/workflows/widgets/injection/order-approval/widget.ts +16 -0
- package/src/modules/workflows/widgets/injection-table.ts +21 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
5
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
7
|
+
import * as ruleEngine from '../../../lib/rule-engine'
|
|
8
|
+
|
|
9
|
+
const executeByIdRequestSchema = z.object({
|
|
10
|
+
data: z.any(),
|
|
11
|
+
dryRun: z.boolean().optional().default(false),
|
|
12
|
+
entityType: z.string().optional(),
|
|
13
|
+
entityId: z.string().optional(),
|
|
14
|
+
eventType: z.string().optional(),
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const executeByIdResponseSchema = z.object({
|
|
18
|
+
success: z.boolean(),
|
|
19
|
+
ruleId: z.string(),
|
|
20
|
+
ruleName: z.string(),
|
|
21
|
+
conditionResult: z.boolean(),
|
|
22
|
+
actionsExecuted: z.object({
|
|
23
|
+
success: z.boolean(),
|
|
24
|
+
results: z.array(z.object({
|
|
25
|
+
type: z.string(),
|
|
26
|
+
success: z.boolean(),
|
|
27
|
+
error: z.string().optional(),
|
|
28
|
+
})),
|
|
29
|
+
}).nullable(),
|
|
30
|
+
executionTime: z.number(),
|
|
31
|
+
error: z.string().optional(),
|
|
32
|
+
logId: z.string().optional(),
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const errorResponseSchema = z.object({
|
|
36
|
+
error: z.string(),
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const routeMetadata = {
|
|
40
|
+
POST: { requireAuth: true, requireFeatures: ['business_rules.execute'] },
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const metadata = routeMetadata
|
|
44
|
+
|
|
45
|
+
interface RouteContext {
|
|
46
|
+
params: Promise<{ ruleId: string }>
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function POST(req: Request, context: RouteContext) {
|
|
50
|
+
const auth = await getAuthFromRequest(req)
|
|
51
|
+
if (!auth) {
|
|
52
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const params = await context.params
|
|
56
|
+
const ruleId = params.ruleId
|
|
57
|
+
|
|
58
|
+
if (!ruleId || !z.uuid().safeParse(ruleId).success) {
|
|
59
|
+
return NextResponse.json({ error: 'Invalid rule ID' }, { status: 400 })
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const container = await createRequestContainer()
|
|
63
|
+
const em = container.resolve('em') as EntityManager
|
|
64
|
+
|
|
65
|
+
let body: any
|
|
66
|
+
try {
|
|
67
|
+
body = await req.json()
|
|
68
|
+
} catch {
|
|
69
|
+
return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const parsed = executeByIdRequestSchema.safeParse(body)
|
|
73
|
+
if (!parsed.success) {
|
|
74
|
+
const errors = parsed.error.issues.map(e => `${e.path.join('.')}: ${e.message}`)
|
|
75
|
+
return NextResponse.json({ error: `Validation failed: ${errors.join(', ')}` }, { status: 400 })
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const { data, dryRun, entityType, entityId, eventType } = parsed.data
|
|
79
|
+
|
|
80
|
+
const execContext: ruleEngine.DirectRuleExecutionContext = {
|
|
81
|
+
ruleId,
|
|
82
|
+
data,
|
|
83
|
+
user: {
|
|
84
|
+
id: auth.sub,
|
|
85
|
+
email: auth.email,
|
|
86
|
+
role: (auth.role as string) ?? undefined,
|
|
87
|
+
},
|
|
88
|
+
tenantId: auth.tenantId ?? '',
|
|
89
|
+
organizationId: auth.orgId ?? '',
|
|
90
|
+
executedBy: auth.sub ?? auth.email ?? undefined,
|
|
91
|
+
dryRun,
|
|
92
|
+
entityType,
|
|
93
|
+
entityId,
|
|
94
|
+
eventType,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const result = await ruleEngine.executeRuleById(em, execContext)
|
|
99
|
+
|
|
100
|
+
const response = {
|
|
101
|
+
success: result.success,
|
|
102
|
+
ruleId: result.ruleId,
|
|
103
|
+
ruleName: result.ruleName,
|
|
104
|
+
conditionResult: result.conditionResult,
|
|
105
|
+
actionsExecuted: result.actionsExecuted ? {
|
|
106
|
+
success: result.actionsExecuted.success,
|
|
107
|
+
results: result.actionsExecuted.results.map(ar => ({
|
|
108
|
+
type: ar.action.type,
|
|
109
|
+
success: ar.success,
|
|
110
|
+
error: ar.error,
|
|
111
|
+
})),
|
|
112
|
+
} : null,
|
|
113
|
+
executionTime: result.executionTime,
|
|
114
|
+
error: result.error,
|
|
115
|
+
logId: result.logId,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Return appropriate status based on result
|
|
119
|
+
const status = result.success ? 200 : (result.error === 'Rule not found' ? 404 : 200)
|
|
120
|
+
return NextResponse.json(response, { status })
|
|
121
|
+
} catch (error) {
|
|
122
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
123
|
+
return NextResponse.json(
|
|
124
|
+
{ error: `Rule execution failed: ${errorMessage}` },
|
|
125
|
+
{ status: 500 }
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const openApi: OpenApiRouteDoc = {
|
|
131
|
+
tag: 'Business Rules',
|
|
132
|
+
summary: 'Execute a specific business rule by ID',
|
|
133
|
+
methods: {
|
|
134
|
+
POST: {
|
|
135
|
+
summary: 'Execute a specific rule by its database UUID',
|
|
136
|
+
description: 'Directly executes a specific business rule identified by its UUID, bypassing the normal entityType/eventType discovery mechanism. Useful for workflows and targeted rule execution.',
|
|
137
|
+
pathParams: z.object({
|
|
138
|
+
ruleId: z.string().uuid().describe('The database UUID of the business rule to execute'),
|
|
139
|
+
}),
|
|
140
|
+
requestBody: {
|
|
141
|
+
contentType: 'application/json',
|
|
142
|
+
schema: executeByIdRequestSchema,
|
|
143
|
+
},
|
|
144
|
+
responses: [
|
|
145
|
+
{
|
|
146
|
+
status: 200,
|
|
147
|
+
description: 'Rule executed successfully',
|
|
148
|
+
schema: executeByIdResponseSchema,
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
status: 404,
|
|
152
|
+
description: 'Rule not found',
|
|
153
|
+
schema: errorResponseSchema,
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
errors: [
|
|
157
|
+
{ status: 400, description: 'Invalid request payload or rule ID', schema: errorResponseSchema },
|
|
158
|
+
{ status: 401, description: 'Unauthorized', schema: errorResponseSchema },
|
|
159
|
+
{ status: 500, description: 'Execution error', schema: errorResponseSchema },
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
}
|
|
@@ -287,3 +287,43 @@ export const ruleDiscoveryOptionsSchema = z.object({
|
|
|
287
287
|
})
|
|
288
288
|
|
|
289
289
|
export type RuleDiscoveryOptionsInput = z.infer<typeof ruleDiscoveryOptionsSchema>
|
|
290
|
+
|
|
291
|
+
// Direct Rule Execution Context Schema (for executing a specific rule by ID)
|
|
292
|
+
export const directRuleExecutionContextSchema = z.object({
|
|
293
|
+
ruleId: z.uuid('ruleId must be a valid UUID'),
|
|
294
|
+
data: z.any(),
|
|
295
|
+
user: z.looseObject({
|
|
296
|
+
id: z.string().optional(),
|
|
297
|
+
email: z.string().optional(),
|
|
298
|
+
role: z.string().optional(),
|
|
299
|
+
}).optional(),
|
|
300
|
+
tenantId: z.uuid('tenantId must be a valid UUID'),
|
|
301
|
+
organizationId: z.uuid('organizationId must be a valid UUID'),
|
|
302
|
+
executedBy: z.string().optional(),
|
|
303
|
+
dryRun: z.boolean().optional(),
|
|
304
|
+
entityType: z.string().optional(),
|
|
305
|
+
entityId: z.string().optional(),
|
|
306
|
+
eventType: z.string().optional(),
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
export type DirectRuleExecutionContextInput = z.infer<typeof directRuleExecutionContextSchema>
|
|
310
|
+
|
|
311
|
+
// Rule ID Execution Context Schema (for executing a specific rule by its string rule_id identifier)
|
|
312
|
+
export const ruleIdExecutionContextSchema = z.object({
|
|
313
|
+
ruleId: z.string().min(1, 'ruleId must be a non-empty string').max(50),
|
|
314
|
+
data: z.any(),
|
|
315
|
+
user: z.looseObject({
|
|
316
|
+
id: z.string().optional(),
|
|
317
|
+
email: z.string().optional(),
|
|
318
|
+
role: z.string().optional(),
|
|
319
|
+
}).optional(),
|
|
320
|
+
tenantId: z.uuid('tenantId must be a valid UUID'),
|
|
321
|
+
organizationId: z.uuid('organizationId must be a valid UUID'),
|
|
322
|
+
executedBy: z.string().optional(),
|
|
323
|
+
dryRun: z.boolean().optional(),
|
|
324
|
+
entityType: z.string().optional(),
|
|
325
|
+
entityId: z.string().optional(),
|
|
326
|
+
eventType: z.string().optional(),
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
export type RuleIdExecutionContextInput = z.infer<typeof ruleIdExecutionContextSchema>
|
|
@@ -8,3 +8,28 @@ export const metadata: ModuleInfo = {
|
|
|
8
8
|
author: 'Patryk Lewczuk',
|
|
9
9
|
license: 'Proprietary',
|
|
10
10
|
}
|
|
11
|
+
|
|
12
|
+
// Export rule engine types and functions for programmatic usage
|
|
13
|
+
export {
|
|
14
|
+
executeRules,
|
|
15
|
+
executeRuleById,
|
|
16
|
+
executeRuleByRuleId,
|
|
17
|
+
executeSingleRule,
|
|
18
|
+
findApplicableRules,
|
|
19
|
+
logRuleExecution,
|
|
20
|
+
type RuleEngineContext,
|
|
21
|
+
type RuleEngineResult,
|
|
22
|
+
type RuleExecutionResult,
|
|
23
|
+
type RuleDiscoveryOptions,
|
|
24
|
+
type DirectRuleExecutionContext,
|
|
25
|
+
type DirectRuleExecutionResult,
|
|
26
|
+
type RuleIdExecutionContext,
|
|
27
|
+
} from './lib/rule-engine'
|
|
28
|
+
|
|
29
|
+
// Export validator schemas
|
|
30
|
+
export {
|
|
31
|
+
directRuleExecutionContextSchema,
|
|
32
|
+
ruleIdExecutionContextSchema,
|
|
33
|
+
type DirectRuleExecutionContextInput,
|
|
34
|
+
type RuleIdExecutionContextInput,
|
|
35
|
+
} from './data/validators'
|
|
@@ -4,7 +4,7 @@ import * as ruleEvaluator from './rule-evaluator'
|
|
|
4
4
|
import * as actionExecutor from './action-executor'
|
|
5
5
|
import type { RuleEvaluationContext } from './rule-evaluator'
|
|
6
6
|
import type { ActionContext, ActionExecutionOutcome } from './action-executor'
|
|
7
|
-
import { ruleEngineContextSchema, ruleDiscoveryOptionsSchema } from '../data/validators'
|
|
7
|
+
import { ruleEngineContextSchema, ruleDiscoveryOptionsSchema, directRuleExecutionContextSchema, ruleIdExecutionContextSchema } from '../data/validators'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Constants
|
|
@@ -85,6 +85,65 @@ export interface RuleDiscoveryOptions {
|
|
|
85
85
|
ruleType?: RuleType
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Direct rule execution context (for executing a specific rule by ID)
|
|
90
|
+
*/
|
|
91
|
+
export interface DirectRuleExecutionContext {
|
|
92
|
+
ruleId: string // Database UUID of the rule
|
|
93
|
+
data: any
|
|
94
|
+
user?: {
|
|
95
|
+
id?: string
|
|
96
|
+
email?: string
|
|
97
|
+
role?: string
|
|
98
|
+
[key: string]: any
|
|
99
|
+
}
|
|
100
|
+
tenantId: string
|
|
101
|
+
organizationId: string
|
|
102
|
+
executedBy?: string
|
|
103
|
+
dryRun?: boolean
|
|
104
|
+
// Optional for logging (falls back to rule's entityType)
|
|
105
|
+
entityType?: string
|
|
106
|
+
entityId?: string
|
|
107
|
+
eventType?: string
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Direct rule execution result
|
|
112
|
+
*/
|
|
113
|
+
export interface DirectRuleExecutionResult {
|
|
114
|
+
success: boolean
|
|
115
|
+
ruleId: string
|
|
116
|
+
ruleName: string
|
|
117
|
+
conditionResult: boolean
|
|
118
|
+
actionsExecuted: ActionExecutionOutcome | null
|
|
119
|
+
executionTime: number
|
|
120
|
+
error?: string
|
|
121
|
+
logId?: string
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Context for executing a rule by its string rule_id identifier
|
|
126
|
+
* Unlike DirectRuleExecutionContext which uses database UUID,
|
|
127
|
+
* this uses the string identifier (e.g., "workflow_checkout_inventory_available")
|
|
128
|
+
*/
|
|
129
|
+
export interface RuleIdExecutionContext {
|
|
130
|
+
ruleId: string // String identifier (e.g., "workflow_checkout_inventory_available")
|
|
131
|
+
data: any
|
|
132
|
+
user?: {
|
|
133
|
+
id?: string
|
|
134
|
+
email?: string
|
|
135
|
+
role?: string
|
|
136
|
+
[key: string]: any
|
|
137
|
+
}
|
|
138
|
+
tenantId: string
|
|
139
|
+
organizationId: string
|
|
140
|
+
executedBy?: string
|
|
141
|
+
dryRun?: boolean
|
|
142
|
+
entityType?: string
|
|
143
|
+
entityId?: string
|
|
144
|
+
eventType?: string
|
|
145
|
+
}
|
|
146
|
+
|
|
88
147
|
/**
|
|
89
148
|
* Execute a function with a timeout
|
|
90
149
|
*/
|
|
@@ -404,6 +463,227 @@ export async function findApplicableRules(
|
|
|
404
463
|
})
|
|
405
464
|
}
|
|
406
465
|
|
|
466
|
+
/**
|
|
467
|
+
* Execute a specific rule by its database UUID
|
|
468
|
+
* This bypasses the entityType/eventType discovery mechanism and directly executes the rule
|
|
469
|
+
*/
|
|
470
|
+
export async function executeRuleById(
|
|
471
|
+
em: EntityManager,
|
|
472
|
+
context: DirectRuleExecutionContext
|
|
473
|
+
): Promise<DirectRuleExecutionResult> {
|
|
474
|
+
const startTime = Date.now()
|
|
475
|
+
|
|
476
|
+
// Validate input
|
|
477
|
+
const validation = directRuleExecutionContextSchema.safeParse(context)
|
|
478
|
+
if (!validation.success) {
|
|
479
|
+
const validationErrors = validation.error.issues.map(e => `${e.path.join('.')}: ${e.message}`)
|
|
480
|
+
return {
|
|
481
|
+
success: false,
|
|
482
|
+
ruleId: context.ruleId,
|
|
483
|
+
ruleName: 'Unknown',
|
|
484
|
+
conditionResult: false,
|
|
485
|
+
actionsExecuted: null,
|
|
486
|
+
executionTime: Date.now() - startTime,
|
|
487
|
+
error: `Validation failed: ${validationErrors.join(', ')}`,
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Fetch rule by ID with tenant/org validation
|
|
492
|
+
const rule = await em.findOne(BusinessRule, {
|
|
493
|
+
id: context.ruleId,
|
|
494
|
+
tenantId: context.tenantId,
|
|
495
|
+
organizationId: context.organizationId,
|
|
496
|
+
deletedAt: null,
|
|
497
|
+
})
|
|
498
|
+
|
|
499
|
+
if (!rule) {
|
|
500
|
+
return {
|
|
501
|
+
success: false,
|
|
502
|
+
ruleId: context.ruleId,
|
|
503
|
+
ruleName: 'Unknown',
|
|
504
|
+
conditionResult: false,
|
|
505
|
+
actionsExecuted: null,
|
|
506
|
+
executionTime: Date.now() - startTime,
|
|
507
|
+
error: 'Rule not found',
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (!rule.enabled) {
|
|
512
|
+
return {
|
|
513
|
+
success: false,
|
|
514
|
+
ruleId: rule.ruleId,
|
|
515
|
+
ruleName: rule.ruleName,
|
|
516
|
+
conditionResult: false,
|
|
517
|
+
actionsExecuted: null,
|
|
518
|
+
executionTime: Date.now() - startTime,
|
|
519
|
+
error: 'Rule is disabled',
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Check effective date range
|
|
524
|
+
const now = new Date()
|
|
525
|
+
if (rule.effectiveFrom && rule.effectiveFrom > now) {
|
|
526
|
+
return {
|
|
527
|
+
success: false,
|
|
528
|
+
ruleId: rule.ruleId,
|
|
529
|
+
ruleName: rule.ruleName,
|
|
530
|
+
conditionResult: false,
|
|
531
|
+
actionsExecuted: null,
|
|
532
|
+
executionTime: Date.now() - startTime,
|
|
533
|
+
error: `Rule is not yet effective (starts ${rule.effectiveFrom.toISOString()})`,
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
if (rule.effectiveTo && rule.effectiveTo < now) {
|
|
537
|
+
return {
|
|
538
|
+
success: false,
|
|
539
|
+
ruleId: rule.ruleId,
|
|
540
|
+
ruleName: rule.ruleName,
|
|
541
|
+
conditionResult: false,
|
|
542
|
+
actionsExecuted: null,
|
|
543
|
+
executionTime: Date.now() - startTime,
|
|
544
|
+
error: `Rule has expired (ended ${rule.effectiveTo.toISOString()})`,
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Build RuleEngineContext (use provided entityType or fall back to rule's)
|
|
549
|
+
const engineContext: RuleEngineContext = {
|
|
550
|
+
entityType: context.entityType || rule.entityType,
|
|
551
|
+
entityId: context.entityId,
|
|
552
|
+
eventType: context.eventType || rule.eventType || undefined,
|
|
553
|
+
data: context.data,
|
|
554
|
+
user: context.user,
|
|
555
|
+
tenantId: context.tenantId,
|
|
556
|
+
organizationId: context.organizationId,
|
|
557
|
+
executedBy: context.executedBy,
|
|
558
|
+
dryRun: context.dryRun,
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Execute via existing executeSingleRule
|
|
562
|
+
const result = await executeSingleRule(em, rule, engineContext)
|
|
563
|
+
|
|
564
|
+
return {
|
|
565
|
+
success: !result.error,
|
|
566
|
+
ruleId: rule.ruleId,
|
|
567
|
+
ruleName: rule.ruleName,
|
|
568
|
+
conditionResult: result.conditionResult,
|
|
569
|
+
actionsExecuted: result.actionsExecuted,
|
|
570
|
+
executionTime: result.executionTime,
|
|
571
|
+
error: result.error,
|
|
572
|
+
logId: result.logId,
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Execute a rule by its string rule_id identifier
|
|
578
|
+
* Looks up rule by rule_id (string column) + tenant_id (unique constraint)
|
|
579
|
+
* This is useful for workflow conditions that reference rules by their string identifiers
|
|
580
|
+
*/
|
|
581
|
+
export async function executeRuleByRuleId(
|
|
582
|
+
em: EntityManager,
|
|
583
|
+
context: RuleIdExecutionContext
|
|
584
|
+
): Promise<DirectRuleExecutionResult> {
|
|
585
|
+
const startTime = Date.now()
|
|
586
|
+
|
|
587
|
+
// Validate input
|
|
588
|
+
const validation = ruleIdExecutionContextSchema.safeParse(context)
|
|
589
|
+
if (!validation.success) {
|
|
590
|
+
const validationErrors = validation.error.issues.map(e => `${e.path.join('.')}: ${e.message}`)
|
|
591
|
+
return {
|
|
592
|
+
success: false,
|
|
593
|
+
ruleId: context.ruleId || 'unknown',
|
|
594
|
+
ruleName: 'Unknown',
|
|
595
|
+
conditionResult: false,
|
|
596
|
+
actionsExecuted: null,
|
|
597
|
+
executionTime: Date.now() - startTime,
|
|
598
|
+
error: `Validation failed: ${validationErrors.join(', ')}`,
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Fetch rule by rule_id (string identifier) + tenant/org
|
|
603
|
+
const rule = await em.findOne(BusinessRule, {
|
|
604
|
+
ruleId: context.ruleId, // String identifier column
|
|
605
|
+
tenantId: context.tenantId,
|
|
606
|
+
organizationId: context.organizationId,
|
|
607
|
+
deletedAt: null,
|
|
608
|
+
})
|
|
609
|
+
|
|
610
|
+
if (!rule) {
|
|
611
|
+
return {
|
|
612
|
+
success: false,
|
|
613
|
+
ruleId: context.ruleId,
|
|
614
|
+
ruleName: 'Unknown',
|
|
615
|
+
conditionResult: false,
|
|
616
|
+
actionsExecuted: null,
|
|
617
|
+
executionTime: Date.now() - startTime,
|
|
618
|
+
error: 'Rule not found',
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
if (!rule.enabled) {
|
|
623
|
+
return {
|
|
624
|
+
success: false,
|
|
625
|
+
ruleId: rule.ruleId,
|
|
626
|
+
ruleName: rule.ruleName,
|
|
627
|
+
conditionResult: false,
|
|
628
|
+
actionsExecuted: null,
|
|
629
|
+
executionTime: Date.now() - startTime,
|
|
630
|
+
error: 'Rule is disabled',
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Check effective date range
|
|
635
|
+
const now = new Date()
|
|
636
|
+
if (rule.effectiveFrom && rule.effectiveFrom > now) {
|
|
637
|
+
return {
|
|
638
|
+
success: false,
|
|
639
|
+
ruleId: rule.ruleId,
|
|
640
|
+
ruleName: rule.ruleName,
|
|
641
|
+
conditionResult: false,
|
|
642
|
+
actionsExecuted: null,
|
|
643
|
+
executionTime: Date.now() - startTime,
|
|
644
|
+
error: `Rule is not yet effective (starts ${rule.effectiveFrom.toISOString()})`,
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
if (rule.effectiveTo && rule.effectiveTo < now) {
|
|
648
|
+
return {
|
|
649
|
+
success: false,
|
|
650
|
+
ruleId: rule.ruleId,
|
|
651
|
+
ruleName: rule.ruleName,
|
|
652
|
+
conditionResult: false,
|
|
653
|
+
actionsExecuted: null,
|
|
654
|
+
executionTime: Date.now() - startTime,
|
|
655
|
+
error: `Rule has expired (ended ${rule.effectiveTo.toISOString()})`,
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Build RuleEngineContext (use provided entityType or fall back to rule's)
|
|
660
|
+
const engineContext: RuleEngineContext = {
|
|
661
|
+
entityType: context.entityType || rule.entityType,
|
|
662
|
+
entityId: context.entityId,
|
|
663
|
+
eventType: context.eventType || rule.eventType || undefined,
|
|
664
|
+
data: context.data,
|
|
665
|
+
user: context.user,
|
|
666
|
+
tenantId: context.tenantId,
|
|
667
|
+
organizationId: context.organizationId,
|
|
668
|
+
executedBy: context.executedBy,
|
|
669
|
+
dryRun: context.dryRun,
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Execute via existing executeSingleRule
|
|
673
|
+
const result = await executeSingleRule(em, rule, engineContext)
|
|
674
|
+
|
|
675
|
+
return {
|
|
676
|
+
success: !result.error,
|
|
677
|
+
ruleId: rule.ruleId,
|
|
678
|
+
ruleName: rule.ruleName,
|
|
679
|
+
conditionResult: result.conditionResult,
|
|
680
|
+
actionsExecuted: result.actionsExecuted,
|
|
681
|
+
executionTime: result.executionTime,
|
|
682
|
+
error: result.error,
|
|
683
|
+
logId: result.logId,
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
407
687
|
/**
|
|
408
688
|
* Sensitive field patterns to exclude from logs
|
|
409
689
|
*/
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createModuleEvents } from '@open-mercato/shared/modules/events'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Catalog Module Events
|
|
5
|
+
*
|
|
6
|
+
* Declares all events that can be emitted by the catalog module.
|
|
7
|
+
*/
|
|
8
|
+
const events = [
|
|
9
|
+
// Products
|
|
10
|
+
{ id: 'catalog.product.created', label: 'Product Created', entity: 'product', category: 'crud' },
|
|
11
|
+
{ id: 'catalog.product.updated', label: 'Product Updated', entity: 'product', category: 'crud' },
|
|
12
|
+
{ id: 'catalog.product.deleted', label: 'Product Deleted', entity: 'product', category: 'crud' },
|
|
13
|
+
|
|
14
|
+
// Categories
|
|
15
|
+
{ id: 'catalog.category.created', label: 'Category Created', entity: 'category', category: 'crud' },
|
|
16
|
+
{ id: 'catalog.category.updated', label: 'Category Updated', entity: 'category', category: 'crud' },
|
|
17
|
+
{ id: 'catalog.category.deleted', label: 'Category Deleted', entity: 'category', category: 'crud' },
|
|
18
|
+
|
|
19
|
+
// Variants
|
|
20
|
+
{ id: 'catalog.variant.created', label: 'Variant Created', entity: 'variant', category: 'crud' },
|
|
21
|
+
{ id: 'catalog.variant.updated', label: 'Variant Updated', entity: 'variant', category: 'crud' },
|
|
22
|
+
{ id: 'catalog.variant.deleted', label: 'Variant Deleted', entity: 'variant', category: 'crud' },
|
|
23
|
+
|
|
24
|
+
// Prices
|
|
25
|
+
{ id: 'catalog.price.created', label: 'Price Created', entity: 'price', category: 'crud' },
|
|
26
|
+
{ id: 'catalog.price.updated', label: 'Price Updated', entity: 'price', category: 'crud' },
|
|
27
|
+
{ id: 'catalog.price.deleted', label: 'Price Deleted', entity: 'price', category: 'crud' },
|
|
28
|
+
|
|
29
|
+
// Lifecycle events - Pricing resolution
|
|
30
|
+
{ id: 'catalog.pricing.resolve.before', label: 'Before Pricing Resolve', category: 'lifecycle', excludeFromTriggers: true },
|
|
31
|
+
{ id: 'catalog.pricing.resolve.after', label: 'After Pricing Resolve', category: 'lifecycle', excludeFromTriggers: true },
|
|
32
|
+
] as const
|
|
33
|
+
|
|
34
|
+
export const eventsConfig = createModuleEvents({
|
|
35
|
+
moduleId: 'catalog',
|
|
36
|
+
events,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
/** Type-safe event emitter for catalog module */
|
|
40
|
+
export const emitCatalogEvent = eventsConfig.emit
|
|
41
|
+
|
|
42
|
+
/** Event IDs that can be emitted by the catalog module */
|
|
43
|
+
export type CatalogEventId = typeof events[number]['id']
|
|
44
|
+
|
|
45
|
+
export default eventsConfig
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { createModuleEvents } from '@open-mercato/shared/modules/events'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Customers Module Events
|
|
5
|
+
*
|
|
6
|
+
* Declares all events that can be emitted by the customers module.
|
|
7
|
+
*/
|
|
8
|
+
const events = [
|
|
9
|
+
// People
|
|
10
|
+
{ id: 'customers.people.created', label: 'Customer (Person) Created', entity: 'people', category: 'crud' },
|
|
11
|
+
{ id: 'customers.people.updated', label: 'Customer (Person) Updated', entity: 'people', category: 'crud' },
|
|
12
|
+
{ id: 'customers.people.deleted', label: 'Customer (Person) Deleted', entity: 'people', category: 'crud' },
|
|
13
|
+
|
|
14
|
+
// Companies
|
|
15
|
+
{ id: 'customers.companies.created', label: 'Customer (Company) Created', entity: 'companies', category: 'crud' },
|
|
16
|
+
{ id: 'customers.companies.updated', label: 'Customer (Company) Updated', entity: 'companies', category: 'crud' },
|
|
17
|
+
{ id: 'customers.companies.deleted', label: 'Customer (Company) Deleted', entity: 'companies', category: 'crud' },
|
|
18
|
+
|
|
19
|
+
// Deals
|
|
20
|
+
{ id: 'customers.deals.created', label: 'Deal Created', entity: 'deals', category: 'crud' },
|
|
21
|
+
{ id: 'customers.deals.updated', label: 'Deal Updated', entity: 'deals', category: 'crud' },
|
|
22
|
+
{ id: 'customers.deals.deleted', label: 'Deal Deleted', entity: 'deals', category: 'crud' },
|
|
23
|
+
|
|
24
|
+
// Comments
|
|
25
|
+
{ id: 'customers.comments.created', label: 'Comment Created', entity: 'comments', category: 'crud' },
|
|
26
|
+
{ id: 'customers.comments.updated', label: 'Comment Updated', entity: 'comments', category: 'crud' },
|
|
27
|
+
{ id: 'customers.comments.deleted', label: 'Comment Deleted', entity: 'comments', category: 'crud' },
|
|
28
|
+
|
|
29
|
+
// Addresses
|
|
30
|
+
{ id: 'customers.addresses.created', label: 'Address Created', entity: 'addresses', category: 'crud' },
|
|
31
|
+
{ id: 'customers.addresses.updated', label: 'Address Updated', entity: 'addresses', category: 'crud' },
|
|
32
|
+
{ id: 'customers.addresses.deleted', label: 'Address Deleted', entity: 'addresses', category: 'crud' },
|
|
33
|
+
|
|
34
|
+
// Activities
|
|
35
|
+
{ id: 'customers.activities.created', label: 'Activity Created', entity: 'activities', category: 'crud' },
|
|
36
|
+
{ id: 'customers.activities.updated', label: 'Activity Updated', entity: 'activities', category: 'crud' },
|
|
37
|
+
{ id: 'customers.activities.deleted', label: 'Activity Deleted', entity: 'activities', category: 'crud' },
|
|
38
|
+
|
|
39
|
+
// Tags
|
|
40
|
+
{ id: 'customers.tags.created', label: 'Tag Created', entity: 'tags', category: 'crud' },
|
|
41
|
+
{ id: 'customers.tags.updated', label: 'Tag Updated', entity: 'tags', category: 'crud' },
|
|
42
|
+
{ id: 'customers.tags.deleted', label: 'Tag Deleted', entity: 'tags', category: 'crud' },
|
|
43
|
+
{ id: 'customers.tags.assigned', label: 'Tag Assigned', entity: 'tags', category: 'crud' },
|
|
44
|
+
{ id: 'customers.tags.removed', label: 'Tag Removed', entity: 'tags', category: 'crud' },
|
|
45
|
+
|
|
46
|
+
// Todos
|
|
47
|
+
{ id: 'customers.todos.created', label: 'Todo Created', entity: 'todos', category: 'crud' },
|
|
48
|
+
{ id: 'customers.todos.updated', label: 'Todo Updated', entity: 'todos', category: 'crud' },
|
|
49
|
+
{ id: 'customers.todos.deleted', label: 'Todo Deleted', entity: 'todos', category: 'crud' },
|
|
50
|
+
] as const
|
|
51
|
+
|
|
52
|
+
export const eventsConfig = createModuleEvents({
|
|
53
|
+
moduleId: 'customers',
|
|
54
|
+
events,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
/** Type-safe event emitter for customers module */
|
|
58
|
+
export const emitCustomersEvent = eventsConfig.emit
|
|
59
|
+
|
|
60
|
+
/** Event IDs that can be emitted by the customers module */
|
|
61
|
+
export type CustomersEventId = typeof events[number]['id']
|
|
62
|
+
|
|
63
|
+
export default eventsConfig
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createModuleEvents } from '@open-mercato/shared/modules/events'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Directory Module Events
|
|
5
|
+
*
|
|
6
|
+
* Declares all events that can be emitted by the directory module.
|
|
7
|
+
*/
|
|
8
|
+
const events = [
|
|
9
|
+
// Tenants
|
|
10
|
+
{ id: 'directory.tenants.created', label: 'Tenant Created', entity: 'tenants', category: 'crud' },
|
|
11
|
+
{ id: 'directory.tenants.updated', label: 'Tenant Updated', entity: 'tenants', category: 'crud' },
|
|
12
|
+
{ id: 'directory.tenants.deleted', label: 'Tenant Deleted', entity: 'tenants', category: 'crud' },
|
|
13
|
+
|
|
14
|
+
// Organizations
|
|
15
|
+
{ id: 'directory.organizations.created', label: 'Organization Created', entity: 'organizations', category: 'crud' },
|
|
16
|
+
{ id: 'directory.organizations.updated', label: 'Organization Updated', entity: 'organizations', category: 'crud' },
|
|
17
|
+
{ id: 'directory.organizations.deleted', label: 'Organization Deleted', entity: 'organizations', category: 'crud' },
|
|
18
|
+
] as const
|
|
19
|
+
|
|
20
|
+
export const eventsConfig = createModuleEvents({
|
|
21
|
+
moduleId: 'directory',
|
|
22
|
+
events,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
/** Type-safe event emitter for directory module */
|
|
26
|
+
export const emitDirectoryEvent = eventsConfig.emit
|
|
27
|
+
|
|
28
|
+
/** Event IDs that can be emitted by the directory module */
|
|
29
|
+
export type DirectoryEventId = typeof events[number]['id']
|
|
30
|
+
|
|
31
|
+
export default eventsConfig
|
package/src/modules/sales/acl.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export const features = [
|
|
2
2
|
{ id: 'sales.orders.view', title: 'View sales orders', module: 'sales' },
|
|
3
3
|
{ id: 'sales.orders.manage', title: 'Manage sales orders', module: 'sales' },
|
|
4
|
+
{ id: 'sales.orders.approve', title: 'Approve sales orders', module: 'sales' },
|
|
4
5
|
{ id: 'sales.quotes.view', title: 'View sales quotes', module: 'sales' },
|
|
5
6
|
{ id: 'sales.quotes.manage', title: 'Manage sales quotes', module: 'sales' },
|
|
6
7
|
{ id: 'sales.shipments.manage', title: 'Manage order shipments', module: 'sales' },
|