@open-mercato/core 0.6.7-develop.6653.1.b5bc7194a0 → 0.6.7-develop.6661.1.0043ed1d03
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 +1 -1
- package/dist/modules/business_rules/lib/value-resolver.js +7 -0
- package/dist/modules/business_rules/lib/value-resolver.js.map +2 -2
- package/dist/modules/communication_channels/lib/pg-errors.js +1 -7
- package/dist/modules/communication_channels/lib/pg-errors.js.map +2 -2
- package/dist/modules/currencies/commands/currencies.js +22 -8
- package/dist/modules/currencies/commands/currencies.js.map +2 -2
- package/dist/modules/currencies/commands/exchange-rates.js +22 -8
- package/dist/modules/currencies/commands/exchange-rates.js.map +2 -2
- package/dist/modules/currencies/commands/scope.js +22 -0
- package/dist/modules/currencies/commands/scope.js.map +7 -0
- package/dist/modules/customers/components/detail/ActivitiesSection.js +5 -4
- package/dist/modules/customers/components/detail/ActivitiesSection.js.map +2 -2
- package/dist/modules/customers/components/detail/ActivityHistorySection.js +5 -4
- package/dist/modules/customers/components/detail/ActivityHistorySection.js.map +2 -2
- package/dist/modules/feature_toggles/api/global/route.js +6 -0
- package/dist/modules/feature_toggles/api/global/route.js.map +2 -2
- package/dist/modules/feature_toggles/commands/global.js +9 -9
- package/dist/modules/feature_toggles/commands/global.js.map +2 -2
- package/dist/modules/query_index/api/openapi.js +2 -2
- package/dist/modules/query_index/api/openapi.js.map +2 -2
- package/dist/modules/query_index/api/status.js +6 -4
- package/dist/modules/query_index/api/status.js.map +2 -2
- package/dist/modules/query_index/cli.js +31 -28
- package/dist/modules/query_index/cli.js.map +2 -2
- package/dist/modules/query_index/components/QueryIndexesTable.js +4 -1
- package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
- package/dist/modules/query_index/lib/batch.js +163 -44
- package/dist/modules/query_index/lib/batch.js.map +3 -3
- package/dist/modules/query_index/lib/jobs.js +3 -2
- package/dist/modules/query_index/lib/jobs.js.map +2 -2
- package/dist/modules/query_index/lib/reindexer.js +50 -16
- package/dist/modules/query_index/lib/reindexer.js.map +2 -2
- package/dist/modules/query_index/lib/stale.js +3 -0
- package/dist/modules/query_index/lib/stale.js.map +2 -2
- package/dist/modules/query_index/lib/subscriber-scope.js +63 -10
- package/dist/modules/query_index/lib/subscriber-scope.js.map +2 -2
- package/dist/modules/query_index/subscribers/delete_one.js +20 -6
- package/dist/modules/query_index/subscribers/delete_one.js.map +2 -2
- package/dist/modules/query_index/subscribers/upsert_one.js +8 -3
- package/dist/modules/query_index/subscribers/upsert_one.js.map +2 -2
- package/dist/modules/workflows/api/definitions/[id]/customize/route.js +2 -0
- package/dist/modules/workflows/api/definitions/[id]/customize/route.js.map +2 -2
- package/dist/modules/workflows/api/definitions/[id]/reset-to-code/route.js +4 -0
- package/dist/modules/workflows/api/definitions/[id]/reset-to-code/route.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/business_rules/lib/value-resolver.ts +16 -0
- package/src/modules/communication_channels/lib/pg-errors.ts +3 -10
- package/src/modules/currencies/commands/currencies.ts +26 -8
- package/src/modules/currencies/commands/exchange-rates.ts +26 -8
- package/src/modules/currencies/commands/scope.ts +32 -0
- package/src/modules/customers/components/detail/ActivitiesSection.tsx +10 -6
- package/src/modules/customers/components/detail/ActivityHistorySection.tsx +6 -3
- package/src/modules/feature_toggles/api/global/route.ts +7 -1
- package/src/modules/feature_toggles/commands/global.ts +9 -12
- package/src/modules/query_index/api/openapi.ts +2 -2
- package/src/modules/query_index/api/status.ts +14 -5
- package/src/modules/query_index/cli.ts +36 -28
- package/src/modules/query_index/components/QueryIndexesTable.tsx +7 -4
- package/src/modules/query_index/i18n/de.json +2 -0
- package/src/modules/query_index/i18n/en.json +2 -0
- package/src/modules/query_index/i18n/es.json +2 -0
- package/src/modules/query_index/i18n/pl.json +2 -0
- package/src/modules/query_index/lib/batch.ts +244 -67
- package/src/modules/query_index/lib/jobs.ts +11 -0
- package/src/modules/query_index/lib/reindexer.ts +69 -16
- package/src/modules/query_index/lib/stale.ts +10 -0
- package/src/modules/query_index/lib/subscriber-scope.ts +98 -14
- package/src/modules/query_index/subscribers/delete_one.ts +21 -10
- package/src/modules/query_index/subscribers/upsert_one.ts +8 -3
- package/src/modules/workflows/AGENTS.md +22 -0
- package/src/modules/workflows/api/definitions/[id]/customize/route.ts +9 -0
- package/src/modules/workflows/api/definitions/[id]/reset-to-code/route.ts +7 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/workflows/api/definitions/%5Bid%5D/customize/route.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Customize Code-Based Workflow Definition\n *\n * POST /api/workflows/definitions/[id]/customize\n *\n * Creates a DB override row for a code-based workflow definition, seeded from\n * the current code registry values. The id param must be of the form\n * \"code:<workflowId>\".\n */\n\nimport { NextRequest, NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { validateCrudMutationGuard, runCrudMutationGuardAfterSuccess } from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { WorkflowDefinition } from '../../../../data/entities'\nimport { serializeWorkflowDefinition } from '../../serialize'\nimport { getCodeWorkflow } from '../../../../lib/code-registry'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('workflows')\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['workflows.definitions.edit'],\n}\n\ninterface RouteContext {\n params: Promise<{\n id: string\n }>\n}\n\nexport async function POST(request: NextRequest, context: RouteContext) {\n try {\n const params = await context.params\n const container = await createRequestContainer()\n const em = container.resolve('em')\n const auth = await getAuthFromRequest(request)\n\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request })\n const tenantId = auth.tenantId\n const organizationId = scope?.selectedId ?? auth.orgId\n\n if (!params.id.startsWith('code:')) {\n return NextResponse.json(\n { error: 'Customize is only supported for code-based workflow definitions' },\n { status: 400 },\n )\n }\n\n const workflowId = params.id.slice(5)\n const codeDef = getCodeWorkflow(workflowId)\n if (!codeDef) {\n return NextResponse.json({ error: 'Workflow definition not found' }, { status: 404 })\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: tenantId ?? '',\n organizationId: organizationId ?? null,\n userId: auth.sub ?? '',\n resourceKind: 'workflows.definition',\n resourceId: params.id,\n operation: 'custom',\n requestMethod: 'POST',\n requestHeaders: request.headers,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const existingOverride = await em.findOne(WorkflowDefinition, {\n workflowId: codeDef.workflowId,\n tenantId,\n })\n\n let saved: WorkflowDefinition\n if (existingOverride) {\n existingOverride.deletedAt = null\n existingOverride.workflowName = codeDef.workflowName\n existingOverride.description = codeDef.description\n existingOverride.version = codeDef.version\n existingOverride.definition = codeDef.definition\n existingOverride.metadata = codeDef.metadata ?? null\n existingOverride.enabled = codeDef.enabled\n existingOverride.codeWorkflowId = codeDef.workflowId\n existingOverride.updatedBy = auth.sub\n existingOverride.updatedAt = new Date()\n await em.flush()\n saved = existingOverride\n } else {\n const override = em.create(WorkflowDefinition, {\n workflowId: codeDef.workflowId,\n workflowName: codeDef.workflowName,\n description: codeDef.description,\n version: codeDef.version,\n definition: codeDef.definition,\n metadata: codeDef.metadata,\n enabled: codeDef.enabled,\n codeWorkflowId: codeDef.workflowId,\n tenantId,\n organizationId,\n createdBy: auth.sub,\n updatedBy: auth.sub,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(override)\n await em.flush()\n saved = override\n }\n\n if (guardResult?.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: tenantId ?? '',\n organizationId: organizationId ?? null,\n userId: auth.sub ?? '',\n resourceKind: 'workflows.definition',\n resourceId: String(saved.id),\n operation: 'custom',\n requestMethod: 'POST',\n requestHeaders: request.headers,\n metadata: guardResult.metadata,\n })\n }\n\n try {\n const eventBus = container.resolve('eventBus') as\n | { emitEvent(event: string, payload: unknown, options?: unknown): Promise<void> }\n | undefined\n if (eventBus && typeof eventBus.emitEvent === 'function') {\n await eventBus.emitEvent(\n 'workflows.definition.customized',\n {\n id: saved.id,\n workflowId: saved.workflowId,\n codeWorkflowId: saved.codeWorkflowId ?? null,\n tenantId: saved.tenantId,\n organizationId: saved.organizationId,\n userId: auth.sub ?? null,\n },\n { tenantId: saved.tenantId, organizationId: saved.organizationId, persistent: true },\n )\n }\n } catch (eventError) {\n logger.error('Failed to emit workflows.definition.customized event', { err: eventError })\n }\n\n return NextResponse.json({\n data: serializeWorkflowDefinition(saved),\n message: 'Workflow definition customized successfully',\n })\n } catch (error) {\n logger.error('Error customizing workflow definition', { err: error })\n return NextResponse.json({ error: 'Failed to customize workflow definition' }, { status: 500 })\n }\n}\n\nexport const openApi = {\n methods: {\n POST: {\n summary: 'Customize code-based workflow definition',\n description: 'Creates a DB override for a code-based workflow definition, seeded from the current code registry values. The id param must be of the form \"code:<workflowId>\".',\n tags: ['Workflows'],\n pathParams: z.object({\n id: z.string().describe('Must be of the form \"code:<workflowId>\"'),\n }),\n responses: [\n {\n status: 200,\n description: 'Workflow definition customized successfully',\n example: {\n data: {\n id: '123e4567-e89b-12d3-a456-426614174000',\n workflowId: 'workflows.simple-approval',\n workflowName: 'Simple Approval Workflow',\n source: 'code_override',\n },\n message: 'Workflow definition customized successfully',\n },\n },\n {\n status: 400,\n description: 'Not a code-based id',\n example: { error: 'Customize is only supported for code-based workflow definitions' },\n },\n {\n status: 404,\n description: 'Code workflow not found',\n example: { error: 'Workflow definition not found' },\n },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["/**\n * Customize Code-Based Workflow Definition\n *\n * POST /api/workflows/definitions/[id]/customize\n *\n * Creates a DB override row for a code-based workflow definition, seeded from\n * the current code registry values. The id param must be of the form\n * \"code:<workflowId>\".\n */\n\nimport { NextRequest, NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { validateCrudMutationGuard, runCrudMutationGuardAfterSuccess } from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { WorkflowDefinition } from '../../../../data/entities'\nimport { serializeWorkflowDefinition } from '../../serialize'\nimport { getCodeWorkflow } from '../../../../lib/code-registry'\nimport { invalidateTriggerCache } from '../../../../lib/event-trigger-service'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('workflows')\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['workflows.definitions.edit'],\n}\n\ninterface RouteContext {\n params: Promise<{\n id: string\n }>\n}\n\nexport async function POST(request: NextRequest, context: RouteContext) {\n try {\n const params = await context.params\n const container = await createRequestContainer()\n const em = container.resolve('em')\n const auth = await getAuthFromRequest(request)\n\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request })\n const tenantId = auth.tenantId\n const organizationId = scope?.selectedId ?? auth.orgId\n\n if (!params.id.startsWith('code:')) {\n return NextResponse.json(\n { error: 'Customize is only supported for code-based workflow definitions' },\n { status: 400 },\n )\n }\n\n const workflowId = params.id.slice(5)\n const codeDef = getCodeWorkflow(workflowId)\n if (!codeDef) {\n return NextResponse.json({ error: 'Workflow definition not found' }, { status: 404 })\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: tenantId ?? '',\n organizationId: organizationId ?? null,\n userId: auth.sub ?? '',\n resourceKind: 'workflows.definition',\n resourceId: params.id,\n operation: 'custom',\n requestMethod: 'POST',\n requestHeaders: request.headers,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const existingOverride = await em.findOne(WorkflowDefinition, {\n workflowId: codeDef.workflowId,\n tenantId,\n })\n\n let saved: WorkflowDefinition\n if (existingOverride) {\n existingOverride.deletedAt = null\n existingOverride.workflowName = codeDef.workflowName\n existingOverride.description = codeDef.description\n existingOverride.version = codeDef.version\n existingOverride.definition = codeDef.definition\n existingOverride.metadata = codeDef.metadata ?? null\n existingOverride.enabled = codeDef.enabled\n existingOverride.codeWorkflowId = codeDef.workflowId\n existingOverride.updatedBy = auth.sub\n existingOverride.updatedAt = new Date()\n await em.flush()\n saved = existingOverride\n } else {\n const override = em.create(WorkflowDefinition, {\n workflowId: codeDef.workflowId,\n workflowName: codeDef.workflowName,\n description: codeDef.description,\n version: codeDef.version,\n definition: codeDef.definition,\n metadata: codeDef.metadata,\n enabled: codeDef.enabled,\n codeWorkflowId: codeDef.workflowId,\n tenantId,\n organizationId,\n createdBy: auth.sub,\n updatedBy: auth.sub,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(override)\n await em.flush()\n saved = override\n }\n\n // Materializing the override moves trigger ownership from the code registry\n // to the new `workflow_definitions` row, so the cached snapshot the wildcard\n // subscriber reads is stale until TRIGGER_CACHE_TTL expires (#4425). Scope\n // the invalidation to the saved row's own organization: reviving an existing\n // override belonging to a sibling organization changes triggers for THAT\n // organization, not for the caller's.\n if (saved.tenantId) invalidateTriggerCache(saved.tenantId, saved.organizationId ?? undefined)\n\n if (guardResult?.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: tenantId ?? '',\n organizationId: organizationId ?? null,\n userId: auth.sub ?? '',\n resourceKind: 'workflows.definition',\n resourceId: String(saved.id),\n operation: 'custom',\n requestMethod: 'POST',\n requestHeaders: request.headers,\n metadata: guardResult.metadata,\n })\n }\n\n try {\n const eventBus = container.resolve('eventBus') as\n | { emitEvent(event: string, payload: unknown, options?: unknown): Promise<void> }\n | undefined\n if (eventBus && typeof eventBus.emitEvent === 'function') {\n await eventBus.emitEvent(\n 'workflows.definition.customized',\n {\n id: saved.id,\n workflowId: saved.workflowId,\n codeWorkflowId: saved.codeWorkflowId ?? null,\n tenantId: saved.tenantId,\n organizationId: saved.organizationId,\n userId: auth.sub ?? null,\n },\n { tenantId: saved.tenantId, organizationId: saved.organizationId, persistent: true },\n )\n }\n } catch (eventError) {\n logger.error('Failed to emit workflows.definition.customized event', { err: eventError })\n }\n\n return NextResponse.json({\n data: serializeWorkflowDefinition(saved),\n message: 'Workflow definition customized successfully',\n })\n } catch (error) {\n logger.error('Error customizing workflow definition', { err: error })\n return NextResponse.json({ error: 'Failed to customize workflow definition' }, { status: 500 })\n }\n}\n\nexport const openApi = {\n methods: {\n POST: {\n summary: 'Customize code-based workflow definition',\n description: 'Creates a DB override for a code-based workflow definition, seeded from the current code registry values. The id param must be of the form \"code:<workflowId>\".',\n tags: ['Workflows'],\n pathParams: z.object({\n id: z.string().describe('Must be of the form \"code:<workflowId>\"'),\n }),\n responses: [\n {\n status: 200,\n description: 'Workflow definition customized successfully',\n example: {\n data: {\n id: '123e4567-e89b-12d3-a456-426614174000',\n workflowId: 'workflows.simple-approval',\n workflowName: 'Simple Approval Workflow',\n source: 'code_override',\n },\n message: 'Workflow definition customized successfully',\n },\n },\n {\n status: 400,\n description: 'Not a code-based id',\n example: { error: 'Customize is only supported for code-based workflow definitions' },\n },\n {\n status: 404,\n description: 'Code workflow not found',\n example: { error: 'Workflow definition not found' },\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -7,6 +7,7 @@ import { validateCrudMutationGuard, runCrudMutationGuardAfterSuccess } from "@op
|
|
|
7
7
|
import { WorkflowDefinition, WorkflowInstance } from "../../../../data/entities.js";
|
|
8
8
|
import { serializeCodeWorkflowDefinition } from "../../serialize.js";
|
|
9
9
|
import { getCodeWorkflow } from "../../../../lib/code-registry.js";
|
|
10
|
+
import { invalidateTriggerCache } from "../../../../lib/event-trigger-service.js";
|
|
10
11
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
11
12
|
const logger = createLogger("workflows");
|
|
12
13
|
const metadata = {
|
|
@@ -93,6 +94,9 @@ async function POST(request, context) {
|
|
|
93
94
|
};
|
|
94
95
|
em.remove(definition);
|
|
95
96
|
await em.flush();
|
|
97
|
+
if (removedSnapshot.tenantId) {
|
|
98
|
+
invalidateTriggerCache(removedSnapshot.tenantId, removedSnapshot.organizationId ?? void 0);
|
|
99
|
+
}
|
|
96
100
|
if (guardResult?.shouldRunAfterSuccess) {
|
|
97
101
|
await runCrudMutationGuardAfterSuccess(container, {
|
|
98
102
|
tenantId: tenantId ?? "",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/workflows/api/definitions/%5Bid%5D/reset-to-code/route.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Reset Customized Workflow Definition to Code\n *\n * POST /api/workflows/definitions/[id]/reset-to-code\n *\n * Deletes the DB override row for a code-based workflow definition,\n * reverting it back to the original code registry version.\n */\n\nimport { NextRequest, NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { validateCrudMutationGuard, runCrudMutationGuardAfterSuccess } from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { WorkflowDefinition, WorkflowInstance } from '../../../../data/entities'\nimport { serializeCodeWorkflowDefinition } from '../../serialize'\nimport { getCodeWorkflow } from '../../../../lib/code-registry'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('workflows')\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['workflows.definitions.edit'],\n}\n\ninterface RouteContext {\n params: Promise<{\n id: string\n }>\n}\n\n/**\n * POST /api/workflows/definitions/[id]/reset-to-code\n *\n * Reset a customized code workflow definition back to its original code version.\n */\nexport async function POST(\n request: NextRequest,\n context: RouteContext\n) {\n try {\n const params = await context.params\n const container = await createRequestContainer()\n const em = container.resolve('em')\n const auth = await getAuthFromRequest(request)\n\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request })\n const tenantId = auth.tenantId\n const organizationId = scope?.selectedId ?? auth.orgId\n\n // Check edit permission\n const rbacService = container.resolve('rbacService')\n const hasPermission = await rbacService.userHasAllFeatures(\n auth.sub,\n ['workflows.definitions.edit'],\n {\n tenantId,\n organizationId,\n }\n )\n\n if (!hasPermission) {\n return NextResponse.json(\n { error: 'Insufficient permissions' },\n { status: 403 }\n )\n }\n\n // Find existing definition\n const definition = await em.findOne(WorkflowDefinition, {\n id: params.id,\n tenantId,\n organizationId,\n deletedAt: null,\n })\n\n if (!definition) {\n return NextResponse.json(\n { error: 'Workflow definition not found' },\n { status: 404 }\n )\n }\n\n // Verify this is a code-based override\n if (!definition.codeWorkflowId) {\n return NextResponse.json(\n { error: 'This workflow definition is not a code-based override and cannot be reset' },\n { status: 400 }\n )\n }\n\n // Check if there are active workflow instances using this definition\n const activeInstances = await em.count(WorkflowInstance, {\n definitionId: definition.id,\n status: { $in: ['RUNNING', 'WAITING'] },\n })\n\n if (activeInstances > 0) {\n return NextResponse.json(\n {\n error: `Cannot reset workflow definition with ${activeInstances} active instance(s)`,\n },\n { status: 409 }\n )\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: tenantId ?? '',\n organizationId: organizationId ?? null,\n userId: auth.sub ?? '',\n resourceKind: 'workflows.definition',\n resourceId: String(definition.id),\n operation: 'custom',\n requestMethod: 'POST',\n requestHeaders: request.headers,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n // Look up the original code definition before deleting\n const codeDef = getCodeWorkflow(definition.codeWorkflowId)\n\n // Snapshot identifying fields before remove() detaches the entity\n const removedSnapshot = {\n id: String(definition.id),\n workflowId: definition.workflowId,\n codeWorkflowId: definition.codeWorkflowId,\n tenantId: definition.tenantId,\n organizationId: definition.organizationId,\n }\n\n // Hard-delete the DB override row\n em.remove(definition)\n await em.flush()\n\n if (guardResult?.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: tenantId ?? '',\n organizationId: organizationId ?? null,\n userId: auth.sub ?? '',\n resourceKind: 'workflows.definition',\n resourceId: removedSnapshot.id,\n operation: 'custom',\n requestMethod: 'POST',\n requestHeaders: request.headers,\n metadata: guardResult.metadata,\n })\n }\n\n try {\n const eventBus = container.resolve('eventBus') as\n | { emitEvent(event: string, payload: unknown, options?: unknown): Promise<void> }\n | undefined\n if (eventBus && typeof eventBus.emitEvent === 'function') {\n await eventBus.emitEvent(\n 'workflows.definition.reset_to_code',\n {\n id: removedSnapshot.id,\n workflowId: removedSnapshot.workflowId,\n codeWorkflowId: removedSnapshot.codeWorkflowId,\n tenantId: removedSnapshot.tenantId,\n organizationId: removedSnapshot.organizationId,\n userId: auth.sub ?? null,\n },\n {\n tenantId: removedSnapshot.tenantId,\n organizationId: removedSnapshot.organizationId,\n persistent: true,\n },\n )\n }\n } catch (eventError) {\n logger.error('Failed to emit workflows.definition.reset_to_code event', { err: eventError })\n }\n\n if (!codeDef) {\n return NextResponse.json({\n message: 'Workflow definition reset to code version (code definition no longer registered)',\n data: null,\n })\n }\n\n const syntheticId = `code:${codeDef.workflowId}`\n\n return NextResponse.json({\n data: serializeCodeWorkflowDefinition(codeDef, syntheticId),\n message: 'Workflow definition reset to code version',\n })\n } catch (error) {\n logger.error('Error resetting workflow definition to code', { err: error })\n return NextResponse.json(\n { error: 'Failed to reset workflow definition to code' },\n { status: 500 }\n )\n }\n}\n\nexport const openApi = {\n methods: {\n POST: {\n summary: 'Reset workflow definition to code version',\n description: 'Deletes the DB override for a code-based workflow definition, reverting it to the original code registry version. Cannot be reset if there are active instances.',\n tags: ['Workflows'],\n pathParams: z.object({\n id: z.string().uuid(),\n }),\n responses: [\n {\n status: 200,\n description: 'Workflow definition reset to code version',\n example: {\n data: {\n id: 'code:checkout-flow',\n workflowId: 'checkout-flow',\n workflowName: 'Checkout Flow',\n description: 'Code-defined checkout workflow',\n version: 1,\n source: 'code',\n isCodeBased: true,\n },\n message: 'Workflow definition reset to code version',\n },\n },\n {\n status: 400,\n description: 'Definition is not a code-based override',\n example: {\n error: 'This workflow definition is not a code-based override and cannot be reset',\n },\n },\n {\n status: 404,\n description: 'Workflow definition not found',\n example: {\n error: 'Workflow definition not found',\n },\n },\n {\n status: 409,\n description: 'Cannot reset - active workflow instances exist',\n example: {\n error: 'Cannot reset workflow definition with 3 active instance(s)',\n },\n },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["/**\n * Reset Customized Workflow Definition to Code\n *\n * POST /api/workflows/definitions/[id]/reset-to-code\n *\n * Deletes the DB override row for a code-based workflow definition,\n * reverting it back to the original code registry version.\n */\n\nimport { NextRequest, NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { validateCrudMutationGuard, runCrudMutationGuardAfterSuccess } from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { WorkflowDefinition, WorkflowInstance } from '../../../../data/entities'\nimport { serializeCodeWorkflowDefinition } from '../../serialize'\nimport { getCodeWorkflow } from '../../../../lib/code-registry'\nimport { invalidateTriggerCache } from '../../../../lib/event-trigger-service'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('workflows')\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['workflows.definitions.edit'],\n}\n\ninterface RouteContext {\n params: Promise<{\n id: string\n }>\n}\n\n/**\n * POST /api/workflows/definitions/[id]/reset-to-code\n *\n * Reset a customized code workflow definition back to its original code version.\n */\nexport async function POST(\n request: NextRequest,\n context: RouteContext\n) {\n try {\n const params = await context.params\n const container = await createRequestContainer()\n const em = container.resolve('em')\n const auth = await getAuthFromRequest(request)\n\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request })\n const tenantId = auth.tenantId\n const organizationId = scope?.selectedId ?? auth.orgId\n\n // Check edit permission\n const rbacService = container.resolve('rbacService')\n const hasPermission = await rbacService.userHasAllFeatures(\n auth.sub,\n ['workflows.definitions.edit'],\n {\n tenantId,\n organizationId,\n }\n )\n\n if (!hasPermission) {\n return NextResponse.json(\n { error: 'Insufficient permissions' },\n { status: 403 }\n )\n }\n\n // Find existing definition\n const definition = await em.findOne(WorkflowDefinition, {\n id: params.id,\n tenantId,\n organizationId,\n deletedAt: null,\n })\n\n if (!definition) {\n return NextResponse.json(\n { error: 'Workflow definition not found' },\n { status: 404 }\n )\n }\n\n // Verify this is a code-based override\n if (!definition.codeWorkflowId) {\n return NextResponse.json(\n { error: 'This workflow definition is not a code-based override and cannot be reset' },\n { status: 400 }\n )\n }\n\n // Check if there are active workflow instances using this definition\n const activeInstances = await em.count(WorkflowInstance, {\n definitionId: definition.id,\n status: { $in: ['RUNNING', 'WAITING'] },\n })\n\n if (activeInstances > 0) {\n return NextResponse.json(\n {\n error: `Cannot reset workflow definition with ${activeInstances} active instance(s)`,\n },\n { status: 409 }\n )\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: tenantId ?? '',\n organizationId: organizationId ?? null,\n userId: auth.sub ?? '',\n resourceKind: 'workflows.definition',\n resourceId: String(definition.id),\n operation: 'custom',\n requestMethod: 'POST',\n requestHeaders: request.headers,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n // Look up the original code definition before deleting\n const codeDef = getCodeWorkflow(definition.codeWorkflowId)\n\n // Snapshot identifying fields before remove() detaches the entity\n const removedSnapshot = {\n id: String(definition.id),\n workflowId: definition.workflowId,\n codeWorkflowId: definition.codeWorkflowId,\n tenantId: definition.tenantId,\n organizationId: definition.organizationId,\n }\n\n // Hard-delete the DB override row\n em.remove(definition)\n await em.flush()\n\n // Trigger ownership falls back to the code registry, so the cached snapshot\n // still holds the embedded triggers of a row that no longer exists (#4425).\n if (removedSnapshot.tenantId) {\n invalidateTriggerCache(removedSnapshot.tenantId, removedSnapshot.organizationId ?? undefined)\n }\n\n if (guardResult?.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: tenantId ?? '',\n organizationId: organizationId ?? null,\n userId: auth.sub ?? '',\n resourceKind: 'workflows.definition',\n resourceId: removedSnapshot.id,\n operation: 'custom',\n requestMethod: 'POST',\n requestHeaders: request.headers,\n metadata: guardResult.metadata,\n })\n }\n\n try {\n const eventBus = container.resolve('eventBus') as\n | { emitEvent(event: string, payload: unknown, options?: unknown): Promise<void> }\n | undefined\n if (eventBus && typeof eventBus.emitEvent === 'function') {\n await eventBus.emitEvent(\n 'workflows.definition.reset_to_code',\n {\n id: removedSnapshot.id,\n workflowId: removedSnapshot.workflowId,\n codeWorkflowId: removedSnapshot.codeWorkflowId,\n tenantId: removedSnapshot.tenantId,\n organizationId: removedSnapshot.organizationId,\n userId: auth.sub ?? null,\n },\n {\n tenantId: removedSnapshot.tenantId,\n organizationId: removedSnapshot.organizationId,\n persistent: true,\n },\n )\n }\n } catch (eventError) {\n logger.error('Failed to emit workflows.definition.reset_to_code event', { err: eventError })\n }\n\n if (!codeDef) {\n return NextResponse.json({\n message: 'Workflow definition reset to code version (code definition no longer registered)',\n data: null,\n })\n }\n\n const syntheticId = `code:${codeDef.workflowId}`\n\n return NextResponse.json({\n data: serializeCodeWorkflowDefinition(codeDef, syntheticId),\n message: 'Workflow definition reset to code version',\n })\n } catch (error) {\n logger.error('Error resetting workflow definition to code', { err: error })\n return NextResponse.json(\n { error: 'Failed to reset workflow definition to code' },\n { status: 500 }\n )\n }\n}\n\nexport const openApi = {\n methods: {\n POST: {\n summary: 'Reset workflow definition to code version',\n description: 'Deletes the DB override for a code-based workflow definition, reverting it to the original code registry version. Cannot be reset if there are active instances.',\n tags: ['Workflows'],\n pathParams: z.object({\n id: z.string().uuid(),\n }),\n responses: [\n {\n status: 200,\n description: 'Workflow definition reset to code version',\n example: {\n data: {\n id: 'code:checkout-flow',\n workflowId: 'checkout-flow',\n workflowName: 'Checkout Flow',\n description: 'Code-defined checkout workflow',\n version: 1,\n source: 'code',\n isCodeBased: true,\n },\n message: 'Workflow definition reset to code version',\n },\n },\n {\n status: 400,\n description: 'Definition is not a code-based override',\n example: {\n error: 'This workflow definition is not a code-based override and cannot be reset',\n },\n },\n {\n status: 404,\n description: 'Workflow definition not found',\n example: {\n error: 'Workflow definition not found',\n },\n },\n {\n status: 409,\n description: 'Cannot reset - active workflow instances exist',\n example: {\n error: 'Cannot reset workflow definition with 3 active instance(s)',\n },\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.6.7-develop.
|
|
3
|
+
"version": "0.6.7-develop.6661.1.0043ed1d03",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -254,16 +254,16 @@
|
|
|
254
254
|
"zod": "^4.4.3"
|
|
255
255
|
},
|
|
256
256
|
"peerDependencies": {
|
|
257
|
-
"@open-mercato/ai-assistant": "0.6.7-develop.
|
|
258
|
-
"@open-mercato/shared": "0.6.7-develop.
|
|
259
|
-
"@open-mercato/ui": "0.6.7-develop.
|
|
257
|
+
"@open-mercato/ai-assistant": "0.6.7-develop.6661.1.0043ed1d03",
|
|
258
|
+
"@open-mercato/shared": "0.6.7-develop.6661.1.0043ed1d03",
|
|
259
|
+
"@open-mercato/ui": "0.6.7-develop.6661.1.0043ed1d03",
|
|
260
260
|
"react": "^19.0.0",
|
|
261
261
|
"react-dom": "^19.0.0"
|
|
262
262
|
},
|
|
263
263
|
"devDependencies": {
|
|
264
|
-
"@open-mercato/ai-assistant": "0.6.7-develop.
|
|
265
|
-
"@open-mercato/shared": "0.6.7-develop.
|
|
266
|
-
"@open-mercato/ui": "0.6.7-develop.
|
|
264
|
+
"@open-mercato/ai-assistant": "0.6.7-develop.6661.1.0043ed1d03",
|
|
265
|
+
"@open-mercato/shared": "0.6.7-develop.6661.1.0043ed1d03",
|
|
266
|
+
"@open-mercato/ui": "0.6.7-develop.6661.1.0043ed1d03",
|
|
267
267
|
"@testing-library/dom": "^10.4.1",
|
|
268
268
|
"@testing-library/jest-dom": "^6.9.1",
|
|
269
269
|
"@testing-library/react": "^16.3.1",
|
|
@@ -26,9 +26,17 @@ export function resolveSpecialValue(template: string, context: EvaluationContext
|
|
|
26
26
|
return getNestedValue(context, path)
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
// Field paths come from rule authors, so a segment could name a prototype key.
|
|
30
|
+
// Rejecting these — and requiring own-property access — keeps traversal on the
|
|
31
|
+
// caller's own data instead of the prototype chain.
|
|
32
|
+
const DANGEROUS_KEYS = new Set<string>(['__proto__', 'constructor', 'prototype'])
|
|
33
|
+
|
|
29
34
|
/**
|
|
30
35
|
* Get nested value from object using dot notation
|
|
31
36
|
* Supports: 'user.name', 'items[0].quantity', 'data.values[2]'
|
|
37
|
+
*
|
|
38
|
+
* Segments naming a prototype key, or resolving to an inherited rather than an
|
|
39
|
+
* own property, yield `undefined`.
|
|
32
40
|
*/
|
|
33
41
|
export function getNestedValue(obj: any, path: string): any {
|
|
34
42
|
if (!obj || !path) {
|
|
@@ -46,6 +54,14 @@ export function getNestedValue(obj: any, path: string): any {
|
|
|
46
54
|
return undefined
|
|
47
55
|
}
|
|
48
56
|
|
|
57
|
+
if (DANGEROUS_KEYS.has(key)) {
|
|
58
|
+
return undefined
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!Object.prototype.hasOwnProperty.call(result, key)) {
|
|
62
|
+
return undefined
|
|
63
|
+
}
|
|
64
|
+
|
|
49
65
|
result = result[key]
|
|
50
66
|
}
|
|
51
67
|
|
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* lib helpers so duplicate-insert handling stays consistent module-wide.
|
|
2
|
+
* @deprecated Import `isUniqueViolation` from `@open-mercato/shared/lib/db/pg-errors` instead.
|
|
3
|
+
* Kept as a re-export so existing hub call sites keep working.
|
|
5
4
|
*/
|
|
6
|
-
export
|
|
7
|
-
if (!err || typeof err !== 'object') return false
|
|
8
|
-
const code = (err as { code?: string }).code
|
|
9
|
-
if (code === '23505') return true // Postgres unique_violation
|
|
10
|
-
const message = (err as { message?: string }).message
|
|
11
|
-
return typeof message === 'string' && /duplicate key value|unique constraint/i.test(message)
|
|
12
|
-
}
|
|
5
|
+
export { isUniqueViolation } from '@open-mercato/shared/lib/db/pg-errors'
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from '../data/validators'
|
|
19
19
|
import type { CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'
|
|
20
20
|
import type { DataEngine } from '@open-mercato/shared/lib/data/engine'
|
|
21
|
+
import { buildCurrencyCommandWhere, ensureCurrencyCommandScope } from './scope'
|
|
21
22
|
|
|
22
23
|
const currencyCrudEvents: CrudEventsConfig = {
|
|
23
24
|
module: 'currencies',
|
|
@@ -48,9 +49,17 @@ type CurrencySnapshot = {
|
|
|
48
49
|
|
|
49
50
|
type CurrencyUndoPayload = UndoPayload<CurrencySnapshot>
|
|
50
51
|
|
|
51
|
-
async function loadCurrencySnapshot(
|
|
52
|
-
|
|
52
|
+
async function loadCurrencySnapshot(
|
|
53
|
+
em: EntityManager,
|
|
54
|
+
id: string,
|
|
55
|
+
ctx: Parameters<typeof buildCurrencyCommandWhere>[0],
|
|
56
|
+
): Promise<CurrencySnapshot | null> {
|
|
57
|
+
const record = await em.findOne(
|
|
58
|
+
Currency,
|
|
59
|
+
buildCurrencyCommandWhere<Currency>(ctx, { id }),
|
|
60
|
+
)
|
|
53
61
|
if (!record) return null
|
|
62
|
+
ensureCurrencyCommandScope(ctx, record)
|
|
54
63
|
return {
|
|
55
64
|
id: record.id,
|
|
56
65
|
organizationId: record.organizationId,
|
|
@@ -91,6 +100,7 @@ const createCurrencyCommand: CommandHandler<CurrencyCreateInput, { currencyId: s
|
|
|
91
100
|
id: 'currencies.currencies.create',
|
|
92
101
|
async execute(input, ctx) {
|
|
93
102
|
const parsed = currencyCreateSchema.parse(input)
|
|
103
|
+
ensureCurrencyCommandScope(ctx, parsed)
|
|
94
104
|
|
|
95
105
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
96
106
|
|
|
@@ -159,7 +169,7 @@ const createCurrencyCommand: CommandHandler<CurrencyCreateInput, { currencyId: s
|
|
|
159
169
|
},
|
|
160
170
|
captureAfter: async (_input, result, ctx) => {
|
|
161
171
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
162
|
-
return loadCurrencySnapshot(em, result.currencyId)
|
|
172
|
+
return loadCurrencySnapshot(em, result.currencyId, ctx)
|
|
163
173
|
},
|
|
164
174
|
buildLog: async ({ snapshots }) => {
|
|
165
175
|
const after = snapshots.after as CurrencySnapshot | undefined
|
|
@@ -204,7 +214,7 @@ const updateCurrencyCommand: CommandHandler<CurrencyUpdateInput, { currencyId: s
|
|
|
204
214
|
async prepare(input, ctx) {
|
|
205
215
|
requireId(input.id, 'Currency ID is required')
|
|
206
216
|
const em = ctx.container.resolve('em') as EntityManager
|
|
207
|
-
const before = await loadCurrencySnapshot(em, input.id)
|
|
217
|
+
const before = await loadCurrencySnapshot(em, input.id, ctx)
|
|
208
218
|
return { before }
|
|
209
219
|
},
|
|
210
220
|
async execute(input, ctx) {
|
|
@@ -212,10 +222,14 @@ const updateCurrencyCommand: CommandHandler<CurrencyUpdateInput, { currencyId: s
|
|
|
212
222
|
requireId(parsed.id, 'Currency ID is required')
|
|
213
223
|
|
|
214
224
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
215
|
-
const record = await em.findOne(
|
|
225
|
+
const record = await em.findOne(
|
|
226
|
+
Currency,
|
|
227
|
+
buildCurrencyCommandWhere<Currency>(ctx, { id: parsed.id }),
|
|
228
|
+
)
|
|
216
229
|
if (!record) {
|
|
217
230
|
throw new CrudHttpError(404, { error: 'Currency not found' })
|
|
218
231
|
}
|
|
232
|
+
ensureCurrencyCommandScope(ctx, record)
|
|
219
233
|
|
|
220
234
|
// Check code uniqueness if changing code
|
|
221
235
|
if (parsed.code && parsed.code !== record.code) {
|
|
@@ -289,7 +303,7 @@ const updateCurrencyCommand: CommandHandler<CurrencyUpdateInput, { currencyId: s
|
|
|
289
303
|
},
|
|
290
304
|
captureAfter: async (_input, result, ctx) => {
|
|
291
305
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
292
|
-
return loadCurrencySnapshot(em, result.currencyId)
|
|
306
|
+
return loadCurrencySnapshot(em, result.currencyId, ctx)
|
|
293
307
|
},
|
|
294
308
|
buildLog: async ({ snapshots, result }) => {
|
|
295
309
|
const before = snapshots.before as CurrencySnapshot | undefined
|
|
@@ -334,7 +348,7 @@ const deleteCurrencyCommand: CommandHandler<CurrencyDeleteInput, { currencyId: s
|
|
|
334
348
|
async prepare(input, ctx) {
|
|
335
349
|
requireId(input.id, 'Currency ID is required')
|
|
336
350
|
const em = ctx.container.resolve('em') as EntityManager
|
|
337
|
-
const before = await loadCurrencySnapshot(em, input.id)
|
|
351
|
+
const before = await loadCurrencySnapshot(em, input.id, ctx)
|
|
338
352
|
return { before }
|
|
339
353
|
},
|
|
340
354
|
async execute(input, ctx) {
|
|
@@ -342,10 +356,14 @@ const deleteCurrencyCommand: CommandHandler<CurrencyDeleteInput, { currencyId: s
|
|
|
342
356
|
requireId(parsed.id, 'Currency ID is required')
|
|
343
357
|
|
|
344
358
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
345
|
-
const record = await em.findOne(
|
|
359
|
+
const record = await em.findOne(
|
|
360
|
+
Currency,
|
|
361
|
+
buildCurrencyCommandWhere<Currency>(ctx, { id: parsed.id }),
|
|
362
|
+
)
|
|
346
363
|
if (!record) {
|
|
347
364
|
throw new CrudHttpError(404, { error: 'Currency not found' })
|
|
348
365
|
}
|
|
366
|
+
ensureCurrencyCommandScope(ctx, record)
|
|
349
367
|
|
|
350
368
|
// Prevent deleting base currency
|
|
351
369
|
if (record.isBase) {
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from '../data/validators'
|
|
18
18
|
import type { CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'
|
|
19
19
|
import type { DataEngine } from '@open-mercato/shared/lib/data/engine'
|
|
20
|
+
import { buildCurrencyCommandWhere, ensureCurrencyCommandScope } from './scope'
|
|
20
21
|
|
|
21
22
|
const exchangeRateCrudEvents: CrudEventsConfig = {
|
|
22
23
|
module: 'currencies',
|
|
@@ -46,9 +47,17 @@ type ExchangeRateSnapshot = {
|
|
|
46
47
|
|
|
47
48
|
type ExchangeRateUndoPayload = UndoPayload<ExchangeRateSnapshot>
|
|
48
49
|
|
|
49
|
-
async function loadExchangeRateSnapshot(
|
|
50
|
-
|
|
50
|
+
async function loadExchangeRateSnapshot(
|
|
51
|
+
em: EntityManager,
|
|
52
|
+
id: string,
|
|
53
|
+
ctx: Parameters<typeof buildCurrencyCommandWhere>[0],
|
|
54
|
+
): Promise<ExchangeRateSnapshot | null> {
|
|
55
|
+
const record = await em.findOne(
|
|
56
|
+
ExchangeRate,
|
|
57
|
+
buildCurrencyCommandWhere<ExchangeRate>(ctx, { id }),
|
|
58
|
+
)
|
|
51
59
|
if (!record) return null
|
|
60
|
+
ensureCurrencyCommandScope(ctx, record)
|
|
52
61
|
return {
|
|
53
62
|
id: record.id,
|
|
54
63
|
organizationId: record.organizationId,
|
|
@@ -97,6 +106,7 @@ const createExchangeRateCommand: CommandHandler<ExchangeRateCreateInput, { excha
|
|
|
97
106
|
id: 'currencies.exchange_rates.create',
|
|
98
107
|
async execute(input, ctx) {
|
|
99
108
|
const parsed = exchangeRateCreateSchema.parse(input)
|
|
109
|
+
ensureCurrencyCommandScope(ctx, parsed)
|
|
100
110
|
|
|
101
111
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
102
112
|
|
|
@@ -158,7 +168,7 @@ const createExchangeRateCommand: CommandHandler<ExchangeRateCreateInput, { excha
|
|
|
158
168
|
},
|
|
159
169
|
captureAfter: async (_input, result, ctx) => {
|
|
160
170
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
161
|
-
return loadExchangeRateSnapshot(em, result.exchangeRateId)
|
|
171
|
+
return loadExchangeRateSnapshot(em, result.exchangeRateId, ctx)
|
|
162
172
|
},
|
|
163
173
|
buildLog: async ({ snapshots }) => {
|
|
164
174
|
const after = snapshots.after as ExchangeRateSnapshot | undefined
|
|
@@ -198,7 +208,7 @@ const updateExchangeRateCommand: CommandHandler<ExchangeRateUpdateInput, { excha
|
|
|
198
208
|
async prepare(input, ctx) {
|
|
199
209
|
requireId(input.id, 'Exchange rate ID is required')
|
|
200
210
|
const em = ctx.container.resolve('em') as EntityManager
|
|
201
|
-
const before = await loadExchangeRateSnapshot(em, input.id)
|
|
211
|
+
const before = await loadExchangeRateSnapshot(em, input.id, ctx)
|
|
202
212
|
return { before }
|
|
203
213
|
},
|
|
204
214
|
async execute(input, ctx) {
|
|
@@ -206,10 +216,14 @@ const updateExchangeRateCommand: CommandHandler<ExchangeRateUpdateInput, { excha
|
|
|
206
216
|
requireId(parsed.id, 'Exchange rate ID is required')
|
|
207
217
|
|
|
208
218
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
209
|
-
const record = await em.findOne(
|
|
219
|
+
const record = await em.findOne(
|
|
220
|
+
ExchangeRate,
|
|
221
|
+
buildCurrencyCommandWhere<ExchangeRate>(ctx, { id: parsed.id }),
|
|
222
|
+
)
|
|
210
223
|
if (!record) {
|
|
211
224
|
throw new CrudHttpError(404, { error: 'Exchange rate not found' })
|
|
212
225
|
}
|
|
226
|
+
ensureCurrencyCommandScope(ctx, record)
|
|
213
227
|
|
|
214
228
|
// Validate currencies if changed
|
|
215
229
|
const fromCode = parsed.fromCurrencyCode ?? record.fromCurrencyCode
|
|
@@ -288,7 +302,7 @@ const updateExchangeRateCommand: CommandHandler<ExchangeRateUpdateInput, { excha
|
|
|
288
302
|
},
|
|
289
303
|
captureAfter: async (_input, result, ctx) => {
|
|
290
304
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
291
|
-
return loadExchangeRateSnapshot(em, result.exchangeRateId)
|
|
305
|
+
return loadExchangeRateSnapshot(em, result.exchangeRateId, ctx)
|
|
292
306
|
},
|
|
293
307
|
buildLog: async ({ snapshots, result }) => {
|
|
294
308
|
const before = snapshots.before as ExchangeRateSnapshot | undefined
|
|
@@ -332,7 +346,7 @@ const deleteExchangeRateCommand: CommandHandler<ExchangeRateDeleteInput, { excha
|
|
|
332
346
|
async prepare(input, ctx) {
|
|
333
347
|
requireId(input.id, 'Exchange rate ID is required')
|
|
334
348
|
const em = ctx.container.resolve('em') as EntityManager
|
|
335
|
-
const before = await loadExchangeRateSnapshot(em, input.id)
|
|
349
|
+
const before = await loadExchangeRateSnapshot(em, input.id, ctx)
|
|
336
350
|
return { before }
|
|
337
351
|
},
|
|
338
352
|
async execute(input, ctx) {
|
|
@@ -340,10 +354,14 @@ const deleteExchangeRateCommand: CommandHandler<ExchangeRateDeleteInput, { excha
|
|
|
340
354
|
requireId(parsed.id, 'Exchange rate ID is required')
|
|
341
355
|
|
|
342
356
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
343
|
-
const record = await em.findOne(
|
|
357
|
+
const record = await em.findOne(
|
|
358
|
+
ExchangeRate,
|
|
359
|
+
buildCurrencyCommandWhere<ExchangeRate>(ctx, { id: parsed.id }),
|
|
360
|
+
)
|
|
344
361
|
if (!record) {
|
|
345
362
|
throw new CrudHttpError(404, { error: 'Exchange rate not found' })
|
|
346
363
|
}
|
|
364
|
+
ensureCurrencyCommandScope(ctx, record)
|
|
347
365
|
|
|
348
366
|
record.deletedAt = new Date()
|
|
349
367
|
record.isActive = false
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { FilterQuery } from '@mikro-orm/core'
|
|
2
|
+
import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
|
|
3
|
+
import { buildScopedWhere } from '@open-mercato/shared/lib/api/crud'
|
|
4
|
+
import {
|
|
5
|
+
ensureOrganizationScope,
|
|
6
|
+
ensureTenantScope,
|
|
7
|
+
} from '@open-mercato/shared/lib/commands/scope'
|
|
8
|
+
|
|
9
|
+
type CurrencyCommandScopedRecord = {
|
|
10
|
+
organizationId: string
|
|
11
|
+
tenantId: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function buildCurrencyCommandWhere<T extends object>(
|
|
15
|
+
ctx: CommandRuntimeContext,
|
|
16
|
+
base: FilterQuery<T>,
|
|
17
|
+
): FilterQuery<T> {
|
|
18
|
+
const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? undefined
|
|
19
|
+
return buildScopedWhere(base as Record<string, unknown>, {
|
|
20
|
+
organizationId,
|
|
21
|
+
organizationIds: ctx.organizationIds ?? undefined,
|
|
22
|
+
tenantId: ctx.auth?.tenantId ?? undefined,
|
|
23
|
+
}) as FilterQuery<T>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function ensureCurrencyCommandScope(
|
|
27
|
+
ctx: CommandRuntimeContext,
|
|
28
|
+
record: CurrencyCommandScopedRecord,
|
|
29
|
+
): void {
|
|
30
|
+
ensureTenantScope(ctx, record.tenantId)
|
|
31
|
+
ensureOrganizationScope(ctx, record.organizationId)
|
|
32
|
+
}
|
|
@@ -36,6 +36,8 @@ export type ActivitiesSectionProps = {
|
|
|
36
36
|
runGuardedMutation?: GuardedMutationRunner
|
|
37
37
|
refreshKey?: number
|
|
38
38
|
onEditActivity?: (activity: InteractionSummary) => void
|
|
39
|
+
/** Interaction type hidden from the timeline by default ('task' unless overridden); pass null to show every type. */
|
|
40
|
+
excludeInteractionType?: string | null
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
function toDateOnly(value: string | null | undefined): string {
|
|
@@ -109,6 +111,7 @@ export function ActivitiesSection({
|
|
|
109
111
|
refreshKey = 0,
|
|
110
112
|
onEditActivity,
|
|
111
113
|
runGuardedMutation,
|
|
114
|
+
excludeInteractionType = 'task',
|
|
112
115
|
}: ActivitiesSectionProps) {
|
|
113
116
|
const t = useT()
|
|
114
117
|
const [filterTypes, setFilterTypes] = React.useState<string[]>([])
|
|
@@ -169,17 +172,18 @@ export function ActivitiesSection({
|
|
|
169
172
|
setLoading(true)
|
|
170
173
|
try {
|
|
171
174
|
// Always fetch canonical interactions (new activities are always created here)
|
|
172
|
-
const
|
|
175
|
+
const excludedFilterActive = excludeInteractionType ? filterTypes.includes(excludeInteractionType) : true
|
|
173
176
|
const canonicalParams = new URLSearchParams({
|
|
174
177
|
entityId,
|
|
175
178
|
limit: '50',
|
|
176
179
|
sortField: 'occurredAt',
|
|
177
180
|
sortDir: 'desc',
|
|
178
181
|
})
|
|
179
|
-
// Hide
|
|
180
|
-
// but lift the exclusion when the user
|
|
181
|
-
//
|
|
182
|
-
|
|
182
|
+
// Hide the configured type (tasks by default — they have their own tab)
|
|
183
|
+
// from the activity timeline, but lift the exclusion when the user
|
|
184
|
+
// explicitly toggled that type's chip on (mirrors
|
|
185
|
+
// `ActivityHistorySection.tsx` after the #1805 fix).
|
|
186
|
+
if (excludeInteractionType && !excludedFilterActive) canonicalParams.set('excludeInteractionType', excludeInteractionType)
|
|
183
187
|
if (dealId) canonicalParams.set('dealId', dealId)
|
|
184
188
|
if (filterTypes.length > 0) canonicalParams.set('type', filterTypes.join(','))
|
|
185
189
|
if (filterDateFrom) canonicalParams.set('from', filterDateFrom)
|
|
@@ -261,7 +265,7 @@ export function ActivitiesSection({
|
|
|
261
265
|
} finally {
|
|
262
266
|
setLoading(false)
|
|
263
267
|
}
|
|
264
|
-
}, [dealId, entityId, filterDateFrom, filterDateTo, filterTypes, loadedPages, useCanonicalInteractions, refreshKey, t])
|
|
268
|
+
}, [dealId, entityId, excludeInteractionType, filterDateFrom, filterDateTo, filterTypes, loadedPages, useCanonicalInteractions, refreshKey, t])
|
|
265
269
|
|
|
266
270
|
React.useEffect(() => {
|
|
267
271
|
setLoadedPages(1)
|
|
@@ -30,6 +30,8 @@ type ActivityHistorySectionProps = {
|
|
|
30
30
|
/** Optional guarded-mutation runner so per-row mutations route through the parent's
|
|
31
31
|
* `useGuardedMutation` and emit retry-last-mutation context. */
|
|
32
32
|
runMutation?: GuardedMutationRunner
|
|
33
|
+
/** Interaction type hidden from the history by default ('task' unless overridden); pass null to show every type. */
|
|
34
|
+
excludeInteractionType?: string | null
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
type InteractionListResponse = {
|
|
@@ -170,6 +172,7 @@ export function ActivityHistorySection({
|
|
|
170
172
|
refreshKey = 0,
|
|
171
173
|
onEditActivity,
|
|
172
174
|
runMutation,
|
|
175
|
+
excludeInteractionType = 'task',
|
|
173
176
|
}: ActivityHistorySectionProps) {
|
|
174
177
|
const t = useT()
|
|
175
178
|
const [searchInput, setSearchInput] = React.useState('')
|
|
@@ -231,14 +234,14 @@ export function ActivityHistorySection({
|
|
|
231
234
|
let firstPageHasMore = false
|
|
232
235
|
let pagesLoaded = 0
|
|
233
236
|
|
|
234
|
-
const
|
|
237
|
+
const excludedFilterActive = excludeInteractionType ? activeTypes.includes(excludeInteractionType) : true
|
|
235
238
|
do {
|
|
236
239
|
const params = new URLSearchParams({
|
|
237
240
|
entityId,
|
|
238
241
|
limit: String(pageSize),
|
|
239
242
|
from: rangeStart,
|
|
240
243
|
})
|
|
241
|
-
if (!
|
|
244
|
+
if (excludeInteractionType && !excludedFilterActive) params.set('excludeInteractionType', excludeInteractionType)
|
|
242
245
|
if (activeTypes.length > 0) params.set('type', activeTypes.join(','))
|
|
243
246
|
if (search) params.set('search', search)
|
|
244
247
|
if (sortMode === 'recent') {
|
|
@@ -310,7 +313,7 @@ export function ActivityHistorySection({
|
|
|
310
313
|
} finally {
|
|
311
314
|
if (!isStale()) setLoading(false)
|
|
312
315
|
}
|
|
313
|
-
}, [activeTypes, dateRange, entityId, loadedPages, search, sortMode, t, useCanonicalInteractions])
|
|
316
|
+
}, [activeTypes, dateRange, entityId, excludeInteractionType, loadedPages, search, sortMode, t, useCanonicalInteractions])
|
|
314
317
|
|
|
315
318
|
React.useEffect(() => {
|
|
316
319
|
const controller = new AbortController()
|
|
@@ -103,13 +103,19 @@ const crud = makeCrudRoute({
|
|
|
103
103
|
entity: FeatureToggle,
|
|
104
104
|
idField: 'id',
|
|
105
105
|
orgField: null,
|
|
106
|
-
|
|
106
|
+
// The query engine requires the caller's tenant context even when the
|
|
107
|
+
// underlying entity is global; the list below explicitly disables its
|
|
108
|
+
// automatic tenant predicate.
|
|
109
|
+
tenantField: 'tenantId',
|
|
107
110
|
softDeleteField: 'deletedAt'
|
|
108
111
|
},
|
|
109
112
|
indexer: { entityType: E.feature_toggles.feature_toggle },
|
|
110
113
|
list: {
|
|
111
114
|
schema: listQuerySchema,
|
|
112
115
|
entityId: E.feature_toggles.feature_toggle,
|
|
116
|
+
// FeatureToggle rows and their query-index projections are global
|
|
117
|
+
// (null/null scope), so filtering them by the actor's tenant hides them.
|
|
118
|
+
omitAutomaticTenantOrgScope: true,
|
|
113
119
|
fields: listFields,
|
|
114
120
|
sortFieldMap: {
|
|
115
121
|
id: 'id',
|