@open-mercato/core 0.4.7-develop-78d7541539 → 0.4.7-develop-74069040de
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/AGENTS.md +1 -0
- package/dist/modules/catalog/api/bulk-delete/route.js +86 -0
- package/dist/modules/catalog/api/bulk-delete/route.js.map +7 -0
- package/dist/modules/catalog/api/prices/route.js +39 -6
- package/dist/modules/catalog/api/prices/route.js.map +2 -2
- package/dist/modules/catalog/api/products/route.js +6 -11
- package/dist/modules/catalog/api/products/route.js.map +2 -2
- package/dist/modules/catalog/commands/products.js +2 -0
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/catalog/components/products/ProductsDataTable.js +9 -1
- package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
- package/dist/modules/catalog/lib/bulkDelete.js +70 -0
- package/dist/modules/catalog/lib/bulkDelete.js.map +7 -0
- package/dist/modules/catalog/widgets/injection/product-bulk-delete/widget.js +185 -0
- package/dist/modules/catalog/widgets/injection/product-bulk-delete/widget.js.map +7 -0
- package/dist/modules/catalog/widgets/injection-table.js +9 -1
- package/dist/modules/catalog/widgets/injection-table.js.map +2 -2
- package/dist/modules/catalog/workers/catalog-product-bulk-delete.js +40 -0
- package/dist/modules/catalog/workers/catalog-product-bulk-delete.js.map +7 -0
- package/dist/modules/data_sync/api/options.js +52 -0
- package/dist/modules/data_sync/api/options.js.map +7 -0
- package/dist/modules/data_sync/api/run.js +30 -35
- package/dist/modules/data_sync/api/run.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/cancel.js +2 -2
- package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/retry.js +15 -30
- package/dist/modules/data_sync/api/runs/[id]/retry.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/[id]/route.js +109 -0
- package/dist/modules/data_sync/api/schedules/[id]/route.js.map +7 -0
- package/dist/modules/data_sync/api/schedules/route.js +72 -0
- package/dist/modules/data_sync/api/schedules/route.js.map +7 -0
- package/dist/modules/data_sync/api/schedules/serialize.js +21 -0
- package/dist/modules/data_sync/api/schedules/serialize.js.map +7 -0
- package/dist/modules/data_sync/backend/data-sync/page.js +656 -47
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js +116 -34
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js.map +2 -2
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js +394 -0
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +7 -0
- package/dist/modules/data_sync/data/validators.js +32 -0
- package/dist/modules/data_sync/data/validators.js.map +2 -2
- package/dist/modules/data_sync/di.js +2 -0
- package/dist/modules/data_sync/di.js.map +2 -2
- package/dist/modules/data_sync/lib/id-mapping.js +24 -2
- package/dist/modules/data_sync/lib/id-mapping.js.map +2 -2
- package/dist/modules/data_sync/lib/start-run.js +57 -0
- package/dist/modules/data_sync/lib/start-run.js.map +7 -0
- package/dist/modules/data_sync/lib/sync-engine.js +93 -4
- package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-run-service.js +5 -1
- package/dist/modules/data_sync/lib/sync-run-service.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-schedule-service.js +138 -0
- package/dist/modules/data_sync/lib/sync-schedule-service.js.map +7 -0
- package/dist/modules/data_sync/workers/sync-export.js +28 -2
- package/dist/modules/data_sync/workers/sync-export.js.map +2 -2
- package/dist/modules/data_sync/workers/sync-import.js +28 -2
- package/dist/modules/data_sync/workers/sync-import.js.map +2 -2
- package/dist/modules/data_sync/workers/sync-scheduled.js +5 -0
- package/dist/modules/data_sync/workers/sync-scheduled.js.map +2 -2
- package/dist/modules/entities/api/definitions.js +5 -2
- package/dist/modules/entities/api/definitions.js.map +2 -2
- package/dist/modules/entities/lib/field-definitions.js +3 -1
- package/dist/modules/entities/lib/field-definitions.js.map +2 -2
- package/dist/modules/integrations/api/[id]/route.js +14 -15
- package/dist/modules/integrations/api/[id]/route.js.map +2 -2
- package/dist/modules/integrations/api/route.js +3 -3
- package/dist/modules/integrations/api/route.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/[id]/page.js +148 -33
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/integrations/lib/state-service.js +15 -1
- package/dist/modules/integrations/lib/state-service.js.map +2 -2
- package/dist/modules/messages/api/[id]/route.js +24 -22
- package/dist/modules/messages/api/[id]/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/webhook/[provider]/route.js.map +2 -2
- package/dist/modules/progress/api/active/route.js +3 -1
- package/dist/modules/progress/api/active/route.js.map +2 -2
- package/dist/modules/progress/api/jobs/[id]/route.js +1 -1
- package/dist/modules/progress/api/jobs/[id]/route.js.map +2 -2
- package/dist/modules/progress/api/jobs/route.js +1 -1
- package/dist/modules/progress/api/jobs/route.js.map +2 -2
- package/dist/modules/progress/lib/events.js.map +1 -1
- package/dist/modules/progress/lib/progressService.js.map +2 -2
- package/dist/modules/progress/lib/progressServiceImpl.js +42 -1
- package/dist/modules/progress/lib/progressServiceImpl.js.map +2 -2
- package/dist/modules/query_index/lib/document.js +35 -1
- package/dist/modules/query_index/lib/document.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +91 -4
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/query_index/lib/indexer.js +2 -0
- package/dist/modules/query_index/lib/indexer.js.map +2 -2
- package/dist/modules/sales/api/adjustment-kinds/route.js +3 -9
- package/dist/modules/sales/api/adjustment-kinds/route.js.map +2 -2
- package/dist/modules/sales/api/channels/route.js +3 -10
- package/dist/modules/sales/api/channels/route.js.map +2 -2
- package/dist/modules/sales/api/delivery-windows/route.js +3 -10
- package/dist/modules/sales/api/delivery-windows/route.js.map +2 -2
- package/dist/modules/sales/api/payment-methods/route.js +3 -11
- package/dist/modules/sales/api/payment-methods/route.js.map +2 -2
- package/dist/modules/sales/api/price-kinds/route.js +3 -5
- package/dist/modules/sales/api/price-kinds/route.js.map +2 -2
- package/dist/modules/sales/api/shipping-methods/route.js +3 -11
- package/dist/modules/sales/api/shipping-methods/route.js.map +2 -2
- package/dist/modules/sales/api/tags/route.js +3 -9
- package/dist/modules/sales/api/tags/route.js.map +2 -2
- package/dist/modules/sales/api/tax-rates/route.js +3 -13
- package/dist/modules/sales/api/tax-rates/route.js.map +2 -2
- package/dist/modules/sales/api/utils.js +9 -0
- package/dist/modules/sales/api/utils.js.map +2 -2
- package/dist/modules/sales/lib/makeStatusDictionaryRoute.js +3 -9
- package/dist/modules/sales/lib/makeStatusDictionaryRoute.js.map +2 -2
- package/dist/modules/workflows/api/definitions/[id]/route.js +3 -2
- package/dist/modules/workflows/api/definitions/[id]/route.js.map +2 -2
- package/dist/modules/workflows/api/definitions/route.js +4 -3
- package/dist/modules/workflows/api/definitions/route.js.map +2 -2
- package/dist/modules/workflows/api/definitions/serialize.js +25 -0
- package/dist/modules/workflows/api/definitions/serialize.js.map +7 -0
- package/package.json +3 -3
- package/src/modules/catalog/api/bulk-delete/route.ts +93 -0
- package/src/modules/catalog/api/prices/route.ts +53 -6
- package/src/modules/catalog/api/products/route.ts +6 -11
- package/src/modules/catalog/commands/products.ts +2 -0
- package/src/modules/catalog/components/products/ProductsDataTable.tsx +8 -0
- package/src/modules/catalog/i18n/de.json +10 -0
- package/src/modules/catalog/i18n/en.json +10 -0
- package/src/modules/catalog/i18n/es.json +10 -0
- package/src/modules/catalog/i18n/pl.json +10 -0
- package/src/modules/catalog/lib/bulkDelete.ts +106 -0
- package/src/modules/catalog/widgets/injection/product-bulk-delete/widget.ts +242 -0
- package/src/modules/catalog/widgets/injection-table.ts +8 -0
- package/src/modules/catalog/workers/catalog-product-bulk-delete.ts +48 -0
- package/src/modules/data_sync/AGENTS.md +11 -3
- package/src/modules/data_sync/api/options.ts +58 -0
- package/src/modules/data_sync/api/run.ts +34 -36
- package/src/modules/data_sync/api/runs/[id]/cancel.ts +2 -2
- package/src/modules/data_sync/api/runs/[id]/retry.ts +14 -31
- package/src/modules/data_sync/api/schedules/[id]/route.ts +130 -0
- package/src/modules/data_sync/api/schedules/route.ts +77 -0
- package/src/modules/data_sync/api/schedules/serialize.ts +31 -0
- package/src/modules/data_sync/backend/data-sync/page.tsx +756 -2
- package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +179 -53
- package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +512 -0
- package/src/modules/data_sync/data/validators.ts +35 -0
- package/src/modules/data_sync/di.ts +6 -0
- package/src/modules/data_sync/i18n/de.json +72 -0
- package/src/modules/data_sync/i18n/en.json +72 -0
- package/src/modules/data_sync/i18n/es.json +72 -0
- package/src/modules/data_sync/i18n/pl.json +72 -0
- package/src/modules/data_sync/lib/adapter.ts +4 -1
- package/src/modules/data_sync/lib/id-mapping.ts +32 -2
- package/src/modules/data_sync/lib/start-run.ts +90 -0
- package/src/modules/data_sync/lib/sync-engine.ts +111 -4
- package/src/modules/data_sync/lib/sync-run-service.ts +5 -1
- package/src/modules/data_sync/lib/sync-schedule-service.ts +207 -0
- package/src/modules/data_sync/workers/sync-export.ts +33 -2
- package/src/modules/data_sync/workers/sync-import.ts +33 -2
- package/src/modules/data_sync/workers/sync-scheduled.ts +7 -0
- package/src/modules/entities/api/definitions.ts +12 -2
- package/src/modules/entities/lib/field-definitions.ts +2 -0
- package/src/modules/integrations/AGENTS.md +16 -3
- package/src/modules/integrations/api/[id]/route.ts +14 -15
- package/src/modules/integrations/api/route.ts +3 -3
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +176 -54
- package/src/modules/integrations/lib/state-service.ts +25 -1
- package/src/modules/messages/api/[id]/route.ts +25 -22
- package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +3 -3
- package/src/modules/progress/api/active/route.ts +4 -1
- package/src/modules/progress/api/jobs/[id]/route.ts +1 -1
- package/src/modules/progress/api/jobs/route.ts +1 -1
- package/src/modules/progress/lib/events.ts +6 -0
- package/src/modules/progress/lib/progressService.ts +1 -0
- package/src/modules/progress/lib/progressServiceImpl.ts +47 -1
- package/src/modules/query_index/lib/document.ts +52 -1
- package/src/modules/query_index/lib/engine.ts +104 -4
- package/src/modules/query_index/lib/indexer.ts +2 -0
- package/src/modules/sales/api/adjustment-kinds/route.ts +3 -9
- package/src/modules/sales/api/channels/route.ts +3 -10
- package/src/modules/sales/api/delivery-windows/route.ts +3 -10
- package/src/modules/sales/api/payment-methods/route.ts +3 -11
- package/src/modules/sales/api/price-kinds/route.ts +3 -5
- package/src/modules/sales/api/shipping-methods/route.ts +3 -11
- package/src/modules/sales/api/tags/route.ts +3 -9
- package/src/modules/sales/api/tax-rates/route.ts +3 -13
- package/src/modules/sales/api/utils.ts +9 -0
- package/src/modules/sales/lib/makeStatusDictionaryRoute.ts +3 -9
- package/src/modules/workflows/api/definitions/[id]/route.ts +3 -2
- package/src/modules/workflows/api/definitions/route.ts +4 -3
- package/src/modules/workflows/api/definitions/serialize.ts +23 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/workflows/api/definitions/route.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Workflow Definitions API\n *\n * Endpoints:\n * - GET /api/workflows/definitions - List workflow definitions\n * - POST /api/workflows/definitions - Create workflow definition\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 { WorkflowDefinition } from '../../data/entities'\nimport {\n createWorkflowDefinitionInputSchema,\n type CreateWorkflowDefinitionApiInput,\n} from '../../data/validators'\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['workflows.definitions.view'],\n}\n\n/**\n * GET /api/workflows/definitions\n *\n * List workflow definitions with optional filters\n */\nexport async function GET(request: NextRequest) {\n try {\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 const { searchParams } = new URL(request.url)\n const enabled = searchParams.get('enabled')\n const workflowId = searchParams.get('workflowId')\n const search = searchParams.get('search')\n const limit = parseInt(searchParams.get('limit') || '50')\n const offset = parseInt(searchParams.get('offset') || '0')\n\n // Build where clause with tenant scoping\n const where: any = {\n tenantId,\n organizationId,\n deletedAt: null,\n }\n\n if (enabled !== null) {\n where.enabled = enabled === 'true'\n }\n\n if (workflowId) {\n where.workflowId = workflowId\n }\n\n if (search) {\n where.$or = [\n { workflowId: { $ilike: `%${search}%` } },\n { 'definition.workflowName': { $ilike: `%${search}%` } },\n ]\n }\n\n const [definitions, total] = await em.findAndCount(\n WorkflowDefinition,\n where,\n {\n orderBy: { createdAt: 'DESC' },\n limit,\n offset,\n }\n )\n\n return NextResponse.json({\n data: definitions,\n pagination: {\n total,\n limit,\n offset,\n hasMore: offset + limit < total,\n },\n })\n } catch (error) {\n console.error('Error listing workflow definitions:', error)\n return NextResponse.json(\n { error: 'Failed to list workflow definitions' },\n { status: 500 }\n )\n }\n}\n\n/**\n * POST /api/workflows/definitions\n *\n * Create a new workflow definition\n */\nexport async function POST(request: NextRequest) {\n try {\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 create permission\n const rbacService = container.resolve('rbacService')\n const hasPermission = await rbacService.userHasAllFeatures(\n auth.sub,\n ['workflows.definitions.create'],\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 const body = await request.json()\n\n // Validate input\n const validation = createWorkflowDefinitionInputSchema.safeParse(body)\n if (!validation.success) {\n return NextResponse.json(\n {\n error: 'Validation failed',\n details: validation.error.issues,\n },\n { status: 400 }\n )\n }\n\n const input: CreateWorkflowDefinitionApiInput = validation.data\n\n // Check if workflow with same ID and version already exists\n const existing = await em.findOne(WorkflowDefinition, {\n workflowId: input.workflowId,\n version: input.version,\n tenantId,\n organizationId,\n deletedAt: null,\n })\n\n if (existing) {\n return NextResponse.json(\n {\n error: `Workflow definition with ID \"${input.workflowId}\" and version ${input.version} already exists`,\n },\n { status: 409 }\n )\n }\n\n // Create workflow definition\n const definition = em.create(WorkflowDefinition, {\n workflowId: input.workflowId,\n workflowName: input.workflowName,\n description: input.description,\n version: input.version,\n definition: input.definition,\n metadata: input.metadata,\n enabled: input.enabled ?? true,\n tenantId,\n organizationId,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n\n await em.persistAndFlush(definition)\n\n return NextResponse.json(\n {\n data: definition,\n message: 'Workflow definition created successfully',\n },\n { status: 201 }\n )\n } catch (error) {\n console.error('Error creating workflow definition:', error)\n return NextResponse.json(\n { error: 'Failed to create workflow definition' },\n { status: 500 }\n )\n }\n}\n\nexport const openApi = {\n methods: {\n GET: {\n summary: 'List workflow definitions',\n description: 'Get a list of workflow definitions with optional filters. Supports pagination and search.',\n tags: ['Workflows'],\n query: createWorkflowDefinitionInputSchema.pick({ workflowId: true }).extend({\n enabled: z.boolean().optional(),\n search: z.string().optional(),\n limit: z.number().int().positive().default(50).optional(),\n offset: z.number().int().min(0).default(0).optional(),\n }),\n responses: [\n {\n status: 200,\n description: 'List of workflow definitions with pagination',\n example: {\n data: [\n {\n id: '123e4567-e89b-12d3-a456-426614174000',\n workflowId: 'checkout-flow',\n workflowName: 'Checkout Flow',\n description: 'Complete checkout workflow for processing orders',\n version: 1,\n definition: {\n steps: [\n {\n stepId: 'start',\n stepName: 'Start',\n stepType: 'START',\n },\n {\n stepId: 'validate-cart',\n stepName: 'Validate Cart',\n stepType: 'AUTOMATED',\n },\n {\n stepId: 'end',\n stepName: 'End',\n stepType: 'END',\n },\n ],\n transitions: [\n {\n transitionId: 'start-to-validate',\n fromStepId: 'start',\n toStepId: 'validate-cart',\n trigger: 'auto',\n },\n {\n transitionId: 'validate-to-end',\n fromStepId: 'validate-cart',\n toStepId: 'end',\n trigger: 'auto',\n },\n ],\n },\n enabled: true,\n tenantId: '123e4567-e89b-12d3-a456-426614174001',\n organizationId: '123e4567-e89b-12d3-a456-426614174002',\n createdAt: '2025-12-08T10:00:00.000Z',\n updatedAt: '2025-12-08T10:00:00.000Z',\n },\n ],\n pagination: {\n total: 1,\n limit: 50,\n offset: 0,\n hasMore: false,\n },\n },\n },\n ],\n },\n POST: {\n summary: 'Create workflow definition',\n description: 'Create a new workflow definition. The definition must include at least START and END steps with at least one transition connecting them.',\n tags: ['Workflows'],\n requestBody: {\n schema: createWorkflowDefinitionInputSchema,\n example: {\n workflowId: 'checkout-flow',\n workflowName: 'Checkout Flow',\n description: 'Complete checkout workflow for processing orders',\n version: 1,\n definition: {\n steps: [\n {\n stepId: 'start',\n stepName: 'Start',\n stepType: 'START',\n },\n {\n stepId: 'validate-cart',\n stepName: 'Validate Cart',\n stepType: 'AUTOMATED',\n description: 'Validate cart items and check inventory',\n },\n {\n stepId: 'payment',\n stepName: 'Process Payment',\n stepType: 'AUTOMATED',\n description: 'Charge payment method',\n retryPolicy: {\n maxAttempts: 3,\n backoffMs: 1000,\n },\n },\n {\n stepId: 'end',\n stepName: 'End',\n stepType: 'END',\n },\n ],\n transitions: [\n {\n transitionId: 'start-to-validate',\n fromStepId: 'start',\n toStepId: 'validate-cart',\n trigger: 'auto',\n },\n {\n transitionId: 'validate-to-payment',\n fromStepId: 'validate-cart',\n toStepId: 'payment',\n trigger: 'auto',\n },\n {\n transitionId: 'payment-to-end',\n fromStepId: 'payment',\n toStepId: 'end',\n trigger: 'auto',\n activities: [\n {\n activityName: 'Send Order Confirmation',\n activityType: 'SEND_EMAIL',\n config: {\n to: '{{context.customerEmail}}',\n subject: 'Order Confirmation #{{context.orderId}}',\n template: 'order_confirmation',\n },\n },\n ],\n },\n ],\n },\n enabled: true,\n },\n },\n responses: [\n {\n status: 201,\n description: 'Workflow definition created successfully',\n example: {\n data: {\n id: '123e4567-e89b-12d3-a456-426614174000',\n workflowId: 'checkout-flow',\n workflowName: 'Checkout Flow',\n description: 'Complete checkout workflow for processing orders',\n version: 1,\n definition: {\n steps: [\n { stepId: 'start', stepName: 'Start', stepType: 'START' },\n {\n stepId: 'validate-cart',\n stepName: 'Validate Cart',\n stepType: 'AUTOMATED',\n },\n {\n stepId: 'payment',\n stepName: 'Process Payment',\n stepType: 'AUTOMATED',\n },\n { stepId: 'end', stepName: 'End', stepType: 'END' },\n ],\n transitions: [\n {\n transitionId: 'start-to-validate',\n fromStepId: 'start',\n toStepId: 'validate-cart',\n trigger: 'auto',\n },\n {\n transitionId: 'validate-to-payment',\n fromStepId: 'validate-cart',\n toStepId: 'payment',\n trigger: 'auto',\n },\n {\n transitionId: 'payment-to-end',\n fromStepId: 'payment',\n toStepId: 'end',\n trigger: 'auto',\n },\n ],\n },\n enabled: true,\n tenantId: '123e4567-e89b-12d3-a456-426614174001',\n organizationId: '123e4567-e89b-12d3-a456-426614174002',\n createdAt: '2025-12-08T10:00:00.000Z',\n updatedAt: '2025-12-08T10:00:00.000Z',\n },\n message: 'Workflow definition created successfully',\n },\n },\n {\n status: 400,\n description: 'Validation error - invalid workflow structure',\n example: {\n error: 'Validation failed',\n details: [\n {\n code: 'invalid_type',\n message: 'Workflow must have at least START and END steps',\n path: ['definition', 'steps'],\n },\n ],\n },\n },\n {\n status: 409,\n description: 'Conflict - workflow with same ID and version already exists',\n example: {\n error: 'Workflow definition with ID \"checkout-flow\" and version 1 already exists',\n },\n },\n ],\n },\n },\n}\n\n// Full OpenAPI documentation (kept for reference but not used by type system)\nexport const _openApiDetailedDocs = {\n get: {\n summary: 'List workflow definitions',\n description: 'Get a list of workflow definitions with optional filters',\n tags: ['Workflows'],\n parameters: [\n {\n name: 'enabled',\n in: 'query',\n description: 'Filter by enabled status',\n schema: { type: 'boolean' },\n },\n {\n name: 'workflowId',\n in: 'query',\n description: 'Filter by workflow ID',\n schema: { type: 'string' },\n },\n {\n name: 'search',\n in: 'query',\n description: 'Search in workflow ID and name',\n schema: { type: 'string' },\n },\n {\n name: 'limit',\n in: 'query',\n description: 'Number of results to return',\n schema: { type: 'integer', default: 50 },\n },\n {\n name: 'offset',\n in: 'query',\n description: 'Offset for pagination',\n schema: { type: 'integer', default: 0 },\n },\n ],\n responses: {\n 200: {\n description: 'List of workflow definitions',\n content: {\n 'application/json': {\n schema: {\n type: 'object',\n properties: {\n data: {\n type: 'array',\n items: { $ref: '#/components/schemas/WorkflowDefinition' },\n },\n pagination: {\n type: 'object',\n properties: {\n total: { type: 'integer' },\n limit: { type: 'integer' },\n offset: { type: 'integer' },\n hasMore: { type: 'boolean' },\n },\n },\n },\n },\n },\n },\n },\n },\n },\n post: {\n summary: 'Create workflow definition',\n description: 'Create a new workflow definition',\n tags: ['Workflows'],\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema: { $ref: '#/components/schemas/CreateWorkflowDefinition' },\n },\n },\n },\n responses: {\n 201: {\n description: 'Workflow definition created',\n content: {\n 'application/json': {\n schema: {\n type: 'object',\n properties: {\n data: { $ref: '#/components/schemas/WorkflowDefinition' },\n message: { type: 'string' },\n },\n },\n },\n },\n },\n 400: {\n description: 'Validation error',\n },\n 409: {\n description: 'Workflow definition already exists',\n },\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAQA,SAAsB,oBAAoB;AAC1C,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,0BAA0B;AACnC;AAAA,EACE;AAAA,OAEK;
|
|
4
|
+
"sourcesContent": ["/**\n * Workflow Definitions API\n *\n * Endpoints:\n * - GET /api/workflows/definitions - List workflow definitions\n * - POST /api/workflows/definitions - Create workflow definition\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 { WorkflowDefinition } from '../../data/entities'\nimport {\n createWorkflowDefinitionInputSchema,\n type CreateWorkflowDefinitionApiInput,\n} from '../../data/validators'\nimport { serializeWorkflowDefinition } from './serialize'\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['workflows.definitions.view'],\n}\n\n/**\n * GET /api/workflows/definitions\n *\n * List workflow definitions with optional filters\n */\nexport async function GET(request: NextRequest) {\n try {\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 const { searchParams } = new URL(request.url)\n const enabled = searchParams.get('enabled')\n const workflowId = searchParams.get('workflowId')\n const search = searchParams.get('search')\n const limit = parseInt(searchParams.get('limit') || '50')\n const offset = parseInt(searchParams.get('offset') || '0')\n\n // Build where clause with tenant scoping\n const where: any = {\n tenantId,\n organizationId,\n deletedAt: null,\n }\n\n if (enabled !== null) {\n where.enabled = enabled === 'true'\n }\n\n if (workflowId) {\n where.workflowId = workflowId\n }\n\n if (search) {\n where.$or = [\n { workflowId: { $ilike: `%${search}%` } },\n { workflowName: { $ilike: `%${search}%` } },\n ]\n }\n\n const [definitions, total] = await em.findAndCount(\n WorkflowDefinition,\n where,\n {\n orderBy: { createdAt: 'DESC' },\n limit,\n offset,\n }\n )\n\n return NextResponse.json({\n data: definitions.map(serializeWorkflowDefinition),\n pagination: {\n total,\n limit,\n offset,\n hasMore: offset + limit < total,\n },\n })\n } catch (error) {\n console.error('Error listing workflow definitions:', error)\n return NextResponse.json(\n { error: 'Failed to list workflow definitions' },\n { status: 500 }\n )\n }\n}\n\n/**\n * POST /api/workflows/definitions\n *\n * Create a new workflow definition\n */\nexport async function POST(request: NextRequest) {\n try {\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 create permission\n const rbacService = container.resolve('rbacService')\n const hasPermission = await rbacService.userHasAllFeatures(\n auth.sub,\n ['workflows.definitions.create'],\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 const body = await request.json()\n\n // Validate input\n const validation = createWorkflowDefinitionInputSchema.safeParse(body)\n if (!validation.success) {\n return NextResponse.json(\n {\n error: 'Validation failed',\n details: validation.error.issues,\n },\n { status: 400 }\n )\n }\n\n const input: CreateWorkflowDefinitionApiInput = validation.data\n\n // Check if workflow with same ID and version already exists\n const existing = await em.findOne(WorkflowDefinition, {\n workflowId: input.workflowId,\n version: input.version,\n tenantId,\n organizationId,\n deletedAt: null,\n })\n\n if (existing) {\n return NextResponse.json(\n {\n error: `Workflow definition with ID \"${input.workflowId}\" and version ${input.version} already exists`,\n },\n { status: 409 }\n )\n }\n\n // Create workflow definition\n const definition = em.create(WorkflowDefinition, {\n workflowId: input.workflowId,\n workflowName: input.workflowName,\n description: input.description,\n version: input.version,\n definition: input.definition,\n metadata: input.metadata,\n enabled: input.enabled ?? true,\n tenantId,\n organizationId,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n\n await em.persistAndFlush(definition)\n\n return NextResponse.json(\n {\n data: serializeWorkflowDefinition(definition),\n message: 'Workflow definition created successfully',\n },\n { status: 201 }\n )\n } catch (error) {\n console.error('Error creating workflow definition:', error)\n return NextResponse.json(\n { error: 'Failed to create workflow definition' },\n { status: 500 }\n )\n }\n}\n\nexport const openApi = {\n methods: {\n GET: {\n summary: 'List workflow definitions',\n description: 'Get a list of workflow definitions with optional filters. Supports pagination and search.',\n tags: ['Workflows'],\n query: createWorkflowDefinitionInputSchema.pick({ workflowId: true }).extend({\n enabled: z.boolean().optional(),\n search: z.string().optional(),\n limit: z.number().int().positive().default(50).optional(),\n offset: z.number().int().min(0).default(0).optional(),\n }),\n responses: [\n {\n status: 200,\n description: 'List of workflow definitions with pagination',\n example: {\n data: [\n {\n id: '123e4567-e89b-12d3-a456-426614174000',\n workflowId: 'checkout-flow',\n workflowName: 'Checkout Flow',\n description: 'Complete checkout workflow for processing orders',\n version: 1,\n definition: {\n steps: [\n {\n stepId: 'start',\n stepName: 'Start',\n stepType: 'START',\n },\n {\n stepId: 'validate-cart',\n stepName: 'Validate Cart',\n stepType: 'AUTOMATED',\n },\n {\n stepId: 'end',\n stepName: 'End',\n stepType: 'END',\n },\n ],\n transitions: [\n {\n transitionId: 'start-to-validate',\n fromStepId: 'start',\n toStepId: 'validate-cart',\n trigger: 'auto',\n },\n {\n transitionId: 'validate-to-end',\n fromStepId: 'validate-cart',\n toStepId: 'end',\n trigger: 'auto',\n },\n ],\n },\n enabled: true,\n tenantId: '123e4567-e89b-12d3-a456-426614174001',\n organizationId: '123e4567-e89b-12d3-a456-426614174002',\n createdAt: '2025-12-08T10:00:00.000Z',\n updatedAt: '2025-12-08T10:00:00.000Z',\n },\n ],\n pagination: {\n total: 1,\n limit: 50,\n offset: 0,\n hasMore: false,\n },\n },\n },\n ],\n },\n POST: {\n summary: 'Create workflow definition',\n description: 'Create a new workflow definition. The definition must include at least START and END steps with at least one transition connecting them.',\n tags: ['Workflows'],\n requestBody: {\n schema: createWorkflowDefinitionInputSchema,\n example: {\n workflowId: 'checkout-flow',\n workflowName: 'Checkout Flow',\n description: 'Complete checkout workflow for processing orders',\n version: 1,\n definition: {\n steps: [\n {\n stepId: 'start',\n stepName: 'Start',\n stepType: 'START',\n },\n {\n stepId: 'validate-cart',\n stepName: 'Validate Cart',\n stepType: 'AUTOMATED',\n description: 'Validate cart items and check inventory',\n },\n {\n stepId: 'payment',\n stepName: 'Process Payment',\n stepType: 'AUTOMATED',\n description: 'Charge payment method',\n retryPolicy: {\n maxAttempts: 3,\n backoffMs: 1000,\n },\n },\n {\n stepId: 'end',\n stepName: 'End',\n stepType: 'END',\n },\n ],\n transitions: [\n {\n transitionId: 'start-to-validate',\n fromStepId: 'start',\n toStepId: 'validate-cart',\n trigger: 'auto',\n },\n {\n transitionId: 'validate-to-payment',\n fromStepId: 'validate-cart',\n toStepId: 'payment',\n trigger: 'auto',\n },\n {\n transitionId: 'payment-to-end',\n fromStepId: 'payment',\n toStepId: 'end',\n trigger: 'auto',\n activities: [\n {\n activityName: 'Send Order Confirmation',\n activityType: 'SEND_EMAIL',\n config: {\n to: '{{context.customerEmail}}',\n subject: 'Order Confirmation #{{context.orderId}}',\n template: 'order_confirmation',\n },\n },\n ],\n },\n ],\n },\n enabled: true,\n },\n },\n responses: [\n {\n status: 201,\n description: 'Workflow definition created successfully',\n example: {\n data: {\n id: '123e4567-e89b-12d3-a456-426614174000',\n workflowId: 'checkout-flow',\n workflowName: 'Checkout Flow',\n description: 'Complete checkout workflow for processing orders',\n version: 1,\n definition: {\n steps: [\n { stepId: 'start', stepName: 'Start', stepType: 'START' },\n {\n stepId: 'validate-cart',\n stepName: 'Validate Cart',\n stepType: 'AUTOMATED',\n },\n {\n stepId: 'payment',\n stepName: 'Process Payment',\n stepType: 'AUTOMATED',\n },\n { stepId: 'end', stepName: 'End', stepType: 'END' },\n ],\n transitions: [\n {\n transitionId: 'start-to-validate',\n fromStepId: 'start',\n toStepId: 'validate-cart',\n trigger: 'auto',\n },\n {\n transitionId: 'validate-to-payment',\n fromStepId: 'validate-cart',\n toStepId: 'payment',\n trigger: 'auto',\n },\n {\n transitionId: 'payment-to-end',\n fromStepId: 'payment',\n toStepId: 'end',\n trigger: 'auto',\n },\n ],\n },\n enabled: true,\n tenantId: '123e4567-e89b-12d3-a456-426614174001',\n organizationId: '123e4567-e89b-12d3-a456-426614174002',\n createdAt: '2025-12-08T10:00:00.000Z',\n updatedAt: '2025-12-08T10:00:00.000Z',\n },\n message: 'Workflow definition created successfully',\n },\n },\n {\n status: 400,\n description: 'Validation error - invalid workflow structure',\n example: {\n error: 'Validation failed',\n details: [\n {\n code: 'invalid_type',\n message: 'Workflow must have at least START and END steps',\n path: ['definition', 'steps'],\n },\n ],\n },\n },\n {\n status: 409,\n description: 'Conflict - workflow with same ID and version already exists',\n example: {\n error: 'Workflow definition with ID \"checkout-flow\" and version 1 already exists',\n },\n },\n ],\n },\n },\n}\n\n// Full OpenAPI documentation (kept for reference but not used by type system)\nexport const _openApiDetailedDocs = {\n get: {\n summary: 'List workflow definitions',\n description: 'Get a list of workflow definitions with optional filters',\n tags: ['Workflows'],\n parameters: [\n {\n name: 'enabled',\n in: 'query',\n description: 'Filter by enabled status',\n schema: { type: 'boolean' },\n },\n {\n name: 'workflowId',\n in: 'query',\n description: 'Filter by workflow ID',\n schema: { type: 'string' },\n },\n {\n name: 'search',\n in: 'query',\n description: 'Search in workflow ID and name',\n schema: { type: 'string' },\n },\n {\n name: 'limit',\n in: 'query',\n description: 'Number of results to return',\n schema: { type: 'integer', default: 50 },\n },\n {\n name: 'offset',\n in: 'query',\n description: 'Offset for pagination',\n schema: { type: 'integer', default: 0 },\n },\n ],\n responses: {\n 200: {\n description: 'List of workflow definitions',\n content: {\n 'application/json': {\n schema: {\n type: 'object',\n properties: {\n data: {\n type: 'array',\n items: { $ref: '#/components/schemas/WorkflowDefinition' },\n },\n pagination: {\n type: 'object',\n properties: {\n total: { type: 'integer' },\n limit: { type: 'integer' },\n offset: { type: 'integer' },\n hasMore: { type: 'boolean' },\n },\n },\n },\n },\n },\n },\n },\n },\n },\n post: {\n summary: 'Create workflow definition',\n description: 'Create a new workflow definition',\n tags: ['Workflows'],\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema: { $ref: '#/components/schemas/CreateWorkflowDefinition' },\n },\n },\n },\n responses: {\n 201: {\n description: 'Workflow definition created',\n content: {\n 'application/json': {\n schema: {\n type: 'object',\n properties: {\n data: { $ref: '#/components/schemas/WorkflowDefinition' },\n message: { type: 'string' },\n },\n },\n },\n },\n },\n 400: {\n description: 'Validation error',\n },\n 409: {\n description: 'Workflow definition already exists',\n },\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
function serializeWorkflowDefinition(definition) {
|
|
2
|
+
return {
|
|
3
|
+
id: definition.id,
|
|
4
|
+
workflowId: definition.workflowId,
|
|
5
|
+
workflowName: definition.workflowName,
|
|
6
|
+
description: definition.description ?? null,
|
|
7
|
+
version: definition.version,
|
|
8
|
+
definition: definition.definition,
|
|
9
|
+
metadata: definition.metadata ?? null,
|
|
10
|
+
enabled: definition.enabled,
|
|
11
|
+
effectiveFrom: definition.effectiveFrom ?? null,
|
|
12
|
+
effectiveTo: definition.effectiveTo ?? null,
|
|
13
|
+
tenantId: definition.tenantId,
|
|
14
|
+
organizationId: definition.organizationId,
|
|
15
|
+
createdBy: definition.createdBy ?? null,
|
|
16
|
+
updatedBy: definition.updatedBy ?? null,
|
|
17
|
+
createdAt: definition.createdAt,
|
|
18
|
+
updatedAt: definition.updatedAt,
|
|
19
|
+
deletedAt: definition.deletedAt ?? null
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
serializeWorkflowDefinition
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=serialize.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/workflows/api/definitions/serialize.ts"],
|
|
4
|
+
"sourcesContent": ["import type { WorkflowDefinition } from '../../data/entities'\n\nexport function serializeWorkflowDefinition(definition: WorkflowDefinition) {\n return {\n id: definition.id,\n workflowId: definition.workflowId,\n workflowName: definition.workflowName,\n description: definition.description ?? null,\n version: definition.version,\n definition: definition.definition,\n metadata: definition.metadata ?? null,\n enabled: definition.enabled,\n effectiveFrom: definition.effectiveFrom ?? null,\n effectiveTo: definition.effectiveTo ?? null,\n tenantId: definition.tenantId,\n organizationId: definition.organizationId,\n createdBy: definition.createdBy ?? null,\n updatedBy: definition.updatedBy ?? null,\n createdAt: definition.createdAt,\n updatedAt: definition.updatedAt,\n deletedAt: definition.deletedAt ?? null,\n }\n}\n"],
|
|
5
|
+
"mappings": "AAEO,SAAS,4BAA4B,YAAgC;AAC1E,SAAO;AAAA,IACL,IAAI,WAAW;AAAA,IACf,YAAY,WAAW;AAAA,IACvB,cAAc,WAAW;AAAA,IACzB,aAAa,WAAW,eAAe;AAAA,IACvC,SAAS,WAAW;AAAA,IACpB,YAAY,WAAW;AAAA,IACvB,UAAU,WAAW,YAAY;AAAA,IACjC,SAAS,WAAW;AAAA,IACpB,eAAe,WAAW,iBAAiB;AAAA,IAC3C,aAAa,WAAW,eAAe;AAAA,IACvC,UAAU,WAAW;AAAA,IACrB,gBAAgB,WAAW;AAAA,IAC3B,WAAW,WAAW,aAAa;AAAA,IACnC,WAAW,WAAW,aAAa;AAAA,IACnC,WAAW,WAAW;AAAA,IACtB,WAAW,WAAW;AAAA,IACtB,WAAW,WAAW,aAAa;AAAA,EACrC;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.4.7-develop-
|
|
3
|
+
"version": "0.4.7-develop-74069040de",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -217,10 +217,10 @@
|
|
|
217
217
|
"semver": "^7.6.3"
|
|
218
218
|
},
|
|
219
219
|
"peerDependencies": {
|
|
220
|
-
"@open-mercato/shared": "0.4.7-develop-
|
|
220
|
+
"@open-mercato/shared": "0.4.7-develop-74069040de"
|
|
221
221
|
},
|
|
222
222
|
"devDependencies": {
|
|
223
|
-
"@open-mercato/shared": "0.4.7-develop-
|
|
223
|
+
"@open-mercato/shared": "0.4.7-develop-74069040de",
|
|
224
224
|
"@testing-library/dom": "^10.4.1",
|
|
225
225
|
"@testing-library/jest-dom": "^6.9.1",
|
|
226
226
|
"@testing-library/react": "^16.3.1",
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
4
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
5
|
+
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
6
|
+
import type { ProgressService } from '../../../progress/lib/progressService'
|
|
7
|
+
import {
|
|
8
|
+
CATALOG_PRODUCT_BULK_DELETE_QUEUE,
|
|
9
|
+
getCatalogQueue,
|
|
10
|
+
} from '../../lib/bulkDelete'
|
|
11
|
+
|
|
12
|
+
const requestSchema = z.object({
|
|
13
|
+
confirm: z.literal(true),
|
|
14
|
+
ids: z.array(z.string().uuid()).min(1).max(10000),
|
|
15
|
+
scope: z.enum(['selected', 'filtered']),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const responseSchema = z.object({
|
|
19
|
+
ok: z.boolean(),
|
|
20
|
+
progressJobId: z.string().uuid().nullable(),
|
|
21
|
+
message: z.string(),
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
export const metadata = {
|
|
25
|
+
POST: { requireAuth: true, requireFeatures: ['catalog.products.manage'] },
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const openApi = {
|
|
29
|
+
tags: ['Catalog'],
|
|
30
|
+
summary: 'Start bulk deleting catalog products',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function POST(req: Request) {
|
|
34
|
+
const auth = await getAuthFromRequest(req)
|
|
35
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
36
|
+
return NextResponse.json(responseSchema.parse({
|
|
37
|
+
ok: false,
|
|
38
|
+
progressJobId: null,
|
|
39
|
+
message: 'Unauthorized',
|
|
40
|
+
}), { status: 401 })
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const parsed = requestSchema.safeParse(await readJsonSafe(req))
|
|
44
|
+
if (!parsed.success) {
|
|
45
|
+
return NextResponse.json(responseSchema.parse({
|
|
46
|
+
ok: false,
|
|
47
|
+
progressJobId: null,
|
|
48
|
+
message: 'Invalid payload',
|
|
49
|
+
}), { status: 400 })
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const ids = Array.from(new Set(parsed.data.ids))
|
|
53
|
+
const container = await createRequestContainer()
|
|
54
|
+
const progressService = container.resolve('progressService') as ProgressService
|
|
55
|
+
|
|
56
|
+
const progressJob = await progressService.createJob(
|
|
57
|
+
{
|
|
58
|
+
jobType: 'catalog.products.bulk_delete',
|
|
59
|
+
name: parsed.data.scope === 'filtered'
|
|
60
|
+
? 'Delete filtered products'
|
|
61
|
+
: 'Delete selected products',
|
|
62
|
+
description: `${ids.length} catalog products queued for deletion`,
|
|
63
|
+
totalCount: ids.length,
|
|
64
|
+
cancellable: false,
|
|
65
|
+
meta: {
|
|
66
|
+
source: 'catalog.bulk-delete',
|
|
67
|
+
scope: parsed.data.scope,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
tenantId: auth.tenantId,
|
|
72
|
+
organizationId: auth.orgId,
|
|
73
|
+
userId: auth.sub,
|
|
74
|
+
},
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
const queue = getCatalogQueue(CATALOG_PRODUCT_BULK_DELETE_QUEUE)
|
|
78
|
+
await queue.enqueue({
|
|
79
|
+
progressJobId: progressJob.id,
|
|
80
|
+
ids,
|
|
81
|
+
scope: {
|
|
82
|
+
organizationId: auth.orgId,
|
|
83
|
+
tenantId: auth.tenantId,
|
|
84
|
+
userId: auth.sub,
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
return NextResponse.json(responseSchema.parse({
|
|
89
|
+
ok: true,
|
|
90
|
+
progressJobId: progressJob.id,
|
|
91
|
+
message: 'Bulk delete started.',
|
|
92
|
+
}), { status: 202 })
|
|
93
|
+
}
|
|
@@ -242,7 +242,7 @@ const crud = makeCrudRoute({
|
|
|
242
242
|
Number.isFinite(query.quantity) &&
|
|
243
243
|
query.quantity > 0
|
|
244
244
|
) {
|
|
245
|
-
|
|
245
|
+
await resolveNormalizedQuantityForFilter({
|
|
246
246
|
em,
|
|
247
247
|
organizationId,
|
|
248
248
|
tenantId,
|
|
@@ -251,11 +251,6 @@ const crud = makeCrudRoute({
|
|
|
251
251
|
quantity: query.quantity,
|
|
252
252
|
quantityUnit: query.quantityUnit,
|
|
253
253
|
});
|
|
254
|
-
filters.min_quantity = { $lte: normalizedQuantity };
|
|
255
|
-
filters.$or = [
|
|
256
|
-
{ max_quantity: null },
|
|
257
|
-
{ max_quantity: { $gte: normalizedQuantity } },
|
|
258
|
-
];
|
|
259
254
|
}
|
|
260
255
|
return filters;
|
|
261
256
|
},
|
|
@@ -269,6 +264,58 @@ const crud = makeCrudRoute({
|
|
|
269
264
|
return { ...normalized, ...cfEntries };
|
|
270
265
|
},
|
|
271
266
|
},
|
|
267
|
+
hooks: {
|
|
268
|
+
afterList: async (payload, ctx) => {
|
|
269
|
+
const query = ctx.query as PriceQuery;
|
|
270
|
+
if (
|
|
271
|
+
typeof query.quantity !== "number" ||
|
|
272
|
+
!Number.isFinite(query.quantity) ||
|
|
273
|
+
query.quantity <= 0
|
|
274
|
+
) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const organizationId =
|
|
279
|
+
ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null;
|
|
280
|
+
const tenantId = ctx.auth?.tenantId ?? null;
|
|
281
|
+
if (!organizationId || !tenantId) return;
|
|
282
|
+
|
|
283
|
+
const em = ctx.container.resolve("em") as EntityManager;
|
|
284
|
+
const normalizedQuantity = await resolveNormalizedQuantityForFilter({
|
|
285
|
+
em,
|
|
286
|
+
organizationId,
|
|
287
|
+
tenantId,
|
|
288
|
+
productId: query.productId,
|
|
289
|
+
variantId: query.variantId,
|
|
290
|
+
quantity: query.quantity,
|
|
291
|
+
quantityUnit: query.quantityUnit,
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const filteredItems = payload.items.filter((item: unknown) => {
|
|
295
|
+
const row = item as Record<string, unknown>;
|
|
296
|
+
const minQuantity = Number(row.min_quantity ?? row.minQuantity ?? 1);
|
|
297
|
+
if (!Number.isFinite(minQuantity) || minQuantity > normalizedQuantity) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const maxRaw = row.max_quantity ?? row.maxQuantity ?? null;
|
|
302
|
+
if (maxRaw === null || maxRaw === undefined) return true;
|
|
303
|
+
|
|
304
|
+
const maxQuantity = Number(maxRaw);
|
|
305
|
+
return Number.isFinite(maxQuantity) && maxQuantity >= normalizedQuantity;
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
payload.items = filteredItems;
|
|
309
|
+
payload.total = filteredItems.length;
|
|
310
|
+
payload.totalPages =
|
|
311
|
+
filteredItems.length === 0
|
|
312
|
+
? 0
|
|
313
|
+
: Math.max(
|
|
314
|
+
1,
|
|
315
|
+
Math.ceil(filteredItems.length / Math.max(payload.pageSize, 1)),
|
|
316
|
+
);
|
|
317
|
+
},
|
|
318
|
+
},
|
|
272
319
|
actions: {
|
|
273
320
|
create: {
|
|
274
321
|
commandId: "catalog.prices.create",
|
|
@@ -165,17 +165,6 @@ export async function buildProductFilters(
|
|
|
165
165
|
if (query.id) {
|
|
166
166
|
filters.id = { $eq: query.id };
|
|
167
167
|
}
|
|
168
|
-
const term = sanitizeSearchTerm(query.search);
|
|
169
|
-
if (term) {
|
|
170
|
-
const like = `%${escapeLikePattern(term)}%`;
|
|
171
|
-
filters.$or = [
|
|
172
|
-
{ title: { $ilike: like } },
|
|
173
|
-
{ subtitle: { $ilike: like } },
|
|
174
|
-
{ sku: { $ilike: like } },
|
|
175
|
-
{ handle: { $ilike: like } },
|
|
176
|
-
{ description: { $ilike: like } },
|
|
177
|
-
];
|
|
178
|
-
}
|
|
179
168
|
if (query.status && query.status.trim()) {
|
|
180
169
|
filters.status_entry_id = { $eq: query.status.trim() };
|
|
181
170
|
}
|
|
@@ -190,6 +179,12 @@ export async function buildProductFilters(
|
|
|
190
179
|
if (query.productType) {
|
|
191
180
|
filters.product_type = { $eq: query.productType };
|
|
192
181
|
}
|
|
182
|
+
const term = sanitizeSearchTerm(query.search);
|
|
183
|
+
if (term) {
|
|
184
|
+
filters.search_text = {
|
|
185
|
+
$ilike: `%${escapeLikePattern(term)}%`,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
193
188
|
const scope = {
|
|
194
189
|
organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
|
|
195
190
|
tenantId: ctx.auth?.tenantId ?? null,
|
|
@@ -807,6 +807,7 @@ async function syncCategoryAssignments(
|
|
|
807
807
|
product: CatalogProduct,
|
|
808
808
|
categoryIds: string[] | undefined,
|
|
809
809
|
): Promise<void> {
|
|
810
|
+
if (categoryIds === undefined) return;
|
|
810
811
|
const normalized = Array.from(
|
|
811
812
|
new Set(
|
|
812
813
|
(Array.isArray(categoryIds) ? categoryIds : [])
|
|
@@ -866,6 +867,7 @@ async function syncProductTags(
|
|
|
866
867
|
product: CatalogProduct,
|
|
867
868
|
tags: string[] | undefined,
|
|
868
869
|
): Promise<void> {
|
|
870
|
+
if (tags === undefined) return;
|
|
869
871
|
const labelMap = new Map<string, string>();
|
|
870
872
|
if (Array.isArray(tags)) {
|
|
871
873
|
tags.forEach((raw) => {
|
|
@@ -147,6 +147,7 @@ export default function ProductsDataTable() {
|
|
|
147
147
|
const [page, setPage] = React.useState(1)
|
|
148
148
|
const [total, setTotal] = React.useState(0)
|
|
149
149
|
const [totalPages, setTotalPages] = React.useState(1)
|
|
150
|
+
const [cacheStatus, setCacheStatus] = React.useState<'hit' | 'miss' | null>(null)
|
|
150
151
|
const [sorting, setSorting] = React.useState<SortingState>([{ id: 'title', desc: false }])
|
|
151
152
|
const [search, setSearch] = React.useState('')
|
|
152
153
|
const [filterValues, setFilterValues] = React.useState<FilterValues>({})
|
|
@@ -536,6 +537,7 @@ export default function ProductsDataTable() {
|
|
|
536
537
|
let cancelled = false
|
|
537
538
|
async function load() {
|
|
538
539
|
setIsLoading(true)
|
|
540
|
+
setCacheStatus(null)
|
|
539
541
|
try {
|
|
540
542
|
const fallback: ProductsResponse = { items: [], total: 0, totalPages: 1 }
|
|
541
543
|
const call = await apiCall<ProductsResponse>(
|
|
@@ -546,10 +548,12 @@ export default function ProductsDataTable() {
|
|
|
546
548
|
if (!call.ok) {
|
|
547
549
|
const message = t('catalog.products.list.error.load', 'Failed to load products')
|
|
548
550
|
flash(message, 'error')
|
|
551
|
+
if (!cancelled) setCacheStatus(null)
|
|
549
552
|
return
|
|
550
553
|
}
|
|
551
554
|
const payload = call.result ?? fallback
|
|
552
555
|
if (cancelled) return
|
|
556
|
+
setCacheStatus(call.cacheStatus ?? null)
|
|
553
557
|
const items = Array.isArray(payload.items) ? payload.items : []
|
|
554
558
|
const normalized = items.filter((item): item is ProductRow => typeof item?.id === 'string')
|
|
555
559
|
setRows(normalized)
|
|
@@ -557,6 +561,7 @@ export default function ProductsDataTable() {
|
|
|
557
561
|
setTotalPages(typeof payload.totalPages === 'number' ? payload.totalPages : 1)
|
|
558
562
|
} catch (error) {
|
|
559
563
|
if (!cancelled) {
|
|
564
|
+
setCacheStatus(null)
|
|
560
565
|
const message =
|
|
561
566
|
error instanceof Error
|
|
562
567
|
? error.message
|
|
@@ -640,7 +645,9 @@ export default function ProductsDataTable() {
|
|
|
640
645
|
injectionContext={{
|
|
641
646
|
search,
|
|
642
647
|
filters: filterValues,
|
|
648
|
+
customFieldset: customFieldsetFilter,
|
|
643
649
|
page,
|
|
650
|
+
sorting,
|
|
644
651
|
scopeVersion,
|
|
645
652
|
}}
|
|
646
653
|
pagination={{
|
|
@@ -649,6 +656,7 @@ export default function ProductsDataTable() {
|
|
|
649
656
|
total,
|
|
650
657
|
totalPages,
|
|
651
658
|
onPageChange: setPage,
|
|
659
|
+
cacheStatus,
|
|
652
660
|
}}
|
|
653
661
|
exporter={exportConfig}
|
|
654
662
|
isLoading={isLoading}
|
|
@@ -25,6 +25,16 @@
|
|
|
25
25
|
"catalog.audit.variants.create": "Variante erstellen",
|
|
26
26
|
"catalog.audit.variants.delete": "Variante löschen",
|
|
27
27
|
"catalog.audit.variants.update": "Variante aktualisieren",
|
|
28
|
+
"catalog.bulkDelete.confirm": "Löschen",
|
|
29
|
+
"catalog.bulkDelete.error": "Produkte konnten nicht gelöscht werden.",
|
|
30
|
+
"catalog.bulkDelete.filtered.confirmText": "{count} Produkte löschen, die den aktuellen Filtern entsprechen? Dies kann nicht rückgängig gemacht werden.",
|
|
31
|
+
"catalog.bulkDelete.filtered.confirmTitle": "Gefilterte Produkte löschen?",
|
|
32
|
+
"catalog.bulkDelete.filtered.label": "Alle gefilterten löschen",
|
|
33
|
+
"catalog.bulkDelete.noneFiltered": "Keine Produkte entsprechen den aktuellen Filtern.",
|
|
34
|
+
"catalog.bulkDelete.noneSelected": "Wählen Sie mindestens ein Produkt zum Löschen aus.",
|
|
35
|
+
"catalog.bulkDelete.selected.confirmText": "{count} ausgewählte Produkte löschen? Dies kann nicht rückgängig gemacht werden.",
|
|
36
|
+
"catalog.bulkDelete.selected.confirmTitle": "Ausgewählte Produkte löschen?",
|
|
37
|
+
"catalog.bulkDelete.selected.label": "Ausgewählte löschen",
|
|
28
38
|
"catalog.categories.flash.created": "Kategorie erstellt",
|
|
29
39
|
"catalog.categories.flash.deleted": "Kategorie archiviert",
|
|
30
40
|
"catalog.categories.flash.updated": "Kategorie aktualisiert",
|
|
@@ -25,6 +25,16 @@
|
|
|
25
25
|
"catalog.audit.variants.create": "Create variant",
|
|
26
26
|
"catalog.audit.variants.delete": "Delete variant",
|
|
27
27
|
"catalog.audit.variants.update": "Update variant",
|
|
28
|
+
"catalog.bulkDelete.confirm": "Delete",
|
|
29
|
+
"catalog.bulkDelete.error": "Failed to delete products.",
|
|
30
|
+
"catalog.bulkDelete.filtered.confirmText": "Delete {count} products matching the current filters? This cannot be undone.",
|
|
31
|
+
"catalog.bulkDelete.filtered.confirmTitle": "Delete filtered products?",
|
|
32
|
+
"catalog.bulkDelete.filtered.label": "Delete all filtered",
|
|
33
|
+
"catalog.bulkDelete.noneFiltered": "No products match the current filters.",
|
|
34
|
+
"catalog.bulkDelete.noneSelected": "Select at least one product to delete.",
|
|
35
|
+
"catalog.bulkDelete.selected.confirmText": "Delete {count} selected products? This cannot be undone.",
|
|
36
|
+
"catalog.bulkDelete.selected.confirmTitle": "Delete selected products?",
|
|
37
|
+
"catalog.bulkDelete.selected.label": "Delete selected",
|
|
28
38
|
"catalog.categories.flash.created": "Category created",
|
|
29
39
|
"catalog.categories.flash.deleted": "Category archived",
|
|
30
40
|
"catalog.categories.flash.updated": "Category updated",
|
|
@@ -25,6 +25,16 @@
|
|
|
25
25
|
"catalog.audit.variants.create": "Crear variante",
|
|
26
26
|
"catalog.audit.variants.delete": "Eliminar variante",
|
|
27
27
|
"catalog.audit.variants.update": "Actualizar variante",
|
|
28
|
+
"catalog.bulkDelete.confirm": "Eliminar",
|
|
29
|
+
"catalog.bulkDelete.error": "No se pudieron eliminar los productos.",
|
|
30
|
+
"catalog.bulkDelete.filtered.confirmText": "¿Eliminar {count} productos que coinciden con los filtros actuales? Esta acción no se puede deshacer.",
|
|
31
|
+
"catalog.bulkDelete.filtered.confirmTitle": "¿Eliminar productos filtrados?",
|
|
32
|
+
"catalog.bulkDelete.filtered.label": "Eliminar todos los filtrados",
|
|
33
|
+
"catalog.bulkDelete.noneFiltered": "Ningún producto coincide con los filtros actuales.",
|
|
34
|
+
"catalog.bulkDelete.noneSelected": "Selecciona al menos un producto para eliminar.",
|
|
35
|
+
"catalog.bulkDelete.selected.confirmText": "¿Eliminar {count} productos seleccionados? Esta acción no se puede deshacer.",
|
|
36
|
+
"catalog.bulkDelete.selected.confirmTitle": "¿Eliminar productos seleccionados?",
|
|
37
|
+
"catalog.bulkDelete.selected.label": "Eliminar seleccionados",
|
|
28
38
|
"catalog.categories.flash.created": "Categoría creada",
|
|
29
39
|
"catalog.categories.flash.deleted": "Categoría archivada",
|
|
30
40
|
"catalog.categories.flash.updated": "Categoría actualizada",
|
|
@@ -25,6 +25,16 @@
|
|
|
25
25
|
"catalog.audit.variants.create": "Utwórz wariant",
|
|
26
26
|
"catalog.audit.variants.delete": "Usuń wariant",
|
|
27
27
|
"catalog.audit.variants.update": "Zaktualizuj wariant",
|
|
28
|
+
"catalog.bulkDelete.confirm": "Usuń",
|
|
29
|
+
"catalog.bulkDelete.error": "Nie udało się usunąć produktów.",
|
|
30
|
+
"catalog.bulkDelete.filtered.confirmText": "Usunąć {count} produktów pasujących do bieżących filtrów? Tej operacji nie można cofnąć.",
|
|
31
|
+
"catalog.bulkDelete.filtered.confirmTitle": "Usunąć przefiltrowane produkty?",
|
|
32
|
+
"catalog.bulkDelete.filtered.label": "Usuń wszystkie z filtrów",
|
|
33
|
+
"catalog.bulkDelete.noneFiltered": "Brak produktów pasujących do bieżących filtrów.",
|
|
34
|
+
"catalog.bulkDelete.noneSelected": "Wybierz co najmniej jeden produkt do usunięcia.",
|
|
35
|
+
"catalog.bulkDelete.selected.confirmText": "Usunąć {count} wybranych produktów? Tej operacji nie można cofnąć.",
|
|
36
|
+
"catalog.bulkDelete.selected.confirmTitle": "Usunąć wybrane produkty?",
|
|
37
|
+
"catalog.bulkDelete.selected.label": "Usuń wybrane",
|
|
28
38
|
"catalog.categories.flash.created": "Kategoria utworzona",
|
|
29
39
|
"catalog.categories.flash.deleted": "Kategoria zarchiwizowana",
|
|
30
40
|
"catalog.categories.flash.updated": "Kategoria zaktualizowana",
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { AwilixContainer } from 'awilix'
|
|
2
|
+
import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
|
|
3
|
+
import { createQueue, type Queue } from '@open-mercato/queue'
|
|
4
|
+
import { getRedisUrl } from '@open-mercato/shared/lib/redis/connection'
|
|
5
|
+
import type { ProgressService, ProgressServiceContext } from '../../progress/lib/progressService'
|
|
6
|
+
|
|
7
|
+
export const CATALOG_PRODUCT_BULK_DELETE_QUEUE = 'catalog-product-bulk-delete'
|
|
8
|
+
|
|
9
|
+
const queues = new Map<string, Queue<Record<string, unknown>>>()
|
|
10
|
+
|
|
11
|
+
export type CatalogProductBulkDeleteScope = {
|
|
12
|
+
organizationId: string
|
|
13
|
+
tenantId: string
|
|
14
|
+
userId?: string | null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type CatalogProductBulkDeleteJobPayload = {
|
|
18
|
+
progressJobId: string
|
|
19
|
+
ids: string[]
|
|
20
|
+
scope: CatalogProductBulkDeleteScope
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type CatalogProductBulkDeleteSummary = {
|
|
24
|
+
affectedCount: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getCatalogQueue(queueName: string): Queue<Record<string, unknown>> {
|
|
28
|
+
const existing = queues.get(queueName)
|
|
29
|
+
if (existing) return existing
|
|
30
|
+
|
|
31
|
+
const created = process.env.QUEUE_STRATEGY === 'async'
|
|
32
|
+
? createQueue<Record<string, unknown>>(queueName, 'async', {
|
|
33
|
+
connection: { url: getRedisUrl('QUEUE') },
|
|
34
|
+
concurrency: Math.max(1, Number.parseInt(process.env.CATALOG_QUEUE_CONCURRENCY ?? '3', 10) || 3),
|
|
35
|
+
})
|
|
36
|
+
: createQueue<Record<string, unknown>>(queueName, 'local')
|
|
37
|
+
|
|
38
|
+
queues.set(queueName, created)
|
|
39
|
+
return created
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function buildCommandContext(
|
|
43
|
+
scope: CatalogProductBulkDeleteScope,
|
|
44
|
+
container: AwilixContainer,
|
|
45
|
+
): CommandRuntimeContext {
|
|
46
|
+
return {
|
|
47
|
+
container,
|
|
48
|
+
auth: null,
|
|
49
|
+
organizationScope: {
|
|
50
|
+
selectedId: scope.organizationId,
|
|
51
|
+
filterIds: [scope.organizationId],
|
|
52
|
+
allowedIds: [scope.organizationId],
|
|
53
|
+
tenantId: scope.tenantId,
|
|
54
|
+
},
|
|
55
|
+
selectedOrganizationId: scope.organizationId,
|
|
56
|
+
organizationIds: [scope.organizationId],
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function deleteCatalogProductsWithProgress(params: {
|
|
61
|
+
container: AwilixContainer
|
|
62
|
+
progressJobId: string
|
|
63
|
+
ids: string[]
|
|
64
|
+
scope: CatalogProductBulkDeleteScope
|
|
65
|
+
}): Promise<CatalogProductBulkDeleteSummary> {
|
|
66
|
+
const { container, progressJobId, ids, scope } = params
|
|
67
|
+
const commandBus = container.resolve('commandBus') as CommandBus
|
|
68
|
+
const progressService = container.resolve('progressService') as ProgressService
|
|
69
|
+
const progressContext: ProgressServiceContext = {
|
|
70
|
+
tenantId: scope.tenantId,
|
|
71
|
+
organizationId: scope.organizationId,
|
|
72
|
+
userId: scope.userId,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
await progressService.startJob(progressJobId, progressContext)
|
|
76
|
+
await progressService.updateProgress(
|
|
77
|
+
progressJobId,
|
|
78
|
+
{ totalCount: ids.length, processedCount: 0 },
|
|
79
|
+
progressContext,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
const commandContext = buildCommandContext(scope, container)
|
|
83
|
+
let affectedCount = 0
|
|
84
|
+
|
|
85
|
+
for (const [index, id] of ids.entries()) {
|
|
86
|
+
await commandBus.execute<{ body?: Record<string, unknown> }, { productId: string }>('catalog.products.delete', {
|
|
87
|
+
input: { body: { id } },
|
|
88
|
+
ctx: commandContext,
|
|
89
|
+
})
|
|
90
|
+
affectedCount += 1
|
|
91
|
+
|
|
92
|
+
await progressService.updateProgress(
|
|
93
|
+
progressJobId,
|
|
94
|
+
{
|
|
95
|
+
totalCount: ids.length,
|
|
96
|
+
processedCount: index + 1,
|
|
97
|
+
},
|
|
98
|
+
progressContext,
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const summary: CatalogProductBulkDeleteSummary = { affectedCount }
|
|
103
|
+
await progressService.completeJob(progressJobId, { resultSummary: summary }, progressContext)
|
|
104
|
+
|
|
105
|
+
return summary
|
|
106
|
+
}
|